Skip to main content

HGAME EldenRingFinal wp

这是hgame线上的最后一题,但是我做的比较快,终于是在最后拿了一个一血,这道题嘛,说实在的,思路比较顺畅,加之在网上实际上是有一些现成的解决方式,所以个人认为难度可能对于我来说不大

首先分析反汇编代码,我其实很迷惑为什么要有这个page和note这种设定,以及为什么要用链表来将他们连接,反正我利用过程是没有用到这些特性,莫非是烟雾弹?迷惑我们的?还是针对这些有其他解法?我反正是花了一个小时来阅读代码,page和note应该都是结构体,结构体的内容如下:

实际上我的思路中这个结构的成员也没有啥用,只需要知道一件事,就是如果add_page,就相当于在malloc(0x20)即0x30,如果add_note,就相当于先malloc(0x20),再malloc一个任意大小的chunk,并且只有这个任意大小的chunk能改写大小,并且我猜到他一定有off by one,为什么这么猜测请看后文

代码分析完毕就可以开始思路分析了:

这道题目没有show函数,没有UAF,没有edit函数,想要在free后还能修改我们有几种方式,直接uaf修改或者double free,的你这都需要uaf,所以我们需要人造uaf,即overlapping,所以我不用看就一定知道存在堆溢出,果然,add_note中就存在off by one漏洞

之后我们就要泄露libc地址先,由于没有show函数,并且提示用IO_FILE,在网上搜索很容易找出现成的方式,就是malloc一个chunk到stdout附近并且修改相关的值(这个原理我将在文末讲解),由于只有fastbin,于是想做到上述这一步就必定要修改fastbin的fd指针,修改的能力上面讲了用overlapping,至于要修改到stdout附近那就需要爆破了,你需要用合理的堆布局手法先实现overlapping,获得一个存在于fastbin但是属于其他chunk的野指针,再使用一些方式在这个fastbin的fd上留下一个脏数据,我听网上说是main_arena+88,反正我是用的unsorted bin的地址,之后再使用爆破的方式,这里为什么只需要爆破半个字节我不是很明白(TODO),反正当时说的是1/16的几率,其实就把它改成在gdb中调试时获得的那个值就可以了,这个值也不能就是stdout,因为fastbin在malloc出来时有一个size的检查,就是需要用字节错位的方式绕过,在IO_2_1_STDOUT上面找到一个合适的字节错位的值,我记得我找的就是一个7f(有点忘了),之后malloc到这个值之后修改相关结构体(怎么修改在文末讲解),使得下一次puts函数调用可以实现leak地址,之后leak地址之后就再重复上述的操作,再来一遍overlapping,写上malloc_hook的附近的地址,因为这里也需要考虑字节错位,malloc到附近后修改成hook为one_gadget的地址,再malloc实现getshell。

思路总结下图,这是我做题时画的

其实我当时想的思路非常顺畅,但是思路简单,真正的实现特别之麻烦,你需要精细的布局chunk,这通常需要很多次的尝试,修改,猜想等等,但其实这题的堆布局没有之前那道off by null麻烦,毕竟off by one直接扩大size的值可以在两个chunk就直接实现overlapping,还不需要通过那些合并来overlapping,因为合并还有特别多恶心的检测,但这题的字节错位对于chunk的大小有着要求,所以还是有点麻烦的,我简要说一下我的布局方式吧(我回头看我这个布局时被我当时的智商惊到了,但当时也只是调试的结果,只能说运气比较号):

先malloc4个chunk,第三个的大小为0x20,用第一个先off by one将第二个扩展到0xd0,再free掉2,3,再malloc一个0x30的,使得头分配在第三个chunk的头,数据部分从2的unsorted bin切割,从而留下脏数据,之后需要修改脏数据,修改还得要修改malloc出来的最终那个chunk的头的size要是0x70,这需要非常精确的把控,我甚至不知道我当时是怎么做到的,当时确实花了很长时间调试这部分,好在思路清晰目标明确,确切的思路看我的exp吧

最后我说一下那个修改stdout的方式,实际上再网上是有现成的,我这里简要说一下原理吧,它就是为了让puts函数绕过一系列的检测输出一个大区域的值,所以最终目的是要改掉io_write_base这个变量为一个比较小的值,并且在运行时不会改变io_write_base,这个值在overflow函数中可能会被改变,如下图

必须让他绕过第一个检测

即这个0x800必须设置,并且还有一个错误检测

8这个位置必须为0

最后还有个io_new_write函数

必须保证运行到syscall之前write_base不变,那就一定不能走第二个分支,第二个分支的绕过方式就是走第一个分支即可,需要设置appending,即0x1000

so,最终的flag数就是0xfbad1800,修改flag之后再修改结构体中的write_base就能实现一调用puts就能输出一个很大的区域的值,从而实现leak libc,确切的payload如下

payload = p64(0xfbad1800)+p64(0)*3+b"\x58"