모바일 앱 보안 진단을 하다 보면, 정적 분석만으로는 확인하기 어려운 정보가 많다.
암호화되기 전의 평문이 무엇인지, 인증 토큰이 실제로 어느 함수에서 만들어지는지, 루팅 탐지가 정확히 어떤 분기에서 동작하는지 등 이런 것들은 앱이 실행되는 순간을 직접 봐야 알 수 있다.
이때 사용하는 기술이 후킹(Hooking) 이다. 이 글에서는 후킹의 개념부터 동작 원리, 실제 활용 방법까지 정리한다.
1. 후킹의 개념
후킹은 프로그램의 실행 흐름 중간에 끼어들어, 특정 함수가 호출되는 순간을 가로채는 기술이다.
프로그램이 실행되면 수많은 함수가 정해진 순서대로 호출된다. 후킹은 그 흐름 위에 갈고리(hook)를 걸어, 원하는 함수가 호출될 때 제어권을 잠시 가져온다. 가져온 뒤에는 값을 들여다보거나, 바꾸거나, 함수 자체를 다른 것으로 교체할 수 있다.
핵심은 앱을 다시 빌드하거나 APK/IPA 파일을 직접 수정하지 않는다는 점이다. 앱이 실행 중인 상태, 즉 런타임(runtime)에서 개입한다. 그래서 후킹을 "동적 계측(dynamic instrumentation)"이라고 부르기도 한다.
2. 후킹의 동작 원리
2.1 가로채기는 어떻게 이뤄지는가
함수를 가로챈다는 게 추상적으로 들릴 수 있는데, 실제로는 메모리에 올라간 함수의 주소를 조작하는 방식이다. 대표적인 방법 두 가지를 쉽게 설명하면 다음과 같다.
인라인 후킹 (Inline Hooking)
함수의 시작 부분 명령어를 "점프(jump) 명령"으로 덮어쓴다. 그러면 원래 함수가 호출될 때, 실행이 곧바로 내가 만든 코드로 튕겨 나온다.
이때 덮어써서 사라진 원래 명령어는 따로 보관해 두는데, 이 보관 장소를 트램폴린(trampoline) 이라고 부른다. 트램폴린 덕분에 가로챈 뒤에도 원래 함수를 정상적으로 호출할 수 있다. 이름 그대로, 원래 함수로 다시 튕겨 보내주는 역할이다.
[원래 흐름] 앱 → 함수 실행
[후킹 후] 앱 → 함수 진입 → (점프) → 내 코드 → 트램폴린 → 원래 함수 → 복귀
메서드 스위즐링 (Method Swizzling)
주로 iOS의 Objective-C에서 쓰는 방식이다. Objective-C는 메서드의 실제 구현 위치를 별도의 표(table)에 저장해 두고 런타임에 찾아가는 구조다. 후킹은 이 표에서 "원래 구현을 가리키던 주소"를 "내 구현 주소"로 바꿔치기한다. 바이너리를 직접 수정하지 않고도 메서드 동작을 바꿀 수 있는 이유가 여기에 있다.
두 방식 모두 결과는 같다. 함수가 호출될 때 내 코드가 먼저(또는 대신) 실행되도록 만드는 것이다. 도구(Frida, Xposed 등)가 이런 저수준 작업을 대신 처리해 주기 때문에, 분석자는 "어디에 무엇을 걸지"만 고민하면 된다.
2.2 후킹으로 할 수 있는 세 가지
가로채기에 성공하면, 할 수 있는 일은 크게 세 가지다.
| 동작 | 설명 |
|---|---|
| 관찰 | 함수가 받은 인자, 반환한 값, 호출 여부를 확인한다 |
| 변조 | 들어오는 인자나 나가는 반환값을 다른 값으로 바꾼다 |
| 대체 | 원래 함수를 통째로 내가 만든 구현으로 교체한다 |
대부분의 보안 진단은 첫 번째 관찰에서 시작한다. 우회나 변조는 그다음 단계다.
3. 후킹(Hooking)과 웹훅(Webhook)의 차이
이름이 비슷해서 자주 혼동되지만, 둘은 완전히 다른 개념이다.
| 구분 | 후킹 (Hooking) | 웹훅 (Webhook) |
|---|---|---|
| 대상 | 프로그램 내부의 실행 흐름 | 외부 HTTP 엔드포인트 |
| 목적 | 함수 관찰 / 변조 / 대체 | 이벤트 발생 시 외부에 알림 전송 |
| 동작 위치 | 실행 중인 프로세스 내부 | 네트워크 레벨 |
| 예시 | 함수 인자 확인, 반환값 변경 | 디스코드 채널에 자동 메시지 게시 |
후킹은 실행 중인 앱 내부에 개입하는 기술이다.
반면 디스코드 웹훅은 특정 URL로 HTTP 요청을 보내 메시지를 게시하는 기능일 뿐이다. 앱 내부 동작과는 무관하며, 외부 시스템 간에 이벤트를 전달하는 통합 방식에 가깝다.
정리하면, "보안 진단에서 후킹한다"와 "디스코드 웹훅을 쓴다"는 용어만 비슷할 뿐 실체가 다르다.
4. 보안 진단에서 후킹이 중요한 이유
모바일 앱 진단에서는 "코드가 어떻게 작성됐는가"보다 "실행될 때 실제로 어떤 값이 오가는가" 가 더 중요할 때가 많다.
디컴파일한 코드에서 암호화 로직을 봐도, 실제 평문·키·IV가 실행 시점에 어떤 값으로 들어가는지 모르면 검증이 어렵다. 게다가 난독화가 적용되어 있으면 정적 분석만으로는 흐름을 따라가기조차 힘들다.
후킹은 바로 이 런타임 정보를 직접 확인하게 해 준다. 함수가 실제로 받은 값, 반환한 값, 호출 순서를 눈으로 보고 증거로 남길 수 있다.
또한 안드로이드 앱은 Java/Kotlin 계층과 네이티브(C/C++) 계층을 함께 사용하는 경우가 많다. 민감한 로직이 네이티브 라이브러리로 내려가 있으면 관리형 코드만 봐서는 놓치게 된다. 후킹은 이 두 계층의 경계를 넘나들며 관찰할 수 있다는 점에서도 유용하다.
5. 취약점 분석 활용 사례
후킹은 무작정 거는 게 아니라, 정적 분석으로 세운 가설을 검증하는 도구다. 실무에서 자주 쓰이는 네 가지 사례를 코드와 함께 살펴본다. (예시는 Frida 기준)
5.1 인증 / 세션 분석
로그인 직후 어떤 토큰이 생성되고 어디에 저장되는지 확인할 때 사용한다. 난독화로 코드 흐름이 잘 안 보여도, 실행 시점의 인자와 반환값을 보면 빠르게 파악된다.
// 안드로이드: 로그인 메서드의 인자와 반환값 확인
Java.perform(function () {
var LoginManager = Java.use("com.example.app.LoginManager");
LoginManager.login.implementation = function (username, password) {
console.log("[*] login() 호출");
console.log(" username:", username);
console.log(" password:", password);
var result = this.login(username, password); // 원래 구현 호출
console.log(" 반환 토큰:", result);
return result;
};
});
Java.perform은 Frida가 안드로이드 런타임(ART)에 안전하게 접근하도록 감싸 주는 함수다..implementation을 재정의하면 원래 메서드 호출 전후에 원하는 코드를 삽입할 수 있다.
5.2 암호화 로직 분석
암호화 결과물만 봐서는 구조를 알기 어렵다. 호출 직전의 평문, 키, IV, 알고리즘을 확인하는 것이 핵심이다.
// 안드로이드: AES 암호화 직전의 평문과 알고리즘 확인
Java.perform(function () {
var Cipher = Java.use("javax.crypto.Cipher");
Cipher.doFinal.overload("[B").implementation = function (input) {
console.log("[*] Cipher.doFinal() 호출");
console.log(" 알고리즘:", this.getAlgorithm());
console.log(" 평문(hex):", bytesToHex(input));
var result = this.doFinal(input);
console.log(" 암호문(hex):", bytesToHex(result));
return result;
};
function bytesToHex(bytes) {
return Array.from(bytes)
.map(b => ("0" + (b & 0xff).toString(16)).slice(-2))
.join("");
}
});
5.3 루팅 탐지 로직 분석
앱이 어떤 기준으로 루팅을 판단하는지, 최종 결정이 어느 메서드에서 내려지는지 확인할 때 사용한다.
// 안드로이드: 루팅 탐지 메서드의 결과 관찰
Java.perform(function () {
var RootDetector = Java.use("com.example.app.security.RootDetector");
RootDetector.isDeviceRooted.implementation = function () {
var result = this.isDeviceRooted();
console.log("[*] isDeviceRooted() 결과:", result);
// 흐름 확인이 목적이면: return result;
// 우회가 목적이면: return false;
return result;
};
});
여기서 중요한 것은 우회 자체보다, 탐지 기준과 분기 구조를 명확히 파악하는 것이다.
5.4 네이티브 함수 분석
민감 로직이 Java/Kotlin이 아니라 C/C++ 네이티브 라이브러리에 있는 경우, 관리형 계층만 봐서는 놓치는 부분이 많다. 이때는 네이티브 함수에 직접 훅을 건다.
// JNI를 통해 호출되는 네이티브 함수 후킹
Interceptor.attach(
Module.findExportByName("libnative-lib.so", "Java_com_example_app_NativeLib_computeToken"),
{
onEnter(args) {
// args[0]: JNIEnv*, args[1]: jobject, args[2]부터 실제 파라미터
this.input = args[2].readCString();
console.log("[*] computeToken() 호출 — 입력:", this.input);
},
onLeave(retval) {
console.log("[*] computeToken() 반환:", retval);
}
}
);
Interceptor.attach는 함수 호출 직전(onEnter)과 직후(onLeave)를 모두 가로챈다.onEnter에서this에 저장한 값은onLeave에서 그대로 꺼내 쓸 수 있다.
6. 후킹에 대한 흔한 오해
"후킹만 알면 어떤 앱이든 분석할 수 있다"
그렇지 않다. 어디에 훅을 걸어야 하는지 모르면 거의 쓸모가 없다. 좋은 후킹은 항상 정적 분석과 호출 흐름 이해 위에서 이뤄진다.
"후킹은 우회·해킹 기법이다"
동작 변경에도 쓰이지만, 실무 진단에서는 관찰과 증거 수집의 비중이 훨씬 크다. 함수 인자와 반환값, 호출 순서를 기록하는 것만으로도 진단 보고서의 신뢰도가 크게 올라간다.
"웹훅도 후킹의 일종이다"
다르다. 웹훅은 이벤트를 외부 URL로 전송하는 기능이고, 후킹은 프로그램 내부 실행 흐름에 개입하는 기술이다.
7. 정리
후킹의 본질은 단순하다.
실행 중인 앱의 특정 지점에 개입해, 실제 동작을 눈으로 확인하는 것.
보안 진단 관점에서는 여기에 한 줄을 더한다.
관찰한 런타임 증거를 바탕으로 취약점을 검증하고 설명하는 것.
후킹은 정적 분석으로 세운 가설을 동적으로 증명하는 도구다. 정적 분석의 한계를 메우고, 실제 위험이 존재함을 증명하는 실전 기법이라고 볼 수 있다.
참고 자료
'Security > Mobile' 카테고리의 다른 글
| [3탄] 취약점 점검 전 알아야 할 모바일 실전 진단 프로세스와 런타임 제어 (0) | 2026.06.01 |
|---|---|
| [2탄] 취약점 점검 전 알아야 할 Android 파일 시스템과 보안 아키텍처 (1) | 2026.04.30 |
| [1탄] 취약점 점검 전 알아야 할 iOS 파일 시스템과 보안 아키텍처 (0) | 2026.02.28 |
| 최신 안드로이드에서도 가능한 USB 위장 연결 공격 (0) | 2026.02.28 |
