kj0on

abex' crackme 2 상세분석 본문

Reversing/abex' crackme

abex' crackme 2 상세분석

kj0on 2025. 6. 17. 20:50
목차 접기

0. 실행환경

 

0-1. 운영체제 (1)

Window 11 Home


0-2. 툴 (2)

x32dbg
Detect it easy


1. 파일

abex' crackme2.exe
0.02MB

 

https://binvis.io/


2. 프로그램 동작

[이미지1] abex' crackme 2

Name과 Serial을 입력하는 입력창이 보인다.

 

[이미지2] 프로그램 문자열 길이 에러 메시지

Name에 입력한 값이 4자리 보다 작은 값을 입력 후 Check버튼 동작 시 해당 오류 메시지를 보여준다.

 

[이미지3] 프로그램 시리얼 에러 메시지

Name에 입력한 값이 4보다 큰 값을 입력 후 Check버튼 동작 시 해당 오류 메시지를 보여준다.

 

[이미지4] 프로그램 정보 메시지

About 버튼 동작 시 프로그램의 정보를 보여준다.

 

[이미지5] 프로그램 Quit 버튼

Quit버튼 동작으로 프로세스가 종료된다.


3. Detect It Easy

[이미지6] DIE 실행 결과

32비트 실행파일, Microsoft Visual Basic으로 작성된 프로그램임을 알 수 있다.


4. 분석

 

4-1. EntryPoint

[이미지7] EntryPoint

VB 파일은 MSVBVM60.dll의 VB 전용 엔진을 사용한다. 따라서 abex' crackme 1에 비해 코드가 방대하다(VB 전용 엔진 코드가 포함되어 있기 때문). EP로 이동해서 인접해 있는 코드들을 살펴봐도 메시지를 출력하는 부분을 찾기 어렵다. 그 이유는 VB의 경우 GUI프로그래밍에 최적화 되어있어 Event Driven 방식으로 동작한다. 버튼을 클릭했을 때의 이벤트가 발생하면 메시지를 띄워주는 방식이다. 따라서 버튼 핸들러에 사용자 코드가 존재할 가능성이 크다. (리버싱 핵심원리)


4-2. 문자열 참조

[이미지8] 프로그램 시리얼 에러 메시지

x32dbg에서 문자열 참조를 통해 원하는 위치로 이동하여 분석이 가능하다.

 

[이미지9] 문자열 참조 결과

EP로 이동 후 문자열 참조를 하면 메시지에 출력됐던 문자를 확인할 수 있다.

 

[이미지10] 문자열 참조 결과

시리얼 키가 일치했을 때 출력되는 메시지임을 추측할 수 있다.


4-3. 모듈 간 호출

[이미지11] Visual Basic rtcMsgBox (https://www.vb-decompiler.org/vb60_reversing.htm)

다른 방법으로 VB에서 메시지 박스를 띄워주는 함수를 파악하여 검색하는 방법이 있다. 앞선 정적분석 결과에 따르면 "컴파일러 : Microsoft Visual Basic(6.0) [Native]"으로 나타난다. 이는 해당 프로그램의 컴파일 옵션이 N code에 해당한다는 사실을 알려준다. 더 쉬운 방법으로 x32dbg에서 코드를 볼 수 있다는 근거로 N code임을 확인할 수 있다. P code는 인터프리터 언어 개념으로 VB 엔진으로 가상 머신을 구현하여 해석가능한 명령어를 사용하는 방식이다. 따라서 P code를 해석하려면 VB엔진을 분석하여 에뮬레이터를 구현해야 한다 (https://decoded.avast.io/davidzimmer/writing-a-vb6-p-code-debugger/). 반면 N code의 경우 디버거에서 해석 가능한 IA-32 Instruction을 사용한다. 다만 컴파일 옵션에 상관없이 메시지를 출력할 때는 rtcMsgBox가 사용된다. P code에서는 가상머신이 바이트코드를 해석하다 내장 연산을 만나면 간접적으로 rtcMsgBox를 호출한다. N code에서는 사용자 기계어가 직접 call rtcMsgBox를 실행한다. 차이점은 “그 함수까지 어떻게 도달하느냐”가 달라진다는 점이다. 즉, 대상 프로그램이 어느 모드로 빌드됐든 살펴봐야 할 핵심 함수는 동일하게 rtcMsgBox다.

 

[이미지12] 모듈간 호출

EP에서 현재 구역에서 모듈간 호출을 살펴보면 rtcMsgBox가 호출되는 부분을 확인할 수 있다.

 

[이미지13] rtcMsgBox

메시지를 띄워주는 코드를 확인할 수 있다.

 

[이미지14] abex' crackme 2

시리얼 키가 일치하는지 확인하려면 비교를 해주는 함수가 있을 것이라는 추측을 할 수 있다. 자체적으로 비교 함수를 작성했을 수도 있겠지만(이 경우는 분기시점 파악하여 하나씩 분석) API함수를 사용했을 가능성이 있다. 후자의 경우 더 쉽게 해당 루틴을 찾을 수 있기 때문에 뒤의 방법부터 적용한 뒤 결과가 안나오면 전자의 방식을 사용한다.

[이미지15] Visual Basic Comparison Functions (https://www.vb-decompiler.org/vb60_reversing.htm)

메시지를 띄워주는 함수를 찾는 방식과 비슷하게 비교함수도 찾아준다. 어떤 방식으로 비교하는지는 추측하기 어렵기 때문에 EP에서 4가지 모두 검색해준다.

 

[이미지16] 모듈간 호출 검색결과

비교함수 4개를 검색하면 하나의 검색 결과를 얻을 수 있다. 비교함수로 __vbavartsteq함수를 사용한다. MSDN에 해당 함수원형을 검색할 수 없는데 그 이유는 비공개 내부 API일 가능성이 크다. 리버싱 핵심원리 에서는 "VB에서 사용되는 각종 정보들은 내부적으로 구조체 형식으로 파일에 저장된다. Microsoft에서는 이러한 구조체 정보를 공식적으로 공개하지 않았기 때문에 VB 파일의 디버깅에 어려움이 있다."고 설명한다. 이러한 이유 때문에 MSDN 검색 결과가 나오지 않는듯 하다.

 

[이미지17] GoSecure Titan Labs Technical Report: BluStealer Malware Threat (https://gosecure.ai/blog/2021/09/22/gosecure-titan-labs-technical-report-blustealer-malware-threat/)

보안업체에서 VB 내부 함수인 rtcDir을 "undocumented VB runtime function"으로 명시하고 있다. rtc, __vba로 시작하는 함수는 공개가 되지 않은걸로 예상된다. 따라서 앞서 찾은 비교함수, 메시지 출력 함수는 공식문서가 아니라 리버서가 분석한 결과로 보인다.

 

[이미지18] __vbaVarTstEq

비교함수가 실행되는 위치로 이동한다.

 

[이미지19] rtcMsgBox

비공개 함수의 자료를 찾을 수 없는 경우에는 문자열 검색 이후 인접한 코드에서 분기발생 위치를 파악해주면서 찾는방법이 있다. 위로 올리다 보면 마찬가지로 비교함수가 실행되는 위치를 찾을 수 있다.


4-4. __vbaVarTstEq 동작 분석

[이미지20] __vbaVarTstEq

코드를 분석해 보면 EDX, EAX에 스택프레임 내부의 임시버퍼 주소를 담는다. EDX, EAX를 push해서 __vbaVarTstEq의 인자로 사용하는 걸 봐서 두개의 값을 비교하는 것으로 보인다. 이후 AX의 값을 test하는 걸로 보아 비교함수의 반환값으로 시리얼 값이 일치하는지, 그렇지 않은지를 판별하는듯 하다. 확실하게 알기 위해 je로 분기되는 위치를 파악해준다.

 

[이미지21] __vbaVarTstEq 호출 이후 test 순서도

test의 경우 and연산을 통해 ZF플래그를 세팅한다. 결과적으로 AX의 값이 0인지 아닌지를 판별한다.

 

[이미지22] __vbaVarTstEq 호출 이후 test 값이 0이 아닐 경우 동작

test 연산 후 ax의 값이 0이 아닐경우 성공 메시지를 띄운 후 종료된다.

 

[이미지23] __vbaVarTstEq 호출 이후 test 값이 0일 경우 동작

test 연산 후 ax의 값이 0일 경우 실패 메시지를 띄운 후 종료된다.

 

[이미지24] __vbaVarTstEq 부분 순서도

여기까지의 과정을 순서도로 표현해보면 위와같은 방식으로 동작한다.

 

[이미지25] __vbaVarTstEq

따라서 실패, 성공의 분기를 결정하는 __vbaVarTstEq에 사용되는 인자값(EAX, EDX)은 Serial값임을 추측할 수 있다. Name의 길이를 계산하는 루틴은 이전에 이루어진다.

 

[이미지26] abex' crackme 2

EAX, EDX 값을 정확하게 파악하기 위해 Name과 Serial을 입력한 뒤 비교함수가 실행되기 전 EAX, EDX에 무슨 값이 담기는지 확인해준다.

 

[이미지27] __vbaVarTstEq 호출 이전 레지스터

lea를 통해 EAX, EDX에 스택의 주소를 담았기 때문에 해당 스택에서 해당 주소에 해당하는 지점으로 이동한다.

 

[이미지28] __vbaVarTstEq 호출 이전 스택

EAX에는 Serial에 입력한 값, EDX에는 실제 Serial로 추정되는 값이 들어가 있는 것을 확인할 수 있다.


4-5. Visual Basic의 객체 표현 방식

[이미지29] EAX, EDX 주소 덤프

특이한 점은 EAX, EDX에 들어가 있는 값을 확인해 보면 Serial의 ASCII값이 그대로 표현된 것이 아니란 것을 알 수 있다. 그 이유는 VB문자열의 표현방식 때문인데 C++의 string클래스와 마찬가지로 가변 길이 문자열 타입을 사용한다. 

 

[이미지30] Visual Basic 필드

각 필드를 자세히 살펴보면 위와 같이 나뉜다. 타입 필드로 데이터 필드에 오는 자료형을 알 수 있다. 데이터 필드에는 타입 필드에 따라 값이 다르게 해석된다. 예약 필드는 향후 확장, 정렬을 위해 비워둔 필드다. 패딩은 구조체의 경계를 맞추기 위해 사용된다. 예약, 패딩 필드는 자체적으로는 기능을 하지 않는다. 또한 초기화 값에 따라 값이 다르게 나타날 수 있다고 한다. 초기화 패턴이 같으면 같은 값으로 나타난다.

 

[이미지31] Visual Basic 필드 (EAX)

EAX에 있는 주소값에 있는 문자열 객체의 필드는 위와 같이 나타 낼 수 있다(리틀엔디언). 의미를 해석하자면 타입 필드의 값이 0x08이기 때문에 VT_BSTR (가변-길이 유니코드 문자열)을 의미한다. 타입 필드에 의해 데이터 필드는 포인터로 해석된다. 따라서 문자열의 위치는 0x0059F63C에 존재한다. 예약필드 0x19F138, 패딩필드 0x0019F0F0은 초기화 패턴에 의해 결정되므로 큰 의미가 없다.

 

[이미지32] EAX 데이터 필드 주소 덤프

문자열의 위치 0x0059F63C를 확인해 보면 앞서 Serial에 입력한 값이 나타난다. 중간중간에 0x00이 포함되는 이유는 VB 6.0이 쓰는 BSTR은 내부적으로 UTF-16LE(유니코드 16 비트, Little-Endian)으로 저장되기 때문이다. 문자열의 끝은 00 00으로 알 수 있다.

 

[이미지33] EDX 데이터 필드 주소 덤프

EDX도 위 방식과 동일하다.


4-6. 시리얼 생성 케이스 추측

[이미지34] 시리얼 값 생성 케이스

시리얼 값이 어떻게 생성되는지 파악하기 위해 위와 같은 경우를 추려봤다. Name입력값과 EDX의 값을 비교해서 세가지 케이스를 적용한다.

[이미지35] 시리얼 값 생성 케이스

확인 결과 Name값에 따라 시리얼이 생성된다는 것을 알 수 있다. 이후 전체 동작을 자세하게 분석하기 위해 Check버튼 동작 시 가장 처음 부분인 문자열 길이를 파악하는 루틴부터 순서대로 분석했다.


5. 문자열 길이 판별 루틴

[이미지36] 문자열 길이 판별 루틴

위는 Check버튼 동작 시 문자열 길이를 판별하는 코드다. 이후 문자열 길이에 따라 조건부 분기가 이루어진다. __vbaLenVar, __vbaVarTstLt와 같이 비공개 함수의 전체 루틴을 분석하는 것은 매우 어렵기 때문에 함수에 사용되는 인자와 반환값을 위주로 분석을 진행했다.

 

5-1. __vbaLenVar 동작 분석

[이미지37] __vbaLenVar 호출 이전 레지스터

먼저 __vbaLenVar의 동작을 파악하기 위해 사용되는 인자인 EAX와 ECX의 값을 확인했다. 호출되기 전 EAX와 ECX에는 주소값이 나타난다.

 

[이미지38] EAX 주소 덤프

EAX의 주소를 참조해 보면 위와 같은 값을 확인할 수 있다. 앞서 살펴본 필드값을 적용하면 위의 데이터를 다음과 같이 해석할 수 있다. 타입 필드는 0x08로 VT_BSTR을 의미하며 이는 데이터 필드가 문자열 포인터로 해석된다는 것을 의미한다. 따라서 데이터 필드 0x5DDAAC에는 문자열이 위치해있다는 것을 알 수 있다.

 

[이미지39] EAX 데이터 필드 주소 덤프

주소를 참조해 보면 Name에 입력한 값이 나타난다. 따라서 EAX인자는 사용자가 Name에 입력한 값이란 사실을 알 수있다.

 

[이미지40] ECX 주소 덤프

ECX의 값을 참조해 보면 빈 공간임을 알 수 있다. 데이터 필드에 Name이 위치해 있는 주소값이 나타나지만 필드값이 0x00이기 때문에 해석되진 않는다(이전 동작으로 남아있는 것으로 추정). 해당 공간이 인자로 사용되는 것으로 봐서 함수 실행 후 연산결과를 저장할 공간인 것으로 보인다.

 

[이미지41] ECX 주소 덤프

 

__vbaLenVar 함수 실행 이후 해당 영역을 확인해 보면 값이 저장된 것을 확인할 수 있다. 값을 해석해 보자면 타입 필드는 0x03으로 VT_I4이다. 이는 데이터 필드 0x05가 4byte signed int로 해석된다는 것을 의미한다.  Name 입력값으로 "abcde"를 입력했기 때문에 문자열 길이에 해당하는 0x05가 저장된 것을 추측할 수 있다. 이후 Name값 길이를 다르게 입력해서 여러번 확인한 결과 데이터 필드의 값은 문자열 길이에 해당한다는 사실을 확인했다. 따라서 ECX의 값은 EAX에 있는 문자열 길이가 담긴 주소임을 알 수있다. 

 

[이미지42] __vbaLenVar 호출 이후 레지스터

__vbaLenVar의 함수 종료 후 문자열 길이가 있는 주소를 반환한다. 따라서 EAX에는 문자열 길이(0x05)가 있는 주소 (0x0019F0F0)가 담긴다.


5-2. __vbaVarTstLt 동작 분석

[이미지43] __vbaVarTstLt

이후 __vbaVarTstLt 함수 동작을 분석하기 위해 사용되는 인자인 EAX, EDX를 확인한다. 

 

[이미지44] __vbaVarTstLt

EAX는 문자열 길이를 담고있는 주소이고 앞서 확인했기 때문에 생략한다. EDX가 어떻게 사용되어 지는지 알기 위해 EDX에 무슨 값이 담기는지 분석한다. lea를 통해 EDX에는 스택의 ebp - 0xDC의 주소를 담고 있다. lea를 하기 전 동작을 보면 해당 주소에 값을 미리 세팅한다.

 

[이미지45] EDX 주소 덤프

EDX주소를 참조해 보면 위와 같은 값이 나타나는 것을 알 수 있다. 이는 앞서 세팅해준 값 0x8002는 타입 필드, 0x4는 데이터 필드에 들어가 있다. 타입 필드가 0x8002인데 왜 이런 타입을 쓰는지는 알 수 없다(이걸 알려면 __vbaVarTstLt 내부 동작을 분석해야한다.).

 

[이미지46] 0X8002 타입을 사용하는 이유 GPT 출력 결과

GPT의 의견... 팩트는 잘 모르겠다. 0x8002의 의미는 "데이터 필드에는 숫자 2바이트가 아니라 그 숫자가 들어 있는 메모리 주소가 들어 있다"고 한다.

 

[이미지47] EDX 주소 덤프

중요한점은 데이터 필드의 0x04라는 값인데 이 값과 (EDX)

 

[이미지48] EAX 주소 덤프

Name 문자열 길이에 해당하는 값(EAX)이 함수 인자로 사용된다는 점이다. 인자값으로만 봤을 때  __vbaVarTstLt는 이 두 값을 비교하는 함수인 것으로 추측된다.

 

[이미지49] __vbaVarTstLt 호출 이후 레지스터

실행 후 함수 리턴 값을 확인해 보면 0이다. 이 경우는 문자열 길이를 4이상으로 입력했을 때의 결과다.

 

[이미지50] __vbaVarTstLt 호출 이후 레지스터

4보다 작은 길이를 입력하면 0xFFFFFFFF이 반환된다.


5-3. 조건부 분기

[이미지51] __vbaVarTstLt

test이후 je를 통해 리턴값이 0이면(문자열 길이 4이상) 다음 루틴으로 이동하고, 0이 아니면 "Please enter at least 4 chars as name!" 에러 메시지 상자를 띄운다.

 

[이미지52] rtcMsgBox

 "Please enter at least 4 chars as name!" 에러 메시지 상자를 띄워주는 코드는 위와 같다. 단순히 메시지를 띄워주는 부분이기 때문에 상세분석은 하지 않았다.

 

[이미지53] 문자열 길이 판별 루틴

Name에 입력한 문자열이 4이상일 경우 다음루틴으로 위 코드가 실행된다. 한가지 특이한 점은 처음 문자열 길이를 판별하는 루틴이랑 동일하다는 점이다. "mov ebx, 4" 부분만 다른데 전체적인 의미는 달라지지 않는다. 검사하는 위치, 글자수는 처음 루틴과 정확하게 동일하다.

 

[이미지54] 문자열 길이 판별 루틴 순서도

이런식으로 다음 루틴으로 가기 전 한번 더 문자열의 길이를 검사한다.

 

[이미지55] 문자열 길이 판별 루틴 순서도

왜 이런 결과가 나왔는지를 생각해 보자면 아마 소스코드에서 해당 루틴이 실제로 두 번 들어가 있을것으로 추측된다. 빌드 옵션에서 /O2 최적화를 하지 않았기 때문에 코드가 두 번 나타난것 같다.

 

[이미지56] 문자열 길이 판별 루틴 순서도

두번째 검사 루틴은 처음과 동일하기 때문에 문자열 길이 루틴 분석은 여기까지로 하고 그 다음 단계인 반복문 초기화 루틴을 분석한다.


6. 반복문 초기화 루틴

[이미지57] 반복문 초기화 루틴

암호화 루틴으로 들어가기 전, 보이는 코드는 위와 같다. 암호화 루틴에서 반복문을 통해 Name 문자열을 하나씩 가져오기 때문에 반복문이 사용되는데 이때 __vbaVarForInit 함수가 사용된다.


6-1. __vbaVarForInit 동작 분석

[이미지58] 반복문 초기화 루틴

총 6개의 인자가 push되어 함수에 사용된다.

 

[이미지59] 주시 값

위 루틴을 치면서 값이 어떻게 변하는지 알기 위해 각각 인자값을 확인했다. 위는 루틴을 실행하기 전 초기값이다.

 

[이미지60] 주시 값

__vbaVarForInit 함수가 실행되면 값은 0x2로 초기화 된다. 이는 타입 필드에 해당하는 값으로 fix된다.

 

[이미지61] 주시 값

데이터 필드 또한 추가해줬다.

 

[이미지62] __vbaVarForNext

위는 __vbaVarForInit 다음 루틴인 암호화 루틴의 일부 코드다. 암호화 마지막 부분에서 __vbaVarForNext 함수가 있는데 해당 함수로 순차적으로 반복문을 실행한다. 데이터 필드의 값을 알기 위해 루프가 한번 될 때마다 데이터 필드를 확인한다. 암호화 단계에서 한단계씩 확인하지 않는 이유는 암호화 루틴 내부에 비공개 API가 많기 때문이다. 하나하나 실행할 때 마다 데이터값을 비교하기에는 무리가 있다. 따라서 비교적 정확한 값을 알아내기 힘들 수 있다.

 

[이미지63] 암호화 루틴

암호화 전체 루틴은 위와 같은데 비공개 API가 너무 많다. 그래서 마지막 지점인 __vbaVarForNext 지점(루틴 한번 돌때 마다)에서 확인하겠다는 것이다.

 

[이미지64] 주시 값

루프를 실행하면서 변화하는 값을 찾았다. 6개 중 총 2개인데 첫번째 인자의 경우 루프를 한번 실행했을 때 0x64로 고정된다. 6번째 인자는 루프를 한번 돌 때마다 1 증가한다. 나머지는 루프를 돌아도 변화가없다. 이것만으로 인자의 용도를 특정하기 어렵기 때문에 값을 수정해 가면서 파악해줬다.


6-1-1. 인자1 (0x19F0B0)

[이미지65] __vbaVarForInit 호출 이전 인자1의 값

초기값은 0x01이다.

 

[이미지66] 루프 실행 이후 인자1의 값

루프를 한번 실행 하면 0x64로 변경된다. 이후 루프가 끝날 때 까지 값은 고정이다.

 

[이미지67] __vbaVarAdd

암호화 루틴에서 살펴보면 해당 값을 찾을 수 있다. 해당 값은 문자열 Name을 암호화 할 때 사용되어 진다. 값을 특정했으니 해당 값을 수정해본다.

 

[이미지68] __vbaVarAdd

0x32로 수정해 준다.

 

[이미지69] 루프 실행 이후 인자1의 값

수정한 상태로 루프를 한번 돌려주면 0x32로 변경된 점을 확인할 수 있다(다른 값으로 해도 동일하게 적용됨). 해당 근거로 __vbaVarForInit의 첫번째 인자는 "반복문에서 특정 연산을 할 때 사용하는 값"임을 알 수 있다. 왜 이 값을 인자로 사용하는지, 반복문에 왜 필요한지는 모르겠다.


6-1-2. 인자2 (0x19f0a0)

[이미지70] __vbaVarForInit 호출 이전 인자2의 값

루프가 이루어 지는 동안 값을 확인해 보면 0X04로 끝날때 까지 고정이다. 한가지 추측해볼만한 점은 총 4번의 루프로 반복문이 진행된다는 점이다. 따라서 해당 값은 "몇 번 루프할 것인가?"에 관여하는 인자일 가능성이 높다. 중요한건 값이 사용되는 '시점'이다.

 

[이미지71] __vbaVarForInit 호출 이후 인자2의 값

값을 0x10으로 변경한 뒤 루프를 쭉 진행해 봤다. 위의 추측이 맞다면 10번 반복되야 하는데 이후의 루틴에 아무런 영향을 끼치지 않고 실행됐다.

 

[이미지72] abex' crackme 2

메시지 출력까지 잘 나오는 모습이다. 여기서 생각해 봐야 할 점은 값을 수정한 시점이다. 이후 동작에 영향을 끼치지 않은 점으로 보아 초기화 세팅에 관여할 가능성이 크다. 따라서 값의 수정이 __vbaVarForInit 이전인가 아니면 이후인가에 따라서 동작이 달라질 것으로 보인다. 위에서는 __vbaVarForInit 이후 값을 수정했다.

 

[이미지73] __vbaVarForInit

__vbaVarForInit을 호출 하기 전에 값을 수정한다. 조금 위에 보이는 "mov dword ptr ss:[ebp-E4], ebx" 부분에서 0x04가 들어간다. 해당 코드가 실행 되기 전 ebx값을 수정한다. 아니면 인자2(0x19f0a0)를 참조해서 직접 값을 수정해도 된다.

 

[이미지74] Run-time error '5'

0x10을 넣고 실행한 결과 다음과 같은 에러를 출력했다(효과가 있는듯 하다). 기존에는 4번루프였는데 5번루프하고 다음 실행할 때 위의 에러가 발생했다. 에러가 발생하는 이유는 루프를 진행하는 동안 안정성 검사에서 문제가 발생하지 않았나 싶다. 관련된 자료를 찾아보니 VB에서 해당 오류는 잘못된 프로시저 호출 또는 인자에 관한 에러를 의미한다(https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/invalid-procedure-call-or-argument-error-5).

 

[이미지75] __vbaVarForInit 호출 이전 인자2의 값

다른 방법으로 기존 0X04보다 작은 값으로 수정해봤다 (큰 값을 넣으면 오류 발생).

 

[이미지76] __vbaVarForInit 호출 이전 인자2의 데이터 필드의 값을 0x03로 수정한 뒤 실행한 결과

정확히 3번 루프되고 메시지가 출력된다. 에러 메시지도 출력되지 않는다. __vbaVarForInit 이전에 값을 수정했을 때 해당 값만큼 루프가 이루어 지는점을 근거로 두번째 인자는 "초기화 단계에서의 반복횟수 값"이다. 좀 더 정확하게 말하자면 "초기화 단계의 조건식에서 루프가 언제 끝날지를 결정하는 종료값"이라고 할 수 있다.


6-1-3. 인자3 (0x19f090)

[이미지77] __vbaVarForInit 호출 이전 인자3의 값

초기값은 0x01이다. 이후 루프를 끝까지 실행해도 값은 변하지 않는다. 앞서 조건식에 해당하는 부분이 나왔기 때문에 해당 값은 증감식 또는 변수 초기화일 가능성을 고려해 봐야한다.

 

[이미지78] __vbaVarForInit 호출 이후 인자3의 값

정확하게 파악하기 위해서 해당 값을 0x03으로 변경하고 실행한 결과 아무런 변화 없이 동작했다.

 

[이미지79] __vbaVarForInit 호출 이전 인자3의 데이터 필드의 값을 0x03로 수정한 뒤 실행한 결과

 __vbaVarForInit 호출 이전에도 값을 수정해 본다. 동일하게 0x03으로 수정한 결과 이번에는 2번 루프 진행 후 루프를 빠져나왔다.  __vbaVarForInit 호출 이전 값을 수정했을 때 변화가나타났기 때문에 해당 값은 초기화에 사용되는 함수임을 알 수 있다. 2번 루프가 진행된 점만 봐서는 step(증감식)인지 i(초기값)인지 구분할 수 없다.

 

[이미지80] __vbaVarForInit 호출 이전 인자3의 값

값을 0x03이 아닌 0x00으로 수정 시 이를 구분할 수 있다. step일 경우에는 무한루프, 초기값일 경우에는 1이 감소했으니까 5번 실행될 것이다. 이후 실행되는 루프를 확인한다.

 

[이미지81] Run-time error '5'

초기값 관련 부분은 값을 수정 시 해당 에러가 출력된다.

 

[이미지82] 주시 값

따라서 0x00으로 수정하는 방식은 사용할 수 없다. 그래서 값을 수정하고 루프를 진행하면서 무슨 값에 영향을 주는지 확인해 봤다. 그 결과 loop counter에 영향을 준다는 사실을 알 수 있었다. 0x03으로 세팅 시 loop counter의 초기값이 0x03으로 세팅됐다. 따라서 해당 값은 step(증감식)에 해당하는 값이 아닌 i(초기화)에 해당하는 인자인 것을 알 수 있다. 정확하게 표현하자면 "인덱스 변수의 초기화 값"이라고 할 수 있다.


6-1-4. 인자4 (0x19f048)

[이미지83] __vbaVarForInit 호출 이후 인자4의 값

초기값은 0x04이고 루프가 끝날 때 까지 변화하지 않는다. 앞에서 살펴본 인자2와 같은 값인걸 봐서 유사한 동작을 할 것이라 예상된다. 따라서 인자2와 비교하면서 분석을 진행했다. 데이터 필드의 값은 0x63DB04인데 앞에있는 0x63DB는 쓰레기 값으로 보인다(확실하진 않음).

 

[이미지84] __vbaVarForInit 호출 이전 인자2의 값

인자2의 경우 __vbaVarForInit을 호출하기 전에 값이 세팅되어 있다.

 

[이미지85] __vbaVarForInit 호출 이전 인자4의 값

반면 인자4의 경우 __vbaVarForInit의 호출 이전에 쓰레기 값이 들어가 있는 것을 볼 수 있다.

 

[이미지86] __vbaVarForInit 호출 이후 인자4의 값

__vbaVarForInit 호출 이후 값이 세팅된다. 따라서 인자4의 경우 초기화에 관여를 하지 않는다는 점을 알 수 있다. 한 가지 확인해봐야 할 점은 두 인자의 값이 우연히 일치했을 가능성도 있다는 것이다. 이를 확인하기 위해 두 인자가 연관되어 있다고 생각하고 동작을 분석한다. 만약 한 인자가 다른 인자에 영향을 준다고 가정하면 인자2의 값을 변경할 때 인자4의 값 역시 동일하게 바뀔 가능성이 크다. 인자2의 경우 초기화에 관여하는 인자이기 때문에 인자4가 인자2의 영향을 일방향으로 받을것이다.

 

[이미지87] __vbaVarForInit 호출 이전 인자2의 값

__vbaVarForInit 호출 이전 인자2의 데이터 필드의 값을 0x02로 수정한 뒤 값을 확인한다. 위의 가정이 맞다면 __vbaVarForInit 호출 이후 인자4의 값도 0x02로 변경될 것이다.

 

[이미지88] __vbaVarForInit 호출 이후 인자4의 값

인자4 역시 동일하게 변경되는 것을 확인했다. 따라서 인자4는 "초기화 단계의 조건식에서 루프가 언제 끝날지를 결정하는 종료값" 과 연관이 있다. 인자2와의 차이를 생각해 보면 인자가 사용되는 시점이다. 이러한 차이를 고려해 생각해 보면 인자2는 초기화를 할 때, 인자4는 초기화 작업 이후 루프에서 사용된다는 차이가 있을 것이다. 이게 맞는지 확인하기 위해 초기화 이후 루프를 진행할 때 인자4의 데이터 필드값을 수정해본다. 앞서 살펴본대로 인자2의 경우 초기화에 사용되는 인자이기 때문에 루프가 돌때에는 값을 수정해도 동작에 변화가 없다([이미지71], [이미지72] 참고).

 

[이미지89] __vbaVarForInit 호출 이후 인자4의 값

__vbaVarForInit 호출 이후 인자4의 값을 0x10으로 수정해 줬다. 위의 가정이 맞다면 10번 반복될 것이다.

 

[이미지90] Run-time error '5'

에러가 출력된다(이유는 알 수 없음). 인자2와 마찬가지로 기존 값보다 작은 값으로 수정해준다.

 

[이미지91] __vbaVarForInit 호출 이후 인자4의 데이터 필드의 값을 0x02로 수정한 뒤 실행한 결과

정확하게 루프 두 번 동작 후 메시지를 출력한다. __vbaVarForInit 함수 호출 이후 값을 수정한 결과 그 값이 그대로 적용된다는 점으로 봤을때 인자 4의 경우 "루프단계에서 반복횟수의 값"이다. 좀 더 정확하게 말하자면 "반복문 실행 단계의 조건식에서 루프가 언제 끝날지를 결정하는 종료값"이라고 할 수 있다.


6-1-5. 인자5 (0x19f058)

[이미지92] __vbaVarForInit 호출 이후 인자5의 값

초기값은 0x01이고 루프가 끝날 때 까지 변화하지 않는다. 앞에서 살펴본 인자3과 같은 값 인걸 봐서 유사한 동작을 할 것이라 예상된다. 따라서 인자3과 비교하면서 분석을 진행했다. 인자4를 분석했을 때와 같은 방식을 적용했다. 따라서 해당 인자는 "인덱스 변수의 초기화 값"이라고 예상해 볼 수 있다. 우선 초기화에 관여하는지를 확인해봤다.

 

[이미지93] __vbaVarForInit 호출 이전 인자5의 값

__vbaVarForInit 함수 호출 이전 값을 살펴보면 쓰레기 값이 들어가 있다.

 

[이미지94] __vbaVarForInit 호출 이후 인자5의 값

__vbaVarForInit 함수 호출 이후 값이 변경된다는 점으로 봐서 초기화에 관여하는 함수는 아니다.

 

[이미지95] __vbaVarForInit 호출 이전 인자3의 값

인자3의 값에 영향을 받는지 확인하기 위해 __vbaVarForInit 함수 호출 전 인자3의 데이터 필드를 0x03으로 바꾸고 변화를 관찰했다.

 

[이미지96] __vbaVarForInit 호출 이후 인자5의 값

인자4의 케이스와는 다르게 __vbaVarForInit 함수 호출 후 인자5의 데이터 필드가 변하지 않았다. 이는 인자5의 경우 인자3과 관련이 없는 값으로 그저 우연히 값이 일치했다는 것을 의미한다. 따라서 인자5의 경우는 "인덱스 변수의 초기화 값"과 관련이 없고 초기화 관련 값이 아니라는 사실을 알 수 있다. 해당 결과를 토대로 값을 변경해주면서 인자를 분석해봤다.

 

[이미지97] __vbaVarForInit 호출 이후 인자5의 값

__vbaVarForInit 함수 호출 이후 인자 값을 0x02로 수정한 뒤 동작을 살펴봤다.

 

[이미지98] __vbaVarForInit 호출 이후 인자5의 데이터 필드의 값을 0x02로 수정한 뒤 실행한 결과

루프가 두번 진행되고 메시지를 출력한다. 인덱스와 초기화에 관여하지 않는다라는 점을 봤을 때 해당 인자값을 step과 연관이 있을거란 가능성이 높다. 이 점을 고려하여 0x00으로 값을 수정한 뒤 동작을 관찰한다. 만약 step이 맞을 시 증감식에 더해주는 값이 0이되어 무한루프가 발생할 것이다.

 

[이미지99] __vbaVarForInit 호출 이후 인자5의 데이터 필드의 값을 0x00로 수정한 뒤 실행한 결과

무한루프가 발생한다. 따라서 인자5는 "step의 값"이다. 좀 더 정확히 표현하자면 "반복문 실행 단계에서 증감식에서 사용하는 값"이라고 할 수 있다.

 

[이미지100] 주시 값

확인을 위해 값의 변화를 살펴봤다. 인자5의 데이터 필드를 0x07로 수정했다. 위 이미지에서 표시한 부분은 loop counter에 해당하는 부분이다. 그리고 루프를 진행하기 전 값이다. step을 0x07로 수정해줬기 때문에 0x07씩 값을 더할것이다.

 

[이미지101] 주시 값

루프를 한번 진행한 뒤 값을 살펴보면 정확이 0x07만큼 값이 증가한다는 사실을 알 수 있다.


6-1-6. 인자6 (0x19f168)

 

[이미지102] __vbaVarForInit 호출 이후 인자6의 값

인자6의 경우 루프를 한번 돌때마다 데이터 필드의 값이 0x01만큼 증가한다. 따라서 해당 값을 loop counter라고 예상할 수 있다. 인자1을 제외하고 루프에서 유일하게 변화하는 값이기 때문에 비교적 쉽게 의미를 파악할 수 있었다.

 

[이미지103] 수정없이 루프를 3번 실행한 결과

loop counter가 맞는지 확인해주기 위해서 루프를 3번 진행한 뒤 인자6의 값을 0x01로 변경해 봤다. loop counter가 맞다면 4번째에서 종료되지 않고 3번 더 반복할 것이다.

 

[이미지104] 수정후 루프를 끝까지 진행한 결과

예상에 맞게 동작했다. 따라서 인자6의 경우 "loop counter"에 해당한다. 좀 더 정확하게 표현하자면 "몇 번째 반복을 수행하고 있는지를 세기 위해 사용하는 값"이라고 할 수 있다.


6-2. __vbaVarForInit 함수 인자

Dim result As integer
Dim x As Integer

Dim a As Integer
Dim init_b As Integer
Dim b As Integer
Dim c As Integer

Dim i As Integer

a = 0x1
init_b = 0x4
b = init_b
c = 0x1

result = 0x0
x = 0x64

For i = a To b Step c
    result = result + x
Next i

매칭 변수 참고용 Visual Basic 코드

인자 의미 매칭 변수 초기값 초기값 세팅 시점 초기화 관여
1 (0x19F0B0) 반복문에서 특정 연산을 할 때 사용하는 값 x 0X00000001 __vbaVarForInit 이전 X
2 (0x19F0A0) 초기화 단계의 조건식에서 루프가 언제 끝날지를 결정하는 종료값 init_b 0X00000004 __vbaVarForInit 이전 O (인자4)
3 (0x19F090) 인덱스 변수의 초기화 값 a 0X00000001 __vbaVarForInit이전 O (인자6)
4 (0x19F048) 반복문 실행 단계의 조건식에서 루프가 언제 끝날지를 결정하는 종료값 b 0X63DB0004 __vbaVarForInit X
5 (0x19F058) 반복문 실행 단계에서 증감식에서 사용하는 값 c 0X63DB0001 __vbaVarForInit X
6 (0x19F068) 몇 번째 반복을 수행하고 있는지를 세기 위해 사용하는 값 i 0X63DB0001 __vbaVarForInit X

 

인자 : 주소는 실행환경에 따라 달라질 수 있다(인자를 좀 더 명확하게 표현하기 위해 표시).

의미 : 인자의 용도

매칭 변수 : 반복문 에서 의미를 대략적으로 파악하기 위한 용도로 위의 VB코드는 실제 동작과 다를 수 있다.

초기값 : 반복문을 실행하기 전 __vbaVarForInit 호출 후 나타나는 값이다(abex' crackme 2에서의 값).

초기값 세팅 시점 : 초기값이 어느 시점에서 세팅이 되는지에 대한 내용이다.

초기화 관여 : __vbaVarForInit 내부 동작에서 다른 인자의 초기화 여부에 관여를 하는지에 대한 내용이다.

 

추가적으로 인자2, 인자3의 경우 __vbaVarForInit 호출 이전에 값을 수정해야 동작에 영향을 준다. 인자1을 제외하고 인자4, 인자5, 인자6의 경우 __vbaVarForInit 호출 이후 값을 수정해야 동작에 영향을 준다.


7. 암호화 루틴

[이미지105] 암호화 루틴

반복문 초기화 루틴 이후 암호화 루틴은 위와 같다. 비공개 API가 많기 때문에 암호화 루틴 또한 인자, 반환값 위주로 분석한다.

 

[이미지106] __vbaVarForInit 이후 동작

__vbaVarForInit 호출 이후 반환값(EAX)는 0x01이다. test로 EAX값이 0x0인지 판별 후 조건부 분기가 이루어 진다.

 

[이미지107] __vbaVarForInit 이후 동작 순서

1번으로 __vbaVarForInit 호출 이후 반환값(EAX)이 0x01이기 때문에 조건부 분기가 이루어지지 않는다. 따라서 2번으로 EIP가 이동한다. 3번의 과정은 암호화 과정으로 사용자가 입력한 Name의 문자열를 하나씩 암호화 하는 루틴이다. 문자 하나를 암호화 했으면 __vbaVarForNext가 호출된다. 해당 함수를 통해서 앞서 살펴본 인자6(loop counter)이 1증가한다. 또한 반환값은(EAX) 인자6(loop counter)과 인자4(종료값)를 비교해서 결정된다. 앞으로 진행 될 루프가 남았으면 반환값은 0x01로 4번으로 이동 후 다시 암호화 루틴을 진행한다. 루프가 모두 진행됐으면 반환값은 0x00으로 더이상 2번의 과정이 진행되지 않고(조건부 분기에 의해) 5번으로 진행된다. 전체적인 동작 방식을 파악했으니 3번의 과정을 하나씩 분석한다.

 

7-1. __vbaI4Var

 

7-1-1. 코드

[이미지108] __vbaI4Var


7-1-2. 인자 (EAX, EDX)

[이미지108] __vbaI4Var 호출 전 레지스터

함수 호출 전 EAX의 값을 확인해 보면 0x19F168이다.

 

[이미지109] EAX(0x19F168) 참조 덤프

값과 주소를 확인해 보면 인자6(loop counter) 인 것을 확인할 수 있다.

 

[이미지110] __vbaI4Var 호출 전 레지스터

함수 호출 전 EDX의 값을 확인해 보면 0x19F0F0이다.

[이미지111] EDX(0x19F0F0) 참조 덤프

타입 필드는 0x02로 VT_I2다. 따라서 데이터 필드의 0x01은 2바이트 정수로 해석된다.

 

이미지[112] EDX 값 세팅 코드

EDX의 경우 위 코드로 인해 값이 세팅된다. EDI는 (0x02)


7-1-3. 동작 (return 인자6)

[이미지113] __vbaI4Var 호출 후 레지스터

함수 호출 후 인자6(loop counter)이 반환된다.


7-1-4. 결과

__vbaI4Var(arg1, arg2); // arg1==EDX, arg2==EAX

arg2의 4byte 주소값을 읽어서 데이터 필드 값을 반환한다. arg1의 경우 변환 과정에서 오류가 발생했을 때 런타임이 예외를 발생시킬지 여부를 담는 플래그다. 0으로 변경 시 런타임 에러메시지를 출력한다.


7-2. rtcMidCharVar

 

7-2-1. 코드

[이미지113] rtcMidCharVar


7-2-2. 인자 (EAX, ECX, EDX)

[이미지114] rtcMidCharVar 호출 전 레지스터

EAX는 인자6(loop counter)이다(__vbaI4Var의 반환값).

 

[이미지115] ECX(0x19F118) 참조 덤프

ECX의 주소를 참조해 보면 문자열 타입이 나타난다.

 

[이미지116] 데이터 필드(0x5DC864) 참조 덤프

데이터 필드의 주소를 참조해 보면 Name의 사용자 입력값이 나타난다.

 

[이미지117] EDX(0x19F0E0) 참조 덤프

EDX의 주소를 참조해 보면 빈 공간이 나타난다.


7-2-3. 동작 (return EDX)

[이미지117] 함수 호출 이후 EDX(0x19F0E0) 참조 덤프

함수 호출 이후 EDX의 주소를 참조해 보면 문자열 타입의 데이터가 나타난다.

 

[이미지118] 데이터 필드(0x5DC89C) 참조 덤프

데이터 필드의 주소값을 참조해 보면 Name에서 제일 앞의 한글자가 문자로 나타난다.

 

[이미지119] 데이터 필드(0x5F5A14) 참조 덤프

쭉 실행 한 뒤 함수를 다시 호출해 보면 그 다음 문자가 들어가 있는 것을 확인할 수 있다. EDX(0x19F0E0)의 값은 동일하지만 내부 문자열 데이터 필드의 주소는 호출할 때 마다 변경된다(0x5DC89C → 0x5F5A14).

[이미지114] rtcMidCharVar 호출 후 레지스터

함수 호출 후 EDX(0x19F0E0)의 값이 반환된다.


7-2-4. 결과

rtcMidCharVar(arg1, arg2, arg3); // arg1==EAX, arg2==ECX, arg3==EDX

arg2 문자열의 arg1번째 인덱스에 해당하는 문자 하나를 arg3에 담는다. 반환값은 arg3의 주소다.


7-3. __vbaVarMove

 

7-3-1. 코드

[이미지115] __vbaVarMove


7-3-2. 인자(ECX, EDX)

[이미지116] ECX(0x19F138) 참조 덤프

ECX의 주소값을 참조해보면 빈공간이 나타난다.

 

[이미지117] EDX(0x19F0E0) 참조 덤프

EDX 주소값을 참조해보면 문자열이나타난다. 해당 주소와 값은 rtcMidCharVar의 호출 결과이다.

 

[이미지118] 데이터 필드 참조(0x6908D4)

앞에서 살펴본 대로 Name의 사용자 입력값중 한 글자를 담고있다.


7-3-3. 동작 (return ECX)

[이미지119] ECX(0x19F118) 참조 덤프

함수 호출 이후 ECX 주소 덤프를 참조해 값을 살펴보면 문자열에 해당하는 객체가 생성된다.

 

[이미지120] EDX(0x19F0E0) 참조 덤프

해당 값은 EDX의 참조 값과 정확히 일치한다.

 

[이미지114] vbaVarMove 호출 후 레지스터

ECX가 반환된다.


7-3-4. 결과

vbaVarMove(arg1, arg2); // arg1==EDX, arg2==ECX

arg1의 값을 arg2로 이동


7-4. __vbaFreeVar

 

7-4-1. 코드

[이미지115] __vbaFreeVar


7-4-2. 인자 (ECX)

[이미지116] ECX(0x19F0F0) 참조 덤프

ECX의 값을 참조해 보면 2바이트 정수형 0x1 값을 확인할 수 있다. 해당 값은 __vbaI4Var에서 사용한 arg1이다(런타임 에러판별 플래그).


7-4-3. 동작 (return none)

[이미지117] ECX(0x19F0F0) 참조 덤프

함수 호출 이후 값을 살펴보면 타입 필드의 값이 0x0으로 세팅된다.


7-4-4. 결과

__vbaFreeVar(arg1); // arg1==ECX

arg1의 자원을 해제한다(타입 필드를 0x0으로 변경).


7-5. __vbaStrVarVal

 

7-5-1. 코드

[이미지118] __vbaStrVarVal


7-5-2. 인자 (EAX, ECX)

[이미지119] EAX(0x19F138) 참조 덤프

vbaVarMove에서 반환된 값이 사용된다.

 

이미지[120] 데이터 필드(0x58C874) 참조 덤프

앞서 살펴본 대로 'a'가 들어있다.

 

[이미지121] ECX(0x19F104) 참조 덤프

빈공간을 확인할 수 있다.


7-5-3. 동작 (return pointer)

[이미지122] __vbaStrVarVal 호출 후 레지스터

함수를 호출 하면 기존 EAX의 데이터 필드가 리턴된다. 이는 Name에 있는 사용자 입력값을 한글자 옮겼던 주소에 해당한다.

[이미지123] ECX(0x19F104) 참조 덤프

ECX의 경우 함수호출, 내부, 호출 이후에도 변화가 없다.

 

[이미지124] __vbaStrVarVal 내부 동작

__vbaStrVarVal의 내부코드를 살펴보면 EAX(0x19F138)의 타입필드를 확인하는 코드가 나온다. 즉 EAX의 객체가 문자열 타입(0x08)인지를 확인한다.

 

[이미지125] EAX(0x19F138) 참조 덤프

EAX의 타입을 0x02로 변경 후 변화를 살펴본다.

 

[이미지126] ECX(0x19F104) 참조 덤프

__vbaStrVarVal의 내부코드에서 조건부 분기가 이루어진다. 그 결과 ECX(0x19F104)에 데이터가 쓰여진다.

 

[이미지127] 0x503694 참조 덤프

해당값을 참조해 보면 문자열이 나타난다. 해당값이 무슨 의미인지(호출할 때 마다 변함), 그리고 ECX의 데이터 타입이 문자열이 아닌데 왜 저 값이 문자열로 해석되는지는 알지 못했다. 이후 저 값이 암호화 루틴에 적용된다.


7-5-4. 결과

__vbaStrVarVal(arg1, arg2); // arg1==EAX, arg2==ECX

arg1의 문자열 주소를(데이터 필드)를 반환한다. arg1이 문자열이 아닐경우 주소를 arg2의 값으로 대체한다.


7-6. rtcAnsiValueBstr


7-6-1. 코드

[이미지128] rtcAnsiValueBstr


7-6-2. 인자 (EAX)

[이미지129] rtcAnsiValueBstr 호출 전 레지스터

EAX는 직전에 호출된 __vbaStrVarVal의 반환값으로 문자열 주소에 해당된다. 주소값은 함수 호출할 때 마다 바뀔 수 있다(다른 이유는 프로그램 종료 후 재시작했기 때문).

[이미지130] EAX(0x55F55C) 참조 덤프

앞서 살펴본 대로 문자 하나가 들어가 있다.


7-6-3. 동작 (return character)

[이미지131] rtcAnsiValueBstr 호출 후 레지스터

문자열 주소에 있는 데이터를 반환한다.


7-6-4. 결과

rtcAnsiValueBstr(arg1); // arg1==EAX

arg1을 참조해 값을 반환한다.


 

[이미지132] __vbaVarMove

흐름상 위 함수 호출 다음 위와 같은 코드가 실행된다. 앞서 살펴본 함수이기 때문에 분석은 생략한다.

 

[이미지133] ECX(0x19F138) 참조 덤프

동작만 간단하게 살펴보면 rtcAnsiValueBstr 호출 결과를 ECX(0x19F138)로 이동한다. 함수 호출 반환값은 이동을 마친 주소(0x19F138)다.


7-7. __vbaFreeStr


7-7-1. 코드

[이미지134] __vbaFreeStr


7-7-2. 인자 (ECX)

[이미지135] ECX(0x19F104) 참조 덤프

ECX는 __vbaStrVarVal의 arg2(에러처리)에 해당한다.


7-7-3. 동작 (return 0x0)

[이미지136] ECX(0x19F104) 참조 덤프

__vbaStrVarVal에 에러가 발생하지 않았을 경우 값의 변화가 없다.

 

[이미지137] ECX(0x19F104) 참조 덤프

__vbaStrVarVal에 에러 발생 시 위와같이 ECX가 세팅된다.

 

[이미지138] ECX(0x19F104) 참조 덤프

함수 호출 후 해당 값이 0으로 초기화된다.

[이미지139] __vbaFreeStr 호출 후 레지스터

0x0을 반환한다.


7-7-4. 결과

__vbaFreeStr(arg1); // arg1==ECX

arg1의 자원을 해제한다(타입 필드를 0x0으로 변경).


7-8. __vbaVarAdd


7-8-1. 코드

[이미지140] __vbaVarAdd


7-8-2. 인자 (EAX, ECX, EDX)

[이미지141] EDX(0x19F138) 참조 덤프

EDX의 경우 0x61이 있다. Name 값에서 한글자 가져온 값이다.

[이미지142] EAX(0x19F0B0) 참조 덤프

EAX의 경우 0x64가 있다. 위에서 mov해준 값이며 암호화 키에 해당한다.

이미지[143] ECX(0x19F0F0) 참조 덤프

ECX의 경우 빈공간이 나타난다.


7-8-3. 동작 (return ECX)

이미지[144] ECX(0x19F0F0) 참조 덤프하

함수 호출 후 ECX(0x19F0F0)에 값이 저장된다. 해당 값은 0x61(EDX) + 0x64(EAX)의 결과다.

[이미지145] __vbaVarAdd 호출 후 레지스터

ECX의 주소가 반환된다.


7-8-4. 결과

__vbaVarAdd(arg1, arg2, arg3); // arg1==EDX, arg2==EAX, arg3==ECX

arg3에 arg1과 arg2의 덧셈 결과를 저장한다. (arg3 = arg1 + arg2) 


[이미지146] __vbaVarMove

다음 순서에 동작하는 코드인데 마찬가지로 위에서 분석했기 때문에 생략한다.

 

[이미지147] ECX(0x19F138) 참조 덤프

동작만 간단하게 살펴보면 __vbaVarAdd 호출 결과를 ECX(0x19F138)로 이동한다. 함수 호출 반환값은 이동을 마친 주소(0x19F138)다.


7-9. rtcHexVarFromVar


7-9-1. 코드

[이미지148] rtcHexVarFromVar


7-9-2. 인자 (EAX, EDX)

[이미지149] EAX(0x19F0F0) 참조 덤프

EAX를 참조해 보면 빈 공간이 나타난다. 이전의 __vbaVarAdd 연산결과 0xC5가 남아있는 듯 보이지만 타입 필드의 값이 0이므로 데이터가 해석되진 않는다.

 

[이미지150] EDX(0x19F138) 참조 덤프

EDX를 참조해 보면 이전의 __vbaVarAdd 연산결과 0xC5가 나타난다( 이전의 __vbaVarMove 동작 결과).


7-9-3. 동작 (return EAX)

[이미지151] EAX(0x19F0F0) 참조 덤프

호출 후 EAX값을 참조해 보면 문자열 객체가 나타난다.

 

[이미지152] 데이터 필드(0x6FC744) 참조 결과

데이터 필드를 참조해 보면 0xC5(__vbaVarAdd 연산결과)가 나타난다.

 

[이미지153] rtcHexVarFromVar 호출 후 레지스터

EAX를 반환한다.


7-9-4. 결과

rtcHexVarFromVar(arg1, arg2); // arg1==EDX, arg2==EAX

arg1을 arg2로 이동한다. 이때 arg1(hex)는 문자열로 표현된다.


[이미지154] __vbaVarMove

위에서 분석했기 때문에 생략한다.

[이미지155] ECX(0x19F138) 참조 덤프

동작을 살펴보면 rtcHexVarFromVar 호출 결과를 ECX(0x19F138)로 이동한다. 함수 호출 반환값은 이동을 마친 주소(0x19F138)다.


7-10. __vbaVarCat


7-10-1. 코드

[이미지156] __vbaVarCat


7-10-2. 인자 (EAX, ECX, EDX)

[이미지157] EAX(0x19F0F0) 참조 덤프

EAX 참조 결과 빈 공간이 나타난다.

 

[이미지158] ECX(0x19F148) 참조 덤프

ECX 참조 결과 빈 공간이 나타난다.

 

[이미지159] EDX(0x19F138) 참조 덤프

EDX 참조 결과 문자열이 나타난다.

 

[이미지160] 데이터 필드(0x6C08B4) 참조 덤프

참조 해보면 rtcHexVarFromVar 호출 결과가 나타난다.


7-10-3. 동작 (return EAX)

[이미지161] __vbaVarCat 동작 (loop 0)

전체 동작을 한눈에 보기 위해 루프를 한단계 실행할 때 마다 인자의 변화를 살펴본다. 위는 함수를 호출 하기 전 초기 상태다(Name 입력값은 "abcd").

 

[이미지162] __vbaVarCat 동작 (loop 1)

__vbaVarCat을 한 번 호출했을 때 값을 살펴보면 EAX에 "C5"의 값이 들어간 것을 확인할 수 있다. ECX와 EDX에는 변화가 없다.

 

[이미지163] __vbaVarCat 동작 (loop 2)

__vbaVarCat을 두 번 호출했을 때 값을 살펴보면 EAX에 "C5C6"의 값이 들어간 것을 확인할 수 있다. ECX에는 이전의 암호화 문자열이 들어가 있다. EDX는 최근 암호화 문자가 나타난다.

 

[이미지164] __vbaVarCat 동작 (loop 3)

__vbaVarCat을 세 번 호출했을 때 값을 살펴보면 EAX에 "C5C6C7"의 값이 들어간 것을 확인할 수 있다. ECX에는 이전의 암호화 문자열이 들어가 있다. EDX는 최근 암호화 문자가 나타난다.

[이미지165] __vbaVarCat 동작 (loop 4)

__vbaVarCat을 세 번 호출했을 때 값을 살펴보면 EAX에 "C5C6C7C8"의 값이 들어간 것을 확인할 수 있다. ECX에는 이전의 암호화 문자열이 들어가 있다. EDX는 최근 암호화 문자가 나타난다.

 

[이미지166] __vbaVarCat 호출 후 레지스터

EAX를 반환한다.

 

7-10-4. 결과

__vbaVarCat(arg1, arg2, arg3); // arg1==ECX, arg2==EDX, agr3==EAX

arg1의 문자열과 arg2의 문자열을 합친 결과를 arg3에 저장한다.


 

[이미지167] __vbaVarMove

위에서 분석했기 때문에 생략한다.

[이미지168] ECX(0x19F138) 참조 덤프

동작을 살펴보면 __vbaVarCat 호출 결과를 ECX(0x19F138)로 이동한다. 함수 호출 반환값은 이동을 마친 주소(0x19F138)다.


7-11. __vbaVarForNext


7-11-1. 코드

[이미지169] __vbaVarForNext


7-11-2. 인자 (EAX, ECX, EDX)

[이미지170] EAX(0x19F168) 참조 덤프

EAX의 값을 참조해 보면 0x01이 나타난다. 해당 값은 인자6(loop counter)에 해당한다.

 

[이미지171] EDX(0x19F058) 참조 덤프

EDX의 값을 참조해 보면 0x01이 나타난다. 해당 값은 인자5(step)에 해당한다.

 

[이미지172] ECX(0x19F048) 참조 덤프

EDX의 값을 참조해 보면 0x01이 나타난다. 해당 값은 인자4(loop end value)에 해당한다.


7-11-3. 동작 (return)

[이미지173] 주시 값

호출 전 주시 값은 위와 같다.

 

[이미지174] 주시 값

함수 호출 후 인자6(loop counter)의 값이 0x01만큼 증가한다.

[이미지175] __vbaVarForNext 호출 후 레지스터

함수 호출 후 반환값은 0x01이다.

[이미지176] 주시 값

인자6(loop counter)의 값 증가는 인자4(loop end value)의 값보다 커질 때 까지 진행된다.

[이미지177] __vbaVarForNext 호출 후 레지스터

인자6(loop counter)의 값이 인자4(loop end value)의 값보다 커졌을 때 반환값은 0x00이 된다.


7-11-4. 결과

__vbaVarForNext(arg1, arg2, arg3); // arg1==ECX, arg2==EDX, arg3==EAX

arg3(loop counter)과 arg2(step)를 더한다. 해당 값이 arg1(loop end value) 이하 일 경우 0x01을 반환하고, 초과 일 경우 0x00을 반환한다.


7-12. 암호화 루틴 전체동작

[이미지178] 암호화 루틴 전체동작


8. 오브젝트 참조 루틴

[이미지179] 암호화 루틴

암호화 루틴 동작 이후 실행되는 코드는 위와 같다.

 

8-1. ds:[ecx+304], __vbaObjSet 동작 분석

[이미지180] __vbaObjSet

__vbaObjSet까지의 동작을 살펴봤으나 정확한 분석을 할 수 없었다. 프로그램 내의 특정 오브젝트 위치를 파악해서 메모리에 쓰는 과정인 것으로 추측된다.

 

[이미지181] ecx+304 호출 후 레지스터

ds:[ecx+304] 호출 후 레지스터의 값은 위와 같다.

 

[이미지182] EAX(0x02B32CDC) 참조 덤프

반환값을 참조해 보면 위와 같은 데이터를 볼 수 있는데 무엇을 나타내는 값인지는 알 수 없었다.

 

[이미지183] 0x02D23410 참조 덤프

처음 나타나는 주소를 참조해 보면 오브젝트를 나타내는 값들을 볼 수 있는데 시작 지점으로 올려보면 VB.TextBox라는 텍스트를 확인할 수 있다. 따라서 해당 값들은 TextBox 오브젝트로 추측된다.

 

[이미지184] abex' crackme 2

프로그램에서 해당 TextBox의 위치를 알아내는 함수로 추측된다.

 

[이미지185] EDX(0x19F100) 참조 덤프

이후 __vbaObjSet를 호출하면 EDX에 값이 쓰여진다. 해당 값은 ds:[ecx+304]의 반환값(TextBox의 오브젝트 위치로 추측)이다.


8-2. ds:[eax+A0] 동작 분석

[이미지186] ds:[eax+A0]

이후 다음의 코드가 실행된다. 인자를 살펴보면 위에서 반환된 값(TextBox의 오브젝트 위치)와 ECX(빈공간)이 사용된다.

 

[이미지187] ECX(0x19F104) 참조 덤프

 

호출 이후 동작을 보면 빈공간이었던 ECX(0x19F104)에 값이 저장된다.

 

[이미지188] 0x6E4F54 참조 덤프

해당 값을 참조해 보면 Serial에 입력한 값인 "1234"가 나타난다. 따라서 위 전체 코드의 동작은 프로그램의 Serial TextBox 오브젝트의 위치를 파악해서 해당 위치의 문자열을 읽어서 메모리에 쓰는 루틴으로 추측된다.


8-3. 오브젝트 참조 이후 동작 분석 (예외처리, 조건부 분기, 자원 해제, 주소 이동)

[이미지189] __vbaHresultCheckobj

여기까지의 동작 과정에서 예외 발생 시 __vbaHresultCheckobj 가 호출되면서 예외 처리 루틴이 실행된다. 예외가 발생하지 않았으면 jge 조건부 분기를 통해 다음 루틴이 실행된다.

 

[이미지190] __vbaVarMove

예외가 발생하지 않았을 경우 __vbaVarMove가 호출된다.

 

[이미지191] ECX(0x19F158) 참조 덤프

호출 시 ECX(0x19F158)에 값이 이동한다.

 

[이미지192] 데이터 필드(0x78472C) 참조 덤프

데이터 필드를 참조해 보면 Serial 입력값이 나타난다.

 

[이미지193] __vbaFreeObj

__vbaVarMove 호출 이후 __vbaFreeObj가 호출된다.

[이미지193] ECX(0x19F100) 참조 덤프

ECX는 __vbaObjSet에서 저장된 오브젝트 포인터 값이다.

 

[이미지194] ECX(0x19F100) 참조 덤프

함수 호출 후 해당 값이 0x00으로 초기화 된다. 자원 해제 이후 비교 루틴으로 넘어간다.


9. 비교 루틴

 

9-1. __vbaVarTstEq 동작 분석

[이미지195] __vbaVarTstEq

이후 __vbaVarTstEq 함수가 호출되는데 해당 함수는 Name값과 Serial값을 비교하는 루틴이다.

 

[이미지196] EAX(0x19F158) 참조 덤프

EAX의 값을 살펴보면 문자열이 나타난다.

 

[이미지197] 데이터 필드(0x5BBE3C) 참조 덤프

데이터 필드를 참조해 보면 "1234" 문자열을 확인할 수 있다. 이는 사용자가 입력한 Serial 이다(오브젝트 참조 루틴).

 

[이미지198] EDX(0x19F148) 참조 덤프

EDX의 값을 살펴보면 문자열이 나타난다.

 

[이미지199] 데이터 필드(0x5D07D4) 참조 덤프

데이터 필드를 참조해 보면 "C5C6C7C8" 문자열을 확인할 수 있다. 이는 Name값에 의해 생성된 Serial 이다(암호화 루틴).


9-2. 조건부 분기

[이미지200] __vbaVarTstEq 호출 후 레지스터

EAX(사용자 입력 시리얼 값)와 EDX(Name값에 의해 생성된 시리얼)가 다를 경우 __vbaVarTstEq는 0x0을 반환한다.

 

[이미지201] rtcMsgBox

이후 조건부 분기로 "Nope, this serial is wrong!" 메시지가 출력된다.

 

[이미지202] __vbaVarTstEq 호출 후 레지스터

EAX(사용자 입력 시리얼 값)와 EDX(Name값에 의해 생성된 시리얼)가 일치 할 경우 __vbaVarTstEq는 0xFFFFFFFF을 반환한다.

 

[이미지203] rtcMsgBox

이후 조건부 분기로 "Yep, this key is right!" 메시지가 출력된다.

 

[이미지204] __vbaVardup

분기문 중간에 __vbaVardup 함수를 mov하는 코드를 볼 수 있는데 이후 rtcMsgBox에서 사용된다. 따라서 상세 분석은 하지 않았다.

 

[이미지205] 비교 루틴 전체

문자열 길이 판별 루틴과 마찬가지로 검사를 한번 더 하는 루틴이 존재한다. 이유는 똑같이 소스코드에서 해당 루틴이 실제로 두 번 들어가 있을것으로 추측된다. 두번째 검사에서는 __vbaVarTstEq가 아니라 __vaVarTstNe가 사용된다. 동작에는 큰 차이가 없고 반환값만 달라진다. __vaVarTstNe의 경우 문자열이 일치 할 때 0x0을 반환한다. 문자열이 다를 경우 0xFFFFFFFF를 반환한다.

 

[이미지206] 비교 루틴 전체

이후 오브젝트의 자원을 해제하는 코드가 실행된다. 오브젝트 관련 코드는 의미 파악이 힘들기 때문에 상세분석은 하지 않았다. 메시지 문자열 부분의 자원, 일부 정수값 등등의 자원을 해제한다.


10. 풀이

 

10-1. ZF 수정

[이미지207] __vbaVarTstEq

EAX(사용자 입력 시리얼 값)와 EDX(Name값에 의해 생성된 시리얼)가 다를 경우 __vbaVarTstEq는 0x0을 반환한다. ZF가 1로 세팅되기 때문에 분기가 발생한다(실패 메시지 출력). 따라서 ZF를 0으로 세팅해서 분기가 발생하지 않게 수정한다.

 

[이미지208] abex crackme 2

잘못된 Serial을 입력해도 성공 메시지가 나타난다.


10-2. nop 패치

[이미지209] __vbaVarTstEq

je 분기문을 nop 패치하여 분기가 발생하지 않게한다.

 

[이미지210] abex crackme 2

잘못된 Serial을 입력해도 성공 메시지가 나타난다.

 

[이미지211] abex crackme 2

이후 실패메시지도 나타난다.


10-3. 생성된 Serial 입력

[이미지212] EAX(0x19F0F0) 데이터 필드(0x54388C) 참조 덤프

__vbaVarCat 함수 호출 부분에서 반환값 EAX(0x19F0F0)값을 살펴보면 생성된 시리얼 값을 볼 수 있다.

 

[이미지213] abex crackme 2

NAME에 "abcd" 입력 후 "C5C6C7C8"을 입력하면 성공 메시지가 나타난다.

 

[이미지214] CPU

위 방법이 아니라도 다양하게 시리얼값을 알아낼 수 있다.

 

[이미지215] Stack

실행하면서 문자열 위주로 확인해 보면 쉽게 찾을 수 있다.


10-4. Serial 계산

[이미지216] __vbaVarAdd

암호화 루틴 과정을 분석하면서 Name값의 암호화 과정과 키값을 알 수 있었다. 시리얼 생성 루틴을 파악했기 때문에 Name값에 어떤 값이 오더라도 Serial을 계산할 수 있다.

 

value = list(input('Name : '))[:4]
result = ''
for i in value:
	result += str(hex(ord(i) + 100)[2:])
print('Serial :', result.upper())

Name 중 앞의 4글자만 가져와서 각각의 ASCII값에 0x64를 더해주는 방법으로 Serial이 생성된다. 직접 계산해도 되지만 편리성을 위해 파이썬 코드로 간단히 작성했다.

 

[이미지217] 파이썬 실행 결과

Name이 "test"일 때의 Serial 값을 알 수 있다.

 

[이미지218] abex crackme 2

계산한 Serial 값을 입력하면 성공 메시지가 나타난다. Name을 다른 값으로 해도 계산이 가능하다.


10-5. 비교값 수정

[이미지219] __vbaVarTstEq

함수 호출 전 push 인자를 수정해 같은 값을 비교하게 하면 0xFFFFFFFF이 반환될 것이다.

 

[이미지220] __vbaVarTstEq

EDX대신 EAX를 인자로 사용한다.

 

[이미지221] abex crackme 2

잘못된 Serial을 입력해도 성공 메시지가 나타난다.


 

'Reversing > abex' crackme' 카테고리의 다른 글

abex' crackme 5 상세분석  (3) 2025.06.23
abex' crackme 4 상세분석  (0) 2025.06.22
abex' crackme 3 상세분석  (1) 2025.06.21
abex' crackme 1 상세분석  (1) 2025.06.17