Window環境下編寫Shellcode(入門篇)


Window環境下編寫Shellcode

一、 什么是shellcode
a) 定義
是一段可注入的指令(opcode),可以在被攻擊的程序內運行。

b) 特點
獨立存在,無需任何文件格式的包裝,因為shellcode直接操作寄存器和函數,所以opcode必須是16進制形式。因此也不能用高級語言編寫shellcode。
在內存中運行,無需運行在固定的宿主進程上。

c) 作用
我們想讓目標程序以不同於設計者預期的方式運行,或者是按照和我們的意圖形式。
d) Shellcode的利用原理
將shellcode注入緩沖區,然后欺騙目標程序執行它。而將shellcode注入緩沖區最常用的方法是利用目標系統上的緩沖區溢出漏洞。
二、 環境的基本搭建
Windows下Visio studio2012 visual c++ 控制台應用程序的設置:
1、 設置入口點,去除自動生成的多余的exe代碼
操作:項目屬性鏈接器高級入口點(可自定義,或加上#pragma comment(linker,"/entry:EntryMain"))
實驗:可在更改配置的前后,將用IDA打開生成的exe文件,
結果: 前=》很多雜七雜八的沒在代碼里調用的函數 后=》只有自己在源代碼中編寫的函數
2、 去除安全檢查
項目->屬性->C/C++->代碼生成->安全檢查
3、 兼容xp平台
設置字符集:未設置
4、 設置運行庫
MT選項:鏈接LIB版的C和C++運行庫。在鏈接時就會在將C和C++運行時庫集成到程序中成為程序中的代碼,程序體積會變大。
MTd選項:LIB的調試版。
MD選項:使用DLL版的C和C++運行庫,這樣在程序運行時會動態的加載對應的DLL,程序體積會減小,缺點是在系統沒有對應DLL時程序無法運行。
MDd選項:表示使用DLL的調試版。
動態版(DLL)和靜態版(LIB)C和C++運行庫的優缺點
動態版(DLL):優點=exe程序小,缺點=沒有對應的dll就無法運行。
靜態版(LIB):缺點=因為靜態版必須把C和C++運行庫復制到目標程序中,所以產生的可執行問會比較大。並且對於大型的項目,在程序運行時會產生對個運行庫,在鏈接時可能會出現重復定義的問題。
5、 關閉生成清單
操作:鏈接器-》清單文件--》關閉
實驗:PEID打開配置前后的exe文件,查看其各個段
結果:前=》只有代碼段 后=》只有數據段
6、 關閉調試信息
鏈接器-》調試器-》FALSE
三、 相關工具
a) Visual studio2012-代碼編寫
b) PEID-PE結構掃描
查看可執行文件的入口點
c) Winhex-16進制查看工具
查找對應位置的字符串,並保存為bin文件
d) IDA-匯編調試工具
e) MSDN Library-庫函數查詢工具
安裝離線庫文檔
可查函數的具體描述,包括調用的系統api
四、 Shellcode編寫原則
a) 避免使用任何全局變量,因為全局變量運行時屬於獨立地內存空間,違背了shellcode使用絕對地址的原則
b) 不能使用static來定義變量,與全局變量的意義類似
c) 確保已加載所需使用的API動態鏈接庫,確保所調用的鏈接庫在內存中,保證正常的跳轉。
五、 第一種shellcode的編寫實例

#include <windows.h>

#pragma comment(linker,"/entry:EntryMain")

/**
例子:message這個彈框文件的存在
*/

int EntryMain()
{
	// MessageBox(NULL,NULL, NULL,NULL);
	//例子2:如何獲取MessageBox的地址,並通過這個返回的地址調用這個函數
	// 方法:通過函數的地址,嵌入匯編->壓棧

	//幾個常用的動態鏈接庫:kernel32.dll user32.dll  gdi32.dll  msvcrt.dll
	LPVOID lp =  GetProcAddress(LoadLibraryA("user32.dll"),"MessageBoxA");
	char * pdata = "hello word";
	_asm
	{
		push 0
		push 0
		push pdata
		push 0   // 壓入4個參數:相當於例子1中的4個為NULL的參數
		call lp  //調用改地址
	} //能夠實現直接調用MessageBox的例子
	return 0;
}

六、 函數生成的位置規律
a) 單文件函數生成的位置規律
1、規律:單文件函數的生成規律,與函數實現的先后順序有關,而與函數的定義順序無關
2、例子:

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

int FuncA(int a ,int b);
int FuncB(int a ,int b);
int main()
{
	DWORD dwSize = (DWORD)FuncB - (DWORD)FuncA;
	return 0;
}
int FuncB(int a ,int b)
{
	puts("FuncB...");
	return a+b;
}
int FuncA(int a ,int b)
{
	puts("FuncA...");
	return a+b;
}

3、結果

b) 多文件函數生成的位置規律
1、 規律:與包含文件的位置無關,與實際調用的順序有關
2、 例子:
文件目錄:
|-頭文件:頭文件夾
|-A.h
|-B.h
|-源文件:源文件夾
|-main,cpp

/********A.h*****************/
#include <stdio.h>
void A()
{
	puts("AAA");
}
/********B.h*****************/
#include <stdio.h>

void B()
{
	puts("BBB");
}
/*********main.cpp*********/
#include "B.h"
#include "A.h"
#include <stdio.h>

int main()
{	
	A();
	B();
	return 0;
}

3、 結果

七、 第二種shellcode的編寫實例
a) Shellcode編寫的基本框架
編寫shellcode的基本邏輯框架:先用調用標准庫中的各種函數實現shellcode的工程,再深入所調用的函數,對其進行通過基地址函數的調用改寫。
以下代碼由【第一種shellcode的實現】改寫而來,仔細對比,發現其中的規律。

#include <stdio.h>
#include <windows.h>
#pragma comment(linker, "/entry:EntryMain")

//獲取kernel32動態鏈接庫的基地址

__declspec(naked) DWORD getKernel32()
{
	_asm
	{
		mov eax,fs:[30h]
		mov eax,[eax+0ch]
		mov eax,[eax+14h]
		mov eax,[eax]
		mov eax,[eax]
		mov eax,[eax+10h]
		ret
	}
}

FARPROC getProcAddress(HMODULE hMoudleBase)
{
	PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hMoudleBase;
	PIMAGE_NT_HEADERS32 lpNtHeader = (PIMAGE_NT_HEADERS)((DWORD)hMoudleBase+lpDosHeader->e_lfanew);
	if(!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size)
	{
		return NULL;
	}

	if(!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
	{
		return NULL;
	}

	PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hMoudleBase+(DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
	PDWORD lpdwFunName = (PDWORD)((DWORD)hMoudleBase +(DWORD)lpExports->AddressOfNames);
	PWORD lpwOrd = (PWORD)((DWORD)hMoudleBase +(DWORD)lpExports->AddressOfFunctions);
	PDWORD lpdwFunAddr = (PDWORD)((DWORD)hMoudleBase +(DWORD)lpExports->AddressOfFunctions);

	DWORD dwLoop = 0;
	FARPROC pRet = NULL;
	for(;dwLoop<=lpExports->NumberOfNames-1; dwLoop++)
	{
		char* pFunName = (char*)(lpdwFunName[dwLoop]+(DWORD)hMoudleBase);
		if (pFunName[0] == 'G' &&
			pFunName[1] == 'e' &&
			pFunName[2] == 't' &&
			pFunName[3] == 'P' &&
			pFunName[4] == 'r' &&
			pFunName[5] == 'o' &&
			pFunName[6] == 'c' &&
			pFunName[7] == 'A' &&
			pFunName[8] == 'd' &&
			pFunName[9] == 'd' &&
			pFunName[10] == 'r' &&
			pFunName[11] == 'e' &&
			pFunName[12] == 's' &&
			pFunName[13] == 's' 
			)
		{
			pRet=(FARPROC)(lpdwFunAddr[lpwOrd[dwLoop]]+(DWORD)hMoudleBase);
			break;
		}
	}
	return pRet;
}

int EntryMain()
{
	//CreateFileA("1.txt",GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL);

	typedef FARPROC(WINAPI* FN_GetProcAddress)
	(
		_In_ HMODULE hModule,
		_In_ LPCSTR lpProcName
    );
	FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());//得到GetProAddress的真實地址

	typedef HANDLE(WINAPI* FN_CreateFileA)(
		_In_ LPCSTR lpFileName,
		_In_ DWORD dwDesiredAccess,
		_In_ DWORD dwShareMode,
		_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
		_In_ DWORD dwCreationDisposition,
		_In_ DWORD dwFlagsAndAttributes,
		_In_opt_ HANDLE hTemplateFile
    );

	char sFile[] = {'C','r','e','a','t','e','F','i','l','e','A',0};
	FN_CreateFileA fn_CreateFileA = (FN_CreateFileA)fn_GetProcAddress((HMODULE)getKernel32(),sFile);
	char sNewFile[] = {'1','.','t','x','t','\0'};
	fn_CreateFileA(sNewFile,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL);
	//MessageBoxA(NULL,"HelloWord!","tip",MB_OK);
	return 0;
}

b) Shellcode編寫框架的優化
1、 文件目錄及說明
|-頭文件:頭文件夾
|--------api.h :文件中所調用的api,在此定義,放入一個結構體中
|--------header.h : 文件中所有的定義都放在這里,便於統一的管理
|-源文件:源文件目錄

-------- 0.entry.cpp :入口文件,提取shellcode,提取a.start.cpp和z.end.cpp中間的代碼。

----- a.start.cpp : shellcode的開始位置,跳轉到shellcode的入口位置,並調用執行shellcode的功能
|--------b.word.cpp :這是中間文件,不會影響shellcode提取
|-------- z.end.cpp : shellcode的結束位置
2、 例子

#pragma once

#include <windows.h>

typedef FARPROC(WINAPI *FN_GetProcAddress)(
    _In_ HMODULE hModule,
    _In_ LPCSTR lpProcName
    );

typedef HMODULE(WINAPI *FN_LoadLibraryA)(
    _In_ LPCSTR lpLibFileName
    );

typedef int (WINAPI *Fn_MessageBoxA)(
    _In_opt_ HWND hWnd,
    _In_opt_ LPCSTR lpText,
    _In_opt_ LPCSTR lpCaption,
    _In_ UINT uType);

typedef HANDLE(WINAPI  *FN_CreateFileA)(
    _In_ LPCSTR lpFileName,
    _In_ DWORD dwDesiredAccess,
    _In_ DWORD dwShareMode,
    _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    _In_ DWORD dwCreationDisposition,
    _In_ DWORD dwFlagsAndAttributes,
    _In_opt_ HANDLE hTemplateFile
    );

typedef struct _FUNCTIONS
{
	FN_GetProcAddress fn_GetProcAddress;
	FN_LoadLibraryA fn_LoadLibraryA;
	Fn_MessageBoxA fn_MessageBoxA;
	FN_CreateFileA fn_CreateFileA;

}FUNCTIONS,*PFUNCTIONS;
#pragma once

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

void ShellcodeStart();
void ShellcodeEntry();
void ShellcodeEnd();

void CreateShellcode();

void InitFunctions(PFUNCTIONS pFn);

void CreateConfigFile(PFUNCTIONS pFn);
void ShowMessage(PFUNCTIONS pFn);

#include "header.h"

#pragma comment(linker,"/entry:EntryMain")


int EntryMain()
{
	CreateShellcode();
	return 0;
}
void CreateShellcode()
{
	HMODULE hMsvcrt=LoadLibraryA("msvcrt.dll");
	typedef int( __cdecl *fn_printf)(_In_z_ _Printf_format_string_ const char * _Format, ...);
	fn_printf xprintf=(fn_printf)GetProcAddress(hMsvcrt,"printf");
	xprintf("1");

	HANDLE hBin=CreateFileA("sh.bin",GENERIC_ALL,0,NULL,CREATE_ALWAYS,0,NULL);

	if(hBin ==INVALID_HANDLE_VALUE)
	{ 
		xprintf("create file error:%d\n",GetLastError());
		return ;
	}

	DWORD dwSize=(DWORD)ShellcodeEnd-(DWORD)ShellcodeStart;//計算程序ShellcodeEnd到ShellcodeStart之間的字節長度

	DWORD dwWriten;

	WriteFile(hBin,ShellcodeStart,dwSize,&dwWriten,NULL);//將這段程序寫入sh.bin文件中。

	CloseHandle(hBin);
	//ShellcodeEntry();
	 
}
#include "api.h"
#include "header.h"



_declspec(naked) void ShellcodeStart()
{
	_asm
	{
		jmp ShellcodeEntry
	}
}
//獲取 kernel32庫地址
_declspec(naked) DWORD getKernel32()//獲取Kernel32地址指針
{
	_asm
	{
		mov eax,fs:[30h]
		mov eax,[eax+0ch]
		mov eax,[eax+14h]
		mov eax,[eax]
		mov eax,[eax]
		mov eax,[eax+10h]
		ret
	}

}


//獲取GetProcAddress 方法地址
FARPROC getProcAddress(HMODULE hModuleBase)
{
	PIMAGE_DOS_HEADER lpDosHeader=(PIMAGE_DOS_HEADER)hModuleBase;
	PIMAGE_NT_HEADERS32 lpNtHeader=(PIMAGE_NT_HEADERS)((DWORD)hModuleBase+lpDosHeader->e_lfanew);
	if(!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size)
	{
		return NULL;
	}

	if(!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
	{
		return NULL;
	}

	PIMAGE_EXPORT_DIRECTORY lpExports=(PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase+(DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
	PDWORD lpdwFunName=(PDWORD)((DWORD)hModuleBase+(DWORD)lpExports->AddressOfNames);
	PWORD lpwOrd=(PWORD)((DWORD)hModuleBase+(DWORD)lpExports->AddressOfNameOrdinals);
	PDWORD lpdwFunAddr=(PDWORD)((DWORD)hModuleBase+(DWORD)lpExports->AddressOfFunctions);
	DWORD dwLoop=0;
	FARPROC pRet=NULL;

	for(;dwLoop<=lpExports->NumberOfNames-1;dwLoop++)
	{
		char*pFunName=(char*)(lpdwFunName[dwLoop]+(DWORD)hModuleBase);
		if(pFunName[0]=='G'&&
			pFunName[1]=='e'&&
			pFunName[2]=='t'&&
			pFunName[3]=='P'&&
			pFunName[4]=='r'&&
			pFunName[5]=='o'&&
			pFunName[6]=='c'&&
			pFunName[7]=='A'&&
			pFunName[8]=='d'&&
			pFunName[9]=='d'&&
			pFunName[10]=='r'&&
			pFunName[11]=='e'&&
			pFunName[12]=='s'&&
			pFunName[13]=='s')
		{
			pRet=(FARPROC)(lpdwFunAddr[lpwOrd[dwLoop]]+(DWORD)hModuleBase);
			break;
		}
	}
	return pRet;
}

void InitFunctions(PFUNCTIONS pFn)
{
	pFn->fn_GetProcAddress=(FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());
	char szLoadLibraryA[]={'L','o','a','d','L','i','b','r','a','r','y','A',0}; 
	pFn->fn_LoadLibraryA=(FN_LoadLibraryA)pFn->fn_GetProcAddress((HMODULE)getKernel32(),szLoadLibraryA);//通過GetProcAddress獲取 LoadLibraryA地址

	char szUser32[]={'U','s','e','r','3','2','.','d','l','l',0};
	char szMessageBoxA[]={'M','e','s','s','a','g','e','B','o','x','A',0};
	pFn->fn_MessageBoxA=(Fn_MessageBoxA)pFn->fn_GetProcAddress(pFn->fn_LoadLibraryA(szUser32),szMessageBoxA);//通過LoadLibraryA加載User32.dll后獲取MessageBoxA方法地址

	char szCreateFileA[]={'C','r','e','a','t','e','F','i','l','e','A',0};
	pFn->fn_CreateFileA=(FN_CreateFileA)pFn->fn_GetProcAddress((HMODULE)getKernel32(),szCreateFileA);//獲取CreateFileA方法地址
}

void ShellcodeEntry()
{ 
	FUNCTIONS fn;
	InitFunctions(&fn); 
	ShowMessage(&fn);
	CreateConfigFile(&fn);
}

#include "api.h"
#include "header.h"

void CreateConfigFile(PFUNCTIONS pFn)
{
	 char fileName[]={'1','.','t','x','t',0};
	 pFn->fn_CreateFileA(fileName,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL);
}

void ShowMessage(PFUNCTIONS pFn)
{
	char szHello[]={'H','e','l','l','o',' ','w','o','r','l','l','d',0};
	char szTip[]={'t','i','p',0};
	pFn->fn_MessageBoxA(NULL,szHello,szTip,MB_OK); 
}

#include "header.h"

void ShellcodeEnd()
{

}

八、 編寫shellcode加載器
作用:實現兩種方式的shellcode的執行
Bin-》exe
Exe-》shellcode
基本操作:
1、 打開文件
2、 讀取文件大小
3、 申請與文件大小相等的內存空間
4、 將文件讀入3申請的空間
5、 調用shellcode

例子:

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

//ShellCode加載器

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("pasue");

	return 0;
}


免責聲明!

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



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