Tools required:

Vmware IDA Disassembler ollydbg Debugger Hex editor

If you haven’t gone through Part I, we recommend you go through Part I before reading this. In this post, we are going to examine the command and controls traffic and we are going to analyse statically the binary Let’s look at the pcap traffic POST /gate.php HTTP/1.0 Host: Accept: / Accept-Encoding: identity, ;q=0 Accept-Language: en-US Content-Length: 274 Content-Type: application/octet-stream Connection: close Content-Encoding: binary User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022) …..ql.H.l.W..F.udS]<…L….^.cF.;!….A5 v…<6…….D….Z.+.xld.{o.JFY.D..Z….aP.}U…W..6..<NR.7@P.1..p5t.……U

..d.!..3..tHJ.J..I……g8…8....f…i..J..(r..MrnW…f.r.v[…….t.}…D`%}U…m…K.E.n..R&+.iD.:4…9.L….EnR…?.<…|.B…$o.. …/….AHTTP/1.1 200 OK Server: nginx/1.6.2 Date: Thu, 12 Nov 2015 15:35:16 GMT Content-Type: text/html Connection: close X-Powered-By: PHP/5.4.41 .Z..O….& This is a basic initialization request sent to the server and, apparently, it is encrypted. Let’s look at the Pony panel source code to figure out what type of encryption it is using and how is can be decoded back Looking at the source code of gat efor handing basic basic request we find out It first checks if the size of greater than 12 and max_db_len_size . After that data is verified again a header in function verify_report_file_header() which tells us that it has a header as well . Let’s dig in to the source code of password_modules.php to find out. We are able to locate the following functions responsible for verifying the packet header public static function verify_new_file_header(&$data)     {         if (strlen($data) < 4)             return false;         $max_header_len = max(strlen(REPORT_HEADER), strlen(REPORT_PACKED_HEADER), strlen(REPORT_CRYPTED_HEADER));         $rc4_key = substr($data, 0, 4);         $encrypted_header = substr($data, 4, $max_header_len);         $decrypted_header = rc4Decrypt($rc4_key, $encrypted_header);         return self::verify_old_file_header($decrypted_header);     }     public static function verify_old_file_header(&$data)     {         if ((substr($data, 0, strlen(REPORT_HEADER))) == REPORT_HEADER)             return true;         if ((substr($data, 0, strlen(REPORT_PACKED_HEADER))) == REPORT_PACKED_HEADER)             return true;         if ((substr($data, 0, strlen(REPORT_CRYPTED_HEADER))) == REPORT_CRYPTED_HEADER)             return true;         return false;     }     public static function verify_report_file_header(&$data)     {         return self::verify_new_file_header($data) || self::verify_old_file_header($data);     } It consists of two predefined headers new and old one and both of them are check consecutively. Following are the defines for header magic keywords define(“REPORT_HEADER”,”PWDFILE0″); // each password report starts with this header define(“REPORT_PACKED_HEADER”, “PKDFILE0”); // header indicating that report is packed define(“REPORT_CRYPTED_HEADER”, “CRYPTED0”); // header indicating that report is encrypted The maximum size of the header is 12 bytes, so twelve bytes after first 4 bytes always contains the header. First four bytes are used as rc4 key.         $rc4_key = substr($data, 0, 4);         $encrypted_header = substr($data, 4, $max_header_len);         $decrypted_header = rc4Decrypt($rc4_key, $encrypted_header); After decryption, another function is called to decrypt the rest of the report and check the integrity of the report. i.e. pre_decrypt_report()     public static function pre_decrypt_report(&$data, $report_password = ”)     {         if (self::verify_new_file_header($data))         {             self::rand_decrypt($data);         }         if ((substr($data, 0, strlen(REPORT_CRYPTED_HEADER))) != REPORT_CRYPTED_HEADER)             return false;         if (strlen($data) == 0)         {             return false;         } else if (strlen($data) < 12) // length cannot be less than 12 bytes (8-byte header + crc32 checksum)         {             return false;         } else if (strlen($data) > REPORT_LEN_LIMIT)         {             return false;         } elseif (strlen($data) == 12) // empty report             return false;         // extract crc32 checksum from datastream         $crc_chk = data_int32(substr($data, strlen($data)-4));         // remove crc32 checksum from the encrypted data stream         $encrypted_data = substr($data, 0, -4);         // check report validness         $crc_chk = obf_crc32($crc_chk);         if ((int)crc32($encrypted_data) != (int)$crc_chk)         {             return false;         }         $decrypted_data = rc4Decrypt($report_password, substr($encrypted_data, 8));         // there’s another crc32 checksum available to verify the decryption process         // extract crc32 checksum from decrypted datastream         $crc_chk = data_int32(substr($decrypted_data, strlen($decrypted_data)-4));         // remove crc32 checksum from the data stream         $decrypted_data_check = substr($decrypted_data, 0, -4);         // check report validness         $crc_chk = obf_crc32($crc_chk);         if ((int)crc32($decrypted_data_check) != (int)$crc_chk)         {             return false;         }         $data = $decrypted_data;         return true;     } } In this function, the header is verified again and 4 bytes value is extracted from the end of data stream. This value is used as a CRC32 check sum for the data crc32 check sum is removed and then integrity is calculated. After successfully verifying the crc32 hash. This data chunk after first 8 bytes is decoded with a predefined rc4 key taken form the database $report_password $pony_db_report_password = $pony_db->get_option(‘report_password’, ”, REPORT_DEFAULT_PASSWORD); Again crc32 check sum is extracted form the last 4 bytes of decrypted stream and is checked for integrity. If it a type packed file then it is uncompressed with aplib . If it is a basic request, it is rc4 decrypted and parsed in a structure             // process report             ob_start(); // detect report processing noise             error_reporting(E_ALL);             $parse_result = $report->process_report($received_report_data,                     $pony_db_report_password);             $ob_data = trim(ob_get_contents());             error_reporting(0);             ob_end_clean(); Before it checks if the report ID is already present in the system and if so it does not proceed with creating a new ID for the particular report. It then proceeds filling up information from unencrypted data into the database, which is of the following format. $pony_db->update_parsed_report($report_id, $report->report_os_name, $report->report_is_win64, $report->report_is_admin, $report->report_hwid, $report->report_version_id, $url_list_array, $report->log->log_lines, $report->cert_lines, $report->wallet_lines, $email_lines); Let’s now have a look how Pony tries to steal passwords. All the routines responsible for stealing stored credentials are stored in a pointer array:

let’s look at a function responsible for stealing FFFTP passwords.

It first looks for encoded stored password in SoftwareSotaFFFTP registry key and after all the keys are found it will try to decode them using its own decoding algorithm