|
32 | 32 | #include <accctrl.h> |
33 | 33 | #include <aclapi.h> |
34 | 34 | #include <imagehlp.h> |
| 35 | +#include <assert.h> |
35 | 36 |
|
36 | 37 | #include "msapi_utf8.h" |
37 | 38 |
|
|
41 | 42 |
|
42 | 43 | #define _STRINGIFY(x) #x |
43 | 44 | #define STRINGIFY(x) _STRINGIFY(x) |
| 45 | +#define MIN_CHUNK_SIZE 2 |
| 46 | +#define MAX_CHUNK_SIZE 128 |
44 | 47 |
|
45 | 48 | #define safe_free(p) do {free((void*)p); p = NULL;} while(0) |
46 | 49 | #define safe_min(a, b) min((size_t)(a), (size_t)(b)) |
@@ -466,25 +469,77 @@ static BOOL UpdateChecksum(const char* filename, DWORD* dwCheckSum) |
466 | 469 | return bRet; |
467 | 470 | } |
468 | 471 |
|
| 472 | +typedef struct { |
| 473 | + size_t size; |
| 474 | + uint8_t* org; |
| 475 | + uint8_t* new; |
| 476 | + bool patched; |
| 477 | +} chunk; |
| 478 | + |
| 479 | +static void FreeChunkList(chunk* list, size_t size) |
| 480 | +{ |
| 481 | + size_t i; |
| 482 | + for (i = 0; i < size; i++) { |
| 483 | + free(list[i].org); |
| 484 | + free(list[i].new); |
| 485 | + } |
| 486 | + free(list); |
| 487 | +} |
| 488 | + |
| 489 | +static uint8_t* HexStringToBin(const char* str) |
| 490 | +{ |
| 491 | + size_t i, len = safe_strlen(str); |
| 492 | + uint8_t val = 0, * ret = NULL; |
| 493 | + char c; |
| 494 | + |
| 495 | + if ((len < 2) || (len % 2)) |
| 496 | + return NULL; |
| 497 | + ret = malloc(len / 2); |
| 498 | + if (ret == NULL) |
| 499 | + return NULL; |
| 500 | + |
| 501 | + for (i = 0; i < len; i++) { |
| 502 | + val <<= 4; |
| 503 | + c = (char)tolower(str[i]); |
| 504 | + if (c < '0' || (c > '9' && c < 'a') || c > 'f') { |
| 505 | + fprintf(stderr, "Invalid hex character '%c' in string '%s'\n", c, str); |
| 506 | + return NULL; |
| 507 | + } |
| 508 | + val |= ((c - '0') < 0xa) ? (c - '0') : (c - 'a' + 0xa); |
| 509 | + if (i % 2) |
| 510 | + ret[i / 2] = val; |
| 511 | + } |
| 512 | + |
| 513 | + return ret; |
| 514 | +} |
| 515 | + |
469 | 516 | static int main_utf8(int argc, char** argv) |
470 | 517 | { |
471 | 518 | FILE* file = NULL; |
472 | 519 | DWORD r, dwCheckSum[2]; |
473 | 520 | int i, patched = 0; |
474 | | - uint64_t* patch, val, pos; |
| 521 | + uint64_t pos; |
| 522 | + uint8_t val[MAX_CHUNK_SIZE]; |
| 523 | + chunk* chunk_list; |
475 | 524 | char system_dir[128]; |
| 525 | + size_t chunk_list_size, min_chunk_size = MAX_CHUNK_SIZE, max_chunk_size = 0; |
| 526 | + size_t val_size; |
| 527 | + bool force = false; |
476 | 528 |
|
477 | 529 | if (!IsCurrentProcessElevated()) { |
478 | 530 | fprintf(stderr, "This command must be run from an elevated prompt.\n"); |
479 | 531 | return -1; |
480 | 532 | } |
481 | 533 |
|
482 | | - if (argc < 2) { |
483 | | - fprintf(stderr, "Usage: %s filename [QWORD QWORD [QWORD QWORD]...].\n", appname(argv[0])); |
| 534 | + fprintf(stderr, "%s %s © 2020 Pete Batard <pete@akeo.ie>\n\n", appname(argv[0]), APP_VERSION_STR); |
| 535 | + |
| 536 | + if ((argc < 2) || (((argv[1][0] == '/') || (argv[1][0] == '-')) && ((argv[1][1] == '?') || (argv[1][1] == 'h')))) { |
| 537 | + fprintf(stderr, "Usage: %s filename <SEARCH> <REPLACE> [<SEARCH> <REPLACE> [...]].\n", appname(argv[0])); |
| 538 | + fprintf(stderr, "Where <SEARCH> and <REPLACE> are string of the same size, containing\n"); |
| 539 | + fprintf(stderr, "the hex data that should be searched and replaced.\n"); |
484 | 540 | return -2; |
485 | 541 | } |
486 | 542 |
|
487 | | - fprintf(stderr, "%s %s © 2020 Pete Batard <pete@akeo.ie>\n\n", appname(argv[0]), APP_VERSION_STR); |
488 | 543 | fprintf(stderr, "This program is free software; you can redistribute it and/or modify it under \n"); |
489 | 544 | fprintf(stderr, "the terms of the GNU General Public License as published by the Free Software \n"); |
490 | 545 | fprintf(stderr, "Foundation; either version 3 of the License or any later version.\n\n"); |
@@ -517,61 +572,110 @@ static int main_utf8(int argc, char** argv) |
517 | 572 | return -1; |
518 | 573 | } |
519 | 574 |
|
| 575 | + if (((argv[2][0] == '/') || (argv[2][0] == '-')) && (argv[2][1] == 'f')) |
| 576 | + goto skip_patch; |
| 577 | + |
520 | 578 | if (argc % 2) { |
521 | | - fprintf(stderr, "Values must be provided in [ORIGINAL PATCHED] pairs\n"); |
| 579 | + fprintf(stderr, "Values must be provided in [<SEARCH> <REPLACE>] pairs\n"); |
522 | 580 | return -1; |
523 | 581 | } |
524 | 582 |
|
525 | | - // We're not going to win prizes for speed, but who cares... |
526 | 583 | file = fopenU(argv[1], "rb+"); |
527 | 584 | if (file == NULL) { |
528 | 585 | fprintf(stderr, "Could not open '%s'\n", argv[1]); |
529 | 586 | return -1; |
530 | 587 | } |
531 | 588 |
|
532 | | - patch = calloc((size_t)argc - 2, sizeof(uint64_t)); |
533 | | - if (patch == NULL) { |
| 589 | + chunk_list_size = ((size_t)argc - 2) / 2; |
| 590 | + chunk_list = calloc(chunk_list_size, sizeof(chunk)); |
| 591 | + if (chunk_list == NULL) { |
534 | 592 | fprintf(stderr, "calloc error\n"); |
535 | 593 | return -1; |
536 | 594 | } |
537 | 595 |
|
538 | | - for (i = 0; i < argc - 2; i++) |
539 | | - patch[i] = strtoull(argv[i + 2], NULL, 16); |
| 596 | + for (i = 1; i < argc / 2; i++) { |
| 597 | + chunk_list[i - 1].patched = false; |
| 598 | + chunk_list[i - 1].size = strlen(argv[2 * i]); |
| 599 | + if (chunk_list[i - 1].size % 2) { |
| 600 | + fprintf(stderr, "The number of hex digits for %s must be a multiple of 2\n", |
| 601 | + argv[(2 * i) - 1]); |
| 602 | + FreeChunkList(chunk_list, chunk_list_size); |
| 603 | + return -1; |
| 604 | + } |
| 605 | + if (chunk_list[i - 1].size != strlen(argv[2 * i + 1])) { |
| 606 | + fprintf(stderr, "<SEARCH> and <REPLACE> length differ for arguments %s and %s\n", |
| 607 | + argv[(2 * i) - 1], argv[2 * i]); |
| 608 | + FreeChunkList(chunk_list, chunk_list_size); |
| 609 | + return -1; |
| 610 | + } |
| 611 | + chunk_list[i - 1].size /= 2; |
| 612 | + if ((chunk_list[i - 1].size < MIN_CHUNK_SIZE) || (chunk_list[i - 1].size > MAX_CHUNK_SIZE)) { |
| 613 | + fprintf(stderr, "A value can not be smaller than %d bytes or larger than %d bytes\n", |
| 614 | + MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); |
| 615 | + FreeChunkList(chunk_list, chunk_list_size); |
| 616 | + return -1; |
| 617 | + } |
| 618 | + min_chunk_size = min(min_chunk_size, chunk_list[i - 1].size); |
| 619 | + max_chunk_size = max(max_chunk_size, chunk_list[i - 1].size); |
| 620 | + chunk_list[i - 1].org = HexStringToBin(argv[2 * i]); |
| 621 | + chunk_list[i - 1].new = HexStringToBin(argv[2 * i + 1]); |
| 622 | + if (chunk_list[i - 1].org == NULL || chunk_list[i - 1].new == NULL) { |
| 623 | + fprintf(stderr, "Could not convert hex string\n"); |
| 624 | + FreeChunkList(chunk_list, chunk_list_size); |
| 625 | + return -1; |
| 626 | + } |
| 627 | + } |
540 | 628 |
|
541 | | - if (fread(&val, 1, 7, file) != 7) { |
| 629 | + val_size = min_chunk_size - 1; |
| 630 | + if (fread(val, 1, val_size, file) != val_size) { |
542 | 631 | fprintf(stderr, "Could not read file\n"); |
| 632 | + FreeChunkList(chunk_list, chunk_list_size); |
543 | 633 | return -1; |
544 | 634 | } |
545 | | - val = _byteswap_uint64(val); |
546 | | - for (pos = 0; fread(&val, 1, 1, file) == 1; pos++) { |
547 | | - for (i = 0; i < argc - 2; i += 2) { |
548 | | - if (val == patch[i]) { |
549 | | - fprintf(stdout, "%08llX: %016llX -> %016llX... ", pos, patch[i], patch[i + 1]); |
550 | | - fseek(file, -1 * (long)sizeof(uint64_t), SEEK_CUR); |
551 | | - val = _byteswap_uint64(patch[i + 1]); |
552 | | - if (fwrite(&val, sizeof(uint64_t), 1, file) != 1) { |
| 635 | + |
| 636 | + for (pos = 0; ; pos++) { |
| 637 | + assert(val_size < max_chunk_size); |
| 638 | + if (fread(&val[val_size++], 1, 1, file) != 1) |
| 639 | + // Note: If we have a patch smaller than max_chunk_size at the very end, |
| 640 | + // it won't be applied because we break too soon. But we don't care about |
| 641 | + // this in this patcher because the end should be the digital signature... |
| 642 | + break; |
| 643 | + for (i = 0; i < (int)chunk_list_size; i++) { |
| 644 | + if ((chunk_list[i].size <= val_size) && (memcmp(chunk_list[i].org, val, chunk_list[i].size) == 0)) { |
| 645 | + if (chunk_list[i].patched) { |
| 646 | + fprintf(stderr, "WARNING: Multiple sections with data %s are being patched!\n", argv[2 * i + 2]); |
| 647 | + } |
| 648 | + fprintf(stdout, "%08llX: %s\n========> %s... ", pos, argv[2 * i + 2], argv[2 * i + 3]); |
| 649 | + memcpy(val, chunk_list[i].new, chunk_list[i].size); |
| 650 | + fseek(file, (long)pos, SEEK_SET); |
| 651 | + if (fwrite(&val, 1, chunk_list[i].size, file) != chunk_list[i].size) { |
553 | 652 | fprintf(stdout, "ERROR!\n"); |
554 | 653 | } else { |
555 | 654 | fprintf(stdout, "SUCCESS\n"); |
| 655 | + chunk_list[i].patched = true; |
556 | 656 | patched++; |
557 | | - fflush(file); |
558 | | - // We don't allow patch overlap so move 7 bytes ahead |
559 | | - pos += fread(&val, 1, 7, file); |
560 | | - val = _byteswap_uint64(val) >> 8; |
561 | 657 | } |
562 | 658 | fflush(file); |
| 659 | + // Now reposition ourselves to the next byte to read... |
| 660 | + fseek(file, (long)pos + val_size, SEEK_SET); |
| 661 | + // ...and prevent patch overlap by removing our data |
| 662 | + val_size -= chunk_list[i].size; |
| 663 | + memmove(val, &val[chunk_list[i].size], val_size); |
563 | 664 | break; |
564 | 665 | } |
565 | 666 | } |
566 | | - val <<= 8; |
| 667 | + if (val_size == max_chunk_size) |
| 668 | + memmove(val, &val[1], --val_size); |
567 | 669 | } |
568 | | - free(patch); |
| 670 | + |
| 671 | + FreeChunkList(chunk_list, chunk_list_size); |
569 | 672 | fclose(file); |
570 | 673 | if (patched == 0) { |
571 | 674 | fprintf(stdout, "No elements were patched - aborting\n"); |
572 | 675 | return 0; |
573 | 676 | } |
574 | 677 |
|
| 678 | +skip_patch: |
575 | 679 | // Since the whole point is to alter the file, remove the digital signature |
576 | 680 | r = RemoveDigitalSignature(argv[1]); |
577 | 681 | switch (r) { |
|
0 commit comments