Я пытаюсь динамически найти номер функции, вызываемой и возвращаемой программой во время выполнения в x86_64 (синтаксис intel).
Для этого я использую ptrace (без PTRACE_SYSCALL), и я проверяю регистратор RIP (который содержит следующий адрес инструкции), и я проверяю его код операции. Я знаю, что функция CALL может быть найдена, если LSB равен 0xE8 (согласно документации Intel или http://icube -avr.unistra.fr / fr / images / 4/41 / 253666.pdf page 105).
Я нашел каждую инструкцию в http://ref.x86asm.net/coder64.html, Так что в моей программе каждый раз, когда я находил 0xE8, 0x9A, 0xF1 и т. Д. ... я находил запись функции (инструкция CALL или INT), и если это 0xC2 , 0XC3 и т. Д. ... это функция выхода (инструкция RET).
Цель состоит в том, чтобы найти его в каждой программе во время выполнения, у меня нет доступа к компиляции тестовой программы, инструментам или использовать магические инструменты gcc.
Я создал небольшую программу, которую можно скомпилировать с помощью gcc -Wall -Wextra your_file.c
и запустить, набрав ./a.out a_program
.
Вот мой код:
#include <sys/ptrace.h>
#include <sys/signal.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
typedef struct user_regs_struct reg_t;
static int8_t increase(pid_t pid, int32_t *status)
{
if (WIFEXITED(*status) || WIFSIGNALED(*status))
return (-1);
if (WIFSTOPPED(*status) && (WSTOPSIG(*status) == SIGINT))
return (-1);
if (ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL) == -1)
return (-1);
return (0);
}
int main(int argc, char *argv[])
{
size_t pid = fork();
long address_rip;
uint16_t call = 0;
uint16_t ret = 0;
int32_t status;
reg_t regs;
if (!pid) {
if ((status = ptrace(PTRACE_TRACEME, 0, NULL, NULL)) == -1)
return (1);
kill(getpid(), SIGSTOP);
execvp(argv[1], argv + 1);
} else {
while (42) {
waitpid(pid, &status, 0);
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
address_rip = ptrace(PTRACE_PEEKDATA, pid, regs.rip, NULL);
address_rip &= 0xFFFF;
if ((address_rip & 0x00FF) == 0xC2 || (address_rip & 0x00FF) == 0xC3 ||
(address_rip & 0x00FF) == 0xCA || (address_rip & 0x00FF) == 0xCB ||
(address_rip & 0x00FF) == 0xCF)
ret += 1;
else if ((address_rip & 0x00FF) == 0xE8 || (address_rip & 0x00FF) == 0xF1 ||
(address_rip & 0x00FF) == 0x9A || (address_rip & 0x00FF) == 0xCC ||
(address_rip & 0x00FF) == 0xCD || (address_rip & 0x00FF) == 0xCF)
call += 1;
if (increase(pid, &status) == -1) {
printf("call: %i\tret: %i\n", call, ret);
return (0);
}
}
}
return (0);
}
Когда я запустил его с a_program
(это пользовательская программа, которая просто входит в какую-то локальную функцию и делает какую-то запись в системном вызове, цель состоит в том, чтобы просто отследить количество введенных / оставленных функций этой программы), ошибки не возникает , это работает нормально, НО у меня не одинаковое количество CALL и RET.
Exemple:
пользователь> ./a.out basic_program
вызов: 636, рет: 651
(Большое количество вызовов и ретрансляторов вызвано тем, что LibC выполняет множество функций перед запуском вашей программы, см. Парсинг вызовов и ретрансляторов с помощью ptrace. )
На самом деле, похоже, что моя программа возвращается больше, чем вызов функции, но я обнаружил, что инструкция 0xFF используется для CALL или CALLF в (r / m64 или r / m16 / m32), но также и для других инструкций, таких как DEC, INC или JMP (которые являются очень распространенной инструкцией).
Итак, как я могу это дифференцировать? в соответствии с http://ref.x86asm.net/coder64.html с «полями кода операции», но как мне его найти?
Если я добавлю 0xFF в свое состояние:
else if ((address_rip & 0x00FF) == 0xE8 || (address_rip & 0x00FF) == 0xF1 ||
(address_rip & 0x00FF) == 0x9A || (address_rip & 0x00FF) == 0xCC ||
(address_rip & 0x00FF) == 0xCD || (address_rip & 0x00FF) == 0xCF ||
(address_rip & 0x00FF) == 0xFF)
call += 1;
Если я запускаю его:
пользователь> ./a.out basic_program
вызов: 1152, рет: 651
Мне кажется, это нормально, потому что он учитывает каждый JMP, DEC или INC, поэтому мне нужно различать каждую инструкцию 0xFF. Я пытался сделать так:
else if ((address_rip & 0x00FF) == 0xE8 || (address_rip & 0x00FF) == 0xF1 ||
(address_rip & 0x00FF) == 0x9A || (address_rip & 0x00FF) == 0xCC ||
(address_rip & 0x00FF) == 0xCD || (address_rip & 0x00FF) == 0xCF ||
((address_rip & 0x00FF) == 0xFF && ((address_rip & 0x0F00) == 0X02 ||
(address_rip & 0X0F00) == 0X03)))
call += 1;
Но это дало мне тот же результат. Я где то не прав? Как я могу найти один и тот же номер звонка и ответа?