계산기 API Hooking 실습

컴컴한해커·2025년 1월 19일

리버스 엔지니어링

목록 보기
16/18

📌 대상 API 선정

📢 목표 : 계산기의 텍스트 에디터에 표시되는 모든 숫자를 한글로 변경하기

SetWindowTextW API와 SetDlgItemTextW API를 후킹하면 될 것이라고 가정한다.

📢 후킹 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를 후킹하면 더 성공적으로 후킹할 수 있을 것이다.

📢 디버거로 calculator.exe의 SetWindowTextW API 확인해보기


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;
}

0개의 댓글