pwnable

dandb3·2024년 3월 25일
0

동아리 스터디

목록 보기
1/1

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)
    • 연산 결과가 0일 때 set 된다.
  • SF (Sign Flag)
    • 부호 있는 연산에 쓰인다.
    • 결과가 양수이면 (최상위 비트가 0이면), 0
    • 결과가 음수이면 (최상위 비트가 1이면), 1
  • OF (Overflow Flag)
    • 부호 있는 연산에 쓰인다.
    • 자리 올림이 생길 경우 set 된다.

세그먼트 레지스터

16비트의 크기를 갖는 레지스터.
8086 아키텍쳐의 경우, 세그먼트 레지스터는 각 세그먼트의 base 주소를 가리키는 역할을 했었음.

  • CS
  • DS
  • ES
  • SS
  • FS
  • GS

그냥 이런 게 있다. 간단하게만 하고 넘어가자.

함수 호출 규약

공통적으로 진행되는 과정

스택 프레임

  • 함수가 호출될 때, 함수만의 스택영역을 구분하기 위해 생성되는 공간.

함수 프롤로그

  • 함수 호출

    call main

    과 같다.
    내부적으로는,

    push rip # 돌아갈 주소(return address)를 stack에 push
    jmp main # main 함수 호출

    과 사실상 동등하다.

  • 함수 호출 후

    push rbp
    mov rbp rsp

함수 에필로그

스택 프레임을 없애고, 원래의 흐름으로 돌아간다.

leave
ret

leave는 사실상 두 개의 명령어로 이루어져 있다고 봐도 무방하다.

mov rsp, rbp
pop rbp

ret은..

pop rip
jmp rip

라고는 하는데, 사실 rip 값이 설정되는 순간 해당 부분의 코드가 실행되기 때문에 내 생각에는 jmp rip는 이해를 위해 추가한 부분 같음.

cdecl

x86 아키텍쳐가 사용하는 함수 호출 규약.
함수 호출에 필요한 변수들을 스택을 통해서 전달한다.

  • 진행 순서
    1. 함수 인자들을 순서대로(역순으로) 스택에 push 한다.
    2. 함수를 호출한다.
    3. 함수 내용 진행
    4. 함수가 끝난다 (return)
    5. 함수에 인자 전달을 위해 사용한 스택을 정리한다.

system V

x64 아키텍쳐가 사용하는 함수 호출 규약.
함수 호출에 필요한 변수들을 레지스터를 통해 전달한다.
많은 인자가 필요한 경우 스택을 이용한다.

진행 순서

  1. 함수 인자들을 레지스터에 저장한다.
    순서 : rdi -> rsi -> rdx -> rcx -> r8 -> r9 -> 스택...
  2. 함수를 호출한다.
  3. 함수 내용 진행
  4. 함수가 끝난다 (return)
  5. 함수에 인자 전달을 위해 사용한 스택(이 있다면)을 정리한다.
profile
공부 내용 저장소

0개의 댓글