尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

从零掌握Joern:基于代码属性图的自动化漏洞挖掘实战指南

从零掌握Joern:基于代码属性图的自动化漏洞挖掘实战指南
📅 发布时间:2026/7/27 11:06:51

1. 项目概述:为什么选择Joern进行漏洞挖掘?

如果你是一名安全研究员或者开发人员,对“漏洞挖掘”这个词一定不陌生。无论是参与SRC(安全应急响应中心)项目,还是进行日常的代码审计,我们都在寻找一种更高效、更深入的方法,来发现那些潜藏在代码深处的安全隐患。传统的漏洞挖掘,比如手动审计、模糊测试或者基于规则的静态扫描,要么对人员经验要求极高,要么误报率居高不下,要么难以发现复杂的逻辑漏洞。尤其是在面对动辄几十万、上百万行代码的大型项目时,靠人力去逐行阅读,无异于大海捞针。

这时候,就需要一种能将代码“结构化”的工具,让我们能以图的形式来“看见”代码的逻辑和数据流。Joern正是这样一个利器。它不是一个简单的代码扫描器,而是一个基于代码属性图(Code Property Graph, CPG)的代码分析平台。简单来说,CPG就像给代码拍了一张立体的“X光片”,把代码的语法结构(AST)、控制流(CFG)和数据流(DFG)等信息融合在一张图里。这样一来,代码中函数如何调用、变量如何传递、数据从哪里来到哪里去,都变得一目了然。基于这张图,我们可以编写查询,像侦探一样沿着数据流路径,精准定位那些可能导致漏洞的“危险组合”。

最近在安全社区,无论是“SRC漏洞挖掘实战”还是“教育SRC漏洞挖掘”,热度一直不减。很多新手朋友想入门,却苦于没有一条清晰的路径。网上资料要么过于零散,要么直接跳到复杂的案例,让人望而却步。这篇内容,我就想从一个实战者的角度,带你从零开始,亲手用Joern构建CPG,并完成一次完整的漏洞挖掘流程。我们不谈空泛的理论,直接上手操作,从环境搭建到编写第一个查询,再到分析一个真实案例,我会把每一步的细节、踩过的坑和私藏的技巧都分享出来。无论你是想入门SRC挖洞的新手,还是想提升自动化审计效率的老手,相信都能从中获得可以直接复用的经验。

2. 核心概念与工具链深度解析

在动手之前,我们必须先吃透两个核心概念:代码属性图(CPG)和Joern工具链。理解它们,是后续一切操作的基础。

2.1 代码属性图(CPG):漏洞挖掘的“全景地图”

你可以把CPG想象成一座城市的数字孪生模型。传统的语法分析(AST)只告诉你这座城市有哪些建筑(函数、语句),控制流分析(CFG)只告诉你街道(执行路径)怎么连接,数据流分析(DFG)只告诉你水管(数据)怎么铺设。而CPG将这三张地图完美叠加,生成了一张包含所有信息的“全景地图”。

  • AST节点:代表了代码的语法单元,比如一个函数声明、一个if语句、一个变量赋值。它反映了代码的静态结构。
  • CFG边:连接AST节点,表示程序执行的可能顺序。比如从一个函数调用指向被调用函数的入口,或者从一个if语句的结束指向两个不同分支的开始。
  • DFG边:连接AST节点,表示数据的定义和使用关系。比如从一个变量被赋值(定义)的地方,指向所有读取这个变量值(使用)的地方。

当这三者结合,威力就显现了。例如,要找一个“用户输入未经净化就直接传递给危险函数(如system)”的漏洞(命令注入)。传统方法可能需要分别匹配危险函数和寻找输入源,再人工判断路径。而在CPG上,这就是一个标准的图遍历问题:找到一个代表“用户输入”(如$_GET[‘cmd’])的节点,沿着DFG边追溯,看它是否最终流向了代表“危险函数”(system)的节点,并且中间没有经过“净化函数”(如escapeshellarg)的节点。Joern的核心能力,就是帮我们高效地完成这种图遍历查询。

2.2 Joern工具链全景与选型考量

Joern不仅仅是一个工具,而是一个生态。对于新手,最容易混淆的是以下几个部分:

  1. Joern (CLI):这是最经典的命令行交互式分析工具。它提供了一个类似数据库查询的界面,你可以导入代码生成CPG,然后用一种叫做Ocular或Cypher的查询语言来探索图。它的优点是灵活、强大,适合深度分析和研究。但缺点是对新手不够友好,需要学习查询语法。
  2. Joern (作为库):Joern的核心是一个Scala库。这意味着你可以用Scala或Java写程序,调用Joern的API来编程式地分析代码,集成到自己的流水线中。这给了开发者最大的自由度。
  3. ShiftLeft Ocular:这是一个商业化的、基于Joern的代码安全分析平台,提供了更友好的UI和自动化能力。但对于个人学习和研究,我们主要关注开源部分。
  4. cpg2vec / ml4code:这些是基于CPG的机器学习扩展,用于代码相似性检测、漏洞模式学习等前沿研究。我们本次实战暂不涉及。

对于从零开始的实战,我强烈建议从 Joern CLI 开始。原因有三:第一,它能让你最直接地接触到CPG的原始数据结构和查询过程,打下坚实的基础;第二,它的所有操作都是可脚本化的,学成之后很容易转化为自动化工具;第三,社区资料和案例大多围绕CLI版本展开,遇到问题更容易找到解决方案。

注意:Joern主要支持C/C++和Java语言的源代码分析,对Python、JavaScript等语言的支持(通过外部工具转换)可能还在完善中或存在限制。在开始前,请确认你的目标代码语言在支持范围内。

3. 从零开始:Joern环境搭建与初体验

理论懂了,接下来就是实战。第一步,把环境搭起来。这里我会提供两种最主流的方法,并分享我踩过的坑。

3.1 方法一:使用Docker(推荐,最省心)

这是最快、最不容易出错的方-法,尤其适合在Linux或macOS系统上操作。

# 1. 拉取官方Docker镜像 docker pull shiftleft/joern # 2. 运行容器,并将本地一个存放待分析代码的目录挂载到容器内 # 假设你的代码放在 /home/user/code_to_analyze 目录 docker run -it --rm -v /home/user/code_to_analyze:/opt/src shiftleft/joern /bin/bash

运行后,你就进入了Joern容器的命令行环境。在容器内,/opt/src目录就对应着你本地的代码目录。

实操心得:

  • 使用-it和--rm参数,表示交互式运行,并且退出后自动删除容器,避免积累一堆停止的容器。
  • 一定要记得用-v参数挂载目录,否则你的代码无法被容器内的Joern访问。
  • 如果是在Windows上使用Docker Desktop,路径写法需要调整,例如-v C:\Users\yourname\code:/opt/src。

进入容器后,直接输入joern命令即可启动Joern CLI。

3.2 方法二:本地安装(适合深度定制)

如果你想更深入地定制,或者需要频繁与本地其他工具交互,可以选择本地安装。Joern是基于Java/Scala的,所以需要先安装Java。

# 1. 确保已安装Java 11或更高版本 java -version # 2. 下载Joern的安装脚本(以Linux/macOS为例) curl -L https://github.com/joernio/joern/releases/latest/download/joern-install.sh -o joern-install.sh chmod +x joern-install.sh ./joern-install.sh # 3. 将Joern加入PATH(根据安装脚本提示操作) # 通常需要将类似 `export PATH=$PATH:$HOME/.joern/joern-cli` 添加到你的shell配置文件(如 ~/.bashrc 或 ~/.zshrc)中 source ~/.bashrc # 4. 验证安装 joern --version

常见问题与排查:

  • 网络问题:如果从GitHub下载安装脚本或Joern本体失败,可能是网络连接问题。可以尝试设置代理(此处不展开)或寻找国内镜像。
  • Java版本不兼容:务必使用Java 11或17等LTS版本。Java 8或某些新版本可能导致兼容性问题。如果报错Unsupported class file major version,就是Java版本不对。
  • 内存不足:分析大型项目时,Joern可能需要较多内存。可以通过环境变量调整JVM内存:export JAVA_OPTS="-Xmx8G -Xms2G"(设置为8GB最大堆内存),然后再运行joern。

3.3 你的第一个CPG:导入并探索代码

环境就绪,让我们导入一个简单的示例代码来感受一下。在容器内或本地终端,启动Joern:

joern

你会看到一个以joern>开头的提示符,这表示你已经进入了交互式查询环境。

我们先准备一个极其简单的C语言文件test.c:

// test.c #include <stdio.h> #include <stdlib.h> #include <string.h> void vulnerable_function(char *input) { char buffer[100]; strcpy(buffer, input); // 潜在的缓冲区溢出 printf("Buffer: %s\n", buffer); } int main(int argc, char **argv) { if (argc > 1) { vulnerable_function(argv[1]); } return 0; }

这个代码有一个经典的栈缓冲区溢出漏洞:vulnerable_function使用不安全的strcpy将外部输入复制到固定大小的缓冲区。

在Joern中,我们这样导入并生成CPG:

joern> importCode(“/opt/src/test.c”, “testcpg”) // 假设test.c在挂载的/opt/src目录下
  • importCode是导入代码的命令。
  • 第一个参数是代码路径。
  • 第二个参数“testcpg”是你给这个CPG工作空间起的名字,可以任意取。

导入成功后,Joern会进行解析并生成CPG。现在,我们可以开始进行一些基础查询了。

joern> cpg.method.name(“vulnerable_function”).l

这条查询的意思是:在CPG中,查找所有方法(函数)名称为“vulnerable_function”的节点,并列出(.l)它们。你应该能看到关于这个函数节点的信息输出。

joern> cpg.method.name(“strcpy”).caller.l

这条查询更有趣:查找所有调用了名为“strcpy”的函数的方法,并列出这些调用者。这能帮助我们快速定位哪些函数里用了危险函数。

初体验要点:

  • Joern查询语言(默认是Ocular,也支持切换到Cypher)的核心是链式调用,从一个起点(如cpg)开始,通过一系列“步骤”(如.method,.name,.caller)来过滤和遍历图。
  • .l是toList的缩写,用于输出结果。类似的还有.p(toJson)用于以JSON格式输出,便于后续处理。
  • 不要怕,多尝试。你可以用cpg.metaData.l查看项目基本信息,用cpg.method.l列出所有方法(对于大项目慎用)。

4. 核心实战:编写查询,挖掘你的第一个漏洞

现在进入最核心的部分:如何编写查询来主动挖掘漏洞。我们继续用上面的test.c作为目标。

4.1 漏洞模式分析:从自然语言到图查询

我们的目标是找到“缓冲区溢出”漏洞。用自然语言描述就是:“找到一个函数,其中存在对strcpy(或strcat,gets等)的调用,并且其目标缓冲区(第一个参数)是一个在栈上分配的、固定大小的数组,而源数据(第二个参数)是来自函数外部的、用户可控的输入。”

在CPG中,我们需要将其分解为几个图遍历的步骤:

  1. 定位危险函数调用:找到所有调用strcpy的节点。
  2. 追溯目标缓冲区:找到这个strcpy调用的第一个参数(即目标缓冲区),并确认它是一个局部数组(在栈上分配)。
  3. 追溯源数据:找到这个strcpy调用的第二个参数(即源数据),并向上追溯,看它是否来源于函数的参数(即外部输入)。

4.2 分步查询实现

在Joern中,我们可以这样实现:

步骤1:找到所有strcpy调用

joern> val strcpyCalls = cpg.call.name(“strcpy”).l

cpg.call表示所有函数调用节点。.name(“strcpy”)过滤出名为strcpy的调用。我们将结果存到变量strcpyCalls中。

步骤2 & 3:对每个调用,分析其参数对于每个调用,我们需要检查它的参数。这需要更精细的遍历。一个更综合的查询可以这样写:

joern> cpg.call.name(“strcpy”).foreach { call => val dest = call.argument(1) // 注意:Joern中参数索引可能从1开始,需验证。第一个参数是目标缓冲区。 val src = call.argument(2) // 第二个参数是源数据 // 检查目标是否是局部变量(粗略检查:它的类型是数组吗?或者它的名字在方法的局部变量列表中?) // 这里简化处理,我们假设目标是一个标识符(变量名) if (dest.isIdentifier) { // 尝试追溯这个dest变量的声明,看它是不是数组类型(这部分查询较复杂,初次可跳过) println(s“潜在危险调用在方法: ${call.method.name}, 目标缓冲区: ${dest.code}”) } // 追溯src的来源 val srcOrigin = src.argumentIn.l // 获取流向这个src参数的节点(即,谁赋值给了它?) // 我们需要检查srcOrigin中是否有来自方法参数的节点 // 一个简单检查:src本身是不是一个参数(Parameter)节点? if (src.isParameter) { println(s“ -> 源数据来自函数参数: ${src.code}, 高危!”) } else { // 更复杂的追溯可以在这里进行 println(s“ -> 源数据: ${src.code}, 需要进一步追溯”) } }

这个查询已经包含了基本的思路,但在实际中,Joern提供了更强大的内置步骤来简化这些操作。例如,可以使用.reachableBy来追溯数据流。

4.3 使用内置数据流分析进行精准挖掘

Joern的强大之处在于其内置的数据流分析引擎。我们可以编写更简洁、更强大的查询:

joern> cpg.call.name(“strcpy”).where(_.argument(1).isIdentifier).where(_.argument(2).reachableBy(cpg.parameter).nonEmpty).l

这个查询的解读:

  • cpg.call.name(“strcpy”):找到所有strcpy调用。
  • .where(_.argument(1).isIdentifier):筛选出其中第一个参数是标识符(变量)的调用。这粗略过滤掉目标不是简单变量的情况(虽然不绝对准确,但是一个好的启发)。
  • .where(_.argument(2).reachableBy(cpg.parameter).nonEmpty):这是关键!筛选出其中第二个参数(源数据)可以被某个函数参数的数据流到达的调用。cpg.parameter代表所有函数参数节点。reachableBy是数据流可达性查询。nonEmpty表示这样的数据流路径存在。
  • 最终.l列出所有满足条件的危险调用。

运行这个查询,它应该能成功捕捉到我们test.c中vulnerable_function里的那个strcpy(buffer, input)调用,因为input是函数参数,并且流向了strcpy的第二个参数。

实操心得:

  • reachableBy是Joern进行漏洞挖掘的“神兵利器”。它自动完成了从“污点源”(如参数、返回值)到“危险函数”(sink)的路径探索。
  • 查询语句需要反复调试和验证。对于一个新项目,先用简单的查询(如找危险函数)摸清代码结构,再逐步增加数据流约束。
  • 可以将常用的查询保存为脚本文件(.sc后缀),在Joern中使用:load script.sc命令加载执行,提高效率。

5. 构建复杂查询与实战案例拆解

掌握了基础查询后,我们来看一个更接近真实SRC挖掘的场景。假设我们审计一个C语言项目,想找“格式化字符串漏洞”。漏洞模式是:用户可控的数据被直接作为printf、sprintf等函数的格式化字符串参数(通常是第一个参数)。

5.1 设计格式化字符串漏洞查询

joern> // 定义“污点源”:来自函数外部的参数 joern> val source = cpg.parameter joern> // 定义“危险函数”(sink):格式化字符串函数族 joern> val sink = cpg.call.name(“printf”, “sprintf”, “fprintf”, “snprintf”) joern> // 执行数据流分析:从source到sink的第一个参数 joern> sink.where(_.argument(1).reachableBy(source)).l

这个查询会列出所有格式化字符串函数的调用点,并且这些调用点的格式化字符串(第一个参数)的数据来源是函数的某个参数。这很可能就是一个漏洞点。

5.2 案例实战:分析一个小型开源项目

让我们找一个真实的小型C项目来练手。比如,我们可以用wget下载一个历史版本(注意选择有漏洞的版本进行教育研究,务必在合法授权环境下进行)。

# 在容器内或本地 cd /opt/src wget https://example.com/some_old_c_project.tar.gz # 此处为示例,请替换为实际可分析的项目 tar -xzf some_old_c_project.tar.gz cd project_dir

在Joern中导入:

joern> importCode(“/opt/src/project_dir”, “realcpg”)

生成CPG可能需要几分钟,取决于项目大小。

实战流程:

  1. 侦察:先运行cpg.metaData.l看语言、文件数。运行cpg.method.count看方法总数,有个大致概念。
  2. 寻找入口点:对于C程序,通常从main函数开始。cpg.method.name(“main”).l。
  3. 应用漏洞查询:将上面编写的格式化字符串漏洞查询,或者缓冲区溢出查询,直接运行在这个CPG上。
  4. 人工复核:查询结果会给出文件名和行号。切记,工具给出的是“潜在漏洞”,不是最终结论!你必须用文本编辑器或IDE打开对应文件,仔细阅读那几行代码及其上下文,判断是否真的可利用。例如,查询可能把printf(“Hello %s”, user_input)报出来,但这里user_input是第二个参数,第一个参数是常量字符串“Hello %s”,这通常是安全的。你需要复核的是printf(user_input)这种模式。

5.3 查询的优化与组合

单一的漏洞模式往往不够。高价值的漏洞常是“组合漏洞”。例如,一个先进行长度检查但检查可被绕过的拷贝操作。

// 查找可能被绕过的长度检查:寻找 memcpy 或 strncpy,其长度参数来源于一个之前被比较的变量 joern> val sizeChecks = cpg.call.name(“<“, “<=”, “>”, “>=”).where(_.argument(2).isConstant).l // 找到比较操作,且比较的另一边是常量 // 假设我们关心 array_size < USER_INPUT 这种比较 // 我们需要更复杂的逻辑来关联这个比较和后续的拷贝操作,这需要用到CPG的“控制流”边。 // 这展示了更高级的查询,可能需要遍历控制流图(CFG)。

对于这种复杂逻辑,Joern支持切换到Cypher查询语言(这是图数据库Neo4j的查询语言),它在表达复杂图模式时有时更直观。在Joern中可以使用:importCypher或相关命令切换模式。

高级技巧:

  • 利用.store和.load:可以将中间查询结果(如所有危险函数调用)存储起来(.store),后续查询直接加载(.load),避免重复计算,提升分析大型项目的效率。
  • 关注Joern社区脚本:Joern开源社区提供了很多预定义的、针对常见漏洞的查询脚本(如joern/joern-scripts仓库)。学习这些脚本是快速提升的捷径。
  • 结合代码变更历史:在真实SRC挖掘中,结合git log和git diff,用Joern分析新增的代码或修改的代码,往往能事半功倍,因为新代码引入漏洞的概率相对更高。

6. 避坑指南与效能提升技巧

在大量实战后,我总结了一些常见的“坑”和提升效率的技巧,这些你在官方文档里未必看得到。

6.1 常见问题与排查表

问题现象可能原因解决方案
importCode失败或报错1. 代码路径错误。
2. 语言不支持或版本不兼容。
3. 代码本身编译错误(Joern依赖解析器)。
1. 检查路径,使用绝对路径。
2. 确认Joern是否支持该语言(C/C++/Java)。对于其他语言,查看是否有外部转换工具(如,对于Go,可能需要先用c2cpg等工具转换)。
3. 尝试导入一个最简单的单文件测试,确认环境正常。对于大型项目,确保其能在普通编译器下通过语法检查。
查询结果为空或不符合预期1. 查询语法错误。
2. CPG生成不完整,某些节点或边缺失。
3. 对代码的抽象理解与CPG实际表示有偏差。
1. 从最简单的查询开始验证,如cpg.method.name.limit(5).l。
2. 使用cpg.metaData.l查看解析是否成功。对于C/C++,复杂的宏和条件编译可能导致解析丢失代码。尝试调整解析参数或预处理代码。
3. 使用.dot或.json导出可疑节点附近的小子图,可视化查看CPG的实际结构。例如cpg.method.name(“targetFunc”).dot.l会生成DOT格式的图。
数据流分析 (reachableBy) 找不到预期路径1. 数据流分析精度限制。Joern的指针分析、别名分析在复杂情况下可能不精确。
2. 路径被清理函数(Sanitizer)阻断,但查询未考虑。
3. 跨文件或跨函数的数据流需要更精确的配置。
1. 这是静态分析的固有限制。需要人工复核,或尝试放宽/收紧源和汇的定义。
2. 在查询中明确排除经过清理函数的路径。例如:...whereNot(_.argument(2).reachableBy(source).via(cpg.call.name(“escapeFunc”)))(注意:via语法可能需根据版本调整)。
3. 确保importCode时包含了所有相关源文件。对于库函数调用,可能需要配置桩函数(stub)来模拟数据流。
分析大型项目时内存不足或速度极慢1. JVM堆内存设置不足。
2. 项目本身极大,CPG过于复杂。
1. 如前所述,设置JAVA_OPTS环境变量增加内存(如-Xmx16G)。
2. 考虑分模块分析。先导入部分关键模块,或者编写更精确的查询减少内存中的中间数据。使用.store存储中间结果避免重复计算。

6.2 效能提升与集成自动化

  1. 脚本化是王道:不要总是在交互式命令行里操作。将你的漏洞挖掘查询写成完整的Scala脚本(.sc文件)。这样你可以进行版本管理、参数化、并集成到CI/CD流水线中。

    // find_strcpy_bof.sc @main def main() = { val projectCPG = CpgLoader.load(“/path/to/cpg.bin”) // 也可以直接从已导入的工作空间加载 val results = projectCPG.call.name(“strcpy”) .where(_.argument(2).reachableBy(projectCPG.parameter)) .l results.foreach { call => println(s“文件: ${call.location.filename}, 行号: ${call.location.lineNumber}”) println(s“ 代码: ${call.code}”) println(s“ 所在函数: ${call.method.name}”) println(“---”) } }

    然后在命令行使用joern --script find_strcpy_bof.sc --params cpgPath=/path/to/cpg.bin来运行。

  2. 与现有工具链结合:Joern可以成为你武器库中的一环。例如:

    • 与Semgrep联动:用Semgrep做快速的、基于模式的初步筛选,再用Joern对筛选出的高危点进行深度的、过程间的数据流分析。
    • 与IDA/Ghidra联动:对于二进制文件,可以先反编译,再将反编译的伪代码(或某种中间表示)导入Joern进行分析。这需要额外的转换工具或脚本。
  3. 建立自定义漏洞知识库:将针对特定框架(如某个Web框架、某个网络协议库)的专用漏洞模式编写成Joern查询脚本,积累成你自己的“武器库”。下次再审计类似项目时,效率倍增。

  4. 可视化辅助:对于特别复杂的漏洞链,Joern的查询结果可能是一堆ID和文本,难以理解。学会使用.dot导出功能,将相关的CPG子图导出为DOT文件,然后用Graphviz工具生成图片,可以直观地看到函数调用链和数据流向,极大帮助人工分析。

最后,我想强调的是,Joern这类基于CPG的静态分析工具,其价值不在于完全替代人工,而在于放大安全研究员的能力。它将我们从繁琐的代码阅读中解放出来,让我们能专注于更高级别的逻辑推理和模式识别。它给出的每一个“潜在漏洞”,都是一个需要你运用经验和智慧去验证的“线索”。从零构建这套流程起初会有学习曲线,但一旦跑通,你会发现面对海量代码时,你拥有了前所未有的洞察力和效率。真正的漏洞挖掘,是工具的科学性与人的艺术性的结合。

相关新闻

  • TMS320C5506 DSP开发实战:内存映射寄存器与中断系统深度解析
  • 2026江南4-5日浪漫出游攻略|情侣专属苏沪杭江南古镇慢游纯玩旅行指南 - 纯玩旅游攻略指南
  • Python粒子系统实战:用Pygame实现烟花模拟动画

最新新闻

  • AI+GitLab CI/CD自动化代码审计体系实战搭建教程
  • 30分钟部署悟空AICRM:Docker容器化AI客户关系管理系统实战指南
  • 高效开源RAW处理器深度解析:5大模块打造专业摄影工作流
  • TrollInstallerX在iPhone 14系列上的内核下载失败问题深度解析与终极解决方案
  • 2026南京靠谱装修公司有哪些?本土优质家装品牌甄选汇总 - 装修新知
  • 3分钟掌握跨平台翻译神器:Pot-Desktop的完整使用指南

日新闻

  • OpenClaw开源智能体网关:AI助手与即时通讯的完美融合
  • 写一个简单的sh脚本
  • 2026年 西安缝隙天线厂家:5G通信与车载天线专业定制供应商深度分析 - 卓企推荐

周新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号