黑客调试技术揭秘 2.1 Ptrace是GDB的基础调试工具(1)

http://book.csdn.net/bookfiles/101/1001012892.shtml

在开始接触 GNU 调试器( GDB )(一个与 MS-DOS 中的 Debug.com 类似的调试器,但是其功能更强大)时, Windows 爱好者们会觉得失望和难受。大量的文档更是使人沮丧,使人觉得无从下手。这真是石器时代!而 UNIX 爱好者又如何在这个原始社会的严酷环境中生存呢?这是一个难解之谜。

每当看到几行 UNIX 的源代码就会使我回想起前几年的窘境,那时没有工具可以进行任何方式的交互调试,程序出错后,惟一的改错方法就是内存转储。程序员不得不花上几个月的时间来研究打印出来的大量信息,以及把出错的代码聚集在一起并进行分门别类的分析。稍后,出现了通过打印和日志来调试的方法。这种方法将打印命令放在程序中的所有关键位置上,将最重要的变量的内容打印出来。如果出现错误,打印在纸上的内容将揭示出程序出错之前在做什么,其结果又是如何。

通过打印来进行调试的方法一直是一种重要的调试方法。在 Windows 世界中,这种方法主要应用于程序的调试版(参见清单 2.1 ),其中的打印命令在发行版中则被删除(参见清单 2.2 )。这实在不是一种好习惯,因为如果最终用户遇到了故障,那么他们就只能得到错误时的内存转储,而这并不能提供多少信息。这就是 UNIX 提供大量日志控制系统 —从标准的 syslog 直到高级的企业时间日志( http://evlog.sourceforge.net/ —的原因。它们减轻了进行输出和记录日志的工作量,并且在相当大程度上提高了程序的运行速度。

通过打印来进行调试的方法能够满足 80% 的调试需求。不论怎么说,调试器主要都是用来确定程序在指定的问题上是如何运行的:它有助于发现如果发生条件跳转的话,函数将返回什么值,变量包含哪些值等信息,在需要的位置插入 fprintf syslog ,并检查结果,就这么简单!

 

清单 2.1   使用打印调试法的一个不好的例子

清单 2.2   使用打印调试法的一个好的例子

人并不是计算机的奴隶!相反,人们发明计算机是为了使人类的工作自动化。令人遗憾的是,在 Windows 世界中,情形并非如此。因此, UNIX 尽可能地自动化处理错误检测过程。将编译器的警告级别设置为最高级或者使用独立的代码校验程序(最著名的代码校验程序是 Lint ,图 2.1 显示了其界面),那么错误就会像老鼠逃离沉船似的离开你的程序。 Windows 的编译器也能产生错误信息,并不比 GNU C 编译器( GCC )所产生的错误信息逊色;但是,令人遗憾的是,由于大多数程序员的编程水平很低,他们并不看重这些信息。

2.1  Lint 正在清除错误

UNIX 环境中,只有在非常严重的情形下,当其他的工具都证明没有用处时,才会使用单步来执行程序和断点。 Windows 爱好者们认为这种方法太陈旧和不方便。但这主要是因为 Windows 调试器有效解决的那些问题根本不会在 UNIX 中出现。 Windows UNIX 的编程文化之间的差异是很大的。因此,在指责任何一方之前,先检讨一下自己。“不寻常”并不意味着“不正确”。当 UNIX 爱好者需要在 Windows 下工作时,他们也同样会觉得不舒服。

2.1   Ptrace GDB 的基础调试工具

GDB 是一个与系统无关的跨平台调试器。与大多数其他的 UNIX 调试器一样,它也是基于 ptrace 库来实现的, ptrace 库实现了底层的调试原语。为了调试多线程和并行的应用程序,我们推荐使用其他的库,或者,使用诸如 TotalView http://www.etnus.com )这样专用的调试器更好。这是因为处理多线程不是 GDB 的强项。

ptrace 能够将进程切换到挂起状态然后再继续执行进行,从被调试进程的地址空间中读取或者向其中写入数据,从中央处理单元( CPU )寄存器中读取或者向其中写入数据。在 i386 中,这类寄存器包括一般用途的寄存器、段寄存器、“协处理器”寄存器、 XMMx 家族的流 SIMD 扩展寄存器( SSE ),以及 DRx 家族(组织硬件断点时需要它们)的调试寄存器。在 Linux 环境中,还有其他的方法可用于操作被调试进程的辅助结构以及追踪系统调用。传统的 UNIX 系统没有这些功能,调试器必须自己实现这些功能。

清单 2.3 列出的例子说明了 ptrace 在程序中的用法。

清单 2.3   使用打印调试法的一个好的例子

上述的例子统计 ls 工具所使用的机器指令数量。如果要在 Linux 下编译这个例子,请用 PTRACE_TRACEME 替换 PT_TRACE_ME ,用 PTRACE_SINGLESTEP 替换 PT_STEP

2.1.1  P trace 及其命令

在用户模式中,虽然只有一个函数可用,即 ptrace(int _request, pid_t _pid, caddr_t _addr, int _data) ,但是这个函数能做所有的事情!如果你愿意,也可以花费几个小时来编写自己的小调试器,以解决特定的问题。

ptrace 函数的 _request 参数是最重要的一个参数,因为它确定你将做什么。 BSD Linux 的头文件使用不同的定义,这使得将 ptrace 应用从一个平台移植到另一个平台变得很复杂。默认地,我们使用 BSD 头文件中的定义。

r   PT_TRACE_ME PTRACE_TRACEME )将当前进程切换到停止状态。它通常总是与 fork/exec 一起使用,虽然也能遇到自我追踪的应用程序。对于每一个进程, PT_TRACE_ME 只能被调用一次。追踪一个正被追踪的进程是会失败的(另一个较不重要的结果是进程不能追踪它自己。如果要这样做,应该首先从自身派生一个进程)。大量的反调试技术都是以这一事实为基础的。为了克服这类技术,必须使用绕过 ptrace 的调试器。一个信号被发送到正被调试的进程,并将该进程切换到停止状态,该进程可以使用从父进程上下文中调用的 PT_CONTINUE PT_STEP 命令从停止状态退出。 wait 函数会延迟父进程的执行,直到被调试的进程切换为停止状态或者终止为止(终止时,返回值为 1407 )。其他的所有参数都被忽略。

r   PT_ATTACH PTRACE_ATTACH 将进程标志为 pid 的运行进程切换为停止状态,在这种情形下,调试器进程成为“父进程”。其他的所有参数都被忽略。进程必须具有与调试进程相同的用户标志( UID ),并且不能是 setuid/setduid 进程(否则就要用 root 来调试)。

r   PT_DETACH PTRACE_DETACH 停止进程标志为 pid 进程(由 PT_ATTACH PT_TRACE_ME 指定)的调试,并继续其常态运行。其他的所有参数都被忽略。

r   PT_CONTINUE PTRACE_CONT 继续进程标志为 pid 的被调试进程的执行,而不中断与调试器进程的通信。如果 addr == 1 (在 Linux 中为 0 ),从上次停止的地址继续执行;否则,从指定的地址继续执行。参数 _data 指定发送到被调试进程的信号数量(零说明没有信号)。

r   PT_STEP PTRACE_SINGLESTEP 进行进程标志为 pid 的进程的单步执行,即执行下一条机器指令并切换为停止状态(在 i386 中,这是根据设置追踪标志来实现的,虽然 有些 “黑客”函数库使用硬件断点)。 BSD 要求将参数 addr 置为 1 ,而 Linux 要求将该参数置为 0 。其他的所有参数都被忽略。

r   PT_READ_I PT_READ_D PTRACE_PEEKTEXT PTRACE_PEEKDATA )分别从代码区和正 调试进程的地址空间区读取机器字。在许多当代的平台中,这两个指令是等价的。 ptrace 函数接收目标地址 addr ,并返回读到的结果。

r   PT_WRITE_I PR_READ_D PTRACE_POKETEXT PTRACE_POKEDATA 将由 _data 传入的机器字写入 addr 所指定的地址。

r   PT_GETREGS PT_GETFPREGS PT_GETDBREGS PTRACE_GETREGS PTRACE_ FPREGS PT_GETFPXREGS 将一般用途寄存器、段寄存器和调试寄存器的值读入到地址由 _addr 指针所指定的调试器进程的内存区中。只有 i386 平台接收这些与系统相关的命令。寄存器结构的描述放在头文件 machine/reg.h 文件中。

r   PT_SETREGS PT_SETFPREGS PT_SETDBREGS PTRACE_SETREGS PTRACE_ SETFPREGS PT_SETFPXREGS 通过拷贝由 _addr 指针所指定的内存区域的内容来设置被调试 进程 的寄存器的值。

r   PT_KILL PTRACE_KILL sigkill 发送到被调试进程,以终止其执行。

SYNOPSIS
       #include <sys/ptrace.h>

       long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
      
 The value of request determines the action to be performed:

       PTRACE_TRACEME
              Indicates that this process is to be traced by its parent.  Any signal (except SIGKILL) delivered to  this  process
              will  cause  it to stop and its parent to be notified via wait.  Also, all subsequent calls to exec by this process
              will cause a SIGTRAP to be sent to it, giving the parent a chance to gain control before  the  new  program  begins
              execution.   A process probably shouldn’t make this request if its parent isn’t expecting to trace it.  (pid, addr,
              and data are ignored.)
             
       The above request is used only by the child process; the rest are used only by the parent.  In the following requests, pid
       specifies the child process to be acted on.  For requests other than PTRACE_KILL, the child process must be stopped.

       PTRACE_PEEKTEXT, PTRACE_PEEKDATA
              Reads  a  word  at  the  location  addr in the child’s memory, returning the word as the result of the ptrace call.
              Linux does not have separate text and data address spaces, so the two  requests  are  currently  equivalent.   (The
              argument data is ignored.)

       PTRACE_PEEKUSR
              Reads  a  word  at  offset addr in the child’s USER area, which holds the registers and other information about the
              process (see <linux/user.h> and <sys/user.h>).  The word is returned as the result of the ptrace  call.   Typically
              the offset must be word-aligned, though this might vary by architecture.  (data is ignored.)

       PTRACE_POKETEXT, PTRACE_POKEDATA
              Copies  the word data to location addr in the child’s memory.  As above, the two requests are currently equivalent.

       PTRACE_POKEUSR
              Copies the word data to offset addr in the child’s USER area.  As above, the offset must typically be word-aligned.
              In order to maintain the integrity of the kernel, some modifications to the USER area are disallowed.

       PTRACE_GETREGS, PTRACE_GETFPREGS
              Copies  the child’s general purpose or floating-point registers, respectively, to location data in the parent.  See
              <linux/user.h> for information on the format of this data.  (addr is ignored.)

       PTRACE_SETREGS, PTRACE_SETFPREGS
              Copies the child’s general purpose or floating-point registers, respectively, from location data in the parent.  As
              for PTRACE_POKEUSER, some general purpose register modifications may be disallowed.  (addr is ignored.)

       PTRACE_CONT
              Restarts  the  stopped  child  process.   If  data is non-zero and not SIGSTOP, it is interpreted as a signal to be
              delivered to the child; otherwise, no signal is delivered.  Thus, for example, the parent  can  control  whether  a
              signal sent to the child is delivered or not.  (addr is ignored.)

       PTRACE_SYSCALL, PTRACE_SINGLESTEP
              Restarts  the  stopped  child  as for PTRACE_CONT, but arranges for the child to be stopped at the next entry to or
              exit from a system call, or after execution of a single instruction, respectively.  (The child will also, as usual,
              be stopped upon receipt of a signal.)  From the parent’s perspective, the child will appear to have been stopped by
              receipt of a SIGTRAP.  So, for PTRACE_SYSCALL, for example, the idea is to inspect the arguments to the system call
              at  the  first  stop,  then do another PTRACE_SYSCALL and inspect the return value of the system call at the second
              stop.  (addr is ignored.)

       PTRACE_KILL
              Sends the child a SIGKILL to terminate it.  (addr and data are ignored.)

       PTRACE_ATTACH
              Attaches to the process specified in pid, making it a traced "child" of the current process; the  behavior  of  the
              child  is as if it had done a PTRACE_TRACEME.  The current process actually becomes the parent of the child process
              for most purposes (e.g., it will receive notification of child events and appears in ps(1) output  as  the  child’s
              parent),  but  a  getppid(2)  by  the  child will still return the pid of the original parent.  The child is sent a
              SIGSTOP, but will not necessarily have stopped by the completion of this call; use wait to wait for  the  child  to
              stop.  (addr and data are ignored.)

       PTRACE_DETACH
              Restarts  the stopped child as for PTRACE_CONT, but first detaches from the process, undoing the reparenting effect
              of PTRACE_ATTACH, and the effects of PTRACE_TRACEME.  Although perhaps not intended, under Linux a traced child can
              be detached in this way regardless of which method was used to initiate tracing.  (addr is ignored.)
        

2.1.2   GDB 的多线程支持

要确定你的 GDB 版本是否支持多线程,可以使用 info thread 命令(它将输出关于线程的信息)。要在线程之间进行切换,可以使用 thread N 命令。

如果不支持多线程,可以升级到 GDB 5.x 或者安装与你的 UNIX 版本一起提供的特别补丁,或者独立发布的补丁。

推荐使用的调试并行应用程序的调试器是 TotalView (如图 2.2 所示)。

清单 2.4   不支持多线程应用程序的调试

清单 2.5   支持多线程应用程序的调试

2.2  专用于并行应用程序的 TotalView 调试器

 

2.1.3   GDB 简明指南

GDB 是一个基于传统命令行形式实现的控制台程序(其界面如图 2.3 所示)。虽然在 GDB 的长期进化中, GDB 接受了大量优秀的图形用户界面( GUI )(如图 2.4 和图 2.5 所示),但 Turbo Debugger TD )风格之类的交互调试目前在 UNIX 世界中依然不流行。这通常都是从 Windows 平台移植程序的人的致命错误,这些人的思维方式不可避免地贴上了“ M$ ”标签。这里简单的模拟并不适用:如果 TD 是长凳工具,那么 GDB 就是具有编程控制的车床。你迟早都会喜欢它的。

为了在源代码级进行调试,程序必须在编译时包含调试信息。在 GCC 中,使用命令行选项 –g 就能实现这一点。如果没有调试信息, GDB 将在反汇编级上进行调试。

被调试文件的文件名通常都是通过命令行来传递的,其格式如下: gdb 文件名 。如果要调试一个活动的进程,请在命令行中指定其进程标志。如果要调试内核转储,请使用命令行选项 –core == 内核名 。这三个参数可以同时使用,还可以使用命令行命令 target 来在它们之间进行切换。命令行命令 target exec 切换到被调试的文件, target child 切换到附加的进程, target core 切换到内核转储。可选用的命令行参数 q 用于禁止显示版权信息。

2.3   传统的 GDB 界面

将应用程序加载到调试器之后,必须设置断点。请使用 break 命令来设置断点(其简称是 b )。例如,命令 b main 将断点设在 C 语言的 main 函数上,而 b _start 将断点设在可执行和链接格式( ELF )文件的入口点(有的文件的入口点具有不同的名称)。也可以将断点设置在任何地址上,比如, b *0x8048424 或者 b *$eax 。寄存器的名称要用小写字母,并以美元 符号为前缀。 GDB 理解两种“跨平台”的寄存器: $pc 用于命令指针,而 $sp 用于堆栈指针。但是请记住,程序被装载到调试器之后并不能立即看到寄存器。只有用命令 run(r) 启动被调试的进程后,寄存器才会显示出来。

调试器执行决定应该设置软件断点还是硬件断点。最好不要干涉这一过程。并非所有的调试器版本都支持强制设置硬件断点的的命 hbreak 。例如,我所用的版本就不支持该命令。用于数据的断点在 GDB 中称为观察点。当地址 addr 处的数据被改变时,命令 watch addr 将调用调试器。当读或写操作访问地址 addr 时,命令 awatch addr 将调用调试器。当读操作访问地址 addr 时,命令 rwatch addr 将调用调试器,但并非所有版本的调试器都支持这一命令。使用命令 info break 可以查看所有已设置的断点和观察点。命令 clear 删除所有的断点,而命令 clear addr 删除所有设置在给定的函数、地址或者行号上的断点。诸如 enable disable 之类的命令用于暂时地允许和禁止断点。还有一种常规命令的高级语法用于处理断点,可以在文档中找到该语法的详细描述。命令 continue(c) 用于继续执行被命令 b 所中断的程序,即从断点继续执行。

2.4   调试器 DDD GDB 提供的图形用户界面

2.5   加在 GDB 上的另一种图形用户界面

命令 next N n N 执行下面的 N 行代码,会越过嵌套的函数,而命令 step N s N )会进入嵌套的函数。如果没有指定 N ,则只执行一行代码。命令 nexti stepi 的功能与它们相似。不同的是,这两条命令用于机器指令,而不是源代码的行数。它们通常与命令 display/i $pc(x/i $pc) 一起使用,这条命令要求调试器显示当前的机器指令。每一次会话只需调用一次该命令就足够了。

命令 jump addr 将控制转到程序的任意位置,而命令 call addr/fname 调用参数 fname 所指定的函数。即使是 SoftIce 也没有提供这一命令!它是非常有用的,也是用户常常要求提供的命令。另外几条很有用的命令包括 finish ,它用于在退出当前函数之前中断执行(它等效于 SoftIce 的命令 P RET ),以及命令 until addr u addr ,它用于继续执行直到到达指定的位置为止。如果启动命令时没带参数,则当下一条命令到达时停止执行(这对循环特别重要)。命令 return 立即取消函数的执行。

命令 print expression p expression 用于输出指定表达式(例如 p 1+2 )的值、指定变量的内容( p my_var )、指定寄存器的内容( p *$eax )或者内存单元的内容( p *0x8048424 p *$eax )。如果需要输出几个单元,就使用命令 x/Nh addr ,其中 N 是要输出的单元的数量。此时并不需要在地址前面加上星号。

命令 info register(i r) 输出所有可用寄存器的值。修改内存单元和寄存器的内容可用命令 set 来实现。例如, set $eax = 0 0 写入寄存器 eax 命令 set var may_var = $ecx 将寄存器 ecx 的值赋给变量 my_var ,而命令 set {unsigned char*} 0x8048424 = 0xCC 将数字 0xCC 写入字节地址。命令 disassemble _addr_from _addr_to 以反汇编清单的形式输出内存的内容,表示的格式由命令 set disassembly-flavor 确定。

命令 info frame info args info local 分别显示堆栈帧,函数参数和局部变量的内容。为了切换到父函数的帧,使用命令 frame N 。命令 backtrace bt 所做的事情与 Windows 调试器的命令 call stack 相同。当研究内核转储时,这是不可或缺 的。

GDB 进行工作的一个完整过程大致如下:将程序装入调试器,执行命令 b main (如果不成功,则使用 b _start ),然后执行命令 r 。用命 n 或者 s 一步一步地调试程序。如果需要,指定 x/i $pc 使 GDB 显示当前所执行的代码。执行命令 quit q 以退出调试器。可在文档中找到所有其他命令的详细描述。希望这里列出的 GDB 命令简明指南能对你有所帮助,而不至于迷失在信息丛林中。

比较 UNIX Windows 的调试器,马上就能发现:后者比前者落后许多,而且能够证明 Windows 调试器并不是针对专业人士的。三维显示的按钮、可以缩放的图标、弹出式菜单和其他过于花哨的东西,确实很好看。然而,需要不停地按 <F10> 键,你不觉得厌烦吗?在 GDB 中,通过编写宏(或者使用已有的宏)可以使事情变得方便许多,因为可以编程实现的每一件事在此都已经实现编程,而且可以免费使用。

UNIX 中的调试工具功能强大而且使用方便。除了 GDB 外,还有一些其他的产品。惟一缺少的东西是可以基于没有符号信息和源代码的二进制文件工作的高质量系统级内核调试器。有一段时期 UNIX 存在多个平台,而且需要进行不同平台之间的移植,这在 UNIX 中留下了一个忧郁记号,以及实现可移植性和跨平台支持的愿望。在这种条件下,破解当然是很困难的!不过,源程序的可用性可以减少一些困难。

2.1.4   追踪系统调用

追踪系统调用打开了一扇真实窗口,透过它可以了解正被研究的程序的内幕。它可以显示正被调用的函数的名称,它们的参数和返回值。对于编程新手来说,有一个共同的问题是常常忘记“额外的”错误检查,而调试器不是查找这类错误的最佳工具。可以使用 truss 或者 ktrace 类的标准工具,或者使用商业代码分析程序的自由软件版本。

清单 2.6 显示了使用 truss 所获得的日志。该程序试图打开名称为 my_good_file 的文件,但是找不到该文件,从而导致程序出错。这是最简单的案例。然而,一个著名的规则认为全部开发时间的 99% 花在寻找错误上,而这些错误都是不值得查找的!

 

清单 2.6  使用 truss 查找错误

2.1.5   相关链接

r   GDB 内幕( http://gnuarm.org/pdf/gdbint.pdf ):一本关于 GDB 内幕的极好的指南。当需要改善源程序时,它非常有用。

r ptrace 追踪进程( http://linuxgazette.net/issue81/sandeep.html ):一篇关于在 Linux 中使用最简单的追踪程序中的例子来进行追踪的论文(在 FreeBSD 中的情形完全不同)。

r 在源程序中修正漏洞( http://www.linux-mag.com/2004-04/code_01.html ):一篇关于利用源代码分析进行早期查错的论文。

r 使用 CTrace 库( http://ctrace.sourceforge.net ):一篇关于使用该库来调试多线程应用程序的论文。

r 内核和用户空间调试技术(德语文章)( http://www.unfug.org/files/debugging.pdf ):专门论述调试,描述少为人知的 GDB 结构细节的论文集。

r ELF/INTEL 系统的逆向工程(法语文章)( http://www.sstic.org/SSTIC03/arcticles/ SSTIC03-Vanegue_roy-Reverse_Intel_ELF.pdf ):关于在没有源程序时在 i386 平台上研究和调试 ELF 文件的论文。

从黑客的观点来看, UNIX 缺少像样的破解工具,而且这类工具也不会很快地出现。只有赤手空拳和绞尽脑汁地进行破解。最令人烦恼的是缺少功能完备的调试器,如果没有 SoftIce ,至少也应该有 OllyDbg 之类的调试器啊。你必须自己编写内存转储程序、各种补丁和大包文件的自动解包程序之类的小工具,因为在网上找不到任何像样的东西。能找到的只有无数长时间以来失败项目的墓地。

我希望这种情形在未来几年内会有所改善。需求刺激供给。尽管如此,然而我在此还是给出一个关于现有的适合破解的软件的概述(顺便提一下,复杂的保护机制也同样需要调试)。

2.2.1   调试器

再重复一次, GDB 是基于 ptrace 库的、跨平台、源代码级调试器,主要用于调试具有源代码的应用程序。它几乎不适用于破解。它支持程序执行的硬件断点,但是不支持对内存的读、写操作(但是,当从 VMware 中启动时,硬件断点有效),也不能暂停和修改共享内存(换句话说,它几乎不能用于调试使用共享内存的程序)。它缺少内存查找功能。它不能装载文件,如果该文件具有非法的段表结构、没有段表结构或者具有被删除段表。它被实现为一个控制台程序,带有一套复杂的命令系统,其命令系统的完整描述包括 300 页的小字体文字。如果你愿意,也可以采用该调试器的图形外壳(有许多的版本)。可是,当需要更正一个糟糕的内核时,一个好的界面并没有太大的帮助。在其长期的演化中, GDB 变得与许多的反调试技术混杂在一起,大多数的这类技术仍保持其重要性。然而, GDB 也有其优点。首先,它是免费的,依照 GNU 的版权许可来发布(因此,它的名称为 GNU 调试器)。其次,它被作为大多数 UNIX 发布版本的一部分而一起提供,允许对其执行文件进行修补升级(进行修补升级时,甚至不需要退出该调试器)。图 2.6 显示了其运行情况。

2.6  GDB 的工作界面

下面是新手须知的一个小技巧:为了在入口点上暂停,需要首先确定入口点的地址。为 实现这一目标,可以使用标准工具 objdump (仅适用于未被保护的文件),或者 biew/ IDA:objdump file_name –f 。然后将被调试的程序装载到 GDB gdb –q file_name ), 并启动命令 break *0xXXXXXXXX ,其中 0xX 是起始地址。再运行命令 run 来启动程序执行。如果所有这些都正确完成, GDB 将马上暂停并将控制传给你。如果没有出现这种情 形,那么请在 BIEW 中打开文件,并在入口点插入断点(代码为 CCh ),请先记下原来的内容。再启动调试器,当达到断点时,恢复其内容( set (char) *0xXXXXXXXX = YY

汇编语言调试器( ALD ,如图 2.7 所示)( http://ald.sourceforge.net/ )是一个速度快的源代码级调试器,它只有很少的控制,适用于调试汇编代码和二进制文件。它基于 ptrace 库实现,也具有该库的局限性。现在,它仅在 x86 平台上成功运行,并能在下列平台上成功编译: Linux FreeBSD NetBSD OpenBSD 。它支持执行的断点、单步执行、查看和编辑转储,以及查看和修改寄存器。它包含一个简单的反汇编器。这些就是它的全部功能!这实在是用于破解工具的最低功能要求。即使是 MS-DOS Debug.com 也提供了更丰富的功能集。然而, ALD 是免费软件,提供了源代码,并能装载没有段表的文件。它适合于学习破解技术。然而,不能将它看成是主要的破解工具。

2.7  ALD 调试器

Dude http://the-dude.sourceforge.net/ )是一个有趣的源代码级调试器,它的实现并未采用 ptrace 。当 GDB ALD 开始走向失败时,它却成功了。令人遗憾的是,它只能在 Linux 中运行,其他操作系统的用户都非常眼红。从系统体系结构来说,它包括如下三个部分:内核模块 the_dude.o ,实现了底层调试功能;包装库 lib-duderino.so ;以及外部用户界面, ddbg 。一般最好从重新编写用户界面开始。该调试器是自由软件,下载之前,必须先在 http://www.sourceforge.net 上注册。

Linice http://www.linice.com/ )是 SoftIce Linux 中的模拟软件(如图 2.8 所示)。这是一个功能强大、用于调试没有源代码的二进制文件的内核级调试器。这是任何 Linux 黑客必备的主要工具。现在它只能在内核 2.4 版中运行(并且大概也能在 2.2 版中运行)。当在所有其他版本中编译时,它会在文件 iceface.c 中出错。它会增加设备 /dev/ice ,因此会透露它的存在(然而,这并不是一个问题,因为有源程序可用)。它弹出 <CTRL>+<Q> 快捷键(现在还不支持通用系列总线( USB )键盘),并且它没有装载程序(而且也不会很快出现)。因此,惟一的调试方法就是将机器命令 INT 03 (操作码为 CCh )插入入口点,过后需要手工恢复原来的内容。

PIce http://pice.sourceforge.net/ )是 Linux 中的一个试验性内核级调试器,只能在控制 台模式下运行(换句话说,不能使用 X-Windows ),实现了最小功能集。但是,它可能很有用。

2.8   不,这不是在做梦。这是 SoftIce Linux

2.9 所示的 x86 仿真器( http://ida-x86emu.sourceforge.net )是一个仿真调试器,这是一个 IDA Pro 插件,并没有进行预编译,而是以源程序的形式发布(这意味着除了 IDA Pro 还需要 SDK ,似乎更难找到)。该仿真器的主要优点是它允许在虚拟处理器上执行任何代码段。例如,它可以将控制传给检查序列号和密码的过程,而绕过其他的代码。这样的技术结合了静态和动态分析的最好特色,极大地简化了破解非常复杂精密保护机制的过程。

2.9   主要的仿真器控制板

2.2.2  反汇编工具

一直以来 IDA Pro http://www.datasecue.com/idabase )是最好的反汇编器,现在也有了 Linux 版本! FreeBSD 和其他操作系统的用户只能在仿真器下使用其控制台 Windows 版本(如图 2.10 所示),或者必须在 MS-DOS OS/2 Windows 上工作。直到现在, IDA Pro 仍然不支持没有段表的文件。然而,这个问题已经在最新的版本中得到了解决。由于在 UNIX 中缺乏像样的调试器,因此 IDA Pro 成为主要的破解工具。

工具 objdump 是与 dumpbin 类似的一个工具,用于处理 ELF 文件,并带有一个简单的反汇编器。它要求文件具有段表,并且其中不能有非法的字段。它不能处理压缩文件。然而,如果你手头没有 IDA Pro 的话,这个工具还是有用的。

2.10  IDA Pro 的控制台版本

2.2.3  侦查软件

truss 是一个很有用的工具,可以在大多数的 UNIX 发布包中找到(如图 2.11 所示)。它应用程序级别追踪系统调用和被调试的程序发出的信号。这样能够收集许多关于保护机制内部的有用信息。

清单 2.7  truss 报告样例

ktrace 是发行包所提供的另一个工具。它追踪系统调用、名称翻译、输入 / 输出操作、信号、用户模式追踪,以及从内核级别进行在被调试程序的上下文切换。总起来说, ktruss truss 的一个增强版本。可是,与后者相比,它产生二进制格式的报告,而不是文本格式的报告。因此,要得到报告就还需使用工具 kdump

2.11   使用 truss 追踪系统调用

清单 2.8  ktrace 报告样例

2.2.4  十六进制编辑器

BIEW http://belnet.dl.sourceforge.net/sourceforge/biew/biew562.tar.bz2 )是一个集十六进制编辑器、反汇编器、加密器和 ELF 文件阅读器等功能于一身的工具(如图 2.12 所示)。它缺少一个内置的汇编器,因此,必须直接在机器码级别上进行破解。当然,这是一个不合理的漏洞。可是,并没有更好的替代工具(惟一的最大可能就是自己编写一个汇编器)。

2.12  BIEW 十六进制编辑器

2.2.5  内存转储程序

UNIX 中,每一个进程的内存内容都表现为驻留在 /proc 目录中的文件集的形式。此外,寄存器内容和其他数据也存放在该处。然而,内存转储还不是直接可用的 ELF 文件,也不适合于直接使用。但是,可以反汇编其原始映像。

2.2.6  自动保护工具

可执行文件的压缩文件不仅可以用来减小程序的大小,也可以用于保护文件免于被破解。在 Windows 中,这样的做法并不能把程序的破解推迟多少时间。但是在 UNIX 中就不一样!在 UNIX 中没有自动的解压缩程序,只有很少的几种存储程序,并且没有像样的调试器(特别是对于 Linux 之外的系统)。因此,使用压缩文件就足够了,能够破解它的黑客并不会太多。为了破解它,黑客们需要很强的动机(一般都没有这样的动机)。

所有压缩文件都存在的一个严重问题是,它们极大地减小了被保护程序的可移植性(当它们包含特别的反调试技术时,情形更严重)。此外,我在实际应用中遇到的压缩程序都只适用于 Linux ,不能用于 FreeBSD 和其他 UNIX 版本(虽然编写这样的压缩程序是可能的)。

Shiva http://www.securereality.com.au/ )是能够使用的功能最强大的压缩程序,虽然它是基于 Windows 程序员很久以前就知道的陈旧思想而实现的。它基于以“洋葱层”模型为基础的一种多层加密模型来实现,使用了带有反调试和反汇编技术的多态引擎,可以抵御 GDB 和其他利用 ptrace 进行调试的调试器,能成功地阻止 strace ltrace fenris ,并且能避免通过 /proc 目录来获取程序转储。详细的信息可从 http://www.blackhat.com/presentations/ bh-federal-03/bh-federal-03-eagle/bh-fed-03-eagle.pdf 中获得。虽然一般认为 Shiva 是不可攻破和不能破解的,但是它并没有给有经验的黑客造成任何严重的障碍。而且,该压缩程序的过分积极的特性反而引起了很多的问题。特别地, fork 函数停止工作。不过, Shiva 的出现仍然是保护机制开发方面的一个很大的进步。对于破解的新手来说,这是最好的选择。

Burneye http://packetstormsecurity.nl/groups/teso/burneye-1.0.1-src.tar.bz2 )是一个常用的,但功能较差的压缩和保护程序。很久以前就被破解了,只有懒惰的人才无法在因特网上找到破解它的指南。这里仅给出能够找到这类指南的一个很短的地址清单:

r   http://www.packetstormsecurity.org/groups/teso/indexsize.html

r   http://www.activalink.net/undex.php/BurnEye Encrypted Binary Analisis

r   http://www.securiteam.com/tools/6T00N0K5SY.html

它使用一种异常的主要机制来检测一个调试器,即简单地发出信号 5 trace/ breakpoint trap )。如果 GDB 或者类似的调试器不存在,该信号会将控制传给特定的过程,该过程会把“秘密的”内存单元的内容加 1 。如果调试器存在,该机制会产生一个异常。如果使用“正确的”调试器,即绕过 ptrace 而工作的调试器,比如 Dude 或者 Linice ,那么该压缩程序就很容易被破解,虽然并不能像你所期望的那么快(因为需要考虑大量的复杂代码)。总起来说, Burneye 适用于抵御没有恶意的黑客。在大多数情况下,并不需要更多的工具。

624 http://sed.free.fr/624/ )是一个小巧且少为人知的压缩程序。它每周运行 6 天,每天 24 小时,星期天休息(开个玩笑)。尽管如此,它还是一个值得收藏的工具。

Ultimate Packer for Executables UPX )( http://upx.sourceforge.net/ )是一个传奇性的跨平台压缩程序,它能在从 Atari Linux 的大多数平台中运行。它并不能阻止被调试,甚至更糟糕,它包含一个内置的解压缩程序,可以用来将被保护的文件恢复到其原始形式。然而,经过一些修改(可能是因为提供了源代码),它具备了抵御黑客技术的完整功能要求。与使用现有的保护程序插件相比,稍微增强 UPX 的功能更有效,因为 UPX 的任何改进版本都必须重新被研究,这使得黑客不能使用一样的破解方法。自然地,你需要使用反调试技术来升级该压缩程序。

几年以前,黑客的主要工具还是反汇编器和调试器。而现在,传统的工具集中增加了仿真器,仿真器在实际使用中为代码挖掘者提供了无数的可能性。直到最近,还是只有大公司才能从这些可能性中得利;而现在,这些可能性已经成为每一个研究者习惯利用的性质。那么,什么是仿真器?它能提供什么样的新功能?

每一个操作系统都具有自己的特色,例如,同一个程序在不同的操作系统(在 Windows 9x Windows NT )中的表现可能相当不一样。在这方面提及大量的类 UNIX 系统和具有相同基因的变体是不明智的。进行网络安全研究的黑客至少必须具备三个操作系统的技能: Windows NT Linux FreeBSD ;其他的在市场上占有一定份额的操作系统也是有必要具备的。许多的弱点(特别是溢出错误)本身只出现在特定的版本中,其他的版本可能没有这类问题。这样你就不可能用手头有的东西来编写和调试所有的目标。然而,经常重装操作系统是很不方便的,虽然你习惯于这样做。除了浪费大量的时间(而时间总是与我们作对),还存在丢失所有已积累数据的潜在风险!

此外,创建内核转储的溢出错误测试非常容易使文件系统陷入混乱。因为丢失的数据可以被部分或者全部地恢复,你必须能够进行这种操作以获得大量的避免破坏后果的经验。愚钝理论(以及“ Norton Disk Destroyer ”)没有任何帮助。因此,黑客在动手前经常连接一个额外的硬盘,用以恢复任何可能的损失。

与病毒和试验程序相关的试验必须在与外界隔离的独立计算机中进行。这是因为内置在 Windows NT 家族和 UNIX 变体之类的操作系统内的本地访问控制系统非常不完善。由此导致的结果是,研究者的任何微小疏忽经常会导致巨大的破坏。

这些问题的传统解决方法是购买多台计算机或者至少大量的硬盘,这些硬盘被交替地连接到计算机上。第一种方法太昂贵(而且普通的平面上也没有空间放置所有的这些计算机),也毫无美感。第二种方法很不方便。而总是这样移来移去也使得硬盘很难幸免因振动而产生坏块。

幸运的是,这种可怕的事情已成过去。现代处理器的强大能力允许我们完整地仿真整台 PC ,并以能够接受的速度来实时地执行程序。似乎每天都有新的仿真器出现,迄今为止,我们已经有 VMware Virtual PC Bochs DOSBox ,还可以列出其他的一些。图 3.1 显示了其中的一个仿真器。应该选择哪一种呢?大多数涉及仿真器的出版物都是专门为游戏玩家和系统管理员预备的。游戏玩家主要对高的执行速度和高质量的音响感兴趣。对于系统管理员而言,虚拟机之间通信机制的出现是特别重要的。黑客对这些特性很少感兴趣或者根本没有兴趣。对他们而言,是否可以运行 SoftIce 是头等重要的(也期望有内置调试器)。此外,相对于真实的处理器而言,仿真的主要优点并不是建立在仿真上的。当然,我不会匆忙地下结论。

3.1   仿真器作为一种测试平台,用于获得恢复损坏的文件系统的技巧

3.1   最低系统要求

大多数仿真器对硬件的要求相当低。例如,为了舒服地与 Windows 2000 FreeBSD 4.5 一起工作,使用 Pentium III 733 MHz 处理器( VMware 将其仿真为 Pentium III 366 MHz ,而 Virtual PC 将其仿真为 Pentium III 187 MHz )就足够了。简单地说,它能让你玩 Quake I ,虽然几乎是处于其能力的极限。

对可用的随机存取内存( RAM )的要求要严格一些。宿主操作系统通常至少需要 128MB 内存,并分配 128MB 256MB 给每一个虚拟机(即寄宿操作系统)。当然,要求的内存数量取决于所仿真的操作系统的类型。例如,如果仿真 MS-DOS ,那么 4MB 就足够了。而 256MB 就足以仿真运行在 Windows 2000 或任何相似的操作系统上的 Windows 2000/XP/2003

硬盘的大小通常并不是特别重要的。虚拟机也并不是用来做数据积累的。在一些少见的异常情形中,除了典型的操作系统安装文件和最低要求的附加应用程序之外,甚至不会安装其他的任何东西,一般不会要求超过 1GB 的磁盘空间。同时,我所知道的所有仿真器都不要求直接访问物理磁盘。相反,虚拟磁盘映像都存放在一个普通文件中,该文件保留在宿主操作系统中,宿主操作系统也能完全控制它。一般地,至少有两种类型的虚拟磁盘,即固定的和动态的(稀疏的)。当创建一个固定的磁盘时,仿真立即将映像文件分散到所要求的磁盘空间中,即使其中并不包含有用的信息。相反,动态磁盘仅在映像文件中存放那些真实使用的虚拟扇区,当用真实数据填充时,映像文件会依据需要而增大。真是一种有趣的方式,是不是?

不一定非要在虚拟机中将物理磁盘划分为相等的两部分,在理论上也可以给每一个机器都分配全部可用的空间。此外,机器本身还能决定哪一个最具紧迫性。但是事情并非如此简单。动态盘的效率比固定盘的效率要低很多。而且,动态盘还会遭受内部碎片的影响(不要将这种碎片与映像文件的碎片和被仿真的系统文件碎片相混淆)。虽然有的仿真器(特别是 VMware )含有内置的消除碎片的工具,但仍然不能解决这个问题。更糟糕的是,动态盘的格式并未标准化,因此不同的仿真器所创建的映像是互相不兼容的。

其他硬件的可用性并不重要,因为它们不会影响仿真器的速度。

Please refer to the site for the rest.

http://book.csdn.net/bookfiles/101/1001012897.shtml

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值