首页
论坛
专栏
课程

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

mlgbwoai
1
2018-3-8 14:07 4771
首先这事得搜索网络,发现网络上的看起来很牛的文章,咱们得承认在之前是还算不错的,但是也没有真正实现。
或许有实现的,但是网上肯定是搜不到具体的,肯定都当私货处理了,拿到现在这年代来说。都算是被淘汰了。
为什么这么说呢,因为这得分具体情况。在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);
       
        /* Check for success */
        if (!NT_SUCCESS(Status))
        {
            /* We failed. Overwrite the return value with the failure */
            ApiMessage->Status = Status;
        }
    }

    /* Return the CSR Result */
    DPRINT("Got back: 0x%lx\n", ApiMessage->Status);
    return ApiMessage->Status;
}

内容很长我们继续自己读一下然后写伪代码来逼迫自己清晰思路
CsrClientCallServer()
{
//填充消息
if (!InsideCsrProcess) //这里我就是顾名思义,我们不往csrss里面注
{
xxxxxxxxxx
NtRequestWaitReplyPort //发送lpc消息
返回然后xxxx我们不管
}
}

好了,看到这里,我想思路基本就明确了,写一下伪代码

MyNtCreateThread()
{
//栈
//上下文
ZwCreateThread
ZwRequestWaitReplyPort //发消息通知csrss等着返回
恢复

}

==================我是华丽的分割线==========================


其中除了ZwRequestWaitReplyPort之外基本没啥难点。下来所说的全部都是针对ZwRequestWaitReplyPort所说的。
如果看到这里的话,请忘记上面所有的内容,重点关注着ZwRequestWaitReplyPort
ZwRequestWaitReplyPort(Handle, PPORT_MESSAGE, PPORT_MESSAGE)

这个里面重要是参数的结构体非常复杂,这里可以网上搜basemsg.h,这里面都包含着呢.所以第二第三个参数可以说是能解决,那么第一个参数呢?

这个从网上找资料说\windows\apiport 不能连(实际上不准,连是可以连的,只是创建了共享内存,发现有共享内存就失败了而已),也不能attach
而有前辈们说可以从目标进程中查找已经存在的port类型句柄(LPC port,ALPC port),然后ZwDuplicateObject出来用。
恩,好象还真是那么回事。那么怎么办?快去想办法实现啊,不去实现你永远也不可能知道行还是不行
那么针对第一个参数这个问题,自然重点就是如何查找指定pid的\windows\apiport的句柄~
放出伪代码

Enumed(IN PHANDLE_TABLE_ENTRY HandleEntry,IN HANDLE Handle,IN PVOID Context)
{
  POBJECT_HEADER Obh;
  PLPCP_PORT_OBJECT CsrApiPort = (PLPCP_PORT_OBJECT)Context;
  Obh = ObpGetObject(HandleEntry);
  Port = (PLPCP_PORT_OBJECT)&Obh->Body;
  if (Port->ConnectionPort == CsrApiPort)
  {
   
  }
}

CsrPortHandle(PEPROCESS proc, PHANDLE pCsrHandle)
{
  UNICODE_STRING usType;
  POBJECT_TYPE *pType;
  UNICODE_STRING uName;
  PLPCP_PORT_OBJECT ApiPort = NULL;

  RtlInitUnicodeString(&uType, L"LpcPortObjectType");
  pType = MmGetSystemRoutineAddress(&uType)))
  RtlInitUnicodeString(&uName, L"\\Windows\\ApiPort");
  ObReferenceObjectByName(&uName, 0, NULL, PORT_ALL_ACCESS, *pType, KernelMode, NULL, &ApiPort);
  ExEnumHandleTable(xxxxxxx, Enumed, ApiPort, pCsrHandle);
  ObDereferenceObject(ApiPort);
}
那么好了,第一  第二  第三个参数都有了,直接调用就好了~

恩。到这里好象可以收尾了。

为什么这么说呢?因为编译好之后 我在xp下 往taskmgr注入了个dll,这个dll里 不仅仅创建了线程,线程里又创建了线程,然后还降权做了点事情

测试结果一切正常。于是就抽了只烟。然后再看看xp 64位,恩也可以,好的,再最后看看2003,
哎呀,我草。。我草。。。完了,不对,杂没出来啊,什么也不对,这这??难道,莫非?,不应该啊。
伤心了。但是伤心之余还得继续看。

首先看了下驱动输出,奇怪,windows\apiport句柄杂返回0  没找到。
把pchunter放进去看看,哎,竟然不支持。好吧 procexp

用这个看了一下,忽然意识到好象要出大问题。因为看到了session\2\windows\apiport

这么忽然一想。感觉好象自己被自己骗了。不行,把session全部弄出来。
对着session=0的随便一个进程注入试试(我不是iocontrol的是驱动里直接注入的所以session=0)
可以预见,这个可以。没问题。再找个session=2的注入试试。真的不行

然后又想到了测试的xp和xp 64。再进去看看,一看里面的所有的session=0

进行内心纠结的总结:因为同都在session 0下  所以才成功,如果用多用户登陆,那肯定失败。或者说驱动session=0 注session=2 失败
这下看来真的碰到江湖上传说中的跨session问题了,或者说跨session通知csrss的问题。


于是仔细观看进程,发现2003里面有两个csrss.exe 这下也意识到原来每个csrss.exe控制着一个session
进程中有10个csrss.exe就控制着10个session

而且每个csrss与每个csrss之间 互不打扰,隔离

或者通俗的说,我们的电脑登陆一个用户就创建一个csrss

于是想到刚接触电脑时候,网上问:“我进程中有两个csrss.exe是怎么回事,下面回答肯定有一个是病毒,杀掉就行,当然我也那样认为”
现在想起来,这好象再正常不过啊~



我们到这里需要对上面所有所有的内容去总结:
那就是注和驱动一样session的完全没有问题,不同session的创建是成功的只是(如果dll里再创建线程等会失败)



那么现在问题就来了,我们的驱动session=0(说了没有使用iocontrol,否则session是调用它的exe所在session)
那么怎么去通知session=2的csrss呢?(我们一切都以我2003上目前的这个状态来说事情)
基于上面已经实现的和总结的,我们是不是可以这样想:我们既然要通知csrss并且session=2的,那么我们是否
选择一个session=2的其他进程(虽然dll里再创建线程失败,但是dllmain入口里的没问题啊)
然后注给它,然后它再去通知csrss(session=2)的。恩
想了想,逻辑没问题。于是继续写查找session的代码一大通。编译好。
自己脑袋里过了一遍,感觉很紧张,因为思路上是通的。要是测试通过,就可以以后安心使用了,也算是对抗精神嫡的再一次成功
如果失败,那么必疯无疑,甚至开始怀疑人生。

拿出来一试,十万个我草。失败。
真想用李云龙的话说事情。。。奶奶个腿

骂没有用,得找出是为什么失败来。经过系列跟踪,发现,csrss(session=2)的  确实是成功了的
但是,csrss回复的时候,是回复给  我们选择的  session=2的 所谓的其他进程


现在到这里,所有的问题成了一个僵局
而且越弄越复杂了。

到底应该怎么办呢?

到底应该怎么办呢?

能不能柳暗花明呢?





==================我是华丽的分割线==========================

下文就是让柳暗花明。

我们上文当中碰到了一个非常麻烦的事情,那就是session=0的驱动,去通知session=2的csrss.exe.
我们尝试的找个session=2的a.exe,往a.exe注一点代码,这个代码就负责通知session=2的csrss
最后发现的是session=2的csrss收到消息后回复给了a.exe,我们驱动里并没有得到通知。
我们所有的问题都是为了得到通知。实际上这里走了很多弯路,实验了很多方法。真的是弄的脑袋爆。

驱动注入用户线程之跨session通知csrss之真正解决 中的所谓的‘真正’的含义!
这里真正是加了引号,实际上我是重点标记,我说的的确是真正。

当问题没办法解决的时候会怎么办呢?当然是所有的办法也猜测,所有的资料也看。这里我们为了节约时间突出重点。
我们不再说我们走的弯路(实际上弯路很多很多)

当然这其中也免不了看 csrss.exe用了什么模块。自然会关注到CSRSRV.dll
网上搜也会看到这个是csrss.exe的核心。既然我们关注了这个dll.我们也就避免不了看看导出啊,对感兴趣的函数逆逆啊,搜搜啊什么的

我们来看这个dll的导出函数。
如下:
导出, CSRSRV.dll
序列 地址 名字
00000001 75AA544F CsrAddStaticServerThread
00000002 75AA4160 CsrCallServerFromServer          看名字我有兴趣
00000003 75AA3FCE CsrConnectToUser 看名字我有兴趣
00000004 75AA5D26 CsrCreateProcess
00000005 75AA60E0 CsrCreateRemoteThread 看名字我有兴趣
00000006 75AA6010 CsrCreateThread 看名字我有兴趣
00000007 75AA63FE CsrCreateWait
00000008 75AA6362 CsrDebugProcess
00000009 75AA636F CsrDebugProcessStop
0000000A 75AA500B CsrDereferenceProcess
0000000B 75AA55AA CsrDereferenceThread
0000000C 75AA658B CsrDereferenceWait
0000000D 75AA5F58 CsrDestroyProcess
0000000E 75AA619A CsrDestroyThread
0000000F 75AA54C2 CsrExecServerThread
00000010 75AA508D CsrGetProcessLuid
00000011 75AA4EE9 CsrImpersonateClient
00000012 75AA535D CsrLockProcessByClientId
00000013 75AA53DD CsrLockThreadByClientId
00000014 75AA6615 CsrMoveSatisfiedWait
00000015 75AA652D CsrNotifyWait 看名字我有兴趣
00000016 75AA2A17 CsrPopulateDosDevices
00000017 75AA3FC3 CsrQueryApiPort 看名字我有兴趣
00000018 75AA4F9D CsrReferenceThread
00000019 75AA4F30 CsrRevertToSelf
0000001A 75AA305E CsrServerInitialization
0000001B 75AA4D1F CsrSetBackgroundPriority
0000001C 75AA516F CsrSetCallingSpooler
0000001D 75AA4CF8 CsrSetForegroundPriority
0000001E 75AA624F CsrShutdownProcesses
0000001F 75AA3204 CsrUnhandledExceptionFilter
00000020 75AA53BA CsrUnlockProcess
00000021 75AA622C CsrUnlockThread
00000022 75AA4421 CsrValidateMessageBuffer
00000023 75AA449D CsrValidateMessageString


我有兴趣的函数我都在reatos里面搜了。
当搜到CsrCreateRemoteThread函数的时候,一切都傻眼了。为什么呢?我们来看CsrCreateRemoteThread的代码。
路径如下:
subsystems\csr\csrsrv\thread.c
代码以及注释如下:

/*++
 * @name CsrCreateRemoteThread
 * @implemented NT4
 *
 * The CsrCreateRemoteThread routine creates a CSR Thread object for
 * an NT Thread which is not part of the current NT Process.
 *
 * @param hThread
 *        Handle to an existing NT Thread to which to associate this
 *        CSR Thread.
 *
 * @param ClientId
 *        Pointer to the Client ID structure of the NT Thread to associate
 *        with this CSR Thread.  
 *
 * @return STATUS_SUCCESS in case of success, STATUS_UNSUCCESSFUL
 *         othwerwise.
 *
 * @remarks None.
 *
 *--*/
NTSTATUS
NTAPI
CsrCreateRemoteThread(IN HANDLE hThread,
                      IN PCLIENT_ID ClientId)
{
    NTSTATUS Status;
    HANDLE ThreadHandle;
    PCSR_THREAD CsrThread;
    PCSR_PROCESS CsrProcess;
    KERNEL_USER_TIMES KernelTimes;

    DPRINT("CSRSRV: %s called\n", __FUNCTION__);

    /* Get the Thread Create Time */
    Status = NtQueryInformationThread(hThread,
                                      ThreadTimes,
                                      (PVOID)&KernelTimes,
                                      sizeof(KernelTimes),
                                      NULL);

    /* Lock the Owner Process */
    Status = CsrLockProcessByClientId(&ClientId->UniqueProcess,
                                      &CsrProcess);

    /* Make sure the thread didn't terminate */
    if (KernelTimes.ExitTime.QuadPart)
    {
        /* Unlock the process and return */
        CsrUnlockProcess(CsrProcess);
        return STATUS_THREAD_IS_TERMINATING;
    }

    /* Allocate a CSR Thread Structure */
    if (!(CsrThread = CsrAllocateThread(CsrProcess)))
    {
        DPRINT1("CSRSRV:%s: out of memory!\n", __FUNCTION__);
        CsrUnlockProcess(CsrProcess);
        return STATUS_NO_MEMORY;
    }

    /* Duplicate the Thread Handle */
    Status = NtDuplicateObject(NtCurrentProcess(),
                               hThread,
                               NtCurrentProcess(),
                               &ThreadHandle,
                               0,
                               0,
                               DUPLICATE_SAME_ACCESS);
    /* Allow failure */
    if (!NT_SUCCESS(Status)) ThreadHandle = hThread;

    /* Save the data we have */
    CsrThread->CreateTime = KernelTimes.CreateTime;
    CsrThread->ClientId = *ClientId;
    CsrThread->ThreadHandle = ThreadHandle;
    CsrThread->Flags = 0;

    /* Insert the Thread into the Process */
    CsrInsertThread(CsrProcess, CsrThread);

    /* Release the lock and return */
    CsrUnlockProcess(CsrProcess);
    return STATUS_SUCCESS;
}

CsrCreateRemoteThread(IN HANDLE hThread,IN PCLIENT_ID ClientId)
从注释和内容看,这明摆着不就是:把我们要注的目标进程的hThread和ClientId传给这个函数,这个函数
自动进行关联起来的内置操作吗?

又是一个挖槽。我们干嘛总是驱动里给他发通知,这不是自己自寻死路吗,我们为什么不
为什么不
为什么不
为什么不

太重要了这点信息,下面说的这点信息将是重点中的重点。通篇废话就是为了下面的这几句。

*********************************************************************************************
我们再来回想一下,我们通篇都在做什么?都在做一件事情,那就是 驱动(session=0) 通知 csrss(session=2)
我们从来就没想过往csrss(session=2)里面注,现在人家csrss(session=2)有个现成的函数自己实现这些。
我们现在就往csrss(session=2)里面注,让csrss自己通知自己。而我们驱动不等port的返回,因为所有的关于
port等的信息都tmd与我们没关系了。我们只等着  注入csrss(session=2)里面的 的一点代码 把CsrCreateRemoteThread
的返回值通过结束线程的接口给传回来。

来点片段代码说清楚上面的看似模糊的话。

片段1:r3里的
注入csrss(session=2)里面的 的一点代码,实际上重点就这两行,当然是r3的代码

//csrss自己去处理通知吧,我们什么也不管,我们只管驱动把这两参数给传好就行了。就让csrss自己通知自己
CsrCreateRemoteThread(ThreadHandle,UniqueProcess);
//如果执行到这里的话肯定上面的代码就已经完成了,下面的这行代码是为了让驱动知道(驱动里可以恢复真正的线程运行了)
NtTerminateThread(GetCurrentThread(),0);


片段2:r0里的
MyZwCreateThread()
{
//栈
//上先文
//ZwCreateThread目标线程挂起
//ZwCreateThread(&CsrssThreadHandle, THREAD_ALL_ACCESS...) 目标线程所在进程所在session的 csrss  不挂起(内容就是片段1里的)
//ZwWaitForSingleObject(CsrssThreadHandle...)
//恢复目标进程
}

已经非常清晰了,重点中的重点啊,真的是放血了。
这样做的好处太多了。简单明了,不用那个通知函数又复杂又麻烦,事情又多,弯路又多,网上又很多忽悠人的看似可以的文章。让大家更加迷茫。
简单几行替代上百行,又稳定又安全又有依据。
*********************************************************************************************

所以标题可以这么称呼:
驱动注入用户线程之跨session通知csrss之真正解决之CsrCreateRemoteThread大法之全球首创

哈哈,够霸气吧~~


感谢busy,感谢pediy ,感谢所有帮助过我的朋友。

测试结果图~



[防守篇]2018看雪.TSRC CTF 挑战赛(团队赛)11月1日征题开启!

上传的附件:
最新回复 (27)
layerfsd 4 2018-3-8 16:18
2

0

好文章,如果能排版好一点就更好了
KevinsBobo 2 2018-3-8 17:33
3

0

好文!感谢楼主分享!楼主有空排下版
pccq 2018-3-8 17:36
4

0

感谢分享!
pccq 2018-3-8 18:15
5

0

ZwCreateThread(&CsrssThreadHandle,  THREAD_ALL_ACCESS...)  目标线程所在进程所在session的  csrss    不挂起(内容就是片段1里的)

这句代码没理解,是attach到csrss里面创建线程执行片段1吗?但是没发现csrss里面有新的线程创建。
pccq 2018-3-8 18:29
6

0

CsrCreateRemoteThread如果是attach到target进程之后去做的,那么是否可以直接调用CsrCreateRemoteThread,而不是创建一个线程去做?
mlgbwoai 1 2018-3-8 18:34
7

0

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
mlgbwoai 1 2018-3-8 18:46
8

0

不是attach的,是ZwCreateThread直接注进去的(dllmain里会执行的,这一点代码不通知没问题)
芃杉 2018-3-8 19:33
9

0

mark
sudozhange 2 2018-3-8 19:47
10

0

学习了
cvcvxk 10 2018-3-8 21:00
11

0

我去对面网吧一看,哪有什么XP  2003,左右都是Win10。现在xp只能在虚拟机看看,新硬件一个都不支持。

聖blue 2018-3-8 22:31
12

0

fengyunabc 1 2018-3-9 11:04
13

0

感谢分享!
hzqst 2 2018-3-9 13:32
14

0

讲道理,直接注入一段shellcode  插apc让用户层自己执行kernel32!CreateTheead不就好了 
开花的水管 2018-3-10 11:13
15

0

mlgbwoai 1 2018-3-12 00:10
16

0

cvcvxk 我去对面网吧一看,哪有什么XP 2003,左右都是Win10。现在xp只能在虚拟机看看,新硬件一个都不支持。
网吧代表不了祖国
mlgbwoai 1 2018-3-12 00:11
17

0

hzqst 讲道理,直接注入一段shellcode 插apc让用户层自己执行kernel32!CreateTheead不就好了
apc一堆毛病
niuzuoquan 2018-3-12 15:02
18

0

感谢分享
bithaha 5 2018-3-12 18:59
19

0

翻了翻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构造的妙就行(要复制到目标进程中).
但是创建进程必须自己通知.
gkdark 1 2018-3-13 01:59
20

0

mark一下,感谢楼主的分享 
lhb天羽 2018-3-22 14:57
21

0

mark    感谢楼主分享
sorrywyb 2018-3-22 15:02
22

0

mark一下        感谢楼主分享
小丑i 2018-3-22 16:16
23

0

Mark  感谢分享!
空白即是正义 2018-3-22 20:18
24

0

Mark一下
niuzuoquan 2018-3-22 20:29
25

0

感谢分享
九阴真经 2018-4-25 18:16
26

0

感谢分享
wx_咖啡_552099 2018-5-20 01:02
27

0

mlgbwoai 不是attach的,是ZwCreateThread直接注进去的(dllmain里会执行的,这一点代码不通知没问题)
CreateRemoteThread  是完全失败,还是会将线程跑起来然后出错?
nickmat 2018-9-26 21:11
28

0

mark
返回