[crackme2] abex' 2nd crackme 풀이 및 복원
※ 단순한 값만 확인하지 않고 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->18F3C4는 4F5084를 가리키고? |
00403261 | 8D95 64FFFFFF | lea edx,dword ptr ss:[ebp-9C] | [위 문장 주석] 4F5084는 C8저장? |
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 값을 계산기로 계산해서 넣을 수도 있다.
'Try Attack > Reverse Engineering[basic]' 카테고리의 다른 글
[crackme5] abex' 5th crackme 풀이 및 복원 (0) | 2018.11.16 |
---|---|
[crackme3] abex' 3rd crackme 풀이 및 복원 (0) | 2018.11.08 |
[crackme1] abex' 1th crackme 풀이 및 복원 (0) | 2018.11.07 |
악성코드와 리버싱 (0) | 2018.10.09 |
전략 (0) | 2018.09.16 |
댓글