typora保护机制与注册逆向分析

、起因

一直比较喜欢Typora的简洁与美观(尝试过用 vscode 搭配插件编辑 markdown 文件,体验还是要差一些的),突然发现自己windows机器上很久前安装的typora不让用了,提示:
 

版本过期


幸好原始安装文件还在,对比一下版本,原始安装文件是 0.9.93 版本,而不让用的是 0.10.11。
在我的 ubuntu22.04 机器上安装有 1.7.4 版本,win10上安装了 1.7.6版本。貌似最新的版本开始收费了,还分国内/国外两个版本。(typora.io 这个官网貌似被墙了,真不知道为了个啥。)
本着一个cracker无知者无畏的折腾到底的精神,开始瞎整!

二、摸底、知识和工具准备

逆向分析的第一步就是要了解目标软件是用啥开发的,架构是啥。
一看 typora.exe 好家伙,100多个M,这么大的可执行程序还分析个啥?IDA分析一下都不知道要多久。
由于目前主用 ubuntu22.04 系统,想着顺便从 0 开始学习使用 ghidra 进行 Linux 平台的逆向分析工作。于是从一个小目标开始,需要瞎搞的知识点有了第一次扩充。

  • ubuntu平台,用ghidra分析 1.7.4 版本;
  • win7平台,用IDA6.8+x64DBG分析0.10.11版本;
  • win10平台,用IDA6.8+x64DBG分析 1.7.6版本。(1.7.6 在我的win7下会出现不能在kernel32里面定位 discardVirtualMemory的错误)

悲催的是用 ghidra 静态分析 1.7.4 版本的 typora 有160多M,分析了10个小时也没结束。这个弯路绕太远了,毫无意义。直接放弃。

正确的打开方式:直接网上搜索
有大神直接用 IDA 分析 typora.exe,提示了一些有用的东东。结合两位成功破解的大神的文章,可以确定 typora 是使用 electron + nodejs 框架,用 javascript 开发的跨平台的桌面程序。(和vscode类似)
所以,啥是 electron/nodejs/javascript?0 基础的暴走,奇怪的知识点需要进行第二次扩充。
这几个每一个都是大部头,越底层的难度越大。被V8引擎绕进去花了不少时间(虽然了解一下好处很多,带着VM的思路去了解,能有不少收获),但就typora的破解来说,只需要涉猎以下内容:

  1. electron app 的目录结构。
  2. asar 打包/解压工具 (npm i -g asar)
  3. node.js: 模块/API/运行环境构建/npm工具;node 命令行环境下运行 js 代码(类似python命令行环境)
  4. V8 引擎:几乎用不到,因为用到了 Addon N-API 接口库。当然了解V8的好处还是很多的。真实的托管类数据结构,都是在V8引擎里面定义的,有详细的参考手册。
  5. node.js Addon:通过 C++ 编写的扩展模块,用于扩展 Node.js 的功能。这个是重点,需要知道怎么写扩展模块的基本逻辑。(至少了解 V8 和 C++ 相互调用变量/函数的一两个例子)
  6. N-API(Node-API)是一组稳定的 C 语言 API,用于编写跨平台的 Node.js Addon。N-API 提供了一套抽象层,使得开发者可以更轻松地编写可移植的扩展模块,而无需担心不同版本的 Node.js 或不同平台之间的兼容性问题。
    这个是需要重点把握的,熟练查阅 nods.js 中 N-API 的文档。
    N-API 实际上是 V8 接口的封装,进行了抽象,但对逆向增加了困难。因为其参数都是一系列 void** 之类的指针,具体在 V8 层进行实际的数据类型转换。所以逆向时,对于托管对象的指针,就别想着查看其数据了。重点应该放在 C++ 的本地数据结构上。

三、聚焦 main.node

node.js Addon 可以在 js 层直接 require 一个 dll/so。electron 入口可能也是 require 引入 package.json 及里面定义的入口 js 文件的。
0.10.11 版本:PEid 查看算法常数,发现 sbox/rsbox,基本可以确定使用了AES算法。只是windows版本去符号,要从 rbox 逆推找到 AES KEY 和取定其 mode 难度还是比较大的。网上也有人分析到这一步放弃的。当然大神可以直接找到 mode CBC iv 和 AES KEY。
1.7.4 linux 版本:神奇的是没有去除符号,但与0.10.11版本不同,KEY 和 mode 的地方进行了混淆处理,不知道把 CBC 的 iv 怎么藏起来了。懒得去反混淆分析了(主要是能力不够)
有大神直接给出了 main.node 保护electron app的概念验证项目:https://github.com/toyobayashi/electron-asar-encrypt-demo
有了第二部分提到的知识准备后,就可以尝试看一下这个项目。当然像我一样 0 基础的,也可以边看边学,借助 N-API 文档和chatGPT的帮助。
简单总结一下这个项目的思路:

  1. 由于 node.js 在 js 层可以 hook api,任意隐藏加解密的手段都会失效,所以要想办法将加解密放到 C++ 层去做。(这里可以只将 license 模块放到 addon 里面实现,估计是太过于明显直接暴露攻击目标,所以选择隐藏和加密入口)
  2. 通过重载 V8::_compile() 函数,实现针对性的解密。具体实现有 global 和 this module 的发现/遍历/重建require函数/require原始入口等问题,不是专家,也没搞懂,就破解来说,也不需要搞懂,根据关键字定位 main.node 里的关键函数就行。

结合V8执行js的基本逻辑来理解上面的思路:

1

2

3

4

5

6

7

// 定义一个 JavaScript 代码字符串

  V8::Local<V8::String> source_code =

      V8::String::NewFromUtf8(isolate, "'Hello, ' + 'World!'").ToLocalChecked();

  // 编译和运行 JavaScript 代码

  V8::Local< V8::Script> script = Script::Compile(context, source_code).ToLocalChecked();

   V8::Local< V8::Value> result = script->Run(context).ToLocalChecked();

V8执行js代码有两个过程,Compile 和 Run。Compile 只接受 V8 的托管字符串(js代码),而 C++ 字符串需要在 isolate(V8的一个独立的运行时虚拟机容器)内创建一个托管的 V8::String,用 N-API 库的话,相应函数是 napi_create_string_utf8(...)。
所以,既然把入口 js 的代码放到 C++ 层加解密,那么要执行它,比然经过上面的三步。重载 _compile 函数就是为了在这一步,给传入的 C++ string(js code)进行解密并创建对应的托管String,以便传给原来的 compile 函数。
理解了这个过程,那么最快、最容易的获取解密后的入口 js code,就在这个过程中。有两个思路,后面介绍。

四、分析 main.node

1、找入口:

  • 0.10.11 windows版本,用IDA查看 exports 导出表,然后一路跟踪下来:

    exports

     

    _register_main

     

    main_module_register

     

    _napi_main

     

    _init


    addon 开发最后一个宏 NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) 实现的导出绑定,实际上就是调用 napi_module_register 注册了一个module,这个module结构包含一些源代码开发环境的一些信息,和一个__napi_main,在__napi_main中传递了 init 函数的地址。
  • 1.7.6 win10版本,导出表里面只有一个 DllEntryPoint。需要手动在 imports 里面找 napi_module_register,剩下的流程和上面一样。可以定位到 sub_18000492B 就是 init 入口。

    1.7.6 init

  • 1.7.4 linux版本,用ghidra查看发现 exports 几乎导出了所有的符号名。不过作者还是遵循了 _init 这个函数名。

实际上找入口这一步是多余的,有了源代码只要结合关键字符串 _compile 和相应的函数调用,就能很快定位到 init 函数。只是作为一个cracker对流程跟踪的执念,瞎整。

2、找_compile的重载函数:跟着源代码中的关键字符串找,很容易定位

1

2

3

4

5

6

7

module_prototype.DefineProperty(

  Napi::PropertyDescriptor::Function(env,

    Napi::Object::New(env),

    "_compile",

    ModulePrototypeCompile,    // 就是这个函数

    napi_enumerable,            // 实际这个传递参数的地方做了一个wrapper。

    addon_data));

3、作者的 trick 一。

疑似的ModulePrototypeCompile这个函数往里面看的时候,和源代码完全不一致:
 

wrapper of ModulePrototypeCompile


当时也懵了,因为自己是在windows系统上用IDA完全对照源码静态分析。使用静态分析工具查看 refrence to 和 refrence from 都连不上。无奈之下启用 x64DBG 调试。(找到真实的ModulePrototypeCompile函数还是很容易的,因为根据源代码,这个函数里面有非常明显的字符串"app.asar",而且是一个对称的if ... else ...结构。只是想弄清楚开发者到底怎么魔改的。)最后发现真实的ModulePrototypeCompile地址被打包在了上面替换函数的参数里面。调用变成了 call qword ptr [r8],从而避免了静态分析时候被一撸到底。实际的trick是这样的:
 

ModulePrototypeCompile


直到分析 1.7.4 linux版本的时候,就看的很清楚了。
 

ModulePrototypeCompile in Linux


两者结合可以清楚看到多了一层 Wrapper 把真实函数地址隐藏在 Wrapper的参数里面。

4、AES解密

后面的部分对于 0.10.11 windows版本来说,和源代码几乎一模一样,就是AES解密的过程,没有源代码那么多函数层级,中间的函数调用估计被 inline 了。但基本结构是一样的。CBC 模式的 iv 被藏在了实际chunck的最前面16个字节。
对于 1.7.4 Linux 版本整体架构不变,但 getKey 和 getIV 做了混淆,IV 怎么藏的也没看明白。尝试用函数的返回值,参照老版本的格式进行解密,发现是错误的。对混淆没什么能力,还是绕路吧。毕竟最终目标是去 main.node 运行。

五、获取源代码/解密app.asar

1.     0.10.11 windows版本:

AES KEY


可以直接解密 app.asar(因为打包了几乎所有重要 js 文件,所以一个个调试获取源码也麻烦。)
其AES KEY就编码在指令中,无论是静态获取还是调试 getKey 处获取都可以。
IV 是放在chunck前的16个字节。根据这两个条件,可以直接编写代码对 app.asar 进行解密。

2.     1.7.4 linux 和 1.7.6 win10版本:

因为看不懂 key 和 iv 的混淆,只能骚操作——直接调试获取 atom.js 的源代码。(只加密了这一个文件,估计是要保证启动速度)
在第三部分的最后,卖了一个关子,说到有两种方式可以获取解密后的 atom.js 入口,这里介绍一下:
(1)其实这个main.node加密框架本身就提供了获取方法,既然能重载_compile,在调用原始_compile之前对js code 进行解密,那么这个框架本身,也可以被cracker用来再次重载_compile,等着后续的main.node将解密好的 js code 传过来,然后 console.log 就可以了。
我不是搞开发的,看这个框架要配置编译环境貌似挺复杂的,留给有electron开发经验的朋友吧。这个方法的好处是:一次投入,终身享受。只要还使用这个框架,点下鼠标就能获得源代码。(当然typora作者也是有备而来,后面会讲到)
(2)napi_create_string_utf8,这个函数接受 C++ 字符串,可以直接从 GDB 或者 x64DBG 中把参数的值 dump 出来。不过因为调用这个函数的地方很多,需要手动定位 ModulePrototypeCompile 函数解密分支里面的那个,下断点。Decrypt函数直接返回了托管字符串,所以还需要深入进去找到正确的返回前最后一个调用 napi_create_string_utf8 的地方。

1

iVar4 = napi_create_string_utf8(pnVar15,&DAT_00145b42,0,ppvVar14);

当然大神使用 frida 动态挂钩 napi_create_string_utf8 这种骚操作,咱小白也不懂。

六、去 main.node 运行

优点是可以保持最快的加载速度,相当于完全开源了。

1、0.10.11版本

根据0.9.93版本的目录架构,将解密后的文件丢到 typora/resources/app/ 目录下,其他保持不变,就可以直接运行了。

2、1.7.4 linux 和 1.7.6 win10版本

使用解密后的 atom.js 按照 0.10.11 版本那样直接运行 win10 会弹窗提示 scheme 变量没有定义;linux 下有 typora 进程,不提示错误,需要用 GDB 调试运行才能知道,错误是一样的。这又是闹哪一出?

  • 找不到暗桩怎么办?
    一开始不能确定导致变量没定义的原因,可能在 js 层,也可能在 main.node 层。js 层搜索了一圈没啥发现,感觉暗桩及有可能还是在 main.node 里面。想个办法验证一下:
    • patch 函数 ModulePrototypeCompile 里面对于加密/非加密 js code 的 if ... else... 逻辑,都转向非加密的逻辑。
    • 将解密出来的 atom.js 打包成 app.asar。
    • 可以成功运行
      其实完成这一步,也可以实现破解了,后面注册逻辑就都是 js 层面的事情了。不过对于一个喜欢瞎折腾的 cracker,做到这个程度,不是很满意,于是继续瞎整。。。
  • 作者的 trick 二
    在 js 层搜索一下 scheme 没有特殊的用法,对照 0.10.11版本,scheme 是定义一个自定义协议解析的字段,其值为 typora,可以使用 typora://xxx 这样的协议进行处理。
    在 main.node 里面全局搜索 scheme 啥也没发现,一时陷入僵局,作者显然是埋了一个深坑。因为是在 win10 下分析的去符号的版本,所以一时没有头绪。后来决定换个思路,atom.js 最后启动窗口肯定是要加载一个 html 的,这就是 typora 程序的入口。按照这个逻辑 发现了 “entry” 字符串/变量,其值应该是 window.html。现在有了两个键值对 scheme --> typora 和 entry --> 包含window.html。用这4个字符串去IDA搜索交叉引用,可以很快找到一个函数 sub_180012290。分析这个函数,实际一共埋了5个暗桩:addonPath,entry,scheme,shost,shostc。

这5个暗桩,有的是隐藏了键名,有的是隐藏了键值。

  • 对键名的隐藏:

    KEY NAME


    这是键值对 addonPath :app.asar。键名 addonPath 的ascii码有些字符做了 + 0x0a 处理。对键值 app.asar 没有处理,就是字符串引用。
  • 对键值的隐藏,1.7.6 win10版本似乎是花了功夫,进行了比较复杂的处理,似乎还加了混淆。(懒得去深入研究,主要是能力不够)还是看 1.7.4 linux版本的比较直观。

    KEY value


    这是键值对 entry :typora://app/typemark/window.html。键名没有加密,键值的处理方式很直观。当然 shost 的值藏的更复杂一些,主要是将单字节的 ascii 变成了4直接的 int。不一一列出来了。
  • 最后的问题,这些键值对加到哪里去了?
    安装函数linux版本名字是 _initGlobal。当然无论哪个版本,这个函数一开始的 napi_get_global 函数说明了一切。都加到 js 层的全局对象 global 里面了。由于我们要去 main.node 运行,5个暗桩实际上只有后面4个有用。

1

2

3

4

5

6

napi_get_global(env, global);

global['addonPath'= 'app.asar'

global['entry'= 'typora://app/typemark/window.html'

global['scheme']='typora'

global['shost'= 'https://store.typora.io'

global['shostc'= 'https://dian.typora.com.cn'

1.7 版本的 js 使用了 webpack 打包,main.node init 最后的入口调用有两步:

1

2

const a = require('./atom.js');        // require js 貌似一定要带路径,不然找不到模块,js 也不懂,唉

a();            // 从 webpack 打包的 atom.js 看,这是一个空函数。反正main.node 调用了,那就照葫芦画瓢吧。

有了上面这些信息,单独写一个 main.js 把4个全局变量加进去,按步骤调用入口就能实现去 main.node 运行了。

七、注册思路

这些属于 js 层面的东东,还是经过 webpack 打包和混淆过的。对于 javascript 小白来说,看这种代码和天书差不多。找了个debundle工具,能还原未加密的 dist/static/js/ 里面的文件,但还原 atom.js 报错。最后找了个 webcrack 工具,效果是比较好的格式化了 atom.js,比vscode js fommatter 之类的强。果断祭出大杀器——chatGPT。一开始心太黑,直接把整个 atom.js 复制给 chatGPT,想让它给格式化代码,并给出解释。结果 chatGPT 直接给了一个长长的菜谱,真的是用心良苦。

1、0.10.11版本

0.x版本应该都是作者发布的开发测试版本。而0.10版本很可能是作者第一个使用 main.node 进行加密的验证版本,因为 js 里面连个注册页面都没有,只有简单的验证逻辑和时间校验,超时不让用。
让 0.10.11版本重新跑起来也很间,直接将 License.js 里面表述注册与否的变量的初始值从 null 改成 true 就可以了。

1

hasLicense = true,  // null,

2、1.7.4 linux 和 1.7.6 win10版本。

这两个版本在 main.node 里面对 AES KEY 和 IV,以及暗桩的处理上,有一些差异,win10版本的更复杂和强化了一些。js 层上,关于注册的 445 类,对win32系统有着更加强化的校验。1.7.4linux版本,似乎是可以无限试用的,超时验证逻辑存在bug。
js 分析后,确定 445 类是处理所有注册/校验相关的。联网校验优先,涉及数据结构:

1

SLicense 的初步结构:base64(RSA.encrypt(json2str({license:xxx, email:xxx, type:xxx...})))#failCounts#lastRetryDate

注册信息会用服务端私钥加密,本地用公钥解密。再进行 email 和 license 的校验。本地注册也是这个解密验证过程。网络验证不成功,会清空 SLicense,回到未注册版本。
由于公钥解密的介入,想要完美本地注册已经没有意义。想持续使用的思路大致两个(没仔细研究,可能还有坑,看个大概齐吧):

(1)无限试用:

手动修改 IDate(install date) 时间。

  • win10 版本在注册表 HKCU\Software\Typora\IDate。
  • linux 版本在 ~/.config/typora/{hash_machine_id} 文件里面。文件格式只是将 json 文件的 ascii 每个字节编成 16 进制文本保存。用 Buffer.from(dataStr, 'hex').toString() 就可以解码。单独改这里貌似也会有问题的,因为会用 ~/.config/typora/profile.data 文件的创建时间恢复 IDate。不过由于时间校验对linux版本的bug存在,似乎不用改,也可以一直免费用。(有可能是我对 js 的理解不够全面,不保证对)
(2)patch js code:
  • 去掉RSA解密,其他保持不变,可以实现手动注册;然后将 shostc 改成和 shost 一样,利用GFW让网络注册失效。
  • 直接 patch 掉整个注册校验过程。反正要patch,不如彻底一些,一劳永逸。

八、加固建议

由于采用 main.node 这种框架把入口的解密放到 C++ 层,那么不可避免的,框架本身可以用来直接获取解密后的 js code,或者是绕不开字符串从 C++ 到 V8容器托管的转换过程。这是 electron 项目保护最大的难点。
作者也进行了很多努力:AES 关键的 key 和 iv 进行了隐藏和混淆;埋了一些暗桩并加密。但用前面的各种骚操作都可以绕过。
个人建议既然埋暗桩,与其花经历在几个全局变量的名字和值上进行各种花式操作,还不如把 445 注册校验类整体/部分放到 main.node 中。至少尽可能让不能脱离 main.node 运行。底层还能使用各种成熟的二进制保护技术,甚至可以引入虚拟机保护,增加逆向成本。

九、后记

typora风格的markdown编辑器,好像github上有开源的了,有道笔记的 markdown笔记似乎也改成typora这种所见即所得风格了(不过上图要付费,或者自己注册一个免费图床)。选择面变多了。不过typora依然是桌面最优雅的。
整个逆向分析过程学到了不少东西,毕竟几乎整个是从 0 开始学起的。
在 ubuntu 平台使用 ghidra 进行静态分析和动态调试;
对面向对象有了更深层次的理解;
托管与非托管的相互调用,从C#、V8 js、python,有点理解python作为胶水语言在这种场景下为什么强大;发现 js 后面的 AST 是一条不归路啊。
对框架的理解,在逆向中的作用越来越重要了。现在的程序似乎都依赖于某个框架。

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

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

相关文章

C++ 模板保姆级详解——template<class T>(什么是模板?模板分哪几类?模板如何应用?)

目录 一、前言 二、 什么是C模板 &#x1f4a6;泛型编程的思想 &#x1f4a6;C模板的分类 三、函数模板 &#x1f4a6;函数模板概念 &#x1f4a6;函数模板格式 &#x1f4a6;函数模板的原理 &#x1f4a6;函数模板的实例化 &#x1f34e;隐式实例化 &#x1f349;显式实…

Jupyter notebook 无法链接内核、运行代码

问题来源 今天想在 vscode 上使用 Jupyter notebook 跑 Python 代码&#xff0c;但无法使用&#xff0c;提示要升级内核。 Running cells with base requires the ipykernel package to be installed or requires an update. 其实这个问题存在好一段时间了&#xff0c;不过之前…

使用visualStudio发布可执行文件

编译成功后会在程序项目的路径下创建一个debug文件夹和一个release文件夹 文件夹中的具体文件入下所示 生成32位的可执行文件 32位的可执行文件可以在64位的计算机中执行&#xff0c;而64位的操作系统程序只能在64位的计算机中执行安装运行库的安装包根据电脑的版本选择合适的…

NLP实战命名实体识别

文章目录 一、导入相关包二、加载数据集三、数据预处理四、创建模型五、创建评估函数六、配置训练参数七、创建训练器八、模型训练九、模型预测 一、导入相关包 DataCollatorForTokenClassification 用于 Token 级别的分类任务 import evaluate from datasets import load_da…

tensorboard报错解决:No dashboards are active for the current data set

版本&#xff1a;tensorboard 2.10.0 问题&#xff1a;文件夹下明明有events文件&#xff0c;但用tensorboard命令却无法显示。 例如&#xff1a; 原因&#xff1a;有可能是文件路径太长了&#xff0c;导致系统无法读取文件。在win系统中规定&#xff0c;目录的绝对路径不得超…

WebSocket网络协议

二十六、WebSocket 26.1 介绍 WebSocket是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工通信&#xff0c;浏览器和服务器只需要完成一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c;并进行双向数据传输。 HHTP协议和WebSocket协议对比&#xff…

零基础算法还原01以及使用python和JS还原C++部分细节

题目一 使用jadx 打开algorithmbase_10.apk JAVA层 使用Frida获取先生成的随机字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // 定义一个名为hook_js的JavaScript函数 function hook_js(){ // 使用Java.perform()函数来…

微信支付服务商消费者投诉及时处理与商户违规及时通知,支持多服务商

大家好&#xff0c;我是小悟 微信直连商户处理消费者投诉的功能解决了很多商户对于投诉处理不及时而导致商户号出现异常的问题&#xff0c;可以说解决了实实在在的问题。 很多小伙伴私信说自己是服务商角色&#xff0c;也需要微信支付服务商处理消费者投诉的功能&#xff0c;…

JS移动端触屏事件

在我们PC端中有许多的事件&#xff0c;那我们在移动端有没有事件呢&#xff1f;让我为大家介绍一下移动端常用的事件&#xff0c;触屏事件 触屏事件 touch (也称触摸事件)&#xff0c;Android 和IOS 都有 touch 对象代表一个触摸点。触摸点可能是一根手指&#xff0c;也可能是一…

加班把数据库重构完毕

加班把数据库重构完毕 本文的数据库重构是基于 clickhouse 时序非关系型的数据库。该数据库适合存储股票数据&#xff0c;速度快&#xff0c;一般查询都是 ms 级别&#xff0c;不需要异步查询更新界面 ui。 达到目标效果&#xff1a;数据表随便删除&#xff0c;重新拉数据以及指…

Matter学习笔记(2)——数据模型和设备类型

一、设备数据模型 Matter 中的设备具有明确定义的 数据模型(DM)&#xff0c;它是设备功能的分层建模。使用 属性(Attribute)、命令(Command) 和 事件(Event) 的概念描述 Matter 节点支持的远程操作&#xff0c;并分组为称为集群的逻辑块。Matter 应用集群规范中包含的集群具有…

POJ 3254 Corn Fields 状态压缩DP(铺砖问题)

一、题目大意 我们要在N * M的田地里种植玉米&#xff0c;有如下限制条件&#xff1a; 1、对已经种植了玉米的位置&#xff0c;它的四个相邻位置都无法继续种植玉米。 2、题目中有说一些块无论如何&#xff0c;都无法种植玉米。 求所有种植玉米的方案数&#xff08;不种植也…

【Java 进阶篇】JQuery DOM操作:轻松驾驭网页内容的魔法

在前端开发的舞台上&#xff0c;DOM&#xff08;文档对象模型&#xff09;是我们与网页内容互动的关键。而JQuery作为一个轻量级的JavaScript库&#xff0c;为我们提供了便捷而强大的DOM操作工具。在本篇博客中&#xff0c;我们将深入探讨JQuery的DOM内容操作&#xff0c;揭开这…

外星人笔记本键盘USB协议逆向

前言 我朋友一台 dell g16 购买时直接安装了linux系统&#xff0c;但是linux上没有官方的键盘控制中心&#xff0c;所以无法控制键盘灯光&#xff0c;于是我就想着能不能逆向一下键盘的协议&#xff0c;然后自己写一个控制键盘灯光的程序。我自己的外星人笔记本是m16&#xff…

阿里系APP崩了?回应来了!

最近&#xff0c;阿里云遭遇了一场可怕的疑似故障&#xff0c;引起了广泛的关注和热议。各种消息纷传&#xff0c;阿里云盘崩了&#xff0c;淘宝又崩了&#xff0c;闲鱼也崩了&#xff0c;连钉钉也不幸中招。这一系列故障让人不禁发问&#xff1a;阿里系的APP都崩了&#xff0c…

【Unity每日一记】“调皮的协程”,协程和多线程的区别在哪里

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

msvcp120.dll丢失的6种解决方法,教你如何修复dll文件丢失

“找不到msvcp120dll,无法继续执行代码的6个修复方案”。我相信很多朋友在运行某些程序时&#xff0c;可能会遇到这样的错误提示&#xff1a;“找不到msvcp120dll&#xff0c;无法继续执行代码”。那么&#xff0c;msvcp120dll究竟是什么&#xff1f;为什么会丢失呢&#xff1f…

发布订阅者模式(观察者模式)

目录 应用场景 1.结构 2.效果 3.代码 3.1.Main方法的类【ObserverPatternExample】 3.2.主题&#xff08;接口&#xff09;【Subject】 3.3.观察者&#xff08;接口&#xff09;【Observer】 3.4.主题&#xff08;实现类&#xff09;【ConcreteSubject】 3.5.观察者&a…

qemu 之 uboot、linux 启动

目录 编译uboot、kernel 编译启动从 uboot 中引导启动 linux注参考 本文主要说明 arm64 在 qemu 上的相关启动。 编译 使用的是 qemu-8.1.1 版本&#xff0c;编译命令如下: ../configure --cc/usr/local/bin/gcc --prefix/home/XXX/qemu_out --enable-virtfs --enable-slir…

Three.js——基于原生WebGL封装运行的三维引擎

文章目录 前言一、什么是WebGL&#xff1f;二、Three.js 特性 前言 Three.js中文官网 Three.js是基于原生WebGL封装运行的三维引擎&#xff0c;在所有WebGL引擎中&#xff0c;Three.js是国内文资料最多、使用最广泛的三维引擎。既然Threejs是一款WebGL三维引擎&#xff0c;那么…