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 로의 리다이렉트 코드를 발견할 경우 KernelBase.dll 에서 해당 함수의 주소를 가져와 인라인 후킹을 진행하도록 작성하였습니다. 1
- 함수를 후킹한 후 내부에서 기존 함수를 실행하여 결과를 그대로 반환하고 싶은 경우에는 인라인 후킹을 진행할 때 NOP 다섯 개와 MOV EDI, EDI; PUSH EBP; MOV EBP, ESP로 총 10바이트의 여유 공간이 있기 때문에 이를 이용하여 후킹 함수로의 점프 5바이트, 후킹 함수 내에서 기존 함수 주소 -5로의 점프, 기존 함수 주소 -5에 PUSH EBP; MOV EBP, ESP와 기존 함수 주소 +5로의 점프(EB 05) 2를 넣어 언훅을 하지 않고도 호출이 가능하게 할 수 있습니다. 하지만 x64에서는 함수 도입부가 정형화되어있지 않은데다 위에서 설명한 KernelBase.dll로의 Redirect Call이 있을 경우 6바이트의 여유밖에 없기 때문에 이것이 불가능하여 위 코드에서는 어떤 방식으로 후킹을 하던 간에 내부에서 언훅 후 기존 함수를 실행하고, 다시 후킹을 건 다음 리턴하도록 작성해 두었습니다. 3
- 145번째 줄에서 타겟 함수 주소 - 5를 하는 이유는 여기 참고
'Programming' 카테고리의 다른 글
LD_PRELOAD를 이용한 가변 인자 함수 후킹 (0) | 2019.04.19 |
---|---|
[C/C++] extern "C"와 네임 맹글링 (6) | 2019.03.29 |
Python + selenium을 이용한 네이버 메일 크롤러 (3) | 2018.09.07 |
Visual Studio에서 보호 기법 해제하고 바이너리 빌드하기 (0) | 2018.07.18 |
[Python] PyV8을 이용한 Javascript 분석 (3) | 2018.04.27 |