“You might need a pitch-perfect voice to solve this one. Once you crack the code, the flag is CTF{code}.”

## Initial thoughts

Unlike other challenges we probably need to find a ‘code’ which will be part of the flag.

Dialtone sounds like something related to telecommunications, and the description probably verifies it.

## Overview

We are given an attachment called “a.out” which is a X86_64 ELF binary.

Taking a brief look at the ELF we notice that in addition to imports from standard libraries we also see imports from `libpulse.so`

and `libpulse-simple.so`

which are libraries of a sound server software called PulseAudio.

## Functions

### main()

```
pa_conn = pa_simple_new(0, *argv, 2, 0, "record", &g_pa_struc,0, 0, &error);
if (!pa_conn) {
fprintf(stderr, "pa_simple_new() failed: %s\n", pa_strerror(error));
return 1;
}
struc.field_0 = 0;
struc.field_4 = 0;
struc.field_8 = 0;
do {
if (pa_simple_read(pa_conn, buff, 0x8000, &error) < 0) {
fprintf(stderr, "pa_simple_read() failed: %s\n", pa_strerror(error));
return 1;
}
x(buff, &derived_buff);
r_result = r(&struc, derived_buff);
if (r_result < 0) {
fwrite("FAILED\n", 1, 7, stderr);
return 1;
}
} while (r_result);
fwrite("SUCCESS\n", 1, 8, stderr);
pa_simple_free(pa_conn, 1);
return 0;
```

### The plan

We probably want “SUCCESS” to be printed instead of “FAILED”.

Thus we want to make r() return 0 at some point, and avoid negative return values.

Find sinks of our data input to see what’s going on.

### x()

```
bit_flip(buff, derived_buff);
for (i = 1; i < 14; i++) {
tmp = 1 << i;
for (j = 0; j < 0x2000; j += tmp) {
y(derived_buff, j, tmp);
}
}
```

This function and the functions down this path seem to not modify the previously read 0x8000 bytes.

Instead it reads our input to produce some other data into `derived_buff`

which is later passed on to `r()`

.

After verifying that input is only read to write something out we decided to skip over most of the code down this path thinking it may be interesting later when we want to know more about how `derived_buff`

is created.

### r()

```
int __fastcall f(struct_0* p_struc, void* derived_buff) {
++p_struc->field_0;
if (p_struc->field_0 > 20) {
return -1;
}
arr_1[0] = f(derived_buff, 1209);
arr_1[1] = f(derived_buff, 1336);
arr_1[2] = f(derived_buff, 1477);
arr_1[3] = f(derived_buff, 1633);
chosen_arr_1_idx = -1;
tmp_arr_1_val = 1.0;
for (i = 0; i < 4; i++) {
if (arr_1[i] > tmp_arr_1_val) {
chosen_arr_1_idx = i;
tmp_arr_1_val = arr_1[i];
}
}
arr_2[0] = f(derived_buff, 697);
arr_2[1] = f(derived_buff, 770);
arr_2[2] = f(derived_buff, 852);
arr_2[3] = f(derived_buff, 941);
chosen_arr_2_idx = -1;
tmp_arr_2_val = 1.0;
for (j = 0; j < 4; j++) {
if (arr_2[j] > tmp_arr_2_val) {
chosen_arr_2_idx = j;
tmp_arr_2_val = arr_2[j];
}
}
if (p_struc->field_8) {
if (chosen_arr_1_idx < 0 && chosen_arr_2_idx < 0) {
p_struc->field_8 = 0;
p_struc->field_0 = 0;
}
return 1;
}
if (chosen_arr_1_idx >= 0 && chosen_arr_2_idx >= 0) {
y = chosen_arr_1_idx | 4 * chosen_arr_2_idx;
found_match = 0;
switch (p_struc->field_4) {
case 0:
found_match = y == 9;
break;
case 1:
found_match = y == 5;
break;
case 2:
found_match = y == 10;
break;
case 3:
found_match = y == 6;
break;
case 4:
found_match = y == 9;
break;
case 5:
found_match = y == 8;
break;
case 6:
found_match = y == 1;
break;
case 7:
found_match = y == 13;
break;
case 8:
if (y == 0) {
return 0;
}
break;
}
if (found_match != 1) {
return -1;
}
++p_struc->field_4;
p_struc->field_0 = 0;
p_struc->field_8 = 1;
}
return 1;
}
```

##### Arguments

`p_struc`

is a reference to a zero’d structure of 3 dwords.

`derived_buff`

is a reference to the derived data written by `x()`

called by `main()`

.

##### Flow

It starts with an increment and a check on the incremented field, so we may treat `p_struc->field_0`

as some kind of counter.

We notice 2 symmetrical pieces of code which call `f()`

and then loop on the results.

Each loop sets its chosen index variable (`chosen_arr_1_idx`

and `chosen_arr_2_idx`

) to max(array) if there exists a value greater than 1.0 in array.

We now reach the first condition for making `main()`

continue looping.

It looks like when `p_struc->field_8 != 0`

the function would always return 1.

When `chosen_arr_1_idx`

and `chosen_arr_2_idx`

are negative `p_struc->field_8`

and `p_struc->field_0`

are set back to 0.

`p_struc->field_0 = 0`

is a good sign because it makes returning a negative value less likely.

It could be said that `p_struc->field_8`

is some sort of state flag which needs to keep changing for `r()`

to do something else.

In a subsequent call to `r()`

when `p_struc->field_8`

is zero we can see that if any of `chosen_arr_1_idx`

or `chosen_arr_2_idx`

are negative then `r()`

would return 1.

This probably means that for some interesting thing to happen in `r()`

we want `(chosen_arr_1_idx >= 0 && chosen_arr_2_idx >= 0) || (chosen_arr_1_idx < 0 && chosen_arr_2_idx < 0)`

.

`y = chosen_arr_1_idx | 4 * chosen_arr_2_idx`

is a 4×4 array index calculation which is compared to constant values inside the switch bracket.

If `found_match != 1`

then a negative value is returned, so we always want `found_match == 1`

.

`p_struc->field_4`

is incremented which would make us compare other values with `y`

in subsequent calls to `r()`

.

`p_struc->field_0 = 0`

makes us less likely to return -1.

`p_struc->field_8 = 1`

would lead us into the possibility of returning it to zero if `chosen_arr_1_idx < 0 && chosen_arr_2_idx < 0`

.

After going over the code it is now clear what is the flow:

`if flag == 0`

`if chosen_arr_1_idx >= 0 and chosen_arr_2_idx >= 0`

`if i == 8 and y == 0`

`SUCCESS`

`if y == constant_nums[i]`

`flag = 1`

`i++`

`else`

`if chosen_arr_1_idx < 0 and chosen_arr_2_idx < 0`

`flag = 0`

We want each call to `r()`

to be followed by another call to `r()`

in a way that keeps comparing `y`

untill we reach `p_struc->field_4 == 8`

where `y == 0`

.

### Our input

At this point we wanted to understand how we can create `derived_buff`

such that it would follow our planned flow untill SUCCESS.

But something caught our attention inside `f()`

.

```
double __fastcall f(_complex *modified_buff, int const_val)
{
return cabs(modified_buff[(const_val << 13) / 44100]);
}
```

The division by `44100`

looked too pretty, so we searched for it on google and the first result was “44,1000 Hz – Wikipedia” which wasn’t unexpected because of the audio context.

This still wasn’t enough to really help us, but we felt lucky so we decided to google search all constant values we could find in the binary.

We found out that the constant values passed to `f()`

calls were all related to DTMF code.

DTMF diagrams from google images also felt like the state machine of `r()`

.

We also found images of DTMF pads which are 4×4.

So we started to write down values from the DTMF pad with `chosen_arr_1_idx`

and `chosen_arr_2_idx`

being our indices in the order of the calculated `y`

value comparsion.

`chosen_arr_1_idx | 4 * chosen_arr_2_idx`

`1 | 4 * 2 == 9`

`1 | 4 * 1 == 5`

`2 | 4 * 2 == 10`

`2 | 4 * 1 == 6`

`1 | 4 * 2 == 9`

`0 | 4 * 2 == 8`

`1 | 4 * 0 == 1`

`1 | 4 * 3 == 13`

`0 | 4 * 0 == 0`

1209 Hz | 1336 Hz | 1477 Hz | 1633 Hz | |
---|---|---|---|---|

697 Hz | 1 | 2 | 3 | A |

770 Hz | 4 | 5 | 6 | B |

852 Hz | 7 | 8 | 9 | C |

941 Hz | * | 0 | # | D |

We got the sequence 859687201 which turned out to be part of the flag as stated in the challenge description: **CTF{859687201}**