SCTF2023 逆向 SycTee 出题与解题思路

新闻资讯   2023-07-12 17:59   41   0  




前言


SCTF2023是本年度xctf的第二次分站赛,这是第一次出这样大型的比赛的题,头一次没打xctf,在观赛者的角度来看比赛进程,是另一种体验,既担心自己题出的太简单,又怕题没人做。


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





做题情况&题目描述


做题情况:



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


题目描述:






出题思路


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的安装:


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

*本文为看雪论坛优秀文章,由 bj777 原创,转载请注明来自看雪社区


# 往期推荐

1、在 Windows下搭建LLVM 使用环境

2、深入学习smali语法

3、安卓加固脱壳分享

4、Flutter 逆向初探

5、一个简单实践理解栈空间转移

6、记一次某盾手游加固的脱壳与修复




球分享

球点赞

球在看

文章引用微信公众号"看雪学苑",如有侵权,请联系管理员删除!

博客评论
还没有人评论,赶紧抢个沙发~
发表评论
说明:请文明发言,共建和谐网络,您的个人信息不会被公开显示。