ptrace не показывает то же самое, что objdump - PullRequest
1 голос
/ 30 апреля 2020

Я пишу C программу, которая показывает инструкции с использованием ptrace. Это код:

#include<stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <string.h>

void run_target()
{
    ptrace(PTRACE_TRACEME, 0, 0, 0);
    execl("./test", "test", NULL);
}

void debugger(pid_t pid)
{
    int status;
    wait(&status);

    while(WIFSTOPPED(status))
    {
        struct user_regs_struct regs;
        ptrace(PTRACE_GETREGS, pid, 0, &regs);
        long instruction = ptrace(PTRACE_PEEKTEXT, pid, regs.rip, 0);
        ptrace(PTRACE_SINGLESTEP, pid, 0, 0);

        //EDITED SO IT PRINTS ONLY LINES I WANT
        if(((regs.rip >> (8*5)) & 0xFF) != 0x7f)    //i noticed all the junk lines that shouldnt be there, their regs.rip began with 7f
            printf("%llx %16lx\n", regs.rip, instruction);

        wait(&status);
    }
}

int main()
{
    pid_t pid;
    pid = fork();

    if(pid == 0)
    {
        run_target();
    }
    else
    {
        debugger(pid);
    }

    return 0;
}

Но вывод выглядит так

...
7f3bf1487308 8348da7426fa8348
7f3bf148730c d47408fa8348da74
7f3bf148730e 8d48d47408fa8348
7f3bf1487312 16ad50d8d48d474
7f3bf14872e8 18c0834810508b48
7f3bf14872ec 48f2014c18c08348
7f3bf14872f0 8948c33948f2014c
7f3bf14872f3 860f118948c33948
7f3bf14872f6 fff670860f118948
7f3bf14872f9 508bfffff670860f

И objdump -d выглядит так:

000000000000064a <main>:
64a:    55                      push   %rbp
64b:    48 89 e5                mov    %rsp,%rbp
64e:    48 8d 3d af 00 00 00    lea    0xaf(%rip),%rdi        # 704 <_IO_stdin_used+0x4>
655:    b8 00 00 00 00          mov    $0x0,%eax
65a:    e8 c1 fe ff ff          callq  520 <printf@plt>
65f:    48 8d 3d a5 00 00 00    lea    0xa5(%rip),%rdi        # 70b <_IO_stdin_used+0xb>
666:    b8 00 00 00 00          mov    $0x0,%eax
66b:    e8 b0 fe ff ff          callq  520 <printf@plt>
670:    b8 00 00 00 00          mov    $0x0,%eax
675:    5d                      pop    %rbp
676:    c3                      retq   
677:    66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
67e:    00 00 

И работает * Например, 1010 * показывает это

7f46f3bb64e2 89483e8b48f90148
7f46f3bb64e5 8b483989483e8b48
7f46f3bb64e8 48087e8b48398948
7f46f3bb64eb 48c70148087e8b48
7f46f3bb64ef 4808798948c70148
7f46f3bb7318 f64ee8ef894c9174
7f46f3bb731a f64ee8ef894c
7f46f3bb731d 4887eb0000f64ee8
7f46f3800b70 4864e889481f8948
7f46f3800b73 2504334864e88948
55dfb208464e b8000000af3d8d48
7f46f38564e2 89440af883481476
7f46f38f2a11 4334864e8458b48
7f46f380505a e264e8ff894c0874

, причем это действительно правильно:

55dfb208464e b8000000af3d8d48

Итак, что-то отсутствует в objdump или что-то отсутствует в моем коде, который он показывает больше разрыва, чем должно быть?

РЕДАКТИРОВАТЬ: добавлен весь код

РЕДАКТИРОВАТЬ: отредактировал printf, теперь он печатает строки, которые он должен, но все равно добавляет случайный гекс в начало, что это должно быть

64a: 55

что это такое

56160a60a64a 9f3d8d48e5894855

Я понимаю, почему инструкция не совпадает, это НЕ моя проблема, моя проблема в том, почему regs.rip не 64a, но это 64а. И случайный гекс в начале каждый раз, когда я перезапускаю программу, меняется.

1 Ответ

2 голосов
/ 01 мая 2020

Ваша программа работает, вам просто нужно посмотреть в нужном месте в правильном порядке в вашем результате.

Имея это определение теста:

#include <stdio.h>
int main()
{
  puts("hello");
  puts("world");
  return 0;
}

objdump -d test производит среди других вещи:

0000000000400526 <main>:
  400526:   55                      push   %rbp
  400527:   48 89 e5                mov    %rsp,%rbp
  40052a:   bf d4 05 40 00          mov    $0x4005d4,%edi
  40052f:   e8 cc fe ff ff          callq  400400 <puts@plt>
  400534:   bf da 05 40 00          mov    $0x4005da,%edi
  400539:   e8 c2 fe ff ff          callq  400400 <puts@plt>
  40053e:   b8 00 00 00 00          mov    $0x0,%eax
  400543:   5d                      pop    %rbp
  400544:   c3                      retq   
  400545:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  40054c:   00 00 00 
  40054f:   90                      nop

если я запустил вашу программу среди результатов, я нашел код с теми же адресами objdump и теми же кодами инструкций, поэтому init section et c затем main (из строки 81208!):

400526 4005d4bfe5894855
400527   4005d4bfe58948
40052a fecce8004005d4bf
40052f  5dabffffffecce8
400400   6800200c1225ff
400406 ffe0e90000000068
40040b  a25ffffffffe0e9
4003f0 25ff00200c1235ff
4003f6 1f0f00200c1425ff
...

Я запускаю на Intel i7, байты инструкции должны читаться в обратном порядке, чем показано objdump , например, первая инструкция 55 , затем 48 89 e5 , затем bf d4 05 40 00 , затем e8 cc fe ff ff et c.

Конечно, в вашем случае это похоже на «шаг», а не «следующий» в отладчике, поэтому на callq * От 1032 * до ставит , введенные вами в ставит , а objdump разбирает. До адреса 4005234 имеется 60084 строки после адреса 400534, поэтому для выполнения команды puts("hello"); необходимо 60084 инструкции!


Конечно, можно печатать только с начала main , например, с ленивым способом, используя objdump :

void debugger(pid_t pid)
{
  FILE * fp = popen("objdump -d ./test | grep \"<main>:\"", "r");

  if (fp == NULL) {
    puts("cannot get main address");
  }
  else {
    char line[256];

    if (fgets(line, sizeof(line), fp) == NULL) {
      puts("no address !");
      pclose(fp);
    }
    else {
      unsigned long long main_addr;

      pclose(fp);
      errno = 0;
      main_addr = strtoull(line, NULL, 16);

      if (errno != 0)
        puts("invalid address");
      else {
        int found = 0;
        int status;

        while(wait(&status), WIFSTOPPED(status))
        {
          struct user_regs_struct regs;

          ptrace(PTRACE_GETREGS, pid, 0, &regs);

          if (found |= (regs.rip == main_addr)) {
            long instruction = ptrace(PTRACE_PEEKTEXT, pid, regs.rip, 0);

            printf("%llx %16lx\n", regs.rip, instruction);
          }

          ptrace(PTRACE_SINGLESTEP, pid, 0, 0);
        }
      }
    }
  }
}

[редактировать из вашего замечания]

ptrace не знает, хотите ли вы прочитать данные или инструкции, если вы хотите отделить каждую инструкцию как objdump вы должны знать код каждой и соответствующей длины, иначе вы можете использовать разницу адресов от одного к другому, даже если это не работает в случае вызова / jmp / условного jmp / return:

void debugger(pid_t pid)
{
  FILE * fp = popen("objdump -d ./test | grep \"<main>:\"", "r");

  if (fp == NULL) {
    puts("cannot get main address");
  }
  else {
    char line[256];

    if (fgets(line, sizeof(line), fp) == NULL) {
      puts("no address !");
      pclose(fp);
    }
    else {
      unsigned long long main_addr;

      pclose(fp);
      errno = 0;
      main_addr = strtoull(line, NULL, 16);

      if (errno != 0)
        puts("invalid address");
      else {
        int found = 0;
        int status;
        unsigned long prev_instr;
        long long prev_addr = -1;

        while(wait(&status), WIFSTOPPED(status))
        {
          struct user_regs_struct regs;

          ptrace(PTRACE_GETREGS, pid, 0, &regs);

          if (found |= (regs.rip == main_addr)) {
            unsigned long instruction =
              (unsigned long) ptrace(PTRACE_PEEKTEXT, pid, regs.rip, 0);

            if (prev_addr != -1) {
              /* longest instruction has 15 bytes on x86 */
              int len = ((regs.rip > prev_addr) && ((regs.rip - prev_addr) <= 15))
                ? regs.rip - prev_addr : 100;

              printf("%llx ", prev_addr);
              while (prev_instr && len--) {
                printf("%02x ", (unsigned) (prev_instr & 0xff));
                prev_instr /= 256;
              }
              if (len > 15)
                puts(" (?)");
              else
                putchar('\n');
            }
            prev_instr = instruction;
            prev_addr = regs.rip;
          }

          ptrace(PTRACE_SINGLESTEP, pid, 0, 0);
        }
      }
    }
  }
}

теперь вывод:

400526 55 
400527 48 89 e5 
40052a bf d4 05 40 00 
40052f e8 cc fe ff ff bf da 05  (?)
400400 ff 25 12 0c 20 00 
400406 68 00 00 00 00 
40040b e9 e0 ff ff ff ff 25 0a  (?)
4003f0 ff 35 12 0c 20 00 
4003f6 ff 25 14 0c 20 00 0f 1f  (?)
7fe20cc1fe10 53 
7fe20cc1fe11 48 89 e3 
7fe20cc1fe14 48 83 e4 c0 
7fe20cc1fe18 48 2b 25 31 df 20 00 
7fe20cc1fe1f 48 89 04 24 
7fe20cc1fe23 48 89 4c 24 08 
...

, чтобы сделать больше, вы должны знать код и длину каждого вызова инструкции / jmp / условное jmp / return. Заметьте, что код операции может быть на 1 или 2 байта.

...