시스템 해킹 #1

yxxun1216·2022년 9월 26일
0

System Hacking

목록 보기
1/4

Linux Memory Layout


배경지식✏️

컴퓨터의 구성 : 컴퓨터는 CPU와 메모리로 구성.

  • CPU : 실행할 명령어와 명령어 처리에 필요한 데이터를 메모리에서 읽고, Instruction Set Architecture(ISA)에 따라 처리
    연산의 결과는 다시 메모리에 적재

메모리 오염 : 공격자가 메모리를 악의적으로 조작하면 CPU가 잘못된 동작을 가능
이를 유발하는 취약점을 메모리 오염 취약점(Memory Corruption)이라고 함.

  • 메모리 오염 취약점의 예 : Stack Buffer Overflow, Off by One, Format String Bug, Double Free Bug, Use After Free, etc.

리눅스 프로세스의 메모리 구조💻

리눅스의 메모리는 5가지의 세그먼트(segment)로 구분.
코드 세그먼트, 데이터 세그먼트, BSS 세그먼트, 힙 세그먼트, 스텍 세그먼트로 구분함.

  • 세그먼트 : 적재되는 데이터의 용도별로 메모리의 구획을 나눈 것

메모리를 나누는 이유 : 각 용도에 맞게 적절한 권한 부여 가능. 권한에는 읽기, 쓰기, 실행이 있음.


1. 코드 세그먼트(Code Segement)

실행 가능한 기계코드가 위치하는 영역. 텍스트 세그먼트(Text Segment)라고도 함.

  • 읽기 권한과 실행 권한이 부여 -> 프로그램이 동작하려면 코드를 실행해야 하기 때문

  • 쓰기 권한 제거 -> 쓰기 권한이 주어질 경우 공격자가 악의적인 코드 삽입이 용이하여 제거
int main() { return 31337; }
//main 함수 컴파일 시 정수가 기계코드로 변환되는데, 이는 코드 세그먼트에 위치하게 됨.

2. 데이터 세그먼트(Data Segment)

컴파일 시점에 값이 정해진 전역 변수 및 전역 상수가 위치하는 영역.

  • 읽기 권한 부여 -> CPU가 이 세그먼트의 데이터를 읽을 수 있어야 하기 때문

  • 쓰기가 가능한 세그먼트와 쓰기가 불가능한 세그먼트로 분류

    - 쓰기가 가능한 세그먼트 : 전역 변수와 같이 프로그램이 실행되면서 값이 변할 수 있는 데이터 위치
    data 세그먼트라고 부름

    - 쓰기가 불가능한 세그먼트 : 프로그램이 실행되면서 값이 변하면 안되는 데이터 위치
    rodata(read-only data)라고 부름
int data_num = 31337; //data
char data_rwstr[] = "writable_data"; //data
const char data_rostr[] = "readonly_data"; //rodata
char *str_ptr = "readonly"; //str_ptr은 data, 문자열은 rodata

int main() {}

3. BSS 세그먼트(BSS Segment)

컴파일 시점에 값이 정해지지 않은 전역 변수가 위치하는 영역. (ex. 초기화 하지 않은 전역변수)
프로그램이 시작될 때 모두 0으로 값이 초기화.

  • 읽기 권한 및 쓰기 권한 부여
int bss_data;
 
int main() {
	printf("%d\n",bss_data); //0
	return 0;

4. 스택 세그먼트(Stack Segment)

프로세스의 스택이 위치하는 영역.
함수의 인자나 지역 변수와 같은 임시 변수들 저장.

  • 스택 프레임(Stack Frame) 단위로 사용
    -함수가 호출될 때 생성되고 반환될 때 해제

  • 프로세스 시작 시 작은 크기의 스택 세그먼트 할당 후 부족할 때마다 확장
    - 스택 확장 시 기존 주소보다 낮은 주소로 확장되어 '아래로 자란다'는 표현 사용

  • 읽기 권한과 쓰기 권한 부여 -> CPU가 자유롭게 값을 읽고 쓸 수 있어야 하기 때문
void func() {
  int choice = 0; //지역 변수 choice가 스택에 저장
  
  scanf("%d",&choice);
  
  if (choice) //유저가 입력한 choice에 따라 결과 다름
  	call_true();
  else
  	call_false();
  
  return 0;
 }

5. 힙 세그먼트(Heap Segment)

힙 데이터가 위치하는 영역.
실행 중에 동적으로 할당 가능하며 스택 세그먼트와 반대 방향으로 자람.
-> 힙과 스택이 메모리를 최대한 자유롭게 사용하고 충돌 문제에서도 비교적 자유로워 짐.

  • 읽기 권한과 쓰기 권한 부여
int main() {
  int *heap_data_ptr = 
  malloc(sizeo(*heap_data_ptr)); //동적 할당한 힙 영역의 주소
  *heap_data_ptr = 31337; //힙 영역에 값을 씀
  printf("%d\n",*heap_data_ptr); //힙 영역의 값을 사용함
  return 0;
}

6. 정리

세그먼트역할일반적인 권한사용 예
코드 세그먼트실행 가능한 코드가 저장된 영역읽기, 실행main() 등의 함수코드
데이터 세그먼트초기화 된 전역 변수 또는 상수가 위치하는 영역읽기와 쓰기 또는 읽기 전용초기화 된 전역 변수, 전역 상수
BSS 세그먼트초기화되지 않은 데이터가 위치하는 영역읽기, 쓰기초기화되지 않은 전역 변수
스택 세그먼트임시 변수가 저장되는 영역읽기, 쓰기지역 변수, 함수의 인자 등
힙 세그먼트실행 중에 동적으로 사용되는 영역읽기, 쓰기malloc(), calloc() 등으로 할당받은 메모리

Computer Architecture


컴퓨터 구조💻

컴퓨터가 효율적으로 작동할 수 있도록 하드웨어 및 소프트웨어의 기능을 고안하고 이들을 구성하는 방법.

컴퓨터의 기능 구조에 대한 설계, 명령어 집합구조, 마이크로 아키텍처, 기타 하드웨어 및 컴퓨팅 방법에 대한 설계 등 포함.

  • 기능 구조의 설계
    - 컴퓨터가 연산을 효율적으로 하기 위해 어떤 기능들이 필요한지 고민하고 설계하는 분야
    - ex. 폰 노이만 구조, 하버드 구조, 수정된 하버드 구조

  • 명령어 집합구조(Instruction Set Architecture)
    - CPU의 명령어에 대한 설계
    - ex. ARM, MIPS, AVR, 인텔 x86, x86-64

  • 마이크로 아키텍처(Micro Architecture)
    - CPU의 하드웨어적 설계
    - 정의된 명령어 집합을 효율적으로 처리할 수 있도록 CPU의 회로를 설계하는 분야

1. 폰 노이만 구조


폰 노이만은 컴퓨터에 연산, 제어, 저장의 세 가지 핵심 기능이 필요하다고 생각함.
이를 위해 근대의 컴퓨터는 중앙처리장치, 기억장치, 버스 사용.

중앙처리장치(Central Processing Unit, CPU)

프로그램의 연산 처리, 시스템을 관리하는 컴퓨터의 두뇌.
프로세스의 코드를 불러오고, 실행하고, 결과를 저장하는 과정이 일어남.

  • 산술논리장치, 제어장치, 레지스터 등으로 구성
    - 산술논리장치(Arithmetic Logic Unit, ALU) : 산술/논리 연산 처리
    - 제어장치(Control Unit) : CPU 제어
    - 레지스터(Register) : CPU에 필요한 데이터 저장
    데이터 교환속도를 획기적으로 단축하기 위해 레지스터와 캐시라는 저장장치를 가짐

기억장치(memory)

컴퓨터가 동작하는데 필요한 여러 데이터를 저장하기 위해 사용.

  • 주기억장치와 보조기억장치로 구성

    - 주기억장치 : 프로그램 실행과정에서 필요한 데이터들을 임시로 저장하기 위해 사용
    대표적으로 램(Random-Access Memory, RAM)이 있음

    - 보조기억장치 : 운영체제, 프로그램 등과 같은 데이터를 장기간보관하고자 할 때 사용
    대표적으로 하드 드라이브(Hard Disk Drive, HDD), SSD(Solid State Drive)가 있음

버스(bus)

컴퓨터 부품과 부품 사이 또는 컴퓨터와 컴퓨터 사이에 신호를 전송하는 통로.

  • 데이터 버스, 주소 버스, 제어 버스 등 있음
    - 데이터 버스(Data Bus) : 데이터가 이동
    - 주소 버스(Address Bus) : 주소를 지정
    - 제어 버스(Control Bus) : 읽기/쓰기를 제어

  • 랜선, 데이터 전송 소프트웨어, 프로토콜 등도 버스라고 불림


2. 명령어 집합 구조(Instruction Set Architecture, ISA)

CPU가 해석하는 명령어의 집합.
프로그램은 기계어로 이루어져 있고, 실행하면 이 명령어들을 CPU가 읽고 처리.
컴퓨터의 연산 능력 수준과 컴퓨팅 환경이 다양하기 때문에 ISA도 IA-32, x86-65(x64), MIPS, AVR 등 다양하게 존재.

x86-64 아키텍처

인텔의 64비트 CPU 아키텍처.
인텔의 32비트 CPU 아키텍처인 IA-32를 64비트 환경에서 사용할 수 있도록 확장.

  • n 비트 아키텍처 : n은 CPU가 한번에 처리할 수 있는 데이터의 크기
    컴퓨터과학에서는 CPU가 이해할 수 있는 데이터의 단위라는 의미에서 WORD라고 부름
    - WORD의 크기 : CPU가 어떻게 설계되었느냐에 따라 달라짐
    - WORD가 크면 유리한 점 : 가용한 메모리 자원이 부족하여 소프트웨어 최고 성능을 낼 수 없거나 실행이 불가능한 상황이 거의 발생하지 않음

x86-64 아키텍처 : 레지스터

CPU가 데이터를 빠르게 저장하고 사용할 때 이용하는 보관소.
산술 연산에 필요한 데이터를 저장하거나 주소를 저장하고 참조하는 등 다양한 용도로 사용.

  • 범용 레지스터, 세그먼트 레지스터, 명령어 포인터 레지스터, 플래그 레지스터 존재

    - 범용 레지스터(General Register)
    주용도와 다양한 용도로 사용될 수 있는 레지스터
    8바이트를 저장할 수 있으며, 부호 없는 정수를 기준으로 2^64-1까지의 수를 나타낼 수 있음

    자주 쓰이는 범용 레지스터
    이름주용도
    rax (accumulator register)함수의 변환값
    rbx (base register)x64에서 주된 용도 없음
    rcx (counter register)반복문의 반복 횟수, 각종 연산의 시행 횟수
    rdx (data register)x64에서 주된 용도 없음
    rsi (source index)데이터를 옮길 때 원본을 가리키는 포인터
    rdi (destination index)데이터를 옮길 때 목적지를 가리키는 포인터
    rsp (stack pointer)사용중인. 스택의 위치를 가리키는 포인터
    rbp (stack base pointer)스택의 바닥을 가리키는 포인터

    - 세그먼트 레지스터
    cs, ss, ds, es, fs, gs의 6가지 세그먼트 레지스터가 존재
    각 레지스터의 크기는 16비트
    용도에 변화 : 물리 메모리의 크기 확장 -> 코드 영역과 스택 메모리 영역 가리키며 나머지는 운영체제 별로 용도를 결정할 수 있도록 제작

    - 명령어 포인터 레지스터
    CPU가 어느 부분의 코드를 실행할지 가리킴
    x64 아키텍처의 명령어 레지스터는 rip이며 크기는 8바이트

    - 플래그 레지스터
    프로세서의 현재 상태를 저장하고 있는 레지스터
    x64 아키텍처에는 RFLAGS라고 불리는 64비트 플래그 레지스터 존재(과거 16비트 플래그 레지스터가 확장)
    여러 비트들로 CPU의 현재 상태 표현 자주 쓰이는 플래그 레지스터
    플래그의미
    CF(Carry Flag)부호 없는 수의 연산 결과가 비트의 범위를 넘을 경우 설정
    ZF(Zero Flag)연산의 결과가 0일 경우 설정
    SF(Sign Flag)연산의 결과가 음수일 경우 설정
    OF(Overflow Flag)부호 있는 수의 연산 결과가 비트 범위를 넘을 경우 설정

레지스터 호환

x86-64 아키텍처는 IA-32의 64비트 확장 아키텍처이며 호환 가능.
IA-32에서 CPU의 레지스터들은 32비트 크기이며 각각의 명칭은 eax, ebx, ecx, edx, esi, edi, esp, ebp임.


x86 Assembly


기본 구조🧱

명령어(Operation Code, Opcode)와 피연산자(Operand)로 구성.

  • 명령어 종류
    - 데이터 이동(Data Transfer) : mov, lea
    - 산술 연산(Arithmetic) : inc, dec, add, sub
    - 논리 연산(Logical) : and, or, xor, not
    - 비교(Comparison) : cmp, test
    - 분기(Branch) : jmp, je, jg
    - 스택(Stack) : push, pop
    - 프로시저(Procedure) : call, ret, leave
    - 시스템 콜(System call) : syscall

피연산자

피연산자에는 상수(Immediate Value), 레지스터(Register), 메모리(Memory)가 있음.

  • 메모리 피연산자
    - QWORD PTR [0x8048000] : 0x8048000의 데이터를 8바이트만큼 참조
    - DWORD PTR [0x8048000] : 0x8048000의 데이터를 4바이트만큼 참조
    - WORD PTR [rax] : rax가 가르키는 주소에서 데이터를 2바이트만큼 참조

명령어

데이터 이동 명령어

  • 어떤 값을 레지스터나 메모리에 옮기도록 지시
    - mov rdi, rsi : rsi의 값을 rdi에 대입
    - mov QWORD PTR [rdi], rsi : rsi의 값을 rdi가 가리키는 주소에 대입
    - mov QWORD PTR [rdi+8*rcx], rsi : rsi의 값을 rdi+8rcx가 가리키는 주소에 대입
    - lea rsi, [rdx+8
    rcx] : rbx+8*rcx를 rsi에 대입

산술 연산 명령어

  • 덧셈, 뺄셈, 곱셈, 나눗셈 연산 지시
    - add dst, src : dst에 src의 값을 더함
    - sub dst, src : dst에서 src의 값을 뺌
    - inc op : op의 값 1 증가
    - dec op : op의 값 1 감소

논리 연산 명령어

  • and, or, xor, neg등 비트 연산 지시
    - and dst, src : dst와 src의 비트가 모두 1이면 1, 아니면 0
    - or dst, src : src의 비트 중 하나라도 1이면 1, 아니면 0
    - xor dst, src : dst와 src의 비트가 서로 다르면 1, 같으면 0
    - not op : op의 비트 전부 반전

비교 명령어

  • 두 피연산자의 값을 비교, 플래그 설정
    - cmp op1, op2 : 두 피연산자를 빼서 대소 비교, 결과는 op1에 대입 안 됨
    결과가 0인 경우 ZF플래그 설정
    - test op1, op2 : 두 피연산자에 AND 비트연산, 결과는 op1에 대입 안 됨
    결과가 0인 경우 ZF 플래그 설정

분기 명령어

  • rip를 이동시켜 실행 흐름 바꿈
    - jmp addr : addr로 rip 이동
    - je addr : 직전에 비교한 두 피연산자가 같으면 점프
    - jg addr : 직전에 비교한 두 연산자 중 전자가 더 크면 점프

Opcode : 스택

  • 다음 명령어로 스택 조작
    - push val : val을 스택 최상단에 쌓음
    - pop reg : 스택 최상단의 값을 꺼내서 reg에 대입

Opcode : 프로시저

  • 특정 기능을 수행하는 코드 조각으로 반복되는 연산을 프로시저 호출로 대체할 수 있어 전체 코드의 크기 축소 가능하며 기능별로 코드 조각에 이름을 붙일 수 있어 가독성 향상

  • 프로시저를 부르는 행위를 호출(Call), 프로시저에서 돌아오는 것을 반환(Return)이라고 하며 call 다음의 명령어 주소를 스택에 저장하고 프로시저로 rip를 이동

    - call addr : addr에 위치한 프로시저 호출
    - leav : 스택프레임 정리
    - ret : return address로 반환

Opcode : 시스템 콜

  • 운영체제는 커널 모드와 유저 모드로 권한이 나뉘는데 시스템 콜은 유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용

  • 유저 모드의 소프트웨어가 도움을 요청 -> 커널이 요청한 동작 수행 -> 유저에게 결과 반환

    - syscall 명령어

    출처 : https://dreamhack.io/

0개의 댓글