环境:
windows2000 sp4 Rollup1
使用工具:
IDA v4.7
softice
这个漏洞出问题的代码是spoolsv.exe静态加载spoolss.dll,由spoolss.dll动态加载win32spl.dll中的函数:AddPrinterW
具体可以参见:
http://blog.0x557.org/Sandro/archives/0day_develop/index.html
(同时表示对作者 Sandro 的感谢)
首先反汇编了win32spl.dll:
.text:769F7C01 push 207h
.text:769F7C06 call DllAllocSplMem
.text:769F7C0B mov edi, eax
.text:769F7C0D cmp edi, ebx
.text:769F7C0F jz short _text_769F7C38
.text:769F7C11 push dword ptr [esi+4]
.text:769F7C14 push [ebp+arg_0]
.text:769F7C17 push offset aWsWs ; LPCWSTR
.text:769F7C1C push edi ; LPWSTR
.text:769F7C1D call ds:wsprintfW ;overflow
.text:769F7C23 add esp, 10h
.text:769F7C26 push edi
.text:769F7C27 call AllocSplStr ;这次分配先检查了字符串总长度,不可能再溢出。
.text:769F7C2C mov [ebp+var_278], eax
.text:769F7C32 push edi
.text:769F7C33 call DllFreeSplMem
实际调试过程中,一开始并没有能在内存中找到这段代码。
尝试远程使用添加打印机,仍然不能保证这段代码的出现(时有时无),因此判断是动态加载后有一个卸载的动作,条件不明。
这个漏洞的触发和039的方式一致,是一个RPC调用的结果。应该是只有2000可以直接利用的。
构造恶意数据包远程调用AddPrinter,经实验,这样可以保证win32spl.dll的加载。
溢出点是wsprintfW函数的调用。被格式化输出的第三个参数是severname,第四个参数是printername。其中printername作了长度检测,severname未作长度检测。可以用来构造超长数据。因为wsprintfW最多输出1024个长字符,所以能利用的空间最多有2048个字节。
参考网上的一些堆溢出资料上介绍的方法(Leven的《总结windows下堆溢出的三种利用方式》等……),作了多次实验,没有能触发任何异常。程序就像打了补丁一样健壮的运行着……
仔细分析了DllAllocSplMem所分配的块,发现其前后紧密地结合着一些已分配出去的忙块。后面覆盖的块大多是一些小块,存储着一些字符串,多为打印机的驱动程序名。多次实验,没有出现过别的情况。
感觉是这次分配后剩下的再分配和释放堆的过程中,都没有一个写任意四字节的机会。似乎跟lookaside这个结构相关比较大(参考《网络渗透技术》page293)。而在反汇编块分配所用到的ntdll中的RtlAllocateHeap、RtlFreeHeap时,发现了在关键流程上的两次比较:
cmp byte ptr [edi+586h], 2
cmp byte ptr [edi+586h], 1
此时,edi的值是当前堆的HEAP_ENTRY地址。而偏移0x586处的信息在各种资料上都没有找到,比如《网络渗透技术》page291就只有:
/*+0x580*/ PVOID LookAside;
/*+0x584*/ USHORT LookasideLockCount;
/*+0x588*/ HEAP_UNCOMMITED_RANGE UnCommittedRanges[8];
唯独漏掉了这个两字节的信息。不知是否有人知道含义……
在能找到的资料中,关于lookaside的介绍不多,能获得的信息是:大于等8小于1024字节的堆块再分配和释放的时候首先会从lookaside里操作。顺便提一下,《网络渗透技术》p293页,说“总共是0x1800字节加上一个8字节的头部结构。”似乎描述得不是很准确。8字节的头部结构,很模糊的概念。
结合调试过程对释放堆块时对lookaside进行的操作进行一下分析。大家看看这个溢出还有没有利用的可能。
分析中提到的实际值都是在这次调试过程中的内存信息。经多次实验一致。
先分析了函数RtlFreeHeap。用IDA反汇编了版本号5.00.2195.7006的ntdll.dll。
在函数入口处,每个堆块释放时都要做的比较:
PAGE:77FCAD44 cmp byte ptr [edi+586h], 2
edi的值是当前堆的HEAP_ENTRY地址。[edi+586h]实际值为1。
(如果是2,检查堆块头部的SegmentIndex,如果为0xFF会有一些操作。这里不是很清楚,因为没见过这个情况,没继续分析。不知道是不是跟调试堆有关)
然后是对堆块的释放符号和堆的ForceFlags作比较。
PAGE:77FCB737 mov eax, [ebp+arg_4]
PAGE:77FCB73A or eax, [edi+10h]
PAGE:77FCB73D mov [ebp+arg_4], eax
PAGE:77FCB740 test eax, 7D030F60h ;实际这里eax == 0;具体意义不是很清楚。
接着检查欲释放块的地址的合法性,忙位是否置1,SegmentIndex是否小于40h,不满足则抛出异常。(?)
检测块标志是否置上HEAP_ENTY_SETTABLE_FLAG1-3,是则直接转入合并算法或是内存释放。
否则进行对[edi+586h]的第二次比较:
PAGE:77FCB781 cmp byte ptr [edi+586h], 1
PAGE:77FCB788 jnz short PAGE_77FCB792
PAGE:77FCB78A mov ecx, [edi+580h]
PAGE:77FCB790 jmp short PAGE_77FCB794
当满足条件:
[edi+586h] == 1
LookAside != NULL
LookasudeLockCount == 0
HEAP_ENTRY_VIRTUAL_ALLOC 标志未置
块大小 < 1024字节(传说中的小堆块)
就会将欲释放的堆块加入到lookaside对应位置的链表上去。
否则转入合并算法或是释放内存。
首先利用以下代码算出堆块大小对应的HEAP_LOOKASIDE在堆中的地址:
PAGE:77FCB7B4 lea eax, [eax+eax*2]
PAGE:77FCB7B7 shl eax, 4 ; 相当于*48,HEAP_LOOKASIDE大小为48字节
PAGE:77FCB7BA add eax, ecx ; ecx = LookAside,算出对应的HEAP_LOOKASIDE地址
PAGE:77FCB7BC push eax
PAGE:77FCB7BD call _text_77FB2C40
这个函数调用对HEAP_LOOKASIDE中的一些数据作一些检测和更改,最终由:
.text:77FBB202 mov ebp, ecx ; ecx指向lookaside对应此堆大小的头部的HEAP_LOOKASIDE
.text:77FBB204 mov ebx, edx ; edx->ebx要释放的块的数据部分指针
.text:77FBB206 mov edx, [ebp+4]
.text:77FBB209 mov eax, [ebp+0]
.text:77FBB20C mov [ebx], eax
.text:77FBB20E mov ecx, edx
.text:77FBB210 add ecx, 10001h ; 给Depth, Sequence都加1。
.text:77FBB216 lock cmpxchg8b qword ptr [ebp+0] ;头插法加入链表。汗,这个指令以前都没见过……
.text:77FBB21B jnz short _text_77FBB20C
在调试过程中,检测到的堆块释放,都是在lookaside上进行的操作。而且被溢出的块更是从lookaside中直接取出,而lookaside位置靠前无法覆盖,溢出后能覆盖的堆块都是存储文件名字符串的小堆块(似乎不会释放),没有空闲块。似乎也就丧失了写任意4字节的机会。望高手指点。
在后来的调试过程中,发现另一个远程调用触发了一个读异常。这个与堆块的释放分配没有关系,看来dos还是可以做的:〉不过,要想进一步利用还要再好好分析一下。有兴趣的来讨论一下。
windows2000 sp4 Rollup1
使用工具:
IDA v4.7
softice
这个漏洞出问题的代码是spoolsv.exe静态加载spoolss.dll,由spoolss.dll动态加载win32spl.dll中的函数:AddPrinterW
具体可以参见:
http://blog.0x557.org/Sandro/archives/0day_develop/index.html
(同时表示对作者 Sandro 的感谢)
首先反汇编了win32spl.dll:
.text:769F7C01 push 207h
.text:769F7C06 call DllAllocSplMem
.text:769F7C0B mov edi, eax
.text:769F7C0D cmp edi, ebx
.text:769F7C0F jz short _text_769F7C38
.text:769F7C11 push dword ptr [esi+4]
.text:769F7C14 push [ebp+arg_0]
.text:769F7C17 push offset aWsWs ; LPCWSTR
.text:769F7C1C push edi ; LPWSTR
.text:769F7C1D call ds:wsprintfW ;overflow
.text:769F7C23 add esp, 10h
.text:769F7C26 push edi
.text:769F7C27 call AllocSplStr ;这次分配先检查了字符串总长度,不可能再溢出。
.text:769F7C2C mov [ebp+var_278], eax
.text:769F7C32 push edi
.text:769F7C33 call DllFreeSplMem
实际调试过程中,一开始并没有能在内存中找到这段代码。
尝试远程使用添加打印机,仍然不能保证这段代码的出现(时有时无),因此判断是动态加载后有一个卸载的动作,条件不明。
这个漏洞的触发和039的方式一致,是一个RPC调用的结果。应该是只有2000可以直接利用的。
构造恶意数据包远程调用AddPrinter,经实验,这样可以保证win32spl.dll的加载。
溢出点是wsprintfW函数的调用。被格式化输出的第三个参数是severname,第四个参数是printername。其中printername作了长度检测,severname未作长度检测。可以用来构造超长数据。因为wsprintfW最多输出1024个长字符,所以能利用的空间最多有2048个字节。
参考网上的一些堆溢出资料上介绍的方法(Leven的《总结windows下堆溢出的三种利用方式》等……),作了多次实验,没有能触发任何异常。程序就像打了补丁一样健壮的运行着……
仔细分析了DllAllocSplMem所分配的块,发现其前后紧密地结合着一些已分配出去的忙块。后面覆盖的块大多是一些小块,存储着一些字符串,多为打印机的驱动程序名。多次实验,没有出现过别的情况。
感觉是这次分配后剩下的再分配和释放堆的过程中,都没有一个写任意四字节的机会。似乎跟lookaside这个结构相关比较大(参考《网络渗透技术》page293)。而在反汇编块分配所用到的ntdll中的RtlAllocateHeap、RtlFreeHeap时,发现了在关键流程上的两次比较:
cmp byte ptr [edi+586h], 2
cmp byte ptr [edi+586h], 1
此时,edi的值是当前堆的HEAP_ENTRY地址。而偏移0x586处的信息在各种资料上都没有找到,比如《网络渗透技术》page291就只有:
/*+0x580*/ PVOID LookAside;
/*+0x584*/ USHORT LookasideLockCount;
/*+0x588*/ HEAP_UNCOMMITED_RANGE UnCommittedRanges[8];
唯独漏掉了这个两字节的信息。不知是否有人知道含义……
在能找到的资料中,关于lookaside的介绍不多,能获得的信息是:大于等8小于1024字节的堆块再分配和释放的时候首先会从lookaside里操作。顺便提一下,《网络渗透技术》p293页,说“总共是0x1800字节加上一个8字节的头部结构。”似乎描述得不是很准确。8字节的头部结构,很模糊的概念。
结合调试过程对释放堆块时对lookaside进行的操作进行一下分析。大家看看这个溢出还有没有利用的可能。
分析中提到的实际值都是在这次调试过程中的内存信息。经多次实验一致。
先分析了函数RtlFreeHeap。用IDA反汇编了版本号5.00.2195.7006的ntdll.dll。
在函数入口处,每个堆块释放时都要做的比较:
PAGE:77FCAD44 cmp byte ptr [edi+586h], 2
edi的值是当前堆的HEAP_ENTRY地址。[edi+586h]实际值为1。
(如果是2,检查堆块头部的SegmentIndex,如果为0xFF会有一些操作。这里不是很清楚,因为没见过这个情况,没继续分析。不知道是不是跟调试堆有关)
然后是对堆块的释放符号和堆的ForceFlags作比较。
PAGE:77FCB737 mov eax, [ebp+arg_4]
PAGE:77FCB73A or eax, [edi+10h]
PAGE:77FCB73D mov [ebp+arg_4], eax
PAGE:77FCB740 test eax, 7D030F60h ;实际这里eax == 0;具体意义不是很清楚。
接着检查欲释放块的地址的合法性,忙位是否置1,SegmentIndex是否小于40h,不满足则抛出异常。(?)
检测块标志是否置上HEAP_ENTY_SETTABLE_FLAG1-3,是则直接转入合并算法或是内存释放。
否则进行对[edi+586h]的第二次比较:
PAGE:77FCB781 cmp byte ptr [edi+586h], 1
PAGE:77FCB788 jnz short PAGE_77FCB792
PAGE:77FCB78A mov ecx, [edi+580h]
PAGE:77FCB790 jmp short PAGE_77FCB794
当满足条件:
[edi+586h] == 1
LookAside != NULL
LookasudeLockCount == 0
HEAP_ENTRY_VIRTUAL_ALLOC 标志未置
块大小 < 1024字节(传说中的小堆块)
就会将欲释放的堆块加入到lookaside对应位置的链表上去。
否则转入合并算法或是释放内存。
首先利用以下代码算出堆块大小对应的HEAP_LOOKASIDE在堆中的地址:
PAGE:77FCB7B4 lea eax, [eax+eax*2]
PAGE:77FCB7B7 shl eax, 4 ; 相当于*48,HEAP_LOOKASIDE大小为48字节
PAGE:77FCB7BA add eax, ecx ; ecx = LookAside,算出对应的HEAP_LOOKASIDE地址
PAGE:77FCB7BC push eax
PAGE:77FCB7BD call _text_77FB2C40
这个函数调用对HEAP_LOOKASIDE中的一些数据作一些检测和更改,最终由:
.text:77FBB202 mov ebp, ecx ; ecx指向lookaside对应此堆大小的头部的HEAP_LOOKASIDE
.text:77FBB204 mov ebx, edx ; edx->ebx要释放的块的数据部分指针
.text:77FBB206 mov edx, [ebp+4]
.text:77FBB209 mov eax, [ebp+0]
.text:77FBB20C mov [ebx], eax
.text:77FBB20E mov ecx, edx
.text:77FBB210 add ecx, 10001h ; 给Depth, Sequence都加1。
.text:77FBB216 lock cmpxchg8b qword ptr [ebp+0] ;头插法加入链表。汗,这个指令以前都没见过……
.text:77FBB21B jnz short _text_77FBB20C
在调试过程中,检测到的堆块释放,都是在lookaside上进行的操作。而且被溢出的块更是从lookaside中直接取出,而lookaside位置靠前无法覆盖,溢出后能覆盖的堆块都是存储文件名字符串的小堆块(似乎不会释放),没有空闲块。似乎也就丧失了写任意4字节的机会。望高手指点。
在后来的调试过程中,发现另一个远程调用触发了一个读异常。这个与堆块的释放分配没有关系,看来dos还是可以做的:〉不过,要想进一步利用还要再好好分析一下。有兴趣的来讨论一下。