首页
社区
课程
招聘
[原创]内核创建用户线程基本套路
发表于: 2017-11-29 15:32 12746

[原创]内核创建用户线程基本套路

2017-11-29 15:32
12746
之前的APC暂告一段落,实际结果表明APC注入还是有很多限制,如果想注入后立即执行,不管你怎么样去唤醒(alert)一个线程,该出问题的还是会有问题,如果实际应用的话也不敢冒然去唤醒一个线程,顶多也只是找可唤醒的进程,所以想要更稳定的注入还是得换个法子。

其实早就在网上看到过大牛写的内核创建用户线程的帖子[原创]从内核创建用户态线程,还从各种评论里瞄到过最稳定的莫过于内核创建线程了,但是无奈帖子里没有给出完整的代码,而且难度有点高,一时实现不了-,-

其实,从我们新人的角度来看,无非就是我们在内核去调用ZwCreateThread,而且参数和线程函数都需要在目标进程地址空间里,这个可以理解,因为我们毕竟是在模拟用户空间去调用CreateThread,那么思路就来了,我们只要能想办法把线程函数和参数拷贝到目标进程地址空间,然后,在内核成功调用 ZwCreateThread的话就可以成功吃鸡了。拷贝函数和参数到用户地址空间其实和前面插apc的时候一样的思路,不在赘述。

观察一下 ZwCreateThread的定义
NTSTATUS
NTAPI
ZwCreateThread (
    __out PHANDLE ThreadHandle,
    __in ACCESS_MASK DesiredAccess,
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
    __in HANDLE ProcessHandle,
    __out PCLIENT_ID ClientId,
    __in PCONTEXT ThreadContext,
    __in PINITIAL_TEB InitialTeb,
    __in BOOLEAN CreateSuspended
    );
这是在wrk里的定义,它是一个未导出的函数,在ida里看一下win7上的定义,还好没有变-,-

这里的参数其实大致上都可以理解,比较难解决的就是这个ThreadContext和InitialTeb了吧

先看一下 InitialTeb,他的定义如下
typedef struct _INITIAL_TEB {
    struct {
        PVOID OldStackBase;
        PVOID OldStackLimit;
    } OldInitialTeb;
    PVOID StackBase;
    PVOID StackLimit;
    PVOID StackAllocationBase;
} INITIAL_TEB, *PINITIAL_TEB;
在wrk里 RtlpCreateStack函数中对他进行了赋值,感兴趣的可以仔细研究一下具体的实现,但是我们暂时先一切从简,关键的复制部分如下
 InitialTeb->OldInitialTeb.OldStackBase = NULL;
 InitialTeb->OldInitialTeb.OldStackLimit = NULL;
 InitialTeb->StackAllocationBase = Stack;
 InitialTeb->StackBase = Stack + MaximumStackSize;
 ......
 InitialTeb->StackLimit = Stack;
 ......
 Status = ZwAllocateVirtualMemory( Process,
                                   (PVOID *)&Stack,
                                   0,
                                   &CommittedStackSize,
                                   MEM_COMMIT,
                                   PAGE_READWRITE
这里我暂时的理解是StackBase就是栈底 StackLimit理解为栈顶,StackAllocationBase为栈空间的起始地址。

再看另一个参数 ThreadContext,他的定义如下
typedef struct _CONTEXT {
    ULONG ContextFlags;
    ULONG   Dr0;
    ULONG   Dr1;
    ULONG   Dr2;
    ULONG   Dr3;
    ULONG   Dr6;
    ULONG   Dr7;
    ULONG   SegGs;
    ULONG   SegFs;
    ULONG   SegEs;
    ULONG   SegDs;
    ULONG   Edi;
    ULONG   Esi;
    ULONG   Ebx;
    ULONG   Edx;
    ULONG   Ecx;
    ULONG   Eax;
    ULONG   Ebp;
    ULONG   Eip;
    ULONG   SegCs;              // MUST BE SANITIZED
    ULONG   EFlags;             // MUST BE SANITIZED
    ULONG   Esp;
    ULONG   SegSs;
    UCHAR   ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
wrk中对他的赋值在RtlInitializeContext中
VOID
RtlInitializeContext(
    IN HANDLE Process,
    OUT PCONTEXT Context,
    IN PVOID Parameter OPTIONAL,
    IN PVOID InitialPc OPTIONAL,
    IN PVOID InitialSp OPTIONAL
    )

/*++

Routine Description:

    This function initializes a context structure so that it can
    be used in a subsequent call to NtCreateThread.

Arguments:

    Context - Supplies a context buffer to be initialized by this routine.

    InitialPc - Supplies an initial program counter value.

    InitialSp - Supplies an initial stack pointer value.

Return Value:

    Raises STATUS_BAD_INITIAL_STACK if the value of InitialSp is not properly
           aligned.

    Raises STATUS_BAD_INITIAL_PC if the value of InitialPc is not properly
           aligned.

--*/

{
    RTL_PAGED_CODE();

    Context->Eax = 0L;
    Context->Ebx = 1L;
    Context->Ecx = 2L;
    Context->Edx = 3L;
    Context->Esi = 4L;
    Context->Edi = 5L;
    Context->Ebp = 0L;

    Context->SegGs = 0;
    Context->SegFs = KGDT_R3_TEB;
    Context->SegEs = KGDT_R3_DATA;
    Context->SegDs = KGDT_R3_DATA;
    Context->SegSs = KGDT_R3_DATA;
    Context->SegCs = KGDT_R3_CODE;

    Context->EFlags = 0x200L;	    // force interrupts on, clear all else.

    //
    // Even though these are optional, they are used as is, since NULL
    // is what these would have been initialized to anyway
    //

    Context->Esp = (ULONG) InitialSp;
    Context->Eip = (ULONG) InitialPc;

    //
    // add code to check alignment and raise exception...
    //

    Context->ContextFlags = CONTEXT_CONTROL|CONTEXT_INTEGER|CONTEXT_SEGMENTS;

    //
    // Set the initial context of the thread in a machine specific way.
    // ie, pass the initial parameter to the start address
    //

    Context->Esp -= sizeof(Parameter);
    ZwWriteVirtualMemory(Process,
			 (PVOID)Context->Esp,
			 (PVOID)&Parameter,
			 sizeof(Parameter),
			 NULL);
    Context->Esp -= sizeof(Parameter); // Reserve room for ret address
}
这里的处理在win7 x86上一样有效,可以清楚的看到这个初始化过程,关键的esp eip的赋值以及参数的压入,最后的 Context->Esp -= sizeof(Parameter);//给返回地址的压入预留空间,可见我们的thread是 call thread方式启动,符合stdcall的调用规则。

这两个参数的赋值方式我们都搞明白了,这时候我们就可以放心调用ZwCreateThread了,这个函数的地址我们可以暴搜,也可以直接拿ssdt表里的NtCreateThread,效果一样(x64我这里就是暴搜的)。调用完成后记得关闭你创建的线程句柄
 status = MyZwCreateThread(g_hProcess, func_address, param_address, &ThreadStackSize, &ThreadStackAddress, &ThreadHandle);
 if (NT_SUCCESS(status))
 {
     ZwClose(ThreadHandle);
 }
这里不关闭这个句柄的话会在pchunter里看到这个线程而且teb=NULL,而且模块名也为空,这里的原因暂时还没太搞懂,我们创建线程的时候其实teb是正常创建的,teb的创建和ethread的初始化在更底层的PspCreateThread内。在windbg里,由于我的线程只加载了dll后就退出了,所以windbg里实际上是看不到这个异常的线程的,只有在puhunter里找到了这个异常的线程并且标红了,猜测:这里pchunter应该是扫描了句柄表,而且此时teb已经被释放了,我们在内核创建的线程又无法定位模块所以才会有这样的现象。

说到这里,我们其实就已经可以顺利创建线程了,我没有去通知crss进程什么的,只是按照最基本的思路走,事实证明win 7 x86与x64同样有效。

还有需要注意的一点就是shellcode吧,注意这里我们写的是一个线程的shllcode,我的线程要加载一个dll,然后要自己退出, 关键在于退出,这里我们需要找到RtlExitUserThread()而不是直接ret ,所以我们需要定位两个系统函数,一个是LoadLibraryA、一个是 RtlExitUserThread(), RtlExitUserThread 在ntdll里,这个也好理解,因为ret到哪呢-,-

最后在说一下x64上的 RtlInitializeContext,只有这里和x86有些出入
VOID
RtlInitializeContext(
IN HANDLE Process,
OUT PCONTEXT Context,
IN PVOID Parameter OPTIONAL,
IN PVOID InitialPc OPTIONAL,
IN PVOID InitialSp OPTIONAL
)
{
    Context->Rax = 0;
    Context->Rbx = 0;
    //只有一个参数,通过rcx传递
    Context->Rcx = (ULONG_PTR)Parameter;
    Context->Rdx = 0;
    Context->Rsi = 0;
    Context->Rdi = 0;
    Context->Rbp = 0;
    Context->SegGs = 0;
    Context->SegFs = 0;
    Context->SegEs = 0;
    Context->SegDs = 0;
    Context->SegSs = 0;
    Context->SegCs = 0;
    Context->EFlags = 0;
    Context->Rsp = (ULONG_PTR)InitialSp;
    Context->Rip = (ULONG_PTR)InitialPc;
    Context->ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS;
    /*
    The stack is 16 - byte aligned.The "call" instruction pushes an 8 - byte return value, so the all
    non-leaf functions must adjust the stack by a value of the form 16n + 8 when allocating stack space
    但是这里预留最少预留0x10也是可以的
    */
    //shadow space + align = 0x20 + 0x8 
    Context->Rsp -= 0x28;
}
关键的寄存器是rsp rip rcx,根据fastcall调用约定,rcx保存参数地址,而且我们只有一个参数,rip指向shellcode入口,rsp指向栈空间,注意这里的rsp操作,预留了0x28的空间。

综上所述,在win7 x86和x64上测试注入没有问题,这个思路应该来说是最简单的了,参数大牛的帖子这里还有很多线程初始化的操作没有做,只是暂时没有发现有什么影响。完整工程在附件哦,下载记得回复~ 说的不对的地方大家不要客气帮我指出来我去查资料哈-,-


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
免费 2
支持
分享
最新回复 (21)
雪    币: 310
活跃值: (2227)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
mark
2017-11-29 17:52
0
雪    币: 60
活跃值: (1010)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
win10行不行?
2017-11-29 21:11
0
雪    币: 608
活跃值: (643)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
NT6可以直接ZwCreateThreadEx啊,这个咋看着这么费劲-  -
2017-11-29 23:45
0
雪    币: 433
活跃值: (891)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
5



Morgion

NT6可以直接ZwCreateThreadEx啊,这个咋看着这么费劲- -
没想到这个啊,我是看wrk里面的ZwCreateThread顺路想到这里的,这里的应该xp也能跑,我去试试
2017-11-30 09:16
0
雪    币: 433
活跃值: (891)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
6
yangya win10行不行?
没试过啊,感兴趣的话可以换下硬编码试试看-,-
2017-11-30 09:18
0
雪    币: 1264
活跃值: (1850)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
...不知道楼主什么情况,我是很正常的apc注入进单线程程序里了
2017-11-30 10:00
0
雪    币: 433
活跃值: (891)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
8
库尔 ...不知道楼主什么情况,我是很正常的apc注入进单线程程序里了
DWORD  g_div  =  0;

DWORD  WINAPI  ThreadProc(
    _In_  LPVOID  lpParameter
    )
{
    int  i  =  30;
    while  (true)
    {
        Sleep(1000);
        i--;
        printf("%d\r\n",  i);
        if  (i  ==  0)
        {
            break;
        }
    }

    g_div  =  2;
    return  0;
}


int  _tmain(int  argc,  _TCHAR*  argv[])
{
    DWORD  ThreadId  =  0;
    HANDLE  hThread  =  CreateThread(NULL,  0,  ThreadProc,  NULL,  0,  &ThreadId);
    if  (hThread  !=  NULL)
    {
        WaitForSingleObject(hThread,  INFINITE);
        printf("%d\r\n",  10  /  g_div);
    }

    printf("end\r\n");
    getchar();
    return  0;
}
这个demo你试试能不能注入
2017-11-30 10:37
0
雪    币: 1264
活跃值: (1850)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
Justgoon DWORD g_div = 0; DWORD WINAPI ThreadProc( _In_ LPVOID lpParameter ) { int i = 30; wh ...
#include<windows.h>  
#include <strsafe.h>//win2003SDK必须安装 要不无此头文件。此文件是为了实现StringCchPrintf,StringCchLength。
#include<conio.h>  
#include<stdio.h> 
typedef
void
(*shellcode_MDL)( void* NormalContext,
void* SystemArgument1,
void* SystemArgument2);
WINUSERAPI int WINAPI MessageBoxA(

HWND hWnd,

LPCSTR lpText,

LPCSTR lpCaption,

UINT uType);
LPCSTR AA = "23333";
DWORD WINAPI APCFunction(LPVOID lpParam);
INT main(void* NormalContext,
void* SystemArgument1,
void* SystemArgument2){
LPDWORD i = NULL;
//CreateThread(NULL, 0, APCFunction, NULL, 0, i);
/*while (true)
{

}
return 1;*/

MessageBoxA(NULL, AA, AA, MB_ICONINFORMATION | MB_YESNO);
_getch();

}

DWORD WINAPI APCFunction(LPVOID lpParam)
{
LPVOID i = lpParam;
//SleepEx(INFINITE, TRUE);
return 0;
}之前写的demo
上传的附件:
2017-11-30 11:04
0
雪    币: 965
活跃值: (89)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
mark
2017-12-9 12:02
0
雪    币: 1264
活跃值: (1850)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
库尔 #include&lt;windows.h&gt;&nbsp;&nbsp;#include &lt;strsafe.h&gt;//win2003SDK必 ...
楼主有研究过csrss报文发送么,我也卡在那了,可以不如加下QQ联系下共同进步。
2017-12-12 19:03
0
雪    币: 429
活跃值: (388)
能力值: ( LV6,RANK:81 )
在线值:
发帖
回帖
粉丝
12
不通知当然有问题了,因为你dll里没创建线程,如果创建了你会发现必然失败,所以必须通知
2018-2-11 22:26
0
雪    币: 429
活跃值: (388)
能力值: ( LV6,RANK:81 )
在线值:
发帖
回帖
粉丝
13
通知还不算太难,难的是跨session通知
2018-2-11 22:27
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
围观,楼主牛逼
2018-2-13 12:45
0
雪    币: 40
活跃值: (303)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
xp下 0xc000000d报错,没找出什么原因,还在看
2018-7-15 20:31
0
雪    币: 516
活跃值: (3141)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wem
16
好文章
2018-7-16 20:07
0
雪    币: 40
活跃值: (303)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
找到原因了,是teb内存对齐问题,xp 2003 没问题了,跨session也ok。
2018-7-20 16:40
0
雪    币: 112
活跃值: (201)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
我在xp下也遇到  0xc000000d 注入失败
2019-4-24 16:09
0
雪    币: 112
活跃值: (201)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
CWangChao 找到原因了,是teb内存对齐问题,xp 2003 没问题了,跨session也ok。
是怎么解闷决的能贴下代码吗
2019-4-24 16:22
0
雪    币: 419
活跃值: (96)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
mark一下
2019-5-29 09:04
0
雪    币: 1084
活跃值: (340)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
21
CWangChao 找到原因了,是teb内存对齐问题,xp 2003 没问题了,跨session也ok。
和对齐没有关系
2019-12-18 09:08
0
雪    币: 1084
活跃值: (340)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
22
zazazabo 我在xp下也遇到 0xc000000d 注入失败
xp下这个情况应该是没有通知csrss导致的
2019-12-18 09:08
0
游客
登录 | 注册 方可回帖
返回
//