우리가 프로그래밍 을하게되면서 쉽게 구할수도있고 많이 접하는
어느한 디버거에 대해서 간략히 설명을해볼까합니다.
오늘은 OllyDBG 란 리버싱 역공학(등등)을하면서 많이 
접하게되는 디버거중 많은사랑을 받는 프로그램중하나라고 해도 과언이아닐것입니다.
본필자는 올리디버거 에대하여 어느정도 아는지식으로 간략히 
쓰는것이므로 올리디버거 에대해서 정확히 안다고말씀드리기가 
애매하다고밝힙니다.

올리디버거? 하면 무엇부터떠로르십니까?
Patch(핵,패치)? 대부분 어느정도 아시는분들은
아실것입니다.하지만 올리디버거가 
무엇인지 잘모르시는분들은 패치/핵 프로그램을 만들떄 쓰는것이구나
하고 생각하시는 분들도 계실테지요^^
그럼 지금부터 간략히 설명드러가겠습니다.
올리디버거 에서 의 장점 이무엇일까요?
저는 이렇게생각합니다. 
1.브레이크포인트
2.실행중인프로세스(예:k4.exe)가 실행중일떄 어태치 를통하여 실행중인 파일을 
   올리디버거로 불러와 분석을하며 원하는값을찾아서 어셈분기문 이나 핵스로 수정할수
   있는기능.
이렇게 2가지로 나누어봤습니다.
우리가 리버싱역공학 을접하게되면서 많이쓰이는게 올리디버거이기떄문입니다.
그러므로 우리는 올리디버거를 사용하기위해서 기초적인
설정과 플러그인 설정  한글 설정  그다음으로 각창의 설명과이해 
이렇게 나누어질것입니다.
물론 올리디버거에서 해당 타겟파일을 불러와서 분석을하면서 
눈여겨봐야할곳은 각 창에 보이는 것들을 유심히봐야할것이며 
어셈블리언어와 함수의역할등등 을 기본적으로 알고이해해야할것입니다
하지만 이것들만안다고해서 무작정 분석하고 어셈분기문을수정 한다해서
되는것이아닙니다.
바로 브레이크포인트 란것도알어야합니다
브레이크포인트란?무엇일까요^^
간략히정리해보면 이렇습니다.
올디로 해당타겟파일을 불러와서 
프로그램 연산이 나왔을떄 내자신이 지금 어느곳을작업하는지
확인할떄 쓰는것이 바로 브레이크포인트라 말할수있겠습니다.
그럼 브레이크포인트는 어떻게쓰냐?
바로이렇게 쓰면됩니다.
브레이크포인트(단축키:F2)를눌러서 브포를걸어준후 실행(단축키:F9)을눌러주게되면
프로그램연산은 자기자신이 작업한곳까지 실행하다가 브레이크포인트가걸린곳부터는
실행이되지않습니다.
더쉽게말해서 집을걸어가야하는대 바로앞에 장애물이 있습니다 그럼 잠시멈추었다가 
피해가겠죠? 
그리고 자기자신이 원하는곳에 브포를걸고싶으면 그곳에다가브포를걸수가있습니다.
그렇다고 아무곳에다가 브포를걸면 안됩니다.
또다른예를들어보죠
프로그램 연산이 1~9 까지 된다고가정해봅시다
그리고 1 2 3 4 5 6 7 8 9 <:~이런식으로요 
그런대 프로그래밍 4~5 과정이필요없다고해서 3~6으로 JMP 를시켯습니다.
그런 상황에 4~5 에다가 브포를걸면 어떨까요?
답변은 아무변화가없습니다.
그러므로 브레이크포인트거는것도 신중히생각하셔서 거셔야합니다
Ps:자신이 프로그램 연산을하던중 어떤부분이 실행될떄 어떤역학을하는지
       알고싶을떄 사용하시면 편리합니다.
 

DLL injection 원리 Reverse 

DLL injection은 간단히 말해 현재 메모리에 로드되어 있는 프로세스의 코드 영역에 접근해서 특정 dll을 로드하는 코드를 실행시켜 임의의 dll을 프로세스의 주소영역에 로딩시키는 것이다. 
현재 메모리에 로드되어 있는 프로세스의 코드영역에 우리가 원하는 코드를 삽입하는 방법은 2가지가 있다. 
첫번째로 VirtualAllocEx() API를 이용해서 타깃 프로세스의 가상 메모리 영역을 확보해서 그 영역에 WriteProcessMemory()로 우리가 원하는 코드를 삽입하는 방법이 있다. VirtualAlloc()으로 할당한 영역에 삽입한 코드를 실행시키기 위해서 thread 형태로 새로운 실행 플로우를 만든다.
그러나 VirtualAlloc() API는 Win NT 이상에서 지원하는 함수로써 win9x/ME에서는 지원되지 않는 API다. 즉 이 방법은 윈98 및 윈미에서는 사용할 수 없다는 것이다. 
두번째 방법은 ReadProcessMemory() 와 WriteProcessMemory()를 이용, 타깃 프로세스의 메모리에 직접 읽고 쓰는 방법이다. 이 API들은 윈도우즈 계열의 모든 플랫폼에서 지원하기 때문에 윈도우즈 버전에 관계없이 사용할 수 있는 장점이 있다.

그럼 현재 메모리에 로딩되어 실행중인 프로세스에 어떤 방법으로 코드를 삽입할수 있을까. 이것도 역시 윈도우즈에서 제공하는 디버깅용 API를 이용한다.

우선 타깃 프로세스를 디버깅용 API를 써서 실행즉시 suspend시킨다.

타깃 프로세스의 컨텍스트(process context: 프로그램 카운터와 레지스터등 프로세서의 상태를 말한다)와 코드를 삽입할 영역(코드섹션의 첫 페이지)을 읽어와서 다른 메모리에 백업한다.

해당영역에 임의의 dll을 로드하는 코드를 덮어쓴다.

인스트럭션 레지스터인 EIP에 실행시킬 코드의 주소를 저장시켜서 프로세스를 run시킨다. 물론 이때 dll을 로드하는 코드의 마지막 부분에는 브레이크 포인트를 set하는 코드(int 3h)가 들어가 있어야 한다.

dll을 로딩하는 코드가 실행된 후 타깃 프로세스는 INT3 명령에 의해 브레이크가 걸리게 된다. 이렇게 프로세스의 내부에 dll을 싸질러놨으니 완전범죄를 위해서 백업해 놓았던 프로세스 컨텍스트와 코드를 원래대로 되돌려놓고 EIP도 복구시켜 놓는다.

마지막으로 프로세스를 run시키면 감쪽같이 원래 실행상태로 되돌아가게 되는 것이다.

일단 이렇게 우리가 원하는 dll을 프로세스의 주소영역에 올려놓으면 dll내의 코드는 어플리케이션의 모든 메모리를 접근할 수 있고 제어할 수 있게되는 것이다. 
원리 설명은 여기까지 하고 실제 코드를 살펴보자. 
먼저 dll을 주입하고자 하는 타깃 프로세스를 실행시켜야 한다. 
STARTUPINFO           sInfo; 
PROCESS_INFORMATION   pInfo;

ZeroMemory((VOID*)&sInfo, sizeof(sInfo));

ret = CreateProcess(Filename, 0, 0, 0, FALSE, DEBUG_ONLY_THIS_PROCESS, 
                        0, 0, &sInfo, &pInfo); 
if(!ret) return FALSE;

타깃 프로세스를 디버그 모드로 create시킨다. fdwCreate 파라미터를 디버드 모드(DEBUG_ONLY_THIS_PROCESS)로 넘겨주고 프로세스를 실행하면 여러 디버그 이벤트가 발생하며 그때마다 타깃 프로세스는 실행을 멈추고 suspend상태가 된다. 
예를 들어 디버그 모드로 프로세스를 create하자마자 CREATE_PROCESS_DEBUG_EVENT가 발생하고 이때 마지막 파리미터인 pInfo에 타깃 프로세스가 로드될 메모리 주소, 프로세스 핸들, 프로세스 id등의 정보가 채워져 return되는 것이다. 
프로세스를 계속 진행시키려면 ContinueDebugEvent를 호출해서 실행제어를 타깃 프로세스에 넘겨주어야 한다. 디버그 이벤트중에서 우리가 처리할 디버그 이벤트는 EXCEPTION_DEBUG_EVENT이고 그외의 이벤트는 처리하지 않는다. 
디버그 이벤트를 처리하는 코드는 다음과 같다.

DEBUG_EVENT     dEvent; 
BOOL            bFirstBreak, bSecondBreak;

bFirstBreak = FALSE; 
bSecondBreak = FALSE;

while(1) 

        if( !(ret = WaitForDebugEvent(&dEvent, INFINITE)) )           // 이벤트 대기 
                return -1;

        continueStatus = DBG_EXCEPTIION_NOT_HANDLED; 
                 
        if(CREATE_PROCESS_DEBUG_EVENT == dEvent.dwDebugEventCode)    // 디버드 모드로 create하자마자 발생 
        { 
                BaseOfImage = dEvent.u.CreateProcessInfo.lpBaseOfImage;  // 프로세스의 base주소를 얻어올 수 있다. 
        } 
        else if(EXCEPTION_DEBUG_EVENT == dEvent.dwDebugEventCode)          // 디버그 이벤트 발생 
        { 
                if(EXCEPTION_BREAKPOINT == dEvent.u.Exception.ExceptionRecord.ExceptionCode) 
                {  //브레이크포인트에 걸렸을 때... 
                        if(FALSE == bFirstBreak) 
                        {   //첫번째 브포. 원하는 dll을 주입한다 
                                InjectDLL( pInfo.hProcess, pInfo.hThread, BaseOfImage,  
                                                      DllName);

                                bFirstBreak = TRUE; 
                        }else if(FALSE == bSecondBreak) 
                        {  //두번째 브포. 코드페이지를 원상태로 되돌려놓는다 
                                RestoreOriginalCodePage( PHandle, THandle, 0); 
                                bSecondBreak = TRUE; 
                        } 
                        continueStatus = DBG_CONTINUE; 
                } 
        } 
         
        if(EXIT_PROCESS_DEBUG_EVENT == dEvent.dwDebugEventCode) 
                break;

     // 실행제어를 debugged process에 넘긴다. 
    ContinueDebugEvent(dEvent.dwProcessId, dEvent.dwThreadId, continueStatus); 
}

일단 디버깅 당하는 프로세스가 메모리에 로드되면 EXCEPTION_BREAKPOINT가 발생한다. 첫번째 브포에서 프로세스의 첫번째 코드 페이지와 프로세스 context를 백업한 후, dll을 로딩하는 코드를 삽입해서 프로세스를 실행시키고 두번째 오는 브포에서 코드 페이지를 원래데로 되돌려놓는 작업을 하면 되는 것이다. 
프로세스 컨텍스트와 코드 페이지를 읽어오는 코드는 다음과 같다.

// Global Variables 
PVOID pFirstCodePage; 
CONTEXT OriginalContext;

BOOL InjectDLL(HANDLE hProcess, HANDLE hThread, VOID* hModuleBase, char *DllName) 

        FARPROC LoadLibProc; 
        CONTEXT MyContext;

        // DLL을 삽입하기 위해선 LoadLibraryA의 주소를 알아와야 한다. 
         LoadLibProc = GetProcAddress(GetModuleHandle("KERNEL32.dll"), "LoadLibraryA");   
         if (!LoadLibProc ) return FALSE;

         OriginalContext.ContextFlags = CONTEXT_FULL; //CONTEXT_CONTROL;         
         if(!GetThreadContext( hThread, &OriginalContext))              //  process context를 가져온다. 
                return FALSE;

        // debugged process의 첫번째 코드 페이지의 주소를 알아와야 한다 
        pFirstCodePage = GetFirstCodePage(hProcess, hModuleBase);        
        if (pFirstCodePage) return FALSE;

       // 첫번째 코드 페이지를 읽어와서 OriginalCodePage에 저장한다. 
        ret = ReadProcessMemory(hProcess, pFirstCodePage, OriginalCodePage, PAGE_SIZE, &dwRead); 
        if (!ret || PAGE_SIZE != dwRead)  
                return FALSE;

참고로 프로세스의 컨텍스트를 가져오는 부분에서 GetThreadContext()가 아닌 GetProcessContext()를 써야하는 것 아닌가 의아해할 사람도 있겠지만 context는 thread의 영역임을 알아두자. 
첫번째 코드 페이지(4096바이트)의 주소를 가져오는 GetFirstCodePage()에 대한 설명은 뒤에서 하겠다. 이제 타깃 프로세스의 첫번째 코드 페이지에 dll을 로딩하는 코드를 삽입해야 한다. 
방법은 다음과 같다.

        char NewCodePage[PAGE_SIZE] =  
        {        
                0xB8, 00, 00, 00, 00,   // mov EAX,  0h | Pointer to LoadLibraryA() (DWORD) 
                0xBB, 00, 00, 00, 00,   // mov EBX,  0h | DLLName to inject (DWORD) 
                0x53,                   // push EBX 
                0xFF, 0xD0,             // call EAX 
                0x5b,                   // pop EBX 
                0xcc                    // INT 3h 
        }; 
        int nob=15; 
        char *DLLName; 
        DWORD *EBX, *EAX;

        DLLName = (char*)((DWORD)NewCodePage + nob); 
        EAX = (DWORD*)( CodePage +  1); 
        EBX = (DWORD*) ( NewCodePage +  6);

        strcpy( DLLName, DllName );               // DllName: 삽입하고자 하는 dll의 full path 
        *EAX = (DWORD)LoadLibProc; 
        *EBX = (DWORD)pFirstCodePage + nob;

        // 코드를 첫번째 코드 페이지에 삽입한다. 
        ret = WriteProcessMmory(hProcess, pFirstCodePage, &NewCodePage, PAGE_SIZE, &dwRead); 
        if (!ret || PAGE_SIZE != dwRead)  
                return FALSE;

LoadLibraryA()를 호출하는 opcode를 만들어서 타깃 프로세스의 첫번재 코드 페이지에 덮어쓰고 있다. 여기서 dll을 로드하는 opcode를 만드는 방법은 위와 같이 해도 되고 아니면 아래와 같은 방법을 쓰기도 한다.

#pragma pack(1) 
typedef struct  

        BYTE            push; 
        DWORD   operand_push; 
        BYTE            call; 
        DWORD   operand_call; 
        BYTE            int3; 
        char            dllName[1]
} MY_CODE, *PMY_CODE;

BYTE NewCodePage[PAGE_SIZE]
PMY_CODE pNewCode; 
        
        pNewCode->push = 0x68;               // opcode 
        pNewCode->operand_push = (DWORD)pFirstCodePage + offsetof(MY_CODE, dllName); 
        pNewCode->call = 0xE8;                 // opcode 
        pNewCode->operand_call = (DWORD)LoadLibProc - (DWORD)pFirstCodePage 
                                                - offsetof(MY_CODE, call) - 5; 
        pNewCode->int3 = 0xCC;

        lstrcpy(pNewCode->dllName, DllName);   // Copy DLL name

        // 코드를 첫번째 코드 페이지에 삽입한다. 
        ret = WriteProcessMmory(hProcess, pFirstCodePage, &NewCodePage, PAGE_SIZE, &dwRead); 
        if (!ret || PAGE_SIZE != dwRead)  
                return FALSE;

여기서는 LoadLibrary의 offset주소를 계산해서 call 인스트럭션의 오퍼랜드를 만들었다. 파라미터로 dll명의 offset을 넘겨주고 있으므로 LoadLibrary에 의해 dll이 로드될 것이다. 어떤 방법을 쓰든 결과는 똑같다. 
두 방법 모두 LoadLibrary 의 호출이 끝난 후 브레이크 인스트럭션인 INT3(0xcc)를 삽입했으므로 dll이 로드되고 나서 브레이크가 걸리게 된다. 그럼 위에서 설명한 데로 두 번째 브레이크 이벤트가 발생해서 코드 페이지를 원래데로 되돌리는 RestoreOriginalCodePage()가 불려지게 된다. 
자, dll코드를 삽입시켰으니 이 코드를 실행시키도록 context를 변경시켜야 한다.

        MyContext= OriginalContext; 
        MyContext.Eip = (DWORD)pFirstCodePage;        //  프로그램 카운터 레지스터 변경 
        ret = SetThreadContext(hThread, &MyContext);  // 프로세스 컨텍스트 셋팅 
        if(!ret) return FALSE;

변경된 첫번째 코드 페이지를 실행시키도록 인스트럭션 포인트를 설정했다. 이렇게 해서 프로세스를 run시키면 dll을 프로세스의 주소영역에 로딩하는 opcode가 실행된다. 
위에서 설명을 빠뜨렸던 프로세스의 첫번째 코드 페이지를 가져오는 코드는 다음과 같다.

PVOID GetFirstCodePage(HANDLE hProcess, PVOID pProcessBase); 

    DWORD baseOfCode; 
    DWORD peHdrOffset; 
    DWORD dwRead; 
     
    // Read in the offset of the PE header within the debuggee 
    if ( !ReadProcessMemory(hProcess,                 // e_lfanew 변수값을 가져온다. 
                            (PBYTE)pProcessBase + 0x3C, 
                            &peHdrOffset, 
                            sizeof(peHdrOffset), 
                            &dwRead) ) 
        return FALSE; 
             
    // Read in the IMAGE_NT_HEADERS.OptionalHeader.BaseOfCode field 
    if ( !ReadProcessMemory(hProcess,                // BaseOfCode 변수값을 가져온다 
                            (PBYTE)pProcessBase + peHdrOffset 
                            + 4 + IMAGE_SIZEOF_FILE_HEADER 
                            + offsetof(IMAGE_OPTIONAL_HEADER, BaseOfCode), 
                            &baseOfCode, sizeof(baseOfCode), 
                            &dwRead) ) 
        return FALSE;

    return (PVOID) ((DWORD)pProcessBase + baseOfCode);   // 코드의 주소값 
}

PE 포맷에서 offset 0x3C은 dos stuff부분인 IMAGE_DOS_HEADER의 e_lfanew의 위치를 가리킨다. dos stuff은 윈도우 어플리케이션이 도스 모드에서 실행될때 도스 모드에서는 실행될 수 없음을 나타내는 오류 메시지를 나타내는 역활을 한다. e_lfanew는 실제 PE 포맷의 상대주소를 저장하고 있는 변수이다. 즉, PE 포맷에서 IMAGE_NT_HEADERS의 주소를 가지고 있다. 
PE 헤더의 주소를 가져왔으면 IMAGE_OPTIONAL_HEADER에서 코드 영역의 처음 주소가 저장된 BaseOfCode 변수값을 읽어올 수 있다. IMAGE_FILE_NT_HEADER의 시작 주소를 얻어왔으면 구조체 내의 Signature변수 4바이트와 IMAGE_SIZEOF_FILE_HEADER 구조체 다음에 위치하는 IMAGE_OPTIONAL_HEADER32 구조체 속에 BaseOfCode 변수가 들어 있다. BaseOfCode 변수의 위치는 하드코드로 계산할 수도 있지만 여기서는 offsetof라는 함수를 이용했다. offsetof(A, B)는 A구조체에서 B 구성원의 byte offset주소를 리턴하는 함수이다. 
참고로 같은 구조체에 있는 AddressOfEntryPoint를 코드 페이지의 시작 주소로 혼돈하지 않도록 한다. AddressOfEntryPoint는 코드 영역의 처음 주소가 아니라 Main 함수의 위치이다. 우리가 처음 프로그램 코드를 시작하는 Main이나 WinMain은 유저 어플리케이션의 시작 포인트지 프로세스의 startup 코드가 아니다. 
BaseOfCode는 offset주소이므로 process base주소에서 offset을 더하면 실제 코드 페이지의 절대 주소가 나온다.

다음은 원래 첫번째 페이지의 코드를 원상태로 되돌려 놓는 함수이다.


DWORD RestoreOriginalCodePage( HANDLE hProcess, HANDLE hThread, DWORD *outSize ) 

        BOOL ret; 
        DWORD written; 
        CONTEXT Context;

        if(outSize) *outSize = PAGE_SIZE;   //Just for user's info

        ret = WriteProcessMemory( hProcess, pFirstCodePage, OriginalCodePage, PAGE_SIZE, &written );

        if(!ret || (dwWritten != PAGE_SIZE) ) 
                return -1;

        Context.ContextFlags = CONTEXT_FULL; 
        // GetThreadContext( hThread, &Context);

        ret = SetThreadContext( hThread, (CONST CONTEXT*)&OriginalContext); 
        if(!ret) 
                return -1;

        return 0; 
}

첫번째 코드 페이지의 내용이 저장된 OriginalCodePage를 WriteProcessMemory로 쓰고 프로세스 컨텍스트를 원상태로 복귀시키고 있다.