Practical Malware Analysis
(extracted from the main diary)
Playing with PMA Labs
Let's start with Lab01-01.exe.
I'm even using IDE Free 70 instead of my licensed version.
According to " detect it easy " it's a 32bits PE executable, unpacked, compiled with MSVC 6.0
Opening it in IDA with default option
Only the EntryPoint is exported, it imports kernel32 and msvcrt
Some useful strings
it's easy to guess what's going to happens if you execute it. it will destroy/replace your kernel32.dll
Opening the Entry Point in IDA. Nothing unusual, it appears to be some standard init.
Before exiting, it calls a sub, which should be our WinMain and the var pushed before the call should be our usual args.
Let's rename it to WinMain and let IDA do its magic (the stuff we're willing to pay for)
Jumping into WinMain, it feels wrong. Let's call it main instead.
Isn't it much better ? initenv (whatever that is) create envp. now it feels right.
The first few lines of our main(int argc, const char **argv, const char **envp), commented
Let's explain, because I have time. remove the bookkeeping stuff and focus on the user code.
eax = argc. nothing to see here. argc is the number of argument. 1 mean "no argument" (the 1st arg is the program name), 2 mean, of course, 1 argument
compare eax with 2. So we can guess it's expecting to be called with an argument in command line.
cmp set the ZF and CF flag according to the result of the comparison.
cmp dst, src | ZF | CF |
---|---|---|
dst = src | 1 | 0 |
dst < src | 0 | 1 |
dst > src | 0 | 0 |
So if argc == 2 then ZF should be 1
Next is JNZ (Jump Non-Zero), also known as JNE (Jump Not Equal)
I'll be honest here. I always get confused by JNZ. it jumps if ZF = 0. But if you think of it as being "JNE" it's much easier.
Anyway : if(argc != 2) { goto loc_401813; }
it jumps directly to the end of our main. Therefore, because eax = 0, we get something like :
if(argc != 2) { return 0; }
our "malware" won't work without argument. It's probably a security measure because this is a fake malware for educational purpose.
What's happening if argc = 2 ?
First, it gets a pointer to argv (which contain our arguments from command line)
next, esi will point to a string, esi is often used for loop
finally, eax = eax+4. we're in 32bit, a pointer is 4 byte long. Basically, eax will now point to argv[1] instead of argv[0]
we have a string, a loop and argv[1]. easy guess : it will check if the exe will be called like this :
I'm skipping a bunch of mov, cmp, test, loop, with a final jnz going straight to exit if the comparison fail.
For the curious :
Next, all the following jnz goes to exit ,so I'll skip it :
Thank you, IDA, for knowing the win32 api <3
Hey... did you know that writing this take forever ?
I'm supposed to be on a break.
Enough for now. All the calls above speak for themselves. go read MSDN to know more :)
2021/03/32
Dear diary, this day doesn't exist (citation needed)
2021/04/01
Dear Diary, new COVID lockdown, again ... meh... :[
Anyway, back to the previous exercise.
MapViewOfFile: https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile
CreateFileMappingA: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga
CreateFileA: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
About CreateFileA dwDesiredAccess :
dwDesiredAccess | Mask |
---|---|
GENERIC_READ | 0x80000000 |
GENERIC_WRITE | 0x40000000 |
GENERIC_EXECUTE | 0x20000000 |
GENERIC_ALL | 0x10000000 |
CreateFileA return a file handle (in eax, return value are always in eax),
then CreateFileMappingA is passing this eax as hFile argument and return a file handle (HANDLE) in eax
which is used as the hFileMappingObject argument.
finally, CreateFileMappingA return a LPVOID
If the function succeeds, the return value is the starting address of the mapped view.
If the function fails, the return value is NULL. To get extended error information, call GetLastError.
TL;DR : "C:\Windows\System32\Kernel32.dll" is mapped in memory
Lab01-01.dll is created with CreateFileA, follow by CreateFileMappingA & MapViewOfFile. same as above.
I'm not at home with my IDA, VM, Windows, etc... so that's it for now. I'll have to go home to read the rest of the code.
Dear Diary, I'm home again.
I'm reading the next piece of code, and it's going to get a little bit messy. The reason is that it call a bunch of sub_xxxxxx, and I'll also need to rename some local var. It's not difficult to guess what's going to happens, but since I'm writing this diary I'll push myself to reverse every part of it.
The list of local vars :
With a quick look at the code, I have no way to really what the vars mean so I'll have to reverse every sub one by one first. Luckily, we have only 7 sub_, which is very, very low.
After some browsing each of them, this is a small one. I already renamed it :
But it's only called by the entry point, which isn't user code, so we can safely ignore it. _controlfp : Gets and sets the floating-point control word.
Another one, renamed it, also called by the entry point :
We're down to 5 sub. 3 of them do not do any external call to dll's function. 2 of them call kernel32.dll (& msvcrt ?).
sub_4010A0 (renamed to : read_file)
called by : sub_4011E0 (which I renamed to search_file)
system calls : CreateFileA, CreateFileMappingA, MapViewOfFile, IsBadReadPtr, UnmapViewOfFile, CloseHandle, _stricmp
sub_4011E0 (renamed to : search_file)
called by : main, sub_4011E0 (yes, it's calling itself)
system calls : FindFirstFileA, malloc, _stricmp, FindClose, FindNextFileA,
For now, I'll just rename "sub_4011E0" to "search_file" and "sub_4010A0" to "read_file". Considering how it also calls "Create_File" it's probably not just "reading" files, but for now I'll keep it as is. It's just a random guess according to the calls it's doing.
So, "main" call "search file" which is calling "read_file". I wish I could do graphs, but GitHub doesn't support it.
"read_file" also call "sub_401040"
"sub_401040" only call "401000"
I guess it's time for a graph, I'll use the IDA's proximity browser.
Not bad, huh ? So, what is sub_401000 ? there is a loop in it, but here is the first part before the loop.
Let's remove some stuff for now :
Haaa what a pain ! "cx" is the lower 16bit of the 32bit ecx register.
Basically it seems to test if whatever is at edx+6 == 0
"whatever is at edx+6" is defined by esp+arg_4.
So, again, what is arg_4 ?
Dear Diary, I'm going to eat before I forget to, and read some manga. (Beware of the villainess)
See you tomorrow. I'm forcing myself to take a break.
2021/04/02
Dear Diary, I just tested IDA Free 70 on my Mac M1 : it works.
The part of the code where I stopped yesterday still upset me.
why +6 ? why 16 bits ? it's important to give up and keep going, perhaps I'll know why later. Perhaps it does not matter and it's just the compiler doing compiler stuff. But the purpose of the Diary is to go in depth. Or not... it's just my notepad, I do wtf I want to.
For some reason I can't extract the PMA Labs on my M1, it says the password is incorrect.
it seems to be a known problem. I installed "keka" and the extraction works.
TL;DR : I can keep on reversing this exe on my Mac now
To be honest, this is the kind of code where a decompiler would be helpful.
And I have one, hopper disassembler. does it help ?
Meh ...
By the way, if we go back near the end of the main :
Just saying.
Anyway... I have a little bit of dilemma here. is it my diary or does it become some kind of tutorial ? If it was just me, I would have happily just ignored this annoying sub. it doesn't seem to matter that much. I can always come back to it later, or not. does it even matter ?
I really want to go check that dll instead. And drink coffee too.
Lab01.dll
It imports kernel32 (createProcess, sleep, createmutex, ...), msvcrt, and ws2_32. nice :]
Isn't it much more fun ? ws2 is winsock <3 internet stuff \o/ https://docs.microsoft.com/en-us/windows/win32/winsock/windows-sockets-start-page-2
it exports the EntryPoint only, what least that's what IDA says, and hopper agrees.
strings are : "hello", "sleep", "SADFHUHF", "172.26.152.13"
A quick look at the main function tell me that :
It creates a mutex named SADFHUHF
it opens a socket, send "hello"
wait to receive something
"sleep" : call Sleep
"exec" : call CreateProcessA
"q" : call CloseHandle and exit
loop back to hello
I don't think I need to do any intensive reverse engineering here. it's a 5mn job.
Lab01_02.exe
Opening in IDA :
Nice <3
Only function found, the entry point.
Import : LoadLibraryA, GetProcAddress, VirtualProtect, VirtualAlloc, VirtualFree, ExitProcess, CreateServiceA, exit, InternetOpenA
The imports are super suspicious of course. the broken PE too.
Conclusion : it's probably UPX packed.
Strings : Kernel32.dll, Advapi32.dll, msvcrt.dll, wininet.dll
I guess I have to explain a little bit what's happening here ?
It's very easy, it's trying to load dll dynamically :
LoadLibraryA :
https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya
Loads the specified module into the address space of the calling process. The specified module may cause other modules to be loaded.
HMODULE LoadLibraryA( LPCSTR lpLibFileName );
lpLibFileName : The name of the module. This can be either a library module (a .dll file) or an executable module (an .exe file). The name specified is the file name of the module and is not related to the name stored in the library module itself, as specified by the LIBRARY keyword in the module-definition (.def) file.
GetProcAddress :
https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress
Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL).
FARPROC GetProcAddress( HMODULE hModule, LPCSTR lpProcName );
hModule : A handle to the DLL module that contains the function or variable.
lpProcName : The function or variable name, or the function's ordinal value.
If the function succeeds, the return value is the address of the exported function or variable.
If the function fails, the return value is NULL.
Easy ? Right ? Instead of importing a function from a dll, you load it at runtime.
About the broken PE now :
IDA -> View -> Open Subviews -> Segments
UPX0
UPX1
UPX2
.idata
UPX2
Toldyaso, it's UPX packed. There is an IDA plugin. Named "Universal Unpacker" or something, but I need my licensed IDA version.
And it's on my computer at home. My Mac run IDA Free 70. Pooh.
I don't want to bother trying to unpack it on my Mac when in can do it at home. I'll do something else instead.