您的位置:首页 > 其它

SharedUserData

2016-08-16 00:27 337 查看
SharedUserData 是操作系统为每个进程提供的个共享数据结构,里面存放有很多重要的系统信息,如TickCount、系统时间、SystemRoot等。。。

其在DDK定义为:

代码:

#define KI_USER_SHARED_DATA        0xffdf0000

#define SharedUserData  ((KUSER_SHARED_DATA * const) KI_USER_SHARED_DATA)

他在内核中的地址是0xffdf0000,操作系统通过共享映射把这个结构以只读方式映射到每个进程的0x7ffe0000的地方。

操作系统为每个进程提供4G虚拟空间的访问能力(空间访问能力不代表空间容量),不过实际访问不了这么多,因为4G空间中低2G是用户空间,

高2G是系统空间(非3G模式的情况下),其中低2G空间中某些地址是受系统保护的,普通用户进程也不能访问,而高2G访问需要ring0权限。

操作系统采用分页机制来映射物理内存,并会为每个进程分配一个PT(Pages Table)和PD(Pages Directory),

每个进程的PTE(Page Table Entry)和PDE(Page Directory Entry)通常映射了不同的物理页,所以不同进程的同一虚拟地址的内容是不相干的。

上面说的“通常”也有例外,那就是不同进程的PTE或PDE也可能会映射相同的物理页,如果他们映射了相同的物理页,说明他们共享了一段内存,

SharedUserData就是这个例外,每个进程用户空间的0x7ffe0000都以只读方式映射到相同的物理页面上,而这个物理页面上就是KUSER_SHARED_DATA结构

的数据,因此操作系统上的每个进程都有一个这个结构,而操作系统只需要维护这一个结构就行了。

实际上在Windows NT操作系统的实现中大量的使用了这种内存共享机制,分页机制处理也远比这个我说的复杂,具体可参考《JIURL玩玩Win2k内存篇》。

我们回到我们的主题,我们要替换指定进程的SharedUserData。了解了上面我说的共享机制,可能你已经知道了,使进程的0x7ffe0000不要映射到0xffdf0000

指向的物理页,而是映射到我们自己的物理内存页就行了。没错。。。话是这么说,但实际上Windows的内存管理机制相当复杂,我在具体实现想法的时候碰

到了许多问题,真正彻底解决这些问题花了我足足两周的时间,其中最棘手的问题的是:

1. 死锁问题

2. 内存被置换到Standby链表上去的问题

事出必有因,我坚信操作系统能做到的事情我们也同样能做到。通过不断的调试操作系统代码终于解决了各种问题,现在我的代码能在2000-2003的系统下跑起来了。

我们需要定义一些结构,PTE和PDE的基地址如下:

代码:

#define PTE_BASE    0xC0000000

#define PDE_BASE    0xC0300000

根据虚拟地址得到PTE的地址和PDE的地址:

代码:

#define GetPteAddress(va) ((PMMPTE)(((((ULONG)(va)) >> 12) << 2) + PTE_BASE))

#define GetPdeAddress(va)  ((PMMPTE)(((((ULONG)(va)) >> 22) << 2) + PDE_BASE))

我们需要分配一段非分页内存,必须以小页边界(4K)对齐,0x7ffe0000也以页对齐的:

代码:

ProcessPTE->p = ExAllocatePool(NonPagedPool, PAGE_SIZE)

现在我们就替换SharedUserData,并把日期锁定:

代码:

RtlCopyMemory(ProcessPTE->p, SharedUserData, sizeof(KUSER_SHARED_DATA));

pMySharedData = ProcessPTE->p;

RtlTimeToTimeFields((PLARGE_INTEGER)&pMySharedData->SystemTime, &TimeFields);

TimeFields.Year = 2007;

TimeFields.Month = 1;

TimeFields.Day = 1;

RtlTimeFieldsToTime(&TimeFields, (PLARGE_INTEGER)&pMySharedData->SystemTime);

pMySharedData->SystemTime.High2Time = pMySharedData->SystemTime.High1Time;

这样日期就被我们锁定在2007-1-1了。。。,现在我们要让时间继续走,于是建一个系统线程去更新SystemTime:

代码:

pSharedData = ProcessPTE->p;

RtlTimeToTimeFields((PLARGE_INTEGER)&SharedUserData->SystemTime, &TimeFields);

TimeFields.Year = 2007;

TimeFields.Month = 1;

TimeFields.Day = 1;

RtlTimeFieldsToTime(&TimeFields, (PLARGE_INTEGER)&pSharedData->SystemTime);

pSharedData->SystemTime.High2Time = pSharedData->SystemTime.High1Time;

测试一下,我以现程序有些问题,在被替换进程的窗口最小化的时候会让系统死锁,是于我中断下来,查看调用堆栈:

代码:

80473ae0 8046950e 00000001 82060102 000000d1 nt!RtlpBreakWithStatusInstruction

80473ae0 80069b02 00000001 82060102 000000d1 nt!KeUpdateSystemTime+0x13e

80473b64 80464b59 0000000e 00000000 00000000 hal!HalProcessorIdle+0x2

80473b68 00000000 00000000 00000000 00000000 nt!KiIdleLoop+0x10

系统在这里空转, 查看我们目标进程的调用堆栈:

代码:

be482a50 8042d87a 00cc0020 00000000 0000032c nt!KiSwapThread+0xc5

be482a78 a007dbad 00000001 00000000 be482a8c nt!KeDelayExecutionThread+0x180

be482a94 a007e005 00000001 a03106e0 01010057 win32k!UserSleep+0x2b

be482af4 a007e11a 0000032c 01010057 000000c0 win32k!xxxAnimateCaption+0x229

be482b48 a004d809 a0332cf8 00000003 a0332cf8 win32k!xxxDrawAnimatedRects+0xc8

be482c20 a00151d3 e1f32468 00000006 00010000 win32k!xxxMinMaximize+0x1fc

be482c48 a003e872 00000010 00010006 00000112 win32k!xxxShowWindow+0x147

be482c7c a008977d a0332cf8 0000f020 00000000 win32k!xxxSysCommand+0x2a6

be482cdc a00045fa a0332cf8 00000112 0000f020 win32k!xxxDefWindowProc+0xc43

be482cf0 a00045e1 a0332cf8 00000112 0000f020 win32k!xxxWrapDefWindowProc+0x15

be482d0c a0004588 a0332cf8 00000112 0000f020 win32k!NtUserfnDWORD+0x25

be482d40 80465691 00050100 00000112 0000f020 win32k!NtUserMessageCall+0x89

be482d40 77df37e7 00050100 00000112 0000f020 nt!KiSystemService+0xc4

0012fe0c 00000000 00000000 00000000 00000000 +0x77df37e7

xxxAnimateCaption之上是UserSleep,这看起来是一个循环,于是我们转去对xxxAnimateCaption进行调度,发现之所以在这里卡住,是因为

我们没有更新SharedUserData的TickCountLow,而xxxAnimateCaption会从0x7ffe0000取这个值来画出动画效果,

具体可看xxxAnimateCaption的反汇编代码,比较多,我就不列出来了。。。

当然光是一个死循环还不至于让系统死锁,我们再向上看,xxxDrawAnimatedRects的时候调用GetDCEx导致锁定一个对象,具体见函数

GreLockDisplay的代码。而一个死循环导致了系统一直没有机会ReleaseDC。系统会不断的等待这个对象,所以导致了系统的死锁。

通过调试,我们发现GreLockDisplay锁定的对象是0x81e1bc08,

代码:

kd> dt 0x81e1bc08 _ERESOURCE -b

  +0x000 SystemResourcesList : _LIST_ENTRY [ 0x81e1bbc8 - 0x81e1bd08 ]

      +0x000 Flink            : 0x81e1bbc8 

      +0x004 Blink            : 0x81e1bd08 

  +0x008 OwnerTable      : (null) 

  +0x00c ActiveCount      : 1

  +0x00e Flag            : 0x80

  +0x010 SharedWaiters    : 0x81cf4168 

  +0x014 ExclusiveWaiters : 0x81e0c068 

  +0x018 OwnerThreads    : 

    [00] _OWNER_ENTRY

      +0x000 OwnerThread      : 0x81d0d020

      +0x004 OwnerCount      : 1

      +0x004 TableSize        : 1

    [01] 

      +0x000 OwnerThread      : 0

      +0x004 OwnerCount      : 0

      +0x004 TableSize        : 0

  +0x028 ContentionCount  : 0x5e9

  +0x02c NumberOfSharedWaiters : 0

  +0x02e NumberOfExclusiveWaiters : 3

  +0x030 Address          : (null) 

  +0x030 CreatorBackTraceIndex : 0

  +0x034 SpinLock        : 0

0x81d0d020就是我们的线程,从这个结构来看,我们的线程占有了一个对象,而还有3个等待者(NumberOfExclusiveWaiters : 3),

这3个等待者在ExclusiveWaiters : 0x81e0c068指向的地方,

代码:

kd> dt 0x81e0c068 _KEVENT -b

  +0x000 Header          : _DISPATCHER_HEADER

      +0x000 Type            : 0x1 ''

      +0x001 Absolute        : 0 ''

      +0x002 Size            : 0x4 ''

      +0x003 Inserted        : 0 ''

      +0x004 SignalState      : 0

      +0x008 WaitListHead    : _LIST_ENTRY [ 0x81cf962c - 0x81da15cc ]

        +0x000 Flink            : 0x81cf962c 

        +0x004 Blink            : 0x81da15cc

kd> dt 0x81cf962c _KWAIT_BLOCK -b

  +0x000 WaitListEntry    : _LIST_ENTRY [ 0x81e0ca6c - 0x81e0c070 ]

      +0x000 Flink            : 0x81e0ca6c 

      +0x004 Blink            : 0x81e0c070 

  +0x008 Thread          : 0x81cf95c0 

  +0x00c Object          : 0x81e0c068 

  +0x010 NextWaitBlock    : 0x81cf9674 

  +0x014 WaitKey          : 0

  +0x016 WaitType        : 1

kd> dt 0x81e0ca6c _KWAIT_BLOCK -b

  +0x000 WaitListEntry    : _LIST_ENTRY [ 0x81da15cc - 0x81cf962c ]

      +0x000 Flink            : 0x81da15cc 

      +0x004 Blink            : 0x81cf962c 

  +0x008 Thread          : 0x81e0ca00 

  +0x00c Object          : 0x81e0c068 

  +0x010 NextWaitBlock    : 0x81e0cab4 

  +0x014 WaitKey          : 0

  +0x016 WaitType        : 1

kd> dt 0x81da15cc _KWAIT_BLOCK -b

  +0x000 WaitListEntry    : _LIST_ENTRY [ 0x81e0c070 - 0x81e0ca6c ]

      +0x000 Flink            : 0x81e0c070 

      +0x004 Blink            : 0x81e0ca6c 

  +0x008 Thread          : 0x81da1560 

  +0x00c Object          : 0x81e0c068 

  +0x010 NextWaitBlock    : 0x81da1614 

  +0x014 WaitKey          : 0

  +0x016 WaitType        : 1

其中两个线程0x81da1560和0x81cf95c0是VMWare的关键线程(看来VMWare需要跟系统配合运行的,并不是完全模拟硬件),

另一个线程0x81e0ca00是csrss.exe(csrss负责win32子系统)的关键线程。以上就是导致系统死锁的根本原因。

(windbg中可以用!locks来搜索系统中所有的锁,当然有时候搜索出来的结果比较多,还没有手工来得直接)

要解决这个问题,我们可采用以下方法:

1. 关闭系统的动画效果

2. 我们来更新这个值

如果采用1的方法,我难保系统的其他地方没有用SharedUserData的TickCountLow这个值来做循环因子,因此最彻底的方法是我们自己更新这个值,于是

我们在我们的系统线程中加入:

代码:

pSharedData->TickCountLow = SharedUserData->TickCountLow;

现在死锁问题解决了,我们又迎来了一个新的问题:我们申请的用于替换的内存会被置换到Standby链表上去。当有程序要访问0x7ffe0000的时候就会非法访问。

用Arm加壳的程序在调用SetProcessWorkingSetSize或程序最小化时会出现这个问题。这是因为Windows分页机制造成的,Windows会根据需要调整进程的工作集空间。

什么情况下内存会置换出去呢?我们申请的明明是非分页内存是不允许被置换出去的,为什么会出现这种情况呢?事出必有因,什么时候Windows会调整进程工作集,

调整工作集的条件是什么?为什么有些页不会被置换出去?要解答这些问题,我们需要分析Windows的相关代码。

通过不断的调试分析,最终我把相当代码锁定到了MiFreeWsle函数,拿出我们手里都有的那份代码,经过简化后,关键的地方就是如下:

代码:

if ((Pfn1->u2.ShareCount > 1) &&

    (Pfn1->u3.e1.PrototypePte == 0)) {

    UNLOCK_PFN (OldIrql);

    return FALSE;

}

//

// Found a candidate, remove the page from the working set.

//

MiEliminateWorkingSetEntry (WorkingSetIndex,

      PointerPte,

      Pfn1,

      Wsle);

如果一个页的ShareCount(共享数)为1,就会调用MiEliminateWorkingSetEntry把页从工作集中移走。

了解了吧?跃然我们在系统空间中申请了一页非分页内存,但映射到0x7ffe000后相关PfnDatabase并不知道,而我们这页的共享数为1,所以0x7ffe000会被置换走.

当有程序试图访问0x7ffe0000的时候会产生页访问异常。此时系统不知道如何处理这个突如其来的异常而导致系统崩溃。

所以要解决这个问题,我们需要手工改PfnDatabase的SharedCount为大于1的数,这里蛮干,我机器上PfnDatabase的地址是0x820bf000。

代码:

PMMPFN      MmPfnDatabase = (PMMPFN)0x820bf000;

PointerPte = GetPteAddress (0x7ffe0000);

PageFrameNumber = PointerPte->u.Hard.PageFrameNumber;

Pfn1 = &MmPfnDatabase[PageFrameIndex];

Pfn1->u3.e2.ReferenceCount = 2;

Pfn1->u2.ShareCount = 2;

Pfn1->u3.e1.PrototypePte = 0;

到这里我们已经把重要的问题都解决了,但还有一个比较重要的问题。。。有借有还,在进程退出的时候我们需要还原原始的PTE,这样就不至于让系统产生PFN_LIST_CORRUPT异常了。

ShareUserData的结构如下:

kd> dt ntkrnlpa!_KUSER_SHARED_DATA 7ffe0000
+0x000 TickCountLow : 0x17fd8
+0x004 TickCountMultiplier : 0xfa00000
+0x008 InterruptTime : _KSYSTEM_TIME
+0x014 SystemTime : _KSYSTEM_TIME
+0x020 TimeZoneBias : _KSYSTEM_TIME
+0x02c ImageNumberLow : 0x14c
+0x02e ImageNumberHigh : 0x14c
+0x030 NtSystemRoot : [260] 0x43
+0x238 MaxStackTraceDepth : 0
+0x23c CryptoExponent : 0
+0x240 TimeZoneId : 0
+0x244 Reserved2 : [8] 0
+0x264 NtProductType : 1 ( NtProductWinNt )
+0x268 ProductTypeIsValid : 0x1 ''
+0x26c NtMajorVersion : 5
+0x270 NtMinorVersion : 1
+0x274 ProcessorFeatures : [64] ""
+0x2b4 Reserved1 : 0x7ffeffff
+0x2b8 Reserved3 : 0x80000000
+0x2bc TimeSlip : 0
+0x2c0 AlternativeArchitecture : 0 ( StandardDesign )
+0x2c8 SystemExpirationDate : _LARGE_INTEGER 0x0
+0x2d0 SuiteMask : 0x110
+0x2d4 KdDebuggerEnabled : 0x3 ''
+0x2d5 NXSupportPolicy : 0x2 ''
+0x2d8 ActiveConsoleId : 0
+0x2dc DismountCount : 0
+0x2e0 ComPlusPackage : 0xffffffff
+0x2e4 LastSystemRITEventTickCount : 0x254cd
+0x2e8 NumberOfPhysicalPages : 0x3ff7c
+0x2ec SafeBootMode : 0 ''
+0x2f0 TraceLogging : 0
+0x2f8 TestRetInstruction : 0xc3
+0x300 SystemCall : 0x7c92e4f0
+0x304 SystemCallReturn : 0x7c92e4f4
+0x308 SystemCallPad : [3] 0
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : 0
+0x330 Cookie : 0x5b973f62结束语:我们在进程创建的时候就替换SharedUserData,可根据普通应用程序的特性做出一些与时间有关的应用,如加速,减速,Anti GetTickCount等。。。

本文引用:
http://www.xuebuyuan.com/1349751.html http://bbs.pediy.com/showthread.php?t=177655 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: