一
前言
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与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的安装:
mkdir ~/bin
PATH=~/bin:$PATH
//下载工具并执行
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo
接下来初始化repo:
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配置单。
<?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>
接下来就是要下载配置单的过程:
//-j是开启多线程, 不加也可以
repo sync -j8
在下载前呢,需要设置代理,这样更快,或者在qemu_v8清单中的revision后加上clone-depth="1",或者:
//将配置单换成.git速度更快
sed -i "s/\.git//g" .repo/manifest.xml
最后别忘了代理:
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
不过经过测验,这个拉取也是需要时间的,不管用服务器还是设代理有时候照样拉不了,多尝试几次就可以拉了,我是下午的时候拉成功的。
构建完的目录如下:
optee_example和optee_rust目录都是可以自己创建ta程序.
如果某些文件实在拉不下来,就手拉,从qemu-v8里找,一个一个拉,但版本要一定。
接下来获取工具链:
cd build
make toolchains
实在不能获取,就只能手拉下载,解压即可。
然后在build目录下:
make
之后能得到/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。
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
/*
* 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
/*
* 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, ¶ms[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加密。
四
解题思路
本题采用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:
bj666: 4194350e-9204-4348-ac59-ace9f0c055af
bj777: 372b9188-934f-469b-9bd3-124463c650bd
bj888:045ccc45-ee83-43ec-b69f-121819c1ba6b
从上述分析,我们只需要看bj888文件即可,先看ca文件。
__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文件。
__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,下面可以直接用在线网站跑出结果:
当然,最好的方法是修改optee_example_aes文件中host目录下的main:
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!}
当然该题还可以进行动态调试,只需要把run.sh最后一行改为:
-monitor null -serial tcp:localhost:port1 -serial tcp:localhost:port2 -s -S
指定串口即可,然后run.sh运行,再用gdb连接,接下来下断点到文件加载处,便可以进行动态调试。
五
总结
这次出题学到了很多,在赛后也受到许多大师傅的启发,之后会让题出的更有逻辑和思路,而不是一味的堆砌。在这次出题后,在平时需要进行沉淀和思考,不能急于求成。
题目链接:链接:https://pan.baidu.com/s/1kVAGU1RwJJ8iDUkPhqPpGA
提取码:8u9r
看雪ID:bj777
https://bbs.kanxue.com/user-home-939034.htm
# 往期推荐
3、安卓加固脱壳分享
球分享
球点赞
球在看
文章引用微信公众号"看雪学苑",如有侵权,请联系管理员删除!