[Pwn] Basic knowledge

코코·2023년 5월 29일
0

pwn

목록 보기
3/10

앞으로 Pwnable 공격 기법에 필요한 개념과 공격 기법을 차근차근 정리해보려고 한다...❗


Stack Buffer overflow를 살펴보기 전, 알아야할 Memory Layout함수 호출 규약(Calling Convention)에 대해 먼저 정리해보려고 한다.

  • 환경
    OS : Ubuntu-18.04

✔ Memory Layout

Memroy Layout과 각 영역에 대한 권한을 같이 살펴보자.

Memory Layout은 아래와 같이 총 5개로 구분할 수 있다.
1. Text Segment
2. Initialized Data Segment
3. Uninitialized Data Segment(BSS)
4. Heap
5. Stack


Text Segment(Code Segment)

첫 번째로 살펴볼 구역은 Text Segment(텍스트 세그먼트)이다.
텍스트 세그먼트는 코드 세그먼트(Code Segment)라고도 불리며, 컴파일 된 프로그램의 기계어 코드를 가지고 있다.

초기에 텍스트 세그먼트는 읽기 & 쓰기 권한이 부여되었지만, 최근 OS에는 임의로 수정되는 것을 방지하기 위해 읽기 권한(Read)만 부여한다.


Initialized data Segment(Data Segment)

다음은 초기화 된 데이터 세그먼트이다.
초기화 된 데이터 세그먼트는 일반적으로 데이터 세그먼트(Data Segment)라고 불리며, 컴파일 시점에 값이 정해진 전역 변수 및 전역 상수들이 위치한다.

데이터 세그먼트는 읽기 권한(Read)만 부여되어있다.


추가로 데이터 세그먼트는 아래의 2종류로 분류할 수 있다.

  1. data 세그먼트
    : 쓰기가 가능한 세그먼트는 전역 변수와 같이 프로그램이 실행되면서 값이 변할 수 있는 데이터들이 위치한다.

  2. rodata 세그먼트
    : 쓰기가 불가능한 세그먼트로 프로그램이 실행되어도 값이 변하지 않는 데이터(전역 상수)들이 위치한다.


Uninitialized data Segment(BSS Segment)

다음은 초기화되지 않은 데이터 세그먼트이다.
초기화되지 않은 데이터 세그먼트는 BSS영역으로 불리며, 컴파일 시점에 값이 정해지지 않은 전역 변수가 위치한다.(선언 후 초기화하지 않은 데이터들이 위치)

BSS영역은 쓰기(Write) 및 읽기 권한(Read)이 부여되어 있다.


Heap

다음은 Heap 영역이다.
실행 중에 동적으로 할당될 수 있는 영역으로 Stack과 반대 방향으로 자라난다.

Heap영역은 쓰기(Write) 및 읽기 권한(Read)이 부여되어 있다.


Stack

마지막으로 Stack영역이다.
Stack 영역은 함수의 인자나 지역 변수와 같은 임시 변수들이 실행 중에 저장되는 공간으로 Heap 공간으로 반대 방향으로 자란다.

또한 Stack은 스택 프레임(Stack Frame)이라는 단위를 사용하고, 이 스택 프레임은 함수가 호출될 때 생성되었다가 반환될 때 해제된다.

Stack영역은 일반적으로 쓰기(Write) 및 읽기 권한(Read)이 부여되었지만, 보호기법 적용 여부에 따라 읽기(Read) 권한만 부여되어 있는 경우도 있다.


✔ Calling Convention

Ubuntu-18.04는 x86-64 아키텍처를 사용하며, 함수 호출 규약으로 AMD64 System V 호출 규약을 따른다.

⭐️ AMD64 System V

AMD64 SystmeV 함수 호출 규약은 짧게 SYSV라고 불린다.
SYSV는 함수 호출 시, 인자를 각각 차례대로 rdi ⇨ rsi ⇨ rdx ⇨ rcx ⇨ r8 ⇨ r9 레지스터들을 사용하여 전달하고 이후 인자들은 Stack을 통해 전달한다.

또한 함수의 반환값은 rax 레지스터를 사용하며 전달하고, 스택 프레임은 rsp 레지스터를 기준으로 아래쪽으로 확장된다.


  • study.c
// gcc -o study study.c

#include <stdio.h>

int sub_func(char *arg1, char *arg2, char *arg3, char *arg4, char *arg5, char *arg6, char *arg7);

int main(int argc, char argv[]){
    int ret;

    printf("[*] AMD64 System V!\n");
    
    ret = sub_func("Arg1", "Arg2", "Arg3", "Arg4", "Arg5", "Arg6", "Arg7");

    printf("[*] Sub_function return value : %d\n", ret);

    return 0;
}

int sub_func(char *arg1, char *arg2, char *arg3, char *arg4, char *arg5, char *arg6, char *arg7){
    printf("[*] Sub Function is Called !\n");

    printf("Arg1 : %s\n", arg1);
    printf("Arg2 : %s\n", arg2);
    printf("Arg3 : %s\n", arg3);
    printf("Arg4 : %s\n", arg4);
    printf("Arg5 : %s\n", arg5);
    printf("Arg6 : %s\n", arg6);
    printf("Arg7 : %s\n", arg7);

    return 1+2+3+4+5+6+7;
}

위의 코드를 컴파일하여, pwndbg를 통해 살펴보자.

sub_func를 호출하는 부분으로 가서, 레지스터와 RSP를 확인한 결과는 아래와 같다.
AMD64 SYSV 함수 호출 규약에 따라,

  • rdi = "Arg1"
  • rsi = "Arg2"
  • rdx = "Arg3"
  • rcx = "Arg4"
  • r8 = "Arg5"
  • r9 = "Arg6"

차례대로 들어간 것을 확인할 수 있다. 그리고 Arg7만 별도로 Stack을 통해 전달되는 것을 확인할 수 있다.


마지막으로 함수의 리턴값이 rax 레지스터를 통해 전달되는지 확인해보자.

RAX 레지스터에 0x1c(=28)가 전달된 것을 확인할 수 있다!


Bye 👋




※ 참고
👉 https://www.geeksforgeeks.org/memory-layout-of-c-program/
👉 http://www.tcpschool.com/c/c_memory_structure
👉 https://dystopia050119.tistory.com/34#x86-64%ED%98%B8%EC%B6%9C%20%EA%B7%9C%EC%95%BD%20%3A%20SYSV-1

profile
화이팅!

0개의 댓글