SCTF2023 逆向 SycTee 出题与解题思路
1.前言
SCTF2023是本年度xctf的第二次分站赛,这是第一次出这样大型的比赛的题,头一次没打xctf,在观赛者的角度来看比赛进程,是另一种体验,一方面担心自己的题目没有人做,另一方面也会担心自己的题目是不是过于简单了。
这次比赛出题时间并不多,因为中间还穿插着一些比赛,所以真正开始出题大概是在比赛开始前2天,通宵两天把题目出完,在晚上9点上题前甚至还在添加一些东西,可能有些师傅会觉得有些脑洞,给师傅们造成了不好的体验,在这给各位师傅磕头了orz。
2.做题情况&题目描述
做题情况:
看到师傅们都在通宵做题,还是很感动的。
题目描述:
3.出题思路
1.TEE和TA
TEE的全称trusted execution environment,它是移动设备(智能手机、平板电脑、智能电视)CPU上的一块区域。这块区域的作用是给数据和代码的执行提供一个更安全的空间,并保证它们的机密性和完整性。TEE提供了一个与REE隔离的环境保存用户的敏感信息,TEE可以直接获取REE的信息,而REE不能获取TEE的信息。
而该题的出题思路就是旨在构建一个tee的系统。
TA(Trusted Application)是TEE中完成特定功能的应用,也叫做可信应用程序。由于TEE中完成计算因此具有较高的安全性。每一个TA在REE中有一个或者多个对应的CA,在REE环境中可以通过调用CA的接口,将信息传送到TEE环境中执行TA,完成对应功能然后返回计算结果。
了解了tee是什么,还应该了解一下一些基本名词:
CA(Client APP):对应一些上层应用,通过调用TEE Client API实现与TEE环境的交互。
REE Communication Agent:为TA和CA之间的消息传递提供了REE支持
TEE Client API:是REE中的TEE驱动程序提供给外部的接口,可以使运行在REE中的CA能够与运行在TEE中的TA交换数据。
TEE Communication Agent:是可信操作系统的特殊组成部分,它与REE Communication Agent一起工作,使TA与CA之间安全地传输消息。
TEE Internal Core API:是TEE操作系统提供给TA调用的内部接口,包括密码学算法,内存管理等功能。
Trusted Device Drivers:可信设备驱动程序,为专用于TEE的可信外设提供通信接口。
Shared Memory:是一块只有CA和TA可以访问的一块安全内存,CA和TA通过共享内存来快速有效传输指令和数据
CA与TA交互流程:CA首先调用TEE Client API触发系统调用,进入REE的操作系统内核态,根据CA调用的参数找到对应的REE驱动程序,REE驱动程序通过调用SMC汇编指令进入Monitor模式,并将处理器切换到安全内核状态,进入安全模式。切换进入TEE以后,CA的服务请求通过总线传到TEE侧,然后TEE OS通过TEE Internal API调用对应的TA,最后TA运行结束后将运行结果和数据返回给CA,执行完以后回到TEE内核态调用SMC汇编指令进入Monitor切回REE环境。
其实用更简单的理解就是:应用层输入 -> 内核 -> TA -> 内核 -> 应用层验证结果
2.思路
如果在网上搜,其实这种类型题就在RealWorld上出现过:https://bestwing.me/RWCTF-4th-TrustZone-challenge-Writeup.html这个题他的考点是求被加密的 FEK
,类似一个密码题,所以这次出题就换一个思路,原本思路是在optee框架下,build出一个rust的基于arm架构的ta文件,在上一些全局变量,顺便来点动调,把整个optee项目打包出题,不过由于时间问题,并没有完成大部分。在构建系统的时候踩过很多坑,接下来我在介绍的时候会尽量详细介绍,网上可阅读的解决方案实在少之又少。
OPTEE
项目文档:https://optee.readthedocs.io/en/latest/
OP-TEE 是一个可信执行环境 (TEE),旨在与在 Arm 上运行的非安全 Linux 内核配套使用;使用 TrustZone 技术的 Cortex-A 内核。
该项目的构建有两种方法:第一种是用repo直接懒人布置,第二种是手拉文件,逐项布置。
两种方式各有各的坏处,但综合起来第一种方式更好。首先如果选用repo来部署的话,会面临一个被墙的问题,即使在虚拟机里配代理,又或者是在服务器上拉都会遇到很大报错问题。手拉文件也会遇到很多问题,比如optee-client的make会报很多错误,网上并没有很多解决方案。
环境构建:
repo的安装:
1 | mkdir ~/bin |
接下来初始化repo:
1 | cd optee |
在这里我拉取的是3.18.0版本的optee,而且用qemu_v8平台,建议换源来拉。
在.repo\manifests目录下可以看到qemu_v8.xml配置单。
1 |
|
接下来就是要下载配置单的过程:
1 | //-j是开启多线程, 不加也可以 |
在下载前呢,需要设置代理,这样更快,或者在qemu_v8清单中的revision后加上clone-depth=”1”,或者:
1 | //将配置单换成.git速度更快 |
最后别忘了代理:
1 | export https_proxy=http://127.0.0.1:7890 |
不过经过测验,这个拉取也是需要时间的,不管用服务器还是设代理有时候照样拉不了,多尝试几次就可以拉了,我是下午的时候拉成功的。
构建完的目录如下:
optee_example和optee_rust目录都是可以自己创建ta程序
如果某些文件实在拉不下来,就手拉,从qemu-v8里找,一个一个拉,但版本要一定。
接下来获取工具链:
1 | cd build |
实在不能获取,就只能手拉下载,解压即可
然后在build目录下:
1 | make |
之后能得到/out/bin文件夹下就是启动文件,设置run.sh启动
1 |
|
接下来是编译rust,在optee_rust里有许多测试用例:
1 | (cd build && make toolchains && make OPTEE_RUST_ENABLE=y CFG_TEE_RAM_VA_SIZE=0x00300000) |
不过当时编译了一天一直报错,首先是helloworld-rs有错误,删除之后再编译就会出现缺少glibc库,解决方案还没有思路,不过应该要看一下makefile是怎么做的。
源码:
bj666将数据全部存储在了ta文件中,不需要host的main进行传输数据,用了rc4,输入key就能得到right。
1 | static TEE_Result www(uint32_t param_types, |
重点是bj888文件,采取了魔改原有aes的方法:
main.c
1 | /* |
bj888.ta
1 | /* |
该加密是aes的ctr加密。
4.解题思路:
本题采用optee项目,项目文档地址:https://optee.readthedocs.io/en/latest/
该题采用arm架构,意在找出系统中关键加密文件,从而拿到flag
文件目录:
输入test进入测试,或者root进入系统,主要关注/usr/bin下可执行程序,由于optee环境中optee_example_*开头文件是源项目optee_expample文件夹下所编译文件,再对比源库,甚至可以从名字看出optee_example_bj666,optee_example_bj777,optee_example_bj888这写文件是后来写的文件
其实每一个文件都是有深意的,如果仔细看过optee_example_bj666代码,其实只需要找到ta文件就能逆向,而optee_example_bj777文件,是通过ca向ta传入key才可以逆向。上述两个文件也就演示了一些可信文件的基本操作。
找到可疑ca文件后,如何定位到ta文件也是考点之一,ta文件在/lib/optee_armtz目录下,以uuid开头,两种方法定位到对应uuid,第一种可以遍历目录下文件,查看字符串(如文件名bj777或者wrong)这种关键字符串。第二种通过strace可以轻松拿到对应ta文件的uuid:
1 | bj666: 4194350e-9204-4348-ac59-ace9f0c055af |
从上述分析,我们只需要看bj888文件即可,先看ca文件
1 | __int64 __fastcall sub_A30(__int64 a1, __int64 a2) |
该main函数向ta文件传了一个字符串,并且判断字符串长度是否为27
接下来是045ccc45-ee83-43ec-b69f-121819c1ba6b.ta文件
1 | __int64 __fastcall sub_1DC(int *a1, unsigned int a2, int a3, _QWORD *a4) |
可以看到主要逻辑是获取key和iv然后进行加密,再对比。如果有手动构建过optee的话,这个其实就是内置的aes的ctr加密魔改过来的,加密并没有魔改,只是将key和iv初始化在了host,下面可以直接用在线网站跑出结果:
当然,最好的方法是修改optee_example_aes文件中host目录下的main:
1 | int main(void) |
然后编译运行,可以直接跑出flag:sctf{T3e_not_s4f3_anym0re!}
当然该题还可以进行动态调试,只需要把run.sh最后一行改为:
1 | -monitor null -serial tcp:localhost:port1 -serial tcp:localhost:port2 -s -S |
指定串口即可,然后run.sh运行,再用gdb连接,接下来下断点到文件加载处,便可以进行动态调试。
5.总结
这次出题学到了很多,在赛后也受到许多大师傅的启发,之后会让题出的更有逻辑和思路,而不是一味的堆砌。在这次出题后,在平时需要进行沉淀和思考,不能急于求成。