You might need a pitch-perfect voice to solve this one. Once you crack the code, the flag is CTF{code}.
Time
40 minutes
Behavior
It's a ELF executable that will record some voice using your microphone, and then tell you whether you pass the test.
Solution
TL;DR
- Reverse the executable
- Extract the sequence from switch case.
- Convert to value using a DTMF keypad table.
Reversing
Here's its main function:
//...
mic = pa_simple_new(0LL, *argv, 2LL, 0LL, "record", &ss_3811, 0LL, 0LL, &err);
if ( mic ) {
v8 = 0;
v9 = 0;
v10 = 0;
do {
if ( (signed int)pa_simple_read(mic, &buf, 0x8000LL, &err) < 0 ) {
errStr = pa_strerror(err);
fprintf(stderr, "pa_simple_read() failed: %s\n", errStr);
return 1;
}
x(&buf, &v7);
good = r(&v8, &v7);
if ( good < 0 ) {
fwrite("FAILED\n", 1uLL, 7uLL, stderr);
return 1;
}
} while ( good );
fwrite("SUCCESS\n", 1uLL, 8uLL, stderr);
pa_simple_free(mic, 1LL);
result = 0;
} else {
v3 = pa_strerror(err);
fprintf(stderr, "pa_simple_new() failed: %s\n", v3);
result = 1;
}
return result;
And the library function's prototype found from here.
pa_simple * pa_simple_new (
const char *server, const char *name, pa_stream_direction_t dir,
const char *dev, const char *stream_name, const pa_sample_spec *ss,
const pa_channel_map *map, const pa_buffer_attr *attr, int *error);
int pa_simple_read (pa_simple *s, void *data, size_t bytes, int *error);
It use PulseAudio to record some audio, and run some test.
Here's how x
looks like:
void x(__int64 buf, __int64 output) {
int step;
bit_flip(buf, output);
for ( int i = 1; i < 14; ++i ) {
step = 1 << i;
for ( int j = 0; j < 0x2000; j += step )
y(output, j, step);
}
}
It's still not clear what is will do. Let's look into bit_flip
.
void __fastcall bit_flip(__int64 buf, __int64 output) {
double v2; // ST00_8
signed __int64 v3; // rax
int i; // [rsp+24h] [rbp-4h]
for ( i = 0; i <= 0x1FFF; ++i ) {
v2 = *(4LL * i + buf);
v3 = 16LL * reverse_bits(i) + output;
*v3 = v2;
*(v3 + 8) = 0LL;
}
}
It tells us that buf
is a float32 array, and output
is a double array.
Let's change its type:
void bit_flip(float *buf, double *output) {
double v2; // ST00_8
double *v3; // rax
int i; // [rsp+24h] [rbp-4h]
for ( i = 0; i <= 0x1FFF; ++i ) {
v2 = buf[i];
v3 = &output[2 * reverse_bits(i)];
*v3 = v2;
v3[1] = 0.0;
}
}
OK, it converts the float32 input into double array, and insert a zero between each elements.
Let's look at y
:
__int64 __fastcall y(double *output, __int64 a2, int a3) {
// ...
for ( i = 0; ; ++i ) {
// ...
cexp(output, a2);
// ...
complex_mul(output);
// ...
complex_add(output);
// ...
complex_sub(output);
// ...
}
return result;
}
It looks like that the output is a double complex array instead of a double array. To change the type to double complex in IDA, we have to create a structure first:
00000000 Complex struc ; (sizeof=0x10, mappedto_13)
00000000 real dq ?
00000008 imag dq ?
00000010 Complex ends
Now, we can change their types. Remember to fix those signature of library functions.
void bit_flip(float *sample, Complex *output) {
double inp_i; // ST00_8
Complex *out_i; // rax
signed int i; // [rsp+24h] [rbp-4h]
for ( i = 0; i <= 0x1FFF; ++i ) {
inp_i = sample[i];
out_i = &output[reverse_bits(i)];
out_i->real = inp_i;
out_i->imag = 0.0;
}
}
void y(Complex *A, signed int k, __int64 step) {
Complex w; // kr00_16
Complex u; // ST30_16
Complex t; // kr10_16
signed int m; // [rsp+10h] [rbp-60h]
int j; // [rsp+5Ch] [rbp-14h]
m = step;
for ( j = 0; j < m / 2; ++j ) {
w = cexp(__PAIR__(j * -6.283185307179586477 / m, (-0.0 * j / m)));
u = A[k + j];
t = complex_mul(w, A[j + k + m / 2]);
A[k + j] = complex_add(u, t);
A[j + k + m / 2] = complex_sub(u, t);
}
}
It looks like a radix-2 FFT, Here's the pseudo code from wikipedia. You can compare it with our reversed code.
algorithm iterative-fft is
input: Array a of n complex values where n is a power of 2
output: Array A the DFT of a
bit-reverse-copy(a,A)
n ← a.length
for s = 1 to log(n)
m ← 2s
ωm ← exp(−2πi/m)
for k = 0 to n-1 by m
ω ← 1
for j = 0 to m/2 – 1
t ← ω A[k + j + m/2]
u ← A[k + j]
A[k + j] ← u + t
A[k + j + m/2] ← u – t
ω ← ω ωm
return A
Back to our main function. Now, it looks like:
do {
if ( pa_simple_read(mic, sample, 0x8000LL, &err) < 0 ) {
// raise error
}
FFT(sample, freq);
good = r(&state, freq, v3);
if ( good < 0 ) {
fwrite("FAILED\n", 1uLL, 7uLL, stderr);
return 1;
}
}
while ( good );
fwrite("SUCCESS\n", 1uLL, 8uLL, stderr);
And here's how r
check our sound:
int run(State *state, Complex *freq, double a3) {
double f2[4]; // [rsp+10h] [rbp-70h]
double f1[4]; // [rsp+30h] [rbp-50h]
int num; // [rsp+54h] [rbp-2Ch]
bool ok; // [rsp+5Bh] [rbp-25h]
int j; // [rsp+5Ch] [rbp-24h]
double max_f2; // [rsp+60h] [rbp-20h]
int argmax_f2; // [rsp+68h] [rbp-18h]
int i; // [rsp+6Ch] [rbp-14h]
double max_f1; // [rsp+70h] [rbp-10h]
int argmax_f1; // [rsp+7Ch] [rbp-4h]
if ( ++state->retry > 20 )
return 0xFFFFFFFFLL;
f1[0] = f(freq, 1209);
f1[1] = f(freq, 1336);
f1[2] = f(freq, 1477);
f1[3] = f(freq, 1633);
argmax_f1 = -1;
max_f1 = 1.0;
for ( i = 0; i <= 3; ++i ) {
if ( f1[i] > max_f1 ) {
argmax_f1 = i;
max_f1 = f1[i];
}
}
f2[0] = f(freq, 697);
f2[1] = f(freq, 770);
f2[2] = f(freq, 852);
f2[3] = f(freq, 941);
argmax_f2 = -1;
max_f2 = 1.0;
for ( j = 0; j <= 3; ++j ) {
if ( f2[j] > max_f2 ) {
argmax_f2 = j;
max_f2 = f2[j];
}
}
if ( state->need_blank ) {
if ( argmax_f1 < 0 && argmax_f2 < 0 ) {
state->need_blank = 0;
state->retry = 0;
}
} else if ( argmax_f1 >= 0 && argmax_f2 >= 0 ) {
num = argmax_f1 | 4 * argmax_f2;
ok = 0;
switch ( state->idx ) {
case 0u:
ok = num == 9;
goto LABEL_30;
case 1u:
ok = num == 5;
goto LABEL_30;
case 2u:
ok = num == 10;
goto LABEL_30;
case 3u:
ok = num == 6;
goto LABEL_30;
case 4u:
ok = num == 9;
goto LABEL_30;
case 5u:
ok = num == 8;
goto LABEL_30;
case 6u:
ok = num == 1;
goto LABEL_30;
case 7u:
ok = num == 13;
goto LABEL_30;
case 8u:
if ( num )
goto LABEL_30;
return 0LL;
default:
LABEL_30:
if ( ok != 1 )
return 0xFFFFFFFFLL;
++state->idx;
state->retry = 0;
state->need_blank = 1;
break;
}
}
return 1LL;
}
where f
returns the amplitude of that frequency:
double f(Complex *freq, int v) {
return cabs(freq[(v << 13) / 44100]);
}
The goal is clear now, we have to input an audio that will be converted to 9, 5, 10, 6, 9, 8, 1, 13, 0
.
Googling with the challange name dialtone
and those frequency constant, we can find a frequency to keypad table from wikipedia. This is the sound you'll hear while dialing:
1209Hz | 1336Hz | 1477Hz | 1633Hz | |
---|---|---|---|---|
697Hz | 1 | 2 | 3 | A |
770Hz | 4 | 5 | 6 | B |
852Hz | 7 | 8 | 9 | C |
941Hz | * | 0 | # | D |
convert the index to those character, we get CTF{859687201}
.