blob: 3807fc70a9e5c2d48512094de4e923b70951e54c [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2020-2021 Google, Inc. All rights reserved.
* Copyright (c) 2008-2010 VMware, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
#include "dr_api.h"
#include <stdlib.h> /* for realloc */
#include <assert.h>
#include <stddef.h> /* for offsetof */
#include <string.h> /* for memcpy */
#include <imagehlp.h>
#include <stdio.h>
/*
* NOTE - I haven't tried running this with the dynamorio.dll in this directory so it
* might not work. I've been using a tot build of core.
*
* ls *.dll > LIST
* for i in $( cat LIST ) ; do ../vista_hash.exe $i; done > ../OUTPUT
*/
void
dummy()
{
}
static bool v = false;
static bool vv = false;
#define VVERBOSE_PRINT \
if (vv) \
printf
#define VERBOSE_PRINT \
if (v) \
printf
#define ASSERT(x) ((x) ? 0 : dr_messagebox("Error %d %s\n", __LINE__, #x), 0)
#define ALIGN_BACKWARD(x, alignment) (((ptr_uint_t)x) & (~((alignment)-1)))
#define ALIGN_FORWARD(x, alignment) \
((((ptr_uint_t)x) + ((alignment)-1)) & (~((alignment)-1)))
bool
compare_pages(void *drcontext, byte *start1, byte *start2, uint start_offset)
{
static byte copy_buf1_i[2 * PAGE_SIZE + 100];
static byte copy_buf2_i[2 * PAGE_SIZE + 100];
static byte buf1_i[2 * PAGE_SIZE + 100];
static byte buf2_i[2 * PAGE_SIZE + 100];
byte *copy1 = (byte *)ALIGN_FORWARD(copy_buf1_i, PAGE_SIZE);
byte *copy2 = (byte *)ALIGN_FORWARD(copy_buf2_i, PAGE_SIZE);
byte *buf1 = (byte *)ALIGN_FORWARD(buf1_i, PAGE_SIZE);
byte *buf2 = (byte *)ALIGN_FORWARD(buf2_i, PAGE_SIZE);
byte *p1 = copy1 + start_offset, *p2 = copy2 + start_offset;
byte *b1 = buf1, *c1 = buf1, *b2 = buf2, *c2 = buf2;
int skipped_bytes1 = 0, skipped_identical_bytes1 = 0;
int skipped_bytes2 = 0, skipped_identical_bytes2 = 0;
/* we make a copy (zero extend the page) for decode to avoid walking to next,
* potentially invalid page */
memcpy(copy1, start1, PAGE_SIZE);
memcpy(copy2, start2, PAGE_SIZE);
/* Note - we do the compare ~1 instruction at a time, would be more effiecient
* to do the whole page and compare, but this makes it easier to track down where
* the differences originate from. */
while (p1 < copy1 + PAGE_SIZE) {
/* The idea is to do a lightweight decoding of the page and eliminate likely
* relocations from the instruction stream. I expect that relocations within
* the stream to be 4-byte immeds or 4-byte displacements with pointer like
* values. For now just using decode_sizeof to get the size of the instuction
* and based on that determine how much to chop off the end (instruction format
* is [...][disp][imm]) to remove the potential relocation. With better
* information from decode_sizeof (it knows whether an immed/disp is present or
* not and what offset it would be etc.) we could keep more of the bytes but
* this works for testing. What we'll miss unless we get really lucky is
* relocs in read only data (const string arrays, for dr the const decode
* tables full of pointers for ex.). */
/* How the assumptions work out. The assumption that we quickly get to the
* appropriate instruction frame seems valid. Most cases we synchronize very
* quickly, usually just a couple of bytes and very rarely more then 20.
* I Haven't seen any relocations in instructions that weren't caught
* below. However, so far only matching ~60% of sibling pages because of
* read only data relocations interspersed in the text sections. Instruction
* frame alignment isn't an issue that often and the second pass is catching
* most of those. */
while (p1 - copy1 <= p2 - copy2) {
uint num_bytes, i, num_prefix = 0;
uint size = decode_sizeof(drcontext, p1, &num_prefix);
size -= num_prefix;
num_bytes = num_prefix;
if (size == 0) {
size = 1; /* treat invalid as size == 1 */
num_bytes += 1;
} else if (size <= 4) {
num_bytes += size; /* no 4-byte disp or imm */
} else if (size <= 6) {
num_bytes += size - 4; /* could have 4-byte disp or immed */
} else if (size <= 7) {
num_bytes += size - 5; /* could have 4-byte disp and upto 1-byte immed
* or 4-byte immed */
} else if (size <= 9) {
num_bytes += size - 6; /* could have 4-byte disp and upto 2-byte immed
* or 4-byte immed */
} else {
num_bytes += size - 8; /* could have 4-byte disp and 4-byte immed */
}
for (i = 0; i < num_bytes; i++)
*b1++ = *p1++;
skipped_bytes1 += size - (num_bytes - num_prefix);
for (i = 0; i < size - (num_bytes - num_prefix); i++) {
if (*p1 == *(copy2 + (p1 - copy1)))
skipped_identical_bytes1++;
p1++;
}
}
while (p2 - copy2 < p1 - copy1) {
uint num_bytes, i, num_prefix = 0;
uint size = decode_sizeof(drcontext, p2, &num_prefix);
size -= num_prefix;
num_bytes = num_prefix;
if (size == 0) {
size = 1; /* treat invalid as size == 1 */
num_bytes += 1;
} else if (size <= 4) {
num_bytes += size;
} else if (size <= 6) {
num_bytes += size - 4;
} else if (size <= 7) {
num_bytes += size - 5;
} else if (size <= 9) {
num_bytes += size - 6;
} else {
num_bytes += size - 8;
}
for (i = 0; i < num_bytes; i++)
*b2++ = *p2++;
skipped_bytes2 += size - (num_bytes - num_prefix);
for (i = 0; i < size - (num_bytes - num_prefix); i++) {
if (*p2 == *(copy1 + (p2 - copy2)))
skipped_identical_bytes2++;
p2++;
}
}
/* Do check */
while (c1 < b1 && c2 < b2) {
if (*c1++ != *c2++) {
VVERBOSE_PRINT("Mismatch found near offset 0x%04x of page %08x\n",
p1 - copy1, start1);
return false;
}
}
}
ASSERT(skipped_bytes1 == skipped_bytes2);
ASSERT(skipped_identical_bytes1 == skipped_identical_bytes2);
VVERBOSE_PRINT("Match found! skipped=%d skipped_identical=%d\n", skipped_bytes1,
skipped_identical_bytes1);
return true;
}
/* Note that we should keep an eye for potential additional qualifier
* flags. Alternatively we may simply mask off ~0xff to allow for any
* future flags added here.
*/
#define PAGE_PROTECTION_QUALIFIERS (PAGE_GUARD | PAGE_NOCACHE | PAGE_WRITECOMBINE)
bool
prot_is_writable(uint prot)
{
prot &= ~PAGE_PROTECTION_QUALIFIERS;
return (prot == PAGE_READWRITE || prot == PAGE_WRITECOPY ||
prot == PAGE_EXECUTE_READWRITE || prot == PAGE_EXECUTE_WRITECOPY);
}
#define OPT_HDR(nt_hdr_p, field) ((nt_hdr_p)->OptionalHeader.field)
#define OPT_HDR_P(nt_hdr_p, field) (&((nt_hdr_p)->OptionalHeader.field))
bool
get_IAT_section_bounds(app_pc module_base, app_pc *iat_start, app_pc *iat_end)
{
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)module_base;
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)(((ptr_uint_t)dos) + dos->e_lfanew);
IMAGE_DATA_DIRECTORY *dir;
if (dos->e_magic != IMAGE_DOS_SIGNATURE || nt == NULL ||
nt->Signature != IMAGE_NT_SIGNATURE)
return false;
dir = &OPT_HDR(nt, DataDirectory)[IMAGE_DIRECTORY_ENTRY_IAT];
*iat_start = module_base + dir->VirtualAddress;
*iat_end = module_base + dir->VirtualAddress + dir->Size;
return true;
}
int
usage(char *name)
{
printf("Usage: %s [-v] [-vv] [-no_second_pass] [-second_pass_offset <val>] "
"[-no_assume_IAT_written] [-spin_for_debugger] <dll>\n",
name);
return -1;
}
int
main(int argc, char *argv[])
{
byte *dll_1, *dll_2, *p1, *p2, *iat_start1, *iat_end1, *iat_start2, *iat_end2;
bool has_iat = false;
MEMORY_BASIC_INFORMATION info;
void *drcontext = dr_standalone_init();
uint writable_pages = 0, reserved_pages = 0, IAT_pages = 0;
uint matched_pages = 0, second_matched_pages = 0, unmatched_pages = 0;
uint exact_match_pages = 0, exact_no_match_pages = 0;
char reloc_file[MAX_PATH] = { 0 }, orig_file[MAX_PATH], *input_file;
uint old_size = 0, new_size = 0;
uint old_base = 0, new_base = 0x69000000; /* unlikely to collide */
/* user specified option defaults */
uint arg_offs = 1;
bool use_second_pass = true;
bool assume_header_match = true;
uint second_pass_offset = 16; /* FIXME arbitrary, what's a good choice? */
bool assume_IAT_written = true;
bool spin_for_debugger = false;
if (argc < 2)
return usage(argv[0]);
while (argv[arg_offs][0] == '-') {
if (strcmp(argv[arg_offs], "-vv") == 0) {
vv = true;
} else if (strcmp(argv[arg_offs], "-v") == 0) {
v = true;
} else if (strcmp(argv[arg_offs], "-no_second_pass") == 0) {
use_second_pass = false;
} else if (strcmp(argv[arg_offs], "-second_pass_offset") == 0) {
if ((uint)argc <= arg_offs + 1)
return usage(argv[0]);
second_pass_offset = atoi(argv[++arg_offs]);
} else if (strcmp(argv[arg_offs], "-no_assume_IAT_written") == 0) {
assume_IAT_written = false;
} else if (strcmp(argv[arg_offs], "-spin_for_debugger") == 0) {
spin_for_debugger = true;
} else {
return usage(argv[0]);
}
arg_offs++;
}
input_file = argv[arg_offs++];
if (arg_offs != argc)
return usage(argv[0]);
_snprintf(reloc_file, sizeof(reloc_file), "%s.reloc.dll", input_file);
reloc_file[sizeof(reloc_file) - 1] = '\0';
if (!CopyFile(input_file, reloc_file, FALSE)) {
LPSTR msg = NULL;
uint error = GetLastError();
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0,
GetLastError(), 0, msg, 0, NULL);
VERBOSE_PRINT("Copy Error %d (0x%x) = %s\n", error, error, msg);
return 1;
}
snprintf(orig_file, sizeof(orig_file), "%s.orig.dll", input_file);
orig_file[sizeof(orig_file) - 1] = '\0';
if (!CopyFile(input_file, orig_file, FALSE)) {
LPSTR msg = NULL;
uint error = GetLastError();
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0,
GetLastError(), 0, msg, 0, NULL);
VERBOSE_PRINT("Copy Error %d (0x%x) = %s\n", error, error, msg);
return 1;
}
if (ReBaseImage(reloc_file, "", TRUE, FALSE, FALSE, 0, &old_size, &old_base,
&new_size, &new_base, 0)) {
VERBOSE_PRINT("Rebased imsage \"%s\" from 0x%08x to 0x%08x\n"
"Size changed from %d bytes to %d bytes\n",
input_file, old_base, new_base, old_size, new_size);
} else {
LPSTR msg = NULL;
uint error = GetLastError();
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0,
GetLastError(), 0, msg, 0, NULL);
VERBOSE_PRINT("Rebase Error %d (0x%x) = %s\n", error, error, msg);
return 1;
}
dll_1 = (byte *)ALIGN_BACKWARD(
LoadLibraryExA(orig_file, NULL, DONT_RESOLVE_DLL_REFERENCES), PAGE_SIZE);
p1 = dll_1;
dll_2 = (byte *)ALIGN_BACKWARD(
LoadLibraryExA(reloc_file, NULL, DONT_RESOLVE_DLL_REFERENCES), PAGE_SIZE);
p2 = dll_2;
VVERBOSE_PRINT("Loaded dll @ 0x%08x and 0x%08x\n", dll_1, dll_2);
if (dll_1 == NULL || dll_2 == NULL) {
VERBOSE_PRINT("Error loading %s\n", input_file);
return 1;
}
/* Handle the first page specially since I'm seeing problems with a handful of
* dlls that aren't really getting rebased. mcupdate_GenuineIntel.dll for ex.
* (which does have relocations etc.) not sure what's up, but it's only a couple of
* dlls so will ignore them. If we really rebased the header should differ. */
if (memcmp(dll_1, dll_2, PAGE_SIZE) == 0) {
printf("%s - ERROR during relocating\n", input_file);
return 1;
} else {
exact_no_match_pages++;
if (assume_header_match)
/* We could modify the hash function to catch header pages. */
matched_pages++;
else
unmatched_pages++;
}
p1 += PAGE_SIZE;
p2 += PAGE_SIZE;
if (assume_IAT_written && get_IAT_section_bounds(dll_1, &iat_start1, &iat_end1)) {
has_iat = true;
ASSERT(get_IAT_section_bounds(dll_2, &iat_start2, &iat_end2) &&
iat_start1 - dll_1 == iat_start2 - dll_2 &&
iat_end1 - dll_1 == iat_end2 - dll_2);
}
while (dr_virtual_query(p1, &info, sizeof(info)) == sizeof(info) &&
info.State != MEM_FREE && info.AllocationBase == dll_1) {
/* we only check read-only pages (assumption writable pages aren't shareable) */
ASSERT(p1 == info.BaseAddress);
if (info.State != MEM_COMMIT) {
reserved_pages += info.RegionSize / PAGE_SIZE;
VVERBOSE_PRINT("skipping %d reserved pages\n", info.RegionSize / PAGE_SIZE);
p1 += info.RegionSize;
p2 += info.RegionSize;
} else if (!prot_is_writable(info.Protect)) {
uint i;
for (i = 0; i < info.RegionSize / PAGE_SIZE; i++) {
bool exact = false;
if (assume_IAT_written && has_iat && iat_end1 > p1 &&
iat_start1 < p1 + PAGE_SIZE) {
/* overlaps an IAT page */
IAT_pages++;
p1 += PAGE_SIZE;
p2 += PAGE_SIZE;
continue;
}
if (memcmp(p1, p2, PAGE_SIZE) == 0) {
VVERBOSE_PRINT("Page Exact Match\n");
exact_match_pages++;
exact = true;
} else {
VVERBOSE_PRINT("Page Exact Mismatch\n");
exact_no_match_pages++;
}
if (compare_pages(drcontext, p1, p2, 0)) {
VVERBOSE_PRINT("Matched page\n");
matched_pages++;
} else {
VVERBOSE_PRINT("Failed to match page\n");
if (use_second_pass &&
compare_pages(drcontext, p1, p2, second_pass_offset)) {
second_matched_pages++;
} else {
unmatched_pages++;
}
ASSERT(!exact);
}
p1 += PAGE_SIZE;
p2 += PAGE_SIZE;
}
} else {
writable_pages += info.RegionSize / PAGE_SIZE;
VVERBOSE_PRINT("skipping %d writable pages\n", info.RegionSize / PAGE_SIZE);
p1 += info.RegionSize;
p2 += info.RegionSize;
}
}
VERBOSE_PRINT("%d exact match, %d not exact match\n%d hash_match, %d "
"second_hash_match, %d hash_mismatch\n",
exact_match_pages, exact_no_match_pages, matched_pages,
second_matched_pages, unmatched_pages);
printf("%s : %d pages - %d w %d res %d IAT = %d same %d differ : %d hash differ %d "
"first hash differ : %d%% found, %d%% found first hash\n",
input_file,
writable_pages + reserved_pages + IAT_pages + exact_match_pages +
exact_no_match_pages,
writable_pages, reserved_pages, IAT_pages, exact_match_pages,
exact_no_match_pages, unmatched_pages, unmatched_pages + second_matched_pages,
(100 * (matched_pages + second_matched_pages - exact_match_pages)) /
exact_no_match_pages,
(100 * (matched_pages - exact_match_pages)) / exact_no_match_pages);
while (spin_for_debugger)
Sleep(1000);
dr_standalone_exit();
return 0;
}
#if 0
/* First tried something like this, but we hit too many issues in decode and encode */
bool
compare_pages(void *drcontext, byte *start1, byte *start2)
{
byte *p1 = start1, *p2 = start2;
int skipped_bytes = 0, identical_skipped_bytes = 0;
while (p1 < start1 + PAGE_SIZE) {
int instr_size = decode_sizeof(drcontext, p1, NULL _IF_X64(NULL));
if (p1 + instr_size > start1 + PAGE_SIZE) {
/* We're overlapping the end of the page, skip these. */
int end_skip = start1 + PAGE_SIZE - p1;
VVERBOSE_PRINT("Passing PAGE_END %d bytes", end_skip);
skipped_bytes += end_skip;
if (memcmp(p1, p2, end_skip) == 0)
identical_skipped_bytes += end_skip;
break;
}
if (decode_sizeof(drcontext, p2, NULL _IF_X64(NULL)) != instr_size) {
VVERBOSE_PRINT("Instruction alignment mismatch\n");
return false;
}
/* assumption - instructions <= 4 bytes in size won't have relocations */
if (instr_size < 5) {
if (memcmp(p1, p2, instr_size) != 0) {
VVERBOSE_PRINT("Difference found in small instr\n");
return false;
}
p1 += size;
p2 += size;
} else {
/* guess if there could be a relocation */
instr_t *instr1 = instr_create(drcontext);
instr_t *instr2 = instr_create(drcontext);
p1 = decode(drcontext, p1, instr1);
p2 = decode(drcontext, p2, instr2);
if (p1 - start1 != p2 - start2) {
VVERBOSE_PRINT("Instruction alignment mismatch on full decode\n");
/* Fixme - free instr, don't expect this to happen */
return false;
}
if (instr_get_num_srcs(instr1) != instr_get_num_srcs(instr2) ||
instr_get_num_dsts(instr1) != instr_get_num_dsts(instr2)) {
VVERBOSE_PRINT("Full decode operand mismatch");
return false;
}
for (i = instr_get_num_srcs(instr1); i > 0; i--) {
opnd_t opnd = instr_get_src(instr1, i);
if (opnd_is_immed_int(opnd) && opnd_get_immed_int(opnd) > 0x10000) {
instr_set_src(instr1, i, opnd_create_immed_int(opnd_get_immed_int(opnd), opnd_get_size(opnd)));
}
}
}
}
}
void
modify_instr_for_relocations(void *drcontext, instr_t *inst,
ptr_uint_t *immed, ptr_uint_t *disp)
{
int i;
ptr_uint_t limmed = 0, ldisp = 0;
for (i = instr_num_srcs(inst) - 1; i >= 0; i--) {
opnd_t opnd = instr_get_src(inst, i);
if (opnd_is_immed_int(opnd) && opnd_get_immed_int(opnd) > 0x10000) {
if (limmed != 0) {
ASSERT(false);
} else {
limmed = opnd_get_immed_int(opnd);
}
instr_set_src(inst, i, opnd_create_immed_int(0, opnd_get_size(opnd)));
}
if (opnd_is_base_disp(opnd) && opnd_get_disp(opnd) > 0x10000) {
if (ldisp != 0 && ldisp != opnd_get_disp(opnd)) {
ASSERT(false);
} else {
ldisp = opnd_get_disp(opnd);
}
instr_set_src(inst, i, opnd_create_base_disp(opnd_get_base(opnd), opnd_get_index(opnd), opnd_get_scale(opnd), 0, opnd_get_size(opnd)));
}
}
for (i = instr_num_dsts(inst) - 1; i >= 0; i--) {
opnd_t opnd = instr_get_dst(inst, i);
ASSERT(!opnd_is_immed(opnd));
if (opnd_is_base_disp(opnd) && opnd_get_disp(opnd) > 0x10000) {
if (ldisp != 0 && ldisp != opnd_get_disp(opnd)) {
ASSERT(false);
} else {
ldisp = opnd_get_disp(opnd);
}
instr_set_dst(inst, i, opnd_create_base_disp(opnd_get_base(opnd), opnd_get_index(opnd), opnd_get_scale(opnd), 0, opnd_get_size(opnd)));
}
}
if (limmed != 0)
*immed = limmed;
if (ldisp != 0)
*disp = ldisp;
}
#endif