9번 문제 : 송영균 이병의 제대일을 구하세요.





문제풀이------------------------------------------------------------------------------------------------

문제 풀이 보기

Posted by Dakuo


올리디버거 창들 중에 Executable Modules 창이 있다.

현재 불러온 프로세스에서 실행중인 모듈들을 보여준다.

한 프로그램에서 여러개의 모듈(ex. DLL 등)을 사용할 경우

올리디버거는 하나의 모듈만을 트레싱할 수 있기 때문에 제대로 분석할수가 없다.

따라서 Executable Modules 창을 이용해 실행 가능한 모듈을 골라서 중요한 코드에

BreakPoint 를 걸어놓음으로써 해당 코드가 실행될 때에 제어권을 올리디버거로 받아 문제를 해결할 수 있다.


일단 해당문제를 실행시켜보자.

Ezbeat 문제 패스워드 찾기
올바른 패스워를 찾으시오!
난이도 : ★☆☆☆☆

위의 메시지 박스가 뜨며 확인을 누르면

콘솔창 하나가 뜨며 Input Password : 를 입력하라고 뜬다.


올리디버거로 프로그램을 오픈한다.

메시지 박스가 뜨므로 back to user mode 를 이용한다.

Run(F9)을 한다. 메시지 박스가 뜨면 pause(F12)를 한다.

back to user mode(Alt + F9)를 한 후 확인창을 누른다.


그러면 다음과 같은 코드로 이동한다.

00401149  |.  57            PUSH EDI                                    ; /Style
0040114A  |.  68 5CD34000   PUSH CrackMe_.0040D35C       ; |E
0040114F  |.  68 80D34000   PUSH CrackMe_.0040D380        ; |Text = "올바른 패스워드를 찾으시오!.
                                                                                     난이도 : ★☆☆☆☆"
00401154  |.  51            PUSH ECX                                  ; |hOwner
00401155  |.  FF15 4CC14000 CALL DWORD PTR DS:[<&USER32.MessageBoxW>>; \MessageBoxW
0040115B  |.  6A 03         PUSH 3                                     ; /ShowState = SW_SHOWMAXIMIZED
0040115D  |.  68 40D34000   PUSH CrackMe_.0040D340        ; |c:\solve.exe
00401162  |.  FF15 18C04000 CALL DWORD PTR DS:[<&KERNEL32.WinExec>]  ; \WinExec

위의 코드를 보면 WinExec 라는 WinAPI 가 보인다.

이 WinExec 함수는 CreateProcess 와 같이 프로세스를 생성하는 함수이다.

따라서 위의 3줄을 해석해보면 

WinExec 함수를 사용하여 최대창으로(SW_SHOWMAXIMIZED) c:\solve.exe 를 실행시켜라라는 뜻이다.

즉, 우리는 분석하던 CrackMe_2.exe 프로세스가 아니라 solve.exe 프로세스를 분석해야 한다.


따라서 WinExec 함수까지 실행을 한후에 solve.exe 가 실행되면

Attach 로 solve.exe 프로세스를 올리디버거로 불러온다.

E창을 눌러 Executable Modules 창을 연다.


맨 위의 solve 를 클릭한다. 실행영역에 올라온 코드들이다.

조금 아래로 가서 살펴보면

0040105C   .  68 3CD34000   PUSH solve.0040D33C                      ;  ASCII "Input Password : "
00401061   .  E8 28020000   CALL solve.0040128E
00401066   .  83C4 08       ADD ESP,8
00401069   .  E8 2F030000   CALL solve.0040139D
0040106E   .  50            PUSH EAX
0040106F   .  6A 63         PUSH 63
00401071   .  8D55 90       LEA EDX,DWORD PTR SS:[EBP-70]
00401074   .  52            PUSH EDX
00401075   .  E8 69070000   CALL solve.004017E3
0040107A   .  83C4 0C       ADD ESP,0C
0040107D   .  6A 0E         PUSH 0E

정답으로 보이는 값이 보인다.

입력하면 congratulation~!   가 출력된다.

Posted by Dakuo

안티 디버깅(Anti-Debugging) : 디버깅을 방지하고 분석을 하지 못하도록 하는 기술.
디버깅을 당한다면 해당 디버거 프로그램을 종료시키거나 에러를 발생시키는 방법등 다양한 방법을 사용하여 분석을 방해한다.

(참고 : 디버깅(Debugging) : 프로그램의 특정 부분에 Break Point를 설정한 후 실행을 하면 그 위치에 프로그램이 멈추게 되며 메모리에 값이 제대로 들어가 있는지, 코딩한 흐름대로 프로그램이 진행되는지 단계적으로 실행할 수 있다)

안티 디버깅 기술에는 수많은 방법들이 존재하며 계속 발전하고 있으며 이에 따라 이를 우회하는 기술도 계속적으로 발전하고 있다.

 CheckRemoteDebuggerPresent() Windows API
 Detecting Breakpoints by CRC
 Detecting SoftlCE by Opening Its Drivers
 UnhandledExceptionFilter
 Hardware Breakpoint Detection
 INT 2D Debugger Detecton
 IsDebuggerPresent() Direct PEB Access
 IsDebuggerPresent() Windows API
 LordPE Anti Dumping
 NtGlobalFlag Edbugger Detection
 Obfuscated RDTSC
 OllyDbg Filename Format String
 FindWindow
 OllyDbg Instruction Prefix Detection
 OllyDbg INT3 Exception Detection
 NtSetInformationThread
 Memory Breakpoint Detection
 NtQueryInformationProcess()
 OllyDbg OllyInvisible Detection
 OllyDbg OpenProcess() HideDebugger Detection

 OllyDbg OpenProcess() String Detection
 OllyDbg OutputDebugString() Format String Vulnerability
 OllyDbg PE Header Parsing DoS Vulnerabilities
 OllyDbg Registry Key Detection
 OutputDebugString on Win2K and WinXP
 PEB ProcessHeap Flag Debugger Detection
 PeID GenOEP Spoofing
 PeID OEP Signature Spoofing
 ProcDump PE Header Corruption
 RDG OEP Signature Spoofing
 RDTSC Instruction Debugger Latency Detection
 Ring3 Debugger Detection via LDR_MODULE
 Single Step Detection
 SoftIce Driver Detection
 SoftIce Registry Detection
 SoftIce WinICE.dat Detection
 TLS-CallBack +IsDebuggerPresent() Debugger Detection
 Using the CMPXCHG8B with the LOCK Prefix
 kernel32!CloseHandle and NtClose
 kernel-mode timers

 User-mode timers
 Timestamp counters
 Popf and the trap flag
 Stack Segment register
 Debug registers manipulation
 Context manipulation
 CC scanning
 EntryPoint RVA set to 0

(참조 : http://www.openrce.org/reference_library/anti_reversing)



1. IsDebuggerPresent() :

PEB 구조체의 디버깅 상태값을 확인하여 디버깅을 당하고 있다면 1을, 아닐경우 0을 리턴.
커널 모드 디버거는 탐지하지 못하며 kernel32.dll에서 export되는 함수이며 보통 프로그램의 보호 차원에서 쓰인다.

IsDebuggerPresent() 샘플 코드다. (Microsoft Visual Studio 6.0에서 작성)

#define _WIN32_WINNT 0x0500
#include <stdio.h>
#include <windows.h>

int main()
{
         while(1)
        {
                      Sleep(1000);
                      if(IsDebuggerPresent())
                                printf("디버깅 당함\n");
                     else
                               printf("정상\n");
        }

        return 0;
}


(tip. #define_WIN32_WINNT 0x0500은 어떤 함수를 사용할 때 해당 프로그램이 실행될 최소 OS를 지정해 주는 것이다. 넣지 않으면 해당 함수를 인식하지 못하게 된다 0x0500은
Windows 2000이다)

파일을 실행시켜보면 디버깅을 당하지 않았기 때문에 '정상'이라는 메시지를 출력한다.
이제 올리디버거로 Attach 하면은


위와 같이 '디버깅 당함' 메시지를 출력한다. 이제 IsDebuggerPresent()를 우회하는 방법을 생각해보자. (물론 OllyDbg Advenced 플러그인을 이용하면 자동 우회된다)

IsDebuggerPresent()함수는 디버깅 당하면 1을 리턴한다.
즉 Call IsDebuggerPresent() 이 구문이 실행된 후에 EAX 값은 1일 것이다. ( 함수의 리턴값은 EAX에 저장됨)
mov eax, 0 을 추가한다면 항상 정상으로 인식할 것이다.

IsDebuggerPresent()함수를 콜하는 부분을 찾아보자.
Search for -> All intermodular calls를 선택한다.
(그상태에서 IsDebuggerPresent()를 입력해보면 제목표시줄에 Find 되는것이 보일것이다.)

00401047 CALL DWORD PTR DS:[<&KERNEL32.IsDebuggerPresent>]
kernel32.IsDebuggerPresent

더블클릭하여 이동후 IsDebuggerPresent()에서 마우스 클릭 후 'Enter'를 누르면 함수의 내용을 볼 수 있다. (되돌아가는 것은 '-')

Call IsDebuggerPresent() 대신에 mov eax, 0 으로 변경한 후 실행을 하면


'정상' 메시지만 출력되는 것을 볼 수 있다.



2. IsDebugged :

kernel32!IsDebuggerPresent 함수는 PEB 구조체의 BeingDebugged 멤버의 값을 확인하여 디버깅을 당하고 있는지의 여부를 알려준다. 따라서 실제 정보를 주는 IsDebugged에 대해서 알아보자.

PEB(Process Environment Block)는 프로세스에 대한 정보를 담고 있는 구조체

PEB의 구조체

typedef struct _PEB {
       BOOLEAN InheritedAddressSpace;
       BOOLEAN ReadImageFileExecOptions;
       BOOLEAN BeingDebugged;
       BOOLEAN Spare;
       ...
       ULONG PostProcessInitRoutine;
       ULONG TlsExpansionBitmap;
       BYTE TlsExpansionBitmapBits[0x80];
       ULONG SessionId;
} PEB *PPEB;

IsDebugged 샘플 코드다. (Microsoft Visual Studio 6.0에서 작성)

#define _WIN32_WINNT 0x0500
#include <stdio.h>
#include <windows.h>

Int Check();

int main()
{
       while(1)
      {
               Sleep(1000);
               if(Check()==0xFFFF0001)
                           printf("디버깅 당함\n");
               else
                           printf("정상\n");
       }
       return 0;
}

int Check()
{
        _asm
       {
              mov eax, fs:[30h]
              mov eax, [eax+2]
              test eax, eax
       }
}

(tip. 인라인 어셈블러를 쓰는 목적은 프로그램의 최적화를 위해 쓰이고 프로그래밍 언어에서 지원하지 않거나 미흡한 부분을 어셈블리어를 이용해서 직접 데이터를 가져오거나 계산할 때 유용하게 쓰일 수 있다)

실행을 하면 1초(1000ms) 단위로 Check()함수가 실행이 되고 리턴 값이 0 이어서 '정상'메시지가 출력된다.

올리디버거(OllyDbg)로 열어서 실행시켜보면 '디버깅 당함'이라는 메시지가 출력된다.
IsDebugged 를 우회할 방법을 생각해보자. IsDebugged 는 BeingDebugged 값을 가져오게 된다. 이값이 1이냐 0이냐에 따라 가져올 값이 1이냐 0이냐가 되어 디버깅 여부를 판단하게 된다. 따라서 무조건 0을 가져온다면 우회를 할수 있을것이다.

자세히 분석하기 위해 Search for -> All intermodular calls 눌러 Sleep를 찾아 이동한다.
(IsDebugged는 검색되지 않습니다)

004010A0  |.  E8 5B000000   |CALL Sample.__chkesp
004010A5  |.  E8 5BFFFFFF   |CALL IsDebugged.00401005

함수의 내용들 'Enter'로 들여다 보니 Check()함수는 두번째 Call 부분이다.

내용을 보니

mov eax, fs:[30h]
mov eax, [eax+2]
test eax, eax

인라인 어셈으로 입력했던 부분이다.
함수의 시작 부분에 BreakPoint 를 걸어 Run을 하여 여기까지 실행시킨 후 덤프창에서 fs:[30]로 이동해보면 00 00 01 00 FF FF FF FF 가 보일것이다. 3번째 값 01 이므로 디버깅 당했다는걸 알 수 있다.(안당했을시 00) 한단계식 실행해보니 mov eax, [eax+2]부분에서 값을 가져온다. 따라서 이부분을 0을 가져오게 한다면 우회가 될것이다.
즉 mov eax, [eax+2] -> mov eax, 0


다시 실행해보면 '정상'메시지가 뜨는 것을 볼수가 있다.(이상태에서 restart 하면 수정한 부분이 지워진다는것을 간과하지 마세요 restart 하고 수정하고 실행하세요)



3. NtGlobalFlags :

NtGlobalFlag 값을 이용해서 디버깅 여부를 판단하는 방법이다.
해당 프로세스가 디버깅될 때 설정되는 Flag 중의 하나이다

PEB(Process Environment Block)의 0x68 위치에 있는 NtGlobalFlag 값이 0이면 정상 0이 아니라면 디버깅을 당한것이라고 볼 수 있다.

PEB의 구조체에서 0x68 위치에 있는 NtGlobalFlag

000 byte InheritedAddressSpace
001 byte ReadImageFileExecOptions
002 byte BeingDebugged
003 byte SpareBool
004 void *Mutant
008 void *ImageBaseAddress
...
05c void *OemCodePageData
060 void *UnicodeCaseTableData
064 uint32 NumberOfProcessors
068 uint32 NtGlobalFlag
070 union _LARGE_INTEGER CriticalSectionTimeout
...
1dc uint16 Length
1de uint16 MaximumLength
1e0 uint16 *Buffer

디버깅을 당하게 되면 0x70이 설정되는데 다음 FLAG 값들이 더해진 결과이다.

FLG_HEAP_ENABLE_TAIL_CHECK(Heap Tail Checking)                    0x10
FLG_HEAP_ENABLE_FREE_CHECK(Heap Free Checking)                  0x20
FLG_HEAP_VALIDATE_PARAMETERS(Heap Parameter Checking)      0x40

NtGlobalFlags 샘플 코드다. (Microsoft Visual Studio 6.0에서 작성)

#define _WIN32_WINT 0x0500
#include <stdio.h>
#include <windows.h>

int Check();

int main()
{
       while(1)
      {
               Sleep(1000);
               if(Check()!=0)
                           printf("디버깅 당함\n");
               else
                           printf("정상\n");
       }
       return 0;
}

int Check()
{
        _asm
       {
              mov eax, fs:[30h]
              mov eax, [eax+68h]
              and eax, 0x70
              test eax, eax
       }
}

우회할 방법을 생각해보자. 디버깅을 당하면 0x68 위치에 0x70 값이 들어간다. 따라서 이 값을
0으로 바꿔주면 디버깅 여부에 상관없이 '정상' 메시지를 출력할 것이다.

Search for -> All intermodular calls 클릭 후 Sleep를 더블클릭하여 이동한다.

004010A0  |.  E8 6B000000   |CALL NtGlobal.__chkesp
004010A5  |.  E8 5BFFFFFF   |CALL NtGlobal.00401005

아까 보았듯이 Check() 함수는 두번째 부분이다
'Enter'키로 내용을 들여다본다.

00401038  |.  64:A1 3000000>MOV EAX,DWORD PTR FS:[30]
0040103E  |.  8B40 68       MOV EAX,DWORD PTR DS:[EAX+68]
00401041  |.  83E0 70       AND EAX,70
00401044  |.  85C0          TEST EAX,EAX

덤프창에서 fs:[30]로 이동해보면 7FFD4000으로 가는데 여기에 0x68을 더한 7FFD4068을 입력하면 0x70이 들어있는것을 확인할 수 있다.


따라서 AND EAX, 70 -> mov eax, 0 으로 바꿔준다.

Run을 해보면 '정상' 메시지가 출력된다.



4. CheckRemoteDebuggerPresent() :

CheckRemoteDebuggerPresent ()는 Windows XP 이상부터 사용할 수 있고
ZwQueryInformationProcess()를 사용하여 프로세스의 DebugProt 정보를 얻게 되는데 리턴값이 0이면 정상이고 0이 아니면 디버깅 중이다.

BOOL CheckRemoteDebuggerPresent(
        HANDLE hProcess,
        PBOOL pbDebuggerPresent
);

CheckRemoteDebuggerPresent() 샘플 코드다. (Microsoft Visual Studio 2005에서 작성)
(Microsoft Visual Studio 6.0에서 작성해봤었는데 에러 뜨네요)

#define _WIN32_WINNT 0x0501   ( 0x0501 = XP)
#include <stdio.h>
#include <windows.h>

int main()
{
         BOOL bDebugged = FALSE;
         while(1)
         {
                   Sleep(1000);
                   CheckRemoteDebuggerPresent( GetCurrentProcess(), &bDebugged );
                   if(bDebugged)
                               printf("디버깅 당함\n");
                   else
                               printf("정상\n");
          }
         return 0 ;
}

역시 올리디버거로 열어보면 '디버깅 당함' 이라는 메시지가 출력된다.

우회할 방법을 생각해보면 bDebugged 값을 0으로 수정해주면 디버깅 여부에 상관없이
'정상'메시지가 출력 될것이다. 또 분기문에서 점프구문을 무조건 정상쪽으로 이동하게 흐름을 정해주면 될것이다. 이번엔 점프구문을 수정하여 우회를 해보겠다.

Search for -> All intermodular calls 을 눌러 이번엔 CheckRemoteDebuggerPresent()
를 더블클릭하여 이동한다.

004113F8    FF15 A0814100   CALL DWORD PTR DS:[<&KERNEL32.CheckRemot>; kernel32.CheckRemoteDebuggerPresent
004113FE    3BF4            CMP ESI,ESP
00411400    E8 31FDFFFF     CALL CheckRem.00411136
00411405    837D F8 00      CMP DWORD PTR SS:[EBP-8],0
00411409   /74 19           JE SHORT CheckRem.00411424

위의 소스코드와 비교해보면 [EBP-8] = bDebugged 이다. 또 이 값은
한단계씩 진행을 하며 확인해보면 CheckRemoteDebuggerPresent()의 리턴값
(즉, EAX값 = 1)이라는 것을 알 수 있다. 따라서 CMP DWORD PTR SS:[EBP-8],0 에서 같지 않으므로 JE 구문에서 점프를 하지 않아 '디버깅 당함' 메시지가 출력된다. JE 문을 JMP 문으로 바꾼다면 디버깅 여부와 상관없이 ( 리턴값에 상관없이) 무조건 '정상' 메시지가 출력될 것이다.

(tip. Hex 값으로 수정하려면 74(=JE)를 EB(=JMP)로 하면 된다)



5. FindWindow :

FindWindow() API는 특정 윈도우 이름이나 클래스 이름을 찾아주는 함수이다.
(tip. 특정 프로그램의 윈도우 이름이 Null로 되어 있는 경우가 있기 때문에 클래스 이름으로 찾는것이 좋다)

FindWinodw()
HWND FindWindow(
        LPCTSTR lpClassName,
        LPCTSTR lpWindowName
);

파일의 정보들을 나타내주는 프로그램을 이용하여 클래스 이름이나 윈도우 이름을 찾는다.
(참조 : Property Edit(http://www.mh-nexus.de))



'OllyDbg'가 있다면 '디버깅 당함' 메시지를 출력해주는 FindWindow 샘플 코드다
(Microsoft Visual Studio 6.0에서 작성)

#define _WIN32_WINNT 0x500
#include <stdio.h>
#include <windows.h>

int Check();

int main()
{
         while(1)
         {
                 Sleep(1000);
                 if(Check()!=0)
                          printf("디버깅 당함\n");
                 else
                          printf("정상\n");
          }
          return 0;
}

int Check()
{
         char debugger[] = "OLLYDBG";
         int ret;
         _asm
         {
                 push 0        // WindowName
                 lea eax, debugger
                 push eax    // ClassName
                 call dword ptr FindWindowA
                 mov ret, eax
        }
        return ret;
}

올리디버거를 실행하는 즉시 '디버깅 당함' 이라는 메시지가 출력된다.

우회할 방법을 생각해보자 FindWindow 함수는 윈도우 이름과 클래스 이름을 인자로 가지는데 여기서는 윈도우 이름을 0으로 넣었다.(즉 파일명 변경으로는 해결할 수 없다) 그렇다면 두번재 클래스 이름에 넣은 'OLLYDBG' 문자열을 다른 값으로 변경한다면 FindWindow가 올리디버거를 찾지 못 할것이다.

해당 구문을 찾기위해 Ultra String Reference 플러그인을 이용한다.

(Release 폴더에 ustrref.dll을 ollydbg폴더에 Plugin폴더로 옮긴다)

올리디버거로 FindWindow.exe를 Attach 한 후
메뉴에 Plugin -> Ultra String Reference -> Find ASCII 를 누르면
해당 프로그램에서 쓰이는 문자열들을 볼 수 있으며 여기서 '디버깅 당함' 문자열을 클릭하면 해당 문자열을 stack에 push하는 곳으로 이동한다. ('OLLYDBG'라는 문자열은 보이지 않는다)

00401048  |.  FF15 80514200 |CALL DWORD PTR DS:[<&KERNEL32.Sleep>]   ; \Sleep
0040104E  |.  3BF4          |CMP ESI,ESP
00401050  |.  E8 4B010000   |CALL FindWind.004011A0
00401055  |.  E8 ABFFFFFF   |CALL FindWind.00401005
0040105A  |.  85C0          |TEST EAX,EAX
0040105C  |.  74 0F         |JE SHORT FindWind.0040106D
0040105E  |.  68 24004200   |PUSH FindWind.00420024                  ; /디버깅 당함\n

JE 구문에 의해서 흐름이 갈라지며 그 위에 두번의 CALL 이 존재하는데 'Enter'내용을 분석해보면 두번째 CALL 구문이 우리가 찾는 FindWindow 함수 이다. 그 내용을 보면

004010D9  |.  6A 00         PUSH 0                                   ; /Title = NULL
004010DB  |.  8D85 F8FFFFFF LEA EAX,DWORD PTR SS:[EBP-8]             ; |
004010E1  |.  50            PUSH EAX                                 ; |Class
004010E2  |.  FF15 94524200 CALL DWORD PTR DS:[<&USER32.FindWindowA>>; \FindWindowA

PUSH 0은 윈도우 이름이며 PUSH EAX는 클래스 이름이다. 즉 EAX 에 문자열 'OLLYDBG'가 있을 것이다. 이 부분에 BreakPoint 를 설정하여 확인해보면 추측이 맞다는 걸 알 수 있다.

004010D9  |.  6A 00         PUSH 0                                   ; /Title = NULL
004010DB      6A 01         PUSH 1
004010DD      90            NOP
004010DE      90            NOP
004010DF      90            NOP
004010E0      90            NOP
004010E1      90            NOP
004010E2      FF15 94524200 CALL DWORD PTR DS:[<&USER32.FindWindowA>>;  USER32.FindWindowA

PUSH EAX 대신에 PUSH 1 으로 바꿔주면 클래스 이름이 1인 것을 찾게 되므로 FindWindow함수를 우회할 수 있다. 실행을 해보면 '정상' 메시지가 출력된다.
(주의! : 처음에 PUSH EAX 를 PUSH 1로 바꾸니 밑에 4줄이 NOP로 바뀌더군요. 그래서 윗줄에 LEA EAX,DWORD PTR SS:[EBP-8] 을 PUSH 1로 바꾸었는데 원래 밑에 줄에는 영향을 안주더군요. 그러고 밑에 PUSH EAX는 NOP 처리 해주었습니다)

(위의 5가지의 안티 디버깅 기술은 Olly_Advanced에 있는 옵션의 안티 디버깅 우회에서 체크만 해주면 자동으로 해결된다 - 연습을 위해서)

Posted by Dakuo

크로스레퍼런스 : 함수가 사용된 곳을 교차하여 참고할 수 있게 표시를 해주는 것

(참고 : 어떤 함수가 어디에서 또 쓰였는지를 찾기 쉽도록 표시를 해주며 Win32 API에 대해서는 심볼 함수 이름까지도 표현해준다. IDA의 가장 강력한 기능이라고도 할 수 있다)

지뢰찾기(winmine.exe)를 크로스레퍼런스 기능을 이용해 해킹해보도록 하겠다.

'C:\WINDOWS\system32\winmine.exe'를 IDA로 open한다.

(참고 : 윈도우 기본 프로그램을 open할 경우 사용할 수 있는 디버거 정보가 담겨있는 심볼 파일을 IDA가 자동으로 연결하여 자세한 정보를 표현해준다.
           설정법 : http://dakuo.tistory.com/entry/아이다IDA에-심볼-파일-연결하기)

함수 목록을 살펴본다(Functions탭)


함수 목록을 보다 보면 ShowBombs(x)라는 폭탄을 보여준다는 이름의 함수가 있다. 정확하게 어떤 함수인지 더블클릭하여 추적해보자.

이동을 해보면 우측 상단에 GameOver(x) 부분에서 이 함수가 사용되었음을 표시해준다. 그 부분에 마우스 커서를 가져다 대면 풍선 도움말 형태로 그 사용된 지점의 코드를 보여준다. 마우스 휠을 이용하면 보여지는 코드의 양을 조절할 수 있다.



따라서 어떤 메뉴를 선택했을때 ShowBombs(x)함수를 실행하게 하면 게임중 그 메뉴를 선택했을때 지뢰가 보일것이다.

1. ShowBombs(x) 함수 분석

2. 원하는 메뉴에 Call ShowBombs(x) 코드 추가


먼저 ShowBombs(x)함수를 분석해서 x 파라미터를 확인해보겠다.

.text:01002F80 ; =============== S U B R O U T I N E =================================
.text:01002F80
.text:01002F80
.text:01002F80 ; __stdcall ShowBombs(x)
.text:01002F80 _ShowBombs@4    proc near               ; CODE XREF: GameOver(x)+2Fp

GameOver(x) 부분에서 ShowBombs(x) 함수가 실행되므로 함수 목록에서 GameOver(x)를 클릭하여 GameOver(x) 함수로 이동한다.
Call ShowBombs(x) 부분에 Breakpoint 를 설정한 후 디버깅(F9)해보면


eax에 0Ah를 넣고 그 eax를 push한다. 즉 ShowBombs(x)함수에 지뢰를 보여주는 파라미터 x = 0Ah이다.

이제 원하는 메뉴에 Call ShowBombs(x) 코드를 추가하겠다.
도움말을 클릭하면 도움말 대신 지뢰가 보이게 구현을 해보겠다.

함수목록중에 help와 관련된 것을 찾아보니까 DoHelp(x, x)함수가 보인다.
DoHelp(x, x)함수가 언제 실행되는지 찾기 위해 IDA의 크로스레퍼런스 기능을 이용한다.

DoHelp(x, x)함수를 더블클릭한 후 View->Open subviews->Cross references를 선택하여
목록에 Up p MainWndProc(x,x,x,x):loc_1001F0F call    _DoHelp@8       ; DoHelp(x,x)를
더블클릭하여 이동한다.

.text:01001F0B
.text:01001F0B loc_1001F0B:                      ; CODE XREF: MainWndProc(x,x,x,x)+31Dj
.text:01001F0B                 push    0
.text:01001F0D                 push    3
.text:01001F0F
.text:01001F0F loc_1001F0F:                       ; CODE XREF: MainWndProc(x,x,x,x)+33Aj
.text:01001F0F                                         ; MainWndProc(x,x,x,x)+340j
.text:01001F0F                 call    _DoHelp@8       ; DoHelp(x,x)
.text:01001F14                 jmp     loc_10021A9     ; default
.text:01001F14                                         ; jumptable 01001F75 cases 2,5,8-15

다음에서 Call _DoHelp@8 을 Call ShowBombs(x)로 바꿔주고 파라미터도 바꿔주면 끝날것 같다.

이제 분석이 끝났으니 올리디버거를 열어 코드를 수정하도록 하겠다.
(참고 : 분석기능은 IDA가 좋지만 디버깅 기능은 올리디버거가 좋기 때문에
'IDA 분석 + 올리디버거 디버깅'  하는 조합이 좋다)



IDA로 찾아놓은 주소 번지들을 이용하여 올리디버거에서 Ctrl+G(주소이동)으로 01001F0F주소로 이동한다.


파라미터 2개 중에 어떤것이 ShowBombs(x)에 영향을 주는지 알기 위해 우선 파리미터는 놔두고 Call winmine.01003D76만 ShowBombs(x)의 주소인 01002F80로 수정한다.

도움말 F1을 누르니 3이 표현된것을 볼 수 있다.


따라서 PUSH 3을 지뢰를 보여주는 파라미터인 0Ah로 수정한다.


성공하였다 이제 save file하자.



IDA의 강력한 분석 기능과 올리디버거의 강력한 디버깅을 잘 조합해서 사용한다면 리버싱을 보다 효율적으로 잘할 수 있다.

Posted by Dakuo

IDA의 디버깅을 통한 스택의 Stack 분석

분석해볼 프로그램은 IDA의 사용법을 알아보기 위해 예제로 썻던 것을 계속 쓰겠다.

다시 한번 설명하면 func( ) 함수에 세 개의 값을 파라미터로 전달하고 그 값들의 합을 리턴하는 프로그램이다.


#include <stdio.h>

 func(int a, int b, int c)
{
           return a+b+c;
}

int main(void)
{
           int sum;
           sum = func(1, 2, 3);
           return sum;
}


프로그램이 실행되기 전의 Stack이다. ESP, EBP의 위치를 살펴본다.


.text:00401060 push    ebp :
EBP가 가리키는 곳이 SFP(Saved Frame Pointer)로 변하고, 그 윗부분이 RET로 바뀌며 스택에 EBP값 12FFC0h값을 넣고 ESP는 한 칸 내려간다.
(참고 : EBP는 함수 시작전의 기준점이 되며. 이 최초의 EBP가 할당된 값을 스택에 저장한다. SFP(SavedFramePointer) : 스택에 저장된 EBP, RET : 함수 종료시 점프 할 주소 값을 저장)

.text:00401061 mov     ebp, esp :
EBP가 ESP가 가리키는 곳을 가리키게 된다. 즉 EBP와 ESP가 같은 지점을 가리킨다.

.text:00401063 sub     esp, 44h :
EBP가 가리키는 곳이 SFP로 변하고 바로 밑에 RET가 있다. 또한 ESP에서 44를 뺀다. 즉 ESP는 var_44변수를 가리키게 된다.

.text:00401066 push ebx :
ESP가 한 칸 내려가면서 현재 EBX값 7FFDA000이 스택에 들어간다.

.text:00401067 push    esi :
ESP가 한 칸 내려가면서 현재 ESI값 01B26D97이 스택에 들어간다.

.text:00401068 push    edi :
ESP가 한 칸 내려가면서 현재 EDI값 01B26CF8이 스택에 들어간다.
       
.text:00401069 lea     edi, [ebp+var_44] :
[ebp+var_44]의 주소를 EDI에 넣는다.

.text:0040106C mov     ecx, 11h :
11을 ECX에 넣는다.

.text:00401071 mov     eax, 0CCCCCCCCh :
CCCCCCCC를 EAX에 넣는다.

.text:00401076 rep stosd :
기본 할당 공간(ECX값 11)을 EAX값으로 채운다, EDI는 반복할 때마다 한 칸 올라간다.
                                    
.text:00401078 push    3 :
ESP가 한 칸 내려가면서 3값이 들어간다.
                       
.text:0040107A push    2 :
ESP가 한 칸 내려가면서 2값이 들어간다.

.text:0040107C push    1
ESP가 한 칸 내려가면서 1값이 들어간다.

.text:0040107E call    j_func :
j_func를 call한다.
call j_func의 내부로 추적해 들어가겠다.(Step Into)


stack

       .text:00401020 push    ebp :
       EBP가 가리키는 곳이 SFP(Saved Frame Pointer)로 변하고, 그 윗부분이 RET로
       바뀌며 ESP는 다음 주소를 가리킨다.

      .text:00401021 mov     ebp, esp :
      EBP가 ESP가 가리키는 곳을 가리킨다.

      .text:00401023 sub     esp, 40h :
       EBP가 가리키는 곳이 SFP로 변하고 바로 밑에 RET가 있다. 또한 ESP에서 40을
       뺀다. 즉 ESP는 var_40변수를 가리킨다.

      .text:00401026 push    ebx : 
      ESP가 한 칸 내려가면서 현재 EBX값 7FFDB000h이 스택에 들어간다.
      
      .text:00401027 push    esi :
      ESP가 한 칸 내려가면서 현재 ESI값 01B26D97이 스택에 들어간다.

      .text:00401028 push    edi :
      ESP가 한 칸 내려가면서 현재 EDI값 12FF80h이 스택에 들어간다.

      .text:00401029 lea     edi, [ebp+var_40] :
      [ebp+var_40]의 주소를 EDI에 넣는다.

      .text:0040102C mov     ecx, 10h :
      10을 ECX에 넣는다.

      .text:00401031 mov     eax, 0CCCCCCCCh :
      CCCCCCCC를 EAX에 넣는다.

      .text:00401036 rep stosd :
      기본 할당 공간(ECX값 10)을 EAX값으로 채운다, EDI는 반복할 때마다 한 칸 올라 간다.

      .text:00401038 mov     eax, [ebp+arg_0] :
      [ebp+arg_0]의 값인 1을 EAX에 넣는다.

      .text:0040103B add     eax, [ebp+arg_4] :
      [ebp+arg_4]의 값인 2를 EAX에 더한다. (EAX = 1+2 = 3)

      .text:0040103E add     eax, [ebp+arg_8] :
      [ebp+arg_8]의 값인 3을 EAX에 더한다. (EAX = 3+3 = 6)

      .text:00401041 pop     edi :
      스택의 값(0012FF80)을 꺼내서 EDI에 넣는다. ESP는 한칸 내려간다.

      .text:00401042 pop     esi :
      스택의 값(01B26D97)을 꺼내서 EDI에 넣는다. ESP는 한칸 내려간다.

      .text:00401043 pop     ebx :
      스택의 값(7FFDB000)을 꺼내서 EDI에 넣는다. ESP는 한칸 내려간다.
      
      .text:00401044 mov     esp, ebp :
      EBP가 ESP가 가리키는 곳을 가리킨다.

      .text:00401046 pop     ebp :
      스택의 값(12FF80h)을 꺼내서 EBP에 넣는다. ESP는 한칸 내려간다.

      .text:00401047 retn :
      ESP는 한칸 위를 가리킨다.


.text:00401083 add     esp, 0Ch :
ESP에 0Ch를 더한다.

.text:00401086 mov     [ebp+var_4], eax :
EAX의 값인 6을 ebp+4에 위치에 넣는다.

.text:00401089 mov     eax, [ebp+var_4] :
의미없는 명령으로 ebp+4의 값인 6을 eax에 넣는다.

.text:0040108C pop     edi :
스택의 값(01B26CF8)을 꺼내서 EDI에 넣는다. ESP는 한칸 내려간다.

.text:0040108D pop     esi :
스택의 값(01B26D97)을 꺼내서 ESI에 넣는다. ESP는 한칸 내려간다.

.text:0040108E pop     ebx :
스택의 값(7FFDB000)을 꺼내서 EBX에 넣는다. ESP는 한칸 내려간다.

.text:0040108F add     esp, 44h :
ESP에 44를 더한다. EBP가 가리키는 곳을 가리킨다.

.text:00401092 cmp     ebp, esp :
EBP와 ESP값을 비교한다. (값이 같다)

.text:00401094 call    __chkesp :
__chkesp를 call한다.
건너뛰겠다.(StepOver)

.text:00401099 mov     esp, ebp :
EBP의 주소를 ESP에 넣게 되어 ESP는 현재의 EBP주소를 가리킨다.

.text:0040109B pop     ebp :
EBP는 이전의 EBP인 12FFC0h을 가리키게 되고 ESP는 한칸 내려간다.

.text:0040109C retn :
ESP가 가리키는 4011D9h주소로 이동하고(call명령 다음의 코드) ESP는 한칸 위를 가리킨다.
(참고 : 즉 처음 EBP값을 스택에 넣어 함수의 시작주소를 저장하고 RET로 함수가 끝난 후 메인루틴의 call 함수 다음의 코드로 리턴하여 메인루틴을 실행한다)

Posted by Dakuo
레나(Lena)라는 제작자가 리버싱 플래시 강좌 40강 중에 3번 강좌에 있는 RegisterMe.exe와 RegisterMe.Oops.exe파일을 풀어보겠다. 강좌 파일을 다운로드 받으면 연습 문제와 강의 파일이 모두 들어 있다.

참고로 관심이 있으신 분들은 1강부터 40강까지 순서대로 모두 보는 것을 추천한다. 자료는 모두 영문이다. 조만간 풀이를 올리도록 하겠다.



문제의 설명을 보면 nag를 제거하고, 헤더 문제를 해결이라고 되있다. nag는 경고창 정도를 의미한다.

#03.tutorial 폴더의 files 폴더에 RegisterMe.exe 문제를 올리디버거로 실행해보면


nag 창이 떴다. 확인을 누르면 메인 폼이 뜬다.


종료를 하면 또 nag 창이 뜬다.


처리해야 할 부분은 실행할 때 1번, 종료할 때 1번 총 두 곳이다.

대부분 Strings를 검색해보기 마련인데, 패킹이나 암호화 프로텍터가 있거나, 함정이 숨겨져 있는 경우가 많기 때문에 올리디버거로 재시작하고, 초반부터 F8로 StepOver해 보겠다.


GetModuleHandleA를 통해서 얻어진 값을 비교한 후에 JE 구문을 만나 첫 nag 창이 뜬다.
강제로 jump를 시켜서 진행해보면, 종료할 때에 창이 또 뜨게 된다. 모든 nag 창을 강제로 jump시켜버려도 되지만, PE 구조를 이용하기 위해 EP 주소를 변경하여 해당 nag가 실행되지 않도록 해보겠다.

(윈도우 PE 구조 참고 : http://dakuo.tistory.com/entry/Windows-PE-구조)

M(Memory Map)을 클릭해서 PE 헤더 부분을 확인해보면


00400000주소부터 사이즈인 0x1000h까지가 PE 헤더이다. 해당 라인에서 더블클릭하면 DUMP창이 나타나고, DOS 헤더 구조를 볼 수 있다.


DOS 헤더를 지나 PE 구조 부분까지 내려보면 PE signature 부분을 시작으로 해서 PE 구조가 나타난다.


004000E8    00100000    DD 00001000          ;  AddressOfEntryPoint = 1000

ImageBase 값에 OEP 값을 더해서 시작하므로 00401000 주소에서 코드가 실행된다.

시작되는 지점을 수정해서 첫 번째 nag 창을 제거할 것이므로 Memory Map 창에서 PE 덤프를 하여 나타난 OEP 주소지(004000E8)로 Hex dump 창에서 이동(Ctrl+G)한다.


덤프된 hex 값 00 10 00 00 을 확인하고 24 10 00 00 으로 수정한다. 수정할 곳을 블록잡고, 우클릭을 하여 Binary -> Edit를 선택하여 수정하고 저장하자.
Hex dump에서 24 10 00 00으로 수정하면 리틀엔디안에 의해서 00 00 10 24로 처리된다.

004000E8    24100000    DD 00001024          ;  AddressOfEntryPoint = 1024


저장한 파일을 실행해보면 첫 번째 nag는 해결되었다.



종료시에 나타나는 nag 창은 프로그램을 종료할 때 나타나는 것으로 Messagebox 부분을 Nop(No Operation) 처리하면 된다. 종료할 때에 나타나는 nag 창은 어떻게 찾는 것이 좋을까?
1. nag 창에 나타나는 문자열을 추출하여 검색.
2. Back To User 모드 기능 사용
3. Commandline or Commandbar 플러그인 사용 bpx MessageboxA로 검색.

각각 이미 서술한 방법이니 3가지 방법중에 마음에 드는 것으로 골라 해당 코드 부분을 찾자.


PUSH 0부터 Call MessageBoxA까지 블록 선택하고 Binary -> Fill with NOPs를 선택한다. 수정후 저장한다. 실행해보면 nag 창이 하나도 안뜨며 문제 풀이가 끝났음을 알 수 있다.



이번에는 RegisterMe.Oops.exe파일을 풀기 위해 올리디버거로 열어보자. 그러면 화면과 같은 잘못된 실행 포맷이라는 에러 문구가 나타난다.

확인을 누르면 이상한 주소지가 선택되어 디버거가 실행될 것이다. 이문제는 기존 문제에서 PE 구조만 꼬아놓은 경우로, 수작업으로 수정하거나 자동으로 PE 구조를 보정해주는 플러그인을 이용하는 방법을 소개할 것이다.

먼저 PE 구조를 살펴보고, 어떤 문제가 있는지 확인해보자.
M(MemoryMap)을 선택한 후 PE header 부분을 더블클릭하여 Dump 창을 연다.


일반적인 PE Header의 길이는 1000인데, 5000으로 되어 있는 것을 체크하고 더블클릭하여 덤프된 창에서 내려가면서 PE 구조를 확인한다.


AddressOfEntryPoint = 1000, ImageBase = 400000. EP(시작되는 부분)는 00400000 + 1000 = 00401000이다. 직접 CPU 창에서 00401000 주소지로 이동(Ctrl+G)하고, BreakPoint(F2)를 걸면 올리디버거가 혼란스러워 하며, 다음과 같은 경고를 낸다.


~~~~~~~~~~~~~~

'예(Y)' 버튼을 눌러 Breakpoint(F2)를 설정하고, Run(F9)을 하면 00401000에서 정지된다.

PE 구조가 잘못되었더라도 시작 지점까지 로드가 되었기 때문에 M(Memory Map)을 열어서 다시 PE 구조를 확인해보면 아까와는 다르게 나타난다.


PE 사이즈는 5000이고 text 섹션은 없어져 있는 상태이다. PE 구조를 DUMP에서 보면 더 확실하게 우리가 알고있는 일반적인 PE 구조와 어떻게 다른지 알 수 있다.


이상한 부분과 수정해야 할 값을 정리하면 다음과 같이
NumberOfRvaAndSize를 제외하고 사이즈 값을 크게 늘려놓은 것을 복원하는것 뿐이다.

004000DC    00040040    DD 40000400          ;  SizeOfCode = 40000400 (1073742848.)
: 40000400 -> 400

004000E0    000A0040    DD 40000A00          ;  SizeOfInitializedData = 40000A00 (1073744384.)
: 40000A00 -> A00

004000EC    00100040    DD 40001000          ;  BaseOfCode = 40001000
: 40001000 -> 1000

004000F0    00200040    DD 40002000          ;  BaseOfData = 40002000
: 40002000 -> 2000

00400134    04000040    DD 40000004          ;  NumberOfRvaAndSizes = 40000004 (1073741828.)
: 40000004 -> 10 (이 값은 옵셔널 헤더의 디텍터리 엔트리 개수로 일반적으로 10이며, 이 값을 변경함으로써 올리디버거가 오작동하게 만들 수 있다고 알려져 있다.)

00400138    00005000    DD 00500000          ;  Export Table address = 500000
: 500000 -> 0

0040013C    00000500    DD 00050000          ;  Export Table size = 50000 (327680.)
: 500000 -> 0

이문제는 문제 제작자가 사이즈 값들이 400이면 40000을 앞에 붙여 PE 구조를 바꾸어놓거나 특별한 값으로 변경하여 올리더버거가 잘못 작동하도록 해둔 것이다.
수정을 할 때에는 NumberOfRvaAndSzies 값의 경우 hex dump 창에서 00400134 주소로 이동(Ctrl_+G)하여 , 기존의 값인 40000004를 10으로 변경하면 된다. 리틀엔디안 값으로 표시되어 04 00 00 40으로 나타나 있으니 거꾸로 뒤집어서 10 00 00 00 으로 수정해서 정상적인 값으로 복원시키면 된다. 나열한 항목들을 모두 수정한 후에 바이너리를 저장하고 올리디버거로 다시 열어 PE 구조를 확인하면 text 색션도 나타나며, 기존의 nag 창이 뜨는 문제와 동일하게 나타나게 된다.

참고로 Hex Dump에서 Binary -> Edit 기능을 사용할 때는 PE 구조 부분을 한꺼번에 블록을 씌워서 수정을 하도록 하고, 아니면 헥스에디터를 이용하여 수정하는 것이 더 편할 수도 있다.



Tip : 올리디버거의 플러그 인 중 Olly_Advanced_v1.27.rar을 소개한다. 이것은 설치하여 옵션을 조절하는 것만으로 자동으로 fake된 ExportTable을 수정해주거나 Copy to Exe 메뉴에서 에러나는 버그를 수정해주고, 그 외에도 anti debugging을 우회할 수 있는 기능 등을 사용할 수 있다.


다운받아서 advancedolly.dll 파일만 올리디버거의 plugin 폴더에 넣어주면 된다.
다음은 Olly Advanced 1.27의 옵션창이다.


Bugfixes 탭에는 올리디버거의 버그를 고치는 옵션들이 있다.

Additonal Options 탭에는 여러 기능들이 있는데 잘 생각해서 체크하기 바란다. 나중에 플러그인을 많이 설치하다 보면 플러그인들끼리 기능상 충돌이 생기게 된다 따라서 필요할때만 켜놓는것이 좋다.

Anti-Debug 탭에는 디버그하는 것을 막는 루틴들이 여러 가지 있는데 그런 부분들을 우회할 수 있도록 해준다.


마지막에 풀었던 PE 헤더를 꼬아놓은 문제는 이 플러그 인을 설치한 후에 열면 모두 정확한 값으로 수정되어 열리기 때문에 풀이에서처럼 수작업을 해줄 필요가 없었다.

Posted by Dakuo

KeyFile : 인증을 위해서 시리얼키를 원하는 것과 비슷하게 키값을 파일에 저장하거나 어떤 파일 이름이 같은 위치에 있는 것을 확인하는 인증을 할 때 사용되는 파일을 의미한다.

파일과 관련된 문제에서는 파일이나 레지스트리 사용하는 것을 확인하기 위해서 모니터링툴을 사용하는데, Filemon을 주로 사용할 것이다.
         
                                       (Filemon 사용법 참고 : http://dakuo.tistory.com/22)


CrackMe 15 문제를 풀어보도록 하겠다.
다운로드 하여 Project1.exe을 실행해보면


올리디버거로 열어서 스트링에 참고할 만한 내용이 있는지 찾아본다.
스트링 추출을 하고 살펴보면 solved라는 문자열을 보인다.


이상한 점은 File Monitor라고 해서 Sysinternals 홈페이지 표시되어 있다는 것이다.
아마도 문제 제작자가 파일을 모니터링 해보라고 힌트를 준것 같다. 프로그램을 실행해 놓고 Filemon을 실행해보도록 하겠다.


Filemon을 실행하고 문제 파일의 이벤트만 볼수 있게 Incude에 Project1.exe를 넣고 OK 버튼을 누른다.


Filemon을 켜놓은 상태에서 문제 파일을 실행하면 화면과 같은 이벤트를 볼 수 있다.
C:\challneges-crackme15\kernel32.dll을 찾고 있다. 따라서 해당 위치에 kernel32.dll이라는 이름으로 파일을 만들어둔다. 파일을 만들어도 문제 파일에서는 A 문자열만 나타난다.

올리디버거를 열어서 solved 위치 근처를 분석해보면


mov 명령으로 solved 문자열을 넣고 뭔가 call을 하는 것을 알수 있다.
이 명령이 시작되는 부분을 보면 분기점이 존재하고, 이 분기점의 점프 위치는 solved의 다음 위치이다.

00442514  |. /E9 8E000000   JMP Project1.004425A7

여기서 분기를 못하도록 JMP 구문을 NOP 처리한다.

JMP 구문이 작동하지 않고 다음 명령들이 진행되는데, 그 후에 점프 구문을 또 만나게 된다.

00442574  |. /74 51         |JE SHORT Project1.004425C7

이 부분을 한번 더 NOP 처리해주면 문제는 해결되었다고 표시된다.




또 다른 풀이로는 처음 점프 구문을 solved 문자열이 mov되는 곳으로 바로 이동시켜도 같은 결과를 낼 수 있다.

00442514     /EB 7A         JMP SHORT Project1.00442590

이번에는 어셈코드를 직접 수정하지 않고 hex dump 창에서 바이너리를 직접 수정하는 방법을 설명하겠다. 일단 Ctrl+F2를 눌러 문제 파일을 재시작하고 두 번째 풀이 위치인 00442514로 이동(Ctrl+G)을 한다.

00442514     /E9 8E000000   JMP Project1.004425A7

현재 이렇게 되어있는 것을 다음처럼 변경하려고 한다.

00442514     /EB 7A         JMP SHORT Project1.00442590
00442516     |90            NOP
00442517     |90            NOP
00442518     |90            NOP

스페이스바를 눌러서 어셈코드 수정 창에서 바꿔주어도 되지만, hex dump 부분의 바이너리 를 수정해보겠다.

(참고 : 어셈블리어를 수정하고 그것을 취소하려고 할 때는 수정된 라인에 커서를 둔 상태에서 Alt+BackSpace를 누르면 된다)


기존에 있는 값은 E9 8E 00 00 00 이고, 수정해야 하는 값은 EB 7A 90 90 90 이다.
블록을 씌운 다음에 우클릭 메뉴에서 Binary -> Edit 를 선택한다.


바이너리를 수정해준다.


저장한 파일을 실행해보면 문제 풀이가 완료됨을 알 수 있다.



이와 같은 리버싱 과정들을 보면 간단한 인증 절차는 점프문 한 개 수정하는 것으로도 우회가 가능하다.

Posted by Dakuo
키젠 문제는 프랑스의 haiklr라는 아이디를 쓰는 사람의 홈페이지에서 다운받았다.
문제를 다운로드 받아서 압축을 풀어보면 ReadMe.txt 파일이 있다.


Type : Keygenme
Level : Newbie
Langage : C (console)
Packed : Non

시리얼키들을 생성해내는 문제고, 난이도는 쉬운 편이며, C언어 콘솔어플리케이션이고, 패킹은 하지 않았다고 설명되어 있다.

문제를 실행해보자.


Name과 Serial을 입력하라고 나타난다.
입력을 하니까 '잘못된 패스워드'라는 문자열이 출력된다.

올리디버거로 열어서 어떤 알고리즘에 의해서 잘못된 패스워드로 판단했는지 확인해보고 정확한 시리얼값을 찾아보겠다.


문제 파일을 열고, 마우스 우클릭 메뉴에서 Search for -> All referenced text strings를 선택하여 문자열을 추출한다.


시리얼키가 일치했을 때 나타날 "Yeah, c'es" 부분에서 더블클릭하여 해당 위치로 이동한다.
어셈블리 코드를 보면서 프로그램을 분석해보면 성공 메시지 부분에서 위로 이동하면 Name을 입력받는 부분이 나타난다.


'[x] Name : '이 출력되고, scanf로 입력값을 받고, strlen을 통해서 이름의 길이를 체크한다.


어셈블리 코드를 보면 CMP를 이용해서 조건이 '3 < Name 길이 < 15'와 같은지 비교하고
맞으면 JMP구문에서 004014B0주소로 점프하고, 맞지 않으면 점프해서 printf하고 system으로 pause명령을 실행하게 된다.

004014B0주소로 가면 시리얼키를 넣는 부분이 나타나고, 입력한 값이 반복문을 통해서 변경된다.


시리얼값을 입력받은 다음에 반복되는 형태로 JMP를 하여 위쪽으로 이동되는 것을 볼 수 있다. 시리얼값이 들어온 부분 다음을 Breakpoint(F2)설정하고 StepOver(F8)을 누르면서 Value창을 확인하면서 반복되는 과정을 보면 Name값의 문자열이 한 개씩 들어오면서 중간에 공백 문자가 들어가는 것을 볼 수 있다.

Name에 'test'을 입력하고 값이 변경되는 과정을 주요 어셈블리 코드들을 보며 체크해보겠다.

StepOver(F8)해가면서 하단의 Value창과 우측 상단의 레지스터 값들을 체크하면서 봐야한다.
(Hex dump창의 아래 나온 변수의 주소값으로 이동해서 확인을 해보면 값들이 어떻게 바뀌어 가는지를 볼 수 있다)

004014E0   > /8B45 E8       MOV EAX,DWORD PTR SS:[EBP-18]
// [EBP-18]에는 test의 길이인 4가 들어가 있고, 그것을 EAX에 저장

004014E3   . |89C2          MOV EDX,EAX
// EAX값을 EDX에 저장

004014E5   . |8D0412        LEA EAX,DWORD PTR DS:[EDX+EDX]
// [EDX+EDX]는 EDX의 값을 곱하기 2 하여 8이 된 주소를 EAX에 저장

004014E8   . |3945 FC       CMP DWORD PTR SS:[EBP-4],EAX
// EAX와 [EBP-4]를 비교한다. for문을 언제까지 할지 결정한다.

004014EB   . |7C 03         JL SHORT Babylon_.004014F0
// CMP에 의해 값이 같지 않으면 점프한다.(반복문 진행)

004014ED   . |EB 31         JMP SHORT Babylon_.00401520
// 반복이 완료되면 반복 구문 밖으로 점프하여 반복을 종료한다.

004014EF     |90            NOP
// 아무일도 하지 않는다.

004014F0   > |8D85 A0FDFFFF LEA EAX,DWORD PTR SS:[EBP-260]
// [EBP-260]의 주소를 EAX에 저장

004014F6   . |8B55 FC       MOV EDX,DWORD PTR SS:[EBP-4]
// EDX = 0

004014F9   . |8D8D E0FEFFFF LEA ECX,DWORD PTR SS:[EBP-120]
// 이름의 주소를 ECX에 저장

004014FF   . |8B5D F8       MOV EBX,DWORD PTR SS:[EBP-8]
// EBX = 0

00401502   . |8A0C0B        MOV CL,BYTE PTR DS:[EBX+ECX]
// 이름의 첫번째 값의 아스키 코드를 CL에 저장, test의 t가 먼저 저장

00401505   . |880C02        MOV BYTE PTR DS:[EDX+EAX],CL
// CL의 값을 [EDX+EAX]에 저장

00401508   . |8B45 FC       MOV EAX,DWORD PTR SS:[EBP-4]
// EAX = 0

0040150B   . |40            INC EAX
// EAX를 INC로 1증가 EAX = 1

0040150C   . |8D95 A0FDFFFF LEA EDX,DWORD PTR SS:[EBP-260]
// [EBP-260]의 주소를 EDX에 저장

00401512   . |C60410 20     MOV BYTE PTR DS:[EAX+EDX],20
// [EAX+EDX]에 16진수로 20 -> 0x20 아스키값으로 공백이다.

00401516   . |FF45 F8       INC DWORD PTR SS:[EBP-8]
// [EBP-8]값에 INC로 1증가

00401519   . |8345 FC 02    ADD DWORD PTR SS:[EBP-4],2
// [EBP-4]값에 2를 더함

0040151D   .^\EB C1         JMP SHORT Babylon_.004014E0
// 다시 반복을 시도한다.

이해를 돕기 위해 위의 어셈블리 코드들을 C언어로 재구현했다. C언어와 비교해보면서
어셈블리 코드들을 다시 한번 분석해보자.

char str[5] = "test";  // 4 = [EBP-18] (test의 길이) ,
                             // 'test' 문자열의 인덱스(str[0])주소 = [EBP-120]
char str1[8] = {0};   // 변경된 문자열을 저장할 배열의 인덱스(str1[0])주소 = [EBP-260]

for( int i =0 , int j = 0 ; i < 8 ; i+=2, j++ )  // i = [EBP-4], j = [EBP-8]
{
            str1[i] = str[j];                                  // str[j] = CL -> str1[i]에 대입.
            str1[i+1] = 0x20;
}

이렇게 반복문을 지나고 나면 SS:[EBP-260]에는 "t e s t "가 들어가게 된다.
그 후에 이상한 값들이 추가되는데, 위치와 값을 체크해보면


이상한 값이 들어가고 길이를 체크하며, 반복문을 통해 문자열들의 길이만큼 반복을 하면서 BL에 한 글자씩 넣고 00401564   .  FEC3          INC BL 을 통하여 값을 변경한다.
이 반복문을 C언어로 재구현하면,

char str2[35] = "-[#]]=}&&&+(=$*,,)&.*/+++[][;/..?" [EBP-160]

for( int i = 0 ; i < 34 ; i++ ) // i = [EBP-4]
{
            str2[i] = str2[i]+1;
}

아래로 내려오면 또 반복문이 나오면서 XOR연산을 한다.
004015C9   .  321C37        XOR BL,BYTE PTR DS:[EDI+ESI]
이 반복문을 C언어로 재구현하면,

char str2[35] = ".\$^^>~''',)>%+--*'/+0,,,\^\<0//?" [EBP-160]
char str1[8] = "t e s t ";                                          [EBP-260]

for( int i = 0 ; i < 8 ; i++ ) // i = [EBP-4]
{
            str2[i] = str2[i]^str1[i];                   // '^' 는 XOR 연산
}

아래로 내려오면 또 반복문이 나온다.
이 반복문을 C언어로 재구현하면,

char str2[35] = "Z|A~-'',)>%+--*'/+0,,,\^\<0//?" [EBP-160]
char str3[35] = {0};                                             [EBP-360]

for( int i = 33, int j = 0 ; i >= 0 ; i--, j++ ) // i = [EBP-4], j = [EBP-C]
{
             str3[j] = str2[i];
}

아래로 내려오면 또 반복문을 만난다.
이 반복문을 C언어로 재구현하면,

char str2[35] = "Z|A~-'',)>%+--*'/+0,,,\^\<0//?" [EBP-160]
char str3[35] = "1?/0<\^\,,,0+/'*--+%>),''-~A|Z" [EBP-360]
char str4[35] = {0};                                              [EBP-460]

for( int i = 0, int j = 0, int k = 0 ; i < 34 ; j++, k++ ) i = [EBP-4], j = [EBP-10], k = [EBP-14]
{
           str4[i] = str3[j];
           i++;
           str4[i] = str2[k];
           i++;
}

아래로 내려오면 또 반복문을 만난다.
이 반복문을 C언어로 재구현하면,

char str4[35] =  "1Z?/A/~0-<\.^\',',,,)0>+%/+'-*-" [EBP-460]

for(int i = 0 ; i < 34 ; i++ )
{
        if(str4[i]<=1F)
        {
                     str4[i]=0x36;
        }
        
        if(str4[i]>'z')
        {
                     str4[i]=0x36           
        }
}

분기문을 만났다.
여기까지 정리를 하면 (이표에 나와있는 문자열값은 정확하지 않다.
올리디버거에 Hex dump창의 아래 나온 변수의 주소값으로 이동해서 확인을 해보자)

 [EBP-160] = "-[#]]=}&&&+(=$*,,)&.*/+++[][;/..?"
                                    ↓
 [EBP-160] = ".\$^^>~''',)>%+--*'/+0,,,\^\<0//?"
                                    ↓
 [EBP-160] = "Z|A~-'',)>%+--*'/+0,,,\^\<0//?"
                                    ↓ 
 [EBP-360] = "1?/0<\^\,,,0+/'*--+%>),''-~A|Z"
                                    ↓
 [EBP-460] = "1Z?/A/~0-<\.^\',',,,)0>+%/+'-*-"
                                    ↓
 [EBP-460] = "1Z66/A/60-<6\6^6\',',,,)0>+%/+'-*-"

004016E3   .  3B45 E8       CMP EAX,DWORD PTR SS:[EBP-18]  // [EBP-18] = 4
004016E6   .  7C 08          JL SHORT Babylon_.004016F0
004016E8   .  EB 4B         JMP SHORT Babylon_.00401735

JL구문에서 점프를 하지 않으면 JMP를 만나 성공 메시지가 있는 곳으로 간다.

마지막 반복문을 진행하여 성공 or 실패 메시지로 점프하는데 이 반복문을 C언어로 재구현하면,

char str5[5] = "1234";                                                    [EBP - 560]
char str4[35] = "1Z66/A/60-<6\6^6\',',,,)0>+%/+'-*-";    [EBP - 460]

for ( int i = 0 ; i < 4 ; i++ ) // i = [EBP-4]
{
        if(str5[i]!=str4[i])
        {
                    실패메시지 호출;
        }
}
성공메시지 호출;


여기까지의 분석을 통해 우리는 Name : test 를 입력한 경우에 시리얼값은 1Z66 이라는 것을 찾아냈다.


이제 시리얼값이 어떻게 만들어지는지 정리해보겠다. 이러한 알고리즘을 프로그래밍하면 그것을 키젠이라고 한다.

1. Name 입력 : "test"

2. [EBP-160]에 "-[#]]=}&&&+(=$*,,)&.*/+++[][;/..?"값이 들어간다.

3. [EBP-160]에 값을 INC한다. ".\$^^>~''',)>%+--*'/+0,,,\^\<0//?"

4. Name 문자열 사이에 공백을 추가한다. "t e s t "

5. 공백이 들어간 Name의 길이만큼 반복하면서 INC된 [EBP-160]값과 XOR 연산한다.

6. [EBP-360]에는 INC된 [EBP-160]값이 거꾸로 뒤집혀서 들어간다.

7. [EBP-460]에는 [EBP-360]값과 [EBP-160]값을 번갈아 가며 넣는다.
    ex. [EBP-460][1] = [EBP-360][1]
          [EBP-460][2] = [EBP-160][1]

8. [EBP-460]의 문자중에 'z'보다 크거나 0x1F보다 작거나 같으면 6으로 변경한다.

9. [EBP-18](Name의 길이)만큼 [EBP-460]을 앞에서부터 잘라낸다.

다음 내용들을 정리하여 짜본 소스를 첨부한다.
이해가 안되는 부분은 질문을 해주시기 바랍니다.


키젠 문제를 풀어보았는데, 일반 CrackMe 문제보다는 더 꼼꼼히 정리하고, 값들이 어떻게 변화되는지를 살펴봐야 한다. 문제 자체가 중요한 것이 아니라 문제를 풀면서 어느 정도 어셈블리어에 익숙해지고, 분석하는 방법을 배워가는게 중요하다.

(참고로 아스키 값과 10진수, 16진수 코드 값은 여기에 정리된 것이 있다
                                         
                                           http://www.asciitable.com/)

Posted by Dakuo
이번에 풀어볼 문제는 upx로 패킹이 되있는 문제로 upx -d 옵션으로 압축을 풀어도 되지만 메뉴얼 언패킹하는 방법을 간단하게 설명하기 위해서 올리디버거의 덤프 플러그인을 사용하여문제를 풀어보도록 하겠다.
다음을 다운받아 Plugin 폴더에 넣는다.


올리디버거의 메모리 덤프의 리빌드 기능만으로도 upx 압축은 풀리기 떄문에 쉽게 따라할 수 있는 문제이다.


먼저 PEID로 바이너리를 확인해본다.
               (PEID 사용법 참고 : http://dakuo.tistory.com/entry/바이너리-분석)


upx 압축되어 있는 문제 파일인 CrackMe2.exe를 올리디버거로 열어본다.


코드가 압축되었으며, 분석을 더할 것인지 묻는다. 예를 누른 후 아래쪽으로 내려가다 보면 압축이 모두 풀려서 메모리에 올라가 있는 초기 상태인 OEP(Original Entry Point)부분으로 가는 JMP문이 나타난다.


upx 같은 경우는 프로세스를 보호하기 위해서 압축하는 것이 아니기 때문에 OEP를 찾는 것이 어렵지 않다.
JMP구문에서 Breakpoint(F2)를 설정하고, Run(F9)을 하면 이 주소에서 Paused가 된다.
Step Over(F8)을 한 번 해주면 OEP(Original Entry Point) 위치로 이동한다.

PUSH EBP 위치에서 올리디버거의 plugin의 덤프를 하면 된다.


마우스 우클릭 후 Dump debugged process를 선택하거나 플러그인 메뉴에서 선택을 해도 된다.



올리디버거의 덤프 플러그인에 import 테이블을 리빌드하는 옵션이 있다. upx로 된 경우에는 옵션을 체크한 후에 덤프 버튼을 누르면 메모리상의 프로그램이 덤프가 되어 압축이 풀린다. 파일을 저장하고 정확하게 되었는지 PEID로 확인해보면


upx 압축이 풀리고 제작 툴인 볼렌드 델파이라고 탐지해주는 것을 확인할 수 있다.

upx가 패킹을 하는 원리는 기존의 소스 코드 부분들을 모두 새로 만든 upx 섹션에 압축하여 저장을 해놓고, 실행 부분에서는 upx 섹션에 저장해놓은 부분을 풀어서 메모리에 올리게 된다. 이런 원리로 압축이 다 풀려서 OEP(Original Entry Point)로 가게되는데, 이 부분에서 메모리상의 프로그램을 덤프받는 것이다. 그냥 덤프받으면 IAT 정보가 정확하지 않으므로 덤프 플러그인의 옵션에서 Rebuild Import를 체크하는 것이다. 저장한 파일을 올리디버거로 열어서 Run(F9)을 눌러 프로그램이 어떤 문제인지 확인해보면


실행 후 시리얼을 넣는 editbox가 있으며 시리얼을 넣고 check the serial 버튼을 누르면 Wrong Serial이라는 타이틀의 메시지 창이 나타난다.


스트링 검색을 해서 이벤트 시점을 찾아도 되지만 새로운 방법으로 풀어보겠다. 메시지 창이 나타나는 경우 더 빠르게 이벤트를 찾을 수 있는 방법이다.


Back to user mode : 
특정 이벤트를 일어나기 전에 설정해두고 Call 명령이 일어난 바로 다음 위치를 잡을 수 있다.

사용순서 :

1. 프로세스 attach(open)
2. Run(F9)
3. 프로그램을 메시지 창과 같은 정지된 상태로 만든다.
4. paused(F12)
5. back to user mode(Alt+F9) 설정
6. 프로그램에서 이벤트 발생(메시지 창에서는 확인 버튼 클릭)
7. 올리디버거에서 해당 부분에 커서가 설정된다.

(주의할 점 : back to user mode 기능은 브라우저나 내부 스레드가 작동하는 프로그램의 경우에는 내부 이벤트가 수시로 일어나서 해당 모드가 풀려버리니 사용하려면 메시지 박스와 같이 프로세스가 멈추는 상태에서 사용한다)

Wrong Serial 창이 뜬 상태에서 올리디버거에서 일시정지(F12)를 시킨 후에 back to user mode 설정키인 Alt + F9를 눌러준다.
Debug 메뉴에서 Excute till user code를 선택해도 된다.


일시정지(F12)를 하면 Paused라고 표시가 되고,


Alt + F9를 누르면 back to user mode가 표시된다.


이런 상태에서 CrackMe 문제의 메시지 창의 확인 버튼을 눌러준다.

보이는 것과 같이 MessageBoxA 함수가 호출된 다음의 위치가 선택된다.
틀렸다는 메시지 창이 나타나는 곳은 찾았으니 그 메시지 내용이 설정되는 곳을 찾기 위해 파라미터를 확인한다.

MessageBoxA를 Winapi 확인을 해보면 다음과 같다.


메시지 내용인 "You are a bad cracker!"가 어디에서 입력됬는지 어셈블리 코드에서 Text 부분이 입력되는 곳을 확인한다.


PUSH ESI를 통해서 파라미터가 들어갔음을 알 수 있다.
올리디버거의 CPU 레지스터값을 보면 우리가 찾는 Text가 있다.


Ctrl+G 단축키를 이용하여 ESI가 가리키고 있는 주소인 00442C68를 입력하면 다음과 같은 곳으로 이동한다.


문자열들이 보인다. 이 부분에서 한 페이지만 위로 올려보면 값들이 구분되는 부분이 보인다.


첫 줄에서 Value 창을 확인하면서 Breakpoint를 잡아놓고 StepOver하다 보면 내가 입력한 값이 들어가는 것을 볼 수 있다.



1234라고 입력한 값이 Value창에 찍히는 것을 볼 수 있다.


다음 줄에서 시리얼같은 값이 EDX에 저장되고, CALL 명령이 실행되면서 StepInto(F7)을 하여 내부로 들어가서 확인해보면 CMP를 통해 입력한 값과 시리얼 키를 비교하는 부분이 나타난다.



결정적으로 JNZ 구문에서 "Congrats! You were successful!"과 "You are a bad cracker!"가 구분된다.

이 문제 같은 상황에서는 back to user mode 기능을 사용하면 보다 빠르게 분석을 할 수 있다.

이제 "12011982" 숫자 값을 넣어서 확인해보자.

Posted by Dakuo

crackme author : abex
type                 : cdrom check
level                 : easy
tute author         : HaQue


Ollydbg.exe 실행해서 abexcm1.exe를 open한다.
프로그램이 어떻게 작동되는지 보기 위해 Run(F9)을 한다.


첫 번째 메시지박스가 나타나고, 확인을 클릭하면 두 번째 메시지박스가 나타난다.


확인을 클릭하고 올리디버거를 보면 프로그램이 종료되었음을 알 수 있다.


첫번째 메시지박스에는 CD-ROM을 체크하는 루틴을 우회하라고 나오고
두번째 메시지박스에는 CD-ROM이 아니라고 하며 프로그램이 종료되었다.


이 문제를 푸는 방법에는 메모리 주소 흐름을 바꾸는 방법과, CD-ROM을 체크하는 함수의
리턴값을 변경해서 푸는 방법 2가지 방법이 있다.


1번 방법 풀이(메모리 주소 흐름 변경) : 

취약한 프로그램 리버싱 순서 :
1. 프로그램 흐름 분석
2. 문자열을 이용한 특정 이벤트 시점 찾기
3. 성공이었을 경우의 문자열 위치를 찾아 무조건 성공하도록 흐름 변경

프로그램의 흐름을 파악했으니 문자열을 통해 특정 이벤트 시점을 찾아보겠다.
프로그램이 종료되어 있는 상태니 Restart한다.(Ctrl + F2)  어셈블리코드가 보이는 화면에서 우클릭후 Search for -> All referenced text strings 를 선택후 문자열을 확인해본다.


문자열 목록을 보면, CD-ROM이 인식되었을때는 00401044 주소의 값이 출력되는 것을 예상
할수 있다. 해당 문자열을 더블 클릭하여 해당 주소지로 이동하자.


위에 GetDriveTypeA라는 드라이브의 유형을 리턴해주는 API가 보인다.(두번째 풀이는 이 API를 이용한다)
또 JE문에 의해 점프를 할때에는 성공 메시지박스가 점프를 안할 때는 실패 메세지박스가 뜬다. 실패메시지박스가 뜬 뒤에는 JMP구문에 의해 프로세스가 종료된다. 그러므로 JE문을 JMP문으로 바꿔준다면 ZF플래그와 상관없이 무조건 성공메시지박스만이 뜰것이다.

                  (어셈블리어 참고 : http://dakuo.tistory.com/entry/기초-어셈블리어)


수정을 하고 나서 Run(F9)을 하면 문제가 풀어진 것을 알수 있다.
수정한 내용을 파일로 저장을 하기 위해 수정한 부분에서 우클릭 후 Copy to executable -> All modifications를 선택한 후 Copy all을 클릭한다.


수정된 어셈블리어들이 나타난 창에서 우클릭 후 Save file을 선택한다.




2번 방법 풀이(WinAPI함수의 리턴값 변경) :

E 버튼을 눌러서 Executable modules창을 연다.
실행중인 문제 파일인 abexcm1.exe 항목에서 우클릭 후 View Names를 선택한다.


사용되고 있는 api함수 목록을 볼수 있다.
GetDriveTypeA라는 함수가 드라이브의 유형을 리턴해줄것 같다. 원형을 보기 위해서 우클릭후 Help on symbolic name을 선택한다. 기존에 연결시켜 놓은 헬프 파일에서 해당 api이름을 검색해서 보여준다.


레퍼런스를 확인하면 목적, 파라미터, 리턴값을 알 수 있다.
그런데 리턴값에서 0, 1 다음에는 문자열로 지정되어 있다. 정확한 값을 알기 위해 MSDN에서 검색해보면 CDROM은 5가 리턴된다. 이제 Breakpoint를 설정하여 해당 api가 실행되는 과정을 분석하고, 리턴 값을 수정해주면 된다. 해당 api함수에 우클릭 후 Set breakpoint on every reference 메뉴를 선택한다. Command Line이라는 플러그인에서 bpx 함수명으로 명령을 주는 것과 비슷한 기능이다.(이플러그인은 기본플러그인이다) plugin메뉴 -> Command line -> Command line을 선택해주면 창이 생긴다. 여기서 bpx GetDriveTypeA라고 명령어를 치면 해당 api에 모두 브레이크 포인트를 설정해준다. 익숙해지면 디버깅 속도를 단축시킬수 있어서 편리하며, 찾고 싶은 api를 미리 알고 있다면 특정 시점을 찾기가 쉬워진다.


(참고로 Command Bar(cmdbar.dll)라는 플러그인을 사용하면 올리디버거 하단에 붙어있어서 자주 커맨드 명령을 사용할 경우 Command Bar를 사용하는것이 효율적이다. 올리디버거 플러그인이 잘 정리된 곳으로는 OpenRCE가 있다)

 

CPU창에서 Run(F9)을 해보면 GetDriveTypeA에서 멈추게 된다. Step Over(F8)시켜서 api를 실행시킨다.


위부분은 api가 실행된 후부터 (성공or실패)메시지박스를 만나기 위한 분기점까지의 어셈코드이다. 이부분을 해석하면


마지막에 DEC EAX 구문만 없으면 CMP EAX ESI 에서 2 : 2로 똑같아져서 JE문에서 점프를 하게 된다. DEC EAX 를 NOP로 바꾸자. 변경을 한 후에 Run(F9)를 해보면 문제를 푼 것을 확인 할수 있다. 변경한 코드를 저장하면 모든 풀이가 끝이 난다.
Posted by Dakuo

리버싱을 하기 위해서 이용되는 툴중에 디버거라는 것이 있다.
이 디버거들 중에 가장 대표적인 것은 올리디버거(OllyDebugger)이다.



odbg110.zip를 다운로드 받아서 odbg110 폴더에 압축을 풀고 Plugin폴더와 UDD폴더를 생성한다.
Win32_Programmers_Reference.rar로 압축을 풀어 wn32.hlp 파일을 obdg110 폴더에 넣는다. BOOKMARK.dll과 cmdline.dll은 Plugin폴더에 넣는다.

Ollydbg.exe를 실행하고  Option -> Appearance ->Directories를 열고 그림과 같이 경로 설정을 하고 다시 실행을 해준다.


이외에 설정해 주면 좋은 옵션은
작업관리자의 프로세스 목록에서 바로 디버깅 연결이 가능한 Jit(Just-in-time Debugging)
탐색기에서 파일 우클릭으로 바로 연결 가능한 Add explorer이 있다.


Help메뉴에서 Select API help file 선택을 해서 win32,hlp을 열어주면 자동으로 winapi도움말과 연결이 된다.


그리고 디버깅 옵션의 Event탭에서 프로그램을 열었을 때 시작할 위치를 Entry point of main module로 시작 지점을 잡아주도록 수정하는것이 좋다.


자 이제 마지막으로 점프위치를 화살표로 보기 좋게 표현하도록 고쳐보자.
ini파일을 메모장으로 열어서 [Settings]섹션을 다음과 같이 수정한다.

이렇게 바꿔주면 왼쪽처럼 화살표가 없던것이 오른쪽과 같이 화살표가 생긴다.

다른 옵션들은 자기에 맞게 변경하면 되고 그 옵션값들은 ollydbg.ini에 저장이 된다.
따라서 파일을 직접 수정해서 고치는것도 가능하다.

(참고로 TUTS4YOU 사이트에는 여러 형태의 리버싱 연습 문제들과 강좌도 많으므로
리버싱 공부를 위해 참고하면 좋다. : http://www.tuts4you.com/)



// ps. 추가로 한글판도 올립니다.



올리디버거로 notepad.exe를 열었을때의 화면이다.


1. 어셈블리코드
2. 레지스터 상태
3. Dump
4. 스택

3. Dump창에는 Heap이나 FileMap, Text 영역의 값들이 올 수 있다. 주소 부분을 16진수로 보여준다.

MENU :

[File] : 파일을 디버거로 불러오는 Open, 실행중인 프로세스를 디버거로 붙이는 Attach(해당 프로세스는 정지상태가 된다), 디버거를 종료하는 Exit가 있다.

[View] : 보여주는 기능. Memory를 선택하면 현재 Memory의 모습을 보여주고, Call Stack(Alt + K)을 선택하면 어디서 어떤 함수가 호출되었는지를 보여준다. 각 기능을 실행해보고 파악하자.

[Debug] :
Run : 디버거로 불러온 프로그램을 실행시킨다. Breakpoint를 만나거나 Exception이 발생해서 프로그램이 정지할때까지 실행한다. (단축키 : F9)
Restart : 프로그램을 처음부터 실행시키기 위해 재실행한다. Run을 해야 프로그램이 실행된다. (단축키 : Ctrl + F2)
Step into : 코드를 한줄씩 실행한다. Call(함수호출)나 Rep (반복문) 명령어를 만나면 함수내부로 들어가고 조건을 만족할때까지 계속 수행한다. (단축키 : F7)
Step over : 코드를 한줄씩 실행한다. Call(함수호출)나 Rep(반복문) 명령어를 만났을때 함수내부로 추적하지 않고 실행하며 반복문도 한번에 처리한후 다음줄로 넘어간다. (단축키 : F8)
Execute till return : 추적할 필요가 없는 함수 내부로 들어갔을때 그 함수의 ret명령 지점까지 한번에 실행하고 함수를 나올수 있다. (단축키 : Ctrl + F9)

[Plugin] : 올리디버거에서 사용하는 여러 플러그인 기능들이 있다. 다운받아 사용한다.

[Options] : 올리디버거의 설정 사항을 조정할수 있다.
Appearance에서는 Udd폴더나 Plugins폴더를 설정할 수 있고, 폰트도 변경할 수 있다.
Debugging options에서는 디버깅과 관련된 여러가지 설정을 변경할 수 있다.

[Windows] : 올리디버거 내부 윈도우들의 정렬 방식을 바꿀수 있으며, 원하는 윈도우를 활성화시킬 수도 있다.

1. 어셈블리코드 부분에서 우클릭시에 나오는 메뉴다.


Comment : 어셈블리코드 옆에 주석을 달아 놓을 수 있는 기능이다. 주석을 달아 놓음으로써 프로그램 분석을 보다 편리하게 할 수 있으므로 주석을 자주 달자.

Breakpoint : 디버거로 대상 프로그램을 실행시켰을 때 Breakpoint를 만나면 실행을 멈추고
사용자에게 제어를 넘겨준다.

Breakpoint에는 크게 두 가지가 있다.

 Software Breakpoint
CPU는 인터럽트를 이용하여 운영체제에게 특정 이벤트를 알려주며 CC(int3)라는 인터럽트가 발생되면 프로그램의 동작이 멈추게 되며 디버거에게 넘겨지게 되고 제어가 된다.
 Hardware Breakpoint
디버그 레지스터인 DR0, DR1, DR3, DR6, DR7을 이용하며 DR0~DR3은 Breakpoint를
걸어줄 대상의 주소 또는 범위를 설정할수 있으며 DR6은 인터럽트가 발생했을때의 결과를 알려주며 DR7은 Breakpoint의 속성을 설정할수 있다. 이것을 이용하면 특정 메모리 주소에 Write, Read, Excute가 발생했을 때 어떤 주소에서 실행이 되었는지 알수가 있다.

Search for :


All intermodular calls 기능은 Import된 함수들의 목록을 볼 수 있다.
All referenced text string 기능은 사용되는 문자열 값을 보여준다.

어셈블리코드가 있는 부분에서 더블클릭할 시에 직접 어셈블리 코드를 수정할 수 있다.
나머지 메뉴는 직접 실행해보며 파악하자.


2. 레지스터창에서 우클릭시 나오는 메뉴이다.


레지스터 상태창의 레지스터를 더블클릭하면 레지스터에 설정된 값을 변경할 수 있다.


3. Dump창에서 우클릭시 나오는 메뉴이다.



4. 스택창에서 우클릭시 나오는 메뉴이다.


Lock stack : 스택이 표시되는 윈도우가 ESP의 값에 따라서 변하는 것을 막아 표시되는 부분이 변하지 않도록 하여 스택의 특정 부분을 감시한다.
Go to EBP : 현재 EBP 레지스터가 가리키고 있는 주소로 스택 윈도우를 바꿔준다.
Follow in Dump : 스택에 들어있는 주소값에 어떤 데이터가 존재하는지 확인한다.

올리디버거의 화면을 세부적으로 설명하겠다.


1. 메모리 주소 : 프로그램을 디버깅하는 동안에 표시되는 주소지로 Ctrl + G 단축키로 특정
위치로 이동할 수 있다.

2. Opcode : 오퍼레이션 코드로 어떤 작동을 하는 코드들이 1byte로 정리되어 있다.
(1opcode == 1byte/2bytes == 1word/2words == 1dword)

3. Assembler Code : 디스어셈블된 코드들이다.

4. Olly가 분석한 내용 : api 함수들이나 문자열, 함수 파라미터들을 올리디버거가 보기 좋게 정리해서 보여준다.

5. 사용자가 입력한 주석 표시 : 세미클론(;)을 누르면 임시로 구분해 놓기 위한 내용들을 적을 수 있다.

6. 변수값 출력창 : 디버깅 중에 주요 변수들에 어떤 값들이 들어갔는지 표시해주는 창으로 자주 확인해야 한다.

7. Hex Dump창 : 16진수로 덤프를 하여 표시해주는 영역으로 바이너리 파일을 직접 수정할 때에 쓰인다.(hex값은 두 자리가 1byte이며, 1byte는 8bit가 된다)

8. 레지스터와 플래그 값 : CPU가 필요할 경우 사용하는 레지스터와 플래그의 값이 어떻게 변화하는지 보여준다. 레지스트리는 특별한 메모리 공간으로써 data를 저장하고 이용 가능하다.



다음은 OllyDBG 110의 단축키이다.

Pop-up menus display only items that apply. Frequently used menu functions:  

Function

Window

Menu command

Shortcut

Edit memory as binary, ASCII or UNICODE string

Disassembler, Stack Dump

Binary|Edit

Ctrl+E

Undo changes

Disassembler, Dump Registers

Undo selection Undo

Alt+BkSp

Run application

Main

Debug|Run

F9

Run to selection

Disassembler

Breakpoint|Run to selection

F4

Execute till return

Main

Debug|Execute till return

Ctrl+F9

Execute till user code

Main

Debug|Execute till user code

Alt+F9

Set/reset INT3 breakpoint

Disassembler Names, Source

Breakpoint|Toggle Toggle breakpoint

F2

Set/edit conditional INT3 breakpoint

Disassembler Names, Source

Breakpoint|Conditional Conditional breakpoint

Shift+F2

Set/edit conditional logging breakpoint (logs into the Log window)

Disassembler Names, Source

Breakpoint|Conditional log Conditional log breakpoint

Shift+F4

Temporarily disable/restore INT3 breakpoint

Breakpoints

Disable Enable

Space

Set memory breakpoint (only one is allowed)

Disassembler, Dump

Breakpoint|Memory, on access Breakpoint|Memory, on write

 

Remove memory breakpoint

Disassembler, Dump

Breakpoint|Remove memory breakpoint

 

Set hardware breakpoint (ME/NT/2000 only)

Disassembler, Dump

Breakpoint|Hardware (select type and size!)

 

Remove hardware breakpoint

Main

Debug|Hardware breakpoints

 

Set single-short break on access to memory block (NT/2000 only)

Memory

Set break-on-access

F2 

Set break on module, thread, debug string 

Options

Events

 

Set new origin

Disassembler

New origin here

 

Display list of all symbolic names

Disassembler, Dump Modules

Search for|Name (label) View names

Ctrl+N

Context-sensitive help (requires external help file!)

Disassembler, Names

Help on symbolic name

Ctrl+F1

Find all references in code to selected address range

Disassembler Dump

Find references to|Command Find references

Ctrl+R

Find all references in code to the constant

Disassembler

Find references to|Constant Search for|All constants

 

Search whole allocated memory

Memory 

Search Search next

Ctrl+L

Go to address or value of expression

Disassembler Dump

Go to|Expression Go to expression

Ctrl+G

Go to previous address/run trace item

Disassembler

Go to|Previous

Minus

Go to next address/run trace item

Disassembler

Go to|Next

Plus

Go to previous procedure

Disassembler

Go to|Previous procedure

Ctrl+Minus

Go to next procedure

Disassembler

Go to|Next procedure

Ctrl+Plus

View executable file

Disassembler, Dump, Modules

View|Executable file

 

Copy changes to executable file

Disassembler

Copy to executable file

 

Analyse executable code

Disassembler

Analysis|Analyse code

Ctrl+A

Scan object files and libraries

Disassembler

Scan object files

Ctrl+O

View resources

Modules, Memory

View all resources View resource strings

 

Suspend/resume thread

Threads

Suspend Resume

 

Display relative addresses

Disassembler, Dump, Stack

Doubleclick address

 

Copy

Most of windows

Copy to clipboard

Ctrl+C



Frequently used global shortcuts:
 

Ctrl+F2

Restart program

Alt+F2

Close program

F3

Open new program

F5

Maximize/restore active window

Alt+F5

Make OllyDbg topmost

F7

Step into (entering functions)

Ctrl+F7

Animate into (entering functions)

F8

Step over (executing function calls at once)

Ctrl+F8

Animate over (executing function calls at once)

F9

Run

Shift+F9

Pass exception to standard handler and run

Ctrl+F9

Execute till return

Alt+F9

Execute till user code

Ctrl+F11

Trace into

F12

Pause

Ctrl+F12

Trace over

Alt+B

Open Breakpoints window

Alt+C

Open CPU window

Alt+E

Open Modules window

Alt+L

Open Log window

Alt+M

Open Memory window

Alt+O

Open Options dialog

Ctrl+T

Set condition to pause Run trace

Alt+X

Close OllyDbg



Frequently used Disasembler shortcuts:
 

F2

Toggle breakpoint

Shift+F2

Set conditional breakpoint

F4

Run to selection

Alt+F7

Go to previous reference

Alt+F8

Go to next reference

Ctrl+A

Analyse code

Ctrl+B

Start binary search

Ctrl+C

Copy selection to clipboard

Ctrl+E

Edit selection in binary format

Ctrl+F

Search for a command

Ctrl+G

Follow expression

Ctrl+J

Show list of jumps to selected line

Ctrl+K

View call tree

Ctrl+L

Repeat last search

Ctrl+N

Open list of labels (names)

Ctrl+O

Scan object files

Ctrl+R

Find references to selected command

Ctrl+S

Search for a sequence of commands

Asterisk (*)

Origin

Enter

Follow jump or call

Plus (+)

Go to next location/next run trace item

Minus (-)

Go to previous location/previous run trace item

Space (  )

Assemble

Colon (:)

Add label

Semicolon (;)

Add comment



'Tool' 카테고리의 다른 글

디컴파일러(Decompliers)  (0) 2009.11.07
아이다(IDA)에 MS 심볼 서버 연동하기  (9) 2009.11.06
아이다(IDA)에 Hex-Ray 연동  (9) 2009.11.06
아이다(IDA) 사용법  (46) 2009.11.02
시스템 모니터링 툴  (0) 2009.11.01
올리디버거(OllyDBG) 사용법  (12) 2009.10.24
Posted by Dakuo