From 2084d0d70dbd7d69c743afc10bed85cb4dc4b152 Mon Sep 17 00:00:00 2001 From: svfcode Date: Tue, 20 Jan 2026 07:15:47 +0300 Subject: [PATCH 1/5] New. BinaryCheck. Init module. --- inc/spbc-settings.php | 6 + js/spbc-scanner-plugin.min.js | 2 +- js/spbc-scanner-plugin.min.js.map | 2 +- js/src/spbc-scanner-plugin.js | 2 + .../BinaryCheckModule/BinaryCheckModule.php | 123 ++++++++++++++++++ .../SpbctWP/Scanner/ScannerQueue.php | 13 +- .../Stages/BinaryAnalysis.php | 59 +++++++++ lib/CleantalkSP/SpbctWP/Scanner/Surface.php | 14 ++ 8 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php create mode 100644 lib/CleantalkSP/SpbctWP/Scanner/ScanningStagesModule/Stages/BinaryAnalysis.php diff --git a/inc/spbc-settings.php b/inc/spbc-settings.php index d2486c918..ec51d0290 100644 --- a/inc/spbc-settings.php +++ b/inc/spbc-settings.php @@ -3014,6 +3014,12 @@ function spbc_field_scanner() . ' -> '; } + // if ($spbc->settings['scanner__binary_analysis']) { + echo '' + . __('Binary analysis', 'security-malware-firewall') + . ' -> '; + // } + if ($spbc->settings['scanner__schedule_send_heuristic_suspicious_files']) { echo '' . __('Schedule suspicious files sending', 'security-malware-firewall') diff --git a/js/spbc-scanner-plugin.min.js b/js/spbc-scanner-plugin.min.js index e635fef3e..3f805b82b 100644 --- a/js/spbc-scanner-plugin.min.js +++ b/js/spbc-scanner-plugin.min.js @@ -1,2 +1,2 @@ -class SpbcMalwareScanner{first_start=!0;active=!1;root="";settings=[];states=["get_cms_hashes","get_modules_hashes","clean_results","file_system_analysis","get_approved_hashes","get_denied_hashes","signature_analysis","heuristic_analysis","schedule_send_heuristic_suspicious_files","auto_cure_backup","auto_cure","os_cron_analysis","db_trigger_analysis","outbound_links","frontend_analysis","important_files_listing","send_results"];state=null;offset=0;amount=0;amount_coefficient=1;total_scanned=0;scan_percent=0;percent_completed=0;paused=!1;button=null;spinner=null;progress_overall=null;progressbar=null;progressbar_text=null;timeout=6e4;state_timer=0;constructor(t){for(var e in jQuery("#spbcscan-results-log-module").length&&jQuery(".spbc-scan-log-title").removeClass("spbc---hidden"),void 0!==t.settings.auto_cure&&(t.settings.scanner__auto_cure_backup="1"),t)void 0!==this[e]&&(this[e]=t[e])}actionControl(){null===this.state?this.start():this.paused?(this.resume(),this.controller()):this.pause()}start(){this.active=!0,this.state_timer=Math.round((new Date).getTime()/1e3),this.state=this.getNextState(null),this.setPercents(0),this.scan_percent=0,this.offset=0,this.progress_overall.children("span").removeClass("spbc_bold").filter(".spbc_overall_scan_status_"+this.state).addClass("spbc_bold"),this.progressbar.show(500),this.progress_overall.show(500),this.button.html(spbcScaner.button_scan_pause),this.spinner.css({display:"inline"}),setTimeout(()=>{this.controller()},1e3)}pause(t,e,s){console.log("PAUSE"),this.button.html(spbcScaner.button_scan_resume),this.spinner.css({display:"none"}),this.paused=!0,this.active=!1}resume(t){console.log("RESUME"),this.button.html(spbcScaner.button_scan_pause),this.spinner.css({display:"inline"}),this.paused=!1,this.active=!0}end(t){this.progressbar.hide(500),this.progress_overall.hide(500),this.button.html(spbcScaner.button_scan_perform),this.spinner.css({display:"none"}),this.state=null,this.plug=!1,this.total_scanned=0,this.active=!1,t?document.location=document.location:spbcSendAJAXRequest({action:"spbc_scanner_tab__reload_accordion"},{notJson:!0,callback:function(t,e,s,a){jQuery(a).accordion("destroy").html(t).accordion({header:"h3",heightStyle:"content",collapsible:!0,active:!1}),spbcTblBulkActionsListen(),spbcTblRowActionsListen(),spbcTblPaginationListen(),spbcTblSortListen(),spbcStartShowHide(),spbcScannerReloadScanInfo()}},jQuery("#spbc_scan_accordion"))}controller(t){if(console.log(this.state),void 0!==t&&t.end){if(this.state=this.getNextState(this.state),void 0===this.state)return void this.end();this.setPercents(0),this.scan_percent=0,this.offset=0,this.progress_overall.children("span").removeClass("spbc_bold").filter(".spbc_overall_scan_status_"+this.state).addClass("spbc_bold")}if(!0!==this.paused){var e={action:"spbc_scanner_controller_front",method:this.state,offset:this.offset},t={type:"GET",success:this.success,callback:this.successCallback,error:this.error,errorOutput:this.errorOutput,complete:null,context:this,timeout:12e4};switch(this.state){case"get_modules_hashes":this.amount=2;break;case"clear_table":this.amount=1e4;break;case"file_system_analysis":this.amount=700;break;case"auto_cure":this.amount=5;break;case"outbound_links":this.amount=10;break;case"frontend_analysis":this.amount=spbcSettings.frontendAnalysisAmount;break;case"signature_analysis":this.amount=10,e.status="UNKNOWN,MODIFIED,OK,INFECTED,ERROR";break;case"heuristic_analysis":this.amount=4,e.status="UNKNOWN,MODIFIED,OK,INFECTED,ERROR";break;case"schedule_send_heuristic_suspicious_files":this.amount=1}e.amount=Math.round(this.amount*this.amount_coefficient),spbcSendAJAXRequest(e,t,jQuery("#spbc_scan_accordion"))}}setCoefficients(t){let e=this.amount_coefficient;"file_system_analysis"===t&&(e*=1.5),this.amount_coefficient=e}getNextState(t){return t=null===t?this.states[0]:this.states[this.states.indexOf(t)+1],t=void 0!==this.settings["scanner__"+t]&&0==+this.settings["scanner__"+t]?this.getNextState(t):t}setPercents(t){this.percent_completed=Math.floor(100*t)/100,this.progressbar.progressbar("option","value",this.percent_completed),this.progressbar_text.text(spbcScaner["progressbar_"+this.state]+" - "+this.percent_completed+"%")}success(t){t.error?this.error({status:200,responseText:t.error},t.error,t.msg):this.successCallback&&this.successCallback(t,this.data,this.obj)}successCallback(e){if(console.log(e),this.interactAccordion(e),void 0!==e.total&&(this.scan_percent=100/e.total),void 0!==e.processed_items&&("heuristic_analysis"===this.state&&0!==typeof e.total&&this.logRaw('

Heuristic Analysis

'),"signature_analysis"===this.state&&0!==typeof e.total&&this.logRaw('

Signature Analysis

'),this.logFileEntry(e.processed_items)),void 0!==e.stage_data_for_logging&&this.logStageEntry(e.stage_data_for_logging),void 0!==e.cured&&0{this.controller(e)},300)}interactAccordion(t){t.hasOwnProperty("interactivity_data")&&t.interactivity_data.hasOwnProperty("update_text")&&t.interactivity_data.update_text&&t.interactivity_data.hasOwnProperty("refresh_data")&&t.interactivity_data.refresh_data.hasOwnProperty("do_refresh")&&t.interactivity_data.refresh_data.do_refresh&&t.interactivity_data.refresh_data.hasOwnProperty("control_tab")&&t.interactivity_data.refresh_data.control_tab&&spbcReloadAccordion(t.interactivity_data.refresh_data.control_tab,t.interactivity_data.update_text)}error(t,e,s){var a=this.errorOutput;if(console.log("%c APBCT_AJAX_ERROR","color: red;"),console.log(e),console.log(s),console.log(t),"error"==e&&(""==s||"Not found"==s)&&(this.tryCount||(this.tryCount=0,this.retryLimit=30),this.tryCount++,console.log("Try #"+this.tryCount),this.setCoefficients(this.state),this.tryCount<=this.retryLimit))this.pause(),this.resume(),this.controller();else{if(200===t.status)if("parsererror"===e)a("Unexpected response from server. See console for details.",this.state),console.log("%c "+t.responseText,"color: pink;");else{let t=e;void 0!==s&&(t+=" Additional info: "+s),a(t,this.state)}else 500===t.status?a("Internal server error.",this.state):a("Unexpected response code: "+t.status+". Error: "+e,this.state);this.progressbar&&this.progressbar.fadeOut("slow"),this.end()}}errorOutput(t,e){spbcModal.open().putError(t+"
Stage: "+e)}logRaw(t){jQuery(".spbc-scan-log-title").removeClass("spbc---hidden"),jQuery(".spbc_log-wrapper").removeClass("spbc---hidden"),jQuery(".spbc_log-wrapper .panel-body").prepend(t)}logFileEntry(t){for(var e in t)e&&this.logRaw('

'+this.getSiteUTCShiftedTimeString()+" - "+t[e].path+" - "+t[e].module+": "+t[e].status+"

")}logStageEntry(t){void 0!==jQuery(".panel-body .spbc_log-line span").first()&&void 0!==jQuery(".panel-body .spbc_log-line span").first()[0]&&jQuery(".panel-body .spbc_log-line span").first()[0].textContent===t.description||this.logRaw('

test '+this.getSiteUTCShiftedTimeString()+" - "+t.title+" "+t.description+"

")}showLinkForShuffleSalts(t){jQuery("#spbc_notice_about_shuffle_link").remove(),jQuery(jQuery(".spbc_tab--active .spbc_wrapper_field p")[1]).after('")}getSiteUTCShiftedTimeString(){let t=!1;var e=-1*(new Date).getTimezoneOffset()*1e3*60,e=(t="undefined"!=typeof spbcScaner&&void 0!==spbcScaner.timezone_shift&&!1!==spbcScaner.timezone_shift?Date.now()-e+1e3*spbcScaner.timezone_shift:t)?new Date(t):new Date,s=new Intl.DateTimeFormat("en-US",{month:"short"}).format,a=String(e.getMinutes()).padStart(2,"0"),i=String(e.getSeconds()).padStart(2,"0");return s(e)+" "+e.getDate()+" "+e.getFullYear()+" "+e.getHours()+":"+a+":"+i}} +class SpbcMalwareScanner{first_start=!0;active=!1;root="";settings=[];states=["get_cms_hashes","get_modules_hashes","clean_results","file_system_analysis","get_approved_hashes","get_denied_hashes","signature_analysis","heuristic_analysis","schedule_send_heuristic_suspicious_files","auto_cure_backup","auto_cure","os_cron_analysis","db_trigger_analysis","binary_analysis","outbound_links","frontend_analysis","important_files_listing","send_results"];state=null;offset=0;amount=0;amount_coefficient=1;total_scanned=0;scan_percent=0;percent_completed=0;paused=!1;button=null;spinner=null;progress_overall=null;progressbar=null;progressbar_text=null;timeout=6e4;state_timer=0;constructor(t){for(var e in jQuery("#spbcscan-results-log-module").length&&jQuery(".spbc-scan-log-title").removeClass("spbc---hidden"),void 0!==t.settings.auto_cure&&(t.settings.scanner__auto_cure_backup="1"),t)void 0!==this[e]&&(this[e]=t[e])}actionControl(){null===this.state?this.start():this.paused?(this.resume(),this.controller()):this.pause()}start(){this.active=!0,this.state_timer=Math.round((new Date).getTime()/1e3),this.state=this.getNextState(null),this.setPercents(0),this.scan_percent=0,this.offset=0,this.progress_overall.children("span").removeClass("spbc_bold").filter(".spbc_overall_scan_status_"+this.state).addClass("spbc_bold"),this.progressbar.show(500),this.progress_overall.show(500),this.button.html(spbcScaner.button_scan_pause),this.spinner.css({display:"inline"}),setTimeout(()=>{this.controller()},1e3)}pause(t,e,s){console.log("PAUSE"),this.button.html(spbcScaner.button_scan_resume),this.spinner.css({display:"none"}),this.paused=!0,this.active=!1}resume(t){console.log("RESUME"),this.button.html(spbcScaner.button_scan_pause),this.spinner.css({display:"inline"}),this.paused=!1,this.active=!0}end(t){this.progressbar.hide(500),this.progress_overall.hide(500),this.button.html(spbcScaner.button_scan_perform),this.spinner.css({display:"none"}),this.state=null,this.plug=!1,this.total_scanned=0,this.active=!1,t?document.location=document.location:spbcSendAJAXRequest({action:"spbc_scanner_tab__reload_accordion"},{notJson:!0,callback:function(t,e,s,a){jQuery(a).accordion("destroy").html(t).accordion({header:"h3",heightStyle:"content",collapsible:!0,active:!1}),spbcTblBulkActionsListen(),spbcTblRowActionsListen(),spbcTblPaginationListen(),spbcTblSortListen(),spbcStartShowHide(),spbcScannerReloadScanInfo()}},jQuery("#spbc_scan_accordion"))}controller(t){if(console.log(this.state),void 0!==t&&t.end){if(this.state=this.getNextState(this.state),void 0===this.state)return void this.end();this.setPercents(0),this.scan_percent=0,this.offset=0,this.progress_overall.children("span").removeClass("spbc_bold").filter(".spbc_overall_scan_status_"+this.state).addClass("spbc_bold")}if(!0!==this.paused){var e={action:"spbc_scanner_controller_front",method:this.state,offset:this.offset},t={type:"GET",success:this.success,callback:this.successCallback,error:this.error,errorOutput:this.errorOutput,complete:null,context:this,timeout:12e4};switch(this.state){case"get_modules_hashes":this.amount=2;break;case"clear_table":this.amount=1e4;break;case"file_system_analysis":this.amount=700;break;case"auto_cure":this.amount=5;break;case"outbound_links":this.amount=10;break;case"frontend_analysis":this.amount=spbcSettings.frontendAnalysisAmount;break;case"signature_analysis":this.amount=10,e.status="UNKNOWN,MODIFIED,OK,INFECTED,ERROR";break;case"heuristic_analysis":case"binary_analysis":this.amount=4,e.status="UNKNOWN,MODIFIED,OK,INFECTED,ERROR";break;case"schedule_send_heuristic_suspicious_files":this.amount=1}e.amount=Math.round(this.amount*this.amount_coefficient),spbcSendAJAXRequest(e,t,jQuery("#spbc_scan_accordion"))}}setCoefficients(t){let e=this.amount_coefficient;"file_system_analysis"===t&&(e*=1.5),this.amount_coefficient=e}getNextState(t){return t=null===t?this.states[0]:this.states[this.states.indexOf(t)+1],t=void 0!==this.settings["scanner__"+t]&&0==+this.settings["scanner__"+t]?this.getNextState(t):t}setPercents(t){this.percent_completed=Math.floor(100*t)/100,this.progressbar.progressbar("option","value",this.percent_completed),this.progressbar_text.text(spbcScaner["progressbar_"+this.state]+" - "+this.percent_completed+"%")}success(t){t.error?this.error({status:200,responseText:t.error},t.error,t.msg):this.successCallback&&this.successCallback(t,this.data,this.obj)}successCallback(e){if(console.log(e),this.interactAccordion(e),void 0!==e.total&&(this.scan_percent=100/e.total),void 0!==e.processed_items&&("heuristic_analysis"===this.state&&0!==typeof e.total&&this.logRaw('

Heuristic Analysis

'),"signature_analysis"===this.state&&0!==typeof e.total&&this.logRaw('

Signature Analysis

'),this.logFileEntry(e.processed_items)),void 0!==e.stage_data_for_logging&&this.logStageEntry(e.stage_data_for_logging),void 0!==e.cured&&0{this.controller(e)},300)}interactAccordion(t){t.hasOwnProperty("interactivity_data")&&t.interactivity_data.hasOwnProperty("update_text")&&t.interactivity_data.update_text&&t.interactivity_data.hasOwnProperty("refresh_data")&&t.interactivity_data.refresh_data.hasOwnProperty("do_refresh")&&t.interactivity_data.refresh_data.do_refresh&&t.interactivity_data.refresh_data.hasOwnProperty("control_tab")&&t.interactivity_data.refresh_data.control_tab&&spbcReloadAccordion(t.interactivity_data.refresh_data.control_tab,t.interactivity_data.update_text)}error(t,e,s){var a=this.errorOutput;if(console.log("%c APBCT_AJAX_ERROR","color: red;"),console.log(e),console.log(s),console.log(t),"error"==e&&(""==s||"Not found"==s)&&(this.tryCount||(this.tryCount=0,this.retryLimit=30),this.tryCount++,console.log("Try #"+this.tryCount),this.setCoefficients(this.state),this.tryCount<=this.retryLimit))this.pause(),this.resume(),this.controller();else{if(200===t.status)if("parsererror"===e)a("Unexpected response from server. See console for details.",this.state),console.log("%c "+t.responseText,"color: pink;");else{let t=e;void 0!==s&&(t+=" Additional info: "+s),a(t,this.state)}else 500===t.status?a("Internal server error.",this.state):a("Unexpected response code: "+t.status+". Error: "+e,this.state);this.progressbar&&this.progressbar.fadeOut("slow"),this.end()}}errorOutput(t,e){spbcModal.open().putError(t+"
Stage: "+e)}logRaw(t){jQuery(".spbc-scan-log-title").removeClass("spbc---hidden"),jQuery(".spbc_log-wrapper").removeClass("spbc---hidden"),jQuery(".spbc_log-wrapper .panel-body").prepend(t)}logFileEntry(t){for(var e in t)e&&this.logRaw('

'+this.getSiteUTCShiftedTimeString()+" - "+t[e].path+" - "+t[e].module+": "+t[e].status+"

")}logStageEntry(t){void 0!==jQuery(".panel-body .spbc_log-line span").first()&&void 0!==jQuery(".panel-body .spbc_log-line span").first()[0]&&jQuery(".panel-body .spbc_log-line span").first()[0].textContent===t.description||this.logRaw('

test '+this.getSiteUTCShiftedTimeString()+" - "+t.title+" "+t.description+"

")}showLinkForShuffleSalts(t){jQuery("#spbc_notice_about_shuffle_link").remove(),jQuery(jQuery(".spbc_tab--active .spbc_wrapper_field p")[1]).after('")}getSiteUTCShiftedTimeString(){let t=!1;var e=-1*(new Date).getTimezoneOffset()*1e3*60,e=(t="undefined"!=typeof spbcScaner&&void 0!==spbcScaner.timezone_shift&&!1!==spbcScaner.timezone_shift?Date.now()-e+1e3*spbcScaner.timezone_shift:t)?new Date(t):new Date,s=new Intl.DateTimeFormat("en-US",{month:"short"}).format,a=String(e.getMinutes()).padStart(2,"0"),i=String(e.getSeconds()).padStart(2,"0");return s(e)+" "+e.getDate()+" "+e.getFullYear()+" "+e.getHours()+":"+a+":"+i}} //# sourceMappingURL=spbc-scanner-plugin.min.js.map diff --git a/js/spbc-scanner-plugin.min.js.map b/js/spbc-scanner-plugin.min.js.map index d43b21e41..403885ce3 100644 --- a/js/spbc-scanner-plugin.min.js.map +++ b/js/spbc-scanner-plugin.min.js.map @@ -1 +1 @@ -{"version":3,"file":"spbc-scanner-plugin.min.js","sources":["spbc-scanner-plugin.js"],"sourcesContent":["'use strict';\n\n/**\n * class SpbcMalwareScanner\n */\nclass SpbcMalwareScanner {/* eslint-disable-line no-unused-vars */\n first_start = true;\n\n active = false;\n\n root = '';\n settings = [];\n states = [\n 'get_cms_hashes',\n 'get_modules_hashes',\n 'clean_results',\n 'file_system_analysis',\n 'get_approved_hashes',\n 'get_denied_hashes',\n 'signature_analysis',\n 'heuristic_analysis',\n 'schedule_send_heuristic_suspicious_files',\n 'auto_cure_backup',\n 'auto_cure',\n 'os_cron_analysis',\n 'db_trigger_analysis',\n 'outbound_links',\n 'frontend_analysis',\n 'important_files_listing',\n 'send_results',\n ];\n state = null;\n offset = 0;\n amount = 0;\n amount_coefficient = 1;\n total_scanned = 0;\n scan_percent = 0;\n percent_completed = 0;\n\n paused = false;\n\n button = null;\n spinner = null;\n\n progress_overall = null;\n progressbar = null;\n progressbar_text = null;\n\n timeout = 60000;\n\n state_timer = 0;\n\n /**\n * constructor\n * @param {array} properties\n */\n constructor( properties ) {\n if (jQuery('#spbcscan-results-log-module').length) {\n jQuery('.spbc-scan-log-title').removeClass('spbc---hidden');\n }\n\n // Crunch for cure backups\n if ( typeof properties['settings']['auto_cure'] !== 'undefined' ) {\n properties['settings']['scanner__auto_cure_backup'] = '1';\n }\n\n for ( let key in properties ) {\n if ( typeof this[key] !== 'undefined' ) {\n this[key] = properties[key];\n }\n }\n };\n\n /**\n * Function Action Control\n */\n actionControl() {\n if (this.state === null) {\n this.start();\n } else if (this.paused) {\n this.resume();\n this.controller();\n } else {\n this.pause();\n }\n };\n\n /**\n * Function Start\n */\n start() {\n this.active = true;\n this.state_timer = Math.round(new Date().getTime() /1000);\n\n this.state = this.getNextState( null );\n\n this.setPercents( 0 );\n this.scan_percent = 0;\n this.offset = 0;\n this.progress_overall.children('span')\n .removeClass('spbc_bold')\n .filter('.spbc_overall_scan_status_' + this.state)\n .addClass('spbc_bold');\n\n this.progressbar.show(500);\n this.progress_overall.show(500);\n this.button.html(spbcScaner.button_scan_pause);\n this.spinner.css({display: 'inline'});\n\n setTimeout(() => {\n this.controller();\n }, 1000);\n };\n\n /**\n * Function Pause\n * @param {*} result\n * @param {*} data\n * @param {*} opt\n */\n pause( result, data, opt ) {\n console.log('PAUSE');\n this.button.html(spbcScaner.button_scan_resume);\n this.spinner.css({display: 'none'});\n this.paused = true;\n this.active = false;\n };\n\n /**\n * Function Resume\n * @param {*} opt\n */\n resume( opt ) {\n console.log('RESUME');\n this.button.html(spbcScaner.button_scan_pause);\n this.spinner.css({display: 'inline'});\n this.paused = false;\n this.active = true;\n };\n\n /**\n * Function End\n * @param {bool} reload\n */\n end( reload ) {\n this.progressbar.hide(500);\n this.progress_overall.hide(500);\n this.button.html(spbcScaner.button_scan_perform);\n this.spinner.css({display: 'none'});\n this.state = null;\n this.plug = false;\n this.total_scanned = 0;\n this.active = false;\n\n if (reload) {\n document.location = document.location;\n } else {\n spbcSendAJAXRequest(\n {action: 'spbc_scanner_tab__reload_accordion'},\n {\n notJson: true,\n callback: function(result, data, params, obj) {\n jQuery(obj).accordion('destroy')\n .html(result)\n .accordion({\n header: 'h3',\n heightStyle: 'content',\n collapsible: true,\n active: false,\n });\n spbcTblBulkActionsListen();\n spbcTblRowActionsListen();\n spbcTblPaginationListen();\n spbcTblSortListen();\n spbcStartShowHide();\n spbcScannerReloadScanInfo();\n },\n },\n jQuery('#spbc_scan_accordion'),\n );\n }\n };\n\n /**\n * Function Controller\n * @param {obj} result\n */\n controller( result ) {\n console.log(this.state);\n\n // The current stage is over. Switching to the new one\n if ( typeof result !== 'undefined' && result.end ) {\n this.state = this.getNextState( this.state );\n\n // End condition\n if (typeof this.state === 'undefined') {\n this.end();\n return;\n }\n\n // Set percent to 0\n this.setPercents( 0 );\n this.scan_percent = 0;\n this.offset = 0;\n\n // Changing visualizing of the current stage\n this.progress_overall.children('span')\n .removeClass('spbc_bold')\n .filter('.spbc_overall_scan_status_' + this.state)\n .addClass('spbc_bold');\n }\n\n // Break execution if paused\n if ( this.paused === true ) {\n return;\n }\n\n // // AJAX params\n let data = {\n action: 'spbc_scanner_controller_front',\n method: this.state,\n offset: this.offset,\n };\n\n let params = {\n type: 'GET',\n success: this.success,\n callback: this.successCallback,\n error: this.error,\n errorOutput: this.errorOutput,\n complete: null,\n context: this,\n timeout: 120000,\n };\n\n switch (this.state) {\n case 'get_modules_hashes': this.amount = 2; break;\n case 'clear_table': this.amount = 10000; break;\n case 'file_system_analysis': this.amount = 700; break;\n case 'auto_cure': this.amount = 5; break;\n case 'outbound_links': this.amount = 10; break;\n case 'frontend_analysis': this.amount = spbcSettings.frontendAnalysisAmount; break;\n case 'signature_analysis': this.amount = 10; data.status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR'; break;\n case 'heuristic_analysis': this.amount = 4; data.status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR'; break;\n case 'schedule_send_heuristic_suspicious_files': this.amount = 1; break;\n }\n\n data.amount = Math.round(this.amount * this.amount_coefficient);\n\n spbcSendAJAXRequest(\n data,\n params,\n jQuery('#spbc_scan_accordion'),\n );\n };\n\n /**\n * Set Coefficients\n * @param {string} state\n */\n setCoefficients( state ) {\n let coefficient = this.amount_coefficient;\n switch (state) {\n case 'file_system_analysis': coefficient *= 1.5; break;\n }\n this.amount_coefficient = coefficient;\n };\n\n /**\n * Get Next State\n * @param {string} state\n * @return {number}\n */\n getNextState( state ) {\n state = state === null ? this.states[0] : this.states[this.states.indexOf( state ) + 1];\n\n if (typeof this.settings['scanner__' + state] !== 'undefined' && +this.settings['scanner__' + state] === 0) {\n state = this.getNextState( state );\n }\n\n return state;\n };\n\n /**\n * Set Percents\n * @param {number} percents\n */\n setPercents( percents ) {\n this.percent_completed = Math.floor( percents * 100 ) / 100;\n this.progressbar.progressbar( 'option', 'value', this.percent_completed );\n this.progressbar_text.text( spbcScaner['progressbar_' + this.state] + ' - ' + this.percent_completed + '%' );\n };\n\n /**\n * Function Success\n * @param {obj} response\n */\n success( response ) {\n if ( !! response.error ) {\n this.error(\n {status: 200, responseText: response.error},\n response.error,\n response.msg,\n );\n } else {\n if ( this.successCallback ) {\n this.successCallback( response, this.data, this.obj );\n }\n }\n };\n\n // Processing response from backend\n /**\n * Success Callback\n * @param {obj} result\n */\n successCallback( result ) {\n console.log( result );\n\n this.interactAccordion(result);\n\n if ( typeof result.total !== 'undefined' ) {\n this.scan_percent = 100 / result.total;\n }\n\n if ( typeof result.processed_items !== 'undefined') {\n if ( this.state === 'heuristic_analysis' && typeof result.total !== 0 ) {\n this.logRaw('

Heuristic Analysis

');\n }\n if ( this.state === 'signature_analysis' && typeof result.total !== 0 ) {\n this.logRaw('

Signature Analysis

');\n }\n\n this.logFileEntry( result.processed_items );\n }\n\n if ( typeof result.stage_data_for_logging !== 'undefined') {\n this.logStageEntry( result.stage_data_for_logging );\n }\n\n // Add link on shuffle salt if cured\n if (result.cured !== undefined && Number(result.cured) > 0) {\n this.showLinkForShuffleSalts(result.message);\n }\n\n if ( result.end !== true && result.end !== 1 ) {\n let processedPercents = this.percent_completed + result.processed * this.scan_percent;\n if (result.stage_data_for_logging.title === 'File System Analysis' && processedPercents > 100) {\n processedPercents = 100;\n }\n this.setPercents(processedPercents);\n this.offset = this.offset + result.processed;\n this.controller( result );\n } else {\n console.log( this.state +\n ' stage took ' +\n ( Math.round(new Date().getTime() /1000) - this.state_timer ) +\n ' seconds to complete' );\n this.state_timer = Math.round(new Date().getTime()/1000);\n this.setPercents( 100 );\n this.scan_percent = 0;\n this.offset = 0;\n setTimeout(() => {\n this.controller( result );\n }, 300);\n }\n };\n\n /**\n * Run interactive refresh for accordion.\n * @param {obj|string[]} result\n */\n interactAccordion(result) {\n // validation control\n if (result.hasOwnProperty('interactivity_data') &&\n result.interactivity_data.hasOwnProperty('update_text') &&\n result.interactivity_data.update_text &&\n result.interactivity_data.hasOwnProperty('refresh_data') &&\n result.interactivity_data.refresh_data.hasOwnProperty('do_refresh') &&\n result.interactivity_data.refresh_data.do_refresh &&\n result.interactivity_data.refresh_data.hasOwnProperty('control_tab') &&\n result.interactivity_data.refresh_data.control_tab\n ) {\n spbcReloadAccordion(\n result.interactivity_data.refresh_data.control_tab,\n result.interactivity_data.update_text,\n );\n }\n }\n\n /**\n * Function Error\n * @param {object} xhr\n * @param {string} status\n * @param {string} error\n */\n error( xhr, status, error ) {\n let errorOutput = this.errorOutput;\n\n console.log( '%c APBCT_AJAX_ERROR', 'color: red;' );\n console.log( status );\n console.log( error );\n console.log( xhr );\n\n if (status == 'error' && (error == '' || error == 'Not found')) {\n if (!this.tryCount) {\n this.tryCount = 0;\n this.retryLimit = 30;\n }\n this.tryCount++;\n console.log('Try #' + this.tryCount);\n this.setCoefficients(this.state);\n if (this.tryCount <= this.retryLimit) {\n this.pause();\n this.resume();\n this.controller();\n return;\n }\n }\n\n if ( xhr.status === 200 ) {\n if ( status === 'parsererror' ) {\n errorOutput( 'Unexpected response from server. See console for details.', this.state );\n console.log( '%c ' + xhr.responseText, 'color: pink;' );\n } else {\n let errorString = status;\n if ( typeof error !== 'undefined' ) {\n errorString += ' Additional info: ' + error;\n }\n errorOutput( errorString, this.state );\n }\n } else if (xhr.status === 500) {\n errorOutput( 'Internal server error.', this.state);\n } else {\n errorOutput('Unexpected response code: ' + xhr.status + '. Error: ' + status, this.state);\n }\n\n if ( this.progressbar ) {\n this.progressbar.fadeOut('slow');\n }\n\n this.end();\n };\n\n /**\n * Error Output\n * @param {string} errorMsg\n * @param {string} stage\n */\n errorOutput( errorMsg, stage ) {\n spbcModal.open().putError( errorMsg + '
Stage: ' + stage);\n };\n\n /**\n * Log Raw\n * @param {htmlString|Element|Text|Array|jQuery} messageToLog\n */\n logRaw(messageToLog) {\n jQuery('.spbc-scan-log-title').removeClass('spbc---hidden');\n jQuery('.spbc_log-wrapper').removeClass('spbc---hidden');\n jQuery('.spbc_log-wrapper .panel-body').prepend( messageToLog );\n };\n\n /**\n * Log File Entry\n * @param {array} items\n */\n logFileEntry(items) {\n for ( let key in items ) {\n if ( key ) {\n this.logRaw(\n '

' +\n this.getSiteUTCShiftedTimeString() + ' - ' +\n items[key].path + ' - ' + items[key].module +\n ': ' + items[key].status + '' +\n '

');\n }\n }\n };\n\n /**\n * Log Stage Entry\n * @param {obj} data\n */\n logStageEntry(data) {\n if (typeof jQuery('.panel-body .spbc_log-line span').first() !== 'undefined' &&\n typeof jQuery('.panel-body .spbc_log-line span').first()[0] !== 'undefined' &&\n jQuery('.panel-body .spbc_log-line span').first()[0].textContent === data.description\n ) {\n return;\n }\n this.logRaw( '

test ' +\n this.getSiteUTCShiftedTimeString() + ' - ' + '' +\n data.title + ' ' + '' + data.description + '

' );\n };\n\n /**\n * Show Link For Shuffle Salts\n * @param {string} message\n */\n showLinkForShuffleSalts(message) {\n jQuery('#spbc_notice_about_shuffle_link').remove();\n jQuery(jQuery('.spbc_tab--active .spbc_wrapper_field p')[1])\n .after(\n '
' +\n '' +\n message +\n '' +\n '
',\n );\n }\n\n /**\n * Get Site UTC Shifted Time String\n * @return {string}\n */\n getSiteUTCShiftedTimeString() {\n let utcShiftedTs = false;\n // gettings current system/browser offset\n let currentBrowserOffset = new Date().getTimezoneOffset();\n currentBrowserOffset = currentBrowserOffset * -1 * 1000 * 60;\n // chek if global ct object is defined\n if (typeof spbcScaner !== 'undefined' &&\n typeof spbcScaner.timezone_shift !== 'undefined' &&\n spbcScaner.timezone_shift !== false) {\n utcShiftedTs = Date.now() - currentBrowserOffset + (spbcScaner.timezone_shift * 1000);\n }\n let ctDate = utcShiftedTs ? new Date(utcShiftedTs) : new Date();\n // construct date string\n let shortMonthName = new Intl.DateTimeFormat('en-US', {month: 'short'}).format;\n let minutes = String(ctDate.getMinutes()).padStart(2, '0');\n let seconds = String(ctDate.getSeconds()).padStart(2, '0');\n return shortMonthName(ctDate) + ' ' +\n ctDate.getDate() + ' ' + ctDate.getFullYear() + ' ' +\n ctDate.getHours() + ':' + minutes + ':' + seconds;\n }\n}\n"],"names":["SpbcMalwareScanner","first_start","active","root","settings","states","state","offset","amount","amount_coefficient","total_scanned","scan_percent","percent_completed","paused","button","spinner","progress_overall","progressbar","progressbar_text","timeout","state_timer","constructor","properties","let","key","jQuery","length","removeClass","this","actionControl","start","resume","controller","pause","Math","round","Date","getTime","getNextState","setPercents","children","filter","addClass","show","html","spbcScaner","button_scan_pause","css","display","setTimeout","result","data","opt","console","log","button_scan_resume","end","reload","hide","button_scan_perform","plug","document","location","spbcSendAJAXRequest","action","notJson","callback","params","obj","accordion","header","heightStyle","collapsible","spbcTblBulkActionsListen","spbcTblRowActionsListen","spbcTblPaginationListen","spbcTblSortListen","spbcStartShowHide","spbcScannerReloadScanInfo","method","type","success","successCallback","error","errorOutput","complete","context","spbcSettings","frontendAnalysisAmount","status","setCoefficients","coefficient","indexOf","percents","floor","text","response","responseText","msg","interactAccordion","total","processed_items","logRaw","logFileEntry","stage_data_for_logging","logStageEntry","undefined","cured","Number","showLinkForShuffleSalts","message","processedPercents","processed","title","hasOwnProperty","interactivity_data","update_text","refresh_data","do_refresh","control_tab","spbcReloadAccordion","xhr","tryCount","retryLimit","errorString","fadeOut","errorMsg","stage","spbcModal","open","putError","messageToLog","prepend","items","getSiteUTCShiftedTimeString","path","module","first","textContent","description","remove","after","utcShiftedTs","currentBrowserOffset","getTimezoneOffset","ctDate","timezone_shift","now","shortMonthName","Intl","DateTimeFormat","month","format","minutes","String","getMinutes","padStart","seconds","getSeconds","getDate","getFullYear","getHours"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"spbc-scanner-plugin.min.js","sources":["spbc-scanner-plugin.js"],"sourcesContent":["'use strict';\n\n/**\n * class SpbcMalwareScanner\n */\nclass SpbcMalwareScanner {/* eslint-disable-line no-unused-vars */\n first_start = true;\n\n active = false;\n\n root = '';\n settings = [];\n states = [\n 'get_cms_hashes',\n 'get_modules_hashes',\n 'clean_results',\n 'file_system_analysis',\n 'get_approved_hashes',\n 'get_denied_hashes',\n 'signature_analysis',\n 'heuristic_analysis',\n 'schedule_send_heuristic_suspicious_files',\n 'auto_cure_backup',\n 'auto_cure',\n 'os_cron_analysis',\n 'db_trigger_analysis',\n 'binary_analysis',\n 'outbound_links',\n 'frontend_analysis',\n 'important_files_listing',\n 'send_results',\n ];\n state = null;\n offset = 0;\n amount = 0;\n amount_coefficient = 1;\n total_scanned = 0;\n scan_percent = 0;\n percent_completed = 0;\n\n paused = false;\n\n button = null;\n spinner = null;\n\n progress_overall = null;\n progressbar = null;\n progressbar_text = null;\n\n timeout = 60000;\n\n state_timer = 0;\n\n /**\n * constructor\n * @param {array} properties\n */\n constructor( properties ) {\n if (jQuery('#spbcscan-results-log-module').length) {\n jQuery('.spbc-scan-log-title').removeClass('spbc---hidden');\n }\n\n // Crunch for cure backups\n if ( typeof properties['settings']['auto_cure'] !== 'undefined' ) {\n properties['settings']['scanner__auto_cure_backup'] = '1';\n }\n\n for ( let key in properties ) {\n if ( typeof this[key] !== 'undefined' ) {\n this[key] = properties[key];\n }\n }\n };\n\n /**\n * Function Action Control\n */\n actionControl() {\n if (this.state === null) {\n this.start();\n } else if (this.paused) {\n this.resume();\n this.controller();\n } else {\n this.pause();\n }\n };\n\n /**\n * Function Start\n */\n start() {\n this.active = true;\n this.state_timer = Math.round(new Date().getTime() /1000);\n\n this.state = this.getNextState( null );\n\n this.setPercents( 0 );\n this.scan_percent = 0;\n this.offset = 0;\n this.progress_overall.children('span')\n .removeClass('spbc_bold')\n .filter('.spbc_overall_scan_status_' + this.state)\n .addClass('spbc_bold');\n\n this.progressbar.show(500);\n this.progress_overall.show(500);\n this.button.html(spbcScaner.button_scan_pause);\n this.spinner.css({display: 'inline'});\n\n setTimeout(() => {\n this.controller();\n }, 1000);\n };\n\n /**\n * Function Pause\n * @param {*} result\n * @param {*} data\n * @param {*} opt\n */\n pause( result, data, opt ) {\n console.log('PAUSE');\n this.button.html(spbcScaner.button_scan_resume);\n this.spinner.css({display: 'none'});\n this.paused = true;\n this.active = false;\n };\n\n /**\n * Function Resume\n * @param {*} opt\n */\n resume( opt ) {\n console.log('RESUME');\n this.button.html(spbcScaner.button_scan_pause);\n this.spinner.css({display: 'inline'});\n this.paused = false;\n this.active = true;\n };\n\n /**\n * Function End\n * @param {bool} reload\n */\n end( reload ) {\n this.progressbar.hide(500);\n this.progress_overall.hide(500);\n this.button.html(spbcScaner.button_scan_perform);\n this.spinner.css({display: 'none'});\n this.state = null;\n this.plug = false;\n this.total_scanned = 0;\n this.active = false;\n\n if (reload) {\n document.location = document.location;\n } else {\n spbcSendAJAXRequest(\n {action: 'spbc_scanner_tab__reload_accordion'},\n {\n notJson: true,\n callback: function(result, data, params, obj) {\n jQuery(obj).accordion('destroy')\n .html(result)\n .accordion({\n header: 'h3',\n heightStyle: 'content',\n collapsible: true,\n active: false,\n });\n spbcTblBulkActionsListen();\n spbcTblRowActionsListen();\n spbcTblPaginationListen();\n spbcTblSortListen();\n spbcStartShowHide();\n spbcScannerReloadScanInfo();\n },\n },\n jQuery('#spbc_scan_accordion'),\n );\n }\n };\n\n /**\n * Function Controller\n * @param {obj} result\n */\n controller( result ) {\n console.log(this.state);\n\n // The current stage is over. Switching to the new one\n if ( typeof result !== 'undefined' && result.end ) {\n this.state = this.getNextState( this.state );\n\n // End condition\n if (typeof this.state === 'undefined') {\n this.end();\n return;\n }\n\n // Set percent to 0\n this.setPercents( 0 );\n this.scan_percent = 0;\n this.offset = 0;\n\n // Changing visualizing of the current stage\n this.progress_overall.children('span')\n .removeClass('spbc_bold')\n .filter('.spbc_overall_scan_status_' + this.state)\n .addClass('spbc_bold');\n }\n\n // Break execution if paused\n if ( this.paused === true ) {\n return;\n }\n\n // // AJAX params\n let data = {\n action: 'spbc_scanner_controller_front',\n method: this.state,\n offset: this.offset,\n };\n\n let params = {\n type: 'GET',\n success: this.success,\n callback: this.successCallback,\n error: this.error,\n errorOutput: this.errorOutput,\n complete: null,\n context: this,\n timeout: 120000,\n };\n\n switch (this.state) {\n case 'get_modules_hashes': this.amount = 2; break;\n case 'clear_table': this.amount = 10000; break;\n case 'file_system_analysis': this.amount = 700; break;\n case 'auto_cure': this.amount = 5; break;\n case 'outbound_links': this.amount = 10; break;\n case 'frontend_analysis': this.amount = spbcSettings.frontendAnalysisAmount; break;\n case 'signature_analysis': this.amount = 10; data.status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR'; break;\n case 'heuristic_analysis': this.amount = 4; data.status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR'; break;\n case 'binary_analysis': this.amount = 4; data.status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR'; break;\n case 'schedule_send_heuristic_suspicious_files': this.amount = 1; break;\n }\n\n data.amount = Math.round(this.amount * this.amount_coefficient);\n\n spbcSendAJAXRequest(\n data,\n params,\n jQuery('#spbc_scan_accordion'),\n );\n };\n\n /**\n * Set Coefficients\n * @param {string} state\n */\n setCoefficients( state ) {\n let coefficient = this.amount_coefficient;\n switch (state) {\n case 'file_system_analysis': coefficient *= 1.5; break;\n }\n this.amount_coefficient = coefficient;\n };\n\n /**\n * Get Next State\n * @param {string} state\n * @return {number}\n */\n getNextState( state ) {\n state = state === null ? this.states[0] : this.states[this.states.indexOf( state ) + 1];\n\n if (typeof this.settings['scanner__' + state] !== 'undefined' && +this.settings['scanner__' + state] === 0) {\n state = this.getNextState( state );\n }\n\n return state;\n };\n\n /**\n * Set Percents\n * @param {number} percents\n */\n setPercents( percents ) {\n this.percent_completed = Math.floor( percents * 100 ) / 100;\n this.progressbar.progressbar( 'option', 'value', this.percent_completed );\n this.progressbar_text.text( spbcScaner['progressbar_' + this.state] + ' - ' + this.percent_completed + '%' );\n };\n\n /**\n * Function Success\n * @param {obj} response\n */\n success( response ) {\n if ( !! response.error ) {\n this.error(\n {status: 200, responseText: response.error},\n response.error,\n response.msg,\n );\n } else {\n if ( this.successCallback ) {\n this.successCallback( response, this.data, this.obj );\n }\n }\n };\n\n // Processing response from backend\n /**\n * Success Callback\n * @param {obj} result\n */\n successCallback( result ) {\n console.log( result );\n\n this.interactAccordion(result);\n\n if ( typeof result.total !== 'undefined' ) {\n this.scan_percent = 100 / result.total;\n }\n\n if ( typeof result.processed_items !== 'undefined') {\n if ( this.state === 'heuristic_analysis' && typeof result.total !== 0 ) {\n this.logRaw('

Heuristic Analysis

');\n }\n if ( this.state === 'signature_analysis' && typeof result.total !== 0 ) {\n this.logRaw('

Signature Analysis

');\n }\n\n this.logFileEntry( result.processed_items );\n }\n\n if ( typeof result.stage_data_for_logging !== 'undefined') {\n this.logStageEntry( result.stage_data_for_logging );\n }\n\n // Add link on shuffle salt if cured\n if (result.cured !== undefined && Number(result.cured) > 0) {\n this.showLinkForShuffleSalts(result.message);\n }\n\n if ( result.end !== true && result.end !== 1 ) {\n let processedPercents = this.percent_completed + result.processed * this.scan_percent;\n if (result.stage_data_for_logging.title === 'File System Analysis' && processedPercents > 100) {\n processedPercents = 100;\n }\n this.setPercents(processedPercents);\n this.offset = this.offset + result.processed;\n this.controller( result );\n } else {\n console.log( this.state +\n ' stage took ' +\n ( Math.round(new Date().getTime() /1000) - this.state_timer ) +\n ' seconds to complete' );\n this.state_timer = Math.round(new Date().getTime()/1000);\n this.setPercents( 100 );\n this.scan_percent = 0;\n this.offset = 0;\n setTimeout(() => {\n this.controller( result );\n }, 300);\n }\n };\n\n /**\n * Run interactive refresh for accordion.\n * @param {obj|string[]} result\n */\n interactAccordion(result) {\n // validation control\n if (result.hasOwnProperty('interactivity_data') &&\n result.interactivity_data.hasOwnProperty('update_text') &&\n result.interactivity_data.update_text &&\n result.interactivity_data.hasOwnProperty('refresh_data') &&\n result.interactivity_data.refresh_data.hasOwnProperty('do_refresh') &&\n result.interactivity_data.refresh_data.do_refresh &&\n result.interactivity_data.refresh_data.hasOwnProperty('control_tab') &&\n result.interactivity_data.refresh_data.control_tab\n ) {\n spbcReloadAccordion(\n result.interactivity_data.refresh_data.control_tab,\n result.interactivity_data.update_text,\n );\n }\n }\n\n /**\n * Function Error\n * @param {object} xhr\n * @param {string} status\n * @param {string} error\n */\n error( xhr, status, error ) {\n let errorOutput = this.errorOutput;\n\n console.log( '%c APBCT_AJAX_ERROR', 'color: red;' );\n console.log( status );\n console.log( error );\n console.log( xhr );\n\n if (status == 'error' && (error == '' || error == 'Not found')) {\n if (!this.tryCount) {\n this.tryCount = 0;\n this.retryLimit = 30;\n }\n this.tryCount++;\n console.log('Try #' + this.tryCount);\n this.setCoefficients(this.state);\n if (this.tryCount <= this.retryLimit) {\n this.pause();\n this.resume();\n this.controller();\n return;\n }\n }\n\n if ( xhr.status === 200 ) {\n if ( status === 'parsererror' ) {\n errorOutput( 'Unexpected response from server. See console for details.', this.state );\n console.log( '%c ' + xhr.responseText, 'color: pink;' );\n } else {\n let errorString = status;\n if ( typeof error !== 'undefined' ) {\n errorString += ' Additional info: ' + error;\n }\n errorOutput( errorString, this.state );\n }\n } else if (xhr.status === 500) {\n errorOutput( 'Internal server error.', this.state);\n } else {\n errorOutput('Unexpected response code: ' + xhr.status + '. Error: ' + status, this.state);\n }\n\n if ( this.progressbar ) {\n this.progressbar.fadeOut('slow');\n }\n\n this.end();\n };\n\n /**\n * Error Output\n * @param {string} errorMsg\n * @param {string} stage\n */\n errorOutput( errorMsg, stage ) {\n spbcModal.open().putError( errorMsg + '
Stage: ' + stage);\n };\n\n /**\n * Log Raw\n * @param {htmlString|Element|Text|Array|jQuery} messageToLog\n */\n logRaw(messageToLog) {\n jQuery('.spbc-scan-log-title').removeClass('spbc---hidden');\n jQuery('.spbc_log-wrapper').removeClass('spbc---hidden');\n jQuery('.spbc_log-wrapper .panel-body').prepend( messageToLog );\n };\n\n /**\n * Log File Entry\n * @param {array} items\n */\n logFileEntry(items) {\n for ( let key in items ) {\n if ( key ) {\n this.logRaw(\n '

' +\n this.getSiteUTCShiftedTimeString() + ' - ' +\n items[key].path + ' - ' + items[key].module +\n ': ' + items[key].status + '' +\n '

');\n }\n }\n };\n\n /**\n * Log Stage Entry\n * @param {obj} data\n */\n logStageEntry(data) {\n if (typeof jQuery('.panel-body .spbc_log-line span').first() !== 'undefined' &&\n typeof jQuery('.panel-body .spbc_log-line span').first()[0] !== 'undefined' &&\n jQuery('.panel-body .spbc_log-line span').first()[0].textContent === data.description\n ) {\n return;\n }\n this.logRaw( '

test ' +\n this.getSiteUTCShiftedTimeString() + ' - ' + '' +\n data.title + ' ' + '' + data.description + '

' );\n };\n\n /**\n * Show Link For Shuffle Salts\n * @param {string} message\n */\n showLinkForShuffleSalts(message) {\n jQuery('#spbc_notice_about_shuffle_link').remove();\n jQuery(jQuery('.spbc_tab--active .spbc_wrapper_field p')[1])\n .after(\n '
' +\n '' +\n message +\n '' +\n '
',\n );\n }\n\n /**\n * Get Site UTC Shifted Time String\n * @return {string}\n */\n getSiteUTCShiftedTimeString() {\n let utcShiftedTs = false;\n // gettings current system/browser offset\n let currentBrowserOffset = new Date().getTimezoneOffset();\n currentBrowserOffset = currentBrowserOffset * -1 * 1000 * 60;\n // chek if global ct object is defined\n if (typeof spbcScaner !== 'undefined' &&\n typeof spbcScaner.timezone_shift !== 'undefined' &&\n spbcScaner.timezone_shift !== false) {\n utcShiftedTs = Date.now() - currentBrowserOffset + (spbcScaner.timezone_shift * 1000);\n }\n let ctDate = utcShiftedTs ? new Date(utcShiftedTs) : new Date();\n // construct date string\n let shortMonthName = new Intl.DateTimeFormat('en-US', {month: 'short'}).format;\n let minutes = String(ctDate.getMinutes()).padStart(2, '0');\n let seconds = String(ctDate.getSeconds()).padStart(2, '0');\n return shortMonthName(ctDate) + ' ' +\n ctDate.getDate() + ' ' + ctDate.getFullYear() + ' ' +\n ctDate.getHours() + ':' + minutes + ':' + seconds;\n }\n}\n"],"names":["SpbcMalwareScanner","first_start","active","root","settings","states","state","offset","amount","amount_coefficient","total_scanned","scan_percent","percent_completed","paused","button","spinner","progress_overall","progressbar","progressbar_text","timeout","state_timer","constructor","properties","let","key","jQuery","length","removeClass","this","actionControl","start","resume","controller","pause","Math","round","Date","getTime","getNextState","setPercents","children","filter","addClass","show","html","spbcScaner","button_scan_pause","css","display","setTimeout","result","data","opt","console","log","button_scan_resume","end","reload","hide","button_scan_perform","plug","document","location","spbcSendAJAXRequest","action","notJson","callback","params","obj","accordion","header","heightStyle","collapsible","spbcTblBulkActionsListen","spbcTblRowActionsListen","spbcTblPaginationListen","spbcTblSortListen","spbcStartShowHide","spbcScannerReloadScanInfo","method","type","success","successCallback","error","errorOutput","complete","context","spbcSettings","frontendAnalysisAmount","status","setCoefficients","coefficient","indexOf","percents","floor","text","response","responseText","msg","interactAccordion","total","processed_items","logRaw","logFileEntry","stage_data_for_logging","logStageEntry","undefined","cured","Number","showLinkForShuffleSalts","message","processedPercents","processed","title","hasOwnProperty","interactivity_data","update_text","refresh_data","do_refresh","control_tab","spbcReloadAccordion","xhr","tryCount","retryLimit","errorString","fadeOut","errorMsg","stage","spbcModal","open","putError","messageToLog","prepend","items","getSiteUTCShiftedTimeString","path","module","first","textContent","description","remove","after","utcShiftedTs","currentBrowserOffset","getTimezoneOffset","ctDate","timezone_shift","now","shortMonthName","Intl","DateTimeFormat","month","format","minutes","String","getMinutes","padStart","seconds","getSeconds","getDate","getFullYear","getHours"],"mappings":"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"} \ No newline at end of file diff --git a/js/src/spbc-scanner-plugin.js b/js/src/spbc-scanner-plugin.js index e29332984..a3990bf60 100644 --- a/js/src/spbc-scanner-plugin.js +++ b/js/src/spbc-scanner-plugin.js @@ -24,6 +24,7 @@ class SpbcMalwareScanner {/* eslint-disable-line no-unused-vars */ 'auto_cure', 'os_cron_analysis', 'db_trigger_analysis', + 'binary_analysis', 'outbound_links', 'frontend_analysis', 'important_files_listing', @@ -242,6 +243,7 @@ class SpbcMalwareScanner {/* eslint-disable-line no-unused-vars */ case 'frontend_analysis': this.amount = spbcSettings.frontendAnalysisAmount; break; case 'signature_analysis': this.amount = 10; data.status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR'; break; case 'heuristic_analysis': this.amount = 4; data.status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR'; break; + case 'binary_analysis': this.amount = 4; data.status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR'; break; case 'schedule_send_heuristic_suspicious_files': this.amount = 1; break; } diff --git a/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php b/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php new file mode 100644 index 000000000..1ea3fd5f3 --- /dev/null +++ b/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php @@ -0,0 +1,123 @@ + true, 'end' => 1); + + try { + $this->getBinaryFiles(); + $this->analyzeBinaryFiles(); + } catch (\Exception $error) { + $output['error'] = $error->getMessage(); + wp_send_json($output); + } + + wp_send_json($output); + } + + private function getBinaryFiles() + { + global $wpdb; + + $binary_files = $wpdb->get_results( + "SELECT * FROM " . SPBC_TBL_SCAN_FILES . " WHERE source = 'BINARY'" + ); + + return $binary_files; + } + + private function analyzeBinaryFiles() + { + $binary_files = $this->getBinaryFiles(); + + foreach ($binary_files as $binary_file) { + $path = realpath(ABSPATH . $binary_file->path); + if (!file_exists($path)) { + continue; + } + + if (!is_readable($path)) { + continue; + } + + $isSuspicious = $this->analyzeBinaryFile($path); + if ($isSuspicious) { + $this->markAsSuspicious($binary_file->path); + } + } + } + + private function analyzeBinaryFile($binary_file_path) + { + // Read first bytes of the file (we need at least 4 bytes for all checks) + $handle = fopen($binary_file_path, 'rb'); + if (!$handle) { + return false; + } + + // Read first 4 bytes + $first_bytes = fread($handle, 4); + fclose($handle); + + if (strlen($first_bytes) < 4) { + return false; + } + + // Method 1: Check using ord() to get byte values + $byte1 = ord($first_bytes[0]); + $byte2 = ord($first_bytes[1]); + $byte3 = ord($first_bytes[2]); + $byte4 = ord($first_bytes[3]); + + // Check for ELF (0x7F followed by "ELF" = 0x45 0x4C 0x46) + if ($byte1 === 0x7F && $byte2 === 0x45 && $byte3 === 0x4C && $byte4 === 0x46) { + return true; // ELF file detected + } + + // Check for PE/MZ (Windows executable: 0x4D 0x5A = "MZ") + if ($byte1 === 0x4D && $byte2 === 0x5A) { + return true; // PE file detected + } + + // Check for MACHO (macOS/iOS executables) + // 32-bit big-endian: 0xFE 0xED 0xFA 0xCE + // 32-bit little-endian: 0xCE 0xFA 0xED 0xFE + // 64-bit big-endian: 0xFE 0xED 0xFA 0xCF + // 64-bit little-endian: 0xCF 0xFA 0xED 0xFE + $macho_signatures = array( + array(0xFE, 0xED, 0xFA, 0xCE), // 32-bit big-endian + array(0xCE, 0xFA, 0xED, 0xFE), // 32-bit little-endian + array(0xFE, 0xED, 0xFA, 0xCF), // 64-bit big-endian + array(0xCF, 0xFA, 0xED, 0xFE), // 64-bit little-endian + ); + + foreach ($macho_signatures as $signature) { + if ($byte1 === $signature[0] && $byte2 === $signature[1] && + $byte3 === $signature[2] && $byte4 === $signature[3]) { + return true; // MACHO file detected + } + } + + return false; + } + + private function markAsSuspicious($binary_file_path) + { + global $wpdb; + + $wpdb->query( + $wpdb->prepare( + "UPDATE " . SPBC_TBL_SCAN_FILES . " SET status = 'INFECTED', severity = 'SUSPICIOUS' WHERE path = %s", + $binary_file_path + ) + ); + } +} diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php index 0173897b1..2dfce49e6 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php @@ -31,6 +31,7 @@ use CleantalkSP\SpbctWP\Scanner\ScanningStagesModule\Stages\OutboundLinks; use CleantalkSP\SpbctWP\Scanner\ScanningStagesModule\Stages\ScheduleSendHeuristicSuspiciousFiles; use CleantalkSP\SpbctWP\Scanner\ScanningStagesModule\Stages\SignatureAnalysis; +use CleantalkSP\SpbctWP\Scanner\BinaryCheckModule\BinaryCheckModule; use CleantalkSP\SpbctWP\Scanner\Stages\CureStage; use CleantalkSP\SpbctWP\Scanner\Stages\SendResultsStage; use CleantalkSP\SpbctWP\Scanner\Stages\SignatureAnalysis\SignatureAnalysisFacade; @@ -55,6 +56,7 @@ class ScannerQueue 'get_approved_hashes', 'signature_analysis', 'heuristic_analysis', + 'binary_analysis', 'schedule_send_heuristic_suspicious_files', 'auto_cure_backup', 'auto_cure', @@ -952,7 +954,7 @@ public function file_system_analysis($offset = null, $amount = null, $path_to_sc //should be offset $detected_at = current_time('timestamp'); $sql_hat = 'INSERT INTO ' . SPBC_TBL_SCAN_FILES - . ' (`path`, `size`, `perms`, `mtime`, `fast_hash`, `full_hash`, `detected_at`, `checked_heuristic`) VALUES '; + . ' (`path`, `size`, `perms`, `mtime`, `fast_hash`, `full_hash`, `detected_at`, `checked_heuristic`, `source`) VALUES '; foreach ( $scanner->output_files as $_key => $file ) { // skip restored files as is @@ -973,6 +975,9 @@ public function file_system_analysis($offset = null, $amount = null, $path_to_sc $file['checked_heuristic'] = $ext === 'js' ? 1 : 0; //JS files end + // if extension is not set, set 'source' to 'binary' + $file['source'] = ! $ext ? 'BINARY' : ''; + if ( ! spbc_check_ascii($file['path']) ) { $sql_query__values_non_ascii[] = '(\'' . implode('\',\'', $file) . '\')'; } else { @@ -1855,6 +1860,12 @@ public function schedule_send_heuristic_suspicious_files() // phpcs:ignore PSR1. ); } + public function binary_analysis() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps + { + $binary_check_module = new BinaryCheckModule(); + return $binary_check_module->run(); + } + public function auto_cure_backup() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps { return spbc_backup__files_with_signatures(true); diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScanningStagesModule/Stages/BinaryAnalysis.php b/lib/CleantalkSP/SpbctWP/Scanner/ScanningStagesModule/Stages/BinaryAnalysis.php new file mode 100644 index 000000000..67efff864 --- /dev/null +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScanningStagesModule/Stages/BinaryAnalysis.php @@ -0,0 +1,59 @@ +total_count_files_for_analysis + . '; ' + . __('Files to check ', 'security-malware-firewall') + . $this->count_files_to_check + . '; ' + . __('Scanned files ', 'security-malware-firewall') + . $this->scanned_count_files + . '; ' + . __('Statuses ', 'security-malware-firewall') + . $this->getStatusesWithTitle(); + } + + public function getStatusesWithTitle() + { + $description_array = array(); + foreach ($this->statuses as $status => $count) { + $description_array[] = $status . ': ' . $count; + } + return $description_array ? implode('; ', $description_array) . '.' : ''; + } + + public function getName() + { + return __CLASS__; + } + + public function getData() + { + return array( + 'total_count_files_for_analysis' => $this->total_count_files_for_analysis, + 'count_files_to_check' => $this->count_files_to_check, + 'scanned_count_files' => $this->scanned_count_files, + 'statuses' => $this->statuses + ); + } +} diff --git a/lib/CleantalkSP/SpbctWP/Scanner/Surface.php b/lib/CleantalkSP/SpbctWP/Scanner/Surface.php index 441e59b44..acae2429d 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/Surface.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/Surface.php @@ -531,6 +531,11 @@ public function countFilesInDir($main_path) $filename = $fileInfo->getFileName(); $currentFileExtension = pathinfo($filename, PATHINFO_EXTENSION); + if ($currentFileExtension === '') { + $this->output_files_count++; + continue; + } + // Extensions filter if ( $this->ext_except || $this->ext ) { if ( @@ -623,6 +628,15 @@ public function getFileStructure($main_path, $iterator_result, $is_root_dir) $path = (string) $path; if ( is_file($path) && !is_link($path) ) { + + $currentFileExtension = pathinfo($path, PATHINFO_EXTENSION); + + if ($currentFileExtension === '') { + $this->output_counter_files++; + $this->output_files[]['path'] = $path; + continue; + } + // Extensions filter if ( $this->ext_except || $this->ext ) { $currentFileExtension = pathinfo($path, PATHINFO_EXTENSION); From 8f7dbf63302ce7aaef1b67b535197789b2b1ce59 Mon Sep 17 00:00:00 2001 From: svfcode Date: Tue, 20 Jan 2026 07:36:54 +0300 Subject: [PATCH 2/5] fix phpcs --- .../SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php | 2 +- lib/CleantalkSP/SpbctWP/Scanner/Surface.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php b/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php index 1ea3fd5f3..5ba882652 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php @@ -9,7 +9,7 @@ class BinaryCheckModule * @return void */ public function run() - { + { $output = array('success' => true, 'end' => 1); try { diff --git a/lib/CleantalkSP/SpbctWP/Scanner/Surface.php b/lib/CleantalkSP/SpbctWP/Scanner/Surface.php index acae2429d..2c2bc7546 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/Surface.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/Surface.php @@ -628,7 +628,6 @@ public function getFileStructure($main_path, $iterator_result, $is_root_dir) $path = (string) $path; if ( is_file($path) && !is_link($path) ) { - $currentFileExtension = pathinfo($path, PATHINFO_EXTENSION); if ($currentFileExtension === '') { From b3bee641825f15f920bf7a8375b4d6b7fd6b9609 Mon Sep 17 00:00:00 2001 From: AntonV1211 Date: Mon, 9 Feb 2026 19:25:32 +0700 Subject: [PATCH 3/5] New. BinaryCheck. Edits for the new module in the scanner --- inc/spbc-backups.php | 2 +- inc/spbc-settings.php | 23 +++++- .../BinaryCheckModule/BinaryCheckModule.php | 82 +++++++++++++++---- .../ScannerActions/CloudAnalysisActions.php | 3 +- .../ScannerActions/FileSystemActions.php | 5 ++ .../SpbctWP/Scanner/ScannerQueue.php | 54 ++++++++++-- .../Services/SendFileToCloudService.php | 5 ++ .../SpbctWP/Scanner/Stages/CureStage.php | 4 +- lib/CleantalkSP/SpbctWP/SpbcEnqueue.php | 1 + lib/CleantalkSP/SpbctWP/State.php | 1 + 10 files changed, 149 insertions(+), 31 deletions(-) diff --git a/inc/spbc-backups.php b/inc/spbc-backups.php index 78a4e84ca..5fb6474ba 100644 --- a/inc/spbc-backups.php +++ b/inc/spbc-backups.php @@ -174,7 +174,7 @@ function spbc_backup__files_with_signatures_handler() $output = array('success' => true); - $files_to_backup = $wpdb->get_results('SELECT path, weak_spots, checked_heuristic, checked_signatures, status, severity FROM ' . SPBC_TBL_SCAN_FILES . ' WHERE weak_spots LIKE "%\"SIGNATURES\":%";', ARRAY_A); + $files_to_backup = $wpdb->get_results('SELECT path, weak_spots, checked_heuristic, checked_signatures, status, severity FROM ' . SPBC_TBL_SCAN_FILES . ' WHERE weak_spots LIKE "%\"SIGNATURES\":%" AND source != \'BINARY\';', ARRAY_A); if (!is_array($files_to_backup) || !count($files_to_backup)) { $output = array('success' => true); diff --git a/inc/spbc-settings.php b/inc/spbc-settings.php index ec51d0290..548fcb10d 100644 --- a/inc/spbc-settings.php +++ b/inc/spbc-settings.php @@ -685,6 +685,12 @@ function spbc_settings__register() 'description' => __('Will search for known malicious signatures in files. Unknown files will be shown in the results only if both options heuristic analysis and signature analysis are enabled.', 'security-malware-firewall'), 'long_description' => true, ), + 'scanner__binary_analysis' => array( + 'type' => 'field', + 'title' => __('Binary analysis', 'security-malware-firewall'), + 'description' => __('Search for suspicious binary patterns in files.', 'security-malware-firewall'), + 'long_description' => false, + ), 'scanner__os_cron_analysis' => array( 'type' => 'field', 'title' => Scanner\OSCron\View\OSCronLocale::getInstance()->settings__option_title, @@ -2383,6 +2389,15 @@ function spbc_field_scanner__prepare_data__files(&$table) unset($row->actions['delete']); } + // Binary files: restrict modification/analysis actions, allow only approve/delete/quarantine + if ( isset($row->source) && $row->source === 'BINARY' ) { + unset($row->actions['send']); + unset($row->actions['view']); + unset($row->actions['view_bad']); + unset($row->actions['replace']); + unset($row->actions['compare']); + } + $table->items[] = array( 'cb' => $row->fast_hash, 'uid' => $row->fast_hash, @@ -3014,11 +3029,11 @@ function spbc_field_scanner() . '
-> '; } - // if ($spbc->settings['scanner__binary_analysis']) { + if ($spbc->settings['scanner__binary_analysis']) { echo '' - . __('Binary analysis', 'security-malware-firewall') - . ' -> '; - // } + . __('Binary analysis', 'security-malware-firewall') + . ' -> '; + } if ($spbc->settings['scanner__schedule_send_heuristic_suspicious_files']) { echo '' diff --git a/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php b/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php index 5ba882652..6aa4eb9de 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php @@ -4,57 +4,103 @@ class BinaryCheckModule { + /** + * @var int Total count of binary files for analysis + */ + private $total_count = 0; + + /** + * @var int Count of scanned files + */ + private $scanned_count = 0; + + /** + * @var array Statuses of scanned files + */ + private $statuses = array(); + /** * Runs the BinaryCheckModule to find malicious binary files. - * @return void + * @return array Analysis results */ public function run() { - $output = array('success' => true, 'end' => 1); - - try { - $this->getBinaryFiles(); - $this->analyzeBinaryFiles(); - } catch (\Exception $error) { - $output['error'] = $error->getMessage(); - wp_send_json($output); - } + $this->total_count = 0; + $this->scanned_count = 0; + $this->statuses = array(); - wp_send_json($output); + $binary_files = $this->getBinaryFiles(); + $this->total_count = count($binary_files); + $this->analyzeBinaryFiles($binary_files); + + return array( + 'success' => true, + 'end' => 1, + 'total_count' => $this->total_count, + 'scanned_count' => $this->scanned_count, + 'statuses' => $this->statuses, + ); } - private function getBinaryFiles() + /** + * @return array + */ + public function getBinaryFiles() { global $wpdb; $binary_files = $wpdb->get_results( - "SELECT * FROM " . SPBC_TBL_SCAN_FILES . " WHERE source = 'BINARY'" + $wpdb->prepare( + "SELECT * FROM " . SPBC_TBL_SCAN_FILES . " WHERE source = %s", + 'BINARY' + ) ); - return $binary_files; + return $binary_files ?: array(); } - private function analyzeBinaryFiles() + /** + * @param array $binary_files + * @return void + */ + private function analyzeBinaryFiles($binary_files) { - $binary_files = $this->getBinaryFiles(); - foreach ($binary_files as $binary_file) { $path = realpath(ABSPATH . $binary_file->path); - if (!file_exists($path)) { + if (!$path || !file_exists($path)) { + $this->incrementStatus('SKIPPED_NOT_FOUND'); continue; } if (!is_readable($path)) { + $this->incrementStatus('SKIPPED_NOT_READABLE'); continue; } $isSuspicious = $this->analyzeBinaryFile($path); + $this->scanned_count++; + if ($isSuspicious) { $this->markAsSuspicious($binary_file->path); + $this->incrementStatus('SUSPICIOUS'); + } else { + $this->incrementStatus('OK'); } } } + /** + * @param string $status + * @return void + */ + private function incrementStatus($status) + { + if (!isset($this->statuses[$status])) { + $this->statuses[$status] = 0; + } + $this->statuses[$status]++; + } + private function analyzeBinaryFile($binary_file_path) { // Read first bytes of the file (we need at least 4 bytes for all checks) diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScannerActions/CloudAnalysisActions.php b/lib/CleantalkSP/SpbctWP/Scanner/ScannerActions/CloudAnalysisActions.php index 7c845fac5..bff4f25e0 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScannerActions/CloudAnalysisActions.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScannerActions/CloudAnalysisActions.php @@ -146,7 +146,8 @@ public static function bulkSendFileForAnalysis($fast_hashes_list = array()) $sql_result = $wpdb->get_results( 'SELECT fast_hash FROM ' . SPBC_TBL_SCAN_FILES . ' WHERE last_sent IS NULL - AND status NOT IN ("APPROVED_BY_USER","APPROVED_BY_CT","APPROVED_BY_CLOUD","DENIED_BY_CT")', + AND status NOT IN ("APPROVED_BY_USER","APPROVED_BY_CT","APPROVED_BY_CLOUD","DENIED_BY_CT") + AND source != \'BINARY\'', ARRAY_A ); diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScannerActions/FileSystemActions.php b/lib/CleantalkSP/SpbctWP/Scanner/ScannerActions/FileSystemActions.php index 115ecc4fd..651d0889f 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScannerActions/FileSystemActions.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScannerActions/FileSystemActions.php @@ -331,6 +331,11 @@ public static function viewFile($file_id) return array('error' => 'FILE_NOT_FOUND'); } + // Binary files cannot be viewed as text + if (isset($file_info['source']) && $file_info['source'] === 'BINARY') { + return array('error' => esc_html__('Binary files cannot be viewed. These files contain executable code that is not human-readable.', 'security-malware-firewall')); + } + $file_path = $file_info['status'] == 'QUARANTINED' ? $file_info['q_path'] : $root_path . $file_info['path']; if (!file_exists($file_path)) { diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php index 2dfce49e6..a409df17b 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php @@ -31,6 +31,7 @@ use CleantalkSP\SpbctWP\Scanner\ScanningStagesModule\Stages\OutboundLinks; use CleantalkSP\SpbctWP\Scanner\ScanningStagesModule\Stages\ScheduleSendHeuristicSuspiciousFiles; use CleantalkSP\SpbctWP\Scanner\ScanningStagesModule\Stages\SignatureAnalysis; +use CleantalkSP\SpbctWP\Scanner\ScanningStagesModule\Stages\BinaryAnalysis; use CleantalkSP\SpbctWP\Scanner\BinaryCheckModule\BinaryCheckModule; use CleantalkSP\SpbctWP\Scanner\Stages\CureStage; use CleantalkSP\SpbctWP\Scanner\Stages\SendResultsStage; @@ -861,7 +862,7 @@ public function countFilesByStatusAndChecked($status = '', $caller = '') $query = 'SELECT COUNT(fast_hash) AS cnt' . ' FROM ' . SPBC_TBL_SCAN_FILES - . ' WHERE ' . $caller . " = '0' AND status IN (" . $status . ');';// No need to validate or sanitize, already did + . ' WHERE ' . $caller . " = '0' AND status IN (" . $status . ") AND source != 'BINARY'";// No need to validate or sanitize, already did $result = $this->db->fetch($query); return $result !== null @@ -1430,7 +1431,7 @@ public function signature_analysis($status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR $files = $this->db->fetchAll( 'SELECT path, source_type, source, version, status, checked_heuristic, checked_signatures, fast_hash, real_full_hash, full_hash, weak_spots, difference, severity, size, error_msg' . ' FROM ' . SPBC_TBL_SCAN_FILES - . " WHERE checked_signatures = 0 AND status IN ($status)" + . " WHERE checked_signatures = 0 AND status IN ($status) AND source != 'BINARY'" . " LIMIT 1000" ); @@ -1617,7 +1618,7 @@ public function heuristic_analysis($status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR $files = $this->db->fetchAll( 'SELECT path, source_type, source, version, status, checked_heuristic, checked_signatures, fast_hash, real_full_hash, full_hash, weak_spots, difference, severity, size, error_msg' . ' FROM ' . SPBC_TBL_SCAN_FILES - . " WHERE checked_heuristic = 0 AND status IN ($status)" + . " WHERE checked_heuristic = 0 AND status IN ($status) AND source != 'BINARY'" . " LIMIT 1000" ); @@ -1820,7 +1821,8 @@ public function schedule_send_heuristic_suspicious_files() // phpcs:ignore PSR1. . ' AND checked_heuristic = 1 ' . ' AND weak_spots NOT LIKE "%SIGNATURES%" ' . ' AND status NOT IN ("APPROVED_BY_USER", "APPROVED_BY_CT", "APPROVED_BY_CLOUD")' - . ' AND (pscan_pending_queue IS NULL OR pscan_pending_queue = 0); ' + . ' AND (pscan_pending_queue IS NULL OR pscan_pending_queue = 0) ' + . ' AND source != \'BINARY\'; ' ); $count = (int)$result_db; @@ -1862,8 +1864,50 @@ public function schedule_send_heuristic_suspicious_files() // phpcs:ignore PSR1. public function binary_analysis() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps { + $output = array(); + + // Initialize scanning stages storage + $scanning_stages_storage = new ScanningStagesStorage(); + $scanning_stages_storage->converter->loadCollection(); + $stage_data_obj = $scanning_stages_storage->getStage(BinaryAnalysis::class); + + // Run binary check module $binary_check_module = new BinaryCheckModule(); - return $binary_check_module->run(); + $result = $binary_check_module->run(); + + // Update stage data + $stage_data_obj->set('total_count_files_for_analysis', $result['total_count']); + $stage_data_obj->set('count_files_to_check', $result['total_count']); + $stage_data_obj->set('scanned_count_files', $result['scanned_count']); + $stage_data_obj->set('statuses', $result['statuses']); + + // Save stage data to DB + $scanning_stages_storage->saveToDb(); + + // Adding to log + ScanningLogFacade::writeToLog( + '' . $stage_data_obj::getTitle() . ' ' . $stage_data_obj->getDescription() + ); + + // Prepare output + $output['success'] = $result['success']; + $output['end'] = $result['end']; + $output['processed'] = $result['scanned_count']; + $output['total'] = $result['total_count']; + $output['stage_data_for_logging'] = array( + 'title' => $stage_data_obj::getTitle(), + 'description' => $stage_data_obj->getDescription() + ); + + // Accordion interactivity + $suspicious_count = isset($result['statuses']['SUSPICIOUS']) ? $result['statuses']['SUSPICIOUS'] : 0; + $refresh_data = array( + 'do_refresh' => !empty($suspicious_count), + 'control_tab' => 'files', + ); + $output['interactivity_data'] = ScannerInteractivityData::prepare(__FUNCTION__, $refresh_data); + + return $output; } public function auto_cure_backup() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps diff --git a/lib/CleantalkSP/SpbctWP/Scanner/Services/SendFileToCloudService.php b/lib/CleantalkSP/SpbctWP/Scanner/Services/SendFileToCloudService.php index c431d207d..e738e1407 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/Services/SendFileToCloudService.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/Services/SendFileToCloudService.php @@ -31,6 +31,11 @@ public static function sendFile($file_id, $do_rescan = true) return array('error' => 'FILE_NOT_FOUND'); } + // Binary files cannot be sent for analysis + if (isset($file_info['source']) && $file_info['source'] === 'BINARY') { + return array('error' => __('Binary files cannot be sent for analysis.', 'security-malware-firewall')); + } + $root_path = spbc_get_root_path(); // if file not exists, remove it from the database diff --git a/lib/CleantalkSP/SpbctWP/Scanner/Stages/CureStage.php b/lib/CleantalkSP/SpbctWP/Scanner/Stages/CureStage.php index a86b5e545..8af2c5b61 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/Stages/CureStage.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/Stages/CureStage.php @@ -103,11 +103,11 @@ public function runStage($offset, $amount) */ public function getFilesToCure($limit = null) { - // get files with signatures + // get files with signatures (excluding binary files) $files_with_signatures = ' SELECT fast_hash, full_hash, mtime FROM ' . SPBC_TBL_SCAN_FILES . ' - WHERE weak_spots LIKE "%SIGNATURES%" + WHERE weak_spots LIKE "%SIGNATURES%" AND source != \'BINARY\' '; $files_with_signatures = $this->db->fetchAll($files_with_signatures, OBJECT_K); diff --git a/lib/CleantalkSP/SpbctWP/SpbcEnqueue.php b/lib/CleantalkSP/SpbctWP/SpbcEnqueue.php index eecf642ce..af1d87d55 100644 --- a/lib/CleantalkSP/SpbctWP/SpbcEnqueue.php +++ b/lib/CleantalkSP/SpbctWP/SpbcEnqueue.php @@ -272,6 +272,7 @@ function ($key) { 'check_links' => $spbc->settings['scanner__outbound_links'] ? 1 : 0, 'check_heuristic' => $spbc->settings['scanner__heuristic_analysis'] ? 1 : 0, 'check_signature' => $spbc->settings['scanner__signature_analysis'] ? 1 : 0, + 'check_binary' => $spbc->settings['scanner__binary_analysis'] ? 1 : 0, 'auto_cure' => $spbc->settings['scanner__auto_cure'] ? 1 : 0, 'check_frontend' => $spbc->settings['scanner__frontend_analysis'] ? 1 : 0, 'check_listing' => $spbc->settings['scanner__important_files_listing'] ? 1 : 0, diff --git a/lib/CleantalkSP/SpbctWP/State.php b/lib/CleantalkSP/SpbctWP/State.php index dae3c7532..2a06db0a9 100644 --- a/lib/CleantalkSP/SpbctWP/State.php +++ b/lib/CleantalkSP/SpbctWP/State.php @@ -74,6 +74,7 @@ class State extends \CleantalkSP\Common\State 'scanner__outbound_links_mirrors' => '', 'scanner__important_files_listing' => 1, 'scanner__heuristic_analysis' => 1, + 'scanner__binary_analysis' => 0, 'scanner__schedule_send_heuristic_suspicious_files' => 2, //0 - OFF, 1 - ON, 2 - AUTO 'scanner__signature_analysis' => 1, 'scanner__auto_cure' => 1, From 0a97983c4d2abec09ffb8f64629cb7da89582978 Mon Sep 17 00:00:00 2001 From: datorik Date: Wed, 17 Jun 2026 09:27:32 +0300 Subject: [PATCH 4/5] Code. BinaryCheck. Code review --- inc/spbc-settings.php | 6 +++++- lib/CleantalkSP/SpbctWP/State.php | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/inc/spbc-settings.php b/inc/spbc-settings.php index ec29915fe..b7e5fc97c 100644 --- a/inc/spbc-settings.php +++ b/inc/spbc-settings.php @@ -556,7 +556,7 @@ function spbc_settings__register() 'type' => 'field', 'title' => __('Binary analysis', 'security-malware-firewall'), 'description' => __('Search for suspicious binary patterns in files.', 'security-malware-firewall'), - 'long_description' => false, + 'long_description' => true, ), 'scanner__os_cron_analysis' => array( 'type' => 'field', @@ -4902,6 +4902,10 @@ function spbc_settings__get_description() 'title' => __('Signature analysis', 'security-malware-firewall'), 'desc' => __('Code signatures — it\'s a code sequence a malicious program consists of. Signatures are being added to the database after analysis of the infected files. Search for such malicious code sequences is performed in scanning by signatures. If any part of code matches a virus code from the database, such files would be marked as critical.', 'security-malware-firewall') ), + 'scanner__binary_analysis' => array( + 'title' => __('Binary analysis', 'security-malware-firewall'), + 'desc' => __('Search for suspicious binary patterns in files.', 'security-malware-firewall') + ), 'scanner__auto_cure' => array( 'title' => __('Cure malware', 'security-malware-firewall'), 'desc' => __('It cures infected files automatically if the scanner knows cure methods for these specific cases. If the option is disabled then when the scanning process ends you will be presented with several actions you can do to the found files: Cure. Malicious code will be removed from the file. Replace. The file will be replaced with the original file. Delete. The file will be put in quarantine. Do nothing. Before any action is chosen, backups of the files will be created and if the cure is unsuccessful it\'s possible to restore each file.', 'security-malware-firewall') diff --git a/lib/CleantalkSP/SpbctWP/State.php b/lib/CleantalkSP/SpbctWP/State.php index 69c8ea816..a92167320 100644 --- a/lib/CleantalkSP/SpbctWP/State.php +++ b/lib/CleantalkSP/SpbctWP/State.php @@ -74,7 +74,7 @@ class State extends \CleantalkSP\Common\State 'scanner__outbound_links_mirrors' => '', 'scanner__important_files_listing' => 1, 'scanner__heuristic_analysis' => 1, - 'scanner__binary_analysis' => 0, + 'scanner__binary_analysis' => 1, 'scanner__schedule_send_heuristic_suspicious_files' => 2, //0 - OFF, 1 - ON, 2 - AUTO 'scanner__signature_analysis' => 1, 'scanner__auto_cure' => 1, From 4af5867a95d33aa353586a8f2605285f5f6dc3e0 Mon Sep 17 00:00:00 2001 From: datorik Date: Wed, 17 Jun 2026 19:30:21 +0300 Subject: [PATCH 5/5] Code. BinaryCheck. Code review --- inc/spbc-settings.php | 3 +- .../BinaryCheckModule/BinaryCheckModule.php | 12 +-- .../ScannerActions/FileSystemActions.php | 93 ++++++++++++++++++- .../SpbctWP/Scanner/ScannerQueue.php | 6 +- 4 files changed, 98 insertions(+), 16 deletions(-) diff --git a/inc/spbc-settings.php b/inc/spbc-settings.php index b7e5fc97c..31a6f7700 100644 --- a/inc/spbc-settings.php +++ b/inc/spbc-settings.php @@ -2330,10 +2330,9 @@ function spbc_field_scanner__prepare_data__files(&$table) unset($row->actions['delete']); } - // Binary files: restrict modification/analysis actions, allow only approve/delete/quarantine + // Binary files: leave only view/delete/quarantine actions if ( isset($row->source) && $row->source === 'BINARY' ) { unset($row->actions['send']); - unset($row->actions['view']); unset($row->actions['view_bad']); unset($row->actions['replace']); unset($row->actions['compare']); diff --git a/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php b/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php index 6aa4eb9de..aa82c0fc2 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/BinaryCheckModule/BinaryCheckModule.php @@ -77,12 +77,12 @@ private function analyzeBinaryFiles($binary_files) continue; } - $isSuspicious = $this->analyzeBinaryFile($path); + $isCritical = $this->analyzeBinaryFile($path); $this->scanned_count++; - if ($isSuspicious) { - $this->markAsSuspicious($binary_file->path); - $this->incrementStatus('SUSPICIOUS'); + if ($isCritical) { + $this->markAsCritical($binary_file->path); + $this->incrementStatus('CRITICAL'); } else { $this->incrementStatus('OK'); } @@ -155,13 +155,13 @@ private function analyzeBinaryFile($binary_file_path) return false; } - private function markAsSuspicious($binary_file_path) + private function markAsCritical($binary_file_path) { global $wpdb; $wpdb->query( $wpdb->prepare( - "UPDATE " . SPBC_TBL_SCAN_FILES . " SET status = 'INFECTED', severity = 'SUSPICIOUS' WHERE path = %s", + "UPDATE " . SPBC_TBL_SCAN_FILES . " SET status = 'INFECTED', severity = 'CRITICAL' WHERE path = %s", $binary_file_path ) ); diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScannerActions/FileSystemActions.php b/lib/CleantalkSP/SpbctWP/Scanner/ScannerActions/FileSystemActions.php index 3808ed147..9d4d30d03 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScannerActions/FileSystemActions.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScannerActions/FileSystemActions.php @@ -331,11 +331,6 @@ public static function viewFile($file_id) return array('error' => 'FILE_NOT_FOUND'); } - // Binary files cannot be viewed as text - if (isset($file_info['source']) && $file_info['source'] === 'BINARY') { - return array('error' => esc_html__('Binary files cannot be viewed. These files contain executable code that is not human-readable.', 'security-malware-firewall')); - } - $file_path = $file_info['status'] == 'QUARANTINED' ? $file_info['q_path'] : $root_path . $file_info['path']; if (!file_exists($file_path)) { @@ -346,6 +341,23 @@ public static function viewFile($file_id) return array('error' => 'FILE_NOT_READABLE'); } + // Binary files: render as hex dump (first chunk only) + if (isset($file_info['source']) && $file_info['source'] === 'BINARY') { + $file_text = self::renderBinaryHexDump($file_path); + if (empty($file_text)) { + return array('error' => 'FILE_EMPTY'); + } + + return array( + 'success' => true, + 'file' => $file_text, + 'file_path' => $file_path, + 'difference' => $file_info['difference'], + 'weak_spots' => $file_info['weak_spots'], + 'exec_time' => round(microtime(true) - $time_start), + ); + } + $file = file($file_path); if (!$file) { return array('error' => 'FILE_EMPTY'); @@ -370,4 +382,75 @@ public static function viewFile($file_id) 'exec_time' => round(microtime(true) - $time_start), ); } + + /** + * Render the first bytes of a binary file as a classic hex dump. + * Each output line: offset | 16 hex bytes | ASCII gutter. + * + * @param string $file_path + * @return array + */ + private static function renderBinaryHexDump($file_path) + { + $max_bytes = 4096; + $bytes_per_line = 16; + + $handle = @fopen($file_path, 'rb'); + if (!$handle) { + return array(); + } + + $data = @fread($handle, $max_bytes); + $file_size = @filesize($file_path); + @fclose($handle); + + if ($data === false || $data === '') { + return array(); + } + + $len = strlen($data); + $rows = array(); + $line_no = 1; + + for ($offset = 0; $offset < $len; $offset += $bytes_per_line) { + $chunk = substr($data, $offset, $bytes_per_line); + $hex_parts = array(); + $ascii = ''; + $chunk_len = strlen($chunk); + for ($i = 0; $i < $bytes_per_line; $i++) { + if ($i < $chunk_len) { + $byte = ord($chunk[$i]); + $hex_parts[] = sprintf('%02X', $byte); + $ascii .= ($byte >= 0x20 && $byte <= 0x7E) ? chr($byte) : '.'; + } else { + $hex_parts[] = ' '; + $ascii .= ' '; + } + } + $hex_block = implode(' ', array_slice($hex_parts, 0, 8)) + . ' ' + . implode(' ', array_slice($hex_parts, 8, 8)); + + $rows[$line_no] = sprintf( + '%08X %s |%s|', + $offset, + $hex_block, + htmlspecialchars($ascii) + ); + $line_no++; + } + + if (is_int($file_size) && $file_size > $len) { + $rows[$line_no] = sprintf( + '... %s', + sprintf( + esc_html__('truncated, showing first %1$d of %2$d bytes', 'security-malware-firewall'), + $len, + $file_size + ) + ); + } + + return $rows; + } } diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php index 52c039603..bff2a3dd3 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php @@ -1928,10 +1928,10 @@ public function binary_analysis() // phpcs:ignore PSR1.Methods.CamelCapsMethodNa ); // Accordion interactivity - $suspicious_count = isset($result['statuses']['SUSPICIOUS']) ? $result['statuses']['SUSPICIOUS'] : 0; + $critical_count = isset($result['statuses']['CRITICAL']) ? $result['statuses']['CRITICAL'] : 0; $refresh_data = array( - 'do_refresh' => !empty($suspicious_count), - 'control_tab' => 'files', + 'do_refresh' => !empty($critical_count), + 'control_tab' => 'critical', ); $output['interactivity_data'] = ScannerInteractivityData::prepare(__FUNCTION__, $refresh_data);