• Welcome to Valhalla Legends Archive.
 

GetFileVersionInfo

Started by iago, January 23, 2006, 11:57 AM

Previous topic - Next topic

iago

Does anybody have any details on how GetFileVersionInfo works?  I'd like to have an implementation in Java, it would save me some work in other areas, but I can't seem to find any details on how it's implemented. 

I don't care what language, as long as it's not making the Win32 call GetFileVersionInfo :)
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


K

Take a look at the WINE implementation.

You can find the meat in GetFileVersioInfoW here

Skywing

Quote from: iago on January 23, 2006, 11:57 AM
Does anybody have any details on how GetFileVersionInfo works?  I'd like to have an implementation in Java, it would save me some work in other areas, but I can't seem to find any details on how it's implemented. 

I don't care what language, as long as it's not making the Win32 call GetFileVersionInfo :)
Yes.  You need to implement an NE/PE/PE+ parser (depending on which image formats that you care about).  PE/PE+ share a common resource format that is fairly well documented; I don't know much for sure about NE though, except that the file format is a mess.  Fortunately, being for Win16, nothing much uses it anymore so you may not care about it.

Anyways, the basic idea is to navigate to the resource directory in the PE image and pull out the resource structure you care about.

This may be useful to you: http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx

As far as I know this only covers PE and not PE+, but PE+ is fairly similar.  You can probably figure out the relevant differences from the PE documentation linked above and the differing structures in winnt.h from the Platform SDK for PE and PE+.

iago

Excellent, thanks to you both! 

Just to be clear, I need this for Battle.net logins.  It'll be nice to be able to query the file instead of manually updating the file version. 
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


Skywing

You might consider just using the Mac version check files.  It is much easier to extract the version information from those than the PC ones if you aren't on Windows.

iago

Hmm, good point.  Unfortunately, it's harder to get updated Mac files when the version changes. 

Didn't I read somewhere that Mac just sends 0x0000000 for its version hash?
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


UserLoser

Quote from: iago on January 24, 2006, 05:37 PM
Hmm, good point.  Unfortunately, it's harder to get updated Mac files when the version changes. 

Didn't I read somewhere that Mac just sends 0x0000000 for its version hash?

I think you did, and I believe that is true.

Skywing

#7
Quote from: iago on January 24, 2006, 05:37 PM
Hmm, good point.  Unfortunately, it's harder to get updated Mac files when the version changes. 

Didn't I read somewhere that Mac just sends 0x0000000 for its version hash?
1. The Mac patching system is for the most part compatible with the Win32 one.  There are some slight differences in the way they are packaged (as an mpq embedded in a self-executing patch just like the standalone Win32 patches) but it is otherwise compatible.  Assuming you have written a compatible patcher it should be fairly easy to modify it to work with the Mac patches.  Alternatively, you could probably rewrite patch.lst slightly and tell bnupdate to work with the Mac patch MPQ for you (perhaps under WINE).

2. No, Mac versions of the client do not send a zero ulong for that field.  At one time you could send any value for the version field to Blizzard Battle.net and it would accept it, but as far as I know, this is no longer the case.  The Mac clients use something compatible with CheckRevision, except that it uses a much simpler structure at the end of the file to retrieve the version number and datestamp.

iago

#8
I was playing around with 2 different PE Parsers.  On both of them, I could get Diablo2's version information, but I couldn't get Starcraft, War3, or War2 (they are 0x00).  Does anybody know what's different about Diablo 2's format that could make it work?  My first thought was that Diablo 2 either is or isn't PE+?

The fields I'm looking at are MajorImageVersion and MinorImageVersion in the "NT Additional Fields" section.  Are those the proper fields? 

I might just have to implement it from the specifications, we'll see :)

Thanks

<edit> I notice that the "Resources" section has some version information in it.  I'm guessing that that's what I should look for, because even commercial PE browsers say that Major and MinorImageVersion are 0x00. 

<edit2> Nope, all the files are PE, not PE+.  Guess they just compiled Diablo2's files differently. 
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


iago

Ok, this is driving me crazy.  I wrote a parser for PE headers, and I'm almost done.  At the very last step, while reading the resources table, you end up with a "Resource Data Entry" structure (section 6.8.4 in the guide Skywing posted). 

Here is what I get (the "Leaf" is the type, name, and language):

Leaf: [1, 3, 1033]
  address = 002d3d18, size = 02ec
Leaf: [3, 1, 1033]
  address = 002d32b0, size = 0128
Leaf: [3, 2, 1033]
  address = 002d33d8, size = 02e8
Leaf: [5, 106, 1033]
  address = 002d3a28, size = 02ec
Leaf: [9, 113, 1033]
  address = 002d4430, size = 0050
Leaf: [14, 102, 1033]
  address = 002d36c0, size = 0022
Leaf: [16, 1, 1033]
  address = 002d36e8, size = 033c

The last one (16) is the version.  However, the address that it gives in the entries is a RVA, it's not a pointer to within a file.  Is there any way to convert the RVA to an address in the file?
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


Skywing

Look at the IMAGE_SECTION_HEADER for the section the resource directory data is contained in - between VirtualAddress and PointerToRawData I think you should be able to calculate it.

iago

#11
Yep, you're absolutely right, I just figured it out.  Now I just have to parse the resource, but that looks easy enough. 

Here's the snippit of code I used to get the virtual address:

int virtualLength = file.getInt(ptrSectionTable + (i * 40) + 8);
int virtualStart = file.getInt(ptrSectionTable + (i * 40) + 12);
int sectionLength = file.getInt(ptrSectionTable + (i * 40) + 16);
int sectionStart = file.getInt(ptrSectionTable + (i * 40) + 20);

System.out.println(String.format(
"Section %s [%d]; virtual is from %08x to %08x [%08x]; raw is from %08x to %08x [%08x]",
name.toString(), i, virtualStart, virtualStart + virtualLength, virtualLength, sectionStart, sectionStart + sectionLength, sectionLength));

if(file.getLong(ptrSectionTable + (i * 40)) == rsrc)
{
[b]int rsrcVirtualToRaw = sectionStart - virtualStart;[/b]
ptrRsrcInfo = ptrSectionTable + (i * 40);
parseRSRC(file, file.getInt(ptrRsrcInfo + 20), file.getInt(ptrRsrcInfo + 16),
rsrcVirtualToRaw);
}


It feels messy, but it seems to work. 

Thanks for the help!  I've learned more about PE files today than I ever thought I'd know.  I'm glad I decided to do this.

[Skywing: Edit: Changed line spacing a bit on the big System.out.println line so it wouldn't break the forum tables quite so badly.]
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


Skywing

Sure.

BTW, make sure that you grab the version info from the VS_FIXEDFILEINFO instead of grabbing the Translation\language\ProductVersion, which does not always match.  YobGuls's C CheckRevision implementation incorrectly did the latter originally.

iago

#13
Hmm, I'm grabbing the RT_VERSION section, then I use the one named "1" and the language "1033".  I actually take the first name and the first language, but all the files I've checked only have one section, so I think that'll work.  Is that the right place to get the information from?

I noticed that I'm getting different results than BNLS for War2 -- BNLS is sending me 01 01 00 01 and in the version info (in every place I could find the version using PE Explorer), it's 02 00 02 00.  Any idea why that's happening?  I haven't had a chance to try it on Battle.net (I haven't integrated this into any bots), but I'm curious if it's going to work.
<edit> I just packetlogged War2, and the real client is sending 00 02 00 02.  So I guess we are working with different files? 

The other 3 products work fine, though.  But I noticed that Warcraft 3 has a weird version, 1.20.2.6065.. although the 6065 is truncated to 177 when it's sent to Battle.net.  Any idea what Blizzard did?  I assume they mistyped something. 

<edit> Here's my output:
QuoteLoading /home/ron/.hashes/STAR/starcraft.exe
01 01 03 0b

Loading /home/ron/.hashes/W2BN/Warcraft II BNE.exe
02 00 02 00

Loading /home/ron/.hashes/WAR3/war3.exe
01 14 02 b1

Loading /home/ron/.hashes/D2DV/Game.exe
01 00 0b 00
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


Skywing

Looks like BNLS has actually been using an older version of the War2BNE check files for a long time, so either nobody has ever tried War2BNE with BNLS or Battle.net doesn't bother to check the version/checksum for that product anymore if the version code is correct.

If you get a chance, let me know if the values BNLS sends are actually good on Battle.net - if not, I'll fix it.

As for War3, if you look at how CheckRevision is implemented...:

.text:10002A72                 call    VerQueryValueA
.text:10002A77                 test    eax, eax
.text:10002A79                 jz      short loc_10002AAF
.text:10002A7B                 mov     eax, [ebp+lpBuffer]
.text:10002A7E                 cmp     eax, ebx
.text:10002A80                 jz      short loc_10002AAF
.text:10002A82                 cmp     [ebp+puLen], 34h
.text:10002A86                 jb      short loc_10002AAF
.text:10002A88                 mov     ecx, [eax+VS_FIXEDFILEINFO.dwProductVersionMS]
.text:10002A8B                 xor     edx, edx
.text:10002A8D                 shr     ecx, 10h
.text:10002A90                 mov     dh, cl
.text:10002A92                 mov     ecx, [eax+VS_FIXEDFILEINFO.dwProductVersionLS]
.text:10002A95                 mov     dl, byte ptr [eax+VS_FIXEDFILEINFO.dwProductVersionMS]
.text:10002A98                 movzx   eax, byte ptr [eax+VS_FIXEDFILEINFO.dwProductVersionLS]
.text:10002A9C                 shr     ecx, 10h
.text:10002A9F                 movzx   ecx, cl
.text:10002AA2                 shl     edx, 8
.text:10002AA5                 or      edx, ecx
.text:10002AA7                 shl     edx, 8
.text:10002AAA                 or      edx, eax
.text:10002AAC                 mov     [ebp+A], edx


It does something like this:


unsigned char* buf; // allocated above
VS_FIXEDFILEINFO* info;
unsigned long size;
DWORD A;

if(VerQueryValueA(buf, "\\", (void**)&info, &size) && info && size >= sizeof(VS_FIXEDFILEINFO))
{
  A = ((info->dwProductVersionMS >> 16) << 8) & 0x000000FF;
  A |= (info->dwProductVersionMS) & 0x000000FF;
  A <<= 8;
  A |= (info->dwProductVersionLS >> 16) & 0x000000FF;
  A <<= 8;
  A |= (info->dwProductVersionLS) & 0x000000FF;
}


or perhaps cleaned up, the middle bit might look more like this:


DWORD A;

A = (info->dwProductVersionMS <<  8) & 0xFF000000 |
    (info->dwProductVersionMS << 16) & 0x00FF0000 |
    (info->dwProductVersionLS >>  8) & 0x0000FF00 |
    (info->dwProductVersionLS      ) & 00000000FF
    ;