OLLVM虚假控制流源码分析

news2024/11/28 2:56:29

文章目录

    • runOnFunction函数
    • bogus函数
    • 目前源码:
    • addBogusFlow函数1
    • createAlteredBasicBlock函数
      • 原基本块:
      • copy的基本块:
    • addBogusFlow函数2

runOnFunction函数

if (ObfTimes <= 0) {
        errs()<<"BogusControlFlow application number -bcf_loop=x must be x > 0";
		return false;
      }
if ( !((ObfProbRate > 0) && (ObfProbRate <= 100)) ) {
        errs()<<"BogusControlFlow application basic blocks percentage -bcf_prob=x must be 0 < x <= 100";
		return false;
      }
const int defaultObfRate = 30, defaultObfTime = 1;

static cl::opt<int>
ObfProbRate("bcf_prob", cl::desc("Choose the probability [%] each basic blocks will be obfuscated by the -bcf pass"), cl::value_desc("probability rate"), cl::init(defaultObfRate), cl::Optional);

static cl::opt<int>
ObfTimes("bcf_loop", cl::desc("Choose how many time the -bcf pass loop on a function"), cl::value_desc("number of times"), cl::init(defaultObfTime), cl::Optional);

这里的ObfTimes对应过来的默认值就是1,对函数进行混淆的次数,ObfProbRate就是30,对基本块进行混淆的概率,分别是opt时传入的参数。

    // check for compatible
      for (BasicBlock &bb : F.getBasicBlockList()) {
        if (isa<InvokeInst>(bb.getTerminator())) {
          return false;
        }
      }

枚举这个函数的所有基本块,如果这个函数后面基本块中含有invoke(调用了某个函数),它就不执行了,就退出这个基本块。

if(toObfuscate(flag,&F,"bcf")) {
        bogus(F);
        doF(*F.getParent());
        return true;
      }

判断有没有bcf也就是虚假控制流,有的话就进入。

bogus函数

  if(ObfProbRate < 0 || ObfProbRate > 100){
        DEBUG_WITH_TYPE("opt", errs() << "bcf: Incorrect value,"
            << " probability rate set to default value: "
            << defaultObfRate <<" \n");
        ObfProbRate = defaultObfRate;
      }
 if(ObfTimes <= 0){
        DEBUG_WITH_TYPE("opt", errs() << "bcf: Incorrect value,"
            << " must be greater than 1. Set to default: "
            << defaultObfTime <<" \n");
        ObfTimes = defaultObfTime;
      }

首先进行判断这个次数和概率,是否符合条件,不符合的话会进行设置默认值。
紧接着就是一个大型的do while循环里面包含着的代码:

 		std::list<BasicBlock *> basicBlocks;
          for (Function::iterator i=F.begin();i!=F.end();++i) {
            basicBlocks.push_back(&*i);
          }

把所有的基本块放在basicblock list里面。
获取一个随机值,如果符合的话就进入

 if((int)llvm::cryptoutils->get_range(100) <= ObfProbRate){
              DEBUG_WITH_TYPE("opt", errs() << "bcf: Block "
                  << NumBasicBlocks <<" selected. \n");
              hasBeenModified = true;
              ++NumModifiedBasicBlocks;
              NumAddedBasicBlocks += 3;
              FinalNumBasicBlocks += 3;
              // Add bogus flow to the given Basic Block (see description)
              BasicBlock *basicBlock = basicBlocks.front();
              addBogusFlow(basicBlock, F);
            }else{
              DEBUG_WITH_TYPE("opt", errs() << "bcf: Block "
                  << NumBasicBlocks <<" not selected.\n");
            }

每个基本块都有ObfProbRate的概率被混淆,即基本块调用了addBogusFlow函数。
这个函数的作用就是对指定函数的每个基本块以ObfProbRate的概率去进行调用函数混淆。

目前源码:

define dso_local i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  %a = alloca i32, align 4
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i8**, i8*** %argv.addr, align 8
  %arrayidx = getelementptr inbounds i8*, i8** %0, i64 1
  %1 = load i8*, i8** %arrayidx, align 8
  %call = call i32 @atoi(i8* %1) #2
  store i32 %call, i32* %a, align 4
  %2 = load i32, i32* %a, align 4
  %cmp = icmp eq i32 %2, 0
  br i1 %cmp, label %if.then, label %if.else

if.then:                                          ; preds = %entry
  store i32 1, i32* %retval, align 4
  br label %return

if.else:                                          ; preds = %entry
  store i32 10, i32* %retval, align 4
  br label %return

return:                                           ; preds = %if.else, %if.then
  %3 = load i32, i32* %retval, align 4
  ret i32 %3
}

addBogusFlow函数1

 Instruction *i1 = &*basicBlock->begin();
      if(basicBlock->getFirstNonPHIOrDbgOrLifetime())
        i1 = basicBlock->getFirstNonPHIOrDbgOrLifetime();
      Twine *var;
      var = new Twine("originalBB");
      BasicBlock *originalBB = basicBlock->splitBasicBlock(i1, *var);

执行完后这里的basicBlock指令是br label %originalBB,而originalBB目前代码块如下:

originalBB:
 %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  %a = alloca i32, align 4
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i8**, i8*** %argv.addr, align 8
  %arrayidx = getelementptr inbounds i8*, i8** %0, i64 1
  %1 = load i8*, i8** %arrayidx, align 8
  %call = call i32 @atoi(i8* %1) #2
  store i32 %call, i32* %a, align 4
  %2 = load i32, i32* %a, align 4
  %cmp = icmp eq i32 %2, 0
  br i1 %cmp, label %if.then, label %if.else

而之前的entry就是目前basicblock:

entry:
br label %originalBB

紧接着:

 Twine * var3 = new Twine("alteredBB");
      BasicBlock *alteredBB = createAlteredBasicBlock(originalBB, *var3, &F);

createAlteredBasicBlock会把这个originalBB进行克隆

createAlteredBasicBlock函数

    virtual BasicBlock* createAlteredBasicBlock(BasicBlock * basicBlock,
        const Twine &  Name = "gen", Function * F = 0){
      // Useful to remap the informations concerning instructions.
      ValueToValueMapTy VMap;
      BasicBlock * alteredBB = llvm::CloneBasicBlock (basicBlock, VMap, Name, F);
  
  
        // Remap attached metadata.
        SmallVector<std::pair<unsigned, MDNode *>, 4> MDs;
        i->getAllMetadata(MDs);
        // important for compiling with DWARF, using option -g.
        i->setDebugLoc(ji->getDebugLoc());
        ji++;
      } // The instructions' informations are now all correct

这里的代码的话主要解决两个问题,就cloneBasicBlock函数进行的克隆并不是完全的克隆,第一它不会对操作数进行替换,比如:

orig:
  %a = ...
  %b = fadd %a, ...
 
clone:
  %a.clone = ...
  %b.clone = fadd %a, ... ; Note that this references the old %a and
not %a.clone!

在clone出来的基本块中,fadd指令的操作数不是%a.clone,而是a%,所以之后要通过VMap对所有操作数进行映射,使其恢复正常:

      BasicBlock::iterator ji = basicBlock->begin();
      for (BasicBlock::iterator i = alteredBB->begin(), e = alteredBB->end() ; i != e; ++i){
        // Loop over the operands of the instruction
        for(User::op_iterator opi = i->op_begin (), ope = i->op_end(); opi != ope; ++opi){
          // get the value for the operand
          Value *v = MapValue(*opi, VMap,  RF_NoModuleLevelChanges, 0);
          if (v != 0){
            *opi = v;
          }
        }

第二,它不会对PHI Node进行任何处理,PHI Node的前驱块仍是原始基本块的前驱块,但是新克隆出来的基本块没有任何前驱块,所以要对PHI Node的前驱块进行remap:

// Remap phi nodes' incoming blocks.
        if (PHINode *pn = dyn_cast<PHINode>(i)) {
          for (unsigned j = 0, e = pn->getNumIncomingValues(); j != e; ++j) {
            Value *v = MapValue(pn->getIncomingBlock(j), VMap, RF_None, 0);
            if (v != 0){
              pn->setIncomingBlock(j, cast<BasicBlock>(v));
            }
          }
        }

解释一下PHI Node,所有的LLVM都要使用SSA(Static Single Assignment,静态一次性赋值)方式表示,即所有变量都只能被赋值一次,这样做主要是为了代码优化,如下图,%temp的值被赋值成1后就永远都是1了:
在这里插入图片描述
PHI Node是一条可以一定程序上绕开SSA机制的指令,它可以根据不同的前驱基本块来赋值(有点像三元运算符),如下图,如果PHI Node 的前驱基本块是entry,则将current_i赋值为2,如果是for_body,则赋值为%i_plus_one在这里插入图片描述

 for (BasicBlock::iterator i = alteredBB->begin(), e = alteredBB->end() ; i != e; ++i){
        // in the case we find binary operator, we modify slightly this part by randomly
        // insert some instructions
        if(i->isBinaryOp()){ // binary instructions
          unsigned opcode = i->getOpcode();
          BinaryOperator *op, *op1 = NULL;
          Twine *var = new Twine("_");
          // treat differently float or int
          // Binary int
          if(opcode == Instruction::Add || opcode == Instruction::Sub ||
              opcode == Instruction::Mul || opcode == Instruction::UDiv ||
              opcode == Instruction::SDiv || opcode == Instruction::URem ||
              opcode == Instruction::SRem || opcode == Instruction::Shl ||
              opcode == Instruction::LShr || opcode == Instruction::AShr ||
              opcode == Instruction::And || opcode == Instruction::Or ||
              opcode == Instruction::Xor){
            for(int random = (int)llvm::cryptoutils->get_range(10); random < 10; ++random){
              switch(llvm::cryptoutils->get_range(4)){ // to improve
                case 0: //do nothing
                  break;
                case 1: op = BinaryOperator::CreateNeg(i->getOperand(0),*var,&*i);
                        op1 = BinaryOperator::Create(Instruction::Add,op,
                            i->getOperand(1),"gen",&*i);
                        break;
                case 2: op1 = BinaryOperator::Create(Instruction::Sub,
                            i->getOperand(0),
                            i->getOperand(1),*var,&*i);
                        op = BinaryOperator::Create(Instruction::Mul,op1,
                            i->getOperand(1),"gen",&*i);
                        break;
                case 3: op = BinaryOperator::Create(Instruction::Shl,
                            i->getOperand(0),
                            i->getOperand(1),*var,&*i);
                        break;
              }
            }
          }
          // Binary float
          if(opcode == Instruction::FAdd || opcode == Instruction::FSub ||
              opcode == Instruction::FMul || opcode == Instruction::FDiv ||
              opcode == Instruction::FRem){
            for(int random = (int)llvm::cryptoutils->get_range(10); random < 10; ++random){
              switch(llvm::cryptoutils->get_range(3)){ // can be improved
                case 0: //do nothing
                  break;
                case 1: op = BinaryOperator::CreateFNeg(i->getOperand(0),*var,&*i);
                        op1 = BinaryOperator::Create(Instruction::FAdd,op,
                            i->getOperand(1),"gen",&*i);
                        break;
                case 2: op = BinaryOperator::Create(Instruction::FSub,
                            i->getOperand(0),
                            i->getOperand(1),*var,&*i);
                        op1 = BinaryOperator::Create(Instruction::FMul,op,
                            i->getOperand(1),"gen",&*i);
                        break;
              }
            }
          }
          if(opcode == Instruction::ICmp){ // Condition (with int)
            ICmpInst *currentI = (ICmpInst*)(&i);
            switch(llvm::cryptoutils->get_range(3)){ // must be improved
              case 0: //do nothing
                break;
              case 1: currentI->swapOperands();
                      break;
              case 2: // randomly change the predicate
                      switch(llvm::cryptoutils->get_range(10)){
                        case 0: currentI->setPredicate(ICmpInst::ICMP_EQ);
                                break; // equal
                        case 1: currentI->setPredicate(ICmpInst::ICMP_NE);
                                break; // not equal
                        case 2: currentI->setPredicate(ICmpInst::ICMP_UGT);
                                break; // unsigned greater than
                        case 3: currentI->setPredicate(ICmpInst::ICMP_UGE);
                                break; // unsigned greater or equal
                        case 4: currentI->setPredicate(ICmpInst::ICMP_ULT);
                                break; // unsigned less than
                        case 5: currentI->setPredicate(ICmpInst::ICMP_ULE);
                                break; // unsigned less or equal
                        case 6: currentI->setPredicate(ICmpInst::ICMP_SGT);
                                break; // signed greater than
                        case 7: currentI->setPredicate(ICmpInst::ICMP_SGE);
                                break; // signed greater or equal
                        case 8: currentI->setPredicate(ICmpInst::ICMP_SLT);
                                break; // signed less than
                        case 9: currentI->setPredicate(ICmpInst::ICMP_SLE);
                                break; // signed less or equal
                      }
                      break;
            }

          }
          if(opcode == Instruction::FCmp){ // Conditions (with float)
            FCmpInst *currentI = (FCmpInst*)(&i);
            switch(llvm::cryptoutils->get_range(3)){ // must be improved
              case 0: //do nothing
                break;
              case 1: currentI->swapOperands();
                      break;
              case 2: // randomly change the predicate
                      switch(llvm::cryptoutils->get_range(10)){
                        case 0: currentI->setPredicate(FCmpInst::FCMP_OEQ);
                                break; // ordered and equal
                        case 1: currentI->setPredicate(FCmpInst::FCMP_ONE);
                                break; // ordered and operands are unequal
                        case 2: currentI->setPredicate(FCmpInst::FCMP_UGT);
                                break; // unordered or greater than
                        case 3: currentI->setPredicate(FCmpInst::FCMP_UGE);
                                break; // unordered, or greater than, or equal
                        case 4: currentI->setPredicate(FCmpInst::FCMP_ULT);
                                break; // unordered or less than
                        case 5: currentI->setPredicate(FCmpInst::FCMP_ULE);
                                break; // unordered, or less than, or equal
                        case 6: currentI->setPredicate(FCmpInst::FCMP_OGT);
                                break; // ordered and greater than
                        case 7: currentI->setPredicate(FCmpInst::FCMP_OGE);
                                break; // ordered and greater than or equal
                        case 8: currentI->setPredicate(FCmpInst::FCMP_OLT);
                                break; // ordered and less than
                        case 9: currentI->setPredicate(FCmpInst::FCMP_OLE);
                                break; // ordered or less than, or equal
                      }
                      break;
            }
          }
        }
      }

大概思路就是往基本块里面添加一些没用的赋值指令,或者修改cmp的条件,binaryop大概指的是add,mul,cmp这类运算指令

原基本块:

originalBB:
 %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  %a = alloca i32, align 4
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i8**, i8*** %argv.addr, align 8
  %arrayidx = getelementptr inbounds i8*, i8** %0, i64 1
  %1 = load i8*, i8** %arrayidx, align 8
  %call = call i32 @atoi(i8* %1) #2
  store i32 %call, i32* %a, align 4
  %2 = load i32, i32* %a, align 4
  %cmp = icmp eq i32 %2, 0
  br i1 %cmp, label %if.then, label %if.else

copy的基本块:

originalBBalteredBB:                              ; preds = %originalBB, %entry
  %retvalalteredBB = alloca i32, align 4
  %argc.addralteredBB = alloca i32, align 4
  %argv.addralteredBB = alloca i8**, align 8
  %aalteredBB = alloca i32, align 4
  store i32 0, i32* %retvalalteredBB, align 4
  store i32 %argc, i32* %argc.addralteredBB, align 4
  store i8** %argv, i8*** %argv.addralteredBB, align 8
  %70 = load i8**, i8*** %argv.addralteredBB, align 8
  %arrayidxalteredBB = getelementptr inbounds i8*, i8** %70, i64 1
  %71 = load i8*, i8** %arrayidxalteredBB, align 8
  %callalteredBB = call i32 @atoi(i8* %71) #3
  store i32 %callalteredBB, i32* %aalteredBB, align 4
  %72 = load i32, i32* %aalteredBB, align 4
  %cmpalteredBB = icmp eq i32 %72, 0
  br i1 %cmpalteredBB,label %if.then,label %if.else

copy后变量名字进行改动

addBogusFlow函数2

 	  alteredBB->getTerminator()->eraseFromParent();
      basicBlock->getTerminator()->eraseFromParent();

这里的话是指把entry里面的跳转和拷贝出来的block块最后一个跳转也删掉:

entry:

originalBBalteredBB:                              ; preds = %originalBB, %entry
  %retvalalteredBB = alloca i32, align 4
  %argc.addralteredBB = alloca i32, align 4
  %argv.addralteredBB = alloca i8**, align 8
  %aalteredBB = alloca i32, align 4
  store i32 0, i32* %retvalalteredBB, align 4
  store i32 %argc, i32* %argc.addralteredBB, align 4
  store i8** %argv, i8*** %argv.addralteredBB, align 8
  %70 = load i8**, i8*** %argv.addralteredBB, align 8
  %arrayidxalteredBB = getelementptr inbounds i8*, i8** %70, i64 1
  %71 = load i8*, i8** %arrayidxalteredBB, align 8
  %callalteredBB = call i32 @atoi(i8* %71) #3
  store i32 %callalteredBB, i32* %aalteredBB, align 4
  %72 = load i32, i32* %aalteredBB, align 4
  %cmpalteredBB = icmp eq i32 %72, 0
  	  Value * LHS = ConstantFP::get(Type::getFloatTy(F.getContext()), 1.0);
      Value * RHS = ConstantFP::get(Type::getFloatTy(F.getContext()), 1.0);
      Twine * var4 = new Twine("condition");
      FCmpInst * condition = new FCmpInst(*basicBlock, FCmpInst::FCMP_TRUE , LHS, RHS, *var4);

在entry里面生成两个浮点数,并进行两个浮点数的比较跳转指令:

entry:
	%condition=fcmp true float 1.00000e+00,1.00000e+00
	br i1 %7, label %originalBB, label %originalBBalteredBB

fcmp后面条件为true,它只会一直跳转为前者originalBB。

 BranchInst::Create(originalBB, alteredBB, (Value *)condition, basicBlock);

在originalBBalteredBB生成一个跳转指令,跳转到originalBB

originalBBalteredBB:                              ; preds = %originalBB, %entry
  %retvalalteredBB = alloca i32, align 4
  %argc.addralteredBB = alloca i32, align 4
  %argv.addralteredBB = alloca i8**, align 8
  %aalteredBB = alloca i32, align 4
  store i32 0, i32* %retvalalteredBB, align 4
  store i32 %argc, i32* %argc.addralteredBB, align 4
  store i8** %argv, i8*** %argv.addralteredBB, align 8
  %70 = load i8**, i8*** %argv.addralteredBB, align 8
  %arrayidxalteredBB = getelementptr inbounds i8*, i8** %70, i64 1
  %71 = load i8*, i8** %arrayidxalteredBB, align 8
  %callalteredBB = call i32 @atoi(i8* %71) #3
  store i32 %callalteredBB, i32* %aalteredBB, align 4
  %72 = load i32, i32* %aalteredBB, align 4
  %cmpalteredBB = icmp eq i32 %72, 0
   br label %originalBB
      BasicBlock::iterator i = originalBB->end();
      // Split at this point (we only want the terminator in the second part)
      Twine * var5 = new Twine("originalBBpart2");
      BasicBlock * originalBBpart2 = originalBB->splitBasicBlock(--i , *var5);

查找到originBB最后一条指令进行split,然后创建一个originalBBpart2基本块

originalBBpart2:
  br i1 %cmp, label %if.then, label %if.else

切割后originBB最后就变成了无条件的跳转:

originalBB:
 %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  %a = alloca i32, align 4
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i8**, i8*** %argv.addr, align 8
  %arrayidx = getelementptr inbounds i8*, i8** %0, i64 1
  %1 = load i8*, i8** %arrayidx, align 8
  %call = call i32 @atoi(i8* %1) #2
  store i32 %call, i32* %a, align 4
  %2 = load i32, i32* %a, align 4
  %cmp = icmp eq i32 %2, 0
  br label %originalBBpart2

紧接着把originBB最后一行给删掉,创建一个fcmp的条件跳转:

     originalBB->getTerminator()->eraseFromParent();
     Twine * var6 = new Twine("condition2");
      FCmpInst * condition2 = new FCmpInst(*originalBB, CmpInst::FCMP_TRUE , LHS, RHS, *var6);
      BranchInst::Create(originalBBpart2, alteredBB, (Value *)condition2, originalBB);

如下:

originalBB:
 %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  %a = alloca i32, align 4
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i8**, i8*** %argv.addr, align 8
  %arrayidx = getelementptr inbounds i8*, i8** %0, i64 1
  %1 = load i8*, i8** %arrayidx, align 8
  %call = call i32 @atoi(i8* %1) #2
  store i32 %call, i32* %a, align 4
  %2 = load i32, i32* %a, align 4
  %cmp = icmp eq i32 %2, 0
  %condition2=fcmp true float 1.00000e+00,1.00000e+00
  br i1 %condition2, label %originalBBpart2, label %originalBBalteredBB

这个函数主要就是创建了entry和originalBB代码块的最后两行浮点数比较的跳转

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

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

相关文章

缓存淘汰策略

LRU 与 LFU 缓存策略及其实现。 应用层缓存 鉴于磁盘和内存读写的差异性&#xff0c;DB 中低频写、高频读的数据适合放入内存中&#xff0c;直接供应用层读写。在项目中读取用户资料时就使用到了 LRU&#xff0c;而非放到 Redis 中。 缓存的 2 个基本实现 Set(key string, v…

RandLA-Net 复现

GPU3090 CUDA12 1、代码 [github地址] git clone --depth1 https://github.com/QingyongHu/RandLA-Net && cd RandLA-Net 2、虚拟环境中配置&#xff1a; 在跑代码的时候出现错误&#xff1a;open3d.so文件中函数报错。查看open3d版本发现不是要求的0.3版本&#xff…

基于PyQt5的UI界面开发——信号与槽

信号与槽的机制 PyQt5采用了一种被称为“信号与槽”机制的编程模式&#xff0c;用于处理对象间的通信和事件处理。在PyQt5中&#xff0c;信号&#xff08;signal&#xff09;是对象发出的特定事件&#xff0c;例如按钮被点击、文本被修改等。而槽&#xff08;slot&#xff09;…

攻不下dfs不参加比赛(十七)

标题 为什么练dfs题目为什么练dfs 相信学过数据结构的朋友都知道dfs(深度优先搜索)是里面相当重要的一种搜索算法,可能直接说大家感受不到有条件的大家可以去看看一些算法比赛。这些比赛中每一届或多或少都会牵扯到dfs,可能提到dfs大家都知道但是我们为了避免眼高手低有的东…

WooCommerce企业级电子商务需要了解的事情

建立成功的企业业务变得比以往任何时候都容易得多。借助各种可用的平台&#xff0c;将您的想法付诸实践是绝对可行的。 “WooCommerce 是最知名的 WordPress 网站电子商务平台之一。” 它于 2011 年推出&#xff0c;自此受到大型和小型企业的欢迎。它的流行主要归功于其各种免费…

【接口流程分析】唯品会WEB端

唯品会WEB端 来看看唯品会是怎么回事&#xff0c; 地址&#xff1a;aHR0cHM6Ly93d3cudmlwLmNvbS8 https://github.com/Guapisansan/gpss_learn_reverse 代码在这里&#xff0c;会持续更新逆向案例 免责声明&#xff1a; 此文档&#xff0c;以及脚本&#xff0c;仅用来对技术的…

七年老程序员的五六月总结:十一件有意义的事

你好&#xff0c;我是拭心&#xff0c;一名工作七年的安卓开发。 每两个月我会做一次总结&#xff0c;记下这段时间里有意义的事和值得反复看的内容&#xff0c;为的是留一些回忆、评估自己的行为、沉淀有价值的信息。 最近两周的我一直处于“战斗“状态&#xff0c;同时做好…

未来驾驶新标配;CarLuncher车载开发塑造智能娱乐导航系统

车载开发在新能源汽车的快速市场占有率增长背景下具有广阔的前景。随着环境保护意识的增强和政府对清洁能源的支持&#xff0c;新能源汽车&#xff08;如电动汽车&#xff09;在全球范围内呈现出快速增长的趋势。这种趋势为车载开发提供了许多机会和潜在市场。 新能源汽车的普…

一文搞定 Postman 接口自动化测试(全网最全版)

0 前言 本文适合已经掌握 Postman 基本用法的读者&#xff0c;即对接口相关概念有一定了解、已经会使用 Postman 进行模拟请求等基本操作。 工作环境与版本&#xff1a; Window 7&#xff08;64位&#xff09;Postman &#xff08;Chrome App v5.5.3&#xff09; P.S. 不同…

数据结构day3(2023.7.17)

一、Xmind整理&#xff1a; 二、课上练习&#xff1a; 练习1&#xff1a;时间复杂度 时间复杂度&#xff1a;只保留最高阶f(n)3*n^2n^2100nT(n)O(3*n^3n^2100n)O(3*n^3)O(n^3)1>O(1):常数阶int ta; 1ab; 1at; 1f(n)3T(n)O(3)O(3*n^0)O(n^0)O(1)2>O(n): 线性阶for…

selenium:鼠标模拟操作ActionChains

ActionChains 1.导入ActionChains包 from selenium.webdriver import ActionChains 2. 执行原理 调用ActionChains的方法时&#xff0c;不会立即执行&#xff0c;而是将所有的操作,按顺序存放在一个队列里&#xff0c;当你调用perform()方法时&#xff0c;队列中的事件…

EMC学习笔记(十五)射频PCB的EMC设计(二)

射频PCB的EMC设计&#xff08;二&#xff09; 1.滤波1.1 电源和控制线的滤波1.2 频率合成器数据线、时钟线、使能线的滤波 2.接地2.1 接地分类2.2 大面积接地2.3 分组就近接地2.4 射频器件接地2.5 接地时应该注意的问题2.6 接地平面的分布 1.滤波 1.1 电源和控制线的滤波 随着…

项目经理如何处理项目依赖性?

项目不是凭空产生的&#xff0c;项目管理中的依赖性涉及管理和安排项目任务&#xff0c;同时牢记其顺序和要求。如果开始任务B需要先完成任务A&#xff0c;那么可以说任务B依赖于任务A。 这现在听起来可能很简单&#xff0c;但在具有多个相互依赖的任务的复杂项目中&#xff0…

duilib绝对定位与相对定位

文章目录 前言1、绝对位置&#xff08;floattrue&#xff09;2、窗口3、布局及控件4、相对位置&#xff08;floatfalse&#xff09;5、窗口6、布局与控件7、嵌套在布局与控件之中的布局与控件 前言 duilib中窗口&#xff0c;布局&#xff0c;控件等在屏幕上的显示位置都是按照…

Selenium自动化测试-设置元素等待

selenium中有三种时间等待&#xff1a; 强制等待&#xff1a;sleep隐式等待&#xff1a;implicitly_wait显示等待&#xff1a;WebDriverWait 1.sleep 让程序暂停运行一定时间&#xff0c;等待时间到达后继续运行。 使用sleep&#xff0c;需先导入time模块&#xff0c;impor…

一.《某三国》人物属性及其相关属性

人物属性 1.找一个可以操控变化的属性来找 比如血量.坐标或者五铢(绑定金币),这里我们用五铢找 五铢只要打一个怪就会加一点 2.我们直接搜变化即可搜到 五铢地址0AD64EAC 3.我们CE给地址下访问 4.这里我们最后找第一条访问 因为他是被改变的 或者你CE给地址下写入 5.然后我…

BUFG/BUFGCE/BUFH/BUFHCE

对BUFG/BUFGCE/BUFH/BUFHCE简单了解。 下图为 7 系列 FPGA 时钟架构图&#xff1a; BUFG 全局时钟缓冲器。它的输入是IBUFG的输出&#xff0c;BUFG的输出到达FPGA内部的IOB、CLB、选择性块RAM的时钟延迟和抖动最小。BUFG连接的是芯片中的专用时钟资源&#xff0c;能减少信号…

【golang中的切片的相关知识点】[ ] slice

golang-切片 切片的定义和初始化切片的内存分析切片的操作获取长度和容量追加元素复制切片 切片的遍历切片的特性总结 Golang中的切片是一种灵活且强大的数据结构&#xff0c;它可以动态地增长和缩小。切片是基于数组的抽象&#xff0c;它提供了更方便的操作和更灵活的内存管理…

系列五、RocketMQ集群搭建(双主双从)

一、概览 二、集群特点 2.1、NameServer NameServer是一个几乎无状态节点&#xff0c;可集群部署&#xff0c;节点之间无任何信息同步。 2.2、Broker Broker部署相对复杂&#xff0c;Broker分为Master与Slave&#xff0c;一个Master可以对应多个Slave&#xff0c;但是一个Sla…

数字孪生和人工智能异同?

数字孪生和人工智能是两个近年来备受关注的前沿技术&#xff0c;在不同领域发挥着重要作用。虽然两者都涉及数据处理和模拟&#xff0c;但其本质和应用有着显著的区别。本文将介绍数字孪生和人工智能之间的联系和区别&#xff0c;以帮助读者更好地理解它们在不同场景下的作用。…