【文章标题】: RVS2008进入设置密码算法分析
【文章作者】: PT
【软件名称】: RVS 2008 2.0.0.7055
【软件大小】: 4.71 MB
【下载地址】: Returnil2008C 自己搜索下载
【加壳方式】: 无
【保护方式】: IDEA
【编写语言】: DELPHI
【使用工具】: PEID,IDA,DeDe,OllyICE,Virtual PC
【操作平台】: WIN32
【软件介绍】: 瑞泰尼尔安全防御系统软件
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大牛赐教!
--------------------------------------------------------------------------------
【详细过程】
0、前言
Returnil Virtual System(简称RVS) 是国外的一个优秀虚拟保护软件,它使用简便,功能强大,深受网友欢迎。
最近公司里公共机安装上了RVS来保护计算机安全,可是网管也太小气了,居然连管理员权限都不给,用起来非常不顺。
在网络上搜索只看到注册算法分析与注册机的,但没有人分析如何破解进入设置的密码,没办法只得伸手破解掉它。本来以为待驱动的程序破起来会
很困难,哪知道分析后真想晕倒。考虑到自己太懒了这样可不好,于是要求自己这次不能只破解,要分析清楚流程,最好能够
将算法分析出来。于是就有了本文,全当锻炼自己,拒绝潜水。
1、初步分析流程如下
A、分析该软件安装内容包含r3下的外壳进程和r0下的磁盘驱动程序组成,首先分析最简单的Returnil.exe程序,
(其安装目录下有同名的Returnil.e文件,比较内容两者完全相同,貌似是防止exe被修改还原用的,没做分析。);
B、程序启动后发现系统托盘中有其图标,点击显示面板命令弹出提示要求输入密码,此处可以作为分析入口点,
随便输入密码:123456点击确定后,上面的标签显示密码错误,重复试验3次后对话框会自动关闭;
C、使用PEID0.94分析程序为“Borland Delphi 6.0 - 7.0”,KANAL2.9分析无加密算法(居然未分析到加密算法很奇怪,
不知道是不是工具太老了);
D、既然是Delphi程序,当然要祭起DeDe。运行DeDe后查看窗口信息,找到按钮事件的处理函数btnOKClick地址;
E、习惯了使用IDA进行静态分析,于是使用DeDe导出map文件,然后在IDA中使用loadmap脚本加载map文件后,进行静态分析;
F、分析中发现软件居然采用明文比较,暴汗~~~~ 获取密码或爆破到此终结即可;
G、没难度继续分析算法逻辑,请看关键代码分析。
2、关键代码分析
btnOKClick proc near 检查密码对话框OK按钮事件处理函数
CODE:004FE808 mov eax, ds:GlobalData_off_53D9B0 ; 全局数据对象地址
CODE:004FE80D mov eax, [eax]
CODE:004FE80F call ReadPasswordSet_sub_49E180 ; 读取是否需要判断密码 ——————分析密码存放位置以及加密方法的重点函数
CODE:004FE80F
CODE:004FE814 test al, al
CODE:004FE816 jz short loc_4FE880 ; 如果未设置密码则直接跳转到密码正确位置
CODE:004FE816
CODE:004FE818 lea edx, [ebp+var_4] ; 输入密码缓冲区
CODE:004FE81B mov eax, [ebx+314h] ; *edtpass:TRzEdit
CODE:004FE821 call TActionListCollection_GetListItem ; 获取输入的密码
CODE:004FE821
CODE:004FE826 mov eax, [ebp+var_4]
CODE:004FE829 push eax
CODE:004FE82A lea eax, [ebp+Password_var_8] ; 真实密码缓冲区
CODE:004FE82D mov edx, ds:GlobalData_off_53D9B0 ; 全局数据对象地址
CODE:004FE833 mov edx, [edx]
CODE:004FE835 add edx, 3DDh ; 密码保存的地址偏移(约在配置文件偏移0x165处)
CODE:004FE83B mov ecx, 20h ; 保存的密码最大长度
CODE:004FE840 call unknown_libname_21 ; 复制字符串
CODE:004FE840
CODE:004FE845 mov eax, [ebp+Password_var_8] ; 从文件中读取的真密码
CODE:004FE848 pop edx ; 输入的错误密码
CODE:004FE849 call CompareText ; 密码明文比较 ————————可直接爆破或直接获取密码
CODE:004FE849
CODE:004FE84E test eax, eax
CODE:004FE850 jz short loc_4FE880 ; 跳转到密码正确处
CODE:004FE850
CODE:004FE852 mov edx, ds:off_53DD30 ; 密码错误逻辑
CODE:004FE858 mov edx, [edx]
CODE:004FE85A mov eax, [ebx+300h] ; *lblTip:TLabel
CODE:004FE860 call TControl_SetText ; 设置出错信息文本
CODE:004FE860
CODE:004FE865 inc dword ptr [ebx+31Ch] ————错误次数记录
CODE:004FE86B cmp dword ptr [ebx+31Ch], 4 设置密码错误状态
CODE:004FE872 jle short loc_4FE88A
CODE:004FE872
CODE:004FE874 mov dword ptr [ebx+24Ch], 2 设置密码错误状态
CODE:004FE87E jmp short loc_4FE88A
CODE:004FE87E
CODE:004FE880
CODE:004FE880 loc_4FE880: ; CODE XREF: btnOKClick+26j
CODE:004FE880 ; btnOKClick+60j
CODE:004FE880 mov dword ptr [ebx+24Ch], 1 ; 设置密码正确标志
————————————————————————————————————————————————————————
CODE:0049E180 ReadPasswordSet_sub_49E180 proc near ; CODE XREF: btnOKClick+1Fp
CODE:0049E180 lea edx, [eax+278h] ; 设置保存配置文件的缓冲区起始地址
CODE:0049E186 call ReadConfigData_sub_49CD48 ——————继续跟进
CODE:0049E18B retn
CODE:0049E18B
CODE:0049E18B ReadPasswordSet_sub_49E180 endp
————————————————————————————————————————————————————————
CODE:0049CD48 ReadConfigData_sub_49CD48 proc near ; CODE XREF: sub_49D0B0+5Bp
CODE:0049CD48 ; sub_49D154+3Bp
CODE:0049CD48 ; sub_49D228+4Ep
CODE:0049CD48 ; sub_49D4A8+24p
CODE:0049CD48 ; sub_49D518+23p
CODE:0049CD48 ; ReadPasswordSet_sub_49E180+6p
CODE:0049CD48
CODE:0049CD48 Buffer_var_209 = byte ptr -209h
CODE:0049CD48 OKFlag_var_9 = byte ptr -9
CODE:0049CD48 EDX_var_8 = dword ptr -8
CODE:0049CD48 EAX_var_4 = dword ptr -4
CODE:0049CD48
CODE:0049CD48 push ebp
CODE:0049CD49 mov ebp, esp
CODE:0049CD4B add esp, 0FFFFFDF4h
CODE:0049CD51 push ebx
CODE:0049CD52 push esi
CODE:0049CD53 push edi
CODE:0049CD54 mov [ebp+EDX_var_8], edx
CODE:0049CD57 mov [ebp+EAX_var_4], eax
CODE:0049CD5A mov eax, [ebp+EDX_var_8]
CODE:0049CD5D xor ecx, ecx
CODE:0049CD5F mov edx, 200h
CODE:0049CD64 call FillChar
CODE:0049CD64
CODE:0049CD69 lea eax, [ebp+Buffer_var_209]
CODE:0049CD6F xor ecx, ecx
CODE:0049CD71 mov edx, 200h
CODE:0049CD76 call FillChar
CODE:0049CD76
CODE:0049CD7B mov [ebp+OKFlag_var_9], 0 ; 初始化返回标志
CODE:0049CD7F xor eax, eax
CODE:0049CD81 push ebp
CODE:0049CD82 push offset loc_49CEDA
CODE:0049CD87 push dword ptr fs:[eax]
CODE:0049CD8A mov fs:[eax], esp
CODE:0049CD8D xor ebx, ebx
CODE:0049CD8F mov eax, [ebp+EAX_var_4]
CODE:0049CD92 mov eax, [eax+260h] ; RVSYSTEM.DAT文件路径
CODE:0049CD98 call FileExists ————————检查配置文件是否存在
CODE:0049CD98
CODE:0049CD9D test al, al
CODE:0049CD9F jz loc_49CE6A
CODE:0049CD9F
CODE:0049CDA5 test bl, bl
CODE:0049CDA7 jnz short loc_49CDD9
CODE:0049CDA7
CODE:0049CDA9 lea eax, [ebp+Buffer_var_209]
CODE:0049CDAF push eax ; 读取后数据存放缓冲区
CODE:0049CDB0 mov eax, [ebp+EAX_var_4]
CODE:0049CDB3 mov eax, [eax+260h] ; RVSYSTEM.DAT文件路径
CODE:0049CDB9 mov ecx, 200h ; 读取长度
CODE:0049CDBE xor edx, edx ; 应该是读取位置
CODE:0049CDC0 call ReadFileData_sub_498D40 ; 读取文件前0x200个字节 ————读取文件函数不需要跟进
CODE:0049CDC0
CODE:0049CDC5 test al, al
CODE:0049CDC7 jz short loc_49CDD9
CODE:0049CDC7
CODE:0049CDC9 lea edx, [ebp+Buffer_var_209] ; 解密后数据存放地址
CODE:0049CDCF mov eax, [ebp+EAX_var_4] ; 全局对象
CODE:0049CDD2 call DecodeData_sub_49DC44 ; 解密数据块 ————————重点,跟进
CODE:0049CDD2
CODE:0049CDD7 mov ebx, eax
CODE:0049CDD7
CODE:0049CDD9
CODE:0049CDD9 loc_49CDD9: ; CODE XREF: ReadConfigData_sub_49CD48+5Fj
CODE:0049CDD9 ; ReadConfigData_sub_49CD48+7Fj
CODE:0049CDD9 test bl, bl ; 判断解密数据长度
CODE:0049CDDB jnz short loc_49CE23 解密成功
CODE:0049CDDB
...............................................................
————————————————————————————————————————————————————————
CODE:0049DC44 DecodeData_sub_49DC44 proc near ; CODE XREF: ReadConfigData_sub_49CD48+8Ap
CODE:0049DC44 ; ReadConfigData_sub_49CD48+BEp
CODE:0049DC44 ; ReadConfigData_sub_49CD48+105p
CODE:0049DC44 ; ReadConfigData_sub_49CD48+14Fp
CODE:0049DC44
CODE:0049DC44 Buffer_var_20C = byte ptr -20Ch
CODE:0049DC44
CODE:0049DC44 push ebx
CODE:0049DC45 push esi
CODE:0049DC46 push edi
CODE:0049DC47 add esp, 0FFFFFE00h
CODE:0049DC4D mov esi, edx
CODE:0049DC4F mov ebx, eax
CODE:0049DC51 mov edx, esp
CODE:0049DC53 mov edi, esi
CODE:0049DC55 mov eax, edi
CODE:0049DC57 mov ecx, 200h
CODE:0049DC5C call Move
CODE:0049DC5C
CODE:0049DC61 push 200h ; 待解密数据长度
CODE:0049DC66 lea ecx, [esp+210h+Buffer_var_20C] ; 加密数据存放地址
CODE:0049DC6A lea edx, [esp+210h+Buffer_var_20C] ; 解密后数据存放地址
CODE:0049DC6E mov eax, ebx
CODE:0049DC70 call IDEAdecode_sub_49E2D4 —————————————解密函数,重点
CODE:0049DC70
CODE:0049DC75 mov eax, esp
CODE:0049DC77 cmp dword ptr [eax], 71982h ; ——检查解密后数据头4字节是否为0x71982
CODE:0049DC7D setz bl
CODE:0049DC80 test bl, bl
CODE:0049DC82 jz short loc_49DC92
CODE:0049DC82
CODE:0049DC84 mov edx, edi
CODE:0049DC86 mov eax, esp
CODE:0049DC88 mov ecx, 200h
CODE:0049DC8D call Move
CODE:0049DC8D
CODE:0049DC92
CODE:0049DC92 loc_49DC92: ; CODE XREF: DecodeData_sub_49DC44+3Ej
CODE:0049DC92 mov eax, ebx
CODE:0049DC94 add esp, 200h
CODE:0049DC9A pop edi
CODE:0049DC9B pop esi
CODE:0049DC9C pop ebx
CODE:0049DC9D retn
CODE:0049DC9D
CODE:0049DC9D DecodeData_sub_49DC44 endp
————————————————————————————————————————————————————————
CODE:0049E2D4 IDEAdecode_sub_49E2D4 proc near ; CODE XREF: DecodeData_sub_49DC44+2Cp
CODE:0049E2D4 ; sub_536EAC+18Dp
CODE:0049E2D4
CODE:0049E2D4 var_EA = byte ptr -0EAh
CODE:0049E2D4 Size_var_9 = byte ptr -9
CODE:0049E2D4 ECX_var_8 = dword ptr -8
CODE:0049E2D4 EDX_var_4 = dword ptr -4
CODE:0049E2D4 arg_0 = dword ptr 8
CODE:0049E2D4
CODE:0049E2D4 push ebp
CODE:0049E2D5 mov ebp, esp
CODE:0049E2D7 add esp, 0FFFFFF14h
CODE:0049E2DD push ebx
CODE:0049E2DE push esi
CODE:0049E2DF mov [ebp+ECX_var_8], ecx
CODE:0049E2E2 mov [ebp+EDX_var_4], edx
CODE:0049E2E5 mov ebx, eax
CODE:0049E2E7 lea eax, [ebp+var_EA]
CODE:0049E2ED xor ecx, ecx
CODE:0049E2EF mov edx, 0E0h
CODE:0049E2F4 call FillChar
CODE:0049E2F4
CODE:0049E2F9 lea eax, [ebx+22Ch] ; B060D775-C0FE-49 (密钥)
CODE:0049E2FF push eax
CODE:0049E300 lea edx, [ebx+22Ch]
CODE:0049E306 lea eax, [ebp+var_EA]
CODE:0049E30C mov ecx, 10h
CODE:0049E311 call MakeIDEAKey_sub_499410 ——————重点,生成IDEA密钥
CODE:0049E311
CODE:0049E316 mov esi, [ebp+arg_0]
CODE:0049E319 test esi, esi
CODE:0049E31B jns short loc_49E320
CODE:0049E31B
CODE:0049E31D add esi, 7
CODE:0049E31D
CODE:0049E320
CODE:0049E320 loc_49E320: ; CODE XREF: IDEAdecode_sub_49E2D4+47j
CODE:0049E320 sar esi, 3
CODE:0049E323 dec esi
CODE:0049E324 test esi, esi
CODE:0049E326 jl short loc_49E34A ;
CODE:0049E326
CODE:0049E328 inc esi
CODE:0049E329 xor ebx, ebx ; ——————————下面为解密循环
CODE:0049E329
CODE:0049E32B
CODE:0049E32B loc_49E32B: ; CODE XREF: IDEAdecode_sub_49E2D4+74j
CODE:0049E32B mov eax, ebx
CODE:0049E32D shl eax, 3
CODE:0049E330 mov edx, [ebp+ECX_var_8]
CODE:0049E333 lea ecx, [edx+eax] ; Ecx为读取出的加密数据
CODE:0049E336 mov edx, [ebp+EDX_var_4]
CODE:0049E339 add edx, eax ; EDX为存放解密后的数据缓冲区
CODE:0049E33B lea eax, [ebp+var_EA] ; 密钥
CODE:0049E341 call sub_499708
CODE:0049E341
CODE:0049E346 inc ebx
CODE:0049E347 dec esi
CODE:0049E348 jnz short loc_49E32B
CODE:0049E348
CODE:0049E34A
CODE:0049E34A loc_49E34A: ; CODE XREF: IDEAdecode_sub_49E2D4+52j
CODE:0049E34A lea eax, [ebp+var_EA] ; —————————执行到此EDX_var_4中即为解密后的明文数据
.................................................................
CODE:0049E360 mov al, [ebp+Size_var_9]
CODE:0049E363 pop esi
CODE:0049E364 pop ebx
CODE:0049E365 mov esp, ebp
CODE:0049E367 pop ebp
CODE:0049E368 retn 4
CODE:0049E368
CODE:0049E368 IDEAdecode_sub_49E2D4 endp
————————————————————————————————————————————————————————
CODE:00499410 MakeIDEAKey_sub_499410 proc near ; CODE XREF: IDEAdecode_sub_49E2D4+3Dp
CODE:00499410 ; sub_49E36C+3Dp
CODE:00499410
CODE:00499410 KeySize_var_8 = dword ptr -8
CODE:00499410 Buffer_var_4 = dword ptr -4
CODE:00499410 arg_0 = dword ptr 8
CODE:00499410
CODE:00499410 push ebp
CODE:00499411 mov ebp, esp
CODE:00499413 add esp, 0FFFFFFF8h
CODE:00499416 push ebx
CODE:00499417 push esi
CODE:00499418 push edi
CODE:00499419 mov [ebp+KeySize_var_8], ecx
CODE:0049941C mov edi, edx
CODE:0049941E mov [ebp+Buffer_var_4], eax
CODE:00499421 mov ebx, [ebp+arg_0]
CODE:00499424 cmp [ebp+KeySize_var_8], 10h ; 检查长度
CODE:00499428 jz short loc_499440 ; 检查字符串是否为空
CODE:00499428
CODE:0049942A mov ecx, offset aIdeaInvalidKey ; "IDEA: Invalid key length" ——————这里暴露了算法
CODE:0049942F mov dl, 1
CODE:00499431 mov eax, off_407CF4
CODE:00499436 call TOleStream_Create ; Delphi2006/BDS2006 Visual Component Library
CODE:00499436
CODE:0049943B call @@RaiseExcept ; __linkproc__ RaiseExcept
CODE:0049943B
CODE:00499440
CODE:00499440 loc_499440: ; CODE XREF: MakeIDEAKey_sub_499410+18j
CODE:00499440 test ebx, ebx ; 检查字符串是否为空
CODE:00499442 jnz short loc_499467 ; 复制前8字符两次到缓冲区内
CODE:00499442
CODE:00499444 mov eax, [ebp+Buffer_var_4] ; 用0填充
CODE:00499447 xor ecx, ecx
CODE:00499449 mov edx, 8
CODE:0049944E call FillChar
CODE:0049944E
CODE:00499453 mov eax, [ebp+Buffer_var_4]
CODE:00499456 add eax, 8
CODE:00499459 xor ecx, ecx
CODE:0049945B mov edx, 8
CODE:00499460 call FillChar
CODE:00499460
CODE:00499465 jmp short loc_49948A ;
CODE:00499465
CODE:00499467
CODE:00499467 loc_499467: ; CODE XREF: MakeIDEAKey_sub_499410+32j
CODE:00499467 mov edx, [ebp+Buffer_var_4] ; 复制前8字符B060D775两次到缓冲区内作为一组密钥?不知道为什么不直接使用B060D775-C0FE-49
CODE:0049946A mov esi, ebx
CODE:0049946C mov eax, esi
CODE:0049946E mov ecx, 8
CODE:00499473 call Move
CODE:00499473
CODE:00499478 mov edx, [ebp+Buffer_var_4]
CODE:0049947B add edx, 8
CODE:0049947E mov eax, esi
CODE:00499480 mov ecx, 8
CODE:00499485 call Move
CODE:00499485
CODE:0049948A
CODE:0049948A loc_49948A: ; CODE XREF: MakeIDEAKey_sub_499410+55j
————————以下是密钥生成逻辑
CODE:0049948A mov ecx, edi ; B060D775-C0FE-49
CODE:0049948C mov eax, [ebp+Buffer_var_4] ; B060D775B060D775
CODE:0049948F add eax, 10h
CODE:00499492 xor edx, edx
CODE:00499492
CODE:00499494
CODE:00499494 loc_499494: ; CODE XREF: MakeIDEAKey_sub_499410+9Dj
CODE:00499494 xor ebx, ebx
CODE:00499496 mov bl, [ecx+1]
CODE:00499499 shl ebx, 8
CODE:0049949C movzx esi, byte ptr [ecx]
CODE:0049949F add bx, si
CODE:004994A2 mov [eax+edx*2], bx
CODE:004994A6 add ecx, 2
CODE:004994A9 inc edx
CODE:004994AA cmp edx, 8
CODE:004994AD jnz short loc_499494
CODE:004994AD
CODE:004994AF mov edx, 6
CODE:004994AF
CODE:004994B4
CODE:004994B4 loc_4994B4: ; CODE XREF: MakeIDEAKey_sub_499410+14Ej
CODE:004994B4 mov cx, [eax+2]
CODE:004994B8 shl ecx, 9
CODE:004994BB movzx ebx, word ptr [eax+4]
CODE:004994BF shr ebx, 7
CODE:004994C2 or cx, bx
CODE:004994C5 mov [eax+10h], cx
CODE:004994C9 mov cx, [eax+4]
CODE:004994CD shl ecx, 9
CODE:004994D0 movzx ebx, word ptr [eax+6]
CODE:004994D4 shr ebx, 7
CODE:004994D7 or cx, bx
CODE:004994DA mov [eax+12h], cx
CODE:004994DE mov cx, [eax+6]
CODE:004994E2 shl ecx, 9
CODE:004994E5 movzx ebx, word ptr [eax+8]
CODE:004994E9 shr ebx, 7
CODE:004994EC or cx, bx
CODE:004994EF mov [eax+14h], cx
CODE:004994F3 mov cx, [eax+8]
CODE:004994F7 shl ecx, 9
CODE:004994FA movzx ebx, word ptr [eax+0Ah]
CODE:004994FE shr ebx, 7
CODE:00499501 or cx, bx
CODE:00499504 mov [eax+16h], cx
CODE:00499508 mov cx, [eax+0Ah]
CODE:0049950C shl ecx, 9
CODE:0049950F movzx ebx, word ptr [eax+0Ch]
CODE:00499513 shr ebx, 7
CODE:00499516 or cx, bx
CODE:00499519 mov [eax+18h], cx
CODE:0049951D mov cx, [eax+0Ch]
CODE:00499521 shl ecx, 9
CODE:00499524 movzx ebx, word ptr [eax+0Eh]
CODE:00499528 shr ebx, 7
CODE:0049952B or cx, bx
CODE:0049952E mov [eax+1Ah], cx
CODE:00499532 mov cx, [eax+0Eh]
CODE:00499536 shl ecx, 9
CODE:00499539 movzx ebx, word ptr [eax]
CODE:0049953C shr ebx, 7
CODE:0049953F or cx, bx
CODE:00499542 mov [eax+1Ch], cx
CODE:00499546 mov cx, [eax]
CODE:00499549 shl ecx, 9
CODE:0049954C movzx ebx, word ptr [eax+2]
CODE:00499550 shr ebx, 7
CODE:00499553 or cx, bx
CODE:00499556 mov [eax+1Eh], cx
CODE:0049955A add eax, 10h
CODE:0049955D dec edx
CODE:0049955E jnz loc_4994B4
CODE:0049955E
CODE:00499564 mov edx, [ebp+Buffer_var_4]
CODE:00499567 add edx, 78h
CODE:0049956A mov eax, [ebp+Buffer_var_4]
CODE:0049956D add eax, 10h
CODE:00499570 call sub_4992B4
CODE:00499570
CODE:00499575 pop edi
CODE:00499576 pop esi
CODE:00499577 pop ebx
CODE:00499578 pop ecx
CODE:00499579 pop ecx
CODE:0049957A pop ebp
CODE:0049957B retn 4
CODE:0049957B
CODE:0049957B MakeIDEAKey_sub_499410 endp
————————————————————————————————————————————————————————
3、算法分析
本来想写算法分析着,但是看uuk写的“RVS Lite2011 注册算法 IDEA 分析”大家可以去看下他的帖子。链接:http://bbs.pediy.com/showthread.php?t=130698&highlight=IDEA
已经将所有内容全部分析出来了,这里我就偷懒了。
4、总结
A、RVS将密码采用IDEA加密后存储在C:\RETURNIL\RVSYSTEM.DAT文件中偏移0x165处;
B、RVSYSTEM.DAT文件以0x200大小为一块加解密;
C、配置文件加解密密钥为:B060D775-C0FE-49;
D、密码明文比较。(分析其升级版本发现已经不再明文比较密码,但爆破分析仍然很容易)
--------------------------------------------------------------------------------
【经验总结】
软件是否安全,不是使用加密算法软件就足够安全了,关键看是否存在短板,就比如说当前的这个软件明文比较当然容易死翘翘了,搞不得为什么不
与驱动程序想结合操作?
破解分析要选好入口点(如何选就要仁者见仁智者见智了),合理利用工具,在结合动态调试很容易分析出软件的逻辑。顺便说一句delphi程序使用
寄存器传参静态分析真累,建议结合动态调试器跟踪分析。
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!