Summary: An ARM crackme is transferred over Bluetooth. Extracting the binary allows us to apply angr to it to automatically find the flag.

Challenge Description

I smell updates!
1982
INTERNET OF THINGS
16 SOLVES

DESCRIPTION
Agent 47, we were able to retrieve the enemy's security log from our QA technician's file! It has come to our attention that the technology used is a 2.4 GHz wireless transmission protocol. We need your expertise to analyse the traffic and identify the communication between them and uncover some secrets! The fate of the world is on you agent, good luck.

This challenge:
- Unlocks other challenge(s)
- Is eligible for Awesome Write-ups Award

The challenge provides a single PCAP file.

$ file iot-challenge-3.pcap
iot-challenge-3.pcap: pcap capture file, microsecond ts (little-endian) - version 2.4 (Bluetooth HCI H4 with pseudo-header, capture length 262144)

Examining the PCAP shows that it contains multiple Bluetooth captures.

Bluetooth captures

Protocol hierarchy

Running strings on the PCAP shows that there appears to be various streams. One such stream involves a chat session and another appears to include references to common libc functions.

$ strings -a iot-challenge-3.pcap
q#jMv+j
Galaxy S7 edge
_5t

_ub

Bro: Dude did u ate my chips
_4$

/lib/ld-linu
x-armhf.so.3
_eX

(Too cool 4 u) TK: Emma owes me $36 for the dinner
|fUa
libc.so
exit
puts
stdin
printf
_+Q

fgets
strlen
ibc_start_main

Searching through the PCAP turns up a write request packet containing an ELF header.

Write request

Inspecting the packet structure tells us that the write request is represented with the 0x12 byte and the handle of the network is 0x008c. We can use this information to identify the right write request packets.

Inspecting write request packets

A Scapy script can be written to extract the data from such packets:

#!/usr/bin/env python

'''
Solver script for the I Smell Updates! challenge.
'''

from scapy.all import *


def main():
    packets = rdpcap('./iot-challenge-3.pcap')
    elf = b''
    marker = b'\x12\x8c\x00'

    for i in packets:
        data = bytes(i)
        if marker in data:
            marker_end = data.index(marker) + 3
            payload = data[marker_end:]
            elf += payload

    # Get rid of the \x30\x31.
    elf = elf[2:]

    open('dumped_elf', 'wb').write(elf)


if __name__ == '__main__':
    main()

Running the script extracts the relevant data and dumps it to a file. Running file on it identifies it as an ARM ELF binary.

$ python solve.py
$ file dumped_elf
dumped_elf: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.32, BuildID[sha1]=d73f4011dd87812b66a3128e7f0cd1dcd813f543, not stripped

Running the binary on an ARM machine prompts us for a secret.

ubuntu@ip-172-31-23-17:~$ uname -a
Linux ip-172-31-23-17 5.4.0-1029-aws #30-Ubuntu SMP Tue Oct 20 10:08:09 UTC 2020 aarch64 aarch64 aarch64 GNU/Linux
ubuntu@ip-172-31-23-17:~$ ./dumped_elf
Secret?bad_password
Sorry wrong secret! An alert has been sent!

Analysing the string in Binary Ninja shows us one reference to an ‘Authorised!’ message.

Binary Ninja Strings

The cross-reference leads to the main function of the program. We can see that the program branches to the successful path at 0x10394.

Identifying the branches

To solve this without actually putting in any effort, we can utilise angr. The following script does this:

import angr
project = angr.Project("./dumped_elf", auto_load_libs=False)
@project.hook(0x10798)
def print_flag(state):
    print("FLAG SHOULD BE:", state.posix.dumps(0))
    project.terminate_execution()
project.execute()

Running the script gives us a fairly usable output:

$ python solve.py
FLAG SHOULD BE: b'aNtiB!e\x01\x00'

Verifying the secret against the binary shows that the angr script worked.

ubuntu@ip-172-31-23-17:~$ ./dumped_elf
Secret?aNtiB!e
Authorised!
ubuntu@ip-172-31-23-17:~$

Flag: govtech-csg{aNtiB!e}

Leave a Comment