note20210509

软件保护技术

防范算法求逆

堡垒战术

  • MD5算法

为了确保完整性,有时会在msg末尾加上数字签名等。

  • RSA算法

一般用于软件保护。

常见的实现:

1
2
R = U^d mod n //注册机
U = R^e mod n //验证函数

算法本身没啥问题,但是使用RSA仍有一定的风险:

  1. 使用第三方公用代码,这些代码可能有漏洞;
  2. 使用不同e,d但是相同N导致共模组攻击;
  3. 若解密端使用的函数运用了中国剩余定理就会暴露p,q;
  4. 一些伪随机数产生器会产生相同的随机数序列,若使用了这些生成器生成N,可能会暴露p,q;
  5. RSA使用过程中若使用的p,q为某些特殊素数,就会产生弱密钥,产生风险;

游击战术

  • 化整为零

即将要验证的内容分段进行验证函数。

  • 虚虚实实

R分段成R1,R2,R3,R4……后,可以将验证的值U与这些分段关联起来,在运用一些方法使得R和U不呈现一一对应的关系。

  • 战略转移
  1. 内存复制;
  2. 写入注册表或文件;
  3. 同时将注册码复制到多个地址;
  4. 突然变化转移注册码的方法;
  5. 随机使用上述方案;

抵御静态分析

花指令

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<iostream>
#include<windows.h>
using std::cout;

int main() {

int t;

__asm {
xor eax, eax
test eax, eax
jz label1
jnz label1

label1:
xor eax,3
add eax,4
xor eax,5
mov t,eax
}
cout << "eax:" << t << "\n";

}

OD反汇编的代码:

1
2
3
4
5
6
7
00412A58    33C0            xor eax,eax
00412A5A 85C0 test eax,eax
00412A5C 74 02 je short junk_ins.00412A60
00412A5E 75 00 jnz short junk_ins.00412A60
00412A60 83F0 03 xor eax,0x3
00412A63 83C0 04 add eax,0x4
00412A66 83F0 05 xor eax,0x5

加入花指令后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
#include<windows.h>
using std::cout;

int main() {

int t;

__asm {
xor eax, eax
test eax, eax
jz label1
jnz label1
_emit 0e8h

label1:
xor eax,3
add eax,4
xor eax,5
mov t,eax
}
cout << "eax:" << t << "\n";

}

OD反汇编的代码:

1
2
3
4
5
6
7
8
9
10
11
00412A58    33C0            xor eax,eax
00412A5A 85C0 test eax,eax
00412A5C 74 03 je short junk_ins.00412A61
00412A5E 75 01 jnz short junk_ins.00412A61
00412A60 E8 83F00383 call 83451AE8
00412A65 c00483 f0 rol byte ptr ds:[eax*4+ebx],0xf0
00412A69 05 8945F468 add eax,0x68F44589
00412A6E 309B 41008BF4 xor byte ptr ds:[ebx-0xB74FFBF],bl
00412A74 8B45 F4 mov eax,dword ptr ss:[ebp-0xC]
00412A77 50 push eax
00412A78 68 349B4100 push junk_ins.00419B34 ; ASCII "eax:"

可以看到插入的数据0xe8被汇编器解析成了call指令。

使用dejunk code插件去除花指令后:

1
2
3
4
5
6
7
8
9
10
00412A58    33C0            xor eax,eax
00412A5A 85C0 test eax,eax
00412A5C 90 nop
00412A5D 90 nop
00412A5E 90 nop
00412A5F 90 nop
00412A60 90 nop
00412A61 83F0 03 xor eax,0x3
00412A64 83C0 04 add eax,0x4
00412A67 83F0 05 xor eax,0x5

使用合理的地址将花指令所在的地址“合理化”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<iostream>
#include<windows.h>
using std::cout;

int main() {

int t;

__asm {
xor eax, eax
test eax, eax
jz label1
jnz label0

label0:
_emit 0e8h

label1:
xor eax,3
add eax,4
xor eax,5
mov t,eax
}
cout << "eax:" << t << "\n";

}

此时dejunk code去除花指令的插件失败了:

SMC技术实现

Self-Modifying Code技术,可以在程序执行时进行动态解密需要执行的代码。

信息隐藏

对一些关键的字符串信息之类的进行加密隐藏。

简单的多态变形技术

多态即将病毒进行编码,运行时使用特殊的解码器对病毒进行解码。

变形就是指令替换,如将add eax,5替换成5个inc eax

混淆通常就是使用花指令和无用的代码来进行代码的混淆。

文件完整性校验

CRC32的方法

  1. 计算文件头开始到文件末尾的CRC32的值A;
  2. 读取存储在文件头前的之前的CRC32值B;
  3. 将A和B进行比较;

校验和

PE的可选映像头中的Checksum字段,可使用MapFileAndCheckSumA函数来测试文件的校验和。

内存映像校验

  1. 对整个代码数据进行校验;

就是计算内存映射中文件的CRC32的值,然后和存储的CRC32值进行比较。

  1. 校验内存代码片段;

仅计算关键代码段的CRC32的值,然后和存储的CRC32值进行比较即可。

代码与数据结合

感觉就是把SMC技术和注册码相结合。

将关键代码括起来方便处理:

1
2
3
begindecrypt: 
......//打开文件的函数
enddecrypt:

加密方式就是一个异或:

1
2
3
4
5
6
7
8
9
10
11
//对输入的注册码进行一定的变换,得到密钥k ,k = F(注册码)
k=1;
GetDlgItemText(hWnd,IDC_TXT0,szBuffer,sizeof(szBuffer)/sizeof(TCHAR)+1);
for (i=0;i<strlen(szBuffer);i++)
{
k = k*6 + szBuffer[i];
}

......
*ptr=(*ptr)^k;//加密处
......

解密反过来就行:

1
2
3
4
5
while(Size--)
{
*pData=(*pData)^value;
pData++;
}