日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

前言

此文章記錄了我們對(duì)殺毒軟件某些方面的研究,以及我們?nèi)绾卧O(shè)法自動(dòng)重構(gòu)Meterpreter以繞過(guò)我們所遇到的每個(gè)AV / EDR。 雖然下面詳細(xì)介紹了每種技術(shù)的思想和字符串混淆過(guò)程的實(shí)現(xiàn),但我們決定在以后的文章中發(fā)布有關(guān)API導(dǎo)入隱藏/系統(tǒng)調(diào)用重寫的詳細(xì)信息,以使此內(nèi)容盡可能簡(jiǎn)短。 源代碼位于https://github.com/scrt/avcleaner

大多數(shù)公司通常采取防御措施來(lái)保護(hù)其信息系統(tǒng)免受攻擊,在這些措施中,殺毒軟件或EDR等安全軟件通常是必不可少的工具集。盡管過(guò)去幾年來(lái)繞過(guò)任何類型的惡意軟件檢測(cè)機(jī)制非常容易,但如今這樣做無(wú)疑需要付出更多的努力。

另一方面,在POC本身被反病毒軟件阻止的情況下,與漏洞相關(guān)的風(fēng)險(xiǎn)交流變得更具有挑戰(zhàn)性。盡管理論上可以說(shuō)有可能繞過(guò)檢測(cè)并保留它,但實(shí)際上這樣做可能會(huì)增加強(qiáng)度。

鑒于此,需要能夠繞過(guò)殺毒軟件。使事情稍微復(fù)雜化的是,在SCRT,我們盡可能地使用公開可用的開源代碼工具,以展示我們的工作可以被任何熟練使用它們的人復(fù)制,并且不依賴于昂貴的私人工具。

 

問(wèn)題現(xiàn)狀

現(xiàn)在的社區(qū)通常喜歡將任何防病毒的檢測(cè)機(jī)制歸類為靜態(tài)或動(dòng)態(tài)。通常,如果在惡意軟件執(zhí)行之前觸發(fā)了檢測(cè),則將其視為一種靜態(tài)檢測(cè)。但是,值得一提的是,殺軟也可以在惡意軟件執(zhí)行期間調(diào)用靜態(tài)檢測(cè)機(jī)制(例如簽名),以響應(yīng)諸如進(jìn)程創(chuàng)建,內(nèi)存中文件下載等事件。無(wú)論如何,如果我們想對(duì)任何類型的安全軟件使用舊的Meterpreter,我們都必須對(duì)其進(jìn)行修改,使其滿足以下要求:

在文件系統(tǒng)掃描或內(nèi)存掃描時(shí),繞過(guò)任何靜態(tài)簽名。繞過(guò)“行為檢測(cè)”,這種行為通常與繞過(guò)用戶界面API hooking有關(guān)。

但是,Meterpreter包含幾個(gè)模塊,整個(gè)代碼庫(kù)總計(jì)約700’000行代碼。此外,它會(huì)不斷更新,這意味著運(yùn)行項(xiàng)目的私有分支肯定會(huì)導(dǎo)致擴(kuò)展性很差。簡(jiǎn)而言之,我們需要一種自動(dòng)轉(zhuǎn)換代碼庫(kù)的方法。

 

解決方案

經(jīng)過(guò)多年繞過(guò)防病毒軟件的實(shí)際經(jīng)驗(yàn),我們發(fā)現(xiàn)惡意軟件檢測(cè)幾乎都是基于字符串,API hooks或兩者的結(jié)合。

即使對(duì)于實(shí)施機(jī)器學(xué)習(xí)分類的產(chǎn)品(例如Cylance),一個(gè)沒有字符串,API導(dǎo)入和可掛鉤API調(diào)用的惡意軟件也可以像足球射門一樣穿過(guò)Sergio Rico的防御網(wǎng)絡(luò)。

Meterpreter具有成千上萬(wàn)個(gè)字符串,不會(huì)以任何方式隱藏API導(dǎo)入,并且可以使用用戶界面API hook輕松攔截敏感的API,例如WriteProcessMemory。 因此,我們需要以自動(dòng)化的方式對(duì)此進(jìn)行補(bǔ)救,這將產(chǎn)生兩個(gè)潛在的解決方案:

源到源代碼重構(gòu)LLVM在編譯時(shí)混淆代碼庫(kù)。

顯然,后者將是首選方法,這是許多流行的研究得出相同的結(jié)論。 主要原因是,轉(zhuǎn)換遍歷可以只編寫一次,而可以獨(dú)立于軟件的編程語(yǔ)言或目標(biāo)體系結(jié)構(gòu)重復(fù)使用。

自動(dòng)重構(gòu)Meterpreter繞過(guò)殺軟

 

但是,這樣做需要有用Visual Studio以外的編譯器編譯Meterpreter的能力。盡管我們已于2018年12月發(fā)布了一些更改工作,但在一年多之后,正式代碼庫(kù)的采用仍然是一個(gè)需要持續(xù)的過(guò)程。

同時(shí),我們已決定不顧一切成本的實(shí)施第一種方法。在對(duì)源代碼重構(gòu)的最新技術(shù)進(jìn)行了徹底的回顧之后,libTooling(Clang / LLVM工具鏈的一部分)似乎是解析C / C ++源代碼并對(duì)其進(jìn)行修改的唯一可行的選擇。

注意:由于代碼庫(kù)高度依賴Visual Studio,因此Clang無(wú)法解析Metepreter的很大一部分。但是,仍然有可能以一半的概率繞過(guò)目標(biāo)防病毒軟件。在這里,我們可能遇到源到源轉(zhuǎn)換相對(duì)于編譯時(shí)轉(zhuǎn)換的唯一優(yōu)勢(shì):那就是編譯時(shí)轉(zhuǎn)換要求整個(gè)項(xiàng)目進(jìn)行編譯而沒有任何錯(cuò)誤。而源到源轉(zhuǎn)換可以允許成千上萬(wàn)的編譯錯(cuò)誤。而你只會(huì)得到一個(gè)不完整的抽象語(yǔ)法樹,這非常好。

 

字符串混淆

在C / C ++中,字符串可能位于許多不同的上下文中。 libTooling并不是一個(gè)足夠好的工具,因此我們應(yīng)用了帕雷托法則(也即二八定律),將研究范圍限制在Meterpreter代碼庫(kù)中最可疑的字符串出現(xiàn)的代碼上:

函數(shù)參數(shù)列表初始化

 

函數(shù)參數(shù)

例如,我們知道,在以下情況下,ESET Nod32將標(biāo)記字符串ntdll為可疑:

ntdll = LoadLibrary(TEXT("ntdll"))

但是,用以下方式重寫此代碼段將成功繞過(guò)檢測(cè):

wchar_t ntdll_str[] = {'n','t','d','l','l',0};
ntdll = LoadLibrary(ntdll_str)

在看不見的幕后,第一個(gè)代碼片段使字符串ntdll存儲(chǔ)在生成的二進(jìn)制文件的.rdata段內(nèi),并且防病毒程序很容易發(fā)現(xiàn)該字符串。 第二個(gè)片段是字符串在運(yùn)行時(shí)存儲(chǔ)在堆棧中,并且在通常情況下與代碼在靜態(tài)上無(wú)法區(qū)分。 IDA Pro或替代產(chǎn)品通常能夠識(shí)別字符串,但它們也需要對(duì)二進(jìn)制文件運(yùn)行更高級(jí)且計(jì)算量更大的分析。

 

列表初始化

在Meterpreter的代碼庫(kù)中,可以在幾個(gè)文件中找到這種構(gòu)造,例如在c/meterpreter/source/extensions/extapi/extapi.c 中:

Command customCommands[] =
{
    COMMAND_REQ("extapi_window_enum", request_window_enum),
    COMMAND_REQ("extapi_service_enum", request_service_enum),
    COMMAND_REQ("extapi_service_query", request_service_query),
    COMMAND_REQ("extapi_service_control", request_service_control),
    COMMAND_REQ("extapi_clipboard_get_data", request_clipboard_get_data),
    COMMAND_REQ("extapi_clipboard_set_data", request_clipboard_set_data),
    COMMAND_REQ("extapi_clipboard_monitor_start", request_clipboard_monitor_start),
    COMMAND_REQ("extapi_clipboard_monitor_pause", request_clipboard_monitor_pause),
    COMMAND_REQ("extapi_clipboard_monitor_resume", request_clipboard_monitor_resume),
    COMMAND_REQ("extapi_clipboard_monitor_purge", request_clipboard_monitor_purge),
    COMMAND_REQ("extapi_clipboard_monitor_stop", request_clipboard_monitor_stop),
    COMMAND_REQ("extapi_clipboard_monitor_dump", request_clipboard_monitor_dump),
    COMMAND_REQ("extapi_adsi_domain_query", request_adsi_domain_query),
    COMMAND_REQ("extapi_ntds_parse", ntds_parse),
    COMMAND_REQ("extapi_wmi_query", request_wmi_query),
    COMMAND_REQ("extapi_pageant_send_query", request_pageant_send_query),
    ...
}

這些字符串以明文形式存儲(chǔ)在ext_server_espia.x64.dll的.rdata節(jié)中,并由ESET Nod32進(jìn)行選擇。

更糟糕的是,這些字符串是位于列表初始化程序中的宏的參數(shù)。 這引入了很多棘手的案例,但這些案例并不需要關(guān)心。我們的目的是自動(dòng)重寫此代碼段,如下所示:

char hid_extapi_UQOoNXigAPq4[] = {'e','x','t','a','p','i','_','w','i','n','d','o','w','_','e','n','u','m',0};
char hid_extapi_vhFHmZ8u2hfz[] = {'e','x','t','a','p','i','_','s','e','r','v','i','c','e','_','e','n','u','m',0};
char hid_extapi_pW25eeIGBeru[] = {'e','x','t','a','p','i','_','s','e','r','v','i','c','e','_','q','u','e','r','y'
0};
char hid_extapi_S4Ws57MYBjib[] = {'e','x','t','a','p','i','_','s','e','r','v','i','c','e','_','c','o','n','t','r'
'o','l',0};
char hid_extapi_HJ0lD9Dl56A4[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','g','e','t'
'_','d','a','t','a',0};
char hid_extapi_IiEzXils3UsR[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','s','e','t'
'_','d','a','t','a',0};
char hid_extapi_czLOBo0HcqCP[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','m','o','n'
'i','t','o','r','_','s','t','a','r','t',0};
char hid_extapi_WcWbTrsQujiT[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','m','o','n'
'i','t','o','r','_','p','a','u','s','e',0};
char hid_extapi_rPiFTZW4ShwA[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','m','o','n'
'i','t','o','r','_','r','e','s','u','m','e',0};
char hid_extapi_05fAoaZLqOoy[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','m','o','n'
'i','t','o','r','_','p','u','r','g','e',0};
char hid_extapi_cOOyHTPTvZGK[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','m','o','n','i','t','o','r','_','s','t','o','p',0};
char hid_extapi_smtmvW05cI9y[] = {'e','x','t','a','p','i','_','c','l','i','p','b','o','a','r','d','_','m','o','n','i','t','o','r','_','d','u','m','p',0};
char hid_extapi_01kuYCM8z49k[] = {'e','x','t','a','p','i','_','a','d','s','i','_','d','o','m','a','i','n','_','q','u','e','r','y',0};
char hid_extapi_SMK9uFj6nThk[] = {'e','x','t','a','p','i','_','n','t','d','s','_','p','a','r','s','e',0};
char hid_extapi_PHxnGM7M0609[] = {'e','x','t','a','p','i','_','w','m','i','_','q','u','e','r','y',0};
char hid_extapi_J7EGS6FRHwkV[] = {'e','x','t','a','p','i','_','p','a','g','e','a','n','t','_','s','e','n','d','_','q','u','e','r','y',0};

Command customCommands[] =
{

    COMMAND_REQ(hid_extapi_UQOoNXigAPq4, request_window_enum),
    COMMAND_REQ(hid_extapi_vhFHmZ8u2hfz, request_service_enum),
    COMMAND_REQ(hid_extapi_pW25eeIGBeru, request_service_query),
    COMMAND_REQ(hid_extapi_S4Ws57MYBjib, request_service_control),
    COMMAND_REQ(hid_extapi_HJ0lD9Dl56A4, request_clipboard_get_data),
    COMMAND_REQ(hid_extapi_IiEzXils3UsR, request_clipboard_set_data),
    COMMAND_REQ(hid_extapi_czLOBo0HcqCP, request_clipboard_monitor_start),
    COMMAND_REQ(hid_extapi_WcWbTrsQujiT, request_clipboard_monitor_pause),
    COMMAND_REQ(hid_extapi_rPiFTZW4ShwA, request_clipboard_monitor_resume),
    COMMAND_REQ(hid_extapi_05fAoaZLqOoy, request_clipboard_monitor_purge),
    COMMAND_REQ(hid_extapi_cOOyHTPTvZGK, request_clipboard_monitor_stop),
    COMMAND_REQ(hid_extapi_smtmvW05cI9y, request_clipboard_monitor_dump),
    COMMAND_REQ(hid_extapi_01kuYCM8z49k, request_adsi_domain_query),
    COMMAND_REQ(hid_extapi_SMK9uFj6nThk, ntds_parse),
    COMMAND_REQ(hid_extapi_PHxnGM7M0609, request_wmi_query),
    COMMAND_REQ(hid_extapi_J7EGS6FRHwkV, request_pageant_send_query),
    COMMAND_TERMINATOR
};

 

隱藏API導(dǎo)入

調(diào)用由外部庫(kù)導(dǎo)出的函數(shù)會(huì)使鏈接器向?qū)氲刂繁?IAT)寫入一個(gè)條目。 最終函數(shù)名稱將在二進(jìn)制文件中以明文形式顯示,因此在無(wú)需執(zhí)行惡意文件的情況下,可以靜態(tài)恢復(fù)函數(shù)名。 當(dāng)然,有些函數(shù)名稱相比其他名稱更可疑。 一個(gè)更明智的做法是隱藏所有可疑二進(jìn)制文件,同時(shí),保留大多數(shù)合法二進(jìn)制文件中存在的文件。

例如,在Metepreter的kiwi擴(kuò)展中,可以找到以下行:

enumStatus = SamEnumerateUsersInDomain(hDomain, &EnumerationContext, 0, &pEnumBuffer, 100, &CountRetourned);

該函數(shù)由samlib.dll導(dǎo)出,因此鏈接器會(huì)使字符串samlib.dll和SamEnumerateUsersInDomain出現(xiàn)在已編譯的二進(jìn)制文件中。

要解決此問(wèn)題,可以在運(yùn)行時(shí)使用LoadLibrary/GetProcAddresss導(dǎo)入API。當(dāng)然,這兩個(gè)函數(shù)都適用于字符串,因此也必須對(duì)其進(jìn)行混淆。 因此,我們希望按如下所示自動(dòng)重寫上述代碼段:

typedef NTSTATUS(__stdcall* _SamEnumerateUsersInDomain)(
    SAMPR_HANDLE DomainHandle,
    PDword EnumerationContext,
    DWORD UserAccountControl,
    PSAMPR_RID_ENUMERATION* Buffer,
    DWORD PreferedMaximumLength,
    PDWORD CountReturned
);
char hid_SAMLIB_01zmejmkLCHt[] = {'S','A','M','L','I','B','.','D','L','L',0};
char hid_SamEnu_BZxlW5ZBUAAe[] = {'S','a','m','E','n','u','m','e','r','a','t','e','U','s','e','r','s','I','n','D','o','m','a','i','n',0};
HANDLE hhid_SAMLIB_BZUriyLrlgrJ = LoadLibrary(hid_SAMLIB_01zmejmkLCHt);
_SamEnumerateUsersInDomain ffSamEnumerateUsersInDoma =(_SamEnumerateUsersInDomain)GetProcAddress(hhid_SAMLIB_BZUriyLrlgrJ, hid_SamEnu_BZxlW5ZBUAAe);
enumStatus = ffSamEnumerateUsersInDoma(hDomain, &EnumerationContext, 0, &pEnumBuffer, 100, &CountRetourned);
Rewriting syscalls

 

重寫系統(tǒng)調(diào)用

默認(rèn)情況下,在運(yùn)行Cylance的計(jì)算機(jī)上使用Meterpreter的migration命令會(huì)觸發(fā)殺軟檢測(cè)(請(qǐng)謹(jǐn)慎)。 Cylance會(huì)使用用戶界面hook檢測(cè)進(jìn)程注入。 為了避開檢測(cè),可以去掉hook,這似乎是現(xiàn)在的主流趨勢(shì),或者可以完全避免使用hook。 我們發(fā)現(xiàn),讀取ntdll,恢復(fù)系統(tǒng)調(diào)用號(hào)并將其插入隨時(shí)可以調(diào)用的Shellcode中更為簡(jiǎn)單,這可以有效地繞過(guò)任何殺軟用戶區(qū)的鉤子。 迄今為止,我們尚未找到可以將從磁盤讀取的NTDLL.DLL的行為識(shí)別為可疑的藍(lán)隊(duì)。

 

實(shí)現(xiàn)

上述所有想法都可以在基于libtools的源代碼重構(gòu)工具中實(shí)現(xiàn)。本節(jié)說(shuō)明我們是如何做到這一點(diǎn)的,這是在時(shí)間和耐心之間的妥協(xié)。因?yàn)槿鄙賚ibtools文檔,所以此處還有改進(jìn)的空間,如果你發(fā)現(xiàn)了什么,那可能是我們希望得到的反饋。

抽象語(yǔ)法樹101

編譯器通常包括幾個(gè)組件,最常見的是解析器和詞法分析器。 當(dāng)將源代碼提供給編譯器時(shí),它首先從原始源代碼(程序員編寫的代碼)中生成一個(gè)解析樹,然后將語(yǔ)義信息添加到節(jié)點(diǎn)(編譯器真正需要的)。 此步驟的結(jié)果稱為抽象語(yǔ)法樹。 維基百科展示了以下示例:

while b ≠ 0
  if a > b
    a := a − b
  else
    b := b − a
return a

這個(gè)小程序的典型AST如下所示:

自動(dòng)重構(gòu)Meterpreter繞過(guò)殺軟

 

在編寫需要理解其他程序?qū)傩缘某绦驎r(shí),這個(gè)數(shù)據(jù)結(jié)構(gòu)允許使用更精確的算法,因此執(zhí)行大規(guī)模代碼重構(gòu)是一個(gè)不錯(cuò)的選擇。

Clang的抽象語(yǔ)法樹

由于我們需要正確修改源代碼,因此我們需要熟悉Clang的AST。 好消息是Clang公開了一個(gè)命令行開關(guān),以漂亮的顏色轉(zhuǎn)儲(chǔ)AST。 壞消息是,對(duì)于除”玩具項(xiàng)目”以外的所有項(xiàng)目,設(shè)置正確的編譯器標(biāo)志都非常棘手。

現(xiàn)在,讓我們做一個(gè)現(xiàn)實(shí)而簡(jiǎn)單的翻譯測(cè)試單元:

#include <windows.h>

typedef NTSTATUS (NTAPI *f_NtMapViewOfSection)(HANDLE, HANDLE, PVOID *, ULONG, ULONG,
PLARGE_INTEGER, PULONG, ULONG, ULONG, ULONG);

int main(void)
{
    f_NtMapViewOfSection lNtMapViewOfSection;
    HMODULE ntdll;

    if (!(ntdll = LoadLibrary(TEXT("ntdll"))))
    {
        return -1;
    }

    lNtMapViewOfSection = (f_NtMapViewOfSection)GetProcAddress(ntdll, "NtMapViewOfSection");
    lNtMapViewOfSection(0,0,0,0,0,0,0,0,0,0);
    return 0;
}

然后,將以下腳本插入.sh文件中:

WIN_INCLUDE="/Users/vladimir/headers/winsdk"
CLANG_PATH="/usr/local/Cellar/llvm/9.0.1"#"/usr/lib/clang/8.0.1/"

clang -cc1 -ast-dump "$1" -D "_WIN64" -D "_UNICODE" -D "UNICODE" -D "_WINSOCK_DEPRECATED_NO_WARNINGS"
  "-I" "$CLANG_PATH/include" 
  "-I" "$CLANG_PATH" 
  "-I" "$WIN_INCLUDE/Include/msvc-14.15.26726-include"
  "-I" "$WIN_INCLUDE/Include/10.0.17134.0/ucrt" 
  "-I" "$WIN_INCLUDE/Include/10.0.17134.0/shared" 
  "-I" "$WIN_INCLUDE/Include/10.0.17134.0/um" 
  "-I" "$WIN_INCLUDE/Include/10.0.17134.0/winrt" 
  "-fdeprecated-macro" 
  "-w" 
  "-fdebug-compilation-dir"
  "-fno-use-cxa-atexit" "-fms-extensions" "-fms-compatibility" 
  "-fms-compatibility-version=19.15.26726" "-std=c++14" "-fdelayed-template-parsing" "-fobjc-runtime=gcc" "-fcxx-exceptions" "-fexceptions" "-fseh-exceptions" "-fdiagnostics-show-option" "-fcolor-diagnostics" "-x" "c++"

請(qǐng)注意,WIN_INCLUDE指向一個(gè)文件夾,其中包含與Win32 API進(jìn)行交互的所有必需的標(biāo)頭。 這些是從標(biāo)準(zhǔn)Windows 10安裝中直接獲取的,為了避免讓你頭疼,我們建議你執(zhí)行相同操作,千萬(wàn)不要選擇MinGW。 然后,以需要測(cè)試的C文件作為參數(shù)來(lái)調(diào)用腳本。 雖然這會(huì)產(chǎn)生一個(gè)18MB的文件,但通過(guò)搜索我們定義的字符串之一(例如“ NtMapViewOfSection”),可以輕松導(dǎo)航到AST的有趣部分:

自動(dòng)重構(gòu)Meterpreter繞過(guò)殺軟

 

現(xiàn)在,我們有了一種可視化AST的方法,可以更輕易地了解如何更新節(jié)點(diǎn)才能獲得結(jié)果,同時(shí)不在結(jié)果源代碼中引入任何語(yǔ)法錯(cuò)誤。 以下各節(jié)包含與使用libTooling進(jìn)行AST操作有關(guān)實(shí)現(xiàn)的詳細(xì)信息。

ClangTool樣板

在進(jìn)入有趣的內(nèi)容之前,需要一些樣板代碼,因此需要將以下代碼插入main.cpp中:

#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Type.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "clang/Rewrite/Core/Rewriter.h"

// LLVM includes
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/raw_ostream.h"

#include "Consumer.h"
#include "MatchHandler.h"

#include <IOStream>
#include <memory>
#include <string>
#include <vector>
#include <fstream>
#include <clang/Tooling/Inclusions/IncludeStyle.h>
#include <clang/Tooling/Inclusions/HeaderIncludes.h>
#include <sstream>

namespace ClSetup {
    llvm::cl::OptionCategory ToolCategory("StringEncryptor");
}

namespace StringEncryptor {

    clang::Rewriter ASTRewriter;
    class Action : public clang::ASTFrontendAction {

    public:
        using ASTConsumerPointer = std::unique_ptr<clang::ASTConsumer>;

        ASTConsumerPointer CreateASTConsumer(clang::CompilerInstance &Compiler,
                                             llvm::StringRef Filename) override {

            ASTRewriter.setSourceMgr(Compiler.getSourceManager(), Compiler.getLangOpts());
            std::vector<ASTConsumer*> consumers;

            consumers.push_back(&StringConsumer);

            // several passes can be combined together by adding them to `consumers`
            auto TheConsumer = llvm::make_unique<Consumer>();
            TheConsumer->consumers = consumers;
            return TheConsumer;
        }

        bool BeginSourceFileAction(clang::CompilerInstance &Compiler) override {
            llvm::outs() << "Processing file " << 'n';
            return true;
        }

        void EndSourceFileAction() override {

            clang::SourceManager &SM = ASTRewriter.getSourceMgr();

            std::string FileName = SM.getFileEntryForID(SM.getMainFileID())->getName();
            llvm::errs() << "** EndSourceFileAction for: " << FileName << "n";

            // Now emit the rewritten buffer.
            llvm::errs() << "Here is the edited source file :nn";
            std::string TypeS;
            llvm::raw_string_ostream s(TypeS);
            auto FileID = SM.getMainFileID();
            auto ReWriteBuffer = ASTRewriter.getRewriteBufferFor(FileID);

            if(ReWriteBuffer != nullptr)
                ReWriteBuffer->write((s));
            else{
                llvm::errs() << "File was not modifiedn";
                return;
            }

            std::string result = s.str();
            std::ofstream fo(FileName);

            if(fo.is_open())
                fo << result;
            else
                llvm::errs() << "[!] Error saving result to " << FileName << "n";
        }
    };
}

auto main(int argc, const char *argv[]) -> int {

    using namespace clang::tooling;
    using namespace ClSetup;

    CommonOptionsParser OptionsParser(argc, argv, ToolCategory);
    ClangTool Tool(OptionsParser.getCompilations(),
                   OptionsParser.getSourcePathList());

    auto Action = newFrontendActionFactory<StringEncryptor::Action>();
    return Tool.run(Action.get());
}

由于該樣板代碼取自官方文檔中的示例,因此不需要再進(jìn)一步的描述。 唯一值得一提的修改是在CreateASTConsumer的內(nèi)部。 我們的最終目標(biāo)是在同一個(gè)翻譯單元上進(jìn)行多次轉(zhuǎn)換。 可以通過(guò)將項(xiàng)目添加到集合中來(lái)完成(基本行是consumers.push_back(&...);)。

字符串混淆

本節(jié)描述有關(guān)字符串混淆過(guò)程的最重要的實(shí)現(xiàn)細(xì)節(jié),包括三個(gè)步驟:

在源代碼中找到字符串。用變量替換它們?cè)谶m當(dāng)?shù)奈恢茫ò瘮?shù)或全局上下文中)插入變量定義/賦值。

在源代碼中查找字符串文字

可以如下定義StringConsumer(在StringEncryptor命名空間的開頭):

class StringEncryptionConsumer : public clang::ASTConsumer {
public:

    void HandleTranslationUnit(clang::ASTContext &Context) override {
        using namespace clang::ast_matchers;
        using namespace StringEncryptor;

        llvm::outs() << "[StringEncryption] Registering ASTMatcher...n";
        MatchFinder Finder;
        MatchHandler Handler(&ASTRewriter);

        const auto Matcher = stringLiteral().bind("decl");

        Finder.addMatcher(Matcher, &Handler);
        Finder.matchAST(Context);
    }
};

StringEncryptionConsumer StringConsumer = StringEncryptionConsumer();

給定一個(gè)翻譯單元,我們可以告訴Clang在AST中找到一個(gè)模式,同時(shí)注冊(cè)一個(gè)“處理程序”以在找到匹配項(xiàng)時(shí)調(diào)用。 Clang的ASTMatcher公開的模式匹配功能非常強(qiáng)大,但在這里并未得到充分利用,因?yàn)槲覀儍H是用它來(lái)定位字符串。

然后,我們可以通過(guò)實(shí)現(xiàn)MatchHandler來(lái)解決問(wèn)題,它將為我們提供MatchResult實(shí)例。 MatchResult包含對(duì)標(biāo)識(shí)的AST節(jié)點(diǎn)的引用以及上下文信息。

接下來(lái)我們可以實(shí)現(xiàn)一個(gè)類的定義,并從clang::ast_matchers::MatchFinder::MatchCallback:繼承一些東西:

#ifndef AVCLEANER_MATCHHANDLER_H
#define AVCLEANER_MATCHHANDLER_H

#include <vector>
#include <string>
#include <memory>
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/ArrayRef.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Tooling/Tooling.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Basic/SourceManager.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/AST/Type.h"
#include "clang/AST/Decl.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTConsumer.h"
#include "MatchHandler.h"

class MatchHandler : public clang::ast_matchers::MatchFinder::MatchCallback {

public:
    using MatchResult = clang::ast_matchers::MatchFinder::MatchResult;

    MatchHandler(clang::Rewriter *rewriter);
    void run(const MatchResult &Result) override; // callback function that runs whenever a Match is found.

};

#endif //AVCLEANER_MATCHHANDLER_H

在MatchHandler.cpp中,我們必須實(shí)現(xiàn)MatchHandler的構(gòu)造函數(shù)和run回調(diào)函數(shù)。 構(gòu)造函數(shù)非常簡(jiǎn)單,僅僅只需要存儲(chǔ) clang::Rewrite的實(shí)例以供以后使用即可:

using namespace clang;

MatchHandler::MatchHandler(clang::Rewriter *rewriter) {
    this->ASTRewriter = rewriter;
}

run的實(shí)現(xiàn)如下所示:

void MatchHandler::run(const MatchResult &Result) {
    const auto *Decl = Result.Nodes.getNodeAs<clang::StringLiteral>("decl");
    clang::SourceManager &SM = ASTRewriter->getSourceMgr();

    // skip strings in included headers
    if (!SM.isInMainFile(Decl->getBeginLoc()))
        return;

    // strings that comprise less than 5 characters are not worth the effort
    if (!Decl->getBytes().str().size() > 4) {
        return;
    }

    climbParentsIgnoreCast(*Decl, clang::ast_type_traits::DynTypedNode(), Result.Context, 0);
}

上面摘錄的三段代碼中有三個(gè)點(diǎn)值得一提:

我們提取與StringEncryptionConsumer中定義的模式匹配的AST節(jié)點(diǎn)。為此,可以調(diào)用函數(shù)getNodeAs,它需要一個(gè)字符串作為參數(shù),該參數(shù)與模式綁定的標(biāo)識(shí)符相關(guān)(請(qǐng)參見const auto Matcher = stringLiteral().bind("decl")行)我們會(huì)跳過(guò)分析中沒有在翻譯單元中定義的字符串。實(shí)際上,我們的過(guò)程會(huì)在Clang的預(yù)處理程序之后進(jìn)行干預(yù),它會(huì)將包含的系統(tǒng)頭的內(nèi)容復(fù)制粘貼到翻譯單元中。然后,我們準(zhǔn)備處理字符串。 由于我們需要在上下文中找到此字符串,因此需將提取的節(jié)點(diǎn)沿著Result.Context傳遞給用戶定義的函數(shù)(在本例中為climbParentsIgnoreCast,因?yàn)槿鄙俑玫拿Q),其中包含一個(gè) 參考隨附的AST。 目標(biāo)是向上訪問(wèn)樹,直到找到有趣的節(jié)點(diǎn)。 在這種情況下,我們對(duì)CallExpr類型的節(jié)點(diǎn)感興趣。

bool
MatchHandler::climbParentsIgnoreCast(const StringLiteral &NodeString, clang::ast_type_traits::DynTypedNode node,
                                     clang::ASTContext *const pContext, uint64_t iterations) {

    ASTContext::DynTypedNodeList parents = pContext->getParents(NodeString);

    if (iterations > 0) {
        parents = pContext->getParents(node);
    }

    for (const auto &parent : parents) {

        StringRef ParentNodeKind = parent.getNodeKind().asStringRef();

        if (ParentNodeKind.find("Cast") != std::string::npos) {

            return climbParentsIgnoreCast(NodeString, parent, pContext, ++iterations);
        }

        handleStringInContext(&NodeString, pContext, parent);
    }

    return false;
}

簡(jiǎn)而言之,此函數(shù)以遞歸方式查找StringLiteral節(jié)點(diǎn)的父節(jié)點(diǎn),直到找到一個(gè)有趣的節(jié)點(diǎn)(非“ cast”)。 handleStringInContext也很簡(jiǎn)單:

void MatchHandler::handleStringInContext(const clang::StringLiteral *pLiteral, clang::ASTContext *const pContext,
                                         const clang::ast_type_traits::DynTypedNode node) {

    StringRef ParentNodeKind = node.getNodeKind().asStringRef();

    if (ParentNodeKind.compare("CallExpr") == 0) {
        handleCallExpr(pLiteral, pContext, node);
    } else if (ParentNodeKind.compare("InitListExpr") == 0) {
        handleInitListExpr(pLiteral, pContext, node);
    } else {
        llvm::outs() << "Unhandled context " << ParentNodeKind << " for string " << pLiteral->getBytes() << "n";
    }
}

從上面的代碼片段可以明顯看出,實(shí)際上只有兩種類型的節(jié)點(diǎn)可以處理。 如果需要,添加更多內(nèi)容也應(yīng)該很容易。 事實(shí)上,這兩種情況都已經(jīng)用類似的方式處理。

void MatchHandler::handleCallExpr(const clang::StringLiteral *pLiteral, clang::ASTContext *const pContext,
                                  const clang::ast_type_traits::DynTypedNode node) {

    const auto *FunctionCall = node.get<clang::CallExpr>();

    if (isBlacklistedFunction(FunctionCall)) {
        return; // exclude printf-like functions when the replacement is not constant anymore (C89 standard...).
    }

    handleExpr(pLiteral, pContext, node);
}

void MatchHandler::handleInitListExpr(const clang::StringLiteral *pLiteral, clang::ASTContext *const pContext,
                                      const clang::ast_type_traits::DynTypedNode node) {

    handleExpr(pLiteral, pContext, node);
}

替換字符串

由于CallExpr和InitListExpr都可以用類似的方式處理,因此我們定義了一個(gè)公共的可用函數(shù)。

bool MatchHandler::handleExpr(const clang::StringLiteral *pLiteral, clang::ASTContext *const pContext,
                                  const clang::ast_type_traits::DynTypedNode node) {

    clang::SourceRange LiteralRange = clang::SourceRange(
            ASTRewriter->getSourceMgr().getFileLoc(pLiteral->getBeginLoc()),
            ASTRewriter->getSourceMgr().getFileLoc(pLiteral->getEndLoc())
    );

    if(shouldAbort(pLiteral, pContext, LiteralRange))
        return false;

    std::string Replacement = translateStringToIdentifier(pLiteral->getBytes().str());

    if(!insertVariableDeclaration(pLiteral, pContext, LiteralRange, Replacement))
        return false ;

    Globs::PatchedSourceLocation.push_back(LiteralRange);

    return replaceStringLiteral(pLiteral, pContext, LiteralRange, Replacement);
}

我們隨機(jī)生成一個(gè)變量名。在最近的位置找到一些空白空間,然后插入變量聲明。 這基本上是一個(gè)ASTRewriter->InsertText()的包裝將字符串替換為在步驟1中生成的標(biāo)識(shí)符將字符串所在位置添加到集合中。 這很有用,因?yàn)樵谠L問(wèn)InitListExpr時(shí),相同的字符串將出現(xiàn)兩次(不知道為什么)。

最后一步是真正難以實(shí)現(xiàn)的一步,因此讓我們首先關(guān)注這一點(diǎn):

bool MatchHandler::replaceStringLiteral(const clang::StringLiteral *pLiteral, clang::ASTContext *const pContext,
                                        clang::SourceRange LiteralRange,
                                        const std::string& Replacement) {

    // handle "TEXT" macro argument, for instance LoadLibrary(TEXT("ntdll"));
    bool isMacro = ASTRewriter->getSourceMgr().isMacroBodyExpansion(pLiteral->getBeginLoc());

    if (isMacro) {
        StringRef OrigText = clang::Lexer::getSourceText(CharSourceRange(pLiteral->getSourceRange(), true),
                                                         pContext->getSourceManager(), pContext->getLangOpts());

        // weird bug with TEXT Macro / other macros...there must be a proper way to do this.
        if (OrigText.find("TEXT") != std::string::npos) {

            ASTRewriter->RemoveText(LiteralRange);
            LiteralRange.setEnd(ASTRewriter->getSourceMgr().getFileLoc(pLiteral->getEndLoc().getLocWithOffset(-1)));
        }
    }

    return ASTRewriter->ReplaceText(LiteralRange, Replacement);
}

通常情況下,應(yīng)該使用ReplaceText API實(shí)現(xiàn)替換文本,但是實(shí)際上遇到了很多錯(cuò)誤。 當(dāng)涉及到宏時(shí),由于Clang的API行為不一致,因此事情會(huì)變得非常復(fù)雜。 例如,如果取消選中isMacroBodyExpansion(),最終將替換“ TEXT”而不是其參數(shù)。

例如,在LoadLibrary(TEXT("ntdll"))中,實(shí)際結(jié)果會(huì)變成LoadLibrary(your_variable("ntdll")),這明顯是錯(cuò)誤的。

原因是TEXT是一個(gè)宏,當(dāng)由Clang的預(yù)處理器處理時(shí),會(huì)將其替換為L(zhǎng)"ntdll"。 我們的轉(zhuǎn)換過(guò)程是在預(yù)處理器完成工作之后發(fā)生的,因此查詢標(biāo)記”ntdll”的開始和結(jié)束位置將產(chǎn)生幾個(gè)字符的額外值,這對(duì)我們沒有用處。 不幸的是,使用Clang的API來(lái)查詢?cè)挤g單元中的實(shí)際位置是一種魔咒,只能通過(guò)不斷試錯(cuò)來(lái)進(jìn)行。

在最近的空白位置插入變量聲明

現(xiàn)在我們可以用變量標(biāo)識(shí)符替換字符串了,我們的目標(biāo)是定義該變量并為它分配給原始字符串的值。 簡(jiǎn)單的說(shuō),我們希望打補(bǔ)丁后的源代碼包含char your_variable[] = "ntdll",同時(shí)不覆蓋任何內(nèi)容。

可能有兩種情況:

字符串文字位于函數(shù)體內(nèi)。字符串文字位于函數(shù)主體外部。

后者是最直接的方法,因?yàn)橹恍枰檎沂褂米址谋磉_(dá)式的開頭即可。

對(duì)于前者,我們需要找到封閉函數(shù)。 Clang公開了一個(gè)API來(lái)查詢函數(shù)體的起始位置。 這是插入變量聲明的理想位置,因?yàn)樵撟兞繉⒃谡麄€(gè)函數(shù)中可見,并且我們插入的標(biāo)記不會(huì)覆蓋內(nèi)容。

在任何情況下,這兩種情況都可以通過(guò)訪問(wèn)每一個(gè)父節(jié)點(diǎn)來(lái)解決,直到找到一個(gè)FunctionDecl或VarDecl類型的節(jié)點(diǎn)為止:

MatchHandler::findInjectionSpot(clang::ASTContext *const Context, clang::ast_type_traits::DynTypedNode Parent,
                                const clang::StringLiteral &Literal, bool IsGlobal, uint64_t Iterations) {

    if (Iterations > CLIMB_PARENTS_MAX_ITER)
        throw std::runtime_error("Reached max iterations when trying to find a function declaration");

    ASTContext::DynTypedNodeList parents = Context->getParents(Literal);;

    if (Iterations > 0) {
        parents = Context->getParents(Parent);
    }

    for (const auto &parent : parents) {

        StringRef ParentNodeKind = parent.getNodeKind().asStringRef();

        if (ParentNodeKind.find("FunctionDecl") != std::string::npos) {
            auto FunDecl = parent.get<clang::FunctionDecl>();
            auto *Statement = FunDecl->getBody();
            auto *FirstChild = *Statement->child_begin();
            return {FirstChild->getBeginLoc(), FunDecl->getEndLoc()};

        } else if (ParentNodeKind.find("VarDecl") != std::string::npos) {

            if (IsGlobal) {
                return parent.get<clang::VarDecl>()->getSourceRange();
            }
        }

        return findInjectionSpot(Context, parent, Literal, IsGlobal, ++Iterations);
    }
}

 

測(cè)試

git clone https://github.com/SCRT/avcleaner
mkdir avcleaner/CMakeBuild && cd avcleaner/CMakeBuild
cmake ..
make
cd ..
bash run_example.sh test/string_simplest.c
自動(dòng)重構(gòu)Meterpreter繞過(guò)殺軟

 

如您所見,這很好用。 現(xiàn)在,此示例非常簡(jiǎn)單,可以使用正則表達(dá)式解決,并減少代碼行數(shù)。

分享到:
標(biāo)簽:重構(gòu) Meterpreter
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定