• 勒索病毒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语句的结构。

    1. static void Main(string[] args)
    2. {
    3. ///* sql有效性验证算法+打印算法
    4. int linenum = 0;
    5. string filename = "I:\\mysql_backup_20231008__00_00.sql.lockbit";
    6. string filename1 = "I:\\log.text";
    7. string filename2 = "I:\\xiufuz_error.sql";
    8. String line;
    9. bool bResult;
    10. int[] errorcode = new int[5];
    11. try
    12. {
    13. StreamReader sr = new StreamReader(filename);
    14. StreamWriter sw1 = new StreamWriter(filename1);
    15. //StreamWriter sw2 = new StreamWriter(filename2);
    16. StringBuilder sb = new StringBuilder(); //定义
    17. sb.Clear();
    18. int isbinary = 0;
    19. int errortick = 0;
    20. int isrightorerror = 0;
    21. while (!sr.EndOfStream)
    22. {
    23. //lstchar2 = lstchar1;
    24. //lstchar1 = currentChar;
    25. string readlinest = sr.ReadLine();
    26. byte[] charArray = Encoding.Default.GetBytes(readlinest);
    27. sb.Append(readlinest);
    28. if (charArray.Count() > 0)
    29. {
    30. if (charArray[charArray.Count() - 1] == 0x3b)
    31. {
    32. line = sb.ToString();
    33. sb.Clear(); //清空
    34. linenum++;
    35. byte[] bytetemp = Encoding.Default.GetBytes(line);
    36. isbinary = 0;
    37. errortick = 0;
    38. isrightorerror = 0; //正确
    39. for (int i = 0; i < line.Length; i++)
    40. {
    41. if (i + 7 < line.Length)
    42. {
    43. if (line[i] == '_')
    44. {
    45. try
    46. {
    47. if (line[i+1]=='b'&&line[i+2]=='i'&&line[i+3]=='n'&&line[i+4]=='a'&&line[i+5]=='r')
    48. {
    49. isbinary = 1;
    50. }
    51. }
    52. catch
    53. {
    54. }
    55. }
    56. }
    57. if (isbinary == 0)
    58. {
    59. if (!IsChineseLetter(line, i))
    60. {
    61. errorcode[errortick] = Char.ConvertToUtf32(line, i);
    62. errortick++;
    63. if (errortick > 3)
    64. {
    65. if (Char.ConvertToUtf32(line, i) < 0xff00)
    66. {
    67. }
    68. isrightorerror = 1; //错误
    69. break;
    70. }
    71. }
    72. }
    73. else
    74. {
    75. }
    76. }
    77. if (isrightorerror == 1)
    78. {
    79. //错误
    80. //sw2.WriteLine(line);
    81. sw1.WriteLine("行:" + linenum.ToString("D5") + "错误,内容:" + line.Substring(0, 50) + "---" + errorcode[0].ToString("X4") + ',' + errorcode[1].ToString("X4") + ',' + errorcode[2].ToString("X4"));
    82. //Console.WriteLine("错误字符,line:" + linenum.ToString() + ",长度:"+line.Length.ToString()+"---" + errorcode[0].ToString("X4") + ',' + errorcode[1].ToString("X4") + ',' + errorcode[2].ToString("X4"));
    83. }
    84. else
    85. {
    86. //正确
    87. //sw1.WriteLine(line);
    88. sw1.WriteLine("行:" + linenum.ToString("D5") + "正确,内容:" + line.Substring(0, line.Length > 50 ? 50 : line.Length));
    89. }
    90. if (linenum % 100 == 0)
    91. Console.WriteLine("Line:" + linenum.ToString());
    92. }
    93. }
    94. }
    95. sr.Close();
    96. sw1.Close();
    97. //sw2.Close();
    98. Console.WriteLine("Final Line:" + linenum.ToString());
    99. }
    100. catch (Exception e)
    101. {
    102. Console.WriteLine("Exception: " + e.Message);
    103. }
    104. finally
    105. {
    106. Console.WriteLine("Executing finally block.");
    107. }
    108. while (true) ;
    109. }

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

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

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

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

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

    附C#代码

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5. using System.Threading.Tasks;
    6. using System.IO;
    7. namespace 勒索修复程序
    8. {
    9. class Program
    10. {
    11. //我的数据库中各个国家的文字都有,因此这里做了针对性的排除
    12. static bool IsChineseLetter(string input, int index)
    13. {
    14. int code = 0;
    15. int chfrom = Convert.ToInt32("4e00", 16); //范围(0x4e00~0x9fff)转换成int(chfrom~chend)
    16. int chend = Convert.ToInt32("9fff", 16);
    17. if (input != "")
    18. {
    19. code = Char.ConvertToUtf32(input, index); //获得字符串input中指定索引index处字符unicode编码
    20. if (code < 0x80) //英文是正确的
    21. return true;
    22. if (code >= chfrom && code <= chend)
    23. {
    24. return true; //当code在中文范围内返回true
    25. }
    26. if (code >= 0xa0 && code <= 0xff)
    27. {
    28. return true; //当code在中文范围内返回true
    29. }
    30. if (code >= 0xff00 && code <= 0xffee)
    31. {
    32. return true; //当code在中文范围内返回true
    33. }
    34. if (code >= 0x3000 && code <= 0x30ff)
    35. {
    36. return true; //当code在中文范围内返回true
    37. }
    38. if (code >= 0x3300 && code <= 0x36ff)
    39. {
    40. return true; //当code在中文范围内返回true
    41. }
    42. if (code >= 0x13a0 && code <= 0x13ff)
    43. {
    44. return true; //当code在中文范围内返回true
    45. }
    46. if (code >= 0x2000 && code <= 0x22ff)
    47. {
    48. return true; //当code在中文范围内返回true
    49. }
    50. if (code >= 0x2400 && code <= 0x27ff)
    51. {
    52. return true; //当code在中文范围内返回true
    53. }
    54. if (code >= 0x0100 && code <= 0x02ff)
    55. {
    56. return true; //当code在中文范围内返回true
    57. }
    58. if (code >= 0x0370 && code <= 0x03ff)
    59. {
    60. return true; //当code在中文范围内返回true
    61. }
    62. if (code >= 0x0400 && code <= 0x05ff)
    63. {
    64. return true; //当code在中文范围内返回true
    65. }
    66. if (code >= 0x0600 && code <= 0x06ff)
    67. {
    68. return true; //当code在中文范围内返回true
    69. }
    70. if (code >= 0x0e00 && code <= 0x0fda)
    71. {
    72. return true; //当code在中文范围内返回true
    73. }
    74. if (code >= 0xac00 && code <= 0xd7a3) //韩文
    75. {
    76. return true; //当code在中文范围内返回true
    77. }
    78. if (code >= 0xaa80 && code <= 0xaac2)
    79. {
    80. return true; //当code在中文范围内返回true
    81. }
    82. if (code >= 0x1d00 && code <= 0x1eff)
    83. {
    84. return true; //当code在中文范围内返回true
    85. }
    86. if (code >= 0x0900 && code <= 0x0bff)
    87. {
    88. return true; //当code在中文范围内返回true
    89. }
    90. if (code >= 0x3400 && code <= 0x4dbf) //繁体
    91. {
    92. return true; //当code在中文范围内返回true
    93. }
    94. else
    95. {
    96. return false; //当code不在中文范围内返回false
    97. }
    98. }
    99. return false;
    100. }
    101. static void Main(string[] args)
    102. {
    103. ///* sql有效性验证算法
    104. int linenum = 0;
    105. string filename = "I:\\mysql_backup_20231008__00_00.sql.lockbit"; //目标文件
    106. string filename1 = "I:\\xiufuz_right.sql"; //正确的保存文件
    107. string filename2 = "I:\\xiufuz_error.sql"; //错误的保存文件
    108. String line;
    109. bool bResult;
    110. try
    111. {
    112. StreamReader sr = new StreamReader(filename);
    113. StreamWriter sw1 = new StreamWriter(filename1);
    114. StreamWriter sw2 = new StreamWriter(filename2);
    115. StringBuilder sb = new StringBuilder(); //定义
    116. sb.Clear();
    117. int isbinary = 0;
    118. int errortick = 0;
    119. int isrightorerror = 0;
    120. while (!sr.EndOfStream)
    121. {
    122. //lstchar2 = lstchar1;
    123. //lstchar1 = currentChar;
    124. string readlinest = sr.ReadLine();
    125. byte[] charArray = Encoding.Default.GetBytes(readlinest);
    126. sb.Append(readlinest);
    127. if (charArray.Count() > 0)
    128. {
    129. if (charArray[charArray.Count() - 1] == 0x3b)
    130. {
    131. line = sb.ToString();
    132. sb.Clear(); //清空
    133. linenum++;
    134. byte[] bytetemp = Encoding.Default.GetBytes(line);
    135. isbinary = 0;
    136. errortick = 0;
    137. isrightorerror = 0; //正确
    138. for (int i = 0; i < line.Length; i++)
    139. {
    140. if (i + 7 < line.Length)
    141. {
    142. if (line[i] == '_')
    143. {
    144. try
    145. {
    146. if (line[i+1]=='b'&&line[i+2]=='i'&&line[i+3]=='n'&&line[i+4]=='a'&&line[i+5]=='r')
    147. {
    148. isbinary = 1;
    149. }
    150. }
    151. catch
    152. {
    153. }
    154. }
    155. }
    156. if (isbinary == 0)
    157. {
    158. if (!IsChineseLetter(line, i))
    159. {
    160. errorcode[errortick] = Char.ConvertToUtf32(line, i);
    161. errortick++;
    162. if (errortick > 3)
    163. {
    164. Console.WriteLine("错误字符,line:" + linenum.ToString() + ",长度:"+line.Length.ToString()+"---" + errorcode[0].ToString("X4") + ',' + errorcode[1].ToString("X4") + ',' + errorcode[2].ToString("X4"));
    165. if (Char.ConvertToUtf32(line, i) < 0xff00)
    166. {
    167. }
    168. isrightorerror = 1; //错误
    169. break;
    170. }
    171. }
    172. }
    173. else
    174. {
    175. }
    176. }
    177. if (isrightorerror == 1)
    178. {
    179. //错误
    180. sw2.WriteLine(line);
    181. }
    182. else
    183. {
    184. //正确
    185. sw1.WriteLine(line);
    186. }
    187. }
    188. }
    189. }
    190. sr.Close();
    191. sw1.Close();
    192. sw2.Close();
    193. Console.WriteLine("Final Line:" + linenum.ToString());
    194. }
    195. catch (Exception e)
    196. {
    197. Console.WriteLine("Exception: " + e.Message);
    198. }
    199. finally
    200. {
    201. Console.WriteLine("Executing finally block.");
    202. }
    203. while (true) ;
    204. // */
    205. while(true);
    206. }
    207. }
    208. }

    执行效果如下图

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

    3.3 mysql文件导入库

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

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

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

    附代码

    1. static void Main(string[] args)
    2. {
    3. ///* //拆分算法
    4. try
    5. {
    6. string line="";
    7. int linenum=0;
    8. byte lstchar1=0, lstchar2=0;
    9. byte currentChar=0;
    10. int block = 0;
    11. string filename = "I:\\xiufuz_error.sql";
    12. string filename1 = "I:\\error1.sql";
    13. string filename2 = "I:\\error2.sql";
    14. string filename3 = "I:\\error3.sql";
    15. string filename4 = "I:\\error4.sql";
    16. string filename5 = "I:\\error5.sql";
    17. string filename6 = "I:\\error6.sql";
    18. string filename7 = "I:\\error7.sql";
    19. string filename8 = "I:\\error8.sql";
    20. string filename9 = "I:\\error9.sql";
    21. string filename10 = "I:\\error10.sql";
    22. StreamReader sr = new StreamReader(filename);
    23. StreamWriter sw1 = new StreamWriter(filename1);
    24. StreamWriter sw2 = new StreamWriter(filename2);
    25. StreamWriter sw3 = new StreamWriter(filename3);
    26. StreamWriter sw4 = new StreamWriter(filename4);
    27. StreamWriter sw5 = new StreamWriter(filename5);
    28. StreamWriter sw6 = new StreamWriter(filename6);
    29. StreamWriter sw7 = new StreamWriter(filename7);
    30. StreamWriter sw8 = new StreamWriter(filename8);
    31. StreamWriter sw9 = new StreamWriter(filename9);
    32. StreamWriter sw10 = new StreamWriter(filename10);
    33. //Read the first line of text
    34. StringBuilder sb = new StringBuilder(); //定义
    35. sb.Clear();
    36. while (!sr.EndOfStream)
    37. {
    38. //lstchar2 = lstchar1;
    39. //lstchar1 = currentChar;
    40. string readlinest = sr.ReadLine();
    41. byte[] charArray = Encoding.Default.GetBytes(readlinest);
    42. sb.Append(readlinest);
    43. if (charArray.Count() > 0)
    44. {
    45. if (charArray[charArray.Count() - 1] == 0x3b)
    46. {
    47. line = sb.ToString();
    48. sb.Clear(); //清空
    49. linenum++;
    50. if (linenum % 100 == 0)
    51. Console.WriteLine("Line:" + linenum.ToString());
    52. //清空前处理
    53. if (block == 0)
    54. sw1.WriteLine(line);
    55. else if (block == 1)
    56. sw2.WriteLine(line);
    57. else if (block == 2)
    58. sw3.WriteLine(line);
    59. else if (block == 3)
    60. sw4.WriteLine(line);
    61. else if (block == 4)
    62. sw5.WriteLine(line);
    63. else if (block == 5)
    64. sw6.WriteLine(line);
    65. else if (block == 6)
    66. sw7.WriteLine(line);
    67. else if (block == 7)
    68. sw8.WriteLine(line);
    69. else if (block == 8)
    70. sw9.WriteLine(line);
    71. else if (block == 9)
    72. sw10.WriteLine(line);
    73. if (linenum > 50)
    74. {
    75. if (block < 9)
    76. {
    77. linenum = 0;
    78. block++;
    79. }
    80. }
    81. }
    82. }
    83. }
    84. sw1.Close();
    85. sw2.Close();
    86. sw3.Close();
    87. sw4.Close();
    88. sw5.Close();
    89. sw6.Close();
    90. sw7.Close();
    91. sw8.Close();
    92. sw9.Close();
    93. sw10.Close();
    94. Console.WriteLine("Final Line:" + linenum.ToString());
    95. while(true);
    96. }
    97. catch (Exception e)
    98. {
    99. Console.WriteLine("Exception: " + e.Message);
    100. }
    101. finally
    102. {
    103. Console.WriteLine("Executing finally block.");
    104. }
    105. // */
    106. while(true);
    107. }

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

    4 总结

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

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

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

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

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

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

  • 相关阅读:
    单元测试与集成测试:软件质量的双重保障
    树莓派4B(Ubuntu 22.04 server)与Windows11网线直连(无显示器)
    基于LeNet实现手写体数字识别实验
    unity urp 棉麻织物渲染
    深度神经网络算法有哪些,python深度神经网络算法
    wininet,winhttp,xmlhttprequest,各版本区别 《转》
    OpenFeign的三种远程调用方式
    BottomSheetDialogFragment大量踩坑-自适应高度和最大高度和滚动问题等等
    如何在 Excel 中进行加,减,乘,除
    如何將人臉變漂亮(七)
  • 原文地址:https://blog.csdn.net/qq1003442507/article/details/133872984