一种简单的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\n", ".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\n", dyn_symtab);
                }

                if(dyn.d_tag == DT_STRTAB){
                        dyn_strtab = dyn.d_un.d_ptr;
                        flag++;
                        printf("Find .dynstr, addr = 0x%lx\n", dyn_strtab);
                }

                if(dyn.d_tag == DT_STRSZ){
                        dyn_strsz = dyn.d_un.d_val;
                        flag++;
                        printf("Find .dynstr size, size = 0x%x\n", dyn_strsz);
                }

        }
        if(flag != 3){
                puts("Find needed .section failed\n");
                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\n", 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\n", argv[1]);
                return (-1);
        }

        if (find_target_section_addr(fd, sec_name) == -1) {
                printf("Find section %s failed\n", sec_name);
                return (-1);
        }

        if (get_target_func_info(fd, func_name, &info) == -1) {
                printf("Find function %s failed\n", 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\n", 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.
[email protected]:~/_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整体加密。

时间: 2024-11-15 00:31:55

一种简单的ELF加固方法的相关文章

推荐一种简单易学的读书方法

推荐一种简单易学的读书方法 本文由有道云笔记推荐 前段时间我简单统计了一下,从大学毕业后到现在的6年多时间里,总共读了200多本书,平均1年读20-40本,范围涉及企业管理.营销.励志.传记.小说.历史.哲学等等.书读的多了,慢慢的也形成了一套自认为适合自己的读书方法,可以简单地用12个字进行概括:"定目标.列书单.读两遍.致运用".下面我会仔细地跟大家聊聊. 一.定目标(确定读书目标)人的时间和精力是有限的,因此读书.学习都存在成本.为了能够以最小的投入获得最大的产出,我们一定要先确

Gradle实现的两种简单的多渠道打包方法

Android多渠道打包Gradle多渠道打包友盟多渠道打包productFlavors 本来计划今天发Android的官方技术文档的翻译——<Gradle插件用户指南>的第五章的,不过由于昨天晚上没译完,还差几段落,所以只好推后了. 今天就说一下使用Gradle进行类似友盟这样的多渠道打包的方法吧. 本文原创,转载请注意在CSDN上的出处: http://blog.csdn.net/maosidiaoxian/article/details/42000913 目前我掌握的方法有两种,都非常简

IOS几种简单有效的数组排序方法

//第一种,利用数组的sortedArrayUsingComparator调用 NSComparator ,obj1和obj2指的数组中的对象 NSComparator cmptr = ^(id obj1, id obj2){  if ([obj1 integerValue] > [obj2 integerValue]) {         return (NSComparisonResult)NSOrderedDescending;     }       if ([obj1 integerV

一种简单的图像修复方法

该方法可以用于美颜中的祛斑,通过快速迭代的方式去除斑点. 假设输入图像为: 计算方向权重: 对于其他方向,操作类似We. 最终的输出为: 对要修复的区域,反复进行同样的操作即可. 简单的matlab仿真代码如下: clear clc close all % 设定迭代次数 iter=100; InputData = imread('anish.jpg'); InputData = rgb2gray(InputData); InputData = double(InputData); [height

一种简单的对象赋值方法,定义实例后以{}赋值,比传统方法更简洁

public class Rectangle    {        public Point TopLeft { get; set; }        public Point BottomRight { get; set; }    } static void CompareObjectInitMethods()    {        // 传统初始化方法        Rectangle r = new Rectangle();        Point p1 = new Point()

一种简单实用的全屏方法

实现程序全屏幕显示的思路有很多种,最常见的一种就是: 1)利用Windows API提供的一个结构体WINDOWPLACEMENT来存储全屏显示前视图和主框架窗口在屏幕上的位置和显示信息.非客户区窗口的显隐信息,以便全屏幕显示时恢复窗口原状: 2)通过API函数GetDesktopWindow()得到桌面窗口,由GetWindowRect()得到显示器的尺寸: 3)通过AdjustWindowRect()来计算当客户区大小为屏幕大小时相应的窗口大小: 4)通过SetWindowPlacement

一种简单快速的模板解析方法,活用with javascript版

//一种简单快速的模板解析方法,活用with var parseTpl = function( str, data ) { var tmpl = 'var __p=[];' + 'with(obj||{}){__p.push(\'' + str.replace( /\\/g, '\\\\' ) .replace( /'/g, '\\\'' ) .replace( /<%=([\s\S]+?)%>/g, function( match, code ) { return '\',' + code.

元素水平居中和垂直居中的几种简单方法

一.如何使元素在水平方向上居中 1. 使用text-align:center. 在父级元素的CSS样式设置text-align:center.把内部嵌套一个div把它当作文本来对待,不过这个方法有时候是不管用的. 2.在需要居中的元素设置margin:0 auto.     这句CSS的意思为:设置margin-left和margin-right为自动适应父级元素宽度.所以可以使元素水平居中,不过记得设置其width. 二.如何使元素在垂直方向上居中 1.使图片在垂直方向上居中. div{bac

.net 签名加密实现的一种简单方法

加密方法有很多,以下是其中一种简单的签名模式 1.首先客户端通过webapi按照IP地址,时间戳,随机数生成签名,并传递序列号 private Result_Sign Valid()        {            string ServerIP = "192.168.1.6";// HttpContext.Request.ServerVariables.Get("Local_Addr").ToString(); //地址            string