ShareDiscreetlyWebServer v1.0.0.3

by Ryan 9. April 2013 13:17

I wrote a web service.  I call it "ShareDiscreetly".  Creative name, huh?

I wrote the server in C# .NET 4.0.  It runs as a Windows service.

ShareDiscreetlyWebServer serves a single purpose: to allow two people to share little bits of information - secrets - such as passwords, etc., in a secure, discreet manner.  The secrets are protected both in transit and at rest, using the FIPS-approved AES-256 algorithm with asymmetric keys supplied by an X.509 certificate.

Oh, and I made sure that it's thoroughly compatible with Powershell so that the server can be used in a scriptable/automatable way.

You can read a more thorough description of the server as you try it out here.

Please let me know if you find any bugs, exploits, or if you have any feature requests!

Users.exe v1.0.0.3

by Ryan 16. January 2013 11:42

In my last post, I showed how to get RDP/TS session information from a local or remote computer, including ClientName, on the command line using Powershell. (But it's not really Powershell. It's a PS wrapper around some C# that P/Invokes a native API. Which is pretty nuts but it works.)  Since I can't think of any other way to get this information on the command line, I thought it would be useful to convert the thing to native code. I named the program users.exe. It's written in C. Here is what it looks like:

C:\Users\joe\Desktop>users /?

USAGE: users.exe [hostname]

Not specifying a hostname implies localhost.
This command will return information about users currently logged onto
a local or remote computer, including the client's hostname and IP.
Users.exe was written by Ryan Ries.

C:\Users\joe\>users SERVER01

Session ID  : 0
Domain\User : System
Client Name : Local
Net Address : n/a
Conn. State : Disconnected

Session ID  : 1
Domain\User : System
Client Name : Local
Net Address : n/a
Conn. State : Connected

Session ID  : 25
Domain\User : DOMAIN\joe
Client Name : JOESLAPTOP
Net Address : 10.122.124.21 (AF_INET)
Conn. State : Active

I had a pretty rough time shaking the rust off of my native/unmanaged code skills, but I pulled it off.  That said, if you wanna give it a try, I would very much appreciate any bug reports/feature requests.

users.exe (63.50 kb)

Getting RDP Sessions with Client Computer Name

by Ryan 13. January 2013 14:25

I came across this question on ServerFault today, which I thought was quite interesting.  And so I spent all morning formulating a solution to it.  How do you go about getting the list of currently connected users of a Windows machine including the remote client's machine name, on the command line? You can type query session, but that doesn't tell you the client's computer name or IP address.  The various Terminal Services and Remote Desktop-related Windows event logs are of very limited help. The Users tab in Task Manager tells you the usernames and their session IDs, but not their computer name.  You've got a bunch of WMI classes like Win32_LoggedOnUser and Win32_LogonSession, but none of them seem to contain any data about the connected client's machine name or IP address.

I wanted to come up with a Powershell solution.  Something that could also be run over the network and not just on the local machine.  So after a couple hours of research, finding the WTSQuerySessionInformation documentation, and spending some quality time on pinvoke.net, here is the "Powershell" solution that I came up with:

(I put "Powershell" in quotes because there's one single actual Powershell command in the whole thing.  Add-Type.  It's really all .NET code, which P/Invokes an unmanaged DLL.  Nevertheless, it's pretty sweet that you can do all that from within Powershell.)

 

# QuerySessionInformation.ps1
# Written by Ryan Ries, Jan. 2013, with help from MSDN and Stackoverflow.

$Code = @'
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
public class RDPInfo
{
    [DllImport("wtsapi32.dll")]
    static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] String pServerName);

    [DllImport("wtsapi32.dll")]
    static extern void WTSCloseServer(IntPtr hServer);

    [DllImport("wtsapi32.dll")]
    static extern Int32 WTSEnumerateSessions(
        IntPtr hServer,
        [MarshalAs(UnmanagedType.U4)] Int32 Reserved,
        [MarshalAs(UnmanagedType.U4)] Int32 Version,
        ref IntPtr ppSessionInfo,
        [MarshalAs(UnmanagedType.U4)] ref Int32 pCount);

    [DllImport("wtsapi32.dll")]
    static extern void WTSFreeMemory(IntPtr pMemory);

    [DllImport("Wtsapi32.dll")]
    static extern bool WTSQuerySessionInformation(System.IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out System.IntPtr ppBuffer, out uint pBytesReturned);

    [StructLayout(LayoutKind.Sequential)]
    private struct WTS_SESSION_INFO
    {
        public Int32 SessionID;
        [MarshalAs(UnmanagedType.LPStr)]
        public String pWinStationName;
        public WTS_CONNECTSTATE_CLASS State;
    }

    public enum WTS_INFO_CLASS
    {
        WTSInitialProgram,
        WTSApplicationName,
        WTSWorkingDirectory,
        WTSOEMId,
        WTSSessionId,
        WTSUserName,
        WTSWinStationName,
        WTSDomainName,
        WTSConnectState,
        WTSClientBuildNumber,
        WTSClientName,
        WTSClientDirectory,
        WTSClientProductId,
        WTSClientHardwareId,
        WTSClientAddress,
        WTSClientDisplay,
        WTSClientProtocolType
    }

    public enum WTS_CONNECTSTATE_CLASS
    {
        WTSActive,
        WTSConnected,
        WTSConnectQuery,
        WTSShadow,
        WTSDisconnected,
        WTSIdle,
        WTSListen,
        WTSReset,
        WTSDown,
        WTSInit
    }

    public static IntPtr OpenServer(String Name)
    {
        IntPtr server = WTSOpenServer(Name);
        return server;
    }

    public static void CloseServer(IntPtr ServerHandle)
    {
        WTSCloseServer(ServerHandle);
    }

    public static void ListUsers(String ServerName)
    {
        IntPtr serverHandle = IntPtr.Zero;
        List<String> resultList = new List<string>();
        serverHandle = OpenServer(ServerName);

        try
        {
            IntPtr SessionInfoPtr = IntPtr.Zero;
            IntPtr userPtr = IntPtr.Zero;
            IntPtr domainPtr = IntPtr.Zero;
            IntPtr clientNamePtr = IntPtr.Zero;
            Int32 sessionCount = 0;
            Int32 retVal = WTSEnumerateSessions(serverHandle, 0, 1, ref SessionInfoPtr, ref sessionCount);
            Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
            Int32 currentSession = (int)SessionInfoPtr;
            uint bytes = 0;
            if (retVal != 0)
            {
                for (int i = 0; i < sessionCount; i++)
                {
                    WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)currentSession, typeof(WTS_SESSION_INFO));
                    currentSession += dataSize;

                    WTSQuerySessionInformation(serverHandle, si.SessionID, WTS_INFO_CLASS.WTSUserName, out userPtr, out bytes);
                    WTSQuerySessionInformation(serverHandle, si.SessionID, WTS_INFO_CLASS.WTSDomainName, out domainPtr, out bytes);
                    WTSQuerySessionInformation(serverHandle, si.SessionID, WTS_INFO_CLASS.WTSClientName, out clientNamePtr, out bytes);

                    if(Marshal.PtrToStringAnsi(domainPtr).Length > 0 && Marshal.PtrToStringAnsi(userPtr).Length > 0)
                    {
                        if(Marshal.PtrToStringAnsi(clientNamePtr).Length < 1)                       
                            Console.WriteLine(Marshal.PtrToStringAnsi(domainPtr) + "\\" + Marshal.PtrToStringAnsi(userPtr) + "\tSessionID: " + si.SessionID + "\tClientName: n/a");
                        else
                            Console.WriteLine(Marshal.PtrToStringAnsi(domainPtr) + "\\" + Marshal.PtrToStringAnsi(userPtr) + "\tSessionID: " + si.SessionID + "\tClientName: " + Marshal.PtrToStringAnsi(clientNamePtr));
                    }
                    WTSFreeMemory(clientNamePtr);
                    WTSFreeMemory(userPtr);
                    WTSFreeMemory(domainPtr);
                }
                WTSFreeMemory(SessionInfoPtr);
            }
        }
        catch(Exception ex)
        {
            Console.WriteLine("Exception: " + ex.Message);
        }
        finally
        {
            CloseServer(serverHandle);
        }
    }
}
'@

Add-Type $Code

Copy all of that into a file named QuerySessionInformation.ps1. Now launch the 32 bit version of Powershell in C:\Windows\SysWOW64\WindowsPowershell\v1.0. The code above uses pointers that will not work in a native 64 bit environment.

Now run the script. If you've never run the 32 bit version of Powershell on that server before, you will need to modify the script execution policy with Set-ExecutionPolicy, as 32 bit and 64 bit Powershell have separate execution policies. Note that there should be no output from the script itself, as all it is doing is compiling the .NET code and adding it to the current environment. Also note that once a type is added with Add-Type, you can not unload it without exiting that Powershell session... AFAIK. It makes debugging this sort of stuff really annoying as you have to restart Powershell every time you modify the code.

Now that the code is loaded, type this:

PS C:\> [RDPInfo]::ListUsers("REMOTESERVER") 

If there are any active user sessions on REMOTESERVER, the output will look like this:

DOMAIN\UserName SessionID: 2 ClientName: RYAN-PC 

This will work on remote computers as well as the local computer, but beware that if the user running this does not have sufficient permissions to the remote computer, it will fail silently (no output.)

PS: There are other bits of info in WTS_INFO_CLASS that may be of interest to you, such as WTSConnectState and WTSClientAddress. All you have to do is query for them with WTSQuerySessionInformation().

Accept-MeetingInvites.ps1

by Ryan 13. December 2012 15:17

Accept isn't an accepted verb in Powershell (Get-Verb,) but I don't really care.  Someone asked if there was a way, using Powershell, to log in to an Exchange mailbox, find all the meeting invites and accept them.  Piece of cake:

[Reflection.Assembly]::LoadWithPartialname("Microsoft.Office.Interop.Outlook") | Out-Null
$Folders = "Microsoft.Office.Interop.Outlook.OlDefaultFolders" -As [Type]
$Outlook = New-Object -ComObject Outlook.Application
$Namespace = $Outlook.GetNameSpace("MAPI")
$Inbox = $Namespace.getDefaultFolder($Folders::olFolderInbox)
ForEach ($_ In $Inbox.Items)
{
    If ($_.MessageClass -eq "IPM.Schedule.Meeting.Request") 
    {
        $AppointmentItem = $_.GetAssociatedAppointment($true)       
        $Response = $AppointmentItem.Respond(3,$True,$False)
        $Response.Send()
    }
}

Of course you need Outlook installed on the machine where you want to run this script, or otherwise have access to that assembly. You'd likely want to spruce the script up a bit to do things like log on to other mailboxes, have error handling, etc., but this is just a proof-of-concept that you can certainly build yourself a complete alternative Outlook client in Powershell if you wanted to.

Inside Windows 8: Pedro Teixeira - Thread pools

by Ryan 11. December 2012 20:30

Sorry for making a post consisting of nothing but a link, but this is a seriously cool video.  This guy is approaching Russinovich levels.  It's about some of the advances in Windows 8/Server 2012 at the kernel code level.

 

Inside Windows 8: Pedro Teixeira - Thread pools.

EventLogClearer v1.1.3.22

by Ryan 19. November 2012 20:45

I have released an updated version of my EventLogClearer, bringing it up to version 1.1.3.22. For the original release, see this post.

EventLogClearer 1.1.3.22

Improvements made in this version include:

  • Fixed a bug where the application acted weird if you ran the log clearing procedure two or more times in a row.
  • Added a new mechanism for supplying alternate credentials, instead of only being able to run as the currently logged on user. This applies to both auto-populating the list of computers from AD, and running the event log clearing procedure. If you leave the credentials blank or as the default, "username," the current user will be used.
  • Added the ability to clear a ton more Applications and Services logs than before, due to me realizing the potential of the EventLogSession class.

As before, .NET 4.5 is required to run the application. The project was built in Visual Studio 2012.

Here is the executable: EventLogClearer-1.1.3.22-exe.zip (68.71 kb)

Here is the source code: EventLogClearer-1.1.3.22-source.zip (308.11 kb)

Antivirus and .NET-ception

by Ryan 24. October 2012 14:15

So I was playing around the other day with a couple of antivirus products for Windows, trying to see if I could figure out ways to evade detection, (it was a slow day.) I feel very ambivalent pretty much hate antivirus software. As an IT guy, I know that it's a necessary evil, and every once in a while it does legitimately catch something that would have infected your system, but:

  • AV software often negatively impacts the performance of the entire system, unless you make so many concessions on what it doesn't scan that it practically defeats the purpose. I mean, yeah, your website probably would perform better if I excluded the entire inetpub folder, but then who's going to slow down that e-mail spam bot that you went and got yourself infected with? 
  • You might as well consider the installation of AV software on your machine a permanent modification. Just try and cleanly uninstall that crap I dare you.
  • False sense of security. Some AV products might be slightly better or worse than others, but none of them are approaching perfect. And none of them are a replacement for bad habits, such as clicking on all the links on that popup window that didn't get blocked by your web browser for some reason when you went to that one website with farm animals on it... I mean... I wouldn't know anything about that.

So the European Institute for Computer Antivirus Research (EICAR) made up this string of ASCII characters that antivirus product vendors and users can use to test their AV products. You can put it in a text file or in an executable file or whatever, and it's supposed to trigger the AV on your machine. Depending on your AV settings, even copying the EICAR string to your clipboard might trigger your antivirus. The "on-access" scanning of antivirus products uses a file system filter driver to intercept all disk I/O, or whenever an IRP_MJ_CREATE is issued for a file, but unless the AV product is also configured to actively scan memory and network activity, then you can evade it as long as your "evil code" stays in memory and doesn't touch the disk.

So I was playing around in Powershell and C#, and I wrote this rather silly bit of script... er, code... or scrode? It's a Powershell script that uses the Add-Type cmdlet to load in a C# class on the fly. That C# class in turn, contains code to compile some more code on the fly, and turn it into an executable. (Finally the Inception reference starts to make sense.) So just for fun I download the EICAR test file from the internet (which triggers some AV products, but not others, depending on how they're configured.) Now that I have the evil EICAR string in memory, I compile my executable, which does not itself contain any malicious code, so it won't trigger AV. Then I run that new process and pass the EICAR data to it as a parameter. In this way, I feel like I was able to get "malicious" data onto the computer and into a process that could then do whatever it wants with it - all without triggering the antivirus on the system because the EICAR data never touched the disk.

$SourceCode = @"
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CSharp;
using System.CodeDom.Compiler;

public class EvilClass
{
    public static void Main(string[] args)
    {
        var csc = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
        var parameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll" }, Environment.GetEnvironmentVariable("temp") + "\\evil.exe", true);
        parameters.GenerateExecutable = true;
        CompilerResults results = csc.CompileAssemblyFromSource(parameters,
        @"using System;
		  using System.Reflection;
          class AnotherEvilClassInsideAnotherEvilClass 
			{				
				public static void Main(string[] args)
				{					
					string msg = ""Data in evil.exe Main(): "" + args[0];
					System.Console.WriteLine(msg);					
				}
            }");
        results.Errors.Cast<CompilerError>().ToList().ForEach(error => Console.WriteLine(error.ErrorText));		
    }
}
"@

Add-Type -TypeDefinition $SourceCode -Language CSharp

Write-Host "Downloading some evil code from the internet and displaying it in the console..."
ForEach($_ in $(New-Object System.Net.Webclient).DownloadData('http://eicar.org/download/eicar.com'))
{
	$evilString += [char]$_
	Write-Host $([char]$_) -NoNewLine -ForegroundColor Red
}
Write-Host "`nGenerating evil.exe..."
[EvilClass]::Main([String]::Empty)
Write-Host "Executing evil.exe..."
& $Env:TEMP\evil.exe $evilString
Write-Host "Deleting evil.exe..."
Remove-Item $Env:TEMP\evil.exe

And here is the scrode in action:

Powershell power

EventLogClearer v1.0.1.22

by Ryan 30. September 2012 14:12

Update on 11/19/2012: Check out my updated version here.

I've been playing with Visual Studio 2012 the past couple of days, and this is the first full application I've written with it. I originally was going to try to make a Windows 8 modern-style application, but that's still a little too foreign to me and I ended up giving up and going back to a regular desktop application.

This application is designed to clear the selected event logs on remote systems. You can add the computer names manually, or you can scan the Active Directory of your current domain and the app will auto-populate the list with all the computers it found. There is some basic threading in the app to improve performance and GUI responsiveness. When you exit the app, your settings are saved in the Windows registry, and loaded back in again when you restart the application. All messages regarding success or failure will be shown in the Status window.

It requires .NET 4.5.  Testing was done on Windows 8 x64, but should run on any version of Windows with .NET 4.5. Thanks for Stackoverflow for helping me figure out that bug fix.

Screenshots:

EventLogClearer v1

 

EventLogClearer v1

Executable: EventLogClearer.exe (127.00 kb)

VS2012 Project (Source): EventLogClearer.v.1.0.1.22.zip (479.52 kb)

AliceAndBob v1.0: My Diffie-Hellman Tribute

by Ryan 25. September 2012 13:53

Sorry I haven't posted in a couple weeks. Busy as usual. However, I watched this video on Youtube the other day, which inspired me to write another C# application. It is of almost no actual use, but it is entertaining and maybe even a little educational.

It is my ode to the Diffie-Hellman key exchange concept, where two actors (Alice and Bob) are able to confidently exchange messages back and forth, even though Eve is eavesdropping on their line of communication and will intercept anything they send across the wire. This is because each Alice and Bob retain a secret key that is never shared publicly, but is an essential piece of information in coming to an agreement on a shared key. Alice and Bob are able to both agree on a shared key that can then be used as the seed in a symmetric encryption algorithm to hide their messages. Eve's only recourse is to calculate all possible discrete logarithms in attempt to arrive at the same solution as Alice and Bob did. With sufficiently large numbers, this can take a very, very long time. I highly recommend the Youtube video I posted above for a much nicer explanation.

I developed the application on Windows 7 x64, with .NET 4.0. (To make it work on Windows 8, I needed to put Microsoft.VisualBasic.PowerPacks.Vs.dll in the same directory since it wasn't on my Windows 8 machine.) Since this isn't a serious, production application, I didn't put any effort into error handling or threading, but I didn't encounter any crashes. I used the System.Numerics.BigInteger class to handle large numbers, but be careful when trying to use large numbers, as the calculations quickly become astronomical and it'll peg the processor it's running on. 

Without further ado, I present AliceAndBob v1.0:

AliceAndBobHelp

 

Here's the executable: AliceAndBob.exe (354.50 kb)

And here is the entire Visual Studio 2010 project (source code): AliceAndBob.zip (746.71 kb)

As always, bug reports and enhancement requests are welcome. Take it easy on me as I never claimed to be a mathematician or a cryptography guru, but I welcome your constructive criticisms.

Mortal Countdown

by Ryan 21. August 2012 19:06

I admit, this one is a little bit macabre. But it's something I was thinking about, and I was thinking about programming at the same time, so I decided to write a C# app for it!

 

 

You can use the Setup function of the app to set your birthday in UTC, down to the second if you know it, and how many years you anticipate spending on this mortal coil. (A figure which is subject to change, obviously.) The settings are saved in HKCU so you do not need to reset the info every time you reopen the app. Warning: It's a little creepy when you realize the percentage has crept up every time you hover over the hourglass.

That's pretty much all there is to it. 64-bit Windows and .NET 4 are required. There's no good reason for that other than that I don't like 32-bit Windows and < .NET 4. It's 2012, people.

As always, feel free to hit me with bug reports and enhancement requests.

Here's the executable: Mortal Countdown.exe (40.50 kb)

And here's the source (VS2010): Mortal Countdown.zip (64.30 kb)

About

Name: Ryan Ries
Location: Texas, USA
Occupation: Systems Engineer 

I am a Windows engineer and Microsoft advocate, but I can run with pretty much any system that uses electricity.  I'm all about getting closer to the cutting edge of technology while using the right tool for the job.

This blog is about exploring IT and documenting the journey.


Blog Posts (or Vids) You Must Read (or See):

Pushing the Limits of Windows by Mark Russinovich
Mysteries of Windows Memory Management by Mark Russinovich
Accelerating Your IT Career by Ned Pyle
Post-Graduate AD Studies by Ned Pyle
MCM: Active Directory Series by PFE Platforms Team
Encodings And Character Sets by David C. Zentgraf
Active Directory Maximum Limits by Microsoft


MCITP: Enterprise Administrator

Profile for Ryan Ries at Server Fault, Q&A for system administrators

Twitter

LOPSA

 

I do not discuss my employers on this blog and all opinions expressed are mine and do not reflect the opinions of my employers.