在过去两个部分中,我们已经讨论了使用IDAPython让逆向工程更容易一些。这一部分我们来看一下条件断点。
当在IDA中调试时,分析者经常会遇到希望可以在一个特殊的地址中断下来的情况,但这只有在一些特定的情况能够触发。
一个典型的例子:只有在特殊的参数传递进去的时候,才能断到一个特殊的函数的调用处。另外一个实例:我希望我的分析虚拟机加载一个特定的链接库时能够产生中断。
今天,我们来看看如何用IDAPython来解决这个特殊的问题。
背景
在进行分析时经常会逆向DLL。在这类实例中,这些文件通常都是被其他可执行文件加载的。解决这个问题的一个方案是确保调试器在每个链接库加载时都能中断下来,然后DLL或者驱动最后加载完的时候才能停下来。
但事实上这种方法其实是很低效的。通常需要分析者人工停止和开始:继续执行,判断哪些最近使用的链接库和驱动是想要断下来的。
其实简单的运行一条命令,然后坐着等待感兴趣的文件被加载进来,会让分析工作变得更简单。
条件断点
在这个特殊的例子中,我们将会使用’dd.dll’文件。这个特殊的文件在运行时被提取之后会执行一个利用过程注入到其他进程中。为了在IDA中运行这个动态链接库,我将会加载可执行文件rundll32.exe将其关联到system32目录,这样能够将dd.dll文件作为参数传进来,就像下面这张图能够看到的。然后rundll32.exe可以让用户通过给定的导出名,加载一个特定的DLL。在这个特定的样本中,我对DLL的导出函数’Setting’比较感兴趣。我在IDA中填入了下面这些参数:
下一个步骤是,在DLL被可执行文件加载进来之后找到正确的地方设置断点。 为了达到这个目的,我先在调试器中勾选了‘Suspend on library load/unload’选项。当暂停在第一个DLL实例被加载进来之后,我们可以看到断点设在了NtMapViewOfSection函数调用的后一句。
接下来我在0x7C91ADFB 地址处下了一个断点。 在我的代码中,我调用add_bpt() 和enable_bpt() 函数来创建和生效断点。
''' ntdll.dll:7C91ADF1 push 0FFFFFFFFh ntdll.dll:7C91ADF3 push dword ptr [ebp-20h] ntdll.dll:7C91ADF6 call near ptr ntdll_NtMapViewOfSection ntdll.dll:7C91ADFB mov edi, eax ''' address = 0x7C91ADFB # Just after NtMapViewOfSection add_bpt(address, 0, BPT_SOFT) enable_bpt(address, True)
在这个步骤,我已经在0x7C91ADFB 处设置了断点,每次DLL加载进来的时候调试器都会中断下来。为了确保只有在’dd.dll’加载进来的时候才会中断,我们必须使用条件断点。分析者可以使用代码来判断条件断点是否真的被触发(不管用IDC或者Python都行)。如果代码返回值为True,断点会被触发,否则,断点将会被忽略。 我们首选Python作为编程语言。 然后我们将Python代码存储到变量中,然后将这个变量传递给SetBptCnd()函数,这个函数会将这个代码作为条件设置到断点上。 这个条件设置之后,我们让调试器继续运行,然后等待调试器暂停事件触发。伪代码如下所示:
address = 0x7C91ADFB # Just after NtMapViewOfSection RunPlugin("python", 3) # Python default programming StartDebugger("","",""); dll = "dd.dll" condition = """ for m in Modules(): if "%s".lower() in m.name.lower(): print "Breaking on", m.name.lower() del_bpt(%d) return True return False """ % (dll, address) add_bpt(address, 0, BPT_SOFT) enable_bpt(address, True) SetBptCnd(address, condition) continue_process() GetDebuggerEvent(WFNE_SUSP, -1)
现实中使用的条件代码如下所示:
for m in Modules(): if "dd.dll".lower() in m.name.lower(): print "Breaking on", m.name.lower() del_bpt(0x7C91ADFB) return True return False
这个代码会反复的查找’dd.dll’是否被加载进来。 如果是,会打印调试信息,最开始的断点被删除掉了,返回的布尔值为True时,断点就会被触发。我们运行程序,IDA窗口中输出如下信息:
下面这个步骤,我们会对导出函数’Setting’设置断点然后运行程序直到断点触发。为了自动完成这个步骤,我们可以使用下面的代码:
def get_names(base, size, desired_name): current_address = base while current_address <= base+size: current_address = NextHead(current_address) print hex(current_address) if desired_name in Name(current_address): return current_address for m in Modules(): if 'dd.dll' in m.name.lower(): base = m.base size = m.size analyze_area(base, base+size) setting = get_names(base, size, "Setting") if setting: add_bpt(setting, 0, BPT_SOFT) enable_bpt(setting, True) continue_process() GetDebuggerEvent(WFNE_SUSP, -1)
这段代码反复的在加载的模块中查找’dd.dll’。找到之后,分析包含DLL的代码。 然后在这段代码中寻找’Setting’函数。一旦找到,就在这个地址设置断点,让调试器继续执行,等待断点触发。执行程序,我们可以看到正如我们预期的那样断点在我们想要的地址上中断下来。
结论:
设置条件断点看起来是一个非常小的技术点, 但是能够节省分析者大量的时间。一个很小的代码片段能够代替我们完成一次次手动的寻找我们想要的断点这么繁琐的工作。我们又一次借助 IDAPython的力量来简化我们的逆向工作,帮助我们节省了大量的时间和精力。
*原文: Paloalto 东二门陈冠希/编译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)