0%

SCTF2023逆向SycTee出题与解题思路

SCTF2023 逆向 SycTee 出题与解题思路

1.前言

SCTF2023是本年度xctf的第二次分站赛,这是第一次出这样大型的比赛的题,头一次没打xctf,在观赛者的角度来看比赛进程,是另一种体验,一方面担心自己的题目没有人做,另一方面也会担心自己的题目是不是过于简单了。

这次比赛出题时间并不多,因为中间还穿插着一些比赛,所以真正开始出题大概是在比赛开始前2天,通宵两天把题目出完,在晚上9点上题前甚至还在添加一些东西,可能有些师傅会觉得有些脑洞,给师傅们造成了不好的体验,在这给各位师傅磕头了orz。

2.做题情况&题目描述

做题情况:

image-20230619141751294

image-20230619141808893

看到师傅们都在通宵做题,还是很感动的。

题目描述:

image-20230619141825357

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
2
3
4
5
mkdir ~/bin
PATH=~/bin:$PATH
//下载工具并执行
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo

接下来初始化repo:

1
2
3
cd optee
mkdir optee
repo init -u https://github.com/OP-TEE/manifest.git -m qemu_v8.xml --repo-url=https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/ -b 3.18.0

在这里我拉取的是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
29
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<remote name="github" fetch="https://github.com" />
<remote name="tfo" fetch="https://git.trustedfirmware.org" />

<default remote="github" revision="master" clone-depth="1"/>

<!-- OP-TEE gits -->
<project path="optee_client" name="OP-TEE/optee_client.git" revision="refs/tags/3.8.0" clone-depth="1" />
<project path="optee_os" name="OP-TEE/optee_os.git" revision="refs/tags/3.8.0" clone-depth="1" />
<project path="optee_test" name="OP-TEE/optee_test.git" revision="refs/tags/3.8.0" clone-depth="1" />
<project path="build" name="OP-TEE/build.git" revision="refs/tags/3.8.0" clone-depth="1">
<linkfile src="qemu_v8.mk" dest="build/Makefile" />
</project>

<!-- linaro-swg gits -->
<project path="linux" name="linaro-swg/linux.git" revision="9823b258b332b4ac98e05fa23448bbc9e937b24c" clone-depth="1" />
<project path="optee_benchmark" name="linaro-swg/optee_benchmark.git" revision="refs/tags/3.8.0" clone-depth="1"/>
<project path="optee_examples" name="linaro-swg/optee_examples.git" revision="refs/tags/3.8.0" clone-depth="1" />
<project path="soc_term" name="linaro-swg/soc_term.git" revision="5493a6e7c264536f5ca63fe7511e5eed991e4f20" clone-depth="1" />

<!-- Misc gits -->
<project path="buildroot" name="buildroot/buildroot.git" revision="95942f5fcd35d783a49adce621ccf33480f1c88c" clone-depth="1" />
<project path="edk2" name="tianocore/edk2.git" revision="dd4cae4d82c7477273f3da455084844db5cca0c0" clone-depth="1" />
<project path="mbedtls" name="ARMmbed/mbedtls.git" revision="refs/tags/mbedtls-2.16.0" clone-depth="1" />
<project path="qemu" name="qemu/qemu.git" revision="refs/tags/v3.1.0-rc3" clone-depth="1" />
<project path="trusted-firmware-a" name="TF-A/trusted-firmware-a.git" revision="34efb683e32254b8c325ac3071c5776d243a7b99" remote="tfo" />
</manifest>

接下来就是要下载配置单的过程:

1
2
//-j是开启多线程, 不加也可以
repo sync -j8

在下载前呢,需要设置代理,这样更快,或者在qemu_v8清单中的revision后加上clone-depth=”1”,或者:

1
2
//将配置单换成.git速度更快
sed -i "s/\.git//g" .repo/manifest.xml

最后别忘了代理:

1
2
3
export https_proxy=http://127.0.0.1:7890 
http_proxy=http://127.0.0.1:7890
all_proxy=socks5://127.0.0.1:7890

不过经过测验,这个拉取也是需要时间的,不管用服务器还是设代理有时候照样拉不了,多尝试几次就可以拉了,我是下午的时候拉成功的。

构建完的目录如下:

image-20230619164151741

optee_example和optee_rust目录都是可以自己创建ta程序

如果某些文件实在拉不下来,就手拉,从qemu-v8里找,一个一个拉,但版本要一定。

接下来获取工具链:

1
2
cd build
make toolchains

实在不能获取,就只能手拉下载,解压即可

然后在build目录下:

1
make

之后能得到/out/bin文件夹下就是启动文件,设置run.sh启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/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里有许多测试用例:

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
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
static TEE_Result www(uint32_t param_types,
TEE_Param params[4])
{
uint32_t exp_param_types = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INOUT,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE);

//who需要爆破
const char *who = (const char *)(params[0].memref.buffer);
const size_t GREETING_LEN = strlen(who);
if (param_types != exp_param_types)
return TEE_ERROR_BAD_PARAMETERS;
char *buf = TEE_Malloc(strlen(who) + GREETING_LEN + 1, 0);

if (!buf)
{
return TEE_ERROR_OUT_OF_MEMORY;
}
int data[34] = {0x73,0x63,0x74,0x66,0x7b,0x54,0x65,0x33,0x26,0x54,0x41,0x5f,0x69,0x73,0x5f,0x73,0x61,0x66,0x33,0x5f,0x62,0x75,0x74,0x5f,0x46,0x41,0x4b,0x45,0x5f,0x46,0x4c,0x41,0x47,0x7d};
enc_dec(who,data);
int cmp[34] = {0x8e,0xd6,0x93,0x67,0x84,0xce,0xd2,0x7d,0xcb,0x9a,0xb7,0xa8,0x65,0xe6,0x97,0x80,0x63,0x26,0x74,0x7c,0xdf,0xcd,0x3a,0x8b,0x9f,0x38,0x9e,0x9f,0x7a,0xd4,0x9d,0xfe,0x36,0x88};
for(int i = 0; i < strlen(data); i++){
if(cmp[i] != data[i])
{
sprintf(buf, "wrong %s", who);
params[0].memref.size = strlen(buf) + 1;
TEE_MemMove(params[0].memref.buffer, buf, params[0].memref.size);
TEE_Free(buf);
return TEE_ERROR_BAD_PARAMETERS;
}
}
char *buf1 = TEE_Malloc(strlen(who) + GREETING_LEN + 1, 0);
sprintf(buf1, "right but it is a test ^_^%s", who);
params[0].memref.size = strlen(buf1) + 1;
TEE_MemMove(params[0].memref.buffer, buf1, params[0].memref.size);
TEE_Free(buf1);
return TEE_SUCCESS;
}

重点是bj888文件,采取了魔改原有aes的方法:

main.c

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
200
201
202
203
204
205
206
207
208
209
210
/*
* 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
200
/*
* 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 <inttypes.h>
#include<string.h>
#include <tee_internal_api.h>
#include <tee_internal_api_extensions.h>

#include <bj888_ta.h>

#define BJ888128_KEY_BIT_SIZE 128
#define BJ888128_KEY_BYTE_SIZE (BJ888128_KEY_BIT_SIZE / 8)
#define BJ888256_KEY_BIT_SIZE 256
#define BJ888256_KEY_BYTE_SIZE (BJ888256_KEY_BIT_SIZE / 8)


static TEE_Result set_bj888_key(void *session, uint32_t param_types,
TEE_Param params[4])
{
const uint32_t exp_param_types =
TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE);
struct bj888_cipher *sess;
TEE_Attribute attr;
TEE_Result res;
uint32_t key_sz1;
char *key1;

/* Get ciphering context from session ID */
DMSG("Session %p: load ", session);
sess = (struct bj888_cipher *)session;

/* Safely get the invocation parameters */
if (param_types != exp_param_types)
return TEE_ERROR_BAD_PARAMETERS;

key1 = params[0].memref.buffer;
key_sz1 = params[0].memref.size;

if (key_sz1 != sess->key_size) {
EMSG("Wrong size %" PRIu32 ", expect %" PRIu32 " bytes",
key_sz1, sess->key_size);
return TEE_ERROR_BAD_PARAMETERS;
}

/*
* Load the key material into the configured operation
* - create a secret key attribute with the key material
* TEE_InitRefAttribute()
* - reset transient object and load attribute data
* TEE_ResetTransientObject()
* TEE_PopulateTransientObject()
* - load the key (transient object) into the ciphering operation
* TEE_SetOperationKey()
*
* TEE_SetOperationKey() requires operation to be in "initial state".
* We can use TEE_ResetOperation() to reset the operation but this
* API cannot be used on operation with key(s) not yet set. Hence,
* when allocating the operation handle, we load a dummy key.
* Thus, set_key sequence always reset then set key on operation.
*/
//bj888默认一个key
//char *key = "snbjklefsdcvfsyc";
//uint32_t key_sz = 16;
TEE_InitRefAttribute(&attr, TEE_ATTR_SECRET_VALUE, key1, key_sz1);

TEE_ResetTransientObject(sess->key_handle);
res = TEE_PopulateTransientObject(sess->key_handle, &attr, 1);
if (res != TEE_SUCCESS) {
EMSG("TEE failed, %x", res);
return res;
}

TEE_ResetOperation(sess->op_handle);
res = TEE_SetOperationKey(sess->op_handle, sess->key_handle);
if (res != TEE_SUCCESS) {
EMSG("TEE failed %x", res);
return res;
}

return res;
}

/*
* Process command TA_BJ888_CMD_SET_IV. API in bj888_ta.h
*/
static TEE_Result reset_bj888_iv(void *session, uint32_t param_types,
TEE_Param params[4])
{
const uint32_t exp_param_types =
TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE);
struct bj888_cipher *sess;
size_t iv_sz1;
char *iv1;

/* Get ciphering context from session ID */
DMSG("Session %p: no hint", session);
sess = (struct bj888_cipher *)session;

/* Safely get the invocation parameters */
if (param_types != exp_param_types)
return TEE_ERROR_BAD_PARAMETERS;

iv1 = params[0].memref.buffer;
iv_sz1 = params[0].memref.size;

/*
* Init cipher operation with the initialization vector.
*/
//char *iv = "snbjklefsdcvfsyc";
//uint32_t iv_sz = 16;
TEE_CipherInit(sess->op_handle, iv1, iv_sz1);

return TEE_SUCCESS;
}

/*
* Process command TA_BJ888_CMD_CIPHER. API in bj888_ta.h
*/
static TEE_Result cipher_buffer(void *session, uint32_t param_types,
TEE_Param params[4])
{
const uint32_t exp_param_types =
TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT,
TEE_PARAM_TYPE_MEMREF_OUTPUT,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE);
struct bj888_cipher *sess;

/* Get ciphering context from session ID */
DMSG("Session %p: no hints", session);
sess = (struct bj888_cipher *)session;

/* Safely get the invocation parameters */
if (param_types != exp_param_types)
return TEE_ERROR_BAD_PARAMETERS;

if (params[1].memref.size < params[0].memref.size) {
EMSG("Bad sizes: in %d, out %d", params[0].memref.size,
params[1].memref.size);
return TEE_ERROR_BAD_PARAMETERS;
}

if (sess->op_handle == TEE_HANDLE_NULL)
return TEE_ERROR_BAD_STATE;
const char *cipher = (const char *)(params[0].memref.buffer);
const size_t cipher_len = strlen(cipher);
/*
* Process ciphering operation on provided buffers
*/
TEE_CipherUpdate(sess->op_handle,
params[0].memref.buffer, params[0].memref.size,
params[1].memref.buffer, &params[1].memref.size);
const char *en = (const char *)(params[1].memref.buffer);
const size_t en_len = 27;
//char cmp[] = {0x20,0x0b,0x1c,0xff,0xc5,0xa4,0x0a,0xfe,0x31,0x10,0x9d,0x67,0xfe,0xf5,0x60,0xa9,0x9e,0xc6,0x44,0x93,0x36,0xa4,0xfd,0xe9,0x37,0x9d,0x07,0xf1,0x50,0xcc,0x84,0x95};
char cmp[27] = {0x25,0x03,0x0a,0x6c,0xf8,0xb1,0xce,0x7f,0xc9,0x42,0x0c,0x0d,0x68,0xb3,0x1c,0x04,0x64,0xfa,0xe5,0xa4,0x22,0xd4,0x2c,0xff,0x4e,0x36,0x2a};
for(int i = 0; i < 27; i++){
if(cmp[i] != en[i])
{
char *buf = "wrong";
TEE_MemMove(params[0].memref.buffer, buf, 6);
TEE_Free(buf);
return TEE_ERROR_BAD_PARAMETERS;
}
}
char *buf1 = "right";
TEE_MemMove(params[0].memref.buffer, buf1, 6);
TEE_Free(buf1);
return TEE_SUCCESS;
}

该加密是aes的ctr加密。

4.解题思路:

本题采用optee项目,项目文档地址:https://optee.readthedocs.io/en/latest/

该题采用arm架构,意在找出系统中关键加密文件,从而拿到flag

文件目录:

6

运行run.sh

5

输入test进入测试,或者root进入系统,主要关注/usr/bin下可执行程序,由于optee环境中optee_example_*开头文件是源项目optee_expample文件夹下所编译文件,再对比源库,甚至可以从名字看出optee_example_bj666,optee_example_bj777,optee_example_bj888这写文件是后来写的文件

4

其实每一个文件都是有深意的,如果仔细看过optee_example_bj666代码,其实只需要找到ta文件就能逆向,而optee_example_bj777文件,是通过ca向ta传入key才可以逆向。上述两个文件也就演示了一些可信文件的基本操作。

3

找到可疑ca文件后,如何定位到ta文件也是考点之一,ta文件在/lib/optee_armtz目录下,以uuid开头,两种方法定位到对应uuid,第一种可以遍历目录下文件,查看字符串(如文件名bj777或者wrong)这种关键字符串。第二种通过strace可以轻松拿到对应ta文件的uuid:

1
2
3
bj666: 4194350e-9204-4348-ac59-ace9f0c055af
bj777: 372b9188-934f-469b-9bd3-124463c650bd
bj888:045ccc45-ee83-43ec-b69f-121819c1ba6b

从上述分析,我们只需要看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
__int64 __fastcall sub_A30(__int64 a1, __int64 a2)
{
size_t v2; // x19
const char *v3; // x21
size_t v4; // x0
char v6[24]; // [xsp+30h] [xbp-2040h] BYREF
__int64 v7[4]; // [xsp+48h] [xbp-2028h] BYREF
char v8[4096]; // [xsp+68h] [xbp-2008h] BYREF
__int64 v9; // [xsp+1068h] [xbp-1008h] BYREF

v2 = 0LL;
v3 = *(const char **)(a2 + 8);
qmemcpy(v7, "snbjklefsdcvfsycsnbjklefsdcvfsyc", sizeof(v7));
while ( 1 )
{
v4 = strlen(v3);
if ( v4 <= v2 )
break;
v8[v2] = v3[v2];
++v2;
}
if ( v4 != 27 )
{
puts("wrong");
exit(1);
}
sub_C64(v6);
sub_D64(v6, 1LL);
sub_E14(v6, v7, 16LL);
sub_EB8(v6, &v7[2], 16LL);
sub_F40(v6, v8, &v9, 4096LL);
sub_D3C(v6);
return 0LL;
}

该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
__int64 __fastcall sub_1DC(int *a1, unsigned int a2, int a3, _QWORD *a4)
{
int v7; // w5
unsigned __int16 v8; // w0
int v9; // w5
int v10; // w5
unsigned int v11; // w19
__int64 v12; // x0
__int64 v14; // x2
unsigned int v15; // w3
unsigned int v16; // w0
unsigned int v17; // w0
unsigned int v18; // w2
__int64 v19; // x0
__int64 v20; // x20
__int64 v21; // x3
__int64 i; // x0
char v23[32]; // [xsp+40h] [xbp+40h] BYREF

switch ( a2 )
{
case 0u:
sub_930("alloc_resources", 124LL, 3LL, 1LL, "Session %p:", a1);
if ( a3 != 273 )
return (unsigned int)-65530;
v7 = *(_DWORD *)a4;
if ( *(_DWORD *)a4 == 1 )
{
v8 = 272;
}
else if ( v7 == 2 )
{
v8 = 528;
}
else
{
if ( v7 )
{
sub_930("ta2tee_algo_id", 70LL, 1LL, 1LL, "Invalid %u");
return (unsigned int)-65530;
}
v8 = 16;
}
*a1 = v8 | 0x10000000;
v9 = *((_DWORD *)a4 + 4);
if ( v9 != 16 && v9 != 32 )
{
sub_930("ta2tee_key_size", 82LL, 1LL, 1LL, "Invalid %u");
return (unsigned int)-65530;
}
a1[2] = v9;
v10 = *((_DWORD *)a4 + 8);
if ( v10 )
{
if ( v10 != 1 )
{
sub_930("ta2tee_mode_id", 96LL, 1LL, 1LL, "Invalid mode %u");
return (unsigned int)-65530;
}
a1[1] = 0;
}
else
{
a1[1] = 1;
}
if ( *((_QWORD *)a1 + 2) )
sub_3018();
v11 = sub_305C(a1 + 4, (unsigned int)*a1, (unsigned int)a1[1], (unsigned int)(8 * a1[2]));
if ( v11 )
{
sub_930("alloc_resources", 160LL, 1LL, 1LL, "Failed to allocate");
*((_QWORD *)a1 + 2) = 0LL;
}
else
{
if ( *((_QWORD *)a1 + 3) )
sub_2400();
v11 = sub_2380(2684354576LL, (unsigned int)(8 * a1[2]), a1 + 6);
if ( v11 )
{
sub_930("alloc_resources", 174LL, 1LL, 1LL, "Failed to allocate");
*((_QWORD *)a1 + 3) = 0LL;
}
else
{
v14 = sub_1F70((unsigned int)a1[2], 0LL);
if ( v14 )
{
sub_2578(v23, 3221225472LL, v14, (unsigned int)a1[2]);
v11 = sub_24A8(*((_QWORD *)a1 + 3), v23, 1LL);
if ( v11 )
{
sub_930("alloc_resources", 198LL, 1LL, 1LL, "TEE failed, %x", v11);
}
else
{
v11 = sub_3DC0(*((_QWORD *)a1 + 2), *((_QWORD *)a1 + 3));
if ( !v11 )
return v11;
sub_930("alloc_resources", 204LL, 1LL, 1LL, "TEE failed %x", v11);
}
}
else
{
v11 = -65524;
}
}
}
if ( *((_QWORD *)a1 + 2) )
sub_3018();
v12 = *((_QWORD *)a1 + 3);
*((_QWORD *)a1 + 2) = 0LL;
if ( v12 )
sub_2400();
*((_QWORD *)a1 + 3) = 0LL;
return v11;
case 1u:
sub_930("set_bj888_key", 240LL, 3LL, 1LL, "Session %p: load ", a1);
if ( a3 != 5 )
return (unsigned int)-65530;
v15 = *((_DWORD *)a4 + 2);
if ( v15 != a1[2] )
{
sub_930("set_bj888_key", 251LL, 1LL, 1LL, "Wrong size %u, expect %u bytes", v15);
return (unsigned int)-65530;
}
((void (*)(void))sub_2578)();
sub_2454(*((_QWORD *)a1 + 3));
v16 = sub_24A8(*((_QWORD *)a1 + 3), v23, 1LL);
v11 = v16;
if ( v16 )
{
sub_930("set_bj888_key", 280LL, 1LL, 1LL, "TEE failed, %x", v16);
}
else
{
sub_3D48(*((_QWORD *)a1 + 2));
v17 = sub_3DC0(*((_QWORD *)a1 + 2), *((_QWORD *)a1 + 3));
v11 = v17;
if ( v17 )
sub_930("set_bj888_key", 287LL, 1LL, 1LL, "TEE failed %x", v17);
}
return v11;
case 2u:
sub_930("reset_bj888_iv", 310LL, 3LL, 1LL, "Session %p: no hint", a1);
if ( a3 != 5 )
return (unsigned int)-65530;
sub_42E4(*((_QWORD *)a1 + 2), *a4, *((unsigned int *)a4 + 2));
return 0;
case 3u:
sub_930("cipher_buffer", 344LL, 3LL, 1LL, "Session %p: no hints", a1);
if ( a3 != 101 )
return (unsigned int)-65530;
v18 = *((_DWORD *)a4 + 2);
if ( *((_DWORD *)a4 + 6) < v18 )
{
sub_930("cipher_buffer", 352LL, 1LL, 1LL, "Bad sizes: in %d, out %d", v18);
return (unsigned int)-65530;
}
v19 = *((_QWORD *)a1 + 2);
if ( !v19 )
return (unsigned int)-65529;
sub_43C4(v19, *a4);
v20 = a4[2];
v21 = sub_20(v23, &unk_11AFA, 27LL);
for ( i = 0LL; i != 27; ++i )
{
if ( *(unsigned __int8 *)(v21 + i) != *(unsigned __int8 *)(v20 + i) )
{
sub_1E10(*a4, "wrong", 6LL);
sub_1FD8("wrong");
return (unsigned int)-65530;
}
}
sub_1E10(*a4, "right", 6LL);
sub_1FD8("right");
return 0;
default:
v11 = -65526;
sub_930("TA_InvokeCommandEntryPoint", 452LL, 1LL, 1LL, " 0x%x is not supported", a2);
return v11;
}
}

可以看到主要逻辑是获取key和iv然后进行加密,再对比。如果有手动构建过optee的话,这个其实就是内置的aes的ctr加密魔改过来的,加密并没有魔改,只是将key和iv初始化在了host,下面可以直接用在线网站跑出结果:

1

当然,最好的方法是修改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
int main(void)
{
struct test_ctx ctx;
char key[AES_TEST_KEY_SIZE]="snbjklefsdcvfsyc";
char iv[AES_BLOCK_SIZE]="snbjklefsdcvfsyc";
char clear[AES_TEST_BUFFER_SIZE];
char ciph[AES_TEST_BUFFER_SIZE]={0x25, 0x03, 0x0A, 0x6C, 0xF8,
0xB1, 0xCE, 0x7F, 0xC9, 0x42, 0x0C, 0x0D, 0x68, 0xB3, 0x1C, 0x04,
0x64, 0xFA, 0xE5, 0xA4, 0x22, 0xD4, 0x2C, 0xFF, 0x4E, 0x36, 0x2A};
char temp[AES_TEST_BUFFER_SIZE];

//printf("Prepare session with the TA\\n");
//prepare_tee_session(&ctx);

//printf("Prepare encode operation\\n");
//prepare_aes(&ctx, ENCODE);

//printf("Load key in TA\\n");
//memset(key, 0xa5, sizeof(key)); /* Load some dummy value */
//set_key(&ctx, key, AES_TEST_KEY_SIZE);

//printf("Reset ciphering operation in TA (provides the initial vector)\\n");
//memset(iv, 0, sizeof(iv)); /* Load some dummy value */
//set_iv(&ctx, iv, AES_BLOCK_SIZE);

//printf("Encode buffer from TA\\n");
//memset(clear, 0x5a, sizeof(clear)); /* Load some dummy value */
//cipher_buffer(&ctx, clear, ciph, AES_TEST_BUFFER_SIZE);

printf("Prepare decode operation\\n");
prepare_aes(&ctx, DECODE);

//printf("Load key in TA\\n");
//memset(key, 0xa5, sizeof(key)); /* Load some dummy value */
set_key(&ctx, key, AES_TEST_KEY_SIZE);

//printf("Reset ciphering operation in TA (provides the initial vector)\\n");
//memset(iv, 0, sizeof(iv)); /* Load some dummy value */
set_iv(&ctx, iv, AES_BLOCK_SIZE);

printf("Decode buffer from TA\\n");
cipher_buffer(&ctx, ciph, temp, AES_TEST_BUFFER_SIZE);

/* Check decoded is the clear content */
if (memcmp(clear, temp, AES_TEST_BUFFER_SIZE))
printf("Clear text and decoded text differ => ERROR\\n");
else
printf("Clear text and decoded text match\\n");

terminate_tee_session(&ctx);
return 0;
}

然后编译运行,可以直接跑出flag:sctf{T3e_not_s4f3_anym0re!}

0

当然该题还可以进行动态调试,只需要把run.sh最后一行改为:

1
-monitor null -serial tcp:localhost:port1 	-serial tcp:localhost:port2 -s -S

指定串口即可,然后run.sh运行,再用gdb连接,接下来下断点到文件加载处,便可以进行动态调试。

5.总结

这次出题学到了很多,在赛后也受到许多大师傅的启发,之后会让题出的更有逻辑和思路,而不是一味的堆砌。在这次出题后,在平时需要进行沉淀和思考,不能急于求成。