본문 바로가기

리버싱/x64dbg 디버거를 활용한 리버싱과 시스템 해킹의 원리

ch02 - 컴퓨터 시스템의 명령어 처리 구조

728x90

1. 데이터 표현 방식

2. 프로그램의 생성

3. 프로세스

4. 레지스터의 구성과 역할

 

1. 데이터 표현 방식

 1.1 데이터 표현 단위

 - 컴퓨터에선 이진 데이터를 기반으로 데이터를 표현. 바이트는 컴퓨터에서 문자를 표현하는 기본 단위이며, 1바이트로 표현하는 문자의 대표적인 예가 아스키코드이다.

컴퓨터에서 명령어 처리를 하거나 연산을 할 때는 워드 단위가 기반이고 4바이트 이상을 1 워드로 표현.

 

 1.2 숫자의 표현

 - 정수의 저장 방식을 엔디언(endian)이라 한다.

  • 빅 엔디언 - 원래의 순서대로 저장하는 방법

큰 숫자가 먼저 나타나며, 주로, IBM, Sun, Sparc, 네트워크 주소 등에서 사용한다.

ex) 0x12345678은 저장할 때 원래의 값 순서대로 [12 34 56 78]로 저장(리틀 엔디언과 상반됨)

 

  • 리틀 엔디언은 정수를 원래의 순서와 반대로 저장하는 방법

ex) 상위 자리의 값이 뒤에 나타나며 [78 56 34 12] 순서로 1바이트씩 저장된다.

 

  • 빅 엔디언 - 위의 두 가지를 선택적으로 적용할 수 있다.

 

 1.3 문자의 표현

 - 컴퓨터에서 문자의 표현 단위는 1바이트이다. 아스키코드는 미국표준협회가 제정한 코드이며 7비트로 문자를 표현한다. 

 

2. 프로그램의 생성

 2.1 프로그램의 생성

컴파일러(Compiler)는 고급 언어로 작성된 원시 프로그램(source program)을 기계어로 번역하는 프로그램을 말한다.

ex) 여기서 원시 프로그램은 고급 프로그래밍 언어로 작성된 것을 말한다. C, C++, JAVA, Pascal..

 

인터프리터(interpreter)는 원시 프로그램을 컴파일 과정 없이 직접 실행하는 방법이다. 대부분의 스크립트 프로그램은 인터프리터 형식으로 작동된다. ex) BASIC, java script, shell script, python..

https://lucete1230-cyberpolice.tistory.com/157

 

#컴파일러와 인터프리터 차이

설명을 들어보면 컴파일러나 인터프리터나 비슷해보인다. 둘다 사람이 이해할 수 있는 고급언어로 작성된 소스코드를 기계가 이해할 수 있는 기계어로 번역한 후에 프로그램을 실행하는 것이다.

즉, 차이는 컴파일러는 소스코드를 한번에 번역하고 인터프리터는 한줄 한줄 단위로 번역한다.

둘의 특징으로

컴파일러는 한번에 번역하기 때문에 시간이 오래걸리고 복잡하지만 한 번 번역을 하면 실행파일(목적파일)이 생성 되어 메모리를 사용해 다음에 실행할 때 인터프리터에 비해 빠르다, 반면에 인터프리터는 한줄씩 번역해 실행시간은 느리고 실행파일을 생성하지 않아 메모리를 사용하지 않는다.

  컴파일러 인터프리터
번역 단위 전체 한줄
실행 속도 상대적으로 빠름 상대적으로 느림
번역 속도 상대적으로 느림 상대적으로 빠름
목적파일 생성 유무 생성  생성 안함
메모리 할당 할당 받음 사용 안함

https://steady-snail.tistory.com/1

 

C 프로그램의 컴파일 과정은 전처리기(preporcessor), 컴파일러, 어셈블러, 링커 과정을 거쳐서 목적 프로그램을 로더(loader)에 의해 메모리에 적재되고 그 프로그램 명령어를 수행할 수 있게 된다.

전처리기는 데이터를 처리하기 전에 변경이 필요한 부분을 처리하는 과정이다. 

ex) C언어에서 #include <stdio.h>와 같은 형태로 가장 기본적인 라이브러리인 stdio를 가져와서 사용하는 것.

 

어셈블러는 어셈블리어 프로그램을 목적 코드로 변환한다. C언어 컴파일러는 C의 확장된 원시 프로그램을 해석하여 어셈블리어로 변환한다.

링커는 여러 목적 프로그램을 하나로 연결하는 역할을 수행한다. 링커에 의해 여러 컴파일된 라이브러리와 현재의 프로그램과 결합이 필요한 목적 프로그램을 결합시켜 하나의 목적 프로그램으로 생성한다. 큰 프로그램의 경우 여러 개의 원시 프로그램으로 작성될 수 있으나 실제 실행 프로그램은 링커로 하나로 만들어져야 한다.

 

 2.2 컴파일러의 역할

 - 컴파일러는 일반적으로 어휘 분석(lexical analysis), 구문 분석(syntax analysis), 의미 분석(semantic analysis),  코드 최적화(optimization) 과정을 거친다.

어휘 분석은 프로그램을 문법적 단위(Token)로 자른다. 그래서 예약어, 변수, 숫자, 문자열, 기호 문자 등으로 구분하고 구분된 단위는 구문 분석 과정을 거친다.

  • 구문분석 : 문법적 단위를 결합하여 프로그램의 문법 구조로 완성하면서 문법적 오류를 검사한다.

ex) C언어 문법에서 괄호가 맞지 않는 경우나 (;)세미콜론이 빠진 경우 등..

  • 의미분석 : 프로그램의 의미상의 오류를 검사한다. 주로 warning으로 표현되는 경우가 많다.

ex) 자료형이 불일치하거나 매개변수의 개수와 형(type)불일치하는 경우..

  • 코드최적화 : 목적 코드의 길이를 줄이고 실행시간을 단축시키기 위한 것. 

 

3. 프로세스

 - 프로세스는 실행 중인 프로그램이다. 프로그램을 실행시키면 코드가 메모리 주소 공간에 적재되고 첫 명령어 위치로 프로그램 카운터를 옮기게 된다. 이러한 프로세스는 운영체제 프로세스와 사용자 프로세스로 나눌 수 있다.

운영체제 프로세스 - 시스템 운영에 필요한 내용 관리, 프로세스 실행 순서 제어, 프로세스 영역 감시, 사용자 프로세스 생성, 입출력 프로세스 관리 등의 역할을 수행한다.

 

 3.1 프로세스 상태

 - 모든 프로세스는 종료될 때까지 준비, 실행, 대기 상태로 변화되며 실행되는 과정을 반복하고 이를 작업 스케줄러가 관리한다.

대부분 프로세스는 준비 큐에 대기하고 있으며 실행상태에는 하나의 프로세스만 존재하고 스케줄러는 준비 큐의 맨 앞에 있던 프로세스를 선택하여 CPU 자원을 쓸 수 있도록 한다. 실행상태의 프로세스는 CPU를 점유하여 사용하고 있는 상태이고 한 프로세스가 CPU를 독점하지 않도록 인터럽트 클록(Interrupt Clock)을 둬서 이용시간에 제한을 주며 시간이 지나도 반환하지 않으면 인터럽트가 발생하고 권한을 뺐는다.

 

 - 프로세스 제어 블록(PCB, Process Control Block)은 프로세스 관리를 위해 유지되는 레코드 데이터 블록으로 프로세스 생성 시 만들어지고 메인 메모리에 유지된다. 실행 중인 프로세스가 다른 프로세스로 CPU 제어를 넘겨줄 때, CPU의 레지스터 내용의 자원을 저장하고 이 저장되는 정보가 PCB이며 프로세스 상태를 표현한다.

프로세스 교환 : 운영체제가 다른 프로세스를 실행 상태로 변경하고 해당 프로세스에 제어를 넘겨주는 과정

문맥 교환 : 프로세스를 다른 프로세스로 교환하기 위해 이전 프로세스의 상태 레지스터 내용을 보관하고 다른 프로세스의 레지스터를 적재하는 일련의 과정

 

 3.2 프로세스의 실행

 - 프로세스는 고유의 식별자(PID)가 있어 프로세스 생성 시스템을 호출을 통해 새로운 프로세스를 생성 가능하며 서로 연결 리스트로 관계를 유지하는 일정한 구조체를 가지고 있다.

 

- 프로세스는 운영체제나 다른 사용자의 응용 프로그램 요청에 의해 생성되며 운영체제가 새로운 프로세스를 생성/추가하려면 프로세스 제어 블록(PCB)을 만든 후 프로세스에 주소 공간을 할당해야 한다. 그리고 프로세스의 모든 구성 요소를 할당할 수 있는 공간을 할당하고 PCB정보에서 프로세스의 상태 정보, 프로그램 카운터 등을 초기화하고 작업 대기 큐에 삽입

 

- 프로세스 생성 시에 부모/자식 관계를 유지하며 계층적으로 생성된다. 생성하는 프로세스를 부모 프로세스(Parent Process), 생성되는 프로세스를 자식 프로세스(Child Process) 또는 서브 프로세스라 부른다.

 

- 스레드(thread)는 CPU를 사용하는 기본 단위이며 프로그램 내 명령어를 실행 할 수 있는 제어 흐름을 표현하고 코드, 주소 공간, 운영체제의 자원 등을 공유한다. 하나의 작업을 할 때 적어도 하나의 스레드가 있어야 하며 안에 실행상태, 스택, 정적 저장소 등을 포함하고 있다.

 

- 하나의 프로세스에서 다중 스레드를 수행할 수 있는데 이는 프로세스 자원과 메모리를 공유해 자원의 오버헤드를 줄여서 자원 이용률을 높이고 다른 CPU에서 병렬 수행이 가능해 처리속도를 향상시킨다.

 

 3.3 프로세스 실행 정보의 확인

 - 작업 관리자에서 확인 가능

 프로세스 익스 플로러 : 프로세스 정보를 확인할 수 있는 도구로 프로세스 리스트를 트리 구조로 보여주고, 프로세스 설명, 제조 회사, 바이러스의 악성코드 매핑 회수 등 정보를 보여준다.

윈도우의 경우 작업관리자 프로그램에서 확인할 수 있다.(Ctrl + ALT + Del)

또는 프로세스 정보를 확인할 수 있는 도구로 프로세스 익스플로러(Process Explorer)가 있다.

[프로세스 실행 파일 위치, 스레드 동작, TCP/IP 네트워크 연결 정보, 계정과 권한 정보, 적재된 프로세스 공간의 문자열 정보 등..]

 

4. 레지스터의 구성과 역할

 - CPU(Central Processing Unit)는 소프트웨어 명령의 실행이 이뤄지는 컴퓨터의 부분 또는 그 기능을 내장한 칩을 말한다. CPU는 기계어로 쓰인 컴퓨터 프로그램에 따라 외부에서 정보를 입력, 기억, 연산하고 외부로 출력시키기 때문에 기능을 수행하는 레지스터(register), 산술 논리 연산장치(ALU, Arithmetic Logic Unit), 제어부(control unit)와 내부 버스가 필요하다.

[프로그램이 실행되면 실행 프로그램이 메모리에 적재되고 메모리의 내용을 CPU로 가져와 레지스터에 저장한 후 제어부를 통해서 ALU에 전달된 후 연산을 수행한다.]

 

 - 레지스터 : CPU 연산에 사용하는 작고 빠른 메모리로 고속 연산과 컴퓨터 프로그램 제어에 사용.(쉽게 c언어에서 변수라고 생각하면 편하다)

[주기억장치 -> 레지스터로 데이터를 옮겨와 데이터를 처리한 후 그 내용을 다시 레지스터 -> 주기억 장치로 저장하는 구조로 설계]

 

 4.1 레지스터 종류

 - 소프트웨어 레지스터/하드웨어 레지스터

 

소프트웨어 레지스터

역할에 따라 범용, 제어, 세그먼트, 상태로 구분

 

  • 범용 레지스터(GPR, Genaral Purpose Register) - 논리, 산술 연산에 사용되는 오퍼랜드나 주소 계산을 위한 오퍼랜드, 메모리 포인터에 사용

  • 명령어 레지스터(Instruction Register) - 명령어 주소를 계산하는 데 사용하는 것으로 IP(Instruction Pointer register)가 있다. PC(Program Counter)라고도 불리며, 프로그램 코드에서 다음에 수행해야 할 명령어의 위치를 저장하고 있다.
  • 세그먼트 레지스터(Segment Register) - 각 세그먼트의 위치를 나타내며, 물리 주소로 변환할 때 사용하며 세그먼트는 코드(text), 데이터(data), 스택(stack), 라이브러리 세그먼트 구성된다. FS(TEB주소 지정)와 GS는 여분의 세그먼트로 ES에 이어진 이름으로 사용되는데 데이터 전송 시 데이터(문자열) 시작 주소를 저장하는 데 사용

  • 플래그 레지스터(Flag Register) - 프로그램이 수행되는 순간마다 프로그램의 수행 상태를 비트 단위로 저장하며 연산 결과에서 캐리(비트가 오버됐는지)가 발생하였는지, 짝수인지, 음수인지 등을 표현.

 

하드웨어 레지스터

 - 프로세스를 제어하기 위한 제어 레지스터(Control Register)와 디버깅 모드에서 사용되는 디버그 레지스터(Debug Register)로 구분된다.

 

  • 제어 레지스터(Control Register)

CR0(paging) - PE(보호/리얼 모드)와 PG(no paging/paging)을 제어

CR2(PFLA, Page Fault Linear Address) - 페이징 오류 주소를 저장

CR3 - 페이지 데이터의 베이스 주소(PDBA, page-table directory base address)로, PG가 1일 때 선형 주소를 물리 주소로 바꿀 때 사용

CR4 - 보호 모드에서 8086 가상화 운영

CR8 - 64비트 모드에서 사용

 

  • 디버그 레지스터(Debug Register)

DR0~DR3 - 4개의 중단점(break point) 주소

DR6(Debug Status Register) - 디버그 상태를 발생시킴

DR7(Debug Control Register) - 중단점 상태를 선택적으로 활성화

 

 4.2 컴퓨터 명령어 코드

 - 컴퓨터 시스템의 명령어는 명령어 코드(Operation Code)오퍼랜드(Operand)로 구성

 -> 디버거에서 명령어에 대한 기계어와 어셈블리어 부분을 보여준다.

 

#명령어 처리 절차

1) EIP 주소에 접근하여 OP-code 부분(1바이트) 가져오기

2) OP-code를 해석해 명령어 길이만큼 EIP 주소 변경

3) Operand 부분 읽어옴

4) OP-code에 따라 명령어 실행

5) 실행 결과를 레지스터에 저장

6) 다음 EIP 주소에 접근

 

#컴퓨터의 주소 처리 방식

 - 직접 주소 vs 간접 주소

직접 주소(Direct Address) : 기억 장소 주소를 직접 지정하는 방법

간접 주소(Indirect Address) : 실제 피연산자가 있는 주소를 지정하는 방법