首页
社区
课程
招聘
IDA静态分析REvil家族勒索病毒
发表于: 2021-5-14 21:05 63955

IDA静态分析REvil家族勒索病毒

2021-5-14 21:05
63955

本文献给所有像我一样刚从事安全行业的萌新,希望能对您有一定帮助。同时感谢所有在我迷茫时伸出援手的大佬们。

REvil又称Sodinokibi,是一种以私有软件即服务(RaaS)体系运营牟利的黑客组织。他们通过招募会员为其分发勒索软件攻击,当受害者支付赎金后该组织会将赎金与会员进行分成,因此很难定位其组织真实所在地。

md5:FBF8E910F9480D64E8FF6ECF4B10EF4B

sha1:E6B32975ACB2CC5230DD4F6CE6F243293FD984FA

sha256:CEC23C13C52A39C8715EE2ED7878F8AA9452E609DF1D469EF7F0DEC55736645B

VT:https://www.virustotal.com/gui/file/cec23c13c52a39c8715ee2ed7878f8aa9452e609df1d469ef7f0dec55736645b/detection

VT沙箱以将其标识为Sodinokibi。

查看节区信息,可以看到存在一个自定义节.11hix,且各个节区熵值都较高证明程序存在大量加密数据。

查看导入dll,识别出导入了kernel32.dll和user32.dll两个模块。证明样本对程序的导入表做了处理。

查看识别出的API,没有什么重要内容,恶意行为被隐藏起来。需要病毒样本动态运行进行解密修复IAT。

查看字符串资源,大部分为未识别的字符串,进一步验证了数据被加密处理的猜想。

第一次静态加载时应该尽量勾选右下角选项,这样IDA会额外加载资源数据动态导入的DLL数据以及默认不加载的静态节数据,以便我们透彻分析样本。

加载完成后,我们可以看到入口点OEP只有两个函数调用

点击进入第一个函数调用,并没有发现有趣的内容,继续点击跟进第一个函数查看内容。

进入函数后发现了有趣的内容,病毒样本貌似在填充一个由ESI寄存器控制的数组。首先传入数组的4字节数据给sub_406817函数,然后将运算结果(eax)的值重新回填给数组,一共循环了160次,计算了640个字节。这与我们熟知的动态解析IAT方法很相似,加上我们之前分析病毒抹去了IAT可以合理猜测该处在做回填IAT表的操作。

使用F5插件查看如下

点击查看该数组内容,发现是.data节的一个全局数组。根据之前的分析运算一共占用640字节。每4字节为一个hash值(经过hash处理的数据通常会将4字节全部占满,所以合理猜测为hash数据)

设置4字节的hash数组,数组元素为160,每个元素占用4字节。

处理后的数据如下

最后我们还需验证下我们的猜想。程序调用动态导入函数的时候,汇编会以间接调用的方式呈现出来,如下形式。我们只需要在后续找到该形式的函数调用,且数据在该数组内即可验证成功。

继续向下查找,可以看到“call 数组基址+数组偏移”的形式的函数调用(数组最大偏移为0x280),该调用在重复调用同一个函数。验证了该静态数组为hash处理过的IAT表。

同时也可以确定第一个函数整体逻辑在修复IAT表,修改变量名。

进入mv_dy_get_api函数查看代码内容,可以看到IDA为我们解析的函数原型如下,虽然数长度相同但是看着很别扭(IDA最强大的就是注释功能,不要小看每条注释。小的注释积累起来可以引发质变)

按Y快捷键修改函数原型如下

继续向下查看代码,将输入的hash进行异或运算得到转换后的hash,又将转换后的hash值高11位提取赋值给临时变量v2。可以看到

继续阅读代码,看到疑似switch/case的判断比较高11位数据。根据不同数据进行执行不同流程中的函数地址赋值,然后跳转到LABEL_29。根据病毒动态加载的经验,第一件事一般是动态获取目标模块的基地址,然后根据基地址解析对应导出函数的位置。所以我们合理假设该switch/case语句是在做获取dll基址的操作。而转换后的高11位hash值代表不同的hash值。

整理后如下

继续向下阅读代码,看到调用了赋值的函数指针pfn_ld_dll,符合获取指定dll的基址的猜想,将临时变量改名为imagebase可以看到v18变量在获取导出符号的地址偏移。

0x3c偏移为_IMAGE_DOS_HEADER的e_lfanew字段,通过该字段值获取imagebase到_IMAGE_NT_HEADERS的偏移。

可以看到_IMAGE_NT_HEADERS一共占用0x18个字节,也就是说0x78中有0x18字节偏移是为了跳过_IMAGE_NT_HEADERS占用的空间,为了获取_IMAGE_OPTIONAL_HEADER结构体偏移0x60 == 0x78 - 0x18的字段值。

查看_IMAGE_OPTIONAL_HEADER结构体。偏移0x60处字段正好为数据目录表的首地址,而数据目录表第0项正好记录着导出符号表的偏移和大小。

查看数据目录表表项结构体如下

可以推出该语句为获取VirtualAddress偏移值

查看导出表结构体

整理出以下内容

按下shift+F1快捷键,切换到本地类型窗口。按快捷键ins,插入一个结构体。

双击导入该结构体

将变量va_offset转换成结构体指针

导入结构体指针类型。

整理得到如下结果。

查看后续代码,因为之前的注释修改,我们可以很清晰的看到函数逻辑。

进入calc_fn_name_hash函数可以看到是对导出函数字符串进行逐字节hash运算。

PE解析API地址整体逻辑如下

接下来继续向上回溯分析加载dll的方法。点开加载dll的函数,可以发现绝大多数函数模板类似,都传入了一个固定hash值进行函数递归。获取api后将v2变量作为参数传递给该动态获取的API并调用执行。根据函数形式结合之前的分析,合理推测0xCB0F8A29为LoadLibraryA函数的hash值。而v2是要加载的dll名称。

继续回溯,发现除了上述模板的调用方式,还有直接传入了静态hash给一个函数,这种方式值得我们额外关注。

继续跟进来到以下函数中,看到有循环猜测正在做某种运算。看到循环中有个判断条件在与一个常数做差,转换成16进制为0x41,正好对应Ascii码表中的‘A’字符。因此可以猜测在比较字符串大小。

我们首先修改v5为chr,接下来就会发现非常神奇的事,找寻其他跟chr关联的变量修改名称让整个逻辑结构一下变得清晰。

继续向上查阅代码可以看到函数sub_404B12,跟进函数内部可以看到熟悉的fs:30h。通过fs寄存器获取PEB

获取PEB基址偏移0xC处的字段值,即LDR地址。

再通过LDR偏移0x14获取InMemoryOrderModuleList地址值。

判断内存模块加载顺序链表是否为空。

InMemoryOrderModuleList是一个双向链表指针,它链接的是LDR_DATA_TABLE_ENTRY结构体中的InMemoryOrderModuleList链表节点。

获取dll的名称(F5插件在此处解析错误,所以使用汇编查看)

如果匹配hash值则返回模块基址

list_entry+0x10为DllBase模块基址

总体逻辑如下

接下来继续分析其他模块加载函数中对字符串的解密函数。首先可以看到解密函数传入了5个参数,第一个参数为一个静态全局数据,第2、3、4个参数全部是确定的常量值。

传入的静态数据

将静态数据处理成数组

进入解密函数查看内容,发现第二个函数将传入的参数重新组合传入。类似计算偏移值。

继续跟入函数,将返回变量名修改为解密可以确定最后一个参数为解密后的字符串数据。

继续跟入第一个函数,可以看到两个明显循环次数256。

汇编形式表现如下。(如果逆向时突然出现两次循环且每次循环次数都为256,后续存在异或操作基本可以猜测为rc4解密算法,这种特性为rc4算法特征)

接下来我们需要进入后续函数寻找异或算法,可以发现字节异或算法。基本可以确定为rc4算法。

首先根据Wiki百科获取rc4算法的伪代码,rc4算法分为初始阶段和加密阶段。初始化阶段首先会创建一个256字节从0递增不重复的数组。第二次循环会循环会根据密钥数组、长度、第一次构建的s盒(用来随机交换加密的数组,这里用S标识)来重新打乱S盒中,用于随机加密数据。由此可以看出,初始化阶段需要一个s盒,一个密钥以及密钥长度作为基本元素。

回到IDA可以清晰看到一个长度为256字节的数组,且无论初始化和加密阶段都使用了该数组。确定该数组为S盒。

由于初始化阶段还需要密钥盒密钥长度,我们先将参数按顺序重命名。

进入初始化函数,可以看到与我们的伪代码逻辑一致,参数传入顺序正确。

对算法进行调整如下

接下来我们查看加密数据部分的伪代码。下面i,j是两个指针。每收到一个字节,就进行while循环。通过一定的算法((a),(b))定位S盒中的一个元素,并与输入字节异或,得到k。循环中还改变了S盒((c))。从该伪代码中需要获取的参数有:打乱顺序的S盒、要加密/解密的数据、数据长度,最后返回的数据是加密/解密后的数组字符串。

与初始化部分类似,首先按照分析和假设修改变量。可以看到整体加密算法逻辑变得清晰

跟进加密函数,根据伪代码调整函数变量内容。

利用交叉引用继续向上回溯来到一下函数中,可以看到调用解密函数的父函数初始的时候就传入了:密钥长度、加密数据长度、加密的数据。而第一个参数密钥是利用基址加偏移的方式计算的,第三个在第一个参数基础上还增加了密钥长度的偏移。但是我们不确定哪个参数是密钥基址哪个是密钥偏移。

继续向上回溯,来到了整体调用的地方,可以看到第一个参数为静态未知数据(第一个参数是固定的),而第二个参数正好类似一个偏移差值。

查看第一个静态数组

调整为数组类型

返回继续调整变量可以看到清晰的函数调用逻辑:

1、传入固定的加密数据首地址+密钥偏移获取密钥地址

2、密钥偏移+密钥长度获取解密数据的地址

在分析完了病毒构建导入表和字符串加密的手法后,我们便可以开始解析IDB数据。核心手法与脱壳类似,就是对IDA未解析的IAT数组进行修复。

所谓动态修复IAT类似动态脱壳,原理是利用病毒自身携带的解密程序,在病毒自动修复完成后中断。将内存状态保存到IDB中进行解析IAT。首先我们要在IDA中设置断点,让IDA调试器运行起来断住。

可以看到iat表已经全部修复为函数地址。

按O快捷键进行符号解析。

使用脱壳插件进行动态修复IAT表

修复IAT后的效果如下。

IDAPython修复脚本是通过对算法的逆向,利用ida提供的python接口编写脚本,从静态数据中解密出我们想要的内容。以下是我编写的IDAPython脚本,为了方便没有做优化有亿点卡。(最好重新加载程序到IDA,并勾选Load Resouce选项)

程序中利用了pefile.py模块,如果当前python环境变量中没有该模块可以通过pip安装。IDAPython调试方法可以参考我的文章https://bbs.pediy.com/thread-267362.htm

IDAPython脚本

除了解析API外,该脚本解密完成后会将解密字符串以注释形式添加到rc4解密函数后,方便我们静态查看病毒后续行为。

因为python的发展,IDAPython也逐渐从python2.x过度到python3.x。IDA作者也在博客中表示过度到新的IDA7.2版本以上的python默认使用3.x并不在支持IDAPython2.x的部分接口。我们在编写脚本的过程中很可能因为使用了老版本的接口导致无法实现我们的功能。

编写时可参考IDAPython官方手册:https://www.hex-rays.com/products/ida/support/idapython_docs/

利用浏览器的搜索功能搜索想要的功能API(不知道有哪些接口就靠猜,比如我想要一个修改函数名的接口我尝试了很多简单词组组合最后找到了idc.set_name),如果某一模块中搜索不到,点击手册左上角的所有模块再次搜索。

还要善用IDAPython API过度表:https://www.hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml

使用方法仍然是浏览器搜索匹配不同版本的接口

可以看到完整的修复IAT后的逻辑,首先动态获取API后再加载ole32模块获取创建COM组件所需接口。

退回到函数主逻辑,可以看到病毒首先修复了IAT表保证程序正常运行

设置错误模式获取错误信息并创建名为Global\01EB1FCA-9835-27F4-DB93-6F722EB23FB4的全局互斥体保证进程唯一性。

如果返回系统错误码显示已存在互斥体则返回标志1。

使用M快捷键找到宏定义对常量进行替换

替换后的样式如下

再次来到主函数,可以看到病毒有两个逻辑函数,暂且分别命名为mv_logic_x。

来到主逻辑1中可以看到以下几个解析的函数与字符串信息。

进入第一个未解析函数,用于F5插件解析错误通过汇编查看内容。可以看到该函数获取PEB地址后分别读取了偏移0xA4和0xA8处地址的低一个字节数据。

在PEB中,0xA4和0xA8分别是当前操作系统的主版本号和次版本号

调整如下,可以看出该函数用于获取当前系统版本号。

返回逻辑可以看出病毒在Vista版本以上的操作系统上运行时会执行

进入sub_404842函数可以看到该函数主要获取当前进程令牌信息。

返回的令牌类型是一个enum类型

根据微软描述一共可以返回如下三个类型。

所以以下代码为判断是否有管理员权限,如果没有则执行以下提权代码。

在判断当前用户进程处于受限权限用户组下时,病毒打开令牌获取用户SID和属性的组合值,总逻辑如下。

首先通过GetTokenInformation函数获取_SID_AND_ATTRIBUTES结构体。该结构体包含了Sid和Sid的属性。

可以看到微软官方形容SID代表一个用户组,而SID的属性用来表示当前用户组启动、禁用以及强制托管的权限属性。SID同时也标志如何使用这些属性。

病毒接下来获取SID中的偏移值,获取SID中Subauthority的第一个四字节值判断权限。

首先我们来观察下SID结构体,该结构体前8字节是用于记录SID的一些信息,后20字节分为5部分,每部分占用4字节表示域账户或组使用的唯一ID也称为SIDs。SIDs中前4个四字节元素用来表示域ID(我理解为网络域名之类的)。最后一个字节表示RID,也就是表示用户组或用户。

根据Identifier Authority字段与后续的域ID组合可以代表出不同的含义。微软将特定的组合称为"Well-know SIDs"。以下是对该类型的介绍

返回函数调用位置使用IDA插件查看内容。可以看到他在比较SIDs的第一个4字节内容。

我们通过计算器换算成10进制,并查询"Well-know SIDs"表,发现为高强制级别的含义,所以我们了解了该if判断仍然是在判断权限。我们可以看到该类型在Vista和Win server 2008中添加,这也印证了病毒为什么要判断系统版本。

接下来病毒会通过动态申请堆内存用于存储当前病毒路径字符串。

以下为病毒申请堆函数的简单分析

可以看到sub_404DC9函数将某个值赋给了变量v4,这里我们可以不用着急分析该函数,因为v4与后续的函数调用有直接关系,可以考虑先分析明显行为反向猜测推导内容。继续向下分析可以看到病毒调用了ShellExecuteExW,并且将v7作为了一个参数。

MSDN官方定义结构体为SHELLEXECUTEINFOW

将v7变量类型修改为SHELLEXECUTEINFOW,可以看到病毒实际上在通过ShellExecuteExW循环调用runas命令提权当前进程。

整理得到函数获取参数字符串逻辑如下。

分析完成前述所有代码后整理提权函数逻辑如下:首先通过令牌分别进行系统信息、令牌首先信息以及令牌权级三个方面进行判断,如果进程处于高度受限的环境下则通过ShellExecuteExW调用runas进程循环提升当前进程权限,否则就陷入提权界面循环中。(如果受害者因为厌烦而点击提权确认则病毒开始加密逻辑)

Runas是一个命令行工具,它会帮助进程提升为管理员权限运行。

接下来我们进入加密逻辑进行静态分析

进入加密逻辑函数首先看到病毒进程设置了线程状态为ES_CONTINUOUS + ES_SYSTEM_REQUIRED

第一个属性代表设置一个状态值,该值在当前病毒逻辑函数中一直有效,直到下一次调用SetThreadExecutionState(ES_CONTINUOUS )函数时该值会被清除。第二个属性通过设置系统计时器强制系统处于工作状态。

接下来病毒会调用函数,使病毒进程模拟管理员账户登录执行病毒逻辑。

首先进入第一个未解析的函数,进入函数可以看到函数内部又一次调用了一个函数,参数为1个常量,要获取的目标进程字符串名和一个函数指针。

继续跟进,病毒创建了一个进程快照通过循环对当前内存中进程信息块进行遍历,并将获取到的环境进程块作为参函数和进程名一起传入函数指针,调用函数指针指向的地址。

再看函数指针指向的函数地址,它将传入的进程字符串名和目标进程字符串进行比较,如果与目标进程字符串命相等则获取进程信息块的进程ID,并将它传入字符串指针向后偏移4字节的地址存储,所以我们可以判断出函数传入的参数实际上为病毒作者设计的一个结构体,拥有目标进程字符串指针和进程ID两个元素。

定义结构体

修改后参数如下

使用Vistual Studio查看宏对应的值计算出0xF01FF代表所有令牌权限


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-5-17 12:15 被独钓者OW编辑 ,原因:
收藏
免费 18
支持
分享
打赏 + 2.00雪花
打赏次数 1 雪花 + 2.00
 
赞赏  kanxue   +2.00 2021/05/17 精品文章~
最新回复 (19)
雪    币: 3776
活跃值: (5544)
能力值: ( LV7,RANK:115 )
在线值:
发帖
回帖
粉丝
2



这个公式可以拆解成以下的形式


B是由查表操作得来的,可以简化成S(A)的形式。又因为轮密钥加只是单纯的异或相当于,所以最后通过线代表示出来的简化公式如下。该公式为SubBytes、MixColumns、AddRoundKey三个轮操作的合并结果。


由于轮密钥需要通过密钥数据动态计算,所以不做识别加密算法的特征查询。

而S盒与Golois域矩阵却是固定的,但是S盒是一个16 x 16的矩阵(256字节)根据分块矩阵乘法,所以衍生出了4个T盒,用于查表。表达式如下。4个T盒常量是固定的,所以在优化的AES算法中,我们用T盒作为特征查找加密轮位置。



最终简化的轮操作公式如下,计算机中通常将每一个列向量看作一个整体,储存为一个32位无符号整数(Int32),因此每一个T表都是一个有2^8=256个Int32组成的数组。



在实践中,T表一般提前编写程序算出,然后作为一个常量数组硬编码在AES的软件实现代码中。这样,就将繁琐的伽罗瓦域上的运算和矩阵运算变成了对于计算机而言非常简单高效的查表和按位运算。我们随便在网上找一个优化过的项目查看T盒常量,例如https://android.googlesource.com/platform/external/openssh/+/idea133/rijndael.c





输入第一个常量0xc66363a5

可以看到以下常量。


根据交叉引用便可以定位到病毒优化过的AES轮操作位置。病毒作者往往都会使用开源算法对数据进行处理,所以可以通过github查询是否存在源码。





Salsa20算法

Salsa20与ChaCha算法类似,都是由Daniel J. Bernstein 开发的算法。Salsa20在2005年提出,而ChaCha在2008年提出,它相较Salsa20算法使用了新的轮函数,使其在一些架构上的扩散性和有效性增加。这两种算法都构建了一个利用基于add-rotate-xor(ARX,32位加——旋转——位异或)的伪随机函数。改算法核心函数使用了一个32字节(256位)密钥、一个8字节(64位)Nonce(只使用一次的随机数或伪随机数)和一个8字节(64位)计数器三个参数运算出一个64字节(512位)的密钥流块。我们这里以https://github.com/andres-erbsen/salsa20项目作为代码对比。







初始化State结构体

在Salsa20算法需要传入密钥、Nonce、字符串常量和明文数据流的索引值(也就是数组索引)作为参数每轮运算产生新的State块。该State块是一个4 x 4的矩阵。初始化的State块由密钥、Nonce、字符常量和要加密的数据流索引值组成。例如,下图中每个颜色的小方块代表4字节。其中明文数据流的索引值是红色的区域一共8字节,将索引高4位和低四位分别计算成两个4字节元素。常量字符串是白色部分的"expand 32-byte k",拆解成4字节块为"expa", "nd 3", "2-by"和"te k"与剩余三个参数合并成一个待加密的状态块。经过多轮运算计算出加密的64字节数据。因为Salsa20系列算法都使用"expand 32-byte k"字符串,所以可以将该字符串数据作为识别Salsa20系列算法的特征(ChaCha算法也使用该字符串)



代码实现如下:

注意:index为要被加密的数据在数组中的索引,在处理成状态矩阵数据时分别将索引值和索引右移32位值处理成4字节数据填充。






核心运算

Salsa20根据输入的State状态块进行加密运算。加密的核心函数是一个只处理16字节数据的函数QR(a, b, c, d),它每次只处理State数据中的某一行或某一列数据(a,b,c,d是输入列或行的数组元素,占用4字节)。QR的计算方法如下。我们可以看到固定的位移值,在算法中如果碰到连续固定的位移值需要注意是否是Salsa20加密算法。

b ^= (a + d) <<< 7;
c ^= (b + a) <<< 9;
d ^= (c + b) <<< 13;
a ^= (d + c) <<< 18;


轮加密操作

在Salsa20中每轮加密都需要区分奇数轮和偶数轮。奇数轮计算State矩阵的每一列数据,偶数轮计算State矩阵的每一行数据,如果每轮行列数据都进行计算则称为双轮。如下为C代码表示,数字代表在State数组中的索引对应元素。

// Odd round奇数轮
QR( 0,  4,  8, 12)	// column 1
QR( 5,  9, 13,  1)	// column 2
QR(10, 14,  2,  6)	// column 3
QR(15,  3,  7, 11)	// column 4
// Even round偶数轮
QR( 0,  1,  2,  3)	// row 1
QR( 5,  6,  7,  4)	// row 2
QR(10, 11,  8,  9)	// row 3
QR(15, 12, 13, 14)	// row 4


以下代码是Wiki百科为我们提供的代码实现,它直接使用了双轮思想。因为Salsa20代表计算20轮,而下面代码一共执行了10次循环,按照奇数轮和偶数轮顺序执行。注意代码最后一行将加密后的State块与初始化的State块做和得到最终的加密数据,因为20次轮操作是可逆的。

#include <stdint.h>
#define ROTL(a,b) (((a) << (b)) | ((a) >> (32 - (b))))
#define QR(a, b, c, d)(		\
	b ^= ROTL(a + d, 7),	\
	c ^= ROTL(b + a, 9),	\
	d ^= ROTL(c + b,13),	\
	a ^= ROTL(d + c,18))
#define ROUNDS 20
 
void salsa20_block(uint32_t out[16], uint32_t const in[16])
{
	int i;
	uint32_t x[16];

	for (i = 0; i < 16; ++i)
		x[i] = in[i];
	// 10 loops × 2 rounds/loop = 20 rounds
	for (i = 0; i < ROUNDS; i += 2) {
		// Odd round
		QR(x[ 0], x[ 4], x[ 8], x[12]);	// column 1
		QR(x[ 5], x[ 9], x[13], x[ 1]);	// column 2
		QR(x[10], x[14], x[ 2], x[ 6]);	// column 3
		QR(x[15], x[ 3], x[ 7], x[11]);	// column 4
		// Even round
		QR(x[ 0], x[ 1], x[ 2], x[ 3]);	// row 1
		QR(x[ 5], x[ 6], x[ 7], x[ 4]);	// row 2
		QR(x[10], x[11], x[ 8], x[ 9]);	// row 3
		QR(x[15], x[12], x[13], x[14]);	// row 4
	}
	for (i = 0; i < 16; ++i)
		out[i] = x[i] + in[i];	//加密数据与初始化State相加得到加密后数据
}

Salsa20加密文件流完整操作

Salsa20首先输入要加密的明文数据,每64字节的明文就会产生1个64字节State加密块。(因为初始化State块受加密数据流的索引影响,也就是红色Pos区域)。


image.png

获取到论加密操作生成的最终State块后,明文与State块逐字节异或生成最终的密文。我们可以看到以下代码


image.png


完整Salsa20算法如下。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> // we use 32-bit words

// rotate x to left by n bits, the bits that go over the left edge reappear on the right
#define R(x,n) (((x) << (n)) | ((x) >> (32-(n))))

// addition wraps modulo 2^32
// the choice of 7,9,13,18 "doesn't seem very important" (spec)
#define quarter(a,b,c,d) do {\
	b ^= R(d+a, 7);\
	c ^= R(a+b, 9);\
	d ^= R(b+c, 13);\
	a ^= R(c+d, 18);\
} while (0)

void salsa20_words(uint32_t* out, uint32_t in[16]) {
	uint32_t x[4][4];
	int i;
	for (i = 0; i < 16; ++i) 
		x[i / 4][i % 4] = in[i];
	for (i = 0; i < 10; ++i) { // 10 double rounds = 20 rounds
		// column round: quarter round on each column; start at ith element and wrap
		quarter(x[0][0], x[1][0], x[2][0], x[3][0]);
		quarter(x[1][1], x[2][1], x[3][1], x[0][1]);
		quarter(x[2][2], x[3][2], x[0][2], x[1][2]);
		quarter(x[3][3], x[0][3], x[1][3], x[2][3]);
		// row round: quarter round on each row; start at ith element and wrap around
		quarter(x[0][0], x[0][1], x[0][2], x[0][3]);
		quarter(x[1][1], x[1][2], x[1][3], x[1][0]);
		quarter(x[2][2], x[2][3], x[2][0], x[2][1]);
		quarter(x[3][3], x[3][0], x[3][1], x[3][2]);
	}
	for (i = 0; i < 16; ++i) 
		out[i] = x[i / 4][i % 4] + in[i];
}

// inputting a key, message nonce, keystream index and constants to that transormation
void salsa20_block(uint8_t* out, uint8_t key[32], uint64_t nonce, uint64_t index) {
	static const char c[16] = "expand 32-byte k"; // 初始化常量块
#define LE(p) ( (p)[0] | ((p)[1]<<8) | ((p)[2]<<16) | ((p)[3]<<24) )
	uint32_t in[16] = { LE(c),            LE(key),    LE(key + 4),        LE(key + 8),
					   LE(key + 12),       LE(c + 4),    nonce & 0xffffffff, nonce >> 32,
					   index & 0xffffffff, index >> 32,  LE(c + 8),          LE(key + 16),
					   LE(key + 20),       LE(key + 24), LE(key + 28),       LE(c + 12) };
	uint32_t wordout[16];
	salsa20_words(wordout, in);//轮加密State
	int i;
	for (i = 0; i < 64; ++i) //将加密用的数组元素转换成异或
		out[i] = 0xff & (wordout[i / 4] >> (8 * ((i+1) % 4)));
}

// enc/dec: xor a message with transformations of key, a per-message nonce and block index
void salsa20(uint8_t* message, uint64_t mlen, uint8_t key[32], uint64_t nonce) {
	int i;
	uint8_t block[64];
	for (i = 0; i < mlen; i++) {
		if (i % 64 == 0) 
			salsa20_block(block, key, nonce, i / 64);
		message[i] ^= block[i % 64];
	}
}

//Set 2, vector# 0:
//                         key = 00000000000000000000000000000000
//                               00000000000000000000000000000000
//                          IV = 0000000000000000
//               stream[0..63] = 9A97F65B9B4C721B960A672145FCA8D4
//                               E32E67F9111EA979CE9C4826806AEEE6
//                               3DE9C0DA2BD7F91EBCB2639BF989C625
//                               1B29BF38D39A9BDCE7C55F4B2AC12A39

int  main() {
	uint8_t key[32] = { 0 };
	uint64_t nonce = 0;
	uint8_t msg[64] = { 0 };
	memset(msg, 0x90, 64);
	salsa20(msg, sizeof(msg), key, nonce);
	int i; for (i = 0; i < sizeof(msg); ++i)
		printf("%02X", msg[i]); printf("\n");
}


ChaCha家族算法

chacha家族算法与Salsa20算法很相似。它也是利用State加密块对明文数据流进行加密。只不过它修改了State状态块数据的组成元素和核心运算QR函数部分并减少了轮数。我们可以通过下图比较两个算法的State块之间的区别



image.png



ChaCha核心QR参数改为以下代码,这里的16、12、8、7可以帮助我们逆向时区分ChaCha和Salsa20算法。

 a += b; d ^= a; d <<<= 16;
 c += d; b ^= c; b <<<= 12;
 a += b; d ^= a; d <<<= 8;
 c += d; b ^= c; b <<<= 7;

ChaCha轮运算减少

#define ROTL(a,b) (((a) << (b)) | ((a) >> (32 - (b))))
#define QR(a, b, c, d) (			\
	a += b,  d ^= a,  d = ROTL(d,16),	\
	c += d,  b ^= c,  b = ROTL(b,12),	\
	a += b,  d ^= a,  d = ROTL(d, 8),	\
	c += d,  b ^= c,  b = ROTL(b, 7))
#define ROUNDS 20
 
void chacha_block(uint32_t out[16], uint32_t const in[16])
{
	int i;
	uint32_t x[16];

	for (i = 0; i < 16; ++i)	
		x[i] = in[i];
	// 10 loops × 2 rounds/loop = 20 rounds
	for (i = 0; i < ROUNDS; i += 2) {
		// Odd round
		QR(x[0], x[4], x[ 8], x[12]); // column 0
		QR(x[1], x[5], x[ 9], x[13]); // column 1
		QR(x[2], x[6], x[10], x[14]); // column 2
		QR(x[3], x[7], x[11], x[15]); // column 3
		// Even round
		QR(x[0], x[5], x[10], x[15]); // diagonal 1 (main diagonal)
		QR(x[1], x[6], x[11], x[12]); // diagonal 2
		QR(x[2], x[7], x[ 8], x[13]); // diagonal 3
		QR(x[3], x[4], x[ 9], x[14]); // diagonal 4
	}
	for (i = 0; i < 16; ++i)
		out[i] = x[i] + in[i];
}


病毒实例算法定位

我们回到当前样本,经过之前分析我们已知Salsa20和ChaCha算法都包含常量字符串"expand 32-byte k",根据这个线索我们可以直接在IDA中搜索该字符串,但是并没有搜到相关内容。


我们可以换一种角度思考,该字符串可能被处理成字节数组形式(16进制)。我们再次搜索该字符串的16进制数据。

0x61707865, 0x3320646E, 0x79622D32, 0x6B206574

搜索第一个4字节字符串数据。


定位到两个数据


找到静态常量值位置



将数据转换成字符串观察,正是算法特征数据。


根据交叉引用找到核心算法位置,看到常量7、9、13、18正是Salsa20的核心运算。


我们观察双轮轮数为10,奇数轮偶数轮各为10轮,一共20轮。由此识别算法为Salsa20。


加密算法库的识别技巧

由于椭圆曲线加密算法内容篇幅过长,这里就不在详细讲述其原理。如果大家想要了解更多细节可以参考这个gitbook链接,该书以数学和开发人员角度思考密码学十分值得学习。

https://cryptobook.nakov.com/asymmetric-key-ciphers/elliptic-curve-cryptography-ecc#elliptic-curves-over-finite-fields-calculations

经过上述我们的分析其实我们已经能确定了加密算法所在的几个函数。在加密函数中我们可以看到可疑的加密函数,因为这个函数传入了加密用的key引起了我们的注意。


我们可以根据不断跟进函数内部,观察密钥作为参数的函数。


多次跟进可以看到来到sub_408B69这个函数内部。


查看第一个函数可以看到大量的位移运算,这时需要引起我们的注意。因为根据我们分析之前的算法我们会得到一个规律,加密操作往往是通过位移、异或等操作体现的。该处很可能是在做某种加密。根据之前分析算法总结的规律可能是在利用密钥初始化。


跟进第二个函数,我们看到了非常明显的循环异或位移,而且有明显的常量这里再次引起我们的注意。很可能在做核心轮加密运算,那么我们可以尝试在算法中寻找常量特征。


最终我们在一个for循环中发现了一处常量值。


使用谷歌搜索,切换搜索关键字。可以看到这是与椭圆曲线加密算法相关的内容。有很大可能使用的是Curve25519这种曲线做运算


按照这个思路我们继续搜索Curve25519相关的源码(如果我是开发人员我不会花费大量时间去开发一个已经存在的算法,而是选择Github找到源码项目)


打开源码项目搜索关键常量121665,可以定位到源码位置。对比两个代码可以发现很多相似之处。稍有不同点就是IDA反编译出了一个循环。


我们再根据搜索功能找源码函数的内容,发现正是IDA编译的循环。由此我们可以确定该处使用了ECC密码系统进行加密处理。具体细节可以参照源码。


参考链接

https://blog.amossys.fr/sodinokibi-malware-analysis.html#figure3

https://www.intel471.com/blog/revil-ransomware-as-a-service-an-analysis-of-a-ransomware-affiliate-operation/

https://www.vergiliusproject.com/kernels/x86/Windows%207/SP1

https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works

https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/cc771525(v=ws.11)

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908a-554ab29148ab

https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/security-identifiers#what-are-security-identifiers

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/81d92bba-d22b-4a8c-908a-554ab29148ab

https://www.installsetupconfig.com/win32programming/accesscontrollistaclexample6_1.html

https://bbs.pediy.com/thread-266419.htm

https://bbs.pediy.com/thread-265692.htm

https://gchq.github.io/CyberChef/

https://en.wikipedia.org/wiki/Advanced_Encryption_Standard

https://github.com/dhuertas/AES/blob/master/aes.c

https://github.com/polymorf/findcrypt-yara/issues/34

https://android.googlesource.com/platform/external/openssh/+/idea133/rijndael.c

https://zhuanlan.zhihu.com/p/42264499

https://www.anquanke.com/post/id/85656

https://docs.microsoft.com/zh-cn/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f

https://docs.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports

https://en.wikipedia.org/wiki/Salsa20

https://github.com/andres-erbsen/salsa20

https://zhuanlan.zhihu.com/p/36326221

https://zhuanlan.zhihu.com/p/44743146

https://zhuanlan.zhihu.com/p/66794410

https://cryptobook.nakov.com/asymmetric-key-ciphers/elliptic-curve-cryptography-ecc


最后于 2021-5-17 19:08 被独钓者OW编辑 ,原因:
2021-5-14 21:20
0
雪    币: 47147
活跃值: (20415)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
3
文章很详细,感谢分享!
2021-5-17 12:10
0
雪    币: 3776
活跃值: (5544)
能力值: ( LV7,RANK:115 )
在线值:
发帖
回帖
粉丝
4
kanxue 文章很详细,感谢分享!
谢谢版主大大
2021-5-17 12:13
0
雪    币: 1475
活跃值: (14652)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
5
这钵啊,这钵是紧跟时事
2021-5-17 13:05
1
雪    币: 754
活跃值: (180)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
太详细了, 论坛发贴标准模范
2021-5-17 17:16
0
雪    币: 3776
活跃值: (5544)
能力值: ( LV7,RANK:115 )
在线值:
发帖
回帖
粉丝
7
道之为一 太详细了, 论坛发贴标准模范
我只是把我遇到的问题总结出来了
2021-5-17 18:38
0
雪    币: 3903
活跃值: (3564)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
8
只能膜拜了,用了很长时间吧
2021-5-18 15:10
0
雪    币: 3776
活跃值: (5544)
能力值: ( LV7,RANK:115 )
在线值:
发帖
回帖
粉丝
9
APT_华生 只能膜拜了,用了很长时间吧
是的,查了大量资料去解决问题
2021-5-18 15:19
0
雪    币: 3307
活跃值: (3524)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
10
啊这,详细的一批,下次我也要这样写了。。。
2021-5-19 09:34
1
雪    币: 1475
活跃值: (14652)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
11
REvil5月10号出了个Prometheus勒索,.NET混淆的
2021-5-19 16:29
0
雪    币: 13
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
历时一个小时10分钟看完,感受如下:一、楼主很强大,很细心。 二、这个病毒设计的还是很缜密的。三、看完了自己就晕了,知识量不够,好多地方看不懂。  但是获益非浅,还是自己的知识面不够广。
2021-5-25 09:30
0
雪    币: 3776
活跃值: (5544)
能力值: ( LV7,RANK:115 )
在线值:
发帖
回帖
粉丝
13
醉卧品香茗 历时一个小时10分钟看完,感受如下:一、楼主很强大,很细心。 二、这个病毒设计的还是很缜密的。三、看完了自己就晕了,知识量不够,好多地方看不懂。 但是获益非浅,还是自己的知识面不够广。
可以参考下我下面列出的参考链接,那里有我查到的资料,也是对样本一些细节的解释
2021-5-25 15:53
1
雪    币: 226
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
楼主很牛逼,我看不懂!慢慢拜读!
2021-12-15 20:22
0
雪    币: 576
活跃值: (2035)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
感谢分享,很有时间系列
2021-12-16 11:05
0
雪    币: 423
活跃值: (501)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
16
收下我的膝盖吧...第一次见分析勒索病毒这么细致!膜拜大佬
2021-12-16 11:30
0
雪    币: 259
活跃值: (3475)
能力值: ( LV5,RANK:75 )
在线值:
发帖
回帖
粉丝
17
复现完毕 一人血书求更新
2023-12-14 14:21
0
雪    币: 97
活跃值: (141)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
18
感谢分享
2023-12-31 18:58
0
雪    币: 259
活跃值: (3475)
能力值: ( LV5,RANK:75 )
在线值:
发帖
回帖
粉丝
19
哥 查熵那个工具叫啥来着我又又又又又忘了QAQ
2024-3-22 17:53
0
雪    币: 259
活跃值: (3475)
能力值: ( LV5,RANK:75 )
在线值:
发帖
回帖
粉丝
20
大河向东流哇 哥 查熵那个工具叫啥来着我又又又又又忘了QAQ
pestudio 这次记住了
2024-3-22 17:57
0
游客
登录 | 注册 方可回帖
返回
//