ReadProcessMemory函数的分析
2009-08-27 10:10
357 查看
ReadProcessMemory函数用于读取其他进程的数据。我们知道自远古时代结束后,user模式下的进程都有自己的地址空间,进程与进程
间互不干扰,这叫私有财产神圣不可侵犯。但windows里还真就提供了那么一个机制,让你可以合法的获取别人的私有财产,这就是
ReadProcessMemory和WriteProcessMemory。为什么一个进程居然可以访问另一个进程的地址空间呢?因为独立的只是低2G
的用户态空间,高2G的内核态空间是所有进程共享的。一段执行中的线程进入内核态后,它可以拿到别人的cr3寄存器,用该cr3替换自己的cr3便完成了
地址空间的转换。理论说明完毕,下面来看实现细节:gussing.cnblogs.com
间互不干扰,这叫私有财产神圣不可侵犯。但windows里还真就提供了那么一个机制,让你可以合法的获取别人的私有财产,这就是
ReadProcessMemory和WriteProcessMemory。为什么一个进程居然可以访问另一个进程的地址空间呢?因为独立的只是低2G
的用户态空间,高2G的内核态空间是所有进程共享的。一段执行中的线程进入内核态后,它可以拿到别人的cr3寄存器,用该cr3替换自己的cr3便完成了
地址空间的转换。理论说明完毕,下面来看实现细节:gussing.cnblogs.com
BOOL STDCALL ReadProcessMemory( HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead ) { NTSTATUSStatus; Status=NtReadVirtualMemory(hProcess,(PVOID)lpBaseAddress,lpBuffer,nSize, (PULONG)lpNumberOfBytesRead ); if (!NT_SUCCESS(Status)) { SetLastErrorByStatus(Status); return FALSE; } return TRUE; }
这是用户态ReadProcessMemory的实现,它只做了一件事那就是调用NtReadVirtualMemory。NtReadVirtualMemory函数位于ntdll中,属于所谓的桩函数,
作用就是把用户态的函数调用翻译成相应的系统调用,进入内核态。内核中一般有一个相同名字的处理函数,接收到该类型的系统调用后做实际的工作。系统调用
的细节按下不表,让我们来看NtReadVirtualMemory到底在做什么事情:gussing.cnblogs.com
NTSTATUSSTDCALL
NtReadVirtualMemory(INHANDLEProcessHandle,
INPVOIDBaseAddress,
OUTPVOIDBuffer,
INULONGNumberOfBytesToRead,
OUTPULONGNumberOfBytesRead)
{
NTSTATUSStatus;
PMDLMdl;
PVOIDSystemAddress;
PEPROCESSProcess;
DPRINT("NtReadVirtualMemory(ProcessHandle%x,BaseAddress%x,
"
"Buffer%x,NumberOfBytesToRead%d)/n
",ProcessHandle,BaseAddress,
Buffer,NumberOfBytesToRead);
Status=ObReferenceObjectByHandle(ProcessHandle,
PROCESS_VM_WRITE,
NULL,
UserMode,
(PVOID*)(&Process),
NULL);
if
(Status!=STATUS_SUCCESS)
{
return
(Status);
}
ObReferenceObjectByHandle函数从代表目标进程的handle里获取EPROCESS类型的指针,存放在变量Process中。EPROCESS结构保存了能代表一个进程的
几乎所有关键数据,包括我们这里急需的cr3。gussing.cnblogs.com
struct
_EPROCESS
{
/*Microkernelspecificprocessstate.*/
KPROCESSPcb;/*000*/
。。。/*其他*/
typedef
struct
_KPROCESS
{
/*Soit'spossibletowaitfortheprocesstoterminate*/
DISPATCHER_HEADER DispatcherHeader;/*000*/
/*
*Presumablyalistofprofileobjectsassociatedwiththisprocess,
*currentlyunused.
*/
LIST_ENTRYProfileListHead;/*010*/
/*
*Weusethefirstmemberofthisarraytoholdthephysicaladdressof
*thepagedirectoryforthisprocess.
*/
PHYSICAL_ADDRESSDirectoryTableBase;/*018这是cr3*/
。。。/*其他*/
接下来是从目标地址里创建一个MDL并将其锁定在主存里: gussing.cnblogs.com
Mdl=MmCreateMdl(NULL,
Buffer,
NumberOfBytesToRead);
MmProbeAndLockPages(Mdl,
UserMode,
IoWriteAccess);
为什么要创建这个MDL?等会儿再说。
然后是最关键的一步,当前线程要当逃兵,叛逃至目标进程里了。。。gussing.cnblogs.com
KeAttachProcess(Process);
执行完KeAttachProcess后,当前线程就成了Process进程所属的线程了,悲剧啊。怎么着咱们就被策反了呢?细节我们等下再看,让我们完成主逻辑先。gussing.cnblogs.com
SystemAddress=MmGetSystemAddressForMdl(Mdl);
memcpy(SystemAddress,BaseAddress,NumberOfBytesToRead);
KeDetachProcess();
if
(Mdl->MappedSystemVa!=NULL)
{
MmUnmapLockedPages(Mdl->MappedSystemVa,Mdl);
}
MmUnlockPages(Mdl);
ExFreePool(Mdl);
ObDereferenceObject(Process);
*NumberOfBytesRead=NumberOfBytesToRead;
return
(STATUS_SUCCESS);
}
attach到目标进程里之后,我们又从之前生成好的MDL里获取一个虚拟地址映射,然后执行memcpy操作。这下为什么要创建MDL的秘密就清楚了,假如我们直接这样
写memcpy:gussing.cnblogs.com
memcpy(Buffer,BaseAddress,NumberOfBytesToRead);
看着好像没什么问题,其实问题很大。Buffer所代表的地址应该是前一个进程空间里的,但现在确实新进程空间里的,根本不是一回事。我们费劲拷贝
过去的数据,其实位于错误的内存里,等KeDetachProcess执行完切回原来的进程空间后,这些数据就全丢了,找都没地方找去。所以我们应该先从Buffer里
生成一个MDL,切换进程完成后再从该MDL里反生成一个VirtualAddress,然后memcpy就可以正确的将数据拷贝到该去的地方了。
完成内存拷贝后,KeDetachProcess函数又将我们的线程从Process进程转回原来的进程,这下好,数据也偷到了,组织也回归了,原来这家伙是个间谍啊。。。
现在我们可以来看看KeAttachProcess函数到底做了什么事情了。核心行为很明确,那就是替换cr3,但是细节到底如何呢:gussing.cnblogs.com
VOIDSTDCALL
KeAttachProcess(PEPROCESSProcess)
{
KIRQLoldlvl;
PETHREADCurrentThread;
PULONGAttachedProcessPageDir;
ULONGPageDir;
DPRINT("KeAttachProcess(Process%x)/n
",Process);
CurrentThread=PsGetCurrentThread();
if
(CurrentThread->OldProcess!=NULL)
{
DbgPrint("Invalidattach(threadisalreadyattached)/n
");
KEBUGCHECK(0);
}
KeRaiseIrql(DISPATCH_LEVEL,&oldlvl);
KiSwapApcEnvironment(&CurrentThread->Tcb,&Process->Pcb);
这里我们把当前的IRQL提升到了DPClevel,为的就是防止线程切换。然后调用KiSwapApcEnvironment把当前的apc队列也贴到目标进程里,按下不表。gussing.cnblogs.com
/*Thestackofthecurrentprocessmaybelocatedinapagewhichis
notpresentinthepagedirectoryoftheprocesswe'reattachingto.
Thatwouldleadtoapagefaultwhenthisfunctionreturns.However,
sincetheprocessorcan'tcallthepagefaulthandler'causeitcan't
pushEIPonthestack,thiswillshowupasastackfaultwhichwill
crashtheentiresystem.
Topreventthis,makesurethepagedirectoryoftheprocesswe're
attachingtoisup-to-date.*/
AttachedProcessPageDir=ExAllocatePageWithPhysPage(Process->Pcb.DirectoryTableBase);
MmUpdateStackPageDir(AttachedProcessPageDir,&CurrentThread->Tcb);
ExUnmapPage(AttachedProcessPageDir);
接下来如注释所说,Process->Pcb.DirectoryTableBase所代表的数据很有可能正在硬盘里的,物理如何也要保证它在内存里,因为函数返回时要做栈操作,
如果Process->Pcb.DirectoryTableBase在硬盘上,栈操作就会引起pagefault,而处理pagefault前又必须要pusheip,悲剧就要发生了。同样的,
stackbase和stacktop这两哥们也一定得在内存里,MmUpdateStackPageDir做的就是这个事情。gussing.cnblogs.com
CurrentThread->OldProcess=PsGetCurrentProcess();
CurrentThread->ThreadsProcess=Process;
PageDir=Process->Pcb.DirectoryTableBase.u.LowPart;
DPRINT("Switchingprocesscontextto%x/n
",PageDir);
Ke386SetPageTableDirectory(PageDir);
KeLowerIrql(oldlvl);
}
最后做的事情就简单了,把当前线程的ThreadsProcess换成新的,再把当前的cr3换成Process->Pcb.DirectoryTableBase.u.LowPart。一番梳妆打扮后,
敌人就分不清咱的身份了。
至此为止,ReadProcessMemory函数分析完毕。个人觉得有几个细节是需要注意的:第一呢,lpBaseAddress和lpBuffer所在的进程空间是不同的。第二呢,
KeRaiseIrql和KeLowerIrql这两个函数一定要限制在进程空间切换的函数内,绝对不能把memcpy放在它们中间,因为KeRaiseIrql之后pagefault就没法处理
了,而memcpy不产生pagefault那是不可能的,想都不要想。gussing.cnblogs.com
相关文章推荐
- ReadProcessMemory函数的分析
- ReadProcessMemory函数的分析
- ReadProcessMemory函数的用法
- ReadProcessMemory与WriteProcessMemory用例分析
- 函数分析AndroidInitProcess分析心得(2)
- 用WriteProcessMemory函数注入进程的流程
- FFMPeg代码分析:av_read_frame()函数的内部构造
- ReadProcessMemory
- Memcached drive_machine 函数分析_process_update_command
- Linux C flie操作: open write read lseek close函数分析
- GetProcessMemoryInfo函数
- ReadProcessMemory
- 用R语言做数据分析(8)——数据的输入与输出之READ函数 3ff8
- [转载] MFC下关于“建立空文档失败”问题的分析二 ---ProcessShellCommand()函数分析
- 进程间通讯-WriteProcessMemory和ReadProcessMemory
- 关于 readn、writen 函数--read返回值分析
- Readprocessmemory用法
- Readprocessmemory使用方法
- 如何利用Win32API取得另一支程式中的ListView內的所有值(RegisterHotKey,ReadProcessMemory,WindowFromPoint和VirtualAllocEx)
- Readprocessmemory使用方法