리버싱 기록
📌 1. Anti-Debugging 심화 분석
기본적인 IsDebuggerPresent 호출을 넘어서, 실제 크랙미나 악성코드에서 자주 사용하는 안티 디버깅 기법 정리.
🧩 (1) PEB 직접 접근 기반 Anti-Debugging
PEB 구조체 내부의 BeingDebugged 플래그를 직접 읽어서 디버거 여부 확인하는 방식.
#include <stdio.h>
int main() {
unsigned char *peb = (unsigned char*)__readfsdword(0x30);
unsigned char BeingDebugged = peb[2]; // PEB + 0x2
if (BeingDebugged)
printf("Debugger detected via PEB check!\n");
else
printf("No debugger detected.\n");
}
요약
- __readfsdword(0x30) → x86 환경에서 PEB 주소 얻는 방식
- peb[2] = BeingDebugged 플래그
- 값이 1이면 디버거 존재
ASM 흐름
mov eax, fs:[30h] mov al, [eax+2] test al, al jnz debugger_found
분석 관점
- [eax+2] 값을 0으로 패치하는 방식
- 또는 jnz → jz 반전
🧩 (2) NtQueryInformationProcess 기반 탐지
Native API를 이용해 디버그 플래그 확인하는 방식.
#include <Windows.h>
#include <winternl.h>
typedef NTSTATUS (WINAPI *PFN)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
int main() {
PFN NtQueryInformationProcess =
(PFN)GetProcAddress(GetModuleHandleA("ntdll"), "NtQueryInformationProcess");
DWORD flags = 0;
NtQueryInformationProcess(GetCurrentProcess(),
0x1f,
&flags, sizeof(flags), NULL);
if (flags == 0)
printf("Debugger detected (DebugFlags == 0)\n");
else
printf("No debugger detected.\n");
}
요약
- ProcessDebugFlags = 0 → 디버거 존재
- 값을 강제로 1로 패치하는 방식
🧩 (3) Timing 기반 Anti-Debugging
#include <Windows.h>
int main() {
DWORD t1 = GetTickCount();
for (volatile int i = 0; i < 10000000; i++);
DWORD t2 = GetTickCount();
if ((t2 - t1) > 50)
printf("Debugger likely detected.\n");
else
printf("Normal execution.\n");
}
요약
- 디버깅 시 루프 실행 시간 증가 → threshold 이상이면 탐지
- 타이밍 비교문 패치 방식
📌 2. VM 기반 난독화 구조 분석
코드를 직접 실행하지 않고 VM 명령 집합(bytecode)으로 변환하여 핸들러 위에서 실행하는 방식. 난이도 높은 난독화 기법.
🧩 (1) VM Dispatcher 구조
VM_Dispatch:
movzx eax, byte [esi]
inc esi
jmp [HandlerTable + eax*4]
요약
- esi = VM 바이트코드 포인터
- opcode = [esi]
- HandlerTable[opcode] → 해당 연산 핸들러로 점프
🧩 (2) VM Handler 예시
Handler_ADD:
mov edx, [stack_top]
mov ecx, [stack_top - 4]
add ecx, edx
mov [stack_top - 4], ecx
sub stack_top, 4
jmp VM_Dispatch
요약
- 스택 상단 두 값 pop → add 연산
- 결과를 push하는 구조
- Dispatcher로 복귀
분석 관점
- 핸들러 기능을 테이블화하여 의미 매핑
- 바이트코드를 순차 분석하여 원래 로직 복원
📌 3. CrackMe 예시
🔍 (1) Anti-Debugging 우회
CALL IsDebuggerPresent TEST EAX, EAX JNZ fail
요약
- EAX != 0 → 디버거 탐지
- JNZ → JZ 패치 방식
🔍 (2) Serial 검증 루틴 분석
xor eax, eax
mov ecx, [input]
xor_loop:
mov dl, [ecx]
test dl, dl
jz check_end
xor al, dl
inc ecx
jmp xor_loop
check_end:
cmp al, 3Ch
je correct
jmp fail
요약
- 문자 반복 XOR 누적 방식
- 결과값 == 0x3C → correct
✔ 예시 정답
ABCDEF<
정리:
- Anti-Debugging은 API 검사 → PEB → 타이밍 검사 순으로 고도화
- VM 난독화는 Dispatcher/Handler 구조 파악이 핵심
- CrackMe는 작성자의 의도 파악 중심 분석
📍Written by Code & Compass