原文地址:http://drops.wooyun.org/tips/9650

(本文纯属虚构,如有雷同,实属巧合)

大名鼎鼎的AspxSpy,罹患间歇性木马病而被白道追杀。大权在握的系统管理员,冷笑着把这位老兄打入了十八层地狱。拒绝访问,组件错误……连最宝贵的命令执行都要封杀。是可忍孰不可忍!拿上小馒头,我闭关数月。

国庆悄然而至,满街地小姐们穿着爆乳装,齐P小短裙(学名硬得快)挤来挤去,中国首位本土科学家获得了诺贝尔奖,我静静地呆在家中,把这漫长的历练一一道来,以飨读者。

0x00 词汇约定


非专业术语,仅供娱乐:

Shell:这里指类似于命令提示符的,一个用于运行可执行文件的C#页面。

本机代码:与托管代码对应。

0x01 *外杀马


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

图1 *外杀马

图2 IDA中的*外杀马

一个名为NotifyRoutine的函数引起了我的兴趣。这是一个回调函数,它的伪代码如下:

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的伪代码如下:

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编译器的命令,否则源代码就无法在线编译执行了。它允许的命令:

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等命令。

所有命令都带有完整路径,如果拥有足够的权限(如提权成功后),可将存在于白名单且系统中一般不存在的命令替换为你的工具,如:

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

图5 x64 兼容工具命令提示

修改配置

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

图6 新建Win32 Debug64配置

修改项目配置

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

图7 设置输出路径

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

图8 设置调试信息

3、在“Link”标签上, “Project options”的输入框中, 将“/machine:I386”改为“/machine:AMD64”(命令中将出现两个不同的/machine)

图9 设置编译指令

编译后成功得到大腹便便的64位dll。

(3) w3wp.exe遇到无法处理的异常,网站崩溃

我的破坏力还蛮大的。把本机调试通过的程序放到服务器上运行,瞬间弄崩了网站,重启IIS后再执行几次,服务器蓝屏死机。

代码基本未变,我只是把它封装在DLL中,由w3wp.exe加载运行,怎么就崩溃了呢。长夜漫漫,无心睡眠,在迷糊中把IIS 7应用程序池从集成模式调整为经典模式后,异常莫明地消失了。比尔,你想玩死人么 ?集成与经典模式到底是怎么一回事?

图10 集成模式处理管道

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

图11 经典模式处理流程

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

图12 请求处理模块

书读了不少,按流行俚语,“然而这并没有什么ruan用”,还缺少某种催化剂,看来我只能再次求助于Windbg了。

使用kp命令查看异常发生时的调用栈,前后几十个调用,秘密就隐藏在它们中间。仔细观察两种模式,为了运行我的代码,最后都从托管环境转入了非托管环境,非托管环境下的调用完全相同。完蛋了,究竟要从哪里下手哇。咬咬牙,开始跟吧,十多天下来,人比黄花瘦。

今天是个好日子,我升级了。我察觉自己又犯了老毛病,没有认真检查关键语句的运行结果。

if (x)
{
    // y
}

经典模式下x为真,这符合预期,然而在操蛋的集成模式下为假,稍不留神就忽略了。再追溯x的来源,终于发现集成模式似乎由于缓存的影响,对本机DLL的变化不能做出实时的更新。

解决起来简单而粗暴,加个else直接返回错误提示。我并不介意多运行三两次,谁让它们不是我的机子呢,等集成模式睡醒了,更新了它的缓存,权限也到手了。

(4) w3wp.exe已提升为System权限,但Shell权限不变。

经过第三阶段的苦战,我的代码终于可以稳定地运行了,但又出现了一个奇怪的现象。

在提升权限之前,查询应用程序池的用户:

图13 提权前用户

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

图14 Shell代码提权

请忽略若干无用的调试信息,我们需要关注的是最后三行。w3wp.exe的进程号仍为75804(也可能会创建新的w3wp.exe),用户权限已提升为SYSTEM。

让我们再次查看此时应用程序池的用户:

图15 提权成功后用户未变

怪哉?应用程序池的用户为何没有改变呢?

晚饭时间到了,补充足够的能量后,我已经想到了一种可能。IIS启用了模拟功能后,即使w3wp.exe以SYSTEM权限运行,应用程序池用户仍保持不变。

为了验证这种可能性,让我们亮剑,它就是用来中断模拟的API——RevertToSelf。美女现身,Beautiful!

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

0x03 胜利的号角

西元201x年,001号大杀器研制成功,为纪念cctv,我把它命名为cv51。

图17 未能进入内核无权读写致出错

首次执行出错,不必理会,再次尝试执行。

图18 提权成功

cv51的兄弟cvbb此时运行在SYSTEM权限下,顺利添加用户tingting。

图19 其他工具共享同一个w3wp.exe的权限

登录服务器。任务管理器中,w3wp.exe(PID:22324)在提升权限后的身份可能显示为SYSTEM,也可能保持不变。

图20 模拟使任务管理器不能真实显示Shell的权限

查看管理员的密码,统计是否有使用强密码的习惯,获得的密码仅用于学习与研究,将在24小时内删除。上传mimikatz,在命令提示符下执行如下命令,所有登录用户尽在日志中:(mimikatz是开源工具,请到官方站点下载,不吃毒苹果)

mimikatz log privilege::debug sekurlsa::logonpasswords

图21 mimikatz破解曾经登录的用户信息

以上测试在Windows 2003、2008及R2版本中通过。