【问题标题】:Trapping malloc in ptrace在 ptrace 中捕获 malloc
【发布时间】:2014-09-14 02:02:30
【问题描述】:

我试图在 ptrace 内部发生 malloc 时进行陷阱。

我已经能够在调用 malloc 时进行挂钩,所以我应该能够通过一些自定义模块capture;但是,那是在使用动态库时(使用 -static 标志)。

有没有一种方法可以让我以通用的方式做到这一点?

如果我们查看以下程序集,我知道我需要捕获的位置。我只是不知道如何:

  .file "new.c"
  .section  .rodata
.LC0:
  .string "Hello World"
  .text
  .globl  main
  .type main, @function
main:
.LFB2:
  .cfi_startproc
  pushq %rbp
  .cfi_def_cfa_offset 16
  .cfi_offset 6, -16
  movq  %rsp, %rbp
  .cfi_def_cfa_register 6
  subq  $16, %rsp
  movl  $4, %edi
  call  malloc ;<= TRAP HERE
  movq  %rax, -8(%rbp)
  movl  $.LC0, %edi
  call  puts
  movq  -8(%rbp), %rax
  movq  %rax, %rdi
  call  free
  leave
  .cfi_def_cfa 7, 8
  ret
  .cfi_endproc
.LFE2:
  .size main, .-main
  .ident  "GCC: (SUSE Linux) 4.8.1 20130909 [gcc-4_8-branch revision 202388]"
  .section  .note.GNU-stack,"",@progbits

来自ptrace(2)

PTRACE_SINGLESTEP

像 PTRACE_CONT 一样重新启动停止的跟踪对象,但安排跟踪对象分别在系统调用的下一个入口或退出时停止,或在执行单个指令后停止。 (被跟踪者也会像往常一样在收到信号后停止。)`

所以我相当肯定我需要那个选项。从我读过的tutorial,我可以单步执行;但是,没有任何输出有任何意义。特别是如果我有某种输出语句。这是有输出时的简短输出:

RIP: 7ff6cc4387c2 Instruction executed: 63158b48c35d5e41
RIP: 7ff6cc4387c4 Instruction executed: 2f0663158b48c35d
RIP: 7ff6cc4387c5 Instruction executed: 2f0663158b48c3
RIP: 400c38 Instruction executed: 7500e87d83e84589
RIP: 400c3b Instruction executed: b93c7500e87d83
RIP: 400c3f Instruction executed: ba00000000b93c75
RIP: 400c41 Instruction executed: ba00000000b9
RIP: 400c46 Instruction executed: be00000000ba
RIP: 400c4b Instruction executed: bf00000000be
RIP: 400c50 Instruction executed: b800000000bf
RIP: 400c55 Instruction executed: fe61e800000000b8
RIP: 400c5a Instruction executed: bafffffe61e8
RIP: 400ac0 Instruction executed: a68002015a225ff
RIP: 400ac6 Instruction executed: ff40e90000000a68
RIP: 400acb Instruction executed: 9a25ffffffff40e9
RIP: 400a10 Instruction executed: 25ff002015f235ff
RIP: 400a16 Instruction executed: 1f0f002015f425ff
RIP: 7ff6ccf6c160 Instruction executed: 2404894838ec8348
RIP: 7ff6ccf6c164 Instruction executed: 244c894824048948
RIP: 7ff6ccf6c168 Instruction executed: 54894808244c8948
RIP: 7ff6ccf6c16d Instruction executed: 7489481024548948
....
Hello world
....

为什么 IP 的价值会发生如此剧烈的变化?这是因为我事先处于内核模式吗?

此外,执行指令的输出似乎没有正确排列(就像它被分割成多行一样),但这可能只是我试图在没有的地方放置一个模式。

无论如何,这是我运行到该输出的程序: 警告,讨厌的 C\C++ 混合

#include <iostream>
#include <sys/ptrace.h>
#include <unistd.h>
#include <asm/unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/reg.h>
#include <sys/user.h>

#include <iomanip>

using namespace std;

///for when dealing with different archectures.
#if __WORDSIZE == 64
#define REG(reg) reg.orig_rax
#else
#define REG(reg) reg.orig_eax
#endif

int main()
{
  pid_t child;
  long orig_eax;
  const int long_size = sizeof(long);

  child = fork();

  long ins;
  if(child == 0)
  {
    ptrace(PTRACE_TRACEME, 0, NULL, NULL);
    execl("./dummy", "dummy", NULL);
  }
  else
  {
    ptrace(PTRACE_ATTACH, child, NULL, NULL);
    ptrace(PTRACE_SYSCALL, child, NULL, NULL);
    int status;
    union u {
      long val;
      char chars[long_size];
    }data;
    struct user_regs_struct regs;
    int start = 0;
    long ins;
    while(1)
    {
      wait(&status);
      if(WIFEXITED(status))
        break;
      ptrace(PTRACE_GETREGS,child, NULL, &regs);
      ins = ptrace(PTRACE_PEEKTEXT, child, regs.rip, NULL);
      cout << "RIP: " << hex << regs.rip << " Instruction executed: " << ins << endl;
      ptrace(PTRACE_SINGLESTEP, child, NULL, NULL);
    }
    ptrace(PTRACE_DETACH, child, NULL, NULL);
  }
}

如果需要任何其他信息,请告诉我。我知道我有点冗长,但如果这个问题得到回答,希望它会为下一个尝试学习 ptrace 的人提供足够的信息。

【问题讨论】:

  • malloc(3) 是 C 库的函数而不是系统调用。
  • @BSH 正确,这是否意味着我不能用 ptrace 捕获它?编辑:重新阅读手册页,它没有指定它只适用于系统调用,这可能就是为什么有单步功能以及 peek 和 poke 的原因。
  • 单步执行时,程序执行完每条指令后都会停止。但是,当进程处于内核模式时,不会发生这种跟踪(单步执行)。这就是文档仅在系统调用进入和退出时停止的意思。请注意,系统调用有一个小存根,它在切换到内核模式之前在用户模式下执行,因此将跟踪这个存根中的指令。你想通过挂钩 malloc 来完成什么?
  • @RossRidge 挂钩 malloc 时,我可以(理论上)让新的 malloc 进行虚假的系统调用,该调用会被跟踪器捕获。问题是,如果在编译时使用了-static 标志,那么我将无法判断是否\何时调用了 malloc。
  • 为什么要让malloc 被跟踪器捕获?为什么你想知道malloc 是否或何时被调用?

标签: c assembly malloc glibc ptrace


【解决方案1】:

没有实用的方法来挂钩malloc,它适用于所有静态链接的可执行文件。为了钩住它,无论如何,你需要知道它的地址。您可以做到这一点的唯一方法是在可执行文件的符号表中查找malloc,但由于它是静态链接的,因此不能保证它有一个。动态库必须有符号表才能动态链接,但由于静态链接的程序是完全链接的,因此不需要。

也就是说,许多静态链接的可执行文件都有一个符号表,因为如果没有符号表,调试几乎是不可能的。它们占用的额外尺寸不再是什么大问题。您可以使用nm 命令检查您可能希望与您的应用程序一起使用的任何可执行文件,以了解此问题可能对您产生何种影响。

假设您有一个带有符号的可执行文件,下一个问题是如何实际读取程序中的符号。 ELF 格式并不是那么简单,因此您可能想要使用 BFD(来自 binutils)或 libelf 之类的东西。您也可以在命令行中使用 nm 并手动为您的问题提供地址。

一旦您有了malloc 的地址,您就可以通过在函数开始处设置断点来使用ptrace 跟踪对其的调用。设置断点很简单。只需使用PTRACE_PEEKTEXT 读取函数的第一个字节,将其保存在某处,然后使用PTRACE_POKETEXT 将字节更改为0xCC,即英特尔x86 断点指令的操作码(INT 3)。然后当malloc被调用时,被跟踪的进程将被发送一个SIGTRAP信号,你可以拦截它。

那么你需要做的事情就更复杂了。您需要执行如下一系列步骤:

  1. 读取寄存器和/或堆栈以找到malloc 的参数并记录它们。
  2. 使用PTRACE_POKETEXT恢复函数原来的第一个字节。
  3. 从栈顶读取返回地址
  4. malloc 将返回的位置设置断点,保存旧值。
  5. 从程序计数器 (EIP/RIP) 中减去 1(断点指令的大小)。
  6. 继续运行被跟踪的进程。您拦截的下一个SIGTRAP 将在malloc 返回之后。
  7. 通过读取返回寄存器(EAX/RAX)记录malloc返回的值。
  8. 使用PTRACE_POKETEXT去除返回地址处的断点
  9. 使用PTRACE_POKETEXT 将断点放回到malloc 的开头
  10. 从程序计数器中减去 1。
  11. 继续运行被跟踪的进程。

可能有些事情我还没有想到,但这是你需要做的事情。

如果您只想使用自己编译的代码,那么还有很多更简单的选择,例如使用 glibc 对memory allocation hooks 的内置支持。

【讨论】:

  • 另一个要考虑的选项是valgrind 程序。这些可以分析堆使用情况以及堆滥用。
  • @JonathanLeffler 我想看看 valgrind 的源代码,看看它是如何检测内存泄漏的。
  • 让我阅读一些手册页并玩一会儿。我想这可能是答案。你不会碰巧有一个链接到某个地方让我开始吧?
  • 这个博客条目似乎有一个很好的例子来说明如何做断点:mainisusuallyafunction.blogspot.ca/2011/01/…。注意使用0xcd 0x80 0xcc 作为断点的旧Linux Journal 文章。这没有意义,因为 0xcd 0x80int 0x80 指令,它是旧的 32 位 Linux 系统调用条目。它所要做的就是进行一些随机的系统调用。使用多于一个字节的断点也是危险的,因为它可以修改多条指令。
猜你喜欢
  • 2018-04-10
  • 2013-12-24
  • 1970-01-01
  • 2012-12-18
  • 2019-10-02
  • 1970-01-01
  • 2012-05-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多