Как подписываються APK-файлы

Как подписываються APK-файлы

В файлах APK есть особенность — специальная подпись со специальным блоком метаданных Frosting. Он позволяет однозначно определить, распространялся ли файл через Google Play. Эта сигнатура будет полезна поставщикам антивирусов и песочниц при анализе вредоносных программ. Кроме того, это может помочь судебным экспертам найти источник файла.

APK Signing Block и Frosting

Как подписываються APK-файлы

Для идентификации блока подписи APK используется magic: APK Sig Block 42. Внутри блока подписи могут быть другие блоки, назначение которых определяется 4-байтовым идентификатором. Это приводит к расширению формата ZIP с обратной совместимостью. Если вы хотите прочитать больше или просмотреть исходный код, здесь описывается метод ApkSigningBlockUtils.findSignature.

Возьмем любой файл, например 2124948e2b7897cd6fbbe5fbd655c26d. Вы можете просмотреть идентификаторы блоков, содержащиеся в блоке подписи APK, используя androguard:

from androguard.core.bytecodes import apk apk_obj = apk.APK("2124948e2b7897cd6fbbe5fbd655c26d.apk") apk_obj.parse_v2_v3_signature() print(["0x%X" % key for key in apk_obj._v2_blocks.keys()]) ['0x7109871A', '0x2146444E']

Есть некоторое количество блоков с разными идентификаторами которые описаны в документации:

Некоторые блоки можно найти в исходниках Android:

 

  • 0x42726577 (VERITY_PADDING_BLOCK_ID) — блок, используемый для нулевого выравнивающего блока;
  • 0x6dff800d (SOURCE_STAMP_BLOCK_ID) — относительно новый вид блоков.

 

Встречаются и другие блоки:

 

  • 0x504b4453 (DEPENDENCY_INFO_BLOCK_ID) — блок, в котором, по всей видимости, содержится метаинформация о зависимостях, сохраняемая плагином Android Gradle для определения проблем с ними;
  • 0x71777777 (APK_CHANNEL_BLOCK_ID) — блок китайской приблуды Walle для сборки, который содержит JSON с именем канала;
  • 0xff3b5998 — нулевой блок, который встретился мне в файле — найти какую-либо информацию о нем я не смог;
  • 0x2146444e — блок с необходимой метаинформацией от Google Play.

Frosting и «Play Маркет»

 

Вернемся к анализу рассматриваемого блока 0x2146444e. Для начала следует изучить внутренности приложения «Play Маркет».

.method public static b(ByteBuffer)ByteBuffer
        .registers 2
    00000000  invoke-static       bny->a(ByteBuffer)aea, p0
    00000006  move-result-object  p0
    00000008  const               v0, 0x2146444E
    0000000E  invoke-virtual      aea->a(I)Object, p0, v0
    00000014  move-result-object  p0
    00000016  check-cast          p0, bnx
    0000001A  if-eqz              p0, :00000024
    0000001E  iget-object         p0, p0, bnx->a:ByteBuffer
    00000022  return-object       p0
    00000024  new-instance        p0, SigBlockUtil$BlockNotFoundException
    00000028  const-string        v0, "Block entry id (go/apk-structure-glossary) "
                                      "not present in APK Signing Block"
    0000002C  invoke-direct       SigBlockUtil$BlockNotFoundException->
                                      <init>(String)V, p0, v0
    00000032  throw               p0
.end method

Идентификатор который нас интересует был найден в двух местах. Посмотрим глубже. Очень быстро находим класс, отвечающий за анализ блока. Здесь имя блока Frosting впервые появляется среди констант:

public enum aysn implements avto {
    UNKNOWN(0),
    SUCCESS(81),
    NO_FROSTING_BLOCK(1),
    FROSTING_BLOCK_TOO_SHORT(2),
    BAD_SIGNED_DATA_LENGTH_VARINT(3),
    NON_POSITIVE_SIGNED_DATA_LENGTH(4),
    SIGNED_DATA_LENGTH_TOO_LONG(5),
    BAD_FROSTING_LENGTH_VARINT(6),
    NON_POSITIVE_FROSTING_LENGTH(7),
    FROSTING_LENGTH_BEYOND_SIGNED_DATA(8),
    FROSTING_LENGTH_BEYOND_BLOCK(9),
    MALFORMED_FROSTING(10),
    // ...
    INSTALLER_NO_ACCOUNT_WITH_MATCHING_REGION(74);
    // ...
}

Я сравнил разные версии приложения Play Store и обнаружил следующее: Код, отвечающий за анализ этого типа подписи, появился примерно в январе 2018 года с выпуском версии 8.6.X. Блок метаданных Frosting существовал раньше, но тогда он принял ту форму, в которой существует сейчас.

Для разбора данных нам понадобится примитив чтения 4-байтового числа. Схема представляет собой стандартный varint без каких-либо ухищрений с отрицательными числами:

private static int read_int32(ByteBuffer arg2) {
    int v0 = arg2.get();
    if(v0 >= 0) {
        return v0;
    }

    int v0_1 = v0 & 0x7F;
    int v1 = arg2.get();
    if(v1 >= 0) {
        return v1 << 7 | v0_1;
    }

    v0_1 |= (v1 & 0x7F) << 7;
    int v1_1 = arg2.get();
    if(v1_1 >= 0) {
        return v1_1 << 14 | v0_1;
    }

    v0_1 |= (v1_1 & 0x7F) << 14;
    int v1_2 = arg2.get();
    if(v1_2 >= 0) {
        return v1_2 << 21 | v0_1;
    }

    int v2 = arg2.get();
    int v0_2 = v0_1 | (v1_2 & 0x7F) << 21 | v2 << 28;
    if(v2 >= 0) {
        return v0_2;
    }

    throw new IllegalArgumentException();
}

Функция разбора блока довольно большая, хоть и устроена просто. Она позволяет разобраться в самой структуре данных:

{
    var_int32 size_signed_data,
    var_int32 size_frosting,
    byte frosting[size_frosting],
    var_int32 size_validation_sequence,
    array validation_sequence {
        var_int32 size_validation_data,
        var_int32 validation_strategy,
        var_int32 signing_key_index,
        byte sha256[0x20]},
    var_int32 size_signature_sequence,
    array signature_sequence {
        var_int32 size_signature,
        byte signature[size_signature]}}

Хэш и ключ первого поля с validation_sequence с validation_strategy, равным нулю, используются для проверки подписи. Сама подпись берется из последовательности подписей с тем же порядковым номером, что и запись последовательности проверки. На рисунке ниже показан псевдокод, все объясняющий:

 

def get_signing_data(frosting_block):
    for i, validation in enumerate(frosting_block.validation_sequence):
        if validation.validation_strategy != 0:
            continue
        return (
            validation.sha256, validation.signing_key_index,
            frosting_block.signature_sequence[i].signature)
    raise AttributeError()

Значение signing_key_index указывает на индекс в массиве finsky.peer_app_sharing_api.frosting_public_keys, который определен следующим образом и пока содержит только один ключ

gyd.iH = arip.a(
    "finsky.peer_app_sharing_api.min_tos_version", v8);
gyd.iI = arip.a(
    "finsky.peer_app_sharing_api.frosting_public_keys",
    "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZH2+1+E07dnErAD3L6BbTnaohU0bbXriNlJI7VxJU+LjdSwPyXR5pomARAMoyPkMksLz/gitUPtFuJoPL2ziEw==");
gyd.iJ = arip.a(
    "finsky.peer_app_sharing_api.startup_package_blacklist",
    "com.android.vending,com.google.android.gms,com.android.providers.downloads");
gyd.iK = arip.a(
    "finsky.frosting_only_update_time_ms", Long.valueOf(TimeUnit.DAYS.toMillis(30L)));

Подписываются данные размером size_signed_data, начиная с переменной size_frosting, алгоритмом ECDSA_SHA256. При этом подписываемые данные содержат SHA-256 от данных файла:

1) данные от начала файла до блока подписи;

2) данные от central directory до конца end of central directory, с заменой в end of central directory значения поля offset of start of central with respect to the starting disk number на оффсет блока подписи.

Если есть блок подписи схемы версии 2, то он вставляется между данными из пунктов 1 и 2 с добавлением APK_SIGNATURE_SCHEME_V2_BLOCK_ID перед ним.

Как подписываються APK-файлы

Функция, вычисляющая хеш в приложении «Play Маркет», выглядит так:

 

private static byte[] get_frosting_hash(RandomAccessFile f_apk,
         long offset_signing_block, ByteBuffer signature_scheme_v2_block,
         long offset_zip_central_dir, long size_from_central_dir_to_end_central_dir,
         ByteBuffer end_central_dir) {
    MessageDigest v0;
    try {
        v0 = MessageDigest.getInstance("SHA-256");
    }
    catch(NoSuchAlgorithmException unused_ex) {
        throw new FrostingUtil.FailureException(aysn.N);  // NO_SHA_256_ALGORITHM
    }

    mcc.update_digest(v0, f_apk, 0L, offset_signing_block);
    if(signature_scheme_v2_block != null) {
        v0.update(mcc.a);
        v0.update(signature_scheme_v2_block);
    }

    mcc.update_digest(v0, f_apk, offset_zip_central_dir,
            size_from_central_dir_to_end_central_dir);
    bnz.a(end_central_dir, offset_signing_block);
    end_central_dir.position(end_central_dir.position());
    end_central_dir.limit(end_central_dir.limit());
    v0.update(end_central_dir);
    return v0.digest();
}

 

Frosting и ProtoBuf

Этой информации достаточно для проверки действительности подписки. Но, к сожалению, мне так и не удалось выяснить, что скрывается в данных в блоке Frosting. Мне удалось только определить, что данные находятся в формате ProtoBuf и могут сильно различаться по размеру и наличию полей в зависимости от файла.

Обычно декодированные без схемы данные выглядят так (4b005c9e9ea0731330a757fcf3abeb6e):

 

cat ./ru.sberbankmobile_11.1.0_2020072413.protobuf | ./protodec -p

{
  "1:0:varint": 2,
  "2:1:varint": 0,
  "3:2:varint": 1,
  "4:3:varint": 1603811598348,
  "5:4:embedded": {
    "8:0:embedded": {
      "1:0:embedded": {
        "1:0:varint": 21
      },
      "6:1:varint": 3
    },
    "9:1:embedded": {
      "1:0:embedded": {
        "1:0:varint": 2020072413,
        "4:1:varint": 3
      },
      "2:1:embedded": {
        "1:0:varint": 2020072414,
        "4:1:varint": 5
      }
    },
    "10:2:embedded": {
      "1:0:bytes": [
        255, 255, 255, 255, 255, 255, 255, 255, 255,
        255, 255, 255, 255, 255, 255, 255, 255, 7,
        0, 40, 0, 16, 0, 0, 80, 5, 16, 64, 0, 56
      ]
    }
  },
  "8:5:varint": 1,
  "9:6:varint": 2
}

 

Но встречаются экземпляры (471c589acc800135eb318057c43a8068), содержащие под пятьсот полей.

В данных изредка попадаются занятные строки: android.hardware.ram.lowcom.samsung.feature.SAMSUNG_EXPERIENCEcom.google.android.apps.photos.PIXEL_2018_PRELOAD. Эти строки — не очень документированные имена функций, которые могут быть у устройства.

 

Описание функций, если они есть, можно посмотреть, на устройстве в файлах в папке /etc/sysconfig/:

 

adb shell cat /etc/sysconfig/pixel_experience_2017.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- These are configurations that should exist on Google's 2017 and newer Nexus devices. -->
<config>
    <!-- This is meant to be the canonical feature identifying 2017 and newer Nexus devices. -->
    <feature name="com.google.android.feature.PIXEL_2017_EXPERIENCE" />
</config>

adb shell cat /etc/sysconfig/pixel_2017_exclusive.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- These are configurations that should exist on Google's 2017 devices (and not newer/older) -->
<config>
    <!-- This defines the Photos preload feature for specifically the 2017 Pixel devices. -->
    <feature name="com.google.android.apps.photos.PIXEL_2017_PRELOAD" />
</config>

 

В качестве документированного примера можно привести проверку наличия камеры запросом функции android.hardware.camera через метод hasSystemFeature класса PackageManager. Но зачем нужны эти строки, в данном контексте непонятно.

 

Мне не удалось угадать, найти или восстановить схему данных из классов APK «Play Маркет». Если кто-то расскажет, что там и как получилось это определить, будет интересно. Пока есть только предположения разработчиков утилиты от Avast о структуре ProtoBuf и о том, что строка com.google.android.apps.photos.PIXEL_2018_PRELOAD указывает, является ли приложение системным/предустановленным:

 

1 <varint> = 1  // frosting versions?
2 <varint> = 0
3 <varint> = 1
4 <varint> = 1541545744578  // Timestamp of the frosting creation?
5 <chunk> = message:
    8 <chunk> = message:
        1 <chunk> = message(1 <varint> = 22)  // minSdkLevel?
        6 <varint> = 2
    9 <chunk> = message:
        1 <chunk> = message(1 <varint> = 2266, 4 <varint> = 2)  // versionCode
        2 <chunk> = message(1 <varint> = 50003, 4 <varint> = 4)
    10 <chunk> = message:
        1 <chunk> = bytes (30)  // ?? only last byte changes across apks
            0000   FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
            0010   FF FE FF FF FF FF FF FF FF FF FF FF FF 3F
            '.............................?'
        3 <chunk> = message:
            1 <chunk> = bytes (32)  // sha256 of something?
                0000   16 F8 22 A6 93 26 89 34 D8 2A 88 BB 8C AD B6 68
                0010   2C EB 77 A8 AA E4 5F AA F9 3C CA 63 44 2A A4 B9
                '.."..&.4.*.....h,.w..._..<.cD*..'
            2 <varint> = 20

 

Заключение

Следовательно, блок Frosting в подписи помогает однозначно определить, был ли файл распространен через официальный магазин. Эта подпись не принесла никаких дополнительных преимуществ.

 

 

Click to rate this post!
[Total: 0 Average: 0]

Leave a reply:

Your email address will not be published.