5 min read

TryHackMe Basic Malware RE Walkthrough: Static Analysis with Ghidra

TryHackMe Basic Malware RE Walkthrough: Static Analysis with Ghidra

Room Info

  • Platform: TryHackMe
  • Room Name: Basic Malware RE
  • Category: Malware Analysis / Reverse Engineering
  • Room Link: https://tryhackme.com/room/basicmalwarere
  • Difficulty: Easy
  • ZIP Password: MalwareTech
  • Learning Objective: Fundamentals of static malware analysis
  • Note: These archives really do contain live malware, and Windows will delete them automatically the moment you run strings or file on them. So you may want to temporarily disable your antivirus first. If you're purely interested in reversing and not yet ready to dive into malware analysis, feel free to skip this room if you have any concerns!

Introduction

This room contains three challenges (strings1, strings2, strings3), all designed to teach static analysis techniques. Every task shares one thing in common:

  • When the program runs, it computes and displays the MD5 hash of some string
  • Our job is to find that original string (the flag) using reverse engineering tools, without running the program

Golden rule: 👉 Never run the executable! This is the cardinal rule of malware analysis. If you absolutely must run it, do so in a sandbox or a virtual machine to avoid infecting your machine.

Tooling

Essential Tools

  • Ghidra: A free, open-source decompiler (https://ghidra-sre.org/)
  • strings: Linux's built-in string extraction utility
  • md5sum: For verifying answers

Environment Recommendations

  • A Linux virtual machine (Kali, Ubuntu, etc.)
  • Or Windows + WSL
  • An isolated environment (never analyze malware samples on your main system)

Challenge 1: strings1.exe

Task Description

This executable prints an MD5 Hash on the screen when executed. Can you grab the exact flag? Note: You don't need to run the executable!

Walkthrough

Step 1: Basic Recon

First, check the file type:

file strings1.exe_
# strings1.exe_: PE32 executable (GUI) Intel 80386, for MS Windows, 6 sections

md5sum strings1.exe_
# 76b58619d2834419e82e0f6a605c8811

Step 2: String Scan (Hitting a Trap)

strings strings1.exe_ | head -50

What we find:

  • ⚠️ Thousands of fake flags in FLAG{...} format
  • Suspicious string: "We've been compromised!"
  • API calls: MessageBoxA, sprintf, %02x

Spotting the trap: The flood of fake flags is a classic anti-analysis technique, designed to throw off the naive strings approach.

Step 3: Ghidra Decompilation Analysis

3.1 Import and Analyze

  1. Open Ghidra and create a new project
  2. Import strings1.exe_
  3. Run the automatic analysis (Analysis → Auto Analyze)

3.2 Locate the entry Function

In the Symbol TreeFunctions, find the entry function:

void entry(void)
{
  char *lpText;

  lpText = md5_hash(PTR_s_FLAG{CAN-I-MAKE-IT-ANYMORE-OBVIO_00432294);
  MessageBoxA((HWND)0x0, lpText, "We've been compromised!", 0x30);
  ExitProcess(0);
}

Program logic:

  1. Calls the md5_hash() function on some string
  2. Displays the MD5 result via a MessageBox
  3. The title is "We've been compromised!" ← this is not the content being hashed!

3.3 Follow the Pointer Chain (the key step)

First hop - double-click PTR_s_FLAG{CAN-I-MAKE-IT-ANYMORE-OBVIO_00432294:

PTR_s_FLAG{CAN-I-MAKE-IT-ANYMORE-OBVIO_00432294 XREF[1]: entry:004022b4(R)
00432294 28 48 42 00     addr    s_FLAG{CAN-I-MAKE-IT-ANYMORE-OBVIO_00424828
                                 = "FLAG{CAN-I-MAKE-IT-ANYMORE-OB"

→ This is a pointer, pointing to address 0x00424828

Second hop - double-click s_FLAG{CAN-I-MAKE-IT-ANYMORE-OBVIO_00424828:

s_FLAG{CAN-I-MAKE-IT-ANYMORE-OBVIO_00424828     XREF[2]: entry:004022b9(*), 00432294(*)
00424828 46 4c 41        ds      "FLAG{Redacted}"
         47 7b 43
         41 4e 2d

🎯 Found the real flag!

Step 4: Verify the Answer

echo -n "FLAG{Redacted}" | md5sum
# 4c827c4ca62781d707cd049da13539ee

Program Behavior, Diagrammed

┌────────────────────────────────────────────┐
│ entry()                                    │
│                                            │
│ 1. Read pointer 0x00432294                  │
│    └→ points to 0x00424828                 │
│       └→ "FLAG{CAN-I-MAKE-IT-ANYMORE-      │
│           OBVIOUS}"                        │
│                                            │
│ 2. Compute MD5                              │
│    └→ 4c827c4ca62781d707cd049da13539ee    │
│                                            │
│ 3. Show in MessageBox                       │
│    Title: "We've been compromised!"        │
│    Text: (MD5 hash)                        │
└────────────────────────────────────────────┘

Flag

FLAG{Redacted}

MD5: 4c827c4ca62781d707cd049da13539ee

Key Takeaways

🎯 Recognizing Anti-Analysis Techniques

  • A flood of fake flags: used to overwhelm the strings tool
  • Multi-level pointers: the real data hides behind a pointer to a pointer
  • Misleading strings: the MessageBox title looks important but is really a smokescreen

Challenge 2: strings2.exe

Task Description

Same as strings1: find the original string whose MD5 is computed.

Walkthrough

Step 1: Ghidra Decompilation

Open strings2.exe straight in Ghidra and find the entry function:

void entry(void)
{
  char local_2c [36];
  char *local_8;

  builtin_strncpy(local_2c, "FLAG{STACK-STRINGS-ARE-BEST-STRINGS}", 0x24);
  local_8 = md5_hash(local_2c);
  MessageBoxA((HWND)0x0, local_8, "We've been compromised!", 0x30);
  ExitProcess(0);
}

🎯 The full flag is right there! Even easier than strings1!

Step 2: Verify

echo -n "FLAG{Redacted}" | md5sum
# e80782d8c30671eb61acc63c5dca914e

Why Is This One Actually Easier?

The original design intent.
Per MalwareTech's teaching design, strings2 was supposed to make learners:

  1. Look at the stack operations in the assembly
  2. Manually parse the hex value of each byte
  3. Convert the hex to ASCII characters to reconstruct the string

You were originally supposed to see something like this:

mov    BYTE PTR [ebp-0x2c], 0x46    ; 'F'
mov    BYTE PTR [ebp-0x2b], 0x4c    ; 'L'
mov    BYTE PTR [ebp-0x2a], 0x41    ; 'A'
mov    BYTE PTR [ebp-0x29], 0x47    ; 'G'
; ... more byte-by-byte assignments

But Ghidra Is Too Smart!

Ghidra's decompiler recognizes this as a strncpy operation and displays the full string directly, which is exactly why this challenge turns out to be the easiest of the three!

Verifying with the strings Tool

strings strings2.exe_ | grep "STACK"
# (no results)

❌ Nothing! Because the string is built dynamically on the stack and doesn't exist in any static data section.

Technical Comparison

Trait strings1 strings2
Storage location .rdata (static) Stack (dynamic)
Visible to strings ✅ (buried among fake flags) ❌ completely invisible
Ghidra difficulty Follow 2 levels of pointers Displayed directly
Hiding technique Flood of fake flags Stack strings

Flag

FLAG{Redacted}

MD5: e80782d8c30671eb61acc63c5dca914e

Key Takeaways

🎯 The Stack Strings Technique

  • Definition: building a string dynamically on the stack at runtime, rather than hardcoding it in a data section
  • Purpose: to evade static scanning tools like strings
  • Detection: can only be spotted through decompilation or disassembly

🤔 The Flag's Little Joke

"STACK-STRINGS-ARE-BEST-STRINGS" says exactly what it means — because they're so effective at slipping past simple static analysis!

Challenge 3: strings3.exe

Task Description

Same as the first two challenges: find the original string whose MD5 is computed.

Walkthrough

Step 1: Ghidra Decompilation

Open strings3.exe in Ghidra and find the entry function:

void entry(void)
{
  CHAR local_4a4;
  undefined1 local_4a3 [1027];
  char *local_a0;
  MD5 local_9c [144];
  HRSRC local_c;
  undefined4 local_8;

  MD5::MD5(local_9c);
  local_4a4 = '\0';
  memset(local_4a3, 0, 0x3ff);

  // Find the resource
  local_c = FindResourceA((HMODULE)0x0, "rc.rc", &DAT_00000006);

  // Load the string resource with String ID = 0x110 (272)
  local_8 = 0x110;
  LoadStringA((HINSTANCE)0x0, 0x110, &local_4a4, 0x3ff);

  // Compute MD5
  local_a0 = MD5::digestString(local_9c, &local_4a4);
  MessageBoxA((HWND)0x0, local_a0, "We've been compromised!", 0x30);
  ExitProcess(0);
}

Step 2: Understand the Program Logic

Key observation:

LoadStringA((HINSTANCE)0x0, 0x110, &local_4a4, 0x3ff);
  • The program uses the Windows API LoadStringA()
  • It loads the string with String ID = 0x110 (decimal 272) from the resource table
  • This string is stored in the PE file's Resource Section (.rsrc)

Step 3: Locate the Resource in Ghidra

3.1 Open Program Trees

  1. Menu: WindowProgram Trees
  2. Expand it and find the .rsrc section
  3. Double-click into .rsrc

3.2 Browse the String Tables

In the Listing window, you'll see a structure like this:

**************************************************************
* Rsrc_StringTable_8_409 Size of resource: 0x55c bytes      *
**************************************************************
Rsrc_StringTable_9_409
00408xxx ... p_unicode  u"FLAG{...}" Rsrc String ID 128
00408xxx ... p_unicode  u"FLAG{...}" Rsrc String ID 129
...

3.3 Find String ID 272

Windows String Resource structure:

  • Each String Table holds 16 strings
  • String ID 272 = 17 × 16
  • So it lives in Rsrc_StringTable_12_409 (0x11 = 17)

What we find:

Rsrc_StringTable_12_409                    XREF[1]: entry:004022ff(*)
0040aef0 27 00 46   p_unicode  u"FLAG{Redacted}"
         00 4c 00                                              Rsrc String ID 272
         41 00 47

🎯 Found the flag!

Step 4: Verify the Answer

echo -n "FLAG{Redacted}" | md5sum
# 1011cafbd736cdf2ae90964613c911fe

Windows PE Resource Table Basics

What Is the Resource Section?

A PE file's .rsrc section can store:

  • 🖼️ Icons
  • 📝 String Tables
  • 🎵 Audio files
  • 📋 Dialog definitions
  • 🗂️ Arbitrary binary data (RCDATA)

Why Use the Resource Section?

Legitimate uses:

  • Multi-language support (strings in different languages)
  • Separating UI resources from program logic
  • Easier resource management and updates

Flag

FLAG{Redacted}

MD5: 1011cafbd736cdf2ae90964613c911fe

Key Takeaways

🎯 The Resource Hiding Technique

  • Definition: stashing sensitive data inside the PE resource table
  • Purpose: to evade static scanning while supporting a modular design
  • Detection: requires dedicated PE analysis tools

🤔 The Flag's Little Joke

"RESOURCES-ARE-POPULAR-FOR-MALWARE" — the flag itself is teaching you a real-world attack technique!

Lessons Learned

Technical Comparison Table

Trait strings1 strings2 strings3
Storage location .rdata (static data) Stack .rsrc (resource table)
strings visibility ✅ visible (but obfuscated) ❌ invisible ❌ invisible
Ghidra difficulty ⭐⭐ follow pointers ⭐ displayed directly ⭐⭐ inspect resource table
Anti-obfuscation technique 4000+ fake flags Stack strings Resource hiding
Real-world case Statically embedded config API hashing Multi-language / modular malware
Time to complete ~15 minutes ~5 minutes ~10 minutes