LR Return Address를 이용한 스텔스 인라인 훅 감지
Source: Dev.to
배경: 기존 인라인 훅 탐지가 실패하는 이유
Android에서 인라인 훅 탐지는 전통적으로 다음에 의존합니다:
- CRC 또는 코드‑해시 검증
- 함수 프로로그 바이트 비교 (예:
LDR/BR트램폴린 탐지)
이 방법들은 큰 약점을 가지고 있습니다:
- 코드 페이지를 읽기 때문에 메모리 브레이크포인트로 쉽게 추적될 수 있습니다.
- 공격자는 탐지 함수 자체를 훅할 수 있습니다.
- 그들의 “시그니처”는 잘 알려져 있어 우회가 쉽습니다.
따라서 실제 공격‑방어 시나리오에서는 눈에 띄고 위험도가 높습니다.
LR 반환 주소를 이용한 스텔스 인라인 훅 탐지
제안된 방법은 명령어를 읽지 않고, 메모리를 스캔하지 않으며, Frida(또는 유사 프레임워크)가 우회하기 매우 어렵습니다.
인라인‑훅 프레임워크(Frida, Dobby, xhook 등)는 일반적으로:
- LR 레지스터를 덮어씁니다.
RET을 원본 모듈 외부에 사용자 할당 메모리 페이지에 저장된 트램폴린으로 리다이렉트합니다.
핵심 관찰:
함수의 LR이 해당 모듈의 메모리 범위 밖을 가리키면, 그 함수는 인라인‑훅된 것입니다.
장점
.text코드 섹션을 읽지 않습니다.- 하드웨어 브레이크포인트로 추적할 수 없습니다.
- ARM64와 x86_64(Android 및 Windows) 모두에서 동작합니다.
탐지 파이프라인
Step 1 – 현재 모듈 범위 가져오기
static uint64_t g_begin = 0;
static uint64_t g_end = 0;
__attribute__((constructor))
static void init_module_range() {
Dl_info info;
if (dladdr((void*)init_module_range, &info)) {
g_begin = (uint64_t)info.dli_fbase;
g_end = g_begin + get_module_size(info.dli_fname);
}
}
Step 2 – 링크 레지스터(LR) 읽기
__attribute__((always_inline))
uint64_t get_lr() {
uint64_t lr;
asm volatile(
"mov x10, x29 \n"
"ldr %0, [x10, #8]\n"
: "=r"(lr)
:
: "x10"
);
return lr;
}
Step 3 – 인라인 훅 탐지
void check_inline_hook() {
uint64_t lr = get_lr();
if (lr g_end) {
LOGD("[!] Inline‑hook detected. lr = %llx", lr);
} else {
LOGD("[+] Function not hooked.");
}
}
안티‑디버그 하드닝 (선택 사항)
디버깅을 거의 불가능하게 만들기 위해, 보호 로직이 스택을 손상시키고 잘못된 주소로 점프하도록 할 수 있습니다:
void anti_debug_crash() {
uint64_t fp;
asm volatile("mov %0, x29" : "=r"(fp));
memset((void*)fp, 0xCC, 2048); // 스택 메모리 덮어쓰기
((void(*)())0x12345678)(); // 잘못된 주소로 점프
}
이렇게 하면 스택 트레이스가 파괴되고 보호 로직의 역공학이 크게 방해됩니다.
요약
- LR 기반 인라인 훅 탐지는 코드 페이지를 읽지 않으며 거의 브레이크포인트에 강합니다.
- Frida, Dobby, xhook 및 기타 트램폴린 메커니즘을 신뢰성 있게 탐지합니다.
- Android와 Windows 모두에서 ARM64와 x86_64 아키텍처를 지원합니다.
이 기법은 모바일 보안, 안티‑치트 시스템 및 런타임 보호를 위한 새로운 방향을 제시합니다.