简单方法从内核创建用户态线程.
在内核想要执行用户态的代码,通常的方式有.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
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!