// hw.dll dumper (c) 2006 tabris/Niall FitzGibbon
#include "windows.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
using namespace std;
const size_t hw_text_hdr_size = 0x44;
typedef struct
{
DWORD magic; // 0x00
DWORD num_sections; // 0x04
DWORD entry_2; // 0x08
DWORD base; // 0x0c
DWORD entry; // 0x10
DWORD iat_addr; // 0x14
} hw_pe_hdr;
typedef struct
{
DWORD base; // -0x08
DWORD size; // -0x04
DWORD size_on_disk; // 0x00
DWORD offset; // 0x04
DWORD u5; // 0x08
} hw_section_hdr;
typedef struct
{
DWORD orig_first_thunk; // -0x0c
DWORD time_stamp; // -0x08
DWORD forwarder_chain; // -0x04
DWORD dllname_addr; // 0x00
DWORD first_thunk; // 0x04
} hw_iat_entry;
size_t addr_to_offset(hw_pe_hdr *phwhdr, size_t addr)
{
//cout << "Trying to convert address " << addr << " to offset." << endl;
hw_section_hdr *phwsec = (hw_section_hdr*)((char*)phwhdr + sizeof(hw_pe_hdr));
for(size_t i = 0; i < phwhdr->num_sections + 1; i++, phwsec++)
{
if(phwsec->base <= addr && phwsec->base + phwsec->size > addr)
{
//cout << "Converted address " << addr << " to offset " << (addr - phwsec->base) + phwsec->offset << " in section " << i << "." << endl;
return (addr - phwsec->base) + phwsec->offset;
}
}
return 0;
}
int main(int argc, char **argv)
{
cout << hex;
if(argc != 2)
{
cout << "Usage: " << argv[0] << " <hw.dll path>" << endl;
return 0;
}
ifstream fin(argv[1], ios::binary);
if(!fin)
{
cout << "Couldn't open " << argv[1] << " for input." << endl;
return 0;
}
ofstream fout("hw.out", ios::binary);
if(!fout)
{
cout << "Couldn't create output file hw.out" << endl;
return 0;
}
fin.seekg(0, ios::end);
size_t fin_size = fin.tellg();
cout << "File size: " << fin_size << endl;
fin.seekg(0, ios::beg);
vector<char> buf;
buf.resize(fin_size);
fin.read(&buf[0], (std::streamsize)buf.size());
fin.close();
// decode the whole file aside from the plaintext header (hl.exe .text:01401D6D)
char key = 0x57;
for(size_t i = hw_text_hdr_size; i < fin_size; i++)
{
buf[i] ^= key;
key += buf[i] + 0x57;
}
// do some special XOR operations outside of the main loop
// (hl.exe .text:01401DEA)
*(DWORD*)&buf[hw_text_hdr_size + 0x8] ^= 0x7A32BC85;
// (hl.exe .text:01401E18)
*(DWORD*)&buf[hw_text_hdr_size + 0xc] ^= 0x49C042D1;
// (hl.exe .text:01401E05)
*(DWORD*)&buf[hw_text_hdr_size + 0x14] ^= 0x872C3D47;
// the file is now decrypted, but it isn't in the standard Windows PE format
// dump the decrypted version to a file and continue to convert the Valve PE format to a Windows PE
fout.write(&buf[0], (std::streamsize)buf.size());
fout.close();
fout.open("hw.pe", ios::binary);
if(!fout)
{
cout << "Couldn't create output file hw.pe" << endl;
return 0;
}
hw_pe_hdr *phwhdr = (hw_pe_hdr*)&buf[hw_text_hdr_size];
cout << "magic: " << phwhdr->magic << endl;
cout << "entry_2: " << phwhdr->entry_2 << endl;
cout << "entry: " << phwhdr->entry << endl;
// uncomment this to list all imports
// (hl.exe .text:01401DFF)
/*
cout << "base: " << phwhdr->base << endl;
cout << "iat_addr: " << phwhdr->iat_addr << endl;
hw_iat_entry *phwiatentry = (hw_iat_entry*)((char*)&buf[addr_to_offset(phwhdr, phwhdr->iat_addr)]);
while(phwiatentry->dllname_addr != 0)
{
char *dll_name = &buf[addr_to_offset(phwhdr, phwiatentry->dllname_addr + phwhdr->base)];
cout << "dll_name: " << dll_name << endl;
DWORD *pthunk = (DWORD*)&buf[addr_to_offset(phwhdr, phwiatentry->first_thunk + phwhdr->base)];
while(*pthunk != 0)
{
if(*pthunk & ~0x7fffffff)
{
cout << " ordinal: " << (*pthunk & 0x7fffffff) << endl;
}
else
{
cout << " " << (char*)&buf[addr_to_offset(phwhdr, (*pthunk + phwhdr->base + 0x2))] << endl;
}
pthunk++;
}
phwiatentry++;
}
*/
// create the new exports section to export the second entry point
const DWORD export_addr = 0x13380000;
string export_func_name("valve_init");
const size_t export_string_space = 512;
struct
{
IMAGE_EXPORT_DIRECTORY ied;
char dll_name[32];
DWORD eat[1];
DWORD ent[1];
WORD ordinals[1];
char names[export_string_space];
} exports_section;
memset(&exports_section, 0, sizeof(exports_section));
exports_section.eat[0] = phwhdr->entry_2 - phwhdr->base;
exports_section.ent[0] = (DWORD)((size_t)exports_section.names - (size_t)&exports_section) + export_addr - phwhdr->base;
exports_section.ordinals[0] = 0;
strcpy(exports_section.names, export_func_name.c_str());
strcpy(exports_section.dll_name, "hw.pe");
exports_section.ied.NumberOfFunctions = 1;
exports_section.ied.NumberOfNames = 1;
exports_section.ied.Base = 1;
exports_section.ied.AddressOfFunctions = (DWORD)((size_t)exports_section.eat - (size_t)&exports_section) + export_addr - phwhdr->base;
exports_section.ied.AddressOfNames = (DWORD)((size_t)exports_section.ent - (size_t)&exports_section) + export_addr - phwhdr->base;
exports_section.ied.AddressOfNameOrdinals = (DWORD)((size_t)exports_section.ordinals - (size_t)&exports_section) + export_addr - phwhdr->base;
exports_section.ied.Name = (DWORD)((size_t)exports_section.dll_name - (size_t)&exports_section) + export_addr - phwhdr->base;
// create the new PE header
vector<char> outbuf;
size_t in_hdr_size = hw_text_hdr_size + sizeof(hw_pe_hdr) + (phwhdr->num_sections + 1) * sizeof(hw_section_hdr);
size_t out_hdr_size = sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS) + (phwhdr->num_sections + 2) * sizeof(IMAGE_SECTION_HEADER);
size_t diff_hdr_size = out_hdr_size - in_hdr_size;
cout << "Valve header size: " << in_hdr_size << endl;
cout << "Windows PE header size: " << out_hdr_size << endl;
cout << "Difference: " << diff_hdr_size << endl;
outbuf.resize(out_hdr_size);
IMAGE_DOS_HEADER *pdoshdr = (IMAGE_DOS_HEADER*)&outbuf[0];
IMAGE_NT_HEADERS *pnthdr = (IMAGE_NT_HEADERS*)&outbuf[sizeof(IMAGE_DOS_HEADER)];
IMAGE_SECTION_HEADER *psechdr = (IMAGE_SECTION_HEADER*)&outbuf[sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)];
memset(pdoshdr, 0, sizeof(IMAGE_DOS_HEADER));
pdoshdr->e_magic = IMAGE_DOS_SIGNATURE;
pdoshdr->e_lfanew = sizeof(IMAGE_DOS_HEADER);
memset(pnthdr, 0, sizeof(IMAGE_NT_HEADERS));
pnthdr->Signature = IMAGE_NT_SIGNATURE;
pnthdr->FileHeader.Machine = IMAGE_FILE_MACHINE_I386;
pnthdr->FileHeader.NumberOfSections = (WORD)(phwhdr->num_sections + 2);
pnthdr->FileHeader.SizeOfOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER);
pnthdr->FileHeader.Characteristics = IMAGE_FILE_DLL | IMAGE_FILE_32BIT_MACHINE | IMAGE_FILE_LOCAL_SYMS_STRIPPED | IMAGE_FILE_LINE_NUMS_STRIPPED | IMAGE_FILE_RELOCS_STRIPPED;
pnthdr->OptionalHeader.AddressOfEntryPoint = phwhdr->entry - phwhdr->base;
pnthdr->OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR32_MAGIC;
pnthdr->OptionalHeader.ImageBase = phwhdr->base;
pnthdr->OptionalHeader.SizeOfHeaders = (DWORD)out_hdr_size;
pnthdr->OptionalHeader.FileAlignment = 0x1;
pnthdr->OptionalHeader.SectionAlignment = 0x1;
pnthdr->OptionalHeader.NumberOfRvaAndSizes = 0x02;
pnthdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = phwhdr->iat_addr - phwhdr->base;
pnthdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress = export_addr - phwhdr->base;
// transfer the section information
// (hl.exe .text:01401D88)
hw_section_hdr *phwsec = (hw_section_hdr*)((char*)phwhdr + sizeof(hw_pe_hdr));
for(size_t i = 0; i < phwhdr->num_sections + 1; i++, phwsec++, psechdr++)
{
memset(psechdr, 0, sizeof(IMAGE_SECTION_HEADER));
_snprintf_s((char*)psechdr->Name, IMAGE_SIZEOF_SHORT_NAME, _TRUNCATE, ".valve%d", i);
psechdr->VirtualAddress = phwsec->base - phwhdr->base;
psechdr->Misc.VirtualSize = phwsec->size;
psechdr->PointerToRawData = phwsec->offset + diff_hdr_size + sizeof(exports_section);
psechdr->SizeOfRawData = phwsec->size_on_disk;
}
// now add a section for export data
strcpy((char*)psechdr->Name, ".export");
psechdr->VirtualAddress = export_addr - phwhdr->base;
psechdr->Misc.VirtualSize = 0x1000;
psechdr->PointerToRawData = (DWORD)out_hdr_size; // right after our new header
psechdr->SizeOfRawData = sizeof(exports_section);
// append our export section to the ned of the header
for(char *p = (char*)&exports_section; p < ((char*)&exports_section) + sizeof(exports_section); p++)
{
outbuf.push_back(*p);
}
// now copy the rest of the input file over (do this last in case it gets moved during reallocation)
outbuf.insert(outbuf.end(), buf.begin() + in_hdr_size, buf.end());
// write the new PE file to disk
fout.write(&outbuf[0], (std::streamsize)outbuf.size());
fout.close();
return 0;
}