如何防止堆栈回溯信息?

时间:2014-03-27 10:17:47

标签: c gdb

想象一下,你有一些代码,编译它,并删除所有符号。如果你在它上运行gdb,在任意点停止它,并回溯它,你仍然会得到有意义的信息

int baz() {
    sleep(10);
    return 0;
}
int bar() {
    return baz();
}
int foo() {
    return bar();
}
int main() {
    return foo();
}

$ gcc -fomit-frame-pointer test.c 
$ strip -x a.out 
$ gdb ./a.out 


#0  0x0000003a4989a470 in __nanosleep_nocancel () from /lib64/libc.so.6
#1  0x0000003a4989a2c4 in sleep () from /lib64/libc.so.6
#2  0x00000000004004b7 in baz ()
#3  0x00000000004004cf in bar ()
#4  0x00000000004004e2 in foo ()
#5  0x00000000004004f5 in main ()

假设您想阻止用户从baz到main查看(或混淆)。可能吗?欢迎硬核方法。

3 个答案:

答案 0 :(得分:4)

如果您希望函数能够返回,则无法阻止堆栈跟踪。

如果不需要返回,则覆盖返回地址和帧指针将阻止堆栈跟踪

int bar() {
    int nptrs; void * buffer[100];
    nptrs = backtrace(buffer, 100);
    backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO);

    // this is machine and compiler specific
    // the following works for most i386 compilant compilers that create frame pointers
    void ** stack_base_pointer;
    __asm__ ("mov %%rbp, %0" : "=g" (stack_base_pointer));

    // we will save the original values
    void * return_address = stack_base_pointer[1];
    void * frame_pointer = stack_base_pointer[0];
    // overwrite them
    stack_base_pointer[1] = NULL;
    stack_base_pointer[0] = NULL;
    // this function can not return now

    printf("return_address = %p\nframe pointer = %p\n", return_address, frame_pointer);
    puts("Removed return address and frame pointer");

    nptrs = backtrace(buffer, 100);
    backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO);

    // we need to restore the return address in order to be able to return
    stack_base_pointer[1] = return_address;

    return 0;
}

int foo() {
    puts("calling bar");
    bar();
    puts("successfully returned from bar");
    return 0;
}

int main() {
    foo();
    return 0;
}

但请注意,这将特定于您正在编译的编译器和目标系统。

在使用Apple LLVM version 5.1 (clang-503.0.38) (based on LLVM 3.4svn)

编译的OS X上
$ ./a.out
calling bar
0   a.out                               0x000000010e23cd7f bar + 31
1   a.out                               0x000000010e23ce48 foo + 26
2   a.out                               0x000000010e23ce69 main + 14
3   libdyld.dylib                       0x00007fff905a35fd start + 1
return_address = 0x10e23ce48
frame pointer = 0x7fff519c3b90
Removed return address and frame pointer
0   a.out                               0x000000010e23cdff bar + 159
successfully returned from bar

答案 1 :(得分:2)

正如M. Waltz在评论中已经说过的那样。

从条形手册页:

   -x
   --discard-all
       Remove non-global symbols.

产生的回溯:__ nanosleep_nocancel,__sleep,baz,bar,foo,main

   -s
   --strip-all
       Remove all symbols.

产生的回溯:__ nanosleep_nocancel,_ _ snle,?? (),?? (),?? (),?? ()

你真的想使用-s,而不是-x。 -x仅删除非全局符号,对于C(例如),这意味着所有非静态函数仍将在符号表中具有条目,并且在回溯中可见(您可以使用readelf -a a.out进行验证)。

如果你还想隐藏回溯的地址,其中一些将通过使用更高级别的优化来完成,因为函数被内联等等。对于“真正的硬核”,你可以把编译器和ABI搞得一团糟,所以通用工具无法读取,但这可能太过分了。

答案 2 :(得分:1)

  

假设您想阻止用户从baz到main查看(或混淆)。可能吗?欢迎硬核方法。

在Linux上似乎可以隐藏你的一些功能。使用共享库并移动除main之外的所有函数。隐藏您不想被人看到的功能:

>more mylib.c
__attribute__ ((visibility ("hidden"))) int baz() {
  sleep(10);
  return 0;
}
__attribute__ ((visibility ("hidden"))) int bar() {
  return baz();
}
int foo() {
  return bar();
}

Build&条:

gcc -fomit-frame-pointer -fno-inline -fpic -shared mylib.c -o libmylib.so
strip -x libmylib.so

并运行:

>gdb -q a.out
Reading symbols from /home/a.out...(no debugging symbols found)...done.
(gdb) r
Starting program: /home/a.out
^C
Program received signal SIGINT, Interrupt.
0x0000003c412ab900 in __nanosleep_nocancel () from /lib64/libc.so.6
(gdb) bt
#0  0x0000003c412ab900 in __nanosleep_nocancel () from /lib64/libc.so.6
#1  0x0000003c412ab790 in sleep () from /lib64/libc.so.6
#2  0x00007ffff7dfc537 in ?? () from libmylib.so
#3  0x00007ffff7dfc54f in ?? () from libmylib.so
#4  0x00007ffff7dfc562 in foo () from libmylib.so
#5  0x00000000004005ba in main ()
(gdb)