Проверка подписи приложения NDK

У меня есть ключ безопасности в приложении. Я хочу хранить его в безопасности. Мне нравится хранить его в родной общей библиотеке (может быть, сгенерирован из некоторого кода). После этого я хочу, чтобы он возвращался методом, который проверяет подпись исходного APK. Поэтому никто не может использовать этот файл, кроме доверенных приложений. Я знаю, что библиотека ndk также может быть декомпилирована, но это еще сложнее сделать обратное проектирование собственного кода, а затем java.class файлов.

Вопрос:

  • Есть ли способ заглушить подпись исходного apk из собственного кода (c/С++)?
  • Как я могу убедиться, что библиотека вызывается из доверенного приложения?

Ответ 1

Я постараюсь ответить на ваш первый вопрос здесь:

Подпись вашего приложения хранится в исполняемом файле DEX (Dalvik) вашего APK. Файлы DEX имеют следующую структуру:

  • Заголовок
  • Раздел данных (содержит строки, инструкции кода, поля и т.д.)
  • Массивы идентификаторов методов, идентификаторов классов и т.д.

Итак, это начало заголовка файла DEX:

  • DEX_FILE_MAGIC константа - ubyte [8]
  • Контрольная сумма Adler-32 вашего приложения (кроме DEX_FILE_MAGIC и самой контрольной суммы) - uint
  • подпись SHA-1 вашего приложения (кроме DEX_FILE_MAGIC, контрольная сумма и хэш) - ubyte [20]

Итак, чтобы скрыть подпись вашего apk, вы должны вычислить подпись SHA-1 вашего DEX файла, начиная со смещения 32.

Чтобы получить доступ к файлу DEX вашего apk из собственного кода, вы можете прочитать память процесса, которая хранится в /proc/self/maps:

FILE *fp;
fp = fopen("/proc/self/maps", "r");

Каждая строка в файле proc/$ID/maps имеет следующую структуру:

  • адрес
  • разрешения
  • смещение
  • Устройство
  • инода
  • путь к файлу

Здесь вы можете найти более точное описание структуры файлов proc/$ID/maps: Понимание Linux/proc/id/maps

Чтобы обнаружить местоположение файла DEX в памяти процесса, вы должны проверить столбец "путь" в каждой строке вашего файла proc/self/maps. Когда строка, соответствующая файлу DEX, будет найдена, вы должны получить начальный и конечный адреса области файла DEX:

while (fgets(line, 2048, fp) != NULL) {
    // search for '.dex'
    if (strstr(line, ".dex") != NULL) {
        // get starting and ending addresses of the DEX file region

Итак, когда у вас будут начальные и конечные адреса вашего байт-кода apk, вы сможете вычислить подпись своего apk.

Ответ 2

TL; DR Пример можно найти здесь.

Я получаю подпись на собственном слое (C-code) другим способом:

  • Получить путь от APK;
  • Извлечь 'META-INF/CERT.RSA' из APK;
  • Анализ 'META-INF/CERT.RSA'

Код для получения пути APK:

    static char *getPackageName() {
    const size_t BUFFER_SIZE = 256;
    char buffer[BUFFER_SIZE] = "";
    int fd = open("/proc/self/cmdline", O_RDONLY);
    if (fd > 0) {
        ssize_t r = read(fd, buffer, BUFFER_SIZE - 1);
        close(fd);
        if (r > 0) {
            return strdup(buffer);
        }
    }
    return NULL;
}

static const char *getFilenameExt(const char *filename) {
    const char *dot = strrchr(filename, '.');
    if (!dot || dot == filename) return "";
    return dot + 1;
}

char *pathHelperGetPath() {

    char *package = getPackageName();
    if (NULL == package) {
        return NULL;
    }

    FILE *fp = fopen("/proc/self/maps", "r");
    if (NULL == fp) {
        free(package);
        return NULL;
    }
    const size_t BUFFER_SIZE = 256;
    char buffer[BUFFER_SIZE] = "";
    char path[BUFFER_SIZE] = "";

    bool find = false;
    while (fgets(buffer, BUFFER_SIZE, fp)) {
        if (sscanf(buffer, "%*llx-%*llx %*s %*s %*s %*s %s", path) == 1) {
            if (strstr(path, package)) {
                char *bname = basename(path);
                NSV_LOGI("check basename[%s]", bname);
                if (strcasecmp(getFilenameExt(bname), "apk") == 0) {
                    find = true;
                    break;
                }
            }
        }
    }
    fclose(fp);
    free(package);
    if (find) {
        return strdup(path);
    }
    return NULL;
}

Мы можем использовать zlib с 3 API в Android, и я использую minizip для удобства.

Код для извлечения META-INF/CERT.RSA ниже:

//return MZ_ERROR
static int32_t unzipHelperGetCertFileInfo(void *handle, mz_zip_file **file_info) {

    int32_t err = MZ_OK;

    err = mz_zip_goto_first_entry(handle);

    if (err != MZ_OK && err != MZ_END_OF_LIST) {
        NSV_LOGE("Error %d going to first entry in zip file\n", err);
        return err;
    }

    while (err == MZ_OK) {
        err = mz_zip_entry_get_info(handle, file_info);

        if (err != MZ_OK) {
            NSV_LOGE("Error %d getting entry info in zip file\n", err);
            *file_info = NULL;
            break;
        }

        if (NULL != (*file_info)->filename && strcasecmp((*file_info)->filename,"META-INF/CERT.RSA") == 0) {
            return MZ_OK;
        }

        err = mz_zip_goto_next_entry(handle);

        if (err != MZ_OK && err != MZ_END_OF_LIST) {
            *file_info = NULL;
            NSV_LOGE("Error %d going to next entry in zip file\n", err);
            return err;
        }
    }

    *file_info = NULL;

    if (err == MZ_END_OF_LIST) {
        return MZ_OK;
    }
    return err;
}

static void unzipHelperPrintFileInfo(const mz_zip_file *file_info) {
    uint32_t ratio = 0;
    struct tm tmu_date;
    const char *string_method = NULL;
    char crypt = ' ';

    usleep(500);
    NSV_LOGI("  Length  Method      Size  Attribs Ratio   Date    Time   CRC-32     Name\n");
    usleep(500);
    NSV_LOGI("  ------  -------     ----  ------- -----   ----    ----   ------     ----\n");
    ratio = 0;
    if (file_info->uncompressed_size > 0)
        ratio = (uint32_t)((file_info->compressed_size * 100) / file_info->uncompressed_size);

    // Display a '*' if the file is encrypted
    if (file_info->flag & MZ_ZIP_FLAG_ENCRYPTED)
        crypt = '*';

    switch (file_info->compression_method)
    {
        case MZ_COMPRESS_METHOD_RAW:
            string_method = "Stored";
            break;
        case MZ_COMPRESS_METHOD_DEFLATE:
            string_method = "Deflate";
            break;
        case MZ_COMPRESS_METHOD_BZIP2:
            string_method = "BZip2";
            break;
        case MZ_COMPRESS_METHOD_LZMA:
            string_method = "LZMA";
            break;
        default:
            string_method = "Unknown";
    }

    mz_zip_time_t_to_tm(file_info->modified_date, &tmu_date);
    usleep(500);
    NSV_LOGI(" %7"PRIu64"  %6s%c %7"PRIu64" %8"PRIx32" %3"PRIu32"%%  %2.2"PRIu32"-%2.2"PRIu32\
               "-%2.2"PRIu32"  %2.2"PRIu32":%2.2"PRIu32"  %8.8"PRIx32"   %s\n",
             file_info->uncompressed_size, string_method, crypt,
             file_info->compressed_size, file_info->external_fa, ratio,
             (uint32_t)tmu_date.tm_mon + 1, (uint32_t)tmu_date.tm_mday,
             (uint32_t)tmu_date.tm_year % 100,
             (uint32_t)tmu_date.tm_hour, (uint32_t)tmu_date.tm_min,
             file_info->crc, file_info->filename);

}

unsigned char *unzipHelperGetCertificateDetails(const char *fullApkPath, size_t *len) {

    unsigned char *result = NULL;
    int32_t err = 0;
    int32_t read_file = 0;

    void *handle = NULL;
    void *file_stream = NULL;
    void *split_stream = NULL;
    void *buf_stream = NULL;
    char *password = NULL;

    int64_t disk_size = 0;
    int16_t mode = MZ_OPEN_MODE_READ;
    int32_t err_close = 0;

    if (mz_os_file_exists(fullApkPath) != MZ_OK) {
        NSV_LOGE("file %s doesn't exit\n", fullApkPath);

    }
    mz_stream_os_create(&file_stream);
    mz_stream_buffered_create(&buf_stream);
    mz_stream_split_create(&split_stream);

    mz_stream_set_base(split_stream, file_stream);

    mz_stream_split_set_prop_int64(split_stream, MZ_STREAM_PROP_DISK_SIZE, disk_size);

    err = mz_stream_open(split_stream, fullApkPath, mode);
    mz_zip_file *file_info = NULL;
    if (err != MZ_OK) {
        NSV_LOGE("Error opening file %s\n", fullApkPath);
    } else {
        handle = mz_zip_open(split_stream, mode);

        if (handle == NULL) {
            NSV_LOGE("Error opening zip %s\n", fullApkPath);
            err = MZ_FORMAT_ERROR;
        } else {
            err = unzipHelperGetCertFileInfo(handle, &file_info);
            if (err == MZ_OK && NULL != file_info) {
                unzipHelperPrintFileInfo(file_info);
                //unzip
                err = mz_zip_entry_read_open(handle, 0, password);
                if (err != MZ_OK) {
                    NSV_LOGW("Error %d opening entry in zip file\n", err);
                } else {
                    result = calloc(file_info->uncompressed_size, sizeof(unsigned char));
                    if (NULL != result) {
                        read_file = mz_zip_entry_read(handle, result,
                                                      (uint32_t) (file_info->uncompressed_size));
                        if (read_file < 0) {
                            free(result);
                            result = NULL;
                            err = read_file;
                            NSV_LOGW("Error %d reading entry in zip file\n", err);
                        } else {
                            NSV_LOGI("read %d from zip file\n", read_file);
                            *len = (size_t) read_file;
                        }
                    }
                }
            }
        }

        err_close = mz_zip_close(handle);

        if (err_close != MZ_OK) {
            NSV_LOGE("Error in closing %s (%d)\n", fullApkPath, err_close);
            err = err_close;
        }

        mz_stream_close(split_stream);

    }
    mz_stream_split_delete(&split_stream);
    mz_stream_buffered_delete(&buf_stream);
    mz_stream_os_delete(&file_stream);

    return result;
} 

Для разбора META-INF/CERT.RSA я использую части кода из одного публичного репозитория. Он слишком велик для публикации в StackOverflow, поэтому полный исходный код рабочего примера можно найти здесь.

UPD:

Вот пример того, как мы можем получить MD5 из подписи (используя mbed TLS):

    size_t len_in = 0;
    size_t len_out = 0;
    content = unzipHelperGetCertificateDetails(path, &len_in);
    LOGI("unzipHelperGetCertificateDetails finishes\n");
    if (!content) {
        return;
    }
    LOGI("pkcs7HelperGetSignature starts\n");
    unsigned char *res = pkcs7HelperGetSignature(content, len_in, &len_out);
    LOGI("pkcs7HelperGetSignature finishes, len_out:[%zu]\n", len_out);
    if (NULL == res) {
        return;
    }
    LOGI("calculating md5\n");
    unsigned char md5sum[16] = {""};
    mbedtls_md5((unsigned const char *) res, len_out, md5sum);
    char md5string[33];
    for (int i = 0; i < 16; ++i) {
        sprintf(&md5string[i * 2], "%02x", (unsigned int) md5sum[i]);
    }
    LOGI("md5:[%s]\n", md5string);