[Rev] 리버싱 · 작성: 2026-07-19 19:34:18 · 수정: 2026-07-21 23:29:11 · 조회 17
Frida를 "후킹 스크립트를 파이썬/JS로 짤 수 있는 도구"로만 알면, 이게 동적 분석 글에서 다룬 ptrace 기반 디버거와 근본적으로 다른 방식으로 동작한다는 걸 놓치게 된다. Frida는 "멈춰서 들여다보는" 디버거가 아니라, 대상 프로세스 안에 자기 자신의 JS 런타임을 통째로 주입해서, 그 프로세스가 계속 실행되는 동안 실시간으로 함수 호출을 가로채는 방식이다.
Frida를 대상 프로세스에 attach하면 내부적으로 이런 일이 벌어진다.
ptrace(리눅스) 또는 플랫폼별 API로 대상 프로세스의 메모리 공간에 자신의 에이전트 라이브러리(공유 라이브러리)를 강제로 로드시킨다 — 대상 프로세스 입장에서는 마치 자기가 dlopen()으로 그 라이브러리를 직접 로드한 것처럼 보이게 만드는 기법이다Interceptor.attach()의 실제 동작 — 인라인 후킹(Trampoline): 후킹하려는 함수의 시작 주소에서, Frida는 **그 함수의 원래 명령어 몇 바이트를 자신의 트램폴린 코드로 교체(패치)**한다. 함수가 호출되면 원래 코드 대신 이 트램폴린으로 먼저 진입해서 우리가 등록한 onEnter/onLeave 콜백을 실행한 뒤, 백업해둔 원래 명령어를 실행하고 원래 흐름으로 복귀시킨다 — ptrace로 매번 멈추는 게 아니라, 함수 자체의 코드를 실행 중에 살짝 바꿔치기해서 "가로채기 코드"를 심어두는 방식이라는 게 디버거와의 근본적인 차이다// 예: is_valid_license()가 항상 true를 반환하도록 강제
Interceptor.attach(Module.findExportByName(null, "is_valid_license"), {
onEnter(args) {
console.log("is_valid_license() 호출됨, 인자:", args[0].readCString());
},
onLeave(retval) {
console.log("원래 반환값:", retval.toInt32());
retval.replace(1); // 반환값을 강제로 1(성공)로 조작
}
});
retval.replace()는 함수가 반환하기 직전, RAX 레지스터에 담긴 반환값을 그대로 덮어쓰는 동작이다 — 스택 버퍼 오버플로우로 리턴 주소를 덮는 것과 마찬가지로, "CPU가 다음에 참조할 값을 신뢰 없이 그대로 조작한다"는 같은 발상이 여기서는 훨씬 안전하고 통제된 방식으로 재현된다.
안티 디버깅 글에서 다룬 ptrace(PTRACE_TRACEME) 셀프 트레이스 탐지는 gdb 같은 ptrace 기반 디버거를 겨냥한 것이다. Frida도 주입 시점에는 ptrace를 쓰지만, 이후 함수 후킹은 코드 패치 방식이라 TracerPid 확인 같은 일부 탐지에는 여전히 걸릴 수 있는 반면, int3 소프트웨어 브레이크포인트를 전혀 심지 않으므로 코드 무결성 체크섬 기반 탐지는 그대로 통과하는 경우가 많다 — 안티 디버깅 기법마다 Frida에 대한 효과가 다르다는 점을 이해하고 대상에 맞는 우회를 골라야 한다.
안드로이드 앱 리버싱이나 iOS 앱 리버싱에서는 앱을 재컴파일하거나 서명을 다시 하지 않고도, Frida로 런타임에 SSL Pinning 검증 함수나 루팅/탈옥 탐지 함수를 즉석에서 무력화할 수 있다 — 모바일 플랫폼의 코드 서명 제약 때문에 바이너리를 영구적으로 패치하기 번거로운 환경에서, "실행 중에만 잠깐 바꿔치기하는" Frida의 방식이 특히 실전에서 선호되는 이유다.
malloc/free/printf 같은 함수의 인자를 실시간으로 로깅해서 어떤 크기로, 어떤 값이 오가는지 관찰하는 퍼징/취약점 탐색 보조 도구로도 널리 쓰인다Frida를 "편리한 후킹 도구"가 아니라 **"대상 프로세스 안에 직접 실행 환경을 심어서, 코드 자체를 실행 중에 패치하는 인라인 계측 기법"**으로 이해하면, 왜 이게 ptrace 기반 디버거보다 훨씬 유연하면서도 일부 탐지 기법에는 여전히 걸릴 수 있는지가 같은 논리로 설명된다.