我有GDB,但是我要动态进行逆向工程的二进制文件没有符号,也就是说,当我运行file实用程序时,它显示出我被剥夺了:

ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped


我有哪些选择如果运行该环境的环境不允许远程IDA Pro实例连接到gdbserver?简而言之:您所拥有的环境在允许您执行的操作中受到限制,但是您确实拥有可信赖的旧版gdb和可反向工程的二进制文件。

评论

unstrip是一种工具,可以尝试恢复已知库调用的丢失符号名称。

b __libc_start_main

#1 楼

为了方便起见,我使用了惯例和初步说明。为了简洁起见,我整理了GDB的输出,因为它通常在每届会议开始时都会显示版权和其他信息。当我重现输出时,我将从第一个(gdb)提示符行开始,或者从第一条真正的输出行开始,以防万一或自动执行命令。

为了区分在GDB提示符下输入的命令,就像在现实世界中一样,它们将具有领先的(gdb)。对于shell命令,它根本不是前缀,或者是$,因为它似乎是大多数unixoid系统上的惯例。

当我使用诸如vim这样的特定命令作为编辑器时,您可以自由使用当然要使用您自己喜欢的编辑器。无论是emacs还是nano,我都不会判断;)

入门

本节内容是关于设置gdb环境并开始该过程的。我还将为新手们介绍一些花絮。

您应该知道的窍门

GDB有一个很好的提示,提示您在程序中断后或任何时候停止光标您正在执行步进或类似操作。


运行GDB命令后按RETURN(又名ENTER)将再次运行相同的命令。当您单步执行stepnext的代码,并且只想一个接一个地继续执行时,这很有用。
只要命令是明确的,就可以将其缩写。对于某些经常使用的命令,存在一个特殊的速记,尽管存在歧义,但该优先级仍然优先:



b(对于break)(尽管btbacktrace

ccontcontinue(尽管catchcall等)

nnext(尽管ninexti


您可以使用命令call从调试程序中调用实际的库函数甚至函数。这意味着您可以尝试行为或强制行为。
您可以使用gdbtuigdb -tui启动GDB,以获取(可能更方便)更直观的文本用户界面。它在顶部显示源代码,在下面显示(gdb)提示符。您还可以通过在layout src提示符下执行命令(gdb)来切换到此布局。
GDB与许多shell一样,具有命令行完成功能,因此请使用Tab来发挥优势,并确保在每次使用时都使用helphelp [keyword|command]需要帮助。

shell允许您在Shell中执行命令,以便可以从GDB会话中运行命令。在开发过程中,示例为shell make

printexaminedisplay知道各种格式(/FMT),可以使用这些格式使输出更易读。
当进行源代码级调试时,可以使用C类型强制转换为显示值。想象一下void *后面的C字符串(在这种情况下GDB知道了这些符号)。只需将其转换为(char*)并打印:print (char*)variable

让进程运行

由于我们要动态分析二进制文件,因此需要首先启动它。

命令行

我们不仅可以通过传递二进制文件的路径,还可以传递我们想从其开始的参数来直接从命令行执行此操作。整个过程如下所示:

$ gdb --args ./exe argument1 argument2


很容易。然后,从(gdb)提示符下,您可以发出run命令(简写为r)以使用命令行上给定的参数运行./exe。我更喜欢这种方法,但是您的里程可能会有所不同。



GDB提示

启动GDB并在(gdb)提示符下使用file命令加载二进制文件,然后使用run命令以您要传递的参数启动它:

$ gdb
(gdb) file exe
(gdb) run argument1 argument2


上面的一种替代方法是使用set args,例如:

$ gdb
(gdb) file exe
(gdb) set args argument1 argument2
(gdb) run




在任何情况下,您还可以看到参数run将通过发出以下命令进入启动过程:

(gdb) show args


btw:如果您想了解环境变量,请使用GDB内置的help命令作为help sethelp show。指针:set environment VARNAME=VALUEshow environment [VARNAME]unset environment VARNAME。但是,为什么程序会因SIGSEGV(段错误)而停止?

好吧,我们还不知道,但是看起来这个小兽想要适当的治疗。由于我们练习防御性计算,因此我们不想运行任何我们不了解的东西,对吗?因此,让我们重新开始。如果这将是恶意软件,则如果它是VM guest虚拟机,则必须刷新计算机并重新安装或还原快照。

首先,我们将要运行info命令,如下所示:

(gdb) info file


观察:



有两个重要的信息,与我们最相关的是线路说明:

Entry point: 0x400710


好了,所以我们可以在该处设置一个断点,然后使用我们喜欢的参数对过程进行run


.gdbinit赢了

但是等等,这已经很乏味了。没有简单的方法以某种方式自动执行这些步骤吗?事实上有。启动时,可以使用名为.gdbinit的文件向GDB发出命令。您还可以使用(shell)命令行上的-x参数,使用GDB命令传递文件。如果我有很多项目,通常它们都在每个都有.gdbinit文件的子文件夹中。

旁注:-nx阻止了.gdbinit的内容在启动时执行。

所以我们知道我们要传递的参数以及断点的地址,这将转换为以下.gdbinit文件:

file exe
break *0x400710
run argument1 argument2


启动gdb时得到的输出没有任何其他参数是:

Breakpoint 1 at 0x400710

Breakpoint 1, 0x0000000000400710 in ?? ()
(gdb) 


不错!但这看起来不一样...

组装和GDB

所以您习惯于看到要执行的下一行,然后看到可靠的旧(gdb)提示符。但是没有这样的事情。我们没有该二进制和符号的来源。 h!因此,我们考虑在(gdb)提示符下闪烁的插入符号,并想知道该怎么做。不用担心,GDB也可以处理汇编代码。唯一的问题,在我看来,它默认为不方便的AT&T汇编语法。我更喜欢Intel风格,并且以下命令告诉GDB做到这一点:

(gdb) set disassembly-flavor intel


显示汇编代码

怎么样给我们看汇编代码?好吧,类似于TUI模式(使用gdb的标签Wiki,请使用以下命令):

>
(gdb) layout asm


还将在概述中向您显示寄存器的内容。

让我们再次运行它

所以我们出于我们的目的而得到以下.gdbinit

(gdb) layout regs


当我们在不带参数的情况下启动gdb时,我们得到的结果是:



甜。这样我们就可以在遍历代码时看到反汇编。我们可以在这里得出结论,但是当然还有更多的技巧要学习,所以为什么不走得更远呢。当我们刚启动程序时,它不是太有意义,但是在以后逐步执行代码时可能很有用。

顺便说一句,如果您希望节省屏幕资源

...并且视觉效果较差,那么从GDB 7.0开始,您可以使用:

file exe
break *0x400710
set disassembly-flavor intel
layout asm
layout regs
run argument1 argument2


在GDB版本之前,您可以通过设置自动display来模仿行为:

set disassemble-next-line on


或更短的disp/i $pc其中/i是格式,您可以通过思考来最好地记住它“指令”和$pc是指令指针,也称为程序计数器,因此也称为pc

也很容易理解

有时在逐步组装regsasm视图时​​,感到无聊。只需再次执行相应的layout命令,即可将它们恢复到以前的状态:

display/i $pc


在组装级别的“调试”

在汇编模式下,您习惯于从源代码级调试中使用的某些命令根本无法使用。这是有道理的,因为单个源代码行通常意味着十几条指令或更多指令。但是,nextstep命令具有指令级的对应项:



nexti(简写ni ...其他想到灌木丛的人吗?)

stepi(简写为si

从上面的反汇编中我们知道:

(gdb) layout asm
(gdb) layout regs


,实际上,这是main函数。当然,如果您要对恶意软件进行反向工程,则应格外小心,但在这种情况下,请务必谨慎。因此,让我们在此地址(0x40f961)上添加一个断点,而不是在入口点上添加一个断点:可以看到:

0x40072d        mov    rdi,0x40f961


好吧,我们要关注examine,所以让我们使用x进入它。进入函数时,我们会在指令指针处立即看到另一个call

break *0x40f961


si将我们引向一个调用call的函数,现在为什么要这么做呢?

(gdb) x/5i $pc
x/5i $pc
=> 0x40f961:    push   rbp
   0x40f962:    mov    rbp,rsp
   0x40f965:    mov    eax,0x0
   0x40f96a:    call   0x40911f
   0x40f96f:    pop    rbp


好吧,这是Mellowcandle在另一个反调试器技巧中描述的在这里进行问答:


在Linux中检测跟踪

但是我们如何解决呢?我们必须将call覆盖为使用ptrace(PTRACE_TRACEME, ...)或类似的代码调用call的函数。

这就是GDB有点笨拙的地方。但是我们可以使用ptrace(),为我们做魔术。让我们先检查指令字节:

(gdb) x/5i $pc
x/5i $pc
=> 0x40911f:    call   0x400b8c
   0x409124:    push   rbp
   0x409125:    mov    rbp,rsp
   0x409128:    push   r10
   0x40912a:    push   r11


nop是一个调用指令,我们现在知道它的长度为5个字节。因此,让我们对此进行set。 (0xe8表示在程序计数器处检查10个字节-默认格式已为十六进制)。

所以我们在nop处停止时做:

0x400bab        call   0x4006b8 <ptrace@plt>


并验证修补位置:

(gdb) x/10b $pc
x/10b $pc
0x40911f:       0xe8    0x68    0x7a    0xff    0xff    0x55    0x48    0x89
0x409127:       0xe5    0x41


非常好。我们现在可以执行它。

给定方法的替代方法


修补的替代方法:x/10b $pc后跟0x40911f

操作程序计数器(指令指针):



set {unsigned int}0x40911f = 0x90909090或更明确的set {unsigned char}0x409123 = 0x90

set $pc+=5



操纵/修补正在运行的程序的更好的方法

Tavis Ormandy还有其他类似的方法(也是一种更好的方法)。我正在复制下面的set $pc=$pc+5宏(以防它从另一个地方脱机):

(gdb) set write
(gdb) set {unsigned int}$pc = 0x90909090
(gdb) set {unsigned char}($pc+4) = 0x90
(gdb) set write off


同样,上面的脚本片段不是我写的,但是Tavis Ormandy撰写-请参见上面的链接。

这个小小的问答结束了。

评论


很棒的文章!只是一个注意事项:-q作为cmd arg摆脱了版权内容

–0xea
13年4月27日在8:02

尽管我非常了解gdb,但我学到了很多东西。谢谢 !

–恐怖
13年4月27日在9:16



这个真棒答案不只一次挽救了我的性命。谢谢,再次感谢。

– Hackndo
2015年9月7日在8:00

我见过的最好的gdb介绍。

– RichieHH
11月21日6:47