PowerLoaderV2 Full Code Analysis| Log file function. [Part 3]

Mahmoud NourEldin
14 min readJan 15, 2023

--

Assalamu-Alikum

This write-up is for Beginners only to teach them How To code analysis and if you need a brief story just see my Linkedin.

In the last two articles, We analyze this malware statically and behaviorally so if you don’t see it just read them to understand this writeup.

سبحان الله وبحمده سبحان الله العظيم

So First before we start, I assume you understand Malware Analysis Process and know IDA, x64Debugger, and Ghidra.

Please don’t continue this article if you don’t know static analysis and dynamic analysis because this article is based on your understanding of the Malware Analysis process.

لو أنت عربي فالأفضل تكون مخلص الجزء الأول والثاني من كورسيس موقع مهاره تك للبشمهندس أحمد بهاء لأن المقال دا مخصص للأمثله الواقعيه المبنيه على فهمك للكورسيس دي فلو مش مخلصهم فالأفضل ليك تخلصهم وتفهمهم كويس وبعدين تيجي تكمل المقال علشان تستفاد منه.

اضغط هنا تشوف كورس اساسيات تحليل البرمجيات الخبيثه وكورس الهندسه العكسيه للبرمجيات الخبيثه

First: You should Read Part 1 and Part 2 But I advise you to analyze the malware by yourself and then read the articles.

How to Code Analysis?

After knowing what can malware do in the behavioral analysis we need to know more details and more info about the log file and network communications and also registry keys so it’s important to Code Analysis for details.

But how?

If it’s not packed and we saw in Part 1 it’s not and I said why not, just open it from any disassembler and debugger to Code Analysis. I prefer IDA with x64 Debugger, you can choose your tools.

figure(1): IDA main view of PowerLoaderV2 Malware

So here are our Assembly instructions for The Malware [ Start function ] and sub short for subroutines which mean functions or methods.

So What’s the 1st step?

I got this technique from Eng. Amr Thabet advised me to rename all functions to their behavior like this:

figure(2): The 1st sub for PowerLoaderV2 Malware.

What is the behavior of this subroutine? just checking MZ, and PE signatures so we will rename it to MZchecks [We Will explain later in detail].

figure(3): The 2nd sub for PowerLoaderV2 Malware.

Here It takes SystemInfo so let’s rename it to GetSystemInfo.

figure(4): The 3rd sub for PowerLoaderV2 Malware.

Here what can this subroutine do? I think it is related to privilege escalation so let’s rename it. [ We won’t analyze just renaming it and I don’t know if it’s related to privilege escalation or not we will see later ].

so rename all subroutines and this our code before renaming:

figure(5): The start of PowerLoaderV2 Malware.

And this is our code after renaming:

figure(6): The start of PowerLoaderV2 Malware.

So How about our analysis now? don’t thank me hhh.

So What’s the 2nd step?

If you want only important info just go to the network APIs, Registry keys, the processes created, file system APIs, or any important APIs like exported APIs or any important once.

But here in our writeup, we will explain every command and every API because it’s for beginners like me xD.

so our next step now analyzes everything let’s go:

Here as we can see 11 exported functions include the main function.

we can understand their functionalities from their name: downloading from C2C and executing the 2nd stage, downloading from C2C and updating the last stage, injecting the code into processes[maybe], writing logs of the malware, and writing its config.

figure(7): Exported Functions from IDA Freeware

let’s go to the main function:

NOTE: Any Malware Analyst doesn’t analyze all functions of the malware and writes these comments as I did only if they share their thoughts or want to know every instruction of the malware. So It’s usual to not do what I did it’s only for you.

Here the malware Reterivies the file name of the exciting file and then gets info about the Entry Point of that file.

figure(8): The first instructions of the start program in IDA Freeware

Always open The debugger with your disassembler:

figure(9): 1st instructions of Start in x64 Debugger

As you can see the module name in the dump which got from GetModuleFileNameA API and then pass this retrieved value to the VirtualQuery API with the address of the Entry Point of the program to check some info.

NOTE: I always open MDSN with my analysis, so Every API you met goes to Google and writes its name with MDSN like [ GetModuleFileNameA MDSN ] to know the arguments and the return value with its behavior.

Then pass the output to the function called MZcheck to check MZ and PE signatures from the AllocationBase of the Buffer.

Here checking the first 2-byte value in the Malware for MZ header and it exists ZM because as we know the memory is stored in little-endian.

And if not just return with 0, else add 3C to the AllocationBase to check the PE signature if it exists just return it.

Here is the output from Ghidra Decompiler and I don’t know why it’s 0xle, not 0x3C. Ghidra is always easy but IDA PRO is eased and I have IDA freeware only so we will analysis by IDA freeware with Ghidra.

So now the EAX has the address of PE headers. As you can at the figure below getting eax+50h into ECX, what does that means?

Let’s go to Wikipedia and take this SVG to understand PE headers.

Source: Wikipedia

As you can see the 0x50h from the PE header include SizeOfImage Value which is The size of the file in memory.

And after executing this instruction in the x64 debugger to see the result the ECX includes the same value in size-of-image which is located in Optional Header.

So it’s important to know the PE headers because it can help you for every thing in malware analysis.

And then save this value to the SizeOfImage variable, and push Dest after pushing count which is defined by IDA, Let’s see the GetNativeSysInfo function.

So it has 2 arguments Dest, Count which is pushed, and 4 local variables as we see in the figure below. The memset API sets the specific value to a specific memory with a specific length and it takes 3 arguments:

void* memset( void* str, int ch, size_t n);
  • str[]: Pointer to the object to copy the character.
  • ch: The character to copy. It can be a character, a normal value as well a boolean value.
  • n: Number of bytes to copy.

[ Thanks for GeeksforGeeks]

So it will fill the dwMajorVersion with zeros and the length of zeros is 98h or 152bit. This is like int i=0; but in memory.

And The IDA defined VersionInformation Value and we will see it later.

In the figure below it pushes the GetNativeSystemInfo API variable with Kernel32.dll Module and then GetProcAddress and saves the return value to ESI.

If you are confused let’s explain.

GetModuleHandleA API takes an argument to the DLL or EXE to load or give it a NULL value if you want to load the running module[The Malware Itself]. So here it will take kernel32.dll and we see that in x64 Debugger:

Then GetProcAddress API takes 2 arguments as we see it from MDSN: The module handle [The return address from GetModuleHandleA API -> kernel32.dll], and the ProceName [ GetNativeSystemInfo].

FARPROC GetProcAddress(
[in] HMODULE hModule,
[in] LPCSTR lpProcName
);

And then The return address in EAX moved to ESI.So the ESI includes a handle for GetNativeSystemInfo API.

NOTE: This process is called Dynamic Loading: The malware calls this API at the running time only, that’s why we don’t see this API in the import table. A lot of malware does this way for calling important APIs which hide statically.

Let’s continue:

So here the Malware has 2 ways to identify the system info By calling GetVersionExA: if not successful call GetSystemInfo Or by calling ESI [ Which has a handle to GetNativeSystemInfo ].

So in two ways, you should PUSH a struct as an argument called:

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;

So here is a struct of GetNativeSystemInfo and Contains information about the current computer system. This includes the architecture and type of the processor, the number of processors in the system, the page size, and other such information. [ MDSN]

So in the address of memory, 003EF9EC contains the SYSTEM_INFO struct and the first value DWORD dwOemId contains 9 which means if you see the MDSN:

Source: MDSN

So My processor is x64 Intel so that’s why included 9.

That’s why The Malware checking for the 9 value. It targets AMD or Intel computers. And then Make a condition if it’s not x64 bit( Intel Or AMD), make EAX equal to 32bit because the value 9 means x64 bit.

And the end of the function print this information: dwMajorVersion, dwMinorVersion, dwBuildNumber, and 64bit or 32bit.

Let’s see the output from the x64 debugger which in our case output to ECX value.

Do you remember it? in our behavioral analysis see it.

So it’s my Processor Info ”6.2 9200 sp0.0 64bit

What are these numbers?

Because GetVersionExA APIT Contains a struct to:

typedef struct _OSVERSIONINFOA {
DWORD dwOSVersionInfoSize;
DWORD dwMajorVersion;
DWORD dwMinorVersion;
DWORD dwBuildNumber;
DWORD dwPlatformId;
CHAR szCSDVersion[128];
} OSVERSIONINFOA, *POSVERSIONINFOA, *LPOSVERSIONINFOA;

And based on MDSN 6.2[6 -> dwMajorVersion, 2 -> dwMinorVersion ] means my OS version is Windows 8 or Windows Server 2012 and it’s wrong because my OS version is 10 that’s because this API GetVersionExA altered or unavailable for releases after Windows 8.1.

That’s why it gives me a 6.2 9200 build number.

So From this function, we know The malware targets Intel OR AMD processors and then save the version info to memory.

And that’s the Ghidra Decompiler of this function as we explain there is no odd.

So it’s time now to explain important things only:

I renamed these subroutines and it’s a task for you to understand these APIs:

OpenProcessToken, GetTokenInformation, GetSidSubAuthority, AllocateAndInitializeSid, CheckTokenMembership, FreeSid.

These APIs are related to System Profiling to know the level of the user or admin and this article can help you:

Understanding and Abusing Process Tokens — Part I

So here we have to analyze the first IOC in our malware which is related to the log file that we extracted from behavioral analysis.

It takes EDX as an argument for the output from the membership token, EAX that the last output from the last print string [ system info ], Filename which is the full path of the malware, ESI as the output from GET_SID subroutine, and the last output string.

Here from x64 Debugger:

This a good indicator for Yara Rules:

“Entry(): integrity: %x, current: ‘%s’, win: ‘%s’, admin: ‘%d’, uac: ‘%d’, wow64: ‘%d’\n”

And we extracted this string from our last static analysis and also it’s the output string to the log file extracted from behavioral analysis.

so let’s continue:

vsprintf API takes the destination variable which will include the final output string, The format which in our case the strange string->

“Entry(): integrity: %x, current: ‘%s’, win: ‘%s’, admin: ‘%d’, uac: ‘%d’, wow64: ‘%d’\n”

, and the string itself to be printed with these formatted which is the EAX value and must include integrity: %x value, current: ‘%s’ value, win: ‘%s’ value, admin: ‘%d’ value, uac: ‘%d’ value, and wow64: ‘%d’ value.

let’s see the EAX value in the debugger.

[1]00200000 [2]1893B800 [3]70FC9800 [4]00000000. ….¸.pü……
[5]01000000 [6]01000000 305EB800 305EB800……..0^¸.0^¸.
00A07C00 00000000 362E3220 39323030. |…..6.2 9200
20737030 2E302036 34626974 00000000sp0.0 64bit….

So the first value integrity: %x is 00002000 [ because it saves little-endian in memory ].

And the second value current: ‘%s’ is 008B9318, let’s see what is included in this address. And as you can see it’s the full path of the executed malware.

The third value win: ‘%s’ is 0098FC70 which is also a memory address to the output string which we analyzed from GetSystemInfo subroutine.

The fourth value admin: ‘%d’ is 0 [which is a task for a function to analyze].

The fifth value uac: ‘%d’ is 1 User Account Control [which is a task for a function to analyze].

The sixth value wow64: ‘%d is 1 [which means 32bit process can run under 64bit process via wow64].

So our expected output must be:

“Entry(): integrity: 2000, current: ‘$MalwarePath’, win: ‘$YourSystemInfo’, admin: ‘IsUserAdmin?’, uac: ‘IsUACenable?’, wow64: ‘IsProcessUnderWOW64Emulation?’\n”

In my case is:

”Entry(): integrity: 2000, current: ‘\\PowerLoaderV2.bin’, win: ‘6.2 9200 sp0.0 64bit’, admin: ‘0’, uac: ‘1’, wow64: ‘1’\n”

And this is the meaning of the string which we extracted from our behavioral analysis so We now understood what the string means and that’s the power of Code Analysis.

Here it takes our string [ let’s called it our machine info ] to the debugger to be printed by OutputDebugStringA API[ IDK why cos it didn’t check for the output ] and then gets the length of our MachineInfo string by lstrlenA.

Then call CreateLogsFile_wrtie2it [which we called it in our analysis] subroutine with MachineInfo string as 1st argument and The length as 2nd argument.

The malware use GetTempPathA the get %temp% folder which in my case is: C:\Users\User\AppData\Local\Temp -> ss:[ebp-104]

[just search on windows %temp%].

And save to TempPath variable with 260bit maximum chars.

Then call PathAppendA API to append one path to the end of another and it passes %temp% path and “logs123.txt” so the result will be: %temp%logs.txt

So here the full is %temp%\\logs.txt which in my case is:

“C:\\Users\\User\\AppData\\Local\\Temp\\logs123.txt”

Then pass this value as a first argument to CreateFileA API to create this file so it’s our first IOCs which we extracted from Code Analysis.

HANDLE CreateFileA(
[in] LPCSTR lpFileName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwShareMode,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
[in] DWORD dwCreationDisposition,
[in] DWORD dwFlagsAndAttributes,
[in, optional] HANDLE hTemplateFile
);

based on MDSN with our code our creation file is:

HANDLE CreateFileA( "%temp%\\logs123.txt",1F01FFh,7,0,4,0,0 );
);

And for desired access 1F01FF I think it’s FILE_GENERIC_READ | FILE_GENERIC_WRITE because after executing CreateFileA API by x64 Debugger and then going to the path of the log file then going to the properties of it we will see:

So this file has full control access.

And the ShareMode is 7 and based on MDSN it includes all share modes: FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE so The malware can read, write, and delete from anywhere in your network.

Also, CreationDisposition is 4 which means based on MDSN is OPEN_ALWAYS [ Open the file if exists or create if not ].

So the source code of this creation maybe is:

HANDLE CreateFileA( "%temp%\\logs123.txt",FILE_GENERIC_READ | FILE_GENERIC_WRITE,FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,0,OPEN_ALWAYS,0,0 );
);

If you went to the Log File to open it, it won’t open because we don’t close the handle and it will get you it:

So be patient.

Let’s continue:

If the CreateFileA failed just return if not ESI will include the handle of the creation log file, EAX equal to 2, and call Write2File subroutine with MachineInfo variable as the 1st argument and the 2nd argument is the length of MachineInfo variable.

And it is from Ghidra Decompiler and there is no unusual thing:

So Let’s analyze the write2file subroutine:

So always rename these variables to their behavior to know everything happening to it.

So ESI has the handle opening log file which is the first argument to SetFilePointer API with MoveMethod 2 value.

Based on MDSN The file pointer will be at the end of the file which means if data exists, set the pointer after it, if not, the pointer will be in the first file.

Then WriteFile API:

BOOL WriteFile(
[in] HANDLE hFile,
[in] LPCVOID lpBuffer,
[in] DWORD nNumberOfBytesToWrite,
[out, optional] LPDWORD lpNumberOfBytesWritten,
[in, out, optional] LPOVERLAPPED lpOverlapped
);

Which will be:

BOOL WriteFile(
hFile->"%temp%\logs123.txt",
lpBuffer->"Entry(): integrity: 2000, current: '\\PowerLoaderV2.bin', win: '6.2 9200 sp0.0 64bit', admin: '0', uac: '1', wow64: '1'\n",
nNumberOfBytesToWrite -> LenMachineInfo,
lpNumberOfBytesWritten -> &LenMachineInfo
lpOverlapped -> 0
);

Then push the handle of the Log File to SetEndOfFile API which Sets the physical file size for the specified file to the current position of the file pointer.[ copied from MDSN and As I know it ends the file pointer ].

And This is from Ghidra Decompiler:

So I think it’s a void function and has no return value.

After stepping this function from Debugger it will close the handle of the Log File, then we can open it:

So We Analyze our first IOC Command.

Let’s return to the Start function:

Here is The Malware check for the value of ESI and if you remember the value of SID:

So if the SID = 1000h, it calls a subroutine I called it SendReportC2C_ShlExe. Let’s see this function in Ghidra.

To Be Continued…يتبع…

--

--

Mahmoud NourEldin
Mahmoud NourEldin

Written by Mahmoud NourEldin

Threat Researcher and Malware Analyst.

No responses yet