Группа Threat Detection and Response (TDR) компании Sekoiao обнаружила ранее не документированные методы работы хакерской группы APT28 (также известной как Fancy Bear, Forest Blizzard и др.), связанные с внедрением вредоносного кода в изображения формата PNG. Кампания направлена против украинских военных структур и использует усовершенствованные методы обфускации и легитимные облачные сервисы для скрытной передачи данных.
Описание
В начале 2025 года партнеры Sekoia.io предоставили два ранее неизвестных образца вредоносного программного обеспечения, атрибутированных APT28. Последующий анализ показал их полное соответствие образцам, описанным CERT-UA в отчете от 21 июня 2025 года, посвященном использованию фреймворков BeardShell и Covenant. Совместное исследование позволило выявить дополнительные документы Microsoft Office и методы работы, которые ранее не были публично задокументированы.
Целевыми объектами атаки стали украинские военные административные структуры. Злоумышленники использовали фишинговые документы, имитирующие служебные характеристики, акты передачи оборудования и другие документы, характерные для workflow воинских частей. Документы распространялись через приватные чаты Signal под видом сообщений от коллег или руководства, что создавало искусственное ощущение срочности и повышало вероятность успеха атаки.
Основным нововведением в данной кампании стало использование стеганографии для сокрытия вредоносной нагрузки в PNG-файлах (в частности, windows.png и koala.png). С помощью макросов в документах Word злоумышленники реализовали механизм подмены COM-объектов, что позволяло загружать вредоносную DLL-библиотеку prnfldr.dll. Данная библиотека, в свою очередь, расшифровывала и исполняла шеллкод, скрытый в наименее значимых битах пикселей изображения.
Загрузчик инициировал среду CLR для выполнения .NET-сборки, идентифицированной как HTTP Grunt Stager из фреймворка Covenant. Этот компонент использовал API облачного хранилища Koofr для организации командного канала. Аналитикам удалось получить доступ к учетным записям Koofr, связанным с кампанией, и изучить более 115 файлов, используемых для обмена данными между злоумышленниками и зараженными системами.
На последующих этапах атаки применялся бэкдор BeardShell, написанный на C++ и использующий облачный сервис icedrive в качестве канала управления. Он выполнял команды PowerShell, собирал системную информацию и обеспечивал долгосрочный доступ к системе. Дополнительно в рамках кампании был обнаружен шпионский модуль SlimAgent, ответственный за кейлоггинг и съём скриншотов.
По мнению аналитиков Sekoia.io, данная кампания демонстрирует рост технической оснащенности APT28 и её способность адаптировать открытые инструменты (такие как Covenant) в сочетании с легитимными облачными сервисами. Это позволяет группе оставаться незамеченной и эффективно обходить традиционные средства защиты.
Кампания остается активной, а её методы продолжают развиваться. В августе 2025 года наблюдалось использование аналогичной цепочки заражения через документы Excel в публичном облачном сервисе Filen.
Индикаторы компрометации
Domains
- api.icedrive.net
- app.koofr.net
- egest.filen.io
- egest.filen.net
- egest.filen-1.net
- egest.filen-2.net
- egest.filen-3.net
- egest.filen-4.net
- egest.filen-5.net
- egest.filen-6.net
- gateway.filen.io
- gateway.filen.net
- gateway.filen-1.net
- gateway.filen-2.net
- gateway.filen-3.net
- gateway.filen-4.net
- gateway.filen-5.net
- gateway.filen-6.net
- ingest.filen.io
- ingest.filen.net
- ingest.filen-1.net
- ingest.filen-2.net
- ingest.filen-3.net
- ingest.filen-4.net
- ingest.filen-5.net
- ingest.filen-6.net
MD5
- 0fbc2bf2f66fc72c521a9b8561bab1da
- 1498f1df4ca0e9cf23babe00cf34ed3d
- 2338f420d66ef191c5a419353da2c12b
- 2632fa8fc67dd2fd5c5a6275465dcc95
- 2cd2bd837e2a2554c9c34a1564388e0b
- 3b4ea6079ac9f154e0d4ec2cb6d05431
- 50199e69c6a23ce935267be72372de0a
- 5ddc34c5a9a2a1dc97c79d8777d54f14
- 608877a9e11101da53bce99b0effc75b
- 66007a1ca6d07ebb4ed85bf82e79719d
- 72c90b34fc75b251df525258c543be11
- 766a89de96c50df2e33b42f05218c22e
- 7de7febec6bed06c49efb4e2c3dd23e1
- 81159738f7ffb50d5bc3c75e5e0ac546
- 8169a4e2e826d82b57cc98bc71ea6d7e
- 82bb741aa37df26772188643bd7b3c84
- 889b83d375a0fb00670af5276816080e
- 8cb79686725831395879227658c0dd5f
- 8edc3c4868f2ef688c5250119c8aa6bb
- 8f916b6661e013ffbf318ed78e24a7c2
- 915179579ab7dc358c41ea99e4fcab52
- b52c71318815836126f1257a180a74e7
- b6e3894c17fb05db754a61ac9a0e5925
- bbfb92161cb71825a16e49e2aa4d2750
- bd76f54d26bf00686da42f3664e3f2ae
- bef42c5c079fe43c8353b24c607d9e4d
- C60991effda994e4168ec2a63406cd6a
- d802290cb9e5c3fed1ba1a8daf827882
- f21b63ddd7d2a773eb21a065015cdd01
- f442db1753a7475842607307a439870e
YARA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | rule APT_APT28_phantomnetvoxel_BeardShell: STABLE { meta: malware = "BeardShell" intrusion_set = "APT28" source = "Sekoia.io" creation_date = "2025-02-25" classification = "TLP:GREEN" hash = "5d938b4316421a2caf7e2e0121b36459" strings: $rtti1 = "@Pwrshl" $rtti2 = "$WinHttpWrapper@" $CLSID_CorRuntimeHost = {23 67 2F CB 3A AB D2 11 9C 40 00 C0 4F A3 0A 3E} $NetWkstaUserGetInfo = "NetWkstaUserGetInfo" $GetCurrentHwProfileW = "GetCurrentHwProfileW" $XOR_decryption = {50 88 54 24 07 88 4C 24 06 0F B6 44 24 06 0F B6 4C 24 07 31 C8 59 c3} condition: uint16be(0) == 0x4d5a and all of them and filesize < 4MB } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | rule APT_APT28_phantomnetvoxel_ModifiedGruntStager: RESEARCH { meta: description = "Test rule to detect NET stage based on the decryption routine. Maybe need to add some conditions" source = "Sekoia.io" creation_date = "2025-05-22" classification = "TLP:WHITE" hash = "f442db1753a7475842607307a439870e" hash = "c60991effda994e4168ec2a63406cd6a" hash = "8edc3c4868f2ef688c5250119c8aa6bb" strings: // loop used to decrypt strings. $ = { 73 ?? ?? ?? 0A 0A 16 0b 2B 25 06 02 07 91 7E ?? ?? ?? 04 07 7E ?? ?? ?? 04 6F ?? ?? ?? 0A 5D 6F ?? ?? ?? 0A 61 D2 6F ?? ?? ?? 0A 07 17 58 0B 07 02 8e 69 32 d5 06 6f ?? ?? ?? 0A 2A } condition: uint16be(0) == 0x4d5a and all of them } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | rule APT_APT28_phantomnetvoxel_SecondStageDLL_Steganography : HUNTING { meta: intrusion_set = "APT28" description = "Detects DLL based on the loop that extracts encrypted payloads from pixels" source = "Sekoia.io" creation_date = "2025-08-11" classification = "TLP:WHITE" hash = "2338f420d66ef191c5a419353da2c12b" hash = "766a89de96c50df2e33b42f05218c22e" hash = "8cb79686725831395879227658c0dd5f" hash = "d802290cb9e5c3fed1ba1a8daf827882" hash = "72c90b34fc75b251df525258c543be11" hash = "8169a4e2e826d82b57cc98bc71ea6d7e" strings: /* 0x180004dc4 41FFC1 inc r9d 0x180004dc7 4183F908 cmp r9d, 8 0x180004dcb 7CC3 jl 0x180004d90 0x180004dcd 440FB64B01 movzx r9d, byte ptr [rbx + 1] 0x180004dd2 4533D2 xor r10d, r10d 0x180004dd5 6666660F1F840000000000 nop word ptr [rax + rax] */ $ = {41 FF C1 41 83 F9 08 7C ?? 44 0F B6 4B ?? 45 33 D2 66 66 66 0F 1F 84 00} $ = "DllRegisterServer" condition: uint16be(0) == 0x4d5a and filesize < 750KB and all of them } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | rule APT_APT28_phantomnetvoxel_Beardshell_Encrypted_communications : HUNTING { meta: malware = "Beardshell" intrusion_set = "ATP28" description = "Detects format of uploaded/downloaded file of Beardshell" source = "Sekoia.io" creation_date = "2025-07-23" classification = "TLP:WHITE" strings: $TIFFHeader = {49 49 2A 00} $BMPHeader = {42 4D} $GIFHeader = {47 49 46 38 39 61} $GIFFooter = {00 3B} $JPEGHeader = {FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00} $JPEGFooter = {FF D9} $PNGHeader = {89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52} $PNGFooter = {00 00 00 00 49 45 4e 44 ae 42 60 82} condition: ($TIFFHeader at 0 and uint32(0x4) == filesize-4) or ($BMPHeader at 0 and uint32(0x2) == filesize-2) or ($GIFHeader at 0 and $GIFFooter at filesize-2 and uint32(0x6) == filesize-8) or ($JPEGHeader at 0 and $JPEGFooter at filesize-2 and uint32(0xf) == filesize-0x11) or ($PNGHeader at 0 and $PNGFooter at filesize-0xc and uint32(0x10) == filesize-0x1C) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | rule APT_APT28_phantomnetvoxel_malicious_vba_thisdocument_variant1_win : HUNTING { meta: intrusion_set = "APT28" description = "These macro are used in a lure document that drop a DLL and a PNG file once executed. The PNG conceal shellcode extracted by the DLL." source = "Sekoia.io" creation_date = "2025-07-24" classification = "TLP:WHITE" // sha256 of doc used hash = "41e116d1ee5c60dd31c2d15415f513e6c3807ca16630f7185cbb9e6a0cdbf592" hash = "89684b10d5eaa8d5c09c5a72c621acb6f8d107d5746d44bb7e9462ec6e4cf758" hash = "a610e249e3987103ebdb66ecf8198903afca93b1dcaf077fdecf80f371e9842d" hash = "ab5638089c42c6154345016962950dab8cf3092a23d5bf26ca051bbeaae8ea53" hash = "d040ce57289c3bd96b53a21cf77e411fe78b041606fb757e44d1da008a6296d7" hash = "f54af3cbf646ef363ab6b4663afd70a34e098233237d3d09bc6581342d1d0b38" strings: $ = "Private Sub Document_Open()" nocase ascii $ = "GetImageResolution(StrPtr(" nocase ascii $ = "SearchShape(" nocase ascii $ = "syswow64 = VBA.Environ(" nocase ascii $ = "GetImageResolution(StrPtr(" nocase ascii $ = "CreateImage(" nocase ascii $ = "GetRGBA(" nocase ascii $ = "Call getPixel(" nocase ascii $ = "DeleteImage(StrPtr(" nocase ascii $ = "ReadImage(" nocase ascii $ = "GetImageSize(" nocase ascii $ = "Unblur" nocase ascii condition: all of them and filesize > 15KB and filesize < 40KB } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | rule APT_APT28_phantomnetvoxel_malicious_vba_module1_variant2_win : HUNTING { meta: intrusion_set = "APT28" description = "These macro are used in a lure document that drop a DLL and a PNG file once executed. The PNG conceal shellcode extracted by the DLL." source = "Sekoia.io" creation_date = "2025-07-24" classification = "TLP:WHITE" // sha256 of doc used hash = "8f049b3a100747167eb87fb3a134e446d9057f179b4f334a5a4006369605095a" hash = "20987f7163c8fe466930ece075cd051273530dfcbe8893600fd21fcfb58b5b08" hash = "57253f322504e0a8256d46f31c19e228b8c55a14ee18e759936c71941c8ee4ad" strings: $ = "CreateObject(\"ADODB.Stream\")" nocase ascii $ = ".Open" ascii $ = ".Type" ascii $ = ".LoadFromFile" ascii $ = ".Close" ascii $ = "&H4D" ascii $ = "&H5A" ascii $ = "&H90" ascii $ = "VBA.LenB(" ascii condition: filesize < 5KB and all of them } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | rule APT_APT28_phantomnetvoxel_malicious_vba_module_variant1_win : HUNTING { meta: intrusion_set = "APT28" description = "These macro are used in a lure document that drop a DLL and a PNG file once executed. The PNG conceal shellcode extracted by the DLL." source = "Sekoia.io" creation_date = "2025-07-24" classification = "TLP:WHITE" // sha256 of doc used hash = "41e116d1ee5c60dd31c2d15415f513e6c3807ca16630f7185cbb9e6a0cdbf592" hash = "89684b10d5eaa8d5c09c5a72c621acb6f8d107d5746d44bb7e9462ec6e4cf758" hash = "a610e249e3987103ebdb66ecf8198903afca93b1dcaf077fdecf80f371e9842d" hash = "ab5638089c42c6154345016962950dab8cf3092a23d5bf26ca051bbeaae8ea53" hash = "d040ce57289c3bd96b53a21cf77e411fe78b041606fb757e44d1da008a6296d7" hash = "f54af3cbf646ef363ab6b4663afd70a34e098233237d3d09bc6581342d1d0b38" strings: $declare1 = "Public Declare PtrSafe Function" nocase ascii $declare2 = "Public Declare Function" nocase ascii $libkernel32 = "Lib \"kernel32\" Alias \"" nocase ascii $function1 = "CreateImage" nocase ascii $function2 = "ReadImag" nocase ascii $function3 = "GetImageSize" nocase ascii $function4 = "WriteImage" nocase ascii $function5 = "CloseImage" nocase ascii $function6 = "WaitForImage" nocase ascii $function7 = "DeleteImage" nocase ascii $function8 = "GetImageResolution" nocase ascii $alias1 = "CreateProcessW" nocase ascii $alias2 = "ReadFile" nocase ascii $alias3 = "GetFileSize" nocase ascii $alias4 = "WriteFile" nocase ascii $alias5 = "CloseHandle" nocase ascii $alias6 = "Sleep" nocase ascii $alias7 = "CreateFileW" nocase ascii $alias8 = "GetFileAttributesW" nocase ascii condition: filesize < 10KB and ($declare1 or $declare2) and $libkernel32 and 4 of ($function*) and 4 of ($alias*) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | rule APT_APT28_phantomnetvoxel_malicious_vba_thisdocument_variant2_win : HUNTING { meta: intrusion_set = "APT28" description = "These macro are used in a lure document that drop a DLL and a PNG file once executed. The PNG conceal shellcode extracted by the DLL." source = "Sekoia.io" creation_date = "2025-07-24" classification = "TLP:WHITE" // sha256 of doc used hash = "20987f7163c8fe466930ece075cd051273530dfcbe8893600fd21fcfb58b5b08" hash = "57253f322504e0a8256d46f31c19e228b8c55a14ee18e759936c71941c8ee4ad" strings: $ = "Private Sub Document_Open()" nocase ascii $ = "Private Sub Document_Close()" nocase ascii $ = "ThisDocument.ActiveWindow.View.Type" nocase ascii $ = "ThisDocument.name" nocase ascii $ = "VBA.Environ(" nocase ascii $ = "FileExists(" nocase ascii $ = "CreateFolder" nocase ascii $ = ".CopyFile" nocase ascii $ = "\"Temp\"" nocase ascii condition: all of them and filesize < 3KB } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | rule APT_APT28_phantomnetvoxel_malicious_vba_module2_variant2_win : HUNTING { meta: intrusion_set = "APT28" description = "These macro are used in a lure document that drop a DLL and a PNG file once executed. The PNG conceal shellcode extracted by the DLL." source = "Sekoia.io" creation_date = "2025-07-24" classification = "TLP:WHITE" // sha256 of doc used hash = "8f049b3a100747167eb87fb3a134e446d9057f179b4f334a5a4006369605095a" hash = "20987f7163c8fe466930ece075cd051273530dfcbe8893600fd21fcfb58b5b08" hash = "57253f322504e0a8256d46f31c19e228b8c55a14ee18e759936c71941c8ee4ad" strings: $declare1 = "Public Declare PtrSafe Function" nocase ascii $declare2 = "Public Declare Function" nocase ascii $lib1 = "Lib \"kernel32\" Alias \"" nocase ascii $lib2 = "Lib \"advapi32.dll\" Alias \"" nocase ascii $function1 = "CP" nocase ascii $function2 = "WFSO" nocase ascii $function3 = "IsU" nocase ascii $function4 = "RCKE" nocase ascii $function5 = "RSVE" nocase ascii $function6 = "RCK" nocase ascii $alias1 = "CreateProcessW" nocase ascii $alias2 = "WaitForSingleObject" nocase ascii $alias3 = "IsUserAnAdmin" nocase ascii $alias4 = "RegCreateKeyExW" nocase ascii $alias5 = "RegSetValueExW" nocase ascii $alias6 = "RegCloseKey" nocase ascii condition: filesize < 10KB and ($declare1 or $declare2) and ($lib1 or $lib2) and 3 of ($function*) and 3 of ($alias*) } |