Back to blog
Mar 07, 2025
34 min read

Malware

Ransomware

Introduction

Before getting into every detail, I would like to mention that I have a section called References, where I will leave some useful links that may help you understand some topics I will be ‘talking’ about. Even though I tried to explain as much as I could, I am not a professional in this area, so I may have made some mistakes. If you find any mistakes, please let me know so I can fix them and learn from them.


I’m not going to start with the basic defintion of malware, i will try to talk about some general things but i would like to mention that in this blog i didn’t talk about ida pro basics or asm basics so it would be nice if you have some knowledge about these topics.

Sample Data:

  • Name: Unknown
  • MD5 Hash: MD5:D69EBD183B2E0072C396E55503D5EDE7
  • SHA256 Hash: 3FEF5C7FA519F5384DE6F61C954EAD6DFD4DA727005BFEC954DC801BD120A938
  • File Type: PE32+ executable
  • Language: Rust
  • Compiler: Visual Studio
  • Imports:
    • kernel32.dll : CrateEventW, CreateFileW, CreateMutexA, AcquireSRWLockExclusive, IsDebuggerPresent, GetSystemInfo…
    • bcrypt.dll
    • advapi32.dll
    • user32.dll
drawing

This blog post marks my first foray into malware analysis, where I examine a real-world sample. The sample in question is identified as SHA256[you can find it somwehre in sample data] and is detected as a ransomware variant that encrypts files on the infected system.


Now, let’s understand the purpose of MD5 and why it is used in malware analysis—if it’s even used 👀👀. After all, I’m not a malware analyst.

MD5:D69EBD183B2E0072C396E55503D5EDE7

SHA256: 3FEF5C7FA519F5384DE6F61C954EAD6DFD4DA727005BFEC954DC801BD120A938

In Malware Analysis, MD5 and SHA-256 are cryptographic hash functions employed to generate unique identifiers known as hash values for files. These hash values serve as digital fingerprints, providing analysts with crucial information that enables them to perform critical tasks such as:

-> File Identification and Classification
-> Data Integrity Verification
-> Data Searching

While MD5 has historically been popular , it is known that the MD5 algorithm has long been considered insecure for cryptographic purposes due to significant vulnerabilities. In contrast, we have SHA-256, which is of the SHA-2 family. The algorithm offers enhances security by generating a 256-bit hash value, making it more resistant to attacks.

drawing

Now let’s move to the analysis part. There are 2 types of analysis which are crucial in reverse engineering Static Analysis and Dynamic Analysis.. Let’s take a basic overview on these 2 types of Analysis , what’s the difference and what do we need them?

Static Analysis:

Static analysis is a method of “debugging” and acquiring information without running the actual software. It focuses on analyzing the structure, syntax, and code. This approach provides valuable insights that help us build a foundational understanding of how the software works.

  1. Understanding Malware Behavior:
    By dissecting the code, we can comprehend its intended operation, execution flow, and embedded functions. This step is crucial in malware analysis.

  2. Identifying Indicators of Compromise (IOCs):
    At this stage, we can extract “artifacts” such as IP addresses, domain names, and specific code patterns.

  3. Detecting Obfuscation Techniques:
    In the modern era, malware authors often employ obfuscation to conceal malicious code. Static analysis (and reverse engineering skills) helps identify these techniques, providing (or sometimes not) the possibility of deobfuscating the code and understanding its malicious intent.

    When do we use?

    I think it’s pretty clear when to use static analysis, first and foremost, before running the software, of course! 🙂

    Why?

    Pre-execution examination can reveal potential risks without actually running the software. Besides that, many malware samples include techniques to detect sandboxes. If they detect one, they might delete themselves or enter sleep mode, making analysis harder—so it’s better to avoid execution when possible. In the end, it’s all about gathering information about the software, which is crucial for our analysis.

Dynamic Analysis:

Now that we’ve gathered our information, we can move on to Dynamic Analysis. This involves executing the software (preferably within a secure, controlled environment — yeah, a sandbox, you’re damn right). This approach allows us to gain a much better understanding of the malware’s actions during execution

  1. Behavioral Observation: By running the malware, we can monitor its interaction with our OS, file systems, and other processes, providing crucial information about its potential impact.
  2. Detection of Evasion Techniques: As I mentioned before, the deobfuscation methods are related to the code, and now we’re referring to the process. There exists malware designed to detect analysis environments and alter its behavior. Dynamic analysis helps us identify these techniques, and we’re not only referring to altering their actions — there are also anti-debugging techniques implemented, which can sometimes be a barrier to understanding them.

When to use it?

So I think it’s actually pretty clear that this is supposed to be post-static analysis; it’s more about understanding additional behaviors that may not be evident from code examination alone. However, we should be aware that malware that changes its code structure or is compressed to evade detection can often be more effectively analyzed through dynamic execution. Also, there’s something called “behavioral signatures,” which can help enhance detection capabilities.


Before getting into analyzing the malware, i just want to let you know that i will provide a link to every tool i’ve used.

Now let’s gather some information about our malware maybe we can create a basic idea of how it works and what it does.

One tool that i love is DIE knows as Detect-It-Easy. It is a multi-tool that can give us a a huge amount of information about the software. But the point isn’t to talk how nice is the tool but how useful it’s therefore you can play with it.

Let’s analyze the file and see what can we find about it.

chunk

So we found that is a PE64 which means it was compiled for x86_64 architecture or known as 64-bit. It was used Microsoft Linker but meh, the language used was rust. So by knowing that Microsoft Linker was used we know already(aka tool field) that it was used Visual Studio to compile sooo if the executable wasn’t stripped of debugging symbol it could contain some useful metadata, such as function, maybe variables names and file paths. Not to mention that it can give insights into the binary’s structure, such as how the sections are aligned or how the data and code are organized.

Nextttt one in the line please. Okay if we check the advanced box we will get more functions that DIE can do for us like FIle Info, Memory Map, Hash, Entropy, Signatures and so many mores.

But Pe-Bear is also pretty good to analyze the software related to PE structure. Okay we can use DIE or PE-Bear it doesn’t really mater for this part but as for me PE-Bear gives me everything on my face and i can see everything that i need in one blink.

We already know that is an PE but DOS Header and DOS stub also tells us that so yeah :) So first of all i would take a look at the Sections category because based on the nr of sections you can say if the malware was packed or not (sometimes).

chunk

Now if we take a loot everything seems legit we got our Code, Data and Relocation section.

->.pdata section it contains some information about exception handling for functions.

chunk

Let’s take a look at the Section Headers as it offers much more information and we’re interesed in the Raw Size and the Virtual Size.

Now before getting into analyzing the basic idea of them let’s understand what’s Raw Size and Virtual Size.

-> Virtual Size :

This is the size of the section as it is loaded into memory when the program is executed. It includes any extra space reserved for the section such as padding and sometimes it may be larger.

-> Raw Size:

This is the size of the section as it appears on the disk file. It’s the actual size of the section, including any compressed or packed data.

So based o this we can say that when the Raw Size < Virtual Size, it may indicate that the section is packed. Why? Because in packed executables, the raw data is compressed to reduce file size of to obfuscate the content.

-> Upon loading into memory the data is unpacked to retore the original code and data => Virtual Size appears much larger.

chunk

Here is an approximate representation of how a packed software runs.

[Still consider the unpacked still packed because Unpacking Stub is a small piece of code that is responsible for decompressing/ unpacking the original executable code into memory]

As we can see, the .text, .rdata, and .reloc sections are almost the same. For now, we can assume that there is no packer.


Import Section

->The Import Section it refers to the part of the PE file format that lists external functions or symbols the executable requires from other modules in order to function correctly**.

[There will be a blog about DLL hijacking :) ]**

This section provides details about which external libraries and functions are being imported and used by the binary.

chunk

Let’s take a look we got each library that our software uses and if we click on it we can see further information about the functions that are used from that library. The scope isn’t me to talk about what’s a Original Thunk or First Thunk or Thunk itself.

Okay joking.

Let’us have a brief look each one of them has a hex-value which is approximately equivalent to a mem address but The OriginalFirstThunk named OTF points to an array of pointer-sized IMAGE_THUNK_DATA structures that specify the functions being imported. This table allows the Windows loader to identify and resolve the addresses of imported functions when the executable is loaded into memory.

First Thunk: Is a pointer to the Import Address Table(IAT), which is also an array of IMAGE_THUNK_DATA structure. Initially, the IAT is a copy of the ILT from the OFT. When the Executable is loaded, the loader resolves the addresses of imported functions and updated the IAT with the actual function pointers. This allows the program to directly call imported functions at their resolved addresses.

Thunk: It refers to a small piece that acts as an intermediary between the calling function and the actual function being called. Thunks are used to facilitate various operations, such as dynamic function resolution, argument marshalling or implementing callbacks. In the import mechanism, thunks are used to redirect functions calls to the appropriate addresses in the IAT.


There are few functions that i want to talk about:

  • CreateMutexA
  • CreateFileW
  • MoveFileExW
  • CreateEventW
  • IsDebuggerPresent

I just took a few functions that could help us to understand how does the software works, okay it is malware same thing.

CreateMutexA:

Microsoft: Creates or opens a named or unnamed mutex object.

Yay Microsoft thanks for the information, actually there is pretty good information at the Remarks section im going to attach the link to their webpage my point isn’t to re-write their stuff.

What is a mutex(mutual exclusion) is used to prevent multiple threads or processes from accessing a shared resource resource simultaneously which can lead to race conditions or data corruption.

What is a mutex object though? Microsoft-Mutex-Object

CreateFile and MoveFile are two functions that seem quite strange for now. Why does the software need to create a file and move it? Besides that, there is CreateEvent function , which doesn’t just imply a function to create and manipulate the path of a file, but also creates or opens events, making the software seem quite suspicious at the moment.

Yeah, creating and moving files isn’t really a sign of malicious activity, but we’ve got Mutexes, anti-debug techniques, and besides that, there are so many malware variants that have used CreateEventW. Therefore, we have something like proof that the software is starting to look more suspicious, not to mention the GetSystemInfo function that is called.

drawing

CreateEventW:

An event object is a sync. primitive that allows threats to communicate with each other. Usually used for coordinating activities between threads or processes.

As for the last function it checks if there is any debuggers attached to the program. I talked about it in my post : Introduction-Anti-Debugging

[I’m not going to analyze each library using theory, as I mentioned in the first part of the blog. Instead, we can actually read some static code, which may give a better interpretation of everything.]

As for now, i’m going to open IDA Pro and analyze some of the code functions.

chunk

This is will be the main block of code that you’re going to see, as we can see there is the __fastcall convention and it’s called main.

As for the explanation of how calling conventions works here is a source: Calling-Conventions

We have a few subroutines here so let’s check which is the main one, the last one: sub_1400A3F80 seems more of a block of code that works with the exception handles but we don’t really need it so let’s search for another subroutine. And boom we have only 2 of them so if it’s not the last one it’s the first one in this case, there are other few methods to search for the main block of the code but we’re already here so let’s search what sub_14000C240 hides from us.

chunk

Isn’t that beautiful nor the worst.

But just as some additional information: The disassembly may appear messy, with poorly structured functions, excessive jumps, or an overly complex control flow. This can happen due to the way Rust compiles code and interacts with the Windows toolchain.

; __unwind { // __CxxFrameHandler3
push    rbp
push    r15
push    r14
push    r13
push    r12
push    rsi
push    rdi
push    rbx
sub     rsp, 968h
lea     rbp, [rsp+80h]
movaps  [rbp+920h+var_50], xmm6
mov     [rbp+920h+var_58], 0FFFFFFFFFFFFFFFEh
lea     rcx, [rbp+920h+var_978] ; Dst
call    sub_14000D540
mov     [rbp+920h+var_5B], 1

So this seems to be the prologue as we can see that we’re saving the non-volatile registers , we can see the sub instruction that indicates that it reserve stack space for local variables, and we can observe lea rbp, [rsp+80h] that indicated the adjustment of the frame pointer

As i’ve been looking throughout the code i saw some stack and exception handling as for now that doesn’t really tell us anything about the malware and what is his intention buuut, i found something interesting and i will share it here.

cmp     dword ptr [rbp+920h+var_978], 2
jnz     loc_14000CF03

If the Zero Flag (ZF) is set to 0, the jump will be made to loc_14000CF03. We need the ZF to be 1 in order to jump to the code below.

mov     rax, qword ptr [rbp+920h+var_978+8]
test    rax, rax
jz      loc_14000CFA3

Jz means that the ZF is supposed to be 0 in order to make the jump. Somehow if the ZF is 0 it brings us to the loc_14… yeah you got it. :)

loc_14000CFA3:
lea     rdx, aTheFollowingRe_1 ; "The following required argument was not"...
mov     r8d, 35h ; '5'
mov     cl, 9
call    sub_140036F30
mov     rsi, rax
cmp     [rbp+920h+var_80], 0
jz      short loc_14000CFDD

Even though we cannot really call it an IOC, it tells us something like: We can run the malware from cmd and use some arguments.

chunk

As we can see in the image, there are multiple blocks that tell us that there is an argument that wasn’t provided. For now, I’m not sure why there are three blocks indicating that the argument was not provided.

aTheFollowingRe_1 db 'The following required argument was not provided: srv'
chunk

Thanks to IDA and not only there is DIE, there is Pe-Bear and so many tools that show us the strings inside a software.

Ughhh yeah breaking the rules but isn’t that bad , isn’t it?

C:\Users\ldmd>C:\Users\ldmd\Desktop\3fef5c7fa519f5384de6f61c954ead6dfd4da727005bfec954dc801bd120a938.exe -srv
error: the following required arguments were not provided:
  --key <K>

Usage: 3fef5c7fa519f5384de6f61c954ead6dfd4da727005bfec954dc801bd120a938.exe --key <K> --srv <SRV>

So, of course, I messed up the command to check the purpose of these args. Also, ‘k’ stands for key, and ‘srv’ may come from the server, which I realized after using the —help argument.

Usage: 3fef5c7fa519f5384de6f61c954ead6dfd4da727005bfec954dc801bd120a938.exe [OPTIONS] --key <K>

Options:
  -k, --key <K>    Password
  -p, --path <P>   Path [default: C:\]
  -s, --srv <SRV>  Service stop [default: 1]
  -h, --help       Print help

Now, this information makes me think that the creator may have wanted to write a debug part to check if the malware works, as the -k set might be used to decrypt the files. Still, this is just an assumption. Let’s take a note, the malware accepts parameters via command-line arguments to control it’s behavior.


Just a note, it seems that the assembly code uses lea instruction to push the arguments, in the most cases push or mov is used to load the arguments of a function.

mov     qword ptr [rbp+920h+var_978], rdi
lea     rax, sub_140009EC0
mov     qword ptr [rbp+920h+var_978+8], rax
lea     rax, off_1400E7108 ; "Password -  "
mov     qword ptr [rbp+920h+Dst], rax
mov     qword ptr [rbp+920h+Dst+8], 2
mov     [rbp+920h+var_3C0], 0
mov     qword ptr [rbp+920h+var_3D0], rbx
mov     qword ptr [rbp+920h+var_3D0+8], 1
mov     [rbp+920h+var_59], 1

loc_14000C868:
;   try {
lea     rcx, [rbp+920h+Dst]
call    sub_1400A8A00

RBP is the base pointer registers used for local variables i mean static, but that’s not my main point, as you can observe rbp is used to reference local variables.

 lea     rcx, [rbp+920h+Dst]

Rcx is the registers that is pushed as a function parameter. If we double click on the subroutine we get into the subroutine’s code.

__int64 __fastcall sub_1400A8A00(_QWORD)

As we can see our subroutines has 1 parameter. Besides that, as a reminder, IDA isn’t perfect, and sometimes it may not load parameters or detect the calling convention

Let’s analyze another part of code:

lea     rax, aStdout    ; "stdout"
mov     [rbp+20h+var_30], rax
mov     [rbp+20h+var_28], 6
call    sub_1400A87F0

The mov instruction is used to set up function parameters by storing them in memory locations relative to the base pointer (rbp). In this context, var_30 holds the address of the string we want to print ( “stdout”), and var_28 stores its length. [Assumptions]

As for now i will come back at this part if i will find anything interesting but let’s analyze those functions like CreateFile, MoveFile and so on to check what do they use them.


GetSystemInfo function

This function retrieve the information about the current system

void GetSystemInfo(
  [out] LPSYSTEM_INFO lpSystemInfo
);

lpSystemInfo is a pointer to a SYSTEM_INFO structure that receives the information.

Let’s analyze the SYSTEM_INFO structure:

typedef struct _SYSTEM_INFO {
  union {
    DWORD dwOemId;
    struct {
      WORD wProcessorArchitecture;
      WORD wReserved;
    } DUMMYSTRUCTNAME;
  } DUMMYUNIONNAME;
  DWORD     dwPageSize;
  LPVOID    lpMinimumApplicationAddress;
  LPVOID    lpMaximumApplicationAddress;
  DWORD_PTR dwActiveProcessorMask;
  DWORD     dwNumberOfProcessors;
  DWORD     dwProcessorType;
  DWORD     dwAllocationGranularity;
  WORD      wProcessorLevel;
  WORD      wProcessorRevision;
} SYSTEM_INFO, *LPSYSTEM_INFO;
Member NameDescription
**dwOemId**Obsolete member – No longer in use.
**wProcessorArchitecture**Specifies the processor architecture of the installed OS (e.g., x86, x64).
**dwPageSize**Defines the smallest chunk of memory the system can manage or allocate, typically in bytes.
**lpMinimumApplicationAddress**Denotes the highest memory address that an application can use.
**dwActiveProcessorMask**Indicates which processors are available, using a bitmask where each bit corresponds to a processor.
**dwNumberOfProcessors**The total number of logical processors that are active and available on the system.
**dwProcessorType**Obsolete member – No longer in use.
**dwAllocationGranularity**Defines the smallest block of memory that can be allocated by the system.
**wProcessorLevel**Represents the generation of the processor, often indicating the family or type of CPU.
**wProcessorRevision**Provides details on the specific revision of the processor, useful for identifying minor changes.
sub_1400A4DB0 proc near
.....
movaps  xmmword ptr [rsp+58h+SystemInfo.dwNumberOfProcessors], xmm0
movaps  xmmword ptr [rsp+58h+SystemInfo.lpMaximumApplicationAddress], xmm0
movaps  xmmword ptr [rsp+58h+SystemInfo], xmm0
lea     rcx, [rsp+58h+SystemInfo] ; lpSystemInfo
call    cs:GetSystemInfo
mov     eax, [rsp+58h+SystemInfo.dwNumberOfProcessors]
...

The MOVAPS instruction (Move Aligned Packed Single-Precision Floating-Point Values) moves a 128-bit chunk of data containing four single-precision floating-point values (each 32-bit) from one source to another.That is, it’s used to:

  • Load an XMM register from a 128-bit memory location.
  • Store the contents of an XMM register to memory.
  • Move data between XMM registers.

The most critical thing about MOVAPS is that it requires the memory address to be aligned to a 16-byte boundary that is, the

movaps  xmmword ptr [rsp+58h+SystemInfo.dwNumberOfProcessors], xmm0
movaps  xmmword ptr [rsp+58h+SystemInfo.lpMaximumApplicationAddress], xmm0
movaps  xmmword ptr [rsp+58h+SystemInfo], xmm0

These 3 lines of code clear some memory before calling GetSystemInfo. The memory being cleared is where the system information will be stored.

lea     rcx, [rsp+58h+SystemInfo] ; lpSystemInfo

This prepares the function argument for GetSystemInfo.

Think more of: We need to tell GetSystemInfo where to put the system details. So we grab, the address of a blank form and have it over.

call cs:GetSystemInfo

This calls the GetSystemInfo function. cs refers to the Code Segment. So cs:function means that the call is made to a function located within the code segment. The function will fill the SystemInfo structure with details about the system.

mov     eax, [rsp+58h+SystemInfo.dwNumberOfProcessors]

This extract the number of processors from the structure and puts it into the eax register.

In GDB Basic i talked about rax and eax. GDB-Basic Therefore test rax, rax is almost as the same as test eax, eax but as we know we’re in a x64 app not in a x32.

test    rax, rax
jz      short loc_1400A4DE6

Here we have a condition jump and you can translate like jump if ZF == 1, in other words if RAX == 0 then the ZF is set.

If ZF is set our code is beign redirected to loc_1400A4DE6:

lea rax, off_1400FF408 ; “The number of hardware threads is not known for the target platfom” mov [rsi+8], rax mov eax, 1


So if the number of hardware threads is not known then mov 1 inside eax which cand be a signal of an error.

Otherise:
```cpp
mov     [rsi+8], rax
xor     eax, eax
jmp     short loc_1400A4DF6

So, it may be used to detect if the sandbox and we stores the value of rax which it seems to holds the number of proc. in rsi+8 or we could say it stores the value of rax at memory location [rsi+8]

xox just sets eax 0 much faster than mom eax, 0.

Be pressing the magic key F5 we can read the disassembled code in something much more readable to humans.

memset(&SystemInfo, 0, sizeof(SystemInfo));
  GetSystemInfo(&SystemInfo);
  if ( SystemInfo.dwNumberOfProcessors )
  {
    a1[1] = SystemInfo.dwNumberOfProcessors;
    v2 = 0LL;
  }
  else
  {
    a1[1] = (__int64)&off_1400FF408;            // "The number of hardware threads is not known for the target platform"
    v2 = 1LL;
  }
  *a1 = v2;
  return a1;

As we can see we were right and eax (32 bit) or rax is the values that is returned. As for now it seems to be a sandbox evasion but let’s check where si this function used.

chunk

sub__bla_bla it’s kinda hard to read and keep them in our memory, let me change the name by pressing n.

chunk
lea     rcx, [rbp+20h+var_40]
call    sandbox_evasion_maybe
cmp     [rbp+20h+var_40], 0
jz      loc_14002A975

[rbp+20h+var_40] - it seems to be the variables that is set based on the return value. Therefore if RAX would be 0 then [rbp+20h+var_40] = 0. The next instruction says something like:

if ([rbp + 0x20 + var_40] == 0) {
    goto exit_block_code;
}

As of now, it seems that the malware tries to reduce its activity when it is run in a sandbox environment. Therefore, it makes sense why ANY.RUN reports that there isn’t anything malicious.

[Headache]


Let’s check the next function “CreateEventW”

CreateEventW is a Windows API function used to create or open an event object. Events are synchronization primitives that allow threads to signal each other when a certain condition is met.

HANDLE CreateEventW(
  [in, optional] LPSECURITY_ATTRIBUTES lpEventAttributes,
  [in]           BOOL                  bManualReset,
  [in]           BOOL                  bInitialState,
  [in, optional] LPCWSTR               lpName
);
Member NameDescription
**pEventAttributes**A pointer to a SECURITY_ATTRIBUTES structure, specifying security settings for the event.
**bManualReset**Defines whether the event is manual-reset or auto-reset:
- Truemanual-reset: The event stays signaled until explicitly reset using ResetEvent.
- Falseauto-reset: The event automatically resets to non-signaled after releasing a waiting thread.
**bInitialState**Determines the initial state of the event:
- TRUE → The event starts in the signaled state, allowing threads to proceed immediately.
- FALSE → The event starts in the non-signaled state, and threads must wait for it to be signaled.
**lpName**Name of the event. If NULL, the event is unnamed.
xor     ecx, ecx        ; lpEventAttributes
mov     edx, 1          ; bManualReset
mov     r8d, 1          ; bInitialState
xor     r9d, r9d        ; lpName
call    cs:CreateEventW

----

xor ecx, ecx  => lpEventAttributes = NULL
mov     edx, 1  => bManualReset = manual-reset-event
xor r9d, r9d => lpname=> Null (The event cannot be shared across processes; only threads within the same process can use it.)

If the function succeeds, the ret value is a handle to the event object, otherwise the return value is NULL;

By analyzing the created event is somehow related to the heap allocation which is more related to the process itself as for now i can’t really say a lot. [Beta]

MoveFileExW

BOOL MoveFileW(
  [in] LPCWSTR lpExistingFileName,
  [in] LPCWSTR lpNewFileName
);

lpExistingFileName -> This is a pointer to a wide-character string (UTF-16 encoded) that specifies the path of the file or directory to be moved or renamed.

lpNewFileName -> This is a pointer to a wide-character string (UTF-16 encoded) that specifies the new path or name for the file or directory.

mov     r15, [rbp+30h+lpExistingFileName]
mov     rcx, r15        ; lpExistingFileName
mov     rdx, rdi        ; lpNewFileName
mov     r8d, 1          ; dwFlags
call    cs:MoveFileExW  ; ;0x1 -> MOVEFILE_REPLACE_EXISTING
test    eax, eax

This function attempts to move or rename the file/directory, with the flag MOVEFILE_REPLACE_EXISTING specifying that the destination should be overwritten if it exists.

Still based on the code there isn’t such ifnormation to tell what;s moved or anything like this.

Another interesting thing is that it generates a file called HelloReadme.txt

chunk

The content of the file is:

chunk

For now i will move on to dynamic analysis, there is still information that can be extracted. The software checks if the pages are valid, uses DeviceIoControl to send a control code directly to a specified device driver, prompting it to perform the corresponding operation, and much more.


Dynamic Analysis

The sad reality is that this ransomware doesn’t actually run by itself; it needs external help to execute. Do you remember when I mentioned that the malware accepts parameters via the command line? ->Bingo. So what happens if I run the ransomware? Nothing. It will either close or perform some actions, but nothing significant.

Now this doesn’t mean we’ll stop here and say: oh then here is the sign to stop.

First of all, I’m interested in what the program does when I run it, even without providing arguments. For this, there is a tool called procman that can help us.

By pressing Ctrl + L, we can set a filter based on the process name. If the process name is found active, it will register every action the process performs.

chunk

”There are quite a few things it does even though it hasn’t started yet. Even though there are many SUCCESS messages, they are almost all related to creating, closing, opening, loading images, and closing threads.”


In order to debug the code i had to make a shortcut “Ransomware.exe” -k 3 Now when you run the exe the memory addresses from IDA PRO and the exe are not going to be the same maybe not even mine or yours. So what can you do is to go to Memory Map and search for you exe copy the address go to ida pro->Edit->Segments->Rebase and paste 0xcopied_value there and press the button to rebase.

->From here it will be kinda hard for me to give any screenshots but i will try my best.

```"ERROR: The process \"sql*\" not found.\r\n"
"ERROR: The process \"oracle*\" not found.\r\n"
"ERROR: The process \"ocssd*\" not found.\r\n"
"ERROR: The process \"dbsnmp*\" not found.\r\n"
"ERROR: The process \"synctime*\" not found.\r\n"
"ERROR: The process \"agntsvc*\" not found.\r\n"
"ERROR: The process \"isqlplussvc*\" not found.\r\n"
[I found the print function - yeah it's useless]

if you wish to check it:

loc_7FF6D4CEA278:
;   try {
lea     rsi, [rbp+12180h+var_21D0]
mov     rcx, rsi
call    maybee_print

“Here’s a tip for when you’re debugging the program.

chunk

Keep an eye on the RFlags to observe any changes. Pay attention to which registers change their values and note what those values are. This can help you understand the program’s behavior and what it’s trying to do.

Right now, I’m analyzing the “sub_7FF6D4D09020” function, and it seems this is the main “ransomware thing,” if we can call it that. :))

The fastcall convention in x64 uses 4 registers as arguments: RCX, RDX, R8, and R9.

One of the functions run by our dear non-malicious software is…

vssadmin.exe delete shadows /all /quiet /C

The command vssadmin.exe delete shadows /all /quiet /C is used to delete all shadow copies (also known as Volume Shadow Copies) on the specified volume (in this case, the C: drive) without displaying any messages or prompts. This command is often used by ransomware to remove backup copies, making it harder to recover files.

Besides that there is another command that is executed by the software:

```lea     rcx, [rax+1]
mov     [rbp+440h+var_1E8], rcx
shl     rax, 4
movups  xmm6, [rbp+rax+440h+var_3D8]
mov     r8d, 3
mov     rcx, r12
lea     rdx, aCmd       ; "cmd"
call    sub_7FF6D4D96F80
mov     r8d, 0C8h       ; MaxCount
mov     rcx, r14        ; Dst
mov     rdx, r12        ; Src
call    memcpy
lea     rax, aC_0       ; "/C"
mov     [rbp+440h+Src], rax
mov     [rbp+440h+var_1D0], 2
lea     rax, aCmdExe    ; "cmd.exe"
mov     [rbp+440h+var_1C8], rax
mov     [rbp+440h+var_1C0], 7
lea     rax, aC_1       ; "/c"
mov     [rbp+440h+var_1B8], rax
mov     [rbp+440h+var_1B0], 2
lea     rax, aTaskkill  ; "taskkill"
mov     [rbp+440h+var_1A8], rax
mov     [rbp+440h+var_1A0], 8
lea     rax, asc_7FF6D4DC8C22 ; "/f"
mov     [rbp+440h+var_198], rax
mov     [rbp+440h+var_190], 2
lea     rax, aIm        ; "/im"
mov     qword ptr [rbp+440h+var_188], rax
mov     qword ptr [rbp+440h+var_188+8], 3
movups  [rbp+440h+var_178], xmm6
mov     [rbp+440h+var_160], 7
xor     eax, eax
nop     dword ptr [rax+rax+00000000h]

Reading assembly isn’t exactly pleasing, is it?

drawing
mov     r8d, 3
mov     rcx, r12
lea     rdx, aCmd       ; "cmd"
call    sub_7FF6D4D96F80

Based on the call subroutine:

  • r8d is actually MaxCount.
  • rcx appears to be a pointer to some structure.
  • rdx is cmd.

There happens some memory manipulation so i will mark it as [Beta].

Besides that there is another command that is executed by the software:

lea     rdx, aCmd       ; "cmd"
lea     rsi, [rbp+440h+a1]
mov     r8d, 3          ; MaxCount
mov     rcx, rsi        ; a1
call    sub_7FF6D4D96F80
lea     rax, aC_0       ; "/C"
mov     [rbp+440h+Src], rax
mov     [rbp+440h+var_1D0], 2
lea     rax, aCmdExe    ; "cmd.exe"
mov     [rbp+440h+var_1C8], rax
mov     [rbp+440h+var_1C0], 7
lea     rax, aC_1       ; "/c"
mov     [rbp+440h+var_1B8], rax
mov     [rbp+440h+var_1B0], 2
lea     rax, aPowershell ; "powershell"
mov     [rbp+440h+var_1A8], rax
mov     [rbp+440h+var_1A0], 0Ah
lea     rax, aCommand   ; "-command "
mov     [rbp+440h+var_198], rax
mov     [rbp+440h+var_190], 9
lea     rax, aGetVmStopVmFor ; "\"Get-VM | Stop-VM -Force\""
mov     qword ptr [rbp+440h+var_188], rax
mov     qword ptr [rbp+440h+var_188+8], 19h
mov     qword ptr [rbp+440h+var_178+8], 6

Again, we can find the same call sub_7FF6D4D96F80. It may be used to write the command strings into a specific memory location, obfuscate its activities, or prepare data structures that are going to be used afterward.

Based on those 2 block of code we can observe the commands that are executed:

 cmd.exe /C "powershell -command \"Get-VM | Stop-VM -Force\""

This command is running cmd.exe with the /C option to execute the specified command and then terminate. The command being executed is powershell -command “Get-VM | Stop-VM -Force”, which uses PowerShell to stop all virtual machines (VMs) forcibly.

and

 cmd.exe /C "taskkill /f /im <process_name>"

This command runs cmd.exe with the /C option to execute the specified command and then terminate. The command being executed is taskkill /f /im <process_name>, which uses the taskkill command to forcefully (/f) terminate the specified process (/im <process_name>).

Something interesting though is that there is something that is kinda executed a little bit different:

cmd.exe/cpowershell-command \"Get-VM | Stop-VM -Force\"taskkill/f/imnetstopRAYON_NUM_THREADSRAYON_RS_NUM_CPUSfailed to write whole buffer

This is stored in rbp+x which means that it’s stored i nthe mrmorty ofcourse but this is the command that will be executed. It’ not as readeable as it shpuld be but we can assume that there is the third command executed okay not assume but observe:net stop

But there are 2 keywords that are interesting: RAYON_NUM_THREADS and RAYON_RS_NUM_CPUS. As i did some research are some variables that are used in the Rayon library. Old environment variable: RAYON_NUM_THREADS is a one-to-one replacement of the now deprecated RAYON_RS_NUM_CPUS environment variable. If both variables are specified, RAYON_NUM_THREADS will be preferred.

These are used in struct.ThreadPoolBuilder so it seems that this library is used to work with threads, stack, handlers and so on.

Do you remember about “sub_7FF6D4D97070” that i mentioned earlier, take a look here:

loc_7FF6D4D09857:
lea     rax, aSql_0     ; "sql*"
mov     [rbp+440h+a1], rax
mov     [rbp+440h+a1+8], 4
lea     rax, aOracle    ; "oracle*"
mov     [rbp+440h+var_3C8], rax
mov     [rbp+440h+var_3C0], 7
lea     rax, aOcssd     ; "ocssd*"
mov     [rbp+440h+var_3B8], rax
mov     [rbp+440h+var_3B0], 6
lea     rax, aDbsnmp    ; "dbsnmp*"
mov     [rbp+440h+var_3A8], rax
mov     [rbp+440h+var_3A0], 7
lea     rax, aSynctime  ; "synctime*"
mov     [rbp+440h+var_398], rax
mov     [rbp+440h+var_390], 9
lea     rax, aAgntsvc   ; "agntsvc*"
mov     [rbp+440h+var_388], rax
mov     [rbp+440h+var_380], 8
lea     rax, aIsqlplussvc ; "isqlplussvc*"
mov     [rbp+440h+var_378], rax
mov     [rbp+440h+var_370], 0Ch
lea     rax, aXfssvccon ; "xfssvccon*"
mov     [rbp+440h+var_368], rax
mov     [rbp+440h+var_360], 0Ah
lea     rax, aMydesktopservi ; "mydesktopservice*"
mov     [rbp+440h+var_358], rax
mov     [rbp+440h+var_350], 11h
lea     rax, aOcautoupds ; "ocautoupds*"
mov     [rbp+440h+var_348], rax
mov     [rbp+440h+var_340], 0Bh
lea     rax, aEncsvc    ; "encsvc*"
mov     [rbp+440h+var_338], rax
mov     [rbp+440h+var_330], 7
lea     rax, aFirefox   ; "firefox*"
mov     [rbp+440h+var_328], rax
mov     [rbp+440h+var_320], 8
lea     rax, aTbirdconfig ; "tbirdconfig*"
mov     [rbp+440h+var_318], rax
mov     [rbp+440h+var_310], 0Ch
lea     rax, aMydesktopqos ; "mydesktopqos*"
mov     [rbp+440h+var_308], rax
mov     [rbp+440h+var_300], 0Dh
lea     rax, aOcomm     ; "ocomm*"
mov     [rbp+440h+var_2F8], rax
mov     [rbp+440h+var_2F0], 6
lea     rax, aDben50    ; "dben50*"
mov     [rbp+440h+var_2E8], rax
mov     [rbp+440h+var_2E0], 7
lea     rax, aSqbcoreservice ; "sqbcoreservice*"
mov     [rbp+440h+var_2D8], rax
mov     [rbp+440h+var_2D0], 0Fh
lea     rax, aExcel     ; "excel*"
mov     [rbp+440h+var_2C8], rax
mov     [rbp+440h+var_2C0], 6
lea     rax, aInfopath  ; "infopath*"
mov     [rbp+440h+var_2B8], rax
mov     [rbp+440h+var_2B0], 9
lea     rax, aMsaccess  ; "msaccess*"
mov     [rbp+440h+var_2A8], rax
mov     [rbp+440h+var_2A0], 9
lea     rax, aMspu      ; "mspu*"
mov     [rbp+440h+var_298], rax
mov     [rbp+440h+var_290], 5
lea     rax, aOnenote   ; "onenote*"
mov     [rbp+440h+var_288], rax
mov     [rbp+440h+var_280], 8
lea     rax, aOutlook   ; "outlook*"
mov     [rbp+440h+var_278], rax
mov     [rbp+440h+var_270], 8
lea     rax, aPowerpnt  ; "powerpnt*"
mov     [rbp+440h+var_268], rax
mov     [rbp+440h+var_260], 9
lea     rax, aSteam     ; "steam*"
mov     [rbp+440h+var_258], rax
mov     [rbp+440h+var_250], 6
[there are few processes more ]

Is a set of processes and those commands are applied to them. So maybe the sub_7FF6D4D97070 could be i mean it’s related to a rust function to execute cmd code or something like this.

First of all it seems that we load each process name inside a some sort of struct and after that we check if the process is alive or not.

mov     rsi, [rbp+440h+var_1D0]
mov     r8, [rbp+440h+var_1C8]
mov     rbx, [rbp+440h+var_1C0]
mov     rax, [rbp+440h+var_1B8]
mov     [rbp+440h+var_70], rax
mov     r15, [rbp+440h+var_1B0]
;   } // starts at 7FF6D4D09CC5

loc_7FF6D4D09D2A:
;   try {
lea     rcx, [rbp+440h+var_110]
mov     [rbp+440h+var_68], rdx
call    sub_7FF6D4DA7180
mov     r13, r12
mov     [rbp+440h+var_5A], 1
;   } // starts at 7FF6D4D09D2A

loc_7FF6D4D09D47:
;   try {
lea     rcx, [rbp+440h+var_F8]
mov     r12, rbx
mov     rdx, rbx
mov     r8, r15
call    sub_7FF6D4DA7180
mov     rax, [rbp+440h+var_100]
mov     [rbp+440h+var_80], rax
movups  xmm0, [rbp+440h+var_110]
movaps  [rbp+440h+var_90], xmm0
mov     rax, [rbp+440h+var_E8]
mov     [rbp+440h+var_D0], rax
movups  xmm0, [rbp+440h+var_F8]
movaps  [rbp+440h+var_E0], xmm0
;   } // starts at 7FF6D4D09D47

loc_7FF6D4D09D94:
;   try {
lea     r15, [rbp+440h+var_90]
mov     rcx, r15
mov     rbx, rdi
mov     rdx, rdi
call    sub_7FF6D4DA67D0
...
I'm not going to paste the whole code >:(

This function is quite long, but it’s more like a loop. Imagine you wrote a loop to check if every process exists: if yes, kill it, delete backups, move to the next one, and so on until you’ve gone through every desired process. So we have an array of processes Now this data is based on dynamic analysis and i can’t really upload the same screen of the same function 9000 times. Actually i can sorry :)

chunk

Conclusion

drawing

How does it work?

  • The malware checks if it’s running in a sandbox environment. [False]

    As i could observe the program didn’t try to close itself or anything like this.

  • IsDebuggerPresent():

    The malware may check if it’s being debugged, but I didn’t see any signs of this therefore i could assume that there was no thread to analyze if there is a debugger attached to the process in real time.

  • The malware deletes shadow copies.

    The malware deletes shadow copies to prevent the user from restoring their files from backups.

  • The malware kills processes.
  • The malware executes commands to stop VMs[i’m not sure] and kill processes.
  • The malware uses the Rayon library to work with threads.
  • The VM will crash out because of the encryption 👍🏻[True]

Yeah, I didn’t analyze every function in depth, as there’s a lot to discuss, but I tried my best to generalize them.

Besides that, I’m not sure if GetSystemInfo is used as a sandbox evasion or not. But if that comparison isn’t used for this, it may be used to check if it is a sandbox in order to run without an argument if it’s not a virtual machine. As it seems, the malware is not running without one, at least on my machine.


I will conclude the blog post here. The scope was to analyze the malware and understand its behavior as much as possible. [Yeah, I forgot to break a call to stop the encryption, so my VM crashed.] I didn’t intend to write such a long post, but it seems the malware is much more complex than crackmes, which is understandable. I didn’t go through the process of generating any YARA rules, as there are already pre-existing ones, and I’m actually tired. I will write a blog about how to work with YARA rules in the future.

If you’re a malware analyst, security researcher, or someone who does this as a hobby and have recommendations or feedback, I’m open to them, as this is my first malware-related blog.

Discord: ldmd

References:

Tools: