商软A v4.7.2的序列号算法分析
准备:商软A,Ollydbg 2.0
先说说A的加密方式。联网激活:用户通过A将用户名,邮箱,序列号和硬盘信息发送给服务器,服务器返回激活码,A写入keyfile文档以备再次运行时激活校验,主要校验的是序列号和激活码。因为有大神已经早已写了篇简析,并给出了一枚正确的序列号,所以我只是站在巨人肩上谈谈序列号算法。文章结尾写到了过调试器检测和文件完整性校验,对此不敢兴趣的读者可以忽略。一枚正确的序列号:
"YH23U-R65WC-CKPA2-RN2JB-XENVZ"
UNICODE F(x):
59 00 48 00 32 00 33 00 55 00 2D 00 52 00 36 00
35 00 57 00 43 00 2D 00 43 00 4B 00 50 00 41 00
32 00 2D 00 52 00 4E 00 32 00 4A 00 42 00 2D 00
58 00 45 00 4E 00 56 00 5A 00
第一部分 序列号算法
1 第一次映射
用OD载入A后,对CreateFileW下条件断点[UNICODE [ESP+4]]=="F:\\A\\Options.ini",运行A,发现A和OD都崩溃了。由于在此处A使用CRC校验,不能直接bp,因此对CreateFileW内部的RETN处断点,然后bp ReadFile,对被读取的序列号下断点,一段无聊的序列号非空和长度检测后,来到
6EB41382 8A08 MOV CL,BYTE PTR DS:[EAX]
此处是系统领空“Charset”,因此执行到用户代码
6EB41397 FF15 04C0B46E CALL DWORD PTR DS:[<&KERNEL32.MultiByteToWideChar>]
一个字符串转换函数。对转换后的UNICODE序列号下断点,OD给出弹窗
然后UNICODE序列号就被XX覆盖了。我们姑且不去探究到底发生了什么。事实上A多次读取KeyFile文件并进行检测。(因为在KeyFile文件写入正确序列号,运行程序后,再去更改KeyFile文件的序列号。当点击输入注册码,A仍然显示未注册。)好吧,让OD重新载入并运行A,点击输入注册码,同样对CreateFileW内部的RETN处下断点,一运行A就崩溃,此处也被CRC校验了。没关系,可以直接对MultiByteToWideChar下条件断点[STRING [ESP+C]]=="YH23U-R65WC-CKPA2-RN2JB-XENVZ"。事实上此时读取了两次KeyFile文件,也MultiByteToWideChar两次,对第二次转换后的UNICODE序列号下断点,运行来到
6EAF2B40 0FB701 MOVZX EAX,WORD PTR DS:[ECX]
6EAF2B43 8D49 02 LEA ECX,[ECX+2]
6EAF2B46 66:89440E FE MOV WORD PTR DS:[ECX+ESI-2],AX; 复制数据
(注:类似上面这种数据复制或转移,在下面直接类似表示为 [ECX]-> [ECX+ESI-2]结构)
对被复制后序列号下断点,去到最后一次读取序列号②处,
[EAX] ->[EBP]; [EDX]->[EBP]①<假>; 数据访问
[EAX] ->[EBP]; [EDX]->[EBP]①<真>; 数据访问
[EAX] ->[EBP];②
上面①处运行了两次,第一次为假——即虽然访问了序列号,但不会对校验结果产生影响。我感觉马上就要到达算法代码了。我不知道之后会发生什么,当然可以继续跟下去,这是最稳妥的办法。然而我对此处序列号的尾字节下断点,运行到断点处,然后看看序列号前后的内存信息。向上翻了翻内存页,看到有一可疑处,地址[5D858690] :
54 CC 89 56 89 28 2D 2D 85 07 D3 59 FD 1D BA 0C // F``(x)
在这串数据的右边,OD没有给出任何有效信息。这串数据是什么时候写入的?和序列号是否关联?放在此处有什么作用?重新运行到①处,对[5D858690]处的内存下写入断点,果然在①处访问了序列号首字节后,A在[5D858690]处写下1C,却与②无关。此时断在
5D6C4935 8810 MOV BYTE PTR DS:[EAX],DL
在①处对序列号尾字节下断点,重新截取一下写入的数据
7C D0 D3 71 D9 79 2D 2D 05 C5 88 56 A8 1C BA 0C// F`(x)
以下为写入详细
终于找到序列号的首次映射F`(x)。而第一次发现的F``(x)则为序列号F(x)的第二次映射,有关系如下
F(x)~ F`(x);F`(x)~ F``(x);
现在, 对此条代码下INT3断点,重新运行到①处继续跟进,
[EBP]-> [EDI+EAX];
[EDI+EAX]-> [EBP] ③<假>;
[EDI+EAX]-> [EBP] ③<真>; [EBP] ->[EDI+EAX] ④;
[EDI+EAX]->[EBP]<假>
下面回到了③处进行循环,我现在需要找到循环过后,序列号被用来干嘛。当然能够直接在出口处下断点跳过循环是最好不过,但是由于混乱的代码片段、高强度的混淆和VMP指令,我很难办到。于是我在③的下一行设条件日志断点(条件:DL==59,表达式:LOG=1,暂停程序:从不,日志表达式值:打开条件),运行。在日志窗口中,我发现了69条记录,经过反复运行检查,确定第59条为循环出口。此处被提取的序列号还能影响F`(x)。因此从此处继续往下跟进
[EDI+EAX]-> [EBP];
6E1BCDE9 0045 04 ADD BYTE PTR SS:[EBP+4],AL
上条代码过后原来的序列号直接变成0xFF,我觉得不用再跟下去或者再去检查0xFF是否影响F`(x),该字节本身就很能说明问题。此时AL=0xA6,我需要再去找找0xA6是怎么来的。为了避免频繁运行A,我将之前的一部分循环代码(第56~60条记录)用运行跟踪记录到文档,然后导入到EXCEL进行分析,大概10w条左右的样子,定位到上面代码处。现在开始向上查找A6的来源
ADD DWORD PTR SS:[EBP+4],EAX; EAX=0000001C,EBP=0019E5B8,
MOV EDX,DWORD PTR SS:[EBP]; EBP=0019E5BC,
MOV AL,BYTE PTR DS:[EDX]; EDX=6E2DA0DC,
AND AL,DL; EAX=000000D9, EDX=0000007F,
(最后一条是图片)_
我找到A6是由D9计算来的。为什么不是7F?可以向上跟一下7F,发现它是由前面的一些数计算来的。如果非要跟进这些数,就迷失在A的陷阱里面了。如果修改序列号首字节,这里的7F不变。D9是由[6E2DA0DC]地址处的内存得来的,而6E2DA0DC=1C+某数,这个某数可以看成是基址,也是由前面的一些数计算来的。把此处数据提出来
static unsigned char g_rippedData[] ={0xC6, 0xC4, 0xC1, 0xC8, 0xD4, 0xB6, 0xCD, 0xB3, 0xCE, 0xB7, 0xD8, 0xC3, 0xDA, 0xC7, 0xC5, 0xD3, 0xC2, 0xB9, 0xD0, 0xD1, 0xB2, 0xCA, 0xB8, 0xD6, 0xD2, 0xD7, 0xCB, 0xB5, 0xD9, 0xD5, 0xB4, 0xCC, 0xB0, 0xB1, 0xC9, 0xCF
};
1C很可疑,因为最终序列号首字节就是要生成1C。继续向上跟一下,
ADD BYTE PTR SS:[EBP+4],AL;EAX=0000001B,
继续向上跟1B,
ADD BYTE PTR SS:[EBP+4],AL;EAX=0000001A,
再向上跟,没有了。这里循环到1C,不再进行循环。而1C就是最终F1`(x),伪代码:
if (regcode [i]==(g_rippedData[j]&0x7F)){if (i==0){data_A[m]=j;}else{…}
}
同样的方法,分析F2`(x)=0x7C,不过此时的循环到03,不再进行循环,03并非最终的F2`(x),因此还需分析后面代码直到7C写入。关键代码提取
SHL AL,CL; EAX=00000003, ECX=51636005, //0x03<<0x05=0x60,
NOT DL; EDX=516D0060, //0x9F=~0x60
NOT AL ; EAX=0000001C, //前面写入的0x1C
AND AL,DL; EAX=000000E3, EDX=516D009F, //0x83=0xE3&0x9F
NOT AL ; EAX=00000083, //0x7C=~0x83
这里唯一奇怪的就是05(我把此处数据命名为h_rippedData),怎么来的呢?又是自动生成的常数。后面的数与F2`(x)=0x7C的生成方式一样,只不过在序列号相同位生成第2个F`(x)的时候用SHR而非SHL,就不再赘述了。在SHR和SHL处下条件断点拦截后面的h_rippedData就行了。
2 第二次映射
同样的道理,对F`(x)下断点,A被断在
6CDDF0BB 8B00 MOV EAX,DWORD PTRDS:[EAX] ; 2次映射 访问
6CDB46B3 8910 MOV DWORD PTRDS:[EAX],EDX ; 2次映射 写入
得出F``(x)访问/写入详细
写入 访问 |
① |
② |
③ |
④ |
①②③③④ |
71D3D004 |
2D2D79D9 |
||
① |
71D3D054 |
|||
② |
2D2D79D9 |
0CBA1CA8 |
||
④ |
5688C005 |
0CBA1CA8 |
||
③ |
5688C785 |
|||
①②④③ |
71D3C054 |
2D2D79D9 |
||
① |
71D3CC54 |
|||
② |
2D2D79D9 |
0CBA1CA8 |
||
④ |
56880785 |
0CBA1CA8 |
||
③ |
56890785 |
|||
①②③④ |
71C1CC54 |
2D2D79D9 |
||
① |
71C9CC54 |
|||
② |
2D2D79D9 |
0CBA1CA8 |
||
④ |
56810785 |
0CBA1CA8 |
||
③ |
56930785 |
|||
②① |
7009CC54 |
|||
③④ |
2D2D79D9 |
|||
① |
7689CC54 |
|||
② |
2D2D79D9 |
0CBA1CA8 |
||
④ |
50130785 |
0CBA1CA8 |
||
③ |
51D30785 |
|||
①②④③ |
1689CC54 |
2D2D79D8 |
||
① |
5689CC54 |
|||
② |
2D2D79D9 |
0CBA1CA8 |
||
④ |
0CBA1CA8 |
|||
③ |
01D30785 |
|||
③ |
59D30785 |
|||
①③④ |
5689CC54 |
2D2D7809 |
||
① |
5689CC54 |
|||
② |
2D2D7889 |
0CBA1CA0 |
||
④ |
0CBA1CBD |
|||
② |
59D30785 |
|||
①③④ |
5689CC54 |
2D2D0089 |
||
① |
5689CC54 |
|||
② |
2D2D2889 |
0CBA1C1D |
||
④ |
0CBA1DFD |
|||
④ |
59D30785 |
先跟踪一下数据,
[EAX]->[EBP]
[EBP]->[EDI+EAX]
接着访问了 2D2D79D9 5688C505,马上又回到③处,作4次停留,然后跳走。提代码(第3次停留到写入71D3D004)分析
SHL EAX,CL; EAX=0000001F,ECX=00000003,
NOT EAX; EAX=000000F8,
AND EAX,EDX; EAX=FFFFFF07,EDX=71D3D07C,
写得很明白,71D3D004最终还是由1F和03决定,1F是A自动生成的常数1,去找找03如何生成
MOV DWORD PTR SS:[EBP+4],EAX EAX=00000001,EBP=0019E5A0,
SHL EAX,CL; EAX=00000001,ECX=6D023A01,
ADD DWORD PTR SS:[EBP+4],EAX; EAX=00000002,EBP=0019E5A0,
这里都得去验证哪个1是常数,哪个1是变量。注意之前读取过2D2D79D9 5688C505,那么尝试改变5688C505为12345678重新运行A,发现之前的03变成了01。再跟进5688C505
AND EAX,EDX; EAX=5688C505,EDX=0000007F,
AND EAX,EDX; EAX=00000005,EDX=00000001,
由5688C505&0x7F&0x01得到01。至此,71D3D004分析完毕,再看看一下71D3D054的生成过程
SHRD EAX,EDX,CL EAX=5688C505, ECX=68903807, EDX=0CBA1CA8,
AND EAX,EDX EAX=50AD118A, EDX=0000001F,
SHL EAX,CL EAX=0000000A, ECX=00000003,
NOT EDX; EDX=00000050,
AND EAX,EDX; EAX=8E2C2FFB, EDX=FFFFFFAF,
NOT EAX; EAX=8E2C2FAB,
分析发现SHRD中CL也是自动生成的常数。至于下面的数,分析方法相同。只是规律不明显,分析每组数的写入都要导出代码。
3 比较映射值
写完F``(x)之后,程序还在继续访问F``(x)。最后访问的是0CBA1DFD,跟进,在③处停留8次跳走,第6次为有效数据,跟进,接着将数据计算为65D0,又在下面代码处被覆盖了,并且[ESP+4]变成了FFFF
67CEB661 66:0145 04 ADD WORD PTR SS:[EBP+4],AX
之前[EBP+4]处的值就是65D0,是我们一直跟过来的,计算方法很容易找到。而对应的9A2F怎么找呢?只有再提代码,倒着提(第8次停留到最后,第7次到第8次停留。。。),最后找到第4次停留处(EAX=00000028)取出的数据。第3次到第4次之间的代码太长,一个晚上都没提出来。只有反方向去跟踪、分析了,
67C837AF 8B1438 MOV EDX,DWORD PTRDS:[EDI+EAX] ;③处
Ctrl+R查看跳转来源,这种方法要求来源代码必须有跳转地址。像return 4,call <edx>就不行了,得另外想办法。关键代码
6E45CEF6 66:891438 MOV WORD PTR DS:[EDI+EAX],DX ; 末尾1次
6E443912 66:8B55 00 MOV DX,WORD PTR SS:[EBP] ; 末尾3次
找到了来源处就换种方法跟,因为存储数据到[EBP]后可能运行了很长段代码才被上面代码访问。直接对[EBP]处的数据下硬件条件断点,重新运行代码。不过似乎出了点问题,下断点[0019E9BC]==00A165D0能行,WORD [0019E9BC]==65D0就不行,不理解。用这种方法继续向上跟进
6E46F269 66:8945 04 MOV WORD PTR SS:[EBP+4],AX
6E4435F5 66:D3E8 SHR AX,CL
6E523B25 66:8B45 00 MOV AX,WORD PTR SS:[EBP]
6E47F7F5 66:8945 00 MOV WORD PTR SS:[EBP],AX//末尾4次
6E4C3871 66:8B0438 MOV AX,WORD PTR DS:[EDI+EAX] ; A3
6E4D11EE FF7424 0C PUSH DWORD PTR SS:[ESP+0C]
6E4D11F2 8F45 00 POP DWORD PTR SS:[EBP]
6E535E72 66:2145 04 AND WORD PTR SS:[EBP+4],AX
6E535E67 66:8B45 00 MOV AX,WORD PTR SS:[EBP]
6E4CF965 F755 00 NOT DWORD PTR SS:[EBP]
6E535E72 66:2145 04 AND WORD PTR SS:[EBP+4],AX
6E4CF965 F755 00 NOT DWORD PTR SS:[EBP]
6E444704 66:8945 00 MOV WORD PTR SS:[EBP],AX
6E4446FC 66:36:8B00 MOV AX,WORD PTR SS:[EAX]
E47F7F5 66:8945 00 MOV WORD PTR SS:[EBP],AX ; 末尾4次
6E46F269 66:8945 04 MOV WORD PTR SS:[EBP+4],AX
6E4435F5 66:D3E8 SHR AX,CL
一步步跟到这里发现代码循环回去了,在上面几条关键循环代码处下条件记录断点拦截一下,下面是记录结果
接着我们要讨论的关键就在这张表中。这张表由”SHR”、”NOT”、”AND” 3条指令构成(后面简称S、N、A),需要找到它的循环条件,什么时候S,什么时候N,什么时候A。写完F``(x)后,对F``(x)数据下访问断点,前面8个数据每个都被访问了8次,然后F``(x)。猜想是F``1(x)~ F``8(x)经过计算得到65D0。结合表中数据,猜想SNA是头,后面NANANA按条件附加。拦截几个此处访问和写入数据期间的代码,得到表中数据的计算方法。然而发现这8个数据只能生成8*8=64组数据,而表中的S有327/3=109组,因此需要拦截后面的代码再分析。事实上A也访问了第9个数据到14个数据,可能是在第二次映射刚完成时被保留下来的吧,计算方法一样,第14个数据只被访问了5次就跳走。
4 code
#include <Windows.h>
#include <stdio.h>char data_A[16]={0};
char* data="YH23UR65WCCKPA2RN2JBXENVZ";
//char* data="DNAZLBDYDTA8AL97RXAUFNK56";
int flag;DWORD password;DWORD shld(DWORD a,DWORD b,int c){_asm{MOV EAX,aMOV EDX,bMOV ECX,cSHLD EAX,EDX,CL}
}
DWORD shrd(DWORD a,DWORD b,int c){_asm{MOV EAX,aMOV EDX,bMOV ECX,cSHRD EAX,EDX,CL}
}void F1x(){int m=0,n=1;char order[]={1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1};static unsigned char g_rippedData[] ={0xC6, 0xC4, 0xC1, 0xC8, 0xD4, 0xB6, 0xCD, 0xB3, 0xCE, 0xB7, 0xD8, 0xC3, 0xDA, 0xC7, 0xC5, 0xD3, 0xC2, 0xB9, 0xD0, 0xD1, 0xB2, 0xCA, 0xB8, 0xD6, 0xD2, 0xD7, 0xCB, 0xB5, 0xD9, 0xD5, 0xB4, 0xCC, 0xB0, 0xB1, 0xC9, 0xCF};static unsigned char h_rippedData[] ={0x5,0x3,0x2,0x7,0x1,0x4,0x4,0x1,0x6,0x2,0x3,0x0,0x5,0x3,0x2,0x7,0x1,0x4,0x4,0x1,0x6,0x2,0x3,0x0,0x5,0x3,0x2,0x7,0x1,0x4,0x4,0x1,0x6,0x2,0x3,0x0};for (int i=0;i<25;i++){for(int j=0;j<36;j++){if (data[i]==(g_rippedData[j]&0x7F)){for (int k=0;k<order[i];k++,n++){if (i==0){data_A[m]=j;}else{if (k==0){if (n%12%5%2){data_A[m]=~(~data_A[++m]&~(j<<h_rippedData[n-2]));}else{data_A[m]=~(~data_A[m]&~(j<<h_rippedData[n-2]));}}else{if (n%12%5%2){data_A[m]=~(~data_A[++m]&~(j>>h_rippedData[n-2]));}else{data_A[m]=~(~data_A[m]&~(j>>h_rippedData[n-2]));}}} }break;}}}
}
void F2x(){int flag1,flag2,flag3;static unsigned char deviation1[]={0x01,0x08,0x0F,0x16,0x1D,0x24,0x2B};static unsigned char deviation2[]={0x07,0x0C,0x11,0x16,0x1B,0x00,0x05};static unsigned int deviation3[]={0xF80,0x1F000,0x3E0000,0x7C00000,0xF8000000,0x1F,0x3E0};struct data_AA{DWORD data_A1,data_A2,data_A3,data_A4;}data;memcpy(&data,data_A,sizeof data_A);flag=data.data_A3&0x7F;for (int i=0;i<7;i++){flag1=(((flag>>i)&0x01)<<0x01)+deviation1[i];if (i<5){flag2=shrd(data.data_A3,data.data_A4,deviation2[i])&0x1F;flag3=shrd(data.data_A1,data.data_A2,flag1)&0x1F;if (i==4){data.data_A2=~shld(0,0x1F,flag1)&data.data_A2;data.data_A2=~(~data.data_A2&~shld(0,flag2,flag1));};data.data_A1=~(0x1F<<flag1)&data.data_A1;data.data_A1=~(~data.data_A1&~(flag2<<flag1));data.data_A3=data.data_A3&~deviation3[i];data.data_A3=~(~data.data_A3&~(flag3<<deviation2[i]));}else{flag1=flag1&0x1F;flag2=(data.data_A4>>deviation2[i])&0x1F;flag3=(data.data_A2>>flag1)&0x1F;data.data_A2=~(0x1F<<flag1)&data.data_A2;data.data_A2=~(~data.data_A2&~(flag2<<flag1));data.data_A4=data.data_A4&~deviation3[i];data.data_A4=~(~data.data_A4&~(flag3<<deviation2[i]));}}password=data.data_A4>>(0x2D&0x1F);memcpy(data_A,&data,sizeof data_A);
}
void Compare(){int k;WORD a=0xFFFF,b=0x8408,aa;for (int j=0;j<14;j++){for (int i=0;i<8;i++){aa=a;a>>=1;flag=~(~(data_A[j]>>i)&~(char)aa)&~((data_A[j]>>i)&(char)aa)&0x01;if (flag) a=~(b&a)&~(~b&~a);if (j==13&&i==4) break;}}if (password==a){printf("\n序列号正确,%x=%x\n",password,a);}else{printf("\n序列号错误,%x<>%x\n",password,a);}
}int main(){printf("F(x):");for(int i=0;i<25;i++){printf("%02x ",(unsigned char)data[i]);}F1x();printf("\nF`(x):");for(int i=0;i<16;i++){printf("%02x ",(unsigned char)data_A[i]);}F2x();printf("\nF``(x):");for(int i=0;i<16;i++){printf("%02x ",(unsigned char)data_A[i]);}Compare();system("PAUSE");
}
第二部分 调试器检测
说起调试器检测,大家可能觉得没什么难的,用插件过掉好了,大多数情况是这样。不过在我开启OllyExt v1.73全部过滤选项+Vic Plug-In 2隐藏PEB后,A还是给出了一个检测到调试器的弹窗。要知道这可比StrongOD强力多了。虽然在点击确定后,程序可以继续运行,但我本着严谨的态度决心探究一下到底是什么原因导致这个弹窗的发生。看到弹窗的标题Letarm.dll,自然想到该弹窗是由Letarm.dll引发的。开启插件全部过滤选项,对LoadLibraryW下条件断点[UNICODE [ESP+4]]=="Letarm.dll",运行到Letarm.dll载入为止,然后bp CheckRemoteDebuggerPresent。虽然在载入Letarm.dll之前A已经进行过CheckRemoteDebuggerPresent,但此处难保不会再次检测。在这里这个函数不会起作用,但可以得到函数的调用方式。运行,断在函数内部,此时堆栈跳转信息是错的,根本找不出是哪里跳来的。
0019E62C [56F5870F 蜏囵 ; 返回到 Letarm.56F58F47 来自 Letarm.56F5870F
0019E630 FFFFFFFF ..
0019E634 0019EEF8
对从Letarm.dll载入到CheckRemoteDebuggerPresent之间运行跟踪代码,找到了
56F275B0 9C PUSHFD
56F275B1 9C PUSHFD
56F275B2 FF7424 38 PUSH DWORD PTR SS:[ESP+38]
56F275B6 C2 3C00 RETN 3C
原来是由RETN 3C调用的函数。函数的调用方式找到了,可CheckRemoteDebuggerPresent并不是导致弹窗的关键。对RETN 3C下条件记录断点记录一下从Letarm.dll导入到弹窗出现为止经过此处的次数---26次。对此处下执行26次的条件断点,运行,堆栈
0019E5E0 77426D40 浀睂 ; ntdll.NtQueryInformationProcess
此时将跳往NtQueryInformationProcess,步进一下,看看堆栈
0019E620 56F5870F 蜏囵 ; 返回到 Letarm.56F58F47 来自 Letarm.56F5870F
0019E624 FFFFFFFF ..
0019E628 0000001E .
0019E62C 0019EEF8
0019E630 00000004 .
0019E634 00000000 ..
OD没有给注释,不过我确信这就是NtQueryInformationProcess的调用参数,看看第3个参数1E(十进制30),对应的是ProcessDebugObjectHandle,因此此处确实在检测调试器。NtQueryInformationProcess可以用下面的值检测调试器
ProcessDebugPort = 7,
ProcessDebugObjectHandle = 30,
ProcessDebugFlags = 31,
SystemKernelDebuggerInformation = 35,
事实上CheckRemoteDebuggerPresent内部就是用NtQueryInformationProcess 检测ProcessDebugPort = 7实现的。当运行到返回时,函数返回信息缓存处[0019EEF8]仍然为0,证明此处的检测也被插件过滤掉了。那么直接将此处到MessageBoxW参数传入之间运行跟踪导出代码分析一下
56F5898D 8910 MOV DWORD PTR DS:[EAX],EDX ; Msg参数传入EDX=00200041
有一个地方比较可疑,
04CF87D8 PUSH DWORD PTR SS:[ESP+3C]
04CF87DC RETN 40
04CF0A19 POP SS
04CF7B8D PUSHAD
04CF7B8E BSWAP EAX
执行04CF0A19后直接跳到04CF7B8D处继续执行,这是什么鬼?没有CALL或RETN,程序不可能正常进入系统领空。我姑且认为遭遇了异常,04CF0A19之后直接进入SEH代码,出来后跳到04CF7B8D处继续执行。实际的情况还是需要手动跟踪一下,在RETN 40处下断点。发现在执行POP SS后又返回了RETN 40,没有进入SEH代码,真是莫名其妙,接着A才正常起来。运行,又断在RETN 40,紧接着的是INT3断点,这才跳到了SEH代码。运行,抵达Msg参数传入。关键就在POP SS处,重新运行A到此处,CPU窗口向上翻页时,POP SS突然变成另外一条指令。这是遇到花指令了。右键让OD分析代码
04D20A17 FE DB FE
04D20A18 79 DB 79 ; CHAR 'y'
04D20A19 17 DB 17
04D20A1A 9C DB 9C
04D20A1B E8 DB E8
原来是一些无意义的数据,在OD中A跳转这里后将这些数据当成了指令执行。解决办法,直接将POP SS改成INT3,让程序去执行预先SEH代码就行了。
第三部分 文件完整性校验
对于一个庞大的商业性的程序来说,有很多可供探索的地方,比如改动UI文字。修改程序是一件很酷的事情。首先在内存中搜索菜单栏中的”文件”,找到字符串出处LangCRes.dll,确定这是一个提供UI文字的dll。将”文件”改成”开始”,重新运行程序,A崩溃。A对LangCRes.dll进行了完整性校验,修改被发现了。一般来说程序的完整性校验方式有散列值校验、校验和、数字签名等,但都要动用到一个函数CreateFile,不打开文件怎么进行校验呢。因此我对CreateFileW下条件断点[UNICODE [ESP+4]]=="LangCRes.dll",之后运行到返回,步进出来,发现A处于WINTRUST领空,查了下是系统文件,作用之一就是进行数字签名验证,关键验证函数是WinVerifyTrust。我想现在A应该已经处在WinVerifyTrust内部了吧。查一下MSDN关于WinVerifyTrust返回值说明:
Return value
If the trust provider verifies that the subject is trusted for the specified action, the return value is zero. No other value besides zero should be considered a successful return.
因此运行A到用户代码,修改返回值为0,运行,A不再崩溃。之后通过HOOK或内存补丁修改WinVerifyTrust的返回值为0就行了。
商软A v4.7.2的序列号算法分析相关推荐
- cxonev4验证用户_CXONE v4.31版本安装序列号
CX-Programmer Ver.9的改进功能 ===Ver.9.42=== 1)支持型号CP1E-E60SDR-A. ===Ver.9.40=== 1)支持CP1L-EMxxxx-x和CP1L-E ...
- 160个Crackme034拆解KeyFile验证升级版
文章目录 查壳 分析程序 用户名算法分析(前14位) 序列号算法分析(后4位) 算法总结 注册机探索 验证结果 这个Crackme和上一个是同一个作者,保护方式是KeyFile.难度两颗星 查壳 同样 ...
- 160个Crackme033
文章目录 查壳 分析程序 用户名算法分析 序列号算法分析 写出注册机 校验结果 这个Crackme的作者是<使用OllyDbg从零开始Cracking>那部教程的作者在教程中使用的配套教程 ...
- java 给word加水印,Java 实现在线给word 文档添加水印
1:描述 在线编辑文档后需要添加专属水印.防止文档被盗用. 2:方案 用 pageoffice 提供的 添加水印的方法 3:核心代码 (1)后台方法添加文字水印 doc.getWaterMark(). ...
- 算法 64式 4、回溯算法整理__第1部分_1到13题
1算法思想 回溯 1.1含义 以深度优先方式搜索问题解的算法称为回溯法. 1.2思想 按照深度优先搜索策略,从根节点出发搜索解空间树,如果某结点不包含问题解,则逐层向祖先结点回溯:否则进入子树. 1. ...
- 使用PageOffice实现文档(word,excel,pdf)在线预览编辑
最近发现一款不错的插件的PageOffice,地址是:http://www.zhuozhengsoft.com/Technical/ 他可以实现word,excel.pdf在线预览以及在线编辑.虽然商 ...
- PageOffice免费试用过期后再次启用操作
这里写自定义目录标题 PageOffice免费试用过期后再次启用操作 PageOffice免费试用过期后再次启用操作 访问"离线注册页":http://www.zhuozhengs ...
- java 集成pageoffice_springboot 集成 pageoffice,实现在线打开,编辑,保存 word 文件...
一. 构建Sping Boot + Thymeleaf框架的项目(不再详述): 新建一个maven project项目:springboot-pageoffice-demo. 修改pom.xml配置, ...
- 支持备份/还原win10系统的分区工具DiskGenius v4.9.3专业版下载+序列号注册文件激活教程
关于DiskGenius这款分区软件,相信大家都使用的炉火纯青了,今天之所以给大家重新介绍分享,是因为之前给大家分享的DiskGenius版本都是网上现成的绿色版本,而今天给大家分享的版本是官方原版, ...
最新文章
- [moka同学笔记]redis练习Demo
- centos7加固手册
- c++:opencv的安装和配置
- SQL提取时间段内数据
- Android单击、长按获取当前触点坐标下(TextView)文字字符
- python CS游戏1--角色创建,武器购买
- python url中传递中文_Python编程:URL网址链接中的中文编码与解码
- DALI调光的计算方式
- jQuery ligerUI中通过ligerDateEditor设置默认日期
- day9http协议
- 十四、K8s calico网络的通信及网络策略
- excel验证身份证信息是否正确
- 微信公众号(头部GIF动图)制作方法
- linux审计工具audit,Linux audit安全审计工具
- Andorid 方法数超过64K的问题
- Win10 屏幕保护突然不出现怎么办
- 全面理解面向对象的 JavaScript(转载)
- 计量经济学及Stata应用 第五章习题 5.7 使用回归模型进行餐馆选址。数据集Woody3.dta包含33家Woody‘s连锁餐馆的以下变量……
- 八人抢答器讲解_8人抢答器的制作原理和过程说明是怎样的?
- 主导问题排查的流程总结
热门文章
- 如何在eplan里面画一个伺服驱动器_如何设置伺服电机驱动器
- 判断mysql的关键字_MySQL关键字以及保留字
- 如何看待有些家庭“财产归儿子,父母生病养老归女儿”?
- 蒋鑫鸿:9.10国际黄金原油最新外盘行情趋势点评附解一套技术指导
- 分享链接在微信内置浏览器中无法打开也无法下载怎么办(Mindjump实现自动跳转浏览器)
- HTML5多媒体素材的运用
- sas univariate 结果解释_SAS:SAS 常用过程之 统计描述过程proc univariate
- 瑜伽,驾驭心灵的节拍丨感受最真实的心灵秘语!
- C小程序【RGB24 to RGB16颜色转换器】
- 打包时Javascript heap out of memory的错误处理