【KLEE】源码阅读笔记----KLEE执行流程

本文架构

  • 1. 动机
  • 2.KLEE简介
  • 3.KLEE的代码工程结构
  • 4. 从KLEE主函数入手
    • main函数
      • step1: 初始化
      • step2:加载.bc文件
      • 进行符号执行
    • 读取测试用例
    • 输出日志信息

1. 动机

最近准备对KLEE进行修改使其符合我的需要,因此免不了需要对源码进行修改。读懂源码是对在其进行修改之前的必经之路。但其工程量巨大,该如何下手呢?

于是我将阅读源码过程中所得分享出来,希望能为同样学习KLEE的同行之人提供一些参考。内容可能写的存在纰漏,还请大家及时指出,不吝赐教。
程序分析-klee工具分析
符号执行, KLEE 与 LLVM

2.KLEE简介

KLEE(可读作“克利”)是一个基于符号执行的自动化测试工具,旨在帮助发现软件中的错误和漏洞。该工具可以用于分析 C/C++ 程序,并生成能够触发程序错误路径的测试用例。KLEE的主要目标是执行程序的所有可能路径,而不仅仅是具体输入下的一条路径。通过符号执行技术,它能够在未提供具体输入的情况下模拟程序执行的各种路径,从而发现隐藏在程序中的潜在漏洞。

klee-log

KLEE的核心思想是将程序中的输入视为符号(symbol),而不是具体的数值。这意味着在执行过程中,KLEE不会给定实际输入值,而是用符号代替输入,从而创建了一种执行路径的符号表示。然后,KLEE使用约束求解器来解决程序中各种条件语句的约束,以确定是否存在输入,能够导致程序执行到不同的路径上。

KLEE的工作流程可以分为几个主要步骤:

  • 符号执行: KLEE通过对程序进行符号执行,以符号形式代表程序的输入和执行路径。在执行过程中,它记录了执行路径上的每个条件分支以及相应的约束条件。
  • 路径探索: KLEE使用路径探索技术来导航程序的不同执行路径。它会尝试通过不同的路径执行程序,并在执行过程中收集约束条件。
  • 约束求解: 在执行过程中,KLEE将收集到的约束条件传递给约束求解器,以确定是否存在一组输入能够满足这些约束条件。如果找到了满足条件的输入,那么就找到了一条可以导致特定程序路径执行的输入序列。
  • 测试用例生成: 当约束求解器找到满足条件的输入时,KLEE会生成相应的测试用例,并将其用于进一步的测试和验证。

KLEE的强大之处在于它能够自动化地发现程序中的潜在错误,例如内存泄漏、空指针解引用、整数溢出等。通过覆盖程序的多个执行路径,KLEE可以提高测试覆盖率,从而增加程序的健壮性和可靠性。

3.KLEE的代码工程结构

从【Github】KLEE: Symbolic Execution Engine下载KLEE的源代码。

git clone https://github.com/klee/klee.git

使用你电脑中的IDE(本文使用的是jetbrain公司的CLion)打开后可以看到其工程结构如下所示:

klee-archi
以弄懂其大致流程为目标导向,我们只需要关注其includelib文件夹。
其中:

  • include文件夹包含了KLEE的所有接口信息,你可以在该文件下获取大部分KLEE的数据结构信息。
  • lib文件夹包含了KLEE核心内容的具体实现,即我们需要阅读的大部分源码。将该文件夹展开还能进一步获取信息:
    lib-archi
文件夹名存放内容说明
lib/Basic Low level support for both klee and kleaver which should be independent of LLVM.
lib/SupportHigher level support, but only used by klee. This can use LLVM facilities.
lib/ExprThe core kleaver expression library.
lib/SolverThe kleaver solver library.
lib/Moduleklee facilities for working with LLVM modules, including the shadow module/instruction structures we use during execution.
lib/CoreThe core symbolic virtual machine.

其中Core是我们最关心的内容,里面包含了对符号执行模拟器(Executor)、符号执行树(ExecutionTree)以及符号执行状态(ExecutionState)等关键概念的定义(.h)和实现(.cpp)。

|-- AddressSpace.cpp
|-- AddressSpace.h
|-- CallPathManager.cpp
|-- CallPathManager.h
|-- Context.cpp
|-- Context.h
|-- CoreStats.cpp
|-- CoreStats.h
|-- ExecutionState.cpp
|-- ExecutionState.h
|-- Executor.cpp
|-- Executor.h
|-- ExecutorUtil.cpp
|-- ExternalDispatcher.cpp
|-- ExternalDispatcher.h
|-- GetElementPtrTypeIterator.h
|-- ImpliedValue.cpp
|-- ImpliedValue.h
|-- Memory.cpp
|-- Memory.h
|-- MemoryManager.cpp
|-- MemoryManager.h
|-- MergeHandler.cpp
|-- MergeHandler.h
|-- PTree.cpp
|-- PTree.h
|-- Searcher.cpp
|-- Searcher.h
|-- SeedInfo.cpp
|-- SeedInfo.h
|-- SpecialFunctionHandler.cpp
|-- SpecialFunctionHandler.h
|-- StatsTracker.cpp
|-- StatsTracker.h
|-- TimingSolver.cpp
|-- TimingSolver.h
|-- UserSearcher.cpp
|-- UserSearcher.h

但大家也可以看见,内容过多,虽然去除头文件但依然还有十几个文件。回想当初学习C语言时,面对繁多的函数定义,我们要了解一个程序的执行流程(基本执行思路)时,都是从主函数入手的。这给我们提供了一个有益的思路:从主函数开始我们的阅读!
当然,也有的文章直接从core下的文件入手,这里给供大家参考吧~
【安全客Blog】KLEE 源码阅读笔记

4. 从KLEE主函数入手

KLEE的主函数并不在我们上述的重要工程目录中。它位于tool/klee/main.cpp中我们点开这个文件后可以发现:(这TM也有近两千行代码~)。一点开Structure,五花八门的函数定义和结构体定义。

在这里插入图片描述
但!没关系,我们关注的只有main函数!(大概位于1200行左右,由于我添加了注释所以跟原始的可能有偏差)

main函数

让我们看看主函数主要做了什么。

step1: 初始化

  • step1: 初始化加载各种初始环境
  • step2: 调用parseArguments()分析你在命令行中对KLEE做出的配置。
  • step3: 设置异常处理函数SetInterruptFunction()
 KCommandLine::KeepOnlyCategories(
     {&ChecksCat,   ... &ExecTreeCat});
  llvm::InitializeNativeTarget();//初始化环境

  parseArguments(argc, argv);//分析传入的命令行参数
..
  //设置异常处理函数
  // 会报一些错
  sys::SetInterruptFunction(interrupt_handle);

step2:加载.bc文件

现在开始加载.bc字节码文件进行符号分析

int main(int argc, char **argv, char **envp) 

  // Load the bytecode...
  // 加载字节码
  std::string errorMsg;
  LLVMContext ctx;
...

  std::vector<std::unique_ptr<llvm::Module>> loadedModules;
  /**
   * loadFile 需要解析bc文件,bc文件是一个整体运行单元
   */
  if (!klee::loadFile(InputFile, ctx, loadedModules, errorMsg)) {
    klee_error("error loading program '%s': %s", InputFile.c_str(),
               errorMsg.c_str());
  }

  // linkModule 将bc文件中的module合并为一个整体的module
  std::unique_ptr<llvm::Module> M(klee::linkModules(
      loadedModules,
      "" /* link all modules together */,
      errorMsg));
  if (!M) {//链接错误的话,报错
    klee_error("error loading program '%s': %s", InputFile.c_str(),
               errorMsg.c_str());
  }
  //链接完成,返回mainModule
  llvm::Module *mainModule = M.get();
...
  // 将这个module作为第一个项
  // Push the module as the first entry
  loadedModules.emplace_back(std::move(M));
  std::string LibraryDir = KleeHandler::getRunTimeLibraryPath(argv[0]);
  //配置module基本信息
  Interpreter::ModuleOptions Opts(LibraryDir.c_str(), EntryPoint, opt_suffix,
                                  /*Optimize=*/OptimizeModule,
                                  /*CheckDivZero=*/CheckDivZero,
                                  /*CheckOvershift=*/CheckOvershift);
  // 遍历已经加载完毕的modules,以找到主函数main
  for (auto &module : loadedModules) {
    mainFn = module->getFunction("main");
    if (mainFn)
      break;
  }
  // 找到入口点
  if (EntryPoint.empty())
    klee_error("entry-point cannot be empty");

  // 找到入口函数
  for (auto &module : loadedModules) {
    entryFn = module->getFunction(EntryPoint);
    if (entryFn)
      break;
  }

  if (!entryFn)
    klee_error("Entry function '%s' not found in module.", EntryPoint.c_str());


  //如果设置了POSIX运行时环境
  if (WithPOSIXRuntime) {
   ...
  }
  // 如果设置了UBSan运行时
  if (WithUBSanRuntime) {
    ...
  }

  // 如果设置了libcxx
  if (Libcxx) {
	...
  }
// gen
  switch (Libc) {
 ..
  }
  // 检查是否成功加载字节码库
  for (const auto &library : LinkLibraries) {
    if (!klee::loadFile(library, mainModule->getContext(), loadedModules,
                        errorMsg))
      klee_error("error loading bitcode library '%s': %s", library.c_str(),
                 errorMsg.c_str());
  }
  int pArgc;
  char **pArgv;
  char **pEnvp;
  //如果设置了Environ 环境
  if (Environ != "") {
    std::vector<std::string> items;
    // 打开环境配置信息
    std::ifstream f(Environ.c_str());

    if (!f.good())
      klee_error("unable to open --environ file: %s", Environ.c_str());
    //读取环境配置文件
    while (!f.eof()) {
      std::string line;
      std::getline(f, line);
      line = strip(line);
      if (!line.empty())
        items.push_back(line);
    }//end of the while(!f.eif())
    f.close();//关闭文件
    pEnvp = new char *[items.size()+1];
    unsigned i=0;
    for (; i != items.size(); ++i)
      pEnvp[i] = strdup(items[i].c_str());
    pEnvp[i] = 0;
  } else {
    pEnvp = envp;
  }

  pArgc = InputArgv.size() + 1;
  pArgv = new char *[pArgc];
  for (unsigned i=0; i<InputArgv.size()+1; i++) {
    std::string &arg = (i==0 ? InputFile : InputArgv[i-1]);
    unsigned size = arg.size() + 1;
    char *pArg = new char[size];

    std::copy(arg.begin(), arg.end(), pArg);
    pArg[size - 1] = 0;

    pArgv[i] = pArg;
  }//end of for

进行符号执行

Interpreter是进行符号执行的重要部件

  Interpreter::InterpreterOptions IOpts;
  IOpts.MakeConcreteSymbolic = MakeConcreteSymbolic;
  KleeHandler *handler = new KleeHandler(pArgc, pArgv);
  Interpreter *interpreter =
    theInterpreter = Interpreter::create(ctx, IOpts, handler);
  // 条件为假则终止程序继续执行
  assert(interpreter);
  handler->setInterpreter(interpreter);

  //输出详细信息(info)
  for (int i = 0; i < argc; i++)
    //逐个输出你刚才设置的命令行参数
    handler->getInfoStream() << argv[i] << (i + 1 < argc ? " " : "\n");
  handler->getInfoStream() << "PID: " << getpid() << "\n";

  // 最终的module
  // Get the desired main function.  klee_main initializes uClibc
  // locale and other data and then calls main.
  auto finalModule = interpreter->setModule(loadedModules, Opts);
  entryFn = finalModule->getFunction(EntryPoint);
  if (!entryFn)
    klee_error("Entry function '%s' not found in module.", EntryPoint.c_str());

  externalsAndGlobalsCheck(finalModule);

  //重放路径
  std::vector<bool> replayPath;
  if (!ReplayPathFile.empty()) {
    //加载重放路径文件
    KleeHandler::loadPathFile(ReplayPathFile, replayPath);
    interpreter->setReplayPath(&replayPath);
  }
// 这一部分也是打印到日志info里面
  // 开始时间
  auto startTime = std::time(nullptr);
  { // output clock info and start time
    std::stringstream startInfo;
    startInfo << time::getClockInfo()
              << "Started: "
              << std::put_time(std::localtime(&startTime), "%Y-%m-%d %H:%M:%S") << '\n';
    handler->getInfoStream() << startInfo.str();
    handler->getInfoStream().flush();
    // 输出到info文件中
  }

读取测试用例

  // 读取用例文件
  if (!ReplayKTestDir.empty() || !ReplayKTestFile.empty()) {
    assert(SeedOutFile.empty());
    assert(SeedOutDir.empty());

    std::vector<std::string> kTestFiles = ReplayKTestFile;
    for (std::vector<std::string>::iterator
           it = ReplayKTestDir.begin(), ie = ReplayKTestDir.end();
         it != ie; ++it)
      KleeHandler::getKTestFilesInDir(*it, kTestFiles);
    std::vector<KTest*> kTests;
    for (std::vector<std::string>::iterator
           it = kTestFiles.begin(), ie = kTestFiles.end();
         it != ie; ++it) {
      KTest *out = kTest_fromFile(it->c_str());
      if (out) {
        kTests.push_back(out);
      } else {
        klee_warning("unable to open: %s\n", (*it).c_str());
      }
    }

    if (RunInDir != "") {
      int res = chdir(RunInDir.c_str());
      if (res < 0) {
        klee_error("Unable to change directory to: %s - %s", RunInDir.c_str(),
                   sys::StrError(errno).c_str());
      }
    }

    unsigned i=0;
    for (std::vector<KTest*>::iterator
           it = kTests.begin(), ie = kTests.end();
         it != ie; ++it) {
      KTest *out = *it;
      interpreter->setReplayKTest(out);
      llvm::errs() << "KLEE: replaying: " << *it << " (" << kTest_numBytes(out)
                   << " bytes)"
                   << " (" << ++i << "/" << kTestFiles.size() << ")\n";
      // XXX should put envp in .ktest ?
      interpreter->runFunctionAsMain(entryFn, out->numArgs, out->args, pEnvp);
      if (interrupted) break;
    }//end of for
    interpreter->setReplayKTest(0);
    //当测试用例不为空
    while (!kTests.empty()) {
      kTest_free(kTests.back());
      kTests.pop_back();
    }
  }
  //当replay路径为空时,从SeedOutFile中读取用例生成种子
  else {
    std::vector<KTest *> seeds;
    for (std::vector<std::string>::iterator
           it = SeedOutFile.begin(), ie = SeedOutFile.end();
         it != ie; ++it) {
      KTest *out = kTest_fromFile(it->c_str());
      if (!out) {
        klee_error("unable to open: %s\n", (*it).c_str());
      }
      seeds.push_back(out);
    }
    //输入测试用例
    for (std::vector<std::string>::iterator
           it = SeedOutDir.begin(), ie = SeedOutDir.end();
         it != ie; ++it) {
      std::vector<std::string> kTestFiles;
      KleeHandler::getKTestFilesInDir(*it, kTestFiles);

      for (std::vector<std::string>::iterator
             it2 = kTestFiles.begin(), ie = kTestFiles.end();
           it2 != ie; ++it2) {
        //从文件中读取用例
        KTest *out = kTest_fromFile(it2->c_str());
        if (!out) {
          klee_error("unable to open: %s\n", (*it2).c_str());
        }
        // 将out加入用例队列
        seeds.push_back(out);
      }
      
      // kTest是一种记录程序执行路径的文件格式,文件包含了程序
      // 执行结束时的状态信息,如寄存器值、内存的内容等
      if (kTestFiles.empty()) {
        klee_error("seeds directory is empty: %s\n", (*it).c_str());
      }
    }
    // 如果存在测试用例,开始使用其进行测试
    if (!seeds.empty()) {
      klee_message("KLEE: using %lu seeds\n", seeds.size());
      interpreter->useSeeds(&seeds);
    }// end of if
    if (RunInDir != "") {
      int res = chdir(RunInDir.c_str());
      if (res < 0) {
        klee_error("Unable to change directory to: %s - %s",
                   RunInDir.c_str(),
                   sys::StrError(errno).c_str());
      }
    }// end of if

    interpreter->runFunctionAsMain(entryFn, pArgc, pArgv, pEnvp);
// 释放种子
    while (!seeds.empty()) {
      // 释放最后一个队列中的最后一个用例
      kTest_free(seeds.back());
      seeds.pop_back();
    }
  }
  

输出日志信息

  // 结束时间,计算测试执行时间。输出日志信息
  auto endTime = std::time(nullptr);
  { // output end and elapsed time
    std::uint32_t h;
    std::uint8_t m, s;
    std::tie(h,m,s) = time::seconds(endTime - startTime).toHMS();
    std::stringstream endInfo;
    endInfo << "Finished: "
            << std::put_time(std::localtime(&endTime), "%Y-%m-%d %H:%M:%S") << '\n'
            << "Elapsed: "
            << std::setfill('0') << std::setw(2) << h
            << ':'
            << std::setfill('0') << std::setw(2) << +m
            << ':'
            << std::setfill('0') << std::setw(2) << +s
            << '\n';
            handler->getInfoStream() << endInfo.str();
    handler->getInfoStream().flush();
  }

  // 释放所有参数
  // Free all the args.
  for (unsigned i=0; i<InputArgv.size()+1; i++)
    //释放数组
    delete[] pArgv[i];
  delete[] pArgv;

  delete interpreter;

  // 获取统计信息
  uint64_t queries =
    *theStatisticManager->getStatisticByName("SolverQueries");
...

  uint64_t forks =
    *theStatisticManager->getStatisticByName("Forks");

  handler->getInfoStream()
    << "KLEE: done: explored paths = " << 1 + forks << "\n";

  // Write some extra information in the info file which users won't
  // necessarily care about or understand.?
  // 查询的相关信息,这的结果体现在info文件中
  if (queries)
    handler->getInfoStream()
      << "KLEE: done: avg. constructs per query = "
                             << queryConstructs / queries << "\n";
  handler->getInfoStream()
    << "KLEE: done: total queries = " << queries << "\n"
    << "KLEE: done: valid queries = " << queriesValid << "\n"
    << "KLEE: done: invalid queries = " << queriesInvalid << "\n"
    << "KLEE: done: query cex = " << queryCounterexamples << "\n";

  // 一些统计的信息
  std::stringstream stats;
  stats << '\n'
        << "KLEE: done: total instructions = " << instructions << '\n'
        << "KLEE: done: completed paths = " << handler->getNumPathsCompleted()
        << '\n'
        << "KLEE: done: partially completed paths = "
        << handler->getNumPathsExplored() - handler->getNumPathsCompleted()
        << '\n'
        << "KLEE: done: generated tests = " << handler->getNumTestCases()
        << '\n';
  return 0;
}

这一部分也就对应我们测试后生成的info文件中的内容
info

几个月前学习时写的草稿,若有错误和不足,还望不吝赐教。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【hackmyvm】soul靶机wp

tags: HMVrbash绕过图片隐写PHP配置解析 1. 基本信息^toc 文章目录 1. 基本信息^toc2. 信息收集3. 图片解密3.1. 爆破用户名3.2. 绕过rbash3.3. 提权检测 4. 获取webshell4.1. 修改php配置 5. www-data提权gabriel6. gabriel提取到Peter7. Peter提权root 靶机链接 https://ha…

macos 隐藏、加密磁盘、文件

磁盘加密 打开磁盘工具 点击添加 设置加密参数 设置密码 查看文件 不用的时候右键卸载即可使用的时候装载磁盘&#xff0c;并输入密码即可 修改密码 解密 加密&#xff0c;输入密码即可 禁止开机自动挂载此加密磁盘 如果不禁止自动挂载磁盘&#xff0c;开机后会弹出输入…

基于OpenCV和Python的人脸识别系统_django

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 用户管理 公告信息管理 操作日志管理 用户登录界面 用户…

如何永久解决Apache Struts文件上传漏洞

Apache Struts又双叒叕爆文件上传漏洞了。 自Apache Struts框架发布以来&#xff0c;就存在多个版本的漏洞&#xff0c;其中一些漏洞涉及到文件上传功能。这些漏洞可能允许攻击者通过构造特定的请求来绕过安全限制&#xff0c;从而上传恶意文件。虽然每次官方都发布补丁进行修…

回归预测 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络多输入单输出回归预测

回归预测 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现CNN-LSTM卷积长短期记忆神经网络多输入单输出回归…

LeetCode - Google 校招100题 第5天 双指针(Two Pointers) (11题)

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/144742777 LeetCode 合计最常见的 112 题: 校招100题 第1天 链表(List) (19题)校招100题 第2天 树(Tree) (21题)校招100题 第3天 动态规划(DP) (20题)

【无人机】无人机测绘路径优化策略与实践:探索高效、精准的测绘技术路径

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;传知代码 欢迎大家点赞收藏评论&#x1f60a; 目录 一、背景介绍二、算法原理&#xff08;一&#xff09;算法模型构建&#xff08;二&#xff09;算法求解流程 三、代码实现&#xff08;一&…

Anaconda搭建Python虚拟环境并在Pycharm中配置(小白也能懂)

为什么要搭建虚拟环境&#xff1f; 搭建虚拟环境的主要目的是为了解决多个Python项目之间可能存在的库冲突问题。当你在同一台计算机上运行多个Python项目时&#xff0c;不同的项目可能会依赖于不同版本的库或者相同版本的库的不同补丁。如果所有项目都共享相同的Python环境&am…

mac_录屏

参考&#xff1a; mac m1上系统内录方法BlackHole代替soundflower录音(附安装包) https://blog.csdn.net/boildoctor/article/details/122765119录屏后没声音&#xff1f;这应该是 Mac&#xff08;苹果电脑&#xff09; 内录声音最优雅的解决方案了 https://www.bilibili.com/…

upload-labs关卡记录13

这里和关卡12非常类似&#xff0c;唯一不同就是12关用到get方法&#xff0c;这里用到post方法。因此对应的截断方式也不一样&#xff0c;依旧是使用我们的bp进行抓包&#xff0c; 然后依旧是在upload后加上shell.php&#xff0c;这里用是为了hex时好区别我们要在哪里更改&#…

网络管理-期末项目(附源码)

环境&#xff1a;网络管理 主机资源监控系统项目搭建 &#xff08;保姆级教程 建议点赞 收藏&#xff09;_搭建网络版信息管理系统-CSDN博客 效果图 下面3个文件的项目目录(python3.8.8的虚拟环境) D:\py_siqintu\myproject5\Scripts\mytest.py D:\py_siqintu\myproject5\Sc…

通过GRE协议组建VPN网络

GRE&#xff08;Generic Routing Encapsulation&#xff0c;通用路由封装协议&#xff09;协议是一种简单而有效的封装协议&#xff0c;它在网络中的广泛应用&#xff0c;比如在构建VPN网络。   GRE是一种封装协议&#xff0c;它允许网络层协议&#xff08;如IP&#xff09;的…

kafka的备份策略:从备份到恢复

文章目录 一、全量备份二、增量备份三、全量恢复四、增量恢复 前言&#xff1a;Kafka的备份的单元是partition&#xff0c;也就是每个partition都都会有leader partiton和follow partiton。其中leader partition是用来进行和producer进行写交互&#xff0c;follow从leader副本进…

CHM助手 >> 如何安装CHM助手

1 如何安装CHM助手 下载CHM助手.ezip&#xff0c;下载地址打开EverEdit&#xff0c;选择主菜单“扩展 -> 扩展管理 -> 从本地文件安装扩展”&#xff0c;在弹出的文件浏览窗口中选择插件安装包&#xff0c;如下图所示&#xff1a; &#x1f56e;说明&#xff1a;   …

vulnhub靶场【shuriken】之node

前言 靶机&#xff1a;shuriken-node&#xff0c;ip地址192.168.1.127 攻击&#xff1a;kali&#xff0c;ip地址192.168.1.16 主机发现 使用arp-scan -l或者netdiscover -r 192.168.1.1/24扫描 信息收集 使用nmap扫描端口 网站信息探测 访问8080端口网站&#xff0c;可以…

数据仓库工具箱—读书笔记02(Kimball维度建模技术概述04、使用一致性维度集成)

Kimball维度建模技术概述 记录一下读《数据仓库工具箱》时的思考&#xff0c;摘录一些书中关于维度建模比较重要的思想与大家分享&#x1f923;&#x1f923;&#x1f923; 第二章前言部分作者提到&#xff1a;技术的介绍应该通过涵盖各种行业的熟悉的用例展开&#xff08;赞同…

[实战]推流服务SRS安装

业务场景 在Web浏览器端展示摄像头的视频数据。 协议 物联代理推流协议&#xff1a;rtmp 浏览器器拉流协议&#xff1a;http-flv 推流方案 1、Nginx加nginx-http-flv-modules模块 2、采用SRS服务器 推流服务SRS网站&#xff1a;https://ossrs.io/lts/zh-cn/ 推流服务…

PH热榜 | 2024-12-25

1. Assistive24 标语&#xff1a;为残障人士提供的免费辅助技术 介绍&#xff1a;Assistive24 是一款免费的 Chrome 浏览器扩展程序&#xff0c;可以帮助患有注意力缺陷多动障碍 (ADHD)、阅读障碍 (dyslexia) 和低视力等障碍的用户更方便地浏览网页。它提供语音导航、自定义…

Java中三大构建工具的发展历程(Ant、Maven和Gradle)

&#x1f438; 背景 我们要写一个Java程序&#xff0c;一般的步骤是编译&#xff0c;测试&#xff0c;打包。 这个构建的过程&#xff0c;如果文件比较少&#xff0c;我们可以手动使用java, javac,jar命令去做这些事情。但当工程越来越大&#xff0c;文件越来越多&#xff0c…

自学记录HarmonyOS Next DRM API 13:构建安全的数字内容保护系统

在完成了HarmonyOS Camera API的开发之后&#xff0c;我开始关注更复杂的系统级功能。在浏览HarmonyOS Next文档时&#xff0c;我发现了一个非常有趣的领域&#xff1a;数字版权管理&#xff08;DRM&#xff09;。最新的DRM API 13提供了强大的工具&#xff0c;用于保护数字内容…