總結一下以前學習過的在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 }
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; }