B的验证方式:登录时联网验证vip,验证通过即可享受下载加速、在线解压、自动备份等vip功能。在登录时,B会首先提取本地缓存文件中的vip信息写入内存,之后联网验证账号、本地缓存的vip信息,返回服务器端vip信息,若本地缓存的vip信息与服务器端的相同,则不必写入,否则进行修改。
我想要做的就是破解这个vip权限。这里我只会谈及它的本地校验,尽管它不是什么秘密,破解补丁也一箩筐。

B没有加壳,特别是vmp壳,这很让我欣慰。从哪里入手呢,截包?这听起来像是一个不错的主意,但不是最好的。在我使用B的vip功能的时候会出现一个弹窗阻止。刚学Crack的人都知道去寻找字符串、拦截一些窗口函数。因此,我CreateWindowExW下断点,点击它的自动备份功能。断下之后可以向上找,也可以向下找,我更倾向于向下找。几次Retn后,来到01412172处,

01412154    E8 9772FDFF     CALL 013E93F0                                      ; [00717280]=0
01412159    F7D8            NEG EAX                                            ; EAX=0 jump w
0141215B    1BC0            SBB EAX,EAX
0141215D    F7D8            NEG EAX
0141215F    75 19           JNE SHORT 0141217A
01412161    8B4D 08         MOV ECX,DWORD PTR SS:[EBP+8]
01412164    FF75 0C         PUSH DWORD PTR SS:[EBP+0C]
01412167    8D89 EC180000   LEA ECX,[ECX+18EC]
0141216D    E8 AE42F4FF     CALL 01356420                                      ; 关键windows
01412172    33C0            XOR EAX,EAX

看到上方有一个JNZ,第一个想法就是把它改成JMP无条件跳转,跳过弹窗。然后重启软件,bingo,伎俩成功骗过了程序,此时可以正常使用自动备份功能了。但是在点击在线解压时又出现了弹窗。向上看,01412154处有一个CALL,跟进去看看,原来是将内存中的一个字节传给了EAX,该字节等0则出现弹窗。如果仅是为了破解vip,那么现在就可以写补丁了,但我要进一步了解。注销之后对该字节下断点,再次登录,断在

6B212FB9    FF15 5857396B   CALL DWORD PTR DS:[<&USER32.PostMessageW>]
6B212FBF    8B83 88020000   MOV EAX,DWORD PTR DS:[EBX+288]
6B212FC5    C783 2C020000 F MOV DWORD PTR DS:[EBX+22C],-1
6B212FCF    C783 30020000 F MOV DWORD PTR DS:[EBX+230],-1
6B212FD9    C783 34020000 F MOV DWORD PTR DS:[EBX+234],-1
6B212FE3    C783 38020000 F MOV DWORD PTR DS:[EBX+238],-1
6B212FED    85C0            TEST EAX,EAX
6B212FEF    74 06           JE SHORT 6B212FF7
6B212FF1    C700 00000000   MOV DWORD PTR DS:[EAX],0                           ; 登陆写入1//清零

不用怀疑,这只是一次变量的清零操作,与验证无关,上面还出现了一个.PostMessageW。继续运行,来到

6B213203   /74 4F           JE SHORT 6B213254
6B213205   |68 E0163C6B     PUSH OFFSET 6B3C16E0                               ; UNICODE "membership_info"
6B21320A   |8D4D E4         LEA ECX,[EBP-1C]                                   ; 0018F08C
6B21320D   |C745 EC 0000000 MOV DWORD PTR SS:[EBP-14],0
6B213214   |E8 4714E7FF     CALL 6B084660                                      ; 无关//[EBP-14]
6B213219   |68 789B396B     PUSH OFFSET 6B399B78                               ; UNICODE "GeneralSetting"
6B21321E   |8D4D E8         LEA ECX,[EBP-18]
6B213221   |8B38            MOV EDI,DWORD PTR DS:[EAX]
6B213223   |E8 3814E7FF     CALL 6B084660
6B213228   |8B0E            MOV ECX,DWORD PTR DS:[ESI]                         ; 传地址5D9960]=70F5B2A0
6B21322A   |8D55 EC         LEA EDX,[EBP-14]
6B21322D   |52              PUSH EDX                                           ; 0018F094
6B21322E   |57              PUSH EDI                                           ; membership返回值
6B21322F   |FF30            PUSH DWORD PTR DS:[EAX]                            ; generals返回值
6B213231   |56              PUSH ESI                                           ; 005D9960
6B213232   |FF51 24         CALL DWORD PTR DS:[ECX+24]                         ; 有关ian
6B213235   |FF75 E8         PUSH DWORD PTR SS:[EBP-18]
6B213238   |8B3D 3057396B   MOV EDI,DWORD PTR DS:[<&OLEAUT32.#6>]
6B21323E   |FFD7            CALL EDI
6B213240   |FF75 E4         PUSH DWORD PTR SS:[EBP-1C]
6B213243   |FFD7            CALL EDI
6B213245   |8B8B 88020000   MOV ECX,DWORD PTR DS:[EBX+288]
6B21324B   |85C9            TEST ECX,ECX
6B21324D   |74 05           JE SHORT 6B213254
6B21324F   |8B45 EC         MOV EAX,DWORD PTR SS:[EBP-14]                      ; [0018F274]
6B213252   |8901            MOV DWORD PTR DS:[ECX],EAX                         ; 登陆写入2

这里向该字节写入本地缓存的vip信息0,那么修改最后的EAX=1,继续运行,来到

6B2134FA    8908            MOV DWORD PTR DS:[EAX],ECX                         ; 登陆写入3//功能

这里又继续向该字节写入0。看看B,此时已经出现vip头像,由于程序处于暂停状态,不能对B进行任何操作。经过测试,发现第二次为本地vip写入,若与服务器端不同,则进行第三次写入。由于本文只谈及本地校验,所以只跟踪第二次写入的EAX=0。跟到这里跟不下去了,

6ED21D13    8945 FC         MOV DWORD PTR SS:[EBP-4],EAX                       ; 没玩//写入x
6ED21D16    807F 0D 00      CMP BYTE PTR DS:[EDI+0D],0                         ; 有未循环的量
6ED21D1A  ^ 74 8C           JE SHORT 6ED21CA8
6ED21D1C    5E              POP ESI                                            ; EAX直接+40//最后的指向
6ED21D1D    5B              POP EBX
6ED21D1E    5F              POP EDI                                            ; [ebp-8]="hjm"
6ED21D1F    8BE5            MOV ESP,EBP
6ED21D21    5D              POP EBP
6ED21D22    C2 0400         RETN 4

此时,存在这么个情况,

szBYTE=*(BYTE*)(*(*DWORD)(*(DWORD*)(*(DWORD*)(*(DWORD *)(EAX+0x40)+0x28)+0)+0x14)+0xA)

即取[EAX+0x40]处的值,作为地址,再取[该地址+0x28]的处的值,作为地址,再取……。该字节等于最后取到的值。但此时处于一个函数结束位置,返回值EAX还要由这个函数决定。此函数完整的汇编代码就不贴了,贴出本人提取并修改自IDA伪代码的C++代码,

int A::sub_70EF1C90(int a2){//from 5DCAD3B0 ,from 有关ianint result,v3,v4,v5,v6,v8;signed int v7;result = *(DWORD *)this;//取出x->EAXv8 = *(DWORD *)this;//取出x->[EBP-4]v3 = *(DWORD *)(*(DWORD *)this + 4);//DS:[EAX+4]=EDI=space2[1]=space1while ( !*(BYTE *)(v3 + 13) ){//2次后中断if ( *(DWORD *)(a2 + 20) < 8u )v4 = a2;elsev4 = *(DWORD *)a2;//比较字符串,若相同,则返回0。<span style="font-family: Arial, Helvetica, sans-serif;">【1】v3地址【3】v3长度【4】gen地址【5】gen长度</span>if ( sub_70EF2010(v3 + 0x10, 0, *(DWORD *)(v3 + 0x20), v4, *(DWORD *)(a2 + 0x10))){if ( *(DWORD *)(a2 + 20) < 8u )v6 = a2;elsev6 = *(DWORD *)a2;v7 = sub_70EF2010(v3 + 0x10, 0, *(DWORD *)(v3 + 0x20), v6, *(DWORD *)(a2 + 0x10));}else{v5 = a2 + 24;if (*(DWORD*)(a2+44)>=8u) v5=*(DWORD *)v5;//如果sp3 mem长度>8,则取sp3+18处 mem地址v7 = sub_70EF2010(v3 + 0x28, 0, *(DWORD *)(v3 + 0x38), v5, *(DWORD *)(a2 + 0x28));//【4】mem地址【5】mem长度}if ( v7 < 0 ){v3 = *(DWORD *)(v3 + 8);//v3指针更新if (v3==0) printf("异常 v3指针移动出错");//self添加result = v8;}else{result = v3;//v3指针保存v3 = *(DWORD *)v3;//v3指针更新v8 = result;}}return result;
}

上段代码意思是说,输入一个结构地址参数A2,然后将this指针关联地址V3指向的堆栈结构内容与之比较,若返回值小于0,则将V3+8处DWORD作为新的V3地址,否则取V3处DWORD作为新的V3地址,然后再次比较,直到取到相同结构为止。返回值EAX为最后的V3指针地址。当然,我已经完全分析出了结构内容,也写好了能够运行的代码。然而我发现这是毫无意义的。将一段IDA伪代码翻译成能够运行的代码,费时费力。最终总结出,

第一处比较,
A2:"GeneralSetting"
V3:"GeneralSetting"

第二处比较,
A2:"membership_info";
V3:"download_dir";"list_sync_count_from_null_cursor";"safebox_mail_phone_binded";"new_set_guide_id";"membership_info";

现在需要判断一下,这个最后的0究竟是与被比较的V3有关,还是与主动比较的A2有关。也就是说,是否只要判断的字符串是"membership_info",则"membership_info"一定指向0?假如我是VIP,就会传入另一个指向1的字符串去判断。还是说,被判断的字符串一定是"membership_info",但"membership_info"不一定指向0。就目前为止掌握的信息还不足以判断。在往上找找,我注意到在写入第二次登陆写入的上方有”membership_info"、"GeneralSetting"等字符串。作一个大胆的猜测,这个就是A2指向的判断字符串。会不会是V3的?也有可能。但不管是谁的,既然这里出现了”membership_info",那么第一种假设就不成立。剩下的,就去找找这个"membership_info"所在结构地址(返回值EAX)+40处的DWORD是何时写入的吧。直接对这个DWORD下断点是无效的,因为B是在登录写入1之后才初始化结构。当然,也不能直接寻找"membership_info",因为我们不知道"membership_info"和0是否联系紧密。如果"membership_info"和0不是从同一文件的临近位置取出的,那么寻找"membership_info"就会变得毫无意义。这时需要对this指针+4处的DWORD(即首个V3)下断点。因为this指针处的结构是在登录写入1之前初始化的,和后面存放字符串地址的结构不同。最终,我追到一个CALL里(汇编代码很长,就不贴了),这个CALL作用就是对V3结构前3个DWORD写入地址,以便将这几个结构连起来,后一个结构地址就存放前一个结构的前3个DWORD中。从这个CALL出来后,再经历2个RETN,来到6838E77C处,

6838E777    E8 F4D8FFFF     CALL 6838C070                            ; 出来2***************
6838E77C    8BC8            MOV ECX,EAX                              ; sp3+40
6838E77E    E8 ADD7FFFF     CALL 6838BF30                            ; 关键//末尾值写入

下面的CALL就在对V3+0x40处的DWORD进行写入了。接着从这个CALL向上找被写入值得来源。就在上面不远处的一个CALL内,连续进几个CALL,

683917BA    E8 01F2FFFF     CALL 683909C0                            ; TRUE
683917BF    8B75 E8         MOV ESI,DWORD PTR SS:[EBP-18]
683917C2    68 38A73F68     PUSH OFFSET 683FA738                     ; UNICODE "true"
683917C7    FF75 88         PUSH DWORD PTR SS:[EBP-78]
683917CA    8B3E            MOV EDI,DWORD PTR DS:[ESI]
683917CC    FF15 A8733F68   CALL DWORD PTR DS:[<&MSVCR120._wcsicmp>] ; 关键比较

从CALL 683909C0里面取出TRUE/FALSE ,wcsicmp将其转换成最终需要的1/0写入V3+0x40关联的结构。如果取出的是1/0,就不需要这段代码判断。再去找找这个TRUE的来源,

6F2ACD1F    8B48 1C         MOV ECX,DWORD PTR DS:[EAX+1C]            ; 02730348
6F2ACD22    E8 B1C90000     CALL 6F2B96D8                            ; 取出EAX

取值就是ECX+20处指向的内容,CALL里面就不分析了

就这样可能看不出什么,但将这个地址-1后

上图内容比较乱,但要表达的意思是,每个字符串后面都跟了一个描述符,TURE/FALSE或者1/0。到现在为止,才算真正的确定字符串和最后1/0的关系。字符串和描述符不会是分开放的了,因为如果是分开放的话,在这里又何必把它们放在一起呢?难道将分开的两部分直接写入V3结构不好?下面就该去找字符串的来源了。找字符串就容易多了,因为字符串不会总是变来变去,在内存中也好搜索。从写入登录1开始,每过两三个CALL,就在内存中搜索一下字符串(membership_info或是上图内存块中的其它字符串都可以)。事实上,第一次被搜索到的字符串是个暗桩,很容易就在堆栈发现。它以明文形式存在于resource.db文件中,对登录写入2的字节值毫无影响。我随意对这个暗桩字符串下断点,我没期望它能起什么作用。但它又被相同的一些字符串覆盖了。绝对是意外之喜。经测试,这是真正的解密字符串。即使此处没有对暗桩字符串进行覆盖,细心排查也能找到真正的字符串。继续排查,在”出来2”处上方的某个CALL内, 最终跟到了

6E2BA1D3    E8 E8170000     CALL 6E2BB9C0
6E2BA1D8    8B7D 18         MOV EDI,DWORD PTR SS:[EBP+18]
6E2BA1DB    8BCB            MOV ECX,EBX
6E2BA1DD    56              PUSH ESI
6E2BA1DE    8B75 0C         MOV ESI,DWORD PTR SS:[EBP+0C]
6E2BA1E1    FF75 14         PUSH DWORD PTR SS:[EBP+14]
6E2BA1E4    FF75 10         PUSH DWORD PTR SS:[EBP+10]
6E2BA1E7    56              PUSH ESI
6E2BA1E8    84C0            TEST AL,AL
6E2BA1EA    74 08           JE SHORT 6E2BA1F4
6E2BA1EC    57              PUSH EDI
6E2BA1ED    E8 DEFAFFFF     CALL 6E2B9CD0                               ; 解密CALL22222222

上面的CALL根据文件名取出密文,下面的CALL进行解密,步过这个CALL就会出现明文字符串。CALL内会在某处进入bull120u.dll这个算法dll内部。B首先对字符串进行了WideCharToMultiByte转换,然后用Base64和RC4对文件进行解密。看,外部函数写的如此之好,

6E2A9EC4    E8 87F0FFFF     CALL ?Base64Decode@BULL@@YAJPA_WPAPAUIGeneralBuffer@1@@Z     ; BASE64 关键

作为一款知名软件,加密的各个环节居然用如此简单。其实加密前可以考虑前一种方式(”只要判断的字符串是"membership_info",则"membership_info"一定指向0”)阻止解密者找到描述符来源。这样,以它们自身衔接V3结构的方式,将 membership_info作为实际判断标准,但最后仍然判断1/0用以迷惑解密者。结果由中间环节决定。一开始就进行中间V3结构地址乱序衔接,完成之后,再将的首个V3结构地址写入this结构+4处。解密代码没什么技术含量,仅仅是将CryptoAPI Base64和百度百科的 RC4源码凑在一块。为何不用CryptoAPI进行RC4换算,下面就是原因,

http://www.phdcc.com/cryptorc4.htm

#include <Windows.h>
#include <iostream>
#include <io.h>
#pragma comment(lib,"crypt32.lib")unsigned char s[256] = {0};
static unsigned char key[] ={0x56, 0x11, 0xF2, 0x80, 0x4C, 0x75, 0x32, 0xDA, 0xAB, 0x16, 0xC2, 0x58, 0x50, 0x68, 0x19, 0xF7
};
void rc4_init(unsigned char*s, unsigned char*key, unsigned long Len){int i = 0;BYTE j = 0;char k[256] = { 0 };unsigned char tmp = 0;for (i = 0; i<256; i++){s[i] = i;k[i] = key[i%Len];}for (i = 0; i<256; i++){j = (j + s[i] + k[i]) % 256;tmp = s[i];s[i] = s[j];//交换s[i]和s[j]s[j] = tmp;}
}
void rc4_crypt(unsigned char*s, unsigned char*Data, unsigned long Len){BYTE j = 0,i = 0,t = 0;unsigned long k = 0;unsigned char tmp;for (k = 0; k<Len; k++){i = (i + 1) % 256;j = (j + s[i]) % 256;tmp = s[i];s[i] = s[j];//交换s[x]和s[y]s[j] = tmp;t = (s[i] + s[j]) % 256;Data[k] ^= s[t];}
}int main(){DWORD dwOutBufferLen;FILE *fp=fopen("PersonalSetting.xml","rb+");int lSize = filelength(fileno(fp));wchar_t* cTemp = new wchar_t[lSize/2+1];int numread = fread(cTemp,sizeof(wchar_t),lSize/2,fp);fclose(fp);CryptStringToBinaryW(cTemp,numread,CRYPT_STRING_BASE64,NULL,&dwOutBufferLen,NULL,NULL);BYTE *pbOutBuffer = (BYTE *)malloc(dwOutBufferLen+1);CryptStringToBinaryW(cTemp,numread,CRYPT_STRING_BASE64,pbOutBuffer,&dwOutBufferLen,NULL,NULL);free(cTemp);rc4_init(s, (unsigned char*)key, sizeof(key));rc4_crypt(s, (unsigned char*)pbOutBuffer, dwOutBufferLen);setlocale(LC_ALL,"chs");wprintf(L"%s\n",pbOutBuffer);
}

商软B v5.4.1的登录验证分析相关推荐

  1. 微信加密与登录验证分析

    微信加密与安全通信流程分析 背景 ​ 微信渐渐已经成为了大多数中国人日常会话的通讯工具.微信的通信安全,很大程度上保证了普通民众的数据安全,也因此显得十分重要. ​ 本文主要在其他对微信研究的基础上, ...

  2. [转载]ESFramework 4.0 快速上手(15) -- 客户端登录验证

    ESFramework 4.0 快速上手(15) -- 客户端登录验证 在之前版本的Rapid引擎中,是没有提供客户端登陆验证的机制的,如果要验证用户的帐号密码信息,我们只有自己手动通过自定义信息来实 ...

  3. java web 怎么用solr_使用web过滤器增加solr后台登录验证

    solr后台自带是没有登录功能的,默认访问地址是:http://localhost:8983/solr/#/(内置jetty运行). 要给sorl后台增加登录验证方法: 1.使用web服务器的登录验证 ...

  4. flask token 登录验证

    flask token 登录验证 视频 https://www.youtube.com/watch?v=J5bIPtEbS0Q 代码 #!/usr/bin/env python # -*- encod ...

  5. 【ADO.NET】2、各种版本的 简单登录验证

    一.简单登录验证(防SQL注入) GetString(序号) 返回某一列的值(当用户不记得列名序号时,可使用GetOrdinal()获取到序号) GetInt32(序号) 针对的是 int 字段,返回 ...

  6. 使用SSH框架实现用户登录验证

    今天,写一个非常简单的通过struts+hibernate+spring的集成来实现用户登录验证的例子,让大家了解一下三者是如何是整合的. 我们可以通过myeclipse的向导,生成相应的环境搭建,如 ...

  7. centos transmission 无法开启登录验证

    centos上用transmission下载bt yum install transmission transmission-daemon 安装并运行transmission 但是却一直无法使用用户和 ...

  8. Uchome的登录验证机制

    2019独角兽企业重金招聘Python工程师标准>>> 登录: 成功后设置cookie //设置cookie ssetcookie('auth', authcode("$s ...

  9. shiro 实现登录验证功能

    实现登录验证功能 1.创建自己的Realm对象,继承AuthorizingRealm ​    实现父类的doGetAuthenticationInfo 认证方法 MyRealm.java packa ...

最新文章

  1. [BZOJ3337] ORZJRY I --块状链表大毒瘤
  2. [Zend PHP5 Cerification] Lectures -- 4. XML Web Service
  3. Python数据类型之字符串
  4. Kibana查询说明
  5. AnimalTFDB 3.0:动物转录因子注释和预测的综合资源库
  6. linux主频限制服务,linux抵御DDOS攻击 通过iptables限制TCP连接和频率
  7. 用C++访问SQL Server 2000的实例
  8. c++vector操作
  9. c语言程序仪表称重编程,基于WinCE的双台面动态汽车称重装置仪表设计
  10. 这是我对智能制造的所有理念
  11. 华为交换机常用的查询命令(自己学习时统计的)
  12. [转]多普达818、828+升级中文WM6.1及必装软件全过程讲解
  13. day2-运算符和分支
  14. 四旋翼飞行器6——光流传感器简介
  15. 计算机上如何转换搜狗,无法切换到搜狗输入法怎么办
  16. python实现qq登录腾讯视频_QQ腾讯视频爬取和qv_rmt限速算法python版
  17. 图像配准(匹配)与变化检测
  18. 51nod 1770数数字(找规律)
  19. Android入门第50天-读写本地文件
  20. Apache Doris 向量化版本在小米A/B实验场景的调优实践

热门文章

  1. 成都拓嘉启远:拼多多补单套券的危害
  2. 潜在语义索引(LSI)
  3. python爬虫遇到验证码、输入验证码后提醒验证码错误_爬虫遇到头疼的验证码?Python实战讲解弹窗处理和验证码识别...
  4. Android 语言国际化
  5. 我的时间管理——计划与总结的重要性
  6. Linux 中用 dd 命令来测试硬盘读写速度
  7. java 三点_[Java教程]三点运算符使用方法
  8. 爬虫经典项目——HFUT GPA计算器
  9. python图像处理(二)绘制函数图像
  10. [转]普通软件项目开发过程规范(五)—— 总结