VMP3.2授权分析
多数加壳工具都会提供许可保护功能,来方便用户进行快速的软件产品授权发布。当一款这样的软件被破解后,其他同类软件也会遭到严重威胁,城门失火,殃及池鱼。这里分析一下VMP3.2最新版的授权许可功能及安全性,希望给大家带来一些思考和警示。下面仅使用x86测试用例进行逆向,以此达到通用破解补丁的目的。
1、授权系统
加壳时使用RSA算法对授权信息进行加密,程序运行时对其进行解密并验证。授权信息加密后使用BASE64算法进行编码。
代码虚拟化绑定产品代码
VMP许可详细
其中序列号为加密后的许可信息,序列号内容为实际要加密的数据,具体格式:
struct VMProtectSerialNumberInfo
{
INT flags;
wchar_t *pUserName;
wchar_t *pEMail;
DWORD dwExpDate;
DWORD dwMaxBuildDate;
BYTE nRunningTimeLimit;
char *pHardwareID;
size_t nUserDataLength;
BYTE *pUserData;
};
2、测试用例
若要使用VMP的许可授权系统,需要程序中调用VMP特定API实现。无源码程序无法使用VMP授权系统如记事本,代码参考VMP帮助文档,测试代码如下:
void print_state(INT state)
{
if (state == 0)
{
printf("state = 0\n");
return;
}
printf("state = ");
PRINT_HELPER(state, SERIAL_STATE_FLAG_CORRUPTED);
PRINT_HELPER(state, SERIAL_STATE_FLAG_INVALID);
PRINT_HELPER(state, SERIAL_STATE_FLAG_BLACKLISTED);
PRINT_HELPER(state, SERIAL_STATE_FLAG_DATE_EXPIRED);
PRINT_HELPER(state, SERIAL_STATE_FLAG_RUNNING_TIME_OVER);
PRINT_HELPER(state, SERIAL_STATE_FLAG_BAD_HWID);
PRINT_HELPER(state, SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED);
printf("\n");
}
char *read_serial(const char *fname)
{
FILE *f;
if (0 != fopen_s(&f, fname, "rb")) return NULL;
fseek(f, 0, SEEK_END);
int s = ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = new char[s + 1];
fread(buf, s, 1, f);
buf[s] = 0;
fclose(f);
return buf;
}
// The foo() method is very short, but we need it to be an individual function
// so we asked the compiler to not compile it inline
__declspec(noinline) void foo()
{
printf("I'm foo!\n");
}
int main(int argc, char **argv)
{
printf("foo:0x%08x\n", foo);
char *serial = read_serial("serial.txt");
int res = VMProtectSetSerialNumber(serial);
delete[] serial;
if (res)
{
printf("serial number is bad\n");
print_state(res);
int nSize = VMProtectGetCurrentHWID(NULL, 0); // get the required buffer size
char *pBuf = new char[nSize+1]; // allocate memory for the buffer
VMProtectGetCurrentHWID(pBuf, nSize); // obtain the identifier
pBuf[nSize] = 0;
printf("hwid:%s\n", pBuf);
// use the identifier
delete[] pBuf; // release memory
return 0;
}
VMProtectSerialNumberData sd = { 0 };
VMProtectGetSerialNumberData(&sd, sizeof(sd));
printf("name = %ls,\ne-mail = %ls\n", sd.wUserName, sd.wEMail);
printf("serial number is correct, calling foo()\n");
foo();
printf("done\n");
return 0;
}
VMProtectSetSerialNumber 对许可信息进行验证,返回值表示是否验证通过。
3、破解分析
下面提供几种破解方法,通过分析利弊选择方法3进行破解分析。
1)、找到VMProtectSetSerialNumber并修改返回值进行破解,由有虚拟化或混淆进行保护比较难找到并且每个程序每次加壳后都需要重新分析确定函数地址方可实现,通用性无法保证。
2)、替换程序中的RSA公钥,首先需要定位RSA-N,由于N的存储进行了加密,无法在文件或内存中进行扫描得到。必须进行虚拟机逆向分析出N的加密算法和密钥,通过特征数据定位内存中的N进行替换。此方法通用性较佳但分析虚拟机和特征定位非常困难,此方法为完美破解同时也需要一定的技术。
3)、通过HOOK特定API,在关键点对RSA-N进行修改替换达到破解的目的。VMP中N在内存里也是被加密的,N在计算过程中不会出现完整的明文N,所以为了替换N还需要分析部分虚拟机加密算法,但工作量远远小于2的方法。
4、算法分析
下面分析方法3中的算法
1). Base64解码N
N=
002B1038 bb 73 e3 cb a2 c4 b9 c5 ba b7 b8 85 79 4f 16 2d ?s??????????yO.-
002B1048 eb 1a fa 20 00 d0 a4 b1 c0 f0 a1 17 c6 8b db 68 ?.? .??????.???h
002B1058 90 70 0d ab 7e 0e db 6e f2 89 43 f8 8b e0 18 3b ?p.?~.?n??C???.;
002B1068 36 cd 55 de 7b 1a d8 62 88 73 84 c4 81 46 5a 20 6?U?{.?b?s???FZ
002B1078 9c bb af 79 44 8b c3 f1 67 35 fc 4c 6d e6 43 64 ???yD???g5?Lm?Cd
002B1088 76 eb 6d 17 8f 8f ef a5 4a 14 13 68 f9 05 44 0b v?m.????J..h?.D.
002B1098 53 b3 56 ef 61 9e c2 1a 38 98 c6 c1 b3 03 c3 48 S?V?a??.8????.?H
002B10A8 d5 8f e8 18 39 45 7d 15 bf 2b c7 13 b5 80 fb 0c ???.9E}.?+?.?€?.
002B10B8 d7 f2 9d 06 82 ed f1 65 5c 2d 3d 62 e1 24 d0 c0 ???.???e\-=b?$??
002B10C8 84 0e 0c cd f7 29 04 ea a2 08 85 85 31 48 e1 cc ?..??).??.??1H??
002B10D8 a6 f4 1e 0e e0 02 47 b4 e9 e3 d8 31 47 93 fd 55 ??..?.G????1G??U
002B10E8 41 b2 23 19 ca 7e 44 71 d6 03 75 2a 06 29 87 e5 A?#.?~Dq?.u*.)??
002B10F8 bc 51 bc e5 3b 00 05 e7 a5 86 0f e0 2b ce f9 12 ?Q??;..???.?+??.
002B1108 3d 5f d1 84 8e af 25 a4 18 d2 aa fd 60 b4 a9 2e =_????%?.???`??.
002B1118 8c cc 87 fb 9a ea e7 79 81 06 96 06 87 5f 5d 74 ???????y?.?.?_]t
002B1128 0b c9 ff d2 ed 2c 78 99 ca be 6b 59 8a 6c ad 31 .?.??,x???kY?l?1
使用大数库时内存中为:
0x00C7BE08 80 00 31 ad 6c 8a 59 6b be ca 99 78 2c ed d2 ff €.1?l?Yk???x,??.
0x00C7BE18 c9 0b 74 5d 5f 87 06 96 06 81 79 e7 ea 9a fb 87 ?.t]_?.?.?y?????
0x00C7BE28 cc 8c 2e a9 b4 60 fd aa d2 18 a4 25 af 8e 84 d1 ??.??`???.?%????
0x00C7BE38 5f 3d 12 f9 ce 2b e0 0f 86 a5 e7 05 00 3b e5 bc _=.??+?.???..;??
0x00C7BE48 51 bc e5 87 29 06 2a 75 03 d6 71 44 7e ca 19 23 Q???).*u.?qD~?.#
0x00C7BE58 b2 41 55 fd 93 47 31 d8 e3 e9 b4 47 02 e0 0e 1e ?AU??G1????G.?..
0x00C7BE68 f4 a6 cc e1 48 31 85 85 08 a2 ea 04 29 f7 cd 0c ????H1??.??.)??.
0x00C7BE78 0e 84 c0 d0 24 e1 62 3d 2d 5c 65 f1 ed 82 06 9d .???$?b=-\e???.?
0x00C7BE88 f2 d7 0c fb 80 b5 13 c7 2b bf 15 7d 45 39 18 e8 ??.?€?.?+?.}E9.?
0x00C7BE98 8f d5 48 c3 03 b3 c1 c6 98 38 1a c2 9e 61 ef 56 ??H?.????8.??a?V
0x00C7BEA8 b3 53 0b 44 05 f9 68 13 14 4a a5 ef 8f 8f 17 6d ?S.D.?h..J????.m
0x00C7BEB8 eb 76 64 43 e6 6d 4c fc 35 67 f1 c3 8b 44 79 af ?vdC?mL?5g???Dy?
0x00C7BEC8 bb 9c 20 5a 46 81 c4 84 73 88 62 d8 1a 7b de 55 ?? ZF???s?b?.{?U
0x00C7BED8 cd 36 3b 18 e0 8b f8 43 89 f2 6e db 0e 7e ab 0d ?6;.???C??n?.~?.
0x00C7BEE8 70 90 68 db 8b c6 17 a1 f0 c0 b1 a4 d0 00 20 fa p?h???.??????. ?
0x00C7BEF8 1a eb 2d 16 4f 79 85 b8 b7 ba c5 b9 c4 a2 cb e3 .?-.Oy??????????
0x00C7BF08 73 bb
扫描上述大数发现程序中未找到,说明该N已被加密,其加密方式后续。
2). 定位RSA-N
大数在使用时通常都需要分配内存,VMP通过RtlAllocateHeap进行大数空间分配其大小为大数字节数+2字节,对其下断点可跟踪到RSA-E/RSA-N和密文空间的分配:
当第一次分配大小为102即为N分配内存空间。
3). 加密算法
通过对N内存空间下硬件断点可查看加密后的数据变化(多重加密),最终形态为密文:
00A72C80 BC A7 A0 F3 92 2E 24 4D 60 ED F1 3F 94 B3 43 A0 ¼§ ó?$M`íñ?”³C?
00A72C90 84 AC F2 43 B0 29 88 1A 39 27 20 C9 7B 1D 55 28 „¬òC??9' É{U(
00A72CA0 92 13 9D 0F 6C 47 AE 0D 72 9F FA 8C 35 11 DF D7 ??lG?rŸú?ß×
00A72CB0 10 63 72 BF 67 8C D0 90 97 0B 2C AD 73 7D 2D E3 cr¿gŒÐ
经过多次跟踪发现,N由分配后地址进行加密,因此每次加密可能都不一样,因地址而变化。每次加解密2字节,其加密算法如下:
已知条件:
Ptr=00A72C82 N 所在内存地址;
P1 = 2C82 地址低2字节;
P2 = 72C8 地址2;
K = P2 + 10 + 37 = 730F 其中37为固定值,10为随机大小为1字节,每次程序运行该值都有变化(key)。
加密算法如下:
M = AD31 明文 N的最低2字节;
A = NOT(M) + P1 = 7F50 中间结果;
I = NOT(A OR K) = 80A0;
J = NOT((NOT(A))OR(NOT(K)) = 7300;
C = I OR J = F3A0 结果,可以对照明文和密文。
反推解密算法:
M = NOT(NOT(C XOR K) – P1) = AD31
N 的加密过程很可能是 N = A + B 其中A和B 分别存储,如下:
其中 1 很可能是A,而2是最终的A + B,有待进一步验证,如果验证通过可通过方法2进行完美破解。
如果VMP使用了压缩还需要进一步验证,但不会影响方法3。
5. KEY算法
继续分析RSA-N的加密算法,主要针对key的生成及相关算法。
KEY1: EC, 1C, 47, 61, D2, 7E, 8A, 0E,
KEY2: 14, 40, 19, DC, 22, 01, 13, F9,
相应RSA-N加密结果及KEY 使用情况:
00EC,
001C, C34A->0047, C364->0061, C3D5->00D2,
007E, C38D->008A, C311->000E, C3F0->00EC,
001C, C34B->0047, C365->0061, C3D6->00D2,
007E, C38E->008A, C312->000E, C3F1->00EC,
001C, C34C->0047, C366->0061, C3D7->00D2,
007E, C38F->008A, C313->000E, C3F2->00EC,
001C, C34D->0047, C367->0061, C3D8->00D2,
007E, C390->008A, C314->000E,
0014,
0040, C320->0019, C3E3->00DC, C329->0022,
0001, C31A->0013, C400->00F9, C31C->0014,
0040, C321->0019, C3E4->00DC, C32A->0022,
0001, C31B->0013, C401->00F9, C31D->0014,
0040, C322->0019, C3E5->00DC, C32B->0022,
0001, C31C->0013, C402->00F9, C31E->0014,
0040, C323->0019, C3E6->00DC, C32C->0022,
0001, C31D->0013, C403->00F9, C31F->0014,
0040, C324->0019, C3E7->00DC, C32D->0022,
0001, C31E->0013, C404->00F9, C320->0014,
0040, C325->0019, C3E8->00DC, C32E->0022,
0001, C31F->0013, C405->00F9, C321->0014,
0040, C326->0019, C3E9->00DC, C32F->0022,
0001, C320->0013, C406->00F9, C322->0014,
0040, C327->0019, C3EA->00DC, C330->0022,
0001, C321->0013, C407->00F9,
001C,
0047, C370->0061, C3E1->00D2, C38D->007E,
008A, C31D->000E, C3FB->00EC, C32C->001C,
0047, C371->0061, C3E2->00D2, C38E->007E,
008A, C31E->000E, C3FC->00EC, C32D->001C,
0047, C372->0061, C3E3->00D2, C38F->007E,
008A, C31F->000E, C3FD->00EC, C32E->001C,
0047, C373->0061, C3E4->00D2, C390->007E,
008A, C320->000E, C3FE->00EC, C32F->001C,
每次加密RSA-N分段及KEY随机变化。其分析代码如下:
void test(unsigned char *ptr, unsigned char *key)
{
int addr = (int)ptr;
for (int i = 0; i < sizeof(rsa_n); i += 2)
{
int P1 = (addr + i) & 0xFFFF;
int P2 = ((addr + i) >> 4) & 0xFFFF;
int A = ~(~(*(unsigned short *)&rsa_n[i]) + P1);
int K = A ^ (*(unsigned short *)&ptr[i]);
int X = K - P2 - 0x37;
int vk = P2 + X + 0x37;
printf("%04X->%04X, ", (unsigned short)K, (unsigned short)X);
if (i % 8 == 0)
printf("\n");
}
for (int i = 0; i < 20; i++)
{
printf("%02X ", key[i]);
}
printf("\n");
}
查找KEY
Key大小为20字节,存放在栈中,通过堆分配地址定位该KEY。
0019F910 00000000
0019F914 00000003
0019F918 001F2C80 《- RSA-N 地址
0019F91C 11998C2C 《- KEY 由SHA-1 生成
0019F920 69BE32AA
0019F924 9A7C91D9
0019F928 E5974DF1
0019F92C 6C9C967D
0019F930 00000292
0019F934 0019FF70
0019F938 6B7C2FD9 MSVCR120.printf
0019F93C 0019FA0C
0019F940 001F1168
查找算法如下:
unsigned char *find_key(void *stack, DWORD addr)
{
int first = 0;
unsigned char *key = 0;
unsigned char *ptr = (unsigned char *)stack;
for (int i = 0; i < STACK_SIZE; i+=4)
{
if (addr == *(DWORD *)&ptr[i])
{
if (first != 0)
{
if (*(DWORD *)&ptr[i + 4] != 0x80)
{
key = &ptr[i + 4];
break;
}
}
if (*(DWORD *)&ptr[i + 4] == 0x80)
{
first = 1;
}
}
}
return key;
}
6、解密序列号
必须先拥有一份序列号从中获取product code 和usr data
解密序列号
首先使用BASE64 进行解码,再使用N进行解密找到product code 和usr data:
0x00B9B630 00 02 6b 64 b8 f2 d3 f7 e7 f0 79 65 b8 00 01 01 ..kd??????ye?...
0x00B9B640 02 04 74 65 73 74 03 11 74 65 73 74 40 73 65 6e ..test..test@sen
0x00B9B650 73 65 2e 63 6f 6d 2e 63 6e 05 1a 0a e4 07 07 59 se.com.cn...?..Y
0x00B9B660 51 24 25 a4 d1 bd 51 ff 38 fb 3f 2e 69 88 02 c1 Q$%???Q.8??.i?.?
0x00B9B670 e9 72 48 26 5b 84 1c c4 6e bf 53 af 3d e3 75 df ?rH&[?.?n?S?=?u?
0x00B9B680 2e cc c1 26 1a 01 71 65 af 8b 3f 00 13 03 27 8f .??&..qe???...'?
0x00B9B690 ca ef 6f d7 da 4f e5 f8 2d 72 68 e7 36 0e a9 1f ??o??O??-rh?6.?.
0x00B9B6A0 d8 90 33 94 83 b1 a9 e9 a0 4a 4f e2 2f 69 cc 1c ??3??????JO?/i?.
0x00B9B6B0 ae a4 2f fc 3d ad 2f e5 01 27 b7 ae c6 b8 a2 53 ??/?=?/?.'?????S
0x00B9B6C0 e8 60 c3 ea 73 09 28 d9 89 5f 9f 47 04 e1 7a d0 ?`??s.(??_?G.?z?
0x00B9B6D0 5d 38 90 7d cd c8 84 f3 b1 86 4a ea 33 09 e6 e1 ]8?}??????J?3.??
0x00B9B6E0 27 e3 77 10 33 31 75 9f 33 71 37 15 db 94 b8 13 '?w.31u?3q7.???.
0x00B9B6F0 9e 54 9a 42 cf c8 6b 8a 07 36 28 85 c6 28 ff 33 ?T?B??k?.6(??(.3
0x00B9B700 5d c2 59 ef 0b 52 18 a1 66 29 1d 36 fc aa 0e 4d ]?Y?.R.?f).6??.M
0x00B9B710 3b a0 56 33 8e d5 6d 12 c9 e0 59 65 c7 3e 75 af ;?V3??m.??Ye?>u?
0x00B9B720 51 a4 71 6d 42 94 9a 49 e9 2f 5b 90 af 4a 04 e6 Q?qmB??I?/[??J.?
Tag定义如下:
enum eChunks
{
SERIAL_CHUNK_VERSION = 0x01, // 1 byte of data - version
SERIAL_CHUNK_USER_NAME = 0x02, // 1 + N bytes - length + N bytes of customer's name (without enging \0).
SERIAL_CHUNK_EMAIL = 0x03, // 1 + N bytes - length + N bytes of customer's email (without ending \0).
SERIAL_CHUNK_HWID = 0x04, // 1 + N bytes - length + N bytes of hardware id (N % 4 == 0)
SERIAL_CHUNK_EXP_DATE = 0x05, // 4 bytes - (year << 16) + (month << 8) + (day)
SERIAL_CHUNK_RUNNING_TIME_LIMIT = 0x06, // 1 byte - number of minutes
SERIAL_CHUNK_PRODUCT_CODE = 0x07, // 8 bytes - used for decrypting some parts of exe-file
SERIAL_CHUNK_USER_DATA = 0x08, // 1 + N bytes - length + N bytes of user data
SERIAL_CHUNK_MAX_BUILD = 0x09, // 4 bytes - (year << 16) + (month << 8) + (day)
SERIAL_CHUNK_END = 0xFF // 4 bytes - checksum: the first four bytes of sha-1 hash from the data before that chunk
};
RSA-N 的获取
对 RtlAllocateHeap 下断点将KEY 修改为 0x01,第二次断下后使用固定key 对RSA-N 进行解密:
if (key[0] == KEY && key[1] == KEY)
{
for (int i = 0; i < sizeof(rsa_n); i+=2)
{
int P1 = (addr + i) & 0xFFFF;
int P2 = ((addr + i) >> 4) & 0xFFFF;
unsigned short K = P2 + KEY + 0x37;
unsigned short C = *(unsigned short *)&ptr[i];
unsigned short M = ~(~(C^K) - P1);
*(unsigned short *)&rsa_n[i] = M;
}
write_rsa_n(rsa_n, sizeof(rsa_n));
}
得到的RSA-N (BigNum)如下:
解密后的结果:
7、替换RSA-N
通过对KEY 进行修改并且对RSA-N 进行加密进行替换:
for (int i = 0; i < sizeof(rsa_n); i+=2)
{
int P1 = (addr + i) & 0xFFFF;
int P2 = ((addr + i) >> 4) & 0xFFFF;
unsigned short K = P2 + KEY + 0x37;
unsigned short M = *(unsigned short *)&rsa_n[i];
unsigned short A = ~M + P1;
unsigned short C = (~A) ^ K;
*(unsigned short *)&ptr[i] = C;
}
// SHA1-SIZE
memset(key, KEY, 20);
8、许可生成
借助VMP 加壳工具可以生成破解程序的许可序列号,具体方法如下:
替换product code:打开现有.VMP 配置文件找到ProductCode,将该值替换为原序列号中的ProductCode,保存。
获取现有RSA-N 加入hook程序中,制作DLL 进行注入。
使用VMP 加壳工具发许可。
9、程序实现
主要流程:
将DLL 注入到目标程序,在程序启动时加载;
HOOK RtlAllocateHeap ,记录第一次 102 大小分配地址为N;
当第二次102大小(M)分配时对 第一次的N 进行加密替换;
完成。
程序列表
launch
主程序,启动程序用于辅助注入DLL,参数如下:
Launch.exe [-n] <exe_file> [exe_cmdline]
-n 用于注入 rsa-n.dll 否则注入 crack.dll
Gen
通过rsa-n.bn 和 serial.txt 解析序列号获取相关信息
Rsa-n.dll
辅助获取原RSA-N 的注入DLL
Crack.dll
替换原RSA-N 的DLL
- End -
看雪ID:HighHand
https://bbs.pediy.com/user-157550.htm
本文由看雪论坛 HighHand 原创
转载请注明来自看雪社区
热门技术文章推荐:
公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com