Try Attack/Reverse Engineering[basic]

[crackme2] abex' 2nd crackme 풀이 및 복원

D4tai1 2018. 11. 7.

※ 단순한 값만 확인하지 않고 Serial Key Algorithm을 찾는데 목적을 두었습니다.


1. [abex' 2nd crackme] 풀이시연

1) 실행파일 실행

 [1] 파일 열기

  ▶ 아무 것도 입력하지 않고 Check를 누르면 4글자 이상의 이름을 입력하라는 메세지가 출력된다.


 [2] 이름 및 시리얼번호 입력

  ▶ 이름과 시리얼번호를 입력해주고 체크를 눌러본다.


 [3] Check 버튼 후 결과 확인

  ▶ 시리얼번호가 잘못됬다는 메세지를 확인한다.


2) 개발언어 및 링커 확인


 [1] Visual Basic 언어로 작성됨을 알 수 있다.

 [2] 32비트 실행파일임을 알 수 있다.

 [3] Microsoft 링커를 사용하여 링킹한 것을 알 수 있다.

 [4] 헤더타입이 PE라고 적힌 것이 확인되어 Windows용 실행파일임을 알 수 있다.

 [5] ImageBase(기준주소)가 0x400000임을 알 수 있다 

 [6] EntryPoint(시작주소)가 기준주소로부터 0x1238 만큼 떨어진 곳임을 알 수 있다.

 [7] 기타 시그니쳐 및 옵션 세부사항은 직접 하나씩 확인해보면 된다.

 [8] 필자는 Detect It Easy 2.0 툴을 사용하였다.


3) 상상

 [1] 1)에서 실행해본 결과 name의 길이가 4보다 작은경우 메세지박스가 출력되었다.

 [2] name의 길이가 4보다 길다면 가지고있는 시리얼 값과 입력한 시리얼 값을 비교한다.

 [3] 의사코드

if (4 <= name.length) {

if(Serial값 비교) {

//참인 경우? 실행할 내용

} else {

MessageBox(핸들번호, "Nope, this serial is wrong!", "Wrong Serial!", MB_OK);

}

} else {

MessageBox(핸들번호, "Please enter at least 4 chars as name!", "Error!", MB_OK);

}

종료..


4) 디버거 툴을 사용한 분석

 [1] Main함수 실행

  ▶ 아까 기준주소와 상대적인 상대주소를 얻었으므로 더해서 메인함수의 시작주소가 0x401238임을 알 수 있다,

  ▶ 디버거프로그램으로 F9를 누르면 자동으로 브레이크포인트가 있는 것으로 체크해서 멈춘다.

  ▶ 0x0040123D주소에서 Main함수로 접근한다.


 [2] 문자열 검색

  ▶ 내가 입력한 Serial 값이랑 같은지 비교하는 Serial값이 보이는지 먼저 확인하려보니... 보이지 않는다.

  ▶ 이 경우 어딘가에 Serial값을 만드는 함수가 있을지도 모를 것이라고 유추할 수 있다.

  ▶ 같은 문자열이지만 위부터 0x401101, 0x402D88, 0x402DC9, 0x402DD9의 경우 프로그램 자체 및 도움말에 출력된다.

  ▶ 그래서 아까 4글자 이상 입력하라고 했던 문자열의 위치로 이동하려고 한다.

 

 [3] [Please enter at least 4 chars as name!] 문자열로 이동


  ▶ 위 이미지로 문자열 검색을 통해 왔고 의사코드로 구조를 상상해 보겠다.

  ▶ [Please enter at least 4 chars as name!] 문자열을 출력하는 메세지박스가 보이기 전 무엇을 할까?

  ▶ 아마도 (1) name의 길이를 알아낸다. / (2) name의 길이와 4를 비교한다. / (3) 조건문에 따라 jmp한다.


  ▶ 가장 최근에 보여지는게 jmp 문이 0x403026이다.

  ▶ 그리고 jmp문의 조건은 같을경우 0x4030F9로 점프한다. (위 그림 맨 아래로 점프)

  ▶ 그렇다면 앞쪽에는 비교해서 플래그에 저장하는 compare문이 있을 것이다.


  ▶ 먼저 0x40300F에서 호출하는 __vbaLenVar( ) 함수를 호출하려고 한다.

  ▶ Visual Basic의 API를 잘 모르기 때문에 정확히는 알 수 없으나, API이름으로 유추해보면 입력한 문자열의 길이를 반환해주는 것으로 보인다.

  ▶ 그리고 아래 eax와 edx를 스택에 push 하는 것이 보이는데 eax에는 아마도 __vbaLenVar( )함수의 반환 값인 문자열의 길이가 들어있을 것으로 추측된다.

  ▶ eax와 edx를 push 후 __vbaVarTstLt( ) 함수를 호출하려고 한다.

  ▶ __vbaVarTstLt( ) 함수가 어떤기능을 하는지는 모르겠지만.. 이쯤?에 4글자 이상인지 비교해서 True나 False의 값을 반환하는 것으로 보여진다.

  ▶ 0x403023주소에서 비교를 한 후 ax가 0과 같으면, 0x4030F9로 점프한다.

  ▶ 여기까지 정적으로 분석을 해보면 [3]의 (1), (2), (3)이 전부 들어가 있음을 확인할 수 있다.


 

 [4] 소스코드(정적분석)

0040318B

FF15 30104000

call dword ptr ds:[<&__vbaVarForInit>]

초기값 부르기?

00403191

8B1D 4C104000

mov ebx,dword ptr ds:[<&rtcMidCharVar>]

중간문자...?

00403197

85C0

test eax,eax

 

00403199

0F84 06010000

je crackme2.4032A5

 

0040319F

8D95 64FFFFFF

lea edx,dword ptr ss:[ebp-9C]

 

004031A5

8D45 DC

lea eax,dword ptr ss:[ebp-24]

 

004031A8

52

push edx

 

004031A9

50

push eax

 

004031AA

C785 6CFFFFFF 01000000

mov dword ptr ss:[ebp-94],1

 

004031B4

89BD 64FFFFFF

mov dword ptr ss:[ebp-9C],edi

 

004031BA

FF15 A8104000

call dword ptr ds:[<&__vbaI4Var>]

 

004031C0

8D4D 8C

lea ecx,dword ptr ss:[ebp-74]

 

004031C3

50

push eax

 

004031C4

8D95 54FFFFFF

lea edx,dword ptr ss:[ebp-AC]

 

004031CA

51

push ecx

 

004031CB

52

push edx

 

004031CC

FFD3

call ebx

 

004031CE

8D95 54FFFFFF

lea edx,dword ptr ss:[ebp-AC]

 

004031D4

8D4D AC

lea ecx,dword ptr ss:[ebp-54]

 

004031D7

FFD6

call esi

 

004031D9

8D8D 64FFFFFF

lea ecx,dword ptr ss:[ebp-9C]

 

004031DF

FF15 0C104000

call dword ptr ds:[<&__vbaFreeVar>]

 

004031E5

8D45 AC

lea eax,dword ptr ss:[ebp-54]

 

004031E8

8D8D 78FFFFFF

lea ecx,dword ptr ss:[ebp-88]

 

004031EE

50

push eax

 

004031EF

51

push ecx

 

004031F0

FF15 80104000

call dword ptr ds:[<&__vbaStrVarVal>]

인덱스의 한 문자

004031F6

50

push eax

 

004031F7

FF15 1C104000

call dword ptr ds:[<&rtcAnsiValueBstr>]

아스키->문자변환?, 입력한 값 처음문자 아스키값 반환..?

004031FD

8D95 24FFFFFF

lea edx,dword ptr ss:[ebp-DC]

 

00403203

8D4D AC

lea ecx,dword ptr ss:[ebp-54]

 

00403206

66:8985 2CFFFFFF

mov word ptr ss:[ebp-D4],ax

 

0040320D

89BD 24FFFFFF

mov dword ptr ss:[ebp-DC],edi

 

00403213

FFD6

call esi

 

00403215

8D8D 78FFFFFF

lea ecx,dword ptr ss:[ebp-88]

 

0040321B

FF15 CC104000

call dword ptr ds:[<&__vbaFreeStr>]

해제?

00403221

8D55 AC

lea edx,dword ptr ss:[ebp-54]

bp-54 = 2

00403224

8D85 24FFFFFF

lea eax,dword ptr ss:[ebp-DC]

bp-dc = 2

0040322A

52

push edx

2의 주소

0040322B

8D8D 64FFFFFF

lea ecx,dword ptr ss:[ebp-9C]

 

00403231

50

push eax

2의 주소

00403232

51

push ecx

2의 주소

00403233

C785 2CFFFFFF 64000000

mov dword ptr ss:[ebp-D4],64

64:'d'

0040323D

89BD 24FFFFFF

mov dword ptr ss:[ebp-DC],edi

edi = 2

00403243

FF15 AC104000

call dword ptr ds:[<&__vbaVarAdd>]

변수에 더하기?

00403249

8BD0

mov edx,eax

edx = 2

0040324B

8D4D AC

lea ecx,dword ptr ss:[ebp-54]

ecx = 2

0040324E

FFD6

call esi

vbaVarMove로 이동?

00403250

8D55 AC

lea edx,dword ptr ss:[ebp-54]

edx = 2

00403253

8D85 64FFFFFF

lea eax,dword ptr ss:[ebp-9C]

eax = 0

00403259

52

push edx

 

0040325A

50

push eax

 

0040325B

FF15 94104000

call dword ptr ds:[<&rtcHexVarFromVar>]

16진수로부터 변수?, 함수종료 후 eax->18F3C44F5084를 가리키고?

00403261

8D95 64FFFFFF

lea edx,dword ptr ss:[ebp-9C]

[위 문장 주석] 4F5084C8저장?

00403267

8D4D AC

lea ecx,dword ptr ss:[ebp-54]

 

0040326A

FFD6

call esi

변수이동?

0040326C

8D4D BC

lea ecx,dword ptr ss:[ebp-44]

 

0040326F

8D55 AC

lea edx,dword ptr ss:[ebp-54]

 

00403272

51

push ecx

 

00403273

8D85 64FFFFFF

lea eax,dword ptr ss:[ebp-9C]

 

00403279

52

push edx

 

0040327A

50

push eax

 

0040327B

FF15 84104000

call dword ptr ds:[<&__vbaVarCat>]

strcat과 비슷(연결하기, 덧붙이기)

00403281

8BD0

mov edx,eax

 

00403283

8D4D BC

lea ecx,dword ptr ss:[ebp-44]

 

00403286

FFD6

call esi

 

00403288

8D8D BCFEFFFF

lea ecx,dword ptr ss:[ebp-144]

 

0040328E

8D95 CCFEFFFF

lea edx,dword ptr ss:[ebp-134]

 

00403294

51

push ecx

 

00403295

8D45 DC

lea eax,dword ptr ss:[ebp-24]

 

00403298

52

push edx

 

00403299

50

push eax

 

0040329A

FF15 C0104000

call dword ptr ds:[<&__vbaVarForNext>]

다음변수 부르는?

004032A0

E9 F2FEFFFF

jmp crackme2.403197

 

  ▶ 0x4030F9주소에서 F8을 누르면서 진행하다보면 이 소스까지 접근하게 된다.

  ▶ 0x40318B주소에서 호출하는 __vbaVarForInit( )함수를 보면 초기변수로부터 무언가를 하는 것 같지만 정확히는 모르겠다.

  ▶ 0x403197주소는 주목할 필요가 있어보인다. 이유는 맨 아래소스에서 jmp문으로 여기를 가리키고 있기 때문이다.

  ▶ 0x4031F0주소에서 호출하는 __vbaStrVarVal( )함수를 보면 문자열의 값 중 첫 번째 한 문자를 반환하는 것으로 보인다.

  ▶ 0x4031F6의 push eax는 __vbaStrVarVal( )함수에서 반환받은 문자의 주소를 0x4031F7주소에서 호출하는 rtcAnsiValueBstr( )함수의 파라미터로 사용한다.

  ▶ rtcAnsiValueBstr( )함수는 __vbaStrVarVal( )함수에서 반환받은 문자의 주소를 문자로 아스키코드 값으로 보여주는 것으로 보여진다.

  ▶ 0x403233주소에서 어떤 상수 0x64를 [ebp-D4]에 저장한다.

  ▶ 0x403243주소에서 호출하는 __vbaVarAdd( )함수는 어떤변수가 연산을 하는 것으로 보여진다.

  ▶ 아마도 아까 넣은 0x64의 상수와 rtcAnsiValueBstr( )함수에서 반환한 문자열을 더하는 것으로 유추된다.

  ▶ 0x40325B주소에서 호출하는 rtcHexVarFromVar( )함수는 16진수 변수로부터 값을 얻어 저장하는 것으로 유추된다.

  ▶ 0x40327B주소에서 호출하는 __vbaVarCat( )함수는 c언어에서 strcat( )함수와 비슷할 것으로 보이며, 그렇다면 문자열을 연결하고 덧붙여 추가하는 기능을 할 것으로 예상된다.


5) 동적분석

 [1] 위에서 정적분석한 내용을 바탕으로 F8(Step Over)과 F2(BreakPoint)를 이용하여 따라가본다.

 [2] API내부에 들어갈 때는 스택에 파라미터를 저장하므로 파라미터를 확인하고 함수가 종료된 후 반환값을 확인한다.

 

 

6) 풀이

 [1] 시작 및 입력

  ▶ Name = abcd, Serial = 1234567 을 지정하고 Check를 누른다.


 [2] 첫 번째 분기문

  ▶ vbaLenVar( )에서 Name의 문자열 길이를 파악한다.

  ▶ vbaVarTstLt( )에서 Name의 길이가 4이상인지 확인하여 이상이면 0, 4미만이면 -1을 반환한다.

  ▶ 4미만이면 -1을 반환해서 [3]으로 이동하고, 4이상이면 0을 반환해서 [4]로 이동한다.

 

 [3] Name의 길이가 4미만인 경우

  ▶ 여기로 들어오게 되면 Name의 길이가 4자보다 작으므로 4자 이상 입력하라는 메세지박스를 띄운다.


  ▶ 위 그림과 같은 메세지박스를 띄우고 종료한다.


 [4] Name의 길이가 4이상인 경우

  ▶ 이 구문은 솔직히 잘 이해가 가지 않는다.

  ▶ 마지막에 jne 0x4034F3의 경우는 가보면 스택복원하고 종료하는 것으로 보여진다.


 [5] Name값 가져오기

  ▶ 여기서는 초기화된 문자열을 가져오는 것으로 보인다.

  ▶ 실행 시 eax에는 1이 반환된다.


 [6] 두 번째 분기문

  ▶ eax가 1이므로 빠져나가지 못한다.

  ▶ 이후 [7]로 이동한다. 만약 eax가 0이라면 [8]로 이동한다.

  ▶ 아마도 [7]로 미루어볼 때 반복문 횟수가 넘어가면 [8]로 넘어가는 듯 하다.


 [7] Serial 값 생성

  ▶ vbaStrVarVal( )함수에서 eax에 Name의 첫 번째 문자의 주소를 저장한다.

  ▶ rtcAnsiValueBstr( )함수에서 위에서 있던 주소에서 값을 가져와서 eax에 저장한다.

  ▶ vbaValAdd( )함수에서 첫 번째 문자의 아스키코드 값과 0x64(10진수 100)을 더해준다.

   - 첫 번째 문자가 a라면 [97+100 = 197]의 연산을 수행한다.

  ▶ rtcHexVarFromVar( )함수에서 더해준 값을 16진수로 나타내고 그 16진수를 String으로 변환한다.

   - 더해준 값 197을 16진수로 변환하면 0xC5가 되고, C5를 문자열로 변환한다.

  ▶ vbaVarCat( )함수에서 기존문자열 뒤에 방금 생성한 16진수 String 2개를 붙인다.

  ▶ 이 반복문을 4번만 반복하는 것으로 확인했다.

  ▶ 마지막 jmp문으로 [6]으로 이동하고 5번째에는 [6]에서 [8]로 이동하게 된다.

 

  ▶ 여기서 알 수 있는 것은 처음에 Name을 4자 이상 입력을 받은 이유는 Name을 가지고 Serial키를 만들기 때문이다.

  ▶ 또한 Serial키의 경우 4자 이상 어떤 수가 와도 그 값과 Serial키는 관련이 없다.

  ▶ Serial키를 구하는 알고리즘은 Name이 "abcdefg"라면

      

   위 그림과 같이 [(한 문자의 Ascii값 + 100) -> 16진수화 -> String 변환 후 이전 String에 추가]

   방법을 총 4번 반복하면 Serial 키를 구할 수 있으며 "abcd" 외 "efg"는 관련이 없기에 연산을 하지 않는다.

 

 [8] Serial 값 비교


  ▶ vbaVarTstEq( )함수에서 입력한 Serial값과 생성한 Serial값을 비교한다.

  ▶ 같다면 [9]로 이동하고 다르다면 [10]으로 이동한다.

  ▶ 만약 출력문을 확인하고 싶은 것이라면 맨 아래 je를 jne로 변경하면 Serial값이 다를경우 [9]로 이동할 것이다.

 

 [9] Serial 값이 정상적인 경우

  ▶ 축하한다는 타이틀과 키가 맞다는 내용을 담은 메세지박스가 출력된다.

 

 [10] Serial 값이 정상적이지 않을 경우 비교

  ▶ vbaVarTstNe( )함수에서 Not Equal일 때 [11]로 이동하게 된다.

  ▶ [9]에서 정상임을 확인했다면 [12]로 이동하게 된다.

 

 [11] Serial 값이 정상적이지 않을 경우

  ▶ Serial이 틀리다는 내용을 담은 메세지박스가 출력된다.

 

 [12] 프로그램 종료

 

 

  ▶ 기능을 정상적으로 수행하고 프로그램을 종료한다.

 

 7) 분석완료 후 변조

 [1] 필자는 리버싱 공부를 처음하며 Visual Basic함수를 모르는 상태이다.

 [2] 그러기 때문에 영어단어 약자 및 다른 언어의 비슷한 함수를 정적으로 유추해 보았다.

 [3] 이 후 유추한 내용이 맞는지 분석한다.

 [4] 만약 정상이라는 메세지박스를 보기 위함이라면 단순히 마지막 분기문만 바꿔 적거나, 문자열을 교환하거나, 출력 문자열의 주소를 교환하는 등 혹은 많은 블로그에서 다루고 있는 출력되는 문자열 부근에서 움직이는 스택 내 값을 찾아보는 방법 등이 있다.

 [5] [4]에서 말한 여러가지 방법은 이전에 다루었고 같은 방식이라 다루지는 않지만 Serial키를 만드는 알고리즘을 구하는 것이 목표인 것 같다.

 [6] 이 경우 알고리즘을 알고있기에 Name에 따른 Serial 값을 계산기로 계산해서 넣을 수도 있다.


댓글