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 (有詳細的視頻過程演示和說明)