공부해요/리버싱_핵심원리

30. 메모장 WriteFile() 후킹

yenas0 2024. 11. 17. 07:26
반응형

디버거(Debugger) - 디버깅 프로그램
디버기(Debuggee) - 디버깅 당하는 프로그램
 
디버거 기능
디버기가 올바르게 실행되는지 확인하고 예상치 못한 프로그램의 오류를 발견하는 것
 
디버거의 동작 원리
디버거 프로세스로 등록되면 운영체제는 디버기에서 디버그 이벤트가 발생할 때 디버기의 실행을 멈추고 해당 이벤트를 디버거에게 통보
exception도 디버그 이벤트에 해당된다. 만약에 디버깅 중 아니였으면 그냥 자체예외처리 아니면 OS의 예외 처리 루틴이 됨.
 
디버그 이벤트 종류
• 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_DEBUG_STRING_EVENT
• RIP_EVENT
 
디버깅 이벤트랑 관련된 Exception
• 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
• EXCEPTIONJLLEGALJNSTRUCTION
• EXCEPTION_IN_PAGE_ERROR
• EXCEPTIONJNT_DIVIDE_BY_ZERO
• EXCEPTION_INT_OVERFLOW
• EXCEPTION JNVAnD_DISPOSITION
• EXCEPTION_NONCONTINUABLE_EXCEPnON
• EXCEPTION_PRIVJNSTRUCnON
• EXCEPTION_SINGLE_STEP
• EXCEPTION_STACK_OVERFLOW
 
 
작업 순서
- 후킹 원하는 프로세스 디버기로 만듦
- 훅: API 시작 주소 첫 바이트를 0xCC로 바꿈
- API 호출되면 제어권이 디버거한테로..
- 원하는 작업 수행
- Unhook: 0xCC 복원
- API 실행
- hook: 다시 0xCC
- 디버기한테 제어 돌려줌
 
 
실습
목표: 파일이 저장될 때 입력된 파라미터 조작해 소문자를 대문자로 변경
과정
- Notepad.exe의 PID 알아냄
- 후킹 프로그램 실행(hookdbg.exe)
- notepad에 아무거나 쓰고 저장
- hookdbg.exe 보고 메모장 다시 열어보기
 
 
동작 과정
WriteFile()API를 보면 두번째 파라미터는 쓰기 버퍼고 세번째 파라미터는 써야 할 크기임
내가 글씨를 쓰면 현재 스택에는 리턴 주소가 있고 현재주소+8에는 쓰기 버퍼 주소가 있다. 쓰기 버퍼 주소에 가면 내가 쓴 글씨가 보임. 즉 여기서 API를 후킹해서 쓰기버퍼를 원하는 문자열로 덮기만 하면된다.
어디 수정해야되는지 아니까 수정한 문자열이 파일에 저장되야함.
EIP는 API시작 주소 + 1이다. 왤까.. BP를 WriteFile() API 시작 주소에 설치했고 디버기 내부에서 이게 호출되면 API 시작주소에서 0xCC를 만난다. 그래서 저 명령어가 1바이트라 1 증가한거다.
실행 흐름을 WriteFile()로 해두기만 하면 무한루프에 빠지니까 이 API 시작주소에 설치한 BP를 제거해야됨. 이게 언훅..
쓰기 버퍼를 덮어쓰고 API 코드를 정상으로 돌리고 EIP값을 WriteFile()API로 변경하면 변경된 문자열이 파일에 저장됨
 
 
소스코드
main에서는 프로그램 실행 파라미터로 API 후킹하려는 프로세스의 PID를 받은 후 DebugActiveProcess() API를 통해 실행 중인 프로세스에 Attach해서 디버깅 시작한다.
DebugLoop()에서 디버기로부터 발생하는 이벤트를 받아서 처리하고 디버기의 실행을 재개한다.
• EXIT_PROCESS_DEBUG_EVENT: 디버기 프로세스 종료 시 발생
• CREATE_PROCESS_DEBUG_EVENT: 디버기의 프로세스가 시작될 때 호출
• EXCEPTION_DEBUG_EVENT: 디버기의 INT3(0xCC) 명령을 처리하게 될 함수
위에는 DebugLoop()에서 처리하는 이벤트..
 
EXCEPTION_DEBUG_EVENT 얘는 중요..하다고 하니 코드를 보자
 
언훅(API 훅 제거)

// OxCC로덮어쓴부분올 original byte로되돌림
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chOrgByte, sizeof(BYTE), NULL);

목표작업 이후 WriteFile()을 정상적인 상태로 호출하기 위함.
 
스레드의 CONTEXT 구하기

// Thread Context 구하기
ctx.ContextFlags = C0NTEXT_CONTROL;
GetTh readContext(g_cpdi.hTh read, &ctx);

 
WriteFile()의 param 2,3 값 구하기

// 함수의 파라미터는 해당 프로세스의 스택에 존재함 // param 2 : ESP + 0x8
// param 3 : 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. 임시버퍼할당
IpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
memset(IpBuffer, 0, dwNumOfBytesToWrite+1);

// #5. WriteFileO의 버퍼를임시버퍼에 복사
ReadProcessMemory(g cpdi.hProcessz (LPVOID)dwAddrOfBuffer, IpBuffer, dwNumOfBytesToWrite, NULL);
printf("\n### original string : 잉s\n", IpBuffer);

// #6. 소문자 - 대문자변환
for( i = 0; i < dwNumOfBytesToWrite; i++ ) {
	if( 0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A ) 
    	lpBuffer[i] -= 0x20;
}
printfconverted string : "%s\n", IpBuffer);

// #7. 변환된버퍼를 WriteFileO 버퍼로복사
WriteProcessMemory(g cpdi.hProcess, (LPVOID)dwAddrOfBuffer, IpBuffer, dwNumOfBytesToWrite, NULL);

// #8. 임시버퍼해제
free(lpBuffer);

 
스레드 컨텍스트의 EIP를 WriteFile()시작으로 변경

// (현재는 WriteFileO + 1 위치 INT3 명령 이후) 
ctx.Eip = (DWORD)gpfWriteFile;
SetThreadContext(g_cpdi.hThread, &ctx);

 
디버거 프로세스를 진행시킴: 정상 WriteFile() API 호출해야하니..

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

 
API 훅 설치

WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chINT3, sizeof(BYTE)/ NULL?;
반응형