Challenge: Playing with Pointers · CTF: DalCTF 2026 · Category: Crypto · Difficulty: Easy

I watched a youtube video earlier showing off this funny trick you can do with pointers. I forget what it’s called though. Maybe I’ll go play some Quake to think about it.

That little flavor-text is the whole challenge. Hold onto the word Quake — it’s not decoration, it’s the hint.


The Files

We’re handed two things: a C source file and a text file full of suspiciously large numbers.

Playingwithpointers.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>

int main() {
	char FLAG[] = "DalCTF{test}";
	


	float fflag[sizeof(FLAG)];
	long lflag[sizeof(fflag)];

	int x = sizeof(FLAG);

	for(int i=0;i<x;i++){
	fflag[i] = (float) FLAG[i];
	fflag[i] = fflag[i] * fflag[i];
	// man I forgot what line needs to go here... Maybe I should play some quake to think about it
	}

	x = x - 1;

	for(int i=0;i<x;i++){
	printf("\n%d", lflag[i]);
	};

	return 0;
}

playing.txt

1
2
3
4
5
6
1167097856
1175651328
1177960448
1166821376
1172078592
...

Thirty lines, each a 32-bit-ish integer hovering around 1.1–1.2 billion.


Reading the Code

Let’s trace what the program actually does, line by line.

  1. char FLAG[] holds the real flag (here stubbed out as "DalCTF{test}" so the source doesn’t give it away).
  2. float fflag[...] is an array of floats.
  3. long lflag[...] is an array of longs.
  4. The loop walks every character of the flag and does two things:
    • fflag[i] = (float) FLAG[i]; — convert the character’s ASCII code to a floating-point number. So 'D' (68) becomes 68.0f.
    • fflag[i] = fflag[i] * fflag[i]; — square it. 68.0f * 68.0f = 4624.0f.
  5. Then there’s a missing line marked by the comment, and…
  6. The second loop prints lflag[i] — the long array — as integers.

Here’s the catch that makes this a puzzle: lflag is never written to. The squared values all go into fflag (the float array), but the program prints lflag (the long array). As written, this code would just print uninitialized garbage.

So the missing line — the one the author “forgot” — has to be the bridge that moves data from fflag into lflag. And the comment tells us exactly how to find it:

Maybe I should play some quake to think about it.


The Quake Connection

The most famous “funny trick you can do with pointers” in all of programming comes from the source code of Quake III Arena: the fast inverse square root. Its legendary line is:

1
i = * ( long * ) &y;   // evil floating point bit level hacking

That snippet does something delightfully cursed: it takes the address of a float (&y), reinterprets that address as a pointer to a long ((long *)), and dereferences it (*). The result is that you read the raw bit pattern of the float as if it were an integer — no conversion, no rounding, just the underlying IEEE-754 bytes reinterpreted.

That’s the missing line. Filled in, the loop body is:

1
2
3
fflag[i] = (float) FLAG[i];
fflag[i] = fflag[i] * fflag[i];
lflag[i] = * ( long * ) &fflag[i];   // <-- the "forgotten" Quake line

So each number in playing.txt is the IEEE-754 bit representation of (char × char) stored as a 32-bit float, then printed as an integer.

That also explains why the numbers are around 1.1 billion. A small positive float like 4624.0 has an exponent that lands its bit pattern in exactly that range when read as an int.


Reversing It

To get the flag back, we just run the pipeline backwards. For each integer n in playing.txt:

  1. Pack n as a 32-bit integer to get its raw 4 bytes.
  2. Unpack those same bytes as a 32-bit float — this is the type-pun in reverse, recovering the squared value (e.g. 4624.0).
  3. Square-root it to undo the fflag[i] * fflag[i] step (sqrt(4624) = 68).
  4. Round and convert to a character (68 → 'D').

A quick note on why we use a 32-bit float and int even though the C source says long: the printed values fit in 32 bits and were produced from a 4-byte float. The long/float size mismatch is part of the trap in the original code, but the bit pattern that was actually printed is the 32-bit float reinterpretation, so that’s what we decode.

Here’s the solve script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import struct

out = []
for line in open('playing.txt'):
    line = line.strip()
    if not line:
        continue
    n = int(line)
    # reinterpret the integer's bits as a 32-bit float
    f = struct.unpack('f', struct.pack('I', n))[0]
    # undo the squaring, round to nearest ASCII code
    c = round(f ** 0.5)
    out.append(chr(c))

print(''.join(out))

Running it:

1
2
$ python3 solve.py
DalCTF{s0m3_fUn_w17h_P01n73r5}

The Flag

1
DalCTF{s0m3_fUn_w17h_P01n73r5}

Takeaways

  • Read the flavor text. “Quake” + “trick with pointers” is a direct pointer (pun intended) to the fast inverse square root and its *(long*)&y type-pun.
  • Spot the dead variable. The squared values went into fflag, but the program printed lflag. The only way that makes sense is if a line copies bits across — and the kind of copy (reinterpret vs. cast) is what the whole challenge hinges on.
  • Type-punning ≠ casting. A normal cast (long)fflag[i] would have given 4624. Reinterpreting the bits with *(long*)&fflag[i] gives 1166821376. The challenge lives entirely in that distinction.
  • Reversing was straightforward once the transform was understood: int bits → float → sqrt → char.

A clean little 50-pointer that rewards recognizing one of the most famous lines of C ever written.