windows:shellcode生成框架和加載


  https://www.cnblogs.com/theseventhson/p/13194646.html  分享了shellcode 的基本原理,核心思路是動態獲取GetProcAddress和LoadLibrary函數地址,再通過這兩個函數獲取其他windows dll提供的函數地址;需要注意的是shellcode不能有全局變量和字符串,只能用局部變量和字符數組替代,這樣才能在函數執行時在棧動態分配存儲空間,不會和目標進程現有的空間沖突。

       實戰中,通過這種方式生成shellcode技術上可行,但比較麻煩,需要改動的地方太多,下面介紹一種shellcode現成的框架,開發人員可直接在特定的地方添加特定的代碼即可,簡單、靈活、易用;

  1、先介紹一下vs的編譯順序

        一個大型的工程,往往有多個cpp文件組成,最后都被編譯成一個exe文件,那么exe中那么多函數,這些函數都是怎么排序的了?

      (1)文件之間:比如有多個文件,默認是按照文件名排序,比如先按照0-9排序,再按照a-z排序;下面默認先編譯A.cpp,再編譯B.cpp,最后編譯main.c;但是也可以認為調整配置文件中的順序,讓B最先編譯,A稍后編譯,這樣一來生成exe中函數順序就按照這個來排序;

         

   (2)文件內部:按照定義/實現的順序;比如下面的例子:函數實現的順序分別是main、FuncB、FuncA,那么編譯器在生成exe時編譯的順序也是這樣的。

        

   上面說了這么多編譯順序,能用來干啥了?------>   確認shellcode 的范圍和提取代碼,比如上面用戶把shellcode寫進A和B函數,那么FuncA-FuncB者之間的代碼都是shellcode,再調用CreateFile函數就能很容易保存到磁盤了;

  2、shellcode框架介紹

  (1)先來看看shellcode生成框架:四個文件起名很有講究,這樣一來編譯器就會按照這個順序來編譯源文件了,生成的exe中也會按照這個順序排列函數;

    0.entry文件中,並不執行shellcode,只是通過shellcodeEnd-shellcodeStart 確認shellcode 的范圍和起始地址,然后寫入磁盤的bin文件;

       

   (2)a.start: shellcodeStart函數直接jmp到shellcodeEntry;InitFuntions初始化需要用到的幾個關鍵函數;最關鍵的shellcodeEntry函數:用戶可以在這里添加shellcode的自定義邏輯

       

      這里的shellcodeStart函數,是個裸函數,僅有一行代碼,就是jmp到shellcodeEntry,這么簡單的功能,為啥不直接把shellcodeEntry的代碼復制到這里來,從這里跳轉的意義是啥?

  •  shellcodeStart在這里,生成shellcode的時候才能把最核心的getKernel32和getProcAddress函數包進來;
  •    后續加載shellcode的時候會用call,那么需要從shellcode返回,這里是裸函數,沒有ret,等后續shellcode所有的函數執行完后,最后一個ret剛好可以返回shellcode加載器,這里設計比較巧妙;
__declspec(naked) void ShellcodeStart() 
{
    __asm 
    {
        jmp ShellcodeEntry
    }

}

   (3)b.works:開發人員可以在這里自定義shellcode的執行邏輯(包裝在單獨的函數里),然后在上面的shellcodeEntry中調用該函數即可;

      

   (4)最后一個z.end:收尾工作,目前是空白的,啥也沒做;由於shellcode的范圍是shellcodeEnd-shellcodeStart,所以shellcode在End這行代碼已經截至,這個函數內部任何代碼都不會被收錄到shellcode,這里請注意

      

   上面是整個框架的簡介,最核心的地方是a.start的shellcodeEntry和b.works,開發人員直接在這里添加自定義的處理邏輯就好,其他地方一般都不用更改了,整個框架的復用性非常好;編譯的時候也能看到各個文件編譯的順序:

  

       用IDA查看如下:函數的編譯順序確實是按照上面講的順序編譯的:

        

   3、shellcode代碼加載

  利用上述框架生成的shellcode,保存在磁盤bin文件,怎么加載和運行了?這里先介紹一個簡單的方法:自己編寫一個loader;

  核心原理很簡單:調用VirtualAlloc分配一段內存,然后把shellcode拷貝到這段內存,最后call跳轉到shellcode執行;

#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
int main(int argc, char* argv[])
{
    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 lpAddress = VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (lpAddress == NULL)
    {
        printf("VirtualAlloc error:%d\n", GetLastError());
        CloseHandle(hFile);
        return -1;

    }
    DWORD dwRead;
    ReadFile(hFile, lpAddress, dwSize, &dwRead, 0);
    __asm
    {
        call lpAddress;

    }
    _flushall();
    system("pause");
    return 0;
}

  用該加載器成功加載shellcode並運行:

  

 

   4、這里的shellcode是自己loader加載的,運行空間也是loader分配的,容易被查出來;后續會介紹一些其他方法,比如把shellcode注入其他進程執行,增加被查找到的難度;

最后感謝這兩個鏈接的作者(特別是視頻介紹,由淺入深,通俗易懂),供初學者參考:

https://www.freebuf.com/articles/system/93983.html (有現成的工程供參考使用)

https://www.bilibili.com/video/BV1y4411k7ch?p=10  (有詳細的視頻過程演示和說明)


免責聲明!

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



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