• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Чистая Windows и разработка «без всего»

Panchitos

HDD-drive
Пользователь
Регистрация
15.05.2024
Сообщения
26
Реакции
16
Гарант сделки
4
Статья целиком взята с ХАБРа - https://habr.com/ru/articles/837570/ - автор alex0x08




Есть компьютер с чистой копией Windows, без доступа в интернет и без каких‑либо установленных средств разработки. Только одна чистая пользовательская «венда». Не поверите, но даже в таких спартанских условиях возможно написать и запустить полноценную программу. И сейчас я расскажу как.


fe580dba685bd8bb82eaa625cbfbd7d7.png


Ради этого скриншота я честно развернул пользовательскую версию Windows 11 в виртуальной машине. Чего не сделаешь ради искусства! (цитата автора)

Ужасы познания​

На самом деле в ОС семейства Windows с самого их начала было внутри столько всякого интересного, что никакой статьи не хватит описать, так что выпусков будет много ;)

Но почему-то мало кто об этом знает даже из разработчиков, особенно современных.

Спросите ради интереса знакомых разработчиков, возможно ли программировать на «чистой» пользовательской Windows без установки Visual Studio — удивитесь ответам.
Ну и разумеется насаждаемый «пользовательский» подход самой Microsoft, которая ковыряние во внутренностях своих продуктов мягко говоря никогда не поощряла, создал эдакий ареол простоты и надежности, без необходимости разбираться как оно внутри устроено.

Поэтому описанное ниже наверное вызовет определенный ужас как у обычных пользователей так и некоторых разработчиков — особенно если они обучались по видеокурсам ничего не знают об истории ОС Windows.

Начну с цитаты из одной интересной статьи:

Over the past few months, I've received several variations on this question for other operating systems and all of the released versions of the .NET Framework. When the .NET Framework is installed as a part of the OS, it does not appear in the Programs and Features (or Add/Remove Programs) control panel. The following is a complete list of which version of the .NET Framework is included in which version of the OS
И ниже длинный такой список с версиями. А вот еще один если вдруг первого оказалось недостаточно.

Ну казалось бы и.. что? Чего тут такого?

Про .NET SDK все и так знают, временами его необходимо установить «для запуска игор», временами он сам ставится в виде зависимой библиотеки и никому не мешает.

Все так, да.
Только что-то мне подсказывает внутрь вы не заглядывали, правда? Поэтому на что эта штука на самом деле способна не представляете.
А я представляю и сейчас расскажу.

Заходите в папку Windows на вашем компьютере, вот сюда:
6ad7ff8c589e0ff86484014f2647034a.png


Этот снимок из Windows 10, в нем используется системная .NET SDK 3.5, в Windows 11 будет уже 4.0

Файлт csc.exe — самый настоящий компилятор, фактически портал в ад на вашем обычном домашнем компьютере.

Почему все так страшно?
Потому что через какое-то время вы обнаружите себя сильно заросшим, с бородой и красными глазами, проводящим ночи за компьютером и медленно мутирующим в программиста.

Шучу.

А если серьезно:

появляется возможность создания нативных программ сразу на вашем компьютере, минуя стадию проверки электронной подписи, проверки антивирусом, проверки электронного письма и так далее.
В отличие от VB или PowerShell-скриптов, которые анализируются перед запуском любым приличным антивирусом, антивирусы не анализируют исходный код программ на C# и куда лояльнее относятся к программам собранным локально на этой же машине.

Так что веселье начинается.

Простой пример​

Для начала будет простой пример, который просто показывает стандартный диалог с сообщением. Именно его в запущенном виде вы можете видеть на заглавной картинке в статье.

Весь процесс от кода до запуска я записал на видео:

Исходный код тут казалось бы максимально простой, но с одним интересным нюансом про который ниже:

C#:
using System;
using System.Runtime.InteropServices;

namespace yoba
{
  class Program
  {
    // импортирование нативной WinAPI функции MessageBox.
    [DllImport("user32.dll")]
    public static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);

    static void Main(string[] args)
    {
      //вызываем и показываем диалог
      MessageBox(IntPtr.Zero, "Йоу!", "Добро пожаловать в разработку!", 0);
    }
  }
}

Сохраняете этот текст обычным «блокнотом» в файл yoba.cs и запускаете сборку:

c:\Windows\Microsoft.NET\Framework\v3.5\csc.exe yoba.cs

После сборки рядом с исходным файлом yoba.cs появится и запускабельный бинарник yoba.exe, который вы сможете запустить.

А теперь про нюанс.

Нюанс​

Существует определенное предубеждение по отношению к managed‑языкам вроде Java и С# — они не подходят для серьезных дел вроде написания эксплоитов, использования 0day‑уязвимостей и пенетрации ядра.

Что все подобные вещи творят в глубокой тайне на чистом Си, в крайнем случае на C++ а все эти ваши Java/C# не более чем «погремушки для детей», не достойные даже косого взгляда серьезного профессионала.
Вот тут и начинается нюанс, посмотрите на эту радость:

C#:
[DllImport("user32.dll")]
public static extern int MessageBox(IntPtr hWnd,
                string lpText, string lpCaption, uint uType);

Это мои дорогие читатели, ни что иное как вызов нативного WinAPI, с помощью которого творили всякое нехорошее в далекие 90е.

C# и .NET имеет оооочень глубокую интеграцию с Windows, несмотря на всю свою «безопасность» и управляемость, поэтому легко и просто может заменить собой и Си и С++ в качестве инструмента для нехороших дел.

И оно живет на вашем компьютере, дома и в офисе, с постоянной пропиской и регистрацией.
Но разумеется столь простого примера несколько мало для осознания глубины проблемы, поэтому я подготовил кое-что более серьезное.

Сложный пример: выключаем Windows​

Итак, это будет относительно небольшое приложение на C#, выключающее компьютер без предупреждения и подтверждения пользователя. И само собой без прав администратора.

Просто так, внезапно.
Последствия думаю каждый из читателей сможет оценить для себя сам.

Весь процесс на видео (разумеется это виртуальная машина):

А теперь код:
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security;
using System.Diagnostics;
using System.Management;
using System.Security.Permissions;
using System.Runtime.InteropServices;
 
namespace yoba
{   
    // See http://www.developmentnow.com/g/33_2004_12_0_0_33290/Access-Denied-on-ManagementEventWatcher-Start.htm
    // Calling this code on backup/restore seems to enable BCD
    public class TokenHelper
    {
        // PInvoke stuff required to set/enable security privileges
        [DllImport("advapi32", SetLastError=true),
        SuppressUnmanagedCodeSecurityAttribute]
        static extern int OpenProcessToken(
            System.IntPtr ProcessHandle, // handle to process
            int DesiredAccess, // desired access to process
            ref IntPtr TokenHandle // handle to open access token
            );

        [DllImport("kernel32", SetLastError=true),
        SuppressUnmanagedCodeSecurityAttribute]
        static extern bool CloseHandle(IntPtr handle);

        
        [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
        SuppressUnmanagedCodeSecurityAttribute]
        static extern int AdjustTokenPrivileges(
            IntPtr TokenHandle,
            int DisableAllPrivileges,
            IntPtr NewState,
            int BufferLength,
            IntPtr PreviousState,
            ref int ReturnLength);

        [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
        SuppressUnmanagedCodeSecurityAttribute]
        static extern bool LookupPrivilegeValue(
            string lpSystemName,
            string lpName,
            ref LUID lpLuid);

        [StructLayout(LayoutKind.Sequential)]
            internal struct LUID
        {
            internal int LowPart;
            internal int HighPart;
        }

        [StructLayout(LayoutKind.Sequential)]
            struct LUID_AND_ATTRIBUTES
        {
            LUID Luid;
            int Attributes;
        }

        [StructLayout(LayoutKind.Sequential)]
            struct _PRIVILEGE_SET
        {
            int PrivilegeCount;
            int Control;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst=1)] // ANYSIZE_ARRAY = 1
            LUID_AND_ATTRIBUTES [] Privileges;
        }

        [StructLayout(LayoutKind.Sequential)]
            internal struct TOKEN_PRIVILEGES
        {
            internal int PrivilegeCount;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]
            internal int[] Privileges;
        }
        const int SE_PRIVILEGE_ENABLED = 0x00000002;
        const int TOKEN_ADJUST_PRIVILEGES = 0X00000020;
        const int TOKEN_QUERY = 0X00000008;
        const int TOKEN_ALL_ACCESS = 0X001f01ff;
        const int PROCESS_QUERY_INFORMATION = 0X00000400;

        public static bool SetPrivilege (string lpszPrivilege, bool
            bEnablePrivilege )
        {
            bool retval = false;
            int ltkpOld = 0;
            IntPtr hToken = IntPtr.Zero;
            TOKEN_PRIVILEGES tkp = new TOKEN_PRIVILEGES();
            tkp.Privileges = new int[3];
            TOKEN_PRIVILEGES tkpOld = new TOKEN_PRIVILEGES();
            tkpOld.Privileges = new int[3];
            LUID tLUID = new LUID();
            tkp.PrivilegeCount = 1;
            if (bEnablePrivilege)
                tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
            else
                tkp.Privileges[2] = 0;
            if(LookupPrivilegeValue(null , lpszPrivilege , ref tLUID))
            {
                Process proc = Process.GetCurrentProcess();
                if(proc.Handle != IntPtr.Zero)
                {
                    if (OpenProcessToken(proc.Handle, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
                        ref hToken) != 0)
                    {
                        tkp.PrivilegeCount = 1;
                        tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
                        tkp.Privileges[1] = tLUID.HighPart;
                        tkp.Privileges[0] = tLUID.LowPart;
                        const int bufLength = 256;
                        IntPtr tu = Marshal.AllocHGlobal( bufLength );
                        Marshal.StructureToPtr(tkp, tu, true);
                        if(AdjustTokenPrivileges(hToken, 0, tu, bufLength, IntPtr.Zero, ref
                            ltkpOld) != 0)
                        {
                            // successful AdjustTokenPrivileges doesn't mean privilege could be    changed
                                if (Marshal.GetLastWin32Error() == 0)
                                {
                                    retval = true; // Token changed
                                }
                        }
                        TOKEN_PRIVILEGES tokp = (TOKEN_PRIVILEGES) Marshal.PtrToStructure(tu,
                            typeof(TOKEN_PRIVILEGES) );
                        Marshal.FreeHGlobal( tu );
                    }
                }
            }
            if (hToken != IntPtr.Zero)
            {
                CloseHandle(hToken);
            }
            return retval;
        }
    }
    
    class ShutDown
    {
      
        [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
        internal static extern bool ExitWindowsEx(int flg, int rea); 
        
        internal const int EWX_FORCE = 0x00000004;
        internal const int EWX_POWEROFF = 0x00000008;
    
        static void Main(string[] args)
        {
            TokenHelper.SetPrivilege("SeShutdownPrivilege",true);             
            ExitWindowsEx(EWX_FORCE | EWX_POWEROFF, 0);           
        }
    }
}

Обращаю внимание что это не эксплоит, не дыра, не баг и не уявимость а вполне себе стандартный функционал. Просто так получилось что о нем мало кто знает.

Собирается по аналогии с предыдущим примером:

c:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Shutdown.cs


После запуска компьютер практически немедленно выключится:

проверено и в виртуальной машине и на железе, на 10й и 11й Windows.
Рассказываю как это работает.

Ключевая функция — ExitWindowsEx, которая и отвечает за завершение работы ОС. Эта функция очень старая и известная, существует еще со времен Windows 95.

Но для ее вызова нужны «привилегии», которые и выставляет программно класс TokenHelper.

Константы ниже:

C#:
internal const int EWX_FORCE = 0x00000004;
internal const int EWX_POWEROFF = 0x00000008;

используются вместе с "побитовым или" для указания на требуемое действие.

Вот еще допустимые варианты:

C#:
internal const int EWX_LOGOFF = 0x00000000;
internal const int EWX_SHUTDOWN = 0x00000001;
internal const int EWX_REBOOT = 0x00000002;
internal const int EWX_FORCEIFHUNG = 0x00000010;

Описание их всех находится все там же — в официальном руководстве, не поверите.

Теперь давайте разбираться как же работает столь жесткое забивание на систему защиты еще и стандартными средствами:,

TokenHelper.SetPrivilege("SeShutdownPrivilege",true);

И начнем мы с импортов.

Первое что импортируется это функция OpenProcessToken:

C#:
[DllImport("advapi32", SetLastError=true),
        SuppressUnmanagedCodeSecurityAttribute]
        static extern int OpenProcessToken(
            System.IntPtr ProcessHandle, // handle to process
            int DesiredAccess, // desired access to process
            ref IntPtr TokenHandle // handle to open access token
            );


Функция отвечает за получение данных о наборе «привилегий», связанных с конкретным процессом. Собственно набор таких привилегий и называется «токеном».

Вот как эта функция вызывается:,

C#:
if (OpenProcessToken(proc.Handle, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
                        ref hToken) != 0)
                    {
                    ..

Тут надо отметить передачу по ссылке в стиле Си (ref hToken), когда в функцию передается ссылка на объект C#, дальше функция этот объект заполняет данными. А возвращает она просто true или false — статус выполнения, отработала функция или нет.

Дальше импортируется простая и банальная функция освобождения ресурсов:

C#:
[DllImport("kernel32", SetLastError=true),
        SuppressUnmanagedCodeSecurityAttribute]
        static extern bool CloseHandle(IntPtr handle);

Вызывается она в самом конце, после всей логики и нужна только для освобождения использованной памяти под токен привилегий:

C#:
if (hToken != IntPtr.Zero)
            {
                CloseHandle(hToken);
            }

Наконец главная функция, непосредственно отвечающая за переключение привилегий:

C#:
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
        SuppressUnmanagedCodeSecurityAttribute]
        static extern int AdjustTokenPrivileges(
            IntPtr TokenHandle,
            int DisableAllPrivileges,
            IntPtr NewState,
            int BufferLength,
            IntPtr PreviousState,
            ref int ReturnLength);


Вот весь ключевой блок логики смены привилегий:

C#:
if (OpenProcessToken(proc.Handle, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
                        ref hToken) != 0)
                    {
                        tkp.PrivilegeCount = 1;
                        tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
                        tkp.Privileges[1] = tLUID.HighPart;
                        tkp.Privileges[0] = tLUID.LowPart;
                        const int bufLength = 256;
                        IntPtr tu = Marshal.AllocHGlobal( bufLength );
                        Marshal.StructureToPtr(tkp, tu, true);
                        if(AdjustTokenPrivileges(hToken, 0, tu, bufLength, IntPtr.Zero, ref
                            ltkpOld) != 0)
                        {
                            // successful AdjustTokenPrivileges doesn't mean privilege could be    changed
                                if (Marshal.GetLastWin32Error() == 0)
                                {
                                    retval = true; // Token changed
                                }
                        }
                        TOKEN_PRIVILEGES tokp = (TOKEN_PRIVILEGES) Marshal.PtrToStructure(tu,
                            typeof(TOKEN_PRIVILEGES) );
                        Marshal.FreeHGlobal( tu );
                    }

Как видите вызов достаточно сложный, используется Сишный процедурный подход к заполнению полей структуры и передачи его по ссылке в вызываемую функцию.

После вызова проверяется наличие ошибки, также в стиле Си:

C#:
if (Marshal.GetLastWin32Error() == 0)
                                {
                                    retval = true; // Token changed
                                }

0 это код возрата для успешного вызова, если он есть — считается что операция смены привилегий была выполнена успешно.

Наконец последняя функция, про которую стоит рассказать:

C#:
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true),
        SuppressUnmanagedCodeSecurityAttribute]
        static extern bool LookupPrivilegeValue(
            string lpSystemName,
            string lpName,
            ref LUID lpLuid);

Она отвечает за поиск привилегии по имени, полагаю ведь заметили что мы передаем некое кодовое наименование при вызове TokenHelper:

TokenHelper.SetPrivilege("SeShutdownPrivilege",true);

Именно эта функция отвечает за поиск конкретной привилегии по названию «SeShutdownPrivilege», вот так выглядит ее вызов:

C#:
if (bEnablePrivilege)
    tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
else
    tkp.Privileges[2] = 0;

if(LookupPrivilegeValue(null , lpszPrivilege , ref tLUID))
            {
            ..

Переменная bEnablePrivilege булевая, это и есть то самое true передаваемое в качестве второго аргумента, а блок:

C#:
if (bEnablePrivilege)
    tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
else
    tkp.Privileges[2] = 0;

Итого​

Все описанное не призыв к немедленным действиям, а лишь повод к размышлению о смысле бытия. Ну там насчет надежности, безопасности и всего такого — что вам продает большая иностранная корпорация.

Задумайтесь, если увидите любимую венду на атомной станции или военном объекте — без всяких ЦРУ и хакеров в ОС Windows адова гора функционала, который легко и просто можно использовать во вред.


Копипаста для любимого XSS от Panchitos
Дополнительная ссылка на источник и на автора -
 
Пожалуйста, обратите внимание, что пользователь заблокирован
жаль не у всех может стоять .Net 3.5
У меня к примеру он не стоит его нужно ставить самому
1724294742667.png
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Насколько я понимаю это не версия из "коробки"?
У меня винда урезаная
 
У меня винда урезаная
То то и оно) как по мне неплохой функционал для запуска различной малвари в рамках пентеста , но не совсем уверен будет ли это функционировать так как я себе это представляю
 
Новое, это хорошо забытое старое. Тема раскрыта как минимум 6 лет назад.


csc.png
 
Новое, это хорошо забытое старое. Тема раскрыта как минимум 6 лет назад.


Посмотреть вложение 93149
Отлично, что вы тут!

каие комментарии можете дать касательно запуска малвари через эту фичу , и насколько это может "конкурировать" с обфускацией и детектингом всей заразы авером?
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Отлично, что вы тут!

каие комментарии можете дать касательно запуска малвари через эту фичу , и насколько это может "конкурировать" с обфускацией и детектингом всей заразы авером?
какая разница где компилировать шарпы у себя или у юзера?
 
какая разница где компилировать шарпы у себя или у юзера?
в этом и есть мой вопрос , я понимаю что единственный верный вариант протестировать, но прежде хотел узнать у более осведомленных пользователей что они думают на этот счет
 
Отлично, что вы тут!

каие комментарии можете дать касательно запуска малвари через эту фичу , и насколько это может "конкурировать" с обфускацией и детектингом всей заразы авером?

Я думаю в этом возможно есть смысл, если исходник обфусцировать, а если компилировать условно вот такое:
C#:
[DllImport("kernel32")]
public static extern IntPtr VirtualAlloc(uint lpAddress, int dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32")]
public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32")]
public static extern int WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
Тот же дефендер прихлопнет бинарь прям на месте рождения.

и куда лояльнее относятся к программам собранным локально на этой же машине.
С чего вдруг? Он не следит за сборкой, АВ без разницы происхождение бинаря, бинарь будет проверен по общим правилам.
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
Я просто напомню, что csc, который предустановлен в системе, компилит очень старую версию Шарпов, удачи вам на этом старом стандарте что-то большое написать и не самовыпилиться от стресса. Для новых стандартов нужно более современные мсбилды ставить. А так да, забавно, но бесполезно чуть более, чем полностью.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх