ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리버싱 핵심원리 study] 30장 메모장 WriteFile() 후킹
    Reverse Engineering 2021. 1. 18. 19:38

    이전 29장 스터디 내용의 테크 맵 중 debug 방법에 대한 실습이다. kernel32.dll의 WriteFile() API를 후킹 하여 기존과는 다른 동작을 하도록 만든다. debug 방식의 장점은 debugging을 사용함으로써 사용자와 더 interactive 한 후킹을 수행할 수 있다는 점이다. 실습을 진행하기에 앞서 디버거에 대한 설명을 정리해보자.

     

    1. 디버거 설명

    1) 용어

    debugger - 디버깅 프로그램 (단순 ollyDbg, x64dbg 등의 프로그램 이외에도 사용자 제작 프로그램 포괄하는 개념)

    debuggee - 디버깅 당하는 프로그램

     

    2) 기능

    debugger는 debuggee가 올바르게 실행되는지 확인하고 프로그램의 오류를 발견하는 기능을 한다. 또한 debuggee의 명령어를 하나씩 실행할 수 있으며 레지스터와 메모리에 대한 모든 접근 권한을 갖는다.

     

    3) 디버거 동작원리

    debugger 프로세스로 등록된 경우 OS는 debuggee에서 debug event가 발생할 때 debuggee의 실행을 멈추고 해당 이벤트를 debugger에게 통보한다. debugger는 해당 이벤트에 대한 적절한 처리를 한 후 debuggee의 실행을 재개할 수 있다.

     

    4) 디버그 이벤트

    debug event의 종류로는 아래와 같은 것들이 있다.

    - EXCEPTION_DEBUG_EVENT(*)

    - CREATE_THREAD_DEBUG_EVENT

    - CREATE_PROCESS_DEBUG_EVENT

    - EXIT_THREAD_DEBUG_EVENT

    - EXIT_PROCESS_DEBUG_EVENT

    - LOAD_DLL_DEBUG_EVENT

    - UNLOAD_DLL_DEBUG_EVENT

    - OUTPUT_DEBUT_STRING_EVENT

    - RIP_EVENT

     

    위 이벤트 중 디버깅과 관련된 이벤트는 EXCEPTION_DEBUG_EVENT이다. 이와 관련돼 예외 목록으로 아래와 같은 것들이 있다.

    - EXCEPTION_ACCESS_VIOLATION

    - EXCEPTION_ARRAY_BOUNDS_EXCEEDED

    - EXCEPTION_BREAKPOINT(*)

    - EXCEPTION_DATATYPE_MISALIGNMENT

    - EXCEPTION_FLT_DENORMAL_OPERAND

    - EXCEPTION_FLT_DIVIDE_BY_ZERO

    - EXCEPTION_FLT_INEXACT_RESULT

    - EXCEPTION_FLT_INVALID_OPERATION

    - EXCEPTION_FLT_OVERFLOW

    - EXCEPTION_FLT_STACK_CHECK

    - EXCEPTION_FLT_UNDERFLOW

    - EXCEPTION_FLT_ILLEGAL_INSTRUCTIOIN

    - EXCEPTION_IN_PAGE_ERROR

    - EXCEPTION_INT_DIVIDE_BY_ZERO

    - EXCEPTION_INT_OVERFLOW

    - EXCEPTION_INVALID_DISPOSITION
    - EXCEPTION_NONCONTINUABLE_EXCEPTION

    - EXCEPTION_PRIV_INSTRUCTION

    - EXCEPTION_SINGLE_STEP

    - EXCEPTION_STACK_OVERFLOW

     

    위 예외 중 debugger가 반드시 처리해야 하는 예외가 바로 EXCEPTION_BREAKPOINT 예외다. Break Point(이하 BP)는 어셈블리 명령어로 'INT3'이며, IA-32 Instruction으로는 0xCC다. 코드 디버깅 중 해당 명령어를 만나면 실행이 중지되고 EXCEPTION_BREAKPOINT 예외 이벤트가 발생한다. Debugger에서 BP를 설치하기 원하는 코드의 메모리 시작 주소의 1byte를 0xCC로 바꿈으로써 BP를 구현할 수 있다. 이번 API 후킹 실습에서는 이와 같은 방식으로 진행된다.

     

    2. 작업 순서

    기본적으로 디버그 기법을 이용한 API 후킹에서는 API 시작 부분을 0xCC로 바꿔 debugger에게 제어권을 넘긴 후, 원하는 작업을 수행한 후 debuggee를 다시 실행상태로 바꾼다. 아래와 같은 순서로 작업을 한다.

     

    1) 대상 프로세스에 'attach'하여 debuggee로 만들기

    2) API 시작 주소의 첫 바이트를 0xCC로 변경

    3) 해당 API가 호출 시 제어가 debugger에게 넘어옴

    4) 원하는 작업 수행

    5) Unhook (0xCC를 원래대로 복원)

    6) API 실행

    7) Hook (지속적 후킹을 위해 다시 0xCC로 바꿈)

    8) debugee에게 제어를 돌려줌

     

    3. 실습

    notepad.exe의 WriteFile() API 후킹을 진행해본다. 파일이 저장되는 순간 입력된 파라미터를 조작하여 소문자를 모두 대문자로 바꾸는 실습이다.

     

    우선 Process Explorer로 notepad.exe의 PID를 알아낸 후 예제 파일인 hookdbg.exe를 실행시킨다.

    notepad.exe의 PID 구하기

     

    hookdbg.exe 실행

    이후에 메모장에 아무 내용이나 입력하고 저장해보자.

     

    내용 입력

    그러면 콘솔에 다음과 같은 내용이 나오는 것을 확인할 수 있다.

    실행 결과

    API 후킹에 의해 내부 진행과정을 보여준다. 실제로 대문자로 저장이 되었는지 test.txt 파일을 열어보자.

     

    test.txt

    위와 같이 대문자로 저장된 것을 확인할 수 있다.

     

    4. 동작 원리

    notepad.exe에서 파일을 저장할 때는 kernel32.dll의 WriteFile() API가 호출된다. WriteFile() API의 정의는 다음과 같다.

    BOOL WriteFile(
      HANDLE       hFile,
      LPCVOID      lpBuffer,
      DWORD        nNumberOfBytesToWrite,
      LPDWORD      lpNumberOfBytesWritten,
      LPOVERLAPPED lpOverlapped
    );

    두 번째 파라미터 lpBuffer가 '쓰기 버퍼'이고 세 번째 파라미터 nNumberOfBytesToWrite가 써야 할 크기이다. 함수의 파라미터는 역순으로 스택에 쌓인다는 점을 기억하며 OllyDbg를 이용해 notepad 디버깅을 해보자.

     

    Search for all intermodular calls를 이용해 WriteFile()이 호출되는 부분을 찾은 후, 해당 부분에 BP를 설정하고 F9를 눌러 프로그램을 실행시킨다.

     

    notepad.exe 실행

    아무 문자열이나 입력하고 저장을 해보자. 그러면 아래와 같이 이전에 설정해두었던 BP에서 프로그램이 멈추게 된다.

     

    WriteFile()

    이때, WriteFile()의 parameter 중 Buffer가 가리키는 주소를 dump창에서 보면 "maple19out!" (입력한 문자열)이 저장되어 있는 것을 확인할 수 있다. 후킹을 통해 notepad에서 저장하려고 하는 문자열 버퍼를 다른 값으로 덮어쓰면 목표를 달성할 수 있다.

     

    실습에서는 디버그 방법을 이용해 API 후킹을 구현하기 위해 WriteFile() API 시작 주소에 BP(INT3)를 설치하면, debuggee인 notepad에서 파일을 저장하려고 할 때 debugger(hookdbg.exe)에게 EXCEPTION_BREAKPOINT 이벤트가 전달된다. 이때 주의해야 할 점은 notepad의 EIP는 WriteFile() API의 시작 주소가 아니라 BP 1byte를 더한 값이 된다. 따라서 후킹이 끝난 다음 코드를 되돌릴 때 EIP를 WriteFile() API로 맞춰주는 과정이 필요하다. 또한 단순히 EIP만 수정해서는 계속 BP에 걸릴 것이므로, 무한루프에 빠지지 않기 위해 설치했던 BP를 없애주는 작업도 추가로 필요하다.

     

    5. 소스코드 설명

    main()

    int main(int argc, char* argv[]) {
    	DWORD dwPID;
    
    	if (argc != 2) {
    		printf("\nUsage : hookdbg.exe pid\n");
    		return 1;
    	}
    
    	dwPID = atol(argv[1]);
    	if (!DebugActiveProcess(dwPID)) {
    		printf("DebugActiveProcess(%d) failed!!!\n"
    			"Error Code = %d\n", dwPID, GetLastError());
    		return 1;
    	}
    
    	DebugLoop();
    
    	return 0;
    }

    main loop는 간단하다. parameter로 받은 대상 process의 PID를 이용해서 DebugActiveProcess() API를 통해 실행 중인 프로세스에 attach 하여 디버깅을 시작하고, DebugLoop() (사용자 정의 함수)를 호출하여 debuggee로 부터 오는 디버그 이벤트를 처리한다.

     

    DebugLoop()

    void DebugLoop() {
    	DEBUG_EVENT de;
    	DWORD dwContinueStatus;
    
    	//debuggee로 부터 event가 발생할 때까지 기다림
    	while (WaitForDebugEvent(&de, INFINITE)) {
    		dwContinueStatus = DBG_CONTINUE;
    
    		if (CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
    			OnCreateProcessDebugEvent(&de);
    		else if (EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode) {
    			if (OnExceptionDebugEvent(&de))
    				continue;
    		}
    		else if (EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
    			break;
    		ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    	}
    }

    WndProc의 GetMessage() 부분과 유사하게 동작한다. While loop를 통해 debuggee로 부터 발생하는 이벤트를 받아 처리한 후, 디버기의 실행을 재개한다. WaitForDebugEvent() API는 debuggee로부터 디버그 이벤트가 발생할 때까지 기다리는 역할을 한다. 만약 디버그 이벤트가 발생하면, 첫 번째 파라미터인 de 변수에 해당 이벤트 정보를 설정한 후 반환한다. DEBUG_EVENT 구조체의 정의는 아래와 같다.

     

    typedef struct _DEBUG_EVENT {
      DWORD dwDebugEventCode;
      DWORD dwProcessId;
      DWORD dwThreadId;
      union {
        EXCEPTION_DEBUG_INFO      Exception;
        CREATE_THREAD_DEBUG_INFO  CreateThread;
        CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
        EXIT_THREAD_DEBUG_INFO    ExitThread;
        EXIT_PROCESS_DEBUG_INFO   ExitProcess;
        LOAD_DLL_DEBUG_INFO       LoadDll;
        UNLOAD_DLL_DEBUG_INFO     UnloadDll;
        OUTPUT_DEBUG_STRING_INFO  DebugString;
        RIP_INFO                  RipInfo;
      } u;
    } DEBUG_EVENT, *LPDEBUG_EVENT;

    앞에서 설명한 디버그 이벤트 9가지 중 dwDebugEventCode에 하나가 세팅되며, 해당 이벤트 종류에 따라 유니온 멤버 u도 세팅된다. 

     

    ContinueDebugEvent() API는 debuggee의 실행을 재개하는 함수다. 해당 API의 마지막 파라미터인 dwContinueStatus는 DBG_CONTINUE(정상적으로 처리) 또는 DBG_EXCEPTION_NOT_HANDLED(처리하지 못함) 중에서 하나의 값을 갖는다.

     

    DebugLoop() 함수에서는 다음과 같은 세 가지 디버그 이벤트를 처리한다.

     

    (1) EXIT_PROCESS_DEBUG_EVENT : debuggee가 종료될 때 발생. debugger도 종료되도록 코드가 작성되어 있다.

    (2) CREATE_PROCESS_DEBUG_EVENT : debuggee 프로세스가 시작 혹은 attach 될 때 발생하며, OnCreateProcessDebugEvent 함수를 호출하도록 하였다. 해당 함수에 대한 코드는 다음과 같다.

    BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde) {
    	g_pfWriteFile = GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "WriteFile");
    	
    	//API Hook-WriteFile()
    	//첫 번째 byte를 0xCC로 변경
    	memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
    	ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chOrgByte, sizeof(BYTE), NULL);
    	WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chINT3, sizeof(BYTE), NULL);
    
    	return TRUE;
    }

    우선 WriteFile API의 주소를 GetProcAddress API를 통해 구한 후, g_cpdi 변수에 memcpy를 통해 debuggee process 정보를 복사한다. g_cpdi는 코드에 선언한 전역변수인데 CREATE_PROCESS_DEBUG_INFO 구조체 변수다. CREATE_PROCESS_DEBUG_INFO 구조체는 아래와 같다.

    typedef struct _CREATE_PROCESS_DEBUG_INFO {
      HANDLE                 hFile;
      HANDLE                 hProcess;
      HANDLE                 hThread;
      LPVOID                 lpBaseOfImage;
      DWORD                  dwDebugInfoFileOffset;
      DWORD                  nDebugInfoSize;
      LPVOID                 lpThreadLocalBase;
      LPTHREAD_START_ROUTINE lpStartAddress;
      LPVOID                 lpImageName;
      WORD                   fUnicode;
    } CREATE_PROCESS_DEBUG_INFO, *LPCREATE_PROCESS_DEBUG_INFO;

    이제 debuggee의 핸들이 g_cpdi의 hProcess 멤버에 저장되었으므로 이를 이용해 ReadProcessMemory() API로 부터 WriteFile() API의 시작 BYTE를 g_chOrgByte에 저장하고 WriteProcessMemory() API를 이용해 g_chINT3 값 (0xCC 혹은 INT3)으로 update 해준다. g_chOrgByte에 기존 정보를 저장하는 이유는 이후에 후킹을 해제하기 위해서다.

     

    (3) EXCEPTION_DEBUG_EVENT - OnExceptionDebugEvent()

    debuggee의 INT3 명령을 처리하게 될 함수로 이번 실습의 핵심 부분이다.

    //가장 핵심
    BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde) {
    	CONTEXT ctx;
    	PBYTE lpBuffer = NULL;
    	DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
    	PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
    
    	//BP exception (INT 3)인 경우
    	if (EXCEPTION_BREAKPOINT == per->ExceptionCode) {
    		//BP주소가 WriteFile() API 주소인 경우
    		if (g_pfWriteFile == per->ExceptionAddress) {
    			//#1.Unhook
    			//0xCC로 덮어쓴 부분을 원래대로 되돌림
    			WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chOrgByte, sizeof(BYTE), NULL);
    			
    			//#2.Thread Context 구하기
    			ctx.ContextFlags = CONTEXT_CONTROL;
    			GetThreadContext(g_cpdi.hThread, &ctx);
    
    			//#3.WriteFile()의 param 2, 3구하기
    			//param2 : ESP+0x8
    			//param3 : ESP+0xC
    			ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8), &dwAddrOfBuffer, sizeof(DWORD), NULL);
    			ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC), &dwNumOfBytesToWrite, sizeof(DWORD), NULL);
    
    			//#4.임시 버퍼 할당
    			lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite + 1);
    			memset(lpBuffer, 0, dwNumOfBytesToWrite + 1);
    
    			//#5.WriteFile()의 버퍼를 임시 버퍼에 복사
    			ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, lpBuffer, dwNumOfBytesToWrite, NULL);
    			printf("\n### original string : %s\n", lpBuffer);
    
    			//#6.소문자-대문자 변환
    			for (i = 0; i < dwNumOfBytesToWrite; i++) {
    				if ('a' <= lpBuffer[i] && lpBuffer[i] <= 'z')
    					lpBuffer[i] -= 0x20;
    			}
    
    			printf("\n### converted string : %s\n", lpBuffer);
    
    			//#7.변환된 버퍼를 WriteFile()에 복사
    			WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, lpBuffer, dwNumOfBytesToWrite, NULL);
    			
    			//#8.임시 버퍼 해제
    			free(lpBuffer);
    
    			//#9.Thread Context의 EIP를 WriteFile() 시작으로 변경
    			ctx.Eip = (DWORD)g_pfWriteFile;
    			SetThreadContext(g_cpdi.hThread, &ctx);
    
    			//#10.Debuggee 프로세스 진행
    			ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
    			Sleep(0);
    
    			//#11.API 훅
    			WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chINT3, sizeof(BYTE), NULL);
    
    			return TRUE;
    		}
    	}
    	return FALSE;
    }

    하나하나 숫자를 달아놓은 주석을 살펴보자.

     

    #1. Unhook

    //0xCC로 덮어쓴 부분을 원래대로 되돌림
    WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chOrgByte, sizeof(BYTE), NULL);

    후킹을 수행한 후 WriteFile()을 정상적으로 호출하기 위해서 미리 INT3으로 변경된 부분을 원래 값으로 바꿔준다.

     

    #2. Thread Context 구하기

    ctx.ContextFlags = CONTEXT_CONTROL;
    GetThreadContext(g_cpdi.hThread, &ctx);

    기존 thread를 실행하면서 중요한 정보 (레지스터 값)를 저장하기 위해 CPU 레지스터 정보를 CONTEXT 구조체에 저장한다. CONTEXT 구조체의 정의는 다음과 같다. (64bit로 확장되면서 책에 있는 구조체와는 다른 형태를 띈다.)

    typedef struct _CONTEXT {
      DWORD64 P1Home;
      DWORD64 P2Home;
      DWORD64 P3Home;
      DWORD64 P4Home;
      DWORD64 P5Home;
      DWORD64 P6Home;
      DWORD   ContextFlags;
      DWORD   MxCsr;
      WORD    SegCs;
      WORD    SegDs;
      WORD    SegEs;
      WORD    SegFs;
      WORD    SegGs;
      WORD    SegSs;
      DWORD   EFlags;
      DWORD64 Dr0;
      DWORD64 Dr1;
      DWORD64 Dr2;
      DWORD64 Dr3;
      DWORD64 Dr6;
      DWORD64 Dr7;
      DWORD64 Rax;
      DWORD64 Rcx;
      DWORD64 Rdx;
      DWORD64 Rbx;
      DWORD64 Rsp;
      DWORD64 Rbp;
      DWORD64 Rsi;
      DWORD64 Rdi;
      DWORD64 R8;
      DWORD64 R9;
      DWORD64 R10;
      DWORD64 R11;
      DWORD64 R12;
      DWORD64 R13;
      DWORD64 R14;
      DWORD64 R15;
      DWORD64 Rip;
      union {
        XMM_SAVE_AREA32 FltSave;
        NEON128         Q[16];
        ULONGLONG       D[32];
        struct {
          M128A Header[2];
          M128A Legacy[8];
          M128A Xmm0;
          M128A Xmm1;
          M128A Xmm2;
          M128A Xmm3;
          M128A Xmm4;
          M128A Xmm5;
          M128A Xmm6;
          M128A Xmm7;
          M128A Xmm8;
          M128A Xmm9;
          M128A Xmm10;
          M128A Xmm11;
          M128A Xmm12;
          M128A Xmm13;
          M128A Xmm14;
          M128A Xmm15;
        } DUMMYSTRUCTNAME;
        DWORD           S[32];
      } DUMMYUNIONNAME;
      M128A   VectorRegister[26];
      DWORD64 VectorControl;
      DWORD64 DebugControl;
      DWORD64 LastBranchToRip;
      DWORD64 LastBranchFromRip;
      DWORD64 LastExceptionToRip;
      DWORD64 LastExceptionFromRip;
    } CONTEXT, *PCONTEXT;

    GetThreadContext() API를 호출하여 ctx 구조체 변수에 해당 thread인 g_cpdi.hThread의 CONTEXT를 저장한다.

     

    #3. WriteFile()의 param 2, 3 구하기

    //param2 : ESP+0x8
    //param3 : ESP+0xC
    ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8), &dwAddrOfBuffer, sizeof(DWORD), NULL);
    ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC), &dwNumOfBytesToWrite, sizeof(DWORD), NULL);

    WriteFile()을 호출할 때 넘어온 parameter 중에서 param2(Buffer), param3(Buffer size)를 알아내야 하는데 스택에 저장되므로 각각 Esp로부터 떨어진 거리 값만큼 더해준 주소에 대해 ReadProcessMemory() API를 호출하여 각각 dwAddrOfBuffer와 dwNumOfBytesToWrite에 그 값을 저장한다. 

     

    #4 ~ #8 소문자 -> 대문자 변환

    //#4.임시 버퍼 할당
    lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite + 1);
    memset(lpBuffer, 0, dwNumOfBytesToWrite + 1);
    
    //#5.WriteFile()의 버퍼를 임시 버퍼에 복사
    ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, lpBuffer, dwNumOfBytesToWrite, NULL);
    printf("\n### original string : %s\n", lpBuffer);
    
    //#6.소문자-대문자 변환
    for (i = 0; i < dwNumOfBytesToWrite; i++) {
    	if ('a' <= lpBuffer[i] && lpBuffer[i] <= 'z')
    		lpBuffer[i] -= 0x20;
    }
    
    printf("\n### converted string : %s\n", lpBuffer);
    
    //#7.변환된 버퍼를 WriteFile()에 복사
    WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, lpBuffer, dwNumOfBytesToWrite, NULL);
    			
    //#8.임시 버퍼 해제
    free(lpBuffer);

    임시 buffer인 lpBuffer를 할당한 후, dwAddrOfBuffer 내용을 복사한 후 for 문을 통해 소문자인 문자가 있는 경우 0x20 (소문자와 대문자의 아스키코드 차이)를 빼주어 대문자가 되게 만든 후, WriteProcessMemory를 통해 기존 버퍼에 덮어쓰는 역할을 한다.

     

    #9. Thread Context의 EIP를 WriteFile() 시작으로 변경

    ctx.Eip = (DWORD)g_pfWriteFile;
    SetThreadContext(g_cpdi.hThread, &ctx);

    Eip를 INT3에 의해 1byte 밀린 값을 보정하기 위해 다시 WriteFile()의 시작 주소가 저장된 g_pfWriteFile을 대입해준다. 이후에 SetThreadContext() API를 통해 저장해두었던 CONTEXT 정보도 복원해준다.

     

    #10. Debugger 프로세스 진행

    ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
    Sleep(0);

    ContinueDebugEvent() API를 호출하여 debuggee 프로세스 실행을 진행하게 되면 WriteFile()이 호출된다. 이때 Sleep(0)을 통해 현재 thread에게 할당된 CPU 작업 시간을 포기시켜 다른 thread인 notepad.exe의 thread가 실행되면서 WriteFile() API가 정상적으로 호출된다.

     

    #11. API 훅 설치

    WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chINT3, sizeof(BYTE), NULL);

    다시 훅을 설치하여 이후에도 파일을 저장할 때 후킹이 되도록 설정한다.

     

     

    꽤 흥미로운 실습이었다. 그러나 64-bit 환경에서 동일한 코드를 작성하였을 때는 컴파일 자체가 되지 않았다. 아마도 CONTEXT 등의 구조체가 멤버도 달라지는 등 환경이 변함에 따라 동일한 코드로는 후킹이 제한되는 것 같다.

    반응형

    댓글

Designed by Tistory.