1. abex crackme 첫 번째 예제
- 알아볼 내용
- 디버거의 기능
- 스텝 오버
- 스텝 인투
- 브레이크포인트
- 엔트리포인트
- 리버싱의 핵심 지식
1.1. 프로그램 동작 방식
- abex crackme 첫 예제
- 프로그램을 실행했을 때 하드디스크가 CD롬으로 인식되도록 변경하는 것이 목표
1.2. 엔트리 포인트
- OllyDbg가 쓰기 싫어서 x32dbg로 열었는데 다행히도 단축키가 대부분 같았다
- F9를 몇 번 누르다 보면 EntryPoint라고 comment된 위치에 멈추게 된다
1.3. 스텝 오버(F8)과 스텝 인투(F7)
- 스텝 오버(F8)
- 프로그램을 한 줄 씩 실행
- 서브루틴 안으로 안들어간다
- 함수에 입력과 출력만 볼 수 있고, 내부에서 어떤 일이 벌어지는지 알 수 없다
- 스텝 인투(F7)
- 프로그램을 한 줄 씩 실행
- 서브루틴 안으로 들어간다
- 어떤 상황에서 스텝 오버와 스템 인투를 사용해야 할지 신중하게 결정해야 한다
- 스텝 오버만 사용할 경우
- 스텝 인투를 너무 많이 사용할 경우
- 분석이 복잡해진다
- 서브루틴의 반복되는 구조 속으로 빠져들어 어디까지 분석했는지 잃어버릴 수 있다
- 일반적으로 시스템 DLL은 스텝 인투를 하지 않는다
- IAT (Import Address Table)에 있는 API를 호출하는 경우
- IAT = PE 파일에서 사용하는 외부 DLL에서 제공하는 함수 주소를 모아놓은 자료 구조
JMP DWORD PRT DS:[함수 주소]
FF25 [함수 주소] JMP DWORD PTR DS:[함수 주소]
- IAT 테이블에 들어 있는 함수 주소를 호출할 때 사용되는 명령어 형식
1.4. 브레이크포인트(F2)와 프로그램 실행(F9)
- 브레이크 포인트(F2)
- 특정 명령어에 브레이크 포인트를 설정하면 프로그램 실행이 그 위치에서 일시 정지
- 명령어 맨 앞의 2 Byte를 ‘CC’로 대체하는 INT 3 인터럽트 기능을 사용하는 기술
- 디버거 내부에 파일(udd)로 브레이크포인트 관련 정보를 저장
- 프로그램을 다시 시작해도 동일하게 설정되어 있음
1.5. 전진(NumLock +)과 후진(NumLock -) 기능 사용하기
- 전진과 후진
- 무엇을 분석했는지, 어디까지 분석했는지 다시 확인해야할 때
- 레지스터와 메모리에 저장된 값을 전의 상태로 되돌리는 것이 아님
- 스텝 오버와 스텝 인투를 사용해서 분석했던 로그를 다시 돌아가는 것
1.6. 프로그램 다시 시작 (Ctrl+F12)
- 프로그램 다시 시작
- 이전 명령어를 다시 실행하고자 한다면 프로그램을 다시 실행해야 한다
- 리버싱은 프로그램의 동작 방식에 대한 하나의 가정을 세우고, 그것이 맞는지 확인해가며 구조를 알아내는 것이므로 가정이 틀렸을 시 계속 다시 시작해가면서 맞춰가는 것이 중요하다
- x32dbg 에서의 단축키는
Ctrl + F2
1.7. MessageBox() 함수 구조
- abex crackme 1
0040100E
에서 USER32.dll
의 MessageBoxA()
호출하고 있다
int WINAPI MessageBox(
_In_opt_ HWND hWnd,
_In_opt_ LPCTSTR 1pText,
_In_opt_ LPCTSTR 1pCaption,
_In_ UINT uType
);
PUSH OFFSET 00402000
- 메모리 주소
00402000
에 있는 데이터를 스택에 넣는다
- 메모리에 있는 문자열을 읽을 때는 널(NULL)을 의미하는 ‘00’이 나오기 전까지 연속적으로 읽는다
- 메모리에 있는 데이터를 보려면 메모리 덤프 기능 사용하면 된다
1.8. 서브루틴과 스택 프레임
- 서브루틴(Subroutine)은 전형적인 스택 프레임(Stack Frame)구조로 되어 있다
0040100E
에서 서브루틴 안으로 들어간다
00401013
(복귀주소 =서브루틴을 호출하는 코드 바로 다음에 오는 명령어를 가리키는 주소) 를 스택에 백업
- 서브루틴 내부
PUSH EBP
- 이전 루틴에서 사용하고 있는 스택 베이스를 스택에 백업
- 이전 루틴으로 복귀할 때 해당 값을 EBP 레지스터로 복구
MOV EBP, ESP
- 현재 스택의 최상위 주소를 EBP 레지스터에 입력하는 명령어
- EBP
- 스택 베이스
- 서브루틴에서 사용하고 있는 스택 영역에 있는 값을 참조할 떄 기준이 되는 주소
RETN 10
- 서브루틴이 종료하면 ESP 레지스터가 가리키는 주소로 이동
10(16) = 16
- 16 Byte를 반환해야 한다
- 서브루틴을 호출하면서 PUSH 명령어를 통해 인자로 넣어준 값들
- 복귀 주소로 돌아가면서 시스템으로 반환된다
1.9. 프로그램 구조 분석
GetDriveTypeA()
함수 호출
- KERNEL32 DLL에서 제공하는
GetDriveTypeA()
- 드라이브 종류를 정수 형태로 반환
- 함수의 반환값은 EAX에 저장
- INC, DEC
INC
: 레지스터의 값을 1 증가
DEC
: 레지스터의 값을 1 감소
- 비교문과 점프문
CMP
: 뒤에 오는 두 레지스터에 저장된 값이 같으면 ZF = 1
JE (Jump Equal)
: 앞에 수행한 비교 구문의 결과가
- 같으면 (ZF == 1) 지정된 주소로 점프
- 다르면 다음 행을 실행
1.10. 문제 해결
두 가지 문제 풀이 방법이 존재한다
- 프로그램 코드 변경하기
- 플래그 값을 변경해서 실행 시간에 동작의 흐름 바꾸기
프로그램 코드 변경하기
- 실행 파일을 영구적으로 바꿀 수 잇는 방법
- 코드를 변경할 때
<Assemble>
메뉴에서 프로그램을 수정
- 코드를 수정할 때에는 절대 주소를 입력할 것
- x32dbg에서의 단축키는 <Space>

- 기존의 코드는 레지스터에 저장된 값과 비교하여 일치했을 때만 성공메시지를 띄운다는 것을 알 수 있다
je
코드를 그냥 해당 지점으로 jmp
하는 코드로 바꿔버리면 어떤 값이 있던 상관없이 성공 메시지를 띄우는 곳으로 보내버릴 수 있다

- 패치 후에 실행하면 성공 메시지를 확인할 수 있다
플래그 값을 변경해서 실행 시간에 동작의 흐름 바꾸기
- 프로그램이 동작하는 방식을 알아볼 때 좋은 방법
- 실행할 때마다 성공 조건을 체크하는 점프문에서 제로 플래그를 변경해주어야 한다
- 우선 조건을 검사해서 Z FLAG에 결과를 저장하고 난 직후에 BP를 걸고 실행시켜서 멈추게 한다
- 레지스터
ZF
플래그를 두 번 클릭하면 수정할 수 있게 된다
- 이 때
ZF
를 1
로 수정해서 원하는 곳으로 점프할 수 있게 변경했더니 성공