你的分享就是我们的动力 ---﹥

猎豹移动反病毒工程师(二)

时间:2015-01-11 21:15来源:www.chengxuyuans.com 点击:
 这次拿三个问题来讨论,是关于调试器的。因为对于反病毒工程师而言,类似于OllyDbg和IDA的使用方法是必须掌握的,但是在面试中又不太方便考察,所以只能对其快捷键或者调试器实现原理之类的问题进行提问。这里我还是选取比较权威的书籍上的答案,免得仅仅提供我自己的答案会误导大家,也难有说服力。如果说大家以后在面试的时候遇到这些问题,只要回答出关键点就可以,无需一字不落地背下来。因为我摘录的答案的篇幅往往比较长,为的就是让大家更为全面地看待这个问题。

        问题4——6如下:


4、在IDA中,快捷键X的作用是什么?什么是交叉引用?

         答:(以下内容选自《加密与解密 第三版》第3.2.5节)
         通过交叉参考(XREF)可以知道指令代码相互调用关系。如下面代码所示,这句“CODE XREF:sub_401120+B↑j”,表示该调用地址是401120,“j”表示跳转(jump)。其他一些符号:“o”表示偏移值(offset),“p”表示子程序(procedure)。双击这里或按回车键可以跳到调用该处的地方。

[plain] view plaincopy

    .text:00401165                    loc_401165:                ;CODE XREF:sub_401120+B↑j  
    .text:00401065                        push 0                 ;nExitCode  
    .text:00401067 FF 15 B0 40 40 00      call ds:PostQuitMessage</span>  

        在loc_401165字符上按X键,将打开交叉参考窗口。

        (关于交叉引用更为详细的说明,可参考《IDA Pro权威指南 第2版》第9章——交叉引用与绘图功能)

 

5、请解释一下什么是DR寄存器。

        答:(以下内容选自《IDA Pro代码破解揭秘》第6.2节)

        要真正理解调试,了解各种中断及调试寄存器是很有必要的,特别是有关进程状态变化的。Intel Software Developers Manual在这方面依然是最好的参考资料,我知道的与它相比,不过是小巫见大巫。不过,IA-32平台处理调试基本上是通过二者(中断或调试寄存器)之一实现的。

        首先介绍一下调试寄存器。IA-32平台有8个调试寄存器,最多可以监视4个地址。访问这些寄存器是通过mov指令(调试寄存器暗中充当目的操作数或源操作数)的变体实现的。要知道,访问这些寄存器是需要有RING0特权的,这当然是一个限制,但考虑其影响极大,这样做也是有道理的。对每一个断点,有必要具体指定涉及的地址、位置的长度(范围在一个字节与双字之间)、处理程序(当生成一个调试异常时)以及这个断点是否被启用。前4个调试寄存器,DR0到DR3可以包含4个32位地址(定义了断点应该出现的地址)。接下来的2个调试寄存器DR4和DR5取决于操作模式可以交替使用。当DE(Debugging Extension,调试扩展)标志被设在CR4(Control Register4,控制寄存器4)里时,DR4和DR5保留,试图引用它们时,将引发一个无效操作码异常。如果这个标志没有被设置,DR4和DR5则作为别名替代调试寄存器6和7。

        DR6(DebugRegister 6,调试寄存器6),作为调试状态寄存器指示对最后发生的调试异常进行条件检查的结果。用位模式访问DR6,位0到位3与前4个调试寄存器相关。这些位指示哪个断点条件被满足而生成了一个调试异常。DR6的位13在置位时表示下一条指令引用一个调试寄存器,连同DR7(我们随后就会介绍)的一部分被使用。位14对我们来说或许是最有诱惑力的,它被置位时指示处理器处于单步模式(single-step mode),我们稍后介绍。最后使用的是位15,它指示当调试陷阱标志被置位时,作为任何切换的结果都会抛出调试异常。最后,我们介绍调试寄存器7,也就是DR7。所有的黑客都对这个寄存器垂涎欲滴,它也被称为调试控制寄存器,像DR6一样被理解为位字段。这个寄存器的第一个字节对应断点是否是活动的,如果是活动的则对应它的作用域。位0、2、4和6确定调试寄存器是否被启用,位1、3、5和7对应同样的断点,但基于全局的作用域。在这个例子里,作用域被定义为在任务切换时断点是否保持,是否针对全局启用的断点对所有的任务都是可用的。根据Intel的手册,现在的处理器不支持位8和9。不过在以前,可以用它们确定引起断点事件的精确的指令。接下来是位13,这是一个比较有意思的位,它允许在访问调试寄存器本身之前中断。最后是位16到31,这些位确定什么类型的访问会引起断点,以及所在地址的数据长度。当CR4中的DE标志被置位时,位16到17、20到21、24到25、28到29被解释成下面的意思:

        00 – Break onexecution

        01 – Break onwrite

        10 – Break onI/O read or writes

        11 – Break onread and writes but not instruction fetches

        当DE标志没有置位时,除了10的值没有定义以外,其他的解释同上面一样。位18到19、22到23、26到27、30到31对应各种断点的长度,值00表示长度为1,值01表示2字节长度。在32位平台上10未被定义,在64位平台上它表示8字节长度。你可能已经猜到了,11表示长度为4字节。

        现在,把所有这些位与DR0至DR3联系起来似乎比较困难,但实际并不是这样的。每个2位组合对应DR0至DR3范围里特定的连续的寄存器。这些长度必须对齐某些边界(取决于它们的大小)——例如,16位需要以字为界,32位以双字为界。这由处理器通过掩码地址的低位相关地址来强制执行,因此,一个未对齐的地址将不会产生预期的结果。如果访问起始地址加上它的长度范围内的任意地址,将产生异常,通过使用2个断点有效地允许未对齐的断点;每个断点被适当地对齐,在它们两个之间包含了讨论的长度。这里还有一点值得一提。当断点访问类型只是执行时,应当把长度设为00,其它的值导致未定义的行为。

        注意:有趣的是,调试寄存器本身并没有受到很多关注。不过,私下里还是有很多利用它们的rootkits和后门。比如说,可以在指向进程结构的指针上设置一个全局访问断点,在进程的链表里隐藏进程。当访问这个地址时,将出现调试异常,并重定向到它们的处理程序并执行任意多的任务,包括返回列表里下一个进程的地址。

        更糟的是,它们可以启用DR7里的GD标志,并导致访问调试寄存器本身并引发异常,阻挠审查寄存器来检查它们当前的配置。

 

6、请说明单步执行分为哪几种,区别是什么,其实现原理是什么。

        答:(以下内容选自《IDA Pro代码破解揭秘》第5.2.2节)

        单步是指每次执行一条指令,然后把控制权交给调试器的过程。IA-32系列处理器在硬件上直接支持单步。通过一次执行一条指令,我们可以仔细监视代码的特定部分。不过不太可能用这个方法调试整个程序。单步通常用于理解特定部分的代码。

        从CPU的角度来看,调试器设置EFLAGS寄存器里的TF(Trap Flag,陷阱标志)位。一条指令执行完后就会生成调试异常,调试器会把这个调试异常当做中断(INT 0x01)捕获。

        注意:大多数调试器都提供step(单步)命令。通常把他们称为步入(step into)和步过(step over)。从用户角度来看,它们之间唯一的不同是出现在某些指令上,其实就是call和rep指令上。当碰到call时,步入命令将跟随call,而步过命令将在call之后的指令上中断。这通常由设置断点来完成,而不是单步执行到返回。

        知识扩展:

        (以下内容选自《加密与解密 第三版》第2.1.5节)

        当执行一个INT 3断点时,该地址处的内容被调试器用INT 3指令替换了,此时OllyDbg将INT 3隐藏了,显示出来仍是下断前的指令,而实际上这个指令已经被替换成了CC。

        这个INT 3指令,其机器码是CCh,也常称为CC指令。当被调试进程执行INT3指令导致一个异常时,调试器就会捕捉这个异常从而停在断点处,然后将断点处的指令恢复成原来的指令。当然,如果自己写调试器,也可用其他一些指令代替INT 3来触发异常。

用INT 3断点的好处是可以设置无数个断点,缺点是改变了原程序指令,容易被软件检测到。例如为了防范API被下断,一些软件会检测API的首地址是否为CCh,以此来判断是否被下了断点。在这用C语言来实现这个检测,方法是取得检测函数的地址,然后读取它的第一个字节,判断它是否等于“CCh”。下面这段代码就是对MessageBoxA函数进行的断点检测:
[cpp] view plaincopy

    FARPROC Uaddr;  
    BYTE Mark = 0;  
    (FARPROC&)Uaddr = GetProcAddress(LoadLibrary("user32.dll"), "MessageBoxA");  
    Mark = *((BYTE*)Uaddr);         //取MessageBoxA函数第一字节  
    if(Mark = = 0xCC)               //如该字节为CC,则认为MessageBoxA函数被下断  
            return TRUE;            //发现断点  

        程序编译后,对MessageBoxA设断,程序将会发现自己被设断跟踪。当然躲过检测的方法是将断点下在函数内部或末尾,例如可以将断点下在函数入口的下一行,就可以躲过检测了。

        (以下内容选自《加密与解密 第三版》第15.3.7节——通过TrapFlag检测)

        CPU符号位EFLAGS中一位叫做TF,即TRAP FLAG。当这个TF=1的时候CPU执行完EIP的指令就会触发一个单步异常。例如这段代码:
[plain] view plaincopy

    pushfd  
    push eflags  
    or dword ptr [esp],100h ;TF=1  
    popfd                      ;这条指令之后TF=1了  
    nop                        ;这一条执行后会触发异常,应该提前安装SHE,跳到其他地方执行  
    jmp die                    ;如果顺序执行下来了,说明被跟踪

转载注明地址:http://www.chengxuyuans.com/job_interview/88733.html