电竞比分网-中国电竞赛事及体育赛事平台

分享

惡意代碼常用技術(shù)解析

 昵稱70680357 2020-06-30

惡意代碼分析之注入技術(shù)

? 在很多時候?yàn)榱四軌驅(qū)δ繕?biāo)進(jìn)程空間數(shù)據(jù)進(jìn)行修改,或者使用目標(biāo)進(jìn)程的名稱來執(zhí)行自己的代碼,實(shí)現(xiàn)危害用戶的操作,通常是將一個DLL文件或者ShellCode注入到目標(biāo)進(jìn)程中去執(zhí)行。這里分享四種常用的注入技術(shù),其中使用DLL注入的方法最為普遍。

全局鉤子注入

? 在Windows中大部份的應(yīng)用程序都是基于消息機(jī)制的,他們都有一個消息過程函數(shù),根據(jù)消息完成不同的功能。Windows操作系統(tǒng)提供的鉤子機(jī)制就是用來截獲和監(jiān)視這些消息的。按照鉤子的范圍不同,它們又可以分為局部鉤子和全局鉤子,局部鉤子是針對某個線程的;而全局鉤子則是作用于整個系統(tǒng)的基于消息的應(yīng)用。全局鉤子需要使用DLL文件,在DLL中實(shí)現(xiàn)相應(yīng)的鉤子函數(shù)。

  • 關(guān)鍵函數(shù)安裝鉤子程序SetWindowsHookEx()
WINUSERAPI
HHOOK
WINAPI SetWindowsHookExA(
    _In_ int idHook,        // 要安裝的鉤子的類型例如鍵盤 鼠標(biāo) 對話框等
    _In_ HOOKPROC lpfn,     // 一個指向鉤子程序的指針
    _In_opt_ HINSTANCE hmod,// 包含lpfn參數(shù)指向的鉤子過程的DLL句柄
    _In_ DWORD dwThreadId); // 與鉤子程序相關(guān)聯(lián)的線程標(biāo)識符

成功返回DLL句柄,失敗返回NULL

  • 卸載鉤子函數(shù)UnhookWindowsHookEx
BOOL UnhookWindowsHookEx(
  HHOOK hhk
);
  • 鉤子回調(diào)函數(shù)
// 表示將當(dāng)前的鉤子傳遞給鉤子鏈中的下一個鉤子
LRESULT
WINAPI CallNextHookEx(
    _In_opt_ HHOOK hhk,
    _In_ int nCode,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam);

遠(yuǎn)程線程注入

遠(yuǎn)程線程注入是指一個進(jìn)程在另一個進(jìn)程中創(chuàng)建線程的技術(shù),是一種經(jīng)典的注入技術(shù)

  • 函數(shù)OpenProcess()——打開目標(biāo)進(jìn)程
HANDLE
WINAPI OpenProcess(
    _In_ DWORD dwDesiredAccess,  // 訪問進(jìn)程對象
    _In_ BOOL bInheritHandle,    // 若此值為true,則此進(jìn)程創(chuàng)建的進(jìn)程將繼承該句柄
    _In_ DWORD dwProcessId       // 要打開的本地進(jìn)程的PID
    );
// 返回值: 若成功返回句柄,失敗返回NULL
  • 函數(shù)VirtualAllocEx()

指定進(jìn)程的虛擬地址空間內(nèi)保留、提交或者更改內(nèi)存的狀態(tài)

LPVOID
WINAPI
VirtualAllocEx(
    _In_ HANDLE hProcess,      // 進(jìn)程句柄
    _In_opt_ LPVOID lpAddress, // 指定要分配頁面所需的起始指針,為NULL自動分配
    _In_ SIZE_T dwSize,        // 要分配內(nèi)存的大小
    _In_ DWORD flAllocationType, // 內(nèi)存分配的類型:保留、提交和更改
    _In_ DWORD flProtect         // 頁面區(qū)域的內(nèi)存保護(hù)
    );
// 返回值:函數(shù)成功返回分配的基址,失敗返回NULL
  • 函數(shù)WriteProcessMemory()——在指定的進(jìn)程中將數(shù)據(jù)寫入內(nèi)存區(qū)域
BOOL
WINAPI WriteProcessMemory(
    _In_ HANDLE hProcess,   // 要修改的進(jìn)程句柄
    _In_ LPVOID lpBaseAddress, // 指向指定進(jìn)程中寫入數(shù)據(jù)的基地址指針
    _In_reads_bytes_(nSize) LPCVOID lpBuffer, // 指向緩沖區(qū)的指針
    _In_ SIZE_T nSize,    // 要寫入指定進(jìn)程的字節(jié)數(shù)
    _Out_opt_ SIZE_T* lpNumberOfBytesWritten  // 指向變量的指針,該變量接收傳輸?shù)街付ㄟM(jìn)程的字節(jié)數(shù)
    );
// 返回值: 函數(shù)成功 != 0;失敗返回0 
// 注意:寫入?yún)^(qū)域的內(nèi)存要可訪問,否則操作失敗
  • 函數(shù)CreateRemoteThread()——實(shí)現(xiàn)注入的核心函數(shù)在另一個進(jìn)程的虛擬地址中創(chuàng)建運(yùn)行的線程
HANDLE
WINAPI CreateRemoteThread(
    _In_ HANDLE hProcess,   // 要創(chuàng)建線程的進(jìn)程的句柄
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    		// 指向安全描述符的指針
    _In_ SIZE_T dwStackSize,  // 堆棧的初始大小,若為0則新線程使用可執(zhí)行文件的默認(rèn)大小
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
    		// 指向由線程執(zhí)行類型為LPTHREAD_START_ROUTINE的應(yīng)用程序定義的函數(shù)指針,并表示遠(yuǎn)程進(jìn)程 中線程的起始地址,該函數(shù)必須存在于遠(yuǎn)程進(jìn)程中
    _In_opt_ LPVOID lpParameter,  // 要傳遞給線程函數(shù)的變量的指針
    _In_ DWORD dwCreationFlags,   // 控制線程創(chuàng)建的標(biāo)志
    _Out_opt_ LPDWORD lpThreadId  // 接收線程標(biāo)志符變量的指針
    );
// 返回值: 成功 新線程的句柄,失?。悍祷豊ULL

? 從以上這些函數(shù)的作用我們實(shí)現(xiàn)的原理就很清晰了,先在指定進(jìn)程申請一段地址然后將準(zhǔn)備好的shellcode或者一個DLL文件寫入到這塊內(nèi)存空間中。

? 注意:對于一些系統(tǒng)服務(wù)這樣通常會注入失敗,由于系統(tǒng)存在SESSION 0隔離的安全機(jī)制,需調(diào)用一個更加底層的ZwCreateThreadEx()來實(shí)現(xiàn)。

APC隊(duì)列注入

? APC(Asynchronus Procedure Call)為異步過程調(diào)用,是指函數(shù)在特定線程中被異步執(zhí)行。在Windows系統(tǒng)中,APC是一種并發(fā)機(jī)制,用于異步IO或者定時器。

每一個線程都有自己的APC隊(duì)列,使用QueueUserAPC函數(shù)把一個APC函數(shù)壓入APC隊(duì)列中。當(dāng)處于用戶模式的APC壓入線程APC隊(duì)列后,該線程并不直接調(diào)用APC函數(shù),除非該線程處于可通知狀態(tài),調(diào)用的順序?yàn)橄热胂瘸觥?/p>

  • 函數(shù)
WINBASEAPI
語言 方法
7279 6JQ2GywG8D
EIwRA 緒川里緒
8742 2010.09.08 05-16-01
DWORD WINAPI QueueUserAPC( _In_ PAPCFUNC pfnAPC, // 指向APC函數(shù)的指針 _In_ HANDLE hThread, // 線程句柄 _In_ ULONG_PTR dwData // 由pfnAPC參數(shù)指向的APC函數(shù)的單個值 ); // 返回值 成功非0; 失敗返回0

? APC的注入原理是利用當(dāng)線程被喚醒時APC中的注冊函數(shù)會執(zhí)行的機(jī)制,并以此去執(zhí)行DLL加載代碼,進(jìn)而完成DLL注入。為了增加成功率,可以向目標(biāo)進(jìn)程中的所有線程都插入APC。

自定義HOOK

  • 自定義HOOK大致可以分為兩類
    • inlineHOOK
    • IATHOOK
  • inlineHook是一種通過修改機(jī)器碼的方式來實(shí)現(xiàn)HOOK的技術(shù)

原理:對于一個正常的程序如下圖,通過CALL指令來調(diào)用函數(shù)。關(guān)于CALL指令相當(dāng)于push 當(dāng)前函數(shù)地址和jmp要執(zhí)行的指令位置,即 push 0171B7B3 jmp 0171B430,這是我們正常執(zhí)行00.0171B430這個函數(shù)的樣子。

我們在hook的時候就是將CALL指令直接改成jmp指令,跳到我們自己編寫的函數(shù)的位置,執(zhí)行完成之后跳回函數(shù)原來指令的下一條指令0171B7B3,需要注意的是跳轉(zhuǎn)偏移要多計(jì)算5個字節(jié)

計(jì)算公式: 跳轉(zhuǎn)偏移 = 目標(biāo)地址 - jmp所在的地址 - 5

  • 實(shí)現(xiàn)方法
  1. 獲取函數(shù)的實(shí)際地址
  2. 修改內(nèi)存分頁屬性
  3. 計(jì)算跳轉(zhuǎn)偏移,修改目標(biāo)地址,還原內(nèi)存屬性
  4. 獲取實(shí)際地址返回
void OnHook() {   
    //獲取函數(shù)實(shí)際地址    
    HMODULE Module = GetModuleHandleA("kernel32.dll");    
    LPVOID func = GetProcAddress(Module, "OpenProcess");
    //保存5個字節(jié)    
    memcpy(g_oldCode, func, 5);
    //修改內(nèi)存分頁屬性,由于代碼段是不可寫的,所有必須先將它的屬性變成可寫    
    DWORD dwProtect;    
    VirtualProtect(func, 5, PAGE_EXECUTE_READWRITE, &dwProtect);    
    //計(jì)算跳轉(zhuǎn)偏移
    *(DWORD*)&g_newCode[1] = (DWORD)MyOpenProcess - (DWORD)func - 5;    
    //修改目標(biāo)地址 
    memcpy(func, g_newCode, 5);    
    //還原內(nèi)存分頁屬性    
    VirtualProtect(func, 5, dwProtect, &dwProtect); 
};
  • 用戶層的IATHook是通過替換IAT表中函數(shù)的原始地址從而實(shí)現(xiàn)的Hook

? 與普通的InlineHook不一樣,IATHook需要充分理解PE文件的結(jié)構(gòu)才能完成,關(guān)于相對虛擬地址(RVA)、文件偏移地址(FOA)和加載基址等概念可以自行查閱相關(guān)資料。

  • 實(shí)現(xiàn)方法
//獲取指定dll導(dǎo)出地址表的中函數(shù)地址 
DWORD * GetIatAddress(const char * dllName, const char* funName) {    
    // 1. 獲取加載基址并轉(zhuǎn)換成DOS頭    
    auto DosHeader = (PIMAGE_DOS_HEADER)GetModuleHandle(NULL);
    // 2. 通過 DOS 頭的后一個字段 e_lfanew 找到 NT 頭的偏移    
    auto NtHeader = (PIMAGE_NT_HEADERS)(DosHeader->e_lfanew + (DWORD)DosHeader);
    // 3. 在數(shù)據(jù)目錄表下標(biāo)為[1]的地方找到導(dǎo)入表的RVA    
    DWORD ImpRVA = NtHeader->OptionalHeader.DataDirectory[1].VirtualAddress;
    // 4. 獲取到導(dǎo)入表結(jié)構(gòu)體,因?yàn)槌绦蛞呀?jīng)運(yùn)行了,所以不需要轉(zhuǎn)FOA    
    auto ImpTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)DosHeader + ImpRVA);
    // 遍歷導(dǎo)入表,以一組全0的結(jié)構(gòu)結(jié)尾    
    while (ImpTable->Name)  
    {        
        // 獲取當(dāng)前導(dǎo)入表結(jié)構(gòu)描述的結(jié)構(gòu)體的名稱        
        CHAR* Name = (CHAR*)(ImpTable->Name + (DWORD)DosHeader);
        // 忽略大小寫進(jìn)行比較,查看是否是需要的導(dǎo)入表結(jié)構(gòu)        
        if (!_stricmp(Name, dllName))        
        {            
            // 找到對應(yīng)的 INT 表以及 IAT 表            
            DWORD* IntTable = (DWORD*)((DWORD)DosHeader + ImpTable->OriginalFirstThunk);            
            DWORD* IatTable = (DWORD*)((DWORD)DosHeader + ImpTable->FirstThunk);            
            // 遍歷所有的函數(shù)名稱,包括有/沒有名稱            
            for (int i = 0; IntTable[i] != 0; ++i)            
            {                
                // 比對函數(shù)是否存在函數(shù)名稱表中                
                if ((IntTable[i] & 0x80000000) == 0)                
                {                    
                    // 獲取到導(dǎo)入名稱結(jié)構(gòu)                    
                    auto Name = (PIMAGE_IMPORT_BY_NAME)((DWORD)DosHeader + IntTable[i]);
                    // 比對函數(shù)的名稱                    
                    if (!strcmp(funName, Name->Name))                    
                    {                        
                        // 返回函數(shù)在IAT中保存的地址                        
                        return &IatTable[i];                    
                    }                
                }            
            }        
        }        
        ImpTable++;    
    }    
    return 0; 
}

總結(jié)

? 鉤子技術(shù)總結(jié)起來就是通過各種手段來修改代碼或者地址從而讓程序來執(zhí)行我們自己編寫的代碼,在分析惡意程序時關(guān)注一下這些敏感的API函數(shù)組合,在查看程序基本信息的時候就可以大致做出猜測。下一篇繼續(xù)分享常見的啟動和隱藏技術(shù),繼續(xù)刨析病毒的實(shí)現(xiàn)原理。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多