首页
社区
课程
招聘
[原创]关于从系统内核创建一个进程的深入学习
2009-1-13 12:50 17925

[原创]关于从系统内核创建一个进程的深入学习

zhuwg 活跃值
11
2009-1-13 12:50
17925
关于这个问题已经有了几篇文章了
Gary Nebbett的 炉子的。还有就是第八个门的,基本上都还是说的比较清楚的
但是都不去考虑一个关键问题
如果忽视了这个问题 那么仅仅也就就是 使用native api创建进程而已
而不是真正的 r0下面创建

关于r0下面怎么call r3的讨论比较多了 其实createprocess也算在里面
关于hook法 apc法 dispatch法等可以看看sudami牛的文章 说的很详细的

下面开始正题
利用一个可执行的PE文件去创建一个进程
首先 ZwOpenFile-ZwCreateSection-ZwCreateProcess
这时。进程的一些东西已经准备好了
下面是ZwAllocateVirtualMemory-做一些PEB之类的初始化工作
下面要来创建这个进程的第一个线程 ZwCreateThread
这个东西有几点要注意 第一。ZwCreateThread的参数
NTSTATUS NTAPI ZwCreateThread(
OUT PHANDLE       ThreadHandle,
IN   ACCESS_MASK           DesiredAccess,
IN   POBJECT_ATTRIBUTES   ObjectAttributes OPTIONAL,
IN   HANDLE                 ProcessHandle,
OUT PCLIENT_ID             ClientId,
IN   PCONTEXT               ThreadContext,
IN   PINITIAL_TEB           InitialTeb,
IN   BOOLEAN               CreateSuspended
);
CreateSuspended 这个参数在创建的一定要设置成TRUE
即使你想直接在创建完成之后运行,那也是后面ZwResumeThread的事情
这些在以上三位大牛的文章中都详细给出了,偶也不多提及

第二。CONTEXT 结构的初始化问题
BaseCreateStack和BaseInitializeContext完成初始化
Windows启动一个新进程的时候 并不是把目标程序OEP
直接设置成ZwCreateThread的起点EIP
而是把目标程序OEP设置成ZwCreateThread-CONTEXT-EAX
如果是创建进程的第一个线程,那么设置的就是BaseProcessStartThunk
如果是创建其他情况的线程,那么就是BaseThreadStartThunk
实际设置的是_BaseProcessStart和_BaseThreadStart
另外还有一个BaseFiberStart 这个偶就不知道是什么了。。
好了,这个BaseProcessStart将会设置一个SEH 然后call eax
在返回的时候执行_pspexitthread
这就是为什么我们写代码只需要在末尾写一个ret 0c就可以自动正确退出的原因
ret会回到BaseProcessStart处pspexitthread
如果是我们直接设置成OEP了。那么返回的时候就要注意注意了。
退出时不仔细注意,会卡住的。这是第一个问题

还有一个问题。这个也比较重要 就是InformCsrss
任何一个进程或者线程,创建之后都需要告知CSRSS.EXE,这个功能是
由CsrClientCallServer函数发送一个message告知csrss,
具体的端口是\\Windows\\ApiPort,该端口是在本程序之前CsrClientConnectToServer
函数中
NTSTATUS STDCALL
CsrClientConnectToServer(VOID)
{
NTSTATUS Status;
UNICODE_STRING PortName;
ULONG ConnectInfoLength;
CSRSS_API_REQUEST Request;
CSRSS_API_REPLY Reply;

RtlInitUnicodeString(&PortName, L"\\Windows\\ApiPort");
ConnectInfoLength = 0;
Status = NtConnectPort(&WindowsApiPort,
&PortName,
NULL,
NULL,
NULL,
NULL,
NULL,
&ConnectInfoLength);
if (!NT_SUCCESS(Status))
{
return(Status);
}

Request.Type = CSRSS_CONNECT_PROCESS;
Status = CsrClientCallServer(&Request,
&Reply,
sizeof(CSRSS_API_REQUEST),
sizeof(CSRSS_API_REPLY));
if (!NT_SUCCESS(Status))
{
return(Status);
}
if (!NT_SUCCESS(Reply.Status))
{
return(Reply.Status);
}
return(STATUS_SUCCESS);
}

另外一个函数是CsrClientCallServer,这个函数负责发送message
NTSTATUS STDCALL
CsrClientCallServer(PCSRSS_API_REQUEST Request,
PCSRSS_API_REPLY Reply OPTIONAL,
ULONG Length,
ULONG ReplyLength)
{
NTSTATUS Status;

if (INVALID_HANDLE_VALUE == WindowsApiPort)
{
DbgPrint ("NTDLL.%s: client not connected to CSRSS!\n", __FUNCTION__);
return (STATUS_UNSUCCESSFUL);
}

// DbgPrint("CsrClientCallServer(Request %x, Reply %x, Length %d, "
// "ReplyLength %d)\n", Request, Reply, Length, ReplyLength);

Request->Header.DataSize = Length;
Request->Header.MessageSize = sizeof(LPC_MESSAGE_HEADER) + Length;


Status = NtRequestWaitReplyPort(WindowsApiPort,
&Request->Header,
(Reply?&Reply->Header:&Request->Header));

// DbgPrint("Status %x\n", Status);

return(Status);
}

从代码中我们可以明白就是NtRequestWaitReplyPort完成了端口message发送
该函数的结构并不复杂,仅仅传送了handle和client_id过去
但是问题在于 这是一个r3下面才有的,在r0下面不好模拟他
偶尝试了在r0下面NtConnectPort,但是失败了
于是偶决定采取一个办法,自己操作csrss里面的句柄表
反正我们在r0下面。任何进程都可以随意读写
csrss完成了2个工作,第一CsrInsertThread CsrInsertProcess
向两个链表中分别插入记录
VOID
NTAPI
CsrInsertThread(IN PCSR_PROCESS Process,
IN PCSR_THREAD Thread)
{
ULONG i;

/* Insert it into the Regular List */
InsertTailList(&Process->ThreadList, &Thread->Link);

/* Increase Thread Count */
Process->ThreadCount++;

/* Hash the Thread */
i = CsrHashThread(Thread->ClientId.UniqueThread);

/* Insert it there too */
InsertHeadList(&CsrThreadHashTable, &Thread->HashLinks);
}
VOID
NTAPI
CsrInsertProcess(IN PCSR_PROCESS Parent OPTIONAL,
IN PCSR_PROCESS CurrentProcess OPTIONAL,
IN PCSR_PROCESS CsrProcess)
{
PCSR_SERVER_DLL ServerDll;
ULONG i;

/* Set the parent */
CsrProcess->Parent = Parent;

/* Insert it into the Root List */
InsertTailList(&CsrRootProcess->ListLink, &CsrProcess->ListLink);

/* Notify the Server DLLs */
for (i = 0; i < CSR_SERVER_DLL_MAX; i++)
{
/* Get the current Server DLL */
ServerDll = CsrLoadedServerDll;

/* Make sure it's valid and that it has callback */
if (ServerDll && ServerDll->NewProcessCallback)
{
(*ServerDll->NewProcessCallback)(CurrentProcess, CsrProcess);
}
}
}


上面的2段代码在控制着2个未导出链表,下面是2段炉子的资料
我花了大量时间在这些参考资料的基础上来逆向和重建整个子系统管理器的代码, 却发现它并没有想象中的那么难, 而且很有趣。Csrss 只是一个外壳, 所有有趣的东西都在 csrsrv.dll 里面。

csrsrv.dll 里面有一个未导出符号叫做 CsrRootProcess, 对 csrss 起着重要的作用。CsrRootProcess 指向一个 CSR_PROCESS 结构。

CSR_PROCESS 结构 (Vista/2008, 对于 XP/2003 同样适用) 如下:

typedef struct _CSR_PROCESS {
struct _CLIENT_ID ClientId;
struct _LIST_ENTRY ListLink;
struct _LIST_ENTRY ThreadList;
struct _CSR_NT_SESSION* NtSession;
ULONG ExpectedVersion;
void* ClientPort;
char* ClientViewBase;
char* ClientViewBounds;
void* ProcessHandle;
ULONG SequenceNumber;
ULONG Flags;
ULONG DebugFlags;
ULONG ReferenceCount;
ULONG ProcessGroupId;
ULONG ProcessGroupSequence;
ULONG fVDM;
ULONG ThreadCount;
ULONG LastMessageSequence;
ULONG NumOutstandingMessages;
ULONG ShutdownLevel;
ULONG ShutdownFlags;
struct _LUID Luid;
void* ServerDllPerProcessData[1];
} CSR_PROCESS, *PCSR_PROCESS;
Csrss 里面还存在着一个相似的, 关于线程的未导出符号叫做 CsrHashThread。它是一个有 256 个元素的数组, 每一个元素都指向一个 CSR_THREAD 结构, 而 CSR_THREAD 结构同样包含一个 LIST_ENTRY。

typedef struct _CSR_THREAD { // <size 0x38>
union _LARGE_INTEGER CreateTime;
struct _LIST_ENTRY Link;
struct _LIST_ENTRY HashLinks;
struct _CLIENT_ID ClientId;
struct _CSR_PROCESS* Process;
struct _CSR_WAIT_BLOCK* WaitBlock;
void* ThreadHandle;
unsigned long Flags;
unsigned long ReferenceCount;
unsigned long ImpersonateCount;
} CSR_THREAD, *PCSR_THREAD;

同样, 可以通过 CsrLockThreadByClientId 来定位 CsrHashThread。

炉子的文章中提到了这2者可以用来检测隐藏进程,因为这是进程建立必须的
我们可不可以不建立这个链表呢?可以,这样进程和线程是可以启动的
但是不能退出。一旦退出 CPU立刻100%,这是我们所不希望的
当然。权宜之计是 运行后suspend 这样没事。。不会cpu100%
我们一定要完全的 手工的做出这些链表来
不过我们不要担心,里面很多段是我们所不关心的,所以偶想了一个办法
就是伪造,找一个其他进程的字段来修改掉关键部分,然后 “欺骗欺骗”CSRSS
当然这也是会引起一些其他问题,就是这个进程要是再启动一个新进程或者是
一些需要再次告知csrss的操作可能会失败。。

先去吃饭。。。

[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞7
打赏
分享
最新回复 (10)
雪    币: 709
活跃值: (2230)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
sudami 25 2009-1-13 13:02
2
0
猪乌龟同学的好文要顶~~

学习~
雪    币: 66
活跃值: (16)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
炉子 3 2009-1-13 13:22
3
0
“下面是2段炉子的资料”

- -

我记得我转载的时候注明出处了。。

而且我转载的是英文的 - -#

PortHandle可以用FindCsrss的方法,Findcsrss的代码里面会找到ApiPort的LpcPortObject,ObOpenObjectByPointer然后NtRequestWaitReplyPort

btw:没测试
雪    币: 479
活跃值: (25)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
dttom 3 2009-1-13 15:34
4
0
谢谢分享,学习了
雪    币: 364
活跃值: (152)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
weolar 10 2009-1-13 19:40
5
0
这几天坐火车累的要死,没来的及修正我那文章的代码的dug,感谢补充
炉子说的FindCsrss是不是我那代码的方法?
雪    币: 618
活跃值: (65)
能力值: ( LV13,RANK:290 )
在线值:
发帖
回帖
粉丝
dge 6 2009-1-13 20:36
6
0
俺来学习。。。
雪    币: 7781
活跃值: (2246)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
option 2009-1-14 10:49
7
0
坚持到看懂为止
雪    币: 34
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jordanpz 2009-1-14 19:45
8
0
看不懂,。。。。
雪    币: 224
活跃值: (15)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
SafeNdis 3 2009-1-14 20:33
9
0
没到关键的,你这个没多大意思,我尝试过在驱动层CreateProcess,完全没有磁盘文件的,
发现ZWCREATEPROCESS必需要SECTION映射一个文件,所以也要有文件过滤驱动配合才能完成,没时间搞了,呵呵
雪    币: 224
活跃值: (15)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
SafeNdis 3 2009-1-14 20:35
10
0
有时间给我发表的贴子回复一下,谢谢,相互促进.
雪    币: 217
活跃值: (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
heretic 2009-1-14 23:09
11
0
这个其实可以借其他image的壳就是了,直接在其他进程里面写内存,建立线程。或者创建一个新的进程,暂停主线程,然后run自己写的代码。
游客
登录 | 注册 方可回帖
返回