十分钟的爬虫erAST解混淆

article/2025/9/25 22:37:30

前言

本文大约4000字,阅读大约时间10分钟。

可以一口气读完入门在爬虫er手中如何使用AST去解混淆。

正文

抽象语法树(Abstract Syntax Tree)通常被称为AST语法树,指的是源代码语法所对应的树状结构。也就是一种将源代码通过构建语法树,将源代码的语句映射到树上的每一个节点。

在爬虫er手中,通常将JavaScript源代码解析为语法树,操作节点的增删改查来实现解混淆的目的。

需要用到的技术:

  1. node.js
  2. node.js的第三方库包Babel下的部分工具(@babel/parser,@babel/traverse,@babel/types,@babel/generator)

安装

1、下载node.js msi安装包一路确认就好了,最后在命令行下输入 node -v 验证是否安装成功

2、使用npm安装Babel库

// 安装命令
// 只需要babel中以下的工具,不建议使用npm install @babel/core安装,会造成编辑器中只能补全不全。
npm install @babel/parser
npm install @babel/traverse
npm install @babel/types
npm install @babel/generator
// 查看版本
npm ls [package_name]

必须的知识

1、node.js中文件读写

node中提供文件系统模块(fs)进行文件读写,提供异步(readFile)和同步(readFileSync)的方法读取文件,相应的也提供writeFile、writeFileSync写入文件。

基本使用如下:

// 读取input.js文件base64编码后写入output.js
// node.js 在16.00之后也有atob,btoa了const fs = require('fs')fs.readFile('input.txt', "utf8", (err, input_js_code) => {console.log(input_js_code);let output_code = btoa(encodeURIComponent(input_js_code));console.log(output_code);fs.writeFileSync('output.txt', output_code, {encoding: "utf-8"})
});// 输出:
// 又是一个爬虫er
// JUU1JThGJTg4JUU2JTk4JUFGJUU0JUI4JTgwJUU0JUI4JUFBJUU3JTg4JUFDJUU4JTk5JUFCZXI=

2、babel中的一些工具的基本使用

需要用到babel中的工具有:@babel/parser@babel/traverse@babel/generator@babel/types。其中@babel/parser负责接受源码进行词法分析、语法分析最终生成AST;@babel/traverse负责对AST进行深度优先的遍历;@babel/generator则和@babel/parser相反,负责将AST转化为AST源码;@babel/types用于判断节点类型、生成新节点。

  1. parser.parse将JavaScript源代码解析为AST;generator(ast).code将AST转化为JavaScript源代码。
  2. traverse遍历AST节点,当遍历到节点名与visitor对象方法名一致时调用该visitor方法。
  3. types构造新节点、判断节点类型
  4. path,remove()删除节点path.insertBefore(node)

基本使用如下:

const fs = require('fs')
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const b_type = require("@babel/types");
const generator = require("@babel/generator").default;fs.readFile('resources/input.js', "utf8", (err, input_js_code) => {// 将JavaScript源代码转化为ASTlet ast = parser.parse(input_js_code);//对AST各个节点进行遍历,当遍历到visitor内声明的节点时,进入并执行。traverse(ast, visitor)// 将AST转化为JavaScript代码,jsescOption选项去除16进制和Unicodelet output_code = generator(ast, {minified: true, jsescOption: {minimal: true}}).codefs.writeFileSync('resources/output.js', output_code, {encoding: "utf-8"})
});function funToStr(path) {// @babel/types常用api// let new_node = b_type.stringValue()// let new_node = b_type.numberValue()// let new_node = b_type.valueToNode()// 替换节点// path.replaceWith(string_node)// path.replaceWithMultiple(string_node)// 删除节点// path.remove()// 在这之前、之后插入节点// path.insertBefore()// path.insertAfter()// 获取兄弟节点// path.getAllPrevSiblings()// ...
}const visitor = {// 调用函数节点CallExpression: {enter: [funToStr]},// ...
}

3、利用astexplorer和AST节点速查手册快速编写反混淆代码。

astexplorer网址:https://astexplorer.net/

AST节点速查手册网址:https://github.com/yacan8/blog/blob/master/posts/JavaScript%E6%8A%BD%E8%B1%A1%E8%AF%AD%E6%B3%95%E6%A0%91AST.md

使用AST进行解混淆的一般步骤为

  1. 观察JavaScript源代码,分析那些部分需要替换、删除
  2. 将源代码放入到astexplorer,观察需要处理的JavaScript片段特征。在astexplorer中点击左侧源代码,右侧AST即会跳转到对于节点。
  3. 结合AST节点速查手册在visitor中快速编写规则处理需要修改的节点。

4、一些能提升开发效率的工具

  • babel中文文档:https://www.babeljs.cn/docs
  • ast explorer助手(油猴插件):https://github.com/CC11001100/ast-explorer-helper

基本操作

了解完基础知识我们就可以进行一些简单的基本操作了

1、简单还原常量与标识符的混淆

在本小段中只讨论了常见的对字符常量名进行Unicode编码、数值常量进制转化混淆的还原。其他对字符常量btoa、ob混淆,对数值常量替换为表达式运算结果的情况不做讨论。

以下代码也可以作为我们利用AST解混淆的基本模板,本文所有代码都是基于此模板。

const fs = require('fs')
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const b_type = require("@babel/types");
const generator = require("@babel/generator").default;// 模板
fs.readFile('resources/Unicode.js', "utf8", (err, input_js_code) => {// 将JavaScript源代码转化为ASTlet ast = parser.parse(input_js_code);// 对AST各个节点进行遍历,当遍历到visitor内声明的节点时,进入并执行。traverse(ast, visitor)// 将AST转化为JavaScript代码,jsescOption选项还原16进制和Unicodelet output_code = generator(ast, {minified: true, jsescOption: {minimal: true}}).codefs.writeFileSync('resources/deUnicode.js', output_code, {encoding: "utf-8"})
});const visitor = {}

还原前后对照

2、简单还原ob混淆

对JavaScript源码使用基本的ob混淆后在文件开头会见到一个大数组,之后可能会有一个自执行函数对开头的大数组进行位移操作,在这后面还会提供一个解密函数返回被混淆的字符串。

还原这种类型的混淆我们首先需要将这个大数组、自执行的位移函数和解密函数提取出来,部分开发同学还会将解密函数在函数内赋值给一个新的局部变量在一定程度上干扰ob混淆被还原。

  1. 提出数组、自执行的位移函数和解密函数
    为了提高代码的可读性,一般情况下我们将数组、自执行的位移函数和解密函数提出到一个单独的文件,使用exports导出解密函数提供给解混淆使用。

    // 一般情况下只有一个解密函数
    exports.decryptStr = _0x36a8;
    exports.decryptStrFnName = '_0x36a8';
    

  2. 编写规则处理代码
    以下以jsjiami的v5基础版本做实例,在这一小段只简单还原了OB混淆段。

const fs = require('fs')
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const b_type = require("@babel/types");
const generator = require("@babel/generator").default;
const {decryptStr, decryptStrFnName} = require("./tools_v5")// 模板
fs.readFile('resources/jsjiami_v5.js', "utf8", (err, input_js_code) => {// 将JavaScript源代码转化为ASTlet ast = parser.parse(input_js_code);// 对AST各个节点进行遍历,当遍历到visitor内声明的节点时,进入并执行。traverse(ast, visitor)// 将AST转化为JavaScript代码,jsescOption选项还原16进制和Unicodelet output_code = generator(ast, {minified: true, jsescOption: {minimal: true}}).codefs.writeFileSync('resources/dejsjiami_v5.js', output_code, {encoding: "utf-8"})
});function funToStr(path) {let {callee, arguments} = path.nodeif (callee.name === decryptStrFnName) {let replace_value = decryptStr(arguments[0].value, arguments[1].value)let replace_node = b_type.stringLiteral(replace_value)path.replaceWith(replace_node)}
}const visitor = {CallExpression: {enter: [funToStr]}
}

还原前后对照:

3、简单还原平坦化

除去对变量的混淆,常见的还有代码执行流程的混淆,通常使用while-switch,for-switch来将正常的执行流程打乱,然后通过swtich case块分发代码块并控制执行顺序,部分混淆还会塞入假代码块。对于这一类的混淆,在while-switch中我们首先需要确定分发器,之后通过分发器的执行顺序抽取出对应case内的代码块存入数组使用replaceWithMultiple替换整个节点;在for-switch中和while-switch不同的是分发器的生成,while-switch会在代码进入while前就生成分发器确定代码执行顺序且while一般为死循环在执行完分发器分发的最后一个case后跳出;for-switch则是在case内确定下一个case的执行,当分发器的值和for循环中的跳出吻合时结束。

while-switch平坦化还原:

const fs = require('fs')
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const b_type = require("@babel/types");
const generator = require("@babel/generator").default;fs.readFile('resources/switch_case.js', "utf8", (err, input_js_code) => {// 将JavaScript源代码转化为ASTlet ast = parser.parse(input_js_code);//对AST各个节点进行遍历,当遍历到visitor内声明的节点时,进入并执行。traverse(ast, visitor);// 将AST转化为JavaScript代码,jsescOption选项去除16进制和Unicodelet output_code = generator(ast, {minified: false, jsescOption: {minimal: true}}).code;fs.writeFileSync('resources/de_switch_case.js', output_code, {encoding: "utf-8"})
});function de_planarization(path) {let {body, test} = path.node// 判断while内是否为!![]if (test.type !== "UnaryExpression" || test.operator !== "!"|| test.argument.type !== "UnaryExpression" || test.argument.operator !== "!"|| test.argument.argument.type !== "ArrayExpression" || test.argument.argument.elements.length !== 0) return;// 判断while内代码块是否为switch caseif (body.body.length === 0|| body.body[0].type !== 'SwitchStatement'|| body.body[1].type !== 'BreakStatement') return;// 取switch内分发器名字let discriminant = body.body[0].discriminantlet dispatcher_name = discriminant.object.name// 取while循环上面的兄弟节点,用于获取case执行步骤let PrevSiblings = path.getAllPrevSiblings();// case执行步骤let replace_node = []//迭代兄弟节点PrevSiblings.forEach(Sibling => {// 判断是否为分发器的赋值语句if (Sibling.node.type === "VariableDeclaration" && Sibling.node.declarations[0].id.name === dispatcher_name) {let {callee, arguments} = Sibling.node.declarations[0].init// 分发器字符串切割符号let split_str = arguments[0].value// 分发器字符串的值let dispatcher_value = callee.object.value// 分发器处理函数,一般看源码就可以确认了,split_str、dispatcher_func可以不获取let dispatcher_func = callee.property.value// case代码块执行步骤let real_steps = dispatcher_value[dispatcher_func](split_str)// let real_steps = dispatcher_value.split('|')let switch_node = body.body[0]real_steps.forEach(step => {// 获取case内代码块let consequent = switch_node.cases[step].consequent// 移出continue代码if (b_type.isContinueStatement(consequent[consequent.length - 1])) {consequent.pop();}replace_node = replace_node.concat(consequent);})}//删除前面的兄弟节点Sibling.remove();})//替换整个while节点path.replaceWithMultiple(replace_node);
}const visitor = {WhileStatement: {enter: [de_planarization]}
}

还原前后对比:

for-switch平坦化还原:

const fs = require('fs')
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const b_type = require("@babel/types");
const generator = require("@babel/generator").default;fs.readFile('resources/for_switch_case.js', "utf8", (err, input_js_code) => {// 将JavaScript源代码转化为ASTlet ast = parser.parse(input_js_code);//对AST各个节点进行遍历,当遍历到visitor内声明的节点时,进入并执行。traverse(ast, visitor)// 将AST转化为JavaScript代码,jsescOption选项去除16进制和Unicodelet output_code = generator(ast, {minified: false, jsescOption: {minimal: true}}).codefs.writeFileSync('resources/de_for_switch_case.js', output_code, {encoding: "utf-8"})
});function de_planarization(path) {let {init, test, update, body} = path.node// 判断for循环跳出特征if (test.operator !== "!==") return;// 起始代码块索引let start_index = init.declarations[0].init.value// 结束索引let end_index = test.right.value// 索引在源代码中的名字let dispatcher_name = test.left.name;// 替换节点let replace_node = []body.body.forEach(node => {if (node.type === "SwitchStatement") {// switch case分发器特征是否满足条件if (node.discriminant.name !== dispatcher_name) return;// 按源代码顺序迭代casefor (let i = start_index; i !== end_index;) {let SwitchCase = node.cases[i]let need_save = []SwitchCase.consequent.forEach(Statement => {// 获取下一个执行的代码块indexif (Statement.type !== 'ExpressionStatement'|| Statement.expression.type !== "AssignmentExpression"|| Statement.expression.left.name !== dispatcher_name) {// 将其与代码保存need_save.push(SwitchCase.consequent.indexOf(Statement))return}// 下一个执行的代码块i = Statement.expression.right.value})need_save.forEach(index => {let case_code = SwitchCase.consequent[index]// 删除continueif (b_type.isContinueStatement(case_code)) return;replace_node = replace_node.concat(case_code);})}path.replaceWithMultiple(replace_node)}})
}const visitor = {ForStatement: {enter: [de_planarization]}
}

还原前后代码对比:

使用基本操作进行基本还原

实战我们以某验三代滑块最新fullpage.9.1.0.jshttps://static.geetest.com/static/js/fullpage.9.1.0.js为例进行实战还原。

观察JavaScript源码明显可见有常量unicode编码混淆,收缩代码观察JavaScript源代码主体,最后一个代码块为自执行函数,前五个代码块定义了一个zmSjO函数对象,之后往塞了里面四个方法 A i 、 _Ai、 Ai_BE、、 C s 、 _Cs、 Cs_DB,我们暂时不知道这个有啥用。展开最后一个自执行函数观察,发现一段大量重复的代码,截取一段如下:

var $_CBJHn = zmSjO.$_Cs
, $_CBJGy = ['$_CCAAe'].concat($_CBJHn)
, $_CBJIX = $_CBJGy[1];
$_CBJGy.shift();
var $_CBJJK = $_CBJGy[0];

zmSjO. C s 是在前五个代码块中塞入的方法,这段代码中 _Cs是在前五个代码块中塞入的方法,这段代码中 Cs是在前五个代码块中塞入的方法,这段代码中_CBJHn、 C B J I X 、 _CBJIX、 CBJIX_CBJGy都等于zmSjO.$_Cs,在之后的代码中使用函数返回字符串来代替可见字符串也就是常说的ob混淆。虽然与基本操作中使用的示例有一定区别但是基本思路一致。

继续观察源代码,除此之外还存在基本操作中提到的for-switch类型平坦化,截取一段代码如下:

var $_DEFDa = zmSjO.$_DB()[2][4];
for (; $_DEFDa !== zmSjO.$_DB()[0][3];) {switch ($_DEFDa) {case zmSjO.$_DB()[2][4]:this[$_DAHB(20)] = this[$_DAHB(905)]();$_DEFDa = zmSjO.$_DB()[2][3];break;}
}

代码中定义的分发器 D E F D a 是从 z m S j O . _DEFDa是从zmSjO. DEFDa是从zmSjO._DB函数返回的大数组中取的值,case也是从zmSjO.$_DB函数返回的大数组中取的值作的索引,这种和我们基本操作中使用的示例有一定区别,case索引不在是顺序的1-9,但是还原思路一致。

还原代码如下:

const fs = require('fs')
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const b_type = require("@babel/types");
const generator = require("@babel/generator").default;
const {decryptStr, decryptStrFnName, case_arrays} = require("./tools_fullpage9.1.0")fs.readFile('resources/fullpage.9.1.0.js', "utf8", (err, input_js_code) => {// 将JavaScript源代码转化为ASTlet ast = parser.parse(input_js_code);//对AST各个节点进行遍历,当遍历到visitor内声明的节点时,进入并执行。traverse(ast, visitor)// 将AST转化为JavaScript代码,jsescOption选项去除16进制和Unicodelet output_code = generator(ast, {minified: true, jsescOption: {minimal: true}}).codefs.writeFileSync('resources/de_fullpage.9.1.0.js', output_code, {encoding: "utf-8"})
});let fake_array = []function affirm_ob_variable(path) {let {kind, declarations} = path.nodefake_array = []if (kind !== "var"|| declarations[0].init === null|| declarations[0].init.type !== "MemberExpression"|| declarations[0].init.object.name !== "zmSjO"|| declarations[0].init.property.name !== decryptStrFnName) returnfake_array.push(declarations[0].id.name)fake_array.push(declarations[1].id.name)fake_array.push(declarations[2].id.name)path.getFunctionParent().traverse(visitor_2)path.getNextSibling().remove()path.getNextSibling().remove()path.remove()
}function funToStr(path) {let {callee, arguments} = path.nodeif (fake_array.indexOf(callee.name) > -1) {let replace_str = b_type.valueToNode(decryptStr(arguments[0].value))path.replaceWith(replace_str)}}function de_planarization(path) {let {init, test, update, body} = path.node// 判断for循环跳出特征if (test.operator !== "!=="|| test.right.type !== "MemberExpression"|| test.right.object.type !== "MemberExpression"|| test.right.object.object.type !== "CallExpression"|| test.right.object.object.callee.object.name !== "zmSjO"|| test.right.object.object.callee.property.name !== "$_DB") return;// 用于替换for-switch节点的节点列表let replace_node = []// case分发器值和相应case代码段、下一步组成的字典let case_map = {}// 起始代码块索引let before_code = path.getPrevSibling()// 分发器特征判断if (before_code.type !== "VariableDeclaration") return;let {declarations, kind} = before_code.node// 起始索引let start_index = case_arrays[declarations[0].init.object.property.value] [declarations[0].init.property.value]// 分发器名let dispatcher_name = declarations[0].id.name// 删除分发器初始化赋值代码before_code.remove()// 第一步执行的代码段// 遍历 for内代码块body.body.forEach(node => {if (node.type === "SwitchStatement") {// switch case分发器特征是否满足条件if (node.discriminant.name !== dispatcher_name) return;// 遍历case节点node.cases.forEach(SwitchCase => {// case对应的分发器值let case_index = case_arrays[SwitchCase.test.object.property.value][SwitchCase.test.property.value]let next_index;// 如果该case最后一句为break则不需要记录下一步分发器的值if (!b_type.isBreakStatement(SwitchCase.consequent[SwitchCase.consequent.length - 1])) {SwitchCase.consequent.forEach(Statement => {if (Statement.type === "ExpressionStatement") {let {expression} = Statementif (expression.type !== "AssignmentExpression"|| expression.left.name !== dispatcher_name) return// 读取下一步分发器的值next_index = case_arrays[expression.right.object.property.value][expression.right.property.value]}})} else {// 移出 最后一句break语句SwitchCase.consequent.pop()// 移出 倒数第二句break中无效的分发器赋值if (b_type.isExpressionStatement(SwitchCase.consequent[SwitchCase.consequent.length - 1])) {let consequent = SwitchCase.consequent[SwitchCase.consequent.length - 1]if (consequent.expression.type === "AssignmentExpression"|| consequent.expression.left.name === dispatcher_name) {SwitchCase.consequent.pop()}}}// 存入字典case_map[case_index] = {"consequent": SwitchCase.consequent,'next_index': next_index}})}})// 当start_index 为undefined时,结束循环while (start_index) {// 存在假代码,删除不执行的caseif (!case_map[start_index]) breaklet consequent = case_map[start_index]["consequent"]// 下一步执行顺序start_index = case_map[start_index]["next_index"]// 将节点数组合并 存入replace_nodereplace_node = replace_node.concat(consequent)}// 批量替换path.replaceWithMultiple(replace_node)
}const visitor = {VariableDeclaration: {enter: [affirm_ob_variable]},ForStatement: {enter: [de_planarization]},
}const visitor_2 = {CallExpression: {enter: [funToStr]}
}

结尾

本文为入门科普向文章,还有很多有用的API和混淆没有提到,总归是常见的混淆都讲了一遍,足够我们对绝大多数JavaScript源代码混淆在一定程度上还原。AST还原JavaScript代码混淆并不困,但要还原出精简的JavaScript源代码还是需要大量的练习。

在web端逆向、还原过程中使用AST反混淆可以极大地提升我们的效率,但是更需要的是我们对JavaScript的熟悉、对JavaScript逆向还原的经验。

所有代码已经上传Github:https://github.com/luojunjunjun/article/tree/master/10min%20AST


http://chatgpt.dhexx.cn/article/khL7f1k3.shtml

相关文章

什么是目标检测?github项目解析(持续更新中)

温馨提示:文章内容完整但是过长,由于前后内容有关联,读者学习可以多开几个浏览器分屏有助于定位 目录 目标检测理论部分: 1.目标检测介绍 2.YOLOv5的检测原理 3.目标检测的意义 4.目标检测的应用场景 5.先行知识储备 &…

2021年你必须拥有的10个最流行和最基本的Linux应用

点击上方 "编程技术圈"关注, 星标或置顶一起成长 后台回复“大礼包”有惊喜礼包! 每日英文 If you’re brave to say “good bye”, life will reward you with a new “hello”. -- Paulo Coelho. 如果你勇于对过去说“拜拜”,生活就会回赠给…

中国linux系统

对于广大电脑初级用户来说,Windows就是操作系统的代名词。但“天外有天,OS外有OS”,操作系统并非只有Windows一种。早在Windows诞生之前,Unix、Macintosh这些知名的操作系统就已经存在。只是因为Windows占据了操作系统绝大部分的市场份额,所以许多用户都不太熟悉Windows之…

Linux 下一代架构基金会宣布:联合腾讯等企业和社区,发力微服务标准化建设

2022年3月23日,NextArch 基金会正式宣布成立微服务 SIG(Special Interest Group,以下简称“微服务技术组”),来自腾讯、字节跳动、七牛云、快手、BIGO、好未来和蓝色光标等多家企业的技术专家成为首批成员。 该小组聚…

2021年你应该拥有的10个最流行和最基本的Linux应用

前言 本文介绍了预计在2021年最受欢迎的十大Linux应用程序。那么现在让我们看看这个列表。 您是否正在寻找适合台式机或笔记本电脑的最佳Linux应用程序,以带您进入2021年?在本文中,我们将列出您必须在Linux操作系统上安装的最流行的Linux应用…

关于 Linux

Linus(林纳斯托瓦兹):Linux 的开发作者,被称为Linux 之父,Linux 诞生时是芬兰赫尔辛基大学的在校大学生。 Stallman 斯特曼:开源文化的倡导人。 2、Linux 的含义 狭义:由Linus 编写的一段内核代码。 广义&#xff…

linux发展现状和学习建议

一、Linux在各领域发展的现状与趋势 很多新手都有一个很疑惑的问题:“Linux我听过,但是学习linux系统,能在上面干什么呢,或者说linux系统具体能做什么”,带着这个疑问,在本书的开篇,我们先来了解…

linux哪国的,Linux是什么它是哪个国家开发的

摘要 腾兴网为您分享:Linux是什么它是哪个国家开发的,信用管家,天天爱,美食天下,美甲帮等软件知识,以及平安app金管家,美发秀秀,圈子账本,传媒人,绝望表情包,…

Linux系统在信息社会的发展

随着信息技术的高速发展并迅速渗透到社会生活的各个方面,Linux日益成为人们学习、工作、生活不可缺少的基本工具,再过不了几年,不会使用Linux,就会象不识字一样使人举步维艰。进入大学,Linux无时无刻在伴随着我们&…

针对边缘计算,红帽企业Linux 9有哪些新功能?

每一次发布红帽企业Linux(RHEL)都激动人心,每一个(大的)新版本为业界提供十年以上的技术底座。RHEL 9标志着下一个里程碑,在稳定性、性能、升级和安全能力等领域提供全面改进。由于边缘计算是我们许多客户的…

Linux初期在中国的发展

Linux发展初期,中国各界对Linux这类开源软件是什么看法?回顾一下1999年发表在《互联网周刊》上的这篇题为《Linux:打开自由那扇窗》的旧文,也许对我们今天看待以RISC-V为代表的开源芯片未来发展多少会有些启发。 发表在1999年《互…

Linux是什么?它是哪个国家开发的

刚接触Linux的用户可能会有个想法,这个系统是什么?哪个国家开发的?有什么用?对于这些疑问,小编也有产生过,但是万能的互联网已经给与了答案,下面小编就将其汇总给大家,感兴趣的不妨看…

如何查看电脑ip和端口

1.查看电脑ip 使用WindowsR键打开“运行”窗口,然后输入CMD进入命令提示窗口,进入命令窗口之后,输入:ipconfig/all,按回车即可看到整个电脑的详细的IP配置信息。 2.查看电脑端口 使用WindowsR键打开“运行”窗口&…

windows系统查看进程端口号的命令

查看进程端口号 1、查看windows所有端口进程 netstat -ano 命令提示符窗口: 2、查询指定的端口占用 netstat -aon|findstr “端口” 显示列表中的PID,然后根据PID在电脑的任务管理器中查看对应的占用程序 根据查询的PID找到对应的进程 根据上面…

win10查看端口号

进程与端口号 我们知道,一些需要网络权限的进程都有着对应的端口号。然而我们有些时候却不知道这些可执行程序的端口号。我们打开任务管理器查看服务只能找到进程号PID,却怎么也找不到指定进程的端口号 网上找了一大堆办法,都是查看win上所有…

linux查看服务端口号

netstat - atulnp会显示所有端口和所有对应的程序,用grep管道可以过滤出想要的字段 -a :all,表示列出所有的连接,服务监听,Socket资料-t :tcp,列出tcp协议的服务-u :udp,…

Linux查看服务器开放的端口号/Linux查看服务器开放了哪些端口

Linux查看服务器开放的端口号 - 百度文库 https://wenku.baidu.com/view/73638115ed06eff9aef8941ea76e58fafab04596.html linux怎么查看开放了哪些端口 原创2021-12-21 18:19:0719653 关注公众号:每天精选资源文章推送 linux查看开放端口的方法&#xff1…

Centos7查看开放端口及端口号命令

1.查看已开放的端口 firewall-cmd --list-ports2.查看端口是否被占用 netstat -tunpl | grep "端口"3.开放单个端口(开放后需要要重启防火墙才生效) firewall-cmd --zonepublic --add-port8080/tcp --permanent #(--permanent …

如何查看自己电脑开启了哪些端口号

如何查看自己电脑开启了哪些端口号 最近在看网络安全方面的知识,学习了部分知识之后就想看看如何查看自己电脑上开启了哪些端口号,查询资料之后发现了方法。 在Windows系统当中通过Winr键输入cmd命令进入控制台 下面的命令用于显示各种网络相关信息 net…

如何查看自己电脑的并口端口号?

关注“心仪脑”查看更多脑科学知识的分享。 大家好,上期给大家分享了脑电实验中关于打Mark方面的一些检查注意事项。本期继续给大家分享关于实验方面的小知识。端口号在心理学实验打标记的过程中非常重要,刺激呈现软件可以通过向特定端口发送脉冲信号实…