반응형

abex' crackme2.exe
0.02MB

 

 

 

abex 2 crackme를 실행하면 위의 사진과 같은 화면이 나온다.

 

 

아무 값도 입력 하지 않고 check를 누르면 위의 메시지가 뜬다.

 

 

about을 누르면 위의 메시지가 뜬다.

 

 

임의의 값을 주고 check를 누르면 위의 메시지가 뜬다.

 

crackme 2는 serial key를 알아내야 하는데, Serial 값을 생성할 때 Name 문자열이 사용되는 것 같다.

 

crackme 2는 Visual Basic이라는 언어로 작성되었다.

 

 

VB 전용 엔진

 

VB 파일은 MSBVM60.dll(Microsoft Visual Basic Virtual Machine 6.0) 이라는 VB 전용 엔진을 사용하는데, 이는 The Thunder Runtime Engine 이라는 이름으로도 불린다.

 

VB 엔진의 사용 예시는 메시지 박스를 출력하고 싶을 때 VB 코드에서 MsgBox() 함수를 사용하는데

 

이때 VB 컴파일러는 MSVBVM60.dll!rtcMsgBox() 함수가 호출되도록 만들고, MSVBVM60.dll!rtcMsgBox() 함수 내에서 Win32 API인 user32.dll!MEssageBoxW() 함수를 호출한다.

 

(VB 코드에서 user32.dll!MessageBoxW() 함수를 직접 호출하는 것도 가능하다.)

 

 

VB 파일의 N 코드와 P 코드

 

VB 파일은 컴파일 옵션에 따라 N 코드와 P 코드로 컴파일이 가능하다.

 

N 코드 : 일반적인 디버거에서 해석 가능한 IA-32 명령어를 사용한다.

 

P 코드 : 인터프리터 언어 개념으로서 VB 엔진으로 가상 머신을 구현하여 자체적으로 해석 가능한 명령어(Byte code)를 사용하기 때문에 VB 엔진을 분석하여 에뮬레이터를 구현해야 정확한 코드 해석이 가능하다.

(ex. JAVA, Python)

 

 

이벤트 핸들러

 

VB는 주로 GUI 프로그래밍을 할 때 사용되며, IDE 인터페이스 자체도 GUI 프로그래밍에 최적화되어 있다.

 

즉 VB 프로그램은 Windows 운영체제의 Event Driven 방식으로 동작하기에 main() 혹은 WinMain()에 사용자 코드(디버깅을 원하는 코드)가 존재하는 것이 아니라, 각 Event Handler에 사용자 코드가 존재한다.

 

(아마 abex’ crackme 2에서는 Check 버튼 Handler에 사용자 코드가 존재할 것이다.)

 

 

undocumented 구조체

 

VB에서 사용되는 각종 정보들(Dialog, Control, From, Module, Function 등)은 내부적으로 구조체 형식으로 파일에 저장된다.

 

하지만 Microsoft에서는 이러한 구조체 정보를 정식으로 공개하지 않았기 때문에 VB 파일의 디버깅에 어려움이 있다.

 

 

프로그램이 시작되면 Entry Point에서 VB 엔진의 메인 함수인 ThunRTMain를 호출한다.

 

EP의 주소는 401238이고, push 명령으로 RT_MainStruct 구조체 주소 401E14를 스택에 입력한다.

 

그리고 40123D 주소의 CALL 명령에 의해 401232 주소의 JMP 명령어가 실행한다.

 

이 JMP 명령어에 의해 VB 엔진의 메인 함수인 ThunRTMain() 함수로 간다.

(이전에 스택에 입력한 401E14 값은 ThunRTMain() 함수의 파라미터이다.)

 

위 사진의 3줄의 코드가 VB 파일의 Startup 코드의 전부이다.

 

 

간접 호출

40123D 주소의 CALL 명령은 ThunRTMain() 함수 호출인데, 특이하게도 MSVBVM60.dll!ThunRTMain()으로 직접 가는 것이 아니라 중간의 401232 주소의 JMP 명령을 통해서 가는 방식이다.

 

 

해당 기법은 VC++, VB 컴파일러에서 많이 사용하는 간접 호출(Indirect Call) 기법이다.

 

(4010A0 주소는 IAT(Import Address Table) 영역이고 MSVBVM60.ThunRTMain() 함수의 실제 주소가 담겨있다.)

 

 

RT_MainStruct 구조체

 

RT_MainStruct는 ThunRTMain() 함수의 파라미터이고, 401E14 주소에 RT_MainStruct가 존재한다.

 

MS에서 RT_MainStruct를 공개하지 않았지만, 해외 Reverser들이 분석을 완료하고 인터넷에 공개했다.

 

RT_MainStruct 구조체의 멤버는 또 다른 구조체의 주소들이다.

 

즉, VB 엔진은 파라미터로 넘어온 RT_MainStruct 구조체를 가지고 프로그램의 실행에 필요한 모든 정보를 얻는다는 걸 알 수 있다.

 

 

ThunRTMain()

 

ThunRTMain() 함수의 코드이다.

 

메모리 주소가 완전히 달라진다.

 

현재 메모리 주소는 MSVBVM60.dll 모듈의 주소 영역이다.

즉, VB 엔진의 코드이므로 딱히 분석할 필요가 없다.

 

 

풀이

 

문자열 검색 기능으로 프로그램 내의 문자열들을 확인 후 Wrong serial! 문자열 부분을 더블 클릭해 이동한다.

 

 

위의 코드를 보면 Wrong serial!과 Nope, this serial is wrong! 문자열이 있고 메시지 박스 함수를 호출하는 call 명령어가 4934A6 주소에 있다.

 

프로그래밍 관점에서 생각했을 때 어떤 알고리즘으로 시리얼 키를 생성하고 사용자가 입력한 키와 문자열 비교를 통해서 각각 TRUE(키가 같음)와 FALSE(키가 틀림)로 코드가 갈라질 것이다.

 

즉, 위 코드 전후에 문자열 비교 코드 그리고 키가 맞았을 때 출력될 성공 메시지 박스 호출 코드가 존재할 것이다.

 

 

스크롤을 조금 위로 올리다보면 키가 같았을 때 출력될 메시지 부분이 있고

 

 

403332 주소에 je 명령어로 조건 분기를 하고 있다.

 

403329 주소의 sub_401058 함수의 코드를 보면 아래와 같은데

 

 

sub_401058 함수의 이름은 __vbaVarTstEq() 함수이다.

 

 

다시 조건 분기 부분 코드를 보면 __vbaVarTstEq() 함수의 리턴 값을 test 병령어로 비교하여 403332 주소의 je 명령어에 의해서 참, 거짓 코드로 분기하게 된다.

 

그렇다면 403329 주소의 __vbaVarTstEq() 함수가 문자열 비교 함수라면 그 위에 있는 두 개의 push edx, push eax 명령어는 비교 함수의 파라미터, 즉 비교할 문자열들일 것이다.

 

참고)

TEST : 논리 비교(Logical Compare)로 AND 연산과 동일하지만 operand 값이 변경되지 않고 EFLAGS 레지스터의 ZF 플래그에 영향을 준다.

 

JE : 같으면 점프라는 의미(Jump if equal)이고, ZF = 1이면 점프한다.

 

 

403329에 BP를 걸어두고 F9 키를 눌러 진행한다.

 

 

창이 뜨면 디버깅이 계속 진행될 수 있도록 임의의 값을 입력하고 Check를 누르면

 

403329까지 디버깅이 진행되었다.

 

위의 사진에서 403321에 있는 ss:[ebp-44] 코드는 스택 내의 주소를 의미하는데, 이것이 바로 함수에서 선언된 로컬 객체의 주소이다.

(로컬 객체는 스택 영역에 저장된다.)

 

이 상태에서 스택을 보면 아래와 같다.

 

 

스택에 저장된 메모리 주소 19F264와 19F254의 데이터를 dump 창에서 보면 아래와 같다.

 

위의 사진을 보면 다른 부분은 다 똑같지만 3C 8A 67 00과 AC 87 67 00 부분만 다르다.

 

VB의 문자열은 C++의 string 클래스와 마찬가지로 가변 길이 문자열 타입을 사용하기에 위의 그림과 같이 바로 문자열이 나타나지 않고 16byte 크기의 데이터가 나타난다.

(이게 VB에서 사용하는 문자열 객체이다.)

 

다른 값을 동일하고 3번째 4byte의 값만 다른데, 마치 메모리 주소처럼 보인다.

(가변 길이 문자열 타입은 내부에 동적으로 할당한 실제 문자열 버퍼 주소를 가지고 있다.)

 

메모리(dump) 창에서 마우스 우클릭 → address(주소)를 클릭하면 아래와 같이 나온다.

 

 

위의 사진을 보면 스택 창처럼 변경되었고, 특히 문자열 주소인 경우에는 해당 문자열을 표시해줬다.

(원래대로 보고 싶을 때는 마우스 우클릭 → Hex → ASCII)

 

위의 사진을 보면 알 수 있듯이 EDX(19F254)에는 실제 serial 값이고

EAX(19F264)에는 입력했던 “slay” 라는 serial 값이다.

(참고로 VB는 유니코드 문자열을 사용한다.)

 

주소 부분을 찾아가면 실제 문자열을 확인할 수 있다.

 

그렇다면 이제 Name에 slay를 줬을 때 만들어지는 serial 값이 D7D0C5DD라는 것을 알았으니 프로그램을 켜 Name에 slay를 serial에 D7D0C5DD를 입력한다.

 

 

그러면 위의 사진처럼 키가 맞다는 메시지가 나옴으로써 crackme 2 크랙에 성공했다.

 


Serial 생성 알고리즘

 

Crackme 2에서는 Name에 입력한 문자열을 기반으로 Serial을 생성한다.

 

이는 Name에 slay가 아닌 다른 값을 주고 Serial에 D7D0C5DD를 입력해보면 알 수 있다.

 

문제 풀이에서 봤던 조건 분기 코드 부분은 어떤 함수에 속해있을 것이고, 그 함수는 Check 버튼의 Event handler일 것이다.

 

이유는 Check 버튼을 선택했을 때 위 함수가 호출되었으며, 성공/실패 메시지 박스를 출력하는 사용자 코드를 포함하고 있기 때문이다.

 

 

EP에 있는 Call 명령을 실행하고, Name과 Serial 값을 입력한다.

 

 

그러면 참/거짓 조건 분기 코드가 있는 부분이 있을 것이고

 

스크롤을 쭉 올려서 402ED0 주소 부분을 보면, 함수가 시작될 때 스택 프레임을 구성하는 전형적인 코드이기에 이 위치가 함수의 시작 부분임을 알 수 있고, 이 부분이 바로 Check 버튼을 눌렀을 때 실행되는 Event handler이다.

 

402ED0에 BP를 걸고 디버깅한다.

 

만약 Win32 API 프로그램이라면 아래와 비슷한 방법으로 serial key가 생성될 것이다.

  • Name 문자열 읽기(GetWindowText, GetDlgItemText 등의 API 사용)
  • 루프를 돌면서 문자를 암호화하기(XOR, ADD, SUB 등)

 

VB 엔진 함수로 작성된 위 파일도 원리는 이와 같을 것이고, 예상이 맞다면 Event handler 시작 코드부터 디버깅하다보면 Name 문자열을 읽는 부분이 나올 것이고 바로 이어서 암호화 루프가 나올 것이다.

 

 

위 사진의 코드 부분은 Name 문자열을 읽는 코드이다.

 

VB 엔진 API를 이용하여 사용자가 입력한 문자열을 가져올 것이므로 CALL 명령어 위주로 디버깅을 한다.

 

(API에 전달되는 파라미터와 리턴 값을 잘 보아야 한다.)

디버깅을 진행다가 보면 위의 사진과 같이 4번째 CALL 명령어 부분이 있다.

 

402F8E의 코드를 보면 함수의 로컬 객체 ss:[EBP-88] 주소를 함수의 파라미터로 전달(push)하고 있다.

 

402F94 주소의 명령어까지 실행한 후 스택 창을 보면 아래와 같다.

 

 

그리고 dump 창에서 19F210의 데이터를 보면 아래와 같다.

 

 

찾을려는 것은 Name 문자열이고, VB에서 문자열은 스트링 객체를 사용한다고 했으니 메모리를 Hex → ASCII 값으로 보면 실제 문자열을 알아보기 쉽지 않기 때문에 address 보기 모드로 바꾸면 위의 사진처럼 보인다.

 

위의 사진처럼 보기 방법을 변경하면 VB 스트링 객체의 실제 문자열 저장 버퍼 주소를 직접 볼 수 있고, 이 상태로 402F98 주소의 CALL 명령까지 실행하면 아래의 사진과 같이 스트링 객체에 값이 저장된다.

 

 

이후 계속 디버깅을 해나가다 보면 아래와 같이 루프, 즉 반복문을 만나게 된다.

 

403102부터 4032A5까지가 암호화 루프 구간인데

 

간단히 설명하자면 __vbaVarForInit(), __vbaVarForNext()는 마치 linked list에서 next pointer를 이용해 다음 element를 참조하듯이 문자열 객체에서 한 글자씩 참조할 수 있게 해주고

 

loop count(EBX)를 세팅해서 정해진 횟수만큼 루프를 돌게 된다.

(실제로 테스트 해봤을 때는 입력 받은 Name 문자열의 앞 문자만 사용한다.

코드 내에 문자열의 길이를 체크하여 4보다 작으면 에러 메시지 박스를 출력하는 부분이 존재한다.)

 

 

name에 입력한 문자열은 “slay” 이다.

 

40323D까지 디버깅을 진행하고 스택을 보면 아래와 같다.

 

 

19F244, 19F1BC, 19F1FC의 각 메모리 주소를 보면 아래와 같다.

 

 

위에서부터 각각 EDX, EAX, ECX 레지스터의 값이다.

 

EAX 레지스터의 값에 암호화 키인 d = 64가 들어있다.

 

그리고 아래의 함수를 실행시키면 ECX 레지스터가 가리키는 버퍼에 암호화된 값이 저장된다.

 

 

 

403243 주소의 함수의 결과 값인 D7이 ECX에 저장되었는데

D7는 원본값 73(’s’)에 암호화 키 64를 더해서 생성된 값이며, Serial 값 D7D0C5DD에서 첫 두글자를 의미한다.

 

그리고 아래 코드에서 숫자 D7을 UNICODE 문자 ‘D7’으로 변환한다.

 

 

 

51873C로 가보면 “D7” 문자열이 생성되어 있다.

즉, 숫자 D7이 유니코드 문자열 “D7”으로 변경된 것이다.

 

 

그리고 이렇게 변환된 문자열을 이어 붙여주는 코드가 있다.

 

 

마지막 루프를 실행하며 이런 식이 생성된다.

 

serial = old(”D7D0C5”) + add(”DD”) = “D7D0C5DD”(최종적으로 완성된 serial 문자열)

 

암호화 방법을 정리하면 아래와 같다.

1. 주어진 Name 문자열을 앞에서부터 한 문자씩 읽 기(총 4회)

2. 문지를 숫자(ASCII 코드)로 변환

3. 변환된 숫자에 64를 더함

4. 숫자를 다시 문자로 변환

5. 변환된 분자를 연결시킴

반응형

'전쟁 > crackme' 카테고리의 다른 글

crackme #6(ReWrit's Crackme#1)  (0) 2022.06.21
abex' crackme #5  (0) 2022.06.14
abex' crackme #4  (0) 2022.06.14
abex' crackme #3  (0) 2022.06.14
abex' crackme #1  (0) 2022.06.05

+ Recent posts