Part1前言
近期对汽车Tbox进行了一些逆向分析,由于只有一个零部件分析起来还是较为困难的,本次呢,针对TBOX对外开放服务进行了逆向分析,这里就分享一下过程。下一次我们将分析TSP->TBOX->MCU->CAN的过程。
Part2代码分析
拿到设备点亮之后,通过adb拿到shell,netstat -antpl可以看到监听在0.0.0.0的端口为53、8888。根据AVN这个程序名,就可以猜测8888端口这是一个与车机交互的程序。
Ida打开AVN服务Main方法往下可以看到这里有一个HU服务初始化的函数,这里8888就是端口,刚刚通过netstat -antpl也看过。
进入到函数实现,有几个指向,虽然我们已经知道这个服务的端口是8888,但是我们可以先关注传过来的a1跟踪一下看看,进入到TCPServer_init的实现。
a1传给了addr接着调用了bind,很明显了这就是绑定的端口。
不理解的朋友没关系,我们先来看一个经典的socket通信代码。
服务端:
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
char *response = "Server response";
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置服务器地址和端口
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定套接字到指定地址和端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("listen failed");
exit(EXIT_FAILURE);
}
printf("Server listening on port %dn", PORT);
// 接受客户端连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {
perror("accept failed");
exit(EXIT_FAILURE);
}
// 接收客户端数据
int bytes_read = recv(new_socket, buffer, BUFFER_SIZE, 0);
printf("Received from client: %sn", buffer);
// 发送响应给客户端
send(new_socket, response, strlen(response), 0);
printf("Response sent to clientn");
return 0;
}
客户端:
int main() {
int client_socket;
struct sockaddr_in server_address;
char buffer[BUFFER_SIZE] = {0};
char *message = "Hello from client";
// 创建套接字
if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置服务器地址和端口
server_address.sin_family = AF_INET;
server_address.sin_port = htons(PORT);
// 将IPv4地址从点分十进制转换为二进制格式
if (inet_pton(AF_INET, SERVER_IP, &server_address.sin_addr) <= 0) {
perror("inet_pton failed");
exit(EXIT_FAILURE);
}
// 连接服务器
if (connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address)) < 0) {
perror("connect failed");
exit(EXIT_FAILURE);
}
// 发送数据给服务器
send(client_socket, message, strlen(message), 0);
printf("Message sent to server: %sn", message);
// 接收服务器响应
int bytes_read = recv(client_socket, buffer, BUFFER_SIZE, 0);
printf("Response from server: %sn", buffer);
return 0;
}
通过看这个socket通信例子,想必一下子就明白了。那我们接下来继续看。
开了一个线程,跟进看看。
这里recv函数代表接收车机端的数据,数据为空移除客户端句柄否则就指向dword_1AFA4,但是并不知道这个函数的作用是什么不过根据上面提到的socket的代码,可以推断出就是发送了。
那我们验证一下。Ida中快捷键CTRL+T,搜索一下。
取决于v3,v3取决于result,CTRL+X继续回溯。
很明显了,dword_1AFA4就是newDataInCb。
newDataInCb可以看出这是一个跟车机端交互的逻辑,但似乎要满足某些数据校验,还需要登录呢,先不着急构造,这里我的目的是要模拟一个车机跟tbox去交互,先看看可不可行。
看到一个地方,猜测是一个设置用户登录信息的部分哇。
确实是,*(_BYTE *)(result + 4) = 1应该是代表登录的状态,剩下的猜测就是类似token的东西了。那思路就来了,我们模拟一个车机客户端去跟服务端tbox交互,构造特定的socket数据包,就可以是一个登录状态的client了。
看一下哪里能走进 LABEL_18。
通过分析,只需要满足下图框起来的几个条件就可以了,这看起来是安全可行的,满足头部,长度,命令ID,以及校验码就可以了。
构造如下客户端socket:
查看tbox这个程序的日志,成功进入预想的逻辑且client也已经绕过了 HUClient_checkLogin变成登录状态。
那在继续看其他的,命令ID为1025,且长度满足为9的时候会进入到一个SendAVNSystemLangToRRSrv方法,传入了请求头中的第8个字节v11[7]。
通过分析得知tbox里面的进程其实都是通过消息队列去交互的,通过函数名可以猜测是发给了一个叫RR的服务。
核心的几个方法有:
FmcOsGetMsg 创建消息队列,参数一为消息队列的长度,参数二为消息队列的类型。
FmcOsMsgQSend 发送消息队列:参数一为发送的消息队列ID,参数二为具体的消息。
FmcOsMsgQWait 接收消息队列:参数一为接收的消息队列ID,参数二为超时实际,参数三为具体的消息。
ida打开RR服务,找到FmcOsMsgQWait参数一为23的位置,重点为参数三,是消息队列的结构。
这里recv msg到的*238代表第一个字节代表了消息队列的类型也决定了下面case的走向。
所以只需要修改命令ID为1025,[7]为想传入的数据即可,构造socket脚本如下:
Tbox日志看到走到了预想的逻辑内。
到这里迷茫了,消息队列的ID不能直接控,只有数据可控,也就是FmcOsGetMsg(1, 40)中的参数二,newDataInCb中也没有其他的发送消息队列到其他服务的地方了,登录进来啥也干不了?就突然想起来初始化之后还有个定时器呢,继续跟进。
如下代码在满足登录认证后,在满足某些条件的情况下还是可能会进入到lnm,gnss等一些服务,但很遗憾重要的功能应该控制不了比如类似sendMsgToMCUByTSPApp(TSP->TBOX),然后进程之间都是通过消息队列等待某个消息,执行proc表里面的回调函数。
Part2总结
整个tbox分析起来还是挺复杂的,一个tbox起好多服务它们之间的相互调用关系分析要花费大量的时间,还是要明确你分析的目标是什么,侧重点还是看跟目标有关的,还有就是在没有一个好的环境下要花费很多本来不需要花费很多时间的事,这次分析由于只有一个tbox很多东西没法调试,所以这种情况只能去硬看代码了。
原文始发于微信公众号(DX安全实验室):记一次汽车T-BOX分析