libredex/JarLoader.cpp (780 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <boost/iostreams/device/mapped_file.hpp>
#include <cstdint>
#include <iostream>
#include <utility>
#include <vector>
#include <zlib.h>
#include "Macros.h"
#if IS_WINDOWS
#include <Winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <netinet/in.h>
#endif
#include "Creators.h"
#include "DexClass.h"
#include "DuplicateClasses.h"
#include "JarLoader.h"
#include "Show.h"
#include "Trace.h"
#include "Util.h"
/******************
* Begin Class Loading code.
*/
namespace JarLoaderUtil {
uint32_t read32(uint8_t*& buffer) {
uint32_t rv;
memcpy(&rv, buffer, sizeof(uint32_t));
buffer += sizeof(uint32_t);
return htonl(rv);
}
uint32_t read16(uint8_t*& buffer) {
uint16_t rv;
memcpy(&rv, buffer, sizeof(uint16_t));
buffer += sizeof(uint16_t);
return htons(rv);
}
} // namespace JarLoaderUtil
using namespace JarLoaderUtil;
namespace {
static const uint32_t kClassMagic = 0xcafebabe;
struct cp_entry {
uint8_t tag;
union {
struct {
uint16_t s0;
uint16_t s1;
};
struct {
uint32_t i0;
uint32_t i1;
};
struct {
uint16_t len;
uint8_t* data;
};
};
};
struct cp_field_info {
uint16_t aflags;
uint16_t nameNdx;
uint16_t descNdx;
};
struct cp_method_info {
uint16_t aflags;
uint16_t nameNdx;
uint16_t descNdx;
};
} // namespace
/* clang-format off */
// Java Virtual Machine Specification Chapter 4, Section 4.4
#define CP_CONST_UTF8 (1)
#define CP_CONST_INT (3)
#define CP_CONST_FLOAT (4)
#define CP_CONST_LONG (5)
#define CP_CONST_DOUBLE (6)
#define CP_CONST_CLASS (7)
#define CP_CONST_STRING (8)
#define CP_CONST_FIELD (9)
#define CP_CONST_METHOD (10)
#define CP_CONST_INTERFACE (11)
#define CP_CONST_NAMEANDTYPE (12)
// Since Java 7
#define CP_CONST_METHHANDLE (15)
#define CP_CONST_METHTYPE (16)
#define CP_CONST_INVOKEDYN (18)
// Since Java 9
#define CP_CONST_MODULE (19)
#define CP_CONST_PACKAGE (20)
/* clang-format on */
static bool parse_cp_entry(uint8_t*& buffer, cp_entry& cpe) {
cpe.tag = *buffer++;
switch (cpe.tag) {
case CP_CONST_CLASS:
case CP_CONST_STRING:
case CP_CONST_METHTYPE:
case CP_CONST_MODULE:
case CP_CONST_PACKAGE:
cpe.s0 = read16(buffer);
return true;
case CP_CONST_FIELD:
case CP_CONST_METHOD:
case CP_CONST_INTERFACE:
case CP_CONST_NAMEANDTYPE:
case CP_CONST_METHHANDLE:
cpe.s0 = read16(buffer);
cpe.s1 = read16(buffer);
return true;
case CP_CONST_INT:
case CP_CONST_FLOAT:
cpe.i0 = read32(buffer);
return true;
case CP_CONST_LONG:
case CP_CONST_DOUBLE:
cpe.i0 = read32(buffer);
cpe.i1 = read32(buffer);
return true;
case CP_CONST_UTF8:
cpe.len = read16(buffer);
cpe.data = buffer;
buffer += cpe.len;
return true;
case CP_CONST_INVOKEDYN:
fprintf(stderr, "INVOKEDYN constant unsupported, Bailing\n");
return false;
}
fprintf(stderr, "Unrecognized constant pool tag 0x%02x, Bailing\n", cpe.tag);
return false;
}
static void skip_attributes(uint8_t*& buffer) {
/* Todo:
* Consider adding some verification so we don't walk
* off the end in the case of a corrupt class file.
*/
uint16_t acount = read16(buffer);
for (int i = 0; i < acount; i++) {
buffer += 2; // Skip name_index
uint32_t length = read32(buffer);
buffer += length;
}
}
#define MAX_CLASS_NAMELEN (8 * 1024)
static DexType* make_dextype_from_cref(std::vector<cp_entry>& cpool,
uint16_t cref) {
char nbuffer[MAX_CLASS_NAMELEN];
if (cpool[cref].tag != CP_CONST_CLASS) {
fprintf(stderr, "Non-class ref in get_class_name, Bailing\n");
return nullptr;
}
uint16_t utf8ref = cpool[cref].s0;
const cp_entry& utf8cpe = cpool[utf8ref];
if (utf8cpe.tag != CP_CONST_UTF8) {
fprintf(stderr, "Non-utf8 ref in get_utf8, Bailing\n");
return nullptr;
}
if (utf8cpe.len > (MAX_CLASS_NAMELEN + 3)) {
fprintf(stderr, "classname is greater than max, bailing");
return nullptr;
}
nbuffer[0] = 'L';
memcpy(nbuffer + 1, utf8cpe.data, utf8cpe.len);
nbuffer[1 + utf8cpe.len] = ';';
nbuffer[2 + utf8cpe.len] = '\0';
return DexType::make_type(nbuffer);
}
static bool extract_utf8(std::vector<cp_entry>& cpool,
uint16_t utf8ref,
char* out,
uint32_t size) {
const cp_entry& utf8cpe = cpool[utf8ref];
if (utf8cpe.tag != CP_CONST_UTF8) {
fprintf(stderr, "Non-utf8 ref in get_utf8, bailing\n");
return false;
}
if (utf8cpe.len > (size - 1)) {
fprintf(stderr, "Name is greater (%hu) than max (%u), bailing\n",
utf8cpe.len, size);
return false;
}
memcpy(out, utf8cpe.data, utf8cpe.len);
out[utf8cpe.len] = '\0';
return true;
}
static DexField* make_dexfield(std::vector<cp_entry>& cpool,
DexType* self,
cp_field_info& finfo) {
char dbuffer[MAX_CLASS_NAMELEN];
char nbuffer[MAX_CLASS_NAMELEN];
if (!extract_utf8(cpool, finfo.nameNdx, nbuffer, MAX_CLASS_NAMELEN) ||
!extract_utf8(cpool, finfo.descNdx, dbuffer, MAX_CLASS_NAMELEN)) {
return nullptr;
}
auto name = DexString::make_string(nbuffer);
DexType* desc = DexType::make_type(dbuffer);
DexField* field =
static_cast<DexField*>(DexField::make_field(self, name, desc));
field->set_access((DexAccessFlags)finfo.aflags);
field->set_external();
return field;
}
static DexType* simpleTypeB;
static DexType* simpleTypeC;
static DexType* simpleTypeD;
static DexType* simpleTypeF;
static DexType* simpleTypeI;
static DexType* simpleTypeJ;
static DexType* simpleTypeS;
static DexType* simpleTypeZ;
static DexType* simpleTypeV;
void init_basic_types() {
simpleTypeB = DexType::make_type("B");
simpleTypeC = DexType::make_type("C");
simpleTypeD = DexType::make_type("D");
simpleTypeF = DexType::make_type("F");
simpleTypeI = DexType::make_type("I");
simpleTypeJ = DexType::make_type("J");
simpleTypeS = DexType::make_type("S");
simpleTypeZ = DexType::make_type("Z");
simpleTypeV = DexType::make_type("V");
}
static DexType* parse_type(const char*& buf) {
char typebuffer[MAX_CLASS_NAMELEN];
char desc = *buf++;
switch (desc) {
case 'B':
return simpleTypeB;
case 'C':
return simpleTypeC;
case 'D':
return simpleTypeD;
case 'F':
return simpleTypeF;
case 'I':
return simpleTypeI;
case 'J':
return simpleTypeJ;
case 'S':
return simpleTypeS;
case 'Z':
return simpleTypeZ;
case 'V':
return simpleTypeV;
case 'L': {
char* tpout = typebuffer;
*tpout++ = desc;
while (*buf != ';') {
*tpout++ = *buf++;
}
*tpout++ = *buf++;
*tpout = '\0';
return DexType::make_type(typebuffer);
break;
}
case '[': {
char* tpout = typebuffer;
*tpout++ = desc;
while (*buf == '[') {
*tpout++ = *buf++;
}
if (*buf == 'L') {
while (*buf != ';') {
*tpout++ = *buf++;
}
*tpout++ = *buf++;
} else {
*tpout++ = *buf++;
}
*tpout++ = '\0';
return DexType::make_type(typebuffer);
}
}
fprintf(stderr, "Invalid parse-type '%c', bailing\n", desc);
return nullptr;
}
static DexTypeList* extract_arguments(const char*& buf) {
buf++;
if (*buf == ')') {
buf++;
return DexTypeList::make_type_list({});
}
DexTypeList::ContainerType args;
while (*buf != ')') {
DexType* dtype = parse_type(buf);
if (dtype == nullptr) return nullptr;
if (dtype == simpleTypeV) {
fprintf(stderr, "Invalid argument type 'V' in args, bailing\n");
return nullptr;
}
args.push_back(dtype);
}
buf++;
return DexTypeList::make_type_list(std::move(args));
}
static DexMethod* make_dexmethod(std::vector<cp_entry>& cpool,
DexType* self,
cp_method_info& finfo) {
char dbuffer[MAX_CLASS_NAMELEN];
char nbuffer[MAX_CLASS_NAMELEN];
if (!extract_utf8(cpool, finfo.nameNdx, nbuffer, MAX_CLASS_NAMELEN) ||
!extract_utf8(cpool, finfo.descNdx, dbuffer, MAX_CLASS_NAMELEN)) {
return nullptr;
}
auto name = DexString::make_string(nbuffer);
const char* ptr = dbuffer;
DexTypeList* tlist = extract_arguments(ptr);
if (tlist == nullptr) return nullptr;
DexType* rtype = parse_type(ptr);
if (rtype == nullptr) return nullptr;
DexProto* proto = DexProto::make_proto(rtype, tlist);
DexMethod* method =
static_cast<DexMethod*>(DexMethod::make_method(self, name, proto));
if (method->is_concrete()) {
fprintf(stderr, "Pre-concrete method attempted to load '%s', bailing\n",
SHOW(method));
return nullptr;
}
uint32_t access = finfo.aflags;
bool is_virt = true;
if (nbuffer[0] == '<') {
is_virt = false;
if (nbuffer[1] == 'i') {
access |= ACC_CONSTRUCTOR;
}
} else if (access & (ACC_PRIVATE | ACC_STATIC))
is_virt = false;
method->set_access((DexAccessFlags)access);
method->set_virtual(is_virt);
method->set_external();
return method;
}
bool parse_class(uint8_t* buffer,
Scope* classes,
attribute_hook_t attr_hook,
const DexLocation* jar_location) {
uint32_t magic = read32(buffer);
uint16_t vminor DEBUG_ONLY = read16(buffer);
uint16_t vmajor DEBUG_ONLY = read16(buffer);
uint16_t cp_count = read16(buffer);
if (magic != kClassMagic) {
fprintf(stderr, "Bad class magic %08x, Bailing\n", magic);
return false;
}
std::vector<cp_entry> cpool;
cpool.resize(cp_count);
/* The zero'th entry is always empty. Java is annoying. */
for (int i = 1; i < cp_count; i++) {
if (!parse_cp_entry(buffer, cpool[i])) return false;
if (cpool[i].tag == CP_CONST_LONG || cpool[i].tag == CP_CONST_DOUBLE) {
cpool[i + 1] = cpool[i];
i++;
}
}
uint16_t aflags = read16(buffer);
uint16_t clazz = read16(buffer);
uint16_t super = read16(buffer);
uint16_t ifcount = read16(buffer);
if (is_module((DexAccessFlags)aflags)) {
// Classes with the ACC_MODULE access flag are special. They contain
// metadata for the module/package system and don't have a superclass.
// Ignore them for now.
TRACE(MAIN, 5, "Warning: ignoring module-info class in jar '%s'",
jar_location->get_file_name().c_str());
return true;
}
DexType* self = make_dextype_from_cref(cpool, clazz);
DexClass* cls = type_class(self);
if (cls) {
// We are seeing duplicate classes when parsing jar file
if (cls->is_external()) {
// Two external classes in .jar file has the same name
// Just issue an warning for now
TRACE(MAIN, 1,
"Warning: Found a duplicate class '%s' in two .jar files:\n "
" Current: '%s'\n"
" Previous: '%s'",
SHOW(self), jar_location->get_file_name().c_str(),
cls->get_location()->get_file_name().c_str());
} else if (!dup_classes::is_known_dup(cls)) {
TRACE(MAIN, 1,
"Warning: Found a duplicate class '%s' in .dex and .jar file."
" Current: '%s'\n"
" Previous: '%s'\n",
SHOW(self), jar_location->get_file_name().c_str(),
cls->get_location()->get_file_name().c_str());
// TODO: There are still blocking issues in instrumentation test that are
// blocking. We currently only fail for duplicate `android*` classes,
// we can make this throw for all the classes once they are fixed.
if (boost::starts_with(cls->str(), "Landroid")) {
throw RedexException(RedexError::DUPLICATE_CLASSES,
"Found duplicate class in two different files.",
{{"class", SHOW(self)},
{"jar", jar_location->get_file_name()},
{"dex", cls->get_location()->get_file_name()}});
}
}
return true;
}
ClassCreator cc(self, jar_location);
cc.set_external();
if (super != 0) {
DexType* sclazz = make_dextype_from_cref(cpool, super);
cc.set_super(sclazz);
}
cc.set_access((DexAccessFlags)aflags);
if (ifcount) {
for (int i = 0; i < ifcount; i++) {
uint16_t iface = read16(buffer);
DexType* iftype = make_dextype_from_cref(cpool, iface);
cc.add_interface(iftype);
}
}
uint16_t fcount = read16(buffer);
auto invoke_attr_hook =
[&](const boost::variant<DexField*, DexMethod*>& field_or_method,
uint8_t* attrPtr) {
if (attr_hook == nullptr) {
return;
}
uint16_t attributes_count = read16(attrPtr);
for (uint16_t j = 0; j < attributes_count; j++) {
uint16_t attribute_name_index = read16(attrPtr);
uint32_t attribute_length = read32(attrPtr);
char attribute_name[MAX_CLASS_NAMELEN];
auto extract_res = extract_utf8(cpool, attribute_name_index,
attribute_name, MAX_CLASS_NAMELEN);
always_assert_log(
extract_res,
"attribute hook was specified, but failed to load the attribute "
"name due to insufficient name buffer");
attr_hook(field_or_method, attribute_name, attrPtr);
attrPtr += attribute_length;
}
};
for (int i = 0; i < fcount; i++) {
cp_field_info cpfield;
cpfield.aflags = read16(buffer);
cpfield.nameNdx = read16(buffer);
cpfield.descNdx = read16(buffer);
uint8_t* attrPtr = buffer;
skip_attributes(buffer);
DexField* field = make_dexfield(cpool, self, cpfield);
if (field == nullptr) return false;
cc.add_field(field);
invoke_attr_hook({field}, attrPtr);
}
uint16_t mcount = read16(buffer);
if (mcount) {
for (int i = 0; i < mcount; i++) {
cp_method_info cpmethod;
cpmethod.aflags = read16(buffer);
cpmethod.nameNdx = read16(buffer);
cpmethod.descNdx = read16(buffer);
uint8_t* attrPtr = buffer;
skip_attributes(buffer);
DexMethod* method = make_dexmethod(cpool, self, cpmethod);
if (method == nullptr) return false;
cc.add_method(method);
invoke_attr_hook({method}, attrPtr);
}
}
DexClass* dc = cc.create();
if (classes != nullptr) {
classes->emplace_back(dc);
}
//#define DEBUG_PRINT
#ifdef DEBUG_PRINT
fprintf(stderr, "DexClass constructed from jar:\n%s\n", SHOW(dc));
if (dc->get_sfields().size()) {
fprintf(stderr, "Static Fields:\n");
for (auto const& field : dc->get_sfields()) {
fprintf(stderr, "\t%s\n", SHOW(field));
}
}
if (dc->get_ifields().size()) {
fprintf(stderr, "Instance Fields:\n");
for (auto const& field : dc->get_ifields()) {
fprintf(stderr, "\t%s\n", SHOW(field));
}
}
if (dc->get_dmethods().size()) {
fprintf(stderr, "Direct Methods:\n");
for (auto const& method : dc->get_dmethods()) {
fprintf(stderr, "\t%s\n", SHOW(method));
}
}
if (dc->get_vmethods().size()) {
fprintf(stderr, "Virtual Methods:\n");
for (auto const& method : dc->get_vmethods()) {
fprintf(stderr, "\t%s\n", SHOW(method));
}
}
#endif
return true;
}
bool load_class_file(const std::string& filename, Scope* classes) {
// It's not exactly efficient to call init_basic_types repeatedly for each
// class file that we load, but load_class_file should typically only be used
// in tests to load a small number of files.
init_basic_types();
std::ifstream ifs(filename, std::ifstream::binary);
auto buf = ifs.rdbuf();
size_t size = buf->pubseekoff(0, ifs.end, ifs.in);
buf->pubseekpos(0, ifs.in);
auto buffer = std::make_unique<char[]>(size);
buf->sgetn(buffer.get(), size);
auto jar_location = DexLocation::make_location("", filename);
return parse_class(reinterpret_cast<uint8_t*>(buffer.get()), classes,
/* attr_hook */ nullptr, jar_location);
}
/******************
* Begin Jar Loading code.
*
*/
namespace {
static const int kSignatureSize = 4;
/* CDFile
* Central directory file header entry structures.
*/
static constexpr uint16_t kCompMethodStore = 0;
static const uint16_t kCompMethodDeflate(8);
static const uint8_t kCDFile[] = {'P', 'K', 0x01, 0x02};
PACKED(struct pk_cd_file {
uint32_t signature;
uint16_t vmade;
uint16_t vextract;
uint16_t flags;
uint16_t comp_method;
uint16_t mod_time;
uint16_t mod_date;
uint32_t crc32;
uint32_t comp_size;
uint32_t ucomp_size;
uint16_t fname_len;
uint16_t extra_len;
uint16_t comment_len;
uint16_t diskno;
uint16_t interal_attr;
uint32_t external_attr;
uint32_t disk_offset;
});
/* CDirEnd:
* End of central directory record structures.
*/
static const int kMaxCDirEndSearch = 100;
static const uint8_t kCDirEnd[] = {'P', 'K', 0x05, 0x06};
PACKED(struct pk_cdir_end {
uint32_t signature;
uint16_t diskno;
uint16_t cd_diskno;
uint16_t cd_disk_entries;
uint16_t cd_entries;
uint32_t cd_size;
uint32_t cd_disk_offset;
uint16_t comment_len;
});
/* LFile:
* Local file header structures.
* (Yes, this made more sense in the world of floppies and tapes.)
*/
static const uint8_t kLFile[] = {'P', 'K', 0x03, 0x04};
PACKED(struct pk_lfile {
uint32_t signature;
uint16_t vextract;
uint16_t flags;
uint16_t comp_method;
uint16_t mod_time;
uint16_t mod_date;
uint32_t crc32;
uint32_t comp_size;
uint32_t ucomp_size;
uint16_t fname_len;
uint16_t extra_len;
});
struct jar_entry {
struct pk_cd_file cd_entry;
uint8_t* filename;
jar_entry() = default;
jar_entry(const jar_entry&) = delete;
jar_entry(jar_entry&& other) noexcept { *this = std::move(other); }
jar_entry& operator=(const jar_entry&) = delete;
jar_entry& operator=(jar_entry&& other) noexcept {
if (this != &other) {
filename = std::exchange(other.filename, nullptr);
cd_entry = other.cd_entry;
memset(&other.cd_entry, 0, sizeof(other.cd_entry));
}
return *this;
}
~jar_entry() {
if (filename != nullptr) {
free(filename);
filename = nullptr;
}
}
};
} // namespace
static bool find_central_directory(const uint8_t* mapping,
ssize_t size,
pk_cdir_end& pce) {
ssize_t soffset = (size - sizeof(pk_cdir_end));
ssize_t eoffset = soffset - kMaxCDirEndSearch;
if (soffset < 0) return false;
if (eoffset < 0) eoffset = 0;
do {
const uint8_t* cdsearch = mapping + soffset;
if (memcmp(cdsearch, kCDirEnd, kSignatureSize) == 0) {
memcpy(&pce, cdsearch, sizeof(pk_cdir_end));
return true;
}
} while (soffset-- > eoffset);
fprintf(stderr, "End of central directory record not found, bailing\n");
return false;
}
static bool validate_pce(pk_cdir_end& pce, ssize_t size) {
/* We only support a limited feature set. We
* don't support disk-spanning, so bail if that's the case.
*/
if (pce.cd_diskno != pce.diskno || pce.cd_diskno != 0 ||
pce.cd_entries != pce.cd_disk_entries) {
fprintf(stderr, "Disk spanning is not supported, bailing\n");
return false;
}
ssize_t data_size = size - sizeof(pk_cdir_end);
if (pce.cd_disk_offset + pce.cd_size > data_size) {
fprintf(stderr, "Central directory overflow, invalid pce structure\n");
return false;
}
return true;
}
static bool extract_jar_entry(const uint8_t*& mapping, jar_entry& je) {
if (memcmp(mapping, kCDFile, kSignatureSize) != 0) {
fprintf(stderr, "Invalid central directory entry, bailing\n");
return false;
}
memcpy(&je.cd_entry, mapping, sizeof(pk_cd_file));
mapping += sizeof(pk_cd_file);
je.filename = (uint8_t*)malloc(je.cd_entry.fname_len + 1);
memcpy(je.filename, mapping, je.cd_entry.fname_len);
je.filename[je.cd_entry.fname_len] = '\0';
mapping += je.cd_entry.fname_len;
mapping += je.cd_entry.extra_len;
mapping += je.cd_entry.comment_len;
return true;
}
static bool get_jar_entries(const uint8_t* mapping,
pk_cdir_end& pce,
std::vector<jar_entry>& files) {
const uint8_t* cdir = mapping + pce.cd_disk_offset;
files.resize(pce.cd_entries);
for (int entry = 0; entry < pce.cd_entries; entry++) {
if (!extract_jar_entry(cdir, files[entry])) return false;
}
return true;
}
static int jar_uncompress(Bytef* dest,
uLongf* destLen,
const Bytef* source,
uLong sourceLen,
uint32_t comp_method) {
if (comp_method == kCompMethodStore) {
if (sourceLen > *destLen) {
std::cerr << "Not enough space for STOREd entry: " << sourceLen << " vs "
<< *destLen << std::endl;
return Z_BUF_ERROR;
}
memcpy(dest, source, sourceLen);
*destLen = sourceLen;
return Z_OK;
}
z_stream stream;
int err;
stream.next_in = (Bytef*)source;
stream.avail_in = (uInt)sourceLen;
stream.next_out = dest;
stream.avail_out = (uInt)*destLen;
stream.zalloc = (alloc_func)0;
stream.zfree = (free_func)0;
err = inflateInit2(&stream, -MAX_WBITS);
if (err != Z_OK) return err;
err = inflate(&stream, Z_FINISH);
if (err != Z_STREAM_END) {
inflateEnd(&stream);
return err;
}
*destLen = stream.total_out;
err = inflateEnd(&stream);
return err;
}
static bool decompress_class(jar_entry& file,
const uint8_t* mapping,
uint8_t* outbuffer,
ssize_t bufsize) {
if (file.cd_entry.comp_method != kCompMethodDeflate &&
file.cd_entry.comp_method != kCompMethodStore) {
fprintf(stderr, "Unknown compression method %d for %s, Bailing\n",
file.cd_entry.comp_method, file.filename);
return false;
}
const uint8_t* lfile = mapping + file.cd_entry.disk_offset;
if (memcmp(lfile, kLFile, kSignatureSize) != 0) {
fprintf(stderr, "Invalid local file entry, bailing\n");
return false;
}
pk_lfile pkf;
memcpy(&pkf, lfile, sizeof(pk_lfile));
if (pkf.comp_size == 0 && pkf.ucomp_size == 0 &&
pkf.comp_size != file.cd_entry.comp_size &&
pkf.ucomp_size != file.cd_entry.ucomp_size) {
pkf.comp_size = file.cd_entry.comp_size;
pkf.ucomp_size = file.cd_entry.ucomp_size;
}
lfile += sizeof(pk_lfile);
if (pkf.fname_len != file.cd_entry.fname_len ||
pkf.comp_size != file.cd_entry.comp_size ||
pkf.ucomp_size != file.cd_entry.ucomp_size ||
pkf.comp_method != file.cd_entry.comp_method ||
memcmp(lfile, file.filename, pkf.fname_len) != 0) {
fprintf(stderr,
"Directory entry doesn't match local file header, "
"Bailing %d %d %d %d, %d %d %d %d extra %d\n",
pkf.fname_len, pkf.comp_size, pkf.ucomp_size, pkf.comp_method,
file.cd_entry.fname_len, file.cd_entry.comp_size,
file.cd_entry.ucomp_size, file.cd_entry.comp_method, pkf.extra_len);
return false;
}
lfile += pkf.fname_len;
lfile += pkf.extra_len;
uLongf dlen = bufsize;
int zlibrv = jar_uncompress(outbuffer, &dlen, lfile, pkf.comp_size,
file.cd_entry.comp_method);
if (zlibrv != Z_OK) {
fprintf(stderr, "uncompress failed with code %d, Bailing\n", zlibrv);
return false;
}
if (dlen != pkf.ucomp_size) {
fprintf(stderr, "mis-match on uncompressed size, Bailing\n");
return false;
}
return true;
}
static const int kStartBufferSize = 128 * 1024;
static bool process_jar_entries(const DexLocation* location,
std::vector<jar_entry>& files,
const uint8_t* mapping,
Scope* classes,
const attribute_hook_t& attr_hook) {
ssize_t bufsize = kStartBufferSize;
uint8_t* outbuffer = (uint8_t*)malloc(bufsize);
static char classEndString[] = ".class";
static size_t classEndStringLen = strlen(classEndString);
init_basic_types();
for (auto& file : files) {
if (file.cd_entry.ucomp_size == 0) continue;
if (file.cd_entry.fname_len < (classEndStringLen + 1)) continue;
// Skip non-class files
uint8_t* endcomp =
file.filename + (file.cd_entry.fname_len - classEndStringLen);
if (memcmp(endcomp, classEndString, classEndStringLen) != 0) continue;
// Resize output if necessary.
if (bufsize < file.cd_entry.ucomp_size) {
while (bufsize < file.cd_entry.ucomp_size)
bufsize *= 2;
free(outbuffer);
outbuffer = (uint8_t*)malloc(bufsize);
}
if (!decompress_class(file, mapping, outbuffer, bufsize)) {
free(outbuffer);
return false;
}
if (!parse_class(outbuffer, classes, attr_hook, location)) {
free(outbuffer);
return false;
}
}
free(outbuffer);
return true;
}
bool process_jar(const DexLocation* location,
const uint8_t* mapping,
ssize_t size,
Scope* classes,
const attribute_hook_t& attr_hook) {
pk_cdir_end pce;
std::vector<jar_entry> files;
if (!find_central_directory(mapping, size, pce)) return false;
if (!validate_pce(pce, size)) return false;
if (!get_jar_entries(mapping, pce, files)) return false;
if (!process_jar_entries(location, files, mapping, classes, attr_hook)) {
return false;
}
return true;
}
bool load_jar_file(const DexLocation* location,
Scope* classes,
const attribute_hook_t& attr_hook) {
boost::iostreams::mapped_file file;
try {
file.open(location->get_file_name().c_str(),
boost::iostreams::mapped_file::readonly);
} catch (const std::exception& e) {
fprintf(stderr, "error: cannot open jar file: %s\n",
location->get_file_name().c_str());
return false;
}
auto mapping = reinterpret_cast<const uint8_t*>(file.const_data());
if (!process_jar(location, mapping, file.size(), classes, attr_hook)) {
fprintf(stderr, "error: cannot process jar: %s\n",
location->get_file_name().c_str());
return false;
}
return true;
}
//#define LOCAL_MAIN
#ifdef LOCAL_MAIN
int main(int argc, char* argv[]) {
if (argc < 2) {
fprintf(stderr, "You must specify a jar file\n");
return -1;
}
for (int jarno = 1; jarno < argc; jarno++) {
if (!load_jar_file(DexLocation::make_location("", argv[jarno]))) {
fprintf(stderr, "Failed to load jar %s, bailing\n", argv[jarno]);
return -2;
}
}
}
#endif