@zoand
2017-04-24T09:45:46.000000Z
字数 6808
阅读 2650
amd64驱动
英文原文:http://blog.rewolf.pl/blog/?p=102
前几个月,我做了一些关于32位进程在WOW64里执行原生x64代码的探索。第二个想法就是在64位进程里运行原生x86代码。以上两个想法都是可能的,且我搜索到一些人已经使用它了:
http://vx.netlux.org/lib/vrg02.html
http://www.corsix.org/content/dll-injection-and-wow64
http://int0h.wordpress.com/2009/12/24/the-power-of-wow64/
http://int0h.wordpress.com/2011/02/22/anti-anti-debugging-via-wow64/
很不幸的是,在我刚开始研究时我没有注意到以上的结果,所以我仅仅提出我自己的独立想法。
最简单的办法来查看x86和x64间的转换莫过于去观察64位windows系统中32位版本的ntdll.dll中的任何系统调用:
32-bits ntdll from Win7 x86
mov eax, Xmov edx, 7FFE0300hcall dword ptr [edx];ntdll.KiFastSystemCallretn Z
32-bits ntdll from Win7 x64
mov eax, Xmov ecx, Ylea edx, [esp+4]call dword ptr fs:[0C0h];wow64cpu!X86SwitchTo64BitModeadd esp, 4ret Z
正如你可以看到,在64位系统上有一个到fs:[0xC0](wow64cpu!X86SwitchTo64BitMode)的调用,替代了到ntdll.KiFastSystemCall的标准调用。wow64cpu!X86SwitchTo64BitMode实施了一个远跳到64位段:
wow64cpu!X86SwitchTo64BitMode:748c2320 jmp 0033:748C271E ;wow64cpu!CpupReturnFromSimulatedCode
这就是在64位windows系统里关于x64和x86模式切换的所有内幕戏法了。而且它也能工作在非WOW64进程里(标准的原生64位应用程序),所以32位代码也能在64位应用程序里执行。综上所述,在64位系统里运行的所有进程(x86 & x64)都分配了两个代码段:
cs = 0x23 -> x86 modecs = 0x33 -> x64 mode
首先我已经准备了一些宏,它们将会在64位代码的头尾作为标记使用:
#define EM(a) __asm __emit (a)#define X64_Start_with_CS(_cs) \{ \EM(0x6A) EM(_cs) /* push _cs */ \EM(0xE8) EM(0) EM(0) EM(0) EM(0) /* call $+5 */ \EM(0x83) EM(4) EM(0x24) EM(5) /* add dword [esp], 5 */ \EM(0xCB) /* retf */ \}#define X64_End_with_CS(_cs) \{ \EM(0xE8) EM(0) EM(0) EM(0) EM(0) /* call $+5 */ \EM(0xC7) EM(0x44) EM(0x24) EM(4) /* */ \EM(_cs) EM(0) EM(0) EM(0) /* mov dword [rsp + 4], _cs */ \EM(0x83) EM(4) EM(0x24) EM(0xD) /* add dword [rsp], 0xD */ \EM(0xCB) /* retf */ \}#define X64_Start() X64_Start_with_CS(0x33)#define X64_End() X64_End_with_CS(0x23)
执行完X64_Start()宏后CPU会马上切换到x64模式,执行完X64_End()宏之后又会马上回到x86模式。以上两个宏的地位是独立的,这要归功于远程返回的操作码(译者注:这句不知如何翻译好,原文是Above macros are position independent thanks to far return opcode.)。
在调用x64版本的API时,以上两个宏也是有用的。我曾经尝试加载x64版本的kernel32.dll但这不是琐碎的任务且我没干成功,所以我坚持只调用原生API。现在主要的问题是一些附加的检查导致无法加载64位版本的kernel32.dll,因为(进程)已经完全加载了x86版本的kernel32.dll库。我相信还是可能实现这个目标的,通过一些下流的挂钩手段突破kernel32!BaseDllInitialize,但这是一个非常复杂的的任务。当我开始做这个研究时,我是在WindowsVista下测试的,我成功加载了64位的kernel32和user32库(使用了一些黑客手段),但是它们并没有完全工作。而且当我换成Windows7(测试)时(发现)那个能在Vista上使用的方法已经完全不能工作了。
言归正传,调用原生API需要查找x64版本的ntdll.dll在内存中的位置。为了完成这一任务我分析了来自_PEB_LDR_DATA中InLoadOrderModuleList的结构。64位_PEB能从64位_TEB中获得。而获得64位_TEB的过程和在x86平台上的过程很相似(在x64上我需要使用gs来替代fs):
mov eax, gs:[0x30]
它还可以更简单,因为wow64cpu!CpuSimulate(此函数负责CPU切换到x86模式)移动gs:[0x30]的值到r12寄存器,所以我的getTEB64代码如下:
//to fool M$ inline asm compiler I'm using 2 DWORDs instead of DWORD64//use of DWORD64 will generate wrong 'pop word ptr[]' and it will break stackunion reg64{DWORD dw[2];DWORD64 v;};//macro that simplifies pushing x64 registers#define X64_Push(r) EM(0x48 | ((r) >> 3)) EM(0x50 | ((r) & 7))WOW64::TEB64* getTEB64(){reg64reg;reg.v = 0;X64_Start();//R12 register should always contain pointer to TEB64 in WoW64 processesX64_Push(_R12);//below pop will pop QWORD from stack, as we're in x64 mode now__asm pop reg.dw[0]X64_End();//upper 32 bits should be always 0 in WoW64 processesif(reg.dw[1] != 0)return0;return(WOW64::TEB64*)reg.dw[0];}
WOW64的命名空间已经定义在了os_structs.h文件里,这个文件和源代码一起都在本文的附件里。
以下函数负责获得64位ntdll.dll的位置:
DWORD getNTDLL64(){static DWORD ntdll64 = 0;if(ntdll64 != 0)returnntdll64;WOW64::TEB64* teb64 = getTEB64();WOW64::PEB64* peb64 = teb64->ProcessEnvironmentBlock;WOW64::PEB_LDR_DATA64* ldr = peb64->Ldr;printf("TEB: %08X\n", (DWORD)teb64);printf("PEB: %08X\n", (DWORD)peb64);printf("LDR: %08X\n", (DWORD)ldr);printf("Loaded modules:\n");WOW64::LDR_DATA_TABLE_ENTRY64* head = \(WOW64::LDR_DATA_TABLE_ENTRY64*)ldr->InLoadOrderModuleList.Flink;do{printf(" %ws\n", head->BaseDllName.Buffer);if(memcmp(head->BaseDllName.Buffer, L"ntdll.dll",head->BaseDllName.Length) == 0){ntdll64 = (DWORD)head->DllBase;}head = (WOW64::LDR_DATA_TABLE_ENTRY64*)head->InLoadOrderLinks.Flink;}while(head != (WOW64::LDR_DATA_TABLE_ENTRY64*)&ldr->InLoadOrderModuleList);printf("NTDLL x64: %08X\n", ntdll64);Return ntdll64;}
为了完全支持x64原生API调用,我将需要一些等价于GetProcAddress,可以被ntdll!LdrGetProcedureAddress简单取代的函数。以下代码负责获取LdrGetProcedureAddress的地址:
DWORD getLdrGetProcedureAddress(){BYTE* modBase = (BYTE*)getNTDLL64();IMAGE_NT_HEADERS64* inh = \(IMAGE_NT_HEADERS64*)(modBase + ((IMAGE_DOS_HEADER*)modBase)->e_lfanew);IMAGE_DATA_DIRECTORY& idd = \inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];if(idd.VirtualAddress == 0)return0;IMAGE_EXPORT_DIRECTORY* ied = \(IMAGE_EXPORT_DIRECTORY*)(modBase + idd.VirtualAddress);DWORD* rvaTable = (DWORD*)(modBase + ied->AddressOfFunctions);WORD* ordTable = (WORD*)(modBase + ied->AddressOfNameOrdinals);DWORD* nameTable = (DWORD*)(modBase + ied->AddressOfNames);//lazy search, there is no need to use binsearch for just one functionfor(DWORDi = 0; i < ied->NumberOfFunctions; i++){if(strcmp((char*)modBase + nameTable, "LdrGetProcedureAddress"))continue;elsereturn(DWORD)(modBase + rvaTable[ordTable]);}Return 0;}
接下来我介绍一个函数,能用x86的C/C++代码来调用x64原生API:
DWORD64 X64Call(DWORDfunc, intargC, ...){va_listargs;va_start(args, argC);DWORD64_rcx = (argC > 0) ? argC--, va_arg(args, DWORD64) : 0;DWORD64_rdx = (argC > 0) ? argC--, va_arg(args, DWORD64) : 0;DWORD64_r8 = (argC > 0) ? argC--, va_arg(args, DWORD64) : 0;DWORD64_r9 = (argC > 0) ? argC--, va_arg(args, DWORD64) : 0;reg64 _rax;_rax.v = 0;DWORD64restArgs = (DWORD64)&va_arg(args, DWORD64);//conversion to QWORD for easier use in inline assemblyDWORD64_argC = argC;DWORD64_func = func;DWORDback_esp = 0;__asm{;//keep original esp in back_esp variablemov back_esp, esp;//align esp to 8, without aligned stack some syscalls;//may return errors !and esp, 0xFFFFFFF8X64_Start();;//fill first four argumentspush _rcxX64_Pop(_RCX);push _rdxX64_Pop(_RDX);push _r8X64_Pop(_R8);push _r9X64_Pop(_R9);push edipush restArgsX64_Pop(_RDI);push _argCX64_Pop(_RAX);;//put rest of arguments on the stacktest eax, eaxjz _ls_elea edi, dword ptr [edi+ 8*eax- 8]_ls:test eax, eaxjz _ls_epush dword ptr [edi]sub edi, 8sub eax, 1jmp _ls_ls_e:;//create stack space for spilling registerssub esp, 0x20call _func;//cleanup stackpush _argCX64_Pop(_RCX);lea esp, dword ptr [esp+ 8*ecx+ 0x20]pop edi;//set return valueX64_Push(_RAX);pop _rax.dw[0]X64_End();mov esp, back_esp}Return _rax.v;}
这函数有点长,但注释和整个想法非常简单。第一个参数是我想调用的x64函数的地址,第二个参数是函数参数的个数。剩下的参数依据要调用的函数而定(译者注:剩下的参数就是被调用函数的各个参数),但所有参数的类型必须都是DWORD64类型的。以下是一个关于X64Call()的简单用法:
DWORD64 GetProcAddress64(DWORDmodule, char* funcName){static DWORD _LdrGetProcedureAddress = 0;if(_LdrGetProcedureAddress == 0){_LdrGetProcedureAddress = getLdrGetProcedureAddress();printf("LdrGetProcedureAddress: %08X\n", _LdrGetProcedureAddress);if(_LdrGetProcedureAddress == 0)return0;}WOW64::ANSI_STRING64 fName = { 0 };fName.Buffer = funcName;fName.Length = strlen(funcName);fName.MaximumLength = fName.Length + 1;DWORD64funcRet = 0;X64Call(_LdrGetProcedureAddress, 4,(DWORD64)module, (DWORD64)&fName,(DWORD64)0, (DWORD64)&funcRet);printf("%s: %08X\n", funcName, (DWORD)funcRet);Return funcRet;}
在64位进程里执行x86代码:
这和刚才的例子非常像,不过有点小小的不便,就是64版本的微软C/C++编译器不支持内嵌汇编了。所有的技巧(tricks?)都在一个单独的.asm文件里实现。以下是MASM64中X86_Start和X86_End的宏定义:
X86_Start MACROLOCAL xx, rtcall $+5xx equ$mov dword ptr [rsp+ 4], 23hadd dword ptr [rsp], rt - xxretfrt:ENDMX86_End MACROdb6Ah, 33h ; push 33hdb0E8h, 0, 0, 0, 0 ; call $+5db83h, 4, 24h, 5 ; add dword ptr [esp], 5db0CBh ; retfENDM