-
-
[原创][分享]堆喷和悬空指针利用的本质:新手入门技巧
-
发表于: 14小时前 166
-
堆喷的概念和本质其实很简单:你只需要知道分配器是如何分配空间的
更多的时候分配器分配空间做的是匹配工作
分配器就是一页巨大的空闲空间清单而不是仓库,分配器不储存数据和空间
当一个数据被释放后,这个数据所占的空间(也就是地址)就会被放进分配器的空闲链表
等到下一次为一个数据分配空间时,分配器就会从空闲链表中拿取和这个数据的空间大小相近或者一致的首地址,像划分地盘一样分给这个数据
这也就是为什么kmalloc会返回一个指针,因为它的工作原理返回一个指向分配器选出的首地址的指针,并且分配器不负责清理初始化地址上的数据,所以你需要在拿到指针后初始化空间,不然你就会读取到这片地址的残留数据
所以释放后的源地址是不会变的,但是使用它的人变了———这是一种非常危险的信号,如果你拥有一个指针指向这个地址,那么来了下一个住户时,你就可以通过指针改写它
因为分配器分配空间的规则是匹配分配,一个50字节空间大小的地址被释放后添加进空闲列表后,这块内存空间的下一位使用者大概率是同样50字节的数据体
理解了这些,再去看悬空指针和堆喷占位就非常简单了
探查你想要修改的数据大小,然后定义一个相同大小的数据并且分配空间,用一个指针指向分配空间的绝对地址
释放这片空间(也就是kfree)——这时候指针指向的地址已经加入了空闲链表,这个指针就变成了悬空指针,这时候我们立刻定义并且分配大量的相同空间大小的数据,很可能我们的悬空指针指向的地址就会被我们的其中一个数据占据,我们就可以通过这个指针改写这个数据,无论这个数据是可读还是可写你都可以更改它
为什么无论可读还是可写你都可以更改它呢?
因为可读或者可写的本质,在内存里它就是0或者1的数据,一个状态位,它就是一个整数,一个bool值,仅此而已,它能做到限制你写入读取的原因不是因为它的数据是不可更改的,是因为你调用的函数根据这个状态位来决定是否写入或者读取这个数据,状态位本身没有任何作用和限制你的权力,你甚至可以把它改成一个垃圾值让系统崩溃
这也是为什么堆喷一旦成功命中目标后会极其危险呢,因为它不仅绕过了权限检查,它还亲手触碰到了数据本身
一个验证你是否理解这些概念的例子,如果你理解了,你不会有任何疑惑:
1.探查保存了管理员权限状态位结构体,比如Linux上的cred,它控制了进程的权限,假设探查后它的大小是500字节
2.定义一个结构体a(或者任何你想要的数据,只需要保持大小一致),kmalloc分配500字节空间,然后用指针p_a指向结构体a
3.释放结构体a,kfree(a); (悬空指针)
4.当前进程或者子进程,疯狂定义cred结构体并且分配空间(堆喷),很可能这时候p_a指向的地址就被分配给了其中一个cred结构体
5.验证p_a指向的地址被分配给了我们大量的cred结构体,强制转换指针p_a为cred结构体 (struct cred)p_a;
6.然后读取p_a的uid(user id)和gid(group id)是否是一个正常值(比如 1100>uid/gid>=1000),一旦匹配成功立刻尝试将uid和gid设置成0(root权限),然后复写储存管理员密码路径的文件,将密码置空,一旦成功,你就取消了管理员密码,这样你就以最粗暴的方式攻破了系统,就算你不更改管理员密码,在这个程序里你已经拿到了管理员权限,你可以做任何事情。 如果失败就继续循环
7.成功后退出程序,失败继续循环,如果可以,你可以尝试每隔一段时间就统一清理那些未命中的数据空间,或者匹配失败后即时清理,但那会拖累我们的宝贵的有效堆喷窗口,如果长时间未匹配成功就直接退出程序然后重试
总有一次,你会通过悬空指针命中cred结构体,一旦命中,整个系统就会被攻破
欢迎斧正
赞赏
赞赏
雪币:
留言: