# 磨针记1——从*外杀马说起

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2015-10-15T06:57:00Z
Canonical: https://wepostx.com/topics/1108

> 乌云历史资料归档
>
> **原始作者：** 路人甲
> **原始编号：** superkieran-wooyundrops:825
> **原始发布时间：** 2015-10-15 14:57
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

（本文纯属虚构，如有雷同，实属巧合）
大名鼎鼎的AspxSpy，罹患间歇性木马病而被白道追杀。大权在握的系统管理员，冷笑着把这位老兄打入了十八层地狱。拒绝访问，组件错误……连最宝贵的命令执行都要封杀。是可忍孰不可忍！拿上小馒头，我闭关数月。
国庆悄然而至，满街地小姐们穿着爆乳装，齐P小短裙（学名硬得快）挤来挤去，中国首位本土科学家获得了诺贝尔奖，我静静地呆在家中，把这漫长的历练一一道来，以飨读者。

# 0x00 词汇约定

非专业术语，仅供娱乐：
Shell：这里指类似于命令提示符的,一个用于运行可执行文件的C#页面。
本机代码：与托管代码对应。

# 0x01 *外杀马

和* 外真是有缘，这不，最近又偶遇* 外的服务器，规模约莫二十上下。向来服务器的安全检测，也有不少无法获得最高权限的情形，但这次最令人困惑的，是无论如何也无法在Shell中执行命令。管理员，你在挑逗我么？不想让我知道其中的奥妙，为何又留下了一个漏洞？给管理员戴好可爱的绿帽后，我搜遍系统，终于发现装神弄鬼的，是一个小小驱动——*外杀马。

![原文图片](/media/2016/06/521935ffef2000b7ae04e1220a9d644c)

图1 *外杀马

![原文图片](/media/2016/06/fb7de34d40ccafb7058acf38e3329560)

图2 IDA中的*外杀马
一个名为NotifyRoutine的函数引起了我的兴趣。这是一个回调函数，它的伪代码如下：

```text
void __stdcall NotifyRoutine(HANDLE ParentId, HANDLE ProcessId, BOOLEAN Create) { // … ProcessHandle = ProcessId; v66 = ProcessId; if ( Create && PsLookupProcessByProcessId(ParentId, &Object) >= 0 ) { v3 = (const char *)PsGetProcessImageFileName(Object); if ( !stricmp(v3, "w3wp.exe") || (v4 = (const char *)PsGetProcessImageFileName(Object), !stricmp(v4, "php-cgi.exe")) ) { v5 = 1; PsLookupProcessByProcessId(ProcessId, &TokenHandle); // …
```
图3 NotifyRoutine
DriverEntry注册NotifyRoutine的伪代码如下：

```text
int __stdcall sub_407000(int a1, int a2) { // … DbgPrint("加载*外驱动.\n"); PsSetCreateProcessNotifyRoutine(NotifyRoutine, 0); // … return dword_405248(dword_4056BC, a1, a2, &v3, &v7, 0); }
```
图4 PsSetCreateProcessNotifyRoutine
NotifyRoutine的流程很简单，最重要的分支如下：
（1） 判断是否创建了进程
（2） 是，则判断是否由w3wp.exe所创建
（3） 是，则与白名单比较。
（4） 中止所有不在白名单上的进程
（5） 给用户返回“拒绝访问”的提示
白名单为硬编码，主要放行了.Net编译器的命令，否则源代码就无法在线编译执行了。它允许的命令：

```text
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc.exe C:\\WINDOWS\\Microsoft.NET\\Framework\\v4.0.30319\\csc.exe” ……
```
它还允许System用户（SID为S-1-5-18）执行C:\WINDOWS\system32\cmd.exe等命令。
所有命令都带有完整路径，如果拥有足够的权限（如提权成功后），可将存在于白名单且系统中一般不存在的命令替换为你的工具，如：

```text
C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\csc.exe C:\\WINDOWS\\Microsoft.NET\\Framework\\v3.5\\csc.ex …
```
替换后的命令在Shell中可正常执行，仅会在C盘根目录下的日志中留下一条NotKill记录。
判断系统中是否存在*外杀马，一看C盘根目录是否存在 7i24_com_FreeHostKill_vxxx.txt的日志文件，二看system32\drivers下是否存在FreeHostKill.sys，三看注册表。
总之，*外杀马不仅仅杀马，它是什么人都杀。它只是一个极其原始的防火墙，通过监控所有进程的创建，在白名单的控制之下，哪怕位高权重的Administrators组用户也无法在Shell中执行命令。

# 0x02 光明而曲折的破解之路

道高一尺，魔高一丈，既然明白了原理，就来尝试如何破解吧。
我不知道是否有人在低权限的脚本环境中有办法躲过驱动的监控，如果有，那真要顶礼膜拜了。
我不是圣人，还是按照通常的思路来完成我的Shell：
（1） 不创建进程
（2） 不创建线程
（3） 直接使用Shell代码将用户提升至最高权限
前途是光明的，道路是曲折的。只要有人尝试了这条路，很快就会出现更多的探索者，期待更多的奇思妙想的出现。
许多漏洞Exploit都采用了vc++编写，因此最初的做法是弄清漏洞的原理后，使用C#语法逐行重写这些源代码。十多天磨练下来，唯一的感受就是——痛苦，九九八十一重的炼狱也不过如此。
重写内容对人的摧残度说明
API中DllImport API、结构、常数，PInvoke站点提供查询
C++指针小IntPtr
托管内存与非托管内存的数据交换小Marshal类支持
内嵌汇编大ShellCode，召唤请叫我雷锋的人
好想开个遵义会议，让人指出一条明路。日夜google中，终于一个家伙说，使用dll封装你的代码吧。神啊，如此有创意的点子，为何不是我想出来的呢？
醍醐灌顶之后，本人的血压暂时飙升到160，刷刷刷地把main函数改名导出，两分钟内dll封装完毕，真是无本生意的典范。看导演的安排，接下的剧情无非就是千里狙敌，裤裆藏雷，开着21世纪的R2概念车去欺负刚摆脱丛林文化的小膏药旗了。
可惜天亮了，美梦成空，为了让大伙不再走我的老路，我把那些令人欲仙欲死的问题总结如下：

## （1） Wow64麻烦
省略几千字，我把它放到了本系列篇（三）中加以详述。

## （2） LoadLibrary加载DLL提示无法找到模块
在本机上正常加载的DLL，天杀的虚拟机却提示无法找到模块。这是因为操作系统缺少DLL所依赖的运行库文件。若不想把自己的宝贝工具弄得像超生游击队，只有想办法把库代码直接封装进DLL中。我使用两种方式来解决：
（1）右键打开项目的属性，C/C++ - 代码生成 – 运行库 – 将/MD选项改为/MT（或/MTd）选项。再次编译，立刻发现生成的dll臃肿了不少，问题解决。
（2）把源代码放到vc6下编译。我首先尝试利用vs2015环境来辅助vc6的编译。不曾想捅了一个危险的马蜂窝，弹出了一大串与头文件有关的警告和错误，想来微软也懒得保持那代价高昂的兼容性了。前后折腾了半小时，乖乖放弃。接着切换到vs2010环境，无须任何的修改，vc6顺利完成编译，问题解决。
在vc6中如何编译64位程序？山寨某前辈的经验给懒汉们参考一下：（以vs2010为例）
分为3个步骤：

### 创建64位环境
在开始菜单下点击“Visual Studio 2010 – Visual Studio Tools – Visual Studio x64 兼容工具命令提示(2010)”，然后在此命令提示符中运行VC6：
D:\vc\vc6common\MSDev98\Bin\MSDEV.EXE /useenv

![原文图片](/media/2016/06/9bbcd076713177afc9c80b3b2c9e37ee)

图5 x64 兼容工具命令提示

### 修改配置
复制Release或Debug的配置，起名xx64,激活该配置。(1、Build – Configurations – Add…；2、Build – Set Active Configuration…)

![原文图片](/media/2016/06/b2a6fbbbd2a41c9089d48acf6c2003af)

图6 新建Win32 Debug64配置

### 修改项目配置
1、在“Project Settings”对话框中, 点击“General”标签. 在“Output directories”, 在“Intermediate files” 和“Output files”输入框中, 键入“Debug64”(无引号)

![原文图片](/media/2016/06/df9f6e36b69921b0843208e2c76b7056)

图7 设置输出路径
2、在“C/C++”标签上, “Debug info”下拉列表中, 选择“Program database”(参数选项对应是 /Zi)

![原文图片](/media/2016/06/48ffe1a8574f8e620bcad5eb1865f523)

图8 设置调试信息
3、在“Link”标签上, “Project options”的输入框中, 将“/machine:I386”改为“/machine:AMD64”（命令中将出现两个不同的/machine）

![原文图片](/media/2016/06/310be9f22bffcf0354761e4295b553bc)

图9 设置编译指令
编译后成功得到大腹便便的64位dll。

## （3） w3wp.exe遇到无法处理的异常，网站崩溃
我的破坏力还蛮大的。把本机调试通过的程序放到服务器上运行，瞬间弄崩了网站，重启IIS后再执行几次，服务器蓝屏死机。
代码基本未变，我只是把它封装在DLL中，由w3wp.exe加载运行，怎么就崩溃了呢。长夜漫漫，无心睡眠，在迷糊中把IIS 7应用程序池从集成模式调整为经典模式后，异常莫明地消失了。比尔，你想玩死人么 ？集成与经典模式到底是怎么一回事？

![原文图片](/media/2016/06/c75e850d521683aa0c372372c1627677)

图10 集成模式处理管道
八股文常常引用上图说明IIS7的集成模式。IIS 7集成了ASP.NET运行库，使用统一的管道来处理请求。经典模式兼容了IIS 6，如下图所示，处理ASP.NET动态请求的只是IIS的一个插件——ISAPI模块。同理，PHP插件只处理PHP请求，它们各司其职，互不干涉。两种模式的差别泾渭分明。

![原文图片](/media/2016/06/cad301470dfb2c9b99cd7d907a4deee6)

图11 经典模式处理流程
还有一些值得阅读的入门文档，如《ASP.NET Application Life Cycle Overview for IIS 7.0》，概述了ASP.NET应用程序在生命周期里发生了什么事情。《IIS Modules Overview》主要说明模式及其配置方式。从IIS 7的模块界面可以看到，集成模式使用托管代码模块来处理.aspx页面的请求，而经典模式使用本机代码模块。

![原文图片](/media/2016/06/f3bda3bee328fe52d7d8692359176e88)

图12 请求处理模块
书读了不少，按流行俚语,“然而这并没有什么ruan用”，还缺少某种催化剂，看来我只能再次求助于Windbg了。
使用kp命令查看异常发生时的调用栈，前后几十个调用，秘密就隐藏在它们中间。仔细观察两种模式，为了运行我的代码，最后都从托管环境转入了非托管环境，非托管环境下的调用完全相同。完蛋了，究竟要从哪里下手哇。咬咬牙，开始跟吧，十多天下来，人比黄花瘦。
今天是个好日子，我升级了。我察觉自己又犯了老毛病，没有认真检查关键语句的运行结果。

```text
if (x) { // y }
```
经典模式下x为真，这符合预期，然而在操蛋的集成模式下为假，稍不留神就忽略了。再追溯x的来源，终于发现集成模式似乎由于缓存的影响，对本机DLL的变化不能做出实时的更新。
解决起来简单而粗暴，加个else直接返回错误提示。我并不介意多运行三两次，谁让它们不是我的机子呢，等集成模式睡醒了，更新了它的缓存，权限也到手了。

## （4） w3wp.exe已提升为System权限，但Shell权限不变。
经过第三阶段的苦战，我的代码终于可以稳定地运行了，但又出现了一个奇怪的现象。
在提升权限之前，查询应用程序池的用户：

![原文图片](/media/2016/06/963bbca69d38e82566d87805dbc2ea9e)

图13 提权前用户
此时，75804号进程，即w3wp.exe拥有IIS_IUSRS用户权限。紧接着提升权限，页面显示了执行结果：

![原文图片](/media/2016/06/e113cc2c46bc478788c16eb6d14c58d5)

图14 Shell代码提权
请忽略若干无用的调试信息，我们需要关注的是最后三行。w3wp.exe的进程号仍为75804（也可能会创建新的w3wp.exe），用户权限已提升为SYSTEM。
让我们再次查看此时应用程序池的用户：

![原文图片](/media/2016/06/32e677ea22f20ee8068c8ea639bf3edf)

图15 提权成功后用户未变
怪哉？应用程序池的用户为何没有改变呢？
晚饭时间到了，补充足够的能量后，我已经想到了一种可能。IIS启用了模拟功能后，即使w3wp.exe以SYSTEM权限运行，应用程序池用户仍保持不变。
为了验证这种可能性，让我们亮剑，它就是用来中断模拟的API——RevertToSelf。美女现身，Beautiful!

![原文图片](/media/2016/06/6e05bbfd353ddff186381715b9ed1847)

图16 强制中断IIS模拟恢复用户身份

# 0x03 胜利的号角
西元201x年，001号大杀器研制成功，为纪念cctv，我把它命名为cv51。

![原文图片](/media/2016/06/fdad6a6ec74f043b604a06152bb3dd5e)

图17 未能进入内核无权读写致出错
首次执行出错，不必理会，再次尝试执行。

![原文图片](/media/2016/06/cf18fff52fd561d973718176b21cb3b3)

图18 提权成功
cv51的兄弟cvbb此时运行在SYSTEM权限下，顺利添加用户tingting。

![原文图片](/media/2016/06/cdc9d8cdb27f85534e7ebf16a88c4d7e)

图19 其他工具共享同一个w3wp.exe的权限
登录服务器。任务管理器中，w3wp.exe（PID：22324）在提升权限后的身份可能显示为SYSTEM，也可能保持不变。

![原文图片](/media/2016/06/92043094dc5d2206a020beaf8d970cec)

图20 模拟使任务管理器不能真实显示Shell的权限
查看管理员的密码，统计是否有使用强密码的习惯，获得的密码仅用于学习与研究，将在24小时内删除。上传mimikatz，在命令提示符下执行如下命令，所有登录用户尽在日志中：（mimikatz是开源工具，请到官方站点下载，不吃毒苹果）
mimikatz log privilege::debug sekurlsa::logonpasswords

![原文图片](/media/2016/06/01d7beb3427ac2f5b7a6f9de1ee4b99d)

图21 mimikatz破解曾经登录的用户信息
以上测试在Windows 2003、2008及R2版本中通过。

## Replies
