From 27f62fce85a07a4687dcfaf32675515e60726b08 Mon Sep 17 00:00:00 2001 From: StudentEx Date: Mon, 20 Aug 2018 13:18:10 +0800 Subject: [PATCH] Unit test for plugin launch --- test/ProcessEnvironment.cs | 713 +++++++++++++++++++++++++++++++++++++ test/UnitTest.cs | 163 +++++++++ test/test.csproj | 2 + 3 files changed, 878 insertions(+) create mode 100644 test/ProcessEnvironment.cs diff --git a/test/ProcessEnvironment.cs b/test/ProcessEnvironment.cs new file mode 100644 index 00000000..166cad97 --- /dev/null +++ b/test/ProcessEnvironment.cs @@ -0,0 +1,713 @@ +/* *************************************************************************** + +The component allows to read the environment variables of another process +running in a Windows system. + +History: + + - v1.2.ss Add GetCommandLine for convenience. + + - v1.2: Added support for inspection of 64 bit processes from 32 bit host + - v1.1: Fixed issue with environment block size detection + - v1.0: Initial + + +****************************************************************************** + +The MIT License (MIT) + +Copyright (c) 2011-2014 Oleksiy Gapotchenko +Copyright (c) 2018 Shadowsocks Project + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*************************************************************************** */ + +using System; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; +using System.Management; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace test +{ + static class ProcessEnvironment + { + public static StringDictionary ReadEnvironmentVariables(this Process process) + { + return _GetEnvironmentVariablesCore(process.Handle); + } + + public static StringDictionary TryReadEnvironmentVariables(this Process process) + { + try + { + return ReadEnvironmentVariables(process); + } + catch + { + return null; + } + } + + public static string GetCommandLine(this Process process) + { + using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT CommandLine FROM Win32_Process WHERE ProcessId = " + process.Id)) + using (ManagementObjectCollection objects = searcher.Get()) + { + return objects.Cast().SingleOrDefault()?["CommandLine"]?.ToString(); + } + + } + + /// + /// Universal pointer. + /// + struct UniPtr + { + public UniPtr(IntPtr p) + { + Value = p.ToInt64(); + Size = IntPtr.Size; + } + + public UniPtr(long p) + { + Value = p; + Size = sizeof(long); + } + + public long Value; + public int Size; + + public static implicit operator IntPtr(UniPtr p) + { + return new IntPtr(p.Value); + } + + public static implicit operator UniPtr(IntPtr p) + { + return new UniPtr(p); + } + + public override string ToString() + { + return Value.ToString(); + } + + public bool FitsInNativePointer + { + get + { + return Size <= IntPtr.Size; + } + } + + public bool CanBeRepresentedByNativePointer + { + get + { + int actualSize = Size; + + if (actualSize == 8) + { + if (Value >> 32 == 0) + actualSize = 4; + } + + return actualSize <= IntPtr.Size; + } + } + + public long ToInt64() + { + return Value; + } + } + + static StringDictionary _GetEnvironmentVariablesCore(IntPtr hProcess) + { + var penv = _GetPenv(hProcess); + + const int maxEnvSize = 32767; + byte[] envData; + + if (penv.CanBeRepresentedByNativePointer) + { + int dataSize; + if (!_HasReadAccess(hProcess, penv, out dataSize)) + throw new Exception("Unable to read environment block."); + + if (dataSize > maxEnvSize) + dataSize = maxEnvSize; + + envData = new byte[dataSize]; + var res_len = IntPtr.Zero; + bool b = WindowsApi.ReadProcessMemory( + hProcess, + penv, + envData, + new IntPtr(dataSize), + ref res_len); + + if (!b || (int)res_len != dataSize) + throw new Exception("Unable to read environment block data."); + } + else if (penv.Size == 8 && IntPtr.Size == 4) + { + // Accessing 64 bit process under 32 bit host. + + int dataSize; + if (!_HasReadAccessWow64(hProcess, penv.ToInt64(), out dataSize)) + throw new Exception("Unable to read environment block with WOW64 API."); + + if (dataSize > maxEnvSize) + dataSize = maxEnvSize; + + envData = new byte[dataSize]; + long res_len = 0; + int result = WindowsApi.NtWow64ReadVirtualMemory64( + hProcess, + penv.ToInt64(), + envData, + dataSize, + ref res_len); + + if (result != WindowsApi.STATUS_SUCCESS || res_len != dataSize) + throw new Exception("Unable to read environment block data with WOW64 API."); + } + else + { + throw new Exception("Unable to access process memory due to unsupported bitness cardinality."); + } + + return _EnvToDictionary(envData); + } + + static StringDictionary _EnvToDictionary(byte[] env) + { + var result = new StringDictionary(); + + int len = env.Length; + if (len < 4) + return result; + + int n = len - 3; + for (int i = 0; i < n; ++i) + { + byte c1 = env[i]; + byte c2 = env[i + 1]; + byte c3 = env[i + 2]; + byte c4 = env[i + 3]; + + if (c1 == 0 && c2 == 0 && c3 == 0 && c4 == 0) + { + len = i + 3; + break; + } + } + + char[] environmentCharArray = Encoding.Unicode.GetChars(env, 0, len); + + for (int i = 0; i < environmentCharArray.Length; i++) + { + int startIndex = i; + while ((environmentCharArray[i] != '=') && (environmentCharArray[i] != '\0')) + { + i++; + } + if (environmentCharArray[i] != '\0') + { + if ((i - startIndex) == 0) + { + while (environmentCharArray[i] != '\0') + { + i++; + } + } + else + { + string str = new string(environmentCharArray, startIndex, i - startIndex); + i++; + int num3 = i; + while (environmentCharArray[i] != '\0') + { + i++; + } + string str2 = new string(environmentCharArray, num3, i - num3); + result[str] = str2; + } + } + } + + return result; + } + + static bool _TryReadIntPtr32(IntPtr hProcess, IntPtr ptr, out IntPtr readPtr) + { + bool result; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + } + finally + { + int dataSize = sizeof(Int32); + var data = Marshal.AllocHGlobal(dataSize); + IntPtr res_len = IntPtr.Zero; + bool b = WindowsApi.ReadProcessMemory( + hProcess, + ptr, + data, + new IntPtr(dataSize), + ref res_len); + readPtr = new IntPtr(Marshal.ReadInt32(data)); + Marshal.FreeHGlobal(data); + if (!b || (int)res_len != dataSize) + result = false; + else + result = true; + } + return result; + } + + static bool _TryReadIntPtr(IntPtr hProcess, IntPtr ptr, out IntPtr readPtr) + { + bool result; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + } + finally + { + int dataSize = IntPtr.Size; + var data = Marshal.AllocHGlobal(dataSize); + IntPtr res_len = IntPtr.Zero; + bool b = WindowsApi.ReadProcessMemory( + hProcess, + ptr, + data, + new IntPtr(dataSize), + ref res_len); + readPtr = Marshal.ReadIntPtr(data); + Marshal.FreeHGlobal(data); + if (!b || (int)res_len != dataSize) + result = false; + else + result = true; + } + return result; + } + + static bool _TryReadIntPtrWow64(IntPtr hProcess, long ptr, out long readPtr) + { + bool result; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + } + finally + { + int dataSize = sizeof(long); + var data = Marshal.AllocHGlobal(dataSize); + long res_len = 0; + int status = WindowsApi.NtWow64ReadVirtualMemory64( + hProcess, + ptr, + data, + dataSize, + ref res_len); + readPtr = Marshal.ReadInt64(data); + Marshal.FreeHGlobal(data); + if (status != WindowsApi.STATUS_SUCCESS || res_len != dataSize) + result = false; + else + result = true; + } + return result; + } + + static UniPtr _GetPenv(IntPtr hProcess) + { + int processBitness = _GetProcessBitness(hProcess); + + if (processBitness == 64) + { + if (Environment.Is64BitProcess) + { + // Accessing 64 bit process under 64 bit host. + + IntPtr pPeb = _GetPeb64(hProcess); + + IntPtr ptr; + if (!_TryReadIntPtr(hProcess, pPeb + 0x20, out ptr)) + throw new Exception("Unable to read PEB."); + + IntPtr penv; + if (!_TryReadIntPtr(hProcess, ptr + 0x80, out penv)) + throw new Exception("Unable to read RTL_USER_PROCESS_PARAMETERS."); + + return penv; + } + else + { + // Accessing 64 bit process under 32 bit host. + + var pPeb = _GetPeb64(hProcess); + + long ptr; + if (!_TryReadIntPtrWow64(hProcess, pPeb.ToInt64() + 0x20, out ptr)) + throw new Exception("Unable to read PEB."); + + long penv; + if (!_TryReadIntPtrWow64(hProcess, ptr + 0x80, out penv)) + throw new Exception("Unable to read RTL_USER_PROCESS_PARAMETERS."); + + return new UniPtr(penv); + } + } + else + { + // Accessing 32 bit process under 32 bit host. + + IntPtr pPeb = _GetPeb32(hProcess); + + IntPtr ptr; + if (!_TryReadIntPtr32(hProcess, pPeb + 0x10, out ptr)) + throw new Exception("Unable to read PEB."); + + IntPtr penv; + if (!_TryReadIntPtr32(hProcess, ptr + 0x48, out penv)) + throw new Exception("Unable to read RTL_USER_PROCESS_PARAMETERS."); + + return penv; + } + } + + static int _GetProcessBitness(IntPtr hProcess) + { + if (Environment.Is64BitOperatingSystem) + { + bool wow64; + if (!WindowsApi.IsWow64Process(hProcess, out wow64)) + return 32; + if (wow64) + return 32; + return 64; + } + else + { + return 32; + } + } + + static IntPtr _GetPeb32(IntPtr hProcess) + { + if (Environment.Is64BitProcess) + { + var ptr = IntPtr.Zero; + int res_len = 0; + int pbiSize = IntPtr.Size; + int status = WindowsApi.NtQueryInformationProcess( + hProcess, + WindowsApi.ProcessWow64Information, + ref ptr, + pbiSize, + ref res_len); + if (res_len != pbiSize) + throw new Exception("Unable to query process information."); + return ptr; + } + else + { + return _GetPebNative(hProcess); + } + } + + static IntPtr _GetPebNative(IntPtr hProcess) + { + var pbi = new WindowsApi.PROCESS_BASIC_INFORMATION(); + int res_len = 0; + int pbiSize = Marshal.SizeOf(pbi); + int status = WindowsApi.NtQueryInformationProcess( + hProcess, + WindowsApi.ProcessBasicInformation, + ref pbi, + pbiSize, + ref res_len); + if (res_len != pbiSize) + throw new Exception("Unable to query process information."); + return pbi.PebBaseAddress; + } + + static UniPtr _GetPeb64(IntPtr hProcess) + { + if (Environment.Is64BitProcess) + { + return _GetPebNative(hProcess); + } + else + { + // Get PEB via WOW64 API. + var pbi = new WindowsApi.PROCESS_BASIC_INFORMATION_WOW64(); + int res_len = 0; + int pbiSize = Marshal.SizeOf(pbi); + int status = WindowsApi.NtWow64QueryInformationProcess64( + hProcess, + WindowsApi.ProcessBasicInformation, + ref pbi, + pbiSize, + ref res_len); + if (res_len != pbiSize) + throw new Exception("Unable to query process information."); + return new UniPtr(pbi.PebBaseAddress); + } + } + + static bool _HasReadAccess(IntPtr hProcess, IntPtr address, out int size) + { + size = 0; + + var memInfo = new WindowsApi.MEMORY_BASIC_INFORMATION(); + int result = WindowsApi.VirtualQueryEx( + hProcess, + address, + ref memInfo, + Marshal.SizeOf(memInfo)); + + if (result == 0) + return false; + + if (memInfo.Protect == WindowsApi.PAGE_NOACCESS || memInfo.Protect == WindowsApi.PAGE_EXECUTE) + return false; + + try + { + size = Convert.ToInt32(memInfo.RegionSize.ToInt64() - (address.ToInt64() - memInfo.BaseAddress.ToInt64())); + } + catch (OverflowException) + { + return false; + } + + if (size <= 0) + return false; + + return true; + } + + static bool _HasReadAccessWow64(IntPtr hProcess, long address, out int size) + { + size = 0; + + WindowsApi.MEMORY_BASIC_INFORMATION_WOW64 memInfo; + var memInfoType = typeof(WindowsApi.MEMORY_BASIC_INFORMATION_WOW64); + int memInfoLength = Marshal.SizeOf(memInfoType); + const int memInfoAlign = 8; + + long resultLength = 0; + int result; + + IntPtr hMemInfo = Marshal.AllocHGlobal(memInfoLength + memInfoAlign * 2); + try + { + // Align to 64 bits. + IntPtr hMemInfoAligned = new IntPtr(hMemInfo.ToInt64() & ~(memInfoAlign - 1L)); + + result = WindowsApi.NtWow64QueryVirtualMemory64( + hProcess, + address, + WindowsApi.MEMORY_INFORMATION_CLASS.MemoryBasicInformation, + hMemInfoAligned, + memInfoLength, + ref resultLength); + + memInfo = (WindowsApi.MEMORY_BASIC_INFORMATION_WOW64)Marshal.PtrToStructure(hMemInfoAligned, memInfoType); + } + finally + { + Marshal.FreeHGlobal(hMemInfo); + } + + if (result != WindowsApi.STATUS_SUCCESS) + return false; + + if (memInfo.Protect == WindowsApi.PAGE_NOACCESS || memInfo.Protect == WindowsApi.PAGE_EXECUTE) + return false; + + try + { + size = Convert.ToInt32(memInfo.RegionSize - (address - memInfo.BaseAddress)); + } + catch (OverflowException) + { + return false; + } + + if (size <= 0) + return false; + + return true; + } + + static class WindowsApi + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PROCESS_BASIC_INFORMATION + { + public IntPtr Reserved1; + public IntPtr PebBaseAddress; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public IntPtr[] Reserved2; + public IntPtr UniqueProcessId; + public IntPtr Reserved3; + } + + public const int ProcessBasicInformation = 0; + public const int ProcessWow64Information = 26; + + [DllImport("ntdll.dll", SetLastError = true)] + public static extern int NtQueryInformationProcess( + IntPtr hProcess, + int pic, + ref PROCESS_BASIC_INFORMATION pbi, + int cb, + ref int pSize); + + [DllImport("ntdll.dll", SetLastError = true)] + public static extern int NtQueryInformationProcess( + IntPtr hProcess, + int pic, + ref IntPtr pi, + int cb, + ref int pSize); + + [DllImport("ntdll.dll", SetLastError = true)] + public static extern int NtQueryInformationProcess( + IntPtr hProcess, + int pic, + ref long pi, + int cb, + ref int pSize); + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PROCESS_BASIC_INFORMATION_WOW64 + { + public long Reserved1; + public long PebBaseAddress; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public long[] Reserved2; + public long UniqueProcessId; + public long Reserved3; + } + + [DllImport("ntdll.dll", SetLastError = true)] + public static extern int NtWow64QueryInformationProcess64( + IntPtr hProcess, + int pic, + ref PROCESS_BASIC_INFORMATION_WOW64 pbi, + int cb, + ref int pSize); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool ReadProcessMemory( + IntPtr hProcess, + IntPtr lpBaseAddress, + [Out] byte[] lpBuffer, + IntPtr dwSize, + ref IntPtr lpNumberOfBytesRead); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool ReadProcessMemory( + IntPtr hProcess, + IntPtr lpBaseAddress, + IntPtr lpBuffer, + IntPtr dwSize, + ref IntPtr lpNumberOfBytesRead); + + [DllImport("ntdll.dll", SetLastError = true)] + public static extern int NtWow64ReadVirtualMemory64( + IntPtr hProcess, + long lpBaseAddress, + IntPtr lpBuffer, + long dwSize, + ref long lpNumberOfBytesRead); + + [DllImport("ntdll.dll", SetLastError = true)] + public static extern int NtWow64ReadVirtualMemory64( + IntPtr hProcess, + long lpBaseAddress, + [Out] byte[] lpBuffer, + long dwSize, + ref long lpNumberOfBytesRead); + + public const int STATUS_SUCCESS = 0; + + public const int PAGE_NOACCESS = 0x01; + public const int PAGE_EXECUTE = 0x10; + + [StructLayout(LayoutKind.Sequential)] + public struct MEMORY_BASIC_INFORMATION + { + public IntPtr BaseAddress; + public IntPtr AllocationBase; + public int AllocationProtect; + public IntPtr RegionSize; + public int State; + public int Protect; + public int Type; + } + + [DllImport("kernel32.dll")] + public static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, ref MEMORY_BASIC_INFORMATION lpBuffer, int dwLength); + + [StructLayout(LayoutKind.Sequential)] + public struct MEMORY_BASIC_INFORMATION_WOW64 + { + public long BaseAddress; + public long AllocationBase; + public int AllocationProtect; + public long RegionSize; + public int State; + public int Protect; + public int Type; + } + + public enum MEMORY_INFORMATION_CLASS + { + MemoryBasicInformation + } + + [DllImport("ntdll.dll")] + public static extern int NtWow64QueryVirtualMemory64( + IntPtr hProcess, + long lpAddress, + MEMORY_INFORMATION_CLASS memoryInformationClass, + IntPtr lpBuffer, // MEMORY_BASIC_INFORMATION_WOW64, pointer must be 64-bit aligned + long memoryInformationLength, + ref long returnLength); + + [DllImport("kernel32.dll")] + public static extern bool IsWow64Process(IntPtr hProcess, out bool wow64Process); + } + } +} diff --git a/test/UnitTest.cs b/test/UnitTest.cs index f46478c6..7592aff8 100755 --- a/test/UnitTest.cs +++ b/test/UnitTest.cs @@ -9,6 +9,9 @@ using System.Collections.Generic; using Shadowsocks.Controller.Hotkeys; using Shadowsocks.Encryption.Stream; using Shadowsocks.Model; +using Shadowsocks.Controller.Service; +using System.Diagnostics; +using System.Net; namespace test { @@ -481,5 +484,165 @@ namespace test Assert.AreEqual(expected, actual); } } + + [TestMethod] + public void PluginSupport() + { + string fake_plugin = "ftp"; + + var NoPlugin = Sip003Plugin.CreateIfConfigured(new Server + { + server = "192.168.100.1", + server_port = 8888, + password = "test", + method = "bf-cfb" + }); + + RunPluginSupportTest( + NoPlugin, + "", + "", + "", + "192.168.100.1", + 8888); + + var Plugin = Sip003Plugin.CreateIfConfigured(new Server + { + server = "192.168.100.1", + server_port = 8888, + password = "test", + method = "bf-cfb", + plugin = fake_plugin + }); + RunPluginSupportTest( + Plugin, + fake_plugin, + "", + "", + "192.168.100.1", + 8888); + + var PluginWithOpts = Sip003Plugin.CreateIfConfigured(new Server + { + server = "192.168.100.1", + server_port = 8888, + password = "test", + method = "bf-cfb", + plugin = fake_plugin, + plugin_opts = "_option" + }); + RunPluginSupportTest( + PluginWithOpts, + fake_plugin, + "_option", + "", + "192.168.100.1", + 8888); + + var PluginWithArgs = Sip003Plugin.CreateIfConfigured(new Server + { + server = "192.168.100.1", + server_port = 8888, + password = "test", + method = "bf-cfb", + plugin = fake_plugin, + plugin_args = "_test" + }); + RunPluginSupportTest( + PluginWithArgs, + fake_plugin, + "", + "_test", + "192.168.100.1", + 8888); + + var PluginWithOptsAndArgs = Sip003Plugin.CreateIfConfigured(new Server + { + server = "192.168.100.1", + server_port = 8888, + password = "test", + method = "bf-cfb", + plugin = fake_plugin, + plugin_opts = "_option", + plugin_args = "_test" + }); + RunPluginSupportTest( + PluginWithOptsAndArgs, + fake_plugin, + "_option", + "_test", + "192.168.100.1", + 8888); + + var PluginWithArgsReplaced = Sip003Plugin.CreateIfConfigured(new Server + { + server = "192.168.100.1", + server_port = 8888, + password = "test", + method = "bf-cfb", + plugin = fake_plugin, + plugin_args = "_test,%SS_REMOTE_HOST%" + }); + RunPluginSupportTest( + PluginWithArgsReplaced, + fake_plugin, + "", + "_test,192.168.100.1", + "192.168.100.1", + 8888); + + var PluginWithOptsAndArgsReplaced = Sip003Plugin.CreateIfConfigured(new Server + { + server = "192.168.100.1", + server_port = 8888, + password = "test", + method = "bf-cfb", + plugin = fake_plugin, + plugin_opts = "_option", + plugin_args = "_test,%SS_REMOTE_HOST%,%SS_PLUGIN_OPTIONS%" + }); + RunPluginSupportTest( + PluginWithOptsAndArgsReplaced, + fake_plugin, + "_option", + "_test,192.168.100.1,_option", + "192.168.100.1", + 8888); + } + + private static void RunPluginSupportTest(Sip003Plugin plugin, string pluginName, string pluginOpts, string pluginArgs, string serverAddress, int serverPort) + { + + if (string.IsNullOrWhiteSpace(pluginName)) return; + + plugin.StartIfNeeded(); + + Process[] processes = Process.GetProcessesByName(pluginName); + Assert.AreEqual(processes.Length, 1); + Process p = processes[0]; + + + var penv = ProcessEnvironment.ReadEnvironmentVariables(p); + var pcmd = ProcessEnvironment.GetCommandLine(p).Trim(); + pcmd = pcmd.IndexOf(' ') >= 0 ? pcmd.Substring(pcmd.IndexOf(' ') + 1) : ""; + + Assert.AreEqual(penv["SS_REMOTE_HOST"], serverAddress); + Assert.AreEqual(penv["SS_REMOTE_PORT"], serverPort.ToString()); + Assert.AreEqual(penv["SS_LOCAL_HOST"], IPAddress.Loopback.ToString()); + + int _ignored; + Assert.IsTrue(int.TryParse(penv["SS_LOCAL_PORT"], out _ignored)); + + Assert.AreEqual(penv["SS_PLUGIN_OPTIONS"], pluginOpts); + Assert.AreEqual(pcmd, pluginArgs); + + + plugin.Dispose(); + for (int i = 0; i < 50; i++) + { + if (Process.GetProcessesByName(pluginName).Length == 0) return; + Thread.Sleep(50); + } + } } } diff --git a/test/test.csproj b/test/test.csproj index 654a9afa..f12e71b3 100755 --- a/test/test.csproj +++ b/test/test.csproj @@ -39,6 +39,7 @@ ..\shadowsocks-csharp\3rd\GlobalHotKey.1.1.0\lib\GlobalHotKey.dll + @@ -54,6 +55,7 @@ +