ASLR 걸린 모듈 후킹

2018. 3. 13. 19:010x03 Reversing Theory

728x90

ATTACKER 


#include <tchar.h> // use _tprintf

#include <iostream> // 입출력

#include <Windows.h> // 식별자, Read/Write ProcessMemory

#include <psapi.h> // EnumProcessModulesEx

//#include <stdlib.h> // malloc

#include <tlhelp32.h>

#define BASE_OF_CODE (0x1000)


#define TARGET "BYPASSTESTGAME.exe"

#define x86 1

#define x64 2

using namespace std;

BOOL GetStart();

void PrintProcessMemory(HANDLE, LPCVOID, SIZE_T);

void ModifyProcessMemory(HANDLE, LPVOID, LPCVOID, SIZE_T);

LPCVOID GetBaseAddress(DWORD);

void PrintError(const TCHAR*);


int main(int, char *[])

{

cout << "main function start" << endl;

GetStart();


cout << "\Mrealrudaganya?!?!?!?" << endl;

return 0;

}


BOOL GetStart()

{

SIZE_T lpOffset_R = 0x1e; // 계속 유동적으로 리버싱으로 찾아야 함  

LPCVOID lpBaseAddress_R; // read용 

SIZE_T nSize_R = 0x7; // n바이트를 읽어올 예정 (여기선 7바이트)


SIZE_T lpOffset_W;

LPVOID lpBaseAddress_W; // write용 

SIZE_T nSize_W; // 변경할 사이즈 

LPCVOID lpBuffer_W; // nSize_W만큼 할당할 것임 


// The portions of the system to be included in the snapshot.

DWORD dwFlags = TH32CS_SNAPPROCESS;

// The process identifier of the process to be included in the snapshot.

DWORD th32ProcessID = NULL;


bool exit = false;


do

{

cout << "\nPlease choose a method to modify the value of memory.";

cout << "\n1. Change the value of the operand.";

cout << "\n2. Change conditional branch statement.";


char choice = 0;

do

{

cout << "\nInput: ";

cin >> choice;

} while (choice != '1' && choice != '2');


//조건분기에 따라 주소가 다르므로 분기문으로 해두었는데

// 더 자동화 시키려면 어떻게 해야하는지 연구해보기 

if (choice == '1')

{

lpOffset_W = 0xCC;

nSize_W = 0x1;

lpBuffer_W = new char[nSize_W] { 0x01 }; // 특정 오프셋에 0x01대입 

}

else

{

lpOffset_W = 0xE4;

nSize_W = 0x1;

lpBuffer_W = new unsigned char[nSize_W] { 0xEB }; // 특정 오프셋에 0xEB 대입 (0XEB = OPCODE = JMP)

}


cout << "\nFinding processes...";


bool wasFound = false;

do

{

/* CreateToolhelp32Snapshot function

특정 프로세스들뿐 아니라 이러한 프로세스들에 의해 사용되는 힙, 모듈 및 스레드의 스냅 샷을 가져옵니다. */

HANDLE hSnapshot = CreateToolhelp32Snapshot(dwFlags, th32ProcessID);


if (hSnapshot == INVALID_HANDLE_VALUE)

{

PrintError(TEXT("CreateToolhelp32Snapshot"));

return(FALSE);

}


PROCESSENTRY32 pe;

/* PROCESSENTRY32 구조체에 대한 포인터.

실행 파일의 이름, 프로세스 식별자 및 상위 프로세스의 프로세스 식별자와 같은 프로세스 정보를 포함합니다. */

//LPPROCESSENTRY32 lppe = &pe;

pe.dwSize = sizeof(PROCESSENTRY32);


/* Process32First function

시스템 스냅샷에서 마주친 첫 번째 프로세스에 대한 정보를 가져옵니다. */

if (!Process32First(hSnapshot, &pe))

{

PrintError(TEXT("Process32First"));

CloseHandle(hSnapshot);

return(FALSE);

}


HANDLE hProcess;

DWORD dwPriorityClass;


do

{

if (wcscmp(pe.szExeFile, TEXT(TARGET)) == 0) // 스냅샷정보와 현재 타겟 바이너리 비교 

{

wasFound = true;

//dwPriorityClass = 0;


// The access to the process object.

DWORD dwDesiredAccess = PROCESS_ALL_ACCESS; // POSSIBLE READ + WRITE 


/* If this value is TRUE, processes created by this process will inherit the handle.

Otherwise, the processes do not inherit this handle. */

BOOL bInheritHandle = FALSE;

// The identifier of the local process to be opened.

DWORD dwProcessId = pe.th32ProcessID;


/* OpenProcess function

Opens an existing local process object. */

hProcess = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);

// 프로세스가 열리면 null이 아님 

if (hProcess == NULL)

{

PrintError(TEXT("OpenProcess"));

continue;

}


_tprintf(TEXT("\n\nhProcess: 0x%p"), hProcess);

_tprintf(TEXT("\n\nhProcess: 0x%d"), hProcess);


LPCVOID lpBaseAddress = GetBaseAddress(dwProcessId); // 베이스어드레스 얻음

printf("GET BASEADDR : %p\n", lpBaseAddress);

int addr = (int)lpBaseAddress;

printf("RVA : %p\n", (int)lpBaseAddress + BASE_OF_CODE);

// LPCVOID -> BYTE* 로 쓰이는 듯

lpBaseAddress_R = (LPCVOID)((const BYTE *)lpBaseAddress + BASE_OF_CODE + lpOffset_R);

printf("lpbaseaddr read = %p\n", lpBaseAddress_R);

lpBaseAddress_W = (LPVOID)((const BYTE *)lpBaseAddress + + BASE_OF_CODE+ lpOffset_W);

printf("lpbaseaddr write = %p\n", lpBaseAddress_W);

/* GetPriorityClass function

지정된 프로세스의 우선 순위 클래스를 가져옵니다.

이 값은 프로세스의 각 스레드의 우선 순위 값과 함께 각 스레드의 기본 우선 순위 수준을 결정합니다. */

_tprintf(TEXT("\n\tdwSize              = %lu"), pe.dwSize);

_tprintf(TEXT("\n\tdwProcessId         = 0x%p"), dwProcessId);

_tprintf(TEXT("\n\tcntThreads          = %lu"), pe.cntThreads);

_tprintf(TEXT("\n\tth32ParentProcessID = 0x%p"), pe.th32ParentProcessID);

_tprintf(TEXT("\n\tPriority base       = %d"), pe.pcPriClassBase);


dwPriorityClass = GetPriorityClass(hProcess);


if (!dwPriorityClass)

PrintError(TEXT("GetPriorityClass"));

else

_tprintf(TEXT("\n\tdwPriorityClass     = 0x%08X"), dwPriorityClass);


PrintProcessMemory(hProcess, lpBaseAddress_R, nSize_R);

ModifyProcessMemory(hProcess, lpBaseAddress_W, lpBuffer_W, nSize_W);

PrintProcessMemory(hProcess, lpBaseAddress_R, nSize_R);


/*PrintProcessMemory(hProcess, (LPVOID)0x401080, 0x11);

ModifyProcessMemory(hProcess, (LPVOID)0x40108E, new char[nSize_W] { 0x01 }, 0x1);

PrintProcessMemory(hProcess, (LPVOID)0x401080, 0x11);*/


CloseHandle(hProcess);

}

} while (

/* Process32Next function

스냅샷에 기록 된 첫 번째 프로세스에 대한 정보를 가져오기 위해 Process32First 함수를 사용합니다. */

Process32Next(hSnapshot, &pe));


CloseHandle(hSnapshot);

} while (!wasFound);


cout << "\n\nPlease choose what you want to do.";

cout << "\n1. Memory modification.";

cout << "\n2. Exit the program.";


do

{

cout << "\nInput: ";

cin >> choice;


if (choice == '2')

{

exit = true;

break;

}

} while (choice != '1');

} while (!exit);


return(TRUE);

}


void PrintProcessMemory(

/* A handle to the process with memory that is being read.

The handle must have PROCESS_VM_READ access to the process. */

HANDLE  hProcess,

/* A pointer to the base address in the specified process from which to read.

Before any data transfer occurs, the system verifies that all data in the base address and memory of the specified size is accessible for read access, and if it is not accessible the function fails.

0x401080 */

LPCVOID lpBaseAddress,

// The number of bytes to be read from the specified process.

SIZE_T  nSize

)

{

// A pointer to a buffer that receives the contents from the address space of the specified process.

LPVOID  buffer = operator new(nSize);

// A pointer to a variable that receives the number of bytes transferred into the specified buffer. If lpNumberOfBytesRead is NULL, the parameter is ignored.

SIZE_T *lpNumberOfBytesRead = NULL;


printf("READING...buffer = %p\n", (INT)lpBaseAddress+nSize);

if (ReadProcessMemory(hProcess, lpBaseAddress, buffer, nSize, lpNumberOfBytesRead)) {

cout << "\n\nbuffer =>" << endl;


SIZE_T i = 0;

while (i < nSize) {

printf("%02X ", ((unsigned char *)buffer)[i++]);

}

}

else

PrintError(TEXT("ReadProcessMemory"));

}


void ModifyProcessMemory(

HANDLE  hProcess,

/* A pointer to the base address in the specified process to which data is written.

Before data transfer occurs, the system verifies that all data in the base address and memory of the specified size is accessible for write access, and if it is not accessible, the function fails.

*/

LPVOID  lpBaseAddress,

LPCVOID lpBuffer,

SIZE_T  nSize

)

{

SIZE_T  *lpNumberOfBytesWritten = NULL;


// lpBuffer에 사이즈만큼 삽입 

if (!WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesWritten))

PrintError(TEXT("WriteProcessMemory"));

}


LPCVOID GetBaseAddress(DWORD th32ProcessID)

{

DWORD dwFlags = TH32CS_SNAPMODULE;


// 가져온 프로세스 아이디로 핸들 값 획득 

HANDLE hModuleSnap = CreateToolhelp32Snapshot(dwFlags, th32ProcessID);


if (hModuleSnap == INVALID_HANDLE_VALUE)

{

PrintError(TEXT("CreateToolhelp32Snapshot(INVALID_HANDLE_VALUE, th32ProcessID)"));

return(FALSE);

}


MODULEENTRY32 me;

//LPMODULEENTRY32 lpme = &me;

me.dwSize = sizeof(MODULEENTRY32); // 사이즈를 구함 


if (!Module32First(hModuleSnap, &me))

{

PrintError(TEXT("Module32First"));

CloseHandle(hModuleSnap);

return(FALSE);

}


do

{

// 모듈명과 바이너리 명 비교 

if (wcscmp(me.szModule, TEXT(TARGET)) == 0)

{

CloseHandle(hModuleSnap);

return me.modBaseAddr;

}

} while (Module32Next(hModuleSnap, &me)); // 다음 모듈 반복하다가 모듈이 더이상 없을 때 종료 


CloseHandle(hModuleSnap); // 핸들 해제 

return(FALSE);

}


void PrintError(const TCHAR* msg)

{

DWORD eNum;

TCHAR sysMsg[256];

TCHAR* p;


eNum = GetLastError();

FormatMessage(

FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,

NULL,

eNum,

MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language

sysMsg, 256, NULL);


p = sysMsg;

while ((*p > 31) || (*p == 9))

++p;

do { *p-- = 0; } while ((p >= sysMsg) && ((*p == '.') || (*p < 33)));


_tprintf(TEXT("\n\tWARNING: %s failed with error %d (%s)"), msg, eNum, sysMsg);

}



TARGET

#include <stdio.h>

#include <Windows.h>


// ASLR 


void playgame()

{

int a = 1000; // health

// 특정 오프셋에서 read byte 하여서 그 read byte와 tmp배열에 read byte에 쓰일 만한거 적어두고 

// 맞으면 패스 

printf("charater's health = %d\n", a);

if (a == 0) // a의 주소를 알아내거나, 분기문을 교체하거나 

{

MessageBoxA(NULL, "Congratulations", "MODULATION", MB_OK);

}

}


int main(int argc, char*argv[])

{

int n = 0; // n의 주소를 알아내거나, case문 분기문을 교체하거나



// case 1 : n이 위치한 주소 ASLR 걸려있지만, 이제 베이스주소를 얻었기 때문에 그 만큼 빼면 된다.


// 일단 진행햇을 때 BASE Addr : 0x01010000 + 0x1000 = 0x01011000

// n이 위치한 주소 : 0x010110c6 하지만, 정확한 offset은 0x010110cc

// 그렇다면, 0x010110cc - 0x01011000 => 0xcc 만큼 차이가 남 


// case 2 : n 값을 변경하지 않고, 분기를 변경 

// 원래 opcode및 instruction

/*

010110E0   837D FC 00                   CMP DWORD PTR SS:[EBP-4],0

010110E4   74 08                        JE SHORT BYPASSTE.010110EE


여기의 74(JE를 JMP로 패치할 거임) -> JMP는 EB, JE는 74임 

정확한 주소는 010110E4 , base addr은 0x0101000 + 0X1000 => 0x01011000(RVA)

0x10110E4 - 0x01011000 = ‭변조 주소는 oep에서 0xE4‬ 만큼 뒤에 있음 


*/


/*

좀 더 제대로 해보기위해 메인함수 시작부터 쭉 읽어오다가 내가 정해둔 offset에서 멈추게 해보자.

메인함수 주소 : 0x3B10C0  (3B는 유동)     0x1e바이트 

조작할 주소 :   0x3B10DD

*/

printf("play game\n");

switch (n)

{

case 0:

while (1) {

printf("End Game\n");

Sleep(1000);

}

case 1:

printf("Oh.. you can modulation..\n");

exit(0);

system("pause");

playgame();

}

return 0;

}