首页
社区
课程
招聘
[原创]利用内核知识,自己实现ReadProcessMemory
2018-2-3 23:55 30174

[原创]利用内核知识,自己实现ReadProcessMemory

2018-2-3 23:55
30174

前言

转眼来到科锐学习已经超过一年的时间了,眼看三阶段已经进入尾声,内核的学习也快要结束,记录一下笔记和心得,也给刚接触的朋友做一个参考。当然,学习新知识最好的办法就是带着目的来学习,所以在文章后半部分,介绍如何自己实现了Windows的一个API:ReadProcessMemory,为什么要选这个API呢?首先在软件保护上,任何开发者都不会希望别人可以随意查看自己的内存内容,于是多数人会选择在这个函数上挂钩,监视并保护内存,那么我们可以绕过他的保护,用自己的函数来查看进程内存。

本篇文章主要涉及两个部分:
  • 介绍内核基础知识
  • 自己实现ReadProcessMemory

环境介绍:真机64位Windows10,虚拟机:32位XP_sp3

学习内核的本质其实是学习操作系统的原理,而学习的过程应该是从CPU架构出发,操作系统作为使用CPU提供功能的例子。

第一部分:内核基础知识

内核可以分成两类:

  • 宏内核:追求性能,大部分系统代码放在0环,代表:Linux
  • 微内核:追求维护性,大部分系统代码在3环,代表:Minix(Linux作者老师的作品),其中著名的设计:动态链接库,在Windows中也使用

Windows算是微内核和宏内核特点都具有的操作系统。

80x86处理器的工作模式:

8086处理器有三种工作模式,分别是:实模式,保护模式,虚拟86模式,其中关系为:
8086处理器有三种工作模式

16位汇编中 iret可以进入保护模式。

分段式内存管理

如何保证操作内存的动作是否合法?

GDT、LDT

操作系统通电后进入实模式,做了一系列初始化的动作后进入到保护模式,在保护模式中,CPU执行所有和内存有关的操作都会通过查表来确定操作是否合法,这个表就是GDT和LDT表,表的格式由CPU厂商决定,所以为了能兼容多款CPU,操作系统代码里多用条件宏来实现。

地址的转换

  • 逻辑地址:在程序调试中见到的地址,实际上是:段+偏移的形式
  • 线性地址:逻辑地址转物理地址的中间层,逻辑地址是段中的偏移地址然后加上基地址就是线性地址。
  • 物理地址:物理内存条上的真实地址

逻辑地址如何转换到物理地址?

首先通过逻辑地址的偏移查第一次表得到线性地址,再查第二次表得到物理地址。为什么要查第二次表呢?因为第二张表实际上是为了实现虚拟内存,那么就是说这段内存可能是在磁盘上的,访问的时候会先查表,然后从磁盘上调到内存中,有些情况下(关闭了虚拟内存)查第一次表的结果等价于物理地址。

为什么要叫线性地址?

从逻辑地址转换到线性地址,是一块平坦且连续的地址,实际上对应到物理地址上,并不是连续的。

如何计算线性地址的范围?

例如:

  • 设段A的基地址等于00012345H,段界限等于5678H,并且段界限以字节为单位(G=0),那么段A对应线性地址空间中从00012345H-000179BDH的区域。
  • 如果段界限以4K字节为单位 (G=1),那么段A对应线性地址空间中从00012345H-0568B344H(=00012345H+5678000H+0FFFH) 的区域。

如何从逻辑地址 ===查表===>> 线性地址?

这个表也叫做分段表,结构如下图:

这种奇葩的做法来源于为了兼容286的历史遗留问题。

描述符

用于表示上述定义段的三个参数的数据结构称为描述符。每个描述符长8个字节。在保护方式下,每一个段都有一个相应的描述符来描述。

存储段描述符

存储段是存放可由程序直接进行访问的代码和数据的段。存储段描述符描述存储段,所以存储段描述符也被称为代码和数据段描述符。

 

描述符是一个8个字节的结构,具体结构如下:
图片描述

  • Limit 0:15项和Limit 16:19项一起构成20位的段界限。20位的段界限最大值为0xFFFFF,单位是字节或者分页(有Flags项Gr位来确定)。在分页机状态下最大可以表达4G的内存空间。
  • Base0:23项和Base24:31一起构成32位的段基址,是线性还是物理地址也取绝于分页机制是否开启。

整个的解析结构如下图:
图片描述

Access Byte
  • Pr:存在位,对于一个有效的内存分段此值必定为1。
  • Privl(2bit):优先级位,取值从0-3,对应Ring0-Ring3级别。

  • Ex:可执行位,为1时表示此描述符对应是代码段,为0时为数据段。

  • DC:
    • 对于数据段,表于数据段的增长方向,0表示向上。1表示向下,也就是偏移大于段基址。
    • 对于代码段,表示是否遵循一致原则。当此位为1时,也就是遵循一致原则,不同优先级代码跳转时,优先级同目标代码所在段一致。为0时,刚跳转时优先级不变。
  • R/W:读写位
    • 对于数据段,为1时表示可写,为0时表示不可写。数据段总是可读。
    • 对于代码段,为1时表示可读,为0时表示不可读。代码段总是不可写。
  • Ac:保留位,设置为0,当被访问过时系统将其改写为1。
Flags
  • Gr:表示段界限的单位也叫做粒度,为1时表示单位为4KB(一个页面),为0时表示单位为1字节。
  • Sz:区分是16位保护模式,还是32位保护模式。可以同时有两种类型描述符在同一个GDT中。

全局和局部描述符表

  • 每个任务的局部描述符表LDT含有该任务自己的代码段、数据段和堆栈段的描述符,也包含该任务所使用的一些门描述符,如任务门和调用门描述符等。随着任务的切换,系统当前的局部描述符表LDT也随之切换。

  • 全局描述符表GDT含有每一个任务都可能或可以访问的段的描述符,通常包含描述操作系统所使用的代码段、数据段和堆栈段的描述符,也包含多种特殊数据段描述符,如各个用于描述任务LDT的特殊数据段等。在任务切换时,并不切换GDT。

通过LDT可以使各个任务私有的各个段与其它任务相隔离,从而达到受保护的目的。通过GDT可以使各任务都需要使用的段能够被共享。

 

GDT存储在GDTR寄存器, 通过汇编指令LGDT载入。它的操作码是一个结构的地址,这个结构描述GDT的大小和地址。共6个字节,如下:
图片描述

  • Size项(2个字节)是GDT的字节数减1(这也意味着GDT大小不可能为0)。2个字节对应最大值是65535,也就是说一个GDT最大也就是65536字节(8192个内存分段)。
  • Offset项(4个字节)指向GDT的线性地址(未开启分页机制则是物理地址)。

LDT存在LDTR寄存器中,存有局部进程的描述符表,LDTR中的内容根据线程的切换不停切换,表中的内容由操作系统来修改,若我们拿到0环权限,自己修改LDTR,改到目标进程,那么修改自己的内存就相当于修改了目标进程的内存,这是内核修改的一个经典招式。


通过段选择子确定逻辑地址到物理地址的转化(未开启分页机制)

段选择子

在保护方式下,虚拟地址空间(相当于逻辑地址空间)中存储单元的地址由段选择子和段内偏移两部分组成。段选择子长16位,在32位程序下,CPU的段寄存器中保存的就是选择子,其格式如下表所示:
图片描述

  • 段选择子的高13位是描述符索引(Index):所谓描述符索引是指描述符在描述符表中的序号。
  • 段选择子的第2位是引用描述符表指示位,标记为TI(Table Indicator),TI=0指示从全局描述符表GDT中读取描述符;TI=1指示从局部描述符表LDT中读取描述符。 (windows不使用这种CPU的做法,而Linux使用)
  • RPL:特权级描述符,CPU比较这一项个描述符的特权级判断访问操作是否能进行下去

具体通过逻辑地址查找线性地址的例子:

现有逻辑地址:23:13ac34b,假如段寄存器中的的选择子Index为:0000000000100,RPL:11,先比对,是三环程序,继续操作,去LDT表中的第4项拿到段首地址,加上偏移13ac34b,得到线性地址。

值得一提的是Windows中并没有使用LDT,而Linux是使用了LDT的,但是有意思的是在阅读Windows源码时发现微软也留下了LDT的接口,难道微软想什么时候顺便兼容一下Linux?


分页管理

80386开始支持存储器分页管理机制。分页机制是存储器管理机制的第二部分。上述的段管理机制实现虚拟地址(由段和偏移构成的逻辑地址)到线性地址的转换,分页管理机制实现线性地址到物理地址的转换。

线性地址到物理地址的转换

线性地址到物理地址的转换方式受很多变量的影响,我们先以一个其中最具代表性的方式来讲解基本概念和转换流程,再来总结所有的转换方式。

 

流程如图所示采用了二级表的结构:
图片描述

页目录表(PDE)

一级表称为页目录表(Page Directory Entry),共有1024(1k)个表项,每个表项的大小是4bit,总大小为4k,表项内容包括了页表的指针和指向页表的属性。

页表(PTE)

二级表称为页表(Page Table Entry),每张页表里有1024(1k)个表项,每个表项的大小是4bit,总大小为4k,最多有1024(1k)张页表,最大占用空间为4M,而操作系统一般是动态申请页表,大小大概在1M左右。表项内容包括了物理地址的指针和属性。

CR3

控制寄存器CR3的高20位存放了指向页目录表的指针(这里存的是物理地址,如果这里存虚拟地址就会产生悖论),每个进程都会有一张PDE,切换进程其实就是CPU在切换CR3的值,这一点非常重要,是我们自己实现ReadProcessMemory的基础!

表项

PDE和PTE的表项结构基本相似,略有差别,如下图所示:

  • 页目录表表项:
    图片描述
  • 页表表项:
    图片描述

结构基本类似,高20位存指向目标首地址,低12位表示指向目标的属性

  • P:Present,存在标志,该标志标明当前表项所指向的页或页表是否存在于内存中。当标志位置位表示该页在内存中,当标志位清零表示该页不在内存中,若CPU试图访问则会产生一个缺页异常,值得一提的是CPU并不会主动操作该标志位,而是让操作系统来维护。
  • R/W:Read or Write,读写标志位,当标志位置位,所指页表或页是可读可写的,清零表示所指页表或页是只读的。
  • U/S:User or Surpervisor,用户权限标志,置位时表示普通用户权限也就是我们常说的3环权限,清零则表示超级用户权限也就是0环权限。
  • PWT:Page Write Through,页直写标记,控制页或页表的直写或回写缓存策略。
  • PCD:Page Cache Disabled,页层次的缓存禁用,控制页或页表的的缓存,置位时缓存被禁止,清零时表示可以缓存。
  • A:Accessed,访问标志,指明这个页或页表是否曾经被访问过,当指向的页或页表第一次载入内存,会清零标志位,当页或页表第一被访问,改标志位置位
  • D :Dirty,脏位(在PDE的表项中,该位是0,不使用此标志位),指明该页是否曾经被写入过,,当指向的页第一次载入内存,会清零标志位,当页第一次写操作完成,改标志位置位
  • PAT:Page Table Attribute Index(PTE表项中的第7位)页属性索引。
  • PS :Page Size(PDE表项中的第7位),该位指明指向的页表尺寸,当改标志清零,页尺寸为4k。当改标志被置位,页的尺寸为32位寻址的4M(物理地址拓展启用,页尺寸为2M)
  • G:Global,全局标志。
  • Avl:保留位。

32位线性地址结构:

图片描述

  • 线性地址的最高10位(即位22至位31)作为页目录表的索引
  • 线性地址的中间10位(即位12至位21)作为所指定的页目录表中的页表项的索引
  • 线性地址的低12位作为32位物理地址的低12位。

转换实例:

如何搭建双机调试环境,请自行谷歌

 

我们以GDT地址作为例子:
寄存器环境:
图片描述

 

gdt内容:
图片描述

 

dd :查看虚拟内存地址

 

现在我们有线性地址:0x8003f000,CR3:39000

  1. 拆分线性地址:1000000000   0000111111  000000000000
    • PDE Index:0x200
    • PTE Index :0x3f
    • Offset :0x0
  2. 页目录表首地址为CR3的高20位,找到对应页目录表项:
    图片描述

!dd:查看物理内存地址, 0x200 * 4是因为表项是4个字节

  1. 表项 0x0003b163的前20位指向页表的首地址,也就是0x0003b000,页表的Index为0x3f,于是:
    图片描述
  2. 表项0x0003f163的前20位指向物理地址页,也就是0x0003f000,加上Offset,最后得到物理地址:0x0003f000。
  3. 检验:
    图片描述
    和虚拟地址对应的内容是相同的,说明虚拟地址:0x8003f000映射到物理地址:0x0003f000

所有转换方式(查表方式):

以上我们所阐述的线性地址转物理地址的方法适用于没有物理地址拓展,且页表大小为4k的情况。

如何决定适用哪种查表方式?

查表方式根据页表大小来决定,而页表大小根据以下标志决定:

  • PG:分页标志,CR0的31位
  • PSE : 页尺寸拓展标志,CR4的第4位
  • PAE : 物理地址拓展标志,CR4的第5位
  • PS :页表尺寸,PDE表项中的第7位

    图片描述

未开启PAE,页表大小4k

以下图片均来自Intel手册,详细解释请参考手册

 

图片描述

未开启PAE,页表大小4M

图片描述

 

当符合页表大小是4M的情况下,只需要查一次PDE表再加上偏移就能得出物理地址。

开启PAE分页机制的36位物理寻址

开启PAE时,寻址的方式有所不同,CR3里保存的不再是PDE的首地址,而是一个保存了PDE指针的表的首地址,这张表我们称作页目录指针表(PDPT),对于线性地址的拆分也有所不同,高两位作为了PDPT的索引。

开启PAE,页表大小4K

图片描述

 

当开启PAE且页表大小是4k的情况,需要查三次表,线性地址的21-29位作为PDE表的索引,12-20位作为PTE表的索引

开启PAE,页表大小2M

图片描述


第二部分:ReadProcessMemory的实现

说了这么多关于表的格式和查表的方法,在实践中我们该如何利用呢?一个用处是我们经常需要把虚拟地址转换成物理地址,明白其转换原理利于分析问题,另外上面说了,每个进程都会有一套自己的分页机制,切换进程实际上是切换CR3中的内容,那么如何实现我们自己的ReadProcessMemory呢?

 

问题变成了以下4步:

  1. 拿到指定进程保存在CR3中的内容
  2. 切换当前的CR3
  3. 读取指定内存的内容
  4. 还原CR3

第一步,如何拿到指定进程保存在CR3中的内容?

我们知道在3环程序里,fs[0]保存的是线程环境块TEB,在0环,保存的则是处理器控制区(_KPCR),部分内核数据结构如下图:

 

从_EPROCESS中的进程链表我们可以遍历所有进程,当匹配到目标进程时,拿出
目标进程DirectoryTableBase中保存的地址。

NTSTATUS GetProcessDirBase(IN DWORD dwPID, OUT PDWORD pDirBase)
{
    PEPROCESS Process;
    PEPROCESS CurProcess;
    CHAR  *pszImageName;
    DWORD dwCurPID;
    DWORD i;

    __try
    {        
        __asm
        {
            //ETHREAD
            mov eax, fs:[124h] 
            //Current EPROCESS 
            mov eax, [eax + 44h] 
            mov Process, eax
        }

        CurProcess = Process;
        i = 0;
        //traversing  EPROCESS
        do
        {
            pszImageName = (char*)CurProcess + 0x174;
            dwCurPID = (*(DWORD*)((char*)CurProcess + 0x084));

            if (dwCurPID == dwPID)
            {
                *pDirBase = (*(DWORD*)((char*)CurProcess + 0x018));
                return STATUS_SUCCESS;
            }
             //Next
            CurProcess = (*(DWORD*)((char*)CurProcess + 0x088)) - 0x88;
        } while (CurProcess != Process);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        dprintf("[MyReadProcessMemory] GetProcessDirBase __except \r\n");
    }

    return STATUS_INVALID_DEVICE_REQUEST;
}

第二步,切换当前的CR3

切换CR3的值之前,我们需要屏蔽调当前CPU核心的中断,以防线程切换,如果是多核的CPU,每个核心都需要屏蔽掉中断。同时,为了预防内存属性不可写,暂时改掉CR0中表示所有内存属性的标志,让所有内存暂时都可写。

        __asm
        {
            //Shielding interrupt
            cli     
            //close memory protect         
            mov eax, cr0                
            and eax, not 10000h
            mov cr0, eax
            mov eax, cr3
            mov dwOldDirBase, eax
            //swap CR3
            mov eax, dwDirBase
            mov cr3, eax
        }

第三步,读取指定内存的内容

申请一段空间,暂存一下读取的数据,记得要检查目标内存地址是否有效

        //Alloc ring0 Buff
        char* szRing0Buf = (char*)MmAllocateNonCachedMemory(dwBufSize);
        //check address invalid
        if (MmIsAddressValid(dwTargetAdddress))
        {
            RtlCopyMemory(szRing0Buf, dwTargetAdddress, dwBufSize);
            bIsRead = TRUE;
        }

第四步,还原CR3

恢复内存属性,恢复中断

        __asm
        {
            mov eax, dwOldDirBase
            mov cr3, eax
            //Reset  memory protect
            mov eax, cr0                
            or  eax, 10000h
            mov cr0, eax
            //Restore interrupt
            sti                         
        }

总结

内核的学习也开始进入尾声,温故而知新,整理知识本身也是一种学习的过程。衷心感谢一年多以来钱老师,张老师,姚老师,戚老师,王老师,唐老师的指导。要毕业了,帮科锐宣传下,科锐30期正在招生中


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

上传的附件:
收藏
点赞3
打赏
分享
最新回复 (46)
雪    币: 22980
活跃值: (3342)
能力值: (RANK:648 )
在线值:
发帖
回帖
粉丝
KevinsBobo 8 2018-2-4 10:08
2
0
干货很多,希望以后多来分享!
雪    币: 58
活跃值: (71)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
断剑重铸 2018-2-4 14:25
3
0
666666666,向大佬学习,以后大佬的笔记就是我的工具书?
雪    币: 5
活跃值: (11)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
苏stone 2018-2-4 14:25
4
0
很详细,收走了
雪    币: 1
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
marinekinghn 2018-2-4 16:45
5
0
看了帖子感觉学到了很多遗漏的东西。对自己的知识又有了补充。
雪    币: 5676
活跃值: (1303)
能力值: ( LV17,RANK:1185 )
在线值:
发帖
回帖
粉丝
holing 15 2018-2-4 19:22
6
0
这个强
雪    币: 43
活跃值: (388)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
malokch 2 2018-2-4 22:16
7
0
我的方案是改pte  map远程进程的内存到本地进程。想干啥就干啥
雪    币: 575
活跃值: (112)
能力值: ( LV5,RANK:140 )
在线值:
发帖
回帖
粉丝
五行猫 1 2018-2-4 22:34
8
0
malokch 我的方案是改pte map远程进程的内存到本地进程。想干啥就干啥
想了下,确实是个好方法
雪    币: 48
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
想要养只猫 2018-2-4 22:38
9
0
围观
雪    币: 1484
活跃值: (1135)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
冰雄 2018-2-5 09:48
10
0
难道没有办法防?
雪    币: 12839
活跃值: (8998)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
hzqst 3 2018-2-5 10:48
11
0
冰雄 难道没有办法防?
有,硬件虚拟化,直接保护物理地址,不过处理很复杂就是了
顺便这篇文章没有涉及到一点64位内存管理的东西,全程在讲32位,放到2004年大家都在用xp的年代或许不错。但是今天已经是2018年了,至少也该讲讲64位的东西了吧
雪    币: 1484
活跃值: (1135)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
冰雄 2018-2-5 11:03
12
0
HOOK这个RtlCopyMemory函数也是可以防吧
雪    币: 1484
活跃值: (1135)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
冰雄 2018-2-5 11:04
13
0
最看到利用VT可以突破任何  驱动防护。。
雪    币: 310
活跃值: (1917)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
niuzuoquan 2018-2-5 14:03
14
0
mark
雪    币: 12839
活跃值: (8998)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
hzqst 3 2018-2-5 14:18
15
0
冰雄 最看到利用VT可以突破任何 驱动防护。。
本来就是矛和盾的故事,在同样的竞争下没有谁一定会更强一点,一般来说都是哪边花的精力/钱多哪边就更强
雪    币: 1484
活跃值: (1135)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
冰雄 2018-2-5 14:34
16
0
@hzqst    随着内核的深入学习,发现目前主流的杀软也太弱  了  。。测试了一下。通过PID  就kill  TX管家。大公司的安全软件都那样。小公司怎么生存。任人鱼肉了
  MyKillProcess(HANDLE  Pid)
{
       HANDLE  hProcess;
       NTSTATUS  ret=  MyOpenProcess(Pid,&hProcess,1);
       MyTerminateProcess(hProcess);
       return  ret;
}
雪    币: 12839
活跃值: (8998)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
hzqst 3 2018-2-5 15:08
17
0
冰雄 @hzqst 随着内核的深入学习,发现目前主流的杀软也太弱 了 。。测试了一下。通过PID 就kill TX管家。大公司的安全软件都那样。小公司怎么生存。任人鱼肉了 MyKillProcess( ...
1、你kill的是GUI进程,主防进程并么有kill掉
2、如果你kill的是主防进程,那么你一定加载自己的驱动了。你加载驱动的时候大数字拦截并询问了吗,你大概是点了允许加载驱动吧。
雪    币: 5023
活跃值: (2506)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
FaEry 6 2018-2-5 16:01
18
0
hzqst 1、你kill的是GUI进程,主防进程并么有kill掉 2、如果你kill的是主防进程,那么你一定加载自己的驱动了。你加载驱动的时候大数字拦截并询问了吗,你大概是点了允许加载驱动吧。
Bypass  Zhudongfangyu  x64  可没那么好搞~
雪    币: 688
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
maomaolk 2018-2-20 10:44
19
0
Mark
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
聖blue 2018-2-20 21:18
20
0
雪    币: 78
活跃值: (1433)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Archar 2018-2-24 16:07
21
0
雪    币: 70
活跃值: (72)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
FadeTrack 1 2018-2-27 04:06
22
0
”屏蔽调当前CPU核心的中断,以防线程切换,如果是多核的CPU,每个核心都需要屏蔽掉中断“


我只有一个问题,假设待读的内存页当前处于被交换状态你该如何处理,我只粗略看了一下你的4个小标题。

如果是我 我大概会这样做,先挂靠目标进程,然后映射当前进程空间的待读内存到内核然后锁一下(如果嫌麻烦就直接拷贝一份到内核空间),再切回原进程之后去读那个映射/拷贝,最后回收一下资源。


挂靠目标进程是个比较大的过程,估计是做了 插apc,修改当前线程所属,陷入等待 唤醒之类的一系列玩意组成的(具体我没看,猜测是这个过程),

切CR3 也只是 KiSwapProcess 里面的小小一环,暴力的代价很大(以前测过,最少有一半的目标进程内存是读不到的),为了那一点可有可无的隐蔽性,老实说很亏。

如果说仅仅是为了读带保护属性的内存也有其它更好的解决思路。

最后于 2018-2-27 04:22 被FadeTrack编辑 ,原因:
雪    币: 199
活跃值: (65)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
gkdark 1 2018-2-28 21:17
23
0
mark一下,up主写的还是很辛苦的    鼓励  鼓励
雪    币: 108
活跃值: (878)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yangya 2018-2-28 22:50
24
0
都是一些老方法了,现在的新系统都不行了。加上win10的的VBS功能,这些东西都没什么用武之地了。
雪    币: 273
活跃值: (277)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
niceli 2018-3-1 06:04
25
0
去看看TP大保护的DXF你就晓得你这样根本就读不到内存因为页面已经给你  替换了  哈哈.
游客
登录 | 注册 方可回帖
返回