02 리버싱 시작하기 - 1. abex crackme 첫 번째 예제

HnBrd·2023년 5월 31일
0

리버싱 입문

목록 보기
2/4
post-thumbnail

1. abex crackme 첫 번째 예제

  • 알아볼 내용
    • 디버거의 기능
      • 스텝 오버
      • 스텝 인투
      • 브레이크포인트
      • 엔트리포인트
    • 리버싱의 핵심 지식
      • 스택
      • 함수 호출 구조
      • 서브루틴
      • 스택 프레임

1.1. 프로그램 동작 방식

  • abex crackme 첫 예제
    - 프로그램을 실행했을 때 하드디스크가 CD롬으로 인식되도록 변경하는 것이 목표

1.2. 엔트리 포인트

  • 엔트리 포인트 (Entry Point, a.k.a EP)

    • 디버거를 통해 프로그램을 열었을 때 멈추는 특정 지점
    • 운영체제가 사용자 프로그램으로 최초로 제어를 넘기는 지점
    • 프로그래머가 만든 실행 코드가 최초로 실행되는 지점
    • PE 파일의 헤더 영역에 엔트리 포인트가 상대 주소(RVA)로 지정되어 있음
      • 실제로 저장되는 위치는?
        • Image Base = 00400000
        • Address of Entry Point = 000010000
        • 실제로 저장되는 위치 = Base Address + RVA = 00401000
  • 프로그램의 길이는 00401000~004001061 = 6 Byte의 코드

  • 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.dllMessageBoxA() 호출하고 있다
  • MessageBox()의 구조
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 레지스터가 가리키는 주소로 이동
        • ESP = 스택 복귀 주소 0018FF78
      • 10(16) = 16
        • 16 Byte를 반환해야 한다
        • 서브루틴을 호출하면서 PUSH 명령어를 통해 인자로 넣어준 값들
        • 복귀 주소로 돌아가면서 시스템으로 반환된다

1.9. 프로그램 구조 분석

  • GetDriveTypeA() 함수 호출
    • KERNEL32 DLL에서 제공하는 GetDriveTypeA()
    • 드라이브 종류를 정수 형태로 반환
    • 함수의 반환값은 EAX에 저장
      • 반환값 3 = HDD (MSDN 참조)
  • INC, DEC
    • INC : 레지스터의 값을 1 증가
    • DEC : 레지스터의 값을 1 감소
  • 비교문과 점프문
    • CMP : 뒤에 오는 두 레지스터에 저장된 값이 같으면 ZF = 1
    • JE (Jump Equal) : 앞에 수행한 비교 구문의 결과가
      • 같으면 (ZF == 1) 지정된 주소로 점프
      • 다르면 다음 행을 실행


1.10. 문제 해결

두 가지 문제 풀이 방법이 존재한다

  1. 프로그램 코드 변경하기
  2. 플래그 값을 변경해서 실행 시간에 동작의 흐름 바꾸기

프로그램 코드 변경하기

  • 실행 파일을 영구적으로 바꿀 수 잇는 방법
  • 코드를 변경할 때 <Assemble> 메뉴에서 프로그램을 수정
    - 코드를 수정할 때에는 절대 주소를 입력할 것
    - x32dbg에서의 단축키는 <Space>
  • 기존의 코드는 레지스터에 저장된 값과 비교하여 일치했을 때만 성공메시지를 띄운다는 것을 알 수 있다
  • je 코드를 그냥 해당 지점으로 jmp하는 코드로 바꿔버리면 어떤 값이 있던 상관없이 성공 메시지를 띄우는 곳으로 보내버릴 수 있다

  • 패치 후에 실행하면 성공 메시지를 확인할 수 있다

플래그 값을 변경해서 실행 시간에 동작의 흐름 바꾸기

  • 프로그램이 동작하는 방식을 알아볼 때 좋은 방법
  • 실행할 때마다 성공 조건을 체크하는 점프문에서 제로 플래그를 변경해주어야 한다

  • 우선 조건을 검사해서 Z FLAG에 결과를 저장하고 난 직후에 BP를 걸고 실행시켜서 멈추게 한다
  • 레지스터 ZF플래그를 두 번 클릭하면 수정할 수 있게 된다
  • 이 때 ZF1로 수정해서 원하는 곳으로 점프할 수 있게 변경했더니 성공

profile
잡식

0개의 댓글