참고 강의: 드림핵(DreamHack) - System Hacking
System Hacking
dreamhack.io
STAGE 1. System Hacking Introduction
[환경 구축]
해당 강의는 Ubuntu 22.04(x86-64), x86-64 아키텍처를 기반으로 학습할 예정
VMware, Ubuntu 22.04 ISO 다운 -> 리눅스 가상환경 구축
[용어]
가상 머신 | 컴퓨터를 에뮬레이팅(emulating, 모방)한 것 |
호스트(Host) | 가상 머신을 작동시키는 컴퓨터 |
게스트(Guest) | 가상 머신 안에서 작동하는 컴퓨터 |
* 대표적인 가상화 소프트웨어: VMware, VirtualBox, Parallels, QEMU 등
STAGE 2. Background - Computer Science
시스템 해킹 기술은 컴퓨터 과학에 뿌리를 두고 있음
따라서 해킹 기술뿐만 아니라, 컴퓨터 과학의 전반을 이해하기 위한 노력이 필요
[컴퓨터 구조(Computer Architecture)]
컴퓨터가 효율적으로 작동할 수 있도록 하드웨어 및 소프트웨어 기능 고안 및 구성하는 방법
CPU | 연산(컴퓨터의 작동에 핵심) 처리 |
저장장치 | 데이터 저장 |
GPU | 그래픽 데이터 처리 |
랜카드 | 네트워크 통신 처리 |
사운드 카드 | 소리 데이터 처리 |
▶ 서로 다른 부품들이 모여 컴퓨터라는 하나의 기계로 작동
✓ 컴퓨터 구조는 아래를 모두 포함
1. 컴퓨터의 기능 구조에 대한 설계
2. 명령어 집합구조
3. 마이크로 아키텍처
4. 기타 하드웨어 및 컴퓨팅 방법에 대한 설계 등
[명령어 집합 구조(Instruction Set Architecture, ISA)]
CPU가 사용하는 명령어와 관련된 설계 구조 (CPU가 해석하는 명령어의 집합)
가장 널리 사용되는 ISA
▶ x86-64 아키텍처
(그 외 ISA: ARM, MIPS, AVR, x86 등)
※ 다양한 ISA가 개발되고 사용되는 이유?
모든 컴퓨터가 동일한 수준의 연산 능력을 요구하지 않으며, 컴퓨팅 환경도 다양하기 때문
[마이크로 아키텍처(Micro Architecture)]
명령어 집합을 효율적으로 처리할 수 있도록 CPU의 회로(하드웨어적) 설계
[폰 노이만 구조]
컴퓨터의 3가지 핵심 기능
• 연산
• 제어
• 저장
중앙처리장치(CPU) | 연산, 제어 기능 수행 |
기억장치(memory) | 저장 기능 수행 |
버스(Bus) | 장치간에 데이터나 제어 신호 교환을 위한 전자 통로 |
중앙처리장치(Central Processing Unit, CPU)
프로세스의 코드를 불러오고, 실행하고, 결과를 저장하는 일련의 모든 과정이 CPU에서 발생
구성 요소 | 내용 |
산술논리장치(ALU) | 산술/논리 연산 처리 |
제어장치(Control Unit) | CPU 제어 |
레지스터(Register) | CPU에 필요한 데이터를 저장 및 사용할 때 이용하는 보관소 |
기억장치(memory)
주기억장치 | 프로그램 실행에 필요한 데이터 임시 저장 (e.g., 램(RAM)) |
보조기억장치 | 운영체제, 프로그램 등의 데이터를 장기간 보관 (e.g., 하드 드라이브(HDD), SSD) |
버스(Bus)
컴퓨터-부품, 컴퓨터-컴퓨터 사이의 신호 전송 통로
데이터 버스 (Data Bus) |
데이터 이동 |
주소 버스 (Address Bus) |
주소 지정 |
제어 버스 (Control Bus) |
읽기/쓰기 제어 |
[x86-64 아키텍처]
대다수 개인용 컴퓨터는 x86-64 아키텍처 기반의 CPU를 탑재
• 워드(WORD)
CPU가 이해할 수 있는 데이터의 단위 (크기는 CPU가 어떻게 설계됐느냐에 따라 상이)
✓ WORD가 크면 유리한 점
16엑사 바이트(=16,777,216 테라바이트)의 가상메모리를 제공
→ 가용한 메모리 자원이 부족해서 소프트웨어 실행 불가능한 상황이 발생하지 않음
[레지스터]
산술 연산에 필요한 데이터와 주소를 저장 및 참조하는 등 다양한 용도로 사용
범용 레지스터(General Register)
주 용도 외에 다양한 용도로 사용 가능
세그먼트 레지스터(Segment Register)
• 과거 → 메모리 세그멘테이션, 가용 메모리 공간 확장 목적 사용
• 현재 → 메모리 보호를 위해 사용
- cs, ss, ds, es, fs, gs 존재 (각 레지스터의 크기 = 16비트)
- x64에서는 cs, ds, ss → 코드 영역과 데이터, 스택 메모리 영역 가리킬 때 사용
- 나머지 → 운영체제 별로 용도 결정
명령어 포인터 레지스터(Instruction Pointer Register, IP)
CPU가 실행해야 할 코드 지칭
- x64에서는 rip 사용
플래그 레지스터(Flag Register)
CPU의 상태 저장
- x64에서는 REFLAGS 존재 (64비트 중 20여개의 비트만 사용)
※ CPU의 동작과 메모리 사이에는 밀접한 연관이 존재
IF, 공격자가 메모리를 악의적으로 조작한다면?
조작된 메모리 값에 의해 CPU도 잘못된 동작 수행 가능성 有 → 메모리 오염(Memory Corruption)
메모리 오염을 유발하는 취약점 → 메모리 오염 취약점
• 메모리 오염 취약점 종류
1. Stack Buffer Overflow
2. Format String Bug
3. Use After Free
4. Double Free Bug
[리눅스 메모리 구조(Memory Layout)]
리눅스에서의 프로세스 메모리는 세그먼트(Segment)로 구분.
■ 세그먼트
적재되는 데이터의 용도별로 메모리의 구획을 나눈 것
→ 각 용도에 맞게 적절한 권한 부여 가능
■ 권한
읽기 권한, 쓰기 권한, 실행 권한
세그먼트 | 역할 | 일반적인 권한 | 사용 예 | |
코드 세그먼트 | 실행 가능한 코드가 저장된 영역 | 읽기, 실행 | main() 등의 함수 코드 | |
데이터 세그먼트 | 초기화된 전역 변수, 상수가 위치하는 영역 | 읽기, 쓰기 (data 세그먼트) |
초기화된 전역 변수 | |
읽기 (rodata(read-only data) 세그먼트) |
초기화된 전역 상수 | |||
BSS 세그먼트 | 초기화되지 않은 데이터가 위치하는 영역 | 읽기, 쓰기 | 초기화되지 않은 전역 변수 | |
스택 세그먼트 | 임시 변수가 저장되는 영역 | 읽기, 쓰기 | 지역 변수, 함수의 인자 등 | |
힙 세그먼트 | 실행 중에 동적으로 사용되는 영역 | 읽기, 쓰기 | malloc(), calloc() 등으로 할당받은 메모리 |
✓ 코드 세그먼트의 쓰기 권한이 없는 이유?
공격자가 악의적인 코드를 삽입하기 쉬워지기 때문
■ BSS 세그먼트
프로그램 시작 시 값이 모두 0으로 초기화
→ C코드 작성 시 초기화되지 않은 전역 변수의 값 = 0
■ 스택 세그먼트
스택 프레임(Strack Frame) 단위 사용
스택이 확장될 때 기존 주소보다 낮은 주소로 확장되기 때문에 ‘아래로 자란다’ 표현 사용
→ 함수 호출 시 생성, 반환 시 해제
■ 힙 세그먼트
스택 세그먼트와 반대 방향으로 할당
※ 힙과 스택 세그먼트가 자라는 방향이 반대인 이유?
두 세그먼트가 동일한 방향으로 자라게 되는 경우, 힙 세그먼트를 모두 사용 후 확장 과정에서 스택 세그먼트와 충돌하기 때문
So, 스택을 메모리 끝에 위치시키고, 힙과 스택을 반대로 자라게 함
→ 힙과 스택은 메모리를 최대한 자유롭게 사용 + 충돌 문제 해결 가능
[어셈블리어]
컴퓨터의 공통 언어 = 기계어(Machine Code)
시스템 해커는 컴퓨터의 언어로 작성된 소프트웨어에서 취약점을 발견해야 함
컴퓨터의 언어인 기계어는 0과 1로만 구성돼 있어서, 인간으로서는 이해하기가 매우 어려움
이를 해결하기 위해 David Wheeler는 EDSAC을 개발하면서 어셈블리 언어(Assembly Language)와 어셈블러(Assembler) 라는 것을 고안함
■ 어셈블러 작동 방식
인간언어 → 어셈블리 → 기계어
■ 역어셈블러 작동 방식
인간언어 ← 어셈블리 ← 기계어
x64 어셈블리어
문법 구조 | |
명령어(동사) | 피연산자(목적어) |
mov | eax, 3 |
대입해라 | eax에 3을 |
피연산자
[]으로 표현
■ 피연산자 종류
• 상수
• 레지스터
• 메모리
■ 앞에 크기 지정자인 TYPE PTR 추가 가능
• BYTE: 1바이트
• WORD: 2바이트
• DWORD: 4바이트
• QWORD: 8바이트
(e.g., QWORD PTR[0x8048000] -> 0x8048000의 데이터를 8바이트 만큼 참조)
명령어
명령코드 | ||
데이터 이동 | mov dst, src | src 값을 dst에 대입 |
lea dst, src | src의 유효 주소를 dst에 대입 | |
산술 연산 | add eax, 3 | eax += 3 |
sub eax, 3 | eax -= 3 | |
inc eax | eax += 1 | |
dec eax | eax -= 1 | |
논리 연산 | AND | 비트가 모두 1이면 1, 아니면 0 |
OR | 비트 중 하나라도 1이면 1, 아니면 0 | |
XOR | 비트가 서로 다르면 1, 같으면 0 | |
NOT | 비트 전부 반전 | |
비교 | cmp op1, op2 | op1에서 op2를 빼서 비교 후 플래그 설정 |
test op1, op2 | op1과 op2에 AND 연산 후 플래그 설정 | |
분기 | jmp addr | addr로 rip 이동 (jump) |
je addr | 직전에 비교한 두 피연산자가 같으면 점프 (jump if equal) | |
jg add | 직전에 비교한 두 피연산자 중 전자가 더 크면 점프 (jump if greater) | |
스택 | push val | rsp를 8만큼 빼고, 스택 최상단에 val 쌓음 |
pop reg | 스택 최상단의 값을 reg에 대입 후 rsp를 8만큼 더함 | |
프로시저 | call addr | addr의 프로시저 호출 |
leave | 프로시저가 반환되기 전, 스택 프레임 정리 | |
ret | 함수에서 반환해 원래 실행하던 코드로 돌아옴 | |
시스템 콜 | syscall | 유저 모드에서 커널 모드의 시스템 소프트웨어에게 동작 요청 |
* 스택
- LIFO (Last In, First Out, 후입선출) 방식으로 동작
- x86 아키텍처에서는 데이터를 추가할 때마다 메모리 주소가 감소하며, 데이터를 제거하면 다시 증가 하는 특성
* rsp (스택 포인터 레지스터)
- 항상 스택의 가장 위(Top)를 가리킴
✓ 시스템 콜(System call, syscall)
• 사용자 모드(User Mode)
운영체제가 사용자에게 부여하는 권한
→ 접근할 수 있는 메모리 영역 및 권한이 한정적 + 하드웨어에 직접적인 접근 불가능
• 커널 모드(Kernel Mode)
운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한
→ 모든 메모리 영역 및 하드웨어에 접근 가능
※ 모드를 구분하는 이유?
사용자가 권한 없이 운영체제 내부의 데이터를 읽거나 쓰는 경우 시스템에 악의적인 영향을 끼칠 수 있기 때문
'3. Pwnable (포너블) > 2) 개념 정리' 카테고리의 다른 글
[2025.04.04] 3주차 활동_Pwnabless (0) | 2025.04.04 |
---|---|
[2025.03.28] 2주차 활동 _ Pwnabless (0) | 2025.03.29 |
[24.05.18] 7주차 활동- 포너블 개념 (0) | 2024.05.18 |
[24.05.11] 6주차 활동- 포너블 개념 (0) | 2024.05.11 |
[24.05.04] 4주차 활동 - 리눅스 기초, 포너블 기초 (0) | 2024.05.05 |