• 0、前言

首先,这是一件很无聊的事,把CRC的值内置到文本文件中什么的。

顺便一提,之前在csdn写的那些文章,由于那个网站的广告太多了就不想在那写了,就先搬过来看看(好像文章中有很多公式不见了,想看原文的话,去那个网站翻翻吧,那些公式敲起来贼麻烦,懒得补了)。

之前写过两篇文章,分别叫《指定CRC反构数据》、《内置CRC于hex程序中的方法》,前者针对bin文件(虽支持文本文件,但会产生乱码),后者针对hex文件(暂不支持数字信号处理器,也就是输出给dsp使用的hex文件)。

再顺带一提,当时我并没有给出我设计的针对hex文件的CRC算法。首先说说“SEGGER J-Flash”软件给出的CRC算法,那个软件使用的算法,是按照用户选择的MCU型号,得到Flash的首地址与容量,将hex文件未使用的部分全部填充0xFF,转换成bin文件之后,以bin模式计算标准CRC32,得到的结果作为hex文件的CRC。我不使用这个的理由,在于用户必须选择MCU型号,这是用户的负担,也是检验码软件的负担。用一个相同的hex文件,选择不同Flash容量的MCU型号,算出的CRC会不一样。J-Flash软件的更新频繁,很大程度上是为了扩充新MCU的Flash容量之类的数据。

我使用的方法,则是分别对有效地址和有效数据计算CRC32,但是模型稍有变动,不影响内置保持不变的性质。

例如,有这么个文件:

分别取出有效地址,跳过窟窿字节,32比特拆成4个字节时,高字节在前,低字节在后,排列如下:

08 00 00 00 08 00 00 01 08 00 00 03 08 00 00 04 08 00 00 05 08 00 00 08 08 00 00 09 08 00 00 0A 08 00 00 0B 08 00 00 0C 08 00 00 0D 

再分别取出有效数据,同样跳过窟窿字节,排列如下:

12 34 56 78 90 AA BB CC DD EE FF

分别计算两个的CRC,异或之后作为hex文件的CRC。

  • 1、内置CRC于bin文件中的方法

太简单啦,通过《指定CRC反构数据》里面的方法,已经可以做到了。

例如这么个文件:

这个文件有19个字节,CRC32是FA6C02E4。我想在第7个字节后边添加CRC,CRC占用8个字节的字符,CRC后添加4个字节的尾巴,称为平衡算子,初值暂时写零,平衡算子之后是原有的剩余12个字节。平衡算子的目的,是抵消填充物,对整体CRC值的影响。

临时文件共计31字节,内容如下:

之后,使用《指定CRC反构数据》里面的方法,填充4个字节的平衡算子,得到的新文件如下:

算一下这个新文件的CRC32就是FA6C02E4,与原文件一致。

  • 2、内置CRC于bin文件中的烦恼

其实上一节给出的文件也是一个文本文件,里面是文字且无乱码,但是串进内置CRC后,会出现乱码,实际上,用文本编辑器打开就会有乱码:

别跟我说什么选的编码方式不对什么的,这玩意就不该给人看到。假设我把CRC和平衡算子放置到某个C语言的源文件中,用注释符括起来,那么就有这样的风险,平衡算子里面出现换行符,甚至出现匹配为注释结尾符,那甚至会破坏那个源文件的语法结构。。。所以有必要让平衡算子“消失”。

想要让文字消失,最好的办法,就是使用空格,或者类似于空格的不可见字符。我找到了一对很适合拿来用的不可见字符,由于不可见,所以这里仅仅给出其UTF-8编码:分别是E2 80 8C、E2 80 8D。这一对编码有个两个特点:首先是不可见,不但不可见,甚至连宽度都是零;然后是近似,二者有且只有1个比特的差别,只要叠32次,就有可能构造出有效的平衡算子。根据我的调查,E2 80 8C被称为ZERO WIDTH NON-JOINER,E2 80 8D被称为ZERO WIDTH JOINER,这两个控制字符放在注释里,不但是隐形的,而且不会破坏源文件的语法。

当然,唯一的限制就是,原文本文件最好要使用UTF-8编码,才能支持这两个字符。用GB2312编码或者用UTF-16编码什么的,后续思路是类似的,但是要想办法找到一对空白字符,两者有且只有1个比特的差别就行。统一使用UTF-8编码并不是什么过分的要求,后文只针对UTF-8编码。

  • 3、构建文本文件的平衡算子

我提出一个假说:在二进制文件中(尺寸4字节以上),指定连续的32比特,通过穷举其所有可能的组合,总有一种组合满足希望的CRC32。这个假说是成立的,因为那篇文章中已经推演过了。

我再提出第二个假说:在二进制文件中(尺寸4字节以上),指定任意的32比特,通过穷举其所有可能的组合,总有一种组合满足希望的CRC32。这个假说不成立,因为TruncPoly=0x104C11DB7,写成二进制模式就是1 0000 0100 1100 0001 0001 1101 1011 0111,只要按照这个模式中的1的位置,指定15个比特,同时反转这15个比特的值,就可以保证CRC不变。因此,在这15个比特的基础上,再随便找其他17个比特,凑齐32比特,穷举时,结果就会出现冲突。

虽然第二个假说不成立,但是没准我的运气不错,我选择的32个比特,满足我的目的呢?根据上一节提到的两个字符:E2 80 8C、E2 80 8D,叠32次,共计96字节,也就是24比特叠32次,共计768比特。将每3个字节的最后一个字节的第0位,共计32比特拿出来,作为我的平衡算子。

之后就是求解平衡算子。这个与连续的32比特平衡算子不同,很难用数学方法推演出来。数学高手可以尝试下。。。

嘛,毕竟2的32次方并不是很大的数,只要找到每个比特变化,对应的平衡算子的变化,制成查找表格就好啦。毕竟是穷举法,这个方法对CRC64是绝望的,对CRC16就很友好了。

说一下平很算子的解法。把字符E2 80 8C重复32次,作为原始素材。用0作为余数,从原始素材最右边,向左边做CRC的逆运算,得到初值rem。之后,仍然用0作为余数,但是要改变原始素材中,被选为平衡算子的那32个比特,从新的素材最右边,向左边做CRC的逆运算,得到新的初值rem。不断重复这个过程,直到遍历完成全部的2的32次方种组合。我们关注的余数有33个,首先是0x00000000,这个是基准,这意味着用那个平衡算子对0做CRC运算,得到的结果仍然是0。之后是0x00000001、0x00000002、0x00000004、0x00000008、……、0x80000000,共计32个数,其规律是,都是2的整幂,这个想法是理所当然的,只要组合这32种情况,就能拟合出任意的rem变化。

当然,我们关注的并不是这些平衡算子的值,我们只关注后边的32个值,相对于基准值的变化。在rem扫描平衡算子之前的部分之后,我们无法更改rem的值,但是我们可以对平衡算子之后的部分求取CRC的逆,得到rem的变化。只要找到了这些变化的一一对应关系,就可以组合出最终的平衡算子。

  • 4、求取破解表
#include <stdint.h>
#include <stdio.h>static uint32_t s_gen_table[0x100] = { 0 };
static uint32_t s_inv_table[0x100] = { 0 };static uint32_t s_empty = 0;
static uint32_t s_crack[0x20] = { 0 };void init_table()
{for (int i = 0; i < 0x100; ++i){uint32_t gen = i;uint32_t inv = i << 24;for (int j = 0; j < 8; ++j){gen = (gen >> 1) ^ (gen & 0x00000001 ? 0xEDB88320 : 0);inv = (inv << 1) ^ (inv & 0x80000000 ? 0xDB710641 : 0);}s_gen_table[i] = gen;s_inv_table[i] = inv;}return;
}void crack_search(uint32_t rem = 0, size_t deep = 0, uint32_t value = 0)
{if (deep == 6){printf (".");}if (deep == 32){if (rem == 0){s_empty = value;}else if (((rem - 1U) & rem) == 0){for (size_t i = 0; i < 32; i++){if ((rem >> i) == 1U){s_crack[i] = value;break;}}}return;}rem = (rem << 8U) ^ s_inv_table[rem >> 24U];rem = (rem << 8U) ^ s_inv_table[rem >> 24U];rem = (rem << 8U) ^ s_inv_table[rem >> 24U];crack_search (rem ^ 0x8C80E2, deep + 1, value);crack_search (rem ^ 0x8D80E2, deep + 1, value ^ (0x80000000U >> deep));return;
}int main(int argc, char *argv[])
{init_table ();crack_search ();printf ("\n");for (size_t i = 0; i < 0x20; i++){printf ("0x%08X, ", s_crack[i] ^ s_empty);if (i % 4 == 3){printf ("\n");}}return 0;
}

这是一个递归二叉搜索,只是为了重复利用穷举过程的中间结果。执行之后,就可得到破解表。if (deep == 6)是为了显示穷举进度。

0x160E142B, 0x5972F1D7, 0xBE7DBE2F, 0xC5804111,
0x1A13ACF2, 0x016AD10A, 0xCD8E8D74, 0x1A45AE34,
0x77B133B2, 0x78CF4017, 0x51802E15, 0xE3D30532,
0xBA09386A, 0x1A5D560A, 0x39F02BC8, 0x413B6A57,
0x00000001, 0xFB359B60, 0x7AAE788F, 0x28209B40,
0x656482F8, 0xCDA12D7A, 0x3B206904, 0x3CBC58E5,
0x2C1C2856, 0xB2E5E3AE, 0x6E0C5043, 0x99F7AE3F,
0x342759E4, 0x02D5A214, 0x89EA36F5, 0x348B5C68,

说明一下破解表的用途:

首先将CRC和初始平衡算子(就是将字符E2 80 8C重复32次,作为原始素材的平衡算子),用注释符包裹起来,放置在原有文件的任一行中。之后,求取平衡算子之前部分的rem,并用原始文件的CRC32的值,从文件结尾向前,求取CRC逆运算,直到越过平衡算子,得到后半部分的rem。理论上这两个rem如果一致,那么就表示,rem不需变化就可以连接上,新文件的CRC32与原始文件的CRC32就是一致的。但是这种概率很低,实际上两个rem之间,会有很多个比特是不同的,找出不同的比特,根据其占在第几位,对应出破解表的项。破解表中的值,是用来修正平衡算子的。遍历所有不同的比特,将其在对应破解表中的值找出,全部异或在一起,就是我们求出来的最终的平衡算子。

  • 5、内置CRC于文本文件中

跟内置CRC于hex文件中一样,我希望将当前日期和时间一起内置在注释中,并通过求取平衡算子,保证新文件的CRC,与原有文件的CRC一致。

编译环境VS2015。测试文件为“R:\test.txt”,其内容与文章开头给出的“test.bin”一致,CRC32=FA6C02E4。

HIJKLMNOPQRSTUVWXYZ

测试代码如下:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>#ifdef WIN32
#include <malloc.h>
#endifstatic const uint32_t CRACK_TABLE[0x20] =
{0x160E142B, 0x5972F1D7, 0xBE7DBE2F, 0xC5804111,0x1A13ACF2, 0x016AD10A, 0xCD8E8D74, 0x1A45AE34,0x77B133B2, 0x78CF4017, 0x51802E15, 0xE3D30532,0xBA09386A, 0x1A5D560A, 0x39F02BC8, 0x413B6A57,0x00000001, 0xFB359B60, 0x7AAE788F, 0x28209B40,0x656482F8, 0xCDA12D7A, 0x3B206904, 0x3CBC58E5,0x2C1C2856, 0xB2E5E3AE, 0x6E0C5043, 0x99F7AE3F,0x342759E4, 0x02D5A214, 0x89EA36F5, 0x348B5C68,
};static uint32_t s_gen_table[0x100] = { 0 };
static uint32_t s_inv_table[0x100] = { 0 };void init_table()
{for (int i = 0; i < 0x100; ++i){uint32_t gen = i;uint32_t inv = i << 24;for (int j = 0; j < 8; ++j){gen = (gen >> 1) ^ (gen & 0x00000001 ? 0xEDB88320 : 0);inv = (inv << 1) ^ (inv & 0x80000000 ? 0xDB710641 : 0);}s_gen_table[i] = gen;s_inv_table[i] = inv;}return;
}bool self_crc32_utf8(const char *filename, int line = 0)
{
#ifdef WIN32
#define fseeko64 _fseeki64
#define ftello64 _ftelli64
#define localtime_r(rawtime, timeinfo) localtime_s (timeinfo, rawtime)
#endifif (filename == nullptr){return false;}if (filename[0] == '\0'){return false;}int filename_len = strlen (filename);char *tmp_filename = (char *) alloca (filename_len + 5);snprintf (tmp_filename, filename_len + 5, "%s.tmp", filename);FILE *dst_stream = fopen (tmp_filename, "wb");if (dst_stream == nullptr){return false;}FILE *src_stream = fopen (filename, "rb");if (src_stream == nullptr){fclose (dst_stream);remove (tmp_filename);return false;}uint32_t new_rem = 0xFFFFFFFF;int64_t new_pos = -1;int line_i = -1;uint32_t rem = 0xFFFFFFFF;bool cr_flag = false;   // "\r"bool lf_flag = false;   // "\n"bool last_flag = false; // last == '\r'bool error_flag = false;enum { BUF_SIZE = 0x400 };unsigned char buf[BUF_SIZE] = {};size_t text_len = 0;while (size_t read_len = fread (buf + text_len, 1, BUF_SIZE - text_len, src_stream)){text_len += read_len;size_t valid_len = 0;if (line_i == -1){line_i = 0;if (text_len >= 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF){valid_len += 3;}}while (valid_len < text_len || line_i == line){if (line_i == line){++line_i;if (valid_len > 0){size_t write_len = fwrite (buf, 1, valid_len, dst_stream);if (write_len != valid_len){error_flag = true;break;}for (size_t i = 0; i < valid_len; i++){rem = (rem >> 8U) ^ s_gen_table[(rem ^ buf[i]) & 0xFFU];}text_len -= valid_len;if (text_len > 0){memmove (buf, buf + valid_len, text_len);}valid_len = 0;}new_rem = rem;new_pos = ftello64 (dst_stream);int seek_ans = fseeko64 (dst_stream, 144, SEEK_CUR);if (seek_ans != 0){error_flag = true;break;}continue;}unsigned char ch0 = buf[valid_len];if (last_flag){last_flag = false;++line_i;if (ch0 == '\n'){lf_flag = true;++valid_len;}continue;}if (ch0 == '\n'){lf_flag = true;++line_i;++valid_len;continue;}if (ch0 == '\r'){cr_flag = true;last_flag = true;++valid_len;continue;}last_flag = false;if (ch0 > 0 && ch0 < 0x80U){++valid_len;continue;}if (valid_len + 1U >= text_len){break;}unsigned char ch1 = buf[valid_len + 1U];if ((ch1 & 0xC0U) != 0x80U){error_flag = true;break;}if ((ch0 & 0xE0U) == 0xC0U){unsigned value = ((ch0 & 0x1FU) << 6U)| ((ch1 & 0x3FU) << 0U);if (value < 0x80U){error_flag = true;break;}valid_len += 2U;continue;}if (valid_len + 2U >= text_len){break;}unsigned char ch2 = buf[valid_len + 2U];if ((ch2 & 0xC0U) != 0x80U){error_flag = true;break;}if ((ch0 & 0xF0U) == 0xE0U){unsigned value = ((ch0 & 0x0FU) << 12U)| ((ch1 & 0x3FU) <<  6U)| ((ch2 & 0x3FU) <<  0U);if (value < 0x800U || (value & 0xF800U) == 0xD800U){error_flag = true;break;}valid_len += 3;continue;}if (valid_len + 3U >= text_len){break;}unsigned char ch3 = buf[valid_len + 3U];if ((ch3 & 0xC0U) != 0x80U){error_flag = true;break;}if ((ch0 & 0xF8U) == 0xF0U){unsigned value = ((ch0 & 0x07U) << 18U)| ((ch1 & 0x3FU) << 12U)| ((ch2 & 0x3FU) <<  6U)| ((ch3 & 0x3FU) <<  0U);if (value < 0x10000U || value > 0x10FFFFU){error_flag = true;break;}valid_len += 4;continue;}error_flag = true;break;}if (error_flag){break;}if (valid_len == 0){continue;}size_t write_len = fwrite (buf, 1, valid_len, dst_stream);if (write_len != valid_len){error_flag = true;break;}for (size_t i = 0; i < valid_len; i++){rem = (rem >> 8U) ^ s_gen_table[(rem ^ buf[i]) & 0xFFU];}text_len -= valid_len;if (text_len > 0){memmove (buf, buf + valid_len, text_len);}}if (error_flag || text_len > 0){fclose (src_stream);fclose (dst_stream);remove (tmp_filename);return false;}const char *head0 = nullptr;const char *tail1 = nullptr;const char *tail2 = nullptr;int crack_pos = 0;if (new_pos >= 0){fseeko64 (dst_stream, new_pos, SEEK_SET);head0 = "/*";if (cr_flag && lf_flag){tail1 = "  ";tail2 = "*/\r\n";crack_pos = 44;}else if (cr_flag){tail1 = "   ";tail2 = "*/\r";crack_pos = 45;}else{tail1 = "   ";tail2 = "*/\n";crack_pos = 45;}}else // if (new_pos < 0)
    {if (cr_flag && lf_flag){head0 = "\r\n/*";tail1 = "  ";tail2 = "*/";}else if (cr_flag){head0 = "\r/*";tail1 = "   ";tail2 = "*/";}else{head0 = "\n/*";tail1 = "   ";tail2 = "*/";}crack_pos = 46;}time_t rawtime = time (nullptr);struct tm timeinfo = {};localtime_r (&rawtime, &timeinfo);rem ^= 0xFFFFFFFFU;snprintf ((char *)buf, BUF_SIZE,"%s %04d-%02d-%02d %.3s %02d:%02d:%02d CRC=0x%08X %s",head0,timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,3 * timeinfo.tm_wday + "SunMonTueWedThuFriSat",timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec,rem, tail1);for (size_t i = 0; i < 96; i += 3){buf[crack_pos + i + 0] = 0xE2;buf[crack_pos + i + 1] = 0x80;buf[crack_pos + i + 2] = 0x8C;}memcpy (buf + crack_pos + 96, tail2, 48 - crack_pos);rem = new_rem;for (int i = 0; i < crack_pos; i++){new_rem = (new_rem >> 8U) ^ s_gen_table[(new_rem ^ buf[i]) & 0xFFU];}for (int i = 143; i >= crack_pos; i--){rem = (rem << 8U) ^ s_inv_table[rem >> 24U];rem ^= buf[i];}rem ^= new_rem;uint32_t mask = 0;for (size_t i = 0; i < 32; i++){if ((rem & (1U << i)) != 0){mask ^= CRACK_TABLE[i];}}for (size_t i = 0; i < 96; i += 3){if ((mask & 1U) != 0){buf[crack_pos + i + 2] = 0x8D;}mask >>= 1U;}size_t write_len = fwrite (buf, 1, 144, dst_stream);if (write_len != 144){fclose (src_stream);fclose (dst_stream);remove (tmp_filename);return false;}fclose (src_stream);fclose (dst_stream);#ifdef WIN32
#undef fseeko64
#undef ftello64
#undef localtime_r
#endifreturn true;
}int main(int argc, char *argv[])
{init_table ();self_crc32_utf8 ("R:\\test.txt");return 0;
}

函数self_crc32_utf8的第二个参数,用来表示希望把注释插到第几行。没有对应行数则插到文件结尾。

运行得到的文件“R:\test.txt.tmp”,内容如下:

/* 2019-01-04 Fri 20:51:47 CRC=0xFA6C02E4    ‌‍‍‌‌‍‍‌‌‌‍‌‌‍‌‌‌‌‍‌‍‌‍‍‌‍‍‌‍‍‍‌*/
HIJKLMNOPQRSTUVWXYZ

其中E2 80 8C、E2 80 8D可能被网站过滤掉了看不到。其CRC32仍然是FA6C02E4。

以上。

转载于:https://www.cnblogs.com/sugar13/p/10222461.html

内置CRC于文本文件中的方法相关推荐

  1. 内置CRC于hex程序中的方法

    [摘要] 为了让MCU程序显示自身的CRC值,需要将其内置到程序中.但是,通常情况下,用计算好的CRC值,代替程序中原有的默认值之后,会导致程序发生变动,进而引发CRC值的变动.最终,新程序显示的值, ...

  2. python中的random模块_Python内置random模块生成随机数的方法

    本文我们详细地介绍下两个模块关于生成随机序列的其他使用方法. 随机数参与的应用场景大家一定不会陌生,比如密码加盐时会在原密码上关联一串随机数,蒙特卡洛算法会通过随机数采样等等.Python内置的ran ...

  3. 解决 IntelliJ IDEA 内置的 Tomcat 日志中显示的中文乱码

    解决 IntelliJ IDEA 内置的 Tomcat 日志中显示的中文乱码 方法 1 方法 2(不推荐) 笔者的环境: JDK 13.0.2 Maven 3.6.3 Tomcat 9.0.41(Se ...

  4. python生成50个随机数_Python内置random模块生成随机数的方法

    本文我们详细地介绍下两个模块关于生成随机序列的其他使用方法. 随机数参与的应用场景大家一定不会陌生,比如密码加盐时会在原密码上关联一串随机数,蒙特卡洛算法会通过随机数采样等等.Python内置的ran ...

  5. python产生随机数random.random_Python内置random模块生成随机数的方法

    本文我们详细地介绍下两个模块关于生成随机序列的其他使用方法. 随机数参与的应用场景大家一定不会陌生,比如密码加盐时会在原密码上关联一串随机数,蒙特卡洛算法会通过随机数采样等等.Python内置的ran ...

  6. win8无法使用内置管理员账户打开的解决方法

    win8无法使用内置管理员账户打开的解决方法 win8启用administrator账户之后打开Metro程序总提示"win8无法使用内置管理员账户打开..." 在运行中输入:&q ...

  7. Python内置函数—vars的具体使用方法

    本文文章主要介绍了Python内置函数-vars的具体使用方法,分享给大家,具体如下: 英文文档: vars([object]) Return the dict attribute for a mod ...

  8. java控制台输出到文件_如何将java控制台的输出内容存入到文本文件中 经典方法...

    如何将java控制台的输出内容存入到文本文件中 经典方法 (2014-04-17 19:27:23) 修改LogWriter类的静态域即可随意切换输出了.main方法中代码不用改变. 代码如下: im ...

  9. 列举至少五个python内置函数和使用方法_Python内置函数 next的具体使用方法 Python中seek和next区别...

    python列表本来没有next方法,为什么用iter函数生...对list用__dir__()发现没有next方法,但是用iter()生成迭代器,对该迭代next是进行迭代的方法,只有迭代器和生成器 ...

最新文章

  1. vue缓存页面【二】
  2. 你还在为了进高校做教师而读博吗?
  3. GetSafeHdc( )
  4. 傅里叶级数FS, 离散傅里叶变换DFT
  5. 区块链参考资源, 雷达 信号处理
  6. HALCON示例程序optical_flow.hdev如何使用optical_flow_mg计算图像序列中的光流以及如何分割光流。
  7. Android之OKHttp使用总结
  8. Python 入门级1
  9. h5 移动端 监听软键盘弹起、收起
  10. 84相似标准形05——有理标准形的不变因子、矩阵的有理标准形
  11. 【POJ2775】The Number of the Same BST(二叉搜索树+计数+lucas定理)
  12. 最短路应用 —— 解决某些计数、数论问题
  13. Springboot+Vue+EasyExcel实现web页面的excel读取
  14. widthStep、width
  15. vue引用echarts
  16. [收藏] Flash闪存颗粒和工艺知识深度解析
  17. windows下安装zookeeper以及监控中心
  18. 1.Windows环境配置
  19. 假设 A 类有如下定义,设 a 是 A 类的一个实例,下列语句调用哪个是错误的?()
  20. 习题 4.6 有一个函数:。。。 写程序,输入x的值,输出y相应的值。

热门文章

  1. 单词拆分java与填表法_139. 单词拆分
  2. python编程源码
  3. MetaTrader软件的功能扩展(关于程序补丁制作的一个例子)
  4. 生活中48条让人匪夷所思的诡秘心理
  5. “春城”昆明郁金香盛开 万紫千红引游人
  6. March 4 2017 Week 10 Saturday
  7. 如何网上打印广州市个人社保缴纳证明
  8. B2B-Destoon--注册会员公司名称修改方法
  9. 看我如何破解隔壁家小姐姐的wifi
  10. iOS和tvOS游戏按需加载资源简介