Trustwave SpiderLabs Exposes Unique Cybersecurity Threats in the Public Sector. Learn More

Trustwave SpiderLabs Exposes Unique Cybersecurity Threats in the Public Sector. Learn More

Services
Capture
Managed Detection & Response

Eliminate active threats with 24/7 threat detection, investigation, and response.

twi-managed-portal-color
Co-Managed SOC (SIEM)

Maximize your SIEM investment, stop alert fatigue, and enhance your team with hybrid security operations support.

twi-briefcase-color-svg
Advisory & Diagnostics

Advance your cybersecurity program and get expert guidance where you need it most.

tw-laptop-data
Penetration Testing

Test your physical locations and IT infrastructure to shore up weaknesses before exploitation.

twi-database-color-svg
Database Security

Prevent unauthorized access and exceed compliance requirements.

twi-email-color-svg
Email Security

Stop email threats others miss and secure your organization against the #1 ransomware attack vector.

tw-officer
Digital Forensics & Incident Response

Prepare for the inevitable with 24/7 global breach response in-region and available on-site.

tw-network
Firewall & Technology Management

Mitigate risk of a cyberattack with 24/7 incident and health monitoring and the latest threat intelligence.

Solutions
BY TOPIC
Offensive Security
Solutions to maximize your security ROI
Microsoft Exchange Server Attacks
Stay protected against emerging threats
Rapidly Secure New Environments
Security for rapid response situations
Securing the Cloud
Safely navigate and stay protected
Securing the IoT Landscape
Test, monitor and secure network objects
Why Trustwave
About Us
Awards and Accolades
Trustwave SpiderLabs Team
Trustwave Fusion Security Operations Platform
Trustwave Security Colony
Partners
Technology Alliance Partners
Key alliances who align and support our ecosystem of security offerings
Trustwave PartnerOne Program
Join forces with Trustwave to protect against the most advance cybersecurity threats
SpiderLabs Blog

Decoding Hancitor Malware with Suricata and Lua

Many types of malware send and receive data via HTTP. They may either be sending updates back to their command and control (CnC) centers or they may receive updates. Typically these won’t be sent in plain text but rather with some type of encoding to avoid detection. Much like in my other blog post “Advanced Malware Detection with Suricata Lua Scripting”, we will use Suricata’s Lua scripting engine to decode some payload on the fly. Unlike the last one though, we’ll also create a log file to record the decoded data.

For this example we’ll use a Hancitor malware traffic sample. Hancitor is a malware downloader first seen in the wild in 2014. When Hancitor initially infects a system it send a POST request to its CnC server with information about the system it infected. This information is in clear text and can be detected with normal signatures. So we don’t need any additional detection for this and what is sent back from the server could have valuable information stored inside.

Picture1
Figure 1: Hancitor POST


Suricata Signature:

alert tcp $HOME_NET any -> $EXTERNAL_NET $HTTP_PORTS (msg:"Hancitor Infection - POST System Info to PHP"; 
flow:established,to_server; content:"POST"; http_method; content:".php"; http_uri; content:"GUID="; http_client_body;
content:"&BUILD="; http_client_body; content:"&IP="; http_client_body; sid:123456; rev:1;)

Since Hancitor has been around for a while, it’s pretty well known that the payload from the server is XOR’d with a key of 0x7A and then base64 encoded. 4 bytes are also added to the beginning of the payload before it’s sent. To extract the data we can use something like CyberChef to quickly verify that the payload is encoded like other samples were. Of course, we don’t want to do this manually every time. It would be much more efficient to have it done automatically and logged for review.

Picture2
Figure 2: Hancitor POST decode in CyberChef

Lua output scripts need four functions defined. The first of those four is the init() function, which registers where the script will hook into the Suricata output engine. In this case, we’ll need “packet” and “alerts”.

function init (args)
local needs = {}
needs["type"] = "packet"
needs["filter"] = "alerts"
return needs
end

Setup() and deinit() functions will also be needed. Setup() sets the log files and deinit() cleans up afterwards.

function setup (args)
filename = SCLogPath() .. "/" .. "hancitor.log"
file = assert(io.open(filename, "a"))
SCLogInfo("HTTP Log Filename " .. filename)
http = 0
end

function deinit (args)
SCLogInfo ("HTTP transactions logged: " .. http);
file:close(file)
end

The real work goes in the log() function. This is where we will see if the above signatures has generated an alert and then extract and decode the payload. This is also where we’ll format out output to the log.

function log(args)
local decoded = {}
local output = ""
--local test = ""
--local key = 0x7a
sid, rev, gid = SCRuleIds()
a, o, e = HttpGetResponseBody();
--print("offset " .. o .. " end " .. e)

--Make sure we only alert when the correct signatures triggers
if sid == 123456 then
for n, v in ipairs(a) do
b64_str = string.sub(v,5) --Strip 4 bytes
b64_decoded = base64.dec(b64_str)
end
key, output = CheckXoR(b64_decoded)

--Gather other traffic info
http_host = HttpGetRequestHost()
if http_host == nil then
http_host = ""
end
http_host = string.gsub(http_host, "%c", ".")

http_uri = HttpGetRequestUriRaw()
if http_uri == nil then
http_uri = ""
end
http_uri = string.gsub(http_uri, "%c", ".")

timestring = SCPacketTimeString()
ip_version, src_ip, dst_ip, protocol, src_port, dst_port = SCFlowTuple()
--Setup log file for json
local json_data = "{"
..'"'.."p_timestamp"..'"'..":"..'"'..timestring..'"'..","
..'"'.."ipv"..'"'..":"..'"'..ip_version..'"'..","
..'"'.."sip"..'"'..":"..'"'..src_ip..'"'..","
..'"'.."dip"..'"'..":"..'"'..dst_ip..'"'..","
..'"'.."sp"..'"'..":"..'"'..src_port..'"'..","
..'"'.."dp"..'"'..":"..'"'..dst_port..'"'..","
..'"'.."host"..'"'..":"..'"'..http_host..'"'..","
..'"'.."raw_uri"..'"'..":"..'"'..http_uri..'"'..","
..'"'.."xor_key"..'"'..":"..'"'.."0x"..bit.tohex(key, 2)..'"'..","
..'"'.."data"..'"'..":"..'"'..output..'"'..
"}"

file:write(json_data)
file:flush()
else
http = http + 1
return
end
http = http + 1
end

The code used is fairly simple, it checks to make sure that the correct sid number has generated an alert. If an alert hasn’t been generated by this signature then it will exit the script. This also reduces the number of response payloads we’ll have to go through in order to find the correct one. After we are sure we’re looking at the correct payload, we can strip the first 4 bytes, store it in variable “b64_str” and then pass that string to a base64 decoder function.

sid, rev, gid = SCRuleIds()
a, o, e = HttpGetResponseBody();
...
if sid == 123456 then
for n, v in ipairs(a) do
b64_str = string.sub(v,5) --Strip 4 bytes
b64_decoded = base64.dec(b64_str)
end
key, output = CheckXoR(b64_decoded)

end

I needed to create a XoR function that would accept the byte array, iterate through the array while XoR’ing it with the key of 0x7A, and finally return the decoded string.

function CheckXoR(data)
local key = 0x7a
local decoded_data = {}
local test = ""
local output = ""
local xkey = 0x00

for i=1, #data, 1 do
decoded_data[i] = bit.bxor(string.byte(data, i), key)
--io.write(bit.tohex(decoded_data[i], 2))
if i <= 11 then
test = test .. string.char(decoded_data[i])
end
if i == 11 then
if not string.find(test, "http") then
xkey, output = BruteXoR(data)
return xkey, output
end
end
end

for i=1, #decoded_data, 1 do
output = output .. string.char(decoded_data[i])
end
return key,output
end

After writing the function to XoR the payload with a static key, it came to mind that this key could change at any time. We’d then have to update the code to use the new keys or, perhaps, we can simply brute force it.

In the CheckXoR() function and the BruteXoR() function I only decode the first 10bytes and check to see if the decoding is correct by searching for “https”. If CheckXoR() finds this string it will continue on using 0x7A as they key, otherwise it will stop and call BruteXoR(). BruteXoR() will then iterate from 0x00 to 0xff trying to decode the first 10bytes of the payload, again check for “https”. Once it’s found it will continue with the decoding process and return the key and output string.

function BruteXoR(data)
local test = ""
local decoded_data = {}
local found = 0
local output = ""
local xkey = 0x00

for key=0x01, 0xff, 1 do
if found == 0 then
for i=1, #data, 1 do
decoded_data[i] = bit.bxor(string.byte(data, i), key)
if i <= 11 then
test = test .. string.char(decoded_data[i])
end
if i == 11 then
if string.find(test, "http") then
xkey = key
found = 1
else
test = ""
break
end
end

end
else
break
end
end
for i=1, #decoded_data, 1 do
output = output .. string.char(decoded_data[i])
end
return xkey, output
end

From here we can gather other pertinent information, like the HOST, URI, packet timestamp, as well as source and destination IP information. Once gathered you can format the data as needed. I choose to JSON format as shown below.

local json_data = "{"
..'"'.."p_timestamp"..'"'..":"..'"'..timestring..'"'..","
..'"'.."ipv"..'"'..":"..'"'..ip_version..'"'..","
..'"'.."sip"..'"'..":"..'"'..src_ip..'"'..","
..'"'.."dip"..'"'..":"..'"'..dst_ip..'"'..","
..'"'.."sp"..'"'..":"..'"'..src_port..'"'..","
..'"'.."dp"..'"'..":"..'"'..dst_port..'"'..","
..'"'.."host"..'"'..":"..'"'..http_host..'"'..","
..'"'.."raw_uri"..'"'..":"..'"'..http_uri..'"'..","
..'"'.."xor_key"..'"'..":"..'"'.."0x"..bit.tohex(key, 2)..'"'..","
..'"'.."data"..'"'..":"..'"'..output..'"'..
"}"

With the Lua script in place our log output will look something like this:

Log File Output:

{"p_timestamp":"10/17/2018-14:50:24.165203","ipv":"4","sip":"192.168.56.103","dip":"81.177.165.226","sp":"49867","dp":"80","host":"repkehanhar.com","raw_uri":"/4/forum.php","xor_key":"0x7a","data":"{l:https://guz-nmgb.ru/wp-content/plugins/contact-form-7/1|https://brouwershuys.nl/wp-content/plugins/92938dc3b901/1}{b:https://guz-nmgb.ru/wp-content/plugins/contact-form-7/2|https://brouwershuys.nl/wp-content/plugins/92938dc3b901/2}{r:https://guz-nmgb.ru/wp-content/plugins/contact-form-7/3|https://brouwershuys.nl/wp-content/plugins/92938dc3b901/3}"}

Formatted Output:

{
"p_timestamp": "10/17/2018-14:50:24.165203",
"ipv": "4",
"sip": "192.168.56.103",
"dip": "81.177.165.226",
"sp": "49867",
"dp": "80",
"host": "repkehanhar.com",
"raw_uri": "/4/forum.php",
"xor_key": "0x7a",
"data": "{l:https://guz-nmgb.ru/wp-content/plugins/contact-form-7/1|https://brouwershuys.nl/wp-content/plugins/92938dc3b901/1}{b:https://guz-nmgb.ru/wp-content/plugins/contact-form-7/2|https://brouwershuys.nl/wp-content/plugins/92938dc3b901/2}{r:https://guz-nmgb.ru/wp-content/plugins/contact-form-7/3|https://brouwershuys.nl/wp-content/plugins/92938dc3b901/3}"
}


This is of course just one example of the possibilities for using lua output scripts. This can not only extract IoCs from malicious network traffic while it’s happening but because it’s automated the logs can be extracted and used to further prove an infection and/or distributed to other devices for investigation or blocking.

Latest SpiderLabs Blogs

2024 Public Sector Threat Landscape: Trustwave Threat Intelligence Briefing and Mitigation Strategies

Trustwave SpiderLabs’ 2024 Public Sector Threat Landscape: Trustwave Threat Intelligence Briefing and Mitigation Strategies report details the security issues facing public sector security teams as...

Read More

How to Create the Asset Inventory You Probably Don't Have

This is Part 12 in my ongoing project to cover 30 cybersecurity topics in 30 weekly blog posts. The full series can be found here.

Read More

Guardians of the Gateway: Identity and Access Management Best Practices

This is Part 10 in my ongoing project to cover 30 cybersecurity topics in 30 weekly blog posts. The full series can be found here.

Read More