【原创】从内核创建用户态线程

绝地灬酷狼 2022-08-10 15:54 348阅读 0赞

http://bbs.pediy.com/showthread.php?p=1304480\#post1304480

标 题: 【原创】从内核创建用户态线程
作 者: xSpy
时 间: 2014-07-29,11:56:33
链 接: http://bbs.pediy.com/showthread.php?t=190599

简单方法从内核创建用户态线程.

在内核想要执行用户态的代码,通常的方式有.apc, usermodecallback等.
但是都各有缺点.

APC.

  1. apc的分发必须不被禁用,
  2. 目标进程必须有处于alertable的线程.
    特别是后者这个条件,很多时候不一定有.
    比如explorer进程有很多线程,通常能找到.
    但是像记事本这种单线程程序,就找不到.尴尬

UserModeCallback.
必须在目标进程空间调用,不能是attach.的.
必须加载过User32的.,这样才有Kernelcallbacktable

在某些特定的时机,我们是有机会执行的.
比如在进程刚刚创建的时候,我们可以修改OEP,修改IAT等加载我们的dll
在第一个线程创建之后,我们可以插入apc.这些条件都很好满足.

还有WOW64的兼容处理,在另一篇文章里说明.

但是如果任何时候.不限制调用的时机,比如在进程正常运行之后,这个时候,这些条件都不满足了.

虽然我们可以构造出场景,
比如,如果你是一个过滤驱动或者hook型的,那么总是有机会会切换到目标进程上空的,这个时候就有机会可以UserModeCallback.

现在要说的就是没有这些限制的做法.可以在任意时机,任意进程空间在任意进程中执行代码.
那就是直接在内核态给一个用户态进程创建一个用户态的线程.

模拟用户态进程给自己创建一个非远程线程的基本流程.

1.创建线程初始的栈,分配和保留栈空间.设置栈保护页.实现栈的自动增长.

2设置线程上下文,各个段寄存器和基本寄存器,设置eip指向Kernel32!BaseThreadTrunk

.text:7C810473
.text:7C810473 ; =============== S U B R O U T I N E =======================================
.text:7C810473
.text:7C810473 ; Attributes: bp-based frame
.text:7C810473
.text:7C810473 ; int __stdcall BaseInitializeContext(PCONTEXT Context, PVOID Parameter, PVOID StartAddress, PVOID StackAddress, ULONG ContextType)
.text:7C810473 _BaseInitializeContext@20 proc near ; CODE XREF: CreateRemoteThread(x,x,x,x,x,x,x)+84↓p
.text:7C810473 ; CreateProcessInternalW(x,x,x,x,x,x,x,x,x,x,x,x)+690↓p …
.text:7C810473
.text:7C810473 Context = dword ptr 8
.text:7C810473 Parameter = dword ptr 0Ch
.text:7C810473 StartAddress = dword ptr 10h
.text:7C810473 StackAddress = dword ptr 14h
.text:7C810473 ContextType = dword ptr 18h
.text:7C810473
.text:7C810473 ; FUNCTION CHUNK AT .text:7C81F10A SIZE 00000019 BYTES
.text:7C810473 ; FUNCTION CHUNK AT .text:7C8316A2 SIZE 0000000F BYTES
.text:7C810473
.text:7C810473 8B FF mov edi, edi
.text:7C810475 55 push ebp
.text:7C810476 8B EC mov ebp, esp
.text:7C810478 8B 45 08 mov eax, [ebp+Context]
.text:7C81047B 8B 4D 10 mov ecx, [ebp+StartAddress]
.text:7C81047E 83 A0 8C 00 00 00 00 and [eax+CONTEXT.SegGs], 0
.text:7C810485 83 7D 18 01 cmp [ebp+ContextType], 1
.text:7C810489 89 88 B0 00 00 00 mov [eax+CONTEXT._Eax], ecx
.text:7C81048F 8B 4D 0C mov ecx, [ebp+Parameter]
.text:7C810492 89 88 A4 00 00 00 mov [eax+CONTEXT._Ebx], ecx
.text:7C810498 6A 20 push 20h
.text:7C81049A 59 pop ecx
.text:7C81049B 89 88 94 00 00 00 mov [eax+CONTEXT.SegEs], ecx
.text:7C8104A1 89 88 98 00 00 00 mov [eax+CONTEXT.SegDs], ecx
.text:7C8104A7 89 88 C8 00 00 00 mov [eax+CONTEXT.SegSs], ecx
.text:7C8104AD 8B 4D 14 mov ecx, [ebp+StackAddress]
.text:7C8104B0 C7 80 90 00 00 00 38 00+ mov [eax+CONTEXT.SegFs], 38h
.text:7C8104BA C7 80 BC 00 00 00 18 00+ mov [eax+CONTEXT.SegCs], 18h
.text:7C8104C4 C7 80 C0 00 00 00 00 30+ mov [eax+CONTEXT.EFlags], 3000h
.text:7C8104CE 89 88 C4 00 00 00 mov [eax+CONTEXT._Esp], ecx
.text:7C8104D4 0F 85 30 EC 00 00 jnz loc_7C81F10A
.text:7C8104DA C7 80 B8 00 00 00 29 07+ mov [eax+CONTEXT._Eip], offset _BaseThreadStartThunk@8 ; BaseThreadStartThunk(x,x)
.text:7C8104E4
.text:7C8104E4 loc_7C8104E4: ; CODE XREF: BaseInitializeContext(x,x,x,x,x)+ECAB↓j
.text:7C8104E4 ; BaseInitializeContext(x,x,x,x,x)+21239↓j
.text:7C8104E4 83 C1 FC add ecx, 0FFFFFFFCh
.text:7C8104E7 C7 00 07 00 01 00 mov [eax+CONTEXT.ContextFlags], 10007h
.text:7C8104ED 89 88 C4 00 00 00 mov [eax+CONTEXT._Esp], ecx
.text:7C8104F3 5D pop ebp
.text:7C8104F4 C2 14 00 retn 14h
.text:7C8104F4 _BaseInitializeContext@20 endp
.text:7C8104F4
.text:7C8104F4 ; —————————————————————————————————————-

3对于vista以后,还得分配TEB的ActiveContextStackPointer.要不然执行某些用户态的API的时候,那些API没有检查TEB的ActiveContextStackPointer是否为NULL就从中取值,造成崩溃.
windows的CreateThread也做了这些事.

.text:0DCEBD8A 23 4D 10 and ecx, [ebp+dwStackSize]
.text:0DCEBD8D 51 push ecx ; MaximumStackSize
.text:0DCEBD8E F7 D8 neg eax
.text:0DCEBD90 1B C0 sbb eax, eax
.text:0DCEBD92 23 45 10 and eax, [ebp+dwStackSize]
.text:0DCEBD95 50 push eax ; StackSize
.text:0DCEBD96 53 push ebx ; ZeroBits
.text:0DCEBD97 56 push esi ; CreateThreadFlags
.text:0DCEBD98 FF B5 B8 FD FF FF push [ebp+StartContext] ; StartContext
.text:0DCEBD9E FF B5 D0 FD FF FF push [ebp+StartRoutine] ; StartRoutine
.text:0DCEBDA4 FF B5 CC FD FF FF push [ebp+ProcessHandle] ; ProcessHandle
.text:0DCEBDAA FF B5 BC FD FF FF push [ebp+ObjectAttributes] ; ObjectAttributes
.text:0DCEBDB0 68 FF FF 1F 00 push 1FFFFFh ; DesiredAccess
.text:0DCEBDB5 8D 85 E4 FD FF FF lea eax, [ebp+hThread]
.text:0DCEBDBB 50 push eax ; ThreadHandle
.text:0DCEBDBC FF 15 74 13 CE 0D call ds:__imp__NtCreateThreadEx@44 ; NtCreateThreadEx(x,x,x,x,x,x,x,x,x,x,x)
.text:0DCEBDC2 89 85 E8 FD FF FF mov [ebp+var_218], eax
.text:0DCEBDC8 3B C3 cmp eax, ebx
.text:0DCEBDCA 0F 8C A5 F8 01 00 jl loc_DD0B675
.text:0DCEBDD0 89 5D FC mov [ebp+ms_exc.disabled], ebx
.text:0DCEBDD3 64 A1 18 00 00 00 mov eax, large fs:18h
.text:0DCEBDD9 8B 8D C0 FD FF FF mov ecx, [ebp+var_240]
.text:0DCEBDDF 3B 48 20 cmp ecx, [eax+20h]
.text:0DCEBDE2 75 73 jnz short loc_DCEBE57
.text:0DCEBDE4 8D 85 E0 FD FF FF lea eax, [ebp+var_220]
.text:0DCEBDEA 50 push eax
.text:0DCEBDEB FF 15 70 13 CE 0D call ds:__imp__RtlAllocateActivationContextStack@4 ; RtlAllocateActivationContextStack(x)
.text:0DCEBDF1 89 85 E8 FD FF FF mov [ebp+var_218], eax
.text:0DCEBDF7 3B C3 cmp eax, ebx
.text:0DCEBDF9 0F 8C B2 F8 01 00 jl loc_DD0B6B1
.text:0DCEBDFF 8B 85 E0 FD FF FF mov eax, [ebp+var_220]
.text:0DCEBE05 8B 8D D4 FD FF FF mov ecx, [ebp+var_22C]
.text:0DCEBE0B 89 81 A8 01 00 00 mov [ecx+1A8h], eax
.text:0DCEBE11 53 push ebx
.text:0DCEBE12 6A 08 push 8
.text:0DCEBE14 8D 85 D8 FD FF FF lea eax, [ebp+var_228]
.text:0DCEBE1A 50 push eax
.text:0DCEBE1B 56 push esi

4获取当前进程的BaseObject目录,可以是默认的

5 ZwCreateThread创建线程对象了.挂起的

6最重要的一点了.通知csrss进程,有新线程创建了.

.text:0DCEBE65 8B 85 E4 FD FF FF mov eax, [ebp+hThread]
.text:0DCEBE6B 89 85 18 FE FF FF mov [ebp+var_1E8], eax
.text:0DCEBE71 8B 85 C0 FD FF FF mov eax, [ebp+var_240]
.text:0DCEBE77 89 85 1C FE FF FF mov [ebp+var_1E4], eax
.text:0DCEBE7D 8B 85 C4 FD FF FF mov eax, [ebp+var_23C]
.text:0DCEBE83 89 85 20 FE FF FF mov [ebp+var_1E0], eax
.text:0DCEBE89 6A 0C push 0Ch
.text:0DCEBE8B 68 01 00 01 00 push 10001h
.text:0DCEBE90 53 push ebx
.text:0DCEBE91 8D 85 F0 FD FF FF lea eax, [ebp+var_210]
.text:0DCEBE97 50 push eax
.text:0DCEBE98 FF 15 F0 11 CE 0D call ds:__imp__CsrClientCallServer@16 ; CsrClientCallServer(x,x,x,x)
.text:0DCEBE9E 8B 85 10 FE FF FF mov eax, [ebp+var_1F0]
.text:0DCEBEA4
.text:0DCEBEA4 loc_DCEBEA4: ; CODE XREF: GetDiskFreeSpaceExA(x,x,x,x)+2FB9↓j
.text:0DCEBEA4 89 85 E8 FD FF FF mov [ebp+var_218], eax

7恢复线程的执行.

.text:0DCEBEC8
.text:0DCEBEC8 loc_DCEBEC8: ; CODE XREF: CreateRemoteThreadEx(x,x,x,x,x,x,x,x)+22A↑j
.text:0DCEBEC8 F6 45 1C 04 test byte ptr [ebp+dwCreationFlags], 4
.text:0DCEBECC 75 13 jnz short loc_DCEBEE1
.text:0DCEBECE 8D 85 AC FD FF FF lea eax, [ebp+var_254]
.text:0DCEBED4 50 push eax
.text:0DCEBED5 FF B5 E4 FD FF FF push [ebp+hThread]
.text:0DCEBEDB FF 15 3C 13 CE 0D call ds:__imp__NtResumeThread@8 ; NtResumeThread(x,x)
.text:0DCEBEE1
.text:0DCEBEE1 loc_DCEBEE1: ; CODE XREF: CreateRemoteThreadEx(x,x,x,x,x,x,x,x)+238↑j
.text:0DCEBEE1 ; GetDiskFreeSpaceExA(x,x,x,x)+2F6E↓j …
.text:0DCEBEE1 C7 45 FC FE FF FF FF mov [ebp+ms_exc.disabled], 0FFFFFFFEh
.text:0DCEBEE8 E8 34 00 00 00 call sub_DCEBF21
.text:0DCEBEED 8B 85 E4 FD FF FF mov eax, [ebp+hThread]
.text:0DCEBEF3
.text:0DCEBEF3 loc_DCEBEF3: ; CODE XREF: GetDiskFreeSpaceExA(x,x,x,x)+2F27↓j
.text:0DCEBEF3 E8 A1 AD FF FF call __SEH_epilog4_GS
.text:0DCEBEF8 C2 20 00 retn 20h
.text:0DCEBEF8 _CreateRemoteThreadEx@32 endp
.text:0DCEBEF8
.text:0DCEBEF8 ; —————————————————————————————————————-

对于windows的CreateThread还有一些其他的操作,比如判断是否是csrss进程自己在创建线程.
vista以后对于远程的线程,还有session的检查等.

听起来很麻烦的一件事情,其实我们可以简化问题.
在我的实现里,不考虑csrss自己给自己创建线程的情况,
实际上我们创建的都是普通的线程,非远程的,

很多同学尝试过模拟这个过程,大部分都在第6步卡住了,.这一步比较麻烦.

每个用户态进程在创建的时候,都会连接 \\Windows\\ApiPort ,
但是发现,如果我们在内核直接连接csrss的这个port,是连不上的.需要patch.

其实可以不用patch.,直接切换到csrss空间,自己来操作CsrProcessTable等内置数据结构,但是不同意.

我用到的办法比较简单.
因为目标进程已经连接过了.这个句柄还是有符号的, CsrPortHandle.
既然从内核连接不上,我们可以在系统句柄表里去搜索这个句柄,

搜索所有的 LpcPort或 AlpcPort类型的句柄,
判断是否是我们需要的进程,
然后判断他们的ConnectionPort是否是\\Windows\\ApiPort.
找到句柄之后,duplicate到当前进程,

就可以ZwRequestWaitReplyPort 或ZwAlpcSendWaitReceivePort通知csrss了.

关于 Kernel32!BaseThreadTrunk 我并没有直接把eip指向这个地方,这个函数没有导出.

.text:7C810729
.text:7C810729 ; =============== S U B R O U T I N E =======================================
.text:7C810729
.text:7C810729 ; Attributes: noreturn
.text:7C810729
.text:7C810729 ; int __stdcall BaseThreadStartThunk(int, int)
.text:7C810729 _BaseThreadStartThunk@8 proc near ; DATA XREF: BaseInitializeContext(x,x,x,x,x)+67↑o
.text:7C810729
.text:7C810729 arg_0 = dword ptr 4
.text:7C810729 arg_4 = dword ptr 8
.text:7C810729
.text:7C810729 33 ED xor ebp, ebp
.text:7C81072B 53 push ebx ; Param
.text:7C81072C 50 push eax ; StartAddress
.text:7C81072D 6A 00 push 0
.text:7C81072F E9 BE AF FF FF jmp _BaseThreadStart@8 ; BaseThreadStart(x,x)
.text:7C81072F _BaseThreadStartThunk@8 endp
.text:7C81072F
.text:7C81072F ; —————————————————————————————————————-

.text:7C80B6F2
.text:7C80B6F2 ; =============== S U B R O U T I N E =======================================
.text:7C80B6F2
.text:7C80B6F2 ; Attributes: noreturn bp-based frame
.text:7C80B6F2
.text:7C80B6F2 ; int __stdcall BaseThreadStart(int StartAddress, int ThreadParam)
.text:7C80B6F2 _BaseThreadStart@8 proc near ; CODE XREF: BaseThreadStartThunk(x,x)+6↓j
.text:7C80B6F2 ; BaseFiberStart()+12↓p
.text:7C80B6F2
.text:7C80B6F2 Teb = dword ptr -20h
.text:7C80B6F2 ms_exc = CPPEH_RECORD ptr -18h
.text:7C80B6F2 StartAddress = dword ptr 8
.text:7C80B6F2 ThreadParam = dword ptr 0Ch
.text:7C80B6F2
.text:7C80B6F2 6A 10 push 10h
.text:7C80B6F4 68 30 B7 80 7C push offset stru_7C80B730
.text:7C80B6F9 E8 D8 6D FF FF call __SEH_prolog
.text:7C80B6FE 83 65 FC 00 and [ebp+ms_exc.disabled], 0
.text:7C80B702 64 A1 18 00 00 00 mov eax, large fs:18h
.text:7C80B708 89 45 E0 mov [ebp+Teb], eax
.text:7C80B70B 81 78 10 00 1E 00 00 cmp dword ptr [eax+10h], 1E00h
.text:7C80B712 75 0F jnz short loc_7C80B723
.text:7C80B714 80 3D 08 50 88 7C 00 cmp _BaseRunningInServerProcess, 0
.text:7C80B71B 75 06 jnz short loc_7C80B723
.text:7C80B71D FF 15 F8 12 80 7C call ds:__imp__CsrNewThread@0 ; CsrNewThread()
.text:7C80B723
.text:7C80B723 loc_7C80B723: ; CODE XREF: BaseThreadStart(x,x)+20↑j
.text:7C80B723 ; BaseThreadStart(x,x)+29↑j
.text:7C80B723 FF 75 0C push [ebp+ThreadParam]
.text:7C80B726 FF 55 08 call [ebp+StartAddress]
.text:7C80B729 50 push eax ; dwExitCode
.text:7C80B72A
.text:7C80B72A loc_7C80B72A: ; CODE XREF: .text:7C83AB3B↓j
.text:7C80B72A E8 C9 09 00 00 call _ExitThread@4 ; ExitThread(x)
.text:7C80B72A _BaseThreadStart@8 endp
.text:7C80B72A
.text:7C80B72A ; —————————————————————————————————————-

而且我还需要分配ActiveContextStackPointer,.
所以新线程的eip实际上是指向一段stub,
在stub里分配ActiveContextStackPointer,然后模拟的call 线程的起始地址,
然后调用RtlExitUserThread,确保在StartAddress ret的时候,可以自行退出.
就像系统做的那样.

mov edi,API_RtlExitUserThread
test edi,edi
je _DirectRet

;调用用户提供的线程函数地址
mov eax,var_StartAddress
mov ebx,var_ThreadParam

push ebx ;线程的参数
call eax ;线程的起始地址

;是的用户线程函数返回时,我们可以让线程退出
push eax
call API_RtlExitUserThread

流程说完了.现在我们已经在内核模拟一个用户态线程给自己创建了一个线程.
非远程的,支持WOW64.

没有那么多限制条件执行用户态代码之后,可以做的事情就只局限于你的想象力了.
给目标进程注入一个dll简直是一个小意思了.

如果有事先执行的机会,就可以伪造各个杀毒软件或者系统进程的身份了.狂笑

附一些代码,因为依赖比较多,只贴关键的说明问题.

全文代码在下列系统测试通过.

xp/2003 32
win7/8/8.1 32/64

xSpy@binvul.com
xSpy@vxjump.net

排版的问题,代码还是传word吧.恐怖!
CreateUserModeThreadFromKernelLand.doc.

发表评论

表情:
评论列表 (有 0 条评论,348人围观)

还没有评论,来说两句吧...

相关阅读

    相关 用户内核

    用户态是指应用程序运行的环境,应用程序在用户态下运行,可以访问系统资源,如文件、网络等。用户态下的应用程序运行在受限的环境中,不能直接访问系统硬件资源,必须通过系统调用来请求内

    相关 linux内核用户

    用户态和内核态是操作系统的两种运行级别,两者最大的区别就是特权级不同。 1、内核态、用户态概念 内核态:也叫内核空间,是内核进程/线程所在的区域。主要负责运行系统、硬

    相关 03 用户内核

    用户态和内核态 划分的原因:为了防止用户进程破坏操作系统的稳定,对一些资源的访问进行了等级划分,与系统相关的一些关键性操作必须由高级别的程序来完成,这样可以做到集中

    相关 Linux用户内核

    1. 用户态和内核态的概念区别 究竟什么是用户态,什么是内核态,这两个基本概念以前一直理解得不是很清楚,根本原因个人觉得是在于因为大部分时候我们在写程序时关注的重点和着眼的角