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)  (50) 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)  (32) 2009/02/28
abex' crackme #1  (34) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (3)  (40) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (2)  (6) 2009/02/28
"Hello World!" - 내 생애 첫 디버깅 (1)  (50) 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 을 쓰는데 그래서 그럴까요?


◀ PREV : [1] : ... [83] : [84] : [85] : [86] : [87] : [88] : [89] : [90] : [91] : ... [93] : NEXT ▶