欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[翻译]Windows内核ShellCode的动态加载和调试

[翻译]Windows内核ShellCode的动态加载和调试

Windbg下Windows内核ShellCode的加载和调试。DoublePulsar ShellCode调试实战。

在本篇文章中,我将分享一个可以从文件中加载一段shellcode到内核空间并且建立内核线程运行它的windbg脚本。由于我玩过的脚本并不是很多,所以如果有一些bug请告知我。

Windng加载shellcode以及创建线程运行该shellcode的脚本

你可以从我的github上找到这段脚本:

load_code_to_kernel_memory.wdbg

脚本的运行参数:

$$>a<load_code_to_kernel_memory.wdbg <src code> <mem size> <offset start routine>

第一个参数是含有shellcode的文件的路径。第二个参数是申请的内存大小(足够分配shellcode即可)。第三个参数是被执行的shellcode的起始偏移。

注意:包含shellcode的文件的大小应该被填充至内存分页的大小。我们是使用.readmem命令来加载shellcode,该命令将会读取一个0x1000字节大小的区块。举个例子,若你的shellcode有0x2800字节,那么.readmem将只会加载0x2000字节。所以你需要使用0x800附加的垃圾数据来补全文件以便加载完整的代码。

该脚本的简明说明

我们需要劫持一个正在运行的线程一小会。将该线程的执行重定向到ExAllocatePool来为shellcode申请内存(同时需要操作被劫持线程的栈来完成这个过程,之后再来恢复)

为此,我们需要在NtCreateFile中设置一个断点(非常频繁使用的一个API)。当线程停在该断点上的时候,就可以进行操作了:

$$首先在一个频繁使用的API上设置一个断点(这里的例子是NtCreateFile)以便劫持线程

ba e1 nt!CreateFile

g

.printf"${$arg1}"

.printf"${$arg2}"

.printf"${$arg3}"

bc *

$$保存为了调用ExAllocatePool个PsCreateSystemThread而即将被修改的原始的ESP寄存器以及栈中的参数

r @$t19 = (poi esp)

r @$t18 = (poi esp+4)

r @$t17 = (poi esp+8)

r @$t16 = (poi esp+c)

r @$t15 = (poi esp+10)

r @$t14 = (poi esp+14)

r @$t13 = (poi esp+18)

r @$t12 = (poi esp+1c)

r @$t11 = esp

一旦建立线程之后,修改EIP以及栈信息来执行ExAllocatePool

$$修改ExAllocatePool执行所需要的栈参数

ed (esp+4) 0

ed (esp+8) ${$arg2}

$$劫持运行在NtCreateFile上的线程执行ExAllocatePool

u nt!ExAllocatePool

dd esp

r eip = nt!ExAllocatePool

$$步入,直到发现ret指令。我们无法执行步出(gu)命令,因为会产生一个

$$0x30 bugcheck,原因如下:

$$"当从陷阱中恢复上下文时,当检测到堆栈下溢时,会发生该错误检查。"

$$"有一个错误检查来验证当前的ESP是否小于保存在陷阱帧中的ESP"

$$"current_esp 是否小于 saved_esp"

.while(1)

{

p

r @$t10 = (poi eip)

r @$t10 = @$t10 & 0x000000ff

.if (@$t10 == 0xc2)

{

.break

}

}

r @$t0 = eax

.printf “allocated mem: %x\n”, @$t0

此时,我们已经为shellcode分配了足够的内存空间,加载它吧:

$$从文件中加载shellcode到申请的内存中

$$careful: .readmem 将会读取0x1000大小的块.例如,文件有0x2800字节,

$$.readmem只会读取0x2000字节

$$你需要使用0x800附加的垃圾数据来补全文件以便加载完整的代码。

.readmem ${$arg1} @$t0

$$ @$t1 = allocated mem membase, code is read

r @$t1 = @$t0

现在,我们希望在shellcode + arg3处创建一个线程:

$$此时,将在 @$t1 + arg3 (membase + startroutine_offset) 处建立内核线程

$$ NTSTATUS PsCreateSystemThread(

$$ _Out_ PHANDLE ThreadHandle,

$$ _In_ ULONG DesiredAccess,

$$ _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,

$$ _In_opt_ HANDLE ProcessHandle,

$$ _Out_opt_ PCLIENT_ID ClientId,

$$ _In_ PKSTART_ROUTINE StartRoutine,

$$ _In_opt_ PVOID StartContext

$$ );

ed (esp+1c) 0

ed (esp+18) @$t1+${$arg3}

ed (esp+14) 0

ed (esp+10) 0

ed (esp+c) 0

ed (esp+8) 0

$$ThreadHandle, 我们使用未使用的参数StartContext的内存地址。

ed (esp+4) (esp+1c)

$$在即将被创建的线程中设置一个断点。

ba e1 @$t1+${$arg3}

u nt!PsCreateSystemThread

dd esp

r eip = nt!PsCreateSystemThread

$$again steps until ret instruction is found

.while (1)

{

p

r @$t10 = (poi eip)

r @$t10 = @$t10 & 0x000000ff

.if (@$t10 == 0xc2)

{

.break

}

}

最后,我们恢复堆栈和eip以在NtCreateFile正确地继续执行被劫持的线程,否则系统将崩溃:

$$恢复原始寄存器及栈来继续无障碍的运行

r eip = nt!NtCreateFile

r esp = @$t11

ed esp @$t19

ed (esp+4) @$t18

ed (esp+8) @$t17

ed (esp+c) @$t16

ed (esp+10) @$t15

ed (esp+14) @$t14

ed (esp+18) @$t13

ed (esp+1c) @$t12

g

在此之后,windbg应该在线程启动的shellcode的偏移处的断点处停止。

使用DoublePulsar shellcode来测试脚本

我们将使用从一个worm/ransom 恶意软件 i don’t want to remember的中提取的DoublePulsar Shellcode来测试脚本。

你可以在此处下载shellcode here (rar password: infected).

文件的大小是0x3000。我并没有深入的逆向shellcode,但开始调试的好起点看起来应该是0x221偏移处(稍后我们将会知道why)。

执行脚本,下变是打印的调试跟踪:

kd> $$>a<load_code_to_kernel_memory.wdbg shellcode.bin 3000 221

Breakpoint 0 hit <- nt!NtCreateFile hit

nt!ExAllocatePool:

8261e976 8bff mov edi,edi

8261e978 55 push ebp

8261e979 8bec mov ebp,esp

8261e97b 684e6f6e65 push 656E6F4Eh

8261e980 ff750c push dword ptr [ebp+0Ch]

8261e983 ff7508 push dword ptr [ebp+8]

8261e986 e87a461100 call nt!ExAllocatePoolWithTag (82733005)

8261e98b 5d pop ebp

9e867d04 826511ea 00000000 00003000 040bfb54 <- stack for ExAllocatePool

allocated mem: 849f4000 <- allocated memory

Reading 10000 bytes…… <- .readmem shellcode

nt!PsCreateSystemThread:

8281bfb6 8bff mov edi,edi

8281bfb8 55 push ebp

8281bfb9 8bec mov ebp,esp

8281bfbb 83e4f8 and esp,0FFFFFFF8h

8281bfbe 83ec34 sub esp,34h

8281bfc1 a148da7382 mov eax,dword ptr [nt!__security_cookie (8273da48)]

8281bfc6 33c4 xor eax,esp

8281bfc8 89442430 mov dword ptr [esp+30h],eax

9e867d04 826511ea 9e867d20 00000000 00000000 <- stack for PsCreateSystemThread

9e867d14 00000000 00000000 849f4221 00000000

9e867d24 00000001 00000060 00000000 00000000

Breakpoint 0 hit <- shellcode hit

849f4221 b923000000 mov ecx,23h

0: kd> u eip

849f4221 b923000000 mov ecx,23h

849f4226 6a30 push 30h

849f4228 0fa1 pop fs

849f422a 8ed9 mov ds,cx

849f422c 8ec1 mov es,cx

849f422e 648b0d40000000 mov ecx,dword ptr fs:[40h]

849f4235 8b6104 mov esp,dword ptr [ecx+4]

849f4238 ff35fcffdfff push dword ptr ds:[0FFDFFFFCh]

SYSENTER_EIP HOOK

可以看见新线程停在了我们指定的位置:

在0x20B偏移处可以看见shellcode hook了SYSENTER_EIP。

shellcode使用了地址0xFFDFFFFC来存储 MSR[0x176]。可以执行下边的命令:

1: kd> dt nt!_kuser_shared_data ffdf0000

Nt!_kuser_shared_data结构位于0xFFDF0000处,所以我猜测shellcode是使用了和nt!_kuser_shared_data的结构之后的位于同页的内存空间来存储其需要的临时变量。

关于通过MSR来HOOK系统调用可以参考下边这篇文章(非常有趣哦):

http://resources.infosecinstitute.com/hooking-system-calls-msrs

所以,0x221偏移是HOOK SYSENTER_EIP,因此,我认为这里是一个好的调试点。

然我们继续逆向 SYSENTER_EIP HOOK的代码:

在内核领空中,FS:[0]指向_KPCR结构。在这里,可以看见shellcode是如何从_KPCR中获取一些它所需要的值以及其他的结构体指针的过程。

  • fs:0x40 -> _KTSS

  • _KTSS + 4 -> Esp0  (correct Esp for continue executing in kernel)

  • nt!kuser_shared_data+0x304 -> SystemCallReturn

  • _KPCR + 0x1C -> SelfPcr (_KPCR)

  • SelfPcr + 0x120 -> PrcbData (_KPRCB)

  • PrcbData+0x4 -> CurrentThread (_KTHREAD)

  • CurrentThread+0x28 -> InitialStack

    初始化完成之后,shellcode调用了HOOK的主代码,但是在这之前,它又恢复了MSR[0x176] (SYSENTER_EIP)。它已经有一个来自用户模式的线程,并且可能对它的目的而言已经足够了。

    SHELLCODE 主代码

    我并没有深入的调试这段shellcode,但是我们将会看一下shellcode主代码从SYSENTER_EIP HOOK处执行的第一部分。

以上可以看到shellcode是如何使用IDT中的指针来获取ntoskrnl.exe的地址。

以这种遍历内存空间直到找到PE头的方式,它可以找到ntoskrnl.exe的基址。

在找到基址之后,shellcode通过API的CRC(译者注:应该是HASH,更加短小精悍,而不是CRC):ExAllocatePool ExFreePool ZwQuerySystemInformation

接下来,shellcode使用了ZwQuerySystemInformation来枚举所有的内核模块,来查找kdcom.dll(反调试?) 以及特定的srv.sys模块:

当shellcode找到srv.sys之后,将遍历该模块的PE区段,试图查找某些数据。

该shellcode是与SMB漏洞利用相结合的。以我的观点来看,现在它试图找到exploit发送的数据的其他部分(或许是一个要加载的PE文件)

总结

我没有接着调试下去,因为这只是测试脚本,并展示如何调试shellcode而不需要执行完整的exploit(有很多有关DoublePulsar的信息,例如:https://zerosum0x0.blogspot.com.es/2017/04/doublepulsar-initial-smb-backdoor-ring.html)

有时加载shellcode和调试比安装漏洞机器要快得多,包括执行漏洞,等等。。。

我希望这个脚本以及这篇文章将会对诸位的逆向工作有所帮助。

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功