서론
프로그램 개발에선 배열이 필요하다
배열은 같은 자료형 요소(Element)들로 이루어져 있는데, 각 요소를 인덱스(Index)라고 한다.
처음 c언어에서 배열을 사용하면 인덱스에서 실수가 생긴다.
현실에서 1번쨰가 프로그래밍에서선 0번째 요소라고 하는데서 도 실수가 일어나고
사소한 부등호나, 인덱스를 벗어나서 참조해도 경고가 없는 컴파일러등이 이유가 된다
👆이런 실수는 lucky person이면 비정상 종료지만
때때로 취약점이 된다
대표적으론 Out of Bounds (OOB) : 배열의 인덱스에 접근 가능
💡Out of Bounds의 유래
스포츠(농구)에서 필드, 강의를 벗어난 행위를 말한다.
이 취약점도 배열의 경계를 넘어가는 점에서 비슷헤서 Out of Bounds라고 이름 지었다.
Out of Bounds
배열의 속성
배열은 연속된 공간을 가지며, 그 크기는 요소의 개수와, 자료형의 크기를 곱한 값이다.
배열의 길이 (Length): 배열이 포함하는 요소의 개수
배열의 각 요소의 주소는 배열의 주소, 요소의 인덱스, 요소 자료형의 크기를 이용하여 계싼할 수 있다!
👇그 방법 이다.
Out of Bounds
oob는 요소를 참조할 떄
- 인덱스 값이 음수
- 배열의 길이를 벗어날 떄
발생한다.
개발자가 인덱스 범위를 프로그래밍 안하면,
프로세스는 앞에서 말한방법에 따라 주소를 계싼하지, 그 주소가 배열의 범위 안에 있는지는 모른다.
사용자가 배열의 참조 인덱스를 맘데로 할 수 있으면,
배열의 범위를 벗어난 참조(Out of Bounds)를 할 수 있다.
Proof-of-Concept
👇 아래 코드는 int형 변수 10개가 요소인 배열 arr이 있고, 배열 내부/외부 주소를 출력한다.
실행결과
먼저 컴파일러(gcc)는 배열의 범위를 벗어나는 -1,100을 인덱스로 썻는데 error가 없다
그래서 개발자들이 알잘딱으로 oob를 방지 해야 한다.
그리고 arr[0]과 arr[100]의 주소 차이는
0x7ffc4b7ef6d0 - 0x7ffc4b7ef860 = 0x190 = 100*4
배열의 범위를 벗어난 인덱스도 앞에서 사용한 식을 그대로 쓴다.
임의 주소 읽기
oob로 임의 주소의 값을 읽으려면,
.읽으려는 변수와 배열의 오프셋을 알아야 한다. (오프셋은 기준점으로부터의 거리를 의미합니다)
배열과 변수가 같은 세그먼트에 할당되어 있으면,
배열과 변수 사이의 메모리 주소 간 거리가 일정해서 디버깅으로 쉽게 알아낼 수 있따.
그러나 다른 세그먼트라면, 다른 취약점으로 주소를 구하고, 차이를 계산해야한다.
👇아래 코드는 인덱스에 대한 검증이 약한 주소 읽기가 가능한 코드다
// 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.txt` 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;
}
길이가 4인 배열 docs를 참조하는데, 인덱스값이 4보단 큰지는 검사하는데 음수인지는 검사 안한다.
그리고 docs와 secret_code는 모두 스택에 할당되어 있으니까, docs에대한 oob로 secret_code의 값을 읽을 수 있따.
실행결과
secret.txt파일을 만들고 그 값을 읽어보자
👇0을 입력하면 docs[-1]이 되고 그러면 경계를 벗어나기 때문에 그 이전의 변수를 가리킨다.
임의 주소 쓰기
임의 주소의 값을 쓸 수도 있따.
👇아래 코드는 주소 값을 쓸 수 있는 예제이다. (인덱스 검정이 부족하다ㅠㅠ)
// Name: oob_write.c
// Compile: gcc -o oob_write oob_write.c
#include <stdio.h>
#include <stdlib.h>
struct Student {
long attending; //8바이트
char *name; //8바이트
long age; //8바이트
};
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바이트 크기의 구조체를 10개 가진 stu와 isAdmin을 전역 변수로 선언한다.
사용자로 부터 입력받은 인덱스에 해당하는 구조체의 attending에 1을 대입한다.
하지만 코드에는 10이상의 숫자르 입력했을떄 예외처리가 없기 떄문에 oob를 할 수 있다.
👇 isAdmin이 stu보다 240바이트 높이 있는걸 알 수 있따.
stu배열을 구성하는 student 구조체는 24바이트크기로, 10번째 인덱스를 참조하면 isAdmin을 조작할 수 잇따.
그래서 11을 입력해서 10번쨰 인덱스에 1을 넣어주었다.
마치며
끝
'hacking > pwnable' 카테고리의 다른 글
[Dream hack] bof (0) | 2024.10.06 |
---|---|
[Dream hack] Quiz: Out of Bounds-1 (0) | 2024.09.20 |
[Dream hack] Return Address Overwrite (0) | 2024.09.18 |
[Dream hack] Logical Bug: Command Injection (2) | 2024.09.06 |
[Dream hack] sint (p32) (0) | 2024.09.05 |