Linux Memory Layout

실행중인 프로세스의 메모리가 5개의 영역으로 구분됨.
- 코드 세그먼트 - 실행 가능한 코드가 위치하는 영역. 읽기 권한과 실행 권한 부여. main()함수 사용 등
- 데이터 세그먼트 - 초기화된 전역 변수 또는 상수가 위치하는 영역. 읽기 권환와 쓰기 권환 or 읽기 권한 전용
- BSS 세그먼트 - 초기화되지 않은 데이터가 위치하는 영역. 읽기 권환과 쓰기 권한 부여.
- 스택 세그먼트 - 임시 변수가 저장되는 영역. 읽기 권한과 쓰기 권한 부여. 지역변수, 함수의 인자 등 사용
- 힙 세그먼트 - 실행중에 동적으로 사용되는 영역. 읽기 권환과 쓰기 권한 부여. malloc(), calloc()등으로 할당받은 메모리 사용
Background : Computer Architecture
컴퓨터 구조
- 기능 구조의 설계 - 폰 노이만 구조, 하버드 구조, 수정된 하버드 구조
- 명령어 집합구조 - 인텔의 x86, x86-64, ARM, MIPS, AVR
- 마이크로 아키텍처 - 캐시 설계, 파이프라이닝, 슈퍼 스칼라, 분기 예측, 비순차적 명령어 처리
- 하드웨어 및 컴퓨팅 방법론 - 직접 메모리 접근
폰 노이만 구조

연산, 제어를 위한 중앙처리장치(CPU) + 저장을 위한 기억장치 + 데이터나 신호교환할수 있는 버스 전자통로
- CPU - 프로그램의 연산을 처리하고 시스템을 관리. 산술논리장치(산술/논리 연산 처리) + 제어장치(CPU 제어) + 레지스터 (CPU에 필요한 데이터를 저장)
- 기억장치 - 컴퓨터 동작을 위한 여러 데이터를 저장. 주기억장치 + 보조기억장치 주기억장치 : 프로그램 실행과정에서 필요한 데이터를 임시저장 ex) 램(RAM) 보조기억장치 : 운영체제, 프로그램 데이터를 장기간 보관 ex) 하드 드라이브, SSD
- 버스 - 컴퓨터 부품과 부품 사이, 컴퓨터와 컴퓨터 사이 신호를 전송하는 통로 ex)프로토콜 등.. 데이터 버스 : 데이터 이동 / 주소 버스 : 주소 지정 / 제어 버스 : 읽기, 쓰기를 제어
명령어 집합 구조 - CPU가 해석하는 명령어의 집합
x86-64 아키텍처 - 인텔의 64비트 CPU 아키텍처
- n비트 아키텍처 - n비트는 CPU가 한번에 처리할 수 있는 데이터의 크기. CPU가 이해할 수 있는 데이터의 단위라는 의미에서 WORD 라고 불림.
- 레지스터 - CPU가 데이터를 빠르게 저장하고 사용할 때 이용하는 보관소
-
범용 레지스터
8바이트를 저장할 수 있으며, 부호 없는 정수를 기준으로 2^64-1까지의 수를 나타낼 수 있음

-
세그먼트 레지스터
x64 아키텍처에는 cs,ss,ds,es,fs,gs 6가지가 존재하며, cs,ds,ss 레지스터는 코드 영역과 데이터, 스택 메모리 영역을 가리킬 때 사용되고, 나머지는 범용적인 용도로 제작됨.
-
명령어 포인터 레지스터
CPU가 어느 부분의 코드를 실행할지 가리키는 역할. rip 명령어 이며, 크기는 8바이트
- 플래그 레지스터
프로세서의 현재 상태를 저장하고 있는 레지스터. 자신을 구성하는 여러 비트들로 CPU의 현재 상태를 표현


상위, 하위 레지스터로 호환 가능
x86 Assembly
gdb(GNU debugger)
리눅스의 대표적인 디버거. gdb로 실행.
리눅스는 실행파일 형식으로 ELF 를 규정하고 있음.
ELF - 헤더 / 섹션으로 구성. 헤더 : 실행에 필요한 여러 정보 . 섹션 : 코드 등 여러 데이터 포함
헤더 중 ‘진입점’(Entry Point, EP) : 운영체제는 ELF를 실행할 때, 진입점의 값부터 실행
readelf로 확인
context - 메모리들의 상태를 프로그램이 실행되고 있는 맥락. 가독성 있게 표현하는 인터페이스를 갖춤
context는 크게 4개의 영역으로 구분됩니다.
- registers: 레지스터의 상태를 보여줍니다.
- disasm: rip부터 여러 줄에 걸쳐 디스어셈블된 결과를 보여줍니다.
- stack: rsp부터 여러 줄에 걸쳐 스택의 값들을 보여줍니다.
- backtrace: 현재 rip에 도달할 때까지 어떤 함수들이 중첩되어 호출됐는지 보여줍니다.
명령어
- start: 진입점에 중단점을 설정하고, 실행
- break(b): 중단점 설정
- continue(c): 계속 실행
- disassemble: 디스어셈블 결과 출력
- u, nearpc, pd: 디스어셈블 결과 가독성 좋게 출력
- x: 메모리 조회
- run(r): 프로그램 처음부터 실행
- context: 레지스터, 코드, 스택, 백트레이스의 상태 출력
- nexti(ni): 명령어 실행, 함수 내부로는 들어가지 않음
- stepinto (si): 명령어 실행, 함수 내부로 들어감
- telescope(tele): 메모리 조회, 메모리값이 포인터일 경우 재귀적으로 따라가며 모든 메모리값 출력
- vmmap: 메모리 레이아웃 출력
pwntools 기능
- process & remote: 로컬 프로세스 또는 원격 서버의 서비스를 대상으로 익스플로잇 수행
- send & recv: 데이터 송수신
- packing & unpacking: 정수를 바이트 배열로, 또는 바이트 배열을 정수로 변환
- interactive: 프로세스 또는 서버와 터미널로 직접 통신
- context.arch: 익스플로잇 대상의 아키텍처
- context.log_level: 익스플로잇 과정에서 출력할 정보의 중요도
- ELF: ELF헤더의 여러 중요 정보 수집
- shellcraft: 다양한 셸 코드를 제공
- asm: 어셈블리 코드를 기계어로 어셈블
python3 치면 pwntools 임포트 됨
STAGE 4 Shell code
쉘 코드 : 익스플로잇을 위해 제작된 어셈블리 코드 조각
orw 셸코드 : 파일을 열고, 읽은 뒤 화면에 출력해주는 셸코드


rax 함수
STAGE 5 Stack Buffer Overflow
Calling Convention
함수 호출 규약은 함수의 호출 및 반환에 대한 약속
함수를 호출할 때는 반환된 이후를 위해 호출자(Caller)의 상태 및 반환 주소를 저장해야 한다.
호출자는 피호출자(Callee)가 요구하는 인자를 전달해주고, 피호출자의 실행이 종료될 때는 반환값을 전달받아야 함
- x86호출 규약 : cdecl
- 스택을 통해 인자를 전달하고, 인자를 전달하기 위해 사용한 스택을 호출자가 정리한다.
- 스택을 통해 인자를 전달할 때, 마지막 인자부터 첫번째 인자까지 거꾸로 스택에 push한다.

- x86-64호출 규약 : SYSV

Stack Buffer Overflow
- 버퍼 : 데이터가 목적지로 이동되기 전에 보관되는 임시 저장소
- 스택 버퍼 : 스택에 있는 지역 변수 / 힙 버퍼 : 힙에 할당된 메모리 영역
- 버퍼 오버플로우 : 버퍼가 넘치는 것. 뒤에 있는 버퍼들의 값이 조작될 위험이 있음
버퍼에 오버플로우를 발생시켜서 다른 버퍼와의 사이에 있는 널바이트를 모두 제거하면, 해당 버퍼를 출력시켜서 다른 버퍼의 데이터를 읽을 수 있기 때문에 데이터 유출이 발생할수있다.
또한, 함수를 호출할 때 반환 주소를 스택에 쌓고, 함수에서 반환될 때 이를 꺼내어 원래의 실행 흐름으로 돌아간다는 것을 보았을때 ‘스택 버퍼 오버플로우’로 반환 주소를 조작하면 프로세스의 실행 흐름을 바꿀수있다.
STAGE 6 Stack Canary
스택 카나리 : 스택 버퍼와 반환 주소 사이에 임의의 값을 삽입하고, 함수의 에필로그에서 해당 값의 변조를 확인하는 보호기법
카나리 값 : 프로세스가 시작될 때, TLS에 전역 변수로 저장되고, 각 함수마다 프롤로그와 에필로그에서 이 값을 참조한다.
- 카나리 생성 : security_init 함수에서 TLS에 랜덤 값으로 카나리를 설정하면, 매 함수에서 이를 참조하여 사용한다.
카나리 우회 기법
- 무차별 대입 (Brute Force) 연산량이 많아서 사실상 불가능
- TLS 접근 TLS의 주소는 매 실행마다 바뀌지만 TLS의 주소를 알 수 있고, 임의의 주소에 대한 읽기 또는 쓰기가 가능하다면 TLS에 설정된 카나리 값을 읽거나, 임의의 값으로 조작할 수 있다. 그 뒤, 스택 버퍼 오버플로우를 수행할 때 알아낸 카나리 값으로 스택 카나리를 덮으면 함수의 에필로그에 있는 카나리 검사를 우회할 수 있다.
- 스택 카나리 릭 함수의 프롤로그에서 스택에 카나리 값을 저장하므로, 이를 읽어낼 수 있으면 카나리를 우회할 수 있다.
STAGE 7 ASLR & NX
ASLR & NX
- ASLR : 바이너리가 실행될 때마다 스택, 힙, 공유 라이브러리 등을 임의의 주소에 할당하는 보호 기법


checksec을 이용하면 바이너리에 NX가 적용됐는지 알 수 있다.

Library - Static Link vs Dynamic Link
-
라이브러리(Library) - 컴퓨터 시스템에서 프로그램들이 함수나, 변수를 공유해서 사용할 수 있게 함. ex) printf, scanf, strlen, memcpy, malloc
-
링크(Link) - 컴파일의 마지막 단계
- 동적 링크 (Dynaminc link) : 동적 링크된 바이너리를 실행하면 동적 라이브러리가 프로세스의 메모리에 매핑됨. 실행 중에 라이브러리의 함수를 호출하면 매핑된 라이브러리에서 호출할 함수의 주소를 찾고, 그 함수를 실행함
- 정적 링크 (Static link) : 정적 링크를 하면 바이너리에 정적 라이브러리의 필요한 모든 함수가 포함됨. 해당 함수를 호출할 때, 라이브러리를 참조하는 것이 아니라, 자신의 함수를 호출하는 것처럼 호출할 수 있음. 여러 바이너리에서 라이브러리를 사용하면 그 라이브러리의 복제가 여러 번 이루어지므로 용량을 낭비하게 됨
-
PLT, GOT - 라이브러리에서 동적 링크된 심볼의 주소를 찾을 때 사용하는 테이블
STAGE 8 Bypass PIE & RELRO
RELRO(RELocation Read-Only) - 프로세스의 데이터 세그먼트를 보호함. 쓰기 권한이 불필요한 데이터 세그먼트에 쓰기 권한을 제거함
- Partial RELRO - RELRO를 부분적으로 적용. init arrar, fini array 등 여러 섹션에 쓰기 권한을 제거함. Lazy binding을 사용하므로 라이브러리 함수들의 GOT 엔트리는 쓰기가 가능함.
- Full RELRO - 가장 넓은 영역에 RELRO를 적용. init arrar, fini array 뿐만 아니라 GOT에도 쓰기 권한을 제거함. Lazy binding을 사용하지 않으며 라이브러리 함수들의 주소는 바이너리가 로드되는 시점에 바인딩됨. libc의 malloc hook, free hook과 같은 함수 포인터를 조작하는 공격으로 우회 가능

Position-Independent Executable(PIE) - ASLR이 코드 영역에도 적용되게 해주는 기술
- PIC - 재배치가 가능하도록 설계된 코드. (재배치가 가능하다는 것은 메모리의 어느 주소에 적재되어도 코드의 의미가 훼손되지 않음을 의미) 어떤 주소에 매핑되어도 실행 가능하다. 절대 주소를 사용하지 않으며 일반적으로 rip를 기준으로 한 상대 주소를 사용함

- PIE - 무작위 주소에 매핑돼도 실행 가능한 실행 파일. PIE의 코드는 모두 PIC이다.
- 우회 방법
-
코드 베이스 구하기 : 바이너리가 적재된 주소를 PIE 베이스, 코드 베이스라고 부르는데, 코드 베이스를 구하려면 라이브러리의 베이스 주소를 구할 때 처럼 코드 영역의 임의 주소를 읽고, 그 주소에서 오프셋을 빼야 한다.
-
Partial Overwrite : 코드 베이스를 구하기 어려울 때 반환 주소의 일부 바이트만 덮는 공격. 특정 함수의 호출 관계는 정적 분석 or 동적 분석으로 쉽게 확인할 수 있으므로, 공격자는 반환 주소를 예측할 수 있다.
ASLR의 특성 상, 코드 영역의 주소도 하위 12비트 값은 항상 같은데, 사용하려는 코드 가젯의 주소가 반환 주소와 하위 한 바이트만 다르다면, 이 값만 덮어서 원하는 코드를 실행시킬 수 있다.
(두 바이트 이상이 다른 주소로 실행 흐름을 옮기고자 하면, ASLR로 뒤섞이는 주소를 맞춰야 하므로 브루트 포싱이 필요하다.)
STAGE 9 Out of bounds
배열은 같은 자료형의 요소(Element)들로 이루어져 있고, 각 요소의 위치를 인덱스(Index)라고 함.
-
배열의 길이: 배열이 포함하는 요소의 개수
-
배열의 크기: ****배열의 길이 X 요소의 크기
-
배열의 참조: 배열의 주소, 요소의 크기, 인덱스를 활용하여 참조할 요소의 주소를 계산함. 이 과정에서, 계산된 주소가 배열의 범위를 벗어나는지 검사하지 않으므로 out of bounds 취약점이 발생할 수 있음.
-
OOB - 배열의 임의 인덱스에 접근할 수 있는 취약점. 요소를 참조할 때, 인덱스 값이 음수이거나 배열의 길이를 벗어날 때 발생함. 개발자가 인덱스의 범위에 대한 검사를 명시적으로 프로그래밍 하지 않으면, 계산한 주소가 배열의 범위 안에 있는지 검사하지 않음.
사용자가 배열 참조에 사용되는 인덱스를 임의 값으로 설정할 수 있다면, 배열의 주소로부터 특정 오프셋에 있는 메모리의 값을 참조 가능
- 포맷 스트링 취약점 : 포맷 스트링을 인자로 사용하는 scanf, fprintf, fscanf, sprintf, sscanf는 포맷 스트링을 채울 값들을 레지스터나 스택에서 가져오는데, 이들 내부에는 포맷 스트링이 필요로 하는 인자의 개수와 함수에 전달된 인자의 개수를 비교하는 루틴이 없다. 사용자가 포맷 스트링을 입력할 수 있으면, 악의적으로 다수의 인자를 요청해 레지스터나 스택의 값을 읽어낼 수 있음



- 포맷 스트링 버그 - 포맷 스트링을 사용자가 입력할 수 있을 때, 공격자는 레지스터와 스택을 읽을 수 있고, 임의 주소 읽기 및 쓰기를 할 수 있다.
- 형식 지정자(Format Specifier) - 포맷 스트링에 대입되는 인자의 형식을 지정함. %d, %x, %u, %s, %n 등이 있음
STAGE 11 User After Free
- Dangling Pointer - 유효하지 않은 메모리 영역을 가리키는 포인터 ( 메모리 동적 할당 시 일반적으로 포인터 선언 → 그 포인터에 malloc 함수가 할당한 메모리의 주소 저장 → 그 포인터를 참조하여 할당한 메모리에 접근 ) ( 메모리 해제 시 free 함수 호출. free 함수는 청크를 ptmalloc에 반환하기만 할 뿐 청크의 주소를 담고있던 포인터를 초기화하는건 X )
- UAF - 해제된 메모리에 접근할 수 있을 때 발생하는 취약점 / 메모리에 남아있던 데이터가 유출되거나 사용될 수 있음
STAGE 12 Double Free Bug
ptmalloc2
- Memory Allocator: 프로세스의 요청에 따라 동적으로 메모리를 할당 및 해제해주는 주체, 또는 관련된 알고리즘들의 집합. dlmalloc, ptmalloc, jemalloc, tcmalloc 등이 있으며, 리눅스는 그 중에서 ptmalloc2를 사용한다. 구현되는 방식은 다소 차이가 있지만, 핵심 목표는 메모리 단편화의 최소화, 공간 복잡도 및 시간 복잡도의 최적화이다.
- ptmalloc(pthread memory-allocation): dlmalloc을 모태로하는 메모리 할당자. malloc, free 등을 기반으로 사용자의 동적 메모리 요청을 처리함. 사용하는 주요 객체로는 청크, bins, arena, tcache가 있음.
- 청크(Chunk): ptmalloc2가 메모리를 할당하는 단위.
- bins: 해제된 청크들을 보관함. ptmalloc은 bin을 이용하여 청크를 빠르게 재할당하고, 단편화를 최소화함. bins에는 fastbin, smallbin, largebin, unsortedbin이 있음.
- arena: ptmalloc이 관리하는 메모리들의 정보가 담겨있음. 모든 쓰레드가 공유하는 자원으로, 한 쓰레드가 이를 점유하면 race condition을 막기 위해 lock이 걸림. 병목 현상을 막기 위해 64개까지 생성 가능하지만, 이를 초과할 정도로 많은 연산이 발생하면 병목 현상이 일어남.
- tcache: 쓰레드마다 해제된 청크들을 보관하는 저장소. 멀티 쓰레드 환경에서 arena가 가지고 있는 병목 현상의 문제를 일부 해결해줄 수 있음. 쓰레드마다 할당되므로 용량을 고려하여 각 tcache당 7개의 청크만 보관할 수 있음
Double Free Bug
- tcache_entry: 해제된 tcache 청크를 나타내는 구조체. 각 청크는 next 라는 멤버 변수로 연결됨. Double free 보호 기법이 적용되면서, key 라는 멤버 변수가 추가됨.
- tcache_perthread_struct: tcache를 처음 사용하면 할당되는 구조체.
- Double Free Bug: 한 청크를 두 번 해제할 수 있는 버그.
- Tcache Duplication: tcache에 같은 청크가 두 번 연결되는 것. double free bug로 발생시킬 수 있으며, tcache poisoning으로 응용될 수 있음.
STAGE 13 Type Error
2의 보수
Type Error - 변수의 크기, 용도, 부호여부를 고려하지 않고 부적절한 자료형을 사용했을때 발생
변수에 어떤 값을 대입할 때, 그 값이 변수에 저장될 수 있는 범위를 벗어나면, 저장할 수 있는 만큼만 저장하고 나머지는 모두 유실된다.
- Type Overflow: 변수가 저장할 수 있는 최댓값을 넘어서서 최솟값이 되는 버그
- Type Underflow: 변수가 저장할 수 있는 최솟값보다 작아 최댓값이 되는 버그
- Most Significant Bit(MSB): 데이터의 최상위 비트, 부호를 표현하기 위해 사용됨
STAGE 14 Command Injection
💡 system 함수가 명령어를 실행하는 과정
system
함수는 라이브러리 내부에서 do_system
함수를 호출합니다. do_system
은 “sh -c”와 system
함수의 인자를 결합하여 execve
시스템 콜을 호출합니다.
인젝션 - 악의적인 데이터를 프로그램에 입력하여 이를 시스템 명령어, 코드, 데이터베이스 쿼리 등으로 실행되게 하는 기법
Command Injection - 사용자의 입력을 시스템 명령어로 실행하게 하는 것. 명령어를 실행하는 함수에 사용자가 임의의 인자를 전달할 수 있을 때 발생
앞서 소개한 system
함수를 사용하면 사용자의 입력을 소프트웨어의 인자로 전달할 수 있습니다. 사용자가 입력한 임의 IP에 ping을 전송하고 싶다면 system(“ping [user-input]”)
을, 임의 파일을 읽고 싶다면 system(“cat [user-input]”)
등의 형태로 system
함수를 사용할 수 있습니다.
system 함수는 셸 프로그램에 명령어를 전달하여 실행하는데, 셸프로그램은 다양한 메타 문자를 지원. ;를 사용하면 여러개의 명령어를 순서대로 실행시킬 수 있다.

STAGE 15 Path Traversal
Path Traversal - 경로 문자열에 대한 검사가 미흡할 때, 허용되지 않는 디렉토리에 접근할 수 있는 취약점. 제한된 디렉토리를 벗어나서 서버의 중요 파일을 읽거나, 덮어쓸 수 있음
- 절대 경로 (Absolute Path) : 루트 디렉토리(’\’)부터 파일에 이를 때까지 거쳐야 하는 디렉토리 명을 모두 연결하여 구성. 각 디렉토리는 ‘\’로 구분, 끝에 대상 파일의 이름을 추가해 완성. 한 파일에 대응되는 절대 경로는 유일, 그 파일의 고유값. 어떤 디렉토리에 있더라도 절대 경로를 사용하면 대상 파일을 가리킬 수 있음.
- 상대 경로 (Relative Path) : 현재 디렉토리를 기준으로 다른 파일에 이르는 경로를 상대적으로 표현. 어떤 파일을 가리키는 상대 경로의 수는 무한. .. → 이전 디렉토리 / . → 현재 디렉토리
.. 뿐만 아니라 ? → 뒤는 쿼리문으로 해석 # → 뒤는 주석처리 & → and 처럼 다른 문자들을 조합해서 사용가능

아주 유익한 내용이네요!