在 Objectice-C 或 C++ 项目中,使用字符串预编译加密可以有效防止代码被逆向工程。使用 js 加密生成 16 进行数组拷贝到项目中是一种解决方法,还可以使用开源宏定义域编译或者使用开源项目插件来防止逆向。这些方法可以有效防止程序中的敏感代码被逆向工程,并保护代码的可维护性和安全性。obfuscator-llvm、STCObfuscator、Obfuscator-iOS 等。

image-20230221145948954

1.使用 js 加密生成 16 进行数组拷贝到项目 调用解密

AES 加密等加密

JS 生成加密字符串数据可以使用常见的加密算法,例如 AES、DES、RSA 等。以下是使用 AES 算法生成加密字符串数据的示例代码:

// 密钥
const key = "0123456789abcdef0123456789abcdef";
// 原始数据
const data = "hello world";

// 加密
const aesEncrypt = (data, key) => {
  const cipher = crypto.createCipheriv("aes-256-cbc", key, key.slice(0, 16));
  let encrypted = cipher.update(data, "utf8", "hex");
  encrypted += cipher.final("hex");
  return encrypted;
};

const encrypted = aesEncrypt(data, key);
console.log(encrypted); // 3f6dd8b6a402bda28077c0bf

// 将16进制字符串转为16进制数组
const hexToArray = hex => {
  const array = new Uint8Array(hex.length / 2);
  for (let i = 0; i < hex.length; i += 2) {
    array[i / 2] = parseInt(hex.substr(i, 2), 16);
  }
  return array;
};

const encryptedArray = hexToArray(encrypted);
console.log(encryptedArray); // [63, 109, 216, 182, 164, 2, 189, 162, 128, 119, 192, 191]

Objective-C++解密函数可以使用相应的解密算法进行解密,例如以下是使用 AES 算法进行解密的示例代码:

[hidecontent type=“reply”]

#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonCryptor.h>

NSData* aesDecrypt(NSData *data, NSData *key) {
    // 加密算法为AES,模式为CBC,填充方式为PKCS7Padding
    const NSUInteger kAlgorithm = kCCAlgorithmAES;
    const NSUInteger kMode = kCCModeCBC;
    const NSUInteger kPadding = kCCOptionPKCS7Padding;

    // 初始化向量
    const char iv[16] = {0};

    // 设置密钥
    const void *keyPtr = [key bytes];
    const NSUInteger keyLength = [key length];

    // 分配内存
    size_t bufferSize = [data length] + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    // 解密
    size_t bytesDecrypted = 0;
    CCCryptorStatus result = CCCrypt(kCCDecrypt, kAlgorithm, kPadding,
                                     keyPtr, keyLength, iv,
                                     [data bytes], [data length],
                                     buffer, bufferSize, &bytesDecrypted);

    // 返回解密后的数据
    NSData *resultData = nil;
    if (result == kCCSuccess) {
        resultData = [NSData dataWithBytesNoCopy:buffer length:bytesDecrypted];
    } else {
        free(buffer);
        @throw [NSException exceptionWithName:@"DecryptException"
                                       reason:@"解密失败"
                                     userInfo:nil];
    }
    return resultData;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 密钥
        NSString *keyString = @"0123456789abcdef0123456789abcdef";
        NSData *keyData = [keyString dataUsingEncoding:NSUTF8StringEncoding];

        // 加密后的数据(16进制数组)
        const unsigned char encryptedArray[] = {0x3f, 0x6d, 0xd8, 0xb6, ...];
        // 将16进制数组转为NSData对象
        NSData *encryptedData = [NSData dataWithBytes:encryptedArray length:sizeof(encryptedArray)];

        // 解密
        NSData *decryptedData = aesDecrypt(encryptedData, keyData);
        NSString *decryptedString = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding];

        // 打印解密后的字符串
        NSLog(@"%@", decryptedString);
    }
        return 0;
}

注意:以上代码仅作为示例,实际使用时应当根据具体需求选择合适的加密算法、填充方式等,并进行适当的安全性优化。

[/hidecontent]

位运算等基本操作进行加密

使用位运算等基本操作进行加密,安全性会比较低,容易被破解,不建议在实际应用中使用。以下是使用位运算等基本操作进行加密的示例代码:

// 原始数据
const data = "hello world";

// 加密
const encrypt = data => {
  const key = 0x7f; // 密钥为0x7f
  let encrypted = "";
  for (let i = 0; i < data.length; i++) {
    const c = data.charCodeAt(i) ^ key; // 使用异或进行加密
    encrypted += c.toString(16); // 转为16进制字符串
  }
  return encrypted;
};

const encrypted = encrypt(data);
console.log(encrypted); // 5e5c5f5c5b3e5c5f5d

// 将16进制字符串转为16进制数组
const hexToArray = hex => {
  const array = new Uint8Array(hex.length / 2);
  for (let i = 0; i < hex.length; i += 2) {
    array[i / 2] = parseInt(hex.substr(i, 2), 16);
  }
  return array;
};

const encryptedArray = hexToArray(encrypted);
console.log(encryptedArray); // [94, 92, 95, 92, 91, 62, 92, 95, 93]

Objective-C++解密函数可以使用相应的解密算法进行解密,例如以下是使用异或进行解密的示例代码:

[hidecontent type=“reply”]

#import <Foundation/Foundation.h>

NSData* decrypt(NSData *data, int key) {
    const NSUInteger length = [data length];
    unsigned char *bytes = (unsigned char *)malloc(length);
    [data getBytes:bytes length:length];

    for (NSUInteger i = 0; i < length; i++) {
        bytes[i] ^= key; // 使用异或进行解密
    }

    return [NSData dataWithBytesNoCopy:bytes length:length];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 加密后的数据(16进制数组)
        const unsigned char encryptedArray[] = {0x5e, 0x5c, 0x5f, 0x5c, 0x5b, 0x3e, 0x5c, 0x5f, 0x5d};
        NSData *encryptedData = [NSData dataWithBytes:encryptedArray length:sizeof(encryptedArray)];

        // 解密
        NSData *decryptedData = decrypt(encryptedData, 0x7f);
        NSString *decryptedString = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding];

        // 打印解密后的字符串
        NSLog(@"%@", decryptedString);
    }
    return 0;
}

[/hidecontent]

2.使用开源宏定义域编译

在项目中保存此文件并在最顶部引入,然后将编译环境最低设置为 c++11,然后就可以使用此宏定义头文件了。

Obtuse.h:

[hidecontent type=“reply”]

#include <stdio.h>
#include <stdint.h>

//-------------------------------------------------------------//
// "Malware related compile-time hacks with C++11" by LeFF   //
// You can use this code however you like, I just don't really //
// give a shit, but if you feel some respect for me, please //
// don't cut off this comment when copy-pasting... ;-)       //
//-------------------------------------------------------------//

#if defined(_MSC_VER)
#define ALWAYS_INLINE __forceinline
#else
#define ALWAYS_INLINE __attribute__((always_inline))
#endif

// Usage examples:
void setup()    __attribute__((noinline));
void startAuthentication()  __attribute__((noinline));

#ifndef seed
    // I use current (compile time) as a seed
    // Convert time string (hh:mm:ss) into a number
    constexpr int seedToInt(char c) { return c - '0'; }
    const int seed = seedToInt(__TIME__[7]) +
                     seedToInt(__TIME__[6]) * 10 +
                     seedToInt(__TIME__[4]) * 60 +
                     seedToInt(__TIME__[3]) * 600 +
                     seedToInt(__TIME__[1]) * 3600 +
                     seedToInt(__TIME__[0]) * 36000;
#endif

// The constantify template is used to make sure that the result of constexpr
// function will be computed at compile-time instead of run-time
template <uintptr_t Const> struct
vxCplConstantify { enum { Value = Const }; };

// Compile-time mod of a linear congruential pseudorandom number generator,
// the actual algorithm was taken from "Numerical Recipes" book
constexpr uintptr_t vxCplRandom(uintptr_t Id)
{ return (1013904223 + 1664525 * ((Id > 0) ? (vxCplRandom(Id - 1)) : (/*vxCPLSEED*/seed))) & 0xFFFFFFFF; }

// Compile-time random macros, can be used to randomize execution
// path for separate builds, or compile-time trash code generation
#define vxRANDOM(Min, Max) (Min + (vxRAND() % (Max - Min + 1)))
#define vxRAND()           (vxCplConstantify<vxCplRandom(__COUNTER__ + 1)>::Value)

// Compile-time recursive mod of string hashing algorithm,
// the actual algorithm was taken from Qt library (this
// function isn't case sensitive due to vxCplTolower)
constexpr char   vxCplTolower(char Ch)                { return (Ch >= 'A' && Ch <= 'Z') ? (Ch - 'A' + 'a') : (Ch); }
constexpr uintptr_t vxCplHashPart3(char Ch, uintptr_t Hash) { return ((Hash << 4) + vxCplTolower(Ch)); }
constexpr uintptr_t vxCplHashPart2(char Ch, uintptr_t Hash) { return (vxCplHashPart3(Ch, Hash) ^ ((vxCplHashPart3(Ch, Hash) & 0xF0000000) >> 23)); }
constexpr uintptr_t vxCplHashPart1(char Ch, uintptr_t Hash) { return (vxCplHashPart2(Ch, Hash) & 0x0FFFFFFF); }
constexpr uintptr_t vxCplHash(const char* Str)           { return (*Str) ? (vxCplHashPart1(*Str, vxCplHash(Str + 1))) : (0); }

// Compile-time hashing macro, hash values changes using the first pseudorandom number in sequence
#define HASH(Str) (uintptr_t)(vxCplConstantify<vxCplHash(Str)>::Value ^ vxCplConstantify<vxCplRandom(1)>::Value)

// Compile-time generator for list of indexes (0, 1, 2, ...)
template <uintptr_t...> struct vxCplIndexList {};
template <typename  IndexList, uintptr_t Right> struct vxCplAppend;
template <uintptr_t... Left,      uintptr_t Right> struct vxCplAppend<vxCplIndexList<Left...>, Right> { typedef vxCplIndexList<Left..., Right> Result; };
template <uintptr_t N> struct vxCplIndexes { typedef typename vxCplAppend<typename vxCplIndexes<N - 1>::Result, N - 1>::Result Result; };
template <> struct vxCplIndexes<0> { typedef vxCplIndexList<> Result; };

// Compile-time string encryption of a single character
const char vxCplEncryptCharKey = (const char)vxRANDOM(0, 0xFF);
constexpr char ALWAYS_INLINE vxCplEncryptChar(const char Ch, uintptr_t Idx) { return Ch ^ (vxCplEncryptCharKey + Idx); }

// Compile-time string encryption class
template <typename IndexList> struct vxCplEncryptedString;
template <uintptr_t... Idx> struct vxCplEncryptedString<vxCplIndexList<Idx...> >
{
    char Value[sizeof...(Idx) + 1]; // Buffer for a string

    // Compile-time constructor
    constexpr ALWAYS_INLINE vxCplEncryptedString(const char* const Str)
    : Value{ vxCplEncryptChar(Str[Idx], Idx)... } {}

    // Run-time decryption
    inline const char* decrypt()
    {
        for(uintptr_t t = 0; t < sizeof...(Idx); t++)
        { this->Value[t] = this->Value[t] ^ (vxCplEncryptCharKey + t); }
        this->Value[sizeof...(Idx)] = '\0'; return this->Value;
    }
};

// Compile-time string encryption macro
#define ENCRYPT(Str) (vxCplEncryptedString<vxCplIndexes<sizeof(Str) - 1>::Result>(Str).decrypt())

#ifdef __APPLE__
// Compile-time Objective-c string encryption macro
#define NSSENCRYPT(Str) @(ENCRYPT(Str))
#endif

// Compile-time offset string encryption macro, converts back to uint64_t.
#define ENCRYPTOFFSET(Str) strtoull(ENCRYPT(Str), NULL, 0)

// Compile-time hex string encryption macro, does same as ENCRYPT, but the naming is just more clear.
#define ENCRYPTHEX(Str) ENCRYPT(Str)

[/hidecontent]

用法:

NSSENCRYPT("诚哥博客"); //等同 @"诚哥博客"
ENCRYPT("诚哥博客"); //等同 "诚哥博客"

使用逆向工具,伪代码是看不出来的。当然并非无解。

image-20230221141413559

3.使用开源项目插件等

Obfuscator-iOS

通过模糊处理所有硬编码的安全敏感字符串来保护应用。此库通过模糊处理然后编码为十六进制,将典型的 NSString 硬编码为 C 语言字符串。 当你的应用需要原始的未混淆的 NSString 时,它会动态地将其解码回来。

开源地址: https://github.com/pjebs/Obfuscator-iOS

STCObfuscator

iOS 全局自动化 代码混淆 工具!支持 cocoapod 组件代码一并 混淆,完美避开 hardcode 方法、静态库方法和系统库方法!STCObfuscator 是用来进行 object-c 代码混淆的工具,在模拟器 DEBUG 环境下运行生成混淆宏, 混淆的宏可以在其他环境下进行编译,支持 Cocoapod 代码混淆.

开源地址: https://github.com/chenxiancai/STCObfuscator

混淆前后效果图:

imageimage

obfuscator-llvm

Obfuscator-LLVM 是 2010 年 6 月由伊韦尔东莱班应用科学与艺术大学信息安全小组 ( HEIG-VD ) 发起的一个项目。

该项目的目的是提供 LLVM 编译套件的开源分支,能够通过代码混淆和防篡改提供更高的软件安全性。由于我们目前主要在中间表示(IR) 级别工作,因此我们的工具兼容所有编程语言(C、C++、Objective-C、Ada 和 Fortran)和目标平台(x86、x86-64、PowerPC、PowerPC-64) , ARM, Thumb, SPARC, Alpha, CellSPU, MIPS, MSP430, SystemZ, 和 XCore)目前由 LLVM 支持。

Obfuscator-LLVM 是一个基于 LLVM 框架开发的代码混淆器,其原始仓库地址为: https://github.com/obfuscator-llvm/obfuscator

你可以通过上述地址访问 Obfuscator-LLVM 的原始代码,获取最新版本的源码、文档等信息。同时,Obfuscator-LLVM 也提供了一些文档和示例代码,方便用户了解和使用该工具。

Obfuscator-LLVM 是一个基于 LLVM 框架开发的代码混淆器,它可以帮助开发人员加固和保护他们的软件,使得黑客和逆向工程师更难破解和盗用代码。以下是 Obfuscator-LLVM 混淆器的一些优点:

  1. 代码保护:Obfuscator-LLVM 可以对软件代码进行混淆,从而保护代码免受恶意攻击和盗用。
  2. 抵御反编译:Obfuscator-LLVM 可以通过修改代码的控制流、混淆符号等方式来增加反编译的难度,使得黑客更难破解和分析代码。
  3. 保护软件知识产权:Obfuscator-LLVM 可以帮助软件开发者保护他们的知识产权,避免代码被盗用和滥用。
  4. 提高软件安全性:Obfuscator-LLVM 可以通过增加代码混淆、模糊和随机性等方式来提高软件的安全性和鲁棒性。
  5. 提高软件性能:Obfuscator-LLVM 可以通过对代码进行优化和精简,从而提高软件的性能和效率。

综上所述,Obfuscator-LLVM 混淆器可以帮助软件开发者加固和保护他们的软件,提高软件的安全性、性能和稳定性。

控制流扁平化

#include <stdlib.h>
int main(int argc, char** argv) {
  int a = atoi(argv[1]);
  if(a == 0)
    return 1;
  else
    return 10;
  return 0;
}

展平通道会将此代码转换为以下代码:

#include <stdlib.h>
int main(int argc, char** argv) {
  int a = atoi(argv[1]);
  int b = 0;
  while(1) {
    switch(b) {
      case 0:
        if(a == 0)
          b = 1;
        else
          b = 2;
        break;
      case 1:
        return 1;
      case 2:
        return 10;
      default:
        break;
    }
  }
  return 0;
}

伪控制流

此方法通过在当前基本块之前添加基本块来修改函数调用图。这个新的基本块包含一个不透明的谓词,然后有条件地跳转到原来的基本块。

原始基本块也被克隆并填充了随机选择的垃圾指令。

无BCF

伪造的控制流通过后,我们可能会得到如下流程图:

与BCF

原始 obfuscator 版本现在比较旧,新版使用需要手动移植到新版上使用,而且 linux 上的 llvm 和苹果的还不一样不通用,所以需要的可以自己移植编译,这个混淆效果是这里面最好的,比较全面,之后的混淆都是衍生与此。我自己也移植了 apple-llvm 版本用于使用,编译过程很久,感兴趣的可以自己去试试。

LLVM 介绍

LLVM(Low Level Virtual Machine)是一个开源编译器架构,它被设计成可重用和可扩展的,可以支持多种编程语言和多种操作系统。LLVM 不仅包括编译器的前端和后端,还包括优化器、调试器和模拟器等多种工具和组件。

LLVM 的核心设计思想是将编译器前端和后端分离,前端将源代码转换为 LLVM IR(Intermediate Representation),后端将 LLVM IR 转换为目标机器的代码。这种设计思想使得 LLVM 可以支持多种编程语言和多种目标平台,并且提供了更好的优化和代码生成能力。

LLVM 的优点包括:

  1. 可重用性:LLVM 的组件可以被重用和组合,从而可以构建定制化的编译器和工具链。
  2. 可扩展性:LLVM 可以轻松地支持新的编程语言和新的目标平台,只需要实现相应的前端和后端即可。
  3. 高性能:LLVM 的优化器可以对代码进行多层次的优化,从而提高代码的性能和效率。
  4. 易于调试:LLVM 的调试器和模拟器可以方便地进行代码调试和测试。
  5. 开源:LLVM 是一个开源项目,任何人都可以获取源代码并进行修改和定制。

总之,LLVM 是一个灵活、高效、可扩展和可重用的编译器架构,为编译器和相关工具的开发者提供了一个强大的工具和平台。在编译器、静态分析、动态二进制翻译等领域,LLVM 都有着广泛的应用。

Apple LLVM 介绍

Apple LLVM 是苹果公司基于 LLVM 框架开发的 C/C++/Objective-C 编译器,用于编译和构建 macOS 和 iOS 操作系统以及相关应用程序。LLVM 是一个开放源代码的编译器框架,其源代码可以通过 Github 开源项目进行获取。

苹果公司的 LLVM 源代码也是托管在 Github 上的,你可以通过以下链接获取到最新版本的源代码:

https://github.com/apple/llvm-project

该仓库包含了苹果公司 LLVM 编译器的所有源代码,包括 Clang 编译器、LLDB 调试器、LLVM IR(Intermediate Representation)优化器等多个子项目。如果你想深入了解 LLVM 的实现细节,或者想要为 LLVM 贡献代码,可以考虑访问上述仓库,并参与其中的开发活动。