esp32ctf_thu
2024-03-17 21:04:17

esp32ctf_thu

项目地址:

https://github.com/xuanxuanblingbling/esp32ctf_thu

环境:ubuntu18.04

安装esp-idf:

#依赖
sudo apt-get install git wget flex bison gperf python3.7 python3.7-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0

#获取esp-idf
mkdir -p ~/esp
cd ~/esp
git clone -b v4.3 --recursive https://github.com/espressif/esp-idf.git

#安装工具链
cd ~/esp/esp-idf
./install.sh all

使用esp-idf:

#在当前终端启用esp-idf
. $HOME/esp/esp-idf/export.sh

#打开esp-idf配置菜单
idf.py menuconfig 
Serial flasher config  --->  Flash size (4 MB) 
Partition Table        --->  Partition Table (Custom partition table CSV)

image-20240316005705114

==》

image-20240316005631117

image-20240316005647865

#根据配置和源码 编译esp-idf项目
idf.py build 

#将编译好的固件烧录到esp32
idf.py flash 

image-20240316172134087

开始挑战

题目说明

有以下四个方向的题目,方向间题目并行,方向内题目采取闯关模式串行。但由于配置冲突,采用跳帽来切换题目方向:

  • 跳帽连接GND与23号引脚:硬件、网络、蓝牙
  • 拔掉GND与23号引脚跳帽:MQTT

由于我的esp32的GND和23号引脚相距过远,用不了跳帽,所以直接用杜邦线连上了(一样的)

image-20240316190805048

硬件

task1 -> task2 -> task3 

task1

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

#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)){ //获取引脚电平状态
            hit ++ ;
        }else{
            hit = 0;
        }
        if(hit>3){
            printf("[+] hardware task I : %s\n",hardware_flag_1);
            break;
        }
        vTaskDelay(1000 / portTICK_RATE_MS);
    }
}

==》使用杜邦线将18号引脚和3.3V引脚连起来,18号引脚就将处于高电平状态

==》通过CRT建立与esp32的通信

image-20240316174937663

==》THUCTF{Ev3ryth1ng_st4rt_fr0m_GPIO_!!!}

image-20240316180353504

task2

在GPIO18处构造出1w个上升沿==》信号从低电平(逻辑 0)变为高电平(逻辑 1)的过程

int trigger = 0;

static void IRAM_ATTR gpio_isr_handler(void* arg){
    trigger++;
}

void hardware_gpio_setup(){
    ……
    gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
}

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

==》只需要将18号引脚与TX相接,利用串口一直有数据输出,就可以自动构造上升沿

==》THUCTF{AuT0++_is_th3_r1ght_w4y_hhhhhh}

image-20240316193619010

task3

在另一个串口处寻找第三个flag

#define ECHO_TEST_TXD  (GPIO_NUM_4)
#define ECHO_TEST_RXD  (GPIO_NUM_5)

void hardware_uart_setup(){
    ……
    //定义UART1 TXD为4号引脚 RXD为5号引脚
    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 控制器(UART1)发送flag
        uart_write_bytes(UART_NUM_1, hardware_flag_3, strlen(hardware_flag_3));
        vTaskDelay(1000 / portTICK_RATE_MS);
    }
}

==》使用USB转TTL

==》TX对RX(5号引脚),RX对TX(4号引脚),GND对GND

网络

       -> task2 
task1 
       -> task3

task1

连接板子目标端口,尝试获得flag

//打印一个随机SSID和password的WIFI
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和password连接WIFI
void connect_wifi(char * ssid, char * pass){
    nvs_init();
    wifi_init_sta(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);  //打印IP
        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);
    }
}

==》需要我们构造一个程序提供的随机SSID和password的WIFI热点

==》下图是我又重新运行后产出的随机WIFIimage-20240316225012854

==》提供给板子连接,将会返回IP地址和3333监听端口

image-20240316213430117

==》监听3333端口,并发送”getflag“即可通过验证获得flag:THUCTF{M4k3_A_w1rele55_h0t5p0ts}

image-20240316213753059

task2

你知道他发给百度的flag么

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)第一关的基础上,程序会向baidu发送带有flag的数据包

image-20240316215734944

==》通过wireshark捕获给esp32提供热点的wifi

==》得到flag:THUCTF{Sn1ffer_N3tw0rk_TrAffic_In_7h4_Main_r0aD}

image-20240316215814268

task3

flag在空中

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}; // 802.11 数据包,后续将包含flag
    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);
    }
}

蓝牙

task1

修改蓝牙名称并设置可被发现即可获得flag

//启用蓝牙
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);
}

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);  //验证,打印flag
            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();
    }
}

image-20240316230956881

==》创建一个该名称的蓝牙

==》获得flag:THUCTF{b1u3t00th_n4me_a1s0_c4n_b3_An_aTT4ck_surfAce}

image-20240316231257888

task2

flag在空中

//启动BLE
void next_task(){
    //包含flag的数据包
    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);
    //注册GATT
    esp_ble_gatts_register_callback(gatts_event_handler);
    esp_ble_gatts_app_register(PROFILE_A_APP_ID);
    //设置MTU
    esp_ble_gatt_set_local_mtu(500);
}

==》由经典蓝牙转为BLE(低功耗蓝牙)

==》只要能接收到BLE发出的广播数据包就可以拿到flag

image-20240316231257888

==》通过nRF connect连接随机生成的BLE设备名,点击raw接收广播数据

==》将获取到的16进制数据转个ASCII即可获得flag:THUCTF{AdVD47a}

image-20240316231635261

task3

分析GATT业务并获得flag

#define GATTS_CHAR_UUID_TEST_A      0xFF01

static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
    switch (event) {
    //注册GATT应用程序事件
    case ESP_GATTS_REG_EVT:
        ESP_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id);
        gl_profile_tab[PROFILE_A_APP_ID].service_id.is_primary = true;
        gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00;
        gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16;
        gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_A;
        esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_A);
        break;
    //读取属性事件,当远程设备尝试读取属性值时触发
    case ESP_GATTS_READ_EVT: {
        ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle);
        esp_gatt_rsp_t rsp;
        memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
        rsp.attr_value.handle = param->read.handle;
        
        //根据open_task3提供flag
        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);
        break;
    }
    //写入属性事件
    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);
            //如果写入的数据为bt_flag_2
            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;//标志位置1
            }
        }
        esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
        break;
    }
    //执行写入属性事件
    case ESP_GATTS_EXEC_WRITE_EVT:
        ESP_LOGI(GATTS_TAG,"ESP_GATTS_EXEC_WRITE_EVT");
        esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
        break;
    //MTU事件
    case ESP_GATTS_MTU_EVT:
        ESP_LOGI(GATTS_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
        break;
    case ESP_GATTS_UNREG_EVT:
        break;
    //创建服务事件
    //保存创建的服务保存,并添加一个特征
    case ESP_GATTS_CREATE_EVT:
        ESP_LOGI(GATTS_TAG, "CREATE_SERVICE_EVT, status %d,  service_handle %d\n", param->create.status, param->create.service_handle);
        gl_profile_tab[PROFILE_A_APP_ID].service_handle = param->create.service_handle;
        gl_profile_tab[PROFILE_A_APP_ID].char_uuid.len = ESP_UUID_LEN_16;
        gl_profile_tab[PROFILE_A_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_A;  //0xff01

        esp_ble_gatts_start_service(gl_profile_tab[PROFILE_A_APP_ID].service_handle);
        a_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
        esp_err_t add_char_ret = esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].char_uuid,
                                                        ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
                                                        a_property,
                                                        &gatts_demo_char1_val, NULL);
        if (add_char_ret){
            ESP_LOGE(GATTS_TAG, "add char failed, error code =%x",add_char_ret);
        }
        break;
    case ESP_GATTS_ADD_INCL_SRVC_EVT:
        break;
    //添加特征事件
    case ESP_GATTS_ADD_CHAR_EVT: {
        uint16_t length = 0;
        const uint8_t *prf_char;

        ESP_LOGI(GATTS_TAG, "ADD_CHAR_EVT, status %d,  attr_handle %d, service_handle %d\n",
                param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle);
        gl_profile_tab[PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle;
        gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.len = ESP_UUID_LEN_16;
        gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
        esp_err_t get_attr_ret = esp_ble_gatts_get_attr_value(param->add_char.attr_handle,  &length, &prf_char);
        if (get_attr_ret == ESP_FAIL){
            ESP_LOGE(GATTS_TAG, "ILLEGAL HANDLE");
        }

        ESP_LOGI(GATTS_TAG, "the gatts demo char length = %x\n", length);
        for(int i = 0; i < length; i++){
            ESP_LOGI(GATTS_TAG, "prf_char[%x] =%x\n",i,prf_char[i]);
        }
        esp_err_t add_descr_ret = esp_ble_gatts_add_char_descr(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].descr_uuid,
                                                                ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, NULL, NULL);
        if (add_descr_ret){
            ESP_LOGE(GATTS_TAG, "add char descr failed, error code =%x", add_descr_ret);
        }
        break;
    }
    //添加特征描述符事件
    case ESP_GATTS_ADD_CHAR_DESCR_EVT:
        gl_profile_tab[PROFILE_A_APP_ID].descr_handle = param->add_char_descr.attr_handle;
        ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n",
                 param->add_char_descr.status, param->add_char_descr.attr_handle, param->add_char_descr.service_handle);
        break;
    case ESP_GATTS_DELETE_EVT:
        break;
    case ESP_GATTS_START_EVT:
        ESP_LOGI(GATTS_TAG, "SERVICE_START_EVT, status %d, service_handle %d\n",
                 param->start.status, param->start.service_handle);
        break;
    case ESP_GATTS_STOP_EVT:
        break;
    //连接事件
    case ESP_GATTS_CONNECT_EVT: {
        esp_ble_conn_update_params_t conn_params = {0};
        memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
        /* For the IOS system, please reference the apple official documents about the ble connection parameters restrictions. */
        conn_params.latency = 0;
        conn_params.max_int = 0x20;    // max_int = 0x20*1.25ms = 40ms
        conn_params.min_int = 0x10;    // min_int = 0x10*1.25ms = 20ms
        conn_params.timeout = 400;    // timeout = 400*10ms = 4000ms
        ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:",
                 param->connect.conn_id,
                 param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2],
                 param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5]);
        gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id;
        //start sent the update connection parameters to the peer device.
        esp_ble_gap_update_conn_params(&conn_params);
        break;
    }
    //断开连接
    case ESP_GATTS_DISCONNECT_EVT:
        ESP_LOGI(GATTS_TAG, "ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x%x", param->disconnect.reason);
        esp_ble_gap_start_advertising(&ble_adv_params);
        break;
    //确认事件
    case ESP_GATTS_CONF_EVT:
        ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONF_EVT, status %d attr_handle %d", param->conf.status, param->conf.handle);
        if (param->conf.status != ESP_GATT_OK){
            esp_log_buffer_hex(GATTS_TAG, param->conf.value, param->conf.len);
        }
        break;
    case ESP_GATTS_OPEN_EVT:
    case ESP_GATTS_CANCEL_OPEN_EVT:
    case ESP_GATTS_CLOSE_EVT:
    case ESP_GATTS_LISTEN_EVT:
    case ESP_GATTS_CONGEST_EVT:
    default:
        break;
    }
}

==》创建了一个UUID为0xff01的GATT应用程序

==》当在这个应用程序触发写入事件写入task2中获得的flag,将使得open_task3置1

==》再当触发读取事件时,再根据open_task3为1就可以获取flag的值

image-20240317002309497

触发写入事件:

image-20240317002653110

再触发读取事件:

image-20240317002910615

最后将获得的十六进制转ASCII就获得flag:THUCTF{WrItE_4_gA7T}

image-20240316233040435

MQTT

由于项目中提供的mqtt靶机我访问不到了,我就干脆在本地根据项目提供的Dockerfile搭建一个:

FROM ubuntu:16.04

RUN sed -i "s/http:\/\/archive.ubuntu.com/http:\/\/mirrors.tuna.tsinghua.edu.cn/g" /etc/apt/sources.list && \
    apt-get update 

RUN apt-get install -y software-properties-common && \
    apt-add-repository ppa:mosquitto-dev/mosquitto-ppa && \
    apt-get install -y mosquitto 
    
CMD ["/bin/sh","-c","service mosquitto start; while true ; do sleep 10; done"]

# docker build -t mqtt:v1 .
# docker run -p 1883:1883 -d mqtt:v1 

本地采用ubuntu16.04

sudo apt-get update

sudo apt-get install -y software-properties-common
sudo apt-add-repository -y ppa:mosquitto-dev/mosquitto-ppa
sudo apt-get install -y mosquitto

sudo service mosquitto start
sudo service mosquitto status

image-20240317144308217

使用MQTT工具测试一下:MQTTX,注意MQTT版本

靶机服务没问题,因为我的靶机是虚拟机,还需要再做一个端口转发:

#管理员运行
#netsh interface portproxy add v4tov4 listenaddress=本地IP listenport=本地端口 connectaddress=虚拟机IP connectport=虚拟机端口
netsh interface portproxy add v4tov4 listenaddress=192.168.182.170 listenport=1883 connectaddress=192.168.121.142 connectport=1883

#关闭端口转发
netsh interface portproxy delete v4tov4 listenaddress=192.168.182.170 listenport=1883

image-20240317152058951

测试完毕,修改一下代码:main.c

image-20240317152230989

最后重新编译,烧录即可

task1

你知道MQTT的上帝是谁么

//MQTT事件处理器回调函数
//event包含事件的详细信息
static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
{
    //从事件中获取MQTT客户端的句柄
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    switch (event->event_id) {
        //当MQTT客户端与代理连接时
        case MQTT_EVENT_CONNECTED:
            ESP_LOGI("mqtt", "MQTT_EVENT_CONNECTED");
            //向主题(/topic/flag1)发布消息(mqtt_flag_1)
            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);
            
            //订阅主题(topic_2)
            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;
        //当MQTT客户端成功订阅主题时触发
        case MQTT_EVENT_SUBSCRIBED:
            ESP_LOGI("mqtt", "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
            break;
        //当MQTT客户端取消订阅主题时触发
        case MQTT_EVENT_UNSUBSCRIBED:
            ESP_LOGI("mqtt", "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
            break;
        //MQTT客户端成功发布消息时触发
        case MQTT_EVENT_PUBLISHED:
            ESP_LOGI("mqtt", "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
            break;
        //MQTT接收订阅的消息时触发
        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;
}

image-20240317152901594

==》程序将flag发布到主题/topic/flag1上,订阅主题就可以接收到flag

==》我这里使用#订阅所有主题,就可以接收到flag:

(这里我在使用hexo g生成静态网页的时候发现文章后续消失了,只剩下到THUCTF为止,但是在我使用代码块框住时又没有问题了)

THUCTF{#_1s_God_in_MQTT}

image-20240317153204573

task2

你能欺骗订阅者么

static void mqtt_app_start(char * broker)
{
    char r[10]={0};
    get_random(r,6);
    //凭借随机字符串获得主题
    sprintf(topic_2,"%s%s","/topic/flag2/",r);

    esp_mqtt_client_config_t mqtt_cfg = {
        .uri = broker,
    };

    //初始化MQTT客户端
    client = esp_mqtt_client_init(&mqtt_cfg);
    //注册事件处理函数
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);
    //启动MQTT客户端,连接MQTT代理
    esp_mqtt_client_start(client);

    while(1){
        printf("[+] MQTT task II: I send second flag to baidu\n");
        //向topic_2主题发布消息
        esp_mqtt_client_publish(client, topic_2, "www.baidu.com?46", 0, 1, 0);
        vTaskDelay(10000 / portTICK_RATE_MS);
    }
}

static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
    ESP_LOGD("mqtt", "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
    mqtt_event_handler_cb(event_data);
}

//执行HTTP GET请求
static void http_get_task(char * webserver,char * httpdata)
{
    const struct addrinfo hints = {
        .ai_family = AF_INET,
        .ai_socktype = SOCK_STREAM,
    };
    struct addrinfo *res;
    struct in_addr *addr;
    int s;

    //使用DNS查找指定webserver的IP地址,结果存储于res
    getaddrinfo(webserver, "80", &hints, &res);
    //从res中获取IP地址
    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);
    ESP_LOGI("network", "... allocated socket");
    //连接指定主机
    connect(s, res->ai_addr, res->ai_addrlen);
    ESP_LOGI("network", "... connected");
    //释放res
    freeaddrinfo(res);
    //向套接字写入hhtp请求
    write(s, httpdata, strlen(httpdata));
    //关闭套接字
    close(s);
}

char tag3[] = " [+] MQTT task III: ";

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: ";
    //字符串拼接,注意其中有flag2和flag3
    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;
    }

    //收到的数据(不包含查询参数)复制到url
    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";

    //收到的数据长度小于 flag和tag3的总长度
    if( a < (int)(sizeof(mqtt_flag_2) + sizeof(tag3) - 1 ) ){
        //将flag附加到out
        memcpy(out,flagdata,a & 0xff);
        //将http请求头填充到httpdata
        sprintf(httpdata,fmt,out);
        //发送http请求
        http_get_task(url,httpdata);
    }          
}

==》程序会向topic_2主题发布消息”www.baidu.com?46

==》程序会根据发布的消息向目标地址(www.baidu.com)发送请求

==》但是当我们给topic_2主题发布自己VPS的地址时,程序也会向VPS的80端口发送请求,请求中包含了flag

image-20240317154652873

image-20240317154639207

==》我们只需要在VPS上监听80端口,等待程序自动发送请求,就可以获得flag:THUCTF{attAck_t0_th3_dev1ce_tcp_r3cV_ch4nnel}

image-20240317154619300

task3

这是个内存破坏的前戏

//字符串拼接,注意其中有flag2和flag3
sprintf(flagdata,"%s%s%s",mqtt_flag_2,tag3,mqtt_flag_3);

//收到的数据长度小于 flag和tag3的总长度
    if( a < (int)(sizeof(mqtt_flag_2) + sizeof(tag3) - 1 ) ){
        //将flag附加到out
        //长度a & 0xff
        memcpy(out,flagdata,a & 0xff);
        //将http请求头填充到httpdata
        sprintf(httpdata,fmt,out);
        //发送http请求
        http_get_task(url,httpdata);
    }          

==》注意此处在进行判断时使用的是有符号数,但是在后续memcpy中长度参数使用a & 0xff就将变为无符号数

==》当传入-1即可绕过限制,将flagdata中的flag3也复制进去

image-20240317154810727

==》监听80端口,获得flag:THUCTF{0ver_the_Air_y0u_c4n_a77ack_t0_1ntranet_d3v1ce}

image-20240317154745804

固件

查看提供的源码可以看到其实还有一个flag:

image-20240317202703776

==》创建了一个以flag为名称的任务==》直接通过esptools提取固件

esptool.py.exe --baud 115200  read_flash 0x10000 0x310000 dump.bin

直接在固件文件中找HUCTF字符串就可以获得flag:HUCTF{DuMp_the_b1n_by_espt00l.py_Ju5t_1n_0ne_Lin3}

image-20240317205947968