#823 Added support for Android 10's Dalvik 039 (hidden api annotation)

Open Meir Komet mkomet
Coverage Reach
androguard/core/bytecodes/dvm.py androguard/core/bytecodes/axml/__init__.py androguard/core/bytecodes/axml/types.py androguard/core/bytecodes/apk.py androguard/core/bytecodes/dvm_types.py androguard/core/analysis/analysis.py androguard/core/bytecode.py androguard/core/mutf8.py androguard/core/__init__.py androguard/core/androconf.py androguard/core/api_specific_resources/__init__.py androguard/core/resources/public.py androguard/decompiler/dad/instruction.py androguard/decompiler/dad/opcode_ins.py androguard/decompiler/dad/writer.py androguard/decompiler/dad/dast.py androguard/decompiler/dad/graph.py androguard/decompiler/dad/decompile.py androguard/decompiler/dad/control_flow.py androguard/decompiler/dad/dataflow.py androguard/decompiler/dad/basic_blocks.py androguard/decompiler/dad/node.py androguard/decompiler/dad/util.py androguard/decompiler/decompiler.py androguard/cli/main.py androguard/cli/entry_points.py androguard/cli/__init__.py androguard/session.py androguard/misc.py androguard/util.py androguard/__init__.py tests/test_apk.py tests/test_dex.py tests/test_analysis.py tests/test_axml.py tests/test_entry_points.py tests/dataflow_test.py tests/parse_dex.py tests/rpo_test.py tests/test_arsc.py tests/test_session.py tests/dominator_test.py tests/test_decompiler.py tests/test_types.py tests/test_misc.py tests/test_dexcodeparsing.py tests/test_rename.py tests/test_decompilerjadx.py tests/test_strings.py tests/test_loadorder.py tests/test_annotations.py

No flags found

Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.

e.g., #unittest #integration

#production #enterprise

#frontend #backend

Learn more about Codecov Flags here.


@@ -32,6 +32,7 @@
Loading
32 32
DEX_FILE_MAGIC_36 = b'dex\n036\x00'
33 33
DEX_FILE_MAGIC_37 = b'dex\n037\x00'
34 34
DEX_FILE_MAGIC_38 = b'dex\n038\x00'
35 +
DEX_FILE_MAGIC_39 = b'dex\n039\x00'
35 36
36 37
ODEX_FILE_MAGIC_35 = b'dey\n035\x00'
37 38
ODEX_FILE_MAGIC_36 = b'dey\n036\x00'
@@ -1142,6 +1143,109 @@
Loading
1142 1143
        return length
1143 1144
1144 1145
1146 +
class HiddenApiClassDataItem:
1147 +
    """
1148 +
    This class can parse an hiddenapi_class_data_item of a dex file (from Android 10 dex version 039)
1149 +
1150 +
    :param buff: a string which represents a Buff object of the hiddenapi_class_data_item
1151 +
    :type buff: Buff object
1152 +
    :param cm: a ClassManager object
1153 +
    :type cm: :class:`ClassManager`
1154 +
    """
1155 +
1156 +
    class RestrictionApiFlag(IntEnum):
1157 +
        WHITELIST = 0
1158 +
        GREYLIST = 1
1159 +
        BLACKLIST = 2
1160 +
        GREYLIST_MAX_O = 3
1161 +
        GREYLIST_MAX_P = 4
1162 +
        GREYLIST_MAX_Q = 5
1163 +
        GREYLIST_MAX_R = 6
1164 +
1165 +
    class DomapiApiFlag(IntEnum):
1166 +
        NONE = 0
1167 +
        CORE_PLATFORM_API = 1
1168 +
        TEST_API = 2
1169 +
1170 +
    def __init__(self, buff, cm):
1171 +
        self.CM = cm
1172 +
1173 +
        self.offset = buff.get_idx()
1174 +
1175 +
        self.section_size, = cm.packer["I"].unpack(buff.read(4))
1176 +
1177 +
        # Find the end of the offsets array (first non-zero offset entry is the start of `flags`)
1178 +
        offsets_size = 0
1179 +
        i = 0
1180 +
        while buff.get_idx() - self.offset < self.section_size:
1181 +
            if offsets_size != 0 and i >= offsets_size:
1182 +
                break
1183 +
            offset, = cm.packer["I"].unpack(buff.read(4))
1184 +
            if offset != 0 and offsets_size == 0:
1185 +
                offsets_size = (offset - 4) // 4
1186 +
            i += 1
1187 +
1188 +
        self.flags = []
1189 +
        for i in range(offsets_size):
1190 +
            flag = readuleb128(cm, buff)
1191 +
            self.flags.append((
1192 +
                self.RestrictionApiFlag(flag & 0b111),
1193 +
                self.DomapiApiFlag(flag >> 3)))
1194 +
1195 +
    def get_section_size(self):
1196 +
        """
1197 +
        Return the total size of this section
1198 +
1199 +
        :rtype: int
1200 +
        """
1201 +
        return self.section_size
1202 +
1203 +
    def get_flags(self, idx):
1204 +
        """
1205 +
        Return a tuple of the flags per class
1206 +
1207 +
        :param idx: The index to return the flags of (index of the class)
1208 +
        :type idx: int
1209 +
1210 +
        :rtype: Tuple[RestrictionApiFlag, DomainApiFlag]
1211 +
        """
1212 +
        return self.flags[idx]
1213 +
1214 +
    def set_off(self, off):
1215 +
        self.offset = off
1216 +
1217 +
    def get_off(self):
1218 +
        return self.offset
1219 +
1220 +
    def show(self):
1221 +
        bytecode._PrintSubBanner("HiddenApi Class Data Item")
1222 +
        bytecode._PrintDefault(
1223 +
            "section_size=0x%x\n"
1224 +
            % (self.section_size,))
1225 +
1226 +
        for i, (rf, df) in enumerate(self.flags):
1227 +
            bytecode._PrintDefault(
1228 +
                "[%u] %s, %s\n"
1229 +
                % (i, rf, df))
1230 +
1231 +
    def get_obj(self):
1232 +
        base = 4 + len(self.flags)
1233 +
        raw_offsets = b''
1234 +
        raw_flags = b''
1235 +
        for rf, df in self.flags:
1236 +
            raw_offsets += self.CM.packer["I"].pack(base + len(raw_flags))
1237 +
            raw_flags += writeuleb128(self.CM, (df.value << 3) | rf.value)
1238 +
1239 +
        return (self.CM.packer["I"].pack(self.section_size) +
1240 +
                raw_offsets + raw_flags)
1241 +
1242 +
    def get_raw(self):
1243 +
        return self.get_obj()
1244 +
1245 +
    def get_length(self):
1246 +
        return self.section_size
1247 +
1248 +
1145 1249
class TypeItem:
1146 1250
    """
1147 1251
    This class can parse a type_item of a dex file
@@ -6956,6 +7060,11 @@
Loading
6956 7060
            buff.set_idx(self.offset + (self.offset % 4))
6957 7061
            self.item = [AnnotationsDirectoryItem(buff, cm) for _ in range(self.size)]
6958 7062
7063 +
        elif TypeMapItem.HIDDENAPI_CLASS_DATA_ITEM == self.type:
7064 +
            # Byte aligned
7065 +
            buff.set_idx(self.offset)
7066 +
            self.item = HiddenApiClassDataItem(buff, cm)
7067 +
6959 7068
        elif TypeMapItem.ANNOTATION_SET_REF_LIST == self.type:
6960 7069
            # 4-byte aligned
6961 7070
            buff.set_idx(self.offset + (self.offset % 4))
@@ -7208,12 +7317,17 @@
Loading
7208 7317
        for i in self.__manage_item[TypeMapItem.ANNOTATION_OFF_ITEM]:
7209 7318
            if i.get_off() == off:
7210 7319
                return i
7211 -
    
7320 +
7212 7321
    def get_annotation_item(self, off):
7213 7322
        for i in self.__manage_item[TypeMapItem.ANNOTATION_ITEM]:
7214 7323
            if i.get_off() == off:
7215 7324
                return i
7216 7325
7326 +
    def get_hiddenapi_class_data_item(self, off):
7327 +
        for i in self.__manage_item[TypeMapItem.HIDDENAPI_CLASS_DATA_ITEM]:
7328 +
            if i.get_off() == off:
7329 +
                return i
7330 +
7217 7331
    def get_string(self, idx):
7218 7332
        """
7219 7333
        Return a string from the string table at index `idx`
@@ -7577,6 +7691,7 @@
Loading
7577 7691
            self.codes = self.map_list.get_item_type(TypeMapItem.CODE_ITEM)
7578 7692
            self.strings = self.map_list.get_item_type(TypeMapItem.STRING_DATA_ITEM)
7579 7693
            self.debug = self.map_list.get_item_type(TypeMapItem.DEBUG_INFO_ITEM)
7694 +
            self.hidden_api = self.map_list.get_item_type(TypeMapItem.HIDDENAPI_CLASS_DATA_ITEM)
7580 7695
7581 7696
        self._flush()
7582 7697
@@ -7696,6 +7811,14 @@
Loading
7696 7811
        """
7697 7812
        return self.header
7698 7813
7814 +
    def get_hidden_api(self):
7815 +
        """
7816 +
        This function returns the hidden api item (from Android 10)
7817 +
7818 +
        :rtype: :class:`HiddenApiClassDataItem` object
7819 +
        """
7820 +
        return self.hidden_api
7821 +
7699 7822
    def get_class_manager(self):
7700 7823
        """
7701 7824
        This function returns a ClassManager object which allow you to get

@@ -325,6 +325,17 @@
Loading
325 325
        meths = [x.name for x in dx.get_permission_usage('android.permission.ACCESS_NETWORK_STATE')]
326 326
        self.assertListEqual(sorted(meths), sorted(network_meths))
327 327
328 +
    def testHiddenAnnotation(self):
329 +
        a, d, dx = AnalyzeAPK("examples/android/TestsAnnotation/OPCommonTelephony.jar")
330 +
331 +
        class1 = dx.classes["Lvendor/mediatek/hardware/radio_op/V1_2/IRadioIndicationOp$Stub;"]
332 +
        self.assertEqual(class1.restriction_flag, dvm.HiddenApiClassDataItem.RestrictionApiFlag.WHITELIST)
333 +
        self.assertEqual(class1.domain_flag, dvm.HiddenApiClassDataItem.DomapiApiFlag.CORE_PLATFORM_API)
334 +
335 +
        class1 = dx.classes["Lcom/mediatek/opcommon/telephony/MtkRILConstantsOp;"]
336 +
        self.assertEqual(class1.restriction_flag, dvm.HiddenApiClassDataItem.RestrictionApiFlag.BLACKLIST)
337 +
        self.assertEqual(class1.domain_flag, dvm.HiddenApiClassDataItem.DomapiApiFlag.NONE)
338 +
328 339
329 340
if __name__ == '__main__':
330 341
    unittest.main()

@@ -353,6 +353,10 @@
Loading
353 353
        self.xrefnewinstance = set()
354 354
        self.xrefconstclass = set()
355 355
356 +
        # For Android 10+
357 +
        self.restriction_flag = None
358 +
        self.domain_flag = None
359 +
356 360
        # Reserved for further use
357 361
        self.apilist = None
358 362
@@ -1301,6 +1305,34 @@
Loading
1301 1305
        """
1302 1306
        return self.orig_class
1303 1307
1308 +
    def set_restriction_flag(self, flag):
1309 +
        """
1310 +
        Set the level of restriction for this class (hidden level, from Android 10)
1311 +
        (only applicable to internal classes)
1312 +
1313 +
        :param flag: The flag to set to
1314 +
        :type flag: androguard.core.bytecodes.dvm.HiddenApiClassDataItem.RestrictionApiFlag
1315 +
        """
1316 +
        if self.is_external():
1317 +
            raise RuntimeError(
1318 +
                "Can\'t set restriction flag for external class: %s"
1319 +
                % (self.orig_class.name,))
1320 +
        self.restriction_flag = flag
1321 +
1322 +
    def set_domain_flag(self, flag):
1323 +
        """
1324 +
        Set the api domain for this class (hidden level, from Android 10)
1325 +
        (only applicable to internal classes)
1326 +
1327 +
        :param flag: The flag to set to
1328 +
        :type flag: androguard.core.bytecodes.dvm.HiddenApiClassDataItem.DomainApiFlag
1329 +
        """
1330 +
        if self.is_external():
1331 +
            raise RuntimeError(
1332 +
                "Can\'t set domain flag for external class: %s"
1333 +
                % (self.orig_class.name,))
1334 +
        self.domain_flag = flag
1335 +
1304 1336
    # Alias
1305 1337
    get_class = get_vm_class
1306 1338
@@ -1397,12 +1429,20 @@
Loading
1397 1429
        log.info("Adding DEX file version {}".format(vm.version))
1398 1430
        # TODO: This step can easily be multithreaded, as there is no dependecy between the objects at this stage
1399 1431
        tic = time.time()
1400 -
        for current_class in vm.get_classes():
1432 +
        for i, current_class in enumerate(vm.get_classes()):
1401 1433
            self.classes[current_class.get_name()] = ClassAnalysis(current_class)
1434 +
            new_class = self.classes[current_class.get_name()]
1435 +
            # Fix up the hidden api annotations (Android 10)
1436 +
            hidden_api = vm.get_hidden_api()
1437 +
            if hidden_api:
1438 +
                rf, df = hidden_api.get_flags(i)
1439 +
                new_class.set_restriction_flag(rf)
1440 +
                new_class.set_domain_flag(df)
1441 +
1402 1442
            for method in current_class.get_methods():
1403 1443
                self.methods[method] = MethodAnalysis(vm, method)
1404 1444
1405 -
                self.classes[current_class.get_name()].add_method(self.methods[method])
1445 +
                new_class.add_method(self.methods[method])
1406 1446
1407 1447
                # Store for faster lookup during create_xrefs
1408 1448
                m_hash = (current_class.get_name(), method.get_name(), str(method.get_descriptor()))

@@ -72,6 +72,7 @@
Loading
72 72
    ANNOTATION_ITEM = 0x2004
73 73
    ENCODED_ARRAY_ITEM = 0x2005
74 74
    ANNOTATIONS_DIRECTORY_ITEM = 0x2006
75 +
    HIDDENAPI_CLASS_DATA_ITEM = 0xf000
75 76
76 77
    @staticmethod
77 78
    def _get_dependencies():
@@ -106,7 +107,8 @@
Loading
106 107
             {TypeMapItem.PROTO_ID_ITEM, TypeMapItem.STRING_ID_ITEM, TypeMapItem.TYPE_ID_ITEM,
107 108
              TypeMapItem.FIELD_ID_ITEM, TypeMapItem.METHOD_ID_ITEM}),
108 109
            (TypeMapItem.ANNOTATIONS_DIRECTORY_ITEM,
109 -
             {TypeMapItem.FIELD_ID_ITEM, TypeMapItem.METHOD_ID_ITEM, TypeMapItem.ANNOTATION_SET_ITEM})
110 +
             {TypeMapItem.FIELD_ID_ITEM, TypeMapItem.METHOD_ID_ITEM, TypeMapItem.ANNOTATION_SET_ITEM}),
111 +
            (TypeMapItem.HIDDENAPI_CLASS_DATA_ITEM, set()),
110 112
        ])
111 113
112 114
    @staticmethod

Learn more Showing 1 files with coverage changes found.

Changes in androguard/core/__init__.py
-2
+6
Loading file...
Files Coverage
androguard 0.03% 73.56%
tests 0.01% 96.96%
Project Totals (51 files) 76.85%
Loading