촉촉한초코칩
시스템 스터디 정리 본문
ISA (Instruction Set Architecture, 명령어 집합 구조)
- CPU가 처리할 수 있는 명령어 집합
- CPU를 만들 때 특정 ISA를 기반으로 제작한다.
ISA x86-64 아키텍서 레지스터
- 크기 : 8바이트 (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) | 스택의 바닥을 가리키는 포인터 |
2) 세그먼트 레지스터 (크기 : 16비트)
이름 | 주용도 |
cs | 코드 영역 |
ss | 스택 메모리 영역 |
ds | 데이터 영역 |
es | 범용 |
fs | 범용 |
gs | 범용 |
3) 명령어 포인터 레지스터
- rip (instruction pointer) : 크기 8바이트, CPU가 어느 부분의 코드를 실행할 지 가리킨다.
플래그 레지스터 (RFLAGS)
- 크기 : 64비트
- 한 플래그의 크기 : 1비트
플래그 | 의미 |
CF(Carry Flag) | 부호 없는 수의 연산 결과가 비트의 범위를 넘을 경우 설정 |
ZF(Zero Flag) | 연산의 결과가 0일 경우 설정 |
SF(Sign Flag) | 연산의 결과가 음수일 경우 설정 |
OF(Overflow Flag) | 부호 있는 수의 연산 결과가 비트 범위를 넘을 경우 설정 |
* 64비트 : rax, rbx, rcx, rdx, rsi, rdi, rsp, rbp
* 32비트(하위 32비트) : eax, ebx, ecx, edx, esi, edi, esp, ebp
* 16비트(하위 16비트) : ax, bx, dx, si, di, sp, bp
* 다시 상/하위 8비트로 나뉜다.
ex) rax = 0x0123456789abcdef 일 때, eax의 값은?
eax = 0x89abcdef
ex) rax = 0x0123456789abcdef 일 때, ax의 값은?
ax = 0xcdef
세그먼트
- 적재되는 메모리의 용도별로 메모리 구획을 나눈다.
- 세그먼트마다 CPU의 권한이 다르다.
- 권한 : read, write, execute
세그먼트 | 역할 | 일반적인 권한 | 사용 예시 |
코드 세그먼트 | 실행 가능한 코드가 저장된 영역 | 읽기, 실행 | main() 등의 함수 코드 |
데이터 세그먼트 | 초기화된 전역 변수 또는 상수가 위치하는 영역 | 읽기와 쓰기 또는 읽기 전용 | 초기화된 전역 변수, 전역 상수 |
BSS 세그먼트 | 초기화되지 않은 데이터가 위치하는 영역 | 읽기, 쓰기 | 초기화되지 않은 전역 변수 |
스택 세그먼트 | 임시 변수가 저장되는 영역 | 읽기, 쓰기 | 지역 변수, 함수의 인자 등 |
힙 세그먼트 | 실행중에 동적으로 사용되는영역 | 읽기, 쓰기 | malloc, calloc() 등으로 할당 받은 메모리 |
* 읽기 전용 데이터(rodata) : 초기화된 전역 변수의 값? (상수, 상수형 문자열, printf의 중괄호 부분 등)
x64 어셈블리어 주요 명령어
데이터 이동 (Data Transfer) | mov, lea ex) lea dst, src : src의 유효 주소 (Effective Address, EA)를 dst에 저장한다. |
산술 연산 (Arithmetic) | inc, dec, add, sub |
논리 연산 (Logical) | and, or, xor, not ex) xor dst, src : dst와 src의 비트가 서로 다르면 1, 같으면 0 ex) not op : op 비트 전부 반전 |
비교 (Comparison) | cmp, test (두 피연산자에 AND 비트 연산을 취한다.) > 연산 후 플래그를 보고 판 |
분기 (Branch) | jmp, je(Jump if equal), jg(Jump if greater, 직전에 비교한 두 연산자 중 전자가 더 크면 점프) |
스택 (Stack) | push (피연산자를 스택 최상단에 쌓는다) pop (스택 최상단의 값을 꺼내서 피연산자에대입한다.) |
프로시져 (Procedure) : 특정 기능을 수행하는 코드 조각 반복되는 연산을 프로시저 호출로 대체하여 전체 코드 크기를 줄이고, 기능별로 코드 조각에 이름을 붙일 수 있게 되어 코드의 가독성을 크게 높일 수 있게 된다. |
call : 프로시저 부르는 행위 / return : 프로시저에서 돌아오는 행위 (반환) > 프리시저 실행 후 원래의 실행 흐름으로 돌아와야 하므로 call 다음의 명령어 주소르 스택에 저장하고 프로시저로 rip을 이동시킨다. ret : return address로 반환 leave : 스택 프레임 정리 |
시스템 콜 (System call) | 리눅스 계층 : 커널 모드 / 사용자 모드 / 시스템 콜 syscall : 유저모드에서 커널 모드의 시스템 소프트웨어에게 어떤 동작을 요청할 때 사용 |
* 스택 연산
- 스택 : 높은 주소에서 낮은 주소로 자란다.
- rsp 에서 어떤 값을 뺀다 : 주소 공간 마련
- rsp 에서 어떤 값을 더한다 : 주소 공간 비움
* 스택 프레임 : 특정 함수가 사용하는 스택 영역
* x64 syscall 테이블
syscall | rax | arg0 (rdi) | arg1 (rsi) | arg2 (rdx) |
read | 0x00 | unsigned int fd | char *buf | size_t count |
write | 0x01 | unsigned int fd | const char *buf | size_t count |
open | 0x02 | const char *filename | int flags | umode_t mode |
close | 0x03 | unsigned int fd | ||
mprotect | 0x0a | unsigned long start | size_t len | unsigned long prot |
connect | 0x2a | int sockfd | struct sockaddr * addr | int addrlen |
execve | 0x3b | const char *filename | const char *const *argv | const char *const *envp |
x64 어셈블리어의 피연산자
- 피연산자 종류 : 상수, 레지스터, 메모리
- 메모리 피연산자 표현법 : TYPE PTR [주소값 또는 레지스터]
- Byte - 1 byte
- Word - 2 bytes
- Dword - 4 bytes
- Qword - 8 bytes
메모리 피연산자 | |
QWORD PTR [0x8048000] | 0x8048000의 데이터를 8바이트 만큼 참조 |
DWORD PTR [0x8048000] | 0x8048000의 데이터를 4바이트 만큼 참조 |
WORD PTR [rax] | rax가 가리키는 주소에서 데이터를 2바이트 만큼 참조 |
* 참고) https://cnu-cse-pgs.tistory.com/9, https://streetdeveloper.tistory.com/95
rcx * 8 = 10 (16진수)
rbx + rcx = 0x555555554010 = 0x00...03
rax = rax + rbx = 0x31337 + 0x00...03 = 3133A
Code 2 rcx : 4
rbx + rcx * 8 = 0x555555554010 + 4 * 8 = 0x555555554020 (0x00...3133A)
rax = rax - 0x000000000003133A = 0 (Code 1에서 계산한 rax 값이 3133A이므로)
gdb : 리눅스 디버거
pwndbg
- gdp의 플러그인
- 바이너리 분석 용도
- entry(ELF 파일에서 프로그램이 실행되는 시작점), context(프로그램이 실행되고 있는 맥락), break&continue, start, run, disassemble(함수 전체를 어셈블리 코드로 보여준다.), navigate(다음 명령어로 rip 이동), examine(특정 주소에서 원하느 길이만큼의 데이터 인코딩해서 보기), telescope(메모리가 참조하고 있는 주소를 재귀적으로 탐색하여 값을 보여줌), vmmap(가상 메모리의 레이아웃, 어떤 파일이 매핑 된 영역일 경우, 해당 파일의 경로까지 보여준다.)
pwntools
- 파이썬 모듈
- 익스플로잇할 때 자주 구현하게 되는 기능들을 파이썬 모듈로 만들어 제공
- pwntools API 함수 : process(로컬 바이너리 테스트, 디버깅용), remote(원격 서버 실제 공격용),send(데이터 보내기), revc(데이터 받기), packing&unpacking(바이트배열을 리틀앤디언으로 변경 혹은 반대), interactive(터미널에서 프로세스에 데이터 입출력하기), ELF(헤더에 기록된 각종 정보 보기), asm(주어진 코드를 기계어로 어셈블)
- 객체 : context.log (디버깅의 편의를 돕는 로그 기능), context.arch(공격 대상의 아키텍처 정보 설정), shellcraft (자주 사용되는 셀코드를 모아놓은 객체)
Bomb Lab
- 폭탄을 터뜨리지 않고 안전하게 해체한다.
- 6가지 phase 함수가 있으며 각 phase마다 read_line(사용자 입력값받음), phase(입력값 검증), phase_defused(폭탄해체)가 반복된다.
Calling Convention 함수 호출 규약
- 함수의 호출 및 반환에 대한 규약
- Caller(호출자), Callee(피호출자)
- 호출 시 Caller 상태 저장 > return address 저장 > callee가 요구하는 인자(rdi, rsi, rdx, rcx, r8, r9, 더 많은 인자 사용해야 할 때는 스택 사용) 전달
- 반환 시 Callee의 return 값 저장
- x86-64아키텍처용 함수 호출 규약 : system v(sysv)
> 인자 전달, 반환 주소 저장, (Caller의) 스택 프레임 저장, (Callee에게) 스택 프레임 할당, 반환값 전달, 반환 - x86아키텍처용 함수 호출 규약 : cdecl
마지막 인자부터 첫번째 인자까지 거꾸로 스택에 push
Stack Overflow
- 스택 영역 자체가 넘친 것
Stack Buffer Overflow
- 스택 영역 안에 할당된 특정 버퍼가 넘친 것
- 정해진 버퍼의 크기보다 큰 데이터가 들어가면 발생한다.
- 중요 데이터 변조 https://learn.dreamhack.io/60#6
- 데이터 유출 https://learn.dreamhack.io/60#7
- 실행 흐름 조작 https://learn.dreamhack.io/60#8
Stack Buffer Overflow 취약점이 있는 함수들
'Study > System' 카테고리의 다른 글
[Dreamhack] basic_exploitation_001 (0) | 2024.03.31 |
---|---|
[Dreamhack] basic_exploitation_000 (0) | 2024.03.31 |
[Dreamhack] Return Address Overwrite (1) | 2024.03.31 |
Bomb Lab Phase 1-3 (0) | 2024.03.24 |
[DreamHack] Quiz: x86 Assembly 2, Quiz: x86 Assembly 3 (0) | 2024.03.17 |