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

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


목요일, 8월 10, 2017



업무 특성상(?) Hex 값을 다루는 경우가 많은데...

이번에 파이썬용 Hex Dump 함수를 간단하게 만들어 봤습니다.

앞으로 파이썬으로 작업하다가 Hex 값이 필요할 땐 이 함수를 우려먹는 걸로... :)


def print_hex_dump(buffer, start_offset=0):
    print('-' * 79)

    offset = 0
    while offset < len(buffer):
        # Offset
        print(' %08X : ' % (offset + start_offset), end='')

        if ((len(buffer) - offset) < 0x10) is True:
            data = buffer[offset:]
        else:
            data = buffer[offset:offset + 0x10]

        # Hex Dump
        for hex_dump in data:
            print("%02X" % hex_dump, end=' ')

        if ((len(buffer) - offset) < 0x10) is True:
            print(' ' * (3 * (0x10 - len(data))), end='')

        print('  ', end='')

        # Ascii
        for ascii_dump in data:
            if ((ascii_dump >= 0x20) is True) and ((ascii_dump <= 0x7E) is True):
                print(chr(ascii_dump), end='')
            else:
                print('.', end='')

        offset = offset + len(data)
        print('')

    print('-' * 79)


사용법은 간단합니다~ :)

print_hex_dump(mft_buf, mft_offset)


요런 식으로 내용을 보고 싶은 버퍼와 시작 오프셋 값을 지정해주면~



요렇게 딱~~

시작 오프셋을 지정하지 않으면 기본적으로 '0' 으로 됩니다.

별거아닌데 쓸데없이 만족스럽네요.. :)




화요일, 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 다운로드 ]






일요일, 2월 05, 2017



안드로이드 앱을 만들어 보고 싶어서 '안드로이드 스튜디오 (2.2.3)'를 설치하고~

버벅대며 이것저것 만져봤습니다.


버튼 위젯을 하나 올려놓고 테스트를 하던 중...

버튼 텍스트를 영어로 할 땐 문제가 없는데, 한글로 할 떄 글자가 깨지는 현상이 발생하더군요.. @_@;

한글이 안보인다.. oTL


구글을 통해 찾아보니 기본 한글 폰트 설정이 잘못되어 있어서 폰트를 읽어오지 못하기 때문에...

글자가 깨진다고 하더군요.


안드로이드 스튜디오 레이아웃 폰트 관련 폴더

fonts.xml 파일의 내용


안드로이드 스튜디오가 설치된 폴더의 "plugins\android\lib\layoutlib\data\fonts" 폴더의

fonts.xml 에 레이아웃 디자인 시 사용되는 폰트들이 설정되어 있는데...

한글인 경우 "NotoSansCJK-Regular.ttc" 폰트로 되어 있습니다.

그런데 이 폴더에는 "NotoSansCJK-Regular.ttc" 폰트가 없더군요... =_=;;;; ??

한글을 처리할 수 있는 폰트가 없어서 글자가 깨지는거죠... oTL;;

그럼 한글을 처리할 수 있는 폰트를 지정해주면 문제가 해결이 되겠죠~? :)



가장 간단한(?) 방법은 기본 지정된 폰트를 다른 걸로 바꿔주는 겁니다.

"나눔고딕" 폰트를 사용하기


다행히도 레이아웃 폰트 관련 폴더에 "나눔고딕(NanumGothic.ttf)" 폰트가 이미 포함되어 있습니다.

fonts.xml 파일에서

<family lang="ko">
    <font weight="400" style="normal" index="1">NotoSansCJK-Regular.ttc</font>
</family>
이렇게 되어있는 부분을

<family lang="ko">
    <font weight="400" style="normal">NanumGothic.ttf</font>
</family>
이렇게 바꿔주면 되는거죠~ :)

일단 요렇게만 해도 레이아웃 화면에서 한글은 잘 나오더군요...



다른 방법으로는 "NotoSansCJK-Regular.ttc" 폰트를 추가해주는 방법이 있습니다.

"NotoSansCJK-Regular.ttc" 폰트를 추가하기


"NotoSansCJK" 폰트는 구글의 NotoSansCJK 페이지 에서 구할 수 있습니다.

[ NotoSansCJK-Regular 폰트 다운로드 ]


ZIP 파일 압축을 풀고 "NotoSansCJK-Regular.ttc" 파일을 레이아웃 폰트 폴더에 복사를 한 다음..

레이아웃 폰트 폴더의 "fontsInSdk.txt" 파일을 열어서 내용을 추가해줍니다.

fontsInSdk.txt 파일에 "NotoSansCJK-Regular.ttc" 추가

폰트만 복사하고 "fontsInSdk.txt" 파일에 내용 추가를 하지 않으면 인식이 되지 않으니...

꼭~ 추가해 주셔야 됩니다 @_@;;


두 방법 중 입맛에 맞는 방법으로 해결을 하시면 되겠습니다. ^^:;;

한글이 잘보인다.. @_@





월요일, 1월 30, 2017



리버싱을 하다보면 필요에 따라 자신만의 툴을 만드는 경우가 종종 있습니다.

툴을 만들다보면 윈도우 API 를 사용해야 할 때가 있는데...

파이썬에서 ctypes 로 윈도우 API 를 사용하는 방법을 정리해봤습니다.


우선은 ctypes 와 ctypes.wintypes(자료형 모음) 를 임포트 해줍니다.

from ctypes import *
from ctypes.wintypes import *


윈도우 API  는 대체로 windll 을 이용하면 되는데요~

아래처럼 모듈 이름과 함수 이름을 적어주면 됩니다.

# Type 1
GetModuleFileName = windll.kernel32.GetModuleFileNameW
GetModuleHandle = windll.kernel32.GetModuleHandleW

# Type 2
Kernel32 = windll.kernel32

GetModuleFileName = Kernel32.GetModuleFileNameW
GetModuleHandle = Kernel32.GetModuleHandleW


입력하기 쉽게 "GetModuleFileName", "GetModuleHandle" 로 했을 뿐...

windll.kernel32.GetModuleFileNameW / Kernel32.GetModuleFileNameW 이나

windll.kernel32.GetModuleHandleW / Kernel32.GetModuleHandleW 를 그대로 사용해도 됩니다.

사용하려는 API 가 많을 경우 Type 2 처럼 사용하는게 더 편하지 않을까 생각되네요 ^^;;;


API 사용 예 #1 - GetModuleHandleW

from ctypes import *
from ctypes.wintypes import *

Kernel32 = windll.kernel32

print("[*] GetModuleHandleW [*]")
Kernel32_BaseAddr = Kernel32.GetModuleHandleW("KERNEL32.DLL")
print("    - KERNEL32.DLL = 0x%X" % Kernel32_BaseAddr)

GetModuleHandleW 호출 결과

가장 단순한 형태의 사용 예입니다. 그냥 인자를 넣어주기만 하면 되는거죠~ :)

참고로 64비트 파이썬에서는 GetModuleHandleW 를 호출하기 전에...

restype 을 직접 지정해줘야 주소값을 제대로 가져옵니다.

Kernel32.GetModuleHandleW.restype = c_void_p
Kernel32_BaseAddr = Kernel32.GetModuleHandleW("KERNEL32.DLL")


API 사용 예 #2 - GetModuleFileNameW

from ctypes import *
from ctypes.wintypes import *

Kernel32 = windll.kernel32

path = create_unicode_buffer(MAX_PATH)

print("[*] GetModuleFileNameW [*]")
Kernel32.GetModuleFileNameW(0, path, MAX_PATH)
print("    - Path = %s" % path.value)

GetModuleFileNameW 호출 결과

윈도우 API 중에는 GetModuleFileName 처럼 데이터를 담을 버퍼를 인자로 받아서

그 버퍼에 데이터를 넘겨주는 방식도 있습니다.


create_string_buffer, create_unicode_buffer 로 데이터를 담을 수 있는 객체를 만들 수 있는데요...

create_string_buffer 는 C 언어의 "char *", 파이썬의 "bytes" 와 대응되며,

create_unicode_buffer 는 C 언어의 "wchar *", 파이썬의 "str" 과 대응됩니다.

~A 계열 함수를 사용할 때는 create_string_buffer 를 사용하고,

~W 계열 함수를 사용할 때는 create_unicode_buffer 를 사용하면 됩니다.

객체에 담겨진 실제 데이터는 value 를 통해 얻을 수 있습니다.

create_string_buffer / create_unicode_buffer


API 사용 예 #3 - CreateProcessW

from ctypes import *
from ctypes.wintypes import *


class PROCESS_INFORMATION(Structure):
    _fields_ = [("hProcess", HANDLE),
                ("hThread", HANDLE),
                ("dwProcessId", DWORD),
                ("dwThreadId", DWORD)]


class STARTUPINFO(Structure):
    _fields_ = [('cb', DWORD),
                ('lpReserved', LPWSTR),
                ('lpDesktop', LPWSTR),
                ('lpTitle', LPWSTR),
                ('dwX', DWORD),
                ('dwY', DWORD),
                ('dwXSize', DWORD),
                ('dwYSize', DWORD),
                ('dwXCountChars', DWORD),
                ('dwYCountChars', DWORD),
                ('dwFillAttribute', DWORD),
                ('dwFlags', DWORD),
                ('wShowWindow', WORD),
                ('cbReserved2', WORD),
                ('lpReserved2', LPBYTE),
                ('hStdInput', HANDLE),
                ('hStdOutput', HANDLE),
                ('hStdError', HANDLE)]


Kernel32 = windll.kernel32

startupinfo = STARTUPINFO()
processinfo = PROCESS_INFORMATION()

print("[*] CreateProcessW [*]")
Kernel32.CreateProcessW("C:\\Windows\\NOTEPAD.exe", None, None, None, 0, 0,
                        None, None, byref(startupinfo), byref(processinfo))
print("    - hProcess = %X" % processinfo.hProcess)
print("    - dwProcessId = %d (%X)" % (processinfo.dwProcessId, processinfo.dwProcessId))

CreateProcessW 호출 결과

앞의 두 예제와 비교하면 코드의 양이 꽤 깁니다...;;; ( 이게 다 구조체 때문임.. =_=;;;; )


파이썬은 C 언어의 '구조체' 를 그대로 사용할 수 없기 때문에...

'구조체' 를 인자로 받는 API 를 사용하기 위해서는 추가 작업이 필요합니다.

ctypes 의 "Structure" 클래스를 상속받아서 임의의 클래스를 만든 다음...

"_fields_" 에 구조체 멤버들을 추가해주면 됩니다.

함수 인자에 참조 연산자('&')를 사용하는 경우가 있는데 파이썬은 "byref" 를 이용하면 됩니다.



이상의 세가지 형태의 API 사용 방법을 숙지하고 있으면...

대부분의 윈도우 API 는 문제없이 사용할 수 있을거라 생각합니다. @_@;;;





화요일, 11월 15, 2016



얼마 전, 네이버 블로그 첨부파일을 다운로드 받는 파이썬 코드를 작성했는데요...

자매품으로(?) 티스토리 첨부파일을 받는 코드도 만들어봤습니다. ^^;;;;

Win95/98 용 고전 게임들 찾다보면 첨부파일이 어마어마하게 많은 경우가 있는데...

하나하나 클릭하기 귀찮아서 코딩까지 하게 되었네요~ ^^;;;;


네이버 블로그는 첨부파일 정보가 있는 페이지까지 접근하기가 좀 귀찮을뿐(?)

그 페이지까지 접근만 하면 첨부파일 다운로드는 비교적 쉽게(?) 해결이 됩니다.

"aPostFiles" 라는 자바스크립트 배열이 있고 그 안에 모든 첨부파일의 정보(링크, 파일이름, 크기 등...)가

가지런히 담겨져 있기에 해당 배열의 내용만 잘 가져오면 되거든요 :)


티스토리는 처음에 접근하는 페이지에 첨부파일 정보가 바로 있어서~

첨부파일 정보를 확인하는 건 어렵지 않습니다.

그런데 네이버 블로그처럼 첨부파일 정보가 한 곳에 몰려있는게 아니라...

<a href="첨부파일 링크"> .... 파일이름</a>
형태로 포스팅 내용 전역에 걸쳐서 포함될 수 있기에 이걸 처리하는게 좀 귀찮습니다. -_-;;;

( 전 그냥 페이지 전체 소스에서 '첨부파일 링크' 를 포함한 태그를 모조리 찾는 방법으로 해결을.. -_-;;;; )


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

# GMF [File Downloader] for Tistory Blog

import re
import sys
from http import client
from urllib import request


def print_logo():
    print("#------------------------------------------#")
    print("# [GMF] Give Me a File!! [File Downloader] #")
    print("#------------------------------------------#")
    print("# for Tistory Blog\n")


def get_url_source(url):
    try:
        f = request.urlopen(url)
        url_info = f.info()
        url_charset = client.HTTPMessage.get_charsets(url_info)[0]
        url_source = f.read().decode(url_charset)

        return url_source

    except Exception as e:
        print("[-] Error : %s" % e)
        sys.exit(-1)


def main():
    print_logo()

    if len(sys.argv) != 2:
        print("[*] Usage : gmf_ti.py [Tistory Blog URL]")
    else:
        url = sys.argv[1]
        print("[*] Target URL : %s\n" % url)
        url_source = get_url_source(url)

        # find 's1.daumcdn.net/cfs.tistory'
        if url_source.find("t1.daumcdn.net/tistory") == -1:
            print("[-] It is not a Tistory Blog")
            sys.exit(0)

        try:
            # find all 'attach file link'
            p_attach = re.compile(r"href=[\'\"](\S+?/attachment/.*?)[\'\"]\s*.*?/> (.*?)</", re.IGNORECASE | re.DOTALL)
            result = p_attach.findall(url_source)

            if result:
                for each_file in result:
                    file_url = each_file[0]
                    if each_file[1] == "":
                        file_name = file_url[file_url.rfind('/') + 1:]
                    else:
                        file_name = each_file[1]
                    print("* File : %s" % file_name)
                    print("  Link : %s" % file_url)
                    request.urlretrieve(file_url, file_name)
                    print("  ==> Done")
            else:
                print("[-] Attached File not found !!")

        except Exception as e:
            print("[-] Error : %s" % e)
            sys.exit(-1)

if __name__ == "__main__":
    sys.exit(main())

gmf_ti.py 실행

티스토리 첨부파일 태그의 모든 형태를 다 확인한게 아니라...

경우에 따라서는 정보를 못가져올 수도 있습니다... @_@;;;;




일요일, 11월 13, 2016



첨부파일이 2~3개 정도면 별 생각없이 클릭~ 클릭하면서 다운받지만...

7~8개가 넘어가면 귀차니즘 지수가 상승하면서 고민을 하게 됩니다.

' 받을까...? 말까...? '

기분이 좋은 날은 받을 수도 있고... 아닌 날은 그냥 넘어갈 수도 있겠죠... -_-;;;

받아야 될 첨부파일이 20개 이상이면 솔직히(?) 받기 싫어집니다.

네이버 블로그 첨부파일


정말 필요한 것일까...? 다시 한번 고민을 하게 되죠...


며칠 전에 총 50개가 넘는 파일을 일일이 클릭해서 받자니 미칠 것 같아서...

고민 좀 하다가 파이썬으로 뚝딱뚝딱 만들어봤습니다.

일단은 '네이버 블로그' 전용으로~ ^^;;;;


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

# GMF [File Downloader] for NAVER Blog

import re
import sys
import json
from http import client
from urllib import request


def print_logo():
    print("#------------------------------------------#")
    print("# [GMF] Give Me a File!! [File Downloader] #")
    print("#------------------------------------------#")
    print("# for NAVER Blog\n")


def get_url_source(url):
    try:
        while url.find("PostView.nhn") == -1 and url.find("PostList.nhn") == -1:
            f = request.urlopen(url)
            url_info = f.info()
            url_charset = client.HTTPMessage.get_charsets(url_info)[0]
            url_source = f.read().decode(url_charset)

            # find 'NBlogWlwLayout.nhn'
            if url_source.find("NBlogWlwLayout.nhn") == -1:
                print("\n[-] It is not a NAVER Blog")
                sys.exit(0)

            # get frame src
            p_frame = re.compile(r"\s*.*?<iframe.*?mainFrame.*?(.*)hiddenFrame", re.IGNORECASE | re.DOTALL)
            p_src_url = re.compile(r"\s*.*?src=[\'\"](.+?)[\'\"]", re.IGNORECASE | re.DOTALL)
            src_url = p_src_url.match(p_frame.match(url_source).group(1)).group(1)
            url = src_url

        if url.find("http://blog.naver.com") == -1:
            last_url = "http://blog.naver.com" + url
        else:
            last_url = url

        print("   => Last URL : %s\n" % last_url)
        f = request.urlopen(last_url)
        url_info = f.info()
        url_charset = client.HTTPMessage.get_charsets(url_info)[0]
        url_source = f.read().decode(url_charset)

        return url_source

    except Exception as e:
        print("[-] Error : %s" % e)
        sys.exit(-1)


def main():
    print_logo()

    if len(sys.argv) != 2:
        print("[*] Usage : gmf_nb.py [NAVER Blog URL]")
    else:
        url = sys.argv[1]
        print("[*] Target URL : %s" % url)
        url_source = get_url_source(url)

        # find 't.static.blog.naver.net'
        if url_source.find("t.static.blog.naver.net") == -1:
            print("\n[-] It is not a NAVER Blog")
            sys.exit(0)

        try:
            # find 'aPostFiles'
            p_attached_file = re.compile(r"\s*.*aPostFiles\[1\] = \[(.*?)\]", re.IGNORECASE | re.DOTALL)
            result = p_attached_file.match(url_source).group(1)
            if result:
                # convert to JSON style
                data = "[" + result.replace('\'', '\"') + "]"
                json_data = json.loads(data)

                for each_file in json_data:
                    print("* File : %s, Size : %s Bytes" % (each_file["encodedAttachFileName"], each_file["attachFileSize"]))
                    print("  Link : %s" % each_file["encodedAttachFileUrl"])
                    # File Download
                    request.urlretrieve(each_file["encodedAttachFileUrl"], each_file["encodedAttachFileName"])
                    print("  => Done!!\n")
            else:
                print("[-] Attached File not found !!")

        except Exception as e:
            print("[-] Error : %s" % e)
            sys.exit(-1)

if __name__ == "__main__":
    sys.exit(main())


사용법은 간단합니다.

스크립트 파일의 인자로 첨부파일을 받고 싶은 블로그의 주소를 넣어주면 됩니다.

ex) gmf_nb.py http://blog.naver.com/janghs1117/70066915050

gmf_nb.py 실행

일단... 제가 원하는 선에서는 문제없이 동작하는 것 같네요... ^^;;;

파이썬 만세 i(-0-)i




화요일, 11월 01, 2016



지난 번 포스팅(http://www.xeronichs.com/2016/10/study-x64dbg-plugin-02.html)에서

눈에 보이는 '메뉴' 까지 구현을 했습니다.

플러그인의 메뉴

이번에는 '메뉴' 를 클릭할 때, 지정된 동작을 실행하도록 기능 추가를 해보겠습니다 :)


디버거에서 지원해주는 함수들을 사용하기 위해서 bridge 라이브러리가 필요한데요...

x32dbg.lib/x64dbg.lib 라이브러리를 포함하는 부분에 아래처럼 코드를 추가해줍니다.

#include "pluginsdk\_plugins.h"

#ifdef _WIN64
#pragma comment(lib, "pluginsdk\\x64dbg.lib")
#pragma comment(lib, "pluginsdk\\x64bridge.lib") // 추가
#else
#pragma comment(lib, "pluginsdk\\x32dbg.lib")
#pragma comment(lib, "pluginsdk\\x32bridge.lib") // 추가
#endif //_WIN64


'메뉴' 실행 처리


'메뉴'에 대한 처리를 위해서 "CBMENUENTRY" 라는 export 함수를 추가로 구현해줘야 합니다.

#define MENU_TEST1 0
#define MENU_TEST2 1
#define MENU_DISASM_TEST1 2
#define MENU_DISASM_TEST2 3
#define MENU_DUMP_TEST1 4
#define MENU_DUMP_TEST2 5
#define MENU_STACK_TEST1 6
#define MENU_STACK_TEST2 7

//-------------------------------------------------------------------

extern "C" __declspec(dllexport) void CBMENUENTRY(CBTYPE cbType, PLUG_CB_MENUENTRY* info);


//-------------------------------------------------------------------
extern "C" __declspec(dllexport) void CBMENUENTRY(CBTYPE cbType, PLUG_CB_MENUENTRY* info)
{
    switch (info->hEntry)
    {
    case MENU_TEST1:

        break;

    case MENU_TEST2:

        break;

    case MENU_DISASM_TEST1:

        break;

    case MENU_DISASM_TEST2:

        break;

    case MENU_DUMP_TEST1:

        break;

    case MENU_DUMP_TEST2:

        break;

    case MENU_STACK_TEST1:

        break;

    case MENU_STACK_TEST2:

        break;
    }
}

switch 구문 안에서 info->hEntry 의 값에 따라 다르게 처리를 하고 있는데요...

이 때 hEntry 는 지난 번에 "_plugin_menuaddentry" 함수로 메뉴를 등록할 때 지정한 ID 값입니다.

각 메뉴에 해당하는 case 구문 안에서 원하는 동작을 하도록 코드를 작성해주면 됩니다 :)

//-------------------------------------------------------------------
extern "C" __declspec(dllexport) void CBMENUENTRY(CBTYPE cbType, PLUG_CB_MENUENTRY* info)
{
    SELECTIONDATA sel;
    BASIC_INSTRUCTION_INFO basicinfo;

    duint uiAddr = 0;
 
    switch (info->hEntry)
    {
    case MENU_TEST1:
        MessageBox(g_hwndDlg, L"Menu Test 1", L"BasicPlugin", MB_ICONINFORMATION | MB_OK);

        break;

    case MENU_TEST2:
        if (DbgIsDebugging())
            MessageBox(g_hwndDlg, L"Menu Test 2\r\n\r\n[ DbgIsDebugging() -> true ]", L"BasicPlugin", MB_ICONINFORMATION | MB_OK);
        else
            MessageBox(g_hwndDlg, L"Menu Test 2\r\n\r\n[ DbgIsDebugging() -> false ]", L"BasicPlugin", MB_ICONINFORMATION | MB_OK);

        break;

    case MENU_DISASM_TEST1:
        GuiSelectionGet(GUI_DISASSEMBLY, &sel);
        _plugin_logprintf("[BasicPlugin] Disasm [ START : 0x%p - END : 0x%p ]\n", sel.start, sel.end);

        break;

    case MENU_DISASM_TEST2:
        GuiSelectionGet(GUI_DISASSEMBLY, &sel);
  
        uiAddr = sel.start;
        while (uiAddr <= sel.end) {
            DbgDisasmFastAt(uiAddr, &basicinfo);
            _plugin_logprintf("[BasicPlugin] [0x%p] : %s\n", uiAddr, basicinfo.instruction);
            uiAddr += basicinfo.size;
        }

        break;

    case MENU_DUMP_TEST1:
        GuiSelectionGet(GUI_DUMP, &sel);
        _plugin_logprintf("[BasicPlugin] Dump [ START : 0x%p - END : 0x%p ]\n", sel.start, sel.end);

        break;

    case MENU_DUMP_TEST2:
        GuiSelectionGet(GUI_DUMP, &sel);

        _plugin_logprintf("[BasicPlugin] Dump : ");
        uiAddr = sel.start;
        while (uiAddr <= sel.end) {
            _plugin_logprintf("%02X", *(BYTE*)uiAddr);
            uiAddr++;
        }
        _plugin_logprintf("\n");

        break;

    case MENU_STACK_TEST1:
    case MENU_STACK_TEST2:
        GuiSelectionGet(GUI_STACK, &sel);
        _plugin_logprintf("[BasicPlugin] Stack [ START : 0x%p - END : 0x%p ]\n", sel.start, sel.end);

        break;
    }
}

메인 메뉴의 'MENU_TEST1', 'MENU_TEST2' 를 클릭할 경우...

MessageBox 를 이용해서 메시지를 띄우도록 해봤습니다.

'MENU_TEST2' 에서는 "DbgIsDebugging" 이라는 함수를 이용해

결과에 따라 실행이 달라지도록 처리를 했습니다.

bool DbgIsDebugging();
현재 디버거가 디버깅 중인지 확인하는 함수.
디버깅 중일 땐 true 를 리턴, 아닐 땐 false 를 리턴.

디버깅 중이 아닐 때... false
디버깅 중일 때... true


Disasm 창 팝업 메뉴의 'MENU_DISASM_TEST1', 'MENU_DISASM_TEST2' 를 클릭할 경우...

"GuiSelectionGet" 함수로 선택된 영역에 대한 주소 정보를 가져와서 처리를 하도록 했습니다.

bool GuiSelectionGet(int hWindow, SELECTIONDATA *selection);
선택된 영역에 대한 주소 정보(시작 주소, 끝 주소)를 가져오는 함수

hWindow : 정보를 가져올 대상 창 지정
- GUI_DISASSEMBLY : Disasm 창
- GUI_DUMP : Dump 창
- GUI_STACK : Stack 창

selection : 주소 정보를 담을 구조체
- start 에 시작 주소, end 에 끝 주소가 담긴다.
typedef struct
{
    duint start;
    duint end;
} SELECTIONDATA;


Disasm 창 팝업 메뉴의 'MENU_DISASM_TEST1'
Disasm 창 팝업 메뉴의 'MENU_DISASM_TEST1' 실행 결과

"DbgDisasmFastAt" 함수를 이용해 선택된 영역의 디스어셈블 정보도 구할 수 있습니다.

void DbgDisasmFastAt(duint addr, BASIC_INSTRUCTION_INFO *basicinfo);
지정한 주소에 대한 디스어셈블 정보를 구하는 함수

addr : 디스어셈블 정보를 구할 주소

basicinfo : 디스어셈블 정보를 담을 구조체
typedef struct
{
    DWORD type; //value|memory|addr
    VALUE_INFO value; //immediat
    MEMORY_INFO memory;
    duint addr; //addrvalue (jumps + calls)
    bool branch; //jumps/calls
    bool call; //instruction is a call
    int size;
    char instruction[MAX_MNEMONIC_SIZE * 4];
} BASIC_INSTRUCTION_INFO;

Disasm 창 팝업 메뉴의 'MENU_DISASM_TEST2'
Disasm 창 팝업 메뉴의 'MENU_DISASM_TEST2' 실행 결과

( 참고로 "_plugin_logprintf" 함수로 출력을 했기에 Log 창에 결과가 출력됩니다. )


이처럼 디버거에서 지원해주는 함수를 이용해서 플러그인 기능 구현이 가능한데요...

이 함수들의 리스트는 x64dbg 사이트의 "Help" 페이지에 정리가 되어있습니다.

[ http://help.x64dbg.com/en/latest/developers/index.html ]


단, 함수 리스트는 제공하지만 함수에 대한 상세한 스펙은 아직 완전히 정리가 되지는 않은 느낌이라...

어떤 역할을 하는 함수인지... 어떻게 사용하는지는 직접 코드 작성을 해가며 확인할 수 밖에 없을 듯 합니다. ^^;;;;


[ BasicPlugin 소스 다운로드 ]


마치며...


3회에 걸쳐 x64dbg 플러그인을 만드는 과정을 살펴봤는데요...

플러그인 등록 및 메뉴 처리까지 가능하다면 플러그인 개발의 기본 정도는 익힌게 아닐까... 생각됩니다. ^^;;;

나머지는 플러그인으로 어떤 기능을 구현할 것인지... 디버거 지원 함수를 어떻게 사용할 것인지...

응용 차원의 문제인지라... ^^;;;

x64dbg 도 좋은 플러그인이 많이 나오길 바라면서 살포시 마무리 합니다.




금요일, 10월 28, 2016



지난 번 포스팅(http://www.xeronichs.com/2016/10/study-x64dbg-plugin-01.html)에서

플러그인(껍데기 뿐이지만;;)을 x64dbg 에 로딩하는 것까지 했습니다.


이번에는 '아~ 플러그인이 확실히 로딩되었구나~!!' 알 수 있도록...

눈에 보이는(?) 플러그인 '메뉴' 를 추가하는 방법을 알아보도록 하겠습니다.


그 전에...

32비트용, 64비트용 개발할 때마다 라이브러리 포함 코드를 바꾸는건 귀찮으니

이 부분부터 정리를 하고 넘어가죠~ ^^;;;;


매크로로 32비트 / 64비트 구별하기


64비트 프로젝트의 경우 기본적으로 '_WIN64' 라는 매크로가 정의되어 있는데요...

이걸로 32비트 / 64비트 프로젝트를 구별할 수 있습니다.

#include "pluginsdk\_plugins.h"

#ifdef _WIN64
#pragma comment(lib, "pluginsdk\\x64dbg.lib")
#else
#pragma comment(lib, "pluginsdk\\x32dbg.lib")
#endif //_WIN64

이렇게하면...

'_WIN64' 매크로가 있으면 64비트 프로젝트로 판단해서 "x64dbg.lib" 를 포함시키고,

없으면 32비트 프로젝트로 판단해서 "x32dbg.lib" 를 포함하게 됩니다.

이후에 다른 코드를 작성할 때도 32비트 / 64비트를 다르게 처리해야 될 경우,

이런 방식으로 매크로를 이용하면 됩니다 :)


지극히 개인적인 편의(?)를 위해 '프로젝트 속성' 에서 '출력 디렉터리', '중간 디렉터리' 를 살짝 바꿨습니다.

프로젝트 속성 '출력 디렉터리, 중간디렉터리' 변경
'플랫폼'이 하위폴더가 되도록 변경

x86(Release & Debug), x64(Release & Debug) 모든 구성, 플랫폼에 대해 변경을 했습니다.

'일괄빌드' 를 이용해서 x86, x64 프로젝트를 동시에 빌드를 해보면...

Debug, Release 폴더 안에 Win32, x64 폴더로 나뉘어져 결과물이 저장됩니다

Win32, x64 프로젝트 동시에 빌드
Release 폴더가 생성됨
Release 폴더 안에 Win32, x64 폴더가 생성됨

테스트를 좀 더 편하게(?) 하기 위해 프로젝트 폴더에 BAT 파일을 하나 만들어서...

32비트, 64비트 플러그인을 디버거 폴더에 각각 복사하도록 해뒀습니다.

@echo off
copy .\Release\Win32\x64dbg_basic_plugin.dll C:\MyTools\x64dbg\release\x32\plugins\basic_plugin.dp32
copy .\Release\x64\x64dbg_basic_plugin.dll C:\MyTools\x64dbg\release\x64\plugins\basic_plugin.dp64
pause


플러그인 '메뉴' 구현


지난 번에 플러그인을 인식시키기 위해서 "pluginit" 이라는 export 함수를 구현을 했구요.

이번에는 디버거의 UI 쪽 정보를 얻기 위해 "plugsetup" 이라는 export 함수 를 구현해줘야 합니다.

HWND g_hwndDlg;
int g_hMenu;
int g_hMenuDisasm;
int g_hMenuDump;
int g_hMenuStack;

//-------------------------------------------------------------------
.
.
extern "C" __declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT* setupStruct);
.
.
//-------------------------------------------------------------------
__declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT* setupStruct)
{
    g_hwndDlg = setupStruct->hwndDlg;  // x64dbg 윈도우 핸들
    g_hMenu = setupStruct->hMenu; // x64dbg 메인 메뉴 핸들
    g_hMenuDisasm = setupStruct->hMenuDisasm; // x64dbg Disasm 창의 팝업메뉴 핸들
    g_hMenuDump = setupStruct->hMenuDump; // x64dbg Dump 창의 팝업메뉴 핸들
    g_hMenuStack = setupStruct->hMenuStack; // x64dbg Stack 창의 팝업메뉴 핸들
}

"plugsetup" 함수는 PLUG_SETUPSTRUCT 구조체를 인자로 사용하는데요...

이 구조체의 멤버 변수에 디버거 윈도우 핸들 및 각종 메뉴의 핸들이 담겨져 있습니다.

x64dbg UI 구성

메뉴를 추가할 때는 "_plugin_menuaddentry" 함수를 사용하며 사용법은 다음과 같습니다.

bool _plugin_menuaddentry(int hMenu, int hEntry, const char *title);
- hMenu : 플러그인 메뉴를 추가할 대상의 핸들
- hEntry : 플러그인 메뉴 ID 값 (이후 메뉴에 대한 실행코드 작성 시 사용됨)
- title : 화면에 보여지는 메뉴 텍스트

다음은 메뉴를 실제로 추가하는 코드입니다.

.
.
#define MENU_TEST1 0
#define MENU_TEST2 1
#define MENU_DISASM_TEST1 2
#define MENU_DISASM_TEST2 3
#define MENU_DUMP_TEST1 4
#define MENU_DUMP_TEST2 5
#define MENU_STACK_TEST1 6
#define MENU_STACK_TEST2 7
.
.
__declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT* setupStruct)
{
.
.
.
    // 메인 메뉴
    _plugin_menuaddentry(g_hMenu, MENU_TEST1, "Menu Test 1");
    _plugin_menuaddseparator(g_hMenu); // 메뉴 분리선
    _plugin_menuaddentry(g_hMenu, MENU_TEST2, "Menu Test 2");

    // 디스어셈블 창 메뉴
    _plugin_menuaddentry(g_hMenuDisasm, MENU_DISASM_TEST1, "Menu Disasm Test 1");
    _plugin_menuaddseparator(g_hMenuDisasm); // 메뉴 분리선
    _plugin_menuaddentry(g_hMenuDisasm, MENU_DISASM_TEST2, "Menu Disasm Test 2");

    // 덤프 창 메뉴
    _plugin_menuaddentry(g_hMenuDump, MENU_DUMP_TEST1, "Menu Dump Test 1");
    _plugin_menuaddentry(g_hMenuDump, MENU_DUMP_TEST2, "Menu Dump Test 2");

    // 스택 창 메뉴
    _plugin_menuaddentry(g_hMenuStack, MENU_STACK_TEST1, "Menu Stack Test 1");
    _plugin_menuaddentry(g_hMenuStack, MENU_STACK_TEST2, "Menu Stack Test 2");
}

플러그인이 종료될 때 정리작업을 해주는 "plugstop" export 함수도 작성을 해줍니다.

//-------------------------------------------------------------------
.
.
extern "C" __declspec(dllexport) bool plugstop(void);
.
.
//-------------------------------------------------------------------
__declspec(dllexport) bool plugstop(void)
{
    // 메뉴에 설정한 내용들 정리
    _plugin_menuclear(g_hMenu);
    _plugin_menuclear(g_hMenuDisasm);
    _plugin_menuclear(g_hMenuDump);
    _plugin_menuclear(g_hMenuStack);

    return true;
}

어제 작업했던 껍데기(?) 플러그인 코드에 오늘 내용을 덧붙이면 아래처럼 되겠네요~ :)

// plugin.cpp
//
//-------------------------------------------------------------------
// basic plugin [ x64dbg plugin ]
//-------------------------------------------------------------------

#include <Windows.h>

#include "pluginsdk\_plugins.h"

#ifdef _WIN64
#pragma comment(lib, "pluginsdk\\x64dbg.lib")
#else
#pragma comment(lib, "pluginsdk\\x32dbg.lib")
#endif //_WIN64

//-------------------------------------------------------------------
#define plugin_name "BasicPlugin"
#define plugin_version 1

#define MENU_TEST1 0
#define MENU_TEST2 1
#define MENU_DISASM_TEST1 2
#define MENU_DISASM_TEST2 3
#define MENU_DUMP_TEST1 4
#define MENU_DUMP_TEST2 5
#define MENU_STACK_TEST1 6
#define MENU_STACK_TEST2 7

int g_iPluginHandle;

HWND g_hwndDlg;
int g_hMenu;
int g_hMenuDisasm;
int g_hMenuDump;
int g_hMenuStack;

//-------------------------------------------------------------------
extern "C" __declspec(dllexport) bool pluginit(PLUG_INITSTRUCT* initStruct);
extern "C" __declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT* setupStruct);
extern "C" __declspec(dllexport) bool plugstop(void);

//-------------------------------------------------------------------
__declspec(dllexport) bool pluginit(PLUG_INITSTRUCT* initStruct)
{
    initStruct->sdkVersion = PLUG_SDKVERSION;
    initStruct->pluginVersion = plugin_version;
    strcpy_s(initStruct->pluginName, 256, plugin_name);
    g_iPluginHandle = initStruct->pluginHandle;

    _plugin_logprintf("[BasicPlugin] pluginHandle : %d\n", g_iPluginHandle);

    return true;
}

__declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT* setupStruct)
{
    g_hwndDlg = setupStruct->hwndDlg;
    g_hMenu = setupStruct->hMenu;
    g_hMenuDisasm = setupStruct->hMenuDisasm;
    g_hMenuDump = setupStruct->hMenuDump;
    g_hMenuStack = setupStruct->hMenuStack;

    // 메인 메뉴
    _plugin_menuaddentry(g_hMenu, MENU_TEST1, "Menu Test 1");
    _plugin_menuaddseparator(g_hMenu);
    _plugin_menuaddentry(g_hMenu, MENU_TEST2, "Menu Test 2");

    // 디스어셈블 창 메뉴
    _plugin_menuaddentry(g_hMenuDisasm, MENU_DISASM_TEST1, "Menu Disasm Test 1");
    _plugin_menuaddseparator(g_hMenuDisasm);
    _plugin_menuaddentry(g_hMenuDisasm, MENU_DISASM_TEST2, "Menu Disasm Test 2");

    // 덤프 창 메뉴
    _plugin_menuaddentry(g_hMenuDump, MENU_DUMP_TEST1, "Menu Dump Test 1");
    _plugin_menuaddentry(g_hMenuDump, MENU_DUMP_TEST2, "Menu Dump Test 2");

    // 스택 창 메뉴
    _plugin_menuaddentry(g_hMenuStack, MENU_STACK_TEST1, "Menu Stack Test 1");
    _plugin_menuaddentry(g_hMenuStack, MENU_STACK_TEST2, "Menu Stack Test 2");
}

__declspec(dllexport) bool plugstop(void)
{
    _plugin_menuclear(g_hMenu);
    _plugin_menuclear(g_hMenuDisasm);
    _plugin_menuclear(g_hMenuDump);
    _plugin_menuclear(g_hMenuStack);

    return true;
}

//-------------------------------------------------------------------
// DllMain
//-------------------------------------------------------------------
BOOL WINAPI DllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved)
{
    return TRUE;
}

이 코드를 빌드를 해서 디버거 플러그인 폴더에 복사해서 실행을 해보면 메뉴가 뜨는 걸 볼 수 있을 겁니다 :)

'메인 메뉴' 의 플러그인 메뉴
'Disasm 창 팝업 메뉴' 의 플러그인 메뉴
'Dump 창 팝업 메뉴' 의 플러그인 메뉴
'Stack 창 팝업 메뉴' 의 플러그인 메뉴

이번에는 여기까지~~ 플러그인 '메뉴' 를 추가하는 방법을 알아봤구요...

다음에는  이렇게 추가한 '메뉴' 를 클릭할 때, 특정 동작을 실행시키는 걸 알아보도록 하겠습니다.


[ BasicPlugin 소스 다운로드 ]




카테고리

가장 많이 본 글

통계

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