[ 리버싱 / 프로그래밍 / 음악 / 게임 / 그 외... ]

레이블이 reverse engineering인 게시물을 표시합니다. 모든 게시물 표시
레이블이 reverse engineering인 게시물을 표시합니다. 모든 게시물 표시


일요일, 4월 29, 2018



※ 구글 드라이브를 통해 파일 공유 중이었는데 PCHunter_free.zip 파일이 악용사례로 신고되어서
블로그의 다운로드 링크로는 다운로드 불가능한 상황입니다. ( 검토 요청 중 )
필요하신 분은 글 마지막 부분의 "PC Hunter 홈페이지" 에서 다운로드 하시면 됩니다.



4월 26일자로 PC Hunter V1.54 버전이 업데이트 되었습니다.


[ PC Hunter 다운로드 링크 ]





[ PC Hunter 홈페이지 : http://www.xuetr.com ]



화요일, 4월 11, 2017



지난번에 올린 Pin 을 이용한 메모리 조작 분석에 이어서 ~

이번에도 '게임 해킹툴 분석' 측면으로 접근해보려 합니다 :)

물론 응용하기에 따라 다른 쪽으로 이용도 가능하겠지만요.. ^^;;; 

Pin 을 이용한 비정상 함수 호출 분석


게임 내부의 함수들은 게임이 진행되는 중에 게임에서 호출하는 것이 일반적입니다.

FPS 게임에서 캐릭터가 죽는 경우를 예를 들어봅시다.

게임 내부적으로 무기 데미지 / 캐릭터 체력 / 착탄 부위 등 이것저것 계산을 해서

조건을 만족하는 경우에 게임이 Kill 함수를 호출하는 것이 정상적인 흐름입니다.


만약 이 Kill 함수가 게임이 아닌 다른 곳에서 호출되면 어떻게 될까요...? @_@

게임 진행과 상관없이 캐릭터가 죽겠죠... -_-;;;

이런 식으로 게임 해킹툴이 게임 내부 함수를 호출하는 것을 '비정상 함수 호출' 이라고 합니다.


Pin 을 이용해서 인젝션 된 DLL 이 대상 프로세스의 함수를 호출하는 것을 분석해봅시다.

테스트 샘플 구성은 '메모리 조작 분석' 과 동일합니다.

[ Sample.zip 다운로드 ]


- Sample.exe : 게임 프로세스

- SampleDll.dll : 게임 프로세스에 인젝션되는 게임 해킹툴

Sample.exe 가 실행될 때 SampleDll.dll 을 로드하고

DLL 이 로드된 후, 'F2' 키를 누르면~ SampleDll.dll 이 Sample.exe 의 함수를 호출합니다.

Sample.exe 실행

'F2' 키를 눌렀을 때... 실행되는 SampleDll.dll 의 코드는 아래와 같습니다.

SampleDll.dll 의 비정상 함수 호출 코드 (소스코드)
참고로 lpFunc 는 프로세스 베이스 주소 + 0x1000 의 값으로 고정해두었습니다.

이 코드를 디버거로 보면 아래와 같은 디스어셈블 코드가 나옵니다.

SampleDll.dll 의 비정상 함수 호출 코드 (디스어셈블 코드)
노란 박스 안의 저 "CALL" 명령으로 시작되는 코드를 Pin 으로 잡아내면 되겠죠~? :)


핵심 API 는 "INS_IsCall" 입니다.

CALL 명령이 나오면 이 API 의 리턴값이 TRUE 가 됩니다.

아래는 Pin tool 예제 코드입니다.


// CallTrace.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_Call(string &CallFunc, ADDRINT target)
{
    if ((target >= SampleBaseAddr) && 
        (target < (SampleBaseAddr + SampleMappedSize))) {
        TraceLog << CallFunc + " [ Target : " + hexstr(target) + " ]" << endl;
    }
}

VOID Trace(TRACE trace, VOID *v)
{
    for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
    {
        INS tail = BBL_InsTail(bbl);
        ADDRINT Address = INS_Address(tail);

        if ((Address >= DllBaseAddr) &&
            (Address <= (DllBaseAddr + DllMappedSize))) {

            if (INS_IsCall(tail))
            {
                string CallFunc = "- eip: " + hexstr(Address) +
                    "   [CallFunc]  " + INS_Disassemble(tail);

                INS_InsertPredicatedCall(tail, IPOINT_BEFORE, AFUNPTR(Log_Call),
                    IARG_PTR, new string(CallFunc),
                    IARG_BRANCH_TARGET_ADDR,
                    IARG_END);
            }
        }
    }
}

//-------------------------------------------------------------------
int main(int argc, char *argv[])
{
    PIN_InitSymbols();

    if (PIN_Init(argc, argv)) return Usage();

    TraceLog.open("CallTrace_Log.txt", ofstream::out);
    TraceLog << "### Call Trace Log ###" << endl << endl;

    IMG_AddInstrumentFunction(ImageLoad, NULL);
    TRACE_AddInstrumentFunction(Trace, NULL);

    PIN_AddFiniFunction(Fini, NULL);

    PIN_StartProgram();

    return 0;
}


빌드해서 생성된 CallTrace.dll 을 이용해보면~

Sample.exe 실행 (with Pin - CallTrace.dll)

CallTrace.dll 에서 남긴 로그

이번에도 역시 SampleDll.dll 에서 비정상 함수 호출을 하는 코드가 로그 파일에 제대로 기록되었네요~ :)

마치며

Pin 을 이용해서 '메모리 조작 분석' 과 '비정상 함수 호출 분석' 을 분석하는 방법을 살펴봤는데요.

기능별로 설명(?)하기 위해 MemTrace / CallTrace 로 나눴지만...

잘 조립하면 하나의 DLL 로 합칠 수도 있습니다 :)


실제 게임에 적용하기 위해서는 이것저것 고려해야 될 사항들과 장벽들이 많지만... @_@;;

Pin 을 공부하시는 분들께 조금이나마 도움이 되었으면 좋겠네요~~ :)





일요일, 3월 26, 2017



일전에 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 의 메모리 조작 코드 (소스코드)
 참고로 hModule 은 Sample.exe 프로세스의 베이스 주소입니다.
 
SampleDll.dll 의 메모리 조작 코드 (디스어셈블 코드)
우리의 목표는 SampleDll.dll 에서 Sample.exe 의 메모리를 0x90 으로 조작하는 노란 박스의 코드를...

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년 전에 회사에서 이것저것 시도하다 한계에 부딪혔던걸...

지금 올리네요... -_-;;;;;




수요일, 2월 15, 2017



Pin 3.2 버전이 올라왔군요... @_@ !!!

[ Pin - A Binary Instrumentation Tool 다운로드 페이지 ]


Pin 2.14 버전은 VS2015 를 지원하지 않고~

Pin 3.0 은 작년에 올라왔다가 어느 순간 윈도우 버전 다운로드 링크가 사라져서 난감했는데...

Pin 3.2 버전으로 다시 올라왔네요~ :)


Pin 3.x 로 넘어오면서 프로젝트 생성 시 손봐줘야 될 것들이 많아졌는데~

기록보관 차원에서 포스팅해봅니다.


시작하기 전에


준비물 : Visual Studio 2015 Community, Pin 3.2 (윈도우용)

Pin 3.2 는 다운로드 페이지에서 받은 후, 원하는 곳에 압축을 풀어두면 됩니다.


프로젝트 생성 및 Pin 설정


Win32 응용 프로그램 - DLL - 빈 프로젝트

PinTool 은 DLL 형태이기 때문에~ DLL 을 선택한 후 "빈 프로젝트" 로 프로젝트를 생성합니다.

프로젝트에 파일이 하나도 없으면 '프로젝트 속성' 페이지에 'C/C++' 항목이 안보이니...

편의상 비어있는 cpp 파일이라도 하나 만들어서 프로젝트에 추가해줍니다.


이제 '프로젝트 속성' 페이지를 열어서~ 하나하나 만져주면 됩니다.

구성속성 - 일반
'구성속성' - '일반' 페이지에서 '문자 집합'"멀티바이트 문자 집합 사용" 지정해줍니다.


다음은 'C/C++' - '일반' 페이지의 '추가 포함 디렉터리' 에 Pin 관련 Include 폴더들을 추가해줍니다.

C/C++ - 일반 - 추가 포함 디렉터리
PinTool 프로젝트 생성시 제일 귀찮은 부분이 아닐까~ 싶네요.

이번 버전에 비해 포함시켜줘야 되는 폴더들이 많아졌습니다. oTL;;;;

압축을 풀어둔 폴더가 PIN_ROOT 라는 가정하에 아래의 폴더들을 추가하면 됩니다.
( 제 PC 의 경우 PIN_ROOT=E:\Work\Source\pin-3.2-81205-msvc-windows 가 되겠죠~ )

------------------------------------------------------------------------------------------------
%PIN_ROOT%\source\include\pin
%PIN_ROOT%\source\include\pin\gen
%PIN_ROOT%\extras
%PIN_ROOT%\extras\components\include
%PIN_ROOT%\extras\crt
%PIN_ROOT%\extras\crt\include
%PIN_ROOT%\extras\crt\include\arch-x86 (64비트용은 arch-x86_64)
%PIN_ROOT%\extras\crt\include\kernel\uapi
%PIN_ROOT%\extras\crt\include\kernel\uapi\asm-x86
%PIN_ROOT%\extras\libstdc++\include
%PIN_ROOT%\extras\stlport\include
%PIN_ROOT%\extras\xed-ia32\include\xed
( 64비트용은 %PIN_ROOT%\extras\xed-intel64\include\xed )
------------------------------------------------------------------------------------------------

일단 이 정도는 최소한으로 포함이 되어야 되는 걸로 보입니다.

없어도 되겠지 싶어서 폴더를 없애보면 컴파일 에러가... -_-;;;;;


C/C++ - 전처리기
'C/C++' - '전처리기' 페이지에서 아래 값들을 추가해줍니다.

------------------------------------------------------------------------------------------------
TARGET_IA32
(64비트용은 TARGET_IA32E)
HOST_IA32
(64비트용은 HOST_IA32E)
TARGET_WINDOWS
__PIN__=1
PIN_CRT=1
__i386__
(64비트용은 __LP64__ )
------------------------------------------------------------------------------------------------


다음은 'C/C++' - '코드 생성' 페이지~

C/C++ - 코드 생성
'C++ 예외 처리 가능' 항목을 "아니요" 로~

'런타임 라이브러리' 항목을 "다중 스레드(/MT)" 로~

'보안 검사' 항목을 "보안 검사 사용 안 함(/GS-)" 으로 지정해줍니다.


C/C++ - 언어
'C/C++' - '언어' 페이지에선 '런타임 형식 정보 사용' 항목을 "아니요(/GR-)" 로 지정합니다.


그리고 'C/C++' - '명령줄' 페이지의 '추가 옵션' 부분에~

"/FIinclude/msvc_compat.h" 를 입력해줍니다.

C/C++ - 명령줄



다음은 '링커' 항목입니다.

'링커' - '일반' 페이지의 '추가 라이브러리 디렉터리' 에 Pin 관련 라이브러리 폴더를 추가해줍니다.

링커 - 일반 - 추가 라이브러리 디렉터리
PIN_ROOT 를 기준으로 아래의 폴더들을 추가해줍니다.

------------------------------------------------------------------------------------------------
[ 32비트 ]

%PIN_ROOT%\ia32\lib
%PIN_ROOT%\ia32\lib-ext
%PIN_ROOT%\ia32\runtime\pincrt
%PIN_ROOT%\extras\xed-ia32\lib


[ 64비트 ]

%PIN_ROOT%\intel64\lib
%PIN_ROOT%\intel64\lib-ext
%PIN_ROOT%\intel64\runtime\pincrt
%PIN_ROOT%\extras\xed-intel64\lib
------------------------------------------------------------------------------------------------

라이브러리 폴더를 추가한 다음~ 실제 사용할 라이브러리를 지정해야되는데요...

링커 - 입력 - 추가 종속성

'링커' - '입력' 페이지의 '추가 종속성' 항목에 아래의 라이브러리들을 추가해줍니다.

------------------------------------------------------------------------------------------------
pin.lib
pinvm.lib
xed.lib
kernel32.lib
ntdll-32.lib (64비트는 ntdll-64.lib)
c-static.lib
m-static.lib
os-apis.lib
stlport-static.lib
crtbeginS.obj
------------------------------------------------------------------------------------------------

라이브러리 추가 후엔 '모든 기본 라이브러리 무시' 항목을 "예(/NODEFAULTLIB)" 로 지정합니다.

링커 - 입력


'링커' - '고급' 페이지에도 만져줘야 될 항목들이 있습니다.

링커 - 고급

'진입점' 항목에 "Ptrace_DllMainCRTStartup%4012" 를~ (64비트는 "Ptrace_DllMainCRTStartup" 만)

'기준 주소' 항목에 "0x55000000" 을~ (64비트는 "0xC5000000") 입력하고

'이미지에 안전한 예외 처리기 포함' 항목을 "아니요(/SAFESEH:NO)" 로 지정합니다.


마지막으로 '링커' - '명령줄' 페이지의 '추가 옵션'"/export:main" 을 입력~!!

링커 - 명령줄



꽤 번거롭긴(?) 하지만  일단 여기까지하면~

Pin 코드를 작성해서 테스트 해볼 수가 있습니다. @_@

빌드 성공


다음엔 게임 해킹툴 분석 시 도움이 될만한 PinTool 을 만들어보는 걸로~~ ㅋ :)








화요일, 2월 14, 2017



안드로이드쪽 공부하면서 바이너리 분석도 같이 보고 있는데...

ELF 포맷에 ARM 타입의 바이너리가 많더군요.. @_@;;;

IDA Pro 를 장만할(?) 여력은 안되고... -_-;;;

이것저것 뒤져보다 ARM 지원하는 디스어셈블 엔진(Capstone Engine)이 있길래...

요걸로 간단한 디스어셈블러 만들어보자~~ 는 생각으로 작업을 했습니다.


디스어셈블 엔진을 사용하려는데 파일의 어느 부분이 코드영역인지 몰라서... -_-;;;;;

또 ELF 포맷 문서를 한참 들여다봤네요...

공부한 내용을 바탕으로 C++ Builder 로 뚝딱뚝딱~~ (이라 쓰고 삽질이라고 읽...;;; )


ELF 포맷 - Info View

Hex View

Disasm View


디스어셈블은 섹션 중 Execute 플래그가 있는 부분만 하도록 했는데...

ELF 포맷에 대한 이해도가 부족하다보니 요렇게 하는게 맞는건지 모르겠네요...ㅋ ^^;;;

좀 더 공부를 해야겠어요... @_@;;...



[ Simple ELF View 다운로드 ]






수요일, 12월 07, 2016



며칠 전입니다...

익명의 어느 분께서 x64dbg 로 crackme 실행파일을 열면 Crash 가 발생한다며...

혹시 그 이유를 아는지 블로그 댓글로 질문을 하셨습니다.

( 참고 - http://www.xeronichs.com/2016/11/attach-helper-x64dbg-plugin.html )

같은 OS 환경인데도 제 PC 에서는 별다른 문제가 없었기에...

플러그인 문제나 다른 외부 프로그램과 충돌이 아닐까~ 추측만 하고 넘어갔었습니다...


이번에 다른 분께서 동일한 문제가 발생한다며 댓글을 남기셨고...

그 분 나름대로 문제가 발생하는 부분까지 찾아내셨습니다... @_@ ;;;

11월 5일 2번째 릴리즈(snapshot_2016-11-05_19-55.zip) 이후로  언어를 '한글' 로 설정하면...

실행파일을 불러올 때 Crash 가 발생한다고 하시더군요.

저는 x64dbg 언어 설정을 처음부터 "영어" 로 맞춰두고 사용을 하다보니 문제가 발생하지 않았던거구요.. ^^;;

확인 차 언어 설정을 '한글' 로 잠깐 바꿔봤더니 Crash 가 발생하더군요... oTL;;;

언어 설정 "한글" 로 하면 파일을 열 때 Crash 발생

조금 더 정확하게 설명하면 x64dbg -> Settings 에서 Events 탭에...

"Entry Breakpoint*"  가 체크가 되어있을 때 Crash 가 발생합니다...

"System Breakpoint*" 를 체크하고 "Entry Breakpoint*" 를 체크하지 않으면 실행이 잘 되더군요..;;

한글 언어 적용 후, "Entry Breakpoint*"  체크 시 Crash 발생


언어 설정을 '영어' 로 하면 문제가 없고, '한글' 로 하면 Crash 발생하는 건...

언어 파일 자체에 뭔가 문제가 있다고 볼 수 있겠죠...

실제로 Crash 문제가 발생하기 시작한 11월 5일 두번째 릴리즈의 한글 언어 파일과...

문제가 발생하지 않는 한글 언어 파일을 비교하면 파일 크기 차이가 꽤 납니다... ;;;

왼쪽은 Crash 발생하지 않는 버전, 오른쪽은 Crash 발생하는 버전

한글 언어 파일에 내용이 추가되면서 뭔가 문제가 생긴 것 같아서 그 부분을 분석해봤습니다.


디버거 UI 에 언어 파일을 적용해주는 함수

위의 함수에서 "GuiTranslateText" 함수를 이용해 현재 적용된 언어 파일의 내용을 구해오도록 되어 있더군요.


Crash 발생하는 원본 문자열

분석한 결과 "%s breakpoint "%s" at %s (%p)!" 라는 문자열을 한글로 출력하려고 할 때...

Crash 가 발생했습니다.

포맷스트링 %s, %s, %s, %p 가 사용되고 이에 대한 실제 값은 ESI(0xA19DCE8)에 있습니다.

포맷스트링 실제 값

첫번째 %s = "INT3"
두번째 %s = "entry breakpoint"
세번째 %s = "<asmjit.EntryPoint>"
네번째 %p = 0x5801EA22

언어 설정이 '영어' 일 때 아래와 같은 문자열이 완성됩니다.

INT3 breakpoint "entry breakpoint" at <asmjit.EntryPoint> (5801EA22)!


언어 설정이 '한글' 인 경우에는...

"%s breakpoint "%s" at %s (%p)!" 문자열이 아래와 같이 변경됩니다.

한글 적용 시 변경된 문자열

한글로는 "%s (%p) 에 %s 중단점 "%s"!" 라는 내용이 됩니다. ( oTL;;;; )


원래 포맷스트링의 순서는 %s, %s, %s, %p 인데...

한글 언어 파일의 내용에는 %s, %p, %s, %s 로 되어있네요.. @_@..!?!?

이 내용이 "_vsnprintf_s" 함수로 들어가면~

마지막 %s 가 엉뚱한 곳에서 문자열을 가져오려고 하다가 실패하고 Crash 가 딱~!!;;

엉뚱한 곳에서 문자열을 찾으려고 함


이 부분이 정상적으로 동작하려면 한글 언어 파일의 포맷스트링도 %s, %s, %s, %p 의 순서로 되어야 됩니다.


일단 제가 확인한 부분은 이 부분인데 다른 부분도 포맷스트링 순서가 일치하지 않을 경우...

얼마든지 Crash 가 발생할 수 있습니다. @_@;;;

한글 언어 파일 만드신 분이 번역을 조금 더 자연스럽게 하려고 하다가...

포맷스트링 부분은 미처 확인 못해서 이런 문제가 발생한게 아닌가 싶네요...

얼른 문제가 수정될 수 있길...





수요일, 11월 09, 2016





예~전에 OllyDBG 용으로 만들었던 플러그인(http://iam-hs.com/207)을

x64dbg 용으로도 만들어 봤습니다.


디버거로 프로세스에 Attach 할 때...

DbgBreakPoint, DbgUiRemoteBreakin 이 조작된 경우 정상적으로 Attach 가 안될 수도 있는데요..

해당 API 들을 원래대로 복구시켜서 Attach 가능하게 해주는 플러그인입니다~ :)


dp32 파일은 32비트용 디버거 플러그인 폴더에,

dp64 파일은 64비트용 디버거 플러그인 폴더에 넣어주면 됩니다.


[ AttachHelper 다운로드 ]

[ GitHub - https://github.com/XeroNicHS/x64dbg_AttachHelper ]



혹시라도 플러그인 로드에 문제가 있는 분은 재배포 가능 패키지를 설치해보시기 바랍니다.

[ Visual Studio 2015 재배포 가능 패키지 ]





금요일, 11월 04, 2016



스카이림 스페셜 에디션

개인적으로 자유도가 높은 게임을 선호하다보니 '스카이림' 에도 푹 빠졌었는데요...

이 게임이 공식적으로 '한글' 을 지원하지 않다보니 국내 유저들이 제작한 '한글화 모드' 를 설치해서...

게임을 즐기곤 했습니다 :)

저 같은 영어 Looser 에게 '한글화 모드' 는 없어서는 안될 필수 모드랄까요..? ^^;;;

( 모드 만세.... ioi )


10월 28일 !! '스카이림 스페셜 에디션' 이 출시가 됐습니다. @_@ !!!

64비트 OS 를 지원하고~ 그래픽도 더 좋아지고~ 기존 버전의 세이브 파일 호환도 되고~

....그런데 모드 적용시 '도전 과제' 를 달성할 수 없다는 희안한 기능이 추가가 되었더군요 oTL;;;

모드 적용하지 않은 상태의 'New Game' 메시지
모드 적용한 상태의 'New Game' 메시지

'도전 과제' 달성 상태를 보면서 뿌듯함을 얻는 스타일인데 '한글화 모드' 적용하면 그게 안된다니... oTL;;;


직업 정신(?)이 스믈스믈 발동하면서... 호기심이 생겨 (*-_-*) 디버거를 붙여서 저 부분을 한 번 살펴봤습니다...;;;

모드 적용 시 도전 과제 달성 불가 메시지
먼저 화면에 뿌려주는 메시지를 찾아봤더니... 떡하니 2개가 보이더군요. @_@

위의 메시지는 게임을 로드할 때 뿌려주는 메시지고...

아래의 메시지는 새 게임을 시작할 때 뿌려주는 메시지입니다.


일단은 새 게임을 시작할 때의 메시지부터~~ :)

모드 적용한 상태의 'New Game' 메시지 출력 코드
해당 코드를 보니 AL 값이 0 이면 다른 곳(skyrimse.7FF63AF93498)으로 점프하고...

아니면 모드 적용시 도전 과제를 달성할 수 없다는 메시지를 보여주도록 되어있더군요.

혹시나 싶어 다른 곳(skyrimse.7FF63AF93498)의 코드도 봤습니다.

모드 적용하지 않은 상태의 'New Game' 메시지 출력 코드
어라...!? @_@... AL 값이 0 일 때 점프하는 곳에

모드를 적용하지 않은 상태의 'New Game' 메시지를 출력하는 코드가 있었습니다.


AL 값이 뭐길래... !? =_=;;;


AL 값을 확인하는 코드 조~금 위에 CALL 명령이 있습니다. (skyrimse.7FF63A489019 호출)


일반적으로 함수 실행이 끝날 때... 리턴값이 EAX 에 담기게 되는데요.

여기선 skyrimse.7FF63A489019 의 리턴값이 0 이면 '모드 적용하지 않은 상태'로 처리하고...

0 이 아니면 '모드 적용한 상태'로 처리하고 있군요... @_@


즉, skyrimse.7FF63A489019 쪽에서 뭔가~ 모드 적용 여부를 검사한다고 볼 수 있겠죠~? :)

skyrimse.7FF63A489019
skyrimse.7FF63A6C1800
일단... skyrimse.7FF63A6C1800 이 함수 안에서 모드 적용 여부를 검사하는 것으로 강하게 의심됩니다 :)

; skyrimse.7FF63A6C1800
00007FF63A6C1800      | PUSH    RDI                                                  |
00007FF63A6C1802      | PUSH    R14                                                  |
00007FF63A6C1804      | SUB     RSP, 28                                              |
00007FF63A6C1808      | XOR     DIL, DIL                                             |
00007FF63A6C180B      | MOV     R14, RCX                                             |
00007FF63A6C180E      | TEST    DL, DL                                               |
00007FF63A6C1810      | JE      skyrimse.7FF63A6C1836                                |
00007FF63A6C1812      | MOV     RAX, QWORD PTR DS:[7FF63DDB4CB8]                     |
00007FF63A6C1819      | MOVZX   EDI, BYTE PTR DS:[RAX+BD5]                           |
00007FF63A6C1820      | SHR     DIL, 3                                               |
00007FF63A6C1824      | AND     DIL, 1                                               |
00007FF63A6C1828      | JE      skyrimse.7FF63A6C1836                                |
00007FF63A6C182A      | MOVZX   EAX, DIL                                             |
00007FF63A6C182E      | ADD     RSP, 28                                              |
00007FF63A6C1832      | POP     R14                                                  |
00007FF63A6C1834      | POP     RDI                                                  |
00007FF63A6C1835      | RET                                                          |
00007FF63A6C1836      | MOV     QWORD PTR SS:[RSP+40], RBX                           |
00007FF63A6C183B      | MOV     QWORD PTR SS:[RSP+48], RBP                           |
00007FF63A6C1840      | MOV     EBP, DWORD PTR DS:[RCX+D70]                          |
00007FF63A6C1846      | MOV     QWORD PTR SS:[RSP+20], R15                           |
00007FF63A6C184B      | MOV     R15D, 1                                              |
00007FF63A6C1851      | MOV     EBX, R15D                                            |
00007FF63A6C1854      | CMP     EBP, EBX                                             |
00007FF63A6C1856      | JBE     skyrimse.7FF63A6C18C6                                |
00007FF63A6C1858      | MOV     QWORD PTR SS:[RSP+50], RSI                           |
00007FF63A6C185D      | NOP     DWORD PTR DS:[RAX]                                   |
00007FF63A6C1860      | TEST    DIL, DIL                                             |
00007FF63A6C1863      | JNE     skyrimse.7FF63A6C18A6                                |
00007FF63A6C1865      | TEST    EBX, EBX                                             |
00007FF63A6C1867      | JS      skyrimse.7FF63A6C18A0                                |
00007FF63A6C1869      | CMP     EBX, FE                                              |
00007FF63A6C186F      | JA      skyrimse.7FF63A6C18A0                                |
00007FF63A6C1871      | MOVSXD  RAX, EBX                                             |
00007FF63A6C1874      | MOV     RSI, QWORD PTR DS:[R14+RAX*8+D78]                    |
00007FF63A6C187C      | TEST    RSI, RSI                                             |
00007FF63A6C187F      | JE      skyrimse.7FF63A6C18A0                                |
00007FF63A6C1881      | MOV     RCX, RSI                                             |
00007FF63A6C1884      | CALL    skyrimse.7FF63A46639D                                |
00007FF63A6C1889      | CMP     AL, FF                                               |
00007FF63A6C188B      | JE      skyrimse.7FF63A6C18A0                                |
00007FF63A6C188D      | LEA     RCX, QWORD PTR DS:[RSI+58]                           |
00007FF63A6C1891      | CALL    skyrimse.7FF63A4BCFF9                                |
00007FF63A6C1896      | TEST    AL, AL                                               |
00007FF63A6C1898      | MOVZX   EDI, DIL                                             |
00007FF63A6C189C      | CMOVE   EDI, R15D                                            |
00007FF63A6C18A0      | INC     EBX                                                  |
00007FF63A6C18A2      | CMP     EBX, EBP                                             |
00007FF63A6C18A4      | JB      skyrimse.7FF63A6C1860                                |
00007FF63A6C18A6      | MOV     RSI, QWORD PTR SS:[RSP+50]                           |
00007FF63A6C18AB      | MOVZX   EAX, DIL                                             |
00007FF63A6C18AF      | MOV     RBP, QWORD PTR SS:[RSP+48]                           |
00007FF63A6C18B4      | MOV     RBX, QWORD PTR SS:[RSP+40]                           |
00007FF63A6C18B9      | MOV     R15, QWORD PTR SS:[RSP+20]                           |
00007FF63A6C18BE      | ADD     RSP, 28                                              |
00007FF63A6C18C2      | POP     R14                                                  |
00007FF63A6C18C4      | POP     RDI                                                  |
00007FF63A6C18C5      | RET                                                          |
00007FF63A6C18C6      | MOVZX   EAX, DIL                                             |
00007FF63A6C18CA      | JMP     skyrimse.7FF63A6C18AF                                |
00007FF63A6C18CC      | INT3                                                         |
00007FF63A6C18CD      | INT3                                                         |
00007FF63A6C18CE      | INT3                                                         |
00007FF63A6C18CF      | INT3                                                         |


확인 결과...

7FF63A6C1840 코드에서 현재 로드된 모드의 개수를 가져오고... (EBP)

7FF63A6C188D 코드에서 현재 로드된 모드 파일의 이름을 가져와서... (RCX)

모드의 개수 만큼 루프를 돌면서 7FF63A6C1891 코드에서 파일 이름을 비교하고 있었습니다.

모드 파일 이름 비교 대상
파일 이름이 Dawnguard.esm, HearthFires.esm, Dragonborn.esm, Update.esm 인 경우에 한해서는...

도전 과제 달성이 가능하도록 허용을 해주고 있더군요...;;; 그 외에는 불가능~ oTL;;;


어쨌든, skyrimse.7FF63A6C1800 이 함수가 모드를 검사하는 함수라는 것과

검사 방식은 모드의 파일 이름 비교라는 걸 알아냈습니다 :)

이 모드 검사를 우회할 방법이야 여러가지지만...

그냥 간단하게(?) 함수 호출 시 무조건 0 을 리턴하도록 코드를 조작해봤습니다.

skyrimse.7FF63A6C1800 코드 조작

이렇게 조작해둔 상태에서 게임을 진행해보니...

'한글화 모드' 를 적용한 상태에서도 '도전 과제' 가 제대로 달성이 되더군요... ( 오예!! =_=v )

모드 적용 한 상태에서 '도전 과제' 달성

게임 실행할 때 저 함수 부분을 자동으로 조작해주는 툴을 만들면 조금 더 편하겠네요~ :)



ps... 어우.. 64비트 어셈블리 코드를 보니 머리가 어질어질하네요...






토요일, 10월 22, 2016



이전 블로그에 해당 주제를 정리해 둔 포스팅(http://iam-hs.com/146)이 있지만~

내용을 조금 업데이트해서 새 블로그에도 올려봅니다. ^^;;;


VirtualBox 에서 '직렬 포트' 설정

먼저 VirtualBox 에서 Guest OS 의 '직렬포트' 를 설정합니다.


Guest OS '직렬포트' 설정

'직렬 포트 사용하기' 체크 박스를 클릭해서 활성화 시키면 하위 항목들에 대한 추가 설정이 가능해집니다.

'포트 번호' 는 그대로 "COM1" 로 두고, '포트 모드' 를 "호스트 파이프" 로 변경하고...

'존재하는 파이프/소켓에 연결' 항목을 클릭해서 체크 표시를 없애줍니다. (꼭!!!)

'경로/주소' 는 "\\.\pipe\[원하는이름]" 의 형태로 설정합니다.


참고로  '존재하는 파이프/소켓에 연결' 항목이 체크되어 있으면... 

Guest OS 구동 시 아래와 같은 오류가 발생합니다. -_-;;;


'존재하는 파이프/소켓에 연결' 에 체크가 되어있으면 오류 발생

Guest OS 의 부팅 옵션 설정

VirtualBox 에서 '직렬 포트' 설정을 마친 후엔... 

실제 Guest OS 안에서 커널 디버깅이 가능하도록 부팅 옵션을 변경해줘야 합니다.

WinXP 까지는 boot.ini 파일을 편집해서 설정이 쉬웠지만, 

비스타 부터는 "BCDEDIT" 를 이용해야 되기에 다소 귀찮아졌습니다. oTL;;;


관리자 권한으로 '명령 프롬프트' 를 실행해서 "BCDEDIT" 를 이용해봅시다~~ :)

부팅 로더 추가 후 디버그 모드 활성화

"BCDEDIT" 를 실행하면 기본적으로 'Windows 부팅 로더' 에 {current} 하나가 있을 겁니다.

이 {current} 로더를 이용해도 되지만, 저는 커널 디버깅용 로더를 따로 구성하는 방식으로 진행했습니다.


BCDEDIT /copy {current} /d "부팅시 화면에 보일 설명"
- "/copy" 옵션으로 {current} 로더와 동일한 로더를 하나 추가합니다.
- "/d" 옵션으로 부팅 시 화면에 보일 내용을 설정합니다.

※ 저는 기존의 description ("Windows 8.1") 뒤에 "[DEBUG]" 를 추가했습니다.
BCDEDIT /set {복사한 로더} debug on
- "/set" 옵션으로 복사한 로더에 디버깅이 가능하도록 설정합니다.

디버그 환경 설정

BCDEDIT /dbgsettings SERIAL DEBUGPORT:1 BAUDRATE:115200
- "/dbgsettings" 옵션으로 디버그 환경을 설정합니다.

※ "직렬 포트 : COM1" 을  이용하기 위해 SERIAL DEBUGPORT:1 로 설정합니다.

Guest OS 의 부팅 옵션 설정이 정상적으로 되었다면 재부팅 시 멀티 부팅 화면이 뜰 겁니다.

'기본 부팅'과 '디버그 가능 부팅'

WinDbg 에서 커널 디버깅 설정

마지막으로 WinDbg 에서 커널 디버깅을 설정합니다.

"File -> Kernel Debug..." 메뉴를 실행 후 'COM" 탭으로 변경~

WinDbg 의 커널 디버깅 설정

'Baud Rate' 항목은 Guest OS 에서 설정한 값인 "115200" 로 지정을 하고...

'Pipe' 항목에 체크...!!!

'Port' 항목엔 VirtualBox 직렬 포트 설정에서 입력한 경로를 그대로 입력합니다.


'확인'을 누르면...

OS 연결 대기 상태

디버깅할 대상을 기다리는 대기 상태가 됩니다.

이 상태에서 Guest OS 의 '디버그 가능 부팅' 을 선택하면...

OS 인식

Guest OS 에 연결되었다는 메시지가 뜨면서 커널 디버깅이 가능해집니다 ~ :)





카테고리

가장 많이 본 글

통계

Copyright © XeroNic(HS) BLOG | Powered by Blogger
Design by WP Lift | Blogger Template by NewBloggerThemes.com