mirror of
https://github.com/upx/upx
synced 2025-09-28 19:06:07 +08:00

p_vmlinz.cpp stub/macros.ash stub/l_lx_elf86.asm stub/l_lx_exec86.asm stub/l_vmlinz.asm committer: jreiser <jreiser> 1058406408 +0000
453 lines
13 KiB
C++
453 lines
13 KiB
C++
/* p_vmlinz.cpp --
|
|
|
|
This file is part of the UPX executable compressor.
|
|
|
|
Copyright (C) 1996-2003 Markus Franz Xaver Johannes Oberhumer
|
|
Copyright (C) 1996-2003 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 ml1050@cdata.tvnet.hu
|
|
*/
|
|
|
|
|
|
#include "conf.h"
|
|
|
|
#include "file.h"
|
|
#include "filter.h"
|
|
#include "packer.h"
|
|
#include "p_vmlinz.h"
|
|
#include <zlib.h>
|
|
|
|
static const
|
|
#include "stub/l_vmlinz.h"
|
|
|
|
static const unsigned kernel_entry = 0x100000;
|
|
static const unsigned stack_during_uncompression = 0x90000;
|
|
|
|
// from /usr/src/linux/arch/i386/boot/compressed/Makefile
|
|
static const unsigned zimage_offset = 0x1000;
|
|
static const unsigned bzimage_offset = 0x100000;
|
|
|
|
|
|
/*************************************************************************
|
|
//
|
|
**************************************************************************/
|
|
|
|
PackVmlinuzI386::PackVmlinuzI386(InputFile *f) :
|
|
super(f)
|
|
{
|
|
COMPILE_TIME_ASSERT(sizeof(boot_sect_t) == 0x218);
|
|
}
|
|
|
|
|
|
const int *PackVmlinuzI386::getCompressionMethods(int method, int level) const
|
|
{
|
|
return Packer::getDefaultCompressionMethods_le32(method, level);
|
|
}
|
|
|
|
|
|
const int *PackVmlinuzI386::getFilters() const
|
|
{
|
|
static const int filters[] = {
|
|
0x49,
|
|
0x26, 0x24, 0x11, 0x14, 0x13, 0x16, 0x25, 0x15, 0x12,
|
|
-1 };
|
|
return filters;
|
|
}
|
|
|
|
|
|
bool PackVmlinuzI386::canPack()
|
|
{
|
|
return readFileHeader() == getFormat();
|
|
}
|
|
|
|
|
|
/*************************************************************************
|
|
// common util routines
|
|
**************************************************************************/
|
|
|
|
int PackVmlinuzI386::readFileHeader()
|
|
{
|
|
boot_sect_t h;
|
|
|
|
setup_size = 0;
|
|
|
|
fi->readx(&h, sizeof(h));
|
|
if (h.boot_flag != 0xAA55)
|
|
return 0;
|
|
const bool hdrs = (memcmp(h.hdrs, "HdrS", 4) == 0);
|
|
|
|
setup_size = (1 + (h.setup_sects ? h.setup_sects : 4)) * 0x200;
|
|
if (setup_size <= 0 || setup_size >= file_size)
|
|
return 0;
|
|
|
|
int format = UPX_F_VMLINUZ_i386;
|
|
unsigned sys_size = ALIGN_UP(file_size, 16) - setup_size;
|
|
|
|
const unsigned char *p = (const unsigned char *) &h + 0x1e3;
|
|
|
|
if (hdrs && memcmp(p, "\x0d\x0a\x07""ELKS", 7) == 0)
|
|
{
|
|
format = UPX_F_ELKS_8086;
|
|
}
|
|
else if (hdrs && (h.load_flags & 1) != 0)
|
|
{
|
|
format = UPX_F_BVMLINUZ_i386;
|
|
// account for 16-bit h.sys_size, wrap around at 20 bits
|
|
sys_size &= (1 << 20) - 1;
|
|
}
|
|
|
|
if (16u * h.sys_size != sys_size)
|
|
return 0;
|
|
|
|
// FIXME: add more checks for a valid kernel
|
|
|
|
return format;
|
|
}
|
|
|
|
|
|
// read full kernel into obuf[], gzip-decompress into ibuf[],
|
|
// return decompressed size
|
|
int PackVmlinuzI386::decompressKernel()
|
|
{
|
|
// read whole kernel image
|
|
obuf.alloc(file_size);
|
|
fi->seek(0, SEEK_SET);
|
|
fi->readx(obuf, file_size);
|
|
|
|
checkAlreadyPacked(obuf + setup_size, UPX_MIN(file_size - setup_size, 1024));
|
|
|
|
for (int gzoff = setup_size; gzoff < file_size; gzoff++)
|
|
{
|
|
// find gzip header (2 bytes magic + 1 byte method "deflated")
|
|
int off = find(obuf + gzoff, file_size - gzoff, "\x1F\x8B\x08", 3);
|
|
if (off < 0)
|
|
break;
|
|
gzoff += off;
|
|
const int gzlen = file_size - gzoff;
|
|
if (gzlen < 256)
|
|
break;
|
|
// check gzip flag byte
|
|
unsigned char flags = obuf[gzoff + 3];
|
|
if ((flags & 0xe0) != 0) // reserved bits set
|
|
continue;
|
|
//printf("found gzip header at offset %d\n", gzoff);
|
|
|
|
// try to decompress
|
|
int klen;
|
|
int fd;
|
|
off_t fd_pos;
|
|
for (;;)
|
|
{
|
|
klen = -1;
|
|
fd = -1;
|
|
fd_pos = -1;
|
|
// open
|
|
fi->seek(gzoff, SEEK_SET);
|
|
fd = dup(fi->getFd());
|
|
if (fd < 0)
|
|
break;
|
|
gzFile zf = gzdopen(fd, "r");
|
|
if (zf == NULL)
|
|
break;
|
|
// estimate gzip-decompressed kernel size & alloc buffer
|
|
if (ibuf.getSize() == 0)
|
|
ibuf.alloc(gzlen * 3);
|
|
// decompress
|
|
klen = gzread(zf, ibuf, ibuf.getSize());
|
|
fd_pos = lseek(fd, 0, SEEK_CUR);
|
|
gzclose(zf);
|
|
fd = -1;
|
|
if (klen != (int)ibuf.getSize())
|
|
break;
|
|
// realloc and try again
|
|
unsigned s = ibuf.getSize();
|
|
ibuf.dealloc();
|
|
ibuf.alloc(3 * s / 2);
|
|
}
|
|
if (fd >= 0)
|
|
(void) close(fd);
|
|
if (klen <= 0)
|
|
continue;
|
|
|
|
if (klen <= gzlen)
|
|
continue;
|
|
|
|
if (opt->force > 0)
|
|
return klen;
|
|
|
|
// some checks
|
|
if (fd_pos != file_size)
|
|
{
|
|
//printf("fd_pos: %ld, file_size: %ld\n", (long)fd_pos, (long)file_size);
|
|
throwCantPack("trailing bytes after kernel image; use option `-f' to force packing");
|
|
}
|
|
// see /usr/src/linux/arch/i386/kernel/head.S:
|
|
if (memcmp(ibuf, "\xFC\xB8", 2) != 0)
|
|
throwCantPack("unrecognized kernel architecture; use option `-f' to force packing");
|
|
|
|
// FIXME: more checks for special magic bytes in ibuf ???
|
|
// FIXME: more checks for kernel architecture ???
|
|
|
|
return klen;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void PackVmlinuzI386::readKernel()
|
|
{
|
|
int klen = decompressKernel();
|
|
if (klen <= 0)
|
|
throwCantPack("kernel decompression failed");
|
|
//OutputFile::dump("kernel.img", ibuf, klen);
|
|
|
|
// copy the setup boot code
|
|
setup_buf.alloc(setup_size);
|
|
memcpy(setup_buf, obuf, setup_size);
|
|
//OutputFile::dump("setup.img", setup_buf, setup_size);
|
|
|
|
obuf.dealloc();
|
|
obuf.allocForCompression(klen);
|
|
|
|
ph.u_len = klen;
|
|
ph.filter = 0;
|
|
}
|
|
|
|
|
|
/*************************************************************************
|
|
// vmlinuz specific
|
|
**************************************************************************/
|
|
|
|
int PackVmlinuzI386::buildLoader(const Filter *ft)
|
|
{
|
|
// prepare loader
|
|
initLoader(nrv_loader, sizeof(nrv_loader));
|
|
addLoader("LINUZ000",
|
|
ft->id ? "LZCALLT1" : "",
|
|
"LZIMAGE0",
|
|
getDecompressor(),
|
|
NULL
|
|
);
|
|
if (ft->id)
|
|
{
|
|
assert(ft->calls > 0);
|
|
addLoader("LZCALLT9", NULL);
|
|
addFilter32(ft->id);
|
|
}
|
|
addLoader("LINUZ990""IDENTSTR""UPX1HEAD", NULL);
|
|
return getLoaderSize();
|
|
}
|
|
|
|
|
|
void PackVmlinuzI386::pack(OutputFile *fo)
|
|
{
|
|
readKernel();
|
|
|
|
// prepare filter
|
|
Filter ft(ph.level);
|
|
ft.buf_len = ph.u_len;
|
|
ft.addvalue = kernel_entry;
|
|
// compress
|
|
compressWithFilters(&ft, 1 << 20);
|
|
|
|
const unsigned lsize = getLoaderSize();
|
|
MemBuffer loader(lsize);
|
|
memcpy(loader, getLoader(), lsize);
|
|
|
|
patchPackHeader(loader, lsize);
|
|
patchFilter32(loader, lsize, &ft);
|
|
patch_le32(loader, lsize, "ESI1", zimage_offset + lsize);
|
|
patch_le32(loader, lsize, "KEIP", kernel_entry);
|
|
patch_le32(loader, lsize, "STAK", stack_during_uncompression);
|
|
|
|
boot_sect_t * const bs = (boot_sect_t *) ((unsigned char *) setup_buf);
|
|
bs->sys_size = ALIGN_UP(lsize + ph.c_len, 16) / 16;
|
|
|
|
fo->write(setup_buf, setup_buf.getSize());
|
|
fo->write(loader, lsize);
|
|
fo->write(obuf, ph.c_len);
|
|
#if 0
|
|
printf("%-13s: setup : %8ld bytes\n", getName(), (long) setup_buf.getSize());
|
|
printf("%-13s: loader : %8ld bytes\n", getName(), (long) lsize);
|
|
printf("%-13s: compressed : %8ld bytes\n", getName(), (long) ph.c_len);
|
|
#endif
|
|
|
|
// verify
|
|
verifyOverlappingDecompression();
|
|
|
|
// finally check the compression ratio
|
|
if (!checkFinalCompressionRatio(fo))
|
|
throwNotCompressible();
|
|
}
|
|
|
|
|
|
/*************************************************************************
|
|
// bvmlinuz specific
|
|
**************************************************************************/
|
|
|
|
int PackBvmlinuzI386::buildLoader(const Filter *ft)
|
|
{
|
|
// prepare loader
|
|
initLoader(nrv_loader, sizeof(nrv_loader));
|
|
addLoader("LINUZ000",
|
|
(0x40==(0xf0 & ft->id)) ? "LZCKLLT1" : (ft->id ? "LZCALLT1" : ""),
|
|
"LBZIMAGE""IDENTSTR",
|
|
"+40D++++", // align the stuff to 4 byte boundary
|
|
"UPX1HEAD", // 32 byte
|
|
"LZCUTPOI""+0000000",
|
|
getDecompressor(),
|
|
NULL
|
|
);
|
|
if (ft->id)
|
|
{
|
|
assert(ft->calls > 0);
|
|
if (0x40==(0xf0 & ft->id)) {
|
|
addLoader("LZCKLLT9", NULL);
|
|
}
|
|
else {
|
|
addLoader("LZCALLT9", NULL);
|
|
}
|
|
addFilter32(ft->id);
|
|
}
|
|
addLoader("LINUZ990", NULL);
|
|
return getLoaderSize();
|
|
}
|
|
|
|
|
|
void PackBvmlinuzI386::pack(OutputFile *fo)
|
|
{
|
|
readKernel();
|
|
|
|
// prepare filter
|
|
Filter ft(ph.level);
|
|
ft.buf_len = ph.u_len;
|
|
ft.addvalue = kernel_entry;
|
|
// compress
|
|
compressWithFilters(&ft, 512);
|
|
|
|
// align everything to dword boundary - it is easier to handle
|
|
unsigned clen = ph.c_len;
|
|
memset(obuf + clen, 0, 4);
|
|
clen = ALIGN_UP(clen, 4);
|
|
|
|
const unsigned lsize = getLoaderSize();
|
|
MemBuffer loader(lsize);
|
|
memcpy(loader, getLoader(), lsize);
|
|
|
|
patchFilter32(loader, lsize, &ft);
|
|
patchPackHeader(loader, lsize);
|
|
|
|
const int e_len = getLoaderSectionStart("LZCUTPOI");
|
|
assert(e_len > 0);
|
|
|
|
const unsigned d_len4 = ALIGN_UP(lsize - e_len, 4);
|
|
const unsigned decompr_pos = ALIGN_UP(ph.u_len + ph.overlap_overhead, 16);
|
|
const unsigned copy_size = clen + d_len4;
|
|
const unsigned edi = decompr_pos + d_len4 - 4; // copy to
|
|
const unsigned esi = ALIGN_UP(clen + lsize, 4) - 4; // copy from
|
|
|
|
unsigned jpos = find_le32(loader, e_len, get_le32("JMPD"));
|
|
patch_le32(loader, e_len, "JMPD", decompr_pos - jpos - 4);
|
|
|
|
patch_le32(loader, e_len, "ESI1", bzimage_offset + decompr_pos - clen);
|
|
patch_le32(loader, e_len, "ECX0", copy_size / 4);
|
|
patch_le32(loader, e_len, "EDI0", bzimage_offset + edi);
|
|
patch_le32(loader, e_len, "ESI0", bzimage_offset + esi);
|
|
|
|
if (0x40==(0xf0 & ft.id)) {
|
|
patch_le32(loader, e_len, "ULEN", ph.u_len);
|
|
}
|
|
patch_le32(loader, e_len, "KEIP", kernel_entry);
|
|
patch_le32(loader, e_len, "STAK", stack_during_uncompression);
|
|
|
|
boot_sect_t * const bs = (boot_sect_t *) ((unsigned char *) setup_buf);
|
|
bs->sys_size = (ALIGN_UP(lsize + clen, 16) / 16) & 0xffff;
|
|
|
|
fo->write(setup_buf, setup_buf.getSize());
|
|
fo->write(loader, e_len);
|
|
fo->write(obuf, clen);
|
|
fo->write(loader + e_len, lsize - e_len);
|
|
#if 0
|
|
printf("%-13s: setup : %8ld bytes\n", getName(), (long) setup_buf.getSize());
|
|
printf("%-13s: entry : %8ld bytes\n", getName(), (long) e_len);
|
|
printf("%-13s: compressed : %8ld bytes\n", getName(), (long) clen);
|
|
printf("%-13s: decompressor : %8ld bytes\n", getName(), (long) (lsize - e_len));
|
|
#endif
|
|
|
|
// verify
|
|
verifyOverlappingDecompression();
|
|
|
|
// finally check the compression ratio
|
|
if (!checkFinalCompressionRatio(fo))
|
|
throwNotCompressible();
|
|
}
|
|
|
|
|
|
/*************************************************************************
|
|
// unpack
|
|
**************************************************************************/
|
|
|
|
int PackVmlinuzI386::canUnpack()
|
|
{
|
|
if (readFileHeader() != getFormat())
|
|
return false;
|
|
fi->seek(setup_size, SEEK_SET);
|
|
return readPackHeader(1024) ? 1 : -1;
|
|
}
|
|
|
|
|
|
void PackVmlinuzI386::unpack(OutputFile *fo)
|
|
{
|
|
// no uncompression support for this format, so that
|
|
// it is possible to remove the original deflate code (>10KB)
|
|
|
|
// FIXME: but we could write the uncompressed "vmlinux" image
|
|
|
|
ibuf.alloc(ph.c_len);
|
|
obuf.allocForUncompression(ph.u_len);
|
|
|
|
fi->seek(setup_size + ph.buf_offset + ph.getPackHeaderSize(), SEEK_SET);
|
|
fi->readx(ibuf, ph.c_len);
|
|
|
|
// decompress
|
|
decompress(ibuf, obuf);
|
|
|
|
// unfilter
|
|
Filter ft(ph.level);
|
|
ft.init(ph.filter, kernel_entry);
|
|
ft.cto = (unsigned char) ph.filter_cto;
|
|
ft.unfilter(obuf, ph.u_len);
|
|
|
|
// write decompressed file
|
|
if (fo)
|
|
{
|
|
throwCantUnpack("build a new kernel instead :-)");
|
|
//fo->write(obuf, ph.u_len);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
vi:ts=4:et
|
|
*/
|
|
|
|
|