几个月前我小小的研究了在WOW64下的32位进程中运行native x64代码。

第二个设想是在64位进程下运行x86代码。它们都是可以的,如我google的一样,

已经有人在使用这两种方法了:

当我研究的时候还没有看上面搜索到的结果,所以下面仅仅代表我自己的见解;)

x86 <-> x64 Transition(x86和x64之间的转换)

最早的来检查x86到x64转换的方法是观察windows中32位版本和64位版本的ntdll.dll中的任意syscall:

32-bits ntdll from Win7 x86 32-bits ntdll from Win7 x64
  1. mov eax, X
  2.  
  3. mov edx, 7FFE0300h
  4. call dword ptr [edx]
  5. ;ntdll.KiFastSystemCall
  6.  
  7. retn Z
  1. mov eax, X
  2. mov ecx, Y
  3. lea edx, [esp+4]
  4. call dword ptr fs:[0C0h]
  5. ;wow64cpu!X86SwitchTo64BitMode
  6. add esp, 4
  7. ret Z
正如你所见,在64位系统上新的call
fs:[0xC0](wow64cpu!X86SwitchTo64BitMode) 代替了标准call ntdll.KiFastSystemCall. 
             

wow64cpu!X86SwitchTo64BitMode  执行了一个简单的远跳转到64位的段中了:

  1. wow64cpu!X86SwitchTo64BitMode:
  2. 748c2320 jmp 0033:748C271E ;wow64cpu!CpupReturnFromSimulatedCode

这就是64位Windows系统上转换x64和x86后面的魔术。

此外他也能在非WoW64进程中运行(标准的native 64位应用程序),所以32位代码也能运行在64位应用程序中。

总结一下,运行在64位Windows中的每个进程(x86和x64),都分配了两个代码段:

  • cs = 0×23 -> x86 mode
  • cs = 0×33 -> x64 mode

Running x64 code inside 32-bits process(在32位进程中运行x64代码)

首先,我准备了一些宏,将用它来标记64位代码的开始和结尾:

  1. #define EM(a) __asm __emit (a)
  2.  
  3. #define X64_Start_with_CS(_cs) \
  4. { \
  5. EM(0x6A) EM(_cs) /* push _cs */ \
  6. EM(0xE8) EM(0) EM(0) EM(0) EM(0) /* call $+5 */ \
  7. EM(0x83) EM(4) EM(0x24) EM(5) /* add dword [esp], 5 */ \
  8. EM(0xCB) /* retf */ \
  9. }
  10.  
  11. #define X64_End_with_CS(_cs) \
  12. { \
  13. EM(0xE8) EM(0) EM(0) EM(0) EM(0) /* call $+5 */ \
  14. EM(0xC7) EM(0x44) EM(0x24) EM(4) /* */ \
  15. EM(_cs) EM(0) EM(0) EM(0) /* mov dword [rsp + 4], _cs */ \
  16. EM(0x83) EM(4) EM(0x24) EM(0xD) /* add dword [rsp], 0xD */ \
  17. EM(0xCB) /* retf */ \
  18. }
  19.  
  20. #define X64_Start() X64_Start_with_CS(0x33)
  21. #define X64_End() X64_End_with_CS(0x23)

执行完X64_Start()宏后,CPU直接转换到x64模式,执行完X64_End()宏后立即回到x86模式。

由于远返回的opcode,以上宏都是位置独立的。

能够调用x64版本的APIs是非常有用的。我尝试加载过x64版本的kernel32.dll,他不是一个微不足道的任务,

并且我失败了,所以我需要坚持使用Native API。x64版本的kernel32.dll的主要问题是在已经加载x86版本的

kernel32.dll的情况下,x64 kernel32.dll 有一些额外的检查来阻止正常的加载。我相信通过一些猥琐的hook

来拦截kernel32!BaseDllInitialize能达到目的,但是这是非常复杂的任务。当我开始研究的时候,我是在WIndows

Vista上,并且我能加载(用一些hacks)64位版本的kernel32和user32库,但是他们没有完整的功能,同时我又

转换到Windows7,使用在Vista上的方法不能够正常工作了。

让我们回到主题上,为了使用Native APIs,我需要定位内存中你给的x64版本的ntdll.dll。为了完成这个任务,

我需要解析_PEB_LDR_DATA结构中的InLoadOrderModuleList。64位的_PEB被64位的_TEB包含,并且64位_TEB

类似于x86平台的(在x64上我们需要使用gs段代替fs):

  1. mov eax, gs:[0x30]

他甚至可以更简单,因为 wow64cpu!CpuSimulate(负责转换CPU到x86模式的函数)将gs:[0x30]的值移动到r12寄存器中,

所以我们的getTEB64()版本看起来像这样:

  1. //to fool M$ inline asm compiler I'm using 2 DWORDs instead of DWORD64
  2. //use of DWORD64 will generate wrong 'pop word ptr[]' and it will break stack
  3. union reg64
  4. {
  5. DWORD dw[2];
  6. DWORD64 v;
  7. };
  8.  
  9. //macro that simplifies pushing x64 registers
  10. #define X64_Push(r) EM(0x48 | ((r) >> 3)) EM(0x50 | ((r) & 7))
  11.  
  12. WOW64::TEB64* getTEB64()
  13. {
  14. reg64 reg;
  15. reg.v = 0;
  16.  
  17. X64_Start();
  18. //R12 register should always contain pointer to TEB64 in WoW64 processes
  19. X64_Push(_R12);
  20. //below pop will pop QWORD from stack, as we're in x64 mode now
  21. __asm pop reg.dw[0]
  22. X64_End();
  23.  
  24. //upper 32 bits should be always 0 in WoW64 processes
  25. if (reg.dw[1] != 0)
  26. return 0;
  27.  
  28. return (WOW64::TEB64*)reg.dw[0];
  29. }

WOW64名字空间定义在"os_structs.h"文件中,随后将会和其他示例代码添加到文章尾部。

负责定位64位ntdll.dll函数定义如下:

  1. DWORD getNTDLL64()
  2. {
  3. static DWORD ntdll64 = 0;
  4. if (ntdll64 != 0)
  5. return ntdll64;
  6.  
  7. WOW64::TEB64* teb64 = getTEB64();
  8. WOW64::PEB64* peb64 = teb64->ProcessEnvironmentBlock;
  9. WOW64::PEB_LDR_DATA64* ldr = peb64->Ldr;
  10.  
  11. printf("TEB: %08X\n", (DWORD)teb64);
  12. printf("PEB: %08X\n", (DWORD)peb64);
  13. printf("LDR: %08X\n", (DWORD)ldr);
  14.  
  15. printf("Loaded modules:\n");
  16. WOW64::LDR_DATA_TABLE_ENTRY64* head = \
  17. (WOW64::LDR_DATA_TABLE_ENTRY64*)ldr->InLoadOrderModuleList.Flink;
  18. do
  19. {
  20. printf(" %ws\n", head->BaseDllName.Buffer);
  21. if (memcmp(head->BaseDllName.Buffer, L"ntdll.dll",
  22. head->BaseDllName.Length) == 0)
  23. {
  24. ntdll64 = (DWORD)head->DllBase;
  25. }
  26. head = (WOW64::LDR_DATA_TABLE_ENTRY64*)head->InLoadOrderLinks.Flink;
  27. }
  28. while (head != (WOW64::LDR_DATA_TABLE_ENTRY64*)&ldr->InLoadOrderModuleList);
  29. printf("NTDLL x64: %08X\n", ntdll64);
  30. return ntdll64;
  31. }

为了完整支持x64 Native API调用,我们还需要等价于GetProcAddress的函数,通过ntdll!LdrGetProcedureAddress更容易

的交流。下面代码负责获取LdrGetProcedureAddress的地址:

  1. DWORD getLdrGetProcedureAddress()
  2. {
  3. BYTE* modBase = (BYTE*)getNTDLL64();
  4. IMAGE_NT_HEADERS64* inh = \
  5. (IMAGE_NT_HEADERS64*)(modBase + ((IMAGE_DOS_HEADER*)modBase)->e_lfanew);
  6. IMAGE_DATA_DIRECTORY& idd = \
  7. inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
  8. if (idd.VirtualAddress == 0)
  9. return 0;
  10.  
  11. IMAGE_EXPORT_DIRECTORY* ied = \
  12. (IMAGE_EXPORT_DIRECTORY*)(modBase + idd.VirtualAddress);
  13.  
  14. DWORD* rvaTable = (DWORD*)(modBase + ied->AddressOfFunctions);
  15. WORD* ordTable = (WORD*)(modBase + ied->AddressOfNameOrdinals);
  16. DWORD* nameTable = (DWORD*)(modBase + ied->AddressOfNames);
  17. //lazy search, there is no need to use binsearch for just one function
  18. for (DWORD i = 0; i < ied->NumberOfFunctions; i++)
  19. {
  20. if (strcmp((char*)modBase + nameTable[i], "LdrGetProcedureAddress"))
  21. continue;
  22. else
  23. return (DWORD)(modBase + rvaTable[ordTable[i]]);
  24. }
  25. return 0;
  26. }

为了锦上添花,我将介绍有用的函数,能让我在x86的C/C++代码中直接的调用x64 Native APIs:

  1. DWORD64 X64Call(DWORD func, int argC, ...)
  2. {
  3. va_list args;
  4. va_start(args, argC);
  5. DWORD64 _rcx = (argC > 0) ? argC--, va_arg(args, DWORD64) : 0;
  6. DWORD64 _rdx = (argC > 0) ? argC--, va_arg(args, DWORD64) : 0;
  7. DWORD64 _r8 = (argC > 0) ? argC--, va_arg(args, DWORD64) : 0;
  8. DWORD64 _r9 = (argC > 0) ? argC--, va_arg(args, DWORD64) : 0;
  9. reg64 _rax;
  10. _rax.v = 0;
  11.  
  12. DWORD64 restArgs = (DWORD64)&va_arg(args, DWORD64);
  13.  
  14. //conversion to QWORD for easier use in inline assembly
  15. DWORD64 _argC = argC;
  16. DWORD64 _func = func;
  17.  
  18. DWORD back_esp = 0;
  19.  
  20. __asm
  21. {
  22. ;//keep original esp in back_esp variable
  23. mov back_esp, esp
  24.  
  25. ;//align esp to 8, without aligned stack some syscalls
  26. ;//may return errors !
  27. and esp, 0xFFFFFFF8
  28.  
  29. X64_Start();
  30.  
  31. ;//fill first four arguments
  32. push _rcx
  33. X64_Pop(_RCX);
  34. push _rdx
  35. X64_Pop(_RDX);
  36. push _r8
  37. X64_Pop(_R8);
  38. push _r9
  39. X64_Pop(_R9);
  40.  
  41. push edi
  42.  
  43. push restArgs
  44. X64_Pop(_RDI);
  45.  
  46. push _argC
  47. X64_Pop(_RAX);
  48.  
  49. ;//put rest of arguments on the stack
  50. test eax, eax
  51. jz _ls_e
  52. lea edi, dword ptr [edi + 8*eax - 8]
  53.  
  54. _ls:
  55. test eax, eax
  56. jz _ls_e
  57. push dword ptr [edi]
  58. sub edi, 8
  59. sub eax, 1
  60. jmp _ls
  61. _ls_e:
  62.  
  63. ;//create stack space for spilling registers
  64. sub esp, 0x20
  65.  
  66. call _func
  67.  
  68. ;//cleanup stack
  69. push _argC
  70. X64_Pop(_RCX);
  71. lea esp, dword ptr [esp + 8*ecx + 0x20]
  72.  
  73. pop edi
  74.  
  75. ;//set return value
  76. X64_Push(_RAX);
  77. pop _rax.dw[0]
  78.  
  79. X64_End();
  80.  
  81. mov esp, back_esp
  82. }
  83. return _rax.v;
  84. }

函数有一点长,但是有注释,并且整个想法也是非常简单的。第一个参数是我们想调用的x64函数地址,第二个参数是指定函数

需要的参数个数,其他的参数依赖于被调用的函数,所有的参数都应该转换成DWORD64。调用X64Call的一个小例子:

  1. DWORD64 GetProcAddress64(DWORD module, char* funcName)
  2. {
  3. static DWORD _LdrGetProcedureAddress = 0;
  4. if (_LdrGetProcedureAddress == 0)
  5. {
  6. _LdrGetProcedureAddress = getLdrGetProcedureAddress();
  7. printf("LdrGetProcedureAddress: %08X\n", _LdrGetProcedureAddress);
  8. if (_LdrGetProcedureAddress == 0)
  9. return 0;
  10. }
  11.  
  12. WOW64::ANSI_STRING64 fName = { 0 };
  13. fName.Buffer = funcName;
  14. fName.Length = strlen(funcName);
  15. fName.MaximumLength = fName.Length + 1;
  16. DWORD64 funcRet = 0;
  17. X64Call(_LdrGetProcedureAddress, 4,
  18. (DWORD64)module, (DWORD64)&fName,
  19. (DWORD64)0, (DWORD64)&funcRet);
  20.  
  21. printf("%s: %08X\n", funcName, (DWORD)funcRet);
  22. return funcRet;
  23. }

Running x86 code inside 64-bits process(在64位进程中运行x86代码)

  1. X86_Start MACRO
  2. LOCAL xx, rt
  3. call $+5
  4. xx equ $
  5. mov dword ptr [rsp + 4], 23h
  6. add dword ptr [rsp], rt - xx
  7. retf
  8. rt:
  9. ENDM
  10. X86_End MACRO
  11. db 6Ah, 33h ; push 33h
  12. db 0E8h, 0, 0, 0, 0 ; call $+5
  13. db 83h, 4, 24h, 5 ; add dword ptr [esp], 5
  14. db 0CBh ; retf
  15. ENDM

Ending notes

文章中使用到的源码链接:

http://download.csdn.net/detail/u014249041/7074553

http://download.csdn.net/detail/u014249041/7074555

原文地址:

http://blog.rewolf.pl/blog/?p=102#.UysCxM4pCqS

Mixing x86 with x64 code (混合编写x86和x64代码)的更多相关文章

  1. FreeBSD上编写x86 Shellcode初学者指南

    FreeBSD上编写x86 Shellcode初学者指南 来源 https://www.4hou.com/binary/14375.html 介绍 本教程的目的是帮助你熟悉如何在FreeBSD操作系统 ...

  2. Qt5 Addin 出现问题模块计算机类型“x64”与目标计算机类型“X86”冲突

    Qt5 Addin 出现问题   怎样VS2013下安装Qt5的插件   http://jingyan.baidu.com/article/a948d65159d8890a2dcd2e84.html ...

  3. 模块计算机类型“X64”与目标计算机类型“x86”冲突

    问题描述:在X64 平台上开发dll 文件,在生成dll时Vs 2010 出现如下错误 :"fatal error LNK1112: 模块计算机类型"X64"与目标计算机 ...

  4. 在VS Code中编写IAR项目

    在VS Code中编写IAR项目 首先按照网上的教程,下载C/C++插件,以及IAR Eebedded Workbench插件,安装完成重启VS Code. 项目目录下新建.vscode文件夹,并新建 ...

  5. Visual Studio Code如何编写运行C、C++

    Visual Studio Code如何编写运行C.C++ 作者:知乎用户链接:https://www.zhihu.com/question/30315894/answer/154979413来源:知 ...

  6. VS Code中编写C

    Visual Studio Code如何编写运行C.C++? Visual Studio Code的C/C++扩展功能 vscode配置C/C++的编译调试环境

  7. 02 How to Write Go Code 如何编写go语言代码

    How to Write Go Code   如何编写go语言代码 Introduction   介绍 Code organization  组织代码 Overview  概述 Workspaces  ...

  8. 使用Visual Studio Code编写和激活ABAP代码 (上)

    猪年春节后的第一篇,Jerry祝各位猪年大吉! 2019年的六分之一马上就快过完了,不知道大家在新的一年是否给自己定了新的小目标呢?这里Jerry先预祝大家到2019年年底的时候,在年初制定的小目标都 ...

  9. 使用VS Code快速编写HTML

    VS Code 有自动补全HTML代码方法体的功能 1.打开VS Code并新建文件,点击底部右侧语言模式选项,默认为纯文本(plaintext),将其改为HTML. 2.在空文件第一行输入”!“,光 ...

随机推荐

  1. js获取请求地址后面带的参数

    浏览器输入页面地址的时候在后面带有请求参数, 页面加载后需要获取携带的参数, 可以使用js, 在页面加载js的时候获取参数 http://localhost:8080/demo/index.html? ...

  2. PTA天梯 L3-007 天梯地图

    L3-007 天梯地图 题目: 本题要求你实现一个天梯赛专属在线地图,队员输入自己学校所在地和赛场地点后,该地图应该推荐两条路线:一条是最快到达路线:一条是最短距离的路线.题目保证对任意的查询请求,地 ...

  3. Sublime 汉化、快捷键打开浏览器

    Sublime 是一个优秀的代码编译工具,它具有漂亮的用户界面和强大的功能,例如代码缩略图,Python 的插件,代码段等.不仅如此,它还可自定义按键绑定,菜单和工具栏.由于是歪果仁开发的,所以官方版 ...

  4. Eclipse的各种查找,类的查找,方法查找快捷键

    eclipse开发中,查找会是一个经常用到的功能所以总结一下1,查找一个类 Shift + Ctrl + h 这种方式能快速的定位接口,类还有注解在那个包里面2.综合查找 Ctrl + H 这是一种综 ...

  5. 游览器发送http请求经过的步骤

    OSI参考模型(Open System Interconnection,开放系统互连),全称为开放系统互联参考模型 ,OSI将计算机网络体系结构划分为了七层 TCP/IP协议族(TCP/IP Prot ...

  6. [找工作] 2019秋招|从春招到秋招,Java岗经验总结(收获AT)

    转自(有更多) https://blog.csdn.net/zj15527620802/article/month/2018/10 前言 找工作是一件辛酸而又难忘的历程.经历过焦虑.等待.希望,我们最 ...

  7. [国家集训队]happiness

    嘟嘟嘟 就这么建. --- #include<cstdio> #include<iostream> #include<cmath> #include<algo ...

  8. 转:// LINUX下为ORACLE数据库设置大页--hugepage

    一.在解释什么情况下需要开启大页和为啥需要开启大页前先了解下Linux下页的相关的知识:以下的内容是基于32位的系统,4K的内存页大小做出的计算1)目录表,用来存放页表的位置,共包含1024个目录en ...

  9. 二、Oracle 数据库基本操作

    一.oracle常用数据类型数字:number(p,s) p表示数字的长度包括小数点后的位数,s表示小数点后的位数固定长度字符:char(n):n表示最大长度,n即是最大也是固定的长度,当数据不满长度 ...

  10. 吴恩达课后作业学习1-week2-homework-logistic

    参考:https://blog.csdn.net/u013733326/article/details/79639509 希望大家直接到上面的网址去查看代码,下面是本人的笔记 搭建一个能够 “识别猫” ...