data overwrite by setbuf
unsigned __int64 __fastcall main(int a1, char **a2, char **a3)
{
char v3; // bl
unsigned __int64 v5; // [rsp+8h] [rbp-10h]
v5 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
v3 = makw();
*(_BYTE *)makw() = v3;
return __readfsqword(0x28u) ^ v5;
}다음과 같이 문제가 있었을때 보통 setvbuf를 쓰지만 setbuf 도 같은 기능을 한다. 물론 설정이 좀더 약하지만,
자세한 내용은
그런데 이때 저 함수를 사용해서 버퍼를 바꾼다면, file 관련 함수에서 _IO_file_underflow 가 발생할때 내부 버퍼에 많은 양에 데이터를 한번에 읽어 드린다. 이때 버퍼에 많은 양에 데이터를 입력할 수 있다. file 관련 함수가 getchar() 같은 1바이트 입력함수여도 말이다.
따라서 만약 ROP등으로 setbuf 함수 계열의 인자를 설정 할 수 있다면, buf를 스택 또는 Data 영역으로 설정 할 수 있다면, 큰 바이트의 overwrite가 가능하게 된다.
대충 실제 문제의 writeup을 아래에 그대로 적겠다.
Idea
Dump of assembler code for function main:
0x00000000004005d3 <+0>: push rbx
0x00000000004005d4 <+1>: xor esi,esi
0x00000000004005d6 <+3>: sub rsp,0x10
0x00000000004005da <+7>: mov rdi,QWORD PTR [rip+0x20061f] # <stdin@@GLIBC_2.2.5>
0x00000000004005e1 <+14>: mov rax,QWORD PTR fs:0x28
0x00000000004005ea <+23>: mov QWORD PTR [rsp+0x8],rax
0x00000000004005ef <+28>: xor eax,eax
0x00000000004005f1 <+30>: call 0x400560 <setbuf@plt>만약에 rsi를 원하는 주소로 설정하고, 흐름을 0x4005da로 변경했다고 하자.
그러면, setbuf(stdin,rsi) 를 호출하게 된다.
그러면, rsi가 가리키는 곳을 0x2000만큼 덮을 수 있게 된다.
그러면, rsi를 어떻게 setting하냐?? sleep을 이용하면 된다.
unsigned int
__sleep (unsigned int seconds)
{
int save_errno = errno;
const unsigned int max
= (unsigned int) (((unsigned long int) (~((time_t) 0))) >> 1);
struct timespec ts = { 0, 0 };
do
{
...
if (__nanosleep (&ts, &ts) < 0) // here!
/* We were interrupted.
Return the number of (whole) seconds we have not yet slept. */
return seconds + ts.tv_sec;
}
while (seconds > 0);
__set_errno (save_errno);
return 0;
}__nanosleep (&ts, &ts) 이 부분에서, rdi, rsi에 스택 영역의 주소가 들어간다고 한다.
그래서, puts_got를 변조해서 setbuf(stdin,&ts)가 호출되도록 하고, getchar()가 호출되게 되면 스택에 0x2000만큼 적을 수 있게 된다고 한다.
getchar가 한글자만 받는 거 아닌가라는 생각을 할수는 있지만, buffer에 저장된다!!!
게다가 놀랍게도 적기시작하는 주소가 getchar 내부에서 호출하는 __IO_file_underflow 함수의 return 주소라고 한다. ㄷㄷ
즉, 이제 부터는 그냥 rop다.