SCTF2023是本年度xctf的第二次分站赛,这是第一次出这样大型的比赛的题,头一次没打xctf,在观赛者的角度来看比赛进程,是另一种体验,既担心自己题出的太简单,又怕题没人做。
这次比赛出题时间并不多,因为中间还穿插着一些比赛,所以真正开始出题大概是在比赛开始前2天,通宵两天把题目出完,在晚上9点上题前甚至还在添加一些东西,可能有些师傅会觉得有些脑洞,给师傅们造成了不好的体验,在这给各位师傅磕头了orz。
做题情况:
看到师傅们都在通宵做题,还是很感动的。
题目描述:
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 -> 内核 -> 应用层验证结果
如果在网上搜,其实这种类型题就在RealWorld上出现过:https://bestwing.me/RWCTF-4th-TrustZone-challenge-Writeup.html这个题他的考点是求被加密的 FEK
,类似一个密码题,所以这次出题就换一个思路,原本思路是在optee框架下,build出一个rust的基于arm架构的ta文件,在上一些全局变量,顺便来点动调,把整个optee项目打包出题,不过由于时间问题,并没有完成大部分。在构建系统的时候踩过很多坑,接下来我在介绍的时候会尽量详细介绍,网上可阅读的解决方案实在少之又少。
项目文档:https://optee.readthedocs.io/en/latest/
OP-TEE 是一个可信执行环境 (TEE),旨在与在 Arm 上运行的非安全 Linux 内核配套使用;使用 TrustZone 技术的 Cortex-A 内核。
该项目的构建有两种方法:第一种是用repo直接懒人布置,第二种是手拉文件,逐项布置。
两种方式各有各的坏处,但综合起来第一种方式更好。首先如果选用repo来部署的话,会面临一个被墙的问题,即使在虚拟机里配代理,又或者是在服务器上拉都会遇到很大报错问题。手拉文件也会遇到很多问题,比如optee-client的make会报很多错误,网上并没有很多解决方案。
repo的安装:
1 2 3 4 5 |
|
接下来初始化repo:
1 2 3 |
|
在这里我拉取的是3.18.0版本的optee,而且用qemu_v8平台,建议换源来拉。
在.repo\manifests目录下可以看到qemu_v8.xml配置单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
接下来就是要下载配置单的过程:
1 2 |
|
在下载前呢,需要设置代理,这样更快,或者在qemu_v8清单中的revision后加上clone-depth="1",或者:
1 2 |
|
最后别忘了代理:
1 2 3 |
|
不过经过测验,这个拉取也是需要时间的,不管用服务器还是设代理有时候照样拉不了,多尝试几次就可以拉了,我是下午的时候拉成功的。
构建完的目录如下:
optee_example和optee_rust目录都是可以自己创建ta程序
如果某些文件实在拉不下来,就手拉,从qemu-v8里找,一个一个拉,但版本要一定。
接下来获取工具链:
1 2 |
实在不能获取,就只能手拉下载,解压即可
然后在build目录下:
之后能得到/out/bin文件夹下就是启动文件,设置run.sh启动
#!/bin/sh qemu-system-aarch64 \ -nographic \ -smp 2 \ -machine virt,secure=on,gic-version=3,virtualization=false \ -cpu cortex-a57 \ -d unimp -semihosting-config enable=on,target=native \ -m 1024 \ -bios bl1.bin \ -initrd rootfs.cpio.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" \ -no-reboot \ -monitor null
接下来是编译rust,在optee_rust里有许多测试用例:
(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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
重点是bj888文件,采取了魔改原有aes的方法:
main.c
/* * Copyright (c) 2017, Linaro Limited * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include <err.h> #include <stdio.h> #include <string.h> /* OP-TEE TEE client API (built by optee_client) */ #include <tee_client_api.h> /* For the UUID (found in the TA's h-file(s)) */ #include <bj888_ta.h> #define BJ888_TEST_BUFFER_SIZE 4096 #define BJ888_TEST_KEY_SIZE 16 #define BJ888_BLOCK_SIZE 16 #define DECODE 0 #define ENCODE 1 /* TEE resources */ struct test_ctx { TEEC_Context ctx; TEEC_Session sess; }; void prepare_tee_session(struct test_ctx *ctx) { TEEC_UUID uuid = TA_BJ888_UUID; uint32_t origin; TEEC_Result res; /* Initialize a context connecting us to the TEE */ res = TEEC_InitializeContext(NULL, &ctx->ctx); if (res != TEEC_SUCCESS) errx(1, "TEEC failed with code 0x%x", res); /* Open a session with the TA */ res = TEEC_OpenSession(&ctx->ctx, &ctx->sess, &uuid, TEEC_LOGIN_PUBLIC, NULL, NULL, &origin); if (res != TEEC_SUCCESS) errx(1, "TEEC failed with code 0x%x origin 0x%x", res, origin); } void terminate_tee_session(struct test_ctx *ctx) { TEEC_CloseSession(&ctx->sess); TEEC_FinalizeContext(&ctx->ctx); } void prepare_bj888(struct test_ctx *ctx, int encode) { TEEC_Operation op; uint32_t origin; TEEC_Result res; memset(&op, 0, sizeof(op)); op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, TEEC_VALUE_INPUT, TEEC_VALUE_INPUT, TEEC_NONE); op.params[0].value.a = TA_BJ888_ALGO_CTR; op.params[1].value.a = TA_BJ888_SIZE_128BIT; op.params[2].value.a = encode ? TA_BJ888_MODE_ENCODE : TA_BJ888_MODE_DECODE; res = TEEC_InvokeCommand(&ctx->sess, TA_BJ888_CMD_PREPARE, &op, &origin); if (res != TEEC_SUCCESS) errx(1, "TEEC_InvokeCommand failed 0x%x origin 0x%x", res, origin); } void set_key(struct test_ctx *ctx, char *key, size_t key_sz) { TEEC_Operation op; uint32_t origin; TEEC_Result res; memset(&op, 0, sizeof(op)); op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, TEEC_NONE, TEEC_NONE, TEEC_NONE); op.params[0].tmpref.buffer = key; op.params[0].tmpref.size = key_sz; res = TEEC_InvokeCommand(&ctx->sess, TA_BJ888_CMD_SET_KEY, &op, &origin); if (res != TEEC_SUCCESS) errx(1, "TEEC_InvokeCommand failed 0x%x origin 0x%x", res, origin); } void set_iv(struct test_ctx *ctx, char *iv, size_t iv_sz) { TEEC_Operation op; uint32_t origin; TEEC_Result res; memset(&op, 0, sizeof(op)); op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, TEEC_NONE, TEEC_NONE, TEEC_NONE); op.params[0].tmpref.buffer = iv; op.params[0].tmpref.size = iv_sz; res = TEEC_InvokeCommand(&ctx->sess, TA_BJ888_CMD_SET_IV, &op, &origin); /* if (res != TEEC_SUCCESS) errx(1, "TEEC_InvokeCommand failed 0x%x origin 0x%x", res, origin); */ } void cipher_buffer(struct test_ctx *ctx, char *in, char *out, size_t sz) { TEEC_Operation op; uint32_t origin; TEEC_Result res; memset(&op, 0, sizeof(op)); op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, TEEC_MEMREF_TEMP_OUTPUT, TEEC_NONE, TEEC_NONE); op.params[0].tmpref.buffer = in; op.params[0].tmpref.size = sz; op.params[1].tmpref.buffer = out; op.params[1].tmpref.size = sz; res = TEEC_InvokeCommand(&ctx->sess, TA_BJ888_CMD_CIPHER, &op, &origin); /* if (res != TEEC_SUCCESS) errx(1, "TEEC_InvokeCommand failed 0x%x origin 0x%x", res, origin); */ //const char *g = (const char*)(op.params[1].tmpref.buffer); //for(int i = 0; i < 32; i++){ //printf("%02x",g[i]); // } const char *greeting = (const char *)(op.params[0].tmpref.buffer); printf("%s\n",greeting); } int main(int argc, char *argv[]) { struct test_ctx ctx; char key[BJ888_TEST_KEY_SIZE] = "snbjklefsdcvfsyc"; char iv[BJ888_BLOCK_SIZE] = "snbjklefsdcvfsyc"; char clear[BJ888_TEST_BUFFER_SIZE]; char ciph[BJ888_TEST_BUFFER_SIZE]; char temp[BJ888_TEST_BUFFER_SIZE]; for( int i = 0; i < strlen(argv[1]); i++){ clear[i] = argv[1][i]; } if(strlen(argv[1]) != 27){ printf("wrong\n"); exit(1); } //printf("%s\n",clear); //printf("Prepare session with the TA\n"); prepare_tee_session(&ctx); //printf("Prepare encode operation\n"); prepare_bj888(&ctx, ENCODE); //printf("Load key in TA\n"); //memset(key, 0xa5, sizeof(key)); /* Load some dummy value */ set_key(&ctx, key, BJ888_TEST_KEY_SIZE); //printf("key:::%02x,%02x,%02x\n",key[0],key[1],key[15]); //printf("Reset ciphering operation in TA (provides the initial vector)\n"); //memset(iv, 0, sizeof(iv)); /* Load some dummy value */ set_iv(&ctx, iv, BJ888_BLOCK_SIZE); //printf("iv:::%02x,%02x,%02x\n",iv[0],iv[1],iv[15]); //memset(clear, 0x5a, sizeof(clear)); /* Load some dummy value */ cipher_buffer(&ctx, clear, ciph, BJ888_TEST_BUFFER_SIZE); terminate_tee_session(&ctx); return 0; }
bj888.ta
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
|
该加密是aes的ctr加密。
本题采用optee项目,项目文档地址:https://optee.readthedocs.io/en/latest/
该题采用arm架构,意在找出系统中关键加密文件,从而拿到flag
文件目录:
运行run.sh
输入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 2 3 |
|
从上述分析,我们只需要看bj888文件即可,先看ca文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
该main函数向ta文件传了一个字符串,并且判断字符串长度是否为27
接下来是045ccc45-ee83-43ec-b69f-121819c1ba6b.ta文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
|
可以看到主要逻辑是获取key和iv然后进行加密,再对比。如果有手动构建过optee的话,这个其实就是内置的aes的ctr加密魔改过来的,加密并没有魔改,只是将key和iv初始化在了host,下面可以直接用在线网站跑出结果:
当然,最好的方法是修改optee_example_aes文件中host目录下的main:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
然后编译运行,可以直接跑出flag:sctf{T3e_not_s4f3_anym0re!}
当然该题还可以进行动态调试,只需要把run.sh最后一行改为:
1 |
|
指定串口即可,然后run.sh运行,再用gdb连接,接下来下断点到文件加载处,便可以进行动态调试。
这次出题学到了很多,在赛后也受到许多大师傅的启发,之后会让题出的更有逻辑和思路,而不是一味的堆砌。在这次出题后,在平时需要进行沉淀和思考,不能急于求成。
题目链接:链接:https://pan.baidu.com/s/1kVAGU1RwJJ8iDUkPhqPpGA
提取码:8u9r
[2023春季班]《安卓高级研修班(网课)》月薪两万班招生中~
最后于 2023-6-20 16:43 被bj777编辑 ,原因: