很久之前整理了一篇《C# 調用非託管程式》文章,在部落格園zhongzf同學《在.net程式中嵌入asm彙編代碼》進行了簡單的討論,現在才有時間整理。
《C# 調用非託管程式》最後一種方法通俗的講是構造符合彙編代碼(機器代碼)格式的資料,把該資料當作可執行代碼執行。Windows提供了DEP(Data Execution Prevention 資料執行防止)機制,也就是Windows會試圖阻止程式運行非可執行記憶體地區的可執行代碼。如果開啟DEP,《C# 調用非託管程式》一文中最後一種方法執行會失敗;如果關閉DEP,Windows XP SP2中執行該代碼會成功,但Vista/Win7由於安全性增強原因,該代碼執行會失敗。
是不是這用嵌入彙編代碼的方式在開啟DEP及Vista/Win7中一定不能使用呢?答案在下面分析中得出。
如果我們把儲存彙編代碼的記憶體地區標記為可執行,上面的方法也許可用。
Win API VirtualAlloc中有參數,可以指定新分配記憶體的許可權。
使用完記憶體後,調用VirtualFree釋放。
沿著這樣的思路,將《C# 調用非託管程式》一文中最後一種方法修改如下(篇幅原因簡化了注釋):/*修改記錄
2008-5-11 8:07 曲濱
>> 基本實現預期功能
[!] 明天進行最佳化
2008-5-12 15:54 曲濱
[E] 最佳化完成
[N] 加入 NativeCodeHelper 類便於使用
2010-6-17 周振興
修改相容性,可在開啟DEP及Vista/Win7中運行。
*/
namespace NShellNativeCode
{
using System;
using System.Runtime.InteropServices;
delegate int AddProc(int p1, int p2);
class Program
{
static void Main(string[] args)
{
byte[] codeBytes = {
0x8B, 0x44, 0x24, 0x08 // mov eax,[esp+08h]
, 0x8B, 0x4C, 0x24, 0x04 // mov ecx,[esp+04h]
, 0x03, 0xC1 // add eax,ecx
, 0xC3 // ret
};
/*
上面的位元組數組,就是下面函數的機器碼;
int add(int x,int y) {
return x+y;
}
*/
IntPtr handle = IntPtr.Zero;
handle = VirtualAlloc(
IntPtr.Zero,
codeBytes.Length,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
try
{
Marshal.Copy(codeBytes, 0, handle, codeBytes.Length);
AddProc add
= Marshal.GetDelegateForFunctionPointer(handle, typeof(AddProc)) as AddProc;
int r = add(1976, 1);
Console.WriteLine("機器碼返回:{0}", r);
}
finally
{
VirtualFree(handle, 0, MEM_RELEASE);
}
Console.ReadLine();
}
//Windows API
[DllImport("Kernel32.dll", EntryPoint = "VirtualAlloc")]
public static extern IntPtr VirtualAlloc(IntPtr address, int size, uint allocType, uint protect);
[DllImport("Kernel32.dll", EntryPoint = "VirtualFree")]
public static extern bool VirtualFree(IntPtr address, int size, uint freeType);
//flags
const uint MEM_COMMIT = 0x1000;
const uint MEM_RESERVE = 0x2000;
const uint PAGE_EXECUTE_READWRITE = 0x40;
const uint MEM_RELEASE = 0x8000;
}
}
事實證明,這種嵌入彙編代碼的方式在開啟DEP及Vista/Win7可運行。
Win7 (X86)開啟DEP環境下測試通過。
註:codeBytes數組中是X86彙編代碼,如果要在X64中運行,需修改該代碼!