There are a number of ways to accomplish what you've stated. Generally, more robust techniques, while affording a higher level of protection, will also put more burden on the programmer creating the software. So in approximately increasing order of difficulty, here are some ideas:
Store the data non-contiguously
The simplest approach is to simply not store the data contiguously. That is, store the data in separate pieces and reassemble it at runtime just before you need it. With this as with all of the other techniques, it's generally recommended to keep the value in memory for as short a duration as is practical.
Obfuscate the stored data
There are many ways to obfuscate data. One simple method is to simply XOR with some fixed constant. A more sophisticated approach is to encrypt the data, but unless you have some secure way to store the encryption key, this might not actually offer that much more security. One possibility would be to use a cryptographic hash of the entire program (minus the protected data) as the encryption key. This would largely prevent alteration of the binary as well as providing a non-obvious way to store the key.
Recalculate the data at runtime
If you can avoid storing the data at all, we eliminate the problem of being able to derive it from static analysis. If you are precomputing hashes of certain data for performance reasons, consider doing so at program startup instead of at compile time. Alternatively, if the data is fixed, consider writing a polymorphic generator which could be included at compile time. That is, write a program that takes a fixed constant and generates code which, when run, produces that value without explicitly including it. Then link the generated code with your program. Because the polymorphic generator would be part of the build process rather than part of the runtime, it is much less likely to trigger a A/V warning.
Proof of concept for this idea
I wrote a little program in C++ to more fully demonstrate this technique. Here is the program:
linear.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";
}
}
How it works
This is a very simple program that takes a series of integers as input and creates one linear function per integer. For example, with this command line:
./linear 39181 3802830 938833 -41418699
The program generated the following output:
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));
The assert
s are simply there for documentation and testing. In real usage, if you want to recreate the constant 39181
in your code, you would use generate1(32308)
. If we rearrange the lines into a full program, we get this:
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));
}
Obviously, you could multiply or concatenate these if you need longer numbers or strings, and my choice of linear functions with the random number value range I chose was entirely arbitrary. Feel free to substitute and experiment.
Fetch the data remotely
Depending on the environment, it may be possible to store the data remotely and then fetch it securely via something like HTTPS when and as needed. Be aware that doing this could also mean that even an ordinary network outage or misconfigured firewall would render your software inoperable, but you can decide if that is acceptable for your purposes.