商软B v5.4.1的登录验证分析
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的登录验证分析相关推荐
- 微信加密与登录验证分析
微信加密与安全通信流程分析 背景 微信渐渐已经成为了大多数中国人日常会话的通讯工具.微信的通信安全,很大程度上保证了普通民众的数据安全,也因此显得十分重要. 本文主要在其他对微信研究的基础上, ...
- [转载]ESFramework 4.0 快速上手(15) -- 客户端登录验证
ESFramework 4.0 快速上手(15) -- 客户端登录验证 在之前版本的Rapid引擎中,是没有提供客户端登陆验证的机制的,如果要验证用户的帐号密码信息,我们只有自己手动通过自定义信息来实 ...
- java web 怎么用solr_使用web过滤器增加solr后台登录验证
solr后台自带是没有登录功能的,默认访问地址是:http://localhost:8983/solr/#/(内置jetty运行). 要给sorl后台增加登录验证方法: 1.使用web服务器的登录验证 ...
- flask token 登录验证
flask token 登录验证 视频 https://www.youtube.com/watch?v=J5bIPtEbS0Q 代码 #!/usr/bin/env python # -*- encod ...
- 【ADO.NET】2、各种版本的 简单登录验证
一.简单登录验证(防SQL注入) GetString(序号) 返回某一列的值(当用户不记得列名序号时,可使用GetOrdinal()获取到序号) GetInt32(序号) 针对的是 int 字段,返回 ...
- 使用SSH框架实现用户登录验证
今天,写一个非常简单的通过struts+hibernate+spring的集成来实现用户登录验证的例子,让大家了解一下三者是如何是整合的. 我们可以通过myeclipse的向导,生成相应的环境搭建,如 ...
- centos transmission 无法开启登录验证
centos上用transmission下载bt yum install transmission transmission-daemon 安装并运行transmission 但是却一直无法使用用户和 ...
- Uchome的登录验证机制
2019独角兽企业重金招聘Python工程师标准>>> 登录: 成功后设置cookie //设置cookie ssetcookie('auth', authcode("$s ...
- shiro 实现登录验证功能
实现登录验证功能 1.创建自己的Realm对象,继承AuthorizingRealm 实现父类的doGetAuthenticationInfo 认证方法 MyRealm.java packa ...
最新文章
- [BZOJ3337] ORZJRY I --块状链表大毒瘤
- [Zend PHP5 Cerification] Lectures -- 4. XML Web Service
- Python数据类型之字符串
- Kibana查询说明
- AnimalTFDB 3.0:动物转录因子注释和预测的综合资源库
- linux主频限制服务,linux抵御DDOS攻击 通过iptables限制TCP连接和频率
- 用C++访问SQL Server 2000的实例
- c++vector操作
- c语言程序仪表称重编程,基于WinCE的双台面动态汽车称重装置仪表设计
- 这是我对智能制造的所有理念
- 华为交换机常用的查询命令(自己学习时统计的)
- [转]多普达818、828+升级中文WM6.1及必装软件全过程讲解
- day2-运算符和分支
- 四旋翼飞行器6——光流传感器简介
- 计算机上如何转换搜狗,无法切换到搜狗输入法怎么办
- python实现qq登录腾讯视频_QQ腾讯视频爬取和qv_rmt限速算法python版
- 图像配准(匹配)与变化检测
- 51nod 1770数数字(找规律)
- Android入门第50天-读写本地文件
- Apache Doris 向量化版本在小米A/B实验场景的调优实践
热门文章
- 成都拓嘉启远:拼多多补单套券的危害
- 潜在语义索引(LSI)
- python爬虫遇到验证码、输入验证码后提醒验证码错误_爬虫遇到头疼的验证码?Python实战讲解弹窗处理和验证码识别...
- Android 语言国际化
- 我的时间管理——计划与总结的重要性
- Linux 中用 dd 命令来测试硬盘读写速度
- java 三点_[Java教程]三点运算符使用方法
- 爬虫经典项目——HFUT GPA计算器
- python图像处理(二)绘制函数图像
- [转]普通软件项目开发过程规范(五)—— 总结