談談64 位程序的內嵌彙編

我在二月份發表了拙文《在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 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章