๋์ API ํด์(dynamic API resolution) ํจํด๊ณผ, ๊ทธ ๊ณผ์ ์์ ๋ํ๋๋ PE ๊ตฌ์กฐ ๊ธฐ๋ฐ ํ์, ํธ์ถ ๊ท์ฝ, ๋ ์ง์คํฐ ์ฌ์ฉ ๋ฐฉ์, ๊ทธ๋ฆฌ๊ณ ๊ธฐ๋ณธ์ ์ธ ์ํฐ๋๋ฒ๊น ํ๋ฆ์ ํ์ ํ๋ ๋ด์ฉ.
๋ฆฌ๋ฒ์ฑ ๊ธฐ๋ก
1. ๊ฐ์
์ผ๋ถ ์
์ฑ ๋ก๋๋ Import Table ์ ์ด์ฉํ์ง ์๊ณ ์ปค์คํ
PE ํ์๋ฅผ ํตํด ํ์ํ API ์ฃผ์๋ฅผ ์ง์ ํ๋ํฉ๋๋ค.
์ด ํจํด์ ์ ์ ๋ถ์์ ๋ฐฉํดํ๊ณ , ๋จ์ํ ๋ฌธ์์ด ๊ธฐ๋ฐ ํ์ง๋ฅผ ์ฐํํ๊ธฐ ์ํด ํํ ์ฌ์ฉ๋ฉ๋๋ค.
- ๊ณ ์ ๋ DLL ์ด๋ฆ ๋ฌธ์์ด
- API ์ด๋ฆ Hashing
- ์ปค์คํ ์ฃผ์ ํ์ ๋ฃจํด
- ๋ฌดํดํ โplaceholderโ ํธ์ถ
2. C ๊ธฐ๋ฐ ๋ก๋ ๊ตฌ์กฐ(๋นํ์ฑ/๋ฌดํด ์์)
์๋ ์ฝ๋๋ ์ค์ ์
์ฑ ๊ธฐ๋ฅ์ด ์ ๊ฑฐ๋ ์์ ํ ๊ตฌ์กฐ ์์์
๋๋ค.
๋ถ์ ํจํด ์ฐ๊ตฌ ๋ชฉ์ ์ด๋ฉฐ, ์คํํด๋ ์๋ฌด ์์
๋ ํ์ง ์์ต๋๋ค.
#include <stdint.h>
#include <windows.h>
typedef FARPROC(WINAPI* ResolveFn)(HMODULE, const char*);
// ๋จ์ ๋ฌธ์์ด hash (๋ฌดํด)
uint32_t simple_hash(const char* s) {
uint32_t h = 0;
while (*s) {
h = (h << 5) - h + (unsigned char)*s++;
}
return h;
}
// ์์: Export Table์ ์ํํด API ์ฃผ์๋ฅผ ๊ฐ์ ธ์ค๋ ํจํด
FARPROC resolve_api(HMODULE mod, uint32_t target_hash) {
uint8_t* base = (uint8_t*)mod;
IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)base;
IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)(base + dos->e_lfanew);
IMAGE_EXPORT_DIRECTORY* exp = (IMAGE_EXPORT_DIRECTORY*)(
base + nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
);
uint32_t* names = (uint32_t*)(base + exp->AddressOfNames);
uint16_t* ords = (uint16_t*)(base + exp->AddressOfNameOrdinals);
uint32_t* funcs = (uint32_t*)(base + exp->AddressOfFunctions);
for (uint32_t i = 0; i < exp->NumberOfNames; i++) {
const char* api_name = (const char*)(base + names[i]);
uint32_t h = simple_hash(api_name);
if (h == target_hash) {
uint16_t ord = ords[i];
return (FARPROC)(base + funcs[ord]);
}
}
return NULL;
}
int main() {
HMODULE k32 = LoadLibraryA("kernel32.dll");
if (!k32) return 0;
// ์: โGetTickCountโ ํด์๊ฐ
uint32_t hash = simple_hash("GetTickCount");
FARPROC api = resolve_api(k32, hash);
if (api) {
// ์ค์ ํธ์ถํ์ง ์๊ณ ์ฃผ์๋ง ํ์ธ
// ((DWORD(WINAPI*)())api)(); // ๋นํ์ฑํ
}
return 0;
}
3. ์ด์ ๋ธ๋ฆฌ ๋ ๋ฒจ ์ ์ด ํ๋ฆ ๋ถ์
์๋๋ ํต์ฌ ๋ฃจํด์ธ resolve_api ๋ด๋ถ ๋ฐ๋ณต ๊ตฌ์กฐ๋ฅผ ๋์ค์ด์
๋ธํ ํํ์ด๋ฉฐ,
๋ ์ง์คํฐ์ ์ญํ ๊ณผ ์คํ ํ๋ ์์ ๋ณํ๋ฅผ ์์ธํ๊ฒ ์ค๋ช
ํฉ๋๋ค.
loc_export_loop:
mov eax, [esi] ; EAX = current RVA of API name
add eax, ebx ; EBX = base, so EAX = VA(name)
push eax ; push(api_name)
call simple_hash
add esp, 4 ; clean args
cmp eax, edi ; target_hash in EDI?
jne short next_api
; If matched:
mov cx, [eds + ecx*2] ; fetch ordinal
mov edx, [funcs + ecx*4] ; fetch RVA
add edx, ebx ; convert RVA โ VA
mov eax, edx ; return address
jmp end_resolve
next_api:
inc ecx
cmp ecx, [number_of_names]
jb loc_export_loop
end_resolve:
ret
๐งท ๋ ์ง์คํฐ ๋์ ํด์ค
- EBX: ๋ชจ๋ ๋ฒ ์ด์ค ์ฃผ์
- ECX: ๋ฐ๋ณต ์ธ๋ฑ์ค(i)
- EDI: ๋ชฉํ ํด์ ๊ฐ
- EDI == EAX ๋น๊ต: API ์ด๋ฆ hash ๋น๊ต
- EAX: ํ์ฌ ๊ณ์ฐ๋ hash ๋๋ ๋ฐํ ์ฃผ์
๐ฆ ์คํ ํ๋ ์ ๊ด์ฐฐ
- ํธ์ถ ๊ท์ฝ์ ๋จ์ C ์ ์ธ์ด๋ฏ๋ก caller-cleaned,
call simple_hashํadd esp, 4์ํ - recursion์ด๋ frame pointer ์ฌ์ฉ ์์
- ์ง์ญ ๋ณ์๋ ๋ ์ง์คํฐ ๊ธฐ๋ฐ์ผ๋ก ์ฒ๋ฆฌ๋จ
4. ๊ฐ๋จํ ์ํฐ๋๋ฒ๊น ๊ด์ฐฐ(๋นํ์ฑยท๋ฌดํด ์์)
์ผ๋ถ ๋ก๋๋ API ๋์ ํ์๊ณผ ํจ๊ป ๋๋ฒ๊ฑฐ ์กด์ฌ ์ฌ๋ถ๋ฅผ ๊ฒ์ฌํฉ๋๋ค.
BOOL anti_debug_stub() {
__asm {
mov eax, fs:[0x30] ; PEB
mov al, [eax+2] ; BeingDebugged
; al == 0์ด๋ฉด ๋ฏธ๋๋ฒ๊น
์ํ
; ์ค์ ๋์ ์ ๊ฑฐ
}
return FALSE;
}
์ด ํจํด์ PEB ์ ๊ทผ โ ๋๋ฒ๊น ์ฌ๋ถ ํ์ธ โ ์กฐ๊ฑด ๋ถ๊ธฐ ํ๋ฆ
5. ํฌ๋ ์ ๊ด์ฐฐ ํฌ์ธํธ
๋ฐ์ดํฐ ํ๋ฆ(Data Flow)์ ๊ธฐ์ค์ผ๋ก ๋ถ์ํ๋ฉด ๋ค์ ๋จ๊ณ๊ฐ ๋ช ํํ๊ฒ ๋๋ฌ๋ฉ๋๋ค.
- LoadLibraryA ํธ์ถ
- PE Export Table ํ์ฑ
- ์ด๋ฆ ๋ฌธ์์ด ๋ฐ๋ณต โ hashing
- ํด์ ๋งค์นญ ์ Ordinal โ Function RVA โ VA ๋ณํ
- ๋ฐํ๋ ์ฃผ์ ๊ธฐ๋ฐ์ ํ์ ๋ก์ง ์กด์ฌ ์ฌ๋ถ ๊ฒ์ฌ
- ์ํฐ๋๋ฒ๊น ์คํ ๊ณผ ๊ฒฐํฉ ์ฌ๋ถ ํ์ธ
- ์ด์ ๋์ ํ์ง ์ ์๋๋ฐ์ค/๋ฉ๋ชจ๋ฆฌ ๋คํ ํ ์คํ ํ๋ฆ ์ฌ๊ตฌ์ฑ
6. ์ ๋ฆฌ
- PE ๊ตฌ์กฐ: Export Directory Table, Name/Ordinal/Function ๋ฐฐ์ด ๊ด๊ณ
- ์คํ ํ๋ ์: Caller-cleaned ํธ์ถ ๊ท์ฝ์ ESP ์กฐ์
- ๋์ค์ด์ ๋ธ ํจํด: ์ด๋ฆ ๋ฐ๋ณต ๋ฃจํ, RVAโVA ๋ณํ
- ์ ์ด ํ๋ฆ ๋ถ์: ํด์ ๋น๊ต โ ๋ถ๊ธฐ โ ์กฐ๊ธฐ ์ข ๋ฃ
- API ํํน ๋์: IAT ๋ฏธ์ฌ์ฉ์ผ๋ก ํํน ์ฐํ
- ์ํธํ ๋ณต์ ํจํด: Hash ๊ธฐ๋ฐ ๋ฌธ์์ด ๋งคํ
- ๋์ ๋ก๋ฉ ์๋ช : LoadLibraryA + ์ปค์คํ resolver
7. ๋ง๋ฌด๋ฆฌ
๋์ API ํจํด์ ๋จ์ํด ๋ณด์ด์ง๋ง, PE ๊ตฌ์กฐ์ ์ ์ด ํ๋ฆ์ ๊น์ด ์ดํดํ ์๋ก ๊ทธ ์์์ ๋๋ฌ๋๋ โ๋ก๋์ ์๋โ๋ฅผ ๋์ฑ ๋ช ํํ ํฌ์ฐฉํ ์ ์์ต๋๋ค.
๐ Written by Code & Compass