原文链接:恶意样本分析精要及实践9-IDA使用(二)
反编译windows API 恶意软件通常使用windows API函数影响操作系统(例如文件系统、进程、内存以及网络配置等)。如第二章静态分析和动态分析部分,windows扩展主要依赖文件DLL动态连接库文件。可执行程序的引用和调用来自于大量DLL中的提供不同功能的API。为了调用这些dll文件,需要先将其加载到内存中,然后调用API函数。检查一个恶意样本的dll引用情况可以指导我们分析其功能和能力。下面的表格展示了部分常见的DLL以及其执行功能: 3.1 弄清楚Windows API 为了展示病毒程序如何使用windows API并且帮助你了解关于一个API更多的信息。以一个病毒样本为例。加载样本到IDA,在引用窗口展示出的相关windows API函数里,检查函数在windows引用情况。 无论什么时候,在遇到windows API 函数的时候,可以通过微软的开发者MSDN中搜索或者在谷歌中搜索, https://msdn.microsoft.com/。MSDN文档对于API函数进行了相关描述,如函数参数、参数类型、返回值等。这里取Creat or open file 作为举例。
通过文档可以知道这个函数的功能为创建和打开文件。第一个参数(lpfilename),用于记录文件名称。第二个参数(dwdesiredaccess),说明需要的权限如读或血的权限,第5个参数也是对文件创建和打开一个已经存在的文件。 - HANDLE CreateFileA(
- LPCSTR lpFileName,
- DWORD dwDesiredAccess,
- DWORD dwShareMode,
- LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- DWORD dwCreationDisposition,
- DWORD dwFlagsAndAttributes,
- HANDLE hTemplateFile
- );
复制代码与数据类型和参数不同,之前的函数样本包括注释,例如_in_和_out_,描述了函数使用的参数和返回的值。_in_表示输入参数,调用必须通过提供参数给函数才能执行函数。_in_opt表示可选的输入参数(可以为null)。_out_表示输出的参数;表示函数将会输出参数作为返回值。这个特性对于了解函数调用后是否从存储中读取任何数据到输出函数很有帮助。_inout_对象可以让我们分辨函数参数和函数的输出。
在交叉参考中我们可以看到API调用情况,通过查阅相关API手册,我们可以知道,相关API的输入和输出参数。以createfile为例,通过查看函数的相关的两个函数,起始地址如下:
双击第一个参数,调转到代码反汇编窗口对应位置。并且高亮显示。通过分散,IDA提供了一个叫做快速识别库的技术(FLIRT),包括图像匹配算法用于确定函数函数是库函数还是一个引用函数(从dll引入的函数)。在这个例子中IDA能够识别引入的分散的函数,并且将其命名为CreateFileA。IDA的分辨引用函数和库函数的能力非常有用,因为当你分析恶意样本的时候,不会去浪费时间分辨是引用的函数还是库函数。IDA还会为参数添加参数的名字作为注释,标记出Windows API函数调用的对应的参数的名称。 - .text:00401708 mov dword ptr [esp+18h], 0 ; hTemplateFile
- .text:00401710 mov dword ptr [esp+14h], 80h ; dwFlagsAndAttributes
- .text:00401718 mov dword ptr [esp+10h], 3 ; dwCreationDisposition
- .text:00401720 mov dword ptr [esp+0Ch], 0 ; lpSecurityAttributes
- .text:00401728 mov dword ptr [esp+8], 3 ; dwShareMode
- .text:00401730 mov dword ptr [esp+4], 80000000h ; dwDesiredAccess
- .text:00401738 mov dword ptr [esp], offset FileName ; lpFileName
- .text:0040173F call ds:CreateFileA
复制代码IDA的另一个特性是列出使用象征名标记Windows API,或C标准库函数。例如在80000000h可以通过右键值,选择使用标准象征内容参数,标记内容;这个操作将会出现一个窗口展示所有有关选择的值的象征名字。你需要选择一个适当的标志名称这里就是Generic_read。用相同的方式,你可以替换掉第五个参数内容3,为象征名称,OPEN_EXISTING; 在使用象征名替换了内容之后,反汇编窗口列被转化成下图所示内容。代码变得更加可读。在函数调用之后,句柄到文件(可以在EAX寄存器中找到)被返回。通过函数操作文件还可以通过其他API来实现,例如readfile()或者writefile(),也可以实现类似的效果: 3.1.1 ANSI和Unicode API函数 windows支持两个相似的API设置:一个是对于ANSI字符,另一个是Unicode字符。很多函数使用一个字符作为参数,在参数的名字后面包含A或者W。例如CreateFileA。换句话说,API名称的尾部,可以让你分辨通过函数的字符的种类(ANSI或Unicode)。以上面的CreateFileA为例,A表示函数使用一个ANSI字符作为输入。相应的CreateFileW则是表示函数使用一个Unicode字符作为输入。在恶意软件分析的过程中,当你看到一个函数名为CreateFileA或CreateFileW形式,可以删掉尾字母A或W,然后在MSDN中搜索函数文档。
3.1.2 执行API函数 你可能会遇到很多名字带有Ex后缀的函数,例如RegCreateKeyEx(扩展RegCreateKey的变体)。当Microsoft升级一个与旧函数矛盾的函数的时候,升级的函数命名在原函数名的基础上增加Ex。 3.2 32位和64位Windows API对比 让我们看一个32位恶意样本去了解恶意样本如何运用大量API函数去影响操作系统的,让我们尝试了解如何反汇编代码,去了解恶意程序的活动。在接下来的反汇编输出中,32位的恶意样本调用了RegOpenKeyEx API开启了一个句柄执行run注册表的值。当我们执行32位恶意样本的时候,所有regOpenKeyEx的API参数被压到栈上。
输出参数phkResult是一个变量的指针(输出的参数由**out**注释指出)在函数调用后,指向打开注册表值的句柄。这里可以注意到,phkResult的地址是从ecx寄存器复制过去的,这个地址是作为RegOpenKeyEx API的第5个参数录入的。 - lea ecx, [esp+7E8h+phkResult] ➊
- push ecx ➋ ; phkResult
- push 20006h ; samDesired
- push 0 ; ulOptions
- push offset aSoftwareMicros ;Software\Microsoft\Windows\CurrentVersion\Run
- push HKEY_CURRENT_USER ; hKey
- call ds:RegOpenKeyExW
复制代码在恶意软件通过调用RegOpenKeyEx打开run注册值后,返回的句柄(在phkResult变量存储)被移动到ecx寄存器中,并且作为RegSetValueExW的第一个参数传递。从MSDN关于这个API的文档中,可以发现使用RegSetValueEx API设置一个变量到run注册表的值中(持久化)。变量通过的第二个参数设置,system字符。对应的内容可以通过第五个参数的值去确认。从前面的描述中,可以确定eax保持由pszPath的地址的值。pszPath变量与在运行时的相关内容相关;因此通过查看代码,很难判断数据是病毒添加到注册表里的(你可以通过调试病毒样本确认)。但是在这点,通过静态分析(反汇编),你可以确定病毒添加了一个入口到注册表中作为持久化的方式: - mov ecx, [esp+7E8h+phkResult] ➌
- sub eax, edx
- sar eax, 1
- lea edx, ds:4[eax*4]
- push edx ; cbData
- lea eax, [esp+7ECh+pszPath] ➐
- push eax ➏ ; lpData
- push REG_SZ ; dwType
- push 0 ; Reserved
- push offset ValueName ; "System" ➎
- push ecx ➍ ; hKey
- call ds:RegSetValueExW
复制代码在添加了一个入口到注册表中之后,病毒通过在句柄获取值之前(存有phkResult变量)关闭句柄到注册表值,如下所示: - mov edx, [esp+7E8h+phkResult]
- push edx ; hKey
- call esi ; RegCloseKey
复制代码之前的例子展示了恶意样本如何使用多个windows API添加一个入口到注册表中,该注册遍能够在计算机重启的时候自动运行。你还可以看到,恶意样本如何获得一个对象的句柄,并分享句柄到其他API函数执行其他行为。
当你在看从64位病毒程序反汇编输出的函数的时候,可能会略显不同,这是由于参数通过64位架构。接下的一个64位样本调用CreateFile函数。在64位架构下,在寄存器中前4个参数被使用(rcx,rdx,r8和r9),并且剩余的参数被放置在寄存器中。在接下来的反汇编中,注意到第一个参数是如何通过rcx寄存器,第二个参数在edx寄存器中,第三个参数在r8,第四个在r9寄存器中。新增的参数被放置在栈中(注意这里没有push指令),这里使用mov指令。注意IDA如何识别参数柄添加注释到指令旁边的。函数的返回值(到文件的句柄)从rax寄存器中被移动到rsi寄存器中: - xor r9d, r9d ➍ ; lpSecurityAttributes
- lea rcx, [rsp+3B8h+FileName] ➊ ; lpFileName
- lea r8d, [r9+1] ➌ ; dwShareMode
- mov edx, 40000000h ➋ ; dwDesiredAccess
- mov [rsp+3B8h+dwFlagsAndAttributes], 80h ➏ ; dwFlagsAndAttributes
- mov [rsp+3B8h+dwCreationDisposition], 2 ➎ ; lpOverlapped
- call cs:CreateFileW
- mov rsi, rax ➐
复制代码 下面的反汇编为WriteFile API的,注意文件句柄在API调用之前被复制到rsi寄存器,现在通过writeFile函数第一个参数移动到rex寄存器。相同的方式,另一个参数被传入寄存器进入堆,如下所示:- and qword ptr [rsp+3B8h+dwCreationDisposition], 0
- lea r9,[rsp+3B8h+NumberOfBytesWritten] ; lpNumberOfBytesWritten
- lea rdx, [rsp+3B8h+Buffer] ; lpBuffer
- mov r8d, 146h ; nNumberOfBytesToWrite
- mov rcx, rsi ➑ ; hFile
- call cs:WriteFile From the preceding example,
复制代码从之前的案例可以看到,病毒程序创建一个文件和写入内容到文件,但是当你查找静态代码的时候,并不那么清楚的可以看出恶意软件创建了什么文件或者写入了什么内容到文件中。例如,想要知道软件创建的文件名,你需要检查ipFileName(传入CreateFile的一个参数)地址的内容;但ipFileName变量并非硬编码,并且只有当程序运行的时候才存在。 4. 使用IDA补丁二进制程序 当完成恶意程序分析,你想要修改二进制程序改变其内部工作原理或者逆向逻辑以便个人使用。你可以使用选择Edit/Patch program菜单。需要注意的是,当你使用这个菜单堆二进制进行修改的时候,你并不会直接对二进制文件本身进行修改;这个修改只会在IDA数据库中进行操作。如果需要应用修改到原始的二进制文件的话,你需要使用Apply patches to input file: 4.1 补丁程序字节 考虑到代码通过32位恶意软件dll执行(RDSS rootkit),通过检测可以确保其运行与spoolsv.exe下面。这里的检测会使用字符对比功能;如果自负对比失败,则代码跳转到函数结束,并且回到函数调用。特殊的,这个dll的恶意行为只发生在当其被spoolsv.exe调用的时候;除此之外,其都无返回。 - 10001BF2 push offset aSpoolsv_exe ; "spoolsv.exe"
- 10001BF7 push edi ; char *
- 10001BF8 call _stricmp ➊
- 10001BFD test eax, eax
- 10001BFF pop ecx
- 10001C00 pop ecx
- 10001C01 jnz loc_10001CF9
- [REMOVED]
- 10001CF9 loc_10001CF9: ➋ ; CODE XREF: DllEntryPoint+10j
- 10001CF9 xor eax, eax
- 10001CFB pop edi
- 10001CFC pop esi
- 10001CFD pop ebx
- 10001CFE leave
- 10001CFF retn 0Ch
- K A, Monnappa. Learning Malware Analysis: Explore the concepts, tools, and techniques to analyze and investigate Windows malware (p. 189). Packt Publishing. Kindle 版本.
复制代码假定你想要恶意dll执行恶意行为在任一程序下,例如执行在notepad.exe下面。你可以改变硬编码的字符从spoolsv.exe到notepad.exe。为了实现这个,通过点击aSpoolsv_exe定位硬编码地址,在下面的内容中展示:
现在,将鼠标放在变量名上(aSpoolsv_exe)。此时,hex视图窗口中将会同步展示地址信息。在hex-View-1标签展示的hex和ascii导出内存地址。补丁字节内容,选择Edit/patch program/change byte;将会如下图所示带来补丁字节日志。你可以修改原始的二进制字节通过输入一个新的二进制值到栏目中。Address字段表示游标位置的虚拟地址,File offset字段指定二进制文件中字节所在的文件偏移量。
Original value字段显示当前地址的原始字节;即使你修改了这些值,该字段中的值也不会改变:
您所做的修改将应用于IDA数据库;要将更改应用到原始可执行文件,可以选择“Edit | Patch program | apply patches to the input file”。下面的屏幕截图显示了“应用补丁到输入文件”对话框。当您点击OK时,更改将应用到原始文件;您可以通过检查“创建备份”选项来保存原始文件的备份;在这种情况下,它会以.bak扩展名保存你的原始文件: 前面的示例演示了修补字节;以同样的方式,您可以通过选择Edit | patch program | Change word来一次打一个单词(2字节)的补丁。您还可以从十六进制视图窗口中修改字节,通过右键单击一个字节并选择Edit (F2),您可以通过再次右键单击并选择apply changes (F2)应用更改。 4.2 补丁命令 在之前的例子中,TDSS rootkit DLL执行了一个检查判断程序是否在spoolsv.exe下面运行。可以通过修改程序中的二进制信息将spoolsv.exe改为notepad.exe。可以通过逆向逻辑判断DLL可以运行在任意进程下面。为了实现这个想法,我们可以修改jnz命令使其变为jz,通过选择Edit|patch program|Assemble,如下所示。我们将要逆向逻辑并且让程序运行在spoolsv.exe下时,程序不会表现任何恶意行为表现,而运行在非spoolsv.exe时将会表现出恶意行为。在修改了命令之后,点击OK,命令将会被汇编,但是对话仍然保持打开状态,提示你在下一个地址汇编下一个命令。如果没有其他需要会变的可以点击取消结束。为了将修改保存到原始文件中,选择Edit|patch program|apply patches 将修改保存到文件中。 当你给任何命令打补丁的时候,小心需要确保所有的的命令的结合是正确的;除此之外,补丁的程序可能会出现无法预料的行为。如果新的命令比原始命令短的话,你可以使用nop命令保持长度完整。如果你在汇编一个新的命令超出原始的命令,IDA将会覆盖原始程序的后面的命令,这个行为可能并非我们希望如此的。
|