摸着石头过河,参照以下文章自己手动实践一次:XX XX v12.0.23.0 破解教程x86,x64通用,手动破解,不要补丁,无病毒 Navicat 17 破解教程 Navicat for Sqlite 17.3.2 英文版 一字节替换RSA公钥 记破解Navicat Premium 17.3.4过程
软件下载地址:https://www.navicat.com.cn/download/navicat-premium
找到公钥 根据前人的经验,用 x64dbg 在 libcc.dll 模块中调试搜索汇编特征在“搜索匹配特征”窗口中,使用十六进制搜索模式查找字节序列41 FF D0 90 48 8B D0 48 8B CF
,该字节序列对应以下汇编指令。
1 2 3 4 call r8 nop mov rdx,rax mov rcx,rdi
到这里
1 2 3 4 5 00007FF8C33EE637 | 41:FFD0 | call r8 // 拼接公钥字符串 00007FF8C33EE63A | 90 | nop 00007FF8C33EE63B | 48:8BD0 | mov rdx,rax 00007FF8C33EE63E | 48:8BCF | mov rcx,rdi 00007FF8C33EE641 | FFD3 | call rbx
在libcc+214E63A
处设置断点,也就是call r8
之后的一行,此时在rax
中就是公钥。
1 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889IqdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginva5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOFR0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmtYyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQawIDAQAB
最开始我直接从 x64dbg 中拷贝出公钥,新建了一个不带换行的公钥文件
1 2 3 -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889IqdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginva5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOFR0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmtYyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQawIDAQAB -----END PUBLIC KEY-----
这和我们常见的密钥文件不同,它没有换行,于是有些在线工具不兼容,比如 http://tool.chacuo.net/cryptrsakeyparse 但是有的在线兼容性更强,比如 https://www.oren.net.cn/rsa/info.html 不过无论如何,这种一行 Base64 编码的证书是不标准的。 AI对此的回答:
PEM 格式最初是在 RFC 1421 中定义的,它的全称是“隐私增强邮件”(Privacy-Enhanced Mail)。这个标准被设计用来在电子邮件中安全地传输加密数据。 标准规定:该标准明确规定,Base64 编码后的数据文本,每行长度不应超过 64 个字符。 行业惯例:因此,所有标准的加密工具(如 OpenSSL)在生成 PEM 格式的密钥或证书时,都会严格遵守这个规定,自动在每 64 个字符后添加一个换行符 (\n)。
所以标准的公钥文件内容应该是这样的:
1 2 3 4 5 6 7 8 9 -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889I qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2 WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ awIDAQAB -----END PUBLIC KEY-----
通过前面的在线工具分解出模数(N):
1 C3576A1774A409A00098CCECF3CF48A9D5BD336748761DE31BDC8F7262E7989886A4117813D54748C19EF283C0CB69090E674DB7805C13282FB2C11F8229EF6B9B798E6DF9D940280E8B142644D7190869016305E1F0660693B711E746EB6ACEB2A30182649D032C4248F0512483854744206456D19D4D1FAE3DF87E256681888BB994809886ECF19C621CF769D680F8B54A6FB05722E1026D60D8CD9C653658F1FAAEF0A11A5D4844AAF132DA8B89E96CBDA8D48F2AE0382E8D9B299832F64D111DE1B4512D2F57906C2C937C39AD6324355ADE0EB75DA5C5FD3054847999C18DED70972511CE14749FD60CD759A6D14AF9ADD67AE5EEC5BA723C658EEB906B
指数(e):
构造新的私钥、公钥 下载 RSA 一字节替换工具:https://www.52pojie.cn/thread-2008824-1-1.html https://www.chinapyg.com/thread-158380-1-1.html
计算得到两种结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 替换位置 0 :C3 --> BD new N: BD576A1774A409A00098CCECF3CF48A9D5BD336748761DE31BDC8F7262E7989886A4117813D54748C19EF283C0CB69090E674DB7805C13282FB2C11F8229EF6B9B798E6DF9D940280E8B142644D7190869016305E1F0660693B711E746EB6ACEB2A30182649D032C4248F0512483854744206456D19D4D1FAE3DF87E256681888BB994809886ECF19C621CF769D680F8B54A6FB05722E1026D60D8CD9C653658F1FAAEF0A11A5D4844AAF132DA8B89E96CBDA8D48F2AE0382E8D9B299832F64D111DE1B4512D2F57906C2C937C39AD6324355ADE0EB75DA5C5FD3054847999C18DED70972511CE14749FD60CD759A6D14AF9ADD67AE5EEC5BA723C658EEB906B new P: 11 new Q: B234279D9AF4BDC3C45393B1D668BCDC1475D606DAC9858A749359D511CAE9F8F7310161F48C7F53890E116CF1B062DB58D9B28E96ED3F34E1992E3BC5CD1D9274362BB2CD08B4DA680A6D5131BB62DABD2E7B50D4A605ABD651F2BB8E0ABEE0A820F25C9AEE211A98BD1E6A7CB804F7C7A6041579FD75C376D0E9E023334CBCBFBDB8F1807EFD1FA23E3961548DA68FB9AF782D7F2FE2D51BA671B27513F6EA4D282C2DC4CD84F8B9195B7B280ADC26FCEEBD044A82B4E99539FB727120E7D0101C1FB8C4DF3B9DB51A844E93091BA89A8C91C1EFBBA36ED875D3225E9090B62B39D36113D485B8E63C14C0CAAEBB1F559FB2ABBEF6865FBE89A24177744BB new D: 119EB68C0ED6442D272C5C5E2925120770BE75FB1786F57D4B2986A85AAA05986858A01A5F63C4F32D8A068F0F16B1AF9E8967EE515E03F1E69C8EC5BA391DC36D5A2DD7F00DFC53DC15ABD96B616BD4D4FFD3919A750FFA7DA67CA9AED1C12DD571EC84DB8CD019212111F0D5841C79A373F6D368CC76E86CC71B58DE22246445512D0833FC5A6AD64FF0BC7EEFC2985373D61E298542986FB3756A7FC90991013007AE3ED7535E846ECD6CCFF8C2EBD7F68319F98412FF4592061ABAB4D636EC60DB821975391FCED8605CA48D31FEE9C91C6060A3DCF508D21D9E385B72F390329783F1C201B6D68CFA4055CA11167810E249B90807215065AB756DAA421 校验结果:成功! 替换位置 0 :C3 --> CC new N: CC576A1774A409A00098CCECF3CF48A9D5BD336748761DE31BDC8F7262E7989886A4117813D54748C19EF283C0CB69090E674DB7805C13282FB2C11F8229EF6B9B798E6DF9D940280E8B142644D7190869016305E1F0660693B711E746EB6ACEB2A30182649D032C4248F0512483854744206456D19D4D1FAE3DF87E256681888BB994809886ECF19C621CF769D680F8B54A6FB05722E1026D60D8CD9C653658F1FAAEF0A11A5D4844AAF132DA8B89E96CBDA8D48F2AE0382E8D9B299832F64D111DE1B4512D2F57906C2C937C39AD6324355ADE0EB75DA5C5FD3054847999C18DED70972511CE14749FD60CD759A6D14AF9ADD67AE5EEC5BA723C658EEB906B new P: 35 new Q: 3DB0200714B8C41CFB5999A34E6EE0CDD642C710B070F0DF208FDE058CF3CD767F96F6C879E9714B18A3E89BB2F4F43CB23C2AC84D643FBED944744379686EEB5A6851A39D8A09B52633A57AA5B0078E9D48DF19ED43BE3249930A3C28BAFE6EB3835291A0C9F74C2754CFCB367EA5D1E443BDAFF1FF2F6EFF7824600B4A6ABA20854EA46802173A72D52A8E5040C18036BAB29F7FBD3F18E23A37CA258DA624830339A46A98DD5E4508222C554BEFABE6DD7B6BA3F99F7664FF46FE10F73BDD4D9EC1B8D9B6B284B2D8341933FE175CB8D1509051B9C553DB2A9F7EF2DC41BCD8BB9ABE88C69527FC90DB2F59298E22339D7CEEA2AFAD8424F703BE0E2A21F new D: 6B6659CC9A3A394ED019A650D97C57AE674C29F2978E7AFFAE5C6C4B33A540E1FFAFA10756A1A55AFE9258A7C381903CA07FFA6873BD86F24AA950D62CE932BC55D1EBD935AE1FCAA0B128790941195725E5BA7014536E2F7543287DA6D831A420AC11B473C14DE9EB4C50DED788D64040EFE3B0115E9D81DE0C9920126844BABB65FC8E667AC560408F7B20FCFE51265DA2DCDCAEF49887454FE0D3F45FD2941CF91F816F09A7D7B9CE385901444D09F7CF42531CD0BCDDCF67577D7DA2443ABF2A18D33151512B9188B3E3F14E7FF431DE532A80EF336C14D6F78B795F1AA0A4A2B3FEE878FD34FA186553E123654F8C97B3EC7197F53C6D7316B3ED4B5361 校验结果:成功! 计算成功总数:2 总耗时:0 秒 141 毫秒 线程数: 20 素数集:2~251 区间内的素数 素数个数:54
计算出新的参数后就可以构造出公钥和私钥文件了 创建一个 python 脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 from cryptography.hazmat.primitives.asymmetric import rsafrom cryptography.hazmat.primitives import serializationimport osprivate_key_filename = "private_key.pem" public_key_filename = "public_key.pem" n = int ("BD576A1774A409A00098CCECF3CF48A9D5BD336748761DE31BDC8F7262E7989886A4117813D54748C19EF283C0CB69090E674DB7805C13282FB2C11F8229EF6B9B798E6DF9D940280E8B142644D7190869016305E1F0660693B711E746EB6ACEB2A30182649D032C4248F0512483854744206456D19D4D1FAE3DF87E256681888BB994809886ECF19C621CF769D680F8B54A6FB05722E1026D60D8CD9C653658F1FAAEF0A11A5D4844AAF132DA8B89E96CBDA8D48F2AE0382E8D9B299832F64D111DE1B4512D2F57906C2C937C39AD6324355ADE0EB75DA5C5FD3054847999C18DED70972511CE14749FD60CD759A6D14AF9ADD67AE5EEC5BA723C658EEB906B" , 16 ) e = int ("10001" , 16 ) d = int ("119EB68C0ED6442D272C5C5E2925120770BE75FB1786F57D4B2986A85AAA05986858A01A5F63C4F32D8A068F0F16B1AF9E8967EE515E03F1E69C8EC5BA391DC36D5A2DD7F00DFC53DC15ABD96B616BD4D4FFD3919A750FFA7DA67CA9AED1C12DD571EC84DB8CD019212111F0D5841C79A373F6D368CC76E86CC71B58DE22246445512D0833FC5A6AD64FF0BC7EEFC2985373D61E298542986FB3756A7FC90991013007AE3ED7535E846ECD6CCFF8C2EBD7F68319F98412FF4592061ABAB4D636EC60DB821975391FCED8605CA48D31FEE9C91C6060A3DCF508D21D9E385B72F390329783F1C201B6D68CFA4055CA11167810E249B90807215065AB756DAA421" , 16 ) p = int ("11" , 16 ) q = int ("B234279D9AF4BDC3C45393B1D668BCDC1475D606DAC9858A749359D511CAE9F8F7310161F48C7F53890E116CF1B062DB58D9B28E96ED3F34E1992E3BC5CD1D9274362BB2CD08B4DA680A6D5131BB62DABD2E7B50D4A605ABD651F2BB8E0ABEE0A820F25C9AEE211A98BD1E6A7CB804F7C7A6041579FD75C376D0E9E023334CBCBFBDB8F1807EFD1FA23E3961548DA68FB9AF782D7F2FE2D51BA671B27513F6EA4D282C2DC4CD84F8B9195B7B280ADC26FCEEBD044A82B4E99539FB727120E7D0101C1FB8C4DF3B9DB51A844E93091BA89A8C91C1EFBBA36ED875D3225E9090B62B39D36113D485B8E63C14C0CAAEBB1F559FB2ABBEF6865FBE89A24177744BB" , 16 ) print ("正在验证 RSA 参数..." )if p * q != n: raise ValueError(f"p * q ≠ n\np*q = {p * q} \nn = {n} " ) phi = (p - 1 ) * (q - 1 ) if (e * d) % phi != 1 : raise ValueError(f"e * d ≢ 1 mod φ(n)\n(e*d) % phi = {(e*d) % phi} " ) print ("正在计算 CRT 参数..." )dmp1 = d % (p - 1 ) dmq1 = d % (q - 1 ) try : iqmp = pow (q, -1 , p) except ValueError: raise ValueError("q has no inverse mod p (gcd(q,p) ≠ 1?)" ) try : print ("正在构造密钥对象..." ) private_numbers = rsa.RSAPrivateNumbers( p=p, q=q, d=d, dmp1=dmp1, dmq1=dmq1, iqmp=iqmp, public_numbers=rsa.RSAPublicNumbers(e=e, n=n) ) private_key = private_numbers.private_key() print ("已成功构造私钥!" ) pem_private = private_key.private_bytes( encoding=serialization.Encoding.PEM, format =serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption() ) pem_public = private_key.public_key().public_bytes( encoding=serialization.Encoding.PEM, format =serialization.PublicFormat.SubjectPublicKeyInfo ) with open (private_key_filename, "wb" ) as f: f.write(pem_private) print (f"\n私钥已成功保存到文件:{private_key_filename} " ) with open (public_key_filename, "wb" ) as f: f.write(pem_public) print (f"公钥已成功保存到文件:{public_key_filename} " ) except Exception as ex: print ("创建密钥失败:" , str (ex))
新的公钥文件:
1 2 3 4 5 6 7 8 9 -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvVdqF3SkCaAAmMzs889I qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2 WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ awIDAQAB -----END PUBLIC KEY-----
私钥文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 -----BEGIN PRIVATE KEY----- MIIEPAIBADANBgkqhkiG9w0BAQEFAASCBCYwggQiAgEAAoIBAQC9V2oXdKQJoACY zOzzz0ip1b0zZ0h2HeMb3I9yYueYmIakEXgT1UdIwZ7yg8DLaQkOZ023gFwTKC+y wR+CKe9rm3mObfnZQCgOixQmRNcZCGkBYwXh8GYGk7cR50bras6yowGCZJ0DLEJI 8FEkg4VHRCBkVtGdTR+uPfh+JWaBiIu5lICYhuzxnGIc92nWgPi1Sm+wVyLhAm1g 2M2cZTZY8fqu8KEaXUhEqvEy2ouJ6Wy9qNSPKuA4Lo2bKZgy9k0RHeG0US0vV5Bs LJN8Oa1jJDVa3g63XaXF/TBUhHmZwY3tcJclEc4UdJ/WDNdZptFK+a3WeuXuxbpy PGWO65BrAgMBAAECggEAARnraMDtZELScsXF4pJRIHcL51+xeG9X1LKYaoWqoFmG hYoBpfY8TzLYoGjw8Wsa+eiWfuUV4D8eacjsW6OR3DbVot1/AN/FPcFavZa2Fr1N T/05GadQ/6faZ8qa7RwS3VceyE24zQGSEhEfDVhBx5o3P202jMduhsxxtY3iIkZE VRLQgz/Fpq1k/wvH7vwphTc9YeKYVCmG+zdWp/yQmRATAHrj7XU16Ebs1sz/jC69 f2gxn5hBL/RZIGGrq01jbsYNuCGXU5H87YYFykjTH+6ckcYGCj3PUI0h2eOFty85 Ayl4PxwgG21oz6QFXKERZ4EOJJuQgHIVBlq3VtqkIQIBEQKCAQALI0J52a9L3DxF OTsdZovNwUddYG2smFinSTWdURyun49zEBYfSMf1OJDhFs8bBi21jZso6W7T804Z kuO8XNHZJ0NiuyzQi02mgKbVExu2LavS57UNSmBavWUfK7jgq+4Kgg8lya7iEamL 0eany4BPfHpgQVef11w3bQ6eAjM0y8v7248YB+/R+iPjlhVI2mj7mveC1/L+LVG6 ZxsnUT9upNKCwtxM2E+LkZW3soCtwm/O69BEqCtOmVOftycSDn0BAcH7jE3zudtR qETpMJG6iajJHB77ujbth10yJekJC2KznTYRPUhbjmPBTAyq67H1Wfsqu+9oZfvo miQXd0S7AgEBAoIBAAEZ62jA7WRC0nLFxeKSUSB3C+dfsXhvV9SymGqFqqBZhoWK AaX2PE8y2KBo8PFrGvnoln7lFeA/HmnI7Fujkdw21aLdfwDfxT3BWr2Wtha9TU/9 ORmnUP+n2mfKmu0cEt1XHshNuM0BkhIRHw1YQceaNz9tNozHbobMcbWN4iJGRFUS 0IM/xaatZP8Lx+78KYU3PWHimFQphvs3Vqf8kJkQEwB64+11NehG7NbM/4wuvX9o MZ+YQS/0WSBhq6tNY27GDbghl1OR/O2GBcpI0x/unJHGBgo9z1CNIdnjhbcvOQMp eD8cIBttaM+kBVyhEWeBDiSbkIByFQZat1bapCECAQc= -----END PRIVATE KEY-----
对比新旧公钥文件可以发现替换了两个字节:
1 2 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA【w1】dqF3SkCaAAmMzs889I MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA【vV】dqF3SkCaAAmMzs889I
即w1
改为vV
。
查找公钥修改点 回到libcc+214E637
处,进入函数一条条分析,很容易可以看出这个函数在拼接公钥字符串,我们一步步走直到出现w1
,对应HEX为0x7731
在libcc+2156FD4
处就是了!
1 2 3 4 5 6 7 8 9 10 00007FF8C3076FD4 | 66:C785 20010000 7731 | mov word ptr ss:[rbp+120],3177 | 字符"w1" 00007FF8C3076FDD | C685 22010000 64 | mov byte ptr ss:[rbp+122],64 | 00007FF8C3076FE4 | 48:8D85 20010000 | lea rax,qword ptr ss:[rbp+120] | 00007FF8C3076FEB | 48:8985 60050000 | mov qword ptr ss:[rbp+560],rax | 00007FF8C3076FF2 | 48:8D85 23010000 | lea rax,qword ptr ss:[rbp+123] | 00007FF8C3076FF9 | 48:8985 68050000 | mov qword ptr ss:[rbp+568],rax | 00007FF8C3077000 | 48:8D95 60050000 | lea rdx,qword ptr ss:[rbp+560] | 00007FF8C3077007 | 49:8BCF | mov rcx,r15 | 00007FF8C307700A | E8 B1F7FFFF | call libcc.7FF8C30767C0 | 00007FF8C307700F | 48:8D15 6EFDFA03 | lea rdx,qword ptr ds:[7FF8C7026D84] |
将操作数3177
替换为5676
即可。
1 mov word ptr ss:[rbp+120],5676
但是修改了字符后,函数返回的公钥完全是错误的:
1 2 3 4 5 6 7 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvVdqF3SkCaAAmMzs889I qdW9M2dId3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginva t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOFR0 QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdpVoD4tUpvsFci4QJtYNjNnGU2WP H6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmtYy Q1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQaw IDAQAB
陷阱一:
1 2 3 4 00007FF8C307724D | 0FB610 | movzx edx,byte ptr ds:[rax] | rax:"1dqF3SkCaAAmMzs889IqdW9M2dId" 00007FF8C3077250 | 80C2 37 | add dl,37 00007FF8C3077253 | 49:8BCF | mov rcx,r15 00007FF8C3077256 | E8 054C52FF | call libcc.7FF8C259BE60
这里做了一个加法运算,修改了公钥后这里的结果就不一样了。只需要让其在add
操作后保持一致就行了。 正常时:31(1) + 37 修改后:56(V) + 37
37 - (56 - 31) = 12,所以这里改为:
陷阱二:
1 2 3 4 5 6 7 8 9 10 11 12 13 00007FF8C3078545 | 0FB610 | movzx edx,byte ptr ds:[rax] // 取了修改处的"V" 00007FF8C3078548 | 49:8BCF | mov rcx,r15 00007FF8C307854B | E8 103952FF | call libcc.7FF8C259BE60 -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw【1】dqF3SkCaAAmMzs889I qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp【1】oD4tUpvsFci4QJtYNjNnGU2 WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ awIDAQAB -----END PUBLIC KEY-----
它从前面修改处取字符赋值给后面的字符,原来是1
时就对了,修改后导致第4行括号那里变成了V
。。。看得出来作者也是用1字节替换RSA
工具计算过的。。。
1 2 3 4 5 6 movzx edx,byte ptr ds:[rax] 改为 push 0x31 pop edx
测试可行。 根据 AI 分析,7FF8C259BE60
就是追加字符的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 _QWORD *__fastcall sub_18167BE60(_QWORD *a1, __int64 a2) { unsigned __int64 v2; // r8 unsigned __int64 v4; // r9 _QWORD *v5; // rax v2 = a1[2]; v4 = a1[3]; if ( v2 >= v4 ) { sub_181683AE0(a1, a2, v2, (unsigned __int8)a2); return a1; } else { v5 = a1; a1[2] = v2 + 1; if ( v4 > 0xF ) v5 = (_QWORD *)*a1; *((_BYTE *)v5 + v2) = a2; *((_BYTE *)v5 + v2 + 1) = 0; return a1; } }
制作一个.1377
补丁文件
1 2 3 4 5 6 7 >libcc.dll 0000000002156FDB:77->76 0000000002156FDC:31->56 0000000002157252:37->12 0000000002158545:0F->6A 0000000002158546:B6->31 0000000002158547:10->5A
至此,公钥就替换成功了。
计算序列号 这个软件在离线激活前必须先输入正确的序列号,而序列号是在本地计算出来的。 我们在输入序列号时会实时的检测输入内容是否正确,正确后才会启用“激活”按钮 为了找到事件发生的位置,我用GetWindowTextA
和GetWindowTextW
都没有断下来,于是我全内存搜索输入的序列号,找到了
分析发现可以在WideCharToMultiByte
上设置断点,返回几层后来到这里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 000000000168FE8F | E8 0CBB33FF | call navicat.9CB9A0 | Unicode转ANSI 000000000168FE94 | C685 E7000000 00 | mov byte ptr ss:[rbp+E7],0 | 000000000168FE9B | C785 E0000000 00000000 | mov dword ptr ss:[rbp+E0],0 | 000000000168FEA5 | 48:8B9D F0000000 | mov rbx,qword ptr ss:[rbp+F0] | [rbp+F0]:"NAVPLTBQ2X7G7BNS" 000000000168FEAC | 48:85DB | test rbx,rbx | rbx:"NAVPLTBQ2X7G7BNS" 000000000168FEAF | 74 09 | je navicat.168FEBA | 000000000168FEB1 | 8B43 FC | mov eax,dword ptr ds:[rbx-4] | 000000000168FEB4 | 8985 E0000000 | mov dword ptr ss:[rbp+E0],eax | 000000000168FEBA | 83BD E0000000 10 | cmp dword ptr ss:[rbp+E0],10 | 序列号长度必须是16位 000000000168FEC1 | 75 33 | jne navicat.168FEF6 | 000000000168FEC3 | 48:8B8D F0000000 | mov rcx,qword ptr ss:[rbp+F0] | [rbp+F0]:"NAVPLTBQ2X7G7BNS" 000000000168FECA | E8 B12AE3FE | call navicat.4C2980 | 000000000168FECF | 48:8B8D 20010000 | mov rcx,qword ptr ss:[rbp+120] | [rbp+120]:&L"圐n" 000000000168FED6 | 48:8B89 580B0000 | mov rcx,qword ptr ds:[rcx+B58] | 000000000168FEDD | 48:89C2 | mov rdx,rax | 000000000168FEE0 | 4C:8D85 E8000000 | lea r8,qword ptr ss:[rbp+E8] | 000000000168FEE7 | 48:8B05 BADF3002 | mov rax,qword ptr ds:[399DEA8] | 验证序列号的函数 000000000168FEEE | FF10 | call qword ptr ds:[rax] | 验证序列号 000000000168FEF0 | 8885 E7000000 | mov byte ptr ss:[rbp+E7],al | 成功时al=1 000000000168FEF6 | 80BD E7000000 00 | cmp byte ptr ss:[rbp+E7],0 | 000000000168FEFD | 0F84 1C020000 | je navicat.169011F |
跟进分析
1 2 3 4 5 00007FFBC8E2E8F8 | 48:8B01 | mov rax,qword ptr ds:[rcx] | 00007FFBC8E2E8FB | 4C:8D4424 38 | lea r8,qword ptr ss:[rsp+38] | [rsp+38]:L"談椃" 00007FFBC8E2E900 | 48:8D5424 60 | lea rdx,qword ptr ss:[rsp+60] | [rsp+60]:"NAVPLTBQ2X7G7BNS" 00007FFBC8E2E905 | FF50 10 | call qword ptr ds:[rax+10] | 验证序列号 00007FFBC8E2E908 | 0FB6D8 | movzx ebx,al | ebx:"NAVPLTBQ2X7G7BNS"
再跟进后的伪代码,用 IDA 查看
1 2 3 4 5 // Hidden C++ exception states: #wind=1 char __fastcall sub_182149DE0(__int64 a1, __int64 a2, _QWORD *a3) { // 省略... }
丢给 AI 分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 **这是一个注册中心的关键检查和保存函数**,从函数名和代码内容可以看出这是 `CSRegistrationCenter::checkAndSaveKey` 函数。 ### 主要功能: 1. **输入验证**: - 检查输入字符串的长度是否为16字节(`*(_QWORD *)(a2 + 16) == 16`) - 如果不是16字节,直接返回失败(0) 2. **密钥验证**: - 调用虚函数进行密钥验证(`*(_QWORD *)a1 + 48LL`) - 如果验证失败,进入错误处理流程 3. **成功处理流程**: - 获取当前时间戳 - 调用UI相关函数更新状态 - 检查验证结果的状态码(`sub_18214EB60`) - 根据状态码决定是否调用特定的回调函数 4. **时间管理**: - 获取或设置时间戳 - 如果提供了输出参数(`a3`),计算30天后的时间戳(`v8 + 2592000`) 5. **日志记录**: - 记录函数调用信息到日志系统 - 包含文件名:`"D:\\dev\\navicatlibs\\libcc\\src\\cs_registration_center.cpp"` - 包含函数签名:`"bool __cdecl CSRegistrationCenter::checkAndSaveKey(...)"` 6. **状态保存**: - 将验证结果保存到对象的状态字段中 - 调用清理函数 ### 关键特征: - **安全保护**:使用栈保护cookie防止缓冲区溢出 - **虚函数调用**:通过虚函数表调用不同的验证方法 - **错误处理**:完整的错误处理和资源清理 - **时间计算**:2592000秒 = 30天的计算 - **日志系统**:完整的调试日志记录 ### 返回值: - 成功时返回 1 - 失败时返回 0 ### 总结: 这是一个**软件注册验证函数**,用于检查用户提供的注册密钥是否有效,并保存相关的验证状态。函数包含完整的输入验证、密钥验证、状态管理和错误处理机制,是软件授权系统的核心组件。
根据 AI 给出的提示,关键在a1 + 48LL
这个验证函数
1 2 3 4 5 6 7 8 9 10 11 12 00007FFBC9879E0A | 0F85 C5010000 | jne libcc.7FFBC9879FD5 | 序列号必须是16位 00007FFBC9879E10 | 0F57C0 | xorps xmm0,xmm0 | 00007FFBC9879E13 | 0F1145 C8 | movups xmmword ptr ss:[rbp-38],xmm0 | 00007FFBC9879E17 | 66:0F6F0D D1952204 | movdqa xmm1,xmmword ptr ds:[7FFBCDAA33F0] | 00007FFBC9879E1F | F3:0F7F4D D8 | movdqu xmmword ptr ss:[rbp-28],xmm1 | 00007FFBC9879E24 | C645 C8 00 | mov byte ptr ss:[rbp-38],0 | 00007FFBC9879E28 | 0F1145 E8 | movups xmmword ptr ss:[rbp-18],xmm0 | 00007FFBC9879E2C | 48:8B01 | mov rax,qword ptr ds:[rcx] | 00007FFBC9879E2F | 4C:8D45 C8 | lea r8,qword ptr ss:[rbp-38] | 00007FFBC9879E33 | FF50 30 | call qword ptr ds:[rax+30] | 验证序列号 00007FFBC9879E36 | 85C0 | test eax,eax | 00007FFBC9879E38 | 0F85 59010000 | jne libcc.7FFBC9879F97 |
伪代码
1 2 3 4 5 // Hidden C++ exception states: #wind=5 __int64 __fastcall sub_182149FF0(__int64 a1, __int64 a2, __int64 a3) { // 省略... }
AI 给出的总结:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 **这是一个复杂的密钥验证和解析函数**,主要用于验证和解析某种格式的注册密钥。 ### 主要功能: 1. **密钥预处理**: - 将输入字符串中的字符进行替换:'8' → 'I','9' → 'O' - 检查密钥格式是否以 "h*" 开头(h=0x68, *=0x2A) - 如果是正确格式,移除前两个字符 2. **密钥解码**: - 调用解码函数处理处理后的密钥 - 提取8字节的密钥数据 3. **版本和类型检查**: - 检查密钥的第6字节的高4位,确保版本号 >= 17 (0x11) - 根据密钥的不同字节进行复杂的验证逻辑 4. **许可证类型验证**: - 根据密钥的第5字节('e', 'f', 'g')确定许可证类型 - 调用不同的验证函数检查许可证状态 5. **产品类型映射**: - 根据密钥的第7字节(高字节)映射到不同的产品类型: - 0xFF: 默认类型 - 0xFE: 类型9 - 0xFD: 类型6或8 - 0xFC: 类型5或7 - 0xFB: 类型4 - 0xFA: 类型3 - 其他: 类型1 6. **全局数据表查找**: - 在两个全局数据表中查找匹配的条目 - 使用线程局部存储进行线程安全的初始化 7. **结果输出**: - 将验证结果保存到输出结构体中 - 包含产品类型、许可证状态、版本信息等 ### 关键特征: - **字符替换**:将容易混淆的字符进行标准化 - **格式验证**:严格的密钥格式检查 - **多级验证**:版本、类型、许可证状态的多重验证 - **线程安全**:使用线程局部存储和初始化锁 - **错误处理**:完整的错误码返回机制 ### 返回值: - 0: 成功 - 1: 格式错误 - 2: 版本过低 - 3: 未找到匹配项 - 4: 验证失败 ### 总结: 这是一个**软件注册密钥验证和解析函数**,负责验证用户输入的注册密钥的有效性,解析其中的产品类型、许可证信息等,并将结果保存到输出结构体中。函数包含完整的格式检查、版本验证、许可证状态检查等机制。
关键在解码函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 00007FFBC987A11F | 48:8D55 40 | lea rdx,qword ptr ss:[rbp+40] | 序列号 00007FFBC987A123 | 48:8D4D F0 | lea rcx,qword ptr ss:[rbp-10] | 准备输出缓冲区 00007FFBC987A127 | E8 241F0000 | call libcc.7FFBC987C050 | Base32 解码 00007FFBC987A12C | 90 | nop | 00007FFBC987A12D | 48:8B55 08 | mov rdx,qword ptr ss:[rbp+8] | 00007FFBC987A131 | 4C:8B45 F0 | mov r8,qword ptr ss:[rbp-10] | 00007FFBC987A135 | 48:837D 00 0A | cmp qword ptr ss:[rbp],A | 00007FFBC987A13A | 0F85 96020000 | jne libcc.7FFBC987A3D6 | 00007FFBC987A140 | 48:8D45 F0 | lea rax,qword ptr ss:[rbp-10] | 00007FFBC987A144 | 48:83FA 0F | cmp rdx,F | 00007FFBC987A148 | 49:0F47C0 | cmova rax,r8 | 00007FFBC987A14C | 8038 68 | cmp byte ptr ds:[rax],68 | 第1个字符是否等于'h' 00007FFBC987A14F | 0F85 81020000 | jne libcc.7FFBC987A3D6 | 00007FFBC987A155 | 48:8D45 F0 | lea rax,qword ptr ss:[rbp-10] | 00007FFBC987A159 | 48:83FA 0F | cmp rdx,F | 00007FFBC987A15D | 49:0F47C0 | cmova rax,r8 | 00007FFBC987A161 | 8078 01 2A | cmp byte ptr ds:[rax+1],2A | 第2个字符是否等于'*' 00007FFBC987A165 | 0F85 6B020000 | jne libcc.7FFBC987A3D6 | 00007FFBC987A16B | 48:8D4D F0 | lea rcx,qword ptr ss:[rbp-10] | 00007FFBC987A16F | 48:83FA 0F | cmp rdx,F | 00007FFBC987A173 | 49:0F47C8 | cmova rcx,r8 | 00007FFBC987A177 | 48:8D51 02 | lea rdx,qword ptr ds:[rcx+2] | 00007FFBC987A17B | 41:B8 09000000 | mov r8d,9 | 00007FFBC987A181 | E8 5C1BA601 | call <JMP.&memcpy> | 00007FFBC987A186 | B9 08000000 | mov ecx,8 | 00007FFBC987A18B | 48:894D 00 | mov qword ptr ss:[rbp],rcx | 00007FFBC987A18F | 48:8B03 | mov rax,qword ptr ds:[rbx] | 00007FFBC987A192 | 894C24 20 | mov dword ptr ss:[rsp+20],ecx | 00007FFBC987A196 | 4C:8D0D 037F9C04 | lea r9,qword ptr ds:[7FFBCE2420A0] | DES Key 00007FFBC987A19D | 4C:8D45 F0 | lea r8,qword ptr ss:[rbp-10] | 00007FFBC987A1A1 | 48:8D55 10 | lea rdx,qword ptr ss:[rbp+10] | 去除头2字节的序列号 Hex 00007FFBC987A1A5 | 48:8BCB | mov rcx,rbx | 00007FFBC987A1A8 | FF50 60 | call qword ptr ds:[rax+60] | DES 解密 00007FFBC987A1AB | 90 | nop | RAX = 序列号 Hex
DES密钥是硬编码:E9 7F B0 60 77 45 90 AE
。
至于序列号的一些含义可以参考其他文章,这里不展开了。 生成序列号的流程是这样:
根据各个位的含义,手动构造一个 8 字节的 Hex。
用 DES 对上面的 Hex 进行加密,DES 密钥是E97FB060774590AE
。
得到解密后的 Hex 后在前面追加固定 2 个字节0x682A
。
将上面得到的 Hex 用 Base32 编码,就得到了一个序列号。
解密请求码 断网,输入有效序列号开始激活,出现离线激活对话框 去 http://tool.chacuo.net/cryptrsaprikey 用自己的私钥来解密请求码,得到一个类似这样的 JSON 内容:
1 { "K" : "NAVPLTBQ2X7G7BNS" , "DI" : "0A4897AEF970D541827A" , "P" : "WIN" }
根据前人的经验,只需要在 JSON 中加入N
、O
、T
即可
1 { "K" : "NAVPLTBQ2X7G7BNS" , "DI" : "0A4897AEF970D541827A" , "P" : "WIN" , "N" : "admin" , "O" : "google" , "T" : 1516280990 }
最后用私钥文件加密这个 JSON 就能得到最终的激活码了。