본문 바로가기
Security/System Hacking

[Dreamhack System Hacking] STAGE 9

by 단월໒꒱ 2022. 2. 16.

[Memory Corruption : Out of Bounds]

1. 배열

 1) 배열의 속성

   - 배열은 연속된 메모리 공간을 점유

   - 배열이 점유하는 공간의 크기는 요소의 개수와 요소 자료형의 크기를 곱한 값

   - 배열이 포함하는 요소의 개수 = 배열의 길이

   - 배열 각 요소의 주소는 배열의 주소, 요소의 인덱스, 요소 자료형의 크기를 이용하여 계산됨

 

 

 

 

 2) Out of Bounds

   - 배열의 임의 인덱스에 접근할 수 있는 취약점

   - 배열의 범위를 벗어나는 참조

   - 요소를 참조할 때 인덱스 값이 음수이거나 배열의 길이를 벗어날 때 발생

   - 개발자가 인덱스 범위에 대한 검사를 명시적으로 프로그래밍하지 않으면 프로세스는 계산한 주소가 배열의 범위 안에 있는지 검사 x

   - 사용자가 배열 참조에 사용되는 인덱스를 임의 값으로 설정할 수 있다면, 배열의 주소로부터 특정 오프셋에 있는 메모리의 값 참조 가능

   - gcc에서 관련한 경고 띄우지 않음 -> 코드를 작성할 때, 인덱스 값이 음수인지, 배열의 길이를 넘어서지는 않는지 검사하는 게 바람직

 

2. Proof-of-Concept

 1) 예제 코드

 

// Name: oob.c
// Compile: gcc -o oob oob.c

#include <stdio.h>

int main() {
  int arr[10];

  printf("In Bound: \n");
  printf("arr: %p\n", arr);
  printf("arr[0]: %p\n\n", &arr[0]);

  printf("Out of Bounds: \n");
  printf("arr[-1]: %p\n", &arr[-1]);
  printf("arr[100]: %p\n", &arr[100]);

  return 0;
}

 

 

   - int형 변수 10개를 요소로 하는 배열 arr을 선언하고, 다양한 인덱스를 사용하여 배열 내부와 외부의 주소들을 출력하는 코드

   - 위의 예제 코드를 컴파일하고 실행

 

 

 

 

   - 컴파일러(gcc)는 배열의 범위를 명백히 벗어나는 -1과 100을 인덱스로 사용했음에도 아무런 경고를 띄워주지 않음

   - arr[0]와 arr[100]의 주소 차이 : 0x7fff764446b0 - 0x7fff76444520 = 0x190 =

   - 배열의 범위를 벗어난 인덱스를 참조해도 앞서 살펴본 식을 그대로 사용함을 확인할 수 있음

 

3. 임의 주소 읽기

 1) 임의 주소 읽기

   - OOB로 임의 주소의 값을 읽으려면, 읽으려는 변수와 배열의 오프셋을 알아야 함

   - 배열과 변수가 같은 세그먼트에 할당되어 있다면, 둘 사이의 오프셋은 항상 일정하므로 디버깅을 통해 쉽게 알아낼 수 있음

   - 만약 같은 세그먼트가 아니라면, 다른 취약점을 통해 두 변수의 주소를 구하고, 차이를 계산하는 과정이 필요

 

 2) 예제

 

// Name: oob_read.c
// Compile: gcc -o oob_read oob_read.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char secret[256];
int read_secret() {
  FILE *fp;
  if ((fp = fopen("secret.txt", "r")) == NULL) {
    fprintf(stderr, "`secret.exe` does not exist");
    return -1;
  }
  fgets(secret, sizeof(secret), fp);
  fclose(fp);
  return 0;
}
int main() {
  char *docs[] = {"COMPANY INFORMATION", "MEMBER LIST", "MEMBER SALARY",
                  "COMMUNITY"};
  char *secret_code = secret;
  int idx;
  // Read the secret file
  if (read_secret() != 0) {
    exit(-1);
  }
  // Exploit OOB to print the secret
  puts("What do you want to read?");
  for (int i = 0; i < 4; i++) {
    printf("%d. %s\n", i + 1, docs[i]);
  }
  printf("> ");
  scanf("%d", &idx);
  if (idx > 4) {
    printf("Detect out-of-bounds");
    exit(-1);
  }
  puts(docs[idx - 1]);
  return 0;
}

 

 

   - 길이가 3인 배열 docs를 참조하는데, 인덱스 값이 3보다 큰지만 검사하고, 음수인지는 검사하지 않음

   - docs와 secret_code은 모두 스택에 할당되어 있으므로, docs에 대한 OOB를 이용하면 secret_code의 값을 쉽게 읽을 수 있음

   - secret.txt파일을 만들고, oob_read의 OOB를 이용하여 secret.txt의 값을 읽어보자

 

 

 

 

4. 임의 주소 쓰기

 1) 임의 주소 쓰기

   - OOB를 이용하면 임의 주소에 값을 쓰기 가능

 

 2) 예제

   - 인덱스에 대한 검증이 미흡해 임의 주소에 값을 쓸 수 있는 코드

 

// Name: oob_write.c
// Compile: gcc -o oob_write oob_write.c
#include <stdio.h>
#include <stdlib.h>
struct Student {
  long attending;
  char *name;
  long age;
};
struct Student stu[10];
int isAdmin;
int main() {
  unsigned int idx;
  // Exploit OOB to read the secret
  puts("Who is present?");
  printf("(1-10)> ");
  scanf("%u", &idx);
  stu[idx - 1].attending = 1;
  if (isAdmin) printf("Access granted.\n");
  return 0;
}

 

 

   - 24바이트 크기의 Student 구조체 10개를 포함하는 배열 stu와 isAdmin를 전역 변수로 선언

   - 사용자로부터 인덱스를 입력받아서 인덱스에 해당하는 Student구조체의 attending에 1을 대입

   - 코드의 마지막 부분을 보면 isAdmin이 참인지 검사하는 부분이 있음

       - 해당 변수에 값을 직접 쓰는 부분은 없지만, 코드에 OOB취약점이 있으므로 이를 이용하여 isAdmin의 값 조작 가능

       - 이를 위해 디버거로 stu와 isAdmin의 주소를 확인해보면, isAdmin이 stu보다 240바이트 높은 주소에 있음을 알 수 있음

 

 

 

 

   - 배열을 구성하는 Student 구조체의 크기가 24바이트이므로, 10번째 인덱스를 참조하면 isAdmin을 조작할 수 있음

   - 예제를 컴파일하고 OOB취약점을 공격하여 isAdmin값을 조작할 수 있음

 

 

 

 

 

댓글