abex` 2nd crackme Write-Up
사용 도구: 올리디버거 2.01
abex` 2nd crackme(이하 원본파일)를 풀어보자.
먼저 원본파일을 실행시키면 다음과 같은 메세지 박스가 뜬다.
Quit 버튼을 클릭하면 프로그램이 종료되고 이외의 버튼을 클릭했을 때는 다음과 같이 메세지 박스가 뜬다.
1. Check 버튼 클릭
2. About 버튼 클릭
3. Name에 'test' 문자열을 입력하고 Check 버튼을 클릭
4. Name에 'test' 문자열 입력, Serial에 '1' 1개 입력하고 Check 버튼을 클릭
5. Name에 'test' 문자열 입력, Serial에 '1' 20개 입력하고 Check 버튼을 클릭
다양한 방법으로 프로그램을 실행해봤지만 Name과 Serial에 어떤 값을 입력해야 하는지는 모르겠다.
Name 또는 Serial의 값이 고정되어 있는 건지 Name의 값에 따라 Serial이 달라지는 건지 분석해보자.
올리디버거로 원본파일을 열어보면 위 같은 코드를 확인할 수 있다.
첫 번째 문제와 달리 두 번째 문제의 코드에서 main 함수를 바로 찾기란 쉽지 않다.
따라서 올리디버거의 기능인 문자열 검색을 이용해보자.
코드 섹션에서 우클릭 - Search for - All referenced strings 기능을 클릭하면 위 같은 창이 뜬다.
위 창에서 앞서 살펴봤던 실행결과들의 내용과 관련된 문자열들을 찾을 수 있는데 두 번 반복되는 것을 확인할 수 있다.
사진 내에서 윗부분은 문자열들의 정의되는 부분이고 아랫부분이 실제로 사용되는 부분이다.
[사진 7]에서 아랫 부분의 문자열을 따라가서 살펴보면 위와 같이 함수 프롤로그 부분을 찾을 수 있다.
함수 프롤로그에 브레이크 포인트(이하 BP)를 설정하고 실행하면 [사진 1]처럼 Name과 Serial을 입력할 수 있는 창이 뜬다. Name과 Serial을 입력(Name: TEST, Serial: abcd)하면 창은 사라지고 디버깅을 할 수 있다. 따라서 이 곳은 메인함수는 아니고 메인함수에서 호출되는 함수라고 추측할 수 있다.
먼저 이 함수에서 사용하는 스택의 크기가 매우 넓었다. 402ED3 주소에서 스택을 0x0C 크기만큼 확보하고 402EE9 주소에서 0x144크기 만큼 추가로 확보한다. 그래서 내가 입력한 Name(TEST)과 Serial(abcd)이 스택에서 찾기가 쉽지 않았지만 디버깅을 한줄씩 해보면 위 같이 [EBP-88] 주소를 어떤 함수에 인자로 전달한 후에 함수를 실행하면 [EBP-88] 주소에 내가 입력한 Name이 저장된다.
그 다음 [사진 12]의 명령어들을 요약하자면 [EBP-88] 주소에 저장된 "TEST" 문자열을 [EDB-74] 주소에 저장한다.
*[참고: 이 파일은 Visual Basic(이하 VB)으로 만들어졌는데, VB에서는 유니코드 기반의 가변 길이 문자열 객체를 사용한다. 따라서 가변 길이 문자열 객체는 필요에 따라 내부에서 수시로 동적 메모리 할당/해제 작업이 발생한다.]
그 다음, 많은 명령어 및 함수들이 있지만 각각 어떤 기능을 하는지는 파악하지 못했다. 그래서 함수를 실행하고 리턴되는 값을 중심으로 찾아봤다. 그 결과 [사진 14]의 명령어들을 찾았는데, 4031F0 주소의 __vbaStrVarVal 함수를 실행하면 어떤 주소가 리턴되었다. 이 주소를 참조해보면 [사진 15]처럼 'T' 문자가 저장되어 있었다. 그 다음 4031F7 주소의 rtcAnsiValueBstr 함수를 실행하면 [사진 16]처럼 값 0x54가 리턴되었다. 이 값은 내가 Name에 입력한 "TEST" 문자열의 첫 번째 문자의 아스키 코드 값이다.
*[메모: 내가 입력한 "TEST" 문자열은 [EBP-74] 주소에 저장되어 있다. 하지만 위 함수들의 전달인자로 [EBP-54], [EBP-88] 주소를 전달하고 있는데, 어떻게 [EBP-74] 주소가 아닌 다른 주소들을 전달하여 값을 리턴하는지는 정확히 이해하지 못하였다.]
[사진 17]의 다음 명령어들을 분석해보면, 리턴된 아스키 코드 값을 스택에 저장하고 403243 __vbaVarAdd 함수의 인자로 전달한다. 그리고 리턴되는 주소를 참조하면 [사진 18]을 확인할 수 있는데, 이는 위에서 확인했던 [사진 13]의 VB string 포맷과 비슷하다. 즉 문자열은 18F37C 주소의 0xB8인 셈이다. __vbaVaradd 함수의 기능이 무엇인지 정확히 모르지만 이 함수에 값 0x64와 내가 입력했던 첫 번째 문자의 아스키코드 값 0x54를 인자로 전달한다. 그리고 이 두 값을 더하면 0xB8이다. 일단 확실하지 않으므로 가정만 해두자.
다음으로 루프문을 끝까지 실행하고 스택을 보면 위에서 확인했던 0xB8이 유니코드로 변환되어 스택에 여러번 저장된 것을 확인할 수 있다.
그리고 루프문의 끝에 BP를 설정하고 실행하면 [사진 21]과 같이 스택에 문자열 "B8A9"이 저장된 것을 확인할 수 있다. "A9" 문자열도 "B8" 문자열과 같은 루프문에서 생성되었으므로 위에서 가정한 계산을 거쳤을 것이다. 내가 Name에 입력한 두 번째 문자는 'E'이고 아스키 코드 값은 0x45이다. 이 값에 0x64를 더하면 0xA9가 된다.
루프문을 탈출하고 스택을 확인하면 [사진 22]처럼 길이 8의 문자열 "B8A9B7B8"이 저장되어 있다. 이 값들은 Name에 입력한 "TEST" 문자열들이 변환된 것이다. 그리고 디버깅을 진행하면 [사진 23]처럼 내가 입력한 Serial과 변환된 "B8A9B7B8"을 비교하고 다를 경우엔 실패했다는 내용의 메시지 박스 함수를 실행하는 코드로 분기한다.
위 처럼 Name: Test, Serial: B8A9B7B8을 입력 시 성공했다는 내용의 메시지 박스가 실행된다.
그리고 다른 문자열 "test"로 테스트해보았다. Serial 계산과정에 따라서 각 문자들의 아스키 코드 값에 0x64를 더하였다.
문자 't'의 아스키 코드 값 0x74 + 0x64 = 0xD8
문자 'e'의 아스키 코드 값 0x65 + 0x64 = 0xC9
문자 's'의 아스키 코드 값 0x73 + 0x64 = 0xD7
문자 't'의 아스키 코드 값 0x74 + 0x64 = 0xD8
Serial: D8C9D7D8
*후기
VB 함수들의 지식이 없어서 이해하는데 힘들었다. 그리고 명령어 코드가 너무 길어서 각 코드들이 정확히 어떤 기능을 하는지도 이해하지 못했다. 어렵다.