Name과 Serial 값을 입력하고 Check 버튼을 누르면 된다.
abex’ crackme 2번 문제와 같은 것 같다.
위와 같이 slay / slayslay를 입력하고 Check 버튼을 누르면 어떠한 문구도 뜨지 않은 채 입력했던 내용이 초기화되어 삭제된다.
그렇다면 맞지 않은 serial 값을 입력했을 때 아무런 문구도 띄우지 않는다는 것이다.
Visual Basic으로 만들어진 프로그램의 기초 지식
역시나 abex’ crackme 2번 문제처럼 Visual Basic으로 작성된 언어이다.
VB 파일은 MSBVM60.dll(Microsoft Visual Basic Virtual Machine 6.0) 이라는 VB 전용 엔진을 사용한다.
그리고 VB는 주로 GUI 프로그래밍을 할 때 사용되고, VB 프로그램은 Windows 운영체제의 Evet Driven 방식으로 동작하므로 Main() 혹은 WinMain()에 사용자 코드(디버깅을 원하는 코드)가 없고 각 이벤트 핸들러에 사용자 코드가 있다.
즉, crackme 7번에서는 Check 버튼 Handler에 사용자 코드가 있을 것이다.
위의 3개의 주소 부분은 VB 파일의 Startup 코드이다.
Entry Point에서 VB 엔진의 메인 함수인 ThunRTMain을 호출하는데 직접 MSVBVM60.dll!ThunRTMain()으로 가는 것이 아니라 JMP 명령을 통해 간접 호출한다.
또한 push 40150C는 RT_MainStruct의 주소이고, 이는 ThunRTMain() 함수의 파라미터이다.
참고로 ThunRTMain() 함수 내부는 MSVBVM60.dll 모듈의 주소 영역이므로 VB 엔진이기 때문에 딱히 분석하지 않아도 된다.
풀이
문자열 찾기 기능을 이용해보면 위의 문구가 전부이다.
여기서는 어떠한 힌트도 얻을 수 없지만
Name을 입력하고 Serial 값을 일치하게 입력하면 문제가 풀릴 것이기 때문에
지정된 Serial 값과 입력한 값을 비교하는 부분이 있을 것이다.
그리고 문자열을 비교하는 함수에는 대부분 “cmp” 키워드가 들어간다.
상단 우측에 있는 아이콘들 중 휴대폰에 화살표가 그려진 아이콘을 누르면 모듈들을 출력해준다.
여기서 cmp 키워드로 검색하면 두 개의 함수가 나온다.
- vbaVarCmpEq
- vbaVarCmpNe
위의 두 개의 함수는 각각 문자열이 같을 때(equal)와 같지 않을 때(Not equal)를 의미하는 것 같다.
위의 두 가지 함수에 BP를 설정한다.
디버거에서 F9 키를 눌러 실행한 뒤 위의 사진과 같이 입력한 후 Check 버튼을 누른다.
그렇다면 위와 같이 vbaVarCmpEq 부분에서 멈춘다.
여기서 먼저 Serial 값을 확인할 수 있는데 vbaVarCmpEq 함수의 인자로 비교할 문자열 1과 비교 대상 문자열 2가 필요할 것이다.
vbaVarCmpEq 함수 호출 전에 내가 입력한 값과 지정된 Serial 값이 인자로 넘어갈 것이기에 아주 살짝만 스크롤을 위로 올리면 Name의 값이 slay 일 때의 Serial 값이 나와있다.
위와 같이 Name에 slay를 주고 Serial 값에 이전에 확인했던 Serial 값을 입력 한 뒤 Check 버튼을 누르면 Registered! 문구가 뜬다.
하지만 Name을 다른 값으로 입력할 때마다 디버거로 Serial 값을 알아내기는 번거로우니 Serial 값을 생성하는 알고리즘을 알아낸다.
먼저 분석 결과를 정리하자면 아래와 같다.
1. Name에 입력된 문자열을 가져온다.
2. 해당 문자열의 길이만큼 문자 하나 하나의 ascii 값을 35와 xor 연산한다.
3. 각 문자(ascii 16진수 값)와 35를 xor 연산한 결과를 합치면 serial 값이 된다.
ex)
Name : abc을 입력했을 때
a : 0x61 xor 35 = 54
b : 0x62 xor 35 = 57
c : 0x63 xor 35 = 56
== 545756
Check 버튼 눌렀을 때 입력한 값을 가져오는 함수와, Serial을 생성하는 함수 위치 찾기
abex’ Crackme 2번 문제와 같이 Name에 입력한 문자열을 기반으로 Serial을 생성한다.
위에서 봤던 vbaVarCmpEq와 vbaVarCmpNe 함수를 호출하는 부분 즉, 문자열이 일치하는지 일치하지 않은지 조건에 따라 호출하는 부분은 어떤 함수에 속해있을 것이고, 그 함수는 Check 버턴의 Event Handler일 것이다.
이유는 abex crackme 2번 문제와 동일하게 Check 버튼을 눌렀을 때 vbaVarCmpEq 또는 vbaVarCmpNe 함수가 호출되기 때문이다.
402A54 주소의 vbaVarCmpEq 함수 부분에서 스크롤을 쭉 올리다보면
4029000 주소에 함수 프롤로그 부분을 의미하는 스택 프레임을 구성하는 전형적인 코드가 있다.
즉, 402900 주소가 Check 버튼을 눌렀을 때 실행되는 Event handler의 시작 위치이므로 BP를 설정한다.
Ctrl + F2 키로 재실행 후 F9키를 눌러 실행한 뒤 Name : slay / Serial : slayslay를 입력한 후 Check 버튼을 누르면 위의 사진과 같이 402900 주소에서 멈추게 된다.
Name 기반으로 Serial Key가 생성되기 때문에 Name에 있는 문자열을 가져오는 함수 부분이 있을 것이고, 해당 함수 부분 다음에 이어서 암호화 하는 부분이 있을 것이다.
VB 엔진 API를 이용하여 사용자가 입력한 문자열을 가져올 것이기에 CALL 명령어 위주로 디버깅한다.
위의 사진은 402900 주소로부터 4번째 call 명령어 부분이다.
F8 키를 눌러 진행하다보면 4번째 call 명령 실행 됐을 대 Name에 입력했던 slay 값을 가져온다.
40298A와 40298D의 명령을 보면 함수의 로컬 객체 ss:[ebp-2C] 주소를 함수의 파라미터로 전달(push)한다.
40298E 주소까지 EIP를 위치시키고, 스택창을 보면 ebp-2C주소인 19F26C 주소가 스택에 저장되어있다.
19F262C 주소를 Dump 창에서 확인하면 현재는 0으로 채워져있다.
하지만 위의 사진과 같이 402995 주소의 call 명령이 실행된 후에는 19F26C 주소에 “slay”값이 들어가있다.
이후 4029B9 주소로 점프한다.
4029C2 주소의 vbaStrMove() 함수를 통해 19F26C(ebp-2C) 주소에 있는 “slay” 문자열을 19F280(ebp-18) 주소로 옮기고 19F26C 주소의 값은 0으로 초기화한다.
그리고 ecx 레지스터에 19F268(ebp-30) 주소를 담고, 4029CB 주소의 vbaFreeObj() 함수를 통해 19F268 주소의 값을 0으로 만든다.
이어서 4029DC 주소의 [edx+704]의 함수를 통해 Serial 값이 생성된다.
(4029DC 주소를 실행하고 나면 19F26C(ebp-2C) 주소에 Serial 값이 담긴다.)
Serial key가 생성되는 함수를 호출하는 부분을 좀 더 보자면
현재 esi에는 7744C8 주소가 담겨있는데 7744C8 주소의 값은 4042F4로 BASIC_CLASS_QueryInterface가 들어있고 4042F4를 edx에 담는다.
eax에 19F26C(ebp-2C) 주소를 담고
ecx에 19F280(ebp-18) 주소를 담은 뒤
eax, ecx, esi 순으로 스택에 저장하고 [edx+704] 주소의 함수를 호출한다.
4029DC 주소의 [edx+704] 주소의 함수 호출 명령이 실행되고 난 후
19F26C(ebp-2C) 주소에는 Serial key가 들어가게 되는데 이후 vbaStrMove() 함수를 통해 19F26C(ebp-2C) 주소에 있는 Serial key를 19F280(ebp-18) 주소로 옮긴다.
이후 402A0A 주소에서 다시한번 [ecx+A0] 주소의 함수를 호출하는데 이번에는 입력한 Serial 값을 가져온다.
입력한 Serial 값을 가져온 후에는 위의 로직이 실행된다.
처음에 봤던 vbaVarCmpEq 부분이다.
Serial 생성 알고리즘 분석
위의 부분이 Serial Key를 생성하는 함수를 호출하는 부분이다.
인자로 eax, ecx, esi가 넘겨지는데 이는 각각 19F26C(ebp-2C), 19F280(ebp-18), 647E88이다.
Serial Key를 생성하는 함수 내부로 들어가면 4027E0 주소로 점프한다.
4027E0 주소로 가면 스택 프레임이 이루어진다.
즉, 이 부분이 함수의 시작이다.
ebp+C는 “slay” 문자열이 있는 19F280 주소, ebp+8는 647E88 값, ebp+10은 19F26C 주소이다.
즉, 아래와 같다.
eax = 19F26C
esi = 647E88
ebx = 19F280
그리고 edx에 19F190(ebp-1C) 주소를 담고
19F26C의 값을 0으로 만든 뒤
ecx에 647E88 주소에 있는 값 4042F4을 넣는다.
그리고 나서 스택에 edx, ebx, esi 순으로 넣는다.
그 다음 스택에 edx, ebx, esi를 넣은 후 402824 주소의 [ecx+6F8] 주소의 함수를 호출하는 명령을 실행하면
edx의 값 19F190(ebp-1C)에 Name에 입력했던 값이 역순으로 들어간다.
그리고 402840 주소로 이동하여 역순으로 바뀐 값 “yals”를 edx에 담고, 19F190 주소의 값을 0으로 초기화한 후 ecx에 19F294 주소를 담은 뒤 vbaStrMove() 함수를 호출하고 나면 19F194에 “yals” 문자열이 담긴다.
esi에 담긴 주소의 값 4042F4를 eax에 넣고 19F190(ebp-1C) 주소를 ecx에 담은 후 ecx, ebx, esi 순으로 스택에 넣고 [eax+6FC] 주소의 함수를 호출한다.
402859 주소에서 [eax+6FC] 주소의 함수가 실행되고 나면 19F190 주소에 “FYTL” 이라는 값이 담기고, 19F280 주소의 값이 “slay”에서 “FYTL”로 바뀐다.
402859 주소에서 호출하는 [eax+6FC] 주소의 함수 내부로 가보면
Name에 입력한 “slay”의 값을 문자열 길이 만큼 반복하여 한 문자 한 문자를 가져와 35와 xor 연산한 결과의 문자를 vbaMidStmtBstr() 함수로 바꾼다.
이 함수가 끝나면 “slay” 문자열의 각 문자를 35와 xor 연산한 값인 “FYTL”이 반환된다.
402859 주소에서 [eax+6FC] 주소의 함수가 실행되고 나면 19F190(ebp-1C)에는 “FYTL”, 19F194(ebp-18)에는 “yals” 값이 들어간다.
그리고 402882 주소에서 edi에 담긴 주소의 함수인 vbaStrMove 함수를 호출하면 19F194(ebp-18) 주소에 “FYTL” 문자열이 담긴다.
이제 seriam 값을 생성하는 함수를 호출한다.
인자로 eax(19F190), ebx(19F280), esi를 스택에 넣는데
19F190 주소는 0으로 아무런 값이 안 들어가 상태이고
10F280 주소는 “FYTL” 문자열이 들어가 있는 상태이다.
그리고 esi 주소의 값은 4042F4로 BASIC_CLASS_QueryInterface 값이다.
[edx+700] 주소의 함수 내부로 들어간다.
4026E4 주소에서 vbaStrVarVal 함수를 통해 “FYTL” 문자열 중 문자 한 개의 주소를 가져온다.
4026EB 주소에서 rtcAnsiValueBstr 함수를 통해 vbaStrVarVal의 반환값인 문자의 주소를 문자 형태의 ascii 값으로 보여준다.
402705 주소에서 rtcHexVarFromVar 함수를 통해 ascii 값을 hex 값으로 변환한다.
그리고 402719 주소의 vbaVarCat에서 이 hex 값들을 이어붙힌 후 vbaStrMove로 문자열을 옮긴 뒤 반환한다.
최종적으로 Serial Key 생성하는 함수가 끝나면 Serial key 4659544C가 만들어진다.
info
1.vbaStrMove()
BSTR * __vbaStrMove ( BSTR * 소스 , BSTR * 대상 )
{
//src = ECX
//dest = EDX
*ECX = EDX ;
return* ECX ;
}
2. rtcBstrFromAnsi()
- ascii 16진수 값에 해당하는 문자를 반환한다.
3. rtcMidCharVar
- 루프 인덱스 값과 Name에 입력한 문자열 이렇게 2개의 인자로 Name에 입력한 문자열의 n 번째 문자열을 새로운 변수에 저장한다.
(숫자값을 문자형으로 바꾼다.)
4. vbaStrVarVal()
- 문자열에서 N 번째 문자의 주소를 반환한다.
5. rtcAnsiValueBstr()
- 문자에 해당하는 ascii 16진수 값을 반환한다.
ex) a에 해당하는 0x61을 반환
int rtcAnsiValueBstr ( BSTR * In )
{
return atoi ( In );
}
6. rtcHexVarFromVar()
값을 Hex 값으로 변환한다.
7. vbaVarCat()
- 기존 문자열과 붙인다.
(루프를 사용해 생성된 hex 문자열들 하나씩 합친다.)
BSTR* __vbaVarCat(Variant* Out, Variant *second, Variant *first)
{
Out = new Variant;
Out->pbstrVal = malloc(sizeof(*(second->pbstrVal)) + sizeof(*(first->pbstrVal));
Out->vt = 8;
*(Out->pbstrVal) = *(first->pbstrVal) + *(first->pbstrVal);
return Out;
}
8. vbaHresultCheckObj()
- 완성된 암호화 사용자가 입력한 암호가 동일한지 체크할 때가 오류가 발생하면 호출된다.
9. vbaUI1I2()
int rtcAnsiValueBstr(Int In)
{
//In = ECX
return ECX & 0xFF;
}
10. vbaR8Str()
float __vbaR8Str(BSTR *In)
{
return atof(In);
}
11. vbaVarStrMove
BSTR* __vbaVarStrMove(Variant *Source)
{
if(Source->vt == VT_BSTR)
{
return Source->pbstrVal; //Address of the string is stored in the 8th position
}
//Other stuff happened here but since it was never called i didn't reversed it
return 0;
}
'전쟁 > crackme' 카테고리의 다른 글
Crackme #9 (0) | 2022.06.23 |
---|---|
Crackme # 8(Crack me by hackereh@!) (0) | 2022.06.23 |
crackme #6(ReWrit's Crackme#1) (0) | 2022.06.21 |
abex' crackme #5 (0) | 2022.06.14 |
abex' crackme #4 (0) | 2022.06.14 |