scwuaptx / Pwngdb

gdb for pwn
GNU General Public License v3.0
888 stars 126 forks source link

glibc heap analyzing displays wrong indexes for largebins #24

Closed eLoopWoo closed 5 years ago

eLoopWoo commented 6 years ago

Analyzing glibc2.24 malloc internals on a 64-bit machine, I'm using both parseheap & heapinfo functionalities.

Basically, when we use heapinfo we get "wrong" index number for largebins. It will be easier to explain with an example.

 1 heap_k.c                                                                                                                       X 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>

 #define NSMALLBINS 64 // from glibc 2.24

 // actual calculation of NSMALLBINS 
 #define NUNSORTEDBINS 1 
 #define MIN_LARGE_SIZE (NSMALLBINS * 0x10)
 #define MIN_CHUNK_SIZE 0x20
 #define CHUNKS_SKIP ((MIN_CHUNK_SIZE / 0x10) - 1)
 #define OVER_HEAD 0x8
 #define REAL_NSAMLLBINS ((MIN_LARGE_SIZE - (NUNSORTEDBINS * 0x10)) / 0x10 - CHUNKS_SKIP) // 62

 void main(){

     setvbuf(stdin,NULL,_IONBF,0);  // no buffering
     setvbuf(stdout,NULL,_IONBF,0); // no buffering
     char* a00 = (char*) malloc(512);  // no coalescing 
     char* a01 = (char*) malloc(0xb0); // smallbin will be splitted for malloc(0x0) request
     char* a02 = (char*) malloc(512);  // no coalescing 
     char* a03 = (char*) malloc(REAL_NSAMLLBINS * 0x10 + CHUNKS_SKIP * 0x10 - OVER_HEAD); // biggest smallbin chunk possible
     char* a04 = (char*) malloc(512);  // no coalescing 
     char* a05 = (char*) malloc(REAL_NSAMLLBINS * 0x10 + CHUNKS_SKIP * 0x10 - OVER_HEAD + 1); // smallest largebin chunk possible
     char* a06 = (char*) malloc(512);  // no coalescing 
     char* a07 = (char*) malloc(9000); // random largebin chunk
     char* a08 = (char*) malloc(512);  // no coalescing 

     free(a01);
     free(a03);
     free(a05);
     free(a07);

     char* a09 = (char*) malloc(0x90); // a01 is spillted to size 0xb0-0x90 = 0x20 
     char* a10 = (char*) malloc(0x9001); // sort unorderedbin

 }
 ~     

What this example does is forcing a split of chunks so a chunk of size 0x20 (smallest) will be inserted to the 0x20 smallbin which is the first one. Then it allocates the biggest chunk that fits smallbin requirments 0x3f0 - 8 so it will be inserted to the 0x3f0 which is the last smallbin. Afterwards it allocates the smallest chunk that fits largebin requirments 0x3f0 + 1 so it will be inserted to the first largebin.

The calculation problem, lets break into this program at the end of main. gdb heap_h -x utils.sh

# utils.sh
...
define hip
parseheap
heapinfo
end
...

output

gdb-peda$ hip
addr                prev                size                 status              fd                bk                
0x555555756000      0x0                 0x210                Used                None              None
0x555555756210      0x0                 0xa0                 Used                None              None
0x5555557562b0      0x0                 0x20                 Freed     0x7ffff7dd3b68    0x7ffff7dd3b68
0x5555557562d0      0x20                0x210                Used                None              None
0x5555557564e0      0x0                 0x3f0                Freed     0x7ffff7dd3f38    0x7ffff7dd3f38
0x5555557568d0      0x3f0               0x210                Used                None              None
0x555555756ae0      0x0                 0x400                Freed     0x7ffff7dd3f48    0x7ffff7dd3f48
0x555555756ee0      0x400               0x210                Used                None              None
0x5555557570f0      0x0                 0x2330               Freed     0x7ffff7dd4208    0x7ffff7dd4208
0x555555759420      0x2330              0x210                Used                None              None
0x555555759630      0x0                 0x9010               Used                None              None
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
                  top: 0x555555762640 (size : 0x149c0) 
       last_remainder: 0x5555557562b0 (size : 0x20) 
            unsortbin: 0x0
(0x3f0)  smallbin[61]: 0x5555557564e0
(0x020)  smallbin[ 0]: 0x5555557562b0
         largebin[64]: 0x555555756ae0 (size : 0x400)
         largebin[108]: 0x5555557570f0 (size : 0x2330)
gdb-peda$

heapinfo output shows us the "a05" is in the 64th largebin, it should be the 1st (0) in largebins.

Sidenote, both smallbins and largebins are part of a bigger array in "malloc_state" struct called "bins". This array length is determined at compile time:

// glibc2.24
// https://github.com/bminor/glibc/blob/release/2.24/master/malloc/malloc.c
#define NBINS             128

The first bin is the unsortedbin, then (in this example) 62 smallbins and the rest should be largebins (63?)..

Example of calculating the last smallbin and first largebin address in malloc_state. Check main_arena (malloc_state) struct

gdb-peda$ p main_arena
$1 = {
  mutex = 0x0, 
  flags = 0x1, 
  fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 
  top = 0x555555762ad0, 
  last_remainder = 0x555555756740, 
  bins = {0x7ffff7dd3b58 <main_arena+88>, 0x7ffff7dd3b58 <main_arena+88>, 0x555555756740, 0x555555756740, 0x555555756000, 
...

calculate

gdb-peda$ set $unorderedbin=(long long int)(&main_arena.bins)
gdb-peda$ set $last_smallbin=(long long int)(&main_arena.bins) + 0x10 * 62
gdb-peda$ set $first_largebin=(long long int)(&main_arena.bins) + 0x10 * 63
gdb-peda$ x $unorderedbin
0x7ffff7dd3b68 <main_arena+104>:    0x00007ffff7dd3b58
gdb-peda$ x $last_smallbin
0x7ffff7dd3f48 <main_arena+1096>:   0x00005555557564e0
gdb-peda$ x $first_largebin
0x7ffff7dd3f58 <main_arena+1112>:   0x0000555555756ae0
scwuaptx commented 6 years ago

Thank you for your report. I think it might be a bit confusing if using the same index , so use the new index for largebin. :) .