首页
社区
课程
招聘
[原创]利用堆栈回溯来编写驱动防火墙
发表于: 2010-11-17 10:04 19254

[原创]利用堆栈回溯来编写驱动防火墙

2010-11-17 10:04
19254

通过堆栈回溯来挂钩未导出函数MmLoadSystemImage来进行拦截驱动,在这里感谢sudami大牛的帮助,本文已发在2010《黑客防线》第11期杂志上,代码如下:


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 7
支持
分享
最新回复 (24)
雪    币: 535
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
sofa ?
2010-11-17 13:11
0
雪    币: 71
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
不错的一个软件
2010-11-17 13:25
0
雪    币: 2105
活跃值: (424)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
我是来打酱油的
2010-11-17 14:06
0
雪    币: 315
活跃值: (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
我是来替微点喊冤的。
拦截驱动不必要这么麻烦,瞎搞!
2010-11-17 16:23
0
雪    币: 8835
活跃值: (2404)
能力值: ( LV12,RANK:760 )
在线值:
发帖
回帖
粉丝
6
这么复杂**~
话说,我一直是搜索的~
2010-11-17 22:06
0
雪    币: 364
活跃值: (1736)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
很好,学习。
2010-11-17 22:42
0
雪    币: 141
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
精彩啊,学习~~
2010-11-17 23:40
0
雪    币: 239
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
和拦截ntcreatesection哪个效果好呀?
2010-11-18 23:16
0
雪    币: 8835
活跃值: (2404)
能力值: ( LV12,RANK:760 )
在线值:
发帖
回帖
粉丝
10
正规拦截应该是NtCreateSection
2010-11-19 05:26
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
11
引用:
但是通过我跟踪堆栈调用,发现要经过很多次KeWaitForSingleObject调用,才会进入


因为楼主没有主动触发,所以要多次.以下代码是主动触发,马上就能定位了啊

VOID
Get_MmLoadSystemImage_addr(
        )
/*++

Author: sudami [sudami@163.com]
Time  : 2008/12/23 [23:12:2008 - 10:23]

Routine Description:
  获取MmLoadSystemImage的地址

--*/

{
        NTSTATUS status ;
        UNICODE_STRING  uFileName;
    STRING                        ntNameString;
        CCHAR ntNameFile[128] = "\\??\\C:\\";

       
        // 得到 KeWaitForSingleObject 原始地址
        g_KeWaitForSingleObject_dwAddr = GetExportFuncOrigAddr( "KeWaitForSingleObject", FALSE ) ;
        dprintf( "KeWaitForSingleObject:0x%08lX \n", g_KeWaitForSingleObject_dwAddr ) ;
        if ( 0 == g_KeWaitForSingleObject_dwAddr ) {
                g_KeWaitForSingleObject_dwAddr = (ULONG) GetNativeFunctionBaseAddress( L"KeWaitForSingleObject" ) ;
        }

        // 得到 RtlImageNtHeader 原始地址
        g_RtlImageNtHeader_addr = GetExportFuncOrigAddr( "RtlImageNtHeader", FALSE ) ;
        dprintf( "RtlImageNtHeader:0x%08lX \n", g_RtlImageNtHeader_addr ) ;
        if ( 0 == g_RtlImageNtHeader_addr ) {
                g_RtlImageNtHeader_addr = (ULONG) GetNativeFunctionBaseAddress( L"RtlImageNtHeader" ) ;
        }
       
        // 今天才发现,不需要释放任何驱动,随便给ZwSetSystemInformation乱传一个路径,都会进入到
        // MmLoadSystemImage函数中,这就够了  by sudami 26-12-2008
//         GenerateRandomStrings(5) ; // 产生随机名
//         if ( NULL != szTmpName )
//         {
//                 sprintf( ntNameFile+strlen(ntNameFile), "%s.sys", szTmpName );
//                 RtlInitAnsiString( &ntNameString, ntNameFile);
//                 RtlAnsiStringToUnicodeString( &uFileName, &ntNameString, TRUE );
//
//                 // 释放测试驱动到C盘根目录
//                 status = PutFile( uFileName.Buffer, Test_sys_data, sizeof(Test_sys_data) ) ;
//
//                 RtlFreeUnicodeString(&uFileName);
//
//         } else {
//                 sprintf( ntNameFile+strlen(ntNameFile), "%s.sys", szTmpName );
//                 RtlInitAnsiString( &ntNameString, ntNameFile);
//                 RtlAnsiStringToUnicodeString( &uFileName, &ntNameString, TRUE );
//                
//                 wcscpy( g_wszName, uFileName.Buffer ) ;
//                
//                 RtlFreeUnicodeString(&uFileName);
//
//                 status = PutFile( g_wszName, Test_sys_data, sizeof(Test_sys_data) ) ;
//         }
//        
//         if ( STATUS_SUCCESS != status )
//         {
//                 dprintf( "PutFile(), Failed!\n" ) ;
//                 return ;
//         }
/*++

  先瞬间Inline Hook掉KeWaitForSingleObject,再迅速调用ZwSetSystemInformation加载释放到C盘
  根目录的测试驱动.这样会马上执行到钩子里,在此进行栈回溯,最终定位到NtSetSystemInformation
  函数调用MmLoadSystemImage的地方. 得到MmLoadSystemImage的地址.

  by sudami

--*/

        g_ntoskrnl_addr = g_ntoskrnl_info.ntoskrnl_addr ;
        g_ntoskrnl_size = g_ntoskrnl_info.ntoskrnl_size ;

        if ( !MmIsAddressValid( (PVOID)g_KeWaitForSingleObject_dwAddr )
                || g_KeWaitForSingleObject_dwAddr < g_ntoskrnl_addr )
        {
                dprintf( "g_KeWaitForSingleObject_dwAddr, INVALID,Failed!\n" ) ;
                return ;
        }

        if( !HookCode95( (PVOID)g_KeWaitForSingleObject_dwAddr, (PVOID)fake_KeWaitForSingleObject, InlineHookPre1) )
        {        // 前5字节JMP.
                dprintf( "HOOK KeWaitForSingleObject, Failed!\n" ) ;
                return ;
        }

        g_tmpThread_for_KeWaitForSingleObject = (ULONG)KeGetCurrentThread();

        load_sysfile( g_wszName ) ;

        g_tmpThread_for_KeWaitForSingleObject = 0 ;
       
        UnhookCode95( fake_KeWaitForSingleObject ) ;
}
2010-11-19 15:49
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
12
VOID
fake_KeWaitForSingleObject (
        )
/*++

Author: sudami [sudami@163.com]
Time  : 2008/12/23 [23:12:2008 - 7:16]

Routine Description:

  栈回溯获取MmLoadSystemImage的地址
  win2k下的调用链如下:
  NtSetSystemInformation->MmLoadSystemImage->MiLoadSystemImage->KeWaitForSingleObject
  
  XP以后没有中间那个>MiLoadSystemImage了.在WIN2K里要多回溯一次
  
--*/

{
        PULONG PEBP ;
        UCHAR *pOpcode;

        __asm {
        pushfd
                pushad
        }

        g_bIsW2k = g_hardOffset.bIsW2k ;

        if ( g_tmpThread_for_KeWaitForSingleObject != (ULONG)KeGetCurrentThread() ) {
                goto _over_for_KeWaitForSingleObject ;
        }
   
        __asm
    {
//                int 3
                mov   PEBP, ebp
                mov   ebx, PEBP
                mov   ebx, [ebx]   ; 回溯1次到 call MmLoadSystemImage 空间
                cmp   g_bIsW2k, 1
                jnz   _xp_
                mov   ebx, [ebx]   ; WIN2K 要多回溯一次
_xp_:
                mov   ebx, [ebx+4] ; ebp+4 --> ret Addr
                sub   ebx, 5       ; ret-5 --> 由ret Addr到Call MmLoadSystemImage
                mov   edi, ebx     ; edi 保存Call MmLoadSystemImage处的地址
/*++

  ChildEBP RetAddr  Args to Child              
  f9df7804 805a370b 80554440 00000012 00000000 VirusAnalyse!fake_KeWaitForSingleObject+0x1f
  f9df79b0 80605cb2 f9df7b38 00000000 00000000 nt!MmLoadSystemImage+0x1a9     <-- 触发成功
  f9df7b60 8053da28 00000026 f9df7bf8 00000008 nt!NtSetSystemInformation+0x37c<-- 到达内核态Nt*
  f9df7b60 804ff711 00000026 f9df7bf8 00000008 nt!KiFastCallEntry+0xf8        <-- 经过中转站
  f9df7be4 f9ca5053 00000026 f9df7bf8 00000008 nt!ZwSetSystemInformation+0x11
  f9df7c0c f9ca4d0d f9df7c2c 816f5cf8 e13b815a VirusAnalyse!load_sysfile+0xc3 <-- 我们来触发它
  f9df7c50 6f526d65 535c746f 65747379 5c32336d VirusAnalyse!Get_MmLoadSystemImage_addr+0x16d

  nt!NtSetSystemInformation+0x365:
  
        80605ca5 6a01            push    1
        80605ca7 57              push    edi
        80605ca8 57              push    edi
        80605ca9 8d45d8          lea     eax,[ebp-28h]
        80605cac 50              push    eax  ; 我们关心的参数,里面包含加载模块的全路径
        80605cad e8b0d8f9ff      call    nt!MmLoadSystemImage (805a3562)  <-- 回溯到这里

        80605cb2 8945e4          mov     dword ptr [ebp-1Ch],eax
        80605cb5 3bc7            cmp     eax,edi
        80605cb7 0f8cb0040000    jl      nt!NtSetSystemInformation+0x837 (8060616d)
        80605cbd ff75ac          push    dword ptr [ebp-54h]
        80605cc0 e82b52f2ff      call    nt!RtlImageNtHeader (8052aef0)   <-- 校验

        80605cc5 898568ffffff    mov     dword ptr [ebp-98h],eax
        80605ccb 3bc7            cmp     eax,edi
        80605ccd 7512            jne     nt!NtSetSystemInformation+0x3ab (80605ce1)
        80605ccf ff75b4          push    dword ptr [ebp-4Ch]
        80605cd2 e8efd2f9ff      call    nt!MmUnloadSystemImage (805a2fc6)<-- 顺便可以把它的地址找到

        80605cd7 b87b0000c0      mov     eax,0C000007Bh
        80605cdc e98c040000      jmp     nt!NtSetSystemInformation+0x837 (8060616d)

--*/

                cmp   g_ntoskrnl_addr, 0
                jz    _Skip2Check_
                cmp   g_ntoskrnl_size, 0
                jz    _Skip2Check_

                mov   ecx, g_ntoskrnl_addr
                cmp   ebx, ecx
                jbe   short _over_for_KeWaitForSingleObject
                mov   edx, g_ntoskrnl_size
                lea   esi, [edx+ecx]
                cmp   ebx, esi
                jnb   short _over_for_KeWaitForSingleObject
                lea   edx, [ebx+30h]
                cmp   edx, ecx
                jbe   short _over_for_KeWaitForSingleObject
                cmp   edx, esi
                jnb   short _over_for_KeWaitForSingleObject

_Skip2Check_: //  是在内核地址范围内,进一步验证
                add   ebx, 5                        ; 到达 call nt!MmLoadSystemImage 的下一条指令                     
                xor   ecx, ecx                        ; ecx == 每次调用SizeOfCode得到的指令长度
_loop_:                                                        ; 以ebx为起始地址, 往下30h字节内搜索第一个CALL,进行校验
               
//                cmp   BYTE ptr [ecx+ebx], 0E8h // 这样找太危险了.刚好碰到一种情况,数据是E8,而不是指令
                // 从而导致地址不可读,BSOD了.这里还是应该带一个反汇编引擎

                push  ecx               ; -->保存ecx
                mov   pOpcode, 0
                lea   edx, pOpcode      ; UCHAR *pOpcode; [OUT] 指令内容                               
                lea   ecx, [ecx+ebx]    ; UCHAR *cPtr ;   [IN] 当前指令地址
                call  SizeOfCode        ; Length = SizeOfCode(cPtr, &pOpcode); // fastcall类型
                // 此时ecx的值变成0了.故之前需要保存下

                pop   ecx                                ; -->恢复ecx
                add   ecx, eax          ; ecx += Length
        cmp   eax,0            
                jnz    __dddd__                       
                inc   ecx                                ; 若反汇编引擎计算的长度为0,则地址也要往后累加
                jmp   _next_

__dddd__:
                mov   esi, [pOpcode]    ; esi得到当前指令的地址
                cmp   BYTE ptr [esi], 0E8h  ; 是否为CALL指令
                jnz   short _next_       
/*++

传进来的地址明明是正确的,但一调用MmIsAddressValid就崩,很奇怪很奇怪~~
STACK_TEXT:  
WARNING: Frame IP not in any known module. Following frames may be wrong.
f9defc54 535c746f 65747379 5c32336d 726b746e 0x6f526d65
f9defc7c 805767dc 815d8b10 814b7000 00000000 0x535c746f  <-- 明显是EIP执行到了非法地址
f9defd4c 805768eb 000000a8 00000001 00000000 nt!IopLoadDriver+0x66c
f9defd74 80534fe6 000000a8 00000000 817bbda8 nt!IopLoadUnloadDriver+0x45
f9defdac 805c5cce f7fe2cf4 00000000 00000000 nt!ExpWorkerThread+0x100
f9defddc 805421c2 80534ee6 00000001 00000000 nt!PspSystemThreadStartup+0x34
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16

//                 push  esi                                ; esi == call RtlImageNtHeader 处的地址
//                 call  MmIsAddressValid
//                 cmp   eax, 0
//                 jz    _next_
//
//                 lea   edx, [esi+1]
//                 push  edx
//                 call  MmIsAddressValid
//                 cmp   eax, 0
//                 jz    _next_

--*/

                lea   edx, [esi+1]                ; esi == call RtlImageNtHeader 处的地址
                mov   eax, [edx]                ; eax == offset
                lea   eax, [esi+eax+5]
                cmp   eax, g_RtlImageNtHeader_addr
                jz    _ok_
_next_:
                cmp   ecx, 30h
                jb    short _loop_
                jmp   _over_for_KeWaitForSingleObject

_ok_:
                lea   ecx, [edi+1]  ; edi 保存Call MmLoadSystemImage处的地址
                mov   edx, [ecx]
                lea   eax, [edx+edi+5]
                mov   g_MmLoadSystemImage_addr, eax ; OK! Well Done.

_over_for_KeWaitForSingleObject:
        popad                        // VOID 类型的申明,非naked.所以编译器会自动生成一些垃圾代码
        popfd                        // 这里的pop是为了平衡堆栈.必须的
                pop     edi
                pop     esi
        pop     ebx
//                pop     ecx     // 若只有一个局部变量,编译器可能会:push ecx,而不是sub esp,4
                                                // 故调用wy的引擎,在fake函数中若用到局部变量,最好>=2 counts

                add     esp, 8  // 清掉局部变量

        _emit   0x5D    // pop ebp
        _emit   0x68    // push xxxx
        _emit   0x00
        _emit   0x00
        _emit   0x00
        _emit   0x00
        ret
    }
}
2010-11-19 15:52
0
雪    币: 596
活跃值: (449)
能力值: ( LV12,RANK:320 )
在线值:
发帖
回帖
粉丝
13
我是来膜拜sudami的
2010-11-19 15:56
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
14
BOOL
load_sysfile(
        IN WCHAR* szPath
        )
{
        PEPROCESS proc                = NULL ;
        BOOL      bSuccess        = FALSE ;
        NTSTATUS  status    = STATUS_SUCCESS ;
        SYSTEM_LOAD_AND_CALL_IMAGE GregsImage ;
       
        if ( NULL == szPath ) {
                return 0 ;
        }

        // 2次调用,得到csrss.exe的EPROCESS
        status =  GetEProcessByName_QueryInfo( L"CSRSS.EXE", &proc );
        if( !NT_SUCCESS(status) )
        {
                status = GetEProcessByName_QueryInfo( L"csrss.exe", &proc );
                if( !NT_SUCCESS(status) ) return 0 ;
        }

        GregsImage.ModuleName.Buffer = ExAllocatePoolWithTag(0, 100, ' kdD');
        if ( NULL == GregsImage.ModuleName.Buffer )
        {
                dprintf( "load_sysfile() ExAllocatePool() Failed\n" ) ;
                return 0 ;
        }

    wcscpy( GregsImage.ModuleName.Buffer, szPath);
        GregsImage.ModuleName.Length = 2 * wcslen( szPath );
    GregsImage.ModuleName.MaximumLength = 100 ;
       
//        RtlInitUnicodeString( &(GregsImage.ModuleName), szPath );
        KeAttachProcess (proc); // 附着到此进程

        status = ZwSetSystemInformation(
                SystemLoadAndCallImage,
                &GregsImage,
                sizeof(SYSTEM_LOAD_AND_CALL_IMAGE) ) ;

        KeDetachProcess();      // Detach

        if( !NT_SUCCESS( status ) )
        {
                dprintf( "load_sysfile() Failed: 0x%08lx\n", status ) ;
                ExFreePool( GregsImage.ModuleName.Buffer);
                return 0 ;
        }
       
        ExFreePool( GregsImage.ModuleName.Buffer);

        dprintf( "load_sysfile() ,OK!!!\n" ) ;
        return 1 ;
}
2010-11-19 15:59
0
雪    币: 321
活跃值: (271)
能力值: ( LV13,RANK:1050 )
在线值:
发帖
回帖
粉丝
15
呵呵,还是大米的代码看上去清然脱俗, 眼前一亮.
2010-11-22 09:58
0
雪    币: 266
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
强力 mark
2010-11-22 14:30
0
雪    币: 379
活跃值: (152)
能力值: ( LV12,RANK:330 )
在线值:
发帖
回帖
粉丝
17
刚刚回来就看到sudami牛的代码,代码写的果然清新脱俗,别具一格,十分感谢,继续向大米兄学习
2010-11-25 15:12
0
雪    币: 160
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
回后再看.哈哈......
2010-11-25 15:17
0
雪    币: 88
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
这么好的文章我第一次看的时候居然没MARK,哎~
2010-11-25 17:03
0
雪    币: 255
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
呵呵···不懂··········
2010-11-25 17:42
0
雪    币: 56
活跃值: (37)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
是什么编程语言啊,看不大懂!!
2010-11-26 12:43
0
雪    币: 585
活跃值: (573)
能力值: ( LV13,RANK:290 )
在线值:
发帖
回帖
粉丝
22
驱动层的HOOK,, 留贴备查,,,
2010-12-8 18:46
0
雪    币: 6092
活跃值: (734)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
23
mark...........牛啊
2010-12-8 19:37
0
雪    币: 55
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
mark,学习了
2010-12-9 16:11
0
雪    币: 384
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
精彩啊,学习~~
2010-12-10 10:32
0
游客
登录 | 注册 方可回帖
返回
//