这是一个比较简单的例子,可以用来学习一下如何替换RSA公钥。
相关下载链接:
macOS https://download.sublimetext.com/sublime_text_build_4126_mac.zip
Windows https://download.sublimetext.com/sublime_text_build_4126_x64.zip
Linux x86-64 https://download.sublimetext.com/sublime_text_build_4126_x64.tar.xz
ARM64 https://download.sublimetext.com/sublime_text_build_4126_arm64.tar.xz
官网:https://www.sublimetext.com/download
目标文件:sublime_text.exe (Windows)
调试分析(构造许可、替换RSA公钥):
在载入调试器后,搜索"Unlimited"字符串,定位到调用代码段。
图(1)
如图(1)所示,这里的代码段将会为你拼接一个伪许可,其格式如下:
Name
Unlimited User License
EA7E-1000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
到这里我们就有了许可的基本格式了,然后继续Step over。在图(1)的代码段下方会进入一个关键调用(所有验证在这里进行)。 如图(2)所示:
图(2)
在图(2)中被选中的调用处,我们Step into,如图(3)所示:
图(3)
你可以通过搜索字符串"E52D"快速跳转到此调用,并向上查找到 图(3)所示的代码段。图中被选中的这段代码(文件不同出现的位置都可能不同,我们进入此调用后,通过Step over,应该很容易找到它)做了一件事情,那就是将一个 数组的每一个字节去异或0x7F(文件不同异或值都可能不同),同时将新的数据放到另一个数 组中。
原始数据(偏移:xxxxx8890):
4FFEE24F72797655F937F988727E7E7E7A7F7CFEF47F4FFEF87DFEFE7FA704DD3A1D88BAAE3573846DC60B73662314A3011211B654D371CDE2269EA6D118F67354F7BCD4A3D08102354CA3BEC0C12C655A639073ED4079C106CD4DFA26D38196F9AA9E213268199529BB9E7928850BA47608BC840AFDC8F3AB04CDB886CD2DCBD639426A89D111965C022BBA37648C9FCFE65F6674B0CC61249A76BC447D7E6E
异或0x7F(局部变量:堆栈):
30819D300D06092A864886F70D010101050003818B0030818702818100D87BA24562F7C5D14A0CFB12B9740C195C6BDC7E6D6EC92BAC0EB29D59E1D9AE67890C2B88C3ABDCAFFE7D4A33DCC1BFBE531A251CEF0C923F06BE79B2328559ACFEE986D5E15E4D1766EA56C4E10657FA74DB0977C3FB7582B78CD47BB2C7F9B252B4A9463D15F6AE6EE9237D54C5481BF3E0B09920190BCFB31E5BE509C33B020111
为了获取异或结果,我们可以通过继续Step over,且在图(4)所示的代码段中被选中的位置停下,通过寄存器rsi的值,来定位它的内存位置。
图(4)
现在我们一起来观察一下异或结果,通过观察我们发现,这不是DER格式的RSA Public Key? 有点小兴奋,不过我们还是继续Step over,直到到达图(5)所示的代码段中被选中的位置停下。
图(5)
在图(5)中被选中的调用处,我们Step into,如图(6)所示:
图(6)
在图(6)中被选中的调用主要用于许可格式的验证(在这里我们暂时不去详细分析它)。我们Step into后继续Step over,会发现有一小段代码,被用来检查许可内容的行数。如果总行数(非空行)乘上0x20后的结果既不等于0x1A0也不等于0x160,则说明许可内容的行数错误。也就是说我们许可的总行数(非空行)必须是13行或者11行,而我们的伪许可有12行,在这里我们删掉一行0(其它信息看似是必须的),我们就得到了一个新的伪许可。
Name
Unlimited User License
EA7E-1000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
接下来我们让程序彻底运行起来,直到我们可以通过help菜单输入许可为止。
图(7)
在许可输入窗口中输入我们新的伪许可(如图(7)所示),然后点击使用许可按钮。程序将在图(3)的位置被断下。我们让程序继续运行,直到再次到达图(6)中被选中的调用处,Setp over后发现许可格式顺利通过(通过该调用下方跳转来判定)。
图(8)
我们继续Step over,到达图(8)中被选中的调用处,此处主要用于完成签名验证(在这里我们暂时不去详细分析它) 。我们Step into,跟踪分析后发现在内部的子调用中的注解栏中出现了和图(9)类似的信息。
图(9)
这,... 图(9)给我们展示了非常多的信息了,首先它使用了一个第三方库,库名叫tomcrypt,同时也知道了这个子调用的函数原型来自rsa_verify_hash.c这个文件(其它子调用类似)。我们直接bing.com找到这个库(https://www.libtom.net ),下载源代码,解压到桌面,找到rsa_verify_hash.c,同时打开源代码文件夹中的pdf,得知图(9)这个子调用的函数原型对应的是rsa_verify_hash这个函数,且功能为RSA签名验证。
经过追踪分析得到了结论:我们新的伪许可中的0字串在转换到16进制后作为签名传递给了对应图(9)的子调用。我们新的伪许可中的非0字串被格式化为"Name\n Unlimited User License\n EA7E-1000 ",并将它的SHA1结果传递给了对应图(9)的子调用。除此外RSA签名padding使用的是PKCS #1 V1.5格式(被签名内容不够时追加0xFF),且通过公钥信息可以知道我们的模为1024位(RSA-1024)。
根据追踪分析得到的结论,我们找到了逆向计算签名的过程: 首先将" Name\n Unlimited User License\n EA7E-1000 "原始信息进行 SHA1处理,然后将处理结果进行DER编码,编码完成后使用 PKCS #1 V1.5 签名padding(被签名内容不够时追加0xFF ),最后使用幂模运算对padding后的数据进行加密,加密结果即是我们最终的签名 。
至此,我们已然知道我们的许可必须由以下内容构成:
用户名 + 许可类型 + EA7E-1000 + 签名(1024bit)
现在我们终止调试,因为接下来我们需要创建一个新密钥,且使用新密钥的公钥来替换原始公钥,使用新密钥的私钥来进行签名。 这里我们直接使用tomcrypt密码库来完成(使用简单,且容易理解)。 tomcrypt的RSA功能需要依赖math库,有两个可供选择:tommath、tomfastmath(在这里我们选择tommath)。为了使用一个固定的RSA密钥,所以我们不使用tomcrypt的rsa_make_key。而直接使用openssl来产生一个固定的RSA密钥。
// generate a private key.
> openssl genrsa -3 -out rsa_prvkey.pem 1024
// convert to traditional DER format private key.
> openssl rsa -in rsa_prvkey.pem -traditional -out rsa_prvkey.der -outform DER
-traditional:旧版的openssl是不需要这个选项的,新版openssl需要加上。
在产生rsa_prvkey.der文件后,我们可以在程序中直接读取该文件内容,也可以把该文件内容以字节数组的方式添加到代码中(此处我们选择第二种方式)。
const unsigned char prvkey[] = { 内容省略 };
const char *info = " yystarsky\n 256 User License \nEA7E-1000 " ;
int main(void) {
unsigned char pubkey[160], sign[128];
unsigned char hash[20];
unsigned long lh, lp, ls, idx;
rsa_key key;
int err, hash_idx;
/* register a math library */
ltc_mp = ltm_desc;
if (register_hash(&sha1_desc) == -1) {
printf("Error registering sha1");
return EXIT_FAILURE;
}
hash_idx = find_hash("sha1");
err = rsa_import(prvkey, sizeof(prvkey), &key);
if (err != CRYPT_OK) {
printf("hash_memory %s", error_to_string(err));
goto __err;
}
lh = 20;
err = hash_memory(hash_idx, (const unsigned char*)info, strlen(info), hash, &lh);
if (err != CRYPT_OK) {
printf("hash_memory %s", error_to_string(err));
goto __err;
}
ls = 128;
err = rsa_sign_hash_ex(hash, lh, sign, &ls, LTC_PKCS_1_V1_5, NULL, 0, hash_idx, 0, &key);
if (err != CRYPT_OK) {
printf("rsa_sign_hash_ex %s", error_to_string(err));
goto __err;
}
lp= 160;
err = rsa_export(pubkey, &lp, PK_STD, &key);
if (err != CRYPT_OK) {
printf("rsa_export %s", error_to_string(err));
goto __err;
}
for (idx = 0; idx < lp; idx++) {
pubkey[idx] ^= 0x7F;
}
// put your code for outputting sign[128] and pubkey[160] on here!
__err:
return 0;
}
我们可以通过以上简短的代码来计算签名,以及计算公钥异或0x7F的结果。(注意:附件中相关实例的key值不同于文中所引用的key值。)
新RSA公钥(DER编码):
30819D300D06092A864886F70D010101050003818B0030818702818100E15C30122A18B7DDFD4E9566CA1A21E9AAE6BAF974E5103EB5BE32522866E013A2B0C726C8167066D610E195F54386537F89B64FED64995D319CE7EA900DF6F8E713BCB2A275007D9875497FA2615A062F677FD98CD597F10157C4955681EFDA92155F0A7F754D98CEB3218FEEA3D6844F7080ABCC35FFE29D7894B19F75B497020103
异或0x7F后:
4ffee24f72797655f937f988727e7e7e7a7f7cfef47f4ffef87dfefe7f9e234f6d5567c8a28231ea19b5655e96d599c5860b9a6f41cac14d2d57199f6cddcfb859b7690f19a96f9eea8a3cf92c00f6c930921be6224ee39895ef728987986cc3cddd0a7f02e70a3600dd1e2579501800a6f3aae88e7e28bbea29fe90a5ed6a2075000a32e7b1cc5ef091dca9fb300fffd4b34a809de207ebcee00acbe87d7e7c
使用我们最喜欢的二进制编辑工具打开可执行文件(sublime_text.exe),根据以下内容完成搜索和替换操作。
搜索值:
4FFEE24F72797655F937F988727E7E7E7A7F7CFEF47F4FFEF87DFEFE7FA704DD3A1D88BAAE3573846DC60B73662314A3011211B654D371CDE2269EA6D118F67354F7BCD4A3D08102354CA3BEC0C12C655A639073ED4079C106CD4DFA26D38196F9AA9E213268199529BB9E7928850BA47608BC840AFDC8F3AB04CDB886CD2DCBD639426A89D111965C022BBA37648C9FCFE65F6674B0CC61249A76BC447D7E6E
替换值:
4ffee24f72797655f937f988727e7e7e7a7f7cfef47f4ffef87dfefe7f9e234f6d5567c8a28231ea19b5655e96d599c5860b9a6f41cac14d2d57199f6cddcfb859b7690f19a96f9eea8a3cf92c00f6c930921be6224ee39895ef728987986cc3cddd0a7f02e70a3600dd1e2579501800a6f3aae88e7e28bbea29fe90a5ed6a2075000a32e7b1cc5ef091dca9fb300fffd4b34a809de207ebcee00acbe87d7e7c
原始信息:"yystarsky\n 256 User License \nEA7E-1000 "
签名:
a0615d78d6c882c6ab7e78f1c7a8686f
1ccc77c15d0397db164d6419e0b47d49
1ce702bff67cadb25b384ca41600c09b
e19127dced7aac602082bc5d1a44ba0d
5d0dc02d7b4204a407ea069fcc4ac424
30b92e875554ee9251b0daf3f79d13a7
1e0e1e523257fb8e9322e571e90eab0d
cd463251d169420e70d35aa4fec1817b
则我们的许可文件内容为:
yystarsky
256 User License
EA7E-1000
a0615d78d6c882c6ab7e78f1c7a8686f
1ccc77c15d0397db164d6419e0b47d49
1ce702bff67cadb25b384ca41600c09b
e19127dced7aac602082bc5d1a44ba0d
5d0dc02d7b4204a407ea069fcc4ac424
30b92e875554ee9251b0daf3f79d13a7
1e0e1e523257fb8e9322e571e90eab0d
cd463251d169420e70d35aa4fec1817b
此时,我们将替换公钥后的程序载入调试器,且让程序彻底运行起来,直到我们可以通过help菜单输入许可为止。
图(10)
在许可输入窗口中输入我们的自签名许可(如图(10)所示) ,然后点击使用许可按钮。程序将在图(3)的位置被断下。我们让程序继续运行,直到再次到达 图(8)中被选中的调用处,Setp over后发现自签名顺利通过(通过eax返回值判定)。
在自签名检测通过之后是不是就已经OK了呢?多数情况下是不行的,因为使用RSA签名验证的程序,通常会做一些防止修改公钥的手段。当然在这里也毫不例外,“sublime text”的开发者为它添加了SHA256检测(只检测了SHA256结果的第31(偏移为30)个字节和第32(偏移为31)个字节)。我们让程序继续运行,直到到达 图(11)中被选中的调用处(对应图(5)尾部那个调用)。
图(11)
在图(11)中被选中的调用处,我们Step into后,会看到如图(11)中快捷框中的代码段,从偏移xxxxx9600和xxxxx9610两个位置可以找到SHA256的初始值,利用初始值和该调用的执行结果可以判定此处在做SHA256计算。由于我们已经修改过公钥,因此这里的计算将会得到错误的结果。所以我们要想一个办法给它传递一个没有被修改过的公钥,让它产生正确的结果,从而使得后续的检测能顺利通过。在调试分析时,我们发现程序内部还存有一份没有被加密的公钥,所以,在这里我们把这个没有被加密的公钥直接传递给它。因此我们将在这个调用的上方做如图(12)的修改(这里推荐一下网友提供的思路,相关内容在第6楼和第10楼,感谢网友无私分享!)。
(这里需要说明一下,4126是来自稳定通道的版本,在开发通道的版本中这个没加密的公钥主要用于升级验证(和许可验证公钥不同)。如果我们分析的文件是来自开发通道的,我们这里有两个选择:一、直接将用于升级的公钥替换成用于许可的公钥(能检测新版本,但不能下载);二、在只读数据段找到一个空段(数据全0,大小一致)将许可的公钥填进去(没任何影响,得注意一下是否需要对齐到边界)。开发通道最新版本是4129,把下载链接的4126改成4129即可下载到它!另外,开发通道的版本启动时就会打开许可输入窗口,但是并不影响我们的分析步骤。)
图(12)
修改完成后,另存为新文件,然后将这个另存为的新文件载入调试器,且让程序彻底运行起来,直到我们可以通过help菜单输入许可为止。在许可输入窗口中输入我们的自签名许可 ,如图(10),然后点击使用许可按钮。程序将在图(3)的位置被断下。我们让程序继续运行,直到再次到达 图(12)中被选中行下方的调用处,Setp over后发现SHA256校验顺利通过(通过该调用下方跳转来判定)。继续追踪下去,会看到它将我们的许可信息格式化后存放到了: ".\Local\License.sublime_license" 文件中,格式化时在许可内容首尾分别添加了一行信息。
----- BEGIN LICENSE -----
------ END LICENSE ------
看到这里突然明白之前许可内容长度为什么会有一个值为0x1A0了。那么我们的许可就可以使用以下格式来表示了。如图(13)所示:
图(13)
许可内容说明:
用户名:ASCII 字符串
许可类型:Single User License,Unlimited User License,%d User License (%d = [2, 2147483647])
产品类型:E52D(Sublime merge),EA7E和E3D2(Sublime text)
许可编号:0000~9999(十进制,Sublime merge需要从0001开始)
获取许可提示(GET):
https://www.sublimetext.com/license_notification?n=1000&b=4129&m=2evpkm1BrI_9BbSd7ZwVN
n=1000:许可编号。
b=4129:版本编号。
m=2evpkm1BrI_9BbSd7ZwVN: Base64( MD5( MachineGuid ) )。
在我们输入的许可验证通过后,会通过网络去获取许可提示(尝试多次返回内容都为空)。(可通过字符串" license_notification "快速定位到该代码段。)
许可在线检测(GET):
https://license.sublimehq.com/check/0550e07da38d21f30e73f0f51248e5fc47976d17918342ebf26f3b9140e2c06b?n=1000&b=4129&m=2evpkm1BrI_9BbSd7ZwVN
0550e07da38d21f30e73f0f51248e5fc47976d17918342ebf26f3b9140e2c06b:许可内容sha256的计算结果。
n=1000:许可编号。
b=4129:版本编号。
m=2evpkm1BrI_9BbSd7ZwVN: Base64( MD5( MachineGuid ) )。
这个功能应该是用于屏蔽一些特定用户的,因为请求结果需要满足一定格式才能被后边的代码解析。(可通过字符串" license_check "快速定位到该代码段。)
更新下载检测(GET):
https://download.sublimetext.com/_pak/sublime_text_windows_x64_4129.manifest.xz
在点击了下载按钮后,程序首先会去下载一个压缩文件并解压缩出里边包含json格式的文件(xxxx.manifest),然后将用于签名验证的内容和签名一起解析出来并进行RSA签名验证(开发通道的版本使用的是非许可公钥进行验证),在验证通过后才能下载。(可通过字符串"download_update"快速定位到该代码段。)
自定义RSA密钥:
公钥为(n, e),私钥为(n, d)。n、e 和 d 需要满足关系式:ed ≡ 1(mod φ(n) ),即 gcd(e,φ(n) )= 1。所以我们可以通过以下一些方式来构造密钥:
1、随机选择 两个质数 p 和 q,n = p * q,则 φ(n)=(p - 1)*(q - 1),当 1 < e < φ(n) 且与 φ(n)互质, 通过扩展欧几里得算法计算 d;
2、随机选择 一个质数 n,则 φ(n)= n - 1,当 1 < e < φ(n) 且与 φ(n)互质, 通过 扩展欧几里得算法计算 d;
3、当 e = 1,则 d = 1,因为1与任何数都构成互质关系, 所以 φ(n)可以为任意正整数。
(这里的第一种方式对加密信息而言是最安全的。因为如果想要从公钥中获取私钥,唯一可行的就是对 n 进行因式分解,但是对一个很大的数做因式分解是很困难的。)
BCCS MPN多精度整型库:
这个库是从早期项目中提取的部分代码,重新编排整理后的结果(对部分算法和功能进行了调整)。该库目前仍有许多地方需要优化和改进,如果您在使用该库的过程中发现错误或有更好的建议,如果方便可以告知我,我非常期待您的帮助。(附件:mpn-1.0.5.pdf 和 mpn-1.0.5.tar.gz)
附件中实例运行结果:
附图(1)rsa_sign_example
图(2)rsa_sign_with_gmp
阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开
发者可享99元/年,续费同价!
最后于 2022-5-21 07:50
被Mr.zhong编辑
,原因: 编辑
上传的附件: