我在二月份发表了拙文《在64位 VC程序里内嵌汇编》后,没想到被一些朋友提了一大
堆意见,主要都是说我没有说清楚怎么编译Win64 汇编代码。我之前以为人人都会用VS2010
的命令行,没想到竟然有不少人不会用,所以我在此文里认真介绍下编译Win64 汇编代码的
工具,以及使用方法。上次的文章还有两个重大不足,一是内嵌的汇编程序不能有参数和返
回值,这使内嵌汇编的价值大打折扣;二是只能说了在VC里内嵌汇编的方法,没有说在VB
程序里内嵌汇编的方法,这就愧对了不少喜爱VB的读者。
Win64汇编代码我是使用MASM64来编译的。请注意:这个MASM64不是微软出的,第一
个M不是Microsoft的意思,也不是macro的意思,而是压根没有意思。只是叫惯了MASM的叫
法,Win32汇编开发工具包叫MASM32,所以Win64汇编开发工具包就叫MASM64了。MASM64
汇编开发工具包最早是由64 位汇编语言论坛(www.x64asm.com/asmbbs)的旖旎网友制作,
我下载后修改了一些我认为不好的地方,现在把修改后的压缩包挂在了老马的紫水晶编程技
术论坛上下载。下载地址是:http://www.m5home.com/bbs/thread-5170-1-1.html(建议编
辑去下载一下放在PDF里,我就不发邮件了,因为那个东西有10MB大,不好发,Tudor在那
里有帐号,下载很方便)。下载完文件后请按照帖子的说明操作,把压缩包解压到C盘根目录,
既C盘下面有个名为MASM64的文件夹(必须!)。
编译Win64汇编代码需要使用到一个CMD文件,它的内容大致如下:
@Set ProgName=64位程序
@Color 0E
@Title %ProgName%
@call C:\Masm64\Env.Cmd
::此处修改为您的应用程序所在目录
@set Directory="F:\x64asm"
::此处修改为您的应用程序源文件名称(无需扩展名)
@Set SrcName=AsmSrc
@cd %Directory%
@echo 正在删除 %ProgName% 早期版本文件与临时文件...
@del "%Directory%\%SrcName%.obj"
@del "%Directory%\%SrcName%.exe"
@Echo 正在编译 %ProgName% 代码...
@C:\Masm64\BIN\x64\ml64 /c "%Directory%\%SrcName%.asm"
@Echo 正在链接 %ProgName% 代码...
@C:\Masm64\BIN\x64\Link /RELEASE /subsystem:windows /Entry:Entry
"%Directory%\%SrcName%.obj" /Out:"%Directory%\%SrcName%.exe"
@Color 0E
@Echo 构建完成请按任意键运行 %ProgName% 应用程序,按 CTRL+C键退出...
@pause
@"%Directory%\%SrcName%.Exe"
每次编译 Win64汇编代码时,仅仅需要修改染成蓝色部分的文字。修改完毕后,双击这
个 CMD文件即可。
为了方便大家获得机器码,我还特地写了个获取Win64汇编机器码的程序。使用方法很
简单,在上面的文本框输入汇编代码(用10个NOP分割两条汇编指令),然后点击【Get BIN】
按钮即可,生成的机器码样式支持VB 和C++样式。不过,这个程序必须在安装了MASM64之
后才能使用,而且计算机上必须安装了.NET 4.0运行库:
上次我仅仅说了在VC 里内嵌汇编的方法,但是却没有说明如何让内嵌的汇编子程序有
返回值和给汇编子程序传入参数。现在我把这个方法给大家说说。首先是使用typedef定义
一下你的汇编子程序的原型,然后获得机器码所在CHAR 数组的地址(指针p),最后把指针
p 强制转换为你用typedef定义的原型。最后把那个存放了机器码的CHAR数组当作函数调
用。当然,要把CHAR的内存属性改为PAGE_EXECUTE_READWRITE。为了方便传递参数,汇编
子程序使用__fastcall约定比较好。代码示例如下(输入四个数字相加,返回它们相加的
值):
#include <stdio.h>
#include <Windows.h>
typedef UINT64 (__fastcall *SCFN)(UINT64,UINT64,UINT64,UINT64);
void test_function_params_return()
{
SCFN scfn;
DWORD xxx;
UINT64 ret;
UCHAR
strShellCode[14]="\x48\x03\xCA\x49\x03\xC8\x49\x03\xC9\x48\x8B\xC1\xC3";
/*
add rcx,rdx
add rcx,r8
add rcx,r9
mov rax,rcx
ret
*/
PVOID p=strShellCode;
VirtualProtect(strShellCode,45,PAGE_EXECUTE_READWRITE,&xxx);
scfn=(SCFN)p;
ret=scfn(11,22,33,44);
printf("%lld",ret);
getchar();
}
void main()
{
test_function_params_return();
}
在驱动程序里内嵌汇编也类似,不过需要注意的是不能直接把数组当作函数来执行,首
先要用ExAllocatePool申请一块NonPagedPool内存,然后把数组的内容复制到那片内存里,
再把那片内存当作函数来执行:
typedef UINT64 (__fastcall *SCFN)(UINT64,UINT64,UINT64,UINT64);
VOID test()
{
SCFN scfn;
UINT64 ret;
UCHAR
strShellCode[14]="\x48\x03\xCA\x49\x03\xC8\x49\x03\xC9\x48\x8B\xC1\xC3";
/*
add rcx,rdx
add rcx,r8
add rcx,r9
mov rax,rcx
ret
*/
scfn=ExAllocatePool(NonPagedPool,14);
ret=scfn(11,22,33,44);
DbgPrint("[x64Drv] Inline ASM return: %lld",ret);
ExFreePool(scfn);
}
需要注意的是,在Windows 7系统中 DebugView可能无法截获任何输出,这是因为系统
引入了一个新函数DebugPrintEx导致的。要想DebugView截获输出,需要执行以下步骤:
1.在 HKLM\SYSTEM中查找Debug Print Filter项
2.把 Debug Print Filter项中 Default键的值改为ffffffff
在 VB2010中内嵌汇编比在VB6中内嵌汇编困难多了,因为VB2010不支持指针操作。为
此,微软有一篇技术文章专门是写如何在VB.NET中内嵌汇编(网址:
http://www.microsoft.com/china/msdn/library/langtool/vbnet/SDaskgui12172002.msp
x?mfr=true)。这篇文章十分长,让我来评价就四个字:废话连篇!因为在VB里内嵌汇编的
原理是十分简单的,就是使用 CallWindowProcW,它的第一个参数为待调用汇编子程序的起
始地址,后四个参数位汇编子程序的头四个参数。两句话就讲完的问题,何须如此故弄玄虚,
故作高深?!编程本来很容易,但是被砖家弄得无比复杂,本人最讨厌的就是把简单问题复
杂化的文章,以及写这种文章的作者。我认为,把复杂的问题简单化才是能耐。
言归正传,尽管VB2010不能直接使用指针(不能用VarPtr获得指针的值),但是还是可
以用 ByRef变相传递指针的。不过在VB2010中内嵌汇编还是有一点限制的,详述如下:1.
内嵌的汇编子程序只能是__fastcall约定,不能是别的约定。CallWindowProcW的后四个参
数分辨传入的是rcx、rdx、r8、r9的值;2.内嵌的汇编子程序只能方便地传递四个参数,
多于四个参数就比较麻烦了(但不是没有办法传递)。在声明CallWindowProcW时对第一个
参数用 ByRef As Byte,就是传送字节指针的意思,调用的时候把数组的第0个元素写入实
参,通过这种方式就变相把汇编子程序起始地址传递给了CallWindowProcW,因为在VB 里
汇编子程序的机器码也是放在CHAR数组里的(等效于C 语言里通过&array[0]来传递数组地
址)。我写下了如下的代码测试:
Module Module1
Private Declare Function VirtualProtect Lib "kernel32.dll" (ByRef
lpAddress As Byte, ByVal dwSize As Long, ByValflNewProtect As Long, ByRef
lpflOldProtect As Long) As Long
Private Declare Function CallWindowProc Lib "user32.dll" Alias
"CallWindowProcW" (ByRef lpFunc As Byte,ByVal rcx As Long, ByVal rdx As Long,
ByVal r8 As Long, ByVal r9 As Long) As Long
Sub Main()
Dim x(12) As Byte, y As Long, z As Long
x(0) = &H48
x(1) = &H3
x(2) = &HCA
x(3) = &H49
x(4) = &H3
x(5) = &HC8
x(6) = &H49
x(7) = &H3
x(8) = &HC9
x(9) = &H48
x(10) = &H8B
x(11) = &HC1
x(12) = &HC3
'PAGE_EXECUTE_READWRITE = &H40
VirtualProtect(x(0), 7, &H40, y)
'四个参数
z = CallWindowProc(x(0), 11, 22, 33, 44)
'显示结果
MsgBox(z)
End Sub
End Module