Ghidra: a triage reading of a lab sample
Triage reading asks what a sample is built to refer to: which hosts appear as strings, and which imports look like network or persistence activity. You are annotating references in a database. You are not running the sample, and you are not reconstructing a working program.
The sample stays in the isolated lab
A triage reading starts from a file you are allowed to study, on a machine that is allowed to hold it. For anything hostile, that machine is a virtual machine with no shared folders, no route to your real files, and a snapshot you can revert. Ghidra’s import does not execute the sample. Analysis does not execute it either. You still do not double-click the sample, you do not open it in a normal desktop application that might, and you do not copy the project onto a laptop you use for anything else. The project contains the bytes.
The fictional file in this lesson is lab-sample-07.exe, a stand-in so the tables have something to point at. Your real lab may use a sample the instructor captured and authorized. The method is the same either way, and it ends in a paragraph a defender can read. It does not end in a working replica of the sample.
Network-looking names and host strings
Start with the import tree, the same capability reading you practiced on harbor-check. Group names that exist to open network conversations. On Windows, the WinINet family includes InternetOpenA, InternetConnectA, HttpOpenRequestA, and HttpSendRequestA. A socket-style set would include names such as socket, connect, and send. The group is a lead that the file is built to speak on the network. It is not a log of a conversation.
Then search strings for text shaped like a host or a URL. Keep a string only if a function references it. Open that function and see whether the string is the argument to one of the network imports. If InternetConnectA’s host argument is the literal updates.lab.internal, the sentence you can defend is: this function references a connect API and that host string. You do not add “and then it downloads a second stage,” unless the bytes of that function actually contain the later calls and you have read them. Even then you describe the references you saw. You do not rebuild the client.
Persistence-looking names are a separate column
A second group of imports exists to write durable configuration: RegCreateKeyExA and RegSetValueExA for the registry, CreateServiceA for a service, and similar APIs whose purpose is to leave a setting behind. A string beside them is what tells you the target. The path Software\Microsoft\Windows\CurrentVersion\Run is a well-known auto-start key. A function that references both RegSetValueExA and that path contains a persistence-looking reference. Record the function’s address, the API, and the string. Leave the value that would be written as “not read” if you did not read the buffer that supplies it.
Keep this column separate from the network column. Two leads can both be true without you stitching them into a procedure. The triage note lists them as two observations. A reader who needs to know whether one runs only after the other can ask for a deeper read of the control flow between those two functions. That deeper read is still a description of branches you can see. It is not a guide to placing a file in the Run key.
What the static picture leaves out
Some samples resolve API names at runtime, so the import table under-reports what the code is built to call. Some strings are encoded and will not appear in the string window as readable hosts. Some readable strings have no references and are leftovers or decoys. When the import list is only a loader and a library-resolution API, say that the static import picture is incomplete and stop. Filling the gap by writing a decoder, or by running the sample “just to see,” is outside this lesson. Running anything is a separate authorization, on a network that is built for it, with a different set of tools.
The note you hand over has three parts. What you opened and that you did not execute it. The references, grouped, each tied to a function address. The unresolved list, including empty reference lists and encoded regions you did not interpret. A defender can turn a referenced host string into a detection lead. They cannot turn your note into proof of a connection that analysis never made.
Lab: write the lead, then stop
If your instructor provided an authorized sample, import it on the isolated VM and fill the three columns from the diagram with addresses from that file. If they did not, use the fictional tables in this lesson as a paper exercise: rewrite the lead in your own words, strike any sentence that claims a connection was made or a key was written, and add an unresolved line. Either way, the finished note fits on half a page.
Do not add a “how it would be built” section. Do not write a script that calls the APIs you found. Do not copy the sample off the VM when you are done. Snapshot or revert according to the lab’s rules, and leave the project on that machine.
A lead you can hand someone, about a fictional lab sample Sample: lab-sample-07.exe Not executed. Read in Ghidra on the isolated VM. References, not a run: WinINet imports include InternetOpenA, InternetConnectA, HttpOpenRequestA, HttpSendRequestA. The function at <addr> references InternetConnectA and the string "updates.lab.internal". Advapi32 imports include RegSetValueExA. A separate function references RegSetValueExA and the string of the CurrentVersion\Run key. Unresolved: Two short strings in .rdata have no direct reference. Static reading does not say the host was contacted.
Diagram
Three columns of a static triage note
- Isolate
The sample and the project stay on a virtual machine with no shared folders. Opening the file in Ghidra does not execute it. You still do not launch it.
- Imports
Group the import tree into network-looking names, persistence-looking names, and everything else.
- Strings
Find host-shaped and path-shaped strings. Keep the ones a function actually references.
- Note
Write a short lead for a defender: references you saw, the function that ties them, and the limits of a static read.
What each column is allowed to claim
- Network-looking
Imports from a networking library, plus a string that is shaped like a host or a URL, plus the function that references both. The claim is “contains these references.”
- Persistence-looking
Imports that write registry values or create a service, plus a string that names a well-known auto-start location. The claim is that the bytes refer to that location.
- Not enough
A string with no reference, an import the code never calls, or a decoded blob you have not read. Those stay in an “unresolved” line. They do not get promoted because they would make the story complete.
Sample: lab-sample-07.exe Not executed. Read in Ghidra on the isolated VM. References, not a run: WinINet imports include InternetOpenA, InternetConnectA, HttpOpenRequestA, HttpSendRequestA. The function at <addr> references InternetConnectA and the string "updates.lab.internal". Advapi32 imports include RegSetValueExA. A separate function references RegSetValueExA and the string of the CurrentVersion\Run key. Unresolved: Two short strings in .rdata have no direct reference. Static reading does not say the host was contacted.
Check yourself
1. The isolated VM has the sample and Ghidra. What does importing the sample into a project do?
2. InternetConnectA is imported, and one function references both that import and the string updates.lab.internal. What is the accurate sentence?
3. A function references RegSetValueExA and a string that is the Run auto-start key path. How do you record it?