{ Copyright 2018 Stas'M Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. } program RDPWInst; {$APPTYPE CONSOLE} {$R resource.res} uses SysUtils, Windows, Classes, WinSvc, Registry, WinInet, AccCtrl, AclAPI; function EnumServicesStatusEx( hSCManager: SC_HANDLE; InfoLevel, dwServiceType, dwServiceState: DWORD; lpServices: PByte; cbBufSize: DWORD; var pcbBytesNeeded, lpServicesReturned, lpResumeHandle: DWORD; pszGroupName: PWideChar): BOOL; stdcall; external advapi32 name 'EnumServicesStatusExW'; function ConvertStringSidToSid( StringSid: PWideChar; var Sid: PSID): BOOL; stdcall; external advapi32 name 'ConvertStringSidToSidW'; type FILE_VERSION = record Version: record case Boolean of True: (dw: DWORD); False: (w: record Minor, Major: Word; end;) end; Release, Build: Word; bDebug, bPrerelease, bPrivate, bSpecial: Boolean; end; SERVICE_STATUS_PROCESS = packed record dwServiceType, dwCurrentState, dwControlsAccepted, dwWin32ExitCode, dwServiceSpecificExitCode, dwCheckPoint, dwWaitHint, dwProcessId, dwServiceFlags: DWORD; end; PSERVICE_STATUS_PROCESS = ^SERVICE_STATUS_PROCESS; ENUM_SERVICE_STATUS_PROCESS = packed record lpServiceName, lpDisplayName: PWideChar; ServiceStatusProcess: SERVICE_STATUS_PROCESS; end; PENUM_SERVICE_STATUS_PROCESS = ^ENUM_SERVICE_STATUS_PROCESS; const SC_ENUM_PROCESS_INFO = 0; TermService = 'TermService'; var Installed: Boolean; Online: Boolean; WrapPath: String; Arch: Byte; OldWow64RedirectionValue: LongBool; TermServicePath: String; FV: FILE_VERSION; TermServicePID: DWORD; ShareSvc: Array of String; sShareSvc: String; function SupportedArchitecture: Boolean; var SI: TSystemInfo; begin GetNativeSystemInfo(SI); case SI.wProcessorArchitecture of 0: begin Arch := 32; Result := True; // Intel x86 end; 6: Result := False; // Itanium-based x64 9: begin Arch := 64; Result := True; // Intel/AMD x64 end; else Result := False; end; end; function DisableWowRedirection: Boolean; type TFunc = function(var Wow64FsEnableRedirection: LongBool): LongBool; stdcall; var hModule: THandle; Wow64DisableWow64FsRedirection: TFunc; begin Result := False; hModule := GetModuleHandle(kernel32); if hModule <> 0 then Wow64DisableWow64FsRedirection := GetProcAddress(hModule, 'Wow64DisableWow64FsRedirection') else Exit; if @Wow64DisableWow64FsRedirection <> nil then Result := Wow64DisableWow64FsRedirection(OldWow64RedirectionValue); end; function RevertWowRedirection: Boolean; type TFunc = function(var Wow64RevertWow64FsRedirection: LongBool): LongBool; stdcall; var hModule: THandle; Wow64RevertWow64FsRedirection: TFunc; begin Result := False; hModule := GetModuleHandle(kernel32); if hModule <> 0 then Wow64RevertWow64FsRedirection := GetProcAddress(hModule, 'Wow64RevertWow64FsRedirection') else Exit; if @Wow64RevertWow64FsRedirection <> nil then Result := Wow64RevertWow64FsRedirection(OldWow64RedirectionValue); end; procedure CheckInstall; var Code: DWORD; TermServiceHost: String; Reg: TRegistry; begin if Arch = 64 then Reg := TRegistry.Create(KEY_WOW64_64KEY) else Reg := TRegistry.Create; Reg.RootKey := HKEY_LOCAL_MACHINE; if not Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Services\TermService') then begin Reg.Free; Code := GetLastError; Writeln('[-] OpenKeyReadOnly error (code ', Code, ').'); Halt(Code); end; TermServiceHost := Reg.ReadString('ImagePath'); Reg.CloseKey; if (Pos('svchost.exe', LowerCase(TermServiceHost)) = 0) and (Pos('svchost -k', LowerCase(TermServiceHost)) = 0) then begin Reg.Free; Writeln('[-] TermService is hosted in a custom application (BeTwin, etc.) - unsupported.'); Writeln('[*] ImagePath: "', TermServiceHost, '".'); Halt(ERROR_NOT_SUPPORTED); end; if not Reg.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Services\TermService\Parameters') then begin Reg.Free; Code := GetLastError; Writeln('[-] OpenKeyReadOnly error (code ', Code, ').'); Halt(Code); end; TermServicePath := Reg.ReadString('ServiceDll'); Reg.CloseKey; if (Pos('termsrv.dll', LowerCase(TermServicePath)) = 0) and (Pos('rdpwrap.dll', LowerCase(TermServicePath)) = 0) then begin Reg.Free; Writeln('[-] Another third-party TermService library is installed.'); Writeln('[*] ServiceDll: "', TermServicePath, '".'); Halt(ERROR_NOT_SUPPORTED); end; Reg.Free; Installed := Pos('rdpwrap.dll', LowerCase(TermServicePath)) > 0; end; function SvcGetStart(SvcName: String): Integer; var hSC: SC_HANDLE; hSvc: THandle; Code: DWORD; lpServiceConfig: PQueryServiceConfig; Buf: Pointer; cbBufSize, pcbBytesNeeded: Cardinal; begin Result := -1; Writeln('[*] Checking ', SvcName, '...'); hSC := OpenSCManager(nil, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT); if hSC = 0 then begin Code := GetLastError; Writeln('[-] OpenSCManager error (code ', Code, ').'); Exit; end; hSvc := OpenService(hSC, PWideChar(SvcName), SERVICE_QUERY_CONFIG); if hSvc = 0 then begin CloseServiceHandle(hSC); Code := GetLastError; Writeln('[-] OpenService error (code ', Code, ').'); Exit; end; if QueryServiceConfig(hSvc, nil, 0, pcbBytesNeeded) then begin Writeln('[-] QueryServiceConfig failed.'); Exit; end; cbBufSize := pcbBytesNeeded; GetMem(Buf, cbBufSize); if not QueryServiceConfig(hSvc, Buf, cbBufSize, pcbBytesNeeded) then begin FreeMem(Buf, cbBufSize); CloseServiceHandle(hSvc); CloseServiceHandle(hSC); Code := GetLastError; Writeln('[-] QueryServiceConfig error (code ', Code, ').'); Exit; end else begin lpServiceConfig := Buf; Result := Integer(lpServiceConfig^.dwStartType); end; FreeMem(Buf, cbBufSize); CloseServiceHandle(hSvc); CloseServiceHandle(hSC); end; procedure SvcConfigStart(SvcName: String; dwStartType: Cardinal); var hSC: SC_HANDLE; hSvc: THandle; Code: DWORD; begin Writeln('[*] Configuring ', SvcName, '...'); hSC := OpenSCManager(nil, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT); if hSC = 0 then begin Code := GetLastError; Writeln('[-] OpenSCManager error (code ', Code, ').'); Exit; end; hSvc := OpenService(hSC, PWideChar(SvcName), SERVICE_CHANGE_CONFIG); if hSvc = 0 then begin CloseServiceHandle(hSC); Code := GetLastError; Writeln('[-] OpenService error (code ', Code, ').'); Exit; end; if not ChangeServiceConfig(hSvc, SERVICE_NO_CHANGE, dwStartType, SERVICE_NO_CHANGE, nil, nil, nil, nil, nil, nil, nil) then begin CloseServiceHandle(hSvc); CloseServiceHandle(hSC); Code := GetLastError; Writeln('[-] ChangeServiceConfig error (code ', Code, ').'); Exit; end; CloseServiceHandle(hSvc); CloseServiceHandle(hSC); end; procedure SvcStart(SvcName: String); var hSC: SC_HANDLE; hSvc: THandle; Code: DWORD; pch: PWideChar; procedure ExitError(Func: String; ErrorCode: DWORD); begin if hSC > 0 then CloseServiceHandle(hSC); if hSvc > 0 then CloseServiceHandle(hSvc); Writeln('[-] ', Func, ' error (code ', ErrorCode, ').'); end; begin hSC := 0; hSvc := 0; Writeln('[*] Starting ', SvcName, '...'); hSC := OpenSCManager(nil, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT); if hSC = 0 then begin ExitError('OpenSCManager', GetLastError); Exit; end; hSvc := OpenService(hSC, PWideChar(SvcName), SERVICE_START); if hSvc = 0 then begin ExitError('OpenService', GetLastError); Exit; end; pch := nil; if not StartService(hSvc, 0, pch) then begin Code := GetLastError; if Code = 1056 then begin // Service already started Sleep(2000); // or SCM hasn't registered killed process if not StartService(hSvc, 0, pch) then begin ExitError('StartService', Code); Exit; end; end else begin ExitError('StartService', Code); Exit; end; end; CloseServiceHandle(hSvc); CloseServiceHandle(hSC); end; procedure CheckTermsrvProcess; label back; var hSC: SC_HANDLE; dwNeedBytes, dwReturnBytes, dwResumeHandle, Code: DWORD; Svc: Array of ENUM_SERVICE_STATUS_PROCESS; I: Integer; Found, Started: Boolean; TermServiceName: String; begin Started := False; back: hSC := OpenSCManager(nil, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT or SC_MANAGER_ENUMERATE_SERVICE); if hSC = 0 then begin Code := GetLastError; Writeln('[-] OpenSCManager error (code ', Code, ').'); Halt(Code); end; dwResumeHandle := 0; SetLength(Svc, 1489); FillChar(Svc[0], sizeof(Svc[0])*Length(Svc), 0); if not EnumServicesStatusEx(hSC, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL, @Svc[0], sizeof(Svc[0])*Length(Svc), dwNeedBytes, dwReturnBytes, dwResumeHandle, nil) then begin Code := GetLastError; if Code <> ERROR_MORE_DATA then begin CloseServiceHandle(hSC); Writeln('[-] EnumServicesStatusEx error (code ', Code, ').'); Halt(Code); end else begin SetLength(Svc, 5957); FillChar(Svc[0], sizeof(Svc[0])*Length(Svc), 0); if not EnumServicesStatusEx(hSC, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL, @Svc[0], sizeof(Svc[0])*Length(Svc), dwNeedBytes, dwReturnBytes, dwResumeHandle, nil) then begin CloseServiceHandle(hSC); Code := GetLastError; Writeln('[-] EnumServicesStatusEx error (code ', Code, ').'); Halt(Code); end; end; end; CloseServiceHandle(hSC); Found := False; for I := 0 to Length(Svc) - 1 do begin if Svc[I].lpServiceName = nil then Break; if LowerCase(Svc[I].lpServiceName) = LowerCase(TermService) then begin Found := True; TermServiceName := Svc[I].lpServiceName; TermServicePID := Svc[I].ServiceStatusProcess.dwProcessId; Break; end; end; if not Found then begin Writeln('[-] TermService not found.'); Halt(ERROR_SERVICE_DOES_NOT_EXIST); end; if TermServicePID = 0 then begin if Started then begin Writeln('[-] Failed to set up TermService. Unknown error.'); Halt(ERROR_SERVICE_NOT_ACTIVE); end; SvcConfigStart(TermService, SERVICE_AUTO_START); SvcStart(TermService); Started := True; goto back; end else Writeln('[+] TermService found (pid ', TermServicePID, ').'); SetLength(ShareSvc, 0); for I := 0 to Length(Svc) - 1 do begin if Svc[I].lpServiceName = nil then Break; if Svc[I].ServiceStatusProcess.dwProcessId = TermServicePID then if Svc[I].lpServiceName <> TermServiceName then begin SetLength(ShareSvc, Length(ShareSvc)+1); ShareSvc[Length(ShareSvc)-1] := Svc[I].lpServiceName; end; end; sShareSvc := ''; for I := 0 to Length(ShareSvc) - 1 do if sShareSvc = '' then sShareSvc := ShareSvc[I] else sShareSvc := sShareSvc + ', ' + ShareSvc[I]; if sShareSvc <> '' then Writeln('[*] Shared services found: ', sShareSvc) else Writeln('[*] No shared services found.'); end; function AddPrivilege(SePriv: String): Boolean; var hToken: THandle; SeNameValue: Int64; tkp: TOKEN_PRIVILEGES; ReturnLength: Cardinal; ErrorCode: Cardinal; begin Result := False; if not OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken) then begin ErrorCode := GetLastError; Writeln('[-] OpenProcessToken error (code ' + IntToStr(ErrorCode) + ').'); Exit; end; if not LookupPrivilegeValue(nil, PWideChar(SePriv), SeNameValue) then begin ErrorCode := GetLastError; Writeln('[-] LookupPrivilegeValue error (code ' + IntToStr(ErrorCode) + ').'); Exit; end; tkp.PrivilegeCount := 1; tkp.Privileges[0].Luid := SeNameValue; tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; if not AdjustTokenPrivileges(hToken, False, tkp, SizeOf(tkp), tkp, ReturnLength) then begin ErrorCode := GetLastError; Writeln('[-] AdjustTokenPrivileges error (code ' + IntToStr(ErrorCode) + ').'); Exit; end; Result := True; end; procedure KillProcess(PID: DWORD); var hProc: THandle; Code: DWORD; begin hProc := OpenProcess(PROCESS_TERMINATE, False, PID); if hProc = 0 then begin Code := GetLastError; Writeln('[-] OpenProcess error (code ', Code, ').'); Halt(Code); end; if not TerminateProcess(hProc, 0) then begin CloseHandle(hProc); Code := GetLastError; Writeln('[-] TerminateProcess error (code ', Code, ').'); Halt(Code); end; CloseHandle(hProc); end; function ExecWait(Cmdline: String): Boolean; var si: STARTUPINFO; pi: PROCESS_INFORMATION; begin Result := False; ZeroMemory(@si, sizeof(si)); si.cb := sizeof(si); UniqueString(Cmdline); if not CreateProcess(nil, PWideChar(Cmdline), nil, nil, True, 0, nil, nil, si, pi) then begin Writeln('[-] CreateProcess error (code: ', GetLastError, ').'); Exit; end; CloseHandle(pi.hThread); WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess); Result := True; end; function ExpandPath(Path: String): String; var Str: Array[0..511] of Char; begin Result := ''; FillChar(Str, 512, 0); if Arch = 64 then Path := StringReplace(Path, '%ProgramFiles%', '%ProgramW6432%', [rfReplaceAll, rfIgnoreCase]); if ExpandEnvironmentStrings(PWideChar(Path), Str, 512) > 0 then Result := Str; end; procedure SetWrapperDll; var Reg: TRegistry; Code: DWORD; begin if Arch = 64 then Reg := TRegistry.Create(KEY_WRITE or KEY_WOW64_64KEY) else Reg := TRegistry.Create; Reg.RootKey := HKEY_LOCAL_MACHINE; if not Reg.OpenKey('\SYSTEM\CurrentControlSet\Services\TermService\Parameters', True) then begin Code := GetLastError; Writeln('[-] OpenKey error (code ', Code, ').'); Halt(Code); end; try Reg.WriteExpandString('ServiceDll', WrapPath); if (Arch = 64) and (FV.Version.w.Major = 6) and (FV.Version.w.Minor = 0) then ExecWait('"'+ExpandPath('%SystemRoot%')+'\system32\reg.exe" add HKLM\SYSTEM\CurrentControlSet\Services\TermService\Parameters /v ServiceDll /t REG_EXPAND_SZ /d "'+WrapPath+'" /f'); except Writeln('[-] WriteExpandString error.'); Halt(ERROR_ACCESS_DENIED); end; Reg.CloseKey; Reg.Free; end; procedure ResetServiceDll; var Reg: TRegistry; Code: DWORD; begin if Arch = 64 then Reg := TRegistry.Create(KEY_WRITE or KEY_WOW64_64KEY) else Reg := TRegistry.Create; Reg.RootKey := HKEY_LOCAL_MACHINE; if not Reg.OpenKey('\SYSTEM\CurrentControlSet\Services\TermService\Parameters', True) then begin Code := GetLastError; Writeln('[-] OpenKey error (code ', Code, ').'); Halt(Code); end; try Reg.WriteExpandString('ServiceDll', '%SystemRoot%\System32\termsrv.dll'); except Writeln('[-] WriteExpandString error.'); Halt(ERROR_ACCESS_DENIED); end; Reg.CloseKey; Reg.Free; end; procedure ExtractRes(ResName, Path: String); var ResStream: TResourceStream; begin ResStream := TResourceStream.Create(HInstance, ResName, RT_RCDATA); try ResStream.SaveToFile(Path); except Writeln('[-] Failed to extract file.'); Writeln('[*] Resource name: ' + ResName); Writeln('[*] Destination path: ' + Path); ResStream.Free; Exit; end; Writeln('[+] Extracted ', ResName, ' -> ', Path); ResStream.Free; end; function ExtractResText(ResName: String): String; var ResStream: TResourceStream; Str: TStringList; begin ResStream := TResourceStream.Create(HInstance, ResName, RT_RCDATA); Str := TStringList.Create; try Str.LoadFromStream(ResStream); except end; ResStream.Free; Result := Str.Text; Str.Free; end; function GitINIFile(var Content: String): Boolean; const URL = 'https://raw.githubusercontent.com/stascorp/rdpwrap/master/res/rdpwrap.ini'; var NetHandle: HINTERNET; UrlHandle: HINTERNET; Str: String; Buf: Array[0..1023] of Byte; BytesRead: DWORD; begin Result := False; Content := ''; NetHandle := InternetOpen('RDP Wrapper Update', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0); if not Assigned(NetHandle) then Exit; UrlHandle := InternetOpenUrl(NetHandle, PChar(URL), nil, 0, INTERNET_FLAG_RELOAD, 0); if not Assigned(UrlHandle) then begin InternetCloseHandle(NetHandle); Exit; end; repeat InternetReadFile(UrlHandle, @Buf[0], SizeOf(Buf), BytesRead); SetString(Str, PAnsiChar(@Buf[0]), BytesRead); Content := Content + Str; until BytesRead = 0; InternetCloseHandle(UrlHandle); InternetCloseHandle(NetHandle); Result := True; end; procedure GrantSidFullAccess(Path, SID: String); var p_SID: PSID; pDACL: PACL; EA: EXPLICIT_ACCESS; Code, Result: DWORD; begin p_SID := nil; if not ConvertStringSidToSid(PChar(SID), p_SID) then begin Code := GetLastError; Writeln('[-] ConvertStringSidToSid error (code ', Code, ').'); Exit; end; EA.grfAccessPermissions := GENERIC_ALL; EA.grfAccessMode := GRANT_ACCESS; EA.grfInheritance := SUB_CONTAINERS_AND_OBJECTS_INHERIT; EA.Trustee.pMultipleTrustee := nil; EA.Trustee.MultipleTrusteeOperation := NO_MULTIPLE_TRUSTEE; EA.Trustee.TrusteeForm := TRUSTEE_IS_SID; EA.Trustee.TrusteeType := TRUSTEE_IS_WELL_KNOWN_GROUP; EA.Trustee.ptstrName := p_SID; Result := SetEntriesInAcl(1, @EA, nil, pDACL); if Result = ERROR_SUCCESS then begin if SetNamedSecurityInfo(pchar(Path), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nil, nil, pDACL, nil) <> ERROR_SUCCESS then begin Code := GetLastError; Writeln('[-] SetNamedSecurityInfo error (code ', Code, ').'); end; LocalFree(Cardinal(pDACL)); end else begin Code := GetLastError; Writeln('[-] SetEntriesInAcl error (code ', Code, ').'); end; end; procedure ExtractFiles; var RDPClipRes, RfxvmtRes, S: String; OnlineINI: TStringList; begin if not DirectoryExists(ExtractFilePath(ExpandPath(WrapPath))) then if ForceDirectories(ExtractFilePath(ExpandPath(WrapPath))) then begin S := ExtractFilePath(ExpandPath(WrapPath)); Writeln('[+] Folder created: ', S); GrantSidFullAccess(S, 'S-1-5-18'); // Local System account GrantSidFullAccess(S, 'S-1-5-6'); // Service group end else begin Writeln('[-] ForceDirectories error.'); Writeln('[*] Path: ', ExtractFilePath(ExpandPath(WrapPath))); Halt(0); end; if Online then begin Writeln('[*] Downloading latest INI file...'); OnlineINI := TStringList.Create; if GitINIFile(S) then begin OnlineINI.Text := S; S := ExtractFilePath(ExpandPath(WrapPath)) + 'rdpwrap.ini'; OnlineINI.SaveToFile(S); Writeln('[+] Latest INI file -> ', S); end else begin Writeln('[-] Failed to get online INI file, using built-in.'); Online := False; end; OnlineINI.Free; end; if not Online then begin S := ExtractFilePath(ParamStr(0)) + 'rdpwrap.ini'; if FileExists(S) then begin OnlineINI := TStringList.Create; OnlineINI.LoadFromFile(S); S := ExtractFilePath(ExpandPath(WrapPath)) + 'rdpwrap.ini'; OnlineINI.SaveToFile(S); Writeln('[+] Current INI file -> ', S); OnlineINI.Free; end else ExtractRes('config', ExtractFilePath(ExpandPath(WrapPath)) + 'rdpwrap.ini'); end; RDPClipRes := ''; RfxvmtRes := ''; case Arch of 32: begin ExtractRes('rdpw32', ExpandPath(WrapPath)); if (FV.Version.w.Major = 6) and (FV.Version.w.Minor = 0) then RDPClipRes := 'rdpclip6032'; if (FV.Version.w.Major = 6) and (FV.Version.w.Minor = 1) then RDPClipRes := 'rdpclip6132'; if (FV.Version.w.Major = 10) and (FV.Version.w.Minor = 0) then RfxvmtRes := 'rfxvmt32'; end; 64: begin ExtractRes('rdpw64', ExpandPath(WrapPath)); if (FV.Version.w.Major = 6) and (FV.Version.w.Minor = 0) then RDPClipRes := 'rdpclip6064'; if (FV.Version.w.Major = 6) and (FV.Version.w.Minor = 1) then RDPClipRes := 'rdpclip6164'; if (FV.Version.w.Major = 10) and (FV.Version.w.Minor = 0) then RfxvmtRes := 'rfxvmt64'; end; end; if RDPClipRes <> '' then if not FileExists(ExpandPath('%SystemRoot%\System32\rdpclip.exe')) then ExtractRes(RDPClipRes, ExpandPath('%SystemRoot%\System32\rdpclip.exe')); if RfxvmtRes <> '' then if not FileExists(ExpandPath('%SystemRoot%\System32\rfxvmt.dll')) then ExtractRes(RfxvmtRes, ExpandPath('%SystemRoot%\System32\rfxvmt.dll')); end; procedure DeleteFiles; var Code: DWORD; FullPath, Path: String; begin FullPath := ExpandPath(TermServicePath); Path := ExtractFilePath(FullPath); if not DeleteFile(PWideChar(Path + 'rdpwrap.ini')) then begin Code := GetLastError; Writeln('[-] DeleteFile error (code ', Code, ').'); Exit; end; Writeln('[+] Removed file: ', Path + 'rdpwrap.ini'); if not DeleteFile(PWideChar(FullPath)) then begin Code := GetLastError; Writeln('[-] DeleteFile error (code ', Code, ').'); Exit; end; Writeln('[+] Removed file: ', FullPath); if not RemoveDirectory(PWideChar(ExtractFilePath(ExpandPath(TermServicePath)))) then begin Code := GetLastError; Writeln('[-] RemoveDirectory error (code ', Code, ').'); Exit; end; Writeln('[+] Removed folder: ', ExtractFilePath(ExpandPath(TermServicePath))); end; function GetFileVersion(const FileName: TFileName; var FileVersion: FILE_VERSION): Boolean; type VS_VERSIONINFO = record wLength, wValueLength, wType: Word; szKey: Array[1..16] of WideChar; Padding1: Word; Value: VS_FIXEDFILEINFO; Padding2, Children: Word; end; PVS_VERSIONINFO = ^VS_VERSIONINFO; const VFF_DEBUG = 1; VFF_PRERELEASE = 2; VFF_PRIVATE = 8; VFF_SPECIAL = 32; var hFile: HMODULE; hResourceInfo: HRSRC; VersionInfo: PVS_VERSIONINFO; begin Result := False; hFile := LoadLibraryEx(PWideChar(FileName), 0, LOAD_LIBRARY_AS_DATAFILE); if hFile = 0 then Exit; hResourceInfo := FindResource(hFile, PWideChar(1), PWideChar($10)); if hResourceInfo = 0 then Exit; VersionInfo := Pointer(LoadResource(hFile, hResourceInfo)); if VersionInfo = nil then Exit; FileVersion.Version.dw := VersionInfo.Value.dwFileVersionMS; FileVersion.Release := Word(VersionInfo.Value.dwFileVersionLS shr 16); FileVersion.Build := Word(VersionInfo.Value.dwFileVersionLS); FileVersion.bDebug := (VersionInfo.Value.dwFileFlags and VFF_DEBUG) = VFF_DEBUG; FileVersion.bPrerelease := (VersionInfo.Value.dwFileFlags and VFF_PRERELEASE) = VFF_PRERELEASE; FileVersion.bPrivate := (VersionInfo.Value.dwFileFlags and VFF_PRIVATE) = VFF_PRIVATE; FileVersion.bSpecial := (VersionInfo.Value.dwFileFlags and VFF_SPECIAL) = VFF_SPECIAL; FreeLibrary(hFile); Result := True; end; procedure CheckTermsrvVersion; var SuppLvl: Byte; VerTxt: String; procedure UpdateMsg; begin Writeln('Try running "update.bat" or "RDPWInst -w" to download latest INI file.'); Writeln('If it doesn''t help, send your termsrv.dll to project developer for support.'); end; begin GetFileVersion(ExpandPath(TermServicePath), FV); VerTxt := Format('%d.%d.%d.%d', [FV.Version.w.Major, FV.Version.w.Minor, FV.Release, FV.Build]); Writeln('[*] Terminal Services version: ', VerTxt); if (FV.Version.w.Major = 5) and (FV.Version.w.Minor = 1) then begin if Arch = 32 then begin Writeln('[!] Windows XP is not supported.'); Writeln('You may take a look at RDP Realtime Patch by Stas''M for Windows XP'); Writeln('Link: http://stascorp.com/load/1-1-0-62'); end; if Arch = 64 then Writeln('[!] Windows XP 64-bit Edition is not supported.'); Exit; end; if (FV.Version.w.Major = 5) and (FV.Version.w.Minor = 2) then begin if Arch = 32 then Writeln('[!] Windows Server 2003 is not supported.'); if Arch = 64 then Writeln('[!] Windows Server 2003 or XP 64-bit Edition is not supported.'); Exit; end; SuppLvl := 0; if (FV.Version.w.Major = 6) and (FV.Version.w.Minor = 0) then begin SuppLvl := 1; if (Arch = 32) and (FV.Release = 6000) and (FV.Build = 16386) then begin Writeln('[!] This version of Terminal Services may crash on logon attempt.'); Writeln('It''s recommended to upgrade to Service Pack 1 or higher.'); end; end; if (FV.Version.w.Major = 6) and (FV.Version.w.Minor = 1) then SuppLvl := 1; if Pos('[' + VerTxt + ']', ExtractResText('config')) > 0 then SuppLvl := 2; case SuppLvl of 0: begin Writeln('[-] This version of Terminal Services is not supported.'); UpdateMsg; end; 1: begin Writeln('[!] This version of Terminal Services is supported partially.'); Writeln('It means you may have some limitations such as only 2 concurrent sessions.'); UpdateMsg; end; 2: begin Writeln('[+] This version of Terminal Services is fully supported.'); end; end; end; procedure CheckTermsrvDependencies; const CertPropSvc = 'CertPropSvc'; SessionEnv = 'SessionEnv'; begin if SvcGetStart(CertPropSvc) = SERVICE_DISABLED then SvcConfigStart(CertPropSvc, SERVICE_DEMAND_START); if SvcGetStart(SessionEnv) = SERVICE_DISABLED then SvcConfigStart(SessionEnv, SERVICE_DEMAND_START); end; procedure TSConfigRegistry(Enable: Boolean); var Reg: TRegistry; Code: DWORD; begin if Arch = 64 then Reg := TRegistry.Create(KEY_WRITE or KEY_WOW64_64KEY) else Reg := TRegistry.Create; Reg.RootKey := HKEY_LOCAL_MACHINE; if not Reg.OpenKey('\SYSTEM\CurrentControlSet\Control\Terminal Server', True) then begin Code := GetLastError; Writeln('[-] OpenKey error (code ', Code, ').'); Halt(Code); end; try Reg.WriteBool('fDenyTSConnections', not Enable); except Writeln('[-] WriteBool error.'); Halt(ERROR_ACCESS_DENIED); end; Reg.CloseKey; if Enable then begin if not Reg.OpenKey('\SYSTEM\CurrentControlSet\Control\Terminal Server\Licensing Core', True) then begin Code := GetLastError; Writeln('[-] OpenKey error (code ', Code, ').'); Halt(Code); end; try Reg.WriteBool('EnableConcurrentSessions', True); except Writeln('[-] WriteBool error.'); Halt(ERROR_ACCESS_DENIED); end; Reg.CloseKey; if not Reg.OpenKey('\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon', True) then begin Code := GetLastError; Writeln('[-] OpenKey error (code ', Code, ').'); Halt(Code); end; try Reg.WriteBool('AllowMultipleTSSessions', True); except Writeln('[-] WriteBool error.'); Halt(ERROR_ACCESS_DENIED); end; Reg.CloseKey; if not Reg.KeyExists('\SYSTEM\CurrentControlSet\Control\Terminal Server\AddIns') then begin if not Reg.OpenKey('\SYSTEM\CurrentControlSet\Control\Terminal Server\AddIns', True) then begin Code := GetLastError; Writeln('[-] OpenKey error (code ', Code, ').'); Halt(Code); end; Reg.CloseKey; if not Reg.OpenKey('\SYSTEM\CurrentControlSet\Control\Terminal Server\AddIns\Clip Redirector', True) then begin Code := GetLastError; Writeln('[-] OpenKey error (code ', Code, ').'); Halt(Code); end; try Reg.WriteString('Name', 'RDPClip'); Reg.WriteInteger('Type', 3); except Writeln('[-] WriteInteger error.'); Halt(ERROR_ACCESS_DENIED); end; Reg.CloseKey; if not Reg.OpenKey('\SYSTEM\CurrentControlSet\Control\Terminal Server\AddIns\DND Redirector', True) then begin Code := GetLastError; Writeln('[-] OpenKey error (code ', Code, ').'); Halt(Code); end; try Reg.WriteString('Name', 'RDPDND'); Reg.WriteInteger('Type', 3); except Writeln('[-] WriteInteger error.'); Halt(ERROR_ACCESS_DENIED); end; Reg.CloseKey; if not Reg.OpenKey('\SYSTEM\CurrentControlSet\Control\Terminal Server\AddIns\Dynamic VC', True) then begin Code := GetLastError; Writeln('[-] OpenKey error (code ', Code, ').'); Halt(Code); end; try Reg.WriteInteger('Type', -1); except Writeln('[-] WriteInteger error.'); Halt(ERROR_ACCESS_DENIED); end; Reg.CloseKey; end; end; Reg.Free; end; procedure TSConfigFirewall(Enable: Boolean); begin if Enable then begin ExecWait('netsh advfirewall firewall add rule name="Remote Desktop" dir=in protocol=tcp localport=3389 profile=any action=allow'); ExecWait('netsh advfirewall firewall add rule name="Remote Desktop" dir=in protocol=udp localport=3389 profile=any action=allow'); end else ExecWait('netsh advfirewall firewall delete rule name="Remote Desktop"'); end; function CheckINIDate(Filename, Content: String; var Date: Integer): Boolean; var Str: TStringList; I: Integer; begin Result := False; Str := TStringList.Create; if Filename <> '' then begin try Str.LoadFromFile(Filename); except Writeln('[-] Failed to read INI file.'); Exit; end; end else Str.Text := Content; for I := 0 to Str.Count - 1 do if Pos('Updated=', Str[I]) = 1 then Break; if I >= Str.Count then begin Writeln('[-] Failed to check INI date.'); Exit; end; Content := StringReplace(Str[I], 'Updated=', '', []); Content := StringReplace(Content, '-', '', [rfReplaceAll]); Str.Free; try Date := StrToInt(Content); except Writeln('[-] Wrong INI date format.'); Exit; end; Result := True; end; procedure CheckUpdate; var INIPath, S: String; Str: TStringList; I, OldDate, NewDate: Integer; begin INIPath := ExtractFilePath(ExpandPath(TermServicePath)) + 'rdpwrap.ini'; if not CheckINIDate(INIPath, '', OldDate) then Halt(ERROR_ACCESS_DENIED); Writeln('[*] Current update date: ', Format('%d.%.2d.%.2d', [OldDate div 10000, OldDate div 100 mod 100, OldDate mod 100])); if not GitINIFile(S) then begin Writeln('[-] Failed to download latest INI from GitHub.'); Halt(ERROR_ACCESS_DENIED); end; if not CheckINIDate('', S, NewDate) then Halt(ERROR_ACCESS_DENIED); Writeln('[*] Latest update date: ', Format('%d.%.2d.%.2d', [NewDate div 10000, NewDate div 100 mod 100, NewDate mod 100])); if NewDate = OldDate then Writeln('[*] Everything is up to date.') else if NewDate > OldDate then begin Writeln('[+] New update is available, updating...'); CheckTermsrvProcess; Writeln('[*] Terminating service...'); AddPrivilege('SeDebugPrivilege'); KillProcess(TermServicePID); Sleep(1000); if Length(ShareSvc) > 0 then for I := 0 to Length(ShareSvc) - 1 do SvcStart(ShareSvc[I]); Sleep(500); Str := TStringList.Create; Str.Text := S; try Str.SaveToFile(INIPath); except Writeln('[-] Failed to write INI file.'); Halt(ERROR_ACCESS_DENIED); end; Str.Free; SvcStart(TermService); Writeln('[+] Update completed.'); end else Writeln('[*] Your INI file is newer than public file. Are you a developer? :)'); end; var I: Integer; begin Writeln('RDP Wrapper Library v1.6.2'); Writeln('Installer v2.6'); Writeln('Copyright (C) Stas''M Corp. 2018'); Writeln(''); if (ParamCount < 1) or ( (ParamStr(1) <> '-l') and (ParamStr(1) <> '-i') and (ParamStr(1) <> '-w') and (ParamStr(1) <> '-u') and (ParamStr(1) <> '-r') ) then begin Writeln('USAGE:'); Writeln('RDPWInst.exe [-l|-i[-s][-o]|-w|-u[-k]|-r]'); Writeln(''); Writeln('-l display the license agreement'); Writeln('-i install wrapper to Program Files folder (default)'); Writeln('-i -s install wrapper to System32 folder'); Writeln('-i -o online install mode (loads latest INI file)'); Writeln('-w get latest update for INI file'); Writeln('-u uninstall wrapper'); Writeln('-u -k uninstall wrapper and keep settings'); Writeln('-r force restart Terminal Services'); Exit; end; if ParamStr(1) = '-l' then begin Writeln(ExtractResText('license')); Exit; end; if not CheckWin32Version(6,0) then begin Writeln('[-] Unsupported Windows version:'); Writeln(' only >= 6.0 (Vista, Server 2008 and newer) are supported.'); Exit; end; if not SupportedArchitecture then begin Writeln('[-] Unsupported processor architecture.'); Exit; end; CheckInstall; if ParamStr(1) = '-i' then begin if Installed then begin Writeln('[*] RDP Wrapper Library is already installed.'); Halt(ERROR_INVALID_FUNCTION); end; Writeln('[*] Notice to user:'); Writeln(' - By using all or any portion of this software, you are agreeing'); Writeln(' to be bound by all the terms and conditions of the license agreement.'); Writeln(' - To read the license agreement, run the installer with -l parameter.'); Writeln(' - If you do not agree to any terms of the license agreement,'); Writeln(' do not use the software.'); Writeln('[*] Installing...'); if ParamStr(2) = '-s' then WrapPath := '%SystemRoot%\system32\rdpwrap.dll' else WrapPath := '%ProgramFiles%\RDP Wrapper\rdpwrap.dll'; if Arch = 64 then DisableWowRedirection; CheckTermsrvVersion; CheckTermsrvProcess; Writeln('[*] Extracting files...'); Online := (ParamStr(2) = '-o') or (ParamStr(3) = '-o'); ExtractFiles; Writeln('[*] Configuring service library...'); SetWrapperDll; Writeln('[*] Checking dependencies...'); CheckTermsrvDependencies; Writeln('[*] Terminating service...'); AddPrivilege('SeDebugPrivilege'); KillProcess(TermServicePID); Sleep(1000); if Length(ShareSvc) > 0 then for I := 0 to Length(ShareSvc) - 1 do SvcStart(ShareSvc[I]); Sleep(500); SvcStart(TermService); Sleep(500); Writeln('[*] Configuring registry...'); TSConfigRegistry(True); Writeln('[*] Configuring firewall...'); TSConfigFirewall(True); Writeln('[+] Successfully installed.'); if Arch = 64 then RevertWowRedirection; end; if ParamStr(1) = '-u' then begin if not Installed then begin Writeln('[*] RDP Wrapper Library is not installed.'); Halt(ERROR_INVALID_FUNCTION); end; Writeln('[*] Uninstalling...'); if Arch = 64 then DisableWowRedirection; CheckTermsrvProcess; Writeln('[*] Resetting service library...'); ResetServiceDll; Writeln('[*] Terminating service...'); AddPrivilege('SeDebugPrivilege'); KillProcess(TermServicePID); Sleep(1000); Writeln('[*] Removing files...'); DeleteFiles; if Length(ShareSvc) > 0 then for I := 0 to Length(ShareSvc) - 1 do SvcStart(ShareSvc[I]); Sleep(500); SvcStart(TermService); Sleep(500); if ParamStr(2) <> '-k' then begin Writeln('[*] Configuring registry...'); TSConfigRegistry(False); Writeln('[*] Configuring firewall...'); TSConfigFirewall(False); end; if Arch = 64 then RevertWowRedirection; Writeln('[+] Successfully uninstalled.'); end; if ParamStr(1) = '-w' then begin if not Installed then begin Writeln('[*] RDP Wrapper Library is not installed.'); Halt(ERROR_INVALID_FUNCTION); end; Writeln('[*] Checking for updates...'); CheckUpdate; end; if ParamStr(1) = '-r' then begin Writeln('[*] Restarting...'); CheckTermsrvProcess; Writeln('[*] Terminating service...'); AddPrivilege('SeDebugPrivilege'); KillProcess(TermServicePID); Sleep(1000); if Length(ShareSvc) > 0 then for I := 0 to Length(ShareSvc) - 1 do SvcStart(ShareSvc[I]); Sleep(500); SvcStart(TermService); Writeln('[+] Done.'); end; end.