大连响应式网站制作/湖南seo博客seo交流

大连响应式网站制作,湖南seo博客seo交流,茶文化网站设计免费,英文网站建设电话版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 前言 当我们如果没有对字符串进行加密&#xff0c;使用 IDA 反汇编一下 so 可以看到 C 代码中的字符串就直接暴露了。 字符串加密原理 sobf.c #include <…

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

前言

当我们如果没有对字符串进行加密,使用 IDA 反汇编一下 so 可以看到 C++ 代码中的字符串就直接暴露了。

word/media/image1.png

字符串加密原理

sobf.c

#include <stdio.h>int main() {// 定义字符串常量const char *greeting = "Hello, World!";const char *name = "Cyrus";const char *message = "Welcome to C programming.";// 打印字符串常量printf("%s\n", greeting);printf("My name is %s.\n", name);printf("%s\n", message);return 0;
}

生成 ir 文件

clang -S -emit-llvm sobf.c -o sobf.ll

通过生成 ir 文件可以看到字符串的定义都是 @“??C@ ,引用的的地方通过 @”??C@ 使用

; ModuleID = 'sobf.c'
source_filename = "sobf.c"
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-windows-msvc19.42.34433"$sprintf = comdat any$vsprintf = comdat any$_snprintf = comdat any$_vsnprintf = comdat any$printf = comdat any$_vsprintf_l = comdat any$_vsnprintf_l = comdat any$__local_stdio_printf_options = comdat any$_vfprintf_l = comdat any$"??_C@_0O@KLMCIIGF@Hello?0?5World?$CB?$AA@" = comdat any$"??_C@_03OFAPEBGM@?$CFs?6?$AA@" = comdat any$"??_C@_05KECNEMLJ@Cyrus?$AA@" = comdat any$"??_C@_0BA@ENLIINEF@My?5name?5is?5?$CFs?4?6?$AA@" = comdat any@"??_C@_0O@KLMCIIGF@Hello?0?5World?$CB?$AA@" = linkonce_odr dso_local unnamed_addr constant [14 x i8] c"Hello, World!\00", comdat, align 1
@"??_C@_03OFAPEBGM@?$CFs?6?$AA@" = linkonce_odr dso_local unnamed_addr constant [4 x i8] c"%s\0A\00", comdat, align 1
@"??_C@_05KECNEMLJ@Cyrus?$AA@" = linkonce_odr dso_local unnamed_addr constant [6 x i8] c"Cyrus\00", comdat, align 1
@"??_C@_0BA@ENLIINEF@My?5name?5is?5?$CFs?4?6?$AA@" = linkonce_odr dso_local unnamed_addr constant [16 x i8] c"My name is %s.\0A\00", comdat, align 1
@__local_stdio_printf_options._OptionsStorage = internal global i64 0, align 8; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @sprintf(ptr noundef %0, ptr noundef %1, ...) #0 comdat {%3 = alloca ptr, align 8%4 = alloca ptr, align 8%5 = alloca i32, align 4%6 = alloca ptr, align 8store ptr %1, ptr %3, align 8store ptr %0, ptr %4, align 8call void @llvm.va_start(ptr %6)%7 = load ptr, ptr %6, align 8%8 = load ptr, ptr %3, align 8%9 = load ptr, ptr %4, align 8%10 = call i32 @_vsprintf_l(ptr noundef %9, ptr noundef %8, ptr noundef null, ptr noundef %7)store i32 %10, ptr %5, align 4call void @llvm.va_end(ptr %6)%11 = load i32, ptr %5, align 4ret i32 %11
}; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @vsprintf(ptr noundef %0, ptr noundef %1, ptr noundef %2) #0 comdat {%4 = alloca ptr, align 8%5 = alloca ptr, align 8%6 = alloca ptr, align 8store ptr %2, ptr %4, align 8store ptr %1, ptr %5, align 8store ptr %0, ptr %6, align 8%7 = load ptr, ptr %4, align 8%8 = load ptr, ptr %5, align 8%9 = load ptr, ptr %6, align 8%10 = call i32 @_vsnprintf_l(ptr noundef %9, i64 noundef -1, ptr noundef %8, ptr noundef null, ptr noundef %7)ret i32 %10
}; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @_snprintf(ptr noundef %0, i64 noundef %1, ptr noundef %2, ...) #0 comdat {%4 = alloca ptr, align 8%5 = alloca i64, align 8%6 = alloca ptr, align 8%7 = alloca i32, align 4%8 = alloca ptr, align 8store ptr %2, ptr %4, align 8store i64 %1, ptr %5, align 8store ptr %0, ptr %6, align 8call void @llvm.va_start(ptr %8)%9 = load ptr, ptr %8, align 8%10 = load ptr, ptr %4, align 8%11 = load i64, ptr %5, align 8%12 = load ptr, ptr %6, align 8%13 = call i32 @_vsnprintf(ptr noundef %12, i64 noundef %11, ptr noundef %10, ptr noundef %9)store i32 %13, ptr %7, align 4call void @llvm.va_end(ptr %8)%14 = load i32, ptr %7, align 4ret i32 %14
}; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @_vsnprintf(ptr noundef %0, i64 noundef %1, ptr noundef %2, ptr noundef %3) #0 comdat {%5 = alloca ptr, align 8%6 = alloca ptr, align 8%7 = alloca i64, align 8%8 = alloca ptr, align 8store ptr %3, ptr %5, align 8store ptr %2, ptr %6, align 8store i64 %1, ptr %7, align 8store ptr %0, ptr %8, align 8%9 = load ptr, ptr %5, align 8%10 = load ptr, ptr %6, align 8%11 = load i64, ptr %7, align 8%12 = load ptr, ptr %8, align 8%13 = call i32 @_vsnprintf_l(ptr noundef %12, i64 noundef %11, ptr noundef %10, ptr noundef null, ptr noundef %9)ret i32 %13
}; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {%1 = alloca i32, align 4%2 = alloca ptr, align 8store i32 0, ptr %1, align 4store ptr @"??_C@_0O@KLMCIIGF@Hello?0?5World?$CB?$AA@", ptr %2, align 8%3 = load ptr, ptr %2, align 8%4 = call i32 (ptr, ...) @printf(ptr noundef @"??_C@_03OFAPEBGM@?$CFs?6?$AA@", ptr noundef %3)%5 = call i32 (ptr, ...) @printf(ptr noundef @"??_C@_0BA@ENLIINEF@My?5name?5is?5?$CFs?4?6?$AA@", ptr noundef @"??_C@_05KECNEMLJ@Cyrus?$AA@")ret i32 0
}; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @printf(ptr noundef %0, ...) #0 comdat {%2 = alloca ptr, align 8%3 = alloca i32, align 4%4 = alloca ptr, align 8store ptr %0, ptr %2, align 8call void @llvm.va_start(ptr %4)%5 = load ptr, ptr %4, align 8%6 = load ptr, ptr %2, align 8%7 = call ptr @__acrt_iob_func(i32 noundef 1)%8 = call i32 @_vfprintf_l(ptr noundef %7, ptr noundef %6, ptr noundef null, ptr noundef %5)store i32 %8, ptr %3, align 4call void @llvm.va_end(ptr %4)%9 = load i32, ptr %3, align 4ret i32 %9
}; Function Attrs: nocallback nofree nosync nounwind willreturn
declare void @llvm.va_start(ptr) #1; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @_vsprintf_l(ptr noundef %0, ptr noundef %1, ptr noundef %2, ptr noundef %3) #0 comdat {%5 = alloca ptr, align 8%6 = alloca ptr, align 8%7 = alloca ptr, align 8%8 = alloca ptr, align 8store ptr %3, ptr %5, align 8store ptr %2, ptr %6, align 8store ptr %1, ptr %7, align 8store ptr %0, ptr %8, align 8%9 = load ptr, ptr %5, align 8%10 = load ptr, ptr %6, align 8%11 = load ptr, ptr %7, align 8%12 = load ptr, ptr %8, align 8%13 = call i32 @_vsnprintf_l(ptr noundef %12, i64 noundef -1, ptr noundef %11, ptr noundef %10, ptr noundef %9)ret i32 %13
}; Function Attrs: nocallback nofree nosync nounwind willreturn
declare void @llvm.va_end(ptr) #1; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @_vsnprintf_l(ptr noundef %0, i64 noundef %1, ptr noundef %2, ptr noundef %3, ptr noundef %4) #0 comdat {%6 = alloca ptr, align 8%7 = alloca ptr, align 8%8 = alloca ptr, align 8%9 = alloca i64, align 8%10 = alloca ptr, align 8%11 = alloca i32, align 4store ptr %4, ptr %6, align 8store ptr %3, ptr %7, align 8store ptr %2, ptr %8, align 8store i64 %1, ptr %9, align 8store ptr %0, ptr %10, align 8%12 = load ptr, ptr %6, align 8%13 = load ptr, ptr %7, align 8%14 = load ptr, ptr %8, align 8%15 = load i64, ptr %9, align 8%16 = load ptr, ptr %10, align 8%17 = call ptr @__local_stdio_printf_options()%18 = load i64, ptr %17, align 8%19 = or i64 %18, 1%20 = call i32 @__stdio_common_vsprintf(i64 noundef %19, ptr noundef %16, i64 noundef %15, ptr noundef %14, ptr noundef %13, ptr noundef %12)store i32 %20, ptr %11, align 4%21 = load i32, ptr %11, align 4%22 = icmp slt i32 %21, 0br i1 %22, label %23, label %2423:                                               ; preds = %5br label %2624:                                               ; preds = %5%25 = load i32, ptr %11, align 4br label %2626:                                               ; preds = %24, %23%27 = phi i32 [ -1, %23 ], [ %25, %24 ]ret i32 %27
}declare dso_local i32 @__stdio_common_vsprintf(i64 noundef, ptr noundef, i64 noundef, ptr noundef, ptr noundef, ptr noundef) #2; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local ptr @__local_stdio_printf_options() #0 comdat {ret ptr @__local_stdio_printf_options._OptionsStorage
}; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @_vfprintf_l(ptr noundef %0, ptr noundef %1, ptr noundef %2, ptr noundef %3) #0 comdat {%5 = alloca ptr, align 8%6 = alloca ptr, align 8%7 = alloca ptr, align 8%8 = alloca ptr, align 8store ptr %3, ptr %5, align 8store ptr %2, ptr %6, align 8store ptr %1, ptr %7, align 8store ptr %0, ptr %8, align 8%9 = load ptr, ptr %5, align 8%10 = load ptr, ptr %6, align 8%11 = load ptr, ptr %7, align 8%12 = load ptr, ptr %8, align 8%13 = call ptr @__local_stdio_printf_options()%14 = load i64, ptr %13, align 8%15 = call i32 @__stdio_common_vfprintf(i64 noundef %14, ptr noundef %12, ptr noundef %11, ptr noundef %10, ptr noundef %9)ret i32 %15
}declare dso_local ptr @__acrt_iob_func(i32 noundef) #2declare dso_local i32 @__stdio_common_vfprintf(i64 noundef, ptr noundef, ptr noundef, ptr noundef, ptr noundef) #2attributes #0 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { nocallback nofree nosync nounwind willreturn }
attributes #2 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }!llvm.module.flags = !{!0, !1, !2, !3}
!llvm.ident = !{!4}!0 = !{i32 1, !"wchar_size", i32 2}
!1 = !{i32 8, !"PIC Level", i32 2}
!2 = !{i32 7, !"uwtable", i32 2}
!3 = !{i32 1, !"MaxTLSAlign", i32 65536}
!4 = !{!"clang version 18.1.8 (https://github.com/CYRUS-STUDIO/LLVM.git fb76c7a7578309cbd36b56fa8b17b4c6538d41ef)"}
  • ??_C@:表示一个字符串常量,后面是哈希值和字符串名称的编码。

只要在定义的地方把字符串加密,引用时候再解密就可以实现字符串的自动加解密了。

字符串常量

@"??_C@_0O@KLMCIIGF@Hello?0?5World?$CB?$AA@" = linkonce_odr dso_local unnamed_addr constant [14 x i8] c"Hello, World!\00", comdat, align 1
@"??_C@_05KECNEMLJ@Cyrus?$AA@" = linkonce_odr dso_local unnamed_addr constant [6 x i8] c"Cyrus\00", comdat, align 1
@"??_C@_0BA@ENLIINEF@My?5name?5is?5?$CFs?4?6?$AA@" = linkonce_odr dso_local unnamed_addr constant [16 x i8] c"My name is %s.\0A\00", comdat, align 1
  • “Hello, World!”

  • “Cyrus”

  • “My name is %s.”

main()函数

define dso_local i32 @main() #0 {store ptr @"??_C@_0O@KLMCIIGF@Hello?0?5World?$CB?$AA@", ptr %2, align 8%3 = load ptr, ptr %2, align 8%4 = call i32 (ptr, ...) @printf(ptr noundef @"??_C@_03OFAPEBGM@?$CFs?6?$AA@", ptr noundef %3)%5 = call i32 (ptr, ...) @printf(ptr noundef @"??_C@_0BA@ENLIINEF@My?5name?5is?5?$CFs?4?6?$AA@", ptr noundef @"??_C@_05KECNEMLJ@Cyrus?$AA@")ret i32 0
}
  • 读取 “Hello, World!” 并通过 printf 输出。

  • 使用 printf 打印 “My name is Cyrus.”,通过 %s 占位符传入 “Cyrus”。

实现字符串加密

1. StringEncryption.h

#ifndef LLVM_STRING_ENCRYPTION_H
#define LLVM_STRING_ENCRYPTION_H
// LLVM libs
#include "llvm/Transforms/Utils/GlobalStatus.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Value.h"
#include "llvm/Pass.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
//#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/SHA1.h"
#include "llvm/Transforms/Utils/ModuleUtils.h"// User libs
#include "CryptoUtils.h"
#include "Utils.h"
// System libs
#include <map>
#include <set>
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <vector>#include "ObfuscationOptions.h"using namespace std;
namespace llvm {
struct EncryptedGV {GlobalVariable *GV;uint64_t key;uint32_t len;
};class StringEncryptionPass : public PassInfoMixin<StringEncryptionPass> {
public:bool flag;struct CSPEntry {CSPEntry(): ID(0), Offset(0), DecGV(nullptr), DecStatus(nullptr),DecFunc(nullptr) {}unsigned ID;unsigned Offset;GlobalVariable *DecGV;GlobalVariable *DecStatus; // is decrypted or notstd::vector<uint8_t> Data;std::vector<uint8_t> EncKey;Function *DecFunc;};struct CSUser {CSUser(Type *ETy, GlobalVariable *User, GlobalVariable *NewGV): Ty(ETy), GV(User), DecGV(NewGV), DecStatus(nullptr),InitFunc(nullptr) {}Type *Ty;GlobalVariable *GV;GlobalVariable *DecGV;GlobalVariable *DecStatus; // is decrypted or notFunction *InitFunc;        // InitFunc will use decryted string to// initialize DecGV};ObfuscationOptions *Options;CryptoUtils RandomEngine;std::vector<CSPEntry *> ConstantStringPool;std::map<GlobalVariable *, CSPEntry *> CSPEntryMap;std::map<GlobalVariable *, CSUser *> CSUserMap;GlobalVariable *EncryptedStringTable;std::set<GlobalVariable *> MaybeDeadGlobalVars;map<Function * /*Function*/, GlobalVariable * /*Decryption Status*/>encstatus;StringEncryptionPass(bool flag) {this->flag = flag;Options = new ObfuscationOptions;//EncryptedStringTable = new GlobalVariable;}PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); // Pass实现函数bool do_StrEnc(Module &M, ModuleAnalysisManager &AM);void collectConstantStringUser(GlobalVariable *CString,std::set<GlobalVariable *> &Users);bool isValidToEncrypt(GlobalVariable *GV);bool processConstantStringUse(Function *F);void deleteUnusedGlobalVariable();Function *buildDecryptFunction(Module *M, const CSPEntry *Entry);Function *buildInitFunction(Module *M, const CSUser *User);void getRandomBytes(std::vector<uint8_t> &Bytes, uint32_t MinSize,uint32_t MaxSize);void lowerGlobalConstant(Constant *CV, IRBuilder<> &IRB, Value *Ptr,Type *Ty);void lowerGlobalConstantStruct(ConstantStruct *CS, IRBuilder<> &IRB,Value *Ptr, Type *Ty);void lowerGlobalConstantArray(ConstantArray *CA, IRBuilder<> &IRB, Value *Ptr,Type *Ty);static bool isRequired() { return true; } // 直接返回true即可
};
StringEncryptionPass *createStringEncryption(bool flag); // 创建字符串加密
}
#endif

2. StringEncryption.cpp

2.1 收集字符串常量

在 run 方法迭代每一条指令,收集字符串常量

for (GlobalVariable &GV : M.globals()) {if (!GV.isConstant() || !GV.hasInitializer()) {continue;}if (ConstantDataSequential *CDS = dyn_cast<ConstantDataSequential>(GV.getInitializer())) {if (CDS->isCString()) {CSPEntry *Entry = new CSPEntry();StringRef Data = CDS->getRawDataValues();for (unsigned i = 0; i < Data.size(); ++i) {Entry->Data.push_back(static_cast<uint8_t>(Data[i]));}Entry->ID = static_cast<unsigned>(ConstantStringPool.size());}}
}
  • 遍历 Module 内的 GlobalVariable。

  • 只处理常量字符串 ConstantDataSequential。

  • 存入 ConstantStringPool(字符串池)。

2.2 加密字符串 和 构造解密函数

// encrypt those strings, build corresponding decrypt function
for (CSPEntry *Entry : ConstantStringPool) {getRandomBytes(Entry->EncKey, 16, 32);for (unsigned i = 0; i < Entry->Data.size(); ++i) {Entry->Data[i] ^= Entry->EncKey[i % Entry->EncKey.size()];}Entry->DecFunc = buildDecryptFunction(&M, Entry);
}
  • getRandomBytes() 生成 16-32 字节的随机密钥。

  • 使用 XOR 操作加密字符串。

2.3 创建一个初始化函数

为字符串变量创建一个初始化函数,用于在运行时执行解密操作。

// build initialization function for supported constant string users
for (GlobalVariable *GV : ConstantStringUsers) {if (isValidToEncrypt(GV)) {Type *EltType = GV->getValueType();ConstantAggregateZero *ZeroInit = ConstantAggregateZero::get(EltType);GlobalVariable *DecGV =new GlobalVariable(M, EltType, false, GlobalValue::PrivateLinkage,ZeroInit, "dec_" + GV->getName());DecGV->setAlignment(MaybeAlign(GV->getAlignment()));GlobalVariable *DecStatus = new GlobalVariable(M, Type::getInt32Ty(Ctx), false, GlobalValue::PrivateLinkage, Zero,"dec_status_" + GV->getName());CSUser *User = new CSUser(EltType, GV, DecGV);User->DecStatus = DecStatus;User->InitFunc = buildInitFunction(&M, User);CSUserMap[GV] = User;}
}

2.4 修改使用字符串的地方

processConstantStringUse() 找到所有使用加密字符串的地方,替换为解密后的变量。

// decrypt string back at every use, change the plain string use to the
// decrypted one
bool Changed = false;
for (Function &F : M) {if (F.isDeclaration())continue;Changed |= processConstantStringUse(&F);
}for (auto &I : CSUserMap) {CSUser *User = I.second;Changed |= processConstantStringUse(User->InitFunc);
}

完整代码

#include "StringEncryption.h"#define DEBUG_TYPE "strenc"using namespace llvm;bool StringEncryptionPass::do_StrEnc(Module &M, ModuleAnalysisManager &AM) {std::set<GlobalVariable *> ConstantStringUsers;// collect all c stringsLLVMContext &Ctx = M.getContext();ConstantInt *Zero = ConstantInt::get(Type::getInt32Ty(Ctx), 0);for (GlobalVariable &GV : M.globals()) {if (!GV.isConstant() || !GV.hasInitializer() ||GV.hasDLLExportStorageClass() || GV.isDLLImportDependent()) {continue;}Constant *Init = GV.getInitializer();if (Init == nullptr)continue;if (ConstantDataSequential *CDS = dyn_cast<ConstantDataSequential>(Init)) {if (CDS->isCString()) {CSPEntry *Entry = new CSPEntry();StringRef Data = CDS->getRawDataValues();Entry->Data.reserve(Data.size());for (unsigned i = 0; i < Data.size(); ++i) {Entry->Data.push_back(static_cast<uint8_t>(Data[i]));}Entry->ID = static_cast<unsigned>(ConstantStringPool.size());ConstantAggregateZero *ZeroInit =ConstantAggregateZero::get(CDS->getType());GlobalVariable *DecGV = new GlobalVariable(M, CDS->getType(), false, GlobalValue::PrivateLinkage, ZeroInit,"dec" + Twine::utohexstr(Entry->ID) + GV.getName());GlobalVariable *DecStatus = new GlobalVariable(M, Type::getInt32Ty(Ctx), false, GlobalValue::PrivateLinkage, Zero,"dec_status_" + Twine::utohexstr(Entry->ID) + GV.getName());DecGV->setAlignment(MaybeAlign(GV.getAlignment()));Entry->DecGV = DecGV;Entry->DecStatus = DecStatus;ConstantStringPool.push_back(Entry);CSPEntryMap[&GV] = Entry;collectConstantStringUser(&GV, ConstantStringUsers);}}}// encrypt those strings, build corresponding decrypt functionfor (CSPEntry *Entry : ConstantStringPool) {getRandomBytes(Entry->EncKey, 16, 32);for (unsigned i = 0; i < Entry->Data.size(); ++i) {Entry->Data[i] ^= Entry->EncKey[i % Entry->EncKey.size()];}Entry->DecFunc = buildDecryptFunction(&M, Entry);}// build initialization function for supported constant string usersfor (GlobalVariable *GV : ConstantStringUsers) {if (isValidToEncrypt(GV)) {Type *EltType = GV->getValueType();ConstantAggregateZero *ZeroInit = ConstantAggregateZero::get(EltType);GlobalVariable *DecGV =new GlobalVariable(M, EltType, false, GlobalValue::PrivateLinkage,ZeroInit, "dec_" + GV->getName());DecGV->setAlignment(MaybeAlign(GV->getAlignment()));GlobalVariable *DecStatus = new GlobalVariable(M, Type::getInt32Ty(Ctx), false, GlobalValue::PrivateLinkage, Zero,"dec_status_" + GV->getName());CSUser *User = new CSUser(EltType, GV, DecGV);User->DecStatus = DecStatus;User->InitFunc = buildInitFunction(&M, User);CSUserMap[GV] = User;}}// emit the constant string pool// | junk bytes | key 1 | encrypted string 1 | junk bytes | key 2 | encrypted// string 2 | ...std::vector<uint8_t> Data;std::vector<uint8_t> JunkBytes;JunkBytes.reserve(32);for (CSPEntry *Entry : ConstantStringPool) {JunkBytes.clear();getRandomBytes(JunkBytes, 16, 32);Data.insert(Data.end(), JunkBytes.begin(), JunkBytes.end());Entry->Offset = static_cast<unsigned>(Data.size());Data.insert(Data.end(), Entry->EncKey.begin(), Entry->EncKey.end());Data.insert(Data.end(), Entry->Data.begin(), Entry->Data.end());}Constant *CDA =ConstantDataArray::get(M.getContext(), ArrayRef<uint8_t>(Data));EncryptedStringTable =new GlobalVariable(M, CDA->getType(), true, GlobalValue::PrivateLinkage,CDA, "EncryptedStringTable");// decrypt string back at every use, change the plain string use to the// decrypted onebool Changed = false;for (Function &F : M) {if (F.isDeclaration())continue;Changed |= processConstantStringUse(&F);}for (auto &I : CSUserMap) {CSUser *User = I.second;Changed |= processConstantStringUse(User->InitFunc);}// delete unused global variablesdeleteUnusedGlobalVariable();for (CSPEntry *Entry : ConstantStringPool) {if (Entry->DecFunc->use_empty()) {Entry->DecFunc->eraseFromParent();}}return Changed;
}PreservedAnalyses StringEncryptionPass::run(Module &M, ModuleAnalysisManager &AM) {if (this->flag) {outs() << "[Soule] force.run.StringEncryptionPass\n";if (do_StrEnc(M, AM))return PreservedAnalyses::none();}return PreservedAnalyses::all();
}void StringEncryptionPass::getRandomBytes(std::vector<uint8_t> &Bytes,uint32_t MinSize, uint32_t MaxSize) {uint32_t N = RandomEngine.get_uint32_t();uint32_t Len;assert(MaxSize >= MinSize);if (MinSize == MaxSize) {Len = MinSize;} else {Len = MinSize + (N % (MaxSize - MinSize));}char *Buffer = new char[Len];RandomEngine.get_bytes(Buffer, Len);for (uint32_t i = 0; i < Len; ++i) {Bytes.push_back(static_cast<uint8_t>(Buffer[i]));}delete[] Buffer;
}//
// static void goron_decrypt_string(uint8_t *plain_string, const uint8_t *data)
//{
//  const uint8_t *key = data;
//  uint32_t key_size = 1234;
//  uint8_t *es = (uint8_t *) &data[key_size];
//  uint32_t i;
//  for (i = 0;i < 5678;i ++) {
//    plain_string[i] = es[i] ^ key[i % key_size];
//  }
//}Function *StringEncryptionPass::buildDecryptFunction(Module *M, const StringEncryptionPass::CSPEntry *Entry) {LLVMContext &Ctx = M->getContext();IRBuilder<> IRB(Ctx);FunctionType *FuncTy = FunctionType::get(Type::getVoidTy(Ctx), {IRB.getPtrTy(), IRB.getPtrTy()}, false);Function *DecFunc = Function::Create(FuncTy, GlobalValue::PrivateLinkage,"goron_decrypt_string_" + Twine::utohexstr(Entry->ID), M);auto ArgIt = DecFunc->arg_begin();Argument *PlainString = ArgIt; // output++ArgIt;Argument *Data = ArgIt; // inputPlainString->setName("plain_string");PlainString->addAttr(Attribute::NoCapture);Data->setName("data");Data->addAttr(Attribute::NoCapture);Data->addAttr(Attribute::ReadOnly);BasicBlock *Enter = BasicBlock::Create(Ctx, "Enter", DecFunc);BasicBlock *LoopBody = BasicBlock::Create(Ctx, "LoopBody", DecFunc);BasicBlock *UpdateDecStatus =BasicBlock::Create(Ctx, "UpdateDecStatus", DecFunc);BasicBlock *Exit = BasicBlock::Create(Ctx, "Exit", DecFunc);IRB.SetInsertPoint(Enter);ConstantInt *KeySize =ConstantInt::get(Type::getInt32Ty(Ctx), Entry->EncKey.size());Value *EncPtr = IRB.CreateInBoundsGEP(IRB.getInt8Ty(), Data, KeySize);Value *DecStatus =IRB.CreateLoad(Entry->DecStatus->getValueType(), Entry->DecStatus);Value *IsDecrypted = IRB.CreateICmpEQ(DecStatus, IRB.getInt32(1));IRB.CreateCondBr(IsDecrypted, Exit, LoopBody);IRB.SetInsertPoint(LoopBody);PHINode *LoopCounter = IRB.CreatePHI(IRB.getInt32Ty(), 2);LoopCounter->addIncoming(IRB.getInt32(0), Enter);Value *EncCharPtr =IRB.CreateInBoundsGEP(IRB.getInt8Ty(), EncPtr, LoopCounter);Value *EncChar = IRB.CreateLoad(IRB.getInt8Ty(), EncCharPtr);Value *KeyIdx = IRB.CreateURem(LoopCounter, KeySize);Value *KeyCharPtr = IRB.CreateInBoundsGEP(IRB.getInt8Ty(), Data, KeyIdx);Value *KeyChar = IRB.CreateLoad(IRB.getInt8Ty(), KeyCharPtr);Value *DecChar = IRB.CreateXor(EncChar, KeyChar);Value *DecCharPtr =IRB.CreateInBoundsGEP(IRB.getInt8Ty(), PlainString, LoopCounter);IRB.CreateStore(DecChar, DecCharPtr);Value *NewCounter =IRB.CreateAdd(LoopCounter, IRB.getInt32(1), "", true, true);LoopCounter->addIncoming(NewCounter, LoopBody);Value *Cond = IRB.CreateICmpEQ(NewCounter, IRB.getInt32(static_cast<uint32_t>(Entry->Data.size())));IRB.CreateCondBr(Cond, UpdateDecStatus, LoopBody);IRB.SetInsertPoint(UpdateDecStatus);IRB.CreateStore(IRB.getInt32(1), Entry->DecStatus);IRB.CreateBr(Exit);IRB.SetInsertPoint(Exit);IRB.CreateRetVoid();return DecFunc;
}Function *
StringEncryptionPass::buildInitFunction(Module *M,const StringEncryptionPass::CSUser *User) {LLVMContext &Ctx = M->getContext();IRBuilder<> IRB(Ctx);FunctionType *FuncTy = FunctionType::get(Type::getVoidTy(Ctx),{User->DecGV->getType()}, false);Function *InitFunc = Function::Create(FuncTy, GlobalValue::PrivateLinkage,"__global_variable_initializer_" + User->GV->getName(), M);auto ArgIt = InitFunc->arg_begin();Argument *thiz = ArgIt;thiz->setName("this");thiz->addAttr(Attribute::NoCapture);// convert constant initializer into a series of instructionsBasicBlock *Enter = BasicBlock::Create(Ctx, "Enter", InitFunc);BasicBlock *InitBlock = BasicBlock::Create(Ctx, "InitBlock", InitFunc);BasicBlock *Exit = BasicBlock::Create(Ctx, "Exit", InitFunc);IRB.SetInsertPoint(Enter);Value *DecStatus =IRB.CreateLoad(User->DecStatus->getValueType(), User->DecStatus);Value *IsDecrypted = IRB.CreateICmpEQ(DecStatus, IRB.getInt32(1));IRB.CreateCondBr(IsDecrypted, Exit, InitBlock);IRB.SetInsertPoint(InitBlock);Constant *Init = User->GV->getInitializer();lowerGlobalConstant(Init, IRB, User->DecGV, User->Ty);IRB.CreateStore(IRB.getInt32(1), User->DecStatus);IRB.CreateBr(Exit);IRB.SetInsertPoint(Exit);IRB.CreateRetVoid();return InitFunc;
}void StringEncryptionPass::lowerGlobalConstant(Constant *CV, IRBuilder<> &IRB,Value *Ptr, Type *Ty) {if (isa<ConstantAggregateZero>(CV)) {IRB.CreateStore(CV, Ptr);return;}if (ConstantArray *CA = dyn_cast<ConstantArray>(CV)) {lowerGlobalConstantArray(CA, IRB, Ptr, Ty);} else if (ConstantStruct *CS = dyn_cast<ConstantStruct>(CV)) {lowerGlobalConstantStruct(CS, IRB, Ptr, Ty);} else {IRB.CreateStore(CV, Ptr);}
}void StringEncryptionPass::lowerGlobalConstantArray(ConstantArray *CA,IRBuilder<> &IRB, Value *Ptr,Type *Ty) {for (unsigned i = 0, e = CA->getNumOperands(); i != e; ++i) {Constant *CV = CA->getOperand(i);Value *GEP = IRB.CreateGEP(Ty, Ptr, {IRB.getInt32(0), IRB.getInt32(i)});lowerGlobalConstant(CV, IRB, GEP, CV->getType());}
}void StringEncryptionPass::lowerGlobalConstantStruct(ConstantStruct *CS,IRBuilder<> &IRB, Value *Ptr,Type *Ty) {for (unsigned i = 0, e = CS->getNumOperands(); i != e; ++i) {Constant *CV = CS->getOperand(i);Value *GEP = IRB.CreateGEP(Ty, Ptr, {IRB.getInt32(0), IRB.getInt32(i)});lowerGlobalConstant(CV, IRB, GEP, CV->getType());}
}bool StringEncryptionPass::processConstantStringUse(Function *F) {if (!toObfuscate(flag, F, "cse")) {return false;}if (Options && Options->skipFunction(F->getName())) {return false;}LowerConstantExpr(*F);SmallPtrSet<GlobalVariable *, 16>DecryptedGV; // if GV has multiple use in a block, decrypt only at the// first usebool Changed = false;for (BasicBlock &BB : *F) {DecryptedGV.clear();if (BB.isEHPad()) {continue;}for (Instruction &Inst : BB) {if (Inst.isEHPad()) {continue;}if (PHINode *PHI = dyn_cast<PHINode>(&Inst)) {for (unsigned int i = 0; i < PHI->getNumIncomingValues(); ++i) {if (GlobalVariable *GV = dyn_cast<GlobalVariable>(PHI->getIncomingValue(i))) {auto Iter1 = CSPEntryMap.find(GV);auto Iter2 = CSUserMap.find(GV);if (Iter2 !=CSUserMap.end()) { // GV is a constant string userCSUser *User = Iter2->second;if (DecryptedGV.count(GV) > 0) {Inst.replaceUsesOfWith(GV, User->DecGV);} else {Instruction *InsertPoint =PHI->getIncomingBlock(i)->getTerminator();IRBuilder<> IRB(InsertPoint);IRB.CreateCall(User->InitFunc, {User->DecGV});Inst.replaceUsesOfWith(GV, User->DecGV);MaybeDeadGlobalVars.insert(GV);DecryptedGV.insert(GV);Changed = true;}} else if (Iter1 !=CSPEntryMap.end()) { // GV is a constant stringCSPEntry *Entry = Iter1->second;if (DecryptedGV.count(GV) > 0) {Inst.replaceUsesOfWith(GV, Entry->DecGV);} else {Instruction *InsertPoint =PHI->getIncomingBlock(i)->getTerminator();IRBuilder<> IRB(InsertPoint);Value *OutBuf = IRB.CreateBitCast(Entry->DecGV, IRB.getPtrTy());Value *Data = IRB.CreateInBoundsGEP(EncryptedStringTable->getValueType(),EncryptedStringTable,{IRB.getInt32(0), IRB.getInt32(Entry->Offset)});IRB.CreateCall(Entry->DecFunc, {OutBuf, Data});Inst.replaceUsesOfWith(GV, Entry->DecGV);MaybeDeadGlobalVars.insert(GV);DecryptedGV.insert(GV);Changed = true;}}}}} else {for (User::op_iterator op = Inst.op_begin();op != Inst.op_end(); ++op) {if (GlobalVariable *GV = dyn_cast<GlobalVariable>(*op)) {auto Iter1 = CSPEntryMap.find(GV);auto Iter2 = CSUserMap.find(GV);if (Iter2 != CSUserMap.end()) {CSUser *User = Iter2->second;if (DecryptedGV.count(GV) > 0) {Inst.replaceUsesOfWith(GV, User->DecGV);} else {IRBuilder<> IRB(&Inst);IRB.CreateCall(User->InitFunc, {User->DecGV});Inst.replaceUsesOfWith(GV, User->DecGV);MaybeDeadGlobalVars.insert(GV);DecryptedGV.insert(GV);Changed = true;}} else if (Iter1 != CSPEntryMap.end()) {CSPEntry *Entry = Iter1->second;if (DecryptedGV.count(GV) > 0) {Inst.replaceUsesOfWith(GV, Entry->DecGV);} else {IRBuilder<> IRB(&Inst);Value *OutBuf = IRB.CreateBitCast(Entry->DecGV, IRB.getPtrTy());Value *Data = IRB.CreateInBoundsGEP(EncryptedStringTable->getValueType(),EncryptedStringTable,{IRB.getInt32(0), IRB.getInt32(Entry->Offset)});IRB.CreateCall(Entry->DecFunc, {OutBuf, Data});Inst.replaceUsesOfWith(GV, Entry->DecGV);MaybeDeadGlobalVars.insert(GV);DecryptedGV.insert(GV);Changed = true;}}}}}}}return Changed;
}void StringEncryptionPass::collectConstantStringUser(GlobalVariable *CString, std::set<GlobalVariable *> &Users) {SmallPtrSet<Value *, 16> Visited;SmallVector<Value *, 16> ToVisit;ToVisit.push_back(CString);while (!ToVisit.empty()) {Value *V = ToVisit.pop_back_val();if (Visited.count(V) > 0)continue;Visited.insert(V);for (Value *User : V->users()) {if (auto *GV = dyn_cast<GlobalVariable>(User)) {Users.insert(GV);} else {ToVisit.push_back(User);}}}
}bool StringEncryptionPass::isValidToEncrypt(GlobalVariable *GV) {if (GV->isConstant() && GV->hasInitializer()) {return GV->getInitializer() != nullptr;} else {return false;}
}void StringEncryptionPass::deleteUnusedGlobalVariable() {bool Changed = true;while (Changed) {Changed = false;for (auto Iter = MaybeDeadGlobalVars.begin();Iter != MaybeDeadGlobalVars.end();) {GlobalVariable *GV = *Iter;if (!GV->hasLocalLinkage()) {++Iter;continue;}GV->removeDeadConstantUsers();if (GV->use_empty()) {if (GV->hasInitializer()) {Constant *Init = GV->getInitializer();GV->setInitializer(nullptr);if (isSafeToDestroyConstant(Init))Init->destroyConstant();}Iter = MaybeDeadGlobalVars.erase(Iter);GV->eraseFromParent();Changed = true;} else {++Iter;}}}
}StringEncryptionPass *llvm::createStringEncryption(bool flag){return new StringEncryptionPass(flag);
}

3. 注册命令行

static cl::opt<bool> s_obf_sobf("sobf", cl::init(false), cl::desc("String Obfuscation"));

4. 注册Pass

PassBuilder::PassBuilder(TargetMachine *TM, PipelineTuningOptions PTO,std::optional<PGOOptions> PGOOpt,PassInstrumentationCallbacks *PIC): TM(TM), PTO(PTO), PGOOpt(PGOOpt), PIC(PIC) {// 注册 Obfuscation 相关 Passthis->registerPipelineStartEPCallback([](llvm::ModulePassManager &MPM,llvm::OptimizationLevel Level) {MPM.addPass(StringEncryptionPass(s_obf_sobf)); // 先进行字符串加密 出现字符串加密基本块以后再进行基本块分割和其他混淆 加大解密难度});
}

测试

关于 OLLVM 的编译可以参考这篇文章:移植 OLLVM 到 LLVM 18,C&C++代码混淆

启用字符串加密并编译 C 代码

 clang -mllvm -sobf sobf.c -o sobf.exe

使用 IDA 反汇编

word/media/image2.png

未启用字符串加密

clang sobf.c -o nosobf.exe

使用 IDA 反汇编

word/media/image3.png

程序运行正常

word/media/image4.png

移植到 Android NDK

详细移植过程可以参考这篇文章:移植 OLLVM 到 Android NDK,Android Studio 中使用 OLLVM

在 CMakeLists.txt 中添加如下配置启用字符串加密

# 全局启用指令替换、字符串加密
add_definitions("-mllvm -sub -mllvm -sobf")

C++ 源码如下:

word/media/image5.png

编译完成后,通过 IDA 打开 so 可以看到字符串常量 “cyrus” 已经被替换成 &unk_6DE20

word/media/image6.png

替换成了一个未初始化的全局变量

word/media/image7.png

app 运行正常

word/media/image8.png

完整源码

LLVM 源码地址:https://github.com/CYRUS-STUDIO/LLVM

Android 示例源码地址:https://github.com/CYRUS-STUDIO/AndroidExample

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

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

相关文章

自动化逆向框架使用(Objection+Radare2)

1. 工具链架构与核心优势 1.1 动静结合逆向体系 graph LR A[动态分析] -->|Objection实时Hook| B[关键点定位] B --> C[行为数据捕获] D[静态分析] -->|Radare2深度解析| E[控制流重建] E --> F[漏洞模式识别] B --> F C --> F 组合优势对比&…

Docker-Volume数据卷详讲

Docker数据卷-Volume 一&#xff1a;Volume是什么&#xff0c;用来做什么的 当删除docker容器时&#xff0c;容器内部的文件就会跟随容器所销毁&#xff0c;在生产环境中我们需要将数据持久化保存&#xff0c;就催生了将容器内部的数据保存在宿主机的需求&#xff0c;volume …

单片机和微控制器知识汇总——《器件手册--单片机、数字信号处理器和可编程逻辑器件》

目录 四、单片机和微控制器 4.1 单片机(MCU/MPU/SOC) 一、定义 二、主要特点 三、工作原理 四、主要类型 五、应用领域 六、选型与设计注意事项 七、发展趋势 4.2 数字信号处理器(DSP/DSC) ​编辑​编辑 一、定义 二、工作原理 三、结构特点 四、应用领域 五、选型与设计注…

高数下---8.1平面与直线

目录 平面的确定 直线的确定 若要求某一直线或平面就根据要素来求。 例题 平面中的特殊情况 平面中的解题思路 直线的解题思路 平面的确定 两要素 一 一点 二 倾斜角 即法向量 点法式 可化为一般式 Ax By Cz D 0; (A,B,C) 即法向量&#xff1b; 改变D 即…

CMS迁移中SEO优化整合步骤详解

内容概要 在CMS迁移过程中&#xff0c;系统化的规划与执行是保障SEO排名稳定性的核心。首先需明确迁移流程的关键阶段&#xff0c;包括数据备份、URL适配、元数据同步及安全配置等环节。其中&#xff0c;数据备份不仅需覆盖原始数据库与静态资源&#xff0c;还需验证备份文件的…

Python:进程间的通信,进程的操作队列

进程间的队列&#xff1a; 队列的基本操作&#xff1a; 入队&#xff1a;将数据放到队列尾部 出队&#xff1a;从队列的头部取出一个元素 maxsize&#xff1a;队列中能存放数据个数的上限(整数)&#xff0c;一旦达到上限插入会导致阻塞&#xff0c;直到队列中的数据被消费掉 …

【C++初阶】--- 类与对象(中)

1.类的默认成员函数 默认成员函数就是⽤⼾没有显式实现&#xff0c;编译器会⾃动⽣成的成员函数称为默认成员函数。⼀个类&#xff0c;我们不写的情况下编译器会默认⽣成以下6个默认成员函数&#xff0c;我们主要需要掌握前4个&#xff0c;后两个了解以下即可&#xff0c;默认…

python处理音频相关的库

1 音频信号采集与播放 pyaudio import sys import pyaudio import wave import timeCHUNK 1024 FORMAT pyaudio.paInt16 CHANNELS 1#仅支持单声道 RATE 16000 RECORD_SECONDS 3#更改录音时长#录音函数&#xff0c;生成wav文件 def record(file_name):try:os.close(file_…

YoloV8训练和平精英人物检测模型

概述 和平精英人物检测&#xff0c;可以识别游戏中所有人物角色&#xff0c;并通过绘制框将人物选中&#xff0c;训练的模型仅仅具有识别功能&#xff0c;可以识别游戏中的视频、图片等文件&#xff0c;搭配Autox.js可以推理&#xff0c;实现实时绘制&#xff0c;但是对手机性…

智能汽车图像及视频处理方案,支持视频实时拍摄特效能力

在智能汽车日新月异的今天&#xff0c;美摄科技作为智能汽车图像及视频处理领域的先行者&#xff0c;凭借其卓越的技术实力和前瞻性的设计理念&#xff0c;为全球智能汽车制造商带来了一场视觉盛宴的革新。美摄科技推出智能汽车图像及视频处理方案&#xff0c;一个集高效性、智…

rosbag|ROS中.bag数据包转换为matlab中.mat数据类型

代码见代码 msg_dict中设置自定义消息类型 test_config中设置需要记录的具体的值 test_config中topic_name以及message_type照搬plotjuggler打开时的参数 最后生成.mat文件在matlab中进行使用

JS 对象转数组,数组转对象

数据格式 objMap : {apiP: 8000, sder: true, host: "1.111", wPort: "1335" }要求&#xff1a;将 objMap 转化为 数组 const equipArray Object.keys(objMap ).map(key > {return {name: key,value: objMap [key]}打印结果 数组转为对象 let equipAr…

vue - [Vue warn]: Duplicate keys detected: ‘0‘. This may cause an update error.

问题描述&#xff1a; vue项目中&#xff0c;对表单数组赋值时&#xff0c;控制台抛出警告&#xff1a; 问题代码&#xff1a; 问题分析&#xff1a; 1、Vue 要求每个虚拟 DOM 节点必须有唯一的 key。该警告信息通常出现在使用v-for循环的场景中&#xff0c;多个同级节点使用…

DeepSeek V3–0324 vs DeepSeek-V3, 排名最高非推理模型

最近DeepSeek V3 升级。 本文将带您了解该模型的核心特性、基准表现,以及如何通过Hugging Face推理终端和OpenRouter平台亲身体验。我们还将通过创意生成与逻辑分析两大测试案例,直观展示其卓越性能。 DeepSeek-V3-0324 2025年3月24日,深度求索(DeepSeek)AI正式发布了V3…

3.0 Disruptor的使用介绍(一)

Disruptor: 其官网定义为&#xff1a;“A High Performance Inter-Thread Messaging Library”&#xff0c;即&#xff1a;线程间的高性能消息框架&#xff0c;与Labview的生产者、消费者模型很相似。 其组成部分比较多&#xff0c;先介绍几个常用的概念&#xff1a; …

在 Windows 系统下,将 FFmpeg 编译为 .so 文件

1. 准备环境 确保你的 Windows 系统已安装以下工具&#xff1a; Android Studio NDK&#xff08;Native Development Kit&#xff09; MSYS2&#xff08;用于提供类 Unix 环境&#xff09; FFmpeg 源码 Git Bash&#xff08;可选&#xff0c;推荐使用&#xff09; 安装 …

leetcode二叉树3

404.左叶子之和 给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 示例 1&#xff1a; 输入: root [3,9,20,null,null,15,7] 输出: 24 解释: 在这个二叉树中&#xff0c;有两个左叶子&#xff0c;分别是 9 和 15&#xff0c;所以返回 24示例 2: 输入: root [1] 输…

QT网络通信的接口与使用

文章目录 前言1.服务端实现流程1.1步骤 1&#xff1a;创建 QTcpServer 并监听端口1.2步骤 2&#xff1a;处理新连接请求1.3步骤 3&#xff1a;接收客户端数据1.4步骤 4&#xff1a;处理客户端断开 2.客户端实现流程2.1步骤 1&#xff1a;创建 QTcpSocket 并连接服务器2.2步骤 2…

华为OD机试2025A卷七日集训第1期 - 按算法分类,由易到难,循序渐进,玩转OD(Python/JS/C/C++)

目录 一、适合人群二、本期训练时间三、如何参加四、7日集训第1期五、精心挑选21道高频100分经典题目&#xff0c;作为入门。第1天、逻辑分析第2天、逻辑分析第3天、逻辑分析第4天、逻辑分析第5天、双指针第6天、二叉树第7天、回溯 六、集训总结六、国内直接使用最新GPT-4.5、满…