环境搭建

硬件

esp32、micro-usb、杜邦线、usb->ttl、支持嗅探的无线网卡、BLE Sniffer

软件

这是源码:
esp32 的 windows 烧录环境:,直接点进来下载离线安装包
[

image.png](https://cdn.nlark.com/yuque/0/2022/png/268938/1663401939422-92048e0d-64e1-4672-8e71-5fc4562289c9.png#clientId=u5b2aaab0-268b-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=331&id=u3cf42c8c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=414&originWidth=760&originalType=binary∶=1&rotation=0&showTitle=false&size=37035&status=error&style=none&taskId=u69341c37-925e-4dec-b77b-218a93422d9&title=&width=608)
image.png](https://cdn.nlark.com/yuque/0/2022/png/268938/1663401939422-92048e0d-64e1-4672-8e71-5fc4562289c9.png#clientId=u5b2aaab0-268b-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=331&id=u3cf42c8c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=414&originWidth=760&originalType=binary∶=1&rotation=0&showTitle=false&size=37035&status=error&style=none&taskId=u69341c37-925e-4dec-b77b-218a93422d9&title=&width=608)

我下载的是这个,无脑安装

image.png
image.png

完事之后出来了两个快捷方式

image.png
image.png

这俩用哪个都行,打开之后切换目录到源码的文件夹,cd esp32ctf_thu/thuctf/
输入命令:idf.py menuconfig,稍等片刻会打开一个新界面,设置 Serial flasher config 的 Flash size 为 4MB

image.png
image.png

设置 Partition Table 的 Partition Table 为 Custom partition table CSV

image.png
image.png

选完之后 Q 保存退出
然后idf.py build编译代码

image.png
image.png

等它编译一阵,完事之后就可以idf.py flash了,出现 Connecting….. 的时候要摁住板子上的 BOOT 键

image.png
image.png

烧写完成之就可以关掉了,随便找个串口工具,选择波特率 115200 就能看到 log 了

image.png
image.png

注意

题目其实是这里,烧录好之后拿着这个文件夹里的内容做题,里面有个 tar 包,是删掉了真实 flag 的源码,有些关卡需要分析源码才知道咋做,源码按照不同的题目方向分开了,很友好!

开搞

咱从头开始,先把 GND 和 23 号引脚连起来,如果前面已经供电了在连 GND 和 23 引脚需要断电重新供电,或者摁一下板子上的 EN 摁扭才能切换到硬件的题目这一方向

image.png
image.png

硬件题目

task1

题目:将 GPIO18 抬高,持续 3s 即可获得 flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define GPIO_INPUT_IO_0     18
....
void hardware_task1(){
int hit = 0;
while(1) {
printf("[+] hardware task I : hit %d\n",hit);
if(gpio_get_level(GPIO_INPUT_IO_0)){ //gpio_get_level是获取GPIO电平值,低电平是0,高电平是1
hit ++ ;
}else{
hit = 0;
}
if(hit>3){
printf("[+] hardware task I : %s\n",hardware_flag_1);
break;
}
vTaskDelay(1000 / portTICK_RATE_MS);
}
}

此时日志如下

image.png
image.png

GPIO 是指板子上的一组引脚。这些引脚可以发送或接收电信号,但它们不是为任何特定目的而设计的,可以由我们通过编程来实现任意功能。这就是为什么它们被称为通用 IO(General-purpose input/output)
抬高就是给它供电,把板子上的 3.3V 或 5V 与他接起来就行了

image.png
image.png

task2

题目:在 GPIO18 处构造出 1w 个上升沿

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
#define GPIO_INPUT_IO_0     18
#define GPIO_INPUT_PIN_SEL ((1ULL<<GPIO_INPUT_IO_0) )
#define ESP_INTR_FLAG_DEFAULT 0

static void IRAM_ATTR gpio_isr_handler(void* arg){ //GPIO中断处理程序,中断了就执行这个
trigger++;
}

void hardware_gpio_setup(){
gpio_config_t io_conf; //GPIO的配置参数
io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL; //要配置的GPIO引脚
io_conf.mode = GPIO_MODE_INPUT; //仅输入
io_conf.intr_type = GPIO_INTR_POSEDGE; //GPIO中断类型,上升沿
io_conf.pull_up_en = 0; //禁止上拉使能
gpio_config(&io_conf);
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0); //注册GPIO_INPUT_IO_0,也就是GPIO18中断处理程序
}

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);
}
}

上升沿指的是数字电路中数字电平从低电平(数字 0)到高电平(数字 1)的一瞬间,下降沿同理
借助一个 TX 的引脚会一直输出这一特点来与 GPIO18 连起来,这样就可以啦

image.png
image.png

补充:上下拉是给 IO 一个默认的状态,上拉和下拉是指 GPIO 输出高电位(上拉)还是低电位(下拉),从程序设计的角度讲,上拉就是如果没有输入信号则此时 I/O 状态为 1,下拉相反
关于上下拉电阻看一下这个

试着理解一下代码的意思,给 GPIO18 注册了一个上升沿中断处理函数,函数的功能是 trigger+1,同时把 GPIO18 的上拉关掉,这样没有输入时候的 I/O 状态就不是 1,有输入的时候就会触发上升沿,这样 trigger 就会增加了

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
26
#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);
}
}

被晃了呜呜呜,这个板子上有个 TX2 我以为是这个呐,结果等了半天啥也没有,这个是让你分析代码,看一下用的哪一个 GPIO 作为 TX,通过 define 可以看到,TXD 是 GPIO4,那就把 GPIO4 接到 USB->TTL 的 RX 上就可以看到了

image.png
image.png
image.png
image.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
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);
}

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);
}
}

这里得往上翻日志了,他随机指定了一个 wifi 名和密码,会去连接那个密码,用手机开个热点即可
ssid: kbmxet , password svtujgjb

image.png
image.png
image.png
image.png

日志里给出了 IP 和端口,用电脑也连接上开的热点

image.png
image.png

nc 一下,连上之后根据源码里的逻辑,发送 getflag 即可

image.png
image.png

task2

题目:你知道他发给百度的 flag 么

此部分代码不完善,可能会因死循环爆栈导致重启,请见谅…(确实容易重启 🤣)

改为用电脑开这个热点,然后直接抓取网卡的流量,嗯,,他好像不会切换,得重新做一遍

image.png
image.png

电脑开启热点后会有一个新的网卡,就抓这个网卡即可

image.png
image.png

追踪 HTTP 流发现 flag

image.png
image.png

task3

题目:flag 在空中
同时日志如下:

image.png
image.png

把无线网卡插到 kali 里面,得搞到无线网卡再做

蓝牙题目

task1

题目:修改蓝牙名称并设置可被发现即可获得 flag
也是刚开始的日志中随机指定了蓝牙设备的名字

image.png
image.png

直接改手机的名字就行了,现在手机好像默认不被发现?在手机上点击扫描周围设备就可以了

image.png
image.png
image.png
image.png

task2

题目:flag 在空中
那就抓包吧,开启 Hollong,直接全选广播包中就有,同时也确定了他的 MAC 地址和设备名

image.png
image.png

task3

题目:分析 GATT 业务并获得 flag
用 nRF Connect 连接,一开始读,只有 DEEDBEEF

image.png
image.png

搜索源码里的 [+] bluetooth task III 定位到这里,我们写入的值与 flag2 进行了对比,通过则 open_task3 = 1

image.png
image.png

只有当 open_task3 = 1 时才会把真正的 flag 拷贝过去

image.png
image.png

发送 task2 的 flag

image.png
image.png

再次读取,转成 ASCII 码即可 THUCTF{WrItE_4_gA7T}

image.png
image.png