B站pwn教程笔记-9

前言:可以去一些开源镜像站下载libc老的乌班图镜像,因为堆题的libc可能比较老,没有新的一些保护措施和机制。

格式化字符串漏洞

归根结底,可以读写任意地址内存。

泄露栈数据/任意地址数据

主要问题就是printf不知道自己有没有参数,只会根据格式化字符个数在栈上往上读取,就算不是自己的参数也读取。

如果%s过多,没有足够参数情况下,程序就会崩溃

%x用来输出对应数据的16进制(不加0x的16进制)%p是把栈数据当作指针来输出,和%x相比前面有0x,所以一般也常用%p泄露。如下图,2者差不多。%p太多了,有可能给之前某个函数的canary也泄露了。

如果printf("%p","hello"),实际上入栈的是hello对应的地址。C语言传递字符串就是传递它的地址的,函数参数传递就是如此,不可能直接把数据压栈的。

这也是C语言用指针不安全的一点,比如字符串截断漏洞。将对应地址的字符串的\x00删除或篡改,puts函数这样的输出函数在输出对应地址内容的时候,就会不遇到\x00不停止,一直输出后面的内容。相应的,配合strlen和read(xxx,str,str_len)还可以篡改字符串数据。

因此不难理解,%s则是会读取这段地址,尝试解析为字符串。其实这样也有一个好处,比如栈上存放的got表自己的地址,%s读取后,就是got表内存放的数据的地址了,达到泄露got表的目的。

而%n$d,表示这是第n个参数,并且按照有符号整数打印出来。

那么,格式化字符串自己存放在什么地方呢?如果程序没有刻意搞bss之类存放,那么应该就是在栈上存放,在参数那个栈的地方存放其地址,见下图

因此,我们可以在栈上利用格式化字符串这一特性,在栈上写入敏感数据的地址,而后利用格式化字符串读取,见下图

由于给里面的地址超长了(x86一个栈是4字节),因此%6$s会在下一个栈的区域存放。

这里需要注意,0x00402004会被打印出,具体解释请看下图。

篡改栈数据/任意地址数据

需要用%n,他也是和%s一样解析,但不同的是它是向地址写入,写入前面格式化字符打印成功的个数。但是如果我们要用这个写入很大的数据,必须在前方打印足够长的字节吗?

可以用格式化字符串控制,比如%20d,意思是宽度是20的一个d,这样就算打印出来了一个变量,他也会认为前方已经打印出20个字符了。同时%n也支持%4$n这样的语法。

由于%n不是直接修改栈上的值,想要修改栈的值还得是通过%p泄露EBP之类的值,再利用%n来操作。

这里要注意,%n默认是4字节整数写入。%hn是2字节,%hhn是一个字节(h代表half)

例题讲解

fmtstr1

有canary,估计无法栈溢出。看源码估计也是格式化字符串。因为第10行的printf函数的格式化参数我们是完全可控的。

老师的思路是先调试一下看看。我们得看看x的地址(因为是%n)对于printf来说是第几个参数才行,ida看不出来这,必须得动调。

易错点:printf的第一个参数是格式化字符串,格式化字符串的参数是%n,%s之类。也就是printf的第二个参数才是格式化字符串的第一个参数。以此类推,主要是格式化字符串也是一个参数,他的地址同样入栈,千万不要搞混淆。

同时,格式化字符串中的%n$的n指的是相对于格式化字符串的参数的意思,也就是参数是第N个printf的参数减去1。

更加具体的介绍,下面是程序执行流停在了call printf。这里可以看出esp指向的AAAA就是压栈的格式化字符串地址,这个参数实际上是储存在了eax那一行的。从上往下数刚好是第12个(针对于printf而言)而格式化字符串里面肯定是11$.

exp可以证明这一点:

goodluck

这是个64位程序。

程序竟然让我们自己输入flag,他进行比对。这肯定是不行的。format完全是我们控制,所以这也是格式化字符串漏洞的题目。我估计要想办法泄露fp的地址把。实际上v10保存着flag字符串,应该是泄露v10(刚好他也在栈上)

ms的用法:

在 C 语言里,scanf函数的%ms格式说明符是一个拓展功能,主要用于动态分配内存来存储输入的字符串。下面为你详细介绍它的功能和用法:

主要功能

  • 动态内存分配%ms会依据输入字符串的实际长度,自动分配足够的内存空间,这样就无需提前指定缓冲区的大小。
  • 自动字符串终止 :和%s一样,%ms会在读取到空白字符(例如空格、制表符、换行符)时停止,并会自动在字符串末尾添加\0作为结束符。
  • 返回分配的指针成功读取输入后,%ms会把分配的内存地址存储到对应的指针变量中

关键要点

  • 需要指针参数 :调用scanf时,传给%ms的参数必须是一个char**类型的指针,也就是一个指向字符指针的指针。
  • 内存管理 :使用完分配的内存后,要记得用free()函数释放内存,防止出现内存泄漏。
  • 输入截断%ms会一直读取,直到遇到空白字符或者文件结束符(EOF),这一点和%s是相同的。

这就有意思了,因为泄露任意地址数据肯定要保证格式化字符串在栈上呀 。但是无关紧要,我们只需要泄露栈上的v10。

注意64位printf的参数。首先第一个参数格式化字符串在rdi寄存器,现在我们知道前6个参数肯定在寄存器,第六个才入栈,明显printf也会按照这样的顺序来读取。

可以直接正常执行程序输入%7$p,看看程序泄露出来的第七个参数的地址。当然这样一眼不一定能看出来,可以在输入89多对比看看。因为一般具有栈地址随机化,但是栈上某些部分的值却是特殊的,可以多打印几个明显看出来。

一句话就是由于传参方式不一样,不能直接通过在栈上数参数来找偏移了,必须试出来按正常执行的第七个参数究竟在什么地方。

其实这道题经过我的观察也可以直接从x64入栈方式来找偏移,第七个参数按理来说就是靠近addr的那个栈保存的。根据此数出来的偏移也刚好符合题意,实战中二者结合使用吧。

堆引入

想要解决第三题,必须有堆的基础,接下来就认识一下堆。实际的漏洞栈多于堆,但是CTF中堆的题目数量是大于栈的。

shared这块就是mmap段。

一般linux就是glibc。GNU协会搞linux的软件环境,所以这linux很多地方都有g的身影。

堆管理器是用户态代码,所以必须通过系统调用和内存这方面硬件建立联系。

在Linux进程的虚拟内存布局中,数据段包含已初始化和未初始化的全局及静态变量,其大小在程序加载时就已确定,运行过程中无法改变。而堆位于数据段之后,是动态内存分配的区域,`brk`系统调用并非直接扩展数据段,而是通过调整堆的结束地址(`break`指针)来实现堆空间的动态扩展与收缩。当程序调用`malloc`请求内存,若现有堆空间不足,`malloc`就可能通过`brk`系统调用增加`break`指针的值,使堆向高地址方向延伸;当释放大量内存时,`break`指针可能减小,但内核通常不会立即将内存归还系统,而是保留以供后续分配 。--豆包

mmap有意思了,map就是映射,也就是在物理内存开辟一块空间,映射到相应虚拟内存。

主线程两种方法都可以(申请内存空间相对大就用mmap,否则是brk),子线程只能用mmap。释放内存则是free

具体了解堆,且听下回分解

相关推荐
大筒木老辈子1 小时前
Linux笔记---协议定制与序列化/反序列化
网络·笔记
草莓熊Lotso1 小时前
【C++】递归与迭代:两种编程范式的对比与实践
c语言·开发语言·c++·经验分享·笔记·其他
我爱挣钱我也要早睡!4 小时前
Java 复习笔记
java·开发语言·笔记
汇能感知9 小时前
摄像头模块在运动相机中的特殊应用
经验分享·笔记·科技
阿巴Jun9 小时前
【数学】线性代数知识点总结
笔记·线性代数·矩阵
茯苓gao9 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾9 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
DKPT10 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习
ST.J10 小时前
前端笔记2025
前端·javascript·css·vue.js·笔记
Suckerbin11 小时前
LAMPSecurity: CTF5靶场渗透
笔记·安全·web安全·网络安全