I looked at two ways so far. Both did not convince me in regard to false positives or false negatives:

  1. Using strings: How to retrieve the GCC version used to compile a given ELF executable?

  2. Using the linker version field in the PE header: Can I tell what version of Visual Studio was used to build a DLL by examining the DLL itself?

What is a good heuristic to tell if a PE executable was compiled with VisualStudio/gcc ?

For example, are there certain strings, header values, sections, imports, etc. that allow to differentiate one from the other? The exact compiler versions and used compiler flags do not matter...

I am also not expecting malicious executables.


There‘s a tool fort that: peid. The format used by peid is also the de facto standard format for detection of compilers and runtime packers (=programs that modify compiled programs). While peid seems to no longer be actively developed you can find up to date third party signature files for peid on the net.

And of course by looking at the peid signatures you can see what they use to identify the compiler. It‘s mostly the code at the entry point, i.e. the instructions that are run before your main() or WinMain() are even called.

| improve this answer | |

While not a definitive way of determining if GCC OR MSVC (Visual Studio) was used, the presence of the Rich header does determine whether Microsoft's link.exe (MS VC Toolset's linker) was used. (Note: Newer Visual Studio also supports building with clang)

I get that it's officially undocumented, but it's arguably the most publicly well-known and documented undocumented PE structure.

Link.exe always inserts the Rich header, there's no way to tell it not to. There's even a request on visualstudio.com to add a switch to remove it, but the official response is that there's no way currently: https://developercommunity.visualstudio.com/idea/740443/add-linker-option-to-strip-rich-stamp-from-exe-hea.html. They are also likely never going to remove this header even with "developer privacy" as the stated concern because the information contained is build environment related (not personally identifiable usually) and is currently a very popular way to loosely link malware attribution.

Conversely, MinGW, GCC, and others do not insert this header.

If you want some python code to detect the presence of the Rich header (the pefile module is packed full of features to also parse and manipulate headers):

from pefile import PE

pe = PE('/path/to/file.exe')

rich_header = pe.RICH_HEADER

return rich_header is not None

If rich_header is None then there is no header present, otherwise, this contains the header and its values.

| improve this answer | |

Additional to PE detection tools (like PEiD, Detect it easy ,Etc) there is some especial code patterns for GCC and MSVC for example GCC use MOVinst instead of PUSH inst for pushing a value on stack.

| improve this answer | |

The best heuristic would be code. I would create a hello world test file and compile it with the compilers I was interested in identifying.

Load them in the free version of IDA (or Pro if you've got it), and then examine the instructions at the entry point. Matching on compiler code at the entry point is a sure fire was of getting it right. You may have to try a few versions of the same compiler to ensure the code you've chosen to match is consistent.

| improve this answer | |

If you use WinDbg, an easy way to tell might be to simply do a search for 0x00400000 (or whatever the image base address is) through every code page.

I don't know why GCC does this but apparently, it seems to make a habit of (unnecessarily?) referencing the image headers. For example, the following code can be found in Simutrans version 120.2.2 r8163 which is compiled by GCC:

00401002:    lea     esi,[esi]
00401009:    lea     edi,[edi]
00401010:    sub     esp,1Ch
00401013:    xor     eax,eax
00401015:    cmp     word ptr [image00400000 (00400000)],5A4Dh  <--- reference to the MS-DOS Stub
0040101e:    mov     dword ptr [image00400000+0x59c7ec (0099c7ec)],1
00401028:    mov     dword ptr [image00400000+0x59c7e8 (0099c7e8)],1
00401032:    mov     dword ptr [image00400000+0x59c7e4 (0099c7e4)],1
0040103c:    mov     dword ptr [image00400000+0x59c490 (0099c490)],1
00401046:    je      image00400000+0x10b0 (004010b0)
00401048:    mov     dword ptr [image00400000+0x4b8008 (008b8008)],eax
0040104d:    mov     eax,dword ptr [image00400000+0x59c7f8 (0099c7f8)]
00401052:    test    eax,eax
00401054:    je      image00400000+0x10a0 (004010a0)
00401056:    mov     dword ptr [esp],2
0040105d:    call    image00400000+0x2c0c38 (006c0c38)
00401062:    mov     dword ptr [esp],0FFFFFFFFh
00401069:    call    image00400000+0x2a2ef0 (006a2ef0)
0040106e:    mov     edx,dword ptr [image00400000+0x59c808 (0099c808)]
00401074:    mov     dword ptr [image00400000+0x59de98 (0099de98)],eax
00401079:    mov     dword ptr [image00400000+0x59de9c (0099de9c)],eax
0040107e:    mov     eax,dword ptr [image00400000+0x59e6fc (0099e6fc)]
00401083:    mov     dword ptr [eax],edx
00401085:    call    image00400000+0x2a2be0 (006a2be0)
0040108a:    cmp     dword ptr [image00400000+0x3d0708 (007d0708)],1
00401091:    je      image00400000+0x1100 (00401100)
00401093:    xor     eax,eax
00401095:    add     esp,1Ch
00401098:    ret
00401099:    lea     esi,[esi]
004010a0:    mov     dword ptr [esp],1
004010a7:    call    image00400000+0x2c0c38 (006c0c38)
004010ac:    jmp     image00400000+0x1062 (00401062)
004010ae:    xchg    ax,ax
004010b0:    mov     edx,dword ptr [image00400000+0x3c (0040003c)]
004010b6:    cmp     dword ptr image00400000 (00400000)[edx],4550h
004010c0:    lea     ecx,image00400000 (00400000)[edx]
004010c6:    jne     image00400000+0x1048 (00401048)
004010c8:    movzx   edx,word ptr [ecx+18h]
004010cc:    cmp     dx,10Bh
004010d1:    je      image00400000+0x1112 (00401112)
004010d3:    cmp     dx,20Bh
004010d8:    jne     image00400000+0x1048 (00401048)
004010de:    cmp     dword ptr [ecx+84h],0Eh
004010e5:    jbe     image00400000+0x1048 (00401048)
004010eb:    mov     edx,dword ptr [ecx+0F8h]
004010f1:    xor     eax,eax
004010f3:    test    edx,edx
004010f5:    setne   al
004010f8:    jmp     image00400000+0x1048 (00401048)
004010fd:    lea     esi,[esi]
00401100:    mov     dword ptr [esp],offset image00400000+0x2a2e70 (006a2e70)
00401107:    call    image00400000+0x2a2e60 (006a2e60)
0040110c:    xor     eax,eax
0040110e:    add     esp,1Ch
00401111:    ret
00401112:    cmp     dword ptr [ecx+74h],0Eh
00401116:    jbe     image00400000+0x1048 (00401048)
0040111c:    mov     ecx,dword ptr [ecx+0E8h]
00401122:    xor     eax,eax
00401124:    test    ecx,ecx
00401126:    setne   al
00401129:    jmp     image00400000+0x1048 (00401048)
0040112e:    xchg    ax,ax
00401130:    sub     esp,2Ch
00401133:    mov     eax,dword ptr [image00400000+0x59c7e0 (0099c7e0)]
00401138:    mov     dword ptr [esp+10h],offset image00400000+0x4b8000 (008b8000)
00401140:    mov     dword ptr [esp+8],offset image00400000+0x4b8010 (008b8010)
00401148:    mov     dword ptr [esp+4],offset image00400000+0x4b8014 (008b8014)
00401150:    mov     dword ptr [esp],offset image00400000+0x4b8018 (008b8018)
00401157:    mov     dword ptr [image00400000+0x4b8000 (008b8000)],eax
0040115c:    mov     eax,dword ptr [image00400000+0x3d06cc (007d06cc)]
00401161:    mov     dword ptr [esp+0Ch],eax
00401165:    call    image00400000+0x2c0c40 (006c0c40)
0040116a:    add     esp,2Ch
0040116d:    ret

I did a search for the image base address in Simutrans and got the following results:

0:000> s -d 00401000 L?003cd38c 0x00400000
00401018  00400000 05c75a4d 0099c7ec 00000001  ..@.MZ.......... <--- relevant
004010b8  00400000 00004550 00008a8d 80750040  ..@.PE......@.u. <--- relevant
005b50e0  00400000 04788908 01044783 0000e281  ..@...x..G......
006a3170  00400000 8d10fa83 4000009f c4458900  ..@........@..E.
006a31d0  00400000 85e8d389 81fffffd 8168f4fe  ..@...........h.
006a32b0  00400000 d0480f00 29c4458b 89d001ca  ..@...H..E.)....
006a32d0  00400000 810cc683 8168f4fe 7c820f00  ..@.......h....|
006a3a28  00400000 0672b70f 1802448d 2074f685  ..@...r..D....t 
006a3ae4  00400000 efeb0c75 0026748d 8301eb83  ..@.u....t&.....
007cf75c  00400000 0049004f 007e0288 007e0290  ..@.O.I...~...~.
009a3378  00400000 00400040 00400080 004000ff  ..@.@.@...@...@.
009a49a0  00400000 00400040 00400080 004000ff  ..@.@.@...@...@.
009a5248  00400000 00400040 00400080 004000ff  ..@.@.@...@...@.
009a5dd8  00400000 00400040 00400080 004000ff  ..@.@.@...@...@.
009e3ea0  00400000 004014c0 005a8000 005e005c  ..@...@...Z.\.^.
009e3ef4  00400000 00000000 00000000 abababab  ..@.............
0:000> s -d 00401001 L?003cd38c 0x00400000
0043b53d  00400000 0c2444c7 00000000 08244489  ..@..D$......D$.
006a3179  00400000 0fc44589 00011984 20fa8300  ..@..E......... 
006a383d  00400000 0b745a4d 891cc483 5f5e5bd8  ..@.MZt......[^_ <--- relevant
006a3851  00400000 ffff46e8 74c085ff 003ca1e7  ..@..F.....t..<.
006a38b5  00400000 03745a4d 56c3d089 0000b853  ..@.MZt....VS... <--- relevant
006a38e1  00400000 0000eb81 b70f0040 548d0670  ..@.....@...p..T
006a3925  00400000 03745a4d b8c3d089 00400000  ..@.MZt.......@. <--- relevant
006a3931  00400000 fffe66e8 74c085ff 003ca1ef  ..@..f.....t..<.
006a3955  00400000 8b535a4d 7408244c 5bd0890e  ..@.MZS.L$.t...[ <--- relevant
006a3971  00400000 fffe26e8 74c085ff 003ca1e4  ..@..&.....t..<.
006a39c5  00400000 03745a4d b8c3d089 00400000  ..@.MZt.......@. <--- relevant
006a39d1  00400000 fffdc6e8 b8c085ff 00400000  ..@...........@.
006a39dd  00400000 89d0450f f689c3d0 0027bc8d  ..@..E........'.
006a39f5  00400000 03745a4d 569066c3 0000b853  ..@.MZt..f.VS... <--- relevant
006a3a91  00400000 fffd06e8 74c085ff 003ca1e6  ..@........t..<.
006a3aa5  00400000 0080808b c0850040 b70fd174  ..@.....@...t...
007dfa85  00400000 00448000 003f0000 00000000  ..@...D...?.....
007fffe1  00400000 003f0000 ffffc000 1641dfff  ..@...?.......A.
009e0045  00400000 00000000 11001000 953e16a5  ..@...........>.

When looking at the disassembly for all the the results that are relevant, I see mostly the following instruction:

cmp word ptr [image00400000 (00400000)],5A4Dh

I've seen similar memory references in other EXEs but I'm yet to see Visual Studio produce this kind of code (and I ran the same search on an EXE built by Visual Studio and didn't see these specific memory references) so I'd say that if you see code like the above, you are almost certainly dealing with an EXE compiled by GCC.

| improve this answer | |

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.