// dllmain.cpp : DLL 애플리케이션의 진입점을 정의합니다.
#include "pch.h"
#include<stdio.h>
#include<Windows.h>
#pragma pack(push,1)//구조체 데이터 정렬크기를 1바이트로 정렬, 구조체 변수간에 공간이 있을수있기에 1바이트로 할당하는것같다.
struct GOGO_NAM {
BYTE opcode1;
DWORD lpTarget1;
DWORD opcode2;
DWORD lpTarget2;
BYTE opcode3;
};
#pragma pack(pop)//위의 설정 이전상태로 돌림
DWORD WINAPI Hook64();
DWORD WINAPI Unhook64();
GOGO_NAM orgFP;
BOOL Hooked = FALSE;
INT WINAPI NewWriteFile(
_In_ HANDLE hFile,
_In_ LPCVOID lpBuffer,
_In_ DWORD nNumberOfBytesToWrite,
_Out_opt_ LPDWORD lpNumberOfBytesWritten,
_Inout_opt_ LPOVERLAPPED lpOverlapped) {
MessageBoxA(NULL, (LPCSTR)lpBuffer, "hooking api call success", MB_OK);
for (int i = 0; i < 10; i++) {
MessageBoxA(NULL, "내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈", "hooking api call success", MB_OK);
}
Unhook64();
BOOL ret = WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
Hook64();
return ret;
}
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(GOGO_NAM), PAGE_EXECUTE_READWRITE, &dwOldProtect) == NULL)
return -1;
memcpy_s(&orgFP, sizeof(GOGO_NAM), lpOrgFunc, sizeof(GOGO_NAM));
GOGO_NAM 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(GOGO_NAM), &newFuncObj, sizeof(GOGO_NAM)); // Replacing
// Rollback protection
VirtualProtect(lpOrgFunc, sizeof(GOGO_NAM), 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(GOGO_NAM), PAGE_EXECUTE_READWRITE, &dwOldProtect) == NULL)
return -1; // reset org FP
memcpy_s(lpOrgFunc, sizeof(GOGO_NAM), &orgFP, sizeof(GOGO_NAM)); // Rollback protection
VirtualProtect(lpOrgFunc, sizeof(GOGO_NAM), 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:
Hook64();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
후킹의 역할을 하는 dll이다. 이 dll을 프로세스에 넣는 방법은 해당링크 참고
https://nameng.tistory.com/117
후킹 dll을 차근차근 부분별로 분석해보자
#pragma pack(push,1)//구조체 데이터 정렬크기를 1바이트로 정렬, 구조체 변수간에 공간이 있을수있기에 1바이트로 할당하는것같다.
struct GOGO_NAM {
BYTE opcode1;
DWORD lpTarget1;
DWORD opcode2;
DWORD lpTarget2;
BYTE opcode3;
};
#pragma pack(pop)//위의 설정 이전상태로 돌림
#progma pack->각 변수간의 할당공간의 크기를 1바이트로 정렬한다. padding을 없애려는 의도같다.
struct 구조체->후킹할 어셈블리 명령어가 들어갈 공간을 할당
INT WINAPI NewWriteFile(
_In_ HANDLE hFile,
_In_ LPCVOID lpBuffer,
_In_ DWORD nNumberOfBytesToWrite,
_Out_opt_ LPDWORD lpNumberOfBytesWritten,
_Inout_opt_ LPOVERLAPPED lpOverlapped) {
MessageBoxA(NULL, (LPCSTR)lpBuffer, "hooking api call success", MB_OK);
for (int i = 0; i < 10; i++) {
MessageBoxA(NULL, "내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈내돈", "hooking api call success", MB_OK);
}
Unhook64();
BOOL ret = WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
Hook64();
return ret;
}
wrtiefile api를 덮어쓸 함수인 newwritefile, 실제 writefile api가 실행되기전에 실행되므로 동일한 인자값을 만들어주면 똑같기때문에 그대로 써먹을수있다.
원하는 동작을 한 후 14바이트 공간으로 인해 프로그램이 깨지지 않도록 unhook을 한 후 실제 동작을 하는것을 알수있다. 해당소스에서는 ret변수에 진짜 writefile동작을 해주고 return 값을 받는 모습을 알수있다.
이후 다시 후킹을 진행해 후킹상태를 유지해준다.
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(GOGO_NAM), PAGE_EXECUTE_READWRITE, &dwOldProtect) == NULL)
return -1;
memcpy_s(&orgFP, sizeof(GOGO_NAM), lpOrgFunc, sizeof(GOGO_NAM));
GOGO_NAM 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(GOGO_NAM), &newFuncObj, sizeof(GOGO_NAM)); // Replacing
// Rollback protection
VirtualProtect(lpOrgFunc, sizeof(GOGO_NAM), dwOldProtect, NULL);
Hooked = TRUE;
return 0;
}
if getprocaddress ->요즘 운영체제에서는 kernel32.dll에서 소스가 바로 나오지 않고 kernelbase.dll로 리다이렉트 되어 나오는 경우가 있다. 모든 api가 리다이렉트가 되는것은 아니므로 조건문을 통해 구별해준다. 이때 구별하기 위해 0x25FF값을 이용하게 되는데 이는 리다이렉트 될때 Absolute Jump를 하면서 앞 2바이트가 0x25FF가 되기때문이다.
첫번째 VirtualProtect-> 메모리 영역에 쓰기 권한을 줘야 overwrite를 할 수 있기에 virtualprotect를 통해 실제 api주소에 14바이트 영역만큼 읽고쓰기권한을 준다. 권한 원상복구용으로 dwoldprotect에 저장해준다.
memcpy_s =>orgFP에 실제 writefile의 메모리값을 14바이트 복사 ->나중에 unhook하면서 원상복구할때 사용
구조체 생성 및 각각의 opcode값은 주석 참고
newwritefile & 0xFFFFFFFF은 4바이트만 추출하기 위함-> PUSH (4바이트 값)
newwritefile >>32는 상위 32비트만뽑음
memcpy_s -> 실제 writefile주소에 우리가 만들어준 14바이트 공간 만큼 덮어써줌
VirtualProtect->실제 writefile주소의 권한을 다시 돌려놓음
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(GOGO_NAM), PAGE_EXECUTE_READWRITE, &dwOldProtect) == NULL)
return -1; // reset org FP
memcpy_s(lpOrgFunc, sizeof(GOGO_NAM), &orgFP, sizeof(GOGO_NAM)); // Rollback protection
VirtualProtect(lpOrgFunc, sizeof(GOGO_NAM), dwOldProtect, NULL);
Hooked = FALSE;
return 0;
}
대부분 Hooking부분과 동일
memcpy_s-> 실제 writefile의 14바이트를 백업해둔 writefile의 메모리값으로 바꿔줌
소스가 길어서 어려워보이나 실제로 분석해보면 별거없다.
실제 writefile의 부분으로 앞의 14바이트가 후킹되어 뒤에 많은내용이 있지만 ret로 넘어가는모습
14바이트의 후킹코드가 박히는 구간 7FFA4D9110E6의 내가만든 NewWriteFile의 주소를 가지고 연산을 한다.
0xFFFFFFFF와 and연산을하여 하위 4바이트를 넣고 NewWriteFile과 shift right 32를 통해 상위바이트를 뽑게된다. 64비트 주소에서 0xFFFFFFFF로 하위 4바이트 추출 SHIFT RIGHT 32를 통해 4바이트를 움직여줌으로써 상위 바이트 4바이트가 추출이 된다.
이는 첫번째 사진의 PUSH (하위 4바이트) MOV dword ptr [rsp+4],(상위 4바이트->앞부분은 0000이므로 7FFA만 입력)
이과정은 64비트에서 RSP+8부분에 하위 4바이트, RSP+4부분에 상위 4바이트를 넣어줌으로써 NewWriteFile로 JMP를 하게 된다.
비어있는 스택에서
PUSH 를 하게 되면 RSP-8(스택은 높은주소부터 낮은주소 이므로)후 RSP가 가르키고 있는위치에 값을 넣는다.
mov [rsp+4]를 한모습
이후 ret과정으로
POP RIP를 통해 기존의 RSP가 가르키고 있던 값을 RIP에 저장하고 RSP+8을 하게 된다.
이후 JMP RIP를 하므로 가르키고 있는 [high addr+low addr]->우리가 만든 writefile 주소로 점프를 하게 된다.
32비트 후킹코드도 여기에
'리버싱!' 카테고리의 다른 글
가상 함수(Virtual function)와 가상 함수 테이블(vtable) 공부 (0) | 2022.02.16 |
---|---|
detours를 이용한 hooking (0) | 2022.02.16 |
Remote Template Injection (2) | 2022.02.04 |
CAT-Security Game Hack Challenge~~! (끝) (0) | 2021.07.28 |
DLL Injection 공부 (0) | 2021.06.22 |