首页
社区
课程
招聘
[原创]驱动注入用户线程之跨session通知csrss之真正解决
发表于: 2018-3-8 14:07 17899

[原创]驱动注入用户线程之跨session通知csrss之真正解决

2018-3-8 14:07
17899
首先这事得搜索网络,发现网络上的看起来很牛的文章,咱们得承认在之前是还算不错的,但是也没有真正实现。
或许有实现的,但是网上肯定是搜不到具体的,肯定都当私货处理了,拿到现在这年代来说。都算是被淘汰了。
为什么这么说呢,因为这得分具体情况。在vista以及以后的系统中,未导出函数ZwCreateThreadEx已经自己内置实现了
跨session 通知csrss等操作,所以用起来还是很省心的。填充好参数,直接就不管了,也很顺利的运行。
但是吧,咱们写程序就是为了兼容性(不然内心一万匹草泥马在翻滚)。在国内xp可还是一大堆的~

但是xp却没有ZwCreateThreadEx,只有ZwCreateThread
(请自行ida一下xp的ntkrnlpa.exe,
至于为什么是这个而不是ntoskrnl.exe当然这个pchunter看下驱动的加载就好了啊,本文不是扫盲篇,这些不具体解释,当然我也是大菜鸟)

然而ZwCreateThread这个家伙 可真是让人操碎了心。
因为按照ZwCreateThread的参数 我填充好之后,编译,然后直接往taskmgr.exe里面注入dll,而dll的代码就是dllmain里MessageBox了一下
大家可以想到结果是什么。
结果是:弹框出来了,哎呀,万事大吉,收工保存,抽只烟,然后休息下,真爽。

等过了些时候,打算用用了。于是就真正的写功能dll,至于dll的功能当然是有点小复杂。
然后测试下。

哎,不对啊,怎么有点问题。是不是我系统坏了,或者dll那里有问题?真是奇怪。
仔细检查dll,没发现啥错误啊,为啥结果不正确。
得仔细定位dll.加了点调试信息输出。最终定位到dll里 线程创建失败了?然后,又仔细核对了下创建线程的函数
也没发现什么问题啊。真是莫名其妙。
还是网上搜搜前辈们吧。这一搜不要紧,原来这事 绝没这么简单,复杂的在后面呢。

所以本文的重点是针对版本号3790(2003和64位xp)以及以下的所有的系统的兼容性,核心问题也就是:如何正确处理ZwCreateThread

那么到底如何正确处理呢?
我们为了解决这个问题,自然会网上搜,但是真正会思考这个问题的一定会联想到CreateRemoteThread这个r3的api因为这个大家肯定太熟悉了。
好吧,先说个题外话。鲁迅曾经说过:"不想吃天鹅的蛤蟆不是好的蝌蚪",还说过:"电脑不备好ida,windbg,od,wrk,reatos的程序员不是好的厨师"

那么我最开始的想法是:先看看reatos里有没有,实在没有就ida逆CreateRemoteThread(反正有符号,没符号的话真没太大勇气逆)

天公做美竟然给搜出来了,路径在这里
dll\win32\kernel32\thread\thread.c
我们来看下内容

HANDLE
WINAPI
CreateRemoteThread(HANDLE hProcess,
                   LPSECURITY_ATTRIBUTES lpThreadAttributes,
                   DWORD dwStackSize,
                   LPTHREAD_START_ROUTINE lpStartAddress,
                   LPVOID lpParameter,
                   DWORD dwCreationFlags,
                   LPDWORD lpThreadId)
{
    NTSTATUS Status;
    INITIAL_TEB InitialTeb;
    CONTEXT Context;
    CLIENT_ID ClientId;
    OBJECT_ATTRIBUTES LocalObjectAttributes;
    POBJECT_ATTRIBUTES ObjectAttributes;
    HANDLE hThread;
    ULONG Dummy;

    DPRINT("CreateRemoteThread: hProcess: %ld dwStackSize: %ld lpStartAddress"
            ": %p lpParameter: %lx, dwCreationFlags: %lx\n", hProcess,
            dwStackSize, lpStartAddress, lpParameter, dwCreationFlags);

    /* Clear the Context */
    RtlZeroMemory(&Context, sizeof(CONTEXT));

    /* Write PID */
    ClientId.UniqueProcess = hProcess;

    /* Create the Stack */
    Status = BasepCreateStack(hProcess,
                              dwStackSize,
                              dwCreationFlags & STACK_SIZE_PARAM_IS_A_RESERVATION ?
                              dwStackSize : 0,
                              &InitialTeb);
    if(!NT_SUCCESS(Status))
    {
        SetLastErrorByStatus(Status);
        return NULL;
    }

    /* Create Initial Context */
    BasepInitializeContext(&Context,
                           lpParameter,
                           lpStartAddress,
                           InitialTeb.StackBase,
                           1);

    /* initialize the attributes for the thread object */
    ObjectAttributes = BasepConvertObjectAttributes(&LocalObjectAttributes,
                                                    lpThreadAttributes,
                                                    NULL);

    /* Create the Kernel Thread Object */
    Status = NtCreateThread(&hThread,
                            THREAD_ALL_ACCESS,
                            ObjectAttributes,
                            hProcess,
                            &ClientId,
                            &Context,
                            &InitialTeb,
                            TRUE);
    if(!NT_SUCCESS(Status))
    {
        BasepFreeStack(hProcess, &InitialTeb);
        SetLastErrorByStatus(Status);
        return NULL;
    }

    /* Are we in the same process? */
    if (hProcess == NtCurrentProcess())
    {
        PTEB Teb;
        PVOID ActivationContextStack;
        THREAD_BASIC_INFORMATION ThreadBasicInfo;
#ifndef SXS_SUPPORT_FIXME
        ACTIVATION_CONTEXT_BASIC_INFORMATION ActivationCtxInfo;
        ULONG_PTR Cookie;
#endif
        ULONG retLen;

        /* Get the TEB */
        Status = NtQueryInformationThread(hThread,
                                          ThreadBasicInformation,
                                          &ThreadBasicInfo,
                                          sizeof(ThreadBasicInfo),
                                          &retLen);
        if (NT_SUCCESS(Status))
        {
            /* Allocate the Activation Context Stack */
            Status = RtlAllocateActivationContextStack(&ActivationContextStack);
        }

        if (NT_SUCCESS(Status))
        {
            Teb = ThreadBasicInfo.TebBaseAddress;

            /* Save it */
            Teb->ActivationContextStackPointer = ActivationContextStack;
#ifndef SXS_SUPPORT_FIXME
            /* Query the Context */
            Status = RtlQueryInformationActivationContext(1,
                                                          0,
                                                          NULL,
                                                          ActivationContextBasicInformation,
                                                          &ActivationCtxInfo,
                                                          sizeof(ActivationCtxInfo),
                                                          &retLen);
            if (NT_SUCCESS(Status))
            {
                /* Does it need to be activated? */
                if (!ActivationCtxInfo.hActCtx)
                {
                    /* Activate it */
                    Status = RtlActivateActivationContext(1,
                                                          ActivationCtxInfo.hActCtx,
                                                          &Cookie);
                    if (!NT_SUCCESS(Status))
                        DPRINT1("RtlActivateActivationContext failed %x\n", Status);
                }
            }
            else
                DPRINT1("RtlQueryInformationActivationContext failed %x\n", Status);
#endif
        }
        else
            DPRINT1("RtlAllocateActivationContextStack failed %x\n", Status);
    }

    /* Notify CSR */
    Status = BasepNotifyCsrOfThread(hThread, &ClientId);
    if (!NT_SUCCESS(Status))
    {
        ASSERT(FALSE);
    }
    
    /* Success */
    if(lpThreadId) *lpThreadId = HandleToUlong(ClientId.UniqueThread);

    /* Resume it if asked */
    if (!(dwCreationFlags & CREATE_SUSPENDED))
    {
        NtResumeThread(hThread, &Dummy);
    }

    /* Return handle to thread */
    return hThread;
}

我们来简化一下代码方便继续。

CreateRemoteThread()
{
//栈
BasepCreateStack
//上下文
BasepInitializeContext
//创建挂起线程
NtCreateThread
//判断是否自身,我们不管忽略过去,我们不会往csrss里面注
//通知
BasepNotifyCsrOfThread
//恢复
NtResumeThread
}

从上面的伪代码中我们可以看到除了通知这个,其他的都很好处理。所以我们直接将重点精力继续追下去,也就是说看BasepNotifyCsrOfThread的具体实现

当然还是reatos里面搜,路径如下
dll\win32\kernel32\process\procsup.c
内容如下:
NTSTATUS
WINAPI
BasepNotifyCsrOfThread(IN HANDLE ThreadHandle,
                       IN PCLIENT_ID ClientId)
{
    ULONG Request = CREATE_THREAD;
    CSR_API_MESSAGE CsrRequest;
    NTSTATUS Status;

    DPRINT("BasepNotifyCsrOfThread: Thread: %lx, Handle %lx\n",
            ClientId->UniqueThread, ThreadHandle);

    /* Fill out the request */
    CsrRequest.Data.CreateThreadRequest.ClientId = *ClientId;
    CsrRequest.Data.CreateThreadRequest.ThreadHandle = ThreadHandle;

    /* Call CSR */
    Status = CsrClientCallServer(&CsrRequest,
                                 NULL,
                                 MAKE_CSR_API(Request, CSR_NATIVE),
                                 sizeof(CSR_API_MESSAGE));
    if (!NT_SUCCESS(Status) || !NT_SUCCESS(CsrRequest.Status))
    {
        DPRINT1("Failed to tell csrss about new thread\n");
        return CsrRequest.Status;
    }

    /* Return Success */
    return STATUS_SUCCESS;
}


一看就知道,实际上这个函数算是个小封装,没意思,重点还是在CsrClientCallServer里面。

那么继续搜,路径在

dll\ntdll\csr\connect.c
看下内容如下:

NTSTATUS 
NTAPI
CsrClientCallServer(PCSR_API_MESSAGE ApiMessage,
                    PCSR_CAPTURE_BUFFER CaptureBuffer OPTIONAL,
                    CSR_API_NUMBER ApiNumber,
                    ULONG RequestLength)
{
    NTSTATUS Status;
    ULONG PointerCount;
    PULONG_PTR Pointers;
    ULONG_PTR CurrentPointer;
    DPRINT("CsrClientCallServer\n");

    /* Fill out the Port Message Header */
    ApiMessage->Header.u2.ZeroInit = 0;
    ApiMessage->Header.u1.s1.DataLength = RequestLength - sizeof(PORT_MESSAGE);
    ApiMessage->Header.u1.s1.TotalLength = RequestLength;

    /* Fill out the CSR Header */
    ApiMessage->Type = ApiNumber;
    //ApiMessage->Opcode = ApiNumber; <- Activate with new CSR
    ApiMessage->CsrCaptureData = NULL;

    DPRINT("API: %lx, u1.s1.DataLength: %x, u1.s1.TotalLength: %x\n", 
           ApiNumber,
           ApiMessage->Header.u1.s1.DataLength,
           ApiMessage->Header.u1.s1.TotalLength);
                
    /* Check if we are already inside a CSR Server */
    if (!InsideCsrProcess)
    {
        /* Check if we got a a Capture Buffer */
        if (CaptureBuffer)
        {
            /* We have to convert from our local view to the remote view */
            ApiMessage->CsrCaptureData = (PVOID)((ULONG_PTR)CaptureBuffer +
                                                 CsrPortMemoryDelta);

            /* Lock the buffer */
            CaptureBuffer->BufferEnd = 0;

            /* Get the pointer information */
            PointerCount = CaptureBuffer->PointerCount;
            Pointers = CaptureBuffer->PointerArray;

            /* Loop through every pointer and convert it */
            DPRINT("PointerCount: %lx\n", PointerCount);
            while (PointerCount--)
            {
                /* Get this pointer and check if it's valid */
                DPRINT("Array Address: %p. This pointer: %p. Data: %lx\n",
                        &Pointers, Pointers, *Pointers);
                if ((CurrentPointer = *Pointers++))
                {
                    /* Update it */
                    DPRINT("CurrentPointer: %lx.\n", *(PULONG_PTR)CurrentPointer);
                    *(PULONG_PTR)CurrentPointer += CsrPortMemoryDelta;
                    Pointers[-1] = CurrentPointer - (ULONG_PTR)ApiMessage;
                    DPRINT("CurrentPointer: %lx.\n", *(PULONG_PTR)CurrentPointer);
                }
            }
        }

        /* Send the LPC Message */
        Status = NtRequestWaitReplyPort(CsrApiPort,
                                        &ApiMessage->Header,
                                        &ApiMessage->Header);

        /* Check if we got a a Capture Buffer */
        if (CaptureBuffer)
        {
            /* We have to convert from the remote view to our remote view */
            DPRINT("Reconverting CaptureBuffer\n");
            ApiMessage->CsrCaptureData = (PVOID)((ULONG_PTR)
                                                 ApiMessage->CsrCaptureData -
                                                 CsrPortMemoryDelta);

            /* Get the pointer information */
            PointerCount = CaptureBuffer->PointerCount;
            Pointers = CaptureBuffer->PointerArray;

            /* Loop through every pointer and convert it */
            while (PointerCount--)
            {
                /* Get this pointer and check if it's valid */
                if ((CurrentPointer = *Pointers++))
                {
                    /* Update it */
                    CurrentPointer += (ULONG_PTR)ApiMessage;
                    Pointers[-1] = CurrentPointer;
                    *(PULONG_PTR)CurrentPointer -= CsrPortMemoryDelta;
                }
            }
        }

        /* Check for success */
        if (!NT_SUCCESS(Status))
        {
            /* We failed. Overwrite the return value with the failure */
            DPRINT1("LPC Failed: %lx\n", Status);
            ApiMessage->Status = Status;
        }
    }
    else
    {
        /* This is a server-to-server call. Save our CID and do a direct call */
        DbgBreakPoint();
        ApiMessage->Header.ClientId = NtCurrentTeb()->ClientId;
        Status = CsrServerApiRoutine(&ApiMessage->Header,
                                     &ApiMessage->Header);

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 2
支持
分享
最新回复 (30)
雪    币: 8188
活跃值: (2847)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
2
好文章,如果能排版好一点就更好了
2018-3-8 16:18
0
雪    币: 23080
活跃值: (3432)
能力值: (RANK:648 )
在线值:
发帖
回帖
粉丝
3
好文!感谢楼主分享!楼主有空排下版
2018-3-8 17:33
0
雪    币: 75
活跃值: (718)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享!
2018-3-8 17:36
0
雪    币: 75
活跃值: (718)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
ZwCreateThread(&CsrssThreadHandle,  THREAD_ALL_ACCESS...)  目标线程所在进程所在session的  csrss    不挂起(内容就是片段1里的)

这句代码没理解,是attach到csrss里面创建线程执行片段1吗?但是没发现csrss里面有新的线程创建。
2018-3-8 18:15
0
雪    币: 75
活跃值: (718)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
CsrCreateRemoteThread如果是attach到target进程之后去做的,那么是否可以直接调用CsrCreateRemoteThread,而不是创建一个线程去做?
2018-3-8 18:29
0
雪    币: 429
活跃值: (418)
能力值: ( LV6,RANK:81 )
在线值:
发帖
回帖
粉丝
7
CsrCreateRemoteThread是  csrss.exe(session=2)里  CSRSRV.dll模块里的  一个导出函数,r0注一快内存到里面(这快内存获取这个函数调用一下既可)

main  proc
        call  @F
        @@:
        pop  ebx
        sub  ebx,offset  @B

        assume  fs:nothing
        mov  eax,fs:[30h]
        mov  eax,[eax+0ch]
        mov  esi,[eax+1ch]  ;  ldr->InInitializationOrderModuleList
        mov  eax,[esi+08h]
        mov  dword  ptr  [ebx  +  offset  hNtdll],  eax
        mov  esi,[esi]  ;  Flink
        mov  eax,[esi+08h]
        mov  dword  ptr  [ebx  +  offset  hCsrsrv],  eax

        assume  ebx:nothing

        lea  esi,  [ebx  +  offset  szCsrCreateRemoteThread]
        push  esi
        push  dword  ptr  [ebx  +  offset  hCsrsrv]
        call  _GetApi

        test  eax,  eax
        jz  clear

        lea  esi,  [ebx  +  offset  UniqueProcess]
        push  esi
        push  dword  ptr  [ebx  +  offset  ThreadHandle]
        call  eax

clear:
        lea  esi,  [ebx  +  offset  szNtTerminateThread]
        push  esi
        push  dword  ptr  [ebx  +  offset  hNtdll]
        call  _GetApi
       
        push  0  ;  exit  code
        push  0fffffffeh  ;  GetCurrentThread()
        call  eax  ;  TerminateThread
main  endp
2018-3-8 18:34
0
雪    币: 429
活跃值: (418)
能力值: ( LV6,RANK:81 )
在线值:
发帖
回帖
粉丝
8
不是attach的,是ZwCreateThread直接注进去的(dllmain里会执行的,这一点代码不通知没问题)
2018-3-8 18:46
0
雪    币: 36
活跃值: (1061)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
9
mark
2018-3-8 19:33
0
雪    币: 285
活跃值: (1095)
能力值: ( LV13,RANK:405 )
在线值:
发帖
回帖
粉丝
10
学习了
2018-3-8 19:47
0
雪    币: 8835
活跃值: (2404)
能力值: ( LV12,RANK:760 )
在线值:
发帖
回帖
粉丝
11
我去对面网吧一看,哪有什么XP  2003,左右都是Win10。现在xp只能在虚拟机看看,新硬件一个都不支持。

2018-3-8 21:00
0
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
2018-3-8 22:31
0
雪    币: 3738
活跃值: (3872)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
13
感谢分享!
2018-3-9 11:04
0
雪    币: 12848
活跃值: (9147)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
14
讲道理,直接注入一段shellcode  插apc让用户层自己执行kernel32!CreateTheead不就好了 
2018-3-9 13:32
0
雪    币: 1535
活跃值: (695)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
2018-3-10 11:13
0
雪    币: 429
活跃值: (418)
能力值: ( LV6,RANK:81 )
在线值:
发帖
回帖
粉丝
16
cvcvxk 我去对面网吧一看,哪有什么XP 2003,左右都是Win10。现在xp只能在虚拟机看看,新硬件一个都不支持。
网吧代表不了祖国
2018-3-12 00:10
0
雪    币: 429
活跃值: (418)
能力值: ( LV6,RANK:81 )
在线值:
发帖
回帖
粉丝
17
hzqst 讲道理,直接注入一段shellcode 插apc让用户层自己执行kernel32!CreateTheead不就好了
apc一堆毛病
2018-3-12 00:11
0
雪    币: 300
活跃值: (2477)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
感谢分享
2018-3-12 15:02
0
雪    币: 1505
能力值: (RANK:210 )
在线值:
发帖
回帖
粉丝
19
翻了翻10年的老代码 

    #ifdef  _WIN32
                for(DWORD  i  =  0x1000;  i  <  dwNtdllSize;  ++i) 
                {
                      if(*((PBYTE)pvNtdllBase  +  i)  ==  0x8B) 
                      {
                              if(*(PDWORD)((PBYTE)pvNtdllBase  +  i)  ==  0x340FD48B) 
                              {
                                      m_spvKisystemFastCall  =  (PVOID)((PBYTE)pvNtdllBase  +  i);
                                      if  (m_pulCsrPortHandle)
                                      {
                                              break;
                                      }
                              }
                      }
                      else  if  (*((PBYTE)pvNtdllBase  +  i)  ==  0x56)
                      {
                                if  (*(PDWORD)((PBYTE)pvNtdllBase  +  i)  ==  0x35FF5656  &&
                                        *(PWORD)((PBYTE)pvNtdllBase  +  i  -  6)  ==  0x850F)
                                {
                                        m_pulCsrPortHandle  =  *(PULONG*)((PBYTE)pvNtdllBase  +  i  +  4);
                                        if  (m_spvKisystemFastCall)
                                        {
                                                break;
                                        }
                                }
                      }
                }

                #endif
不知道这样好使不?    太久啦,记得好像createthread是不需要自己通知csrss的,BaseThreadStartThunk构造的妙就行(要复制到目标进程中).
但是创建进程必须自己通知.
2018-3-12 18:59
0
雪    币: 199
活跃值: (65)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
20
mark一下,感谢楼主的分享 
2018-3-13 01:59
0
雪    币: 25
活跃值: (506)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
mark    感谢楼主分享
2018-3-22 14:57
0
雪    币: 3110
活跃值: (143)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
22
mark一下        感谢楼主分享
2018-3-22 15:02
0
雪    币: 13
活跃值: (274)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
Mark  感谢分享!
2018-3-22 16:16
0
雪    币: 2347
活跃值: (58)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
Mark一下
2018-3-22 20:18
0
雪    币: 300
活跃值: (2477)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
感谢分享
2018-3-22 20:29
0
游客
登录 | 注册 方可回帖
返回
//