1. 기술 맵 디버그 테크닉
디버그(Debug) 기법을 이용한 API 후킹기술을 이용하여 notepad. exe 프로세스의 kernel32! WriteFile() API를 후킹한다.
2. 디버거 설명
용어 정리
① 디버거(Debugger) · 디버깅을 위해 사용하는 프로그램
② 디버기(Debuggee) - 디버깅 당하는 target 프로그램
디버거 기능: 디버기 명령을 하나씩 수행, 레지스터와 메모리에 대한 모든 접근 권한을 갖는다.
디버거 동작 원리
① 디버거 프로세스로 등록
② OS는 디버기에서 디버그 이벤트가 발생하면 디버기의 실행을 멈추고 해당 이벤트를 디버거에게 통보
③ 디버거는 해당 이벤트를 처리
④ 디버거는 디버기의 실행을 재개
디버그 이벤트 (Debug Event): 9가지
① CREATE_PROCESS_DEBUG_EVENT
② CREATE_THREAD_DEBUG_EVENT
③ EXCEPTION_DEBUG_EVENT → 디버깅 관련 이벤트
④ EXIT_PROCESS_DEBUG_EVENT
⑤ EXIT_THREAD_DEBUG_EVENT
⑥ LOAD_DLL_DEBUG_EVENT
⑦ OUTPUT_ DEBUG_STRING_ EVENT
⑧ UNLOAD_DLL_DEBUG_ EVENT
⑨ RIP_EVENT
3. 작업 순서
① 디버거 프로그램을 작성/실행하여, 후킹을 원하는 프로세스에 attach하여 해당 프로세스를 디버기로 설정한다.
② 디버거에서 후킹 하려는 API 함수의 시작 주소의 첫 바이트를 0xCC로 변경(BP설정)한다.
③ 디버기를 수행한다.
④ 해당 API 함수가 호출되면 제어는 디버거에게 넘어 온다.
⑤ 디버기에 대하여 원하는 작업을 수행한다.
⑥ 언혹: OxCC를 원래대로 복원시킨다 (API 함수의 정상 수행을 위하여)
⑦ 해당 API 함수가 정상 실행된다.
4. 실습
① notepad.exe 실행 → PID 확인
② hookdbg. exe 실행
③ notepad.exe (메모장)에 글 입력 → 저장
④ hookdbg. exe 콘솔의 결과 확인
⑤ 저장한 메모장 파일을 다시 열어서 내용을 확인
: 소문자로 입력한 것이 모두 대문자로 변경되어 파일에 저장됨
5.1 디버거 소스코드 분석: hookdbg.cpp - main()
int main(int argc, char* argv[])
{
DWORD dwPID;
if (argc != 2)
{
printf("\nUSAGE : hookdbg.exe <pid>\n");
return 1;
}
// attach 프로세스
dwPID = atoi(argv[1]); // argv[1]: 디버기(notepad.exe)의 PID
if (!DebugActiveProcess(dwPID)) // 디버거(hookdbg 프로세스)를 디버기할 프로세스(notepad)에 attach시켜서 디버깅을 시작할 수 있게 한다.
{
printf("DebugActiveProcess(%d) failed!!!\n"
"Error Code = %d\n", dwPID, GetLastError());
return 1;
}
// 디버거 루프
DebugLoop();
return 0;
}
5.2 디버거 소스코드 분석: hookdbg.cpp - DebugLoop()
void DebugLoop()
{
DEBUG_EVENT de;
DWORD dwContinueStatus;
while (WaitForDebugEvent(&de, INFINITE)) // Debuggee 로부터 event가 발생할 때까지 기다림 > 발생한 이벤트 정보는 de에 저장
{
dwContinueStatus = DBG_CONTINUE;
if (CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode) // CREATE_PROCESS_DEBUG_EVENT: 디버거가 attach될 때 발생
{ // Debuggee 프로세스 생성 혹은 attach 이벤트
OnCreateProcessDebugEvent(&de);
}
else if (EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode) // EXCEPTION_DEBUG_EVENT: 디버기 수행 중 BP 명령이 수행되면 발생
{ // 예외 이벤트
if (OnExceptionDebugEvent(&de))
continue;
}
else if (EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode) // EXIT_PROCESS_DEBUG_EVENT: 디버기가 종료될 때 발생하는 이벤트
{ // Debuggee 프로세스 종료 이벤트 > Debugger도 종료
break;
}
// 이벤트 처리 후, Debuggee의 실행을 재개시킴 (dwContinueStatus == DBG_CONTINUE)
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
}
}
5.3 디버거 소스코드 분석: hookdbg.cpp - OnExceptionDebugEvent()
BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
CONTEXT ctx;
PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
PCHAR lpFileName = NULL;
DWORD dwAddrOfFileName, i;
HANDLE hFile;
DWORD dwFileSize, dwBytesRead, dwBytesWritten;
if (EXCEPTION_BREAKPOINT == per->ExceptionCode) // 전송된 '이벤트/예외' == BP Exception인 경우만 처리
{
if (g_pfCreateFile == per->ExceptionAddress) // Event를 발생시킨 주소(=BP주소)가 WriteFile() API함수의 시작 주소인지 확인
{
// 1) Unhook: 0xCC로 덮어쓴 부분을 WriteFile()API 코드의 원래 byte로 되돌림
if (!WriteProcessMemory(g_cpdi.hProcess, g_pfCreateFile, &g_chOrgByte, sizeof(BYTE), NULL)) {
printf("WriteProcessMemory failed with error %d\n", GetLastError());
return FALSE;
}
// 2) Thread Context 구하기: CONTEXT ctx;
ctx.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(g_cpdi.hThread, &ctx)) {
printf("GetThreadContext failed with error %d\n", GetLastError());
return FALSE;
}
// 3) WriteFile()의 파라미터 2, 3(IpBuffer, numberOfBytesToWrite) 값 구하기 > 스택 확인
if (!ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x4), &dwAddrOfFileName, sizeof(DWORD), NULL)) {
printf("ReadProcessMemory failed with error %d\n", GetLastError());
return FALSE;
}
lpFileName = (PCHAR)malloc(MAX_PATH);
if (!lpFileName) {
printf("Memory allocation failed\n");
return FALSE;
}
memset(lpFileName, 0, MAX_PATH);
if (!ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfFileName, lpFileName, MAX_PATH, NULL)) {
printf("ReadProcessMemory failed with error %d\n", GetLastError());
free(lpFileName);
return FALSE;
}
printf("\n### original file name ###\n%s\n", lpFileName);
hFile = CreateFileA(lpFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
// 4) 임시 버퍼 할당 후, WriteFile()의 버퍼를 임시 버퍼에 복사
dwFileSize = GetFileSize(hFile, NULL);
PCHAR lpFileBuffer = (PCHAR)malloc(dwFileSize + 1);
if (!lpFileBuffer) {
printf("Memory allocation failed\n");
CloseHandle(hFile);
free(lpFileName);
return FALSE;
}
memset(lpFileBuffer, 0, dwFileSize + 1);
if (!ReadFile(hFile, lpFileBuffer, dwFileSize, &dwBytesRead, NULL)) {
printf("ReadFile failed with error %d\n", GetLastError());
free(lpFileBuffer);
CloseHandle(hFile);
free(lpFileName);
return FALSE;
}
printf("\n### original file content ###\n%s\n", lpFileBuffer);
// 5) 소문자 > 대문자 변환
for (i = 0; i < dwBytesRead; i++) {
if (0x61 <= lpFileBuffer[i] && lpFileBuffer[i] <= 0x7A)
lpFileBuffer[i] -= 0x20;
}
printf("\n### converted file content ###\n%s\n", lpFileBuffer);
// 6) 변환된 버퍼를 WriteFile() 버퍼로 복사한 후, 임시 버퍼를 해제
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
if (!WriteFile(hFile, lpFileBuffer, dwBytesRead, &dwBytesWritten, NULL)) {
printf("WriteFile failed with error %d\n", GetLastError());
}
CloseHandle(hFile);
free(lpFileBuffer);
}
else {
printf("CreateFileA failed with error %d\n", GetLastError());
}
free(lpFileName);
// 7) Thread Context의 EIP를 WriteFile() 시작으로 변경: 현재는 WriteFile()+1만큼 지나왔음
ctx.Eip = (DWORD)g_pfCreateFile;
if (!SetThreadContext(g_cpdi.hThread, &ctx)) {
printf("SetThreadContext failed with error %d\n", GetLastError());
return FALSE;
}
// 8) Debuggee 프로세스 실행 재개 및 Sleep(0)
if (!ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE)) {
printf("ContinueDebugEvent failed with error %d\n", GetLastError());
return FALSE;
}
Sleep(0);
// 9) API Hook을 다시 설정하고 True를 return하여 작업을 마친다.
if (!WriteProcessMemory(g_cpdi.hProcess, g_pfCreateFile, &g_chINT3, sizeof(BYTE), NULL)) {
printf("WriteProcessMemory failed with error %d\n", GetLastError());
return FALSE;
}
return TRUE; // 디버거는 다음 이벤트를 기다림
}
}
return FALSE; // 이벤트 처리 종료 > 디버기 실행 재기
}
'공부 > 리버싱 핵심원리' 카테고리의 다른 글
DLL Injection&코드 인젝션 (0) | 2024.05.22 |
---|---|
DLL 인젝션 (0) | 2024.05.21 |
Windows 메시지 후킹 (0) | 2024.05.21 |
인라인 패치 실습 (0) | 2024.05.14 |
UPX 언패킹 (0) | 2024.05.12 |