• 프로세스의 메모리 구조
프로세스의 메모리를 5가지의 세그먼트로 구분하고, 운영체제가 메모리를 용도별로 나누며 각 용도에 맞게 적절한 권한을 부여한다(읽기, 쓰기, 실행) -> CPU가 메모리에 대해 권한이 부여된 행위만 할 수 있다.
1. 코드 세그먼트
- 실행 가능한 기계 코드가 위치하는 영역으로 텍스트 세그먼트라고 불린다.
- 프로그램이 동작하려면 코드를 실행(읽기, 실행 권한) => 쓰기 권한은 공격자가 악의적인 코드를 삽입하기가 쉬워져 제거한다
- main() 등의 함수 코드
2. 데이터 세그먼트
- 컴파일 시점에 값이 정해진 전역 변수 및 전역 상수들이 위치한다.
- CPU가 데이터 세그먼트를 읽을 수 있어야 하므로, 읽기 권한이 부여된다.
- data 세그먼트 : 전역 변수와 같이 프로그램이 실행되면서 값이 별할 수 있는 데이터
- rodata 세그먼트 : 프로그램이 실행되면서 값이 변하면 안 되는 데이터
3. BSS 세그먼트
- 컴파일 시점에 값이 정해지지 않은 전역 변수가 위치한 메모리
- 프로그램이 시작될 때, 모두 0으로 값이 초기화
- 보통 개발자가 선언만 하고 초기화하지 않은 전역 변수 등 포함
- 읽기 및 쓰기 권한이 부여
4. 힙 세그먼트
- 실해중에 동적으로 할당될 수 있으며, 리눅스에서는 스택 세그먼트와 반대 방향으로 기존 주소보다 높은 주소로 확장된다.
- C언어에서 malloc(), calloc() 등을 호출해서 할당 받는 메모리가 힙 세그먼트에 위치하게 되고, 일반적으로 읽기와 쓰기 권한이 부여된다.
5. 스택 세그먼트
- 프로세스의 스택이 위치하는 영역(함수의 인자나 지역 변수와 같은 임시 변수들이 실행중에 저장되는 영역)
- 스택 프레임 단위로 사용
- 운영체제는 프로세스를 시작할 때 작은 크기의 스택 세그먼트를 먼저 할당해 주고, 부족해질 때마다 이를 확장한다.(기존 주소보다 낮은 주소로 확장된다)
* 힙과 스택 세그먼트가 자라는 방향이 반대인 이유
- 기존의 힙 세그먼트를 모두 사용하고 나면, 이를 확장하는 과정에서 스텍 세그먼트와 충돌하게 된다.
#include <stdlib.h> int a = 0xa; //a은 데이터 세그먼트 const char b[] = "d_str"; // b와 d_str은 rodata 세그먼트 int c; // BSS 세그먼트 int foo(int arg) { //foo 는 코드 세그먼트 int d = 0xd; //d은 스택 세그먼트 retrun 0; } int main() { int *e = malloc(sizeof(*e)); //e는 힙 세그먼트 return 0; } |
• 컴퓨터 구조
1. 폰 노이만 구조
컴퓨터에 연산, 제어, 저장의 세 사지 핵심 기능이 필요하다. 연산과 제어를 위한 중앙처리장치(CPU), 저장을 위해 기억 장치(memory)를 사용한다. 장치 간에 데이터나 제어 신호를 교환할 수 있도록 버스(bus)라는 전자 통로를 사용한다.
- 중앙처리장치
프로세스의 코들르 불러오고, 실행하고, 결과를 저장하는 일련의 모든 과정이 일어나는 곳
산술논리장치와 cpu를 제어하는 제어장치, 데이터를 저장하는 레지스터 등으로 구성되어 있다.
- 기억장치
컴퓨터가 동작하는데 필요한 여러 데이터를 저장하기 위해 사용되며 용도에 따라 주기억장치와 보조기억장치로 분류된다.
주기억장치는 프로그램 실행과정에서 필요한 데이터들을 임시로 저장하기 위해 사용 -> 램(RAM)
보조기억장치는 운영체제, 프로그램 등과 같은 데이터를 장기간 보관하고자 할 때 사용 -> 하드 드라이브(HDD), SSD
- 버스
데이터가 이동하는 데이터 버스, 주소를 지정하는 주소 버스, 읽기/쓰기를 제어하는 제어 버스가 있다.
랜선이나 데이터 전송 소프트웨어, 프로토콜 등도 버스라고 불린다.
* CPU안에 레지스터가 필요한 이유
필요한 데이터를 빠르게 공급하고, 반출할 수 있어야 자신의 효율을 제대로 발휘할 수 있다. CPU는 교환속도를 획기적으로 단축하기 위해 레지스터와 캐시라는 저장장치를 내부에 갖고 있습니다.
• x86-64 아키텍처
- 인텔의 64비트 CPU 아키텍처이다.
- 레지스터는 CPU가 데이터를 빠르게 저장하고 사용할 때 이용하는 보관소이며, 산술 연산에 필요한 데이터를 저장하거나 주소를 지정하고 참조하는 등 다양한 용도로 사용한다.
1. 범용 레지스터
- 8바이트를 저장할 수 있으며, 부호 없는 정수를 기준으로 2^64-1까지의 수를 나타낼 수 있다.
이름 | 주용도 |
rax (accumulator register) | 함수의 반환 값 |
rbx (base register) | x64에서는 주된 용도 없음 |
rcx (counter register) | 반복문의 반복 횟수, 각종 연산의 시행 횟수 |
rsi (source index) | 데이터를 옮길 때 원본을 가리키는 포인터 |
rdi (destination index) | 데이터를 옮길 때 목적지를 가리키는 포인터 |
rsp (stack pointer) | 사용중인 스택의 위치를 가리키는 포인터 |
rbp (stack base pointer) | 스택의 바닥을 가리키는 포인터 |
2. 세그먼트 레지스터
- cs, ss, ds, es, fs, gs 총 6가지 세그먼트 레지스터가 존재하며, 레지스터의 크기는 16비트이다.
- cs, ds, ss 레지스터는 코드 영역과 데이터,스택 메모리 영역을 가리킬 때 사용되고 나머지는 레지스터 운영체제 별로 용도를 결정할 수 있도록 범용적인 용도로 제작된 세그먼트 레지스터이다.
3. 명령어 포인트, 레지스터
- CPU가 어느 부분의 코드를 실핼할지 가리키는 레지스터이다.
- rip 아키텍처의 명령어이며, 크기는 8바이트이다.
4. 플래그 레지스터
- 프로세스의 현재 상태를 저장하고 있는 레지스터이다.
- RFLAGS라고 불리며 64비트 크기의 플래그 레지스터가 존재한다.
- 자신을 구성하는 여러 비트들로 CPU의 현재 상태를 표현한다.
- 최대 64비트이므로 최대 64개의 플래그를 사용할 수 있지만, 실제로는 오른쪽의 20여 개의 비트만 사용한다.
플래그 | 의미 |
CF (Carry Flag) | 부호 없는 수의 연산 결과가 비트의 범위를 넘을 경우 설정 됩니다. |
ZF (Zero Flag) | 연산의 결과가 0일 경우 설정 됩니다. |
SF (Sign Flag) | 연산의 결과가 음수일 경우 설정됩니다. |
OF (Overflow Flag) | 부호 있는 수의 연산 결과가 비트 범위를 넘을 경우 설정 됩니다. |
rax는 64비트 기준
eax는 32비트
ax는 16비트
ah, al는 8비트
ah(high)는 ax의 상위 8비트
al(low)는 ax의 하위 8비트
// 한문자에 4비트 이다.
rax: 0x0123456789abcdef // 64비트 기준
eax: 0x89abcdef // 32비트
ax : 0xcdef // 16비트
ah : 0xcd //상위 8비트
al : 0xef // 하위 8비트
'모의해킹 > 시스템 해킹' 카테고리의 다른 글
shell_basic (0) | 2023.12.09 |
---|---|
Welcome (0) | 2023.12.09 |
qemu & gdb 세팅 (0) | 2023.05.30 |
리눅스 커널 & 파일 시스템 빌드 (0) | 2023.05.29 |
리눅스 커널 해킹 (배경 지식) (0) | 2023.05.27 |