'분석'에 해당되는 글 7건

  1. 2017.01.30 리틀엔디언 VS 빅엔디언
  2. 2017.01.29 어셈블리어(Assembly) 기초 5
  3. 2017.01.21 RVA to RAW(PE File Format)
  4. 2017.01.21 Hello World 디버깅 실습
  5. 2017.01.19 Crackme 두번째 관문
  6. 2017.01.19 Crackme 첫번째 관문
  7. 2017.01.19 PE File Format에대해 알아보겠습니다. 1

리틀엔디언 VS 빅엔디언

|

엔디언이란 무엇인가?

 

엔디언(Endianness) 컴퓨터의  메모리와 같은 1차원의 공간에 여러 개의 연속된 대상을 배열하는 방법을 뜻하며바이트를 배열하는 방법을 특히  바이트 순서(Byte order)라 한다.

엔디언은 보통 큰 단위가 앞에 나오는 빅 엔디언(Big-endian)과 작은 단위가 앞에 나오는  리틀 엔디언(Little-endian)으로 나눌 수 있으며, 두 경우에 속하지 않거나 둘을 모두 지원하는 것을  미들 엔디언(Middle-endian)이라 부르기도 한다.

빅 엔디안은 최상위 바이트(MSB - Most Signficant Byte)부터 차례로 저장하는 방식이며리틀 엔디안은 최 하위 바이트(LSB - Least Significant Byte) 부터 차례로 저장하는 방식이다.

 

그렇다면 최 상위와 최 하위라는 것은?

 


 

뭔가 공감이 되지 않는 설명인데, 이런 설명이 조금 더 씌여있다.

빅 엔디언은 사람이 숫자를 쓰는 방법과 같이 큰 단위의 바이트가 앞에 오는 방법이고

리틀 엔디언은 반대로 작은 단위의 바이트가 앞에 오는 방법이다.

빅 엔디언    <- 높은 주소 - - - - 낮은 주소 - >

리틀 엔디언 <- 낮은 주소 - - - - 높은 주소 - >



 

즉 단순히 1234567887654321과 같이 거꾸로 저장된다기 보다는..

해당 수치를 데이터의 단위 단위로 나누었을 때 그 단위가 거꾸로 배열되는 형태인듯 하다.

 



메모리의 0에서부터 끝으로 쓰는 방식이 빅 엔디안.

메모리의 끝에서부터 0으로 쓰는 방식이 리틀 엔디안이라 할 수 있다.

 

오늘날 x86 아키텍처를 사용하는 대부분의 데스크톱 컴퓨터는 리틀 엔디언을 쓰며 이를 인텔 포맷이라 한다. 거꾸로  네트워크에서는 주소를 빅 엔디언으로 쓰는데, 역사적으로  라우팅이 전화를 거는 식으로  접두 부호로 이루어졌기 때문이다.

 

장단점

빅 엔디언은 소프트웨어의 디버그 를 편하게 해 주는 경향이 있다사람이 숫자를 읽고 쓰는 방법과 같기 때문에 디버깅 과정에서 메모리의 값을 보기 편한데, 예를 들어 0x59654148은 빅 엔디언으로  59 65 41 48로 표현된다.

반대로 리틀 엔디언은  메모리에 저장된 값의 하위 바이트들만 사용할 때 별도의 계산이 필요 없다는 장점이 있다. 예를 들어, 32비트 숫자인 0x2A는 리틀 엔디언으로 표현하면  2A 00 00 00 이 되는데, 이 표현에서 앞의 두 바이트 또는 한 바이트만 떼어 내면 하위 16비트 또는 8비트를 바로 얻을 수 있다반면 32비트 빅 엔디언 환경에서는 하위 16비트나 8비트 값을 얻기 위해서는 변수 주소에 2바이트 또는 3바이트를 더해야 한다 보통 변수의 첫 바이트를 그 변수의 주소로 삼기 때문에 이런 성질은 종종 프로그래밍을 편하게 하는 반면, 리틀 엔디언 환경의 프로그래머가 빅 엔디언 환경에서 종종 실수를 일으키는 한 이유이기도 하다.

또한 가산기가 덧셈을 하는 과정은 LSB로부터 시작하여 자리 올림을 계산해야 하므로, 첫 번째 바이트가 LSB인 리틀 엔디언에서는 가산기 설계가 조금 더 단순해진다. 빅 엔디언에서는 가산기가 덧셈을 할때 마지막 바이트로부터 시작하여 첫 번째 바이트까지 역방향으로 진행해야 한다. 그러나 오늘날의 프로세서는 여러개의 바이트를 동시에 읽어들여 동시에 덧셈을 수행하는 구조를 갖고 있어 두 엔디언 사이에 사실상 차이가 없다.

 

엔디안 방식이 중요해지는 것은 네트워크인데.데이터 전송을 할 때 엔디안  방식의 차이에 주의해야 한다. 서로 다른 데이터 저장 방식의 시스템끼리 통신하게 되면 전혀 엉뚱한 값을 
주고받기 때문이다

네트웍 데이터 통신에서는 네트워크 바이트 순서(network byte order, 빅 엔디안) 
따르도록 데이터의 바이트 순서를 변경해야 한다

(TCP/IP, XNS, SNA 규약은 16비트와 32비트 정수에서 빅 엔디안 방식을 사용함

이럴 때에 htonl 같은 함수(host to network)를 이용해서 바이트 순서를 정렬시켜주면 된다.

 

 

몇몇 아키텍처는 빅 엔디언과 리틀 엔디언 중 하나를 선택할 수 있도록 설계되어 있고, 이를 바이 엔디언(Bi-endian)이라 부른다.

종종 한 방향으로 순서가 정해져 있는 게 아니라, 이를테면 32비트 정수가 2바이트 단위로는 빅 엔디언이고 그 안에서 1바이트 단위로는 리틀 엔디언인 경우가 종종 있는데 이를  미들 엔디언(Middle-endian)이라 한다.

 

둘이 차이점은 계산할때와 비교할 때 눈에 띈다.

 빅 엔디언을 통한 숫자 비교시숫자의 비교는 가장 큰 값이 들어가는 왼쪽부터 하게 되는데 엔디언은  수치를 앞에서부터 차곡차곡 스택에 집어 넣는 반면  리틀  엔디언으로 하게된다면 리틀  엔디언은 숫자 뒤에서부터 스택에 집어 넣기 때문이 빅  엔디언보다 속도가 느리다.


그러나 수치 계산시에는  리틀 엔디안이 빅 엔디안보다 속도가 더 빠른데  두 숫자를 계산을 할 경우 가장 낮은 자리수에 있는 숫자를 계산을 해보고서 자리 올림수가 있는지 없는지 판단을 하고서 자리 올림수와 다음 숫자를 계산을 하기 때문이다.

  

 엔디언은 UNIX 에서 사용하는 RISC 프로세서에서 사용 하는 바이트 오더, 동시에 소켓 프로그래밍에서 중요한 네트워크 바이트 오더리틀  엔디언은 Intel 계열의 프로세서에서 사용하는 바이트 오더이다.

 

빅 엔디안

리틀 엔디안 

Unix RISC계열의 프로세서가 사용하는 바이트 오더링

네트워크에서 사용하는 바이트 오더링

앞에서부터 스택에 PUSH

비교연산에서 리틀 엔디안보다 속도가 빠름

Intel 계열의 프로세서가 사용하는 바이트 오더링

 

뒤에서부터 스택에 PUSH

계산연산에서 빅 엔디안보다 속도가 빠름

 

출처 및 참고 

: 위키백과

script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js">

'Reverse Engineering' 카테고리의 다른 글

(Themida) 디버깅 경고 메시지 출력 해결 방법!!  (4) 2017.01.31
어셈블리어(Assembly) 기초  (5) 2017.01.29
RVA to RAW(PE File Format)  (0) 2017.01.21
Hello World 디버깅 실습  (0) 2017.01.21
Crackme 두번째 관문  (0) 2017.01.19
And

어셈블리어(Assembly) 기초

|







어셈블리어(Assembly) 입문자를 위한 어셈블리어 기초



## 목차 ##


0x01. 어셈블리 언어란? & 배우는 목적

0x02. 어셈블리 언어를 위한 기본 지식

0x03. 어셈블리 명령어의 구성

0x04. 주소지정방식의 이해

0x05. 어셈블리 명령어 정리

0x06. 어셈블리 명령어 상세

0x07. 참조 사이트 및 문서




0x01. 어셈블리 언어란? & 배우는 목적


CPU 에는 해당 프로세서에 명령을 내리기 위해 고유의 명령어 세트가 마련되어 있는데 이 명령어 세트를 기계어라고 한다. 이 기계어는 숫자들의 규칙조합임으로 프로그래밍에 상당히 난해하다. 그래서 이 기계 명령어를 좀더 이해하기 쉬운 기호 코드로 나타낸것(기계어와 1:1로 대응된 명령을 기술하는 언어)이 어셈블리어이다. 어셈블리 언어는 그 코드가 어떤 일을 할지를 추상적이 아닌, 직접적으로 보여준다. 논리상의 오류나, 수행 속도, 수행 과정에 대해 명확히 해준다는 점에서 직관적인 언어이다. 어셈블리 언어를 사용하면 메모리에대한 이해도도 높아진다. 어셈블리를 익히고, 배우는데 있어서는 여러 가지 목적이 있을 수있다. 컴퓨터 시스템&구조를 좀 더 깊게 이해하고, 메모리상의 데이터나 I/O기기를 직접 액세스 하는등의 고급언어에서는 할 수 없는 조작을위해서이다. 프로그램의 최적화 및 리버스 엔지니어링을 위해서도 필요하다.


+ 2줄 요약 +


- 어셈블리 언어는 기계어와 1:1 대응을 하는 언어이다.

- 어셈블리 언어를 배우면 시스템을 이해하는데 도움이 된다.



0x02. 어셈블리를 위한 기본 지식


(1) 기본적인 하드웨어


1) CPU


- 메모리에 있는 내용을 읽고, 쓰고 데이터를 메모리와 각 레지스터로 보낸다.프로그램의 명령을 해석하고 실행한다.하나의 프로세서는 12~14개의 레지스터를 가지고 있으며,CPU의 연산, 논리 장치는 숫자와 기호에 관한 연산자를 인식한다. 보통 이러한 장치들은 기본적인 연산만을 수행할 수 있다(덧셈, 뺄셈, 곱셈, 나눗셈, 숫 자비교). 퍼스널 컴퓨터는 한번에 처리할 수있는 비트수(Word) 따라서 분류된다.


2) RAM


- 반도체로 조립된 셀들의 집합. 프로세스가 프로그램을 실행시키고 작동하기위해서 필요한

정보들을 저장하는데 쓰인다. 각각의 셀들은 숫자값을 포함하고 주소가 정해질 수 있는 형

식이며 프로그램에서 흔히 메모리라고 하는 것들은 메인메모리, , 램이라고 할 수 있다.


(2) 80x86 프로세서


1) CPU 레지스터 종류 : 범용 레지스터, 상태 레지스터, 플래그 레지스터

 - 레지스터 : CPU내부의 기억장소로 PC가 정보를 처리하기 위해서는 정보가 특정한 셀에 저장되어 있어야 한다. 이러한 셀을 레지스터라고 불린다. 레지스터들은 8또는 16비트 플립-플롭 회로들의 집합이다. 플립-플롭 회로란 두 단계의 전압으로 정보를 저장할 수 있는 장치이다. 낮은 전압은 0.5 볼트이고 높은 전압은 5볼트이다. 낮은단계의 에너지는 0으로 해석되고 높은 전압은 1. 이 상태는 보통 비트로 불리며 컴퓨터의 가장 작은 정보 단위이다.






< 레지스터의 구조 >


데이터 레지스터

- 데이터 레지스터는 각종 데이터 처리를 대상으로 하는 하는 32비트 레지스터 및 16비트 레지스터 일부를 프로그래머가 명령 중에서 자유롭게 지정을 할 수 있는 범용 레지스터이다.

: EAX, EBX, ECX ,EDX


포인터 레지스터

: ESP, EBP


인덱스 레지스터 (Index register)

: ESI, EDI


세그먼트 레지스터 (segment register)

: CS, DS, SS, ES



+ 범용 레지스터 +

32Bit 16Bit 상위8Bit 하위8Bit

기능

EAX AX AH AL 누산기(Accumulator, 중간 결과를 저장해 놓음)
레지스터라 불리며, 곱셈이나 나눗셈 연산에 중요하게 사용
EBX BX BH BL 베이스 레지스터라 불리며 메모리 주소 지정시에 사용
ECX CX CH CL 계수기(Counter)레지스터라 불리며 Loop등의 반복 명력에 사용
EDX DX DH DL 데이터(Data)레지스터라 不利며 곱셈, 나눗셈에서EAX함께
쓰이며 부호 확장 명령 등에 사용 
ESI SI     다량의 메모리를 옮기거나 비교할 때 그 소스(Source)의 주소를
가진다
EDI DI     다량의 메모리를 옮기거나 비교할때 그 목적지의 주소를 가리키다.
ESP SP     스택 포인터로 스택의 최종점을 저장한다.
EBP BP    

ESP를 대신해 스택에 저장된 함수의 파라미터 지역 변수의
주소를 가리키는 용도로 사용된다.



+ 세그먼트 레지스터 +

16Bit 기능
ES 보조 세그먼트 레지스터다. 두 곳 이상의 데이터 저장영역을 가리켜야 할 때 DS와 함께 사용된다.
하지만 32Bit 프로그램에서는 DS와 ES가 같은 영역을 가리키고 있기 때문에 굳이 신경 쓰지 않아도 된다.
CS 코드 세그먼트를 가리키는 레지스터. 프로그래머 코드의 시작조소를 가지고 있다.
SS 스택 세그먼트를 가리키는 레지스터. 스택의 시작 주소를 담고 있다.
스택 조작에 의해서 데이터를 처리하는 동작이 이루어 진다.
DS 데이터 세그먼트를 가리키는 레지스터.
프로그래머가 정해놓은 데이터의 시작주소를 담고 있다.
FS

보조 세그먼트 레지스터. FS, GS는 286 이후에 추가된 것으로 운영체제를 작성하는 게 아니라면 없듯이 여겨도 된다.

GS



+ 상태 레지스터+

32Bit 16Bit 기능
EIP IP EIP는 현재 실행되고 있는 프로그램으 ㅣ실행코드가 저장된 메모리의 주소를 가리키는 레지스터로 프로그램의 실행이 진행됨에 따라 자동으로 증가하고 프로그램의 실행 순서가 변경되는 제어문이 실행될때 자동으로 변경괸다. 그래서 직접 접근해서 값을 저장하거나 읽거나 하는 일이 없기 때문에 응용 프로그램에서는 손 댈 일이 없는 레지스터이다.
EFLAGS FLAGS 비타 단위의 플래그 들을 저장하는 레지스터로 아주 특별한 용도로 사용된다.



0x03. 어셈블리 명령어의 구성


- 어셈블리는 어셈블리어라고도 부르는데 이 어셈블리어는 명령어들의 조합이다. 인텔 CPU 안에는 이 명령어들이 회로로 구현되어 있어서 어셈블리 코드를 실행할 수 있다. CPU2진수로 모든 것을 처리하는데 어셈블리 명령어들도 2진수로 되어 있다. 하지만 2진수로 된 것

알아보기가 힘들어 mov, add와 같은 형태로 변환하여 보여진다. 아래 그림을 보자.



명령어 다음에 오는 레지스터 이름이나 값들은 operand라고 한다. mov %eax, %ebx에서 %eax 를 제1오퍼랜드, %ebx를 제2오퍼랜드라고 한다. mov %eax, %ebxC언어로 보면 ebx = eax의 경우와 같다. eax에 저장된 값을 ebx에 할당(assignment)한다.(특정 장소(주로 메모리상에서)에서 특정 장소(주로 레지스터)로 데이터를 읽어 와서 적재(load)).

 ‘L1:‘과 같은 명령은 직접적으로 기계어 코드로 번역되지 않고 분기명령(jmp)등에서 참조될 때에, 번지의 계산에 사용된다.


0x04. 주소 지정 방식의 이해


- 어셈블리는 메모리를 직접 다룰 수 있다는 점에서 우리가 어셈블리를 배우는 큰 이유가 될 수 있다. 이 메모리를 다루기 위해서 다양한  주소 지정방식이 있는데 어떤식으로 주소를 사용하고, 참조하는지 확인할 필요가 있다. 참고로 '0x04'에서 예를 들었던 'mov %eax,%ebx'의 경우는 레지스터 어드레싱(register addressing)이다.


1. 즉시 지정방식(immediate addressing)

 - mov $0x1, %eax  :  eax(16진수)1을 값을 넣는(할당) 방식이다.

 - 이렇게 메모리(기억장치)의 주소의 내용을 꺼내지 않고 직접 값을 대응시키는 방식을 즉 시지정방식이라고 한다.


2. 레지스터 지정방식(register addressing)

 - mov %esp, %ebp : 레지스터 ebp에 레지스터 esp의 값을 넣는다.(할당 개념)

 나중에 알 수 있겠지만, 위의 명령은 스택포인터를 베이스 포인터에 넣는 명령으로 함수가 시작될때 ebp의 값(일종의 시작기준점)을 정하는 명령이다.

 - 레지스터에서 직접 레지스터로 값을 대응시키는 방식을 레지스터 지정방식이라고 한다.

 - 속도는 빠르지만 레지스터의 크기(32비트)로 인해 크기가 제한된다.


3. 직접 주소 지정방식(directly addressing)

 - mov %eax, $0x80482f2 : 주소 0x80482f2에 있는 값을 eax에 할당한다.

- 가장 일반적인 주소지정방식이며, 메모리의 주소를 직접 지정해서 바로 찾아오는 방식이 . eax레지스터에 0x80482f2주소의 내용을 로드(load)한다는 의미이다.


4. 레지스터 간접 주소 지정 방식

 - mov (%ebx), %eax

 : ebx의 값을 주소로 하여(간접적으로) eax레지스터에 할당

 - ‘( )’가 들어간다면 간접 지정이라고 볼 수 있다. ‘( )’의 의미는 괄호 안에 들어간 값의 주소이다.


5. 베이스 상대 주소 지정 방식

 - mov 0x4(%esi), %eax

 : esi레지스터에서 4(byte)를 더한 주소의 값을 eax레지스터에 할당한다.

 - 보통 레지스터의 크기가 4byte이기 때문에 레지스터 다음 주소를 의미한다문자열 열산이나 메모리 블록 전송등에 나오는 방식이다.


0x05. 어셈블리어 명령어 정리



0x06. 어셈블리 명령어 상세


- 명령어의 분류

1) 데이터 이동 : mov, lea

2) 논리, 연산 : add, sub, inc, dec

3) 흐름제어 : cmp,jmp

4) 프로시져 : call, ret

5) 스택조작 : push, pop

6) 인터럽트 : int


1) 데이터 전송

 1. mov (move data)

- 형식 : mov SOURCE, DESTINATION

- 기능 : SOURCE위치에 들어있는 데이터를 복사하여 DESTINATION위치에 저장.

- 원칙 : 메모리와 레지스터(모든 연산은 레지스터에 저장된뒤 이루어진다.) 사이의 데이터 이동, 레지스터와 레지스터 사이의 데이터 이동이나 값을 메모리나 레지스터에 대 입할 때 사용한다. (SOURCEDESTINATION의 크기가 동일해야 한다.)

      ! DESTINATION 레지스터가 CS가 될수 없다.(프로그램실행위치가 변경되기때문)

      (CS의 변경은 int, jmp, call, ret등의 명령으로 가능)

! SOURCEDESTINATION이 전부 메모리를 가르칠수 없다. (설계상 불가능)

! SOURCE가 직접지정방식일경우에는 DESTINATIONCS일 수 없다.


 2. lea

- 형식 : lea SOURCE, DESTINATION

- 기능 : SOURCE OPERAND에서 지정된 주소를 DESTINATION으로 로드한다.

LEA의 주된 용도는 매개변수나 지역변수의 주소를 얻어오는 것이다.

예를 들어 C언어에서 지역변수나 매개변수에 &연산자를 사용한다면 컴파일러는

lea명령어를 생성한다.

- 원칙 : SOURCE OPERAND는 메모리에 위치해야하며, 변경될 주소는 index register나 DESTINATION에 정의된 주소여야 한다.


2) 논리, 연산 : add, sub, inc, dec


1. add

- 형식 : add opr1, opr2

- 기능 : opr2의 내용에 op1의 내용이 더해져서 그 결과를 opr2에 저장.

- 원칙 : ! 두 개의 오퍼랜드 모두에 메모리로 조합되는 것은 불가능.


2. sub (subtract)

- 형식 : sub opr1, opr2

- 기능 : 첫번째 오퍼랜드로 부터 2번째 오퍼랜드 의 내용을 뺀 다음 결과를 첫 번째 오퍼

- 원칙 : ! 메모리끼리는 뺄셈을 할수 없다.


3. inc (Increment)

- 형식 : inc DESTINATION

- 기능 : DESTINATION1 증가시키고 결과값을 다시 저장


4. dec (decrement)

- 형식 : dec DESTINATION

- 기능 : DESTINATION1 감소시키고 결과값을 다시 저장


3) 흐름 제어 : jmp, cmp

- 형식 : jmp proc

- 기능 : 프로그램의 흐름을 바꿀 때 사용. proc의 주소로 가서 그곳의 명령어를 실행.

if/else, loop(루프가 아직 끝나지 않았을때, 처음위치로 돌아가기 위해)

등에서 나타난다.


2. cmp

- 형식 : cmp value, value

ex) cmp %eax, 0 (eax레지스터의 값을 0과 비교한다.)

     je start        (비교 결과가 같다면 start로 분기한다.)

                     (같지 않다면 je 다음에 오는 명령어를 실행한다.)

- 기능 : 두값을 비교하고 비교결과에 따라 분기한다. 보통 레지스터나 메모리 및 숫자의 크기를 비교한다. cmp 명령어는 Zero, Sign, Overflow 등의 플래그를 set or clear 한다. 이 플래그의 결과에 의해서 Jcc 명령어들은 분기할 것인지를 결정한다. 보통 CMP 명령어 다음에 JE, JNE 등의 jmp관련 명령어가 위치한다.

- 원칙 : cmp 명령은 혼자 사용되지 않고 언제나 조건 점프 명령어나 조건 이동(mov) 명령어와 함께 사용된다.

- 조건 점프 명령어 : cmp 명령어의 결과에 따라 점프하는 명령어.



* 참고 *

Unsigned 계열 (부호가 없는 값)

je : jump equal - 비교 결과가 같을 때 점프

jne : jump not equal - 비교 결과가 다를 때 점프

jz : jump zero - 결과가 0일 때 점프, je와 같음. (cmp 명령에서 결과가 같으면 0을 출력)

jnz : jump not zero - 결과가 0이 아닐 때 점프

ja : jump above - cmp a, b에서 a가 클 때 점프

jae : jump above or equal - 크거나 같을 때 점프

jna : jump not above - 크지 않을 때 점프

jnae : jump not above or equal - 크지 않거나 같지 않을 때 점프

jb : jump below - cmp a, b에서 a가 작을 때 점프

jbe : jump below or equal - 작거나 같을 때 점프

jnb : jump not below - 작지 않을 때 점프

jnbe : jump not below or equal - 작지 않거나 같지 않을 때 점프

jc : jump carry - 캐리 플래그가 1일 때 점프

jnc : jump not carry - 캐리 플래그가 0일 때 점프

jnp/jpo : jump not parity / parity odd - 패리티 플래그가 0일 때 / 홀수일 때 점프

jp/jpe : jump parity / parity even - 패리티 플래그가 1일 때 / 짝수일 때 점프

jecxz : jump ecx zero - ecx 레지스터가 0일때 점프

Signed 계열 (부호가 있는 값)

jg : jump greater - cmp a, b에서 a가 클 때 점프

jge : jump greater or equal - 크거나 같을 때 점프

jng : jump not greater - 크지 않을 때 점프

jnge : jump not greater or equal - 크지 않거나 같지 않을 때 점프

jl : jump less - cmp a, b에서 a가 작을 때 점프

jle : jump less or equal - 작거나 같을 때 점프

jnl : jump not less - 작지 않을 때 점프

jnle : jump not less or equal - 작지 않거나 같지 않을 때 점프

jo/jno : jump overflow / not overflow - 오버플로 플래그가 1일 때 / 0일 때 점프

js/jns : jump sign / not sign - 사인(부호) 플래그가 1일 때(음수) / 0일 때(양수) 점프

조건 점프 명령을 조합하여 if ( a > b ), for, while등의 조건문 구현



4) 프로시져 : call, ret


1. call

- 형식 : call Target

- 기능 : 스택 상에서 CS를 다음에 오는 명령의 오프셋 어드레스를 PUSH하고 target으로 이동한다. , 다른 함수로 제어를 옮긴다는 뜻이다. CALL 명령어 다음에 오는 명령어의 주소를 스택에 PUSH하고 주어진 주소로 제어를 옮긴다는 뜻(EIP를 변경시킴)

# CALL명령은 CALL명령 다음위치에 있는 명령의 주소를 스택에 push한다.


2. ret (return)

- 형식 : ret

- 기능 : 호출된 함수에서 호출한 함수로 복귀. esp에 있는 값을 꺼내서(pop) EIP레지스터에 할당한다. , call명령 당시 push되었던 주소를 pop하여 eip에 넣는 것이다.


5) 스택조작 : push, pop


1. pop

- 형식 : pop DESTINATION

- 기능 : 스택 맨 윗부분(top)에서 하나의 워드를 DESTINATION에 로드(load)그리고 스택포인터는 그 바로 전의 데이터를 가리킨다(point).


2. push

- 형식 : push DESTINATION

- 기능 : 메모리상에 설정된 스택이라는 공간에 데이터를 저장한다. 스택의 가장 윗부분에

데이터를 저장. 스택 포인터(esp)도 워드크기만큼 증가한다.


6) 인터럽트 : int

- 형식 : int (interrupt-type)

- 기능 : 운영체제에 할당된 인터럽트 영역을 system call.


0x07.참조 사이트 및 문서


1. “김병희님이 쓰신 문서. (제목이 나와있지 않네요)

2. Lecture Note in Assembly Language Written by Han, Kwang-Rok

3. 블루님의 매크로 어셈 이야기

4. Just Enough Assembly Language to Get By, Part 1,2 by Matt Pietrek

5. 김성훈님의 C 프로그래머가 알아야 할 것들 - Chapter 7 어셈블리

6. 유용수님의 어셈블리 강좌

7. 김영빈님의 헥커가 되자

8. C를 어셈블리어로

9. Assembly Tutor



script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js">

'Reverse Engineering' 카테고리의 다른 글

(Themida) 디버깅 경고 메시지 출력 해결 방법!!  (4) 2017.01.31
리틀엔디언 VS 빅엔디언  (0) 2017.01.30
RVA to RAW(PE File Format)  (0) 2017.01.21
Hello World 디버깅 실습  (0) 2017.01.21
Crackme 두번째 관문  (0) 2017.01.19
And

RVA to RAW(PE File Format)

|




 이번에는 "RVA to RAW"에 대해서 포스팅하도록 하겠습니다. "RVA to RAW"를 간단하게 설명하자면, RVA to RAW 는 PE 파일이 메모리에 로딩되었을 때 각 섹션에서 메모리의 주소(RVA)와 File Offset(RAW)을 매핑하는 것을 말합니다.

   이전 PE File Format 포스팅에서 언급했던 VA와 RVA에 대해서 간단하게 다시 얘기하고 시작하도록 하겠습니다.

   RVA ↔ RAW(File Offset) 변환 작업은 PE 헤더를 공부할 때 가장 기본이 되는 개념이므로 잘 숙지하도록 합시다.



# RVA to RAW


- RVA는 PE 파일이 메모리에 로딩되었을 때의 메모리 주소를 의미하고, RAW는 PE 파일이 로딩되기 전에 File Offset을 의미합니다.

- RVA to RAW는 PE 파일의 각 섹션이 메모리에 로딩되기 전의 File Offset과 메모리에 로딩된 후에 로딩된 위치의 주소를 매칭하는 것입니다.




# RVA to RAW 방법 (매핑하는 방법)


  ① RVA가 속해 있는 섹션을 찾는다.

  ② RAW 구하는 비례식을 이용하여 RAW(File Offset)을 구한다.







# RVA to RAW 매핑하는 방법은 위의 설명 처럼 간단합니다. 익숙해질 수 있도록 연습을 해보도록 하겠습니다.




- 바로 위에 보이시는 그림이 Windows7 운영체제의 Calc,exe 파일이 메모리에 로딩되는 모습을 그려놓은 것입니다. 이그림을 가지고 매핑

  하는 연습을 해보도록 하겠습니다.


- 우선 왼쪽편이 File (Offset) 입니다. 오른쪽편이 Memory 구요. 이전 PE File Format 포스팅에서 언급했었는데, PE File 일 때의 섹션 크기와

  위는 Memory에 로딩되면 각 섹션의 크기와 위치가 달라진다고 했었습니다. ( 크게 변하진 않지만, 동일하진 않다고 했었습니다 )


- 위에 그림에서 보이시는 File의 Offset과 Memory에서의 VA(Virtual Address)는 제가 PE View 툴을 이용하여 다 구한 값들입니다. File에서의

  섹션 크기와 Memory에서의 섹션 크기가 조금씩 다르신게 보이시죠? 앞에서 말했듯이 크게 변하진 않았습니다. 우선 이런점들을 미리 알고 계

  셔야 "RVA to RAW"를 이해하시기 편하실것 같습니다.


- 예제 문제를 풀기전에 VA(Virtual Address)와 RVA(Relative Virtual Address)에 대해서 다시 한번 정리하고 예제 문제를 풀어보도록 하겠습

  니다.





- RVA to RAW 비례식을 이용하여 RAW 주소를 구할 수 있는데요. 이 비례식에 존재하는 VirtualAddress는 우리가 지금껏 말하고 있던 VA

  (Virtual Address)가 아닙니다. VA는 프로세스의 메모리에서의 절대주소라고 설명했습니다. 위에서 RAW(Offset)를 구하기 위한 식에서의

  VA(VirtualAddress)는 Section Header의 멤버인 VirtualAddress를 의미합니다. Section Header 구조체의 멤버인 VirtualAddress 멤버는

  메모리에서 섹션의 시작 주소(RVA)를 의미합니다. 결론적으로 RAW 비례식에서의 VirtualAddress는 우리가 앞에서 계속 얘기하던 RVA

  (프로세스 메모리에서의 상대주소)를 말하는 것입니다. 이 부분만 주의하신다면 쉽게 매핑할 수 있으리라 생각합니다.

  ( 저는 RAW를 구하는 비례식의 VirtualAddress가 프로세스 가상 메모리의 절대주소를 의미하는 것인줄 알고 한참 삽질을 했습니다. )








# 1. RVA = 3000일 때 File Offset (RAW) 은 얼마인가 ?


  - RVA 값이 3000입니다. RVA는 프로세스 가상 메모리에서의 상대주소입니다. 위에 Memory 쪽의 VA를 보시면 제일 처음 "01000000"으로 시작

     하는 것을 볼 수 있는데요. 바로 저 값이 기준 위치가 되는 ImageBase 값입니다. [ ImageBase = 01000000 ]

   

  - 지금부터 RVA to RAW 매핑 방법대로 순서대로 해보도록 하죠. 첫 번째가 주어진 RVA가 속해있는 섹션이 어떤 섹션인지 찾는 것입니다. 

    위 그림을 보시면 메모리상에서 .text 섹션의 시작 RVA가 1000이고, 크기가 52CA1이니까 53CA1까지 .text 섹션이 되겠네요. RVA 3000은 

    .text 섹션에 포함되는 군요. [ RVA = 3000 해당 섹션 → .text 섹션 ]


 - 이제 두 번째 해야할 것이 RAW 비례식을 이용하여 RAW (Offset)을 구하는 것입니다. 비레식을 이용해서 구해보도록 하죠.

   식 : 3000 (RVA) - 1000 (VirtualAddress) + 400 (PointerToRawData) = 2400 (RAW)


 * RAW 비례식에서의 VirtualAddress와 PointerToRawData는 Section Header 구조체의 멤버들로, VirtualAddress는 메모리에서 섹션의 시작

   주소(RVA)를 의미하고, PointerToRawData는 파일에서 섹션의 시작 위치를 의미합니다.



 * RVA to RAW 가 어떤것인지 이제 어느정도 감이 오시죠? 리버싱 관련 작업들을 할 땐 이렇게 RVA에서 RAW로 RAW에서 RVA로 바꾸는 작업이

   가장 기본이 된다고 하네요. 꼭 숙지하도록 해야겠습니다. 예제 하나만 더 풀어보고 이번 포스팅을 마치도록 하겠습니다.




# 2. RVA = BC123일 때 File Offset (RAW) 은 얼마인가 ?


  - RVA = BC123 입니다. 해당 RVA가 속해 있는 섹션을 찾아보니, .reloc 섹션에 포함되어있네요.  [ RVA = BC123 해당 섹션 → .reloc 섹션 ]


  - 식 : BC123 (RVA) - BC000 (VirtualAddress) + B9C00 (PointerToRawData) = B9D23 (RAW)


  - 위에서 처럼 RAW (Offset)을 구한 후에는 실제로 그 Offset에 해당 섹션이 있는지 확인하셔야합니다. 혹시 메모리상에서 포함되었던

    섹션이 아니거나, 벗어난다면 " 해당 RVA에 대한 RAW를 정의할 수 없다" 라고 해야 합니다. 이렇게 딱 떨어지지 않는 경우가 Virtual

    Size와 SizeOfRawData 값이 서로 달라서 벌어지는 일들이라고 하는데요. 이러한 부분은 좀 더 공부를 한 후에 알 수 있다고 하네요.




# "RVA to RAW"에 대해서 알아 봤는데요. 생각보다 쉬운 내용이었던 것 같습니다. 




# 참고 도서 : 리버싱 핵심원리



출처: http://yokang90.tistory.com/21 [yO Kang]

script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js">

'Reverse Engineering' 카테고리의 다른 글

리틀엔디언 VS 빅엔디언  (0) 2017.01.30
어셈블리어(Assembly) 기초  (5) 2017.01.29
Hello World 디버깅 실습  (0) 2017.01.21
Crackme 두번째 관문  (0) 2017.01.19
Crackme 첫번째 관문  (0) 2017.01.19
And

Hello World 디버깅 실습

|

# 안녕하세요. 이번 포스팅에서는 디버깅시에 자주 쓰이는 OllyDbg, IDA Pro, GDB를 이용하여 Window 및 Linux 환경에서 간단한 실행 파일 디버깅해보겠습니다. 리버싱 공부를 시작한지 얼마안되서 틀린 내용이나 부족한 부분이 많을 수 있으니, 그러한 점은 양해부탁드립니다. (틀린 내용이 있을 시 지적해주시면 정말 감사하겠습니다 ^^)



우선 Visual Studio를 이용하여 C 언어로 "Hello World!" 라는 문구를 출력하는 프로그램을 작성했습니다.



[ 그림 1 ] "Hello World!" 문구를 출력하는 콘솔 프로그램 작성





1. 첫 번째로 OllyDbg를 이용하여 디버깅해보도록 하겠습니다.


  

[ 그림 2 ] OllyDbg를 이용하여 "Hello World.exe" 파일 불러오기

- OllyDbg로 앞에서 작성한 "Hello World.exe" 파일을 실행했을 때, 처음 시작하는 부분입니다. (ENTRY POINT)

- 가장 먼저 mainCRTStartup 이라는 부분으로 점프하는 명령어를 볼 수 있는데요. 이 mainCRTstartup 이라는 함수는 저희가 작성한 코드의 main

  함수부분일까요? 해당 명령어를 실행시켜 어떤 함수인지 한번 살펴보도록 하겠습니다.





[ 그림 3 ] mainCRTStartup 함수로 이동

- mainCRTStartup 부분으로 점프한 모습입니다. 대충 명령어들을 살펴보면, security_init_cookie 함수와 tmainCRTStartup 이라는 함수를 호출

  하고, 리턴하는간단한 함수입니다. 호출하는 security_init_cookie 함수와 tmainCRTStartup 함수가 어떤 작업을 하는지 간단하게 확인해보도

  록 합시다.





[ 그림 4 ] security_init_cookie 함수

[ 출처 : http://msdn.microsoft.com/ko-kr/library/ms235362.aspx ]


- 전역 보안 쿠키를 초기화하는 함수라고 합니다. 우선 이러한 함수가 있다는 점으로 보아, 우리가 찾던 C 코드의 main 부분이 아니라는 것을 확실히 알 

  수 있습니다.





[ 그림 5 ] Entry

[ 출처 : http://msdn.microsoft.com/ko-kr/library/f9t8842e.aspx ]


- 디버깅 툴을 이용하여 어떠한 파일을 열었을 때, 시작하는 첫 부분을 Entry Point 라고 합니다. 위의 그림에서 설명하고 있는 Entry 함수에 내용을 

  살펴보니, mainCRTStartup 함수에 관해서 나와있네요. 제가 작성한 프로그램은 CONSOLE 프로그램이므로, mainCRTStartup 함수가 해당 파일의 

  시작점임을 확인할 수 있습니다.


- 참고로 mainCRTStartup 함수는 다음과 같은 일들을 수행합니다.

   

   1. 전체 명령행(commandLine)을 가리키는 포인터를 획득

   2. 환경 변수를 가리키는 포인터를 획득

   3. C/C++ 런타임 라이브러리의 전역 변수를 초기화

   4. C/C++ 런타임 라이브러리의 메모리 할당 함수(malloc, calloc...)와 저수준 입출력 루틴이 사용하는 힙을 초기화

   5. 모든 전역 오브젝트와 static c++ 클래스 오브젝트의 생성자를 호출

   6. 사용자가 정의한 진입점 함수(WinMain 또는 main)를 호출



- 위의 내용들을 정리해보면, 어떠한 프로그램을 디버깅할 때 시작 Entry는 우리가 만든 프로그램의 main 함수가 아닌, 실행전 필요한 작업들을 위한 

  함수들이 먼저 호출된다는 것입니다. 그러한 함수들을 호출한 후, 그 함수들로 하여금 우리가 작성한 main 함수를 호출하게 하는 것입니다.


- 사실 저런 함수들은 실제로 디버깅할 시에는 자세하게 볼 필요가 없을 듯 합니다. 저런 함수가 어떤 기능인지 간단하게 알고 있다면, 다음에 디버깅

  땐 저러한 함수들을 그냥 쉽게 쉽게 넘어가면 될 것 같습니다.



- 이제 우리가 작성한 프로그램의 main 함수를 찾아 보겠습니다.





[ 그림 6 ] main 함수

- mainCRTStartup 함수내에서 실행시키다보면, 쉽게 main 함수를 호출하는 부분을 찾을 수 있을 겁니다. 위 화면은 main 함수를 호출한 모습입니다.

  main 함수 자료형이 int 형이였고, 인자는 void로 작성했는데 첫 째줄 주석부분을 보면 쉽게 확인할 수 있습니다.


- 명령어들을 살펴보면, "Hello World!" 라는 데이터를 입력하기 위해 0C0만큼 할당하는 부분을 볼 수 있고, "Hello World!" 문자열을 인자로 printf

  함수를 호출하는 것을 볼 수 있습니다. 그 이후에 할당했던 스택을 정리하고 리턴, RTC_CheckEsp 함수는 정확하게는 모르겠는데, 대충 함수 

  호출 전후의 ESP를 체크하는 함수로 예상되네요.






2. 두 번째로 IDA를 이용하여 디버깅해보도록 하겠습니다.



[ 그림 7 ] IDA를 이용한 디버깅

- IDA를 이용하여 "Hello World.exe" 파일을 열었을 때 모습입니다. 보시는것 처럼 제가 작성한 코드의 main 함수를 바로 찾아주었네요.

  코드 내용은 차이가 거의 없습니다.


- IDA는 저도 이번에 처음 써보는데요. 정말 좋은 디버깅 툴인것 같습니다. 우선 왼쪽 상단에 보시면 Function name 이라고 해당 프로그램에서

  사용되는 함수들의 이름을 나열해주구요. 저기서 함수명을 더블클릭하면 오른쪽 편에 코드를 바로 찾아서 띄워줍니다. 그 아래에 있는 Graph

  overview 부분은 프로그램의 흐름도를 그림으로 보여주는 건데, 해당 메인 함수의 코드가 너무 간단해서 아무것도 뜨지 않네요.



[ 참고 ] Graph overview

- 이게 바로, Graph overview 기능입니다. 왼쪽에 check_managed_app 함수 부분을 본거에요. OllyDbg로 디버깅할 땐 디버깅하다보면, 어디 

  부분을 디버깅하고 있는지, 헷갈릴 때가 있는데, IDA는 이런 좋은 기능들이 많아서 편하게 디버깅할 수 있을 것 같습니다. 이 외에도 엄청

  나게 많은 기능들이 있는 걸로 아는데, 사용법좀 익힌후 블로깅하도록 하겠습니다.






3. 세 번째로 GDB를 이용하여 디버깅해보도록 하겠습니다.


- 참고 : GDB는 C, C++, Modula-2 로 구현된 프로그램을 디버그할 수 있는 도구입니다.


[ 그림 8 ] 우분투(리눅스)에서 C 코딩

- 우분투에서도, vim 편집기를 이용하여 "Hello World!"라는 문구를 출력하는 프로그램을 작성합니다.





[ 그림 9 ] 작성한 코드 컴파일 후 실행

- 리눅스 환경에서는 gcc 라는 프로그램을 이용하여 코딩한 파일을 컴파일 합니다.

- 작성한 c 파일을 컴파일하고, 실행한 모습입니다. "Hello World!"라는 문구를 출력하는 것을 볼 수 있습니다.

- 이 프로그램을 이제 GDB를 이용하여 디버깅 해보겠습니다.


- 참고 : 저는 gcc로 컴파일할 때 -g 옵션을 주지 않았는데, -g 옵션 주어 컴파일하면 gdb가 필요로 하는 부가 정보들이 추가되어 디버깅할 때

           더 도움이 된다고 합니다.





[ 그림 10 ] GDB를 이용한 디버깅 - 1

- 위 사진을 보시면 우선 첫 째줄에서 'hello' 파일을 gdb로 불러옵니다. 


- (gdb) set disassembly-flavor intel     :  어셈블리 코드를 intel 기반 어셈블리어로 변경하는 작업입니다.

- (gdb) disassemble main                  :  intel 기반으로 변경한 후, main 함수를 보기위한 명령어입니다.


- 보시는 코드가 앞에서 작성한 C 코드의 Main 함수부분에 해당하는 코드입니다. Visual에서 작업했던것보다 더 짧네요.

- 보시면 <+4> 부분에서 edi 레지스터에 0x4005f4 라는 주소를 복사하고 있는데요. 0x4005f4 부분이 아마도 "Hello World!" 

  문자열이 들어가있는 주소로 예상되네요. 그런 후 출력하는 함수를 호출하는 것으로 예상되고, pop으로 할당한 공간을 비우고

  리턴합니다. 


- 그럼 break point를 걸어서 디버깅해보도록 하겠습니다.





[ 그림 11 ] GDB를 이용한 디버깅 - 2

- 우선 break point를 3곳으로 설정하겠습니다. 

   1. 첫번째는 main 함수의 첫번째 줄입니다.

   2. 두번째는 *main + 9 입니다. 이렇게 break point를 걸고, 실행하면 break point가 지정된 코드 직전 코드까지 실행되게 됩니다.

      따라서, *main + 4 까지 실행되는거죠. *main + 4 의 코드가 0x4005f4 라는 주소를 edi 레지스터에 복사하는 것인데 저 레지스터에

      들어가는 주소에 "Hello World!"라는 문구가 있다고 예상했습니다. 그 주소에 있는 문자열을 확인하기 위해 *main + 9 에 break point를

      설정한 것입니다.

   3. 마지막으로 *main+14 입니다. 여기까지 실행하면 중간에 함수를 호출하는 부분을 실행하게 되는데, 예상한 것이 맞다면 "Hello World!"를

      출력할 것입니다. 출력한다면 호출하는 함수는 printf 함수임을 알 수 있습니다.


- break point를 설정한 후 run 명령어를 이용하여 디버깅을 시작합니다. break point에서 멈추는 것이 보이시죠?

  그 다음 break point 직전 코드까지 실행하기 위해서는 continue 명령어를 사용합니다.

  자, 우리가 추측했던 코드를 실행한 후까지 왔습니다. $edi 레지스터에 있는 String을 확인했더니, "Hello World!" 가 정확하네요.

  그런후 한번더 continue 하니 해당 문자열이 출력됩니다. 




# 코드가 간단한 파일이라 정말 쉽게 디버깅할 수 있었는데요. 프로그램이 커질수록 디버깅하기가 정말 힘든것 같습니다. 그리고, 명령어들도 

  많이 알아야 하구요. 명령어나 디버깅 툴에 대한 사용법은 구글링으로 쉽게 찾으실 수 있을 겁니다. 다음에는 조금 더 복잡한 프로그램을 가지고

  디버깅해보도록 하겠습니다. 그럼 이번 포스팅은 여기서 마치겠습니다.



script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js">

'Reverse Engineering' 카테고리의 다른 글

어셈블리어(Assembly) 기초  (5) 2017.01.29
RVA to RAW(PE File Format)  (0) 2017.01.21
Crackme 두번째 관문  (0) 2017.01.19
Crackme 첫번째 관문  (0) 2017.01.19
PE File Format에대해 알아보겠습니다.  (1) 2017.01.19
And

Crackme 두번째 관문

|

# 아주 간단한 Crack me 샘플 분석 (abex' crackme#2)

 

Crackme2를 분석해보도록 하겠습니다.

 

1. 우선 해당 크랙미 파일이 어떤 동작을 하는지 실행시켜 본다.

2. 파일을 실행시켜 실마리를 찾고, 디버깅을 할 때 참고한다.

3. 크랙미 파일에서 요구하는 부분을 만족 시키기 위한 패치를 하거나 

   혹은 디버깅을 통하여 필요한 정보를 확인한다.

 

 

 

 

[ 그림 1 ] abex' crackme#2 파일 실행

- 파일을 실행시켰더니, Name 그리고 Serial 이란 입력란이 있고, 3가지 버튼이 존재했다.


- 이번 크랙미 문제는 시리얼 번호를 알아내야하는 문제인것으로 예상된다.


- 임의의 입력값들을 주고 Check 버튼을 눌러보도록 하자.

 

 


 

 

[ 그림 2 ] 임의의 입력값을 주고 Check 버튼을 눌러본 경우

- Name의 값은 적어도 4글자 이상으로 설정해야 한다는 것을 확인할 수 있었다.

- Name의 값을 4글자 이상으로 수정한후 다시 Check 버튼을 눌렀더니, 잘못된 시리얼 값이라고 에러 메시지 박스가 떳다.


- 여기서 다시 정리를 해보면, 우리가 찾아야 할 것은 시리얼 값인데, 이 시리얼 값이 Name값과는 상관없는 고정된 하나의 값을 가지고 있거나

  혹은 입력값으로 같이 받는 Name 값을 이용하여 유동적으로 변하는 값일 수 도 있을 것이다. 이제 디버깅을 시작해보도록 하자.

 

 

 

 

 

[ 그림 3 ] OllyDbg를 이용한 디버깅 (EP 코드)

- 크랙미 파일을 불러온 첫 화면이다. 주소 00401238이 Entry Point 이며, EP로부터 두세줄을 살펴보면 0040123D에서 CALL 명령어를 이용하여

  MSVBVM60.ThunRTmain 이라는 것을 호출한다. 그 윗부분에서도 MSVBVM60 이라는 문자가 많이 보이는데 이것은 VB 전용 엔진

  (The Thunder Runtime Engine)을 의미한다. 

  즉, 이 크랙미 파일은 VB 전용 엔진을 사용하는 Visual Basic으로 제작되었다는 것을 확인할 수 있다.

 

* 참고 : VB(Visual Basic) 파일은 MSVBVM60.dll(Microsoft Visual Basic Virtual Machine 6.0)이라는 VB 전용 엔진을 사용

          한다. VB 파일은 컴파일 옵션에 따라서 N code와 P code로 컴파일이 가능하다. N code는 일반적인 디버거에서 해석 가능한

          IA-32 Instruction을 사용하는 반면에 P code는 인터프리터(Interpreter) 언어 개념으로서 VB 엔진으로 가상 머신을 구현하여

          자체적으로 해석 가능한 명령어(바이트코드)를 사용한다. 따라서 VB의 P code를 정확히 해석하기 위해선 VB 엔진을 분석하여 

          에뮬레이터를 구현해야한다.

 

- 이 크랙미 파일이 VB 파일이라는 것을 알게 되었다. VB는 주로 GUI 프로그래밍을 할 때 사용되며, IDE 인터페이스 자체도 GUI 프로그래밍에

  최적화되어 있다.


  즉 VB 프로그램은 Windows 운영체제의 Event Driven 방식으로 동작하기 때문에 main() 혹은 WinMain()에 사용자 코드(우리가 

  디버깅을 원하는 코드)가 존재하는 것이 아니라, 각 event handler에 사용자 코드가 존재한다. 따라서 event handler를 중점적으

  로 하여 디버깅을 하면 될 것이다.


- 따라서, 위의 EP 코드는 VB 엔진의 메인 함수를 호출후에 돌아올 리턴 어드레스를 스택에 입력한 후 VB 엔진의 메인 함수를 

  호출한다. (ThunRTMain())






[ 그림 4 ] ThunRTMain() 함수 호출

- ThunRTMain() 함수를 호출한 모습이다. 메모리 주소를 살펴보면 완전히 달라진 것을 확인할 수 있다. (0040123D에서 733735A4)

  이 주소는 MSVBVM60.dll 모듈의 주소 영역이다. 즉, 우리가 분석하는 프로그램의 코드가 아니라 VB 엔진의 코드라는 것이다.

  따라서 이러한 코드들을 모두 분석할 필요가 없다.






[ 그림 5 ] 문자열 검색을 이용하여 코드 찾아가기

- 문자열 검색을 이용하여 핸들러 근처의 사용자 코드를 찾아야 한다. 우리가 Name 값과 Serial 값을 주고 Check를 눌렀을 때, 에러 메시지 

  박스의 타이틀 문자열인 "Wrong serial!" 이 있는 코드로 우선 이동하여 그 코드가 실행되기전의 조건 분기문을 찾아보도록 하자.

 

 

 

 

 

[ 그림 6 ] "Wrong serial!" 문자열이 있는 주소로 이동

- 지금 위치해 있는 주소는 "Wrong serial!" 문자열을 타이틀로 가진 메시지 박스에 관련된 코드 영역으로 추측된다.


- 따라서 이 코드가 나오기전에 시리얼이 맞는지, 틀린지 확인을 하는 조건 분기문이 있을 것으로 생각되며 따라서 그 조건 분기문을 찾아 

  보도록 하자.

 

 

 

 

 

[ 그림 7 ] 조건 분기문 발견

- [ 그림 6 ] 의 메모리 주소 영역에서 스크롤을 올려가며 코드를 살펴보니 분기문이 있는 메모리 주소를 찾을 수 있었다.


- 00403332 주소에 명령어 : JZ 00403408  (JZ 명령어는 연산 결과가 0 일경우, 즉 ZF(Zero Flag)=1로 세팅되면 00403408로 점프하라)


- 00403332 바로 직전의 0040332F 주소의 명령어를 살펴보면 TEST AX,AX 연산을 하는 것을 확인할 수 있다. 

  (TEST 명령어는 두 개의 오퍼랜드를 AND 연산하여 그 값이 0인지를 확인하기 위한 명령어이다. AX 값이 0이라면 AND 연산후에 0이 되고, 

   ZF 값은 1로 세팅된다. 만약 AX 값이 0이 아닌 값이라면  TEST AX, AX 를 실행한 후의 값은 AX 자기 자신이 나오는데 이 값은 0이아닌 쓸모없는 

   값이다. 즉, 앞에서 말했듯이 0인지 확인하는 명령어라고 생각하면 쉽다.)


- AX 값이 만약 0 이였다면 ZF=1로 세팅되고 조건 분기문이 참이되어 00403408 주소로 점프하게 될 것이다. 조건 분기문 아래의

  코드들을 차례대로 살펴보면 조건 분기문이 참이되어 00403408로 점프한다는 것은 Serial 값이 틀린 경우라는 것을 확인할 수 있다. 

  (왜냐하면 맞췄을 경우 뜨는 메시지박스에 관련된 문자열 코드에 해당하는 부분들을 모두 뛰어 넘어 가는 것이기 때문이다)


- 이제 분기문 위의 코드들을 살펴보도록 하자.

 

 

 

 

 

[ 그림 8 ] 조건 분기문 이전의 코드 분석

- 위에서 확인했듯이 TEST AX, AX 에서 연산 결과가 0이되어 ZF=1이 되면, 실패인 경우로 가게 된다.

  TEST 연산의 결과는 그 윗줄에서 호출되는 함수에 의해서 결정된다. (AX 값이 호출된 함수안에서 변경되기 때문에)


- [ 그림 7 ]에서 00403332 주소의 JZ 00403408 명령어는 결국 시리얼값이 맞는지 틀린지 확인한 직후의 조건 분기문이라고 했다.

  그렇다면 그 직전에 호출되는 함수가 결국 사용자가 입력한 시리얼값과 프로그램의 시리얼값과 비교하는 함수이고, 직전의 

  2개의 PUSH는 함수에서 참조될 파라미터 값이라고 예상할 수 있을 것이다. (하나는 사용자가 입력한 시리얼값, 나머지 하나는 

  프로그램의 시리얼값)


- PUSH 되는 값들을 확인해보도록 하자.

 

 

 

 

 

[ 그림 9 ] PUSH되는 EDX와 EAX 값 확인

- PUSH 되는 값을 보니 메모리 EDX=0018F41C   ,   EAX=0018F42C  라는 메모리 주소 값이 스택에 입력되는 것을 확인할 수 있다.

- 저 2개의 메모리 주소를 확인해보도록하자.

 

 

 

 

 

[ 그림 10 ] EDX 및 EAX가 가리키는 메모리 주소의 값을 확인

- Dump 창에서 해당 메모리 주소의 Hex 값을 확인해보니 각 각 16바이트 중 4바이트를 제외한 모든 값들이 같다는 것을 확인할 수 있었다.

 

* 참고 : VB의 문자열은 C++의 string 클래스와 마찬가지로 가변 길이 문자열 타입을 사용한다고 한다.

           따라서 위의 [ 그림 10 ]에서 보는 바와 같이 바로 문자열이 나타나지 않고 16바이트 크기의 데이터가 나타난다.

           (이것이 바로 VB에서 사용하는 문자열 객체이다.)

 

위에서 다른 4바이트 부분은 메모리 주소를 나타내는 것으로 추측된다. 

  (가변 길이 문자열 타입은 내부에 동적으로 할당한 실제 문자열 버퍼 주소를 가지고 있다.)


- Dump 창에서 Hex dump 형식이 아닌 Integer-Address with ASCⅡ dump 모드로 변경하여 보면 [ 그림 10 ] 의 두번째와 같이 볼 수 있다.


- 변경한 모드에서 서로 다른 4바이트의 값을 확인해봤더니 (74985000, 1CAC5000 : 리틀엔디언형식으로 저장된 주소값) 

   00509874   에는  UNICODE "B7A5B2AB"

   0050AC1C 에는  UNICODE "12345678"          이라는 문자열이 저장되어 있는 것을 볼 수 있다.

   

   0050AC1C 에 저장된 "12345678" 이란 값은 사용자가 입력한 임의의 시리얼값이라는 것을 확인할 수 있다.

   즉, 00509874 에 저장된 "B7A5B2AB" 이란 값이 시리얼 값이라는 것이다.  


결론적으로, Name이 "SANGDAE"라는 값을 주었을 때 시리얼값이 "B7A5B2AB"라는 것이다.


- 크랙미 파일을 다시 실행하여 "NAME에는 SANGDAE, Serial에는 B7A5B2AB 라는 값을 주고 Check 버튼을 눌러 확인해보도록 하자. 

 

 

 

 

 

[ 그림 11 ] 디버깅으로 확인한 시리얼 값 입력 결과

- 위에서 확인한 시리얼 값을 입력한 결과, 크랙이 성공했다는 메시지 박스를 확인할 수 있었다.

 

 

 

 

 

[ 그림 12 ] 다른 임의의 NAME으로 시도했을 경우

- Name 값을 다르게 주고 위와 동일한 시리얼 값을 주었더니 시리얼 값이 다르다는 메시지 박스가 떳다.

  즉, 사용자가 입력하는 Name의 값을 참조하여 시리얼을 생성한다는 것을 추측할 수 있었다.


- 그렇다면, 시리얼을 생성하는 부분을 다시 분석해보도록 하자.

 

 

 

 

 

[ 그림 13 ] Check Event Handler 시작 부분 찾아가기

- 위의 코드에서 Break Point 된 부분이 Check Event Handler의 시작 부분이다.


우리가 [ 그림 7 ]에서 발견한 조건 분기문은 여러가지 의미를 담고 있다. 그 조건 분기문 이후에 크랙 성공 여부가 나눠지는 메시지박스를 

   띄우므로 그 직전에 입력한 시리얼 값과 프로그램 시리얼 값을 비교하는 부분이 있을 것이고, 더 생각해보면 그 시리얼 값을 비교하기 위해

   그 전 코드에 시리얼 값을 생성하는 코드 부분이 존재할 것이다. 


- 즉, 이러한 작업들은 사용자가 Check 버튼을 입력했을 때 수행되는 작업들이다. 다시 말하면 그 조건 분기문을 기준으로 거슬러

  올라간다면 Check Event Handler 시작 부분을 찾을 수 있다는 말이다.

  (조건 분기문도 Check Event Handler의 일부분에 포함하므로)


- Check Event Handler 코드의 시작 부분을 찾았으므로, 이제 시리얼 값을 생성하는 코드부분을 찾아보도록 하자.

 

 

 

 

----------------------------------------------------------------------------------------------------------------------------------

* 참고 

[ 참고 그림 ] VB 파일에서의 NOP 명령어

- VB 파일에서는 함수와 함수 사이에 NOP 명령어가 존재한다.

- NOP 명령어는 No Operation 을 의미하며, 즉 아무 동작을 하지 않는 명령어이다. (그냥 CPU 클럭만 소모된다)

----------------------------------------------------------------------------------------------------------------------------------

 

 

 

 

 

[ 그림 14 ] Name 문자열 읽어오는 코드 부분

- Check Event Serial 함수 시작 부분에서 순차적으로 내려오면서 Name 문자열을 읽어올것으로 예상되는 부분(CALL 명령어 위주로)들을 

  살펴보면 이러한 코드들이 있는 곳을 찾을 수 있다.


- 00402F8E 주소의 명령어는 LEA EDX, [EBP-88] (레지스터 EDX에 [EBP-88]의 주소값을 복사하는 것이다.)

   즉, Name 문자열을 저장할 스트링 객체를 생성한다고 보면된다.)


- 그리고 5번째 줄인 00402F98 주소에서 사용자가 입력한 문자열을 읽어오는 것으로 추측된다.


- 함수를 호출한 후의 EDX가 가리키는 주소를 확인해보도록 하자.

 

 

 

 


[ 그림 15 ] 함수 호출 후의 [EBP-88] 값 확인

- 00402F98 주소에서 CALL 명령어 (함수호출) 이 후에 문자열을 읽어와 저장할것으로 예측한 주소를 확인해본 결과

  사용자가 입력한 Name 값이 들어있는 것을 확인할 수 있었다.


- 두 번째 그림 : Stack 창에서 확인한 값


- 나머지 부분들을 계속 디버깅해보자.

 

 

 

 


[ 그림 16 ] 새로운 조건 분기문 발견 (Name 값이 4글자 미만인지 확인하는 부분)

- [ 그림 15 ] 에서 이어 뒷부분을 디버깅하다가 새로운 조건 분기문을 발견하였다.


- 빨간 코드영역 : 어떠한 값들(함수 파라미터 값으로 추측)을 스택에 PUSH하고 어떠한 함수를 호출한 후, TEST AX,AX를 실행하고 조건

                        분기문에 의해 두 가지 경우로 나누어진다.


- 노란 코드영역 : 이 노란 코드 영역을 해석하면 일단 00403026 주소에서 조건 분기문이 거짓이 될 때(ZF=0 일때) 실행되는 코드 영역이다.

                        코드를 살펴보니 "Error!" , "Please enter at lest 4 chars as name!" 이라는 유니코드를 확인할 수 있다.

                        이러한 정보들을 봤을 때, 이 노란 코드영역은 사용자가 Name의 값을 4글자 미만으로 줬을 때 진행되는 부분인것으로 추측할 

                        수 있다.


우리가 처음에 실행 파일이 어떻게 동작하는지 확인하기 위해서 Name 값을 임의로 "ABC"로 주었을 때 떳던 메시지 박스에 관한

  코드 부분인 것이다. ( [ 그림 2 ] 참고 )


- Name 값이 4글자 미만일 경우 다시 입력받는 곳으로 돌아가기 때문에 이 부분은 따로 디버깅을 할 필요는 없어보인다.

  따라서, 00403026 주소에서 점프하는 004030F9 주소부터 디버깅을 다시 하면, 암호화 방법에 대해서 실마리를 찾을 수 있을 것이다.

 

 

 

 

 

[ 그림 17 ] Name 문자열에서 한 글자씩 읽어오는 함수 발견 

사용자가 입력한 Name이 "SANGDAE" 였었다. 004031F0 주소에서 __vbaStrVarVal 함수를 호출하고 EAX의 값을 스택에 PUSH 

  하는데, 함수호출 직후의 변경된 EAX값을 참조하여 데이터를 확인했더니 Name의 값 "SANGDAE"에서 한문자를 가져온 "S"임을

  확인할 수 있었다.

 

 

 

 

 

[ 그림 18 ] 004031F7 에서 rtcAnsiValueBstr 함수 호출 직후에 변경된 EAX 값 (유니코드 한문자를 아스키코드로 변환)

- [ 그림 17 ] 에서 __vbaStrVarVal 함수 호출 후 변경된 EAX 값(Name 값에서 유니코드 한문자를 가져온)을 파라미터로 rtcAnsiValueBstr 함수

   를 호출한다.


호출 직후에 파라미터로 주었던 EAX 값이 변경되는데, 이 변경된 값을 살펴보면 "S"라는 유니코드를 아스키코드로 변경한 것을 

  확인할 수 있다.


- 즉, Name의 값에서 유니코드 한 글자를 가져와 아스키코드로 변경하는 작업을 두 개의 함수를 이용하여 한 것이다.






[ 그림 19 ] 그 이후의 작업들 

( # 암호화시키는 키를 생성한후 위에서 변경해뒀던 아스키코드 값과 더한후 데이터형을 문자로 변환시킴 )

- 위에서 유니코드 한 글자를 가져와 아스키코드로 변경하는 작업을 한 후에 나머지 부분들을 디버깅해본 결과, 임의의 수를 생성하고 

  (64라는 값), 그 다음 그 값을 이용하여 앞에서 아스키 코드로 변경한 값을 더해준다. (암호화하는 과정) 그 후 다시 rtcHexVarFromVar 함수를 

  호출하여, 더했던 값을 문자형(유니코드)으로 변경시킨다. 

 

정리를 다시 해보면,  

                                 암호화 과정은 아래의 순서와 같다.  (4번 반복)

                                 1. 주어진 Name 문자열을 앞에서부터 한 문자씩 읽어온다.

                                 2. 읽어온 한 문자를 숫자(아스키 코드)로 변환시킨다.

                                 3. 변환 시킨 숫자(아스키 코드)에 64를 더한다.

                                 4. 더한 결과 값을 다시 문자형으로 변환시킨다.(데이터형을)

                                 5. 변환된 문자를 연결시킨다.

 

이러한 암호화 과정을 사용자가 입력한 Name의 4글자만 가지고 작업을 반복한다. 

( 사진에는 코드가 빠져 있는데, 사용자가 입력한 Name의 입력값이 4글자 미만인지 확인 한후, EBX 레지스터에 4라는 값을 주어, EBX 값만큼 

  루프를 돌린다. )

 

                              

- 결국 "SANGDAE" 라는 Name 값을 주었을 경우에 시리얼 값 생성은 SANG 문자열을 가지고 생성한다는 말이다.

   유니코드인 S 를 아스키코드인 53(Hex 값)으로 변경시키고, 64를 더한다. 

   0x53 + 0x64 = 0xB7,     결과 값을 다시 문자형으로 변경  ->  "B7"

   

   유니코드인 A 를 아스키코드인 41(Hex 값)으로 변경시키고, 64를 더한다. 

   0x41 + 0x64 = 0xA5,     결과 값을 다시 문자형으로 변경  ->  "A5"

   

   유니코드인 N 를 아스키코드인 4E(Hex 값)으로 변경시키고, 64를 더한다. 

   0x4E + 0x64 = 0xB2,    결과 값을 다시 문자형으로 변경  ->  "B2"

   

   유니코드인 G 를 아스키코드인 47(Hex 값)으로 변경시키고, 64를 더한다. 

   0x47 + 0x64 = 0xAB,     결과 값을 다시 문자형으로 변경  ->  "AB"


   변경된 문자들을 모두 연결시키면 "B7A5B2AB" 라는 시리얼이 생성된다.


 

 

# 암호화 방법을 모두 확인하였다. 

   그렇다면 마지막으로 [ 그림 12 ] 에서 실패했던 "YOKANG"이라는 Name의 시리얼값을 계산해서 

   입력해보자.


* YOKA 문자열을 이용하여 시리얼 값 계산

   

  Y   ->   0x59 + 0x64 = 0xBD   ->   "BD"

  O   ->   0x4F + 0x64 = 0xB3   ->   "B3"

  K   ->   0x4B + 0x64 = 0xAF   ->   "AF"

  A   ->   0x41 + 0x64 = 0xA5   ->    "A5"


 Name 값이 "YOKANG" 일 때의 시리얼 값은  "BDB3AFA5" 이다.





[ 그림 20 ] 직접 구한 시리얼 값으로 확인

- 앞에서 분석한 암호화 방법을 이용하여 계산한 시리얼 값이 정확한 것을 확인할 수 있었다.

 


script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js">

'Reverse Engineering' 카테고리의 다른 글

RVA to RAW(PE File Format)  (0) 2017.01.21
Hello World 디버깅 실습  (0) 2017.01.21
Crackme 첫번째 관문  (0) 2017.01.19
PE File Format에대해 알아보겠습니다.  (1) 2017.01.19
OllyDbg 기본 사용법 및 단축키  (0) 2017.01.19
And

Crackme 첫번째 관문

|

# abex' crackme #1 분석

 

 

Crackme첫번째 관문인 Make me think your HD is a CD-ROM을 분석해보도록 하겠습니다.


1. 우선 abex' crackme #1  파일이 어떤 동작을 하는지 확인하기 위해 실행 시켜본다.

2. Crack me 파일에서 수정해야 할 부분들을 패치해준다. (조건에 맞도록)

 

 

 

 

[ 그림 1 ] abex' crackme 파일을 실행 시킨 화면 (왼쪽에서 오른쪽으로 순차)

  

- 파일을 실행 시키니까 "Make me think your HD is a CD-Rom"이란 문자열을 포함한 메시지 박스가 뜨고, 확인 버튼을 누르자,

  "Nah... This is not a CD-ROM Drive!" 라는 문자열을 포함한 에러 메시지 박스가 떳다.


- 이 크랙미 문제는 현재 인식하고 있는 하드 디스크를 CD-ROM 드라이브로 인식하게끔 만들어 달라는 문제로 파악이 된다.


- 이제 OllyDbg를 이용하여 파일을 열어보자.

 

 


 

[ 그림 2 ] OllyDbg를 이용하여 Crack me 파일 디버깅

- 우선 위에서 디스어셈 코드를 살펴보면 EP 코드가 짧은 것을 확인할 수 있다. EP 코드가 비교적 짧은 이유는 abex' crackme 파일이 어셈블

  리 언어로 만들어진 실행 파일이기 때문이다. 어셈블리 언어가 아닌 다른 언어로 만들어진 실행 파일일 경우에는 그 언어의 컴파일러가 Stub

  Code를 추가 시키기 때문에 EP 코드가 복잡하게 보이며 길어지게 되는 것이다.


- 위의 디스어셈 코드에서 주의 깊게 살펴 보아야 할 것은 API 함수들, 그리고 점프문이 있는 부분들을 중점으로 봐야한다.

 

* 참고 : Stub Code는 사용자가 입력한 코드가 아니라 컴파일러가 임의로 추가시킨 코드이다. 이 코드는 EP 코드 영역에 추가

          되며, 이러한 코드를 StartUp Code라고 부르기도 한다. 디버깅을 할때엔 자세히 볼 필요가 없다.

 

 


 

[ 그림 3 ] 디스어셈 코드 분석.1

- 위 코드는 EP 코드의 첫 부분에 해당된다. 명령어를 살펴보니 PUSH 명령어를 이용하여 스택에 어떠한 값들을 입력하고 마지막으로 

  MessageBox API 함수를 호출하고 있다. 오른쪽편에 있는 주석부분을 살펴보면 MessageBox API 함수에 전달되는 파라미터에 대한 값들이 

  스택에 입력되고 있다는 것을 알 수 있다.


- 주의해야 할 점은 스택이 FILO(First In Last Out) 구조라서 API 함수에서 참조하는 파라미터의 값들을 역순으로 입력하고 

  있다는 점이다.

 

* 참고 : 위의 디스어셈 코드 5줄을 C언어로 번역하면 아래의 코드와 같다.

            MessageBox(NULL, "Make me think your HD is a CD-Rom.", "abex' 1st crackme", MB_OK|MB_APPLMODAL);    





[ 그림 4 ] 디스어셈 코드 분석.2    (파란 부분: 우리가 원하는 메시지 // 빨간 부분: 기존의 에러메시지)

- 위의 디스어셈코드는 나머지 EP 코드 부분들이다. 쭉 읽어보면, GetDriveTypeA(드라이브 타입을 가져오는 API 함수)를 호출하고, ESI의 값은

  1씩 증가시키고, EAX의 값은 1씩 감소시킨다. 그리고 점프문도 있는데 여기의 점프문은 바로 아래 코드의 주소값을 참조하기 때문에 신경쓸 필요가

  없어 보인다.

   

  중요한 부분은 CMP 명령어를 이용하여 EAX와 ESI의 값을 비교해서 같으면 0040103D 주소로 점프를 하고 같지 않다면 다음 

  코드로 이동하여 진행한다는 부분이다. 0040103D 주소로 이동하면 우리가 원하는 문자열을 가지고 있는 MessageBox를 띄울 

  수 있을 것이다. 따라서 우리가 이 크랙미 문제에서 우리가 해줘야 하는 것은 코드의 흐름이 에러 메시지 박스를 띄우는 곳으로

  가는 것이 아니라, 파란색 부분의 코드로 이동을 하게 해주어야 한다는 것이다.





[ 그림 5 ] Address 0040101F의 어셈 코드 수정

- 위의 JMP 문이 참조하는 주소를 0040103D로 변경하게 되면 우리가 원하는 메시지를 띄울 수 있다.

 

 



[ 그림 6 ] Address 0040101F의 어셈 코드 수정 결과


- 우리가 원했던 의도대로 변경이 됬음을 확인할 수 있다.

 

 

 

* 이 방법 외에도 이 크랙미를 풀 수 있는 방법은 여러 가지가 있을 것이다. EAX와 ESI의 값이 임의로 같아지게 코드를 수정하여 

  기존의 JE 조건을 만족시켜 성공 메시지를 띄우는 방법과 JE 명령어를 JNE 명령어로 변경하여 EAX와 ESI의 값이 다를 때 점프

  하도록 할 수도 있다.




# 참고 도서 : 리버싱 핵심원리 



출처: http://yokang90.tistory.com/7 [yO Kang]

script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js">
And

PE File Format에대해 알아보겠습니다.

|












안녕하세요. 이번 장은 PE File Format에 대해 알아보고 구조를 분석해보록 하겠습니다.


# PE File Format



- PE(Portable Executable) 파일의 의미를 영단어대로 해석해보면 [Portable=이식가능한, Executable=실행가능한]

  대략 이식이 가능한 실행 파일 정도로 해석이 가능합니다. 처음 PE 파일이 만들어질 때는 다른 운영체제에 이식성을

  좋게 하려했으나 의도대로 되지 않고, 실제로는 Windows 계열의 운영체제에서만 사용되고 있다고 합니다.


  다시 말해, PE File Format은 Windows 운영체제에서만 사용되는 실행 파일의 형식입니다.

  이 PE File Format은 기존 UNIX에서 사용되는 COFF(Common Object File Format)를 기반으로 Microsoft 사에서 만들었습니다.


  PE 파일은 32비트 형태의 실행 파일을 의미하며, PE32라고 불리기도 합니다. 64비트 형태의 실행 파일은 PE+ 또는 PE32+

  라고 표현하며, 이 PE+ 또는 PE32+는 32비트 형태의 실행 파일인 PE 파일의 확장 형태입니다.


  PE File에도 여러 종류가 있는데, 그 종류에 대해서 알아보도록 하겠습니다.





1. PE File 종류


- PE File의 종류에는 실행 계열, 라이브러리 계열, 드라이버 계열, 오브젝트 파일 계열로 총 4가지로 분류되어 있습니다.

- 오브젝트 파일을 제외한 모든 종류는 실행이 가능한 파일입니다. 

- PE 공식 스펙에는 컴파일 결과물인 OBJ(오브젝트) 파일도 PE 파일로 간주합니다. 하지만, OBJ 파일 자체로는 

  어떠한 형태의 실행도 불가능하므로 리버싱에서 관심을 가질 필요는 없습니다. 







간단하게 PE File의 종류에 대해서 알아보았습니다. 그럼 지금부터 PE File의 기본 구조에 대해서 알아보도록 하겠습니다.





2. PE File 기본 구조




- PE File의 기본 구조는 PE 헤더와 PE 바디로 구성되어져 있습니다. PE 헤더에는 DOS header와 DOS Stub, NT header 

  그리고 각 Section header 들로 구성되어 있으며, PE 바디에는 PE 헤더 부분을 제외한 모든 Section 들을 합쳐 PE 바디라고

  부릅니다


- 위 그림이 PE File의 기본 구조를 도식화하고, PE File이 Memory에 로딩되는 모습입니다.


- 왼쪽편의 File에서는 offset으로, 오른쪽편의 File이 로딩된 Memory에서는 VA(Virtual Address)로 위치를 표현합니다.


- PE File이 Memory에 로딩되면 Section의 크기나 위치등이 달라지게 됩니다. 

  (PE 파일은 디스크 상의 모습과 메모리 상의 모습은 거의 같습니다.)


- 참고로 Section의 이름, 크기, 개수, 저장 내용 등은 개발도구와 빌드 옵션에 따라서 달라집니다.


- Section header에는 각 Section에 대한 파일/메모리에서의 크기, 위치, 속성 등의 정보가 정의되어 있고,

  Section 영역에는 실제 실행 코드와 데이터 혹은 리소스와 같은 여러가지 실제적인 정보들이 Section header에서 

  명시한 위치와 크기별로 포함되어 있습니다.




- Section의 종류



# 여기까지가 기본적인 PE File의 기본 구조였습니다. 지금부터는 PE 헤더 부분을 좀 더 자세하게 알아보도록 하겠습니다.

# 앞으로 나오는 내용들을 이해하기 위해서는 VA(Virtual Address)와 RVA(Relative Virtual Address)를 알아야 하는데요. 

  간단하게 VA와 RVA가 어떤것인지 알아보도록 하겠습니다.


# VA & RVA

- VA(Virtual Address)는 프로세스 가상 메모리의 절대 주소를 의미합니다.

- RVA(Relative Virtual Address)는 어느 기준 위치(ImageBase)에서부터의 상대주소를 의미합니다.

- VA와 RVA의 관계는 다음 식과 같습니다.

  RVA + ImageBase = VA


- PE 헤더에서는 VA(절대주소)가 아닌 RVA(상대주소)를 많이 사용합니다. 그 이유는 여러개의 PE File(DLL)이 같은 위치에 로딩될 수 있기

  때문에 정상적인 엑세스가 이루어지지 않을 수 있게 됩니다. 사실 이렇게 같은 위치에 로딩이 되게 될 경우 재배치(Relocation) 과정을 통해

  비어 있는 위치로 PE File(DLL)을 로딩시키게 됩니다. VA(절대주소)를 사용한다고 가정하면, 재배치 과정을 통해 위치가 변하게 되면 VA도

  변해야 정상적인 엑세스가 될 것 입니다. 반면에 RVA(상대주소)인 경우에는 재배치 과정을 통하더라도 기준위치에 대한 상대주소는 변함없

  기 때문에 아무 문제 없이 엑세스할 수 있습니다. 이것이 VA가 아닌 RVA를 사용하는 이유입니다.







3. PE 헤더


- PE 헤더에는 크게 DOS header, DOS stub, NT header, Section header들로 구성되어 있습니다. 그리고 많은 구성 요소들이 구조체로 이루어져 

  있습니다. 지금부터 각 구성요소들에 대해서 하나씩 살펴보도록 하겠습니다.




3.1. DOS Header


- 첫 번째로 살펴볼 부분은 DOS Header의 IMAGE_DOS_HEADER 구조체 입니다. PE File에서 제일 앞부분에 위치하고 있는 구조체로 DOS 파일에 대한  호환을 위한 DOS 실행 이미지입니다. (기존 DOS EXE Header를 확장시킨 IMAGE_DOS_HEADER 구조체)






- IMAGE_DOS_HEADER의 구조체입니다. 많은 구조체 멤버를 가지고 있지만, 모든 멤버들을 다 알아야 할 필요는 없습니다. IMAGE_DOS_HEADER 

  구조체에서 꼭 알아야할 중요한 멤버는 e_magic과 e_lfanew 멤버입니다.


e_magic : DOS signature (4D5A => ASCII 값 "MZ") - 2Byte

e_lfanew : NT Header의 옵셋을 표시 (파일에 따라 가변적인 값을 가짐) - 4Byte


- 모든 PE 파일의 e_magic에는 DOS signature ("MZ")가 존재하며, e_lfanew 값이 가리키는 위치에 NT Header 구조체가 존재합니다.

- IMAGE_DOS_HEADER 구조체의 크기는 64Byte (0x40) 입니다.

  ( DOS signature "MZ"는 DOS 실행 파일을 설계한 마크 주비코브스키라는 사람의 영문 이니셜입니다 )







- Calc.exe(계산기)를 헥사 에디터로 열어본 모습입니다. Calc.exe 파일의 DOS Header 부분이며, 구조체의 첫 2Byte는 File Signature 값으로

  DOS Signature 인 "4D5A(MZ)" 값임을 확인할 수 있습니다. Calc.exe 역시 PE 파일이기 때문입니다. 그리고 DOS Header의 가장 마지막에

  위치하는 e_lfanew 멤버 값을 확인해보면 "000000D8" 임을 확인할 수 있습니다. 000000D8 주소에 NT Header가 존재하겠네요.

  ( 참고로 Intel 계열 CPU는 데이터를 역순으로 저장하는 리틀 엔디언 방식을 이용하여 메모리에 데이터를 저장합니다. )


- 000000D8 주소에 NT Header가 정말 존재하는지 PEview를 이용하여 확인해 보도록 하겠습니다.







3.2. DOS Stub


- DOS Stub은 있는 경우도 있고, 없는 경우도 있습니다. DOS Stub의 존재 여부는 개발 도구의 지원 여부에 의해 결정됩니다. 한 마디로 옵션이며,

  VB, VC++, Delphi 등에서는 DOS Stub을 기본적으로 지원하고 있습니다. 그리고 DOS Stub의 크기 또한 개발 도구에 따라 다르며, DOS Stub이 없어도

  파일 실행에는 아무런 문제가 되지 않습니다. 즉, PE HEADER의 중요 요소가 아니므로 생략이 가능합니다.


- DOS Stub은 코드와 데이터의 혼합으로 이루어져 있으며, OS가 PE File 형식을 인지하지 못할 때 실행되는 부분이라고 생각하시면 되겠습니다.

- Calc.exe 파일의 DOS Stub 영역을 헥사 에디터를 이용하여 한번 보도록 하죠.




- 노란 부분 : 이 부분이 바로 DOS Stub 영역입니다. 파일 Offset (h) 40~4D 영역(DOS Stub 영역 처음부터 14Byte)은 16비트 어셈블리 명령어입니다. 

                   ( 초록 박스 ) 이 16비트 어셈블리 명령어는 32비트 Windows OS에서는 실행되지 않는 명령어입니다. DOS 환경이나 DOS용 디버거를

                   이용하여 실행했을 때, 16비트 어셈블리 명령어가 실행됩니다. 이 코드는 "This program cannot be run in DOS mode" 라는 문구를 출력

                   하고 종료하는 간단한 코드로 구성되어 있습니다. 


- 빨간 부분 : 이 부분은 바로 앞에서 이야기한 DOS Header 영역입니다. 반복해서 계속보는게 공부에 더 도움이 될 것 같아 같이 정리해 보았습니다.

                   제일 첫 부분의 2Byte는 DOS signature, DOS Header 영역의 가장 마지막 4Byte는 e_lfanew 멤버 값입니다. NT Header의 주소를 값으로

                   가지고 있다고 했었습니다. 보시는 것 처럼 NT Header의 첫 부분, IMAGE_NT_HEADERS 구조체의 첫 번째 멤버인 Signature를 가리키고

                   있습니다. 



# DOS Stub 영역에 대해서 정리해보겠습니다.

  ① DOS 환경에서 실행할 때 실행되는 코드와 데이터가 저장되어 있는 영역이다.

  ② PE Header의 중요 요소가 아니므로 생략되어도 된다. (DOS Stub이 없어도 실행엔 문제 없음)

  ③ DOS Stub 크기는 고정되어 있지 않다.

  ④ DOS Stub 영역은 옵션이다. (개발 도구에서 지원을 해주어야 함 - VB, VC++, Delphi 등은 기본으로 지원)

  



3.3. NT Header 


- 이번에는 PE Header의 세 번째 구성요소인 NT Header에 대해서 알아보도록 하겠습니다. NT Header는 IMAGE_NT_HEADERS라는 구조체를 가지고 있습니다.

  IMAGE_NT_HEADERS 구조체는 3개의 멤버로 구성되어 있습니다. 첫 번째 멤버는 Signature, 두세번째는 구조체 멤버로 FileHeader와 OptionalHeader

  있습니다. PE File의 Signature는 50450000("PE"00)의 값을 가집니다. 우선 헥사 에디터를 이용하여 Calc.exe 파일의 IMAGE_NT_HEADERS 구조체를 한번 

  보도록 하죠.



[IMAGE_NT_HEADERS 구조체의 크기는 F8(248Byte) 입니다]




- 위 그림이 Calc.exe 파일의 NT Header 부분입니다. 처음 4Byte는 첫 번째 멤버인 Signature입니다. (50450000 = "PE"00)

  그 아래에 있는 나머지가 FileHeader와 OptionalHeader인데요. 구체적으로 FileHeader의 구조체인 IMAGE_FILE_HEADER의 멤버는 어떤게 있는지, 구조체의

  크기는 얼마나 하는지, FileHeader와 OptionalHeader에 대해서 각각 알아보도록 하겠습니다.




3.3.1 NT Header - File Header 


- NT Header의 세 번째 멤버인 File Header의 IMAGE_FILE_HEADER 구조체는 파일의 개략적인 속성 정보를 가지고 있습니다.

- IMAGE_FILE_HEADER 구조체의 멤버들엔 어떤것들이 있는지 살펴보도록 합시다.



[ IMAGE_FILE_HEADER 구조체의 크기는 14(20Byte) 입니다]




- 위에 보이시는 것이 _IMAGE_FILE_HEADER 구조체입니다. 멤버로 Machine, NumberOfSections, ... 등 총 7개의 멤버를 가지고 있는데요. 이 중에서 중요시

  봐야할 멤버는 4가지 정도 간추려낼 수 있습니다. Machine, NumberOfSection, SizeOfOptionalHeader, Characteristics 이렇게 4가지 멤버인데요.

  이 멤버들이 정확한 값으로 세팅되어 있지 않으면 파일은 정상적으로 실행되지 않습니다. 이 4가지 멤버에 대해서 좀더 자세하게 알아보도록 합시다.


  


#1. Machine

- Machine 넘버는 CPU별로 고유한 값이며, 해당 실행 파일이 실행될 수 있는 시스템(CPU)을 나타내는 값입니다. 아래는 winnt.h 파일에 정의된 Machine 넘버의

  값들입니다.




#2. NumberOfSection

- PE 파일의 PE 바디에는 여러 섹션들이 존재한다고 했습니다. 일반적인 섹션의 종류로는 코드 섹션, 데이터 섹션, 리소스 섹션 등 여러 섹션이 올 수 있는데요.

  NumberOfSection 멤버는 이러한 섹션들의 개수를 값으로 가집니다. 그런데 이 값은 0보다 커야 하며, 정의된 섹션 개수와 실제 섹션이 다르면 실행 에러가 발생

  하게 된다는 점도 알아두시면 좋을 것 같습니다.




#3. SizeOfOptionalHeader

- NT Header의 세 번째 멤버인 OptionalHeader의 IMAGE_OPTIONAL_HEADER32 구조체의 크기를 저장하고 있는 필드입니다. PE 형태의 파일인 경우 IMAGE_

  OPTIONAL_HEADER32 구조체를 사용하고 PE+ 형태의 파일일 경우에는 IMAGE_OPTIONAL_HEADER64 구조체를 사용합니다. 두 구조체는 크기가 다르기 때문

  에 SizeOfOptionalHeader 멤버에 OptionalHeader의 구조체 크기를  명시하는 것입니다.




#4. Characteristics

- Characteristics 멤버는 파일의 속성을 나타내는 값으로, 실행이 가능한 형태인지(executable or not) 혹은 DLL 파일인지 등의 정보들이 bit OR 형식으로 조합됩니다.

  다음은 winnt,h 파일에 정의된 Characteristics 값입니다. (0002h와 2000h의 값을 기억하시길 바랍니다)



[ 출처 : Microsoft Platform SDK - winnt.h ]


- 위에 보이시는 Characteristics 값들을 bit OR 하여 파일의 속성을 나타내는 값을 만드는데요. 0002h가 없는 경우는 실행이 불가능한 파일이라는 것을 의미합니다.

  예를 들면 object 파일 및 resource DLL 같은 파일이 이에 해당합니다. 





#5. TimeDateStamp (참고)

- IMAGE_FILE_HEADER의 TimeDateStamp 멤버는 링커(Linker)에 의해 프로그램이 만들어진 시간 값이 저장되어 있습니다. 이 값은 1970년 1월 1일 오전 9시 

  GMT로 부터 1초 단위의 증가 값으로 표시된 것이며, 파일의 실행에 영향을 미치지 않는 값으로 중요한 멤버는 아닙니다. 단, 개발 도구에 따라서 이 값을 세팅해

  주는 도구(VB, VC++)가 있는 반면 그렇지 않은 도구(Delphi)도 있습니다.



# 지금까지 NT Header의 두 번째 멤버인 IMAGE_FILE_HEADER 구조체의 멤버들에 대해서 알아보았습니다. 실제로 Calc.exe 파일을 헥사 에디터와

   PE view를 이용하여 보도록 하겠습니다.




- Calc.exe 파일을 헥사 에디터로 열어본 모습입니다. 위에서 해당하는 영역이 바로 IMAGE_FILE_HEADER 영역입니다. PE View를 이용하여 Calc.exe 파일을 확인

  해 보도록하겠습니다.



- IMAGE_FILE_HEADER 구조체에 대해서 알아보았는데요. 구조체 이름 처럼 파일에 관한 정보를 담고 있는 구조체였습니다. 그럼 NT_HEADER의 마지막 멤버인 

  IMAGE_OPTIONAL_HEADER 구조체에 대해서 알아보도록 하겠습니다.




3.3.2 NT Header - Optional Header 


- PE 헤더 구조체 중에서 가장 크기가 큰 IMAGE_OPTIONAL_HEADER32입니다. 지금부터 IMAGE_OPTIONAL_HEADER32 구조체에 대해서 알아보도록 하겠

  습니다.

- IMAGE_OPTIONAL_HEADER32 구조체에 대해서 알아보도록 하겠습니다. 아래의 그림에서 강조한 부분에 해당하는 멤버들이 IMAGE_OPTIONAL_HEADER32

  구조체에서 주의깊게 봐야할 멤버들입니다. 이 멤버들의 값은 파일 실행에 있어 필수적이기 때문에 잘못 세팅되면 파일이 정상적으로 실행되지 않습니다.



[ 출처 : Microsoft Platform SDK - winnt.h ]



#1. Magic

- Magic 넘버는 IMAGE_OPTIONAL_HEADER32 구조체인 경우 10B, IMAGE_OPTIONAL_HEADER64 구조체인 경우 20B, ROM 이미지일 경우

  107의 값을 가집니다.




#2. AddressOfEntryPoint

- AddressOfEntryPoint 멤버는 PE 로더가 파일을 메모리에 매핑한 후 처음 실행을 시작할 EP(Entry Point)의 RVA(Relative Virual Address)값을

  가지고 있습니다. 이 값이야말로 프로그램에서 최초로 실행되는 코드의 시작 주소로, 매우 중요한 값입니다.




#3. ImageBase

- ImageBase는 프로세스의 가상 메모리 범위인 0~FFFFFFFF(32비트 운영체제인 경우)에서 PE 파일이 로딩되는 시작 주소를 나타냅니다. EXE, 

  DLL 파일은 user memory 영역인 0~7FFFFFFF 범위에 로딩되고, SYS 파일은 kernel memory 영역인 80000000~FFFFFFFF 범위에 로딩됩니다.

  일반적인 개발 도구인 VB, VC++, Delphi를 이용하여 만든 EXE 파일의 ImageBase 값은 00400000이고, DLL 파일의 ImageBase 값은 10000000

  입니다. PE 로더는 PE 파일을 실행시키기 위해 프로세스를 생성하고 파일을 메모리에 로딩한 후 EIP 레지스터 값을 ImageBase + AddressOfEntry Point 

  값으로 세팅합니다.




#4. SectionAlignment, FileAlignment

- PE File 기본 구조에 대한 그림을 참고하시면 File 영역과 Memory 영역으로 나누어져있는것을 확인할 수 있습니다. PE 파일을 실행하게 되면 File 영역에

  있던 데이터가 Memory 영역에 맵핑되게 됩니다. SectionAlignment 멤버는 메모리에서 섹션의 최소단위를 나타내는 것이고, FileAligment 멤버는 파일에서

  섹션의 최소단위를 나타냅니다. (하나의 파일에서 FileAlignment와 SectionAlignment의 값은 같을 수도 있고 다를 수도 있습니다.) 파일/메모리의 섹션 크기

  는 반드시 각각 FileAlignment/SectionAlignment의 배수가 되어야 합니다.




#5. SizeOfImage

- SizeOfImage는 PE 파일이 메모리에 로딩되었을 때 가상 메모리에서 PE Image가 차지하는 크기를 나타냅니다. 일반적으로 파일의 크기와 메모리에 로딩된

  크기는 다릅니다. SizeOfImage에 들어가는 값의 크기는 PE 헤더를 포함해 모든 세션을 다 포함한 크기이며, 이는 Windows 로더가 해당 파일을 메모리에 

  로드할 때 얼마만큼의 메모리를 할당할지를 결정하는 값이기도 합니다.




#6. SizeOfHeader

- SizeOfHeader는 PE 헤더의 전체 크기를 나타냅니다. (DOS Header, DOS Stub, NT Header, Section Headers) 이 값 역시 FileAlignment의 배수

  여야 합니다. 파일 시작에서 SizeOfHeader 옵센만큼 떨어진 위치에 첫 번째 섹션이 위치합니다.




#7. Subsystem

- Subsystem 멤버에 들어가는 값은 실행 파일의 유저 인터페이스 부분에 대한 필요한 서브 시스템 값입니다. 이 Subsystem의 값을 보고 시스템 드라이버

  파일(*.sys)인지, 일반 실행 파일(*.exe, *.dll)인지 구분할 수 있습니다. Subsystem 멤버가 가질 수 있는 값은 아래와 같습니다.



[ 출처 : Microsoft Platform SDK - winnt.h ]



- 일반적으로 자주 쓰이는 Subsystem 값은 1~3 까지 입니다. 

  * 1 - Driver File - 시스템드라이버 (예 : ntfs.sys)

  * 2 - GUI(Graphic User Interface)파일 - 창 기반 애플리케이션 (예: notepad.exe)

  * 3 - CUI(Console User Interface)파일 - 콘솔 기반 애플리케이션 (예: cmd.exe)




#8. NumberOfRvaAndSizes

- NumberOfRvaAndSizes는 IMAGE_OPTIONAL_HEADER32 구조체의 마지막 멤버인 DataDirectory 배열의 개수를 나타냅니다. 구조체 정의에 

  분명히 배열 개수가 IMAGE_NUMBEROF_DIRECTORY_ENTRIES (16) 이라고 명시되어 있지만, PE 로더는 NumberOfRvaAndSizes의 값을 보고 배열의 

  크기를 인식합니다. 따라서, 꼭 16이 되는 것은 아닙니다.




#9. DataDirectory

- DataDirectory는 IMAGE_DATA_DIRECTORY 구조체의 배열로, 배열의 각 항목 마다 정의된 값을 가집니다.



[ 출처 : 리버싱 핵심원리 ]


- 여기서 중요한 구조체 배열 멤버는 EXPORT, IMPORT, RESOURCE, TLS Directory 정도가 있겠습니다. 특히 IMPORTEXPORT Directory 구조는 

  PE 헤더에서 매우 중요하기 때문에 기억해두시기 바랍니다.



# NT Header의 Optional Header 멤버에 대해서 알아보았습니다. 헥사 에디터, PE View를 통해 실제로 한번 살펴보고 마무리하도록 하겠습니다.





- Calc.exe 파일에서의 Optional Header 영역을 헥사 에디터로 본 모습입니다. 제일 처음 Magic 멤버로 시작하는 것을 확인할 수 있습니다. (010B)

- 다음 사진은 PE View를 이용하여 Calc.exe 파일의 Optional Header 영역을 살펴본 모습입니다.






3.4. Section Header 


- Section Header는 각 Section의 속성을 정의한 것입니다. Section은 비슷한 성격의 자료를 모아 각 각의 Section으로 나누어지는데 이렇게 하는 이유가 프로그램의

  안정성을 위해서라고 합니다. (메모리 관리를 편하게 하기 위함이라는 이유도 있습니다) 우선 Section Header 구조체인 IMAGE_SECTION_HEADER 구조체를 살펴

  보도록 하겠습니다.



[ IMAGE_SECTION_HEADER 구조체의 크기는 28(40Byte) 입니다]

[ 출처 : Microsoft Platform SDK - winnt.h ]



- Section의 속성에는 file/memory에서의 시작 위치, 크기, 엑세스 권한 등이 있습니다. Code, Data, Resourse Section은 각각의 특성, 접근 권한 

  등이 다르게 설정됩니다.



# 메모리 속성별 액세스 권한

[ 출처 : 리버싱 핵심원리 ]



- Section Header 구조체인 IMAGE_SECTION_HEADER 구조체에서 알아야 할 중요 멤버는 5가지가 있습니다. 

  ( VirtualSize, VirtualAddress, SizeOfRawData, PointerToRawData, Characteristics )



 #1. VirtualSize              

- 메모리에서 섹션이 차지하는 크기

 

 #2. VirtualAddress          

- 메모리에서 섹션의 시작 주소 (RVA)

 

 #3. SizeOfRawData        

- 파일에서 섹션이 차지하는 크기

 

 #4. PointerToRawData   

- 파일에서 섹션의 시작 위치

 

 #5. Characteristics         

- 섹션의 속성 (bit OR)


 #6. Name (참고)      

- 8Byte 크기의 세션 이름 값이 저장, PE 스펙에너느 섹션 Name에 대한 어떠한 명시적인 규칙이 없기 때문에 어떠한 값을 넣어

  도 되고 심지어 NULL로 채울 수도 있습니다. 따라서 섹션의 Name은 중요하지 않고, 참고 정도만 하시면 될 것 같습니다.



- VirtualAddress와 PointerToRawData는 아무 값이나 가질 수 없으며, 각각 (IMAGE_OPTIONAL_HEADER32에 정의된) SectionAlignment와 

  FileAlignment에 맞게 결정 됩니다. 앞에서 한번 언급했었는데, 파일에서의 섹션 크기와 메모리에 로딩되는 섹션의 크기는 다르다고 말했었습니다. 때문에, 

  VirtualSize 멤버의 값과 SizeOfRawData 멤버의 값은 일반적으로 다른 값을 가지게 됩니다.


- 마지막 멤버인 Characteristics 멤버의 값은 섹션의 속성 값을 bit OR 연산하여 나온 결과 값을 가지게 됩니다. 섹션의 속성들에는 어떤것들이 있는지 살펴보도록 

  하겠습니다.



[ 출처 : Microsoft Platform SDK - winnt.h ]



#1. IMAGE_SCN_CNT_CODE

- 실행 가능한 코드를 포함하는 세션임을 의미한다.

 

#2. IMAGE_SCN_CNT_INITIALIZED_DATA

- 이 세션은 코드가 실행되기 이전에 초기화가 되어 있던 데이터들이 포함되어 있는 데이터 세션임을 의미한다.

 

#3. IMAGE_SCN_CNT_UNINITIALIZED_DATA

- 이 세션은 초기화가 필요 없는 데이터가 포함된 세션을 의미하며, 로더는 이 메모리 내용에 대하여 0으로 초기화 한다.


#4. IMAGE_SCN_MEM_SHARED

- 이 모듈을 사용하는 모든 프로세스들이 이 플래그가 정의된 세션을 공유하도록 하는 플래그 값이다.


#5. IMAGE_SCN_MEM_EXECUTE

- 로더가 이 세션을 위한 메모리 할당 시 실행이 가능하도록 메모리를 할당하는 플래그 값이다.


#6. IMAGE_SCN_MEM_READ

- 로더가 이 세션을 위한 메모리 할당 시 읽기가 가능하도록 메모리를 할당하는 플래그 값이다.


#7. IMAGE_SCN_MEM_WRITE

- 로더가 이 세션을 위한 메모리 할당 시 쓰기가 가능하도록 메모리를 할당하는 플래그 값이다.


#8. IMAGE_SCN_MEM_DISCARDABLE

- 이 세션은 프로세스가 시작된 이후에 필요 없는 데이터임을 나타내며, 재배치 테이블이 이러할 수 있다.


#9. IMAGE_SCN_MEM_NOT_PAGED

- 이 세션이 페이지 아웃이 안 되고 항상 메모리에 있어야 함을 나타내는 플래그로 디바이스 드라이버의 세션에서 주로 확인할 수 있다.




# 지금까지 Section Header에 대해서 알아보았습니다. 마지막으로 Calc.exe 파일의 Section Header 영역을 PE View, 헥사 에디터로 한번 보도록 

  하겠습니다.




[ Calc.exe 파일의 Section Headers - HXD ]





- 헥사 에디터를 이용하여 Calc.exe 파일의 Section Header 영역을 살펴본 모습입니다. 오른쪽에 ANSI 형태로 표현된 값들을 살펴보면 총 4개의

  섹션이 존재한다는 것을 확인할 수 있습니다. ( .text  ,  .data  ,  .rsrc  ,  .reloc = 각각 40Byte씩 차지하고 있습니다)





[ Calc.exe 파일의 Section Headers - PE View ]


- 위의 사진은 PE View를 이용하여 본 모습입니다. 각 섹션별로 헤더 정보들을 자세하게 볼 수 있습니다.




# 마지막 Section Header 까지, 이렇게 PE File Format에 대하여 알아보았습니다. 저도 공부하는 입장이라 관련 도서 및 관련 자료들을 많이 참고

  하면서 포스팅하게 되었네요. 처음에는 대충 훑어보는데 엄청 어려운 내용들인줄 알았는데, 천천히 그리고 반복해서 계속 읽어보고 모르는 부분은

  여러 자료들 보면서 공부하니 조금이나마 이해가 된 것 같습니다. 제가 참고한 자료들은 아래에 정리해두겠습니다. 개인적으로 리버싱 핵심원리를

  많이 참고했습니다. 책에 좋은 팁들도 많고 초심자에게 정말 좋은 책인것 같습니다. 




# 참고 도서 및 참고 자료


- 리버싱 핵심원리

- Windows 구조와 원리

http://ko.wikipedia.org/wiki/MZ_%EC%8B%A4%ED%96%89_%ED%8C%8C%EC%9D%BC

PE File Format [koromoon].pdf

- PE Structure [Deok9].pdf





script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js">
And
prev | 1 | next