APK逆向 AXML解析失败2:ResChunkHeader大小报错

769 阅读3分钟

1 问题描述

遇到一个之前没见过的axml格式对抗,使用androguard报错如下

[ERROR   ] androguard.axml: Error parsing resource header: declared header size is smaller than required size of 8! Offset=83208
[ERROR   ] androguard.apk: Error while parsing AndroidManifest.xml - is the file valid?

就是文件中声明的头部大小比最小头部大小要小

不了解axml格式的可以先看别人写的解析:blog.csdn.net/beyond702/a…

2 解析

借用大佬的文件格式图:

image.png

所有的块,都以ResChunkHeader开头,这个头的大小最小为8字节(在安卓源码中定义),结构如下,包含了块的类型、头部的大小、整个块大小;头部除了这个8字节文件头,还能写入一些其他信息,所以最小为8字节

struct ResChunk_header
{
    uint_16 type;
    uint_16 headerSize;
    uint_32 size;
}

这里放androguard包解析的代码,unpack那一句解析了头部,可以看出是按照标准解析的。如果解析出来的头部大小小于8,就会报开头的错误。

# This is the minimal size such a header must have. There might be other header data too!
SIZE = 2 + 2 + 4

def __init__(self, buff, expected_type=None):
    """
    :param androguard.core.bytecode.BuffHandle buff: the buffer set to the position where the header starts.
    :param int expected_type: the type of the header which is expected.
    """
    self.start = buff.get_idx()
    # Make sure we do not read over the buffer:
    if buff.size() < self.start + self.SIZE:
        raise ResParserError("Can not read over the buffer size! Offset={}".format(self.start))

    self._type, self._header_size, self._size = unpack('<HHL', buff.read(self.SIZE))

    if expected_type and self._type != expected_type:
        raise ResParserError("Header type is not equal the expected type: Got 0x{:04x}, wanted 0x{:04x}".format(self._type, expected_type))

    # Assert that the read data will fit into the chunk.
    # The total size must be equal or larger than the header size
    if self._header_size < self.SIZE:
        raise ResParserError(
            "declared header size is smaller than required size of {}! Offset={}".format(self.SIZE, self.start))

之后用010打开axml,定位到开头报错的位置83208,转换为16进制为14508

image.png

发现这个位置是错误的,并不是ResChunkHeader,可以确定是因为解析程序定位块的位置错误导致问题;之后再从开头往后逐一排查各个块(文件头、字符串块、资源id块、命名空间块....)。

仔细查看字符串块,发现了问题,如下图,字符串池的首地址scStringPollOffset为1420,换成16进制为0x58c,这个地址是相对于字符串块首地址的,字符串块前面还有8个字节的axml文件头,所以字符串池的地址为0x594,与010模板里面解析出的STRING_ITEM的首地址一样;但是字符串池前面的字符串偏移数组scStringOffsets,长度却有0x2970,这个字符串偏移数组已经盖过了STRING_ITEM里面的部分字符串了,很明显scStringOffsets的长度被修改过

image.png

再看一段androguard里面的代码,这个是从scStringOffsets开始解析的代码,这里先按照错误的长度(2652)读取字符串偏移,保存到数组。styles这里为0,直接跳过,然后计算字符串池的大小(这个是根据scStringPollOffset计算的,所以是正确的),最后再读取字符串池的数据到缓存。

那么字符串池缓存读取完毕后,此时的文件指针已经到了:8 + 28 + 2652* 4 + (73984-1420) = 83208,与上面报错的地方一致。(8和28分别是axml文件头和字符串块文件头)

        # Next, there is a list of string following.
        # This is only a list of offsets (4 byte each)
        for i in range(self.stringCount):
            self.m_stringOffsets.append(unpack('<I', buff.read(4))[0])

        # And a list of styles
        # again, a list of offsets
        for i in range(self.styleCount):
            self.m_styleOffsets.append(unpack('<I', buff.read(4))[0])

        # FIXME it is probably better to parse n strings and not calculate the size
        size = self.header.size - self.stringsOffset

        # if there are styles as well, we do not want to read them too.
        # Only read them, if no
        if self.stylesOffset != 0 and self.styleCount != 0:
            size = self.stylesOffset - self.stringsOffset

        if (size % 4) != 0:
            log.warning("Size of strings is not aligned by four bytes.")

        self.m_charbuff = buff.read(size)

3 总结

androguard库并没有严格按照标准解析字符串(没有从scStringPollOffset开始解析字符串,而是从scStringOffsets末尾开始解析)导致了解析错误。最后修改源码吧字符串池缓存读取位置改一下,就能正确解析了。于此同时,用jeb打开此问题apk,也会解析不出manifest,不过jeb不是开源的,没法自己解决了。