본문 바로가기

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

[2023.04.08] 씽씽이 활동보고

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

 

1. 디버거 화면 구성

PE 파일 실행 과정

PE 로더 : 실행파일의 데이터가 메모리에 적재된다.

CPU : 적재된 코드의 연산이 이루어진다.

CPU 레지스터 : 연산 결과 값을 기억하는 임시 저장소

 

CPU 연산 과정

0040 2000 주소에 있는 00 00 00 00EAX 레지스터에 저장한다.

섹션 주소 어셈블리 코드 레지스터
CODE 0040 1000 MOV EAX, DWORD PTR DS:[0040 2000] EAX : FFFF FFFF
  0040 1005 CMP EAX, 0 EBX : 0000 0001
0040 1008 JE SHORT 0040 1000 ...
0040 100A  
 
DATA 0040 2000 00 00 00 00 ZF : 0

- MOV A B : B값을 A로 옮긴다.

- DWORD : 4byte 크기

- PTR : 포인터

- DS : 데이터 세그먼트

 

EAX 레지스터의 값(0000 0000)0의 값을 비교한다.

섹션 주소 어셈블리 코드 레지스터
CODE 0040 1000 MOV EAX, DWORD PTR DS:[0040 2000] EAX : 0000 0000
  0040 1005 CMP EAX, 0 EBX : 0000 0001
0040 1008 JE SHORT 0040 1000 ...
0040 100A  
 
DATA 0040 2000 00 00 00 00 ZF : 0

CMP A B : AB의 값이 같은지 비교한다.

 

그 결과값을 ZF(ZeroFlag) 레지스터에 저장한다.

섹션 주소 어셈블리 코드 레지스터
CODE 0040 1000 MOV EAX, DWORD PTR DS:[0040 2000] EAX : 0000 0000
  0040 1005 CMP EAX, 0 EBX : 0000 0001
0040 1008 JE SHORT 0040 1000 ...
0040 100A  
 
DATA 0040 2000 00 00 00 00 ZF : 1

 

ZF의 값이 1이므로 0040 1000 주소로 이동한다.

섹션 주소 어셈블리 코드 레지스터
CODE 0040 1000 MOV EAX, DWORD PTR DS:[0040 2000] EAX : 0000 0000
  0040 1005 CMP EAX, 0 EBX : 0000 0001
0040 1008 JE SHORT 0040 1000 ...
0040 100A  
 
DATA 0040 2000 00 00 00 00 ZF : 1

- JE(Jump if Equal) : 연산 결과가 0이면(ZF=1) 이동하고, 아니면(ZF=0) 다음 명령을 실행한다.

 

 

OllyDBG 화면 구성

 

Assembled Code : 기계어를 어셈블리어로 번역한 부분이다. 어셈블리 코드를 실행 또는 중지할 수 있도록 환경을 제공한다.

레지스터 : 코드 연산 과정에서 필요한 값들을 임시로 저장하는 저장소이다. 용도에 따라 범용 레지스터, 세그먼트 레지스터, 플래그 레지스터 등으로 나뉜다.

Memory Dump : 메인 메모리에서 할당된 공간 중, 사용자가 원하는 영역의 데이터를 확인할 수 있도록 환경을 제공한다. 프로세스가 읽고 쓰는 값들을 확인 및 수정할 수 있다.

Stack : 데이터를 일시적으로 겹쳐 쌓아 두었다가 필요할 때 꺼내서 사용할 수 있다.

 

 

2. IA-32 어셈블리와 레지스터

IA-32(Intel Architecture, 32-bit) 또는 x86-32 인텔 32비트 마이크로프로세서에서 사용하는 명령 집합 아키텍처이며, 이전에 사용되던 IA-16 아키텍처의 32비트 확장이다.

IA-32 Instruction를 공부한다는 것은 기계어 체계를 알고, 어셈블리로 변환할 수 있는 능력을 갖추는 작업이다.

 

 기계어 : CPU가 알아들을 수 있는 코드 형태

어셈블리어 : 사람이 알아볼 수 있는 가장 원초적인 형태

 

- IA-32 Instruction은 크게 OpcodeOperand로 구성되어 있다.

- Opcode가 코드 행위의 주체라면, Operand는 행위에 필요한 부가적인 요소이다.

- Opcode를 제외한 나머지 항목들은 Operand의 타입과 크기를 결정하는 요소가 된다.

다음과 같은 기계어 코드가 있을 때, 변환을 하기 위해서는 0x68이 무슨 값인지부터 알아야 한다.

- 0x68PUSH 명령어이고, 오퍼랜드로 Iz 타입 값을 가진다.

- Operand타입표를 보면, Iz4바이트 상수 값을 의미한다.

※ BYTE : 1byte /  WORD : 2byte /  DWORD : 4 byte

 

 

자주 사용하는 어셈블리어

 

 

3. 어셈블리 맛보기

1. Assembly.exe 파일을 디버거에 올린다.

2. F7을 눌러 한 줄 씩 실행시킨다.

- 가장 첫 줄에 보이는 XOR 명령어는 레지스터를 초기화할 때 쓴다.

- 다음 줄을 보면 EAX 레지스터가 초기화된 것이 확인 가능하다.

 

- 2000 값을 EBA 레지스터로 옮긴다(MOV). 그리고 여기에 EDX를 더한다(ADD).

- 실행 파일이 메모리에 로드되면 EDX 레지스터에는 OEP 값이 들어간다. 이 값을 EBX 레지스터에 더한다.

=== OEP(OriginalEntryPoint) ========

OEP = ImageBase + AddressOfEntrypoint

===============================

 

- Assembly.00401010이 새로 등장했다.

- DWORD PTR DS:[EBX]

※대괄호 ‘[ ]’는 주소값, PTR은 기준, DWORD는 크기값이다.

 

- 00401010 주소에 있는 코드가 실행된다.

- 00401010부터 어떤 코드들이 있는지 확인해본다.

=> 먼저 16을 스택에 쌓고, 이거를 다시 ECX 레지스터에 넣는다. 00401013부터 00401020까지 반복동작을 하는데 이것이 복호화 코드다.

- 00403010 주소에 가면 암호화된 문자열 값을 볼 수 있다.

- 문자를 하나씩 복호화한다. (예를 들어 가나다가 있을 때 가나다를 한 번에 하는게 아니라 한 번, ‘한 번, ‘한 번 이렇게 문자열 개수만큼 복호화한다.)

- 이 문자열 count 값이 16이다.

 

- EBX + 10 주소에서 한 바이트를 가져와서 AL에 넣는다.

- 그리고 그걸 FFXOR

- 그 결과를 다시 EBX + 10 주소에 넣는다.

 

- 대문자 C가 나온다. 우리가 복호화를 한 것이다.

- ECX 레지스터 값을 1씩 줄이면서 0이 될 때까지 반복 동작한다.

 

- 복호화된 값이 파일 경로값이다.

- 이걸 스택에 쌓은 다음에 Delete 값을 호출 -> 파일을 삭제하는 API

 

 

 

4. StartUp 코드 이해하기

StartUp

     - 실행파일을 만드는 과정에서 컴파일러가 집어넣는 코드이다.

     - 파일이 동작하는데 필요한 설정 코드로 구성된다.

     - StartUp 코드를 빨리 건너뛰고 메인함수로 찾아갈 수 있어야 한다.

     - StartUp 코드는 컴파일 환경에 따라 형태가 달라진다.

 

1. 디버거를 실행해 메인함수를 찾는다.

 

2. 메인함수 내부의 코드를 확인한다.

 

3. GetCommandLineA() 함수를 찾는다.

 

4. 아래 표를 보면 main()함수가 GetCommandLineA()함수 아래, exit()함수 위에 있는 것을 알 수 있다.

5. 끝나는 함수를 찾는다.

 

6. 5개의 호출 중 하나로 범위가 좁혀졌다.

 

7. 메인함수의 원형은 인자와 환경변수정보를 같이 전달받는다. 그리고 컴파일 과정에서는 사용자가 어떻게 작성하더라도 원형으로 전달받음. (3개의 인자)

 

8. 따라서 세 개의 인자를 받는 아래의 함수가 main()함수인 것을 알 수 있다.

(아래는 메인함수 내부로 들어갔을 때)