论坛首页 漏洞分析研究区 阅读主题

[原创] Pwncollege V8 Exploitation 完结

372 浏览 1 回复
#1 楼主 2026-06-01 21:09:20
笔者以巩固基础为目的,完成了 pwncollege 的 V8 Exploitation 课程。整体而言,题目质量不错,但偏向于入门,因此特地整理了一篇通关笔记。若文中有疏漏或错误之处,欢迎各位师傅批评指正!提前开一波香槟,完结 漏洞分析里有完整的patch内容修改编译参数接着编译为array类型创建了一个run方法首先,该函数会检查传入的对象是否为JSArray,并且要求数组的元素类型必须是简单类型(即没有 holes、不是对象等)。接下来,会判断数组的elements kind 是否为PACKED_DOUBLE_ELEMENTS,也就是要求数组中的所有元素都是double类型(即 JavaScript 的 Number类型,且不是int32、BigInt 或对象等)。随后,函数会检查数组的长度,确保其不会超过4096 字节(也就是最多512 个double元素),以防止溢出。通过这些检查后,函数会调用mmap申请一段4096 字节、具有读写执行(RWX)权限的内存区域。实际上,在这一步就可以将 shellcode写入到该内存区域。随后,函数会将JSArray中的所有 double元素逐个拷贝到新申请的RWX 段中。最后,函数将这段内存作为函数指针直接执行,也就是运行了你拷贝进去的shellcode。鉴定为v8里的ret2shellcode????????????完整的patch文件内容申请了一段rwx段,然后将用户创建的JSArray内容直接执行,那么提前在JSArray里提前布置shellcode就行第一种方法,生成shellcode的脚本,这里是生产的是BigInt类型,所以还需要调用ToDoubleArray转化为浮点数然后将输出的结果放到js脚本里,如下布置的shellcode这里将类型转化,shellcode array转化为double array第二种方法就是写浮点数的shellcode,这里是py脚本输出如下,赋值到脚本里即可这里直接写一个浮点数的shellcode就可以了 漏洞分析里有完整的patch内容修改编译参数编译这里patch了三个函数,从名字看其实就是三个v8利用里常见的原语,获取对象的地址、沙箱内地址任意读写可以看下怎么实现的,获取当前的isolate之后,先判断参数个数,接着判断参数的类型是不是对象(tag),然后直接获取address获取isolate,参数长度为1,也就是读这个参数地址的内容,判断参数类型是否为Number,下面获取cage_base,其实也就是gc申请内存的基地址,然后类型转化一下。接着获取full_addr(r14+addr),然后通过指针解引,返回一个uint32_t的结果。这里之所以需要先获取 cage base 并进行地址转换,是因为 V8 启用了指针压缩(Pointer Compression)机制。该机制下,堆对象的地址并不是完整的 64 位地址,而是相对于 cage base 的 32 位偏移量。不了解的读者可以自行搜索相关资料,原理并不复杂。获取isolate,限定参数长度为2,分别获取两个参数,同时判断类型是否为Number。下面的流程和ArbRead32类似,就是最后这里不是指针解引,而是*(uint32_t *)full_addr = value; ,这里学过c的都能看得懂吧完整的patch文件内容其实这里patch了三个可以直接利用的漏洞原语,思路就很明确了。对于12.8版本,这里可以采用JIT Spray的方法,写立即数的shellcode,然后修改function的code_addr,实现错位字节的shellcode,也就是利用Maglev存在的漏洞实现控制流劫持(这里的环境不存在沙箱,所以并没有实现沙箱逃逸)如果开启了沙箱,其实这个方法还是可以实现沙箱逃逸的,但是会多一点绕过条件mark一下一开始的思路,想通过覆写d8 12.8版本,劫持jump table 失败首先看下这一段代码的输出,这里需要关注code字段job code,看一下输出,这个对象记录了instruction_start的起始地址,下面的一串instruction就是这一个函数经过Maglev优化后的代码下面这一段其实就是我们写的shellcode的位置,浮点数是八字节的表示形式,去掉前面两个字节的操作,剩下的六个字节就是我们可控的内容,然后为了能写成rop的形式,所以6个字节里要已jmp结尾,用来跳到下一个gadget片段,所以我们每一个gadget可以写四字节的内容+一个jmp这里可以看一下写成的效果那么接着就是可以通过修改instruction_start 为我们控制的gadget片段的起始地址,修改为起始地址+0x6b(这里的偏移是根据不同环境来确定的)看一下一开始的code: 0x1244002402a9 <Code MAGLEV>修改之后然后在最后的syscall下一个断点,gdb里c过去,可以看到最后执行的效果,就是执行了execve("catflag",0,0); ,这样就可以拿到flag了这里我首先使用了py脚本生产一段shelcode这里对于catflag这个字符串的处理,我将这个转化为hex的形式,然后分成高位和低位4字节,分别赋值给eax和ebx,然后eax << 32位存储到rax里,接着再讲低4字节赋值给rax,这样字符串就处理好了,然后push到栈上,接着讲rsp赋值给rdi,那么execve的第一个参数就处理好了,后面的rsi和rdx已经系统调用,对于看到这篇文章的读者,应该不会成为一个问题看下这里执行完毕的效果将输出赋值到convert.js脚本里,convert.js脚本的内容,需要转化为BigInt类型,然后前面加上0x输出如下,最后拷贝到exp.js里,这个就是我们需要的shellcode过程中遇到了很奇怪的问题(玄学),这是我一开始的脚本,必须删除一些debug输出,才能在远程环境中输出flag最终拿到flag的脚本以下为最终的脚本,修改了shellcode执行的次数之后,可以稳定的输出flag,但是笔者debug了一下两个脚本的区别,最后的优化代码都是Maglev生成的,且size相同,没有查到根本原因,如果有知道的师傅,可以d一下笔者 漏洞分析里有完整的patch内容修改编译参数编译patch了两个很经典的原语,分别用于获取obj的地址和伪造objGetAddressOf的实现。获取isolate,限制参数长度不能为0,限制参数的类型必须为gc管理的对象,类型转化下,然后获取obj的地址GetFakeObject的实现。获取isolate和对应的上下文环境,限制参数的长度必须为1且为number类型,获取addr,将被压缩的指针恢复为原来的地址,cast为obj类型,接着调用obj_handle转化为obj。这里obj_handle的类型其实还是会做一些检查的,伪造的时候还是需要去伪造map、prototype等,以确保是一个看起来正确的obj下面是完整的diff内容有GetAddressOf和GetFakeObject,可以通过这两个原语来构造出一个fake_array,从而实现AAR和

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-287117.htm
#2 2026-06-01 21:09:20
感谢师傅分享,期待看到更多V8类的进阶文章~

请登录后参与讨论

立即登录 注册账号