API 후킹
리버싱의 핵심, 리버싱의 꽃
Wind32 API를 후킹하는 기술을 API 후킹이라고 함.
API는 하드웨어를 사용하고 싶을때 OS에서 제공하는 방법
Win32 API호출을 중간에 가로채서 제어권을 얻어내는 것.
정상적인 흐름 : 프로세스 -> createFile()
후킹 흐름 : 프로세스 -> MyCreateFile() -> CreateFile()
방법은 static ,dynamic이 있음
static은 파일 수정
dynamic은 메모리 수정
Location(공략 위치)
IAT - 프로세스의 IAT조작
Code - 코드를 수정(시작 코드 JMP 패치, 함수 덮어쓰기, 필요한 부분 변경)
EAT -DLL의 EAT조작
방법
Debug
Injection(Dll injection, Code injection)
디버거를 이용해 dynamic, Code 수정으로 메모장 WriteFile 후킹해서 영어 소문자인 것들을 대문자로 바꾸어 저장하기
#include "windows.h"
#include "stdio.h"
#include "tlhelp32.h"
#include "tchar.h"
#define DEF_PROC_NAME (L"notepad.exe")
LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xCC, g_chOrgByte = 0;
DWORD dwPID;
DWORD FindProcessID(LPCTSTR szProcessName)
{
DWORD dwPID = 0xFFFFFFFF;
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;
// Get the snapshot of the system
pe.dwSize = sizeof(PROCESSENTRY32);
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);
// find process
Process32First(hSnapShot, &pe);
do
{
if (!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
{
dwPID = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapShot, &pe));
CloseHandle(hSnapShot);
return dwPID;
}
//writeFile의 첫 바이트를 저장하고 0xcc로 바꿈
BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");//00007FF9967E5310
printf("%p\n", g_pfWriteFile);
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;
}
// breakPoint 일떄 즉 0xcc 만날때 처리
BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
CONTEXT ctx;
PBYTE lpBuffer = NULL;
DWORD dwNumOfBytesToWrite, i;
long long dwAddrOfBuffer;
PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
if (EXCEPTION_BREAKPOINT == per->ExceptionCode)
{
if (g_pfWriteFile == per->ExceptionAddress)
{
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chOrgByte, sizeof(BYTE), NULL);
// #2. 레지스터 정보 얻기
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(g_cpdi.hThread, &ctx);
GetThreadContext(g_cpdi.hProcess, &ctx);
// dwNumOfBytesToWrite = 5;//ctx.R8;
// dwAddrOfBuffer = ctx.Rdx;
printf("R8 = %p\n", ctx.R8);
printf("Rdi = %p\n", ctx.Rdi);
printf("Rax = %p\n", ctx.Rax);
printf("Rdx = %p\n", ctx.Rdx);
printf("Rbx = %p\n", ctx.Rbx);
printf("R11 = %p\n", ctx.R11);
printf("Rip = %p\n", ctx.Rip);
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Rsp - 0x08),
&dwAddrOfBuffer, sizeof(long long), NULL);
printf("%p\n", dwAddrOfBuffer);
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Rsp + 0x08),
&dwNumOfBytesToWrite, sizeof(DWORD), NULL);
printf("%d\n", dwNumOfBytesToWrite);
lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite + 1);
memset(lpBuffer, 0, dwNumOfBytesToWrite + 1);
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(dwAddrOfBuffer),
lpBuffer, dwNumOfBytesToWrite, NULL);
//lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite + 1);
//memset(lpBuffer, 0, dwNumOfBytesToWrite + 1);
//printf("rsp = %p\n", ctx.Rsp);
/*
lpBuffer = (PBYTE)malloc(0x201);
memset(lpBuffer, 0, 0x201);
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Rsp),
lpBuffer, 0x8, NULL);
for (i = 0; i < 0x8; i++)
{
printf("%02x", lpBuffer[i]);
}
printf("i \n");
lpBuffer = (PBYTE)malloc(0x201);
memset(lpBuffer, 0, 0x201);
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Rsp-0x100),
lpBuffer, 0x200, NULL);
printf("\n### original string ###\n%s\n", lpBuffer);
for (i = 0; i < 0x200; i++)
{
if ((i % 8) == 0) {
printf("i=%d/// \n",i/8);
}
printf("%02x", lpBuffer[i]);
}*/
printf("\n### org string ###\n%s\n", lpBuffer);
for (i = 0; i < dwNumOfBytesToWrite; i++)
{
if (0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A)
lpBuffer[i] -= 0x20;
}
printf("\n### converted string ###\n%s\n", lpBuffer);
WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);
free(lpBuffer);
ctx.Rip = (DWORD)g_pfWriteFile;
SetThreadContext(g_cpdi.hProcess, &ctx);
ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
Sleep(1);
// #11. API Hook
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,&g_chINT3, sizeof(BYTE), NULL);
// DebugActiveProcessStop(dwPID);
// printf("\n### org string ###\n%s\n", lpBuffer);
// OnCreateProcessDebugEvent(pde);
return TRUE;
}
}
printf(
"Error Code = %d\n", GetLastError());
printf("ee = %p\n", per->ExceptionCode);
// ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
return FALSE;
}
void DebugLoop()
{
DEBUG_EVENT de;
DWORD dwContinueStatus;
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))
{
// dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
}
else
continue;
}
else if (EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
{
printf(
"Error Code = %d\n", GetLastError());
printf("break\n");
break;
}
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
}
}
int main(int argc, char* argv[])
{
// Attach Process
dwPID = (DWORD)FindProcessID(DEF_PROC_NAME);
if (!DebugActiveProcess(dwPID))
{
printf("DebugActiveProcess(%d) failed!!!\n"
"Error Code = %d\n", dwPID, GetLastError());
return 1;
}
printf("debugging..\n");
DebugLoop();
return 0;
}
코드를 살펴보면
main
1.프로세스 이름으로 PID를 찾고 PID의 프로세스에 디버거를 붙인다.
2.DebugLoop함수에 들어간다
DebugLoop
1. WaitForDebugEvent함수로 이벤트를 기다린다.
2.발생이벤트에 따라 처리한다
디버거가 붙으면 - OnCreateProcessDebugEvent
디버거가 뗴지면 - break
디버거에 예외 이벤트가 발생하면 - OnExceptionDebugEvent
OnCreateProcessDebugEvent
1.writefile의 주소를 찾는다
2.g_cpdi에 create process info를 복사한다.
3.프로세스의 writefile 함수 첫 바이트를 읽고 저장한다.
3.프로세스의 writefile 함수 첫 바이트를 INT3명령어(0xcc)로 변경한다.
OnExceptionDebugEvent
1. BP 이벤트인지 확인하고 , writefile함수의 첫번째에서 발생한 것인지 확인한다.
2.레지스터의 정보를 얻는다.
3.writefile의 인자를 저장한다.(길이, 문자열 시작 주소)
4.문자열을 대문자로 바꾸어 적는다.
5.Rip 값을 writefile로 바꾸고( 현재 rip는 writefile +1임 INT 3(1바이트를 실행하고 넘어와서) 프로세스에 반영한다.
6.프로세스를 진행시킨다. (제대로 진행 시키기 위해 sleep(1))
7.다시 훅을 걸기 위해 writefile의 맨 앞을 0xcc로 바꾼다.
여기서 내가 x64dbg로 메모장을 분석해보았는데
rcx에는 뭔지 모를게 들어가고
rdx에 문자열 주소, r8에는 길이 r9에도 모를게 들어갔다.
그래서 x64dbg상에서 r8을 늘리고 rdx의 주소를 다른걸로 바꾸고 그 주소에 문자열을 길게 써봤는데 작동을 했다.
하지만 (위 코드)디버거로 코드를 수정할때 reg를 얻어왔을때 다른 값들이 들어있었다. 이유를 모르겠다.
그래서 (위 코드)디버거로 문자의 길이와 주소를 알아오기 위해 stack을 위 아래 0x100 바이트씩 출력해서 RSP 기준 값들을 보면서 찾았다. 그게 rsp+8 ,rsp-8 이었다.
되긴 함 소문자 글자를 다 대문자로 바꾸기
왜 레지스터 값이 다를까