样本基本信息

样本sha256:d7c686ce3a9e4e02a483d2e7ada66e6c8436c75cc8c926a01bf041db60b8ee2d

在线沙箱检测结果:

https://s.threatbook.com/report/file/d7c686ce3a9e4e02a483d2e7ada66e6c8436c75cc8c926a01bf041db60b8ee2d

混淆

这篇文章主要是描述样本混淆相关的内容,主要包括定位加密数据,判断解密手法

Q:如何根据混淆来判断是恶意程序对抗分析还是正常的程序被避免逆向分析呢?

A:综合现实来看,更多的是恶意程序会给自己加上混淆

而正常程序通常是加强壳避免被逆向分析

现实中的很多程序没有壳

Q:混淆的原理是什么呢?

A:将想要隐藏执行的PE文件经过加密之后植入自身的内部,自身运行 的时候,对其执行解密操作并执行

Q:怎么去混淆呢?

A:常见的去混淆的手法,恶意程序想要解密的话,会分配一块内存,在这块内存中保存解密之后的结果,因此可以在诸如VirtualAlloc这类的内存分配函数上下断点,具体指令 bp VirtualAlloc

Q:那么此处下的断点是哪种断点呢?

A:硬件,写入断点

Q:解密完成之后要怎么操作呢?

A:在数据区选中,将解密数据dump出来即可

Q:混淆都是这样的手段吗?

A:上述是一些简单的情况,一些复杂的混淆,会有多个开辟空间的行为,比如说会在新开辟的内存空间里面写入一些机器码执行,然后这段机器码会再开辟新内存,再次写入机器码执行……,多次分配并解密之后才能获得真正的PE文件

Q:在分析混淆时,有哪些可能遇到的难题呢?

A1:硬件断点的个数限制,在遇到多次分配解密时需要及时将不需要的硬件断点删除

A2:判断哪一个VirtualAlloc是目的命中断点,可以通过查看在调用VirtualAlloc时开辟空间的大小,如果开辟的空间过小(比如 说0xE0),而PE文件一般解密后的数据不会太小,所以可以直接F9跳转到下一处调用.

具体分析

定位加密数据来源

首先在X32dbg中载入样本文件

Q:怎么在call VirtualAlloc处下断点

首先Xdbg下方的命令行窗口中输入“bp VirtualAlloc”命令,在VirtualAlloc处下断点

查看断点设置结果

第二个断点就是我们设置的断点,是在VirtualAlloc的起始代码处设置的断点,观察VirtualAlloc的地址"773CF3C0"属于系统领空,

Q:函数的最初的汇编代码是“mov edi,edi”,之后才是建立堆栈的函数,这条指令的作用是将EDI寄存器的值赋值给EDI本身,本身没有什么实际作用(在程序中与NOP指令意义相同),那这条指令存在的含义是什么呢?

A:方便进行热补丁技术,在运行时修改函数的行为,具体的操作就是,将mov edi,edi修改为短跳转指令(两个字节),将mov edi,edi指令之前的五个NOP修改为长跳转指令(五个字节),调用函数时,首先通过短跳转跳转到长跳转指令处,然后通过长跳转跳转到目的地址处。

长跳转中的目的地址如何计算?我将在其他的文章中说明

参考

函数开始处的MOV EDI, EDI的作用_mov edi,edi_swanabin的博客-CSDN博客

继续刚才的问题,VirtualAlloc的地址"773CF3C0"属于系统领空,那如何回到用户空间去查看传递给VirtualAlloc的参数呢?首先运行到函数返回处

然后单步执行回到用户领空

此时分配的空间的首地址已经保存在eax寄存器中,为了避免多级混淆需要一直在数据窗口中跟随,我们不妨看一下传递给VirtualAlloc的参数,通过一些分配空间的大小来初步判断分配的空间是否可能用于存储解密后的PE文件

LPVOID VirtualAlloc(
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);

可以看到第二个参数表示申请分配空间的大小,在通过push压栈传参时,参数传递顺序是从右往左进行传递的,观察四个push,此处分配的size为0xE0,也就是224字节,而一般的PE文件不会这么小,初步判定这次分配空间不是用来解密最后的PE的,F9继续执行命中VirtualAlloc。

第二处位置的VirtualAlloc想要分配903680字节的空间,符合一个PE文件的大小

F8执行call VirtualAlloc指令,选中eax寄存器中的值在内存窗口中转到

在0x0295000处下一个硬件访问的断点。

查看断点

接下来运行程序

程序暂停在0x0019F986地址处,并且分配的空间已经写入了一个字节“E8”

这说明在0x19F986的上一个位置完成的是向分配的内存中写入E8的操作

观察前面的指令

mov byte ptr ds:[eax+edi],dl

首先是将dl中的值赋给了eax+edi,此时的寄存器栏

也就是02950000+00000000=02950000,申请分配内存的基址

也就是将E8赋给分配空间的第一个字节

此时需要找到dl中的数据来源,再上一条指令:mov dl,byte ptr ds:[ecx]

说明数据来源是ecx寄存器中保存的地址:004020B0

在内存2中跟随

查看内存2中的数据如下

004020B0处的第一个字节就是E8,初步猜测,解密数据来源就是004020B0

观察命中硬件访问断点的当前代码

mov ecx, dword ptr ss:[ebp-0x8]

可以看到此处就是将ebp-8处的值(0),赋给ecx,即ecx=0

接下来是针对edx的赋值,也就是将0x0019F61c处的内容赋值给edx,即edx=004020B0

接下来是inc edx,edx自增,相当于指针后移

然后再将自增后的值放回ebp+ecx*4-54对应的地址处

然后是ecx的自增,并将ecx与8进行比较,且ecx初值为0,初步判定ecx作为循环次数

如果ecx==8,那么将对ecx进行异或清零,

如果ecx!=8,那么将会继续执行语句

接下来是将ebx+10中的值赋给edx

即edx=0xDCA00,即最终的循环次数

此时edi中保存的值是0

接下来edi自增,并将edx与edi进行比较,这是一个循环,并且循环次数较大,为0xDCA00

运行到0x0019F9A6处时,再跳转到0x19F97D,

根据上述两个循环判断,即存在一个大循环,大循环中包括多个周期,每个周期包含了8次循环

每次循环将数据源中的数据拷贝到申请分配的空间中

然后跳转到循环开始的地方0x0019F97D

完整的循环代码如下

0019F97D | 8B4C8D AC                | mov ecx, dword ptr ss:[ebp+ecx*4-0x54]          |
0019F981 | 8A11                     | mov dl, byte ptr ds:[ecx]                       |
0019F983 | 881438                   | mov byte ptr ds:[eax+edi], dl                   |
0019F986 | 8B4D F8                  | mov ecx, dword ptr ss:[ebp-0x8]                 |
0019F989 | 8B548D AC                | mov edx, dword ptr ss:[ebp+ecx*4-0x54]          |
0019F98D | 42                       | inc edx                                         |
0019F98E | 89548D AC                | mov dword ptr ss:[ebp+ecx*4-0x54], edx          |
0019F992 | 41                       | inc ecx                                         |
0019F993 | 83F9 08                  | cmp ecx, 0x8                                    |
0019F996 | 894D F8                  | mov dword ptr ss:[ebp-0x8], ecx                 |
0019F999 | 75 05                    | jne 0x19F9A0                                    |
0019F99B | 33C9                     | xor ecx, ecx                                    |
0019F99D | 894D F8                  | mov dword ptr ss:[ebp-0x8], ecx       |
0019F9A0 | 8B53 10                  | mov edx, dword ptr ds:[ebx+0x10]                |
0019F9A3 | 47                       | inc edi                                         |
0019F9A4 | 3BFA                     | cmp edi, edx                                    |
0019F9A6 | 72 D5                    | jb 0x19F97D                                     |

之前初步判定的数据来源是0019F61C处存储的004020B0

下一次的循环中发现将0019F61C+4即0019F620地址中的内容004D6A10赋值给ecx寄存器,

对应的汇编为:mov ecx, dword ptr ss:[ebp+ecx*4-0x54]

然后将004D6A10处的第一个字节先保存在dl中,然后赋值到新分配的缓冲区eax+edi中

再利用之前保存在栈中的内容[ebp-8]恢复ecx,为计数器,

之后再利用edx实现0019F620处存储的地址值增1,

综上所述,得出结论,ecx的循环次数为8代表是有8个数据来源,存储这些来源地址的位置为0019F61C,每个数据来源占4个字节空间

在内存中查看0019F61C中的内容

通过上述步骤我们已经清楚了数据来源问题

解密过程

接下来尝试解决数据是如何解密的

Q:如何定位到解密区域的代码?

A:F9直接运行即可,之前在分配的地址空间中已经设置了硬件访问断点,解密时这个位置的数据会修改,就会命中硬件断点,命中之后程序暂停在如下位置:

注意到此处存在一个循环

首先将edi的值赋为0

然后将ebx+edi 53FA20+0地址处的内容赋给cl

将cl中的内容与eax+edx地址处的内容进行异或,此处的eax就是分配空间的基址,edx值为0

第一次异或:将edi+ebx地址处的一个字节与分配空间的一个字节异或,16个为一组与

0x0053FA20-----0x0053FA3F之间的数据异或。

将cl中的内容再与dl的值进行异或,dl是edx的低8位,edx中保存的当前数据是所有数据中的第几个数据,比如说第一个数据,edx的值就是0,第二个数据,edx的值就是1,……

第二次异或:在第一次异或的基础上,与当前字节的偏移异或

edi自增,与0xF比较,此处的edi应该是计数器,与之前的获取数据来源的循环类似,

此处可能会使用到0x0053FA20-----0x0053FA3F之间的数据

if edi==15,edi与0x100进行and操作,其实就是将edi的值清零

if edi!=15,继续执行操作,将cl中保存的解密之后的数据覆盖分配空间中的源数据

Q:解密的原理是什么?

A:两次的异或操作。

第一次异或操作从即原始数据0x02950000中,每16个数据为一组,分别与 0x0053FA20-----0x0053FA3F的数据异或

第二次异或,在第一次异或的基础上,与当前字节的偏移异或,位置偏移只取dl中的值

遇到的问题:

在调试的过程中继续运行出现了如下异常

Logo

加入「COC·上海城市开发者社区」,成就更好的自己!

更多推荐