订阅移动开发RSS CSDN首页> 移动开发

英特尔® Tamper Protection Toolkit 可帮助 Scrypt 加密实用程序抵御逆向工程

发表于2016-06-12 13:39| 来源CSDN| 作者Intel

摘要:本文旨在介绍英特尔® Tamper Protection Toolkit 如何在基于密码的加密实用程序 (Scrypt Encryption Utility) [3]中保护关键代码和宝贵数据免遭静态和动态逆向工程和篡改。

简介

本文旨在介绍英特尔® Tamper Protection Toolkit 如何在基于密码的加密实用程序 (Scrypt Encryption Utility) [3]中保护关键代码和宝贵数据免遭静态和动态逆向工程和篡改。 Scrypt [4] 是一个基于安全密码的现代密钥推导函数,广泛用于安全关键型软件。 Scrypt 有一个潜在威胁(见 [2]),攻击者可通过强迫使用特定的参数强迫生成弱密钥。 英特尔® Tamper Protection Toolkit 可用来缓解该威胁。 我们将解释如何重构相关代码并将篡改保护应用到实用程序中。

在本文中,我们将讨论英特尔® Tamper Protection Toolkit 的以下组件:

  • Iprot。 可创建自修改和自加密代码的混淆工具
  • 加密库 为基本加密操作提供兼容 iprot 的实施的库:加密散列函数、密钥散列消息验证码 (HMAC) 和对称密码。

你可通过以下地址下载英特尔® Tamper Protection Toolkit:https://software.intel.com/zh-cn/tamper-protection

Scrypt Encryption Utility 向 Windows 迁移

由于 Scrypt Encryption Utility 以 Linux* 为目标,而我们想要展示如何在 Windows* 上使用英特尔® Tamper Protection Toolkit,所以我们的首要任务是将 Scrypt Encryption Utility 导入 Windows。 依赖平台的代码将使用以下有条件指令进行构建:

 

 

1#if defined(WIN_TP) // Windows-specific code #else // Linux-specific code #endif  // defined(WIN_TP)

示例 1: 有条件指令的基本结构

WIN_TP 预处理符号可将 Windows 特定代码局部化。 WIN_TP 应针对 Windows 版本定义,否则将为该版本选择参考码。

我们使用 Microsoft Visual Studio* 2013 构建和调试实用程序。 Windows 和 Linux 有许多方面的差异,如进程、线程、内存、文件管理、基础设施服务和用户界面。 为便于迁移,我们需要解决这些差异,具体如下所示。

  1. 实用程序使用 getopt() 处理命令行参数。 参见 [2] 中 Scrypt Encryption Utility 部分列出程序参数。 函数 getopt() 通过 unitstd.h POSIX OS 头文件进行访问。 我们使用了开源项目 getopt_port [1]. 中的 get_opt() 实施。 将从该项目收集的两个新文件 getopt.h 和 getopt.c 添加至源代码树。
  2. POSIX API 中的另一个函数 gettimeofday() 可帮助实用程序测量 salsa opp,即 salsa20/8 在用户平台上的每秒操作数量。 实用程序需要使用衡量标准 salsa opp 为输入参数选取安全配置 N、r 和 p,以便 Scrypt 算法至少执行所需的最低 salsa20/8 操作数,以避免蛮力攻击。 我们在 scryptenc_cpuperf.c 文件中增加了 gettimeofday() 实施 [5]。
  3. 在实用程序开始配置算法前,会先调用 POSIX 系统函数 getrlimit(RLIMIT_DATA, …) 以询问操作系统衍生可用的内存量。 对于 Windows 而言,进程数据片段(初始化数据、未初始化数据和堆)的最大软硬限制尺寸均设定为 4GB:

     

     

    1/* ... RLIMIT_DATA... */ #if defined(WIN_TP) rl.rlim_cur = 0xFFFFFFFF; rl.rlim_max = 0xFFFFFFFF; if((uint64_t)rl.rlim_cur < memrlimit) { memrlimit = rl.rlim_cur; } #else if (getrlimit(RLIMIT_DATA, &rl)) return (1); if ((rl.rlim_cur != RLIM_INFINITY) &&

    2     ((uint64_t)rl.rlim_cur < memrlimit)) memrlimit = rl.rlim_cur; #endif  // defined(WIN_TP)

    示例 2: RLIMIT 数据将进程限制为 4GB。

  4. 此外,在 sysendian.h 中添加内联函数的 MSVS 编译器指令:

     

     

    1#if defined(WIN_TP) static __inline uint32_t #else static inline uint32_t #endif  // WIN_TP be32dec(const void *pp);

    示例 3: 添加 sysendian.h 内联函数

  5. 我们迁移了 tarsnap_readpass(…) 函数,该函数负责处理和遮罩通过终端检索密码的操作。 该函数可关闭回送,并在终端中使用空白字符遮罩密码。 密码存储到内存缓冲区,并发送至下一函数:

     

     

    1/* If we're reading from a terminal, try to disable echo. */ #if defined(WIN_TP) if ((usingtty = _isatty(_fileno(readfrom))) != 0) { GetConsoleMode(hStdin, &mode); if (usingtty) mode &= ~ENABLE_ECHO_INPUT; else mode |= ENABLE_ECHO_INPUT; SetConsoleMode(hStdin, mode); } #else if ((usingtty = isatty(fileno(readfrom))) != 0) { if (tcgetattr(fileno(readfrom), &term_old)) { warn("Cannot read terminal settings"); goto err1; } memcpy(&term, &term_old, sizeof(struct termios)); term.c_lflag = (term.c_lflag & ~ECHO) | ECHONL; if (tcsetattr(fileno(readfrom), TCSANOW, &term)) { warn("Cannot set terminal settings"); goto err1; } } #endif  // defined(WIN_TP)

    示例 4: 通过终端控制密码

  6. 在最初的 getsalt() 中,salt 是通过从 Linux 特定文件 /dev/urandom 中读取伪随机数来创建。 在 Windows 上,我们建议使用 rdrand() 指令从英特尔® 至强™ 和英特尔® 酷睿™ 产品系列(从 Ivy Bridge 微架构起)上可用的硬件随机数生成器上读取。 未使用 C 标准伪随机数生成器,因为getsalt() 与英特尔 Tamper Protection Toolkit 混淆工具不兼容。 函数 getsalt() 应使用混淆器进行保护,以防止出现静态和动态篡改和逆向工程, 因为该函数生成的 salt 在 [2] 的 Scrypt Encryption Utility 部分中归类为机密信息。 以下示例展示了填充 salt 的原始和移植随机数生成代码:

     

     

    1#if defined(WIN_TP) uint8_t i = 0; for (i = 0; i < buflen; i++, buf++) { _rdrand32_step(buf); } #else /* Open /dev/urandom. */ if ((fd = open("/dev/urandom", O_RDONLY)) == -1) goto err0; /* Read bytes until we have filled the buffer. */ while (buflen > 0) { if ((lenread = read(fd, buf, buflen)) == -1) goto err1; /* The random device should never EOF. */ if (lenread == 0) goto err1; /* We're partly done. */ buf += lenread; buflen -= lenread; } /* Close the device. */ while (close(fd) == -1) { if (errno != EINTR) goto err0; } #endif defined(WIN_TP)

    示例 5: 原始和移植随机数生成码

采用英特尔® Tamper Protection Toolkit 的实用程序保护

现在,我们将对实用程序的设计和代码进行变更,帮助保护威胁模式中的机密数据([2] 中的“基于密码的密钥派生”部分)。 机密数据的保护是通过使用 iprot (英特尔® Tamper Protection Toolkit 中的混淆编译器)进行代码混淆来实现。 应仅针对创建、处理和使用敏感数据的函数进行混淆。

通过 [2] 中的代码混淆部分,我们了解到 iprot 用作输入动态库 (.dll),且仅使用命令行中指定的混淆导出函数来生成二进制文件。 因此,我们将与机密数据有关的所有函数放入动态库进行混淆,将其他的函数(如命令行解析和密码读取)留在主可执行文件中。

图 1 显示了采取保护的实用程序的新设计。 实用程序分为两部分:主可执行文件和要进行混淆的动态库。 主可执行文件负责解析命令行,读取密码并将文件输入内存缓冲区。 动态库包括与机密数据 (N、r、p、salt) 相关的导出函数,如scryptenc_file 和 scryptdec_file

动态库使用的密钥数据结构是 Scrypt 环境,可存储有关 Scrypt 参数 N、r、p 和 salt 的 HMAC 信息摘要。 环境中的 HMAC 摘要用于确定环境中的最新变化是否是由可信函数(如 scrypt_ctx_enc_init, scrypt_ctx_dec_init, scryptenc_file 和 scryptdec_file )来完成,这些函数包含一个 HMAC 密钥,可进行委托和验证环境。 这些可信函数可防修改,因为我们有意通过混淆工具对其进行了混淆。 两个新函数 scrypt_ctx_enc_init 和 scrypt_ctx_dec_init 似乎针对加密和解密模式对 Scrypt 环境进行了初始化。


图 1: 采用保护的 Scrypt Encryption Utility 的设计。

加密流程

  1. 实用程序使用 getopt() 处理命令行参数。 参见 [2] 中基于密码的密钥推导函数部分列出程序参数。
  2. 加密输入文件和密码读入内存缓冲区。
  3. 主可执行文件调用 scrypt_ctx_enc_init 以初始化 Scrypt 环境,通过命令行选项(如 maxmem、maxmemfrac 和 maxtime)计算密钥推导的具体 CPU 时间和内存大小的安全 Scrypt 参数(N、r、p 和 salt)。 此次调用结束后,初始化函数将创建 HMAC 摘要,包括最新更新状态,防止函数在返回时进行篡改。 初始化函数还将返回应用需要分配的内存,以便进一步加密。
  4. 主可执行文件中的实用程序可根据初始化函数返回的尺寸来分配内存。
  5. 可执行文件再次调用 scrypt_ctx_enc_init。 函数使用 Hash MAC 摘要验证 Scrypt 环境的完整性。 如果完整性验证通过,那么函数将使用分配的位置在环境中设置缓冲区位置,并更新 HMAC。 文件读取和动态内存分配在可执行文件中完成,以避免动态库中出现不兼容 iprot 的代码。 包含系统调用和 C 标准函数的代码可生成间接转移和再定位,而混淆器对此并不支持。
  6. 可执行文件调用 scryptenc_file,使用用户提供的密码加密文件。 函数使用密钥推导使用的参数(N、r、p 和 salt)验证 Scrypt 环境的完整性。 如果验证通过,它将调用 Scrypt 算法推导密钥。 然后,使用推导出的密钥进行加密。 导出函数与初始 Scrypt 实用程序的输出相同。 这表示,输出的散列值与验证加密数据的完整性以及加密过程中密码的正确性所使用的散列值相同。

解密流程

  1. 实用程序使用 getopt() 处理命令行参数。 参见 [2] 中基于密码的密钥推导部分列出程序参数。
  2. 解密输入文件和密码读入内存缓冲区。
  3. 主可执行文件调用 scrypt_ctx_dec_init 检查加密文件数据中所提供的参数是否有效,密钥推导函数是否能够在允许的内存和 CPU 时间内计算。
  4. 主可执行文件中的实用程序可根据初始化函数返回的尺寸来分配内存。
  5. 可执行文件再次调用 scrypt_ctx_dec_init。 该函数的功能与加密情况下的相同。
  6. 可执行文件调用 scryptdec_file 使用密码解密文件。 函数使用密钥推导使用的参数(N、r、p 和 salt)验证 Scrypt 环境的完整性。 如果验证通过,它将调用 Scrypt 算法推导密钥。 在加密数据中使用散列值,函数可验证密码的正确性以及加密数据的完整性。

在采用保护的实用程序中,我们用英特尔 Tamper Protection Toolkit 加密库中的 OpenSSL* 高级加密标准替换了 CTR 模式加密和密钥散列函数中的该特性。 加密库不同于 OpenSSL*,其满足 iprot 进行混淆所需的全部代码限制,并可直接从混淆代码中使用,无需做其他修改。 在 scryptenc_file 和 scryptdec_file 内调用 AES 密码,以使用从密码推导的密钥加密/解密输入文件。 密钥散列函数通过导出函数(scrypt_ctx_enc_init, scrypt_ctx_dec_init, scryptenc_file 和 scryptdec_file)进行调用,以便在使用 Scrypt 环境前验证其数据完整性。 在采用保护的实用程序中,所有导出的动态库函数均使用 iprot 进行混淆。 英特尔® Tamper Protection Toolkit 可帮助我们缓解 [2] 中的基于密码的密钥推导部分所定义的威胁。

我们的解决方案是使用 iprot 混淆动态库重新设计的实用程序。 这可以抵御上文提到的攻击,而且证明,Scrypt 仅通过导出函数即可进行更新,因为它们采用 HMAC 私有密钥,能够重新计算环境中的 HMAC 摘要。 此外,混淆器还可保护这些函数和 HMAC 密钥不受篡改和逆向工程的影响。 除此之外,其他机密数据(如 Scrypt 生成的密钥)也可得到保护,因为它是在混淆的导出函数 scryptenc_file 和scryptdec_file 中派生出来的。 混淆编译器生成的代码能够在运行时自加密,并且能够保护设备免遭篡改和调试。

接下来,我们来了解一下 scrypt_ctx_enc_init 如何保护 Scrypt 环境。 主可执行文件在scrypt_ctx_enc_init 调用的同时通过指针向 buf_p 发送信号。 如果指针等于空值,那么函数为首次调用;否则其为第二次调用。 在首次调用初始化时,它会提取 Scrypt 参数,计算 HMAC 摘要并返回 Scrypt 计算所需的内存量,如下所示:

 

 

1// Execute for the first call when it returns memory size required by scrypt if (buf_p == NULL) { // Pick parameters for scrypt and initialize the scrypt context // <...>

2 

3        // Compute HMAC itp_res = itpHMACSHA256Message((unsigned char *)ctx_p, sizeof(scrypt_ctx)-                              sizeof(ctx_p->hmac), hmac_key, sizeof(hmac_key), ctx_p->hmac, sizeof(ctx_p->hmac)); *buf_size_p = (r << 7) * (p + (uint32_t)N) + (r << 8) + 253; }

示例 6: 首次调用保护 Scrypt 环境的代码

在第二次调用过程中,分配内存的 buf_p 指针传递至 scrypt_ctx_enc_init 函数。 在环境中使用 HMAC 摘要,则函数可验证环境的完整性,确保无人在第一次和第二次调用之间对其做更改。 之后,它使用 buf_p 初始化环境内的地址,并重新计算 HMAC 摘要,因为环境发生了如下变化:

 

 

1// Execute for the second call when memory for scrypt is allocated if (buf_p != NULL) { // Verify HMAC itp_res = itpHMACSHA256Message( (unsigned char *)ctx_p, sizeof(scrypt_ctx)-sizeof(ctx_p->hmac), hmac_key, sizeof(hmac_key), hmac_value, sizeof(hmac_value)); if (memcmp(hmac_value, ctx_p->hmac, sizeof(hmac_value)) != 0) { return -1; } // Initialize pointers to buffers for scrypt computation: // ctx_p->addrs.B0 = … // Recompute HMAC itp_res = itpHMACSHA256Message( (unsigned char *)ctx_p, sizeof(scrypt_ctx)-sizeof(ctx_p->hmac), hmac_key, sizeof(hmac_key), ctx_p->hmac, sizeof(ctx_p->hmac)); }

示例 7: 第二次调用保护 Scrypt 环境的代码

从 [2] 中,我们了解到 iprot 在输入代码中添加了一些限制,以便其进行混淆。 它不需要再定位,也不需要间接转移。 在 C 中使用全局变量、系统函数和 C 标准函数调用编写结构可生成再定位和间接转移。 示例 7 中的代码可调用一个 C 标准函数 memcmp,这会导致代码与 iprot 不兼容。 为此,我们实施了一些自己的 C 标准函数,如实用程序使用的 memcmp、memset 和 memmove。 此外,动态库中的所有全局变量都将转换为本地变量,并负责堆栈上初始化的数据。

除此之外,我们遇到了使用两个值混淆代码的问题,这个问题在教程和英特尔® Tamper Protection Toolkit 用户指南中均未作介绍。 如下所示,在 pickparams 函数中,内核操作限制 salsa20/8 包含双重类型,相当于 32768。 该值未在堆栈上初始化,编译器将该值放入二进制的数据段,可在代码中生成再定位。

 

 

1double opslimit; #if defined(WIN_TP) // unsigned char d_32768[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x40}; unsigned char d_32768[sizeof(double)]; d_32768[0] = 0x00; d_32768[1] = 0x00; d_32768[2] = 0x00; d_32768[3] = 0x00; d_32768[4] = 0x00; d_32768[5] = 0x00; d_32768[6] = 0xE0; d_32768[7] = 0x40; double *var_32768_p = (double *) d_32768; #endif /* Allow a minimum of 2^15 salsa20/8 cores. */ #if defined(WIN_TP) if (opslimit < *var_32768_p) opslimit = *var_32768_p; #else if (opslimit < 32768) opslimit = 32768; #endif

查看全文

更多内容请详见:英特尔开发人员专区

0
0
  • CSDN官方微信
  • 扫描二维码,向CSDN吐槽
  • 微信号:CSDNnews
程序员移动端订阅下载

微博关注