Lendo dados de syscall via ptrace
Salve, salve! Dessa vez, começo minha aventura pelos mares do ptrace! Vejo como uma boa área para adquirir algum aprendizado no que diz respeito a alteração/extração de informação de um processo em execução, entre outros tipos de utilidade. Tentarei reportar de forma didática o que eu descobrir sobre o assunto, e especificamente, com exemplos usando arquitetura x86_64 (o que é raro de ver por ai).
Para quem não conhece, ptrace é o mecanismo (é uma syscall) provido pelo Kernel para fazer com que processo pai consiga observar e controlar a execução de outro processo.
Venho seguindo o artigo Playing with ptrace, Part I [1], o qual possui outras partes em que vai se aprofundando no assunto. Creio que ele irá suprir minhas dúvidas, e tenha as informações complementares à manpage.
Neste primeiro post mostrarei como é possível ler argumentos de uma syscall usando o ptrace e fazendo os seguintes passos: Criar processo filho -> Marcá-lo que haverá trace -> Checar por uma determinada syscall -> Ler os registradores.
Todo o uso do ptrace dar-se por meio da função ptrace():
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
Onde request pode ser um dos possíveis valores: PTRACE_TRACEME, PTRACE_PEEKTEXT, PTRACE_PEEKDATA, PTRACE_PEEKUSER, PTRACE_POKETEXT, PTRACE_POKEDATA, PTRACE_POKEUSER, PTRACE_GETREGS, PTRACE_GETFPREGS, PTRACE_SETREGS, PTRACE_SETFPREGS, PTRACE_CONT, PTRACE_SYSCALL, PTRACE_SINGLESTEP, PTRACE_DETACH.
A seguir veremos o uso de algum deles neste primeiro código de exemplo, com os devidos comentários no código.
Código do programa que será executado pelo processo filho:
#include <stdio.h> int main(int argc, char **argv) { printf("foobar!!\n"); return 0; }
Código do programa principal:
#include <sys/ptrace.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> #include <sys/syscall.h> #include <sys/user.h> #include <stdlib.h> #include <string.h> /* Obtido em /usr/include/asm/ptrace-abi.h */ #define RAX 80 #define RSI 104 const int long_size = sizeof(long); void getdata(pid_t child, long addr, char *str, int len) { char *laddr = str; int i = 0, j = len / long_size; int is_exact = len % long_size; long val; while (i <= j) { if (i == j && is_exact == 0) { break; } /* Lendo dados do processo filho */ val = ptrace(PTRACE_PEEKDATA, child, addr + i * long_size, NULL); memcpy(laddr, &val, i == j ? (len % long_size) : long_size); ++i; laddr += long_size; } str[len] = '\0'; } int main(int argc, char **argv) { pid_t child; long orig_rax, eax; struct user_regs_struct regs; int status, insyscall = 0; child = fork(); if (child == 0) { /* Diz ao kernel que o processo passará a ser "traceado" */ ptrace(PTRACE_TRACEME, 0, NULL, NULL); execl("./foo", NULL, NULL); } else { while(1) { /* Aguarda pela execução da syscall sys_execve */ wait(&status); if (WIFEXITED(status)) { break; } /* Obtendo os valores dos registradores usados na syscall */ ptrace(PTRACE_GETREGS, child, 0, ®s); if (regs.orig_rax == __NR_write) { if (insyscall == 0) { long val; /* Endereço da string como argumento da sys_write() */ val = ptrace(PTRACE_PEEKUSER, child, RSI, NULL); char *str = malloc(regs.rbx + 1 * regs.rdi); getdata(child, val, str, regs.rbx); printf("arg: '%s'\n", str); free(str); insyscall = 1; } else { /* Obtem o valor retornado pela syscall */ eax = ptrace(PTRACE_PEEKUSER, child, RAX, NULL); printf("Write returned with %ld\n", eax); insyscall = 0; } } /* Indicamos que o kernel deve parar o processo filho a cada * syscall ou exit */ ptrace(PTRACE_SYSCALL, child, NULL, NULL); } } return 0; }
Observação: Repare que o primeiro programa é executado neste último código através de "./foo".
Outra parte a se observar no código, é a leitura da string do argumento via variável `val' (do tipo long) na função getdata() usando memcpy().
cara, explique as constantes
cara, explique as constantes e os parametros a função, isso facilita muito, agente tem que descobrir pra que serve cada um, e aumenta a area do código fonte também...
Post new comment