到了Use After Free啦,总体来说这种手法并不复杂,特征也很明显,就是在静态分析阶段观察释放chunk之后指针是否置空。本以为参加hw会往后拖更,没想到这么快就写完了。如果前面一直跟着学的话这部分也应该难不到你,我会在接下来的Fastbin Attack等你,加油~

编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks♪(・ω・)ノ

往期回顾:
好好说话之unlink
好好说话之Chunk Extend/Overlapping
好好说话之off-by-one

Use After Free

原理

我们可以直接从字面上翻译它的意思:使用被释放的内存块。其实当一个内存块被释放之后重新使用有如下几种情况:

  • 内存块被释放后,其对应的指针被设置为NULL,再次使用时程序会崩溃
  • 内存块被释放后,其对应的指针没有被设置为NULL,在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序有可能可以正常运转
  • 内存块被释放后,其对应的指针没有被设置为NULL,但是在下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能出现问题

我们再来看一下正常释放之后是怎么将指针设置为NULL的,这里引用一下前面好好说话之Chunk Extend/Overlapping中题目的源码举例:

我们一般所指的Use After Free漏洞主要是后两种,一般将释放后没有被设置为NULL的内存指针为dangling pointer

举例讲解

//gcc -g test.c -o test1 #include <stdio.h>2 #include <stdlib.h>3 typedef struct name {4   char *myname;5   void (*func)(char *str);6 } NAME;7 void myprint(char *str) { printf("%s\n", str); }8 void printmyname() { printf("call print my name\n"); }9 int main() {10   NAME *a;11   a = (NAME *)malloc(sizeof(struct name));12   a->func = myprint;13   a->myname = "I can also use it";14   a->func("this is my function");15   free(a);16   a->func("I can also use it");17   a->func = printmyname;18   a->func("this is my function");19   a = NULL;20   printf("this pogram will crash...\n");21   a->func("can not be printed...");22 }

简单的解释一下这段代码,首先创建了一个结构体name:

  3 typedef struct name {4   char *myname;5   void (*func)(char *str);6 } NAME;

这个结构体有两个成员变量,第一个是char类型的字符串指针 ,第二个是创建的函数指针

  7 void myprint(char *str) { printf("%s\n", str); }8 void printmyname() { printf("call print my name\n"); }

接下来 定义了两个函数:myprint和printmyname

  • myprint()函数 :需要传入一个char类型的字符串参数,函数执行会打印出传入的字符串
  • printmyname()函数:函数执行会打印出”call print my name“字符串
 10   NAME *a;11   a = (NAME *)malloc(sizeof(struct name));12   a->func = myprint;13   a->myname = "I can also use it";14   a->func("this is my function");

接下来看一下main函数部分,首先创建了一个结构体指针a并分配空间,使得a结构体的func成员变量等于 myprint()函数,并且传入了字符串参数”this is my function“,使得myname成员变量赋值为"I can also use it"。因为在编译的时候加了-g参数,我们使用gdb调试一个这个程序,在第15行下个断点看一下运行结果:


看一看到打印出了传入的字符串参数

 15   free(a);16   a->func("I can also use it");

接下来释放了a结构体,但是注意结构体指针在释放后没有置空,在释放之后再一次调用func成员变量中的myprint()函数,并传入字符串参数”I can also use it“,我们继续在17行下断点并运行:

可以看到myprint()函数依然可以被调用,并且成功执行打印出字符串。我们继续往下看:

 17   a->func = printmyname;18   a->func("this is my function");

接下来不仅仅是对函数的调用了,而是直接将func成员变量中的函数指针更改成了printmyname()函数,并且调用func成员变量。虽然printmyname()函数不需要参数,但为了能够让程序认为这里依然是myprint()函数,并且认为我们的操作是合法的,所以传入了参数"this is my function"。我们在19行下断点,并运行:

可以看到,即使我们改变了成员变量中的函数指针,依然可以顺利执行printmyname()函数,并打印出printmyname()函数中原有打印“call print my name”的功能

 19   a = NULL;20   printf("this pogram will crash...\n");21   a->func("can not be printed...");

继续往下看,我们将a结构体置空,打印出一个提示字符串,这样一来我们再一次调用func成员变量。我们在第22行下断点并运行:

可以看到只出现了提示标语,而没有出现调用func成员变量执行printmyname()函数的功能。这样一个例子可以很直观的体现出结构体指针在释放之后置空的重要性,以及没有置空情况下我们可以做些什么

例题 : hitcon-training-hacknote

这里我们以 HITCON-training 中的 lab 10 hacknote 为例

检查保护

可以看到这是一个32位的程序,开启了canary和NX保护。这里注意了,进入堆领域以来我们做的都是64位程序,所以在静态分析阶段会习惯性的打开ida x64,但是依然还是会有32位程序,不要搞错了。后面在动态分析阶段我会提醒你32位程序在分析时和64位有什么区别

静态分析

还是老规矩,锻炼自己静态分析的能力,尽可能的在这个阶段搜集好可能会用到的信息,打开ida x86我们看一下:

主函数

通过对main函数的解析,我们可以看到pwn堆这部分的题套路好像都是差不多,都是先做一个选择,然后执行各种功能。简单的解读一下这个代码,首先是执行menu()函数,可以在右面看到menu()函数其实就是个界面提示,对应着左侧的代码,当输入1时调用add_note()函数执行添加功能,当输入2时调用del_note()函数执行删除功能,当输入3时调用print_note()函数执行打印功能

添加功能:add_note()

简单的讲解一下add_note()函数的功能,首先第一个判断说明的是最多创建5个note,接下来循环5次,程序会判断notelist + i的位置是否已经有malloc指针,这里需要注意的是i是从0开始的,也就是说note的id是从0开始的。这个notelist其实是bss段的一个全局变量(红框),里面存放的都是malloc指针,也就是结构体指针,其地址为0x0804A070。在判断之后发现这个位置并没有结构体指针,那么就会创建一个8字节的chunk,后简称struct_chunk。需要注意的是因为这个程序时32位的,所以8个字节是两个地址位宽,也就是说这两个地址位宽中存放的其实是两个成员变量

在判断之后会在notelist + i位置放置print_note_content()函数指针,可以在右侧看到print_note_content()函数需要传入一个int型的参数,并打印出整型 +4的地址处的内容。接下来会打印字符串提示创建note的大小,外部输入的数值会存放到size变量中。v0变量以整型的形式装载结构体指针,并且在整型 + 4的地址处开辟size大小的chunk,后简称content_chunk,接下来是判断是否创建成功。如果创建成功则提示输入note的内容,程序会调用read函数将输入的内容放在*((void **)*(&notelist + i) + 1处,这里的+1其实是加一个地址位宽处的地址,也就是content_chunk中。并且read函数的三参是size,所以这里无法进行溢出

通过前面的分析,我们可以很清晰的看出整个结构体的结构,并且由于struct_chunk和content_chunk都是根据notelist的偏移来的,所以两个chunk一定是紧紧贴在一起的。我们把静态分析和程序结合起来,用代码的形式自动化完成这部分功能的调用:

删除功能:del_note()

简单的说一下这个删除功能,首先提示需要删除的图书id,接下来会将输入的数字赋给v1变量。if判断输入的数值是否合法,如果合法下一个if判断notelist + v1的位置是否有结构体,如果有的话首先释放的是content_chunk,然后释放的是struct_chunk。这里就出现了释放之后chunk指针不置空的问题,很有可能触发Use After Free

我们把静态分析和程序结合起来,用代码的形式自动化完成这部分功能的调用:

打印功能:print_note()

简单的说一下打印功能,还是首先提示输入需要打印的note的id,接下来做一个合法性判断,第二个if判断notelist + v1位置是否有结构体被创建,如果有则打印content_chunk中的内容。这里有点绕的地方就是怎么打印的:

(*(void (__cdecl **)(_DWORD))*(&notelist + v1))(*(&notelist + v1))

我们拆开来看,首先第一个&notelist + v1代表的是print_note_content()函数,因为在创建note功能的时候print_note_content()函数指针就是放在结构体的第一个成员变量中的,后面的(*(&notelist + v1))其实是print_note_content()函数的参数,我们再将print_note_content()函数拿出来:

(*(&notelist + v1))本身其实是个地址,但是存入print_note_content()函数后被强制转换成int型,+ 4之后其实是加了4个字节,也就是正好到content_chunk的位置,就相当于puts(content_chunk)

我们把静态分析和程序结合起来,用代码的形式自动化完成这部分功能的调用:

flag

在ida左侧的函数栏中还可以发现一个叫magic()的函数,这个函数执行的是system("cat flag"),压参的地址为0x0804898F,也就是说我们需要使用这个system函数来拿flag

动态调试

尝试

通过静态分析阶段的探索,我们知道了如下两个重要的点:

  • 出现问题的点:在释放后没有将chunk指针置空
  • chunk指针起始位置为notelist全局变量的地址:0x0804A070
  • system(“cat flag”)地址为:0x0804898F

在得到这两个关键信息后就可以着手尝试动态调试了,尝试的目的主要是在gdb中查看一下chunk之间的排列结构,进而判断应该溢出还是会重复申请释放。那么就先尝试和创建两个24个字节的note。gdb运行程序,并在主程序中选择1选项创建两个24个字节的note,由于malloc指针存放在0x0804A070处,所以使用命令x/20wx 0x0804A070查看一下:

可以看到这两个就是note1和note2的malloc指针:0x0804b0080x804b038。但是这里需要注意的是malloc指针其实指向的是chunk的内容部分,如果想要看完整的chunk结构体还要减去0x8(64位减0x10),因为需要把prev_size和size部分让出来。我们直接使用命令x/30wx 0x0804b000(0x0804b008 - 0x8)查看一下这几个chunk的分布情况:

可以看到,展现的样子和我们前面静态分析阶段得出的结论一样,struct_chunk和content_chunk是紧紧挨在一起的,图形化结构如下:

可能出现的问题:为什么申请的两个note要24个字节呢?

下个环节解答~

确定目标

虽然chunk在一起,但是无法进行溢出。没有修改功能,也不能自己构造结构体。那么只能去考虑释放与重新申请的过程中能够有所突破。在实施尝试之前我们需要确定一下要更改的目标:

没有溢出,无法构造,那么就只能将目光放在结构体中的print()函数指针上了,因为只有这里指向的位置才具有一定的执行功能,假设我们将print()函数指针替换成system(“cat flag”)指针就可以拿到flag了,那我们就随便选一个吧,将note0的print()函数指针替换掉

回答问题:为什么申请的两个note要24个字节呢?

其实note的content_chunk的大小无关紧要,随便申请多大的都可以(我没试过超出bin的),因为我们的目标在结构体struct_chunk中,所以只要content_chunk超过8个字节不影响接下来的操作就行

可能会出现的问题:为什么content_chunk一定要超过8个字节呢

下个环节解答

偷天换日

既然确定了目标,那么接下来考虑的就是怎么将print()函数指针替换成system(“cat flag”)。从我们以往做题的经验来看,所有更改执行流程的动作都是由数据来操作的,也就是说我们必须能够从外部输入,虽然没有修改功能,但是我们有创建功能啊。回想一下前面做过的题,举个栗子,假设在一个32位程序中,如果我们申请一个8字节的chunk,恰好bin中有一个空闲的16个字节(8 + 8)的chunk,那么就会直接从bin中摘这个16字节的chunk为我们所用。那么回到这题上,先将这两个note释放掉我们看一看,首先释放note1,再释放note0:

可以看到在释放note0和note1之后两个struct_chunk指针排进fastbin的0x10单向链表,两个content_chunk指针排进fastbin的0x20单向链表,他们之间的结构如下:

这个时候我们想想,结构体本身就是8个字节,如果在申请一个8字节的content_chunk的话那么程序是不是就会直接在fastbin的0x10单向链表中分配给我们两个0x10的chunk了。这里有一点需要注意申请的先后顺序,在前面在静态分析阶段,通过对添加功能的分析,我们了解到是现申请8个字节的struct_chunk,然后在申请size大小的content_chunk:

由于note0是后释放的,所以在fastbin中先被摘除,所以原note1的结构体struct_chunk1空间被重新启用作为note2的结构体struct_chunk2。接下来被摘除的是原note0的结构体struct_chunk0空间,被重新启用作为note2的内容content_chunk2:

这样一来在创建向content_chunk中写数据的时候直接写上system(“cat flag”)地址,那么sys_addr就会写在原有结构体0的print()函数指针的位置

拿flag

那么通过对note2的创建,将原有note0的print()函数指针修改成了sys_addr。这样一来由于在释放chunk之后指针没有置空,所以我们依然还可以调用note0中的打印功能,所以直接在主界面选择3打印选项,输入要打印的id为0的note,就可以触发system(“cat flag”)了!

EXP

from pwn import *hollk = process('./hacknote')def addnote(size, content):hollk.recvuntil(":")hollk.sendline("1")hollk.recvuntil(":")hollk.sendline(str(size))hollk.recvuntil(":")hollk.sendline(content)def delnote(idx):hollk.recvuntil(":")hollk.sendline("2")hollk.recvuntil(":")hollk.sendline(str(idx))def printnote(idx):hollk.recvuntil(":")hollk.sendline("3")hollk.recvuntil(":")hollk.sendline(str(idx))magic = 0x0804898F #在ida里面找addnote(32, "aaaa")
addnote(32, "ddaa")delnote(0)
delnote(1)addnote(8, p32(magic))
printnote(0)hollk.interactive()

执行结果如下:

好好说话之Use After Free相关推荐

  1. 如何看当前windows是utf8还是gbk_职场中的OKR如何“好好说话”

    在工作中,经常会遇到各种各样的问题需要沟通,不管是团队内部的,还是跨部分,或者是对上级汇报还是管理下属.我们发现,有些能力很优秀的人,他们讲的内容,别人很快能理解.但是有的人,说了半天,大家也不知道他 ...

  2. ⊱人永远需要两种能力:好好说话和情绪稳定

    昨晚已经躺下,收到朋友的一条微信. 是一张截图,题目是:武昌火车站附近爆发社会恶劣事件--面馆老板因与食客发生口角冲突被砍头断臂,事后扔进垃圾桶. 现场十分血腥,被围观群众拍下迅速在微博等网络媒体上传 ...

  3. 读书笔记之《好好说话》

    作者 主创成员:马东.马薇薇.黄执中.周玄毅.邱晨.胡渐彪.刘京京. 感想 这本书的主创者是<奇葩说>的大牛们,这也是将它纳入书单的原因.  不管在生活中还是在工作中"好好说话& ...

  4. 好好说话之Tcache Attack(1):tcache基础与tcache poisoning

    进入到了Tcache的部分,我还是觉得有必要多写一写基础的东西.以往的各种攻击手法都是假定没有tcache的,从练习二进制漏洞挖掘的角度来看其实我们一直模拟的都是很老的环境,那么这样一来其实和真正的生 ...

  5. 好好说话之Fastbin Attack(1):Fastbin Double Free

    好像拖更了好久...实在是抱歉....主要是fastbin attack包含了四个部分,后面的例题不知道都对应着哪个方法,所以做完了例题才回来写博客.fastbin attack应该也会分四篇文章分开 ...

  6. 沟通技巧-《好好说话》书中的精髓:掌握沟通、说服、谈判、演讲、辩论的五维话术,让你在任何场景下,都能做到处变不惊,学会说话这个技术活。

    <好好说话>书中的精髓:掌握沟通.说服.谈判.演讲.辩论的五维话术,让你在任何场景下,都能做到处变不惊,学会说话这个技术活. 相信在生活中,每个人都可能因为不会说话遇到一些困难: 工作辛苦 ...

  7. 好好说话之unlink

    堆溢出的第三部分unlink,这可能是有史以来我做的讲解图最多的一篇文章了累死 .可能做pwn的人都应该听过unlink,见面都要说声久仰久仰.学unlink的时候走了一些弯路,也是遇到了很多困扰的问 ...

  8. 好好说话之IO_FILE利用(1):利用_IO_2_1_stdout泄露libc

    前言 本来是在做tcache attack的例题的,但是wiki上的challenge2考察的重点不仅仅是在tcache.题目程序中没有输出的功能,所以无法像往常一样去泄露libc,这个时候就需要进行 ...

  9. 也谈说话这件事--《好好说话》读后感

    沟通技能已经成为一种重要的生存技能,随着人工智能的兴起,很多专业领域的工作将会慢慢被计算机取代,而与人的沟通和交流相对于专业知识,则相对很难被取代,因为交流和沟通中需要体现人的情绪和感情,计算机在这块 ...

最新文章

  1. mysql是面向对象的语言吗_php一种面向对象的语言,那么什么是面向对象呢?
  2. 华为合作oppovivo小米鸿蒙,华为鸿蒙成功的关键:要让小米、OPPO、VIVO都用上鸿蒙...
  3. Android作业(Activitiy)
  4. 16进制/10进制数转化为浮点型案例
  5. [Swift]LeetCode811. 子域名访问计数 | Subdomain Visit Count
  6. 孤荷凌寒自学python第五十四天使用python来删除Firebase数据库中的文档
  7. 详解MQ消息队列及四大主流MQ的优缺点
  8. [原创]java获取word里面的文本
  9. NAT(NAPT)地址转换过程
  10. 深入浅出 eBPF: (Linux/Kernel/XDP/BCC/BPFTrace/Cillium)
  11. linux 比较内容,Linux命令比较文件内容
  12. 【数据、软件共享】年鉴,夜间灯光,土地数据(永久更新)
  13. eclipse主题彻底美化——你还在用白底黑字的编辑器吗?
  14. vb6 连接 mqtt 服务器
  15. BP算法的原理解释和推导
  16. rust相框加载图片代码_用代码为相片加相框的方法
  17. 【目标检测】《DINO: DETR with Improved DeNoising Anchor Boxes for End-to-End Object Detection》论文阅读笔记
  18. 转:这些道理不懂,你注定就是穷打工的命
  19. Android API与系统版本的关系
  20. 【全开源】六合一口红机完整源码

热门文章

  1. new Date在safair浏览器中不兼容
  2. 一个高中生的编程自学经历
  3. UWB定位算法比较TDOA和TWR究竟哪个好
  4. XCODE性能测试方法
  5. python大数据工程师薪资待遇_2019年就业薪资,凭什么大数据工程师遥遥领先?...
  6. 升级Monterey的血泪史~~哭唧唧~~
  7. 如何分分钟成为Java嵌入式开发人员
  8. ZZULIOJ 1919 D
  9. .net core 使用阿波罗配置中心
  10. 岁月温柔-8 妈妈的抑郁症?