zoukankan      html  css  js  c++  java
  • windows:重写openprocess函数跨进程读数据

       外挂、木马、病毒等可能需要读取其他进程的数据,windows提供了OpenProcess、ReadProcessMemory等函数。但越是大型的软件,防护做的越好,大概率会做驱动保护,比如hook SSDT表等,这些系统调用都会先被过滤一次,导致返回的数据不是想要的;为了确保能读到目标进程数据,最好重写ReadProcessMemory;要想读取其他进程的内容,思路大概有一下几种:

    •  注册PsSetLoadImageNotifyRoutine函数,其他进程加载模块会时调用我们注册的函数,这个时候已经进入目标进程的空间,可用memcpy复制数据
    •     KeStackAttachProcess可以切换到目标进程,然后用memcpy复制数据,最后调用KeUnstackDetachProcess切换回来
    •     利用进程ID查找EPROCESS,根据名称得到目标EPROCESS后再读取CR3,最用利用目标进程的CR3读取其内存数据;

      前两种方式要调用大家熟知的函数,目的性比较明显,这些函数肯定会被大厂家重点关注,返回的结果可能在逻辑上有误;而第三种方式仅仅根据ID查询EPROCESS,相对前两种更加“人畜无害”,被拦截的概率要小很多,今天详细介绍第三种方式。

      1、为了验证读取数据是否正确,先在notepad随便写一些内容,然后用CE查看地址,如下,后续就选这个地址来测试了;

                

           这里多说几句:CE为了防止被针对,自己重写了关键的内存扫描/读写、进程打开函数,并未使用windows原生自带的系统调用函数;

               

       2、遍历进程,根据ID查找EPROCESS,进而得到进程名、CR3等关键信息,这块也比较简单,如下:

    PEPROCESS LookupProcess(HANDLE Pid)
    {
        PEPROCESS eprocess = NULL;
        NTSTATUS Status = STATUS_UNSUCCESSFUL;
        Status = PsLookupProcessByProcessId(Pid, &eprocess);
        if (NT_SUCCESS(Status))
            return eprocess;
        return NULL;
    }

        因不同版本EPROCESS结构体可能有细微区别,保险起见最好在windbg下通过dt _EPROCESS查看一下CR3的偏移,我这里是0x28,那么CR3的获取代码:

    ULONG64 target_CR3 = *(PULONG64)((ULONG64)eproc + 0x28);

      3、目标CR3都拿到了,接下来就可以读进程数据了,这里的环境是win10 x64,默认开启了PAE;最初的想法很简单,系统用的是9-9-9-9-12分页,那么先把虚拟地址拆分,再用CR3一步一步跟踪不就得到物理地址了吗? 物理地址都有了,再读取数据岂不是探囊取物般简单了么?

         所以刚开始的代码是这样的:(1)先拆分虚拟地址,得到各个层级的偏移  (2)再仿造windbg种逐步计算的方式一步一步得到物理地址;  (3)这里有点要注意:& 和移位的优先级较低,注意使用括号,避免计算的逻辑错误;

        ULONG64 PML4E_index = (virtualAddress >> 39) & 0x1ff;
        ULONG64 PDPE_index = (virtualAddress >> 30) & 0x1ff;
        ULONG64 PDE_index = (virtualAddress >> 21) & 0x1ff;
        ULONG64 PTE_index = (virtualAddress >> 12) & 0x1ff;
        ULONG64 physical_offset = virtualAddress & 0xfff;
        ULONG64 PML4E = CR3 + (PML4E_index << 3);
        ULONG64 PDPE = (*(PULONG64)PML4E & 0x00000007fffff000) + (PDPE_index << 3);//上一级table entry的12~35位提供下一级table物理基地址的高24位,此时36~51是保留位,必须置0,低12位补零
        ULONG64 PDE = (*(PULONG64)PDPE & 0x00000007fffff000) + (PDE_index << 3);
        ULONG64 PTE = (*(PULONG64)PDE & 0x00000007fffff000) + (PTE_index << 3);
        ULONG64 physicalAddress = (*(PULONG64)PTE & 0x00000007fffff000) + physical_offset;

       然而一运行就蓝屏,通过下断点逐步跟踪,发现罪魁祸首在这行:ULONG64 PDPE = (*(PULONG64)PML4E & 0x00000007fffff000) + (PDPE_index << 3)

       这样代码做的运算有好几个,为了彻查到底是哪个运算导致的蓝屏,继续把代码拆分地更细,最终发现导致蓝屏的真凶:*(PULONG64)PML4E;

         这行代码本身很简单,就是把PML4E转换成指针,再读取其指向的内容,这么简单的操作,为啥会导致蓝屏了?这就牵扯到win10 x64下用户态CR3和内核态CR3的区别了;本案例用的是驱动,在0环内核态执行,自然用的是内核态CR3。但代码读取数据的虚拟地址明显是3环的,属于用户态。不同的CR3会映射到不同的物理地址,最终出错。问题找到了,怎么解决了?既然是CR3不对,那么读取内存之前先切换一下不就行了? 

              由于vs2019对于x64的程序不允许内联汇编,这里单独新建一个asm文件来切换CR3,如下:

    _swapCR3 PROC
            mov cr3,rcx;
            ret
    _swapCR3 Endp

      然后继续单步调试,切换CR3的函数执行完后,CR3成功更改:

           

       但是在执行*(PULONG64)PML4E前查看时CR3又变回了内核态CR3:

            

       继续执行不出意外又蓝屏:

            

        为什么函数 _swapCR3 执行完后,CR3又变回了内核态?经过多次反复尝试,发现都是断点惹的祸:代码里面设置了断点,执行到断点时会被系统进程接管,然后CR3自然就切换;

      既然ret后CR3又被改回,那么只能在汇编代码里面读取虚拟地址的内容了,这次重新写一个_ReadVirtualMemory代码,如下(这里直接使用mov rax,[rdx],还省去了繁琐的地址转换,这部分直接让cpu自动做了):

    _ReadVirtualMemory PROC
            push rax;
            push rbx;
            mov rbx,cr3;保存旧的CR3
            mov cr3,rcx;
            mov rax,[rdx];第二个参数,是VirtualMemory
            mov data,rax
            mov cr3,rbx;还原旧的CR3
            pop rbx;
            pop rax;
            ret
    _ReadVirtualMemory Endp

      在这里读的数据终于对了,效果如下:0x7ffd350ca309 地址前2个字节的内容确实是CC,和通过CE查找到的一致;

           

       完整代码如下:主文件

    #include "ReadProcessMemory.h"
    
    ULONG64 data;
    
    // 根据进程ID返回进程EPROCESS结构体,失败返回NULL
    PEPROCESS LookupProcess(HANDLE Pid)
    {
        PEPROCESS eprocess = NULL;
        NTSTATUS Status = STATUS_UNSUCCESSFUL;
        Status = PsLookupProcessByProcessId(Pid, &eprocess);
        if (NT_SUCCESS(Status))
            return eprocess;
        return NULL;
    }
    
    VOID EnumProcess()
    {
        PEPROCESS eproc = NULL;
        for (int temp = 0; temp < 100000; temp += 4)
        {
            eproc = LookupProcess((HANDLE)temp);
            if (eproc != NULL )
            {
                if (!strcmp(PsGetProcessImageFileName(eproc),"notepad.exe"))
                {
                    DbgPrint("进程名: %s --> 进程PID = %d --> 父进程PPID = %d -->EPROCESS地址:%p 
    ", PsGetProcessImageFileName(eproc), PsGetProcessId(eproc),
                        PsGetProcessInheritedFromUniqueProcessId(eproc), eproc);
                    ULONG64 target_CR3 = *(PULONG64)((ULONG64)eproc + 0x28);
                    KdBreakPoint();
                    _ReadVirtualMemory(target_CR3, 0x7ffd350ca309);
                    DbgPrint("存储内容:%c 
    ", data);
                    ObDereferenceObject(eproc);
                    return;
                }
                
            }
        }
    }
    
    VOID UnDriver(PDRIVER_OBJECT driver)
    {
        DbgPrint(("Uninstall Driver Is OK 
    "));
    }
    
    NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
    {
        EnumProcess();
        Driver->DriverUnload = UnDriver;
        return STATUS_SUCCESS;
    }

      头文件:

    #ifndef READPROCESSMEMORY_
    #define READPROCESSMEMORY_
    
    #include <ntifs.h>
    
    NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process); //未公开的进行导出即可
    NTKERNELAPI HANDLE PsGetProcessInheritedFromUniqueProcessId(IN PEPROCESS Process);//未公开进行导出
    
    extern void Dbg_Break();
    extern void _swapCR3(ULONG64 target_CR3);
    extern void _ReadVirtualMemory(ULONG64 target_CR3, ULONG64 VirtualMemory);
    
    extern ULONG64 data;
    
    #endif

         asm文件:这里切换CR3的时候为了保险起见,建议先保存旧CR3,用完了再切回去;

    EXTERN data:qword
    
    .code
     
    Dbg_Break Proc
            int 3
            ret
    Dbg_Break Endp
    
    _swapCR3 PROC
            mov cr3,rcx;
            ret
    _swapCR3 Endp
    
    _ReadVirtualMemory PROC
            push rax;
            push rbx;
            mov rbx,cr3;保存旧的CR3
            mov cr3,rcx;
            mov rax,[rdx];第二个参数,是VirtualMemory
            mov data,rax
            mov cr3,rbx;还原旧的CR3
            pop rbx;
            pop rax;
            ret
    _ReadVirtualMemory Endp
    
    END

       最后注意: VS2019在x64的默认调用约定是_stdcall,不是_fastcall,建议改成_fastcall;

  • 相关阅读:
    怎样去掉a标签的蓝框
    textarea中的内容的获取
    移动端rem布局
    Array的push与unshift方法性能比较分析
    浅谈移动前端性能优化(转)
    移动端高清、多屏适配方案 (转)
    js关于事件的一些总结(系列一)
    移动端实用的meta标签
    浅析js绑定同一个事件依次触发问题系列(一)
    关于移动端input框 在微信中 和ios中无法输入文字的问题
  • 原文地址:https://www.cnblogs.com/theseventhson/p/13186678.html
Copyright © 2011-2022 走看看