정현진Jeong Hyeonjin

← 기록 목록

리버싱

함수 호출 규약(cdecl·stdcall·fastcall)과 abex crackme 3~5

인자를 누가 어떻게 넘기고 스택을 누가 정리하는지 정리한 뒤, 키 파일 검사, Visual Basic 문자열 비교, 볼륨 이름으로 만든 일련번호를 차례로 풀었습니다.

발행
2025. 9. 4.
분량
2분
조회
0회

리버싱 입문서 3장을 공부하면서 정리했습니다. 앞부분은 함수 호출 규약, 뒷부분은 abex crackme 3·4·5번입니다.

함수 호출 규약

함수를 부를 때 인자를 어떻게 넘기고, 끝난 뒤 스택을 누가 정리하는지에 대한 약속입니다.

규약 인자 전달 스택 정리
cdecl 스택, 오른쪽 인자부터 호출한 쪽
stdcall 스택, 오른쪽 인자부터 호출된 함수
fastcall 앞의 두 인자는 ECX, EDX 레지스터, 나머지는 스택 호출된 함수

callee(1, 2)를 부르는 경우로 비교했습니다.

cdecl은 호출한 쪽이 ADD ESP, 8로 인자 2개(8바이트)를 직접 치웁니다.

PUSH 2
PUSH 1
CALL callee
ADD  ESP, 8

stdcall은 호출된 함수가 RETN 8로 돌아가면서 치웁니다.

PUSH 2
PUSH 1
CALL callee
; [callee]
PUSH EBP
MOV  EBP, ESP
...
POP  EBP
RETN 8

fastcall은 인자를 레지스터로 넘기므로 PUSH가 줄어듭니다. 함수 안에서는 보통 그 값을 다시 스택의 지역 변수로 옮겨 놓고 씁니다.

MOV EDX, 2
MOV ECX, 1
CALL callee

인자를 오른쪽부터 넣는 이유는 스택이 LIFO라서, 그래야 함수 안에서 첫 번째 인자가 가장 가까운 위치(EBP+8)에 오기 때문입니다.

음수 표현

Windows는 음수를 2의 보수로 표현합니다. 모든 비트를 뒤집고(NOT) 1을 더합니다.

crackme 3: 키 파일의 크기

  1. CreateFileA()로 abex.l2c라는 파일이 있는지 확인합니다. 결과는 EAX에 담깁니다.
  2. GetFileSize()로 크기를 확인해, 0x12(18바이트)인지 비교합니다.

처음에 "1234567890"(10바이트, 0x0A)을 넣은 파일로 확인했고, 18글자로 바꾸자 성공 메시지가 나왔습니다. 키 "내용"이 아니라 크기만 검사하는 구조였습니다.

crackme 4: Visual Basic 프로그램

Visual Basic 프로그램은 MSVBVM60.DLL의 API를 부르며 동작하는 이벤트 기반 프로그램입니다. 코드가 기계어가 아니라 중간 단계인 p-code로 저장돼 있어서 분석이 더 번거롭습니다.

이 문제는 맞는 일련번호를 넣어야 [Registered] 버튼이 활성화됩니다. 오류 메시지가 없어서 문자열 검색을 쓸 수 없었습니다. 대신 [All intermodular calls]에서 문자열 비교 함수 __vbaStrCmp를 찾았습니다.

  • EAX, ECX를 인자로 비교하고 결과는 EAX에 담깁니다.
  • 결과가 EDI로 옮겨지고, TEST DI, DI → JZ로 분기합니다.

푼 방법 두 가지

  • 플래그 수정: TEST DI, DI 직후 ZF를 1에서 0으로 바꾸자 버튼이 활성화됐습니다.
  • 비교 값 확인: __vbaStrCmp에 인자가 들어가는 0040230B에 브레이크포인트를 걸었습니다. EAX가 가리키는 곳에는 내가 넣은 "4"(0x34), ECX 쪽에는 "2166750"이 있었습니다. 이 값을 입력하자 성공했습니다.

crackme 5: 볼륨 이름으로 만든 일련번호

델파이로 만든 프로그램입니다.

GetVolumeInformation()

지정한 드라이브의 파일 시스템·볼륨 정보를 가져오는 함수입니다. MSDN의 _In_opt_는 선택적인 입력, _Out_opt_는 결과가 담길 선택적인 출력이라는 뜻입니다. 함수 실행 전에는 출력 변수가 0이었다가, 실행 후 값이 채워지는 것을 확인했습니다.

일련번호가 만들어지는 과정

  1. 볼륨 이름을 가져옵니다. 제 환경에서는 "OS"였습니다.
  2. lstrcatA()로 "OS"와 프로그램 안의 "4562-ABEX"를 이어 "OS4562-ABEX"를 만듭니다.
  3. 반복문이 앞 4글자의 값을 1씩 두 번 올려 "QU6762-ABEX"로 바꿉니다. (DEC DL이 0이 되면 ZF=1이 되어 반복이 끝납니다.)
  4. 다시 lstrcatA()로 "L2C-5781"과 이어 "L2C-5781QU6762-ABEX"를 만듭니다.
  5. lstrcmpiA()가 입력값과 비교해 같으면 EAX=0, 다르면 1을 돌려줍니다.

이 값을 입력하자 성공 메시지가 나왔습니다. 일련번호가 실행하는 컴퓨터의 볼륨 이름에 따라 달라지는 구조라서, 다른 PC에서는 답이 달라집니다.

정리

  • 호출 규약을 알면 스택에서 인자와 복귀 주소의 위치를 바로 짐작할 수 있습니다.
  • 문자열 검색이 안 되면 API 호출 목록에서 비교 함수를 찾는 방법이 있습니다.
  • 정답이 비교되는 순간의 인자 값을 보면 계산 과정을 다 따라가지 않아도 답을 알 수 있습니다.

단어 467글자 1,626연결 0