[시스템해킹] Stage 1, 2
메모리 오염(Memory Corruption)
: 공격자가 메모리를 악의적으로 조작해 조작된 메모리값에 따라 CPU가 잘못 동작하도록 유도하는 것
- 이를 유발하는 취약점을 메모리 오염 취약점이라고 부름
- 시스템해킹에는 다양한 공격기법이 있으나, 많은 공격기법에 메모리오염을 기반으로 함
1. 세그먼트
: 리눅스에서 적재되는 데이터의 용도별로 메모리의 구획을 나눈 것
- 각 용도에 맞게 구획에 적절한 권한(R/W/E)을 부여할 수 있음
- CPU는 메모리에 대해 권한이 부여된 행위만을 할 수 있다
- 코드 세그먼트
- 데이터 세그먼트
- BSS 세그먼트
- 힙 세그먼트
- 스택 세그먼트
1-1. 코드 세그먼트(Code Segment)
: 실행 가능한 기계 코드가 위치하는 영역 (=Text Segment)
- ex) C 등으로 된 프로그램 코드가 컴파일되면 기계 코드로 변환되어 코드 세그먼트에 들어간다
- 프로그램이 동작하려면 여기서 코드를 읽어 실행할 수 있어야 함 -> 읽기&실행 권한이 부여됨
- / 쓰기 권한이 있으면 공격자가 악의적인 코드를 삽입하기 쉬워짐
- => 대부분의 현대 OS는 이 세그먼트에 쓰기 권한을 두지 않는다
1-2. 데이터 세그먼트(Data Segment)
: 컴파일 시점에 값이 정해진 전역 변수 및 전역 상수들이 위치함
- cpu는 이 세그먼트의 데이터를 읽을 수 있어야 함 -> 읽기 권한이 부여됨
- 쓰기가 가능한 세그먼트와 불가능한 세그먼트가 있음
- 쓰기가 가능한 세그먼트(data 세그먼트) : 프로그램이 실행되면서 값이 변할 수 있는 데이터(전역변수 등)가 위치함
- 쓰기가 불가능한 세그먼트(rodata(read-only data) 세그먼트) : 값이 변하면 안 되는 데이터(전역 상수 등)가 위치함
1-3. BSS 세그먼트(Block Started by Symbol Segment)
: 컴파일 시점에 값이 정해지지 않은 전역 변수가 위치함
- 읽기 및 쓰기 권한이 부여됨
- 개발자가 선언만 하고 초기화하지 않은 전역변수 등이 포함됨
- 시작될 때 모두 값이 0으로 초기화됨 (이 때문에 C에서 초기화되지 않은 전역변수는 0으로 정해짐)
1-4. 스택 세그먼트(Stack Segment)
: 함수 인자나 지역변수 같은 임시 변수들이 위치함
- 읽기 및 쓰기 권한이 부여됨
- 스택 프레임 단위로 사용됨. 함수가 호출될 때 생성, 반환될 때 해제.
- but 프로그램의 전체 실행 흐름은 사용자 입력 등 여러 요인에 영향받으므로
- 이 프로세스가 얼마만큼의 스택프레임을 사용하게 될지 미리 계산하는 것은 일반적으로 불가능
- => 프로세스 시작 시 OS는 작은 크기의 스택 세그먼트를 미리 할당해주고 부족해질 때마다 이를 확장함
- '아래로 자란다' = 스택이 확장될 때 기존 주소보다 낮은 주소로 확장되는 것을 일컬음
1-5. 힙 세그먼트(Heap Segment)
: 힙 데이터가 위치함 (malloc, calloc 등으로 할당되는 메모리)
- 일반적으로 읽기 및 쓰기 권한이 부여됨
- 스택과 마찬가지로 실행 중 동적 할당될 수 있으며 (리눅스에서는) 스택 세그먼트와 반대방향으로(위로) 자람
1. 컴퓨터 구조(Computer Architecture)
: 컴퓨터가 효율적으로 작동할 수 있도록 하드웨어 및 소프트웨어의 기능을 고안하고, 이들을 구성하는 방법
- 컴퓨터의 기능 구조에 대한 설계, 명령어 집합구조, 기타 하드웨어 및 컴퓨팅 방법에 대한 설계 등이 포함됨
- 폰 노이만 구조, 하버드 구조, 수정된 하버드 구조 등이 있음
1-1. 명령어 집합구조(Instruction Set Architecture, ISA)
: CPU가 사용하는 명령어와 관련된 설계
- 그 중에서도 인텔 x86-64 아키텍쳐가 가장 널리 쓰임
1-2. 마이크로 아키텍처(Micro Architecture)
: CPU의 하드웨어적 설계
- 정의된 명령어 집합을 효율적으로 처리할 수 있도록 CPU의 회로를 설계하는 분야
2. 폰 노이만 구조
: 연산, 제어, 저장의 세 가지 핵심 기능을 포함한 컴퓨터 구조
2-1. 중앙처리장치 (CPU)
: 프로그램의 연산을 처리하고 시스템을 관리하는 장치
- 산술논리장치(Arithmetic Logic Unit, ALU)
- 제어장치(Control Unit)
- 레지스터(Register)
2-2. 기억장치
: 컴퓨터가 동작하는데 필요한 여러 데이터를 저장하기 위한 장치
- 주기억장치: 프로그램 실행과정에서 필요한 데이터들을 임시로 저장하기 위해 사용됨
- (ex: 램(Random-Access Memory, RAM))
- 보조기억장치: 운영 체제, 프로그램 등과 같은 데이터를 장기간 보관하기 위해 사용됨
- (ex: 하드 드라이브(Hard Disk Drive, HDD), SSD(Solid State Drive))
2-3. 버스
: 컴퓨터 부품과 부품 사이 또는 컴퓨터와 컴퓨터 사이에 신호를 전송하는 통로
- 데이터 버스(Data Bus)
- 주소 버스(Address Bus)
- 제어 버스(Control Bus)
+ 랜선이나 데이터 전송 소프트웨어, 프로토콜 등도 버스라고 부름
3. 명령어 집합 구조
: CPU가 해석하는 명령어의 집합
- ISA는 IA-32 , x86-64(x64), MIPS, AVR 등 다양하게 존재함
- (모든 컴퓨터가 동일한 수준의 연산 능력을 요구하지 않으며 컴퓨팅 환경도 다양하기 때문)
- 인텔 x86-64: 고성능이나 전력소모/발열이 심함 -> 안정적인 전력공급과 냉각이 가능한 데스크톱/랩탑에 탑재
- ARM, MIPS, AVR: 전력소모/발열이 적음 -> 배터리로 작동하고 발열에 민감한 임베디드 기기, 스마트폰에 탑재
1. x86-64 아키텍처
: 인텔의 64비트 CPU 아키텍처
- 32비트를 1워드로 삼는 IA-32를 64비트 워드 환경에서 사용할 수 있도록 확장한 것
- 워드: CPU가 한번에 처리할 수 있는 데이터의 크기
2. x86-64 아키텍처 - 레지스터
: CPU가 데이터를 빠르게 저장하고 사용할 때 이용하는 보관소
- 범용 레지스터(General Register) : 다용도로 사용하는 레지스터
- 세그먼트 레지스터(Segment Register)
- 명령어 포인터 레지스터(Instruction Pointer Register, IP) : CPU가 어느 부분의 코드를 실행할지 가리키는 레지스터
- 플래그 레지스터(Flag Register) : 프로세서의 현재 상태를 저장하고 있는 레지스터
2-1. 레지스터 호환
- IA-32에서 CPU의 레지스터들은 32비트 크기를 가지며, 이들의 명칭은 각각 eax, ebx, ecx, edx, esi, edi, esp, ebp인데
- 호환성 면에서 이 레지스터들은 x86-64에서도 그대로 사용이 가능함
- 확장된 형태 : rax, rbx, rcx, rdx, rsi, rdi, rsp, rbp + eax, ebx(확장된 레지스터의 하위 32비트)
1. 어셈블리 언어
: 기계어의 난해성을 해결하기 위해 만들어진 초기의 프로그래밍 언어
- 컴퓨터의 기계어와 치환되는 언어 -> 기계어가 여러 종류라면 어셈블리어도 여러 종류여야 함
- ISA의 수만큼의 어셈블리어가 존재함
2. x64 어셈블리 언어
2-1. 기본 구조
- 명령어(Operation Code, Opcode) - 자연어의 동사에 해당
- 피연산자(Operand) - 자연어의 목적어에 해당 (상수, 레지스터, 메모리([])의 3종류가 있음)
2-1-1. 명령어
2-1-2. 피연산자
- 상수(Immediate Value)
- 레지스터(Register)
- 메모리(Memory) - []으로 둘러싸 표현
타입 : BYTE(1바이트), WORD(2바이트), DWORD(4바이트), QWORD(8바이트)
1. 데이터 이동
- MOV A, B : B에 들어있는 값을 A에 대입
- LEA A, B : B의 유효주소(Effective Address, EA)를 A에 저장
2. 산술연산
- ADD A, B : A += B
- SUB A, B : A -= B
- INC A : A++
- DEC A : A--
3. 논리연산
- AND A, B : A&B
- OR A, B : A|B
- XOR A, B : A와 B의 비트가 서로 다르면 1, 같으면 0
- NOT A : A의 비트 전부 반전
4. 비교
- CMP A, B : A와 B의 대소를 비교. 연산결과는 A에 대입하지 않음.
- 결과가 0이 될 경우 ZF플래그가 설정되어 동일 여부 판단 가능
- TEST A, B : A와 B에 AND 비트연산을 취해 비교. 연산결과는 A에 대입하지 않음.
- 두 피연산자가 같을 경우 연산 결과와 피연산자가 같게 되므로
- ZF플래그를 확인하여 1일 경우 피연산자가 0이었음을 확인할 수 있음
5. 분기
: rip를 이동시켜 실행 흐름을 바꿈
- JMP A : A로 rip를 이동시킴
- JE A : 직전에 비교한 두 피연산자가 같으면 점프 (Jump if Equal)
- JG A : 직전에 비교한 두 피연산자 중 전자가 더 크면 점프 (Jump if Greater)
1. Opcode : 스택
: 다음 명령어로 스택을 조작할 수 있음
- PUSH A : A를 스택에 PUSH
- POP A : 스택 최상단 값을 POP해 A에 대입
2. Opcode : 프로시저
- 프로시저: 특정 기능을 수행하는 코드 조각
- 호출: 프로시저를 부르는 행위
- 반환: 프로시저에서 들아오는 것
- 프로시저를 호출할 때는 프로시저를 실행한 뒤 원래의 실행흐름으로 돌아와야 하므로
- call 다음의 명령어 주소(return address, 반환 주소)를 스택에 저장한 뒤 rip를 이동시킴
- CALL A : A에 위치한 프로시저 호출
- LEAVE : 스택프레임(함수들끼리의 스택 사용영역을 구분하기 위한 임시값 저장영역) 정리
- RET : 반환주소로 반환
3. Opcode : 시스템 콜
- 커널 모드: 운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한
- 시스템의 모든 부분을 제어할 수 있음 - 여기까지 해커가 진입하면 시스템은 무방비
- 유저 모드: 운영체제가 사용자에게 부여하는 권한
- 시스템 콜(system call, syscall): 유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하는 것
- SYSCALL : 필요한 기능과 인자에 대한 정보를 레지스터로 전달하면 커널이 이를 읽어서 요청을 처리하는 함수