1. 程式人生 > 實用技巧 >通用ShellCode的編寫與呼叫

通用ShellCode的編寫與呼叫

首先,我們的ShellCode程式碼需要自定位,因為我們的程式碼並不是一個完整的EXE可執行程式,他沒有匯入表無法定位到當前系統中每個函式的虛擬地址,所以我們直接獲取到Kernel32.dll的基地址,裡面的GetProcAddr這個函式,獲取的方式有很多,第一種是暴力搜尋,第二種通過遍歷程序的TEB結構來實現,我們使用第二種方式嘗試,一旦獲取到該函式,就可以動態的呼叫任何想要的函數了。

獲取DLL模組基地址

首先開啟WinDbg載入符號連結檔案,輸入 srv*https://www.blib.cn/symbols

1.首先FS暫存器裡面儲存的是TEB結構,TEB是執行緒環境快,裡面的PET。

TEB的偏移位置30h處,存放的是PEB執行緒環境快。

接著解析一下 dt _peb 0026b000 裡面的0C欄位是LDR,一個指向_PEB_LDR_DATA的結構陣列。

PEB_LDR_DATA 結構體偏移位置為 0x1c 的地方存放著指向模組初始化連結串列的頭指標 InInitializationOrderModuleList,該指標指向了一個雙向連結串列。

模組初始化連結串列 InInitializationOrderModuleList 中按順序存放著PE裝入執行時初始化模組的資訊,第一個連結串列節點是 ntdll.dll,第二個連結串列結點就是kernel32.dll可以先看看 InInitializationOrderModuleList 中的內容。

上圖中的 004e3278 儲存的是第一個連結串列節點的指標,通過dd 004e3278解析這個結點,可發現如下地址0x773a0000就是ntdll.dll的基地址,而 004e3b20 則是下一個模組的指標

繼續跟隨 004e3b20 跟進後的76a90000就是kernel32.dll的基地址,而下一個地址的指標則是004e3760以此類推來遍歷。

最後我們通過!peb命令來驗證一下,如下會發現第一個對上了,這裡的kerlel32.dll其實是kernelbase.dll 這個dll是轉向dll中轉到kernel32.dll中,64位系統特有的。

通過上方的除錯我們可得到公式,接著通過編寫一段彙編程式碼來實現自動的遍歷出 kernel32.dl 的基址。

include windows.inc
include kernel32.inc
includelib kerbcli.lib
assume fs:nothing

.code
	main PROC
		xor eax,eax
		xor edx,edx
		mov eax,fs:[30h]           ; 得到PEB結構地址
		mov eax,[eax + 0ch]        ; 得到PEB_LDR_DATA結構地址
		mov esi,[eax + 1ch]        ; 得到 InInitializationOrderModuleList
		lodsd                      ; 得到KERNEL32.DLL所在LDR_MODULE結構的
		mov eax,[eax]              ; Windows 7 以上要將這裡開啟
		mov edx,[eax + 8h]         ; 得到BaseAddress,既Kernel32.dll基址
		ret
	main ENDP
END main

通過使用C語言也可以實現拿到Kernel32的基地址.

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

int main(int argc, char * argv[])
{
	DWORD *PEB = NULL;
	DWORD *Ldr = NULL;
	DWORD *Init = NULL;
	DWORD *Kernel32 = NULL;

	__asm
	{
		mov eax, fs:[0x30]
		mov PEB,eax
	}
	printf("得到PEB指標 = %x \n", PEB);

	Ldr = *(DWORD **)((unsigned char *)PEB + 0x0c);
	printf("得到LDR結構指標 = %x \n", Ldr);

	Init = *(DWORD **)((unsigned char *)Ldr + 0x1c);
	printf("得到InInitializationOrderModuleList結構指標 = %x \n", Init);


	Kernel32 = *(DWORD **)((unsigned char *)Init + 0x08);
	printf("得到Kernel32的基地址 = %x \n", Kernel32);

	system("pause");
	return 0;
}

獲得映象基地址: 我們來擴充套件一個知識點,首先我們這次想要獲得映象基地址,如何解析結構?

首先映象基地址,在PEB結構中,我們先來獲取到其偏移地址。

此時我們知道TEB結構中 指向 PEB,則 0026b000

接著來解析TEB結構,只需要執行 dt _PEB 0026b000 即可得到該地址。

直接彙編實現,也非常簡單,如下。


列舉程序模組

1.我們來拓展一個知識點,通過PEB/TEB找到自身程序的所有載入模組資料,首先獲取 TEB,也就是執行緒環境塊。在程式設計的時候,TEB 始終儲存在暫存器 FS 中。

先來得到LDR結構:Ldr = *( ( DWORD ** )( ( unsigned char * )PEB + 0x0c ) );

先找到TEB

然後再找到PEB結構 偏移為 0x30 從該命令的輸出可以看出,PEB 結構體的地址位於 TEB 結構體偏移0x30 的位置

找到了PEB也就可以找到_PEB_LDR_DATA結構 其位於 PEB 偏移 0c的位置上。

Ldr = *( ( DWORD ** )( ( unsigned char * )PEB + 0x0c ) );

從輸出結果可以看出,LDR 在 PEB 結構體偏移的 0x0C 處,該地址儲存的地址是 0x77bf0c40 通過該地址來解析 LDR 結構體。

WinDBG 輸出如下內容:

Flink = *( ( DWORD ** )( ( unsigned char * )Ldr + 0x14 ) );

位於LDR偏移14的位置就是InLoadOrderModuleList其所指向的就是模組名稱表。

現在來手動遍歷第一條連結串列,輸入命令 0x4e3370

在連結串列偏移 0x18 的位置是模組的對映地址。
即 ImageBase;在連結串列偏移 0x28 的位置是模組的路徑及名稱的地址;
在連結串列偏移 0x30 的位置是模組名稱的地址。

的確是模組的名稱。既然是連結串列,就來下一條連結串列的資訊,004e3268 儲存著下一個連結串列結構。依次遍歷就是了。

我們找到下一個連結串列位置,然後同樣的方法來驗證一下。

沒錯了吧,下一個是 ntdll.dll

上面介紹的結構,是微軟保留結構,只能從網上找到一個結構定義,然後自行看著解析就好了。

typedef struct _LDR_DATA_TABLE_ENTRY {
	PVOID Reserved1[2];
	LIST_ENTRY InMemoryOrderLinks;
	PVOID Reserved2[2];
	PVOID DllBase;
	PVOID EntryPoint;
	PVOID Reserved3;
	UNICODE_STRING FullDllName;
	BYTE Reserved4[8];
	PVOID Reserved5[3];
	union {
	ULONG CheckSum;
	PVOID Reserved6;
	};
	ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

列舉模組的方法就是:得到TEB -> PEB ->LDR ->遍歷 0x18與0x28中的內容即可。


未完待續。。。


獲取指定API地址

222