// HackTheBox  ·  writeup

TrainingDay

HackTheBox Easy sha256sumfilestringsobjdump
Malware Analysis

Scenario

A reverse engineering challenge for beginners.

Executive Summary

Three tiny executables from the same author were triaged statically. The first was fingerprinted, its architecture and symbol state checked, and the leftover PDB path gave away the author’s IDE and Windows account. Disassembly of all three then showed how each build passes arguments: a 32-bit stack-based convention, a 32-bit register-assisted one, and the 64-bit Windows register convention. No sample was executed, and there is no attack chain here: the case is about reading compiler artifacts and calling conventions.

Artifacts

Format note: The argument questions use stack notation such as esp+4 and register names in lowercase. The answer masks (***+*, ***+**) tell you which notation is expected.

Binary Triage

Q1 — What is sha256sum hash of argument_baby_1.exe ?

Hash the sample as delivered, before touching anything else, so every later note can be tied to the same file.

sha256sum argument_baby_1.exe
Click flag to reveal dc6267608ddfcc5c80571ccd46475a03fb667baf8620d0e91c93ccacacf97ef6
Q2 — Can you find development enviroment of malware author (e.g:ide) ?

The PDB path embedded in the debug directory (Q5) points at a Visual Studio style project layout with a Release build folder, and the PE’s linker version fits MSVC 14.35, which ships with Visual Studio 2022. The environment is read from those two clues together rather than from a single “IDE” field.

strings -a argument_baby_1.exe | grep -i pdb
Click to reveal answer Visual Studio
Q3 — What CPU architecture was argument_baby_1.exe compiled for ?

file reads the PE header machine field directly. It reports a PE32 image for Intel i386, so this is a 32-bit build.

file argument_baby_1.exe
Click flag to reveal 32 bit
Q4 — Is argument_baby_1.exe a symbol-stripped binary?

There are no COFF symbols in the file, which is what a naive reading of “stripped” suggests. The debug directory still carries a PDB reference (Q5), so debug information was left in and the binary is not stripped in the sense the question means.

file argument_baby_1.exe

Gotcha: true looks right because the symbol table is empty, but the PDB path in the debug directory means debug info is present. false is the accepted answer.

Click to reveal answer false
Q5 — Retrieve the full PDB file path from the debug information of argument_baby_1.exe

Linkers write the build-time PDB path into the RSDS debug record. A plain strings pass with a case-insensitive pdb filter surfaces it.

strings -a argument_baby_1.exe | grep -i pdb
Click flag to reveal C:\Users\HTB\Desktop\argument\Release\argument.pdb
Q6 — The malware author was using a Windows OS. What was the associated username?

From the Q5 path, the profile directory under C:\Users\ is the account that built the binary.

strings -a argument_baby_1.exe | grep -i 'users'
Click to reveal answer HTB

argument_baby_1.exe (32-bit)

Q7 — What calling convention is used in argument_baby_1.exe ?

In objdump -d the arguments are pushed onto the stack before the call, nothing is loaded into ecx or edx for them, and the caller cleans the stack afterwards. That combination is the default 32-bit C convention.

objdump -d argument_baby_1.exe
Click flag to reveal cdecl
Q8 — How is the 1st argument passed to the function in argument_baby_1.exe?

With stack passing, the first argument sits at the lowest address at the call site. The offsets for Q8 to Q12 run in 4-byte steps from the stack pointer, in the notation the answer masks ask for. The offsets follow from the push order and the masks, so they were reasoned rather than read as literal [esp+n] operands in one listing.

objdump -d argument_baby_1.exe | grep -B8 call
Click to reveal answer esp
Q9 — How is the 2nd argument passed to the function in argument_baby_1.exe?

Next slot up the stack, 4 bytes above the first.

objdump -d argument_baby_1.exe | grep -B8 call
Click flag to reveal esp+4
Q10 — How is the 3rd argument passed to the function in argument_baby_1.exe?

Third slot, 8 bytes above the stack pointer.

objdump -d argument_baby_1.exe | grep -B8 call
Click to reveal answer esp+8
Q11 — How is the 4th argument passed to the function in argument_baby_1.exe?

Fourth slot, 12 bytes up, written in hex as the mask expects.

objdump -d argument_baby_1.exe | grep -B8 call
Click flag to reveal esp+c
Q12 — How is the 5th argument passed to the function in argument_baby_1.exe?

Fifth slot, 16 bytes up, again in hex.

objdump -d argument_baby_1.exe | grep -B8 call
Click to reveal answer esp+10
Q13 — Which CPU register holds the function's return value in argument_baby_1.exe?

The function computes its result into the accumulator just before ret.

objdump -d argument_baby_1.exe | grep -B5 ret
Click flag to reveal eax

argument_baby_2.exe (32-bit)

Q14 — What calling convention is used in argument_baby_2.exe ?

This 32-bit build takes its first two arguments in ecx and edx, with the rest on the stack, and the function ends in ret 0xc, so the callee pops the stack arguments. That is the fastcall pattern.

objdump -d argument_baby_2.exe
Click to reveal answer fastcall
Q15 — How is the 1st argument passed to the function in argument_baby_2.exe?

First integer argument goes in the first fastcall register.

objdump -d argument_baby_2.exe | grep -B8 call
Click flag to reveal ecx
Q16 — How is the 2nd argument passed to the function in argument_baby_2.exe?

Second argument goes in the second fastcall register.

objdump -d argument_baby_2.exe | grep -B8 call
Click to reveal answer edx
Q17 — How is the 3rd argument passed to the function in argument_baby_2.exe?

Registers are exhausted, so the third argument is the first one on the stack, at the stack pointer.

objdump -d argument_baby_2.exe | grep -B8 call
Click flag to reveal esp
Q18 — How is the 4th argument passed to the function in argument_baby_2.exe?

Next stack slot, 4 bytes up.

objdump -d argument_baby_2.exe | grep -B8 call
Click to reveal answer esp+4
Q19 — How is the 5th argument passed to the function in argument_baby_2.exe?

Last stack slot, 8 bytes up. Three stack arguments at 4 bytes each match the ret 0xc from Q14.

objdump -d argument_baby_2.exe | grep -B8 call
Click flag to reveal esp+8

argument_baby_3.exe (64-bit)

Q20 — What CPU architecture was argument_baby_3.exe compiled for ?

file reports PE32+, the 64-bit PE format.

file argument_baby_3.exe
Click to reveal answer 64 bit
Q21 — How is the 1st argument passed to the function in argument_baby_3.exe?

The 64-bit Windows convention passes the first four integer arguments in registers, starting with rcx.

objdump -d argument_baby_3.exe | grep -B8 call
Click flag to reveal rcx
Q22 — How is the 2nd argument passed to the function in argument_baby_3.exe?

Second register in the sequence.

objdump -d argument_baby_3.exe | grep -B8 call
Click to reveal answer rdx
Q23 — How is the 3rd argument passed to the function in argument_baby_3.exe?

The extended registers r8 and r9 carry the third and fourth arguments, which is why the masks drop to two characters.

objdump -d argument_baby_3.exe | grep -B8 call
Click flag to reveal r8
Q24 — How is the 4th argument passed to the function in argument_baby_3.exe?

Fourth register slot.

objdump -d argument_baby_3.exe | grep -B8 call
Click to reveal answer r9
Q25 — How is the 5th argument passed to the function in argument_baby_3.exe?

The fifth argument spills to the stack, above the 32-byte shadow space the caller reserves for the register arguments, which puts it at rsp+20 in hex.

objdump -d argument_baby_3.exe | grep -B8 call
Click flag to reveal rsp+20
Q26 — In the case of argument_baby_3.exe, which CPU register stores the function's return value?

Same accumulator as the 32-bit builds, but at full 64-bit width.

objdump -d argument_baby_3.exe | grep -B5 ret

Gotcha: eax is the reflex answer carried over from the 32-bit samples. In a 64-bit build the question wants the 64-bit register name.

Click to reveal answer rax

Attack Summary

There is no intrusion in this case. The three files are training binaries.

PhaseAction
TriageHash, architecture and symbol state recorded for sample 1
Attribution artefactsPDB path exposes the author’s IDE, project folder and Windows account
AnalysisCalling convention and argument locations read from disassembly for all three builds

IOCs

TypeValue
SHA-256 (argument_baby_1.exe)dc6267608ddfcc5c80571ccd46475a03fb667baf8620d0e91c93ccacacf97ef6
PDB pathC:\Users\HTB\Desktop\argument\Release\argument.pdb

MITRE ATT&CK

No ATT&CK techniques apply: the case covers static reverse engineering of benign training binaries, with no adversary behaviour.

Defender Takeaways

Build paths leak the author. The PDB path named the IDE project folder and the Windows account that compiled the sample. Collect PDB paths from every sample as a pivot for clustering related tooling.

“Stripped” needs a precise check. An empty symbol table does not mean the debug directory is empty. Check both before calling a binary stripped.

Know the calling convention before reading assembly. Cdecl, fastcall and the x64 convention put arguments in different places, and misreading them sends a decompiler review in the wrong direction. Confirm architecture with file first, then read the call sites and the ret form.

Register width follows architecture. Return values live in eax on 32-bit builds and rax on 64-bit builds, a small difference that is easy to get wrong when switching between samples.

Conclusion

Three small binaries covered the three conventions an analyst meets most often on Windows. The PDB path was the one piece of attribution-relevant data, and it came from a compiler default rather than anything the author chose to hide.