17

I'm sure that many of the middle to high level languages can be reverse engineered. But if a C program can be reverse-engineered, and turned back into editable source code, how do I discourage such a thing?

14
  • 27
    C is actually closer to assembly than higher level languages, meaning it is likely easier to reverse engineer a C program, as there were fewer steps involved in compilation.
    – riwalk
    Commented Mar 7, 2015 at 18:28
  • 55
    If it can be run, it can be reverse engineered. If you don't want it read, don't write it.
    – Blrfl
    Commented Mar 7, 2015 at 18:36
  • 19
    Or think of it another way--you're computer has to be able to run the code. If it couldn't, then you wouldn't have a program. An if the computer can understand it, then a human can understand it. Not much you can do to avoid it.
    – riwalk
    Commented Mar 7, 2015 at 18:36
  • 4
    @Ptolemy: The TRIPS treaty guarantees that computer programs are protected by copyright for at least 50 years. All WTO member states are required to adhere to TRIPS, there are almost 160 signatories. Many other states also recognize copyright for computer programs. Sure, that's by far not all (I believe there are ~220 countries, currently), but it covers most important markets. Commented Mar 7, 2015 at 21:47
  • 15
    @SnazzySanoj As a matter of fact, I've personally reverse-engineered a C application and a C library myself (without IDA, but with objdump and readelf), so I can confidently answer that question with a yes. All it takes is knowledge of the computer architecture for which the code was compiled, knowledge of common compiler idioms, and knowledge of the ABI. What would have made it harder is internal obfuscation, self-modifying code, whole-program optimization (WHOPR) and other such arcana. But against a sufficiently-determined enemy, you cannot stop them. If CPUs can run it, so can I. Commented Mar 7, 2015 at 23:02

9 Answers 9

4

If someone can observe the program in operation, they can reverse-engineer it. Even if you could somehow prevent the user from accessing the internals of the program, they can perform black-box reverse engineering to build up a model of how the program reacts to various inputs.

35

It is impossible to prevent reverse engineering for any program that runs on your computer. But you can make steps that make it harder...

Program languages like Java and .NET are trivial to recreate human source code for, as they are byte code 'compiled'. Meaning the programming language is compiled into a set of tokens that are interpreted by a run time engine or 'virtual machine'. This makes the reverse compilation an achievable task.

Because of this ease of obtaining C# source code for any .NET library, there are several tricks often done to make it harder. One approach is to rewrite the code so that all the class and method names are not meaningful and similar. This makes the C# source code 100% available, but usually unintelligible to a human to understand what is going on in the code.

The next step is to make this harder is to create an encrypted version of the library, with wrapper code that decrypts the byte code library in memory. This makes it harder, but there is nothing stopping a determined person using an assembler debugger to step through the decryption process until the .NET byte code was decompiled and then doing a memory dump to get to the byte codes.


C has a huge advantage in protecting the source code, in that the source is compiled into machine code. This is not fool proof though. To allow developers to step through their code, the compiler will have options that prevent optimisations reorganising the code, and worse still, often add additional data to the executable to provide context to the code that would be useable for decompilation.

Where C code is non-trivial, compiled with optimisations turned on and stripped of all debugging data, trying to recreate understandable source code is beyond the effort/reward for most people.


That said, I learned how to program in assembler as a child by single stepping through the assembler tape loader code on ZX Spectrums designed to make it harder for people to copy, and/or alter the program to give you infinite lives.

Spending three days in a long summer holiday stepping through the code that was transcoding the set of bytes stored on the tape into the modified tape loading code that actually loaded the game seemed like a good use of my time back then, and started me out on a long career as a software developer as an adult.

12
  • 1
    You can write .NET in byte codes (en.wikipedia.org/wiki/List_of_CIL_instructions) and you can even create your own .NET code in your programs using Emit (msdn.microsoft.com/en-us/library/…) however, if you create a combination that does not make sense when run, you can will get an exception thrown. Commented Mar 7, 2015 at 19:02
  • 3
    Of course, just because a tool can't automagically find the real names of variables, doesn't mean a person can't manually trace things to figure out what they represent. They might not ultimately get the easy-to-understand and easy-to-modify source code that the developer has, but they can still get a representation of the logic of the code.
    – cpast
    Commented Mar 7, 2015 at 23:30
  • 4
    Actually, compiling to byte-code is not the sticky point for ease-of-decompilation. It's the fact that said byte-code is chock-full with meta-data enabling (relatively) painless decompilation. There are obfuscators minimizing the amount and usefulness of that meta-data, though they play merry hell with reflection... Commented Mar 8, 2015 at 2:06
  • 2
    There's definitely a mnemonic for every "documented" bit pattern. They may not be unique (multiple possible decodes with equivalent behaviour), and they may not all be documented; even the 6502 had bit patterns which do things which are not enumerated in the manual. It's the other direction which is vaguer; one mnemonic can be multiply encoded and may involve prefix instructions.
    – pjc50
    Commented Mar 8, 2015 at 20:41
  • 2
    .. but generally binary-to-readable-ASM is trivial, and attempting to reassmble it will give you functionally identical code. No doubt someone will supply some heavily-prefixed Intel opcode which comes out a different length when laundered like this, but it's going to be rare.
    – pjc50
    Commented Mar 8, 2015 at 21:01
15

Any program can be reverse engineered, because of the very simple principle that anything that a computer can execute has to be in a format where a person that understands machine code can read. (With the proper tools of course.)

The real question is, how easy is it to reverse engineer? C is trivial to decompile; I've seen some surprisingly clean C code generated from decompilation tools. But because C doesn't have the same rich metadata as managed languages, you lose a lot of useful information, such as names of variables and data types, that help out with the task of reverse engineering. (Then again, most managed code systems have an "obfuscator" tool available that mangles the metadata to make such tasks more difficult on the reverse engineer. So the difference may not necessarily be all that big.)

6
  • 12
    The essence is: computers are much dumber than humans. If the CPU can understand, so can you. And it has to understand in order to run it. Commented Mar 7, 2015 at 21:40
  • 4
    @JörgWMittag there's another saying, attributed to Kernighan: "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it."
    – Arc
    Commented Mar 8, 2015 at 1:37
  • 5
    @Archimedix - but there's probably someone out there who is twice as smart as you and CAN reverse-engineer and debug your code 😊 Commented Mar 8, 2015 at 2:01
  • 1
    @Carson63000 my point was that no matter how convoluted your spaghetti con tutti code is, it's going to run somehow, even if your brain would crash & bang on the desktop the moment you look at it. And if you're smart enough, it might be hard finding some twice your genius just to debug your output... and they might not want to 😁
    – Arc
    Commented Mar 8, 2015 at 2:15
  • 1
    @JörgWMittag Have a bit of a browse on CodeGolf:SE and you may reconsider. The raw instructions must be recoverable, but the meaning and structure are harder to resolve.
    – Nathan
    Commented Mar 8, 2015 at 12:18
11

Many anti-debugging techniques can be used to make reverse-engineering a hell of a task, but it's not impossible.

Malware authors use them regularly, and many are applicable to any native machine code, including that compiled from C.

A simple example that was really popular in the early years was inserting thousands of breakpoints to make running to a particular code point tedious, as you have to remove all those breakpoints first.

Other methods are polymorphism, self-modifying code and run-time decryption/encryption or cache-based hacks to conceal the execution paths taken as long as possible.

Additionally, you can use traditional obfuscation techniques such as creating decoy code, opaque value construction, extra jumps and pseudo-conditions to make understanding code harder.

This works for any language, not just managed .NET, Java or PHP where reverse-engineering is normally trivial, but also for C/C++.

Malware authors provide the best examples, e.g. Rombertik as analyzed by Cisco.

Hardening against rev-engineering is usually an economic problem:

  • how easy is it to add protections
  • how much does it hinder maintenance
  • how does it affect resource use and performance (e.g. memory, CPU use),
  • how compatible is it (does it work anytime, anywhere, may it trigger an anti-virus),
  • how hard is it to break them

This ends in the question: Is it worth it?

Are the savings higher than the expenses?
Are the costs for breaking higher than the potential gains from rev-engineering?

There are obfuscators available for popular languages to automate adding protection measures to your programs, but their quality varies.

4

You can reverse engineer any program, the result will not be the original source code with comments, descriptive variable names, structure etc. but its functionality will be the same. You cannot prevent this, you can make it more difficult but you cannot not prevent it because at the end of day it will be instructions that the CPU will understand and thus you can interpret. The best way IMHO to prevent reverse engineering/pirate copy is to update your program frequently by adding more functionality, you will then reach a point when the effort to reverse engineer is too much of a hassle compared to buying/writing it yourself.

3

Don't let anyone else access the program. Run it in a secure* server you control, and let users interface with it only by providing the input over the network and receiving back the output.

See http://en.wikipedia.org/wiki/Software_as_a_service

The downside is that you now have to manage the server.

*Securing a server is far from trivial. For truly high security, hire a professional.

1
  • Assuming the server is not accessible to anyone you don't want accessing it (technically impossible itself), this is the right answer. You can still make a program that gives the same output based on the same input as the original program, so for all intents and purposes it will be the same program, but if you literally can not see the program being run, anything you create might as well have been written in a different language. In fact, if you don't know the language used (the OP said C but the user won't know that), it might have to be.
    – trysis
    Commented Mar 8, 2015 at 18:32
3

As the other answers say, if you can see the binary you can reverse engineer the function of the program, although possibly with difficulty. The cutting edge is therefore to prevent the user from getting at the binary.

Old arcade systems did this by storing the program or critical parameters in battery-backed RAM linked to tamper switches. Open the case and the program evaporates. Modern iPhones use ARM "TrustZone" to load a microkernel from encrypted bootrom with privileged hardware access; this is used for the fingerprint sensor and some of the payment functions.

(Of course, this means the phone may be factory-compromised with no way for the user to detect or recover..)

Myself I've reported a couple of bugs in manufacturer firmware from ARM disassembly, and considered how much work it might be to write a decompilation assistant program.

2

Simple, at least in the U.S. You just put an anti-reverse-engineering clause in the program's EULA, and threaten to sue the pants off of anyone who cracks it (this is no joke; the DMCA actually lets you do so).

2
  • Can't you threaten to sue the pants off of anyone, whether they did something illegal or not?
    – user20574
    Commented Mar 8, 2015 at 22:08
  • Maybe, but you'd lose credibility, and thus it wouldn't work all that well.
    – Atsby
    Commented Mar 8, 2015 at 22:09
2

To make C almost impossible to reverse engineer? It is possible - write your C such that the entire logic is dependent on the input AT RUNTIME (perhaps now you have to protect your input, which hold the secret to your logic in the C program). So reading the C source code really has no meaning.

How? One way: Using lots of function pointers. So reading:

a->function1();
c->function2();

it is impossible to guess what function1() does, as it is a dynamically allocated pointer assigned only during runtime - depending on the input.

If you create lots of basic functions like the above, and then dynamically construct your logic by arranging the input token, it is possible to implement any logic.

In assembly language, the analogous equivalent is called "Return-oriented programming" - where you can construct almost any logic possible using carefully selected hexadecimal number from the original program:

http://en.wikipedia.org/wiki/Return-oriented_programming

In a way, what I am proposing is something like an "interpreter": eg, A javascript interpreter program itself, being written in C, can behave in many different ways, depending on the "javascript" program as an input. So there is no point reversing the interpreter itself - as the "javascript" input program is the ultimate logic machine.

Update:

Assuming you have the input, to RE it dynamically can be very difficult as well, because if you use software breakpoints (assuming 0xcc instrumented into the code) then by self-checksumming the code area, plus checksum of many randomly selected small block, and compare the checksum with original uninstrumented code, you can easily detect any software breakpoint. And if you use hardware breakpoint? well, timing analysis between any randomly selected pieces of code will easily detect that someone is trying to analyse the code. And if you use a software emulator like Boch/pintool/Intel branch tracing that does not use software breakpoint, and can deceive the clock enough to bypass timing analysis, well, it is possible it can be done. But perhaps the amount of data to analyse may amount many gigs of data. Not easy.

But to be able to do the above two tasks, it is no longer C - you have to take the binary output and instrument it with checksum-checking codes, plus timing analysis code between any two points - all these inserted randomly as well.

A real world example of binary that is difficult to analyse (or taken years and big Anti-Virus companies are still trying to understand it): Stuxnet. And this binary (not sure?) does not connect to internet, run standalone.

7
  • 2
    I'm afraid that your scheme won't work; Your interpreter depends on its inputs at runtime, sure, but 1) You can R.E. the interpreter and 2) The input is available to you, and therefore you can RE the combination of it and the interpreter. If it weren't, then you're comparing apples and oranges: It would be as though you were asked to RE an executable whose data and text sections had been removed. The two cases are not comparable; Either the user has the program and can RE it or he doesn't and can't. All you've done is splitting it across 2+ files; But the user can capture and RE them all. Commented Mar 8, 2015 at 16:07
  • 1. of course you are right, which is what I had preempted by say "input is the the secret to your logic". so in my case, the input data can be changed dynamically at runtime, encoded / decoded with endless combination, making RE very difficult. 2. as i had updated above with edit - dynamic RE assuming you have the input can be very very difficult as well.
    – Peter Teoh
    Commented Mar 9, 2015 at 0:14
  • OP wishes to provide an application to somebody that cannot be RE'd. I don't know what you mean by "input changes at runtime", but it matters not; At some point, a software release will be made to the customer. This release, containing everything necessary to run the application, can be copied, saved, and endlessly extracted and re-run. It's absolutely of no import that the application is in one or two parts, or that part 1 is an interpreter that runs the other part 2 as input and mutates it; The user has and controls both. For all intents and purposes, they are, combined, the program. Commented Mar 9, 2015 at 0:47
  • 1
    Well, sir, it is always a time-space complexity problem. If you said it can be replayed, you are already assuming you know what to replay. But in real life there can be so many possible inputs: assuming the network transaction is client-initiated, then so many possibilities of random input sources are possible: keyboard buffer, clock, interrupts, and 1000 others input sources. If you know and replay all the possible input sources, you win. Eg interrupts captured from all the different I/O ports, matching exactly with the server-generated one-time-token,while replaying net-traffic.
    – Peter Teoh
    Commented Mar 9, 2015 at 3:04
  • 1
    I concede that an application can examine enough variables to determine whether it is being debugged to render the job of the reverse engineer miserable. How cleverly these are concealed makes a difference; If it's an explicit isLicenseValid(), I punch a few bytes to make it return true; But as you do point out, it's nearly impossible to replay with 100% accuracy everything. It would suffice for one information source to be inconsistent, and one cleverly-hidden check with a result taking effect at an unexpected place elsewhere and a few seconds later, to fail to reverse-engineer correctly. Commented Mar 9, 2015 at 3:10

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