TinyWebRedirector

I wrote something this weekend.

TinyWebRedirector v1.0 - Redirects HTTP Requests
Copyright (C) 2015 Joseph Ryan Ries
www.myotherpcisacloud.com

Usage:
Install:   TinyWebRedirector -install
Uninstall: TinyWebRedirector -uninstall

I wrote this micro web server for all the sysadmins out there who have an internal Active Directory that shares the same DNS name as their public domain name. Let's say your internal AD domain name is contoso.com. Your public website is also contoso.com. In this scenario, internal employees at the office cannot reach your public website by entering http://contoso.com into their web browsers, because contoso.com internally resolves to the IP address of one of your AD domain controllers. This has lead to messy solutions, such as installing IIS on each domain controller, for the sole purpose of redirecting requests on port 80 to www.contoso.com. But installing IIS on your domain controllers is not a great idea.

TinyWebRedirector is more suited to this purpose because:

  • It is tiny. The image file is 110KB, and runs with a ~2.6MB working set. It requires no redistributable DLLs.
  • It does one thing and one thing only. This translates to a much smaller potential attack surface than a large web server such as IIS.
  • It is written in C, and so does not require .NET. Will run on any Windows machine Vista/2008 or greater.
  • The listening port (default 80) and the URL to redirect visitors to is configurable in the registry at HKLM\SYSTEM\CurrentControlSet\Services\TinyWebRedirector. Restart the service for changes to take effect.
  • The service runs as Local Service. This is a much safer configuration than services that run as Local System.

Please let me know if you find any bugs or weaknesses.

Github repository is here.

The compiled and signed x64 binary is here:

TinyWebRedirector.exe (112.8KB)

The Basics of DLL Injection Part I

Hello... sorry for not posting in a while.  I've been going through some recent career changes and haven't really come across any IT-related inspiration lately.  I thought I'd post a simple, bare-bones technique of injecting a DLL into a running process on a Windows computer.  It's really simple.

  1. Verify that the PID supplied is valid.
  2. Get a handle to the remote process.
  3. Allocate memory in the remote process.
  4. Write the DLL path into the remote process's memory.
  5. Call CreateRemoteThread to induce LoadLibrary in the remote process. 

As long as the process does not already have the DLL loaded, the remote process will automatically call DllMain (the injected DLL's entry point,) which can contain all sorts of fun code.  For instance, now that you have injected a DLL into the remote process, you are well on your way to performing something like API hooking, for example. You could modify the Import Address Table of the remote process... but I'm getting ahead of myself.  First things first.  Without further ado:

// InjectDLL.cpp
// Joseph Ryan Ries - 2015
// Injects a DLL into another process on the system.

#include 
#include 
#include 

// Returns true if the supplied file path exists and is a file.
// Returns false if the supplied path does not exist, or is a directory.
BOOL FileExists(LPCTSTR FilePath)
{
	DWORD FileAttributes = GetFileAttributes(FilePath);

	return (FileAttributes != INVALID_FILE_ATTRIBUTES && !(FileAttributes & FILE_ATTRIBUTE_DIRECTORY));
}

// Entry point. Returns 0 at the end if everything was successful.
// Returns 1 if something failed. Outputs messages to console.
int wmain(int argc, wchar_t *argv[])
{
	if (argc != 3)
	{
		wprintf_s(L"\nUsage: %s C:\\Temp\\MyDLL.dll 1337\n", argv[0]);
		wprintf_s(L"\nUse the full path to the DLL you want to inject and supply the\n");
		wprintf_s(L"process ID (PID) of the process you want to inject it in to.\n");
		return(1);
	}

	if (!FileExists(argv[1]))
	{
		wprintf_s(L"\nERROR: Unable to find file %s.\n", argv[1]);
		return(1);
	}

	wchar_t DLLPath[MAX_PATH] = { 0 };

	wcscpy_s(DLLPath, argv[1]);

	DWORD Pid = _wtoi(argv[2]);

	if (Pid == 0)
	{
		wprintf_s(L"\nERROR: Unable to interpret the supplied process ID.\n");
		return(1);
	}

	wprintf_s(L"Searching for PID %d.\n", Pid);

	DWORD CurrentProcesses[2048] = { 0 };
	DWORD ProcessListSize = 0;
	DWORD ProcessCount = 0;

	if (EnumProcesses(CurrentProcesses, sizeof(CurrentProcesses), &ProcessListSize) == 0)
	{
		wprintf_s(L"\nERROR: Unable to enumerate currently running processes!\nLastError: 0x%x.\n", GetLastError());
		return(1);
	}

	ProcessCount = ProcessListSize / sizeof(DWORD);

	wprintf_s(L"%d processes currently on the system.\n", ProcessCount - 1);

	BOOL ProcessFound = FALSE;

	for (unsigned int ProcessCounter = 0; ProcessCounter < ProcessCount; ProcessCounter++)
	{
		if (CurrentProcesses[ProcessCounter] == 0)
		{
			continue;
		}
		if (CurrentProcesses[ProcessCounter] == Pid)
		{
			ProcessFound = TRUE;
			break;
		}
	}

	if (ProcessFound == FALSE)
	{
		wprintf_s(L"\nERROR: A process with PID %d could not be found.\n", Pid);
		return(1);
	}

	HANDLE ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);

	if (ProcessHandle == NULL)
	{
		wprintf_s(L"\nERROR: Unable to open handle to process %d. LastError 0x%x.\n", Pid, GetLastError());
		return(1);
	}

	wprintf_s(L"Process with PID %d successfully opened.\n", Pid);

	void* RemoteDLLPathMemory = NULL;		// The DLL entry address in the remote process

	// VirtualAlloc will probably not give us less than 4k. Whatever. We only need ~520 bytes for MAX_PATH * 2.
	RemoteDLLPathMemory = VirtualAllocEx(ProcessHandle, NULL, (MAX_PATH * sizeof(wchar_t)), MEM_COMMIT, PAGE_READWRITE);

	if (RemoteDLLPathMemory == NULL)
	{
		wprintf_s(L"\nERROR: Unable to allocate memory in remote process %d. LastError: 0x%x.\n", Pid, GetLastError());
		return(1);
	}

	wprintf_s(L"Memory allocated in process %d.\n", Pid);

	SIZE_T BytesWritten = 0;

	if (WriteProcessMemory(ProcessHandle, RemoteDLLPathMemory, (void*)DLLPath, sizeof(DLLPath), &BytesWritten) == 0)
	{
		wprintf_s(L"\nERROR: Unable to write DLL path into remote process memory. LastError: 0x%x.\n", GetLastError());
		return(1);
	}

	wprintf_s(L"%I64d bytes written into the memory of process %d.\n", BytesWritten, Pid);
	
	HANDLE ThreadHandle = CreateRemoteThread(
		ProcessHandle,
		NULL,
		NULL,
		(LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW"),
		RemoteDLLPathMemory,
		NULL,
		NULL);

	if (ThreadHandle == NULL)
	{
		wprintf_s(L"\nERROR: Unable to start remote thread in process %d. LastError: 0x%x.\n", Pid, GetLastError());
		return(1);
	}

	wprintf_s(L"Successfully loaded %s into process %d.\n", DLLPath, Pid);

	wprintf_s(L"Note: DllMain will only run if the DLL has not already been loaded by the process.\n");
	
	return(0);
}

Ignore the very last line (</psapi.h></windows.h></stdio.h>), the code editor wigged out.

Bulk Modification of Active Directory ACLs with Powershell

The other day, I encountered an Active Directory-related problem.  The security model on the organizational units in this particular domain was quite complicated, arising from the multi-tenant nature of this domain and its "List Object Mode" configuration. (I've talked about List Object Mode before.)

First, a little background.  Imagine you're looking at the OUs in Active Directory Users & Computers:

CONTOSO.COM
   |
   + Builtin
   + Computers
   - Customers_OU
        |
        + Customer1_OU
        + Customer2_OU
        + Customer3_OU
        + Customer4_OU
        + ...

To cut to the chase, there was a problem with the ACL on each individual OU beneath Customers_OU (and there were hundreds!) where an access control entry (ACE) had been applied directly to each OU that allowed "Authenticated Users" the generic read permission.  This was essentially undermining the inheritance of permissions from parent object to child object, and allowing an account in Customer1_OU to view the contents of Customer2_OU, and vice versa.

*The siren noise from Kill Bill plays here.*

I'm glossing over some of the details, but basically what needed to happen, was for me to enumerate over each and every one of those hundreds of individual customer OUs, and remove those <not inherited> ACEs.

There was no way I was going to do that by hand, through the GUI.

So I scripted it.

Import-Module ActiveDirectory
Set-Location AD:\
$AllOUs = Get-ADOrganizationalUnit -Filter * `
             -SearchBase 'OU=CUSTOMERS_OU,DC=CONTOSO,DC=COM' `
             -Properties * -SearchScope OneLevel

Foreach ($OU In $AllOUs)
{
    $ACL = Get-ACL $OU.DistinguishedName
    
    Foreach ($ACE In $ACL.Access)
    {
        If (($ACE.IdentityReference -EQ 'NT AUTHORITY\Authenticated Users') -AND `
            ($ACE.IsInherited -EQ $False))
        {            
            $ACL.RemoveAccessRule($ACE)
            Set-ACL -AclObject $ACL $OU.DistinguishedName -Verbose
        }
    }
} 

That would have taken hours if done manually.  Hours that I'd rather spend playing GTA V.

Keeping My Sysinternals Tools Up To Date

Microsoft Sysinternals (primarily Mark Russinovich, but with occasional help from his buddies like Andrew Richards, Thomas Garnier, etc.,) has put out a ton of very useful Windows tools and utilities over the years. If you spend any time at all on Windows computers, you undoubtedly know and love these tools.

The tools are updated quite regularly, too.  And I hate having out of date stuff.  So, I wrote a little bit of Powershell this morning that will automatically scan my local folder to see which Sysinternals tools I have in that folder (like C:\Program Files\Sysinternals\,) then compare them with the latest and greatest versions of the executables on the internet (it's WebDAV,) and update my local copy if necessary.  I just run this script as a scheduled task once a week.

Set-StrictMode -Version Latest

[String]$EventSource    = 'UpdateSysinternals'
[String]$LocalDirectory = 'C:\Program Files\SysInternals\*.exe'
[String]$WebDAVShare    = '\\live.sysinternals.com\Tools'
[Int]$FilesUpdated      = 0

If (-Not([System.Diagnostics.EventLog]::SourceExists($EventSource)))
{
    Try
    {
        New-EventLog -LogName Application -Source $EventSource -ErrorAction Stop
        # MSDN says you shouldn't immediately use a new source after creating it.
        Start-Sleep -Seconds 10
        Write-EventLog -LogName Application -Source $EventSource -EntryType Information ` 
             -EventId 1 -Message 'Event source created.'
    }
    Catch
    {
        Exit
    }
}

$LocalFiles = Get-ChildItem $LocalDirectory

Try
{
    New-PSDrive -Name S -PSProvider FileSystem `
           -Root $WebDAVShare -ErrorAction Stop | Out-Null
}
Catch
{
    Write-EventLog -LogName Application -Source $EventSource -EntryType Error `
          -EventId 2 -Message "Failed to connect to $WebDAVShare!"
    Exit
}

Foreach ($LocalFile In $LocalFiles)
{    
    $RemoteFile = Get-ChildItem "S:\$($LocalFile.Name)"
    If ([Version]$RemoteFile.VersionInfo.ProductVersion -NE [Version]$LocalFile.VersionInfo.ProductVersion)
    {
        Write-EventLog -LogName Application -Source $EventSource -EntryType Information `
             -EventId 3 -Message "Remote file $($RemoteFile.Name) has version $($RemoteFile.VersionInfo.ProductVersion), which does not match local version $($LocalFile.VersionInfo.ProductVersion)."
        Copy-Item $RemoteFile.FullName $LocalFile.FullName -Force
        $FilesUpdated++
    }
}

Remove-PSDrive S

Write-EventLog -LogName Application -Source $EventSource -EntryType Information `
     -EventId 4 -Message "$FilesUpdated file(s) were updated."

Removing the 'Protect object from accidental deletion' Flag From A Bunch of OUs

I was recently conducting an experiment that involved creating a ton of Organizational Units in Active Directory.  Typically, when new OUs are created, they have a flag set on them that says "Protect object from accidental deletion."

This is normally fine, as you wouldn't want an admin to accidentally delete an entire OU full of users and computers.  But I had created a large hierarchy of OUs to conduct an experiment, and now that it was done, I wanted to get rid of all the OUs.  Like, immediately.  And I didn't want to sit in the AD Users & Computers GUI and uncheck that box on every OU, one at a time, so that it would let me delete them.

$AllOUs = Get-ADObject -Filter "ObjectClass -EQ 'organizationalUnit'" `
          -SearchBase 'OU=TestOUs,DC=CONTOSO,DC=COM' -Properties *

Foreach ($OU In $AllOUs)
{
    Set-ADOrganizationalUnit $OU.DistinguishedName `
          -ProtectedFromAccidentalDeletion $False
}

Now I can just delete the base of the tree and they'll all vanish.