본문으로 건너뛰기

함수 후킹: 리버스 엔지니어의 비밀 무기

함수 후킹은 처음에는 겁나는 기법처럼 들리지만, 이해하고 나면 엄청나게 강력한 도구가 된다. 악성코드 디버깅, 안티치트 우회, CTF 문제 풀이 어디서든 후킹을 쓰면 프로그램 동작을 런타임에 가로채고 수정할 수 있다.

함수 후킹이란?

핵심은 간단하다 — 실행 중인 프로그램의 함수 호출을 가로채는 것이다. 함수가 정상적으로 실행되는 대신, 내 코드로 먼저 리다이렉트된다. 소프트웨어에 도청 장치를 다는 것처럼 — 무슨 일이 일어나는지 관찰하고, 입출력을 수정하거나, 함수 전체를 교체할 수 있다.

왜 함수를 후킹하나?

보안 연구와 CTF에서 함수 후킹은 다양한 목적으로 쓰인다:

보안 검사 우회: 성가신 라이선스 검증? 검증 함수를 후킹해서 항상 true를 반환하게 만든다.

동작 분석: 악성코드가 네트워크 활동을 난독화한다? send/recv 함수를 후킹해서 전송되는 데이터를 정확히 확인한다.

동적 분석: 복잡한 암호화 알고리즘을 리버싱하는 대신, decrypt 함수를 후킹해서 평문을 덤프한다.

게임 해킹: 무한 체력을 원한다면? 데미지 계산 함수를 후킹한다.

주요 후킹 기법

LD_PRELOAD (Linux)

Linux에서 가장 간단한 방법이다. 후킹하려는 함수와 이름이 같은 함수를 가진 공유 라이브러리를 만들고, 프로그램 실행 전에 LD_PRELOAD로 로드한다.

LD_PRELOAD=./myhook.so ./target_program

후킹된 함수가 먼저 실행되고, dlsym(RTLD_NEXT, "function_name")으로 원본 함수를 호출할 수 있다.

IAT 후킹 (Windows)

IAT(임포트 주소 테이블)는 임포트된 함수들의 주소를 저장한다. 메모리에서 이 주소들을 수정하면 함수 호출을 내 코드로 리다이렉트할 수 있다. API 후킹의 클래식 기법이다.

인라인 후킹

가장 다재다능하지만 복잡한 방법이다. 타깃 함수의 머신 코드를 직접 수정해서 내 후크로 점프하는 명령을 삽입한다. 임포트 테이블에 나타나지 않는 내부 함수에도 동작한다.

실전 예시: strcmp 후킹

CTF 문제에서 strcmp로 비밀번호를 검증한다고 해보자. 올바른 비밀번호를 찾는 대신, strcmp를 후킹해서 항상 0(일치)을 반환하게 만든다.

#define _GNU_SOURCE
#include <dlfcn.h>
#include <string.h>

typedef int (*strcmp_t)(const char*, const char*);

int strcmp(const char *s1, const char *s2) {
printf("strcmp 호출: '%s' vs '%s'\n", s1, s2);

// 특정 문자열에 대해 항상 0 반환
if (strstr(s1, "password") || strstr(s2, "password")) {
return 0;
}

// 나머지는 원본 호출
strcmp_t original = (strcmp_t)dlsym(RTLD_NEXT, "strcmp");
return original(s1, s2);
}

공유 라이브러리로 컴파일하고 LD_PRELOAD로 사용하면, 모든 비밀번호 비교가 보이고 강제로 일치시킬 수 있다.

주요 도구

Frida: 동적 계측의 현대적 표준. JavaScript 기반 API로 플랫폼 전체에서 후킹을 믿을 수 없을 만큼 쉽게 만든다. 모바일 앱 분석에 최적이다.

GDB: 단순 디버거가 아니다. catch syscall과 브레이크포인트로 함수를 가로챌 수 있다. Python 스크립팅과 결합하면 극강의 파워를 발휘한다.

LD_PRELOAD: Linux에서 간단하고 효과적이다. 복잡한 도구 없이 빠른 후킹에 적합하다.

Detours (Windows): Windows 함수 후킹을 위한 Microsoft 공식 라이브러리. 견고하고 잘 문서화되어 있다.

x64dbg/OllyDbg: 내장 후킹 기능을 갖춘 GUI 디버거. 입문자에게 적합하다.

흔한 함정

안티 후킹 메커니즘: 현대 소프트웨어는 후킹을 감지한다. 함수 프롤로그를 검증하거나, 메모리 체크섬을 비교하거나, 라이브러리 후킹을 우회하는 직접 시스콜을 사용한다.

호출 규약 문제: 잘못된 호출 규약 = 크래시. x86에는 여러 규약(cdecl, stdcall, fastcall)이 있다. 잘못 쓰면 스택이 망가진다.

재귀 후킹: 후크가 내부적으로 후킹된 함수를 호출하는 함수를 부르면 무한 루프가 생긴다. 재귀 깊이를 추적하거나 스레드 로컬 스토리지를 써라.

ASLR과 PIE: 주소 공간 레이아웃 랜덤화 때문에 하드코딩된 주소는 동작하지 않는다. 항상 동적 주소 해결을 사용하라.

방어 관점

후킹을 이해하면 방어도 할 수 있다. 다음을 확인하라:

  • 예상치 못한 LD_PRELOAD 환경 변수
  • 수정된 IAT 항목
  • 함수 프롤로그의 의심스러운 점프 명령
  • 프로세스 공간에 로드된 알 수 없는 DLL

무결성 검사, 코드 서명, 커널 레벨 안티치트 시스템이 모두 후킹 탐지와 방지를 목표로 한다.

마치며

함수 후킹은 정적 리버스 엔지니어링을 동적 분석으로 변환한다. 끝없는 어셈블리를 읽는 대신, 프로그램이 실시간으로 비밀을 드러내는 것을 지켜볼 수 있다. CTF 플레이어, 악성코드 분석가, 보안 연구자 모두에게 필수 스킬이다.

LD_PRELOAD 후킹으로 간단하게 시작해서, 더 복잡한 시나리오에서는 Frida로 발전해 나가자. 프로그램 동작을 가로채고 수정하는 능력은 보안 연구의 완전히 새로운 차원을 열어준다.

기억하라: 강한 후킹 능력에는 큰 책임이 따른다. 윤리적으로, 합법적으로 사용하자.