Post

Remcos RAT Analysis

Remcos RAT Analysis

Overview

Remcos (acronym of Remote Control & Surveillance Software) is a commercial Remote Access Tool to remotely control computers. It is advertised as legitimate software which can be used for surveillance and penetration testing purposes, but has been used in numerous hacking campaigns. Remcos, once installed, opens a backdoor on the computer, granting full access to the remote user. It is developed by the cybersecurity company BreakingSecurity.

SAMPLE - 3def24b7506a546ad97fa0447777e57d78aaf37d9e9234240acae6fc582b7f22

Static Analysis - Stage 1

After downloading the sample I did a basic search over MalwareBazaar and virustotal to get some intel about the malware, it came out that it was marked malicious by 11 vendors in malwarebazaar and 14 by virustotal.

virustotal Figure 1: Virustotal

junk Figure 2: Junk strings with special characters

After extraction, got a js script which means that this malware is spreading via E-mail attachements and can be of other file formats too. When i opened it script in notepad, its highly obfuscated with junk strings and special characters and in between these strings code of malware resides. After clearing out junk strings I got the main code but its still highly obfuscated to read and still contained those junk strings, this was done change the entropy of malware and make it difficult for AV to detect. But scrolling through code some strings caught my attention like SaveToFile, Run, CreateObject, PublicLibraries.

obfus Figure 3: Obfuscated strings

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function (_0x395a8b, _0x44740b) {
  var _0x405d1b = _0x395a8b();
  while (true) {
    try {
      var _0x49683b = -parseInt(VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX(0x181)) / 0x1 * (-parseInt(VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX(0x130)) / 0x2) + -parseInt(VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX(0x183)) / 0x3 * (parseInt(VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX(0x148)) / 0x4) + parseInt(VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX(0x1a0)) / 0x5 + -parseInt(VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX(0x1a2)) / 0x6 + parseInt(VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX(0x1af)) / 0x7 + parseInt(VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX(0x11f)) / 0x8 + -parseInt(VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX(0x1b0)) / 0x9;
      if (_0x49683b === _0x44740b) {
        break;
      } else {
        _0x405d1b.push(_0x405d1b.shift());
      }
    } catch (_0x33323c) {
      _0x405d1b.push(_0x405d1b.shift());
    }
  }
})(_0x4085, 0x50ea0);

So the script starts with a IIEF after deofuscating, its understood that it performs String Array Rotation by moving first item to end, with paramters _0x4085 which might be long list of scrambled strings and 0x50ea0 as target value. Then this function WWWWWWWWW(_0x51c5bf) which is a custom encoder and decoder that maps character to custom codes back and forth like 80 <-> 00C7 and next it again has same function that does same thing but its like a different version of it.

1
2
3
4
5
6
7
8
9
function VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX(_0x23ef77, _0x36a72f) {
  var _0x4085fc = _0x4085();		//main array
  VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX = function (VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX1a, _0x2a5b13) {
    VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX1a = VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX1a - 0x10f;
    var _0xd1403d = _0x4085fc[VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX1a];
    return _0xd1403d;
  };
  return VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX(_0x23ef77, _0x36a72f);
}

Then there is VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJXX which is string retriever wrapper that first redefines itself so setup code don’t have to be called again and again, just returns value by calculating index and fetching string at that index also it immediately calls itself so that redefined function now being utilized.

1
2
3
4
var LOUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUONIA = VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJX(0x114);
var OPONIA = VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJX(0x153);
var erONIA = OPONIA.split('99')[VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJX(0x1a4)]('');   //split and join
var myObject = new ActiveXObject(erONIA);

It then uses split and join technique to execute Windows script host using ActiveXObject like WScript.Shell, Scripting.FileSystemObject etc. Then there are some more custom mapped codes and after that it has a varible that store something likely a path but obfuscated between lots of 99 so now we can get what its really mean by using same techniques split and join, and i got the path C:\Users\Public\Libraries\. It then check for a filename VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJ if not exist then creates it in directory and afterwards builds certain variables like newpath, MNBYTUIOP from which MNBYTUIOP stores Scripting.FileSystemObject and creates its object which gets stored in newpath.

1
2
3
4
5
6
 if (awwwank.FileExists(HGJHKKLL)) {} else {
    if ("BBBBBSTBBBBB".split('99').join('') === 'YESSSSSSSS') {
      awwwank.CopyFile(scriptName, HGJHKKLL);
      var IHUUHAIA = 'WScript.Shell'
      var HBBHA = "cmd /c schtasks /create /sc minute /mo 10 /tn" + scriptName + " /tr " + HGJHKKLL;

Now it checks existence of newly created file and also checks if BBBBBSTBBBBB is equal to YESSSSSSSS, obviously not then copies current running script into a directory and then it builds shell command cmd /c schtasks /create /sc minute /mo 10 /tn which is using schedule task for persistence and it runs this task every 10 minutes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function makeid(_0x423f2e) {
  var _0x2bd236 = '';
  var _0xdafb42 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.length;
  for (var _0x4fe693 = 0x0; _0x4fe693 < _0x423f2e; _0x4fe693++) {
    _0x2bd236 += 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.charAt(Math.floor(Math[VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJX(0x136)]() * _0xdafb42));
  }
  return _0x2bd236;
}

function FDAWE(_0x13e65a) {
    var _0x119246 = _0x13e65a.split('');
    var _0x439e6a = _0x119246.reverse();
    var _0x184107 = _0x439e6a.join('');
    return _0x184107;
}

makeid is used to generate random strings for files and directories and also a string reversal function FDAWE that work is to reverse the string and join it together during execution. Now it creates a new object of Microsoft.XMLDOM and creates a t element with datatype of bin.base64 also applies FDAWE on a large blob and cleans out(replace all symbols to space) that blob to store it as a text. Here is the script of it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def clean_and_restore_blob(blob):
    restored_order = blob[::-1]

    step1 = restored_order.replace('99', '')
    step2 = step1.replace('?', '')
    step3 = step2.replace('~', '')
    step4 = step3.replace(' ', '')
    step5 = step4.replace('!', '')
    step6 = step5.replace('#', '')
    step7 = step6.replace('$', '')
    step8 = step7.replace('%', '')
    step9 = step8.replace('^', '')
    step10 = step9.replace('&', '')
    step11 = step10.replace('*', '')
    final_cleaned = step11.replace('>', '')
    
    return final_cleaned
blob = "----------BLOB--------------"
cleaned_blob = clean_and_restore_blob(blob)
print(f"Final Blob: {cleaned_blob}")
1
2
3
4
5
6
7
var b = new ActiveXObject(ADODB.Stream);
b.Type = 0x1;
b.Open();
b.Write(E.nodeTypedValue);
b.Position = 0x0;
b.Type = 0x1;
b.SaveToFile("C:\\Users\\Public\\Libraries\\VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJ", 0x2);

And it then writes binary data using ADODB.Stream and save it into file VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJ and overwrites if already present. Then it does same thing again twice and stores them in .exe and .png file at same location. Now it creates WScript.Shell object and runs both exe and png binary. So this js script is just a dropper which dumps the exe and a png file also making persistence using schedule task.

Dynamic Analysis - Stage 1

I just executed the script and since we already know where its gonna be dropped and it will also schedule task So lets analyze whats happens after its being executed with system informer opened.

dyna1 Figure 3: Dropping files and colorcpl.exe process

It dropped the malware in C:\Users\Public\Libraries and starts the malware with colorcpl.exe but one thing to note that it uses schedule task for persistance but i didn’t saw any entry in autorun to verify it i even put all tasks in a text file and searched in that too but nothings there, its because above condition not satifies that YESSSSSSS check.

Static Analysis - Stage 2

Now lets analysze the dropped exe, implant and png file and the exe file came out to be a autoit script so we need to decompile it but the tools like myAutToExe, autoit-ripper are failing because maybe it can have different header. But I was wrong the main script code is written in that png file and the exe file somehow runs the command in the png file.

dyna2 Figure 4: Flow of malware which i understood

So now, lets analyze what the script does? At first, it has a custom decryption function that takes strings data, convert it to binary and perform BitXOR operation using key 99.Here is the python equivalent script of it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def xor_hex_string(hex_input, xor_key=99):
    if hex_input.startswith("0x"):
        hex_input = hex_input[2:]
    binary_data = bytes.fromhex(hex_input)
    result = ""
    for byte in binary_data:
        result += chr(byte ^ xor_key)
    return result

input_str = "0x0806110D060F50514D070F0F"  #kernal32.dll
input_str_1 = "0x20593F340A0D070C14103F301A10140C1455573F000C0F0C1100130F4D061B06"  #C:\Windows\Syswow64\colorcpl.exe
output = xor_hex_string(input_str)
print(f"Decoded string: {output}")
output = xor_hex_string(input_str_1)
print(f"Decoded string: {output}")

dyna3 Figure 5: Reads raw blob data

Now it creates a struct using DllStructCreate and set some values to it you can also see what its doing just by putting those hex values to script, and then do some checks using Dllcall for strings like kernal32.dll, C:\Windows\Syswow64\colorcpl.exe, CreateProcessA means calls Create process of colorcpl in suspended state, if the checks passes then it opens the raw blob file VDJZUXZDMZHYJTWOMKOIJDVYGRIYWBWPBFAVPKJIZOSXBQMQXOCGGHJ reads it and sets the blob after decoding it using BitXOR with key 1185463. It utilizes same process hollowing techniques as AgentTesla using NtAllocateVirtualMemory, NtWriteVirtualMemory, NtCreateThreadEx to inject the shellcode in legitimate windows process colorcpl.exe.

Dyamic Analysis - Stage 2

dyna4 Figure 6: Interesting strings

So I ran the dropped malware again and dumped all the things while the malware is running as colorcpl using procdump and saw strings like Remcos Agent initialized, details about user, OS, CPU and its architecture and some dns like mindabusiness.duckdns.org, mattersthatmatters.duckdns.org, greatmatteronly.duckdns.org also hints that it crafts http requests. Also it logs the keystrokes, credential theft.

dyna5 Figure 7: HTTP request hints

dyna6 Figure 8: List of different domains and subdomain

dyna7 Figure 9: Trying to communicate to DNS domains

It also tweaks settings of the browser and there is a big list of different domains and subdomains which maybe used as different C2 domains so in case if one goes down it switch to other domain and RAT will still work. While malware is running i made a fakenet to see the C2 domain and got a IP 185.208.156.187 to which is flagged suspicious by a vendor.

IOC

  • DNS Domains: mindabusiness.duckdns.org, mattersthatmatters.duckdns.org, greatmatteronly.duckdns.org
  • Legitimate Process: colorcpl.exe
  • Hashes
    1
    2
    
    aa69940b8845fd03a4784db92bd2352f482d66652cf4e92cd7560567c84dc622 -> Raw Blob
    f60e93d2152e8b3e6c132c37e22a47c6ce63c4ecb4ad4b745a345719d02e71a1 -> AutoIt script
    
  • C2 IP: 185.208.156.187
This post is licensed under CC BY 4.0 by the author.