32位程序下调用64位函数——进程32位模式与64位模式切换
之前学习的32位进程中调用64位进程函数的知识整理一下,也就是32位模式与64位模式之间的切换。
相关博客:http://www.cnblogs.com/lanrenxinxin/p/4821152.html
这个博客中提到了github上的开源库,我在另一份开源项目中也看到了个库,可以切换32位至64位。
如果对这个功能具体实现比较感兴趣的朋友可以看看下面的内容。
我阅读了源码并进行了注释,算是对这个具体方法的分析和学习。
关键:
1.在x64下的进程,不管是32位或者是64位,实际上都映射了两个地址空间,一个是32位,一个是64位。相当于一个进程的两种工作模式,而且这两种工作模式是可以进行切换的,他们之间关键的区别在与cs寄存器 64位: CS = 0x33 ; 32位:CS = 0x23
2.Wow64进程中的r12寄存器指向64位的TEB结构(TEB64);
3.每个32位进程都会加载ntdll32.dll和ntdll.dll模块。其中ntdll.dll是64位模块,我们可以将进程的32位模式改为64位模式,然后再去操作64位进程。
注意:
1.只能实现ntdll下函数模式切换
2.只有64位进程内部包含32位,32位不包含64位
测试在32模式下获得64位函数的地址:
int main() { HMODULE NtdllModuleBase32 = NULL; NtdllModuleBase32 = GetModuleHandle(L"Ntdll.dll"); PVOID v1 = GetProcAddress(NtdllModuleBase32, "NtQuerySystemInformation"); //v1 = 0x77478e60; //v1 = 0x00007ffd30223b80 printf("Ntdll.dll 32bit address:0x%08x\r\n", NtdllModuleBase32); printf("NtQuerySystemInformation 32bit address:0x%08x\r\n", v1); DWORD64 NtdllModuleBase64 = 0; NtdllModuleBase64 = GetModuleHandle64(L"ntdll.dll"); DWORD64 v2 = GetProcAddress64(NtdllModuleBase64, "NtQuerySystemInformation"); printf("Ntdll.dll 32bit address:0x%016llx\r\n", NtdllModuleBase64); printf("NtQuerySystemInformation 64bit address:0x%016llx\r\n", v2); //64 32 ---64 32 return 0; }
DWORD64 GetModuleHandle64(wchar_t* ModuleName) { //定义32位和64位Teb结构 TEB64 Teb; //将得到的teb赋值给结构体 GetMemoy64(&Teb, GetTeb64(), sizeof(TEB64)); PEB64 Peb; GetMemoy64(&Peb, Teb.ProcessEnvironmentBlock, sizeof(PEB64)); 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); return 0; } //获得Teb DWORD64 GetTeb64() { //定义一个寄存器 Register64 v1; v1.dw64 = 0; #ifdef _M_IX86 //开始切换 X64_Start(); // R12 register should always contain pointer to TEB64 in WoW64 processes // R12寄存器指向是64位TEB 将R12值压栈 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] X64_End(); //切换回32位 #endif //返回TEB return v1.dw64; } //32位下执行64位汇编实现字符串copy void GetMemoy64(void* DestinationMemory, DWORD64 SourceMemory, size_t SourceMemoryLength) { if ((NULL == DestinationMemory) || (0 == SourceMemory) || (0 == SourceMemoryLength)) return; Register64 v1 = { SourceMemory }; #ifdef _M_IX86 __asm { X64_Start(); ;// below code is compiled as x86 inline asm, but it is executed as x64 code ;// that's why it need sometimes REX_W() macro, right column contains detailed ;// transcription how it will be interpreted by CPU push edi;// push rdi push esi;// push rsi mov edi, DestinationMemory; // mov edi, dword ptr [dstMem] ; high part of RDI is zeroed REX_W mov esi, v1.dw[0]; // mov rsi, qword ptr [_src] REX_W 自减 mov ecx, SourceMemoryLength; // mov ecx, dword ptr [sz] ; high part of RCX is zeroed mov eax, ecx; // mov eax, ecx and eax, 3; // and eax, 3 shr ecx, 2; // shr ecx, 2 rep movsd; // rep movs dword ptr [rdi], dword ptr [rsi] test eax, eax; // test eax, eax je _move_0; // je _move_0 cmp eax,1; // cmp eax, 1 je _move_1; // je _move_1 movsw // movs word ptr [rdi], word ptr [rsi] cmp eax, 2; // cmp eax, 2 je _move_0; // je _move_0 _move_1: movsb // movs byte ptr [rdi], byte ptr [rsi] _move_0: pop esi;// pop rsi pop edi;// pop rdi X64_End(); } #endif } //32位下执行64位汇编实现字符串比较 BOOL CompareMemory64(void* DestinationMemory, DWORD64 SourceMemory, size_t SourceMemoryLength) { if ((NULL == DestinationMemory) || (0 == SourceMemory) || (0 == SourceMemoryLength)) return FALSE; bool IsOk = FALSE; Register64 v1 = { SourceMemory }; #ifdef _M_IX86 __asm { X64_Start(); ;// below code is compiled as x86 inline asm, but it is executed as x64 code ;// that's why it need sometimes REX_W() macro, right column contains detailed ;// transcription how it will be interpreted by CPU push edi;// push rdi push esi;// push rsi mov edi, DestinationMemory; // mov edi, dword ptr [dstMem] ; high part of RDI is zeroed REX_W mov esi, v1.dw[0]; // mov rsi, qword ptr [_src] mov ecx, SourceMemoryLength; // mov ecx, dword ptr [sz] ; high part of RCX is zeroed mov eax, ecx; // mov eax, ecx and eax, 3;// and eax, 3 shr ecx, 2;// shr ecx, 2 repe cmpsd;// repe cmps dword ptr [rsi], dword ptr [rdi] jnz _ret_false; // jnz _ret_false test eax, eax; // test eax, eax je _move_0; // je _move_0 cmp eax, 1; // cmp eax, 1 je _move_1; // je _move_1 cmpsw; // cmps word ptr [rsi], word ptr [rdi] jnz _ret_false; // jnz _ret_false cmp eax, 2; // cmp eax, 2 je _move_0; // je _move_0 _move_1: cmpsb; // cmps byte ptr [rsi], byte ptr [rdi] jnz _ret_false; // jnz _ret_false _move_0: mov IsOk, 1; // mov byte ptr [result], 1 _ret_false: pop esi; // pop rsi pop edi; // pop rdi X64_End(); } #endif return IsOk; } 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; } DWORD64 GetProcAddress64(DWORD64 ModuleBase, char* FunctionName) { //GetProcAddress() //得到函数地址 if (0 == __LdrGetProcedureAddress) { __LdrGetProcedureAddress = GetFunctionAddressFromExportTable64(L"Ntdll.dll", "LdrGetProcedureAddress"); // //v1 = 0x00007ffd30193560 if (0 == __LdrGetProcedureAddress) return 0; } _UNICODE_STRING_T<DWORD64> v1 = { 0 }; v1.Buffer = (DWORD64)FunctionName; v1.Length = (WORD)strlen(FunctionName); // v1.MaximumLength = v1.Length + 1; DWORD64 FunctionAddress = 0; //根据函数在导出表中的地址 得到函数呼叫地址 X64Call(__LdrGetProcedureAddress, 4, (DWORD64)ModuleBase, (DWORD64)&v1, (DWORD64)0, (DWORD64)&FunctionAddress); return FunctionAddress; } DWORD64 X64Call(DWORD64 FunctionAddresss, int ParameterCount, ...) { //64 rcx rdx r8 r9 [][][][][][] //四寄存器赋值 va_list v1; va_start(v1, ParameterCount); Register64 _rcx = { (ParameterCount > 0) ? ParameterCount--, va_arg(v1, DWORD64) : 0 }; Register64 _rdx = { (ParameterCount > 0) ? ParameterCount--, va_arg(v1, DWORD64) : 0 }; Register64 _r8 = { (ParameterCount > 0) ? ParameterCount--, va_arg(v1, DWORD64) : 0 }; Register64 _r9 = { (ParameterCount > 0) ? ParameterCount--, va_arg(v1, DWORD64) : 0 }; Register64 _rax = { 0 }; //剩余参数 Register64 restArgs = { (DWORD64)&va_arg(v1, DWORD64) }; // conversion to QWORD for easier use in inline assembly #ifdef _M_IX86 Register64 _argC = { (DWORD64)ParameterCount }; //剩余参数数量 DWORD back_esp = 0; WORD back_fs = 0; __asm { // reset FS segment, to properly handle RFG //重置fs段寄存器 mov back_fs, fs mov eax, 0x2B mov fs, ax // keep original esp in back_esp variable // 保存esp mov back_esp, esp // align esp to 0x10, without aligned stack some syscalls may return errors ! // (actually, for syscalls it is sufficient to align to 8, but SSE opcodes // requires 0x10 alignment), it will be further adjusted according to the // number of arguments above 4 //esp地址对齐 and esp, 0xFFFFFFF0 //准备ok 开始切换 X64_Start(); // below code is compiled as x86 inline asm, but it is executed as x64 code // that's why it need sometimes REX_W() macro, right column contains detailed // transcription how it will be interpreted by CPU // fill first four arguments //压四个寄存器 REX_W mov ecx, _rcx.dw[0];// mov rcx, qword ptr [_rcx] REX_W mov edx, _rdx.dw[0];// mov rdx, qword ptr [_rdx] push _r8.dw64;// push qword ptr [_r8] X64_Pop(_R8); ;// pop r8 push _r9.dw64;// push qword ptr [_r9] X64_Pop(_R9); ;// pop r9 //剩余寄存器数量 REX_W mov eax, _argC.dw[0];// mov rax, qword ptr [_argC] // final stack adjustment, according to the // number of arguments above 4 //剩余参数压栈 push test al, 1; // test al, 1 jnz no_adjust; // jnz _no_adjust sub esp, 8; // sub rsp, 8 no_adjust: push edi; // push rdi REX_W mov edi, restArgs.dw[0]; // mov rdi, qword ptr [restArgs] // put rest of arguments on the stack REX_W test eax, eax;// test rax, rax jz _ls_e; // je _ls_e REX_W lea edi, dword ptr[edi + 8 * eax - 8];// lea rdi, [rdi + rax*8 - 8] _ls: REX_W test eax, eax; // test rax, rax jz _ls_e; // je _ls_e push dword ptr[edi]; // push qword ptr [rdi] REX_W sub edi, 8; // sub rdi, 8 REX_W sub eax, 1; // sub rax, 1 jmp _ls; // jmp _ls _ls_e: // create stack space for spilling registers REX_W sub esp, 0x20; // sub rsp, 20h call FunctionAddresss; // call qword ptr [func] // cleanup stack REX_W mov ecx, _argC.dw[0]; // mov rcx, qword ptr [_argC] REX_W lea esp, dword ptr[esp + 8 * ecx + 0x20]; // lea rsp, [rsp + rcx*8 + 20h] pop edi; // pop rdi // set return value REX_W mov _rax.dw[0], eax;// mov qword ptr [_rax], rax X64_End(); mov ax, ds mov ss, ax mov esp, back_esp // restore FS segment mov ax, back_fs mov fs, ax } #endif // _M_IX86 return _rax.dw64; }