• SO加固之section加密

    前言

    没有前言

    实例

    为了简单演示section加密,那就使用默认的native stringFromJNI函数吧

    JNI代码

    原理

    都知道c语言程序一般都是从main函数开始执行,但是需要知道的是,有__attribute__((constructor))属性的函数会先于main函数执行,这就为加壳解密带来了契机。其次,还可以通过__attribute__((section(".mytext")))的方式将特定的函数编译到so文件中特定的节区中。那么,我们就可以通过加密特定的节区实现加固so文件。再通过__attribute__((constructor))优先于main函数的方法,进行section解密,最后运行解密后的节区里面的代码。

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    #include <jni.h>
    #include <string>
    #include <elf.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/mman.h>
    #include <android/log.h>
    #define LOG_TAG "SHUIYES"
    #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG, fmt, ##args)

    extern "C" jstring Java_com_qqq_test_sectionso_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) __attribute__((section(".mytext")));
    void init_getString() __attribute__((constructor));
    unsigned long getLibAddr();

    extern "C"
    JNIEXPORT jstring

    JNICALL
    Java_com_qqq_test_sectionso_MainActivity_stringFromJNI(
    JNIEnv *env,
    jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
    }




    void init_getString()
    {
    char name[15];
    unsigned int length;
    unsigned int nsize;
    unsigned long base;
    unsigned long text_addr;
    unsigned int i;
    Elf32_Ehdr *ehdr;

    base = getLibAddr();//获取so的起始位置
    ehdr = (Elf32_Ehdr *)base;
    text_addr = ehdr->e_flags + base; //偏移值,加密节的地址
    length = ehdr->e_entry >> 16;//加密节的大小(高位)
    nsize = ehdr->e_entry & 0xffff;//(低位)

    LOGD("tips:");
    LOGD("base:0x%x", base);
    LOGD("mysection at 0x%x", ehdr->e_flags);
    LOGD("length:%d", length);
    LOGD("nsize:%d", nsize);
    LOGD("text_addr:0x%x",text_addr);

    int status = mprotect((void *)(base), 4096 * nsize, PROT_EXEC|PROT_READ|PROT_WRITE);
    if ( status != 0)
    {
    //修改内存权限失败
    LOGD("error set .mytext read,mprotect:%d", status);
    return;
    }

    //进行解密
    for (i = 0; i < length; i++)
    {
    char *addr = (char*)(text_addr + i);
    *addr = ~(*addr);
    }
    int sa = mprotect((void *)(base), 4096*nsize, PROT_EXEC|PROT_READ);
    if ( sa != 0)
    {
    //修改内存权限失败
    LOGD("error set .mytext noly read");
    return;
    }
    LOGD("decrept success");
    }

    //获取so文件被加载到内存中的起始地址
    unsigned long getLibAddr()
    {
    int pid = getpid();
    char buf[4096];
    char *temp;
    FILE *fp;
    unsigned long base = 0;

    sprintf(buf,"/proc/%d/maps",pid);//在这个文件中有进程映射的模块信息
    fp = fopen(buf,"r");
    if (fp == NULL)
    {
    puts("error");
    goto error;
    }
    while (fgets(buf, sizeof(buf),fp))
    {
    if (strstr(buf,"libnative-lib.so"))
    {
    temp = strtok(buf, "-");//返回-之前的字符串
    base = strtoul(temp, NULL, 16);//字符串转换为长整型
    break;
    }
    }
    error:
    fclose(fp);
    return base;
    }

    这里主要要注意一下,网上有些博主,mprotect函数修改内存权限,大多写的是mprotect((void*)(text_addr/PAGE_SIZE * PAGE_SIZE), 4096*nsize, PROT_EXEC|PROT_READ),多次测试,程序很多时候都不能运行,也看了网上很多文章,也没有说清楚。

    通过mprotect修改text_addr/PAGE_SIZE * PAGE_SIZE权限为可读时,会修改不成功,我从开始写section加密到调试“程序无法运行”,一直找原因,用了4天左右,还是只知道修改内存权限,这里会爆“SIGSEGV (signal SIGSEGV: address access protected (fault address: 0xa7d380c4))”错误。有博主https://blog.csdn.net/earbao/article/details/120308836 在文章中这样解释,还是感觉有问题。

    section加密程序

    原理

    通过遍历节区名的方式,找到so文件的特定节区,然后读取节区偏移和大小,将其偏移所在的位置进行加密,然后将节区偏移、大小等数据保存在so header里面,即e_entry、e_flags、e_shentsize等部位,因为动态链接库的这些东西都没用。

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    #include <stdio.h>
    #include <tchar.h>
    #include <Windows.h>
    #include "elf.h"
    int main()
    {
    char soPath[MAX_PATH] = "libnative-lib.so";
    char section[] = ".mytext";

    HANDLE hFile = CreateFileA(soPath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
    printf("no such file");
    return -1;
    }

    ULONG ulHigh = 0;
    unsigned int ulLow = GetFileSize(hFile, &ulHigh);
    char fileData[ulLow + 20];
    printf("file at 0x%x \n", fileData);

    ULONG ulReturn = 0;
    if (ReadFile(hFile, fileData, ulLow, &ulReturn, NULL) == 0)
    {
    CloseHandle(hFile);
    return -2;
    }

    Elf32_Ehdr* ehdr = (Elf32_Ehdr*)(fileData);
    Elf32_Shdr* shdrstr = (Elf32_Shdr*)(fileData + ehdr->e_shoff + sizeof(Elf32_Shdr)*ehdr->e_shstrndx);
    char *shstr = (char*)(fileData + shdrstr->sh_offset);
    Elf32_Shdr* shdr = (Elf32_Shdr*)(fileData + ehdr->e_shoff);

    unsigned int base, length, i;
    for (i = 0; i < ehdr->e_shnum; i++)
    {
    if (strcmp(shstr + shdr->sh_name, section) == 0)
    {
    base = shdr->sh_offset;
    length = shdr->sh_size;
    printf("find section %s at 0x%x size: 0x%x\n", section, base, length);
    break;
    }
    shdr++;
    }

    char* content = NULL;
    content = (char*)(fileData + base);
    unsigned short nblock,nsize;
    unsigned char block_size = 16;
    nblock = length / block_size;
    nsize = base/4096 + (base % 4096 == 0 ? 0:1);
    printf("base = 0x%x, length = 0x%x\n", base, length);
    printf("nblock = %d, nsize = %d\n", nblock, nsize);

    ehdr->e_entry = (length << 16) + nsize;//大小
    ehdr->e_flags = base;//地址
    ehdr->e_shentsize = 1;//(可改)
    ehdr->e_shstrndx = 5;//(可改)
    //e_shoff,e_shnum不能改

    printf("content is %x\n", content);

    for (i = 0; i < length; i++)
    {
    content[i] =~content[i];
    }

    strcat(soPath, "_");
    HANDLE hFileOut = CreateFileA(soPath,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);

    if (hFileOut == INVALID_HANDLE_VALUE)
    {
    printf("file create error");
    return -3;
    }

    if (WriteFile(hFileOut, fileData, ulLow, &ulReturn, NULL))
    {
    printf("write success");
    }else
    {
    printf("write error: %d\r\n", GetLastError());
    }
    CloseHandle(hFile);
    CloseHandle(hFileOut);
    return 0;
    }

    演示

    image-20220816194603973

    探究

    通过对secion加密来实现对函数加密,方法虽然不错,但是也能被破解,破解方法很简单,直接在内存里面dump解密后的代码就行了。

    参考链接

    实例下载:https://pan.baidu.com/s/148Mr1Hb6Lu-fuJh3cd0t4w (kata)

    ELF文件格式分析:https://www.cnblogs.com/lhc-java/p/5549774.html

    https://bbs.pediy.com/thread-191649.htm

    基于对so中的section加密技术实现so加固:https://blog.csdn.net/qq_44906504/article/details/89512706

    上一篇:
    Android动态注册NDK函数
    下一篇:
    写一个Android第一代壳
    本文目录
    本文目录