https://issues.oss-fuzz.com/u/1/issues?q=upx modified: p_lx_elf.cpp modified: p_unix.cpp modified: packhead.cpp modified: packhead.h
392 lines
13 KiB
C++
392 lines
13 KiB
C++
/* packhead.cpp --
|
|
|
|
This file is part of the UPX executable compressor.
|
|
|
|
Copyright (C) 1996-2025 Markus Franz Xaver Johannes Oberhumer
|
|
Copyright (C) 1996-2025 Laszlo Molnar
|
|
All Rights Reserved.
|
|
|
|
UPX and the UCL library are free software; you can redistribute them
|
|
and/or modify them 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; see the file COPYING.
|
|
If not, write to the Free Software Foundation, Inc.,
|
|
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
Markus F.X.J. Oberhumer Laszlo Molnar
|
|
<markus@oberhumer.com> <ezerotven+github@gmail.com>
|
|
*/
|
|
|
|
#include "conf.h"
|
|
#include "packhead.h"
|
|
#include "filter.h" // for ft->unfilter()
|
|
|
|
/*************************************************************************
|
|
// PackHeader
|
|
//
|
|
// We try to be able to unpack UPX 0.7x (versions 8 & 9) and at
|
|
// least to detect older versions, so this is a little bit messy.
|
|
**************************************************************************/
|
|
|
|
PackHeader::PackHeader() noexcept { reset(); }
|
|
|
|
void PackHeader::reset() noexcept {
|
|
mem_clear(this);
|
|
version = -1;
|
|
format = -1;
|
|
compress_result.reset();
|
|
}
|
|
|
|
int PackHeader::set_method(int m, unsigned offset) {
|
|
unsigned mc = ~(0x80u << 24) & m; // see ph_forced_method
|
|
if ((mc < M_NRV2B_LE32 || M_LZMA < mc) && ~0u != offset)
|
|
throwCantPack("bad method %#x at %#x", (unsigned) m, offset);
|
|
return method = m;
|
|
}
|
|
|
|
/*************************************************************************
|
|
// extremely simple checksum for the header itself (since version 10)
|
|
**************************************************************************/
|
|
|
|
static upx_uint8_t get_packheader_checksum(SPAN_S(const byte) buf, int blen) {
|
|
assert(blen >= 4);
|
|
assert(get_le32(buf) == UPX_MAGIC_LE32);
|
|
buf += 4;
|
|
blen -= 4;
|
|
unsigned c = 0;
|
|
while (blen-- > 0)
|
|
c += *buf++;
|
|
c %= 251;
|
|
return (upx_uint8_t) c;
|
|
}
|
|
|
|
/*************************************************************************
|
|
//
|
|
**************************************************************************/
|
|
|
|
// Returns the size that will be generated based on version and format,
|
|
// not necessarily the size that actually is present in spoofed input.
|
|
int PackHeader::getPackHeaderSize() const {
|
|
if (format < 0 || version < 0)
|
|
throwInternalError("getPackHeaderSize");
|
|
|
|
int n = 0;
|
|
if (version <= 3) // Note: covers (version <= 0)
|
|
n = 24;
|
|
else if (version <= 9) {
|
|
if (format == UPX_F_DOS_COM || format == UPX_F_DOS_SYS)
|
|
n = 20;
|
|
else if (format == UPX_F_DOS_EXE || format == UPX_F_DOS_EXEH)
|
|
n = 25;
|
|
else
|
|
n = 28;
|
|
} else {
|
|
if (format == UPX_F_DOS_COM || format == UPX_F_DOS_SYS)
|
|
n = 22;
|
|
else if (format == UPX_F_DOS_EXE || format == UPX_F_DOS_EXEH)
|
|
n = 27;
|
|
else
|
|
n = 32;
|
|
}
|
|
if (n < 20)
|
|
throwCantUnpack("unknown header version");
|
|
return n;
|
|
}
|
|
|
|
/*************************************************************************
|
|
// see stub/src/include/header.S
|
|
**************************************************************************/
|
|
|
|
void PackHeader::putPackHeader(SPAN_S(byte) p) const {
|
|
// NOTE: It is the caller's responsibility to ensure the buffer p has
|
|
// sufficient space for the header.
|
|
assert(get_le32(p) == UPX_MAGIC_LE32);
|
|
if (get_le32(p + 4) != UPX_MAGIC2_LE32) {
|
|
NO_fprintf(stderr, "MAGIC2_LE32: %x %x\n", get_le32(p + 4), UPX_MAGIC2_LE32);
|
|
throwBadLoader();
|
|
}
|
|
|
|
int size = 0;
|
|
upx_uint8_t old_chksum = 0;
|
|
|
|
// the new variable length header
|
|
if (format < 128) { // little endian
|
|
if (format == UPX_F_DOS_COM || format == UPX_F_DOS_SYS) {
|
|
size = 22;
|
|
old_chksum = get_packheader_checksum(p, size - 1);
|
|
set_le16(p + 16, u_len);
|
|
set_le16(p + 18, c_len);
|
|
p[20] = (byte) filter;
|
|
} else if (format == UPX_F_DOS_EXE) {
|
|
size = 27;
|
|
old_chksum = get_packheader_checksum(p, size - 1);
|
|
set_le24(p + 16, u_len);
|
|
set_le24(p + 19, c_len);
|
|
set_le24(p + 22, u_file_size);
|
|
p[25] = (byte) filter;
|
|
} else if (format == UPX_F_DOS_EXEH) {
|
|
throwInternalError("invalid format");
|
|
} else {
|
|
size = 32;
|
|
old_chksum = get_packheader_checksum(p, size - 1);
|
|
set_le32(p + 16, u_len);
|
|
set_le32(p + 20, c_len);
|
|
set_le32(p + 24, u_file_size);
|
|
p[28] = (byte) filter;
|
|
p[29] = (byte) filter_cto;
|
|
assert(n_mru == 0 || (n_mru >= 2 && n_mru <= 256));
|
|
p[30] = (byte) (n_mru ? n_mru - 1 : 0);
|
|
}
|
|
set_le32(p + 8, u_adler);
|
|
set_le32(p + 12, c_adler);
|
|
} else { // big endian
|
|
size = 32;
|
|
old_chksum = get_packheader_checksum(p, size - 1);
|
|
set_be32(p + 8, u_len);
|
|
set_be32(p + 12, c_len);
|
|
set_be32(p + 16, u_adler);
|
|
set_be32(p + 20, c_adler);
|
|
set_be32(p + 24, u_file_size);
|
|
p[28] = (byte) filter;
|
|
p[29] = (byte) filter_cto;
|
|
assert(n_mru == 0 || (n_mru >= 2 && n_mru <= 256));
|
|
p[30] = (byte) (n_mru ? n_mru - 1 : 0);
|
|
}
|
|
|
|
p[4] = (byte) version;
|
|
p[5] = (byte) format;
|
|
p[6] = (byte) method;
|
|
p[7] = (byte) level;
|
|
|
|
// header_checksum
|
|
assert(size == getPackHeaderSize());
|
|
// check old header_checksum
|
|
if (p[size - 1] != 0) {
|
|
if (p[size - 1] != old_chksum) {
|
|
// printf("old_checksum: %d %d\n", p[size - 1], old_chksum);
|
|
throwBadLoader();
|
|
}
|
|
}
|
|
// store new header_checksum
|
|
p[size - 1] = get_packheader_checksum(p, size - 1);
|
|
}
|
|
|
|
/*************************************************************************
|
|
//
|
|
**************************************************************************/
|
|
|
|
bool PackHeader::decodePackHeaderFromBuf(SPAN_S(const byte) buf, int blen) {
|
|
int boff = find_le32(raw_bytes(buf, blen), blen, UPX_MAGIC_LE32);
|
|
if (boff < 0)
|
|
return false;
|
|
blen -= boff; // bytes remaining in buf
|
|
if (blen < 20)
|
|
throwCantUnpack("header corrupted 1");
|
|
|
|
SPAN_S_VAR(const byte, const p, buf + boff);
|
|
|
|
version = p[4];
|
|
format = p[5];
|
|
method = p[6];
|
|
level = p[7];
|
|
filter_cto = 0;
|
|
|
|
if (opt->debug.debug_level) {
|
|
fprintf(stderr, " decodePackHeaderFromBuf version=%d format=%d method=%d level=%d\n",
|
|
version, format, method, level);
|
|
}
|
|
if (!((format >= 1 && format <= UPX_F_W64PE_ARM64EC) ||
|
|
(format >= 129 && format <= UPX_F_DYLIB_PPC64))) {
|
|
throwCantUnpack("unknown format %d", format);
|
|
}
|
|
|
|
//
|
|
// decode the new variable length header
|
|
//
|
|
|
|
int off_filter = 0;
|
|
if (format < 128) {
|
|
u_adler = get_le32(p + 8);
|
|
c_adler = get_le32(p + 12);
|
|
if (format == UPX_F_DOS_COM || format == UPX_F_DOS_SYS) {
|
|
u_len = get_le16(p + 16);
|
|
c_len = get_le16(p + 18);
|
|
u_file_size = u_len;
|
|
off_filter = 20;
|
|
} else if (format == UPX_F_DOS_EXE || format == UPX_F_DOS_EXEH) {
|
|
if (blen < 25)
|
|
throwCantUnpack("header corrupted 6");
|
|
u_len = get_le24(p + 16);
|
|
c_len = get_le24(p + 19);
|
|
u_file_size = get_le24(p + 22);
|
|
off_filter = 25;
|
|
} else {
|
|
if (blen < 31)
|
|
throwCantUnpack("header corrupted 7");
|
|
u_len = get_le32(p + 16);
|
|
c_len = get_le32(p + 20);
|
|
u_file_size = get_le32(p + 24);
|
|
off_filter = 28;
|
|
filter_cto = p[29];
|
|
n_mru = p[30] ? 1 + p[30] : 0;
|
|
}
|
|
} else {
|
|
if (blen < 31)
|
|
throwCantUnpack("header corrupted 8");
|
|
u_len = get_be32(p + 8);
|
|
c_len = get_be32(p + 12);
|
|
u_adler = get_be32(p + 16);
|
|
c_adler = get_be32(p + 20);
|
|
u_file_size = get_be32(p + 24);
|
|
off_filter = 28;
|
|
filter_cto = p[29];
|
|
n_mru = p[30] ? 1 + p[30] : 0;
|
|
}
|
|
|
|
if (version >= 10) {
|
|
if (blen < off_filter + 1)
|
|
throwCantUnpack("header corrupted 9");
|
|
filter = p[off_filter];
|
|
} else if ((level & 128) == 0)
|
|
filter = 0;
|
|
else {
|
|
// convert old flags to new filter id
|
|
level &= 127;
|
|
if (format == UPX_F_DOS_COM || format == UPX_F_DOS_SYS)
|
|
filter = 0x06;
|
|
else
|
|
filter = 0x26;
|
|
}
|
|
level &= 15;
|
|
|
|
//
|
|
// now some checks
|
|
//
|
|
|
|
if (version == 0xff)
|
|
throwCantUnpack("cannot unpack UPX ;-)");
|
|
// check header_checksum
|
|
if (version >= 10) {
|
|
int size = getPackHeaderSize(); // expected; based on format and version
|
|
if (size > blen || p[size - 1] != get_packheader_checksum(p, size - 1))
|
|
throwCantUnpack("header corrupted 3");
|
|
}
|
|
if (c_len < 2 || u_len < 2 || !mem_size_valid_bytes(c_len) || !mem_size_valid_bytes(u_len))
|
|
throwCantUnpack("header corrupted 4");
|
|
|
|
//
|
|
// success
|
|
//
|
|
|
|
this->buf_offset = boff;
|
|
return true;
|
|
}
|
|
|
|
/*************************************************************************
|
|
// ph method util
|
|
**************************************************************************/
|
|
|
|
bool ph_is_forced_method(int method) noexcept // predicate
|
|
{
|
|
return (method >> 24) == -0x80;
|
|
}
|
|
|
|
int ph_force_method(int method) noexcept // mark as forced
|
|
{
|
|
return method | (0x80u << 24);
|
|
}
|
|
|
|
int ph_forced_method(int method) noexcept // extract the forced method
|
|
{
|
|
if (ph_is_forced_method(method))
|
|
method &= ~(0x80u << 24);
|
|
assert_noexcept(method > 0);
|
|
return method;
|
|
}
|
|
|
|
bool ph_skipVerify(const PackHeader &ph) noexcept {
|
|
if (M_IS_DEFLATE(ph.method))
|
|
return false;
|
|
if (M_IS_LZMA(ph.method))
|
|
return false;
|
|
if (ph.level > 1)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/*************************************************************************
|
|
// ph decompress util
|
|
**************************************************************************/
|
|
|
|
void ph_decompress(PackHeader &ph, SPAN_P(const byte) in, SPAN_P(byte) out, bool verify_checksum,
|
|
Filter *ft) {
|
|
// verify checksum of compressed data
|
|
if (verify_checksum) {
|
|
unsigned adler = upx_adler32(raw_bytes(in, ph.c_len), ph.c_len, ph.saved_c_adler);
|
|
if (adler != ph.c_adler)
|
|
throwChecksumError();
|
|
}
|
|
|
|
// decompress
|
|
if (ph.u_len < ph.c_len)
|
|
throwCantUnpack("header corrupted");
|
|
unsigned new_len = ph.u_len;
|
|
int r = upx_decompress(raw_bytes(in, ph.c_len), ph.c_len, raw_bytes(out, ph.u_len), &new_len,
|
|
ph_forced_method(ph.method), &ph.compress_result);
|
|
if (r == UPX_E_OUT_OF_MEMORY)
|
|
throwOutOfMemoryException();
|
|
if (r != UPX_E_OK || new_len != ph.u_len)
|
|
throwCompressedDataViolation();
|
|
|
|
// verify checksum of decompressed data
|
|
if (verify_checksum) {
|
|
if (ft)
|
|
ft->unfilter(out, ph.u_len);
|
|
unsigned adler = upx_adler32(raw_bytes(out, ph.u_len), ph.u_len, ph.saved_u_adler);
|
|
if (adler != ph.u_adler)
|
|
throwChecksumError();
|
|
}
|
|
}
|
|
|
|
/*************************************************************************
|
|
// ph overlapping decompression util
|
|
**************************************************************************/
|
|
|
|
bool ph_testOverlappingDecompression(const PackHeader &ph, const byte *buf, const byte *tbuf,
|
|
unsigned overlap_overhead) {
|
|
if (ph.c_len >= ph.u_len)
|
|
return false;
|
|
|
|
assert((int) overlap_overhead >= 0);
|
|
assert((int) (ph.u_len + overlap_overhead) >= 0);
|
|
const int method = ph_forced_method(ph.method);
|
|
|
|
// Because upx_test_overlap() does not use the asm_fast decompressor
|
|
// we must account for extra 3 bytes that asm_fast does use,
|
|
// or else we may fail at runtime decompression.
|
|
unsigned extra = 0;
|
|
if (M_IS_NRV2B(method) || M_IS_NRV2D(method) || M_IS_NRV2E(method))
|
|
extra = 3;
|
|
if (overlap_overhead <= 4 + extra) // don't waste time here
|
|
return false;
|
|
overlap_overhead -= extra;
|
|
|
|
unsigned src_off = ph.u_len + overlap_overhead - ph.c_len;
|
|
unsigned new_len = ph.u_len;
|
|
int r = upx_test_overlap(buf - src_off, tbuf, src_off, ph.c_len, &new_len, method,
|
|
&ph.compress_result);
|
|
if (r == UPX_E_OUT_OF_MEMORY)
|
|
throwOutOfMemoryException();
|
|
return (r == UPX_E_OK && new_len == ph.u_len);
|
|
}
|
|
|
|
/* vim:set ts=4 sw=4 et: */
|