PTRACE 사용법

PTRACE 시작하기

PTRACE를 사용하기 위해서는 <sys/ptrace.h> 헤더 파일을 포함해야 하며 ptrace() 시스템 콜을 사용한다. PTRACE를 사용하기 전에 대상 프로세스를 생성하거나 이미 실행 중인 프로세스를 선택해야 한다.

프로세스 추적 제어

PTRACE_ATTACH

ptrace(PTRACE_ATTACH, pid, NULL, NULL);
waitpid(pid, NULL, 0);

PTRACE_ATTACH 플래그를 사용하여 프로세스를 추적한다. pid는 추적할 프로세스의 PID이다.

waitpid() 함수를 사용하여 프로세스가 멈출 때까지 대기한다.

PTRACE_DETACH

ptrace(PTRACE_DETACH, pid, NULL, NULL);

PTRACE_DETACH 플래그를 사용하여 추적을 중지한다. 이후에는 프로세스가 정상적으로 실행을 이어간다.

레지스터 상태 읽기/수정

PTRACE_GETREGS

struct user_regs_struct regs;
ptrace(PTRACE_GETREGS, pid, NULL, ®s);

PTRACE_GETREGS 플래그를 사용하여 대상 프로세스의 레지스터 상태를 읽어온다. user_regs_struct 구조체에 읽어온 레지스터 값을 저장한다.

PTRACE_SETREGS

regs.ARM_r6 = 42;
ptrace(PTRACE_SETREGS, pid, NULL, ®s);

PTRACE_SETREGS 플래그를 사용하여 대상 프로세스의 레지스터 상태를 수정할 수 있다. regs은 각 아키텍처가 정의한 레지스터를 사용하면 된다. 위의 예시에서는 ARM 아키텍처 r6 레지스터 값에 42를 대입힌다.(참고: https://elixir.bootlin.com/linux/latest/source/arch/arm/include/uapi/asm/ptrace.h#L12)

메모리 읽기/쓰기

PTRACE_PEEKDATA

long data = ptrace(PTRACE_PEEKDATA, pid, address, NULL);

PTRACE_PEEKDATA 플래그를 사용하여 대상 프로세스의 메모리 값을 읽어온다(read). address는 읽어올 메모리 주소이고, 읽어온 값을 long 타입 변수에 저장한다.

PTRACE_POKEDATA

ptrace(PTRACE_POKEDATA, pid, address, data);

PTRACE_POKEDATA 플래그를 사용하여 대상 프로세스의 메모리에 값을 쓴다(write). address는 쓸 메모리 주소이고, data는 쓸 값을 나타낸다.

실행 제어

ptrace(PTRACE_CONT, pid, NULL, NULL);
waitpid(pid, NULL, 0);

PTRACE_CONT 플래그를 사용하여 대상 프로세스의 실행을 재개한다. waitpid 함수를 사용하여 프로세스가 멈출 때까지 대기한다.

Example:

#include <sys/ptrace.h>
#include <sys/user.h>
#include <asm/ptrace.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

int main(int argc, char *argv[])
{
	struct user_regs_struct regs;
	unsigned int data;
	unsigned char buff[4];
	int ret, pid, i, j;
	
	pid = atoi(argv[1]);
	
	ret = ptrace(PTRACE_ATTACH, pid, 0, 0);
	printf("return : %d\n", ret);
	
	ptrace(PTRACE_GETREGS, pid, 0, ®s);
	printf("stack = %p\n", (void *)regs.ARM_sp);
	
	for (i=0; i<10; i++) {
		data = ptrace(PTRACE_PEEKDATA, pid, regs.ARM_sp+i*4, 0);
		memcpy(buff, &data, 4);
		printf("%08x : ", (unsigned int)regs.ARM_sp+i*4);
		for (j=0; j<4; j++) {
			if (isprint(data2[j]))
				printf("%c ", buff[j]);
			else
				printf(". ");
		}
		printf("%08x\n", data);
	}
	ptrace(PTRACE_POKEDATA, pid, 0x7efd97d0, 0xa5a5a5a5);
	
	ptrace(PTRACE_DETACH, pid, 0, 0);
	
	return 0;    
}

프로세스 추적 시작하기

ptrace(PTRACE_TRACEME, 0, NULL, NULL);

PTRACE_TRACEME 플래그를 사용하여 현재 프로세스를 추적 대상으로 설정한다. 이 함수 호출 이후에는 현재 프로세스가 디버거에 의해 추적될 수 있게 제어권을 넘겨준 상태가 된다.

디버거 프로세스 시작하기

pid_t pid = fork();
if (pid == 0) {
    // 자식 프로세스 코드
    execl("/path/to/program", "program", NULL);
} else {
    // 부모 프로세스 코드
    wait(NULL);
}

디버거 프로세스를 시작하기 위해 자식 프로세스를 생성한다. 자식 프로세스는 execl 함수를 사용하여 디버깅할 대상 프로그램을 실행한다. 부모 프로세스는 wait 함수를 사용하여 자식 프로세스의 종료를 기다린다(wait).

종료시키기

ptrace(PTRACE_KILL, pid, NULL, NULL)

자식 프로세스에게 SIGKILL을 보내 종료되도록 한다.

Example:

#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <asm/ptrace.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

int main(int argc, char *argv[])
{
	struct user_regs_struct regs;
	int ret, status;
	
	pid_t pid = fork();
	if (pid == 0) {
		// child process
		ptrace(PTRACE_TRACEME, 0, 0, 0);
		execl("/bin/ls", "/bin/ls", NULL);
		return 0;
	}
	
	wait(&status);
	if (WIFSIGNALED(status)) {
		fprintf(stderr, "child process %d was abnormal exit.\n", pid);
		return -1;
	}
	
	ret = ptrace(PTRACE_GETREGS, pid, 0, ®s);
	printf("return : %d\n", ret);
	printf("stack = %p\n", (void *)regs.ARM_sp);
	printf("pc    = %p\n", (void *)regs.ARM_pc);
	
	ptrace(PTRACE_KILL, pid, 0, 0);
	
	return 0;    
}

PTRACE 사용법
strace 사용법
ltrace 사용법

답글 남기기