首页
论坛
课程
招聘
[分享]HOOK SwapContext 枚举隐藏进程(学习笔记4)
2008-12-11 12:01 22725

[分享]HOOK SwapContext 枚举隐藏进程(学习笔记4)

2008-12-11 12:01
22725
以前想检测一个被隐藏的进程 后来用暴力枚举的方法解决了 但是HOOK SwapContext没有看到有完整的代码 所以搜集了点网上有用的模块 自己整合实现了下 确定支持XP3, XP2没测试 应该也能支持
附完整工程代码

难点:是得到SwapContext地址 还有一些细节问题~
原理:KiSwapContext调用SwapContext时候 ESI存放的是要换进线程的上下文 EDI存放的是要换出线程的上下文 然后截获ESI 即换进的ETHREAD 得到相应的EPROCESS 然后搜集
缺点:对系统性能影响非常大,因为系统线程调度很频繁, wrk的KiSwapContext用汇编写的就能体现出来对效率的要求高~.

另外有个有趣的问题
KiSwapContext 是fastcall调用方式  就是通过ECX EDX传 多出的按STDCALL传
WRK 是这样注释的
; BOOLEAN
; KiSwapContext (
;    IN PKTHREAD OldThread
;    IN PKTHREAD NewThread
;    )
但是看下 WRK中的代码
        mov     edi, ecx                ; set old thread address
        mov     esi, edx                ; set next thread address
        movzx   ecx, byte ptr [edi].ThWaitirql ; set APC interrupt bypass disable
        call    SwapContext             ; swap context
发现的确是两个参数

用WINDBG看
80545975 8bf1            mov     esi,ecx
80545977 8bbb24010000    mov     edi,dword ptr [ebx+124h]
8054597d 89b324010000    mov     dword ptr [ebx+124h],esi
80545983 8a4f58          mov     cl,byte ptr [edi+58h]
80545986 e8f5000000      call    nt!SwapContext (80545a80)
发现KiSwapContext 是一个参数, esi是要换进的线程上下文 是通过ECX (第一个参数得到)  edi 则不是
所以结论 KiSwapContext的参数现在是一个了,也许我WRK没更新 老版本。。

WRK KiSwapContext 代码
cPublicFastCall KiSwapContext, 2
.fpo (0, 0, 0, 4, 1, 0)

;
; N.B. The following registers MUST be saved such that ebp is saved last.
;      This is done so the debugger can find the saved ebp for a thread
;      that is not currently in the running state.
;

        sub     esp, 4*4
        mov     [esp+12], ebx           ; save registers
        mov     [esp+8], esi            ;
        mov     [esp+4], edi            ;
        mov     [esp+0], ebp            ;
        mov     ebx, PCR[PcSelfPcr]     ; set address of PCR
        mov     edi, ecx                ; set old thread address
        mov     esi, edx                ; set next thread address
        movzx   ecx, byte ptr [edi].ThWaitirql ; set APC interrupt bypass disable

        call    SwapContext             ; swap context
        mov     ebp, [esp+0]            ; restore registers
        mov     edi, [esp+4]            ;
        mov     esi, [esp+8]            ;
        mov     ebx, [esp+12]           ;
        add     esp, 4*4                ;
        fstRET  KiSwapContext           ;

fstENDP KiSwapContext

windbg得到的 KiSwapContext 代码
lkd> uf KiSwapContext
nt!KiSwapContext:
8054595c 83ec10          sub     esp,10h
8054595f 895c240c        mov     dword ptr [esp+0Ch],ebx
80545963 89742408        mov     dword ptr [esp+8],esi
80545967 897c2404        mov     dword ptr [esp+4],edi
8054596b 892c24          mov     dword ptr [esp],ebp
8054596e 648b1d1c000000  mov     ebx,dword ptr fs:[1Ch]
80545975 8bf1            mov     esi,ecx
80545977 8bbb24010000    mov     edi,dword ptr [ebx+124h]
8054597d 89b324010000    mov     dword ptr [ebx+124h],esi
80545983 8a4f58          mov     cl,byte ptr [edi+58h]
80545986 e8f5000000      call    nt!SwapContext (80545a80)
8054598b 8b2c24          mov     ebp,dword ptr [esp]
8054598e 8b7c2404        mov     edi,dword ptr [esp+4]
80545992 8b742408        mov     esi,dword ptr [esp+8]
80545996 8b5c240c        mov     ebx,dword ptr [esp+0Ch]
8054599a 83c410          add     esp,10h
8054599d c3              ret

驱动运行后的效果是:   

搜集到的进程是
0x81FE7768   svchost.exe
0x81EC6478   alg.exe
0x81F5E990   svchost.exe
0x81FE83E0   DrvLoader.exe
0x81CA7020   taskmgr.exe
0x81595D70   VMwareService.e
0x815C6608   VMwareTray.exe
0x815D9C08   svchost.exe
0x8159C528   ctfmon.exe
0x8158EB60   VisualTaskTips.
0x815E5DA0   winlogon.exe
0x81E96418   DrvLoader.exe
0x815F8BD8   lsass.exe
0x81FE5020   MSDEV.EXE
0x81EC0B28   mdm.exe
0x81EE0500   explorer.exe
0x81F13020   MSDEV.EXE
0x815523A8   DBGVIEW.EXE
0x81F74020   csrss.exe
0x821507C0   System
0x82009830   VMwareUser.exe
0x81F66DA0   services.exe
0x81F07B88   svchost.exe

搜集到的进程肯定是不全的 因为有的进程在休息 没有线程调度发生 也就没有搜集到= =

下面是主要代码

DWORD gThreadsProcessOffset =0x220;    // ETHREAD 在 EPROCESS偏移
/*

+0x218 TopLevelIrp      : Uint4B
+0x21c DeviceToVerify   : Ptr32 _DEVICE_OBJECT
+0x220 ThreadsProcess   : Ptr32 _EPROCESS
*/

ULONG ProcessNameOffset = 0x174;      // 进程对应的文件名 在 EPROCESS偏移
/*
+0x170 Session          : Ptr32 Void
+0x174 ImageFileName    : [16] UChar
+0x184 JobLinks         : _LIST_ENTRY
*/

PProcessList wLastItem = NULL;        
int BeTerminate = 0;                  //1 表示线程必须要停止  3表示线程不是PENDING状态  0表示线程可以正常运行

void _stdcall CollectProcess(PEPROCESS pEPROCESS)  // 搜集EPROCESS
{
        if (!IsAdded(wLastItem, pEPROCESS)) AddItem(&wLastItem, pEPROCESS);
        return;
}

void __stdcall ThreadCollect(PUCHAR pEthread)   //根据ETHREAD得到EPROCESS 并调用CollectProcess来搜集
{
        PEPROCESS pEprocess = *(PEPROCESS *)(pEthread + gThreadsProcessOffset);
        if (pEprocess) CollectProcess(pEprocess);
        return;
}

DWORD outPEthread = 0;
void __stdcall ProcessData(DWORD pInEthread, DWORD pOutEthread)
{
        DWORD pid, eprocess;
        char * pname;
        if (MmIsAddressValid(PVOID(pInEthread+0x220)) )    // 这里以及下面要判断是不是一个真正的 Ethread 结构体 有时好像调用SwapContext传进来的不是 Ethread 结构体 然后就蓝屏 具体没有深究 加个判断就不蓝了~
        {
                eprocess = *(DWORD*)(pInEthread+0x220);
               
                if (MmIsAddressValid(PVOID(eprocess) ) )
                {
                        ThreadCollect((PUCHAR)pInEthread);
                }
               
        }
}

PBYTE GoBackAddr = NULL;
PBYTE ChangAddr = NULL;

DWORD CallContextOffset = 0;

__declspec(naked) VOID HookSwap()
{

        _asm
        {
                pushad
                        pushfd
                        cli
        }

        _asm
        {
                   // EDI 是换出的线程上下文
                     push edi
                        //ESI 是换入的线程上下文
                        push esi
                        call ProcessData   //搜集进程
        }
       
        _asm
        {  
                sti
                        popfd
                        popad
        }
        _asm jmp DWORD PTR[GoBackAddr]
}

/*
得到SwapContext地址的原理是
用PsLookupThreadByThreadId得到Idle System的KTHREAD
res=(PCHAR)(Thread->Tcb.KernelStack);
SwapAddr=*(DWORD *)(res+0x08);
*/
PCHAR GetSwapAddr()
{
        PCHAR res = 0;
        NTSTATUS  Status;
        PETHREAD Thread;
       
        if (*NtBuildNumber <= 2195)
                Status = PsLookupThreadByThreadId((PVOID)4, &(PETHREAD)Thread);
        else
                Status = PsLookupThreadByThreadId((PVOID)8, &(PETHREAD)Thread);
       
        if (NT_SUCCESS(Status))
        {
                if (MmIsAddressValid(Thread))
                {
                        res = (PCHAR)(Thread->Tcb.KernelStack);

                }
                if (MmIsAddressValid(res+8))
                {
                        _asm
                        {
                                mov eax,res
                                        add eax,8
                                        mov eax,[eax]
                                        mov res,eax
                        }
                }
                else
                {
                        res = 0;
                        return NULL;
                }
        }
        _asm
        {
                mov eax,res
                        sub eax,5
                        mov ChangAddr,eax
                        mov edx,[eax+1]
                        mov CallContextOffset,edx
                        add eax,edx
                        add eax,5
                        mov GoBackAddr,eax
                        mov res,eax
        }
        return res;
}

BOOL  HookSwapFunction(BOOL flag)
{       
        if (flag == TRUE)
        {
                KIRQL OldIrql=0;
                DWORD NewOffset;//HookSwap-ChangAddr-5;
                _asm
                {
                        mov eax,HookSwap
                                mov edx,ChangAddr
                                sub eax,edx
                                sub eax,5
                                mov NewOffset,eax
                }

                PAGED_CODE()
                        ASSERT(KeGetCurrentIrql()<=DISPATCH_LEVEL);
                KeRaiseIrql(2,&OldIrql);//HIGH_LEVEL
                __asm
                {
                        CLI
                                MOV   EAX, CR0
                                AND   EAX, NOT 10000H  //disable WP bit
                                MOV   CR0, EAX
                }
                _asm
                {
                        mov eax,ChangAddr
                                push NewOffset
                                pop dword ptr[eax+1]
                               
                }
               
                __asm
                {
                        MOV   EAX, CR0
                                OR    EAX, 10000H  //enable WP bit
                                MOV   CR0, EAX
                                STI
                }
               
               
                KeLowerIrql(OldIrql);
               
        }
        //Bug Check 0xD1: DRIVER_IRQL_NOT_LESS_OR_EQUAL
       
        else
        {
                KIRQL OldIrql=0;
                KeRaiseIrql(2,&OldIrql);///HIGH_LEVEL
                __asm
                {
                        CLI
                                MOV   EAX, CR0
                                AND   EAX, NOT 10000H  //disable WP bit
                                MOV   CR0, EAX
                }
               
                _asm
                {
                        mov eax,ChangAddr
                                push CallContextOffset
                                pop dword ptr[eax+1]
                }
               
               
                __asm
                {
                        MOV   EAX, CR0
                                OR    EAX, 10000H  //enable WP bit
                                MOV   CR0, EAX
                                STI
                }
                KeLowerIrql(OldIrql);
                //                DbgPrint("HookSwapFunctionFALSE");//jution
        }
       
}

PEPROCESS processObject (PETHREAD ethread) {
        return  (PEPROCESS)(ethread->Tcb.ApcState.Process);
}

void  klisterUnload(IN PDRIVER_OBJECT pDriverObject)
{
        BeTerminate = 1;
        while(BeTerminate != 3)    // = 3时说明创建的线程不是pending状态且马上会结束 这时候可以UNLOAD   否则线程在PENDING状态UNLOADE 会直接蓝
        {
      
        }
       
        if (GoBackAddr)//PBYTE GoBackAddr = NULL;
                HookSwapFunction(FALSE);       
}

void showProcess()
{

        PProcessList temp;
        DWORD count = 0;
        PUCHAR pFileName;
         temp = wLastItem;

        while (temp)     //遍历链表
        {       
            if (temp->pEPROCESS)
                {
                          count++;
                          pFileName = (PUCHAR)((unsigned int)(temp->pEPROCESS) + 0x174);
                          DbgPrint("0x%08X   %s \n",(unsigned int)(temp->pEPROCESS), pFileName);
                }
                temp = PProcessList(temp->NextItem);
        }

        DbgPrint("共有%d个进程", count);
}

void WorkThread(IN PVOID pContext)
{
        LARGE_INTEGER timeout;
       
        while(true)
        {
                if (MmIsAddressValid(&BeTerminate) )    // 因为BeTerminate是在UNLOAD中设置的 可能驱动卸载后 这个变量不能访问 所以用MmIsAddressValid判断下
                {
                        if(BeTerminate == 0)
                        {
                               
                                //等待单位是 100ns         //-10作用是转换成微秒  //2000000微秒=2秒
                                timeout = RtlConvertLongToLargeInteger(-10              *    2000000);   
                               
                                KeDelayExecutionThread(KernelMode, FALSE, &timeout);
                                DbgPrint("搜集到的进程是");                               
                                showProcess();
                        }
                        else
                        {
                            BeTerminate = 3;
                                PsTerminateSystemThread(STATUS_SUCCESS);
                                goto __end;
                        }
                }
                else
                {
                    BeTerminate = 3;
                        PsTerminateSystemThread(STATUS_SUCCESS);
                        goto __end;       
                }
        }
__end:;
}

// 驱动程序加载时调用DriverEntry例程
NTSTATUS DriverEntry(
                                         IN PDRIVER_OBJECT  pDriverObject,
                                         IN PUNICODE_STRING pRegistryPath
                                         )
{
        NTSTATUS dwStAtus;
        HANDLE hThread;
       
        pDriverObject->DriverUnload=klisterUnload;
       
        dwStAtus = PsCreateSystemThread(&hThread,
                (ACCESS_MASK)0,
                NULL,
                (HANDLE)0,
                NULL,
                WorkThread,
                NULL
                );
               
               
                  GetSwapAddr();
                  if (GoBackAddr){
                  HookSwapFunction(TRUE);
}       
        return STATUS_SUCCESS;
}

2021 KCTF 秋季赛 防守篇-征题倒计时(11月14日截止)!

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (27)
雪    币: 250
活跃值: 活跃值 (63)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
weolar 活跃值 10 2008-12-11 12:36
2
0
顶~不过有时候进程切换的比较慢
雪    币: 370
活跃值: 活跃值 (121)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
bzhkl 活跃值 5 2008-12-11 13:08
3
0
程序里有个线程不断更新进程的 只要发生线程调度 就被记录下来了 如果有些进程实在太闲 不发生线程调度 那就不要管了。。 不运行应该不会对系统造成危害 除非他绕过了上面的HOOK

个人觉得这个可以起到辅助枚举进程的作用, 当初写这个的目的是得到一个隐藏进程的EPROCESS ~
雪    币: 478
活跃值: 活跃值 (640)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
sudami 活跃值 25 2008-12-11 14:15
4
0
res=(PCHAR)(Thread->Tcb.KernelStack);
SwapAddr=*(DWORD *)(res+0x08);


KernelStack 是当前线程的内核栈.
我记得里面偏移0x0C是EBP. 好像没有一个对应的结构啊. 难道必须用windbg动态跟踪吗?

LZ若有KernelStack 的结构,麻烦贴一下呀~~
写得不错,学习啦~
雪    币: 370
活跃值: 活跃值 (121)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
bzhkl 活跃值 5 2008-12-11 14:42
5
0
原理这个 http://pjf.blogcn.com/diary,109827361.shtml PJF提出来的 是跟踪得出的= =
雪    币: 478
活跃值: 活跃值 (640)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
sudami 活跃值 25 2008-12-12 09:36
6
0
thanks~~
雪    币: 38
活跃值: 活跃值 (16)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
炉子 活跃值 3 2008-12-12 13:13
7
0
写的不错。

swapcontext的调用不是很频繁,倒是sysenter很频繁(在p3 667上sysenter hook有明显的性能下降。)
雪    币: 836
活跃值: 活跃值 (770)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
上网鱼 活跃值 2008-12-27 23:15
8
0
  楼主 有没有写过断开某一线程的调度链呢?想找个示例,不知道哪能找到
雪    币: 370
活跃值: 活跃值 (121)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
bzhkl 活跃值 5 2008-12-27 23:38
9
0
XP 2000下都实现过  简单说下XPSP2下 因为没资料  2000资料多了 自己搜    你自己先搞

要断开首先要会枚举所有线程  办法是 枚举所有活动进程 通过其中任一 EHTREAD 的 KTHREAD 中的 WaitListEntry、QueueListEntry、ThreadListEntry 可以枚举到系统所有线程   

要断开指定线程就简单多了 找指定进程任一线程  KTHREAD  相关3个链表断开指定项就可以了= =
雪    币: 392
活跃值: 活跃值 (15)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
ImHolly 活跃值 1 2008-12-28 13:59
10
0
学习了,代码很好。
雪    币: 69
活跃值: 活跃值 (10)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
JeTus 活跃值 1 2008-12-31 17:17
11
0
学习,谢谢搂住
雪    币: 137
活跃值: 活跃值 (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wkaka 活跃值 2009-1-1 20:13
12
0
来学习
雪    币: 200
活跃值: 活跃值 (15)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
wowelf 活跃值 2 2009-1-2 15:50
13
0
XP下断开上面三个链表好像不行,KiDispatcherReadyListHead,KiWaitListHead断了也是一样运行如风。BAIDU GOOGLE这方面的资料也难找一个详细说明,楼主如果搞过,希望可以详细指点一下。
雪    币: 370
活跃值: 活跃值 (121)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
bzhkl 活跃值 5 2009-1-2 15:53
14
0
我说的是 WaitListEntry、QueueListEntry、ThreadListEntry   
你说 Ki*****, 你没仔细看我说的吧  我实现了 是可行的= =
雪    币: 200
活跃值: 活跃值 (15)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
wowelf 活跃值 2 2009-1-2 16:19
15
0
上面我也说都试了啊,断那三条链线程也不停,线程退出时候经常BSOD,好郁闷。希望楼主可以说详细一点。
雪    币: 370
活跃值: 活跃值 (121)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
bzhkl 活跃值 5 2009-1-2 17:01
16
0
就相关进程EHTREAD 的 KTHREAD 中的 WaitListEntry、QueueListEntry、ThreadListEntry   我是放在一个循环里 每隔几百毫秒检查下 有就断开。 好像3个链表好像不是同时有要断开的项的  所以要放一个循环了 我是这样解决的 具体调度算法 怎样调度没有深究了
雪    币: 206
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
newsark 活跃值 2009-1-2 22:55
17
0
我是来学习的
雪    币: 370
活跃值: 活跃值 (121)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
bzhkl 活跃值 5 2009-1-3 01:09
18
0
  我说的原理应该正确的 你出现错误应该是你的问题 慢慢调试了= =  传一个附件 WindowsXP 的调度机制的详细解说 并有伪代码   还有一条思路 配合WRK看  看 _KPRCB 中的+124  _KTHREAD *CurrentThread
struct _KTHREAD *NextThread
struct _KTHREAD *IdleThread  是怎么确定的应该就可以了
上传的附件:
雪    币: 200
活跃值: 活跃值 (15)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
wowelf 活跃值 2 2009-1-6 02:01
19
0
非常感谢~~~~~
雪    币: 836
活跃值: 活跃值 (770)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
上网鱼 活跃值 2009-1-8 15:21
20
0
这样应该不算绝对意义上的暂停线程吧,和wowelf一样,改了3个指向后 不做任何动作,等几秒就蓝了。估计这样断不大可靠,不知道从 swapcontext里做这工作效果如何,不过似乎会对系统影响很大。
雪    币: 313
活跃值: 活跃值 (13)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
better 活跃值 2 2009-1-8 18:06
21
0
WRK是windows2003 SP1的代码,XP下跟win2003不一样
雪    币: 2071
活跃值: 活跃值 (10)
能力值: (RANK:170 )
在线值:
发帖
回帖
粉丝
Aker 活跃值 4 2009-2-6 01:39
22
0
去年写一个测试程序时写的,貌似比较通用,但是代码没有错误处理:P
DWORD FindSwapAddr(DWORD pKiDispatchInterrupt)
{
    DWORD pSwapAddr= 0;
    int i;
    DbgMsg("pKiDispatchInterrupt: %.8x\n",pKiDispatchInterrupt);
    for ( i=0; i<0x1000; i++ )
    {
        if ( *(unsigned char*)(pKiDispatchInterrupt + i + 2) == 0xE8
            && *(WORD*)(pKiDispatchInterrupt + i)  == 0x1B1 )
        {
            DbgMsg("pKiDispatchInterrupt + i: %.8X\n",pKiDispatchInterrupt + i);
            DbgMsg("*(DWORD*)(pKiDispatchInterrupt  + i + 5 + 2 + 1): %.8X\n",*(DWORD*)(pKiDispatchInterrupt  + i + 2 + 1));

            pSwapAddr= pKiDispatchInterrupt + i + 5 + 2 + *(DWORD*)(pKiDispatchInterrupt  + i + 2 + 1);
        }
    }
    DbgMsg("pSwapAddr: %.8X\n",pSwapAddr);
    if (i<0x1000)
    {
        return pSwapAddr;
    }
    else
    {
        return 0;
    }
}
雪    币: 212
活跃值: 活跃值 (10)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
安摧 活跃值 2 2009-2-6 12:18
23
0
好帖,在此支持一个!!!
雪    币: 203
活跃值: 活跃值 (10)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
SafeNdis 活跃值 3 2009-2-6 13:02
24
0
支持下楼主!!!
雪    币: 0
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
王小攀 活跃值 2009-7-29 23:58
25
0
收藏,方便以后可以用
游客
登录 | 注册 方可回帖
返回