Dial Tone
189
You might need a pitch-perfect voice to solve this one. Once you crack the code, the flag is CTF{code}.
Challenge comes with an application that we must reverse. Running it gives an error:
1
2
$ ./a.out
FAILED
Let’s reverse it in Ghidra. The code in the main function is very simple:
- Open connection to PulseAudio stream
- Read data from it and process the data with functions
x
andr
- If the return value of
r
is greater than0
- keep reading and processing the data, if it is0
- finish successfully, and otherwise - fail
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
27
28
29
...
local_10 = pa_simple_new(0,*puParm2,2,0,"record",ss.3811,0,0,local_18);
if (local_10 == 0) {
uVar2 = pa_strerror((ulong)local_18[0]);
fprintf(stderr,"pa_simple_new() failed: %s\n",uVar2);
uVar2 = 1;
}
else {
local_24 = 0;
local_20 = 0;
local_1c = 0;
do {
puVar3 = local_18;
iVar1 = pa_simple_read(local_10,auStack163880,0x8000);
if (iVar1 < 0) {
uVar2 = pa_strerror((ulong)local_18[0]);
fprintf(stderr,"pa_simple_read() failed: %s\n",uVar2);
return 1;
}
x(auStack163880,auStack131112,auStack131112);
r(&local_24,auStack131112,(int)auStack131112,(char *)puVar3,(int)pcVar4,(int)puVar5);
if (extraout_EAX < 0) {
fwrite("FAILED\n",1,7,stderr);
return 1;
}
} while (extraout_EAX != 0);
fwrite("SUCCESS\n",1,8,stderr);
pa_simple_free(local_10);
...
Instead of doing full analysis of how the data is processed let’s take a look at r
. Luckily there are clues there that will help us.
Closer inspection shows that the data is processed using f
function with values like 0x4b9
and 0x538
passed to it.
As it turns out these values are frequency values for tone dialing keys - 0x4b9
is 1209 Hz
, 0x538
is 1336 Hz
, and so on:
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
...
local_58[0] = (double)f(param_2,0x4b9);
local_58[1] = f(param_2,0x538);
local_58[2] = f(param_2,0x5c5);
local_58[3] = f(param_2,0x661);
local_c = 0xffffffff;
local_18 = 1.00000000;
local_1c = 0;
while ((int)local_1c < 4) {
if (local_18 < local_58[(long)(int)local_1c]) {
local_c = local_1c;
local_18 = local_58[(long)(int)local_1c];
}
local_1c = local_1c + 1;
}
local_78[0] = (double)f(param_2,0x2b9);
local_78[1] = f(param_2,0x302);
local_78[2] = f(param_2,0x354);
f(param_2,0x3ad);
local_20 = -1;
local_28 = 1.00000000;
local_2c = 0;
while (local_2c < 4) {
if (local_28 < local_78[(long)local_2c]) {
local_20 = local_2c;
local_28 = local_78[(long)local_2c];
}
local_2c = local_2c + 1;
}
if (*(char *)((long)param_1 + 8) == '\0') {
if ((-1 < (int)local_c) && (-1 < local_20)) {
local_c = local_20 << 2 | local_c;
bVar1 = false;
switch(*(undefined4 *)((long)param_1 + 4)) {
case 0:
bVar1 = local_c == 9;
break;
case 1:
bVar1 = local_c == 5;
break;
case 2:
bVar1 = local_c == 10;
break;
case 3:
bVar1 = local_c == 6;
break;
case 4:
bVar1 = local_c == 9;
break;
case 5:
bVar1 = local_c == 8;
break;
case 6:
bVar1 = local_c == 1;
break;
case 7:
bVar1 = local_c == 0xd;
break;
case 8:
if (local_c == 0) {
return;
}
}
...
The frequency value positions are then encoded in 4 bits of a state byte - low tone is the upper 2 bits, and high tone in the lower 2 bits. The state byte is checked in a switch statement, which is easy to decode:
9
(0b1001
) -852 Hz
(position2
(0b10
)) and1336 Hz
(position1
(0b01
)) -Key 8
5
(0b0101
) -770 Hz
(position1
(0b01
)) and1336 Hz
(position1
(0b01
)) -Key 5
- …and so forth
The full key sequence decodes to 859687201
and the flag is CTF{859687201}
.