// KeyHook.dll
#include "stdio.h"
#include "windows.h"
#define DEF_PROCESS_NAME "notepad.exe"
HINSTANCE g_hInstance = NULL; // hook procedure가 속한 DLL 핸들
HHOOK g_hHook = NULL; // Hook 핸들
HWND g_hWnd = NULL;
BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD dwReason, LPVOID lpvReserved) {
switch(dwReason) {
case DLL_PROCESS_ATTACH:
g_hInstance = hinstDll;
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
char szPath[MAX_PATH] = {0, };
char *p = NULL;
if(nCode>=0) {
// 31번째 bit: 0=key press, 1=key release
if(!(lParam&0x80000000)) {
GetModuleFileNameA(NULL, szPath, MAX_PATH);
p = strrchr(szPath, '\\');
// 현재 프로세스 이름 비교해서 notepad.exe라면 메시지 무시
if(!_stricmp(p+1, DEF_PROCESS_NAME))
return 1;
}
}
// 일반적인 경우 CallNextHookEx()를 호출하여 다음 훅(혹은 운영체제)로 메시지 전달
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
// C++ 컴파일러에게 C 문법을 적용해 컴파일하라고 알려줌
#ifdef __cplusplus
extern "C" {
#endif
// dll에서 export하는 함수 정의
__declspec(dllexport) void HookStart() {
// WH_KEYBOARD 후크 사용, 콜백함수, dll 핸들, global hook
g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
}
__declspec(dllexport) void HookStop() {
if(g_hHook) {
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
#ifdef __cplusplus
}
#endif
// HookMain.exe
#include "stdio.h"
#include "conio.h"
#include "windows.h"
#define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"
typedef void(*PFN_HOOKSTART)(); // 반환값 자료형 void인 함수 포인터에게 별칭 붙임
typedef void(*PFN_HOOKSTOP)(); // 반환값 자료형 void인 함수 포인터에게 별칭 붙임
void main() {
HMODULE hDll = NULL;
PFN_HOOKSTART HookStart = NULL;
PFN_HOOKSTOP HookStop = NULL;
char ch = 0;
// KeyHook.dll 로딩
hDll = LoadLibraryA(DEF_DLL_NAME);
// dll의 export 함수 주소 얻기
HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);
// hooking 시작
HookStart();
printf("press 'q' to quit!\n");
while( _getch() != 'q' );
// hooking 종료
HookStop();
// KeyHook.dll 언로딩
FreeLibrary(hDll);
}
0. dll 빌드 방법
1) 새 프로젝트 - Win32 - Win32 콘솔 응용 프로그램 - 이름/솔루션 이름 입력 - 마법사 - DLL, 빈프로젝트 체크 - 마침
2) 프로젝트 - 새 항목 추가 - 코드 - C++ 파일(.cpp) - 이름 입력
3) 솔루션 구성 Release & 솔루션 플랫폼 Win32
4) 프로젝트 - Keyboard.dll 속성 - 구성 속성 - C/C++ - 코드 생성 - 런타임 라이브러리 - 다중 스레드(/MT)
// exe랑 dll이랑 컴파일 옵션 통일하자
// /MD로 해도 잘 동작함. 크기는 46KB에서 7KB로 확 준다!!
// standard c library function(stdio.h 등) 사용하고 있고 MFC library(afx.h 등) 사용 안하므로 MFC 옵션 신경 x
// 마찬가지로 ATL 옵션 신경 x
5) 컴파일(Ctrl+F7 혹은 소스코드 우클릭 - 컴파일)
// obj 파일 생성
6) 솔루션 빌드(F7 혹은 디버그 - 솔루션 빌드)
// 링크 수행되면서 실행 가능한 파일 생성됨 (Release 폴더)
// 컴파일도 자동으로 수행됨
1. exe 빌드 방법
1) 새 프로젝트 - Win32 - Win32 콘솔 응용 프로그램 - 이름/솔루션 이름 입력 - 마법사 - 콘솔응용프로그램, 빈프로젝트 체크 - 마침
2) 프로젝트 - 새 항목 추가 - 코드 - C++ 파일(.cpp) - 이름 입력
3) 솔루션 구성 Release & 솔루션 플랫폼 Win32
4) 프로젝트 - Keyboard.dll 속성 - 구성 속성 - C/C++ - 코드 생성 - 런타임 라이브러리 - 다중 스레드(/MT)
5) 솔루션 빌드(F7 혹은 디버그 - 솔루션 빌드)
2. Process Explorer 호출 => Ctrl+Shift+Esc
3. 키보드 후킹 도움글
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=kmymirae&logNo=150046035481
4. SetWindowsHookEx, KeyBoardProc
HHOOK SetWindowsHookEx(
int idHook, // hook type
HOOKPROC lpfn, // hook procedure
HINSTANCE hMod, // hook procedure가 속해있는 dll 핸들
DWORD dwThreadID // hook을 걸고 싶은 thread의 ID (0은 global hook)
);
- 동작: 프로세스에서 특정 메시지가 발생했을 때 DLL 파일을 프로세스에 강제로 인젝션 후 hook procedure 호출
- 인자 설명
1) idHook: hook type, 여기선 WH_KEYBOARD 후크를 사용
2) lpfn: 콜백함수, 키보드 입력에 대한 default 포맷은 아래와 같다
3) 4) hMod와 dwThreadID는 후킹할 목적지를 정하는 곳, 전자는 다른 프로세스를 후킹할 때 dll 핸들을 받아서 사용하고, 후자는 GetCurrentThreadID()로 받아서 자신을 후킹할 때 주로 사용
LRESULT CALLBACK KeyboardProc(
int code, // hook code
WPARAM wParam, // virtual-key code
LPARAM lParam // keystroke-message information
){ ... }
- 인자 설명
1) code: HC_ACTION(0), HC_NOREMOVE(3) , code>=0으로 해서 해당 메시지가 들어오는지 파악
2) wParam: 사용자가 누른 키보드의 virtual key code
3) lParam: 각 비트별로 다양한 의미 나타냄 (31번째 bit - key press/release 관련)
5. c언어에선 큰따옴표 작은따옴표 차이 존재
' ' : 문자
" " : 문자열
6. /MT 옵션 vs /MD 옵션
- C 런타임 라이브러리의 연결 방식과 관련된 컴파일 옵션이다
- MD 옵션으로 배포 시 'xxx.dll이 없어 코드 실행을 진행할 수 없습니다' 에러 발생하기도 함
- 런타임옵션은 메인 프로그램과 아래 라이브러리 모두 통일하자!
- 이유는, /MT 는 각 모듈별로 메모리를 할당/관리 하는 반면 /MD는 메모리관리를 공유하기 때문인데, 혼용해서 쓰게되면 다른 메모리쪽을 침범할 수 있기 때문
/MD (Multi-Threaded DLL)
- /MD 옵션은 C 런타임 라이브러리를 별도의 DLL로 동적으로 연결(dynamic link)하는 방식
- MSVCRT.lib가 .obj 파일에 배치되어 빌드된다. (디버그 모드의 경우 MSVCRTD.lib)
- 여러 실행 파일이 라이브러리를 공유하여 사용할 수 있어 실행 파일 크기 및 메모리 사용량 감소
- 실행 파일 배포 시 반드시 재배포 패키지를 함께 배포해야 함
/MT (Multi-Threaded)
- /MT 옵션은 C 런타임 라이브러리를 실행 파일 내에 포함하여 정적으로 연결(static link)하는 방식이다.
- LIBCMT.lib가 .obj 파일에 배치되어 빌드된다. (디버그 모드의 경우 LIBCMTD.lib)
- 대상 시스템에 설치된 DLL에 의존하지 않는다.
- 실행 파일의 크기가 커진다.
7. DllMain
BOOL WINAPI DllMain(
HINSTANCE hinstDLL, // handle to DLL module
DWORD fdwReason, // reason for calling function
LPVOID lpvReserved ) // reserved
{
// Perform actions based on the reason for calling.
switch( fdwReason )
{
case DLL_PROCESS_ATTACH:
// Initialize once for each new process.
// Return FALSE to fail DLL load.
break;
case DLL_THREAD_ATTACH:
// Do thread-specific initialization.
break;
case DLL_THREAD_DETACH:
// Do thread-specific cleanup.
break;
case DLL_PROCESS_DETACH:
if (lpvReserved != nullptr)
{
break; // do not do cleanup if process termination scenario
}
// Perform any necessary cleanup.
break;
}
return TRUE; // Successful DLL_PROCESS_ATTACH.
}
- DLL이 가지는 진입점 함수
- DllMain은 프로세스나 쓰레드가 시작 또는 종료할 때 호출된다
- 구현 안할 시 C/C++ 런타임 라이브러리 내에 자체적으로 구현되어 있는 DllMain 함수가 사용됨
- 인자 설명
1) hModule: Dll의 인스턴스 핸들, Dll 파일 이미지가 매핑된 가상 메모리 주솟값
2) fdwReason: 4가지 값 가짐 (Process 시작&종료, Thread 시작&종료)
3) lpReserved:
fdwReason=DLL_PROCESS_ATTACH: DLL이 명시적 런타임 링크 방식으로 로드되면 NULL, DLL이 암시적 로드타임 링크 방식으로 로드되면 not NULL
fdwReason=DLL_PROCESS_DETACH: FreeLibrary가 호출되었거나 DLL 로드가 실패한 경우라면 NULL, 프로세스가 종료중이라면 not NULL.
8. LRESULT CALLBACK KeybaordProc(int, WPARAM, LPARAM)
리턴 타입: LRESULT
호출 규약: CALLBACK // __stdcall, 'CALLBACK' macro is defined in the Windows Header file
#define CALLBACK __stdcall
즉, 이 함수를 import한다고 하면 아래와 같이 function pointer를 정의하고 GetProcAddress 해야한다
typedef LRESULT (CALLBACK* KeyboardProcPtr)(int, WPARAM, LPARAM);
9. DLL에서 함수 export하는 법
- DLL (동적 라이브러리)에 구현한 함수를 외부로 노출시키려면 __declspec( dllexport ) 키워드 붙여야 됨
- 해당 키워드가 붙지 않은 함수는 외부에서 호출 불가
- export 하는 함수들은 ' extern "C" { } ' 로 둘러쌓여있어야 함
#ifdef __cplusplus
extern "C" { // 해당 함수가 C로 컴파일된 함수임을 컴파일러에게 명시적으로 알려줌
#endif
__declspec(dllexport) void HookStart() { }
__declspec(dllexport) void HookStop() { }
#ifdef __cplusplus
}
#endif
why extern "C" ??
extern "C" 라는 키워드는 C언어 형식으로 함수 이름을 짓고 extern "C" 를 제거하면 C++ 규칙대로 함수 이름을 짓는다
extern "C" 방식은 모든 컴파일러에서 알아볼수 있는 형식이므로 extern "C" 방식으로 함수를 작성하면 다른 컴퓨터에서 무리 없이 돌아가지만
extern "C"를 제거한 C++ 방식으로 함수를 작성하면 함수이름이 이상하게 변하는데 그 이상하게 변환하는 규칙이 각각 컴파일러 마다 제각각이여서 다른 컴퓨터와 호환이 되지 않는다 (Name Mangling?)
'security > 리버싱핵심원리' 카테고리의 다른 글
HookMain.exe / KeyHook.dll 디버깅 (0) | 2023.03.17 |
---|---|
SetWindowsHookEx()을 이용한 Windows 메시지 후킹 - (2) (0) | 2023.03.15 |
인라인 패치 - unpackme#1 (0) | 2023.03.14 |
UPack 파일 분석 - HXD, Ollydbg (0) | 2023.03.12 |
PE 재배치 (0) | 2023.03.11 |