热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:从0开始快速上手WebAssembly:打造高性能安全沙盒

篇首语:本文由编程笔记#小编为大家整理,主要介绍了从0开始快速上手WebAssembly:打造高性能安全沙盒相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了从0开始快速上手WebAssembly:打造高性能安全沙盒相关的知识,希望对你有一定的参考价值。






  作者 | 赵洋


  策划 | 蔡芳芳





通过上一篇文章对 Emscripten 的使用介绍,大家应该具备一定的使用 Emscripten 工具链开发相关 WebAssembly 项目的基础能力了。在本文中,我们将继续通过具体案例更深入地了解 Emscripten 的使用技巧,同时加强对 WebAssembly 二进制格式、Low-Level 编译目标及接近 Native 执行效率的理解。

《WebAssembly 技术基础到实践》专题文章回顾:


https://www.infoq.cn/theme/83



前端核心数据加密

Web 技术的开放以及便捷带来了其极高速的发展,但自作者从事 Web 前端相关开发工作以来,并没有听到太多关于前端核心数据加密的方案,因此“前端数据无加密”慢慢的也变成了业界的一个共识。但在某些日常开发的场景中,我们又会涉及到相当强度的前端核心数据加密的问题,特别是需要在于后端的数据通信上面(包括 HTTP、HTTPS 以及 WebSocket 的数据交换),例如前端日志数据加密就是一个典型的场景。


针对于此类需求,在以往的实践中我们通常使用纯 Javascript 实现的 Crypto 库来对相关数据加密,同时使用 UglifyJS、Google Closure Compiler 等混淆器进行相关加密代码的混淆,以增加对应的破译难度。但在目前的实践之中由于浏览器自身的调试工具逐渐方便且强大,因此此类方法的安全性变得越来越脆弱。


考虑到这种情况,越来越多的公司开始将前端核心数据加密的相关逻辑迁移至 WebAssembly,依靠 WebAssembly 在整个过程中的编译及二进制特性来提高对应数据加密逻辑的安全性,以此保障业务安全。对于这种方案而言,由于我们可以将 WebAssembly 视作一个全新的 VM,其他语言通过相关工具链(例如 Emscripten)来产出此 VM 可执行的代码,其安全性相比于单纯的 Crypto 库加 Javascript 混淆器而言具有比较明显的优势。


现在让我们就着手实现一个非常简单的 WebAssembly 签名加密模块来进行一下对应的体验。首先,创建相关的项目,同时下载 Github 上的某 MD5 实现到本项目的 vendor 文件夹中,整体目录结构如下所示:
















├main.c├CMakeList.txt├vendor ├─base64 │ └─CMakeList.txt │ └─base64.c │ └─base64.h └─md5 └─CMakeList.txt └─md5.c └─md5.h


接着我们依照上一章的 CMake 文件进行稍加修改,增加对此 MD5 库的编译链接,如下所示:
























cmake_minimum_required(VERSION 3.15) # 根据你的需求进行修改project(sample C)
set(CMAKE_C_STANDARD 11) # 根据你的C编译器支持情况进行修改set(CMAKE_EXECUTABLE_SUFFIX ".html") # 编译生成.html
include_directories(${PROJECT_SOURCE_DIR}/vendor) # 使得我们能引用第三方库的头文件add_subdirectory(${PROJECT_SOURCE_DIR}/vendor/md5)add_subdirectory(${PROJECT_SOURCE_DIR}/vendor/base64)
add_executable(sample main.c)
# 设置Emscripten的编译链接参数,我们等等会讲到一些常用参数set_target_properties(sample PROPERTIES LINK_FLAGS "\ -s EXIT_RUNTIME=1 \ -s EXPORTED_FUNCTIOnS=\"['_sign']\"")
target_link_libraries(sample md5 base64) # 将第三方库与主程序进行链接



最后实现我们的数据签名逻辑即可,代码如下:































#include #include "md5/md5.h"#include "base64/base64.h"
const char* salt_key = "md5 salt key";
#ifdef __EMSCRIPTEN__int sign(const char* msg, int len, char *output){ int data_len = strlen(salt_key) + len + 1; char *data = malloc(data_len); memset(data, 0, data_len); memcpy(data, msg, len); memcpy(data + len, salt_key, strlen(salt_key));
uint8_t result[16] = {0}; md5((uint8_t *)data, strlen(data), result);
char *encode_out = malloc(BASE64_ENCODE_OUT_SIZE(16)); base64_encode(result, 16, encode_out); memcpy(output, encode_out, BASE64_ENCODE_OUT_SIZE(16)); free(encode_out);
return 0;}#endif


在这里我们使用了上一章介绍的 Memory Copy 的方式来进行结果数据的传递,因此我们的 Javascript 代码调用应如下所示:










const str = intArrayFromString("your data here");const ptr = allocate(str, "i8", ALLOC_NORMAL);const output = Module._malloc(1024);Module._sign(ptr, str.length, output);console.log(UTF8ToString(output)); // O6VFgoqQiF52FYyH4VmpPQ==


在 sign 的具体实现中我们可以看到,为了增加整体的安全性,我们对其中的内容进行了“加盐”处理。这种操作看起来万无一失,但是实际上我们稍加分析是可以拿到对应的“盐”值的。现在我们将生成的 sample.wasm 文件拖入到编辑器中,然后搜索 md5 salt key,我们可以很顺利的得到对应的“盐”值。




从0开始快速上手WebAssembly:打造高性能安全沙盒

WebAssembly 无法隐藏所有信息


针对于这种情况,我们可以进一步使用异或等算法对“盐”值进行相关的处理来达到更深度的处理,但对于需要更高强度的核心数据保护的应用而言这也不过是叠加的障眼法,在这里我们需要更可靠的方式来达到我们的需求。



沙 盒 保 护

沙盒保护的思路比较直接:为了更好的达到代码整体保护的目的,我们会自行创建一个完全独立的代码执行环境。在这个独立环境中,我们根据我们的需求限制内部代码使用的 built-in API 从而达到完全可控的状态。与此同时,只要我们符合执行代码的相关规范,那么我们可以自行设计相关的 Opcode 及 IR,来达到防逆向的目的。针对于前端环境而言,其整体流程如下所示:


从0开始快速上手WebAssembly:打造高性能安全沙盒


沙盒保护的编译/运行过程


由于整个前端环境大部分都依靠于 Javascript,因此在沙盒环境上我们有非常多的选择,在本章中我们采用 QuickJS 来进行相关的介绍和演示。如果有其他的沙盒环境需求,可以参考本章的相关思路来进行适当的修改。


QuickJS 想必大家都不会陌生,其作者 Fabrice Bellard 编写了 TinyCC/TinyGL/FFmpeg/JSLinux 等多个知名项目。QuickJS 实现精小,几乎完整支持 ECMA2019,同时具有极低的启动速度有优秀的解释执行性能。在使用 QuickJS 完成我们的沙盒之前,我们先简单的了解一下 QuickJS 的相关 API,然后再使用 Emscripten 来对其进行编译完成我们的整个示例。


首先我们对 CMake 进行相关修改,增加对 QuickJS 的编译链接支持,如下所示:















cmake_minimum_required(VERSION 3.15)project(sample C)
set(CMAKE_C_STANDARD 11)include_directories(${PROJECT_SOURCE_DIR}/vendor)add_subdirectory(${PROJECT_SOURCE_DIR}/vendor/quickjs)
add_executable(sample main.c)
target_link_libraries(sample quickjs)


然后我们先通过"Hello World"的输出示例来完成整个项目的初始化,代码如下:
















#include "quickjs/quickjs.h"
int main() { JSRuntime *rt = JS_NewRuntime(); JSContext *ctx = JS_NewContext(rt); JSValue value = JS_Eval(ctx, "'Hello World!'", 12, "", 0); printf("%s\n", JS_ToCString(ctx, value)); // Hello World! JS_FreeContext(ctx); JS_FreeRuntime(rt); return 0;}


现在,对于沙盒内部的 Javascript 代码而言,我们将会暴露出一个名为 crypto 的全局函数,此函数会对传入的字符串进行 MD5 的相关加密,并且返回给外部环境,其内部 Javascript 代码如下所示:
















const MD5_SALT_KEY = 'md5 salt key';function md5(str) { // MD5的相关算法实现 // 此处逻辑你可以引入npm上的相关库 // 然后使用Webpack/Parcel等工具进行编译}
function crypto(str) { str = `${str}${MD5_SALT_KEY}`; return md5(str);}


QuickJS 要调用对应的内部全局 crypto 函数很简单,我们使用 JS_Eval 即可完成:

























#include #include "quickjs/quickjs.h"
const char* JS_CODE = "const MD5_SALT_KEY = 'md5 salt key';\n" "function md5(str) { return str; }\n" "function crypto(str) {\n" " return md5(`${str} ${MD5_SALT_KEY}`);\n" "};";
int main() { JSRuntime *rt = JS_NewRuntime(); JSContext *ctx = JS_NewContext(rt); JS_Eval(ctx, JS_CODE, strlen(JS_CODE), "", 0); JSValue value = JS_Eval(ctx, "crypto('data')", 14, "", 0);
printf("%s\n", JS_ToCString(ctx, value)); // data md5 salt key JS_FreeContext(ctx); JS_FreeRuntime(rt); return 0;}


运行我们的代码,我们可以看到,当调用执行结束后其正确的输出了相关内容。接下来我们对目前的实现加入部分 Emscripten 的胶水代码并进行 WebAssembly 的编译,从而使得我们能从 Web 端或 NodeJS 进行相关的执行,调整后的 CMake 如下所示:






















cmake_minimum_required(VERSION 3.15)project(sample C)
set(CMAKE_C_STANDARD 11)set(CMAKE_EXECUTABLE_SUFFIX ".html") # 编译生成.html
include_directories(${PROJECT_SOURCE_DIR}/vendor)add_subdirectory(${PROJECT_SOURCE_DIR}/vendor/quickjs)
add_executable(sample main.c)
set_target_properties(sample PROPERTIES LINK_FLAGS "\ -s EXIT_RUNTIME=0 \ -s EXPORTED_FUNCTIOnS=\"['_init', '_eval', '_dispose']\"")
target_link_libraries(sample quickjs)


接着,我们调整我们的 C++ 部分代码如下:





































#include #include "quickjs/quickjs.h"
static JSRuntime *rt;static JSContext *ctx;
const char* JS_CODE = "const MD5_SALT_KEY = 'md5 salt key';\n" "function md5(str) { return str; }\n" "function crypto(str) {\n" " return md5(`${str} ${MD5_SALT_KEY}`);\n" "};";
int init(){ rt = JS_NewRuntime(); ctx = JS_NewContext(rt); JS_Eval(ctx, JS_CODE, strlen(JS_CODE), "", 0); return 0;}
int eval(const char *str, int len, char *output){ JSValue value = JS_Eval(ctx, str, len, "", 0); const char *retstr = JS_ToCString(ctx, value); memcpy(output, retstr, strlen(retstr)); JS_FreeValue(ctx, value); return 0;}
int dispose(){ JS_FreeContext(ctx); JS_FreeRuntime(rt); return 0;}


最后我们进行相关编译,然后使用如下代码进行相关的调用:














const datastr = "data";const buffer = intArrayFromString(datastr);const ptr = allocate(buffer, 'i8', ALLOC_NORMAL);
const output = Module._malloc(1024);Module._init();Module._eval(ptr, buffer.length, output);Module._dispose();console.log(UTF8ToString(output)); // output: data md5 salt key


根据上一章的实践我们知道,由于我们的内部 Javascript 代码是以字符串的方式直接进行呈现和编译的,因此如果我们对 Emscripten 编译生成 WASM 文件进行二进制查看的话,我们仍然能还原出我们的实际相关实现。同时根据我们的流程来看,我们首先需要将其 Javascript 内容编译为 Opcode,然后再进行相关的嵌入才较为合理。要想得到 QuickJS 的 Opcode ByteStream 比较简单,直接通过 qjc 即可获得:









> cd quickjs> make all> ./qjsc -c index.js> cat out.c



通过查找 out.c 我们可以看到 qjsc 为我们生成了如下的代码:
















/* File generated automatically by the QuickJS compiler. */
#include
const uint32_t qjsc_index_size = 209;
const uint8_t qjsc_index[209] = { 0x02, 0x06, 0x18, 0x4d, 0x44, 0x35, 0x5f, 0x53, 0x41, 0x4c, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x06, 0x6d, 0x64, 0x35, 0x0c, 0x63, 0x72, 0x79, 0x70, // ......};


其中的 qjsc_index 数组就是我们 Javascript 代码对应的 Opcode ByteStream。接着我们修改我们的代码,然后调整执行的方法,其代码如下:


















const uint8 JS_CODE[209] = { 0x02, 0x06, 0x18, 0x4d, 0x44, 0x35, 0x5f, 0x53, 0x41, 0x4c, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x06, 0x6d, 0x64, 0x35, 0x0c, 0x63, 0x72, 0x79, 0x70, // ......};
int init(){ rt = JS_NewRuntime(); ctx = JS_NewContext(rt); JS_Eval(ctx, JS_CODE, 209, "", JS_EVAL_TYPE_MODULE); return 0;}


最后我们同样进行整体编译,然后尝试相同调用,可以看到其执行正常。同时查看 WASM 文件我们已经无法顺利查看到内部执行 Javascript 代码内容了(已变为 QuickJS 的 Opcode ByteStream)。



总    结

在本章我们较为详细的介绍了前端加密及代码保护的困境,以及如何使用 WebAssembly 并结合 QuickJS 打造高性能安全沙盒的相关介绍,关于 QuickJS 的更进一步的内容可以参考其官方网站。在下一章中,我们将使用 WebAssembly 结合 WebGL 来进行一些图形相关的实践,同时对比其在此领域相比 Javascript 的优劣势。


延伸阅读


https://www.infoq.cn/article/4qgBeSHGipa7A9HAr7DP


https://www.infoq.cn/article/F6aI8wuLYiJ4B3CO7Lvf












  •   











点个在看少个 bug 

推荐阅读
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • MACElasticsearch安装步骤及验证方法
    本文介绍了MACElasticsearch的安装步骤,包括下载ZIP文件、解压到安装目录、启动服务,并提供了验证启动是否成功的方法。同时,还介绍了安装elasticsearch-head插件的方法,以便于进行查询操作。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • 本文介绍了Linux系统中正则表达式的基础知识,包括正则表达式的简介、字符分类、普通字符和元字符的区别,以及在学习过程中需要注意的事项。同时提醒读者要注意正则表达式与通配符的区别,并给出了使用正则表达式时的一些建议。本文适合初学者了解Linux系统中的正则表达式,并提供了学习的参考资料。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • Google在I/O开发者大会详细介绍Android N系统的更新和安全性提升
    Google在2016年的I/O开发者大会上详细介绍了Android N系统的更新和安全性提升。Android N系统在安全方面支持无缝升级更新和修补漏洞,引入了基于文件的数据加密系统和移动版本的Chrome浏览器可以识别恶意网站等新的安全机制。在性能方面,Android N内置了先进的图形处理系统Vulkan,加入了JIT编译器以提高安装效率和减少应用程序的占用空间。此外,Android N还具有自动关闭长时间未使用的后台应用程序来释放系统资源的机制。 ... [详细]
  • 如何提高PHP编程技能及推荐高级教程
    本文介绍了如何提高PHP编程技能的方法,推荐了一些高级教程。学习任何一种编程语言都需要长期的坚持和不懈的努力,本文提醒读者要有足够的耐心和时间投入。通过实践操作学习,可以更好地理解和掌握PHP语言的特异性,特别是单引号和双引号的用法。同时,本文也指出了只走马观花看整体而不深入学习的学习方式无法真正掌握这门语言,建议读者要从整体来考虑局部,培养大局观。最后,本文提醒读者完成一个像模像样的网站需要付出更多的努力和实践。 ... [详细]
author-avatar
我的世界由我做主的围脖_708
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有