標 題: 【原創(chuàng)】內核讀寫只讀內存方法總結[Delphi描述]
作 者: Anskya
時 間: 2008-04-26,16:24:39
鏈 接: http://bbs./showthread.php?t=63791
以下代碼均已Delphi描述...至于為什么...
首先我是一個Delphi Coder...雖然我大部分時間使用的是ASM編譯器和C編譯器
但是我喜歡Delphi...好了不廢話了...
已知的三種方法:如果各位有更好的意見歡迎大家提出
[1]使內存可讀寫
1.stl+cr0:
這個方法大家想必經常使用...
(參考I-32.3A文檔)
由于cr0是一個32位寄存器...假設大家的CPU是32位的.沒有64位測試環(huán)境
按照圖這個結構我們可以了解到的信息
第16位:WP——Write Protect,當設置為1時只提供讀頁權限
第0位:PE——Paging,當設置為1時提供分頁
第1位:MP——Protection Enable,當設置為1時進入保護模式
按照這個說明寫出代碼:
//1 關閉寫保護
asm
push eax
mov eax, CR0
and eax, 0FFFEFFFFh
mov CR0, eax
pop eax
end;
//2 打開寫保護
asm
push eax
mov eax, CR0
or eax, NOT 0FFFEFFFFh
mov CR0, eax
pop eax
end;
也許大家看得有點模糊.其實這個代碼就是修改第16位
NOT 0FFFEFFFFh = 00010000h
因為要設置第16位為1...所以結果也就是代碼那種效果...
鄂~我也不知道說什么了.也就是位操作你也可以直接使用
//1 關閉寫保護
asm
push eax
mov eax, cr0
and eax, not 000010000h
mov cr0, eax
pop eax
end;
//2 打開寫保護
asm
push eax
mov eax, cr0
or eax, 000010000h
mov cr0, eax
pop eax
end;
一般使用的時候我們都會加上cli和sti,由于這兩個指令只能控制當前CPU對于
多核系統(tǒng)是無法起到有效的互斥的...所以一般都是配合"自轉鎖"使用
關于自轉鎖的話題我們下面會詳細的說的
2.通過內存描述表(MDL)中描述一塊內存區(qū)域,,MDL包含此內存區(qū)域的起始地址,
擁有者進程,字節(jié)數量以及標志.(據說是Bill提供的方法.難道和VirtualProtect一樣?)
代碼:
type
PMDL = ^_MDL;
_MDL = packed record
Next: PMDL;
Size: USHORT;
MdlFlags: USHORT;
Process: Pointer;
MappedSystemVa: PVOID;
StartVa: PVOID;
ByteCount: ULONG;
ByteOffset: ULONG;
end;
MDL = _MDL;
const
MDL_MAPPED_TO_SYSTEM_VA = $0001;
MDL_PAGES_LOCKED = $0002;
MDL_SOURCE_IS_NONPAGED_POOL = $0004;
MDL_ALLOCATED_FIXED_SIZE = $0008;
MDL_PARTIAL = $0010;
MDL_PARTIAL_HAS_BEEN_MAPPED = $0020;
MDL_IO_PAGE_READ = $0040;
MDL_WRITE_OPERATION = $0080;
MDL_PARENT_MAPPED_SYSTEM_VA = $0100;
MDL_LOCK_HELD = $0200;
MDL_PHYSICAL_VIEW = $0400;
MDL_IO_SPACE = $0800;
MDL_NETWORK_HEADER = $1000;
MDL_MAPPING_CAN_FAIL = $2000;
MDL_ALLOCATED_MUST_SUCCEED = $4000;
// 讀寫只讀內存(源于Gates大叔)
function WriteReadOnlyMemoryGates(lpDest, lpSource: Pointer; Length: Integer):
NTSTATUS;
var
tempSpinLock: KSPIN_LOCK;
oldirql: KIRQL;
mdl: PMDL;
writableAddress: Pointer;
begin
Result := STATUS_UNSUCCESSFUL;
mdl := MmCreateMdl(nil, lpDest, Length);
if (mdl <> nil) then
begin
MmBuildMdlForNonPagedPool(mdl);
mdl^.MdlFlags := mdl^.MdlFlags or MDL_MAPPED_TO_SYSTEM_VA;
writableAddress := MmMapLockedPages(mdl, KernelMode);
if (writableAddress <> nil) then
begin
oldirql := 0;
KeInitializeSpinLock(@tempSpinLock);
fast_KfAcquireSpinLock(@tempSpinLock);
memcpy(writableAddress, lpSource, Length);
fast_KfReleaseSpinLock(@tempSpinLock, oldirql);
MmUnmapLockedPages(writableAddress, mdl);
Result := STATUS_SUCCESS;
end;
MmUnlockPages(mdl);
IoFreeMdl(mdl);
end;
end;
關鍵一步就是修改mdl的屬性.讓他可寫....
下面來看看第三種方法
3.使用IoAllocateMdl來得到可寫內存...
源于Mark早期寫的代碼...我這里直接翻譯了...至于原理
大家參考一下DDK吧...我發(fā)現自己表達能力有限...
代碼:
// 寫只讀內存(源于Mark代碼)
function WriteReadOnlyMemoryMark(lpDest, lpSource: Pointer; Length: Integer):
NTSTATUS;
var
tempSpinLock: KSPIN_LOCK;
oldirql: KIRQL;
mdl: PMDL;
writableAddress: Pointer;
begin
Result := STATUS_UNSUCCESSFUL;
mdl := IoAllocateMdl(lpDest, Length, False, False, nil);
if (mdl <> nil) then
begin
MmBuildMdlForNonPagedPool(mdl);
MmProbeAndLockPages(mdl, KernelMode, IoWriteAccess);
writableAddress := MmMapLockedPages(mdl, KernelMode);
if (writableAddress <> nil) then
begin
oldirql := 0;
KeInitializeSpinLock(@tempSpinLock);
fast_KfAcquireSpinLock(@tempSpinLock);
memcpy(writableAddress, lpSource, Length);
fast_KfReleaseSpinLock(@tempSpinLock, oldirql);
MmUnmapLockedPages(writableAddress, mdl);
Result := STATUS_SUCCESS;
end;
MmUnlockPages(mdl);
IoFreeMdl(mdl);
end;
end;
代碼源于Mark早期編寫的代碼
相信看到這份代碼和上面的第二種方法相信大家似乎都明白了什么...
是不是覺得兩種方法都差不多區(qū)別就是在于
一個是直接修改MDL屬性:MDL_MAPPED_TO_SYSTEM_VA
一個是MmProbeAndLockPages(mdl, KernelMode, IoWriteAccess);
來改寫屬性...其實這兩種方法...嘿嘿~自己看看就明白了...
[2]改寫內存!
平時我們修改內存單核下沒有什么顧慮.stl|代碼|cli
開關中斷以后就可以自己操作了...
因為現在雙核和多核越來越興起了...
這么說吧.內存在沒有完全修改完畢的時候...其他線程去讀取的話...
就會造成讀取錯誤的數據或者斷碼...
如果您是單核的話可以直接修改
1.stl+cli
代碼:
asm
cli //disable WP bit
push eax
mov eax, cr0 //move CR0 register
into EAX
and eax, not 000010000h //disable WP bit
mov cr0, eax //write register back
pop eax
end;
//改寫的代碼
asm
push eax //enable WP bit
mov eax, cr0 //move CR0 register
into EAX
or eax, 000010000h //enable WP bit
mov cr0, eax //write register
back
pop eax
sti
end;
2.利用原子互斥操作
比如說我們需要SSDT,Shadow,etc Hook
修改地址等操作的話...我們有一個簡單的方法
使用...Interlocked系函數
修改地址推薦使用InterlockedExchange...
釋放方法很簡單
代碼:
ZwOpenProcessNextHook := TZwOpenProcess(InterlockedExchange(SystemServiceName
(GetImportFunAddr(@ZwOpenProcess)), LONG(@ZwOpenProcessNextHook)));
提示一下這個函數使用的是fastcall調用方式,如果你使用的是Delphi的話建議你最好
注意一下使用方法...fastcall和Delphi的register的是有區(qū)別的
fastcall:
第一個參數:edx
第二個參數:ecx
register:
第一個參數:eax
第二個參數:edx
第三個參數:ecx
提供以下InterlockedExchange的源代碼
代碼:
FORCEINLINE
LONG
FASTCALL
InterlockedExchange(
IN OUT LONG volatile *Target,
IN LONG Value
)
{
__asm {
mov eax, Value
mov ecx, Target
xchg [ecx], eax
}
}
3.自轉鎖
自轉鎖是內核中的一種同步機制...
關于自轉鎖的介紹請大家去看
<<Programming the Microsoft Windows Driver Model>>這本書...
這本書剛開始看許多東西暫時還不太理解...
當線程獲取自轉鎖的時候...會將當前線程提升到DISPATCH_LEVEL級別
這個時候內核將不會分配時間片給其他的CPU.這樣就不會有其他線程
在你寫操作的時候去訪問你沒有寫完的地方了...放心大膽的寫了...
怎么寫?暈...這個問題這個問題...呵呵我可以不回答嗎?memcpy總會用吧...
自轉鎖使用起來很簡單...
(有個疑問.為什么獲取和釋放自轉鎖的函數在hal.dll函數中,初始化自轉鎖的函數
卻在ntoskrnl.exe模塊中...)
使用方法:
KeInitializeSpinLock(@TemSpinLockp);
KeAcquireSpinLock(@TemSpinLockp, &oldirql);
.
.
你的操作代碼
.
.
KeReleaseSpinLock(@TemSpinLockp, oldirql);
如果有其他內核線程已經獲取自轉鎖的時候你的線程是不會被分配到時間片的
所以你不用擔心你會進去破壞代碼(除非~除非~你提升到更高的級別...)
當你獲取自轉鎖成功后..其他線程將進入自轉狀態(tài)..你可以理解為互斥
(EnterCriticalSection,LeaveCriticalSection)
[警告]請千萬不要連續(xù)使用兩次KeAcquireSpinLock!否則機器會(卡)死的很難看的
etc...
其他方法暫時不清楚.暫時由于自轉鎖的這種特殊效果.
所以大家能不用的時候盡量不要使用...使用的時候盡量減少自轉時間.以達到更高的
效能...
小弟初學驅動編程.如果有不對的地方請各位大牛補充說明...這里拜過
特別感謝:
<<如何寫windows系統(tǒng)已保護的內存區(qū)域>>的作者.由于被轉載了不知道多少次
當我看到這篇文章的時候...作者已經不知道是誰了...感謝這位無名英雄...謝謝
鳴謝:
zhuwg,FlowerCode,農夫大哥,冷風(烤鴨),旋轉"AV"群里的各位神牛們
希望感興趣的Delphi Fans可以PM我大家一起交流一下開發(fā)經驗...