二进制函数_Go二进制文件逆向分析从基础到进阶——MetaInfo、函数符号和源码文件路径列表...
生活随笔
收集整理的這篇文章主要介紹了
二进制函数_Go二进制文件逆向分析从基础到进阶——MetaInfo、函数符号和源码文件路径列表...
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
書接前文,本文主要介紹 Go 二進(jìn)制文件中 Meta Information 的解析,與函數(shù)符號(hào)和源碼文件路徑列表的提取。最后詳細(xì)介紹一下?Moduledata?這個(gè)結(jié)構(gòu)。傳送門:Go二進(jìn)制文件逆向分析從基礎(chǔ)到進(jìn)階——綜述05Meta information>>>>通過 IDAPro 中的 strings 檢索: 2. 通過 Go 語言官方命令?$ go version [FILE]?,該命令不指定文件則打印本地安裝的 Go 語言版本,指定文件則打印目標(biāo)文件對(duì)應(yīng)的 Go 語言版本:>>>>
— Go Module Docs詳情參考:《Go Modules in Real Life》相應(yīng)地,Moduledata?在 Go 二進(jìn)制文件中也是一個(gè)更高層次的數(shù)據(jù)結(jié)構(gòu),它包含很多其他結(jié)構(gòu)的索引信息,可以看作是 Go 二進(jìn)制文件中 RTSI(Runtime Symbol Information) 和 RTTI(Runtime Type Information) 的?地圖。Moduledata?源碼定義如下(關(guān)鍵字段看注釋):type moduledata struct { pclntable []byte // pclntab address ftab []functab // function table address filetab []uint32 // source file table address findfunctab uintptr minpc, maxpc uintptr // minpc: first pc(function) address text, etext uintptr // [.text] section start/end address noptrdata, enoptrdata uintptr data, edata uintptr // [.data] section start/end address bss, ebss uintptr // [.bss] section start/end address noptrbss, enoptrbss uintptr // [.noptrbss] section start/end address end, gcdata, gcbss uintptr types, etypes uintptr // types data start/end address textsectmap []textsect typelinks []int32 // offset table for types itablinks []*itab // interface table ptab []ptabEntry pluginpath string pkghashes []modulehash modulename string modulehashes []modulehash hasmain uint8 // 1 if module contains the main function, 0 otherwise gcdatamask, gcbssmask bitvector typemap map[typeOff]*_type // offset to *_rtype in previous module bad bool // module failed to load and should be ignored next *moduledata}根據(jù) Moduledata 的定義,Moduledata 是可以串成鏈表的形式的,而一個(gè)完整的可執(zhí)行 Go 二進(jìn)制文件中,只有一個(gè) firstmoduledata 包含如上完整的字段。簡(jiǎn)單介紹一下關(guān)鍵字段:go build go build -ldflags “-s -w” go build -buildmode=pie -ldflags “-s -w” 可以看到,第三種編譯方式生成的 PIE 文件,是沒有?.gopclntab?這個(gè) Section 的,而是多了一些重定向相關(guān)的 Section。這也是前文《Go二進(jìn)制文件逆向分析從基礎(chǔ)到進(jìn)階——綜述》中?redress?工具報(bào)錯(cuò)的原因:它根據(jù) Section Name 查找?pclntabl?的思路,對(duì) PIE 文件無效。既然靠 Section Name 來定位?pclntab?的方式不靠譜,其他能用的方式就只剩暴力搜索?pclntab?的 Magic Number 了。通過研究發(fā)現(xiàn),無論是否 PIE ,ELF 文件中的firstmoduledata?總是在?.noptrdata?這個(gè) Section 里,PE 文件中可能會(huì)在?.data?或?.noptrdata?Section,而 MachO 文件中的?firstmoduledata?會(huì)在?__noptrdata?Section 中。如此一來,就可以按 uintptr 為單元遍歷目標(biāo) Section,檢查每個(gè) uintptr 指向的地址前 4 字節(jié)是否為?pclntab?的 Magic Number?0xFFFFFFFB,找到這個(gè)位置,就差不多了。偽碼可表示為:Dword(uintptr(possible_firstmoduledata_addr)) & 0xFFFFFFFF == MagicNumber之所以說?差不多,是因?yàn)槎M(jìn)制文件中可能還有別的值為?0xFFFFFFFB,并不是所有這個(gè)值的位置都是 pclntab。找到這個(gè)值,還要精準(zhǔn)驗(yàn)證它是不是?pclntab,以及上級(jí)數(shù)據(jù)結(jié)構(gòu)是不是?firstmoduledata?。
5.1?Go Build ID
每一個(gè) Go 二進(jìn)制文件內(nèi),都有一個(gè)獨(dú)一無二的 Build ID,詳情參考?src/cmd/go/internal/work/buildid.go。Go Build ID 可以用以下命令來查看:$ go tool buildid 對(duì)于 ELF 文件,也可以用?readelf?命令查看,不過看到的只是 Hex String:轉(zhuǎn)換一下上圖的 Hex String,就是?$ go buildid tracker_nonstrip?的顯示結(jié)果了:另外,如果是用?gccgo?編譯的 ELF 文件,用?file?命令 或者?readelf?命令還能看到?GNU Build ID,注意區(qū)分一下:Build ID 是 Go 二進(jìn)制文件中的元信息之一,但是對(duì)逆向分析二進(jìn)制文件并沒有什么實(shí)際幫助,本文只做簡(jiǎn)單介紹。>>>>
5.2?Go Version
Go 二進(jìn)制文件中,還會(huì)把構(gòu)建本文件的 Go 語言版本號(hào)打包進(jìn)去。查看方式有以下 2 種:5.3?GoROOT
GOROOT 是 Go 語言的安裝路徑,里面有 Go 的標(biāo)準(zhǔn)庫和自帶的命令行工具。一般來說,Go 的二進(jìn)制文件中都會(huì)把?GOROOT?打包進(jìn)去,并在?runtime.GOROOT()?函數(shù)中會(huì)用到這個(gè)值。比如某 Go 樣本中打包的?runtime.GOROOT()?函數(shù),可以看到構(gòu)建該樣本所在主機(jī)的?GOROOT?路徑是?/usr/lib/go?:看 Go 標(biāo)準(zhǔn)庫中?runtime.GOROOT()?函數(shù)的源碼,會(huì)發(fā)現(xiàn)這個(gè)路徑其實(shí)叫?sys.DefaultGoroot?:至于如何提取這個(gè)字符串,就需要在 IDAPro 中先把函數(shù)名符號(hào)恢復(fù)出來,然后根據(jù)函數(shù)名找到?runtime.GOROOT()?這個(gè)函數(shù),最后在這個(gè)函數(shù)中提取出?sys.DefaultGoroot?的值。提取方法可能有多種,go_parser for IDAPro 中的方法是先解析該函數(shù)的?FlowChart?,找到最后帶 return 的 FlowChart,然后再找出該值的地址。那么知道了這個(gè)信息,對(duì)逆向分析有什么用處?后文描述如何提取、過濾 Go 二進(jìn)制文件中的源碼路徑列表時(shí)會(huì)用到。06pclntab>>>>6.1?pclntab介紹
pclntab 全名是 Program Counter Line Table,可直譯為?程序計(jì)數(shù)器行數(shù)映射表, 在 Go 中也叫 Runtime Symbol Table, 所以我把它里面包含的信息叫做 RTSI(Runtime Symbol Information)。2013 年由?Russ Cox?(Go 語言創(chuàng)始團(tuán)隊(duì)成員,核心開發(fā)者)從?Plan9?移植到?Go 1.2?上,至今沒有太大變化。至于 PC(Program Counter),是給 Go 語言的 runtime 來操作的,可以粗淺理解為 runtime 當(dāng)前執(zhí)行的程序代碼(或指令)。按 Russ Cox 在《Go 1.2 Runtime Symbol Information》中的說法,引入?pcnlntab?這個(gè)結(jié)構(gòu)的最初動(dòng)機(jī),是為?Stack Trace?服務(wù)的。參考前文《Go二進(jìn)制文件逆向分析從基礎(chǔ)到進(jìn)階——綜述》中?redress?工具的報(bào)錯(cuò)信息,當(dāng)程序運(yùn)行出錯(cuò)要?panic?的時(shí)候,runtime 需要知道當(dāng)前的位置,層級(jí)關(guān)系如 pkg->src file->function or method->line number,每一層的信息 runtime 都要知道。Go 就把這些信息結(jié)構(gòu)化地打包到了編譯出的二進(jìn)制文件中。除此之外,pcnlntab?中還包含了棧的動(dòng)態(tài)管理用到的棧幀信息、垃圾回收用到的棧變量的生命周期信息以及二進(jìn)制文件涉及的所有源碼文件路徑信息。pclntab?的概要結(jié)構(gòu)如下(其中的?strings?指的是 Function name string、Source File Path string,并不是整個(gè)程序中用到的所有 strings):pclntab:- func table
- _func structs
- pcsp
- psfile
- pcline
- pcdata
- strings
- file table
- pclntab?開頭 4-Bytes 是從 Go1.2 至今不變的?Magic Number:?0xFFFFFFFB?;
- 第 5、6個(gè)字節(jié)為 0x00,暫無實(shí)際用途;
- 第 7 個(gè)字節(jié)代表?instruction size quantum,?1?為 x86, 4 為 ARM;
- 第 8 個(gè)字節(jié)為地址的大小,32bit 的為 4,64 bit 的為 8,至此的前 8 個(gè)字節(jié)可以看作是?pclntab?的 Header;
- 第 9 個(gè)字節(jié)開始是?function table?的起始位置,第一個(gè) uintptr 元素為函數(shù)(pc, Program Counter) 的個(gè)數(shù);
- 第 2 個(gè) uintptr 元素為第 1 個(gè)函數(shù)(pc0) 的地址,第 3 個(gè) uintptr 元素為第 1 個(gè)函數(shù)結(jié)構(gòu)定義相對(duì)于?pclntab?的偏移,后面的函數(shù)信息就以此類推;
- 直到 function table 結(jié)束,下面就是 Source file table。Source file table 以 4 字節(jié)(int32)為單位,前 4 個(gè)字節(jié)代表 Source File 的數(shù)量,后面每一個(gè)?int32?都代表一個(gè) Source File Path String 相對(duì)于?pclntab?的偏移;
- uintptr?代表一個(gè)指針類型,在 32bit 二進(jìn)制文件中,等價(jià)于?uint32,在 64bit 二進(jìn)制文件中,等價(jià)于?uint64?。
6.2 函數(shù)表
函數(shù)表(func table)的起始地址,為?(pclntab_addr + 8),第一個(gè)元素( uintptr N) 代表函數(shù)的個(gè)數(shù),如上圖中的?0x1C3A。解析來就是每?jī)蓚€(gè) uintptr 元素為一組,即?(func_addr, func_struct_offset),每組第一個(gè)元素為函數(shù)的地址,第二個(gè)元素為函數(shù)結(jié)構(gòu)體定義相對(duì)于?pclntab?起始地址的偏移。即:func_struct_addr = pclntab_addr + func_struct_offset# 以上圖第一個(gè)函數(shù) internal_cpu_Initialize() 為例,pclntab 的地址為 0x8FBFA0,它的 func_struct_offset 為 0x1C3C0# 那么它的 func struct address 為:0x8FBFA0 + 0x1C3C0 = 0x918360地址 0x918360 處的 Function Struct 是什么模樣?下圖為?go_parser在 IDAPro 中解析好的結(jié)果:Function Struct 在 Russ Cox 的《Go 1.2 Runtime Symbol Information》有介紹:struct Func{ uintptr entry; // start pc int32 name; // name (offset to C string) int32 args; // size of arguments passed to function int32 frame; // size of function frame, including saved caller PC int32 pcsp; // pcsp table (offset to pcvalue table) int32 pcfile; // pcfile table (offset to pcvalue table) int32 pcln; // pcln table (offset to pcvalue table) int32 nfuncdata; // number of entries in funcdata list int32 npcdata; // number of entries in pcdata list};對(duì)于逆向分析來說,這里最有用的信息,就是函數(shù)名了。如上圖所示,函數(shù)名是一個(gè)以?0x00?結(jié)尾的 C-String。在 Function Struct 中,第二個(gè)元素只是 int32 類型的偏移值,而函數(shù)名字符串的地址,則用以下方式計(jì)算得出:func_name_addr = pclntab_addr + Dword(func_stuct_addr + sizeof(uintptr))# 比如上面函數(shù) internal_cpu_Initialize() 的 func_struct_addr 為 0x918360,# 它的 Func Name String Offset 為 0x1C408,那么它的 Func Name String Address 就是0x8FBFA0(pclntab_addr) + 0x1C408 = 0x9183A8如此一來,就從 Go 二進(jìn)制文件中恢復(fù)出了一個(gè)函數(shù)的名字。用這種方式遍歷 Function Table,便可以恢復(fù)所有函數(shù)名。有的師傅可能還會(huì)問,Function Struct 中第 3 個(gè)元素?args?,有沒有對(duì)逆向分析有用的信息?如果知道函數(shù)的參數(shù)、返回值信息,豈不更爽。這個(gè)……曾經(jīng)有,現(xiàn)在,真沒有了。在 Go 標(biāo)準(zhǔn)庫源碼?src/debug/gosym/symtab.go中解析這個(gè) Function Struct 的一個(gè)類型定義中,有兩條注釋,說 Go 1.3 之后就沒這種信息了:另外,還有一些函數(shù)用上面的方式無法解析,是編譯器做循環(huán)展開時(shí)自動(dòng)生成的匿名函數(shù),也叫?Duff’s Device),函數(shù)體型如下圖:這樣的函數(shù)知道它是用來連續(xù)操作內(nèi)存(拷貝、清零等等)的就可以。>>>>6.3?源碼文件路徑
在 pclntab 中,函數(shù)表下面隔一個(gè) uintptr 的位置,就是源碼文件表(Srouce File Table) 的?偏移,長(zhǎng)度為 4-Bytes:這個(gè)偏移,是相對(duì)?pclntab?的起始地址來說的。接著上面的例子,pclntab?的地址為 0x8FBFA0,此處偏移量為 0x22FDD0 ,那么源碼文件路徑列表的地址就是:srcfiletab_addr = pclntab_addr + srcfiletab_offset0x8FBFA0 + 0x22FDD0 = 0xB2BD70在 IDAPro 中,0xB2BD70 處?go_parser解析好的效果如下:Source File Table 中的元素全都以 4-Bytes(uint32) 為單位,第一個(gè)元素(0x2CF)是本二進(jìn)制文件涉及的所有源碼文件的個(gè)數(shù),包括標(biāo)準(zhǔn)庫的源碼文件、第三方 Pacakge 的源碼文件以及當(dāng)前項(xiàng)目的源碼文件。后續(xù)每個(gè) uint32 元素代表一個(gè)相對(duì)于?pclntab?的偏移量,該偏移量加上?pclntab?的起始地址,即為相應(yīng)源碼文件路徑字符串的起始地址。每個(gè)源碼文件路徑名都是以?0x00?結(jié)尾的 C-String。go_parser對(duì)此的處理,是計(jì)算出每個(gè)偏移量對(duì)應(yīng)的地址,然后創(chuàng)建字符串,并為當(dāng)前偏移所在的地址創(chuàng)建 data reference,在 IDAPro 中雙擊 data reference 即可跳轉(zhuǎn)到相應(yīng)的字符串地址。比如,第一個(gè)源碼文件的偏移量為 0x239134,那么它對(duì)應(yīng)的實(shí)際地址為:0x8FBFA0(pclntab_addr) + 0x239134 = 0xB350D4查看地址 0xB350D4 處的字符串即可印證:另外,?go_parser如果成功定位到?runtime.GOROOT()?函數(shù),并解析出?sys.DefaultGoroot?的值,還會(huì)根據(jù)這個(gè)值過濾掉所有標(biāo)準(zhǔn)庫源碼文件路徑和第三方 Package 源碼文件路徑,在 IDAPro 的 Console 中只打印本項(xiàng)目相關(guān)的所有源碼文件路徑;如果沒能提取出?sys.DefaultGoroot?,則會(huì)打印所有源碼文件路徑。DDG 樣本中的源碼文件路徑列表如下所示(IDAPro Console):07moduledata>>>>7.1?moduledate 介紹
在 Go 語言的體系中,Module 是比 Package 更高層次的概念,具體表現(xiàn)在一個(gè) Module 中可以包含多個(gè)不同的 Package,而每個(gè) Package 中可以包含多個(gè)目錄和很多的源碼文件。“A module is a collection of related Go packages that are versioned together as a single unit.”*— Go Module Docs詳情參考:《Go Modules in Real Life》相應(yīng)地,Moduledata?在 Go 二進(jìn)制文件中也是一個(gè)更高層次的數(shù)據(jù)結(jié)構(gòu),它包含很多其他結(jié)構(gòu)的索引信息,可以看作是 Go 二進(jìn)制文件中 RTSI(Runtime Symbol Information) 和 RTTI(Runtime Type Information) 的?地圖。Moduledata?源碼定義如下(關(guān)鍵字段看注釋):type moduledata struct { pclntable []byte // pclntab address ftab []functab // function table address filetab []uint32 // source file table address findfunctab uintptr minpc, maxpc uintptr // minpc: first pc(function) address text, etext uintptr // [.text] section start/end address noptrdata, enoptrdata uintptr data, edata uintptr // [.data] section start/end address bss, ebss uintptr // [.bss] section start/end address noptrbss, enoptrbss uintptr // [.noptrbss] section start/end address end, gcdata, gcbss uintptr types, etypes uintptr // types data start/end address textsectmap []textsect typelinks []int32 // offset table for types itablinks []*itab // interface table ptab []ptabEntry pluginpath string pkghashes []modulehash modulename string modulehashes []modulehash hasmain uint8 // 1 if module contains the main function, 0 otherwise gcdatamask, gcbssmask bitvector typemap map[typeOff]*_type // offset to *_rtype in previous module bad bool // module failed to load and should be ignored next *moduledata}根據(jù) Moduledata 的定義,Moduledata 是可以串成鏈表的形式的,而一個(gè)完整的可執(zhí)行 Go 二進(jìn)制文件中,只有一個(gè) firstmoduledata 包含如上完整的字段。簡(jiǎn)單介紹一下關(guān)鍵字段:
- 第 1 個(gè)字段?pclntable,即為?pclntab?的地址;
- 第 2 個(gè)字段?ftab,為?pclntab?中 Function Table 的地址(=pclntab_addr + 8);
- 第 3 個(gè)字段?filetab,為?pclntab?中 Source File Table 的地址;
- 第 5 個(gè)字段?minpc,為?pclntab?中第一個(gè)函數(shù)的起始地址;
- 第 7 個(gè)字段?text,在普通二進(jìn)制文件中,對(duì)應(yīng)于 [.text] section 的起始地址;在 PIE 二進(jìn)制文件中則沒有這個(gè)要求;
- 字段?types,為存放程序中所有數(shù)據(jù)類型定義信息數(shù)據(jù)的起始地址,一般對(duì)應(yīng)于 [.rodata] section 的地址;
- 字段?typelinks,為每個(gè)數(shù)據(jù)類型相對(duì)于?types?地址的偏移表,該字段與?types?字段在后文解析 RTTI 時(shí)要用到;
- 字段?itablinks,則是 Interface Table 的起始地址,該字段解析 Interface Table 時(shí)要用到。
7.2?firstmoduledata 定位
通過上面的介紹可以知道,如果在 Go 二進(jìn)制文件中事先找到 firstmoduledata 這個(gè)結(jié)構(gòu),然后在這個(gè)結(jié)構(gòu)中就可以按圖索驥找到其他我們感興趣的信息。那么如何在一個(gè) Go 二進(jìn)制文件中精準(zhǔn)定位到?firstmoduledata?呢?很直觀就想到?pclntab,原因有兩點(diǎn):一是它對(duì)應(yīng)于?firstmoduledata?結(jié)構(gòu)的第一個(gè)字段;最主要的是?pclntab?包含的信息豐富,起始位置還有獨(dú)特的 4-Bytes 的 Magic Number。一旦找到了?pclntab?,就可以定位到?firstmoduledata。7.2.1 定位 pclntab
定位?pclntab?的方法,公開的相關(guān)工具里用的最多的,是根據(jù)二進(jìn)制文件中的 Section Name 來定位。因?yàn)槠匠R姷降?Go 二進(jìn)制文件,PE 文件和 ELF 文件中?.gopclntab?Section 就對(duì)應(yīng)于?pclntab?結(jié)構(gòu),MachO 文件中?__gopclntab?Section 對(duì)應(yīng)于?pclntab?結(jié)構(gòu)。然而這種方法并不靠譜。最主要的原因,是?PIE?二進(jìn)制文件。PIE 全稱為?Position Independent Executable,意思是地址無關(guān)的可執(zhí)行文件,這個(gè)類型的二進(jìn)制文件結(jié)合 ASLR 技術(shù)可以加強(qiáng) Go 二進(jìn)制文件自身安全性。這對(duì) Go 自身的內(nèi)存安全機(jī)制來說,是個(gè)錦上添花的特性。Go 的?build/install?命令在構(gòu)建二進(jìn)制文件時(shí),有幾個(gè)?buildmode?選項(xiàng):? go help buildmodeThe 'go build' and 'go install' commands take a -buildmode argument whichindicates which kind of object file is to be built. Currently supported valuesare: -buildmode=archive Build the listed non-main packages into .a files. Packages named main are ignored. -buildmode=c-archive Build the listed main package, plus all packages it imports, into a C archive file. The only callable symbols will be those functions exported using a cgo //export comment. Requires exactly one main package to be listed. -buildmode=c-shared Build the listed main package, plus all packages it imports, into a C shared library. The only callable symbols will be those functions exported using a cgo //export comment. Requires exactly one main package to be listed. -buildmode=default Listed main packages are built into executables and listed non-main packages are built into .a files (the default behavior). -buildmode=shared Combine all the listed non-main packages into a single shared library that will be used when building with the -linkshared option. Packages named main are ignored. -buildmode=exe Build the listed main packages and everything they import into executables. Packages not named main are ignored. -buildmode=pie Build the listed main packages and everything they import into position independent executables (PIE). Packages not named main are ignored. -buildmode=plugin Build the listed main packages, plus all packages that they import, into a Go plugin. Packages not named main are ignored.Go 在構(gòu)建可執(zhí)行 ELF 文件時(shí),目前還是默認(rèn)使用?-buildmode=exe,而從 2019 年 4 月底 Go 語言官方?Change Log 203606開始,在 Windows 上構(gòu)建可執(zhí)行 PE 文件時(shí),則會(huì)默認(rèn)使用?-buildmode=pie:下圖是同一個(gè)項(xiàng)目,用 3 種不同的編譯方式生成的可執(zhí)行 ELF 文件 Section Headers 對(duì)比,從左至右編譯方式依次為:7.2.2 驗(yàn)證 firstmoduledata
按照上述思路,找到一個(gè)可能的?firstmoduledata 起始地址,其第一個(gè) uintptr 元素指向的位置,前 4 字節(jié)為?pclntab?的 Magic Number。如果是真實(shí)的?firstmoduledata,它內(nèi)部是有幾個(gè)字段可以跟?pclntab?中的數(shù)據(jù)進(jìn)行交叉驗(yàn)證的,比如:- firstmoduledata.ftab == pclntab_addr + 8
- firstmoduledata.filetab == firstmoduledata.ftab + pclntab.functab_size + sizeof(uintptr)
- firstmoduledata.minpc == firstmoduledata.text_addr == uintptr(pclntbl_addr + 8 + ADDR_SZ) == first function of pclntab.functab
- End -
精彩推薦
通達(dá)OA11.7 后臺(tái)sql注入到rce漏洞分析
Windows XP源代碼“正式”泄露
Yii2框架Gii模塊 RCE 分析
金九銀十招聘季,安全客全新招聘欄正式上線!!!
戳“閱讀原文”查看更多內(nèi)容 創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的二进制函数_Go二进制文件逆向分析从基础到进阶——MetaInfo、函数符号和源码文件路径列表...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 虚拟机无法接受组播消息_IPTV(组播)
- 下一篇: php 获取两个日期相隔几周,怎么样计算