Um pouco de ptrace

O que é ptrace?

PTrace é uma chamada de sistema incluída em muitos sistemas Unix e Unix-like, como o seu nome diz (Process Trace) esta syscall e biblioteca permite que um processo possa obter controle sobre outro, dando poderes ao processo ‘pai’ a acessar registradores, flags, instruções… do seu processo filho.

É frequentemente utilizada em debuggers (como o gdb e edb) para o debug de algum programa (geralmente problemático), e que, com uma grande gama de opções podemos controlar um programa de um modo bem minucioso.

O que fazemos com isso?

Bom, nesta série de posts (talvez farei mais de um) não pretendo aqui ensinar Assembly x86 ou mesmo como usar um debugger (suponho que saibam), mas mostrar como utilizar esta biblioteca (ou menos parte dela) e vermos o quão legal é ter o poder de um processo em suas mãos.

Mãos na massa!

O uso do ptrace se resume quase que basicamente em uma função: ptrace, que possui a seguinte aparência:

onde:
request – é a operação a ser realizada
pid – é o pid do processo filho
addr – endereço de leitura/escrita
data – dado a ser utilizado em escrita

algumas bibliotecas que iremos utilizar e suas respectivas funções:

onde:
sys/ptrace.h – Biblioteca do ptrace.
sys/types.h – Algumas definições de tipos, como size_t, pid_t…
sys/wait.h - Referente as funções wait e waitpid.
unistd.h – Algumas funções, constantes… do sistema unix, como fork e execl.
sys/user.h – Precisaremos da struct user_regs_struct definida no arquivo.
sys/reg.h – Definições de constantes para os registradores que iremos utilizar.
sys/syscall.h – Definições de constantes para as syscalls do sistema.

aproveitando, também é interessante ressaltar os tipos mais comuns de requests:
PTRACE_TRACEME  – Seleciona o processo que será ‘interceptado’.
PTRACE_PEEKTEXT - Obtém dados da seção text.
PTRACE_PEEKDATA – Obtém dados da seção data.
PTRACE_PEEKUSER - Obtém dados relativos a registradores, flags, dentre outros.
PTRACE_POKETEXT – Insere dados na seção text do processo alvo.
PTRACE_POKEDATA - Insere dados na seção data.
PTRACE_POKEUSER – Insere dados relativos a registradores, flags, dentre outros.
PTRACE_GETREGS – Obtém os dados dos registradores atuais do processo filho.
PTRACE_SETREGS – Modifica os dados atuais dos registradores do processo filho.
PTRACE_CONT – Forma de interromper processo filho.
PTRACE_SYSCALL – Interrompe processo filho a cada syscall.
PTRACE_SINGLESTEP – Interrompe processo a cada instrução.

Vamos ver alguns usos destes requests, do ptrace e em seguida um exemplo completo:

Obtenção de registradores:

ou

onde child é o pid do processo filho.
* orig_eax é o eax proveniente de uma syscall.

Alterando registradores:

 Obtendo dados:

Alterando dados:

 Exemplo:

Antes de dizer o que o código faz, vamos às novidades inseridas acima, nas linhas:

13 – temos um fork(), o fork é uma chamada de sistema responsável por criar um outro processo exatamente igual ao seu, podemos dizer que é especificamente um clone, clone de processos. (Inclusive, em sistemas Minix, é a única forma para criação de processos). E para quê precisamos disso? Bem, nós vamos ‘atacar’ um outro processo, e este novo processo precisa ser nosso filho.

15 – se child 0, então é nosso filho que está sendo executado e se é ele:

17 e 18 – então dizemos que é para ‘tracar’ o processo filho e executar o processo que será atacado, como o nosso filho que está executando ele, então isto se torna o nosso processo filho.

27 – de dentro do processo pai, precisamos sempre executar o trecho abaixo, até que haja alguma condição específica.

29 – wait(&status) – espera por um ‘pedaço’ (processo filho será executado até que haja alguma mudança de estado) de execução do processo filho e a entrega do controle ao processo pai, isto é muito importante, para que haja a ‘sincronia’ entre os processos.

30 – como dito, a mudança de estado do processo filho será salva em status e a macro WIFEXITED verifica se o processo filho foi encerrado sem código de erro, se sim, o processo pai deve encerrar o seu loop infinito e parar de bisbilhotar o processo filho.

33 – como o processo filho ainda não terminou a sua execução, continuamos a execução, nesta linha, obtemos o valor  de todos os registradores.

34 – utilizando o EIP obtido, obtemos a instrução que será executada.

37 – utilizando o request PTRACE_SINGLESTEP informamos que o código filho deve ser interrompido e devolver o controle ao pai a cada instrução executada.

Okay, mas afinal de contas, o que o código faz (se você ainda não percebeu)? simples, nós executamos o programa alvo (dummy) e a cada instrução executada imprimimos o endereço de EIP atual e a instrução que será executada no instante seguinte. ;-)

HelloWorld em singlestep

HelloWorld em SingleStep


Bom, é isso pessoal, o post acabou ficando um pouco grande mas creio que consegui passar um pouco do ptrace para vocês, e não se esqueçam, haverão mais posts acerca do ptrace e as suas outras utilizações, até + pessoal.

Antes que eu me esqueça, boa parte desse(s) posts terão como base os artigos de Pradeep Padala publicados no Linux Journal em 2002.

Deixe uma resposta

O seu endereço de email não será publicado Campos obrigatórios são marcados *

Você pode usar estas tags e atributos de HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre user="" computer="" escaped="" class="" title="" data-url=""> <span class="" title="" data-url="">