CTF challenges can be intimidating for beginners, especially those without much technical background. This is a step-by-step guide to the IrisCTF 2024 challenge “What the Beep”, aiming to show both the thought process and the details of how I solved it, and how you can too, even without a lot of prior knowledge.
Skills Required
- Googling
- Use of web applications
- Some algebra
- Basic Python scripting
First Look
Challenge Description
A strange beep sound was heard across a part of the San Joaquin Valley. We have the records from some audio volume meters at various locations nearby that picked up this event. It’s understood that the original sound was about 140 dB at the source, but can you find out where it originated from?
Let’s download the attached file and see what it has…
“Wait, the file has a weird extension .tar.gz
. How do I
even open it?”
Tip
Whenever you encounter something you’ve never seen before, just look it up.
Ok, let’s see what we get inside the folder.
The names of the HTML files resemble GPS coordinates, and if we open one of them in a browser it shows a graph as follows:
As the x and y axis have units of time (\(\text{s}\)) and decibel (\(\text{dB}\)) respectively, it’s reasonable to assume that the graph shows the sound level at the corresponding time, in which there is a peak in loudness for about 2 seconds, at around \(50 \text{dB}\). That matches the “loud beep” description, and the attached audio file:
Let’s wrap up what information we have so far:
- A recording of a loud beep
- Four pairs of GPS coordinates, each with a graph of sound level over time
From the above, we can infer that the challenge is about finding the location of the sound, given the intensity of the sound recorded at different locations.
The Approach
To clear things up, let’s sketch a diagram of the situation:
Tip
Diagrams are always helpful, especially when you are stuck.
If we worked out \(r_A\) to \(r_D\), the distance between the sound source and each of the four locations, the location of the source could be easily found by drawing four circles around \(A\) to \(D\) with radii \(r_A\) to \(r_D\) respectively, and look for their intersection:
This graphical method is simple and intuitive, as it avoids solving for the coordinates of the source by hand, but it’s not very accurate. However it suits our purpose as we only need to find the approximate location of the sound source.
Physics Comes In Handy
Now that we only have the sound intensity, we need a way to work out the distance between the source and each location. We all know we can judge a distance simply by the loudness of a sound, but how do we actually calculate it?
That might sound familiar to you if you took high school physics, as it’s the inverse square law:
\[\left(\frac{r_2}{r_1}\right)^2 = \frac{I_1}{I_2}\]
where \(I_1\) is the intensity at distance \(r_1\), and \(r_2\) is the distance at which \(I_2\) is measured. Note how the fraction on the right hand side is inverted, as it involves an inversely proportional relationship.
The equation relates the intensity of sound at two different points with their distances to the source. In our case, \(I_2\) and \(r_2\) represents the intensity and distance at \(A\) to \(D\), and \(I_1\) and \(r_1\) is what we get from a known reference point. Where can we find such a reference point?
Perfect! Now just set \(I_1\) to \(140 \text{dB}\), and \(r_1\) to \(1 \ \text{ft}\)… wait. That’s not how it works. The decibel scale is logarithmic and relative to a threshold \(I_0\), defined by: \[ n = 10 \log_{10} \left(\frac{I}{I_0}\right) \] where \(n\) is the sound intensity in \(\text{dB}\). so we need to convert it to a linear scale first: \[\frac{I}{I_0} = 10^{n/10}\]
Luckily for us, we’re only interested in the ratio of the sound intensity at different locations, so we can just ignore the \(I_0\) term and work it out directly: \[\frac{I_1}{I_2} = \cfrac{\cfrac{I_1}{I_0}}{\cfrac{I_2}{I_0}} = \cfrac{10^{{n_1}/{10}}}{10^{{n_2}/{10}}} = 10^{(n_1 - n_2)/10} \]
Intuitively, an increase of \(10 \text{dB}\) means multiplying the intensity by 10. So to find out the ratio between two different intensities \({I_1}\) and \({I_2}\), we figure out how many times we have to add \(10\) to get from \(n_2\) to \(n_1\), then raise \(10\) to that power.
To sum up, if we were to find distance \(r_A\): \[r_A = r_1 \sqrt{10^{(n_1 - n_A)/10}} = (1 \ \text{ft}) \cdot \sqrt{10^{(140 - n_A)/10}}\]
where \(n_A\) is the intensity at \(A\), measured in \(\text{dB}\).
Obtaining the Data
Now our task is to extract the exact sound level from the graphs. But here’s a small problem: we get little bumps on the curve during the 2-second beep:
There’s not only one number, but a range of numbers. So we have to approximate the sound level instead, as accurately as possible. The best way I can think of is to take the average of the data points.
And rather than copying off the values one by one with the mouse, did you notice how the graphs are interactive? The actual data must be stored somewhere in the HTML file, and we can inspect the page to find out where:
Note how the data is stored in the form of JavaScript arrays. We can just copy the array and paste it into a text editor, and treat it like Python lists:
= [49.4862687673082, 49.11758306247154, 49.35891737763439, 49.60312825002279, 49.28094240869986, 49.33179344636332, 49.77218278810612, 49.33157050295794, 49.954163292100134, 49.46399894576454, 49.75225513933776, 49.51956668498062, 49.72709095876894, 49.08380931815951, 49.80535732712877, 49.466366411374636, 49.272738443513475, 49.537197963188916, 49.42320370510891, 49.324671083447626, 49.54118211146326, 49.49531460381351, 49.976621634496894, 49.3893728063094, 49.921942150468716, 49.19386224160513, 49.36279881936855, 49.3589213415847, 49.56066713691474, 49.12186675176159, 49.98362703411643, 49.52541697547485, 49.35868710209489, 49.43923653057155, 49.98372751405347, 49.28405742162781, 49.401207574823644, 49.01614674667963, 49.13219547793331, 49.64847624718398, 49.498071028322336, 49.334549685095496, 49.458331325541025, 49.16635204000964, 49.2845016923542, 49.04043406000734, 49.911997928476055, 49.522384277676096, 49.63639242519472, 49.5321507012455, 49.580222157005686, 49.462799630990716, 49.15286264591634, 49.5636105290103, 49.24446101814839, 49.17815265294301, 49.277052087309045, 49.34785136315813, 49.2099358209713, 49.18130715442975, 49.81637365701671, 49.58976121006631, 49.26447327335997, 49.07489408373105, 49.10738248956828, 49.82935754558414, 49.7076592827515, 49.56229242462191, 49.67051905124946, 49.042312629812045, 49.561770092276326, 49.66475069029362, 49.858494354189034, 49.048272583835754, 49.9132487579282, 49.71779824360189, 49.79452312717411, 49.50065500658594, 49.84834211295007, 49.220394666568154, 49.66254149768159, 49.83640438670091, 49.10061336144564, 49.42849201280895, 49.646915124964735, 49.78950547033567, 49.4929685819846, 49.73705541695538, 49.22359955303929, 49.79862536749438, 49.652865340678765, 49.066372510572236, 49.19935726756466, 49.12145308818689, 49.438711940843866, 49.004099870912, 49.502682207162174, 49.293165246893956, 49.112557507785844, 49.544065615449895,
data_a 49.663019552626615, 49.46132309525862, 49.28867460561771, 49.04716798758809, 49.35484951313734, 49.37733257790768, 49.84822901146003, 49.81145708386574, 49.88943707227456, 49.8760994755179, 48.99312519891967, 49.48120050185433, 49.52537947160789, 49.90610721676662, 49.914515091218576, 49.416331830579196, 49.348693840298814, 49.545231061032965, 49.114561921757456, 49.026512427769646, 49.14711681989299, 49.77105573603577, 49.536523596883534, 49.60021444492543, 49.60605081543197, 49.64891471841797, 49.4600478177719, 49.977585356380565, 49.64790786367474, 49.05339723365599, 49.776801982915465, 49.345914994020035, 49.460170041286936, 49.458597510753314, 49.404020334240904, 49.80110610568392, 49.92580226915928, 49.892740295161424, 49.32725220374159, 49.14689359709007, 49.272749076596895, 49.77397438750593, 49.092844212042216, 49.29759302412302, 49.44741729129354, 49.41099308272467, 49.079825857328835, 49.386676414641016, 49.10972967558096, 49.043040950598254, 49.11993424749808, 48.99318353627927, 49.10530939042136, 49.21146252088831, 49.15074800916907, 49.61678542581952, 49.35038069687581, 49.03078805691796, 49.6258955230806, 49.63094191644237, 49.47515877815869, 49.26668175133948, 49.31472885646965, 49.640732134272305, 49.228802255830445, 49.59159486655283, 49.06310688917667, 49.49737416549353, 49.97771220245058, 49.636406874411215, 49.173004449388536, 49.33266160439406, 49.014913705550754, 49.40246086899188, 49.329105230947825, 49.86540375321602, 49.843613528504285, 49.8657191427391, 49.219049611824765, 49.57851447868852, 49.98526126350094, 49.94445651733331, 49.45467432812931, 49.76296756625244, 49.7935233989949, 49.83262284962718, 49.93995818802516, 49.67306551801829, 49.54206148681675, 49.31504612258427, 49.94311396484975, 49.94210883611035, 49.11875893744185, 49.46127955948076, 49.7357488917027, 49.878480551044404, 49.897911048398925, 49.58542961303054, 49.59108511515634, 49.31169509848393]
I only extracted the numbers close to 50, since that’s the relevant part of the graph. Now we can work out the distance \(r_A\) in feet:
import math
= sum(data_a) / len(data_a)
avg_n_a = 1 * math.sqrt(10 ** ((140 - avg_n_a) / 10))
r_a print(r_a)
That would give us 33573.94482372866
feet (around 10
km), which looks reasonable.
Same goes for \(r_B\), \(r_C\) and \(r_D\). Try working them out yourself!
Tip
Work the smart way, not the hard way. Perhaps let your machine do the job for you?
Putting It All Together
Now that we have the coordinates and distances, all that’s left is to draw the circles on a map… is there any convenient map application that allows us to draw circles?
There we are (I have no idea why this exists). Let’s try to use it, pasting in the coordinates and distances…
Ooh, there’s an intersection! But one of the circles looks off. What went wrong? Let’s click on the circle around \(A\):
The coordinates shown for the circle is entirely different from what
we entered (37.185287, -120.292548
)! How do we manually
correct it?
We see a generated URL for the created map, in a text box below:
which contains the wrong coordinates of the circle. Now let’s do a little hack, replacing the numbers with the correct ones:
...%2C37.185287%2C-120.292548%2C...
and finally pasting it into the address bar:
Voila! The circles now (nearly) intersect at a single point. Pick a point best approximating the location of the sound source, and we’re done!
Where do we submit our answer? Let’s look at the challenge description again:
“What’s the answer checker service? And what about
nc what-the-beep.chal.irisc.tf 10500
?”
I’ll give you a hint: nc
is short for Netcat.
… no trick questions here, don’t worry. Just follow the instructions and the sacred line of text you’ve been craving for shall reveal itself:
Tip
If something is not working right, don’t complain. Hack your way around it.
TL;DR Solution
- Extract the average sound intensity from the graphs, for each location
- Use the inverse square law to calculate the distance between the sound source and each location
- Draw circles on a map with the coordinates and distances
- Find the intersection of the circles, and submit the coordinates to the answer checker