找回密码
 加入我们

QQ登录

只需一步,快速开始

搜索
查看: 12913|回复: 8

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

[复制链接]

7

主题

67

回帖

2

精华

钻石会员

积分
2565
发表于 2018-2-11 20:40:00 | 显示全部楼层 |阅读模式
首先这事得搜索网络,发现网络上的看起来很牛的文章,咱们得承认在之前是还算不错的,但是也没有真正实现。
或许有实现的,但是网上肯定是搜不到具体的,肯定都当私货处理了,拿到现在这年代来说。都算是被淘汰了。
为什么这么说呢,因为这得分具体情况。在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的 所谓的其他进程


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

到底应该怎么办呢?

到底应该怎么办呢?

能不能柳暗花明呢?

临时有事情,先写到这里吧,年后再接着补上。。。。

声明一点:以上内容局限于我的认识不免有错,如有错误,请斧正。另外还有感谢一个人。谢谢

评分

参与人数 2水晶币 +101 收起 理由
qq892640791 + 1 很给力!
Tesla.Angela + 100 赞一个!

查看全部评分

857

主题

2632

回帖

2

精华

管理员

此生无悔入华夏,  长居日耳曼尼亚。  

积分
36130
发表于 2018-2-11 22:51:10 | 显示全部楼层
文章写得不错,期待下篇,已高亮并加精。
游客,如果您要查看本帖隐藏内容请回复
顺带,建议别老吸烟。

0

主题

108

回帖

0

精华

铜牌会员

积分
87
发表于 2018-2-12 04:43:06 | 显示全部楼层
顺带,建议别老吸烟。

0

主题

9

回帖

0

精华

初来乍到

积分
9
发表于 2018-2-12 14:19:46 | 显示全部楼层
reatos拼错了,是reactos

0

主题

33

回帖

0

精华

金牌会员

积分
739
发表于 2018-2-13 07:51:27 | 显示全部楼层
学习了,给楼主点赞,我在win7上写辅助让它以system权限运行就碰到这种问题,根本看不见窗口,后来才找到方法切换到system的桌面才能看到

2

主题

32

回帖

0

精华

铜牌会员

积分
68
发表于 2018-2-26 19:27:13 | 显示全部楼层
向楼主学习,学到老才能活到老

0

主题

23

回帖

0

精华

铜牌会员

积分
33
发表于 2018-9-26 20:41:53 | 显示全部楼层
楼主很强,写的也很好

1

主题

122

回帖

0

精华

铜牌会员

积分
182
发表于 2019-12-30 11:24:07 | 显示全部楼层
确实6,这种研究精神值得学习

0

主题

52

回帖

0

精华

铜牌会员

积分
77
发表于 2020-8-30 20:39:25 | 显示全部楼层
最近在研究 SHELLcode ,来学习下
您需要登录后才可以回帖 登录 | 加入我们

本版积分规则

快速回复 返回顶部 返回列表