* 출처 : 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()
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 시점까지 진행한 다음 스택을 보시면 아래와 같습니다.

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
매우 간단한 크랙이지만 처음 접하시는 분들을 위해서 상세한 설명을 하였습니다.
다시 한번 강조하지만 크랙은 그냥 리버싱 처음 배울 때 디버깅 연습용(?)으로만 사용하는 것이 좋습니다.
핵심 원리를 파악하여 고급 리버싱을 할 수 있는 기초를 마련하는데 힘을 쏟아야 할 것 입니다.
감사합니다.
코드를 보니 이제 제작자의 의도가 명확하게 이해됩니다.
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 |
abexcm1-voiees.exex
