Description
In the function doStuff with parent and child processes, what does the child process return? There is no exploit here. This is a value-tracking exercise: the binary forks several times, every process adds the same constant to one shared integer, and you have to compute the final value, accounting for 32-bit signed overflow.
Setup
Download the binary and disassemble it.
wget <url>/Forkychmod +x ForkySolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Read doStuff and count the forksObservationI noticed the challenge describes parent and child processes and a function called doStuff, which suggested the binary uses fork() to spawn multiple processes and that disassembling doStuff in Ghidra would reveal how many forks occur and what value each process contributes to the shared result.In Ghidra, doStuff sets up a shared integer (mmap with MAP_SHARED) initialized to 1000000000, then calls fork() four times in a nested fashion. Four forks produce 2^4 = 16 processes that all reach the final code path. Every one of those 16 processes adds the constant 0x499602d2 (1234567890 in decimal) to the shared integer.bashghidra Forky &bash# Find: shared int initialized to 1000000000, 4 nested fork() calls,bash# each surviving process does shared += 0x499602d2 before doNothing(shared).Expected output
-721750240
What didn't work first
Tried: Run ./Forky directly to observe the output and read the return value from there
Running the binary produces no useful printed output because doNothing discards the value silently. You see the process exit with code 0, giving you nothing to work with. You need to disassemble doStuff in Ghidra to find the shared-mapping setup, the fork count, and the constant being added.
Tried: Use 'strings Forky' to find the constant and count the fork calls
strings only surfaces printable ASCII sequences embedded in the binary. The integer constant 0x499602d2 is encoded as raw bytes in the instruction stream, not as a string, so it never appears in strings output. The fork call count is also invisible to strings - only disassembly reveals the nested control flow that produces 16 processes.
Learn more
fork()creates an exact copy of the calling process. In the parent it returns the child PID; in the child it returns 0. Crucially, after a fork both processes keep running the code that follows, so a singlefork()turns one process into two. Four nested forks turn one process into2^4 = 16.Because the integer is in a
MAP_SHAREDmapping, all 16 processes increment the same memory, not private copies. So the constant is added 16 times to the starting value.Step 2
Compute the result under 32-bit signed overflowObservationI noticed that 16 processes each adding 0x499602d2 to a starting value of 1000000000 produces a sum far exceeding the 32-bit signed integer maximum of 2147483647, which suggested the accumulator wraps around and must be interpreted as a signed 32-bit value to get the actual number the binary hands to doNothing.The accumulator is a 32-bit int. Compute 1000000000 + 16 * 0x499602d2, then take it modulo 2^32 and interpret as signed. The result is -721750240, which is the value the final process hands to doNothing.pythonpython3 - <<'PY' import ctypes total = 1000000000 + 16 * 0x499602d2 print(ctypes.c_int32(total).value) # -> -721750240 PYbash# Or confirm dynamically by following forks in gdb:bashgdb ./Forky -ex 'set follow-fork-mode child' \bash-ex 'dprintf *doNothing+19, "%d\n", $eax' -ex 'run' -ex 'quit'The flag for this challenge is literally that number wrapped in the picoCTF format. No decoding or extraction is needed once you have the signed 32-bit result.
What didn't work first
Tried: Submit the unwrapped sum 20753086240 as the flag without applying 32-bit overflow
Python integers are arbitrary precision, so '1000000000 + 16 * 1234567890' happily gives 20753086240 without any wrapping. That number is not the flag. The accumulator in the binary is a 32-bit signed int (4 bytes), so you must wrap it with ctypes.c_int32(total).value or equivalent to get the -721750240 value the real program stores.
Tried: Use 'set follow-fork-mode parent' in gdb to trace the shared value
With follow-fork-mode parent, gdb detaches from every child process the moment it is forked and continues tracing only the parent. The parent calls doNothing with the partially-accumulated value after each fork, not the final 16-process total. You need either follow-fork-mode child (which tracks the deepest surviving child through all four forks) or the static calculation to see the correct final value.
Learn more
Why the value goes negative. A 32-bit signed integer maxes out at
2147483647. The unwrapped sum here is1000000000 + 16 * 1234567890 = 20753086240, which overflows several times. Reducing modulo2^32and reading the top bit as the sign gives-721750240. This is the entire point of the challenge: recognize that the addition silently wraps.
Interactive tools
- Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
Flag
Reveal flag
picoCTF{-721750240}
Four nested fork() calls create 16 processes that each add 0x499602d2 to a shared integer starting at 1000000000. 1000000000 + 16*1234567890 wrapped to a signed 32-bit int is -721750240, which is the flag.