보안/시스템 해킹

[ 시스템 해킹 ] Memory Corruption : Out of Bounds

haena02 2022. 8. 4. 20:19
반응형

같은 자료형의 변수나 객체를 여러 개 관리해야 하면, 이들을 요소로 하는 배열을 선언하여 사용한다.

배열은 같은 자료형의 요소(Element)들로 이루어져 있는데, 각 요소의 위치를 인덱스(Index)라고 한다.

 Out of Bounds (OOB)는 배열의 임의 인덱스에 접근할 수 있는 취약점이다.

배열은 연속된 메모리 공간을 점유하며, 크기는 요소의 개수와 요소 자료형의 크기를 곱한 값이 된다.

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

 

1. Out of Bounds

OOB는 요소를 참조할 때, 인덱스 값이 음수이거나 배열의 길이를 벗어날 때 발생한다.

개발자가 인덱스의 범위에 대한 검사를 명시적으로 프로그래밍하지 않으면, 프로세스는 앞서 배운 식을 따라 요소의 주소를 계산할 뿐, 계산한 주소가 배열의 범위 안에 있는지 검사하지 않는다.

따라서 만약 사용자가 배열 참조에 사용되는 인덱스를 임의 값으로 설정할 수 있다면, 배열의 주소로부터 특정 오프셋에 있는 메모리의 값을 참조할 수 있다. 이를 배열의 범위를 벗어나는 참조라 하여 Out of Bounds라고 부른다.

 

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

즉, OOB를 방지하는 것은 전적으로 개발자의 몫이다.

 

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

배열과 변수가 같은 세그먼트에 할당되어 있다면, 둘 사이의 오프셋은 항상 일정하므로 디버깅을 통해 쉽게 알아낼 수 있다. 만약 같은 세그먼트가 아니라면, 다른 취약점을 통해 두 변수의 주소를 구하고, 차이를 계산해야 한다.

 

 

1.1 임의 주소 읽기

// 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;  //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++) {  //docs 배열 하나씩 읽어보기
    printf("%d. %s\n", i + 1, docs[i]);
  }
  
  printf("> ");
  scanf("%d", &idx);  // 인덱스 입력받기
  
  if (idx > 4) {  // 4보다 크면 종료
    printf("Detect out-of-bounds");
    exit(-1);
  }
  
  puts(docs[idx - 1]);  // 입력한 인덱스 배열 출력
  return 0;
  
}

코드설명을 해보자면 인덱스 번호를 받아 4보다 크면 종료하고 아니면 인덱스에 따른 내용을 출력해준다. 

하지만 음수인지는 검사하지 않는다.

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

 

 

 

1.2 임의 주소 쓰기

 

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

#include <stdio.h>
#include <stdlib.h>

struct Student {  //24비트
  long attending;
  char *name;
  long age;
};

struct Student stu[10];  //240비트
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;
}

 

OOB를 이용하면 임의 주소에 값을 쓰는 것도 가능하다.

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

코드를 살펴보면, 24바이트 크기의 Student 구조체 10개를 포함하는 배열 stu와 isAdmin를 전역 변수로 선언한다.

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

예제 코드의 마지막 부분을 보면 isAdmin이 참인지 검사하는 부분이 있습니다. 해당 변수에 값을 직접 쓰는 부분은 없지만, 코드에 OOB취약점이 있으므로 이를 이용하여 isAdmin의 값을 조작할 수 있다.

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

 

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

실제로 11를 입력하여 그 위치의 값이 1로 바꼈고 if문이 실행되고 값이 출력된다.

반응형