GandCrab V5.2解密工具

简介

该修复报告主要针对GandCrab v5.2的两个版本进行修复的。在分析GandCrab V5.2时,偶然发现v5.2版本就目前我所发现的有两个不同的版本,修复工作也考虑到两者的差异,进行兼容两者都修复。

基本信息

两个同为V5.2版本的不同变种基本信息

样本名称 样本伪装图标 MD5
GandCrab V5.2(1) 浅色文件夹 DE46B3B7F13F12769524755BB0A105FE
GandCrab V5.2(2) Word文档 445DD888ED51E331FDCF2FA89199CCA6

GandCrab V5.2(1)和GandCrab V5.2(2)的图标如下图:

image-20191204100427506

我所发现的两个GandCrab V5.2,作者的加密RSA私钥一样,加密手法一样,大体上没有什么太大的区别,==但在一些行为上有些许的差别,这些差别也影响了修复的方式==。

GandCrab v5.2(1)与GandCrab V5.2(2)的差别

注册表差异

GandCrab V5.2(2)将随机生成的加密后缀保存到注册表:HKLM(HKCU)\SOFTWARE\\ex_data\\data中的ext中,并将加密的==密钥信息==(经过加密的用于加密本地RSA私钥的Salsa20的Key和IV,以及加密后的本地RSA私钥)保存在注册表:HkLM(HKCU)\SOFTWARE\\keys_data\\data中的private

而GandCrab V5.2(1)==并没有将加密相关的信息写入注册表==,这将严重影响修复工具获取密钥信息(经过加密的用于加密本地RSA私钥的Salsa20的Key和IV,以及加密后的本地RSA私钥)的方法,导致==无法直接读取注册表获取密钥信息。==

勒索说明差异

从文本内容看勒索说明都是一样的,唯一的差别在于勒索说明的文件名不同,V5.2(1)为MANUAL.txt,V5.2(2)为DECRYPT.txt如图:

image-20191204102908024

GandCrab V5.2(2)瑞星的详细分析报告: http://it.rising.com.cn/fanglesuo/19523.html

加解密逻辑

加密逻辑

GrandCrab

首先通过硬编码解密出Salsa20的key和IV,然后通过Salsa20解密出作者的RSA-2048的公钥(RSA2048PublicKey),然后生成本地的RSA-2048密钥对,再用随机生成的Salsa20LocRSAPriv的Key和IV去加密本地RSA的私钥(LocRSAPrivateKey),然后再用作者的RSA-2048公钥(RSA2048PublicKey)加密随机生成的Salsa20LocRSAPriv的Key和IV,并将加密后的本地RSA私钥(LocRSAPrivateKey)和加密后的Salsa20LocRSAPriv的Key和IV共同保存起来,V5.2(1)将这些密钥数据经过Base64加密保存在勒索说明中,如图:

image-20191204104848604

而V5.2(2)将这些密钥数据保存在注册表HkLM(HKCU)\SOFTWARE\\keys_data\\data中的private中。注册表中和解密后的勒索说明中的密钥数据如下图:

Key

然后在随机生成Salsa20File的Key和IV去加密文件,并用本地生成的LocRSAPublicKey去加密Salsa20File的Key和IV,并将其保存在文件末尾0x21C的位置,文件中的密钥数据布局,如下图:

image-20191204105430575

其中对加密文件时,有特殊的判断和不同的处理,作者给定了一个要加密的文件后缀表和一个不加密的文件后缀表。

  • 当文件在要加密文件后缀表中,则全文加密。
  • 当文件不在加密文件后缀表中,也不在不加密的文件后缀表中,则只加密前1M的数据,不全文加密。
  • 当文件小于1M,并且不在不加密文件后缀表中,只加密当前数据大小。

加密后缀表:

1
.1st.602.docb.xlm.xlsx.xlsm.xltx.xltm.xlsb.xla.xlam.xll.xlw.ppt.pot.pps.pptx.pptm.potx.potm.ppam.ppsx.ppsm.sldx.sldm.xps.xls.xlt._doc.dotm._docx.abw.act.adoc.aim.ans.apkg.apt.asc.asc.ascii.ase.aty.awp.awt.aww.bad.bbs.bdp.bdr.bean.bib.bib.bibtex.bml.bna.boc.brx.btd.bzabw.calca.charset.chart.chord.cnm.cod.crwl.cws.cyi.dca.dfti.dgs.diz.dne.dot.doc.docm.dotx.docx.docxml.docz.dox.dropbox.dsc.dvi.dwd.dx.dxb.dxp.eio.eit.emf.eml.emlx.emulecollection.epp.err.err.etf.etx.euc.fadein.template.faq.fbl.fcf.fdf.fdr.fds.fdt.fdx.fdxt.fft.fgs.flr.fodt.fountain.fpt.frt.fwd.fwdn.gmd.gpd.gpn.gsd.gthr.gv.hbk.hht.hs.hwp.hwp.hz.idx.iil.ipf.ipspot.jarvis.jis.jnp.joe.jp1.jrtf.jtd.kes.klg.klg.knt.kon.kwd.latex.lbt.lis.lnt.log.lp2.lst.lst.ltr.ltx.lue.luf.lwp.lxfml.lyt.lyx.man.mbox.mcw.md5.me.mell.mellel.min.mnt.msg.mw.mwd.mwp.nb.ndoc.nfo.ngloss.njx.note.notes.now.nwctxt.nwm.nwp.ocr.odif.odm.odo.odt.ofl.opeico.openbsd.ort.ott.p7s.pages.pages-tef.pdpcmd.pfx.pjt.plain.plantuml.pmo.prt.prt.psw.pu.pvj.pvm.pwd.pwdp.pwdpl.pwi.pwr.qdl.qpf.rad.readme.rft.ris.rpt.rst.rtd.rtf.rtfd.rtx.run.rvf.rzk.rzn.saf.safetext.sam.sam.save.scc.scm.scriv.scrivx.sct.scw.sdm.sdoc.sdw.se.session.sgm.sig.skcard.sla.sla.gz.smf.sms.ssa.story.strings.stw.sty.sublime-project.sublime-workspace.sxg.sxw.tab.tab.tdf.tdf.template.tex.text.textclipping.thp.tlb.tm.tmd.tmdx.tmv.tmvx.tpc.trelby.tvj.txt.u3i.unauth.unx.uof.uot.upd.utf8.utxt.vct.vnt.vw.wbk.webdoc.wn.wp.wp4.wp5.wp6.wp7.wpa.wpd.wpd.wpd.wpl.wps.wps.wpt.wpt.wpw.wri.wsd.wtt.wtx.xbdoc.xbplate.xdl.xdl.xwp.xwp.xwp.xy.xy3.xyp.xyw.zabw.zrtf.zw

不加密后缀表:

1
.ani.cab.cpl.cur.diagcab.diagpkg.dll.drv.lock.hlp.ldf.icl.icns.ico.ics.lnk.key.idx.mod.mpa.msc.msp.msstyles.msu.nomedia.ocx.prf.rom.rtp.scr.shs.spl.sys.theme.themepack.exe.bat.cmd.gandcrab.KRAB.CRAB.zerophage_i_like_your_pictures

解密逻辑

由于GandCrab V5.2,根据我发现的两个版本的不同,进行兼容修复。

由于V5.2(2)将密钥数据写入了注册表,而V5.2(1)没有写注册表。也正因为V5.2(1)没有写注册表,所以在修复的时候需要输入一个被加密的目录来获取密钥。

在修复的时候,首先读取注册表密钥信息,当读取不到时,便会遍历当前目录去寻找勒索说明文件,并获取勒索说明中的密钥数据,并进行解密。

附加:去花脚本

GandCrab V5.2中有大量的花指令感染分析,其中每个函数入口点都有花指令,将导致IDA无法F5。我在分析时写了如下去花脚本,若需要再次分析GandCrab V5.2的样本时,可以使用。

1
2
3
4
5
6
7
8
9
10
11
12
def patch_junkcode(addr):
data = list(get_bytes(addr,28))
if((ord(data[0]) == 0xB9) and (ord(data[1]) == 0xD2) and (ord(data[2]) == 0xC3) and ord(data[3]) == 0x01 and (ord(data[5]) == 0xE8) and (ord(data[6]) == 0x0B)):
for i in range(0,28):
patch_byte(addr+i,0x90)
base = 0x401000
len = 0x411000-base
for i in range(len):
patch_junkcode(base+i)

AnalyzeArea(base, 0x411000)
print 'Finished'

解密程序源码:

由于GandCrab作者公开了密钥,因此便可以对GandCrabV5.2的勒索文件进行修复。于是本人就写了修复解密工具,代码如下:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
// Repair_GandCrabV5.2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <cryptlib.h>
#include <iostream>
#include <windows.h>
#include <wincrypt.h>
#include <salsa.h>
#include <Shlwapi.h>
#include <String>
#include <stdio.h>
#include <string.h>
using namespace CryptoPP;
using namespace std;
#pragma comment(lib,"cryptlib.lib")
#pragma comment(lib,"shlwapi.lib")

BYTE AuRSAPubKey[] = {
0x07, 0x02, 0x00, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x52, 0x53,
0x41, 0x32, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
0x6D, 0xC2, 0xF3, 0x82, 0xA9, 0x7E, 0xEB, 0xC3, 0xF3, 0xFD,
0x3F, 0xF3, 0x6A, 0x48, 0x54, 0x04, 0x7B, 0xAD, 0xB5, 0x4C,
0x00, 0x1F, 0x8E, 0x81, 0xB6, 0x51, 0x4C, 0x3A, 0x76, 0x02,
0xC3, 0x45, 0x6A, 0x3A, 0xEA, 0x44, 0xAC, 0x00, 0xD8, 0xE4,
0xAD, 0xBE, 0x42, 0xC5, 0xDC, 0xB6, 0xCD, 0x6F, 0x5F, 0xDF,
0xEB, 0xEE, 0xD1, 0x3B, 0xAC, 0xA2, 0xA5, 0x1F, 0xC1, 0xDE,
0x8C, 0xCB, 0xCC, 0x09, 0xD0, 0x81, 0x1D, 0xD0, 0xFA, 0x49,
0xD1, 0x5D, 0x48, 0xB0, 0x71, 0x39, 0xD3, 0x8F, 0xB3, 0xA4,
0x6D, 0xE8, 0x2F, 0xC8, 0x0C, 0x4B, 0xB4, 0xCF, 0xAF, 0x74,
0x2F, 0x4C, 0xC2, 0xCC, 0x74, 0xC4, 0xCA, 0x1F, 0xFF, 0x1F,
0xD8, 0xB0, 0xC2, 0x8D, 0xF0, 0xDF, 0x6A, 0x4F, 0xD9, 0xCF,
0x3F, 0xED, 0xCE, 0x6F, 0x34, 0x28, 0x9D, 0x9E, 0x39, 0x0D,
0x63, 0x4F, 0x1F, 0xB4, 0x06, 0x7F, 0xA3, 0xF9, 0x8D, 0x0B,
0xD0, 0xAC, 0xE8, 0x89, 0x52, 0x76, 0x2A, 0x91, 0x5B, 0x0B,
0x91, 0xB2, 0xD9, 0xB9, 0x23, 0x60, 0xF2, 0xEA, 0x91, 0x9E,
0x93, 0xC7, 0x83, 0x4D, 0xEF, 0x2E, 0x9E, 0x91, 0x69, 0x1C,
0x2D, 0x33, 0x2D, 0xC9, 0x38, 0x08, 0xA8, 0x87, 0x21, 0x88,
0x90, 0x78, 0x03, 0xEE, 0xF1, 0x12, 0xD2, 0x24, 0x97, 0xCD,
0x31, 0xE5, 0x2C, 0x84, 0xE0, 0x74, 0xEC, 0xC8, 0x6F, 0xC7,
0xF6, 0x32, 0x0A, 0x42, 0xE6, 0x27, 0x51, 0xEB, 0x59, 0x44,
0x46, 0x08, 0x6A, 0x0B, 0x1C, 0xAC, 0x9B, 0x5F, 0x60, 0xB8,
0xDE, 0x89, 0x90, 0xB3, 0xC1, 0xD9, 0x6A, 0x6F, 0x42, 0xA3,
0x03, 0xB5, 0x62, 0xF4, 0x16, 0x39, 0x8D, 0xC8, 0xB9, 0x6C,
0x48, 0xEB, 0x18, 0xA0, 0x4E, 0x79, 0x50, 0x5E, 0x2F, 0x56,
0xAD, 0x28, 0xEA, 0x85, 0x95, 0xD8, 0x01, 0xED, 0x6C, 0x6C,
0xE1, 0x29, 0x06, 0xC3, 0x82, 0xDB, 0xD3, 0x10, 0xAC, 0xD1,
0x65, 0x4C, 0x24, 0x90, 0x56, 0x14, 0x96, 0x25, 0x30, 0xDA,
0x92, 0x6C, 0x04, 0xF0, 0x98, 0xA7, 0x6B, 0x62, 0x1A, 0x66,
0xE0, 0x83, 0xC0, 0x21, 0x26, 0xB7, 0x12, 0x17, 0xCA, 0x42,
0x31, 0xC1, 0xF6, 0xF3, 0x4A, 0x47, 0x23, 0xE6, 0xFB, 0xDC,
0xC2, 0x9B, 0x0F, 0xCD, 0xCF, 0x58, 0x03, 0x06, 0x56, 0x31,
0x19, 0xE4, 0x7D, 0x1B, 0x0B, 0xDD, 0xFD, 0x99, 0xF3, 0x55,
0xAE, 0xCE, 0x29, 0x26, 0xA8, 0x39, 0xD4, 0xCE, 0xB8, 0xF1,
0x10, 0x91, 0x84, 0xC5, 0x3C, 0x9D, 0x8B, 0xE6, 0xCD, 0x90,
0xBF, 0xB5, 0x32, 0x2B, 0x73, 0xB3, 0x4B, 0x95, 0x5A, 0xDD,
0xEA, 0xFE, 0xDB, 0x40, 0xC1, 0xD8, 0xD5, 0xA6, 0xFC, 0x57,
0x95, 0xCD, 0x72, 0xF0, 0x76, 0x1C, 0x04, 0x23, 0xCA, 0xCC,
0xFA, 0xBA, 0xA5, 0xBA, 0x69, 0x8C, 0x05, 0x65, 0x09, 0xC0,
0xF3, 0x75, 0x40, 0xFD, 0xBF, 0xD7, 0x86, 0x51, 0x8D, 0xA2,
0x85, 0x64, 0x53, 0xC1, 0xC9, 0x88, 0x43, 0x56, 0x54, 0x95,
0x0D, 0x14, 0x91, 0x79, 0x76, 0x74, 0xC6, 0x18, 0xF3, 0xF5,
0x98, 0x16, 0x18, 0xD2, 0x42, 0x03, 0xDE, 0x0D, 0xF2, 0x55,
0x5B, 0x51, 0xB6, 0x76, 0x34, 0x74, 0xEB, 0xEF, 0x11, 0x98,
0xF6, 0x6A, 0x2F, 0x22, 0x5D, 0x7E, 0xCD, 0xD7, 0xC5, 0x5E,
0xF4, 0xEF, 0xF8, 0xA9, 0xBB, 0xDA, 0x6F, 0xB1, 0x90, 0x74,
0x87, 0x7B, 0xF2, 0x11, 0xAF, 0x60, 0xEB, 0x2C, 0xC4, 0x46,
0xB5, 0x30, 0xE3, 0xC2, 0xBB, 0x5A, 0x17, 0x07, 0x4D, 0x80,
0x33, 0x61, 0x35, 0x10, 0x01, 0x4E, 0x5B, 0x13, 0x73, 0x5B,
0x12, 0x9F, 0x7A, 0x44, 0xEE, 0x8C, 0x0A, 0xFF, 0xC1, 0xCC,
0xBD, 0x01, 0xBC, 0x2B, 0x76, 0x1F, 0x7C, 0x45, 0x8D, 0x1B,
0xD2, 0x1E, 0x0E, 0x80, 0xD2, 0x14, 0x28, 0x22, 0x59, 0x91,
0xE4, 0xDD, 0xB7, 0x33, 0x05, 0x90, 0x01, 0xF5, 0x0A, 0x93,
0x23, 0x7C, 0xC5, 0xDE, 0xCC, 0x7F, 0x95, 0x24, 0x34, 0xAC,
0xC0, 0x0A, 0x06, 0xC4, 0x95, 0x0D, 0x6D, 0x66, 0x26, 0xCE,
0x95, 0x37, 0x98, 0x5C, 0xE6, 0x84, 0xBB, 0xF0, 0xB4, 0x0D,
0x28, 0xF9, 0xA2, 0x64, 0x6A, 0x9C, 0x0F, 0x66, 0x6D, 0x40,
0xEB, 0xCD, 0x59, 0x0B, 0xD9, 0xFD, 0xB5, 0xF9, 0x44, 0x20,
0x9C, 0xA3, 0x7C, 0x7B, 0x92, 0x0E, 0xFD, 0xD9, 0x20, 0x2C,
0x5F, 0x44, 0xC8, 0xEC, 0x84, 0xBE, 0x62, 0xE9, 0xEB, 0x78,
0x00, 0x7B, 0xC6, 0x4A, 0x5B, 0xB3, 0x47, 0xE1, 0x01, 0x62,
0x4D, 0xCC, 0xD2, 0x91, 0x91, 0x1B, 0xB6, 0x21, 0x63, 0xAD,
0x2F, 0xB7, 0x8F, 0x79, 0xD9, 0x86, 0xA2, 0x21, 0xF8, 0x8E,
0xBA, 0xE3, 0x92, 0x1D, 0xBA, 0xAC, 0xC0, 0xAF, 0x7D, 0xB3,
0x3F, 0x1B, 0xFC, 0x38, 0x27, 0x87, 0x54, 0x85, 0x25, 0xE5,
0x53, 0x61, 0x1D, 0x9E, 0x84, 0xFA, 0x41, 0x4D, 0x0E, 0x5A,
0x54, 0xA7, 0x0B, 0x0C, 0x3F, 0x95, 0xBB, 0xE4, 0xCA, 0x5A,
0x5D, 0x77, 0xA8, 0xF9, 0x91, 0x81, 0xFA, 0x4E, 0x75, 0x44,
0xCD, 0x86, 0xE0, 0x84, 0xEC, 0x1C, 0x7A, 0x17, 0x23, 0xB4,
0x0A, 0x16, 0x10, 0x21, 0xFD, 0xAF, 0x8B, 0xC5, 0xF6, 0x9A,
0xD1, 0x55, 0xF6, 0x17, 0xCA, 0x9B, 0xBF, 0xA9, 0xB2, 0x25,
0x8E, 0x90, 0x8D, 0x99, 0x1B, 0x26, 0xAD, 0xA6, 0x03, 0x91,
0x7F, 0xB4, 0xB9, 0xD4, 0xD1, 0xF4, 0x82, 0xD3, 0x2A, 0xB6,
0x6B, 0x8B, 0x8C, 0x55, 0x78, 0x35, 0x9B, 0x4F, 0x40, 0x69,
0x96, 0x54, 0x33, 0x0A, 0x10, 0x9D, 0xE8, 0xA5, 0x2B, 0xBF,
0x9D, 0xBA, 0x25, 0x9B, 0x98, 0x7A, 0x69, 0x55, 0xC5, 0x2F,
0xFA, 0xFB, 0xF9, 0x8E, 0x2D, 0x43, 0xF9, 0x44, 0x8E, 0xA7,
0x51, 0x68, 0xBF, 0xA1, 0x29, 0x52, 0xF2, 0x64, 0x20, 0xC1,
0x8C, 0x74, 0x6B, 0x62, 0x9F, 0x93, 0x51, 0xC6, 0x30, 0x88,
0xEB, 0x18, 0xED, 0x84, 0x42, 0x4B, 0x72, 0xEC, 0xB8, 0x85,
0x31, 0xFF, 0x44, 0xD7, 0x29, 0xEA, 0x2D, 0xA0, 0xF8, 0x53,
0x3F, 0xB1, 0xCD, 0xA8, 0x58, 0xDD, 0x4C, 0x0F, 0x3F, 0xD0,
0xDD, 0xAA, 0x43, 0x31, 0x67, 0x77, 0x85, 0x79, 0xB7, 0x54,
0xFA, 0xEC, 0x54, 0x77, 0x4C, 0x06, 0xA9, 0x71, 0x59, 0x44,
0x25, 0xDF, 0x5A, 0x01, 0x08, 0x25, 0xEE, 0x73, 0xE3, 0x74,
0xD7, 0x63, 0x77, 0xD2, 0x3D, 0x74, 0x0D, 0xE4, 0xAE, 0x18,
0x49, 0x4F, 0xB9, 0x41, 0x14, 0x3B, 0xA4, 0x7F, 0x8C, 0x5E,
0x99, 0x3C, 0x83, 0x0F, 0xDD, 0x2A, 0xDE, 0x2F, 0xEF, 0xE7,
0xB2, 0x30, 0x6A, 0xB1, 0x94, 0xBB, 0x6E, 0x89, 0xDD, 0xAE,
0xA2, 0xF6, 0x1B, 0x42, 0xEF, 0xBC, 0x96, 0x07, 0x5A, 0xD9,
0xF7, 0xF6, 0x8F, 0x44, 0x51, 0x2E, 0xED, 0x1B, 0xF9, 0x5B,
0xA8, 0x5A, 0x4C, 0x1E, 0x51, 0x2C, 0x68, 0x34, 0x81, 0xDA,
0x5E, 0x17, 0x27, 0xB0, 0xBB, 0x38, 0xDF, 0x67, 0x80, 0xDB,
0x0F, 0x2D, 0xC5, 0x81, 0x48, 0xD7, 0xEB, 0x94, 0x28, 0xAD,
0x5E, 0x72, 0xFD, 0x5B, 0xA2, 0x59, 0x2F, 0x91, 0xA8, 0xB6,
0x8A, 0x9E, 0x77, 0x29, 0x59, 0x96, 0xF7, 0xB8, 0x39, 0xF9,
0xF6, 0x53, 0x5E, 0x03, 0x44, 0x3D, 0x1E, 0xEF, 0x74, 0xB7,
0x95, 0x89, 0x8B, 0xA5, 0x0F, 0x84, 0x59, 0x20, 0xDB, 0xD9,
0x99, 0x02, 0xE0, 0x65, 0x0B, 0xCF, 0x6D, 0x2F, 0x9C, 0xC0,
0xC2, 0x4A, 0x80, 0x4E, 0xD8, 0x9D, 0x14, 0x10, 0x4E, 0xBF,
0x41, 0x51, 0xBF, 0xB5, 0xDF, 0xD3, 0xD0, 0x63, 0x9C, 0x3A,
0x3E, 0x84, 0x64, 0xA5, 0x35, 0x1D, 0xA0, 0x74, 0x9D, 0x7D,
0xEF, 0x6A, 0x6A, 0x24, 0xEC, 0xDE, 0xFC, 0xB7, 0xB8, 0x21,
0xED, 0xE2, 0x64, 0x79, 0x38, 0x36, 0x79, 0xCF, 0x6E, 0x5B,
0xDB, 0x6D, 0xB7, 0xC0, 0x75, 0x55, 0x6E, 0x17, 0x53, 0x5F,
0x34, 0xD9, 0x8F, 0x51, 0x81, 0x9B, 0xC6, 0xF4, 0xAB, 0xE3,
0xE9, 0x48, 0x2A, 0x6E, 0xFE, 0x5D, 0x88, 0x7D, 0xF7, 0x6F,
0xE3, 0x4A, 0x5B, 0xEE, 0xE7, 0xE4, 0xD8, 0x82, 0xC6, 0x7E,
0xDB, 0xCF, 0x8D, 0x95, 0x5B, 0xC0, 0x13, 0x63, 0x00, 0xF5,
0x57, 0x2C, 0xF0, 0xF2, 0x6C, 0x2C, 0x6D, 0x03, 0x6C, 0x69,
0x93, 0x88, 0x1B, 0xCB, 0xF0, 0xCD, 0xF5, 0xC0, 0x4E, 0x06,
0xE1, 0x10, 0x30, 0xC8, 0x92, 0x9E, 0x58, 0xD0, 0xEE, 0xC7,
0xFA, 0x40, 0x16, 0x92, 0x25, 0xAF, 0xEF, 0x82, 0xFC, 0xBB,
0xED, 0x5C, 0xA2, 0x33, 0xC9, 0x09, 0xC4, 0xA0, 0x63, 0x5C,
0xEA, 0xD4, 0x94, 0x6C, 0xFE, 0xE0, 0xB8, 0xBC, 0x79, 0x47,
0x66, 0x61, 0x3E, 0x96, 0xF0, 0x37, 0x2E, 0xB6, 0x3E, 0x49,
0x98, 0xC0
};
BYTE LocRSAPriv[0x500] = { 0 };
enum Base64Option {
Base64Encoding = 0,
Base64UrlEncoding = 1,

KeepTrailingEquals = 0,
OmitTrailingEquals = 2
};
const char* base64char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

inline int num_strchr(const char* str, char c);
BOOL ReadKey(char* filePath, char* encodedKey);
void RepairFile(char* filePath, char* oldFileName);
BOOL DecodeSalsaKey(BYTE* KeyData, BYTE* RSAPrivKey);
BOOL JudgeTxtFile(char* filePath, char* Encodedsuffix);
void TraverseRepair(char* filePath, char* encodedSuffix);
int base64_decode(const char* base64, unsigned char* dedata);
BOOL GetFileKey(char* filePath, BYTE* KeyData, char* encodedSuffix);
BOOL JudgeEncodedFile(char* filePath, char* encodedSuffix, char* oldFileName);
BOOL RegGetKeyValue(WCHAR* Reg, WCHAR* RegKeyName, BYTE* Value, DWORD cbData);
void salsa20Decrypt(BYTE* EncodedData, DWORD SizeofEncodedData, BYTE* SALSA20KEY, BYTE* SALSA20IV, BYTE* DecodedData);
BOOL GetDecodedData(CHAR* FileName, BYTE* salsaKey, BYTE* salsaIV, BYTE fileSize[8], BYTE encodedNumber[8], BYTE* LocRSAPriv);

int main()
{
//声明
char filePath[MAX_PATH] = { 0 };
char path[MAX_PATH] = { 0 };
WCHAR regSuffix[] = L"SOFTWARE\\ex_data\\data"; //存放后缀名的注册表
WCHAR regSuffixKeyName[] = L"ext";
WCHAR regKeyData[] = L"SOFTWARE\\keys_data\\data"; //存放加密本地RSA私钥的Salsa的Key和IV和LocRSAPriv
WCHAR regKeyDataName[] = L"private";
char suffix[50] = { 0 };
WCHAR wSuffix[50] = { 0 };
BYTE keyData[1700] = { 0 };
BOOL resultFileKey;

printf("***GandCrab V5.2修复工具***\n");
printf("===========================\n");
printf("请输入要修复的文件目录:");
gets_s(filePath, MAX_PATH );
lstrcpyA(path,filePath);

BOOL suffixResult = RegGetKeyValue(regSuffix, regSuffixKeyName, (BYTE*)suffix, 50); //读取注册表,获取到加密后缀名
int lenOfSuffix = lstrlenW((WCHAR*)suffix);
lstrcpyW(wSuffix, (WCHAR*)suffix);
BOOL keyDataResult = RegGetKeyValue(regKeyData, regKeyDataName, keyData, 1688); //读取注册表,获取加密本地RSA私钥的Salsa的Key和IV和LocRSAPriv
WideCharToMultiByte(CP_OEMCP, 0, wSuffix, -1, (LPSTR)suffix, lenOfSuffix, NULL, FALSE);
suffix[lenOfSuffix] = '\0';
if (suffixResult && keyDataResult) //当获取注册表失败,便读文件获取密钥数据KeyData
{
printf("读取注册表中的密钥数据失败!\n读取勒索说明中的密钥数据!\n");
if (!GetFileKey(filePath, keyData, suffix)) //读取当前目录勒索信息中的密钥信息
{
printf("获取本地密钥数据失败!该目录无法修复(可能未被感染)!\n");
return 0;
}
}
printf("获取本地密钥信息成功!\n");
printf("=====开始修复当前目录!=====\n");
DecodeSalsaKey(keyData + 4, AuRSAPubKey); //解密用来加密本地RSA私钥的SalsaKey
DecodeSalsaKey(keyData + 0x104, AuRSAPubKey); //解密用来加密本地RSA私钥的SalsaIV
salsa20Decrypt(keyData + 0x204, 0x494, keyData + 4, keyData + 0x104, LocRSAPriv); //根据获取到的SalsaKey和SalsaIV解密本地RSA私钥
TraverseRepair(path, suffix); //遍历当前目录进行修复
printf("=====当前目录修复成功!=====\n");
getchar();
return 0;
}

//获取勒索说明文件中的密钥和后缀名
//获取成功返回1
BOOL GetFileKey(char* filePath, BYTE* KeyData,char* encodedSuffix)
{
HANDLE hFindFile;
WIN32_FIND_DATAA findFileData;
char encodedBuffer[2300] = { 0 };
char path[MAX_PATH] = { 0 };

int lenOfPath = lstrlenA(filePath);
filePath[lenOfPath] = '\\';
lstrcpyA(path, filePath);
lstrcatA(filePath, "*.*");
hFindFile = FindFirstFileA(filePath, &findFileData);
if (hFindFile != INVALID_HANDLE_VALUE)
{
do
{
if (lstrcmpA(findFileData.cFileName, ".") && lstrcmpA(findFileData.cFileName, ".."))
{
lstrcpyA(filePath, path);
lstrcatA(filePath, findFileData.cFileName);
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
GetFileKey(filePath, KeyData,encodedSuffix);
else
{

if (!JudgeTxtFile(filePath, encodedSuffix))
continue;
if (ReadKey(filePath, encodedBuffer))
{
base64_decode(encodedBuffer,KeyData);
return 1;
}
}
}
} while (FindNextFileA(hFindFile, &findFileData));
}
return 0;
}

//判断文件后缀是否为.txt,并且获得加密后缀名
//返回1,表明文件为.txt
BOOL JudgeTxtFile(char* filePath,char* Encodedsuffix)
{
LPCSTR suffix = PathFindExtensionA(filePath);
if (lstrcmpA(suffix, ".txt"))
{
lstrcpyA(Encodedsuffix,suffix);
return 0;
}
else
{
char path[MAX_PATH] = { 0 };
lstrcpyA(path, filePath);
Encodedsuffix = PathFindFileNameA(path);
char* ret = strrchr(Encodedsuffix, '-');
if (ret == NULL)
return 0;
*ret = '\0';
return 1;
}
}

//判断文件是否被加密,并获取原始文件名
//文件被加密,返回1
BOOL JudgeEncodedFile(char* filePath, char* encodedSuffix,char* oldFileName)
{
char* suffix = PathFindExtensionA(filePath);
if (lstrcmpA(suffix, encodedSuffix))
return 0; //文件没有被加密
else
{
lstrcpyA(oldFileName, filePath);
char* ret = strrchr(oldFileName, '.');
*ret = '\0';
return 1; //文件被加密,并获取到原始文件名
}
}

//读取文件中的被Base64加密的密钥
//读取成功返回1
BOOL ReadKey(char* filePath,char *encodedKey)
{
HANDLE hFile;
hFile = CreateFileA(filePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD fileSize = GetFileSize(hFile, 0);
WCHAR* bufferw = (WCHAR*)VirtualAlloc(0, fileSize, MEM_COMMIT, PAGE_READWRITE);
char* bufferA = (char *)VirtualAlloc(0, fileSize, MEM_COMMIT, PAGE_READWRITE);
DWORD NumberOfBytesRead;
if (hFile != INVALID_HANDLE_VALUE)
{
if (ReadFile(hFile, bufferw, fileSize, &NumberOfBytesRead, NULL))
{
WideCharToMultiByte(CP_OEMCP, 0, bufferw, -1, bufferA, fileSize/2-1, NULL, FALSE); //将UNCODE的后缀转成ASCII
char* ret = strstr(bufferA, "---BEGIN GANDCRAB KEY---");
memcpy(encodedKey, ret + 26, 2252);
CloseHandle(hFile);
return 1;
}
int ret = GetLastError();
}
VirtualFree(bufferw, 0, 0x8000);
VirtualFree(bufferA,0, 0x8000);
return 0;
}

//读取注册表键值
//返回0,读取成功
BOOL RegGetKeyValue(WCHAR* Reg, WCHAR* RegKeyName, BYTE* Value, DWORD cbData)
{
HKEY hKey;
BOOL result;
LSTATUS flag = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCWSTR)Reg, 0, KEY_READ | KEY_QUERY_VALUE, &hKey);
if (flag)
flag = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCWSTR)Reg, 0, KEY_READ | KEY_QUERY_VALUE, &hKey);
if (!flag)
{
result = RegQueryValueEx(hKey, (LPCWSTR)RegKeyName, 0, 0, Value, &cbData);
RegCloseKey(hKey);
}
else
result = 1;
return result;
}

//用RSA私钥解密SalsaKey和SalsaIV
//参数1 要解密的Salsakey或SalsaIV
//参数2 RSAPrivKey
BOOL DecodeSalsaKey(BYTE* KeyData, BYTE* RSAPrivKey)
{
HCRYPTPROV phProv;
HCRYPTPROV phKey;
DWORD DataLen = 256;
BOOL result;

if (CryptAcquireContextW(&phProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
{
result = 0;
if (CryptImportKey(phProv, RSAPrivKey, 0x494, 0, 0, &phKey))
{
result = CryptDecrypt(phKey, 0, 1, 0, KeyData, &DataLen);
}
CryptDestroyKey(phKey);
CryptReleaseContext(phProv, 0);
}
return result;
}

//根据Key和IV生成Salsa20,解密数据
//参数1 被加密的数据
//参数2 加密的数据大小
//参数3,4 KEY和IV
//参数5 解密后的数据
void salsa20Decrypt(BYTE* EncodedData, DWORD SizeofEncodedData, BYTE* SALSA20KEY, BYTE* SALSA20IV, BYTE* DecodedData)
{
Salsa20::Decryption dec;//定义解密对象

SecByteBlock KEY(SALSA20KEY, 0x20);
SecByteBlock IV(SALSA20IV, 0x8);

dec.SetKeyWithIV(KEY, KEY.size(), IV); //生成SalsaKey
dec.ProcessData(DecodedData, EncodedData, SizeofEncodedData); //解密数据
}

//读取文件密钥数据,获取加密文件的SalsaKey和SalsaIV
//获取成功返回1
BOOL GetDecodedData(CHAR* FileName,BYTE* salsaKey,BYTE* salsaIV,BYTE fileSize[8],BYTE encodedNumber[8],BYTE* LocRSAPriv)
{
HANDLE hFile;
BYTE fileEndBuffer[0x21C] = { 0 };
DWORD NumberOfBytesRead;
hFile = CreateFileA(FileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
SetFilePointer(hFile,(long)-540,0,FILE_END);
ReadFile(hFile, fileEndBuffer, 0x21C, &NumberOfBytesRead, 0);
if (*(DWORD*)(fileEndBuffer+0x214) == 0x93892918 && *(DWORD*)(fileEndBuffer+0x218) == 0x38281) //判断感染标志
{
if (!DecodeSalsaKey(fileEndBuffer, LocRSAPriv) || !DecodeSalsaKey(fileEndBuffer + 0x100, LocRSAPriv))
return 0;
memcpy(salsaKey, fileEndBuffer,32);
memcpy(salsaIV, fileEndBuffer + 0x100, 8);
memcpy(encodedNumber, (fileEndBuffer + 0x208),8);
memcpy(fileSize,(fileEndBuffer + 0x200),8);
CloseHandle(hFile);
return 1;
}
}
return 0;

}

//Base64解密
int base64_decode(const char* base64, unsigned char* dedata)
{
int i = 0, j = 0;
int trans[4] = { 0,0,0,0 };
for (; base64[i] != '\0'; i += 4) {
// 每四个一组,译码成三个字符
trans[0] = num_strchr(base64char, base64[i]);
trans[1] = num_strchr(base64char, base64[i + 1]);
// 1/3
dedata[j++] = ((trans[0] << 2) & 0xfc) | ((trans[1] >> 4) & 0x03);

if (base64[i + 2] == '=') {
continue;
}
else {
trans[2] = num_strchr(base64char, base64[i + 2]);
}
// 2/3
dedata[j++] = ((trans[1] << 4) & 0xf0) | ((trans[2] >> 2) & 0x0f);

if (base64[i + 3] == '=') {
continue;
}
else {
trans[3] = num_strchr(base64char, base64[i + 3]);
}

// 3/3
dedata[j++] = ((trans[2] << 6) & 0xc0) | (trans[3] & 0x3f);
}

dedata[j] = '\0';

return 0;
}
//Base64带的方法
inline int num_strchr(const char* str, char c) //
{
const char* pindex = strchr(str, c);
if (NULL == pindex) {
return -1;
}
return pindex - str;
}

//遍历当前目录
void TraverseRepair(char* filePath,char* encodedSuffix)
{
HANDLE hFindFile;
WIN32_FIND_DATAA findFileData;
char path[MAX_PATH] = { 0 };
char oldFileName[MAX_PATH] = { 0 };

int lenOfPath = lstrlenA(filePath);
filePath[lenOfPath] = '\\';
lstrcpyA(path, filePath);
lstrcatA(filePath, "*.*");
hFindFile = FindFirstFileA(filePath, &findFileData);
if (hFindFile != INVALID_HANDLE_VALUE)
{
do
{
if (lstrcmpA(findFileData.cFileName, ".") && lstrcmpA(findFileData.cFileName, ".."))
{
lstrcpyA(filePath, path);
lstrcatA(filePath, findFileData.cFileName);
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
TraverseRepair(filePath, encodedSuffix); //递归
else
{
if (JudgeEncodedFile(filePath, encodedSuffix, oldFileName)) //判断文件是否被加密
RepairFile(filePath, oldFileName); //修复文件
}
}
} while (FindNextFileA(hFindFile, &findFileData));
FindClose(hFindFile);
}
}

//修复文件
void RepairFile(char* filePath, char* oldFileName)
{
HANDLE hFile;
DWORD NumberOfBytesRead;
BYTE SalsaKey[40] = { 0 };
BYTE SalsaIV[10] = { 0 };
BYTE fileSize[8] = { 0 };
BYTE encodedNumber[8] = { 0 };
if (GetDecodedData(filePath, SalsaKey, SalsaIV, fileSize, encodedNumber, LocRSAPriv)) //验证感染特征,进一步判断是否被感染
{
hFile = CreateFileA(filePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
LPVOID EncodedBuffer = VirtualAlloc(0, 0x100000, MEM_COMMIT, PAGE_READWRITE); //开辟虚拟空间存储加密后的数据
LPVOID DecodedBuffer = VirtualAlloc(0, 0x100000, MEM_COMMIT, PAGE_READWRITE); //开辟虚拟空间存储解密后的文件
for (LONG64 i = 0; i < *(LONG64*)encodedNumber; i++)
{
if (ReadFile(hFile, EncodedBuffer, 0x100000, &NumberOfBytesRead, 0))
{
salsa20Decrypt((BYTE*)EncodedBuffer, NumberOfBytesRead, SalsaKey, SalsaIV, (BYTE*)DecodedBuffer);
DWORD nNumberOfBytesToWrite = NumberOfBytesRead;
DWORD NumberOfBytesWritten;
SetFilePointer(hFile, -(int)nNumberOfBytesToWrite, 0, FILE_CURRENT); //将指针设置到刚开始读取的位置
if (!WriteFile(hFile, DecodedBuffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0))
printf("修复文件:修复文件%s第%d次出错!\n", filePath, i + 1);
}
else
printf("修复文件:读取文件%s出错!\n", filePath);
}
VirtualFree(EncodedBuffer, 0, 0x8000u);
VirtualFree(DecodedBuffer, 0, 0x8000u);
SetFilePointer(hFile,-0x21C,0,FILE_END);
SetEndOfFile(hFile);
printf("%s文件修复成功!\n",filePath);
CloseHandle(hFile);
MoveFileExA(filePath, oldFileName, 1); //修复原文件名
}
else
printf("修复文件:打开文件%s出错!\n", filePath);
}
}