Pure Windows and development “without anything”

There is a computer with a clean copy of Windows, no internet access and no installed development tools. Just one clean user “Wenda”. You won't believe it, but even in such Spartan conditions it is possible to write and run a full-fledged program. And now I will tell you how.

For this screenshot, I honestly deployed a custom version of Windows 11 in a virtual machine. What wouldn't you do for art!

For this screenshot, I honestly deployed a custom version of Windows 11 in a virtual machine. What wouldn't you do for art!

The Horrors of Knowledge

In fact, the Windows family of operating systems has had so many interesting things inside since the very beginning that no article would be enough to describe them. so there will be a lot of releases 😉

But for some reason, few people know about this, even among developers, especially modern ones.

Just for fun, ask your developer friends if it is possible to program on a “clean” user Windows without installing Visual Studio – you will be surprised by the answers.

And of course, the imposed “user” approach by Microsoft itself, which, to put it mildly, never encouraged poking around in the insides of its products, created a kind of aura of simplicity and reliability, without the need to understand how it works inside.

Therefore, what is described below will probably cause a certain horror among both ordinary users and some developers – especially if they studied using video courses know nothing about the history of Windows OS.

I'll start with a quote from one interesting article:

Over the past few months, I've received several variations on this question for other operating systems and all of the released versions of the .NET Framework. When the .NET Framework is installed as a part of the OS, it does not appear in the Programs and Features (or Add/Remove Programs) control panel. The following is a complete list of which version of the .NET Framework is included in which version of the OS

And below is a long list of versions. And here another if suddenly the first one was not enough.

Well, it would seem so… what? What's wrong with that?

About .NET SDK everyone already knows that sometimes it needs to be installed “to run games”, sometimes it installs itself as a dependent library and doesn’t bother anyone.

That's all true, yes.

But something tells me you haven't looked inside, have you? So you have no idea what this thing is actually capable of.

And I imagine and will tell you now.

Go to the Windows folder on your computer, here:

This screenshot is from Windows 10, it uses the system .NET SDK 3.5, Windows 11 will have 4.0

This screenshot is from Windows 10, it uses the system .NET SDK 3.5, Windows 11 will have 4.0

Filet csc.exe – most real compileressentially a portal to hell on your regular home computer.

Why is everything so scary?

Because after some time you will find yourself very overgrown, with a beard and red eyes, spending nights at the computer and slowly mutating into a programmer.

Kidding.

But seriously:

it becomes possible to create native programs directly on your computer, bypassing the stage of checking the electronic signature, checking the antivirus, checking the email, and so on.

Unlike VB or PowerShell scripts, which are analyzed by any decent antivirus before being launched, antiviruses do not analyze source code of programs in C# and are much more loyal to programs compiled locally on the same machine.

So the fun begins.

A simple example

To begin with, there will be a simple example that simply shows a standard dialog with a message. It is this one in its running form that you can see in the title image in the article.

I recorded the entire process from code to launch on video:

The source code here would seem to be extremely simple, but with one interesting thing nuance about which below:

using System;
using System.Runtime.InteropServices;

namespace yoba
{
  class Program
  {
    // импортирование нативной WinAPI функции MessageBox.
    [DllImport("user32.dll")]
    public static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);

    static void Main(string[] args)
    {
      //вызываем и показываем диалог
      MessageBox(IntPtr.Zero, "Йоу!", "Добро пожаловать в разработку!", 0);
    }
  }
}

Save this text in a regular notepad into the yoba.cs file and run the build:

c:\Windows\Microsoft.NET\Framework\v3.5\csc.exe yoba.cs

This is how I ran the build on Windows 10, but keep in mind that the version of the system .NET SDK may differ and, for example, in Windows 11 it will already be:

c:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe yoba.cs

After assembly next to the original file yoba.cs an executable binary will also appear yoba.exewhich you can run.

Now about the nuance.

Nuance

There is a certain prejudice against managed languages ​​like Java and C# – they are not suitable for serious tasks like writing exploits, using 0day vulnerabilities and core penetration.

What do all these things do? in deep secrecy in pure C, or at most in C++, and all these Java/C# of yours are nothing more than “rattles for children”, not worthy of even a sidelong glance from a serious professional.

This is where it begins nuancelook at this joy:

[DllImport("user32.dll")]
public static extern int MessageBox(IntPtr hWnd, 
                string lpText, string lpCaption, uint uType);

This, my dear readers, is nothing more than a challenge to the native WinAPIwith the help of which they did all sorts of bad things in the distant 90s.

C# and .NET have a very deep integration with Windows, despite all its “security” and manageability, so it can easily and simply replace both C and C++ as a tool for bad deeds.

And it lives on your computer, at home and in the office, with permanent registration and residence.

But of course such a simple example is not enough to understand the depth of the problem, so I have prepared something more serious.

Complex example: turning off Windows

So, it will be a relatively small C# application that will shut down the computer without warning or confirmation from the user. And of course, without administrator rights.

Just like that, suddenly.

I think each reader will be able to assess the consequences for themselves.

The whole process is on video (of course, this is a virtual machine):

And now the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security;
using System.Diagnostics;
using System.Management;
using System.Security.Permissions;
using System.Runtime.InteropServices;
  
namespace yoba
{	
	// See http://www.developmentnow.com/g/33_2004_12_0_0_33290/Access-Denied-on-ManagementEventWatcher-Start.htm 
	// Calling this code on backup/restore seems to enable BCD
	public class TokenHelper
	{
		// PInvoke stuff required to set/enable security privileges
		[DllImport("advapi32", SetLastError=true),
		SuppressUnmanagedCodeSecurityAttribute]
		static extern int OpenProcessToken(
			System.IntPtr ProcessHandle, // handle to process
			int DesiredAccess, // desired access to process
			ref IntPtr TokenHandle // handle to open access token
			);

		[DllImport("kernel32", SetLastError=true),
		SuppressUnmanagedCodeSecurityAttribute]
		static extern bool CloseHandle(IntPtr handle);

		
		[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
		SuppressUnmanagedCodeSecurityAttribute]
		static extern int AdjustTokenPrivileges(
			IntPtr TokenHandle,
			int DisableAllPrivileges,
			IntPtr NewState,
			int BufferLength,
			IntPtr PreviousState,
			ref int ReturnLength);

		[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
		SuppressUnmanagedCodeSecurityAttribute]
		static extern bool LookupPrivilegeValue(
			string lpSystemName,
			string lpName,
			ref LUID lpLuid);

		[StructLayout(LayoutKind.Sequential)]
			internal struct LUID 
		{
			internal int LowPart;
			internal int HighPart;
		}

		[StructLayout(LayoutKind.Sequential)]
			struct LUID_AND_ATTRIBUTES 
		{
			LUID Luid;
			int Attributes;
		}

		[StructLayout(LayoutKind.Sequential)]
			struct _PRIVILEGE_SET 
		{
			int PrivilegeCount;
			int Control;
			[MarshalAs(UnmanagedType.ByValArray, SizeConst=1)] // ANYSIZE_ARRAY = 1
			LUID_AND_ATTRIBUTES [] Privileges;
		}

		[StructLayout(LayoutKind.Sequential)]
			internal struct TOKEN_PRIVILEGES
		{
			internal int PrivilegeCount;
			[MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]
			internal int[] Privileges;
		}
		const int SE_PRIVILEGE_ENABLED = 0x00000002;
		const int TOKEN_ADJUST_PRIVILEGES = 0X00000020;
		const int TOKEN_QUERY = 0X00000008;
		const int TOKEN_ALL_ACCESS = 0X001f01ff;
		const int PROCESS_QUERY_INFORMATION = 0X00000400;

		public static bool SetPrivilege (string lpszPrivilege, bool
			bEnablePrivilege )
		{
			bool retval = false;
			int ltkpOld = 0;
			IntPtr hToken = IntPtr.Zero;
			TOKEN_PRIVILEGES tkp = new TOKEN_PRIVILEGES();
			tkp.Privileges = new int[3];
			TOKEN_PRIVILEGES tkpOld = new TOKEN_PRIVILEGES();
			tkpOld.Privileges = new int[3];
			LUID tLUID = new LUID();
			tkp.PrivilegeCount = 1;
			if (bEnablePrivilege)
				tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
			else
				tkp.Privileges[2] = 0;
			if(LookupPrivilegeValue(null , lpszPrivilege , ref tLUID))
			{
				Process proc = Process.GetCurrentProcess();
				if(proc.Handle != IntPtr.Zero) 
				{
					if (OpenProcessToken(proc.Handle, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
						ref hToken) != 0) 
					{
						tkp.PrivilegeCount = 1;
						tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
						tkp.Privileges[1] = tLUID.HighPart;
						tkp.Privileges[0] = tLUID.LowPart;
						const int bufLength = 256;
						IntPtr tu = Marshal.AllocHGlobal( bufLength );
						Marshal.StructureToPtr(tkp, tu, true);
						if(AdjustTokenPrivileges(hToken, 0, tu, bufLength, IntPtr.Zero, ref
							ltkpOld) != 0)
						{
							// successful AdjustTokenPrivileges doesn't mean privilege could be	changed
								if (Marshal.GetLastWin32Error() == 0)
								{
									retval = true; // Token changed
								}
						}
						TOKEN_PRIVILEGES tokp = (TOKEN_PRIVILEGES) Marshal.PtrToStructure(tu,
							typeof(TOKEN_PRIVILEGES) );
						Marshal.FreeHGlobal( tu );
					}
				}
			}
			if (hToken != IntPtr.Zero)
			{
				CloseHandle(hToken);
			}
			return retval;
		}
	}
	
    class ShutDown
    {
       
        [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
        internal static extern bool ExitWindowsEx(int flg, int rea);  
        
		internal const int EWX_FORCE = 0x00000004;
        internal const int EWX_POWEROFF = 0x00000008;
    
		static void Main(string[] args)
		{
		    TokenHelper.SetPrivilege("SeShutdownPrivilege",true);	          
			ExitWindowsEx(EWX_FORCE | EWX_POWEROFF, 0);			
		}
	}
}

Please note that this is Not exploit, Not hole, Not bug and Not visibility and quite standard functionality. It just so happened that few people know about it.

It is assembled in the same way as the previous example:

c:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Shutdown.cs

After starting, the computer will shut down almost immediately:

tested both in a virtual machine and on hardware, on Windows 10 and 11.

I'll tell you how it works.

Key function – ExitWindowsExwhich is responsible for shutting down the OS. This function is very old and well-known, it has existed since Windows 95.

But to call it, you need “privileges”, which the class sets programmatically. TokenHelper.

Constants below:

internal const int EWX_FORCE = 0x00000004;
internal const int EWX_POWEROFF = 0x00000008;    

are used together with “bitwise or” to indicate the required action.

Here are some other acceptable options:

internal const int EWX_LOGOFF = 0x00000000;
internal const int EWX_SHUTDOWN = 0x00000001;
internal const int EWX_REBOOT = 0x00000002;
internal const int EWX_FORCEIFHUNG = 0x00000010;  

A description of all of them is located still there — in the official manual, you won’t believe it.

Now let's figure out how such a tough ignoring of the protection system works, also using standard means:

 TokenHelper.SetPrivilege("SeShutdownPrivilege",true);

And we'll start with imports.

The first thing that is imported is the function OpenProcessToken:

[DllImport("advapi32", SetLastError=true),
		SuppressUnmanagedCodeSecurityAttribute]
		static extern int OpenProcessToken(
			System.IntPtr ProcessHandle, // handle to process
			int DesiredAccess, // desired access to process
			ref IntPtr TokenHandle // handle to open access token
			);

The function is responsible for obtaining data about the set of “privileges” associated with a specific process. The set of such privileges is actually called a “token”.

Here's how this function is called:

if (OpenProcessToken(proc.Handle, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
						ref hToken) != 0) 
					{
					..

Here we should note the transfer by reference in C style (ref hToken), when a reference to a C# object is passed to a function, the function then fills this object with data. And it simply returns true or false — execution status, whether the function worked or not.

Next, a simple and banal one is imported function freeing up resources:

[DllImport("kernel32", SetLastError=true),
		SuppressUnmanagedCodeSecurityAttribute]
		static extern bool CloseHandle(IntPtr handle);

It is called at the very end, after all the logic, and is only needed to free up the memory used for the privilege token:

if (hToken != IntPtr.Zero)
			{
				CloseHandle(hToken);
			}

Finally main functionwhich is directly responsible for switching privileges:

	[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
		SuppressUnmanagedCodeSecurityAttribute]
		static extern int AdjustTokenPrivileges(
			IntPtr TokenHandle,
			int DisableAllPrivileges,
			IntPtr NewState,
			int BufferLength,
			IntPtr PreviousState,
			ref int ReturnLength);

Here is the entire key block of privilege change logic:

if (OpenProcessToken(proc.Handle, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
						ref hToken) != 0) 
					{
						tkp.PrivilegeCount = 1;
						tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
						tkp.Privileges[1] = tLUID.HighPart;
						tkp.Privileges[0] = tLUID.LowPart;
						const int bufLength = 256;
						IntPtr tu = Marshal.AllocHGlobal( bufLength );
						Marshal.StructureToPtr(tkp, tu, true);
						if(AdjustTokenPrivileges(hToken, 0, tu, bufLength, IntPtr.Zero, ref
							ltkpOld) != 0)
						{
							// successful AdjustTokenPrivileges doesn't mean privilege could be	changed
								if (Marshal.GetLastWin32Error() == 0)
								{
									retval = true; // Token changed
								}
						}
						TOKEN_PRIVILEGES tokp = (TOKEN_PRIVILEGES) Marshal.PtrToStructure(tu,
							typeof(TOKEN_PRIVILEGES) );
						Marshal.FreeHGlobal( tu );
					}

As you can see, the call is quite complex, using the C procedural approach to filling the structure fields and passing it by reference to the called function.

After the call, the presence of an error is checked, also in C style:

if (Marshal.GetLastWin32Error() == 0)
								{
									retval = true; // Token changed
								}

0 This is the return code for a successful call, if it exists, it is considered that the privilege change operation was successful.

Finally the last one functionwhich is worth telling about:

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
		SuppressUnmanagedCodeSecurityAttribute]
		static extern bool LookupPrivilegeValue(
			string lpSystemName,
			string lpName,
			ref LUID lpLuid);

It is responsible for searching for a privilege by name, I suppose you noticed that we pass a certain code name when calling TokenHelper:

TokenHelper.SetPrivilege("SeShutdownPrivilege",true);	 

It is this function that is responsible for searching for a specific privilege by name “SeShutdownPrivilege“, this is what its call looks like:

if (bEnablePrivilege)
	tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
else
	tkp.Privileges[2] = 0;

if(LookupPrivilegeValue(null , lpszPrivilege , ref tLUID))
			{
			..

Variable bEnablePrivilege Boolean, that's the one true passed as the second argument, and the block:

if (bEnablePrivilege)
	tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
else
	tkp.Privileges[2] = 0;

is responsible for forming the correct call using system constants (SE_PRIVILEGE_ENABLED).

When calling, a reference is also passed (ref tLUID) to the object LUIDwhich will contain, after the call, an indication of the privilege found.

That's how things are.

Total

All of this is not a call for immediate action, but merely a food for thought. about the meaning of beingWell, there about reliability, safety and all that stuff – what a large foreign corporation sells to you.

Think about it if you see your favorite Windows at a nuclear power plant or military facility – without any CIA or hackers, Windows OS has a hellish mountain of functionality that can be easily and simply used for harm.

I can tell you a lot more about the world of Windows and its internal structure, so there will be more articles on this topic. And I hope at least someone will think about it, draw conclusions and understand that a “mass” product has no place in serious places where real protection and real security are needed.

This is a slightly edited version of my article, original is available on our blog. The second part of this article is currently being prepared, where there will be a story about modern web development in the same conditions – stay tuned for announcements!

0x08 Software

We are a small team of IT industry veterans, we create and refine a wide variety of software, our software automates business processes on three continents, in a wide variety of industries and conditions.

Bringing it to life long dead, we fix what never worked and we create impossible — then we talk about it in our articles.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *