二进制漏洞分析-1.华为Security Hypervisor漏洞
二进制漏洞分析-2.揭示华为安全管理程序(上)
二进制漏洞分析-3.揭示华为安全管理程序(下)
二进制漏洞分析-4.华为安全监控漏洞(SMC SE 工厂检查 OOB 访问)
二进制漏洞分析-5.华为安全监控漏洞(SMC MNTN OOB 访问)
二进制漏洞分析-6.Parallels Desktop Toolgate 漏洞
二进制漏洞分析-7.华为TrustZone Vsim_Sw漏洞
二进制漏洞分析-8.Huawei TrustZone VprTa漏洞
二进制漏洞分析-9.华为TrustZone TEE_Weaver漏洞
二进制漏洞分析-10.华为TrustZone TEE_SERVICE_VOICE_REC漏洞
二进制漏洞分析-11.华为TrustZone TEE_SERVICE_MULTIDRM漏洞(上)
二进制漏洞分析-11.华为TrustZone TEE_SERVICE_MULTIDRM漏洞(上)
二进制漏洞分析-12.华为TrustZone TEE_SERVICE_MULTIDRM漏洞(下)
二进制漏洞分析-13.华为TrustZone TEE_SERVICE_FACE_REC漏洞(一)
参数 OOB 访问FI_onExec
¶
在函数(如前所述)中,即使 TEE 参数类型正确,也不会检查输入缓冲区的大小。因此,如果缓冲区小于预期,则可以进行 OOB 访问。FI_onExec
params[1]
int FI_onExec(
unsigned int commandID,
void *buffer1,
unsigned int length1,
void *buffer2,
unsigned int length2,
uint32_t paramTypes) {
// ...
switch (commandID) {
case 0x80013:
// ...
image_info.width = ((uint32_t *)buffer1)[0x12C01];
// ...
case 0x80014:
// ...
image_info.width = ((uint32_t *)buffer1)[0x380];
// ...
case 0x80015:
// ...
image_info.width = ((uint32_t *)buffer1)[0x12C01];
// ...
case 0x80016u:
// ...
image_info.width = ((uint32_t *)buffer1)[0x12C01];
// ...
}
// ...
例如,在代码处理命令0x80013中,将值从偏移0x4b004提取到此缓冲区中,如果缓冲区小于此偏移量,则会导致崩溃。可以使用概念验证代码来触发此 bug:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1d00000028 (tid: 40) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x7004e004, fault_code: 0x92000007
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[TEE_SERVICE_FAC] tid=40 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=46 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=7022 prefer-ca=7022
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <_Z9FI_onExecjPvjS_j+0x420/0x9e4>
[HM] <_Z9FI_onExecjPvjS_j>+0x4e4/0x9e4
[HM] <TA_InvokeCommandEntryPoint>+0x4a0/0x8e8
[HM] <tee_task_entry>+0x398/0xcd4
[HM] Dump task states END
[HM]
空指针取消引用MsgController::_sendMsg
¶
要与 trustlet 异步交互,CA 可以注册一个代理,该代理可以等待事件并发送响应。可以设置此通信通道:
-
在 NWd 端使用 ioctl
TC_NS_CLIENT_IOCTL_REGISTER_AGENT
; -
在 SWd 端使用命令 (#ID 0x17)。
HandlePrepareAgentCommand
如下图所示,最终从库中调用函数。HandlePrepareAgentCommand
FI_prepare4Agent
lib_faceidalgo
uint32_t HandlePrepareAgentCommand(uint32_t paramTypes, TEE_Param params[4]) {
// [...]
ret = FR_TA_PrepareAgent();
// [...]
}
uint32_t FR_TA_PrepareAgent() {
// [...]
g_algo_api->FI_prepare4Agent()
// [...]
}
FI_prepare4Agent
调用。然后,此函数调用该函数,该函数将启动一个线程循环,该循环可以根据对象中设置的消息代码接收和发送响应。FaceIdCore::prepare4Agent
MsgController::threadLoop
this->msgCode
MsgController
uint32_t FI_prepare4Agent() {
// [...]
FaceIdCore::prepare4Agent(g_faceid_core);
// [...]
}
uint32_t FaceIdCore::prepare4Agent(FaceIdCore *this) {
// [...]
MsgController = MsgController::getMsgController();
MsgController::threadLoop(MsgController);
// [...]
}
uint32_t MsgController::threadLoop(MsgController *this) {
// [...]
MsgController::agentLock(this);
// [...]
do {
MsgController::wait4Send(this);
// [...]
switch (this->msgCode) {
// [...]
// Handler called in function of msgCode
// [...]
}
} while (!this->field_60);
// [...]
}
在 NWd 端的初始化过程中,将提供通过共享内存分配的代理缓冲区。在调用 期间,将检索此缓冲区并将其映射到 SWd 中,该缓冲区使用函数 进行设置。MsgController::agentLock
this->agentBuffer
TEE_EXT_GetAgentBuffer
uint32_t MsgController::agentLock(MsgController *this) {
// [...]
result = TEE_EXT_GetAgentBuffer(this->agentId, &this->agentBuffer,
&this->agentBufsize);
// [...]
}
相反,可以使用命令 (ID #0x11) 从对象中删除代理缓冲区。此处理程序将调用库中的函数,该函数最终调用 .MsgController
HandleDynamicUnloadCommand
FI_dynamicFini
lib_faceidalgo
MsgController::deInit
uint32_t HandleDynamicUnloadCommand(uint32_t paramTypes, TEE_Param params[4]) {
// [...]
ret = FR_TA_Finish();
// [...]
}
uint32_t FR_TA_Finish() {
// [...]
g_algo_api->FI_dynamicFini();
// [...]
}
int FI_dynamicFini() {
// [...]
return FaceIdCore::deinit(g_faceid_core);
// [...]
}
int FaceIdCore::deinit(FaceIdCore *this) {
// [...]
msg_controller = MsgController::getMsgController();
// [...]
MsgController::deInit(msg_controller);
// [...]
}
MsgController *MsgController::deInit(MsgController *this) {
this->sysfuncs = 0;
this->agentBuffer = 0;
this->agentBufsize = 0;
return this;
}
但是,可以设置为 0,然后让 trustlet 使用它而不进行任何检查。例如,我们可以拼出命令的执行流程 (#ID 0x10),并看到它即使在被删除后也使用代理缓冲区。this->agentBuffer
HandleDynamicInitCommand
HandleDynamicInitCommand
首先调用库中的函数,该函数使用 设置为 0x12 的函数向消息控制器发送消息。FI_dynamicInit
lib_faceidalgo
MsgController::send
msgCode
uint32_t HandleDynamicInitCommand(uint32_t paramTypes, TEE_Param params[4]) {
// [...]
ret = FR_TA_DynamicInit(params, paramTypes);
// [...]
}
uint32_t FR_TA_DynamicInit(TEE_Param *params, int paramTypes) {
// [...]
g_algo_api->FI_dynamicInit(
g_ion_buffers, g_ion_count, iobuf3_addr + 0x10, config_size);
// [...]
}
uint32_t FI_dynamicInit(FIIonBuf *ionBuffers, int ionCount, void *config,
uint configSize) {
// [...]
FaceIdCore::init(g_faceid_core, ionBuffers, ionCount, config, configSize);
// [...]
}
uint32_t FaceIdCore::init(FaceIdCore *this, FIIonBuf *ionBuffers, int ionCount,
void *config, uint configSize) {
// [...]
HiAiManager::sendModelInfo2CA(HiAiManager);
// [...]
}
uint32_t HiAiManager::sendModelInfo2CA(HiAiManager *this) {
// [...]
MsgController = MsgController::getMsgController();
MsgController::sendMsg(MsgController, 0x12, &this->field_C, 0xAA4);
return 0;
}
int MsgController::sendMsg(MsgController *this, int msgCode, char *msgArg, int msgArgSize) {
// [...]
pthread_mutex_lock(&this->field_64);
this->msgCode = msgCode;
this->msgArg = msgArg;
this->msgArgSize = msgArgSize;
MsgController::send(this);
pthread_mutex_unlock(&this->field_64);
return 0;
}
然后接收此消息,并且由于是0x12它将由在默认情况下调用的函数处理。MsgController::threadLoop
msgCode
MsgController::_sendMsg
uint32_t MsgController::threadLoop(MsgController *this) {
// [...]
MsgController::agentLock(this);
// [...]
do {
MsgController::wait4Send(this);
// [...]
switch (this->msgCode) {
// [...]
default:
MsgController::_sendMsg(this);
break;
}
// [...]
MsgController::finishNotify(this);
} while (!this->field_60);
// [...]
}
但是,此函数将取消引用,该函数在调用之前设置为 0,并将导致 null 指针取消引用。this->agentBuffer
HandleDynamicInitCommand
uint32_t MsgController::_sendMsg(MsgController *this) {
// [...]
*(uint32_t *)this->agentBuffer = this->msgCode;
// [...]
}
此 bug 可以使用概念验证代码触发,并导致崩溃,因为地址为 0 写入:
[HM] [ERROR][2171]vmem_as_ondemand_prepare failed
[HM] [ERROR][2496]process 1e00000036 (tid: 54) data abort:
[HM] [ERROR][2498]Bad memory access on address: 0x0, fault_code: 0x92000046
[HM]
[HM] Dump task states for tcb
[HM] ----------
[HM] name=[TEE_SERVICE_FAC] tid=54 is-idle=0 is-curr=0
[HM] state=BLOCKED@MEMFAULT sched.pol=0 prio=46 queued=1
[HM] aff[0]=ff
[HM] flags=1000 smc-switch=0 ca=6744 prefer-ca=6744
[HM] Registers dump:
[HM] ----------
[HM] 32 bits userspace stack dump:
[HM] ----------
[HM] <_ZN13MsgController8_sendMsgEv+0x14/0x48>
[HM] <_ZN13MsgController10threadLoopEv>+0x1f0/0x248
[HM] <_ZN13MsgController10threadLoopEv>+0x1f0/0x248
[HM] <_ZN10FaceIdCore13prepare4AgentEv>+0x2c/0xa0
[HM] <_Z16FI_prepare4Agentv>+0x34/0x98
[HM] <TA_InvokeCommandEntryPoint>+0x738/0x8e8
[HM] <tee_task_entry>+0x398/0xcd4
[HM] Dump task states END
[HM]
Trustlet 函数中的物理地址泄漏FR_TA_CoAuthSignImg
¶
函数中存在静态物理地址泄漏。FR_TA_CoAuthSignImg
int FR_TA_CoAuthSignImg(int paramTypes, TEE_Param *params, int nb_params) {
// ...
_fr_get_static_phy_addr(&phys_addr, 0, params[1].value.a, params[1].value.b);
// ...
tee_print(
0,
"%s %10.10s: check phy addr fail phy:%llu offset:%u n",
"[error]",
"GetCoAuthImgPhyAddr",
phys_addr,
offset);
// ...
}
Trustlet 函数中的参数指针泄漏FR_GetHwAuthToken
¶
函数中存在 TEE 参数指针泄漏。FR_GetHwAuthToken
int HandleGetAuthTokenCommand(uint32_t paramTypes, TEE_Param params[4]) {
// ...
FR_GetHwAuthToken(params[1].memref.buffer, ¶ms[1].memref.size);
// ...
}
int FR_GetHwAuthToken(void *buffer, uint32_t *length_p) {
// ...
tee_print(
0,
"%s %10.10s: check param fail, token 0x%x, len 0x%x n",
"[error]",
"FR_GetHwAuthToken",
buffer,
length_p);
// ...
}
Trustlet 函数中的参数指针泄漏FR_ActiveUserSet
¶
函数中存在 TEE Param 指针泄漏。FR_ActiveUserSet
int HandleActiveUserSetCommand(uint32_t paramTypes, TEE_Param params[4]) {
// ...
active = FR_ActiveUserSet(params[0].value.a, params[1].memref.buffer, &obuf1_size);
// ...
}
int FR_ActiveUserSet(int activeid, int *faceids, unsigned int *length) {
// ...
tee_print(
0,
"%s %10.10s: active user set:param is invalid.activeid:%u,faceids:0x%x,length:0x%x n",
"[error]",
"FR_ActiveUserSet",
activeid,
faceids,
length);
// ...
tee_print(
0,
"%s %10.10s: get face ids fail.ret:0x%x,faceids:0x%x,length:0x%x,length:%u n",
"[error]",
"FR_ActiveUserSet",
DataBase,
faceids,
length,
*length);
// ...
}
Trustlet 函数中的 ION 虚拟地址泄漏FR_HashCheck
¶
函数中映射 ION 缓冲区的虚拟地址泄漏。FR_HashCheck
int HiAiManager::loadModelFromBuffers(HiAiManager *this, FIIonBuf *ionBuffers, int ionCount)
{
// ...
npuLoadMemAddr = this->npuLoadMem.vaddr;
// ...
HiAiManager::modelBuffShaCheck(this, npuLoadMemAddr, sizeOfBufs);
// ...
}
int HiAiManager::modelBuffShaCheck(HiAiManager *this, void *npuLoadMemAddr, unsigned int sizeOfBufs) {
// ...
return this->sysfuncs->FR_HashCheck(
npuLoadMemAddr,
sizeOfBufs,
&Product::mSha256,
0x20);
}
int32_t FR_HashCheck(void *model, int modelLen, void *hash, int hashLen) {
// ...
tee_print(
0,
"%s %10.10s: param invalid,model=0x%x,modelLen=0x%x,hash=0x%x,hashLen=0x%x n",
"[error]",
"FR_HashCheck",
model,
modelLen,
hash,
hashLen);
// ...
}
Trustlet 函数中的参数指针泄漏FR_GetResultAuthToken
¶
函数中存在指针泄漏。TEE_Param
FR_GetResultAuthToken
TEE_Result TA_InvokeCommandEntryPoint(
void *sessionContext,
uint32_t commandID,
uint32_t paramTypes,
TEE_Param params[4]) {
// ...
FR_GetResultAuthToken(params[0].memref.size, params[1].memref.size);
// ...
}
int FR_GetResultAuthToken(void *buffer, int length) {
// ...
tee_print(
0,
"%s %10.10s: param is not valid,authtoken = 0x%x, length = %u n",
"[error]",
"FR_GetResultAuthToken",
buffer,
length);
// ...
}
Trustlet 函数中的堆指针泄漏FR_LoadDataBase
¶
函数中存在堆指针泄漏。FR_LoadDataBase
int FR_LoadDataBase() {
// ...
FR_AllocMemToReadFile(v11, fileOps, &loadData, &dataLen);
// ...
tee_print(
0,
"%s %10.10s: [db_v3] this user has already enrolled faces, and need to restore DB: userId = %u, loadData = %p, dataLen = %u n",
"[info]",
"FR_AllocMemToGetUserInfo",
ActiveUserId,
loadData,
dataLen);
// ...
}
原文始发于微信公众号(安全狗的自我修养):二进制漏洞分析-14.华为TrustZone TEE_SERVICE_FACE_REC漏洞(二)