我写了一个小程序,可以使用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数量不同。
用户>./a.out基本程序
call: 636 ret: 651
(大量的CALL和RET是由LibC引起的,在开始您的程序之前,LibC会进入许多函数,请参见使用ptrace解析Call和Ret。)
实际上,就像我的程序进入了更多的返回而不是函数调用,但我发现0xFF指令用于在(r/m64或r/m16/m32)中进行CALL或CALLF,但也用于其他指令,如DEC、INC或JMP(这些非常常见)。
那么,我该如何区分呢?根据http://ref.x86asm.net/coder64.html的"opcode fields",但我怎样才能找到它呢?
如果我在条件语句中加入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;
但是它给了我同样的结果。我哪里做错了?如何找到相同数量的调用和返回?
longjmp
,甚至是exit
,那么执行的调用指令数量将不等于执行的返回指令数量。如果您要跟踪的代码有任何防止被逆向工程的防御措施,那么这些指令也不太可能成对出现。 - Ross Ridge