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



수요일, 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월 23, 2016



나름 DOS 시절부터 컴퓨터를 사용해서 그런지 모르겠지만...

아직까지 '마우스' 보다는 '키보드' 가 더 편하게 느껴집니다.

꼭 마우스를 이용해야 하거나 마우스가 더 편리한 작업이 아닌 이상...

웬만해선 키보드로 다 작업을 하는데요.

그러다보니 '단축키' 를 많이 활용하는 편입니다.


얼마 전 일입니다...

Visual Studio 로 작업하다가 프로젝트 속성을 변경하려고 "Alt-F7" 을 눌렀는데...

오잉...!? 반응이 없더군요 @_@;;;;;

메뉴에서 직접 실행하면 '프로젝트 속성' 창이 잘 뜨는데

단축키 "Alt-F7" 을 누르면 꿀먹은 벙어리 마냥 반응이 없었습니다.

뭔가 Visual Studio 시스템이 꼬인건가 싶어 이것저것 살펴봤지만 별다른 수확은 없었고

찜찜한 마음에 포맷까지 하게 됐습니다 ~ oTL;;;;


운영체제랑 프로그램 새로 다 설치하고~

산뜻한 마음으로(?) 기분좋게 Visual Studio 를 실행시켜 "Alt-F7" 을 눌러봤는데...

헐...!? 여전히 단축키가 안먹힐 때의 그 멘붕이란...;;;

뭐... 조금 불편하긴 하겠지만 포맷도 했겠다~ 다른 문제는 없는 듯 싶어 넘겼습니다.


그로부터 며칠 후...

별 생각없이 게임을 실행했는데 그날따라 유독~

게임 시작 시 화면에 잠깐 나왔다 사라지는 NVIDIA 메시지가 신경쓰이더군요.

"Alt-Z" 를 누르면 된다길래 눌러봤더니...

NVIDIA GeForce Experience 의 화면이 뜨더군요.

이건 뭔가 싶어 이것저것 둘러보던 중 단축키 부분을 발견..;;;;

맙소사... "Alt-F7" 이 GeForce Experience 의 단축키로 딱 있더군요


NVIDIA GeForce Experience 의 단축키들

GeForce Experience 의 단축키들이 전역으로 동작하는 바람에...

Visual Studio 의 "Alt-F7" 이 그냥 무시된 걸 알았을 때의 그 허탈함이란...


조금 더 만지작 거려보니 저 기능은 "공유" 기능이 켜져있을 때만 동작을 하더군요.

NVIDIA GeForce Experience 의 공유 기능

딱히 게임하는 순간을 스트리밍하거나 할 일은 없을 것 같아서 과감하게  Off~~!!

'공유' 기능을 꺼둔 상태에서는 Visual Studio 에서 "Alt-F7" 이 잘 동작하더군요... 하아 =3=3

( 내가 이러려고 포맷을 했나.... 싶은;;; oTL )


혹시라도... GeForce Experience 를 설치한 불특정 다수(?)의 개발자 분들이

일부 단축키가 안먹히는 상황에 저같은 뻘짓을 하지않길 바라면서 살포시 공유해봅니다.




화요일, 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월 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비트 어셈블리 코드를 보니 머리가 어질어질하네요...






수요일, 11월 02, 2016



1시간 정도 전에 공개된 태연의 따끈따끈한 신곡~ "11:11"

뭔가 싸늘해진 날씨에 어울리는 분위기인 것 같기도 하고...

잔잔한 느낌이라 듣기 편안하네요~ :)

믿고 듣는 태연~ @_@ !!






태연 - 11:11

It’s 11:11
오늘이 한 칸이 채 안 남은 그런 시간
우리 소원을 빌며 웃던 그 시간
별 게 다 널 떠오르게 하지
네 맘 끝자락처럼 차가운 바람
창을 열면 온통 네가 불어와
이 시간이 전부 지나고 나면
이별이 끝나 있을까 Yeah
널 다 잊었을까

모든 게 자릴 찾아서 떠나가고
넌 내 모든 걸 갖고서 떠나도
내 맘은 시계 속의 두 바늘처럼
같은 곳을 두고 맴돌기만 해

Na na na na na na na
na na na na na na oh
Na na na na na na na na
I believe I will be over you

달력 안에 있는
오래 전에 약속했던 몇 월의 며칠
너에겐 다 잊혀져 있었다면
내가 지워야지 뭐
지나고 나면 별 거 아니겠지 뭐

모든 게 자릴 찾아서 떠나가고
넌 내 모든 걸 갖고서 떠나도
내 맘은 시계 속의 두 바늘처럼
같은 곳을 두고 맴돌기만 해

Na na na na na na na
na na na na na na oh
Na na na na na na na na
I believe I will be over you

계절 틈에 잠시 피는 낯선 꽃처럼
하루 틈에 걸려 있는 새벽 별처럼
이 모든 건 언젠가는
다 지나가고 말겠지

모든 게 자릴 찾아서 돌아오고
내가 아무 일 없는 듯이 웃게 되면
너의 이름 한 번쯤 부르는 게
지금처럼 아프지 않을 거야

Na na na na na na na
na na na na na na oh
Na na na na na na na na
I believe I will be over you

Na na na na na na na
na na na na na na oh
Na na na na na na na na
I believe I will be over you



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




목요일, 10월 27, 2016



Delphi / C++ Builder STARTER EDTION 무료 프로모션


얼마전에 스타터 에디션 무료 프로모션 진행을 한 걸로 아는데...

지금까지 진행이 되고 있는건지... 새로 시작한건지는 모르겠지만 @_@...

일단 공유해봅니다~ :)





아... 이 프로모션 제품들은 1인당 등록횟수가 3회로 제한되어있으니 참고하시기 바랍니다. ^^;;;;





예전에 "x64dbg" 에 대한 내용(http://www.xeronichs.com/2016/01/x64dbg.html)을 포스팅 한 이후로...

틈틈이 x64dbg 를 사용하면서 이것저것 익혀보고 있는데요...

이번에 x64dbg 의 '플러그인' 개발에 대해 포스팅을 해볼까~ 합니다 :)

OllyDBG v1.10 의 경우는 플러그인 SDK 안에 SDK 사용법에 관한 참고자료가 있어서 비교적 쉽게 익혔는데...

x64dbg 는 그런게 없어서 예제 플러그인 소스를 참고하며~ 하나 하나 해보면서 익히고 있는 중입니다.

( 혹시라도 x64dbg 플러그인 개발 관련 내용이 정리되어 있는 곳 아시면 공유 좀... ^^;;;;; )



자~ 그럼 이제 시작합니다 ~ !!!

오늘의 주제는 '내가 개발한 플러그인을 x64dbg 에 로딩시키기' 입니다... @_@

'천리길도 한걸음부터...' 라고 프로젝트 생성부터 시작해봅시다~ 

Visual Studio 프로젝트 생성


Visual Studio 프로젝트 생성 단계 #1

"Visual C++" -> "Win32" -> "Win32 프로젝트" 를 선택하고 프로젝트 이름을 작성합니다.

저는 임의로 'x64dbg_basic_plugin' 이라고 했는데 입맛에 맞게 정하시면 됩니다. ^^;;;

또, 개인적인 편의상 '솔루션용 디렉터리 만들기' 도 해제했습니다...


Visual Studio 프로젝트 생성 단계 #2

Visual Studio 프로젝트 생성 단계 #3

플러그인은 DLL 형태의 모듈이기 때문에 "DLL" 로 지정을 하고,

'빈 프로젝트' 항목에 체크를 해준 다음 '마침' 버튼을 눌러서 프로젝트 생성을 마칩니다.

x64dbg 는 XP 에서도 동작을 하는 걸로 알고 있는데...

프로젝트 속성 옵션에서 XP 에서도 동작하는 바이너리를 생성하도록 설정을 변경해줍니다.

XP 지원하도록 프로젝트 속성 변경


플러그인 SDK 폴더를 프로젝트 폴더로 복사


프로젝트 생성이 완료되면 플러그인 SDK 폴더를 프로젝트 폴더로 복사해줍니다.

x64dbg 압축파일의 플러그인 SDK 폴더

x64dbg 는 친절하게도(?) 디버거와 플러그인 SDK 를 묶어서 제공하는데요...

x64dbg  압축파일을 다운로드 받아서 압축 해제 후,  pluginsdk 폴더만 프로젝트 폴더로 복사합니다.

프로젝트 폴더에 플러그인 SDK 폴더 복사


플러그인 소스파일 작성


플러그인 소스파일 작성

소스파일의 이름을 입맛에 맞게 작성하신 다음 코드 작성을 시작하면 됩니다.


일단 DLL 파일이니까 "DllMain" 이 있어야겠죠...?

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

#include <Windows.h>


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

이렇게만 작성하고 '빌드' 를 해보면 DLL 파일이 생성이 될겁니다 :)


이 텅 빈 DLL 을 x64dbg 플러그인 형태로 바꿔봅시다~ :)

먼저 pluginsdk 폴더의 "_plugins.h" 파일을 인클루드하고~

"x32dbg.lib" 라이브러리 파일을 포함하도록 코드를 추가해줍니다.

( 64비트용 플러그인을 만들때는 "x64dbg.lib" 라이브러리 파일을 포함하면 됩니다. )

#include "pluginsdk\_plugins.h"

#pragma comment(lib, "pluginsdk\\x32dbg.lib")


다음은 플러그인 이름과 버전에 대한 내용을 설정해줍니다.

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


이제 x64dbg 가 플러그인이라고 인식할 수 있도록 export 함수("pluginit")를 작성해줍니다.

int g_iPluginHandle;

//-------------------------------------------------------------------
extern "C" __declspec(dllexport) bool pluginit(PLUG_INITSTRUCT* initStruct);

//-------------------------------------------------------------------
__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;

    return true;
}

테스트를 해본 결과 "pluginit" 함수만 있어도 플러그인으로 인식을 하더라구요.. @_@;;

pluginit 함수는 위의 코드처럼 PLUG_INITSTRUCT 구조체의 값을 채우도록 구현을 하면 됩니다.

sdkVersion, pulginVersion, pluginName 의  세 항목들은 값을 넣어줘야하고...

pluginHandle 은 값을 넣는게 아니라 다른 변수에 값을 복사해둡니다. @_@;;;


이 코드들을 몽땅 합치면 아래와 같이 되겠네요...

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

#include <Windows.h>

#include "pluginsdk\_plugins.h"

#pragma comment(lib, "pluginsdk\\x32dbg.lib")

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

int g_iPluginHandle;

//-------------------------------------------------------------------
extern "C" __declspec(dllexport) bool pluginit(PLUG_INITSTRUCT* initStruct);

//-------------------------------------------------------------------
__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;

    return true;
}

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


이렇게 작성한 코드를 빌드하면 DLL 파일이 나올텐데요~

이 DLL 파일의 확장자를 'dp32' 로 바꾸고 32비트용 디버거의 'plugins' 폴더에 복사한 다음...

32비트용 디버거를 실행시키면 다음과 같이 플러그인이 로딩되는 걸 확인할 수 있습니다.

플러그인이 로딩되었다는 로그

64비트 디버거용 플러그인을 빌드할 때는 라이브러리 파일을 "x64dbg.lib" 로 바꾸고 빌드를 합니다.

DLL 파일이 생성되면 확장자를 'dp64' 로 바꿔서 64비트용 디버거의 'plugins'  폴더에 복사하면 됩니다.


여기까지 플러그인 껍데기(?)를 만들어서 x64dbg 에 로딩시키는 걸 알아봤구요...

다음 내용부터는 조금씩 플러그인 알맹이를 채워나가 보도록 하겠습니다 ~ :)


[ BasicPlugin 소스 다운로드 ]






카테고리

가장 많이 본 글

통계

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