본문 바로가기

2. Reversing (리버싱)/1) Write UP

[2023.04.01] 씽씽이 활동보고

활용 강의: 리버싱 이 정도는 알아야지 - 섹션 1

 

 

※ PE 파일 분석 공부 방향 ※
1. PE 헤더는 어떻게 구성되어 있고, 그 값들이 의미하는 바가 무엇인지 알아야 한다.
2. .text 섹션에 기록되는 명령코드를 읽을 줄 알아야 한다.
3. .data 섹션에 기록되는 데이터가 무슨 값인지 알아야 한다.

 

1. PE File Format 이해하기

 

● PE 파일 포맷
: 마이크로소프트에서 실행파일에 대한 원칙을 정해놓은 것
● PE 파일 생성 과정
: 일반적으로 Visual Studio와 같은 개발 도구 사용 -> 코드 작성 -> 컴파일 -> 실행파일 완성

● 컴파일 과정
1. 코드(.text 섹션에 기록)와 데이터(.data 섹션에 기록) 분리한다.
2. 코드를 기계어로 변환한다.
3. 문자열 데이터를 아스키 값으로 바꾼다.
4. 명령코드와 아스키 값을 담기 위한 틀을 만든다.
5. PE 헤더를 채운다. (실행하는데 필요한 정보를 기록해 놓음)
6. 명령코드를 채운다.
7. 데이터를 채운다.

2. PE 헤더 훑어보기

 

● PE 헤더

: 크게 DOS Header, DOS Stub, NT Header, Section Header로 구성

- DOS Heaser: DOS와 호환성을 위해 만들어졌으며 파일의 처음에 위치하고 0x40 크기를 가진다.

- DOS Stub: PE 파일이 MS-DOS에서 실행 될 경우, 화면에 출력될 메세지와 코드가 기록되어 있다. DOS Stub은 옵션이다.(필수x)

- NT Header: 파일 실행에 필요한 전반적인 정보를 가지고 있다. 0xF8 크기를 가진다.

-Section Header: 각 섹션의 속성 정보를 가지고 있다.

 

PE 파일이 메모리에 로딩되었을 때 각 섹션에서 메모리 주소(RVA)와 파일 옵셋(RAW)을 매핑할 수 있어야 한다.

=> FileOffset & RVA (RVA to RAW)

 

● FileOffset & RVA

데이터의 위치를 표현하는 방식

-  FileOffset(RAW): 파일에서의 위치

-  RVA: 메모리에서의 상대 위치. 특정한 위치로부터 얼마만큼 떨어진 곳에 위치하는지 알려주는 주소.

주로 파일 데이터가 프로세스 상태에서는 어디에 위치하는지 찾을 때 활용

-  기본적으로 파일이 메모리에 올라가는 위치에 따라 데이터의 위치는 달라질 수밖에 없다. 그리고 파일에서의 위치와 메모리에서의 상대 위치에 차이가 있을 수도 있다.

≫  따라서 상대위치를 사용.

 

 RVA to RAW 방법

(1) RVA가 속한 섹션을 찾는다.

(2) 아래의 비례식을 사용해 파일 옵셋(RAW)을 계산한다.

 

- VirutalSize : 메모리에서 섹션이 차지하는 크기
- VirutalAddress(RVA) : 메모리에서 섹션의 시작 주소
- SizeOfRawData : 파일에서 섹션이 차지하는 크기
- PointerToRawData : 파일에서 섹션의 시작 주소

 

● 비례식

RAW - PointerToRawData = RVA - VirtualAddress
RAW = RVA - VirtualAddress + PointerToRawData

 

● IAT EAT

IAT (Import Address Table)

: DLL(Dynamic Linked Library)이 제공하는 함수들 중에서 사용하는 것들에 대한 정보를 기술해 놓은 테이블

 

-  EAT (Export Address Table)

: DLL(Dynamic Linked Library) 자신이 서비스하는 함수에 대한 정보를 기술해 놓은 테이블

ex) Sample 01.exe 파일의 Beep API 호출

     -  Sample 01.exe를 실행하면, PE 로더가 Sample 01.exe를 메모리에 올리면서 Kernel32.dll도 같이 올린다.

     -  그 과정에서 Kernel32.dll EAT로 가서 Beep API의 실제 호출 주소를 알아올 것이다.

     -  그리고 이것을 Sample 01.exeIAT에 기록한다.

Beep() API 호출 코드
Sample01.exe의 IAT
실습화면

 

3. PE 파일 만들기 | 01 헤더

 

[ DOS Header ] : DOS와의 호환성을 위해 만들어졌으며 파일의 처음에 위치한다.

  1. DOS signature : PE 헤더의 첫 번째 값은 DOS signature이며, “0x4D5A”의 값을 가진다.

  2. NT Header Offset : NT 헤더의 시작 지점에 대한 정보를 가지고 있으며, “0x50”의 값을 가진다.

[ DOS Stub ] : 16비트 명령코드와 데이터의 혼합으로 이루어져 있다. DOS Stub의 존재 여부는 옵션이고, 크기도 일정하지 않다. (존재하지 않아도 파일 실행에는 문제가 없다.)

[ NT Header ] : 파일 실행에 필요한 전반적인 정보를 가지고 있다.

1. PE Signature : NT 헤더의 첫 번째 값은 PE Signature이며, 해당 파일이 PE 파일임을 명시한다. 이때 “0x50450000” 값을 가진다.

 

2. File Header : 파일의 물리 정보를 가지고 있으며 “IMAGE_FILE_HEADER” 구조체에 값을 채워서 만들어진다.

Machine : 파일이 실행될 CPU 타입의 정보를 가지고 있다. 이때, 해당 프로그램은 intel 32비트의 환경에서 실행되므로 “0x014C”의 값을 가진다.

Number of sections : 섹션 개수 정보를 가지고 있다. 해당 프로그램에는 2개를 갖고 있으므로(.text 섹션과 .data 섹션) “0x02” 값을 가진다.

Size of optional header : Optional header의 크기 값을 가지고 있으며 일반적으로 “0xE0” 값을 가진다.

Characteristics : 파일의 속성 정보를 가지고 있다. 이때, 32bit 실행 파일(0x100)과 실행 가능한 파일(0x0002)값을 포함한 값인 “0x01F0”을 준다.

 

 

3. Optional Header : 파일의 논리 정보를 갖고 있다. “IMAGE_OPTIONAL_HEADER32” 구조체에 값을 채워서 만들어진다.

- Magic : 해당 프로그램의 Optional headerIMAGE_OPTIONAL_HEADER32 구조체이므로 “0x010B”의 값을 가진다.

- Address of Entry Point : 코드 시작 주소는 텍스트 섹션 시작 주소

- Image Base : exe 파일은 기본적으로 “0x00400000” 값을 가진다.

- Section Alignment : 섹션의 최소 단위를 나타낸다. 해당 프로그램에선 Section AlignmentFile Alignment의 값을 “0x0100”으로 부여한다.

- File Alignment : 메모리 섹션의 최소 단위를 나타낸다. 해당 프로그램에선 Section AlignmentFile Alignment의 값을 “0x0100”으로 부여한다.

- Major O/S Version : “0x04”의 값을 부여한다.

- Size of Image : 메모리에 올라간 파일 데이터의 크기 값을 나타낸다. 해당 프로그램에선 “0x0400”의 값을 부여한다.

- Size of Header : PE 헤더의 크기는 “0x0000~0x0198”이므로 “0x00000198” 값을 가진다.

- Subsystem : 서브 시스템의 값을 나타낸다. 해당 프로그램에선 “0x02”의 값을 준다.

- Number of Data Directories : Data Directory의 개수를 나타낸다. 해당 프로그램에선 10개의 Directory를 갖고 있으므로 “0x10”의 값을 준다.

- EAT RVA & EAT size : 해당 프로그램에선 0으로 값을 채운다.

- IAT RVA : RVA의 정보를 .data Section에 저장하기 위해 “0x300”의 값을 준다.

- IAT Size : 해당 프로그램에선 “0x32”의 값을 준다.

 

 

 

4. PE 파일 만들기 | 02 섹션 헤더

 

[ Section Header ] : PE 파일이 가지는 섹션 수만큼 존재한다.

1. .text Section Header

 

- Section Name : 섹션의 이름 정보를 가지고 있다. .text Section“0X2E, 0X74, 0X65, 0X78, 0X74”의 값을 가진다.

VirtualSize : 메모리에서 섹션이 차지하는 크기이다. “0x100”의 값을 가진다.

- RVA(VirtualAddress) : 메모리에서 섹션의 시작 주소이다. “0x200”의 값을 가진다.

- SizeOfRawData : 파일에서 섹션이 차지하는 크기이다. “0x200~0x2FF”를 차지하므로 “0x100”의 값을 가진다.

- OffsetToRawData : 파일에서 섹션의 시작 주소이다. “0x200”의 값을 가진다.

- Characteristics : 섹션의 속성 정보를 가지고 있다. .text Section의 속성은 CODE, EXCUTE, READ가 있으며 “0x60000020”의 값을 가진다.

 

2. .data Section Header

- Section Name : 섹션의 이름 정보를 가지고 있다. .data Section“0X2E, 0X64, 0X61, 0X74, 0X61”의 값을 가진다.

- VirtualSize : 메모리에서 섹션이 차지하는 크기이다. “0x100”의 값을 가진다.

- RVA(VirtualAddress) : 메모리에서 섹션의 시작 주소이다. “0x300”의 값을 가진다.

- SizeOfRawData : 파일에서 섹션이 차지하는 크기이다. “0x300~0x3FF”를 차지하므로 “0x100”의 값을 가진다.

- OffsetToRawData : 파일에서 섹션의 시작 주소이다. “0x300”의 값을 가진다.

- Characteristics : 섹션의 속성 정보를 가지고 있다. .data Section의 속성은 CODE, EXCUTE, READ가 있으며 “0xC0000040”의 값을 가진다.

 
 

 

[ PE 헤더 완성 ]

 
 

5. PE 파일 만들기 | 03 data 섹션

 

IAT* 정보와 문자열 데이터를 채워보자.

*IAT(Import Address Table) : DLL이 제공하는 함수들 중에서 사용하는 것들에 대한 정보를 기술해 놓은 테이블

 

● PE 로더

- PE 파일을 메모리에 올리는 주체이다.

- API 호출 주소를 알아온다.

- 알아온 API 주서를 IAT에 기록한다.

 

[ exe 실행파일을 만들기 위한 준비물 ]

1. 자신이 필요로 하는 DLL을 명시해둬야 한다.

2. 필요한 API 리스트도 가지고 있어야 한다.

3. PE 로더가 알아낸 API 주소를 기록할 수 있도록 공간을 만들어놓아야 한다.

-> IMPORT Table

 

- Import Directory Table : 필요한 DLLAPI에 대한 포괄적인 정보들을 담고 있다.

- Import Address Table : API 호출 주소 정보들을 기록할 수 있게 마련된 공간이다.

- Import Name Table : API의 문자열 위치 정보들을 가진다.

- API DLL 이름 정보 : 실질적인 APIDLL의 이름 문자열 값을 가진다.

 

1. IAT 정보 채우기

- NULL : 여기가 끝이라는 표식

- DLL Name RVA : 0x37A

- KERNEL32.DLL 문자열 값 : 0x4B 0x45 0x52 0x4E 0x4C 0x33 0x32 0x2E 0x44 0x4C 0x4C

- Import Name Table RVA : 0x354

- Index + Beep : 0x00 0x00 0x42 0x65 0x65 0x70

- Import Address Table RVA : 0x33C

- DLL Name RVA : 0x399

- USER32.DLL 문자열 값 : 0x55 0x53 0x45 0x52 0x33 0x32 0x2E 0x44 0x4C 0x4C

- Import Name Table RVA : 0x35C

- Import Address Table RVA : 0x348

 

2) 문자 데이터 - 메시지 박스 호출

 

6. PE 파일 만들기 | 04 text 섹션

 

header에는 정해진 값을 채웠고, data섹션에는 아스키값을 채웠다.

우리는 Code 섹션 채울 기계어는 알지 못한다. 때문에 Sample01를 참고해서 .data 섹션을 채울 것이다.

 

1. Sample 01.exe를 디버거에 올린다.

 

2. 아래 Sample 01.exe 코드에서 빨간색 부분은 비워둔다. (파일에 따라 달라지는 값이 있기 때문)

참고사항※

- Beep 호출 정보가 있는 IAT 주소

- MessageBoxA 세번째 인자의 위치주소

- MessageBoxA 두번째 인자의 위치주소

- MessageBoxA 호출 정보가 있는 IAT 주소

 

3. Challenge 01.exe 파일을 디버거에 올린다.

 

4. 코드섹션 채우기

- 데이터 셋을 파일 형태로 변환한다. (마우스 우클릭 > view> Excutable file )

- 어셈블리 코드 수정 (마우스 우클릭 > Assemble )

- 기본틀 먼저 채워준 후에 파일에 따라 달라지는 값을 채워준다.

 

(1) Beep 호출 정보가 있는 IAT 주소 : ImageBase + 0x33c : 0x0040033c1 

 

(2) MessageBoxA 세번째 인자의 위치주소 : ImageBase + 0x3D0 : 0x004003D0

 

(3) MessageBoxA 두번째 인자의 위치주소 : ImageBase + 0x3B0 : 0x4003B0

 

(4) MessageBoxA호출 정보가 있는 IAT 주소 : ImageBase + 0x348 : 0x400348

 

(5) 코드를 다 채운 후 파일을 저장한다.(마우스 우클릭> save file)