4

I am currently trying to reverse-engineer the firmware of an E-Cigarette but I cannot find any clues with the common tools. Heres everything I found so far.

  • Binwalk doesnt recognize anything even with -E or -A.

  • Entropy = 7.611435 bits per byte.

  • Optimum compression would reduce the size of this 80896 byte file by 4 percent.

  • Chi square distribution for 80896 samples is 99630.01, and randomly would exceed this value less than 0.01 percent of the times.

  • Arithmetic mean value of data bytes is 117.2996 (127.5 = random).

  • Monte Carlo value for Pi is 3.078178312 (error 2.02 percent).

  • Serial correlation coefficient is 0.107920 (totally uncorrelated = 0.0).

Is it possible that this file is encrypted or compressed?

Here is a visualization of the entropy:

Entropy

And here is the graph of the entropy: Entropy_graph

Here is a download of the Firmware. https://www.file-upload.net/download-13422170/HW718V40_20171008.firmware.html

  • Can you share a link to the file? If not, can you post the entropy plot generated by binwalk -E <your file> ? The distribution of higher-entropy data is not as uniform as what I would expect from compressed or encrypted data. Furthermore, there are sizable low entropy regions. What are the results of running strings -n 9 <your file> ? Have you looked at a hex dump of the first few hundred bytes? – julian Dec 6 '18 at 23:13
  • Hi I added the download to my question. I looked at the hexdump of the file and tried to disassemble it with radare2 and different flavours but nothing helpful came out of it. Im not at home so i unfortunatly cant provide you with a entropy Plot. The ASCII strings in the hexdump are also nothing that makes sense to me but maybe you find something. Thanks for the help. – Cubzer Dec 7 '18 at 10:58
  • 1
    Wouldn’t it be wise to first identify the processor, and from that (hopefully) the instruction set? – eggyal Dec 9 '18 at 22:28
3

TL;DR

Yes the file is encrypted, it is a simple XOR cipher with a key length of 0x800 bytes.

Python script for getting decrypted firmware:

import struct

FILENAME = "HW718V40_20171008.firmware"

def xor_cipher(data, key):
    import sys
    from itertools import cycle
    if (sys.version_info > (3, 0)):
        return bytes(a ^ b for a, b in zip(data, cycle(key)))
    else:
        return bytes(bytearray(a ^ b for a, b in zip(bytearray(data), cycle(bytearray(key)))))

key = struct.pack('<I', 0x00001AE7) * 8 + struct.pack('<I', 0x000026EF) \
* 8 + struct.pack('<I', 0x0000565C) * 8 + struct.pack('<I',
    0x00001109) * 8 + struct.pack('<I', 0x00005618) * 8 \
+ struct.pack('<I', 0x000077D6) * 8 + struct.pack('<I', 0x00000F45) \
* 8 + struct.pack('<I', 0x000024AE) * 8 + struct.pack('<I',
    0x00001C42) * 8 + struct.pack('<I', 0x00006D4C) * 8 \
+ struct.pack('<I', 0x0000754E) * 8 + struct.pack('<I', 0x0000449C) \
* 8 + struct.pack('<I', 0x000056A2) * 8 + struct.pack('<I',
    0x00003EED) * 8 + struct.pack('<I', 0x0000106C) * 8 \
+ struct.pack('<I', 0x00000C7D) * 8 + struct.pack('<I', 0x0000578C) \
* 8 + struct.pack('<I', 0x000045CB) * 8 + struct.pack('<I',
    0x0000253C) * 8 + struct.pack('<I', 0x00007F0F) * 8 \
+ struct.pack('<I', 0x00006513) * 8 + struct.pack('<I', 0x000004C0) \
* 8 + struct.pack('<I', 0x00006BFF) * 8 + struct.pack('<I',
    0x0000711C) * 8 + struct.pack('<I', 0x00004829) * 8 \
+ struct.pack('<I', 0x00000B03) * 8 + struct.pack('<I', 0x000038E2) \
* 8 + struct.pack('<I', 0x000020AC) * 8 + struct.pack('<I',
    0x00005708) * 8 + struct.pack('<I', 0x000064FA) * 8 \
+ struct.pack('<I', 0x00007984) * 8 + struct.pack('<I', 0x00004C79) \
* 8 + struct.pack('<I', 0x00007ED1) * 8 + struct.pack('<I',
    0x00000F4F) * 8 + struct.pack('<I', 0x00002BB2) * 8 \
+ struct.pack('<I', 0x00005FA3) * 8 + struct.pack('<I', 0x00001073) \
* 8 + struct.pack('<I', 0x00002046) * 8 + struct.pack('<I',
    0x00003175) * 8 + struct.pack('<I', 0x000005A5) * 8 \
+ struct.pack('<I', 0x000054CF) * 8 + struct.pack('<I', 0x00001B4E) \
* 8 + struct.pack('<I', 0x0000264E) * 8 + struct.pack('<I',
    0x00003A88) * 8 + struct.pack('<I', 0x00002C1A) * 8 \
+ struct.pack('<I', 0x000022D6) * 8 + struct.pack('<I', 0x000019C0) \
* 8 + struct.pack('<I', 0x00002B57) * 8 + struct.pack('<I',
    0x00003C82) * 8 + struct.pack('<I', 0x00007C61) * 8 \
+ struct.pack('<I', 0x0000530F) * 8 + struct.pack('<I', 0x00007AD2) \
* 8 + struct.pack('<I', 0x00007414) * 8 + struct.pack('<I',
    0x00001ADD) * 8 + struct.pack('<I', 0xD6ADBCEF) * 8 \
+ struct.pack('<I', 0x00003E66) * 8 + struct.pack('<I', 0x000061DD) \
* 8 + struct.pack('<I', 0x00002330) * 8 + struct.pack('<I',
    0xDEADBEEF) * 8 + struct.pack('<I', 0x0000475D) * 8 \
+ struct.pack('<I', 0x00002A4F) * 8 + struct.pack('<I', 0x00001F15) \
* 8 + struct.pack('<I', 0x00001162) * 8 + struct.pack('<I',
    0xE3ADBEEF) * 8

buf = open(FILENAME, "rb").read()
xbuf = xor_cipher(buf, key)
open(FILENAME + ".out", "wb").write(xbuf)

Walk-through

Looking at the header of the firmware, we can make some guess (all values stored in Little-endian):

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000  04 83 57 48 9C 3B 01 00 C8 CF 28 00 00 1E 2B 6A  .ƒWHœ;..ÈÏ(...+j
00000010  47 00 0B 00 80 01 00 00 E4 CF 28 00 38 FE 28 00  G...€...äÏ(.8þ(.
  • 0x48578304 : Maybe a signature
  • 0x00013B9C : Almost the length of the firmware file length (originally 0x13C00)

If we look further in the firmware hexdump and assume we are dealing with a CORTEX device or something similar, at offset 0x20 we could think that there is a vector table:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000020  CF 36 00 20 3E 1B 00 08 F2 D5 00 08 A4 D5 00 08  Ï6. >...òÕ..¤Õ..
00000030  A2 D5 00 08 A0 D5 00 08 BE D5 00 08 EF 26 00 00  ¢Õ.. Õ..¾Õ..ï&..
  • 0x200036CF : Initial SP Value
  • 0x08001B3E : Reset Vector
  • 0x0800D5F2 : NMI Vector
  • 0x0800D5A4 : Hard Fault
  • 0x0800D5A2 : Memory Management Fault
  • 0x0800D5A0 : Bus Fault
  • 0x0800D5BE : Usage Fault
  • 0x000026EF : RESERVED
  • 0x0000565C : RESERVED
  • 0x0000565C : RESERVED
  • 0x0000565C : RESERVED

Here we can notice few problems:

  1. Why the Initial SP Value is not aligned?
  2. Why the reserved vector are not NULL?

At this point, grepping the sequence of bytes EF 26 00 00 end up in an interesting place:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00012000  E7 1A 00 00 E7 1A 00 00 E7 1A 00 00 E7 1A 00 00  ç...ç...ç...ç...
00012010  E7 1A 00 00 E7 1A 00 00 E7 1A 00 00 E7 1A 00 00  ç...ç...ç...ç...
00012020  EF 26 00 00 EF 26 00 00 EF 26 00 00 EF 26 00 00  ï&..ï&..ï&..ï&.. << 0x000026EF REPEATED 4 TIMES
00012030  EF 26 00 00 EF 26 00 00 EF 26 00 00 EF 26 00 00  ï&..ï&..ï&..ï&.. << 0x000026EF REPEATED 4 TIMES
00012040  5C 56 00 00 5C 56 00 00 5C 56 00 00 5C 56 00 00  \V..\V..\V..\V..
00012050  5C 56 00 00 5C 56 00 00 5C 56 00 00 5C 56 00 00  \V..\V..\V..\V..
00012060  09 11 00 00 09 11 00 00 09 11 00 00 09 11 00 00  ................
00012070  09 11 00 00 09 11 00 00 09 11 00 00 09 11 00 00  ................
00012080  18 56 00 00 18 56 00 00 18 56 00 00 18 56 00 00  .V...V...V...V..
00012090  18 56 00 00 18 56 00 00 18 56 00 00 18 56 00 00  .V...V...V...V..

We can see that the value 0x000026EF is repeated 8 times, and we have the value 0x0000565C (the other reserved vector) repeated 8 times, and some others values are repeated too.

There is a big chance that we are inside some ZERO pages and it is a simple XOR cipher which is applied on the whole file.

Maybe the XOR cipher key is in the following form:

  • key[0] repeated 8 times
  • key[1] repeated 8 times
  • key[2] repeated 8 times
  • ...
  • key[63] repeated 8 times

By looking at all the offset where are stored the value the 0x000026EF, we can compute the delta between them, we can make a guess that the key length is 0x800 bytes.

Hopefully we can find 3 version of the firmware on the internet:

  • f553e034ba70138c270dac03e7dcda6f : HW718V38_20170216.firmware
  • 7440def1011f50749ff7f1ca8f7bcced : HW718V39_20170221.firmware
  • 0850c80192dcad7a0ce9a5446fb97f16 : HW718V40_20171008.firmware

In order to extract the full key, we split each firmware file in block of 0x800 bytes chuncks, and compute the number of occurence of every 0x04 bytes repeated every 0x20 bytes.

With this we are able to extract the full key, some adjustments were required using the previous method:

  • keys[34] = 0x2BB2
  • keys[38] = 0x3175
  • keys[54] = 0xD6ADBCEF         # 4 bytes key ... wat !?
  • keys[58] = 0xDEADBEEF         # 4 bytes key ... wat !?
  • keys[60] = 0x2A4F
  • keys[62] = 0x1162
  • keys[63] = 0xE3ADBEEF         # 4 bytes key ... wat !?

The last step is now to know the exact value for the loading address, by starting to disassemble the firmware with a processor ARM little-endian at a loading address 0x08000000 and file offset 0x20, we can find an interesting disassembly:

ROM:0800B854                 LDR             R1, =0xE000ED08
ROM:0800B856                 LDR             R0, =0x8003C00
ROM:0800F458                 STR             R0, [R1]

0xE000ED08 is the Vector Table Offset Register and the value 0x8003C00 is written inside it.

Now we can reaload our firmware with an ARM processor in little-endian at the loading address 0x8003C00 and file offset 0x20 and we can start working on the reverse engineering.

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.