题目是一道关于ARM TEE中Trusted Application的Pwnable题目,其中的TEE OS是OP-TEE OS,整体架构如下

image.png-155.1kB

题目的源码在https://github.com/Nautilus-Institute/quals-2022/tree/main/teedium-wallet,在serialize_tx中调用serialize_sighash时没有对原buffer进行realloc,导致可能发生堆溢出,但是只能溢出4个字节,并且是固定值1。

作者已经放出了Exploit,因此主要记录一下调试的过程。调试的难点主要在于OP-TEE OS和TA都开启了ASLR,其中OP-TEE kernel的加载基址是0xe100000,按照https://github.com/ForgeRock/optee-build/blob/master/docs/debug.md#15-debugging-ta中的步骤想要调试TA可以先在tee_thread_enter_user_mode下断,根据汇编对比确定TEE OS(bl32_extra1.bin)中的tee_thread_enter_user_mode0xE101CD8。为了调试可以先patch掉kernel加载时的aslr种子。另外Normal World和Secure World都有日志输出,在Secure World的日志中可以得到TA的加载地址,为了获取日志还需要修改qemu的启动参数:

./qemu-system-arm \
    -nographic \
    -monitor /dev/null \
    -smp 2 \
    -machine virt,secure=on \
    -cpu cortex-a15 \
    -semihosting-config enable=on,target=native \
    -m 1057 \
    -bios bl1.bin \
    -object rng-random,filename=/dev/urandom,id=rng0 \
    -device virtio-rng-pci,rng=rng0,max-bytes=1024,period=1000 \
    -netdev user,id=vmnic -device virtio-net-device,netdev=vmnic \
    -no-reboot \
    -s \
    -S \
    -serial tcp:localhost:54320 -serial tcp:localhost:54321 \ #主要是这一行
    -fsdev local,security_model=none,id=fsdev0,path=/test/optee_examples/hello_world/host \
    -device virtio-9p-device,fsdev=fsdev0,mount_tag=hostshare # 最后两行用于跟host共享目录,guest还需要执行mount hostshare -t 9p /mnt/host

在启动qemu前先启动日志监听nc -z 127.0.0.1 54320 || python3 soc_term.py 54320nc -z 127.0.0.1 54321 || python3 soc_term.py 54321。在启动qemu后就可以使用gdb在0xE101CD8下断:

图片.png-342.9kB

执行exp后每次切换到TEE的User Mode时都会在0xE101CD8断下来,第一次是ldelf,第二次是题目的TA

图片.png-549.7kB

从日志获得了TA的基地址后就可以下断对TA进行调试了。

下面分析一下exp,optee的TA中使用的堆管理器是bget allocator,exp中的利用方式是在free时伪造了一个freed的堆块,在进行合并unlink时,把栈上的fp修改到了输入的buffer,然后迁移栈进行rop。

当释放被溢出的堆块时,由于prevfree为1,当前堆块的前一堆块会被认为处于free的状态,另外由于bget把分配了的堆块的大小用负数表示,处于释放的堆块的大小用正数表示,因此合并完前一个堆块后,它的大小是一个非常大的数

图片.png-633kB

之后在计算后一个堆块时相当于加上负数指向了伪造的堆块

图片.png-637.7kB

由于伪造的堆块的bsize大于0因此会认为后一个堆块也处于free状态,把后一个堆块也进行合并,在unlink时把栈上的fp修改到了输入的buffer,最后迁移栈进行rop:

图片.png-645.7kB

rop调用syscall获取flag,并复制到输出的buffer:

图片.png-629.4kB

另外TA开启了ASLR的情况下,通过观察发现只有TA本身的加载基址会变,变化的范围应该是通过get_pad_begin确定,看默认配置是(0x0-0x80)*0x1000,ARMv7的User Mode的虚拟地址范围的基址应该是0x100000,最后再贴一个TA的内存布局:

E/LD:  region  0: va 0x00102000 pa 0x0e300000 size 0x002000 flags rw-s (ldelf)
E/LD:  region  1: va 0x00104000 pa 0x0e302000 size 0x00b000 flags r-xs (ldelf)
E/LD:  region  2: va 0x0010f000 pa 0x0e30d000 size 0x001000 flags rw-s (ldelf)
E/LD:  region  3: va 0x00110000 pa 0x0e30e000 size 0x004000 flags rw-s (ldelf)
E/LD:  region  4: va 0x00114000 pa 0x0e312000 size 0x001000 flags r--s
E/LD:  region  5: va 0x00115000 pa 0x0e34b000 size 0x001000 flags rw-s (stack)
E/LD:  region  6: va 0x00182000 pa 0x00001000 size 0x02a000 flags r-xs [0] # TA Code
E/LD:  region  7: va 0x001ac000 pa 0x0002b000 size 0x00e000 flags rw-s [0] # TA Data
E/LD:  region  8: va 0x00200000 pa 0x40b90c78 size 0x002000 flags rw-- (param) # 输出buffer
E/LD:  region  9: va 0x00202000 pa 0x40bbc060 size 0x006000 flags rw-- (param) # 输入buffer