笔记
视频5
开头,复习前面所有视频的知识!!!
内存保护措施
ASLR、PIE、NX、Canay、RELRO
NX:栈不可执行
一、ASLR
echo 2 > /proc/sys/kernel/randomize_va_space
0:没有随机化,即关闭ASLR
1:保留的随机化,即共享库、栈、mmap()以及VSDO将被随机化
2:完全的随机化,在1的基础上,通过brk()分配的内存空间也将随机化
二、Canay和PIE
备注:
1、gcc编译Canay:
----gcc -o test test.c // 默认情况下,不开启Canary保护
----gcc -fno-stack-protector -o test test.c //禁用栈保护
----gcc -fstack-protector -o test test.c //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码
----gcc -fstack-protector-all -o test test.c //启用堆栈保护,为所有函数插入保护代码
2、gcc编译关闭PIE:
gcc -no-pie
3、调试显示源码
gcc -g
可以方便gdb调试显示源码
4、Canay:
5、PIE:
PIE技术是一个针对代码段(.text)、数据段(.data)、未初始化全局变量段(.bss)等固定地址的一个防护技术,如果程序开启了PIE保护的话,在每次加载程序时都变换加载地址,从而不能通过ROPgadget等一些工具来帮助解题。
三、ret2libc1解题思路
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200208941-184074799.png)
通用结构:
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200209273-1765148764.png)
system函数代码会先调用push ebq,最后ret回exit
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200209627-384430036.png)
动态链接思路:
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200209972-790257709.png)
重点:在ida中,通过plt表拿到system地址
payload构造如下:
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200210313-1255390029.png)
strings ret2libc1 | grep /bin/sh
elf = ELF("./ret2lib1")
system_plt = elf.plt["system"]
bin_sh = next(elf.search(b"/bin/sh"))
视频6
shellcode网址
一、ret2libc2解题思路:
方法一:
1、对比ret2libc1缺少"/bin/sh"参数
2、发现bss区存在buf2
3、ROP到gets函数,输入/bin/sh到buf2
4、再继续ROP程序执行流到system@plt
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200210649-287159147.png)
方法二(pop_ret版)
1、ROPgadget --binary ret2libc2 --only "pop|ret"
2、
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200210986-1132756283.png)
二、ret2libc3解题思路:
注释:
1、fflush(stdout)(表示一次性打印标准输出)
2、read(0, &buf, 0xAu)(0表示stdin标准输入,u表示unsigned)
3、unsigned int表示0——2^32,有符号表示-2^31——2^31,最高位0和1表示正、负
分析思路:
1、找漏洞(运行ret2libc3)输入超长数据看是否崩溃触发栈溢出漏洞
2、搞清楚main函数数据分布
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200211328-1832932877.png)
3、发现read(0, &src, 0x100u)不会发生数据溢出
4、调用print_message()函数,观察栈情况
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200211711-1535160871.png)
5、把0x100拷贝到0x38中,栈溢出漏洞
攻击方法一:
1、发现没有后门函数,栈不可执行,Rop代码片段不足,plt没有system函数,没有/bin/sh
2、只能找system真实地址,找到libc里的真正的system地址
3、这题可以通过See_something()函数,获得system的真实地址
4、需要使用GOT表
1、用gdb把断点打到read处,b *地址
2、输入got,观察got表
3、把puts函数对应got表的起始地址(转换成10进制)传进去
4、next执行到see_something()函数,泄露出got表中puts函数的libc地址
5、利用libc-2.23.so找到puts函数和system函数的相对位置
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200212060-1635889819.png)
from pwn import *
elf = ELF("./ret2libc3")
libc = ELF("./libc-2.23.so")
io = remote("106.xx.xxx.xxx", 8080)
io.recv()
#得到puts表在服务器libc上的真实地址
io.send(str(elf.got['puts']))
io.recv()
#得到libc中puts函数与system函数的偏移
libc.symbols["system"] - libc.symbols["puts"]
6、打断点b Print_message
7、通过栈溢出strcpy函数,查看栈stack 24
8、构造ROP链
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200212528-925945262.png)
io.sendlineafter(b' :', str(elf.got["puts"]))
io.recvuntil(b' : ')
攻击方法二:
one_gadget详见代码
视频7、8
开头展示ret2libc3的答案
后续学习格式化字符串、堆利用
一、练习题WriteUp:
一、pwn0(ret2text)
1、64位,栈溢出漏洞
2、溢出到后门函数ret2text
3、需要填充8字节(64位)的垃圾数据
4、(不知为何,pwn0题目中systemcall地址需要+1才能打通本地)
5、(原因:x64下在调用system前要加ret)
二、pwn1(ret2shellcode)
1、(shellcraft.sh()#生成32位shellcode汇编码)#生成机械码
2、io.recvuntil(b"this:")
3、转换成int类型数据:buf_addr = int(io.recvuntil(b"?\n", drop=True), 16)
4、\x90表示loop:shellcode.ljust(136, b'\x90')
三、pwn2(ret2libc1)
1、要用到system的PLT表项
2、gdb动态调试的时候,使用set follow-fork-mode parent
指令:info b(查询断点信息)、d 1(删除1号断点)、b 函数名or*地址
3、payload=flat(cyclic(140), system_plt, b'BBBB', bin_sh)
四、pwn2_x64(ret2libc1)
1、与上题类似,但是需要注意x64下!!应该把参数放入寄存器中!
2、栈中32位传参方式!
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200212963-1024392613.png)
3、栈中64位传参方式!
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200213307-534762887.png)
4、思路:通过gadget片段pop_rdi_ret -> /bin/sh
5、payload = cyclic(128) + b'A'*8 + p64(pop_rdi_ret)
+ p64(bin_sh) + p64(ret)+p64(system_plt)
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200213649-668508613.png)
1、前面题目pwn0和pwn2_x64调用system前要加p64(ret)
2、ldd level3 -> file 路径,会显示level3用到的动态链接库地址
3、x64小技巧:通过ret2_csu_int来部署64位的寄存器的变量
五、pwn3(加强版ret2lib3)
1、难点:需要自己构造ROP链来泄露内存内容!!
2、栈溢出漏洞,没有system,没有/bin/sh
3、之前的ret2libc3有一个函数泄露了libc的函数地址
本题需要构造rop来泄露
4、如何写入sh:
string level3 | grep sh(要求sh后面要有截断符)
可以next(elf.search(b'sh')) or next(libc.search(b'sh'))
利用ROp链到gets函数,在bss区将/bin/sh写入缓冲区
方法:
1、通过Rop劫持程序执行流到Wirte@plt的地址,同时把write@got里
的内容打印出来,就可以知道write函数在内存空间中的真实地址了!
2、Base = write函数在内存空间中的真实地址 - 他在libc中的地址
3、Base加上system的偏移和/bin/sh的偏移,得到真实地址!
代码:
1、构造payload1,返回到vulnerable函数进行第二次溢出!
payload1 = flat(cyclic(140), write_plt, vulnerable_addr,
1, write_got, 4) #后三个是参数,显示write_got的4个字节
2、p32的逆运算u32
p32(1234) = b'\xd2\x04\x00\x00'
u32(b'\xd2\x04\x00\x00') = 1234
3、得到write@got的真实地址后,得到libc_base
4、通过libc_base偏移获得system和/bin/sh的真实地址
5、构造payload2,对函数造成栈溢出得到shell
6、libc_base = write_addr - libc.symbols["write"]
system_addr = libc_base + libc.symbols["system"]
bin_sh = libc_base + next(libc.search(b'/bin/sh'))
payload2 = flat(cyclic(140), system_addr, b'BBBB', bin_sh)
二、栈溢出小技巧(不走寻常路):
一、smashes(Canary found且gdb找不到main函数)
1、使用静态分析找main函数地址,手动设置断点(b *0x????)
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200214031-1323227743.png)
2、当数据溢出时,程序会先检查Canary是否和初始值相同
若不同则触发__stack_chk_file,输出stack smashes并退出
3、v4 = __readfsqword(0x28u)放置一个Canary
4、
二、栈迁移
1、情况一:溢出距离较短,够不到ret addr
情况二:虽然能覆盖到ret addr,但是溢出距离不够写ROP链
情况三:如pwn3需要用2次Rop进行攻击
2、解决方案:
leave: mov esp ebp; pop ebp(把esp移到ebp,弹出pre_ebp到现在的ebp中)
ret:将esp当前指向内容,弹入到eip寄存器中
3、步骤:
1、覆盖prev_ebp到vulner_ebp区域
2、覆盖ret_addr到Gadget如:leave指令、mov esp,ebp
3、后续学习-->格式化字符串漏洞和堆溢出漏洞
三、练习题WriteUp:
六、pwn3_X64(加强版ret2lib3)
1、注意x64下,函数的参数应该放在寄存器中
2、------------->
payload1 = cyclic(128+8) + p64(rdi_ret) + p64(1) + p64(rsi_r15_ret) + p64(write_got) + p64(0xdeadbeef) + p64(write_plt) + p64(vulnerable_addr)
3、------------->
payload2 = cyclic(128+8) + p64(rdi_ret) + p64(bin_sh) + p64(system_addr)
视频8末尾+视频9
一、格式化字符串漏洞:
%x是将无符号整数以十六进制形式输出,并且前面不加0x
%p把栈上的数据当作一个指针输出,输出16进制前面有0x
%s传入的仍然是地址,但是打印地址里对应存放的内容
%d打印有符号整数
%p是直接打印栈上的数据,%s是把栈上的数据作为地址解析,然后打印地址对应的数据
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200214419-1606670981.png)
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = 3;
printf("%d%d%d", a, b, c);
printf("%3$d", a, b, c);
return 0;
}
仔细观察下图结构:
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200215460-2125856780.png)
%s用于泄露0x00402004地址对应的值(一般打印read@got表)
%n和%s的区别
%n和%s类似,也是把栈上的内容作为地址去解析
但是是往被解析的地址中写入内容,而不是读取内容
写入的内容:格式化字符串前方已经打印成功的字符的个数
%n写4个字节0x00000004,%hn写2个字节,%hhn写一个字节
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
char c = 'a';
printf("%10c", c);
return 0;
}
输出10个字节,不足的部分用空格来补充( a)
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200215983-1918870055.png)
用%n把目的地址改为4
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200216457-1152627307.png)
用%n把目的地址改为8
总结
- 泄露栈内存
- 泄露任意地址内存
- 串改栈内存
- 串改任意地址内存
(附加)
x64环境下,且修改的地方是%10$p,值修改成100
1、泄露rbp,然后通过栈关系加减,得到buf的地址
2、0xffff88888888 + %100c%10$hhn
是错误的!!!2个误区!!!
· 首先,由于前面输入了地址,所以应该是0xffff88888888 + %92c%10$hhn
· 其次,由于64位有2位\x00\x00,所以其实后面格式化字符串参数被00截断了
· 所以,应该前后调换位置,并且对齐字符串,必须是8的倍数,且修改为%12$n
3、正确的输入是:%100c%12$hhnaaaa + 0xffff88888888
读写任意地址内存(要求:存在printf函数,且第一个参数可以被控制)
二、格式化字符串例题一(fmtstr1):
一、攻击思路
1、memset()开辟内存空间
2、使用%n把参数x由3篡改为4
3、gdb调试,切记要从printf的第一个参数开始
4、注意:printf的第4个参数,则是格式化字符串的第3个参数
5、如下图:printf的第12个参数,乃是格式化字符串的第11个参数
所以格式化字符串应该为:0x????????%11$n即可!!
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200216954-258702257.png)
二、攻击方法
1、利用%n将x的值改为4
2、ida中可以看到x的地址
3、payload = p32(x_addr) + b'%11$n'
三、格式化字符串例题二(64位_fmtstr2):
一、攻击思路
1、v11 = __readfsqword(0x28u)开启Canary防护
2、使用%s泄露flag,可以看到v10保存了真正的flag
3、需要注意:x64架构下,前6个参数在寄存器中,第7个参数才在栈中
4、解决方法:在gdb中调试时,可以输入%7$p%8$p%9$p找到参数地址
5、如下图:printf的第10个参数,乃是格式化字符串的第9个参数
所以格式化字符串应该为:%9$s即可!!
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200217378-541646870.png)
二、攻击方法
1、由于v10保存了flag,所以真正的flag就存放在栈中
2、利用gdb找到偏移位置
3、payload = "%9$s"
四、堆(Heap)的初认识+例题三(fmtstr_uaf):
一、堆
1、是虚拟内存空间中的一块连续的线性区域
2、提供动态内存分配,用户随取随用,且允许程序申请大小未知的内存
二、堆管理器
1、是动态链接库里实现的一段代码,用来管理堆的内存区域
2、处于用户与操作系统之间,作为动态内存管理的中间人
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200218497-1840814723.png)
三、堆管理器的三个关键词(arena、chunk、bin)
学习heap实验
CTFer祖师爷的秘制slide
国外师傅的堆总结笔记
1、arena
内存分配区,可以理解为堆管理器所持有的内存池
堆管理器与用户的内存交易发生于arena中
可以理解为堆管理器向操作系统批发来的有冗余的内存库存
2、chunk
用户申请内存的单位,也是堆管理器管理内存的基本单位
malloc()返回的指针指向一个chunk的数据区域
在arena上,是直接malloc得到的内存区域的单位,返回的指针实际上指向的是中间位置,并且开辟的空间要比用户指定的空间要大
3、bin
管理arena中空闲chunk的结构
以数组的形式存在,数组元素为相应大小的chunk链表的链表头
存在于arena的malloc_state中
· unsorted bin
· fast bins
· small bins
· large bins
· (tcache)
一、malloc chunk
#include <stdio.h>
#include <stdlib.h>
int mian() {
void* ptr = malloc(0x100);
free(ptr);
}
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200218943-879445018.png)
其中:
size的低 3位应该都是0,因为分配的内存只能是8的倍数
比如malloc(31)但是实际上会分配32的内存
所以size的第三位改成了标志位!!!分别是 |A|M|P|
二、free chunk
small bin 和 unsorted bin (4个控制字段)
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200219264-734832055.png)
----------------------------------------------------->>>>>>>>>>>>
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200219605-1690543578.png)
----------------------------------------------------->>>>>>>>>>>>
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200219939-665286608.png)
三、prev size的复用
写数据的时候,会从低地址往高 地址写!!!
所以ptr指针不能指在prev size所在位置
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200220291-160022939.png)
1、假设现在malloc(0x20)得到上面的chunk
2、现在free掉上面的chunk(数据不会删除,而是还到堆管理器中)
3、现在又malloc(0x28)(堆管理器会找有无可用大小的free chunk)
4、此时会把下一个chunk的prev size变成用户空间
· 因为prev size记录的是前一个free chunk的大小
· 由于前一个chunk是一个malloc chunk,所以prev size无用可以被复用
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200220695-1913887659.png)
四、物理链表 和 逻辑链表
4.1 物理链表
· 用来组织相邻chunk之间的关系
· 若是发现邻居也是free chunk就会自动合并成一个新的free chunk。
· 通过prev size和size字段把整个内存分配区串联起来。
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200221044-1848066181.png)
4.2 逻辑链表
· 用指针连起来,通过fd把同类的chunk连接起来,然后放进回收站里。
· 目的是为了方便malloc chunk直接找到对应的chunk
· malloc返回的地址符合后进先出(栈)
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200221377-1693730664.png)
五、各种bins
1、unsorted bin
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200221769-1417609902.png)
· bins数组从1开始
· 只有一项,保存的chunk大小是杂乱无章的
· 是一个双向链表fd和bk
· 其中,bk指向上一个chunk,fd指向下一个chunk
· malloc返回地址符合先进先出(队列)
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200222175-1261045177.png)
2、small bins
· 每一个bin对应串起来的chunk大小是固定的
· 记录的是具体的数
3、large bins
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200222543-1877777888.png)
· 每一个bin中记录的chunk的大小是不固定的
· 记录是一个范围
4、fast bins(具体图片在逻辑链表处)
· 管理的空间,64位程下×2即可
· P位表示:上一个chunk是否正在被占用
· 避免了fast chunk与其他chunk合并,避免检查、合并和重新扩展空间等
5、tcache(视频11/1:41:00)
· 可以看作是一个super fastbin,速度很快
· 不安全,没有安全防护措施(libc-2.28加入了检查)
视频11
一、堆结构+arena+chunk(全局讲解)
· 视频11(00:19:30处)!!!
· 大于fast bin的chunk在申请的时候,如32位下大于64Bytes的chunk
先从unsorted bin里找,还没找到的话,
就会触发unsorted bin遍历,把该合并的chunk合并,该归类的归类,
归类完后,再到对应的small bins和large bins中找,
若还没找到,就只能从top chunk里划分了。
二、其余chunk
2.1
一、Top chunk
· 就是堆管理器向操作系统要来的空间,除去使用的空间,剩余的就是
二、last remainder chunk
· 当用户malloc的时候,先在unsorted bin里面找,若找不到合适的chunk
则触发unsorted的遍历,对chunk进行合并和分类,
归类完后,再到对应的small bins和large bins里找,
若还没找到,就只能从Top chunk里划分了。
· 在上述寻找chunk的基础上,从unsorted bin头部向链表尾部遍历
若找到unsortedbin freechunk 大于 用户malloc的空间,
就会立马把free chunk分配给用户。
· 这时候,多出来的部分就会被重新填入控制字段,
重新列入unsorted bin中,这就是last remainder chunk
三、堆漏洞的讲解
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200222924-1783310212.png)
3.1 UAF(Use After Free)释放重用攻击
how2heap/first_fit.c 实验!!!
一、原理
· 可以看到,首先a=malloc(0x512)再b=malloc(0x256)
· b=malloc(0x256)是因为,free a的时候防止与top chunk合并
· 然后free a,随后马上c=malloc(0x500),
· 由于free a,a会进入unsorted bin双向链表中,
· c=malloc(0x500)的时候,会使用原先a的位置
· 这时候就导致了指针a和指针c指向同一个地址
· 这时候打印a指针,会显示this is c
二、gdb小技巧
1、开启了PIE防护,在gdb中的应对
b *$rebase(0x?????输入偏移地址)
2、在pwntools脚本中开启动态调试
在想调试的代码中,加入一行pause()
打开gdb,输入 attch + 进程号
3.2 double free漏洞
how2heap/glibc_2.23/fastbin_dup_into_stack实验!!
一、原理
· 首先malloc了3个很小的a、b、c,剩下的就是top chunk
· 然后free a,因为a足够小,所以进入fast bin
· 然后free b,因为大小相同,所以b也进入同一个fast bin中
· 继续free a,由于指针a还在,所以导致a又在fastbin中被列入一次
· 此时再malloc 3份内存,这时候第1个和第3个指针
· 在不知情的情况下,都指向了a的内存空间
· malloc b的作用是为了避开fastbin的double free检查
3.3 fastbin_dup_into_stack漏洞
how2heap/glibc_2.23/fastbin_dup_into_stack.c实验!!
一、原理
· 在double free的前提下,malloc一个d
· 此时d指针指向的是a的区域,并且a还挂在fastbin中
· 由于malloc chunk和fastbin chunk的差异
· 导致d可以修改链表中a的fd字段,修改为栈上的地址
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200223368-113111204.png)
二、如何攻击
· 由于fd指针指向的是,链表中下一个chunk的开头位置
· 所以若想要修改var,需要指向栈上var_addr - 2个字长的位置
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200223760-108928953.png)
3.4 unsorted_bin_attack漏洞
how2heap/glibc_2.23/unsorted_bin_attack.c实验!!
一、原理
· 可以把任意地址的值篡改成一个较大的值
· 前提是我们可以控制bk的值
二、步骤
· unsorted bin在libc的data地址,是一个0x7fff的大整数
1、通过篡改原本存在的chunk的bk域,来欺骗unsorted bin
2、让他以为还有下一块chunk,随后释放掉原来的最后一块chunk
3、造成伪造的chunk,为了和unsorted bin连接
4、把自己的fd和bk域写入了unsorted bin的地址
5、造成下一块chunk的数据域写入了unsorted bin的地址
6、若此时的数据域是栈上的遍历,那就可以把变量修改成一个很大的值
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200224139-1234874997.png)
三、可能的目的
· 修改变量成一个很大的值
· 修改fastbins所能记录的上限,造成所有的free chunk都满足fastbin的范围
· 从而更好地执行fastbin attack
四、house_of漏洞(是一整套攻击姿势,不单单是一个漏洞)
4.1 house_of_force
how2heap/glibc_2.23/house_of_force实验!!
一、原理
· 本质上利用到整数溢出漏洞(针对malloc本身)
· 前提是需要有堆溢出漏洞(针对Top chunk进行攻击)
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200224502-1056343021.png)
· 堆溢出漏洞时,chunk会向上溢出,覆盖top chunk的size
· 然后将size修改为很大的数值,此时top chunk就会被标记成一个无限大的空间
· 此时由于top chunk足够大,所以可以malloc任意大小的数字
· 若malloc(0xfffff),此时就会从top chunk向上划分给用户
· 若malloc(-0xffff),此时就会从top chunk向下划分给用户
· 所以可以一直向上malloc,直到malloc到栈上,停止,第二次malloc才控制
· 同时可以一直向下malloc,直到malloc到data段,停止,第二次malloc控制
视频12
一、例题一(fmtstr_uaf):
1.1 漏洞分析
1、格式化字符串漏洞,在echo2处
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200224819-1210549722.png)
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200225138-1678834216.png)
2、UAF(double free)漏洞,值得注意的是echo3中并没有漏洞
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200225473-1357141270.png)
虽然free(s)的时候,没有销毁指针,没有将指针设置为NULL!
但是,由于echo3函数执行完之后,栈帧就会被销毁,所以s会被销毁!
------------------------------------------------------>>>
真正的bug在cleanup()函数,里面调用了free(o)
其中o是用来保存greetings和byebye两个函数的
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200225792-303557636.png)
由于,调用了cleanup()函数,得到了一个o的freechunk
但是,此时选择不退出程序,并且指针又没有被销毁!
1.2 攻击思路
1、目的:把刚free掉的o的chunk重新拿回来,同时向其中写入数据
2、o的内存是0x28,而echo3()中s=malloc(0x20),所以此时s和o指向同一块区域
3、接下来通过s指针,写入想要的地址即可!
4、向栈上写入shellcode,控制程序执行流返回到那里
5、劫持程序控制流,用use_after_free漏洞!!
6、格式化字符串漏洞,用来泄露栈上的地址!!
1.3 攻击方法
1、func在bss段,o在堆内存中
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200226110-1035611595.png)
2、把shellcode写入v7(str_1)、v8(str_2)、v9(str_3)这24个字节中
3、进入echo2(),通过格式化字符串漏洞,泄露echo3栈帧中的pre_rbp的值
4、然后通过ida反汇编知道,main函数中v7与rbp相差0x20h
5、从而的到栈上v7的真实地址
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200226496-2138478648.png)
6、接下来按4退出,程序会free掉o,此时o会进入fast bin
7、选择n,继续执行,选择3,进入echo3()触发s=malloc(0x20)
8、由于0x28>0x20,所以此时s会直接分配到o的空间
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200226853-1410050944.png)
9、调用echo3的时候,会通过(*(o + 3))(o);方式调用greeting
10、所以修*(o + 3)为shellcode地址,以此来达到攻击目的!!
二、例题二 hacknote(堆模板题、更经典的uaf):
2.1 题目分析
小技巧:
1、首先看到一个manu,可以add、delete、print流水线,大概率是堆题!(模板化)
2、(题目去除了符号表,函数命名一头雾水),在ida中对函数名按"n"进行重命名
除此之外,"\"可以去掉冗余信息,"r"可以显示ASCII码对应的字符串,"/"注释
2.2 函数分析add()
1、v5存放的是canary,然后计数add不能超过5个
2、ptr数组用来存放,if找到一项是空的,则malloc(8u),返回一个指针
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200227205-621313336.png)
3、然后把自己的头地址,写入并执行了一个self_puts
4、用户输入一个size,然后创建一个v0保存的是chunk(返回)的地址
5、然后在v0[1]的位置,也就是chunk后面的4字节
又执行了一次malloc,并且把malloc的返回地址存放在了这个chunk里
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200227555-743077059.png)
6、最后往size里面写入size长度的数据,并且note_count+1
2.3 函数分析delete()
1、由下至上,先free size 再free上面的chunk
free(*(ptr[v1] + 1));
free(ptr[v1]);
!!!这也是漏洞所在,只释放了内存,但是没有销毁指针
2.4 攻击方法
目的:创造两个指针指向同一块chunk,来实现uaf攻击
方法:
1、malloc两次大小为16字节的note,每malloc一个note实际上是malloc了2次
一个用来保管控制信息,另一个用来保管note的内容
add_note(b"16",b"aaaaa")
add_note(b"16",b"aaaaa")
2、此时把他们2个都delete掉
3、他们会进入fastbins中,大小相同的会放在一起
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200227950-544545614.png)
4、此时故意malloc一个8字节的chunk,并且
5、此时代码会在索引2处会生成 2 个新的8字节chunk
6、系统会从fastbin1中获得chunk,并遵循后进先出原则(栈)
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200228363-697524024.png)
7、由于self_put函数会打印下一块存放的内容,所以构造payload的时候
8、第一个数据就写入self_puts函数地址,第二个数据写入puts@GOT表
9、接着print index 0,让程序调用self_puts打印puts@GOT
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200228752-983437626.png)
10、成功泄露出puts函数地址,通过libc得到system函数地址
11、接着delete 2,然后重新new写入,system_addr 和 "||sh"
原因:
** 仔细看print函数
if ( ptr[v1] )
(*ptr[v1])(ptr[v1]);
此时调用的应该是system(*0)
应该是system("0x888888||sh")
即system_addr("(system_addr)||sh")
如果第一个命令实行失败,||或命令则会执行第二条
![](https://img2023.cnblogs.com/blog/3318828/202312/3318828-20231218200229131-1336728106.png)