跨进程内存读取, 附源码

来源:互联网 发布:淘宝快递运费价格表 编辑:程序博客网 时间:2024/06/05 20:30
【原创】跨进程内存读取, 附源码
作 者: StNOe
时 间: 2009-06-26,21:24:18
链 接: http://bbs.pediy.com/showthread.php?t=92353

在科锐学习的第4阶段, 保护模式课后作业,跨进程内存读写的小工具.

第一次内核编程,代码写的比较乱,不免也有些错误.各位牛哥们,见笑乐,!时间仓促,也没对代码进程封装,和界面相关太多.
1.  切换目标进程的CR3
      通常,跨进程读写内存,用到ReadProcessMemory, WriteProcessMemory, 但需要进程句柄,如果目标进程受到保护,可能获得进程句柄会失败.
      ReadProcessMemory最后会调用到KeStackAttachProcess附加到目标进程上切换进程环境进行拷贝的, 所以想到拿到目标进程的虚拟内存内容,可以将目标进程的页目录基地址放入CR3中即可.

首先要获得目标进程的cr3寄存器,即页目录基地址(开启PAE, 页目录指针表),
每个进程在内核里都有一个EPROCESS结构.
代码:
nt!_EPROCESS   +0x000 Pcb              : _KPROCESS   +0x06c ProcessLock      : _EX_PUSH_LOCK   +0x070 CreateTime       : _LARGE_INTEGER   +0x078 ExitTime         : _LARGE_INTEGER   +0x080 RundownProtect   : _EX_RUNDOWN_REF   …….
Pcb中就有我们想要得到的CR3
代码:
nt!_KPROCESS   +0x000 Header           : _DISPATCHER_HEADER   +0x010 ProfileListHead  : _LIST_ENTRY   +0x018 DirectoryTableBase : [2] Uint4B   +0x020 LdtDescriptor    : _KGDTENTRY   +0x028 Int21Descriptor  : _KIDTENTRY   ………
那只需要获得目标进程EPROCESS就可以得到CR3了
遍历EPROCESS里的ActiveProcessLinks 的链表获取指定进程的EPROCESS

代码:
// 获得当前进程EPROCESS信息            ULONG uEprocess = 0;            __asm            {                mov eax, fs:[0x124]    // _ethread                mov eax, [eax+0x44]    // _kprocess                mov uEprocess, eax            }            KdPrint(("EPROCESS: 0x%08x\n", uEprocess));            LIST_ENTRY ListHead;            InitializeListHead(&ListHead);            ULONG uFirstEprocess = uEprocess;            ULONG uCount = 0;            PLIST_ENTRY pActiveProcessLinks;            ProcessInfoList *pProcssList = NULL;            ULONG uNameOffset = GetPlantformDependentInfo(FILE_NAME_OFFSET);            ULONG uPidOffset = GetPlantformDependentInfo(PROCESS_ID_OFFSET);            ULONG uLinkOffset = GetPlantformDependentInfo(PROCESS_LINK_OFFSET);            ULONG uExitTime = GetPlantformDependentInfo(EXIT_TIME_OFFSET);            // 遍历链表获得进程信息            do             {                                pProcssList=                     (ProcessInfoList *)ExAllocatePool(PagedPool, sizeof(ProcessInfoList));                if (pProcssList == NULL)                {                    status = STATUS_INSUFFICIENT_RESOURCES;              break;                }                PLARGE_INTEGER ExitTime;                ExitTime = (PLARGE_INTEGER)(uEprocess + uExitTime);                if (ExitTime->QuadPart == 0)                {                    if (*(int *)(uEprocess + uPidOffset) <= 0)                    {                        pProcssList->ProcInfo.uProcessId = 0;                        pProcssList->ProcInfo.uEprocess = uEprocess;                        pProcssList->ProcInfo.uCR3 = *(PULONG)(uEprocess + 0x18);                        RtlCopyMemory(pProcssList->ProcInfo.pszImageFileName, "Idle", 16);                        InsertHeadList(&ListHead, &pProcssList->ListEntry);                        KdPrint(("PID: %d, EPROCESS: 0x%08x, FileName: %s, CR3: 0x%08x\n",                            pProcssList->ProcInfo.uProcessId,                            pProcssList->ProcInfo.uEprocess,                            pProcssList->ProcInfo.pszImageFileName,                            pProcssList->ProcInfo.uCR3));                    }                    else                    {                        pProcssList->ProcInfo.uEprocess = uEprocess;                        pProcssList->ProcInfo.uCR3 = *(PULONG)(uEprocess + 0x18);                        pProcssList->ProcInfo.uProcessId = *(PULONG)(uEprocess + uPidOffset);                        RtlCopyMemory(pProcssList->ProcInfo.pszImageFileName,                             (PVOID)(uEprocess + uNameOffset),                             16);                        InsertHeadList(&ListHead, &pProcssList->ListEntry);                        KdPrint(("PID: %d, EPROCESS: 0x%08x, FileName: %s, CR3:  0x%08x\n",                            pProcssList->ProcInfo.uProcessId,                            pProcssList->ProcInfo.uEprocess,                            pProcssList->ProcInfo.pszImageFileName,                            pProcssList->ProcInfo.uCR3));                    }                    uCount++;                }                pActiveProcessLinks = (PLIST_ENTRY)(uEprocess + uLinkOffset);                uEprocess = (ULONG)pActiveProcessLinks->Blink - uLinkOffset;                if (uEprocess == uFirstEprocess)                {                    break;                }            } while (uEprocess != 0);
   引用了北极星2003大哥的GetPlantformDependentInfo 获取EPROCESS的成员偏移
 
   下面是读写内存的:
代码:
            _try            {                WriteMemoryInfo *pInfo =                     (WriteMemoryInfo *)ExAllocatePool(PagedPool, sizeof(WriteMemoryInfo));                RtlCopyMemory(pInfo, pIoBuffer, sizeof(WriteMemoryInfo));                PVOID pWrite =  ExAllocatePool(PagedPool, pInfo->nWriteSize);                RtlCopyMemory(pWrite, pInfo->pData, pInfo->nWriteSize);                //pInfo->pData = (PBYTE)ExAllocatePool(PagedPool, pInfo->nWriteSize);                ULONG uOldCr3 = 0;                ULONG uCurrentCr3 = *(PULONG)(pInfo->nEprocess + 0x18);                if (pInfo->nMemoryAddr == 0)                {                    status = STATUS_UNSUCCESSFUL;                    break;                }                __asm                {                    mov eax, cr3                    mov uOldCr3, eax                    mov eax, uCurrentCr3                    mov cr3, eax                }                __asm                {                    cli                    push eax                    mov eax, cr0                    and eax, not 10000h                    mov cr0, eax                }                RtlCopyMemory((PVOID)pInfo->nMemoryAddr,                     pWrite, pInfo->nWriteSize);                __asm                {                    mov eax, CR0                    or eax, 10000h                    mov cr0,eax                    pop eax                    sti                }                __asm                {                    mov eax, uOldCr3                    mov cr3, eax                }                uOutSize = pInfo->nWriteSize;                if (pInfo != NULL)                {                    ExFreePool(pInfo);                    pInfo = NULL;                }                // Return success                status = STATUS_SUCCESS;            }            __except(1)            {                status = STATUS_UNSUCCESSFUL;            }
代码:
            __try            {                   ReadMemoryInfo *pInfo =                     (ReadMemoryInfo *)ExAllocatePool(PagedPool, sizeof(ReadMemoryInfo));                RtlCopyMemory(pInfo, pIoBuffer, sizeof(ReadMemoryInfo));                ULONG uOldCr3 = 0;                ULONG uCurrentCr3 = *(PULONG)(pInfo->nEprocess + 0x18);                if (pInfo->nMemoryAddr == 0)                {                    status = STATUS_UNSUCCESSFUL;                    break;                }                __asm                {                    mov eax, cr3                    mov uOldCr3, eax                    mov eax, uCurrentCr3                    mov cr3, eax                }                RtlCopyMemory(pIoBuffer,                     (PVOID)pInfo->nMemoryAddr ,pInfo->nReadSize);                uOutSize = pInfo->nReadSize;                __asm                {                    mov eax, uOldCr3                    mov cr3, eax                }                if (pInfo != NULL)                {                    ExFreePool(pInfo);                    pInfo = NULL;                }                // Return success                status = STATUS_SUCCESS;            }            __except(1)            {                status = STATUS_UNSUCCESSFUL;            }
2. 根据分页机制,进行手工转换,得到虚拟地址的映射的物理地址,读其物理地址得到目标进程虚拟地址的内容.详见附件分析.

获得了CR3,,接下来,就是根据分页机制,把虚拟地址转换为物理地址勒.
在转换之前要判断是否开启PAE,非PAE和开启PAE的转换有所不同
控制寄存器CR4的第5位标记是否开启PAE
代码:
// 获得CR4的值__asm{      _emit 0x0F       _emit 0x20      _emit 0xE0      mov uCR4, eax}
未开启PAE情况
通过CR3寄存器定位到页目录的基地址
线性地址的高10位作为获取页目录表项的索引, 获得一个页目录的一个表项
注: windows的保护实现基本不使用分段机制,主要是通过分页机制来实现保护,这里的就线性地址等于虚拟地址.
代码:
// 获得页目录表项(PDE)dwPageDirIndex = (dwVirtualAddr & 0xffc00000) >> 22;DWORD dwPageDirEntry = ReadPageDirEntryNoPAE(dwPageDirIndex);if (dwPageDirEntry == 0){    return;}

根据PDE获得页表基地址或者页基地址
当没有开启PAE时,有两种PDE格式, 分别指向4KB的页表,和4MB的内存页
名称:  1.jpg查看次数: 3694文件大小:  19.3 KB
名称:  819373657206152704.jpg查看次数: 3692文件大小:  17.4 KB

页目录项的第7位判断页大小
代码:
// 获得页大小DWORD CReadMemoryDlg::GetPageSizeNoPAE(DWORD dwAddr){    if ((dwAddr & 0x00000080) == 0x00000080)    {        return MBSIZE;    }    else    {        return KBSIZE;    }}
4KB时, 页目录项的高20位为页表基地址.线性地址的12位到21位作为选取页表的一个表项.(PTE)
名称:  3.jpg查看次数: 3693文件大小:  42.9 KB

代码:
dwPageTableIndex = (dwVirtualAddr & 0x0003ff000) >> 12;DWORD dwPageTableBaseAddr = dwPageDirEntry & 0xfffff000;// 获得页表DWORD dwPageTable = ReadPageTableNoPAE(dwPageTableBaseAddr,                         dwPageTableIndex);if (dwPageTable == 0){     return;}if (IsPresentNoPAE(dwPageTable) == FALSE){     return;}
取PTE的高20位,作为内存页的基地址,线性地址的低12作为页中偏移得到物理地址
代码:
// 页的基地址DWORD dwPageBaseAddr = dwPageTable & 0xfffff000;dwPageOffset = dwVirtualAddr & 0x00000fff;
这时获得的物理地址就是想要的目标进程虚拟地址的映射的物理地址
代码:
PVOID pReadBuf = new BYTE[dwReadSize];                BOOL bRet = ReadPageMemoryNoPAE(pReadBuf,                     dwPageOffset,                     dwReadSize,                     dwPageBaseAddr);if (bRet == FALSE){      return;}
4MB时,页目录项的高10位作为页的基地址, 线性地址的低22位在物理地址在页中的偏移
名称:  4.jpg查看次数: 3681文件大小:  24.3 KB


代码:
// 线性地址的低22位是页内偏移DWORD dwPageOffsetMB = dwVirtualAddr & 0x003fffff;// 高10位,是页基地址DWORD dwPageBaseAddr = dwPageDirEntry & 0xffc00000;
这时获得的物理地址就是想要的目标进程虚拟地址的映射的物理地址
代码:
PVOID pReadBuf = new BYTE[dwReadSize];BOOL bRet = ReadPageMemoryPAE(pReadBuf,                     dwPageOffsetMB,                     dwReadSize,                     dwPageBaseAddr);if (bRet == FALSE){    return;}

开启PAE情况
CR4中的物理地址扩展(PAE)标志可以开启PAE机制,将物理地址从32位扩展到36位.
当开始PAE机制后,处理器支持两种尺寸的页:4KB和2Mb的页.
增加了页目录指针表项.
表项的大小从32位增加到了64位
表项中的物理基地址扩展到了24位
寄存器CR3中的高位页目录基地址被换为27位的页目录指针表基地址

名称:  5.jpg查看次数: 3668文件大小:  9.4 KB

通过CR3寄存器定位到页目录指针表起始地址,取线性地址的高2位作为选取页目录指针表项的索引
代码:
DWORD dwDirPointerTableIndex = (dwVirtualAddr & 0xc0000000) >> 30;DWORD dwDirPointerTableBaseAddr = m_nDirBase & 0xffffffe0;// 获得页目录指针表__int64 nPageDirPointerTable = ReadPageDirPAE(dwDirPointerTableIndex,                     dwDirPointerTableBaseAddr);if (nPageDir == 0){       return;}
取线性地址的第21位到29位作为页目录索引,页目录指针表项的第12位到第35位为页目录基地址
名称:  6.jpg查看次数: 3666文件大小:  10.7 KB

代码:
// 页目录基地址DWORD dwDirBaseAddr = (DWORD)(nPageDirPointerTable &0x0000000ffffff000);// 页目录项索引DWORD dwPageDirIndex = (dwVirtualAddr & 0x3fe00000) >> 21;__int64 nPageDirEntry = ReadPageDirEntryPAE(dwPageDirIndex, dwDirBaseAddr);if (nPageDirEntry == 0){      return;}
根据页目录项的第7位判断页大小,2MB,还是4KB
代码:
// 获得页大小DWORD CReadMemoryDlg::GetPageSizePAE(__int64 nAddr){    if ((nAddr & 0x0000000000000080) == 0x0000000000000080)    {        return MBSIZE;    }    else    {        return KBSIZE;    }}
4KB时,页目录项的第12位到35位作为页表基地址的高24位.取线性地址第12位到20位作为在页表中的偏移

名称:  7.jpg查看次数: 3679文件大小:  29.5 KB

代码:
// 获得页表DWORD dwPageTableBaseAddr = (DWORD)(nPageDirEntry & 0x0000000ffffff000);DWORD dwPageTableIndex = (dwVirtualAddr & 0x001ff000) >> 12;__int64 nPageTable = ReadPageTablePAE(dwPageTableIndex, dwPageTableBaseAddr);if (nPageTable == 0){     return;}
通过页表项的第12位到第35位作为页基地址的高24位,线性地址的低12为作为在页中的偏移
代码:
// 读取页内容DWORD dwPageBaseAddr = (DWORD)(nPageTable & 0x0000000ffffff000);DWORD dwPageOffsetKb = dwVirtualAddr & 0x00000fff;PVOID pReadBuf = new BYTE[dwReadSize];BOOL bRet = ReadPageMemoryPAE(pReadBuf,                     dwPageOffsetKb,                     dwReadSize,                     dwPageBaseAddr);if (bRet == FALSE){     return;}
所得到的内容就是目标进程虚拟地址内存的数据

2MB时, 页目录项的第21位到35位作为页的基地址的高位,取线性地址的第0位到第20位做为物理地址在页中的偏移
名称:  8.jpg查看次数: 3668文件大小:  25.4 KB

代码:
DWORD dwPageOffsetMB = dwVirtualAddr & 0x000fffff;// 高-35位DWORD dwDirBaseAddr = (DWORD)(nPageDirEntry & 0x00000007fff00000);PVOID pReadBuf = new BYTE[dwReadSize];BOOL bRet = ReadPageMemoryPAE(pReadBuf,                     dwPageOffsetMB,                     dwReadSize,                     dwDirBaseAddr);if (bRet == FALSE){return;}   
所得到的内容就是目标进程虚拟地址内存的数据

     这里谢谢钱老师,这近一年的培养,祝钱老师的培训越办越好,!

原创粉丝点击