golang interface 类型转换_无符号Golang程序逆向方法解析
在去年的inctf2018中,出現了一道Go語言編寫的進程通信逆向題,無論是從題目整體設計還是解題思路上來說都獨樹一幟,自己在解題過程中遇到了很多問題,但我這不打算做過多探討,網上也有大佬的解題過程,本文僅針對該題涉及到的無符號Go語言恢復信息問題進行詳細討論。
前言
在整個后期整理過程中,自己參考了很多資料,現放出所有鏈接,下文中也會有對應的說明。
奈沙夜影師傅的題解
分析靜態編譯無符號文件的方法
Go語言逆向去符號信息還原
reversing_go_binaries_like_a_pro
手把手教你如何專業地逆向GO二進制程序
IDAGolangHelper
diaphora
IDA F.L.I.R.T. Technology: In-Depth
IDA7.0 IDAPython MakeStr Bug fix
angr源碼分析——庫函數識別identify
golang語言編譯的文件大小解析
golang編譯去符號信息逆向
go語言學習-常用命令
初步分析
首先用file命令簡單查看下文件類型,發現是64位的,由題目名也能猜出是和go語言有關的逆向題,但是發現雖然是動態鏈接的,但是無符號,這是比較坑的,心中有點小怕。
$ file ../ultimateGOal ../ultimateGOal: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, stripped那么用ida打開之后,由于沒有符號的原因,我們并不能找到主函數的位置,不熟悉go語言逆向的同學可能會不清楚go語言逆向的入口,那么簡單說明一下。
簡單demo測試
對下面這個簡單的go語言例子而言,我們在進行編譯go程序的時候,會生成可執行文件,而go程序需要滿足2個條件:
go程序中需要包含main包
在main包中還必須包含main函數
也就是說go語言的入口點是main.main(真正的入口點),即是main包下的main函數。對這個demo,我們編譯之后用ida打開查看,搜索主函數,當確定了主函數入口時,嘗試反編譯代碼,缺發現失敗,如下圖所示:
給出的提示是Size of 'int' is 8 (4 expected),對于這種錯誤,通過選項中的編譯設置,修改整型的字長大小即可解決問題,如下圖所示。
得到如下的代碼:
void __cdecl main_main() {__int64 v0; // rdxerror_0 v1; // di__interface_{} ST00_24_2; // ST00_24__interface_{} v3; // [rsp+30h] [rbp-18h]void *retaddr; // [rsp+48h] [rbp+0h]while ( (unsigned __int64)&retaddr <= *(_QWORD *)(__readfsqword(0xFFFFFFF8) + 16) )runtime_morestack_noctxt();v3.array = (__DIE_10518_interface_{} *)&r3;v3.len = (__int64)&main_statictmp_0;ST00_24_2.array = (__DIE_10518_interface_{} *)&v3;ST00_24_2.len = 1LL;ST00_24_2.cap = 1LL;fmt_Println(ST00_24_2, v1, v0); }對于ida反編譯出的go語言代碼,可讀性較差,類型轉換較多,結構體較復雜,異常處理比較冗長,對于main_statictmp_0這個結構體,在go語言中包括了2部分,分別是字符串偏移量和字符串長度,這個結構體所指向的是string ,指出了字符串的偏移量和長度,長度是0xd(即”Hello World!”的長度),另外go語言中所有字符串都是放在一起的,所以偏移量和長度是很關鍵的,如下圖所示:
解決無符號的問題
對于上文那個demo而言,是有符號的程序,逆向起來有信息可以參考,大大加快了逆向的速度,但是對這道題而言,是無符號的,無符號的程序逆向起來由于不知道函數的功能,會特別饒,很容易把人繞進去。如下圖所示:
當然也有人能純靜態分析出來,比如奈沙夜影師傅,參考最上面貼出來的鏈接,使用動態調試結合黑盒測試的方法Orz。師傅也自嘲道:Go語言的逆向感覺目前沒啥方便的工具,只能硬懟匯編,缺少符號的情況下還是有點麻煩的,等一個師傅們的指教,這個題目我是純黑盒調試出來的。針對這個問題,自己參考了幾篇博文,收集了一些方法,供參考。
從簽名角度出發
眾所周知,簽名是對函數、第三方庫很好的檢測方式,通過簽名,ida很容易能分析出函數的名稱,從而大概知道函數的作用。由于沒有官方的簽名庫,所以這需要自己制作,參考了diffway@蘭云科技銀河實驗室的方法,論述如下:
idb2pat.py+sigmake制作簽名
idb2pat.py是火眼公司FireEye Labs Advanced Reverse Engineering團隊編寫的腳本,代碼在GitHub上開源,該腳本主要通過CRC16的方式來計算每個函數塊的特性,從而來識別不同的函數。這點也和IDA官方對簽名文件的說明相符合,參見IDA F.L.I.R.T. Technology: In-Depth。
通過生成pat文件后,再使用ida SDK中提供的sigmake工具來生成相應的sig簽名文件,將其復制到ida安裝目錄下的sigpc目錄,然后我們在ida中就可以載入簽名進行識別。在2088個函數中,識別出1271個函數,但是不是每個識別出的函數都能有效的命名,所以實際識別出的函數個數也就600左右,識別率較低,而且只能識別出被制作簽名的程序所帶的包,如果另一個程序使用了其他的包,那將無法識別,拓展性較低。
bindiff或者diaphora對比
通過bindiff或者diaphora來對比不同是ida數據庫,以獲取函數的特征也是種很好的方法,這種方法在平時分析靜態鏈接的程序也很有用。但是存在的問題也很明顯,由于diaphora是python編寫的,所以運行速度是肉眼可見的慢,對于1000數量級的函數保存到數據庫中竟然也要3分鐘左右,保存完之后,再分析對比的時候,需要更多時間,效率很低。當數據完成對比之后,我們得到情況如下圖所示:
主要分為五欄:完全匹配、部分匹配、只在第一個數據庫出現的、只在第二個數據庫出現的以及不可靠匹配的。對我們來說,我們只需要關注匹配率高的即可,所以我們首選對完全匹配中的函數進行重命名,方法很簡單,就是選中所有的完全匹配的函數,然后右鍵導入即可。
該方法的主要問題是速度太慢,不管是在前期初始化數據庫的時候,還是后面重命名函數的時候,非常卡頓。其次是對函數類別沒有很好的區分度,如下圖所示,同樣都是運行時函數,對函數類別處理不好,也不能對主函數進行區別,和第一種方式一樣,識別率不高,只能識別出被制作簽名的程序所帶的部分庫函數。
Rizzo插件生成數據庫識別
關于rizzo,可以參看GitHub上的介紹Rizzo,同樣也是一種對ida數據庫進行保存然后提取信息進行對比的工具,收錄于devttys0的ida腳本目錄中。自己也進行了測試,速度還可以。
Building Rizzo signatures, this may take a few minutes... Generated 1314 formal signatures and 844 fuzzy signatures for 1784 functions in 10.64 seconds. Saving signatures to C:UsersxxxxxxDesktop11111.riz... done. Built signatures in 12.30 seconds那么在保存完之后,載入riz文件進行測試,如下圖所示。基本識別情況和上面2種方式差不多,也存在對原始文件的局部依賴性,所不同的是,這種方式不會誤識別,而前面2種方式會產生很多未知的函數命名情況,歧義性較低。猜測是前面2種方法對模糊測試效果更好,第三種方式屬于保守型的測試方法,會將極大概率的函數進行重命名,而較低的大概率函數則不會通過。
從Golang特性出發
上面的3種方法雖然能對部分函數進行識別,但是效果一半,前2種方法識別率大概有50%左右,第3種只有20%左右。且三者均不能對簽名生成程序中沒有的函數進行識別,也就是說連每個程序中的主函數都無法定位,因為每個程序中的主函數均不一致。下面將從Go語言的特性來解決這個問題。
GolangAssist腳本
在網上有一篇著名的go語言逆向解析博文,安全客中也有人提供了翻譯,鏈接參見前言部分。該作者對golang的編譯原理有著較深的理解,同時其提供了golang_loader_assist.py腳本用于還原符號信息,這對逆向而言真是再好也不為過了。但是這個腳本無法恢復Windows下編譯的go程序,因為這個腳本中最重要的部分,是找到go語言程序中一個非常重要的段,叫做.gopclntab,在這個段中保存了函數的實際名稱,而在Windows下編譯的程序中則不存在這個段。
在這個腳本中最終的部分如下圖所示:
首先通過get_segm_by_name('.gopclntab')來定位到.gopclntab段的首地址。然后尋找偏移量是8的地方,根據程序字長來創建指針,再接下來一個字長則給出了.gopclntab段的大小,這樣我們就能開始逐個處理每條數據,對每條數據而言,其中都包含了一個函數指針和一個函數名字符串偏移量,循環處理這些數據就能對所有的函數名進行還原,得到帶符號信息的函數名稱。
在實際使用這個腳本進行測試的過程中,需要注意的是,由于ida7.0對sdk和api的大量更新和重新,在idapython中的創建字符串函數MakeStr出錯,主要原因是函數的重復定義,參看前言中看雪論壇的相關討論,修改方式如上圖所示。
自己將最關鍵的部分代碼進行了分析和抽取,腳本如上,這段代碼可直接在ida中運行,用以定位函數名偏移量和修改函數名,但是注意最后這個地方函數名修改會有點問題,原因是函數名中除了下劃線不能出現其他字符,但是當我們運行完畢后,很多函數名是存在特殊符號的,需要自己過濾。
gopclntab = get_segm_by_name('.gopclntab') if gopclntab is not None:base = gopclntab.startEA + 8# 計算函數名個數count = Dword(base)# 基地址base += 8for i in xrange(0, 2 * count, 2):# 創建函數指針MakeQword(base + 8*i)functionAddr = Qword(base + 8*i)# 創建函數名字符串偏移量(相對于gopclntab基地址而言)MakeQword(base + 8*i + 8)offset = Qword(base + 8*i + 8)offset = Dword(offset + gopclntab.startEA + 8)# 函數名字符串偏移量(文件偏移量FOA)functionName = offset + gopclntab.startEAname = GetString(functionName)# 創建字符串MakeStr(functionName, functionName + len(name))# 修改函數名(ida禁止函數名出現特殊符號,需過濾后才能達到100%效果,我這里沒有過濾)MakeName(functionAddr, name)運行結果如下圖所示:
通過分析go語言特有的.gopclntab段,我們可以恢復調試符號信息,只有該段中保存的信息均可以進行恢復,恢復率達到98%以上。
IDAGolangHelper腳本
剛剛討論完了GolangAssist,效果是非常不錯的,而作為GolangAssist的升級版本,IDAGolangHelper做的則更加完善,該腳本的作者在2016年底的zeronights會議中展示了他的成果,有興趣的同學可以參看他的PPT,其實整個腳本的思路和上面一樣,同樣是通過.gopclntab這個段所保存的符號信息來獲取函數信息,如下圖所示。
除此之外,作者進一步分析了go語言的版本問題,通過2種方式來確定當前程序的go語言版本,一是通過特征字符串的來進行查找,二是通過分析go中特有的結構體類型,由于不同版本之間有結構體會產生變化,作者提出了這種思路來確定版本信息。
而通過實際分析證明,第一種方法,即特征字符串的方式來查找版本還是會更高效,更準確些,相比而言,第二種方法由于版本之間的差異不多,則會導致歧義。下圖是使用效果,當我們載入該腳本后,第二種方式只能判斷是go1.8或1.9或1.10,但是特征字符串則較好的確定是go1.9版本。在重命名函數后,再搜索main字符串,就能定位到main包中的所有函數了。
其他方法
當然無符號問題解決的方法還有很多,比如著名符號執行引擎angr中就使用了基于函數語義識別庫函數,也有人對源碼進行了分析。函數語義就是分析函數的功能和執行的操作,包括寄存器、內存、堆棧和對其他函數的調用流,作為人去分析函數的時,也是類似的,所以感覺這里也可以用機器學習的方法來進一步提高分析效率。
當然學術界也有對這方面的研究,比如二進制代碼函數相似度匹配技術研究這篇論文,通過函數的序言部分的特征,提出了二階段函數匹配方法TPM,在識別出相似函數后,找到其調用關系和決策規則,然后遞歸的識別不同版本的函數,據論文所述,識別準確率平均高于diaphora和patchdiff方法,也是值得借鑒的。
結語
至此,提出的這么多方法,就能較好的解決Go語言程序逆向中的無符號的問題了。而其實對無符號程序的分析一直是逆向工程中的一個難點,如何有效的分析無符號的程序也一直是我們所關心的問題。那么總結起來無外乎以下幾點。
從各種語言本身特性出發。比如本文詳細討論的Go語言特性,由于特定段保存了符號信息,從而可以進行恢復。
從代碼復用和庫函數檢測出發。將程序中未知函數和已知功能函數進行某種方式下的對比,比如hash計算、匹配簽名、等等。
從函數語義分析出發。也是最常見的硬核分析手段,就是一個一個函數分析其代碼實現和功能,從而推斷函數的作用,積少成多,就能對整個程序進行逆向。毫無疑問,也可以將人工智能結合到這種方式中,仿照逆向工程師的思路來進行深度學習的,可能也是學術界未來的一個研究方向吧。
總結
以上是生活随笔為你收集整理的golang interface 类型转换_无符号Golang程序逆向方法解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python threading loc
- 下一篇: 训练集 测试集 验证集_Python机器