本次强网杯线下和0x300R的师傅们一起打了,其他师傅都太强啦。但最后一天的时间很短,我们最后demo的4题,基本都因为环境问题,没有成功,其中就包括了我这里demo 的monotint,一道浏览器的nday复现第一个坑是关于v8沙箱的问题。这道题目的chrome版本是139.0.7258.128,启动参数—no-sandbox,意味着render rce之后就可以任意代码执行。但是正常情况下v8沙箱在这个版本下是默认开启的,所以我本地编译了一个开启v8沙箱的chrome,在这个基础上进行利用,但是后来发现其实题目是没有编译v8的沙箱的,所以那一段沙箱逃逸的逻辑根本没有使用到,耽误了很久的时间……(以后还是直接上去测题目给的虚拟机第二个坑是最后弹计算器的问题。公告和题目的信息是分开的,因此没有注意到,公告中提到了为了降低演示的难度,可以自选方法进行验证rce。所以最后花了一段时间去解决弹计算器的问题,其实只需要xauth给个权限可以了,这里需要感谢组里的两位大哥@leommxj和@m4x,帮助我解决了弹计算器的问题第三个坑是cpu保护的问题。公告中提到了现场演示机器的cpu是intel 14gen,不过我确实没有忘pku内存保护的方向去想,所以最后的利用部分造成了问题。绕过方式也很简单,jit spray或者写一个wasm函数绕过就行题目除了下发了一个虚拟机之外,虚拟机里还有一个启动脚本和一段diff所以查看了下启动参数,发现没有沙箱,所以只需要考虑render rce,那么主要需要思考的是v8侧的利用接着查看版本信息,chrome版本是139.0.7258.128,v8版本是13.9.205.19看到了v8的版本不算新,因此看了下commit hash,8月4号的提交,那么之前p0的Big sleep挖出来的CVE-2025-9132是可以直接打的,但是我没准备 :(由于笔者并没有提前准备1day(科恩的师傅应该是有的,所以最后也只有他们demo成功了,太强了),所以我只能看题目准备了什么diffdiff信息,但是看着就觉得很眼熟,之前似乎在issue tracker上看到过,于是关键词搜索,就发现了issue tracker上公开的报告,接着就搜索到了公开的blog分析和完整的脚本,编号是CVE-2024-12695对于正常的render rce流程还需要一个v8的沙箱逃逸,因为是默认开启的,所以笔者接下来做了两件事情在比赛的当天下午6 7点钟这样,已经完成了第一个部分,但是此时笔者并不知道题目下发的虚拟机其实是没有的,因此后面花了大部分时间在v8的沙箱逃逸上……搭chrome的环境编译参数搭d8的环境编译参数调试chrome的时候如果需要使用d8的调试函数,需要加上--js-flags="--allow-natives-syntax",由于个人习惯,我还会加上--auto-open-devtools-for-tabs,这样会自动打开devtools这个nday详细的分析,我觉得看别人已经公开的就好,我这里根据自己的理解,大致再分析了一下从删去的部分不难理解,这里删去的部分其实是对于properties字段的检查,检查这个字段是否是smi,如果是smi则跳转到slow_path执行后续逻辑;如果不是smi则进入fast_path。注意到CSA_DCHECK,意思是当前字段如果不是smi,那么必须是一个EmptyFixedArray。被删去之后,言下之意就是说这个字段可以为smi,也可以为FixedArray(object)所以这个检查的含义可以总结为,删去了对于对象properties字段类型的检查,不再检查properties是否为smi或者对象,无论properties字段为smi或者对象都执行同一个fast_path的流程。此时我们具备了改变对象properties字段的能力而这个逻辑位于一个builtin方法TF_BUILTIN(ObjectAssign, ObjectBuiltinsAssembler)中,所以我们现在知道Object.assign这个方法存在漏洞可以通过这个代码演示一下当注册完gc监控对象target之后,unregister_token 产生了hash字段。接着通过Object.assign的快速路径,使得unregister_token的hash字段被破坏,这个被破坏成了一个FixedArray需要解释一下FinalizationRegistry的使用方法上方的register方法是监控了target对象,当target被gc回收时,会执行自定义的回调函数。上方的第三个参数是注册的token,后续调用了Object.assign破坏了hash字段调试代码如下这里的diff很简洁,将强制CHECK换成了DCHECK,这个函数JSFinalizationRegistry::RemoveCellFromUnregisterTokenMap由上方提到的unregister调用,意味着执行registry.unregister时不再对entry进行检查所以对于的hash值不存在于key_map的情况,entry的值为-1。又由于换成了DCHECK,就会绕过entry.is_found()的检查接着如果当前的weak_cell prev指针为undefined,则会进入到下方的if循环时,会执行key_map->ClearEntry(-1)继续跟踪调用,首先进入下方的模板函数接着进入SetEntry,从上方的传递值可以发现key和value都是the_hole,也就是0x7d9index的转化逻辑调用了如下函数,如果说一开始的entry是-1,也就是没找到对应的hash值,那么则会计算出index=1接着通过调试验证,同时可以得到Derived::kEntryKeyIndex = 0; Derived::kEntryValueIndex=1;所以这里会将this,也就是key_map index为1和2处都设置为the_hole(0x7d9)我们需要观察一下key_map的结构,可以看到这里是由FixedArray申请出来的,但是map是SIMPLE_NUMBER_DICTIONARY_TYPE,所以我们找一下这个map类型的定义这里说明对于header来说没有多余的size,每一个Entry是2项。header位于v8/src/objects/hash-table.h中,slot[0] = kNumberOfElementsIndex,slot[1] = kNumberOfDeletedElementsIndex,slot[2]=kCapacityIndexEntry定义了value在index为1处,那么0处就是key这里涉及到的对象继承关系对应到上方的调试结果就是上方的0x001313d2也就是hash值<<1的内存中的表示,后面紧跟着的是WeakCell接着经过ClearEntry之后,可以发现完整的如下,可以发现容量变成了0x000007d9,所以此时产生了一个越界的SimpleNumberDictionary上方其实已经得到一个可以越界的SimpleN
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-289369.htm
[原创] 强网杯S9 Real World - monotint
341 浏览
1 回复
好文,师傅写的太细了