simple-heap-v1
07/18/2023
By: unvariant
Tags: pwn AmateursCTF-2023Problem Description:
Hints:
Reveal Hints
NoneProvided files
- chal
- Dockerfile
Intended
When performing allocations with malloc, any allocation greater than MMAP_THRESHOLD_MIN
, which is set to 128kb by default, malloc will use mmap instead of its internal heap.
https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L966
#ifndef DEFAULT_MMAP_THRESHOLD_MIN
#define DEFAULT_MMAP_THRESHOLD_MIN (128 * 1024)
#endif
I do not remember why, but the second mmap will always be exactly below the libc in memory. This means that if was can control the size field of a mmapped chunk, we can unmap part of the libc.
https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3042
static void
munmap_chunk (mchunkptr p)
{
size_t pagesize = GLRO (dl_pagesize);
INTERNAL_SIZE_T size = chunksize (p);
assert (chunk_is_mmapped (p));
uintptr_t mem = (uintptr_t) chunk2mem (p);
uintptr_t block = (uintptr_t) p - prev_size (p);
size_t total_size = prev_size (p) + size;
/* Unfortunately we have to do the compilers job by hand here. Normally
we would test BLOCK and TOTAL-SIZE separately for compliance with the
page size. But gcc does not recognize the optimization possibility
(in the moment at least) so we combine the two values into one before
the bit test. */
if (__glibc_unlikely ((block | total_size) & (pagesize - 1)) != 0
|| __glibc_unlikely (!powerof2 (mem & (pagesize - 1))))
malloc_printerr ("munmap_chunk(): invalid pointer");
atomic_decrement (&mp_.n_mmaps);
atomic_add (&mp_.mmapped_mem, -total_size);
/* If munmap failed the process virtual memory address space is in a
bad shape. Just leave the block hanging around, the process will
terminate shortly anyway since not much can be done. */
__munmap ((char *) block, total_size);
}
If we inspect the section layout of the libc in memory:
$ readelf -l libc.so.6
Elf file type is DYN (Shared object file)
Entry point 0x29f50
There are 14 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x0000000000000310 0x0000000000000310 R 0x8
INTERP 0x00000000001e3e30 0x00000000001e3e30 0x00000000001e3e30
0x000000000000001c 0x000000000000001c R 0x10
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000027fe0 0x0000000000027fe0 R 0x1000
LOAD 0x0000000000028000 0x0000000000028000 0x0000000000028000
0x00000000001944c1 0x00000000001944c1 R E 0x1000
LOAD 0x00000000001bd000 0x00000000001bd000 0x00000000001bd000
0x00000000000578cc 0x00000000000578cc R 0x1000
LOAD 0x00000000002148f0 0x00000000002158f0 0x00000000002158f0
0x0000000000004f98 0x0000000000012560 RW 0x1000
DYNAMIC 0x0000000000217bc0 0x0000000000218bc0 0x0000000000218bc0
0x00000000000001d0 0x00000000000001d0 RW 0x8
NOTE 0x0000000000000350 0x0000000000000350 0x0000000000000350
0x0000000000000030 0x0000000000000030 R 0x8
NOTE 0x0000000000000380 0x0000000000000380 0x0000000000000380
0x0000000000000044 0x0000000000000044 R 0x4
TLS 0x00000000002148f0 0x00000000002158f0 0x00000000002158f0
0x0000000000000010 0x0000000000000090 R 0x8
GNU_PROPERTY 0x0000000000000350 0x0000000000000350 0x0000000000000350
0x0000000000000030 0x0000000000000030 R 0x8
GNU_EH_FRAME 0x00000000001e3e4c 0x00000000001e3e4c 0x00000000001e3e4c
0x00000000000070cc 0x00000000000070cc R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x00000000002148f0 0x00000000002158f0 0x00000000002158f0
0x0000000000003710 0x0000000000003710 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_d .gnu.version_r .rela.dyn .rela.plt
03 .plt .plt.got .plt.sec .text __libc_freeres_fn
04 .rodata .stapsdt.base .interp .eh_frame_hdr .eh_frame .gcc_except_table .hash
05 .tdata .init_array __libc_subfreeres __libc_atexit __libc_IO_vtables .data.rel.ro .dynamic .got .got.plt .data .bss
06 .dynamic
07 .note.gnu.property
08 .note.gnu.build-id .note.ABI-tag
09 .tdata .tbss
10 .note.gnu.property
11 .eh_frame_hdr
12
13 .tdata .init_array __libc_subfreeres __libc_atexit __libc_IO_vtables .data.rel.ro .dynamic .got
We can see that the .dynsym
and .dynstr
sections are located underneath the .text
section. The .dynsym
and .dynstr
are used in lazy symbol resolution. If a external function called fgets
is called in a binary compiled with lazy linking (indicated by PARTIAL RELRO), the linker will search shared libraries for a symbol defined with the name fgets
and use the symbol information to retrieve the function address.
After unmapping part of the libc, we can perform another mmap to remap the lower part of the libc with data that we control. We can exploit this to provide malicious values for symbols in the libc to hijack lazy symbol resolution.
Unintendeds
Leak flag by corrupting size
This unintended is a combination of three things:
- flag is allocated on the heap
- size field of chunks can be modified
- the input function does not perform null termination
One can abuse this to modify the size of a chunk to overlap with a flag chunk, then allocate that chunk in the third check and print out the flag in the error message.