mips/arm杂记
前言
总结一下arm/mips栈溢出的基本利用思路及uClibc环境下堆利用的思路。
安洵杯-mips_pwn
程序逻辑 && 漏洞利用
程序是mips32位-little的程序,拿qemu-mipsel-static启动,libc是拿西湖论剑的libc加载的。IDA不支持mips的反编译,因此我们用Ghidra看一下代码。看到vuln里有个栈溢出,在那之前用printf泄露出栈上残留的memcpy地址,进而泄露libc地址。vuln里栈溢出ROP。
1 |
|
replace(“\n”,”\r\n”).format(“A”344+”B”10+cyclic(190)+p32(ret_addr)+p32(0x0417aa0))
寻找gadgets
遇到的最大困难是寻找rop的gadget,我们希望调用system(“/bin/sh”),需要控制a0寄存器,我发现似乎没有很多的lw命令是直接从栈上拷数据到a0-a4的,一般都是拷到s*寄存器,比如拷贝到s0,再使用move指令拷贝到a1寄存器这样,所以我选择使用下面这条指令找到移动到a0的sx寄存器,再找从栈上移动到sx的指令。
1 | #0x00042648 : move $v0, $zero ; move $a0, $s0 ; move $t9, $s2 ; jalr $t9 |
exp.py
1 | #coding=utf-8 |
骇极杯_2018-babyarm
程序逻辑 & 漏洞利用
aarch64的程序,没有开PIE,基础栈溢出。在aarch64架构下,参数寄存器为x0-x7,其中w为x的低32位寄存器。调用指令有BL和BLR,BLR指将下一条指令的返回地址放在x30寄存器中。
1 | int __cdecl main_0(int argc, const char **argv, const char **envp) |
第一次输入时将shellcode写入到bss上,第二次利用rop调用mprotect将bss改为可执行的地址,再跳转到sc执行。这里和mips有点像,也是要寻找将stack数据加载到寄存器的命令,aarch中没有pop指令,替代它的是ldp指令,比如ldp x19,x20,[sp, #0x10]
就是将rsp+0x10处的16字节分别赋值给x19和x20。而诸如LDP X29, X30, [SP+var_s0],#0x40
的指令表示在赋值完毕之后sp的值增加0x40,这就有点类似于pop的指令。
这里通过ROPGadget --binary ./pwn --only 'ldp|ret'
并没有找到可控x0-x3的gadget,因此这里用arm下的csu来调用mprotect。
如下图所示,溢出之后在栈上有保存main的x29和x30的位置,覆写x30的值到csu_start,加载各种寄存器同时控制x30的值跳转到csu_end,x21为第一次输入的bss地址的某处,保存了mprotect@plt。这里我第一次尝试使用mprotect@got,但是后续会crash,原因不明,修改之后即可。再跳回到csu_start的时候调用保存在x30的sc_addr get shell。
1 | .text:00000000004008AC loc_4008AC ; CODE XREF: gadget+60↓j |
exp.payload
1 | #coding=utf-8 |
RCTF2020-mginx
mips64环境搭建
由于qemu-user模拟程序总会遇到各种问题,遂决定拿buildroot搭建一套完整的mips64做题环境,这个想法也是受ruan师傅
一篇博客的启发,由于师傅博客里只标注了几个关键的配置,我自己搭的时候还是踩了不少坑,因此发一份详细的配置及避坑指南。
首先再官网上下载buildroot源码,我这里用的是buildroot-2020.08.1版本。
解压缩之后进入buildroot文件夹,configs
文件夹下包含了buildroot支持的架构的config默认配置文件。我们执行make qemu_mips64_malta_defconfig
,即可得到一个mips64-big-endian
的默认配置文件,同时也是一个最小安装包。
由于做题中我们需要用到gdbserver和ncat,因此需要安装一些其他的工具。以下是比较关键的menuconfig配置选项。
- Target options已经设置了我们想要的格式,进去查看一下即可
- toolchain:需要启动WCHAR支持和c++支持,否则无法安装nmap包;需要跨架构的gdb
- System Configuration:原始配置
- Kernel:这里选择Custon version(内核版本可以查看configs里的qemu_mips64_malta_defconfig)
- Target packages:选择Show packages that are also provided by busybox(否则一些包看不到)。
- Target packages->debugging:选择dt/fio/gdb/strace
- arget packages->Networking applications:选择netcat/nmap
以上是我的配置,如果找不到某项配置可以使用/
进行搜索。
配置完成之后使用make -j6
进行编译,编译主要受网络速度影响,建议挂个代理。
编译完了之后在./output/images目录下生成了kernel文件vmlinux、启动脚本start-qemu.sh和文件系统rootfs.ext2。
编辑start-qemu.h,增加端口转发,在末尾加上-nic user,hostfwd=tcp::3333-:3333,hostfwd=tcp::5555-:5555
,启动之后进去目标目录,使用ncat -vc "gdbserver 0.0.0.0:5555 ./mginx" -kl 0.0.0.0 3333
启动gdbserver,在外面先拿exp去连127.0.0.1:3333
,之后使用gdb-multiarch ./mginx
以及target remote :5555
附加到进程上,进行调试,下面是调试界面。
btw,经过测试,peda+pwngdb去调试的话很多命令都用不了比如vmmap/stack,pwndbg好一点,最好用的是gef,所以目前我换上了gef的配置。
程序逻辑
程序模拟了一个http解析处理。拿ghidra看一下逻辑,发现在二次read调用计算sz时使用的sz为用户输入的sz和数据包content长度之和,缺失检查,因此构造包头中Content-Length:
为0xfff进行栈溢出。
1 |
|
漏洞利用
构造出包的解析规则之后在return的时候劫持控制流,这里有一个问题是iStack4164 == 0
条件如何成立,ghidra中调试发现Connection:
字段为keep-alive
的时候,会设置uStack4216 = 1
,后面虽然也没找到对这个flag处理的东西,不过到最后iStack4164
也会被设置为1,因此只要设置Connection
为no
即可退出循环,这点根据http包的字段含义也比较好理解。
最后溢出可控ra/fp/gp/sp寄存器的值,寻找gadget无果之后决定进行栈迁移,二次读取shellcode到bss上并跳转执行。
1 | 120001c94 df bf 10 b8 ld ra,0x10b8(sp) |
由于fp可控,因此buf位置可控,我们将其控制到bss上。此外gp
的值是各种函数寻址的一个依据,调试可以看到其为一个固定值0x12001a250
,照抄即可。最后经过二次溢出,可以在bss上部署shellcode。
1 | 67 C2 00 88 daddiu $v0, $fp, 0x10C0+var_1038 |
编写shellcode花了很久,主要原因是pwntools似乎没有mips的as,而我无论是拿mips64-linux-as还是buildroot自带的mips64-buildroot-as都只能编译出ELF32的。shell-storm有一个在线的Online Assembler,不过有一些指令也识别不出。radare
的rasm2
同理。
最后的最后我把uClibc的IDA汇编导出,寻找自己想写的汇编指令,再通过alt+t反查字节码,编写出了shellcode。
系统调用的规则和x86相似,这里在buildroot的lib/include里找到了unistd_n64.h
(可以直接拿find命令),其中__NR_Linux
宏的值为十进制5000
。系统调用的参数寄存器为a0/a1/a2,返回值存储到v0。
另外libc中搜到的syscall都是syscall 0
,实际中需要的是syscall 0x40404
,即字节码p32(0x0101010c)。
可以看到最后解析的字节码中t6/t7呢被换成了t2/t3,还是有些差异,不过不影响使用。这里的sw不可换成sd。
exp.py
由于发送的payload数量过大,可能数据会有缺失或者粘连,发送间隔需要久一点,需要多试几次。
1 | #coding=utf-8 |
强网杯2020-MipsGame
前言
首先安利一个插件ida2ghidra,可以增加数据和代码高亮,进行重命名等IDA常用的操作,代码看起来会简单一点。
程序逻辑 && 漏洞利用
程序模拟了一个httpd,accept_request函数中接收类http请求,需要花一些时间理清合法的header结构,在handle
函数中,会对请求做响应。
1 |
|
handle函数中有一些堆菜单题的基本操作,包括Add/Show/Del
。首先关注的就是这里的数据输入是使用strcpy进行赋值,气氛上可以off-by-null,不过测试之后发现并不可以,于是找了下别的洞,发现error_request函数中可以拷贝至多0x400数据至info,而Info在init函数中是通过malloc(0x200)
赋值的,因而存在堆溢出。
1 |
|
1 |
|
有了洞之后需要考虑uClibc的堆分配机制。通过查看libc中的一些字符串,对比源码文件的malloc-simple/malloc.c
、malloc-standard/malloc.c
及malloc/malloc.c
,可以看到使用的是malloc-standard
,其实现和早期dlmalloc差不多,所以一些经典的攻击都可以用,这里溢出之后可以直接改fastbin的fd来实现任意地址写(这里的分配没有sz的合法性检查)。
泄露地址的次数只有一次,这里选择泄露libc,先溢出构造chunk overlapping,释放ub之后切割即可leak libc。注意这里的fastbin的最大值为80,即0x50。另外由于泄露的内容是由strlen(buf)
决定的,大端架构下ub的fd是0x000000xx
开头,因此还得拿一次溢出填充零字节再泄露。
1 | /* The maximum fastbin request size we support */ |
get shell的方法和x86的方式不太一样,看源码之后发现没有__malloc_hook
和__free_hook
,但是libc也有类似got表的结构。这一点可以在libc中调用某个函数前看到,其调用方式较为固定,都是先使用ld t9,-0x6890(gp)
来load,之后再调用。而gp是一个固定的值,再调试一下即可发现这其实就类似于从got表中取函数指针的方式。
1 | .got:00000000000A9A08 sigprocmask_ptr_0:.dword sigprocmask |
1 | 0015d7d8 df 85 98 f8 ld a1=>pthread_mutex_unlock,-0x6708(gp)=>->pthrea = 001829b4 |
因此最后的利用思路是覆写libc中的某个got表,首先排除free,因为binary中的free@got已经写入了free@libc,因此修改也没有用,我们只能改libc内部会调用的函数。
查看源码之后发现我们可以控制munmap@got
为system
,而后将prev_size
改为-0x10
,从而使其free的对象指向data数据,而这里被赋值为/bin/sh\x00
,最终Get shell。
有一些检查需要绕过,一是munmap的chunk的sz要大于0x53;二是需要设置IS_MAPPED位。
1 | else if (!chunk_is_mmapped(p)) { |
调试小技巧
启动qemu之后使用echo 0 > /proc/sys/kernel/randomize_va_space & ncat -vc "gdbserver 0.0.0.0:5555 /m1/httpd" -kl 0.0.0.0 3333
关闭地址随机化并启动gdbserver,而后在gdb的断点可以下成可复用的断点,方便调试。
exp.py
1 | #coding=utf-8 |