http://www.javashuo.com/article/p-kmjmdwcq-ne.html 分享了shellcode 的基本原理,核心思路是動態獲取GetProcAddress和LoadLibrary函數地址,再經過這兩個函數獲取其餘windows dll提供的函數地址;須要注意的是shellcode不能有全局變量和字符串,只能用局部變量和字符數組替代,這樣才能在函數執行時在棧動態分配存儲空間,不會和目標進程現有的空間衝突。html
實戰中,經過這種方式生成shellcode技術上可行,但比較麻煩,須要改動的地方太多,下面介紹一種shellcode現成的框架,開發人員可直接在特定的地方添加特定的代碼便可,簡單、靈活、易用;shell
一、先介紹一下vs的編譯順序windows
一個大型的工程,每每有多個cpp文件組成,最後都被編譯成一個exe文件,那麼exe中那麼多函數,這些函數都是怎麼排序的了?數組
(1)文件之間:好比有多個文件,默認是按照文件名排序,好比先按照0-9排序,再按照a-z排序;下面默認先編譯A.cpp,再編譯B.cpp,最後編譯main.c;可是也能夠認爲調整配置文件中的順序,讓B最早編譯,A稍後編譯,這樣一來生成exe中函數順序就按照這個來排序;框架
(2)文件內部:按照定義/實現的順序;好比下面的例子:函數實現的順序分別是main、FuncB、FuncA,那麼編譯器在生成exe時編譯的順序也是這樣的。ide
上面說了這麼多編譯順序,能用來幹啥了?------> 確認shellcode 的範圍和提取代碼,好比上面用戶把shellcode寫進A和B函數,那麼FuncA-FuncB者之間的代碼都是shellcode,再調用CreateFile函數就能很容易保存到磁盤了;函數
二、shellcode框架介紹spa
(1)先來看看shellcode生成框架:四個文件起名頗有講究,這樣一來編譯器就會按照這個順序來編譯源文件了,生成的exe中也會按照這個順序排列函數;設計
0.entry文件中,並不執行shellcode,只是經過shellcodeEnd-shellcodeStart 確認shellcode 的範圍和起始地址,而後寫入磁盤的bin文件;3d
(2)a.start: shellcodeStart函數直接jmp到shellcodeEntry;InitFuntions初始化須要用到的幾個關鍵函數;最關鍵的shellcodeEntry函數:用戶能夠在這裏添加shellcode的自定義邏輯;
這裏的shellcodeStart函數,是個裸函數,僅有一行代碼,就是jmp到shellcodeEntry,這麼簡單的功能,爲啥不直接把shellcodeEntry的代碼複製到這裏來,從這裏跳轉的意義是啥?
__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查看以下:函數的編譯順序確實是按照上面講的順序編譯的:
三、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並運行:
四、這裏的shellcode是本身loader加載的,運行空間也是loader分配的,容易被查出來;後續會介紹一些其餘方法,好比把shellcode注入其餘進程執行,增長被查找到的難度;
最後感謝這兩個連接的做者(特別是視頻介紹,由淺入深,通俗易懂),供初學者參考:
https://www.freebuf.com/articles/system/93983.html (有現成的工程供參考使用)
https://www.bilibili.com/video/BV1y4411k7ch?p=10 (有詳細的視頻過程演示和說明)