JavaScriptCore内部原理(一):从JS源码到字节码的追踪

2024-03-06 05:18

本文主要是介绍JavaScriptCore内部原理(一):从JS源码到字节码的追踪,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、概述

事实证明,在Fuzzing Webkit的过程中,使用Fuzzilli对JavaScriptCore(JSC)进行Fuzzing会非常成功,随着时间的推移,会产生大量崩溃。但是,一旦出现崩溃,由于不熟悉WebKit代码库,同时又缺少代码库相关的查询文档,要验证一处崩溃是否可以被漏洞利用,往往需要花费相当长的时间。正因如此,我们希望通过这一系列文章,深入研究JSC的内部原理,希望能扩展这部分知识。这一系列文章还针对安全研究人员,可以有助于引擎漏洞研究的过程。

第一部分主要探讨如何将源代码进行解析,并转换为字节码,同时我们跟踪了整个代码库的过程。下面的图片是来源于JSC的演示文档,描述了管道的三个部分,这些也会在我们的系列文章中介绍到。

JavaScriptCore内部原理(一):从JS源码到字节码的追踪

本文将介绍JSC中的源代码解析器,它实际上是一个字节码编译器,使用在解析阶段结束时生成的AST(抽象语法树)并从中生成字节码。字节码是引擎的真实源代码,并且是JSC中各种即时(Just-In-Time)编译器的关键输入之一。这篇文章将探讨生成的字节码,并帮助理解一些操作码和操作数。最后,文章以低级别解释器(LLInt)执行字节码为结尾。在这一系列的第二篇文章中,奇热将深入研究低级别解释器(LLInt)和Baseline JIT。

二、先前研究

在这里,强烈推荐阅读Michael Saboff关于JSC架构和JIT的精彩演讲——《JavaScriptCore,多种编译器让这个引擎执行》。尽管这个演讲没有深入到每个JIT层的内部原理,但确实提供了概述,并说明引擎采用的各种优化技术和配置方法。

WebKit Wiki是在研究代码库时,可以参考的另一个非常有用的博客。其中提供了对引擎的概述,非常有用,但缺少足够的细节。

Saelo发表过一篇简短的论文《攻击JavaScript引擎:JavaScriptCore和CVE-2016-4622的案例研究》,可以帮助我们熟悉JSC运行时,他在研究的各个部分中都对此进行了讨论。

三、准备工作

在这一章,将演示如何设置调试环境,并编译JSC Shell实用程序的调试版本。一个有效的调试环境可以导航JSC代码库,并对运行时检查引擎执行的各个方面都至关重要。

3.1 生成调试版本

按照以下步骤,可以克隆GitHub上Webkit存储库的镜像,并为JSC Shell编译调试版本。

$ git clone https://github.com/WebKit/webkit && cd webkit
$ Tools/gtk/install-dependencies
$ Tools/Scripts/build-webkit --jsc-only --debug
$ cd WebKitBuild/Debug/bin/
$ ./jsc

3.2 设置调试环境

生成调试二进制文件后,需要配置IDE(集成开发环境)和调试器,以进行代码审计,并逐步执行引擎。这篇文章将使用包含ccls的VSCode进行代码审计,并且其中集成了gdb以进行交互式调试。各位读者可以自由使用最为熟悉的IDE和调试器。如果选择使用VSCode和ccls,则需要在VSCode的launch.json中添加以下启动任务。注意:请确保正确修改了以下代码段中列出的文件路径(即program和args),以符合目标调试环境。

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) Launch",
            "type": "cppdbg",
            "request": "launch",
            "program": "/home/amar/WebKit/WebKitBuild/Debug/bin/jsc",
            "args": ["--dumpGeneratedBytecodes=true", "--useJIT=false","/home/amar/workspace/WebKit/WebKitBuild/Debug/bin/test.js"],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "WebKit Debug Build"
        }
    ]
}

这里,添加了一个可选的编译任务来启动配置,该配置将在启动调试器之前编译JSC。这个步骤是可选的,但通常最好将代码库与正在生成的调试版本同步。下面列出了添加到task.json的编译任务:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "WebKit Debug Build",
            "type": "shell",
            "command": "/home/amar/workspace/WebKit/Tools/Scripts/build-jsc --jsc-only --debug"
        }
    ]
}

如果一切正常,调试环境现在应该允许启动gdb(VSCode中为F5)并命中已经设置的断点,如下图所示:

JavaScriptCore内部原理(一):从JS源码到字节码的追踪

四、JSC Shell

这一章将讨论上一章中生成的JSC Shell,并展示它在理解引擎内部原理上的重要性。JSC Shell允许研究人员和开发人员将JavaScriptCore作为一个独立的库进行测试,而无需编译整个WebKit项目。JSC Shell为JavaScript引擎提供了Repeat-Eval-Print-Loop(REPL)环境。此外,它还允许通过命令行传递JS脚本,该脚本由JSC Shell读取,由引擎解析和执行。

Shell的源代码可以在jsc.cpp中找到。

Shell的入口点是jscmain。这个函数负责初始化Web模板框架(WTF),该模板是Webkit代码库中的一组常用功能,并在创建JSC vm之前解析选项。

JSC的初始化开始于对runJSC的调用,它在被调用时为VM对象分配内存,同时初始化并检索GlobalObject引用。

int runJSC(const CommandLine& options, bool isWorker, const Func& func)
{
    //... 为简化代码,此处省略一部分
   
    VM& vm = VM::create(LargeHeap).leakRef();
    //... 为简化代码,此处省略一部分
   
    GlobalObject* globalObject = nullptr;
    {
    //... 为简化代码,此处省略一部分
        globalObject = GlobalObject::create(vm, GlobalObject::createStructure(vm, jsNull()), options.m_arguments);
        globalObject->setRemoteDebuggingEnabled(options.m_enableRemoteDebugging);
        func(vm, globalObject, success);
        //... 为简化代码,此处省略一部分
    }
    //... 为简化代码,此处省略一部分

GlobalObject::create最终会调用JSGlobalObject::init(VM& ),后者负责使用所需的内置程序和其他运行时来设置活动,以此初始化VM。这篇文章不会详细介绍如何解析内置代码并将其链接到VM,有兴趣的读者可以阅读JavaScriptCore/builtins/,了解作为JSC运行时组成部分之一的所有内置对象/构造函数。另外,还可以选择设置断点并逐步执行JSGlobalObject::init的另一种方法。

在初始化VM和GlobalObject之后,将执行传递给runJSC的lambda函数。Lambda函数调用带有三个参数的runWithOptions,这三个参数分别是指向初始化的GlobalObject的指针、传递给JSC Shell的命令行选项以及一个状态变量。

runWithOptions的主要目标是创建必要的缓冲区,以存储提供给JSC Shell的原始JavaScript。就这篇文章而言,以下脚本文件(test.js)将作为命令行参数传递给JSC:

$ cat test.js
let x = 10;
let y = 20;
let z = x + y;
 
$ ./WebKitBuild/Debug/bin/jsc test.js

一旦设置完成,且填充了后备缓冲区之后,Shell现在将通过调用的方式评估和执行脚本:

NakedPtr < Exception> evaluationException;
JSValue returnValue = evaluate(globalObject, jscSource(scriptBuffer, sourceOrigin , fileName), JSValue(), evaluationException);

在上面的代码片段中,scriptBuffer存储了通过命令行传递的test.js的内容,sourceOrigin将URI存储到脚本,在我们的案例中传递的是脚本的绝对路径。jscSource是一个辅助函数,可以从scriptBuffer生成一个SourceCode对象。SourceCode对象封装原始脚本数据。

五、运行时部署

现在,已经通过JSC Shell,将源代码加载到了引擎中,下一步是将其转交给JSC引擎,并启动对已加载脚本的处理。在runtime/Completion.cpp中定义的评估函数调用了executeProgram,这是JSC引擎接管并开始处理的地方:

JSValue evaluate(JSGlobalObject* globalObject, const SourceCode& source, JSValue thisValue, NakedPtr < Exception>& returnedException)
{
    VM& vm = globalObject->vm();
   
    //... 为简化代码,此处省略一部分
 
    JSObject* thisObj = jsCast < JSObject*>(thisValue.toThis(globalObject, ECMAMode::sloppy()));
    JSValue result = vm.interpreter->executeProgram(source, globalObject, thisObj);
 
    //... 为简化代码,此处省略一部分
   
    return result;
}

函数Interpreter::executeProgram负责执行三个重要任务,而这些任务也是本文讨论的重点。主要任务包括:

1、启动词法分析和源代码解析;

2、生成字节码;

3、执行字节码。

在函数代码片段中,可以看到上述任务:

JSValue Interpreter::executeProgram(const SourceCode& source, JSGlobalObject*, JSObject* thisObj)
{
    JSScope* scope = thisObj->globalObject()->globalScope();
    VM& vm = scope->vm();
   
    //.. 省略部分代码
 
    ProgramExecutable* program = ProgramExecutable::create(globalObject, source);
   
    //... 为简化代码,此处省略一部分
   
    VMEntryScope entryScope(vm, globalObject);
 
    // Compile source to bytecode if necessary:
    JSObject* error = program->initializeGlobalProperties(vm, globalObject, scope);  < -- 1. Initiates Lexing and Parsing of the source code
   
//... 为简化代码,此处省略一部分
 
    ProgramCodeBlock* codeBlock;
    {
        CodeBlock* tempCodeBlock;
        Exception* error = program->prepareForExecution < ProgramExecutable>(vm, nullptr, scope, CodeForCall, tempCodeBlock);  < -- 2. Generation of bytecode
        //... 为简化代码,此处省略一部分
        codeBlock = jsCast < ProgramCodeBlock*>(tempCodeBlock)

这篇关于JavaScriptCore内部原理(一):从JS源码到字节码的追踪的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/778942

相关文章

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

C#中async await异步关键字用法和异步的底层原理全解析

《C#中asyncawait异步关键字用法和异步的底层原理全解析》:本文主要介绍C#中asyncawait异步关键字用法和异步的底层原理全解析,本文给大家介绍的非常详细,对大家的学习或工作具有一... 目录C#异步编程一、异步编程基础二、异步方法的工作原理三、代码示例四、编译后的底层实现五、总结C#异步编程

在Spring Boot中浅尝内存泄漏的实战记录

《在SpringBoot中浅尝内存泄漏的实战记录》本文给大家分享在SpringBoot中浅尝内存泄漏的实战记录,结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录使用静态集合持有对象引用,阻止GC回收关键点:可执行代码:验证:1,运行程序(启动时添加JVM参数限制堆大小):2,访问 htt

SpringBoot集成Milvus实现数据增删改查功能

《SpringBoot集成Milvus实现数据增删改查功能》milvus支持的语言比较多,支持python,Java,Go,node等开发语言,本文主要介绍如何使用Java语言,采用springboo... 目录1、Milvus基本概念2、添加maven依赖3、配置yml文件4、创建MilvusClient

浅析Java中如何优雅地处理null值

《浅析Java中如何优雅地处理null值》这篇文章主要为大家详细介绍了如何结合Lambda表达式和Optional,让Java更优雅地处理null值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录场景 1:不为 null 则执行场景 2:不为 null 则返回,为 null 则返回特定值或抛出异常场景

JS+HTML实现在线图片水印添加工具

《JS+HTML实现在线图片水印添加工具》在社交媒体和内容创作日益频繁的今天,如何保护原创内容、展示品牌身份成了一个不得不面对的问题,本文将实现一个完全基于HTML+CSS构建的现代化图片水印在线工具... 目录概述功能亮点使用方法技术解析延伸思考运行效果项目源码下载总结概述在社交媒体和内容创作日益频繁的

Node.js 数据库 CRUD 项目示例详解(完美解决方案)

《Node.js数据库CRUD项目示例详解(完美解决方案)》:本文主要介绍Node.js数据库CRUD项目示例详解(完美解决方案),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考... 目录项目结构1. 初始化项目2. 配置数据库连接 (config/db.js)3. 创建模型 (models/

SpringMVC获取请求参数的方法

《SpringMVC获取请求参数的方法》:本文主要介绍SpringMVC获取请求参数的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下... 目录1、通过ServletAPI获取2、通过控制器方法的形参获取请求参数3、@RequestParam4、@

SpringBoot应用中出现的Full GC问题的场景与解决

《SpringBoot应用中出现的FullGC问题的场景与解决》这篇文章主要为大家详细介绍了SpringBoot应用中出现的FullGC问题的场景与解决方法,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录Full GC的原理与触发条件原理触发条件对Spring Boot应用的影响示例代码优化建议结论F

springboot项目中常用的工具类和api详解

《springboot项目中常用的工具类和api详解》在SpringBoot项目中,开发者通常会依赖一些工具类和API来简化开发、提高效率,以下是一些常用的工具类及其典型应用场景,涵盖Spring原生... 目录1. Spring Framework 自带工具类(1) StringUtils(2) Coll