Back in Black: FIN7’s Flagship Carbanak Malware Reappears After Long Hiatus

October 10, 2023


DeepSeas has identified new samples of the Russian, financially motivated cyber criminal group FIN7’s former flagship backdoor malware, Carbanak. The malware, which has not been seen for several years, reemerged in the last few weeks as a new variant that is being uploaded to public malware repositories.

In the past, Carbanak has primarily been used by the APT group FIN7 for espionage and data exfiltration. FIN7 is known to target financial organizations, and it has used Carbanak in previous campaigns to conduct financial fraud and theft. In May 2019, the source code for Carbanak was reportedly leaked. However, based on threat hunting and retroactive analysis of submissions to public malware repositories, this leak did not lead to widespread adoption by low-skilled criminals.

Present Day

DeepSeas has not observed any detections in public malware repositories in the past three years. However, a recent surge in public submissions to public malware repositories is cause for concern. On VirusTotal alone, eight new examples of Carbanak have been uploaded, with the earliest being in late July 2023. Prior to this date, the earliest example of Carbanak available on VirusTotal was in March 2020.

DeepSeas cannot directly attribute the new malware samples to the FIN7 group. It is unclear whether this is the work of FIN7, another criminal group, or information security professionals testing the codebase. However, DeepSeas has observed several key changes in the malware code which make it likely that it is being used with malicious intent. DeepSeas has developed and deployed the following detection logic to properly identify, classify, and block this threat.

Detection Logic

Below is a Yara rule created by DeepSeas to detect the string decoding and function hashing algorithms.

rule Production_Categorization_Malware_Carbanak_Decoding_Functions

        impact = "100"
        author = "DeepSeas"
        description = "Carbanak API Hashing and String Decoding"
        hash = "460842d20206c6e7709d28b0bb5d31b326f9af0596e9f76e3cfd017e939c9aee"
        reference = ""
        malpedia = ""
        uint CalcHash( const byte* ptr, int c_ptr )
            uint hash = 0;
            if( ptr && c_ptr > 0 )
                for( int i = 0; i < c_ptr; i++, ptr++ )
                    hash = (hash << 4) + *ptr;
                    unsigned t;
                    if( (t = hash & 0xf0000000) != 0)
                        hash = ((hash ^ (t >> 24)) & (0x0fffffff));
            return hash;

        0x18000e06c      4533c0                 xor     r8d, r8d
        0x18000e06f      4885c9                 test    rcx, rcx ; arg1
        0x18000e072      7431                   je      0x18000e0a5
        0x18000e074      83fa01                 cmp     edx, 1 ; 1 ; arg2
        0x18000e077      7c2c                   jl      0x18000e0a5
        0x18000e079      448bca                 mov     r9d, edx ; arg2
        0x18000e07c      0fb601                 movzx   eax, byte [rcx] ; arg1
        0x18000e07f      41c1e004               shl     r8d, 4
        0x18000e083      4403c0                 add     r8d, eax
        0x18000e086      418bc0                 mov     eax, r8d
        0x18000e089      25000000f0             and     eax, 0xf0000000
        0x18000e08e      740d                   je      0x18000e09d
        0x18000e090      c1e818                 shr     eax, 0x18
        0x18000e093      4433c0                 xor     r8d, eax
        0x18000e096      4181e0ffffff0f         and     r8d, 0xfffffff
        0x18000e09d      48ffc1                 inc     rcx ; arg1
        0x18000e0a0      49ffc9                 dec     r9
        $CalcHash_64 = {4533c0 4885c9 7??? 83fa01 7??? 448bca 0fb601 41c1e004 4403c0 418bc0 25000000f0 7??? c1e818 4433c0 4181e0ffffff0f 48ffc1 49ffc9}

        0x0040f3a7      33c9                   xor     ecx, ecx
        0x0040f3a9      85f6                   test    esi, esi
        0x0040f3ab      7427                   je      0x40f3d4
        0x0040f3ad      8b550c                 mov     edx, dword [arg_8h]
        0x0040f3b0      85d2                   test    edx, edx
        0x0040f3b2      7e20                   jle     0x40f3d4
        0x0040f3b4      0fb606                 movzx   eax, byte [esi]
        0x0040f3b7      c1e104                 shl     ecx, 4
        0x0040f3ba      03c8                   add     ecx, eax
        0x0040f3bc      8bc1                   mov     eax, ecx
        0x0040f3be      25000000f0             and     eax, 0xf0000000
        0x0040f3c3      740b                   je      0x40f3d0
        0x0040f3c5      c1e818                 shr     eax, 0x18
        0x0040f3c8      33c8                   xor     ecx, eax
        0x0040f3ca      81e1ffffff0f           and     ecx, 0xfffffff
        0x0040f3d0      46                     inc     esi
        0x0040f3d1      4a                     dec     edx
        $CalcHash_32 = {33c9 85f6 7??? 8b550c 85d2 7??? 0fb606 c1e104 03c8 8bc1 25000000f0 7??? c1e818 33c8 81e1ffffff0f 46 4a}

        StringDecoded DECODE_STRING( const char* codeStr )
            int len = Str::Len(codeStr) - CountStringOpcode;
            if( len < 0 ) len = 0;
            char* s = Str::Alloc(len + 1);
            int lenBlock = len / CountStringOpcode;
            int nb = 0;
            int rb = 0;
            int delta = 0;
            int n = 0;
            int i = 0;
            while( n < len )
                if( rb == 0 )
                    if( nb <= CountStringOpcode )
                        delta = codeStr[i] - 'a';
                        rb = lenBlock;
                        rb = len - n;
                if( rb > 0 )
                    int c = codeStr[i];
                    int min, max;
                    if( c < 32 )
                        min = 1, max = 31;
                    else if( c < 128 )
                        min = 32, max = 127;
                        min = 128, max = 255;
                    c = Config::TableDecodeString[c];
                    c -= delta;
                    if( c < min ) c = max - min + c;
                    s[n++] = c;
            s[n] = 0;
            return StringDecoded(s);

        0x1800019f5      480fbe03               movsx   rax, byte [rbx]
        0x1800019f9      ffc9                   dec     ecx
        0x1800019fb      83f820                 cmp     eax, 0x20 ; 32
        0x1800019fe      7d0b                   jge     0x180001a0b
        0x180001a00      ba1f000000             mov     edx, 0x1f ; 31
        0x180001a05      448d4ae2               lea     r9d, [rdx - 0x1e]
        0x180001a09      eb1b                   jmp     0x180001a26
        0x180001a0b      3d80000000             cmp     eax, 0x80 ; 128
        0x180001a10      7d0b                   jge     0x180001a1d
        0x180001a12      ba7f000000             mov     edx, 0x7f ; 127
        0x180001a17      448d4aa1               lea     r9d, [rdx - 0x5f]
        0x180001a1b      eb09                   jmp     0x180001a26
        0x180001a1d      baff000000             mov     edx, 0xff ; 255
        0x180001a22      448d4a81               lea     r9d, [rdx - 0x7f]
        0x180001a26      4c8d05b3e40100         lea     r8, data.18001fee0 ; 0x18001fee0
        0x180001a2d      460fbe0400             movsx   r8d, byte [rax + r8]
        0x180001a32      442bc5                 sub     r8d, ebp
        $DECODE_STRING_64 = {480fbe03 ffc9 83f820 7??? ba1f000000 448d4ae2 ???? 3d80000000 7??? ba7f000000 448d4aa1 ???? baff000000 448d4a81 4c8d ?????????? 460fbe0400 442bc5}

        0x004029cd      0fbe06                 movsx   eax, byte [esi]
        0x004029d0      49                     dec     ecx
        0x004029d1      83f820                 cmp     eax, 0x20 ; 32
        0x004029d4      7d07                   jge     0x4029dd
        0x004029d6      33db                   xor     ebx, ebx
        0x004029d8      43                     inc     ebx
        0x004029d9      6a1f                   push    0x1f ; 31
        0x004029db      eb0c                   jmp     0x4029e9
        0x004029dd      8b55e8                 mov     edx, dword [var_1ch]
        0x004029e0      3bc2                   cmp     eax, edx
        0x004029e2      7d08                   jge     0x4029ec
        0x004029e4      6a20                   push    0x20 ; 32
        0x004029e6      5b                     pop     ebx
        0x004029e7      6a7f                   push    0x7f ; 127
        0x004029e9      5a                     pop     edx
        0x004029ea      eb07                   jmp     0x4029f3
        0x004029ec      8bda                   mov     ebx, edx
        0x004029ee      baff000000             mov     edx, 0xff ; 255
        0x004029f3      0fbe80f8434200         movsx   eax, byte [eax + 0x4243f8]
        0x004029fa      2b45f8                 sub     eax, dword [var_ch]
        $DECODE_STRING_32 = {0fbe06 49 83f820 7??? 33db 43 6a1f ???? 8b55e8 3bc2 7??? 6a20 5b 6a7f 5a ???? 8bda baff000000 0fbe????????00 }

        any of them


Indicators of Compromise (IOCs)

Hashes File Name
MD5: cbda24f8ac22d68c0c3bfad37d0c2ed8SHA-1: 07fff967d4b10ebf6b6c40584a5ddb27d8ce288a

SHA-256: 59b9f82fd8e6f5aefbdd1c93d9e1d3012bbe843ddb958b1ca50c026b2217e25a

MD5: 091688921520012e70d61125c0f7c269SHA-1: 94d6b21d1b347d6d83c875c71927a6906927ebaa

SHA-256: 1b0f24eb2149bc7ba10d98651488f651e12e00956adcdb90b9775e95168b2fdd

MD5: aa9d7ce1d08ec1a4147846f91423f431SHA-1: 492d6cb1c6f30f628145a14180440dae9d8b2454

SHA-256: 91d4ae2f55f71f13fea98d23c99a6e7110d5bff0217ea195df90d6f96c46c84b

MD5: 8ae185afebe306e8f84fda01a37094d3SHA-1: 9275da21ea5255df3d22d5f8b516234088ea2703

SHA-256: e381d8d00e2d9686c5e0144bfafec980c806210e11331a0a9616c48c66667f7c

MD5: e18a9bb146ccb98e67c8cce6e69ac8b7SHA-1: 7d5df6177acfea5c572d26d0082e203719971b42

SHA-256: 460842d20206c6e7709d28b0bb5d31b326f9af0596e9f76e3cfd017e939c9aee