SomeHash
题目实现了简单的Hash计算的逻辑,提供了3次初始的计算用户输入Hash的机会
其中漏洞点在
这里的v10没有检查负数,可以向bss段上方的数据中写入一字节
这里的利用方法是一个小技巧,来自于got表的lazy函数注册逻辑,也就是checksec显示如下
Lazy函数注册逻辑
当ELF加载时,并不会直接调用dl_runtime_resolve将函数注册成真实地址,此时got表也是可写的,我们再观察一下这里got表指向,这里指向的是plt上方的一个地址,可以看到这里的exit函数,got表进入的是0x1030的位置,之后jmp到了0x1020的函数,0x1020函数内会jmp到0x5010的位置,也就是pwndbg中的_dl_runtijme_resolve_xsavec
关于对于dl_runtime_resolve
的内容这里就不赘述了,网上有很多教程。这里我们并不需要关心dl_runtime_resolve
做了什么,我们只需要动态观察got表未注册的内容的规律,在上面的pwndbg显示的,注意这里got表是可写的,那么我们就有一个大胆的猜想,如果我们在函数注册之前,修改了got表里的数据会怎么样?
可以看到的是,在函数注册前,got表内容十分相近,相差只有一个byte,上述的数组下标溢出的漏洞非常合适这里的利用。
strlen2printf
这里有一个非常合适的错误注册的函数——strlen
这里main函数逻辑中,调用strlen是在漏洞利用之后,我们可以修改未注册的strlen的got表内容,改成printf的偏移,使得strlen错误的注册成printf函数,而在接下来的函数调用中,buf内容是可控的,导致我们可以将数组下标溢出漏洞转换成printf格式化字符串漏洞
(并且,printf的返回值是输出字符的数量,strlen也是字符数量,并不影响后续程序的逻辑)
至此,我们构造了一个非栈上字符串的格式化字符串漏洞,而这个问题也已经有方法解决https://www.freebuf.com/vuls/284210.html
EXP
完整exp如下,直接使用格式化字符串提权是困难的,因为一共只有4次机会,我们可以利用这四次机会,修改dword_5078地址内容,使得while的次数变多,最后完成格式化字符串攻击
1 | from pwn import * |
ps: 附录中有调试使用的dockerfile与docker-compose.yml
SomeTime
本题是单个堆块的堆风水题目,是一个你与some从恶魔手中夺取flag的合作历险故事
漏洞点在
SIGALARM的信号处理函数watch中
这里会将now指针中的低位字节清零,剧情中,some在最后时刻能为你做到最后的事情。
信号注册在init函数中
思路也比较简单只需要利用tcachebin机制,把tcachebin当作以前pwn可以保存多个堆块的题目的堆块数组即可
为了做到上述内容,我们要保证每次申请释放的size大小不同,即可在tcachebin中只存在一个堆块
由于我们可以将申请出来的指针做低字节的修改,所以我们可以很方便的构造堆叠,修改tcachebin的size位使得size变大,扩大溢出范围,之后我们可以通过重复申请tcachebin内容的堆块,泄露地址,最后完成fd修改,最后houseofapple一把梭
关于堆风水
这道一题目,我们需要尽量申请时候使用不同的size,否则将会申请出相同地址的堆块,或者这里可以多次add,使得申请多个无指针引用内存,使得内存地址扩展,之后篡改size顶部tcachebin,size位置,使得刚好大小超过0x420并能完美覆盖中间tcache,衔接上后方伪造的size,使得此时free后能进入unsortedbin,从而可以泄露main_arena地址,使得泄露libc地址。
之后就是修改tcachebin的count使得大于1,这里就要一些堆风水的技巧,一种可行的思路是,我们构造一种堆叠,使得一个大的tcachebin堆块覆盖两个及其以上的堆块,这样我们就可以同时操控chunk1和chunk2的内容,控制这两个size设置为相同的即可
(注意由于本题目只能拿到一个堆块做操作,也就是修改fakechunk的时候chunk1与2是在tcachebin中的,tacachebin中并不检查malloc取出的堆块大小是否正确,同时这里修改chunk2时候,注意恢复chunk1的fd和key字段)
1 | | ---------- fake chunk -------------------| |
EXP
PS:由于本题做了大量的sleep操作,这里在本地调试的时候需要patch掉sleep的时间,使得调试变快
在程序最后需要等待时间到达,系统自动调用exit退出即可获得shell
1 | from pwn import * |
ps: 附录中有调试使用的dockerfile与docker-compose.yml
shutup
此题没有输出,单纯只有输入,没有开PIE,没有开canary,漏洞就是栈溢出
但是这里难点是如何泄露,或者如何构造出libc的任意地址,很明显,这里不给我们第二次的输入机会
需要注意到,题目给了一个没有调用的函数,可以从数组中取出数据,这里可以利用数组下标负数溢出,使得取出got表中read地址
获得了read地址还不足以能够做到取出libc任意地址,但是如果这里的qword_601060 += atoi(nptr);
逻辑就很巧妙,如果我们能够按照下面的方法控制执行流,那么我们就能将read内容存入qword_601060中,之后我们利用rop,在bss上布置一个数字,并使用pop_rdi; ret 0x000400703
的手法,就能在qword_601060中构造出read+offset,我们也就能获得syscall
任意地址写原语
在进入栈溢出函数的开始,我们只能写入0x40个字节,很明显,这是不够的,我们需要找到一种方法,能够任意地址写,并能支持写入多个字符。
答案是:依然还是函数sub_4006B7,我们再次审视下面的函数汇编,会发现,edi的数值会写入[rbp-4]的位置,而rbp我们可以通过pop rbp的rop控制
我们很轻松的就能构造如下的原语
1 | [ |
这就能向addr中写入0xde字节,为什么我们只能写入一个字节呢?因为edi的数值后续会作为数组的索引,数字太大会导致索引到不可读的内存,导致段错误,所以为了保险起见,这里我们每次只写入1个字节
最后我们就能构造任意地址写的payload构造函数
1 | def make_bytes(addr, bbb): |
接下来的内容就比较简单,控制rdi、rsi、rdx之后调用mprotect修改bss的可执行权限,写入shellcode即可
但是rdx的控制这里利用了,这个部分,控制r12、rbx内容使得call的内容刚好是pop rbp,将call在栈上写入的地址pop掉即可
EXP
1 | from pwn import * |
不同的libc,修改一下上面offset变量即可
附录
以下是Ubuntu GLIBC 2.35-0ubuntu3.1的docker调试环境
Dockerfile
1 | FROM ubuntu:22.04@sha256:b492494d8e0113c4ad3fe4528a4b5ff89faa5331f7d52c5c138196f69ce176a6 |
docker-compose.yml
1 | version: '3' |
题目zip
作者: Csome