测试环境:
X | 推荐环境 | 备注 |
---|---|---|
操作系统 | win_xp_sp3 | 简体中文版 |
虚拟机 | vmware | 15.5 |
调试器 | Windbg | Windbg_x86 |
反汇编器 | IDA pro | 版本号:7.0 |
漏洞软件 | Windows Media Player | 版本号: 9.00.00.4503 |
堆调试还是首先配置环境,这里符号表我是直接下载好离线的,然后设置路径就行了
具体可以自行查找离线符号表,对应版本号为xp_sp3
这里偶然看到大佬发的cdn,跟设置普通符号表路径一样,自行查看设置,我没测试过
http://sym.ax2401.com:9999/symbols/
这里还有一份ftp下载的
https://ftp.acc.umu.se/mirror/archive/ftp.sunet.se/pub/security/vendor/microsoft/winxp/Service_Packs/
自行尝试,我已经下载了一份解决了
至于poc什么的,可以从漏洞战争随书资料获取
地址 | 命令 |
---|---|
73b7cbee | call iccvid!CVDecompress (73b721ae) |
73b722cc | rep movs dword ptr es:[edi],dword ptr [esi] |
首先,打开Windows Media Player, 附加后,开启页堆
!gflag +hpa
然后打开poc文件,最终在这里断下
通过栈回溯
kb
获取到上一层,通过ub命令找到上一层调用,
这里是7个参数
同样记录下来,这时候通过
sxe ld:iccvid
下断,在加载这个模块的时候断下,因为是动态加载的,没办法直接对其下断,在加载模块下断后,才能下指定地址
再次g运行,然后跟进去,这里我不会根据漏洞战争这本书进行复现,因为一开头就给繁杂的复现过程不是最好的方法,逆向逆向,我觉得逆着来分析才更可靠一些,
首先这是堆溢出,合理的猜测,poc应该是有大量数据复制的时候才会造成堆溢出, 也就是类似于
rep movs dword ptr es:[edi],dword ptr [esi]
我单步不知道单步了多久发觉了最可疑的地方,
前面的都是那种粗略的测试, 这里你看关键数据,0x2000,以及ecx为0x800,还有rep 每次复制的是dword 0x800*4个字节=3200个字节=0x2000个字节,也就是说每次复制0x2000个数据,
此时看到 edi的堆用户数据大小为0x6000,如果每次复制0x2000个数据,超过三次,势必会造成溢出,也就是说我们要找到,哪里是规定复制次数的,暂时记住这个地址
73b722cc
继续单步,他肯定会往回跳
每次循环都会跳到这里,然后往回跳,同时注意的是,往下走几步过户出现了比较,而这里edx为0x414141,不难想到,这里也是poc破坏的地方
追了几次过后,发觉大概都是在做同样的事,取结构,求长度,复制,
看到这里,add的时候,已经到6000了,接下来再次复制便会溢出了,
这是我认为的思路,接下来根据漏洞战争的思路在走一遍
这个过程照着漏洞战争的思路复现一遍,前面加调试部分不再重复,直接到进入call,进行调试具体细节看漏洞战争,只点出我认为比较重要的点
比较CVID数据长度是否小于0x20,
接下来一堆赋值便是取结构内容了,
截取自漏洞战争
看这里取法,若要取cvid数据长度,便是要取int的后三个字节,也就是类似这种取法,
73b721f0 8a6601 mov ah,byte ptr [esi+1]
73b721f3 0fb64e03 movzx ecx,byte ptr [esi+3]
73b721f7 8a4602 mov al,byte ptr [esi+2]
73b721fa c1e008 shl eax,8
73b721fd 0bc1 or eax,ecx
假设esi指向内容为为 0xffbbccee 则 ah=0xcc , al = 0xbb, eax=0xccbb ecx=0xff , shl 就是变成0xccbb00,在 | ecx就是刚好是0xccbbff
千万不要被图迷惑了,图是这样排布的
而内存中不是,内存中为CVID数据长度, FLAG,也就是最后一位才是FLAG
接下来看到ULongSub函数
猜测是减法,还是查询下,在百度无果后,谷歌一下就找到了
微软文档 就是一个减法, 这里ULongSub就是第一个参数 CVID数据长度减去0xA,也就是这里是判断CVID数据长度是否大于0xA
接下来看到他是比较编码条长度是否大于0x10,从原来的结构图可以推断出来
看下esi数据吧,这样好看一些
也就是说
FLAG为0x00
CVID数据长度为0x000068
编码帧宽度为0x0160
编码帧高度为0x0120
编码条数量为0x0010
这里都要从高位开始读起,开头读取错了..
然后0xA过后便是Strip Header了
截取漏洞战争
然后接下来是
这里说实话我不知道这里是干什么的,看书知道是未解压缩的数据与0x16比较,而我也没有找到证据证明,暂时先过,只知道跟0x5e跟0x16比较
然后
这里毋庸置疑又是取结构数据,而且又是取低三个字节,这里取的是0x10,按照文章所说数据结构,应该是不会这么取值的,这里又是取三个字节,google一番还是只有文中提到的那篇文章
我觉得这里结构应该为
编码条id 占1个字节, 编码条数据大小占3个字节,
保存后,再次比较比较编码条id是否大于0x10,我觉得猜测是正确的
这里判断编码条数量是否大于0xC,
这里就是取顶部加底部的Y坐标,一相减便是高度?
又是未知操作,然后跳转回原来的0x73b723af,然后经过一堆跳,跳来跳去,终于到了快复制数据的地方
这里已经增加底部复制数据了
比较未解压数据是否大于0x16
这里是比较id=0x11而不是0x1100?书里说是0x1100,这里为11是不跳转,也就是可以复制数据
终于追到了复制部分
这里看到UserSize也是0x6000,复制多几次的话就会Bomb
而可以知道,
判断 CVID数据长度>= 0x20 同时编码条id为0x11的大于3条就会造成堆溢出,未解压数据超过
首先利用断在指定模块处的命令断下后,
利用
lmv m iccvid
获取dll位置以及符号位置
我这里拿到后,将两个文件放在同一目录下,打开ida进行查看
跳转到指定地址73b721ae,这里已经将pdb导入了,很明显漏洞点在这里
但是,打开这个源码并不会让我减轻多少压力,幸亏前面动手调试了,大概个流程还是知道的
首先
然后
这里贴出完整注释代码
signed int __stdcall CVDecompress(unsigned int a1, _BYTE *a2, unsigned int a3, int a4, int a5, int a6, int a7) { unsigned int v7; // ebx _BYTE *v8; // esi int v9; // ST18_4 signed int result; // eax _BYTE *v11; // esi int v12; // eax unsigned __int16 v13; // ax _BYTE *v14; // esi unsigned __int16 v15; // cx unsigned __int16 v16; // ax unsigned __int16 v17; // cx int v18; // eax int v19; // edi unsigned __int8 *v20; // ecx unsigned __int16 v21; // dx unsigned int v22; // edx signed int v23; // eax unsigned int v24; // [esp+Ch] [ebp-20h] int v25; // [esp+10h] [ebp-1Ch] _BYTE *v26; // [esp+14h] [ebp-18h] int v27; // [esp+14h] [ebp-18h] int v28; // [esp+18h] [ebp-14h] unsigned int v29; // [esp+1Ch] [ebp-10h] _BYTE *v30; // [esp+20h] [ebp-Ch] unsigned int v31; // [esp+24h] [ebp-8h] int v32; // [esp+28h] [ebp-4h] v7 = a1; v8 = *(_BYTE **)(a1 + 36); if ( v8 ) { v9 = a7; *(_DWORD *)(a1 + 36) = 0; CVDecompress(v7, v8, 0x2446u, 0, 0, 0, v9); LocalFree(v8); } result = 0; if ( a3 >= 0x20 ) // 传入参数为0x68,这里说是CVID数据大小,下面便是获取CVID数据大小并判断 { v11 = a2; BYTE1(result) = a2[1]; LOBYTE(result) = a2[2]; v12 = (unsigned __int8)a2[3] | (result << 8); if ( (signed int)a3 < v12 || (HIBYTE(a3) = *a2, ULongSub(v12, 0xAu, &v29) < 0) )// 判断CVID数据大小是否大于0xA { LABEL_33: result = 0; } else { HIBYTE(v13) = v11[8]; v14 = v11 + 10; v28 = 0; v26 = v14; v30 = v14; LOBYTE(v13) = *(v14 - 1); v25 = v13; if ( (signed int)v13 > 0 ) { v32 = 0; do { if ( v29 < 0x16 ) // 比较未解压数据是否大于0x16 break; HIBYTE(v15) = v14[1]; LOBYTE(v15) = v14[2]; v31 = (unsigned __int8)v14[3] | (v15 << 8);// 取数据结构 if ( v29 < v31 ) break; if ( *v14 == 0x10 || *v14 == 0x11 ) // 比较编码条id { if ( ULongSub(v31, 0xCu, &a1) < 0 ) // 判断是否大于0xC goto LABEL_33; HIBYTE(v16) = v14[8]; // 这部分取结构求宽度 HIBYTE(v17) = v14[4]; LOBYTE(v16) = v14[9]; LOBYTE(v17) = v14[5]; v18 = v16 - v17; LOWORD(v18) = *(_WORD *)(v7 + 46) * v18; a2 = (_BYTE *)v18; if ( v32 && !HIBYTE(a3) && *v14 == 0x11 )// 只有当编码条id为0x11的时候才会进行复制数据 { qmemcpy( (void *)(*(_DWORD *)(v7 + 28) + v32), (const void *)(*(_DWORD *)(v7 + 28) + v32 - 0x2000), 0x2000u); v14 = v26; } v19 = (int)(v30 + 12); v20 = v14 + 12; *(_DWORD *)(v7 + 56) = v32 + *(_DWORD *)(v7 + 32); v27 = (int)(v14 + 12); *(_DWORD *)(v7 + 60) = a7; while ( a1 >= 4 ) { HIBYTE(v21) = v20[1]; LOBYTE(v21) = v20[2]; v22 = v20[3] | (v21 << 8); v24 = v22; if ( a1 < v22 ) break; switch ( *v20 ) { case 0x20u: case 0x21u: case 0x24u: case 0x25u: (*(void (__stdcall **)(int, _DWORD, _DWORD, _DWORD))v7)( v19, *(_DWORD *)(v7 + 56), *(_DWORD *)(v7 + 52), *(_DWORD *)(v7 + 48)); break; case 0x22u: case 0x23u: case 0x26u: case 0x27u: (*(void (__stdcall **)(int, int, _DWORD, _DWORD))(v7 + 4))( v19, *(_DWORD *)(v7 + 56) + 4096, *(_DWORD *)(v7 + 52), *(_DWORD *)(v7 + 48)); break; case 0x30u: (*(void (__stdcall **)(unsigned int, int, unsigned int, int, int, int, _BYTE *))(v7 + 8))( v7, v19 + 4, v22 - 4, a4, a5, a6, a2); break; case 0x31u: (*(void (__stdcall **)(unsigned int, int, unsigned int, int, int, int, _BYTE *))(v7 + 16))( v7, v19 + 4, v22 - 4, a4, a5, a6, a2); break; case 0x32u: (*(void (__stdcall **)(unsigned int, int, unsigned int, int, int, int, _BYTE *))(v7 + 12))( v7, v19 + 4, v22 - 4, a4, a5, a6, a2); break; default: break; } v20 = (unsigned __int8 *)(v24 + v27); v23 = 1; v19 += v24; v27 += v24; if ( v24 > 1 ) v23 = v24; a1 -= v23; } a6 += a7 * (signed __int16)a2; ++v28; v32 += 0x2000; } v30 += v31; v29 -= v31; v14 += v31; v26 = v14; } while ( v28 < v25 ); } result = 1; } } return result; }
ms10-055
什么鬼...,居然不支持了,这里根据分析过程猜测
引用漏洞战争图
暂时没看到相关漏洞利用\
windbg此次用的知识与往常无差别,不进行总结了,而分析过程还是总结一下
<<漏洞战争>>