본문 바로가기

카테고리 없음

2024.8.17 리버싱 공부(나뭇잎 책)

API 후킹 DLL 인젝션 - IAT후킹

 

dynamic방식, process memory iat 변경 ,injection 

loadlibrary로 동적으로 로딩하는 경우는 사용 불가(IAT에 없어서)

 

책에선 계산기 후킹해서 숫자를 한글로 바꾸지만, 나는 저번 예제와 같이 writefile 후킹해서 소문자를 대문자로 바꾸게 바꿔보겠음.

 

방법을 먼저 간단하게 설명하면 파일은 2개인데, 하나는 DLL을 injection/ejection하는 파일,

 

다른 하나는 DLL 파일이다.

 

DLL파일을 간단하게 설명하면

 

DLL이 붙을때, 후킹할 dll이름, 후킹할 함수의 주소, 후킹함수 주소를 넘겨주고 Import Directory를 찾아서 name으로 후킹할 DLL의 이름을 비교해서 같으면 그 IAT에서 후킹할 함수의 주소를 비교해서 찾아서 후킹 함수 주소로 덮어쓴다.

 

떼질때도 같다 후킹할 함수의 주소와 후킹함수 주소를 반대로 넣어줘서 이미 후킹함수로 덮어써져 있으니 원래 함수로 바꿔준다.

 

 

DLL injection/ejection

#include "windows.h"
#include "stdio.h"
#include "tlhelp32.h"
#include "tchar.h"
#include "winbase.h"


#define DEF_PROC_NAME (L"notepad.exe")
#define DEF_DLL_NAME (L"C:\\Users\\a0102\\source\\repos\\Hook_Iat_Dll\\x64\\Release\\Hook_Iat_Dll.dll")

LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xCC, g_chOrgByte = 0;

DWORD FindProcessID(LPCTSTR szProcessName)
{
    DWORD dwPID = 0xFFFFFFFF;
    HANDLE hSnapShot = INVALID_HANDLE_VALUE;
    PROCESSENTRY32 pe;

    // Get the snapshot of the system
    pe.dwSize = sizeof(PROCESSENTRY32);
    hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);

    // find process
    Process32First(hSnapShot, &pe);
    do
    {
        if (!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
        {
            dwPID = pe.th32ProcessID;
            break;
        }
    } while (Process32Next(hSnapShot, &pe));

    CloseHandle(hSnapShot);

    return dwPID;
}


BOOL InjectDll(DWORD dwPID, LPCTSTR szDllName)
{
HANDLE hProcess, hThread;
LPVOID pRemoteBuf;
DWORD dwBufSize = (DWORD)(_tcslen(szDllName) + 1) * sizeof(TCHAR);
LPTHREAD_START_ROUTINE pThreadProc;

if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
DWORD dwErr = GetLastError();
return FALSE;
}

pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);

pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}


BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
BOOL bMore = FALSE, bFound = FALSE;
HANDLE hSnapshot, hProcess, hThread;
MODULEENTRY32 me = { sizeof(me) };
LPTHREAD_START_ROUTINE pThreadProc;

if (INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)))
return FALSE;

bMore = Module32First(hSnapshot, &me);
for (;bMore;bMore = Module32Next(hSnapshot, &me))
{
if (!_tcsicmp(me.szModule, szDllName) || !_tcsicmp(me.szExePath, szDllName))
{
bFound = TRUE;
break;
}
}

if (!bFound)
{
CloseHandle(hSnapshot);
return FALSE;
}

if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
CloseHandle(hSnapshot);
return FALSE;
}

pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "FreeLibrary");
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);
WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);
CloseHandle(hSnapshot);

return TRUE;
}

int _tmain(int argc, TCHAR* argv[])
{
DWORD dwPID = (DWORD)FindProcessID(DEF_PROC_NAME);

InjectDll(dwPID, DEF_DLL_NAME);

EjectDll(dwPID, DEF_DLL_NAME);

return 0;
}


main -> injectDll -> createRemoteThread에 loadlibrary와 인자로 dll 위치 스트링 주소를 넘겨줌 -> 프로세스에서 loadlibrary 호출해서 injection 됨
main -> ejectDll -> dll 정보를 추출하는 함수로 ejection할 dll 주소 찾기 -> createRemoteThread에 freelibrary와 인자로 dll 주소 -> 프로세스에서 freelibrary 호출해서 ejection 됨

 

 

 

 

DLL

// include
#include "stdio.h"
#include "wchar.h"
#include "windows.h"
#include "pch.h"
#include "tlhelp32.h"
#include "tchar.h"

#include <stdlib.h>
typedef unsigned __int64 QWORD;

typedef BOOL(WINAPI* WRITEFILE)(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped);

FARPROC g_pOrgFunc = NULL;

BOOL WINAPI MyWriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
{
   int nLen = nNumberOfBytesToWrite;

   PBYTE lpBuffer_ = NULL;
   lpBuffer_ = (PBYTE)malloc(nNumberOfBytesToWrite + 1);
   memset(lpBuffer_, 0, nNumberOfBytesToWrite + 1);
   memcpy(lpBuffer_, lpBuffer, nNumberOfBytesToWrite);

    for (int i = 0; i < nLen; i++)
    {
        if (0x61 <= lpBuffer_[i] && lpBuffer_[i] <= 0x7A)
            lpBuffer_[i] -= 0x20;
    }

    memcpy((void*)lpBuffer, (void*)lpBuffer_, nNumberOfBytesToWrite);
    free(lpBuffer_);
    
    return ((WRITEFILE)g_pOrgFunc)( hFile,  lpBuffer,  nNumberOfBytesToWrite,  lpNumberOfBytesWritten,  lpOverlapped);
}


// hook_iat
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
    HMODULE hMod;
    LPCSTR szLibName;
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
    PIMAGE_THUNK_DATA pThunk;
    QWORD  dwRVA;
    PBYTE pAddr;
    DWORD dwOldProtect;

    // hMod, pAddr = ImageBase of calc.exe
    //             = VA to MZ signature (IMAGE_DOS_HEADER)
    hMod = GetModuleHandle(NULL);
    pAddr = (PBYTE)hMod;

    // pAddr = VA to PE signature (IMAGE_NT_HEADERS)
    //pAddr += *((DWORD*)&pAddr[0x100]);

    // dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table
    dwRVA = *((DWORD*)&pAddr[0x190]);

    // pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table
    pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((QWORD)hMod + dwRVA);

    for (; pImportDesc->Name; pImportDesc++)
    {
        // szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name
        szLibName = (LPCSTR)((QWORD)hMod + pImportDesc->Name);
        if (_stricmp(szLibName, szDllName) == 0)
        {
            // pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
            //        = VA to IAT(Import Address Table)
            pThunk = (PIMAGE_THUNK_DATA)((QWORD)hMod +
                pImportDesc->FirstThunk);

            // pThunk->u1.Function = VA to API
            for (; pThunk->u1.Function; pThunk++)
            {
                if (pThunk->u1.Function == (QWORD)pfnOrg)
                {
                    VirtualProtect((LPVOID)&pThunk->u1.Function,  8, PAGE_EXECUTE_READWRITE, &dwOldProtect);

                    pThunk->u1.Function = (QWORD)pfnNew;

                    VirtualProtect((LPVOID)&pThunk->u1.Function,4,dwOldProtect,  &dwOldProtect);
                        
                    return TRUE;
                }
            }
        }
    }

    return FALSE;
}



BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    HMODULE hMod = GetModuleHandle(L"kernel32.dll");
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
         hMod = GetModuleHandle(L"kernel32.dll");
        g_pOrgFunc = GetProcAddress(hMod, "WriteFile");

        hook_iat("kernel32.dll", g_pOrgFunc, (PROC)MyWriteFile);
        break;

    case DLL_PROCESS_DETACH:
        hook_iat("kernel32.dll", (PROC)MyWriteFile, g_pOrgFunc);
        break;
    }

    return TRUE;
}


attach -> hook_iat -> Import Directory 위치 구하기 [imagebase + 0x190] -> DLL 이름 비교해서 찾기 -> 함수 주소 비교해서 찾기 -> 덮어쓰기

 

detach -> hook_iat -> Import Directory 위치 구하기 [imagebase + 0x190] -> DLL 이름 비교해서 찾기 -> 함수 주소 비교해서 찾기 -> 덮어쓰기   

 

정리 

DLL 인젝션하면 인젝션 된 프로세스의 메모리를 DLL에서 자유롭게 쓸 수 있어서 후킹할 API의 IAT를 찾아서 DLL에 있는 함수로 수정하고 가지고 논다.

 

 

후킹돼서 writeFile이 hook_iat_dll의 Mywritefile로 된 모습

 

 

unhook 된 모습