diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 628b588..9ab4f75 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,6 +47,9 @@ jobs: - name: Build fsuuid.exe run: | make fsuuid.exe + - name: Build bmtool.exe + run: | + make bmtool.exe - name: Copy files run: | mkdir -p build @@ -55,6 +58,7 @@ jobs: cp ntloader.arm64 build/ cp initrd.cpio build/ cp fsuuid.exe build/ + cp bmtool.exe build/ cp mkinitrd.exe build/ cp utils/bcd.bat build/ - name: Download README.pdf diff --git a/README.md b/README.md index bb33951..1a75695 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,9 @@ mkinitrd.exe rootfs initrd.cpio find * | cpio -o -H newc > ../initrd.cpio ``` +### bmtool +`bmtool` is a program for extracting bootmgr.exe from bootmgr. + ### bcd.bat `bcd.bat` is a batch script to create the BCD file. Do not edit it unless you know how NTloader works. @@ -323,6 +326,12 @@ make fsuuid make fsuuid.exe ``` +### Compile bmtool +``` +make bmtool +make bmtool.exe +``` + ### Compile mkinitrd ``` make mkinitrd diff --git a/include/assert.h b/include/assert.h index 4fde15f..06de060 100644 --- a/include/assert.h +++ b/include/assert.h @@ -32,7 +32,7 @@ #define assert(x) \ do \ { \ - if (DEBUG && ! (x)) \ + if (! (x)) \ { \ die ("Assertion failed at %s line %d: %s\n", \ __FILE__, __LINE__, #x); \ diff --git a/include/huffman.h b/include/huffman.h new file mode 100644 index 0000000..9a7aa7d --- /dev/null +++ b/include/huffman.h @@ -0,0 +1,115 @@ +#ifndef _HUFFMAN_H +#define _HUFFMAN_H + +/* + * Copyright (C) 2014 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * @file + * + * Huffman alphabets + * + */ + +#include + +/** Maximum length of a Huffman symbol (in bits) */ +#define HUFFMAN_BITS 16 + +/** Raw huffman symbol */ +typedef uint16_t huffman_raw_symbol_t; + +/** Quick lookup length for a Huffman symbol (in bits) + * + * This is a policy decision. + */ +#define HUFFMAN_QL_BITS 7 + +/** Quick lookup shift */ +#define HUFFMAN_QL_SHIFT (HUFFMAN_BITS - HUFFMAN_QL_BITS) + +/** A Huffman-coded set of symbols of a given length */ +struct huffman_symbols +{ + /** Length of Huffman-coded symbols (in bits) */ + uint8_t bits; + /** Shift to normalise symbols of this length to HUFFMAN_BITS bits */ + uint8_t shift; + /** Number of Huffman-coded symbols having this length */ + uint16_t freq; + /** First symbol of this length (normalised to HUFFMAN_BITS bits) + * + * Stored as a 32-bit value to allow the value + * (1<bits; +} + +/** + * Get Huffman symbol value + * + * @v sym Huffman symbol set + * @v huf Raw input value (normalised to HUFFMAN_BITS bits) + * @ret raw Raw symbol value + */ +static inline __attribute__ ((always_inline)) huffman_raw_symbol_t +huffman_raw (struct huffman_symbols *sym, unsigned int huf) +{ + + return sym->raw[ huf >> sym->shift ]; +} + +extern int +huffman_alphabet (struct huffman_alphabet *alphabet, + uint8_t *lengths, unsigned int count); +extern struct huffman_symbols * +huffman_sym (struct huffman_alphabet *alphabet, unsigned int huf); + +#endif /* _HUFFMAN_H */ diff --git a/include/lznt1.h b/include/lznt1.h new file mode 100644 index 0000000..ae6ddf8 --- /dev/null +++ b/include/lznt1.h @@ -0,0 +1,48 @@ +#ifndef _LZNT1_H +#define _LZNT1_H + +/* + * Copyright (C) 2012 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * @file + * + * LZNT1 decompression + * + */ + +#include + +/** Extract LZNT1 block length */ +#define LZNT1_BLOCK_LEN(header) (((header) & 0x0fff) + 1) + +/** Determine if LZNT1 block is compressed */ +#define LZNT1_BLOCK_COMPRESSED(header) ((header) & 0x8000) + +/** Extract LZNT1 compressed value length */ +#define LZNT1_VALUE_LEN(tuple, split) \ + (((tuple) & ((1 << (split)) - 1)) + 3) + +/** Extract LZNT1 compressed value offset */ +#define LZNT1_VALUE_OFFSET(tuple, split) (((tuple) >> split) + 1) + +extern ssize_t +lznt1_decompress (const void *data, size_t len, void *buf); + +#endif /* _LZNT1_H */ diff --git a/include/xca.h b/include/xca.h new file mode 100644 index 0000000..4b88871 --- /dev/null +++ b/include/xca.h @@ -0,0 +1,96 @@ +#ifndef _XCA_H +#define _XCA_H + +/* + * Copyright (C) 2012 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** +* @file +* +* Xpress Compression Algorithm (MS-XCA) decompression +* +*/ + +#include +#include "huffman.h" + +/** Number of XCA codes */ +#define XCA_CODES 512 + +/** XCA decompressor */ +struct xca +{ + /** Huffman alphabet */ + struct huffman_alphabet alphabet; + /** Raw symbols + * + * Must immediately follow the Huffman alphabet. + */ + huffman_raw_symbol_t raw[XCA_CODES]; + /** Code lengths */ + uint8_t lengths[XCA_CODES]; +}; + +/** XCA symbol Huffman lengths table */ +struct xca_huf_len +{ + /** Lengths of each symbol */ + uint8_t nibbles[XCA_CODES / 2]; +} __attribute__ ((packed)); + +/** + * Extract Huffman-coded length of a raw symbol + * + * @v lengths Huffman lengths table + * @v symbol Raw symbol + * @ret len Huffman-coded length + */ +static inline unsigned int +xca_huf_len (const struct xca_huf_len *lengths, unsigned int symbol) +{ + return (((lengths->nibbles[ symbol / 2 ]) >> + (4 * (symbol % 2))) & 0x0f); +} + +/** Get word from source data stream */ +#define XCA_GET16(src) \ +({ \ + const uint16_t *src16 = src; \ + src += sizeof (*src16); \ + *src16; \ +}) + +/** Get byte from source data stream */ +#define XCA_GET8(src) \ +({ \ + const uint8_t *src8 = src; \ + src += sizeof (*src8); \ + *src8; \ +}) + +/** XCA source data stream end marker */ +#define XCA_END_MARKER 256 + +/** XCA block size */ +#define XCA_BLOCK_SIZE (64 * 1024) + +extern ssize_t +xca_decompress (const void *data, size_t len, void *buf); + +#endif /* _XCA_H */ diff --git a/libnt/build.mk b/libnt/build.mk index d7c3924..f9ec18c 100644 --- a/libnt/build.mk +++ b/libnt/build.mk @@ -7,5 +7,9 @@ OBJECTS += libnt/vdisk.o OBJECTS += libnt/peloader.o +# OBJECTS += libnt/huffman.c +# OBJECTS += libnt/lznt1.c +# OBJECTS += libnt/xca.c + RM_FILES += libnt/*.s libnt/*.o diff --git a/libnt/huffman.c b/libnt/huffman.c new file mode 100644 index 0000000..f6b75d6 --- /dev/null +++ b/libnt/huffman.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2014 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * @file + * + * Huffman alphabets + * + */ + +#include +#include +#include +#include +#include "huffman.h" + +#ifdef NTLOADER_UTIL +#define DBG(...) \ +do \ +{ \ + fprintf (stderr, __VA_ARGS__); \ +} while (0) +#endif + +/** + * Transcribe binary value (for debugging) + * + * @v value Value + * @v bits Length of value (in bits) + * @ret string Transcribed value + */ +static const char * +huffman_bin (unsigned long value, unsigned int bits) +{ + static char buf[(8 * sizeof (value)) + 1 /* NUL */]; + char *out = buf; + + /* Sanity check */ + assert (bits < sizeof (buf)); + + /* Transcribe value */ + while (bits--) + *(out++) = ((value & (1 << bits)) ? '1' : '0'); + *out = '\0'; + + return buf; +} + +/** + * Dump Huffman alphabet (for debugging) + * + * @v alphabet Huffman alphabet + */ +static void __attribute__ ((unused)) +huffman_dump_alphabet (struct huffman_alphabet *alphabet) +{ + struct huffman_symbols *sym; + unsigned int bits; + unsigned int huf; + unsigned int i; + + /* Dump symbol table for each utilised length */ + for (bits = 1; bits <= (sizeof (alphabet->huf) / + sizeof (alphabet->huf[0])); bits++) + { + sym = &alphabet->huf[bits - 1]; + if (sym->freq == 0) + continue; + huf = (sym->start >> sym->shift); + DBG ("Huffman length %d start \"%s\" freq %d:", bits, + huffman_bin (huf, sym->bits), sym->freq); + for (i = 0; i < sym->freq; i++) + { + DBG (" %03x", sym->raw[huf + i]); + } + DBG ("\n"); + } + + /* Dump quick lookup table */ + DBG ("Huffman quick lookup:"); + for (i = 0; i < (sizeof (alphabet->lookup) / + sizeof (alphabet->lookup[0])); i++) + { + DBG (" %d", (alphabet->lookup[i] + 1)); + } + DBG ("\n"); +} + +/** + * Construct Huffman alphabet + * + * @v alphabet Huffman alphabet + * @v lengths Symbol length table + * @v count Number of symbols + * @ret rc Return status code + */ +int huffman_alphabet (struct huffman_alphabet *alphabet, + uint8_t *lengths, unsigned int count) +{ + struct huffman_symbols *sym; + unsigned int huf; + unsigned int cum_freq; + unsigned int bits; + unsigned int raw; + unsigned int adjustment; + unsigned int prefix; + int empty; + int complete; + + /* Clear symbol table */ + memset (alphabet->huf, 0, sizeof (alphabet->huf)); + + /* Count number of symbols with each Huffman-coded length */ + empty = 1; + for (raw = 0; raw < count; raw++) + { + bits = lengths[raw]; + if (bits) + { + alphabet->huf[bits - 1].freq++; + empty = 0; + } + } + + /* In the degenerate case of having no symbols (i.e. an unused + * alphabet), generate a trivial alphabet with exactly two + * single-bit codes. This allows callers to avoid having to + * check for this special case. + */ + if (empty) + alphabet->huf[0].freq = 2; + + /* Populate Huffman-coded symbol table */ + huf = 0; + cum_freq = 0; + for (bits = 1; bits <= (sizeof (alphabet->huf) / + sizeof (alphabet->huf[0])); bits++) + { + sym = &alphabet->huf[ bits - 1 ]; + sym->bits = bits; + sym->shift = (HUFFMAN_BITS - bits); + sym->start = (huf << sym->shift); + sym->raw = &alphabet->raw[cum_freq]; + huf += sym->freq; + if (huf > (1U << bits)) + { + DBG ("Huffman alphabet has too many symbols with " + "lengths <=%d\n", bits); + return -1; + } + huf <<= 1; + cum_freq += sym->freq; + } + complete = (huf == (1U << bits)); + + /* Populate raw symbol table */ + for (raw = 0; raw < count; raw++) + { + bits = lengths[raw]; + if (bits) + { + sym = &alphabet->huf[ bits - 1 ]; + *(sym->raw++) = raw; + } + } + + /* Adjust Huffman-coded symbol table raw pointers and populate + * quick lookup table. + */ + for (bits = 1; bits <= (sizeof (alphabet->huf) / + sizeof (alphabet->huf[0])); bits++) + { + sym = &alphabet->huf[ bits - 1 ]; + + /* Adjust raw pointer */ + sym->raw -= sym->freq; /* Reset to first symbol */ + adjustment = (sym->start >> sym->shift); + sym->raw -= adjustment; /* Adjust for quick indexing */ + + /* Populate quick lookup table */ + for (prefix = (sym->start >> HUFFMAN_QL_SHIFT); + prefix < (1 << HUFFMAN_QL_BITS); prefix++) + { + alphabet->lookup[prefix] = (bits - 1); + } + } + + /* Check that there are no invalid codes */ + if (! complete) + { + DBG ("Huffman alphabet is incomplete\n"); + return -1; + } + + return 0; +} + +/** + * Get Huffman symbol set + * + * @v alphabet Huffman alphabet + * @v huf Raw input value (normalised to HUFFMAN_BITS bits) + * @ret sym Huffman symbol set + */ +struct huffman_symbols * +huffman_sym (struct huffman_alphabet *alphabet, + unsigned int huf) +{ + struct huffman_symbols *sym; + unsigned int lookup_index; + + /* Find symbol set for this length */ + lookup_index = (huf >> HUFFMAN_QL_SHIFT); + sym = &alphabet->huf[alphabet->lookup[lookup_index]]; + while (huf < sym->start) + sym--; + return sym; +} diff --git a/libnt/lznt1.c b/libnt/lznt1.c new file mode 100644 index 0000000..0c50295 --- /dev/null +++ b/libnt/lznt1.c @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2012 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * @file + * + * LZNT1 decompression + * + */ + +#include +#include +#include +#include +#include "lznt1.h" + +#ifdef NTLOADER_UTIL +#define DBG(...) \ +do \ +{ \ + fprintf (stderr, __VA_ARGS__); \ +} while (0) + +#define DBG2(...) +#endif + +/** + * Decompress LZNT1-compressed data block + * + * @v data Compressed data + * @v limit Length of compressed data up to end of block + * @v offset Starting offset within compressed data + * @v block Decompression buffer for this block, or NULL + * @ret out_len Length of decompressed block, or negative error + */ +static ssize_t +lznt1_block (const void *data, size_t limit, size_t offset, + void *block) +{ + const uint16_t *tuple; + const uint8_t *copy_src; + uint8_t *copy_dest = block; + size_t copy_len; + size_t block_out_len = 0; + unsigned int split = 12; + unsigned int next_threshold = 16; + unsigned int tag_bit = 0; + unsigned int tag = 0; + + while (offset != limit) + { + /* Extract tag */ + if (tag_bit == 0) + { + tag = *((uint8_t *) (data + offset)); + offset++; + if (offset == limit) + break; + } + + /* Calculate copy source and length */ + if (tag & 1) + { + /* Compressed value */ + if (offset + sizeof (*tuple) > limit) + { + DBG ("LZNT1 compressed value overrun at %#zx\n", + offset); + return -1; + } + tuple = (data + offset); + offset += sizeof (*tuple); + copy_len = LZNT1_VALUE_LEN (*tuple, split); + block_out_len += copy_len; + if (copy_dest) + { + copy_src = (copy_dest - + LZNT1_VALUE_OFFSET (*tuple, split)); + while (copy_len--) + *(copy_dest++) = *(copy_src++); + } + } + else + { + /* Uncompressed value */ + copy_src = (data + offset); + if (copy_dest) + *(copy_dest++) = *copy_src; + offset++; + block_out_len++; + } + + /* Update split, if applicable */ + while (block_out_len > next_threshold) + { + split--; + next_threshold <<= 1; + } + + /* Move to next value */ + tag >>= 1; + tag_bit = ((tag_bit + 1) % 8); + } + + return block_out_len; +} + +/** + * Decompress LZNT1-compressed data + * + * @v data Compressed data + * @v len Length of compressed data + * @v buf Decompression buffer, or NULL + * @ret out_len Length of decompressed data, or negative error + */ +ssize_t lznt1_decompress (const void *data, size_t len, void *buf) +{ + const uint16_t *header; + const uint8_t *end; + size_t offset = 0; + ssize_t out_len = 0; + size_t block_len; + size_t limit; + void *block; + ssize_t block_out_len; + + while (offset != len) + { + /* Check for end marker */ + if ((offset + sizeof (*end)) == len) + { + end = (data + offset); + if (*end == 0) + break; + } + + /* Extract block header */ + if ((offset + sizeof (*header)) > len) + { + DBG ("LZNT1 block header overrun at %#zx\n", offset); + return -1; + } + header = (data + offset); + offset += sizeof (*header); + + /* Process block */ + block_len = LZNT1_BLOCK_LEN (*header); + if (LZNT1_BLOCK_COMPRESSED (*header)) + { + /* Compressed block */ + DBG2 ("LZNT1 compressed block %#zx+%#zx\n", + offset, block_len); + limit = (offset + block_len); + block = (buf ? (buf + out_len) : NULL); + block_out_len = lznt1_block (data, limit, offset, + block); + if (block_out_len < 0) + return block_out_len; + offset += block_len; + out_len += block_out_len; + } + else + { + /* Uncompressed block */ + if ((offset + block_len) > len) + { + DBG ("LZNT1 uncompressed block overrun at " + "%#zx+%#zx\n", offset, block_len); + return -1; + } + DBG2 ("LZNT1 uncompressed block %#zx+%#zx\n", + offset, block_len); + if (buf) + { + memcpy (buf + out_len, data + offset, block_len); + } + offset += block_len; + out_len += block_len; + } + } + + return out_len; +} diff --git a/libnt/xca.c b/libnt/xca.c new file mode 100644 index 0000000..60e9ab9 --- /dev/null +++ b/libnt/xca.c @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2012 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * @file + * + * Xpress Compression Algorithm (MS-XCA) decompression + * + */ + +#include +#include +#include +#include +#include "huffman.h" +#include "xca.h" + +#ifdef NTLOADER_UTIL +#define DBG(...) \ +do \ +{ \ + fprintf (stderr, __VA_ARGS__); \ +} while (0) +#endif + +/** + * Decompress XCA-compressed data + * + * @v data Compressed data + * @v len Length of compressed data + * @v buf Decompression buffer, or NULL + * @ret out_len Length of decompressed data, or negative error + */ +ssize_t xca_decompress (const void *data, size_t len, void *buf) +{ + const void *src = data; + const void *end = (src + len); + uint8_t *out = buf; + size_t out_len = 0; + size_t out_len_threshold = 0; + const struct xca_huf_len *lengths; + struct xca xca; + uint32_t accum = 0; + int extra_bits = 0; + unsigned int huf; + struct huffman_symbols *sym; + unsigned int raw; + unsigned int match_len; + unsigned int match_offset_bits; + unsigned int match_offset; + const uint8_t *copy; + int rc; + + /* Process data stream */ + while (src < end) + { + /* (Re)initialise decompressor if applicable */ + if (out_len >= out_len_threshold) + { + /* Construct symbol lengths */ + lengths = src; + src += sizeof (*lengths); + if (src > end) + { + DBG ("XCA too short to hold Huffman lengths " + "table at input offset %#zx\n", src - data); + return -1; + } + for (raw = 0; raw < XCA_CODES; raw++) + xca.lengths[raw] = xca_huf_len (lengths, raw); + + /* Construct Huffman alphabet */ + if ((rc = huffman_alphabet (&xca.alphabet, + xca.lengths, + XCA_CODES)) != 0) + return rc; + + /* Initialise state */ + accum = XCA_GET16 (src); + accum <<= 16; + accum |= XCA_GET16 (src); + extra_bits = 16; + + /* Determine next threshold */ + out_len_threshold = (out_len + XCA_BLOCK_SIZE); + } + + /* Determine symbol */ + huf = (accum >> (32 - HUFFMAN_BITS)); + sym = huffman_sym (&xca.alphabet, huf); + raw = huffman_raw (sym, huf); + accum <<= huffman_len (sym); + extra_bits -= huffman_len (sym); + if (extra_bits < 0) + { + accum |= (XCA_GET16 (src) << (-extra_bits)); + extra_bits += 16; + } + + /* Process symbol */ + if (raw < XCA_END_MARKER) + { + /* Literal symbol - add to output stream */ + if (buf) + *(out++) = raw; + out_len++; + } + else if ((raw == XCA_END_MARKER) && (src >= (end - 1))) + { + /* End marker symbol */ + return out_len; + } + else + { + /* LZ77 match symbol */ + raw -= XCA_END_MARKER; + match_offset_bits = (raw >> 4); + match_len = (raw & 0x0f); + if (match_len == 0x0f) + { + match_len = XCA_GET8 (src); + if (match_len == 0xff) + { + match_len = XCA_GET16 (src); + } + else + { + match_len += 0x0f; + } + } + match_len += 3; + if (match_offset_bits) + { + match_offset = ((accum >> (32 - match_offset_bits)) + + (1 << match_offset_bits)); + } + else + { + match_offset = 1; + } + accum <<= match_offset_bits; + extra_bits -= match_offset_bits; + if (extra_bits < 0) + { + accum |= (XCA_GET16 (src) << (-extra_bits)); + extra_bits += 16; + } + + /* Copy data */ + out_len += match_len; + if (buf) + { + copy = (out - match_offset); + while (match_len--) + *(out++) = *(copy++); + } + } + } + + /* Allow for termination with no explicit end marker symbol */ + if (src == end) + return out_len; + + DBG ("XCA input overrun at output length %#zx\n", out_len); + return -1; +} diff --git a/utils/bmtool.c b/utils/bmtool.c new file mode 100644 index 0000000..20c3f08 --- /dev/null +++ b/utils/bmtool.c @@ -0,0 +1,233 @@ +/* + * ntloader -- Microsoft Windows NT6+ loader + * Copyright (C) 2025 A1ive. + * + * ntloader is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * ntloader is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ntloader. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "lznt1.h" +#include "xca.h" + +#define BOOTMGR_MIN_LEN 16384 + +static void * +load_bootmgr (const char *path, size_t *len) +{ + FILE *fp = fopen (path, "rb"); + if (!fp) + { + fprintf (stderr, "Error opening file '%s': %s\n", + path, strerror (errno)); + return NULL; + } + + if (fseek (fp, 0, SEEK_END) != 0) + { + fprintf (stderr, "Error seeking file '%s': %s\n", + path, strerror(errno)); + fclose (fp); + return NULL; + } + long fsize = ftell (fp); + if (fsize <= 0) + { + fprintf (stderr, "Error getting file size '%s': %s\n", + path, strerror (errno)); + fclose (fp); + return NULL; + } + + rewind (fp); + + char *buffer = malloc (fsize); + if (!buffer) + { + fprintf (stderr, "Memory allocation failed\n"); + fclose (fp); + return NULL; + } + + size_t read_size = fread (buffer, 1, fsize, fp); + if (read_size != (size_t) fsize) + { + fprintf (stderr, "Error reading file '%s'\n", path); + free (buffer); + fclose (fp); + return NULL; + } + + fclose (fp); + *len = (size_t) fsize; + return buffer; +} + +static int +write_data (const char *filename, const void *data, size_t size) +{ + FILE *fp = fopen(filename, "wb"); + if (fp == NULL) + { + fprintf (stderr, "cannot open %s\n", filename); + return -1; + } + + size_t bytes_written = fwrite (data, 1, size, fp); + if (bytes_written != size) + { + fprintf (stderr, "cannot write to %s\n", filename); + fclose (fp); + return -1; + } + + fclose (fp); + return 0; +} + +static int is_empty_pgh (const void *pgh) +{ + const uint32_t *dwords = pgh; + return ((dwords[0] | dwords[1] | dwords[2] | dwords[3]) == 0); +} + +static int +decompress_bootmgr (const char *out, void *data, size_t len) +{ + const uint8_t *cdata; + size_t offset; + size_t cdata_len; + ssize_t (* decompress) (const void *, size_t, void *); + ssize_t udata_len; + void *udata = NULL; + + fprintf (stdout, "bootmgr @%p [%zu]\n", data, len); + + /* Look for an embedded compressed bootmgr.exe on an + * eight-byte boundary. + */ + for (offset = BOOTMGR_MIN_LEN; + offset < (len - BOOTMGR_MIN_LEN); + offset += 0x08) + { + + /* Initialise checks */ + decompress = NULL; + cdata = (uint8_t *) data + offset; + cdata_len = len - offset; + + /* Check for an embedded LZNT1-compressed bootmgr.exe. + * Since there is no way for LZNT1 to compress the + * initial "MZ" bytes of bootmgr.exe, we look for this + * signature starting three bytes after a paragraph + * boundary, with a preceding tag byte indicating that + * these two bytes would indeed be uncompressed. + */ + if (((offset & 0x0f) == 0x00) && + ((cdata[0x02] & 0x03) == 0x00) && + (cdata[0x03] == 'M') && + (cdata[0x04] == 'Z')) + { + fprintf (stdout, + "checking for LZNT1 bootmgr.exe at +0x%zx\n", + offset); + decompress = lznt1_decompress; + } + + /* Check for an embedded XCA-compressed bootmgr.exe. + * The bytes 0x00, 'M', and 'Z' will always be + * present, and so the corresponding symbols must have + * a non-zero Huffman length. The embedded image + * tends to have a large block of zeroes immediately + * beforehand, which we check for. It's implausible + * that the compressed data could contain substantial + * runs of zeroes, so we check for that too, in order + * to eliminate some common false positive matches. + */ + if (((cdata[0x00] & 0x0f) != 0x00) && + ((cdata[0x26] & 0xf0) != 0x00) && + ((cdata[0x2d] & 0x0f) != 0x00) && + (is_empty_pgh (cdata - 0x10)) && + (! is_empty_pgh ((cdata + 0x400))) && + (! is_empty_pgh ((cdata + 0x800))) && + (! is_empty_pgh ((cdata + 0xc00)))) + { + fprintf (stdout, + "checking for XCA bootmgr.exe at +0x%zx\n", + offset); + decompress = xca_decompress; + } + + /* If we have not found a possible bootmgr.exe, skip + * to the next offset. + */ + if (! decompress) + continue; + + /* Find length of decompressed image */ + udata_len = decompress (cdata, cdata_len, NULL); + if (udata_len < 0) + { + /* May be a false positive signature match */ + continue; + } + + /* Extract decompressed image to memory */ + fprintf (stdout, "extracting embedded bootmgr.exe\n"); + udata = malloc (udata_len); + if (! udata) + { + fprintf (stderr, "out of memory\n"); + return -1; + } + decompress (cdata, cdata_len, udata); + break; + } + + if (udata == NULL) + { + fprintf (stderr, "no embedded bootmgr.exe found\n"); + return -1; + } + int rc = write_data (out, udata, udata_len); + free (udata); + return rc; +} + +int main (int argc, char *argv[]) +{ + + if (argc < 2) + { + fprintf (stderr, "Usage: %s BOOTMGR [BOOTMGR.EXE]\n", + argv[0]); + return EXIT_FAILURE; + } + + size_t len; + const char *out = argc >= 3 ? argv[2] : "bootmgr.exe"; + void *data = load_bootmgr (argv[1], &len); + + if (data == NULL) + return EXIT_FAILURE; + + int rc = decompress_bootmgr (out, data, len); + + free (data); + return rc; +} diff --git a/utils/build.mk b/utils/build.mk index 3ea31e9..e57704f 100644 --- a/utils/build.mk +++ b/utils/build.mk @@ -51,3 +51,16 @@ regview : $(REG_FILES) $(HOST_CC) $(HOST_CFLAGS) -iquote include/ $(REG_FILES) -o $@ RM_FILES += regview regview.exe + +# bmtool +# +BMTOOL_FILES := libnt/huffman.c libnt/lznt1.c libnt/xca.c +BMTOOL_FILES += utils/bmtool.c + +bmtool.exe : $(BMTOOL_FILES) + $(MINGW_CC) $(HOST_CFLAGS) -iquote include/ $(BMTOOL_FILES) -o $@ + +bmtool : $(BMTOOL_FILES) + $(HOST_CC) $(HOST_CFLAGS) -iquote include/ $(BMTOOL_FILES) -o $@ + +RM_FILES += bmtool bmtool.exe