MIPS栈溢出入门
这是去年HWS夏令营的一道MIPS栈溢出题目,当时连PWN都还没入门的我欠下的坑,如今一年后补坑,不过我也半年多没有真正意义上做一道PWN题了,现在重新捡起二进制,入坑IoT安全!
上题:没有开保护,是一个MIPS小端程序
MIPS汇编函数调用结构特点(举例这里main函数的):
我的理解是:相对于x86而言,进入函数调用后,MIPS是通过专门的寄存器来保存调用之前的状态,比如这里,通过sp寄存器来分配main函数要用到的栈空间,ra用来保存调用main函数完后的返回地址,如果main函数内部不再调用子函数了,那么ra就在保存main函数的返回地址上发挥了一次作用而已,因为最后会把之前压入的返回地址又还给ra,最后jr $ra跳转到返回地址。如果main函数内有调用子函数,那么ra不光是最后作为跳转到返回地址的中间桥梁,而且中途还帮助别的函数做了一次跳板。原理和x86都是一样的,只不过MIPS多了专门的寄存器用来保存帧栈,不像x86是通过压栈出栈的方式来保存变量的栈空间和返回地址的跳转。
1 | .text:00400AEC main: |
main汇编:
1 | .text:00400AEC # =============== S U B R O U T I N E ======================================= |
sub_400840汇编:
1 | .text:00400840 sub_400840: # CODE XREF: main+9C↓p |
sub_400978汇编:
1 | text:00400978 sub_400978: # CODE XREF: main+B0↓p |
Ida7.5能反汇编MIPS架构的C:
main
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
sub_400840:这里可知Username的前5个字节得为admin
1 | int sub_400840() |
sub_400978:这里的read(0,v2,36)发生了栈溢出,可以溢出覆盖v3(控制v4长度的变量)和v4
1 | int __fastcall sub_400978(int a1) |
上面三个函数可以分析出,main函数调用sub_400840后首先要求输入Username,开始的read函数并没有溢出,后面的strncmp判断要求前5个字符是admin才行,然后会将输入的字符的长度作为返回值传入sub_400978中,其中sub_400978的v3是第一次输入的字符次长度+4,也就是最大是30,紧接着要求输入Pre_Password,read的输入最大是36个字节长度,明显大于v2本身分配的20个字节,存在溢出漏洞,然后再要求输入一次password,最后的if可知第二次输入的和第三次输入的,分别前面得是access和0123456789,最后结束
漏洞利用思路:
对于第一次输入read,可以通过构造满23个字符+\n,使得’\0’没有空位,拼接到返回的地址,这样就能够通过printf(“%s”)打印泄露调用叶子函数之前的地址了
对于第二次输入read,可以通过溢出改变v3的值,因为v3是控制第三次输入字符串的长度,可以让第三次输入的字符串长度足够大溢出到覆盖返回地址并拼接shellcode
对于第三次输入read,将结合第一次输入泄露的返回地址,覆盖堆栈地址和返回地址
调试:
qemu模拟运行,开1234端口:
qemu-arm -g 1234 -L . ./Mplogin
gdb动态调试
set architecture mips
set endian little
target remote :1234
对main函数开头进行断点b *0x400aec
,c之后这样
结合前面的MIPS结构特点,啰嗦一次下面两条指令
第一条指令:0x400aec addiu $sp, $sp, -0x28
因为MIPS是通过sp寄存器来访问函数内部的变量等,所以通过减去0x28个字节来分配main函数需要的栈空间
第二条指令:0x400af0 sw $ra, 0x24($sp)
已经进入call main内部了,所以需要提前把前面ra所保存的地址给保存下来,毕竟程序不知道自己下一步还会不会调用一个相对而言的非叶子函数,如果调用的话,那么ra会用于非叶子函数,值会变为非叶子函数的返回地址,此时,前面对返回地址保存状态的意义就有了,因为到main函数快结束时,ra的值还是非叶子函数的返回地址,但因为有保存,所以ra会被重置为返回地址,再跳转到ra
对main函数调用sub_400840前进行断点
b *0x400B88
,c执行后,发现ra跟开始调用main函数时不一样,因为别忘了,题目在这之前调用过puts、printf等这类系统函数,ra可以说是上一次返回地址的一个历史记录
进入sub_400840中,执行到输入,第一次输入:adminaaaaaaaaaaaaaaaaaaa
,程序执行printf的时候会把返回地址给输出出来,这里的乱码就是返回地址(因为足够长,没有了\0,拼接到了栈中的返回地址,从而造成了泄露,具体可以看前面的反汇编出的C)
可以查看下输入的内存数据,发现的的确确就是返回地址0x40800b90,这里的0x40800510相当于x86的ebp,0x40800b90相当于esp了
返回地址:0x400B90
misel shellcode:https://www.exploit-db.com/shellcodes/35868
EXP:
1 | from pwn import * |