You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
418 lines
15 KiB
418 lines
15 KiB
/*
|
|
* Copyright (C) 2019 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define TRACE_TAG ADB
|
|
|
|
#include "apk_archive.h"
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include "adb_trace.h"
|
|
#include "sysdeps.h"
|
|
|
|
#include <android-base/endian.h>
|
|
#include <android-base/mapped_file.h>
|
|
|
|
#include <openssl/md5.h>
|
|
|
|
constexpr uint16_t kCompressStored = 0;
|
|
|
|
// mask value that signifies that the entry has a DD
|
|
static const uint32_t kGPBDDFlagMask = 0x0008;
|
|
|
|
namespace {
|
|
struct FileRegion {
|
|
FileRegion(borrowed_fd fd, off64_t offset, size_t length)
|
|
: mapped_(android::base::MappedFile::FromOsHandle(adb_get_os_handle(fd), offset, length,
|
|
PROT_READ)) {
|
|
if (mapped_ != nullptr) {
|
|
return;
|
|
}
|
|
|
|
// Mapped file failed, falling back to pread.
|
|
buffer_.resize(length);
|
|
if (auto err = adb_pread(fd.get(), buffer_.data(), length, offset); size_t(err) != length) {
|
|
fprintf(stderr, "Unable to read %lld bytes at offset %" PRId64 " \n",
|
|
static_cast<long long>(length), offset);
|
|
buffer_.clear();
|
|
return;
|
|
}
|
|
}
|
|
|
|
const char* data() const { return mapped_ ? mapped_->data() : buffer_.data(); }
|
|
size_t size() const { return mapped_ ? mapped_->size() : buffer_.size(); }
|
|
|
|
private:
|
|
FileRegion() = default;
|
|
DISALLOW_COPY_AND_ASSIGN(FileRegion);
|
|
|
|
std::unique_ptr<android::base::MappedFile> mapped_;
|
|
std::string buffer_;
|
|
};
|
|
} // namespace
|
|
|
|
using com::android::fastdeploy::APKDump;
|
|
|
|
ApkArchive::ApkArchive(const std::string& path) : path_(path), size_(0) {
|
|
fd_.reset(adb_open(path_.c_str(), O_RDONLY));
|
|
if (fd_ == -1) {
|
|
fprintf(stderr, "Unable to open file '%s'\n", path_.c_str());
|
|
return;
|
|
}
|
|
|
|
struct stat st;
|
|
if (stat(path_.c_str(), &st) == -1) {
|
|
fprintf(stderr, "Unable to stat file '%s'\n", path_.c_str());
|
|
return;
|
|
}
|
|
size_ = st.st_size;
|
|
}
|
|
|
|
ApkArchive::~ApkArchive() {}
|
|
|
|
APKDump ApkArchive::ExtractMetadata() {
|
|
D("ExtractMetadata");
|
|
if (!ready()) {
|
|
return {};
|
|
}
|
|
|
|
Location cdLoc = GetCDLocation();
|
|
if (!cdLoc.valid) {
|
|
return {};
|
|
}
|
|
|
|
APKDump dump;
|
|
dump.set_absolute_path(path_);
|
|
dump.set_cd(ReadMetadata(cdLoc));
|
|
|
|
Location sigLoc = GetSignatureLocation(cdLoc.offset);
|
|
if (sigLoc.valid) {
|
|
dump.set_signature(ReadMetadata(sigLoc));
|
|
}
|
|
return dump;
|
|
}
|
|
|
|
off_t ApkArchive::FindEndOfCDRecord() const {
|
|
constexpr int endOfCDSignature = 0x06054b50;
|
|
constexpr off_t endOfCDMinSize = 22;
|
|
constexpr off_t endOfCDMaxSize = 65535 + endOfCDMinSize;
|
|
|
|
auto sizeToRead = std::min(size_, endOfCDMaxSize);
|
|
auto readOffset = size_ - sizeToRead;
|
|
FileRegion mapped(fd_, readOffset, sizeToRead);
|
|
|
|
// Start scanning from the end
|
|
auto* start = mapped.data();
|
|
auto* cursor = start + mapped.size() - sizeof(endOfCDSignature);
|
|
|
|
// Search for End of Central Directory record signature.
|
|
while (cursor >= start) {
|
|
if (*(int32_t*)cursor == endOfCDSignature) {
|
|
return readOffset + (cursor - start);
|
|
}
|
|
cursor--;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
ApkArchive::Location ApkArchive::FindCDRecord(const char* cursor) {
|
|
struct ecdr_t {
|
|
int32_t signature;
|
|
uint16_t diskNumber;
|
|
uint16_t numDisk;
|
|
uint16_t diskEntries;
|
|
uint16_t numEntries;
|
|
uint32_t crSize;
|
|
uint32_t offsetToCdHeader;
|
|
uint16_t commentSize;
|
|
uint8_t comment[0];
|
|
} __attribute__((packed));
|
|
ecdr_t* header = (ecdr_t*)cursor;
|
|
|
|
Location location;
|
|
location.offset = header->offsetToCdHeader;
|
|
location.size = header->crSize;
|
|
location.valid = true;
|
|
return location;
|
|
}
|
|
|
|
ApkArchive::Location ApkArchive::GetCDLocation() {
|
|
constexpr off_t cdEntryHeaderSizeBytes = 22;
|
|
Location location;
|
|
|
|
// Find End of Central Directory Record
|
|
off_t eocdRecord = FindEndOfCDRecord();
|
|
if (eocdRecord < 0) {
|
|
fprintf(stderr, "Unable to find End of Central Directory record in file '%s'\n",
|
|
path_.c_str());
|
|
return location;
|
|
}
|
|
|
|
// Find Central Directory Record
|
|
FileRegion mapped(fd_, eocdRecord, cdEntryHeaderSizeBytes);
|
|
location = FindCDRecord(mapped.data());
|
|
if (!location.valid) {
|
|
fprintf(stderr, "Unable to find Central Directory File Header in file '%s'\n",
|
|
path_.c_str());
|
|
return location;
|
|
}
|
|
|
|
return location;
|
|
}
|
|
|
|
ApkArchive::Location ApkArchive::GetSignatureLocation(off_t cdRecordOffset) {
|
|
Location location;
|
|
|
|
// Signature constants.
|
|
constexpr off_t endOfSignatureSize = 24;
|
|
off_t signatureOffset = cdRecordOffset - endOfSignatureSize;
|
|
if (signatureOffset < 0) {
|
|
fprintf(stderr, "Unable to find signature in file '%s'\n", path_.c_str());
|
|
return location;
|
|
}
|
|
|
|
FileRegion mapped(fd_, signatureOffset, endOfSignatureSize);
|
|
|
|
uint64_t signatureSize = *(uint64_t*)mapped.data();
|
|
auto* signature = mapped.data() + sizeof(signatureSize);
|
|
// Check if there is a v2/v3 Signature block here.
|
|
if (memcmp(signature, "APK Sig Block 42", 16)) {
|
|
return location;
|
|
}
|
|
|
|
// This is likely a signature block.
|
|
location.size = signatureSize;
|
|
location.offset = cdRecordOffset - location.size - 8;
|
|
location.valid = true;
|
|
|
|
return location;
|
|
}
|
|
|
|
std::string ApkArchive::ReadMetadata(Location loc) const {
|
|
FileRegion mapped(fd_, loc.offset, loc.size);
|
|
return {mapped.data(), mapped.size()};
|
|
}
|
|
|
|
size_t ApkArchive::ParseCentralDirectoryRecord(const char* input, size_t size, std::string* md5Hash,
|
|
int64_t* localFileHeaderOffset, int64_t* dataSize) {
|
|
// A structure representing the fixed length fields for a single
|
|
// record in the central directory of the archive. In addition to
|
|
// the fixed length fields listed here, each central directory
|
|
// record contains a variable length "file_name" and "extra_field"
|
|
// whose lengths are given by |file_name_length| and |extra_field_length|
|
|
// respectively.
|
|
static constexpr int kCDFileHeaderMagic = 0x02014b50;
|
|
struct CentralDirectoryRecord {
|
|
// The start of record signature. Must be |kSignature|.
|
|
uint32_t record_signature;
|
|
// Source tool version. Top byte gives source OS.
|
|
uint16_t version_made_by;
|
|
// Tool version. Ignored by this implementation.
|
|
uint16_t version_needed;
|
|
// The "general purpose bit flags" for this entry. The only
|
|
// flag value that we currently check for is the "data descriptor"
|
|
// flag.
|
|
uint16_t gpb_flags;
|
|
// The compression method for this entry, one of |kCompressStored|
|
|
// and |kCompressDeflated|.
|
|
uint16_t compression_method;
|
|
// The file modification time and date for this entry.
|
|
uint16_t last_mod_time;
|
|
uint16_t last_mod_date;
|
|
// The CRC-32 checksum for this entry.
|
|
uint32_t crc32;
|
|
// The compressed size (in bytes) of this entry.
|
|
uint32_t compressed_size;
|
|
// The uncompressed size (in bytes) of this entry.
|
|
uint32_t uncompressed_size;
|
|
// The length of the entry file name in bytes. The file name
|
|
// will appear immediately after this record.
|
|
uint16_t file_name_length;
|
|
// The length of the extra field info (in bytes). This data
|
|
// will appear immediately after the entry file name.
|
|
uint16_t extra_field_length;
|
|
// The length of the entry comment (in bytes). This data will
|
|
// appear immediately after the extra field.
|
|
uint16_t comment_length;
|
|
// The start disk for this entry. Ignored by this implementation).
|
|
uint16_t file_start_disk;
|
|
// File attributes. Ignored by this implementation.
|
|
uint16_t internal_file_attributes;
|
|
// File attributes. For archives created on Unix, the top bits are the
|
|
// mode.
|
|
uint32_t external_file_attributes;
|
|
// The offset to the local file header for this entry, from the
|
|
// beginning of this archive.
|
|
uint32_t local_file_header_offset;
|
|
|
|
private:
|
|
CentralDirectoryRecord() = default;
|
|
DISALLOW_COPY_AND_ASSIGN(CentralDirectoryRecord);
|
|
} __attribute__((packed));
|
|
|
|
const CentralDirectoryRecord* cdr;
|
|
if (size < sizeof(*cdr)) {
|
|
return 0;
|
|
}
|
|
|
|
auto begin = input;
|
|
cdr = reinterpret_cast<const CentralDirectoryRecord*>(begin);
|
|
if (cdr->record_signature != kCDFileHeaderMagic) {
|
|
fprintf(stderr, "Invalid Central Directory Record signature\n");
|
|
return 0;
|
|
}
|
|
auto end = begin + sizeof(*cdr) + cdr->file_name_length + cdr->extra_field_length +
|
|
cdr->comment_length;
|
|
|
|
uint8_t md5Digest[MD5_DIGEST_LENGTH];
|
|
MD5((const unsigned char*)begin, end - begin, md5Digest);
|
|
md5Hash->assign((const char*)md5Digest, sizeof(md5Digest));
|
|
|
|
*localFileHeaderOffset = cdr->local_file_header_offset;
|
|
*dataSize = (cdr->compression_method == kCompressStored) ? cdr->uncompressed_size
|
|
: cdr->compressed_size;
|
|
|
|
return end - begin;
|
|
}
|
|
|
|
size_t ApkArchive::CalculateLocalFileEntrySize(int64_t localFileHeaderOffset,
|
|
int64_t dataSize) const {
|
|
// The local file header for a given entry. This duplicates information
|
|
// present in the central directory of the archive. It is an error for
|
|
// the information here to be different from the central directory
|
|
// information for a given entry.
|
|
static constexpr int kLocalFileHeaderMagic = 0x04034b50;
|
|
struct LocalFileHeader {
|
|
// The local file header signature, must be |kSignature|.
|
|
uint32_t lfh_signature;
|
|
// Tool version. Ignored by this implementation.
|
|
uint16_t version_needed;
|
|
// The "general purpose bit flags" for this entry. The only
|
|
// flag value that we currently check for is the "data descriptor"
|
|
// flag.
|
|
uint16_t gpb_flags;
|
|
// The compression method for this entry, one of |kCompressStored|
|
|
// and |kCompressDeflated|.
|
|
uint16_t compression_method;
|
|
// The file modification time and date for this entry.
|
|
uint16_t last_mod_time;
|
|
uint16_t last_mod_date;
|
|
// The CRC-32 checksum for this entry.
|
|
uint32_t crc32;
|
|
// The compressed size (in bytes) of this entry.
|
|
uint32_t compressed_size;
|
|
// The uncompressed size (in bytes) of this entry.
|
|
uint32_t uncompressed_size;
|
|
// The length of the entry file name in bytes. The file name
|
|
// will appear immediately after this record.
|
|
uint16_t file_name_length;
|
|
// The length of the extra field info (in bytes). This data
|
|
// will appear immediately after the entry file name.
|
|
uint16_t extra_field_length;
|
|
|
|
private:
|
|
LocalFileHeader() = default;
|
|
DISALLOW_COPY_AND_ASSIGN(LocalFileHeader);
|
|
} __attribute__((packed));
|
|
static constexpr int kLocalFileHeaderSize = sizeof(LocalFileHeader);
|
|
CHECK(ready()) << path_;
|
|
|
|
const LocalFileHeader* lfh;
|
|
if (localFileHeaderOffset + kLocalFileHeaderSize > size_) {
|
|
fprintf(stderr,
|
|
"Invalid Local File Header offset in file '%s' at offset %lld, file size %lld\n",
|
|
path_.c_str(), static_cast<long long>(localFileHeaderOffset),
|
|
static_cast<long long>(size_));
|
|
return 0;
|
|
}
|
|
|
|
FileRegion lfhMapped(fd_, localFileHeaderOffset, sizeof(LocalFileHeader));
|
|
lfh = reinterpret_cast<const LocalFileHeader*>(lfhMapped.data());
|
|
if (lfh->lfh_signature != kLocalFileHeaderMagic) {
|
|
fprintf(stderr, "Invalid Local File Header signature in file '%s' at offset %lld\n",
|
|
path_.c_str(), static_cast<long long>(localFileHeaderOffset));
|
|
return 0;
|
|
}
|
|
|
|
// The *optional* data descriptor start signature.
|
|
static constexpr int kOptionalDataDescriptorMagic = 0x08074b50;
|
|
struct DataDescriptor {
|
|
// CRC-32 checksum of the entry.
|
|
uint32_t crc32;
|
|
// Compressed size of the entry.
|
|
uint32_t compressed_size;
|
|
// Uncompressed size of the entry.
|
|
uint32_t uncompressed_size;
|
|
|
|
private:
|
|
DataDescriptor() = default;
|
|
DISALLOW_COPY_AND_ASSIGN(DataDescriptor);
|
|
} __attribute__((packed));
|
|
static constexpr int kDataDescriptorSize = sizeof(DataDescriptor);
|
|
|
|
off_t ddOffset = localFileHeaderOffset + kLocalFileHeaderSize + lfh->file_name_length +
|
|
lfh->extra_field_length + dataSize;
|
|
int64_t ddSize = 0;
|
|
|
|
int64_t localDataSize;
|
|
if (lfh->gpb_flags & kGPBDDFlagMask) {
|
|
// There is trailing data descriptor.
|
|
const DataDescriptor* dd;
|
|
|
|
if (ddOffset + int(sizeof(uint32_t)) > size_) {
|
|
fprintf(stderr,
|
|
"Error reading trailing data descriptor signature in file '%s' at offset %lld, "
|
|
"file size %lld\n",
|
|
path_.c_str(), static_cast<long long>(ddOffset), static_cast<long long>(size_));
|
|
return 0;
|
|
}
|
|
|
|
FileRegion ddMapped(fd_, ddOffset, sizeof(uint32_t) + sizeof(DataDescriptor));
|
|
|
|
off_t localDDOffset = 0;
|
|
if (kOptionalDataDescriptorMagic == *(uint32_t*)ddMapped.data()) {
|
|
ddOffset += sizeof(uint32_t);
|
|
localDDOffset += sizeof(uint32_t);
|
|
ddSize += sizeof(uint32_t);
|
|
}
|
|
if (ddOffset + kDataDescriptorSize > size_) {
|
|
fprintf(stderr,
|
|
"Error reading trailing data descriptor in file '%s' at offset %lld, file size "
|
|
"%lld\n",
|
|
path_.c_str(), static_cast<long long>(ddOffset), static_cast<long long>(size_));
|
|
return 0;
|
|
}
|
|
|
|
dd = reinterpret_cast<const DataDescriptor*>(ddMapped.data() + localDDOffset);
|
|
localDataSize = (lfh->compression_method == kCompressStored) ? dd->uncompressed_size
|
|
: dd->compressed_size;
|
|
ddSize += sizeof(*dd);
|
|
} else {
|
|
localDataSize = (lfh->compression_method == kCompressStored) ? lfh->uncompressed_size
|
|
: lfh->compressed_size;
|
|
}
|
|
if (localDataSize != dataSize) {
|
|
fprintf(stderr,
|
|
"Data sizes mismatch in file '%s' at offset %lld, CDr: %lld vs LHR/DD: %lld\n",
|
|
path_.c_str(), static_cast<long long>(localFileHeaderOffset),
|
|
static_cast<long long>(dataSize), static_cast<long long>(localDataSize));
|
|
return 0;
|
|
}
|
|
|
|
return kLocalFileHeaderSize + lfh->file_name_length + lfh->extra_field_length + dataSize +
|
|
ddSize;
|
|
}
|