pwnable?
프로그램의 취약점을 찾아서 공격하는 해킹 분야.
워게임의 대부분(?)이 원격 서버에서 돌아가는 프로그램의 취약점을 찾아서 익스플로잇을 해 flag에 있는 값을 읽어내는 것을 목적으로 한다.
알아야 할 것?
- 프로그램은 메모리에 로딩되어서 실행된다.
- 어떤 방식으로 프로그램이 메모리에 로드되는지 이해하고 있어야 한다. (프로세스의 메모리 구성, 함수 호출 규약, 기본 어셈블리, 기본 컴퓨터 구조,, 등등)
프로세스의 메모리 구조
프로세스는 크게 text, data, heap, stack으로 이루어져 있다.
- text
- 실제로 실행되는 바이트코드가 담겨있는 영역.
- 예를 들어, main 함수의 실행 코드가 이 영역에 저장된다.
- data
- 변수가 저장되는 영역.
- 좀 더 자세하게는 data, bss로 나뉘며, data는 초기화 된 전역변수가 저장되고, bss는 초기화되지 않은 전역변수가 저장된다.
- heap
- 동적할당이 이루어지는 경우 사용되는 영역.
- 그림에서 보다시피, 크기가 정해져 있지 않으며, 동적할당이 어떻게 일어나는지에 따라 크기가 변화한다.
- 낮은 메모리주소에서 높은 메모리주소로 증가하게 된다.
- stack
- 실제 프로그램이 실행되면서 필요한 저장공간.
- 함수 호출 / 지역변수 / 버퍼 ... 등의 작업을 하기 위해 꼭 필요하다.
- 스택의 경우에도 크기가 정해져 있지 않으며, 프로그램이 진행하면서 스택의 크기가 계속 변화한다.
- 높은 메모리 주소에서 낮은 메모리 주소로 증가하게 된다.
레지스터
x86_64 아키텍쳐를 기준으로 설명한다.
범용 레지스터
다양한 용도로 사용되는 레지스터. 관행적으로 사용되는 예시가 있으나, 이 외에도 많이 사용된다.
- rax : 함수의 return value가 저장된다.
- rbx
- rcx : 반복문의 반복 횟수를 카운트 하는데에 사용된다.
- rdx
- rsi : 데이터를 옮길 때 원본을 가리킨다.
- rdi : 데이터를 옮길 때 목적지를 가리킨다.
- r8 ~ r15
- rbp : 현재 스택의 base를 가리킨다.
- rsp : 스택의 위치를 가리킨다.
명령어 포인터
- rip : PC(Program Counter)에 해당한다. 현재 코드의 실행 위치가 어디인지를 가리킨다.
레지스터의 호환성
현재는 64비트 아키텍쳐가 상용화 되었지만, 예전에는 더 낮은 비트의 아키텍쳐가 주로 사용되었다.
그래서 예전에 사용되던 레지스터의 명칭은 현재 레지스터의 하위비트를 가리키게 되었다.
정리하면 아래와 같다.
예시로 rax만 들 것이다. 나머지는 동일한 방법으로 사용된다.
- eax : rax의 하위 32비트
- ax : rax의 하위 16비트
- ah : rax의 하위 9 ~ 16비트
- al : rax의 하위 8비트
r8 ~ r15의 경우에는 좀 다르다.
우선 데이터의 사이즈에 대해 알아보자.
데이터의 사이즈
-
QWORD : 64비트 크기
-
DWORD : 32비트 크기
-
WORD : 16비트 크기
-
BYTE : 8비트 크기
-
r8d (r8 DWORD) : r8의 하위 32비트
-
r8w (r8 WORD) : r8의 하위 16비트
-
r8b (r8 BYTE) : r8의 하위 8비트
상태 레지스터 (플래그 레지스터)
64비트 플래그 레지스터.
주로 사용되는 플래그 몇 개만 나열하자면,,
- CF (Carry Flag)
- 부호 없는 연산에 쓰인다.
- 자리 올림이 생길 경우 set 된다.
- ZF (Zero Flag)
- SF (Sign Flag)
- 부호 있는 연산에 쓰인다.
- 결과가 양수이면 (최상위 비트가 0이면), 0
- 결과가 음수이면 (최상위 비트가 1이면), 1
- OF (Overflow Flag)
- 부호 있는 연산에 쓰인다.
- 자리 올림이 생길 경우 set 된다.
세그먼트 레지스터
16비트의 크기를 갖는 레지스터.
8086 아키텍쳐의 경우, 세그먼트 레지스터는 각 세그먼트의 base 주소를 가리키는 역할을 했었음.
그냥 이런 게 있다. 간단하게만 하고 넘어가자.
함수 호출 규약
공통적으로 진행되는 과정
스택 프레임
- 함수가 호출될 때, 함수만의 스택영역을 구분하기 위해 생성되는 공간.
함수 프롤로그
함수 에필로그
스택 프레임을 없애고, 원래의 흐름으로 돌아간다.
leave
ret
leave는 사실상 두 개의 명령어로 이루어져 있다고 봐도 무방하다.
mov rsp, rbp
pop rbp
ret은..
pop rip
jmp rip
라고는 하는데, 사실 rip 값이 설정되는 순간 해당 부분의 코드가 실행되기 때문에 내 생각에는 jmp rip
는 이해를 위해 추가한 부분 같음.
cdecl
x86 아키텍쳐가 사용하는 함수 호출 규약.
함수 호출에 필요한 변수들을 스택을 통해서 전달한다.
- 진행 순서
- 함수 인자들을 순서대로(역순으로) 스택에 push 한다.
- 함수를 호출한다.
- 함수 내용 진행
- 함수가 끝난다 (return)
- 함수에 인자 전달을 위해 사용한 스택을 정리한다.
system V
x64 아키텍쳐가 사용하는 함수 호출 규약.
함수 호출에 필요한 변수들을 레지스터를 통해 전달한다.
많은 인자가 필요한 경우 스택을 이용한다.
진행 순서
- 함수 인자들을 레지스터에 저장한다.
순서 : rdi -> rsi -> rdx -> rcx -> r8 -> r9 -> 스택...
- 함수를 호출한다.
- 함수 내용 진행
- 함수가 끝난다 (return)
- 함수에 인자 전달을 위해 사용한 스택(이 있다면)을 정리한다.