Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NDK application Signature Check

I have some security key in an application. I want to store it securly. I like to store it in a native shared library (maybe generated from some code). After that I want it to be returned by a method that will check the signature of the original APK. So no one can use this file except trusted applications. I know, that ndk library could be also decompiled, but this is even harder to make reverse engineering of native code then java .class files.

Question:

  1. Is there a way to calk the signature of the origin apk from the native code (c/c++)?
  2. How can I make sure that the library is called from the trusted application?
like image 269
Yevgen Kulik Avatar asked Jun 04 '15 17:06

Yevgen Kulik


2 Answers

TL;DR An example can be found here.

I get a signature on native layer(C-code) another way:

  • Get a path of the APK;
  • Extract 'META-INF/CERT.RSA' from the APK;
  • Parse 'META-INF/CERT.RSA'

The code for getting the APK path:

    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;
}

We can use zlib since 3 API in Android and I use minizip for convinience.

The code to extract of the META-INF/CERT.RSA below:

//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;
} 

For parsing META-INF/CERT.RSA I use parts of the code from one public repository. It's too big to be posted on StackOverflow, so the full source code of the working example can be found here.

upd:

Here is an example how we can get MD5 from a signature(using 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);  
like image 114
Dima Kozhevin Avatar answered Nov 14 '22 04:11

Dima Kozhevin


I will try to answer your first question here:

Signature of your application is stored in the DEX(Dalvik executable) file of your APK. DEX files have following structure:

  1. Header
  2. Data section(contains strings, code instructions, fields, etc)
  3. Arrays of method identifiers, class identifiers, etc

So, this is the beginning of the header of DEX file:

  1. DEX_FILE_MAGIC constant - ubyte[8]
  2. Adler-32 checksum of your application(except DEX_FILE_MAGIC and checksum itself) - uint
  3. SHA-1 signature of your application(except of DEX_FILE_MAGIC, checksum and hash itself) - ubyte[20]

So, to calk a signature of your apk, you should compute SHA-1 signature of your DEX file starting from the offset 32.

To get access to DEX file of your apk from native code, you can read process memory, which is stored in /proc/self/maps:

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

Each row in proc/$ID/maps file has following structure:

  1. address
  2. permissions
  3. offset
  4. device
  5. inode
  6. pathname

Here you can find a better description of proc/$ID/maps file's structure: Understanding Linux /proc/id/maps

To detect location of DEX file in process memory you should check out 'pathname' column in every row of your proc/self/maps file. When the row corresponding to DEX file will be found, you should get starting and ending addresses of the DEX file region:

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

So, when you will have starting and ending addresses of your apk's bytecode, you will be able to compute signature of your apk.

like image 12
floyd Avatar answered Nov 14 '22 02:11

floyd