Pwnable - setvbuf
Intro
얼마 전 문제를 풀다가 처음 보는 개념이 나와서 정리해보려고 한다. scanf("%d", &n)
으로 int형 값을 입력받는데 이상하게 오버플로우(?)가 발생하지도 않는데 데이터가 heap에 저장되는 것이다. write up을 살펴봐도 익스 코드만 있고 heap에 paylaod를 pivot한다는 식만 주석으로 간단하게 적혀있어서 왜 heap에 payload가 저장되는지 이해하기가 힘들었다. 이에 대해 간단하게 알아보자.
결론을 말하면 setvbuf
함수 때문이었다.
setvbuf
1 | #include <stdio.h> |
스트림 버퍼 방식을 변경한다. 버퍼링
은 시스템 자원을 효율적으로 사용하기 위해 데이터를 버퍼라는 공간에 담아두고 사용하는 것을 의미한다. 버퍼링을 하지 않으면 버퍼라는 공간을 쓰지 않는 걸로 해석된다.
인자를 살펴보자.
File* stream
- 작업을 수행할 열린 스트림의
FILE
객체를 가리키는 포인터 - 포너블 문제를 풀어본 경험이 있다면
main
초반에stdin
,stdout
,stderr
등을 본 경험이 있을 것이다.
- 작업을 수행할 열린 스트림의
char* buffer
- 유저가 할당한 버퍼인데 포너블 문제를 풀어본 경험이 있다면 보통
NULL
로 설정되어 있다. NULL
이라면 함수는 자동으로 지정한 크기의 버퍼를 할당하게 된다.
- 유저가 할당한 버퍼인데 포너블 문제를 풀어본 경험이 있다면 보통
int mode
- 파일 버퍼링의 형식을 지정하는데 3가지가 있다.
_IOFBF
: 버퍼가 다 차지 않는 이상 쓰기 작업을 하지 않는다. 즉 버퍼에size
바이트 이상 쓰이지 않는 이상stream
에 쓰이지 않는다._IOLBF
: 출력할 때 버퍼가 다 채워지거나 개행 문자가 입력 되었으면 데이터가 버퍼에서 출력되고, 입력 시에는 개행문자까지 입력받는다._IOLBF
: 포너블 문제를 풀어본 경험이 있다면 보통은 이 경우다. 버퍼를 사용하지 않겠다는 뜻이다.stdout
와stderr
는 보통 이 모드로 설정한다.
size_t size
- 지정할 버퍼의 크기의 단위
NULL
이라면 컴퓨터가 알아서 최소의 크기를 지정한다.
example
1 |
|
예시 코드이다. 입력으로는 아래를 보냈다.
1 | 1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
내 예상으로는 1만 n
에 들어가고 (setvbuf
가 있던 말던 ㅎ;) 나머지는 입력이 다 씹힐 것이라고 생각했다.
setvbuf(stdin, 0, 2, 0)
가 없는 경우
일단 stack을 살펴보면 지역변수 n
자리에 1만 정상적으로 들어간 모습을 확인할 수 있다. 하지만 heap 상황은 어떨까?
scanf
로직에 의해 malloc을 하고 버퍼링을 한다. heap에다가 1을 포함한 값들을 씹지 않고 저장한 모습을 볼 수 있다. 후에 이 값은 나중에 scanf
로 값을 다시 입력받을 때 사용자에게 입력받지 않고 이 공간에서 꺼내서 씀으로써 속도 등의 측면에서 성능을 항상시킨다고 한다.
포너블 문제에서 해당 구문이 있는 것은 이 때문인데, 이것이 없으면 입력과 출력 간의 순서가 모호해지고 leak한 값을 출력을 해야하는데 출력이 안될 수도 있다. 그래서 setvbuf
함수로 버퍼를 사용하지 않는, 즉 버퍼링을 하지 않아야 한다.
setvbuf(stdin, 0, 2, 0)
가 있는 경우
디버깅으로 확인했는데 단호하게 1 뒤에 값은 다 씹는다.
Conclusion
포너블 문제에서 일반적으로 setvbuf(stdin, 0, 2, 0)
와 같은 구문이 있어야한다. 하지만 없는 경우도 있다. 저 구문이 없고 scanf
와 같은 함수(malloc을 시도하는 함수)로 사용자에 입력을 받는다면? heap에 payload를 저장해서 ROP를 시도하는 방향으로 진행해봐도 좋을 듯하다.