또 뭐하지
[포너블 기초] Dreamhack System Hacking (1) 본문
환경구축
Ubuntu 22.04 기반
윈도우 : VMware, VirtualBox, WSL2
맥 : VirtualBox
Background - Computer Science
1. Computer Architecture
(1) 컴퓨터 구조 (Computer Architecture)
: 컴퓨터가 효율적으로 작동할 수 있도록 하드웨어 및 소프트웨어의 기능을 고안하고, 이들을 구성하는 방법
- 컴퓨터의 기능 구조에 대한 설계 : 연산을 효율적으로 하기 위한 기능을 설계.
ex) 폰 노이만 구조, 하버드 구조, 수정된 하버드 구조
- 명령어 집합구조 : CPU가 처리하는 명령어를 설계
ex) ARM, MIPS, AVR, 인텔의 x86 및 x86-64 등
- 마이크로 아키텍처 : 명령어 집합을 처리하는 CPU 회로를 설계
ex) 캐시 설계, 파이프라이닝, 슈퍼 스칼라, 분기 예측, 비순차적 명령어 처리
- 하드웨어 및 컴퓨팅 방법에 대한 설계 등
ex) 직접 메모리 접근
- 폰 노이만 구조
컴퓨터의 세 가지 핵심 기능 : 연산, 제어, 저장
중앙처리장치 (Central Processing Unit, CPU) : 연산, 제어
* 프로그램의 연산 처리, 시스템 관리
* CPU 구성
- 산술논리장치 (Arithmetic Logic Unit, ALU) : 산술/논리 연산 처리
- 제어장치 (Control Unit) : CPU 제어
- 레지스터 (Register) : CPU에 필요한 데이터 저장
기억장치 (memory) : 저장
* 컴퓨터가 동작하는데 필요한 데이터 저장
* 용도에 따른 저장 장치 분류
- 주기억장치 : 프로그램 실행과정에서 필요한 데이터 임시 저장. ex) 램(Random-Access Memory, RAM)
- 보조기억장치 : 데이터 장기간 보관. ex) 하드 드라이브(Hard Disk Drive, HDD), SSD(Solid State Drive)
버스 (bus) : 장치간 데이터 및 제어 신호 교환 전자 통로
* 컴퓨터 내부 또는 외부에서 신호를 전송하는 통로
ex) 데이터 버스(Data Bus), 주소 버스(Address Bus), 제어 버스 (Control Bus), 랜선, 데이터 전송 소프트웨어, 프로토콜
(2) 명령어 집합 구조 (Instruction Set Architecture, ISA)
: CPU가 해석하는 명령어의 집합
다양한 명령어 집합 구조
IA-32, x86-64(x64), MIPS, AVR 등 다양한 연산 수준과 컴퓨터 환경에 맞추기 위해 개발
- x86-64 : 고성능 프로세서 설계, 많은 전력 소모, 발열도 상대적으로 심함
- ARM, MIPS, AVR : 임베디드 기기에 사용, 전력 소모와 발열이 적음
(3) x86-64 아키텍처
: 가장 널리 사용되는 ISA
인텔의 32비트 CPU 아키텍처 IA-32 ---(64비트 확장)--> AMD의 AMD64 아키텍처
-> 이를 기반으로 발표된 다양한 이름의 아키텍처 => x86-64 (범용적, 중립적 지칭)
n 비트 아키텍처
n비트 -> WORD : CPU가 이해할 수 있는 데이터 단위 (32bit, 64bit)
WORD가 크면 CPU가 제공할 수 있는 가상 메모리 공간이 커짐
(4) x86-64 아키텍처의 레지스터
레지스터 : CPU가 데이터를 빠르게 저장하고 사용할 때 이용하는 보관소
- 범용 레지스터(General Register)
: 주용도 외에도 다양한 용도로 사용될 수 있는 레지스터. 각 8바이트의 크기.
rax(accumulator register), rbx(base register), rcx(counter register), rdx(data register), rsi(source index), rdi(destination index), rsp(stack pointer), rbp(stack base pointer), r8-r15 - 세그먼트 레지스터(Segment Register)
: cs, ss, ds, es, fs, gs. 메모리 보호를 위해 사용되는 레지스터. 각 16비트 크기.
cs, ds, ss 레지스터 : 코드 영역과 데이터, 스택 메모리 영역 가리킴.
es, fs, gs 레지스터 : 운영체제 별로 용도 결정. 범용적 용도로 제작. - 명령어 포인터 레지스터(Instruction Pointer Register, IP)
: rip. 기계어 코드들 중 어느 부분을 실행할지 가리키는 역할. 8바이트 크기. - 플래그 레지스터(Flag Register)
: RFLAGS. 프로세서의 현재 상태를 저장. 64비트 크기. (과거 16비트)
플래그 레지스터를 구성하는 각 비트를 끄고 키는 것으로 CPU의 현재 상태를 표현.
최대 64개의 플래그 사용 가능하지만, 실제 20여개의 비트 사용.
- 대표적인 플래그
- CF(Carry Flag) : 부호 없는 수의 연산 결과가 비트 범위를 넘을 경우 설정
- ZF(Zero Flag) : 연산 결과가 0인 경우
- SF(Sign Flag) : 연산 결과가 음수일 경우
- OF(Overflow Flag) : 부호 있는 수의 연산 결과가 비트 범위를 넘을 경우
- 대표적인 플래그
- 레지스터 호환
- x86-64는 IA-32의 64비트 확장 아키텍처이므로 호환이 가능.
- IA-32에서 레지스터
: eax, ebx, ecx, edx, esi, edi, esp, ebp. 각 32비트.
-> (확장) rax, rbx, rcx, rdx, rsi, rdi, rsp, rbp. 각 64비트.
* eax, ebx 등은 확장된 레지스터인 rax, rbx 등의 하위 32비트를 가리킴. - IA-16에서 레지스터
: ax, bx, cx, dx, si, di, sp, bp. 각 16비트.
* ax, bx 등은 eax, ebx 등의 하위 16비트를 가리킴.
* 이들 중 ax, bx, cx, dx는 상위 8비트와 하위 8비트로 나뉨.
2. Linux Memory Layout
메모리 오염(Memory Corruption) 관련 배경 지식
(1) 세그먼트 (Segment)
: 적재되는 데이터의 용도별로 메모리의 구획을 나눈 것
-> 프로세스의 메모리를 크게 5가지의 세그먼트로 구분
* 메모리를 용도별로 나누어 각 용도에 맞게 적절한 권한(읽기, 쓰기, 실행) 부여
* 더 자세한 내용 : 운영체제 메모리 관리기법 중 세그먼테이션 기법, 인텔 x86-64(x64) 관련 하드웨어 설계
(2) 코드 세그먼트 (Code Segment)
: 텍스트 세그먼트(Text Segment) , 실행 가능한 기계 코드가 위치하는 영역
- 읽기, 실행 권한 부여 / 쓰기 권한 제거 (악의적 코드 삽입 용이해지기 때문)
ex) main() 등의 함수 코드
(3) 데이터 세그먼트 (Data Segment)
: 컴파일 시점에 값이 정해진 전역 변수 및 전역 상수들이 위치
- 읽기 권한 부여 or 읽기, 쓰기 권한 부여
- 쓰기가 가능한 세그먼트와 쓰기가 불가능한 세그먼트로 다시 분류
- data 세그먼트 : 쓰기가 가능한 세그먼트. ex) 초기화된 전역 변수 (값이 변할 수 있는 데이터)
- rodata(read-only data) 세그먼트 : 쓰기가 불가능한 세그먼트. ex) 전역 상수 (값이 변하면 안되는 데이터)
(4) BSS 세그먼트 (BSS Segment, Block Started By Symbol Segment)
: 컴파일 시점에 값이 정해지지 않은 전역 변수가 위치.
- 읽기, 쓰기 권한 부여
* 이 세그먼트의 메모리 영역은 프로그램이 시작될 때, 모두 0으로 값이 초기화.
ex) 초기화되지 않은 전역 변수
(5) 스택 세그먼트 (Stack Segment)
: 프로세스의 스택이 위치.
- 읽기, 쓰기 권한 부여
* 스택 프레임(Stack Frame) 단위로 사용 : 함수가 호출될 때 생성되고, 반환될 때 해제
* 실행되는 프로세스가 사용할 스택 프레임 양을 미리 계산하는 것은 일반적으로 불가능
-> 시작할 때 작은 크기의 스택 세그먼트를 먼저 할당 -> 부족해 질 때마다 낮은 주소로 확장 (아래로 자란다)
ex) 지역 변수, 함수 인자 등의 임시변수
(6) 힙 세그먼트 (Heap Segment)
: 힙 데이터가 위치. 실행중에 동적으로 할당. 스택 세그먼트와 반대 방향으로 자람(충돌 방지).
- 읽기, 쓰기 권한 부여
ex) malloc(), calloc() 등을 호출해서 할당받는 메모리
3. x86 Assembly
(1) 어셈블리어 (Assembly Language)
: 컴퓨터의 기계어와 치환되는 언어. 명령어 집합구조가 다양함에 따라 어셈블리어도 여러 종류,
- 어셈블러(Assembler) : 어셈블리어 -> 기계
- 역어셈블러(Disassembler) : 기계어 -> 어셈블리어
(2) x86-64 어셈블리어
- 기본 구조 : 명령어(Operation Code, Opcode) + 피연산자(Operand)
- 명령어
- 데이터 이동 : mov, lea
- 산술 연산: add, sub, inc, dec
- 논리 연산: and, or, xor, not
- 비교: cmp, test
- 분기: jmp, je, jg
- 스택 : push, pop
- 프로시져 : call, ret, leave
- 시스템 콜 : syscall
- 피연산자 : []으로 둘러싸인 것으로 표현
- 상수(Immediate Value)
- 레지스터(Register)
- 메모리(Memory)
- 앞에 크기 지정자(Size Directive) TYPE PTR이 추가될 수 있음.
: BYTE(1바이트), WORD(2바이트), DWORD(4바이트), QWORD(8바이트)
(3) 데이터 이동 : 어떤 값을 레지스터나 메모리에 옮기도록 지시
`mov dst, src` : src에 들어있는 값을 dst에 대입
`lea dst, src` : src의 유효 주소(Effective Address, EA)를 dst에 저장
(4) 산술 연산 : 덧셈, 뺄셈, 곱셈, 나눗셈 연산을 지시
`add dst, src` : dst에 src의 값을 더함
`sub dst, src` : dst에서 src의 값을 뺌
`inc op` : op의 값을 1 증가
`dec op` : op의 값을 1 감소
(5) 논리 연산 : and, or, xor, neg(negative, 2의 보수) 등의 비트 연산을 지시
`and dst, src` : dst와 src의 비트가 모두 1이면 1, 아니면 0
`or dst, src` : dst와 src의 비트 중 하나라도 1이면 1, 아니면 0
`xor dst, src` : dst와 src의 비트가 서로 다르면 1, 같으면 0
`not op` : op의 비트 전부 반전
(6) 비교 : 두 피연산자의 값을 비교하고, 플래그를 설정
`cmp op1, op2` : op1에서 op2를 빼고 플래그를 설정
`test op1, op2` : op1과 op2에 AND 연산을 하고, 플래그를 설정
(7) 분기 : `rip`(명령어 포인터 레지스터)를 이동시켜 실행 흐름을 바꿈
`jmp addr` : addr로 rip를 이동
`je addr` : 직전 비교에서 두 피연산자의 값이 같을 경우 addr로 rip 이동 (jump if equal)
`jg addr`: 직전 비교에서 두 피연산자 중 전자의 값이 더 클 경우 addr로 rip 이동 (jump if greater)
(8) 스택 : 스택을 조작
`push val` : rsp를 8만큼 빼고, 스택의 최상단에 val을 쌓음
`pop reg` : 스택 최상단의 값을 reg에 넣고, rsp를 8만큼 더함
(9) 프로시저 : 특정 기능을 수행하는 코드 조각
`call addr`: addr의 프로시저 호출. 반환을 위해 call 다음의 명령어 주소(Return Address, 반환 주소)를 스택에 저장하고 프로시저로 rip 이동.
`leave`: 스택 프레임 정리
`ret`: 호출자의 실행 흐름으로 돌아감
(10) 시스템 콜
모든 하드웨어 및 소프트웨어에 대한 접근 및 제어 권한을 보호하기 위해 커널 모드와 유저 모드로 권한을 나눔
- 커널 모드 : 운영체제가 전체 시스템을 제어하기 위해 시스템 소프트웨어에 부여하는 권한. 모든 저수준의 작업 진행.
- 유저 모드 : 운영체제가 사용자에게 부여하는 권한
- 시스템 콜(system call, syscall) : 유저 모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청하기 위해 사용
`syscall` : 커널에게 필요한 동작 요청
Tool Installation
1. gdb
디버거(Debugger) : 버그를 없애기 위해 사용하는 도구. 프로그램을 어셈블리 코드 단위로 실행하면서, 실행결과를 사용자에게 보여줌.
gdb (GNU debugger) : 리눅스의 대표적인 디버거
바이너리 분석 용도 플러그인 : gef, peda, pwngdb, pwndbg
- entry: 진입점에 중단점을 설정한 후 실행
- break(b): 중단점 설정
- continue(c): 계속 실행
- disassemble: 디스어셈블 결과 출력
- u, nearpc, pd: 디스어셈블 결과 가독성 좋게 출력
- x: 메모리 조회
- run(r): 프로그램 처음부터 실행
- context: 레지스터, 코드, 스택, 백트레이스의 상태 출력
- nexti(ni): 명령어 실행, 함수 내부로는 들어가지 않음
- stepi(si): 명령어 실행, 함수 내부로 들어감
- telescope(tele): 메모리 조회, 메모리값이 포인터일 경우 재귀적으로 따라가며 모든 메모리값 출력
- vmmap: 메모리 레이아웃 출력
- start: main() 심볼이 존재하면 main()에 중단점을 설정한 후 실행. main() 심볼이 없으면 진입점에 중단점을 설정한 후 실행
- main: start 명령어와 동일
2. pwntools
pwntools : 바이너리 익스플로잇과 같은 보안 문제를 해결하기 위한 파이썬 라이브러리
- process & remote: 로컬 프로세스 또는 원격 서버의 서비스를 대상으로 익스플로잇 수행
- send & recv: 데이터 송수신
- packing & unpacking: 정수를 바이트 배열로, 또는 바이트 배열을 정수로 변환
- interactive: 프로세스 또는 서버와 터미널로 직접 통신
- context.arch: 익스플로잇 대상의 아키텍처
- context.log_level: 익스플로잇 과정에서 출력할 정보의 중요도
- ELF: ELF헤더의 여러 중요 정보 수집
- shellcraft: 다양한 셸 코드를 제공
- asm: 어셈블리 코드를 기계어로 어셈블
'I.sly() > 9기 기초 - 포너블' 카테고리의 다른 글
[포너블 기초] Dreamhack System Hacking (3) - BOF (0) | 2024.05.20 |
---|---|
[포너블 기초] Dreamhack System Hacking (2) - Shellcode (0) | 2024.05.11 |
[포너블 기초] 생활코딩 리눅스 섹션 12. 로그인 없이 로그인 하기 ssh key (0) | 2024.04.01 |
[포너블 기초] 생활코딩 리눅스 섹션 11. 인터넷을 통한 서버간 동기화 rsync (0) | 2024.04.01 |
[포너블 기초] 생활코딩 리눅스 섹션 10. 도메인 domain (0) | 2024.04.01 |