10.4 認識Capstone反彙編引擎

lyshark發表於2023-10-06

Capstone 是一款開源的反彙編框架,目前該引擎支援的CPU架構包括x86、x64、ARM、MIPS、POWERPC、SPARC等,Capstone 的特點是快速、輕量級、易於使用,它可以良好地處理各種型別的指令,支援將指令轉換成AT&T彙編語法或Intel彙編語法等多種格式。Capstone的庫可以整合到許多不同的應用程式和工具中,因此被廣泛應用於反彙編、逆向工程、漏洞分析和入侵檢測等領域,著名的比如IDA Pro、Ghidra、Hopper Disassembler等偵錯程式都在使用該引擎。

讀者可自行下載符合條件的版本,這裡筆者選擇的是capstone-4.0.2-win32版本,下載並解壓這個版本,當讀者解壓後以後即可在專案中引用該引擎,Capstone引擎的配置非常容易,僅僅需要配置引用目錄及庫目錄即可,配置完成如下圖所示;

實現反彙編的第一步則是開啟一個可執行檔案,通常在引擎內可呼叫cs_open()函式實現開啟,當開啟成功時則該函式會返回一個控制程式碼(handle)用來進行後續的反彙編操作,函式的原型通常如下:

cs_err cs_open
(
    cs_arch arch, 
    cs_mode mode, 
    csh *handle
)

其中,各引數的含義如下:

  • arch:指定要開啟的CPU架構,例如CS_ARCH_X86表示x86架構,CS_ARCH_ARM表示ARM架構等。
  • mode:指定CPU的模式,例如CS_MODE_32表示32位模式,CS_MODE_64表示64位模式等。
  • handle:一個指標,用於返回開啟成功後的控制程式碼handle。

如上所示,函式返回值為cs_err型別,表示函式執行的狀態或錯誤碼,它是一個列舉型別,當函式執行成功時返回的數值為CS_ERR_OK,其次函式的第一個引數是指定CPU架構為x86,第二個引數是指定模式為32位模式,最後一個引數用來返回(handle)控制程式碼。

當一個程式被開啟後,則下一步可以透過呼叫cs_disasm()函式來實現對開啟檔案的反彙編,cs_disasm函式是Capstone反彙編框架中的一個函式,用於對指定的二進位制資料進行反彙編,返回解碼後的指令資訊。函式原型通常如下:

size_t cs_disasm
(
    csh handle,
    const uint8_t *code,
    size_t code_size,
    uint64_t address,
    size_t count,
    cs_insn *insn
);

其中,各引數的含義如下:

  • handle:反彙編器控制程式碼,表示使用哪一個Capstone例項來執行反彙編操作。
  • code:待反彙編的二進位制資料的指標,可以是一個地址。
  • code_size:待反彙編的資料的長度,以位元組為單位。
  • address:指定待反彙編資料的地址,通常為起始地址。
  • count:指定要反彙編的指令數,如果為0,則會一直反彙編至遇到code_size終止。
  • insn:指向用於儲存反彙編結果的cs_insn結構體物件指標,在函式呼叫結束後儲存反彙編結果。

函式返回值為size_t型別,代表解碼的指令數量。在cs_disasm()函式中,我們透過將待反彙編的資料以及其它必要的引數傳遞給該函式,然後使用cs_insn結構體物件來儲存反彙編結果。透過該函式,我們可以獲取指令的指令助記符、指令運算元、定址模式、使用的暫存器等資訊。

當讀者理解了這兩個API介面後,那麼反彙編實現將變得很容易實現,我們來看一下官方針對反彙編實現的一種方式,我們自行封裝一個DisassembleCode()函式,該函式傳入機器碼字串以及該字串的長度則會輸出該字串的反彙編程式碼片段,這段程式碼的實現如下所示;

#include <stdio.h>
#include <inttypes.h>
#include <capstone/capstone.h>

#pragma comment(lib,"capstone32.lib")

// 反彙編字串
void DisassembleCode(char *start_offset, int size)
{
    csh handle;
    cs_insn *insn;
    size_t count;

    char *buffer = "\x55\x8b\xec\x81\xec\x24\x03\x00\x00\x6a\x17\x90\x90\x90";

    // 開啟控制程式碼
    if (cs_open(CS_ARCH_X86, CS_MODE_32, &handle) != CS_ERR_OK)
    {
        return;
    }

    // 反彙編程式碼,地址從0x1000開始,返回總條數
    count = cs_disasm(handle, (unsigned char *)start_offset, size, 0x1000, 0, &insn);

    if (count > 0)
    {
        size_t index;
        for (index = 0; index < count; index++)
        {
            for (int x = 0; x < insn[index].size; x++)
            {
                printf("機器碼: %d -> %02X \n", x, insn[index].bytes[x]);
            }

            printf("地址: 0x%"PRIx64" | 長度: %d 反彙編: %s %s \n", insn[index].address, insn[index].size, insn[index].mnemonic, insn[index].op_str);
        }

        cs_free(insn, count);
    }
    else
    {
        printf("反彙編返回長度為空 \n");
    }

    cs_close(&handle);
}

int main(int argc, char *argv[])
{
    char *buffer = "\x55\x8b\xec\x81\xec\x24\x03\x00\x00\x6a\x17\x90\x90\x90";
    DisassembleCode(buffer, 14);

    system("pause");
    return 0;
}

執行上述程式碼片段,則可看到如下圖所示的輸出效果;

上述程式碼雖然實現了反彙編但並無法儲存結果,對於一個通用程式來說,我們當然是希望這寫反彙編程式碼能夠儲存到一個特殊的容器內,當需要使用是可以隨時調出來,此處我們透過定義一個MyStruct並將所需反彙編指令透過ptr.push_back(location)放入到一個全域性容器內進行儲存,當讀者呼叫DisassembleCode(buffer, 14)函式是則會返回std::vector<MyStruct> ptr,並在主函式內透過迴圈輸出這個容器,改進後的程式碼將會更加易於使用;

#include <iostream>
#include <vector>
#include <inttypes.h>
#include <capstone/capstone.h>

#pragma comment(lib,"capstone32.lib")

using namespace std;

typedef struct
{
    int OpCodeSize;
    int OpStringSize;
    unsigned long long Address;
    unsigned char OpCode[16];
    char OpString[256];
}MyStruct;

static void print_string_hex(unsigned char *str, size_t len)
{
    unsigned char *c;
    for (c = str; c < str + len; c++)
    {
        printf("0x%02x ", *c & 0xff);
    }
    printf("\n");
}

// 反彙編字串
std::vector<MyStruct> DisassembleCode(char *start_offset, int size)
{
    std::vector<MyStruct> ptr = {};

    csh handle;
    cs_insn *insn;
    size_t count;

    // 開啟控制程式碼
    if (cs_open(CS_ARCH_X86, CS_MODE_32, &handle) != CS_ERR_OK)
    {
        return{};
    }

    // 反彙編程式碼,地址從0x1000開始,返回總條數
    count = cs_disasm(handle, (unsigned char *)start_offset, size, 0x0, 0, &insn);

    if (count > 0)
    {
        size_t index;

        // 迴圈反彙編程式碼
        for (index = 0; index < count; index++)
        {
            // 清空
            MyStruct location;
            memset(&location, 0, sizeof(MyStruct));

            // 迴圈複製機器碼
            for (int x = 0; x < insn[index].size; x++)
            {
                location.OpCode[x] = insn[index].bytes[x];
            }

            // 複製地址長度
            location.Address = insn[index].address;
            location.OpCodeSize = insn[index].size;

            // 複製反彙編指令
            strcpy_s(location.OpString, insn[index].mnemonic);
            strcat_s(location.OpString, " ");
            strcat_s(location.OpString, insn[index].op_str);

            // 得到反彙編長度
            location.OpStringSize = strlen(location.OpString);

            ptr.push_back(location);
        }
        cs_free(insn, count);
    }
    else
    {
        return{};
    }
    cs_close(&handle);
    return ptr;
}

int main(int argc, char *argv[])
{
    char *buffer = "\x55\x8b\xec\x81\xec\x24\x03\x00\x00\x6a\x17\x90\x90\x90";

    // 反彙編並返回容器
    std::vector<MyStruct> ptr = DisassembleCode(buffer, 14);

    // 迴圈整個容器
    for (int x = 0; x < ptr.size(); x++)
    {
        // 輸出地址
        printf("%08X | ", ptr[x].Address);
        printf("%03d | ", ptr[x].OpStringSize);

        // 輸出反彙編
        for (int z = 0; z < ptr[x].OpStringSize; z++)
        {
            printf("%c", ptr[x].OpString[z]);
        }
        printf("\n");

        // 輸出機器碼
        for (int y = 0; y < ptr[x].OpCodeSize; y++)
        {
            printf("%02X ", ptr[x].OpCode[y]);
        }

        printf("\n");
        // print_string_hex(ptr[x].OpCode, ptr[x].OpCodeSize);
    }

    system("pause");
    return 0;
}

執行後輸出效果圖如下圖所示;

本文作者: 王瑞
本文連結: https://www.lyshark.com/post/b277703.html
版權宣告: 本部落格所有文章除特別宣告外,均採用 BY-NC-SA 許可協議。轉載請註明出處!

相關文章