周末和烧卖激情的打了D3ctf,逆向没啥特别想记录的,主要是这次是本人第一次接触车机类的题目,主要时间在和师傅们一起做这个,感觉非常有意思,学到很多,但是最后还是只日出来一个flag,后面俩没日出来。不过日的时候疯狂搜索各种相关知识,也算小小的入了一点点门了?之后再找点相关

发现环境已经寄了,只能靠回忆来复现了

分三部分,相关知识介绍,自己做的部分和复现的部分

相关知识

mqtt

简述

MQTT 是一种基于标准的消息传递协议或规则集,用于机器对机器的通信。智能传感器、可穿戴设备和其他物联网(IoT)设备通常必须通过带宽有限的资源受限网络传输和接收数据。这些物联网设备使用 MQTT 进行数据传输,因为它易于实施,并且可以有效地传输物联网数据。MQTT 支持设备到云端和云端到设备之间的消息传递。

原理

mqtt是基于发布/订阅模型工作的协议,和普通的网络通信协议不一样,一般的网络通信协议是服务端和客户端相互通信,但是mqtt是引入了一个代理,每个客户端(用mqtt通信的设备都可以叫mqtt客户端)都可以是发布者和订阅者。如果是订阅消息,就是客户端向代理发送SUBSCRIBE,后接一个表示订阅目标的参数,然后就可以接收到指定目标的消息。如果是发布消息,设备就需要向代理发送publish的消息,然后代理对发送过来的消息进行筛选,把它转发到订阅了这个消息的订阅者。

如果说通俗一点,就相当于b站订阅up主。视频up主和普通观众就是mqtt客户端,b站就是代理。up主发布视频就是mqtt publish,b站收到up主发布视频的信息后将视频发布的信息转发给订阅了这个up主的观众(也是mqtt客户端)。

image-20240430171929420

工作流程

  • mqtt客户端和mqtt代理建立连接

image-20240430172112706

  • mqtt客户端执行订阅消息或者发布消息操作
  • mqtt代理收到消息后将其转发到订阅这个消息的对应的客户端

UDS

UDS协议(Unified Diagnostic Services,统一诊断服务)是诊断服务的规范化标准,比如读取故障码应该向ecu发什么指令,读数据流又发什么指令。

ISO14229-1协议定义了6类功能,26种服务,分别是:

  • 1)诊断和通信管理功能单元,包括10,11,27,28,3E,83,84,85,86,87共10种服务;
  • 2)数据传输功能单元,包括22,23,24,2A,2C,2E,3D共7种服务;
  • 3)存储数据传输功能单元,包括14,19共2种服务;
  • 4)输入输出控制功能单元,包括2F服务;
  • 5)例行程序功能单元,包括31服务;
  • 6)上传下载控制功能单元,包括34,35,36,37,38共5种服务。

image-20240430181231434

不过咱们这个题只用到了一个服务,27的SecurityAccess(安全访问)

frp

(因为我不熟所以顺便学了)

frp是一个体积轻量的反向代理软件,可以使处于内网或防火墙后的设备对外界提供服务,支持 TCP、UDP、HTTP、HTTPS 等多种协议。将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。

实现原理

frp 主要由客户端(frpc)和服务端(frps)组成,服务端通常部署在具有公网 IP 的机器上,客户端通常部署在需要穿透的内网服务所在的机器上。内网服务由于没有公网 IP,不能被非局域网内的其他用户访问。隐藏用户通过访问服务端的 frps,由 frp 负责根据请求的端口或其他信息将请求路由到对应的内网机器,从而实现通信。

工作原理

image-20240430210731367

自己做的

题目给了俩靶机端口,一个是连接远程的安卓模拟器,如果模拟器启动了就可以用adb连接另一个端口,也就是车机的系统

image-20240430145808293

flag1

第一个flag其实比较简单,但是我们想复杂了。当时随便找了一圈没东西,以为是要打cve提权。找到了linux内核版本和安卓版本在网上找了一圈编译了一圈,但是一个成功的都没有。当时是用adbwebkit直接查看到了所有的包,才看到有一个奇怪的包把apk dump出来的。

image-20240430150253698

也可以用安卓命令行的命令查看所有包名和包名的apk的路径

1
pm list packages -f

都可以。没啥日车的经验,这种指令应该最开始就敲的。

总而言之找到这个com.d3car.factory包后dump出apk,拖进jadx后在layout.xml里找到了第一个flag

image-20240430151337497

非常easy的第一个flag

flag2-未完成

apk拖入jadx后是没有dex代码的,只有资源文件。从manifest中可以得知有三个activity

image-20240430151822032

因为这里上面俩都没设置export=false,所以我们直接am调用就可以调到对应的activity。这里我们直接调用这个pwdAuthActivity。

忘记说了,这里我们用了scrcpy实现了车机窗口的可视化,不然看不到车机显示的东西

1
am start -n com.d3car.factory/.PwdAuthActivity

scrcpy中弹出这个界面

image-20240430152302343

随便输入发现会弹出错误提示,这证明肯定是有验证的地方的。

重新找到apk的目录,发现目录下还有一个odex和vdex。用工具vdexExtractor把vdex还原成dex后得到逻辑

image-20240430152734542

非常简单的异或,注意这里的key是pwd界面的上面那个十六进制,而不是这里的20240419

异或得到1dacdfma34560rDa,本以为是flag,但是发现似乎只是能走到下一个tcpdump的流程

image-20240430152912546

这个password的用处就是把这个isChecked过了,因为如果我们直接去am拉起这个tcpdump的activity会失败,只有这样输入后才能正常走到这个页面。

image-20240430153024303

根据activity的代码可知,开启tcpdump后会执行这个指令

1
/system/xbin/tcpdump -i any -s 0 -w /sdcard/tcpdumplog.pcap -W 1

会将log存到/sdcard/tcpdumplog.pcap,运行一会后dump出来pcap文件用wireshark看看流量

发现了很多mqtt协议的流量(当时的副本)

image-20240430155400596

多dump几次可以发现规律。它首先是进行一个connect请求,可以看到client ID,user name,password都给出来了

image-20240430155603451

然后一个connect的ack表示连接确认,这个上面相关知识部分已经介绍过了。随后就是两个publish的操作,对应的订阅的对象是can/514/write,write的数据是固定的。

1
2
022701ffffffffff
0527022dcf28ffff

由27 01和27 02和他订阅对象名字的can/514/write容易得到,这是用mqtt传输的uds协议,而27 01和27 02是uds的安全访问的经典的数据

image-20240430173552954

我们将其拆分一下

1
2
3
长度 服务请求 子功能        密钥	  填充
02 27 01 ffffffffff
05 27 02 2dcf28 ffff

01代表的是request seed,02代表的是send key。所以可以得知2dcf28是对应的密钥。

既然都这样了,用户名密码key都有,肯定是需要订阅然后发送信息的。所以接下来的操作首先需要找到方法在远程adb环境中执行mqtt的订阅与publish操作,其次是构造mqtt数据发送给代理来监听其他的mqtt客户端publish的消息。

和代理交互

比赛时我们用的方法是github找能进行mqtt交互的arm架构的elf文件。找了老半天csome✌找到了一个有现成二进制的项目。

https://github.com/rainu/mqtt-shell/releases/tag/v2.3.0

然后兴冲冲的push到经典的/data/local/tmp进行交互,不过很后面的时候发现他pub的输入解析比较奇怪,后面csome✌又用重写了输入的parse然后编译了一份新的,当然是后话。

然后我们这样交互了半天突然有师傅指出可以用adb forward转发出端口来然后在上面跑frps和frpc搞内网穿透,就可以直接用python的mqtt库了。原以为远程adb shell不出网搞不了,但是搞成了,非常尴尬。

构造数据

总而言之能交互了,我们最开始随便输入订阅和publish代码,结果发现什么回显都没有。后面发现是得正确构造数据才能有回应,随便构造的数据不符合uds的数据格式也没法回显。

用csome✌的脚本可以写python和车机的mqtt交互

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
from paho.mqtt import client as mqtt_client
import paho.mqtt.client as mqtt

"""
mqttx conn -h '192.168.27.16' -p 1883 -u 'abmaM_kcalb' -P 'Ya5_1_n4C_tahW' -i 'Gojo_Satoru'
"""

broker = '127.0.0.1'
port = 1883
client_id = "Gojo_Satoru"
username = "abmaM_kcalb"
passwd = "Ya5_1_n4C_tahW"
topic = "/*"

def connect_mqtt():
def on_connect(client, userdata, flags, rc):
if rc == 0:
print(f"Connected to MQTT {client}")
else:
print("Failed to connect, return code %d\n", rc)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1, client_id, transport='tcp')
client.username_pw_set(username, password=passwd)

client.on_connect = on_connect
client.connect(broker, port)
return client

def subscribe(client: mqtt_client):
def on_message(client, userdata, msg):
print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic")

client.subscribe(topic)
client.on_message = on_message


def run():
client = connect_mqtt()
subscribe(client)
client.loop_forever()


if __name__ == '__main__':
run()

按照它正确的格式pub就可以有返回的消息,我们最后也是成功过了验证,但是环境拖的时间太久了最后还是没能继续分析就结束了。

构造数据就是

image-20240430213816819

image-20240430213757613

颅内复现

接下来就是看看wp的做法了,后面的内容是我根据几个wp理解而来的。有不对的地方希望大家能指正。

flag2

首先切换session到扩展会话模式。为什么要切换扩展会话模式,因为从前面dump的pcap包可知服务器一直在发三种包

‘022701ffffffffff’和’0527022dcf28ffff’是鉴权请求,’037f277fffffffff’是服务器反馈当前session模式下鉴权失败,返回的7f是错误代码

我个人理解是有mqtt客户端在一直请求一个需要高一些权限的服务,但是当前的权限不够,所以会返回7f,也就是错误代码。

所以接下来就应该切换session到扩展会话模式

image-20240430220207713

然后就可以获得成功反馈。证明确实是权限不够。

push fscan进去扫描端口可以发现80端口,访问可以下载多个seed2key.dll。socat转发端口后下载可以获取所有的dll。只获得dll没用,我们这里需要获取车机的ECU软件版本号,找到版本号后定位对应版本的seed2key.dll再去逆向获得seed。

获取车机ECU版本号是通过22服务,22服务可以通过id去读取数据,而车机ECU版本号是对应的0xf189

image-20240430221327257

这里发送的顺序如下

1
2
3
4
02 01 03 拓展会话
02 27 01 请求种子
05 27 02 xx xx xx 返回key
03 22 f1 89 请求版本号

返回的key是通过交互返回值来判断的,114514(经典)

image-20240430221951125

中间两步原理如下

image-20240430221718045

发送 22 f1 89后可以获得版本号11.405.10.4.233,找到seed2key.dll后逆向找到seed,然后挨个试31功能(例程控制)去鉴权就行了。

服务器实现的功能看起来是找出来的,由于我没有确切复现而是颅内复现,就先默认是找到31功能了

代码都在wm的wp里,放在参考了有需要的可以直接去看(

最后效果是这样

img

flag3

这个就有点没想到了,大家看到桌面的where am i located都以为是要拉起pwd验证,没人往gps方向想,不然说不定有可能解出来的

image-20240430222746081

首先find / -name “D3*”可以找到一个证书D3CA.cer和一个奇怪的数据文件D3CALib

尝试安装apk的话会报错INSTALL_PARSE_FAILED_INVALID_APP_STORE_CERTIFICATE,发现是车机在PackageManagerService里面添加了自定义的签名认证。

官方wp意思是爆破D3CALib这个keystore得到密码d^3ctf就可以用这个keystone安装获取定位的apk从而获取gps,但是我不是很理解怎么看出这个是keystore的而且怎么往爆破想的。

还是wm师傅的wp帅。

因为adb指令中

1
adb shell dumpsys location

可以获取GPS定位信息,但是只有程序定位过一次后才可以看到,不能主动触发。而misc经典的从拍照照片中找到地址信息的经验告诉我们,如果相机定位权限和加入位置信息都开了的话,我们就可以通过一次拍照获取gps,随之使用dumpsys location获取GPS。

分别给相机开定位权限和加入照片的位置信息后成功用dumpsys location获取经纬度信息,然后去高德搜定位后在评论区就可以找到一个用户,简介就是flag

image-20240430223910822

总结

真可惜没有环境了,后面颅内复现的部分可能比较抽象和流程有些问题,或者说整篇复现的文章都是一路写下来的比较意识流,希望师傅们看到有错误的地方后能批评指正一下。

总的来说很好玩,特别是最后的wm师傅做的flag3,太精彩了。队友们也非常给力,陪我熬夜到了4点多。烧卖yyds!

参考

https://aws.amazon.com/cn/what-is/mqtt/

https://blog.csdn.net/huihuige092/article/details/105287912

https://zhuanlan.zhihu.com/p/146410014

https://zhuanlan.zhihu.com/p/403361038

https://blog.wm-team.cn/index.php/archives/75/#D3_car+1

https://www.cnblogs.com/still-smile/p/12022080.html