HGAME2025 pwn方向题解
WEEK1
counting petals
逆向可以看出来,这一段存在一个数组越界
1 |
|
先对v8传入16,就可以覆盖v8和v9。这里要注意v8和v9都是int型,只占4个字节,所以要一次性覆盖完,这里我用103079215128(0x18 0000 0018)来覆盖。
之后的输出就会把libc_start_call_main+128的地址输出,计算一下就可以得到libc的基地址了。
(关于libc_start_call_main这个函数的偏移,我们可以在题目提供的./libc.so.6文件里找到。先将./libc.so.6文件放入ida中,然后找到__libc_start_main这个函数,查看它的伪代码,然后就可以找到libc_start_call_main函数的偏移了,如下图)
(图中的sub_29D10即为__libc_start_call_main函数)
之后程序会再有一次输入的机会,再用数组越界打ROP就可以了。
exp:
1 |
|
ezstack
本题前面的代码实现了一个简单的 TCP 服务器,首先创建并绑定一个监听套接字,监听端口 9999 的客户端连接。接收到连接请求时,服务器会使用 fork(), 创建一个子进程来处理该连接,父进程则继续监听新的连接。
想深入了解的师傅可以看看这篇文章webpwn的一些总结
我们本地调试时,可以先在一个终端上运行这个程序,然后使用nc或者直接在脚本里用io = remote(“0.0.0.0”,9999)。
本题的漏洞在这里:
1 |
|
可以溢出0x10的大小,显然需要使用栈迁移。
并且本题开启了沙箱,要使用orw:
1 |
|
那么接下来,我们先将栈迁移到bss上,泄露出libc地址并且返回到vuln函数。之后我的做法是使用ROP的orw,又迁移了三次输出flag。(我的做法复杂了,可以直接使用mprotect写shellcode,就不用反复迁移来迁移去了)
这里还有一个问题,就是本题使用read和write函数时的文件描述符是多少?我们平时做的题大部分都是3,但是这题因为前面使用了socket,文件描述符就被socket调用占用了3,所以我们再打开文件后,分配到的文件描述符应该为4。
exp:
1 |
|
format
这题首先注意到的就是一个可以无数次使用的格式化字符串漏洞:
1 |
|
但是只能输入三个字符,大概率就只能使用%p来泄露地址了,这里泄露出的是rsi里面的内容,是一个栈上的地址,可以通过动调计算出栈的地址。
接下来的就是一个看上去只能溢出一个字节的栈溢出:
1 |
|
但由于vuln函数的参数是unsigned int类型,我们可以直接输入-1来无限制的溢出。(但是我一开始没注意到这个溢出,傻傻的用那一个字节的溢出去改rbp的最低一个字节,想劫持到栈上的其他返回地址,结果就是劫持到main函数后,调用printf时由于栈没有16字节对齐,程序崩了。。。又想到把地址放到格式化字符串那里,然后再劫持,结果后面的v5是放到格式化字符串的高位上,没办法正确读到地址。。。)
那么现在,我们有栈上地址和一个无限的溢出了,还差一个libc的地址。
这里我是使用了%*d这个格式化字符串来泄露出_IO_2_1_stdin_的地址。
(这个方法的详细解释可以参考这个文章:[一种关于格式化字符串的新利用](https://www.cnblogs.com/viol1t-cheny/articles/18547503#exp)
简单来说,在%*d中,*代表第二个参数,控制输出的宽度,这个参数在调用约定中所使用的寄存器是rsi,而此时rsi是一个很大的值,会填满缓冲区,之后再输入%s,就会输出栈上的内容。这里我得到的就是stdin的地址了。
但其实不太建议使用这个方法,因为往往rsi都是一个非常大数,会接收到很多的数据,导致在打远程时可能需要很长时间(我自己打的时候跑一次远程大概要半个小时。。。)
这里还要注意,libc的最高位不一定都是0x7f,可能是其他的例如0x78,0x76等,我之前一直认为libc最高位都是0x7f。(感谢1SEz师傅的提醒)
之后再利用那个无限的栈溢出,就可以直接打ROP来getshell了。
exp:
1 |
|
WEEK2
signin2heap
逆向分析找到仅有一个漏洞点在:
1 |
|
看出这是一个off-by-null的漏洞。这题的libc版本为2.27,存在tcache,并且这题是2.27后期的版本,tcache中是有key的,所以我们不能直接通过double free来造成任意地址写,需要打两次off-by-null。
第一次off-by-null用来泄露出libc的地址。
第二次off-by-null通过堆块重叠来打tcache poisoning,将free_hook改为system。
exp:
1 |
|
Where_is_the_vulnerability
本题的四个增删查改的函数是使用动态链接库实现的,所以本地动调改libc时要记得把题目给的libhgame.so文件也加上。
逆向后发现delete函数存在uaf漏洞:
1 |
|
并且本题开了沙箱:
1 |
|
需要使用orw。
add函数只能申请到largebin,想到打largbin attack,并且本题libc版本为2.39,只能改bk_nextsize。
之后就是十分模板的house of的利用了。
官方给出的的wp是house of apple+mprotect,十分简洁优雅。相比之下我打的house of cat+ROP就有点复杂了。
我的wp里需要注意一点,由于使用的是ROP的orw,需要控制到rdi,rsi,rdx这三个寄存器,但是本题的libc.so.6库里没有直接的pop rdx ; ret;
,
只有 pop rdx ; ret 0x19
,
所以要在后面加上一个ret 7;
来调栈,就像这样p64(pop_rdx) + p64(0x60) + p64(ret_7) + p64(0)*3 + b'0' + p64(read)
。
exp:
1 |
|
Hit list
本题也是有增删查改四个功能的模板题,实现了一个单链表。做题的时候画了一个草图(有点难看请见谅
对程序分析可以看出,每次调用add函数都会分配两个堆块,一个大小固定为0x20(我们叫它info块),另一个大小我们可以控制(我们叫它data块),但是最大只能有0x3f0 ,也就是说不能直接分配得到一个largebin。
接下来我们注意到,在edit函数中,每次我们调用时,都会先将数据块先free掉,再重新malloc一个。
最后在sub_13F3这个函数中,发现当我们malloc分配失败时,会调用gift函数,在这个函数中,我们有且仅有一次机会可以任意地址free。
1 |
|
想要进入这个分支也很简单,传入一个负数就好了。
逆向分析完之后,我们就该想想怎么打这题了。
这题libc版本为2.35,无沙箱,漏洞点仅在gift函数的一次任意地址free里。
那么首先,我们应该先想办法泄露libc和heap的地址。
heap的地址可以通过tcache泄露出来,但是libc2.32加入了指针保护,即将fd指针地址右移12位再与fd指针本身异或。
一般来说,如果tcachebin链表中只有一个chunk时,只需直接将fd << 12即可得到heap的基址。
这题因为info块里存着下一个堆块的地址,我就直接使用堆风水来泄露heap的地址了。就是将一个free后的info块,让它以data块的身份被申请出来,就可以直接泄露出heap的地址了。
有了heap的地址,现在还剩libc的地址。泄露libc地址常用的方式是利用largebin和unsortedbin。
这题largebin不太好弄,只能使用unsortedbin。但是unsortedbin也不好直接使用,首先因为存在tcache,程序取堆块会先从tcache里取,tcache空后也会先将unsortedbin里的堆块放入tcache中,再取出,这样会导致fd和bk指针被清空从而无法泄露libc地址。
所以我们就想到利用consolidate(关于这个机制可以看看这篇文章:堆漏洞挖掘中的malloc_consolidate与FASTBIN_CONSOLIDATION_THRESHOLD。
为了利用这个机制,我们需要一个可以切割的unsortedbin。这个时候,我们想到了edit这个函数,它会先free掉data块,再重新malloc一个块。那么我们可以edit一个堆块,让它的data块的大小和之前不同,就可以在后面重新得到一个data块。之后再edit另一个堆块,我们就可以得到两个相邻的大堆块了,然后free掉这两个堆块,再add时,这两个堆块会合并,加入到unsortedbin里,然后就可以进行分割,从而泄露出libc地址了。
之后就是任意地址free,利用tcache poisoning打house of obstack了。
exp:
1 |
|