异构学习
前言
异构的getshell与命令执行这方面一直都比较薄弱,虽然之前星盟里面国资师傅有过培训,但是内容都比较简单,在真实的iot设备方面的构造还是有所欠缺,所以这次重新学习一下这方面的内容
参考链接:
https://github.com/ReAbout/pwn-exercise-iot
arm架构
基础知识
arm函数调用约定
ARM 32位:
- 参数1-参数4 分别保存到 R0-R3 寄存器中 ,剩下的参数从右往左依次入栈,被调用者实现栈平衡,返回值存放在 R0 中。
- ARM中使用R0作为默认的返回值。 ARM 64位:
- 参数1-参数8 分别保存到 X0-X7 寄存器中 ,剩下的参数从右往左依次入栈,被调用者实现栈平衡,返回值存放在 X0 中。
arm汇编
https://b0ldfrev.gitbook.io/note/iot/mipsarm-hui-bian-xue-xi#arm
pwn例题:typo
binary:typo
两种方法
1.ret2shellcode
这种方法没记错的话只能在自己本地打通,因为是通过调试获取的栈地址会随着qemu发生变化,在远程大概率栈地址是不一样的
直接通过调试获得偏移地址,接着把shellcode写在栈上,然后覆盖返回地址为shellcode的地址就可以了
exp:
1 |
|
2.rop
svc:r7=0xb;R0=addr(“/bin/sh”);R1=0;R2=0
以上系统调用等同于execve(“/bin/sh”,0,0)
svc: 通过这条指令切换到 svc 模式(svc 替代了以前的 swi 指令,是 ARM 提供的系统调用指令),进入到软件中断处理函数( SWI handler )。
所以我们RoP目标状态如下:
- R0 = “/bin/sh”
- R1 = 0
- R2 = 0
- R7 = 0xb (对应arm下execve的系统调用)
- svc
1./bin/sh的地址可以通过ida直接找到:0x006C384
2.rop
在这里 pc 相当于x86的ret,构成gadget。
1 |
|
没有r2寄存器gadget,需要通过mov方式赋值,我们这有r4的,找个 mov r2 , r4
。再通过blx r3跳转回去
1 |
|
于是还需要找能够控制r3寄存器的gadget
1 |
|
最后找svc的gadget
1 |
|
至此所需要的gadget全部找到,就可以直接写rop了
exp:
1 |
|
mips架构
基础知识
MIPS 重要特性
- mips本身不支持NX
MIPS 函数调用约定
- 调用者将参数保存在寄存器 $a0 - $a3 中。其总共能保存4个参数。如果有更多的参数,或者有传值的结构,其将被保存在栈中。
- 调用者使用 jal 加上子程序的标记。返回地址保存在 $ra 中。
- 返回地址是 PC + 4,PC 是 jal 指令的地址。
- 如果被调用者使用框架指针,它通常将其设置为栈指针。旧的栈指针必须在之前被保存到栈中。
- 被调用者通常在开头将其需要使用的寄存器保存到栈中。如果被调用者调用了辅助子程序,必须将 $ra入栈,同时也必须将临时寄存器或被保留的寄存器入栈。
- 当子程序结束,返回值要保存在 $v0 - $v1 中。
- 被调用者使用 jr $ra 返回到调用者那里。
Ref:https://www.jianshu.com/p/79895392ecb2
MIPS 寄存器
寄存器编号 | 别名 | 用途 |
---|---|---|
$0 | $zero | 常量0(constant value 0) |
$1 | $at | 保留给汇编器(Reserved for assembler) |
$2-$3 | $v0-$v1 | 函数调用返回值(values for results and expression evaluation) |
$4-$7 | $a0-$a3 | 函数调用参数(arguments) |
$8-$15 | $t0-$t7 | 暂时的(或随便用的) |
$16-$23 | $s0-$s7 | 保存的(或如果用,需要SAVE/RESTORE的)(saved) |
$24-$25 | $t8-$t9 | 暂时的(或随便用的) |
$28 | $gp | 全局指针(Global Pointer) |
$29 | $sp | 堆栈指针(Stack Pointer) |
$30 | $fp/$s8 | 栈帧指针(Frame Pointer) |
$31 | $ra | 返回地址(return address) |
基础知识:https://b0ldfrev.gitbook.io/note/iot/mipsarm-hui-bian-xue-xi#mips
前期准备
ida的mipsrop插件:https://github.com/tacnetsol/ida
用ropper或者ROPgadget都不太好找gadget?
例题
HWS夏令营结营赛题:pwn
运行:
1 |
|
调试:
1 |
|
保护措施
分析题目代码有个很明显的memcpy造成的栈溢出
只是格式有点要求,直接调试不能直接获得偏移,会在下面这个地方卡住,具体原因就不太懂了
只能通过去找输入的地址以及最后给到ra寄存器值的地址然后计算出偏移
找到偏移之后就是利用mipsrop去找需要的gadget
思路大概是,找一个能将栈上的地址放进寄存器的gadget,再找一个对应的gadget可以jalr或者jr这个寄存器过去执行shellcode
1 |
|
首先找到这个0x004273C4这个gadget,可以将栈地址给到a2寄存器,然后去找能够jalr或者jr a2的gadget
1 |
|
很明显可以利用0x00421684这段gadget
然后就是想办法怎么执行第一段gadget1后接着执行gadget2,先看看这两端gadget
1 |
|
可以看到第一段gadget最后会jalr到$s0这个寄存器,而在pwn函数的最后刚好可以控制这个寄存器
经过调试确定偏移为0x6c,然后就是在程序最后sp+0x64的地方写上shellcode就可以getshell了
1 |
|
CVE-2020-3331复现
参考:https://github.com/ReAbout/pwn-exercise-iot/blob/main/linux_mips_stack/mips_iot_cc/pwn.md
上面链接有固件下载链接还有复现环境,但是不推荐使用他里面的复现环境(docker),因为版本太老的原因,会出现很多问题,不如直接自己使用qemu模拟环境
漏洞出现的位置:guest_logout_cgi函数当中对于sscanf对于submit_button参数的处理
sscanf 与我们熟悉的 scanf 非常类似,二者的区别在于:在调用 sscanf 时,需要将带解析的字符串作为第一个参数传入;而在调用 scanf 时,待解析的参数则是来自标准输入。为了更好地理解 sscanf 的功能,不妨先来看一个例子:
1 |
|
在本例中, sscanf 利用格式化字符串 “%d %d %d” 将 string 中的1、2、3分别赋值给了变量 a 、 b 和 c 。
sscanf 利用格式化字符串 “%[^;];%*[^=]=%[^\n]” 将 v11 分成了6个部分,其中 sub_string1 是一个不含 ‘;’ 的字符串(对应 %[^;] ),其值将被赋给 v29; sub_string2 是一个不含 ‘=’ 的字符串(对应 %*[^=] ,此处 ***** 起到舍弃该值的作用),其值将被丢弃; sub_string3 是一个不含 ‘\n’ 的字符串(对应 %[^\n] ),其值将被赋给 v28。例如,若 v11 = “AAAA;var=BBBB\n” ,则 v29 = “AAAA” 、 v28 = “BBBB” 。
而触发漏洞需要满足下面三个条件:
- v5 != NULL && v10 != NULL
- v5 是一个合法的MAC地址 && v10 是一个合法的IPv4地址
- v11 中需要包含子串 “status_guestnet.asp” 。
v5=get_cgi(“cmac”);
v10 = get_cgi(“cip”);
v11 = get_cgi(“submit_button”);
然后就可以想办法构造payload了,这个固件比较凑巧在函数最后返回的时候,a0刚好是前面sscanf的第一个参数,也就是在程序最后溢出控制返回地址为system时的第一个参数刚好就是我们前面的输入值
exp:
1 |
|