对抗静态分析——so文件的加密

【预备起~~~】
最近在忙找工作的事情,笔试~面试~笔试~面试。。。很久没有写(pian)文(gao)章(fei)。忙了一阵子之后,终于~~~到了选offer的阶段(你家公司不是牛吗,老子不接你家offer,哈哈哈哈~~~),可以喘(出)口(口)气(恶)了(气)。。。来来来,继续讨论一下抗静态分析的问题,这回要说的是如何对so文件进行加密。

【一二三四】
so文件的作用不明觉厉~~~不对是不言而喻。各大厂商的加固方案都会选择将加固的代码放到native层,主要因为native层的逆向分析的难度更大,而且代码执行效率高,对性能影响小。但是总有些大牛,对这些方法是无感的,为了加大难度,这些厂商更加丧心病狂的对so文件进行加固,比如代码膨胀、ELF文件格式破坏、字节码加密等等。这篇文章就是主要讲简单粗暴的加密,来窥探一下这当中的原理。

首先,我们都知道so文件本质上也是一种ELF文件,ELF的文件头如下

[C] 纯文本查看 复制代码

?


01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

#define EI_NIDENT 16

typedef struct elf32_hdr{

/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */

 unsigned char e_ident[EI_NIDENT];

 Elf32_Half e_type;

 Elf32_Half e_machine;

 Elf32_Word e_version;

/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */

 Elf32_Addr e_entry;

 Elf32_Off e_phoff;

 Elf32_Off e_shoff;

 Elf32_Word e_flags;

/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */

 Elf32_Half e_ehsize;

 Elf32_Half e_phentsize;

 Elf32_Half e_phnum;

 Elf32_Half e_shentsize;

/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */

 Elf32_Half e_shnum;

 Elf32_Half e_shstrndx;

} Elf32_Ehdr;

详细的就不说了,简单看下,开始的16字节是ELF文件魔数,然后是一些文件信息硬件、版本之类的,重点在几个变量
e_phoff、e_shoff、e_phentsize、e_phnum、e_shentsize、e_shnum、e_shstrndx
要知道这几个变量的含义首先要清楚,ELF文件的结构在链接时和执行时是不同的
<ignore_js_op> 
一般情况下(也就是我们看到的情况),ELF文件内部分为多个section,每个section保存不同的信息,比如.shstrtab保存段信息的字符串,.text装载可执行代码等等。这些不同的section根据不同的内容和作用会有不同的读写和执行权限,但是这些section的权限是没有规律的,比如第一个section的权限是只读,第二个是读写、第三个又是只读。如果在内存当中直接以这种形式存在,那么文件在执行的时候会造成权限控制难度加大,导致不必要的消耗。所以当我们将so文件链接到内存中时,存在的不是section,而是segment,每个segment可以看作是相同权限的section的集合。也就是说在内存当中一个segment对应N个section(N>=0),而这些section和segment的信息都会被保存在文件中。
理解了这个,再看那几个变量。e_phoff是segment头部偏移的位置,e_phentsize是segment头部的大小,e_phnum指segment头部的个数(每个segment都有一个头部,这些头部是连续放在一起的,头部中有变量指向这些segment的具体内容)。同样e_shoff、e_shentsize、e_shnum分别表示section的头部偏移、头部大小、头部数量。最后一个e_shstrndx有点难理解。ELF文件中的每个section都是有名字的,比如.data、.text、.rodata,每个名字都是一个字符串,既然是字符串就需要一个字符串池来保存,而这个字符串池也是一个section,或者说准备一个section用来维护一个字符串池,这个字符串池保存了其他section以及它自己的名字。这个特殊的section叫做.shstrtab。由于这个section很特殊,所以把它单独标出来。我们也说了,所有section的头部是连续存放在一起的,类似一个数组,e_shstrndx变量是.shstrtab在这个数组中的下标。(希望我解释清楚了~~~)
segment头部结构

[C] 纯文本查看 复制代码

?


01

02

03

04

05

06

07

08

09

10

11

12

typedef struct elf32_phdr{

 Elf32_Word p_type;

/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */

 Elf32_Off p_offset;

 Elf32_Addr p_vaddr;

 Elf32_Addr p_paddr;

 Elf32_Word p_filesz;

/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */

 Elf32_Word p_memsz;

 Elf32_Word p_flags;

 Elf32_Word p_align;

} Elf32_Phdr;

section头部结构

[C] 纯文本查看 复制代码

?


01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

typedef struct elf32_shdr {

 Elf32_Word sh_name;

/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */

 Elf32_Word sh_type;

 Elf32_Word sh_flags;

 Elf32_Addr sh_addr;

 Elf32_Off sh_offset;

/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */

 Elf32_Word sh_size;

 Elf32_Word sh_link;

 Elf32_Word sh_info;

 Elf32_Word sh_addralign;

/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */

 Elf32_Word sh_entsize;

} Elf32_Shdr;

注意这里都是32位的。。。

在代码当中segment的命名是program,所以segment和program指的是同一个东西
Program header位于ELF header后面,Section Header位于ELF文件的尾部。那可以推出:
e_phoff = sizeof(e_ehsize);
整个ELF文件大小 = e_shoff + e_shnum * sizeof(e_shentsize) + 1

这里多讲一点与加密没有关系的知识。我们知道了在内存当中只有segment而没有section,那么如果section结构被破坏了,ELF文件是不是还能正常执行?答案:是
如何证明大家可以自己去寻找答案,这里不多说。但是由于这样,所以经常会破坏文件的section结构,让比如IDA、readelf等工具失效,这也是so加固的一种方式。

回到正题,我们继续说加密。加密的流程我们设想一下,可以是这样 解析ELF——>找到字节码——>对字节码加密
解密就是 解析ELF——>找到字节码——>对字节码解密
详细一点就是通过偏移、个数等信息找到section的头部,然后看是不是我们要找的section(通过名字)。找到后通过sh_offset(偏移)和sh_size(大小),就找到这个section的内容,整体加密。

【二二三四】
下面看加密的代码

[C] 纯文本查看 复制代码

?


01

02

03

04

05

06

07

08

09

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

fd = open(argv[1], O_RDWR);        //打开文件

if(fd < 0){

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

  goto _error;

}

if(read(fd, &ehdr, sizeof(Elf32_Ehdr)) != sizeof(Elf32_Ehdr)){        //读取头部,验证文件是否正确

  puts("Read ELF header error");

  goto _error;

}

lseek(fd, ehdr.e_shoff + sizeof(Elf32_Shdr) * ehdr.e_shstrndx, SEEK_SET);//移动到shstrtab的头部

if(read(fd, &shdr, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)){//读取shstrtab头部

  puts("Read ELF section string table error");

  goto _error;

}

if((shstr = (char *) malloc(shdr.sh_size)) == NULL){//开辟内存区域,这个用于保存shstrtab的字符串池

  puts("Malloc space for section string table failed");

  goto _error;

}

lseek(fd, shdr.sh_offset, SEEK_SET);                //移动到shstrtab的字符串池

if(read(fd, shstr, shdr.sh_size) != shdr.sh_size){//读取字符串池

  puts("Read string table failed");

  goto _error;

}

lseek(fd, ehdr.e_shoff, SEEK_SET);                //移动到section头部数组的起始位置

for(i = 0; i < ehdr.e_shnum; i++){                //遍历section的头部

  if(read(fd, &shdr, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)){

    puts("Find section .text procedure failed");

    goto _error;

  }

  if(strcmp(shstr + shdr.sh_name, target_section) == 0){//找到目标section

    base = shdr.sh_offset;

    length = shdr.sh_size;

    printf("Find section %s\n", target_section);

    break;

  }

}

这一段是从打开文件到找到制定section的代码,我们为了减小实验难度,不会对一些重要的section加密(可能被玩坏),我们自己新建一个section,新建的方法之后说,所以这里的字符串target_section就是我们自己定义的section的名字。

[C] 纯文本查看 复制代码

?


01

02

03

04

05

06

07

08

09

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

lseek(fd, base, SEEK_SET);                //移动到目标section的内容上

content = (char*) malloc(length);

if(content == NULL){

  puts("Malloc space for content failed");

  goto _error;

}

if(read(fd, content, length) != length){//读取出来

  puts("Read section .text failed");

  goto _error;

}

nblock = length / block_size;

nsize = base / 4096 + (base % 4096 == 0 ? 0 : 1);

printf("base = %d, length = %d\n", base, length);

printf("nblock = %d, nsize = %d\n", nblock, nsize);

ehdr.e_entry = (length << 16) + nsize;//将sh_size和addr写到e_entry,简化解密流程

ehdr.e_shoff = base;

for(i=0;i<length;i++){

  content[/size][i][size=4] = ~content[/size][i][size=4];//整体异或

}

lseek(fd, 0, SEEK_SET);

if(write(fd, &ehdr, sizeof(Elf32_Ehdr)) != sizeof(Elf32_Ehdr)){//将头部写回

  puts("Write ELFhead to .so failed");

  goto _error;

}

lseek(fd, base, SEEK_SET);

if(write(fd, content, length) != length){//将内容写回

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

  goto _error;

}

找到之后就修改加密了,完成后写回。这个so就加密完成了。

【三二三四】
下面我们来看解密代码,首先先看两个函数申明

[C] 纯文本查看 复制代码

?


1

2

void printLog() __attribute__((section(".newsec")));

void init_printLog() __attribute__((constructor));

这两个函数之后都有__attribute__,这是GCC的编译选项,用于设定函数属性。__attribute__((section(".newsec")))的意思就是说这个函数将被放到.newsec这个section中,我们前面所说的自己新建section就是这样实现的。。。那么printLog这个函数就是.newsec的唯一内容。
下面一个是解密函数,constructor属性可以让代码在main之前执行,保证在比较早的时间点执行解密函数,不影响后续的代码。

[C] 纯文本查看 复制代码

?


1

2

3

4

void printLog()

{

  ALOGD("this is a log");

}

printLog代码很简单

[C] 纯文本查看 复制代码

?


01

02

03

04

05

06

07

08

09

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

void init_printLog()

{

  char name[15];

  unsigned int nblock;

  unsigned int nsize;

  unsigned long base;

  unsigned long text_addr;

  unsigned int i;

  Elf32_Ehdr *ehdr;

  Elf32_Shdr *shdr;

  base = getLibAddr();

  ehdr = (Elf32_Ehdr *)base;

  text_addr = ehdr->e_shoff + base;

  nblock = ehdr->e_entry >> 16;

  nsize = ehdr->e_entry & 0xffff;

  printf("nblock = %d\n", nblock);

  if(mprotect((void *) base, 4096 * nsize, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){

    puts("mem privilege change failed");

  }

  for(i=0;i< nblock; i++){

    char *addr = (char*)(text_addr + i);

    *addr = ~(*addr);

  }

  if(mprotect((void *) base, 4096 * nsize, PROT_READ | PROT_EXEC) != 0){

    puts("mem privilege change failed");

  }

  puts("Decrypt success");

}

解密过程,大多数差不多,需要注意两个地方一个是getLibAddr,用于获得内存中so的位置

[C] 纯文本查看 复制代码

?


01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

unsigned long getLibAddr(){

  unsigned long ret = 0;

  char name[] = "libdexloader.so";

  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, name)){

      temp = strtok(buf, "-");

      ret = strtoul(temp, NULL, 16);

      break;

    }

  }

_error:

  fclose(fp);

  return ret;

}

还有个是mprotect
这个函数用于修改内存页的权限,如果不修改,用户对于内存页的权限只有read,你是无法对内存中的数据进行修改的。这个和之前我们所说的segment的权限不一样,要注意区分。

【再来一次】
这种单独建一个section的方法简单粗暴易懂,但是只要解析一下就会知道多了一个section。所以实际上往往都是对固定的section进行加密解密,要注意的是这些section中有重要的信息,不能乱来,所以难度会大很多。大家有兴趣自己实现以下。
就酱~~~

时间: 2024-11-03 21:23:32

对抗静态分析——so文件的加密的相关文章

对抗静态分析——运行时修复dex

对抗静态分析——运行时修复dex 本文来源:i春秋社区-分享你的技术,为安全加点温度 零.写在前面 这个系列本来题目想写对抗反编译,可是想想对抗反编译的这个范围有点大,总结如下 灵魂作图 <ignore_js_op> 自己比较熟悉的是静态分析,所以就从这里入手吧,省的吹大了,挖了坑填不上那就不好了.当然也会写些动态分析的东西. 废话少说,开始吧,最近又把脱壳拿出来看了,就从这里开始吧 一.理论(总要解释一下) 壳是一个比较大的概念,发展的比较迅速.未来(或者现在?)加壳的主流会逐渐变为第四代壳

如何有效的对PDF文件进行加密保护

PDF是办公中保存资料数据文件不可或缺的一类电子文件工具软件,它的优势在于清晰的位图显示形式和良好的阅读体验,所以很多合同报告.电子书.技术文档.设计图纸等都越来越倾向这种存储方式.和普通的电子文档一样,如Word.Excel,PDF文件也存在信息安全泄漏风险,因此加密保护也是必不可少的.下面就来分享下如何对PDF进行权限设置和PDF文件加密操作. PDF格式的官方编辑器Adobe acrobat 软件为我们提供的口令加密包含"打开文档的口令"和"限制文档编辑打印口令&quo

某返利网站admin目录index.php文件混淆加密算法分析

---恢复内容开始--- 文件已经加密,可以在此下载:index.php 文件内容打开大概如此: 简单字符替换之后,发现字符串用base64_decode仍无法解码. 找到一个解码网站:找源码 解码后的文件如下:下载地址 尾部仍然有大量未知编码内容. 简单修改,改为 $ret = ($wmostynefr[].....); ..... print($ret) 然后运行该代码片段,>php tmp.php >output.php 得到output.php文件内容:output.php,还是存在大

十款免费文件夹加密软件推荐

到底文件加密软件哪个好?现在人们越来越注意隐私保护,而保护电脑中的一些重要文件,最好的方式就是加密.文件加密软件既要考虑到加密安全可靠,又要考虑到系统稳定,同时也要想到万一密码忘记该怎么挽回损失,再者就是方便易用.提到文件加密软件,很多电脑用户都非常熟悉.文件加密软件主要是用于给文件设置密码,从而保护文件信息安全的方法.目前,可供电脑用户选择使用的文件加密软件还是非常多的.那么,哪个文件加密软件更好用呢?今天,小编就给大家推荐10款好用的电脑文件加密软件,希望可以帮到大家! 10款免费文件夹加密

[AS3]as3用ByteArray来对SWF文件编码加密实例参考

[AS3]as3用ByteArray来对SWF文件编码加密实例参考,简单来说,就是将 swf 以 binary 的方式读入,并对 ByteArray 做些改变,再重新存成 swf 档.这个作业当然也可能应该是由 Server 进行 步骤一,随便建立一个 swf 当作要被加密的内容档案,怎么做不管,假设档名 asset.swf. 步骤二,做一个用来进行加密工作的 flash: var ul:URLLoader = new URLLoader(); ul.dataFormat = URLLoader

GPG实现文件的加密解密传输

一.rhel1--192.168.10.1--文件的解密端 rhel2--192.168.10.2--文件的加密段 二.rhel1配置如下: 1.查看是否安装所需的软件包(默认已经安装): 2.生成密钥: 3.查看已经生成的密钥: 4.导出公钥文件,并查看: 5.将公钥发送至rhel2: 三.rhel2配置如下: 1.导入rhel发送过来的公钥文件: 2.查看已经导入的公钥: 四.在rhel2上进行文件的的加密: 1.将passwd拷贝到tmp下,然后进行加密: 2.查看加密后的文件: 3.将加

好玩的文件加密方法(自己给文件头部加密)

下面是讲解一个给文件加密的小技巧: 先讲下概念:一般系统识别文件的方式大家都会认为是文件的拓展名,比如.txt .mp4等等,是这样的.但是其实很多时候系统识别文件还通过文件的头部(linux下是这样的)~~ 关于上面的概念大家可以测试一下: 比如你有个视频文件叫xxx.mp4,我把文件名改为xxx.mmmm但是系统(win下)右键鼠标,没有打开方式这一项了,但是你依然可以打开:从已安装程序中找到一个播放器(比如迅雷看看)就可以打开了(会有个提示),这说明文件内容跟文件名没有什么关系,废话是吧,

ZIP文件伪加密

312313123 题目给出图片,那当然是从图片下手啦! 首先下载图片,在Linux系统下用binwalk工具打开,果然不出所料,里面藏有文件! 用dd把它分解出来! 'txt' 格式的文件提取出来!会看到一个Zip压缩包 不能直接解密,文件被加密了,返回Windows(你也可以用Linux的分析工具) 打开二进制分析工具ultraedit  看到加密部分(即4B前面的50) 把50修改为00 ,不懂的就自己百度! 之后解压就得出key.txt文件: 最后答案: http://www.shiya

摆脱任何工具-简单代码让文件夹加密

电脑中或多或少的有很多敏感信息,尤其是大数据时代,信息一旦泄漏对我们造成的损失将会很大.有时候别人借用我们的电脑,有些信息不想让别人看到怎么办?有人会说有隐藏选项,但是,一旦开启显示隐藏文件,隐藏的文件夹又会暴露在视野里,那些用软件加密的软件又太复杂.下面的一段代码将让我们进行文件夹的加密. 加密手段实际上并不复杂,首先将加密文件夹改名成特殊名称:Control Panel.{21EC2020-3AEA-1069-A2DD-08002B30309D},然后将其属性改为隐藏+系统,加入判断语句则是