【网络安全】ollvm反混淆学习
ollvm原理
Ollvm大致可分為 bcf(虛假塊), fla(控制流展開), sub(指令膨脹), Split(基本塊分割)
bcf:
克隆一個真實塊,并隨機替換其中的一些指令,然后用一個永遠為真的條件建立一個分支。克隆后的塊是不會被執行的。
Fla:
將所有的真實塊使用一個switch case結構包裹起來,每個真實塊執行完畢后都會重新賦值switch var,對于有分支的塊會使用select指令,并跳轉到switch起始代碼塊(分發器)上,根據switch var來執行下一個真實塊。
Sub:
指令膨脹,將一條運算指令,替換為多條等價的運算指令。
Split:
利用隨機數產生分割點,將一個基本塊分割為兩個,并使用絕對跳轉連接起來。
關于ollvm具體的實現,可參考源碼。
【網絡安全學習資料】
還原思路
網上有很多還原ollvm的腳本,但是只能還原特征很明顯的ollvm,或者說只是debug版的ollvm。在debug版中ollvm的特征非常明顯,一個分發器,和引用了這個分發器的真實塊。但經過編譯器優化后,分發器可能會變成多個,基本塊會合并造成虛假塊也可能會和真實塊合并,等等。
現實情況是,你基本上碰不到簡單的ollvm,所以那些東西個人感覺意義不是很大,還是需要靠自己。
談下還原思路
Bcf:
Bcf塊是執行不到的塊,所以說當使用unicorn 跑過一遍函數后,其中沒有執行到的塊肯定有包括bcf塊,我們只需要將它挑出來標記下就好。
但函數中可能存在分支,只跑一遍函數是無法覆蓋到所有分支的,所以要想辦法找到函數的所有分支。一開始采用的是無名俠大佬的方法,當碰到csel指令時人工干預讓其覆蓋所有分支,但整個函數經常陷入死循環,分析過后發現虛假塊的跳轉也有可能使用csel指令。后來想到了在二進制漏洞挖掘中的思路fuzz(模糊測試),即變異函數的參數傳遞給函數,來覆蓋更多的分支。這樣做也不能說能夠找到函數的所有分支。影響一個函數的分支執行大概有三種情況,
參數,全局變量,內部函數調用的返回值。后兩種情況的話留意下模糊執行的trace應該能找到些蛛絲馬跡,可能會比較麻煩。
Fla
這個環節會產生控制流塊,我們只需要將這些塊挑出來標記,找出所有的真實塊,并通過模擬執行還原真實塊之間的關系就好。
控制流塊的剔除采用了無名俠大佬對基本塊簽名的方法。
【網絡安全學習資料】
Sub:
指令膨脹的還原,使用llvm的pass優化效果還可以,但目前一些ir翻譯工具對arm64的支持不怎么樣。
Split:
基本塊分割更多是用來增加bcf和fla效果的。
總結整體思路:
1 利用模擬執行和fuzz技術,找出bcf塊并剔除。
2 使用基本塊簽名剔除控制流塊。
3 將剩余的塊標記為真實塊,并使用模擬執行找出對應關系。
4 根據對應關系,重構cfg。
實戰:
自己編譯的一個樣本如下:
void HexDump(char *buf,int len,int addr) __attribute((__annotate__(("split")))) __attribute((__annotate__(("fla")))) __attribute((__annotate__(("bcf")))) {int i,j,k;char binstr[80];for (i=0;i<len;i++) {if (0==(i%16)) {sprintf(binstr,"%08x -",i+addr);sprintf(binstr,"%s %02x",binstr,(unsigned char)buf[i]);} else if (15==(i%16)) {sprintf(binstr,"%s %02x",binstr,(unsigned char)buf[i]);sprintf(binstr,"%s ",binstr);for (j=i-15;j<=i;j++) {sprintf(binstr,"%s%c",binstr,('!'<buf[j]&&buf[j]<='~')?buf[j]:'.');}printf("%s\n",binstr);} else {sprintf(binstr,"%s %02x",binstr,(unsigned char)buf[i]);}}if (0!=(i%16)) {k=16-(i%16);for (j=0;j<k;j++) {sprintf(binstr,"%s ",binstr);}sprintf(binstr,"%s ",binstr);k=16-k;for (j=i-k;j<i;j++) {sprintf(binstr,"%s%c",binstr,('!'<buf[j]&&buf[j]<='~')?buf[j]:'.');}printf("%s\n",binstr);} }
先找出所有的基本塊(以跳轉指令結尾的塊)
這里需要注意下由于編譯器優化的關系,基本塊會合并,有些基本塊并不是以跳轉指令結尾,就如這樣
這些情況,是因為兩個基本塊同時引用了這個塊,所以需要將這個塊拷貝一份,并將另一個塊的引用修改為新拷貝的塊,不然還原關系的時候會亂掉。
我這里占用了main函數的空間。
找出所有的基本塊后開始fuzz執行,并統計所有被執行到的塊。這里fuzz采用了,先使用peach編寫規則生成參數的語料庫保存到文件中,然后讀取文件中的內容當作參數傳遞給函數, 當然如果不關心函數的其他分支,fuzz的步驟感覺可以跳過,例如一些純算法函數。
【網絡安全學習資料】
經過幾十輪fuzz后,共統計到如下被執行了的塊。
這些塊中肯定是包含了控制流塊的,所以現在用簽名法來過濾掉控制流塊。
過濾后還剩下169個塊,這些塊就是真實塊了,為了保險起見我還人工過濾了一下,基本沒什么問題。
接下來開始模擬執行找出他們之間的對應關系了,當碰到一個真實塊時記錄下它上一個執行的真實塊,并保存起來。
傳遞給函數的參數也需要使用上面fuzz使用的參數,這樣才能執行到每一個塊。
模擬執行后,基本塊之間的關系如下:
如果數組中只有一個基本塊的話,那么他們是一個順序關系,
如果有兩個的話則是分支關系, 如果2個以上則有三種情況
1 漏了真實塊, 2 該塊不是一個真實塊, 3 該塊是一個分支共用塊。
經排查這里是第三種情況,如下:
9e8這個塊被兩個基本塊引用,并兩個基本塊都是一個分支塊,所以會出現這種情況。 具分析其中一個塊的分支對應的是bcf,不會被執行到,所以數組中是3個基本塊而不是4個。對于這個情況也需要將9e8這個塊copy一份,將兩個基本塊中的其中一個引用修改為copy后的塊。【網絡安全學習資料】
修改完畢后,記得將copy塊添加到真實塊中,并重試。
可以看到問題解決了。
找出對應關系后需要接著還原分支關系,當條件為真時跳到那個塊,為假時跳轉到那個塊。 因為每個分支塊都會有一條cmp 和csel指令, 如果找到的分支塊中沒有這兩條指令,那么就是漏了真實塊。
還原他們的關系,只需要在模擬執行時,記錄cmp的返回值,和返回值對應的真實塊即可,這里會比較麻煩,需要手動找到cmp的地址, 左右值, 和比較關系。
模擬分支塊的關系如下:
我這里根據記錄的條件,翻譯成了匯編。
最后根據這些真實塊之間的關系patch即可, 注意在patch分支塊時需要注意csel和cmp的關系,像這種
如果我們如果在基本塊的最后patch b.ne xxx b xx, 那么標志位就會被上面的一個cmp干擾,所以需要將上面 一個cmp也patch掉。
【網絡安全學習資料】
好了現在大功告成,直接來看偽代碼。
把偽代碼拿出來編譯測試:
最后:
有需要網絡安全學習資料的話可以關注私信我,基于零基礎開始的都有。
如果大家感覺以上有不妥或者不理解的地方,歡迎來一起學習哦!
【網絡安全學習資料】
總結
以上是生活随笔為你收集整理的【网络安全】ollvm反混淆学习的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【网络安全】如何使用PacketSift
- 下一篇: 殊途同归的CVE-2012-0774 T