Android Binary XML support (#18545) ##print

This commit is contained in:
meme 2021-04-08 12:26:55 -05:00 committed by GitHub
parent 3fedf80036
commit b23a063c3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 2061 additions and 2 deletions

View File

@ -75,7 +75,7 @@ static const char *help_msg_p6[] = {
};
static const char *help_msg_pF[] = {
"Usage: pF[apdb]", "[len]", "parse ASN1, PKCS, X509, DER, protobuf",
"Usage: pF[apdbA]", "[len]", "parse ASN1, PKCS, X509, DER, protobuf, axml",
"pFa", "[len]", "decode ASN1 from current block",
"pFaq", "[len]", "decode ASN1 from current block (quiet output)",
"pFb", "[len]", "decode raw proto buffers.",
@ -83,6 +83,7 @@ static const char *help_msg_pF[] = {
"pFo", "[len]", "decode ASN1 OID",
"pFp", "[len]", "decode PKCS7",
"pFx", "[len]", "Same with X509",
"pFA", "[len]", "decode Android Binary XML from current block",
NULL
};
@ -1270,6 +1271,17 @@ static void cmd_print_fromage(RCore *core, const char *input, const ut8* data, i
}
}
break;
case 'A': // "pFA"
{
char *s = r_axml_decode (data, size);
if (s) {
r_cons_printf ("%s", s);
free (s);
} else {
eprintf ("Malformed object: did you supply enough data?\ntry to change the block size (see b?)\n");
}
}
break;
default:
case '?': // "pF?"
r_core_cmd_help (core, help_msg_pF);

View File

@ -77,6 +77,7 @@ int gettimeofday (struct timeval* p, void* tz);
#include "r_util/r_pkcs7.h"
#include "r_util/r_protobuf.h"
#include "r_util/r_big.h"
#include "r_util/r_axml.h"
// requires io, core, ... #include "r_util/r_print.h"
#ifdef __cplusplus

View File

@ -0,0 +1,16 @@
/* radare - LGPL - Copyright 2021 - keegan */
#ifndef R_AXML_H
#define R_AXML_H
#ifdef __cplusplus
extern "C" {
#endif
R_API char *r_axml_decode(const ut8* buffer, const ut64 size);
#ifdef __cplusplus
}
#endif
#endif // R_AXML_H

View File

@ -22,7 +22,7 @@ OBJS+=utf8.o utf16.o utf32.o strbuf.o lib.o name.o spaces.o signal.o syscmd.o
OBJS+=udiff.o bdiff.o stack.o queue.o tree.o idpool.o assert.o
OBJS+=punycode.o pkcs7.o x509.o asn1.o astr.o json_parser.o json_indent.o skiplist.o
OBJS+=pj.o rbtree.o intervaltree.o qrcode.o vector.o skyline.o str_constpool.o str_trim.o
OBJS+=ascii_table.o protobuf.o graph_drawable.o
OBJS+=ascii_table.o protobuf.o graph_drawable.o axml.o
ifeq (${HAVE_LIB_GMP},1)
OBJS+=big-gmp.o

437
libr/util/axml.c Normal file
View File

@ -0,0 +1,437 @@
/* radare2 - LGPL - Copyright 2021 - keegan */
#include <string.h>
#include <r_types.h>
#include <r_core.h>
#include "axml_resources.h"
enum {
TYPE_STRING_POOL = 0x0001,
TYPE_XML = 0x0003,
TYPE_START_NAMESPACE = 0x100,
TYPE_END_NAMESPACE = 0x101,
TYPE_START_ELEMENT = 0x0102,
TYPE_END_ELEMENT = 0x103,
TYPE_RESOURCE_MAP = 0x180,
};
enum {
RESOURCE_NULL = 0x00,
RESOURCE_REFERENCE = 0x01,
RESOURCE_STRING = 0x03,
RESOURCE_FLOAT = 0x04,
RESOURCE_INT_DEC = 0x10,
RESOURCE_INT_HEX = 0x11,
RESOURCE_BOOL = 0x12,
};
enum {
FLAG_UTF8 = 1 << 8,
};
// Beginning of every header
R_PACKED (
typedef struct {
ut16 type;
ut16 header_size;
ut32 size;
}) chunk_header_t;
// String pool referenced throughout the Binary XML, there must only be ONE
R_PACKED (
typedef struct {
ut32 string_count;
ut32 style_count;
ut32 flags;
ut32 strings_offset;
ut32 styles_offset;
ut32 offsets[];
}) string_pool_t;
R_PACKED (
typedef struct {
ut16 size;
ut8 unused;
ut8 type;
union {
ut32 d;
float f;
} data;
}) resource_value_t;
R_PACKED (
typedef struct {
ut32 namespace;
ut32 name;
ut32 unused;
resource_value_t value;
}) attribute_t;
R_PACKED (
typedef struct {
ut32 line;
ut32 comment;
ut32 namespace;
ut32 name;
ut32 flags;
ut16 attribute_count;
ut16 unused0;
ut16 unused1;
ut16 unused2;
attribute_t attributes[];
}) start_element_t;
R_PACKED (
typedef struct {
ut32 line;
ut32 comment;
ut32 namespace;
ut32 name;
}) end_element_t;
R_PACKED (
typedef struct {
ut32 line;
ut32 comment;
ut32 prefix;
ut32 uri;
}) namespace_t;
static char *string_lookup(string_pool_t *pool, const ut8 *data, ut64 data_size, ut32 i, size_t *length) {
if (i > r_read_le32 (&pool->string_count)) {
return NULL;
}
ut32 offset = r_read_le32 (&pool->offsets[i]);
ut8 *start = (ut8 *)((uintptr_t)data + r_read_le32 (&pool->strings_offset) + 8 + offset);
char *name = NULL;
if (pool->flags & FLAG_UTF8) {
if ((ut64)start > (ut64)data + data_size - sizeof(ut16)) {
return NULL;
}
// Ignore UTF-16LE encoded length
ut32 n = *start++;
if (n & 0x80) {
n = ((n & 0x7f) << 8) | *start++;
}
if ((ut64)start > (ut64)data + data_size - sizeof(ut16)) {
return NULL;
}
// UTF-8 encoded length
n = *start++;
if (n & 0x80) {
n = ((n & 0x7f) << 8) | *start++;
}
name = calloc (n + 1, 1);
if (n == 0) {
if (length) {
*length = 0;
}
return name;
}
if ((ut64)start > (ut64)data + data_size - sizeof(ut32) - n - 1) {
free (name);
return NULL;
}
memcpy (name, start, n);
if (length) {
*length = n;
}
} else {
if ((ut64)start > (ut64)data + data_size - sizeof(ut32)) {
return NULL;
}
ut16 *start16 = (ut16 *)start;
// If >0x7fff, stored as a big-endian ut32
ut32 n = r_read_le16 (start16++);
if (n & 0x8000) {
n |= ((n & 0x7fff) << 16) | r_read_le16 (start16++);
}
// Size of UTF-16LE without NULL
n *= 2;
name = calloc (n * 2 + 1, 1);
if ((ut64)start16 > (ut64)data + data_size - sizeof(ut32) - n - 1) {
free (name);
return NULL;
}
// If UTF-16LE, decode to UTF-8 so we can print it to the screen
if (r_str_utf16_to_utf8 ((ut8 *)name, n * 2, (const ut8 *)start16, n, true) < 0) {
free (name);
eprintf ("Failed to decode UTF16-LE\n");
return NULL;
}
if (length) {
*length = n;
}
}
return name;
}
static char *resource_value(string_pool_t *pool, const ut8 *data, ut64 data_size,
resource_value_t *value) {
switch (value->type) {
case RESOURCE_NULL:
return r_str_new ("");
case RESOURCE_REFERENCE:
return r_str_newf ("@0x%x", value->data.d);
case RESOURCE_STRING:
return string_lookup (pool, data, data_size, r_read_le32 (&value->data.d), NULL);
case RESOURCE_FLOAT:
return r_str_newf ("%f", value->data.f);
case RESOURCE_INT_DEC:
return r_str_newf ("%d", value->data.d);
case RESOURCE_INT_HEX:
return r_str_newf ("0x%x", value->data.d);
case RESOURCE_BOOL:
return r_str_newf (value->data.d ? "true" : "false");
default:
eprintf ("Resource type is not recognized: %#x\n", value->type);
break;
}
return r_str_new ("null");
}
static bool dump_element(RStrBuf *sb, string_pool_t *pool, namespace_t *namespace,
const ut8 *data, ut64 data_size, start_element_t *element,
const ut32 *resource_map, ut32 resource_map_length, st32 *depth, bool start) {
ut32 i;
char *name = string_lookup (pool, data, data_size, r_read_le32 (&element->name), NULL);
for (i = 0; i < *depth; i++) {
r_strbuf_appendf (sb, "\t");
}
if (start) {
r_strbuf_appendf (sb, "<%s", name);
ut16 count = r_read_le16 (&element->attribute_count);
if (*depth == 0 && namespace) {
char *key = string_lookup (pool, data, data_size, namespace->prefix, NULL);
char *value = string_lookup (pool, data, data_size, namespace->uri, NULL);
r_strbuf_appendf (sb, " xmlns:%s=\"%s\"", key, value);
free (key);
free (value);
}
if (count != 0) {
r_strbuf_appendf (sb, " ");
}
for (i = 0; i < count; i++) {
attribute_t a = element->attributes[i];
ut32 key_index = r_read_le32 (&a.name);
const char *key = (const char *)string_lookup (pool, data, data_size, key_index, NULL);
bool resource_key = false;
// If the key is empty, it is a cached resource name
if (key && *key == '\0') {
free ((char *)key);
resource_key = true;
if (resource_map && key_index < resource_map_length) {
ut32 resource = r_read_le32 (&resource_map[key_index]);
if (resource >= 0x1010000) {
resource -= 0x1010000;
if (resource < ANDROID_ATTRIBUTE_NAMES_SIZE) {
key = ANDROID_ATTRIBUTE_NAMES[resource];
}
} else {
key = "null";
}
} else {
key = "null";
}
}
char *value = resource_value (pool, data, data_size, &a.value);
// If there is a namespace on the value, and there is an active
// namespace, assume it is the same
if (r_read_le32 (&a.namespace) != -1 && namespace && namespace->prefix != -1) {
char *ns = string_lookup (pool, data, data_size, namespace->prefix, NULL);
r_strbuf_appendf (sb, "%s:%s=\"%s\"", ns, key, value);
free (ns);
} else {
r_strbuf_appendf (sb, "%s=\"%s\"", key, value);
}
if (i != count - 1) {
r_strbuf_appendf (sb, " ");
}
if (!resource_key) {
free ((char *)key);
}
free (value);
}
} else {
r_strbuf_appendf (sb, "</%s", name);
}
r_strbuf_appendf (sb, ">\n");
free (name);
return true;
}
R_API char *r_axml_decode(const ut8 *data, const ut64 data_size) {
string_pool_t *pool = NULL;
namespace_t *namespace = NULL;
const ut32 *resource_map = NULL;
ut32 resource_map_length = 0;
RStrBuf *sb = NULL;
st32 depth = 0;
r_return_val_if_fail (data && data_size, NULL);
RBuffer *buffer = r_buf_new_with_pointers (data, data_size, false);
if (!buffer) {
eprintf ("Error allocating RBuffer\n");
goto error;
}
ut64 offset = 0;
chunk_header_t header = {0};
if (r_buf_fread_at (buffer, offset, (ut8 *)&header, "ssi", 1) != sizeof(header)) {
goto bad;
}
if (header.type != TYPE_XML) {
goto bad;
}
ut64 binary_size = header.size;
if (binary_size > data_size) {
goto bad;
}
offset += sizeof(header);
sb = r_strbuf_new ("");
while (offset < binary_size) {
if (r_buf_fread_at (buffer, offset, (ut8 *)&header, "ssi", 1) != sizeof(header)) {
goto bad;
}
ut16 type = header.type;
// After reading the original chunk header, read the type-specific
// header
offset += sizeof(header);
switch (type) {
case TYPE_STRING_POOL: {
ut16 header_size = header.size;
if (header_size == 0 || header_size > data_size) {
goto bad;
}
pool = malloc (header_size);
if (!pool) {
goto bad;
}
if (r_buf_read_at (buffer, offset, (void *)pool, header_size) != header_size) {
goto bad;
}
} break;
case TYPE_START_ELEMENT: {
// The string pool must be the first header
if (!pool) {
goto bad;
}
ut16 header_size = header.size;
if (header_size == 0 || header_size > data_size) {
goto bad;
}
start_element_t *element = malloc (header_size);
if (!element) {
goto bad;
}
if (r_buf_read_at (buffer, offset, (void *)element, header_size) != header_size) {
free (element);
goto bad;
}
if (!dump_element (sb, pool, namespace, data, data_size, element,
resource_map, resource_map_length, &depth, true)) {
free (element);
goto bad;
}
depth++;
free (element);
} break;
case TYPE_END_ELEMENT: {
depth--;
if (depth < 0) {
goto bad;
}
end_element_t end;
if (r_buf_read_at (buffer, offset, (void *)&end, sizeof(end)) != sizeof(end)) {
goto bad;
}
// The beginning of the start and end element structs
// are the same, so we can use this interchangably
if (!dump_element (sb, pool, namespace, data, data_size, (start_element_t *)&end,
resource_map, resource_map_length, &depth, false)) {
goto bad;
}
} break;
case TYPE_START_NAMESPACE: {
// If there is already a start namespace, override it
free (namespace);
namespace = malloc (sizeof(*namespace));
if (r_buf_fread_at (buffer, offset, (ut8 *)namespace, "iiii", 1) != sizeof(*namespace)) {
goto bad;
}
} break;
case TYPE_END_NAMESPACE:
break;
case TYPE_RESOURCE_MAP:
resource_map = (ut32 *)(data + offset);
resource_map_length = header.size;
if (resource_map_length > data_size - offset) {
goto bad;
}
resource_map_length /= sizeof(ut32);
break;
default:
eprintf ("Type is not recognized: %#x\n", type);
}
offset += header.size - sizeof(header);
}
r_buf_free (buffer);
free (pool);
free (namespace);
return r_strbuf_drain (sb);
bad:
eprintf ("Invalid Android Binary XML\n");
error:
if (buffer)
r_buf_free (buffer);
free (pool);
r_strbuf_free (sb);
return NULL;
}

1592
libr/util/axml_resources.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -44,6 +44,7 @@ r_util_sources = [
'queue.c',
'asn1.c',
'astr.c',
'axml.c',
'pkcs7.c',
'x509.c',
'utype.c',