11

Let's say I've some function(i.e. hash function), that generates value from input seed and some precomputed hash values, that are stored somewhere in binary. What are the possible approaches for:

  1. Protect string data against dumping
  2. Protect hash algorithm from being reverse engineered for a while

I understand that the goal of complete algorithm concealment from reverser is impossible but what technique will raise the efforts to do this?

My thoughts on this topic:

  1. Protect hash strings with encryption macro
  2. Obfuscating target function using some king of obfuscation/poly/metamorphic engine in order to prevent easy algorithm recovery(can lead to AV false positive but should not harm in general)
  3. Roughly generate new hash function and values for each copy of program(kind of hard to implement and maintain)

Maybe anybody have better concepts that will suit my goal and be so kind to post it here.

P.S. please do not advice to use packers/protectors. False positive AV does not cost couple functions algorithm concealment.

UPD

Here is the implementation if string protection written in C++. I don't know if this solution be useful for other people but it's worth mentioning though.

2 Answers 2

9

I would start with not have critical strings in memory in linear manner

Instead each character can be offseted by some offset like by table mixing all the strings together. For example you got 0123456789 if you store them in memory like this:

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

where x is anything or another string ... it is pretty hard to get the meaning.

Next level is even better

//---------------------------------------------------------------------------
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]];
    }
//---------------------------------------------------------------------------

So create offset table on runtime which will randomly map linear string offsets to nonlinear offsets. This is very hard to decode because it mixes all strings together (and if they are hashes) then without stepping is almost impossible to decode this (not knowing what it does of coarse).

Here an example of how to use it and how output looks:

// [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);

And the results are like this:

output

On the left is decoded text picdec.txt and on the right is encoded text picenc.txt this is how the strings looks in memory.

On top of this you can add any kind of encryption (not only to text itself but to offset table also ...)

2
  • Thanks for sharing your techniques! Can you please expand your second part of answer? If I got you correct all values are stored in large buffer and each copy of application is receive its own copy of rng code and the seed. So the full dynamic string/dword generation is achieved and there is no way to compute in statically without stepping through all rng?
    – see ya
    May 4, 2015 at 21:39
  • @seeya updated my answer see the example (used my image to ASCII Art conversion as source input to emphasize the position/pattern distortion)
    – Spektre
    May 5, 2015 at 8:28
6

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 asserts 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.

4
  • For me third method sounds extremely interesting and promising. Can you please either expand it with more possible implementation details or even ready-made solution of this technique if one exists?
    – see ya
    May 4, 2015 at 21:44
  • I've updated my answer with code and a sample run, showing how this could work.
    – Edward
    May 5, 2015 at 16:06
  • Thank you! I wish I could accept your answer too. Anyway take my upvote.
    – see ya
    May 6, 2015 at 7:55
  • :) No problem! It was a fun problem to think about.
    – Edward
    May 6, 2015 at 13:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.