勒索病毒LockBit2.0 数据库(mysql与sqlsever)解锁恢复思路分享

0.前言

今天公司服务器中招LockBit2.0勒索病毒,损失惨重,全体加班了一天基本解决了部分问题,首先是丢失的文件数据就没法恢复了,这一块没有理睬,主要恢复的是两个数据库,一个是16GB大小的SQLserver数据库,另一个是一个15GB大小的Mysql数据库,最后丢失了1%左右的内容顺利恢复了两个数据库,因为lockbit勒索病毒的分享给文章太少,这里就记录下自己恢复的过程。

1.恢复逻辑

lockbit2.0的锁定逻辑是针对有价值的文件,头部4KB给你加密,尾部4KB给你加密,然后中间隔一段随机(也可能是固定间隔,没有考证过)给你加密大约4KB,全局给你大约加密不到1%的数据,因此恢复的逻辑也就是舍弃这些被加密的数据,尽可能恢复数据库正常。

注意!!!,lockbit2.0不存在找人破解的可能性,找人弄也是按照上面的逻辑,懂程序原理的都知道lockbit的加密是无法被破解的,因此注意各位不要被骗!

另外我们能恢复数据库主要是我们的数据库有以下特点,数据库极大,表内条数极多,这样被损坏的数据才能最大概率着落在表内数据的,并且表内多是流水账数据,丢失部分的损失可控,因此修复的可能性最高,如果你的数据库不大,几百兆,内容不多表结构复杂的,没什么修复的价值。

2.sqlserver恢复

sqlserver市面是存在恢复软件的,我找的是达思科技的软件,1399元买了一天的使用权限,通过被锁的文件和1年前数据库文件(一半大小)一起合并进行了恢复,最后的回复结果是大致回复了99%的数据,部分表格少几条数据,有一个表格是40000多条数据,少了20条,另一个表格18000条数据,少了8条,少的数据就是被加密的数据,被舍掉了。因为sqlsever按照我查的资料,主要被加密的数据一个是头文件,也就是表结构,主要是通过过去的备份文件恢复的,中间的数据就是舍弃。这个恢复成功的思路对我后面回复mysql提供了较大的帮助。

最终sqlsever恢复,但是因为是财务软件的数据库,少的数据还是会导致很多模块计算有问题,这个只能后续慢慢反查和处理了。

注意,一天时间比较宝贵,请提前给自己的机子搭建好本地sql server环境,到恢复成功大约耗时要18个小时,需要通宵干。

3,mysql恢复

mysql恢复是最复杂的,首先mysql市面上是没有成品恢复软件的,不要被骗。因此只能动手恢复mysql的原始文件是分立的文件,每一个文件都被加密了,原始数据库是二进制文件,因此恢复原始数据库文件的难度最大,几乎是不可能被恢复。

因此我们恢复瞄准的是备份的.sql文件,.sql文件是一个类似文本文件,可读性高,恢复起来比较容易。

3.1 mysql文件的逻辑与查看

mysql文件的基本逻辑是如下

建立数据库(一句话)

{

建立表A

建立表A结构(列)

插入表A的数据 *N

。。。。

} *N个表循环

其中头部的4KB是建立表头和建立表结构,这一块只能手敲,具体方法是自己建立数据库,复原表结构后导出为sql文件,然后比对着sql文件自己手动恢复头部4KB文件。

第二部分是建立表结构,表结构不复杂的话大约是1KB左右的样子

第三部分是插入表数据,这个是数据库中最大的一块,我们的mysql会将一大堆数据写成一个insert插入,每一个insert的最长是2MB字节的大小,因此如果损坏的4KB落在这里的话,就要排除掉这2MB,后续再维修这2MB。

加密的逻辑是随机加密(待考),因此文件内部被锁的4KB落在insert中的概率最大。

对于这种单文件超大的sql文件,是没有查看工具的,因此首先用c#写一个分析工具,逐行分析这个sql文件是干什么的

使用下面的代码,可以将每一行都打印到log文件中,使用文本打开log文件可以查看sql语句的结构。

cs 复制代码
static void Main(string[] args)
        {

            ///*          sql有效性验证算法+打印算法
            int linenum = 0;
            string filename = "I:\\mysql_backup_20231008__00_00.sql.lockbit";
            string filename1 = "I:\\log.text";
            string filename2 = "I:\\xiufuz_error.sql";
            String line;
            bool bResult;
            int[] errorcode = new int[5];


            try
            {
                StreamReader sr = new StreamReader(filename);
                StreamWriter sw1 = new StreamWriter(filename1);
                //StreamWriter sw2 = new StreamWriter(filename2);
                StringBuilder sb = new StringBuilder();     //定义
                sb.Clear();
                int isbinary = 0;
                int errortick = 0;
                int isrightorerror = 0;
                while (!sr.EndOfStream)
                {
                    //lstchar2 = lstchar1;
                    //lstchar1 = currentChar;
                    string readlinest = sr.ReadLine();
                    byte[] charArray = Encoding.Default.GetBytes(readlinest);
                    sb.Append(readlinest);
                    if (charArray.Count() > 0)
                    {
                        if (charArray[charArray.Count() - 1] == 0x3b)
                        {
                            line = sb.ToString();
                            sb.Clear();     //清空
                            linenum++;
                            byte[] bytetemp = Encoding.Default.GetBytes(line);
                            isbinary = 0;
                            errortick = 0;
                            isrightorerror = 0;     //正确
                            for (int i = 0; i < line.Length; i++)
                            {
                                if (i + 7 < line.Length)
                                {
                                    if (line[i] == '_')
                                    {
                                        try
                                        {
                                            if (line[i+1]=='b'&&line[i+2]=='i'&&line[i+3]=='n'&&line[i+4]=='a'&&line[i+5]=='r')
                                            {
                                                isbinary = 1;
                                            }
                                        }
                                        catch
                                        {

                                        }
                                    }
                                            
                                }
                                        
                                if (isbinary == 0)
                                {
                                    if (!IsChineseLetter(line, i))
                                    {
                                        errorcode[errortick] = Char.ConvertToUtf32(line, i);
                                        errortick++;
                                        if (errortick > 3)
                                        {
                                            
                                            if (Char.ConvertToUtf32(line, i) < 0xff00)
                                            {

                                            }
                                            isrightorerror = 1; //错误
                                            break;
                                        }
                                                
                                    }
                                }
                                else
                                { 
                                            
                                }
                            }

                            if (isrightorerror == 1)
                            {
                                //错误
                                //sw2.WriteLine(line);
                                sw1.WriteLine("行:" + linenum.ToString("D5") + "错误,内容:" + line.Substring(0, 50) + "---" + errorcode[0].ToString("X4") + ',' + errorcode[1].ToString("X4") + ',' + errorcode[2].ToString("X4"));
                                //Console.WriteLine("错误字符,line:" + linenum.ToString() + ",长度:"+line.Length.ToString()+"---" + errorcode[0].ToString("X4") + ',' + errorcode[1].ToString("X4") + ',' + errorcode[2].ToString("X4"));
                            }
                            else
                            { 
                                //正确
                                //sw1.WriteLine(line);
                                sw1.WriteLine("行:" + linenum.ToString("D5") + "正确,内容:" + line.Substring(0, line.Length > 50 ? 50 : line.Length));
                            }
                            if (linenum % 100 == 0)
                                Console.WriteLine("Line:" + linenum.ToString());
 
                        }
                    }

                }

                sr.Close();
                sw1.Close();
                //sw2.Close();
                Console.WriteLine("Final Line:" + linenum.ToString());
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception: " + e.Message);
            }
            finally
            {
                Console.WriteLine("Executing finally block.");
            }
                    

            while (true) ;
}

通过上述文件打印出来的log可以观察到非常多的信息:

如下图,这是一个正确的建立表格,插入数据的结构,由7行的建立表头,若干行的INSERT数据和1行的解锁组成,按照这个逻辑可以查看每一个表格的语句是否完成,如图我战士的after_sales这个表格就非常完整。

如下图所示,这是一个典型的中间有损失的表,可以看到表结构是正确的,插入语句在21393行出现了错误,这一条语句的数据就要被舍弃,后续再根据重要性选择恢复剩余的细节,但是这个表的绝大多数部分都会恢复并且是好的

3.2 使用程序辅助分离sql文件中的好的部分和坏的部分

这里我使用的C#写的处理程序,处理逻辑是,按照分号加换行符来确认不同的sql语句行,在每一行中逐个字节判断字节是不是有效的文本信息,如果本行文件中出现了三次以上的非Unicode字符的字节,我们就判定这一行被加密了,将这一行复制到错误的文件中等待修复,反之则复制到正确的文件中修复。

附C#代码

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace 勒索修复程序
{
    class Program
    {
        //我的数据库中各个国家的文字都有,因此这里做了针对性的排除
        static bool IsChineseLetter(string input, int index)
        {
            int code = 0;
            int chfrom = Convert.ToInt32("4e00", 16); //范围(0x4e00~0x9fff)转换成int(chfrom~chend)
            int chend = Convert.ToInt32("9fff", 16);
            if (input != "")
            {
                code = Char.ConvertToUtf32(input, index); //获得字符串input中指定索引index处字符unicode编码
                if (code < 0x80)        //英文是正确的
                    return true;
                if (code >= chfrom && code <= chend)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0xa0 && code <= 0xff)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0xff00 && code <= 0xffee)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0x3000 && code <= 0x30ff)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0x3300 && code <= 0x36ff)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0x13a0 && code <= 0x13ff)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0x2000 && code <= 0x22ff)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0x2400 && code <= 0x27ff)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0x0100 && code <= 0x02ff)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0x0370 && code <= 0x03ff)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0x0400 && code <= 0x05ff)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0x0600 && code <= 0x06ff)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0x0e00 && code <= 0x0fda)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0xac00 && code <= 0xd7a3)       //韩文
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0xaa80 && code <= 0xaac2)       
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0x1d00 && code <= 0x1eff)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0x0900 && code <= 0x0bff)
                {
                    return true; //当code在中文范围内返回true
                }
                if (code >= 0x3400 && code <= 0x4dbf)       //繁体
                {
                    return true; //当code在中文范围内返回true
                }
                else
                {
                    return false; //当code不在中文范围内返回false
                }
            }
            return false;
        }
        static void Main(string[] args)
        {

            ///*          sql有效性验证算法
            int linenum = 0;
            string filename = "I:\\mysql_backup_20231008__00_00.sql.lockbit";    //目标文件
            string filename1 = "I:\\xiufuz_right.sql";    //正确的保存文件
            string filename2 = "I:\\xiufuz_error.sql";    //错误的保存文件
            String line;
            bool bResult;
            try
            {
                StreamReader sr = new StreamReader(filename);
                StreamWriter sw1 = new StreamWriter(filename1);
                StreamWriter sw2 = new StreamWriter(filename2);
                StringBuilder sb = new StringBuilder();     //定义
                sb.Clear();
                int isbinary = 0;
                int errortick = 0;
                int isrightorerror = 0;
                while (!sr.EndOfStream)
                {
                    //lstchar2 = lstchar1;
                    //lstchar1 = currentChar;
                    string readlinest = sr.ReadLine();
                    byte[] charArray = Encoding.Default.GetBytes(readlinest);
                    sb.Append(readlinest);
                    if (charArray.Count() > 0)
                    {
                        if (charArray[charArray.Count() - 1] == 0x3b)
                        {
                            line = sb.ToString();
                            sb.Clear();     //清空
                            linenum++;
                            byte[] bytetemp = Encoding.Default.GetBytes(line);
                            isbinary = 0;
                            errortick = 0;
                            isrightorerror = 0;     //正确
                            for (int i = 0; i < line.Length; i++)
                            {
                                if (i + 7 < line.Length)
                                {
                                    if (line[i] == '_')
                                    {
                                        try
                                        {
                                            if (line[i+1]=='b'&&line[i+2]=='i'&&line[i+3]=='n'&&line[i+4]=='a'&&line[i+5]=='r')
                                            {
                                                isbinary = 1;
                                            }
                                        }
                                        catch
                                        {

                                        }
                                    }
                                            
                                }
                                        
                                if (isbinary == 0)
                                {
                                    if (!IsChineseLetter(line, i))
                                    {
                                        errorcode[errortick] = Char.ConvertToUtf32(line, i);
                                        errortick++;
                                        if (errortick > 3)
                                        {
                                            Console.WriteLine("错误字符,line:" + linenum.ToString() + ",长度:"+line.Length.ToString()+"---" + errorcode[0].ToString("X4") + ',' + errorcode[1].ToString("X4") + ',' + errorcode[2].ToString("X4"));
                                            if (Char.ConvertToUtf32(line, i) < 0xff00)
                                            {

                                            }
                                            isrightorerror = 1; //错误
                                            break;
                                        }
                                                
                                    }
                                }
                                else
                                { 
                                            
                                }
                            }

                            if (isrightorerror == 1)
                            {
                                //错误
                                sw2.WriteLine(line);
                            }
                            else
                            { 
                                //正确
                                sw1.WriteLine(line);
                            }
 
                        }
                    }

                }

                sr.Close();
                sw1.Close();
                sw2.Close();
                Console.WriteLine("Final Line:" + linenum.ToString());
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception: " + e.Message);
            }
            finally
            {
                Console.WriteLine("Executing finally block.");
            }
                    

            while (true) ;
            // */

            
            while(true);
        }
    }
}

执行效果如下图

可以看到,被攻击的段落中至少出现三个FFFD以上的,这个就是被攻击的段落的特点,将没被攻击的干净的sql文件导出后进行下一步处理。

3.3 mysql文件导入库

mysql文件导入mysql库的过程需要自己执行,导入过程中发现错误随时进行处理和补全表结构,这里因为表是你自己建的,别人也帮不了你,是一个苦功夫,但是我的数据库16GB大小,表结构占位不到1MB,因此表结构被加密的概率较低,实际恢复过程中我的数据库就一个表结构被损坏了,因此就补了一个表结构就完成了数据库的恢复。

3.4 其他被损坏的文件的处理

首先我通过C#将500MB左右的损坏文件拆成了10个50MB的文件,按照每个文件50行

附代码

cs 复制代码
static void Main(string[] args)
        {

            ///*     //拆分算法
            try
            {
                string line="";
                int linenum=0;
                byte lstchar1=0, lstchar2=0;
                byte currentChar=0;
                int block = 0;
                string filename = "I:\\xiufuz_error.sql";
                string filename1 = "I:\\error1.sql";
                string filename2 = "I:\\error2.sql";
                string filename3 = "I:\\error3.sql";
                string filename4 = "I:\\error4.sql";
                string filename5 = "I:\\error5.sql";
                string filename6 = "I:\\error6.sql";
                string filename7 = "I:\\error7.sql";
                string filename8 = "I:\\error8.sql";
                string filename9 = "I:\\error9.sql";
                string filename10 = "I:\\error10.sql";
                StreamReader sr = new StreamReader(filename);
                StreamWriter sw1 = new StreamWriter(filename1);
                StreamWriter sw2 = new StreamWriter(filename2);
                StreamWriter sw3 = new StreamWriter(filename3);
                StreamWriter sw4 = new StreamWriter(filename4);
                StreamWriter sw5 = new StreamWriter(filename5);
                StreamWriter sw6 = new StreamWriter(filename6);
                StreamWriter sw7 = new StreamWriter(filename7);
                StreamWriter sw8 = new StreamWriter(filename8);
                StreamWriter sw9 = new StreamWriter(filename9);
                StreamWriter sw10 = new StreamWriter(filename10);
                //Read the first line of text

                StringBuilder sb = new StringBuilder();     //定义
                sb.Clear();

                while (!sr.EndOfStream)
                {
                    //lstchar2 = lstchar1;
                    //lstchar1 = currentChar;
                    string readlinest = sr.ReadLine();
                    byte[] charArray = Encoding.Default.GetBytes(readlinest);
                    sb.Append(readlinest);
                    if (charArray.Count() > 0)
                    {
                        if (charArray[charArray.Count() - 1] == 0x3b)
                        {
                            line = sb.ToString();
                            sb.Clear();     //清空
                            linenum++;
                            if (linenum % 100 == 0)
                                Console.WriteLine("Line:" + linenum.ToString());
                            //清空前处理
                            if (block == 0)
                                sw1.WriteLine(line);
                            else if (block == 1)
                                sw2.WriteLine(line);
                            else if (block == 2)
                                sw3.WriteLine(line);
                            else if (block == 3)
                                sw4.WriteLine(line);
                            else if (block == 4)
                                sw5.WriteLine(line);
                            else if (block == 5)
                                sw6.WriteLine(line);
                            else if (block == 6)
                                sw7.WriteLine(line);
                            else if (block == 7)
                                sw8.WriteLine(line);
                            else if (block == 8)
                                sw9.WriteLine(line);
                            else if (block == 9)
                                sw10.WriteLine(line);
                            if (linenum > 50)
                            {
                                if (block < 9)
                                {
                                    linenum = 0;
                                    block++;
                                }
                            }
                        } 
                    }
                    
                }
                sw1.Close();
                sw2.Close();
                sw3.Close();
                sw4.Close();
                sw5.Close();
                sw6.Close();
                sw7.Close();
                sw8.Close();
                sw9.Close();
                sw10.Close();
                Console.WriteLine("Final Line:" + linenum.ToString());
                while(true);
               
                
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception: " + e.Message);
            }
            finally
            {
                Console.WriteLine("Executing finally block.");
            }
     
           // */
            while(true);
        }

拆成50MB左右的文件后就可以使用notepad打开了,打开后根据每一条的重要程度选择性恢复,这里的回复只能自己手动回复的,具体的办法是将里面损坏的部分删除后,手动对齐格式,把剩余能插入的数据再执行插入回数据库中。是一个手工的水磨细活。

4 总结

全部恢复耗时两个人24小时通宵解决了,尽可能的减少了勒索病毒对公司的影响。

勒索病毒是对公司的损失是重大的,本次勒索病毒我们自己总结如下经验

1,首先杀毒系统要装,有钱就买个卡巴斯基装上(淘宝有便宜的),没钱装一个360,一定要有杀软

2,内网别偷懒,该划分vlan就划分vlan,至少能做好数据隔离

3,核心数据库资产一定要做一个云端备份,可以用阿里云或者腾讯云的付费产品,不要迷信本地备份,我们就是本地备份顺着共享文件夹全部一锅端了,三个备份机制全部失效。

4,如果真的依靠本地备份,就在vm exsi中做镜像级别的备份,这个备份不会被干掉,反之所有的windows级别的备份都会被勒索病毒干掉。

相关推荐
刘大浪17 分钟前
后端数据增删改查基于Springboot+mybatis mysql 时间根据当时时间自动填充,数据库连接查询不一致,mysql数据库连接不好用
数据库·spring boot·mybatis
无敌岩雀24 分钟前
MySQL中的索引
数据库·mysql
鲤籽鲲30 分钟前
C# MethodTimer.Fody 使用详解
开发语言·c#·mfc
a_安徒生1 小时前
linux安装TDengine
linux·数据库·tdengine
程序员学习随笔1 小时前
PostgreSQL技术内幕19:逻辑备份工具pg_dump、pg_dumpall
数据库·postgresql
工业3D_大熊1 小时前
3D可视化引擎HOOPS Luminate场景图详解:形状的创建、销毁与管理
java·c++·3d·docker·c#·制造·数据可视化
yngsqq1 小时前
c#使用高版本8.0步骤
java·前端·c#
尘浮生1 小时前
Java项目实战II基于微信小程序的校运会管理系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
偶尔。5351 小时前
什么是事务?事务有哪些特性?
数据库·oracle
安迁岚1 小时前
【SQL Server】华中农业大学空间数据库实验报告 实验六 视图
数据库·sql·mysql·oracle·实验报告