이번 글은 실제 악성코드에서 가장 빈번하게 사용되는
해시 기반 동적 API 호출 기법(Hash-Based Dynamic API Invocation Technique)을 정리함.
이 기법은 다음 목적을 위해 활용된다:
- IAT 없이 API 호출 — 정적 분석 회피
- API 이름 문자열 제거 — 시그니처 탐지 우회
- 쉘코드·패커·언패킹 루틴 등에서 환경 독립적 호출 구조 확보
- 샌드박스·에뮬레이터 우회
리버싱 기록
1. 개요 — 왜 “해시 기반 동적 API 호출 기법”인가?
일반 PE는 Import Table(IAT)을 통해 필요한 API를 정적으로 가져오지만,
악성코드 및 Shellcode는 다음 이유로 IAT를 회피하여 해시 기반 호출을 사용한다.
- 악성코드 분석 시 IAT만 보면 기능이 그대로 드러남
- Shellcode는 PE 구조가 아니라서 IAT 자체가 존재하지 않음
- 패킹된 샘플은 언패킹 전까지 API 주소를 알 수 없음
- API 이름 문자열을 제거해 탐지 난이도를 높임
→ 따라서 API 이름을 해시값만으로 저장하고,
실행 시 PEB → 모듈 → Export Table 순회로 API 주소를 직접 계산해 호출한다.
이를 전체적으로 “해시 기반 동적 API 호출 기법”이라 부른다.
2. PEB 기반 모듈 검색: 호출 기법의 첫 단계
이 기법의 구조적 시작점은 항상 PEB(Process Environment Block) 이다.
Shellcode는 보통 다음 순서로 모듈 위치를 찾는다.
FS:[0x30]→ PEBPEB->LdrLDR_DATA_TABLE_ENTRY리스트 순회kernel32.dll,ntdll.dll등 모듈 이름 해시 비교- 일치 시 Export Table 탐색 시작
C 의사코드
PPEB get_peb() {
return (PPEB)__readfsdword(0x30);
}
어셈블리
mov eax, fs:[0x30] ; eax = PEB
3. 해시 기반 동적 API 호출 기법의 핵심 구조
해시 기반 호출은 다음 세 가지 루틴으로 구성된다.
- 문자열 해시 계산 루틴
- Export Table 순회 & 해시 비교 루틴
- 함수 주소 계산 & 호출
다음은 1~3 항목에 대한 내용이다.
4. 문자열 해시 계산 루틴
가장 흔한 구현은 djb2 변형, ROR/ROL 기반, XOR 기반 해시다.
C 의사코드
DWORD hash_string_ansi(const char* s) {
DWORD h = 0;
while (*s) {
char c = *s++;
if (c >= 'a' && c <= 'z')
c -= 0x20; // uppercase
h = ((h << 5) + h) + c; // djb2 변형
}
return h;
}
어셈블리
push ebp
mov ebp, esp
push ebx
xor eax, eax
mov edx, [ebp+8] ; s
hash_loop:
mov bl, [edx]
cmp bl, 0
je hash_done
; lowercase → uppercase
cmp bl, 'a'
jl skip
cmp bl, 'z'
jg skip
sub bl, 0x20
skip:
inc edx
mov ecx, eax
shl eax, 5
add eax, ecx
add eax, ebx
jmp hash_loop
hash_done:
pop ebx
pop ebp
ret 4
5. Export Table 순회 및 해시 기반 함수 검색
해시 기반 호출 기법의 핵심은
API 이름 문자열 없이 해당 API 주소를 Export Table에서 직접 찾는 것이다.
C 의사코드
FARPROC resolve_function_by_hash(HMODULE mod, DWORD target_hash) {
IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)mod;
IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)((BYTE*)mod + dos->e_lfanew);
DWORD expRVA = nt->OptionalHeader
.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
IMAGE_EXPORT_DIRECTORY* exp =
(IMAGE_EXPORT_DIRECTORY*)((BYTE*)mod + expRVA);
DWORD* names = (DWORD*)((BYTE*)mod + exp->AddressOfNames);
WORD* ords = (WORD*)((BYTE*)mod + exp->AddressOfNameOrdinals);
DWORD* funcs = (DWORD*)((BYTE*)mod + exp->AddressOfFunctions);
for (DWORD i = 0; i < exp->NumberOfNames; i++) {
const char* nm = (const char*)mod + names[i];
if (hash_string_ansi(nm) == target_hash) {
WORD o = ords[i];
DWORD r = funcs[o];
return (FARPROC)((BYTE*)mod + r);
}
}
return NULL;
}
어셈블리 패턴
mov eax, [esi+3Ch] ; e_lfanew
add eax, esi ; NT Headers
mov ecx, [eax+78h] ; Export Directory RVA
add ecx, esi ; pExport
mov edx, [ecx+20h] ; AddressOfNames RVA
add edx, esi ; pNames
이 구조는 거의 모든 악성 Shellcode/패커/드로퍼에서 동일한 패턴을 보인다.
6. 해시 기반 동적 API 호출의 실제 호출 방식
함수 주소를 획득한 뒤, Shellcode는 다음 방식으로 바로 호출한다.
C 의사코드
typedef LPVOID (WINAPI *pVirtualAlloc)(LPVOID, SIZE_T, DWORD, DWORD);
pVirtualAlloc va = (pVirtualAlloc)resolve_function_by_hash(k32, HASH_VIRTUALALLOC);
LPVOID mem = va(NULL, 0x2000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
어셈블리 패턴
push 40h ; PAGE_EXECUTE_READWRITE
push 1000h ; MEM_COMMIT
push 2000h ; size
push 0 ; lpAddress
call eax ; eax = VirtualAlloc resolved address
7. 악성코드에서 해시 기반 호출을 사용하는 이유
| 목적 | 설명 |
|---|---|
| 정적 분석 회피 | Import Table을 비워두어 분석 난도 증가 |
| 문자열 제거 | 함수 이름을 문자열 대신 해시값으로 보관 |
| 패커/언패커 최적화 | 새 환경에서도 API 주소 자동 획득 |
| 샌드박스 우회 | 일부 자동화 엔진은 해시 기반 호출을 트래킹하지 못함 |
| 쉘코드 호환성 | PE가 아니어도 API 호출 가능 |
결과적으로 탐지 난이도가 높고, 분석자가 직접 흐름을 추적해야 한다.
8. 해시 기반 호출 기법에서 흔히 보이는 부가 기능
8.1 안티디버깅
mov eax, fs:[0x30]
mov al, [eax+2]
test al, al
jnz debugger_present
8.2 언패킹 전용 메모리 조작
xor_loop:
xor [edi], al
inc edi
dec ecx
jnz xor_loop
8.3 API 해시 테이블 존재
데이터 섹션에 다음처럼 나열된 DWORD가 보이면 의심:
EC0E4E8E
7C0DFCAA
5D1A902B
1A2B3C4D
9. 리버싱 관점의 분석 전략
- 해시 루틴 먼저 찾기
- 문자열을 한 글자씩 읽는 루프가 핵심 시그니처
- Export Table 접근 코드 식별
e_lfanew,78h,AddressOfNames등 고정 오프셋 활용
- 해시값 목록 수집 → 실제 API 매핑
- 해시 기반 호출이 있는 경우 → Shellcode/Packer 1단계 의심
- PEB 접근 패턴이 보이면 → 동적 로딩 루틴 확률 ↑
10. 결론
해시 기반 동적 API 호출 기법은 악성코드에서 가장 널리 사용되는 스텔스 호출 방식이다.
IAT를 제거하고 Export Table에서 직접 API를 계산하여 호출하기 때문에
정적 분석·샌드박스·시그니처 기반 탐지를 어렵게 만든다.
이 글에서 다룬 분석 절차(PEB 구조, Export Table, 디스어셈블리 패턴 추적)를
기본 틀로 익혀두면, 대부분의 Shellcode/패커 초기 스텁을 빠르게 해석할 수 있다.
📍 Written by Code & Compass