c/c++语言开发共享gdb可以使函数指针指向另一个位置吗?

我会解释一下:

假设我有兴趣替换某个应用程序使用的rand()函数。

所以我将gdb附加到此进程并使其加载我的自定义共享库(具有自定义的rand()函数):

 call (int) dlopen("path_to_library/asdf.so") 

这会将自定义的rand()函数放在进程的内存中。 但是,此时符号rand仍将指向默认的rand()函数。 有没有办法让gdb将符号指向新的rand()函数,强制进程使用我的版本?

我必须说我也不允许使用LD_PRELOAD (linux)或DYLD_INSERT_LIBRARIES (mac os x)方法,因为它们只允许在程序执行开始时注入代码。

我想替换rand()的应用程序,启动几个线程,其中一些启动新进程,我有兴趣在其中一个新进程上注入代码。 正如我上面提到的,GDB非常适合这个目的,因为它允许代码注入特定的进程。

    我按照这篇文章和本演示文稿 ,为OSX提供了以下一组带有x86-64可执行文件的gdb命令,在附加到进程时可以使用-x选项加​​载:

     set $s = dyld_stub_rand set $p = ($s+6+*(int*)($s+2)) call (void*)dlsym((void*)dlopen("myrand.dylib"), "my_rand") set *(void**)$p = my_rand c 

    神奇的是在set $p = ...命令中。 dyld_stub_rand是一个6字节的跳转指令。 跳转偏移量为dyld_stub_rand+2 (4个字节)。 这是一个$rip -relative跳转,所以在此时加上$rip偏移量(在指令dyld_stub_rand+6dyld_stub_rand+6 )。

    这指向一个符号表条目,它应该是真正的rand或动态链接器例程来加载它(如果它从未被调用过)。 然后由my_rand替换。

    有时gdb会从libSystem或其他共享库中获取dyld_stub_rand ,如果发生这种情况,请在运行其他命令之前先使用remove-symbol-file卸载它们。

    这个问题引起了我的兴趣,所以我做了一些研究。 您正在寻找的是’ dll注入 ‘。 你编写了一个函数来替换一些库函数,把它放在.so中,并告诉ld预加载你的dll。 我刚试了一下,效果很好! 我意识到这并没有真正回答你关于gdb的问题,但我认为它提供了一个可行的解决方法。

    对于仅限gdb的解决方案,请参阅我的其他解决方案。


     // -*- compile-command: "gcc -Wall -ggdb -o test test.c"; -*- // test.c #include "stdio.h" #include "stdlib.h" int main(int argc, char** argv) { //should print a fairly random number... printf("Super random number: %dn", rand()); return 0; } 

     / -*- compile-command: "gcc -Wall -fPIC -shared my_rand.c -o my_rand.so"; -*- //my_rand.c int rand(void) { return 42; } 

    编译两个文件,然后运行: LD_PRELOAD="./my_rand.so" ./test

    Super random number: 42

    我有一个新的解决方案,基于 原始约束。 (我没有删除我的第一个答案,因为其他人可能觉得它很有用。)

    我一直在进行一系列的研究,我认为它会更有效。

    这有点复杂,非常圆润,但我很确定它会起作用。 我还没有完成的唯一事情是创建jumpq指令代码。 到目前为止,一切都运行良好。

    我不确定如何在正在运行的程序中执行此操作,但也许LD_PRELOAD将适合您。 如果将此环境变量设置为共享对象列表,则运行时加载程序将在过程的早期加载共享对象,并允许其中的函数优先于其他对象。

     LD_PRELOAD=path_to_library/asdf.so path/to/prog 

    在开始此过程之前,您必须执行此操作,但不必重建程序。

    这里的几个答案以及你在答案中链接到的代码注入文章都涵盖了我认为最佳的面向gdb的解决方案的大块,但是没有一个解决方案将它们全部拉到一起或覆盖所有点。 解决方案的代码表达有点长,所以这里是重要步骤的摘要:

    这是一个例子。 我有与以前相同的ab可执行文件,并从以下代码创建了一个inject2.so

     #include  #include  int (*rand__)(void) = NULL; int rand(void) { int result = rand__(); printf("rand invoked! result = %dn", result); return result % 47; } 

    然后我可以将我的Python detour.py命令放在detour.py并进行以下gdb会话:

      (gdb)source detour.py  (gdb)exec-file a  (gdb)设置follow-fork-mode子  (gdb)赶上执行官  Catchpoint 1(exec)  (gdb)运行 启动程序:/ home / llasram / ws / detour / aa:1933263113  a:831502921  [新流程8500]  b:918844931 过程8500正在执行新程序:/ home / llasram / ws / detour / b  [切换到流程8500]   _start()中的Catchpoint 1(exec'd / home / llasram / ws / detour / b),0x00007ffff7ddfaf0    来自/lib64/ld-linux-x86-64.so.2  (gdb)打破主力 断点2在0x4005d0:文件bc,第7行。  (gdb)续 继续。  断点2,main(argc = 1,argv = 0x7ffffffdd68)at bc:7  7 {  (gdb)绕道libc.so.6:rand inject2.so:rand inject2.so:rand__  (gdb)续 继续。 兰德援引了! 结果= 392103444  b:22  程序正常退出。 

    在子进程中,我创建了一个绕过libc.so.6中的rand()函数到inject2.sorand()函数,并在inject2.sorand__变量中存储指向inject2.so的指针,用于原始rand() inject2.so 。 正如预期的那样,注入的代码调用原始代码,显示完整的结果,并返回结果模47。

    由于篇幅,我只是链接到一个包含我的detour命令代码的牧师。 这是一个相当肤浅的实现(特别是在蹦床生成方面),但它应该在很大比例的情况下都能很好地工作。 我在Linux上使用gdb 7.2(最近发布的版本)测试了它,包括32位和64位可执行文件。 我没有在OS X上测试它,但任何差异都应该相对较小。

    对于可执行文件,您可以使用objdump轻松找到存储函数指针的地址。 例如:

     objdump -R /bin/bash | grep write 00000000006db558 R_X86_64_JUMP_SLOT fwrite 00000000006db5a0 R_X86_64_JUMP_SLOT write 

    因此,0x6db5a0是write指针的地址。 如果更改它,写入的调用将被重定向到您选择的函数。 在早期的post中已经介绍了在gdb中加载新库并获取函数指针。 可执行文件和每个库都有自己的指针。 替换仅影响指针已更改的模块。

    对于库,您需要找到库的基址并将其添加到objdump给出的地址。 在Linux中, /proc//maps将其/proc//maps出来。 我不知道具有地址随机化的位置无关的可执行文件是否可行。 在这种情况下, maps信息可能不可用。

    只要您要替换的函数位于共享库中,您就可以通过在PLT上进行戳操作,在运行时(调试期间)将调用重定向到该函数。 这篇文章可能会有所帮助:

    使用ELF PLT感染的共享库调用重定向

    它是从恶意软件修改程序的角度编写的,但更简单的过程适合于调试器中的实时使用。 基本上你只需要在PLT中找到函数的条目,并用要替换它的函数的地址覆盖地址。

    谷歌搜索“PLT”以及“ELF”,“共享库”,“动态链接”,“PIC”等术语可能会找到有关该主题的更多详细信息。

    如果你让preloaded函数理解它正在使用的情况,你仍然可以使用LD_PRELOAD 。这是一个将正常使用rand()的例子,除了在forked进程中总是返回42时。我使用dl例程将标准库的rand()函数加载到函数指针中以供被劫持的rand()

     // -*- compile-command: "gcc -Wall -fPIC -shared my_rand.c -o my_rand.so -ldl"; -*- //my_rand.c #include  #include  #include  int pid = 0; int (*real_rand)(void) = NULL; void f(void) __attribute__ ((constructor)); void f(void) { pid = getpid(); void* dl = dlopen("libc.so.6", RTLD_LAZY); if(dl) { real_rand = dlsym(dl, "rand"); } } int rand(void) { if(pid == getpid() && real_rand) return real_rand(); else return 42; } 

     //test.c #include  #include  #include  #include  int main(int argc, char** argv) { printf("Super random number: %dn", rand()); if(fork()) { printf("original process rand: %dn", rand()); } else { printf("forked process rand: %dn", rand()); } return 0; } 

     jdizzle@pudding:~$ ./test Super random number: 1804289383 original process rand: 846930886 forked process rand: 846930886 jdizzle@pudding:~$ LD_PRELOAD="/lib/ld-linux.so.2 ./my_rand.so" ./test Super random number: 1804289383 original process rand: 846930886 forked process rand: 42 

    我发现这个教程非常有用,到目前为止,它是我设法实现GDB的唯一方法: 运行Linux应用程序的代码注入 : http : //www.codeproject.com/KB/DLL/code_injection.aspx

    Mac上的代码注入也有一个很好的问答 : http : //www.mikeash.com/pyblog/friday-qa-2009-01-30-code-injection.html

    我经常使用代码注入作为模拟自动测试C代码的方法。 如果这就是你所处的那种情况 – 如果你使用GDB只是因为你对父进程不感兴趣,而不是因为你想以交互方式选择感兴趣的进程 – 那么你仍然可以使用LD_PRELOAD来实现您的解决方案。 您注入的代码只需要确定它是在父进程还是子进程中。 有几种方法可以做到这一点,但在Linux上,由于您的子进程处理exec() ,最简单的可能是查看活动的可执行映像。

    我制作了两个可执行文件,一个名为a ,另一个名为b 。 可执行文件a打印两次调用rand()的结果,然后fork() s和exec() s b两次。 可执行文件b打印一次调用rand()的结果。 我使用LD_PRELOAD将以下代码的编译结果注入可执行文件:

     // -*- compile-command: "gcc -D_GNU_SOURCE=1 -Wall -std=gnu99 -O2 -pipe -fPIC -shared -o inject.so inject.c"; -*- #include  #include  #include  #include  #include  #define constructor __attribute__((__constructor__)) typedef int (*rand_t)(void); typedef enum { UNKNOWN, PARENT, CHILD } state_t; state_t state = UNKNOWN; rand_t rand__ = NULL; state_t determine_state(void) { pid_t pid = getpid(); char linkpath[PATH_MAX] = { 0, }; char exepath[PATH_MAX] = { 0, }; ssize_t exesz = 0; snprintf(linkpath, PATH_MAX, "/proc/%d/exe", pid); exesz = readlink(linkpath, exepath, PATH_MAX); if (exesz < 0) return UNKNOWN; switch (exepath[exesz - 1]) { case 'a': return PARENT; case 'b': return CHILD; } return UNKNOWN; } int rand(void) { if (state == CHILD) return 47; return rand__(); } constructor static void inject_init(void) { rand__ = dlsym(RTLD_NEXT, "rand"); state = determine_state(); } 

    运行有无注射的结果:

     $ ./a a: 644034683 a: 2011954203 b: 375870504 b: 1222326746 $ LD_PRELOAD=$PWD/inject.so ./a a: 1023059566 a: 986551064 b: 47 b: 47 

    我稍后会发布一个面向gdb的解决方案。

      以上就是c/c++开发分享gdb可以使函数指针指向另一个位置吗?相关内容,想了解更多C/C++开发(异常处理)及C/C++游戏开发关注计算机技术网(www.ctvol.com)!)。

      本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

      ctvol管理联系方式QQ:251552304

      本文章地址:https://www.ctvol.com/c-cdevelopment/517874.html

      (0)
      上一篇 2020年12月9日
      下一篇 2020年12月9日

      精彩推荐