/* * Copyright (c) 2020-2023, David Xanatos * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "stdafx.h" #include #include #ifdef _DEBUG #include #endif #include "SbieAPI.h" #include #define WIN32_NO_STATUS typedef long NTSTATUS; #include #include "SbieDefs.h" #include "..\..\Sandboxie\common\win32_ntddk.h" #include "..\..\Sandboxie\common\defines.h" #include "..\..\Sandboxie\core\drv\api_defs.h" #include "..\..\Sandboxie\core\drv\api_flags.h" #include "..\..\Sandboxie\core\svc\msgids.h" #include "..\..\Sandboxie\core\svc\ProcessWire.h" #include "..\..\Sandboxie\core\svc\GuiWire.h" #include "..\..\Sandboxie\core\svc\sbieiniwire.h" #include "..\..\Sandboxie\core\svc\QueueWire.h" #include "..\..\Sandboxie\core\svc\InteractiveWire.h" #include "..\..\Sandboxie\core\svc\MountManagerWire.h" int _SB_STATUS_type = qRegisterMetaType("SB_STATUS"); struct SSbieAPI { SSbieAPI() { SbieApiHandle = INVALID_HANDLE_VALUE; PortHandle = NULL; MaxDataLen = 0; SizeofPortMsg = 0; CallSeqNumber = 0; if (!ProcessIdToSessionId(GetCurrentProcessId(), &sessionId)) sessionId = 0; wsprintf(QueueName, L"*%s_%08X", INTERACTIVE_QUEUE_NAME, sessionId); lastMessageNum = 0; //lastRecordNum = 0; traceBuffer = NULL; traceBufferLen = 0; SbieMsgDll = NULL; SvcLock = 0; } ~SSbieAPI() { if (traceBuffer) free(traceBuffer); } NTSTATUS IoControl(ULONG64 *parms) { IO_STATUS_BLOCK IoStatusBlock; return NtDeviceIoControlFile(SbieApiHandle, NULL, NULL, NULL, &IoStatusBlock, API_SBIEDRV_CTLCODE, parms, sizeof(ULONG64) * API_NUM_ARGS, NULL, 0); } HANDLE SbieApiHandle; HANDLE PortHandle; ULONG MaxDataLen; ULONG SizeofPortMsg; ULONG CallSeqNumber; WCHAR QueueName[64]; QString Password; ULONG sessionId; bool clearingBuffers; ULONG lastMessageNum; //ULONG lastRecordNum; UCHAR* traceBuffer; ULONG traceBufferLen; HMODULE SbieMsgDll; mutable volatile LONG SvcLock; mutable MSG_HEADER* SvcReq; mutable CSbieAPI::SScopedVoid* SvcRpl; mutable SB_STATUS SvcStatus; }; #define SVC_OP_STATE_IDLE 0 #define SVC_OP_STATE_PREP 1 #define SVC_OP_STATE_START 2 #define SVC_OP_STATE_EXEC 3 #define SVC_OP_STATE_DONE 4 #define SVC_OP_STATE_EVAL 5 quint64 FILETIME2ms(quint64 fileTime) { if (fileTime < 116444736000000000ULL) return 0; return (fileTime - 116444736000000000ULL) / 10000ULL; } time_t FILETIME2time(quint64 fileTime) { return FILETIME2ms(fileTime) / 1000ULL; } CSbieAPI::CSbieAPI(QObject* parent) : QThread(parent) { m = new SSbieAPI(); m_pGlobalSection = QSharedPointer(new CSbieIni("GlobalSettings", this)); m_pUserSection = QSharedPointer(new CSbieIni("UserSettings", this)); // dummy m_IniReLoad = false; m_bReloadPending = false; m_bBoxesDirty = false; connect(&m_IniWatcher, SIGNAL(fileChanged(const QString&)), this, SLOT(OnIniChanged(const QString&))); connect(this, SIGNAL(ProcessBoxed(quint32, const QString&, const QString&, quint32, const QString&)), this, SLOT(OnProcessBoxed(quint32, const QString&, const QString&, quint32, const QString&))); } CSbieAPI::~CSbieAPI() { Disconnect(); delete m; } bool CSbieAPI::IsSbieCtrlRunning() { static const WCHAR *SbieCtrlMutexName = SANDBOXIE L"_SingleInstanceMutex_Control"; HANDLE hSbieCtrlMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, SbieCtrlMutexName); if (hSbieCtrlMutex) { CloseHandle(hSbieCtrlMutex); return true; } return false; } bool CSbieAPI::TerminateSbieCtrl() { static const WCHAR *WindowClassName = L"SandboxieControlWndClass"; HWND hwnd = FindWindow(WindowClassName, NULL); if (hwnd) { PostMessage(hwnd, WM_QUIT, 0, 0); } for (int i = 0; i < 10 && hwnd != NULL; i++) { QThread::msleep(100); hwnd = FindWindow(WindowClassName, NULL); } return hwnd == NULL; } CSandBox* CSbieAPI::NewSandBox(const QString& BoxName, class CSbieAPI* pAPI) { return new CSandBox(BoxName, pAPI); } CBoxedProcess* CSbieAPI::NewBoxedProcess(quint32 ProcessId, class CSandBox* pBox) { return new CBoxedProcess(ProcessId, pBox); } QString CSbieAPI__GetRegValue(HANDLE hKey, const WCHAR* pName) { char buf[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + MAX_PATH]; KEY_VALUE_PARTIAL_INFORMATION *value = (KEY_VALUE_PARTIAL_INFORMATION *)buf; ULONG len; UNICODE_STRING uni; RtlInitUnicodeString(&uni, pName); if (NT_SUCCESS(NtQueryValueKey(hKey, &uni, KeyValuePartialInformation, value, sizeof(buf) - 16, &len))) { WCHAR expand[MAX_PATH + 8]; len = ExpandEnvironmentStrings((WCHAR*)value->Data, expand, MAX_PATH + 4); return QString::fromWCharArray(expand); } return QString(); } bool CSbieAPI__GetCurrentSidString(UNICODE_STRING *SidString) { HANDLE token; NTSTATUS status = NtOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &token); if (NT_SUCCESS(status)) { ULONG len; union { TOKEN_USER userToken; WCHAR info_space[64]; } data; status = NtQueryInformationToken(token, TokenUser, &data, sizeof(data), &len); if (NT_SUCCESS(status)) status = RtlConvertSidToUnicodeString(SidString, data.userToken.User.Sid, TRUE); NtClose(token); } return NT_SUCCESS(status); } void CSbieAPI::GetUserPaths() { const WCHAR* ProfileList = L"\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"; UNICODE_STRING objname; RtlInitUnicodeString(&objname, ProfileList); OBJECT_ATTRIBUTES objattrs; InitializeObjectAttributes(&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL); HANDLE hProfileKey; if (NT_SUCCESS(NtOpenKey(&hProfileKey, KEY_READ, &objattrs))) { //m_ProfilesDir = CSbieAPI__GetRegValue(hProfileKey, L"ProfilesDirectory"); m_ProgramDataDir = CSbieAPI__GetRegValue(hProfileKey, L"ProgramData"); //if(ProgramDataDir.isEmpty()) // for Win XP // ProgramDataDir = CSbieAPI__GetRegValue(hProfileKey, L"AllUsersProfile"); m_PublicDir = CSbieAPI__GetRegValue(hProfileKey, L"Public"); if (CSbieAPI__GetCurrentSidString(&objname)) { m_UserSid = QString::fromWCharArray(objname.Buffer); InitializeObjectAttributes(&objattrs, &objname, OBJ_CASE_INSENSITIVE, hProfileKey, NULL); HANDLE hSidKey; if (NT_SUCCESS(NtOpenKey(&hSidKey, KEY_READ, &objattrs))) { m_UserDir = CSbieAPI__GetRegValue(hSidKey, L"ProfileImagePath"); NtClose(hSidKey); } } NtClose(hProfileKey); } } SB_STATUS CSbieAPI::Connect(bool takeOver, bool withQueue) { if (IsConnected()) return SB_OK; UNICODE_STRING uni; RtlInitUnicodeString(&uni, API_DEVICE_NAME); OBJECT_ATTRIBUTES objattrs; InitializeObjectAttributes(&objattrs, &uni, OBJ_CASE_INSENSITIVE, NULL, NULL); IO_STATUS_BLOCK IoStatusBlock; NTSTATUS status = NtOpenFile(&m->SbieApiHandle, FILE_GENERIC_READ, &objattrs, &IoStatusBlock, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0); if (status == STATUS_OBJECT_NAME_NOT_FOUND || status == STATUS_NO_SUCH_DEVICE) status = STATUS_SERVER_DISABLED; if (status != STATUS_SUCCESS) { m->SbieApiHandle = INVALID_HANDLE_VALUE; return SB_ERR(SB_DriverFail, status); } UpdateDriveLetters(); m_SbiePath = GetSbieHome(); m->SbieMsgDll = LoadLibraryEx((m_SbiePath.toStdWString() + L"\\" SBIEMSG_DLL).c_str(), NULL, LOAD_LIBRARY_AS_DATAFILE); m->lastMessageNum = 0; //m->lastRecordNum = 0; // Note: this lib is not using all functions, hence it can be compatible with multiple driver ABI revisions //QStringList CompatVersions = QStringList () << "5.55.0"; //QString CurVersion = GetVersion(); //if (!CompatVersions.contains(CurVersion)) //{ // NtClose(m->SbieApiHandle); // m->SbieApiHandle = INVALID_HANDLE_VALUE; // return SB_ERR(SB_Incompatible, QVariantList() << CurVersion << CompatVersions.join(", ")); //} SB_STATUS Status = SB_OK; if (takeOver) { Status = TakeOver(); if (!Status) // only the session leader manages the interactive queue withQueue = false; } m_bWithQueue = withQueue; m_bTerminate = false; m->clearingBuffers = true; start(); bool bHome = false; m_IniPath = GetIniPath(&bHome); qDebug() << "Config file:" << m_IniPath << (bHome ? "(home)" : "(system)"); if (m_UserName.isEmpty()) { QString UserSection = GetUserSection(&m_UserName); if(!UserSection.isEmpty()) m_pUserSection = QSharedPointer(new CSbieIni(UserSection, this)); } if (m_UserDir.isEmpty()) { GetUserPaths(); } emit StatusChanged(); return Status; } SB_STATUS CSbieAPI::Disconnect() { if (!IsConnected()) return SB_OK; m_bTerminate = true; if (!wait(10 * 1000)) terminate(); if (m->SbieApiHandle != INVALID_HANDLE_VALUE) { NtClose(m->SbieApiHandle); m->SbieApiHandle = INVALID_HANDLE_VALUE; } // ensure that the client thread is entirely terminated to avoid the service keeping the reference // see SbieSvc PortDisconnectByCreateTime QThread::msleep(250); if (m->PortHandle) { NtClose(m->PortHandle); m->PortHandle = NULL; } if (m->SbieMsgDll) { FreeLibrary(m->SbieMsgDll); m->SbieMsgDll = NULL; } m_SandBoxes.clear(); m_BoxedProxesses.clear(); m_bBoxesDirty = true; emit StatusChanged(); return SB_OK; } bool CSbieAPI::IsConnected() const { return m->SbieApiHandle != INVALID_HANDLE_VALUE; } bool CSbieAPI::IsWow64() { static bool IsWow64 = false; #ifndef _WIN64 static bool init = false; if (!init) { ULONG_PTR wow64; if (NT_SUCCESS(NtQueryInformationProcess(NtCurrentProcess(), ProcessWow64Information, &wow64, sizeof(ULONG_PTR), NULL))) { IsWow64 = !!wow64; } init = true; } #endif return IsWow64; } SB_STATUS CSbieAPI__ConnectPort(SSbieAPI* m) { if (m->PortHandle) return SB_OK; SECURITY_QUALITY_OF_SERVICE QoS; QoS.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); QoS.ImpersonationLevel = SecurityImpersonation; QoS.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; QoS.EffectiveOnly = TRUE; UNICODE_STRING PortName; RtlInitUnicodeString(&PortName, SBIESVC_PORT); NTSTATUS status = NtConnectPort(&m->PortHandle, &PortName, &QoS, NULL, NULL, &m->MaxDataLen, NULL, NULL); if (!NT_SUCCESS(status)) return SB_ERR(status); // 2203 // Function associate PortHandle with thread, and sends LPC_TERMINATION_MESSAGE to specified port immediately after call NtTerminateThread. //NtRegisterThreadTerminatePort(m->PortHandle); m->SizeofPortMsg = sizeof(PORT_MESSAGE); if (CSbieAPI::IsWow64()) m->SizeofPortMsg += sizeof(ULONG) * 4; m->MaxDataLen -= m->SizeofPortMsg; return SB_OK; } SB_STATUS CSbieAPI__CallServer(SSbieAPI* m, MSG_HEADER* req, CSbieAPI::SScopedVoid* prpl) { if (!m->PortHandle) { SB_STATUS Status = CSbieAPI__ConnectPort(m); if (!Status) return Status; } UCHAR RequestBuff[MAX_PORTMSG_LENGTH]; PORT_MESSAGE* ReqHeader = (PORT_MESSAGE*)RequestBuff; UCHAR* ReqData = RequestBuff + m->SizeofPortMsg; UCHAR ResponseBuff[MAX_PORTMSG_LENGTH]; PORT_MESSAGE* ResHeader = (PORT_MESSAGE*)ResponseBuff; UCHAR* ResData = ResponseBuff + m->SizeofPortMsg; UCHAR CurSeqNumber = (UCHAR)m->CallSeqNumber++; // Send the request in chunks UCHAR* Buffer = (UCHAR*)req; ULONG BuffLen = req->length; while (BuffLen) { ULONG send_len = BuffLen > m->MaxDataLen ? m->MaxDataLen : BuffLen; // set header memset(ReqHeader, 0, m->SizeofPortMsg); ReqHeader->u1.s1.DataLength = (USHORT)send_len; ReqHeader->u1.s1.TotalLength = (USHORT)(m->SizeofPortMsg + send_len); // set req data memcpy(ReqData, Buffer, send_len); // use highest byte of the length as sequence field if (Buffer == (UCHAR*)req) ReqData[3] = CurSeqNumber; // advance position Buffer += send_len; BuffLen -= send_len; NTSTATUS status = NtRequestWaitReplyPort(m->PortHandle, (PORT_MESSAGE*)RequestBuff, (PORT_MESSAGE*)ResponseBuff); if (!NT_SUCCESS(status)) { NtClose(m->PortHandle); m->PortHandle = NULL; return SB_ERR(SB_ServiceFail, QVariantList() << QString("request %1").arg(status, 8, 16), status); // 2203 } if (BuffLen && ResHeader->u1.s1.DataLength) return SB_ERR(SB_ServiceFail, QVariantList() << QString("early reply")); // 2203 } // the last call to NtRequestWaitReplyPort should return the first chunk of the reply if (ResHeader->u1.s1.DataLength >= sizeof(MSG_HEADER)) { if (ResData[3] != CurSeqNumber) return SB_ERR(SB_ServiceFail, QVariantList() << QString("mismatched reply")); // 2203 // clear highest byte of the size field ResData[3] = 0; BuffLen = ((MSG_HEADER*)ResData)->length; } else BuffLen = 0; if (BuffLen == 0) return SB_ERR(SB_ServiceFail, QVariantList() << QString("null reply (msg %1 len %2)").arg(req->msgid, 8, 16).arg(req->length)); // 2203 // read remaining chunks MSG_HEADER* rpl = (MSG_HEADER*)malloc(BuffLen); Buffer = (UCHAR*)rpl; for (;;) { NTSTATUS status; if (ResHeader->u1.s1.DataLength > BuffLen) status = STATUS_PORT_MESSAGE_TOO_LONG; else { // get data memcpy(Buffer, ResData, ResHeader->u1.s1.DataLength); // adcance position Buffer += ResHeader->u1.s1.DataLength; BuffLen -= ResHeader->u1.s1.DataLength; // are we done yet? if (!BuffLen) break; // set header memset(ReqHeader, 0, m->SizeofPortMsg); ReqHeader->u1.s1.TotalLength = (USHORT)m->SizeofPortMsg; status = NtRequestWaitReplyPort(m->PortHandle, (PORT_MESSAGE*)RequestBuff, (PORT_MESSAGE*)ResponseBuff); } if (!NT_SUCCESS(status)) { free(rpl); rpl = NULL; NtClose(m->PortHandle); m->PortHandle = NULL; return SB_ERR(SB_ServiceFail, QVariantList() << QString("reply %1").arg(status, 8, 16), status); // 2203 } } prpl->Assign(rpl); return SB_OK; } SB_STATUS CSbieAPI__QueueCreate(SSbieAPI* m, const WCHAR* QueueName, HANDLE *out_EventHandle) { QUEUE_CREATE_REQ req; req.h.length = sizeof(QUEUE_CREATE_REQ); req.h.msgid = MSGID_QUEUE_CREATE; wcscpy(req.queue_name, QueueName); req.event_handle = (ULONG64)(ULONG_PTR)CreateEvent(NULL, FALSE, FALSE, NULL); if (!req.event_handle) return SB_ERR(STATUS_UNSUCCESSFUL); NTSTATUS status = STATUS_SERVER_DISABLED; CSbieAPI::SScoped rpl; CSbieAPI__CallServer(m, &req.h, &rpl); if (rpl) status = rpl->h.status; if (!NT_SUCCESS(status)) { CloseHandle((HANDLE)(ULONG_PTR)req.event_handle); return SB_ERR(status); } *out_EventHandle = (HANDLE)(ULONG_PTR)req.event_handle; return SB_OK; } bool CSbieAPI::GetQueue() { QUEUE_GETREQ_REQ req; req.h.length = sizeof(QUEUE_GETREQ_REQ); req.h.msgid = MSGID_QUEUE_GETREQ; wcscpy(req.queue_name, m->QueueName); NTSTATUS status = STATUS_SERVER_DISABLED; SScoped rpl; CSbieAPI__CallServer(m, &req.h, &rpl); if (rpl) { status = rpl->h.status; if (NT_SUCCESS(status) && rpl->data_len >= 4) { QVariantMap Data; switch (*(ULONG*)rpl->data) { case MAN_FILE_MIGRATION: { MAN_FILE_MIGRATION_REQ *req = (MAN_FILE_MIGRATION_REQ *)rpl->data; Data["id"] = (int)eFileMigration; Data["fileSize"] = req->file_size; Data["fileName"] = Nt2DosPath(QString::fromWCharArray(req->file_path)); break; } case MAN_INET_BLOCKADE: Data["id"] = (int)eInetBlockade; break; case -1: Data["id"] = 0; break; } emit QueuedRequest(rpl->client_pid, rpl->client_tid, rpl->req_id, Data); } } //if(status == STATUS_END_OF_FILE) // there are no more requests in the queue at this time // return false; // nothing more to do if (NT_SUCCESS(status)) return true; // we did something return false; } void CSbieAPI::SendReplyData(quint32 RequestId, const QVariantMap& Result) { QByteArray Data; switch(Result["id"].toInt()) { case CSbieAPI::eFileMigration: { MAN_FILE_MIGRATION_RPL rpl; rpl.status = STATUS_SUCCESS; rpl.retval = Result["retval"].toUInt(); Data = QByteArray((char*)&rpl, sizeof(MAN_FILE_MIGRATION_RPL)); break; } case CSbieAPI::eInetBlockade: { MAN_INET_BLOCKADE_RPL rpl; rpl.status = STATUS_SUCCESS; rpl.retval = Result["retval"].toUInt(); Data = QByteArray((char*)&rpl, sizeof(MAN_INET_BLOCKADE_RPL)); break; } default: { ULONG rpl[2]; rpl[0] = STATUS_SUCCESS; rpl[1] = Result["retval"].toUInt(); Data = QByteArray((char*)&rpl, sizeof(rpl)); } } ULONG req_len = sizeof(QUEUE_PUTRPL_REQ) + Data.length(); SScoped req(malloc(req_len)); req->h.length = req_len; req->h.msgid = MSGID_QUEUE_PUTRPL; wcscpy(req->queue_name, m->QueueName); req->req_id = RequestId; req->data_len = Data.length(); memcpy(req->data, Data.constData(), req->data_len); NTSTATUS status = STATUS_SERVER_DISABLED; SScoped rpl; CallServer(&req->h, &rpl); if (rpl) status = rpl->h.status; } void CSbieAPI::run() { int Idle = 0; HANDLE EventHandle = NULL; if(m_bWithQueue) CSbieAPI__QueueCreate(m, m->QueueName, &EventHandle); while (!m_bTerminate) { int Done = 0; if (InterlockedCompareExchange(&m->SvcLock, SVC_OP_STATE_EXEC, SVC_OP_STATE_START) == SVC_OP_STATE_START) { m->SvcStatus = CSbieAPI__CallServer(m, m->SvcReq, m->SvcRpl); InterlockedExchange(&m->SvcLock, SVC_OP_STATE_DONE); Done++; } if (EventHandle != NULL && WaitForSingleObject(EventHandle, 0) == 0) { while(GetQueue()) Done++; } while (GetLog()) // this emits sbie message events if there are any Done++; while (GetMonitor()) Done++; if (Done != 0) Idle = 0; else { if(m->clearingBuffers) m->clearingBuffers = false; if(Idle < 5) Idle++; m_ThreadMutex.lock(); m_ThreadWait.wait(&m_ThreadMutex, 10 * Idle); m_ThreadMutex.unlock(); } } if (EventHandle != NULL) CloseHandle(EventHandle); } SB_STATUS CSbieAPI::CallServer(void* req, SScopedVoid* prpl) const { // // Note: Once we open a port to the server from a threat the service will remember it we can't reconnect after disconnection // So for every new connection we need a new threat, we achieve this by letting our monitor threat issue all requests // // As of Sbie build 5.50.5 the SbieCvc properly handles reconnection attempts so this mechanism is no longer necessary // However for the queue mechanism we need the communication to be still handled by the helper thread // while (InterlockedCompareExchange(&m->SvcLock, SVC_OP_STATE_PREP, SVC_OP_STATE_IDLE) != SVC_OP_STATE_IDLE) QThread::usleep(100); m->SvcReq = (MSG_HEADER*)req; m->SvcRpl = prpl; m->SvcStatus = SB_OK; InterlockedExchange(&m->SvcLock, SVC_OP_STATE_START); // Wake threat imminetly m_ThreadMutex.lock(); m_ThreadWait.wakeAll(); m_ThreadMutex.unlock(); // worker: SVC_OP_STATE_START -> SVC_OP_STATE_EXEC -> SVC_OP_STATE_DONE while (InterlockedCompareExchange(&m->SvcLock, SVC_OP_STATE_EVAL, SVC_OP_STATE_DONE) != SVC_OP_STATE_DONE) QThread::usleep(100); m->SvcReq = NULL; m->SvcRpl = NULL; SB_STATUS Status = m->SvcStatus; InterlockedExchange(&m->SvcLock, SVC_OP_STATE_IDLE); return Status; } QString CSbieAPI::GetVersion() { WCHAR out_version[16]; __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_GET_VERSION_ARGS *args = (API_GET_VERSION_ARGS*)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_GET_VERSION; args->string.val = out_version; if (! NT_SUCCESS(m->IoControl(parms))) wcscpy(out_version, L"unknown"); return QString::fromWCharArray(out_version); } SB_STATUS CSbieAPI::TakeOver() { __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_SESSION_LEADER_ARGS *args = (API_SESSION_LEADER_ARGS *)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_SESSION_LEADER; args->token_handle.val64 = 0; // (ULONG64)(ULONG_PTR)GetCurrentProcessToken(); args->process_id.val64 = 0; // (ULONG64)(ULONG_PTR)&ResultValue; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) return SB_ERR(status); return SB_OK; } SB_STATUS CSbieAPI::WatchIni(bool bEnable, bool bReLoad) { if (bEnable) { m_IniWatcher.addPath(m_IniPath); m_IniReLoad = bReLoad; } else m_IniWatcher.removePath(m_IniPath); return SB_OK; } void CSbieAPI::OnIniChanged(const QString &path) { if (!m_bReloadPending) { m_bReloadPending = true; QTimer::singleShot(500, this, SLOT(OnReloadConfig())); } } void CSbieAPI::OnReloadConfig() { m_bReloadPending = false; m_bBoxesDirty = true; if (m_IniReLoad) ReloadConfig(); } extern "C" { NTSTATUS NTAPI RtlSetThreadErrorMode(IN ULONG NewMode, OUT PULONG OldMode); } quint32 CSbieAPI::GetVolumeSN(const wchar_t* path, std::wstring* pLabel) { ULONG sn = 0; HANDLE handle; IO_STATUS_BLOCK iosb; UNICODE_STRING objname; size_t path_len = wcslen(path); objname.Buffer = new wchar_t[path_len + 2]; wmemcpy(objname.Buffer, path, path_len); objname.Buffer[path_len ] = L'\\'; objname.Buffer[path_len + 1] = L'\0'; objname.Length = (USHORT)(path_len + 1) * sizeof(WCHAR); objname.MaximumLength = objname.Length + sizeof(WCHAR); OBJECT_ATTRIBUTES objattrs; InitializeObjectAttributes( &objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL); ULONG OldMode; RtlSetThreadErrorMode(0x10u, &OldMode); NTSTATUS status = NtCreateFile( &handle, GENERIC_READ | SYNCHRONIZE, &objattrs, &iosb, NULL, 0, FILE_SHARE_VALID_FLAGS, FILE_OPEN, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); RtlSetThreadErrorMode(OldMode, 0i64); delete [] objname.Buffer; if (NT_SUCCESS(status)) { union { FILE_FS_VOLUME_INFORMATION volumeInfo; BYTE volumeInfoBuff[64]; } u; if (NT_SUCCESS(NtQueryVolumeInformationFile(handle, &iosb, &u.volumeInfo, sizeof(u), FileFsVolumeInformation))) { sn = u.volumeInfo.VolumeSerialNumber; if (pLabel) *pLabel = std::wstring(u.volumeInfo.VolumeLabel, u.volumeInfo.VolumeLabelLength/sizeof(wchar_t)); } NtClose(handle); } return sn; } QString CSbieAPI::ResolveAbsolutePath(const QString& Path) { std::wstring path = Path.toStdWString(); UNICODE_STRING uni; RtlInitUnicodeString(&uni, path.c_str()); OBJECT_ATTRIBUTES objattrs; InitializeObjectAttributes(&objattrs, &uni, OBJ_CASE_INSENSITIVE, NULL, NULL); HANDLE FileHandle; IO_STATUS_BLOCK IoStatusBlock; NTSTATUS status = NtCreateFile(&FileHandle, FILE_READ_ATTRIBUTES | SYNCHRONIZE, &objattrs, &IoStatusBlock, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,NULL, 0); if (NT_SUCCESS(status)) { const ULONG NameLen = 4096; WCHAR NameBuf[NameLen]; __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_GET_FILE_NAME_ARGS *args = (API_GET_FILE_NAME_ARGS *)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_GET_FILE_NAME; args->handle.val64 = (ULONG64)(ULONG_PTR)FileHandle; args->name_len.val64 = (ULONG64)(ULONG_PTR)(NameLen * sizeof(WCHAR)); args->name_buf.val64 = (ULONG64)(ULONG_PTR)NameBuf; status = m->IoControl(parms); NtClose(FileHandle); if (NT_SUCCESS(status)) return QString::fromWCharArray(NameBuf); } return Path; } void CSbieAPI::UpdateDriveLetters() { QWriteLocker Lock(&m_DriveLettersMutex); m_DriveLetters.clear(); // \Device\HarddiskVolume10 // \Device\HarddiskVolume1 // \Device\LanmanRedirector\server\share\file.txt // \Device\LanmanRedirector\;Q:0000000000001234\server\share wchar_t lpTargetPath [MAX_PATH]; for (wchar_t ltr = L'A'; ltr <= L'Z'; ltr++) { wchar_t drv[] = { ltr, L':', '\0' }; uint size = QueryDosDevice(drv, lpTargetPath, MAX_PATH); if (size > 0) { SDrive Drive; QString Key = QString::fromWCharArray(lpTargetPath); QStringList Chunks = Key.split("\\"); if (Chunks.count() >= 5 && Chunks[2].compare("LanmanRedirector", Qt::CaseInsensitive) == 0) { Drive.Type = SDrive::EShare; Chunks.removeAt(3); Key = Chunks.join("\\"); Drive.Aux = Chunks.mid(3).join("\\"); } else { Drive.Type = SDrive::EVolume; if (quint32 sn = GetVolumeSN(lpTargetPath)) Drive.Aux = QString("%1-%2").arg((ushort)HIWORD(sn), 4, 16, QChar('0')).arg((ushort)LOWORD(sn), 4, 16, QChar('0')).toUpper(); } Key.append("\\"); Drive.Letter = QString::fromWCharArray(drv) + "\\"; Drive.NtPath = Key; m_DriveLetters.insert(Drive.Letter, Drive); } } } QString CSbieAPI::Nt2DosPath(QString NtPath, bool* pOk) const { if (NtPath.left(11) == "\\??\\Volume{") { wchar_t DeviceName [MAX_PATH]; std::wstring VolumeName = NtPath.mid(4, 44).toStdWString(); if (QueryDosDeviceW(VolumeName.c_str(), DeviceName, ARRAYSIZE(DeviceName))) NtPath.replace(0, 48, QString::fromWCharArray(DeviceName)); } if (NtPath.indexOf("\\device\\mup", 0, Qt::CaseInsensitive) == 0) NtPath = "\\Device\\LanmanRedirector" + NtPath.mid(11); QReadLocker Lock(&m_DriveLettersMutex); for (QMap::const_iterator I = m_DriveLetters.begin(); I != m_DriveLetters.end(); ++I) { const SDrive& Drive = I.value(); if (Drive.NtPath.compare(NtPath.left(Drive.NtPath.length()), Qt::CaseInsensitive) == 0) { if(pOk) *pOk = true; return NtPath.replace(0, Drive.NtPath.length(), Drive.Letter); } } if(pOk) *pOk = false; return NtPath; } QString CSbieAPI::GetSbieHome() const { WCHAR DosPath[MAX_PATH]; ULONG DosPathMaxLen = MAX_PATH; __declspec(align(8)) UNICODE_STRING64 dos_path_uni = { 0, (USHORT)(DosPathMaxLen * sizeof(WCHAR)), (ULONG64)DosPath }; __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_GET_HOME_PATH_ARGS *args = (API_GET_HOME_PATH_ARGS *)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_GET_HOME_PATH; args->nt_path.val64 = NULL; if (DosPath) args->dos_path.val64 = (ULONG64)(ULONG_PTR)&dos_path_uni; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) return QString(); return QString::fromWCharArray(DosPath); } QString CSbieAPI::GetIniPath(bool* IsHome) const { QString IniPath; SBIE_INI_GET_PATH_REQ req; req.h.msgid = MSGID_SBIE_INI_GET_PATH; req.h.length = sizeof(SBIE_INI_GET_PATH_REQ); SScoped rpl; SB_STATUS Status = CallServer(&req.h, &rpl); if (!Status || !rpl) return QString(); if (rpl->h.status == 0) { IniPath = QString::fromWCharArray(rpl->path); if (IsHome) *IsHome = rpl->is_home_path; } return IniPath; } QString CSbieAPI::GetUserSection(QString* pUserName, bool* pIsAdmin) const { QString UserSection; SBIE_INI_GET_USER_REQ req; req.h.msgid = MSGID_SBIE_INI_GET_USER; req.h.length = sizeof(SBIE_INI_GET_USER_REQ); SScoped rpl; SB_STATUS Status = CallServer(&req.h, &rpl); if (!Status || !rpl) return QString(); if (rpl->h.status == 0) { if (pIsAdmin) *pIsAdmin = rpl->admin; UserSection = QString::fromWCharArray(rpl->section); if (pUserName) *pUserName = QString::fromWCharArray(rpl->name); } return UserSection; } SB_RESULT(quint32) CSbieAPI::RunStart(const QString& BoxName, const QString& Command, bool Elevated, const QString& WorkingDir, QProcess* pProcess) { if (m_SbiePath.isEmpty()) return SB_ERR(SB_PathFail); QString StartArgs; if(Elevated) StartArgs += "/elevated "; if (!BoxName.isEmpty()) StartArgs += "/box:" + BoxName + " "; else StartArgs += "/disable_force "; StartArgs += Command; qint64 pid = 0; //wchar_t sysPath[MAX_PATH]; //GetSystemDirectoryW(sysPath, MAX_PATH); if (pProcess) { //pProcess->setWorkingDirectory(QString::fromWCharArray(sysPath)); if (!WorkingDir.isEmpty()) pProcess->setWorkingDirectory(WorkingDir); pProcess->setProgram(GetStartPath()); pProcess->setNativeArguments(StartArgs); pProcess->start(); pid = pProcess->processId(); } else { QProcess process; //process.setWorkingDirectory(QString::fromWCharArray(sysPath)); if (!WorkingDir.isEmpty()) process.setWorkingDirectory(WorkingDir); process.setProgram(GetStartPath()); process.setNativeArguments(StartArgs); process.startDetached(&pid); } /* QString CommandLine = "\"" + GetStartPath() + "\" " + StartArgs; STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); // Start the child process. if( !CreateProcessW( NULL, // No module name (use command line) (wchar_t*)CommandLine.toStdWString().c_str(), // Command line NULL, // Process handle not inheritable NULL, // Thread handle not inheritable FALSE, // Set handle inheritance to FALSE 0, // No creation flags NULL, // Use parent's environment block NULL, // Use parent's starting directory &si, // Pointer to STARTUPINFO structure &pi ) // Pointer to PROCESS_INFORMATION structure ) { printf( "CreateProcess failed (%d).\n", GetLastError() ); return SB_ERR(); } // Wait until child process exits. //WaitForSingleObject( pi.hProcess, INFINITE ); // Close process and thread handles. CloseHandle( pi.hProcess ); CloseHandle( pi.hThread ); */ if(pid == 0) return SB_ERR(); return CSbieResult((quint32)pid); } QString CSbieAPI::GetStartPath() const { return m_SbiePath + "\\" + QString::fromWCharArray(SBIESTART_EXE); } quint32 CSbieAPI::GetSessionID() const { return m->sessionId; } SB_STATUS CSbieAPI::ReloadBoxes(bool bForceUpdate) { if (!bForceUpdate && !m_bBoxesDirty && !m_IniWatcher.files().isEmpty()) return SB_OK; m_bBoxesDirty = false; QMap OldSandBoxes = m_SandBoxes; for (int i = 0;;i++) { QString BoxName = SbieIniGet(QString(), QString(), (i | CONF_GET_NO_EXPAND | CONF_GET_NO_TEMPLS)); if (BoxName.isNull()) break; bool bIsEnabled; if (!IsBox(BoxName, bIsEnabled)) continue; CSandBoxPtr pBox = OldSandBoxes.take(BoxName.toLower()); if (!pBox) { pBox = CSandBoxPtr(NewSandBox(BoxName, this)); m_SandBoxes.insert(BoxName.toLower(), pBox); emit BoxAdded(pBox); } UpdateBoxPaths(pBox.data()); pBox->m_IsEnabled = bIsEnabled; pBox->UpdateDetails(); } foreach(const QString & BoxName, OldSandBoxes.keys()) { CSandBoxPtr pBox = m_SandBoxes.take(BoxName); pBox->SetBoxPaths(QString(), QString(), QString()); emit BoxRemoved(pBox); } return SB_OK; } QString CSbieAPI__FormatNtStatus(long nsCode) { static HMODULE hNtDll = NULL; if(!hNtDll) hNtDll = GetModuleHandle(L"ntdll.dll"); if (hNtDll == NULL) return QString(); WCHAR* ret_str = NULL; DWORD dwRes = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE, hNtDll, nsCode >= 0 ? nsCode : RtlNtStatusToDosError(nsCode), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&ret_str, 0, NULL); QString qStr = dwRes > 0 ? QString::fromWCharArray(ret_str) : QString(); LocalFree(ret_str); return qStr; } SB_STATUS CSbieAPI::SbieIniSet(void *RequestBuf, void *pPasswordWithinRequestBuf, const QString& SectionName, const QString& SettingName) { retry: m->Password.toWCharArray((WCHAR*)pPasswordWithinRequestBuf); // fix-me: potential overflow ((WCHAR*)pPasswordWithinRequestBuf)[m->Password.length()] = L'\0'; SScoped rpl; SB_STATUS Status = CallServer((MSG_HEADER *)RequestBuf, &rpl); SecureZeroMemory(pPasswordWithinRequestBuf, sizeof(WCHAR) * 64); if (!Status || !rpl) return Status; ULONG status = rpl->status; if (status == 0) return SB_OK; if (status == STATUS_LOGON_NOT_GRANTED || status == STATUS_WRONG_PASSWORD) { if (((MSG_HEADER *)RequestBuf)->msgid != MSGID_SBIE_INI_TEST_PASSWORD) { bool bRetry = false; emit NotAuthorized(status == STATUS_WRONG_PASSWORD, bRetry); if (bRetry) goto retry; } return SB_ERR(SB_NotAuthorized, QVariantList() << SectionName, status); } return SB_ERR(SB_ConfigFailed, QVariantList() << SettingName << SectionName << CSbieAPI__FormatNtStatus(status), status); } SB_STATUS CSbieAPI::SbieIniSet(const QString& Section, const QString& Setting, const QString& Value, ESetMode Mode, bool bRefresh) { ULONG msgid = 0; switch (Mode) { case eIniUpdate: msgid = MSGID_SBIE_INI_SET_SETTING; break; case eIniAppend: msgid = MSGID_SBIE_INI_ADD_SETTING; break; case eIniInsert: msgid = MSGID_SBIE_INI_INS_SETTING; break; case eIniDelete: msgid = MSGID_SBIE_INI_DEL_SETTING; break; default: return SB_ERR(); } SScoped req(malloc(sizeof(SBIE_INI_SETTING_REQ) + Value.length() * sizeof(WCHAR))); req->refresh = bRefresh ? TRUE : FALSE; Section.toWCharArray(req->section); // fix-me: potential overflow req->section[Section.length()] = L'\0'; Setting.toWCharArray(req->setting); // fix-me: potential overflow req->setting[Setting.length()] = L'\0'; Value.toWCharArray(req->value); // fix-me: potential overflow req->value[Value.length()] = L'\0'; req->value_len = Value.length(); req->h.msgid = msgid; req->h.length = sizeof(SBIE_INI_SETTING_REQ) + req->value_len * sizeof(WCHAR); SB_STATUS Status = SbieIniSet(&req->h, req->password, Section, Setting); //if (!Status) // emit LogSbieMessage(0xC1020000 | 2203, QStringList() << "" << Status.GetText() << "", GetCurrentProcessId()); return Status; } void CSbieAPI::CommitIniChanges() { bool bRemoved = m_IniWatcher.removePath(m_IniPath); SbieIniSet("", "", ""); // commit and refresh if (bRemoved) m_IniWatcher.addPath(m_IniPath); m_bBoxesDirty = true; } QString CSbieAPI::SbieIniGetEx(const QString& Section, const QString& Setting) { QString Value; SScoped req(malloc(sizeof(SBIE_INI_SETTING_REQ) )); memset(req, 0, sizeof(SBIE_INI_SETTING_REQ)); Section.toWCharArray(req->section); // fix-me: potential overflow req->section[Section.length()] = L'\0'; Setting.toWCharArray(req->setting); // fix-me: potential overflow req->setting[Setting.length()] = L'\0'; req->h.msgid = MSGID_SBIE_INI_GET_SETTING; req->h.length = sizeof(SBIE_INI_SETTING_REQ); SScoped rpl; SB_STATUS Status = CallServer(&req->h, &rpl); if (!Status || !rpl) return QString(); if (rpl->h.status == 0) { Value = QString::fromWCharArray(rpl->value, rpl->value_len - 1); } return Value; } QString CSbieAPI::SbieIniGet(const QString& Section, const QString& Setting, quint32 Index, qint32* ErrCode) { std::wstring section = Section.toStdWString(); std::wstring setting = Setting.toStdWString(); WCHAR out_buffer[CONF_LINE_LEN] = { 0 }; __declspec(align(8)) UNICODE_STRING64 Output = { 0, sizeof(out_buffer) - 4 , (ULONG64)out_buffer }; __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; memset(parms, 0, sizeof(parms)); parms[0] = API_QUERY_CONF; parms[1] = (ULONG64)section.c_str(); parms[2] = (ULONG64)setting.c_str(); parms[3] = (ULONG64)&Index; parms[4] = (ULONG64)&Output; NTSTATUS status = m->IoControl(parms); if (ErrCode) *ErrCode = status; if (!NT_SUCCESS(status)) return QString(); return QString::fromWCharArray(out_buffer); } QString CSbieAPI::SbieIniGet2(const QString& Section, const QString& Setting, quint32 Index, bool bWithGlobal, bool bNoExpand, bool withTemplates) { int flags = (bWithGlobal ? 0 : CONF_GET_NO_GLOBAL); if (!withTemplates) flags |= CONF_GET_NO_TEMPLS; if (bNoExpand) flags |= CONF_GET_NO_EXPAND; return SbieIniGet(Section, Setting, Index | flags); } SB_STATUS CSbieAPI::ValidateName(const QString& BoxName) { if (BoxName.length() > (BOXNAME_COUNT - 2)) return SB_ERR(SB_NameLenLimit); /* invalid file name characters on windows < (less than) > (greater than) : (colon - sometimes works, but is actually NTFS Alternate Data Streams) " (double quote) / (forward slash) \ (backslash) | (vertical bar or pipe) ? (question mark) * (asterisk) */ QStringList DeviceNames = QStringList() << "aux" << "clock$" << "con" << "nul" << "prn" << "com1" << "com2" << "com3" << "com4" << "com5" << "com6" << "com7" << "com8" << "com9" << "com0" << "lpt1" << "lpt2" << "lpt3" << "lpt4" << "lpt5" << "lpt6" << "lpt7" << "lpt8" << "lpt9" << "lpt0"; if (DeviceNames.contains(BoxName, Qt::CaseInsensitive)) return SB_ERR(SB_BadNameDev); if(BoxName.compare("GlobalSettings", Qt::CaseInsensitive) == 0) return SB_ERR(SB_BadNameDev); if(BoxName.left(13).compare("UserSettings_", Qt::CaseInsensitive) == 0) return SB_ERR(SB_BadNameDev); if (BoxName.contains(QRegularExpression("[^A-Za-z0-9_]"))) return SB_ERR(SB_BadNameChar); return SB_OK; } QString CSbieAPI::MkNewName(QString Name) { Name.replace(QRegularExpression("[<>:\"/\\\\|?*\\[\\]]"), "-").replace(" ", "_"); for (int i=0;; i++) { QString NewName = Name; if (i > 0) NewName.append("_" + QString::number(i)); if (m_SandBoxes.contains(NewName.toLower())) continue; return NewName; } } SB_STATUS CSbieAPI::CreateBox(const QString& BoxName, bool bReLoad) { SB_STATUS Status = ValidateName(BoxName); if(Status.IsError()) return Status; if(m_SandBoxes.contains(BoxName.toLower())) return SB_ERR(SB_NameExists); Status = SbieIniSet(BoxName, "Enabled", "y"); if (Status.IsError()) return Status; if (bReLoad) { ReloadConfig(); ReloadBoxes(); } return Status; } SB_STATUS CSbieAPI__GetProcessPIDs(SSbieAPI* m, const QString& BoxName, bool bAllSessions, ULONG* pids, ULONG* count) { WCHAR box_name[BOXNAME_COUNT]; BoxName.toWCharArray(box_name); // fix-me: potential overflow box_name[BoxName.size()] = L'\0'; BOOLEAN all_sessions = bAllSessions ? TRUE : false; ULONG which_session = -1; // -1 means current session, ignoreewd when all_sessions == true __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; memset(parms, 0, sizeof(parms)); parms[0] = API_ENUM_PROCESSES; parms[1] = (ULONG64)pids; parms[2] = (ULONG64)box_name; parms[3] = (ULONG64)all_sessions; parms[4] = (ULONG64)which_session; parms[5] = (ULONG64)count; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) return SB_ERR(status); return SB_OK; } SB_STATUS CSbieAPI::UpdateProcesses(int iKeep, bool bAllSessions) { ULONG count = 0; SB_STATUS Status = CSbieAPI__GetProcessPIDs(m, "", bAllSessions, NULL, &count); // query count if (Status.IsError()) return Status; count += 128; // add some extra space ULONG* boxed_pids = new ULONG[count]; Status = CSbieAPI__GetProcessPIDs(m, "", bAllSessions, boxed_pids, &count); // query pids if (Status.IsError()) { delete[] boxed_pids; return Status; } QMap OldProcessList; foreach(const CSandBoxPtr& pBox, m_SandBoxes) OldProcessList.insert(pBox->m_ProcessList); for (int i=0; i < count; i++) { quint32 ProcessId = (quint32)boxed_pids[i]; CBoxedProcessPtr pProcess = OldProcessList.take(ProcessId); if (!pProcess) { pProcess = CBoxedProcessPtr(NewBoxedProcess(ProcessId, NULL)); UpdateProcessInfo(pProcess); CSandBoxPtr pBox = GetBoxByName(pProcess->GetBoxName()); if (pBox.isNull()) continue; if (pBox->m_ActiveProcessCount == 0) { pBox->m_ActiveProcessCount = 1; pBox->OpenBox(); m_bBoxesDirty = true; emit BoxOpened(pBox); } pProcess->m_pBox = pBox.data(); pBox->m_ProcessList.insert(ProcessId, pProcess); m_BoxedProxesses.insert(ProcessId, pProcess); pBox->m_ActiveProcessDirty = true; pProcess->InitProcessInfo(); } pProcess->UpdateProcessInfo(); } foreach(const CBoxedProcessPtr& pProcess, OldProcessList) { if (!pProcess->IsTerminated()) { pProcess->SetTerminated(); pProcess->m_pBox->m_ActiveProcessDirty = true; } else if (iKeep != -1 && pProcess->IsTerminated(iKeep)) { pProcess->m_pBox->m_ProcessList.remove(pProcess->m_ProcessId); m_BoxedProxesses.remove(pProcess->m_ProcessId); } } foreach(const CSandBoxPtr & pBox, m_SandBoxes) { if (pBox->m_ActiveProcessDirty) { pBox->m_ActiveProcessDirty = false; int ActiveProcessCount = 0; foreach(const CBoxedProcessPtr & pProcess, pBox->GetProcessList()) { if (!pProcess->IsTerminated()) ActiveProcessCount++; } bool WasBoxClosed = pBox->m_ActiveProcessCount > 0 && ActiveProcessCount == 0; pBox->m_ActiveProcessCount = ActiveProcessCount; if (WasBoxClosed) { pBox->CloseBox(); m_bBoxesDirty = true; emit BoxClosed(pBox); } } } delete[] boxed_pids; return SB_OK; } bool CSbieAPI::HasProcesses(const QString& BoxName) { ULONG count; return CSbieAPI__GetProcessPIDs(m, BoxName, false, NULL, &count) && (count > 0); } SB_STATUS CSbieAPI__QueryBoxPath(SSbieAPI* m, const WCHAR *box_name, WCHAR *out_file_path, WCHAR *out_key_path, WCHAR *out_ipc_path, ULONG *inout_file_path_len, ULONG *inout_key_path_len, ULONG *inout_ipc_path_len) { __declspec(align(8)) UNICODE_STRING64 FilePath = { 0, (USHORT)*inout_file_path_len, (ULONG64)out_file_path }; __declspec(align(8)) UNICODE_STRING64 KeyPath = { 0, (USHORT)*inout_key_path_len, (ULONG64)out_key_path }; __declspec(align(8)) UNICODE_STRING64 IpcPath = { 0, (USHORT)*inout_ipc_path_len, (ULONG64)out_ipc_path }; __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_QUERY_BOX_PATH_ARGS *args = (API_QUERY_BOX_PATH_ARGS*)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_QUERY_BOX_PATH; args->box_name.val64 = (ULONG64)(ULONG_PTR)box_name; args->file_path_len.val = inout_file_path_len; if (out_file_path) args->file_path.val64 = (ULONG64)(ULONG_PTR)&FilePath; args->key_path_len.val = inout_key_path_len; if (out_key_path) args->key_path.val64 = (ULONG64)(ULONG_PTR)&KeyPath; args->ipc_path_len.val = inout_ipc_path_len; if (out_ipc_path) args->ipc_path.val64 = (ULONG64)(ULONG_PTR)&IpcPath; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) return SB_ERR(status); return SB_OK; } SB_STATUS CSbieAPI::UpdateBoxPaths(CSandBox* pSandBox) { std::wstring boxName = pSandBox->GetName().toStdWString(); ULONG filePathLength = 0; ULONG keyPathLength = 0; ULONG ipcPathLength = 0; SB_STATUS Status = CSbieAPI__QueryBoxPath(m, boxName.c_str(), NULL, NULL, NULL, &filePathLength, &keyPathLength, &ipcPathLength); if (!Status) return Status; std::wstring FileRoot(filePathLength / 2 + 1, '\0'); std::wstring KeyRoot(keyPathLength / 2 + 1, '\0'); std::wstring IpcRoot(ipcPathLength / 2 + 1, '\0'); Status = CSbieAPI__QueryBoxPath(m, boxName.c_str(), (WCHAR*)FileRoot.data(), (WCHAR*)KeyRoot.data(), (WCHAR*)IpcRoot.data(), &filePathLength, &keyPathLength, &ipcPathLength); if (!Status) return Status; QString FilePath = Nt2DosPath(QString::fromWCharArray(FileRoot.c_str(), wcslen(FileRoot.c_str()))); QString RegPath = QString::fromWCharArray(KeyRoot.c_str(), wcslen(KeyRoot.c_str())); QString IpcPath = QString::fromWCharArray(IpcRoot.c_str(), wcslen(IpcRoot.c_str())); pSandBox->SetBoxPaths(FilePath, RegPath, IpcPath); return SB_OK; } SB_STATUS CSbieAPI::UpdateProcessInfo(const CBoxedProcessPtr& pProcess) { WCHAR box_name[BOXNAME_COUNT] = { 0 }; WCHAR image_name[MAX_PATH]; //WCHAR sid[96]; ULONG session_id; ULONG64 create_time; __declspec(align(8)) UNICODE_STRING64 BoxName = { 0, sizeof(box_name) , (ULONG64)box_name }; __declspec(align(8)) UNICODE_STRING64 ImageName = { 0, sizeof(image_name), (ULONG64)image_name }; //__declspec(align(8)) UNICODE_STRING64 SidString = { 0, sizeof(sid), (ULONG64)sid }; __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_QUERY_PROCESS_ARGS *args = (API_QUERY_PROCESS_ARGS*)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_QUERY_PROCESS; args->process_id.val64 = (ULONG64)pProcess->m_ProcessId; args->box_name.val64 = (ULONG64)&BoxName; args->image_name.val64 = (ULONG64)&ImageName; //args->sid_string.val64 = (ULONG64)&SidString; args->session_id.val64 = (ULONG64)&session_id; args->create_time.val64 = (ULONG64)&create_time; NTSTATUS status = m->IoControl(parms); if(!NT_SUCCESS(status)) return SB_ERR(status); pProcess->m_BoxName = QString::fromWCharArray(box_name); pProcess->m_ImageName = QString::fromWCharArray(image_name, ImageName.Length/sizeof(WCHAR)); pProcess->m_SessionId = session_id; pProcess->m_StartTime = QDateTime::fromMSecsSinceEpoch(FILETIME2ms(create_time)); // sid todo return SB_OK; } CSandBoxPtr CSbieAPI::GetBoxByProcessId(quint32 ProcessId) const { CBoxedProcessPtr pProcess = m_BoxedProxesses.value(ProcessId); if (!pProcess || pProcess->IsTerminated()) return CSandBoxPtr(); return GetBoxByName(pProcess->GetBoxName()); } CBoxedProcessPtr CSbieAPI::GetProcessById(quint32 ProcessId) const { return m_BoxedProxesses.value(ProcessId); } quint64 CSbieAPI::QueryProcessInfo(quint32 ProcessId, quint32 InfoClass) { __declspec(align(8)) ULONG64 ResultValue; __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_QUERY_PROCESS_INFO_ARGS *args = (API_QUERY_PROCESS_INFO_ARGS *)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_QUERY_PROCESS_INFO; args->process_id.val64 = (ULONG64)(ULONG_PTR)ProcessId; args->info_type.val64 = (ULONG64)(ULONG_PTR)InfoClass; args->info_data.val64 = (ULONG64)(ULONG_PTR)&ResultValue; args->ext_data.val64 = (ULONG64)(ULONG_PTR)0; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) return NULL; return ResultValue; } SB_STATUS CSbieAPI::TerminateAll(const QString& BoxName) { PROCESS_KILL_ALL_REQ req; req.h.length = sizeof(PROCESS_KILL_ALL_REQ); req.h.msgid = MSGID_PROCESS_KILL_ALL; req.session_id = -1; BoxName.toWCharArray(req.boxname); // fix-me: potential overflow req.boxname[BoxName.size()] = L'\0'; SScoped rpl; SB_STATUS Status = CallServer(&req.h, &rpl); if (!Status || !rpl) return Status; if(rpl->status != 0) Status = SB_ERR(rpl->status); return Status; } SB_STATUS CSbieAPI::TerminateAll(bool bNoExceptions) { #ifdef _DEBUG qDebug() << "TerminateAll" << bNoExceptions; #endif bool bFailed = false; foreach(const CSandBoxPtr& pBox, m_SandBoxes) { if (!bNoExceptions && pBox->GetBool("ExcludeFromTerminateAll", false)) continue; if (!pBox->TerminateAll()) bFailed = true; } return bFailed ? SB_ERR(SB_FailedKillAll) : SB_OK; } SB_STATUS CSbieAPI::Terminate(quint32 ProcessId) { PROCESS_KILL_ONE_REQ req; req.h.length = sizeof(PROCESS_KILL_ONE_REQ); req.h.msgid = MSGID_PROCESS_KILL_ONE; req.pid = ProcessId; SScoped rpl; SB_STATUS Status = CallServer(&req.h, &rpl); if (!Status || !rpl) return Status; if (rpl->status != 0) Status = SB_ERR(rpl->status); return Status; } LONG CSbieAPI__OpenDeviceMap(SSbieAPI* m, HANDLE *DirectoryHandle) { __declspec(align(8)) ULONG64 ResultHandle; __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_OPEN_DEVICE_MAP_ARGS *args = (API_OPEN_DEVICE_MAP_ARGS*)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_OPEN_DEVICE_MAP; args->handle.val64 = (ULONG64)&ResultHandle; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) ResultHandle = 0; if (DirectoryHandle) *DirectoryHandle = (HANDLE*)ResultHandle; return status; } QString CSbieAPI::GetDeviceMap() { UNICODE_STRING objname; OBJECT_ATTRIBUTES objattrs; InitializeObjectAttributes(&objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL); RtlInitUnicodeString(&objname, L"\\??"); HANDLE handle; NTSTATUS status = NtOpenDirectoryObject(&handle, DIRECTORY_QUERY, &objattrs); if (status == STATUS_ACCESS_DENIED) // if we are missing admin rights, lets the driver do it status = CSbieAPI__OpenDeviceMap(m, &handle); if (!NT_SUCCESS(status)) { emit LogSbieMessage(0xC1020000 | 2321, QStringList() << "" << QString("%1").arg(status, 8, 16) << "", GetCurrentProcessId()); } else { WCHAR dirname[128]; ULONG length = sizeof(dirname); status = NtQueryObject(handle, ObjectNameInformation, dirname, length, &length); NtClose(handle); if (!NT_SUCCESS(status)) { emit LogSbieMessage(0xC1020000 | 2321, QStringList() << "" << QString("%1").arg(status, 8, 16) << "", GetCurrentProcessId()); } else { UNICODE_STRING* ustr = &((OBJECT_NAME_INFORMATION*)dirname)->Name; length = ustr->Length / sizeof(WCHAR); return QString::fromWCharArray(ustr->Buffer, length); } } return QString(); } QByteArray CSbieAPI::MakeEnvironment(bool AddDeviceMap) { QStringList EnvList; //if(AllUsers.length() > 0) // EnvList.push_back("00000000_SBIE_ALL_USERS=" + AllUsers); //if(CurrentUser.length() > 0) // EnvList.push_back("00000000_SBIE_CURRENT_USER=" + CurrentUser); //if(PublicUser.length() > 0) // EnvList.push_back("00000000_SBIE_PUBLIC_USER=" + PublicUser); QString DeviceMap = AddDeviceMap ? GetDeviceMap() : QString(); if (DeviceMap.length() > 0) EnvList.push_back("00000000_SBIE_DEVICE_MAP=" + DeviceMap); ULONG ExtraLength = 0; foreach(const QString& Entry, EnvList) ExtraLength += Entry.length() + 1; WCHAR *Environment = GetEnvironmentStrings(); ULONG EnvLength = 0; for(WCHAR* envPtr = (WCHAR*)Environment; *envPtr;) { ULONG len = wcslen(envPtr) + 1; envPtr += len; EnvLength += len; } QByteArray env; env.resize((ExtraLength + EnvLength + 1) * sizeof(WCHAR)); // key1=value1\0key2=value2\0...keyN=valueN\0\0 WCHAR* envPtr = (WCHAR*)env.data(); foreach(const QString& Entry, EnvList) { Entry.toWCharArray(envPtr); envPtr += Entry.length(); *envPtr++ = L'\0'; } wmemcpy(envPtr, Environment, EnvLength); envPtr += EnvLength; *envPtr = L'\0'; return env; } SB_STATUS CSbieAPI::RunSandboxed(const QString& BoxName, const QString& Command, QString WrkDir, quint32 Flags) { DWORD wShowWindow = SW_SHOWNORMAL; if (Command.isEmpty()) return SB_ERR(ERROR_INVALID_PARAMETER); if (WrkDir.isEmpty()) WrkDir = QDir::currentPath().replace("/","\\"); ULONG cmd_len = Command.length(); ULONG dir_len = WrkDir.length(); QByteArray env = MakeEnvironment(true); ULONG env_len = (env.size() - 1) / sizeof(WCHAR); ULONG req_len = sizeof(PROCESS_RUN_SANDBOXED_REQ) + (cmd_len + dir_len + env_len + 8) * sizeof(WCHAR); SScoped req(malloc(req_len)); req->h.length = req_len; req->h.msgid = MSGID_PROCESS_RUN_SANDBOXED; BoxName.toWCharArray(req->boxname); // fix-me: potential overflow req->boxname[BoxName.length()] = L'\0'; req->si_flags = STARTF_FORCEOFFFEEDBACK; #ifdef _DEBUG if ((QGuiApplication::queryKeyboardModifiers() & Qt::ShiftModifier) != 0) req->si_flags |= 0x80000000; #endif req->si_show_window = wShowWindow; if (req->si_show_window != SW_SHOWNORMAL) req->si_flags |= STARTF_USESHOWWINDOW; req->creation_flags = Flags; WCHAR* ptr = (WCHAR*)((ULONG_PTR)req.Value() + sizeof(PROCESS_RUN_SANDBOXED_REQ)); req->cmd_ofs = (ULONG)((ULONG_PTR)ptr - (ULONG_PTR)req.Value()); req->cmd_len = cmd_len; if (cmd_len) { Command.toWCharArray(ptr); ptr += cmd_len; } *ptr++ = L'\0'; req->dir_ofs = (ULONG)((ULONG_PTR)ptr - (ULONG_PTR)req.Value()); req->dir_len = dir_len; if (dir_len) { WrkDir.toWCharArray(ptr); ptr += dir_len; } *ptr++ = L'\0'; req->env_ofs = (ULONG)((ULONG_PTR)ptr - (ULONG_PTR)req.Value()); req->env_len = env_len; if (env_len) { wmemcpy(ptr, (WCHAR*)env.data(), env_len); ptr += env_len; } *ptr++ = L'\0'; SScoped rpl; SB_STATUS Status = CallServer(&req->h, &rpl); if (!Status) return Status; if (!rpl) return SB_ERR(ERROR_SERVER_DISABLED); if (rpl->h.status != 0) return SB_ERR(rpl->h.status); CloseHandle((HANDLE)rpl->hProcess); CloseHandle((HANDLE)rpl->hThread); return SB_OK; } SB_STATUS CSbieAPI__ProceccssExemptionControl(SSbieAPI* m, quint32 process_id, quint32 action_id, ULONG *NewState, ULONG *OldState) { __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_PROCESS_EXEMPTION_CONTROL_ARGS *args = (API_PROCESS_EXEMPTION_CONTROL_ARGS *)parms; memzero(parms, sizeof(parms)); args->func_code = API_PROCESS_EXEMPTION_CONTROL; args->process_id.val64 = process_id; args->action_id.val = action_id; args->set_flag.val = NewState; args->get_flag.val = OldState; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) return SB_ERR(status); return SB_OK; } SB_STATUS CSbieAPI::SetProcessExemption(quint32 process_id, quint32 action_id, bool NewState) { ULONG uNewState = NewState ? TRUE : FALSE; return CSbieAPI__ProceccssExemptionControl(m, process_id, action_id, &uNewState, NULL); } bool CSbieAPI::GetProcessExemption(quint32 process_id, quint32 action_id) { ULONG uOldState = FALSE; CSbieAPI__ProceccssExemptionControl(m, process_id, action_id, NULL, &uOldState); return uOldState != FALSE; } QString CSbieAPI::GetBoxedPath(const QString& BoxName, const QString& Path) { CSandBoxPtr pBox = GetBoxByName(BoxName); if (!pBox) return QString(); return GetBoxedPath(pBox.data(), Path); } //#pragma comment(lib, "mpr.lib") QString CSbieAPI::GetBoxedPath(CSandBox* pBox, const QString& Path, const QString& Snapshot) { QString BoxRoot = pBox->m_FilePath; if (!Snapshot.isEmpty()) BoxRoot += QString("\\snapshot-%1").arg(Snapshot); //WCHAR Buffer[4096]; //DWORD dwBufferLength = sizeof(Buffer)/sizeof(WCHAR ); //UNIVERSAL_NAME_INFO * unameinfo = (UNIVERSAL_NAME_INFO *) &Buffer; //if (WNetGetUniversalName(Path.toStdWString().c_str(), UNIVERSAL_NAME_INFO_LEVEL, (LPVOID)unameinfo, &dwBufferLength) == NO_ERROR) // return BoxRoot + "\\share" + QString::fromWCharArray(unameinfo->lpUniversalName).mid(1); //if (Path.indexOf("\\device\\mup", 0, Qt::CaseInsensitive) == 0) // return QStringList(BoxRoot + "\\share" + Path.mid(11)); if (pBox->GetBool("SeparateUserFolders", true, true)) { if (Path.indexOf(m_UserDir, 0, Qt::CaseInsensitive) == 0) return BoxRoot + "\\user\\current" + Path.mid(m_UserDir.length()); else if (Path.indexOf(m_ProgramDataDir, 0, Qt::CaseInsensitive) == 0) return BoxRoot + "\\user\\all" + Path.mid(m_ProgramDataDir.length()); else if (Path.indexOf(m_PublicDir, 0, Qt::CaseInsensitive) == 0) return BoxRoot + "\\user\\public" + Path.mid(m_PublicDir.length()); } if (Path.length() >= 3 && Path.left(2) == "\\\\") return BoxRoot + "\\share\\" + Path.mid(2); if (Path.length() < 3 || Path.at(1) != ':') return QString(); QReadLocker Lock(&m_DriveLettersMutex); QMap::const_iterator I = m_DriveLetters.find(Path.left(3).toUpper()); if (I != m_DriveLetters.end()) { if (I->Type == SDrive::EShare) return BoxRoot + "\\share\\" + I->Aux + Path.mid(2); else if (pBox->GetBool("UseVolumeSerialNumbers", false) && !I->Aux.isEmpty()) return BoxRoot + "\\drive\\" + Path.at(0) + "~" + I->Aux + Path.mid(2); } return BoxRoot + "\\drive\\" + Path.at(0) + Path.mid(2); /*QStringList Paths; // todo: include snapshot locations if (pBox->GetBool("UseVolumeSerialNumbers", false)) { QDir Dir(BoxRoot + "\\drive\\"); foreach(const QFileInfo & Info, Dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { if (Info.fileName().left(1).compare(Path.at(0), Qt::CaseInsensitive) == 0) Paths.append(BoxRoot + "\\drive\\" + Info.fileName() + Path.mid(2)); } } if(Paths.isEmpty()) Paths = QStringList(BoxRoot + "\\drive\\" + Path.at(0) + Path.mid(2)); return Paths;*/ } QString CSbieAPI::GetRealPath(CSandBox* pBox, const QString& Path) { QString RealPath; QString BoxRoot = pBox->m_FilePath; if (BoxRoot.right(1) == "\\") BoxRoot.truncate(BoxRoot.length() - 1); if (Path.length() < BoxRoot.length()) return QString(); RealPath = Path.mid(BoxRoot.length()); if (RealPath.left(10).compare("\\snapshot-", Qt::CaseInsensitive) == 0) { int pos = RealPath.indexOf("\\", 1); if (pos != -1) RealPath.remove(0, pos); } if (RealPath.left(6) == "\\share") { QString NtFolder = RealPath.mid(6); bool bBs = false; if ((bBs = (NtFolder.count("\\") < 3))) NtFolder += "\\"; bool bOk; QString Folder = Nt2DosPath("\\Device\\LanmanRedirector" + NtFolder, &bOk); if (!bOk) return "\\" + NtFolder; if (bBs) Folder.truncate(Folder.length() - 1); return Folder; } if (RealPath.left(5) == "\\user") { if (RealPath.mid(5, 8) == "\\current") return m_UserDir + RealPath.mid(5 + 8); else if (RealPath.mid(5, 4) == "\\all") return m_ProgramDataDir + RealPath.mid(5 + 4); else if (RealPath.mid(5, 7) == "\\public") return m_PublicDir + RealPath.mid(5 + 7); } if (RealPath.left(6) == "\\drive") { int pos = RealPath.indexOf("\\", 7); return RealPath.mid(7, 1) + ":" + (pos != -1 ? RealPath.mid(pos) : ""); } return QString(); } /////////////////////////////////////////////////////////////////////////////// // Conf // SB_STATUS CSbieAPI::ReloadConfig(bool ReconfigureDrv) { return ReloadConf(ReconfigureDrv ? SBIE_CONF_FLAG_RECONFIGURE : 0); } SB_STATUS CSbieAPI::ReloadCert() { return ReloadConf(SBIE_CONF_FLAG_RELOAD_CERT); } SB_STATUS CSbieAPI::ReloadConf(quint32 flags, quint32 SessionId) { __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; memset(parms, 0, sizeof(parms)); parms[0] = API_RELOAD_CONF; parms[1] = SessionId; parms[2] = flags; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) return SB_ERR(status); emit ConfigReloaded(); m_bBoxesDirty = true; return SB_OK; } bool CSbieAPI::IsBox(const QString& BoxName, bool& bIsEnabled) { std::wstring box_name = BoxName.toStdWString(); __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_IS_BOX_ENABLED_ARGS *args = (API_IS_BOX_ENABLED_ARGS*)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_IS_BOX_ENABLED; args->box_name.val = (WCHAR*)box_name.c_str(); NTSTATUS status = m->IoControl(parms); bIsEnabled = NT_SUCCESS(status); return bIsEnabled || status == STATUS_ACCOUNT_RESTRICTION; } bool CSbieAPI::IsConfigLocked() { return m->Password.isEmpty() && !SbieIniGet("GlobalSettings", "EditPassword", 0).isEmpty(); } SB_STATUS CSbieAPI::UnlockConfig(const QString& Password) { SScoped req(malloc(REQUEST_LEN)); req->h.msgid = MSGID_SBIE_INI_TEST_PASSWORD; req->h.length = sizeof(SBIE_INI_PASSWORD_REQ); m->Password = Password; SB_STATUS Status = SbieIniSet(&req->h, req->old_password, "GlobalSettings", "*"); if (Status.IsError()) m->Password.clear(); return Status; } SB_STATUS CSbieAPI::LockConfig(const QString& NewPassword) { if(NewPassword.length() > 64) return SB_ERR(SB_PasswordBad, STATUS_INVALID_PARAMETER); SScoped req(malloc(REQUEST_LEN)); req->h.msgid = MSGID_SBIE_INI_SET_PASSWORD; req->h.length = sizeof(SBIE_INI_PASSWORD_REQ); NewPassword.toWCharArray(req->new_password); // fix-me: potential overflow req->new_password[NewPassword.length()] = L'\0'; SB_STATUS Status = SbieIniSet(&req->h, req->old_password, "GlobalSettings", "*"); if (!Status.IsError()) m->Password = NewPassword; return Status; } void CSbieAPI::ClearPassword() { m->Password.clear(); } bool CSbieAPI::GetDriverInfo(quint32 InfoClass, void* pBuffer, size_t Size) { __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_QUERY_DRIVER_INFO_ARGS *args = (API_QUERY_DRIVER_INFO_ARGS*)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_QUERY_DRIVER_INFO; args->info_class.val = InfoClass; args->info_data.val = pBuffer; args->info_len.val = Size; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) { memset(pBuffer, 0, Size); return false; } return true; } quint32 CSbieAPI::GetFeatureFlags() { quint32 flags = 0; GetDriverInfo(0, &flags, sizeof(flags)); return flags; } QString CSbieAPI::GetFeatureStr() { quint32 flags = GetFeatureFlags(); QStringList str; if (flags & SBIE_FEATURE_FLAG_WFP) str.append("WFP"); if (flags & SBIE_FEATURE_FLAG_OB_CALLBACKS) str.append("ObCB"); if (flags & SBIE_FEATURE_FLAG_SBIE_LOGIN) str.append("SbL"); if (flags & SBIE_FEATURE_FLAG_SECURITY_MODE) str.append("SMod"); if (flags & SBIE_FEATURE_FLAG_PRIVACY_MODE) str.append("PMod"); if (flags & SBIE_FEATURE_FLAG_COMPARTMENTS) str.append("AppC"); if (flags & SBIE_FEATURE_FLAG_WIN32K_HOOK) str.append("W32k"); return str.join(","); } SB_STATUS CSbieAPI::SetSecureParam(const QString& Name, const void* data, size_t size) { __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_SECURE_PARAM_ARGS *args = (API_SECURE_PARAM_ARGS*)parms; std::wstring name = Name.toStdWString(); memset(parms, 0, sizeof(parms)); args->func_code = API_SET_SECURE_PARAM; args->param_name.val = (WCHAR*)name.c_str(); args->param_data.val = (void*)data; args->param_size.val = size; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) return SB_ERR(status); return SB_OK; } SB_STATUS CSbieAPI::GetSecureParam(const QString& Name, void* data, size_t size, quint32* size_out, bool bVerify) { __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_SECURE_PARAM_ARGS *args = (API_SECURE_PARAM_ARGS*)parms; std::wstring name = Name.toStdWString(); memset(parms, 0, sizeof(parms)); args->func_code = API_GET_SECURE_PARAM; args->param_name.val = (WCHAR*)name.c_str(); args->param_data.val = (void*)data; args->param_size.val = size; args->param_size_out.val = (ULONG*)size_out; args->param_verify.val = bVerify; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) return SB_ERR(status); return SB_OK; } /////////////////////////////////////////////////////////////////////////////// // Log // /*QString CSbieAPI__FormatSbieMsg(SSbieAPI* m, ULONG code, const WCHAR *ins1, const WCHAR *ins2) { ULONG FormatFlags = FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_ALLOCATE_BUFFER; ULONG Lang = 1033; // English ULONG ret_count = 0; WCHAR* ret_str = NULL; if (m->SbieMsgDll) { const WCHAR *ins[6] = { 0 , ins1, ins2, 0, 0, 0 }; ret_count = FormatMessage(FormatFlags, m->SbieMsgDll, code, Lang, (LPWSTR)&ret_str, 4, (va_list *)ins); } if (ret_count == 0) { ret_str = (WCHAR*)LocalAlloc(LMEM_FIXED, 128 * sizeof(WCHAR)); swprintf(ret_str, L"SBIE%04i: %s; %s", code & 0x0000FFFF, ins1 ? ins1 : L"", ins2 ? ins2 : L""); } QString qStr = QString::fromWCharArray(ret_str); LocalFree(ret_str); return qStr.trimmed(); // note messages may have \r\n at the end }*/ //QString CSbieAPI__GetTag(const QString& data, const QString& tag) //{ // int pos = data.indexOf(tag + "="); // if(pos == -1) // return QString(); // pos += tag.length() + 1; // // int end = data.indexOf(" ", pos); // QString value = data.mid(pos, end == -1 ? -1 : end - pos); // // return value; //} bool CSbieAPI::GetLog() { wchar_t* Buffer[4*1024]; ULONG Length = ARRAYSIZE(Buffer); ULONG MsgCode = 0; ULONG ProcessId = 0; ULONG MessageNum = m->lastMessageNum; __declspec(align(8)) UNICODE_STRING64 msgtext = { 0, (USHORT)Length, (ULONG64)Buffer }; __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_GET_MESSAGE_ARGS *args = (API_GET_MESSAGE_ARGS*)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_GET_MESSAGE; args->msg_num.val = &MessageNum; args->session_id.val = m->sessionId; args->msgid.val = &MsgCode; args->msgtext.val = &msgtext; args->process_id.val = &ProcessId; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) return false; // error or no more entries m->lastMessageNum = MessageNum; if (m->clearingBuffers) return true; QStringList MsgData; MsgData.append(""); for (size_t pos = 0; pos < msgtext.Length; ) { size_t len = wcslen((WCHAR*)(msgtext.Buffer + pos)); if (len == 0) break; MsgData.append(QString::fromWCharArray((WCHAR*)(msgtext.Buffer + pos), len)); pos += (len + 1) * sizeof(WCHAR); } while (MsgData.length() < 3) MsgData.append(""); /* WCHAR *str1 = (WCHAR*)msgtext.Buffer; ULONG str1_len = wcslen(str1); WCHAR *str2 = str1 + str1_len + 1; ULONG str2_len = wcslen(str2); // // 0xTFFFMMMM // // T = ttcr // tt = 00 - Ok // tt = 01 - Info // tt = 10 - Warning // tt = 11 - Error // c = unused // r = reserved // // FFF = 0x000 UIstr // FFF = 0x101 POPUP // FFF = 0x102 EVENT // // MMMM = Message Code // quint8 Severity = MsgCode >> 30; quint16 Facility = (MsgCode >> 16) & 0x0FFF; quint16 MessageId= MsgCode & 0xFFFF;*/ if ((MsgCode & 0xFFFF) == 1399) // Process Start Notification { emit ProcessBoxed(ProcessId, Nt2DosPath(MsgData[1]), MsgData[2], MsgData.length() < 4 ? 0 : MsgData[3].toUInt(), MsgData.length() < 5 ? QString() : MsgData[4]); return true; } if ((MsgCode & 0xFFFF) == 2199) // Auto Recovery notification { QString FilePath = Nt2DosPath(MsgData[2]); QString BoxPath = MsgData.length() >= 4 ? Nt2DosPath(MsgData[3]) : QString(); emit FileToRecover(MsgData[1], FilePath, BoxPath, ProcessId); return true; } /* QString Message = CSbieAPI__FormatSbieMsg(m, MsgCode, str1, str2); if (ProcessId != 4) // if it is not from the driver, add the pid Message += tr(", by process: %1").arg(ProcessId); emit LogMessage(Message, MessageId); */ emit LogSbieMessage(MsgCode, MsgData, ProcessId); return true; } CBoxedProcessPtr CSbieAPI::OnProcessBoxed(quint32 ProcessId, const QString& Path, const QString& Box, quint32 ParentId, const QString& CmdLine) { CBoxedProcessPtr pProcess = m_BoxedProxesses.value(ProcessId); if (!pProcess) { CSandBoxPtr pBox = GetBoxByName(Box); if (!pBox) return CBoxedProcessPtr(); if (pBox->m_ActiveProcessCount == 0) { pBox->m_ActiveProcessCount = 1; pBox->OpenBox(); m_bBoxesDirty = true; emit BoxOpened(pBox); } pProcess = CBoxedProcessPtr(NewBoxedProcess(ProcessId, pBox.data())); pBox->m_ProcessList.insert(ProcessId, pProcess); m_BoxedProxesses.insert(ProcessId, pProcess); pBox->m_ActiveProcessDirty = true; UpdateProcessInfo(pProcess); pProcess->InitProcessInfo(); } if(pProcess->m_ImageName.isEmpty()) pProcess->m_ImageName = Path.mid(Path.lastIndexOf("\\") + 1); if (pProcess->m_ParendPID == 0) pProcess->m_ParendPID = ParentId; if (pProcess->m_ImagePath.isEmpty()) pProcess->m_ImagePath = Path; if (pProcess->m_CommandLine.isEmpty() && !CmdLine.isEmpty()) pProcess->m_CommandLine = CmdLine; return pProcess; } /////////////////////////////////////////////////////////////////////////////// // Forced Processes // SB_STATUS CSbieAPI__ForceProcessControl(SSbieAPI* m, ULONG *NewState, ULONG *OldState) { __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_DISABLE_FORCE_PROCESS_ARGS* args = (API_DISABLE_FORCE_PROCESS_ARGS*)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_DISABLE_FORCE_PROCESS; args->set_flag.val = NewState; args->get_flag.val = OldState; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) return SB_ERR(status); return SB_OK; } SB_STATUS CSbieAPI::DisableForceProcess(bool Set, int Seconds) { if(Seconds > 0) m_pGlobalSection->SetNum("ForceDisableSeconds", Seconds); ULONG uNewState = Set ? TRUE : FALSE; return CSbieAPI__ForceProcessControl(m, &uNewState, NULL); } bool CSbieAPI::AreForceProcessDisabled() { ULONG uOldState = FALSE; CSbieAPI__ForceProcessControl(m, NULL, &uOldState); return uOldState != FALSE; } /////////////////////////////////////////////////////////////////////////////// // Mount Manager // SB_STATUS CSbieAPI::ImBoxCreate(CSandBox* pBox, quint64 uSizeKb, const QString& Password) { std::wstring file_root = L"\\??\\" + pBox->GetFileRoot().toStdWString(); std::wstring password = Password.toStdWString(); if(password.length() > 128) return SB_ERR(ERROR_INVALID_PARAMETER); ULONG req_len = sizeof(IMBOX_CREATE_REQ); req_len += file_root.length() * sizeof(wchar_t); SScoped req(malloc(req_len)); req->h.length = req_len; req->h.msgid = MSGID_IMBOX_CREATE; req->image_size = uSizeKb; wcscpy(req->password, password.c_str()); wcscpy(req->file_root, file_root.c_str()); SScoped rpl; SB_STATUS Status = CallServer(&req->h, &rpl); m_bBoxesDirty = true; if (!Status) return Status; if (!rpl) return SB_ERR(ERROR_SERVER_DISABLED); if (rpl->h.status != 0) return SB_ERR(rpl->h.status); return SB_OK; } SB_STATUS CSbieAPI::ImBoxMount(CSandBox* pBox, const QString& Password, bool bProtect, bool bAutoUnmount) { std::wstring root = pBox->GetRegRoot().toStdWString(); if(root.length() >= MAX_REG_ROOT_LEN) return SB_ERR(ERROR_INVALID_PARAMETER); std::wstring file_root = L"\\??\\" + pBox->GetFileRoot().toStdWString(); std::wstring password = Password.toStdWString(); if(password.length() > 128) return SB_ERR(ERROR_INVALID_PARAMETER); ULONG req_len = sizeof(IMBOX_MOUNT_REQ); req_len += file_root.length() * sizeof(wchar_t); SScoped req(malloc(req_len)); req->h.length = req_len; req->h.msgid = MSGID_IMBOX_MOUNT; wcscpy(req->password, password.c_str()); req->protect_root = bProtect; req->auto_unmount = bAutoUnmount; wcscpy(req->reg_root, root.c_str()); wcscpy(req->file_root, file_root.c_str()); SScoped rpl; SB_STATUS Status = CallServer(&req->h, &rpl); m_bBoxesDirty = true; if (!Status) return Status; if (!rpl) return SB_ERR(ERROR_SERVER_DISABLED); if (rpl->h.status != 0) return SB_ERR(rpl->h.status); return SB_OK; } SB_STATUS CSbieAPI::ImBoxUnmount(CSandBox* pBox) { std::wstring root = pBox->GetRegRoot().toStdWString(); IMBOX_UNMOUNT_REQ req; req.h.length = sizeof(IMBOX_UNMOUNT_REQ); req.h.msgid = MSGID_IMBOX_UNMOUNT; wcscpy(req.reg_root, root.c_str()); SScoped rpl; SB_STATUS Status = CallServer(&req.h, &rpl); m_bBoxesDirty = true; if (!Status) return Status; if (!rpl) return SB_ERR(ERROR_SERVER_DISABLED); if (rpl->h.status != 0) return SB_ERR(rpl->h.status); return SB_OK; } SB_RESULT(QStringList) CSbieAPI::ImBoxEnum() { IMBOX_ENUM_REQ req; req.h.length = sizeof(IMBOX_ENUM_REQ); req.h.msgid = MSGID_IMBOX_ENUM; SScoped rpl; SB_STATUS Status = CallServer(&req.h, &rpl); if (!Status) return Status; if (!rpl) return SB_ERR(ERROR_SERVER_DISABLED); if (rpl->h.status != 0) return SB_ERR(rpl->h.status); QStringList Roots; wchar_t* reg_roots = rpl->reg_roots; while (*reg_roots) { size_t len = wcslen(reg_roots); Roots.append(QString::fromWCharArray(reg_roots, len)); reg_roots += len + 1; } return CSbieResult(Roots); } SB_RESULT(QVariantMap) CSbieAPI::ImBoxQuery(const QString& Root) { std::wstring root = Root.toStdWString(); IMBOX_QUERY_REQ req; req.h.length = sizeof(IMBOX_QUERY_REQ); req.h.msgid = MSGID_IMBOX_QUERY; wcscpy(req.reg_root, root.c_str()); SScoped rpl; SB_STATUS Status = CallServer(&req.h, &rpl); if (!Status) return Status; if (!rpl) return SB_ERR(ERROR_SERVER_DISABLED); if (rpl->h.status != 0) return SB_ERR(rpl->h.status); QVariantMap Info; Info["DiskSize"] = rpl->disk_size; Info["UsedSize"] = rpl->used_size; Info["DiskRoot"] = QString::fromWCharArray(rpl->disk_root); return CSbieResult(Info); } /////////////////////////////////////////////////////////////////////////////// // Monitor // SB_STATUS CSbieAPI__MonitorControl(SSbieAPI* m, ULONG *NewState, ULONG *OldState) { //ULONG Used = 0; __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_MONITOR_CONTROL_ARGS* args = (API_MONITOR_CONTROL_ARGS*)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_MONITOR_CONTROL; args->set_flag.val = NewState; args->get_flag.val = OldState; //args->get_used.val = &Used; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) return SB_ERR(status); //qDebug() << "used bytes" << (quint32)Used; return SB_OK; } SB_STATUS CSbieAPI::EnableMonitor(bool Enable) { ULONG uNewState = Enable ? TRUE : FALSE; return CSbieAPI__MonitorControl(m, &uNewState, NULL); } bool CSbieAPI::IsMonitoring() { ULONG uOldState = FALSE; CSbieAPI__MonitorControl(m, NULL, &uOldState); return uOldState != FALSE; } bool CSbieAPI::GetMonitor() { #if 0 ULONG type; ULONG pid = 0; ULONG tid = 0; wchar_t* Buffer[4 * 1024]; //ULONG RecordNum = m->lastRecordNum; __declspec(align(8)) UNICODE_STRING64 log_buffer = { 0, (USHORT)ARRAYSIZE(Buffer), (ULONG64)Buffer }; __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_MONITOR_GET_EX_ARGS* args = (API_MONITOR_GET_EX_ARGS*)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_MONITOR_GET_EX; //args->log_seq.val = &RecordNum; // set this to NULL for record clearing args->log_type.val = &type; args->log_pid.val = &pid; args->log_tid.val = &tid; args->log_data.val = &log_buffer; if (!NT_SUCCESS(m->IoControl(parms))) return false; // error or no more entries if (type == 0) // versions prior to 5.45.0 return success and type 0 when there are no more entries return false; //if (RecordNum != m->lastRecordNum + 1 && !m->clearingBuffers) // emit LogSbieMessage(0xC1020000 | 1242, QStringList() << "" << "" << "", GetCurrentProcessId()); // Monitor buffer overflow //m->lastRecordNum = RecordNum; if (m->clearingBuffers) return true; QStringList LogData; for (size_t pos = 0; pos < log_buffer.Length; ) { size_t len = wcslen((WCHAR*)(log_buffer.Buffer + pos)); LogData.append(QString::fromWCharArray((WCHAR*)(log_buffer.Buffer + pos), len)); pos += (len + 1) * sizeof(WCHAR); } CTraceEntryPtr LogEntry = CTraceEntryPtr(new CTraceEntry(0, pid, tid, type, LogData)); QMutexLocker Lock(&m_TraceMutex); m_TraceCache.append(LogEntry); return true; #else // bulk retrieval starting with build 1.6.6 if (m->traceBuffer == NULL) { m->traceBufferLen = 256 * PAGE_SIZE; m->traceBuffer = (UCHAR*)malloc(m->traceBufferLen); } ULONG buffer_len = m->traceBufferLen; UCHAR* buffer = m->traceBuffer; __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; API_MONITOR_GET2_ARGS* args = (API_MONITOR_GET2_ARGS*)parms; memset(parms, 0, sizeof(parms)); args->func_code = API_MONITOR_GET2; args->buffer_ptr.val = (WCHAR*)buffer; args->buffer_len.val = &buffer_len; NTSTATUS status = m->IoControl(parms); if (!NT_SUCCESS(status)) return false; // error or no more entries if (m->clearingBuffers) return true; for (UCHAR* ptr = buffer; *(ULONG*)ptr > 0; ) { ULONG uSize = *(ULONG*)ptr; ptr += sizeof(ULONG); LONGLONG uTimestamp = *(LONGLONG*)ptr; ptr += sizeof(LONGLONG); uSize -= sizeof(LONGLONG); ULONG uType = *(ULONG*)ptr; ptr += sizeof(ULONG); uSize -= sizeof(ULONG); ULONG uPid = *(ULONG*)ptr; ptr += sizeof(ULONG); uSize -= sizeof(ULONG); ULONG uTid = *(ULONG*)ptr; ptr += sizeof(ULONG); uSize -= sizeof(ULONG); QStringList LogData; for (; uSize > 0;) { if (*(WCHAR*)ptr == 0xFFFF) { // end of strings marker ptr += sizeof(WCHAR); uSize -= sizeof(WCHAR); break; } size_t len = wcslen((WCHAR*)ptr); QString str = QString::fromWCharArray((WCHAR*)ptr, len); ptr += (len + 1) * sizeof(WCHAR); uSize -= (len + 1) * sizeof(WCHAR); LogData.append(str); } QVector Stack; for (; uSize > 0;) { ULONG uTagID = *(ULONG*)ptr; ptr += sizeof(ULONG); uSize -= sizeof(ULONG); ULONG uTagLen = *(ULONG*)ptr; ptr += sizeof(ULONG); uSize -= sizeof(ULONG); switch (uTagID) { case 'STCK': int PtrSize = sizeof(PVOID); #ifndef _WIN64 if (IsWow64()) PtrSize = sizeof(__int64); #endif int Frames = uTagLen / PtrSize; Stack.reserve(Frames); for (int i = 0; i < Frames; i++) { quint64 Address; #ifndef _WIN64 if (PtrSize == sizeof(quint32)) Address = ((quint32*)ptr)[i]; else #endif Address = ((quint64*)ptr)[i]; if ((Address & 0x8000000000000000ull) == 0) // skip kernel addresses Stack.append(Address); } } ptr += uTagLen; uSize -= uTagLen; } CTraceEntryPtr LogEntry = CTraceEntryPtr(new CTraceEntry(FILETIME2ms(uTimestamp), uPid, uTid, uType, LogData, Stack)); QMutexLocker Lock(&m_TraceMutex); m_TraceCache.append(LogEntry); } return status == STATUS_MORE_ENTRIES; #endif } const QVector& CSbieAPI::GetTrace() { QMutexLocker Lock(&m_TraceMutex); for (int i = 0; i < m_TraceCache.count(); i++) { CTraceEntryPtr& pEntry = m_TraceCache[i]; #ifdef USE_MERGE_TRACE if (!m_TraceList.isEmpty() && m_TraceList.last()->Equals(pEntry)) { m_TraceList.last()->Merge(pEntry); continue; } #endif if (CBoxedProcessPtr proc = m_BoxedProxesses.value(pEntry->GetProcessId())) { pEntry->SetProcessName(proc->GetProcessName()); pEntry->SetBoxPtr(proc->GetBoxPtr()); QVector Stack = pEntry->GetStack(); if(!Stack.isEmpty()) proc->ResolveSymbols(Stack); } m_TraceList.append(pEntry); } m_TraceCache.clear(); return m_TraceList; } /////////////////////////////////////////////////////////////////////////////// // Other // QString CSbieAPI::GetSbieMsgStr(quint32 code, quint32 Lang) { ULONG FormatFlags = FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER; WCHAR* ret_str = NULL; if (!m->SbieMsgDll || (FormatMessage(FormatFlags, m->SbieMsgDll, code, Lang, (LPWSTR)&ret_str, 4, NULL) == 0 && FormatMessage(FormatFlags, m->SbieMsgDll, code, 1033, (LPWSTR)&ret_str, 4, NULL) == 0)) return QString("SBIE%0: %1; %2").arg(code & 0xFFFF, 4, 10); QString qStr = QString::fromWCharArray(ret_str); LocalFree(ret_str); return qStr.trimmed(); // note messages may have \r\n at the end } void CSbieAPI::LoadEventLog() { QByteArray buff; buff.resize(8 * 1024); HANDLE hEventLog = OpenEventLog(NULL, L"System"); while (hEventLog) { ULONG bytesRead, bytesNeeded; if(!ReadEventLog(hEventLog, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_FORWARDS_READ, 0, buff.data(), buff.size(), &bytesRead, &bytesNeeded)) break; EVENTLOGRECORD *rec = (EVENTLOGRECORD *)buff.data(); while (bytesRead > 0) { time_t recently = time(NULL) - 15 * 60; WCHAR *source = (WCHAR *) ((UCHAR *)rec + sizeof(EVENTLOGRECORD)); if ((time_t)rec->TimeGenerated >= recently && (wcscmp(source, SBIEDRV) == 0 || wcscmp(source, SBIESVC) == 0)) { ULONG MsgCode = rec->EventID; QStringList MsgData = QStringList() << "" << "" << ""; WCHAR *str0 = (WCHAR *)((UCHAR *)rec + rec->StringOffset); if (rec->NumStrings >= 2) { WCHAR *str1 = str0 + wcslen(str0) + 1; MsgData[1] = QString::fromWCharArray(str1); if (rec->NumStrings >= 3) { WCHAR *str2 = str1 + wcslen(str1) + 1; MsgData[2] = QString::fromWCharArray(str2); } } emit LogSbieMessage(MsgCode & 0xFFFF, MsgData, 0); } bytesRead -= rec->Length; rec = (EVENTLOGRECORD *)((UCHAR *)rec + rec->Length); } } if (hEventLog) CloseEventLog(hEventLog); } /////////////////////////////////////////////////////////////////////////////// // Updater // SB_RESULT(int) CSbieAPI::RunUpdateUtility(const QStringList& Params, quint32 Elevate, bool Wait) { if (Params.isEmpty()) return SB_ERR(ERROR_INVALID_PARAMETER); QString Command; foreach(const QString & Param, Params) { if (!Command.isEmpty()) Command += " "; Command += "\"" + Param + "\""; } ULONG cmd_len = Command.length(); ULONG req_len = sizeof(PROCESS_RUN_UPDATER_REQ) + (cmd_len + 8) * sizeof(WCHAR); SScoped req(malloc(req_len)); req->h.length = req_len; req->h.msgid = MSGID_PROCESS_RUN_UPDATER; req->elevate = Elevate; WCHAR* ptr = (WCHAR*)((ULONG_PTR)req.Value() + sizeof(PROCESS_RUN_UPDATER_REQ)); req->cmd_ofs = (ULONG)((ULONG_PTR)ptr - (ULONG_PTR)req.Value()); req->cmd_len = cmd_len; if (cmd_len) { Command.toWCharArray(ptr); ptr += cmd_len; } *ptr++ = L'\0'; SScoped rpl; SB_STATUS Status = CallServer(&req->h, &rpl); if (!Status) return Status; if (!rpl) return SB_ERR(ERROR_SERVER_DISABLED); if (rpl->h.status != 0) return SB_ERR(rpl->h.status); DWORD ExitCode = 0; if (Wait) { WaitForSingleObject((HANDLE)rpl->hProcess, INFINITE); GetExitCodeProcess((HANDLE)rpl->hProcess, &ExitCode); } CloseHandle((HANDLE)rpl->hProcess); CloseHandle((HANDLE)rpl->hThread); return CSbieResult((int)ExitCode); }