<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    ollvm反混淆學習

    VSole2021-10-16 16:57:55

    看了@無名俠大佬發的一篇關于使用unicorn模擬執行還原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        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]:'.');            }            printf("%s",binstr);        } else {            sprintf(binstr,"%s %02x",binstr,(unsigned char)buf[i]);        }    }    if (0!=(i%16)) {        k=16-(i%16);        for (j=0;j            sprintf(binstr,"%s   ",binstr);        }        sprintf(binstr,"%s  ",binstr);        k=16-k;        for (j=i-k;j            sprintf(binstr,"%s%c",binstr,('!''~')?buf[j]:'.');        }        printf("%s",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掉。 

    好了現在大功告成,直接來看偽代碼。

    把偽代碼拿出來編譯測試:

    寫在最后

    目前還在測試大概還原了5 6個樣本,可能還有一些細節方面未考慮到,所以發出來希望聽下大佬的意見。

    之所以沒貼代碼出來是因為代碼太雜了和篇幅太大了,實在是不太方便,有需要的話可以參考無名俠大佬的帖子和源碼,我都是基于他之上的。

    如果大家感覺以上有不妥或者不理解的地方,歡迎和我一起探討一起學習。

    代碼混淆匯編指令
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    加密算法共4種,第二個任務注冊機,缺一個算法的解密算法,其他三個算法均已寫好C實現的解密算法。隨后在xxx函數通過frida分析找到XTEA加密,然后用frida在內存中找到并提取了密鑰。Dump && Recover IL2CPP雖然用修改后的frida去hook libsec2023.so仍然會被檢測,但是hook其他庫沒有出現問題。
    免殺知識匯總
    2021-08-25 23:11:00
    免殺知識匯總
    奇安信的報告《使用和海蓮花相似混淆手法的攻擊樣本分析》[1]中分析了一個和APT32使用相同混淆方法的樣本。本文根據奇安信的報告以及報告中提到的參考文章和代碼[2]對該樣本進行去混淆
    得益于Unicorn的強大的指令trace能力,可以很容易實現對cpu執行的每一條匯編指令的跟蹤,進而對ollvm保護的函數進行剪枝,去掉虛假塊,大大提高逆向分析效率。
    少量虛假控制流混淆后的算法還原案例分析!
    結果表明,那些想要保持匿名的程序員需要采取極端的應對措施來保護他們的隱私。因此,我們為可執行二進制作者溯源設計了一個特征集,目標是準確地表示與程序員風格相關的可執行二進制文件的屬性。從頭到尾反匯編二進制文件,遇到無效指令時跳過該字節。特別而言,反編譯器可以重構控制結構,如不同類型的循環和分支結構。因此,需要進行特征選擇的降維操作。
    機器學習模型對這種變化稱為概念漂移,使用舊數據訓練的模型在處理前所未見的新樣本時挑戰極大。為了構建有效且穩健的分類器,必須能夠檢測同一惡意軟件家族中漂移的 IoT 變種,并解釋漂移的成因。通過 VirusTotal 的分析報告,使用 AVClass 對其進行處理聚合家族歸屬。一共確定了 44 個 Mirai 的變種與 11 個 Gafgyt 的變種。相比 Gafgyt 來說,Mirai 的連接更為緊密。
    由于init函數是linker調用的,所以沒法做加密。所以我們合理懷疑初始化函數位置找錯了。其實之所以會搞錯,是因為錯誤的section header干擾了ida的解析。這通常是因為代碼中有花指令的緣故,我們要考慮去除花指令了。所以有理由懷疑,這里就是花指令,用來干擾ida解析的。執行完后再加上0x20,棧是平衡的。所以我們確信,中間的ret部分就是花指令
    Frida工作原理學習
    2022-07-12 16:28:29
    frida是一款便攜的、自由的、支持全平臺的hook框架,可以通過編寫JavaScript、Python代碼來和frida_server端進行交互,還記得當年用xposed時那種寫了一大堆代碼每次修改都要重新打包安裝重啟手機、那種調試調到頭皮發麻的痛苦,百分之30的時間都是在那里安裝重啟安裝重啟。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类