Split PackLinuxElf64::unpack for main program vs shared library

modified:   p_lx_elf.cpp
This commit is contained in:
John Reiser 2021-02-22 16:15:03 -08:00 committed by Markus F.X.J. Oberhumer
parent fb844a8ed1
commit 182e0796df

View File

@ -402,7 +402,6 @@ off_t PackLinuxElf32::pack3(OutputFile *fo, Filter &ft)
set_te32(&elfout.phdr[C_TEXT].p_filesz, v_hole);
set_te32(&elfout.phdr[C_TEXT].p_memsz, v_hole);
// Then compressed gaps (including debuginfo.)
total_in = 0; total_out = 0;
for (unsigned k = 0; k < e_phnum; ++k) {
Extent x;
x.size = find_LOAD_gap(phdri, k, e_phnum);
@ -526,7 +525,6 @@ off_t PackLinuxElf64::pack3(OutputFile *fo, Filter &ft)
set_te64(&elfout.phdr[C_TEXT].p_filesz, v_hole);
set_te64(&elfout.phdr[C_TEXT].p_memsz, v_hole);
// Then compressed gaps (including debuginfo.)
total_in = 0; total_out = 0;
for (unsigned k = 0; k < e_phnum; ++k) {
Extent x;
x.size = find_LOAD_gap(phdri, k, e_phnum);
@ -4567,92 +4565,16 @@ PackLinuxElf64::unRela64(
fo->rewrite(rela0, relasz);
}
void PackLinuxElf64::unpack(OutputFile *fo)
void PackLinuxElf64::un_shlib_1(
OutputFile *const fo,
unsigned &c_adler,
unsigned &u_adler,
Elf64_Ehdr const *ehdr,
Elf64_Phdr const *const dynhdr,
unsigned const orig_file_size,
unsigned const szb_info
)
{
if (e_phoff != sizeof(Elf64_Ehdr)) {// Phdrs not contiguous with Ehdr
throwCantUnpack("bad e_phoff");
}
unsigned const c_phnum = get_te16(&ehdri.e_phnum);
old_data_off = 0;
old_data_len = 0;
upx_uint64_t old_dtinit = 0;
unsigned is_asl = 0; // is Android Shared Library
unsigned szb_info = sizeof(b_info);
{
upx_uint64_t const e_entry = get_te64(&ehdri.e_entry);
if (e_entry < 0x401180
&& get_te16(&ehdri.e_machine)==Elf64_Ehdr::EM_386) { /* old style, 8-byte b_info */
szb_info = 2*sizeof(unsigned);
}
}
fi->seek(overlay_offset - sizeof(l_info), SEEK_SET);
fi->readx(&linfo, sizeof(linfo));
lsize = get_te16(&linfo.l_lsize);
if (UPX_MAGIC_LE32 != get_le32(&linfo.l_magic)) {
throwCantUnpack("l_info corrupted");
}
p_info hbuf; fi->readx(&hbuf, sizeof(hbuf));
unsigned orig_file_size = get_te32(&hbuf.p_filesize);
blocksize = get_te32(&hbuf.p_blocksize);
if ((u32_t)file_size > orig_file_size || blocksize > orig_file_size
|| !mem_size_valid(1, blocksize, OVERHEAD))
throwCantUnpack("p_info corrupted");
ibuf.alloc(blocksize + OVERHEAD);
b_info bhdr; memset(&bhdr, 0, sizeof(bhdr));
fi->readx(&bhdr, szb_info);
ph.u_len = get_te32(&bhdr.sz_unc);
ph.c_len = get_te32(&bhdr.sz_cpr);
if (ph.c_len > (unsigned)file_size || ph.c_len == 0 || ph.u_len == 0
|| ph.u_len > orig_file_size)
throwCantUnpack("b_info corrupted");
ph.filter_cto = bhdr.b_cto8;
MemBuffer u(ph.u_len);
Elf64_Ehdr *const ehdr = (Elf64_Ehdr *)&u[0];
Elf64_Phdr const *phdr = nullptr;
// Uncompress Ehdr and Phdrs.
if (ibuf.getSize() < ph.c_len)
throwCompressedDataViolation();
fi->readx(ibuf, ph.c_len);
decompress(ibuf, (upx_byte *)ehdr, false);
if (ehdr->e_type !=ehdri.e_type
|| ehdr->e_machine!=ehdri.e_machine
|| ehdr->e_version!=ehdri.e_version
// less strict for EM_PPC64 to workaround earlier bug
|| !( ehdr->e_flags==ehdri.e_flags
|| Elf64_Ehdr::EM_PPC64 == get_te16(&ehdri.e_machine))
|| ehdr->e_ehsize !=ehdri.e_ehsize
// check EI_MAG[0-3], EI_CLASS, EI_DATA, EI_VERSION
|| memcmp(ehdr->e_ident, ehdri.e_ident, Elf64_Ehdr::EI_OSABI)) {
throwCantUnpack("ElfXX_Ehdr corrupted");
}
fi->seek(- (off_t) (szb_info + ph.c_len), SEEK_CUR);
unsigned const u_phnum = get_te16(&ehdr->e_phnum);
total_in = 0;
total_out = 0;
unsigned c_adler = upx_adler32(nullptr, 0);
unsigned u_adler = upx_adler32(nullptr, 0);
#define MAX_ELF_HDR 1024
if ((umin64(MAX_ELF_HDR, ph.u_len) - sizeof(Elf64_Ehdr))/sizeof(Elf64_Phdr) < u_phnum) {
throwCantUnpack("bad compressed e_phnum");
}
#undef MAX_ELF_HDR
// Packed ET_EXE has no PT_DYNAMIC.
// Packed ET_DYN has original PT_DYNAMIC for info needed by rtld.
Elf64_Phdr const *const dynhdr = elf_find_ptype(Elf64_Phdr::PT_DYNAMIC, phdri, c_phnum);
bool const is_shlib = !!dynhdr;
if (is_shlib) {
// Unpack and output the Ehdr and Phdrs for real.
// This depends on position within input file fi.
unpackExtent(ph.u_len, fo,
c_adler, u_adler, false, szb_info);
// The first PT_LOAD. Part is not compressed (for benefit of rtld.)
fi->seek(0, SEEK_SET);
fi->readx(ibuf, get_te64(&dynhdr->p_offset) + get_te64(&dynhdr->p_filesz));
@ -4668,13 +4590,6 @@ void PackLinuxElf64::unpack(OutputFile *fo)
xct_off = e_shoff;
}
// un-Relocate dynsym (DT_SYMTAB) which is below xct_off
upx_uint64_t dyn_offset = get_te64(&dynhdr->p_offset);
upx_uint64_t dyn_filesz = get_te64(&dynhdr->p_filesz);
if (orig_file_size < dyn_offset
|| (orig_file_size - dyn_offset) < dyn_filesz) {
throwCantUnpack("bad PT_DYNAMIC");
}
dynseg = (Elf64_Dyn const *)ibuf.subref("bad DYNAMIC", dyn_offset, dyn_filesz);
dynstr = (char const *)elf_find_dynamic(Elf64_Dyn::DT_STRTAB);
sec_dynsym = elf_find_section_type(Elf64_Shdr::SHT_DYNSYM);
if (sec_dynsym) {
@ -4705,25 +4620,16 @@ void PackLinuxElf64::unpack(OutputFile *fo)
if (fo) {
fo->write(ibuf + ph.u_len, xct_off - ph.u_len);
}
// Search the Phdrs of compressed
int n_ptload = 0;
phdr = (Elf64_Phdr *) (void *) (1+ (Elf64_Ehdr *)(unsigned char *)ibuf);
for (unsigned j=0; j < u_phnum; ++phdr, ++j) {
if (PT_LOAD64==get_te32(&phdr->p_type) && 0!=n_ptload++) {
old_data_off = get_te64(&phdr->p_offset);
old_data_len = get_te64(&phdr->p_filesz);
break;
}
}
total_in = xct_off;
total_out = xct_off;
ph.u_len = 0;
// Position the input for next unpackExtent.
fi->seek(sizeof(linfo) + overlay_offset + sizeof(hbuf) + szb_info + ph.c_len, SEEK_SET);
fi->seek(sizeof(linfo) + overlay_offset + sizeof(p_info) + szb_info + ph.c_len, SEEK_SET);
// Decompress and unfilter the tail of first PT_LOAD.
phdr = (Elf64_Phdr *) (void *) (1+ ehdr);
Elf64_Phdr const *phdr = (Elf64_Phdr const *) (void const *) (1+ ehdr);
unsigned const u_phnum = get_te16(&ehdr->e_phnum);
for (unsigned j=0; j < u_phnum; ++phdr, ++j) {
if (PT_LOAD64==get_te32(&phdr->p_type)) {
ph.u_len = get_te64(&phdr->p_filesz) - xct_off;
@ -4733,97 +4639,32 @@ void PackLinuxElf64::unpack(OutputFile *fo)
unpackExtent(ph.u_len, fo,
c_adler, u_adler, false, szb_info);
}
else { // main executable
// Decompress each PT_LOAD.
bool first_PF_X = true;
phdr = (Elf64_Phdr *) (void *) (1+ ehdr); // uncompressed
void PackLinuxElf64::un_DT_INIT(
Elf64_Phdr const *phdr0,
unsigned u_phnum,
unsigned old_dtinit,
OutputFile *fo,
unsigned is_asl
)
{
// DT_INIT must be restored.
// Search the Phdrs of compressed
int n_ptload = 0;
old_data_off = 0;
old_data_len = 0;
Elf64_Phdr const *phdr = phdr0;
for (unsigned j=0; j < u_phnum; ++phdr, ++j) {
if (PT_LOAD64==get_te32(&phdr->p_type)) {
unsigned const filesz = get_te64(&phdr->p_filesz);
unsigned const offset = get_te64(&phdr->p_offset);
if (fo)
fo->seek(offset, SEEK_SET);
if (Elf64_Phdr::PF_X & get_te32(&phdr->p_flags)) {
unpackExtent(filesz, fo,
c_adler, u_adler, first_PF_X, szb_info);
first_PF_X = false;
}
else {
unpackExtent(filesz, fo,
c_adler, u_adler, false, szb_info);
}
}
}
}
phdr = phdri;
load_va = 0;
for (unsigned j=0; j < c_phnum; ++j) {
if (PT_LOAD64==get_te32(&phdr->p_type)) {
load_va = get_te64(&phdr->p_vaddr);
if (PT_LOAD64==get_te32(&phdr->p_type) && 0!=n_ptload++) {
old_data_off = get_te64(&phdr->p_offset);
old_data_len = get_te64(&phdr->p_filesz);
break;
}
}
if (0x1000==get_te64(&phdri[0].p_filesz) // detect C_BASE style
&& 0==get_te64(&phdri[1].p_offset)
&& 0==get_te64(&phdri[0].p_offset)
&& get_te64(&phdri[1].p_filesz) == get_te64(&phdri[1].p_memsz)) {
fi->seek(up4(get_te64(&phdr[1].p_memsz)), SEEK_SET); // past the loader
}
else if (is_shlib
|| ((unsigned)(get_te64(&ehdri.e_entry) - load_va) + up4(lsize) +
ph.getPackHeaderSize() + sizeof(overlay_offset))
< up4(file_size)) {
// Loader is not at end; skip past it.
funpad4(fi); // MATCH01
unsigned d_info[6]; fi->readx(d_info, sizeof(d_info));
if (0==old_dtinit) {
old_dtinit = get_te32(&d_info[2 + (0==d_info[0])]);
is_asl = 1u& get_te32(&d_info[0 + (0==d_info[0])]);
}
fi->seek(lsize - sizeof(d_info), SEEK_CUR);
}
// The gaps between PT_LOAD and after last PT_LOAD
phdr = (Elf64_Phdr *)&u[sizeof(*ehdr)];
upx_uint64_t hi_offset(0);
for (unsigned j = 0; j < u_phnum; ++j) {
if (PT_LOAD64==phdr[j].p_type
&& hi_offset < phdr[j].p_offset)
hi_offset = phdr[j].p_offset;
}
for (unsigned j = 0; j < u_phnum; ++j) {
unsigned const size = find_LOAD_gap(phdr, j, u_phnum);
if (size) {
unsigned const where = get_te64(&phdr[j].p_offset) +
get_te64(&phdr[j].p_filesz);
if (fo)
fo->seek(where, SEEK_SET);
unpackExtent(size, fo,
c_adler, u_adler, false, szb_info,
(phdr[j].p_offset != hi_offset));
}
}
// check for end-of-file
fi->readx(&bhdr, szb_info);
unsigned const sz_unc = ph.u_len = get_te32(&bhdr.sz_unc);
if (sz_unc == 0) { // uncompressed size 0 -> EOF
// note: magic is always stored le32
unsigned const sz_cpr = get_le32(&bhdr.sz_cpr);
if (sz_cpr != UPX_MAGIC_LE32) // sz_cpr must be h->magic
throwCompressedDataViolation();
}
else { // extra bytes after end?
throwCompressedDataViolation();
}
if (is_shlib) {
// DT_INIT must be restored.
// If android_shlib, then the asl_delta relocations must be un-done.
int n_ptload = 0;
upx_uint64_t load_off = 0;
phdr = (Elf64_Phdr *)&u[sizeof(*ehdr)];
phdr = phdr0;
n_ptload = 0;
for (unsigned j= 0; j < u_phnum; ++j, ++phdr) {
if (PT_LOAD64==get_te32(&phdr->p_type) && 0!=n_ptload++) {
load_off = get_te64(&phdr->p_offset);
@ -4833,7 +4674,7 @@ void PackLinuxElf64::unpack(OutputFile *fo)
total_in += old_data_len;
total_out += old_data_len;
Elf64_Phdr const *udynhdr = (Elf64_Phdr *)&u[sizeof(*ehdr)];
Elf64_Phdr const *udynhdr = phdr0;
for (unsigned j3= 0; j3 < u_phnum; ++j3, ++udynhdr)
if (Elf64_Phdr::PT_DYNAMIC==get_te32(&udynhdr->p_type)) {
upx_uint64_t dt_pltrelsz(0), dt_jmprel(0);
@ -4915,6 +4756,184 @@ void PackLinuxElf64::unpack(OutputFile *fo)
}
}
void PackLinuxElf64::unpack(OutputFile *fo)
{
if (e_phoff != sizeof(Elf64_Ehdr)) {// Phdrs not contiguous with Ehdr
throwCantUnpack("bad e_phoff");
}
unsigned const c_phnum = get_te16(&ehdri.e_phnum);
unsigned u_phnum = 0;
upx_uint64_t old_dtinit = 0;
unsigned is_asl = 0; // is Android Shared Library
unsigned szb_info = sizeof(b_info);
{
upx_uint64_t const e_entry = get_te64(&ehdri.e_entry);
if (e_entry < 0x401180
&& get_te16(&ehdri.e_machine)==Elf64_Ehdr::EM_386) { /* old style, 8-byte b_info */
szb_info = 2*sizeof(unsigned);
}
}
fi->seek(overlay_offset - sizeof(l_info), SEEK_SET);
fi->readx(&linfo, sizeof(linfo));
lsize = get_te16(&linfo.l_lsize);
if (UPX_MAGIC_LE32 != get_le32(&linfo.l_magic)) {
throwCantUnpack("l_info corrupted");
}
p_info hbuf; fi->readx(&hbuf, sizeof(hbuf));
unsigned orig_file_size = get_te32(&hbuf.p_filesize);
blocksize = get_te32(&hbuf.p_blocksize);
if ((u32_t)file_size > orig_file_size || blocksize > orig_file_size
|| !mem_size_valid(1, blocksize, OVERHEAD))
throwCantUnpack("p_info corrupted");
ibuf.alloc(blocksize + OVERHEAD);
b_info bhdr; memset(&bhdr, 0, sizeof(bhdr));
fi->readx(&bhdr, szb_info);
ph.u_len = get_te32(&bhdr.sz_unc);
ph.c_len = get_te32(&bhdr.sz_cpr);
if (ph.c_len > (unsigned)file_size || ph.c_len == 0 || ph.u_len == 0
|| ph.u_len > orig_file_size)
throwCantUnpack("b_info corrupted");
ph.filter_cto = bhdr.b_cto8;
MemBuffer u(ph.u_len);
Elf64_Ehdr *const ehdr = (Elf64_Ehdr *)&u[0];
Elf64_Phdr const *phdr = nullptr;
total_in = 0;
total_out = 0;
unsigned c_adler = upx_adler32(nullptr, 0);
unsigned u_adler = upx_adler32(nullptr, 0);
unsigned is_shlib = 0;
Elf64_Phdr const *const dynhdr = elf_find_ptype(Elf64_Phdr::PT_DYNAMIC, phdri, c_phnum);
if (dynhdr) {
upx_uint64_t dyn_offset = get_te64(&dynhdr->p_offset);
upx_uint64_t dyn_filesz = get_te64(&dynhdr->p_filesz);
dynseg = (Elf64_Dyn const *)ibuf.subref("bad DYNAMIC", dyn_offset, dyn_filesz);
// Packed shlib? (ET_DYN without -fPIE)
if (!(Elf64_Dyn::DF_1_PIE & elf_unsigned_dynamic(Elf64_Dyn::DT_FLAGS_1))) {
is_shlib = 1;
un_shlib_1(fo, c_adler, u_adler, ehdr, dynhdr, orig_file_size, szb_info);
}
}
else { // main executable
// Uncompress Ehdr and Phdrs: info for control of unpacking
if (ibuf.getSize() < ph.c_len)
throwCompressedDataViolation();
fi->readx(ibuf, ph.c_len);
decompress(ibuf, (upx_byte *)ehdr, false);
if (ehdr->e_type !=ehdri.e_type
|| ehdr->e_machine!=ehdri.e_machine
|| ehdr->e_version!=ehdri.e_version
// less strict for EM_PPC64 to workaround earlier bug
|| !( ehdr->e_flags==ehdri.e_flags
|| Elf64_Ehdr::EM_PPC64 == get_te16(&ehdri.e_machine))
|| ehdr->e_ehsize !=ehdri.e_ehsize
// check EI_MAG[0-3], EI_CLASS, EI_DATA, EI_VERSION
|| memcmp(ehdr->e_ident, ehdri.e_ident, Elf64_Ehdr::EI_OSABI)) {
throwCantUnpack("ElfXX_Ehdr corrupted");
}
// Rewind: prepare for data phase
fi->seek(- (off_t) (szb_info + ph.c_len), SEEK_CUR);
u_phnum = get_te16(&ehdr->e_phnum);
#define MAX_ELF_HDR 1024
if ((umin64(MAX_ELF_HDR, ph.u_len) - sizeof(Elf64_Ehdr))/sizeof(Elf64_Phdr) < u_phnum) {
throwCantUnpack("bad compressed e_phnum");
}
#undef MAX_ELF_HDR
// Decompress each PT_LOAD.
bool first_PF_X = true;
phdr = (Elf64_Phdr *) (void *) (1+ ehdr); // uncompressed
for (unsigned j=0; j < u_phnum; ++phdr, ++j) {
if (PT_LOAD64==get_te32(&phdr->p_type)) {
unsigned const filesz = get_te64(&phdr->p_filesz);
unsigned const offset = get_te64(&phdr->p_offset);
if (fo)
fo->seek(offset, SEEK_SET);
if (Elf64_Phdr::PF_X & get_te32(&phdr->p_flags)) {
unpackExtent(filesz, fo,
c_adler, u_adler, first_PF_X, szb_info);
first_PF_X = false;
}
else {
unpackExtent(filesz, fo,
c_adler, u_adler, false, szb_info);
}
}
}
}
phdr = phdri;
load_va = 0;
for (unsigned j=0; j < c_phnum; ++j) {
if (PT_LOAD64==get_te32(&phdr->p_type)) {
load_va = get_te64(&phdr->p_vaddr);
break;
}
}
if (0x1000==get_te64(&phdri[0].p_filesz) // detect C_BASE style
&& 0==get_te64(&phdri[1].p_offset)
&& 0==get_te64(&phdri[0].p_offset)
&& get_te64(&phdri[1].p_filesz) == get_te64(&phdri[1].p_memsz)) {
fi->seek(up4(get_te64(&phdr[1].p_memsz)), SEEK_SET); // past the loader
}
else if (is_shlib
|| ((unsigned)(get_te64(&ehdri.e_entry) - load_va) + up4(lsize) +
ph.getPackHeaderSize() + sizeof(overlay_offset))
< up4(file_size)) {
// Loader is not at end; skip past it.
funpad4(fi); // MATCH01
unsigned d_info[6]; fi->readx(d_info, sizeof(d_info));
if (0==old_dtinit) {
old_dtinit = get_te32(&d_info[2 + (0==d_info[0])]);
is_asl = 1u& get_te32(&d_info[0 + (0==d_info[0])]);
}
fi->seek(lsize - sizeof(d_info), SEEK_CUR);
}
// The gaps between PT_LOAD and after last PT_LOAD
phdr = (Elf64_Phdr *)&u[sizeof(*ehdr)];
upx_uint64_t hi_offset(0);
for (unsigned j = 0; j < u_phnum; ++j) {
if (PT_LOAD64==phdr[j].p_type
&& hi_offset < phdr[j].p_offset)
hi_offset = phdr[j].p_offset;
}
for (unsigned j = 0; j < u_phnum; ++j) {
unsigned const size = find_LOAD_gap(phdr, j, u_phnum);
if (size) {
unsigned const where = get_te64(&phdr[j].p_offset) +
get_te64(&phdr[j].p_filesz);
if (fo)
fo->seek(where, SEEK_SET);
unpackExtent(size, fo,
c_adler, u_adler, false, szb_info,
(phdr[j].p_offset != hi_offset));
}
}
// check for end-of-file
fi->readx(&bhdr, szb_info);
unsigned const sz_unc = ph.u_len = get_te32(&bhdr.sz_unc);
if (sz_unc == 0) { // uncompressed size 0 -> EOF
// note: magic is always stored le32
unsigned const sz_cpr = get_le32(&bhdr.sz_cpr);
if (sz_cpr != UPX_MAGIC_LE32) // sz_cpr must be h->magic
throwCompressedDataViolation();
}
else { // extra bytes after end?
throwCompressedDataViolation();
}
if (is_shlib) {
un_DT_INIT(phdr, u_phnum, old_dtinit, fo, is_asl);
}
// update header with totals
ph.c_len = total_in;
ph.u_len = total_out;
@ -5666,8 +5685,6 @@ void PackLinuxElf32::unpack(OutputFile *fo)
throwCantUnpack("bad e_phoff");
}
unsigned const c_phnum = get_te16(&ehdri.e_phnum);
old_data_off = 0;
old_data_len = 0;
unsigned old_dtinit = 0;
unsigned is_asl = 0; // is Android Shared Library
@ -5799,16 +5816,6 @@ void PackLinuxElf32::unpack(OutputFile *fo)
if (fo) {
fo->write(ibuf + ph.u_len, xct_off - ph.u_len);
}
// Search the Phdrs of compressed
int n_ptload = 0;
phdr = (Elf32_Phdr *) (void *) (1+ (Elf32_Ehdr *)(unsigned char *)ibuf);
for (unsigned j=0; j < u_phnum; ++phdr, ++j) {
if (PT_LOAD32==get_te32(&phdr->p_type) && 0!=n_ptload++) {
old_data_off = get_te32(&phdr->p_offset);
old_data_len = get_te32(&phdr->p_filesz);
break;
}
}
total_in = xct_off;
total_out = xct_off;