保护以二进制形式存储的数据

逆向工程 二元分析 混淆 静态分析 加密
2021-06-25 04:17:50

假设我有一些函数(即散列函数),它从输入种子和一些预先计算的散列值生成值,这些值以二进制形式存储在某处。有哪些可能的方法:

  1. 保护字符串数据免于转储
  2. 暂时保护哈希算法不被逆向工程

我知道从反向器中完全隐藏算法的目标是不可能的,但是什么技术会增加这样做的努力?

我对这个话题的看法:

  1. 使用加密宏保护哈希字符串
  2. 使用一些混淆/poly/变形引擎之王混淆目标函数,以防止容易的算法恢复(可能导致AV误报,但一般不会造成伤害)
  3. 为每个程序副本粗略地生成新的哈希函数和值(有点难以实现和维护)

也许有人有更好的概念来满足我的目标,并且很友好地把它张贴在这里。

PS 请不要建议使用封隔器/保护器。误报 AV 不消耗耦合函数算法隐藏。

UPD

是用 C++ 编写的字符串保护的实现。我不知道这个解决方案对其他人是否有用,但值得一提。

2个回答

我会以线性方式在内存中没有关键字符串

相反,每个字符都可以通过将所有字符串混合在一起的表来偏移一些偏移量。例如,0123456789如果您像这样将它们存储在内存中,则会得到

|0x1xx2xxx3xxxx4|
|xxxxx5xxxxxx6xx|
|xxxx7xxxxxxxx8x|
|xxxxxxxx9xxxxxx|

x任何东西或其他字符串在哪里......很难理解含义。

下一级更好

//---------------------------------------------------------------------------
const int N=16384;
char txt[N];    // my texts
int  ofs[N];    // randomized offsets
//---------------------------------------------------------------------------
void ofs_init()                 // randomize offsets
    {
    int i,j,a;
    // here set RandSeed if you need to reproduce same behavior (like for using with files)
    for (i=0;i<N;i++) ofs[i]=i;
    for (i=0;i<N;i++) { j=Random(N); a=ofs[i]; ofs[i]=ofs[j]; ofs[j]=a; }
    }
//---------------------------------------------------------------------------
void ofs_write(int adr,char a) // linear to ofset encoding
    {
    txt[ofs[adr]]=a;
    }
//---------------------------------------------------------------------------
char ofs_read(int adr) // linear to ofset decoding
    {
    return txt[ofs[adr]];
    }
//---------------------------------------------------------------------------

因此,在运行时创建偏移表,它将随机将线性字符串偏移映射到非线性偏移。这很难解码,因为它将所有字符串混合在一起(如果它们是散列),那么没有步进几乎不可能解码它(不知道它对粗略的作用)。

这是一个如何使用它以及输出外观的示例:

// [encode] + save to file for output comparison
// AnsiString s holds oll my texts (an image in this case)
Randomize();
ofs_init();
for (i=0;i<s.Length();i++) ofs_write(i,s[i+1]);
i=FileCreate("picenc.txt"); FileWrite(i,txt,N); FileClose(i);
// [decode] you should not decode critical strings to memory rather decode each char only when needed instead
// I decode the whole thing to save to file for output comparison
for (i=0;i<s.Length();i++) s[i+1]=ofs_read(i);
i=FileCreate("picdec.txt"); FileWrite(i,s.c_str(),s.Length()); FileClose(i);

结果是这样的:

输出

左边是解码文本picdec.txt,右边是编码文本,picenc.txt这就是字符串在内存中的样子。

最重要的是,您可以添加任何类型的加密(不仅是文本本身,还可以添加偏移表......)

有多种方法可以实现您所说的。通常,更强大的技术在提供更高级别的保护的同时,也会给创建软件的程序员带来更多负担。因此,按照难度大致递增的顺序,这里有一些想法:

非连续地存储数据

最简单的方法是简单地不连续存储数据。也就是说,将数据存储在不同的部分,并在运行时在您需要它之前重新组合它。与所有其他技术一样,通常建议将值保留在内存中的时间尽可能短。

混淆存储的数据

有很多方法可以混淆数据。一种简单的方法是简单地与一些固定常数进行异或。一种更复杂的方法是加密数据,但除非您有某种安全的方法来存储加密密钥,否则这实际上可能无法提供更高的安全性。一种可能性是使用整个程序(减去受保护数据)的加密散列作为加密密钥。这将在很大程度上防止更改二进制文件并提供一种非显而易见的方式来存储密钥。

在运行时重新计算数据

如果您可以完全避免存储数据,我们就消除了能够从静态分析中导出数据的问题。如果出于性能原因预先计算某些数据的哈希值,请考虑在程序启动时而不是在编译时进行。或者,如果数据是固定的,请考虑编写一个可以在编译时包含的多态生成器。也就是说,编写一个采用固定常量并生成代码的程序,该代码在运行时会生成该值,而无需明确包含该值。然后将生成的代码与您的程序链接。由于多态生成器将成为构建过程的一部分而不是运行时的一部分,因此触发 A/V 警告的可能性要小得多。

这个想法的概念证明

我用 C++ 编写了一个小程序来更全面地演示这种技术。这是程序:

线性.cpp

#include <iostream>
#include <cstdlib>
#include <random>

int main(int argc, char *argv[])
{
    std::random_device rd;
    std::uniform_int_distribution<> r{-32768,32767};

    for (int i=1; i < argc; ++i) {
        int y = std::atoi(argv[i]);
        int x;
        for (x=r(rd); x==0; x= r(rd));  // make sure x!=0
        int m = r(rd);
        int b = y-m*x;
        std::cout << "int generate" << i << "(int x) { return x * " << m << " + " << b << "; }\n";
        std::cout << "\tassert(" << y << " == generate" << i << "(" << x << "));\n";

    }
}

怎么运行的

这是一个非常简单的程序,它将一系列整数作为输入,并为每个整数创建一个线性函数。例如,使用此命令行:

./linear 39181 3802830 938833 -41418699

该程序生成了以下输出:

int generate1(int x) { return x * -5646 + 182450149; }
    assert(39181 == generate1(32308));
int generate2(int x) { return x * -14922 + 10696794; }
    assert(3802830 == generate2(462));
int generate3(int x) { return x * -15424 + -320805807; }
    assert(938833 == generate3(-20860));
int generate4(int x) { return x * -8144 + -127093579; }
    assert(-41418699 == generate4(-10520));

asserts为只是那里的文档和测试。在实际使用中,如果要39181在代码中重新创建常量,可以使用generate1(32308). 如果我们将这些行重新排列成一个完整的程序,我们会得到:

tryme.cpp

int generate1(int x) { return x * -5646 + 182450149; }
int generate2(int x) { return x * -14922 + 10696794; }
int generate3(int x) { return x * -15424 + -320805807; }
int generate4(int x) { return x * -8144 + -127093579; }
#include <cassert>
int main() {
    assert(39181 == generate1(32308));
    assert(3802830 == generate2(462));
    assert(938833 == generate3(-20860));
    assert(-41418699 == generate4(-10520));
}

显然,如果您需要更长的数字或字符串,您可以将它们相乘或连接,并且我选择的具有随机数值范围的线性函数完全是任意的。随意替换和试验。

远程获取数据

根据环境,可以远程存储数据,然后在需要时通过 HTTPS 之类的方式安全地获取数据。请注意,这样做也可能意味着即使是普通的网络中断或错误配置的防火墙也会导致您的软件无法运行,但您可以决定这是否符合您的目的。