일전에 Visual Studio 에서 Intel Pin 프로젝트를 설정하는 방법을 올린 적이 있습니다.
이번에는 응용차원에서 Pin 을 활용하는 방법을 올려봅니다. @_@
주 업무분야가 '게임보안' 인만큼 '게임 해킹툴 분석' 측면으로 접근해봤습니다... ^^;;
Pin 을 이용한 메모리 조작 분석
캐릭터의 공격력을 높이기 위해 '공격 데미지 값' 이라는 데이터를 조작하거나...
캐릭터가 받는 데미지를 없애기 위해 '데미지 처리 로직' 이라는 코드를 조작하거나...
게임 해킹툴이 게임 치팅을 위해 가장 많이 사용하는 방식 중 하나가 '메모리 조작' 입니다.
'메모리 조작' 은 조작하는 방식에 따라 아래와 같이 크게 두 종류로 나눌 수 있습니다.
- 게임 프로세스 외부의 다른 프로세스에서 WriteProcessMemory 로 조작.
- 게임 프로세스 내부에 DLL 을 인젝션 시켜서 직접 값을 조작.
여기서는 DLL 을 인젝션 시켜서 직접 값을 조작하는 방식을 대상으로
Pin 을 활용해보도록 하겠습니다.
테스트 편의를 위해 Sample.exe 와 SampleDll.dll 을 간단하게 구현했습니다.
[ Sample.zip 다운로드 ]
- Sample.exe : 게임 프로세스
- SampleDll.dll : 게임 프로세스에 인젝션되는 게임 해킹툴
Sample.exe 가 실행될 때 SampleDll.dll 을 로드하고...
DLL 이 로드된 후, 'F1' 키를 누르면~ SampleDll.dll 이 Sample.exe 의 메모리를 조작합니다.
Sample.exe 실행 |
'F1' 키를 눌렀을 때... 실행되는 SampleDll.dll 의 코드는 아래와 같습니다.
게임 해킹툴이 메모리 조작 시 자주 사용하는 형태입니다.
SampleDll.dll 의 메모리 조작 코드 (소스코드) |
SampleDll.dll 의 메모리 조작 코드 (디스어셈블 코드) |
Pin 을 이용해서 찾아내는 겁니다. :)
핵심 API 는 "INS_MemoryOperandIsWritten" 입니다.
메모리 쓰기가 발생하는 경우 이 함수의 리턴값이 TRUE 가 됩니다.
"MOV", "AND", "SUB" 등의 명령으로 메모리 주소에 값이 써지는 경우는 물론...
"PUSH", "CALL" 등의 명령으로 스택 메모리에 값이 써지는 경우도 포함됩니다. @_@;;;
이 함수를 잘 이용하면 DLL 에서 게임 프로세스의 메모리를 조작하는 코드를 찾을 수 있습니다.
아래는 Pin Tool 예제 코드입니다
// MemTrace.cpp #include "pin.H" #include <iostream> #include <fstream> #include <string> using namespace std; ofstream TraceLog; UINT32 SampleBaseAddr = 0; UINT32 SampleMappedSize = 0; UINT32 DllBaseAddr = 0; UINT32 DllMappedSize = 0; //------------------------------------------------------------------- INT32 Usage() { return -1; } VOID Fini(INT32 code, VOID *v) { TraceLog << endl << "#eof..." << endl; if (TraceLog.is_open()) TraceLog.close(); } VOID ImageLoad(IMG img, VOID *v) { // 메인 프로세스 : Sample.exe if (IMG_IsMainExecutable(img) == TRUE) { SampleBaseAddr = IMG_StartAddress(img); SampleMappedSize = IMG_SizeMapped(img); TraceLog << "* Process Name : " << IMG_Name(img) << endl; TraceLog << " StartAddress : " << hexstr(SampleBaseAddr) << ", MappedSize : " << hexstr(SampleMappedSize) << endl; } // SampleDll.dll if (IMG_Name(img).find("SampleDll.dll") != string::npos) { DllBaseAddr = IMG_StartAddress(img); DllMappedSize = IMG_SizeMapped(img); TraceLog << "* Sample Dll : " << IMG_Name(img) << endl; TraceLog << " StartAddress : " << hexstr(DllBaseAddr) << ", MappedSize : " << hexstr(DllMappedSize) << endl; } } VOID Log_MemWrite(VOID *ip, string &MemWrite, VOID *addr) { UINT32 target = UINT32(addr); if ((target >= SampleBaseAddr) && (target < (SampleBaseAddr + SampleMappedSize))) { TraceLog << MemWrite << endl; } } VOID Instruction(INS ins, VOID *v) { ADDRINT Address = INS_Address(ins); if ((Address >= DllBaseAddr) && (Address <= (DllBaseAddr + DllMappedSize))) { UINT32 memOperands = INS_MemoryOperandCount(ins); for (UINT32 memOp = 0; memOp < memOperands; memOp++) { if (INS_MemoryOperandIsWritten(ins, memOp)) { string MemWrite = "- eip: " + hexstr(Address) + " [WriteMem] " + INS_Disassemble(ins); INS_InsertPredicatedCall(ins, IPOINT_BEFORE, (AFUNPTR)Log_MemWrite, IARG_INST_PTR, IARG_PTR, new string(MemWrite), IARG_MEMORYOP_EA, memOp, IARG_END); } } } } //------------------------------------------------------------------- int main(int argc, char *argv[]) { PIN_InitSymbols(); if (PIN_Init(argc, argv)) return Usage(); TraceLog.open("MemTrace_Log.txt", ofstream::out); TraceLog << "### Memory Trace Log ###" << endl << endl; IMG_AddInstrumentFunction(ImageLoad, NULL); INS_AddInstrumentFunction(Instruction, NULL); PIN_AddFiniFunction(Fini, NULL); PIN_StartProgram(); return 0; }
빌드해서 생성된 MemTrace.dll 을 이용해보면~~
Sample.exe 실행 (with Pin - MemTrace.dll) |
MemTrace.dll 에서 남긴 로그 |
SampleDll.dll 에서 메모리 조작을 하는 코드가 깔끔하게(?) 로그 파일에 기록되었습니다~ :)
마치며
일반적으로 Pin 으로 프로그램을 실행시키면 그냥 실행시키는 것보다 속도가 느립니다.게다가 Instrumentation 하는 범위가 넓을 수록... 로깅 위치가 많을 수록...
엄~~~청나게 느려집니다. -_-;;;;;;
Instrumentation 범위 조절과 로깅 위치 조절만 하더라도 실행 속도를 올릴 수 있으니...
적절하게 필터링을 해서 쓰는 걸로~ :)
5년 전에 회사에서 이것저것 시도하다 한계에 부딪혔던걸...
지금 올리네요... -_-;;;;;
정말 많은 도움이 되었습니다 감사합니다 !!^^
답글삭제도움이 되었다니 뭔가 뿌듯하네요~ ^^
삭제깔끔하게 정리해서 감사합니다. 좋은 정보 고맙습니다.
답글삭제방문 감사합니다 ~
삭제이 글이 도움이 되면 좋겠네요~~ ^^
외부에서 WriteProcessMemory 를 통해 조작하는 경우에는 PIN 으로 어떤식으로 탐지할 수 있을까요 ?
답글삭제한동안 블로그를 방치상태로 두다보니 댓글이 달렸었는지도 몰랐네요;;
삭제본문에 정리한 내용은 '프로세스 내에서 메모리 내용을 변경시키는 [CPU 명령어]를 추적'하는 방법이구요..
WriteProcessMemory 를 통해 조작하는 경우는 'API 후킹' 이용하면 PIN 을 사용하지 않아도 쉽게 추적이 가능합니다.
굳이 PIN 을 이용하려고 하신다면 WriteProcessMemory 가 호출되는 걸 로깅할 수 있도록
코드를 작성하신 다음 WriteProcessMemory 를 호출하는 외부 프로세스 대상으로 PIN 을 돌려보시면 될 듯 합니다.