리버싱 핵심원리 1부 8장 정리
Note
본 게시글의 내용은 리버싱 핵심원리를 보며 복습 겸 정리하였습니다.
책의 내용과 일부 상이할 수 있고, 이해를 돕기 위해 강좌 형식의 말투와 적절한(?) 예시를 추가하였습니다.
본 게시글에서 사용되는 소스 코드와 파일, 프로그램은 리버싱 핵심원리에서 제공하는 파일과 언급되는 것들을 기반으로 하며, 일부 상이할 수 있습니다.
abex' 2nd crackme
시리얼 키Serial Key를 알아내야 하는 프로그램입니다.
Name
을 입력받는 걸로 보아 Serial
을 생성할 때 Name
의 문자열 값이 사용될 것으로 보입니다. 물론, 어디까지나 경험을 기반으로 한 추측이기 때문에 맞을 수도 아닐 수도 있습니다.
Name
과 Serial
에 아무 값이나 입력하고 Check 버튼을 눌러주세요.
건방지게 시리얼의 값이 틀렸다고 하네요. 다른 값으로 시도해도 같은 창이 나타납니다.
특징
Visual Basic
abex' 2nd crackme 프로그램은 Visual Basic(VB)으로 만들어졌습니다.
이 프로그램은 MSVBVM60.dll(Microsoft Visual Basic Virtual Machine 6.0)이라는 VB 전용 엔진(The Thunder Runtime Engine)을 사용합니다. 이 엔진은 VB에서 메시지 박스를 출력한다고 할 때 MsgBox()
함수를 사용해 출력하는데, 실제 내부 동작은 Win32 API인 MessageBox()
를 호출합니다.
N code와 P code
VB는 컴파일 옵션에 따라 N code와 P code로 컴파일이 가능합니다.
NNative code는 일반적인 디버거에서 해석 가능한 IA-32 명령을 사용하지만 PPseudo code는 VB 엔진에서 해석 가능한 명령(바이트 코드)를 사용합니다. 책에 별도로 언급은 안 되어 있지만 abex' 2nd crackme는 N code로 추정됩니다.
Event Handler
VB는 GUI 프로그래밍을 할 때 주로 사용되고 개발 도구도 GUI 프로그래밍을 하는 데 최적화되어 있습니다. 즉, VB로 만들어진 프로그램은 Windows의 Event Driven 방식으로 동작하기 때문에 main()
과 같은 곳에 코드가 존재하는 것이 아닌 각 Event Handler에 사용자 코드가 존재합니다. 따라서 abex' 2nd crackme에 있는 각 버튼의 Handler에 사용자 코드가 존재할 것입니다.
디버깅 시작
엔트리 포인트의 주소는 0x401238
입니다. 0x401238
주소에 있는 push 401E14
명령에 의해 RT_MainStruct
구조체의 주소(0x401E14
)를 스택에 삽입합니다. 그리고 0x40123D
주소의 call <JMP.&ThunRTMain>
명령에 의해 0x401232
주소의 명령이 실행됩니다.
jmp dword ptr ds:[<Ordinal#100>]
<Ordinal#100>은 <msvbvm60.ThunRTMain>입니다.
사용자 환경에 따라 다르게 나타날 수 있습니다.
간접 호출 기법
call <JMP.&ThunRTMain>
명령은 다소 신기한 편입니다. 0x401232
주소에 있는 jmp dword ptr ds:[<Ordinal#100>]
로 점프(이동)한 후 ThunRTMain
을 호출하니까요. 이러한 기법은 VC++, VB에서 많이 사용되는 간접 호출(Indirect Call)입니다.
ThunRTMain
함수
ThunRTMain
함수로 진입하면 이미지 1-4를 통해 주소가 ㅈ같이 변한 걸 확인할 수 있습니다. 왜냐하면 이 주소는 MSVBVM60.dll의 주소이기 때문입니다.
즉, 우리가 분석할 프로그램의 주소가 아니기 때문에 이곳에 작성된 엄청난 양의 코드를 분석 및 해석할 필요는 없습니다.
분석
문자열 검색
우리가 패치해야 할 코드는 어디에 있을까요? 지금의 우리 수준으로 RT_MainStruct
구조체를 분석하는 건 양심상 할 수 없으니, 문자열 검색 기능을 통해 실마리를 찾아보도록 합시다.
이미지 1-5와 같이 컨텍스트 메뉴에 문자열 참조 메뉴 아이템을 찾아서 클릭해주세요. 그러면 참조되고 있는 모든 문자열을 찾아서 보여줍니다.
여러 문자열 중 "Wrong serial!
이라는 문자열이 보입니다. 우리가 처음에 프로그램 실행 후 아무 값이나 입력하고 Check 버튼을 눌렀을 때 나타나던 제목 문자열이죠.
해당 문자열을 더블 클릭해주세요.
메시지 박스의 제목 문자열과 내용 문자열이 확인됩니다.
위로 스크롤하면 성공 시 나타나는 메시지 박스의 문자열도 같이 보입니다. 추측상 어떠한 알고리즘을 통해 시리얼 키를 생성하는데 사용자가 입력한 시리얼과 동일할 때 출력할 것으로 보입니다.
즉, 문자열 비교하는 함수가 사용되는게 보일겁니다. 위로 좀 더 올라가봅시다.
멀지 않은 곳에 __vbaVarTstEq()
함수를 호출하는게 확인됩니다. 이 함수를 호출해서 반환값(ax
)을 비교한 후 0x403332
의 조건문에 의해 성공 또는 실패 메시지 박스로 이동합니다.
문자열 주소 찾기
__vbaVarTstEq()
함수가 문자열 비교 함수라면 두 개의 문자열을 넘겨받아 비교할 것입니다. 그렇다면 그 위에 있는 push
와 lea
는 우리가 비교할 문자열 매개변수들이 되겠죠.
push eax
명령까지 실행해주세요.
Name
과 Serial
에는 Sibal
과 abcd1234
를 입력한다고 가정합니다.
이미지 1-9에서 eax
와 edx
에 저장된 값을 스택 창에서 확인하면 아래와 같이 나타납니다:
이미지 1-10을 통해 확인할 수 있는 사실이 있습니다.
eax
에는 사용자가 실제 입력한 Serial
값과 edx
에는 우리가 실제로 입력해야하는 정답 Serial
값이 저장되어 있습니다.
위치랑 조금 동떨어진 곳에 있는 거 같은데요...?
eax
나 edx
레지스터에 저장된 값을 메모리 덤프 창으로 이동해보면 이미지 1-11과 같이 나타납니다.
일부 영역은 값이 동일한데 일부 영역만 값이 다르게 나타납니다. 이는 VB에서 사용하는 문자열 객체의 특징으로, 내부에 동적으로 할당한 실제 문자열 버퍼의 주소를 갖고 있습니다.
즉, 사진 속에서 일부 영역만 다른 바이트의 시작점이 실제 문자열 버퍼가 저장된 주소라 볼 수 있습니다. 위 사진을 기준으로 0x19F2FC
와 0x19F28C
에 실제 문자열 데이터가 있겠죠? 이를 이미지 1-10에서 확인할 수 있습니다.
abex' 2nd crackme 프로그램을 다시 실행한 후 Name
과 Serial
에 Sibal
과 B7CDC6C5
의 값을 입력한 후 Check 버튼을 누르면 성공 메시지 박스가 출력되는 걸 확인할 수 있습니다.
하지만 Name
에 다른 값을 입력하고 Check 버튼을 누르면 실패 메시지 박스가 나타납니다. 이유는 역시 Name
을 기반으로 시리얼 키를 생성하기 때문입니다. 이 시리얼 키를 생성하는 알고리즘을 찾아서 조져봅시다.
시리얼 생성 알고리즘
이미지 1-8에 보이는 je
조건 분기 명령은 분명 함수 안에 속해있는 명령입니다. 위로 올리다보면 함수의 시작 부분을 만날 수 있을겁니다.
이 함수가 바로 Check 버튼의 Event Handler일 것입니다. 이유는 Check 버튼을 눌러야 문자열 비교 후 성공 또는 실패 메시지 박스를 띄우기 때문이죠.
위로 스크롤하다보면 이미지 1-13과 같이 0x402ED0
주소를 만날 수 있습니다. 어딘가 익숙한 코드죠? 네, 스택 프레임 생성 모습을 보이고 있네요. 함수의 시작 부분입니다.
Name
문자열 읽는 코드
계속 디버깅을 진행하다보면 0x402F8E
주소에 도달하게 됩니다.
위 코드를 자세히 보시면 [ebp - 88]
의 주소를 edx
로 옮긴 후 함수의 매개변수로 전달하고 있습니다. 이 명령까지만 실행하고 edx
레지스터의 값을 메모리 덤프 창에서 이동하여 확인해봅시다.
우리가 찾을 건 Name
의 문자열이고 VB에서 문자열은 문자열 객체를 사용한다고 했으니 이미지 1-14처럼 육안으로 하기 힘듭니다.
0x402F98
주소에 있는 call dword ptr ds:[ecx + A0]
명령까지 실행한 후 메모리 덤프 창을 다시 확인해봅시다.
무언 가 기록된 게 확인됩니다.
이미지 1-16과 같이 기록된 영역을 마우스로 드래그하여 선택한 후 우클릭하여 현재 덤프에서 따라가는 기능을 클릭해주세요.
[ebp - 88]
주소에 Name
문자열이 저장되어 있는 걸 확인할 수 있습니다.
스택 메모리 창에서도 확인됨
암호화 루프
00403102 | BB 04000000 | mov ebx, 0x4 |
00403107 | 52 | push edx |
00403108 | 50 | push eax |
00403109 | 899D 2CFFFFFF | mov dword ptr ss:[ebp - 0xD4], ebx |
0040310F | C785 24FFFFFF 02800000 | mov dword ptr ss:[ebp - 0xDC], 0x8002 |
00403119 | FF15 28104000 | call dword ptr ds:[<__vbaLenVar>] |
0040311F | 8D8D 24FFFFFF | lea ecx, dword ptr ss:[ebp - 0xDC] |
00403125 | 50 | push eax |
00403126 | 51 | push ecx |
00403127 | FF15 44104000 | call dword ptr ds:[<__vbaVarTstLt>] |
0040312D | 66:85C0 | test ax, ax |
00403130 | 0F85 BD030000 | jne 0x4034F3 |
00403136 | B8 01000000 | mov eax, 0x1 |
0040313B | 8D95 24FFFFFF | lea edx, dword ptr ss:[ebp - 0xDC] |
00403141 | 8985 2CFFFFFF | mov dword ptr ss:[ebp - 0xD4], eax |
00403147 | 8985 0CFFFFFF | mov dword ptr ss:[ebp - 0xF4], eax |
0040314D | 8D85 14FFFFFF | lea eax, dword ptr ss:[ebp - 0xEC] |
00403153 | 52 | push edx |
00403154 | 8D8D 04FFFFFF | lea ecx, dword ptr ss:[ebp - 0xFC] |
0040315A | 50 | push eax |
0040315B | 8D95 BCFEFFFF | lea edx, dword ptr ss:[ebp - 0x144] |
00403161 | 51 | push ecx |
00403162 | 8D85 CCFEFFFF | lea eax, dword ptr ss:[ebp - 0x134] |
00403168 | 52 | push edx |
00403169 | 8D4D DC | lea ecx, dword ptr ss:[ebp - 0x24] |
0040316C | BF 02000000 | mov edi, 0x2 |
00403171 | 50 | push eax |
00403172 | 51 | push ecx |
00403173 | 89BD 24FFFFFF | mov dword ptr ss:[ebp - 0xDC], edi |
00403179 | 899D 1CFFFFFF | mov dword ptr ss:[ebp - 0xE4], ebx |
0040317F | 89BD 14FFFFFF | mov dword ptr ss:[ebp - 0xEC], edi |
00403185 | 89BD 04FFFFFF | mov dword ptr ss:[ebp - 0xFC], edi |
0040318B | FF15 30104000 | call dword ptr ds:[<__vbaVarForInit>] |
00403191 | 8B1D 4C104000 | mov ebx, dword ptr ds:[<Ordinal#632>] |
00403197 | 85C0 | test eax, eax |
00403199 | 0F84 06010000 | je 0x4032A5 |
0040319F | 8D95 64FFFFFF | lea edx, dword ptr ss:[ebp - 0x9C] |
004031A5 | 8D45 DC | lea eax, dword ptr ss:[ebp - 0x24] |
004031A8 | 52 | push edx |
004031A9 | 50 | push eax |
004031AA | C785 6CFFFFFF 01000000 | mov dword ptr ss:[ebp - 0x94], 0x1 |
004031B4 | 89BD 64FFFFFF | mov dword ptr ss:[ebp - 0x9C], edi |
004031BA | FF15 A8104000 | call dword ptr ds:[<__vbaI4Var>] |
004031C0 | 8D4D 8C | lea ecx, dword ptr ss:[ebp - 0x74] |
004031C3 | 50 | push eax |
004031C4 | 8D95 54FFFFFF | lea edx, dword ptr ss:[ebp - 0xAC] |
004031CA | 51 | push ecx |
004031CB | 52 | push edx |
004031CC | FFD3 | call ebx |
004031CE | 8D95 54FFFFFF | lea edx, dword ptr ss:[ebp - 0xAC] |
004031D4 | 8D4D AC | lea ecx, dword ptr ss:[ebp - 0x54] |
004031D7 | FFD6 | call esi | esi:__vbaVarMove
004031D9 | 8D8D 64FFFFFF | lea ecx, dword ptr ss:[ebp - 0x9C] |
004031DF | FF15 0C104000 | call dword ptr ds:[<__vbaFreeVar>] |
004031E5 | 8D45 AC | lea eax, dword ptr ss:[ebp - 0x54] |
004031E8 | 8D8D 78FFFFFF | lea ecx, dword ptr ss:[ebp - 0x88] |
004031EE | 50 | push eax |
004031EF | 51 | push ecx |
004031F0 | FF15 80104000 | call dword ptr ds:[<__vbaStrVarVal>] |
004031F6 | 50 | push eax |
004031F7 | FF15 1C104000 | call dword ptr ds:[<Ordinal#516>] |
004031FD | 8D95 24FFFFFF | lea edx, dword ptr ss:[ebp - 0xDC] |
00403203 | 8D4D AC | lea ecx, dword ptr ss:[ebp - 0x54] |
00403206 | 66:8985 2CFFFFFF | mov word ptr ss:[ebp - 0xD4], ax |
0040320D | 89BD 24FFFFFF | mov dword ptr ss:[ebp - 0xDC], edi |
00403213 | FFD6 | call esi | esi:__vbaVarMove
00403215 | 8D8D 78FFFFFF | lea ecx, dword ptr ss:[ebp - 0x88] |
0040321B | FF15 CC104000 | call dword ptr ds:[<__vbaFreeStr>] |
00403221 | 8D55 AC | lea edx, dword ptr ss:[ebp - 0x54] |
00403224 | 8D85 24FFFFFF | lea eax, dword ptr ss:[ebp - 0xDC] |
0040322A | 52 | push edx |
0040322B | 8D8D 64FFFFFF | lea ecx, dword ptr ss:[ebp - 0x9C] |
00403231 | 50 | push eax |
00403232 | 51 | push ecx |
00403233 | C785 2CFFFFFF 64000000 | mov dword ptr ss:[ebp - 0xD4], 0x64 | 64:'d'
0040323D | 89BD 24FFFFFF | mov dword ptr ss:[ebp - 0xDC], edi |
00403243 | FF15 AC104000 | call dword ptr ds:[<__vbaVarAdd>] |
00403249 | 8BD0 | mov edx, eax |
0040324B | 8D4D AC | lea ecx, dword ptr ss:[ebp - 0x54] |
0040324E | FFD6 | call esi | esi:__vbaVarMove
00403250 | 8D55 AC | lea edx, dword ptr ss:[ebp - 0x54] |
00403253 | 8D85 64FFFFFF | lea eax, dword ptr ss:[ebp - 0x9C] |
00403259 | 52 | push edx |
0040325A | 50 | push eax |
0040325B | FF15 94104000 | call dword ptr ds:[<Ordinal#573>] |
00403261 | 8D95 64FFFFFF | lea edx, dword ptr ss:[ebp - 0x9C] |
00403267 | 8D4D AC | lea ecx, dword ptr ss:[ebp - 0x54] |
0040326A | FFD6 | call esi | esi:__vbaVarMove
0040326C | 8D4D BC | lea ecx, dword ptr ss:[ebp - 0x44] |
0040326F | 8D55 AC | lea edx, dword ptr ss:[ebp - 0x54] |
00403272 | 51 | push ecx |
00403273 | 8D85 64FFFFFF | lea eax, dword ptr ss:[ebp - 0x9C] |
00403279 | 52 | push edx |
0040327A | 50 | push eax |
0040327B | FF15 84104000 | call dword ptr ds:[<__vbaVarCat>] |
00403281 | 8BD0 | mov edx, eax |
00403283 | 8D4D BC | lea ecx, dword ptr ss:[ebp - 0x44] |
00403286 | FFD6 | call esi | esi:__vbaVarMove
00403288 | 8D8D BCFEFFFF | lea ecx, dword ptr ss:[ebp - 0x144] |
0040328E | 8D95 CCFEFFFF | lea edx, dword ptr ss:[ebp - 0x134] |
00403294 | 51 | push ecx |
00403295 | 8D45 DC | lea eax, dword ptr ss:[ebp - 0x24] |
00403298 | 52 | push edx |
00403299 | 50 | push eax |
0040329A | FF15 C0104000 | call dword ptr ds:[<__vbaVarForNext>] |
004032A0 | E9 F2FEFFFF | jmp 0x403197 |
004032A5 | 8B45 08 | mov eax, dword ptr ss:[ebp + 0x8] |
계속 디버깅하다보면 0x403102
의 주소에 도달하게 되는데, 0x403102
~ 0x4032A5
까지 반복문입니다.
__vbaVarForInit
과 __vbaVarForNext
함수를 볼 수 있는데, 이는 주로 반복문을 실행할 때 사용됩니다. __vbaVarForInit
은 반복문 초기화 단계에서 호출되고 __vbaVarForNext
는 반복문의 반복 단계에서 호출되는데 반복문 변수의 값을 증가 또는 감소시키고 계속 반복할 지 결정합니다.
0x40323D
까지 실행한 후 확인해봅시다.
edx
레지스터에 저장된 값을 보면 B7
이 저장되어 있습니다. 이 값은 우리가 시리얼 키를 알아냈을 때의 시작 값과 같습니다.
어떠한 연산을 통해 시리얼 값이 생성되는데 사용되는 함수를 보면 rtcAnsiValueBstr
과 __vbaVarAdd
가 있습니다. 대충 예상이 가지 않나요?
맞습니다. Name
문자열의 문자를 ASCII 값으로 변환한 후 어떠한 값을 더해 시리얼 키를 생성해내고 있음을 유추할 수 있습니다. 0x403233
을 보시며 암호화 키로 0x64
도 보입니다.
00403249 | 8BD0 | mov edx, eax |
0040324B | 8D4D AC | lea ecx, dword ptr ss:[ebp - 0x54] |
0040324E | FFD6 | call esi | esi:__vbaVarMove
00403250 | 8D55 AC | lea edx, dword ptr ss:[ebp - 0x54] |
00403253 | 8D85 64FFFFFF | lea eax, dword ptr ss:[ebp - 0x9C] |
00403259 | 52 | push edx |
0040325A | 50 | push eax |
0040325B | FF15 94104000 | call dword ptr ds:[<Ordinal#573>] | ;rtcHexVarFromVar
rtcHexVarFromVar
를 통해 ASCII를 다시 유니코드 문자로 변환합니다. eax
레지스터가 가리키는 버퍼를 스택 메모리에서 쫓아가면 "B7" 문자가 생성될 걸 확인할 수 있습니다.
ASCII로 변환된 문자 값에 0x64를 더한 후 유니코드 문자로 변환하는군요. 'S' 문자는 ASCII 값으로 0x53이고 여기에 0x64를 더하면 0xB7이 됩니다. 이 B7이라는 값을 유니코드 문자로 변환한거죠.
0040326C | 8D4D BC | lea ecx, dword ptr ss:[ebp - 0x44] |
0040326F | 8D55 AC | lea edx, dword ptr ss:[ebp - 0x54] |
00403272 | 51 | push ecx |
00403273 | 8D85 64FFFFFF | lea eax, dword ptr ss:[ebp - 0x9C] | [ebp-9C]:ProcCallEngine+2FAB
00403279 | 52 | push edx |
0040327A | 50 | push eax |
0040327B | FF15 84104000 | call dword ptr ds:[<__vbaVarCat>] |
마지막으로 생성된 문자열을 이어 붙이는 __vbaVarCat
을 호출합니다.
ecx
와 edx
의 값을 이어붙여 최종적으로 시리얼 키 생성을 끝냅니다.
Name
의 문자열을 앞에서부터 한 문자씩 읽는다. (총 4회 반복)- 문자를 ASCII 값으로 변환
- 변환된 값에 0x64 더하기
- 값을 다시 유니코드 문자로 변환
- 변환된 문자를 서로 이어붙이기
시리얼 키를 생성해내는 로직은 위와 같습니다.