二进制签名查找器(Aho-Corasick 自动机):设计思路与实现原理(C/C++代码实现)

在逆向工程、恶意软件分析和二进制文件解析领域,快速准确地识别特定字节模式(即“签名”)是一项核心任务。本文将围绕一款基于PE-bear工具的二进制签名查找器,深入解析其设计思路、实现原理及相关技术背景,揭示其如何高效解决带通配符的多模式匹配问题。

一、功能概述:什么是二进制签名查找器?

二进制签名查找器的核心功能是在一段二进制数据(如PE文件、内存镜像等)中,快速定位符合预设“签名”的字节序列。这里的“签名”不仅包括固定的字节模式,还支持通配符(用?表示),可灵活匹配“部分固定、部分可变”的字节序列。

例如,签名40 ?? 4? 8? e?表示:

  • 第一个字节必须为0x40
  • 第二个字节任意(??);
  • 第三个字节的高4位为0x44?);
  • 第四、五个字节的高4位分别为0x80xe8?e?)。

该工具支持单模式匹配、多模式同时匹配,还可从外部文件(SIG格式)加载签名列表,广泛应用于:

  • 逆向工程中识别已知函数序言(如0x55 0x48 0x8B 0xec是x64函数的典型序言);
  • 恶意软件分析中检测特征码;
  • PE文件解析中定位特定结构(如导入表、节表)。

二、核心设计思路:从“朴素匹配”到“智能优化”

该工具的设计围绕“高效匹配”展开,通过对比“朴素匹配”与“基于树结构的多模式匹配”,最终选择后者作为核心方案。其设计思路可总结为三个关键目标:

1. 解决多模式匹配的效率问题

朴素匹配(Naive Matching)的思路是:对每个待匹配的模式,逐个扫描二进制数据,通过memcmp对比是否匹配。例如,若要同时查找10个模式,需对数据进行10次完整扫描,时间复杂度为O(k*n)k为模式数量,n为数据长度)。当模式数量增加或数据量庞大时(如GB级内存镜像),这种方式效率极低。

为解决此问题,工具采用了多模式匹配算法,核心是通过“一次扫描数据,匹配所有模式”,将时间复杂度优化为O(n + m + z)m为所有模式总长度,z为匹配结果数量)。

2. 支持通配符的灵活匹配

二进制数据中,许多特征模式并非完全固定(如包含动态计算的地址、版本号)。例如,某函数调用的指令为E8 ?? ?? ?? ??E8是调用指令,后4字节为相对偏移,每次运行可能不同)。

工具通过“掩码(Mask)”机制支持通配符:为每个签名定义一个与字节序列等长的掩码,掩码中0xFF表示对应字节需精确匹配,0x00(或其他值)表示该字节为通配符(可忽略)。例如,签名55 0? 34 12的掩码为0xFF 0x0F 0xFF 0xFF,表示第二个字节的低4位可变。

3. 兼顾易用性与可扩展性

工具设计了清晰的签名管理机制:

  • 支持直接在代码中定义签名(如addPattern方法);
  • 支持从文本格式解析签名(如loadFromByteStr方法解析"40 ?? 4? 8? e?");
  • 支持从外部SIG文件加载签名(包含名称、长度和字节定义),便于动态更新签名库。

三、实现原理:基于Aho-Corasick自动机的匹配机制

工具的高效匹配能力源于对Aho-Corasick自动机的应用。这是一种专为多模式匹配设计的算法,本质是通过构建“前缀树+失败链接”,实现一次扫描完成所有模式的匹配。

1. 核心数据结构:前缀树(Trie)与节点(Node)

  • 前缀树:将所有模式的字节序列按前缀共享原则构建成树。例如,模式CATCATC可共享前缀C-A-T,树中每个节点代表一个字节,路径代表模式的前缀。
  • 节点(Node):每个节点存储:
    • 子节点指针(指向后续字节);
    • 失败链接(用于匹配失败时跳转,类似KMP算法的“部分匹配表”);
    • 匹配到的模式列表(若当前节点是某模式的结束,则记录该模式)。

2. 算法执行步骤

(1)构建前缀树

将所有待匹配的签名(模式)插入前缀树。例如,插入"CAT""CATC"时,先插入C->A->T(标记CAT为匹配模式),再从T延伸出C(标记CATC为匹配模式)。

(2)构建失败链接

失败链接的作用是:当匹配到当前节点后,若下一个字节不匹配,可通过失败链接跳转到“最长公共后缀”节点,避免重新扫描前缀。例如,若当前匹配到C-A-T,下一个字节不是C,则通过失败链接跳转至其他以T为结尾的前缀(如BATT节点),继续匹配。

(3)扫描数据并匹配

对输入的二进制数据进行一次遍历:

  • 从根节点开始,根据当前字节沿子节点移动;
  • 若子节点不存在,通过失败链接跳转,重复此过程;
  • 每到达一个节点,检查其关联的模式列表,记录所有匹配结果(偏移量、模式名称等)。

3. 通配符的处理机制

在Aho-Corasick框架中,通配符通过“模糊匹配”实现:当比较当前字节与节点字节时,若签名中该位置为通配符(掩码对应位置为0x00),则直接视为匹配,无需检查实际字节值。例如,对于模式40 ?? 4?,第二个字节任意匹配,第三个字节仅检查高4位是否为0x4

四、关键技术点与领域知识

1. 模式匹配算法的选择

算法适用场景时间复杂度工具中的应用
朴素匹配单模式、短数据O(n*m)作为基准测试(naive_search函数)
KMP单模式、长数据O(n + m)未直接使用,但其“部分匹配”思想被失败链接借鉴
Aho-Corasick多模式、任意数据量O(n + m + z)核心算法(tree_searchtree_count函数)

工具通过对比测试(如naive_searchtree_search的结果一致性验证),证明了Aho-Corasick在多模式场景下的效率优势。

2. 二进制签名的工程化表示

工具将签名抽象为Signature类,包含:

  • 名称(用于标识模式,如“ASProtect v1.1 MTEc”);
  • 字节序列(原始字节数组);
  • 掩码(通配符定义);
  • 辅助方法(如toByteStr将字节序列转为字符串,loadFromFile从SIG文件加载)。

这种抽象使签名的管理、解析和匹配解耦,便于扩展(如支持新的通配符格式)。

3. 相关领域背景

  • 逆向工程:函数序言(Prolog)、指令序列等固定模式是识别代码结构的关键,工具中patterns32patterns64即预定义了x86/x64架构的常见函数序言。
  • 恶意软件分析:特征码(Signature-based Detection)是传统杀毒软件的核心技术,工具可通过加载病毒特征库快速定位恶意代码。
  • PE文件解析:PE文件的头部、节表、导入表等结构有固定签名(如MZ头、PE\0\0标记),工具可用于定位这些结构。

五、测试设计:确保正确性与可靠性


namespace sig_finder {inline size_t find_all_matches(Node& rootN, const BYTE* loadedData, size_t loadedSize, std::vector<Match>& allMatches){if (!loadedData || !loadedSize) {return 0;}rootN.getMatching(loadedData, loadedSize, allMatches, false);return allMatches.size();}inline Match find_first_match(Node& rootN, const BYTE* loadedData, size_t loadedSize){Match empty;if (!loadedData || !loadedSize) {return empty;}std::vector<Match> allMatches;rootN.getMatching(loadedData, loadedSize, allMatches, true);if (allMatches.size()) {auto itr = allMatches.begin();return *itr;}return empty;}};...namespace sig_finder {class Node{public:Node(): level(0), val(0), mask(MASK_IMM),wildcard(nullptr), immediates(0x100),partialsL(0x10), partialsR(0x10),sign(nullptr){}Node(BYTE _val, size_t _level, BYTE _mask): val(_val), level(_level), mask(_mask),wildcard(nullptr), immediates(0x100), partialsL(0x10), partialsR(0x10),sign(nullptr){}~Node(){clear();}void clear(){_deleteChildren(immediates);_deleteChildren(partialsL);_deleteChildren(partialsR);if (wildcard) delete wildcard;wildcard = nullptr;if (sign) delete sign;sign = nullptr;}bool isEnd(){return (!immediates.size() && !partialsL.size() && !partialsR.size() && !wildcard) ? true : false;}bool isSign(){return sign ? true : false;}size_t addPatterns(const std::vector<Signature*>& signatures){size_t loaded = 0;for (auto itr = signatures.begin(); itr != signatures.end(); ++itr) {const Signature* sign = *itr;if (!sign) continue;if (this->addPattern(*sign)) {loaded++;}}return loaded;}bool addPattern(const char* _name, const BYTE* pattern, size_t pattern_size, const BYTE* pattern_mask = nullptr);bool addTextPattern(const char* pattern1){return addPattern(pattern1, (const BYTE*)pattern1, strlen(pattern1));}bool addPattern(const Signature& sign){return addPattern(sign.name.c_str(), sign.pattern, sign.pattern_size, sign.mask);}size_t getMatching(const BYTE* data, size_t data_size, std::vector<Match>& matches, bool stopOnFirst, bool moveStart = true);private:Node* getNode(BYTE _val, BYTE _mask);Node* addNext(BYTE _val, BYTE _mask);Node* _findInChildren(ShortMap<Node*>& children, BYTE _val){if (!children.size()) {return nullptr;}Node* next = nullptr;children.get(_val, next);return next;}bool _followMasked(ShortList<Node*>* level2_ptr, Node* curr, BYTE val, BYTE mask){Node* next = curr->getNode(val, mask);if (!next) {return false;}return level2_ptr->push_back(next);}void _followAllMasked(ShortList<Node*>* level2_ptr, Node* node, BYTE val){_followMasked(level2_ptr, node, val, MASK_IMM);_followMasked(level2_ptr, node, val, MASK_PARTIAL_L);_followMasked(level2_ptr, node, val, MASK_PARTIAL_R);_followMasked(level2_ptr, node, val, MASK_WILDCARD);}void _deleteChildren(ShortMap<Node*>& children){size_t startIndx = children.start();size_t endIndx = startIndx + children.maxSize();for (size_t i = startIndx; i < endIndx; i++) {Node* next = nullptr;if (children.get(i, next)) {delete next;children.erase(i);}}}Signature* sign;BYTE val;BYTE mask;size_t level;ShortMap<Node*> immediates;ShortMap<Node*> partialsL;ShortMap<Node*> partialsR;Node* wildcard;};}; 
...
void print_results(const char desc[], size_t counter, size_t timeInMs)
{float seconds = ((float)timeInMs / 1000);float minutes =((float)timeInMs / 60000);std::cout << desc << ":\n\t Occ. counted: " << std::dec << counter << " Time: "<< timeInMs << " ms.";if (seconds > 0.5) {std::cout << " = " << seconds << " sec.";}if (minutes > 0.5) {std::cout << " = " << minutes << " min.";}std::cout << std::endl;
}size_t find_matches(Node &rootN, BYTE loadedData[], size_t loadedSize, const char desc[], bool showMatches, bool showHexPreview = false)
{std::string inputStr((char*)loadedData);std::cout << "Input: " << inputStr << " : " << loadedSize << std::endl;std::vector<Match> allMatches;DWORD start = GetTickCount();size_t counter = find_all_matches(rootN, loadedData, loadedSize, allMatches);DWORD end = GetTickCount();for (auto itr = allMatches.begin(); itr != allMatches.end(); ++itr) {Match m = *itr;if (showMatches) {std::cout << "Match at: " << std::hex << m.offset << ", size: " << m.sign->size() << " : \"" << m.sign->name << "\"";if (showHexPreview) {std::cout << "\t";show_hex_preview(loadedData, loadedSize, m.offset, m.sign->size());}std::cout << std::endl;}}print_results(desc, counter, (end - start));return counter;
}size_t count_patterns(BYTE* buffer, size_t buf_size, BYTE* pattern_buf, size_t pattern_size)
{size_t count = 0;for (size_t i = 0; (i + pattern_size) < buf_size; i++) {if (memcmp(buffer + i, pattern_buf, pattern_size) == 0) {count++;}}return count;
}size_t naive_count(BYTE* loadedData, size_t loadedSize)
{t_pattern patterns[_countof(patterns32) + _countof(patterns64) + 1] = { 0 };// init patterns list:size_t i = 0;for (size_t k = 0; k <_countof(patterns32); k++, i++){const t_pattern& p = patterns32[k];patterns[i].ptr = p.ptr;patterns[i].size = p.size;}for (size_t k = 0; k < _countof(patterns64); k++, i++){const t_pattern& p = patterns64[k];patterns[i].ptr = p.ptr;patterns[i].size = p.size;}const char text_pattern[] = "module";patterns[i].ptr = (BYTE*)text_pattern;patterns[i].size = strlen(text_pattern);// search through the list:size_t counter = 0;DWORD start = GetTickCount();for (DWORD i = 0; i < _countof(patterns); i++) {const t_pattern& p = patterns[i];if (!p.ptr) continue;counter += count_patterns(loadedData, loadedSize, p.ptr, p.size);}DWORD end = GetTickCount();print_results(__FUNCTION__, counter, (end - start));return counter;
}size_t tree_count(BYTE* loadedData, size_t loadedSize)
{Node rootN;// init patterns list:for (size_t i = 0; i < _countof(patterns32); i++) {const t_pattern& pattern = patterns32[i];std::string name = "prolog32_" + std::to_string(i);rootN.addPattern(name.c_str(), pattern.ptr, pattern.size);}for (size_t i = 0; i < _countof(patterns64); i++) {const t_pattern& pattern = patterns64[i];std::string name = "prolog64_" + std::to_string(i);rootN.addPattern(name.c_str(), pattern.ptr, pattern.size);}rootN.addTextPattern("module");// search through the list:std::vector<Match> allMatches;DWORD start = GetTickCount();size_t counter = find_all_matches(rootN, loadedData, loadedSize, allMatches);DWORD end = GetTickCount();print_results(__FUNCTION__, counter, (end - start));return counter;
}size_t naive_search(BYTE* loadedData, size_t loadedSize)
{const BYTE pattern[] = { 0x40, 0x53, 0x48, 0x83, 0xec };size_t counter = 0;DWORD start = GetTickCount();for (size_t i = 0; i < loadedSize; i++) {if ((loadedSize - i) >= sizeof(pattern)) {if (::memcmp(loadedData + i, pattern, sizeof(pattern)) == 0) counter++;}}DWORD end = GetTickCount();print_results(__FUNCTION__, counter, (end - start));return counter;
}size_t tree_search(BYTE* loadedData, size_t loadedSize)
{Node rootN;const BYTE pattern[] = { 0x40, 0x53, 0x48, 0x83, 0xec };rootN.addPattern("pattern", pattern, sizeof(pattern));std::vector<Match> allMatches;DWORD start = GetTickCount();size_t counter = find_all_matches(rootN, loadedData, loadedSize, allMatches);DWORD end = GetTickCount();print_results(__FUNCTION__, counter, (end - start));return counter;
}void multi_search(BYTE* loadedData, size_t loadedSize)
{Node rootN;const BYTE pattern[] = { 0x40, 0x53, 0x48, 0x83, 0xec };rootN.addPattern("prolog32_1", pattern, sizeof(pattern));const BYTE pattern2[] = { 0x55, 0x48, 0x8B, 0xec };rootN.addPattern("prolog32_2", pattern2, sizeof(pattern2));const BYTE pattern3[] = { 0x40, 0x55, 0x48, 0x83, 0xec };const BYTE pattern3_mask[] = { 0xFF, 0x00, 0xF0, 0xF0, 0xF0 };Signature sign("prolog32_3", pattern3, sizeof(pattern3), pattern3_mask);std::cout << sign.name << " : " << sign.toByteStr() << "\n";rootN.addTextPattern("module");Signature* sign1 = Signature::loadFromByteStr("prolog1", "40 ?? 4? 8? e?");std::cout << sign1->name << " : " << sign1->toByteStr() << "\n";if (!sign1) {std::cout << "Could not load the signature!\n";return;}rootN.addPattern(*sign1);find_matches(rootN, loadedData, loadedSize, __FUNCTION__, false);
}bool aho_corasic_test()
{Node rootN;BYTE loadedData[] = "GCATCG";size_t loadedSize = sizeof(loadedData);rootN.addTextPattern("ACC");rootN.addTextPattern("ATC");rootN.addTextPattern("CAT");rootN.addTextPattern("CATC");rootN.addTextPattern("GCG");if (find_matches(rootN, loadedData, loadedSize, __FUNCTION__, true) == 3) {return true;}std::cerr << "[-]" << __FUNCTION__ << " : Test failed.\n";return false;
}bool aho_corasic_test2()
{Node rootN;BYTE loadedData[] = "ushers";size_t loadedSize = sizeof(loadedData);rootN.addTextPattern("hers");rootN.addTextPattern("his");rootN.addTextPattern("he");rootN.addTextPattern("she");if (find_matches(rootN, loadedData, loadedSize, __FUNCTION__, true) == 3) {return true;}std::cerr << "[-]" << __FUNCTION__ << " : Test failed.\n";return false;
}bool aho_corasic_test3()
{Node rootN;BYTE loadedData[] = "h he her hers";size_t loadedSize = sizeof(loadedData);rootN.addTextPattern("hers");if (find_matches(rootN, loadedData, loadedSize, __FUNCTION__, true) == 1) {return true;}std::cerr << "[-]" << __FUNCTION__ << " : Test failed.\n";return false;
}bool aho_corasic_test4()
{Node rootN;BYTE loadedData[] = "hehehehehe";size_t loadedSize = sizeof(loadedData);rootN.addTextPattern("he");rootN.addTextPattern("hehehehe");if (find_matches(rootN, loadedData, loadedSize, __FUNCTION__, true) == 7) {return true;}std::cerr << "[-]" << __FUNCTION__ << " : Test failed.\n";return false;
}bool aho_corasic_test5()
{Node rootN;BYTE loadedData[] = "something";size_t loadedSize = sizeof(loadedData);rootN.addTextPattern("hers");rootN.addTextPattern("his");rootN.addTextPattern("he");if (find_matches(rootN, loadedData, loadedSize, __FUNCTION__, true) == 0) {return true;}std::cerr << "[-]" << __FUNCTION__ << " : Test failed.\n";return false;
}bool sig_check()
{const BYTE test_pattern[] = { 0x54, 0x45, 0x53, 0x54 };const BYTE test_mask[] = { 0xFF, 0xFF, 0xFF, 0xFF};Signature test1("test1", test_pattern, sizeof(test_pattern), test_mask);Signature test2("test2", test_pattern, sizeof(test_pattern), nullptr);if (test2 != test1) {std::cerr << "[-] " << __FUNCTION__ << " : Test failed.\n";return false;}std::cout << "[+] " << __FUNCTION__ << " : Sign test 1 OK\n";const BYTE test_mask3[] = { 0xFF, 0x0F, 0xFF, 0xFF };Signature test3("test3", test_pattern, sizeof(test_mask3), nullptr);if (test1 != test3) {std::cerr << "[-] " << __FUNCTION__ << " : Test failed.\n";return false;}std::cout << "[+] " << __FUNCTION__ << " : Sign test 2 OK\n";return true;
}int main(int argc, char *argv[])
{if (argc < 2) {if (!aho_corasic_test()) return (-1);if (!aho_corasic_test2()) return (-1);if (!aho_corasic_test3()) return (-1);if (!aho_corasic_test4()) return (-1);if (!aho_corasic_test5()) return (-1);if (!sig_check()) return (-1);std::cout << "[+] All passed.\n";std::cout << "To search for hardcoded patterns in a file use:\nArgs: <input_file>\n";return 0;}size_t loadedSize = 0;BYTE* loadedData = load_file(argv[1], loadedSize);if (!loadedData) {std::cout << "Failed to load!\n";return 1;}if (argc < 3) {size_t nRes = naive_search(loadedData, loadedSize);size_t tRes = tree_search(loadedData, loadedSize);if (nRes != tRes) {std::cerr << "[-] Test failed.\n";return (-2);}std::cout << "---\n";nRes = naive_count(loadedData, loadedSize);tRes = tree_count(loadedData, loadedSize);if (nRes != tRes) {std::cerr << "[-] Test failed.\n";return (-2);}std::cout << "---\n";multi_search(loadedData, loadedSize);std::cout << "[+] Finished.\n";std::cout << "To search for external patterns in a file use:\nArgs: <input_file> <patterns_file>\n";return 0;}std::cout << "---\n";std::vector<Signature*> signatures;size_t loaded = 0;if (!(loaded = sig_finder::Signature::loadFromFile(argv[2], signatures))) {std::cerr << "Could not load signatures from file!\n";return 2;}std::cout << "Loaded: " << loaded << "\n";Node rootN;rootN.addPatterns(signatures);find_matches(rootN, loadedData, loadedSize, "from_sig_file", false);std::cout << "[+] Finished.\n";return 0;
}

工具通过多组测试验证核心功能,体现“测试驱动设计”思想:

  • 功能测试:如aho_corasic_test系列函数,验证不同字符串(如"GCATCG""ushers")中模式匹配的数量是否符合预期;
  • 边界测试:如aho_corasic_test5验证无匹配时的返回结果;
  • 签名一致性测试:如sig_check验证掩码不同但实际匹配效果相同的签名是否被视为相等。

If you need the complete source code, please add the WeChat number (c17865354792)

六、总结

二进制签名查找器通过Aho-Corasick自动机实现了高效的多模式匹配,结合掩码机制支持灵活的通配符处理,兼顾了效率、灵活性和易用性。其设计思路体现了“问题抽象→算法选型→工程实现→测试验证”的完整流程,为二进制分析领域的模式识别任务提供了实用解决方案。

在实际应用中,这类工具不仅是逆向工程师的得力助手,也是恶意软件检测、二进制文件解析等领域的基础组件,其核心思想(多模式匹配、通配符处理)对相关技术的学习和研发具有重要参考价值。

\Welcome to follow WeChat official account【程序猿编码

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

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

相关文章

後端開發技術教學(二) 條件指令、循環結構、定義函數

書接上回&#xff1a;後端開發技術教學(一) [附2025最新可用 phpstudy2018下載鏈接] -CSDN博客 必要資源&#xff1a; trae中文版下載網址: TRAE - The Real AI Engineer phpStudy 2018 : phpStudy - Windows 一键部署 PHP 开发环境 小皮出品 目录 一、條件指令 1.1 if() …

状压DP-基本框架

状压DP-基本框架一、状压DP的核心思想与适用场景1.1 问题特征1.2 核心思想1.3 与传统DP的对比二、位运算基础&#xff1a;状压DP的语法三、状压DP的基本框架3.1 步骤拆解3.2 通用代码模板四、经典案例详解4.1 旅行商问题&#xff08;TSP&#xff09;问题描述状压DP设计代码实现…

Web 端 AI 图像生成技术的应用与创新:虚拟背景与创意图像合成

随着 Stable Diffusion、Midjourney 等生成式 AI 模型的爆发,Web 端图像生成技术从“实验室demo”走向“工业化应用”。其中,虚拟背景替换(如视频会议的动态背景生成)和创意图像合成(如用户上传素材与 AI 生成元素的融合)成为最具代表性的场景,它们通过“文本描述→AI 生…

应急响应知识总结

应急响应 Windows系统 查账号 1、查看服务器是否有弱口令&#xff0c;远程管理端口是否对公网开放。 检查方法&#xff1a;据实际情况咨询相关服务器管理员。 2、查看服务器是否存在可疑账号、新增账号。 检查方法&#xff1a;打开 cmd 窗口&#xff0c;输入 lusrmgr.msc …

智慧水务赋能二次供水管理精细化转型:物联网驱动的全链路解决方案

随着我国城镇化率激增&#xff0c;高层建筑占比上升&#xff0c;二次供水系统已成为保障城市供水安全的核心环节。然而&#xff0c;传统管理模式面临设备老化、运维粗放、监管缺失等矛盾&#xff0c;在此背景下&#xff0c;《“十四五”节水型社会建设规划》明确要求推进二次供…

tsmc 5nm lvs之 short难搞的类型

1、M3层以上的层次发生的short&#xff0c;dengsity很高的情况下&#xff0c;两根信号net导致的short&#xff0c;删除其中一根然后ecoRoute fix不掉的情况下&#xff0c;该怎么办&#xff0c;可以尝试去cut 周围或者上方的power。 2、M1&#xff0c; M2由于cell 内部出pin&…

初识神经网络01——认识PyTorch

文章目录一、认识PyTorch1.1 PyTorch是什么1.2 安装PyTorch二、认识Tensor2.1 创建Tensor2.1.1 基本方式2.2.2 创建线性和随机张量2.2 Tensor属性2.2.1 切换设备2.2.2 类型转换2.3 Tensor与Numpy的数据转换2.3.1 张量转ndarray2.3.2 Numpy转张量2.4 Tensor常见操作2.4.1 取值2.…

Android UI 组件系列(十一):RecyclerView 多类型布局与数据刷新实战

博客专栏&#xff1a;Android初级入门UI组件与布局 源码&#xff1a;通过网盘分享的文件&#xff1a;Android入门布局及UI相关案例 链接: https://pan.baidu.com/s/1EOuDUKJndMISolieFSvXXg?pwd4k9n 提取码: 4k9n 引言 在 Android 应用中&#xff0c;RecyclerView 是最常用…

如何学习跨模态对齐(尤其是 CLIP 思想)

学习跨模态对齐&#xff08;尤其是CLIP思想&#xff09;需要结合理论基础、经典模型原理、实践复现和前沿扩展&#xff0c;以下是一套系统的学习路径&#xff0c;从入门到深入逐步展开&#xff1a; 一、先补基础&#xff1a;跨模态对齐的“前置知识” 跨模态对齐的核心是让图…

日记研究:一种深入了解用户真实体验的UX研究方法

在用户体验&#xff08;UX&#xff09;研究中&#xff0c;我们常常需要了解用户在真实世界中如何与产品互动。然而&#xff0c;由于时间和空间的限制&#xff0c;我们很难像“特工”一样全天候跟踪用户。这时&#xff0c;“日记研究”&#xff08;Diary Studies&#xff09;就成…

鸿蒙app 开发中 加载图片的时候闪一下 如何解决

1.解决 在图片上 加载这个属性 .syncLoad(true) 参考的官方链接

【OS】进程与线程

进程进程实体代码段相关数据PCB进程标识符外部标识符&#xff1a;为方便用户对进程的访问&#xff0c;为每个进程设置一个外部标识符&#xff0c;通常由字母和数字组成内部标识符&#xff1a;为方便系统对进程的使用&#xff0c;在OS中又为进程设置了内部标识符&#xff0c;赋予…

Django 序列化详解:从 Model 到 JSON,全面掌握数据转换机制

一、引言&#xff1a;什么是 Django 序列化&#xff1f;在 Web 开发中&#xff0c;序列化&#xff08;Serialization&#xff09; 是指将复杂的数据结构&#xff08;如数据库模型对象&#xff09;转换为可传输的格式&#xff08;如 JSON、XML、YAML 等&#xff09;&#xff0c;…

茶叶蛋大冒险小游戏流量主微信抖音小程序开源

游戏特点 响应式设计&#xff1a;完美适配各种移动设备屏幕尺寸 直观的触摸控制&#xff1a;左右滑动屏幕控制茶叶蛋移动 中式风格元素&#xff1a; 茶叶蛋角色带有裂纹纹理和可爱表情 筷子、蒸笼等中式厨房元素作为障碍物 八角、茶叶等香料作为收集物 锅底火焰动画效果 游戏机…

区分邮科工业交换机与路由器

在这个数字化的时代&#xff0c;我们每天都在享受着互联网带来的便利。无论是工作还是娱乐&#xff0c;网络已经成为我们生活中不可或缺的一部分。然而&#xff0c;在这个看似简单的背后&#xff0c;隐藏着两个至关重要的设备——邮科工业交换机和路由器。它们就像网络世界的双…

【数据结构入门】数组和链表的OJ题(2)

目录 1.回文链表 分析&#xff1a; 代码&#xff1a; 2.相交链表 分析&#xff1a; 代码&#xff1a; 3.环形链表 分析&#xff1a; 代码&#xff1a; 面试提问&#xff1a; 4.环形链表II 分析1&#xff1a; 分析2&#xff1a; 代码&#xff1a; 5.随机链表的复…

文件包含篇

web78 第一题filter伪协议直接读源码即可 ?filephp://filter/convert.base64-encode/resourceflag.php web79 flag.php的php无法用大小写绕过&#xff0c;所以用Php://input只读流 import requests url "http://fadb524a-f22d-4747-a35c-82f71e84bba7.challenge.ctf.sho…

互作蛋白组学技术对比:邻近标记与传统IP-MS、Pull down-MS优势对比

在生命科学领域&#xff0c;蛋白质间的相互作用构成了生命活动的核心网络&#xff0c;驱动着信号传导、基因调控、代谢途径等关键过程。为了绘制这幅复杂的“分子互作地图”&#xff0c;科学家们开发了多种技术&#xff0c;其中免疫共沉淀结合质谱&#xff08;IP-MS&#xff09…

(ZipList入门笔记一)ZipList的节点介绍

ZipList是 Redis 中一种非常紧凑、节省内存的数据结构 Ziplist&#xff08;压缩列表&#xff09; 的内部内存布局。它被用于存储元素较少的 List、Hash 和 Zset。 下面我们来详细介绍每一个节点的含义&#xff1a; 1. zlbytes (ziplist bytes) 含义&#xff1a; 整个压缩列…

Unix 发展史概览

这里是一个简明清晰的 Unix 发展史概览&#xff0c;涵盖从起源到现代的重要节点和演变过程。Unix 发展史概览 1. Unix 起源&#xff08;1969年&#xff09; 贝尔实验室&#xff1a;Ken Thompson 和 Dennis Ritchie 开发出 Unix 操作系统。最初设计目标&#xff1a;简洁、可移植…