【Delphi程序逆向技巧】Protected Folder算法分析+网络注册机编写

信息安全 3281 Views

小编注:由于本文代码篇幅较长,故将代码做部分节选,完整内容请参看论坛原帖。

Protected Folder,这是一款我比较喜欢的软件,保护文件挺方便的.可以看图:

注册方式是本地验证后再网络验证.因此分两部分分析.delphi2009编译的.第一部分: 本地注册算法分析.首先应该明白delphi编译器的特点.参数三个之内用寄存器,多的用堆栈.即delphi(eax,edx,ecx,stack…) 从右往左依次传参!参数位置使用的传参变量固定.所有的符号分析有IDR完成,生成map给od用还是挺方便的.在IDR中可以找到注册按钮事件地址:005D0DB8 > . 55 push ebp ; UnitUserRegister_TFormUserRegister_Button_ActivateClick005D0DB9 . 8BEC mov ebp,esp005D0DBB . B9 39000000 mov ecx,0x39005D0DC0 > 6A 00 push 0x0005D0DC2 . 6A 00 push 0x0005D0DC4 . 49 dec ecx005D0DC5 .^ 75 F9 jnz short Protecte.005D0DC0005D0DC7 . 53 push ebx005D0DC8 . 56 push esi005D0DC9 . 57 push edi005D0DCA . 8985 0CFFFFFF mov dword ptr ss:[ebp-0xF4],eax005D0DD0 . 8D85 10FFFFFF lea eax,dword ptr ss:[ebp-0xF0]005D0DD6 . 8B15 D0C34500 mov edx,dword ptr ds:[0x45C3D0] ; Protecte.0045C3D4005D0DDC . E8 8363E3FF call <Protecte.System_@InitializeRecord>005D0DE1 . 33C0 xor eax,eax005D0DE3 . 55 push ebp005D0DE4 . 68 DB1C5D00 push Protecte.005D1CDB005D0DE9 . 64:FF30 push dword ptr fs:[eax]005D0DEC . 64:8920 mov dword ptr fs:[eax],esp005D0DEF . 8B85 0CFFFFFF mov eax,dword ptr ss:[ebp-0xF4]005D0DF5 . 8B80 A8030000 mov eax,dword ptr ds:[eax+0x3A8]005D0DFB . 33D2 xor edx,edx005D0DFD . E8 6AF2F0FF call <Protecte.TControl_SetVisible>005D0E02 . 8B85 0CFFFFFF mov eax,dword ptr ss:[ebp-0xF4]005D0E08 . 8B80 90030000 mov eax,dword ptr ds:[eax+0x390]005D0E0E . 33D2 xor edx,edx005D0E10 . 8B08 mov ecx,dword ptr ds:[eax]005D0E12 . FF51 64 call dword ptr ds:[ecx+0x64]005D0E15 . 8B85 0CFFFFFF mov eax,dword ptr ss:[ebp-0xF4]005D0E1B . 8B80 94030000 mov eax,dword ptr ds:[eax+0x394]005D0E21 . 8B80 A8010000 mov eax,dword ptr ds:[eax+0x1A8]005D0E27 . 8B40 0C mov eax,dword ptr ds:[eax+0xC]005D0E2A . BA 58020000 mov edx,0x258005D0E2F . E8 00CCFFFF call <Protecte.TGIFImage_SetAnimationSpeed>进入关键call 005D0E92 call <Protecte.regkeyValidate> 分析:005D26D0 >/$ 55 push ebp ; regkeyValidate005D26D1 |. 8BEC mov ebp,esp005D26D3 |. B9 22000000 mov ecx,0x22005D26D8 |> 6A 00 /push 0x0005D26DA |. 6A 00 |push 0x0005D26DC |. 49 |dec ecx005D26DD |.^ 75 F9 \jnz short Protecte.005D26D8005D26DF |. 51 push ecx005D26E0 |. 53 push ebx005D26E1 |. 8955 FC mov [local.1],edx005D26E4 |. 8B45 FC mov eax,[local.1]005D26E7 |. E8 E83FE3FF call <Protecte.j_System_@LStrAddRef>005D26EC |. 33C0 xor eax,eax005D26EE |. 55 push ebp005D26EF |. 68 112F5D00 push Protecte.005D2F11005D26F4 |. 64:FF30 push dword ptr fs:[eax]005D26F7 |. 64:8920 mov dword ptr fs:[eax],esp005D26FA |. 33DB xor ebx,ebx005D26FC |. 8D55 E8 lea edx,[local.6]005D26FF |. 8B45 FC mov eax,[local.1]005D2702 |. E8 BDA4E8FF call <Protecte._Unit13_UpperCase> //将注册码转成大写005D2707 |. 8B55 E8 mov edx,[local.6]005D270A |. 8D45 FC lea eax,[local.1]005D270D |. E8 2E40E3FF call <Protecte.@UStrLAsg> //类似这些函数,是字符串赋值产生的(字符串都是对象)005D2712 |. 0FB605 242F5D>movzx eax,byte ptr ds:[0x5D2F24]

综合以上分析,可以知道,对注册码的要求是: 范围:0-9,A-F.并且key[0-3]_md5 == key[9-12]_md5_r4_md5key[4-8]_MD5_MD5 == key[13-17]_MD5_L5_MD5_MD5显然即 key[0-3]=key[9-12]_md5_r4,key[4-8]=key[13-17]_MD5_L5.

class KeyMaker{public: KeyMaker::KeyMaker(const char* keyset="0123456789abcdeflo") { keyset_.assign(keyset); generator_ = nullptr; distribution_ = nullptr; } KeyMaker::~KeyMaker() { } std::string operator()() { if (distribution_ != nullptr) delete distribution_; if (generator_ != nullptr) delete generator_; distribution_ = new std::uniform_int_distribution<int>(0, keyset_.length() – 1); seed_ = std::chrono::system_clock::now().time_since_epoch().count(); generator_ = new std::default_random_engine(seed_); makeKey(); return rawkey_; }private: void makeKey() { randomKey(); kcer.prepare(key_[2]); kcer.prepare(key_[3]); kcer.upperStr(key_[2]); key_[0] = hasher(key_[2].c_str(), "md5"); key_[0] = key_[0].substr(key_[0].length() – 4, 4); kcer.upperStr(key_[3]); key_[1] = hasher(key_[3].c_str(), "md5"); key_[1] = key_[1].substr(0, 5); rawkey_.insert(0, key_[1]); rawkey_.insert(0, key_[0]); kcer.lowerStr(rawkey_); decorate(); } char randomC() { return keyset_[(*distribution_)(*generator_)]; } void randomKey() { for (int i = 0; i < 4; ++i) { key_[2] += randomC(); } for (int i = 0; i < 5; ++i) { key_[3] += randomC(); } key_[4] += randomC(); key_[4] += randomC(); rawkey_ = key_[2] + key_[3] + key_[4]; } void decorate() { for (size_t i = 1; i < rawkey_.length()/5; i++) { rawkey_.insert(i * 5 + i – 1, "-"); } }private: std::string keyset_; std::string key_[5]; std::string rawkey_; unsigned int seed_; std::default_random_engine *generator_; std::uniform_int_distribution<int> *distribution_; HASH hasher; KeyChecker kcer;//还原软件算法的类};我也还原了一下软件的算法,写成一个类:class KeyChecker{public: KeyChecker::KeyChecker() { } KeyChecker::~KeyChecker() { } void upperStr(std::string &str) { for (size_t i = 0; i < str.length(); i++) { str = toupper(str); } } void lowerStr(std::string &str) { for (size_t i = 0; i < str.length(); i++) { str = tolower(str); } } bool operator()(const char* kstr) { std::string edata(kstr); prepare(edata); key[0] = edata.substr(0, 4); key[2] = edata.substr(9, 4); key[1] = edata.substr(4, 5); key[3] = edata.substr(13, 5); makeK1(); makeK2(); makeK3(); makeK4(); printKeys(); return key[0] == key[2] && key[1] == key[3]; } void prepare(std::string& str) { lowerStr(str); replaceKc(str, "-", ""); replaceKc(str, "o", "0"); replaceKc(str, "l", "1"); replaceKc(str, " ", ""); }private: void replaceKc(std::string& str, std::string oldpat, std::string newpat) { while (str.find(oldpat) != std::string::npos) { str.replace(str.find(oldpat), oldpat.length(), newpat); } } void makeK1() { upperStr(key[0]); key[0] = hashfunc(key[0].c_str(), "md5"); } void makeK2() { upperStr(key[1]); key[1] = hashfunc(key[1].c_str(), "md5"); key[1] = hashfunc(key[1].c_str(), "md5"); } void makeK3() { upperStr(key[2]); key[2] = hashfunc(key[2].c_str(), "md5"); key[2] = key[2].substr(key[2].length() – 4, 4); upperStr(key[2]); key[2] = hashfunc(key[2].c_str(), "md5"); } void makeK4() { upperStr(key[3]); key[3] = hashfunc(key[3].c_str(), "md5"); key[3] = key[3].substr(0, 5); upperStr(key[3]); key[3] = hashfunc(key[3].c_str(), "md5"); key[3] = hashfunc(key[3].c_str(), "md5"); } void printKeys() { printf("%s == %s &&\n%s == %s\n", key[0].c_str(), key[2].c_str(), key[1].c_str(), key[3].c_str()); }private: HASH hashfunc;//计算MD5的用的类,就不放出来了 std::string key[4];};

至此,第一部分本地算法验证部分就分析完了.第二部分:网络验证的过程逻辑分析.从 call <Protecte.regkeyValidate> 出来后本地验证正确的话:

005D0E92 . E8 39180000 call <Protecte.regkeyValidate>005D0E97 . 84C0 test al,al005D0E99 . 0F85 D5000000 jnz Protecte.005D0F74 //本地验证正确会跳转005D0E9F . 8B85 0CFFFFFF mov eax,dword ptr ss:[ebp-0xF4]………………………….

跳到这里:

005D0F74 > \8B85 0CFFFFFF mov eax,dword ptr ss:[ebp-0xF4]005D0F7A . 8B80 90030000 mov eax,dword ptr ds:[eax+0x390]005D0F80 . BA FFFFFF00 mov edx,0xFFFFFF005D0F85 . E8 52F3F0FF call <Protecte.TControl_SetColor>005D0F8A . 8D85 24FFFFFF lea eax,dword ptr ss:[ebp-0xDC]005D0F90 . BA 681D5D00 mov edx,<Protecte.aMmDdYyyyHhMm_2> ; UNICODE "mm/dd/yyyy hh:mm:ss"005D0F95 . E8 A657E3FF call <Protecte.@UStrLAsg>005D0F9A . 66:C785 18FFF>mov word ptr ss:[ebp-0xE8],0x2F005D0FA3 . 66:C785 1AFFF>mov word ptr ss:[ebp-0xE6],0x3A005D0FAC . 8D55 F8 lea edx,dword ptr ss:[ebp-0x8]005D0FAF . 8B85 0CFFFFFF mov eax,dword ptr ss:[ebp-0xF4]005D0FB5 . 8B80 90030000 mov eax,dword ptr ds:[eax+0x390]005D0FBB . E8 64F1F0FF call <Protecte.TControl_GetText>005D0FC0 . 8D95 F8FEFFFF lea edx,dword ptr ss:[ebp-0x108]005D0FC6 . 8B45 F8 mov eax,dword ptr ss:[ebp-0x8]005D0FC9 . E8 F6BBE8FF call <Protecte._Unit13_UpperCase>005D0FCE . 8B95 F8FEFFFF mov edx,dword ptr ss:[ebp-0x108]005D0FD4 . 8D45 F8 lea eax,dword ptr ss:[ebp-0x8]005D0FD7 . E8 6457E3FF call <Protecte.@UStrLAsg>005D0FDC . 0FB605 901D5D>movzx eax,byte ptr ds:[0x5D1D90]005D0FE3 . 50 push eax005D0FE4 . 8D85 F4FEFFFF lea eax,dword ptr ss:[ebp-0x10C]005D0FEA . 50 push eax005D0FEB . B9 A01D5D00 mov ecx,<Protecte.char_0_>005D0FF0 . BA B01D5D00 mov edx,<Protecte.char_O_>005D0FF5 . 8B45 F8 mov eax,dword ptr ss:[ebp-0x8]005D0FF8 . E8 3F56E9FF call <Protecte.StringReplace> //O用0替换005D0FFD . 8B95 F4FEFFFF mov edx,dword ptr ss:[ebp-0x10C]上面那个Disk是你硬盘的serialnumber,他是如何来的呢?我简单跟踪一下,发现在005D114F call <Protecte.ZLIBArchiveGlobals2_sub_005A0>里面,最终追踪到如下:005A013F . E8 1068E6FF call <Protecte.System_@UStrFromWStr>005A0144 . 8B55 B4 mov edx,dword ptr ss:[ebp-0x4C]005A0147 . B8 38045A00 mov eax,Protecte.005A0438 ; UNICODE "SELECT"005A014C . E8 C76FE6FF call <Protecte.Pos_0>005A0151 . 48 dec eax005A0152 . 75 23 jnz short Protecte.005A0177005A0154 . 8D45 F8 lea eax,dword ptr ss:[ebp-0x8]005A0157 . E8 3488E6FF call <Protecte.System_@IntfClear>005A015C . 50 push eax005A015D . 6A 00 push 0x0005A015F . 6A 10 push 0x10005A0161 . 68 4C045A00 push Protecte.005A044C ; UNICODE "WQL"005A0166 . 53 push ebx005A0167 . 8B45 FC mov eax,dword ptr ss:[ebp-0x4]005A016A . 50 push eax005A016B . 8B00 mov eax,dword ptr ds:[eax]005A016D . FF50 3C call dword ptr ds:[eax+0x3C]005A0170 . E8 2B89E6FF call <Protecte.System_@CheckAutoResult>005A0175 . EB 1C jmp short Protecte.005A0193

哈原来是WQL语言查询的(WMI中的查询语言).windows自带 wbemtest.exe WQL测试工具通过查询语句SELECT * FROM Win32_PhysicalMedia可以看到一样的结果:图…………….

向下继续分析来到:005D1441 . 8B85 0CFFFFFF mov eax,dword ptr ss:[ebp-0xF4]005D1447 . 8B80 98030000 mov eax,dword ptr ds:[eax+0x398]005D144D . 8B8D 08FFFFFF mov ecx,dword ptr ss:[ebp-0xF8]005D1453 . 8B55 FC mov edx,dword ptr ss:[ebp-0x4]005D1456 . E8 E59AF8FF call <Protecte.IdHTTP_TIdCustomHTTP_Post> //post数据,地址是http://pf.iobit.com/functions/check.php005D145B . A1 54075F00 mov eax,dword ptr ds:[0x5F0754]005D1460 . 8B00 mov eax,dword ptr ds:[eax]005D1462 . E8 C506F3FF call <Protecte.Forms_TApplication_ProcessMessages>005D1467 . 8B85 08FFFFFF mov eax,dword ptr ss:[ebp-0xF8]005D146D . 8B10 mov edx,dword ptr ds:[eax]005D146F . FF52 44 call dword ptr ds:[edx+0x44]005D1472 . 8B85 08FFFFFF mov eax,dword ptr ss:[ebp-0xF8]005D1478 . 50 push eax005D1479 . 8D95 B4FEFFFF lea edx,dword ptr ss:[ebp-0x14C]005D147F . 8B85 04FFFFFF mov eax,dword ptr ss:[ebp-0xFC]005D1485 . E8 7699EAFF call <Protecte.Classes_TStringStream_GetDataString>//获取服务器传回的数据005D148A . 8B85 B4FEFFFF mov eax,dword ptr ss:[ebp-0x14C]005D1490 . E8 E352E3FF call <Protecte.System_@UStrToPWChar>005D1495 . 8BC8 mov ecx,eax005D1497 . BA 50205D00 mov edx,Protecte.005D2050005D149C . B8 70205D00 mov eax,<Protecte.char_@>005D14A1 . E8 BA52EAFF call <Protecte.ExtractStrings>//结压缩服务器数据,其实就是分割,因为服务器传回的数据是XXX&XXX&XXX形式的.005D14A6 . 8B85 08FFFFFF mov eax,dword ptr ss:[ebp-0xF8]005D14AC . 8B10 mov edx,dword ptr ds:[eax]005D14AE . FF52 14 call dword ptr ds:[edx+0x14]//这里指针调用的是Classes_TStringList_GetCount,即数据分割后得到的数据项数.005D14B1 . 85C0 test eax,eax //必须大于一项,不然就是下面的出错提示了005D14B3 . 75 6A jnz short Protecte.005D151F005D14B5 . 6A 30 push 0x30005D14B7 . 8D85 B0FEFFFF lea eax,dword ptr ss:[ebp-0x150]005D14BD . 50 push eax005D14BE . A1 84055F00 mov eax,dword ptr ds:[0x5F0584]005D14C3 . 8B00 mov eax,dword ptr ds:[eax]005D14C5 . B9 9C205D00 mov ecx,<Protecte.aActiveError> ; UNICODE "Active Error"005D14CA . BA C4205D00 mov edx,<Protecte.aAcerr> ; UNICODE "acerr"005D14CF . E8 3CC1FBFF call <Protecte.PLabelNote_sub_0058D610>005D14D4 . 8B85 B0FEFFFF mov eax,dword ptr ss:[ebp-0x150]005D14DA . E8 9952E3FF call <Protecte.System_@UStrToPWChar>005D14DF . 50 push eax005D14E0 . 8D85 ACFEFFFF lea eax,dword ptr ss:[ebp-0x154]005D14E6 . 50 push eax005D14E7 . A1 84055F00 mov eax,dword ptr ds:[0x5F0584]005D14EC . 8B00 mov eax,dword ptr ds:[eax]005D14EE . B9 DC205D00 mov ecx,<Protecte.aUnknownError> ; UNICODE "Unknown Error!"005D14F3 . BA 08215D00 mov edx,<Protecte.aUnerr> ; UNICODE "unerr"005D14F8 . E8 13C1FBFF call <Protecte.PLabelNote_sub_0058D610>005D14FD . 8B85 ACFEFFFF mov eax,dword ptr ss:[ebp-0x154]005D1503 . E8 7052E3FF call <Protecte.System_@UStrToPWChar>005D1508 . 50 push eax005D1509 . 8B85 0CFFFFFF mov eax,dword ptr ss:[ebp-0xF4]005D150F . E8 2475F1FF call <Protecte.Controls_TWinControl_GetHandle>005D1514 . 50 push eax ; |hOwner005D1515 . E8 E28FE3FF call <Protecte.MessageBoxW> ; \MessageBoxW005D151A . E9 10060000 jmp Protecte.005D1B2F005D151F > 8D8D A8FEFFFF lea ecx,dword ptr ss:[ebp-0x158]//服务器返回的数据项数大于一项会到这里005D1525 . 33D2 xor edx,edx005D1527 . 8B85 08FFFFFF mov eax,dword ptr ss:[ebp-0xF8]005D152D . 8B18 mov ebx,dword ptr ds:[eax]005D152F . FF53 0C call dword ptr ds:[ebx+0xC] //调用的是Classes_TStringList_Get,edx是获取的第几项,这里是第0项005D1532 . 8B85 A8FEFFFF mov eax,dword ptr ss:[ebp-0x158]005D1538 . BA A01D5D00 mov edx,<Protecte.char_0_>005D153D . E8 B658E3FF call <Protecte.j_@UStrCmp> //也就是说,服务器返回数据的第一个是0才是正确的.005D1542 . 0F85 8D010000 jnz Protecte.005D16D5005D1548 . 8B85 08FFFFFF mov eax,dword ptr ss:[ebp-0xF8]005D154E . 8B10 mov edx,dword ptr ds:[eax]005D1550 . FF52 14 call dword ptr ds:[edx+0x14] //调用Classes_TStringList_GetCount,返回数据项数005D1553 . 83F8 05 cmp eax,0x5 //必须是5项005D1556 . 0F85 79010000 jnz Protecte.005D16D5005D155C . B8 CC965F00 mov eax,Protecte.005F96CC005D1561 . 8B55 F8 mov edx,dword ptr ss:[ebp-0x8]005D1564 . E8 8351E3FF call <Protecte.@UStrAsg>005D1569 . B8 D0965F00 mov eax,Protecte.005F96D0005D156E . 8B4D EC mov ecx,dword ptr ss:[ebp-0x14]005D1571 . 8B55 F0 mov edx,dword ptr ss:[ebp-0x10]005D1574 . E8 0756E3FF call <Protecte.System_@UStrCat3>005D1579 . B8 D4965F00 mov eax,Protecte.005F96D4005D157E . BA 20215D00 mov edx,<Protecte.aPro_0> ; UNICODE "Pro"005D1583 . E8 6451E3FF call <Protecte.@UStrAsg>005D1588 . 8D8D A4FEFFFF lea ecx,dword ptr ss:[ebp-0x15C]

用wireshark拦截到发到服务器上去的数据:POST /functions/check.php HTTP/1.0Connection: keep-aliveContent-Type: application/x-www-form-urlencodedContent-Length: 90Host: pf.iobit.comAccept: text/html, */*Accept-Encoding: identityUser-Agent: Mozilla/3.0 (compatible; Indy Library)Code=22EAA-B9A86-4AC54-3FAAC&CPU=0003-06C3-BFEB-FBFF-7FFA-FBBF&Disk=J3390084J8V20D&Ver=100继续往下分析:

005D158E . BA 01000000 mov edx,0x1005D1593 . 8B85 08FFFFFF mov eax,dword ptr ss:[ebp-0xF8]005D1599 . 8B18 mov ebx,dword ptr ds:[eax]005D159B . FF53 0C call dword ptr ds:[ebx+0xC] //Classes_TStringList_Get,edx是获取的第几项,这里是第1项005D159E . 8B95 A4FEFFFF mov edx,dword ptr ss:[ebp-0x15C]005D15A4 . B8 DC965F00 mov eax,Protecte.005F96DC005D15A9 . E8 3E51E3FF call <Protecte.@UStrAsg>005D15AE . 8D8D A0FEFFFF lea ecx,dword ptr ss:[ebp-0x160]005D15B4 . BA 02000000 mov edx,0x2005D15B9 . 8B85 08FFFFFF mov eax,dword ptr ss:[ebp-0xF8]005D15BF . 8B18 mov ebx,dword ptr ds:[eax]005D15C1 . FF53 0C call dword ptr ds:[ebx+0xC]//Classes_TStringList_Get,edx是获取的第几项,这里是第2项

上面是存了几项从服务器返回的数据,自己写个小服务器,分析得到服务器返回最终的数据表示的含义是:——————————————–HTTP/1.1 200 OKContent-Type: text/html; charset=UTF-8Date: Fri, 09 Jan 2015 13:57:04 GMTServer: ApacheContent-Length: 2Connection: keep-alive0&expiredate&seat&unknown&lastserverdate—————————————第四个数据我没分析出来是干嘛用的,可以猜想是校验用的.基本分析完全了.我写了一个简单的网络注册机,可以完美注册.(使用前要把pf.iobit.com加入host文件)

注册机用了boost的asio库.代码凌乱,源码实在不好意思放出来.提供一个现成的网络注册机下载吧.系统版本过低可能不兼容.

–推荐给朋友

公众微信号:吾爱破解论坛

或搜微信号:pojie_52

–内容分享

点击右上角“…”标志,分享到朋友圈

–更多精彩内容

请点击右上角“…”标志,点击“查看公众号”,往下拉一点再点击“查看历史消息”即可!

–官方论坛

www.52pojie.cn

如未说明则本站原创,转载请注明出处:NULL » 【Delphi程序逆向技巧】Protected Folder算法分析+网络注册机编写