zoukankan      html  css  js  c++  java
  • 一种简单的ELF加固方法

    介绍一种ELF文件函数粒度的加固方法,可以有效防止对程序的静态分析。这是一种有源码加固方式,需要被加固程序中代码配合。加固流程如下:

    1)读取ELF文件头,获取e_phoff和e_phnum
    2)通过Elf64_Phdr中的p_type字段,找到DYNAMIC
    3)遍历.dynamic,找到.dynsym、.dynstr 节区偏移,和.dynstr节区的大小
    4)遍历.dynsym,找到函数对应的Elf64_Sym符号后,根据st_value和st_size字段找到函数在ELF的偏移和函数大小
    5)根据函数偏移和大小,加密之

    加固程序代码如下,在x86_64平台测试通过:

    #include <stdio.h>

    #include <fcntl.h>

    #include <elf.h>

    #include <stdlib.h>

    #include <string.h>

    typedef struct {

            Elf64_Addr st_value;

            Elf64_Word st_size;

    }func_info;

    Elf64_Ehdr ehdr;

    int

    find_target_section_addr(const int fd, const char *sec_name){

            lseek(fd, 0, SEEK_SET);

            if(read(fd, &ehdr, sizeof(Elf64_Ehdr)) != sizeof(Elf64_Ehdr)){

                    puts("Read ELF header error");

                    return (-1);

            }

            return (0);

    }

    static char get_target_func_info(int fd, const char *func_name, func_info *info){

            char flag = -1, *dynstr;

            int i;

            Elf64_Sym func_sym;

            Elf64_Phdr phdr;

            Elf64_Off dyn_off;

            Elf64_Word dyn_size, dyn_strsz;

            Elf64_Dyn dyn;

            Elf64_Addr dyn_symtab, dyn_strtab;

            lseek(fd, ehdr.e_phoff, SEEK_SET);

            for(i = 0; i < ehdr.e_phnum; i++){

                    if(read(fd, &phdr, sizeof(Elf64_Phdr)) != sizeof(Elf64_Phdr)){

                            puts("Read segment failed");

                            return (-1);

                    }

                    if(phdr.p_type ==  PT_DYNAMIC){

                            dyn_size = phdr.p_filesz;

                            dyn_off = phdr.p_offset;

                            flag = 0;

                            printf("Find section %s, size = 0x%x, addr = 0x%lx ", ".dynamic", dyn_size, dyn_off);

                            break;

                    }

            }

            if(flag) {

                    puts("Find .dynamic failed");

                    return (-1);

            }

            flag = 0;

            lseek(fd, dyn_off, SEEK_SET);

            for(i=0;i < dyn_size / sizeof(Elf64_Dyn); i++){

                    if(read(fd, &dyn, sizeof(Elf64_Dyn)) != sizeof(Elf64_Dyn)){

                            puts("Read .dynamic information failed");

                            return (-1);

                    }

                    if(dyn.d_tag == DT_SYMTAB){

                            dyn_symtab = dyn.d_un.d_ptr;

                            flag++;

                            printf("Find .dynsym, addr = 0x%lx ", dyn_symtab);

                    }

                    if(dyn.d_tag == DT_STRTAB){

                            dyn_strtab = dyn.d_un.d_ptr;

                            flag++;

                            printf("Find .dynstr, addr = 0x%lx ", dyn_strtab);

                    }

                    if(dyn.d_tag == DT_STRSZ){

                            dyn_strsz = dyn.d_un.d_val;

                            flag++;

                            printf("Find .dynstr size, size = 0x%x ", dyn_strsz);

                    }

            }

            if(flag != 3){

                    puts("Find needed .section failed ");

                    return (-1);

            }

            dynstr = (char*) malloc(dyn_strsz);

            if(dynstr == NULL){

                    puts("Malloc .dynstr space failed");

                    return (-1);

            }

            lseek(fd, dyn_strtab, SEEK_SET);

            if(read(fd, dynstr, dyn_strsz) != dyn_strsz){

                    puts("Read .dynstr failed");

                    return (-1);

            }

            lseek(fd, dyn_symtab, SEEK_SET);

            while (1) {

                    if(read(fd, &func_sym, sizeof(Elf64_Sym)) != sizeof(Elf64_Sym)){

                            puts("Read func_sym failed");

                            return (-1);

                    }

                    if(strcmp(dynstr + func_sym.st_name, func_name) == 0){

                            break;

                    }

            }

            printf("Find: %s, offset = 0x%lx, size = 0x%lx ", func_name, func_sym.st_value, func_sym.st_size);

            info->st_value = func_sym.st_value;

            info->st_size = func_sym.st_size;

            ehdr.e_shoff = info->st_value;

            ehdr.e_shnum = info->st_size;

            lseek(fd, 0, SEEK_SET);

            if(write(fd, &ehdr, sizeof(Elf64_Ehdr)) != sizeof(Elf64_Ehdr)){

                    puts("Write elf header failed");

                    return (-1);

            }

            free(dynstr);

            return 0;

    }

    int main(int argc, char **argv){

            char sec_name[] = ".text";

            char func_name[] = "say_hello"; /* 被加密函数名 */

            char *content = NULL;

            int fd, i;

            Elf64_Off secOff;

            func_info info;

            if(argc < 2){

                    puts("Usage: shell libxxx.so .(section) function");

                    return -1;

            }

            fd = open(argv[1], O_RDWR);

            if(fd < 0){

                    printf("open %s failed ", argv[1]);

                    return (-1);

            }

            if (find_target_section_addr(fd, sec_name) == -1) {

                    printf("Find section %s failed ", sec_name);

                    return (-1);

            }

            if (get_target_func_info(fd, func_name, &info) == -1) {

                    printf("Find function %s failed ", func_name);

                    return (-1);

            }

            content = (char*) malloc(info.st_size);

            if(content == NULL){

                    puts("Malloc space failed");

                    return (-1);

            }

            lseek(fd, info.st_value, SEEK_SET);

            if(read(fd, content, info.st_size) != info.st_size){

                    puts("Malloc space failed");

                    return (-1);

            }

            for(i = 0; i < info.st_size; i++){

                    content[i] = ~content[i];

            }

            lseek(fd, info.st_value, SEEK_SET);

            if(write(fd, content, info.st_size) != info.st_size){

                    puts("Write modified content to .so failed");

                    return (-1);

            }

            puts("Complete!");

            free(content);

            close(fd);

            return 0;

    }


    解密代码放在.init_array节区,使ELF加载时运行解密:


    #include <stdio.h>

    #include <stdlib.h>

    #include <string.h>

    #include <unistd.h>

    #include <sys/types.h>

    #include <elf.h>

    #include <sys/mman.h>

    #define PAGE_SHIFT   12

    #define PAGE_SIZE (1UL << PAGE_SHIFT)

    typedef struct {

            Elf64_Addr st_value;

            Elf64_Word st_size;

    }func_info;

    void say_hello() { /* 被加密函数 */

            puts("hello elf.");

    }

    void __init() __attribute__((constructor));

    static unsigned long get_lib_addr(){

            unsigned long ret = 0;

            char buf[4096], *temp;

            int pid;

            FILE *fp;

            pid = getpid();

            sprintf(buf, "/proc/%d/maps", pid);

            fp = fopen(buf, "r");

            if(fp == NULL) {

                    puts("open failed");

                    goto _error;

            }

            while(fgets(buf, sizeof(buf), fp)){

                    if(strstr(buf, "libdemo.so")){

                            temp = strtok(buf, "-");

                            ret = strtoul(temp, NULL, 16);

                            break;

                    }

            }

    _error:

            fclose(fp);

            return ret;

    }

    void __init(){ /* 解密函数 */

            const char target_fun[] = "say_hello";

            func_info info;

            int i;

            unsigned long npage, base = get_lib_addr();

            Elf64_Ehdr *ehdr = (Elf64_Ehdr *)base;

            info.st_value = ehdr->e_shoff;

            info.st_size = ehdr->e_shnum;

            npage = info.st_size / PAGE_SIZE + ((info.st_size % PAGE_SIZE == 0) ? 0 : 1);

            if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), npage, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){

                    puts("mem privilege change failed");

            }

            for(i = 0; i < info.st_size; i++){

                    char *addr = (char*)(base + info.st_value + i);

                    *addr = ~(*addr);

            }

            if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), npage, PROT_READ | PROT_EXEC) != 0){

                    puts("mem privilege change failed");

            }

    }

    写了一段测试代码,如下:

    #include <stdio.h>

    #include <dlfcn.h>

    int

    main(int argc, char **argv)

    {

            void (*say_hello)();

            void *h;

            char *error;

            h= dlopen("./libdemo.so", RTLD_NOW);

            if (h == NULL) {

                    error = dlerror();

                    printf("%s ", error);

                    return (-1);

            }

            say_hello = dlsym(h, "say_hello");

            say_hello();

            dlclose(h);

            return (0);

    }

    以上参考了ThomasKing在看雪的贴子,但查找符号位置使用了一种新方法。

    原代码使用的DT_HASH,老版本GCC和现在的安卓都在使用这个结构,它比较简单。在Ubuntu 14.04上测试时发现新版GCC并没有用DT_HASH,而是使用的DT_GUN_HASH,它使用BloomFilter算法针对符号不存在的情况做了效率优化。

    这个结构比较复杂,如果再按照ELF加载器的流程来做就比较麻烦,所以选择了遍历的方法。但也有个缺点,当查找的符号不存在时程序会崩溃。

    运行结果如下:

    kiiim@ubuntu :~/_elf/m2$ gcc shell.c 
    kiiim@ubuntu :~/_elf/m2$ gcc loader.c -o loader -ldl
    kiiim@ubuntu :~/_elf/m2$ gcc demo.c -fPIC -shared -o libdemo.so
    kiiim@ubuntu :~/_elf/m2$ ./a.out libdemo.so 
    Find section .dynamic, size = 0x1c0, addr = 0xe18
    Find .dynstr, addr = 0x488
    Find .dynsym, addr = 0x230
    Find .dynstr size, size = 0x10f
    Find: say_hello, offset = 0x9f5, size = 0x12
    Complete!
    kiiim@ubuntu :~/_elf/m2$ ./loader 
    hello elf.
    kiiim@ubuntu:~/_elf/m2$ 

    原贴中还有另一种加固方法,将要加固函数写入新的节区,如.mytext,然后针对节区整体加密。这种方法实现同样比较简单。但评论里有个问题值得讨论下。

    有回复说实现了.text整体加密方案,但我分析了下,觉得不可行。

    观察.init_array节,发现在解密函数__init()执行前,还要执行一个frame_dummy()的系统函数:

    .init_array:0000000000200DF8 _init_array     segment para public 'DATA' use64
    .init_array:0000000000200DF8                 assume cs:_init_array
    .init_array:0000000000200DF8                 ;org 200DF8h
    .init_array:0000000000200DF8 __frame_dummy_init_array_entry dq offset frame_dummy
    .init_array:0000000000200E00                 dq offset __init  ;解密函数

    .init_array:0000000000200E00 _init_array     ends

    而这个函数是在.text中实现的:

    .text:00000000000009C0 frame_dummy     proc near
    .text:00000000000009C0                 cmp     cs:__JCR_LIST__, 0
    .text:00000000000009C8                 jz      short loc_9F0
    .text:00000000000009CA                 mov     rax, cs:_Jv_RegisterClasses_ptr
    .text:00000000000009D1                 test    rax, rax
    .text:00000000000009D4                 jz      short loc_9F0
    .text:00000000000009D6                 push    rbp
    .text:00000000000009D7                 lea     rdi, __JCR_LIST__
    .text:00000000000009DE                 mov     rbp, rsp
    .text:00000000000009E1                 call    rax ; _Jv_RegisterClasses
    .text:00000000000009E3                 pop     rbp
    .text:00000000000009E4                 jmp     register_tm_clones
    .text:00000000000009E4 ; ---------------------------------------------------------------------------
    .text:00000000000009E9                 align 10h
    .text:00000000000009F0
    .text:00000000000009F0 loc_9F0:                                ; CODE XREF: frame_dummy+8 j
    .text:00000000000009F0                                         ; frame_dummy+14 j
    .text:00000000000009F0                 jmp     register_tm_clones

    .text:00000000000009F0 frame_dummy     endp

    也就是说,在解密函数__init()执行之前,frame_dummy()运行就会失败。也就不能对.text整体加密。

  • 相关阅读:
    【赛前集训】20190902
    【2019csp模拟】树上相交路径
    如何给网站添加SSL证书(西部数码)?
    如何将页面blob类型的视频链接下载下来?
    VUE笔记:运行Vue报错error in ./*xxxxx&lang=scss Syntax Error: TypeError: this.getOptions is not a function
    VUE笔记:Failed to resolve loader: sass-loader
    VUE笔记:VScode(英文版)快速生成VUE模板的设置
    VUE笔记:运行VUE报错 Node Sass version 6.0.1 is incompatible with ^4.0.0.
    VUE笔记:yarn add sass-loader node-sass -dev
    Excel多条件求和:SUM和SUMIFS的综合使用
  • 原文地址:https://www.cnblogs.com/gm-201705/p/9864083.html
Copyright © 2011-2022 走看看