499 lines
14 KiB
C++
499 lines
14 KiB
C++
/* p_lx_elf.cpp --
|
|
|
|
This file is part of the UPX executable compressor.
|
|
|
|
Copyright (C) 1996-2000 Markus Franz Xaver Johannes Oberhumer
|
|
Copyright (C) 1996-2000 Laszlo Molnar
|
|
Copyright (C) 2000 John F. Reiser
|
|
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@jk.uni-linz.ac.at ml1050@cdata.tvnet.hu
|
|
|
|
John F. Reiser
|
|
jreiser@BitWagon.com
|
|
*/
|
|
|
|
|
|
#include "conf.h"
|
|
|
|
#include "file.h"
|
|
#include "packer.h"
|
|
#include "p_elf.h"
|
|
#include "p_unix.h"
|
|
#include "p_lx_exc.h"
|
|
#include "p_lx_elf.h"
|
|
|
|
#define PT_LOAD Elf_LE32_Phdr::PT_LOAD
|
|
|
|
|
|
/*************************************************************************
|
|
//
|
|
**************************************************************************/
|
|
|
|
static const
|
|
#include "stub/l_le_n2b.h"
|
|
static const
|
|
#include "stub/l_le_n2d.h"
|
|
|
|
|
|
PackLinuxI386elf::PackLinuxI386elf(InputFile *f) :
|
|
super(f), phdri(NULL)
|
|
{
|
|
}
|
|
|
|
PackLinuxI386elf::~PackLinuxI386elf()
|
|
{
|
|
delete[] phdri;
|
|
}
|
|
|
|
const upx_byte *PackLinuxI386elf::getLoader() const
|
|
{
|
|
if (M_IS_NRV2B(opt->method))
|
|
return linux_i386elf_nrv2b_loader;
|
|
if (M_IS_NRV2D(opt->method))
|
|
return linux_i386elf_nrv2d_loader;
|
|
return NULL;
|
|
}
|
|
|
|
int PackLinuxI386elf::getLoaderSize() const
|
|
{
|
|
if (0 != lsize) {
|
|
return lsize;
|
|
}
|
|
if (M_IS_NRV2B(opt->method))
|
|
return sizeof(linux_i386elf_nrv2b_loader);
|
|
if (M_IS_NRV2D(opt->method))
|
|
return sizeof(linux_i386elf_nrv2d_loader);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void PackLinuxI386elf::updateLoader(OutputFile *fo)
|
|
{
|
|
#define PAGE_MASK (~0<<12)
|
|
Elf_LE32_Phdr *const phdro = (Elf_LE32_Phdr *)(sizeof(Elf_LE32_Ehdr)+loader);
|
|
off_t const totlen = fo->getBytesWritten();
|
|
phdro->p_filesz = totlen;
|
|
|
|
// pre-calculate for benefit of runtime disappearing act via munmap()
|
|
phdro->p_memsz = PAGE_MASK & (~PAGE_MASK + totlen);
|
|
|
|
patchLoaderChecksum();
|
|
fo->seek(0, SEEK_SET);
|
|
fo->rewrite(loader, 0x80);
|
|
#undef PAGE_MASK
|
|
}
|
|
|
|
|
|
void PackLinuxI386elf::patchLoader()
|
|
{
|
|
lsize = getLoaderSize();
|
|
Elf_LE32_Ehdr *const ehdr = (Elf_LE32_Ehdr *)(void *)loader;
|
|
Elf_LE32_Phdr *const phdr = (Elf_LE32_Phdr *)(1+ehdr);
|
|
|
|
// stub/scripts/setfold.pl puts address of 'fold_begin' in phdr[1].p_offset
|
|
off_t const fold_begin = phdr[1].p_offset;
|
|
assert(fold_begin > 0);
|
|
assert(fold_begin < (off_t)lsize);
|
|
MemBuffer cprLoader(lsize);
|
|
|
|
// compress compiled C-code portion of loader
|
|
upx_uint const uncLsize = lsize - fold_begin;
|
|
upx_uint cprLsize;
|
|
int r = upx_compress(loader + fold_begin, uncLsize, cprLoader, &cprLsize,
|
|
NULL, opt->method, 10, NULL, NULL);
|
|
if (r != UPX_E_OK || cprLsize >= uncLsize)
|
|
throwInternalError("loaded compression failed");
|
|
|
|
set_le32(0+fold_begin+loader, uncLsize);
|
|
set_le32(4+fold_begin+loader, cprLsize);
|
|
memcpy( 8+fold_begin+loader, cprLoader, cprLsize);
|
|
lsize = 8 + fold_begin + cprLsize;
|
|
patchVersion(loader,lsize);
|
|
|
|
// Info for OS kernel to set the brk()
|
|
unsigned const brka = getbrk(phdri, ehdri.e_phnum);
|
|
phdr[1].p_offset = 0xfff&brka;
|
|
phdr[1].p_vaddr = brka;
|
|
phdr[1].p_paddr = brka;
|
|
phdr[1].p_filesz = 0;
|
|
phdr[1].p_memsz = 0;
|
|
|
|
// The beginning of our loader consists of a elf_hdr (52 bytes) and
|
|
// two sections elf_phdr (2 * 32 byte), so we have 12 free bytes
|
|
// from offset 116 to the program start at offset 128.
|
|
assert(ehdr->e_phoff == sizeof(Elf_LE32_Ehdr));
|
|
assert(ehdr->e_ehsize == sizeof(Elf_LE32_Ehdr));
|
|
assert(ehdr->e_phentsize == sizeof(Elf_LE32_Phdr));
|
|
assert(ehdr->e_phnum == 2);
|
|
assert(ehdr->e_shnum == 0);
|
|
assert(lsize > 128 && lsize < 4096);
|
|
|
|
patchLoaderChecksum();
|
|
}
|
|
|
|
|
|
bool PackLinuxI386elf::canPack()
|
|
{
|
|
unsigned char buf[sizeof(Elf_LE32_Ehdr) + 14*sizeof(Elf_LE32_Phdr)];
|
|
COMPILE_TIME_ASSERT(sizeof(buf) <= 512);
|
|
|
|
exetype = 0;
|
|
|
|
// FIXME: add special checks for uncompresed "vmlinux" kernel
|
|
// FIXME: add checks for FreeBSD/... ELF executables
|
|
|
|
fi->readx(buf, sizeof(buf));
|
|
fi->seek(0, SEEK_SET);
|
|
Elf_LE32_Ehdr const *const ehdr = (Elf_LE32_Ehdr const *)buf;
|
|
|
|
// now check the ELF header
|
|
if (checkEhdr(ehdr) != 0)
|
|
return false;
|
|
|
|
// additional requirements for linux/elf386
|
|
if (ehdr->e_ehsize != sizeof(*ehdr)) {
|
|
throwCantPack("invalid Ehdr e_ehsize; try `--force-execve'");
|
|
return false;
|
|
}
|
|
if (ehdr->e_phoff != sizeof(*ehdr)) {// Phdrs not contiguous with Ehdr
|
|
throwCantPack("non-contiguous Ehdr/Phdr; try `--force-execve'");
|
|
return false;
|
|
}
|
|
|
|
// The first PT_LOAD must cover the beginning of the file (0==p_offset).
|
|
Elf_LE32_Phdr const *phdr = (Elf_LE32_Phdr const *)(buf + ehdr->e_phoff);
|
|
for (unsigned j=0; j < ehdr->e_phnum; ++phdr, ++j) {
|
|
if (j >= 14)
|
|
return false;
|
|
if (phdr->PT_LOAD == phdr->p_type) {
|
|
if (phdr->p_offset!=0)
|
|
return false;
|
|
exetype = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return super::canPack();
|
|
}
|
|
|
|
|
|
void PackLinuxI386elf::packExtent(
|
|
Extent const &x,
|
|
OutputFile *fo,
|
|
unsigned &total_in,
|
|
unsigned &total_out
|
|
)
|
|
{
|
|
fi->seek(x.offset, SEEK_SET);
|
|
for (off_t rest = x.size; 0 != rest; )
|
|
{
|
|
int l = fi->readx(ibuf, UPX_MIN(rest, (off_t)blocksize));
|
|
if (l == 0)
|
|
break;
|
|
rest -= l;
|
|
|
|
// Note: compression for a block can fail if the
|
|
// file is e.g. blocksize + 1 bytes long
|
|
|
|
// compress
|
|
ph.u_len = l;
|
|
compress(ibuf, obuf); // ignore return value
|
|
|
|
if (ph.c_len < ph.u_len)
|
|
{
|
|
if (!testOverlappingDecompression(obuf, OVERHEAD))
|
|
throwNotCompressible();
|
|
}
|
|
else
|
|
{
|
|
ph.c_len = ph.u_len;
|
|
// must update checksum of compressed data
|
|
ph.c_adler = upx_adler32(ph.c_adler, ibuf, ph.u_len);
|
|
}
|
|
|
|
// write block sizes
|
|
unsigned char size[8];
|
|
set_native32(size+0, ph.u_len);
|
|
set_native32(size+4, ph.c_len);
|
|
fo->write(size, 8);
|
|
|
|
// write compressed data
|
|
if (ph.c_len < ph.u_len)
|
|
{
|
|
fo->write(obuf, ph.c_len);
|
|
verifyOverlappingDecompression(&obuf, OVERHEAD);
|
|
}
|
|
else
|
|
fo->write(ibuf, ph.u_len);
|
|
|
|
total_in += ph.u_len;
|
|
total_out += ph.c_len;
|
|
}
|
|
}
|
|
|
|
|
|
void PackLinuxI386elf::pack(OutputFile *fo)
|
|
{
|
|
// set options
|
|
opt->unix.blocksize = blocksize = file_size;
|
|
progid = 0; // not used
|
|
|
|
fi->readx(&ehdri, sizeof(ehdri));
|
|
off_t const sz_phdrs = ehdri.e_phnum * ehdri.e_phentsize;
|
|
|
|
phdri = new Elf_LE32_Phdr[ehdri.e_phnum];
|
|
fi->seek(ehdri.e_phoff, SEEK_SET);
|
|
fi->readx(phdri, sz_phdrs);
|
|
|
|
// prepare loader
|
|
lsize = getLoaderSize();
|
|
loader.alloc(lsize + sizeof(p_info));
|
|
memcpy(loader,getLoader(),lsize);
|
|
|
|
// patch loader, prepare header info, write loader + header info
|
|
patchLoader(); // can change lsize by packing upx_main
|
|
p_info *const hbuf = (p_info *)(loader + lsize);
|
|
set_native32(&hbuf->p_progid, progid);
|
|
set_native32(&hbuf->p_filesize, file_size);
|
|
set_native32(&hbuf->p_blocksize, blocksize);
|
|
fo->write(loader, lsize + sizeof(p_info));
|
|
|
|
// init compression buffers
|
|
ibuf.alloc(blocksize);
|
|
obuf.allocForCompression(blocksize);
|
|
|
|
assert(ehdri.e_phoff == sizeof(Elf_LE32_Ehdr)); // checked by canPack()
|
|
Extent x;
|
|
unsigned k;
|
|
|
|
// count passes, set ptload vars
|
|
ui_total_passes = 0;
|
|
off_t ptload0hi = 0, ptload1lo = 0;
|
|
int nx = 0;
|
|
for (k = 0; k < ehdri.e_phnum; ++k) {
|
|
if (PT_LOAD == phdri[k].p_type) {
|
|
x.offset = phdri[k].p_offset;
|
|
x.size = phdri[k].p_filesz;
|
|
if (0 == ptload0hi) {
|
|
ptload0hi = x.offset + x.size;
|
|
}
|
|
else if (0 == ptload1lo) {
|
|
ptload1lo = x.offset;
|
|
}
|
|
ui_total_passes++;
|
|
} else {
|
|
if (nx++ == 0)
|
|
ui_total_passes++;
|
|
}
|
|
}
|
|
if (ptload0hi < ptload1lo)
|
|
ui_total_passes++;
|
|
|
|
// compress extents
|
|
unsigned total_in = 0;
|
|
unsigned total_out = 0;
|
|
|
|
x.offset = 0;
|
|
x.size = sizeof(Elf_LE32_Ehdr) + sz_phdrs;
|
|
ui_pass = -1;
|
|
packExtent(x, fo, total_in, total_out);
|
|
ui_pass = 0;
|
|
|
|
nx = 0;
|
|
for (k = 0; k < ehdri.e_phnum; ++k) if (PT_LOAD==phdri[k].p_type) {
|
|
x.offset = phdri[k].p_offset;
|
|
x.size = phdri[k].p_filesz;
|
|
if (0 == nx) {
|
|
x.offset += sizeof(Elf_LE32_Ehdr) + sz_phdrs;
|
|
x.size -= sizeof(Elf_LE32_Ehdr) + sz_phdrs;
|
|
}
|
|
packExtent(x, fo, total_in, total_out);
|
|
++nx;
|
|
}
|
|
if (ptload0hi < ptload1lo) { // alignment hole?
|
|
x.offset = ptload0hi;
|
|
x.size = ptload1lo - ptload0hi;
|
|
packExtent(x, fo, total_in, total_out);
|
|
}
|
|
if ((off_t)total_in < file_size) { // non-PT_LOAD stuff
|
|
x.offset = total_in;
|
|
x.size = file_size - total_in;
|
|
packExtent(x, fo, total_in, total_out);
|
|
}
|
|
|
|
if ((off_t)total_in != file_size)
|
|
throwEOFException();
|
|
|
|
// write block end marker (uncompressed size 0)
|
|
fo->write("\x00\x00\x00\x00", 4);
|
|
|
|
// update header with totals
|
|
ph.u_len = total_in;
|
|
ph.c_len = total_out;
|
|
|
|
// write packheader
|
|
const int hsize = ph.getPackHeaderSize();
|
|
set_le32(obuf, UPX_MAGIC_LE32); // note: always le32
|
|
patchPackHeader(obuf, hsize);
|
|
fo->write(obuf, hsize);
|
|
|
|
// write overlay offset (needed for decompression)
|
|
set_native32(obuf, lsize);
|
|
fo->write(obuf, 4);
|
|
|
|
updateLoader(fo);
|
|
|
|
// finally check the compression ratio
|
|
if (!checkFinalCompressionRatio(fo))
|
|
throwNotCompressible();
|
|
}
|
|
|
|
|
|
void PackLinuxI386elf::unpackExtent(unsigned wanted, OutputFile *fo,
|
|
unsigned &total_in, unsigned &total_out,
|
|
unsigned &c_adler, unsigned &u_adler)
|
|
{
|
|
while (wanted) {
|
|
fi->readx(ibuf, 8);
|
|
int const sz_unc = ph.u_len = get_native32(ibuf+0);
|
|
int const sz_cpr = ph.c_len = get_native32(ibuf+4);
|
|
|
|
if (sz_unc == 0) { // must never happen while 0!=wanted
|
|
throwCompressedDataViolation();
|
|
break;
|
|
}
|
|
if (sz_unc <= 0 || sz_cpr <= 0)
|
|
throwCompressedDataViolation();
|
|
if (sz_cpr > sz_unc || sz_unc > (int)blocksize)
|
|
throwCompressedDataViolation();
|
|
|
|
int j = blocksize + OVERHEAD - sz_cpr;
|
|
fi->readx(ibuf+j, sz_cpr);
|
|
// update checksum of compressed data
|
|
c_adler = upx_adler32(c_adler, ibuf + j, sz_cpr);
|
|
// decompress
|
|
if (sz_cpr < sz_unc)
|
|
{
|
|
decompress(ibuf+j, ibuf, false);
|
|
j = 0;
|
|
}
|
|
// update checksum of uncompressed data
|
|
u_adler = upx_adler32(u_adler, ibuf + j, sz_unc);
|
|
total_in += sz_cpr;
|
|
total_out += sz_unc;
|
|
// write block
|
|
if (fo)
|
|
fo->write(ibuf + j, sz_unc);
|
|
wanted -= sz_unc;
|
|
}
|
|
}
|
|
|
|
|
|
void PackLinuxI386elf::unpack(OutputFile *fo)
|
|
{
|
|
#define MAX_ELF_HDR 512
|
|
char bufehdr[MAX_ELF_HDR];
|
|
Elf_LE32_Ehdr *const ehdr = (Elf_LE32_Ehdr *)bufehdr;
|
|
Elf_LE32_Phdr const *phdr = (Elf_LE32_Phdr *)(1+ehdr);
|
|
|
|
fi->seek(overlay_offset, SEEK_SET);
|
|
p_info hbuf;
|
|
fi->readx(&hbuf, sizeof(hbuf));
|
|
unsigned orig_file_size = get_native32(&hbuf.p_filesize);
|
|
blocksize = get_native32(&hbuf.p_blocksize);
|
|
if (file_size > (off_t)orig_file_size || blocksize > orig_file_size)
|
|
throwCantUnpack("file header corrupted");
|
|
|
|
ibuf.alloc(blocksize + OVERHEAD);
|
|
fi->readx(ibuf, 2*4);
|
|
ph.u_len = get_native32(0+ibuf);
|
|
ph.c_len = get_native32(4+ibuf);
|
|
|
|
// Uncompress Ehdr and Phdrs.
|
|
fi->readx(ibuf, ph.c_len);
|
|
decompress(ibuf, (upx_byte *)ehdr, false);
|
|
|
|
unsigned total_in = 0;
|
|
unsigned total_out = 0;
|
|
unsigned c_adler = upx_adler32(0, NULL, 0);
|
|
unsigned u_adler = upx_adler32(0, NULL, 0);
|
|
off_t ptload0hi=0, ptload1lo=0;
|
|
|
|
// decompress PT_LOAD
|
|
fi->seek(- (off_t) (2*4 + ph.c_len), SEEK_CUR);
|
|
for (unsigned j=0; j < ehdr->e_phnum; ++phdr, ++j) {
|
|
if (PT_LOAD==phdr->p_type) {
|
|
if (0==ptload0hi) {
|
|
ptload0hi = phdr->p_filesz + phdr->p_offset;
|
|
}
|
|
else if (0==ptload1lo) {
|
|
ptload1lo = phdr->p_offset;
|
|
}
|
|
if (fo)
|
|
fo->seek(phdr->p_offset, SEEK_SET);
|
|
unpackExtent(phdr->p_filesz, fo, total_in, total_out, c_adler, u_adler);
|
|
}
|
|
}
|
|
|
|
if (ptload0hi < ptload1lo) { // alignment hole?
|
|
if (fo)
|
|
fo->seek(ptload0hi, SEEK_SET);
|
|
unpackExtent(ptload1lo - ptload0hi, fo, total_in, total_out, c_adler, u_adler);
|
|
}
|
|
if (total_out != orig_file_size) { // non-PT_LOAD stuff
|
|
if (fo)
|
|
fo->seek(0, SEEK_END);
|
|
unpackExtent(orig_file_size - total_out, fo, total_in, total_out, c_adler, u_adler);
|
|
}
|
|
|
|
// check for end-of-file
|
|
fi->readx(ibuf, 2*4);
|
|
unsigned const sz_unc = ph.u_len = get_native32(ibuf+0);
|
|
|
|
if (sz_unc == 0) { // uncompressed size 0 -> EOF
|
|
// note: magic is always stored le32
|
|
unsigned const sz_cpr = get_le32(ibuf+4);
|
|
if (sz_cpr != UPX_MAGIC_LE32) // sz_cpr must be h->magic
|
|
throwCompressedDataViolation();
|
|
}
|
|
else { // extra bytes after end?
|
|
throwCompressedDataViolation();
|
|
}
|
|
|
|
// update header with totals
|
|
ph.c_len = total_in;
|
|
ph.u_len = total_out;
|
|
|
|
// all bytes must be written
|
|
if (total_out != orig_file_size)
|
|
throwEOFException();
|
|
|
|
// finally test the checksums
|
|
if (ph.c_adler != c_adler || ph.u_adler != u_adler)
|
|
throwChecksumError();
|
|
#undef MAX_ELF_HDR
|
|
}
|
|
|
|
|
|
/*
|
|
vi:ts=4:et
|
|
*/
|
|
|