В файлах APK есть особенность — специальная подпись со специальным блоком метаданных Frosting. Он позволяет однозначно определить, распространялся ли файл через Google Play. Эта сигнатура будет полезна поставщикам антивирусов и песочниц при анализе вредоносных программ. Кроме того, это может помочь судебным экспертам найти источник файла.
Для идентификации блока подписи 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:
Встречаются и другие блоки:
Вернемся к анализу рассматриваемого блока 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
перед ним.
Функция, вычисляющая хеш в приложении «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 и могут сильно различаться по размеру и наличию полей в зависимости от файла.
Обычно декодированные без схемы данные выглядят так (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.low
, com.samsung.feature.SAMSUNG_EXPERIENCE
, com.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 в подписи помогает однозначно определить, был ли файл распространен через официальный магазин. Эта подпись не принесла никаких дополнительных преимуществ.
Чтобы взломать сеть Wi-Fi с помощью Kali Linux, вам нужна беспроводная карта, поддерживающая режим мониторинга…
Работа с консолью считается более эффективной, чем работа с графическим интерфейсом по нескольким причинам.Во-первых, ввод…
Конечно, вы также можете приобрести подписку на соответствующую услугу, но наличие SSH-доступа к компьютеру с…
С тех пор как ChatGPT вышел на арену, возросла потребность в поддержке чата на базе…
Если вы когда-нибудь окажетесь в ситуации, когда вам нужно взглянуть на спектр беспроводной связи, будь…
Elastic Security стремится превзойти противников в инновациях и обеспечить защиту от новейших технологий злоумышленников. В…