diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 6c75be691c..6128b61b7e 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -243,13 +243,18 @@ jobs: env: VERSION: ${{ env.INSO_VERSION }} + - name: Compile inso secure wrapper (Windows only) + if: runner.os == 'Windows' + shell: bash + run: ./build-inso-wrapper.sh + - name: Code-sign inso exe (Windows only) if: runner.os == 'Windows' uses: digicert/code-signing-software-trust-action@fae23a455ba4bde62b64fd7cb2f81ade788f5a95 # v1.2.1 with: simple-signing-mode: true - # If the below 2 parameters are supplied, then smctl executable is invoked to attempt the signing. - input: packages/insomnia-inso/binaries/inso.exe + # Sign the directory so both inso.exe (wrapper) and inso-node.exe (inner) are signed. + input: packages/insomnia-inso/binaries keypair-alias: ${{ secrets.DIGICERT_KEYPAIR_ALIAS }} env: SM_HOST: ${{ vars.DIGICERT_SM_HOST }} diff --git a/build-inso-wrapper.sh b/build-inso-wrapper.sh new file mode 100755 index 0000000000..afb21463f4 --- /dev/null +++ b/build-inso-wrapper.sh @@ -0,0 +1,43 @@ + +# Wraps the pkg-bundled inso.exe with a C++ launcher that applies Windows process +# mitigation policies (ProcessImageLoadPolicy PreferSystem32Images=1) to prevent +# DLL sideloading. See packages/insomnia-inso/src/cpp/ for the wrapper source. + +set -e + +VERSION=$(jq .version ./packages/insomnia-inso/package.json -rj) +echo "Starting inso secure wrapper build for version $VERSION..." +MAJOR=$(echo $VERSION | cut -d '.' -f 1) +MINOR=$(echo $VERSION | cut -d '.' -f 2) +PATCH=$(echo $VERSION | cut -d '.' -f 3 | cut -d '-' -f 1) +TAG=$(echo $VERSION | cut -d '-' -f 2) +CPP_DIR=packages/insomnia-inso/src/cpp +BINARIES_DIR=packages/insomnia-inso/binaries + +if [ -n "$TAG" ]; then + TAG="-$TAG" +fi + +# Rename the pkg-built binary so the wrapper can take the inso.exe name. +echo "Renaming inso.exe to inso-node.exe..." +mv $BINARIES_DIR/inso.exe $BINARIES_DIR/inso-node.dll + +echo "Injecting version strings..." +sed "s/__MAJOR__/$MAJOR/g" $CPP_DIR/resources.rc > $CPP_DIR/final.rc +sed -i "s/__MINOR__/$MINOR/g" $CPP_DIR/final.rc +sed -i "s/__PATCH__/$PATCH/g" $CPP_DIR/final.rc +sed -i "s/__TAG__/$TAG/g" $CPP_DIR/final.rc +sed -i "s/__YEAR__/$(date +%Y)/g" $CPP_DIR/final.rc + +echo "Compiling resources..." +windres $CPP_DIR/final.rc $CPP_DIR/res.o + +echo "Compiling inso wrapper..." +# Note: no -mwindows flag — inso is a console application. +g++ -lkernel32 -c $CPP_DIR/inso.cpp -o $CPP_DIR/inso.o + +echo "Linking inso wrapper..." +g++ -O2 -static -static-libgcc -static-libstdc++ -lwinpthread \ + $CPP_DIR/inso.o $CPP_DIR/res.o -o $BINARIES_DIR/inso.exe + +echo "Inso secure wrapper built successfully." diff --git a/packages/insomnia-inso/src/cpp/inso.cpp b/packages/insomnia-inso/src/cpp/inso.cpp new file mode 100644 index 0000000000..7171e249a2 --- /dev/null +++ b/packages/insomnia-inso/src/cpp/inso.cpp @@ -0,0 +1,95 @@ +// NOTE: The calls in this wrapper are only supported on Windows >= 8. +#define _WIN32_WINNT 0x602 + +#include +#include + +int main() { + // Apply process mitigation policies before any further DLL loading occurs. + // PreferSystem32Images is inherited by child processes, so inso-node.exe is + // also protected without needing its own policy setup. + + ::PROCESS_MITIGATION_POLICY psp = ::ProcessSignaturePolicy; + ::PROCESS_MITIGATION_POLICY pilp = ::ProcessImageLoadPolicy; + ::PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY pmbsp; + ::PROCESS_MITIGATION_IMAGE_LOAD_POLICY pmilp; + + if (!::GetProcessMitigationPolicy(::GetCurrentProcess(), psp, &pmbsp, sizeof(pmbsp))) { + ::WriteFile(::GetStdHandle(STD_ERROR_HANDLE), + "inso: could not get ProcessSignaturePolicy\n", 44, NULL, NULL); + return 1; + } + if (pmbsp.MitigationOptIn == 0) { + pmbsp.MitigationOptIn = 1; + if (!::SetProcessMitigationPolicy(psp, &pmbsp, sizeof(pmbsp))) { + ::WriteFile(::GetStdHandle(STD_ERROR_HANDLE), + "inso: could not set ProcessSignaturePolicy\n", 44, NULL, NULL); + return 1; + } + } + + if (!::GetProcessMitigationPolicy(::GetCurrentProcess(), pilp, &pmilp, sizeof(pmilp))) { + ::WriteFile(::GetStdHandle(STD_ERROR_HANDLE), + "inso: could not get ProcessImageLoadPolicy\n", 44, NULL, NULL); + return 1; + } + if (pmilp.PreferSystem32Images == 0) { + pmilp.PreferSystem32Images = 1; + if (!::SetProcessMitigationPolicy(pilp, &pmilp, sizeof(pmilp))) { + ::WriteFile(::GetStdHandle(STD_ERROR_HANDLE), + "inso: could not set ProcessImageLoadPolicy\n", 44, NULL, NULL); + return 1; + } + } + + // Resolve the path to inso-node.exe (same directory as this wrapper). + wchar_t wrapperPath[MAX_PATH]; + ::GetModuleFileNameW(NULL, wrapperPath, MAX_PATH); + std::wstring dir(wrapperPath); + dir = dir.substr(0, dir.find_last_of(L"\\")); + std::wstring innerExe = dir + L"\\inso-node.dll"; + + // Rebuild the command line, replacing argv[0] (this wrapper) with inso-node.exe. + // GetCommandLineW() returns the full command line including the wrapper's path, + // which may be quoted. We skip past that first token to get the remaining args. + LPWSTR origCmd = ::GetCommandLineW(); + LPWSTR argsStart = origCmd; + + if (*argsStart == L'"') { + argsStart++; // skip opening quote + while (*argsStart && *argsStart != L'"') argsStart++; + if (*argsStart == L'"') argsStart++; // skip closing quote + } else { + while (*argsStart && *argsStart != L' ') argsStart++; + } + + // argsStart now points to " arg1 arg2 ..." or an empty string. + std::wstring cmdLine = L"\"" + innerExe + L"\"" + std::wstring(argsStart); + + ::STARTUPINFOW si; + ::PROCESS_INFORMATION pi; + ::ZeroMemory(&si, sizeof(si)); + ::ZeroMemory(&pi, sizeof(pi)); + + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE); + si.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE); + si.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); + + if (!::CreateProcessW(NULL, &cmdLine[0], NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { + const char *msg = "inso: could not launch inso-node.dll\n"; + ::WriteFile(::GetStdHandle(STD_ERROR_HANDLE), msg, 37, NULL, NULL); + return 1; + } + + ::WaitForSingleObject(pi.hProcess, INFINITE); + + DWORD exitCode = 1; + ::GetExitCodeProcess(pi.hProcess, &exitCode); + + ::CloseHandle(pi.hProcess); + ::CloseHandle(pi.hThread); + + return static_cast(exitCode); +} diff --git a/packages/insomnia-inso/src/cpp/manifest.txt b/packages/insomnia-inso/src/cpp/manifest.txt new file mode 100644 index 0000000000..d692363d8d --- /dev/null +++ b/packages/insomnia-inso/src/cpp/manifest.txt @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/insomnia-inso/src/cpp/resource.h b/packages/insomnia-inso/src/cpp/resource.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/insomnia-inso/src/cpp/resources.rc b/packages/insomnia-inso/src/cpp/resources.rc new file mode 100644 index 0000000000..d7d94f8f4f --- /dev/null +++ b/packages/insomnia-inso/src/cpp/resources.rc @@ -0,0 +1,29 @@ +#include "resource.h" +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +1 VERSIONINFO + +FILEVERSION __MAJOR__,__MINOR__,__PATCH__,0 +PRODUCTVERSION __MAJOR__,__MINOR__,__PATCH__,0 +FILEOS 0x40004 +FILETYPE 0x1 +{ +BLOCK "StringFileInfo" +{ + BLOCK "040904B0" + { + VALUE "CompanyName", "Kong" + VALUE "FileDescription", "inso" + VALUE "FileVersion", "__MAJOR__.__MINOR__.__PATCH____TAG__" + VALUE "InternalName", "inso" + VALUE "LegalCopyright", "Copyright \xA9 __YEAR__ Kong" + VALUE "OriginalFilename", "" + VALUE "ProductName", "inso" + VALUE "ProductVersion", "__MAJOR__.__MINOR__.__PATCH__.0" + } +} +BLOCK "VarFileInfo" +{ + VALUE "Translation", 0x0409, 0x04B0 +} +} +1 MANIFEST "manifest.txt" diff --git a/packages/insomnia-inso/src/scripts/artifacts.ts b/packages/insomnia-inso/src/scripts/artifacts.ts index 6a64f05bf4..a437c4c0e2 100644 --- a/packages/insomnia-inso/src/scripts/artifacts.ts +++ b/packages/insomnia-inso/src/scripts/artifacts.ts @@ -24,7 +24,7 @@ const spawnCompressProcess = (cwd: ProcessEnvOptions['cwd']) => { '../binaries', platform === 'win32' ? '-a -cf' : '-cJf', platform === 'win32' ? `inso-windows-${version}.zip` : `inso-linux-${process.arch}-${version}.tar.xz`, - platform === 'win32' ? 'inso.exe' : 'inso', + ...(platform === 'win32' ? ['inso.exe', 'inso-node.dll'] : ['inso']), ], { cwd, shell: platform === 'win32' }, );