어셈블리 튜토리얼 (18) 64비트 api hooking(Trampoline #2)

4.5. api hooking(Trampoline #2)

좀 더 유연한 Trampoline방식과 조금 다른 방식의 소스이다. 원래의 함수를 이어붙인 이전방식과 달리 매번 후킹을 해제했다가 다시거는 방식이다. 후킹된 함수들을 관리하기위해 후킹 관련 정보를 담은 구조체를 추가하였다.

앞서본 Trampoline처럼 기본구현 방식은 14byte(jmp코드)를 덮어씌우는 방식으로 동일하다. 후킹함수내에서 원래 함수를 호출하는 부분만 관심있게 보면 되겠다.

option casemap:none
;option frame:auto

include D:\WinInc208\Include\windows.inc
;include D:\WinInc208\Include\user32.inc
;include D:\WinInc208\Include\kernel32.inc

includelib user32.lib
includelib kernel32.lib
includelib winmm.lib

LoadApiHook proto, lpApihookStruct:qword
RemoveApiHook proto, lpApihookStruct:qword
GetMsgProc proto, nCode:dword, wParam:WPARAM, lParam:LPARAM
AddApiHook proto, lpszDll:qword, lpszProc:qword, lpTossProc:qword
PatchAddress proto lpApihookStruct:qword
UnpatchAddress proto lpApihookStruct:qword

MyMessageBoxA proto, arg1:qword, arg2:qword, arg3:qword, arg4:dword

ApiHookStruct struct
    szDll           byte 32 dup(0)
    szProc          byte 32 dup(0)
    lpTossProc      qword 0
    lpOrgProc       qword 0
    dwOrgProtect    dword 0
    StubOrg         byte 14 dup(0)
    StubHook        byte 14 dup(0)
    bHooking        byte 0
ApiHookStruct ends

.data
lpApiMessageBoxA    qword 0
lpApiMessageBoxW    qword 0

szUSER32        db 'USER32.DLL',0
szMessageBoxA       db 'MessageBoxA',0
szMessageBoxW       db 'MessageBoxW',0

.data?
szVictim            byte 50 dup(?)
hCBTHook            qword ?
hGlobalModule       qword ?

.code
DllEntry proc hInstance:HINSTANCE, reason:DWORD, dwReserved:DWORD
    mov hInstance, rcx
    mov reason, edx
    mov dwReserved, r9d

    .if reason==DLL_PROCESS_ATTACH
        .if hGlobalModule==0
            push hInstance
            pop hGlobalModule
        .endif
        ;invoke MessageBox, 0, addr szVictim, addr szVictim, 0
        invoke GetModuleHandle, addr szVictim
        .if eax!=0
            ;invoke MessageBoxA, NULL, addr szVictim, addr szVictim, MB_OK

            invoke AddApiHook, addr szUSER32, addr szMessageBoxA, addr MyMessageBoxA
            mov lpApiMessageBoxA, rax
            invoke LoadApiHook, rax
        .endif
    .elseif reason==DLL_PROCESS_DETACH
        invoke GetModuleHandle, addr szVictim
        .if eax!=0
            invoke RemoveApiHook, lpApiMessageBoxA
        .endif
    .endif

    mov rax, TRUE
    ret
DllEntry Endp

GetMsgProc proc nCode:dword, wParam:WPARAM, lParam:LPARAM
    mov nCode, ecx
    mov wParam, rdx
    mov lParam, r8

    invoke CallNextHookEx, hCBTHook, nCode, wParam, lParam
    ret
GetMsgProc endp

AddApiHook proc uses rsi, lpszDll:qword, lpszProc:qword, lpTossProc:qword
    mov lpszDll, rcx
    mov lpszProc, rdx
    mov lpTossProc, r8

    invoke GlobalAlloc, GMEM_FIXED, sizeof ApiHookStruct
    mov rsi, rax
    assume rsi:ptr ApiHookStruct
    mov rax, lpTossProc
    mov [rsi].lpTossProc, rax
    invoke lstrcpy, addr [rsi].szDll, lpszDll
    invoke lstrcpy, addr [rsi].szProc, lpszProc
    mov rax, rsi
    ret
AddApiHook endp

RemoveApiHook proc uses rsi rdi rbx, lpApihookStruct:qword
    mov lpApihookStruct, rcx

    mov rbx, lpApihookStruct
    assume rbx:ptr ApiHookStruct
    mov al, [rbx].bHooking

    .if al==1
        ; 원본 stub 복구
        lea rsi, [rbx].StubOrg
        mov rdi, [rbx].lpOrgProc
        movsq
        movsd
        movsw

        ; Protect 복구
        invoke VirtualProtect, [rbx].lpOrgProc, 14, [rbx].dwOrgProtect, 0
    .endif
    ret
RemoveApiHook endp

LoadApiHook proc uses rsi rdi rbx, lpApihookStruct:qword
    local hModule:qword
    local lpOrgProc:qword
    local dwOrgProtect:dword
    local mbi:MEMORY_BASIC_INFORMATION

    mov lpApihookStruct, rcx

    mov rbx, lpApihookStruct
    assume rbx:ptr ApiHookStruct

    invoke GetModuleHandle, addr [rbx].szDll
    mov hModule, rax
    .if rax==0
        jmp LOAD_HOOK_EXIT
    .endif

    invoke GetProcAddress, hModule, addr [rbx].szProc
    mov [rbx].lpOrgProc, rax
    mov lpOrgProc, rax
    .if rax==0
        jmp LOAD_HOOK_EXIT
    .endif

    invoke VirtualQuery, lpOrgProc, addr mbi, sizeof mbi
    mov eax, mbi.Protect
    and eax, not PAGE_READONLY
    and eax, not PAGE_EXECUTE_READ
    ;or eax, PAGE_READWRITE
    or eax, PAGE_EXECUTE_READWRITE
    lea rsi, dwOrgProtect
    invoke VirtualProtect, lpOrgProc, 14, eax, rsi
    mov eax, dwOrgProtect
    mov [ebx].dwOrgProtect, eax

    ; 원본 stub 백업
    mov rsi, lpOrgProc
    lea rdi, [rbx].StubOrg
    movsq
    movsd
    movsw

    ; Hook 함수로 점프하는 stub
    mov rax, [rbx].lpTossProc
    ;sub rax, lpOrgProc
    ;sub rax, 14

    ; push 000000h ; 68 DWORD
    ; mov [rsp+4], 000000h ; c7 44 24 04 DWORD
    ; ret ; c3
    mov byte ptr [rbx].StubHook, 68h
    mov dword ptr [rbx].StubHook + 1, eax
    mov byte ptr [rbx].StubHook + 5, 0c7h
    mov byte ptr [rbx].StubHook + 6, 44h
    mov byte ptr [rbx].StubHook + 7, 24h
    mov byte ptr [rbx].StubHook + 8, 04h
    shr rax, 32
    mov dword ptr [rbx].StubHook + 9, eax
    mov byte ptr [rbx].StubHook + 13, 0c3h

    ; hook stub으로 교체
    lea rsi, [rbx].StubHook
    mov rdi, [rbx].lpOrgProc
    movsq
    movsd
    movsw

    mov al, 1
    mov [rbx].bHooking, al

    xor rax, rax

LOAD_HOOK_EXIT:
    ret
LoadApiHook endp

; export functions
SetVictim proc lpszVictim:qword
    mov lpszVictim, rcx
    invoke lstrcpy, addr szVictim, lpszVictim
    ret
SetVictim endp

StartHook proc
    invoke SetWindowsHookEx, WH_CBT, addr GetMsgProc, hGlobalModule, NULL
    mov hCBTHook, rax
    ret
StartHook endp

EndHook proc
    .if hCBTHook!=0
        invoke UnhookWindowsHookEx, hCBTHook
    .endif
    ret
EndHook endp


RestoreFunc:
    push rsi
    push rdi

    assume rax:ptr ApiHookStruct

    ; 원본 stub 복구
    lea rsi, [rax].StubOrg
    mov rdi, [rax].lpOrgProc
    movsq
    movsd
    movsw

    pop rdi
    pop rsi
    ret

HookFunc:
    push rsi
    push rdi

    assume rax:ptr ApiHookStruct

    ; hook stub으로 교체
    lea rsi, [rax].StubHook
    mov rdi, [rax].lpOrgProc
    movsq
    movsd
    movsw

    pop rdi
    pop rsi
    ret

; hook functions
MyMessageBoxA proc, arg1:qword, arg2:qword, arg3:qword, arg4:dword
    mov arg1, rcx
    mov arg2, rdx
    mov arg3, r8
    mov arg4, r9d

    ; 원래 함수를 호출하기 위한 루틴
    ;-----------------------------------------
    mov rax, lpApiMessageBoxA
    call RestoreFunc
    ;-----------------------------------------

    ; 원래 함수 호출
    mov rax, lpApiMessageBoxA
    assume rax:ptr ApiHookStruct
    mv rax, [rax].lpOrgProc ; 원래의 함수 주소를 구한다.

    mov r9d, arg4
    mov r8, arg3
    mov rdx, arg2
    mov rcx, arg1
    sub esp, 10h
    call rax ; MessageBoxA 호출
    add esp, 10h

        ; 다시 후킹을 건다.
    ;-----------------------------------------
    push rax
    mov rax, lpApiMessageBoxA
    call HookFunc
    pop rax
    ;-----------------------------------------

    ; 결과를 변경
    ; 무조건 cancel
    mov rax, IDCANCEL

    ret
MyMessageBoxA endp

end DllEntry
ApiHookStruct struct
    szDll           byte 32 dup(0) ; DLL명
    szProc          byte 32 dup(0) ; 함수명
    lpTossProc      qword 0        ; 새로운 함수주소(후킹 함수)
    lpOrgProc       qword 0        ; 원래의 함수주소
    dwOrgProtect    dword 0                ; 원래의 Memory Protect값을 저장해두는데 크게 의미없다.
    StubOrg         byte 14 dup(0) ; 원래의 함수 앞부분 (14byte)
    StubHook        byte 14 dup(0) ; 덮어쓸 코드 (후킹 함수로 jmp하는 코드)
    bHooking        byte 0         ; 후킹여부 확인 크게 의미없다.
ApiHookStruct ends

후킹된 함수의 정보를 담은 구조체가 추가되었다. 매번 후킹을 토글해야하므로 관련정보를 저장해서 관리하기위한 구조체이다.

            invoke AddApiHook, addr szUSER32, addr szMessageBoxA, addr MyMessageBoxA
            mov lpApiMessageBoxA, rax
            invoke LoadApiHook, rax

후킹을 거는 함수가 두부분으로 나누어졌다. AddApiHook에서 후킹할 함수정보를 받아서 ApiHookStruct구조체를 생성한다. 메모리 할당을 위해 GlobalAlloc 를 이용한다. 리턴값으로 생성된 구조체의 주소 받아서 lpApiMessageBoxA에 저장한다. 여기서는 MessageBoxA를 후킹하고 있다.

LoadApiHook에서 ApiHookStruct를 파라메터로 받아서 후킹을 걸고 각종 후킹정보를 저장한다. 앞으로 이 정보를 통해서 후킹을 토글하게된다. LoadApiHook 함수는 이전 방식의 LoadApiHook함수와 거의 동일하니 비교하며 훑어보면 되겠다. 이전 방식과 달리 무조건 14byte를 덮어씌운다. 기존 함수의 시작부분 14byte를 구조체에 저장해둔다.

    ; 원본 stub 백업
    mov rsi, lpOrgProc
    lea rdi, [rbx].StubOrg
    movsq
    movsd
    movsw

기존에 rep movsb를 이용하여 복사하던 것과 달리 movsq movsd movsw 로 복사한다. 각각 qword 8byte복사 + dword 4byte복사 + word 2byte복사로 14byte복사와 동일하다. 기존처럼하면 이렇게 할 수 있겠다.

mov rsi, lpOrgProc
lea rdi, [rbx].StubOrg
mov rcx, 14
rep movsb
RestoreFunc:
.
.
.

HookFunc:
.
.
.

naked 함수이다. ApiHookStruct구조체 주소를 rax를 파라메터로 받는다. RestoreFunc는 원래의 함수로 복구하고, HookFunc는 다시 후킹을 한다. 뒤에서 원래의 함수를 호출할때 사용된다.

MyMessageBoxA proc, arg1:qword, arg2:qword, arg3:qword, arg4:dword

MessageBoxA를 후킹한 함수이다. 원래의 MessageBoxA를 호출하기위해 RestoreFunc를 호출하고 ApiHookStruct->lpOrgProc 를 호출한다. 종료하기전에 HookFunc를 호출하여 다시 후킹을 건다.

이전 방식에 비해서 매번 후킹을 토글해야하는 성능적인 하락은 있을 수 있지만 소스는 오히려 깔끔해졌다.

다음에는 간단하게 64bit speed hack 테스트만하고 넘어가도록하자.

목차 이전글 어셈블리 튜토리얼 (17) 64비트 api hooking(Trampoline)