First Run Observation: UI, Files, Logs, and Network Baseline
A first-run baseline is a snapshot of what an app does before you start poking at individual features. Without a baseline, you cannot tell whether a file, log, process, or network event came from normal startup or from the specific button you clicked later.
This module teaches the habit: observe first, interact second.
For a red-team operator, this matters before using internal tooling on a client machine. For an app-security reviewer, it matters before claiming a behavior is feature-specific. For a detection engineer, it matters because startup telemetry is the background noise that later detections must account for.
What A Baseline Means
A baseline is the "normal before I touch anything" record. It is not exciting, but it is what lets you reason later.
Imagine you click a button and then find this file:
~/Library/Application Support/MacSecTrainingApp/state.json
Without a baseline, you cannot answer:
Did launch create the file?
Did the button create the file?
Did a previous lab leave it behind?
Did the file exist before this app ran?
Those questions are not academic. In real work, weak baselines create weak findings. If you cannot prove what changed, you cannot confidently explain what caused the change.
This module uses a simple pattern:
Before:
Record the app package and system state before launch.
Launch:
Start the app without pressing feature buttons.
After:
Record files, logs, process state, and network state.
Compare:
Decide what belongs to startup and what remains unknown.
Later modules use the same pattern around individual actions.
Start From A Clean State
From the training app folder:
./tools/reset_lab_state.sh
./tools/build_app.sh
Create a fresh evidence directory:
EVIDENCE=~/macseclabs/evidence/re-00-first-run
mkdir -p "$EVIDENCE"/{logs,artifacts,screenshots}
Record the target path:
APP="$PWD/build/MacSecTrainingApp.app"
BIN="$APP/Contents/MacOS/MacSecTrainingApp"
printf '%s\n' "$APP" > "$EVIDENCE/target-path.txt"
Why reset first?
Old state is misleading:
A previous run may leave preferences, JSON files, logs, or cached behavior.
Rebuilding gives a known target:
You know which app bundle produced the evidence.
Evidence folders separate sessions:
You can compare runs without overwriting old proof.
Static Snapshot Before Launch
The static snapshot answers: "what can I know before the app executes?" This is the safest evidence you can collect. It does not depend on timing, UI state, or which buttons were clicked.
Capture those facts:
{
echo "## files"
find "$APP/Contents" -maxdepth 3 -type f | sort
echo
echo "## plist"
plutil -p "$APP/Contents/Info.plist"
echo
echo "## binary"
file "$BIN"
echo
echo "## signature"
codesign -dv --verbose=4 "$APP" 2>&1
echo
echo "## linked libraries"
otool -L "$BIN"
} > "$EVIDENCE/artifacts/static-before-launch.txt"
This is your "before" state. Later, if the app writes files or preferences, you can compare against this record.
When reading static-before-launch.txt, separate the evidence types:
Bundle evidence:
Files present before execution.
Identity evidence:
CFBundleIdentifier, CFBundleExecutable, version fields.
Binary evidence:
Architecture and Mach-O type.
Signing evidence:
Signature type and code directory details.
Dependency evidence:
Frameworks and libraries the app may use.
This distinction matters. If a file appears after launch, it is runtime evidence. If a file is already in the bundle before launch, it is packaging evidence.
At this point, pause and write one sentence:
Before launch, I know the app package contains these files and the main binary
has this architecture/signature/dependency profile, but I have not observed
runtime behavior yet.
That sentence prevents a common beginner mistake: treating packaged files as if the app has already used them.
Launch And Observe
Open the app:
open "$APP"
Do not click buttons yet. Observe the window and write a plain description:
cat > "$EVIDENCE/notes.md" <<'EOF'
# First-run UI observation
- Window title:
- Visible buttons:
- Status fields:
- Anything surprising:
EOF
This seems basic, but it teaches a real analysis skill: map user-facing actions before hunting code. If a button says "Simulate License Check", later you can search for related strings, selectors, log messages, and state keys. UI text is often the bridge between what a user does and what a binary implements.
The UI observation should be plain. Do not guess what a button does. Write what a user can see:
Visible:
Window title, button labels, status fields, initial values.
Not proven yet:
Which function handles each button, which file changes, whether network is
used, or whether a helper process runs.
This distinction keeps the analysis honest.
Now capture app-created local state:
STATE="$HOME/Library/Application Support/MacSecTrainingApp/state.json"
PREF="$HOME/Library/Preferences/com.macseclabs.trainingapp.plist"
test -f "$STATE" && cp "$STATE" "$EVIDENCE/artifacts/state-after-launch.json"
test -f "$PREF" && plutil -p "$PREF" > "$EVIDENCE/artifacts/preferences-after-launch.txt"
If a file is missing, write that down. Missing evidence is still evidence.
For example, if state.json exists but the preference plist does not, that tells
you the app persisted state through one mechanism and not the other during this
specific run. Do not "fix" the evidence to match expectations. Record what the
system actually did.
When reading state, ask:
Where is the file?
User home, app container, temporary directory, cache, or bundle path?
What format is it?
JSON, plist, SQLite, plain text, binary, or unknown?
What keys are present?
Do they look like feature state, user identity, timestamps, counters, or
configuration?
What caused it?
Launch only, or a specific later action?
In this module, the answer should be conservative: launch created or updated initial app state. Feature-specific conclusions come later.
Unified Logging Baseline
The app logs under subsystem com.macseclabs.trainingapp. Capture recent
messages:
log show --last 10m \
--predicate 'subsystem == "com.macseclabs.trainingapp"' \
--style compact \
> "$EVIDENCE/logs/unified-log-first-run.txt"
If the log output is empty, verify that the app ran and repeat the command quickly. Unified Logging can be noisy and time-bounded, so capture close to the event.
The important concept is the subsystem. In real macOS reversing, subsystem names often match bundle identifiers or internal component names. They help you filter system noise down to the app you care about. Later, when the app has multiple features, the log stream becomes a timeline: launch happened, action happened, state changed.
Unified Logging can be confusing at first because it is large, time-bounded, and not everything is persisted forever. Learn the habit now:
Use a narrow predicate:
Filter on subsystem or process name instead of reading the whole log.
Capture close to the action:
Do not wait an hour and expect a clean event trail.
Save raw output:
Logs are evidence. Keep the raw file, then summarize separately.
Do not overread missing logs:
No matching log in your time window does not prove the app never logs.
Process And File Baseline
Record whether the app is running:
pgrep -fl MacSecTrainingApp > "$EVIDENCE/artifacts/process-baseline.txt" || true
lsof -c MacSecTrainingApp > "$EVIDENCE/artifacts/lsof-baseline.txt" 2>&1 || true
lsof is useful because it shows open files, libraries, sockets, and current
working directories. Early in the course, you do not need to understand every
line. Learn to spot paths that belong to the target app.
Start by looking for three things:
App bundle paths:
Confirms which executable and resources are open.
Home-directory paths:
Shows user-specific state, preferences, caches, or logs.
Network/socket rows:
Shows whether the app has active communication during the observation window.
For a fresh researcher, lsof output can look noisy. Start with path recognition:
Paths under the app bundle:
The process has opened its executable, resources, or libraries.
Paths under /System or /usr/lib:
Runtime frameworks and system libraries.
Paths under your home directory:
User-scoped state, preferences, caches, or documents.
TCP/UDP rows:
Possible network behavior.
You do not need to understand every descriptor yet. You need to learn how to separate target-related evidence from background system noise.
Loopback Network Baseline
The training app has a button that can call 127.0.0.1:8765. Before pressing
that button, prove there is no established connection:
lsof -iTCP -nP | grep -i MacSecTrainingApp > "$EVIDENCE/artifacts/network-before-button.txt" || true
Later modules start a local mock server and trigger the request. For now, the important lesson is sequencing: capture before the action, then capture after.
The expected result before pressing the button is usually no app-owned TCP connection. If you see one, do not ignore it. Save it and write it as a baseline observation. The lab target is controlled, but your host environment is still a real Mac with real background activity.
Engagement Value
A first-run baseline helps answer operational questions before the deeper reverse-engineering work starts:
- Does the app create state immediately?
- Does it emit logs that would identify the tool or action?
- Does it open network sockets before the operator clicks anything?
- Does it load unexpected frameworks or helper code?
- Which artifacts would a defender see during normal launch?
Those answers are not exploit development. They are tradecraft hygiene. If you cannot explain what a tool does at launch, you are not ready to reason about what it does during an operation.
What A Baseline Lets You Say
After this module, a good statement looks like:
On first launch, the app created a JSON state file under Application Support,
updated a preference plist, emitted Unified Logging events under
com.macseclabs.trainingapp, and did not establish a network connection before
the loopback probe button was used.
That is precise. It separates startup behavior from feature behavior. It also gives you locations to inspect when later modules reverse the code that produced those artifacts.
What You Should Have After This Module
You should have:
static-before-launch.txt:
What the app looked like before execution.
notes.md:
Plain UI observation without guesses.
state-after-launch.json:
Runtime state created or updated by launch, if present.
preferences-after-launch.txt:
UserDefaults evidence, if present.
unified-log-first-run.txt:
Time-bounded app logs.
process-baseline.txt and lsof-baseline.txt:
Process and file/socket context.
network-before-button.txt:
Network state before feature interaction.
More importantly, you should understand why each file exists. If you cannot explain that, repeat this module before clicking feature buttons in later labs.
This is one free module of many.
Get every module across all ten phases, with working code and the detection engineering to catch each technique.
Get full access