GDB无法插入断点,无法访问地址XXX的内存?

18

我写了一个非常简单的程序:

ebrahim@ebrahim:~/test$ cat main.c
int main() {
    int i = 0;
    return i;
}

然后我使用-s参数进行编译,以进入stripped模式:

ebrahim@ebrahim:~/test$ gcc -s main.c -o f3
ebrahim@ebrahim:~/test$ file f3
f3: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=4dc6b893fbae8b418ca41ddeef948df1fcb26d3d, stripped

现在,我正在尝试使用GDB查找主函数的起始地址:

ebrahim@ebrahim:~/test$ gdb -nh f3
GNU gdb (Ubuntu 7.11.90.20161005-0ubuntu2) 7.11.90.20161005-git
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from f3...(no debugging symbols found)...done.

由于文件内没有 符号 信息,我需要在文件入口点处打一个断点,然后反汇编它并查找 main 函数的起始地址。因此,我使用了 info file 命令来查找文件的 入口点 地址:

(gdb) info file
Symbols from "/home/ebrahim/test/f3".
Local exec file:
    `/home/ebrahim/test/f3', file type elf64-x86-64.
    Entry point: 0x530     <<<<=============
    0x0000000000000238 - 0x0000000000000254 is .interp
    0x0000000000000254 - 0x0000000000000274 is .note.ABI-tag
    0x0000000000000274 - 0x0000000000000298 is .note.gnu.build-id
    0x0000000000000298 - 0x00000000000002b4 is .gnu.hash
    0x00000000000002b8 - 0x0000000000000360 is .dynsym
    0x0000000000000360 - 0x00000000000003f1 is .dynstr
    0x00000000000003f2 - 0x0000000000000400 is .gnu.version
    0x0000000000000400 - 0x0000000000000420 is .gnu.version_r
    0x0000000000000420 - 0x00000000000004f8 is .rela.dyn
    0x00000000000004f8 - 0x000000000000050f is .init
    0x0000000000000510 - 0x0000000000000520 is .plt
    0x0000000000000520 - 0x0000000000000528 is .plt.got
    0x0000000000000530 - 0x00000000000006e2 is .text
    0x00000000000006e4 - 0x00000000000006ed is .fini
    0x00000000000006f0 - 0x00000000000006f4 is .rodata
    0x00000000000006f4 - 0x0000000000000728 is .eh_frame_hdr
    0x0000000000000728 - 0x000000000000081c is .eh_frame
    0x0000000000200de0 - 0x0000000000200de8 is .init_array
    0x0000000000200de8 - 0x0000000000200df0 is .fini_array
    0x0000000000200df0 - 0x0000000000200df8 is .jcr
    0x0000000000200df8 - 0x0000000000200fb8 is .dynamic
    0x0000000000200fb8 - 0x0000000000201000 is .got
    0x0000000000201000 - 0x0000000000201010 is .data
    0x0000000000201010 - 0x0000000000201018 is .bss

正如我们所预期的那样,入口点是.text段的开头。因此,我在这个地址上设置了一个断点:

正如预期的那样,入口点是.text节的开头。因此我在这个地址上设置断点:

(gdb) b *0x0000000000000530
Breakpoint 1 at 0x530
(gdb) r
Starting program: /home/ebrahim/test/f3 
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x530

(gdb)

GDB为什么无法插入此断点?

1
不多说了:这些地址看起来都非常小,所以我假设它们只是重定位偏移量,必须加上相应部分的加载地址才能得到真正的运行时虚拟地址。 - user2371524
1
请注意,入口点可能不是main()。它很可能是您的C运行时的一些启动代码。 - user2371524
2
这可能是一个愚蠢的想法,但如果你要调试你的代码,你应该打开调试信息编译选项,而不是关闭它? - Chris Turner
@FelixPalmen 我怎样才能计算/找到偏移量呢?是的,我知道入口点不是main,但我可以在这个入口点经过一些指令后找到main的起始位置。 - Ebrahim Ghasemi
2
@ChrisTurner 我不是在尝试调试,我是在学习逆向工程。 - Ebrahim Ghasemi
@Abraham,你不能这样做,但是请看我的答案,有一个更简单的替代方案。 - user2371524
2个回答

16

除了逆向工程,调试剥离代码可能非常无用,但是您可以使gdb停在第一条指令处,并且您已经偶然地这样做过。如果断点的地址无法映射,gdb会停止并告诉您错误。作为副作用,您的程序将停在其第一条指令上。保证未映射的地址是0,因此只需按照以下步骤操作:

(gdb) b *0
Breakpoint 1 at 0x0
(gdb) r
Starting program: [...]
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x0

(gdb) disas
Dump of assembler code for function _start:
=> 0x00007ffff7ddd190 <+0>: mov    %rsp,%rdi
   0x00007ffff7ddd193 <+3>: callq  0x7ffff7de0750 <_dl_start>

在这里你可以看到 PC 位于 0x00007ffff7ddd190。所以这是你的运行时入口点。

为了能够继续执行(或者例如单步调试),你必须删除这个有问题的断点:

(gdb) delete
Delete all breakpoints? (y or n) y
(gdb) c
Continuing.

本答案的功劳归于这篇关于逆向工程的回答


1
这对我没用。break *0x0 仍然导致 cannot set breakpoint 1 cannot access memory at 0x0 -> 我开始相信这与 x86_64 有关.... 我在虚拟机上,内存地址似乎被打乱了。我通过 readelf 双重检查了 .text 部分,地址是正确的... 然而,break main 起作用了 [不确定是否在剥离时仍然有效]。从那里我可以使用 stepi 到函数调用并使用 x/i $pc 显示实际的内存位置。也许这能帮助某人。 - clockw0rk

6
问题在于你试图像调试可执行文件那样调试共享对象。特别是,你的 file 报告如下:

ELF 64-bit LSB shared object

由于它是一个共享对象而不是可执行文件,你可能需要从一个实际程序开始。在这种情况下,你需要将该共享对象文件与另一个自己创建的程序链接起来。例如,我创建了一个简单的共享对象:

snoot.c

#include <stdio.h>

int square(int test) {
    return test*test;
}

int func() {
    n = 7;
    printf("The answer is %d\n", square(n)-5);
}

编译

gcc -shared -fpic snoot.c -o libsnoot.so
strip libsnoot.so

现在我们已经有了您剥离后的共享库的等效版本。如果我们运行 objdump -T libsnoot.so,我们会得到以下结果:
libsnoot.so:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
0000000000000580 l    d  .init  0000000000000000              .init
0000000000000000  w   D  *UND*  0000000000000000              _ITM_deregisterTMCloneTable
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 printf
0000000000000000  w   D  *UND*  0000000000000000              __gmon_start__
0000000000000000  w   D  *UND*  0000000000000000              _Jv_RegisterClasses
0000000000000000  w   D  *UND*  0000000000000000              _ITM_registerTMCloneTable
0000000000000000  w   DF *UND*  0000000000000000  GLIBC_2.2.5 __cxa_finalize
0000000000201028 g    D  .got.plt   0000000000000000  Base        _edata
00000000000006e0 g    DF .text  0000000000000010  Base        square
0000000000201030 g    D  .bss   0000000000000000  Base        _end
0000000000201028 g    D  .bss   0000000000000000  Base        __bss_start
0000000000000580 g    DF .init  0000000000000000  Base        _init
0000000000000724 g    DF .fini  0000000000000000  Base        _fini
00000000000006f0 g    DF .text  0000000000000032  Base        func

.text节中仅有两个符号,即我们定义的两个函数。不幸的是,没有通用的方法来确定如何调用这些函数(即没有办法恢复原始的C函数原型),但是我们可以简单地猜测。如果我们猜错了,堆栈会出问题。例如,让我们尝试使用以下程序链接到square

testsnoot.c

extern void square(void);

int main() {
    square();
}

假设so文件与当前目录在同一个路径下,我们可以按以下方式进行编译和链接:
gcc testsnoot.c -o testsnoot -L. -lsnoot

现在我们可以正常调试,因为这个测试驱动程序在我们的控制之下:

LD_LIBRARY_PATH="." gdb ./testsnoot

请注意,我们需要设置LD_LIBRARY_PATH,否则我们正在使用的库将不会被加载,执行将终止。
(gdb) b square
Breakpoint 1 at 0x400560
(gdb) r
Starting program: /home/edward/test/testsnoot 
Missing separate debuginfos, use: dnf debuginfo-install glibc-2.24-4.fc25.x86_64

Breakpoint 1, 0x00007ffff7bd56e4 in square () from ./libsnoot.so
(gdb) x/20i $pc
=> 0x7ffff7bd56e4 <square+4>:   mov    %edi,-0x4(%rbp)
   0x7ffff7bd56e7 <square+7>:   mov    -0x4(%rbp),%eax
   0x7ffff7bd56ea <square+10>:  imul   -0x4(%rbp),%eax
   0x7ffff7bd56ee <square+14>:  pop    %rbp
   0x7ffff7bd56ef <square+15>:  retq   
   0x7ffff7bd56f0 <func>:   push   %rbp
   0x7ffff7bd56f1 <func+1>: mov    %rsp,%rbp
   0x7ffff7bd56f4 <func+4>: sub    $0x10,%rsp
   0x7ffff7bd56f8 <func+8>: movl   $0x7,-0x4(%rbp)
   0x7ffff7bd56ff <func+15>:    mov    -0x4(%rbp),%eax
   0x7ffff7bd5702 <func+18>:    mov    %eax,%edi
   0x7ffff7bd5704 <func+20>:    callq  0x7ffff7bd55b0 <square@plt>
   0x7ffff7bd5709 <func+25>:    sub    $0x5,%eax
   0x7ffff7bd570c <func+28>:    mov    %eax,%esi
   0x7ffff7bd570e <func+30>:    lea    0x18(%rip),%rdi        # 0x7ffff7bd572d
   0x7ffff7bd5715 <func+37>:    mov    $0x0,%eax
   0x7ffff7bd571a <func+42>:    callq  0x7ffff7bd55c0 <printf@plt>
   0x7ffff7bd571f <func+47>:    nop
   0x7ffff7bd5720 <func+48>:    leaveq 
   0x7ffff7bd5721 <func+49>:    retq   

现在您可以查看函数的反汇编并了解它正在做什么。在这种情况下,由于我们看到有一个对-0x4(%rbp)的引用,因此很明显这个函数实际上期望一个参数,尽管我们不知道具体是什么类型。
我们可以重写测试驱动函数,并逐步接近调试,直到我们了解剥离库正在做什么。
我假设您可以继续进行,既然我已经展示了一般过程。

所以,重新阅读问题后:你是对的,但我仍然不明白这一点:是否有一种方法可以在没有选项“-shared”的情况下让gcc生成共享对象? - user2371524
我不知道,但那是完全不同的问题。 - Edward
再仔细想一想,是的,这是可以做到的。如果库包含一个合适的 main 函数,它可以被编译为 gcc -fPIC -pie snoot.c -o libsnoot.so -Wl,-E,以创建一个双重可执行文件/共享库。 - Edward
您还可以使用 gcc -v 命令来查看您的特定 gcc 配置。 - Edward

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接