IDA教程-隐藏IDA调试器

IDA教程-隐藏IDA调试器
IDA教程-隐藏IDA调试器

IDA教程-隐藏IDA调试器

译者注:①无论从易用性还是功能上,文中的方法和插件都较OD下面的插件如HideOD,OllyAdvance等还要差些。但总比没有强,聊胜于无,可以对付一些简单的反调试。

②原文为IDA作者于2005年发表在https://www.360docs.net/doc/2613694366.html,上的三篇blog,我给合到一篇里了。③文中的Unpacker我没有翻译为解压器,翻译为壳。

一个隐藏IDA调试器的小技巧

很多IDA使用者都向我索要一个可以隐藏调试器的插件或功能。事实上,有很多反调试的手段,而针对每一种反调试的手段都需要对应一种处理方法。我们先从一些简单的开始,我们的目的是让IsDebuggerPresent函数始终返回0值。

当一个调试器处于运行状态时,我们可以在反汇编窗口使用”go to the specified address”命令进入IsDebuggerPresent函数。不幸的是,当前版本的IDA(译者注:作者文章写于2005年)不能在名称列表中显示输入函数名字,因此我们需要在输入框中手动输入函数名字:

请注意我是如何来构造这个地址的:在Dll名字后面有一个下划线,之后再加上函数名。我们在这个函数的结尾放置一个断点。这样我们就可以拦截它并修改它的返回值:

我们并不希望每次这个IsDebuggerPresent函数被调用时挂起程序,然后每次手动修改这个数值,这很麻烦,我们希望它能自动完成这个操作。方法是:我可以使用条件断点,“断点条件”通常用来确定一个断点应该被触发或抛弃,其语法为IDC表达式,如果表达式等

效为0,断点就不会触发。IDA需要执行这个表达式,以判断其结果,因此我们可以利用它的执行结果,修改寄存器或内存为任何你需要的数值。我们编辑断点的属性如下(右击鼠标,编辑断点):

我们设定条件为”EAX=0”。注意这里不是一个比较,而是一个赋值。当IDA执行它的时候,EAX会被附带设置为0,这正是我们需要的。因为我们不想挂起程序,我们也清除了这个断点的属性。以这种方式断点,就可以让我们的调试器对付IsDebuggerPresent的调用了。这听起来很简单,你或许会问“如果壳作者使用的不是这种幼稚的反调试技巧该如何应付呢?”,请不要着急,我们以后会继续这个话题。

一个隐藏调试器的插件

上次我提到了一个使用条件断点的小技巧,今天我们会给大家提供一个可以自动处理这些断点的插件,以便可以使用IDA解压一些象“Zotob蠕虫”这样的恶意软件。

如果测试一个“活”的恶意软件很危险,因此我们将在演示中使用一个示例程序。Zotob 使用Yoda’s Protector的一个变种来加壳(可以通过这个连接来认识它)。我们将会使用这个示例程序,并使用Yoda’s Protector来保护它。首先我们尝试对它脱壳。

IDA给出了很多关于压缩了的可执行文件的提示并尝试装载它。它找到了这个压缩器的入口,但通常来说,当我们处理一个恶意软件的时候,推荐打开manual load选项,并关闭make imports section选项。Manual load选项可以将输入文件的所有区段都装载到数据库中(恶意软件通常将他们的代码隐藏在文件的任何地方)。取消Creating the imports seciton,则可以是IDA以原始方式显示整个输入表目录的内容,谁说恶意软件不能在输入表目录中隐藏呢?

接着我们遇到的问题是,当我们在调试器中试图跟踪壳时,会有非常多的异常。你必须非常小心的处理这些伪调用和异常。一个不留神我们就会发现已不知身在何处,程序跑飞,崩溃甚至关闭调试器。它是壳有意这样做的-壳作者喜欢将一些事情复杂化以使其“几乎”无法被分析。如果一个程序可以在不需要特殊的key或额外数据的情况下运行,那么我们可以让它运行在一个虚拟环境下,并完整的分析它。今天我们不会虚拟化整个环境,只是其中一小部分-我们将会处理Windows API以让壳无法使用他们。

好,回到我们的程序。壳在解压进程中使用SEH(结构化异常处理),IDA不停的报告着每一次异常。这可能是成百上千次。默认情况下,IDA和多数其它调试器有着类似的设置:一旦发生异常,它就会挂起程序。通常在这种设置适用于调试“正常”程序,但对我们跟踪恶意软件却没有任何帮助。

我们修改一下这些异常处理的设置,以让IDA不在每次异常时都停下来。你可以通过用户界面(Debugger,Debugger options,Edit exceptions)或通过编辑cfg/exceptions.cfg文件来进行设置。第二种方法更好些,因为设置将会应用到所有以后的调试中,而第一种方法,只针对当前的程序有效。我们要告诉IDA所有的异常都应交给应用程序来处理。下面是这个配置文件中的一行:

这行设置表示当该异常发生时,调试器不会停止,应用程序会处理这个异常。如果你有过一些IDA的使用经验,你会发现注意到,尽管有这些设置有时IDA仍会停止在一个异常处。如果这是一个“Second Changce”的异常,IDA将会停止在这个异常:一个未被处理的“Second Changce”异常将会中止应用程序,IDA会给你一个处理它的机会。

你可以使用这个文件替换你的配置文件,方法是打开Debugger,debugger options对话框中的Load按钮来装载这个配置文件。

使用这个新的配置文件,你可以很容易的单步跟踪它了。你甚至可以在4766a4处设置一个断点来查看它的一个小把戏-在4766a8处的自修改代码(就在下面的几条指令)。一旦开始执行在4766a5处的’STOSB’,你就可以在4766a6处按F8,接着代码就在屏幕上出现了。我不会细说每一个壳中的小把戏,你可以参看其它现在做的非常好的站点上的介绍(译者注:比如https://www.360docs.net/doc/2613694366.html,,hehe)。现在,让我们在476854处使用一个硬件断点,并返回到程序中。你会看到壳愉快而又卖力的工作着,并没有发现调试器正在使用SEH技巧。

为什么我们要用一个硬件断点,而不是软件断点呢?原因是因为应用程序可以很容易检测到软件断点。例如,在4775CE处有一个计算校验和的函数。如果你使用软件断点,这里就会得到一个错误的校验和,随之壳就会在其后的某处崩溃。通常说来,推荐使用硬件断点,但不幸的是,IDA并没有自动使用它们的选项。我手动在恶意软件中使用硬件断点以让它从开始运行到一个指定位置。错误是不可避免的,但硬件断点可以让我在整个调试过程中重复到达上一个已知地址中。好处是我可以在几天后,甚至重启机器仍能继续调试进程。

如果你在476854处放置一个硬件断点,你会看到下面代码:

我们面对的是一个间接调用。当前我们处于调试器中,因此我们可以很容易找到这个被调用函数的地址,但是显示出来的却很丑陋。让我们使用IDA命令来修改它,壳使用许多基于EBP寄存器的参考。很明显EBP寄存器并不变化。我们选择整个屏幕,使用“user-defined offset”命令:

偏移是EBP,它是个简单的数字(并没有保存一个地址)。因为我们选择了一个区域,因此它还有一个对话框需要设置:

我们让它转换在400000..500000范围内所有地址为偏移。结果比开始的要好多了:

我们看到壳用到LoadLibrary这个函数来访问Windows API函数。它会检索许多函数的地址并创建导入表。如果你让程序运行到476e77(你可以使用硬件断点),你将会在476451处看到导入表:

(默认情况下,是看不到这张表的;你需要将鼠标放置在起始位置,创建一个双字,再创建双字数组)。这张表还不是很好,因为它包括一些名字的参考,但这些条目却没有被命名。在导入表的每一条将会一个接着一个的被使用,但一个张没有名字的列表没有什么意义。下面这段脚本,将会修正这张表。使用F2进入脚本对话框:

下面是脚本运行结果:

查看这张表,我们会发现很多邪恶函数。著名的IsDebuggerPresent也在其中,还有一些如SuspendThread, TerminateProcess, BlockInput,看上去也不像是好东西。

下面是我们的对策:我们在每一个危险函数上设置条件断点如下:

(EIP=address_of_ret_instruction) && (EAX=return_value)

例如,在BlockInput中:

条件断点应是:

(EIP=0x7D9A059B) && (EAX=0x1)

断点会跳过函数的执行并返回了一个我们预设的结果。它不会挂起运行的程序。壳将没有机会阻止用户的输入,也无法探测调试器和终止它。即使它试图这样,也会失败。

如果每次在运行应用程序时都设置这些断点是很乏味的事情,因此我写了个插件。它非常简单,我提供了源代码。通过这个插件,可以很容易让应用程序运行且无法检测到调试器:只需激活插件,然后运行调试器即可。应用程序将会自加压,然后没有任何疑虑的运行。 

我猜想接下来的问题是:“如何找到壳解压完成,并切换到原始代码的那个时刻(译者注:说的是OEP)”。不幸的是,对这个问题没有一个简单的确定的答案(即使有,下一个壳的作者马上就会处理它)。这是一个有很多可能答案的问题,或许我们会在将来继续这个话题。

相关文件连接:

Sample 'hello world' program with the source code

Plugin source code, binary code for IDA 4.9 and new exceptions.cfg

终极隐藏方法

如果一个应用程序使用了插件中不支持的反调试技巧,之前描述的方法将不再有效。例如,如果应用程序直接检查PEB数据,而不是调用IsDebuggerPresent函数。

我将会演示一种“终极潜行方法”,它也可以用来对付一些未来才能出现反调试方法。我们将会解压一个应用程序并在一个干净的解压过的模式下创建一个数据库。这次,我们将要使用一个示例加壳工具,Telock,但这与加壳工具无关,这是我们的原始示例程序,和它的加壳后的文件。

应用程序试图检测调试器,调试器又试图隐藏它自己。现在我们换一种玩法:我们将会完全移除调试器,而不是隐藏它!应用程序拒绝在调试器下执行?好吧,我们就不用调试器运行它(译者注:是象我们的武侠小说里面说的,无招胜有招吗?呵呵)。毕竟我们关心的是它解压后的,而不是解压代码。因此我们需要当它自解压完成后挂起应用程序。如果我们让应用程序在没有任何修改的情况下运行,它不会挂起。我们将修改它让它自己挂起。

想法是:我们将会Patch一个Windows API函数来挂起我们的应用程序。一旦解压后的代码调用它,应用程序就会被挂起。在我们的示例程序中,我们知道它调用了CreateWindowExA函数。就象这样:

我们patch它来调用SuspendThread而不是创建一个窗口:

下一步是让应用程序自己运行。我们使用Debugger,Detach from Process。壳会开始工作,应用程序将会在创建任何窗体前挂起,因此在屏幕上什么都不会发生。但如果我们检查任务列表。我们将会看到我们的应用程序 (Debugger, Attach to process):

应用程序已解压,它已经准备好被分析了。打开程序段,找到应用程序名字(本例中是sample_telocked.exe)你将会看到一个干净的代码。

简言之,终极潜行方法包括下面几个步骤:

Patch system dlls - Detach – Attach

如果我们不知道那个WindowAPI函数在应用程序中使用呢?我么可以Patch任何我们希望的函数,我们不能Patch壳使用的函数,因为这会阻止壳完成它的解压工作。如果我们确实不知如何做,那么ExitProcess或类似的函数都可以Patch。应用程序将会在退出时刻被挂起,但我们有机会看到它的原始输入表,那里会给出API函数调用表。

我们也可以用其它方法Patch API函数,例如可以将SuspendThread调用替换为0xCC,或无效操作码,甚至是0,应用程序将会崩溃,这时,我们可以使用IDA附加(在附加之前,你要打开Debugger,debugger 选项,设置为just-in-time debugger)

风暴译719110750 2008-02-20

原文在https://www.360docs.net/doc/2613694366.html,

相关主题
相关文档
最新文档