上一篇文章介紹了xorstr的原理和最小化驗證概念的代碼,這篇文章來看下這種已經被廣泛應用于各惡意樣本以及安全組件中的技術如何還原,如果還沒看上篇建議先看下了解其實現后再看本篇文章。
一 xorstr的現狀
隨著相關技術的應用越來越廣,各種攻擊樣本都用上了這一工具,威脅樣本分析變得越來越耗時。這就是為什么需要一個對抗上述混淆技術的還原工具的原因,兩個開源的還原工具flare-floss(https://github.com/mandiant/flare-floss)和AntiXorstr(https://github.com/lstaroth/AntiXorstr),工具的應用讓這種二進制隱藏的字符串還原難度大幅降低。
二 還原方案分析
flare-floss是mandiant公司開發并開源出來的針對病毒分析的自動化二進制字符串啟發式查找工具,針對此類加密方式flare-floss提供了兩種還原邏輯(stack string & tight string),兩者基于不同的假設,但都可以用來處理上述加密,下面給出他的實現原理的簡要分析摘要。
stack string:flare-floss的這種還原方式基于一種非常寬的假設,即這種字符串必須基于棧,在棧上構造并解密。所以他使用模擬執行的方式對每個函數逐匯編代碼的進行模擬執行,并在遇到函數調用指令時對當前棧進行全dump,最后使用字符串明文算法在dump中查找疑似的明文字符串,保存結果。
由于其假設過寬,致使其幾乎擁有對當前開源的xorstr類似項目擁有100%的覆蓋率,但過寬的假設導致大量的誤報,對安全分析來說過多的垃圾信息反而干擾了正確的判斷,這個誤報在其代碼中特別標注為“don't run this on functions with tight loops as this will likely result in FPs”

tight string:而flare-floss為了解決上述解決方案過寬假設帶來的大量False Postive的問題,在去年更新了floss2加入tight string還原模式,這種模式基于這樣的假設:存在棧加密字符串的函數存在一個循環Block塊,這個Block塊出去時棧為解密狀態,也即他瞄準的時上述代碼中的decrypt內聯函數部分,decrypt中的for循環結構即為tight string關注的循環Block,而他也從每個函數調用轉為每個循環block出口時對函數棧進行dump并搜索明文字符串。

AntiXorstr:去年寫這個工具的時候并沒有關注到floss項目因此實現的邏輯和他完全不同。區別于tightstring對decrypt特征的關注,該工具關注的是類構造函數。基于這樣的假設:“棧加密字符串的加密數值必須是編譯期計算出來的”。工具會對函數的棧進行預分析,并對棧進行立即數染色,被非0立即數染色過的聯通區域標記為高可疑區域,并在后續的模擬執行過程中關注此類區域的讀寫并輸出結果。

三 tight string的繞過
在對floss的tight string邏輯分析的過程中發現他的假設實際上并非是棧字符串的必須的,但確實是當前幾乎所有開源的實現一定會存在的特征,即解密函數流程圖中表現為環的形式。所以對floss-tight string的繞過即實現一個不會成環的字符串解密函數。首先我們先看下當前常規字符串的解密邏輯的反匯編流程圖特征,demo如下:

這里的loc_1400010A0即為一個循環的Block,Block中的代碼就是for循環中解密原始數據的decrypt函數的匯編碼。這里讓decrypt函數內部不生成循環即可實現對此還原方式的繞過,那么如何消環呢?這里用模板編程的思路去考慮一下,可以把循環使用模板遞歸展開的方式在編譯期給消除掉,最終需要的效果如下:
__forceinline char* decrypt()
{
for (auto index = 0; index < N; index++)
{
encBuffer[index] -= 1;
}
return encBuffer;
}
//N = 5
__forceinline char* decrypt_noloop()
{
encBuffer[0] -= 1;
encBuffer[1] -= 1;
encBuffer[2] -= 1;
encBuffer[3] -= 1;
encBuffer[4] -= 1;
return encBuffer;
}
剩下就是如何使用C++模板生成decrypt_noloop代碼,使用遞歸展開的方式生成并對N=0做個特化終止即可消除loop環,并且還有一些細節這里限于篇幅不做展開,實現DEMO已開源:xorstr_s.h(https://github.com/lstaroth/xorstr-security/blob/main/xorstr_s.h)
測試demo
void test()
{
printf(Enc("Samsung\n"));
wprintf(Enc(L"Apple\n"));
printf(Enc("Xiaomi\n"));
wprintf(Enc(L"Oppo Group\n"));
printf(Enc("vivo\n"));
wprintf(Enc(L"Transsion\n"));
std::cout << Enc("Honor") << std::endl;
std::wcout << Enc(L"Realme") << std::endl;
std::cout << Enc("Motorola") << std::endl;
std::wcout << Enc(L"Huawei") << std::endl;
std::cout << Enc("Others") << std::endl;
}
測試結果:STACK & TIGHT 繞過,DECODED模式還原出一部分。
───────────────────── FLOSS STACK STRINGS ───────────────────── ───────────────────── FLOSS TIGHT STRINGS ───────────────────── ─────────────────────── FLOSS DECODED STRINGS ─────────────────────── Samsung Apple Xiaomi Oppo Group vivo Transsion Honor
四 更魯棒的繞過策略
上述通過詳細分析floss還原策略寫的xorstr_s似乎顯得不是很魯棒,僅僅針對一個開源的工具去實現定制化的繞過方案顯得成本過高,那么是否有更魯棒的反還原方式呢,暫時有兩種方式,其中一種的核心原理是:”基于堆的明文展開“,使得Floss這樣對堆明文做監控的模式徹底失效。
讀過上一篇文章的應該可以知道棧中保存了字符串密文,但并不是一定要就地解密,完全可以對棧數據只讀而解密后的明文寫入堆中,并利用臨時對象的析構函數完成堆的釋放即可。實現DEMO已開源:xorstr_h.h(https://github.com/lstaroth/xorstr-security/blob/main/xorstr_h.h),測試FLOSS的效果如下:
FLOSS STACK STRINGS
─────────────────────
o]QOIR[6<
}<L<L<P<Y<6<<<
dU]SQU6<
s<L<L<S<
<{<N<S<I<L<6<<<
JUJS6
h<N<]<R<O<O<U<S<R<6<<<
tSRSN
n<Y<]<P<Q<Y<<<
qSHSNSP]<
t<I<]<K<Y<U<<<
sHTYNO<
─────────────────────
FLOSS TIGHT STRINGS
─────────────────────
───────────────────────
FLOSS DECODED STRINGS
───────────────────────
o]QOIR[6<
X0_0X
安全牛
X0_0X
商密君
信息安全與通信保密雜志社
一顆小胡椒
安全牛
上官雨寶
FreeBuf
上官雨寶