首页
论坛
专栏
课程

[调试逆向] [原创]现学现用之windbg的高级玩法外篇二:干掉QQProtect.sys

2013-10-2 00:17 53874

[调试逆向] [原创]现学现用之windbg的高级玩法外篇二:干掉QQProtect.sys

2013-10-2 00:17
53874
前言
今天是国庆节,先祝大家节日快乐。

今天与大家一起见证怎么废了QQProtect.sys。

环境:
QQ版本:2013正式版 sp2(8178)
调试工具:windbg 6.12
操作系统:xp sp3 32bit
其他工具:lordpe

一直很纳闷,你一个QQ干嘛还要上个保护驱动那?这种行为让楼主很不爽。已知QQProtect会启动内核线程、注册内核通知回调例程、SSDT hook, SSDTShadow hook, inline hook。
打算用 QQProtect来练练手,正好好久没玩内核了,快生疏了。
本篇共分为4部分:
1. kill 内核线程
2. 恢复SSDT hook,inline hook
3. 摘除内核通知回调例程
4. 恢复SSDTShadow hook


当然以上的操作都是使用windbg做的。一起来调戏QQProtect吧。


使用APC KILL内核线程

QQ的保护驱动创建了一些内核线程。楼主找到QQ的内核线程
获取system进程的EPROCESS地址
0: kd> [COLOR=Red]!process 0 0 system[/COLOR]
PROCESS [COLOR=Red][B]867b5830  [/B][/COLOR]SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 06ca0020  ObjectTable: e1002e40  HandleCount: 401.
    Image: System
在获取QQProtect驱动的起始地址和结束地址
0: kd>[COLOR=Red] lm m qq*[/COLOR]
start    end        module name
[COLOR=Red]eee0c000 eee36680[/COLOR]   QQProtect   (deferred)    
遍历进程的线程链,找到起始地址在QQProtect模块空间的线程
获取需要的字段在结构中的偏移
0: kd>[COLOR=Red] r @$t0=@@(#FIELD_OFFSET(nt!_EPROCESS, ThreadListHead))[/COLOR]
0: kd> [COLOR=Red]r @$t1= @@(#FIELD_OFFSET(nt!_ETHREAD, ThreadListEntry))[/COLOR]
0: kd> [COLOR=Red]r @$t2=@@(#FIELD_OFFSET(nt!_ETHREAD, StartAddress))[/COLOR]
遍历线程链,找到QQ的内核线程
0: kd>[COLOR=Red] !list "-t nt!_LIST_ENTRY.FLink -e -x \"r @$t3=@$extret-@$t1; r @$t4= @$t3+@$t2; r @$t5=poi(@$t4);.if(@@((unsigned long)@$t5>(unsigned long)[B]0xeee0c000 [/B]&& (unsigned long)@$t5<(unsigned long)[B]0xeee36680[/B])){r @$t3;dt -b nt!_ETHREAD Cid. @$t3; dds @$t4 l1;}; \" [B]867b5830[/B]+@$t0"
[/COLOR]
r @$t3=@$extret-@$t1; r @$t4= @$t3+@$t2; r @$t5=poi(@$t4);.if(@@((unsigned long)@$t5>(unsigned long)0xeee0c000 && (unsigned long)@$t5<(unsigned long)0xeee36680)){r @$t3;dt -b nt!_ETHREAD Cid. @$t3; dds @$t4 l1;};  
$t3=[COLOR=Red]86699130 [/COLOR][COLOR=Blue]//ETHREAD地址[/COLOR]
   +0x1ec Cid  : 
      +0x000 UniqueProcess : 0x00000004[COLOR=Blue] //进程ID[/COLOR]
      +0x004 UniqueThread : 0x00000160[COLOR=Blue]  //线程ID[/COLOR]
86699354  eee11a0c [COLOR=Red]QQProtect+0x5a0c[/COLOR] [COLOR=Blue]//线程的起始地址[/COLOR]

r @$t3=@$extret-@$t1; r @$t4= @$t3+@$t2; r @$t5=poi(@$t4);.if(@@((unsigned long)@$t5>(unsigned long)0xeee0c000 && (unsigned long)@$t5<(unsigned long)0xeee36680)){r @$t3;dt -b nt!_ETHREAD Cid. @$t3; dds @$t4 l1;};  
$t3=[COLOR=Red]862de020[/COLOR]
   +0x1ec Cid  : 
      +0x000 UniqueProcess : 0x00000004 
      +0x004 UniqueThread : 0x00000164 
862de244  eee22626 [COLOR=Red]QQProtect+0x16626[/COLOR]
通过上面的查找,找到了两个QQProtect的内核线程。找到了线程,接下来该怎么办呢?我们知道用户态线程直接强杀就OK了,但是内核线程强杀是不行的。通常使用APC。使用APC机制结束掉线程。
找到方法,那下一步就是实施了。由于不能调用系统API构建APC,只能手动构建和插入APC了。

第一步先找一块用来构建APC的内存。就在QQProtect中找一处吧。
先看一下QQProtect模块的区段信息
0: kd>[COLOR=Red] !dh -s eee0c000[/COLOR]

SECTION HEADER #1
   [COLOR=Blue].text[/COLOR] name
   1B516 virtual size
     480 virtual address
   1B580 size of raw data
     480 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
68000020 flags
         Code
         Not Paged
         (no align specified)
         Execute Read

SECTION HEADER #2
  [COLOR=Blue].rdata [/COLOR]name
    3A8C virtual size
   1BA00 virtual address
    3B00 size of raw data
   1BA00 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
48000040 flags
         Initialized Data
         Not Paged
         (no align specified)
         Read Only


Debug Directories(1)
    Type       Size     Address  Pointer
    cv           8f       1e868    1e868    Format: RSDS, guid, 1, f:\qqprotectdrvbuild\qqbuilder_qd3.5.1_drv2.9\basic_qqprotectdrv_vob\qqprotectdrv\objfre_wxp_x86\i386\QQProtectSYS.pdb

SECTION HEADER #3
   [COLOR=Blue].data[/COLOR] name
    82AC virtual size
   1F500 virtual address
    8300 size of raw data
   1F500 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
C8000040 flags
         Initialized Data
         Not Paged //不分页内存
         (no align specified)
         Read Write

SECTION HEADER #4
   [B][COLOR=Blue]INIT [/COLOR]name[/B]
     CC6 virtual size
   27800 virtual address
     D00 size of raw data
   27800 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
E2000020 flags
         Code
         Discardable [COLOR=Blue]//可废弃的,初始化完成后,内核可以回收这块内存。[/COLOR]
                                     [COLOR=Blue]//但是由于内核的页粒度为0x1000,INIT段的开始处一部分内存与.data段在同一块内存页中,那此段的前0x200个字节就是理想的APC数据块载体[/COLOR]了
         (no align specified)
         Execute Read Write

SECTION HEADER #5
   [COLOR=Blue].rsrc [/COLOR]name
     310 virtual size
   28500 virtual address
     380 size of raw data
   28500 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
42000040 flags
         Initialized Data
         Discardable [COLOR=Blue]//模块加载完成后,此块的内存就被回收了[/COLOR]
         (no align specified)
         Read Only

SECTION HEADER #6
 [COLOR=Blue] .reloc[/COLOR] name
    1D90 virtual size
   28880 virtual address
    1E00 size of raw data
   28880 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
42000040 flags
         Initialized Data
         Discardable [COLOR=Blue]//模块加载完成后,此块的内存就被回收了[/COLOR]
         (no align specified)
         Read Only
眼前一亮,发现了INIT段。我们就在INIT段中构建APC吧。
INIT起始RVA=27800
0: kd> [COLOR=Red]? eee0c000+27800 [/COLOR]
Evaluate expression: -287098880 = [COLOR=Red]eee33800[/COLOR]
//下面咱们就在eee33800地址处构建APC,已经知道QQ创建了两个线程,需要构建两个APC数据库+一个APC回调函数。
//楼主打算在eee33800处构建回调函数,在eee33900构建第一个线程的APC,在eee33950构建第二个线程的APC
//初始化200字节内存
0: kd> [COLOR=Red].for(r @$t1=0;@$t1<200;r @$t1=@$t1+4) {ed [B]eee33800[/B]+@$t1 0;}[/COLOR]
//获取pspExitthread符号地址
0: kd> [COLOR=Red]x nt!pspExitthread[/COLOR]
[B][COLOR=Red]805d3086 [/COLOR][/B]nt!PspExitThread = <no type information>
//构造APC回调函数,函数很简单,直接调用nt!pspExitthread
0: kd> [COLOR=Red]a eee33800[/COLOR]
eee33800 push 748
[COLOR=Red]push 748[/COLOR]
eee33805 call 805d3086 
[COLOR=Red]call 805d3086 [/COLOR]
eee3380a ret 0n20
[COLOR=Red]ret 0n20[/COLOR]
eee3380d 
//看一下构建好的函数
0: kd> [COLOR=Red]uf eee33800[/COLOR]
QQProtect+0x27800:
eee33800 6848070000      push    748h
eee33805 e87cf87991      call    nt!PspExitThread (805d3086)
eee3380a c21400          ret     14h
//构造第一个线程的APC,并插入原始APC队列里面
0: kd> [COLOR=Red]r @$t0=[B]eee33900[/B]; r @$t1=[B]86699130[/B]; r@$t2=[B]eee33800[/B];?? ((nt!_KAPC*)@$t0)->Type=18;?? ((nt!_KAPC*)@$t0)->Size=sizeof(nt!_KAPC);?? ((nt!_KAPC*)@$t0)->Thread=@$t1;?? ((nt!_KAPC*)@$t0)->KernelRoutine=@$t2;?? ((nt!_KAPC*)@$t0)->Inserted=1;r @$t3=@@(&(((nt!_ETHREAD*)@$t1)->Tcb.ApcState.ApcListHead[0]));r @$t4=@@(&(((nt!_KAPC*)@$t0)->ApcListEntry));r @$t5=@@(((nt!_LIST_ENTRY*)@$t3)->Flink);?? ((nt!_LIST_ENTRY*)@$t4)->Flink=@$t5;?? ((nt!_LIST_ENTRY*)@$t4)->Blink=@$t3;?? ((nt!_LIST_ENTRY*)@$t5)->Blink=@$t4;?? ((nt!_LIST_ENTRY*)@$t3)->Flink=@$t4;?? ((nt!_ETHREAD*)@$t1)->Tcb.ApcState.KernelApcPending=1;[/COLOR]
看一下上一条指令执行结果
0: kd> [COLOR=Red]dt nt!_KAPC @$t0[/COLOR]
   +0x000 Type             : [COLOR=Red]0n18 [/COLOR][COLOR=Blue]//ApcObject=18[/COLOR]
   +0x002 Size             : [COLOR=Red]0n48 [/COLOR][COLOR=Blue]//nt!_KAPC结构大小[/COLOR]
   +0x004 Spare0           : 0
   +0x008 Thread           : [COLOR=Red]0x86699130 [/COLOR]_KTHREAD [COLOR=Blue]//所属线程[/COLOR]
   +0x00c ApcListEntry     : _LIST_ENTRY [[COLOR=Red] 0x86699164 - 0x86699164 [/COLOR]] [COLOR=Blue]//用来插入线程APC队列[/COLOR]
   +0x014 KernelRoutine    : [COLOR=Red]0xeee33800     [/COLOR]void  +0 [COLOR=Blue]//APC内核回调函数指针[/COLOR]
   +0x018 RundownRoutine   : (null) 
   +0x01c NormalRoutine    : (null) 
   +0x020 NormalContext    : (null) 
   +0x024 SystemArgument1  : (null) 
   +0x028 SystemArgument2  : (null) 
   +0x02c ApcStateIndex    : 0 ''
   +0x02d ApcMode          : 0 ''
   +0x02e Inserted         : [COLOR=Red]0x1 [/COLOR]''[COLOR=Blue] //已插入[/COLOR]
0: kd>[COLOR=Red] dt -b nt!_KTHREAD ApcState. @$t1[/COLOR]
   +0x034 ApcState  : 
      +0x000 ApcListHead : 
       [00] _LIST_ENTRY [[COLOR=Red] 0xeee3390c - 0xeee3390c[/COLOR] ] [COLOR=Blue]//原始APC列表[/COLOR]
       [01]  [ 0x8669916c - 0x8669916c ]
      +0x010 Process   : 0x867b5830 
      +0x014 KernelApcInProgress : 0 ''
      +0x015 KernelApcPending : [COLOR=Red]0x1 [/COLOR]'' [COLOR=Blue]//需要处理APC标志[/COLOR]
      +0x016 UserApcPending : 0 ''
   +0x138 ApcStatePointer : 
    [00] 
    [01] 
   +0x165 ApcStateIndex : 0 ''
//构造第二个线程的APC,并插入原始APC队列里面
0: kd> [COLOR=Red]r @$t0=[B]eee33950[/B]; r @$t1=[B]862de020[/B]; r@$t2=[B]eee33800[/B];?? ((nt!_KAPC*)@$t0)->Type=18;?? ((nt!_KAPC*)@$t0)->Size=sizeof(nt!_KAPC);?? ((nt!_KAPC*)@$t0)->Thread=@$t1;?? ((nt!_KAPC*)@$t0)->KernelRoutine=@$t2;?? ((nt!_KAPC*)@$t0)->Inserted=1;r @$t3=@@(&(((nt!_ETHREAD*)@$t1)->Tcb.ApcState.ApcListHead[0]));r @$t4=@@(&(((nt!_KAPC*)@$t0)->ApcListEntry));r @$t5=@@(((nt!_LIST_ENTRY*)@$t3)->Flink);?? ((nt!_LIST_ENTRY*)@$t4)->Flink=@$t5;?? ((nt!_LIST_ENTRY*)@$t4)->Blink=@$t3;?? ((nt!_LIST_ENTRY*)@$t5)->Blink=@$t4;?? ((nt!_LIST_ENTRY*)@$t3)->Flink=@$t4;?? ((nt!_ETHREAD*)@$t1)->Tcb.ApcState.KernelApcPending=1;[/COLOR]
看一下上一条指令执行结果
0: kd> [COLOR=Red]dt nt!_KAPC @$t0;dt -b nt!_KTHREAD ApcState. @$t1;[/COLOR]
   +0x000 Type             : [COLOR=Red]0n18[/COLOR]
   +0x002 Size             : [COLOR=Red]0n48[/COLOR]
   +0x004 Spare0           : 0
   +0x008 Thread           : [COLOR=Red]0x862de020 [/COLOR]_KTHREAD
   +0x00c ApcListEntry     : _LIST_ENTRY [ [COLOR=Red]0x862de054 - 0x862de054[/COLOR] ]
   +0x014 KernelRoutine    : [COLOR=Red]0xeee33800     [/COLOR]void  +0
   +0x018 RundownRoutine   : (null) 
   +0x01c NormalRoutine    : (null) 
   +0x020 NormalContext    : (null) 
   +0x024 SystemArgument1  : (null) 
   +0x028 SystemArgument2  : (null) 
   +0x02c ApcStateIndex    : 0 ''
   +0x02d ApcMode          : 0 ''
   +0x02e Inserted         : [COLOR=Red]0x1 [/COLOR]''
   +0x034 ApcState  : 
      +0x000 ApcListHead : 
       [00] _LIST_ENTRY [ [COLOR=Red]0xeee3395c - 0xeee3395c[/COLOR] ]
       [01]  [ 0x862de05c - 0x862de05c ]
      +0x010 Process   : 0x867b5830 
      +0x014 KernelApcInProgress : 0 ''
      +0x015 KernelApcPending : [COLOR=Red]0x1 [/COLOR]''
      +0x016 UserApcPending : 0 ''
   +0x138 ApcStatePointer : 
    [00] 
    [01] 
   +0x165 ApcStateIndex : 0 ''

恢复SSDT和inline hook

获取SSDT和SSDTShadow地址表及大小
0: kd> [COLOR=Red]dp nt!KeServiceDescriptorTableShadow l8[/COLOR]
8055d6c0 [COLOR=Red] 80505450 [/COLOR]00000000 [COLOR=Red]0000011c [/COLOR]805058c4
8055d6d0 [COLOR=Red] bf999b80 [/COLOR]00000000 [COLOR=Red]0000029b [/COLOR]bf99a890
我们可以看到SSDT地址表起始地址为80505450,函数个数为0x11c
我们可以看到SSDTShadow地址表起始地址为bf999b80,函数个数为0x29b

看一下QQ HOOK了几个函数
SSDT HOOK
0: kd> [COLOR=Red]dps 80505450 l11c[/COLOR]
... [COLOR=Blue]//太多了,忽略一部分[/COLOR]
805054e0  8061795a nt!NtCreateEventPair
[B][COLOR=Blue]805054e4  eee1b6f4 QQProtect+0xf6f4[/COLOR][/B]
805054e8  80579a62 nt!NtCreateIoCompletion
...
80505520  805c49b6 nt!NtCreateSymbolicLinkObject
[B][COLOR=Blue]80505524  eee16768 QQProtect+0xa768[/COLOR][/B]
80505528  80617622 nt!NtCreateTimer
...
80505544  806170d6 nt!NtCancelDeviceWakeupRequest
[B][COLOR=Blue]80505548  eee1b58a QQProtect+0xf58a[/COLOR][/B]
8050554c  80624c16 nt!NtDeleteKey
...
8050561c  80617a32 nt!NtOpenEventPair
[B][COLOR=Blue]80505620  eee1b896 QQProtect+0xf896[/COLOR][/B]
80505624  80579b3a nt!NtOpenIoCompletion
...
80505634  805f541a nt!NtOpenObjectAuditAlarm
[COLOR=Blue][B]80505638  eee208d2 QQProtect+0x148d2[/B][/COLOR]
8050563c  805ee722 nt!NtOpenProcessToken
...
80505670  805f4918 nt!NtPrivilegedServiceAuditAlarm
[B][COLOR=Blue]80505674  eee11b3e QQProtect+0x5b3e[/COLOR][/B]
80505678  8060f7ba nt!NtPulseEvent
[B][COLOR=Blue]8050567c  eee212e2 QQProtect+0x152e2[/COLOR][/B]
80505680  806170e4 nt!NtEnumerateBootEntries
...
8050571c  8057ccea nt!NtQueryVolumeInformationFile
[B][COLOR=Blue]80505720  eee21808 QQProtect+0x15808[/COLOR][/B]
80505724  80545eb4 nt!NtRaiseException
...
80505734  805a6e50 nt!NtReadRequestData
[B][COLOR=Blue]80505738  eee10b76 QQProtect+0x4b76[/COLOR][/B]
8050573c  805d3754 nt!NtRegisterThreadTerminatePort
...
805057a0  806170e4 nt!NtEnumerateBootEntries
[B][COLOR=Blue]805057a4  eee20d54 QQProtect+0x14d54[/COLOR][/B]
805057a8  80646ce0 nt!NtSetDebugFilterState
...
805057cc  806439f2 nt!NtSetInformationDebugObject
[B][COLOR=Blue]805057d0  eee1b4ce QQProtect+0xf4ce[/COLOR][/B]
805057d4  805d7928 nt!NtSetInformationJobObject
...
[COLOR=Black]80505848  805d58b0 nt!NtSuspendThread[/COLOR][B][COLOR=Blue]
8050584c  eee20e38 QQProtect+0x14e38[/COLOR][/B]
80505850  805d84bc nt!NtTerminateJobObject
[COLOR=Blue][B]80505854  eee20a0a QQProtect+0x14a0a[/B][/COLOR]
80505858  805d3b98 nt!NtTerminateThread
...
805058a0  805a6e78 nt!NtWriteRequestData
[COLOR=Blue][B]805058a4  eee10fa4 QQProtect+0x4fa4[/B][/COLOR]
805058a8  80505ad8 nt!NtYieldExecution
...
擦这货HOOK的地方还真不少,由于我的测试环境比较干净,只看到QQ的HOOK。SSDT被 HOOK了14

再看一下SSDTShadow HOOK
0: kd>[COLOR=Red] dps bf999b80 l29b[/COLOR]
bf999b80  ????????
bf999b84  ????????
bf999b88  ????????
bf999b8c  ????????
bf999b90  ????????
bf999b94  ????????
bf999b98  ????????
bf999b9c  ????????
bf999ba0  ????????
bf999ba4  ????????
bf999ba8  ????????
bf999bac  ????????
bf999bb0  ????????
bf999bb4  ????????
bf999bb8  ????????
为啥都不能访问呢?看一下bf999b80所属地址
0: kd>[COLOR=Red] !address bf999b80[/COLOR]
  bf800000 - 001c3000                           
          Usage       KernelSpaceUsageImage
          ImageName   win32k.sys
win32k.sys。win32k.sys是win32子系统内核模块,只有当前线程是win32k线程时才能看到win32k驱动内存,否则win32k被换到外存。那只有等会拦截一个属于win32的线程再做打算了。
既然知道HOOK 了SSDT,那怎么恢复呢?怎么知道HOOK前的值是神马呢?

0: kd> [COLOR=Red]!chkimg -d nt[/COLOR]
    805054e4-805054e7  4 bytes - nt!KiServiceTable+94
    [ [COLOR=Blue]84 a0 57 80[/COLOR]:f4 b6 e1 ee ]
    80505524-80505527  4 bytes - nt!KiServiceTable+d4 (+0x40)
    [[COLOR=Blue] d4 1f 5d 80[/COLOR]:68 67 e1 ee ]
    80505548-8050554b  4 bytes - nt!KiServiceTable+f8 (+0x24)
    [ [COLOR=Blue]2c 7c 57 80[/COLOR]:8a b5 e1 ee ]
    80505620-80505623  4 bytes - nt!KiServiceTable+1d0 (+0xd8)
    [ [COLOR=Blue]82 b1 57 80[/COLOR]:96 b8 e1 ee ]
    80505638-8050563b  4 bytes - nt!KiServiceTable+1e8 (+0x18)
    [ [COLOR=Blue]fc c3 5c 80[/COLOR]:d2 08 e2 ee ]
    80505674-80505677  4 bytes - nt!KiServiceTable+224 (+0x3c)
    [[COLOR=Blue] da 93 5b 80[/COLOR]:3e 1b e1 ee ]
    8050567c-8050567f  4 bytes - nt!KiServiceTable+22c (+0x08)
    [ [COLOR=Blue]d6 7e 57 80[/COLOR]:e2 12 e2 ee ]
    80505720-80505723  4 bytes - nt!KiServiceTable+2d0 (+0xa4)
    [ [COLOR=Blue]32 22 5d 80[/COLOR]:08 18 e2 ee ]
    80505738-8050573b  4 bytes - nt!KiServiceTable+2e8 (+0x18)
    [ [COLOR=Blue]8a 52 5b 80[/COLOR]:76 0b e1 ee ]
    805057a4-805057a7  4 bytes - nt!KiServiceTable+354 (+0x6c)
    [ [COLOR=Blue]f6 26 5d 80[/COLOR]:54 0d e2 ee ]
    805057d0-805057d3  4 bytes - nt!KiServiceTable+380 (+0x2c)
    [ [COLOR=Blue]10 c0 57 80[/COLOR]:ce b4 e1 ee ]
    8050584c-8050584f  4 bytes - nt!KiServiceTable+3fc (+0x7c)
    [ [COLOR=Blue]6e 87 61 80[/COLOR]:38 0e e2 ee ]
    80505854-80505857  4 bytes - nt!KiServiceTable+404 (+0x08)
    [ [COLOR=Blue]9e 39 5d 80[/COLOR]:0a 0a e2 ee ]
    805058a4-805058a7  4 bytes - nt!KiServiceTable+454 (+0x50)
    [ [COLOR=Blue]94 53 5b 80[/COLOR]:a4 0f e1 ee ]
    805a2cba-805a2cbd  4 bytes - nt!KeUserModeCallback+8
    [ [COLOR=Blue]c2 9e f9 ff[/COLOR]:c8 45 87 6e ]
    805b3e0f-805b3e15  7 bytes - nt!MmUnmapViewOfSection+17 (+0x11155)
    [ [COLOR=Blue]cc cc cc cc cc 8b ff[/COLOR]:e9 bc bd 85 6e eb f9 ]
[B][COLOR=Blue]67[/COLOR][/B] errors : nt (805054e4-805b3e15)
上面使用!chkimg指令检查了nt模块的所有被修改的数据。(蓝色为原始值)
经过分析,共有对nt!KiServiceTable的HOOK 14处,nt!KiServiceTable存储的就是SSDT地址,也就是SSDT共被HOOK了14处,与刚才咱们看到的一致。那
   
805a2cba-805a2cbd  4 bytes - nt!KeUserModeCallback+8
    [ [COLOR=Blue]c2 9e f9 ff[/COLOR]:c8 45 87 6e ]
    805b3e0f-805b3e15  7 bytes - nt!MmUnmapViewOfSection+17 (+0x11155)
    [ [COLOR=Blue]cc cc cc cc cc 8b ff[/COLOR]:e9 bc bd 85 6e eb f9 ]
这两个是神马?是inline hook!反汇编看一下
0: kd>[COLOR=Red] u nt!KeUserModeCallback[/COLOR]
nt!KeUserModeCallback:
805a2cb2 6a30            push    30h
805a2cb4 6800aa4d80      push    offset nt!KiDebugRegisterContextOffsets+0x24 (804daa00)
805a2cb9 e8c845876e [COLOR=Blue]     call    QQProtect+0xb286 (eee17286)[/COLOR]
805a2cbe e8b9fbf5ff      call    nt!KiGetUserModeStackAddress (8050287c)
805a2cc3 8945e4          mov     dword ptr [ebp-1Ch],eax
805a2cc6 8b18            mov     ebx,dword ptr [eax]
805a2cc8 895dd8          mov     dword ptr [ebp-28h],ebx
805a2ccb 8365fc00        and     dword ptr [ebp-4],0

0: kd> [COLOR=Red]u 805b3e0f [/COLOR]
nt!MmUnmapViewOfSection+0x17:
805b3e0f e9bcbd856e [COLOR=Blue]     jmp     QQProtect+0x3bd0 (eee0fbd0)[/COLOR]
nt!NtUnmapViewOfSection:
805b3e14 ebf9 [COLOR=Blue]           jmp     nt!MmUnmapViewOfSection+0x17 (805b3e0f)[/COLOR]
805b3e16 55              push    ebp
805b3e17 8bec            mov     ebp,esp
805b3e19 51              push    ecx
805b3e1a 56              push    esi
805b3e1b 64a124010000    mov     eax,dword ptr fs:[00000124h]
805b3e21 8a8040010000    mov     al,byte ptr [eax+140h]
原来是HOOK了nt!KeUserModeCallbacknt!NtUnmapViewOfSection。咱们再看看他都HOOK了哪些SSDT
0: kd> [COLOR=Red]ln 8057a084;ln 805d1fd4;ln 80577c2c; ln 8057b182; ln 805cc3fc; ln 805b93da; ln 80577ed6; ln 805d2232;ln 805b528a; ln 805d26f6;ln 8057c010; ln 8061876e; ln 805d399e; ln 805b5394;[/COLOR]
    nt!NtCreateFile = <no type information>
    nt!NtCreateThread = <no type information>
    nt!NtDeleteFile = <no type information>
    nt!NtOpenFile = <no type information>
    nt!NtOpenProcess = <no type information>
    nt!NtProtectVirtualMemory = <no type information>
    nt!NtQueryAttributesFile = <no type information>
    nt!NtQueueApcThread = <no type information>
    nt!NtReadVirtualMemory = <no type information>
    nt!NtSetContextThread = <no type information>
    nt!NtSetInformationFile = <no type information>
    nt!NtSystemDebugControl = <no type information>
    nt!NtTerminateProcess = <no type information>
    nt!NtWriteVirtualMemory = <no type information>
这个变态,HOOK了这么多。楼主要恢复他了
恢复SSDT
0: kd> [COLOR=Red]eb nt!KiServiceTable+94 84 a0 57 80; eb nt!KiServiceTable+d4 d4 1f 5d 80; eb nt!KiServiceTable+f8 2c 7c 57 80; eb nt!KiServiceTable+1d0 82 b1 57 80; eb nt!KiServiceTable+1e8 fc c3 5c 80; eb nt!KiServiceTable+224 da 93 5b 80; eb nt!KiServiceTable+22c d6 7e 57 80;eb nt!KiServiceTable+2d0 32 22 5d 80;eb nt!KiServiceTable+2e8 8a 52 5b 80;eb nt!KiServiceTable+354 f6 26 5d 80;eb nt!KiServiceTable+380 10 c0 57 80;eb nt!KiServiceTable+3fc 6e 87 61 80;eb  nt!KiServiceTable+404 9e 39 5d 80;eb nt!KiServiceTable+454 94 53 5b 80;[/COLOR]
恢复inline hook
0: kd> [COLOR=Red]eb nt!KeUserModeCallback+8 c2 9e f9 ff; eb nt!MmUnmapViewOfSection+17 cc cc cc cc cc 8b ff;[/COLOR]
看一下恢复的效果:
0: kd> [COLOR=Red]!chkimg -d nt[/COLOR]
0 errors : nt 
Ok,恢复完毕

删除内核通知回调

咱们看看QQ有没有注册通知
0: kd>[COLOR=Red] r @$t0=poi([B]nt!PspCreateProcessNotifyRoutineCount[/B]);r @$t1=[B]nt!PspCreateProcessNotifyRoutine[/B];.for(r @$t2=0; @$t2<@$t0; r @$t2=@$t2+1){dds (poi(@$t1+@$t2*4)^7)+4 l1;}[/COLOR]
e170209c  f7710194 vmci!VMCI_DeviceGet+0x96e
0: kd> [COLOR=Red]r @$t0=poi(nt![B]PspCreateThreadNotifyRoutineCount[/B]);r @$t1=nt![B]PspCreateThreadNotifyRoutine[/B];.for(r @$t2=0; @$t2<@$t0; r @$t2=@$t2+1){dds (poi(@$t1+@$t2*4)^7)+4 l1;}[/COLOR]
e1612504  eee15f48 QQProtect+0x9f48
0: kd>[COLOR=Red] r @$t0=poi(nt![B]PspLoadImageNotifyRoutineCount[/B]);r @$t1=nt![B]PspLoadImageNotifyRoutine[/B];.for(r @$t2=0; @$t2<@$t0; r @$t2=@$t2+1){dds (poi(@$t1+@$t2*4)^7)+4 l1;}[/COLOR]
e16249e4  eee190d8 QQProtect+0xd0d8
0: kd>[COLOR=Red] r @$t0=poi(nt![B]CmpCallBackCount[/B]);r @$t1=nt![B]CmpCallBackVector[/B];.for(r @$t2=0; @$t2<@$t0; r @$t2=@$t2+1){dds (poi(@$t1+@$t2*4)^7)+4 l1;}[/COLOR]
e172be7c  eee0fa40 QQProtect+0x3a40
发现共注册了创建线程、加载模、注册表操作回调例程。现在把回调清除
0: kd> [COLOR=Red]ed nt![B]PspCreateThreadNotifyRoutineCount [/B]0; ed nt![B]PspCreateThreadNotifyRoutine [/B]0;[/COLOR]
0: kd> [COLOR=Red]ed nt![B]PspLoadImageNotifyRoutineCount [/B]0; ed nt![B]PspLoadImageNotifyRoutine [/B]0;[/COLOR]
0: kd> [COLOR=Red]ed nt![B]CmpCallBackCount [/B]0; ed nt![B]CmpCallBackVector [/B]0;[/COLOR]
Ok,这个时候,SSDT和inline hook恢复了 ,内核通知回调清除了,线程APC也设置了,继续执行就能终止线程。

0: kd> [COLOR=Red]g[/COLOR]
过一会,在按Ctrl+break断下,检查一下线程是否已终止
0: kd>[COLOR=Red] r @$t0=@@(#FIELD_OFFSET(nt!_EPROCESS, ThreadListHead));r @$t1= @@(#FIELD_OFFSET(nt!_ETHREAD, ThreadListEntry));r @$t2=@@(#FIELD_OFFSET(nt!_ETHREAD, StartAddress));!list "-t nt!_LIST_ENTRY.FLink -e -x \"r @$t3=@$extret-@$t1; r @$t4= @$t3+@$t2; r @$t5=poi(@$t4);.if(@@((unsigned long)@$t5>(unsigned long)[B]0xeee0c000 [/B]&& (unsigned long)@$t5<(unsigned long)[B]0xeee36680[/B])){r @$t3;dt -b nt!_ETHREAD Cid. ExitStatus @$t3; dt -b nt!_KTHREAD Header. @$t3; }; \" [B]867b5830[/B]+@$t0"[/COLOR]
...
r @$t3=@$extret-@$t1; r @$t4= @$t3+@$t2; r @$t5=poi(@$t4);.if(@@((unsigned long)@$t5>(unsigned long)0xeee0c000 && (unsigned long)@$t5<(unsigned long)0xeee36680)){r @$t3;dt -b nt!_ETHREAD Cid. ExitStatus @$t3; dt -b nt!_KTHREAD Header. @$t3; };  
$t3=[COLOR=Red]86699130[/COLOR]
   +0x1d0 ExitStatus : [COLOR=Red]0n1864 [/COLOR][COLOR=Blue]//0n1864=0x478[/COLOR]
   +0x1ec Cid        : 
      +0x000 UniqueProcess : 0x00000004 
      +0x004 UniqueThread : 0x00000160 
   +0x000 Header  : 
      +0x000 Type    : 0x6 ''
      +0x001 Absolute : 0 ''
      +0x002 Size    : 0x70 'p'
      +0x003 Inserted : 0 ''
      +0x004 SignalState : [COLOR=Red]0n1 [/COLOR][COLOR=Blue]//已终止,信号状态变成有信号[/COLOR]
      +0x008 WaitListHead : _LIST_ENTRY [ 0x86730028 - 0x86730028 ]

r @$t3=@$extret-@$t1; r @$t4= @$t3+@$t2; r @$t5=poi(@$t4);.if(@@((unsigned long)@$t5>(unsigned long)0xeee0c000 && (unsigned long)@$t5<(unsigned long)0xeee36680)){r @$t3;dt -b nt!_ETHREAD Cid. ExitStatus @$t3; dt -b nt!_KTHREAD Header. @$t3; };  
$t3=[COLOR=Red]862de020[/COLOR]
   +0x1d0 ExitStatus : [COLOR=Red]0n1864[/COLOR]
   +0x1ec Cid        : 
      +0x000 UniqueProcess : 0x00000004 
      +0x004 UniqueThread : 0x00000164 
   +0x000 Header  : 
      +0x000 Type    : 0x6 ''
      +0x001 Absolute : 0 ''
      +0x002 Size    : 0x70 'p'
      +0x003 Inserted : 0 ''
      +0x004 SignalState : [COLOR=Red]0n1[/COLOR]
      +0x008 WaitListHead : _LIST_ENTRY [ 0x8665ba50 - 0x8665ba50 ]
...
至此,此篇流水基本上结束了。差点忘了还有SSDTShadow没有看。

恢复SSDTShadow hook

我们现在nt!NtWaitForSingleObject 函数上下个断点,断下一个win32线程,就可以看到win32k模块内存了
0: kd> [COLOR=Red]bp nt!NtWaitForSingleObject ".if(@@(((nt!_KTHREAD*)[B]@$thread[/B])->Win32Thread)!=0){;}.else{gc;}"[/COLOR]
breakpoint 0 redefined
下完断点继续执行
0: kd> [COLOR=Red]g[/COLOR]
不一会就断下来了
nt!NtWaitForSingleObject:
805c16b6 6a2c            push    2Ch
看一下线程自己的ServiceTable字段
0: kd> [COLOR=Red]dd @@(((nt!_KTHREAD*)@$thread)->ServiceTable) l8[/COLOR]
8055d6c0 [COLOR=Red] 80505450 [/COLOR]00000000 [COLOR=Red]0000011c [/COLOR]805058c4
8055d6d0 [COLOR=Red] bf999b80 [/COLOR]00000000 [COLOR=Red]0000029b [/COLOR]bf99a890
看SSDTShadow表
0: kd> [COLOR=Red]dds [B]bf999b80 [/B]l29b[/COLOR]
...
bf99a160  bf8f48da win32k!NtUserFillWindow
bf99a164  bf81b5fa win32k!NtUserFindExistingCursorIcon
[B][COLOR=Blue][COLOR=Red]bf99a168  [/COLOR]eee124fe QQProtect+0x64fe[/COLOR][/B]
bf99a16c  bf91540e win32k!NtUserFlashWindowEx
bf99a170  bf8e8688 win32k!NtUserGetAltTabInfo
...
发现Hook了一个函数

检查一下win32k
0: kd> [COLOR=Red]!chkimg -d win32k[/COLOR]
0 errors : win32k 
竟然没有发现hook!为什么呢?经过分析(!dh命令),SSDTShadow表在win32k模块的.data段,SSDT在nt的.text段。看来!chkimg只对代码段做检查。
计算一下修改处地址的Rva
0: kd> [COLOR=Red]? bf99a168-bf800000[/COLOR]
Evaluate expression: 1679720 = [COLOR=Blue]0019a168[/COLOR]
Rva=0019a168
使用lordPE看一下0019a168处的值是多少

楼主看到是bf8b1369,计算Rva
0: kd> [COLOR=Red]? bf8b1369-bf800000[/COLOR]
Evaluate expression: 725865 = [COLOR=Blue]000b1369[/COLOR]
查看是哪个函数
0: kd>[COLOR=Red] ln win32k+000b1369[/COLOR]
(bf8b1369)   win32k!NtUserFindWindowEx   |  (bf8b14cc)   win32k!_FindWindowEx
Exact matches:
    [B][COLOR=Blue]win32k!NtUserFindWindowEx[/COLOR][/B] = <no type information>
原来QQ hook的是win32k!NtUserFindWindowEx
这就好办了,恢复他
1: kd>[COLOR=Red] ed bf99a168 win32k+000b1369[/COLOR]
看一下恢复效果
1: kd> dds bf999b80 l29b
...
bf99a160  bf8f48da win32k!NtUserFillWindow
bf99a164  bf81b5fa win32k!NtUserFindExistingCursorIcon
[COLOR=Blue]bf99a168  bf8b1369 win32k!NtUserFindWindowEx[/COLOR]
bf99a16c  bf91540e win32k!NtUserFlashWindowEx
bf99a170  bf8e8688 win32k!NtUserGetAltTabInfo
...
至此,SSDTShadow表也恢复了。
总结
1. 其实上面的所有操作都可以使用xuetr来完成。但是对于楼主学习内核来说,自己实际的操作才是了解windows操作系统最佳的途径。
2. 虽然杀死了内核线程,但是QQ在每次启动的时候还是再次挂钩SSDT hook, SSDTShadow hook, inline hook。有兴趣的自己调试实现方法并处理之。
3. windows内核交流群:151843490


现学现用之 windbg的高级玩法

[推荐]看雪企服平台,提供安全分析、定制项目开发、APP等级保护、渗透测试等安全服务!

上传的附件:
上一主题 下一主题
最新回复 (61)
asd 2013-10-2 00:29
2
0
lz和qq结缘还是结怨了 呵呵
天高 2013-10-2 00:32
3
0
mark
囧囧 2013-10-2 01:57
4
0
前排学习,比翻windbg帮助爽多了~
学雄 1 2013-10-2 08:00
5
0
很好很强大,很累很蛋碎.~
topofall 2013-10-2 08:02
6
0
这就是所谓的沙发??
mszgx 2013-10-2 09:38
7
0
小白撸过
jecray 2013-10-2 09:54
8
0
大神!!
cailiaock 2013-10-2 10:32
9
0
向楼主学习,很强大。。。
iniwf 2013-10-2 11:01
10
0
楼主确实强大~~
wem 2013-10-2 12:27
11
0
学习,学习
yanyuyao 2 2013-10-2 13:19
12
0
lz很强悍啊 争取搞一个系列的 持续关注中……
tuobaofeng 2013-10-2 13:46
13
0
好东西,学习
姿色狐狸 2013-10-3 23:20
14
0
楼主啊 快送我个注册码 呵呵
Zencer 2013-10-6 09:01
15
0
楼主我不是腾讯的人,但是人家有理由保护自己的软件,所以开发一个驱动保护自身并没什么过错,这跟有些网游使用一些内核技术保护登录是一个道理~
a糊涂虫 2013-10-6 09:46
16
0
我也超喜欢windbg,目前win领域最强大的调试器,IDA除外,楼主的帖子可以省去我不少啃书自己实践摸索的时间,感谢
a糊涂虫 2013-10-6 09:50
17
0
楼主,最好科普下windbg脚本,再弄几个经典的例子
南乡子 2013-10-6 12:31
18
0
看了眼花缭乱,要是能做成一个软件就好,选择---确认---解除---成功
ddlx 5 2013-10-6 12:38
19
0
你直接用xuetr好了
Speeday 8 2013-10-8 17:26
20
0
楼主的实力太强大 了。很多好文,学习一下。
信是有缘 2013-10-9 12:48
21
0
mark学习!
Protected 2013-10-9 14:49
22
0
mark 一下,稍后细读
Tuisiper 2013-10-9 17:27
23
0
good job!
xiejienet 2013-10-9 17:44
24
0
对楼主的崇拜就如同滔滔江水,连绵不绝
pengkui 2013-10-10 11:27
25
0
mark,下班回去看看
永驻零一 2013-10-10 12:00
26
0
楼主请继续
freener 2013-10-10 13:44
27
0
学习学习
PPTV 2013-10-10 13:45
28
0
很好很强大
Akihyou 2013-10-10 14:49
29
0
学windbg才是重点了 orz
jpys 2013-10-10 15:12
30
0
mark
uniking 2013-10-10 16:02
31
0
牛!mark一下
wmg 2013-10-10 16:59
32
0
学习一下,好文章...
豆浆蟹蟹 2013-10-10 17:23
33
0
好东西啊,比自己研究windbg好多了
闹闹 2013-10-10 23:28
34
0
呵呵 来学习啦~~~
tzl 10 2013-10-11 09:43
35
0
分析的很详细,赞一个!
white、、 2013-10-11 10:02
36
0
一如既往 希望能有PDF 或者DOC,这样不用网页保存了,谢谢。
mszjk 2013-10-11 12:52
37
0
自动恢复钩子,活该被爆菊
ddlx 5 2013-10-11 13:16
38
0
已集成到chm文档。下载地址:http://bbs.pediy.com/showthread.php?t=178808
stanford 2013-10-11 13:36
39
0
~~good job!
ydtvip 2013-10-11 13:59
40
0
好东西,学习了,小白无奈的漂过~~~~
okshell 2013-10-11 14:34
41
0
观摩学习了
anywhere杨 2013-10-11 16:38
42
0
坑爹了。。为什么我的虚拟机装上QQ,老是死机。。额,是调试模式。
sunflover 2013-10-12 12:07
43
0
TM没有驱动保护,所以我用着,电脑裸奔着
qbr 2013-10-12 17:00
44
0
很强大!
lionxyes 2 2013-10-20 16:22
45
0
长见识了
hackerlzc 10 2013-11-8 16:44
46
0
一直不大会用windbg,学习了,多谢LZ分享系列文章。
starscc 2013-11-19 10:38
47
0
支持原创,学习下
wenbinling 2013-11-27 10:28
48
0
使用楼主的命令怎么出现GetFieldOffset failed?

!list "-t nt!_LIST_ENTRY.FLink -e -x \"r @$t3=@$extret-@$t1; r @$t4= @$t3+@$t2; r @$t5=poi(@$t4);.if(@@((unsigned long)@$t5>(unsigned long)0xeee0c000 && (unsigned long)@$t5<(unsigned long)0xeee36680)){r @$t3;dt -b nt!_ETHREAD Cid. @$t3; dds @$t4 l1;}; \" 8a85f7f8+@$t0"
GetFieldOffset failed for nt!_LIST_ENTRY.FLink
thisIs 9 2013-11-28 12:27
49
0
膜拜楼主!!
jylaxp 2013-11-28 23:07
50
0
还没有学习内核,先记着,学了之后回来看
游客
登录 | 注册 方可回帖
返回