首页
社区
课程
招聘
[原创]跨进程内存读取, 附源码
发表于: 2009-6-26 21:24 52861

[原创]跨进程内存读取, 附源码

2009-6-26 21:24
52861
在科锐学习的第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的内存页



页目录项的第7位判断页大小
// 获得页大小
DWORD CReadMemoryDlg::GetPageSizeNoPAE(DWORD dwAddr)
{
    if ((dwAddr & 0x00000080) == 0x00000080)
    {
        return MBSIZE;
    }
    else
    {
        return KBSIZE;
    }
}


4KB时, 页目录项的高20位为页表基地址.线性地址的12位到21位作为选取页表的一个表项.(PTE)


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位在物理地址在页中的偏移


// 线性地址的低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位的页目录指针表基地址



通过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位为页目录基地址


// 页目录基地址
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位作为在页表中的偏移



// 获得页表
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位做为物理地址在页中的偏移


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;
}   


所得到的内容就是目标进程虚拟地址内存的数据

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

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
免费 7
支持
分享
最新回复 (74)
雪    币: 342
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
写得好学习一下
2009-6-27 13:54
0
雪    币: 339
活跃值: (72)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
3
很强,支持源码附带。
2009-6-27 14:11
0
雪    币: 213
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
很不错,下载学习,多谢楼主分享。
2009-6-27 17:40
0
雪    币: 2586
活跃值: (920)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
5
支持楼主分享
2009-6-27 17:42
0
雪    币: 116
活跃值: (41)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
6
果真是瞌睡就有人送枕头 谢过LZ
2009-6-27 18:11
0
雪    币: 63
活跃值: (17)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
7
顶一下 有源代码看看
2009-6-27 21:40
0
雪    币: 226
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
谢谢,支持一下.
2009-6-27 21:53
0
雪    币: 264
活跃值: (11)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
9
没仔细看 是指可以抛弃ReadProcessMemory, WriteProcessMemory了么?
2009-6-27 22:04
0
雪    币: 95
活跃值: (11)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
10
是这样的,,,
2009-6-27 23:20
0
雪    币: 1025
活跃值: (225)
能力值: ( LV12,RANK:310 )
在线值:
发帖
回帖
粉丝
11
好文章 顶之
2009-6-27 23:22
0
雪    币: 205
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
不错,好文,学习
2009-6-27 23:41
0
雪    币: 25
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
呵呵 不错 是我学习的对象, ,哎 现在还看不懂 我才第二阶段,学完了希望能像你一样强!学习
2009-6-28 01:20
0
雪    币: 522
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
14
好复杂呀...      直接 KeStackAttachProcess目标  不是很方便吗?
2009-6-28 06:24
0
雪    币: 107
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
学习下,楼主你辛苦啦
2009-6-28 07:22
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
16

如果KeStackAttachProcess被XX了呢?光图方便可是不行的~
2009-6-28 07:39
0
雪    币: 451
活跃值: (78)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
17
进来学校1下PAE模式
2009-6-28 08:36
0
雪    币: 290
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
好贴啊,占个位置仔细膜拜
2009-6-28 09:42
0
雪    币: 62
活跃值: (72)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
19
好文,顶之
2009-6-28 15:01
0
雪    币: 260
活跃值: (18)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
20
占位,方便查阅!
2009-6-28 16:29
0
雪    币: 308
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
21
强大!!!!!
2009-6-29 00:14
0
雪    币: 25
活跃值: (477)
能力值: (RANK:20 )
在线值:
发帖
回帖
粉丝
22
????????????????????????????
2009-6-29 00:24
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
看不懂..头
2009-6-29 00:29
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
不错的好文章,直接跳到内存分页去读取内存,好思路啊,学习学习
2009-6-29 15:23
0
雪    币: 1373
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
有源码,练习一下。
2009-6-29 15:59
0
游客
登录 | 注册 方可回帖
返回
//