IDA識別到函數(shù)名稱方法

背景介紹

??從逆向的角度看,當我們拿到一個二進制需要分析時首先會考慮從函數(shù)搜索、常量字符串等角度找到一個切入口。比如做軟件破解時優(yōu)先根據(jù)界面報錯信息定位驗證函數(shù)、做go二進制逆向時首先根據(jù)函數(shù)名縮小分析范圍。關于字符串的搜集沒什么好說的,掃描二進制搜集不同編碼常量字符串即可。本文主要討論IDA如何將匯編代碼映射到正確函數(shù)名。

0x00 函數(shù)導出表(Export Table)

??當我們在寫代碼調(diào)用一個動態(tài)庫中的文件時,第一步是 LoadLibrary將dll載入內(nèi)存,第二步使用GetProcAddress函數(shù)得到指定函數(shù)的地址。那么GetProcAddress是如何知道某個函數(shù)在內(nèi)存中的地址呢?這就是函數(shù)導出表在發(fā)揮作用。函數(shù)導出表(Export Table)用于記錄可執(zhí)行文件或動態(tài)鏈接庫中需要被其他程序調(diào)用的函數(shù)或變量,通過函數(shù)導出表,其他程序可以獲取到被導出函數(shù)的地址,并通過該地址來調(diào)用相應的函數(shù)。需要注意的是:函數(shù)導出表通常用于Windows操作系統(tǒng)下的動態(tài)鏈接庫(DLL),但是exe中也是可能存在導出函數(shù)的。
IDA中加載exe分析后在Export頁面可以看到函數(shù)導出表信息:

ida查看export信息

從代碼化角度看,可以通過golang的github.com/l0g1n/pefile-go庫解析PE文件的函數(shù)導出表:

pe, err := pefile.NewPEFile(`C:\Users\Spring\Desktop\windowsbrowser.exe`)
if err != nil {
    fmt.Println("Ooopss looks like there was a problem")
    fmt.Println(err)
    os.Exit(2)
}
var imageBase uint64
if pe.OptionalHeader64 != nil {
    imageBase = pe.OptionalHeader64.Data.ImageBase
} else {
    imageBase = uint64(pe.OptionalHeader.Data.ImageBase)
}
fmt.Printf("imageBase:%x\n", imageBase)
if pe.ExportDirectory != nil {
    fmt.Println("\nDIRECTORY_ENTRY_EXPORT\n")
    for _, entry := range pe.ExportDirectory.Exports {
        fmt.Printf("Ordinal:%d Name:%s  Address:0x%x\n", entry.Ordinal, 
                                     string(entry.Name), uint64(entry.Address)+imageBase)
    }
}

其輸出結果如下:

imageBase:400000

DIRECTORY_ENTRY_EXPORT

Ordinal:1 Name:_cgo_dummy_export  Address:0x1dc5430
Ordinal:2 Name:authorizerTrampoline  Address:0xce0910
Ordinal:3 Name:callbackTrampoline  Address:0xce0670
Ordinal:4 Name:clibCommitData  Address:0xc37e80
Ordinal:5 Name:clibFail  Address:0xc37f30
Ordinal:6 Name:clibFinish  Address:0xc37ee0
Ordinal:7 Name:clibLog  Address:0xc37f80
Ordinal:8 Name:clibUpdateStatus  Address:0xc37e10
Ordinal:9 Name:commitHookTrampoline  Address:0xce0800
Ordinal:10 Name:compareTrampoline  Address:0xce0770
Ordinal:11 Name:doneTrampoline  Address:0xce0730
Ordinal:12 Name:preUpdateHookTrampoline  Address:0xce0990
Ordinal:13 Name:rollbackHookTrampoline  Address:0xce0860
Ordinal:14 Name:stepTrampoline  Address:0xce06d0
Ordinal:15 Name:updateHookTrampoline  Address:0xce08a0

0x01 調(diào)試信息(DWARF)

??正常情況下,gcc在編譯時會抹掉代碼中函數(shù)名信息而轉為地址。那么當我們在做開發(fā)過程中需要對程序進行調(diào)試時,一定要在gcc編譯時添加-g參數(shù)才可以使用gdb進行單行跟蹤。那么gcc的-g到底做了什么事情就可以讓gdb輕松知道當前程序運行在了第一行,其函數(shù)名稱是什么呢? 這就是DWARF在發(fā)揮作用。
??DWARF(Debugging With Attributed Record Formats)是一種調(diào)試信息格式,主要用于將源代碼和可執(zhí)行文件中的調(diào)試信息進行關聯(lián)。它可以幫助開發(fā)人員在程序崩潰或出現(xiàn)錯誤時,快速地定位問題并進行修復。DWARF記錄了源代碼中的變量名、類型信息、函數(shù)名等,它同時支持多種編程語言,包括C、C++、Go等,可以為不同的編程語言提供相應的調(diào)試信息。
??使用以下代碼,在Linux平臺使用gcc8.3編譯作為例子:

#include <stdio.h>
int main(int argc, char* argv[])
{
    printf("hello\n");
}

使用 gcc main1.c -g -o main編譯.使用readelf -S main 命令查看文件的所有節(jié)信息,包括存儲了dwarf信息的.debug_info節(jié)信息:

There are 35 section headers, starting at offset 0x41a0:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
... 省略很多不重要的節(jié)信息
  [27] .debug_aranges    PROGBITS         0000000000000000  0000304c
       0000000000000030  0000000000000000           0     0     1
  [28] .debug_info       PROGBITS         0000000000000000  0000307c
       000000000000032a  0000000000000000           0     0     1
  [29] .debug_abbrev     PROGBITS         0000000000000000  000033a6
       00000000000000e1  0000000000000000           0     0     1
  [30] .debug_line       PROGBITS         0000000000000000  00003487
       0000000000000111  0000000000000000           0     0     1
  [31] .debug_str        PROGBITS         0000000000000000  00003598
       0000000000000239  0000000000000001  MS       0     0     1
  [32] .symtab           SYMTAB           0000000000000000  000037d8
       0000000000000678  0000000000000018          33    50     8
  [33] .strtab           STRTAB           0000000000000000  00003e50
       0000000000000203  0000000000000000           0     0     1

使用dwarfdump讀取main的調(diào)試信息,以下是部分輸出:

< 1><0x000002e2>    DW_TAG_subprogram
                      DW_AT_external              yes(1)
                      DW_AT_name                  main
                      DW_AT_decl_file             0x00000001 /home/spring/main1.c
                      DW_AT_decl_line             0x00000002
                      DW_AT_decl_column           0x00000005
                      DW_AT_prototyped            yes(1)
                      DW_AT_type                  <0x00000065>
                      DW_AT_low_pc                0x00001135
                      DW_AT_high_pc               <offset-from-lowpc>34
                      DW_AT_frame_base            len 0x0001: 9c: DW_OP_call_frame_cfa
                      DW_AT_GNU_all_tail_call_sites yes(1)
                      DW_AT_sibling               <0x00000323>
< 2><0x00000304>      DW_TAG_formal_parameter
                        DW_AT_name                  argc
                        DW_AT_decl_file             0x00000001 /home/spring/main1.c
                        DW_AT_decl_line             0x00000002
                        DW_AT_decl_column           0x0000000e
                        DW_AT_type                  <0x00000065>
                        DW_AT_location              len 0x0002: 916c: DW_OP_fbreg -20
< 2><0x00000313>      DW_TAG_formal_parameter
                        DW_AT_name                  argv
                        DW_AT_decl_file             0x00000001 /home/spring/main1.c
                        DW_AT_decl_line             0x00000002
                        DW_AT_decl_column           0x0000001a
                        DW_AT_type                  <0x00000323>
                        DW_AT_location              len 0x0002: 9160: DW_OP_fbreg -32

從上面輸出至少可以得到以下信息:
/home/spring/main1.c第2行有一個函數(shù)名稱為main,有2個參數(shù),分別是 int argc和char** argv。main函數(shù)在內(nèi)存中地址起始位置是 0x00001135,長度是 34個字節(jié)。
當然基于開源庫 libdwarf (https://github.com/davea42/libdwarf-code)我們也可以定制化輸出內(nèi)容。
比如基于libdwarf封裝了一個go庫用于輸出調(diào)試信息中所有函數(shù)名和對應虛擬地址,其輸出如下:

symbol name: main => address : 0x00001135

0x02 go語言符號特征

??參考文章: 如何消除Go的編譯特征.md
??使用go build .進行編譯,會連符號和調(diào)試信息一起編譯到里面。這里的調(diào)試信息是剛才說的DWARF信息。當使用go build -ldflags "-s -w" main.go編譯時可以移除DWARF調(diào)試信息,但是依然會攜帶很多go語言特征,比如go函數(shù)名稱與地址映射表。文章中提到:gostrip可以混淆函數(shù)名,結構體名稱,文件名等諸多編譯特征。換一個思路想:我們也基于gostrip做二次開發(fā),讀取go語言二進制中函數(shù)名和地址信息。
基于go-strip二次開發(fā)后獲取函數(shù)信息后輸出如下:

Name:runtime/debug.ParseBuildInfo.func2 Entry:50db00
Name:runtime/debug.ParseBuildInfo Entry:50dc80
Name:runtime/debug.ParseBuildInfo.func1 Entry:50eea0
Name:main.main Entry:51b700
Name:main.main.func1 Entry:51c320

0x03 FLIRT

??無論導出函數(shù),調(diào)試信息還是go語言符號特征都是基于文件結構分析直接得到的準確函數(shù)信息,那么針對使用靜態(tài)鏈接不不包含調(diào)試信息的C程序怎么辦呢?IDA有一個特色功能是FLIRT。FLIRT全稱是 Fast Library Identification and Recognition Technology。該技術利用庫文件中二進制函數(shù)的機器碼,來快速識別文件中的庫函數(shù),使得反匯編代碼可讀性更強。在IDA的安裝包中sig文件夾下其實包含了很多標準庫函數(shù)的特征信息,如下圖所示:

ida自帶的sig文件
如果在逆向過程中發(fā)現(xiàn)使用了某些三方庫,也可以自行制作sig符號庫,可以參考教程 : https://www.freebuf.com/articles/endpoint/235070.html

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容