detect it easy로 파일을 먼저 열어보면
비주얼 베이직으로 작성된 프로그램임을 알 수 있습니다.
비주얼 베이직은 MSVBVM60.DLL에 있는 API를 호출하면서 동작을 수행합니다.
따라서 내부적으로 호출되는 API를 검색해보면 대부분 MSVBVM60.DLL에 속해있는 것을 알 수 있습니다.
프로그램을 실행해보겠습니다.
일련번호 입력 창이 뜨는데 아무 값이나 넣어보아도 Register 클릭 창이 활성화 되지 않는것으로 보아 특정 값 입력 시 활성화되는 것으로 보입니다.
일단 어떠한 알림 창도 뜨지 않아 문자열을 통한 검색을 어려울 것 같습니다.
그래서 호출된 API목록을 확인해보겠습니다. 코드 우클릭 -> search for -> all intermodular calls 클릭
다음과 같이 호출된 MSVBVR60.DLL에서 호출한 API목록이 나옵니다.
여기서 생각을 해보면 일련번호는 문자열의 비교를 통해 Register 버튼을 활성활 할 것입니다.
그래서 문자열 비교와 관련된 API인 vbaStrCmp를 찾아 위치로 가보았습니다.
__vbaStrCmp() 함수 호출 전에 EAX, ECX를 스택에 push하는 것으로 보아 함수의 인자를 넣어준것 같습니다.
그리고 밑에 연산 후 분기점에서 분기되어 버튼을 활성화 시키거나, 활성화를 시키지 않습니다.
그래서 마지막 TEST 부분에서 브레이크를 걸어 분기를 바꿔보겠습니다.
TEST 명령어는 AND연산을 하여 단순히 값이 0인가 아닌가를 판별하고 보통 분기문과 같이 쓰입니다.
만약 DI가 0이라면 AND연산 시 0이 되어 따로 값은 저장을 하지 않고 ZF만 1로 설정해줍니다.
그래서 밑에 JE분기문에서 ZF가 1이므로 지정된 주소로 점프를 합니다.
그래서 분기가 되기전 ZF값을 0으로 바꾸어 분기가 되지 않도록 해보겠습니다.
분기되지 않고 이어가고 있습니다.
이어서 프로그램 실행 후 활성화 된 Register 버튼 클릭
이번에는 일련번호를 직접 찾아보겠습니다.
이 부분을 보면 분기 이전에 DI값을 TEST 명령을 합니다.
그리고 DI값에 의해 성공과 실패의 분기를 나누게 됩니다.
올라가다 보면 EDI 값은 EAX값을 복사한 것입니다.
그위에는 vbaStrCmp()함수의 결과가 EAX에 저장이 되었죠.
그러므로 vbaStrCmp함수의 인자 즉, 호출 이전의 EAX와 ECX의 값을 보아야 합니다.
그래서 0040230B에 브레이크를 걸고 실행 시
EAX 값의 덤프 메모리는 9를 나타내고 ECX는 ASCII로 2215185를 나타냅니다.
즉, 문자열 비교 함수에 9와 2215185가 인자로 사용된다는 것이죠.
그래서, 우리는 추측을 할 수 있습니다. 2215185가 일련번호라는 것을 말이죠.
그래서 브레이크 문을 모두 제거하고 프로그램 실행 후 2215185를 넣어보았습니다.
성공 메세지가 뜨네요
해당 프로그램도 마찬가지로 실행 시 일련번호를 물어봅니다.
그런데 check버튼이 활성화 되어있습니다.
그래서 체크 버튼을 누르면
다음과 같이 에러 창이 뜨고 프로그램이 종료됩니다.
일단 호출한 API목록을 보면 새로운 것들이 있습니다.
DialogBoxParamA()함수, GetVolumeInformationA()함수
GetVolumeInformationA()함수는 지정된 루트 디렉터리가 속한 파일 시스템 정보와 볼륨 정보를 가져오는 함수입니다.
BOOL GetVolumeInformationA(
[in, optional] LPCSTR lpRootPathName,
[out, optional] LPSTR lpVolumeNameBuffer,
[in] DWORD nVolumeNameSize,
[out, optional] LPDWORD lpVolumeSerialNumber,
[out, optional] LPDWORD lpMaximumComponentLength,
[out, optional] LPDWORD lpFileSystemFlags,
[out, optional] LPSTR lpFileSystemNameBuffer,
[in] DWORD nFileSystemNameSize
);
MSDN에 정의된 내용입니다.
in은 입력 변수, out은 함수의 실행결과가 저장된 변수를 지정하는 것입니다.
optional은 선택적으로 사용할 수 있음을 의미합니다.
예를 들어, lpRootPathName은 선택적으로 사용할 수 있지만 nVolumeNameSize 입력 변수는 필수적으로 사용되어야 합니다.
DialogBoxParamA()함수는 대화 상자 템플릿 리소스에서 모달 대화 상자를 만듭니다. 이 값으로 대화상자의 모형을 초기화할 수 있습니다.
INT_PTR DialogBoxParamA(
[in, optional] HINSTANCE hInstance,
[in] LPCSTR lpTemplateName,
[in, optional] HWND hWndParent,
[in, optional] DLGPROC lpDialogFunc,
[in] LPARAM dwInitParam
);
비슷하게 DialogBoxA()함수가 있습니다. 이 함수는 대화 상자를 만들어줍니다.
void DialogBoxA(
[in, optional] hInstance,
[in] lpTemplate,
[in, optional] hWndParent,
[in, optional] lpDialogFunc
);
GetVolumeInformationA()함수 밑을 보면 반복문이 있습니다.
특정 코드에서 뒤로 점프하여 반복적으로 수행하는 부분을 반복문으로 예상할 수 있습니다.
004010AD를 브레이크로 잡은 후 스텝 오버하여 천천히 보면 반복문의 구조를 파악할 수 있습니다.
step 1
EDX의 하위 16비트 값인 DL에 2를 넣어줍니다.
step 2
40225C, 40225D, 40225E, 40225F의 메모리 값에 각각 1씩 더해줍니다
step 3
DL을 1감소 시킵니다
step 4
JNZ, 즉 ZF가 1이 아닌경우 004010AF로 점프합니다.
DL 값은 1이 감소하고 다시 반복 시작.step 5
두번째 분기에선 DL이 0이되어 ZF가 1이 설정되었으므로 JNZ를 건너뜁니다.
즉, 고급 언어로 작성해보자면
for(int i = 2; i > 0; i--){
<반복할 코드>
}
return 0;
여기서 i는 DL의값으로 추측됩니다.
실패 메시지 아래 성공 메세지 함수 부분이 있습니다.
그렇다면 어딘가에서 분기문에 의해 실패메세지로 가지않고 성공메세지의 호출인 00401117로 분기되는 곳이 있을 것입니다.
코드를 조금 올려보니 역시 있었습니다.
lstrcatA()함수는 MSDN에서 찾아보니 보안에 취약하므로 StringCchCat()함수를 사용하라는 권고문이 있었습니다. 아마 버퍼오버플로우에 의한 취약점이 있는것으로 판단됩니다.
문제에서는 lstrcat()함수가 쓰였으니 우선 알아보겠습니다.
한 문자열을 다른 문자열에 추가하는 함수입니다.
LPSTR lstrcatA(
[in, out] LPSTR lpString1,
[in] LPCSTR lpString2
);
다음으로 lstrcmpiA()함수는 대/소문자를 구분하지 않고 두 문자열을 비교합니다.
int lstrcmpiA(
[in] LPCSTR lpString1,
[in] LPCSTR lpString2
);
문자열이 다르면 반환 값은 1, 문자열이 같으면 반환 값은 0입니다.
반환 값은 EAX에 저장됩니다.
이제 프로그램의 전체적인 분석을 해보겠습니다.
1. 루트 디렉터리의 파일 시스템 정보와 볼륨 정보를 가져와 일련번호를 만들기 위한 입력 값으로 사용
2. 파일 시스템 정보와 프로그램 내부에 들어 있는 문자열을 결합해서 만든 새로운 문자열을 반복문을 통해 다른 문자열로 변형 => 일련번호 생성 과정
3. 앞에서 생성한 새로운 문자열을 가지고 프로그램 내부에 들어 있는 문자열과 결합해 일련번호 생성.
4. 생성한 일련번호와 사용자가 입력한 값이 맞는지 확인. => 맞으면 성공 메세지 틀리면 오류 메세지
step 1
일단 파일 시스템 정보를 가져오는 부분부터 차례대로 스텝 오버 해보겠습니다.
0040225C에 있는 데이터는 일련번호를 만드는데 사용되기 때문에 메모리에 어떤 값이 저장되는지 보아야 합니다. 그래서 immediate constant 후 해당 함수를 끝내면
아무 내용이 없었습니다.
다음에 나오는 함수는 앞서 설명한 lstrcatA() 함수입니다. 이 함수는 인자 2개인 문자열을 결합해주는 함수입니다.
해당 함수를 마친 후 0040225C메모리의 값 확인
제가 보기엔 처음 파일 시스템 정보를 가져오는 곳에서 이유는 모르겠지만 공백을 가져오고 공백과 4562-ABEX를 결합한 것 같습니다.step 2
이제 반복문 부분입니다.
총 2번 반복하고 메모리의 40225C ~ 40225F까지 의 값들을 1씩 증가시킵니다.
반복문 수행 후 변경된 메모리 값step 3
다음은 마지막으로 문자열을 또 결합해줍니다.
lstrcmpiA() 함수 전까지 수행을 하자
EAX에 저장된 일련번호는 다음과 같았습니다.step 4
이제 lstrcmpA 함수를 해석해보면
제가 입력한 "abcd"문자열과 일련번호인 "L2C-57816784-ABEX"을 비교합니다.
lstrcmpA함수는 비교된 문자열이 일치하다면 EAX에 0을 저장하고 일치하지 않으면 1을 저장합니다.
지금같은 경우는 일치하지 않으므로 EAX에는 1이 저장될 것입니다.
그러므로 CMP EAX, 0 연산을 통해 ZF는 0으로 유지되고(EAX-0이 0이 아니므로) JE 분기문에서 ZF가 0이므로(EAX와 0이 같지 않으므로) 성공 메세지로 가는 주소 00401117로 가지 못하고 그대로 이어가게 되어 실패 메세지가 나옵니다.step 5
그럼 이제 일련번호("L2C-57816784-ABEX")를 알았으므로 프로그램을 다시 시작해서 해당 값을 넣어보겠습니다.
성공 메세지가 나왔습니다.
detect it easy으로 프로그램 생성 환경을 알고 비주얼 베이직인 경우 대부분 MSVBVM60.DLL에서 API 호출 => __vbstrcmp()
DL은 EDX의 하위 16비트이다.
lstrcatA() 함수는 문자열을 결합시키는 함수 ##보안문제로 StringCchCat()함수 권장
lstrcmpiA() 함수는 문자열을 비교하여 같으면 EAX에 0을 저장하고 일치하지 않으면 EAX에 1을 저장해준다.
GetVolumeInformationA()함수는 지정된 루트 디렉터리의 파일 시스템 정보와 볼륨 정보를 가져오는 함수이다.
반복문은 보통 후위 코드로 점프하며 주로 암호화나 일련번호를 생성 등에 사용된다.
JE = 비교값이 같을 경우 지정 주소로 점프
CMP = 두 인자 비교하는 명령어 주로 분기문과 같이 쓰인다.