From: ajkhoury aidankhoury@gmail.com
--- dlls/mspatcha/mspatcha_main.c | 89 +- dlls/mspatcha/pa19.c | 2217 ++++++++++++++++++++++++++------- dlls/mspatcha/pa19.h | 14 +- include/patchapi.h | 3 + 4 files changed, 1818 insertions(+), 505 deletions(-)
diff --git a/dlls/mspatcha/mspatcha_main.c b/dlls/mspatcha/mspatcha_main.c index a350dc117b9..1d8744a85e4 100644 --- a/dlls/mspatcha/mspatcha_main.c +++ b/dlls/mspatcha/mspatcha_main.c @@ -67,7 +67,8 @@ static inline void bin2hex(const unsigned char *bin, char *hexstr, size_t maxcou /***************************************************** * TestApplyPatchToFileA (MSPATCHA.@) */ -BOOL WINAPI TestApplyPatchToFileA(LPCSTR patch_file, LPCSTR old_file, ULONG apply_flags) +BOOL WINAPI TestApplyPatchToFileA( + LPCSTR patch_file, LPCSTR old_file, ULONG apply_option_flags) { BOOL ret; WCHAR *patch_fileW, *old_fileW = NULL; @@ -78,36 +79,42 @@ BOOL WINAPI TestApplyPatchToFileA(LPCSTR patch_file, LPCSTR old_file, ULONG appl HeapFree(GetProcessHeap(), 0, patch_fileW); return FALSE; } - ret = apply_patch_to_file(patch_fileW, old_fileW, NULL, apply_flags, NULL, NULL, TRUE); + + ret = apply_patch_to_file(patch_fileW, old_fileW, NULL, + apply_option_flags | APPLY_OPTION_TEST_ONLY, NULL, NULL); + HeapFree(GetProcessHeap(), 0, patch_fileW); HeapFree(GetProcessHeap(), 0, old_fileW); return ret; }
-BOOL WINAPI TestApplyPatchToFileW(LPCWSTR patch_file_name, LPCWSTR old_file_name, ULONG apply_option_flags) +BOOL WINAPI TestApplyPatchToFileW( + LPCWSTR patch_file_name, LPCWSTR old_file_name, ULONG apply_option_flags) { - return apply_patch_to_file(patch_file_name, old_file_name, NULL, apply_option_flags, NULL, NULL, TRUE); + return apply_patch_to_file(patch_file_name, old_file_name, NULL, + apply_option_flags | APPLY_OPTION_TEST_ONLY, NULL, NULL); }
-BOOL WINAPI TestApplyPatchToFileByHandles(HANDLE patch_file_hndl, HANDLE old_file_hndl, ULONG apply_option_flags) +BOOL WINAPI TestApplyPatchToFileByHandles( + HANDLE patch_file_hndl, HANDLE old_file_hndl, ULONG apply_option_flags) { return apply_patch_to_file_by_handles(patch_file_hndl, old_file_hndl, NULL, - apply_option_flags, NULL, NULL, TRUE); + apply_option_flags | APPLY_OPTION_TEST_ONLY, NULL, NULL); }
-BOOL WINAPI TestApplyPatchToFileByBuffers(BYTE *patch_file_buf, ULONG patch_file_size, +BOOL WINAPI TestApplyPatchToFileByBuffers( + BYTE *patch_file_buf, ULONG patch_file_size, BYTE *old_file_buf, ULONG old_file_size, - ULONG* new_file_size, - ULONG apply_option_flags) + ULONG* new_file_size, ULONG apply_option_flags) { /* NOTE: windows preserves last error on success for this function, but no apps are known to depend on it */
- DWORD err = apply_patch_to_file_by_buffers(patch_file_buf, patch_file_size, - old_file_buf, old_file_size, - NULL, 0, new_file_size, NULL, - apply_option_flags, - NULL, NULL, - TRUE); + DWORD err = apply_patch_to_file_by_buffers( + patch_file_buf, patch_file_size, + old_file_buf, old_file_size, + NULL, 0, new_file_size, NULL, + apply_option_flags | APPLY_OPTION_TEST_ONLY, + NULL, NULL);
SetLastError(err);
@@ -117,7 +124,8 @@ BOOL WINAPI TestApplyPatchToFileByBuffers(BYTE *patch_file_buf, ULONG patch_file /***************************************************** * ApplyPatchToFileExA (MSPATCHA.@) */ -BOOL WINAPI ApplyPatchToFileExA(LPCSTR patch_file, LPCSTR old_file, LPCSTR new_file, ULONG apply_flags, +BOOL WINAPI ApplyPatchToFileExA( + LPCSTR patch_file, LPCSTR old_file, LPCSTR new_file, ULONG apply_option_flags, PPATCH_PROGRESS_CALLBACK progress_fn, PVOID progress_ctx) { BOOL ret = FALSE; @@ -131,7 +139,8 @@ BOOL WINAPI ApplyPatchToFileExA(LPCSTR patch_file, LPCSTR old_file, LPCSTR new_f if (!(new_fileW = strdupAW(new_file))) goto free_wstrs;
- ret = apply_patch_to_file(patch_fileW, old_fileW, new_fileW, apply_flags, progress_fn, progress_ctx, FALSE); + ret = apply_patch_to_file(patch_fileW, old_fileW, new_fileW, + apply_option_flags, progress_fn, progress_ctx);
HeapFree(GetProcessHeap(), 0, new_fileW); free_wstrs: @@ -143,7 +152,8 @@ free_wstrs: /***************************************************** * ApplyPatchToFileA (MSPATCHA.@) */ -BOOL WINAPI ApplyPatchToFileA(LPCSTR patch_file, LPCSTR old_file, LPCSTR new_file, ULONG apply_flags) +BOOL WINAPI ApplyPatchToFileA( + LPCSTR patch_file, LPCSTR old_file, LPCSTR new_file, ULONG apply_flags) { return ApplyPatchToFileExA(patch_file, old_file, new_file, apply_flags, NULL, NULL); } @@ -151,11 +161,11 @@ BOOL WINAPI ApplyPatchToFileA(LPCSTR patch_file, LPCSTR old_file, LPCSTR new_fil /***************************************************** * ApplyPatchToFileW (MSPATCHA.@) */ -BOOL WINAPI ApplyPatchToFileW(LPCWSTR patch_file_name, LPCWSTR old_file_name, LPCWSTR new_file_name, - ULONG apply_option_flags) +BOOL WINAPI ApplyPatchToFileW( + LPCWSTR patch_file_name, LPCWSTR old_file_name, LPCWSTR new_file_name, ULONG apply_option_flags) { - return apply_patch_to_file(patch_file_name, old_file_name, new_file_name, apply_option_flags, - NULL, NULL, FALSE); + return apply_patch_to_file(patch_file_name, old_file_name, new_file_name, + apply_option_flags, NULL, NULL); }
/***************************************************** @@ -165,50 +175,45 @@ BOOL WINAPI ApplyPatchToFileByHandles(HANDLE patch_file_hndl, HANDLE old_file_hn ULONG apply_option_flags) { return apply_patch_to_file_by_handles(patch_file_hndl, old_file_hndl, new_file_hndl, - apply_option_flags, NULL, NULL, FALSE); + apply_option_flags, NULL, NULL); }
/***************************************************** * ApplyPatchToFileExW (MSPATCHA.@) */ -BOOL WINAPI ApplyPatchToFileExW(LPCWSTR patch_file_name, LPCWSTR old_file_name, LPCWSTR new_file_name, - ULONG apply_option_flags, - PPATCH_PROGRESS_CALLBACK progress_fn, PVOID progress_ctx) +BOOL WINAPI ApplyPatchToFileExW( + LPCWSTR patch_file_name, LPCWSTR old_file_name, LPCWSTR new_file_name, + ULONG apply_option_flags, PPATCH_PROGRESS_CALLBACK progress_fn, PVOID progress_ctx) { - return apply_patch_to_file(patch_file_name, old_file_name, new_file_name, apply_option_flags, - progress_fn, progress_ctx, FALSE); + return apply_patch_to_file(patch_file_name, old_file_name, new_file_name, + apply_option_flags, progress_fn, progress_ctx); }
/***************************************************** * ApplyPatchToFileByHandlesEx (MSPATCHA.@) */ -BOOL WINAPI ApplyPatchToFileByHandlesEx(HANDLE patch_file_hndl, HANDLE old_file_hndl, HANDLE new_file_hndl, - ULONG apply_option_flags, - PPATCH_PROGRESS_CALLBACK progress_fn, - PVOID progress_ctx) +BOOL WINAPI ApplyPatchToFileByHandlesEx( + HANDLE patch_file_hndl, HANDLE old_file_hndl, HANDLE new_file_hndl, + ULONG apply_option_flags, PPATCH_PROGRESS_CALLBACK progress_fn, PVOID progress_ctx) { return apply_patch_to_file_by_handles(patch_file_hndl, old_file_hndl, new_file_hndl, - apply_option_flags, progress_fn, progress_ctx, FALSE); + apply_option_flags, progress_fn, progress_ctx); }
/***************************************************** * ApplyPatchToFileByBuffers (MSPATCHA.@) */ -BOOL WINAPI ApplyPatchToFileByBuffers(PBYTE patch_file_view, ULONG patch_file_size, - PBYTE old_file_view, ULONG old_file_size, - PBYTE* new_file_buf, ULONG new_file_buf_size, ULONG* new_file_size, - FILETIME* new_file_time, - ULONG apply_option_flags, - PPATCH_PROGRESS_CALLBACK progress_fn, PVOID progress_ctx) +BOOL WINAPI ApplyPatchToFileByBuffers( + PBYTE patch_file_view, ULONG patch_file_size, PBYTE old_file_view, ULONG old_file_size, + PBYTE* new_file_buf, ULONG new_file_buf_size, ULONG* new_file_size, FILETIME* new_file_time, + ULONG apply_option_flags, PPATCH_PROGRESS_CALLBACK progress_fn, PVOID progress_ctx) { /* NOTE: windows preserves last error on success for this function, but no apps are known to depend on it */
DWORD err = apply_patch_to_file_by_buffers(patch_file_view, patch_file_size, old_file_view, old_file_size, new_file_buf, new_file_buf_size, new_file_size, new_file_time, - apply_option_flags, - progress_fn, progress_ctx, - FALSE); + apply_option_flags, progress_fn, progress_ctx);
SetLastError(err);
diff --git a/dlls/mspatcha/pa19.c b/dlls/mspatcha/pa19.c index ad8a41f0527..55248156b39 100644 --- a/dlls/mspatcha/pa19.c +++ b/dlls/mspatcha/pa19.c @@ -18,18 +18,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * * TODO - * - Normalization of 32-bit PE executable files and reversal of special - * processing of these executables is not implemented. - * Without normalization, old files cannot be validated for patching. The - * function NormalizeFileForPatchSignature() in Windows could be used to work - * out exactly how normalization works. - * Most/all of the special processing seems to be relocation of targets for - * some jump/call instructions to match more of the old file and improve - * compression. Patching of 64-bit exes works because mspatchc.dll does not - * implement special processing of them. In 32-bit patches, the variable - * named here 'unknown_count' seems to indicate presence of data related to - * reversing the processing. The changes that must be reversed occur at some, - * but not all, of the positions listed in the PE .reloc table. + * - Implement interleaved decoding when PATCH_OPTION_INTERLEAVE_FILES was + * used or the old file exceeds the lzxd window size. */
#include <stdarg.h> @@ -49,19 +39,17 @@ WINE_DEFAULT_DEBUG_CHANNEL(mspatcha);
#define PA19_FILE_MAGIC 0x39314150 -#define PATCH_OPTION_EXTRA_FLAGS 0x80000000 - -static UINT32 compute_zero_crc32(UINT32 crc, INT_PTR len) -{ - static const BYTE zero_buffer[1024]; - - while (len) - { - crc = RtlComputeCrc32(crc, zero_buffer, min(len, sizeof(zero_buffer))); - len -= min(len, sizeof(zero_buffer)); - } - return crc; -} +#define PATCH_OPTION_EXTRA_FLAGS PATCH_OPTION_RESERVED1 + +/* patch header extra flags field values */ +#define PATCH_TRANSFORM_NO_RELOCS 0x00000001 +#define PATCH_TRANSFORM_NO_IMPORTS 0x00000002 +#define PATCH_TRANSFORM_NO_EXPORTS 0x00000004 +#define PATCH_TRANSFORM_NO_RELJMPS 0x00000008 +#define PATCH_TRANSFORM_NO_RELCALLS 0x00000010 +#define PATCH_TRANSFORM_NO_RESOURCE 0x00000020 +#define PATCH_EXTRA_HAS_LZX_WINDOW_SIZE 0x00010000 +#define PATCH_EXTRA_HAS_PATCH_TRANSFORMS 0x20000000
/*********************************************************************************** * PatchAPI PA19 file header @@ -99,36 +87,53 @@ static UINT32 compute_zero_crc32(UINT32 crc, INT_PTR len) * UINT32 crc_hack; (rounds out the entire file crc32 to 0) */
+struct patch_transform_entry { + ULONG old_rva; + ULONG new_rva; +}; +struct patch_transform_table { + ULONG count; + ULONG allocated; + struct patch_transform_entry *entries; + BYTE *unused; +};
-#define MAX_RANGES 255 - -struct input_file_info { - size_t input_size; - DWORD crc32; - BYTE ignore_range_count; - BYTE retain_range_count; - PATCH_IGNORE_RANGE ignore_table[MAX_RANGES]; - PATCH_RETAIN_RANGE retain_table[MAX_RANGES]; - size_t unknown_count; - size_t stream_size; - const BYTE *stream_start; - int next_i; - int next_r; +struct old_file_info { + HANDLE old_file_handle; /* 0x00 */ + ULONG old_size; /* 0x04 */ + ULONG old_crc32; /* 0x08 */ + ULONG patch_stream_size; /* 0x0C */ + ULONG ignore_range_count; /* 0x10 */ + PATCH_IGNORE_RANGE *ignore_range_array; /* 0x14 */ + ULONG retain_range_count; /* 0x18 */ + PATCH_RETAIN_RANGE *retain_range_array; /* 0x1C */ + struct patch_transform_table xfrm_tbl; /* 0x20 */ + + /* wine private file info fields */ + const BYTE *patch_stream_start; };
struct patch_file_header { - DWORD flags; - DWORD timestamp; - size_t patched_size; - DWORD patched_crc32; - unsigned input_file_count; - struct input_file_info *file_table; + ULONG signature; /* 0x00 */ + ULONG flags; /* 0x04 */ + ULONG extra_flags; /* 0x08 */ + ULONG new_image_base; /* 0x0C */ + ULONG new_image_time; /* 0x10 */ + ULONG new_res_time; /* 0x14 */ + ULONG new_file_time; /* 0x18 */ + ULONG patched_size; /* 0x1C */ + ULONG patched_crc32; /* 0x20 */ + ULONG old_file_count; /* 0x24 */ + struct old_file_info *file_table; /* 0x28 */ + PPATCH_INTERLEAVE_MAP *interleave_map_array;/* 0x2C */ + ULONG compression_window_size; /* 0x30 */ + + /* wine private patch header fields */ const BYTE *src; const BYTE *end; - DWORD err; + ULONG err; };
- /* Currently supported options. Some such as PATCH_OPTION_FAIL_IF_BIGGER don't * affect decoding but can get recorded in the patch file anyway */ #define PATCH_OPTION_SUPPORTED_FLAGS ( \ @@ -143,6 +148,7 @@ struct patch_file_header { | PATCH_OPTION_NO_CHECKSUM \ | PATCH_OPTION_NO_RESTIMEFIX \ | PATCH_OPTION_NO_TIMESTAMP \ + | PATCH_OPTION_INTERLEAVE_FILES \ | PATCH_OPTION_EXTRA_FLAGS)
@@ -231,37 +237,47 @@ static INT64 read_svli(struct patch_file_header *ph)
static int __cdecl compare_ignored_range(const void *a, const void *b) { - LONG delta = ((PATCH_IGNORE_RANGE*)a)->OffsetInOldFile - ((PATCH_IGNORE_RANGE*)b)->OffsetInOldFile; - if (delta > 0) - return 1; - if (delta < 0) - return -1; - return 0; + const LONG delta = ((const PATCH_IGNORE_RANGE*)a)->OffsetInOldFile + - ((const PATCH_IGNORE_RANGE*)b)->OffsetInOldFile; + if (delta > 0) return 1; + else if (delta < 0) return -1; + else return 0; }
static int __cdecl compare_retained_range_old(const void *a, const void *b) { - LONG delta = ((PATCH_RETAIN_RANGE*)a)->OffsetInOldFile - ((PATCH_RETAIN_RANGE*)b)->OffsetInOldFile; - if (delta > 0) - return 1; - if (delta < 0) - return -1; - return 0; -} - -static int __cdecl compare_retained_range_new(const void *a, const void *b) -{ - LONG delta = ((PATCH_RETAIN_RANGE*)a)->OffsetInNewFile - ((PATCH_RETAIN_RANGE*)b)->OffsetInNewFile; - if (delta > 0) - return 1; - if (delta < 0) - return -1; - return 0; + const LONG delta = ((const PATCH_RETAIN_RANGE*)a)->OffsetInOldFile + - ((const PATCH_RETAIN_RANGE*)b)->OffsetInOldFile; + if (delta > 0) return 1; + else if (delta < 0) return -1; + else return 0; }
-static int read_header(struct patch_file_header *ph, const BYTE *buf, size_t size) +static int read_patch_header(struct patch_file_header *ph, const BYTE *buf, size_t size) { - unsigned fileno; + ULONG signature; + ULONG fileno; + struct old_file_info *fi; + ULONG i; + ULONG window_shift; + LONG delta; + LONG delta_new; + ULONG delta_old; + LONG delta_next; + LONG delta_length; + LONG length_next; + ULONG next_offset; + ULONG old_rva; + ULONG new_rva; + ULONG length; + ULONG next_old_offset; + ULONG total_old_offset; + ULONG next_old_length; + ULONG total_old_length; + ULONG remaining_length; + ULONG new_length; + ULONG interleave_count; + PATCH_INTERLEAVE_MAP *interleave_map;
ph->src = buf; ph->end = buf + size; @@ -269,14 +285,20 @@ static int read_header(struct patch_file_header *ph, const BYTE *buf, size_t siz ph->file_table = NULL; ph->err = ERROR_SUCCESS;
- if (read_raw_uint32(ph) != PA19_FILE_MAGIC) + signature = read_raw_uint32(ph); + ph->signature = signature; + if (signature != PA19_FILE_MAGIC) { - TRACE("no PA19 signature\n"); + WARN("no PA19 signature\n"); ph->err = ERROR_PATCH_CORRUPT; return -1; }
ph->flags = read_raw_uint32(ph); + + /* the meaning of PATCH_OPTION_NO_TIMESTAMP is inverted for decoding */ + ph->flags ^= PATCH_OPTION_NO_TIMESTAMP; + if ((ph->flags & PATCH_OPTION_SUPPORTED_FLAGS) != ph->flags) { FIXME("unsupported option flag(s): 0x%08lx\n", ph->flags & ~PATCH_OPTION_SUPPORTED_FLAGS); @@ -284,148 +306,295 @@ static int read_header(struct patch_file_header *ph, const BYTE *buf, size_t siz return -1; }
- /* additional 32-bit flag field */ + /* extra 32-bit flags field */ if (ph->flags & PATCH_OPTION_EXTRA_FLAGS) { - TRACE("skipping extra flag field\n"); - (void)read_raw_uint32(ph); + ph->extra_flags = read_raw_uint32(ph); + WARN("extra flags field is set: 0x%lX\n", ph->extra_flags); }
- /* the meaning of PATCH_OPTION_NO_TIMESTAMP is inverted for decoding */ - if(ph->flags & PATCH_OPTION_NO_TIMESTAMP) - ph->timestamp = read_raw_uint32(ph); + /* decode new file time field */ + if ((ph->flags & PATCH_OPTION_NO_TIMESTAMP) == 0) + { + ph->new_file_time = read_raw_uint32(ph); + } + + /* read the new coff image base and time used for normalization */ + if ((ph->flags & PATCH_OPTION_NO_REBASE) == 0) + { + ph->new_image_base = ((ULONG)*(UINT16 UNALIGNED *)ph->src) << 16; + ph->src += sizeof(UINT16); + + TRACE("new file requires rebasing to 0x%lX\n", ph->new_image_base); + + if (ph->new_file_time) { + delta = read_svli(ph); + ph->new_image_time = ph->new_file_time - delta; + } else { + ph->new_image_time = read_raw_uint32(ph); + } + }
- /* not sure what this value is for, but its absence seems to mean only that timestamps - * in the decompressed 32-bit exe are not modified */ - if (!(ph->flags & PATCH_OPTION_NO_REBASE)) + if ((ph->flags & PATCH_OPTION_NO_RESTIMEFIX) == 0) { - TRACE("skipping rebase field\n"); - (void)read_uvli(ph); + if (ph->new_image_time) { + delta = read_svli(ph); + ph->new_res_time = ph->new_image_time - delta; + } else { + ph->new_res_time = read_raw_uint32(ph); + } }
- ph->patched_size = (size_t)read_uvli(ph); - TRACE("patched file size will be %u\n", (unsigned)ph->patched_size); + ph->patched_size = read_uvli(ph); + TRACE("patched file size will be %lu\n", (ULONG)ph->patched_size); ph->patched_crc32 = read_raw_uint32(ph);
- ph->input_file_count = *ph->src; - ++ph->src; - TRACE("patch supports %u old file(s)\n", ph->input_file_count); - /* if no old file used, input_file_count is still 1 */ - if (ph->input_file_count == 0) + if (ph->extra_flags & PATCH_EXTRA_HAS_LZX_WINDOW_SIZE) { + window_shift = (ULONG)*ph->src; + ++ph->src; + + if (window_shift > 31) { + ERR("invalid compression window shift!\n"); + ph->err = ERROR_EXTENDED_ERROR; + return -1; + } + + ph->compression_window_size = (ULONG)1ul << window_shift; + TRACE("compression window size: %lu\n", ph->compression_window_size); + } + + ph->old_file_count = (ULONG)*ph->src; + ++ph->src; + TRACE("patch supports %lu old file(s)\n", ph->old_file_count); + /* if no old file used, old_file_count is still 1 */ + if (ph->old_file_count == 0) { ph->err = ERROR_PATCH_CORRUPT; return -1; }
- if (ph->err != ERROR_SUCCESS) + if (ph->err != ERROR_SUCCESS) { return -1; + }
- ph->file_table = heap_calloc(ph->input_file_count, sizeof(struct input_file_info)); + ph->file_table = heap_calloc(ph->old_file_count, sizeof(struct old_file_info)); if (ph->file_table == NULL) { ph->err = ERROR_OUTOFMEMORY; return -1; }
- for (fileno = 0; fileno < ph->input_file_count; ++fileno) { - struct input_file_info *fi = ph->file_table + fileno; - ptrdiff_t delta; - unsigned i; + /* If the interleave files option is set, allocate memory for the interleave map */ + if (ph->flags & PATCH_OPTION_INTERLEAVE_FILES) + { + ph->interleave_map_array = heap_calloc(ph->old_file_count, sizeof(PPATCH_INTERLEAVE_MAP)); + if (ph->interleave_map_array == NULL) { + ph->err = ERROR_OUTOFMEMORY; + return -1; + } + }
- delta = (ptrdiff_t)read_svli(ph); - fi->input_size = ph->patched_size + delta; + for (fileno = 0; fileno < ph->old_file_count; ++fileno) + { + fi = &ph->file_table[fileno]; + + delta = read_svli(ph); + fi->old_size = ph->patched_size + delta;
- fi->crc32 = read_raw_uint32(ph); + fi->old_crc32 = read_raw_uint32(ph);
+ /* decode ignore range table */ fi->ignore_range_count = *ph->src; ++ph->src; - TRACE("found %u range(s) to ignore\n", fi->ignore_range_count); - - for (i = 0; i < fi->ignore_range_count; ++i) { - PATCH_IGNORE_RANGE *ir = fi->ignore_table + i; - - ir->OffsetInOldFile = (LONG)read_svli(ph); - ir->LengthInBytes = (ULONG)read_uvli(ph); + if (fi->ignore_range_count) + { + TRACE("found %lu range(s) to ignore\n", fi->ignore_range_count);
- if (i != 0) - { - ir->OffsetInOldFile += fi->ignore_table[i - 1].OffsetInOldFile - + fi->ignore_table[i - 1].LengthInBytes; + fi->ignore_range_array = heap_calloc(fi->ignore_range_count, sizeof(PATCH_IGNORE_RANGE)); + if (fi->ignore_range_array == NULL) { + ph->err = ERROR_OUTOFMEMORY; + return -1; } - if (ir->OffsetInOldFile > fi->input_size - || ir->OffsetInOldFile + ir->LengthInBytes > fi->input_size - || ir->LengthInBytes > fi->input_size) + + next_offset = 0; + for (i = 0; i < fi->ignore_range_count; ++i) { - ph->err = ERROR_PATCH_CORRUPT; - return -1; + delta = (LONG)read_svli(ph); + length = (ULONG)read_uvli(ph); + + fi->ignore_range_array[i].OffsetInOldFile = next_offset + delta; + fi->ignore_range_array[i].LengthInBytes = length; + + if (fi->ignore_range_array[i].OffsetInOldFile > fi->old_size + || (fi->ignore_range_array[i].OffsetInOldFile + length) > fi->old_size) + { + ph->err = ERROR_PATCH_CORRUPT; + return -1; + } + + next_offset += delta + length; } }
+ /* decode retain range table */ fi->retain_range_count = *ph->src; ++ph->src; - TRACE("found %u range(s) to retain\n", fi->retain_range_count); - - for (i = 0; i < fi->retain_range_count; ++i) { - PATCH_RETAIN_RANGE *rr = fi->retain_table + i; - - rr->OffsetInOldFile = (LONG)read_svli(ph); - if (i != 0) - rr->OffsetInOldFile += - fi->retain_table[i - 1].OffsetInOldFile + fi->retain_table[i - 1].LengthInBytes; - - rr->OffsetInNewFile = rr->OffsetInOldFile + (LONG)read_svli(ph); - rr->LengthInBytes = (ULONG)read_uvli(ph); + if (fi->retain_range_count) + { + TRACE("found %lu range(s) to retain\n", fi->retain_range_count);
- if (rr->OffsetInOldFile > fi->input_size - || rr->OffsetInOldFile + rr->LengthInBytes > fi->input_size - || rr->OffsetInNewFile > ph->patched_size - || rr->OffsetInNewFile + rr->LengthInBytes > ph->patched_size - || rr->LengthInBytes > ph->patched_size) + fi->retain_range_array = heap_calloc(fi->retain_range_count, sizeof(PATCH_RETAIN_RANGE)); + if (fi->retain_range_array == NULL) { - ph->err = ERROR_PATCH_CORRUPT; + ph->err = ERROR_OUTOFMEMORY; return -1; }
- /* ranges in new file must be equal and in the same order for all source files */ - if (fileno != 0) + next_offset = 0; + for (i = 0; i < fi->retain_range_count; ++i) { - PATCH_RETAIN_RANGE *rr_0 = ph->file_table[0].retain_table + i; - if (rr->OffsetInNewFile != rr_0->OffsetInNewFile - || rr->LengthInBytes != rr_0->LengthInBytes) + delta = (LONG)read_svli(ph); + delta_new = (LONG)read_svli(ph); + length = (ULONG)read_uvli(ph); + + fi->retain_range_array[i].OffsetInOldFile = next_offset + delta; + fi->retain_range_array[i].OffsetInNewFile = next_offset + delta + delta_new; + fi->retain_range_array[i].LengthInBytes = length; + + if (fi->retain_range_array[i].OffsetInOldFile > fi->old_size + || (fi->retain_range_array[i].OffsetInOldFile + fi->retain_range_array[i].LengthInBytes) > fi->old_size + || fi->retain_range_array[i].OffsetInNewFile > ph->patched_size + || (fi->retain_range_array[i].OffsetInNewFile + fi->retain_range_array[i].LengthInBytes) > ph->patched_size) { ph->err = ERROR_PATCH_CORRUPT; return -1; } + + /* ranges in new file must be equal and in the same order for all source files */ + if (fileno + && (fi->retain_range_array[i].OffsetInNewFile != ph->file_table[0].retain_range_array[i].OffsetInNewFile + || fi->retain_range_array[i].LengthInBytes != ph->file_table[0].retain_range_array[i].LengthInBytes)) + { + ph->err = ERROR_PATCH_CORRUPT; + return -1; + } + + next_offset += delta + length; } }
- fi->unknown_count = (size_t)read_uvli(ph); - if (fi->unknown_count) + /* decode patch transform table */ + fi->xfrm_tbl.count = (ULONG)read_uvli(ph); + if (fi->xfrm_tbl.count) { - FIXME("special processing of 32-bit executables not implemented.\n"); - ph->err = ERROR_PATCH_PACKAGE_UNSUPPORTED; - return -1; + TRACE("%lu patch transform entries found\n", fi->xfrm_tbl.count); + + fi->xfrm_tbl.entries = heap_calloc(fi->xfrm_tbl.count, sizeof(struct patch_transform_entry)); + if (fi->xfrm_tbl.entries == NULL) { + ph->err = ERROR_OUTOFMEMORY; + return -1; + } + + fi->xfrm_tbl.unused = NULL; + + old_rva = 0; + new_rva = 0; + for (i = 0; i < fi->xfrm_tbl.count; i++) + { + delta_old = read_uvli(ph); + delta_new = read_svli(ph); + + fi->xfrm_tbl.entries[i].old_rva = old_rva + delta_old; + fi->xfrm_tbl.entries[i].new_rva = new_rva + delta_new; + + old_rva += delta_old; + new_rva += delta_new; + } + } + + /* decode interleave map */ + if (ph->flags & PATCH_OPTION_INTERLEAVE_FILES) + { + interleave_count = (ULONG)read_uvli(ph); + if (interleave_count) + { + interleave_map = (PATCH_INTERLEAVE_MAP *)heap_alloc( + sizeof(((PATCH_INTERLEAVE_MAP *)0)->Range) * (interleave_count - 1) + + sizeof(PATCH_INTERLEAVE_MAP)); + + if (interleave_map == NULL) + { + ph->err = ERROR_OUTOFMEMORY; + return -1; + } + + interleave_map->CountRanges = interleave_count; + + total_old_offset = 0; + next_old_offset = 0; + total_old_length = 0; + next_old_length = 0; + new_length = 0; + remaining_length = ph->patched_size; + + for (i = 0; i < interleave_map->CountRanges; i++) + { + delta_next = (LONG)read_svli(ph); + length_next = (LONG)read_svli(ph); + + total_old_offset += delta_next; + total_old_length += length_next; + + next_old_offset += total_old_offset; + next_old_length += total_old_length; + + interleave_map->Range[i].OldOffset = next_old_offset; + interleave_map->Range[i].OldLength = next_old_length; + + if (interleave_map->Range[i].OldOffset > fi->old_size + || (interleave_map->Range[i].OldOffset + + interleave_map->Range[i].OldLength) > fi->old_size) + { + ph->err = ERROR_PATCH_CORRUPT; + return -1; + } + + if (i >= (interleave_map->CountRanges - 1)) + { + interleave_map->Range[i].NewLength = remaining_length; + } + else + { + delta_length = (LONG)read_svli(ph) << 15; + new_length += delta_length; + interleave_map->Range[i].NewLength = new_length; + remaining_length -= new_length; + } + } + + ph->interleave_map_array[fileno] = interleave_map; + } } - fi->stream_size = (size_t)read_uvli(ph); + + fi->patch_stream_size = (ULONG)read_uvli(ph); }
- for (fileno = 0; fileno < ph->input_file_count; ++fileno) + /* sort range tables and calculate patch stream start for each file */ + for (fileno = 0; fileno < ph->old_file_count; ++fileno) { - struct input_file_info *fi = ph->file_table + fileno; - - qsort(fi->ignore_table, fi->ignore_range_count, sizeof(fi->ignore_table[0]), compare_ignored_range); - qsort(fi->retain_table, fi->retain_range_count, sizeof(fi->retain_table[0]), compare_retained_range_old); + fi = &ph->file_table[fileno]; + qsort(fi->ignore_range_array, fi->ignore_range_count, sizeof(fi->ignore_range_array[0]), compare_ignored_range); + qsort(fi->retain_range_array, fi->retain_range_count, sizeof(fi->retain_range_array[0]), compare_retained_range_old);
- fi->stream_start = ph->src; - ph->src += fi->stream_size; + fi->patch_stream_start = ph->src; + ph->src += fi->patch_stream_size; }
/* skip the crc adjustment field */ ph->src = min(ph->src + 4, ph->end);
{ - UINT32 crc = RtlComputeCrc32(0, buf, ph->src - buf) ^ 0xFFFFFFFF; - if (crc != 0) + if (RtlComputeCrc32(0, buf, ph->src - buf) ^ 0xFFFFFFFF) { TRACE("patch file crc32 failed\n"); if (ph->src < ph->end) @@ -439,7 +608,35 @@ static int read_header(struct patch_file_header *ph, const BYTE *buf, size_t siz
static void free_header(struct patch_file_header *ph) { - heap_free(ph->file_table); + ULONG i; + + if (ph->interleave_map_array) { + for (i = 0; i < ph->old_file_count; i++) { + if (ph->interleave_map_array[i]) { + heap_free(ph->interleave_map_array[i]); + ph->interleave_map_array[i] = NULL; + } + } + + heap_free(ph->interleave_map_array); + ph->interleave_map_array = NULL; + } + + if (ph->file_table) { + for (i = 0; i < ph->old_file_count; i++) { + if (ph->file_table[i].ignore_range_array) { + heap_free(ph->file_table[i].ignore_range_array); + ph->file_table[i].ignore_range_array = NULL; + } + if (ph->file_table[i].retain_range_array) { + heap_free(ph->file_table[i].retain_range_array); + ph->file_table[i].retain_range_array = NULL; + } + } + + heap_free(ph->file_table); + ph->file_table = NULL; + } }
#define TICKS_PER_SEC 10000000 @@ -452,178 +649,67 @@ static void posix_time_to_file_time(ULONG timestamp, FILETIME *ft) ft->dwHighDateTime = (DWORD)(ticks >> 32); }
-/* Get the next range to ignore in the old file. - * fi->next_i must be initialized before use */ -static ULONG next_ignored_range(const struct input_file_info *fi, size_t index, ULONG old_file_size, ULONG *end) -{ - ULONG start = old_file_size; - *end = old_file_size; - /* if patching is unnecessary, the ignored ranges are skipped during crc calc */ - if (fi->next_i < fi->ignore_range_count && fi->stream_size != 0) - { - start = fi->ignore_table[fi->next_i].OffsetInOldFile; - *end = max(start + fi->ignore_table[fi->next_i].LengthInBytes, index); - start = max(start, index); - } - return start; -} - -/* Get the next range to retain from the old file. - * fi->next_r must be initialized before use */ -static ULONG next_retained_range_old(const struct input_file_info *fi, size_t index, ULONG old_file_size, ULONG *end) -{ - ULONG start = old_file_size; - *end = old_file_size; - if (fi->next_r < fi->retain_range_count) - { - start = fi->retain_table[fi->next_r].OffsetInOldFile; - *end = max(start + fi->retain_table[fi->next_r].LengthInBytes, index); - start = max(start, index); - } - return start; -} - -/* Get the next range to retain in the new file. - * fi->next_r must be initialized before use */ -static ULONG next_retained_range_new(const struct input_file_info *fi, size_t index, ULONG new_file_size, ULONG *end) -{ - ULONG start = new_file_size; - *end = new_file_size; - if (fi->next_r < fi->retain_range_count) - { - start = fi->retain_table[fi->next_r].OffsetInNewFile; - *end = max(start + fi->retain_table[fi->next_r].LengthInBytes, index); - start = max(start, index); - } - return start; -} - -/* Find the next range in the old file which must be assumed zero-filled during crc32 calc +/* Save the retain ranges into an allocated buffer. */ -static ULONG next_zeroed_range(struct input_file_info *fi, size_t index, ULONG old_file_size, ULONG *end) +static PUCHAR save_retained_ranges( + const BYTE *file_buf, ULONG file_size, + const PATCH_RETAIN_RANGE *retain_range_array, ULONG retain_range_count, + BOOL from_new) { - ULONG start = old_file_size; - ULONG end_i; - ULONG start_i; - ULONG end_r; - ULONG start_r; - - *end = old_file_size; - - start_i = next_ignored_range(fi, index, old_file_size, &end_i); - start_r = next_retained_range_old(fi, index, old_file_size, &end_r); + PUCHAR buffer, ptr; + ULONG i; + ULONG offset_in_file; + ULONG allocation_size;
- if (start_i < start_r) - { - start = start_i; - *end = end_i; - ++fi->next_i; - } - else - { - start = start_r; - *end = end_r; - ++fi->next_r; + allocation_size = 0; + for (i = 0; i < retain_range_count; i++) { + allocation_size += retain_range_array[i].LengthInBytes; } - return start; -} - -/* Use the crc32 of the input file to match the file with an entry in the patch file table - */ -struct input_file_info *find_matching_old_file(const struct patch_file_header *ph, const BYTE *old_file_view, ULONG old_file_size) -{ - unsigned i;
- for (i = 0; i < ph->input_file_count; ++i) - { - DWORD crc32 = 0; - ULONG index; + buffer = VirtualAlloc(NULL, allocation_size, MEM_COMMIT, PAGE_READWRITE); + if (buffer) { + ptr = buffer; + for (i = 0; i < retain_range_count; i++) { + if (from_new) { + offset_in_file = retain_range_array[i].OffsetInNewFile; + } else { + offset_in_file = retain_range_array[i].OffsetInOldFile; + }
- if (ph->file_table[i].input_size != old_file_size) - continue; + if (offset_in_file <= file_size + && (offset_in_file + retain_range_array[i].LengthInBytes) <= file_size) + { + memcpy(ptr, &file_buf[offset_in_file], retain_range_array[i].LengthInBytes); + }
- ph->file_table[i].next_i = 0; - for (index = 0; index < old_file_size; ) - { - ULONG end; - ULONG start = next_zeroed_range(ph->file_table + i, index, old_file_size, &end); - crc32 = RtlComputeCrc32(crc32, old_file_view + index, start - index); - crc32 = compute_zero_crc32(crc32, end - start); - index = end; + ptr += retain_range_array[i].LengthInBytes; } - if (ph->file_table[i].crc32 == crc32) - return ph->file_table + i; - } - return NULL; -} - -/* Zero-fill ignored ranges in the old file data for decoder matching - */ -static void zero_fill_ignored_ranges(BYTE *old_file_buf, const struct input_file_info *fi) -{ - size_t i; - for (i = 0; i < fi->ignore_range_count; ++i) - { - memset(old_file_buf + fi->ignore_table[i].OffsetInOldFile, - 0, - fi->ignore_table[i].LengthInBytes); } -}
-/* Zero-fill retained ranges in the old file data for decoder matching - */ -static void zero_fill_retained_ranges(BYTE *old_file_buf, BYTE *new_file_buf, const struct input_file_info *fi) -{ - size_t i; - for (i = 0; i < fi->retain_range_count; ++i) - { - memset(old_file_buf + fi->retain_table[i].OffsetInOldFile, - 0, - fi->retain_table[i].LengthInBytes); - } + return buffer; }
-/* Copy the retained ranges to the new file buffer +/* Copy the saved retained ranges to the new file buffer */ -static void apply_retained_ranges(const BYTE *old_file_buf, BYTE *new_file_buf, const struct input_file_info *fi) +static void apply_retained_ranges( + BYTE *new_file_buf, const PATCH_RETAIN_RANGE *retain_range_array, + const ULONG retain_range_count, const BYTE *retain_buffer) { - size_t i; - - if (old_file_buf == NULL) + if (retain_buffer == NULL) return;
- for (i = 0; i < fi->retain_range_count; ++i) + for (ULONG i = 0; i < retain_range_count; ++i) { - memcpy(new_file_buf + fi->retain_table[i].OffsetInNewFile, - old_file_buf + fi->retain_table[i].OffsetInOldFile, - fi->retain_table[i].LengthInBytes); - } -} - -/* Compute the crc32 for the new file, assuming zero for the retained ranges - */ -static DWORD compute_target_crc32(struct input_file_info *fi, const BYTE *new_file_buf, ULONG new_file_size) -{ - DWORD crc32 = 0; - ULONG index; + memcpy(new_file_buf + retain_range_array[i].OffsetInNewFile, + retain_buffer, + retain_range_array[i].LengthInBytes);
- qsort(fi->retain_table, fi->retain_range_count, sizeof(fi->retain_table[0]), compare_retained_range_new); - fi->next_r = 0; - - for (index = 0; index < new_file_size; ) - { - ULONG end; - ULONG start = next_retained_range_new(fi, index, new_file_size, &end); - ++fi->next_r; - crc32 = RtlComputeCrc32(crc32, new_file_buf + index, start - index); - crc32 = compute_zero_crc32(crc32, end - start); - index = end; - } - return crc32; + retain_buffer += retain_range_array[i].LengthInBytes; + } }
/* Fixup a given mapped image's relocation table for a new image base. */ -BOOL rebase_image( +static BOOL rebase_image( IMAGE_NT_HEADERS32 UNALIGNED *nt_headers, PUCHAR mapped_image_base, ULONG mapped_image_size, ULONG new_image_base) { @@ -715,7 +801,7 @@ BOOL rebase_image( }
/* Remove all bound imports for a given mapped image. */ -BOOL unbind_image( +static BOOL unbind_image( IMAGE_NT_HEADERS32 UNALIGNED *nt_headers, PUCHAR mapped_image, ULONG image_size) { BOOL result; @@ -810,7 +896,7 @@ BOOL unbind_image( }
/* Force all lock prefixes to the x86 LOCK (F0h) opcode in a given mapped image. */ -BOOL normalize_lock_prefixes_in_image( +static BOOL normalize_lock_prefixes_in_image( IMAGE_NT_HEADERS32 UNALIGNED *nt_headers, PUCHAR mapped_image, ULONG image_size) { BOOL result = FALSE; @@ -968,181 +1054,1407 @@ int normalize_old_file_image( return result + 1; }
-DWORD apply_patch_to_file_by_buffers(const BYTE *patch_file_view, const ULONG patch_file_size, - const BYTE *old_file_view, ULONG old_file_size, - BYTE **pnew_file_buf, const ULONG new_file_buf_size, ULONG *new_file_size, - FILETIME *new_file_time, - const ULONG apply_option_flags, - PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx, - const BOOL test_header_only) +static void patch_transform_PE_mark_non_executable( + IMAGE_NT_HEADERS32 UNALIGNED *nt_header, PUCHAR mapped_image, ULONG image_size, + BYTE *hintmap, BOOL force) { - DWORD err = ERROR_SUCCESS; - struct input_file_info *file_info; - struct patch_file_header ph; - size_t buf_size; - BYTE *new_file_buf = NULL; - BYTE *decode_buf = NULL; + IMAGE_SECTION_HEADER UNALIGNED *section_table; + ULONG section_size_raw; + ULONG directory_offset; + ULONG directory_rva; + ULONG directory_size; + ULONG i;
- if (pnew_file_buf == NULL) - { - if (!test_header_only && !(apply_option_flags & APPLY_OPTION_TEST_ONLY)) - return ERROR_INVALID_PARAMETER; - } - else - { - new_file_buf = *pnew_file_buf; + /* Mark all non-executable sections in the hint map */ + section_table = IMAGE_FIRST_SECTION(nt_header); + for (i = 0; i < nt_header->FileHeader.NumberOfSections; i++) { + if ((section_table[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) == 0 + && section_table[i].PointerToRawData < image_size) { + section_size_raw = section_table[i].SizeOfRawData; + if (section_size_raw > (image_size - section_table[i].PointerToRawData)) + section_size_raw = image_size - section_table[i].PointerToRawData; + memset(&hintmap[section_table[i].PointerToRawData], 0x01, section_size_raw); + } + } + + /* Mark headers in the hint map */ + if (nt_header->OptionalHeader.SizeOfHeaders > image_size) { + nt_header->OptionalHeader.SizeOfHeaders = image_size; + } + memset(hintmap, 0x03, nt_header->OptionalHeader.SizeOfHeaders); + + /* Mark all other non-executable regions in the hint map */ + for (i = 0; i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i++) { + directory_rva = nt_header->OptionalHeader.DataDirectory[i].VirtualAddress; + directory_size = nt_header->OptionalHeader.DataDirectory[i].Size; + if (directory_rva && directory_size) { + directory_offset = image_rva_to_file_offset(nt_header, directory_rva, mapped_image, image_size); + if ((directory_offset || force) && directory_offset < image_size) { + if (directory_size > image_size - directory_offset) { + directory_size = image_size - directory_offset; + } + memset(&hintmap[directory_offset], 0x03, directory_size); + } + } } +}
- if (old_file_view == NULL) - old_file_size = 0; +static ULONG get_new_rva_from_xfrm_table(struct patch_transform_table *xfrm_tbl, ULONG old_rva) +{ + ULONG mid, low = 0, high = xfrm_tbl->count; + struct patch_transform_entry *const entries = xfrm_tbl->entries; + + /* Use a binary search to locate the new rva. */ + while (low < high) { + mid = (low + high) >> 1; + if (entries[mid].old_rva >= old_rva) { + if (entries[mid].old_rva == old_rva) { + return old_rva + (LONG)(entries[mid].new_rva - entries[mid].old_rva); + } + high = mid; + } else { + low = mid + 1; + } + }
- if (read_header(&ph, patch_file_view, patch_file_size)) - { - err = ph.err; - goto free_patch_header; + if (!low) { + return old_rva; }
- if (new_file_size != NULL) - *new_file_size = (ULONG)ph.patched_size; + mid = low - 1; + return old_rva + (LONG)(entries[mid].new_rva - entries[mid].old_rva); +}
- if (new_file_buf != NULL && new_file_buf_size < ph.patched_size) - { - err = ERROR_INSUFFICIENT_BUFFER; - goto free_patch_header; - } +struct reloc_entry { + ULONG rva; + UCHAR type; + USHORT hiadj; +};
- file_info = find_matching_old_file(&ph, old_file_view, old_file_size); - if (file_info == NULL) - { - err = ERROR_PATCH_WRONG_FILE; - goto free_patch_header; - } - if (file_info->input_size != old_file_size) - { - err = ERROR_PATCH_CORRUPT; - goto free_patch_header; - } - if (file_info->stream_size == 0 && (apply_option_flags & APPLY_OPTION_FAIL_IF_EXACT)) - { - err = ERROR_PATCH_NOT_NECESSARY; - goto free_patch_header; - } - if (file_info->stream_size != 0 - && file_info->input_size > ((ph.flags & PATCH_OPTION_USE_LZX_LARGE) ? MAX_LARGE_WINDOW : MAX_NORMAL_WINDOW)) - { - /* interleaved by default but not the same as PATCH_OPTION_INTERLEAVE_FILES */ - FIXME("interleaved LZXD decompression is not supported.\n"); - err = ERROR_PATCH_PACKAGE_UNSUPPORTED; - goto free_patch_header; - } +static int __cdecl reloc_entry_compare(const void *a, const void *b) +{ + const LONG diff = ((const struct reloc_entry *)a)->rva + - ((const struct reloc_entry *)b)->rva; + if (diff > 0) return 1; + else if (diff < 0) return -1; + else return 0; +}
- if (test_header_only) - goto free_patch_header; +static void patch_transform_PE_relocations( + IMAGE_NT_HEADERS32 UNALIGNED *nt_headers, PUCHAR mapped_image, ULONG image_size, + struct patch_transform_table *xfrm_tbl, BYTE *hintmap) +{ + PUCHAR mapped_image_end; + ULONG image_base_va; + ULONG reloc_dir_offset; + ULONG reloc_dir_size; + LONG reloc_dir_remaining; + ULONG reloc_target_va; + ULONG reloc_target_rva; + USHORT reloc_type_offset; + UCHAR reloc_type; + USHORT reloc_offset; + ULONG reloc_fixup_rva; + PUCHAR reloc_fixup; + IMAGE_BASE_RELOCATION UNALIGNED *reloc_block; + IMAGE_BASE_RELOCATION UNALIGNED *reloc_block_base; + ULONG reloc_block_base_va; + ULONG reloc_count; + USHORT UNALIGNED *reloc; + USHORT UNALIGNED *reloc_start; + ULONG new_rva; + struct reloc_entry *reloc_entries; + ULONG reloc_entry_count; + ULONG reloc_entry_index; + IMAGE_SECTION_HEADER UNALIGNED *section_table; + ULONG section_count; + ULONG i; + PUCHAR p;
- /* missing lzxd stream means it's a header test extract */ - if (file_info->stream_start + file_info->stream_size > ph.end) - { - err = ERROR_PATCH_NOT_AVAILABLE; - goto free_patch_header; - } + mapped_image_end = mapped_image + image_size; + image_base_va = nt_headers->OptionalHeader.ImageBase; + reloc_dir_offset = image_directory_offset_and_size( + nt_headers, IMAGE_DIRECTORY_ENTRY_BASERELOC, &reloc_dir_size, mapped_image, image_size);
- buf_size = old_file_size + ph.patched_size; - decode_buf = new_file_buf; - if (new_file_buf == NULL || new_file_buf_size < buf_size) + /* Even if relocations do not exist in this PE, then scan the mapped image for + possible relocations that point to within valid mapped image bounds. */ + if (!reloc_dir_offset || (reloc_dir_offset + reloc_dir_size) > image_size) { - /* decode_buf must have room for both files, so allocate a new buffer if - * necessary. This will be returned to the caller if new_file_buf == NULL */ - decode_buf = VirtualAlloc(NULL, buf_size, MEM_COMMIT, PAGE_READWRITE); - if (decode_buf == NULL) + if (nt_headers->FileHeader.Machine == IMAGE_FILE_MACHINE_I386) { - err = GetLastError(); - goto free_patch_header; - } - } - - if (old_file_view != NULL) - memcpy(decode_buf, old_file_view, file_info->input_size); + IMAGE_SECTION_HEADER UNALIGNED *const first_section = IMAGE_FIRST_SECTION(nt_headers); + ULONG const first_section_va = image_base_va + first_section->VirtualAddress; + ULONG const image_end_va = image_base_va + nt_headers->OptionalHeader.SizeOfImage; + for (p = &mapped_image[first_section->PointerToRawData]; p < (mapped_image_end - 4); p++) { + reloc_target_va = *(ULONG UNALIGNED *)p; + + /* is this a possible rva? */ + if (reloc_target_va >= first_section_va && reloc_target_va < image_end_va) { + *(ULONG UNALIGNED *)(hintmap + (p - mapped_image)) |= 0x01010101; + + reloc_target_rva = reloc_target_va - image_base_va; + new_rva = get_new_rva_from_xfrm_table(xfrm_tbl, reloc_target_rva); + if (new_rva != reloc_target_rva) { + *(ULONG UNALIGNED *)p = image_base_va + new_rva; + }
- zero_fill_ignored_ranges(decode_buf, file_info); - zero_fill_retained_ranges(decode_buf, decode_buf + file_info->input_size, file_info); + /* advance by 3 so next loop we advance to the next dword */ + p += 4-1; + } + } + }
- if (file_info->stream_size != 0) - { - err = decode_lzxd_stream(file_info->stream_start, file_info->stream_size, - decode_buf, ph.patched_size, file_info->input_size, - ph.flags & PATCH_OPTION_USE_LZX_LARGE, - progress_fn, progress_ctx); - } - else if (file_info->input_size == ph.patched_size) - { - /* files are identical so copy old to new. copying is avoidable but rare */ - memcpy(decode_buf + file_info->input_size, decode_buf, ph.patched_size); - } - else - { - err = ERROR_PATCH_CORRUPT; - goto free_decode_buf; + return; } + + /* update the hint map with the base reloc directory */ + memset(&hintmap[reloc_dir_offset], 0x03, reloc_dir_size);
- if(err != ERROR_SUCCESS) - { - if (err == ERROR_PATCH_DECODE_FAILURE) - FIXME("decode failure: data corruption or bug.\n"); - goto free_decode_buf; + /* allocate memory for reloc cache entries */ + reloc_entries = VirtualAlloc(NULL, sizeof(struct reloc_entry) * (reloc_dir_size / sizeof(USHORT)), + MEM_COMMIT, PAGE_READWRITE); + if (!reloc_entries) { + return; }
- apply_retained_ranges(old_file_view, decode_buf + file_info->input_size, file_info); + /* loop through the relocation table, updating the new rvas. */ + reloc_entry_count = 0; + reloc_block = (IMAGE_BASE_RELOCATION UNALIGNED *)&mapped_image[reloc_dir_offset]; + reloc_dir_remaining = (LONG)reloc_dir_size; + while (reloc_dir_remaining > 0) { + if ((PUCHAR)reloc_block > (mapped_image_end - sizeof(IMAGE_BASE_RELOCATION))) { + throw_pe_fmt_exception(); + }
- if (ph.patched_crc32 != compute_target_crc32(file_info, decode_buf + file_info->input_size, ph.patched_size)) - { - err = ERROR_PATCH_CORRUPT; - goto free_decode_buf; - } + if ((reloc_block->SizeOfBlock <= (ULONG)reloc_dir_remaining) && + (reloc_block->SizeOfBlock > sizeof(IMAGE_BASE_RELOCATION))) {
- /* retained ranges must be ignored for this test */ - if ((apply_option_flags & APPLY_OPTION_FAIL_IF_EXACT) - && file_info->input_size == ph.patched_size - && memcmp(decode_buf, decode_buf + file_info->input_size, ph.patched_size) == 0) - { - err = ERROR_PATCH_NOT_NECESSARY; - goto free_decode_buf; - } + reloc_block_base = (IMAGE_BASE_RELOCATION UNALIGNED *)(mapped_image + + image_rva_to_file_offset(nt_headers, reloc_block->VirtualAddress, mapped_image, image_size)); + if (reloc_block_base) {
- if (!(apply_option_flags & APPLY_OPTION_TEST_ONLY)) - { - if (new_file_buf == NULL) + reloc_block_base_va = reloc_block->VirtualAddress + image_base_va; + reloc = (PUSHORT)((ULONG_PTR)reloc_block + sizeof(IMAGE_BASE_RELOCATION)); + reloc_count = (reloc_block->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT); + while (reloc_count--) { + if ((PUCHAR)reloc > (mapped_image_end - sizeof(USHORT))) { + throw_pe_fmt_exception(); + } + + reloc_type_offset = *reloc; + reloc_type = (UCHAR)(reloc_type_offset >> 12); + if (reloc_type != IMAGE_REL_BASED_ABSOLUTE) { + + reloc_offset = (USHORT)(reloc_type_offset & 0xFFF); + reloc_fixup = (PUCHAR)reloc_block_base + reloc_offset; + reloc_fixup_rva = reloc_block_base_va + reloc_offset - image_base_va; + + reloc_entries[reloc_entry_count].rva = get_new_rva_from_xfrm_table(xfrm_tbl, reloc_fixup_rva); + reloc_entries[reloc_entry_count].type = reloc_type; + + switch (reloc_type) { + + case IMAGE_REL_BASED_HIGH: + case IMAGE_REL_BASED_LOW: + if (reloc_fixup < mapped_image_end) { + *(USHORT UNALIGNED *)(hintmap + (reloc_fixup - mapped_image)) |= 0x0101; + } + break; + + case IMAGE_REL_BASED_HIGHLOW: + if (reloc_fixup < (mapped_image_end - sizeof(LONG))) { + *(ULONG UNALIGNED *)(hintmap + (reloc_fixup - mapped_image)) |= 0x01010101; + + reloc_target_va = *(ULONG UNALIGNED *)reloc_fixup; + reloc_target_rva = reloc_target_va - image_base_va; + + new_rva = get_new_rva_from_xfrm_table(xfrm_tbl, reloc_target_rva); + if (new_rva != reloc_target_rva) { + *(ULONG UNALIGNED *)reloc_fixup = image_base_va + new_rva; + } + } + break; + + case IMAGE_REL_BASED_HIGHADJ: + if (reloc_fixup < mapped_image_end) { + *(USHORT UNALIGNED *)(hintmap + (reloc_fixup - mapped_image)) |= 0x0101; + } + + ++reloc; + --reloc_count; + + reloc_entries[reloc_entry_count].hiadj = *reloc; + break; + } + + ++reloc_entry_count; + } + + ++reloc; + } + } + } + + reloc_dir_remaining -= reloc_block->SizeOfBlock; + reloc_block = (IMAGE_BASE_RELOCATION UNALIGNED *)((ULONG_PTR)reloc_block + reloc_block->SizeOfBlock); + } + + /* sort reloc entries by rva. */ + if (reloc_entry_count > 1) { + qsort(reloc_entries, reloc_entry_count, sizeof(struct reloc_entry), reloc_entry_compare); + } + + reloc_dir_remaining = (LONG)reloc_dir_size; + + /* calculate the remaining reloc directory size using the ".reloc" section */ + section_table = IMAGE_FIRST_SECTION(nt_headers); + section_count = nt_headers->FileHeader.NumberOfSections; + for (i = 0; i < section_count; i++) { + if ((PUCHAR)§ion_table[i] < mapped_image + || (PUCHAR)§ion_table[i] > (mapped_image_end - sizeof(IMAGE_SECTION_HEADER))) { + throw_pe_fmt_exception(); + } + + /* tolower hack check for ".reloc " */ + if ((*(ULONGLONG UNALIGNED *)§ion_table[i].Name | 0x2020202020202020) == 0x2020636F6C65722E) { + if (reloc_dir_offset >= section_table[i].PointerToRawData + && reloc_dir_offset < (section_table[i].PointerToRawData + + section_table[i].SizeOfRawData)) { + reloc_dir_remaining = + (section_table[i].PointerToRawData + section_table[i].SizeOfRawData) + - reloc_dir_offset; + } + } + } + + reloc_dir_remaining &= ~1; /* make even value */ + reloc_block = (IMAGE_BASE_RELOCATION UNALIGNED *)(mapped_image + reloc_dir_offset); + reloc_entry_index = 0; + while (reloc_dir_remaining > sizeof(IMAGE_BASE_RELOCATION) + && reloc_entry_index < reloc_entry_count) + { + reloc_block->VirtualAddress = (DWORD)(reloc_entries[reloc_entry_index].rva & 0xFFFFF000); + reloc_start = (PUSHORT)((ULONG_PTR)reloc_block + sizeof(IMAGE_BASE_RELOCATION)); + reloc = reloc_start; + reloc_dir_remaining -= sizeof(IMAGE_BASE_RELOCATION); + while (reloc_dir_remaining > 0 + && (reloc_entry_index < reloc_entry_count) + && (reloc_entries[reloc_entry_index].rva & 0xFFFFF000) == reloc_block->VirtualAddress) { - /* caller will VirtualFree the buffer */ - new_file_buf = decode_buf; - *pnew_file_buf = new_file_buf; + reloc_type_offset = ((USHORT)reloc_entries[reloc_entry_index].type << 12) | + ((USHORT)reloc_entries[reloc_entry_index].rva & 0xFFF); + *reloc++ = reloc_type_offset; + + reloc_dir_remaining -= sizeof(USHORT); + + if (reloc_dir_remaining > 0 + && ((ULONG)reloc_entries[reloc_entry_index].type << 12) == IMAGE_REL_BASED_HIGHADJ) { + + *reloc++ = reloc_entries[reloc_entry_index].hiadj; + reloc_dir_remaining -= sizeof(USHORT); + } + + ++reloc_entry_index; + } + + if (reloc_dir_remaining > 0 && ((ULONG_PTR)reloc & 2) != 0) { + *reloc++ = 0; + reloc_dir_remaining -= sizeof(USHORT); + } + + reloc_block->SizeOfBlock = (ULONG)(((ULONG_PTR)reloc - (ULONG_PTR)reloc_start) + sizeof(IMAGE_BASE_RELOCATION)); + reloc_block = (IMAGE_BASE_RELOCATION UNALIGNED *)((ULONG_PTR)reloc_block + reloc_block->SizeOfBlock); + } + + VirtualFree(reloc_entries, 0, MEM_RELEASE); +} + +static void patch_transform_PE_infer_relocs_x86( + IMAGE_NT_HEADERS32 UNALIGNED *nt_headers, PUCHAR mapped_image_base, ULONG mapped_image_size, + struct patch_transform_table *xfrm_tbl, BYTE *hintmap) +{ + const ULONG image_base_va = nt_headers->OptionalHeader.ImageBase; + const ULONG image_end_va = image_base_va + nt_headers->OptionalHeader.SizeOfImage; + const PUCHAR mapped_image_end = &mapped_image_base[mapped_image_size]; + PUCHAR p = &mapped_image_base[nt_headers->OptionalHeader.SizeOfHeaders]; + for (; p < (mapped_image_end - 4); p++) { + + /* Is this a possible valid rva? */ + const ULONG reloc_target_va = *(ULONG UNALIGNED *)p; + if (reloc_target_va >= image_base_va && reloc_target_va < image_end_va) { + + /* Check the hintmap. */ + const ULONG infer_mask = *(ULONG UNALIGNED *)(hintmap + (p - mapped_image_base)); + if ((infer_mask & 0x02020202) == 0) { + + const ULONG reloc_target_rva = reloc_target_va - image_base_va; + const ULONG new_rva = get_new_rva_from_xfrm_table(xfrm_tbl, reloc_target_rva); + if (new_rva != reloc_target_rva) { + *(ULONG UNALIGNED *)p = image_base_va + new_rva; + } + + *(ULONG UNALIGNED *)(hintmap + (p - mapped_image_base)) = infer_mask | 0x01010101; + + /* advance by 3 so next loop we advance to the next dword */ + p += 4-1; + } + } + } +} + +static void patch_transform_PE_infer_relocs( + IMAGE_NT_HEADERS32 UNALIGNED *nt_headers, PUCHAR mapped_image_base, ULONG mapped_image_size, + struct patch_transform_table *xfrm_tbl, BYTE *hintmap) +{ + ULONG reloc_dir_offset; + ULONG reloc_dir_size = 0; + + reloc_dir_offset = image_directory_offset_and_size(nt_headers, IMAGE_DIRECTORY_ENTRY_BASERELOC, + &reloc_dir_size, mapped_image_base, mapped_image_size); + if (reloc_dir_offset && reloc_dir_size + reloc_dir_offset <= mapped_image_size) { + patch_transform_PE_relocations(nt_headers, mapped_image_base, mapped_image_size, xfrm_tbl, hintmap); + } + + switch(nt_headers->FileHeader.Machine) { + case IMAGE_FILE_MACHINE_I386: + patch_transform_PE_infer_relocs_x86(nt_headers, mapped_image_base, mapped_image_size, xfrm_tbl, hintmap); + break; + + default: + break; + } +} + +static void patch_transform_PE_imports( + IMAGE_NT_HEADERS32 UNALIGNED *nt_headers, PUCHAR mapped_image_base, ULONG mapped_image_size, + struct patch_transform_table *xfrm_tbl, BYTE* hintmap) +{ + ULONG import_dir_offset; + ULONG import_dir_size; + IMAGE_IMPORT_DESCRIPTOR UNALIGNED *import_desc; + ULONG thunk_offset; + IMAGE_THUNK_DATA32 UNALIGNED *thunk_data_start; + IMAGE_THUNK_DATA32 UNALIGNED *thunk_data; + IMAGE_IMPORT_BY_NAME UNALIGNED *import_by_name; + ULONG import_by_name_offset; + + import_dir_offset = image_directory_offset_and_size(nt_headers, IMAGE_DIRECTORY_ENTRY_IMPORT, + &import_dir_size, mapped_image_base, mapped_image_size); + + /* Does the mapped image contain imports? */ + if (!import_dir_offset || (import_dir_offset + import_dir_size) > mapped_image_size) { + return; + } + + /* Update the hint map of the import directory. */ + memset(&hintmap[import_dir_offset], 0x03, import_dir_size); + + /* Loop through the import table, updating the new rvas. */ + import_desc = (IMAGE_IMPORT_DESCRIPTOR UNALIGNED *)(mapped_image_base + import_dir_offset); + while (import_desc->OriginalFirstThunk) { + if (import_desc->TimeDateStamp == 0) { /* 0 if not bound */ + + thunk_offset = image_rva_to_file_offset(nt_headers, import_desc->OriginalFirstThunk, + mapped_image_base, mapped_image_size); + thunk_data_start = (IMAGE_THUNK_DATA32 UNALIGNED *)(mapped_image_base + thunk_offset); + thunk_data = thunk_data_start; + while (thunk_data->u1.Ordinal != 0) { + if (!IMAGE_SNAP_BY_ORDINAL32(thunk_data->u1.Ordinal)) { + import_by_name_offset = image_rva_to_file_offset(nt_headers, thunk_data->u1.AddressOfData, + mapped_image_base, mapped_image_size); + import_by_name = (IMAGE_IMPORT_BY_NAME UNALIGNED *)(mapped_image_base + import_by_name_offset); + memset(&hintmap[import_by_name_offset], 0x03, + sizeof(IMAGE_IMPORT_BY_NAME) + strlen((char*)import_by_name->Name)); + thunk_data->u1.AddressOfData = get_new_rva_from_xfrm_table(xfrm_tbl, thunk_data->u1.AddressOfData); + } + thunk_data++; + } + memset(&hintmap[thunk_offset], 0x03, ((size_t)thunk_data - (size_t)thunk_data_start)); + + thunk_offset = image_rva_to_file_offset(nt_headers, import_desc->FirstThunk, + mapped_image_base, mapped_image_size); + thunk_data_start = (IMAGE_THUNK_DATA32 UNALIGNED *)(mapped_image_base + thunk_offset); + thunk_data = thunk_data_start; + while (thunk_data->u1.Ordinal != 0) { + if (!IMAGE_SNAP_BY_ORDINAL32(thunk_data->u1.Ordinal)) { + import_by_name_offset = image_rva_to_file_offset(nt_headers, thunk_data->u1.AddressOfData, + mapped_image_base, mapped_image_size); + import_by_name = (IMAGE_IMPORT_BY_NAME UNALIGNED *)(mapped_image_base + import_by_name_offset); + memset(&hintmap[import_by_name_offset], 0x03, + sizeof(IMAGE_IMPORT_BY_NAME) + strlen((char*)import_by_name->Name)); + thunk_data->u1.AddressOfData = get_new_rva_from_xfrm_table(xfrm_tbl, thunk_data->u1.AddressOfData); + } + thunk_data++; + } + memset(&hintmap[thunk_offset], 0x03, ((size_t)thunk_data - (size_t)thunk_data_start)); + } + + import_desc->Name = get_new_rva_from_xfrm_table(xfrm_tbl, import_desc->Name); + import_desc->OriginalFirstThunk = get_new_rva_from_xfrm_table(xfrm_tbl, import_desc->OriginalFirstThunk); + import_desc->FirstThunk = get_new_rva_from_xfrm_table(xfrm_tbl, import_desc->FirstThunk); + + import_desc++; + } +} + +static void patch_transform_PE_exports( + IMAGE_NT_HEADERS32 UNALIGNED *nt_headers, PUCHAR mapped_image_base, ULONG mapped_image_size, + struct patch_transform_table *xfrm_tbl, BYTE *hintmap) +{ + IMAGE_EXPORT_DIRECTORY UNALIGNED *export_directory; + ULONG UNALIGNED *export; + ULONG export_count; + PUCHAR mapped_image_end; + ULONG offset; + ULONG export_dir_offset; + ULONG export_dir_size; + + mapped_image_end = mapped_image_base + mapped_image_size; + + export_dir_offset = image_directory_offset_and_size(nt_headers, IMAGE_DIRECTORY_ENTRY_EXPORT, + &export_dir_size, mapped_image_base, mapped_image_size); + + /* Does the mapped image contain exports? */ + if (!export_dir_offset || (export_dir_offset + export_dir_size) > mapped_image_size) { + return; + } + + /* Update the hint map of the export directory. */ + memset(hintmap + export_dir_offset, 0x03, export_dir_size); + + export_directory = (IMAGE_EXPORT_DIRECTORY UNALIGNED *)(mapped_image_base + export_dir_offset); + + /* Update the hint map and rvas for the AddressOfFunctions table. */ + export_count = export_directory->NumberOfFunctions; + offset = image_rva_to_file_offset(nt_headers, export_directory->AddressOfFunctions, + mapped_image_base, mapped_image_size); + if ((offset + export_count * sizeof(ULONG)) <= mapped_image_size) { + memset(&hintmap[offset], 0x03, export_count * sizeof(ULONG)); + } + export = (PULONG)(mapped_image_base + offset); + while (export_count--) { + if ((PUCHAR)&export[1] > mapped_image_end) { + break; + } + *export = get_new_rva_from_xfrm_table(xfrm_tbl, *export); + export++; + } + + /* Update the hint map and rvas for the AddressOfNames table. */ + export_count = export_directory->NumberOfNames; + offset = image_rva_to_file_offset(nt_headers, export_directory->AddressOfNames, + mapped_image_base, mapped_image_size); + if ((offset + export_count * sizeof(ULONG)) <= mapped_image_size) { + memset(&hintmap[offset], 0x03, export_count * sizeof(ULONG)); + } + export = (PULONG)(mapped_image_base + offset); + while (export_count--) { + if ((PUCHAR)&export[1] > mapped_image_end) { + break; + } + *export = get_new_rva_from_xfrm_table(xfrm_tbl, *export); + export++; + } + + /* Update the hint map for the AddressOfNameOrdinals table. */ + export_count = export_directory->NumberOfNames; + offset = image_rva_to_file_offset(nt_headers, export_directory->AddressOfNameOrdinals, + mapped_image_base, mapped_image_size); + if ((offset + export_count * sizeof(USHORT)) <= mapped_image_size) { + memset(&hintmap[offset], 0x03, export_count * sizeof(USHORT)); + } + + /* Update export directory rvas. */ + export_directory->Name = get_new_rva_from_xfrm_table(xfrm_tbl, export_directory->Name); + export_directory->AddressOfFunctions = get_new_rva_from_xfrm_table(xfrm_tbl, export_directory->AddressOfFunctions); + export_directory->AddressOfNames = get_new_rva_from_xfrm_table(xfrm_tbl, export_directory->AddressOfNames); + export_directory->AddressOfNameOrdinals = get_new_rva_from_xfrm_table(xfrm_tbl, export_directory->AddressOfNameOrdinals); +} + +static void patch_transform_PE_relative_jmps( + IMAGE_NT_HEADERS32 UNALIGNED *nt_headers, PUCHAR mapped_image_base, ULONG mapped_image_size, + struct patch_transform_table *xfrm_tbl, BYTE *hintmap) +{ + ULONG image_size; + IMAGE_SECTION_HEADER UNALIGNED *section_table; + ULONG section_count; + PBYTE section_start, section_end; + ULONG section_size; + ULONG section_offset; + ULONG section_rva; + LONG disp, new_disp; + ULONG ins_rva, target_rva; + ULONG new_ins_rva, new_target_rva; + ULONG target_offset; + ULONG i, j; + PBYTE p, hint; + BYTE opcode; + BOOL needs_patch; + + image_size = nt_headers->OptionalHeader.SizeOfImage; + section_table = IMAGE_FIRST_SECTION(nt_headers); + section_count = nt_headers->FileHeader.NumberOfSections; + + for (i = 0; i < section_count; i++) { + if ((PUCHAR)§ion_table[i] < mapped_image_base + || (PUCHAR)§ion_table[i+1] > &mapped_image_base[mapped_image_size]) { + throw_pe_fmt_exception(); + } + + /* If this section is executable, then scan it for relative jump instructions. */ + if (section_table[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) { + + section_rva = section_table[i].VirtualAddress; + section_size = min(section_table[i].Misc.VirtualSize, section_table[i].SizeOfRawData); + section_offset = section_table[i].PointerToRawData; + section_start = (PUCHAR)mapped_image_base + section_offset; + + if (section_offset < mapped_image_size + && (section_offset + section_size) <= mapped_image_size + && nt_headers->FileHeader.Machine == IMAGE_FILE_MACHINE_I386) + { + section_end = section_start + section_size - 5; /* 5 is size of relative jmp instruction */ + + for (p = section_start; p < section_end; p++) + { + if ((*p == 0xE9) /* jmp rel32 (E9) */ + || (*p == 0x0F && (p[1] & 0xF0) == 0x80 && (++p < section_end)) /* jcc rel32 (0F 8?) */ + ) + { + /* Check the hint map in case this has already been patched. */ + needs_patch = FALSE; + hint = &hintmap[section_offset + ((ULONG_PTR)p - (ULONG_PTR)section_start) - 1]; + if ((*p & 0xF0) != 0x80 || (*hint & 1) == 0) { + hint++; + j = 0; + while ((*hint++ & 1) == 0) { + if (++j >= 5) { + needs_patch = TRUE; + break; + } + } + } + + if (needs_patch) { + disp = *(LONG UNALIGNED *)(p + 1); + if (disp > 127 || disp < -128) + { + ins_rva = section_rva + (ULONG)((p + 5) - section_start); + target_rva = ins_rva + disp; + if (target_rva < image_size) + { + target_offset = image_rva_to_file_offset(nt_headers, target_rva, + mapped_image_base, mapped_image_size); + if ((hintmap[target_offset] & 1) == 0) { + new_target_rva = get_new_rva_from_xfrm_table(xfrm_tbl, target_rva); + new_ins_rva = get_new_rva_from_xfrm_table(xfrm_tbl, ins_rva); + new_disp = new_target_rva - new_ins_rva; + if (new_disp > 127 || new_disp < -128) { + if (new_disp != disp) { + *(LONG UNALIGNED *)(p + 1) = new_disp; + } + } + else { + opcode = *p; + if (opcode == 0xE9) { + *p = 0xEB; + p[1] = (char)new_disp; + } else { + *p = (char)new_disp; + *(p - 1) = (opcode & 0x0F) | 0x70; + } + } + p += 4; + } + } + } + } + } + } + } + } + } +} + +static void patch_transform_PE_relative_calls( + IMAGE_NT_HEADERS32 UNALIGNED *nt_headers, PUCHAR mapped_image_base, ULONG mapped_image_size, + struct patch_transform_table *xfrm_tbl, PUCHAR hintmap) +{ + IMAGE_SECTION_HEADER UNALIGNED *section_table; + ULONG sizeof_image; + PUCHAR section_start, section_end; + ULONG section_size; + ULONG section_offset; + ULONG section_rva; + LONG disp, new_disp; + ULONG ins_rva, target_rva; + ULONG new_ins_rva, new_target_rva; + ULONG target_offset; + ULONG i, j; + PUCHAR p, hint; + BOOL needs_patch; + + sizeof_image = nt_headers->OptionalHeader.SizeOfImage; + section_table = IMAGE_FIRST_SECTION(nt_headers); + + for (i = 0; i < nt_headers->FileHeader.NumberOfSections; i++) { + if ((PUCHAR)§ion_table[i] < mapped_image_base + || (PUCHAR)§ion_table[i+1] > &mapped_image_base[mapped_image_size]) { + throw_pe_fmt_exception(); + } + + /* If this section is executable, then scan it for relative jump instructions. */ + if (section_table[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) { + + section_rva = section_table[i].VirtualAddress; + section_size = min(section_table[i].Misc.VirtualSize, section_table[i].SizeOfRawData); + section_offset = section_table[i].PointerToRawData; + section_start = (PUCHAR)mapped_image_base + section_offset; + + if (section_offset < mapped_image_size + && (section_offset + section_size) <= mapped_image_size + && nt_headers->FileHeader.Machine == IMAGE_FILE_MACHINE_I386) + { + section_end = section_start + section_size - 5; /* 5 is size of relative jmp instruction */ + + for (p = section_start; p < section_end; p++) + { + if (*p == 0xE8) /* call rel32 (E8) */ + { + /* Check the hint map in case this has already been patched. */ + needs_patch = FALSE; + j = 0; + hint = &hintmap[section_offset + (p - section_start)]; + while ((*hint++ & 1) == 0) { + if (++j >= 5) { + needs_patch = TRUE; + break; + } + } + + if (needs_patch) { + disp = *(LONG UNALIGNED *)(p + 1); + ins_rva = section_rva + (ULONG)((ULONG_PTR)p - (ULONG_PTR)section_start) + 5; + target_rva = ins_rva + disp; + if (target_rva < sizeof_image) { + target_offset = image_rva_to_file_offset(nt_headers, target_rva, + mapped_image_base, mapped_image_size); + if ((hintmap[target_offset] & 1) == 0) { + new_target_rva = get_new_rva_from_xfrm_table(xfrm_tbl, target_rva); + new_ins_rva = get_new_rva_from_xfrm_table(xfrm_tbl, ins_rva); + new_disp = new_target_rva - new_ins_rva; + if (new_disp != disp) { + *(LONG UNALIGNED *)(p + 1) = new_disp; + } + p += 4; + } + } + } + } + } + } + } + } +} + +struct transform_resource_context { /* i386 | x64 */ + struct patch_transform_table* xfrm_tbl; /* 0x00 | 0x00 */ + PUCHAR res_dir_base; /* 0x04 | 0x08 */ + PUCHAR res_dir_end; /* 0x08 | 0x10 */ + ULONG res_dir_size; /* 0x0C | 0x18 */ + ULONG cursor; /* 0x10 | 0x1C */ + ULONG new_res_time; /* 0x14 | 0x20 */ + ULONG sizeof_image; /* 0x18 | 0x24 */ + PUCHAR image_base; /* 0x1C | 0x28 */ + ULONG old_rva; /* 0x20 | 0x30 */ + ULONG new_rva; /* 0x24 | 0x34 */ + BOOL out_of_bounds; /* 0x28 | 0x38 */ +}; + +static void transform_resources_recursive_old_compat( + struct transform_resource_context *ctx, + IMAGE_RESOURCE_DIRECTORY UNALIGNED *res_dir) +{ + ULONG entry_count, i; + IMAGE_RESOURCE_DIRECTORY_ENTRY UNALIGNED *entry; + IMAGE_RESOURCE_DATA_ENTRY UNALIGNED *res_data; + IMAGE_RESOURCE_DIRECTORY UNALIGNED *recursive_res_dir; + ULONG offset_to_data; + ULONG name_offset; + ULONG new_offset; + ULONG new_rva; + + if (((PUCHAR)res_dir + sizeof(IMAGE_RESOURCE_DIRECTORY)) < ctx->res_dir_end) { + + ctx->cursor += sizeof(IMAGE_RESOURCE_DIRECTORY); + + if (ctx->cursor <= ctx->res_dir_size) { + + if (res_dir->TimeDateStamp != ctx->new_res_time) { + res_dir->TimeDateStamp = ctx->new_res_time; + } + + entry_count = res_dir->NumberOfNamedEntries + res_dir->NumberOfIdEntries; + entry = (IMAGE_RESOURCE_DIRECTORY_ENTRY UNALIGNED *)((PUCHAR)res_dir + sizeof(IMAGE_RESOURCE_DIRECTORY)); + + for (i = 0; i < entry_count; i++, entry++) { + if ((PUCHAR)entry >= (ctx->res_dir_end - sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY))) { + break; + } + + offset_to_data = entry->OffsetToData & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY; + if (entry->OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY) { + /* Recurse into next resource directory. */ + recursive_res_dir = (IMAGE_RESOURCE_DIRECTORY UNALIGNED *)&ctx->res_dir_base[offset_to_data]; + if ((PUCHAR)recursive_res_dir >= ctx->image_base) { + transform_resources_recursive_old_compat(ctx, recursive_res_dir); + } + + } else { + /* Update rva of valid resource data entry. */ + res_data = (IMAGE_RESOURCE_DATA_ENTRY UNALIGNED *)&ctx->res_dir_base[offset_to_data]; + if ((PUCHAR)res_data > ctx->res_dir_base + && (PUCHAR)res_data < (ctx->res_dir_end - sizeof(IMAGE_RESOURCE_DATA_ENTRY))) + { + ctx->cursor += res_data->Size; + if (ctx->cursor > ctx->res_dir_size) { + return; + } + + new_rva = get_new_rva_from_xfrm_table(ctx->xfrm_tbl, res_data->OffsetToData); + if (res_data->OffsetToData != new_rva) { + res_data->OffsetToData = new_rva; + } + } + } + + new_offset = get_new_rva_from_xfrm_table(ctx->xfrm_tbl, offset_to_data + ctx->old_rva) - ctx->new_rva; + if (offset_to_data != new_offset) { + entry->OffsetToData ^= (new_offset ^ entry->OffsetToData) & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY; + } + + if (entry->Name & IMAGE_RESOURCE_NAME_IS_STRING) { + name_offset = entry->Name & ~IMAGE_RESOURCE_NAME_IS_STRING; + new_offset = get_new_rva_from_xfrm_table(ctx->xfrm_tbl, name_offset + ctx->old_rva) - ctx->new_rva; + if (name_offset != new_offset) { + entry->Name ^= (new_offset ^ entry->Name) & ~IMAGE_RESOURCE_NAME_IS_STRING; + } + } + + ctx->cursor += sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY); + if (ctx->cursor > ctx->res_dir_size) { + return; + } + } } - memmove(new_file_buf, decode_buf + old_file_size, ph.patched_size); } +}
- if (new_file_time != NULL) +static void patch_transform_PE_resources_old_compat( + IMAGE_NT_HEADERS32 UNALIGNED *nt_headers, PUCHAR mapped_image, ULONG image_size, + ULONG new_res_time, struct patch_transform_table *xfrm_tbl) +{ + struct transform_resource_context ctx; + const PUCHAR mapped_image_end = &mapped_image[image_size]; + + ctx.res_dir_base = image_directory_mapped_address(nt_headers, IMAGE_DIRECTORY_ENTRY_RESOURCE, + &ctx.res_dir_size, mapped_image, image_size); + if (ctx.res_dir_base && ctx.res_dir_size >= sizeof(IMAGE_RESOURCE_DIRECTORY)) { + ctx.res_dir_end = ctx.res_dir_base + ctx.res_dir_size; + if (ctx.res_dir_end >= mapped_image_end) { + ctx.res_dir_end = mapped_image_end; + } + + ctx.cursor = 0; + ctx.out_of_bounds = FALSE; + + ctx.old_rva = image_directory_rva_and_size(nt_headers, IMAGE_DIRECTORY_ENTRY_RESOURCE, + NULL, mapped_image, image_size); + ctx.new_rva = get_new_rva_from_xfrm_table(xfrm_tbl, ctx.old_rva); + + ctx.new_res_time = new_res_time; + ctx.sizeof_image = nt_headers->OptionalHeader.SizeOfImage; + ctx.image_base = mapped_image; + ctx.xfrm_tbl = xfrm_tbl; + + if (ctx.res_dir_base >= mapped_image) { + transform_resources_recursive_old_compat(&ctx, + (IMAGE_RESOURCE_DIRECTORY UNALIGNED *)ctx.res_dir_base); + } + } +} + +static void transform_resources_recursive( + struct transform_resource_context *ctx, + IMAGE_RESOURCE_DIRECTORY UNALIGNED *res_dir) +{ + IMAGE_RESOURCE_DIRECTORY_ENTRY UNALIGNED *entry; + IMAGE_RESOURCE_DATA_ENTRY UNALIGNED *res_data; + IMAGE_RESOURCE_DIRECTORY UNALIGNED *recursive_res_dir; + ULONG i; + ULONG entry_count; + ULONG offset_to_data; + ULONG name_offset; + ULONG new_offset; + ULONG new_rva; + + if ((PUCHAR)res_dir < ctx->res_dir_base + || ((PUCHAR)res_dir + sizeof(IMAGE_RESOURCE_DIRECTORY)) > ctx->res_dir_end) { + ctx->out_of_bounds = TRUE; + return; + } + + ctx->cursor += sizeof(IMAGE_RESOURCE_DIRECTORY); + if (ctx->cursor > ctx->res_dir_size) { + ctx->out_of_bounds = TRUE; + return; + } + + if (res_dir->TimeDateStamp != ctx->new_res_time) { + res_dir->TimeDateStamp = ctx->new_res_time; + } + + entry = (IMAGE_RESOURCE_DIRECTORY_ENTRY UNALIGNED *)((PUCHAR)res_dir + sizeof(IMAGE_RESOURCE_DIRECTORY)); + entry_count = res_dir->NumberOfNamedEntries + res_dir->NumberOfIdEntries; + + for (i = 0; i < entry_count; i++, entry++) { + if (((PUCHAR)entry + sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY)) > ctx->res_dir_end) { + ctx->out_of_bounds = TRUE; + return; + } + + ctx->cursor += sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY); + if (ctx->cursor > ctx->res_dir_size) { + ctx->out_of_bounds = TRUE; + return; + } + + offset_to_data = entry->OffsetToData & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY; + if (entry->OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY) { + if (ctx->res_dir_size >= sizeof(IMAGE_RESOURCE_DIRECTORY) + && (offset_to_data + sizeof(IMAGE_RESOURCE_DIRECTORY)) <= ctx->res_dir_size) { + + /* Recurse into next resource directory. */ + recursive_res_dir = (IMAGE_RESOURCE_DIRECTORY UNALIGNED *)&ctx->res_dir_base[offset_to_data]; + if ((PUCHAR)recursive_res_dir >= ctx->res_dir_base) { + transform_resources_recursive(ctx, recursive_res_dir); + } else { + ctx->out_of_bounds = TRUE; + } + + if (ctx->cursor > ctx->res_dir_size) { + ctx->out_of_bounds = TRUE; + return; + } + } else { + ctx->out_of_bounds = TRUE; + } + + } else { + /* Update rva of valid resource data entry. */ + res_data = (IMAGE_RESOURCE_DATA_ENTRY UNALIGNED *)&ctx->res_dir_base[offset_to_data]; + if ((PUCHAR)res_data < ctx->res_dir_base + && ((PUCHAR)res_data + sizeof(IMAGE_RESOURCE_DATA_ENTRY)) > ctx->res_dir_end) { + ctx->out_of_bounds = TRUE; + } else { + + new_rva = get_new_rva_from_xfrm_table(ctx->xfrm_tbl, res_data->OffsetToData); + if (res_data->OffsetToData != new_rva) { + res_data->OffsetToData = new_rva; + } + } + } + + new_offset = get_new_rva_from_xfrm_table(ctx->xfrm_tbl, offset_to_data + ctx->old_rva) - ctx->new_rva; + if (offset_to_data != new_offset) { + entry->OffsetToData ^= (new_offset ^ entry->OffsetToData) & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY; + } + + if (entry->Name & IMAGE_RESOURCE_NAME_IS_STRING) { + name_offset = entry->Name & ~IMAGE_RESOURCE_NAME_IS_STRING; + new_offset = get_new_rva_from_xfrm_table(ctx->xfrm_tbl, name_offset + ctx->old_rva) - ctx->new_rva; + if (entry->NameOffset != new_offset) { + entry->Name ^= (new_offset ^ entry->Name) & ~IMAGE_RESOURCE_NAME_IS_STRING; + } + } + } +} + +static BOOL patch_transform_PE_resources( + IMAGE_NT_HEADERS32 UNALIGNED *nt_headers, PUCHAR mapped_image, SIZE_T image_size, + ULONG new_res_time, struct patch_transform_table *xfrm_tbl) +{ + BOOL result; + struct transform_resource_context ctx; + + ctx.res_dir_base = image_directory_mapped_address(nt_headers, IMAGE_DIRECTORY_ENTRY_RESOURCE, + &ctx.res_dir_size, mapped_image, image_size); + if (ctx.res_dir_base) { + ctx.res_dir_end = ctx.res_dir_base + ctx.res_dir_size; + ctx.cursor = 0; + ctx.image_base = mapped_image; + ctx.sizeof_image = nt_headers->OptionalHeader.SizeOfImage; + ctx.old_rva = image_directory_rva_and_size(nt_headers, IMAGE_DIRECTORY_ENTRY_RESOURCE, + NULL, mapped_image, image_size); + ctx.new_rva = get_new_rva_from_xfrm_table(xfrm_tbl, ctx.old_rva); + ctx.new_res_time = new_res_time; + ctx.xfrm_tbl = xfrm_tbl; + ctx.out_of_bounds = FALSE; + transform_resources_recursive(&ctx, (IMAGE_RESOURCE_DIRECTORY UNALIGNED *)ctx.res_dir_base); + result = !ctx.out_of_bounds; + } else { + result = TRUE; + } + + return result; +} + +static BOOL patch_coff_image( + PULONG transform_option_flags, IMAGE_NT_HEADERS32 UNALIGNED *nt_headers, + PUCHAR old_file_buffer, ULONG old_file_size, ULONG new_res_time, + struct patch_transform_table* xfrm_tbl, unsigned char *hintmap) +{ + PUCHAR local_hintmap = NULL; + const ULONG transform_flags = *transform_option_flags; + + /* initialize the hintmap */ + if (!hintmap) { + local_hintmap = VirtualAlloc(NULL, old_file_size, MEM_COMMIT, PAGE_READWRITE); + if (!local_hintmap) { + return FALSE; + } + hintmap = local_hintmap; + } + memset(hintmap, 0x00, old_file_size); + + /* Order of transformations may matter here! */ + if (transform_flags & (PATCH_TRANSFORM_PE_RESOURCE_2 | PATCH_TRANSFORM_PE_IRELOC_2)) + { + patch_transform_PE_mark_non_executable(nt_headers, old_file_buffer, old_file_size, hintmap, FALSE); + + if ((transform_flags & PATCH_TRANSFORM_NO_IMPORTS) == 0) { + patch_transform_PE_imports(nt_headers, old_file_buffer, old_file_size, xfrm_tbl, hintmap); + } + + if ((transform_flags & PATCH_TRANSFORM_NO_EXPORTS) == 0) { + patch_transform_PE_exports(nt_headers, old_file_buffer, old_file_size, xfrm_tbl, hintmap); + } + + if ((transform_flags & PATCH_TRANSFORM_NO_RESOURCE) == 0) { + patch_transform_PE_resources(nt_headers, old_file_buffer, old_file_size, new_res_time, xfrm_tbl); + } + + if ((transform_flags & PATCH_TRANSFORM_NO_RELOCS) == 0) { + if (transform_flags & PATCH_TRANSFORM_PE_IRELOC_2) { + patch_transform_PE_infer_relocs(nt_headers, old_file_buffer, old_file_size, xfrm_tbl, hintmap); + } else { + patch_transform_PE_relocations(nt_headers, old_file_buffer, old_file_size, xfrm_tbl, hintmap); + } + } + + if ((transform_flags & PATCH_TRANSFORM_NO_RELJMPS) == 0) { + patch_transform_PE_relative_jmps(nt_headers, old_file_buffer, old_file_size, xfrm_tbl, hintmap); + } + + if ((transform_flags & PATCH_TRANSFORM_NO_RELCALLS) == 0) { + patch_transform_PE_relative_calls(nt_headers, old_file_buffer, old_file_size, xfrm_tbl, hintmap); + } + + } else { + + patch_transform_PE_mark_non_executable(nt_headers, old_file_buffer, old_file_size, hintmap, TRUE); + + if ((transform_flags & PATCH_TRANSFORM_NO_RELOCS) == 0) { + patch_transform_PE_relocations(nt_headers, old_file_buffer, old_file_size, xfrm_tbl, hintmap); + } + + if ((transform_flags & PATCH_TRANSFORM_NO_IMPORTS) == 0) { + patch_transform_PE_imports(nt_headers, old_file_buffer, old_file_size, xfrm_tbl, hintmap); + } + + if ((transform_flags & PATCH_TRANSFORM_NO_EXPORTS) == 0) { + patch_transform_PE_exports(nt_headers, old_file_buffer, old_file_size, xfrm_tbl, hintmap); + } + + if ((transform_flags & PATCH_TRANSFORM_NO_RELJMPS) == 0) { + patch_transform_PE_relative_jmps(nt_headers, old_file_buffer, old_file_size, xfrm_tbl, hintmap); + } + + if ((transform_flags & PATCH_TRANSFORM_NO_RELCALLS) == 0) { + patch_transform_PE_relative_calls(nt_headers, old_file_buffer, old_file_size, xfrm_tbl, hintmap); + } + + if ((transform_flags & PATCH_TRANSFORM_NO_RESOURCE) == 0) { + patch_transform_PE_resources_old_compat(nt_headers, old_file_buffer, old_file_size, new_res_time, xfrm_tbl); + } + } + + if (local_hintmap != NULL) { + VirtualFree(local_hintmap, 0, MEM_RELEASE); + } + + return TRUE; +} + +/* Performs patches on 32-bit PE images. */ +static DWORD perform_patches_on_old_file_image( + PULONG transform_option_flags, PVOID old_file_buffer, ULONG old_file_size, + ULONG new_file_res_time, struct patch_transform_table* xfrm_tbl) +{ + DWORD err = ERROR_SUCCESS; + IMAGE_NT_HEADERS32 UNALIGNED *nt_headers; + + nt_headers = image_get_nt_headers(old_file_buffer, old_file_size); + if (nt_headers) { + *transform_option_flags |= PATCH_EXTRA_HAS_PATCH_TRANSFORMS; + if (!patch_coff_image(transform_option_flags, nt_headers, old_file_buffer, + old_file_size, new_file_res_time, xfrm_tbl, NULL)) { + err = GetLastError(); + } + } else { + err = ERROR_BAD_EXE_FORMAT; + } + + return err; +} + +DWORD apply_patch_to_file_by_buffers( + const BYTE *patch_file_view, const ULONG patch_file_size, + const BYTE *old_file_view, ULONG old_file_size, + BYTE **pnew_file_buf, const ULONG new_file_buf_size, + ULONG *new_file_actual_size, FILETIME *new_file_time, + const ULONG apply_option_flags, + PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx) +{ + DWORD err = ERROR_SUCCESS; + struct patch_file_header ph = {0}; + struct old_file_info *file_info; + struct old_file_info *found_file; + ULONG fileno; + ULONG i; + int normalize_result; + BOOL patched; + ULONG old_crc32; + ULONG patched_crc32; + ULONG buf_size; + BYTE *in_new_file_buf = NULL; + BYTE *out_new_file_buf = NULL; + BYTE *new_file_buf = NULL; + BYTE *old_file_buf = NULL; + BYTE *decode_buf = NULL; + BYTE *retain_buffer = NULL; + + if (new_file_actual_size != NULL) { + *new_file_actual_size = 0; + } + + if (new_file_time != NULL) { new_file_time->dwLowDateTime = 0; new_file_time->dwHighDateTime = 0; + } + + if (pnew_file_buf == NULL) { + if (!(apply_option_flags & APPLY_OPTION_TEST_ONLY)) { + return ERROR_INVALID_PARAMETER; + }
- /* the meaning of PATCH_OPTION_NO_TIMESTAMP is inverted for decoding */ - if (ph.flags & PATCH_OPTION_NO_TIMESTAMP) - posix_time_to_file_time(ph.timestamp, new_file_time); + } else { + in_new_file_buf = *pnew_file_buf; }
-free_decode_buf: - if(decode_buf != NULL && decode_buf != new_file_buf) + if (old_file_view == NULL) { + old_file_size = 0; + } + + /* read the patch file header */ + if (read_patch_header(&ph, patch_file_view, patch_file_size)) { + err = ph.err; + goto cleanup; + } + + if ((ph.flags & PATCH_OPTION_NO_TIMESTAMP) == 0) { + TRACE("new file time is %lX\n", ph.new_file_time); + if (new_file_time != NULL) { + if (ph.new_file_time) { + posix_time_to_file_time(ph.new_file_time, new_file_time); + } + } + } + + if (new_file_actual_size != NULL) { + *new_file_actual_size = (ULONG)ph.patched_size; + } + + if (in_new_file_buf != NULL && new_file_buf_size < ph.patched_size) { + err = ERROR_INSUFFICIENT_BUFFER; + goto cleanup; + } + + buf_size = ph.patched_size + old_file_size; + decode_buf = in_new_file_buf; + if (in_new_file_buf == NULL || new_file_buf_size < buf_size) + { + /* decode_buf must have room for both files, so allocate a new buffer if + * necessary. This will be returned to the caller if new_file_buf == NULL */ + decode_buf = VirtualAlloc(NULL, buf_size, MEM_COMMIT, PAGE_READWRITE); + if (decode_buf == NULL) { + err = GetLastError(); + goto cleanup; + } + new_file_buf = &decode_buf[old_file_size]; + } + + if (old_file_view != NULL) { + old_file_buf = decode_buf; + memcpy(old_file_buf, old_file_view, old_file_size); + } + + patched = FALSE; + retain_buffer = NULL; + found_file = NULL; + + /* Check if the old file matches the new file. */ + if (old_file_size == ph.patched_size) + { + file_info = &ph.file_table[0]; + if (file_info->retain_range_count) + { + /* Save the new file retain ranges for applying later. */ + retain_buffer = save_retained_ranges(old_file_buf, old_file_size, + file_info->retain_range_array, file_info->retain_range_count, TRUE); + if (!retain_buffer) { + err = GetLastError(); + goto cleanup; + } + + /* Zero out retain ranges for crc32 checksum verification. */ + for (i = 0; i < file_info->retain_range_count; i++) { + if (file_info->retain_range_array[i].OffsetInNewFile <= old_file_size + && (file_info->retain_range_array[i].OffsetInNewFile + + file_info->retain_range_array[i].LengthInBytes) <= old_file_size) { + memset(&old_file_buf[file_info->retain_range_array[i].OffsetInNewFile], + 0, + file_info->retain_range_array[i].LengthInBytes); + patched = TRUE; + } + } + } + + /* Verify if the file matches the expected crc32 checksum. */ + old_crc32 = RtlComputeCrc32(0, old_file_buf, old_file_size); + normalize_result = NORMALIZE_RESULT_FAILURE; + if (old_crc32 != ph.patched_crc32) { + + /* Try again with normalizations applied to the file. */ + normalize_result = normalize_old_file_image(old_file_buf, old_file_size, + ph.flags, NULL, ph.new_image_base, ph.new_image_time, + NULL, 0, NULL, 0); + if (normalize_result == NORMALIZE_RESULT_FAILURE) { + err = ERROR_PATCH_CORRUPT; + goto cleanup; + } else if (normalize_result >= NORMALIZE_RESULT_SUCCESS_MODIFIED) { + patched = TRUE; + } + + old_crc32 = RtlComputeCrc32(0, old_file_buf, old_file_size); + } + + /* Did we calculate the expected crc32 checksum? */ + if (old_crc32 == ph.patched_crc32) { + + /* Check if patching was necessary. */ + if ((apply_option_flags & APPLY_OPTION_FAIL_IF_CLOSE) != 0 + || ((apply_option_flags & APPLY_OPTION_FAIL_IF_EXACT) != 0 && patched)) + { + err = ERROR_PATCH_NOT_NECESSARY; + goto cleanup; + } + + if (!(apply_option_flags & APPLY_OPTION_TEST_ONLY) && ph.patched_size) { + /* Files are identical so copy old to new. */ + memcpy(new_file_buf, old_file_buf, old_file_size); + found_file = file_info; + } + } + + /* Cleanup the retain buffer if expected match was not found. */ + if (!found_file && retain_buffer) { + VirtualFree(retain_buffer, 0, MEM_RELEASE); + retain_buffer = NULL; + } + } + + /* If the old file didn't match the new file, then check if the old file + * matches one of the files in the patch file table. + */ + for (fileno = 0; !found_file && fileno < ph.old_file_count; ++fileno) + { + file_info = &ph.file_table[fileno]; + + if (file_info->old_size == old_file_size) + { + if (patched) { + memcpy(old_file_buf, old_file_view, old_file_size); + patched = FALSE; + } + + if (file_info->retain_range_count && !(apply_option_flags & APPLY_OPTION_TEST_ONLY)) { + retain_buffer = save_retained_ranges(old_file_buf, old_file_size, + file_info->retain_range_array, file_info->retain_range_count, FALSE); + if (!retain_buffer) { + err = GetLastError(); + goto cleanup; + } + } + + normalize_result = normalize_old_file_image(old_file_buf, old_file_size, + ph.flags, NULL, ph.new_image_base, ph.new_image_time, + file_info->ignore_range_array, file_info->ignore_range_count, + file_info->retain_range_array, file_info->retain_range_count); + if (normalize_result == NORMALIZE_RESULT_FAILURE) { + err = ERROR_PATCH_CORRUPT; + goto cleanup; + } else if (normalize_result >= NORMALIZE_RESULT_SUCCESS_MODIFIED) { + patched = TRUE; + } + + old_crc32 = RtlComputeCrc32(0, old_file_buf, old_file_size); + if (old_crc32 == file_info->old_crc32) + { + /* Check if patching is necessary. */ + if (!ph.patched_size) { + if (!old_file_size && ((apply_option_flags & APPLY_OPTION_FAIL_IF_CLOSE) != 0 + || (apply_option_flags & APPLY_OPTION_FAIL_IF_EXACT) != 0)) { + err = ERROR_PATCH_NOT_NECESSARY; + } + goto cleanup; + } + + if (file_info->patch_stream_size != 0) + { + /* Check if only testing is necessary. */ + if (apply_option_flags & APPLY_OPTION_TEST_ONLY) { + goto cleanup; + } + + /* Missing lzxd stream means it's a header test extract. */ + if ((file_info->patch_stream_start + file_info->patch_stream_size) > ph.end) { + err = ERROR_PATCH_NOT_AVAILABLE; + goto cleanup; + } + + if (file_info->old_size > ((ph.flags & PATCH_OPTION_USE_LZX_LARGE) + ? MAX_LARGE_WINDOW : MAX_NORMAL_WINDOW)) + { + /* interleaved by default but not the same as PATCH_OPTION_INTERLEAVE_FILES + * TODO: Add interleaved stream support. + */ + FIXME("interleaved LZXD decompression is not supported.\n"); + err = ERROR_PATCH_PACKAGE_UNSUPPORTED; + goto cleanup; + } + + /* Apply the necessary patches according to the patch transform table. */ + if (file_info->xfrm_tbl.count) { + err = perform_patches_on_old_file_image(&ph.extra_flags, old_file_buf, + file_info->old_size, ph.new_res_time, &file_info->xfrm_tbl); + } + + if (err == ERROR_SUCCESS) { + + /* Decode the compressed patch data. + * N.B. This will copy the decoded data into the new file buffer. + */ + err = decode_lzxd_stream( + file_info->patch_stream_start, file_info->patch_stream_size, + decode_buf, ph.patched_size, file_info->old_size, + ph.flags & PATCH_OPTION_USE_LZX_LARGE, + progress_fn, progress_ctx); + } + + if (err == ERROR_SUCCESS) { + + /* Check for expected patched file crc32 checksum. + * + * N.B. This must be done with the retain and ignore ranges zeroed, + * in other words the retain ranges must be applied after checking + * for the expected checksum. + */ + patched_crc32 = RtlComputeCrc32(0, &decode_buf[file_info->old_size], ph.patched_size); + if (patched_crc32 == ph.patched_crc32) { + found_file = file_info; + break; + } + } + + } else { + + /* Check if patching was not necessary. */ + if (old_file_size == ph.patched_size && + (apply_option_flags & APPLY_OPTION_FAIL_IF_CLOSE) != 0 ) { + err = ERROR_PATCH_NOT_NECESSARY; + goto cleanup; + } + + /* Chck if only testing is necessary. */ + if (apply_option_flags & APPLY_OPTION_TEST_ONLY) { + goto cleanup; + } + + /* Files should be identical, so copy old to new. */ + memcpy(new_file_buf, old_file_buf, old_file_size); + found_file = file_info; + break; + } + } + } + + if (retain_buffer) { + VirtualFree(retain_buffer, 0, MEM_RELEASE); + retain_buffer = NULL; + } + } + + /* Check if no suitable matching file was found for patching. */ + if (!found_file) { + err = ERROR_PATCH_WRONG_FILE; + goto cleanup; + } + + /* Apply the retained ranges to the new file. */ + apply_retained_ranges(new_file_buf, + found_file->retain_range_array, found_file->retain_range_count, retain_buffer); + + if (!(apply_option_flags & APPLY_OPTION_TEST_ONLY)) + { + if (in_new_file_buf == NULL) { + out_new_file_buf = decode_buf; + /* caller will VirtualFree the buffer */ + *pnew_file_buf = out_new_file_buf; + } else { + out_new_file_buf = in_new_file_buf; + } + memmove(out_new_file_buf, new_file_buf, ph.patched_size); + } + +cleanup: + if (retain_buffer) { + VirtualFree(retain_buffer, 0, MEM_RELEASE); + } + + if (decode_buf != NULL && decode_buf != *pnew_file_buf) { VirtualFree(decode_buf, 0, MEM_RELEASE); + }
-free_patch_header: free_header(&ph);
return err; }
-BOOL apply_patch_to_file_by_handles(HANDLE patch_file_hndl, HANDLE old_file_hndl, HANDLE new_file_hndl, - const ULONG apply_option_flags, - PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx, - const BOOL test_header_only) +BOOL apply_patch_to_file_by_handles( + HANDLE patch_file_hndl, HANDLE old_file_hndl, HANDLE new_file_hndl, + ULONG apply_option_flags, PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx) { LARGE_INTEGER patch_size; LARGE_INTEGER old_size; @@ -1157,7 +2469,7 @@ BOOL apply_patch_to_file_by_handles(HANDLE patch_file_hndl, HANDLE old_file_hndl DWORD err = ERROR_SUCCESS;
/* truncate the output file if required, or set the handle to invalid */ - if (test_header_only || (apply_option_flags & APPLY_OPTION_TEST_ONLY)) + if (apply_option_flags & APPLY_OPTION_TEST_ONLY) { new_file_hndl = INVALID_HANDLE_VALUE; } @@ -1218,10 +2530,8 @@ BOOL apply_patch_to_file_by_handles(HANDLE patch_file_hndl, HANDLE old_file_hndl
err = apply_patch_to_file_by_buffers(patch_buf, (ULONG)patch_size.QuadPart, old_buf, (ULONG)old_size.QuadPart, - &new_buf, 0, &new_size, - &new_time, - apply_option_flags, progress_fn, progress_ctx, - test_header_only); + &new_buf, 0, &new_size, &new_time, + apply_option_flags, progress_fn, progress_ctx);
if(err) goto free_new_buf; @@ -1261,10 +2571,9 @@ close_patch_map: return res; }
-BOOL apply_patch_to_file(LPCWSTR patch_file_name, LPCWSTR old_file_name, LPCWSTR new_file_name, - const ULONG apply_option_flags, - PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx, - const BOOL test_header_only) +BOOL apply_patch_to_file( + LPCWSTR patch_file_name, LPCWSTR old_file_name, LPCWSTR new_file_name, + const ULONG apply_option_flags, PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx) { HANDLE patch_hndl; HANDLE old_hndl = INVALID_HANDLE_VALUE; @@ -1289,7 +2598,7 @@ BOOL apply_patch_to_file(LPCWSTR patch_file_name, LPCWSTR old_file_name, LPCWSTR } }
- if (!test_header_only && !(apply_option_flags & APPLY_OPTION_TEST_ONLY)) + if (!(apply_option_flags & APPLY_OPTION_TEST_ONLY)) { new_hndl = CreateFileW(new_file_name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); if (new_hndl == INVALID_HANDLE_VALUE) @@ -1299,7 +2608,7 @@ BOOL apply_patch_to_file(LPCWSTR patch_file_name, LPCWSTR old_file_name, LPCWSTR } }
- res = apply_patch_to_file_by_handles(patch_hndl, old_hndl, new_hndl, apply_option_flags, progress_fn, progress_ctx, test_header_only); + res = apply_patch_to_file_by_handles(patch_hndl, old_hndl, new_hndl, apply_option_flags, progress_fn, progress_ctx); if(!res) err = GetLastError();
diff --git a/dlls/mspatcha/pa19.h b/dlls/mspatcha/pa19.h index e8a1fd3a51d..008b0f80bc4 100644 --- a/dlls/mspatcha/pa19.h +++ b/dlls/mspatcha/pa19.h @@ -36,15 +36,11 @@ DWORD apply_patch_to_file_by_buffers(const BYTE *patch_file_view, const ULONG pa BYTE **new_file_buf, const ULONG new_file_buf_size, ULONG *new_file_size, FILETIME *new_file_time, const ULONG apply_option_flags, - PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx, - const BOOL test_header_only); + PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx);
-BOOL apply_patch_to_file_by_handles(HANDLE patch_file_hndl, HANDLE old_file_hndl, HANDLE new_file_hndl, - const ULONG apply_option_flags, - PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx, - const BOOL test_header_only); +BOOL apply_patch_to_file_by_handles( + HANDLE patch_file_hndl, HANDLE old_file_hndl, HANDLE new_file_hndl, + ULONG apply_option_flags, PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx);
BOOL apply_patch_to_file(LPCWSTR patch_file_name, LPCWSTR old_file_name, LPCWSTR new_file_name, - const ULONG apply_option_flags, - PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx, - const BOOL test_header_only); + const ULONG apply_option_flags, PATCH_PROGRESS_CALLBACK *progress_fn, void *progress_ctx); diff --git a/include/patchapi.h b/include/patchapi.h index 95401545688..b2302987fc8 100644 --- a/include/patchapi.h +++ b/include/patchapi.h @@ -40,6 +40,9 @@ extern "C" { #define PATCH_OPTION_INTERLEAVE_FILES 0x40000000 #define PATCH_OPTION_RESERVED1 0x80000000
+#define PATCH_TRANSFORM_PE_RESOURCE_2 0x00000100 +#define PATCH_TRANSFORM_PE_IRELOC_2 0x00000200 + #define APPLY_OPTION_FAIL_IF_EXACT 0x00000001 #define APPLY_OPTION_FAIL_IF_CLOSE 0x00000002 #define APPLY_OPTION_TEST_ONLY 0x00000004