리버싱 엔지니어링/리버싱 핵심 원리
6 abex’ crackme #1 분석
흰싸라기
2022. 3. 13. 23:45
crackme : 크랙 연습 목적으로 작성되어 공개된 프로그램
- crackme 샘플을 분석하여 디버거와 디스어셈 코드에 익숙해질 수 있음
6.1 abex’ crackme #1
6.1.1 프로그램 실행
: 디버깅 전 파일을 실행하여 어떤 프로그램인지 살펴보기
1) 프로그램 실행
- “Make me think your HD is a CD-Rom” 메시지 박스 출력
- CD-ROM : CD를 이용해 정보를 기억시키고, 저장하고 검색할 수 있는 컴퓨터 데이터 저장기구
- CD-ROM을 통해 HD가 HDD(Hard Disk Drive)를 의미한다고 추측할 수 있음
2) 1번 메시지창 확인 버튼 클릭
- Error 메시지 박스 출력 후 종료
6.1.2 디버깅 시작
: OllyDbg를 통해 abex’ crackme #1 실행
- EP에 main 함수가 바로 나타남
- 어셈블리 언어로 만들어진 실행파일이기 때문
- 개발툴을 사용하면 작성한 소스코드 외에 컴파일러가 *Stub Code를 추가하기 때문에 디스어셈을 하면 복잡하게 보임
- *Stub Code : 고급언어로 구현된 바이너리 파일을 실행하기 위해 어셈블리 언어로 작성한 코드
- 에섬블리 언어로 작성하면 어셈 코드가 곧 디스어셈 코드가 됨
6.1.3 코드 분석
1) Win32 API 호출 위주로 살펴보기
MessageBox("Make me think your HD is a CD-Rom")
GetDriveType("C:\\\\")
..
MessageBox("Nah... This is not a CD-ROM Drive!")
MessageBox("OK, I really think that yout HD is a CD-ROM! :p")
ExitProcess()
- GetDriveType("C:\\”) : C드라이브의 타입을 얻어옴
- 조작을 통해 CD-ROM 타입으로 인식하도록 만들어 "OK, I really think that yout HD is a CD-ROM! :p" 메시지 박스가 출력되도록 만들기
2) 코드 상세 분석
: 분석을 통한 에셈블리 명령어 익히기
; MessageBoxA() 호출
00401000 >/>PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
00401002 |>PUSH 402000 ; |Title = "abex' 1st crackme"
00401007 |>PUSH 402012 ; |Text = "Make me think your HD is a CD-Rom."
0040100C |>PUSH 0 ; |hOwner = NULL
0040100E |>CALL 00401061 ; \\MessageBoxA
; GetDriveType() 호출
00401013 |>PUSH 402094 ; /RootPathName = "c:\\"
00401018 |>CALL 00401055 ; \\GetDriveTypeA
0040101D |>INC ESI ; ESI = 0 ; 값을 1을 증가
0040101E |>DEC EAX ; EAX = 2 ; 값을 1 감소
0040101F |>JMP SHORT 00401021 ; 의미없는 JMP 명령(garbage code)
00401021 |>INC ESI ; ESI = 1
00401022 |>INC ESI ; ESI = 2
00401023 |>DEC EAX ; EAX = 1
;조건 분기(401028 또는 40103D)
00401024 |>CMP EAX,ESI ; EAX과 ESI 비교
00401026 |>JE SHORT 0040103D ; JE(Jump if Equal) 조건 분기 명령
; 두 값이 같으면 0040103D로 점프
; 다르면 그냥 밑 401028로 진행
; 0040103D 주소는 제작자가 원하는 메시지 박스 출력코드
; 실패 MessageBoxA() 호출
00401028 |>PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
0040102A |>PUSH 402035 ; |Title = "Error"
0040102F |>PUSH 40203B ; |Text = "Nah... This is not a CD-ROM Drive!"
00401034 |>PUSH 0 ; |hOwner = NULL
00401036 |>CALL 00401061 ; \\MessageBoxA
0040103B |>JMP SHORT 00401050 ; abexcm1-.00401050
; 성공 MessageBoxA() 호출
0040103D |>PUSH 0 ; |/Style = MB_OK|MB_APPLMODAL
0040103F |>PUSH 40205E ; ||Title = "YEAH!"
00401044 |>PUSH 402064 ; ||Text = "Ok, I really think that your HD is a CD-ROM! :p"
00401049 |>PUSH 0 ; ||hOwner = NULL
0040104B |>CALL 00401061 ; |\\MessageBoxA
00401050 \\>CALL 0040105B ; \\ExitProcess
- 에셈블리 명령어 정리
명령어 | 설명 |
PUSH | 스택에 값을 입력 |
CALL | 지정된 주소의 함수를 호출 |
INC | 값을 1 증가 |
DEC | 값을 1 감소 |
JMP | 지정된 주소로 점프 |
CMP | 주어진 두개의 operand(피연산자 : 연산에 사용되는 변수나 상수) 비교 *SUB 명령어와 동일하나 operand 값이 변경되지 않고 EFLAGS 레지스터만 변경됨 (두 operand의 값이 동일하다면 ZF = 1로 세팅) |
JE | 조건 분기(Jump if equal) *ZF = 1이면 점프 |
6.2 크랙
: 코드를 패치(다른 코드로 덮어 쓰는 행위)하여 프로그램을 크랙하기
- 조건 분기(JE) 명령어를 점프(JMP) 명령어로 변경
- 무조건 점프하기 때문에 목적 주소로 이동 가능
- ‘JE SHORT 0040103D’ 명령어를 Assemble 기능[Space]을 이용하여 ‘JMP SHORT 0040103D’ 명령어로 변경
6.3 스택에 파라미터를 전달하는 방법
: 코드를 통해 함수 호출 시 스택에 파라미터를 전달하는 방법
00401000 >/>PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
00401002 |>PUSH 402000 ; |Title = "abex' 1st crackme"
00401007 |>PUSH 402012 ; |Text = "Make me think your HD is a CD-Rom."
0040100C |>PUSH 0 ; |hOwner = NULL
0040100E |>CALL 00401061 ; \\MessageBoxA
위 어셈블리 코드를 C 언어로 변역하면 아래와 같다
MessageBox(NULL, "Make me think your HD is a CD-Rom.", "abex' 1st crackme");
- 실제 C언어 소스코드에서 함수에 넘기는 파라미터의 순서가 어셈블리 언어에서는 역순으로 넘어간다
- 스택 메모리 구조(FILO : First In Last Out, LIFO : Last In First Out)
스택
- 함수의 첫째 파라미터가 스택의 제일 위에 취하고 마지막 파라미터가 아래에 쌓임
- 따라서, 스택에서 파라미터를 하나씩 꺼낼 때 FILO 구조에 따라 첫번째 파라미터부터 꺼낼 수 있다
- 함수 입장에서는 스택에 파라미터가 순서대로 들어 있는 셈