看流星社区

 找回密码
 注册账号
查看: 2291|回复: 0

内核读写只读内存方法总结[Delphi描述]

[复制链接]

该用户从未签到

发表于 2017-6-1 17:25:31 | 显示全部楼层 |阅读模式
标 题:内核读写只读内存方法总结[Delphi描述]
作 者: Anskya
时 间: 2008-04-26,16:24:39
链 接: http://bbs.pediy.com/showthread.php?t=63791

以下代码均已Delphi描述...至于为什么...
首先我是一个DelphiCoder...虽然我大部分时间使用的是ASM编译器和C编译器
但是我喜欢Delphi...好了不废话了...

已知的三种方法:如果各位有更好的意见欢迎大家提出

[1]使内存可读写

1.stl+cr0:
这个方法大家想必经常使用...

(参考I-32.3A文档)
由于cr0是一个32位寄存器...假设大家的CPU是32位的.没有64位测试环境
按照图这个结构我们可以了解到的信息
第16位:WP——WriteProtect,当设置为1时只提供读页权限
第0位E——Paging,当设置为1时提供分页
第1位:MP——ProtectionEnable,当设置为1时进入保护模式

按照这个说明写出代码:
//1关闭写保护
asm
pusheax
moveax,CR0
andeax,0FFFEFFFFh
movCR0,eax
popeax
end;

//2打开写保护
asm
pusheax
moveax,CR0
oreax,NOT0FFFEFFFFh
movCR0,eax
popeax
end;
也许大家看得有点模糊.其实这个代码就是修改第16位
NOT0FFFEFFFFh=00010000h
因为要设置第16位为1...所以结果也就是代码那种效果...
鄂~我也不知道说什么了.也就是位操作你也可以直接使用
//1关闭写保护
asm
pusheax
moveax,cr0
andeax,not000010000h
movcr0,eax
popeax
end;

//2打开写保护
asm
pusheax
moveax,cr0
oreax,000010000h
movcr0,eax
popeax
end;

一般使用的时候我们都会加上cli和sti,由于这两个指令只能控制当前CPU对于
多核系统是无法起到有效的互斥的...所以一般都是配合"自转锁"使用
关于自转锁的话题我们下面会详细的说的


2.通过内存描述表(MDL)中描述一块内存区域,MDL包含此内存区域的起始地址,
拥有者进程,字节数量以及标志.(据说是Bill提供的方法.难道和VirtualProtect一样?)

代码:
type
PMDL=^_MDL;
_MDL=packedrecord
NextMDL;
Size:USHORT;
MdlFlags:USHORT;
Processointer;
MappedSystemVaVOID;
StartVaVOID;
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大叔)
functionWriteReadOnlyMemoryGates(lpDest,lpSourceointer;Length:Integer):

NTSTATUS;
var
tempSpinLock:KSPIN_LOCK;
oldirql:KIRQL;
mdlMDL;
writableAddressointer;
begin
Result:=STATUS_UNSUCCESSFUL;
mdl:=MmCreateMdl(nil,lpDest,Length);
if(mdl<>nil)then
begin
MmBuildMdlForNonPagedPool(mdl);
mdl^.MdlFlags:=mdl^.MdlFlagsorMDL_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;
[/code]

关键一步就是修改mdl的属性.让他可写....

下面来看看第三种方法
3.使用IoAllocateMdl来得到可写内存...
源于Mark早期写的代码...我这里直接翻译了...至于原理
大家参考一下DDK吧...我发现自己表达能力有限...

代码:
//写只读内存(源于Mark代码)
functionWriteReadOnlyMemoryMark(lpDest,lpSourceointer;Length:Integer):

NTSTATUS;
var
tempSpinLock:KSPIN_LOCK;
oldirql:KIRQL;
mdlMDL;
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;
[/code]

代码源于Mark早期编写的代码
相信看到这份代码和上面的第二种方法相信大家似乎都明白了什么...
是不是觉得两种方法都差不多区别就是在于
一个是直接修改MDL属性:MDL_MAPPED_TO_SYSTEM_VA
一个是MmProbeAndLockPages(mdl,KernelMode,IoWriteAccess);
来改写属性...其实这两种方法...嘿嘿~自己看看就明白了...

[2]改写内存!
平时我们修改内存单核下没有什么顾虑.stl|代码|cli
开关中断以后就可以自己操作了...
因为现在双核和多核越来越兴起了...
这么说吧.内存在没有完全修改完毕的时候...其他线程去读取的话...
就会造成读取错误的数据或者断码...
如果您是单核的话可以直接修改
1.stl+cli

代码:
asm
cli//disableWPbit
pusheax
moveax,cr0//moveCR0register

intoEAX
andeax,not000010000h//disableWPbit
movcr0,eax//writeregisterback
popeax
end;

//改写的代码

asm
pusheax//enableWPbit
moveax,cr0//moveCR0register

intoEAX
oreax,000010000h//enableWPbit
movcr0,eax//writeregister

back
popeax
sti
end;
[/code]

2.利用原子互斥操作
比如说我们需要SSDT,Shadow,etcHook
修改地址等操作的话...我们有一个简单的方法
使用...Interlocked系函数
修改地址推荐使用InterlockedExchange...
释放方法很简单

代码:
ZwOpenProcessNextHook:=TZwOpenProcess(InterlockedExchange(SystemServiceName

(GetImportFunAddr(@ZwOpenProcess)),LONG(@ZwOpenProcessNextHook)));
[/code]

提示一下这个函数使用的是fastcall调用方式,如果你使用的是Delphi的话建议你最好
注意一下使用方法...fastcall和Delphi的register的是有区别的
fastcall:
第一个参数:edx
第二个参数:ecx
register:
第一个参数:eax
第二个参数:edx
第三个参数:ecx

提供以下InterlockedExchange的源代码

代码:
FORCEINLINE
LONG
FASTCALL
InterlockedExchange(
INOUTLONGvolatile*Target,
INLONGValue
)
{
__asm{
moveax,Value
movecx,Target
xchg[ecx],eax
}
}
[/code]

3.自转锁
自转锁是内核中的一种同步机制...
关于自转锁的介绍请大家去看
<<rogrammingtheMicrosoftWindowsDriverModel>>这本书...
这本书刚开始看许多东西暂时还不太理解...
当线程获取自转锁的时候...会将当前线程提升到DISPATCH_LEVEL级别
这个时候内核将不会分配时间片给其他的CPU.这样就不会有其他线程
在你写操作的时候去访问你没有写完的地方了...放心大胆的写了...
怎么写?晕...这个问题这个问题...呵呵我可以不回答吗?memcpy总会用吧...

自转锁使用起来很简单...
(有个疑问.为什么获取和释放自转锁的函数在hal.dll函数中,初始化自转锁的函数
却在ntoskrnl.exe模块中...)

使用方法:
KeInitializeSpinLock(@TemSpinLockp);
KeAcquireSpinLock(@TemSpinLockp,&amp;oldirql);
.
.
你的操作代码
.
.
KeReleaseSpinLock(@TemSpinLockp,oldirql);
如果有其他内核线程已经获取自转锁的时候你的线程是不会被分配到时间片的
所以你不用担心你会进去破坏代码(除非~除非~你提升到更高的级别...)
当你获取自转锁成功后..其他线程将进入自转状态..你可以理解为互斥
(EnterCriticalSection,LeaveCriticalSection)
[警告]请千万不要连续使用两次KeAcquireSpinLock!否则机器会(卡)死的很难看的

etc...
其他方法暂时不清楚.暂时由于自转锁的这种特殊效果.
所以大家能不用的时候尽量不要使用...使用的时候尽量减少自转时间.以达到更高的
效能...

小弟初学驱动编程.如果有不对的地方请各位大牛补充说明...这里拜过

特别感谢:
<<如何写windows系统已保护的内存区域>>的作者.由于被转载了不知道多少次
当我看到这篇文章的时候...作者已经不知道是谁了...感谢这位无名英雄...谢谢
鸣谢:
zhuwg,FlowerCode,农夫大哥,冷风(烤鸭),旋转"AV"群里的各位神牛们

希望感兴趣的DelphiFans可以PM我大家一起交流一下开发经验...*转载请注明来自看雪论坛@PEdiy.com
点击按钮快速添加回复内容: 支持 高兴 激动 给力 加油 苦寻 生气 回帖 路过 感恩
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

小黑屋|手机版|Archiver|看流星社区 |网站地图

GMT+8, 2024-3-19 10:56

Powered by Kanliuxing X3.4

© 2010-2019 kanliuxing.com

快速回复 返回顶部 返回列表