32位程序中獲取64位函數地址

  總結一下以前學習過的在blackbone中的一個x86程序中獲取x64位進程函數地址的方法,以前就已經系統地梳理了一遍,今天貼出來分享一下。數組

  這個程序的目的說白了就是要讓讓運行在Wow64環境中的x86應用程序能夠獲取到x64下ntdll.dll中的Native API的地址,從而可以直接調用x64下ntdll.dll中的Native API。ide

 

0x01  理論基礎:函數

  在分析源碼以前,先講幾個相關的知識點:源碼分析

  1.64位計算機系統下的進程,不管是是32 位或者是 64 位,都映射了兩個地址空間,一個 是 32 位,一個是 64 位。因此 32 位和 64 位,能夠理解爲一個進程的兩種工做模式,而且這 兩種工做模式能夠切換。 學習

  2.64位計算機系統下的進程,每一個32位進程都會加劇ntdll32.dll和ntdll.dll模塊。其中ntdll.dll是64位模塊。咱們能夠將進程的32位模式切換到64位,獲取64位的ntdll中的導出函數來使用,這樣就可以操做64位的進程。spa

 

  功能實現的邏輯:指針

  1. 進程由 32 位變成 64 位 CS: 0x23 -- > 0x32 具體代碼以下:利用 retf,把堆棧上的值寫進 cs 寄存器,同時保證 ip 的正確性。code

  2. 獲取 64 位模式下的 TEB (r12寄存器指向64位的TEB結構(TEB64))blog

  3. 從 64 位 TEB 獲取到 64 位 Ntdll.dll 的地址 TEB->PEB->LDR LDR 匹配的 Ntdll.dll,找到基址索引

  4. 找到須要調用的 Ntdll.dll 的函數 找到 Ntdll.dll 地址,分析 PE 結構,能夠找到函數的入口地址。

  5. 調用函數 x64 的調用規則,前 4 個參數依次是 rcx, rdx, r8, r9,更多的參數由堆棧傳遞。 X64Call 這個函數封了 32 位模式下對 64 位函數的調用。

 

 

0x02  源碼分析

   1.32位程序切換到64位模式實現

 

#define X64_Start() X64_Start_with_CS(0x33)

//switch(x64)
//經過retf將0x33賦值給cs寄存器

#define X64_Start_with_CS(_cs) \
    { \
    EMIT(0x6A) EMIT(_cs)                         /*  push   _cs             */ \
    EMIT(0xE8) EMIT(0) EMIT(0) EMIT(0) EMIT(0)   /*  call   $+5             */ \
    EMIT(0x83) EMIT(4) EMIT(0x24) EMIT(5)        /*  add    dword [esp], 5  */ \
	EMIT(0xCB)                                   /*  retf                   */ \
    }

  windbg中對應的反彙編代碼:

00ad2461 6a33            push    33h
00ad2463 e800000000      call    Demo+0x12468 (00ad2468)
00ad2468 83042405        add     dword ptr [esp],5
00ad246c cb              retf

  經過windbg中對應的反彙編代碼能夠看出操做就是將0x33壓棧 原地call將下一條指令地址壓棧,再將esp中保存的地址的值加5,那麼此時在esp棧頂的地址值將成爲retf指令以後的地址值,從而確保了retf以後指令指針寄存器ip的正確性,再retf會pop eip,再pop cs,修改段選擇子,這樣CS寄存器中本來的值0x23就變成了0x33,同時保證了指令指針寄存器中的eip的正確性。

  話很少說,直接看windbg中esp棧頂地址最直白:

  push    33h 以後的棧頂地址內容:(0x33被壓入棧頂)

 

 00ad2463 e800000000      call    Demo+0x12468 (00ad2468) 以後的棧頂內容(call指令的下一跳指令的地址00ad2468被壓入棧頂)

 

00ad2468 83042405        add     dword ptr [esp],5  以後的棧頂內容(棧頂保存的地址加了5個字節大小,其實也就是到了retf指令以後的地址,這裏的00ad246d將成爲rip寄存器中的值)

 

  最終retf指令執行事後,將pop eip,再pop cs,即將ad246d的值賦給eip寄存器,將0x33賦值給cs寄存器。

  如圖,retf前的cs寄存器中的內容爲0x23:

  

  retf後~化腐朽爲神奇了!

  

  當前rip寄存器中的ad246d正是以前esp棧頂中的值,cs寄存器中的值被修改成0x33了,頓時windbg的整個畫風都變了。此時就成功切換到了64位模式下啦。

 

 

  2.獲取32位進程64位模式下的TEB地址

   

 

union Register64
{
DWORD64 dw64;
DWORD dw[2];
};

//定義一個寄存器
Register64 v1;
v1.dw64 = 0;

X64_Push(_R12);
	// below pop will pop QWORD from stack, as we're in x64 mode now
	//將R12pop給v1 TEB在其中
	__asm pop v1.dw[0]  

    #define X64_Push(Value) EMIT(0x48 | ((Value) >> 3)) EMIT(0x50 | ((Value) & 7))

  直接上windbg中對應的彙編指令:

 

  push r12這條指令,由於不能使用嵌入式彙編表示64寄存器,因此採用機器碼書寫,隨後再將r12寄存器中的值保存到咱們的局部變量中,成功取得了TEB的地址(r12寄存器指向64位的TEB結構(TEB64)),這時候再切換回32位模式,將TEB中的內容賦值給咱們自定義的結構體:

  

template <class T>
struct _TEB_T_
{
	_NT_TIB_T<T> NtTib;
	T EnvironmentPointer;
	_CLIENT_ID<T> ClientID;
	T ActiveRpcHandle;
	T ThreadLocalStoragePointer;
	T ProcessEnvironmentBlock;
	DWORD LastErrorValue;
	DWORD CountOfOwnedCriticalSections;
	T CsrClientThread;
	T Win32ThreadInfo;
	DWORD User32Reserved[26];
};

typedef _TEB_T_<DWORD> TEB32;
typedef _TEB_T_<DWORD64> TEB64;

  

 

  3.獲取PEB地址

   這裏咱們經過自定義的TEB結構體和以前獲取的TEB結構體來獲得PEB的基地址

   在/32位下切入64位模式執行64位彙編實現字符串copy:

 1     PEB64 Peb;
 2     GetMemoy64(&Peb, Teb.ProcessEnvironmentBlock, sizeof(PEB64));
 3 
 4         //32位下執行64位彙編實現字符串copy
 5 void GetMemoy64(void* DestinationMemory, DWORD64 SourceMemory, size_t SourceMemoryLength)
 6 {
 7     if ((NULL == DestinationMemory) || (0 == SourceMemory) || (0 == SourceMemoryLength))
 8         return;
 9 
10     Register64 v1 = { SourceMemory };
11 #ifdef _M_IX86
12     __asm
13     {
14         X64_Start();
15 
16         ;// below code is compiled as x86 inline asm, but it is executed as x64 code
17         ;// that's why it need sometimes REX_W() macro, right column contains detailed
18         ;// transcription how it will be interpreted by CPU
19 
20         push   edi;// push     rdi
21         push   esi;// push     rsi
22         mov    edi, DestinationMemory;        // mov      edi, dword ptr [dstMem]        ; high part of RDI is zeroed
23   REX_W mov    esi, v1.dw[0];                 // mov      rsi, qword ptr [_src]    REX_W 自減
24         mov    ecx, SourceMemoryLength;       // mov      ecx, dword ptr [sz]            ; high part of RCX is zeroed
25         
26         mov    eax, ecx;       // mov      eax, ecx
27         and    eax, 3;         // and      eax, 3
28         shr    ecx, 2;         // shr      ecx, 2
29 
30         rep    movsd;          // rep movs dword ptr [rdi], dword ptr [rsi]
31     
32         test   eax, eax;       // test     eax, eax
33         je     _move_0;        // je       _move_0
34         cmp    eax,1;          // cmp      eax, 1
35         je     _move_1;        // je       _move_1
36         movsw                  // movs     word ptr [rdi], word ptr [rsi]
37         cmp    eax, 2;         // cmp      eax, 2
38         je     _move_0;        // je       _move_0
39 _move_1:
40         movsb                  // movs     byte ptr [rdi], byte ptr [rsi]
41 
42 _move_0:
43         pop    esi;// pop      rsi
44         pop    edi;// pop      rdi
45 
46         X64_End();
47     }
48 #endif
49 }
Teb.ProcessEnvironmentBlock

 

 

  4.經過PEB獲得PEB_LDR_DATA64的地址,在經過遍歷PEB_LDR_DATA64結構中的鏈表,找到ntdll.dll的基地址。回憶一下這常常用到的LDR結構和它使人心動的三條LIST_ENTRY鏈表~~~

typedef struct _PEB_LDR_DATA  
{  
 ULONG Length; 
 BOOLEAN Initialized;
 PVOID SsHandle;
 LIST_ENTRY InLoadOrderModuleList; 
 LIST_ENTRY InMemoryOrderModuleList; 
 LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA,*PPEB_LDR_DATA;



	PEB_LDR_DATA64 PebLdrData;
	GetMemoy64(&PebLdrData, Peb.Ldr, sizeof(PEB_LDR_DATA64));

    

DWORD64 LastEntry = Peb.Ldr + offsetof(PEB_LDR_DATA64, InLoadOrderModuleList);
LDR_DATA_TABLE_ENTRY64 LdrDataTableEntry;

LdrDataTableEntry.InLoadOrderLinks.Flink = PebLdrData.InLoadOrderModuleList.Flink;
do
{
//遍歷鏈表
GetMemoy64(&LdrDataTableEntry, LdrDataTableEntry.InLoadOrderLinks.Flink, sizeof(LDR_DATA_TABLE_ENTRY64));

wchar_t BaseDllName[MAX_PATH] = { 0 };
//獲得模塊名
GetMemoy64(BaseDllName, LdrDataTableEntry.BaseDllName.Buffer, LdrDataTableEntry.BaseDllName.MaximumLength);

if (0 == _wcsicmp(ModuleName, BaseDllName))
return LdrDataTableEntry.DllBase;
} while (LdrDataTableEntry.InLoadOrderLinks.Flink != LastEntry);

  

 

  到目前爲止,咱們已經成功get了64位模式下的ntdll.dll的基地址了。

 

 

  5.解析PE結構導出表,獲得對應的函數地址。

DWORD64 GetFunctionAddressFromExportTable64(WCHAR* ModuleName,char* FunctionName)
{
	DWORD* AddressOfFunctions = 0;
	WORD*  AddressOfNameOrdinals = 0;
	DWORD* AddressOfNames = 0;
	DWORD64 ModuleBase = GetModuleHandle64(ModuleName);
	if (0 == ModuleBase)
		return 0;
	__try
	{
		IMAGE_DOS_HEADER ImageDosHeader;
		GetMemoy64(&ImageDosHeader, ModuleBase, sizeof(IMAGE_DOS_HEADER));

		IMAGE_NT_HEADERS64 ImageNtHeaders;
		GetMemoy64(&ImageNtHeaders, ModuleBase + ImageDosHeader.e_lfanew, sizeof(IMAGE_NT_HEADERS64));

		IMAGE_DATA_DIRECTORY& ImageDataDirectory =
			ImageNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];

		if (0 == ImageDataDirectory.VirtualAddress)
			return 0;

		IMAGE_EXPORT_DIRECTORY ImageExportDirectory;

		GetMemoy64(&ImageExportDirectory, ModuleBase + ImageDataDirectory.VirtualAddress, sizeof(IMAGE_EXPORT_DIRECTORY));

		AddressOfFunctions = (DWORD*)malloc(sizeof(DWORD)*ImageExportDirectory.NumberOfFunctions);
		if (NULL == AddressOfFunctions)
		{
			return 0;
		}
		//獲得函數地址數組
		GetMemoy64(AddressOfFunctions, ModuleBase + ImageExportDirectory.AddressOfFunctions, sizeof(DWORD)*ImageExportDirectory.NumberOfFunctions);

		 AddressOfNameOrdinals = (WORD*)malloc(sizeof(WORD)*ImageExportDirectory.NumberOfFunctions);
		if (NULL == AddressOfNameOrdinals)
		{
			return 0;
		}
		//獲得索引數組
		GetMemoy64(AddressOfNameOrdinals, ModuleBase + ImageExportDirectory.AddressOfNameOrdinals, sizeof(WORD)*ImageExportDirectory.NumberOfFunctions);

		AddressOfNames = (DWORD*)malloc(sizeof(DWORD)*ImageExportDirectory.NumberOfNames);
		if (nullptr == AddressOfNames)
		{
			return 0;
		}
		//根據函數名獲得函數索引
		GetMemoy64(AddressOfNames, ModuleBase + ImageExportDirectory.AddressOfNames, sizeof(DWORD)*ImageExportDirectory.NumberOfNames);
		for (DWORD i = 0; i < ImageExportDirectory.NumberOfFunctions; i++)
		{
			if (!CompareMemory64(FunctionName, ModuleBase + AddressOfNames[i],
				strlen(FunctionName) + 1))
				continue;
			else
				//根據索引獲得函數相對地址 基地址+相對地址=函數絕對地址
				return ModuleBase + AddressOfFunctions[AddressOfNameOrdinals[i]];
		}
	}
	__finally
	{
		if (AddressOfFunctions != NULL)
		{
			free(AddressOfFunctions);
			AddressOfFunctions = NULL;

		}
		if (AddressOfNameOrdinals != NULL)
		{
			free(AddressOfNameOrdinals);
			AddressOfNameOrdinals = NULL;
		}
		if (AddressOfNames != NULL)
		{
			free(AddressOfNames);
			AddressOfNames = NULL;
		}
	}

	return 0;
}
相關文章
相關標籤/搜索