You can learn to follow input into a decision using a small check whose source you own. The supplied desk_code function accepts a string when its counted length equals 4. It is a teaching example, sometimes called a crackme: a deliberately inspectable check for practice. It is not a commercial license or a grant to examine other software.
This function calls length_of, then chooses one of two return values. Trace the source and label the result a prediction. Confirming it on a specific binary would require a matched build and recorded run, which are absent. The functions in owned-check.c have no main and are not a standalone executable.
Before you begin
- Recommended first
- An editor that works under pressure, Processes, How to read a script someone else wrote, Where practice is allowed, Opening a binary in Ghidra and finding main
- Estimated time
- 18 min reading, 18 min activity. Setup and optional practice may take longer.
- Where you work
- Read the supplied C/Python source and clearly labeled conceptual models in any current desktop or mobile browser. No compiler, binary execution, debugger, tracer or Ghidra installation is required. Function-only C sources are not standalone programs.
- Without a lab
- Trace the source or model on paper and record the condition, chosen branch and evidence limit. No matched lesson ELF, disassembly, debugger session or Ghidra export is supplied. Historical observations retain their separate context. Reading works without JavaScript. Checking requires JavaScript and a secure context; saving also requires usable storage.
After this lesson you can:
- Locate the comparison in desk_code and state the length it requires.
- Trace the input notes through the source to a return value.
- Write the stage evidence note with an explicit missing-run limit.
01Example
Source trace of desk_code for the input desk. Predicted from owned-check.c. Not observed. The activity uses a different input.
desk_code returns 1 when length_of(text) equals 4.
Otherwise it returns 0.
Input: desk
Characters: d e s k, then the end marker.
Count: 4
4 equals 4, so the source returns 1.
Label: source trace. No binary was run.02The comparison
const char *text declares a pointer used here to read characters. A pointer identifies a memory location; const indicates the characters are not modified through this declaration. text[count] selects a character at an offset, starting at 0. The while loop repeats until it reaches '\0', the zero byte ending a C string. The count excludes that terminator. For the supplied ASCII inputs, each character occupies one byte. This function counts bytes, not Unicode characters in general.
The helper returns -1 for a null pointer, the special pointer value 0 meaning no valid location. For the supplied ordinary text strings, it counts characters before the terminator. This small function assumes a valid terminated string within readable memory; it is not a general safe input-validation routine. A null pointer is different from a string terminator: one means no text location, while the other marks the end inside a valid text sequence.
desk_code compares the count with 4 using ==, returns 1 on equality and 0 otherwise. These are chosen function results, not observed process exits. A strings list cannot prove that branch was taken, and a conceptual branch or decompiler sketch cannot replace a matched binary trace.
The hand-drawn listing uses x86-style notation as a teaching model. eax names the result register in this model. call invokes the named function; cmp compares values; jne means jump if not equal. mov places a value in the named register, ret returns, and fail: labels a location the jump can select. These names explain the drawing; they do not claim a compiler emitted it.
Count, compare and predict the selected pathModel. A teaching model drawn by hand. It shows the idea, not real tool output.
Read the full explanation below.
Source prediction; no binary was run. Hand-drawn listing, not disassembly.
Select notes in this scene for the “Trace a different input” activity.
| Original line | Source |
|---|---|
| 9 | count = 0; |
| 10 | while (text[count] != '\0') { |
| 11 | count = count + 1; |
| 13 | return count; |
| 17 | if (length_of(text) == 4) { |
| 18 | return 1; |
| 20 | return 0; |
Hand-drawn instruction model
desk_code:
call length_of ; result in eax
cmp eax, 4
jne fail ; jump if not equal
mov eax, 1
ret
fail:
mov eax, 0
ret
Current listing lines: 2. Skipped: none established.
- Conceptual eax
- unknown
- Comparison
- not compared
- Equal?
- unknown
- Function return
- not returned
jne decision: not established
Select a named token for its definition.
The terminator ends a valid string and is excluded from count. A null pointer means no valid text location; it is a different source case, not a selectable memory simulation.
Begin desk: count is zero; no result or branch is established.
Follow the explanation
- Begin desk: count is zero; no result or branch is established.
- Count byte 1, d; the terminator is not counted.
- Count byte 2, e; the terminator is not counted.
- Count byte 3, s; the terminator is not counted.
- Count byte 4, k; the terminator is not counted.
- length_of counts d, e, s, k and returns 4 in conceptual eax.
- cmp compares 4 with 4 and records equality.
- jne is not taken because the values are equal; continue to the success assignment.
- The success mov sets conceptual eax to 1.
- desk_code returns 1; the return bypasses the failure block. This is a source prediction.
Both complete source-prediction paths
| Original line | Source |
|---|---|
| 9 | count = 0; |
| 10 | while (text[count] != '\0') { |
| 11 | count = count + 1; |
| 13 | return count; |
| 17 | if (length_of(text) == 4) { |
| 18 | return 1; |
| 20 | return 0; |
desk_code:
call length_of ; result in eax
cmp eax, 4
jne fail ; jump if not equal
mov eax, 1
ret
fail:
mov eax, 0
ret
| Sample | Count and comparison | jne path | Assignment and return |
|---|---|---|---|
| desk | d, e, s, k; count 4; 4 equals 4 | Not taken; lines 5–6 execute; return bypasses failure block | Line 5: eax=1; line 6: function return=1 |
| notes | n, o, t, e, s; count 5; 5 differs from 4 | Taken to fail; lines 5–6 skipped | Line 8: eax=0; line 9: function return=0 |
The while condition tests text[count] against '\0'; each counted ASCII byte increments count once. The terminator is never counted. A null pointer means no valid text location and makes the helper return −1; the supplied two strings are ordinary terminated text.
Source prediction; no binary was run. Function return values 0/1 are not observed process exit codes. A matched executable, disassembly, decompiler listing and syscall trace are missing.
- call
- call invokes the named function in this conceptual listing.
- cmp
- cmp compares values; it does not itself choose a jump.
- jne
- jne means jump if not equal.
- mov
- mov places a value in the named register.
- ret
- ret returns from the function.
- eax
- eax is the conceptual result register in this hand-drawn model. No register was captured.
- fail
- fail: labels the location the jump can select.
/* Desk code check. Original teaching program. Not a product license. Not compiled. */
int length_of(const char *text) {
int count;
if (text == 0) {
return -1;
}
count = 0;
while (text[count] != '\0') {
count = count + 1;
}
return count;
}
int desk_code(const char *text) {
if (length_of(text) == 4) {
return 1;
}
return 0;
}
03Trace a different input
Use the fresh activity text notes and trace its five characters until the terminator. Keep the count, equality result and function return separate. The later understanding question supplies a different string.
Source trace of owned-check.c. Not a program run.
Rule visible in desk_code:
length_of(text) is compared with 4.
Equal: return 1.
Otherwise: return 0.
A text pointer that is 0 makes length_of return -1, which is not 4, so desk_code returns 0.
The end of a text value in this source is the '\0' marker. The count stops before that marker.
Worked input "desk":
Characters d, e, s, k, then the end marker.
Count is 4.
4 equals 4, so the source returns 1.
Predicted from the source. Not observed.
Activity input "notes":
Characters n, o, t, e, s, then the end marker.
Count is 5.
5 equals 4 is false, so the source returns 0.
Predicted from the source. Not observed.
A matched run would be confirmation. No binary was built, so nothing was confirmed by execution.
- Count the characters in notes before the end marker. Check: the count is 5.
- Compare 5 with 4 using the condition in desk_code.
- Check: the source trace returns 0.
- Stop. Do not compile the check and do not feed it an input.
04Stage evidence note
This is the module note and the stage note. It is ungraded. Use one line for each blank on the card. The limit line has to say that an executable, a disassembly, a decompiler listing, and a syscall trace are still missing. A source prediction for the desk code is not a confirmed run.
Recommended reading order for the note: Compiled and interpreted, then Enough C and assembly, then file, strings, and strace, then the Ghidra lesson, then this check. The catalog list is unchanged.
Stage evidence note. Ungraded. Fill the blanks from the lessons. No binary was run.
1. hello.c role: ____
Planned hello ELF role: ____
Was the ELF file produced? ____
2. The compare/jump card is a: conceptual model / captured disassembly. Circle one.
What is still missing? ____
3. A strings list can show a stored sequence. It cannot by itself show: ____
4. The decompiler sketch compared with compare.c differs in: ____
Is the sketch the original source file? ____
5. desk_code returns 1 when length_of(text) equals: ____
Source prediction is not the same thing as a run. Was a binary run? ____
Rubric: one filled line for each item, plus a limit.
A limit names what the page still does not have: an owned executable, a disassembly, a decompiler listing, or a syscall trace.
Stop. Do not compile or run anything to fill a blank.
- Fill blank 1 from the hello card: hello.c is source, and the ELF row is an unproduced compiler-output role.
- Fill blank 2 with the words conceptual model. Name the missing disassembly.
- Fill blank 3 with what a strings list cannot show: which branch ran.
- Fill blank 4 with a name that differs between compare.c and the sketch, and say the sketch is not the source file.
- Fill blank 5 with 4, and write that no binary was run. Stop without compiling.
05Optional challenge
From the source, predict the return of desk_code for the two-character input ab. Write the count, the comparison, and the sentence that a run is still missing. About ten minutes.
06Fresh cases for the checks
Use these fictional cases for the checks below. They have different inputs from the worked examples. Read them here or make notes; no installation or external service is needed.
Length case: optional changed inputOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.
Fresh source rule: desk_code returns 1 exactly when length_of(text) equals 4; otherwise 0.
Input for this case: cord
All four letters count; there is no space or end-marker character in this input.
This is a source prediction only, not a run.
07Explain your evidence
Optional written self-check: explain one decision using the supplied case. No tool run is required.
Check your explanation: count spaces when present and exclude the end marker; connect the length comparison to its branch; distinguish source/model predictions from disassembly and an observed run.
08Common mistakes
If notes is traced as a success, the count was taken from desk. notes has five characters, and the comparison asks for 4.
If the evidence note says a disassembly was captured, the model label was changed. The branch card is conceptual, and the file is still missing.
If the check is treated as a commercial license to defeat, the opening was skipped. The program is an owned length check. Stop if the file in front of you is not owned-check.c.
References
- GCC manual: Options Controlling the Kind of Output
Defines preprocessing, compilation, assembly and linking, input suffixes and output naming.
- Ghidra help: Decompiler window
Explains function decompilation and how labels/types affect the reconstructed view. It does not promise the original source text.
- Debugging with GDB: Starting your Program
Defines run and start, including program selection and temporary stopping at main.
- GNU binutils: strings
Defines printable-sequence extraction, default minimum length and scan-coverage options. A listed sequence is not a run.
Next is the first lesson of Pwn.