[Rev] 리버싱 · 작성: 2026-07-19 19:32:50 · 수정: 2026-07-21 23:27:57 · 조회 21
디버거를 "브레이크포인트 걸고 F5 누르는 도구"로만 알면, gdb가 실제로 다른 프로세스의 실행을 어떻게 멈추고, 메모리를 어떻게 들여다보는지를 놓치게 된다. 이건 커널이 제공하는 단 하나의 시스템 콜, ptrace() 위에 전부 세워진 기능이다.
ptrace()가 디버거에게 주는 권한리눅스 커널은 ptrace(PTRACE_ATTACH, pid, ...) 시스템 콜을 통해, 한 프로세스(디버거)가 다른 프로세스(대상)의 메모리·레지스터·시그널 전달을 완전히 통제할 수 있게 해준다. 이건 일반적인 프로세스 격리 원칙(각 프로세스는 자기 메모리만 본다)에 대한 명시적 예외다 — 커널이 "디버거 프로세스는 이 대상 프로세스에 대해 특별한 권한을 가진다"고 승인해주는 것.
b main으로 브레이크포인트를 걸면, 내부적으로 그 주소의 원래 명령어 바이트를 0xCC(x86의 int3, 소프트웨어 인터럽트 명령어)로 임시 교체한다. CPU가 실행 중 0xCC를 만나면 트랩(예외)을 발생시키고, 커널이 이 트랩을 감지해 ptrace로 연결된 디버거 프로세스에게 제어권을 넘긴다. 디버거는 이 시점에 원래 바이트를 복원하고, 레지스터/메모리를 자유롭게 조회한다PTRACE_SINGLESTEP은 CPU의 **TF(Trap Flag)**를 세팅해서, 명령어 하나를 실행할 때마다 트랩이 발생하도록 만든다 — 한 줄씩 실행하며 매번 디버거에게 제어권이 돌아오는 원리다PTRACE_PEEKDATA/PTRACE_POKEDATA로 대상 프로세스의 가상 주소 공간을 직접 읽고 쓴다 — gdb의 x/, set 명령어가 결국 이 시스템 콜 호출이다(gdb) b *0x401234 # 특정 주소에 브레이크포인트
(gdb) run < input.txt
(gdb) info registers # 현재 레지스터 상태 확인
(gdb) x/20xg $rsp # 스택 상위 20개 8바이트 값을 hex로 확인
(gdb) bt # 콜스택(백트레이스) — 어떤 함수가 어떤 함수를 호출해 여기까지 왔는지
pwndbg/gef 같은 gdb 확장은 이 원시 명령어들을 조합해서, 레지스터·스택·현재 명령어를 한 화면에 자동으로 보여준다 — 원리는 위의 ptrace 기반 기본 명령어 조합과 동일하고, 편의성만 크게 개선된 것이다.
ptrace에 의존한다는 사실 자체가 공격 표면이 된다 — 안티 디버깅 글에서 다루듯, 프로그램이 스스로 ptrace(PTRACE_TRACEME, ...)를 먼저 호출해두면 같은 프로세스에 대해 디버거가 추가로 ptrace attach를 못 하게 커널이 막아준다 — 이게 가장 흔한 안티 디버깅 기법의 근본 원리다ptrace로 매번 멈추고 확인하는 대신, 대상 프로세스 안에 직접 코드를 주입해서 훨씬 유연하게 함수 호출을 가로챈다 — gdb와는 다른 접근이지만, 결국 "다른 프로세스의 실행을 통제한다"는 같은 목표를 다른 메커니즘으로 달성한다는 점에서 연결된다cyclic_find)도 결국 이 글에서 다룬 "크래시 시점 레지스터 확인" 기법 그대로다디버거를 "따로 존재하는 마법 같은 도구"가 아니라 **"커널이 ptrace로 열어준 문 하나를 편리하게 감싼 래퍼"**로 이해하면, 왜 안티 디버깅이 대부분 이 ptrace 시스템 콜 자체를 겨냥하는지, 그리고 왜 그 우회 방법들도 결국 "커널에게 다른 방식으로 같은 정보를 요청하는" 형태로 수렴하는지가 같은 논리로 설명된다.