* 이 글은 stack frame의 구조를 알고 있어야 수월하게 읽을 수 있습니다.
* 이 글은 System V ABI (Application Binary Interface) AMD64 Version 1.0을 기준으로 작성되었습니다.
* 컴파일 command line: gcc test.c -o test -Wl,-z,relro -no-pie -fno-stack-protector -mmanual-endbr -O0 -ggdb
Last update: 6/25/2020
ROP Basic :: RET Overwrite
stack의 return address 값을 조작해 프로그램의 실행 흐름을 변경하는 기법이다.
일반적인 함수의 마지막에는 ret
명령어가 존재한다.
gdb-peda$ pd function
Dump of assembler code for function function: 0x0000000000401136 <+0>: push rbp 0x0000000000401137 <+1>: mov rbp,rsp 0x000000000040113a <+4>: nop 0x000000000040113b <+5>: pop rbp 0x000000000040113c <+6>: ret End of assembler dump. |
ret
는 pop rip
와 동일하다. 즉, 함수 stack의 return address 값을 바꾸면 instruction pointer 레지스터를 바꿀 수 있다.
먼저 프로그램이 정상적으로 실행되는 경우를 살펴보자.
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <stdint.h>
#include <stdlib.h> void shell() { system("/bin/sh"); } void function() { uint64_t s[1]; s[0] = shell; } int main() { function(); return 0; } |
function 함수의 ret
명령에 breakpoint를 설정하고 프로그램을 실행하면,
[----------------------------------registers-----------------------------------]
RAX: 0x401136 (<shell>: push rbp) RBX: 0x401170 (<__libc_csu_init>: endbr64) RCX: 0x401170 (<__libc_csu_init>: endbr64) RDX: 0x7ffedd2aafb8 --> 0x7ffedd2ad296 ("SHELL=/bin/bash") RSI: 0x7ffedd2aafa8 --> 0x7ffedd2ad280 ("/home/syine/temp/test") RDI: 0x1 RBP: 0x7ffedd2aaeb0 --> 0x0 RSP: 0x7ffedd2aaea8 --> 0x401169 (<main+14>: mov eax,0x0) RIP: 0x40115a (<function+17>: ret) R8 : 0x0 R9 : 0x7f97c4230d50 (endbr64) R10: 0x0 R11: 0x0 R12: 0x401050 (<_start>: endbr64) R13: 0x7ffedd2aafa0 --> 0x1 R14: 0x0 R15: 0x0 EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x401154 <function+11>: mov QWORD PTR [rbp-0x8],rax 0x401158 <function+15>: nop 0x401159 <function+16>: pop rbp => 0x40115a <function+17>: ret 0x40115b <main>: push rbp 0x40115c <main+1>: mov rbp,rsp 0x40115f <main+4>: mov eax,0x0 0x401164 <main+9>: call 0x401149 <function> [------------------------------------stack-------------------------------------] 0000| 0x7ffedd2aaea8 --> 0x401169 (<main+14>: mov eax,0x0) 0008| 0x7ffedd2aaeb0 --> 0x0 0016| 0x7ffedd2aaeb8 --> 0x7f97c40350b3 (<__libc_start_main+243>: mov edi,eax) 0024| 0x7ffedd2aaec0 --> 0x7f97c424c620 --> 0x5042200000000 0032| 0x7ffedd2aaec8 --> 0x7ffedd2aafa8 --> 0x7ffedd2ad280 ("/home/syine/temp/test") 0040| 0x7ffedd2aaed0 --> 0x100000000 0048| 0x7ffedd2aaed8 --> 0x40115b (<main>: push rbp) 0056| 0x7ffedd2aaee0 --> 0x401170 (<__libc_csu_init>: endbr64) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x000000000040115a in function () at test.c:11 11 } |
다음에 실행될 코드의 주소인 main+14 (0x401169) 가 rsp (0x7ffedd2aaea8) 에 저장되어 있다.
만약 이 값을 변경한다면 function 함수 실행 후 원하는 주소로 실행 흐름을 변경할 수 있을 것이다.
아래와 같이 function의 s[2]에 shell 함수의 주소를 쓰고 프로그램을 실행해 본다. s[2]는 function 함수 stack의 return address 주소를 가리킨다.
(shell+4를 쓰는 이유는 stack의 16byte alignment를 맞춰주기 위함이다.)
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <stdint.h>
#include <stdlib.h> void gadget() { __asm ( "pop %rdi\n\t" "ret\n\t" ); } void function() { uint64_t s[1]; s[2] = (uint64_t)gadget + 4; s[3] = (uint64_t)"/bin/sh"; s[4] = (uint64_t)gadget + 5; s[5] = (uint64_t)system; } int main() { function(); return 0; } |
아까와 마찬가지로 function 함수의 ret
명령에 breakpoint를 설정하고 프로그램을 실행하면,
[----------------------------------registers-----------------------------------]
RAX: 0x40113a (<shell+4>: lea rdi,[rip+0xec3] # 0x402004) RBX: 0x401180 (<__libc_csu_init>: endbr64) RCX: 0x401180 (<__libc_csu_init>: endbr64) RDX: 0x7ffe27e295f8 --> 0x7ffe27e2b296 ("SHELL=/bin/bash") RSI: 0x7ffe27e295e8 --> 0x7ffe27e2b280 ("/home/syine/temp/test") RDI: 0x1 RBP: 0x7ffe27e294f0 --> 0x0 RSP: 0x7ffe27e294e8 --> 0x40113a (<shell+4>: lea rdi,[rip+0xec3] # 0x402004) RIP: 0x40115e (<function+21>: ret) R8 : 0x0 R9 : 0x7f6f3cd1dd50 (endbr64) R10: 0x0 R11: 0x0 R12: 0x401050 (<_start>: endbr64) R13: 0x7ffe27e295e0 --> 0x1 R14: 0x0 R15: 0x0 EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x401158 <function+15>: mov QWORD PTR [rbp+0x8],rax 0x40115c <function+19>: nop 0x40115d <function+20>: pop rbp => 0x40115e <function+21>: ret 0x40115f <main>: push rbp 0x401160 <main+1>: mov rbp,rsp 0x401163 <main+4>: mov eax,0x0 0x401168 <main+9>: call 0x401149 <function> [------------------------------------stack-------------------------------------] 0000| 0x7ffe27e294e8 --> 0x40113a (<shell+4>: lea rdi,[rip+0xec3] # 0x402004) 0008| 0x7ffe27e294f0 --> 0x0 0016| 0x7ffe27e294f8 --> 0x7f6f3cb220b3 (<__libc_start_main+243>: mov edi,eax) 0024| 0x7ffe27e29500 --> 0x7f6f3cd39620 --> 0x5042200000000 0032| 0x7ffe27e29508 --> 0x7ffe27e295e8 --> 0x7ffe27e2b280 ("/home/syine/temp/test") 0040| 0x7ffe27e29510 --> 0x100000000 0048| 0x7ffe27e29518 --> 0x40115f (<main>: push rbp) 0056| 0x7ffe27e29520 --> 0x401180 (<__libc_csu_init>: endbr64) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x000000000040115e in function () at test.c:11 11 } |
rsp에 저장된 값이 shell의 주소+4 인 0x40113a임을 확인할 수 있다.
ROP Basic :: Return to Shellcode
지금까지 스택의 ret을 수정해 프로그램의 흐름을 변경할 수 있다는 사실을 짚어봤다. 만약 ret에 원하는 위치를 쓸 수 있고 그 위치에 shellcode를 쓸 수 있다면, 공격자가 원하는 코드를 자유자재로 실행할 수 있게 된다.
아래의 코드는 function의 ret에 shellcode의 주소를 적어 넣어서 execve("/bin/sh"); 를 실행한다.
* 아래 코드는 DEP를 해제하기 위해 컴파일 옵션에 -z execstack 을 추가해야 합니다.
1
2 3 4 5 6 7 8 9 10 11 12 13 |
#include <stdint.h>
char shellcode[27] = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"; void function() { uint64_t s[1]; s[2] = (uint64_t)shellcode; } int main() { function(); return 0; } |
function 함수의 ret
명령에 breakpoint를 설정하고 프로그램을 실행한다.
[----------------------------------registers-----------------------------------]
RAX: 0x404030 --> 0x91969dd1bb48c031 RBX: 0x401130 (<__libc_csu_init>: endbr64) RCX: 0x401130 (<__libc_csu_init>: endbr64) RDX: 0x7ffc0bdfd408 --> 0x7ffc0bdff296 ("SHELL=/bin/bash") RSI: 0x7ffc0bdfd3f8 --> 0x7ffc0bdff280 ("/home/syine/temp/test") RDI: 0x1 RBP: 0x7ffc0bdfd300 --> 0x0 RSP: 0x7ffc0bdfd2f8 --> 0x404030 --> 0x91969dd1bb48c031 RIP: 0x401117 (<function+17>: ret) R8 : 0x0 R9 : 0x7fdd27daad50 (endbr64) R10: 0x0 R11: 0x0 R12: 0x401020 (<_start>: endbr64) R13: 0x7ffc0bdfd3f0 --> 0x1 R14: 0x0 R15: 0x0 EFLAGS: 0x206 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x401111 <function+11>: mov QWORD PTR [rbp+0x8],rax 0x401115 <function+15>: nop 0x401116 <function+16>: pop rbp => 0x401117 <function+17>: ret 0x401118 <main>: push rbp 0x401119 <main+1>: mov rbp,rsp 0x40111c <main+4>: mov eax,0x0 0x401121 <main+9>: call 0x401149 <function> [------------------------------------stack-------------------------------------] 0000| 0x7ffc0bdfd2f8 --> 0x404030 --> 0x91969dd1bb48c031 0008| 0x7ffc0bdfd300 --> 0x0 0016| 0x7ffc0bdfd308 --> 0x7fdd27baf0b3 (<__libc_start_main+243>: mov edi,eax) 0024| 0x7ffc0bdfd310 --> 0x7fdd27dc6620 --> 0x5042200000000 0032| 0x7ffc0bdfd318 --> 0x7ffc0bdfd3f8 --> 0x7ffc0bdff280 ("/home/syine/temp/test") 0040| 0x7ffc0bdfd320 --> 0x100000000 0048| 0x7ffc0bdfd328 --> 0x401118 (<main>: push rbp) 0056| 0x7ffc0bdfd330 --> 0x401130 (<__libc_csu_init>: endbr64) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x0000000000401117 in function () at test.c:8 8 } |
rsp에 shellcode의 주소인 0x404030이 있다. ret
명령어 이후 rip가 0x404030으로 이동하고 shellcode를 실행시켜 공격자가 shell을 얻을 수 있다.
하지만, -z execstack 옵션을 넣지 않고 컴파일한 뒤 실행하면 아래와 같이 Segmentation Violation signal을 받는다.
[-------------------------------------code-------------------------------------]
0x40402a: add BYTE PTR [rax],al 0x40402c: add BYTE PTR [rax],al 0x40402e: add BYTE PTR [rax],al => 0x404030 <shellcode>: xor eax,eax 0x404032 <shellcode+2>: movabs rbx,0xff978cd091969dd1 0x40403c <shellcode+12>: neg rbx 0x40403f <shellcode+15>: push rbx 0x404040 <shellcode+16>: push rsp [------------------------------------stack-------------------------------------] 0000| 0x7ffff74f16b0 --> 0x0 0008| 0x7ffff74f16b8 --> 0x7efccad320b3 (<__libc_start_main+243>: mov edi,eax) 0016| 0x7ffff74f16c0 --> 0x7efccaf49620 --> 0x5042200000000 0024| 0x7ffff74f16c8 --> 0x7ffff74f17a8 --> 0x7ffff74f2280 ("/home/syine/temp/test") 0032| 0x7ffff74f16d0 --> 0x100000000 0040| 0x7ffff74f16d8 --> 0x401118 (<main>: push rbp) 0048| 0x7ffff74f16e0 --> 0x401130 (<__libc_csu_init>: endbr64) 0056| 0x7ffff74f16e8 --> 0xb9871b12d8c0b6b5 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x0000000000404030 in shellcode () |
이는 -z execstack 옵션을 넣지 않으면 DEP (NX bit)가 활성화되기 때문이다.
아래는 프로세스의 메모리 매핑 상태를 나타낸 것으로, 첫 번째는 -z execstack 옵션을 넣었을 때, 두 번째는 넣지 않았을 때이다. 옵션을 추가하면 프로세스의 .data section과 stack, libc, linker에 RWX 권한이 있는 것을 확인할 수 있다.
gdb-peda$ vmmap
Start End Perm Name 0x00400000 0x00403000 r-xp /home/syine/temp/test 0x00403000 0x00404000 r-xp /home/syine/temp/test 0x00404000 0x00405000 rwxp /home/syine/temp/test 0x00007f7ebdf45000 0x00007f7ebe12c000 r-xp /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x00007f7ebe12c000 0x00007f7ebe12d000 ---p /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x00007f7ebe12d000 0x00007f7ebe130000 r-xp /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x00007f7ebe130000 0x00007f7ebe133000 rwxp /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x00007f7ebe133000 0x00007f7ebe139000 rwxp mapped 0x00007f7ebe156000 0x00007f7ebe182000 r-xp /usr/lib/x86_64-linux-gnu/ld-2.31.so 0x00007f7ebe183000 0x00007f7ebe184000 r-xp /usr/lib/x86_64-linux-gnu/ld-2.31.so 0x00007f7ebe184000 0x00007f7ebe185000 rwxp /usr/lib/x86_64-linux-gnu/ld-2.31.so 0x00007f7ebe185000 0x00007f7ebe186000 rwxp mapped 0x00007fff78aa9000 0x00007fff78aca000 rwxp [stack] 0x00007fff78af0000 0x00007fff78af3000 r--p [vvar] 0x00007fff78af3000 0x00007fff78af4000 r-xp [vdso] 0xffffffffff600000 0xffffffffff601000 --xp [vsyscall] |
gdb-peda$ vmmap
Start End Perm Name 0x00400000 0x00401000 r--p /home/syine/temp/test 0x00401000 0x00402000 r-xp /home/syine/temp/test 0x00402000 0x00403000 r--p /home/syine/temp/test 0x00403000 0x00404000 r--p /home/syine/temp/test 0x00404000 0x00405000 rw-p /home/syine/temp/test 0x00007fa2e6cc8000 0x00007fa2e6ced000 r--p /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x00007fa2e6ced000 0x00007fa2e6e65000 r-xp /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x00007fa2e6e65000 0x00007fa2e6eaf000 r--p /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x00007fa2e6eaf000 0x00007fa2e6eb0000 ---p /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x00007fa2e6eb0000 0x00007fa2e6eb3000 r--p /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x00007fa2e6eb3000 0x00007fa2e6eb6000 rw-p /usr/lib/x86_64-linux-gnu/libc-2.31.so 0x00007fa2e6eb6000 0x00007fa2e6ebc000 rw-p mapped 0x00007fa2e6ed9000 0x00007fa2e6eda000 r--p /usr/lib/x86_64-linux-gnu/ld-2.31.so 0x00007fa2e6eda000 0x00007fa2e6efd000 r-xp /usr/lib/x86_64-linux-gnu/ld-2.31.so 0x00007fa2e6efd000 0x00007fa2e6f05000 r--p /usr/lib/x86_64-linux-gnu/ld-2.31.so 0x00007fa2e6f06000 0x00007fa2e6f07000 r--p /usr/lib/x86_64-linux-gnu/ld-2.31.so 0x00007fa2e6f07000 0x00007fa2e6f08000 rw-p /usr/lib/x86_64-linux-gnu/ld-2.31.so 0x00007fa2e6f08000 0x00007fa2e6f09000 rw-p mapped 0x00007ffc6f77d000 0x00007ffc6f79e000 rw-p [stack] 0x00007ffc6f7e6000 0x00007ffc6f7e9000 r--p [vvar] 0x00007ffc6f7e9000 0x00007ffc6f7ea000 r-xp [vdso] 0xffffffffff600000 0xffffffffff601000 --xp [vsyscall] |
여기에서, NX bit가 활성화되면 공격의 자유도가 매우 크게 제한된다는 사실을 알 수 있다.
하지만 공격자는 여전히 원하는 코드를 실행시킬 수 있는 방법이 있다. 바로 ROP gadget을 사용하는 방법이다.
ROP Basic :: Return Oriented Programming
ROP gadget은 프로세스 바이너리나 라이브러리의 실행 가능한 코드 조각의 일부분으로, ret
으로 끝나는 명령어 집합을 나타낸다. 대표적인 예시로 pop rdi ; ret
, pop rsi ; ret
등이 있다.
jmp
로 끝나는 코드 조각을 사용하는 것 역시 상황에 따라 가능하며, 이런 경우의 기법은 JOP (Jump Oriented Programming) 라고 한다.
System V ABI AMD64 는 함수의 인자를 rdi, rsi, rdx, rcx, r8, r9 순서로 전달하는 프로토콜을 사용한다.
아래 코드를 보고 ROP gadget이 어떤 방식으로 동작하는지 확인해 보도록 한다. 이 코드는 stack의 ret에 pop rdi ; ret
gadget의 주소를 쓰고, ret+8에는 "/bin/sh" 의 주소, ret+0x10에는 ret
gadget의 주소 (stack alignment를 맞춰주기 위함), ret+0x18에는 system의 주소를 쓴다.
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <stdint.h>
#include <stdlib.h> void gadget() { __asm ( "pop %rdi\n\t" "ret\n\t" ); } void function() { uint64_t s[1]; s[2] = (uint64_t)gadget + 4; s[3] = (uint64_t)"/bin/sh"; s[4] = (uint64_t)gadget + 5; s[5] = (uint64_t)system; } int main() { function(); return 0; } |
컴파일하고 function의 ret
명령어에 breakpoint를 설정한 뒤 실행한다.
[-------------------------------------code-------------------------------------]
0x401143 <function+52>: mov QWORD PTR [rbp+0x20],rax 0x401147 <function+56>: nop 0x401148 <function+57>: pop rbp => 0x401149 <function+58>: ret 0x40114a <main>: push rbp 0x40114b <main+1>: mov rbp,rsp 0x40114e <main+4>: mov eax,0x0 0x401153 <main+9>: call 0x40110f <function> [------------------------------------stack-------------------------------------] 0000| 0x7ffe4449e0d8 --> 0x40110a (<gadget+4>: pop rdi) 0008| 0x7ffe4449e0e0 --> 0x402004 --> 0x68732f6e69622f ('/bin/sh') 0016| 0x7ffe4449e0e8 --> 0x40110b (<gadget+5>: ret) 0024| 0x7ffe4449e0f0 --> 0x7f8b0297c410 (<__libc_system>: endbr64) 0032| 0x7ffe4449e0f8 --> 0x7ffe4449e1d8 --> 0x7ffe4449f280 ("/home/syine/temp/test") 0040| 0x7ffe4449e100 --> 0x100000000 0048| 0x7ffe4449e108 --> 0x40114a (<main>: push rbp) 0056| 0x7ffe4449e110 --> 0x401160 (<__libc_csu_init>: endbr64) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x0000000000401149 in function () at test.c:17 17 } |
rsp에 pop rdi ; ret
gadget의 주소 0x40110a가 저장되어 있다.
[-------------------------------------code-------------------------------------]
0x401104 <frame_dummy+4>: jmp 0x401090 <register_tm_clones> 0x401106 <gadget>: push rbp 0x401107 <gadget+1>: mov rbp,rsp => 0x40110a <gadget+4>: pop rdi 0x40110b <gadget+5>: ret 0x40110c <gadget+6>: nop 0x40110d <gadget+7>: pop rbp 0x40110e <gadget+8>: ret [------------------------------------stack-------------------------------------] 0000| 0x7ffe4449e0e0 --> 0x402004 --> 0x68732f6e69622f ('/bin/sh') 0008| 0x7ffe4449e0e8 --> 0x40110b (<gadget+5>: ret) 0016| 0x7ffe4449e0f0 --> 0x7f8b0297c410 (<__libc_system>: endbr64) 0024| 0x7ffe4449e0f8 --> 0x7ffe4449e1d8 --> 0x7ffe4449f280 ("/home/syine/temp/test") 0032| 0x7ffe4449e100 --> 0x100000000 0040| 0x7ffe4449e108 --> 0x40114a (<main>: push rbp) 0048| 0x7ffe4449e110 --> 0x401160 (<__libc_csu_init>: endbr64) 0056| 0x7ffe4449e118 --> 0x410502de1da5d1b [------------------------------------------------------------------------------] Legend: code, data, rodata, value gadget () at test.c:5 5 __asm( gdb-peda$ reg rdi RDI: 0x1 |
pop rdi
를 실행하면 rsp에 위치한 0x402004 값이 rdi에 들어가게 된다. ret
를 실행하면 다음 gadget의 주소인 gadget+5 로 rip가 설정된다.
인자 2개를 넘겨주려면 pop rdi ; pop rsi ; ret
gadget을 사용할 수 있고, 3개는 pop rdi ; pop rsi ; pop rdx ; ret
gadget을 사용할 수도 있다.
아래는 각각 인자 2개와 인자 3개를 필요로 하는 함수들을 ROP로 호출하는 코드이다.
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <stdint.h>
#include <stdio.h> void gadget() { __asm ( "pop %rdi\n\t" "pop %rsi\n\t" "ret\n\t" ); } void function() { uint64_t s[1]; s[2] = (uint64_t)gadget + 4; s[3] = "calling fputs\n"; s[4] = stderr; s[5] = fputs; } int main() { function(); return 0; } |
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <stdint.h>
#include <unistd.h> void gadget() { __asm ( "pop %rdi\n\t" "pop %rsi\n\t" "pop %rdx\n\t" "ret\n\t" ); } char *argv[2] = {"cat", "flag"}; void function() { uint64_t s[1]; s[2] = (uint64_t)gadget + 4; s[3] = "/bin/cat"; s[4] = argv; s[5] = 0uLL; s[6] = execve; } int main() { function(); return 0; } |
실제로 공격에 필요한 ROP gadget이 적절하게 주어지는 경우는 매우 드물기 때문에 gadget을 나눠서 써야 하는 경우가 있다.
1. 인자 3개를 설정해야 하는데 pop rdi ; pop rsi ; pop rdx ; ret
가 없는 경우, pop rdi ; pop rsi ; ret
과 pop rdx ; ret
을 사용
2. 인자 2개를 설정해야 하는데 pop rdi ; pop rdx ; pop rcx ; pop rsi ; ret
가 있는 경우, rdx와 rcx는 임의의 값으로 채우고 rdi와 rsi를 설정
ROP Basic :: ROP Chain
어떤 상황에서는 ROP로 함수를 2개 이상 호출해야 할 필요가 있다. 64bit에서는 함수가 레지스터를 통해 전달되기 때문에, 특별히 다른 작업을 할 필요는 없고 ROP payload를 연속으로 배치하면 된다.
아래 예시는 ROP로 read를 사용해 전역변수 buf에 최대 0x10의 입력을 받고 puts로 출력한다.
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <stdint.h>
#include <stdio.h> #include <unistd.h> char buf[0x10]; void gadget() { __asm ( "pop %rdx\n\t" "pop %rsi\n\t" "pop %rdi\n\t" "ret\n\t" ); } void function() { uint64_t s[1]; s[2] = (uint64_t)gadget + 4; s[3] = 0x10uLL; s[4] = &buf; s[5] = 0uLL; s[6] = read; s[7] = (uint64_t)gadget + 6; s[8] = &buf; s[9] = puts; } int main() { function(); return 0; } |
How to Find ROP Gadgets
바이너리의 코드를 한 줄씩 살펴가면서 gadget을 찾을 수도 있지만, 몇몇 도구들은 gadget을 알아서 잘 찾아 준다.
1. gdb
프로그램이 실행 중인 상태에서 ropgadget을 입력하면 자주 사용되는 gadget의 주소를 알려준다.
gdb-peda$ help ropgadget
Get common ROP gadgets of binary or library Usage: ropgadget [mapname] gdb-peda$ ropgadget ret = 0x40101a popret = 0x4010ed pop2ret = 0x40110b pop3ret = 0x40110a addesp_8 = 0x401017 gdb-peda$ ropgadget libc addesp_1 = 0x7ff8ae310845 addesp_2 = 0x7ff8ae330085 ret = 0x7ff8ae30c679 addesp_4 = 0x7ff8ae3b3ff5 popret = 0x7ff8ae30c6c0 pop2ret = 0x7ff8ae30c6bf pop3ret = 0x7ff8ae30c6be addesp_8 = 0x7ff8ae30ea19 addesp_16 = 0x7ff8ae30e524 addesp_20 = 0x7ff8ae32ca15 addesp_24 = 0x7ff8ae30db66 addesp_28 = 0x7ff8ae31a358 addesp_32 = 0x7ff8ae32cb76 addesp_36 = 0x7ff8ae32cc31 addesp_40 = 0x7ff8ae30f1f7 addesp_44 = 0x7ff8ae319c7d addesp_48 = 0x7ff8ae31b4df addesp_52 = 0x7ff8ae3f85ee addesp_56 = 0x7ff8ae31023a addesp_60 = 0x7ff8ae37b3d9 addesp_64 = 0x7ff8ae33bdeb ... |
2. rp++
https://github.com/0vercl0k/rp
-r 옵션의 인자로 gadget의 최대 명령어의 개수를 지정해 탐색 범위를 설정할 수 있다.
아래는 ROP Basic :: RET Overwrite 의 첫 번째 코드를 컴파일한 바이너리에서 pop 명령어가 포함된 크기 2의 gadget들을 탐색한 결과이다.
syine@MinetaLinux:~/temp rp-lin-x64 -f ./test -r 2 | grep -i "pop"
0x0040116c: add byte [rax], al ; pop rbp ; ret ; (1 found) 0x0040111b: add byte [rcx], al ; pop rbp ; ret ; (1 found) 0x00401116: mov byte [0x0000000000404030], 0x00000001 ; pop rbp ; ret ; (1 found) 0x00401169: mov eax, 0x00000000 ; pop rbp ; ret ; (1 found) 0x00401146: nop ; pop rbp ; ret ; (1 found) 0x00401158: nop ; pop rbp ; ret ; (1 found) 0x004011d0: pop r14 ; pop r15 ; ret ; (1 found) 0x004011d2: pop r15 ; ret ; (1 found) 0x0040111d: pop rbp ; ret ; (1 found) 0x00401147: pop rbp ; ret ; (1 found) 0x00401159: pop rbp ; ret ; (1 found) 0x0040116e: pop rbp ; ret ; (1 found) 0x004011d3: pop rdi ; ret ; (1 found) 0x004011d1: pop rsi ; pop r15 ; ret ; (1 found) |
'Pwn > Techniques' 카테고리의 다른 글
[Linux] ptmalloc2 Heap Exploitation :: fastbin dup with consolidation (0) | 2020.06.26 |
---|---|
[Linux] ptmalloc2 Heap Exploitation :: fastbin corruption (0) | 2020.06.26 |
[Linux] ptmalloc2 Heap Exploitation :: fastbin dup (0) | 2020.06.25 |
[Linux/x86] Return Oriented Programming (ROP) - x86 32bit (0) | 2020.06.25 |