✔️ Out of bounds
프로그램에서는 배열을 선언하여 사용하는데, 배열은 같은 자료형의
요소(Element)들로 이루어져있고 각 요소의 위치를 인덱스(Index)라고 한다.
코드를 구성할 때 발생하는 실수 혹은 인덱스를 벗어나 참조해도 경고를
띄워주지 않는 컴파일러 등에 의해 취약점의 원인이 될 수 있고,
이러한 것을 배열의 임의 인덱스에 접근한다는 Out of Bounds(OOB)라고 한다.
배열은 연속된 메모리 공간을 차지하고 있고,
해당 공간의 크기는 요소의 개수와 자료형의 크기를 곱한 값이 된다.
이러한 배열의 요소 개수를 배열의 길이(Length)라고 한다.
배열의 각 요소의 주소는 배열의 주소와 요소의 인덱스, 요소 자료형의 크기를
이용하여 계산하게 되는데 &array[k]에 해당하는 인덱스의 주소의 참조 값은
array + sizeof(요소)*k 에 대한 식으로 나타낼 수 있다.
OOB는 요소를 참조할 때 인덱스의 값이 음수 혹은 배열의 길이를 벗어날 때
발생하게 되는데, 프로세스는 요소의 주소를 계산할 뿐 계산한 주소가
배열의 범위 안에 있는지 검사하지 않으므로 취약점의 원인이 될 수 있다.
만약에 사용자가 배열 참조에 사용되는 인덱스를 임의 값으로 설정한다면
배열의 주소로부터 특정 오프셋에 있는 메모리 값을 참조할 수 있고,
이러한 참조를 Out of Bounds라고 부르는 것이다.
다음과 같은 예제 코드가 존재하고 프로그램이 존재할 때 컴파일 후 실행하게 되면,
인덱스가 배열의 범위를 벗어나는 -1과 100에 대해 정상 출력이 되고 gcc는
이러한 인덱스를 사용했음에도 경고를 띄워주지 않는다.
arr[0], arr[100]의 주소차이는 fb0 - e20 = 190에 해당하는 값(hex),
dec으로는 400에 해당하는데 100x4에 해당하는 값이다.
배열의 범위를 벗어난 인덱스를 참조해도 그대로 사용하기 때문에
OOB도 실제로 가능하다는 것을 알 수 있다.
해당 실습에서 사용하는 예제는 다음과 같은데,
oob로 임의의 주소에 해당하는 값을 알려면 읽으려는 변수와 배열의 오프셋을
알아야한다. 배열과 변수가 같은 세그먼트에 할당되어 있으면 둘 사이의 오프셋은
항상 일정하기 때문에 디버깅을 통해서 알아낼 수 있다.
해당 코드에서는 길이가 3에 해당하는 docs 배열을 참조하는데,
인덱스의 값을 검사할 때 음수에 대한 여부는 검사하지 않는다.
해당 코드에서는 docs와 secret_code 모두 스택에 할당되기 때문에 docs에 대한
oob를 이용하여 secret_code 값을 읽어오도록 하자.
우선 secret 텍스트 파일을 먼저 만들어주고 해당 파일을
oob를 이용하여 읽어보는 방법을 진행한다.
fopen 함수로 해당 경로에 있는 secret.txt 파일을 읽어오는데, 인덱스의 값이 1~4가 아닌
0을 입력해주면서 0-1 즉 -1이라는 음수의 인덱스 값을 참조하게 되었다.
4를 초과하여 5이상을 입력해주면 조건문에서 Detect out-of-bounds가 출력되고 종료되는 것도
해당 과정을 통해 확인해볼 수 있다.
그리고 oob를 통해서 임의 주소에 값을 쓰는 것도 가능한데,
다음 예제 코드를 컴파일하여 진행해본다.
해당 코드는 인덱스에 대한 검증이 제대로 이루어지지 않아서
임의 주소에 값을 쓸 수 있는데, 해당 코드를 살펴보면 long, char, long으로 선언된
구조체 Student는 24바이트 크기를 가지고 해당 구조체 10개를 포함하는 배열 stu와
isAdmin이라는 int 형을 전역 변수로 선언되어 있다.
그 다음 프로그램은 인덱스를 입력받아서 인덱스에 해당하는 Student 구조체의
attending에 1을 대입하는 과정을 거친다.
마지막 조건문에서 isAdmin이 참인지에 대한 조건을 검사하게 되는데,
해당 입력 과정에서 oob가 일어날 수 있으므로 isAdmin 값을 조작할 수 있게 된다.
gdb를 통해서 stu와 isAdmin의 주소를 확인해보면 isAdmin이 stu보다
240바이트 높은 주소에 있다는 것을 확인해볼 수 있고, 배열을 구성하는
Student 구조체 크기가 24바이트이므로 10번째 인덱스를 참조하면 조작이 가능할 것이다.
코드에서 보면 [idx - 1]만큼을 참조하고 있기 때문에 10이 아닌 11을 입력해보자.
정상적으로 isAdmin이 조작되어 Access granted라는 문자열을
출력하는 것을 확인해볼 수 있다.
즉, stu가 0부터 24x10의 만큼의 크기를 가지고 있을 때 isAdmin은 240만큼 더 높은 주소에
위치하고 있기 때문에 idx-1이니까 0~9번째의 인덱스보다 높은곳 10번째 인덱스를
참조하면 oob 취약점을 통해서 값이 조작되는 것이다.
🔰 퀴즈
1) D : if (index >= 0x10) {exit(-1);}
✔️ out_of_bound
문제 정보는 다음과 같다.
이 문제는 서버에서 작동하고 있는 서비스(out_of_bound)의 바이너리와 소스 코드가 주어집니다.
프로그램의 취약점을 찾고 익스플로잇해 셸을 획득하세요.
“flag” 파일을 읽어 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{…} 입니다.
코드는 다음과 같은 모습을 띄고 있다.
name에 16만큼의 배열을 전역 변수로 받아주고 있고,
command에는 명령어들이 작성되어 있는 것을 볼 수 있다.
그리고 메인 함수에는 관리자 이름을 출력하고, 왓두유원트? 라고 하면서
무엇을 원하는가에 대해 idx 인덱스 값을 받아주고 있다.
그리고 command[idx] 값에 해당하는 올바른 주소를 system에 할당하면
system 함수를 통해 실행되는 로직인 듯하다.
또한, 인덱스에 대한 검증을 하는 로직이 없기 때문에 oob 취약점이 발생하게 된다.
그러므로 name에 원하는 값을 입력하고 command[idx]를 name으로 바꾸어서
system(name)과 같은 형식으로 셸을 실행시킬 수 있을 것이다.
우선 보호기법은 partial RELRO이고 여러가지 것들이 적용되어 있다.
command 함수와 name 함수의 주소는 76만큼의 차이를 가지고 있다.
scanf 호출 이후의 로직을 살펴보면 mov eax에 *4만큼을 하는 것을 볼 수 있는데
command 주소가 뒤에 오는 것을 확인할 수 있다.
즉, n * 4 = 76이라는 말이 되고 n = 19라는 것을 알 수 있다. (idx)
read 호출 다음 부분인 main+69 주소로 가서 cat flag를 입력하면
스택에 잘 들어오는 것을 확인할 수 있고
idx 값인 19를 입력하면 0x13이 스택에 들어와 정상적으로 출력되는 것을 확인할 수 있다.
근데 cat flag를 입력했음에도 불구하고 cat 만 들어와있는데 이는 system 함수가
인자로 들어오는 변수 주소의 4바이트와 변수 값이 메모리로 들어가게 돼서
name+4 주소와 cat flag를 같이 입력 값으로 받아주어야 한다는 것을 검색을 통해 알게 되었다.
name의 주소는 위에서 구한 것처럼 0x804a0ac+4를 페이로드에 32비트 체제이기때문에
p32비트 패킹을 통해서 보내주고, 문자열로 flag를 확인하는 cat flag를 날리도록 구성한다.
처음에 그냥 페이로드를 날렸을 때 이상없이 실행되고 종료되길래 왜그러지
하고 생각해보니까 recv로 100을 받아서 데이터를 읽어오는데 출력 구문이 없어서
출력하지않고 바로 종료되었다. 그래서 print문을 추가했다.
정상적으로 플래그가 출력되는 것을 볼 수 있다.
여기서 b''가 출력되는데 찾아보니까 decode('utf-8')을 입력해서 출력하면 된다고 한다.
(참고: https://codechacha.com/ko/python-convert-bytes-to-string/)
화이팅 💪
'CTF > 시스템해킹' 카테고리의 다른 글
[Dreamhack] Use After Free (0) | 2022.10.29 |
---|---|
[Dreamhack] Format String Bug (0) | 2022.10.29 |
[Dreamhack] Bypass PIE & RELRO (0) | 2022.10.27 |
[Dreamhack] Bypass NX & ASLR (0) | 2022.10.25 |
[Dreamhack] Stack Canary (0) | 2022.10.23 |