1. 배열의 속성
- 배열은 연속된 메모리 공간을 점유하며, 배열의 각 요소의 주소는 배열의 주소, 요소의 인덱스, 요소 자료형 크기를 이용하여 계산한다.
- 배열이 점유하는 공간의 크기는 요소의 개수와 요소 자료형의 크기를 곱한 값이며, 배열이 포함하는 요소의 개수를 배열의 길이라고 한다.
// array[n]의 배열 크기
sizeof(array) = sizeof(elem) * n
// array[n]의 k번째 배열을 참조
&array[k] = array + sizeof(elem) * k
2. OOB
2-1. 정의
- 배열의 임의 인덱스에 접근이 가능하여, 악의적으로 배열의 경계를 넘어 값을 읽거나 쓸 수 있는 취약점이다.
- 배열 참조에 사용되는 인데스를 임의 값으로 설정할 수 있다면, 배열의 주소로부터 특정 오프셋에 있는 메모리 값을 참조할 수 있다.
2-2. OOB가 발생하는 이유
- 요소를 참조할 때, 인덱스 값이 음수이거나, 배열의 길이를 벗어날 때 발생한다.
- 개발자가 인덱스의 범위에 대한 검사를 명시적으로 프로그래밍하지 않아 발생한다.
즉, 계산한 주소가 배열의 범위 안에 있는지 검사하지 않는 것이다.
3. OOB PoC (Out of bounds Proof-of-Concept)
3-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;
}
3-2. 실습과정 & 결과
- arr 배열의 범위를 벗어나는 -1과 100을 인덱스로 사용했음에도 아무런 경고 없이 출력되는 것을 볼 수 있다.
- arr[0]과 arr[100]주소 차이를 보면, 0x7ffe85050510 - 0x7ffe85050380 = 0x190이고, 0x190은 0x4 * 0x64(=100)이다.
즉, 배열의 범위를 벗어난 인덱스를 참조할 때, 앞서 살펴본 식을 그대로 사용한다는 점을 확인할 수 있다.
4. OOB 임의 주소 읽기
4-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;
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;
}
- secret.txt 파일을 만들어준다.
$ echo "THIS IS SECRET" > ./secret.txt
4-2. 실습 과정 & 결과
(1) 임의 주소 값을 구하려면?
- OOB로 임의 주소 값을 읽으려면, 읽으려는 변수와 배열의 오프셋을 알아야 한다.
- 배열과 변수가 같은 세그먼트에 할당되었다면 둘 사이의 오프셋은 항상 일정하므로 디버깅을 통해 쉽게 알 수 있지만,
같은 세그먼트가 아니라면 다른 취약점을 통해 두 변수의 주소를 구하고 차이를 계산해야 한다.
(2) 실습 코드 분석
- 인덱스에 대한 검증이 미흡해, 임의 주소 읽기가 가능한 코드이다.
- 배열의 크기가 3인 docs를 참조하는데 인덱스 값이 3보다 큰 것은 검사하지만, 음수는 검사하지 않는다.
(3) 실습 과정 및 결과
- docs와 secret_code는 지역변수로 스택에 할당되어 있다.
이에 같은 세그먼트에 할당되어 있기 때문에, docs에 대한 OOB를 통해 secret_code값을 읽을 수 있다. - 입력 값에 0을 입력하면 docs[0 - 1]이 되어서, docs[-1]이 된다. 이로 인해, OOB가 발생하고 secret.txt 파일의 값을 읽을 수 있다.
5. OOB 임의 주소 쓰기
5-1. 실습 코드
// 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;
}
5-2. 실습 과정 & 결과
(1) 실습 코드 분석
- 인덱스에 대한 검증이 미흡해, 임의 주소에 값을 쓸 수 있는 코드이다.
- 24byte 크기의 Student 구조체가 존재하며, 해당 구조체 10개를 포함하는 배열 stu와 isAdmin이 전역 변수로 선언되어 있다.
- 인덱스를 입력받아와, 인덱스에 해당하는 Student구조체의 attending에 1을 대입한다.
- isAdmin이 True이면 "Access granted."가 출력된다.
(2) 실습 과정 및 결과
- stu와 isAdmin 둘 다 전역변수로 같은 data세그먼트에 할당되어 있다.
- 디버거로 주소를 비교해보면, isAdmin이 stu보다 240바이트가 높은 주소에 있는 것을 확인할 수 있다.
( 0x201130 - 0x201040 = 0xF0 )
- Student구조체의 크기가 24byte로 10번째 인덱스를 참조하면, isAdmin에 1이 작성되도록 조작할 수 있다.
6. 해결방안
- 프로그램에서 배열을 사용할 때 인덱스의 값이 음수인지, 배열의 크기가 넘어서지 않는지 검사하는 코드가 포함되어야 한다.
'Dreamhack > Lecture & Practice' 카테고리의 다른 글
[Practice] 64bit_FSB (Format String Bug) (0) | 2022.08.20 |
---|---|
[Lecture] Format String Bug (FSB) (0) | 2022.08.10 |
[Practice] Hook Overwrite (0) | 2022.07.14 |
[Lecture] PIE (Position-Independent Executable) (0) | 2022.07.12 |
[Lecture] RELRO (RELocation Read-Only) (0) | 2022.07.11 |