배열 등의 지정된 메모리 범위를 벗어나는 인덱스에 접근이 가능해진다면, 예상하지 않은 문제가 발생할 것이다.
버퍼 오버플로우랑 범위를 벗어난 취약점 모두 중요한 취약점인데, 둘의 차이점이 궁금했다. Buffer Overflow는 `버퍼에 대한 길이 검증을 수행하지 않아 데이터가 버퍼의 경계를 넘어설 때, 인접한 메모리를 덮어씌우고 프로그램 흐름을 변화시킬 수 있는` 취약점이며 버퍼 오버플로우로 인한 위험성은 임의 코드 실행, 메모리 구조 파괴가 존재한다.
char buffer[8];
strcpy(buffer, "This is a very long string"); // buffer의 경계를 넘는 데이터가 쓰여서 오버플로우 발생
Out-of-Bounds는 `인덱스나 포인터가 유효 범위를 벗어날 때, 할당된 범위 외의 메모리에 접근하려고 할 때 예상하지 못한 메모리 값을 읽어오거나 덮어씌울 수 있는` 취약점이며 범위를 벗어난 취약점으로 인한 위험성은 메모리 누수, 정보 노출, 프로그램 충돌 등의 문제가 존재한다.
int arr[5] = {1, 2, 3, 4, 5};
int out_of_bounds_value = arr[10]; // 유효한 범위 (0~4)를 벗어난 참조
name이랑 command라는 두 가지 배열이 존재하는데, name은 16바이트, command는 OS 명령어로 보여지는 cat, ls, id, ps, file ./oob라는 내용이 담겨있으며, 10개의 포인터를 허용하지만 사실상 5개의 명령어만 존재한다.
여기서 oob라는 파일을 실행하려고 하는 명령어가 있는데 어떤 의도인지 궁금해졌다.
scanf("%d", &idx);
system(command[idx]);
문제는 이 부분인데, command에 0~4 범위를 벗어나게 사용자로부터 idx를 입력받으면 그대로 OOB 문제로 발전할 가능성이 있다. 물론 name 배열도 비슷한 문제가 발생할 수 있지만, 16바이트보다 큰 데이터를 입력받는다는건 Buffer Overflow랑 연결되는거니까 둘의 느낌이 다르다는걸 확인해볼 수 있는거다.
그럼 이제 바이너리 파일을 확인해볼 차례이다.
바이너리에 포함된 함수 목록 출력
이건 바이너리에 포함된 함수 목록인데, initialize, main, alarm_handler 같은 c 소스코드에서도 확인했던 함수들이 포함되어있는 것을 확인할 수 있다. 밑에서 디스어셈블링한거에 함수가 어떻게 나타나는지 알아보자.
여기서 0x10은 16바이트를 나타내기 때문에 후에 나타나는 0x804a0ac는 name 배열로 예상할 수 있고, 0x080486f4 <+41>: call 0x80484b0 <printf@plt>이 부분은 printf("Admin name: "); 여기같다. 0x08048708 <+61>: call 0x80484a0 <read@plt>는 read 함수를 사용하는 부분이다.
1. 0x08048723 <+88>에서 lea -0x10(%ebp), %eax로 eax 레지스터에 0x10(%ebp) 주소를 저장한다. 2. 0x08048727 <+92>에서 push $0x8048832("%d")한다 == scanf 형식 문자열을 스택에 푸시한다. 3. 0x0804872c <+97>에서 call __isoc99_scanf@plt를 호출하여 scanf 함수를 실행한다. 4. scanf를 통해 사용자에게 입력받은 값이 -0x10(%ebp) 주소에 저장된다.
두번째에는 문제의 command[idx]에 접근하는 과정이 나타난다.
1. mov -0x10(%ebp),%eax는 scanf로 입력받은 값을 eax 레지스터에 로드한다. (eax는 command 배열의 인덱스) 2. mov 0x804a060(,%eax,4),%eax 여기는 command 배열에서 입력받은 인덱스를 통해 명령어 주소를 가져온다.
배열의 구조 주소는 여기에서 나온다. 0x804a060 : 여기는 command 배열의 시작 주소로 추정해볼 수 있다. (,%eax,4) : 4바이트 간격으로 eax 인덱스를 사용해 배열에 접근한다. (eax=0 -> 첫번째 명령어, eax=1 -> 두번째 명령어)
그럼 핵심은 eax 값이 초과하면 Out-of-Bounds가 발생하는거 아닌가?
즉, eax=5를 넘어서거나 eax=음수값일 때 메모리 문제가 발생하게 되는 것이다.
근데 이런걸 어디에 활용해야 할지 생각해봤는데 최종적인 목적은 flag파일을 cat해야 하는 것인데 그건 또 name이랑 command를 모두 고려해야 한다는 결과가 나온다.
//command 배열 시작 주소
0x08048737 <+108>: mov 0x804a060(,%eax,4),%eax
//name 배열 시작 주소
0x08048701 <+54>: push $0x804a0ac
위 정보를 통해 각각의 시작 주소는 0x804a060, 0x804a0ac임을 확인할 수 있고,
이 둘의 거리는 76이다.
그런데 eax가 4바이트 간격으로 떨어져 있다고 하니 76/4하면 [19]만큼의 거리로 떨어져 있다는 것을 알 수 있다.
그러니까 command[19]가 곧 name이라는 뜻이 된다..? 라고 생각해볼 수 있다.
그럼
Out-of-Bounds는`인덱스나 포인터가 유효 범위를 벗어날 때, 할당된 범위 외의 메모리에 접근하려고 할 때 예상하지 못한 메모리 값을 읽어오거나 덮어씌울 수 있는`취약점
이 점에 유의할 때,
command[19]는 name 버퍼와 메모리 상 겹쳐 있게 되고, 이는 즉 name 버퍼의 주소를 참조하도록 오버랩 된건데,
여기서 name 버퍼에 원하는 명령을 넣으면 그 명령이 command[19]를 통해 실행할 수 있게 되는거다.
이때 command가 포인터 배열인게 취약점의 핵심인데, 만약 일반 문자열 배열이였다면 system() 함수 호출 시도가 의도대로 실행되지 않아 OOB가 성공하지 못하게 되었을 것이다. command의 각 요소가 특정 문자열 주소를 가리킨다는걸 악용하는게 이 문제의 핵심이다.
그냥 실행해서 여러가지 입력해보면 안된다. OOB exploit 코드를 작성해보자.
브레이크포인트를 main함수에 걸어서 name의 값 0xf7e406c0을 확인해서 직접 바이너리에서 플래그를 얻는 코드를 작성해봤는데 안된다. 그래서 그냥 dreamhack 원격서버랑 포트번호로 플래그를 얻어봐야겠다...
system함수는 문자열 주소를 인수로 받는데, 이때 인수는 const char * 형태의 문자열 포인터여야 한다.
shell = b"\xb0\xa0\x04\x08" + b"cat flag"
name 퍼버 주소를 리틀 엔디안 형식으로 나타내고, 이 주소를 사용해 command[idx]가 name 버퍼를 가리키도록 한다. name 버퍼에 "cat flag"라는 문자열을 넣어두고 system 함수가 command[19]를 통해 name 버퍼 내용을 실행하게 해보자.
exploit 코드 ⬇️
p = remote(host, port)를 통해 서버에 연결하고, 드림핵 서버와 데이터를 주고받도록 한다. name의 버퍼의 메모리 주소 b"\xb0\xa0\x04\x08"와 cat flag라는 실행하고자 하는 명령어를 셸 변수에 저장한다. 즉, 셸 변수는 name 버퍼와 cat flag라는 명령어가 합쳐진 상태이다.
p.sendline(shell)을 통해 name 버퍼에 shell 페이로드를 전송하고, 서버는 이 페이로드를 받아 name 버퍼에 name 버퍼주소+cat flag 명령어를 저장하게 된다.
p.sendline(b"19")를 통해 idx 값으로 19를 입력하고, command[19]가 name 버퍼의 시작 주소를 가리키게 된다. system(command[19])가 호출되면, command[19]가 name 버퍼를 가리키고, name 버퍼 내용 cat flag가 실행된다.
char *command[10] = { "cat", "ls", "id", "ps", "file ./oob" };
printf("What do you want?: ");
scanf("%d", &idx);
system(command[idx]);
이 main함수의 부분을 기억하고 있다면, system(command[idx])가 어떻게 cat flag까지 도달할 수 있는지 알 수 있을 것이다.
플래그를 찾았다!!
┌──(pwn_env)─(root㉿kali)-[/home/kali/Desktop]
└─# ./OOB_exploit.py
[+] Opening connection to host3.dreamhack.games on port 9914: Done
b'Admin name:'
b' What do you want?:'
[*] Switching to interactive mode
DH{}[*] Got EOF while reading in interactive
$