리버서들이 가장 많이 사용하는 도구인 디버거(Debugger)에 대해 이야기 해보도록 하겠습니다.


리버싱 현업에서 사용되는 Debugger 들을 간략히 정리해 보겠습니다. 


1. OllyDbg



OllyDbg 는 사용이 편리하고 가볍고 빠른 무료 Debugger 입니다. 도저히 무료라고 보기 힘든 다양한 기능과 많은 Plugin 을 통한 확장성으로 인하여 수 많은 리버서들의 열광적인 지지를 얻고 있습니다. 

OllyDbg 는 리버싱 초보부터 전문가까지 폭넓게 사용되는 가장 인기 있는 디버거입니다.


<그림 1 - OllyDbg>

OllyDbg 의 장점으로는 가볍고 빠르며 상당히 다양한 기능과 많은 옵션을 제공한다는 것입니다. 또한 PlugIn 기능을 통한 확장성을 제공합니다. 가장 사용자가 많고 OllyDbg 를 이용한 리버싱 강좌가 많이 있기 때문에 초보자도 쉽게 배울 수 있습니다. 그리고 무료로 제공된다는 점도 큰 장점입니다.

단점으로는 개인이 혼자서 개발한 거라 업데이트와 후속 제품의 개발 주기가 늦다는 것입니다. 다행히 최근에 OllyDbg 2.0 Final 버전이 릴리즈 되었습니다. 비록 겉모습은 이전 버전과 동일하지만 내부 코드는 완전히 새롭게 프로그래밍 하여 속도와 정확성 등이 크게 향상되었다고 하네요.


2. IDA Pro



Hex-rays 사의 IDA Pro 는 현재 최고의 Disassembler & Debugger 라고 말할 수 있습니다. 과거에는 Disassembler 성격이 강했으나, 수많은 업데이트를 통해 Debugger 기능 또한 막강해 졌습니다. 

수 많은 다양한 기능들을 설명하는 전문 서적이 따로 존재할 정도로 엄청난 기능을 자랑합니다. 또한 Decompiler Plugin 등을 추가로 장착하면 리버싱이 말할 수 없이 편리해 집니다. 그만큼 가격도 비싸지요.

많은 리버싱 전문가들이 IDA Pro 를 주력으로 삼으면서 리버싱 전문 툴로써의 입지를 탄탄히 굳히고 있습니다.

장점으로는 다 써보지도 못할 정도로 다양한 기능과 충실한 업데이트를 들 수 있습니다. 다만 가격이 비싸고 사용법이 비교적 복잡하며 초기 로딩 시간이 좀 걸린다는 것을 단점으로 들 수 있겠습니다. 


<그림 2 – IDA Pro>


3. WinDbg



WinDbg 는 DOS 시절 16 bit 디버거인 debug.exe 의 Windows 버전입니다. 


<그림 3 – Debug.exe>

콘솔 화면에서 키보드만으로 디버깅을 하는 debug.exe 의 사용자 인터페이스를 그대로 가져왔습니다. 저는 이런 스타일의 프로그램을 매우 좋아하지만 반대로 거부감을 갖는 분들도 상당수 계십니다.

유저 모드 디버깅(User Mode Debugging) 분야에서는 사용자 편리성이 뛰어난 OllyDbg 나 IDA Pro 가 꽉 잡고 있습니다. 따라서 WinDbg 는 주로 커널 모드 디버깅(Kernel Mode Debugging)에 주로 사용됩니다. 전설적인 커널 디버거인 SoftICE 의 후속 제품 개발이 중단된 이후 커널 디버깅 분야에서 사실상 독보적인 존재가 되어 버렸습니다. (경쟁 제품이 없는 상태입니다.)

역사가 오래 된 만큼 기능도 다양하고 사용 방법에 대한 전문 서적이 여러 권 나와 있습니다.


<그림 4 - WinDbg>

장점은 커널 디버깅이 가능하다는 것과 Microsoft 에서 직접 만든 디버거라는 점입니다. 시스템 파일에 대한 심볼(Symbol)을 직접 다운 받을 수 있어서 시스템 내부 구조체(Undocumented 포함) 및 API 에 대한 정보를 얻을 수 있습니다. 또한 Windows OS 의 덤프 파일을 읽어 들여 분석할 수 있기 때문에 시스템 크래쉬(Crash)가 발생했을 때 원인을 분석해 낼 수 있습니다. 

단점으로는 다른 디버거들에 비해 좀 떨어지는 사용자 환경과 편의성을 꼽을 수 있겠습니다. (예를 들어 코드에 직접 주석을 입력할 수 없고, Disassembly 코드에서 호출되는 API 이름도 잘 표시되지 않는 등의 불편함이 있습니다.)

그래도 커널 드라이버 개발과 유지보수에 필수적으로 사용되는 디버거 입니다. 리버싱에서는 커널 드라이버 파일 분석에 주로 사용됩니다. 최근에는 대부분 WinDbg 와 VirtualPC(또는 VMWare) 조합으로 커널 디버깅을 수행합니다. 향후 여러분들의 리버싱 실력이 쌓인 후 커널 드라이버(예: Rootkit) 등을 분석할 때 자주 사용될 것입니다.

+---+

지금까지 리버싱 현업에서 사용되는 대표적인 디버거들에 대해서 간략히 살펴보았습니다.

제 경우에는 유저 모드 디버깅에 OllyDbg 1.10 를 사용하고, 커널 모드 디버깅에는 WinDbg 를 사용합니다.

ReverseCore

'tool' 카테고리의 다른 글

리버싱 현업에서 사용되는 디버거(Debugger)들  (20) 2010/09/29
InjDll.exe – DLL Injection/Ejection 전용 도구  (33) 2010/03/15
(개인) 즐겨찾기  (0) 2009/06/02
Process Explorer - 최고의 작업 관리자  (0) 2009/05/03
www.virustotal.com  (2) 2009/03/20
www.google.com  (2) 2009/03/06

Trackback Address :: http://www.reversecore.com/trackback/91 관련글 쓰기

  1. reversecore 2010/09/30 19:25 댓글주소 | 수정 | 삭제 | 댓글

    - 봉이님 댓글

    ㅁㅁ님!
    제가 리버싱 공부하면서, 안티리버싱, 악성코드 분석에 관심이 생겨서 인터넷 곳곳 돌아 봤는데요..
    안랩 연구원들 분께서 고려대, 서울여대 악성코드 분석 강의를 하시는 것 같더라구요..
    관련 강의자료를 구할수 있을까요?
    꼭 부탁드립니다.

    • reversecore 2010/09/30 19:26 댓글주소 | 수정 | 삭제

      안녕하세요~

      ^^ 제 실명을 거론 하셔서 제가 약간 가렸습니다. 양해바랍니다.

      강의에 대해서 잘 알고 계시군요?

      해당 자료는 수강생 이외에는 비공개 입니다.

      참고로 저도 한 섹션을 맡았습니다. ^^

  2. reversecore 2010/09/30 19:25 댓글주소 | 수정 | 삭제 | 댓글

    - 봉이님 댓글

    core님이 ㅁㅁ님 맞죠?
    혹시 core님 이름이 틀렸을수도 있으니, 제가 위에 표현한 ㅁㅁ님은 core님입니다.

    • reversecore 2010/09/30 18:45 댓글주소 | 수정 | 삭제

      ^^ 네 제가 그님(?) 맞습니다.

      봉이님께서는 혹시 저를 아시는 분인가요?
      너무 친숙하게 물어보셔서요~ ^^
      (비밀 댓글로 답해주시면 됩니다.)

  3. 2010/10/01 15:39 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2010/10/03 23:07 댓글주소 | 수정 | 삭제

      안녕하세요.~

      네, 그러셨군요. ^^

      리버싱을 공부하고 싶으시다는 열의가 느껴집니다.

      죄송하지만 강의 자료를 제공해 드릴 수는 없습니다. (제가 작성한 거라도 불가능합니다.)

      그 대신 공부하시다가 막히시는 부분이 있을때 질문 올려 주시면 제 능력껏 답변 달아 드리겠습니다.

      감사합니다.

  4. 김종현 2010/10/02 23:40 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요.
    처음 인사드리는 김종현입니다.
    테스트중 이상한 현상이 있어 문의 드립니다.

    아래와 같이 정말 간단한 DLL을 만들어 Injection하였는데
    이상하게 iexplore의 자식 프로세스들에서는 ::CreateFile()로
    파일이 생성되지 않는데
    그 이유가 심히 궁금하고
    해결방법에 대하여 고수님들께 조언을 구합니다.

    #include "stdafx.h"
    #include <stdio.h>
    #include <tchar.h>

    /////////////////////////////////////////////////////////////////////////////
    // _Inject

    class _Inject
    {
    public:
    static BOOL IsValidModule(LPCTSTR module_name)
    {
    static LPCTSTR valid_module_name_list[] =
    {
    //_T("notepad.exe"),
    _T("iexplore.exe"),
    NULL
    };
    for (int i=0; valid_module_name_list[i]; i++)
    {
    if (!_tcsicmp(module_name, valid_module_name_list[i])) return TRUE;
    }
    return FALSE;
    }
    }; //_Inject

    static BOOL IsValidCurrentModule()
    {
    TCHAR full_name[MAX_PATH];
    HMODULE module_handle = ::GetModuleHandle(NULL);
    ::GetModuleFileName(module_handle, full_name, MAX_PATH);
    TCHAR* module_name = full_name;
    for (int i=0; full_name[i]; i++)
    {
    if (full_name[i] == '\\') module_name = full_name + i + 1;
    if (full_name[i] == '/') module_name = full_name + i + 1;
    }
    if (!_Inject::IsValidModule(module_name)) return FALSE;
    LPCTSTR file_name = _T("c:\\zzz.txt");
    HANDLE file_handle = ::CreateFile(file_name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file_handle != INVALID_HANDLE_VALUE) ::CloseHandle(file_handle);
    else
    {
    TCHAR text[256];
    _stprintf(text, _T("ERROR: Cannot create file \"%s\"!!!\nPID: %d"), file_name, ::GetCurrentProcessId());
    ::MessageBox(NULL, text, module_name, MB_OK);
    }
    return TRUE;
    }

    BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
    {
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    if (IsValidCurrentModule())
    {
    }
    break;
    case DLL_THREAD_ATTACH:
    break;
    case DLL_THREAD_DETACH:
    break;
    case DLL_PROCESS_DETACH:
    break;
    }
    return TRUE;
    }

    EXTERN_C __declspec(dllexport) LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
    {
    return CallNextHookEx(NULL, code, wParam, lParam);
    }

    • reversecore 2010/10/04 00:14 댓글주소 | 수정 | 삭제

      안녕하세요.

      iexplore.exe 의 자식 프로세스라면...
      7 버전 이후의 탭으로 생성되는 서브 프로세스를 말씀하시는 건지요?

      제 경우에는 정상적으로 c:\\zzz.txt 파일이 생성되었습니다.
      (WinXP SP3 & Win7)

      혹시 다른 경우를 말씀하시는 거라면 다시 질문 올려주세요~ ^^

      감사합니다.

    • 김종현 2010/10/04 12:22 댓글주소 | 수정 | 삭제

      테스트해 주셔서 감사합니다.
      그런데 정작 중요한 환경을 알려 드리지 않았네요.
      제가 테스트한 IE는 버전8이고
      말씀하신데로 탭으로 생기는 서브프로세스에서는
      전부 문제가 발생하고 있습니다.
      유독 부모프로세스만 문제 없구요.

      정확한 정보는 gmail로 다시 보내드리니
      IE8에서 다시 한번 더 봐 주시길 부탁드립니다.

    • reversecore 2010/10/05 21:21 댓글주소 | 수정 | 삭제

      네, 제 테스트 환경과 동일하시네요~

      전 보통 최신 .NET Framework 가 설치되지 않은 환경에서의 실행을 보장하기 위해 /MT 옵션으로 빌드합니다.

      제가 테스트한 파일을 김종현님 email 로 보내드릴테니 한번 실행해 보시고 결과를 알려 주시기 바랍니다. ^^

      * 보안 프로그램등에서 차단당하는 경우도 있으니 참고하시기 바랍니다.

      감사합니다.

  5. lain32 2010/10/04 19:53 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요 :D
    계속해서 좋은글을 많이 올려주셔서 제가 기초가 부실한 부분들이 있었는데
    많은 도움을 받고 있습니다.
    그리고 정말 자세하게 설명하셔서 너무나도 많은 도움이 되고 있구요.
    이렇게 좋은 자료들을 열정적으로 공개해주셔서 너무 감사드립니다.

    힘내시라고 감사에 글을 적어봤습니다. ㅎㅎ

  6. 2011/02/17 22:47 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2011/02/18 22:10 댓글주소 | 수정 | 삭제

      안녕하세요.

      아주 멋진 크랙미 사이트를 만드셨군요~

      구경 잘 했습니다.

      제 경우에 블로그 홍보를 따로 해본 적은 없습니다. ^^

      이곳에 방문 하시는 분들은 대부분 google 과 naver 검색으로 찾아오시는 분들입니다.

      감사합니다.

    • 2011/02/18 23:02 댓글주소 | 수정 | 삭제

      비밀댓글입니다

  7. 2011/09/02 12:01 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2011/09/03 14:28 댓글주소 | 수정 | 삭제

      안녕하세요.

      비슷한 시간에 디버거를 만드신 다는 질문이 여러개 등록되어있군요. ^^

      OllyDbg 정도의 범용 디버거는 매우 높은 수준의 개발 실력과 시스템에 대한 이해가 필요하다고 생각하시면 됩니다.

      다만 특정 상황에서만 간단히 사용한다면... (Disassembler 도 필요없고 GUI 도 필요없고... 단순한 명령어만 디버깅한다고 했을때... ) 개발 난이도는 상당히 낮출 수 있습니다.

      커널 디버거는 저도 아직 어떻게 만들어야 할지 어렴풋이 감만 잡고 있는 수준입니다. ^^~

      감사합니다.

  8. 비밀 2011/12/21 15:59 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요? ^-^
    책 원고가 완성되었다고 하셨는데.. 개인적으로. 가능하다면.
    Immunity Debugger 에 대해서도 설명을 넣으면 좋을것 같습니다 :)
    제가 알기로 OllyDbg 를 만들던 분이 Immunity 로 이직하시면서 Python 기반으로 개발하신걸로 알고 있습니다. 물론 무료입니다 :)
    http://debugger.immunityinc.com

    수고하세요~

    • reversecore 2012/01/11 07:53 댓글주소 | 수정 | 삭제

      안녕하세요.

      Immunity Debugger 의 python 스크립트는 아주 좋습니다. 저도 많이 써봤지요.

      근데 OllyDbg 개발자는 다른 직업이 있고 그냥 취미로 OllyDbg 를 만든걸로 알고있는데요.

      Immunity Debugger 는 그냥 OllyDbg 클론이구요. 제가 잘 못 알고 있나요?

      감사합니다.

  9. 2012/02/09 20:13 댓글주소 | 수정 | 삭제 | 댓글

    이뮤니티 디버거 같은경우 올리디버거와 같지만 디버거 자체의 라이브러리가 좋다고 말할수 있을거 같아요. 거기다 파이썬 코드로 스크립트 가능하니까 윗분은 뭐 그런 부분을 집어 달라는게 아닐까요?



Windows Vista 부터 적용된 ASLR 기술에 대해서 알아보도록 하겠습니다.


* 참고!
모든 소스 코드는 MS Visual C++ 2008 Express Edition SP1 으로 개발 되었으며, Windows 7 환경에서 테스트 되었습니다.



Windows Kernel Version


아래 보이는 그림은 Windows OS 별로 각 Kernel Version 을 보여주고 있습니다.


<Fig. 1>

Windows Vista 이후부터 Kernel Version 이 '6' 으로 올라갔습니다. (약 7 년 만에 Major 버전이 올라간 셈이죠.)

여기서 설명 드리는 ASLR 기능은 Windows Vista(Kernel Version 6) 이후부터 지원되는 기능입니다.



ASLR(Address Space Layout Randomization)


ASLR 기술은 PE 파일이 메모리에 로딩될 때 ImageBase 값을 (동적으로 계산하여) 랜덤하게 바꾸는 것입니다. 또한 해당 프로세스의 Stack, Heap 의 시작 주소도 실행될 때마다 랜덤하게 바뀝니다.

즉, EXE 파일이 실행될 때 프로세스 메모리 상의 실제 ImageBase 는 매번 틀려지게 되며, DLL 파일이 최초로 로딩 될 때도 역시 메모리 상의 실제 ImageBase 가 달라집니다.

* DLL 파일은 메모리에 최초로 loading 되면 그 후부터는 다른 프로세스에 매핑(mapping)되는 개념이기 때문에 같은 DLL 파일에 대해서 프로세스 별로 매핑 주소는 동일합니다. (DLL 이 unloading 되면 메모리에서 사라진 것이기 때문에 다음 번 loading 시에는 다른 주소에 올라갈 것입니다.)

* 위와 같은 특성을 생각했을 때 시스템 DLL(ntdll.dll, kernel32.dll, etc) 들은 부팅 시마다 매핑 주소는 틀려지겠지만 일단 부팅되면 모든 프로세스에서 같은 주소에 매핑이 됩니다.

MS 에서 PE 파일의 로딩 방식을 이런 식으로 바꾼 이유가 뭘까요?
바로 "보안을 강화" 하기 위해서 입니다.

Windows OS 보안 취약점(보통 버퍼 오버플로우)들은 보통 특정 OS, 특정 모듈, 특정 버전에서만 동작합니다. 그러한 취약점을 노리는 exploit code 에는 특정 메모리 주소(버퍼 오버플로우)가 하드 코딩 되어있습니다. (기존 OS 에서는 버전에 따라 특정 DLL 이 언제나 정해진 주소에 로딩되기 때문이죠.)

따라서 MS 에서는 OS 보안 취약점을 노리는 exploit code 작성을 어렵게 하기 위해서 이와 같이 ASLR 기술을 만들게 된 것입니다.




Visual C++ 2008 (Express Edition)


모든 실행 파일에 대해서 자동으로 ASLR 기술이 적용되는 것은 아닙니다. 위에서 설명 드린 대로 OS 의 Kernel Version 이 6 이상이어야 하고, 개발도구(예: VC++ 2008)에서 /DYNAMICBASE 옵션을 지원해 줘야 합니다.

일반적으로 MS Visual C++ 을 이용하여 실행(PE) 파일을 생성하면 (기본값으로) EXE 파일의 ImageBase 는 0x00400000 값이 되고, DLL 파일의 ImageBase 는 0x10000000 값이 됩니다. 이건 VC++ 2008 도 마찬가지 입니다.

하지만 아래와 같이 VC++ 2008 의 옵션이 디폴트로 /DYNAMICBASE 로 되어 있기 때문에 ASLR 이 자동으로 적용됩니다. 

 
<Fig. 2>

만약에 ASLR 기능을 원하지 않을 때는 아래 그림과 같이 옵션을 /DYNAMICBASE:NO 로 변경하시면 됩니다.

 
<Fig. 3>



ASLR.exe


아주 간단한 콘솔 기반 실행 파일을 만들어 보았습니다.
소스코드는 아래와 같습니다.

 
<Fig. 4>

ASLR.exe 는 /DYNAMICBASE 옵션으로 빌드 하였고, ASLR_no.exe 는 /DYNAMICBASE:NO 옵션으로 빌드 하였습니다.

두 파일을 각각 디버거로 실행 시켜 보겠습니다.

 
<Fig. 5>

위 그림은 ASLR.exe 파일을 디버거로 실행시킨 화면입니다. EP 코드 주소와 스택 주소를 봐주세요. (여러분의 환경이 VISTA 이상이라면 실행시킬 때마다 랜덤한 주소가 나타날 것입니다.)

 
<Fig. 6>

위 그림은 ASLR_no.exe 파일을 디버거로 실행시킨 화면입니다. EP 코드 주소와 스택 주소는 우리가 XP 환경에서 익히 보아오던 주소 그대로 입니다.

이 두 파일들을 PEView 로 살펴보면서 서로 비교해보도록 하겠습니다.


# 섹션 정보


<Fig. 7>

<Fig. 7> 에서 왼쪽이 ASLR.exe 파일이고, 오른쪽이 ASLR_no.exe 파일입니다. ASLR.exe 파일에 “.reloc” 섹션이 추가되어 있는걸 보실 수 있습니다. 보통 EXE 파일에는 “.reloc” 섹션이 없는데, ASLR 기능을 지원하는 파일에는 “.reloc” 섹션이 기본으로 생성되는군요. 이 섹션은 PE Loader 가 참고하긴 하지만 EXE 파일의 경우 실행에 필수적인 섹션은 아닙니다. 즉, EXE 파일의 경우 제거해도 되지요. (아래 링크를 참고하세요.)


가장 중요한 내용은 아래에 설명 드리는 IMAGE_FILE_HEADER \ Characteristics 값과 IMAGE_OPTIONAL_HEADER \ DLL Characteristics 값입니다.


# IMAGE_FILE_HEADER \ Characteristics 


<Fig. 8>

<Fig. 8> 에서 위쪽이 ASLR.exe 파일이고, 아래쪽이 ASLR_no.exe 파일입니다. “.reloc” 섹션을 가지는 ASLR.exe 파일에는 Characteristics 값에 IMAGE_FILE_RELOCS_STRIPPED 플래그(1)가 빠져 있습니다. (ASLR.exe 파일에는 “.reloc” 섹션이 하나 더 있기 때문에 Number of Sections 값이 1 많습니다.)


# IMAGE_OPTIONAL_HEADER \ DLL Characteristics 


<Fig. 9>

<Fig. 9> 에서 위쪽이 ASLR.exe 파일이고, 아래쪽이 ASLR_no.exe 파일입니다. ASLR.exe 파일의 IMAGE_OPTIONAL_HEADER \ DLL Characteristics 값에는 IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 플래그(40)가 세팅되어 있습니다. VC++ 2008 에서  /DYNAMICBASE 옵션을 줘서 빌드하면 이 값이 세팅되는 것입니다. (<Fig. 2> 참고)


이제 여러분은 ASLR 기능을 지원하기 위해서 PE Header 에 추가되어야 할 정보가 어떤 것인지 배웠습니다. 이 정보들을 조작하면서 실습을 해보도록 하겠습니다.



ASLR 기능 취소/추가


# ASLR 기능 취소

ASLR.exe 파일을 hex editor 로 수정하여 ASLR 기능을 취소시켜 보겠습니다.

<Fig. 9> 에 나타난 IMAGE_OPTIONAL_HEADER \ DLL Characteristics 값에서 IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 플래그(40) 를 제거해 주면 ASLR 기능이 간단히 취소됩니다.

Hex editor 에서 DLL Characteristics 값을 8140 에서 8100 으로 변경합니다. (136 옵셋의 WORD 값 - <Fig. 9> 참고)


<Fig. 10>

저장 후 디버거로 실행시켜 봅니다.


<Fig. 11>

위 그림과 같이 ASLR 기능이 취소된 걸 확인 하실 수 있습니다.


# ASLR 기능 추가

이번에는 반대로 ASLR_no.exe 파일을 수정하여 ASLR 기능을 추가시켜 보도록 하겠습니다.

IMAGE_OPTIONAL_HEADER \ DLL Characteristics 값에 IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 플래그(40) 를 추가시켜 보겠습니다.

Hex editor 에서 DLL Characteristics 값을 8100 에서 8140 으로 변경합니다. (136 옵셋의 WORD 값 - <Fig. 9> 참고)


<Fig. 12>

사실 이 작업만으로는 부족합니다. 한가지 작업을 더 해줘야 합니다.

IMAGE_FILE_HEADER \ Characteristics 값에 IMAGE_FILE_RELOCS_STRIPPED 플래그(1) 를 제거해줘야 완벽히 ASLR 기능이 동작합니다. (<Fig. 8> 참고)

Hex editor 에서 Characteristics 값을 0103 에서 0102 로 변경합니다. (EE 옵셋의 WORD 값 - <Fig. 8> 참고)


<Fig. 13>

저장 후 디버거로 실행시켜 봅니다.


<Fig. 14>

위 그림과 같이 ASLR 기능이 추가된 걸 확인 하실 수 있습니다.

위 방법을 사용하시면 기존에 사용하시던 유틸리티들도 (직접 hex editor 로 수정하여) ASLR 기능을 추가시킬 수 있습니다. (Vista 이상에서만 동작함)


여기까지가 제가 준비한 ASLR 관련된 내용입니다.

참고적으로 위에서 잠깐 소개되었던 “.reloc” 섹션을 제거하는 방법에 대해서는 아래 포스트를 참고하세요.



+---+

저는 XP 에서 (Vista 를 거치지 않고) 7 으로 바로 넘어왔기 때문에 Vista 부터 추가된 ASLR 기능을 몰랐습니다. 제 블로그를 방문해주시는 어떤 분께서 문의해 주신 내용을 보고 공부를 하게 되었지요. 덕분에 새로운 걸 많이 배우게 되어서 너무 기쁩니다.

감사합니다.


ReverseCore

위 글이 도움이 되셨다면 추천(VIEW ON) 부탁 드려요~

Trackback Address :: http://www.reversecore.com/trackback/69 관련글 쓰기

  1. reversecore 2010/01/27 21:07 댓글주소 | 수정 | 삭제 | 댓글

    원래는 "Code Injection 기법을 이용한 API 후킹" 강좌를 올려야 할 차례입니다.
    하지만 최근에 Vista/7 에서 추가된 ASLR 기능을 공부하면서 정리한 내용을 먼저 올렸습니다.

    그리고 Vista/7 에서의 DLL Injection 방법도 기존과 달라져서 그것도 한번 정리하고 넘어갈 예정입니다.

    API 후킹은 그 다음으로 밀리겠군요... ^^

  2. l4c05t3 2010/02/01 13:39 댓글주소 | 수정 | 삭제 | 댓글

    좋은글 감사합니다. ^^
    항상 쉽게 쉽게 설명해주셔서 이해가 잘 되요.

  3. sonickaka 2010/02/03 23:22 댓글주소 | 수정 | 삭제 | 댓글

    reversecore.com을 안지 3일되었는데 정말 한시도 눈뜰새가없이 게시한 글들을
    정말 재밌게 보구있습니다.
    인터넷에서 떠돌아다니는 그런 문서와는 차원이다른 성의와 이해도에 감동받았습니다. ㅜㅜ
    좋은 글 언제나 감사합니다. ^^

    • reversecore 2010/02/04 01:11 댓글주소 | 수정 | 삭제

      sonickaka님, 안녕하세요.

      칭찬 너무 감사합니다.

      리버싱 공부 재밌게 하시기 바랍니다.

      자주 들러 주세요~~~

  4. hun 2010/03/25 17:27 댓글주소 | 수정 | 삭제 | 댓글

    reloc섹션이 없으면
    재배치가 되지 않아서 로딩시 프로세스가 뻑이 납니다.
    엔트리 포인트는 랜덤하게 들어오지만
    정작 실행을 하면 실행이 제대로 안됩니다.
    PELodaer가 알아서 해주는 게 아닌 듯 싶습니다.

    • reversecore 2010/03/25 22:42 댓글주소 | 수정 | 삭제

      hun님, 안녕하세요.

      reloc 섹션을 제거했을때 정상적으로 로딩되지 않는 경우가 있었나요?

      제가 예전에 올린 포스트입니다.
      .reloc 섹션을 수동으로 제거하는 내용을 담고 있지요.
      http://www.reversecore.com/70

      만약 이 방법대로 해도 안되는 경우가 있다면 그 파일을 저에게 보내주시기 바랍니다. 저도 한번 보고 싶습니다.

      감사합니다.

  5. hun 2010/03/26 09:08 댓글주소 | 수정 | 삭제 | 댓글

    ASLR기능이 없는 파일을 강제로 기능을 추가 시켜버리면 주소는 랜덤하게 올라오지만 reloc섹션이 없기때문에 재배치가 일어나지 않았고 랜덤하게 로드되지만 무조건 40만번지대의 주소에 있는 함수를 호출했습니다. 그래서 프로그램이 뻑이 났었던 것이였고, ASLR기능이 있는 파일에서 reloc섹션을 날렸더니 ASLR기능이 죽어버렸습니다.

    제가 테스트 했던 파일은 reversecore님께서 간단하게 테스트 하셨던 것처럼 저도
    아주 간단하게 자체제작해서 테스트를 하였습니다.

    • reversecore 2010/03/27 23:54 댓글주소 | 수정 | 삭제

      hun님, 안녕하세요.

      ASLR 기능이 없는 PE 파일에 ASLR 기능을 추가시키려 했는데,
      특정 주소가 하드코딩 되어있어서 에러가 발생한다는 말씀이신거 같네요.
      저도 해당 내용을 확인해 보도록 하겠습니다.
      감사합니다.

  6. G2m2000 2010/03/26 16:27 댓글주소 | 수정 | 삭제 | 댓글

    정말 감사합니다~ 금같은 강좌를...![헠헠]
    그런데 질문 있습니다~
    ASLR기능이 베이스 주소를 랜덤하게 바꾸는 것은 확인 했는데 베이스 주소만이 아닌 내부의 함수들의 주소가 랜덤 배치되는 기능에 대해 혹시 아시나요?
    영 껄적지근하네요 -_-;;

    • reversecore 2010/03/28 00:00 댓글주소 | 수정 | 삭제

      G2m2000님, 안녕하세요.

      내부 함수 주소를 랜덤하게 바꾼다는 말씀이신가요?

      일단 OS 에는 그런기능이 없고요...

      간단한 프로그램이라면 수동으로도 가능합니다만, 크고 복잡한 프로그램에서는 쉽지 않겠네요. 불가능한건 아닙니다만 절대 권해드리고 싶지 않네요.

      함수 사이에 NOP 명령어를 하나만 추가해 보세요. 그 이후의 모든 함수들의 주소가 밀리게 됩니다. 그 함수들을 호출하는 코드를 전부 찾아서 바꿔줘야 해요. 결코 쉽지 않은 작업입니다.

      감사합니다.

  7. hun 2010/03/28 01:22 댓글주소 | 수정 | 삭제 | 댓글

    음. 하드코딩이라고 하기 보다도 재배치가 이루어지지 않았다고 말하는게 맞는 듯 싶습니다.^^

  8. hun 2010/03/30 10:29 댓글주소 | 수정 | 삭제 | 댓글

    음.. 제 의견이 reversecore님께 제대로 전달되지 않는 것 같네요.
    스샷을 몇개 찍어서 메일로 보내드리도록 하겠습니다.

  9. 2010/03/30 11:07 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2010/04/01 10:07 댓글주소 | 수정 | 삭제

      hun님, 안녕하세요.

      와~ 블로그에 설명까지 해주시다니~
      정말 감사합니다.

      역시 ASLR 기능을 추가하려고 하셨군요. ^^
      hun님, 설명 덕분에 모르는 부분에 대해서 잘 알게 되었습니다.

      자주 방문해 주셔서 의견 남겨주세요~ ^^
      감사합니다.

  10. SeHwa 2010/04/21 19:38 댓글주소 | 수정 | 삭제 | 댓글

    사실 유닉스 계열 운영체제에선 워낙 오버플로우 관련 이슈나 그외 등등의 이슈들이 많아서
    이미 전부터 지원하던 것인데, Microsoft 가 늑장을 부렸다고 할 수 있겠네요.
    (저 기술도 보안에는 사실 별 의미가 없는 것이지만 말입니다..)

    좋은 포스팅 감사합니다.

  11. SAMSA 2011/11/18 17:28 댓글주소 | 수정 | 삭제 | 댓글

    이걸 보고 딱 영감을받은게... 요즘 프로그램들은 스택 오버플로우를 막기위해 DEP, ASLR 보안 기법을 많이 사용한다는데..!

    그럼 리버서가 버퍼 오버플로우 공격을 시도할 때 공격이 통용되기 참 힘들겠네요.....................................

    • reversecore 2011/11/18 17:52 댓글주소 | 수정 | 삭제

      안녕하세요.

      네, 버퍼오버플로우 공격이 예전처럼 쉽게되진 않습니다.
      물론 그렇다고 포기할 해커들이 아니지만요. ^^*



hookdbg.cpp 의 DebugLoop() 함수에서 호출하는 세가지 Debug event 핸들러에 대해서 설명드리겠습니다.


<hookdbg.cpp 의 DebugLoop() 함수>

이전 설명은 아래 링크에서 보실 수 있습니다.

API Hooking - 메모장 WriteFile() 후킹 (1)
API Hooking - 메모장 WriteFile() 후킹 (2)



코드 설명


앞서 설명드린 DebugLoop() 함수에서는 세가지 Debug event 를 처리합니다. (위의 코드 그림 참고)

- CREATE_PROCESS_DEBUG_EVENT
- EXIT_PROCESS_DEBUG_EVENT
- EXCEPTION_DEBUG_EVENT

하나씩 살펴보겠습니다.


- EXIT_PROCESS_DEBUG_EVENT

Debuggee 프로세스가 종료될 때 발생하는 이벤트입니다.
위 소스 코드에서는 이 이벤트가 발생하면 Debugger 도 같이 종료하도록 하였습니다.


- CREATE_PROCESS_DEBUG_EVENT -> OnCreateProcessDebugEvent()

CREATE_PROCESS_DEBUG_EVENT 이벤트 핸들러인 OnCreateProcessDebugEvent()를 살펴보겠습니다. 이 함수는 Debuggee 의 프로세스가 시작(혹은 Attach)될 때 호출됩니다.

BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
    // WriteFile() API 주소구하기
    g_pfWriteFile = GetProcAddress(GetModuleHandle("kernel32.dll"),
                                   "WriteFile");

    // API Hook - WriteFile()
    //   첫번째 byte 를 0xCC (INT3)로 변경
    //   (orginal byte 는 백업 – g_chOrgByte)
    g_cpdi = pde->u.CreateProcessInfo; // CREATE_PROCESS_DEBUG_INFO

    ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
                      &g_chOrgByte, sizeof(BYTE), NULL);

    WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
                       &g_chINT3, sizeof(BYTE), NULL);

    return TRUE;
}

먼저 WriteFile() API 의 시작 주소를 구합니다.

주목할 점은 Debuggee 프로세스의 메모리 주소가 아니라 Debugger 프로세스의 메모리 주소를 얻어서 사용한다는 것입니다. Windows OS 에서 System DLL 인 경우 모든 프로세스에서 동일한 주소(가상 메모리)에 로딩 되므로 이렇게 해도 문제없습니다. (참고 : DLL Injection)

g_cpdi 는 CREATE_PROCESS_DEBUG_INFO 구조체 변수입니다.

typedef struct _CREATE_PROCESS_DEBUG_INFO {
  HANDLE                 hFile;
  HANDLE                 hProcess;
  HANDLE                 hThread;

  LPVOID                 lpBaseOfImage;
  DWORD                  dwDebugInfoFileOffset;
  DWORD                  nDebugInfoSize;
  LPVOID                 lpThreadLocalBase;
  LPTHREAD_START_ROUTINE lpStartAddress;
  LPVOID                 lpImageName;
  WORD                   fUnicode;
}CREATE_PROCESS_DEBUG_INFO, *LPCREATE_PROCESS_DEBUG_INFO;

* 출처 : http://msdn.microsoft.com/en-us/library/ms679286(VS.85).aspx

CREATE_PROCESS_DEBUG_INFO 구조체 hProcess 멤버(Debuggee 프로세스 핸들)를 이용하여 WriteFile() API 를 후킹 할 수 있습니다. (Debug Method 가 아니라면 OpenProcess() API 를 통해서 해당 프로세스의 핸들을 얻어야 합니다.)

Debug Method 에서 후킹 방법은 아주 간단합니다.

API 시작 위치에 “BreakPoint 를 설치” 하는 것입니다.

Debuggee 의 프로세스 핸들(Debug 권한을 가짐)을 가지고 있기 때문에 ReadProcessMemory(), WriteProcessmemory() API 를 이용하여 Debuggee 의 프로세스 메모리 공간에 자유롭게 읽기/쓰기 작업을 할 수 있습니다.

위 함수들을 이용해서 Debuggee 에 BreakPoint (INT3 – 0xCC)를 설치할 수 있습니다.

ReadProcessMemory() 를 이용해서 WriteFile() API 의 첫 바이트를 읽어서 g_chOrgByte 변수에 저장합니다. 아래 그림을 보시면 WriteFile() API 의 첫 바이트는 0x6A 입니다.

 
<Fig. 4>

g_chOrgByte 변수에 첫 바이트를 저장하는 이유는 나중에 후킹을 해제(Unhook) 할 때 필요하기 때문입니다.

그 후 WriteProcessMemory() 를 이용해서 이 값을 0xCC 로 바꿔버립니다. (아래 그림 참조)

 
<Fig. 5>

0xCC 는 "INT3" 를 뜻하는 OP code 입니다. 즉, BreakPoint 입니다.

CPU 는 INT3 명령을 만나면 프로그램 실행을 멈추고 예외를 발생시킵니다. 만약 해당 프로그램이 디버깅 중이라면 디버거에게 제어를 넘겨서 처리하도록 합니다.

이것이 일반적으로 디버거에서 BreakPoint 를 설치하는 기본 원리입니다.

이제 Debuggee 프로세스에서 WriteFile() API 가 호출되면 Debugger 에게 제어권이 넘어오게 됩니다.


- EXCEPTION_DEBUG_EVENT -> OnExceptionDebugEvent()

이번에는 EXCEPTION_DEBUG_EVENT 이벤트 핸들러인 OnExceptionDebugEvent()를 살펴보겠습니다. 이 함수가 바로 Debuggee 의 INT3 명령을 처리하게 될 함수입니다.

가장 핵심적인 내용이라 설명을 자세히 해보겠습니다.

BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
    CONTEXT ctx;
    PBYTE lpBuffer = NULL;
    DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
    PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;

    // BreakPoint exception (INT 3) 인 경우

    if( EXCEPTION_BREAKPOINT == per->ExceptionCode )
    {
        // BP 주소가 WriteFile() API 주소인 경우
        if( g_pfWriteFile == per->ExceptionAddress )
        {
            // #1. Unhook
            //   0xCC 로 덮어쓴 부분을 original byte 로 되돌림
            WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
                                  &g_chOrgByte, sizeof(BYTE), NULL);

            // #2. Thread Context 구하기

            ctx.ContextFlags = CONTEXT_CONTROL;
            GetThreadContext(g_cpdi.hThread, &ctx);

            // #3. WriteFile() 의 param 2, 3 값 구하기

            //   함수의 파라미터는 해당 프로세스의 스택에 존재함
            //   param 2 : ESP + 0x8
            //   param 3 : ESP + 0xC
            ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8),
                              &dwAddrOfBuffer, sizeof(DWORD), NULL);
            ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC),
                              &dwNumOfBytesToWrite, sizeof(DWORD), NULL);

            // #4. 임시 버퍼 할당

            lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
            memset(lpBuffer, 0, dwNumOfBytesToWrite+1);

            // #5. WriteFile() 의 버퍼를 임시 버퍼에 복사
            ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
                              lpBuffer, dwNumOfBytesToWrite, NULL);
            printf("\n### original string : %s\n", lpBuffer);
 
           // #6. 소문자 -> 대문자 변환

            for( i = 0; i < dwNumOfBytesToWrite; i++ )
            {
                if( 0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A )
                    lpBuffer[i] -= 0x20;
            }

            printf("\n### converted string : %s\n", lpBuffer);

            // #7. 변환된 버퍼를 WriteFile() 버퍼로 복사

            WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
                               lpBuffer, dwNumOfBytesToWrite, NULL);

            // #8. 임시 버퍼 해제

            free(lpBuffer);

            // #9. Thread Context 의 EIP 를 WriteFile() 시작으로 변경

            //   (현재는 WriteFile() + 1 위치 <– INT3 명령 이후)
            ctx.Eip = (DWORD)g_pfWriteFile;
            SetThreadContext(g_cpdi.hThread, &ctx);

            // #10. Debuggee 프로세스를 진행시킴

            ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
            Sleep(0);

            // #11. API Hook

            WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
                               &g_chINT3, sizeof(BYTE), NULL);

            return TRUE;

        }
    }
    return FALSE;
}

코드 양이 좀 많습니다. 하나씩 설명해 보겠습니다.

처음 if 문에서 EXCEPTION_BREAKPOINT 예외인지 체크합니다. (이 외에도 약 19개의 EXCEPTION 이 더 존재합니다. 참고 : API Hooking - 메모장 WriteFile() 후킹 (1) <list 2>)

그 다음 if 문에서 BreakPoint 가 발생한 주소가 kernel32!WriteFile() 시작 주소와 같은지 체크합니다. (WriteFile() 시작 주소는 OnCreateProcessDebugEvent() 에서 미리 얻어 놓았습니다.)

조건이 만족되면 아래 코드가 실행됩니다.

#1. Unhook

//   0xCC 로 덮어쓴 부분을 original byte 로 되돌림
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, 
                   &g_chOrgByte, sizeof(BYTE), NULL);

먼저 Unhook 을 하는데요, 이유는 소문자->대문자 작업 이후에 WriteFile() 을 정상적인 상태로 호출 시키기 위해서 입니다. ( API Hooking - 메모장 WriteFile() 후킹 (2) 의 "동작 원리 – Unhook & Hook" 설명을 참고하세요.)

Unhook 방법은 Hook 과 마찬가지로 아주 간단합니다. 원래 바이트(g_chOrgByte) 를 써주면 됩니다.

* Unhook 과정이 반드시 필요한 것은 아닙니다. 작업 내용에 따라서 해당 API 호출을 취소할 수 도, 사용자 정의 함수 MyWriteFile() 을 호출할 수 도 있습니다. 상황에 따라서 적절히 변형해서 사용하시기 바랍니다.

#2. Thread Context 구하기

Thread Context 는 제가 블로그에서 처음으로 소개하는 내용인데요, 간단히 설명하면 이렇습니다.
 
모든 프로그램은 프로세스 단위로 실행됩니다. 그리고 프로세스의 실제 명령어 코드는 스레드 단위로 실행됩니다. Windows OS 는 multi-thread 기반이기 때문에 하나의 프로세스에서 여러 스레드가 동시에 실행될 수 있습니다.

멀티 테스킹(multi-tasking)이라는 개념이 결국은 CPU 자원을 시분할(time-slice) 해서 모든 스레드들을 (우선 순위를 고려하여) 하나씩 골고루 실행해 주는 것이지요.

CPU 가 하나의 스레드를 실행하다가 (일정 시간 후) 다른 스레드를 실행하고자 할 때 기존 스레드에서 작업하던 내용을 잘 백업해 두어야 다음 번 실행할 때 제대로 실행할 수 있을 것입니다.

기존 스레드를 실행 하면서 중요한 (다음 실행에 필요한) 정보라면 바로 CPU 레지스터 값입니다. 이 값이 유지되어야 다음 실행에서 정확히 작업을 이어서 할 수 있습니다. (메모리 정보 – 스택 & 힙은 해당 프로세스의 가상 메모리 공간에 있으므로 따로 보호할 필요가 없지요.)

그 스레드의 CPU 레지스터 정보를 저장하는 구조체가 바로 CONTEXT 구조체 입니다. (스레드 하나당 CONTEXT 구조체 하나입니다.)

CONTEXT 구조체 정의를 보겠습니다.

typedef struct _CONTEXT {
    DWORD ContextFlags;

    DWORD   Dr0;
    DWORD   Dr1;
    DWORD   Dr2;
    DWORD   Dr3;
    DWORD   Dr6;
    DWORD   Dr7;

    FLOATING_SAVE_AREA FloatSave;

    DWORD   SegGs;
    DWORD   SegFs;
    DWORD   SegEs;
    DWORD   SegDs;

    DWORD   Edi;
    DWORD   Esi;
    DWORD   Ebx;
    DWORD   Edx;
    DWORD   Ecx;
    DWORD   Eax;

    DWORD   Ebp;
    DWORD   Eip;
    DWORD   SegCs;
    DWORD   EFlags;
    DWORD   Esp;
    DWORD   SegSs;

    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;

* 출처 : MS VC++ winnt.h
 
아래는 스레드의 CONTEXT 를 구하는 코드입니다.

// Thread Context 구하기
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(g_cpdi.hThread, &ctx);

위와 같이 GetThreadContext() API 를 호출하면 ctx 구조체 변수에 해당 스레드(g_cpdi.hThread)의 CONTEXT 를 저장합니다. (g_cpdi.hThread 는 Debuggee 의 메인 스레드 핸들입니다.)

BOOL WINAPI GetThreadContext(
    HANDLE hThread,
    LPCONTEXT lpContext
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms679362(VS.85).aspx

#3. WriteFile() 의 param 2, 3 값 구하기

WriteFile() 호출 시 넘어온 파라미터 중에서 param 2("쓰기버퍼주소"), param 3(버퍼크기) 를 알아내야 합니다.
함수의 파라미터는 스택에 저장되므로 #2 에서 구한 CONTEXT.Esp 멤버를 이용해서 각각의 값을 구합니다.

// 함수의 파라미터는 해당 프로세스의 스택에 존재함
//   param 2 : ESP + 0x8
//   param 3 : ESP + 0xC
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8),
                  &dwAddrOfBuffer, sizeof(DWORD), NULL);
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC), 
                  &dwNumOfBytesToWrite, sizeof(DWORD), NULL);

* 주의! : dwAddrOfBuffer 에 저장되는 "쓰기버퍼" 주소는 Debuggee(notepad.exe) 의 가상메모리 공간의 주소입니다.

* param 2 와 param 3 가 각각 ESP+0x8, ESP+0xC 인 이유는  Stack Frame 을 참고하시기 바랍니다.

#4 ~ #8 소문자 -> 대문자 변환 후 덮어쓰기

"쓰기버퍼" 주소와 크기를 알았기 때문에 이를 Debugger 메모리 공간으로 읽어 들인 후 [소문자 -> 대문자] 변환합니다. 그리고 다시 원래 위치 (Debuggee 의 가상메모리) 에 덮어 써주는 작업입니다.

어렵지 않은 코드이므로 주석을 보시면 쉽게 이해할 수 있을 겁니다.

// #4. 임시 버퍼 할당
lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
memset(lpBuffer, 0, dwNumOfBytesToWrite+1);

// #5. WriteFile() 의 버퍼를 임시 버퍼에 복사
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, 
                  lpBuffer, dwNumOfBytesToWrite, NULL);
printf("\n### original string : %s\n", lpBuffer);

// #6. 소문자 -> 대문자 변환
for( i = 0; i < dwNumOfBytesToWrite; i++ )
{
    if( 0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A )
        lpBuffer[i] -= 0x20;
}
printf("\n### converted string : %s\n", lpBuffer);

// #7. 변환된 버퍼를 WriteFile() 버퍼로 복사

WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, 
                   lpBuffer, dwNumOfBytesToWrite, NULL);

// #8. 임시 버퍼 해제

free(lpBuffer);

#9. Thread Context 의 EIP 를 WriteFile() 시작으로 변경

위의 #2 에서 구한 CONTEXT 에서 Eip 멤버를 WriteFile() 시작 위치로 변경합니다. (EIP 현재 위치는 WriteFile() + 1 입니다. API Hooking - 메모장 WriteFile() 후킹 (2) "# 실행흐름" 설명 참고)

CONTEXT.Eip 멤버를 변경한 후 SetThreadContext() API 를 호출합니다.

//   (현재는 WriteFile() + 1 위치 <– INT3 명령 이후)
ctx.Eip = (DWORD)g_pfWriteFile;
SetThreadContext(g_cpdi.hThread, &ctx);

SetThreadContext() API 입니다.

BOOL WINAPI SetThreadContext(
    HANDLE hThread,
    const CONTEXT *lpContext
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms680632(VS.85).aspx

#10. Debuggee 프로세스를 진행시킴

모든 준비는 끝났습니다.
이제는 정상적인 WriteFile() API 를 호출해야 할 때입니다.

ContinueDebugEvent() API 를 호출하여 Debuggee 프로세스의 실행을 재개 시킵니다.
위 #9 에서 CONTEXT.Eip 를 WriteFile() 시작으로 되돌렸으므로 깔끔한 WriteFile() 호출이 진행 됩니다.

ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
Sleep(0);

* Sleep(0) 을 해준 이유?
원본 코드 그대로 테스트 해보시고요, Sleep(0) 를 주석 처리한 후 테스트 해보시기 바랍니다. (notepad 에 글을 쓰고 빠르게 반복해서 저장해보세요.)

두 경우 어떤 차이가 있으며, 왜 그런 차이가 발생하는지 생각해 보시기 바랍니다. ^^
이유를 파악하신 분께서는 댓글 남겨주세요~

#11. API Hook

다음 번 후킹을 위하여 다시 API Hook 을 설치합니다.
(이 과정이 생략되면 #1 에서 Unhook 되었기 때문에 WriteFile() API 후킹은 완전히 풀린 상태가 되버립니다.)

WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, 
                   &g_chINT3, sizeof(BYTE), NULL);


여기까지 입니다. ^^
이것으로써 DebugLoop() 함수를 상세하게 살펴봤습니다.

실제로 코드를 디버깅 해가면서 각 구조체에 어떤 값이 들어가는지 확인해 보시기 바랍니다. 몇 번만 디버깅 해보시면 저절로 흐름이 파악되실 것입니다.

수고하셨습니다.


* 참고
Windows XP 이상부터는 DebugSetProcessKillOnExit() 를 호출하여 Debugger 가 종료되는(detach) 순간에 Debuggee 가 종료되지 않게 할 수 있습니다.
이때 조심해야 할 점은 Debugger 가 종료되기 전에 unhook 을 해줘야 합니다.
그렇지 않으면 해당 API 시작 부분의 0xCC 가 남아있기 때문에 API 가 호출 시 EXCEPTION_BREAKPOINT 예외가 발생합니다. 이때는 Debugger 가 없기 때문에 Debuggee 프로세스는 종료됩니다.


+---+

지금까지 Debug Method 을 이용한 API Hooking 에 대해서 공부하였으며, 간단한 예제를 통하여 실습을 해보았습니다.

좀 자세히 설명하기 위해 글이 많이 길어졌습니다. 양해 부탁 드립니다.

다음 번에는 DLL Injection & IAT 변경을 이용한 API Hooking 에 대해서 살펴보도록 하겠습니다.

질문 있으시면 댓글 남겨주세요~


ReverseCore

 

Trackback Address :: http://www.reversecore.com/trackback/58 관련글 쓰기

  1. 냥냥 2009/11/04 03:10 댓글주소 | 수정 | 삭제 | 댓글

    잘봤습니다~ ㅎㅎ..
    Sleep() 를 해주는 이유는 동기화가 되지 않아기 때문입니다,
    이것은 반복된 상황이 초래될수 있는것을 의미합니다.
    만약 ContinueDebugEvent(); 를 호출후 NOTEPAD.exe 로 스위칭이 일어나게되면 정상적인 동작을 하게되겠지만, 퀀텀 만큼의 시간은 스레드가 소유할수 있는 최소의 시간이기 때문에, ContinueDebugEvent(); 를 호출후에 곧바로 WriteProcessMemory(); 가 호출된 위험이 존재한다는것을 내포하고 있습니다. 결국 위 함수 2개가 연속으로 실행된다는것은, 또다시 INT 3 이 발생한다는것이고 똑같은 반복작업을 수행하게 된다는것을 의미합니다.
    하지만 Sleep(); 를 쓰게된다면 그 스레드는 즉시 블록킹상태에 들어가게되며 ( 비록 0초라 바로 풀려나오겠지만 ) 이 순간 스위칭이 이루어짐으로서 동기화되지않았기 때문에 생기는 위와 같은 문제는 해결할 수 있습니다.

    가 정답이겠죠? > <//
    항상 좋은 자료 감사드립니다~ ㅎ

    • reversecore 2009/11/04 08:45 댓글주소 | 수정 | 삭제

      냥냥님, 안녕하세요.

      설명을 너무 완벽히 해주셨네요. ^^

      냥냥님께서는 개발 경력이 있는 분 같습니다.
      벌써 구사하시는 용어들에서 개발자의 포쓰가 느껴집니다.
      (사실 더 확실한 증거(?)는 댓글이 달린 시간입니다. ^^)

      감사합니다~

  2. 냥냥 2009/11/04 11:35 댓글주소 | 수정 | 삭제 | 댓글

    아는 잔지식을 총동원했을 뿐이에요 ㅠ_ㅠ.. 전 그저 평범한 학생...
    하하... 다음 강좌가 언제나오나 궁금해서 자꾸 들어와서 그래요..ㅎㅎㅎ;;
    혹시 이번 POC참가하나요..?!

  3. 졸작땜시날새는코더 2009/11/04 14:41 댓글주소 | 수정 | 삭제 | 댓글

    한발늦었네여..
    저가일빠인줄알았더니..

  4. 졸작땜시날새는코더 2009/11/04 14:43 댓글주소 | 수정 | 삭제 | 댓글

    덕분에 어느정도 API후킹부분을 마무리할꺼같습니다.
    dll injection 후 api 후킹이라 dll로 만들어야하는데
    잘되지는 모르겠네여 dll을 이번에 처음접하는거라
    항상감사하고 꾸준히 들리겠습니다.
    신종플루가 유행이던데 조심하시고
    좋은일이 가득하시길 바랍니다 감사합니다^^

  5. 늅늅 2009/11/04 16:30 댓글주소 | 수정 | 삭제 | 댓글

    늘 잘 보고 있습니다~!

    옷..POC 참가하시는가봐요?; 회사가 안보내줘서 못가는데..ㅠ.ㅠ..

    늘 친절한 설명 감사합니다 ^^

    • 냥냥 2009/11/04 17:57 댓글주소 | 수정 | 삭제

      전 학생이라... 가격이 좀 괜찮아서..ㅎㅎㅎ
      처음가보는거라... 경험도 쌓고 하려고요 ㅎㅎ

  6. 몽상가 2009/11/05 10:36 댓글주소 | 수정 | 삭제 | 댓글

    SEH 에 대한 문구에서 안티 디버깅에 대한 내용이 나오는군요. :D
    잘보고있습니다. 수고하세요

    • reversecore 2009/11/05 14:35 댓글주소 | 수정 | 삭제

      몽상가님, 안녕하세요.
      SEH 와 Anti-Debugging 둘 다 흥미로운 주제라서 별도로 포스팅할 예정이구요.

      여러 Anti-Debugging 기법 중에 SEH 를 사용하는 기법이 있는데, 한번 보시면 재미있으실 겁니다.

      감사합니다.

  7. 담배값좀내려 2010/05/05 00:03 댓글주소 | 수정 | 삭제 | 댓글

    잘봤습니다^^두어번 계속 보니깐 감이 잡히네요 ㅎ
    근데 kernel32.dll에서 지원하는 WriteFile() api 말고 다른 api함수를 후킹하고 싶다면 어떻게 해야 하나요?
    INT3 말고 다른 주소를 지정해야 하나요?

  8. 담배값좀내려 2010/05/05 15:24 댓글주소 | 수정 | 삭제 | 댓글

    위에 글 수정이 안되길래 이어서 달겠습니다.
    파일을 저장할때는 kernel32.dll의 WriteFile() API를 사용하는 것을 보고 파일을 열때가 갑자기 궁금해져서 OllyDbg를 이용해서 확인을 해보니 OpenFile() API가 마침 있더라구요
    근데 OpenFile()를 breakpoint로 실행을 시켜보니 1. 파일을 저장할때도, 2. 파일열기버튼이나 파일열기 단축키눌러 OpenFile다이얼로그가 나올때도, 3. OpenFile다이얼로그에서파일을 선택할때에도 ReadFile() API가 호출이 되버리네요 ㅡㅡ;;

    notepad로 파일을 열때 파일 내용 중간에 개인문자열을 저장할수 있지 않을까 해서 WriteFile과는 다르게 ReadFile은 종료할때의 주소를 swap하면 되지 않을까 해서 계속 해보고 있는데 이렇게 파일을 열어서 불러올때만이 아닌 다른 경우에도 ReadFile() API가 호출될때에는 어떻게 해야 할까요?

    지금 이거 할때가 아닌데 안되는거 계속 붙들고 있는거 보면 저도 IT인으로서 소질이 있나봅니닼ㅋㅋ

    • reversecore 2010/05/05 22:04 댓글주소 | 수정 | 삭제

      안녕하세요.

      제가 질문하신 내용을 잘 이해하지 못하고 있습니다. @@

      원하시는 작업이 기존에 있는 파일을 notepad 로 열때 그 내용의 중간에 임의의 문자열을 추가하고 싶으시다는 것인지요?

      죄송하지만 원하시는 작업을 간략히 설명해 주실 수 있으신가요?

  9. 담배값좀내려 2010/05/05 22:36 댓글주소 | 수정 | 삭제 | 댓글

    제가 말이 너무 길었나보네요 ㅎ
    운영자분께서는 kernel32.dll ! WriteFile()에 Debug 후킹을 가하셨잖아요?
    간단히 말해서 전 반대로 ReadFile()을 Debug 후킹을 할 수 있지 않을까 해서 시도해보고 있습니다.

    파일을 읽어올때마다 맨위에 이름을 단다던지 맨뒤에 파일명을 저장한다던지 하는 식으로요 ㅎㅎ
    근데 위에서 말한것처럼 OllyDBug를 통해서 확인을 해보니 WriteFile과는 다르게 OpenFile은
    파일을 불러오기 할때만 호출되는게 아니라 저장할때도 호출되고 그러더라구요;;

    • reversecore 2010/05/07 17:52 댓글주소 | 수정 | 삭제

      안녕하세요.

      원하시는 작업은 바로 notepad 에서 파일을 읽어들일때 ReadFile() API 등을 후킹하여 읽기 버퍼의 내용을 적절히 가공하고 싶으신 거 맞으시죠?

      중간에 OpenFile() API 를 써주셔서 제가 잠시 헷갈렸습니다.
      * 참고로 OpenFile() 은 이미 예젅에 CreateFile() 로 대체되었지요. MS 에서 하위 호환을 위해서 남겨둔 것이지요.

      문제는 ReadFile() 을 후킹하고 싶지만 이 API 가 생각보다 자주 호출 된다는 것이구요? 이렇게 많은 호출중에서 내가 원하는 호출을 어떻게 구별하느냐가 문제의 핵심이 되겠지요?

      네, 말씀하신대로 ReadFile() 은 시스템 DLL 에서 자주 사용됩니다. 원하는 내용을 구현하시려면 먼저 스택의 리턴 주소를 보고 이번의 ReadFile() 호출이 어디서 호출되었는지 확인하는 방법도 있구요. -> notepad.exe 의 .text 섹션에서 호출된 것만 관심을 가지면 됩니다.

      더 편한 방법은 kernel32!CreateFileW() 를 먼저 후킹하신 다음에 파라미터를 잘 살펴보시면서 특정 텍스트 파일이름을 읽기 모드로 열 때를 파악하신 후 그다음 ReadFile() 호출을 유효한 호출로써 간주하고 읽기버퍼를 신경쓰시면 되겠네요. 파일 크기에 따라서 ReadFile() 이 여러번 호출될 수 있습니다.

      질문에 비해 설명이 너무 장황하네요. 죄송합니다.
      설명 능력이 부족합니다. 백문이 불여일견이라는 말을 정말 백배 공감합니다.

      다른 궁금한점 있으시면 다시 올려주세요.

      감사합니다.

  10. 담배값좀내려 2010/07/26 22:09 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요^^ 몇가지 질문사항이 있어서 다시 댓글을 달아 봅니다.
    디버깅과 리버싱에 관한 지식이 부족하여 여러 책자나 관리자분의 디버깅쪽
    강좌 등 을 보며 조금씩 공부를 해나가는 중입니다 ^^

    위에서 말씀하신대로 ReadFile을 분석하던 중 ReadFile의 내부적으로 ntdll.dll의 ntReadFile을
    통해 메모리로 파일의 내용을 읽어오더군요. 그런데 Drag&Drop을 통해서 파일을 열었을대에는
    ReadFile이 아예 호출되지도 않더라구요....

    그래서 찾아낸 것이 CreateFile인데 파일을 열때나 Drag&Drop을 통해서 파일을 열때의 전달인자를 보니
    세번째전달인자 = FILE_SHARE_READ | FILE_SHARE_WRITE,
    여섯번째전달인자 = FILE_ATTRIBUTE_NORMAL,
    일곱번째전달인자 = NULL,
    일 때에는 파일을 열때더군요....그런데 그 후로 파일의 내용을 메모리로 읽어들인다음
    메모장을 통해 화면으로 뿌려주는 부분을 찾기가 굉장히 어렵네요 ㅎㅎ;;

    이런 경우엔 파일을 열때에 파일의 내용이 메모리로 올라오는 경우는 대체 언제쯤일까요...
    리버싱경험이 전무하다보니 혼자서 알아내기가 생각보다 쉽지 않네요;;
    혹 ReadFile외에 메모장의 Edit부분에 파일의 내용을 표시할수 있는 TextOut이나 SetWindowText같은 다른 API가 없을까요?
    (TextOut나 SetWindowText는 안되더라구요 ㅎㅎ;)

    • reversecore 2010/07/28 16:39 댓글주소 | 수정 | 삭제

      안녕하세요.

      메모장에서 파일을 열때 CreateFile() -> CreateFileMapping() -> MapViewOfFile() API 를 사용합니다. 즉 메모리 맵 파일 기법을 사용하지요. 그래서 ReadFile() 이 호출되지 않고도 내용을 볼 수 있던 것입니다. 참고하시기 바랍니다.

      감사합니다.

  11. 2011/04/29 15:15 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2011/05/03 00:26 댓글주소 | 수정 | 삭제

      안녕하세요.

      그림 한 두개 정도가 아니라 전부를 가져가시는 것은 안되구요.

      그냥 필요하실 때 방문해 주시면 안될까요? ^^

      감사합니다.

  12. lunaticapple 2011/07/06 16:03 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요? 윈도우 프로그래밍을 처음해봅니다.^^ 제가 위방법으로 readfile함수에 대해
    break를 걸어보았습니다. winamp에서 음악을 플레이할때 readfile을 후킹해서 좀 가져올
    정보가 있어서요^^. 현재 OnExceptionDebugEvent 함수부분은 별도로 아무것도 한거 없이
    print문하나찍고 unhook, 프로세스 재개, hook 의 방식으로 되어있습니다. 그런데 이게,,
    print문 두번정도 찍고(readfile 후킹 두번) 더이상 반응을 하지 않습니다. winamp가 죽은것 같기도 한데 실제 프린트를 찍어보면 OnExceptionDebugEvent함수는 계속 탑니다. 그런데 readfile은 더이상 불리지도 않고 winamp는 소리를 내지 않습니다,, .. 어떻게 디버깅을 해야할지
    몰라서,, 혹시 짐작가시는게 있으시면 조언 부탁드리겠습니다.

    • reversecore 2011/07/14 05:53 댓글주소 | 수정 | 삭제

      안녕하세요.

      위 실습 예제 코드는 동작 원리를 소개하기 위한 목적이기 때문에 코드 가독성을 위해서 예외처리가 빠져 있습니다.

      의심되는 부분이 몇 군데 있기는 한데 비슷한 환경에서 디버깅을 해봐야 정확한 원인을 파악할 수 있을 것 같습니다.

      제가 직접 해보고 다시 댓글 달아드릴께요.

      감사합니다.

  13. LeapOfFaith 2011/07/11 16:43 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요
    조용히 맨날 보면서 따라하다가 막히는 부분이 있어서 여쭤봅니다.
    <fig.4>와 <fig.5>부분인데요.
    notepad에 hookdbg.exe가 붙어있는 상태에서 올리디버거로 notepad의 저기 저 7C7E0E27부분이 바뀌는 것을 확인하려고 했습니다만, 올리디버거가 notepad에 붙질 못하네요.
    아마 hookdbg.exe가 디버거로 이미 notepad에 붙어있어서 올리가 못붙는거 같은데..
    (마찬가지로 올리를 notepad에 먼저 붙인 후, hookdbg.exe를 notepad에 붙이려 시도하면 hookdbg.exe는 DebugActiveProcess<notepad의 PID> failed!!!
    Error Code = 87
    을 출력하고 붙질 못합니다.
    검색해보니 GetLastError() 리턴 값 87은 ERROR_INVALID_PARAMETER라는 군요.)
    어떻게 <fig.4>와 <fig.5>처럼 올리로 지켜보셨는지 궁금합니다..ㅠㅠ

    • reversecore 2011/07/13 21:23 댓글주소 | 수정 | 삭제

      안녕하세요.

      말씀하신 것처럼 하나의 프로세스를 동시에 2개의 디버거로 디버깅을 할 수 없습니다.

      문의하신 <fig. 4> 와 <fig. 5> 는 제가 동작원리를 보여드리기 위해서 OllyDbg 로 notepad 를 디버깅해서 캡쳐한 것입니다. 관련된 설명이 부족해서 착오가 있으셨던것 같습니다.

      감사합니다.

  14. polaris 2011/11/10 00:25 댓글주소 | 수정 | 삭제 | 댓글

    아주 재미나게 잘 보앗습니다. 정말 이런 정보를 공유해 주셔서 감사합니다.
    그런데 한가지 문제가 있어서 이렇게 글을 올립니다.
    Windows7에서 실험해봣는데요 XP에서 할때하고 좀 다른게 있더라구요...
    XP에서는 아무런 문제없이 제대로 되던데 Windows7에서는 Debugger를 실행시킬때 Notepad가 잠시 멈추고 보관할때도 파일보관대화창이 제때에 반응하지 안더니 확인단추를 누르면 아주 응답이 없어집니다.
    Windows7에서는 무엇이 좀 다른거 같은데 여기에 대해서 어떻게 생각하시는지...
    조언부탁드립니다.

  15. 익명 2012/01/21 19:21 댓글주소 | 수정 | 삭제 | 댓글

    thread context에 대해 윗글 링크 따라가 보니 다음과 같이 적혀있더군요
    Protected Processes
    Protected processes enhance support for Digital Rights Management. The system restricts access to protected processes and the threads of protected processes.

    Windows Server 2003 and Windows XP/2000: Protected processes were added starting with Windows Vista.
    The following specific access rights are not allowed from a process to the threads of a protected process:

    THREAD_ALL_ACCESS
    THREAD_DIRECT_IMPERSONATION
    THREAD_GET_CONTEXT
    THREAD_IMPERSONATE
    THREAD_QUERY_INFORMATION
    THREAD_SET_CONTEXT
    THREAD_SET_INFORMATION
    THREAD_SET_TOKEN
    THREAD_TERMINATE
    The THREAD_QUERY_LIMITED_INFORMATION right was introduced to provide access to a subset of the information available through THREAD_QUERY_INFORMATION.

    출처: http://msdn.microsoft.com/en-us/library/ms686769(v=vs.85).aspx



Debug 방식의 API Hooking 실습 예제 코드(hookdbg.cpp)의 동작 원리를 살펴 보고 코드를 자세히 분석해보겠습니다.


<hookdbg.cpp 의 DebugLoop() 함수>

작업 목표 : "notepad 에서 파일 저장할 때 모든 소문자를 대문자로 변경함"

* 참고
API Hooking - 메모장 WriteFile() 후킹 (1)



동작 원리



이해를 돕기 위해 먼저 동작 원리를 설명 드리겠습니다.

notepad 에서 뭔가를 파일에 저장하려면 kernel32!WriteFile() API 를 사용할 거라고 가정합니다. 
(일단 가정이 맞는지 확인을 해봐야겠군요.)


# 스택(Stack)

WriteFile() API 정의를 봐주세요.

BOOL WriteFile(    
    HANDLE hFile,
    LPCVOID lpBuffer, 
    DWORD nNumberOfBytesToWrite,
    LPDWORD lpNumberOfBytesWritten,
    LPOVERLAPPED lpOverlapped
);

* 출처 : http://msdn.microsoft.com/en-us/library/aa365747(VS.85).aspx

두 번째 파라미터(lpBuffer)가 “쓰기 버퍼” 이고, 세 번째 파라미터(nNuberOfBytesToWrite)가 “써야 할 크기” 입니다. 함수의 파라미터는 스택에 역순으로 저장된다는 사실을 기억해주세요. (-> 참고 : Stack Frame)

OllyDbg 를 이용해 실제로 notepad 를 디버깅 하면서 확인해 보겠습니다.

<Fig. 1>

위 그림처럼 OllyDbg 를 이용해서 notepad 를 열어서 kernel32!WriteFile() API 에 BreakPoint 를 설치한 후 실행[F9] 시킵니다.

그리고 테스트로 아래와 같이 적당한 문자열을 입력한 후 적당한 파일 이름으로 저장합니다.

<Fig. 2>

예상대로 BreakPoint 를 설치한 kernel32!WriteFile() 에 멈춥니다.

이 상태에서 스택을 살펴보겠습니다.


<Fig. 3>

현재 스택(ESP : 7FA7C)에는 리턴 주소(01004C30)가 있고, ESP+8 (7FA84) 에 "쓰기버퍼" 주소(0E7310)가 저장되어 있습니다. 바로 그 "쓰기버퍼" 주소(0E7310)로 가면 notepad 에서 저장하려고 하는 문자열("ReverseCore") 이 보입니다.

따라서 WriteFile() API 를 후킹해서 "쓰기버퍼"를 제가 원하는 문자열로 덮어쓰면 목표 달성입니다.


# 실행흐름

이제 Debuggee 의 프로세스 메모리 어느 부분을 수정해야 하는지 알았습니다.
그 다음은 WriteFile() 을 정상적으로 실행시켜서 제가 수정한 문자열이 파일에 저장되도록 하면 됩니다.

지금 우린 Debug Method 를 사용해서 API 후킹을 하고 있습니다.

이전 포스트에서 소개한 hookdbg.exe 를 이용하여 WriteFile() API 시작 주소에 BP (INT3) 를 설치하면, Debuggee(notepad.exe) 에서 파일을 저장 할 때 Debugger(hookdbg.exe) 에게 EXCEPTION_BREAKPOINT 이벤트가 올 것입니다.

그렇다면 그 순간 Debuggee(notepad.exe) 의 EIP 값은 얼마일까요?

얼핏 생각하면 WriteFile() API 시작주소(7C7E0E27) 라고 생각하기 쉽습니다.
하지만 실제 EIP 는 WriteFile() API 시작주소(7C7E0E27) + 1 = 7C7E0E28 입니다.

그 이유는 이렇습니다.

먼저 BP 를 WriteFile() API 시작 주소에 설치하였지요?
Debuggee(notepad.exe) 내부에서 WriteFile() 이 호출되면 시작주소인 7C7E0E27 에 있는 INT3 (0xCC) 명령어를 만나게 됩니다.
이 명령어(BreakPoint – INT3)를 실행 하면 EIP 는 INT3 명령어 길이(1 byte)만큼 늘어나게 됩니다.
그 후에 제어가 Debugger(hookdbg.exe) 로 넘어오게 되는 것입니다. (Debugger - Debuggee 관계에서 Debuggee 에서 발생한 EXCEPTION_BREAKPOINT 예외는 Debugger 에서 처리하도록 되어 있기 때문입니다.)

따라서 "쓰기 버퍼" 에 있는 내용을 수정해서 덮어쓴 다음에 EIP 를 WriteFile() API 시작 주소로 되돌려서 실행 시켜야 합니다.


# Unhook & Hook

또 하나의 문제는 단순히 실행 흐름을 WriteFile() 시작 주소로 되돌리기만 해서는 똑 같은 INT3 명령을 만나게 되기 때문에 무한루프(EXCEPTION_BREAKPOINT 발생)에 빠지게 됩니다.

이러한 무한루프에 빠지지 않으려면 WriteFile() API 시작 주소에 설치한 BP 를 제거해야 합니다.
즉, 0xCC 를 원래 original byte 인 0x6A 로 변경해줘야 합니다. (original byte 는 API 후킹 전에 미리 저장해 둡니다.)

이것을 Unhook 이라고 합니다. API 후킹을 풀어버리는 것이지요.

"쓰기버퍼" 를 덮어쓰고 WriteFile() API 코드를 정상으로 되돌린 후 EIP 값을 WriteFile() API 로 변경하면 드디어 변경된 문자열이 파일에 저장됩니다. 이것이 hookdbg.cpp 의 동작 원리입니다.

후킹이 1 회성이면 여기서 끝이고요, 지속적인 후킹을 원하시면 다시 BP 를 설치합니다.


설명만 읽어서는 잘 이해되지 않을 수 있습니다.
아래 소스 코드(hookdbg.cpp)를 보면서 설명 드리겠습니다.

* 참고
OllyDbg 같이 범용적인 디버거의 경우는 <Fig. 3> 에서 보듯이 EIP 값이 BP 설치 주소와 같고, INT3(0xCC) 명령어가 보이지 않습니다. 이것은 편리한 사용자 인터페이스를 위하여 OllyDbg 에서 제공하는 기능입니다.

즉, INT3(0xCC) 를 덮어 쓴 후 이 명령어를 실행하게 되면 EIP 가 1 증가합니다. 그때 OllyDbg 에서 0xCC 를 원래 byte 로 복원하고 EIP 도 보정해 주는 것이지요. (결과적으로 구현 알고리즘은 위 설명과 동일합니다.)




코드 설명




첨부된 hookdbg.cpp 의 코드를 살펴보도록 하겠습니다.


# main()

#include "windows.h"
#include "stdio.h"

LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xCC, g_chOrgByte = 0;

int main(int argc, char* argv[])
{
    DWORD dwPID;

    if( argc != 2 )
    {
        printf("\nUSAGE : hookdbg.exe <pid>\n");
        return 1;
    }

    // Attach Process
    dwPID = atoi(argv[1]);
    if( !DebugActiveProcess(dwPID) )
    {
        printf("DebugActiveProcess(%d) failed!!!\n"
              "Error Code = %d\n", dwPID, GetLastError());
        return 1;
    }

    // 디버거 루프
    DebugLoop();

    return 0;
}


main() 함수의 코드는 간단합니다.

프로그램 실행 파라미터로 API 후킹 하려는 프로세스의 PID 를 받습니다.

그 후 DebugActiveProcess() API 를 통해서 실행중인 프로세스에 attach 하여 디버깅을 시작합니다. (위에서 입력한 PID 를 파라미터로 넘겨줍니다.)

BOOL WINAPI DebugActiveProcess(
    DWORD dwProcessId
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms679295(VS.85).aspx


그 후 DebugLoop() 함수로 들어가서 Debuggee 로부터 오는 Debug event 를 처리합니다.

* 또 다른 디버깅 시작 방법은 CreateProcess() API 를 사용하여 아예 해당 프로세스를 Debug 모드로 실행시키는 방법이 있습니다. 이와 관련된 설명은 MSDN 을 참고하세요.



# DebugLoop()

void DebugLoop()
{
    DEBUG_EVENT de;
    DWORD dwContinueStatus;

    // Debuggee 로부터 event 가 발생할 때까지 기다림
    while( WaitForDebugEvent(&de, INFINITE) )
    {
        dwContinueStatus = DBG_CONTINUE;

        // Debuggee 프로세스 생성 혹은 attach 이벤트
        if( CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
        {
            OnCreateProcessDebugEvent(&de);
        }
        // 예외 이벤트
        else if( EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode )
        {
            if( OnExceptionDebugEvent(&de) )
                continue;
        }
        // Debuggee 프로세스 종료 이벤트
        else if( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
        {
            // Debuggee 종료 -> Debugger 종료
            break;
        }

        // Debuggee 의 실행을 재개시킴
        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    }
}

DebugLoop() 함수는 마치 윈도우 프로시저 함수(WndProc)와 유사하게 동작합니다.
Debuggee 로부터 발생하는 event 를 받아서 처리한 후 Debuggee 의 실행을 재개 시키는 역할입니다.

역시 간단한 코드이므로 주석을 보시면 쉽게 이해하실 수 있으실 것입니다.

그 중에서 몇 가지 중요 API 들을 알아보겠습니다.

WaitForDebugEvent() API 는 이름 그대로 Debuggee 로부터 Debug event 가 발생할 때까지 기다리는 함수입니다. (WaitForSingleObject() API 와 비슷하게 동작합니다.)

BOOL WINAPI WaitForDebugEvent(
    LPDEBUG_EVENT lpDebugEvent,
    DWORD dwMilliseconds
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms681423(VS.85).aspx

Debug event 가 발생하면 WaitForDebugEvent() API 는 첫 번째 파라미터인 de 변수(DEBUG_EVENT 구조체 객체)에 해당 event 에 대한 정보를 설정한 후 즉시 리턴합니다.
DEBUG_EVENT 구조체 정의는 아래와 같습니다.

typedef struct _DEBUG_EVENT {
    DWORD dwDebugEventCode;
    DWORD dwProcessId;
    DWORD dwThreadId;
    union {
        EXCEPTION_DEBUG_INFO       Exception;
        CREATE_THREAD_DEBUG_INFO   CreateThread;
        CREATE_PROCESS_DEBUG_INFO  CreateProcessInfo;
        EXIT_THREAD_DEBUG_INFO     ExitThread;
        EXIT_PROCESS_DEBUG_INFO    ExitProcess;
        LOAD_DLL_DEBUG_INFO        LoadDll;
        UNLOAD_DLL_DEBUG_INFO      UnloadDll;
        OUTPUT_DEBUG_STRING_INFO   DebugString;
        RIP_INFO                   RipInfo;
    } u;
} DEBUG_EVENT, *LPDEBUG_EVENT;

* 출처 : http://msdn.microsoft.com/en-us/library/ms679308(VS.85).aspx

이전 포스트에서 Debug event 는 9 가지 종류가 있다고 설명 드렸습니다.
(참고 : API Hooking - 메모장 WriteFile() 후킹 (1) <list 1>)

DEBUG_EVENT.dwDebugEventCode 멤버에 9가지 event 종류 중 하나가 세팅되며, 해당 event 종류에 따라 적절한 DEBUG_EVENT.u (유니온) 멤버가 세팅 됩니다. (DEBUG_EVENT.u 유니온 멤버 역시 event 종류 개수에 맞춰서 내부에 9 개의 구조체로 구성되어 있습니다.)

예) Exception event 인 경우 => dwDebugEventCode 멤버가 EXCEPTION_DEBUG_EVENT 로 세팅 되고, u.Exception 구조체가 세팅됩니다.

ContinueDebugEvent() API 는 Debuggee 의 실행을 재개 시키는 함수입니다.

BOOL WINAPI ContinueDebugEvent(
    DWORD dwProcessId,
    DWORD dwThreadId,
    DWORD dwContinueStatus
);

* 출처 : http://msdn.microsoft.com/en-us/library/ms679285(VS.85).aspx

ContinueDebugEvent() API 의 마지막 파라미터인 dwContinueStatus 는 DBG_CONTINUE 또는 DBG_EXCEPTION_NOT_HANDLED 중에서 하나의 값을 가질 수 있습니다.

정상적으로 처리된 경우 DBG_CONTINUE 로 세팅 하고, 처리하지 못했거나 어플리케이션의 SEH(Structured Exception Handler) 에서 처리하길 원할 때는 DBG_EXCEPTION_NOT_HANDLED 로 세팅합니다.

* SEH(Structured Exception Handler) 는 Windows 에서 제공하는 예외 처리 메커니즘입니다. 이를 이용한 예외 처리와 안티 디버깅 기법들에 대해서는 향후 따로 다루어 보도록 하겠습니다.

위의 DebugLoop() 에서는 3 가지의 Debug event 만 처리합니다. (CREATE_PROCESS_DEBUG_EVENT, EXIT_PROCESS_DEBUG_EVENT, EXCEPTION_DEBUG_EVENT)


다음 포스트에서 위 Debug event 에 대한 상세한 코드 설명이 이어집니다.

API Hooking - 메모장 WriteFile() 후킹 (3)


+---+

ReverseCore

 

Trackback Address :: http://www.reversecore.com/trackback/57 관련글 쓰기

  1. alex 2009/11/12 13:20 댓글주소 | 수정 | 삭제 | 댓글

    좋은 글 잘 보고 갑니다!

  2. reversingk 2011/04/27 17:08 댓글주소 | 수정 | 삭제 | 댓글

    항상 감사 합니다.
    코어님 덕에 리버싱 공부하는 것이 즐겁습니다 ^^



앞서 소개해 드린 각 기법들 중에서 Debug 기법에 대한 설명입니다.

실습으로 메모장의 kernel32!WriteFile() API 를 후킹하여 기존과는 다른 동작을 하도록 만들어 보겠습니다.


<실습 예제 – WriteFile() API hooking>

API Hooking 의 기본 설명은 아래 글을 참고하세요.

API Hooking - 리버싱의 '꽃'
API Hooking - Tech Map



Debug Technique




<Fig. 1>

Debug 방식의 API 후킹을 설명 드리겠습니다. (위의 Tech Map 에서 빨간색 표시 부분을 참고하세요)

이 방식의 장점은 후킹을 위해서 '디버깅'을 사용하므로 좀 더 interactive 한 후킹을 수행할 수 있습니다. 즉, 간단한 GUI 를 제공하여 후킹 대상 프로그램의 실행을 제어하고, 메모리를 자유롭게 사용할 수 있습니다.

하지만 먼저 Debugger 구조에 대한 이해가 필요합니다.



Debugger 설명


# 용어

간단한 용어 정리부터 하겠습니다.

Debugger – debugging tool
Debuggee – application to debug

# 디버거 기능

Debugger 의 기능은 Debuggee 가 올바르게 실행되는지 확인하고 (예상치 못한) 프로그램의 오류를 발견하는 것입니다.

Debugger 는 Debuggee 의 명령어 instruction 을 하나씩 실행 가능하며, 레지스터와 메모리에 대한 모든 접근 권한을 가집니다.

# 디버거 동작 원리

일단 Debugger 프로세스로 등록되면 OS 는 Debuggee 에게 debug event가 발생할 때 Debuggee 의 실행을 멈추고 해당 event 를 Debugger 에게 통보합니다. Debugger 는 해당 event 에 대해 적절한 처리를 한 후 Debuggee 의 실행을 재개합니다.

* 일반적인 예외(Exception)도 Debug event 에 해당합니다.
* 만약 해당 프로세스가 디버깅 중이 아니었다면 debug event 는 자체 예외처리 아니면 OS 의 예외처리 루틴에서 처리됩니다.
* Debugger 는 debug event 중에서 처리할 수 없거나 관심 없는 event 들은 OS 가 처리하도록 만들어 줍니다.

아래 그림은 위 설명을 도식화 한 것입니다.


<Fig. 2>

# Debug event

Debug event 입니다.

EXCEPTION_DEBUG_EVENT     
CREATE_THREAD_DEBUG_EVENT  
CREATE_PROCESS_DEBUG_EVENT 
EXIT_THREAD_DEBUG_EVENT     
EXIT_PROCESS_DEBUG_EVENT   
LOAD_DLL_DEBUG_EVENT       
UNLOAD_DLL_DEBUG_EVENT     
OUTPUT_DEBUG_STRING_EVENT  
RIP_EVENT

* 출처 : http://msdn.microsoft.com/en-us/library/ms679302(VS.85).aspx

<List 1>

위 Debug event 중에서 Debugging 관련된 event 는 아래에 표시된 EXCEPTION_DEBUG_EVENT 입니다.

EXCEPTION_ACCESS_VIOLATION
EXCEPTION_ARRAY_BOUNDS_EXCEEDED
EXCEPTION_BREAKPOINT
EXCEPTION_DATATYPE_MISALIGNMENT
EXCEPTION_FLT_DENORMAL_OPERAND
EXCEPTION_FLT_DIVIDE_BY_ZERO
EXCEPTION_FLT_INEXACT_RESULT
EXCEPTION_FLT_INVALID_OPERATION
EXCEPTION_FLT_OVERFLOW
EXCEPTION_FLT_STACK_CHECK
EXCEPTION_FLT_UNDERFLOW
EXCEPTION_ILLEGAL_INSTRUCTION
EXCEPTION_IN_PAGE_ERROR
EXCEPTION_INT_DIVIDE_BY_ZERO
EXCEPTION_INT_OVERFLOW
EXCEPTION_INVALID_DISPOSITION
EXCEPTION_NONCONTINUABLE_EXCEPTION
EXCEPTION_PRIV_INSTRUCTION
EXCEPTION_SINGLE_STEP
EXCEPTION_STACK_OVERFLOW

* 출처 : http://msdn.microsoft.com/en-us/library/aa363082(VS.85).aspx

<List 2>

각종 예외(Exception) 중에서 Debugger 가 반드시 처리해야 하는 예외는 바로 EXCEPTION_BREAKPOINT 예외입니다.

BreakPoint 는 어셈블리 명령어 "INT3" 이며, IA-32 Op code 는 0xCC 입니다. 코드 디버깅 중에 INT3 명령어를 만나면 Debugger 에게 EXCEPTION_BREAKPOINT 예외 이벤트가 날아갑니다.

Debugger 에서 BreakPoint 를 구현하는 방법은 간단합니다.

BreakPoint 를 설치하기 원하는 코드의 메모리 시작 주소에서 1 byte 를 0xCC 로 바꿔 치는 것입니다. 디버깅을 계속 진행하고 싶을 때는 다시 원래 값으로 복원시키고 실행해줍니다.

Debug 방식의 API Hooking 은 이와 같은 BreakPoint 의 특성을 이용하는 것입니다.



API Hooking - Debug Method


Debug 기법을 통한 API Hooking 에 대해 좀 더 자세히 설명 드리겠습니다.

기본적인 아이디어는 Debugger-Debuggee 관계를 가진 상태에서 Debuggee 의 API 시작 부분을 0xCC 로 바꿔 제어를 Debugger 로 가져온 상태에서 원하는 작업을 수행한 후 Debuggee 를 다시 실행상태로 바꾸는 것입니다.

작업 흐름은 아래와 같습니다.

- 후킹을 원하는 프로세스에 ‘attach’ 하여 Debuggee 로 만듦
- Hook : API 시작 주소의 첫 바이트를 0xCC 로 변경
- 해당 API 가 호출되면 제어는 Debugger 에게 넘어옴
- 원하는 작업을 수행(파라미터, 리턴 값 조작 등)
- Unhook : Debuggee 의 0xCC 를 원래대로 복원시킴 (<– API 의 정상 실행을 위해)
- 해당 API 실행 (0xCC 가 빠진 정상적인 상태)
- Hook : 다시 0xCC 로 바꿈 (<– 지속적인 후킹을 위해)
- Debuggee 에게 제어를 되돌려줌


위 방식은 가장 간단한 경우를 소개한 것입니다.

이걸 기준으로 다양하게 변형할 수 있습니다. 가령 original API 를 호출 하지 않을 수 도 있고, 사용자가 제공한 custom API 를 호출 할 수 도 있고, 한번만 후킹 할 수도 있고, 여러 번 후킹 할 수도 있습니다.

작업 목적에 따라서 알맞게 변형해서 사용하시면 됩니다.



Notepad.exe 의 WriteFile() API Hooking


지금 까지 공부한 내용을 바탕으로 실제 코드를 보면서 실습을 해보도록 하겠습니다.

작업할 내용은 Notepad.exe 의 WriteFile() API 후킹입니다.
입력된 파라미터를 조작하여 소문자로 입력된 내용을 전부 대문자로 바꿔 보겠습니다.

즉, Notepad 에서 입력된 모든 소문자는 파일로 저장되는 순간에 대문자로 변경되어 저장됩니다.

Notepad.exe 를 실행시킨 후 PID 를 알아냅니다.


<Fig. 3>

첨부된 후킹 프로그램(hookdbg.exe)을 실행합니다.

hookdbg.exe 는 콘솔 기반 프로그램이며 실행 파라미터로 후킹 할 프로세스의 PID 를 넘겨 받습니다.



<Fig. 4>

위 그림과 같이 hookdbg.exe 를 실행하면 PID 1688 에 해당하는 notepad 프로세스의 WriteFile() API 후킹이 시작됩니다.

그리고 notepad 에 아무 글자나 입력해 보세요.


<Fig. 5>

입력을 마치셨으면 저장을 해주세요.


<Fig. 6>

저장을 마치면 notepad 화면에는 아무런 변화가 일어나지 않습니다. (WriteFile() API 만 후킹 했다는 사실을 기억해 주세요.)

notepad 를 종료해 주시고, hookdbg 프로그램을 봐주세요.


<Fig. 7>

위 그림을 보시면 “original string” 에는 제가 입력한 문자열이 나타나고, “converted string” 에는 변경된(소문자 -> 대문자) 문자열이 나타납니다.

이것은 hookdbg.exe 프로그램 내부에서 후킹의 진행과정을 표시하기 위해서 출력하는 문자열입니다.

실제로 대문자로 저장되었는지 파일을 열어서 확인합니다.


<Fig. 8>

정확히 모든 소문자가 대문자로 변경되어서 저장되었습니다.

이 예제는 아주 간단한 기능을 가지고 있지만 Debug Method 에 대한 기본적인 개념을 잘 설명해줄 수 있습니다.

다음 포스트에서 hookdbg.exe 의 실제 코드를 상세히 살펴보도록 하겠습니다.

API Hooking - 메모장 WriteFile() 후킹 (2)


+---+

ReverseCore

 

Trackback Address :: http://www.reversecore.com/trackback/56 관련글 쓰기

  1. 늅늅 2009/10/28 13:58 댓글주소 | 수정 | 삭제 | 댓글

    언제나 친절하게 설명 되어있는 강좌 잘 보고 있습니다 ^^

  2. 냥냥 2009/10/28 23:59 댓글주소 | 수정 | 삭제 | 댓글

    와... 정말 많은걸 보고 배워가요 ^^*
    좋은글 앞으로도.. 많이 많이 very 많이 많이 부탁드릴께요..ㅎㅎ
    댓글중에 회사원이라고 들었는데요... 하는 일이 무엇인가요..?
    전 학생이라... IT업계의 일에는 무엇이 있는지 궁금해서요... 프라이버시라면... 말 안해주셔도되요~
    히힛~..~ 양질의 좋은 자료 부탁드리겠습니다~ ㅎㅎ.. 근데 퍼가도 되는건가요..?!

    • ReverseCore 2009/10/29 06:14 댓글주소 | 수정 | 삭제

      냥냥님, 안녕하세요.

      도움이 되셨다니 기쁘네요~ ^^

      그냥 평범한 IT 관련 회사원이구요, 개발과 리버싱에 많은 관심을 가지고 있지요.

      퍼가지는 마시구요, 링크만 해주세요~
      (미리 물어봐 주셔서 감사합니다.)

  3. 이승철 2009/10/29 11:07 댓글주소 | 수정 | 삭제 | 댓글

    언제나 잘 보고 있습니다. 감사합니다.

  4. 김형근 2009/10/29 18:05 댓글주소 | 수정 | 삭제 | 댓글

    좋은글 감사합니다.
    궁금한것이 더 많아지네여 ^^
    좋은글 앞으로도 부탁드립니다.

  5. 몽상가 2009/10/30 03:19 댓글주소 | 수정 | 삭제 | 댓글

    저도 요즘 공부 차원에서 다시 보고 있습니다. ^^
    목표는 모 프로그램의 크랙입니다. 물론 나쁜 용도는 아니고, 최소한 내가 의도한 정도까지? 지만, 욕심이 크다보니 할 수 있는데 까지 해보는게 목표입니다.

    근데 일단 가장 기본적인 디버거를 붙이면 강제 종료되는데, 아마 이게 다른 쓰레드에서 디버그가 붙으면 강제종료를 하는 스레드가 있나 봅니다. 이럴땐 어떤 방식으로 해야되는가 궁금합니다.
    아예 실행하고 붙이는게 아니라 실행전에 수정을 해야되는건가요?

    강좌가 계속 나와서 이런 방식까지 나왔으면 좋겠습니다.
    제 블로그 링크도 해주시고 감사합니다. RSS 로 꾸준히 보고있어요 :D

    • reversecore 2009/10/30 07:19 댓글주소 | 수정 | 삭제

      몽상가님, 안녕하세요.

      디버깅 당했다고 판단되면 스스로 종료 <- 안티 디버깅 기법을 사용한 듯 하군요.

      계획에는 일반적인 안티디버깅에 대한 강좌를 하고, 특별한 안티기법은 파일을 분석하는 도중에 따로 정리해보려고 합니다.

      말씀하신 내용도 참고할께요~ (attach 할 수 없는 프로세스)

      감사합니다.

  6. 졸작땜시날새는코더 2009/10/30 13:51 댓글주소 | 수정 | 삭제 | 댓글

    글잘보았습니다 참고많이되었구요
    혹시
    특정 이벤트처리가아니라
    그 해당 프로그램이 사용하는 API함수들을 모두 체크할수있는 방법이없을까요
    변조하는게 아닌 그저 무슨 API함수를 사용했다 정도로만요
    대충 이미지는 나오는데 어떻게해야할지 막막합니다 ㅠㅠ

    • ReverseCore 2009/10/31 12:30 댓글주소 | 수정 | 삭제

      안녕하세요.

      프로그램이 사용하는 모든 API 함수를 확인하고 싶으시다는 말씀이시죠?

      IDAPro, OllyDbg 에서 그런 기능을 지원하는데요, 그 원리는 코드의 CALL, JMP 명령어을 전부 파싱하는 것입니다.

      혹시 이런류의 프로그램이나 OllyDbg 플러그인등이 존재할 수도 있겠네요.

      참고로 예전에 제가 했던 비슷한(?) 방법으로는 관심있는 API 를 전부(약 120여개) 후킹해서 각 파라미터/리턴값을 확인하고 호출 순서등을 확인한 적이 있었습니다.

  7. 냥냥 2009/10/31 03:16 댓글주소 | 수정 | 삭제 | 댓글

    NotePad에 관한 질문드리겠습니다 ㅠ
    제가 더미dll ( Attatch 시 빈 메세지 박스만띄우는 ) 을 만든후 Notepad.exe에 dll injection을 통해서 구현해보려고 했습니다.
    방식은 Notepad를 디버거로서 연다음 Notepad.exe의 메모리공간을 할당받은후 LoadLabrary()를 실행해서 더미dll을 Attatch시키게하는 명령어 코드를 복사한뒤 EIP수정후 실행하게한후, 실행후 다시 EIP를 원래의 EIP주소로 변경하도록 만들었습니다,
    근데... 제대로 동작을안하고 Notepad.exe가 그냥 죽어버리는군요...
    코드가 잘못됐는지 확인하려고... 아무런 보안기능이 없는 전에 만든 툴로 대상을 변경후 실행하니 아무런 이상없이 실행이 잘됐습니다 ( 빈 메세지박스를 확인 )
    Notepad.exe에서는 제대로 실행되지 않는 이유가 무엇을까요..?
    운영체제에서 특별한 알고리즘을 가지고 보호하는건가요..? ( 부팅시 원도우 기본파일들인 calc,notepad 같은 프로그램은 수정되었을시 복원하는 기능은 있다고 들었지만... )

    • ReverseCore 2009/10/31 12:35 댓글주소 | 수정 | 삭제

      안녕하세요

      어떤 OS 를 쓰시나요?

      전 주로 XP 를 쓰는데, DLL Injection 은 아무런 문제가 없습니다.

      다른 프로그램에서는 잘 동작하는데 notepad 만 동작하지 않는다고 하셨는데요, 저도 잘 이해가지 않는군요.

      참고로 DLL Injection 을 위해서 반드시 notepad 를 디버깅 할 필요는 없습니다. 그리고 DLL Injection 의 코드를 확인 해보시는 것이 좋을것 같습니다.

      가능하시면 저에게 보내주시기 바랍니다. 이곳에 올리셔도 되구요.

    • 나그네 2009/11/03 05:34 댓글주소 | 수정 | 삭제

      WFP(Windows File Protection) 때문에 그런거 아닐까요? system 폴더에 있는 notepad.exe 를 다른 폴더로 옮겨서하면 괜찮을 것 같은데요.ㅋ

    • reversecore 2009/11/03 22:25 댓글주소 | 수정 | 삭제

      나그네님, 소중한 답변 감사드립니다. ^^

  8. 냥냥 2009/10/31 16:09 댓글주소 | 수정 | 삭제 | 댓글

    메일 주소가 어떻게 되시나요..? ;;
    마땅히 보낼만한...ㅠㅠ

  9. vice 2009/12/16 11:15 댓글주소 | 수정 | 삭제 | 댓글

    잘 보구 갑니다. 간단하구 쉽게 설명 잘 해주시네요 ^^

  10. thav 2009/12/21 13:23 댓글주소 | 수정 | 삭제 | 댓글

    잘봤습니다. 근데. 님께서 첨부해주신 샘플 소스 구동하다가 notepad 저장할때 정지됩니다.
    혹시 이런 현상 보신적 있나요?
    OS 는 윈도우 XP 입니다.

    • ReverseCore 2009/12/22 11:30 댓글주소 | 수정 | 삭제

      thav님, 안녕하세요.
      notepad 저장시에 프로그램이 멈춘다는 말씀이시죠?

      제 환경(XP SP3)에서는 그런 일이 없었지만, 충분히 그럴 가능성은 있습니다.

      * 참고로 제 테스트 PC 는 OS 만 설치된 클린 PC 환경입니다.

      제가 여러가지 상황에서 충분히 테스트하지 않아서 그럴 수 있고요. thav님 PC 에 실행중인 프로세스들에 의해서 그렇게 될 수 도 있습니다. (충돌!)
      (또한 실습 예제 파일들은 코드의 간결성을 위해서 에러처리가 거의 되어있지 않습니다.)

      (가능하시다면) 다른 PC 에서도 테스트 해보시기 바랍니다.

      감사합니다.



#0. prologue

분야를 막론하고 기술자(특히 엔지니어)들은 자신만의 작업환경과 손에 익은 도구(장비)가 있습니다.

기술자(특히 엔지니어) 란 특정 업무를 위해서 필요한 도구를 아주 능숙하게 다룰 줄 아는 사람들입니다.

같은 도구를 사용하더라도 기술자의 능력에 따라서 전혀 다른 결과를 보여줍니다.
(더 나아가서 필요한 도구를 직접 만들어 내기도 합니다.)

또한 기술자들은 각자 자신만의 도구를 가지고 있으며
한번 손에 익힌 도구를 되도록 오래 쓰고 왠만해서는 바꾸려 하지 않습니다.
(바꿀 때도 될 수 있으면 같은 회사의 후속 제품으로 바꾸려고 하지요.)

남의 도구, 남의 작업환경에서 일을 하면 아무래도 불편하다고 생각합니다.

즉, 자신만의 작업환경과 자신만의 도구가 갖춰져야 그 기술자의 진정한 실력이 100% 발휘 된다고 할 수 있습니다.



#1. Reverse Code Engineer (Reverser)

리버서들은 어떨까요?

리버서도 역시 IT 엔지니어 범주에 들어가기 때문에 위에서 언급한 일반적인 기술자의 성향과 다를 바가 없습니다.

리버싱을 하기 위한 도구의 종류만도 수십 가지가 넘으며, 각 종류별로 다양한 제품들이 존재합니다.
또한 IT 분야의 특성상 계속해서 새로운 도구가 개발되고 있습니다.

도구의 종류만도 아래와 같이 매우 많습니다. (언급하지 못한 것도 많을 것입니다.)

disassembler
debugger - PE, script, etc
development tool - assembly, C/C++, etc
editor(viewer) - text, hex, resource, retistry, string, PE, etc
monitoring tool - process, file, registry, network, message, etc
memory dump
classifier
calculator - hex, binary
compare tool - text, hex
packer/unpacker
encoder/decoder
virtual machine
decompiler - VB, Delphi, etc
emulator
...



#2. 좋은 분석 도구 선택의 5 가지 기준

아래에 ReverseCore 만의 도구 선택 기준(가이드)을 제시합니다. (참고만 하세요~)


첫째, 도구 개수를 최소화 시킨다.

남들이 쓴다고 해서 기능을 알지도 못하는 도구를 잔뜩 가지고 있어봐야 도움이 되지 않습니다.
자신에게 필요한 도구만을 각 종류별로 하나씩만 사용하는 것이 좋습니다.

자신의 실력에 맞는 것만을 고르고 차츰 하나씩 늘려나가시면 됩니다.

또한 중복된 기능의 도구들은 하나로 정리하는 것이 좋습니다.


둘째, 도구는 기능이 단순하고 사용방법이 편리한 것이 좋다.

실력이 늘어날 수록 사용해야 할 도구의 개수도 늘어납니다.
기능이 단순하고 인터페이스가 직관적일 수록 사용하기에 편리합니다.

여기서 기능이 단순하다는 말은 리버싱 도구치고는 단순하다는 말입니다.
(Windows 의 계산기, 메모장 수준을 생각하시면 안됩니다. ^^)

따라서 아무리 단순한 리버싱 도구를 하나 익히는 데만도 어느 정도 시간이 필요합니다.


셋째, 기능을 철저히 익힌다.

아무리 좋은 도구라도 사용할 줄 모르면 무용지물 입니다.

자신이 이미 가지고 있는 도구에서 제공되는 기능인데도,
그걸 알지 못하고 다른 도구를 찾는 분들이 많습니다.

일단 도구를 선택한 후에는 제공되는 메뉴얼을 한번 정독해보시는 것이 좋습니다.
자주 사용하는 기능의 단축키 정도는 외워 두시면 작업이 훨씬 수월합니다.
(단축키를 잘 쓰면 확실히 자타공인 전문가처럼 느껴집니다.)


넷째, 꾸준히 업데이트 한다.

리버싱도 IT 범주에 속하기 때문에 기술 발전 속도가 매우 빠릅니다.

새로운 기술에 대응하기 위해 도구들도 빠르게 변화하기 때문에
사용하는 도구의 업데이트는 매우 중요한 일입니다.

따라서 꾸준히 업데이트를 지원하는 도구를 선택하는 것이 좋습니다.


다섯째, 도구의 핵심 동작 원리를 이해한다.

도구를 더 잘 사용하기 위해서 동작 원리를 이해하는 것이 좋습니다.
더 나아가 테스트용 프로토타입을 만들 수 있다면 금상첨화 입니다.

이 부분을 간과하시는 분들이 매우 많습니다.
하지만 높은 수준의 리버싱 실력을 쌓기 위해서는 필수적인 사항입니다.

예를 들어 debugger 의 동작원리를 이해하고 있으면,
anti-debugging 기법을 잘 회피할 수 있습니다.

원리를 이해하지 못하고 도구에만 의존하면,
간단한 트릭도 해결하지 못하고 다른 도구를 찾아 나서야 합니다.
소위 말하는 "도구의 노예"가 되는 것이지요. (이것만은 꼭 경계해야 겠습니다.)



#3. epilogue

debug.exe 라는 프로그램을 아시나요?
MS-DOS 시절부터 존재하던 16-bit debugger 입니다. (XP 에도 존재합니다.)

커맨드 창에서 debug.exe 를 실행하고 '?' 명령으로 도움말을 보겠습니다.


위에 보이는 명령어가 전부입니다.

단순하지요?

제가 아는 어떤 분이 debug.exe 로 16 bit DOS 프로그램을 분석하시는 걸 본 적이 있습니다.

뭔가를 실행시키더니 키보드를 다다다다 두들기면서 화면이 번쩍 번쩍 넘어가는데,
바로 옆에서 지켜보면서도 무슨 작업을 하는지 도저히 알 수 없었습니다.
(눈이 그분의 작업 속도를 따라가지 못한 거죠.)

처음에는 실행되는 프로그램이 debug.exe 인지 조차도 몰랐었습니다.
(전 그때 당시 이미 debug.exe 를 한달 정도 써본 경험이 있었음에도 불구하고 말이죠.)

그리고는 다 됐다면서 결과를 넘겨 주시더군요.
그때 그분이 사용하신 프로그램이 debug.exe 라는 걸 알고 충격에 휩쌓였었죠.

'저 단순한 debug.exe 만으로 이 일을 이렇게 빨리 해냈단 말인가?' 하고 말이죠.

그 이후로 제가 어떤 도구를 고를 때 나름대로의 기준을 세우게 되었습니다.

그리고 하나의 깨달음을 얻었습니다.

"평범한 도구라도 극한까지 연마하면 천하에 다시 없는 비범한 도구가 된다."

마치 무림고수(武林高手)가 수련에 수련을 거듭해서 검(劍)을 버리고
결국에는 초(草)/목(木)/죽(竹)/석(石)을 모두 검처럼 사용할 수 있듯이 말이죠.


여러분은 어떻게 생각하시나요? ^^


Trackback Address :: http://www.reversecore.com/trackback/16 관련글 쓰기

  1. showtime 2009/10/08 13:00 댓글주소 | 수정 | 삭제 | 댓글

    잘 읽고 갑니다~ ^^ㅋ

  2. 베리굿 2009/11/07 22:20 댓글주소 | 수정 | 삭제 | 댓글

    감사합니다.^^

  3. 부자봉스 2009/11/12 00:43 댓글주소 | 수정 | 삭제 | 댓글

    그렇게 생각합니다 흑

  4. reverse 2011/05/24 01:06 댓글주소 | 수정 | 삭제 | 댓글

    이 블로그가 정말 도움이 많이 되네요
    ~~
    꾸준히 하루에 한문제씩이라도 풀어재끼껫습니다!!


abex' crackme #2 (2)

analysis 2009/03/01 00:54

Serial 생성 알고리즘

Serial 문자열 생성 알고리즘을 알아보겠습니다.

* 참고로 VB 에서는 기본적으로 UNICODE 기반의 가변 길이 문자열 객체를 사용합니다.
   가변 문자열 객체는 필요에 따라 내부에서 수시로 동적 메모리 할당/해제 작업이 발생합니다.
   그래서 디버깅할 때 실제 문자열이 한눈에 잘 들어오지 않는 어려움이 있습니다.



함수 시작 찾기

<Fig. 9>에 보이는 조건 분기 코드는 분명 어떤 함수에 속해있습니다.
아마 그 함수는 [Check] 버튼의 event handler 일 것입니다.

이유는 [Check] 버튼을 선택했을때 위 함수가 호출되었으며,
성공/실패 메시지 박스를 출력하는 사용자 코드를 포함하기 때문입니다.

함수의 시작 부분으로 거슬러 올라가서 하나씩 살펴보는 것이 좋을것 같네요.


<Fig. 15>

위쪽 방향으로 스크롤 하다보면 <Fig. 15> 와 같은 코드를 만나게 됩니다.
00402ED0 주소 명령을 자세히 보겠습니다.

00402ED0    PUSH EBP        ; => [Check] button event handler
00402ED1    MOV EBP,ESP

위 코드는 함수가 시작할 때 나타나는 스택 프레임을 구성하는 (전형적인) 코드입니다.
(스택 프레임은 나중에 따로 설명)

따라서 이 위치가 함수의 시작임을 알 수 있고, 바로 [Check] 버튼의 event handler 입니다.

* NOP : No Operation – 아무 동작을 하지 않는 명령어 (그냥 CPU 클럭만 소모됨)

정확한 코드 분석을 위해서 402ED0 에 BP 를 걸고 디버깅을 시작하도록 하겠습니다.



코드 예측 하기

프로그래밍 경험이 있거나 리버싱 경험이 있다면 serial key 생성 방법에 대해서 예측이 가능해 집니다.

만약 Win32 API 프로그램이라면 아래와 비슷할 것입니다.

– Name 문자열 읽기 (GetWindowText, GetDlgItemText 등의 API 사용)
– 루프를 돌면서 문자를 암호화 하기 (XOR, ADD, SUB, etc)

VB 엔진 함수로 작성된 위 파일도 원리는 이와 같을 것입니다.

예측이 맞다면 <Fig. 15>의 event handler 시작 코드부터 디버깅을 하여
Name 문자열을 읽는 부분을 찾으면, 바로 이어서 암호화 루프가 나타나겠지요.

* 디버깅 하기 전에 코드 구현에 대해 미리 예상하는 것은 리버서에게 좋은 습관입니다.
   예측이 빗나가면 그냥 그대로 처음부터 디버깅 하면 되구요,
   다행히 예측이 맞았다면 디버깅 시간을 절약할 수 있습니다.



Name 문자열 읽는 코드

VB 엔진 API를 이용하여 문자열을 가져올테니 CALL 명령어 위주로 디버깅을 해보겠습니다.
(이때 API 에 전달되는 파라미터리턴값을 유심히 관찰해 주세요)

진행해 나가면 아래와 같이 네번째 CALL 명령어를 만나게 됩니다.

00402F8E    LEA EDX,DWORD PTR SS:[EBP-88]    ; => Name 문자열을 저장할 스트링 객체
00402F94    PUSH EDX
00402F95    PUSH ESI
00402F96    MOV ECX,DWORD PTR DS:[ESI]
00402F98    CALL DWORD PTR DS:[ECX+A0]       ; => Name 을 읽어온다.

00402F8E 의 코드를 보면 함수의 로컬 객체 SS:[EBP-88] 주소를 함수의 파라미터로 전달(PUSH)하고 있습니다.

이 주소를 확인해 보겠습니다.


<Fig. 16>
우리가 찾는 것은 Name 문자열이고,
VB 에서 문자열은 (C언어의 char 배열이 아닌) 스트링 객체를 사용한다고 하였으니,
메모리를 <Fig. 16> 처럼 보면 실제 문자열을 알아보기 쉽지 않습니다.

따라서 OllyDbg 메모리 윈도우를 'Long – Address with ASCII dump' 보기 모드로 변경합니다.


<Fig. 17>

이렇게 보기 방법을 변경하면 VB 스트링 객체의 실제 문자열 저장 버퍼 주소를 직접 볼 수 있게 됩니다.

이 상태로 402F98 주소의 CALL 명령까지 실행하면 아래와 같이 스트링 객체에 값이 저장됩니다.


<Fig. 18>

[EBP-88] 주소에 Name 문자열이 (스트링 객체 형태로) 저장되었습니다.



암호화 루프

계속 디버깅을 해나가면 아래와 같이 루프를 만나게 됩니다.
(불필요한 설명은 생략합니다.)

...
0040318B    CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarForInit>]
00403191    MOV EBX,DWORD PTR DS:[<&MSVBVM60.#632>]            ; MSVBVM60.rtcMidCharVar
00403197    TEST EAX,EAX                                       ; => loop start
00403199    JE abexcm2-.004032A5
...
0040329A    CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarForNext>]
004032A0    JMP abexcm2-.00403197                              ; => loop end
004032A5    MOV EAX,DWORD PTR SS:[EBP+8]

위 루프 동작 원리를 간단하게 설명드리면,
__vbaVarForInit(), __vbaVarForNext() 는 마치 linked list 에서 next pointer 를 이용해
다음 element 를 참조하듯이 문자열 객체에서 한 글자씩 참조할 수 있도록 해줍니다.

또한 loop count 를 세팅해서 정해진 회수만큼 루프를 돌게 합니다.

* 실제 테스트 해보니 Name 문자열의 앞 4 문자만 사용합니다.

* 코드 내에 문자열의 길이를 체크하여 4 보다 작으면 에러 메시지 박스를 출력하는 부분이 존재합니다.

이제 원하는 부분을 다 찾았으니, 암호화 방법만 알아내면 되겠군요.



암호화 방법

입력한 Name 문자열은 "ReverseCore" 입니다.

004031F0    CALL DWORD PTR DS:[<&MSVBVM60.__vbaStrVarVal>]
004031F6    PUSH EAX                               ; => Name 문자열에서 가져온 하나의 문자(UNICODE)
                                                   ; => ‘R’
004031F7    CALL DWORD PTR DS:[<&MSVBVM60.#516>]   ; => rtcAnsiValueBstr() : UNICODE ->
ASCII 변환
                                                   ; => ‘R’= 52
00403221    LEA EDX,DWORD PTR SS:[EBP-54]
00403224    LEA EAX,DWORD PTR SS:[EBP-DC]
0040322A    PUSH EDX                               ; => EDX = 52
0040322B    LEA ECX,DWORD PTR SS:[EBP-9C]
00403231    PUSH EAX
00403232    PUSH ECX                               ; => dest
00403233    MOV DWORD PTR SS:[EBP-D4],64
0040323D    MOV DWORD PTR SS:[EBP-DC],EDI          ; => [EBP-DC] = EAX = 64

여기까지 디버깅 하고 스택을 보시죠.

0012FAB0    0012FB84     ; => ECX
0012FAB4    0012FB44     ; => EAX
0012FAB8    0012FBCC     ; => EDX


각 메모리 주소를 보면 다음과 같습니다.

0012FB84    00000000 .... ; => ECX
0012FB88    00000001 #...
0012FB8C    00000001 #... ; => 결과 저장용 버퍼
0012FB90    77D098CF ??
0012FB44    00000002 #... ; => EAX
0012FB48    00000000 ....
0012FB4C    00000064 d... ; => 암호화 키 (64)
0012FB50    00000000 ....
0012FBCC    00000002 #... ; => EDX
0012FBD0    00000000 ....
0012FBD4    00000052 R... ; => Name 문자열에서 첫번째 문자의 ASCII 값
0012FBD8    00000000 ....


아래 함수를 실행 시키면 ECX 레지스터가 가리키는 버퍼에 암호화 된 값이 저장됩니다.

00403243    CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarAdd>]    ; => __vbaVarAdd() : 52 + 64 = B6

0012FB84    00000002 #... ; => ECX
0012FB88    00000001 #...
0012FB8C    000000B6 ?..  ; 계산 결과 : 52 + 64 = B6
0012FB90    77D098CF ??

계산 결과 B6 는 원본값 52('R') 에 암호화 키 64 를 더해서 생성한 값이며,
위 <Fig. 14> 에서 보는 것처럼 진짜 Serial 의 첫 두글자입니다. (현재는 숫자 입니다.)

아래 코드에서 숫자 B6 를 (UNICODE) 문자 'B6' 로 변환합니다.

00403250    LEA EDX,DWORD PTR SS:[EBP-54]
00403253    LEA EAX,DWORD PTR SS:[EBP-9C]
00403259    PUSH EDX                             ; => EDX = 12FBCC (위 스택 주소 참조)
0040325A    PUSH EAX                             ; => EAX = 12FB84
0040325B    CALL DWORD PTR DS:[<&MSVBVM60.#573>] ; => rtcHexVarFromVar() : UNICODE 로 변경!

함수 호출 직후 EAX 가 가리키는 버퍼(0012FB84)를 보면 아래와 같이 "B6" 문자열이 생성되어 있습니다.

0012FB84    73470008 #.Gs
0012FB88    0012FBCC 悸#.
0012FB8C    001577B4 큪#. UNICODE "B6"
0012FB90    0012FB84 꾺#.

실제 문자열 주소(001577B4)를 확인해 봅니다.
숫자 B6 가 UNICODE 문자열 "B6" 로 변경되었습니다.

001577B4    00360042 B.6.

끝으로 생성된 문자열을 이어 붙여주는 코드가 있습니다.

0040326C    LEA ECX,DWORD PTR SS:[EBP-44]
0040326F    LEA EDX,DWORD PTR SS:[EBP-54]
00403272    PUSH ECX                                    ; => old
00403273    LEA EAX,DWORD PTR SS:[EBP-9C]
00403279    PUSH EDX                                    ; => add
0040327A    PUSH EAX                                    ; => serial
0040327B    CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarCat>] ; => __vbaVarCat() : 문자열 이어 붙이
                                                        ; => serial(EAX) = old(ECX) + add(EDX)


마지막 루프를 실행하면 이런식이 됩니다.

serial = old(“ B6C9DA”) + add(”C9”) => “B6C9DAC9” (최종적으로 완성된 serial 문자열)

다시 한번 암호화 방법을 정리하면 아래와 같습니다.

1) 주어진 Name 문자열을 앞에서부터 한 문자씩 읽기 (총 4 회)
2) 문자를 숫자(ASCII 코드)로 변환
3) 변환된 숫자에 64 를 더함
4) 숫자를 다시 문자로 변환
5) 변환된 문자를 연결시킴



배운 내용

간단한 Assembly 명령어 (TEST)
VB(Visual Basic) 파일의 특징
간단한 문자열 암호화 기법
OllyDbg 명령 : code 윈도우나 stack 윈도우에서 보고 싶은 메모리 주소를 빠르게 찾아가는 방법
                     "Follow in dump" 또는 "Follow in dump → Memory address"



앞으로 배워야 할 내용

Stack Frame(스택 프레임)
VB(Visual Basic) 파일 포멧



Epilogue

크랙 측면에서 보면 쉽게 설명을 끝낼 수 있는 파일이지만,
리버싱 초보자가 배워두면 좋은 내용들(VB파일, 문자열 암호화)이 많이 있기 때문에
그런 부분들을 다루느라 생각보다 글이 길어졌습니다.

디버깅을 따라 하실 때 잘 안된다고 쉽게 포기하지 마세요.

위 설명을 보면 제가 디버깅을 쉽게 한 것처럼 느끼실 수 있습니다. (특히 event handler 코드 설명에서)
하지만 저도 이 문서를 만들기 위해서 약 10 여회의 restart 를 하였습니다.

원하는 코드를 지나치면 다시 시작하고, VB 문자열 객체의 내부 스트링 버퍼를 따라가기 위해 계속 재시작하고...
여러분들도 이런 과정을 많이 거쳐야 디버깅 실력이 늘어나는 것입니다.

문자열 암/복호화는 앞으로 다양한 형태로 자주 나올 것이구요,
VB 파일 포멧에 대해서는 한번 정리를 해서 소개할 계획이 있습니다.

다음 번에는 Stack Frame 에 대해서 정리하도록 하겠습니다.

감사합니다.


'analysis' 카테고리의 다른 글

키로거(KeyLogger) 분석 (3)  (4) 2009/05/03
키로거(KeyLogger) 분석 (2)  (51) 2009/05/03
키로거(KeyLogger) 분석 (1)  (14) 2009/04/24
Lena's Reversing for Newbies #10 (2)  (7) 2009/03/18
Lena's Reversing for Newbies #10 (1)  (16) 2009/03/17
abex' crackme #2 (2)  (27) 2009/03/01
abex' crackme #2 (1)  (31) 2009/02/28
abex' crackme #1  (34) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (3)  (39) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (2)  (5) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (1)  (47) 2009/02/26

Trackback Address :: http://www.reversecore.com/trackback/10 관련글 쓰기

  1. 베리굿 2009/11/08 16:15 댓글주소 | 수정 | 삭제 | 댓글

    감사합니다.

  2. 잘보고가요~ 2010/01/07 17:55 댓글주소 | 수정 | 삭제 | 댓글

    글을 읽을때마다 새로운걸 배웁니다.^^

    그리고 질문있습니다.
    Fig. 15에서 보면 Check 버튼의 시작이라는 것을 어떻게 찾았는지요?
    제가 찾아보려고 해도 잘 안되네요..
    좀 알려주셨으면 합니다.

    • ReverseCore 2010/01/09 16:50 댓글주소 | 수정 | 삭제

      안녕하세요.

      문의하신 내용을 보고 위의 글을 다시 읽어보니
      과연 설명이 좀 어렵게 되어 있군요.

      이전 글의 <Fig. 7> ~ <Fig. 9> 코드를 보시면
      "Wrong serial" 문자열을 찾고 이 문자열을 참조하는 코드를 찾습니다.

      그 코드가 속한 영역이 바로 버튼의 이벤트 핸들러일 테구요.
      스크롤을 죽 올리면 <Fig. 15> 와 같이 stack frame 시작 코드가 나타납니다. (402ED0)

      설명이 잘 되었나 모르겠네요.
      잘 이해 안되시는 부분은 다시 질문 해주세요~

      감사합니다.

  3. 보름♬달★콤 2010/05/18 19:44 댓글주소 | 수정 | 삭제 | 댓글

    계속 잘 읽어가고 있습니다.
    어떻게 문자열 암호화를 유추해 내는지는 도저히 어렵네요.
    말씀하신대로 따라가면서 봐도 잘 모르겠던데..
    경험에 의한 해석인건지요?

    • reversecore 2010/05/20 17:14 댓글주소 | 수정 | 삭제

      안녕하세요.

      네, 사실 처음 접하시는 분들께는 쉽지 않은 내용입니다.
      물론 경험도 크게 작용하지요.

      제가 고민하는 부분도 바로 어떻게 하면 더 쉽게 설명할 수 있을까 하는 것이지요.

      위의 경우는 제가 입력한 "ReverseCore" 문자열 위치를 알아낸 후 거기서 부터 이 문자열을 access 하는 코드를 찾아 가는 중에 암호화하는 코드를 발견한 것이지요.

      조금 더 쉬운 예제를 구하셔서 연습해 보시기 바랍니다.
      저 또한 쉬운 예제를 디버깅하는 포스트를 준비하도록 하겠습니다.

      감사합니다.

  4. 잘보고갑니당 2010/10/20 05:55 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요 글잘보고 있습니다
    그런데 Name을 읽는 위치가 스택프레임 구성후 4번째 call이라는 부분은 어떻게 아신건가요?
    간접적으로 스트링이 구성되서 event코드 시작지점이 어딘지를 찾았다고해도 그 수많은
    call중에 어떤 부분이 name을 읽는 위치인지 어떻게 알아내셨는지 궁금합니다.
    또 event 시작지점이 Fig 15의 이부분인걸 아신것은 그냥 문자열 검색후에 스크롤을 쭉올리다가
    push ebp
    move ebp,esp
    가 나오는 것만보고 아신건가요?

    • reversecore 2010/10/21 06:58 댓글주소 | 수정 | 삭제

      안녕하세요~

      위 설명에는 언급이 안되어 있는데요...

      일단 Handler 를 찾은 후 이부분을 여러번 반복해서 디버깅 해봅니다.

      레지스터, 스택, OllyDbg 의 커멘트를 잘 살피다 보면 아래와 같은 명령어를 볼 수 있습니다.

      00402F8E . 8D95 78FFFFFF LEA EDX,DWORD PTR SS:[EBP-88]
      ...
      00402F98 . FF91 A0000000 CALL DWORD PTR DS:[ECX+A0]
      ...
      00402FB6 > 8B85 78FFFFFF MOV EAX,DWORD PTR SS:[EBP-88]

      => 00402FB6 주소 명령에 의해 EAX 레지스터에 문자열 주소가 세팅됩니다. 그걸 보면 [EBP-88] 변수가 스트링 객체라는 걸 유추할 수 있고 00402F98 주소의 CALL 명령어에서 값이 세팅된다는 걸 알 수 있지요. ^^

      그리고 말씀하신대로 스택 프레임의 prologue (558BEC) 를 보고서 Handler 의 시작일 것이다라고 예측을 한 것이구요.

      궁금하신 내용에 대한 답변이 되었나요?

      감사합니다.

  5. 2010/11/17 22:37 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2010/11/25 21:15 댓글주소 | 수정 | 삭제

      안녕하세요.

      말씀하신대로 API 이름과 호출 흐름을 보면 예측이 쉬워집니다.
      그리고 코드 예측은 어느정도 경험에 의한 '감' 도 많이 작용합니다.

      MOV EBX, 4 명령어의 주석은 제가 완전히 잘 못쓴것이군요... @@~

      바로 수정하였습니다.

      좋은 지적 감사합니다.

  6. 2010/11/28 21:47 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2010/11/30 11:13 댓글주소 | 수정 | 삭제

      안녕하세요.

      주소값 시작이 0x00DXXXX 형태로 되어있다는 말씀이시죠?

      말그대로 주소 영역입니다. 00000000 ~ 80000000 영역은 유저 메모리 영역이라 액세스가 가능합니다.

      그런데 참조할 수 없는 값이라고 하셨는데요... 혹시 디버깅 하시다가 안티디버깅에 의해서 쓰레기 주소값을 만나신 건지요?

  7. 2010/12/01 01:45 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2010/12/03 16:17 댓글주소 | 수정 | 삭제

      안녕하세요.

      네, 그러셨군요~ ^^

      OllyDbg 에서 알파벳으로 시작하는 주소를 보실때는...

      '0' 을 앞에 붙여주세요~

      0D0000, 0C0000

      요렇게 말이죠.

      그 값이 중요한 값인지 아닌지는 실제 코드를 봐야 뭐라고 말씀드릴수 있겠네요~ ^^

      감사합니다.

  8. kuslove 2010/12/16 04:02 댓글주소 | 수정 | 삭제 | 댓글

    설명이 아주 잘되였어여.
    잘 보구 갑니다. ㄱㅅㄱㅅ
    감사합니다.

  9. TeamKhan 2011/03/07 19:06 댓글주소 | 수정 | 삭제 | 댓글

    다른부분은 다 이해가가겟는데
    암호화부분이 너무어렵내요,...
    이런건 직접 이해가 잘안가더라고 10~20번 디버깅해보면서
    디버깅 실력을 키워나가는 노력밖에없겟죠?ㅠㅠ
    리버서의 길은 험난하고도 머내요...
    그리고제가 직접 알고리즘 부분을 분석해봣는데요
    코멘트로 각각 값의 변화가 일어나는부분
    문자열에서 문자를 가져와서 한다는건 모르겟는데
    문자열이 어떤 특정주소에 계쏙 이어붙여지는걸
    볼수잇엇거든요
    이정도의 알고리즘만 파악을햇는데...
    지금 제가 이렇게 공부한것도
    나중에 많이 도움이되겟죠...?ㅠ

    • reversecore 2011/03/10 10:49 댓글주소 | 수정 | 삭제

      안녕하세요.

      위와 같은 디스어셈블리 코드와 문자열 암/복호화를 처음 접하신 분들에게는 당연히 어려운 내용입니다. 저도 마찬가지 였습니다. 아마 다들 그러실 거에요.

      재밌는건요... 어렴다 어렵다 하면서도 한달 일년 보면요...
      좀 쉽게 보입니다. ^^ 끈기 앞에는 장사 없죠.

      말씀하신대로 지금 파악하신 알고리즘과 그러한 경험들이 차곡차곡 쌓이면서 훌륭한 리버서로 거듭 나실 겁니다.

      감사합니다.

  10. 대단하세요 2011/03/11 18:08 댓글주소 | 수정 | 삭제 | 댓글

    402EDO 에 왜저는 체크버튼이라고안나올까요 매우짜증나네요 하.;;

    • reversecore 2011/03/16 22:38 댓글주소 | 수정 | 삭제

      안녕하세요.

      402ED0 이요? 그쪽에 써있는 주석을 말씀하시는 것 같습니다. 그건 제가 써 넣은것입니다. OllyDbg 에서는 기본적으로 보이지 않습니다.

      제가 충분한 설명을 하지 않아서 괜히 짜증만 나시게 만들었네요. 죄송합니다.

      다른 질문 있으시면 올려주세요~

      감사합니다.

  11. 대단하세요 2011/03/11 18:16 댓글주소 | 수정 | 삭제 | 댓글

    fig17 덤프창보면 전 0041로 디셈블러창같이 그런주소로되어있고

    Long Address with ascll dump 해도그러네요 ;

    • reversecore 2011/03/16 22:41 댓글주소 | 수정 | 삭제

      아, 무슨 말씀인지 알겠습니다.

      이것도 제 설명이 좀 부족해서 헷갈리신 것 같습니다.

      <Fig. 16> 위에 보시면 "이 주소를 확인해 보겠습니다." 라고 했는데요. 확인 방법은 덤프 윈도우에서 Ctrl + G 명령으로 해당 주소(12FB98) 을 입력해야 합니다. 제가 요 설명을 빼먹었네요. 이렇게 보니 많은 설명이 생략되어 있네요~

      세세한 지적 감사합니다. 지금 작업하는 책에 큰 도움이 되었습니다.

      감사합니다.

  12. 금빛 star 2011/04/27 10:51 댓글주소 | 수정 | 삭제 | 댓글

    "하지만 저도 이 문서를 만들기 위해서 약 10 여회의 restart 를 하였습니다."

    좋은글과 바쳐진 시간에 감사드립니다.

  13. FCLEX 2011/06/05 19:54 댓글주소 | 수정 | 삭제 | 댓글

    Name 을 읽어오는 함수 뒤에 나오는

    FCLEX 는 뭐하는 명령어 인가요 ?

    어셈블리어 명령어인가요 ? google에서 검색해봤지만.. 별다른 내용이 안나오네요 ..

    • reversecore 2011/06/06 21:56 댓글주소 | 수정 | 삭제

      안녕하세요.

      FCLEX 명령어는 FPU(Floating Point Unit) 명령어 입니다.

      실제로 코드에서 쓰이는 경우도 있고, 디버거가 파싱을 잘 못 해서 그리 보일 수도 있습니다.

      그런데 FCLEX 명령어를 어디 에서 보셨는지요? 저는 본문에서 안보이는 데요... @@~

      감사합니다.

    • FCLEX 2011/06/07 14:42 댓글주소 | 수정 | 삭제

      이 문서에서 다루는 크랙미를 올리디버거로 볼때 그런 명령어가 보였거든요 ㅎ

      Name 문자열을 읽어오는 함수 조금 밑부분에 있길래 궁금해서 질문드렸네요.. 답변 감사합니다

  14. 리버싱생초보 2011/06/29 23:52 댓글주소 | 수정 | 삭제 | 댓글

    저기 그 메모리 윈도우에서 제가 입력한 Name을 못찾겟어여;; 12FB98로 가도 나오질 않네여;; 메모리주소 값도 나와잇는거랑 다른데 원래 그런가여?
    그리구 LEA EDX,DWORD PTR SS : [EBP-54] 같은것은 뭐하려구 선언하려는거져??


abex' crackme #2 (1)

analysis 2009/02/28 23:31



* 출처 : http://www.simples.co.kr/RCEZone



abstract

abex’s crackme #2 파일을 디버깅합니다.



Goal

- 아주 간단한 crackme 파일을 분석하여 디버거와 디스어셈 코드에 익숙해집니다.
- Visual Basic 의 파일 구조를 간단히 살펴보고 분석 방법을 배워봅니다.



Prologue

지난 번에 이어서 abex' crackme 두번째 파일을 분석해보도록 하겠습니다.

두번째 파일은 Visual Basic 으로 제작되었으며,
Visual C++ 혹은 Assembly 로 작성된 파일과는 또 다른 파일 형태를 경험 하실 수 있습니다.

* 설명에 나온 메모리(스택) 주소들은 사용자의 PC 환경에 따라 변경됩니다.
   디버깅 하실 때 이점을 감안하시기 바랍니다.

* 디버깅이 어렵게 느껴진다면 그건 자연스러운 현상입니다.
   꾸준히 노력과 시간을 투자한다면 결국 익숙해지실 것입니다.




VirusTotal 검사

디버깅 할 파일의 안전성을 확인하기 위해 www.virustotal.com 을 이용하시기 바랍니다.


<Fig. 1>



abex' crackme #2

일단 실행시켜서 어떤 프로그램인지 알아봐야겠죠?


<Fig. 2>

전형적인 crackme 의 형태인 serial key 알아내기 프로그램입니다.
Name 을 따로 입력받는 걸로 봐서 Serial 값을 생성할 때 Name 문자열이 사용될 것 같습니다.

Name 과 Serial 을 적절히 입력하고 [Check] 버튼을 눌러봅니다.


<Fig. 3>

"Wrong serial!" 메시지 박스가 출력됩니다.
다른 값으로 몇 번을 시도해도 같은 메시지 박스가 나타납니다.

디버깅을 시작하기 전에 Visual Basic 파일의 특징을 살펴보겠습니다.



Visual Basic 파일 특징

먼저 Visual Basic (VB) 파일 특징에 대해 간단히 설명드리겠습니다.

1) VB 전용 엔진

VB 파일은 MSVBVM60.dll (MicroSoft Visual Basic Virtual Machine 6.0) 이라는 VB 전용 엔진을 사용합니다.
(The Thunder Runtime Engine 이라는 이름으로도 불리고 있습니다.)

VB 엔진의 사용 예를 들어보면 메시지 박스를 출력하고 싶을때 VB 소스 코드에서 MsgBox() 함수를 사용합니다.
VB 컴파일러는 실제로 MSVBVM60.dll!rtcMsgBox() 함수가 호출되도록 만들고,
이 함수 내부에서 Win32 API 인 user32.dll!MessageBoxA() 함수를 호출 해주는 방식으로 동작합니다.
(VB 소스 코드내에서 user32.dll!MessageBoxA() 함수를 직접 호출 하는 것도 가능합니다.)

2) N(Native) code, P(Pseudo) code

VB 파일은 컴파일 옵션에 따라서 N code 와 P code 로 컴파일이 가능합니다.

간단히 설명드리면 N code 는 일반적인 디버거에서 해석 가능한 IA-32 명령어를 사용하는 반면에,
P code 는 인터프리터(Interpreter) 언어 개념으로서 VB 엔진으로 가상 머신을 구현하여
자체적으로 해석가능한 명령어(바이트 코드)를 사용하는 것입니다.

따라서 VB 의 P code 를 정확히 해석하려면 VB 엔진을 분석하여 에뮬레이터를 구현하여야 합니다.

* P code 의 사용 예로 JAVA (JAVA Virtual Machine), Python (Python 전용 엔진)등이 있습니다.

* P code 를 사용했을때의 장점은 이식성이 좋아진다는 것입니다.
  (플랫폼 별로 엔진을 제작/배포하면 다른 플랫폼 에서 기존의 사용자 코드를 거의 수정없이 그대로 사용 가능합니다.)

3) Event Handler

VB 는 주로 GUI 프로그래밍을 할 때 사용되며, IDE 인터페이스 자체도 GUI 프로그래밍에 최적화 되어 있습니다.

즉, VB 프로그램은 Windows 운영체제의 event driven 방식으로 동작하기 때문에
main() 혹은 WinMain() 에 사용자 코드(우리가 디버깅을 원하는 코드)가 존재하는 것이 아니라,
event handler 에 사용자 코드가 존재합니다.

위의 abex' crackme #2 에서는 [Check] 버튼 handler 에 사용자 코드가 있겠지요.

4) undocumented 구조체

VB 에서 사용되는 각종 정보들(Dialog, Control, Form, Module, Function, etc)은
내부적으로 구조체 형식으로 파일에 저장됩니다.

MicroSoft 에서는 이러한 구조체 정보를 공개하지 않았기 때문에 VB 파일의 디버깅에 어려움이 있습니다.

* Visual Basic 파일 포멧은 그 자체로 흥미로운 부분이 많이 있기 때문에,
   향후 VB 파일 포멧에 대한 상세한 분석을 해보도록 하겠습니다.



Start debugging

OllyDbg 를 실행시켜 abex' crackme #2 파일의 디스어셈 코드를 살펴보겠습니다.


<Fig. 4>

프로그램이 시작되면 처음 하는 일은 VB 엔진의 메인 함수(ThunRTMain)를 호출하는 것입니다.

00401232    FF25 A0104000    JMP DWORD PTR DS:[4010A0]    ; MSVBVM60.ThunRTMain
00401238    68 141E4000      PUSH 401E14                  ; => EP
0040123D    E8 F0FFFFFF      CALL 00401232                ; <JMP.&MSVBVM60.#100>

RT_MainStruct 구조체 주소(401E14)를 파라미터로 넘기고 VB 엔진의 메인 함수인 ThunRTMain() 를 호출합니다.

위 3 줄의 코드가 VB 파일의 startup 코드의 전부입니다.

간단하긴 하지만 아래 3 가지 항목은 눈여겨 볼 필요가  있습니다.

1) 간접호출 (Indirect Call)

40123D 주소의 CALL 명령은 ThunRTMain() 함수 호출 인데요, 조금 특이한 기법을 사용합니다.
MSVBVM60.dll!ThunRTMain() 으로 직접 가는 것이 아니라, 중간의 401232 주소의 JMP 명령을 통해서 갑니다.

이 기법은 VC++, VB 컴파일러에서 많이 사용하는 간접호출(Indirect Call) 기법입니다.

* 참고로 4010A0 주소는 IAT(Import Address Table) 영역이며 MSVBVM60.ThunRTMain() 함수의 실제 주소가 담겨있습니다.
   향후 PE header 설명할 때 IAT 에 대해 자세히 설명드리겠습니다.

2) RT_MainStruct 구조체

우리가 주목할 부분은 ThunRTMain() 함수의 파라미터인 RT_MainStruct 구조체 입니다.
여기서는 401E14 주소에 RT_MainStruct 가 존재합니다.


<Fig. 5>

MS 에서 RT_MainStruct 를 공개하지 않았지만, 이미 실력있는 해외 리버서들이 분석을 완료하고 인터넷에 공개하였습니다.

RT_MainStruct 구조체의 멤버는 또 다른 구조체의 주소들입니다.

즉, VB 엔진은 파라미터로 넘어온 RT_MainStruct 구조체를 가지고
프로그램의 실행에 필요한 모든 정보를 얻는다는 걸 알 수 있습니다.

* 나중에 VB 파일 포멧 분석할 때 멤버 하나씩 살펴보도록 하겠습니다.

3) ThunRTMain() 함수

참고로 ThunRTMain() 함수가 나온김에 어떻게 생겼는지 살펴 보겠습니다.


<Fig. 6>

<Fig. 6> 는 ThunRTMain() 코드의 시작부분입니다.

메모리 주소가 완전히 틀려진 것이 보이시죠?

이 주소는 MSVBVM60.dll 이 로딩된 주소입니다.

VB 파일의 대한 설명은 이정도로 마치도록 하고 abex' crackme #2 로 돌아가겠습니다.



단서를 찾아라!

도대체 우리가 보고 싶은 "패치 해야 할 코드" 는 어디 있을까요?

지금 수준에서 RT_MainStruct 구조체를 분석하는 것은 쉽지 않을테니, 좀 더 간단한 방법을 생각해 봅니다.

역시 간단히 떠오르는 생각은 <Fig. 3> 의 에러 메시지 박스와 그 문자열을 단서로 해서 시작하면 될 것 같습니다.



문자열 검색

OllyDbg 의 문자열 검색 기능(All referenced text strings) 을 사용하면 아래와 같은 윈도우가 나타납니다.


<Fig. 7>

아까 본 메시지 박스의 문자열을 확인 하였습니다.

해당 문자열을 더블 클릭하여 이 주소로 가봅니다.


<Fig. 8>

메시지 박스의 타이틀, 내용 그리고 실제 메시지 박스 함수 호출 코드(4034A6)까지 나타났습니다.

프로그래밍 관점에서 생각해보면, 어떤 알고리즘으로 시리얼 키를 생성하고
사용자가 입력한 키와 문자열 비교를 통해서 각각 TRUE(키가 같음) 와 FALSE(키가 틀림) 로 코드가 갈라질 것입니다.

즉, 위 코드 전후에 문자열 비교 코드, 그리고 키가 맞았을 때 출력될 성공 메시지 박스 호출 코드가 존재할 것입니다.
(키가 맞았을때 메시지 박스가 호출될 꺼라는 것은 <Fig. 7> 의 문자열을 보고 유추한 내용입니다.)

<Fig. 8> 에서 스크롤을 조금 올리다 보면 과연 (추측한대로) 분기문을 포함한 코드가 나타납니다.


<Fig. 9>

403329 주소의 __vbaVarTstEq 함수를 호출해서 리턴값(AX) 를 비교(TEST 명령) 한 후
403332 주소의 조건 분기(JE 명령)에 의해서 참, 거짓 코드로 분기하게 됩니다.

* 위 코드에서 사용된 어셈블리 명령어 설명

TEST : 논리 비교 (Logical Compare)
           bit-wise logical ‘AND’연산과 동일 (operand 값이 변경되지 않고 EFLAGS 레지스터만 변경됨)
           두 operand 중에 하나가 0 이면 AND 연산 결과는 0 → ZF = 1 로 세팅됨

JE : 조건 분기 (Jump if equal)
       ZF = 1 이면 점프



문자열 찾기

<Fig. 9> 에서 403329 주소의 __vbaVarTstEq 함수가 문자열 비교 함수라면,
그 위에 있는 두 개의 PUSH 명령어는 비교 함수의 파라미터 - 즉, 비교 문자열이 될 것입니다.
(C 언어의 strcmp 함수를 떠올려서 추측한 것입니다.)

403329 주소까지 가보겠습니다. 403329 주소에 BP 를 설치하시고 실행[F9]해 주세요. 메인 다이알로그가 나타나고 <Fig. 3> 과 같이 입력하신 후 [Check] 버튼을 누르시면 403329 주소에 멈추게 됩니다.

00403321    LEA EDX,DWORD PTR SS:[EBP-44]
00403324    LEA EAX,DWORD PTR SS:[EBP-34]
00403327    PUSH EDX                             ; 0012FBDC
00403328    PUSH EAX                             ; 0012FBEC
00403329    CALL DWORD PTR DS:[<&MSVBVM60.__>    ; MSVBVM60.__vbaVarTstEq

이 상태에서 스택을 보면 아래와 같습니다. (스택의 주소는 디버깅 환경에 따라 달라집니다.)


<Fig. 10>

00403321 주소의 SS:[EBP-44] 는 무엇을 나타내는 걸까요?

 IA-32 Register 기본 설명 에서 SS 는 스택 세그먼트(Stack Segment) 이고, EBP 는 베이스 포인터(Base Pointer) 레지스터라고 소개했었습니다.

즉, SS:[EBP-44] 주소가 의미하는 것은 스택내의 주소를 말하는데 ,
이것이 바로 함수내에서 선언된 로컬 객체의 주소 입니다 .
(로컬 객체는 스택에 저장되는걸 아시죠~)

해당 메모리 주소([EBP-44] = [12FBDC]) 를 보면 아래 그림과 같습니다.


<Fig. 11>

* 참고
OllyDbg 에서 주소를 직접 입력하지 않고 편하게 찾아가는 명령이 있습니다.
<Fig. 9>의 Code 윈도우에서 마우스로 403321 주소를 선택하신 후 마우스 우측 메뉴의 'Follow in dump → Memory address' 명령을 쓰시면 됩니다. 또는 <Fig. 10> 에서 마우스로 12FAB8 (12FBDC) 주소를 선택하신 후 마우스 우측 메뉴의 'Follow in dump' 명령을 사용하시면 됩니다.

VB 의 문자열은 C++ 의 string 클래스 객체와 마찬가지로 가변 길이 문자열 타입을 사용합니다.

따라서 <Fig. 11> 에서 보는 바와 같이 바로 문자열이 나타나지 않고 16 byte 크기의 데이타가 나타납니다.
(이것이 바로 VB 에서 사용하는 문자열 객체일 것입니다.)

다른 값들은 동일하고 노란색으로 표시된 부분의 값이 틀린데 메모리 주소처럼 보이네요.
(가변 문자열 타입은 내부에 -동적으로 할당한- 실제 문자열 버퍼 주소를 가지고 있습니다.)

OllyDbg 의 메모리(덤프) 윈도우에서 마우스 우측 메뉴의 Long – Address with ASCII dump 명령를 선택합니다.

이 명령은 메모리 윈도우의 보기 형식을 마치 스택 윈도우처럼 변경하고,
특히 문자열 주소인 경우 해당 문자열을 표시해줍니다.
(원래대로 보고 싶을때는 마우스 우측 메뉴의 Hex – Hex/ASCII (16 bytes) 명령)
 

<Fig. 12>

위 그림에서 알 수 있듯이 결국 EDX(0012FBDC) 는 실제 serial 값이고,
EAX(0012FBEC) 는 사용자가 입력한 serial 값입니다.
(참고로 VB 는 UNICODE 문자열을 사용합니다.)

이 노란 주소 부분을 찾아가서 보면 실제 문자열을 확인할 수 있습니다.



<Fig. 13>

Crackme 프로그램을 실행해서 Name = "ReverseCore", Serial = "B6C9DAC9" 로 입력해보면
아래와 같이 성공 메시지 박스가 출력됩니다.


<Fig. 14>

Serial 을 찾았으니 크랙은 성공했다고 할 수 있습니다.

그런데 Name 이 Serial 과 어떤 관계가 있을까요?
Name 에 다른 값을 주고 동일한 Serial 을 입력하면 틀렸다고 나옵니다.

역시나 Name 문자열을 기반으로 Serial 을 그때 그때 생성하는 알고리즘입니다.

(continue)


'analysis' 카테고리의 다른 글

키로거(KeyLogger) 분석 (3)  (4) 2009/05/03
키로거(KeyLogger) 분석 (2)  (51) 2009/05/03
키로거(KeyLogger) 분석 (1)  (14) 2009/04/24
Lena's Reversing for Newbies #10 (2)  (7) 2009/03/18
Lena's Reversing for Newbies #10 (1)  (16) 2009/03/17
abex' crackme #2 (2)  (27) 2009/03/01
abex' crackme #2 (1)  (31) 2009/02/28
abex' crackme #1  (34) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (3)  (39) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (2)  (5) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (1)  (47) 2009/02/26

Trackback Address :: http://www.reversecore.com/trackback/9 관련글 쓰기

  1. 메롱이 2009/11/20 02:06 댓글주소 | 수정 | 삭제 | 댓글

    언제나 강좌 재미있게 읽고 갑니다..

    다른분들의 리버싱 강좌에 비해 엄청 세련되고 자세하다는 느낌을 받습니다...초보로서..@@

    그래서 너무나 잼있게 읽게 되는것 같습니다...

    그리고 느낀게..

    역시 고수분들은..글도 잘쓰시는거 같다는..@@

    하루에 한번은 꼭 들어오는 블로그예요..ㅋㅋㅋ

    앞으로도 잼있는 강좌 계속 부탁드립니다..헤헤..

  2. ㅎㅎ 2010/02/06 10:59 댓글주소 | 수정 | 삭제 | 댓글

    강좌 재밌게 보고 갑니다

    리버싱에 대한 기초를 여기서 다 다지게 되네요

    감사합니다

  3. upx 2010/03/02 02:15 댓글주소 | 수정 | 삭제 | 댓글

    다시해보니 새롭네요..그냥 점프하는거보다 키젠미도 괜찮은데요..

    • reversecore 2010/03/04 00:17 댓글주소 | 수정 | 삭제

      upx님, 안녕하세요.

      대부분 위와 같은 크랙미를 풀면서 리버싱을 시작하시는것 같습니다.

      일단 재밌으니까요.

      재미를 느끼고 새로운 것을 배우는게 좋은것 같습니다.

      감사합니다.

  4. 못다한꿈 2010/03/25 19:00 댓글주소 | 수정 | 삭제 | 댓글

    모든 강좌 잘 보고 있습니다!^^
    궁금한점이 있습니다!위 강좌에서 간접호출 쓰는 이유가 뭔가요?~

    • reversecore 2010/03/25 22:35 댓글주소 | 수정 | 삭제

      못다한꿈님, 안녕하세요.

      VC++, VB 등의 고급언어 개발도구에서 간접호출을 사용하는 예를 많이 봤습니다.

      저도 그 이유가 궁금합니다. 그냥 CALL DWORD PTR [] 하면 되지 왜 굳이 CALL + JMP DWORD PTR [] 나를 할까요? 코드도 늘어나고 실행 시간도 길어지는데 말이죠.

      혹시 이유를 알게 되시면 저에게도 공유해주시기 바랍니다.

      감사합니다.

  5. ㅇㅇ 2010/05/17 16:03 댓글주소 | 수정 | 삭제 | 댓글

    와~ 정말 강좌 재미있게 보고갑니다!
    항상 좋은글 감사해요~

  6. 와우 2010/07/19 15:25 댓글주소 | 수정 | 삭제 | 댓글

    좋네요~
    리버싱 공부중인데 좋은 강좌 감사합니다.
    처음 접하는 사람들이 크랙쪽으로 많이 접근해서 배운다는데
    저도 열심히 해야겠군요 ㅎㅎ

    • reversecore 2010/07/19 23:13 댓글주소 | 수정 | 삭제

      크랙은 확실히 초보자에게 흥미를 끄는 부분이 많이 있습니다.
      하지만 너무 빠져 들면 곤란하겠지요? ^^

  7. 질문드립니다 2010/07/24 21:56 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요
    강좌 정말로 잘 보고있습니다. 진심으로 감사드린다는 말씀 드리고싶네요

    이 강좌에 질문이 하나 있는데요
    test연산과 je 에 관련하여 질문드립니다

    test연산이 두 연산자을 AND연산하여서 결과가 0이나오면 ZF =1
    결과가 1이나오면 ZF = 0이고
    JE가 ZF=1이면 Jump라 하는거라 하셨는데요
    ZF가 1이면 AND연산값이 0, 즉 두 연산자가 다르다는 말인데
    왜 JE(Jump if Equal)인지 궁금합니다.
    더불어 연산자들도 1비트가 아닌 여러 비트로 이루어져있을텐데..
    예를들면 test 1010111(2),1011111(2)

    이 크랙에선 test ax,ax인데 ax에 0이 들어가잇더군요
    연산자중 하나가 0이거나 둘이 보수일때 Jump?


    머릿속에 정리가 잘 되지않네요 ..;;답변두탁드릴꼐요

    • newbie 2010/07/25 20:37 댓글주소 | 수정 | 삭제

      JE가 Jump if Equal의 의미로 쓰일때는 cmp문이랑 같이 쓰일때입니다.
      cmp문은 두 피연산자를 빼서 결과를 내기 때문에 같으면 ZF를 1로 만들고 그러므로 JE이 동작하게 되는겁니다.
      TEST문과 JE를 쓰면 주로 피연산자가 0인지 아닌지 확인하는 용도로 쓰기위함입니다. TEST문은 AND 연산이기때문에 피연산자가 0이거나 1인 비트가 전혀 겹치지 않아야 ZF를 1로 만들죠. 보수 관계등을 알기 위해 쓸 수도 있겠지만 대부분의 경우 피연산자는 TEST EAX, EAX 같이 두개를 같은 것을 두고 피연산자 값이 0이면 점프하는 용으로 쓰인다고 보시면 될 것 같습니다. 결과적으로 궁금해하신 Jump if Equal의 의미는 cmp문이랑 같이 쓰일때 그 의미를 가진다고 생각하시면 될 것 같습니다.

    • reversecore 2010/07/28 16:50 댓글주소 | 수정 | 삭제

      안녕하세요.

      newbie님께서 자세히 잘 설명해 주셨네요.
      (같은 피연산자 ax를 비교하였으므로 0 이면 점프하라는 뜻이지요.)
      newbie 님, 감사합니다. ^^

      test ax, ax => ax 와 ax 를 비교하죠?
      je XXXX

      위 구문은 아래 C 코드로 해석하실 수 있습니다.

      if (ax == 0) { goto XXXX; }

  8. 주황 2010/08/19 21:19 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요.
    주황 입니다.

    한동안 너무 바빴다가 짬짬이 들려 공부하고 있습니다.
    굉장히 쉽고 자세히 설명해주시는데 이해력이 딸려서 너무 어렵네요.

    우선 잘 안되는 부분 질문 드립니다.

    403329 까지 디버깅 합니다. 는 어떻게 해야 하나요? 403329에 bp 걸고 f9 하면 되는건가요?

    아.. 여기 이하로 꽉 막혔네요..
    일단 Fig. 10 을 확인 못하고 있습니다.

    Follow in dump -> Selection 이라는 메뉴 밖에 없고요 이 메뉴를 따라가봐도 Fig. 11 처럼 안나오네요..ㅠㅠ

    주소를 403321 을 따라 갔는데 Fig. 11 과 12 는 왜 12FBDC 주소가 나타나는 건가요?
    저는 403321 그대로 나오거든요..
    무엇을 잘못하고 있는건지.. 도무지 모르겠습니다.
    부디 좀 더 자세한 설명 부탁드리겠습니다.

    아.. 그리고 덤프윈도우의 Comment 탭은 어떻게 나오는건가요? 이것저것 다 눌러봐도 안나타 나네요.

    그럼 답변 부탁드리겠습니다.
    감사합니다.

    • reversecore 2010/08/20 16:22 댓글주소 | 수정 | 삭제

      안녕하세요.

      디버깅 하시는 중에 막히셨군요...

      1) 403329 에 BP 걸고 F9 맞습니다. ^^

      2) <Fig. 10> 그림으로 설명드리면,
      이게 스택 윈도우이므로 12FAB8 (12FBDC) 를 선택하신 후 마우스 우측 버튼의 메뉴의 "Follow in dump" 를 사용하시면 덤프 윈도우에 <Fig. 11> 과 같은 그림이 나타납니다.
      이 방법외에 <Fig. 9> 그림의 코드 윈도우에서 403321 주소를 선택하신 후 마우스 우측 메뉴의 "Follo in dump - Memory address" 명령을 사용하셔도 <Fig. 11> 그림이 나타납니다.

      3) dump 윈도우에는 comment 탭이 없습니다. 위 그림은 제가 설명을 위해서 임의로 그려 넣은 것입니다.

      저도 지금 다시 글을 읽어보니... 과연 리버싱 초보분들께서 이해하기 어려운 부분이 많이 보이네요.

      주황님의 소중한 지적을 바탕으로 본문을 수정하도록 하겠습니다.

      제가 바라는 것이 리버싱 초보분들의 눈높이에서 쉽게 설명하는 것이었는데, 이렇게 좋은 피드백을 주셔서 감사합니다. 큰 도움이 되었습니다.

      공부하시다가 잘 이해안가시는 부분이 있으면 또 문의해 주세요~

      감사합니다. ^^

  9. 리버싱입문자 2010/12/16 11:37 댓글주소 | 수정 | 삭제 | 댓글

    초보라서 어렵긴 하지만

    그래도 다른분들이 쓴 내용보다 쉽게 되 있어서

    어느정도(?)는 할수있네요 ~

    흐름은 전체적으로 이해가 됐으니 이제 세부적인 것도 이해해야겠어요 ㅠ

    • reversecore 2010/12/16 15:13 댓글주소 | 수정 | 삭제

      안녕하세요.

      흐름만 이해를 하셔도 대단한거랍니다.

      열심히 리버싱하시고 종종 방문해 주세요~

      감사합니다.

  10. 오늘도 2011/01/13 19:50 댓글주소 | 수정 | 삭제 | 댓글

    리버싱 학습할 때, 문제를 스스로 만들면서 하면 좋을 듯 합니다.

    Name = TEST 이면, Serial은 어떻게 될까?
    처럼 학습하면 좋을 것 같습니다. 답은) B8A9B7B8

    • reversecore 2011/01/14 11:56 댓글주소 | 수정 | 삭제

      안녕하세요.

      좋은 의견입니다.

      시리얼 생성 알고리즘을 확실히 분석하지 않으면 풀 수 없는 문제겠군요. (단순 점프명령어 패치로는 할 수 없지요.)

      감사합니다.

  11. Reverser_H 2011/01/20 19:49 댓글주소 | 수정 | 삭제 | 댓글

    오늘도 매우 재미있게 공부합니다 ㅋ

  12. 대단하세요 2011/03/11 17:51 댓글주소 | 수정 | 삭제 | 댓글

    잘안되서 머리막쥐어짜고 짜증만나네요..

    올리디버거버전이 틀린건지 fig10에서 0013으로되어있고 그옆에 EAX 글자들이 전혀없어요

    그래서 더밑으로진행을못하고.. ㅠ;;

    • reversecore 2011/03/16 22:44 댓글주소 | 수정 | 삭제

      안녕하세요.

      버전 문제가 아니구요... ^^

      EAX 는 제가 그림에 적어 넣은 것이라 OllyDbg 에서는 보이지 않는답니다.

      다른곳에 올려주신 질문에도 비슷한 내용을 올려주셨죠? 매우 죄송하구요. 섬세한 지적 감사합니다.

  13. 리버싱생초보 2011/06/27 01:51 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세여 올려주신 글마다 질문을 올리는것같네여;;

    이번 글에서는 Test AX,AX 에서 똑같은걸 왜 비교하는 지 모르겟네여;;

    그리고 마지막부분에서 노란색 부분을 찾아가서 실제 문자열을 보는건 어떻게하져??

    • reversecore 2011/06/29 12:07 댓글주소 | 수정 | 삭제

      안녕하세요.

      그건 Assembly 문법인데요...

      <그림 9> 의 40332F 주소를 보시면 아래와 같은 명령어가 있습니다.

      TEST AX, AX
      JE 403408

      C언어로 해석하면 개념은 대충 이렇습니다.

      if AX == 0
      goto 403408


      그리고 유니코드 문자열을 보시려면...
      OllyDbg 의 메모리(덤프) 윈도우에서 이동[CTRL+G] 명령으로 원하는 주소로 간 다음...
      마우스 우측 메뉴의 Long – Address with ASCII dump 명령를 선택하시면 됩니다.

      감사합니다.

  14. bypass 2011/09/23 11:40 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요. 이 크랙미 문제에 대해 질문 있습니다.
    ReverseCore님이 언급한 코드부분에 (가장 중요한 흐름분기 일어나는 부분) 브레이크포인트를 걸고, F9명령어로 "계속" 진행을 시켜도, abex crackme.exe의 실행 창이 제대로 뜨질 않습니다.(F9를 누르면 한번에 떠야 할 것 같은데) 다 떴다가도 마우스로 클릭하면 비활성화(최소화) 된다던가 하는데, F9를 눌러도 계속 0x7D??????부근에서 왔다갔다 하는걸보니 vb엔진이나 모듈의 함수에 갇혀서 순환하는 것 같습니다.
    ollydbg 의 옵션 문제인것 같은데 혹시 어느 항목이었는지 알려주실 수 있나요? 아니면 ollydbg.ini 설정파일이라도 제공해주시면 제가 일일이 찾아보겠습니다..
    생각해보니 지뢰찾기 리버싱 할때도 분명히 프로그램이 거쳐갈 만한 곳에 브레이크포인트를 잡아놨는데 아무리 F9를 눌러도 이상한데서 무한반복을 하고 있어서 고생했던 것 같네요. 조언 좀 주시면 감사하겠습니다.
    (좀더 연구해보니 ollydbg 2.0에서는 이런 현상이 일어나지 않고, ollydbg 1.1에서만 일어나네요. 그리고 1.1에서 Trace option의 'alwyas trace over system dll'옵션도 시도해봤는데 역시 해결이 안됩니다.)

    • reversecore 2011/09/28 20:45 댓글주소 | 수정 | 삭제

      안녕하세요.

      안티디버깅이 없는 일반적인 경우라면 OllyDbg 에서 실행[F9] 했을 때 <Fig. 2> 그림이 나타나야 합니다.

      그렇지 않다면... 혹시 plugin 이 설치되어 있는지 확인해 보시기 바랍니다. 어떤 플러그인은 버그가 많아서 디버깅에 오히려 방해가 된답니다. plugin 폴더를 rename 하신후 OllyDbg 를 재실행 해보시기 바랍니다.

      감사합니다.

  15. 낭만고양이 2011/09/26 21:43 댓글주소 | 수정 | 삭제 | 댓글

    ReverseCore님 안녕하셰요.
    강좌 잘 보고 나갑니다.
    근데, bypass님은 아직도 강좌를 이해하지 못하셨는데요. 그건 Shift+F9을 눌러 보셰요.
    ㅋㅋㅋ
    조언 감사


abex' crackme #1

analysis 2009/02/28 22:54

* 출처 : http://www.simples.co.kr/RCEZone



Level


초급



abstract


abex’s crackme #1 샘플을 크랙합니다.



Goal

아주 간단한 crackme 샘플을 분석하여 디버거와 디스어셈 코드에 익숙해집니다.
단순한 크랙이 목표가 아니라 디스어셈 코드와 디버깅에 익숙해지는 것이 목표입니다.



Prologue

crackme 라는 프로그램은 말 그대로 crack 연습 목적으로 작성되어 공개된 프로그램입니다.
리버싱을 처음 시작할때 간단한 crackme 를 분석해보면 실제로 디버거와 디스어셈 코드에 익숙해지는 효과가 있습니다.

abex' crackme 는 매우 간단해서 초보자에게 알맞고, 아주 유명하기 때문에 해외 사이트뿐만 아니라
국내에도 abex' crackme 를 설명한 사이트가 많이 있습니다.

자신이 크랙한 방법과 남들이 했던 방법을 비교할 수 있어서 좋겠죠?

* 상용 프로그램(웹하드, 게임, 기타)을 크랙해달라는 요청은 사양합니다.



VirusTotal 검사

만약 다운 받은 crackme 파일이 바이러스라면 곤란하겠죠?
먼저 www.virustotal.com 으로 검사해봅니다.

새로운 파일을 분석하기 전에 VirusTotal 을 이용하는 것은 좋은 습관입니다.


<Fig. 1>

파일에 별 문제는 없어 보이는군요.



abex' crackme #1

디버깅을 시작하기 전에 먼저 파일을 실행시켜서 어떤 프로그램인지 살펴봅니다.


<Fig. 2>

위 그림과 같이“Make me think your HD is a CD-Rom.” 메시지 박스가 출력됩니다.
처음에 전 이 영어 문장이 이해가지 않았습니다. @@~

뒤의 CD-Rom 단어를 보고 HD 가 HDD(하드 디스크 드라이브)를 의미 한다는걸 간신히 추측했지요.
선택의 여지가 없으니 일단 [확인]을 눌러봅니다.


<Fig. 3>

다시 'Error' 메시지 박스가 출력되면서 종료되는군요.

아직 abex 가 원하는게 뭔지 (뭘 어떻게 크랙하라는 건지) 정확히 이해할 수 없네요.
코드를 직접 보고 원하는 걸 파악해야 겠습니다.

* crackme 샘플은 보통 serial key 를 맞추는 것이 대부분인데 abex #1 은 조금 특이합니다.



Start debugging

OllyDbg 를 실행시켜 파일의 디스어셈 코드를 살펴봅니다.


<Fig. 4>

EP 코드가 매우 짧습니다.
지난번에 분석한 "HelloWorld.exe" 와는 너무나 차이가 나네요.

이것은 바로 Assembly 로 만들어진 실행 파일이기 때문에 그렇습니다.

VC++, VC, Delphi 등의 개발툴을 사용하면 자신이 작성한 소스코드외에
컴파일러가 stub code 를 추가시키기 때문에 디스어셈을 하면 복잡하게 보입니다.

하지만 Assembly 언어로 작성하면 어셈 소스가 곧 디스어셈 코드가 됩니다.
군더더기 없는 직관적인 코드(EP 에 main 함수가 바로 나타남)가 바로 Assembly 언어로 개발했다는 증거입니다.



코드 분석

코드가 매우 짧으므로 눈으로 슥~ 훑어 보겠습니다.

<Fig. 4> 에서 코멘트 부분의 붉은색으로 표시된 Win32 API 함수위주로 살펴봅니다.

MessageBox(“Make me think your HD is a CD-Rom.”)
GetDriveType(“C:\\”)
...
MessageBox(“Nah... This is not a CD-ROM Drive!”)
MessageBox(“OK, I really think that your HD is a CD-ROM! :p”)
ExitProcess()

 
Windows 프로그래밍을 해보셨으면 위 함수들의 의미를 잘 아실겁니다.

코드를 보니 이제 제작자의 의도가 명확하게 이해됩니다.

GetDriveType() 함수로 C 드라이브의 타입을 얻어오는데 (당연히 HDD 타입이 리턴되겠죠.)
이걸 조작해서 CD-ROM 타입으로 인식하도록 만들어서
위에 있는 파란 글씨로 표시된 메시지 박스가 출력되도록 하면 되는거였습니다.

Crackme 첫시간이므로 라인별 상세분석을 해보겠습니다.

00401000    PUSH 0                 ; Style = MB_OK|MB_APPLMODAL
00401002    PUSH 402000            ; Title = "abex' 1st crackme"
00401007    PUSH 402012            ; Text = "Make me think your HD is a CD-Rom."
0040100C    PUSH 0                 ; hOwner = NULL
0040100E    CALL 00401061          ; MessageBoxA
                                   ; => 함수내부에서 ESI = FFFFFFFF 로 세팅됩니다.

00401013    PUSH 402094            ; RootPathName = "c:\\"

00401018    CALL 00401055          ; GetDriveTypeA
                                   ; => 리턴값(EAX)은 3 입니다.

0040101D    INC ESI                ; => ESI = 0

0040101E    DEC EAX                ; => EAX = 2
0040101F    JMP SHORT 00401021     ; => 의미 없는 JMP 명령 (garbage code)
00401021    INC ESI                ; => ESI = 1
00401022    INC ESI                ; => ESI = 2
00401023    DEC EAX                ; => EAX = 1

00401024    CMP EAX,ESI            ; => EAX(1) 와 ESI(2) 비교

00401026    JE SHORT 0040103D      ; => JE(Jump if Equal) 조건 분기 명령
                                   ; => 두 값이 같으면 40103D 로 점프, 틀리면 그냥 밑으로 진행
                                   ; => 40103D 주소는 제작자가 원하는 메시지 박스 출력 코드

00401028    PUSH 0                 ; Style = MB_OK|MB_APPLMODAL

0040102A    PUSH 402035            ; Title = "Error"
0040102F    PUSH 40203B            ; Text = "Nah... This is not a CD-ROM Drive!"
00401034    PUSH 0                 ; hOwner = NULL
00401036    CALL 00401061          ; MessageBoxA
0040103B    JMP SHORT 00401050

0040103D    PUSH 0                 ; Style = MB_OK|MB_APPLMODAL

0040103F    PUSH 40205E            ; Title = "YEAH!"
00401044    PUSH 402064            ; Text = "Ok, I really think that your HD is a CD-ROM! :p"
00401049    PUSH 0                 ; hOwner = NULL
0040104B    CALL 00401061          ; MessageBoxA

00401050    CALL 0040105B          ; ExitProcess
                                   ; => 프로세스 종료
                                   ; => 파라미터를 넘기지 않았음. (현재 스택에 있는 값을 리턴함.)

위 코드에서 사용된 어셈블리 명령어는 별로 어렵지 않기 때문에
제가 파란색으로 표시한 comment 만 보셔도 쉽게 이해할 수 있으실 겁니다.

* 위 코드에서 사용된 어셈블리 명령어 설명

PUSH : 스택에 값을 입력
CALL : 지정된 주소의 함수를 호출
INC : 값을 1 증가 (C 언어의 ++ 연산자)
DEC : 값을 1 감소 (C 언어의 -- 연산자)
JMP : 지정된 주소로 점프 (C 언어의 goto 명령)
CMP : 주어진 두 개의 operand 비교
          SUB 명령어와 동일하나 operand 값이 변경되지 않고 EFLAGS 레지스터만 변경됨
          (두 operand 의 값이 동일하다면 SUB 결과는 0 → ZF = 1 로 세팅됨)
JE : 조건 분기 (Jump if equal)
       ZF = 1 이면 점프



패치

단순 패치가 목적이라면 401026 주소 명령어(JE SHORT 0040103D)를
'Assemble' 기능 [Space] 으로 JMP 0040103D 로 변경합니다.

즉, 조건 분기(JE) 명령어를 점프(JMP) 명령어로 바꾸는 것입니다.
(CMP 명령어의 결과에 상관없이 무조건 점프해버립니다.)

파일로 만들고 싶을땐 'Copy to executable' 메뉴를 사용하시면 됩니다.
(OllyDbg 기능 설명은 'HelloWorld.exe' 참고)



스택에 파라미터 전달 방법

위 코드에서 눈여겨 보아야 할 것은 함수 호출 시 스택에 파라미터를 전달하는 방법입니다.

아래에 00401000 ~ 0040100E 주소 사이의 명령어를 보시면 MessageBoxA() 함수를 호출하기 전에
4 번의 PUSH 명령어를 사용하여 필요한 parameter 를 역순으로 입력하고 있습니다.

00401000    PUSH 0                 ; Style = MB_OK|MB_APPLMODAL
00401002    PUSH 402000            ; Title = "abex' 1st crackme"
00401007    PUSH 402012            ; Text = "Make me think your HD is a CD-Rom."
0040100C    PUSH 0                 ; hOwner = NULL
0040100E    CALL 00401061          ; MessageBoxA


위 코드를 C 언어로 구현하면 아래와 같습니다.

MessageBox(NULL, "Make me think your HD is a CD-Rom.", "abex' 1st crackme", MB_OK|MB_APPLMODAL);

실제 C 언어 소스 코드에서 함수에 넘기는 parameter 의 순서가
Assembly 언어에서는 역순으로 넘어간다는 것을 알고 계시면 됩니다.

그런데 왜 역순으로 입력할까요 ?

이 내용을 이해하시려면 스택 메모리 구조(FILO:First In Last Out)를 떠올리시면 됩니다.

"스택은 FILO 구조이기 때문에 파라미터를 역순으로 넣어주면,
 받는 쪽(MessageBoxA 함수내부)에서 올바른 순서로 꺼낼 수 있습니다."

디버거를 이용해서 EIP = 0040100E 시점까지 진행한 다음 스택을 보시면 아래와 같습니다.


<Fig. 5>

IA-32 에서 스택은 아래로 자라기(스택에 값을 입력하면 ESP 값은 작아짐) 때문에
디버거에서 스택을 보면 <Fig. 5> 에서처럼 MessageBoxA() 함수의 첫번째 파라미터가
스택의 제일 위에 보이고, 마지막(네번째) 파라미터가 아래에 쌓여있게 됩니다.
(PUSH 명령 순서를 따라가 보시면 이해하기 쉽습니다.)

0012FFB4    00000000    hOwner = NULL (1st param)
0012FFB8    00402012    Text = "Make me think your HD is a CD-Rom." (2nd param)
0012FFBC    00402000    Title = "abex' 1st crackme" (3rd param)
0012FFC0    00000000    Style = MB_OK|MB_APPLMODAL (4th param)

따라서 MessageBoxA() 함수 내부에서는 POP 명령을 써서 파라미터를 꺼내게 될 텐데,
스택의 FILO 구조에 따라서 첫번째 파라미터 부터 꺼낼 수 있게 되는 것입니다.

즉, 파라미터를 꺼내서 사용하는 MessageBoxA() 함수 입장에서는 스택에 파라미터의 순서대로 들어 있는 셈이 되는 것입니다.

* 실제로는 함수 내부에서 POP 명령이 아니라 EBP 레지스터를 이용하여 파라미터를 직접 엑세스 하게 됩니다.
   여기서는 개념을 설명드리기 위해서 POP 명령을 얘기한 것 입니다.

* 중요한 것은 함수를 호출할 때 파라미터는 역순으로 입력한다는 것입니다.

* 나중에 'Stack Frame' 을 설명할 때 스택에 대해서 좀 더 자세히 다루도록 하겠습니다.




배운 내용

VirusTotal (www.virustotal.com)
간단한 Assembly 명령어 (PUSH, CALL, JMP, INC, DEC, CMP, JE)
Assembly 언어로 된 파일의 크랙
함수 호출 시 스택을 이용한 파라미터 전달 방법



앞으로 배워야 할 내용

Stack Frame



Epilogue

매우 간단한 크랙이지만 처음 접하시는 분들을 위해서 상세한 설명을 하였습니다.

다시 한번 강조하지만 크랙은 그냥 리버싱 처음 배울 때 디버깅 연습용(?)으로만 사용하는 것이 좋습니다.

핵심 원리를 파악하여 고급 리버싱을 할 수 있는 기초를 마련하는데 힘을 쏟아야 할 것 입니다.

감사합니다.

'analysis' 카테고리의 다른 글

키로거(KeyLogger) 분석 (3)  (4) 2009/05/03
키로거(KeyLogger) 분석 (2)  (51) 2009/05/03
키로거(KeyLogger) 분석 (1)  (14) 2009/04/24
Lena's Reversing for Newbies #10 (2)  (7) 2009/03/18
Lena's Reversing for Newbies #10 (1)  (16) 2009/03/17
abex' crackme #2 (2)  (27) 2009/03/01
abex' crackme #2 (1)  (31) 2009/02/28
abex' crackme #1  (34) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (3)  (39) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (2)  (5) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (1)  (47) 2009/02/26

Trackback Address :: http://www.reversecore.com/trackback/8 관련글 쓰기

  1. 오오 2009/10/29 20:35 댓글주소 | 수정 | 삭제 | 댓글

    이런 좋은강좌에 댓글이 하나도없다니.. 다들 보고 그냥 가시나보군
    감사합니다~

  2. 베리굿 2009/11/07 23:11 댓글주소 | 수정 | 삭제 | 댓글

    감사합니다.^^ 초보들도 이해하기 쉬운강좌네요 ㅎㅎ

  3. 놀람. 2009/11/13 23:26 댓글주소 | 수정 | 삭제 | 댓글

    이렇게 자세한 강좌는 처음보네요. 매번 간단하게 jmp 로 넘기면 됩니다.에서 스택에 대해 나오고 오 감동입니다. 감사합니다.

  4. 2010/01/29 13:47 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

  5. 감사 2010/02/13 13:50 댓글주소 | 수정 | 삭제 | 댓글

    감사합니다. 그런데 처음에


    ESI가 FFFFFFFF로 셋팅된다고 하셨는데 저것은 어떻게 아나요?
    본격적인 작업전에 한번 실행해보면서 옆에 레지스터 화면을 보고 아는건가요?

    • ReverseCore 2010/02/14 10:24 댓글주소 | 수정 | 삭제

      안녕하세요.

      MessageBox() API 내부에서 ESI = FFFFFFFF 로 세팅하는 것입니다. 디버거에서 한번 StepOver[F8] 하면 금방 아실 수 있지요.

      이처럼 Win32 API 함수들 내부에서 레지스터 값이 변경되기 때문에 어셈블리 프로그래밍을 할 때 주의해야 하지요.

      감사합니다.

  6. 리버싱만세! 2010/04/13 00:10 댓글주소 | 수정 | 삭제 | 댓글

    초보자용으로 설명 드린거 감사하게 보고 갑니다^^ 다른것도 있어서
    많이 들르러 올게요~

  7. 비빅 2010/07/20 02:37 댓글주소 | 수정 | 삭제 | 댓글

    좋은 정보 항상 잘 보고 있습니다^^ 감사합니다^^

  8. 리버싱입문자 2010/12/16 11:04 댓글주소 | 수정 | 삭제 | 댓글

    아직 많이많이 부족하지만,,, 정말
    하나하나 알아가는 기분입니다.

    감사합니다 ^^

    • reversecore 2010/12/16 15:14 댓글주소 | 수정 | 삭제

      네, 바로 그겁니다.

      리버싱이란것은 하루에 아주 조금씩 천천히...

      대신 꾸준히 앞으로 전진하는 것이지요... ^^

  9. 2011/01/10 14:15 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2011/01/10 16:13 댓글주소 | 수정 | 삭제

      안녕하세요.

      고급언어든 저급언어든 실행되기 위해서 기계어로 변환되는것은 같지요.

      STUB 코드는 빌더(ex:VC++, VB, Delphi, etc) 에서 추가시키는 코드이며, 빌더만의 독특한 코드로 채워집니다. 같은 C++ 컴파일러라도 VC++ 과 BC++ 이 만들어내는 Stub 코드는 서로 다른 모양이지요.

      저는 VC++ 을 사용하는데요, Stub이 무조건 들어갑니다.

      참고로 제가 사용하는 MASM32 어셈블러에는 Stub 코드가 추가되지 않습니다.

      감사합니다.

  10. 2011/01/10 16:43 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2011/01/11 10:41 댓글주소 | 수정 | 삭제

      Makefile 을 이용하여 VC++ 컴파일러, 링커를 쓰시면...
      그래도 역시 비슷하게 생성되겠죠?
      물론 설정에 따라 생긴 모습은 틀려지겠지만 말이죠~

  11. Reverser_H 2011/01/20 00:17 댓글주소 | 수정 | 삭제 | 댓글

    처음에 리턴값이(EAX)가 3인이유와
    ESI가 0인 이유를 잘 모르겠네요;

    (f8)을 이용해서 실행시키면서 register값을
    확인하면 EAX의 리턴값이 3이고 ESI의 값이 0인것을
    알수 있습니다만

    이렇게 실행하지 않고 단지 소스코드만보고
    알수있나요?
    제가 초보라서;;; 궁금해서 질문드립니다.

    • reversecore 2011/01/26 00:05 댓글주소 | 수정 | 삭제

      안녕하세요.

      당연히 디버깅 해서 알아낸 값이지요. ^^~

      API 에 익숙한 사람이라면 GetDriveType() 의 파라미터를 보고 리턴값 정도는 예측할 수 있겠지요~

      감사합니다.

  12. Apple 2011/02/17 16:39 댓글주소 | 수정 | 삭제 | 댓글

    쓰신 글을 따라 배워 보고 있습니다.

    감사합니다.

  13. 감자 2011/02/21 20:46 댓글주소 | 수정 | 삭제 | 댓글

    garbage code가 있는 이유가 궁금하네요.. 단순히 디버깅을 방해하기 위한건가요??

  14. reverse 2011/03/11 01:02 댓글주소 | 수정 | 삭제 | 댓글

    일단 강좌 감사합니다.ㅎㅎㅎ

    질문 몇가지 남깁니다 ^*^

    1.그전에 0040101F주소값에서 바로 0040103D로 점프해버리면 안되나요?

    2. 그렇게해도 결과는 같은데,, JE를 바꿔주는거랑 어떤 큰 차이점이나 문제점이 있게 되는건가요?ㅎ

    3. CMP 부분을 수정을 통하여 JE 조건 분기가 1이 되도록 해서
    조건을 만족 시키는 방법도 있는데,
    어떻게 EAX, ESI 부분을 저 시점에서 수정 되도록 해야 할지
    모르겠네요. 명령어 부분을 지우는 방법은 없나요?

    4.ESI 부분이 증가 되는 부분을 빼버리면 될것 같은데,
    그리고 ESI 값이 0이고 1이고 2고 그런건 어디 부분에서
    분석이 가능 한건가요?

    • reversecore 2011/03/16 22:57 댓글주소 | 수정 | 삭제

      안녕하세요.

      1. 됩니다. 더 좋은 위치에요~ ^^ 사실 가장 좋은 위치는 401000 이지요. 여기에 JMP 코드를 설치하면 무조건 됩니다. ^^

      2. 레지스터 값이 변경되는 것 말고는 없습니다.

      3. 디버거에서 레지스터 값을 직접 고치셔도 됩니다. 명령어를 지우는 방법은 NOP 로 채우시는 겁니다. 1 byte 의미 없는 명령이기 때문에 코드에 전혀 영향을 끼치지 않습니다.

      4. 디버깅을 하다보면 레지스터 창에서 해당 값이 변하는 걸 쉽게 보실 수 있습니다. 직접 연산하지 않아도 되니 편리하지요.

      감사합니다.

  15. 야호 2011/03/31 17:09 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요.. 늦게 시작하는 직장인입니다
    너무 좋은 사이트를 발견해서 너무 행복하고요...빨리 책을 보고싶습니다..

    오늘 강좌에서 JMP 를 쓰지않고 00401023 을 NOP 처리하여, 패치를 완성하여 exe 파일로 저장하였습니다. 그런데 윈도우에서 exe 파일을 실행하면, 또다시 NO 가 됩니다. 해당파일을 디비거로 올려보면, 정확히 NOP 로 수정이 되어었고요, F9를 이용해 실행하면, YES 가 되어, 정상 Crack되었습니다. 왜 윈도우상에서 exe 파일 실행된 결과값과, 디버거에서의 결과가 틀린지 궁금합니다.
    단지 1바이트 코드만을 수정햇을뿐인데..... 이게 정상인가요??? ㅜㅜ

    답변 부탁드립니다.

    • Elephunk 2011/03/31 22:09 댓글주소 | 수정 | 삭제

      ollydbg 에서 esi레지스터 를 fffffff 로초기화 시키기 때문에

      ollydbg 에서 실행한 결과와 실제 결과가 달라집니다

      이점을 이용해서 ollydbg 를 탐지해 낼수가 있지요

      실제로 이것이 적용된 패킹 프로그램도 있구요

      ollydbg 2.0 에서 수정되었으니 2.0 사용해보세요

    • reversecore 2011/04/06 01:25 댓글주소 | 수정 | 삭제

      안녕하세요.

      Elephunk님, 항상 좋은 답변 감사합니다. ^^

      하나 추가하고 싶은 부분은요~
      이 샘플에서만큼은 그 패치 위치가 좋지 않았다는 것입니다.

      Win 7 에서는 OllyDbg 의 ESI 초기값이 0 이기 때문에 잘 됩니다.
      XP 에서는 Elephunk 님 말씀대로 ESI 가 -1 이라서 안되구요.

      되도록이면 그런 영향을 덜 받을 수 있는 조건 분기 명령어가 제격입니다.

      어디까지나 이건 연습용이기 때문에 테스트로 그러신거겠죠? 그래도 그런 호기심으로 다른 사실을 알게 되셨구요. 리버싱에 도움되는 좋은 습관을 가지셨네요~ ^^

      감사합니다.

  16. 금빛 star 2011/04/27 10:37 댓글주소 | 수정 | 삭제 | 댓글

    좋은 강좌 감사글없이 지나치는건 죄악일것 같아서요..
    감사 10000번
    근데 질문입니다
    우의 예제 assembly로 작성했다고 하셨는데 그럼 masm등을 이용했다는건가요, 혹 그렇다면 assembly에서도 저렇게 api를 호출할수 있는가요?
    무식한넘 감히 질문합니다.

  17. 빵뎅이 2011/05/26 17:37 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요 요즘 리버싱 공부하느라 거의 매일 오고 있어요 ㅎ
    좋은 글 일단 감사합니다..
    그리고 질문이 있는데요 MessageBoxA 함수 내부에서 ESI가 -1 이 세팅된다고 하셨는데, 저는 401013에 BP 걸고 실행하면 ESI가 0이 되서 나오네요.. 궁금해서 F7로 함수 내부로 들어갔는데, ESI가 -1로 세팅이 되긴하는데 어느 순간 0으로 바뀌더군요.
    제가 Win7 이라 올리 2.0 을 쓰는데 그래서 그럴까요?





문자열 패치

목표 달성이 눈앞에 다가왔습니다.

MessageBoxW 호출하는 부분을 찾았으니 이제 "Hello World!" 문자열을 "Hello Reversing!" 으로 패치시킬 차례입니다.

디버깅을 재실행[Ctrl+F2] 시키고, main 함수 시작 부분까지 실행합니다.
(401000 에 BP 를 설정[F2]하고 실행[F9] 하세요. - 이 주소를 2 번째 베이스 캠프라고 하겠습니다.)




문자열을 패치하는 2 가지 방법

가장 쉬운 2 가지 방법을 소개합니다.

  • 문자열 버퍼를 직접 수정
  • 다른 메모리 영역에 새로운 문자열을 생성하여 전달



1) 문자열 버퍼를 직접 수정

MessageBoxW 함수의 전달인자 4092A4 의 메모리 주소 내용("Hello World!\)을 직접 수정해 버리는 것입니다.

메모리 윈도우에서 4092A4 로 갑니다 [Ctrl+G].
그리고 주소를 마우스로 선택한 후 [Ctrl+E] 단축키로 에디트 윈도우를 띄웁니다.


<Fig. 16>


'UNICODE' 항목에 "Hello Reversing!" 을 입력합니다.

변경된 코드는 아래와 같습니다.


<Fig. 17>


명령어는 그대로
이지만 MessageBoxW 함수에 전달되는 파라미터의 내용 자체가 변경되었습니다.
(파라미터의 주소도 그대로 입니다. 주소의 내용이 변경된 것입니다.)

이처럼 문자열 버퍼 내용을 직접 수정하는 방법은 사용하기에 가장 간단한 방법입니다.

* 하지만 기존 문자열 버퍼 크기를 잘 고려해서 수정해야만 프로그램이 에러 없이 잘 동작할 수 있습니다.
  즉, 기존 문자열 버퍼 크기 이상의 문자를 입력할 수 없다는 제약 조건이 있습니다.

변경된 내용을 영구히 보존하려면 파일로 만들어야 합니다.
<Fig. 16> 의 dump 윈도우에서 변경된 내용 ("Hello Reversing!" 문자열)을 선택하여
마우스 우측 버튼의 Copy to executable file 메뉴를 선택하면 아래와 같이 hexa 윈도우가 나타납니다.


<Fig. 18>


다시 마우스 우측 버튼의 Save file 메뉴를 선택하고 파일 이름을 HelloReversing.exe 로 해줍니다.

실행해보면 문자열이 성공적으로 변경되었습니다!


<Fig. 19>




2) 다른 메모리 영역에 새로운 문자열을 생성하여 전달

1) 방법은 MessageBoxW 함수의 파라미터인 문자열 버퍼의 내용을 직접 수정하는 방법이었습니다.

간단하지만 그만큼 단점도 존재합니다.
가령 훨씬 긴 문자열로 수정하고 싶을때 해당 버퍼 크기가 작다면
인접한 다른 메모리 영역을 침범하는 buffer overflow 가 발생할 것입니다.

이럴때 사용할 수 있는 방법이 바로 다른 버퍼 주소를 전달하는 것입니다.
적당한 메모리 주소에 변경하고자 하는 긴 문자열을 적어 놓고
MessageBoxW 함수에게 그 주소를 파라미터로 넘겨주는 것입니다.

즉, 버퍼 자체를 변경하는 것이죠.

아이디어가 좋긴 한데 한가지 고려해야 할 사항은 "메모리 어느 영역에 문자열을 써도 되는가?" 입니다.

자세한 설명은 PE header가상 메모리 구조를 알고 계셔야 하므로 나중에 자세히 다루도록 하고,
여기서는 임의로 적절한 영역을 선택하도록 하겠습니다.

우리가 방법 1) 에서 수정한 버퍼는 408000 ~ 40A000 영역 (.rdata section) 입니다.
이 부분을 다시 dump 윈도우로 살펴보죠. dump 윈도우에서 408000 주소로 갑니다. [Ctrl+G]

스크롤을 밑으로 내리다보면 .rdata section 은 아래와 같이 끝이 납니다.


<Fig. 20>


끝부분에 NULL 로 채워진 영역이 보입니다.

* 이곳은 보통 프로그램에서 사용되지 않는 NULL padding 영역입니다.
  프로그램이 메모리에 로딩될 때 최소 기본 단위(보통 1000)가 있습니다.
  비록 프로그램내에서 메모리를 100 크기만큼만 사용한다고 해도 실제로는 최소 기본 단위인 1000 크기가 잡히는 것입니다.
  (나머지 F00 크기의 사용되지 않는 영역은 그냥 NULL 로 채워집니다.)

이곳을 문자열 버퍼로 사용해서 MessageBoxW 함수에 넘겨주면 좋을 것 같습니다.
끝부분의 적당한 위치 409F50 에 출력하고 싶은 문자열을 써주면 됩니다. [Ctrl+E]


<Fig. 21>


버퍼를 새로 구성하였으니 그 다음에 할 일은 MessageBoxW 함수에게 새로운 버퍼 주소를 전달하는 것입니다.

그러기 위해서는 코드를 수정해야 하는데,
이번에는 Code 윈도우에서 Assemble 명령을 사용해서 코드를 수정해 보겠습니다.

아래 그림처럼 cursor 를 401007 주소위치에 놓고 Assemble 명령(단축키 [Space])을 내리면
아래와 같은 Assemble 윈도우가 나타납니다.


<Fig. 22>


새로운 버퍼주소인 409F50 을 입력합니다.

* 디버깅의 강력한 기능중의 하나가 바로 위와 같이 실행중인 프로세스의 코드를 동적으로 패치 시킬 수 있다는 것입니다.

* 향후 실습해 볼 crackme 샘플에서 serial key 검사 코드를 건너뛰는 방법도 코드를 동적으로 패치하는 것입니다.

OllyDbg 에서 MessageBoxW 함수를 실행하면 결과는 <Fig. 19> 와 같습니다.

그런데 위 수정된 코드를 파일로 만들면 제대로 동작하지 않을 것입니다.
이유는 409F50 메모리 주소 때문입니다.

실행 파일이 메모리에 로딩되어 프로세스로써 실행될 때 그대로 1:1 로 로딩되는 것이 아니라,
어떤 규칙에 의해서 올라가게 되며, 보통은 파일과 메모리가 1:1 로 매칭 되지도 않습니다.

즉, 메모리 409F50 에 대응되는 파일 offset 이 존재하지 않는 것이죠.

방법 2)를 파일로 저장하기 위해서는 아래 2가지 방법중에 하나를 사용하시면 됩니다.

  • PE header 를 분석하여 파일에 존재하지만 프로그램에서 사용되지 않는 공간을 버퍼영역으로 선정
  • 파일 끝을 버퍼 영역 만큼 확장하고, PE header 를 수정하여 그 부분을 메모리에 로딩시킴


역시 PE header 를 알아야 하기에 여기서는 설명을 생략하였습니다.

* 앞으로 작성하게 될 PE header 강좌도 기대해 주세요~



배운내용

- OllyDbg 기초 사용법

Step Into [F7] : 하나의 OP code 실행 (CALL 명령을 만나면, 그 함수 코드 내부로 따라 들어감.)
Step Over [F8] : 하나의 OP code 실행 (CALL 명령을 만나면, 따라 들어가지 않고 그냥 함수자체를 실행함.)
Execute till Return [Ctrl+F9] : 함수 코드 내에서 RETN 명령어 까지 실행 (함수 탈출 목적)
Restart [Ctrl+F2] : 다시 처음부터 디버깅 시작. (디버깅 당하는 프로세스를 종료하고 재실행 시킴.)
Go to [Ctrl+G] : 원하는 주소를 찾아감. (코드를 확인할 때 사용. 실행되는 것은 아님.)
Execute till Cursor [F4] : cursor 위치까지 실행함 (디버깅 하고 싶은 주소까지 바로 갈 수 있음.)
Comment [;] : Comment 추가
User Defined Comments [마우스 우측 메뉴 -> Search for -> User defined Comment] : 사용자가 입력한 comment 목록 보기
Set/Reset BreakPoint [F2] : BP 설정/해제
Run [F9] : 실행 (BP 가 걸려있으면 그곳에서 실행이 정지됨.)
All referenced text strings [마우스 우측 메뉴 -> Search for -> All referenced text strings] : 코드에서 참조되는 문자열 보기
All intermodular calls [마우스 우측 메뉴 -> Search for -> All intermodular calls] : 코드에서 호출되는 모든 API 함수 보기
Name in all modules [마우스 우측 메뉴 -> Search for -> Name in all modules] : 모든 API 함수 보기
Edit data [Ctrl+E] : 메모리 수정
Assemble [Space] : Assembly 코드 작성
Copy to executable file [마우스 우측 메뉴 -> Copy to executable file] : 파일의 복사본 생성 (변경사항 반영됨)

- Assembly 기초 명령어

CALL XXXX : XXXX 주소의 함수를 호출
JMP XXXX : XXXX 주소로 점프
PUSH XXXX : 스택에 XXXX 저장
RETN : 스택에 저장된 복귀주소로 점프

- 프로세스 data/code 패치 방법

OllyDbg 의 ‘Edit data’와’Assemble’기능 이용

- 용어

VA (Virtual Address) : 프로세스내의 가상 메모리
OP code (OPeration code) : CPU 명령어 (byte code)
PE (Portable Executable) : Windows 실행 파일(EXE, DLL, SYS)




배워야할 내용

Virtual memory
PE header
Stack frame
OP code (advanced)
OllyDbg (advanced)



Epilogue

여기까지 따라 오시느라고 수고 하셨습니다.

위에 나온 모든 내용을 한번에 이해하기는 어렵습니다.
2 ~ 3번 반복해서 읽고, 직접 실습해 보시기 바랍니다.

C 프로그래밍에서 Hello World! 는 가장 간단한 프로그램이었습니다.
마찬가지로 Hello World! 디버깅 또한 가장 간단한 디버깅입니다.

Hello World! 를 시작으로 C 프로그래밍을 정복하셨듯이 디버깅 역시 정복하시기 바랍니다.

리버싱에서 디버깅이 차지하는 비중은 매우 큽니다. 또한 가장 재미있습니다.

부디 제 글이 디버깅의 재미를 조금이나마 전달해 드렸으면 좋겠습니다.

감사합니다.

'analysis' 카테고리의 다른 글

키로거(KeyLogger) 분석 (3)  (4) 2009/05/03
키로거(KeyLogger) 분석 (2)  (51) 2009/05/03
키로거(KeyLogger) 분석 (1)  (14) 2009/04/24
Lena's Reversing for Newbies #10 (2)  (7) 2009/03/18
Lena's Reversing for Newbies #10 (1)  (16) 2009/03/17
abex' crackme #2 (2)  (27) 2009/03/01
abex' crackme #2 (1)  (31) 2009/02/28
abex' crackme #1  (34) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (3)  (39) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (2)  (5) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (1)  (47) 2009/02/26

Trackback Address :: http://www.reversecore.com/trackback/6 관련글 쓰기

  1. 베리굿 2009/11/07 23:01 댓글주소 | 수정 | 삭제 | 댓글

    감사합니다. 초보인 저도 이해가 그럭저럭 됩니다.^^

  2. 잘보고가요~ 2010/01/07 13:35 댓글주소 | 수정 | 삭제 | 댓글

    강좌 잘 봤습니다.
    그런데요. Reversing! 이라고 고친다음에..
    Save file 하는 곳을 못찾겠던데요.
    다른곳에서 찾았네요.
    수정한 부분에서 "오른마우스>Copy to executable"
    그럼 창이하나 뜨고, 여기서 다시
    "오른쪽마우스>Save file"이 존재하는 군요~

  3. 잘보고가요~ 2010/01/08 11:08 댓글주소 | 수정 | 삭제 | 댓글

    아.. 재가 Fig. 18을 제대로 못봤군요^^;

  4. 지상낙원 2010/05/13 08:18 댓글주소 | 수정 | 삭제 | 댓글

    정말 최고의 강의네요!
    자주 댓글을 달지않는 제가 이렇게 댓글을 남기게 하는 주인장님께 감사드리고 감탄이 절로 나옵니다.
    분야는 다르지만 저도 프로그래머로서 인터넷강좌 교수법을 깨치고 갑니다.
    나중에 책 내시면 당장 사 볼 것 같은 느낌이네요. ^^
    계속 들려서 지식을 얻어가겠습니다. 파이팅!

  5. 이누 2010/07/28 12:51 댓글주소 | 수정 | 삭제 | 댓글

    최고입니다!
    정말 쉽게 잘 설명해주시는군요!

    책은 준비중이시라니, 책도 너무 기대됩니다!

  6. 라이벌 2010/07/31 11:44 댓글주소 | 수정 | 삭제 | 댓글

    와.. 멋진 강의! 감사합니다.
    여기서 정말 많은 것을 배워갑니다. ^^
    책... 저도 너무 기대됩니다. 흘흘

  7. 2010/08/02 22:40 댓글주소 | 수정 | 삭제 | 댓글

    비밀댓글입니다

    • reversecore 2010/08/04 21:56 댓글주소 | 수정 | 삭제

      안녕하세요.

      이렇게 한번 해보시기 바랍니다.

      변경하신 부분을 마우스로 죽 선택하신 후 그 위치에서 마우스 우측메뉴를 선택해 보세요~

      감사합니다. ^^

  8. 공부중 2010/08/16 15:28 댓글주소 | 수정 | 삭제 | 댓글

    와..정말 자세한 설명 감사합니다..
    이해가 쏙쏙되네요...
    왠지 모르게 모든 리버싱을 할수있을것같은 자신감이 드는이유는 뭘까요 ㅎㅎ ^^

    • reversecore 2010/08/20 15:24 댓글주소 | 수정 | 삭제

      안녕하세요.

      리버싱에 자신감은 매우 중요합니다.

      알고보면 별거 아니거든요. 처음에 겁 먹지 않는 것이 중요하지요. ^^

      분명히 앞으로 리버싱을 잘 하실 것입니다.

  9. 감사합니다 2010/09/27 21:22 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요,
    정말 훌륭한 글이기에 감사를 표현하지 않으수 없네요.
    어려운 리버싱을 이해하는데 너무 많은 도움이 됩니다.
    감사합니다.

  10. reversingK 2010/11/09 20:51 댓글주소 | 수정 | 삭제 | 댓글

    ㅎㅎ Hello World1강 부터 3강 까지 너무 잘봤습니다.
    리버싱 처음 하지만 강의가 너무 좋아~ 쉽고 재미있게
    따라 한거 같습니다.. 뒤에 있는 강의도 기대됩니다^^
    열심히보고 열심히 배우겠습니다. 이런 좋은 강의
    올려주셔서 너무 감사합니다 ^^

  11. 리버싱입문자 2010/12/14 23:45 댓글주소 | 수정 | 삭제 | 댓글

    이제 막 리버싱에 입문했는데
    다른 강좌들은 너무 어려워서 고생하고있었는데
    이 블로그는 입문자에게 좋은 내용인거 같습니다.
    책도 곧 출판되신다는 소식 들었는데
    기대 많이 하고있습니다.
    감사해요~ ㅎ

  12. Reverser_H 2011/01/19 21:33 댓글주소 | 수정 | 삭제 | 댓글

    강의가 너무 좋네요~

    책이 나오면 바로 절판되지 않을까 하는

    걱정이 들기도 하는군요 ^^

    • reversecore 2011/01/20 00:08 댓글주소 | 수정 | 삭제

      ㅎㅎ

      그것도 나름대로 제겐 의미 있는 일입니다.

      리버싱을 널리 보급하는게 목표니까요~

      책이 나와도 블로그 내용은 계속 유지합니다.

      그리고 더 흥미로운 분석 위주의 내용으로 채울 예정입니다.

      감사합니다.

  13. 아르가페 2011/03/02 18:12 댓글주소 | 수정 | 삭제 | 댓글

    너무 쉽게 설명해주시니 초보인 저로서는 배우기가 그만이네요^^

  14. TeamKhan 2011/03/04 17:20 댓글주소 | 수정 | 삭제 | 댓글

    OllyDbg 에서 MessageBoxW 함수를 실행하면 결과는 <Fig. 19> 와 같습니다.

    그런데 위 수정된 코드를 파일로 만들면 제대로 동작하지 않을 것입니다.
    이유는 409F50 메모리 주소 때문입니다.

    실행 파일이 메모리에 로딩되어 프로세스로써 실행될 때 그대로 1:1 로 로딩되는 것이 아니라,
    어떤 규칙에 의해서 올라가게 되며, 보통은 파일과 메모리가 1:1 로 매칭 되지도 않습니다.

    즉, 메모리 409F50 에 대응되는 파일 offset 이 존재하지 않는 것이죠.
    ==========================================
    라는 본문이 잘 이해가안가는데요... 실행파일이 메모리에 로딩되어서
    실행될때 그대로 1:1로 로딩되는것이 아니라고하셧는데 1:1로 로딩안된다는게
    무슨소린지모르겟구요...파일과 메모리가 1:1로 매칭된다는 말씀도 전혀 이해가안가내요...
    무엇보다 409f50에 대응되는 파일 offset이 존재하지않는다는말도 이해가안갑니다...
    파일 offset이 뭐길래...ㅠ 정말 답답합니다 답변부탁드려요 ..ㅠ

    • reversecore 2011/03/10 11:05 댓글주소 | 수정 | 삭제

      안녕하세요.

      PE 파일 구조를 모르시면 저 말을 이해하기 힘드실 겁니다.

      그래서 제가 본문에 PE header 를 공부하셔야 한다고 코멘트를 달아두었죠. ^^

      제 원래 의도는 그러한 개념은 약간 어렵고 재미 없으니 차차 공부하기로 하고, 일단 디버깅에 재미를 가져보자는 것이었습니다.

      리버싱 입문 과정에서부터 PE Header 를 설명하면 너무 지루하기 때문에 일단 기반 기술 설명 없이 디버깅 자체만을 보여드렸습니다.

      질문하신 내용에 대해서 간단히 설명드리자면...
      실행 파일(EXE)이 메모리에 올라가서 실행되면, 파일로 존재할때의 모습과 메모리에 로딩된 모습이 서로 다릅니다.

      어떻게 달라지는지를 알려면 PE 파일 구조를 공부하셔야 되는 것이구요. 리버싱에서 필수 과목이라고 생각하시면 됩니다.

      그리고 파일 Offset 이란 말은 파일을 Hex Editor 로 봤을 때 파일 시작 부터의 거리를 의미합니다. (시작이 0, 끝은 파일크기-1 과 같지요.)

      제 블로그의 PE 설명을 처음부터 읽어보시구요. 또 질문 올려주세요~

      감사합니다.

  15. 대단하세요 2011/03/11 13:24 댓글주소 | 수정 | 삭제 | 댓글

    구글로 몇일쨰 강좌를쳐봐도 제대로된것도없고 어제여기를 발견하게되어서 막꼼꼼히살펴보고해서 지

    금 여기까지오게되었는데.. 제가 해보고싶은것은 게임실행파일이나 다른소프트웨어 등 리버싱을 하고싶은데

    그정도실력까지가려면 오래걸릴까요.. 이번년도는 하려고하는데.. 작성자분께선 정말대단하시네요

    • reversecore 2011/03/16 22:47 댓글주소 | 수정 | 삭제

      안녕하세요. 반갑습니다.

      음... 뭐든지 주변에 좋은 스승이 계시다면 금방 배울 수 있습니다. 1년정도 제대로 교육받으시면 충분히 원하시는 정도의 리버싱이 가능하십니다.

      하지만 저렇게 체계적으로 배울 수 있는 곳은 국내에 몇몇 회사 정도 뿐입니다. (사실 회사에서 배우는것도 거의 독학 수준입니다만... 그래도 아무때나 질문할 수 있다는 것은 엄청난 장점입니다.)

      인터넷으로 독학하신다면... 많은 시간을 투자하시고 시행착오도 겪으셔야 할 것이라고 생각됩니다.

      감사합니다.

  16. 아무개 2011/03/22 00:02 댓글주소 | 수정 | 삭제 | 댓글

    넘 좋은 글을 읽고 가네요...
    근데요.. 한가지 질문,,, 맨위에 있는 첨부파일의 정체는 몬가요?
    앞선 강의에서 사용하던 걸 패치한것 같진 않고요... ????

    • reversecore 2011/04/13 00:53 댓글주소 | 수정 | 삭제

      안녕하세요.

      HelloReversing.exex 파일 말씀이시죠?

      앞에서 소개한 HelloWorld.exe 를 패치시킨 파일 맞습니다. ^^

      감사합니다.

  17. 갓태어난.. 2011/04/26 17:05 댓글주소 | 수정 | 삭제 | 댓글

    본인에 것을 남에게 나누어 주는것이 결코 쉬운일이 아닌데

    정말 대단하신 분 같습니다.

    첫강부터 정말 설명을 너무 잘해주셔서..쉽게 따라 해보았습니다.

    천천히 꾸준이 열심히 따라가겠습니다.

    앞으로 많은 가르침을 주십시요..

    정말 감사합니다.

  18. 리버싱생초보 2011/06/26 20:13 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세여 강의 잘보고 있습니다.

    이 글을 따라 하던도중, 방법2)에서 문자열 출력할때 PUSH 409F50 으로 바꾼 후 실행을 하니

    헥스?에서 409F50주소에 있는 문자열만 출력되고 그 밑에 있는 409F60까지는 출력이 되질 않네

    요 ';;

    요약하면은 409F50과 409F60에 Hello, Reversing 을 입력하면 Hello, 까지밖에 안나옵니다 ㅜㅜ 어

    떻게 된걸까여.. 중간에 띄어쓰기하려고 4칸을 비워둔

    게 문제일까여;;?

    • reversecore 2011/06/29 12:09 댓글주소 | 수정 | 삭제

      안녕하세요.

      네, 문자열 중간에 NULL(0) 이 삽입되어서 그렇습니다.

      띄어쓰기를 하시려면 공백(Space) 에 해당하는 ASCII 코드 0x20 을 입력해 주시면 됩니다.

      감사합니다.

  19. bomberman 2011/07/15 01:45 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요, 리버싱 공부를 시작하는 학생입니다 ^^
    아는분 추천으로 들렀는데 정말 도움많이되었습니다.
    자주 들르겠습니다 ~

  20. bomberman 2011/07/15 01:45 댓글주소 | 수정 | 삭제 | 댓글

    안녕하세요, 리버싱 공부를 시작하는 학생입니다 ^^
    아는분 추천으로 들렀는데 정말 도움많이되었습니다.
    자주 들르겠습니다 ~

  21. 잘읽었습니다. 2011/08/01 23:08 댓글주소 | 수정 | 삭제 | 댓글

    잘읽었습니다.^^:
    감사합니다.