Reverse engineering, Windows internals, x86 magic, low level programming, and everything else I feel like writing about.

CrySyS SecChallenge 2020: Headquaters in Greenland

Second part: Secure? Storage

A research associate was sent to Greenland to conduct observations, but he managed to smuggle a good amount of alcohol between his stuff. After a while he drank almost all the smuggling goods. The problem is that the dictator always comes out of him, and considering how much alcohol he has taken with himself, it would be useful to know what his next destination is, so that they can evacuate the place in time. He reprogrammed the ESP32 that was meant to transmit sensor data through MQTT protocol, now he uses it as a CnC server and also sends the following target locations to his accomplices. Somebody has to stop it before they take over the world!

This challenge had an environment accessible over MQTT. A dump of the content served is available for download

Categories

IoT, Python

Files

Solution

Let’s connect to the MQTT server, subscribe to everything and dump the messages:

import paho.mqtt.client as mqtt
def on_connect(client, userdata, flags, rc):
   client.subscribe('#')
   client.subscribe('$SYS/#')

def on_message(client, userdata, msg):
   print msg.payload

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect('1.2.3.4', 12345, 60)
client.loop_forever()

The output of this command can be found in the mqtt.txt file I uploaded to the files section. It looks like an endless stream of sequences similar to this:

5797 seconds
2
2
2
62.74
50.25
47.51
1899.30
1894.99
0.91
0.91
60.57
10.87
10.28
1861.57
409.85
0.20
0.20
60.16
9.22
8.91
1852.32
353.74
0.10
0.10
48
2
20920
5862
6581
5790
6509
179410
256756
97730
54341
0111001 NAND 1110111

NOT 0100000

1100001 OR 110

1110110 AND 1011011

NOT 001100

101100 NAND 1110

1110101 NAND 11011

NOT 0110011

1110111 AND 1001001

1011011 NAND 10101

NOT 0111011

The story says these are meant to be sensors, so we should probably focus on the less sensor-looking bitwise operation lines instead. After a few tries with manually calculating it turned out these are not 8 bit operations, but rather the size of the longest operand, then the result padded to 8 bits to represent ASCII characters. By manually doing the first few, flag-looking words will be the result. However doing the whole flag manually wouldn’t be too comfortable, so I wrote a program to do this automatically:

uint8_t mask_by_len(size_t len)
{
  return (uint8_t)((1u << len) - 1);
}

uint8_t bin_to_byte(const char* s)
{
  unsigned v = 0;
  while(*s)
  {
    v |= (*(s++) == '1' ? 1 : 0);
    v <<= 1;
  }
  return v >> 1;
}

std::map<std::string_view, uint8_t(*)(uint8_t, uint8_t)> g_bin_ops{
  { "OR", [](uint8_t a, uint8_t b) -> uint8_t { return a | b; } },
  { "XOR", [](uint8_t a, uint8_t b) -> uint8_t { return a ^ b; } },
  { "AND", [](uint8_t a, uint8_t b) -> uint8_t { return a & b; } },
  { "NAND", [](uint8_t a, uint8_t b) -> uint8_t { return ~(a & b); } },
};

std::vector<std::pair<std::regex, uint8_t(*)(std::smatch&)>> g_decode_ops{
  {
    std::regex{ "\\s*NOT\\s*([01]+)\\s*" }, [](std::smatch& match) -> uint8_t
    {
      const auto& uni = match[1].str();
      return ~(bin_to_byte(uni.c_str())) & mask_by_len(uni.size());
    }
  },
  {
    std::regex{ "\\s*([01]+)\\s*([A-Z]+)\\s*([01]+)\\s*" },
    [](std::smatch& match) -> uint8_t
    {
      const auto& lhs = match[1].str();
      const auto& rhs = match[3].str();
      const auto& op = match[2].str();
      const auto v = g_bin_ops[op](
        bin_to_byte(lhs.c_str()),
        bin_to_byte(rhs.c_str())
      );
      return v & mask_by_len(std::max(lhs.size(), rhs.size()));
    }
  },
};

int main()
{
  std::ifstream ifs{ "mqtt.txt" };
  while(!ifs.eof())
  {
    std::string line;
    std::getline(ifs, line);
    for(auto& fn : g_decode_ops)
    {
      std::smatch smatch;
      if(std::regex_match(line, smatch, fn.first))
        printf("%c", fn.second(smatch));
    }
  }
  return 0;
}

Output (rest of the repeating sequence omitted):

cd20{REDACTED}