csapp 3.10 이다
3.11 이 3단원 끝이니 얼른 끝내자
ㅅㅂ 하기 졸라게 싫다 진짜
데이터와 제어가 서로 상호작용하는 것에 대해 설명한단다
포인터와, gdb, 버퍼 오버플로, 스택에 필요한 저장 공간 변경
을 다룬다
포인터에 대한 설명이 나온다
int *ip;
char **cpp;
예시 코드를 보면 ip
는 int
타입 객체를 가리키는 포인터다
cpp
는 char
타입 객체를 가리키는 포인터를 또 가라키는 포인터다
일반적으로, 객체의 타입이 T
라면 포인터 타입은 *T
다
특별한 타입 void *
는 범용 포인터다
포인터 타입 정보는 기계어에는 없고 C언어에서 제공하는 추상 개념이다
이 값은 해당 타입의 객체가 저장된 주소다
단, NULL(0)
은 아무것도 가라키지 않다는 뜻이다
&
연산자로 생성된다&
→ 변수나 구조체·배열 요소(lvalue)의 주소를 가져옴
기계어에서는 주로 leaq
명령어로 주소 계산
*
연산자로 역참조한다*
→ 해당 주소에 있는 값을 가져오거나 저장
기계어에서는 메모리 읽기/쓰기 명령으로 구현
배열 이름은 포인터처럼 참조 가능(단, 값 변경 불가)
a[3]
↔ *(a + 3)
→ 완전히 동일한 의미
포인터 산술(pointer arithmetic)은 타입 크기만큼 주소를 이동
(char *)p + 7 // p + 7
(int *)p + 7 // p + (7 * sizeof(int))
int fun(int x, int *p);
int (*fp)(int, int *);
fp = fun;
int result = fp(3, &y);
gdb라는 디버거 소개한다
솔직히 gdb같은 거 요즘은 안쓰니까 걍 넘어가겠다
뭐 나중에 쓰면 그때에 다시 제대로 정리하겠다
C언어는 배열 참조시 경계 검사 안한다
뭐 어쩌라고라 생각 할 수 있지만 꽤 중요하다
범위를 벗어난 쓰기가 발생해 데이터 손상 가능하고 이로 인해 오류 발생하니 말이다
대표적인게 버퍼 오버플로다
배열에 할당된 공간보다 문자열 길이가 크면 발생한다
이러할 경우 다른 메모리 공간을 침범하기에
악용 가능하다
초과하는 길이의 문자열에 익스플로잇 코드를 넣는 등 말이다
ㅈㄴ 흔한 공격이니 방어하는 법 정돈 알아야한다
공격하려면 코드와 코드의 주소 포인터 다 알아야 한다
그러니 이를 무력화시키기 위해 실행시마다 스택의 위치를 다르게 하는 것이다
두 번째는 스택 손상 여부를 감지하는 것이다
카나리아 값이라는 특별한 보호값을 끼워넣어
이것이 변했는지 체크하며 변했을 경우 즉시 프로그램 종료한다
마지막 방어선은 실행 가능 코드 삽입 능력 자체를 제거하는 것이다
스택, 힙, 전역 데이터 영역 등을 읽기/쓰기는 가능하지만 실행 불가로 설정하는 방법이다
가변 크기 지역 저장소(예: 실행 시 크기 결정되는 배열, alloca
호출)가 있을 때
→ 컴파일 시 스택 크기 예측 불가
프레임 포인터(%rbp
) 사용해 고정 위치 참조
→ 고정 크기 지역 변수와 가변 크기 영역을 안정적으로 접근 가능
%rbp
는 callee-saved → 함수 시작 시 저장, 종료 시 복원
leave
명령 → 스택 프레임 전체 해제 (movq %rbp, %rsp
+ popq %rbp
와 동일)
x86-64에서는 필요한 경우에만 프레임 포인터 사용 (옛 IA32는 기본 사용)
%rbp
를 프레임 포인터로 설정%rbp
기준 고정 오프셋 참조%rbp
아래쪽에 필요한 만큼 동적 공간 확보%rbp
저장 (pushq %rbp
)%rbp
← %rsp
leave
로 프레임 해제ret
높은 주소
┌─────────────────────┐
│ Return address │
├─────────────────────┤
│ Saved %rbp │ ← %rbp
├─────────────────────┤
│ i (8 bytes) │
├─────────────────────┤
│ (Unused) (8 bytes) │
├─────────────────────┤
│ p[0] ... p[n-1] │ (8n bytes)
└─────────────────────┘ ← %rsp
낮은 주소
값의 타입을 강제로 바꾸는 것 ↩