Add the ability to patch android binary XML manifests in-place

* This lets us add the debuggable flag we need, at the cost of needing
  to re-sign the APK. It works in many cases although sometimes it does
  fail - but this is provided just as a 'best effort' and not as a
  recommended workflow.
This commit is contained in:
baldurk
2018-01-29 22:37:55 +00:00
parent 83d921ea84
commit 969fdaa499
8 changed files with 1103 additions and 7 deletions
+1 -1
View File
@@ -110,7 +110,7 @@ The following libraries and components are incorporated into RenderDoc, listed h
* `AOSP <https://source.android.com/>`_ - Copyright (c) 2006-2016, The Android Open Source Project, distributed under the Apache 2.0 License.
Used to simplify Android workflows by distributing some tools from the android SDK.
Used to simplify Android workflows by distributing some tools from the android SDK, as well as patching android manifest files to enable debugging.
Thanks
------
+251
View File
@@ -0,0 +1,251 @@
/*
* Copyright (C) 2005 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.
*/
#pragma once
#include <stdint.h>
//////////////////////////////////////////////////////////////////////////////////
//
// These constants are taken from ResourceTypes.h:
// https://android.googlesource.com/platform/frameworks/base/+/42ebcb80b50834a1ce4755cd4ca86918c96ca3c6/libs/androidfw/include/androidfw/ResourceTypes.h
//
// The ones we need are extracted here to not be dependent on the rest of the android framework.
// There are some slight modifications for ease of integration, but this is still all under the
// android license.
enum class ResType : uint16_t
{
Null = 0x0000,
StringPool = 0x0001,
XML = 0x0003,
NamespaceStart = 0x0100,
NamespaceEnd = 0x0101,
StartElement = 0x0102,
EndElement = 0x0103,
CDATA = 0x0104,
ResourceMap = 0x0180,
};
/**
* Header that appears at the front of every data chunk in a resource.
*/
struct ResChunk_header
{
// Type identifier for this chunk. The meaning of this value depends
// on the containing chunk.
ResType type;
// Size of the chunk header (in bytes). Adding this value to
// the address of the chunk allows you to find its associated data
// (if any).
uint16_t headerSize;
// Total size of this chunk (in bytes). This is the chunkSize plus
// the size of any data associated with the chunk. Adding this value
// to the chunk allows you to completely skip its contents (including
// any child chunks). If this value is the same as chunkSize, there is
// no data associated with the chunk.
uint32_t size;
};
/**
* Reference to a string in a string pool.
*/
struct ResStringPool_ref
{
// Index into the string pool table (uint32_t-offset from the indices
// immediately after ResStringPool_header) at which to find the location
// of the string data in the pool.
uint32_t index;
};
/**
* Representation of a value in a resource, supplying type
* information.
*/
struct Res_value
{
// Number of bytes in this structure.
uint16_t size;
// Always set to 0.
uint8_t res0;
// Type of the data value.
enum class DataType : uint8_t
{
// The 'data' holds an index into the containing resource table's
// global value string pool.
String = 0x03,
// The 'data' is either 0 or 1, for input "false" or "true" respectively.
Boolean = 0x12,
} dataType;
// The data for this item, as interpreted according to dataType.
uint32_t data;
};
/**
* Definition for a pool of strings. The data of this chunk is an
* array of uint32_t providing indices into the pool, relative to
* stringsStart. At stringsStart are all of the UTF-16 strings
* concatenated together; each starts with a uint16_t of the string's
* length and each ends with a 0x0000 terminator. If a string is >
* 32767 characters, the high bit of the length is set meaning to take
* those 15 bits as a high word and it will be followed by another
* uint16_t containing the low word.
*
* If styleCount is not zero, then immediately following the array of
* uint32_t indices into the string table is another array of indices
* into a style table starting at stylesStart. Each entry in the
* style table is an array of ResStringPool_span structures.
*/
struct ResStringPool_header
{
struct ResChunk_header header;
// Number of strings in this pool (number of uint32_t indices that follow
// in the data).
uint32_t stringCount;
// Number of style span arrays in the pool (number of uint32_t indices
// follow the string indices).
uint32_t styleCount;
// Flags.
enum StringFlags : uint32_t
{
// If set, the string index is sorted by the string values (based
// on strcmp16()).
SORTED_FLAG = 1 << 0,
// String pool is encoded in UTF-8
UTF8_FLAG = 1 << 8
};
StringFlags flags;
// Index from header of the string data.
uint32_t stringsStart;
// Index from header of the style data.
uint32_t stylesStart;
};
/**
* Basic XML tree node. A single item in the XML document. Extended info
* about the node can be found after header.headerSize.
*/
struct ResXMLTree_node
{
struct ResChunk_header header;
// Line number in original source file at which this element appeared.
uint32_t lineNumber;
// Optional XML comment that was associated with this element; -1 if none.
ResStringPool_ref comment;
};
/**
* Extended XML tree node for namespace start/end nodes.
* Appears header.headerSize bytes after a ResXMLTree_node.
*/
struct ResXMLTree_namespaceExt
{
// The prefix of the namespace.
struct ResStringPool_ref prefix;
// The URI of the namespace.
struct ResStringPool_ref uri;
};
/**
* Extended XML tree node for element start/end nodes.
* Appears header.headerSize bytes after a ResXMLTree_node.
*/
struct ResXMLTree_endElementExt
{
// String of the full namespace of this element.
struct ResStringPool_ref ns;
// String name of this node if it is an ELEMENT; the raw
// character data if this is a CDATA node.
struct ResStringPool_ref name;
};
/**
* Extended XML tree node for start tags -- includes attribute
* information.
* Appears header.headerSize bytes after a ResXMLTree_node.
*/
struct ResXMLTree_attrExt
{
// String of the full namespace of this element.
struct ResStringPool_ref ns;
// String name of this node if it is an ELEMENT; the raw
// character data if this is a CDATA node.
struct ResStringPool_ref name;
// Byte offset from the start of this structure where the attributes start.
uint16_t attributeStart;
// Size of the ResXMLTree_attribute structures that follow.
uint16_t attributeSize;
// Number of attributes associated with an ELEMENT. These are
// available as an array of ResXMLTree_attribute structures
// immediately following this node.
uint16_t attributeCount;
// Index (1-based) of the "id" attribute. 0 if none.
uint16_t idIndex;
// Index (1-based) of the "class" attribute. 0 if none.
uint16_t classIndex;
// Index (1-based) of the "style" attribute. 0 if none.
uint16_t styleIndex;
};
struct ResXMLTree_attribute
{
// Namespace of this attribute.
struct ResStringPool_ref ns;
// Name of this attribute.
struct ResStringPool_ref name;
// The original raw string value of this attribute.
struct ResStringPool_ref rawValue;
// Processesd typed value of this attribute.
struct Res_value typedValue;
};
/**
* Extended XML tree node for CDATA tags -- includes the CDATA string.
* Appears header.headerSize bytes after a ResXMLTree_node.
*/
struct ResXMLTree_cdataExt
{
// The raw CDATA character data.
struct ResStringPool_ref data;
// The typed value of the character data if this is a CDATA node.
struct Res_value typedData;
};
+2
View File
@@ -93,6 +93,7 @@ set(sources
android/android_patch.cpp
android/android_tools.cpp
android/android_utils.cpp
android/android_manifest.cpp
android/android.h
android/android_utils.h
android/jdwp.h
@@ -156,6 +157,7 @@ set(sources
3rdparty/jpeg-compressor/jpgd.h
3rdparty/jpeg-compressor/jpge.cpp
3rdparty/jpeg-compressor/jpge.h
3rdparty/android/android_manifest.h
3rdparty/catch/catch.cpp
3rdparty/catch/catch.hpp
3rdparty/pugixml/pugixml.cpp
+639
View File
@@ -0,0 +1,639 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2018 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "3rdparty/android/android_manifest.h"
#include "core/core.h"
#include "strings/string_utils.h"
#include "android_utils.h"
const uint32_t debuggableResourceId = 0x0101000f;
const uint32_t addingStringIndex = 0x8b8b8b8b;
namespace Android
{
std::string GetStringPoolValue(ResStringPool_header *stringpool, ResStringPool_ref ref)
{
byte *base = (byte *)stringpool;
uint32_t stringCount = stringpool->stringCount;
uint32_t *stringOffsets = (uint32_t *)(base + stringpool->header.headerSize);
byte *stringData = base + stringpool->stringsStart;
if(ref.index == ~0U)
return "";
if(ref.index >= stringCount)
return "__invalid_string__";
byte *strdata = stringData + stringOffsets[ref.index];
// strdata now points at len characters of string. Check if it's UTF-8 or UTF-16
if((stringpool->flags & ResStringPool_header::UTF8_FLAG) == 0)
{
uint16_t *str = (uint16_t *)strdata;
uint32_t len = *(str++);
// see comment above ResStringPool_header - if high bit is set, then this string is >32767
// characters, so it's followed by another uint16_t with the low word
if(len & 0x8000)
{
len &= 0x7fff;
len <<= 16;
len |= *(str++);
}
std::wstring wstr;
// wchar_t isn't always 2 bytes, so we iterate over the uint16_t and cast.
for(uint32_t i = 0; i < len; i++)
wstr.push_back(wchar_t(str[i]));
return StringFormat::Wide2UTF8(wstr);
}
else
{
byte *str = (byte *)strdata;
uint32_t len = *(str++);
// the length works similarly for UTF-8 data but with single bytes instead of uint16s.
if(len & 0x80)
{
len &= 0x7f;
len <<= 8;
len |= *(str++);
}
// the length is encoded twice. I can only assume to preserve uint16 size although I don't see
// why that would be necessary - it can't be fully backwards compatible even with the alignment
// except with readers that ignore the length entirely and look for trailing NULLs.
if(len < 0x80)
str++;
else
str += 2;
return std::string((char *)str, (char *)(str + len));
}
}
void ShiftStringPoolValue(ResStringPool_ref &ref, uint32_t insertedLocation)
{
// if we found our added attribute, then set the index here (otherwise we'd remap it with the
// others!)
if(ref.index == addingStringIndex)
ref.index = insertedLocation;
else if(ref.index != ~0U && ref.index >= insertedLocation)
ref.index++;
}
void ShiftStringPoolValue(Res_value &val, uint32_t insertedLocation)
{
if(val.dataType == Res_value::DataType::String && val.data >= insertedLocation)
val.data++;
}
template <typename T>
void InsertBytes(std::vector<byte> &bytes, byte *pos, const T &data)
{
byte *start = &bytes[0];
byte *byteData = (byte *)&data;
size_t offs = pos - start;
bytes.insert(bytes.begin() + offs, byteData, byteData + sizeof(T));
}
template <>
void InsertBytes(std::vector<byte> &bytes, byte *pos, const std::vector<byte> &data)
{
byte *start = &bytes[0];
size_t offs = pos - start;
bytes.insert(bytes.begin() + offs, data.begin(), data.end());
}
bool PatchManifest(std::vector<byte> &manifestBytes)
{
// Whether to insert a new string & resource ID at the start or end of the resource map table. I
// can't find anything that indicates there is any required ordering to these, so either should be
// valid.
const bool insertStringAtStart = false;
// reserve room for our modifications up front, to be sure that if we do make them we'll never
// invalidate any pointers. We could add:
manifestBytes.reserve(
manifestBytes.size() +
// - a string (uint32 offset, uint16 length and string characters (possibly
// in UTF-16) including NULL)
sizeof(uint32_t) + sizeof(uint16_t) + sizeof("debuggable") * 2 +
// - a resource ID mapping (one uint32)
sizeof(uint32_t) +
// - an attribute (ResXMLTree_attribute)
sizeof(ResXMLTree_attribute) +
// and we add 16 bytes more just for a safety margin with any necessary padding
16);
// save the capacity so we can check we never resize
size_t capacity = manifestBytes.capacity();
byte *start = &manifestBytes[0];
byte *end = start + manifestBytes.size();
byte *cur = start;
ResChunk_header *xmlroot = (ResChunk_header *)cur;
if((byte *)(xmlroot + 1) > end)
{
RDCERR("Manifest is truncated, %zu bytes doesn't contain full XML header", manifestBytes.size());
return false;
}
if(xmlroot->type != ResType::XML)
{
RDCERR("XML Header is malformed, type is %u expected %u", xmlroot->type, ResType::XML);
return false;
}
if(xmlroot->headerSize != sizeof(*xmlroot))
{
RDCERR("XML Header is malformed, header size is reported as %u but expected %u",
xmlroot->headerSize, sizeof(*xmlroot));
return false;
}
// this isn't necessarily fatal, but it is unexpected.
if(xmlroot->size != manifestBytes.size())
RDCWARN("XML header is malformed, size is reported as %u but %zu bytes found", xmlroot->size,
manifestBytes.size());
cur += xmlroot->headerSize;
ResStringPool_header *stringpool = (ResStringPool_header *)cur;
if(stringpool->header.type != ResType::StringPool)
{
RDCERR("Manifest format is unsupported, expected string pool but got %u",
stringpool->header.type);
return false;
}
if(stringpool->header.headerSize != sizeof(*stringpool))
{
RDCERR("String pool is malformed, header size is reported as %u but expected %u",
stringpool->header.headerSize, sizeof(*stringpool));
return false;
}
if(cur + stringpool->header.size > end)
{
RDCERR("String pool is truncated, expected %u more bytes but only have %u",
stringpool->header.size, uint32_t(end - cur));
return false;
}
cur += stringpool->header.size;
ResChunk_header *resMap = (ResChunk_header *)cur;
if(resMap->type != ResType::ResourceMap)
{
RDCERR("Manifest format is unsupported, expected resource table but got %u", resMap->type);
return false;
}
if(resMap->headerSize != sizeof(*resMap))
{
RDCERR("Resource map is malformed, header size is reported as %u but expected %u",
resMap->headerSize, sizeof(*resMap));
return false;
}
if(cur + resMap->size > end)
{
RDCERR("Resource map is truncated, expected %u more bytes but only have %u", resMap->size,
uint32_t(end - cur));
return false;
}
uint32_t *resourceMapping = (uint32_t *)(cur + resMap->headerSize);
uint32_t resourceMappingCount = (resMap->size - resMap->headerSize) / sizeof(uint32_t);
cur += resMap->size;
bool stringAdded = false;
// now chunks will come along. There will likely first be a namespace begin, then XML tag open and
// close. Since the <application> tag is only valid in one place in the XML we can just continue
// iterating until we find it - we don't actually need to care about the structure of the XML
// since we are identifying a unique tag and adding one attribute.
while(cur < end)
{
ResChunk_header *node = (ResChunk_header *)cur;
if(node->type != ResType::StartElement)
{
cur += node->size;
continue;
}
ResXMLTree_attrExt *startElement = (ResXMLTree_attrExt *)(cur + node->headerSize);
std::string name = GetStringPoolValue(stringpool, startElement->name);
if(name != "application")
{
cur += node->size;
continue;
}
// found the application tag! Now search its attribtues to see if it already has a debuggable
// attribute (that might be set explicitly to false instead of defaulting)
if(startElement->attributeSize != sizeof(ResXMLTree_attribute))
{
RDCWARN("Declared attribute size %u doesn't match what we expect %zu",
startElement->attributeSize, sizeof(ResXMLTree_attribute));
}
if(startElement->attributeStart != sizeof(*startElement))
{
RDCWARN("Declared attribute start offset %u doesn't match what we expect %zu",
startElement->attributeStart, sizeof(*startElement));
}
byte *attributesStart = cur + node->headerSize + startElement->attributeStart;
bool found = false;
for(uint32_t i = 0; i < startElement->attributeCount; i++)
{
ResXMLTree_attribute *attribute =
(ResXMLTree_attribute *)(attributesStart + startElement->attributeSize * i);
std::string attr = GetStringPoolValue(stringpool, attribute->name);
if(attr != "debuggable")
continue;
uint32_t resourceId = 0;
if(attribute->name.index < resourceMappingCount)
{
resourceId = resourceMapping[attribute->name.index];
}
else
{
RDCWARN("Found debuggable attribute, but it's not linked to any resource ID");
if(attribute->typedValue.dataType != Res_value::DataType::Boolean)
{
RDCERR("Found debuggable attribute that isn't boolean typed! Not modifying");
return false;
}
else
{
RDCDEBUG("Setting non-resource ID debuggable attribute to true");
attribute->typedValue.data = ~0U;
if(attribute->rawValue.index != ~0U)
{
RDCWARN("attribute has raw value '%s' which we aren't patching",
GetStringPoolValue(stringpool, attribute->rawValue).c_str());
}
// we'll still add a debuggable attribute that is resource ID linked, so we don't mark the
// attribute as found and break out of the loop yet
continue;
}
}
if(resourceId != debuggableResourceId)
{
RDCERR(
"Found debuggable attribute mapped to resource %x, not %x as we expect! Not modifying",
resourceId, debuggableResourceId);
return false;
}
RDCDEBUG("Found debuggable attribute.");
if(attribute->typedValue.dataType != Res_value::DataType::Boolean)
{
RDCERR("Found debuggable attribute that isn't boolean typed! Not modifying");
return false;
}
else
{
RDCDEBUG("Setting resource ID debuggable attribute to true");
attribute->typedValue.data = ~0U;
if(attribute->rawValue.index != ~0U)
{
RDCWARN("attribute has raw value '%s' which we aren't patching",
GetStringPoolValue(stringpool, attribute->rawValue).c_str());
}
}
found = true;
break;
}
if(found)
break;
if(startElement->attributeSize != sizeof(ResXMLTree_attribute))
{
RDCERR("Unexpected attribute size %u, can't add missing attribute",
startElement->attributeSize);
return false;
}
// default to an invalid value (the manifest would have to be GBs to have this as a valid string
// index.
// If we don't find the existing string to use, then this will be remapped below when we're
// remapping all the other indices.
ResStringPool_ref stringIndex = {addingStringIndex};
// we didn't find the attribute, so we need to search for the appropriate string, add it if not
// there, and add the attribute.
for(uint32_t i = 0; i < resourceMappingCount; i++)
{
if(resourceMapping[i] == debuggableResourceId)
{
std::string str = GetStringPoolValue(stringpool, {i});
if(str != "debuggable")
{
RDCWARN("Found debuggable resource ID, but it was linked to string '%s' not 'debuggable'",
str.c_str());
continue;
}
stringIndex = {i};
}
}
// declare the debuggable attribute
ResXMLTree_attribute debuggable;
debuggable.ns.index = ~0U;
debuggable.name = stringIndex;
debuggable.rawValue.index = ~0U;
debuggable.typedValue.size = sizeof(Res_value);
debuggable.typedValue.res0 = 0;
debuggable.typedValue.dataType = Res_value::DataType::Boolean;
debuggable.typedValue.data = ~0U;
// search the stringpool for the schema, it should be there already.
for(uint32_t i = 0; i < stringpool->stringCount; i++)
{
std::string val = GetStringPoolValue(stringpool, {i});
if(val == "http://schemas.android.com/apk/res/android")
{
debuggable.ns.index = i;
break;
}
}
if(debuggable.ns.index == ~0U)
RDCWARN("Couldn't find android schema, declaring attribute without schema");
// it seems the attribute must be added so that the attributes are sorted in resource ID order.
// We assume the attributes are already sorted according to this order, so we insert at the
// index of the first attribute we encounter with either no resource ID (i.e. if we only
// encountered lower resource IDs then we hit a non-resource ID attribute), or a higher resource
// ID than ours (in which case we're inserting it in the right place).
uint32_t attributeInsertIndex = 0;
for(uint32_t i = 0; i < startElement->attributeCount; i++)
{
ResXMLTree_attribute *attr =
(ResXMLTree_attribute *)(attributesStart + startElement->attributeSize * i);
if(attr->name.index >= resourceMappingCount)
{
attributeInsertIndex = i;
RDCDEBUG("Inserting attribute before %s, with no resource ID",
GetStringPoolValue(stringpool, attr->name).c_str());
break;
}
uint32_t resourceId = resourceMapping[attr->name.index];
if(resourceId >= debuggableResourceId)
{
attributeInsertIndex = i;
RDCDEBUG("Inserting attribute before %s, with resource ID %x",
GetStringPoolValue(stringpool, attr->name).c_str(), resourceId);
break;
}
RDCDEBUG("Skipping past attribute %s, with resource ID %x",
GetStringPoolValue(stringpool, attr->name).c_str(), resourceId);
}
InsertBytes(manifestBytes, attributesStart + startElement->attributeSize * attributeInsertIndex,
debuggable);
// update header
startElement->attributeCount++;
node->size += sizeof(ResXMLTree_attribute);
stringAdded = (stringIndex.index == addingStringIndex);
break;
}
// if we added the string, we need to update the string pool and resource map, then finally update
// all stringrefs in the nodes. We do this in reverse order so that we don't invalidate pointers
// with insertions
if(stringAdded)
{
uint32_t insertIdx = insertStringAtStart ? 0 : resourceMappingCount;
// add to the resource map
{
if(insertIdx == 0)
InsertBytes(manifestBytes, (byte *)resMap + resMap->headerSize, debuggableResourceId);
else
InsertBytes(manifestBytes, (byte *)resMap + resMap->size, debuggableResourceId);
resMap->size += sizeof(uint32_t);
}
// add to the string pool
{
// add the offset
stringpool->header.size += sizeof(uint32_t);
stringpool->stringCount++;
stringpool->stringsStart += sizeof(uint32_t);
// if we're adding a string we don't bother to do it sorted, so remove the sorted flag
stringpool->flags =
ResStringPool_header::StringFlags(stringpool->flags & ~ResStringPool_header::SORTED_FLAG);
byte *base = (byte *)stringpool;
uint32_t *stringOffsets = (uint32_t *)(base + stringpool->header.headerSize);
// we duplicate the offset at the position we're inserting. Then when we fix up all the other
// offsets the duplicated one shifts by the right amount.
InsertBytes(manifestBytes,
(byte *)stringpool + stringpool->header.headerSize + sizeof(uint32_t) * insertIdx,
stringOffsets[insertIdx]);
uint32_t shift = 0;
byte *stringData = (byte *)stringpool + stringpool->stringsStart;
// insert the string, with length prefix and trailing NULL
if(stringpool->flags & ResStringPool_header::UTF8_FLAG)
{
std::vector<byte> bytes = {0xA, 0xA, 'd', 'e', 'b', 'u', 'g', 'g', 'a', 'b', 'l', 'e', 0};
shift = (uint32_t)bytes.size();
InsertBytes(manifestBytes, stringData + stringOffsets[insertIdx], bytes);
}
else
{
std::vector<byte> bytes = {0xA, 0x0, 'd', 0, 'e', 0, 'b', 0, 'u', 0, 'g', 0,
'g', 0, 'a', 0, 'b', 0, 'l', 0, 'e', 0, 0, 0};
shift = (uint32_t)bytes.size();
InsertBytes(manifestBytes, stringData + stringOffsets[insertIdx], bytes);
}
// account for added string
stringpool->header.size += shift;
// shift all the offsets *after* the string we inserted (we inserted precisely at that
// offset).
for(uint32_t i = insertIdx + 1; i < stringpool->stringCount; i++)
stringOffsets[i] += shift;
// if the stringpool isn't integer aligned, add padding bytes
uint32_t alignedSize = AlignUp4(stringpool->header.size);
if(alignedSize > stringpool->header.size)
{
uint32_t paddingLen = alignedSize - stringpool->header.size;
RDCDEBUG("Inserting %u padding bytes to align %u up to %u", paddingLen,
stringpool->header.size, alignedSize);
InsertBytes(manifestBytes, base + stringpool->header.size,
std::vector<byte>((size_t)paddingLen, 0));
stringpool->header.size += paddingLen;
}
}
// now iterate over all nodes and fixup any stringrefs pointing after our insert point
cur = start + xmlroot->headerSize;
// skip string pool
cur += ((ResChunk_header *)cur)->size;
// skip resource map
cur += ((ResChunk_header *)cur)->size;
while(cur < end)
{
ResXMLTree_node *node = (ResXMLTree_node *)cur;
if(node->header.headerSize != sizeof(*node))
RDCWARN("Headersize was reported as %u, but we expected ResXMLTree_node size %zu",
node->header.headerSize, sizeof(*node));
ShiftStringPoolValue(node->comment, insertIdx);
switch(node->header.type)
{
// namespace start and end are identical
case ResType::NamespaceStart:
case ResType::NamespaceEnd:
{
ResXMLTree_namespaceExt *ns = (ResXMLTree_namespaceExt *)(cur + node->header.headerSize);
ShiftStringPoolValue(ns->prefix, insertIdx);
ShiftStringPoolValue(ns->uri, insertIdx);
break;
}
case ResType::EndElement:
{
ResXMLTree_endElementExt *endElement =
(ResXMLTree_endElementExt *)(cur + node->header.headerSize);
ShiftStringPoolValue(endElement->ns, insertIdx);
ShiftStringPoolValue(endElement->name, insertIdx);
break;
}
case ResType::CDATA:
{
ResXMLTree_cdataExt *cdata = (ResXMLTree_cdataExt *)(cur + node->header.headerSize);
ShiftStringPoolValue(cdata->data, insertIdx);
ShiftStringPoolValue(cdata->typedData, insertIdx);
break;
}
case ResType::StartElement:
{
ResXMLTree_attrExt *startElement = (ResXMLTree_attrExt *)(cur + node->header.headerSize);
ShiftStringPoolValue(startElement->ns, insertIdx);
ShiftStringPoolValue(startElement->name, insertIdx);
// update attributes
byte *attributesStart = cur + node->header.headerSize + startElement->attributeStart;
for(uint32_t i = 0; i < startElement->attributeCount; i++)
{
ResXMLTree_attribute *attr =
(ResXMLTree_attribute *)(attributesStart + startElement->attributeSize * i);
ShiftStringPoolValue(attr->ns, insertIdx);
ShiftStringPoolValue(attr->name, insertIdx);
ShiftStringPoolValue(attr->rawValue, insertIdx);
ShiftStringPoolValue(attr->typedValue, insertIdx);
}
break;
}
default:
RDCERR("Unhandled chunk %x, can't patch stringpool references", node->header.type);
return false;
}
cur += node->header.size;
}
}
xmlroot->size = (uint32_t)manifestBytes.size();
if(manifestBytes.capacity() > capacity)
{
RDCERR(
"manifest vector resized during patching! Update reserve() at the start of "
"Android::PatchManifest");
}
return true;
}
};
+197 -6
View File
@@ -23,6 +23,7 @@
******************************************************************************/
#include <sstream>
#include "3rdparty/miniz/miniz.h"
#include "api/replay/version.h"
#include "core/core.h"
#include "strings/string_utils.h"
@@ -87,6 +88,86 @@ bool RemoveAPKSignature(const string &apk)
return true;
}
bool ExtractAndRemoveManifest(const std::string &apk, std::vector<byte> &manifest)
{
// pull out the manifest with miniz
mz_zip_archive zip;
RDCEraseEl(zip);
mz_bool b = mz_zip_reader_init_file(&zip, apk.c_str(), 0);
if(b)
{
mz_uint numfiles = mz_zip_reader_get_num_files(&zip);
for(mz_uint i = 0; i < numfiles; i++)
{
mz_zip_archive_file_stat zstat;
mz_zip_reader_file_stat(&zip, i, &zstat);
if(!strcmp(zstat.m_filename, "AndroidManifest.xml"))
{
size_t sz = 0;
byte *buf = (byte *)mz_zip_reader_extract_to_heap(&zip, i, &sz, 0);
RDCLOG("Got manifest of %zu bytes", sz);
manifest.insert(manifest.begin(), buf, buf + sz);
}
}
}
else
{
RDCERR("Couldn't open %s", apk.c_str());
}
mz_zip_reader_end(&zip);
if(manifest.empty())
return false;
std::string aapt = getToolPath(ToolDir::BuildTools, "aapt", false);
RDCDEBUG("Removing AndroidManifest.xml");
execCommand(aapt, "remove \"" + apk + "\" AndroidManifest.xml");
std::string fileList = execCommand(aapt, "list \"" + apk + "\"").strStdout;
std::vector<std::string> files;
split(fileList, files, ' ');
for(const std::string &f : files)
{
if(trim(f) == "AndroidManifest.xml")
{
RDCERR("AndroidManifest.xml found, that means removal failed!");
return false;
}
}
return true;
}
bool AddManifestToAPK(const std::string &apk, const std::string &tmpDir,
const std::vector<byte> &manifest)
{
std::string aapt = getToolPath(ToolDir::BuildTools, "aapt", false);
// write the manifest to disk
FileIO::dump((tmpDir + "AndroidManifest.xml").c_str(), manifest.data(), manifest.size());
// run aapt to add the manifest
Process::ProcessResult result =
execCommand(aapt, "add \"" + apk + "\" AndroidManifest.xml", tmpDir);
if(result.strStdout.empty())
{
RDCERR("Failed to add manifest to APK. STDERR: %s", result.strStderror.c_str());
return false;
}
return true;
}
bool RealignAPK(const string &apk, string &alignedAPK, const string &tmpDir)
{
std::string zipalign = getToolPath(ToolDir::BuildTools, "zipalign", false);
@@ -243,7 +324,10 @@ bool ReinstallPatchedAPK(const string &deviceID, const string &apk, const string
{
RDCLOG("Reinstalling APK");
adbExecCommand(deviceID, "install --abi " + abi + " \"" + apk + "\"", workDir);
if(abi == "null" || abi.empty())
adbExecCommand(deviceID, "install \"" + apk + "\"", workDir);
else
adbExecCommand(deviceID, "install --abi " + abi + " \"" + apk + "\"", workDir);
// Wait until re-install completes
string reinstallResult;
@@ -315,6 +399,37 @@ bool CheckPatchingRequirements()
return true;
}
std::string DetermineInstalledABI(const std::string &deviceID, const std::string &packageName)
{
RDCLOG("Checking installed ABI for %s", packageName.c_str());
string abi;
string dump = adbExecCommand(deviceID, "shell pm dump " + packageName).strStdout;
if(dump.empty())
RDCERR("Unable to pm dump %s", packageName.c_str());
// Walk through the output and look for primaryCpuAbi
std::istringstream contents(dump);
string line;
string prefix("primaryCpuAbi=");
while(std::getline(contents, line))
{
line = trim(line);
if(line.compare(0, prefix.size(), prefix) == 0)
{
// Extract the abi
abi = line.substr(line.find_last_of("=") + 1);
RDCLOG("primaryCpuAbi found: %s", abi.c_str());
break;
}
}
if(abi.empty())
RDCERR("Unable to determine installed abi for: %s", packageName.c_str());
return abi;
}
bool PullAPK(const string &deviceID, const string &pkgPath, const string &apk)
{
RDCLOG("Pulling APK to patch");
@@ -369,10 +484,7 @@ std::string GetFirstMatchingLine(const std::string &haystack, const std::string
size_t needleOffset = haystack.find(needle);
if(needleOffset == std::string::npos)
{
RDCERR("Couldn't get pkgFlags from adb");
return "";
}
size_t nextLine = haystack.find('\n', needleOffset + 1);
@@ -430,6 +542,85 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CheckAndroidPackage(const c
extern "C" RENDERDOC_API AndroidFlags RENDERDOC_CC RENDERDOC_MakeDebuggablePackage(
const char *hostname, const char *packageName, RENDERDOC_ProgressCallback progress)
{
// stub for now
return AndroidFlags::ManifestPatchFailure;
Process::ProcessResult result = {};
std::string package(basename(std::string(packageName)));
int index = 0;
std::string deviceID;
Android::ExtractDeviceIDAndIndex(hostname, index, deviceID);
// make sure progress is valid so we don't have to check it everywhere
if(!progress)
progress = [](float) {};
progress(0.0f);
if(!Android::CheckPatchingRequirements())
return AndroidFlags::MissingTools;
progress(0.02f);
std::string abi = Android::DetermineInstalledABI(deviceID, package);
// Find the APK on the device
std::string pkgPath = Android::GetPathForPackage(deviceID, package) + "base.apk";
std::string tmpDir = FileIO::GetTempFolderFilename();
std::string origAPK(tmpDir + package + ".orig.apk");
std::string alignedAPK(origAPK + ".aligned.apk");
std::vector<byte> manifest;
// Try the following steps, bailing if anything fails
if(!Android::PullAPK(deviceID, pkgPath, origAPK))
return AndroidFlags::ManifestPatchFailure;
progress(0.4f);
if(!Android::RemoveAPKSignature(origAPK))
return AndroidFlags::ManifestPatchFailure;
progress(0.425f);
if(!Android::ExtractAndRemoveManifest(origAPK, manifest))
return AndroidFlags::ManifestPatchFailure;
progress(0.45f);
if(!Android::PatchManifest(manifest))
return AndroidFlags::ManifestPatchFailure;
progress(0.46f);
if(!Android::AddManifestToAPK(origAPK, tmpDir, manifest))
return AndroidFlags::ManifestPatchFailure;
progress(0.475f);
if(!Android::RealignAPK(origAPK, alignedAPK, tmpDir))
return AndroidFlags::RepackagingAPKFailure;
progress(0.5f);
if(!Android::DebugSignAPK(alignedAPK, tmpDir))
return AndroidFlags::RepackagingAPKFailure;
progress(0.525f);
if(!Android::UninstallOriginalAPK(deviceID, packageName, tmpDir))
return AndroidFlags::RepackagingAPKFailure;
progress(0.6f);
if(!Android::ReinstallPatchedAPK(deviceID, alignedAPK, abi, packageName, tmpDir))
return AndroidFlags::RepackagingAPKFailure;
progress(0.95f);
if(!Android::IsDebuggable(deviceID, packageName))
return AndroidFlags::ManifestPatchFailure;
progress(1.0f);
// All clean!
return AndroidFlags::Debuggable;
}
+2
View File
@@ -65,4 +65,6 @@ ABI GetABI(const std::string &abiName);
std::vector<ABI> GetSupportedABIs(const std::string &deviceID);
std::string GetRenderDocPackageForABI(ABI abi);
std::string GetPathForPackage(const std::string &deviceID, const std::string &packageName);
bool PatchManifest(std::vector<byte> &manifest);
};
+2
View File
@@ -105,6 +105,7 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="3rdparty\android\android_manifest.h" />
<ClInclude Include="3rdparty\catch\catch.hpp" />
<ClInclude Include="3rdparty\jpeg-compressor\jpgd.h" />
<ClInclude Include="3rdparty\jpeg-compressor\jpge.h" />
@@ -308,6 +309,7 @@
</ForcedIncludeFiles>
</ClCompile>
<ClCompile Include="android\android.cpp" />
<ClCompile Include="android\android_manifest.cpp" />
<ClCompile Include="android\android_patch.cpp" />
<ClCompile Include="android\android_tools.cpp" />
<ClCompile Include="android\android_utils.cpp" />
+9
View File
@@ -112,6 +112,9 @@
<Filter Include="Android">
<UniqueIdentifier>{f9ed2873-6120-4b34-a3c2-e57b2de31eb9}</UniqueIdentifier>
</Filter>
<Filter Include="3rdparty\android">
<UniqueIdentifier>{6e798cbc-0eae-4f48-9d7c-f3fe58bd32dc}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="maths\camera.h">
@@ -384,6 +387,9 @@
<ClInclude Include="android\jdwp.h">
<Filter>Android</Filter>
</ClInclude>
<ClInclude Include="3rdparty\android\android_manifest.h">
<Filter>3rdparty\android</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="maths\camera.cpp">
@@ -677,6 +683,9 @@
<ClCompile Include="android\jdwp_util.cpp">
<Filter>Android</Filter>
</ClCompile>
<ClCompile Include="android\android_manifest.cpp">
<Filter>Android</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="os\win32\comexport.def">