Hacking Windows – 第1章: Hello World

原文链接:https://github.com/mytechnotalent/Hacking-Windows

我们从用 Windows 汇编语言编写一个非常简单的 hello world 程序开始我们的旅程。我们将在本章中仅使用纯汇编语言编写,因为我们将专注于 C 语言的演变,几乎所有 Windows 开发都发生,因此您可以更好地了解这些应用程序是如何组合在一起的,包含在 x86 和x64。

让我们首先下载我们将用作集成开发环境的 Visual Studio。在下面的链接中选择 Visual Studio 2019 社区版。确保在设置过程中选择了所有 C++ 和 Windows 选项,以确保构建环境具有所有必要的工具。如有疑问,请选中该框以在安装期间将其包括在内。

https://visualstudio.microsoft.com/downloads

安装后,让我们创建一个新项目,并按照以下步骤开始。

  1. 创建一个新项目
  2. Empty Projec(空项目)
  3. Next(下一步)
  4. Project name(项目名称):0x0001-hello_world-x86
  5. 选中“Place solution and project in the same directory”(将解决方案和项目放在同一目录中)
  6. Create(创建)
  7. 在解决方案资源管理器中右键单击0x0001-hello_world-x86
  8. Add(添加)
  9. New Item…(新建项)
  10. main.asm
  11. 右键单击 0x0001-hello_world-x86
  12. Build Dependencies(生成依赖项)
  13. Build Customizations(生成自定义)
  14. 选择masm
  15. OK(确定)
  16. 右键单击 main.asm
  17. Properties(属性)
  18. Configuration Properties(配置属性)
  19. General(常规)
  20. Item Type(项类型): Microsoft Macro Assembler
  21. OK(确定)

现在让我们来填充我们的main.asm文件包含以下内容 :

祝贺您刚刚在x86 Windows程序集中创建了第一个hello world代码。该吃蛋糕了!

在本课程中,我们将花费大部分时间学习Win32API文档。

让我们花点时间回顾一下。首先,我们指定一个.686,这意味着可以在32位MASM中为奔腾Pro+风格的体系结构组装非特权指令。

(VISIT https://docs.microsoft.com/en-us/cpp/assembler/masm/dot-686?view=msvc-160)

我们调用的第一个Win32API是MessageBoxA,它提供了一个Windows消息框来显示。然后,我们建立了一个平面内存模型,它不使用组合段或偏移寻址。我们还使用stdcall Win32调用约定,将参数按相反顺序推送到堆栈上,然后调用过程。被调用方在调用后清除堆栈。

我们将调用的第二个Win32API是ExitProcess,它简单地退出应用程序,并将操作释放到Windows操作系统。

(VISIT https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-exitprocess)

我们看到,该函数是一个void函数,它不返回任何内容,只有一个参数UINT uExitCode,它只检索进程的退出值。

你可能已经注意到函数后面有一个非常奇怪的@4。这是为了指定该函数有1个参数。我们将每个参数乘以4得到这个名称。

我们的下一个Win32API是MessageBoxA函数,它只显示一个带有标题和消息的模式对话框。

(VISIT https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winusermessageboxa)

我们这里有4个参数,所以我们知道在函数的末尾会有一个@16。

第一个参数是HWND HWND,它是要创建的消息框窗口的所有者的句柄,在我们的例子中,它为NULL意味着消息框没有所有者。

然后我们有了LPCSTR lpText,它将在消息框中显示我们的文本。

然后我们有了LPCSTR lpCaption,它将是消息框上的标题文本。

最后是UINT uType,它只是文档中表格中的标志组合。在我们的情况下,它将是空的。

请记住,在stdcall中,我们按相反的顺序将参数推送到堆栈上,正如您在上面的代码中看到的那样。

此时,我们可以通过单击本地Windows调试器旁边的绿色箭头来运行代码。

万岁,我们的hello world模式对话框弹出。

现在让我们创建这段代码的x64版本。

  1. 创建一个新项目
  2. Empty Projec(空项目)
  3. Next(下一步)
  4. Project name(项目名称):0x0001-hello_world-x64
  5. 选中“Place solution and project in the same directory”(将解决方案和项目放在同一目录中)
  6. Create(创建)
  7. 在解决方案资源管理器中右键单击0x0001-hello_world-x64
  8. Add(添加)
  9. New Item…(新建项)
  10. main.asm
  11. 右键单击 0x0001-hello_world-x64
  12. Build Dependencies(生成依赖项)
  13. Build Customizations(生成自定义)
  14. 选择masm
  15. OK(确定)
  16. 在解决方案浏览器中右键单击0x0001-hello_world-x64
  17. // 右键单击 main.asm
  18. Properties(属性)
  19. Configuration Properties(配置属性)
  20. Linker(链接器)
  21. Advanced(高级)
  22. Entry Point(入口点): main
  23. OK(确定)

选择“调试”菜单栏右侧和本地Windows调试器菜单栏左侧的x64

现在让我们来填充我们的main.asm文件包含以下内容。

我们还看到一个对PostQuitMessage的调用,该调用以int-nExitCode作为参数。

 (VISIT https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuserpostquitmessage

祝贺您刚刚在x64 Windows程序集中创建了第一个hello world代码。又到吃蛋糕的时间了!

让我们花点时间回顾一下。我们首先需要了解x64呼叫约定。

VISIT https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc160)

我们在x64中使用的是Microsoft x64呼叫约定fastcall。我们在参数传递部分看到的是,默认情况下,x64调用约定将前四个参数传递给寄存器中的函数。用于这些参数的寄存器取决于参数的位置和类型。剩余的参数将按从右到左的顺序推送到堆栈上。调用方在调用后清理堆栈。

最左边四个位置的整数值参数分别按RCX、RDX、R8和R9的从左到右顺序传递。如前所述,第五个及以上参数在堆栈上传递。寄存器中的所有整数参数都是右对齐的,因此被调用方可以忽略寄存器的高位,只访问寄存器中必要的部分。

前四个参数中的任何浮点和双精度参数都会在XMM0-XMM3中传递,具体取决于位置。只有当存在varargs参数时,浮点值才会被放入整数寄存器RCX、RDX、R8和R9中。有关详细信息,请参阅Varargs。类似地,当相应的参数是整数或指针类型时,XMM0-XMM3寄存器将被忽略。

根据x64调用约定,我们需要为每个QWORD的内存单元提供一个阴影堆栈,并且堆栈必须与下一条指令的16字节对齐。

阴影空间是我们必须为被调用的过程保留的强制32字节(4×8字节)。在调用之前,我们在堆栈上提供32个字节。这个空间可以不初始化。

在这个调用约定中,第四个之后的参数被推送到堆栈上,堆栈位于这个阴影空间的顶部(推到32字节之前)。

然后,我们设置并再次调用MessageBoxA Win32API。我们不需要查看参数,因为我们在x86示例中已经处理过这个问题。

然后我们恢复阴影堆栈,然后调用ExitProcess。

此时,我们可以通过单击本地Windows调试器旁边的绿色箭头来运行代码。

万岁,我们的hello world模式对话框弹出。

这将是我们在所有汇编中编写代码的唯一例子,我想使用官方的Win32API进行教学,Win32API本机是C语言,但是我想首先向您展示在实际编译时,它到底在做什么。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注