8

The compiler adds system information to the output/object file that is created on compilation.

  • Is there any compiler option that can prevent this information from being added?
  • Can the compiler signature be completely removed so as to make detection of the compiler used hard/improbable?
  • I'm torn. It's not an exact duplicate of this question but there's a lot of common ground, and the answers are going to be almost the same... – Igor Skochinsky Mar 23 '13 at 4:41
  • 2
    @IgorSkochinsky, correct me if I'm wrong, but none of the answers in the question you reference discuss how to remove or obfuscate the compiler attributes, but instead discussed that they existed. – amccormack Mar 23 '13 at 5:26
8

My answer here is specific to common C/C++ compilers, but the principles behind the answer generalize to other scenarios.

Compiler differences manifest themselves in many ways, some of them very subtle. If it was strictly a matter of attributes in the executable header, then we could easily imagine rewriting said header. However, each compiler has its own unique flavor of generated code (and which varies depending upon the optimization level). Each compiler has its own default standard library (which often includes telltale strings), and different default boilerplate code at the executable's entrypoint. There are other differences such as: optimizations used in one compiler that are not used in another; the default calling convention could differ; the code generated for function prologues is noticeably different on gcc and MSVC (for example); different compilers have different code sequences and data structures for exception handling; and many more examples. I would say that it is practically impossible to disguise which compiler produced a given executable.

4

To answer your first question: No, I don't think there are any compiler options for popular compilers that allow you to keep it from adding its compiler artifacts.

Part of the problem in this is explored by @Syzygy's answer: compilers generate, sometimes, very different instructions.

More than this though, knowing the artifacts left behind by different compilers allows you to help make it difficult to distinguish what compiler was used. For example, imagine compiling your Windows executable with mingw, but then adding a RICH header to it. This is a contrived example, but by adding this artifact of the MSVC compiler to the mingw output would probably confuse some automated tools.

4

Many of the previous answers have noted that this is impossible because the compiler will always leave artifacts behind. I decided to do a little case study using a program on Linux, although the idea is transferable.

For example, I created a small "Hello World!" file in C:

#include <stdio.h>

int main(void) {
    puts("Hello World!");
    return 0;
}

Then I compiled it and used hexdump -C on the results. The following except pretty clearly identifies the compiler!

00001020  47 43 43 3a 20 28 55 62  75 6e 74 75 2f 4c 69 6e  |GCC: (Ubuntu/Lin|
00001030  61 72 6f 20 34 2e 36 2e  33 2d 31 75 62 75 6e 74  |aro 4.6.3-1ubunt|
00001040  75 35 29 20 34 2e 36 2e  33 00 00 2e 73 79 6d 74  |u5) 4.6.3...symt|
00001050  61 62 00 2e 73 74 72 74  61 62 00 2e 73 68 73 74  |ab..strtab..shst|
00001060  72 74 61 62 00 2e 69 6e  74 65 72 70 00 2e 6e 6f  |rtab..interp..no|
00001070  74 65 2e 41 42 49 2d 74  61 67 00 2e 6e 6f 74 65  |te.ABI-tag..note|
00001080  2e 67 6e 75 2e 62 75 69  6c 64 2d 69 64 00 2e 67  |.gnu.build-id..g|
00001090  6e 75 2e 68 61 73 68 00  2e 64 79 6e 73 79 6d 00  |nu.hash..dynsym.|
000010a0  2e 64 79 6e 73 74 72 00  2e 67 6e 75 2e 76 65 72  |.dynstr..gnu.ver|
000010b0  73 69 6f 6e 00 2e 67 6e  75 2e 76 65 72 73 69 6f  |sion..gnu.versio|
000010c0  6e 5f 72 00 2e 72 65 6c  61 2e 64 79 6e 00 2e 72  |n_r..rela.dyn..r|
000010d0  65 6c 61 2e 70 6c 74 00  2e 69 6e 69 74 00 2e 74  |ela.plt..init..t|
000010e0  65 78 74 00 2e 66 69 6e  69 00 2e 72 6f 64 61 74  |ext..fini..rodat|
000010f0  61 00 2e 65 68 5f 66 72  61 6d 65 5f 68 64 72 00  |a..eh_frame_hdr.|
00001100  2e 65 68 5f 66 72 61 6d  65 00 2e 63 74 6f 72 73  |.eh_frame..ctors|
00001110  00 2e 64 74 6f 72 73 00  2e 6a 63 72 00 2e 64 79  |..dtors..jcr..dy|
00001120  6e 61 6d 69 63 00 2e 67  6f 74 00 2e 67 6f 74 2e  |namic..got..got.|
00001130  70 6c 74 00 2e 64 61 74  61 00 2e 62 73 73 00 2e  |plt..data..bss..|
00001140  63 6f 6d 6d 65 6e 74 00  00 00 00 00 00 00 00 00  |comment.........|

Running strip -R .comment partially helps, and removes explicit mention of GCC completely, but there are still some tell-tale signs:

00000260  47 4e 55 00 00 00 00 00  02 00 00 00 06 00 00 00  |GNU.............|
00000270  18 00 00 00 04 00 00 00  14 00 00 00 03 00 00 00  |................|
00000280  47 4e 55 00 5f 8a 1b 97  01 5a ac d7 93 fb 96 29  |GNU._....Z.....)|
* * *
00000310  00 00 00 00 00 00 00 00  00 5f 5f 67 6d 6f 6e 5f  |.........__gmon_|
00000320  73 74 61 72 74 5f 5f 00  6c 69 62 63 2e 73 6f 2e  |start__.libc.so.|
00000330  36 00 70 75 74 73 00 5f  5f 6c 69 62 63 5f 73 74  |6.puts.__libc_st|
00000340  61 72 74 5f 6d 61 69 6e  00 47 4c 49 42 43 5f 32  |art_main.GLIBC_2|
00000350  2e 32 2e 35 00 00 00 00  02 00 02 00 00 00 00 00  |.2.5............|
* * *
00001020  00 2e 73 68 73 74 72 74  61 62 00 2e 69 6e 74 65  |..shstrtab..inte|
00001030  72 70 00 2e 6e 6f 74 65  2e 41 42 49 2d 74 61 67  |rp..note.ABI-tag|
00001040  00 2e 6e 6f 74 65 2e 67  6e 75 2e 62 75 69 6c 64  |..note.gnu.build|
00001050  2d 69 64 00 2e 67 6e 75  2e 68 61 73 68 00 2e 64  |-id..gnu.hash..d|
00001060  79 6e 73 79 6d 00 2e 64  79 6e 73 74 72 00 2e 67  |ynsym..dynstr..g|
00001070  6e 75 2e 76 65 72 73 69  6f 6e 00 2e 67 6e 75 2e  |nu.version..gnu.|
00001080  76 65 72 73 69 6f 6e 5f  72 00 2e 72 65 6c 61 2e  |version_r..rela.|
00001090  64 79 6e 00 2e 72 65 6c  61 2e 70 6c 74 00 2e 69  |dyn..rela.plt..i|
000010a0  6e 69 74 00 2e 74 65 78  74 00 2e 66 69 6e 69 00  |nit..text..fini.|
000010b0  2e 72 6f 64 61 74 61 00  2e 65 68 5f 66 72 61 6d  |.rodata..eh_fram|
000010c0  65 5f 68 64 72 00 2e 65  68 5f 66 72 61 6d 65 00  |e_hdr..eh_frame.|
000010d0  2e 63 74 6f 72 73 00 2e  64 74 6f 72 73 00 2e 6a  |.ctors..dtors..j|
000010e0  63 72 00 2e 64 79 6e 61  6d 69 63 00 2e 67 6f 74  |cr..dynamic..got|
000010f0  00 2e 67 6f 74 2e 70 6c  74 00 2e 64 61 74 61 00  |..got.plt..data.|
00001100  2e 62 73 73 00 00 00 00  00 00 00 00 00 00 00 00  |.bss............|

I decided to see what would happen if I replaced the last portion of this code with null bytes. The program still worked fine, but this decreases portability to other operating systems.

If you try to replace GNU with another compiler, say, clang, it still works fine. Although it may confuse the reverse engineer, it certainly isn't that difficult to notice.

Finally, I tried to see what happens if I removed any trace of GLIBC...

./a.out: ./a.out: no version information available (required by ./a.out)
./a.out: relocation error: ./a.out: symbol , version  not defined in file  with link time reference
  • "artifacts" don't include just strings. Even if you remove all strings, the code remains, and often can point to the compiler used quite definitely. – Igor Skochinsky Mar 29 '13 at 19:29
  • Although your own findings were interesting, Igor is right. It's not just about strings, but also portions of code that the compiler inserts in your final binary. You should reverse engineer it and go redefining instruction by instruction until you completely remove all the characteristics. Try to create another version of your program, but instead of puts() use i.e. scanf(), you'll notice that they have some characteristics in common. – jyz Jul 16 '13 at 1:12
2

Short answer: no. I don't know of any compiler that has a "stealth mode" switch and if it would have the end result would IMO just be another signature for the same compiler.

As @omghai2u suggest you can manually modify the binary and test it against automated tools but I don't think it will help much.

A better approach would perhaps be to use an exe packer/protector. Although an experienced RE can probably unpack it, it still means a lot of work and knowledge. So at least it's a first line of defense.

  • 2
    So he packs his exe, you unpack it and you're left with the original compiler artifacts in many cases. By adding in false compiler artifacts it, if done enough and correct, should make it impossible to determine which compiler was really used. A first line of RE defense is to pack, sure maybe (personally, I disagree); but certainly not to hide compiler signature from analysis. – omghai2u Mar 23 '13 at 13:17
1

You can try:

  • To use the strip and the sstrip command to remove some traces.
  • To use weird compilations options
  • To use weird (like tcc) or old compilers
  • To use some packers

But there is no silver bullet. Maybe you should question yourself about the why aspect of your question instead of the how

  • 1
    Is it the sstrip from ElfKickers that you are mentionning ? (because I didn't find a package for my Debian) – perror Jul 16 '13 at 7:26
  • Yes it is. But feel free to open a ticket to get them included in Debian ;) – jvoisin Jul 16 '13 at 8:20
  • I can compile it for me, no package needed. ;-) – perror Jul 16 '13 at 10:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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