
SetWindowTextW API와 SetDlgItemTextW API를 후킹하면 될 것이라고 가정한다.
BOOL SetWindowTextW(
[in] HWND hWnd,
[in, optional] LPCWSTR lpString
);
SetWindowTextW API의 정의를 보면 위와 같다. 해당 API는 창의 문자열을 변경하는 API로, 파라미터로 창 핸들(hWnd)와 문자열 포인터(lpString)을 받는다. 저 문자열을 살펴보고 숫자를 한글로 변경하면 된다.
BOOL SetDlgItemTextW(
[in] HWND hDlg,
[in] int nIDDlgItem,
[in] LPCWSTR lpString
);
SetDlgItemTextW API의 정의를 보면 위와 같다. 창 핸들(hWnd)와 문자열 포인터(lpString)을 받는 건 SetWindowTextW와 같고, 추가로 nIDDlgItem을 받는다. MSDN에는 nIDDlgItem에 설정할 제목이나 텍스트가 있는 컨트롤이라고 한다. 잘 이해가 가지 않아서 ChatGPT에게 이해하기 쉽게 설명해달라고 했더니 아래처럼 답변했다.

일종의 배열의 인덱스 값이라고 개념적으로 생각하면 될 것 같다.
이 2가지 API를 후킹을 위한 API라고 가정했지만, SetDlgItemTextW API는 내부적으로 다시 SetWindowTextW API를 호출한다고 한다. 따라서 더 low-level Hooking에 해당하는 SetWindowTextW를 후킹하면 더 성공적으로 후킹할 수 있을 것이다.

SetWindowTextW의 검색해보면 위와 같이 나오고 그 구간에 BP를 걸어서 확인해보았다.


만약 내가 7을 입력하면 해당 값은 000DF674값에 입력되는 것을 확인 할 수 있다.
이 값을 한글로 고쳐서 실행하면 아래와 같은 값이 된다.

원래 프로세스 동작 과정
1. Call [IAT 주소]를 통해 user32.SetWindowTextW API의 값을 호출
후킹 후 프로세스 동작 과정
1. Call [IAT 주소] 과정은 동일하지만 IAT 주소가 가리키는 값이 변하여 다른 API를 호출한다.
2. 이 때, [IAT 주소]에 다른 함수의 주소를 입력하여 다른 API가 실행되게끔 한다.
3. 다른 함수의 작업이 끝나고 원래 함수의 IAT 주소로 가서 원래 작업을 진행한다.
-->이 때 후킹 하기 전, DLL 인젝션을 진행하고, IAT 주소의 영역을 바꾸고 진행해야 원활하게 진행된다.
DLLmain
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
// original API 주소 저장
// g_pOrgFunc --> 언훅 작업에서 사용하기 위함
g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"),
"SetWindowTextW");
// # hook
// user32!SetWindowTextW() 를 hookiat!MySetWindowText() 로 후킹
hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
break;
case DLL_PROCESS_DETACH :
// # unhook
// calc.exe 의 IAT 를 원래대로 복원
hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
break;
}
return TRUE;
}
MySetWindowTextW
// MySetWindowTextW : 숫자를 한글로 변환하는 함수
// SetWindowTextW : 변환된 한글을 출력하는 함수
BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
wchar_t* pNum = L"영일이삼사오육칠팔구";
wchar_t temp[2] = {0,};
int i = 0, nLen = 0, nIndex = 0;
nLen = wcslen(lpString);
for(i = 0; i < nLen; i++)
{
// '수'문자를 '한글'문자로 변환
// lpString 은 wide-character (2 byte) 문자열
if( L'0' <= lpString[i] && lpString[i] <= L'9' )
{
temp[0] = lpString[i];
nIndex = _wtoi(temp);
lpString[i] = pNum[nIndex];
}
}
// user32!SetWindowTextW() API 호출
// (위에서 lpString 버퍼 내용을 변경하였음)
return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}
hook_iat
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
HMODULE hMod;
LPCSTR szLibName;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
PIMAGE_THUNK_DATA pThunk;
DWORD dwOldProtect, dwRVA;
PBYTE pAddr;
// 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 = "PE" signature
pAddr += *((DWORD*)&pAddr[0x3C]);
// dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table
dwRVA = *((DWORD*)&pAddr[0x80]);
// pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table
// pImportDesc = ImageBase of calc.exe + RVA to IID Table = VA to IID Table
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);
for( ; pImportDesc->Name; pImportDesc++ )
{
// szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name
// szLibName = ImageBase of calc.exe + Name of IDT Table
szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
// szDllName = user32.dll ( 우리가 찾고자 하는 dll )
if( !_stricmp(szLibName, szDllName) )
{
// pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
// = VA to IAT(Import Address Table)
// pThunk = ImageBase of calc.exe + IAT FirstThunk = VA to IAT FirstThunk
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
pImportDesc->FirstThunk);
// pThunk->u1.Function = VA to API
for( ; pThunk->u1.Function; pThunk++ )
{
//pfnOrg = SetWindowTextW( 우리가 찾고자 하는 함수 )
if( pThunk->u1.Function == (DWORD)pfnOrg )
{
// 메모리 속성을 E/R/W 로 변경
// 계산기 프로세스의 IAT 메모리 영역이 읽기 전용이기 때문에 넣은 코드
VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
PAGE_EXECUTE_READWRITE,
&dwOldProtect);
// IAT 값을 변경
pThunk->u1.Function = (DWORD)pfnNew;
// 메모리 속성 복원
VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
dwOldProtect,
&dwOldProtect);
return TRUE;
}
}
}
}
return FALSE;
}