从零开始的 Pwn 之旅 异架构 Arm
Arm 汇编速探
函数调用约定
- TODO: 写一片文章介绍 arm 汇编相关知识
ARM32(AArch32)调用约定
前 4 个参数:
r0 ~ r3返回值:
r0多余参数:栈上传递,从右往左入栈
堆栈平衡:被调用函数负责
函数调用:
bl/b,返回用bx lr或mov pc, lr寄存器:
pc(程序计数器),lr(链接寄存器,保存返回地址)LDR指令LDR r0, [r1]:将r1指向的内存地址的值加载到r0LDR r0, [r1, #4]:将r1 + 4地址的值加载到r0LDR r0, [r1, r2]:将r1 + r2地址的值加载到r0
ARM64(AArch64)调用约定
- 前 8 个参数:
x0 ~ x7 - 返回值:
x0 - 多余参数:依然通过栈传递,但栈对齐要求更高(16 字节对齐),参数顺序依然是从右往左入栈
- 堆栈平衡:被调用函数负责
- 函数调用:
bl/bl <label>,返回用ret - 寄存器:
pc(程序计数器,通常不可直接访问)lr/x30(链接寄存器,保存返回地址)
主要区别
| 对比项 | ARM32 (AArch32) | ARM64 (AArch64) |
|---|---|---|
| 参数传递 | 4 个寄存器(r0~r3) | 8 个寄存器(x0~x7) |
| 返回值 | r0 | x0 |
| 多余参数 | 栈传递,右→左 | 栈传递,右→左,16字节对齐 |
| 返回指令 | bx lr / mov pc, lr | ret (等价于 ret x30) |
| 寄存器命名 | r0~r12, sp(r13), lr(r14), pc(r15) | x0~x30, sp, lr(x30), pc |
| 栈对齐 | 无特殊要求 | 16 字节对齐 |
例题
jarvisOJ_typo
| |
arm 32 位小端, 只开了 NX, 没有启用 ASLR, 也没有启用 canary, 静态链接
ida pro 反编译出了很多函数,start 函数无法反编译,我们来结合程序行为来确定 main 函数
搜索字符串 Let’s Do Some Typing Exercise~ 后得到
| |
发现函数都被去函数名了,分析很困难
我们考虑对程序进行输入测试
| |
发现崩溃点,尝试调试偏移距离
| |
静态链接的程序尝试使用 one_gadget
看起来不支持,我们只能自己寻找 我们尝试符号还原
网上给出方法使用 rizzo 进行符号还原 但是笔者折腾了一下午都没有成功 编译了 arm 版本的 glibc 2.27
笔者使用的编译命令如下:
| |
笔者编译出的 libc.a 的 system.o 中 do_system 实现
| |
程序中的 do_system 实现
| |
可以看到 ida pro 直接分析失败了
使用 rizzo 进行符号还原
| |
没有找到任何匹配
符号还原失败,我们可以尝试分析程序行为
Glibc 的 system 函数实现会调用 do_system 函数
do_system 函数会调用 execve 函数来执行 /bin/sh
system("echo 1"); 其实就是 /bin/sh -c "echo 1"
所以我们只需要交叉引用找到 /bin/sh 的调用者即可
可以看到 /bin/sh 的调用者是 sub_21654 函数
那么我们可以在 sub_21654 函数中就是 execve 函数
sub_21654 函数的上层函数 sub_10BA8 函数就是 do_system 函数
sub_10BA8 函数的上层函数 sub_110B4 就是 system 函数
找一下 gadget
| |
选用下面这条 0x00020904 : pop {r0, r4, pc}
payload:
| |
Shanghai2018_baby_arm
有 NX 栈不可执行
| |
两个输入点都存在栈溢出
| |
注意到是有 mprotect 函数, 考虑使用 mprotect 函数来修改 unk_411068 的权限
但是发现可利用的函数
但是最后的参数不对,我们寻找 gadget 尝试修改 这里的 W2 其实就是 X2 的低 32 位寄存器 寻找 gadget 发现基本没有可用的 gadget 但是有 csu
| |
payload:
| |
这边有个坑要注意以下
| |
笔者在构造参数时长度直接填了 0x1000, 导致 read 函数将内容写入未映射的内存, read 函数直接返回 -1, 没有进行数据读取
| |