mirror of https://github.com/upx/upx.git
pepfile.* disabled; PeFile64 class introduced
This commit is contained in:
parent
e49b65723f
commit
18e55061e1
|
@ -118,9 +118,7 @@ PackW32Pe::PackW32Pe(InputFile *f) : super(f)
|
||||||
|
|
||||||
|
|
||||||
PackW32Pe::~PackW32Pe()
|
PackW32Pe::~PackW32Pe()
|
||||||
{
|
{}
|
||||||
delete [] oloadconf;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const int *PackW32Pe::getCompressionMethods(int method, int level) const
|
const int *PackW32Pe::getCompressionMethods(int method, int level) const
|
||||||
|
@ -858,20 +856,6 @@ void PackW32Pe::pack(OutputFile *fo)
|
||||||
throwNotCompressible();
|
throwNotCompressible();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
extra info added to help uncompression:
|
|
||||||
|
|
||||||
<ih sizeof(pe_head)>
|
|
||||||
<pe_section_t objs*sizeof(pe_section_t)>
|
|
||||||
<start of compressed imports 4> - optional \
|
|
||||||
<start of the names from uncompressed imports> - opt /
|
|
||||||
<start of compressed relocs 4> - optional \
|
|
||||||
<relocation type indicator 1> - optional /
|
|
||||||
<icondir_count 2> - optional
|
|
||||||
<offset of extra info 4>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
vi:ts=4:et
|
vi:ts=4:et
|
||||||
*/
|
*/
|
||||||
|
|
871
src/p_w64pep.cpp
871
src/p_w64pep.cpp
|
@ -35,7 +35,7 @@
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
#include "filter.h"
|
#include "filter.h"
|
||||||
#include "packer.h"
|
#include "packer.h"
|
||||||
#include "pepfile.h"
|
#include "pefile.h"
|
||||||
#include "p_w64pep.h"
|
#include "p_w64pep.h"
|
||||||
#include "linker.h"
|
#include "linker.h"
|
||||||
|
|
||||||
|
@ -93,6 +93,7 @@ static unsigned my_strlen(const unsigned char *s)
|
||||||
#define OPTR_C(type, var, v) type* const var = (v)
|
#define OPTR_C(type, var, v) type* const var = (v)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if 0
|
||||||
static void xcheck(const void *p, size_t plen, const void *b, size_t blen)
|
static void xcheck(const void *p, size_t plen, const void *b, size_t blen)
|
||||||
{
|
{
|
||||||
const char *pp = (const char *) p;
|
const char *pp = (const char *) p;
|
||||||
|
@ -100,7 +101,6 @@ static void xcheck(const void *p, size_t plen, const void *b, size_t blen)
|
||||||
if (pp < bb || pp > bb + blen || pp + plen > bb + blen)
|
if (pp < bb || pp > bb + blen || pp + plen > bb + blen)
|
||||||
throwCantUnpack("pointer out of range; take care!");
|
throwCantUnpack("pointer out of range; take care!");
|
||||||
}
|
}
|
||||||
#if 0
|
|
||||||
static void xcheck(size_t poff, size_t plen, const void *b, size_t blen)
|
static void xcheck(size_t poff, size_t plen, const void *b, size_t blen)
|
||||||
{
|
{
|
||||||
ACC_UNUSED(b);
|
ACC_UNUSED(b);
|
||||||
|
@ -126,7 +126,6 @@ PackW64Pep::PackW64Pep(InputFile *f) : super(f)
|
||||||
isrtm = false;
|
isrtm = false;
|
||||||
use_dep_hack = true;
|
use_dep_hack = true;
|
||||||
use_clear_dirty_stack = true;
|
use_clear_dirty_stack = true;
|
||||||
use_tls_callbacks = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -167,671 +166,6 @@ int PackW64Pep::readFileHeader()
|
||||||
return super::readFileHeader();
|
return super::readFileHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************************************************************
|
|
||||||
// import handling
|
|
||||||
**************************************************************************/
|
|
||||||
|
|
||||||
__packed_struct(import_desc)
|
|
||||||
LE32 oft; // orig first thunk
|
|
||||||
char _[8];
|
|
||||||
LE32 dllname;
|
|
||||||
LE32 iat; // import address table
|
|
||||||
__packed_struct_end()
|
|
||||||
|
|
||||||
/*
|
|
||||||
ImportLinker: 32 and 64 bit import table building.
|
|
||||||
Import entries (dll name + proc name/ordinal pairs) can be
|
|
||||||
added in arbitrary order.
|
|
||||||
|
|
||||||
Internally it works by creating sections with special names,
|
|
||||||
and adding relocation entries between those sections. The special
|
|
||||||
names ensure that when the import table is built in the memory
|
|
||||||
from those sections, a correct table can be generated simply by
|
|
||||||
sorting the sections by name, and adding all of them to the output
|
|
||||||
in the sorted order.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class ImportLinker : public ElfLinkerAMD64
|
|
||||||
{
|
|
||||||
struct tstr : private ::noncopyable
|
|
||||||
{
|
|
||||||
char *s;
|
|
||||||
explicit tstr(char *str) : s(str) {}
|
|
||||||
~tstr() { delete [] s; }
|
|
||||||
operator char *() const { return s; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// encoding of dll and proc names are required, so that our special
|
|
||||||
// control characters in the name of sections can work as intended
|
|
||||||
static char *encode_name(const char *name, char *buf)
|
|
||||||
{
|
|
||||||
char *b = buf;
|
|
||||||
while (*name)
|
|
||||||
{
|
|
||||||
*b++ = 'a' + ((*name >> 4) & 0xf);
|
|
||||||
*b++ = 'a' + (*name & 0xf);
|
|
||||||
name++;
|
|
||||||
}
|
|
||||||
*b = 0;
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *name_for_dll(const char *dll, char first_char)
|
|
||||||
{
|
|
||||||
assert(dll);
|
|
||||||
unsigned l = strlen(dll);
|
|
||||||
assert(l > 0);
|
|
||||||
|
|
||||||
char *name = new char[3 * l + 2];
|
|
||||||
assert(name);
|
|
||||||
name[0] = first_char;
|
|
||||||
char *n = name + 1 + 2 * l;
|
|
||||||
do {
|
|
||||||
*n++ = tolower(*dll);
|
|
||||||
} while(*dll++);
|
|
||||||
return encode_name(name + 1 + 2 * l, name + 1) - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *name_for_proc(const char *dll, const char *proc,
|
|
||||||
char first_char, char separator)
|
|
||||||
{
|
|
||||||
unsigned len = 1 + 2 * strlen(dll) + 1 + 2 * strlen(proc) + 1 + 1;
|
|
||||||
tstr dlln(name_for_dll(dll, first_char));
|
|
||||||
char *procn = new char[len];
|
|
||||||
snprintf(procn, len - 1, "%s%c", (const char*) dlln, separator);
|
|
||||||
encode_name(proc, procn + strlen(procn));
|
|
||||||
return procn;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char zeros[sizeof(import_desc)];
|
|
||||||
|
|
||||||
enum {
|
|
||||||
// the order of identifiers is very important below!!
|
|
||||||
descriptor_id = 'D',
|
|
||||||
thunk_id,
|
|
||||||
dll_name_id,
|
|
||||||
proc_name_id,
|
|
||||||
ordinal_id,
|
|
||||||
|
|
||||||
thunk_separator_first,
|
|
||||||
thunk_separator,
|
|
||||||
thunk_separator_last,
|
|
||||||
procname_separator,
|
|
||||||
};
|
|
||||||
|
|
||||||
unsigned thunk_size; // 4 or 8 bytes
|
|
||||||
|
|
||||||
void add(const char *dll, const char *proc, unsigned ordinal)
|
|
||||||
{
|
|
||||||
tstr sdll(name_for_dll(dll, dll_name_id));
|
|
||||||
tstr desc_name(name_for_dll(dll, descriptor_id));
|
|
||||||
|
|
||||||
char tsep = thunk_separator;
|
|
||||||
if (findSection(sdll, false) == NULL)
|
|
||||||
{
|
|
||||||
tsep = thunk_separator_first;
|
|
||||||
addSection(sdll, dll, strlen(dll) + 1, 0); // name of the dll
|
|
||||||
addSymbol(sdll, sdll, 0);
|
|
||||||
|
|
||||||
addSection(desc_name, zeros, sizeof(zeros), 0); // descriptor
|
|
||||||
addRelocation(desc_name, offsetof(import_desc, dllname),
|
|
||||||
"R_X86_64_32", sdll, 0);
|
|
||||||
}
|
|
||||||
tstr thunk(name_for_proc(dll, proc, thunk_id, tsep));
|
|
||||||
addSection(thunk, zeros, thunk_size, 0);
|
|
||||||
addSymbol(thunk, thunk, 0);
|
|
||||||
if (tsep == thunk_separator_first)
|
|
||||||
{
|
|
||||||
addRelocation(desc_name, offsetof(import_desc, iat),
|
|
||||||
"R_X86_64_32", thunk, 0);
|
|
||||||
|
|
||||||
tstr last_thunk(name_for_proc(dll, "X", thunk_id, thunk_separator_last));
|
|
||||||
addSection(last_thunk, zeros, thunk_size, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *reltype = thunk_size == 4 ? "R_X86_64_32" : "R_X86_64_64";
|
|
||||||
if (ordinal != 0u)
|
|
||||||
{
|
|
||||||
addRelocation(thunk, 0, reltype, "*UND*",
|
|
||||||
ordinal | (1ull << (thunk_size * 8 - 1)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tstr proc_name(name_for_proc(dll, proc, proc_name_id, procname_separator));
|
|
||||||
addSection(proc_name, zeros, 2, 1); // 2 bytes of word aligned "hint"
|
|
||||||
addSymbol(proc_name, proc_name, 0);
|
|
||||||
addRelocation(thunk, 0, reltype, proc_name, 0);
|
|
||||||
|
|
||||||
strcat(proc_name, "X");
|
|
||||||
addSection(proc_name, proc, strlen(proc), 0); // the name of the symbol
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int __acc_cdecl_qsort compare(const void *p1, const void *p2)
|
|
||||||
{
|
|
||||||
const Section *s1 = * (const Section * const *) p1;
|
|
||||||
const Section *s2 = * (const Section * const *) p2;
|
|
||||||
return strcmp(s1->name, s2->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void alignCode(unsigned len) { alignWithByte(len, 0); }
|
|
||||||
|
|
||||||
const Section *getThunk(const char *dll, const char *proc, char tsep) const
|
|
||||||
{
|
|
||||||
assert(dll);
|
|
||||||
assert(proc);
|
|
||||||
tstr thunk(name_for_proc(dll, proc, thunk_id, tsep));
|
|
||||||
return findSection(thunk, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ImportLinker(unsigned thunk_size_) : thunk_size(thunk_size_)
|
|
||||||
{
|
|
||||||
assert(thunk_size == 4 || thunk_size == 8);
|
|
||||||
addSection("*UND*", NULL, 0, 0);
|
|
||||||
addSymbol("*UND*", "*UND*", 0);
|
|
||||||
addSection("*ZSTART", NULL, 0, 0);
|
|
||||||
addSymbol("*ZSTART", "*ZSTART", 0);
|
|
||||||
Section *s = addSection("Dzero", zeros, sizeof(import_desc), 0);
|
|
||||||
assert(s->name[0] == descriptor_id);
|
|
||||||
|
|
||||||
// one trailing 00 byte after the last proc name
|
|
||||||
addSection("Zzero", zeros, 1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename C>
|
|
||||||
void add(const C *dll, unsigned ordinal)
|
|
||||||
{
|
|
||||||
assert(ordinal > 0 && ordinal < 0x10000);
|
|
||||||
char ord[20];
|
|
||||||
snprintf(ord, sizeof(ord), "%c%05u", ordinal_id, ordinal);
|
|
||||||
add((const char*) dll, ord, ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename C1, typename C2>
|
|
||||||
void add(const C1 *dll, const C2 *proc)
|
|
||||||
{
|
|
||||||
assert(proc);
|
|
||||||
add((const char*) dll, (const char*) proc, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned build()
|
|
||||||
{
|
|
||||||
assert(output == NULL);
|
|
||||||
int osize = 4 + 2 * nsections; // upper limit for alignments
|
|
||||||
for (unsigned ic = 0; ic < nsections; ic++)
|
|
||||||
osize += sections[ic]->size;
|
|
||||||
output = new upx_byte[osize];
|
|
||||||
|
|
||||||
// sort the sections by name before adding them all
|
|
||||||
qsort(sections, nsections, sizeof (Section*), ImportLinker::compare);
|
|
||||||
|
|
||||||
for (unsigned ic = 0; ic < nsections; ic++)
|
|
||||||
addLoader(sections[ic]->name);
|
|
||||||
addLoader("+40D");
|
|
||||||
assert(outputlen <= osize);
|
|
||||||
|
|
||||||
//OutputFile::dump("il0.imp", output, outputlen);
|
|
||||||
return outputlen;
|
|
||||||
}
|
|
||||||
|
|
||||||
void relocate(unsigned myimport)
|
|
||||||
{
|
|
||||||
assert(nsections > 0);
|
|
||||||
assert(output);
|
|
||||||
defineSymbol("*ZSTART", /*0xffffffffff1000ull + 0 * */ myimport);
|
|
||||||
ElfLinkerAMD64::relocate();
|
|
||||||
//OutputFile::dump("il1.imp", output, outputlen);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename C1, typename C2>
|
|
||||||
upx_uint64_t getAddress(const C1 *dll, const C2 *proc) const
|
|
||||||
{
|
|
||||||
const Section *s = getThunk((const char*) dll, (const char*) proc,
|
|
||||||
thunk_separator_first);
|
|
||||||
if (s == NULL && (s = getThunk((const char*) dll,(const char*) proc,
|
|
||||||
thunk_separator)) == NULL)
|
|
||||||
throwInternalError("entry not found");
|
|
||||||
return s->offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename C1>
|
|
||||||
upx_uint64_t getAddress(const C1 *dll, unsigned ordinal) const
|
|
||||||
{
|
|
||||||
assert(ordinal > 0 && ordinal < 0x10000);
|
|
||||||
char ord[20];
|
|
||||||
snprintf(ord, sizeof(ord), "%c%05u", ordinal_id, ordinal);
|
|
||||||
|
|
||||||
const Section *s = getThunk((const char*) dll, ord, thunk_separator_first);
|
|
||||||
if (s == NULL
|
|
||||||
&& (s = getThunk((const char*) dll, ord, thunk_separator)) == NULL)
|
|
||||||
throwInternalError("entry not found");
|
|
||||||
return s->offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename C1>
|
|
||||||
upx_uint64_t getAddress(const C1 *dll) const
|
|
||||||
{
|
|
||||||
tstr sdll(name_for_dll((const char*) dll, dll_name_id));
|
|
||||||
return findSection(sdll, true)->offset;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
char ImportLinker::zeros[sizeof(import_desc)];
|
|
||||||
|
|
||||||
ImportLinker ilinker(8);
|
|
||||||
|
|
||||||
void PackW64Pep::processImports(unsigned myimport, unsigned) // pass 2
|
|
||||||
{
|
|
||||||
COMPILE_TIME_ASSERT(sizeof(import_desc) == 20);
|
|
||||||
|
|
||||||
ilinker.relocate(myimport);
|
|
||||||
int len;
|
|
||||||
oimpdlls = ilinker.getLoader(&len);
|
|
||||||
assert(len == (int) soimpdlls);
|
|
||||||
//OutputFile::dump("x1.imp", oimpdlls, soimpdlls);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned PackW64Pep::processImports() // pass 1
|
|
||||||
{
|
|
||||||
static const unsigned char kernel32dll[] = "KERNEL32.DLL";
|
|
||||||
|
|
||||||
unsigned dllnum = 0;
|
|
||||||
import_desc *im = (import_desc*) (ibuf + IDADDR(PEDIR_IMPORT));
|
|
||||||
import_desc * const im_save = im;
|
|
||||||
if (IDADDR(PEDIR_IMPORT))
|
|
||||||
{
|
|
||||||
while (im->dllname)
|
|
||||||
dllnum++, im++;
|
|
||||||
im = im_save;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct udll
|
|
||||||
{
|
|
||||||
const upx_byte *name;
|
|
||||||
const upx_byte *shname;
|
|
||||||
unsigned ordinal;
|
|
||||||
unsigned iat;
|
|
||||||
LE64 *lookupt;
|
|
||||||
unsigned original_position;
|
|
||||||
bool isk32;
|
|
||||||
|
|
||||||
static int __acc_cdecl_qsort compare(const void *p1, const void *p2)
|
|
||||||
{
|
|
||||||
const udll *u1 = * (const udll * const *) p1;
|
|
||||||
const udll *u2 = * (const udll * const *) p2;
|
|
||||||
if (u1->isk32) return -1;
|
|
||||||
if (u2->isk32) return 1;
|
|
||||||
if (!*u1->lookupt) return 1;
|
|
||||||
if (!*u2->lookupt) return -1;
|
|
||||||
int rc = strcasecmp(u1->name,u2->name);
|
|
||||||
if (rc) return rc;
|
|
||||||
if (u1->ordinal) return -1;
|
|
||||||
if (u2->ordinal) return 1;
|
|
||||||
if (!u1->shname) return 1;
|
|
||||||
if (!u2->shname) return -1;
|
|
||||||
return strlen(u1->shname) - strlen(u2->shname);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// +1 for dllnum=0
|
|
||||||
Array(struct udll, dlls, dllnum+1);
|
|
||||||
Array(struct udll *, idlls, dllnum+1);
|
|
||||||
|
|
||||||
soimport = 1024; // safety
|
|
||||||
|
|
||||||
unsigned ic;
|
|
||||||
for (ic = 0; dllnum && im->dllname; ic++, im++)
|
|
||||||
{
|
|
||||||
idlls[ic] = dlls + ic;
|
|
||||||
dlls[ic].name = ibuf + im->dllname;
|
|
||||||
dlls[ic].shname = NULL;
|
|
||||||
dlls[ic].ordinal = 0;
|
|
||||||
dlls[ic].iat = im->iat;
|
|
||||||
dlls[ic].lookupt = (LE64*) (ibuf + (im->oft ? im->oft : im->iat));
|
|
||||||
dlls[ic].original_position = ic;
|
|
||||||
dlls[ic].isk32 = strcasecmp(kernel32dll,dlls[ic].name) == 0;
|
|
||||||
|
|
||||||
soimport += strlen(dlls[ic].name) + 1 + 4;
|
|
||||||
|
|
||||||
// FIXME use IPTR_I as in p32pe.cpp
|
|
||||||
for (LE64 *tarr = dlls[ic].lookupt; *tarr; tarr++)
|
|
||||||
{
|
|
||||||
if (*tarr & (1ULL << 63))
|
|
||||||
{
|
|
||||||
importbyordinal = true;
|
|
||||||
soimport += 2; // ordinal num: 2 bytes
|
|
||||||
dlls[ic].ordinal = *tarr & 0xffff;
|
|
||||||
}
|
|
||||||
else //it's an import by name
|
|
||||||
{
|
|
||||||
unsigned len = strlen(ibuf + *tarr + 2);
|
|
||||||
soimport += len + 1;
|
|
||||||
if (dlls[ic].shname == NULL || len < strlen (dlls[ic].shname))
|
|
||||||
dlls[ic].shname = ibuf + *tarr + 2;
|
|
||||||
}
|
|
||||||
soimport++; // separator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
oimport = new upx_byte[soimport];
|
|
||||||
memset(oimport,0,soimport);
|
|
||||||
|
|
||||||
qsort(idlls,dllnum,sizeof (udll*),udll::compare);
|
|
||||||
|
|
||||||
info("Processing imports: %d DLLs", dllnum);
|
|
||||||
|
|
||||||
// create the new import table
|
|
||||||
ilinker.add(kernel32dll, "LoadLibraryA");
|
|
||||||
ilinker.add(kernel32dll, "GetProcAddress");
|
|
||||||
if (!isdll)
|
|
||||||
ilinker.add(kernel32dll, "ExitProcess");
|
|
||||||
ilinker.add(kernel32dll, "VirtualProtect");
|
|
||||||
|
|
||||||
for (ic = 0; ic < dllnum; ic++)
|
|
||||||
{
|
|
||||||
if (idlls[ic]->isk32)
|
|
||||||
{
|
|
||||||
// for kernel32.dll we need to put all the imported
|
|
||||||
// ordinals into the output import table, as on
|
|
||||||
// some versions of windows GetProcAddress does not resolve them
|
|
||||||
if (idlls[ic]->ordinal)
|
|
||||||
for (LE64 *tarr = idlls[ic]->lookupt; *tarr; tarr++)
|
|
||||||
if (*tarr & (1ULL << 63))
|
|
||||||
{
|
|
||||||
ilinker.add(kernel32dll, *tarr & 0xffff);
|
|
||||||
kernel32ordinal = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (idlls[ic]->ordinal)
|
|
||||||
ilinker.add(idlls[ic]->name, idlls[ic]->ordinal);
|
|
||||||
else if (idlls[ic]->shname)
|
|
||||||
ilinker.add(idlls[ic]->name, idlls[ic]->shname);
|
|
||||||
else
|
|
||||||
throwInternalError("should not happen");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
soimpdlls = ilinker.build();
|
|
||||||
|
|
||||||
Interval names(ibuf),iats(ibuf),lookups(ibuf);
|
|
||||||
|
|
||||||
// create the preprocessed data
|
|
||||||
upx_byte *ppi = oimport; // preprocessed imports
|
|
||||||
for (ic = 0; ic < dllnum; ic++)
|
|
||||||
{
|
|
||||||
LE64 *tarr = idlls[ic]->lookupt;
|
|
||||||
#if 0 && ENABLE_THIS_AND_UNCOMPRESSION_WILL_BREAK // FIXME
|
|
||||||
if (!*tarr) // no imports from this dll
|
|
||||||
continue;
|
|
||||||
#endif
|
|
||||||
set_le32(ppi, ilinker.getAddress(idlls[ic]->name));
|
|
||||||
set_le32(ppi+4,idlls[ic]->iat - rvamin);
|
|
||||||
ppi += 8;
|
|
||||||
for (; *tarr; tarr++)
|
|
||||||
if (*tarr & (1ULL << 63))
|
|
||||||
{
|
|
||||||
unsigned ord = *tarr & 0xffff;
|
|
||||||
if (idlls[ic]->isk32)
|
|
||||||
{
|
|
||||||
*ppi++ = 0xfe; // signed + odd parity
|
|
||||||
set_le32(ppi, ilinker.getAddress(idlls[ic]->name, ord));
|
|
||||||
ppi += 4;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*ppi++ = 0xff;
|
|
||||||
set_le16(ppi, ord);
|
|
||||||
ppi += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*ppi++ = 1;
|
|
||||||
unsigned len = strlen(ibuf + *tarr + 2) + 1;
|
|
||||||
memcpy(ppi,ibuf + *tarr + 2,len);
|
|
||||||
ppi += len;
|
|
||||||
names.add(*tarr,len + 2 + 1);
|
|
||||||
}
|
|
||||||
ppi++;
|
|
||||||
|
|
||||||
unsigned esize = ptr_diff((char *)tarr, (char *)idlls[ic]->lookupt);
|
|
||||||
lookups.add(idlls[ic]->lookupt,esize);
|
|
||||||
if (ptr_diff(ibuf + idlls[ic]->iat, (char *)idlls[ic]->lookupt))
|
|
||||||
{
|
|
||||||
memcpy(ibuf + idlls[ic]->iat, idlls[ic]->lookupt, esize);
|
|
||||||
iats.add(idlls[ic]->iat,esize);
|
|
||||||
}
|
|
||||||
names.add(idlls[ic]->name,strlen(idlls[ic]->name) + 1 + 1);
|
|
||||||
}
|
|
||||||
ppi += 4;
|
|
||||||
assert(ppi < oimport+soimport);
|
|
||||||
soimport = ptr_diff(ppi,oimport);
|
|
||||||
|
|
||||||
if (soimport == 4)
|
|
||||||
soimport = 0;
|
|
||||||
|
|
||||||
OutputFile::dump("x0.imp", oimport, soimport);
|
|
||||||
|
|
||||||
unsigned ilen = 0;
|
|
||||||
names.flatten();
|
|
||||||
if (names.ivnum > 1)
|
|
||||||
{
|
|
||||||
// The area occupied by the dll and imported names is not continuous
|
|
||||||
// so to still support uncompression, I can't zero the iat area.
|
|
||||||
// This decreases compression ratio, so FIXME somehow.
|
|
||||||
infoWarning("can't remove unneeded imports");
|
|
||||||
ilen += sizeof(import_desc) * dllnum;
|
|
||||||
#if defined(DEBUG)
|
|
||||||
if (opt->verbose > 3)
|
|
||||||
names.dump();
|
|
||||||
#endif
|
|
||||||
// do some work for the unpacker
|
|
||||||
im = im_save;
|
|
||||||
for (ic = 0; ic < dllnum; ic++, im++)
|
|
||||||
{
|
|
||||||
memset(im,FILLVAL,sizeof(*im));
|
|
||||||
im->dllname = ptr_diff(dlls[idlls[ic]->original_position].name,ibuf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
iats.add(im_save,sizeof(import_desc) * dllnum);
|
|
||||||
// zero unneeded data
|
|
||||||
iats.clear();
|
|
||||||
lookups.clear();
|
|
||||||
}
|
|
||||||
names.clear();
|
|
||||||
|
|
||||||
iats.add(&names);
|
|
||||||
iats.add(&lookups);
|
|
||||||
iats.flatten();
|
|
||||||
for (ic = 0; ic < iats.ivnum; ic++)
|
|
||||||
ilen += iats.ivarr[ic].len;
|
|
||||||
|
|
||||||
info("Imports: original size: %u bytes, preprocessed size: %u bytes",ilen,soimport);
|
|
||||||
return names.ivnum == 1 ? names.ivarr[0].start : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*************************************************************************
|
|
||||||
// TLS handling
|
|
||||||
**************************************************************************/
|
|
||||||
|
|
||||||
// thanks for theowl for providing me some docs, so that now I understand
|
|
||||||
// what I'm doing here :)
|
|
||||||
|
|
||||||
// 1999-10-17: this was tricky to find:
|
|
||||||
// when the fixup records and the tls area are on the same page, then
|
|
||||||
// the tls area is not relocated, because the relocation is done by
|
|
||||||
// the virtual memory manager only for pages which are not yet loaded.
|
|
||||||
// of course it was impossible to debug this ;-)
|
|
||||||
|
|
||||||
#define TLS_CB_ALIGNMENT 8u // alignment of tls callbacks
|
|
||||||
|
|
||||||
__packed_struct(tls)
|
|
||||||
LE64 datastart; // VA tls init data start
|
|
||||||
LE64 dataend; // VA tls init data end
|
|
||||||
LE64 tlsindex; // VA tls index
|
|
||||||
LE64 callbacks; // VA tls callbacks
|
|
||||||
char _[8]; // zero init, characteristics
|
|
||||||
__packed_struct_end()
|
|
||||||
|
|
||||||
void PackW64Pep::processTls(Interval *iv) // pass 1
|
|
||||||
{
|
|
||||||
COMPILE_TIME_ASSERT(sizeof(tls) == 40) //size of TLS structure is 40 byte now
|
|
||||||
COMPILE_TIME_ASSERT_ALIGNED1(tls)
|
|
||||||
|
|
||||||
if ((sotls = ALIGN_UP(IDSIZE(PEDIR_TLS),4)) == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const tls * const tlsp = (const tls*) (ibuf + IDADDR(PEDIR_TLS));
|
|
||||||
// note: TLS callbacks are not implemented in Windows 95/98/ME
|
|
||||||
if (tlsp->callbacks)
|
|
||||||
{
|
|
||||||
if (tlsp->callbacks < ih.imagebase)
|
|
||||||
throwCantPack("invalid TLS callback");
|
|
||||||
else if (tlsp->callbacks - ih.imagebase + 4 >= ih.imagesize)
|
|
||||||
throwCantPack("invalid TLS callback");
|
|
||||||
upx_uint64_t v = get_le64(ibuf + (tlsp->callbacks - ih.imagebase));
|
|
||||||
if (v != 0)
|
|
||||||
{
|
|
||||||
//count number of callbacks, just for information string - Stefan Widmann
|
|
||||||
unsigned num_callbacks = 0;
|
|
||||||
unsigned callback_offset = 0;
|
|
||||||
while(get_le64(ibuf + (tlsp->callbacks - ih.imagebase) + callback_offset))
|
|
||||||
{
|
|
||||||
//increment number of callbacks
|
|
||||||
num_callbacks++;
|
|
||||||
//increment pointer by 8
|
|
||||||
callback_offset += 8;
|
|
||||||
}
|
|
||||||
info("TLS: %u callback(s) found, adding TLS callback handler", num_callbacks);
|
|
||||||
//set flag to include necessary sections in loader
|
|
||||||
use_tls_callbacks = true;
|
|
||||||
//define linker symbols
|
|
||||||
tlscb_ptr = tlsp->callbacks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const unsigned tlsdatastart = tlsp->datastart - ih.imagebase;
|
|
||||||
const unsigned tlsdataend = tlsp->dataend - ih.imagebase;
|
|
||||||
|
|
||||||
// now some ugly stuff: find the relocation entries in the tls data area
|
|
||||||
unsigned pos,type;
|
|
||||||
Reloc rel(ibuf + IDADDR(PEDIR_RELOC),IDSIZE(PEDIR_RELOC));
|
|
||||||
while (rel.next(pos,type))
|
|
||||||
if (pos >= tlsdatastart && pos < tlsdataend)
|
|
||||||
iv->add(pos,type);
|
|
||||||
|
|
||||||
sotls = sizeof(tls) + tlsdataend - tlsdatastart;
|
|
||||||
// if TLS callbacks are used, we need two more QWORDS at the end of the TLS
|
|
||||||
// ... and those qwords should be correctly aligned
|
|
||||||
if (use_tls_callbacks)
|
|
||||||
sotls = ALIGN_UP(sotls, TLS_CB_ALIGNMENT) + 16;
|
|
||||||
|
|
||||||
// the PE loader wants this stuff uncompressed
|
|
||||||
otls = new upx_byte[sotls];
|
|
||||||
memset(otls,0,sotls);
|
|
||||||
memcpy(otls,ibuf + IDADDR(PEDIR_TLS), sizeof(tls));
|
|
||||||
// WARNING: this can acces data in BSS
|
|
||||||
memcpy(otls + sizeof(tls),ibuf + tlsdatastart,sotls - sizeof(tls));
|
|
||||||
tlsindex = tlsp->tlsindex - ih.imagebase;
|
|
||||||
info("TLS: %u bytes tls data and %u relocations added",sotls - (unsigned) sizeof(tls) - (use_tls_callbacks ? 16 : 0),iv->ivnum);
|
|
||||||
|
|
||||||
// makes sure tls index is zero after decompression
|
|
||||||
if (tlsindex && tlsindex < ih.imagesize)
|
|
||||||
set_le32(ibuf + tlsindex, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PackW64Pep::processTls(Reloc *rel,const Interval *iv,unsigned newaddr) // pass 2
|
|
||||||
{
|
|
||||||
if (sotls == 0)
|
|
||||||
return;
|
|
||||||
// add new relocation entries
|
|
||||||
unsigned ic;
|
|
||||||
for (ic = 0; ic < (use_tls_callbacks ? 32u : 24u); ic += 8)
|
|
||||||
rel->add(newaddr + ic,10);
|
|
||||||
|
|
||||||
tls * const tlsp = (tls*) otls;
|
|
||||||
// now the relocation entries in the tls data area
|
|
||||||
|
|
||||||
|
|
||||||
// FIXME check this code below!!!
|
|
||||||
for (ic = 0; ic < iv->ivnum; ic += 4)
|
|
||||||
{
|
|
||||||
void *p = otls + iv->ivarr[ic].start - (tlsp->datastart - ih.imagebase) + sizeof(tls);
|
|
||||||
upx_uint64_t kc = get_le64(p); //changed to LE64 - Stefan Widmann
|
|
||||||
if (kc < tlsp->dataend && kc >= tlsp->datastart)
|
|
||||||
{
|
|
||||||
kc += newaddr + sizeof(tls) - tlsp->datastart;
|
|
||||||
set_le64(p,kc + ih.imagebase); //changed to LE64 - Stefan Widmann
|
|
||||||
rel->add(kc,iv->ivarr[ic].len);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
rel->add(kc - ih.imagebase,iv->ivarr[ic].len);
|
|
||||||
}
|
|
||||||
|
|
||||||
const unsigned tls_data_size = tlsp->dataend - tlsp->datastart;
|
|
||||||
tlsp->datastart = newaddr + sizeof(tls) + ih.imagebase;
|
|
||||||
tlsp->dataend = tlsp->datastart + tls_data_size;
|
|
||||||
|
|
||||||
tlsp->callbacks = (use_tls_callbacks ? newaddr + sotls + ih.imagebase - 16 : 0);
|
|
||||||
|
|
||||||
if (use_tls_callbacks)
|
|
||||||
{
|
|
||||||
//set handler offset
|
|
||||||
set_le64(otls + sotls - 16, tls_handler_offset + ih.imagebase);
|
|
||||||
//add relocation for TLS handler offset
|
|
||||||
rel->add(newaddr + sotls - 16, 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*************************************************************************
|
|
||||||
// Load Configuration handling
|
|
||||||
**************************************************************************/
|
|
||||||
|
|
||||||
void PackW64Pep::processLoadConf(Interval *iv) // pass 1
|
|
||||||
{
|
|
||||||
if (IDSIZE(PEDIR_LOADCONF) == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const unsigned lcaddr = IDADDR(PEDIR_LOADCONF);
|
|
||||||
const upx_byte * const loadconf = ibuf + lcaddr;
|
|
||||||
soloadconf = get_le32(loadconf);
|
|
||||||
if (soloadconf == 0)
|
|
||||||
return;
|
|
||||||
if (soloadconf > 256)
|
|
||||||
throwCantPack("size of Load Configuration directory unexpected");
|
|
||||||
|
|
||||||
// if there were relocation entries referring to the load config table
|
|
||||||
// then we need them for the copy of the table too
|
|
||||||
unsigned pos,type;
|
|
||||||
Reloc rel(ibuf + IDADDR(PEDIR_RELOC), IDSIZE(PEDIR_RELOC));
|
|
||||||
while (rel.next(pos, type))
|
|
||||||
if (pos >= lcaddr && pos < lcaddr + soloadconf)
|
|
||||||
{
|
|
||||||
iv->add(pos - lcaddr, type);
|
|
||||||
// printf("loadconf reloc detected: %x\n", pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
oloadconf = new upx_byte[soloadconf];
|
|
||||||
memcpy(oloadconf, loadconf, soloadconf);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PackW64Pep::processLoadConf(Reloc *rel, const Interval *iv,
|
|
||||||
unsigned newaddr) // pass2
|
|
||||||
{
|
|
||||||
// now we have the address of the new load config table
|
|
||||||
// so we can create the new relocation entries
|
|
||||||
for (unsigned ic = 0; ic < iv->ivnum; ic++)
|
|
||||||
{
|
|
||||||
rel->add(iv->ivarr[ic].start + newaddr, iv->ivarr[ic].len);
|
|
||||||
//printf("loadconf reloc added: %x %d\n",
|
|
||||||
// iv->ivarr[ic].start + newaddr, iv->ivarr[ic].len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*************************************************************************
|
/*************************************************************************
|
||||||
// pack
|
// pack
|
||||||
|
@ -1288,17 +622,17 @@ void PackW64Pep::pack(OutputFile *fo)
|
||||||
0x2000 : 0x1000); // 2 pages or 1 page
|
0x2000 : 0x1000); // 2 pages or 1 page
|
||||||
linker->defineSymbol("vp_base", addr &~ 0xfff); // page mask
|
linker->defineSymbol("vp_base", addr &~ 0xfff); // page mask
|
||||||
linker->defineSymbol("VirtualProtect", myimport +
|
linker->defineSymbol("VirtualProtect", myimport +
|
||||||
ilinker.getAddress("kernel32.dll", "VirtualProtect"));
|
ilinkerGetAddress("kernel32.dll", "VirtualProtect"));
|
||||||
}
|
}
|
||||||
linker->defineSymbol("start_of_relocs", crelocs);
|
linker->defineSymbol("start_of_relocs", crelocs);
|
||||||
if (!isdll)
|
if (!isdll)
|
||||||
linker->defineSymbol("ExitProcess", myimport +
|
linker->defineSymbol("ExitProcess", myimport +
|
||||||
ilinker.getAddress("kernel32.dll", "ExitProcess"));
|
ilinkerGetAddress("kernel32.dll", "ExitProcess"));
|
||||||
linker->defineSymbol("GetProcAddress", myimport +
|
linker->defineSymbol("GetProcAddress", myimport +
|
||||||
ilinker.getAddress("kernel32.dll", "GetProcAddress"));
|
ilinkerGetAddress("kernel32.dll", "GetProcAddress"));
|
||||||
linker->defineSymbol("kernel32_ordinals", myimport);
|
linker->defineSymbol("kernel32_ordinals", myimport);
|
||||||
linker->defineSymbol("LoadLibraryA", myimport +
|
linker->defineSymbol("LoadLibraryA", myimport +
|
||||||
ilinker.getAddress("kernel32.dll", "LoadLibraryA"));
|
ilinkerGetAddress("kernel32.dll", "LoadLibraryA"));
|
||||||
linker->defineSymbol("start_of_imports", myimport);
|
linker->defineSymbol("start_of_imports", myimport);
|
||||||
linker->defineSymbol("compressed_imports", cimports);
|
linker->defineSymbol("compressed_imports", cimports);
|
||||||
|
|
||||||
|
@ -1384,7 +718,7 @@ void PackW64Pep::pack(OutputFile *fo)
|
||||||
ODSIZE(PEDIR_RESOURCE) = soresources;
|
ODSIZE(PEDIR_RESOURCE) = soresources;
|
||||||
ic += soresources;
|
ic += soresources;
|
||||||
|
|
||||||
processImports(ic, 0);
|
PeFile::processImports(ic, 0);
|
||||||
ODADDR(PEDIR_IMPORT) = ic;
|
ODADDR(PEDIR_IMPORT) = ic;
|
||||||
ODSIZE(PEDIR_IMPORT) = soimpdlls;
|
ODSIZE(PEDIR_IMPORT) = soimpdlls;
|
||||||
ic += soimpdlls;
|
ic += soimpdlls;
|
||||||
|
@ -1399,7 +733,7 @@ void PackW64Pep::pack(OutputFile *fo)
|
||||||
}
|
}
|
||||||
ic += soexport;
|
ic += soexport;
|
||||||
|
|
||||||
processRelocs(&rel);
|
PeFile::processRelocs(&rel);
|
||||||
ODADDR(PEDIR_RELOC) = soxrelocs ? ic : 0;
|
ODADDR(PEDIR_RELOC) = soxrelocs ? ic : 0;
|
||||||
ODSIZE(PEDIR_RELOC) = soxrelocs;
|
ODSIZE(PEDIR_RELOC) = soxrelocs;
|
||||||
ic += soxrelocs;
|
ic += soxrelocs;
|
||||||
|
@ -1524,195 +858,6 @@ void PackW64Pep::pack(OutputFile *fo)
|
||||||
throwNotCompressible();
|
throwNotCompressible();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************************************************************
|
|
||||||
// unpack
|
|
||||||
**************************************************************************/
|
|
||||||
|
|
||||||
int PackW64Pep::canUnpack()
|
|
||||||
{
|
|
||||||
if (!readFileHeader() || ih.cpu != 0x8664)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
unsigned objs = ih.objects;
|
|
||||||
isection = new pe_section_t[objs];
|
|
||||||
fi->seek(pe_offset+sizeof(ih),SEEK_SET);
|
|
||||||
fi->readx(isection,sizeof(pe_section_t)*objs);
|
|
||||||
if (ih.objects < 3)
|
|
||||||
return -1;
|
|
||||||
bool is_packed = (ih.objects == 3 &&
|
|
||||||
(IDSIZE(15) || ih.entry > isection[1].vaddr));
|
|
||||||
bool found_ph = false;
|
|
||||||
if (memcmp(isection[0].name,"UPX",3) == 0)
|
|
||||||
{
|
|
||||||
// current version
|
|
||||||
fi->seek(isection[1].rawdataptr - 64, SEEK_SET);
|
|
||||||
found_ph = readPackHeader(1024);
|
|
||||||
if (!found_ph)
|
|
||||||
{
|
|
||||||
// old versions
|
|
||||||
fi->seek(isection[2].rawdataptr, SEEK_SET);
|
|
||||||
found_ph = readPackHeader(1024);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (is_packed && found_ph)
|
|
||||||
return true;
|
|
||||||
if (!is_packed && !found_ph)
|
|
||||||
return -1;
|
|
||||||
if (is_packed && ih.entry < isection[2].vaddr)
|
|
||||||
{
|
|
||||||
unsigned char buf[256];
|
|
||||||
bool x = false;
|
|
||||||
|
|
||||||
memset(buf, 0, sizeof(buf));
|
|
||||||
try {
|
|
||||||
fi->seek(ih.entry - isection[1].vaddr + isection[1].rawdataptr, SEEK_SET);
|
|
||||||
fi->read(buf, sizeof(buf));
|
|
||||||
|
|
||||||
static const unsigned char magic[] = "\x8b\x1e\x83\xee\xfc\x11\xdb";
|
|
||||||
// mov ebx, [esi]; sub esi, -4; adc ebx,ebx
|
|
||||||
|
|
||||||
int offset = find(buf, sizeof(buf), magic, 7);
|
|
||||||
if (offset >= 0 && find(buf + offset + 1, sizeof(buf) - offset - 1, magic, 7) >= 0)
|
|
||||||
x = true;
|
|
||||||
} catch (...) {
|
|
||||||
//x = true;
|
|
||||||
}
|
|
||||||
if (x)
|
|
||||||
throwCantUnpack("file is modified/hacked/protected; take care!!!");
|
|
||||||
else
|
|
||||||
throwCantUnpack("file is possibly modified/hacked/protected; take care!");
|
|
||||||
return false; // not reached
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: what should we say here ?
|
|
||||||
//throwCantUnpack("file is possibly modified/hacked/protected; take care!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PackW64Pep::rebuildImports(upx_byte *& extrainfo)
|
|
||||||
{
|
|
||||||
if (ODADDR(PEDIR_IMPORT) == 0
|
|
||||||
|| ODSIZE(PEDIR_IMPORT) <= sizeof(import_desc))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// const upx_byte * const idata = obuf + get_le32(extrainfo);
|
|
||||||
OPTR_C(const upx_byte, idata, obuf + get_le32(extrainfo));
|
|
||||||
const unsigned inamespos = get_le32(extrainfo + 4);
|
|
||||||
extrainfo += 8;
|
|
||||||
|
|
||||||
unsigned sdllnames = 0;
|
|
||||||
|
|
||||||
// const upx_byte *import = ibuf + IDADDR(PEDIR_IMPORT) - isection[2].vaddr;
|
|
||||||
// const upx_byte *p;
|
|
||||||
IPTR_I(const upx_byte, import, ibuf + IDADDR(PEDIR_IMPORT) - isection[2].vaddr);
|
|
||||||
OPTR(const upx_byte, p);
|
|
||||||
|
|
||||||
for (p = idata; get_le32(p) != 0; ++p)
|
|
||||||
{
|
|
||||||
const upx_byte *dname = get_le32(p) + import;
|
|
||||||
ICHECK(dname, 1);
|
|
||||||
const unsigned dlen = strlen(dname);
|
|
||||||
ICHECK(dname, dlen + 1);
|
|
||||||
|
|
||||||
sdllnames += dlen + 1;
|
|
||||||
for (p += 8; *p;)
|
|
||||||
if (*p == 1)
|
|
||||||
p += strlen(++p) + 1;
|
|
||||||
else if (*p == 0xff)
|
|
||||||
p += 3; // ordinal
|
|
||||||
else
|
|
||||||
p += 5;
|
|
||||||
}
|
|
||||||
sdllnames = ALIGN_UP(sdllnames, 2u);
|
|
||||||
|
|
||||||
upx_byte * const Obuf = obuf - rvamin;
|
|
||||||
import_desc * const im0 = (import_desc*) (Obuf + ODADDR(PEDIR_IMPORT));
|
|
||||||
import_desc *im = im0;
|
|
||||||
upx_byte *dllnames = Obuf + inamespos;
|
|
||||||
upx_byte *importednames = dllnames + sdllnames;
|
|
||||||
upx_byte * const importednames_start = importednames;
|
|
||||||
|
|
||||||
for (p = idata; get_le32(p) != 0; ++p)
|
|
||||||
{
|
|
||||||
// restore the name of the dll
|
|
||||||
const upx_byte *dname = get_le32(p) + import;
|
|
||||||
ICHECK(dname, 1);
|
|
||||||
const unsigned dlen = strlen(dname);
|
|
||||||
ICHECK(dname, dlen + 1);
|
|
||||||
|
|
||||||
const unsigned iatoffs = get_le32(p + 4) + rvamin;
|
|
||||||
if (inamespos)
|
|
||||||
{
|
|
||||||
// now I rebuild the dll names
|
|
||||||
OCHECK(dllnames, dlen + 1);
|
|
||||||
strcpy(dllnames, dname);
|
|
||||||
im->dllname = ptr_diff(dllnames,Obuf);
|
|
||||||
//;;;printf("\ndll: %s:",dllnames);
|
|
||||||
dllnames += dlen + 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
OCHECK(Obuf + im->dllname, dlen + 1);
|
|
||||||
strcpy(Obuf + im->dllname, dname);
|
|
||||||
}
|
|
||||||
im->iat = iatoffs;
|
|
||||||
|
|
||||||
OPTR_I(LE64, newiat, (LE64 *) (Obuf + iatoffs));
|
|
||||||
|
|
||||||
// restore the imported names+ordinals
|
|
||||||
for (p += 8; *p; ++newiat)
|
|
||||||
if (*p == 1)
|
|
||||||
{
|
|
||||||
const unsigned ilen = strlen(++p) + 1;
|
|
||||||
if (inamespos)
|
|
||||||
{
|
|
||||||
if (ptr_diff(importednames, importednames_start) & 1)
|
|
||||||
importednames -= 1;
|
|
||||||
omemcpy(importednames + 2, p, ilen);
|
|
||||||
//;;;printf(" %s",importednames+2);
|
|
||||||
*newiat = ptr_diff(importednames, Obuf);
|
|
||||||
importednames += 2 + ilen;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
OCHECK(Obuf + (*newiat + 2), ilen + 1);
|
|
||||||
strcpy(Obuf + (*newiat + 2), p);
|
|
||||||
}
|
|
||||||
p += ilen;
|
|
||||||
}
|
|
||||||
else if (*p == 0xff)
|
|
||||||
{
|
|
||||||
*newiat = get_le16(p + 1) + (1ULL << 63);
|
|
||||||
//;;;printf(" %llx",(unsigned long long)*newiat);
|
|
||||||
p += 3;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*newiat = get_le64(get_le32(p + 1) + import);
|
|
||||||
assert(*newiat & (1ULL << 63));
|
|
||||||
p += 5;
|
|
||||||
}
|
|
||||||
*newiat = 0;
|
|
||||||
im++;
|
|
||||||
}
|
|
||||||
//memset(idata,0,p - idata);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
extra info added to help uncompression:
|
|
||||||
|
|
||||||
<ih sizeof(pe_head)>
|
|
||||||
<pe_section_t objs*sizeof(pe_section_t)>
|
|
||||||
<start of compressed imports 4> - optional \
|
|
||||||
<start of the names from uncompressed imports> - opt /
|
|
||||||
<start of compressed relocs 4> - optional \
|
|
||||||
<relocation type indicator 1> - optional /
|
|
||||||
<icondir_count 2> - optional
|
|
||||||
<offset of extra info 4>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
vi:ts=4:et
|
vi:ts=4:et
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -33,9 +33,9 @@
|
||||||
// w64/pep
|
// w64/pep
|
||||||
**************************************************************************/
|
**************************************************************************/
|
||||||
|
|
||||||
class PackW64Pep : public PepFile
|
class PackW64Pep : public PeFile64
|
||||||
{
|
{
|
||||||
typedef PepFile super;
|
typedef PeFile64 super;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PackW64Pep(InputFile *f);
|
PackW64Pep(InputFile *f);
|
||||||
|
@ -49,7 +49,6 @@ public:
|
||||||
virtual void pack(OutputFile *fo);
|
virtual void pack(OutputFile *fo);
|
||||||
|
|
||||||
virtual bool canPack();
|
virtual bool canPack();
|
||||||
virtual int canUnpack();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual int readFileHeader();
|
virtual int readFileHeader();
|
||||||
|
@ -57,25 +56,9 @@ protected:
|
||||||
virtual void buildLoader(const Filter *ft);
|
virtual void buildLoader(const Filter *ft);
|
||||||
virtual Linker* newLinker() const;
|
virtual Linker* newLinker() const;
|
||||||
|
|
||||||
virtual unsigned processImports();
|
|
||||||
virtual void processImports(unsigned, unsigned);
|
|
||||||
virtual void rebuildImports(upx_byte *&);
|
|
||||||
|
|
||||||
virtual void processTls(Interval *); //NEW: TLS callback handling - Stefan Widmann
|
|
||||||
void processTls(Reloc *, const Interval *, unsigned); //NEW: TLS callback handling - Stefan Widmann
|
|
||||||
|
|
||||||
void processLoadConf(Reloc *, const Interval *, unsigned);
|
|
||||||
void processLoadConf(Interval *);
|
|
||||||
upx_byte *oloadconf;
|
|
||||||
unsigned soloadconf;
|
|
||||||
|
|
||||||
unsigned tlscb_ptr; //NEW: TLS callback handling - Stefan Widmann
|
|
||||||
unsigned tls_handler_offset;
|
|
||||||
|
|
||||||
bool isrtm;
|
bool isrtm;
|
||||||
bool use_dep_hack;
|
bool use_dep_hack;
|
||||||
bool use_clear_dirty_stack;
|
bool use_clear_dirty_stack;
|
||||||
bool use_tls_callbacks; //NEW: TLS callback handling - Stefan Widmann
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
211
src/pefile.cpp
211
src/pefile.cpp
|
@ -142,21 +142,6 @@ PeFile::PeFile(InputFile *f) : super(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PeFile::~PeFile()
|
|
||||||
{
|
|
||||||
delete [] isection;
|
|
||||||
delete [] orelocs;
|
|
||||||
delete [] oimport;
|
|
||||||
oimpdlls = NULL;
|
|
||||||
delete [] oexport;
|
|
||||||
delete [] otls;
|
|
||||||
delete [] oresources;
|
|
||||||
delete [] oxrelocs;
|
|
||||||
delete [] oloadconf;
|
|
||||||
//delete res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool PeFile::testUnpackVersion(int version) const
|
bool PeFile::testUnpackVersion(int version) const
|
||||||
{
|
{
|
||||||
if (version != ph_version && ph_version != -1)
|
if (version != ph_version && ph_version != -1)
|
||||||
|
@ -478,6 +463,107 @@ void PeFile32::processRelocs() // pass1
|
||||||
info("Relocations: original size: %u bytes, preprocessed size: %u bytes",(unsigned) IDSIZE(PEDIR_RELOC),sorelocs);
|
info("Relocations: original size: %u bytes, preprocessed size: %u bytes",(unsigned) IDSIZE(PEDIR_RELOC),sorelocs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PeFile64::processRelocs() // pass1
|
||||||
|
{
|
||||||
|
big_relocs = 0;
|
||||||
|
|
||||||
|
Reloc rel(ibuf + IDADDR(PEDIR_RELOC),IDSIZE(PEDIR_RELOC));
|
||||||
|
const unsigned *counts = rel.getcounts();
|
||||||
|
unsigned rnum = 0;
|
||||||
|
|
||||||
|
unsigned ic;
|
||||||
|
for (ic = 1; ic < 16; ic++)
|
||||||
|
rnum += counts[ic];
|
||||||
|
|
||||||
|
if ((opt->win32_pe.strip_relocs && !isdll) || rnum == 0)
|
||||||
|
{
|
||||||
|
if (IDSIZE(PEDIR_RELOC))
|
||||||
|
ibuf.fill(IDADDR(PEDIR_RELOC), IDSIZE(PEDIR_RELOC), FILLVAL);
|
||||||
|
orelocs = new upx_byte [1];
|
||||||
|
sorelocs = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ic = 15; ic; ic--)
|
||||||
|
if (ic != 10 && counts[ic])
|
||||||
|
infoWarning("skipping unsupported relocation type %d (%d)",ic,counts[ic]);
|
||||||
|
|
||||||
|
LE32 *fix[16];
|
||||||
|
for (ic = 15; ic; ic--)
|
||||||
|
fix[ic] = new LE32 [counts[ic]];
|
||||||
|
|
||||||
|
unsigned xcounts[16];
|
||||||
|
memset(xcounts, 0, sizeof(xcounts));
|
||||||
|
|
||||||
|
// prepare sorting
|
||||||
|
unsigned pos,type;
|
||||||
|
while (rel.next(pos,type))
|
||||||
|
{
|
||||||
|
// FIXME add check for relocations which try to modify the
|
||||||
|
// PE header or other relocation records
|
||||||
|
|
||||||
|
if (pos >= ih.imagesize)
|
||||||
|
continue; // skip out-of-bounds record
|
||||||
|
if (type < 16)
|
||||||
|
fix[type][xcounts[type]++] = pos - rvamin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove duplicated records
|
||||||
|
for (ic = 1; ic <= 15; ic++)
|
||||||
|
{
|
||||||
|
qsort(fix[ic], xcounts[ic], 4, le32_compare);
|
||||||
|
unsigned prev = ~0;
|
||||||
|
unsigned jc = 0;
|
||||||
|
for (unsigned kc = 0; kc < xcounts[ic]; kc++)
|
||||||
|
if (fix[ic][kc] != prev)
|
||||||
|
prev = fix[ic][jc++] = fix[ic][kc];
|
||||||
|
|
||||||
|
//printf("xcounts[%u] %u->%u\n", ic, xcounts[ic], jc);
|
||||||
|
xcounts[ic] = jc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// preprocess "type 10" relocation records
|
||||||
|
for (ic = 0; ic < xcounts[10]; ic++)
|
||||||
|
{
|
||||||
|
pos = fix[10][ic] + rvamin;
|
||||||
|
set_le64(ibuf + pos, get_le64(ibuf + pos) - ih.imagebase - rvamin);
|
||||||
|
}
|
||||||
|
|
||||||
|
ibuf.fill(IDADDR(PEDIR_RELOC), IDSIZE(PEDIR_RELOC), FILLVAL);
|
||||||
|
orelocs = new upx_byte [rnum * 4 + 1024]; // 1024 - safety
|
||||||
|
sorelocs = ptr_diff(optimizeReloc64((upx_byte*) fix[10], xcounts[10],
|
||||||
|
orelocs, ibuf + rvamin,1, &big_relocs),
|
||||||
|
orelocs);
|
||||||
|
|
||||||
|
for (ic = 15; ic; ic--)
|
||||||
|
delete [] fix[ic];
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// Malware that hides behind UPX often has PE header info that is
|
||||||
|
// deliberately corrupt. Sometimes it is even tuned to cause us trouble!
|
||||||
|
// Use an extra check to avoid AccessViolation (SIGSEGV) when appending
|
||||||
|
// the relocs into one array.
|
||||||
|
if ((rnum * 4 + 1024) < (sorelocs + 4*(2 + xcounts[2] + xcounts[1])))
|
||||||
|
throwCantUnpack("Invalid relocs");
|
||||||
|
|
||||||
|
// append relocs type "LOW" then "HIGH"
|
||||||
|
for (ic = 2; ic ; ic--)
|
||||||
|
{
|
||||||
|
memcpy(orelocs + sorelocs,fix[ic],4 * xcounts[ic]);
|
||||||
|
sorelocs += 4 * xcounts[ic];
|
||||||
|
delete [] fix[ic];
|
||||||
|
|
||||||
|
set_le32(orelocs + sorelocs,0);
|
||||||
|
if (xcounts[ic])
|
||||||
|
{
|
||||||
|
sorelocs += 4;
|
||||||
|
big_relocs |= 2 * ic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
info("Relocations: original size: %u bytes, preprocessed size: %u bytes",(unsigned) IDSIZE(PEDIR_RELOC),sorelocs);
|
||||||
|
}
|
||||||
|
|
||||||
/*************************************************************************
|
/*************************************************************************
|
||||||
// import handling
|
// import handling
|
||||||
**************************************************************************/
|
**************************************************************************/
|
||||||
|
@ -850,6 +936,7 @@ unsigned PeFile::processImports(ord_mask_t ord_mask) // pass 1
|
||||||
|
|
||||||
info("Processing imports: %d DLLs", dllnum);
|
info("Processing imports: %d DLLs", dllnum);
|
||||||
|
|
||||||
|
ilinker = new ImportLinker(sizeof(LEXX));
|
||||||
// create the new import table
|
// create the new import table
|
||||||
addKernelImports();
|
addKernelImports();
|
||||||
|
|
||||||
|
@ -1152,6 +1239,23 @@ struct PeFile::tls_traits<LE32>
|
||||||
static const unsigned reloc_type = 3;
|
static const unsigned reloc_type = 3;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct PeFile::tls_traits<LE64>
|
||||||
|
{
|
||||||
|
__packed_struct(tls)
|
||||||
|
LE64 datastart; // VA tls init data start
|
||||||
|
LE64 dataend; // VA tls init data end
|
||||||
|
LE64 tlsindex; // VA tls index
|
||||||
|
LE64 callbacks; // VA tls callbacks
|
||||||
|
char _[8]; // zero init, characteristics
|
||||||
|
__packed_struct_end()
|
||||||
|
|
||||||
|
static const unsigned sotls = 40;
|
||||||
|
static const unsigned cb_size = 8;
|
||||||
|
typedef upx_uint64_t cb_value_t;
|
||||||
|
static const unsigned reloc_type = 10;
|
||||||
|
};
|
||||||
|
|
||||||
template <typename LEXX>
|
template <typename LEXX>
|
||||||
void PeFile::processTls1(Interval *iv,
|
void PeFile::processTls1(Interval *iv,
|
||||||
typename tls_traits<LEXX>::cb_value_t imagebase,
|
typename tls_traits<LEXX>::cb_value_t imagebase,
|
||||||
|
@ -2101,8 +2205,8 @@ void PeFile::rebuildImports(upx_byte *& extrainfo,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
OCHECK(Obuf + *newiat + 2, ilen + 1);
|
OCHECK(Obuf + (*newiat + 2), ilen + 1);
|
||||||
strcpy(Obuf + *newiat + 2, p);
|
strcpy(Obuf + (*newiat + 2), p);
|
||||||
}
|
}
|
||||||
p += ilen;
|
p += ilen;
|
||||||
}
|
}
|
||||||
|
@ -2307,6 +2411,26 @@ int PeFile::canUnpack0(unsigned max_sections, LE16 &ih_objects,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
upx_uint64_t PeFile::ilinkerGetAddress(const char *d, const char *n) const
|
||||||
|
{
|
||||||
|
return ilinker->getAddress(d, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
PeFile::~PeFile()
|
||||||
|
{
|
||||||
|
delete [] isection;
|
||||||
|
delete [] orelocs;
|
||||||
|
delete [] oimport;
|
||||||
|
oimpdlls = NULL;
|
||||||
|
delete [] oexport;
|
||||||
|
delete [] otls;
|
||||||
|
delete [] oresources;
|
||||||
|
delete [] oxrelocs;
|
||||||
|
delete [] oloadconf;
|
||||||
|
delete ilinker;
|
||||||
|
//delete res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*************************************************************************
|
/*************************************************************************
|
||||||
// PeFile32
|
// PeFile32
|
||||||
|
@ -2319,14 +2443,10 @@ PeFile32::PeFile32(InputFile *f) : super(f)
|
||||||
|
|
||||||
iddirs = ih.ddirs;
|
iddirs = ih.ddirs;
|
||||||
oddirs = oh.ddirs;
|
oddirs = oh.ddirs;
|
||||||
|
|
||||||
ilinker = new ImportLinker(4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PeFile32::~PeFile32()
|
PeFile32::~PeFile32()
|
||||||
{
|
{}
|
||||||
delete ilinker;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PeFile32::readPeHeader()
|
void PeFile32::readPeHeader()
|
||||||
{
|
{
|
||||||
|
@ -2361,6 +2481,53 @@ void PeFile32::processTls(Reloc *r, const Interval *iv, unsigned a)
|
||||||
super::processTls2<LE32>(r, iv, a, ih.imagebase);
|
super::processTls2<LE32>(r, iv, a, ih.imagebase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*************************************************************************
|
||||||
|
// PeFile64
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
|
PeFile64::PeFile64(InputFile *f) : super(f)
|
||||||
|
{
|
||||||
|
COMPILE_TIME_ASSERT(sizeof(pe_header_t) == 264)
|
||||||
|
COMPILE_TIME_ASSERT_ALIGNED1(pe_header_t)
|
||||||
|
|
||||||
|
iddirs = ih.ddirs;
|
||||||
|
oddirs = oh.ddirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
PeFile64::~PeFile64()
|
||||||
|
{}
|
||||||
|
|
||||||
|
void PeFile64::readPeHeader()
|
||||||
|
{
|
||||||
|
fi->readx(&ih,sizeof(ih));
|
||||||
|
isdll = ((ih.flags & DLL_FLAG) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PeFile64::unpack(OutputFile *fo)
|
||||||
|
{
|
||||||
|
super::unpack<pe_header_t, LE64>(fo, ih, oh, 1ULL << 63, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
int PeFile64::canUnpack()
|
||||||
|
{
|
||||||
|
return canUnpack0(3, ih.objects, ih.entry, sizeof(ih));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned PeFile64::processImports() // pass 1
|
||||||
|
{
|
||||||
|
return super::processImports<LE64>(1ULL << 63);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PeFile64::processTls(Interval *iv)
|
||||||
|
{
|
||||||
|
super::processTls1<LE64>(iv, ih.imagebase, ih.imagesize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PeFile64::processTls(Reloc *r, const Interval *iv, unsigned a)
|
||||||
|
{
|
||||||
|
super::processTls2<LE64>(r, iv, a, ih.imagebase);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
extra info added to help uncompression:
|
extra info added to help uncompression:
|
||||||
|
|
||||||
|
|
59
src/pefile.h
59
src/pefile.h
|
@ -80,6 +80,7 @@ protected:
|
||||||
ImportLinker *ilinker;
|
ImportLinker *ilinker;
|
||||||
void addKernelImport(const char *, const char *);
|
void addKernelImport(const char *, const char *);
|
||||||
virtual void addKernelImports();
|
virtual void addKernelImports();
|
||||||
|
upx_uint64_t ilinkerGetAddress(const char *, const char *) const;
|
||||||
|
|
||||||
virtual void processRelocs() = 0;
|
virtual void processRelocs() = 0;
|
||||||
void processRelocs(Reloc *);
|
void processRelocs(Reloc *);
|
||||||
|
@ -428,6 +429,64 @@ protected:
|
||||||
pe_header_t ih, oh;
|
pe_header_t ih, oh;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PeFile64 : public PeFile
|
||||||
|
{
|
||||||
|
typedef PeFile super;
|
||||||
|
protected:
|
||||||
|
PeFile64(InputFile *f);
|
||||||
|
virtual ~PeFile64();
|
||||||
|
virtual void unpack(OutputFile *fo);
|
||||||
|
virtual int canUnpack();
|
||||||
|
|
||||||
|
virtual void readPeHeader();
|
||||||
|
|
||||||
|
virtual unsigned processImports();
|
||||||
|
virtual void processRelocs();
|
||||||
|
virtual void processTls(Interval *);
|
||||||
|
void processTls(Reloc *, const Interval *, unsigned);
|
||||||
|
|
||||||
|
__packed_struct(pe_header_t)
|
||||||
|
// 0x0
|
||||||
|
char _[4]; // pemagic
|
||||||
|
LE16 cpu;
|
||||||
|
LE16 objects; // number of sections
|
||||||
|
char __[12]; // timestamp + reserved
|
||||||
|
LE16 opthdrsize;
|
||||||
|
LE16 flags; // characteristics
|
||||||
|
// optional header
|
||||||
|
LE16 coffmagic; // NEW: Stefan Widmann
|
||||||
|
char ___[2]; // linkerversion
|
||||||
|
LE32 codesize;
|
||||||
|
// 0x20
|
||||||
|
LE32 datasize;
|
||||||
|
LE32 bsssize;
|
||||||
|
LE32 entry; // still a 32 bit RVA
|
||||||
|
LE32 codebase;
|
||||||
|
// 0x30
|
||||||
|
//LE32 database; // field does not exist in PE+!
|
||||||
|
// nt specific fields
|
||||||
|
LE64 imagebase; // LE32 -> LE64 - Stefan Widmann standard is 0x0000000140000000
|
||||||
|
LE32 objectalign;
|
||||||
|
LE32 filealign; // should set to 0x200 ?
|
||||||
|
// 0x40
|
||||||
|
char ____[16]; // versions
|
||||||
|
// 0x50
|
||||||
|
LE32 imagesize;
|
||||||
|
LE32 headersize;
|
||||||
|
LE32 chksum; // should set to 0
|
||||||
|
LE16 subsystem;
|
||||||
|
LE16 dllflags;
|
||||||
|
// 0x60
|
||||||
|
char _____[36]; // stack + heap sizes + loader flag
|
||||||
|
// 0x84
|
||||||
|
LE32 ddirsentries; // usually 16
|
||||||
|
|
||||||
|
ddirs_t ddirs[16];
|
||||||
|
__packed_struct_end()
|
||||||
|
|
||||||
|
pe_header_t ih, oh;
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* already included */
|
#endif /* already included */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
changes in:
|
changes in:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if 0
|
||||||
#include "conf.h"
|
#include "conf.h"
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
#include "filter.h"
|
#include "filter.h"
|
||||||
|
@ -1731,7 +1731,7 @@ void PepFile::unpack(OutputFile *fo)
|
||||||
<offset of extra info 4>
|
<offset of extra info 4>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#endif
|
||||||
/*
|
/*
|
||||||
vi:ts=4:et
|
vi:ts=4:et
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
#ifndef __UPX_PEPFILE_H
|
#ifndef __UPX_PEPFILE_H
|
||||||
#define __UPX_PEPFILE_H 1
|
#define __UPX_PEPFILE_H 1
|
||||||
|
|
||||||
|
#if 0
|
||||||
/*************************************************************************
|
/*************************************************************************
|
||||||
// general/pe handling
|
// general/pe handling
|
||||||
**************************************************************************/
|
**************************************************************************/
|
||||||
|
@ -378,6 +378,7 @@ protected:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
#endif /* already included */
|
#endif /* already included */
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue