August 11, 2026
Investigative Reversing 0 & 1 — When Reverse Engineering Meets Forensics
PicoCTF — Investigative Reversing 0 & Investigative Reversing 1

By Mohamed Sameh
6 min read
PicoCTF — Investigative Reversing 0 & Investigative Reversing 1
When we see a challenge labeled Reverse Engineering, we usually expect everything we need to be inside the binary.
Open the executable, perform some static analysis, understand the logic, and recover the flag.
But Investigative Reversing 0 and 1 take a slightly different approach.
The binary is not the whole story.
Instead, the binary tells us what happened to the flag, while the images contain the actual data produced by those operations.
This means that if you completely ignore the images and focus only on the binary, you may understand the entire algorithm and still be unable to recover the flag.
And that's where some basic Digital Forensics skills become essential.
The Big Picture
The general idea behind both challenges can be summarized like this:
Challenge Files
│
┌─────────┴─────────┐
│ │
Image Binary
│ │
│ "What happened?"
│ │
▼ ▼
Extract Data Analyze Logic
│ │
└─────────┬─────────┘
▼
Reverse Process
│
▼
FLAGChallenge Files
│
┌─────────┴─────────┐
│ │
Image Binary
│ │
│ "What happened?"
│ │
▼ ▼
Extract Data Analyze Logic
│ │
└─────────┬─────────┘
▼
Reverse Process
│
▼
FLAGThe binary tells us about the transformation or mapping.
The image contains the output data that we need to reverse.
This is the key idea to keep in mind throughout both challenges.
Investigative Reversing 0
First Look
The challenge provided an image along with a binary file associated with it.
Instead of immediately opening the binary in Ghidra or IDA, it made sense to first ask:
Is there something hidden inside the image?
This is where the forensics part begins.
You can use tools like
hexdump,xxd,binwalk, orobjdump(or GUI editors like HxD on Windows). By inspecting the raw hex data of the image, especially toward the end of the file, you can directly spot the obfuscated flag bytes appended to it
Investigating the Image
I started by examining the image and checking whether it contained any embedded or appended data.
The goal wasn't to perform anything particularly complicated.
I was simply looking for things such as:
- Hidden files
- Appended data
- Embedded files
- Interesting strings
- Binary data that could be extracted
After investigating the image, we found a binary that could be analyzed.
Now we can move on to the Reverse Engineering part.
Analyzing the Binary
After extracting the binary, I used objdump to inspect its contents and look for the logic responsible for processing the flag.
Among the strings we could find was:
picoCTK.k5zsid6q_35f69dab}picoCTK.k5zsid6q_35f69dab}At first glance, this looks very close to a flag:
picoCTK...picoCTK...But something is clearly wrong.
A normal PicoCTF flag should look more like:
picoCTF{...}picoCTF{...}rather than:
picoCTK...picoCTK...This strongly suggests that the string has been transformed.
Finding the Transformation
After analyzing the binary, we found the important section:
putc(ptr[0], v8);
fputc(ptr[1], v8);
fputc(ptr[2], v8);
fputc(ptr[3], v8);
fputc(ptr[4], v8);
fputc(ptr[5], v8);
for (i = 6; i <= 14; ++i)
fputc((char)(ptr[i] + 5), v8);
fputc((char)(ptr[15] - 3), v8);
for (j = 16; j <= 25; ++j)
fputc((char)ptr[j], v8);putc(ptr[0], v8);
fputc(ptr[1], v8);
fputc(ptr[2], v8);
fputc(ptr[3], v8);
fputc(ptr[4], v8);
fputc(ptr[5], v8);
for (i = 6; i <= 14; ++i)
fputc((char)(ptr[i] + 5), v8);
fputc((char)(ptr[15] - 3), v8);
for (j = 16; j <= 25; ++j)
fputc((char)ptr[j], v8);We don't need to understand every instruction in the binary.
We only need to track what happens to each character.
Index 0 → 5
These characters are written without modification:
0 → unchanged
1 → unchanged
2 → unchanged
3 → unchanged
4 → unchanged
5 → unchanged0 → unchanged
1 → unchanged
2 → unchanged
3 → unchanged
4 → unchanged
5 → unchangedIndex 6 → 14
The binary performs:
ptr[i] + 5ptr[i] + 5So if we already have the transformed output and want to recover the original value, we simply reverse the operation:
Encoded - 5 = OriginalEncoded - 5 = OriginalIndex 15
The binary performs:
ptr[15] - 3ptr[15] - 3Therefore the reverse operation is:
Encoded + 3 = OriginalEncoded + 3 = OriginalIndex 16 → 25
These characters are written unchanged.
Writing the Solver
Once the transformation is understood, we can write a small Python solver:
flag = "picoCTK.k5zsid6q_35f69dab}"
for i in range(len(flag)):
if i <= 5:
print(flag[i], end="")
elif 6 <= i <= 14:
print(chr(ord(flag[i]) - 5), end="")
elif i == 15:
print(chr(ord(flag[i]) + 3), end="")
else:
print(flag[i], end="")flag = "picoCTK.k5zsid6q_35f69dab}"
for i in range(len(flag)):
if i <= 5:
print(flag[i], end="")
elif 6 <= i <= 14:
print(chr(ord(flag[i]) - 5), end="")
elif i == 15:
print(chr(ord(flag[i]) + 3), end="")
else:
print(flag[i], end="")The important part is that we're not guessing the flag.
We're simply reversing the exact operations performed by the binary.
if you run this code you will get the flag .
An Important Detail
There is an important lesson hidden in this challenge.
The binary itself did not simply contain the final flag.
It contained the logic needed to understand how the data had been transformed.
The actual data came from the image.
So the challenge is better viewed as a combination of:
Forensics + Reverse EngineeringForensics + Reverse EngineeringIf you completely ignored the image, you would be missing the data required to perform the reverse transformation.
Investigative Reversing 1
The second challenge follows the same general idea, but makes the process more interesting.
This time, the flag is not simply transformed.
The binary distributes different parts of the flag across three different images/files.
out_flag[]
│
┌────────────┼────────────┐
▼ ▼ ▼
image1 image2 image3out_flag[]
│
┌────────────┼────────────┐
▼ ▼ ▼
image1 image2 image3So now we have two separate problems:
- Determine where each character went.
- Determine which characters were modified.
Investigating the Images
After examining the challenge files, we found multiple images.
At first, their contents may look like unrelated pieces of data:
image1 → CF{An1_b044e3b4}
image2 → [binary data]
image3 → icT0tha_image1 → CF{An1_b044e3b4}
image2 → [binary data]
image3 → icT0tha_The important thing is not to assume that any of these strings is the flag.
They are simply pieces of the transformed data.
Once again:
The images contain the output, while the binary tells us how that output was generated.
Analyzing the Binary
The important section of the binary is:
v7 = out_flag[0];
fputc(out_flag[1], image3);
fputc((v7 + 21), image2);
fputc(out_flag[2], image3);
v5 = out_flag[3];
v6 = out_flag[4];
fputc(out_flag[5], image3);
fputc(v6, image1);
for (i = 6; i <= 9; ++i)
{
++v5;
fputc(out_flag[i], image1);
}
fputc(v5, image2);
for (j = 10; j <= 14; ++j)
fputc(out_flag[j], image3);
for (k = 15; k <= 25; ++k)
fputc(out_flag[k], image1);v7 = out_flag[0];
fputc(out_flag[1], image3);
fputc((v7 + 21), image2);
fputc(out_flag[2], image3);
v5 = out_flag[3];
v6 = out_flag[4];
fputc(out_flag[5], image3);
fputc(v6, image1);
for (i = 6; i <= 9; ++i)
{
++v5;
fputc(out_flag[i], image1);
}
fputc(v5, image2);
for (j = 10; j <= 14; ++j)
fputc(out_flag[j], image3);
for (k = 15; k <= 25; ++k)
fputc(out_flag[k], image1);The easiest way to understand this is to track every out_flag[index].
Analyzing image3
The binary first writes:
out_flag[1]
out_flag[2]
out_flag[5]out_flag[1]
out_flag[2]
out_flag[5]Then:
out_flag[10]
out_flag[11]
out_flag[12]
out_flag[13]
out_flag[14]out_flag[10]
out_flag[11]
out_flag[12]
out_flag[13]
out_flag[14]Therefore:
image3:
1, 2, 5, 10, 11, 12, 13, 14image3:
1, 2, 5, 10, 11, 12, 13, 14Its contents were:
icT0tha_icT0tha_Analyzing image1
The binary first writes:
out_flag[4]out_flag[4]Then:
out_flag[6]
out_flag[7]
out_flag[8]
out_flag[9]out_flag[6]
out_flag[7]
out_flag[8]
out_flag[9]And finally:
out_flag[15]
...
out_flag[25]out_flag[15]
...
out_flag[25]So:
image1:
4
6 → 9
15 → 25image1:
4
6 → 9
15 → 25Its contents were:
CF{An1_b044e3b4}CF{An1_b044e3b4}Analyzing image2 — The Interesting Part
This is where the actual transformation happens.
First:
v7 = out_flag[0];
fputc((v7 + 21), image2);v7 = out_flag[0];
fputc((v7 + 21), image2);Therefore:
image2[0] = out_flag[0] + 21image2[0] = out_flag[0] + 21To reverse it:
out_flag[0] = image2[0] - 21out_flag[0] = image2[0] - 21The Second Transformation
We also have:
v5 = out_flag[3];v5 = out_flag[3];Then:
for (i = 6; i <= 9; ++i)
{
++v5;
...
}for (i = 6; i <= 9; ++i)
{
++v5;
...
}The loop runs four times:
i = 6
i = 7
i = 8
i = 9i = 6
i = 7
i = 8
i = 9Therefore:
v5 = out_flag[3] + 4v5 = out_flag[3] + 4Then:
fputc(v5, image2);fputc(v5, image2);So:
image2[1] = out_flag[3] + 4image2[1] = out_flag[3] + 4Reversing it gives:
out_flag[3] = image2[1] - 4out_flag[3] = image2[1] - 4The Raw Byte Trap
This was one of the interesting parts of the challenge.
When image2 was viewed as text, it could appear something like:
.s.sIt would be tempting to write:
image2 = ".s"image2 = ".s"But that is not the correct representation of the underlying data.
The binary works with raw bytes, not just printable characters.
We know the final flag starts with:
ppThe ASCII value of p is:
112112The binary performs:
112 + 21 = 133112 + 21 = 133Therefore, the first byte stored in image2 is:
133133or:
0x850x85It is not actually the . character.
This also explains why:
chr(ord('.') - 21)chr(ord('.') - 21)doesn't print p.
Because:
ord('.') = 46
46 - 21 = 25ord('.') = 46
46 - 21 = 25And chr(25) is a non-printable control character.
This is an important Digital Forensics lesson:
When dealing with binary files, not every byte represents a printable character.
Reconstructing the Flag
After analyzing both the binary and the images, we can build the following mapping:
Index Source
──────────────────────────────
0 image2[0] - 21
1 image3[0]
2 image3[1]
3 image2[1] - 4
4 image1[0]
5 image3[2]
6 image1[1]
7 image1[2]
8 image1[3]
9 image1[4]
10 image3[3]
11 image3[4]
12 image3[5]
13 image3[6]
14 image3[7]
15 image1[5]
...
25 image1[15]Index Source
──────────────────────────────
0 image2[0] - 21
1 image3[0]
2 image3[1]
3 image2[1] - 4
4 image1[0]
5 image3[2]
6 image1[1]
7 image1[2]
8 image1[3]
9 image1[4]
10 image3[3]
11 image3[4]
12 image3[5]
13 image3[6]
14 image3[7]
15 image1[5]
...
25 image1[15]At this point, the challenge is basically a reconstruction problem.
We need to take the scattered pieces, reverse the transformations, and place every character back into its original position.
Python Solver
We can translate the mapping into Python:
image1 = 'CF{An1_b044e3b4}'
image2 = bytes([133, ord('s')]) # look here
image3 = 'icT0tha_'
image1 = 'CF{An1_b044e3b4}'
image2 = bytes([133, ord('s')])
image3 = 'icT0tha_'
flag = ''
flag += chr(image2[0] - 21)
flag += image3[0]
flag += image3[1]
flag += chr(image2[1] - 4)
flag += image1[0]
flag += image3[2]
flag += image1[1]
flag += image1[2]
flag += image1[3]
flag += image1[4]
flag += image3[3]
flag += image3[4]
flag += image3[5]
flag += image3[6]
flag += image3[7]
for i in range(5, len(image1)):
flag += image1[i]
print(flag)image1 = 'CF{An1_b044e3b4}'
image2 = bytes([133, ord('s')]) # look here
image3 = 'icT0tha_'
image1 = 'CF{An1_b044e3b4}'
image2 = bytes([133, ord('s')])
image3 = 'icT0tha_'
flag = ''
flag += chr(image2[0] - 21)
flag += image3[0]
flag += image3[1]
flag += chr(image2[1] - 4)
flag += image1[0]
flag += image3[2]
flag += image1[1]
flag += image1[2]
flag += image1[3]
flag += image1[4]
flag += image3[3]
flag += image3[4]
flag += image3[5]
flag += image3[6]
flag += image3[7]
for i in range(5, len(image1)):
flag += image1[i]
print(flag)The output now is our flag
What Makes These Challenges Interesting?
What makes Investigative Reversing different from traditional Reverse Engineering is that the binary alone is not enough.
For the first challenge:
Image
↓
Hidden Binary
↓
Analyze Transformation
↓
Use Image Data
↓
Reverse Transformation
↓
FlagImage
↓
Hidden Binary
↓
Analyze Transformation
↓
Use Image Data
↓
Reverse Transformation
↓
FlagFor the second:
Images
/ | \
image1 image2 image3
\ | /
\ | /
Binary
↓
Understand Mapping
↓
Reverse Transformations
↓
Reconstruct FlagImages
/ | \
image1 image2 image3
\ | /
\ | /
Binary
↓
Understand Mapping
↓
Reverse Transformations
↓
Reconstruct FlagThis makes the challenges a combination of:
Forensics
+
Reverse Engineering
+
Basic ScriptingForensics
+
Reverse Engineering
+
Basic ScriptingLessons Learned
1. Don't Trust Interesting Strings
Finding something like:
picoCTK...picoCTK...doesn't necessarily mean you've found the flag.
Always ask:
How was this string generated?
2. Use the Binary to Understand the Algorithm
The binary may not contain the final answer.
Instead, it may contain something more useful:
The transformation logic.The transformation logic.Once we understand that logic, we can reverse it.
3. Treat Files as Bytes
In Digital Forensics, a byte can be:
Printable CharacterPrintable Characteror:
Non-printable ByteNon-printable ByteFor example:
0x850x85may not display as a normal character at all.
This is why inspecting raw bytes with tools such as xxd or hexdump can be important.
4. Don't Ignore the Challenge Files
In these challenges, the images are not decoration.
They are part of the data.
If you say:
"I'll just focus on the binary."
you have effectively ignored half of the challenge.
Final Takeaway
The two challenges demonstrate a simple but very useful CTF lesson:
Sometimes the binary tells you how the data was created, while the artifact contains the data itself.
So when facing a similar challenge, don't only reverse the binary.
Investigate everything:
📁 Files
🖼️ Images
🔢 Raw Bytes
🔍 Strings
💻 Binary
🧠 Program Logic📁 Files
🖼️ Images
🔢 Raw Bytes
🔍 Strings
💻 Binary
🧠 Program LogicThen connect all of these pieces together.
Because sometimes the flag isn't hidden in one place.
You have to reconstruct it yourself.