WriteFileHook.cpp

// The MIT License
// Copyright (c) 2019 Sanghyeon Jeon
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "pch.h" // Precompiled Header
#include <stdio.h>
#include <Windows.h>
#pragma pack(push, 1)
struct JMP_5Bytes
{
BYTE opcode; // 0xE9 : Relative Jump
LPVOID lpTarget;
};
struct JMP_6Bytes
{
SHORT opcode; // 0x25FF : Absolute Jump
LPVOID lpTarget;
};
struct JMP_14Bytes // Absolute Jump for 64bit
{
BYTE opcode1; // 0x68 : Push
DWORD lpTarget1; // Address - 4~8
DWORD opcode2; // 0x042444C7 : MOV [ESP+4], ????
DWORD lpTarget2; // Address - 0~4
BYTE opcode3; // 0xC3 : RET
};
#pragma pack(pop)
const char* OrgFP = "\x8B\xFF\x55\x8B\xEC"; // MOV EDI, EDI; PUSH EBP; MOV EBP, ESP;
const char* FPandJmp5Bytes = "\x55\x8B\xEC\xEB\x05"; // PUSH EBP; MOV EBP, ESP; JMP $(Current)+0x5;
typedef BOOL WINAPI tWriteFile(
_In_ HANDLE hFile,
_In_ LPCVOID lpBuffer,
_In_ DWORD nNumberOfBytesToWrite,
_Out_opt_ LPDWORD lpNumberOfBytesWritten,
_Inout_opt_ LPOVERLAPPED lpOverlapped
);
#ifdef _WIN64
DWORD WINAPI Hook64();
DWORD WINAPI Unhook64();
#else
DWORD WINAPI Hook32();
DWORD WINAPI Unhook32();
#endif
tWriteFile* newWriteFile;
tWriteFile* orgWriteFile;
JMP_14Bytes orgFP;
BOOL Hooked = FALSE;
BOOL WINAPI NewWriteFile(
_In_ HANDLE hFile,
_In_ LPCVOID lpBuffer,
_In_ DWORD nNumberOfBytesToWrite,
_Out_opt_ LPDWORD lpNumberOfBytesWritten,
_Inout_opt_ LPOVERLAPPED lpOverlapped
)
{
if (nNumberOfBytesToWrite > 0)
MessageBoxA(NULL, (LPCSTR)lpBuffer, NULL, NULL);
#ifdef _WIN64
Unhook64();
BOOL ret = WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
Hook64();
#else
Unhook32();
BOOL ret = WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
Hook32();
#endif
return ret;
}
DWORD WINAPI Hook32()
{
if (Hooked)
return 0; // Already Hooked
// Get address of target function
LPVOID lpOrgFunc = NULL;
if ((lpOrgFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile")) == NULL)
return -1;
// Check Inline Hook or KernelBase-Redirect-Version Hook
if (*(SHORT*)lpOrgFunc == 0x25FF) // KernelBase-Redirect
{
// Backup old protect
DWORD dwOldProtect;
if (VirtualProtect(lpOrgFunc, sizeof(JMP_6Bytes), PAGE_EXECUTE_READWRITE, &dwOldProtect) == NULL)
return -1;
// Backup old function IAT
JMP_6Bytes * lpSavedFunc = (JMP_6Bytes*)VirtualAlloc(NULL, sizeof(JMP_6Bytes), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
JMP_6Bytes newFuncObj;
memcpy_s(lpSavedFunc, sizeof(JMP_6Bytes), lpOrgFunc, sizeof(JMP_6Bytes));
orgWriteFile = (tWriteFile*)lpSavedFunc->lpTarget;
// Absolute Jump
newFuncObj.opcode = 0x25FF;
// Set new functon to replace
newWriteFile = &NewWriteFile;
newFuncObj.lpTarget = &newWriteFile;
// Replacing
memcpy_s(lpOrgFunc, sizeof(JMP_6Bytes), &newFuncObj, sizeof(JMP_6Bytes));
// Rollback protection
VirtualProtect(lpOrgFunc, sizeof(JMP_6Bytes), dwOldProtect, NULL);
}
else
{ // Inline Hook
orgWriteFile = (tWriteFile*)((DWORD)lpOrgFunc - 5);
// Backup old protect
DWORD dwOldProtect;
if (VirtualProtect((LPVOID)((DWORD)lpOrgFunc - 5), 10, PAGE_EXECUTE_READWRITE, &dwOldProtect) == NULL)
return -1;
JMP_5Bytes newFuncObj;
newFuncObj.opcode = 0xE9; // Relative Jump
newFuncObj.lpTarget = (LPVOID)((DWORD)(&NewWriteFile) - (DWORD)lpOrgFunc - 5); // Set new functon to replace
memcpy_s((LPVOID)((DWORD)lpOrgFunc - 5), 5, FPandJmp5Bytes, 5);// Replacing
memcpy_s(lpOrgFunc, 5, &newFuncObj, 5);
// Rollback protection
VirtualProtect((LPVOID)((DWORD)lpOrgFunc - 5), 10, dwOldProtect, NULL);
}
Hooked = TRUE;
return 0;
}
DWORD WINAPI Unhook32()
{
if (!Hooked)
return 0; // Not Hooked
LPVOID lpOrgFunc = NULL;
if ((lpOrgFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile")) == NULL)
return -1;
// Check Inline Hook or KernelBase-Redirect-Version Hook
if (*(SHORT*)lpOrgFunc == 0x25FF) // KernelBase-Redirect
{
// Backup old protect
DWORD dwOldProtect;
if (VirtualProtect(lpOrgFunc, sizeof(JMP_6Bytes), PAGE_EXECUTE_READWRITE, &dwOldProtect) == NULL)
return -1;
// Set org functon to replace
JMP_6Bytes newFuncObj;
newFuncObj.opcode = 0x25FF;
newFuncObj.lpTarget = orgWriteFile;
// Replacing
memcpy_s(lpOrgFunc, sizeof(JMP_6Bytes), &newFuncObj, sizeof(JMP_6Bytes));
// Rollback protection
VirtualProtect(lpOrgFunc, sizeof(JMP_6Bytes), dwOldProtect, NULL);
}
else
{ // Inline Hook
// Backup old protect
DWORD dwOldProtect;
if (VirtualProtect((LPVOID)((DWORD)lpOrgFunc - 5), 10, PAGE_EXECUTE_READWRITE, &dwOldProtect) == NULL)
return -1;
JMP_5Bytes newFuncObj;
newFuncObj.opcode = 0xE9; // Relative Jump
newFuncObj.lpTarget = (LPVOID)((DWORD)(&NewWriteFile) - (DWORD)lpOrgFunc - 5); // Set new functon to replace
memset((tWriteFile*)((DWORD)lpOrgFunc - 5), 0x90, 5);// Set 5-byte NOP
memcpy_s(lpOrgFunc, 5, OrgFP, 5);
// Rollback protection
VirtualProtect((LPVOID)((DWORD)lpOrgFunc - 5), 10, dwOldProtect, NULL);
}
Hooked = FALSE;
return 0;
}
DWORD WINAPI Hook64()
{
if (Hooked)
return 0; // Already Hooked
// Get address of target function
LPVOID lpOrgFunc = NULL;
if ((lpOrgFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile")) == NULL)
return -1;
// Check KernelBase-Redirect
if (*(SHORT*)lpOrgFunc == 0x25FF)
{
if ((lpOrgFunc = GetProcAddress(GetModuleHandleA("kernelbase.dll"), "WriteFile")) == NULL)
return -1;
}
// Inline Hook
// Backup old protect
DWORD dwOldProtect;
if (VirtualProtect(lpOrgFunc, sizeof(JMP_14Bytes), PAGE_EXECUTE_READWRITE, &dwOldProtect) == NULL)
return -1;
memcpy_s(&orgFP, sizeof(JMP_14Bytes), lpOrgFunc, sizeof(JMP_14Bytes));
JMP_14Bytes newFuncObj;
newFuncObj.opcode1 = 0x68; // Push ????
newFuncObj.lpTarget1 = (DWORD)((DWORD64)(&NewWriteFile) & 0xFFFFFFFF);
newFuncObj.opcode2 = 0x042444C7; // MOV [ESP+4], ????
newFuncObj.lpTarget2 = (DWORD)((DWORD64)(&NewWriteFile) >> 32);
newFuncObj.opcode3 = 0xC3; // RET
memcpy_s(lpOrgFunc, sizeof(JMP_14Bytes), &newFuncObj, sizeof(JMP_14Bytes)); // Replacing
// Rollback protection
VirtualProtect(lpOrgFunc, sizeof(JMP_14Bytes), dwOldProtect, NULL);
Hooked = TRUE;
return 0;
}
DWORD WINAPI Unhook64()
{
if (!Hooked)
return 0; // Not Hooked
LPVOID lpOrgFunc = NULL;
if ((lpOrgFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile")) == NULL)
return -1;
// Check KernelBase-Redirect
if (*(SHORT*)lpOrgFunc == 0x25FF)
{
if ((lpOrgFunc = GetProcAddress(GetModuleHandleA("kernelbase.dll"), "WriteFile")) == NULL)
return -1;
}
// Inline Hook
// Backup old protect
DWORD dwOldProtect;
if (VirtualProtect(lpOrgFunc, sizeof(JMP_14Bytes), PAGE_EXECUTE_READWRITE, &dwOldProtect) == NULL)
return -1;
// reset org FP
memcpy_s(lpOrgFunc, sizeof(JMP_14Bytes), &orgFP, sizeof(JMP_14Bytes));
// Rollback protection
VirtualProtect(lpOrgFunc, sizeof(JMP_14Bytes), dwOldProtect, NULL);
Hooked = FALSE;
return 0;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBoxA(NULL, "Hook Ready", "Hook Ready", MB_OK);
#ifdef _WIN64
Hook64();
#else
Hook32();
#endif
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}


 - 위 소스는 Kernel32.dll의 WriteFile 함수를 후킹하는 DLL의 소스코드입니다. 32비트의 경우 밑에서 설명할 Redirect 루틴을 탈 경우 0x25FF(Absolute Jump)를 사용하고, 실제 함수 구현체 루틴으로 갈 경우 E9(Relative Jump)를 사용합니다. 64비트의 경우 Redirect 루틴을 탈 경우 KernelBase의 함수를 후킹하며, 실제 함수 구현체 루틴으로 갈 경우엔 해당 함수를 바로 후킹하는데 둘 모두 기존 코드를 점프에 필요한 크기만큼 백업해두고 인라인 후킹을 진행합니다.


 - Windows 7에서부터 Kernel32.dll 외에 KernelBase.dll 이라는 DLL 파일이 생겨 일부 함수들에 대해 구현은 KernelBase.dll에 존재하고 Kernel32.dll 을 통해호출시 KernelBase.dll 의 함수로 리다이렉트시킵니다. 자세한 내용은 여기 참고. Redirect Call 명령은 0x25FF 로 Absolute Jump를 하고 있기 때문에 x86 환경에서는 이를 주소 오퍼랜드만 변경하여 쉽게 후킹이 가능하지만 x64환경에서는 6바이트만으로는 원하는 메모리 주소로의 점프가 불가능합니다. 이를 위해 위 코드에서는 64비트 환경에서 KernelBase.dll 로의 리다이렉트 코드를 발견[각주:1]할 경우 KernelBase.dll 에서 해당 함수의 주소를 가져와 인라인 후킹을 진행하도록 작성하였습니다.


 - 함수를 후킹한 후 내부에서 기존 함수를 실행하여 결과를 그대로 반환하고 싶은 경우에는 인라인 후킹을 진행할 때 NOP 다섯 개와 MOV EDI, EDI; PUSH EBP; MOV EBP, ESP로 총 10바이트의 여유 공간이 있기 때문에 이를 이용하여 후킹 함수로의 점프 5바이트, 후킹 함수 내에서 기존 함수 주소 -5로의 점프[각주:2], 기존 함수 주소 -5에 PUSH EBP; MOV EBP, ESP와 기존 함수 주소 +5로의 점프(EB 05)[각주:3]를 넣어 언훅을 하지 않고도 호출이 가능하게 할 수 있습니다. 하지만 x64에서는 함수 도입부가 정형화되어있지 않은데다 위에서 설명한 KernelBase.dll로의 Redirect Call이 있을 경우 6바이트의 여유밖에 없기 때문에 이것이 불가능하여 위 코드에서는 어떤 방식으로 후킹을 하던 간에 내부에서 언훅 후 기존 함수를 실행하고, 다시 후킹을 건 다음 리턴하도록 작성해 두었습니다.


 - 145번째 줄에서 타겟 함수 주소 - 5를 하는 이유는 여기 참고




  1. 260번째 줄 [본문으로]
  2. 145번째 줄 [본문으로]
  3. 148번째 줄 [본문으로]
블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,