Digital Lockpicking: Why Your Front Door Shouldn't Be On The Internet


FingerTec is a company that offers time attendance and door access hardware and solutions. MWR identified vulnerabilities in their access control biometric devices that can be abused to achieve the following:

  • Create a back door administrative user account
  • Extract a list of all users on the device(s), including PINs and RFID numbers
  • Brute force the Comm Key (message authentication key) - if it is set
  • Execute custom commands
  • Unlock a door for a short period of time

All of the above can be achieved by an adversary who can communicate directly with the device over a TCP/IP network. The communications are sent using unencrypted UDP datagrams using an undocumented proprietary messaging protocol.

An adversary could leverage the issues to gain access to restricted areas in buildings protected by such a system and/or affect the integrity of any audit trails.


With most traditional access control systems, the readers; whether they are RFID, biometric, PIN pad, etc, connect via a serial connection to a master controller in a centralized system. The controller however does all of the processing, and controls unlocking and locking doors and also keeps an audit trail.

The FingerTec system works differently, the logic and the processing is performed by the device. When a user is validated and authorized for access on the FingerTec device, a signal is sent directly to the power supply, which then unlocks the door. The FingerTec device downloads the authorized user database from a central server and stores it locally.

On January 12th, 2016 (Packet Storm) I published an advisory that disclosed the hard coded default root password for the device. This was identified during my initial research into its attack surface. At the same time I also reverse engineered the user database (user.dat) format. The database contains user names, IDs, PINs, and RFID numbers. At the time I was unable to reverse the proprietary UDP protocol in use for network communications.

I've since had time to revisit this research and this blog post details the approach taken, what was achieved, and the issues identified.

Understanding the Messaging Protocol

To approach the challenge of understanding the protocol, I set about methodically executing functionality in order to trigger network communications that could be recorded in a full packet capture.

The server was found to attempt a connection with the controller device over TCP port 4370. However on the devices assessed, this service was not listening, so the connection would predictably fail. It then reverts to communicating with the device using UDP sending datagrams to port 4370. The server always sends the same 8 byte initialization packet:


The response from the device was similar, but different every time. After comparing several different packet captures, I saw that the first 8 bytes of the packet were the command header, and anything occurring after was either data or arguments.

Request Made        Response From Reader
e80317fc00000000    d007c2016df60000
4c0445056df60100    d00751a66df60100
0b00b3b96df60200    d007bd746df60200
e80317fc00000000    d0073466fb910000
4c04b769fb910100    d007c40afb910100
0b00261efb910200    d0072fd9fb910200

After receiving an initialization message from the server, the device replies with a response code, checksum, session ID, and a sequence number. Some examples are below.

Initialization string
e803       17fc       0000         0000 

Response   Checksum   Session ID   Sequence
d007       c201       6df6         0000 

Command    Checksum   Session ID   Sequence 
4c04       4505       6df6         0100 

Response   Checksum   Session ID   Sequence
d007       51a6       6df6         0100 

It took me way longer to figure out that the second set of numbers was a checksum. I got lucky and found someone had posted the source code for the ZK Software API on GitHub, which also suspiciously communicated over port UDP 4370. After reading through it, I determined that it was the same protocol that the FingerTec devices were using, and it contained the source code on how to calculate the checksum.

I wrote a Proof of Concept (PoC) tool that allowed me to generate basic communications in order to allow me to begin mapping out the various commands. However the packet captures were not easy to work with. The raw UDP data was ugly, making it difficult to identify any meaning to the giant mass of packets. The capture also contained multiple simultaneous sessions. Visually this made it difficult to track packet order. In order to make my life easier, I wrote a Python script to parse the PCAP files, sort the packets by session and then print everything out in a much more readable format. It was then much easier to map out some of the basic commands.

The following capture was obtained when using the software to copy a user to a device:

Selection 006

Having previously reverse engineered the database format, the big chunk of binary data looked familiar to me. These are database rows in binary data.

Here's the layout:

Selection 009

 If we ignore the third row, since it was malformed, the data is laid out like this:

  1. The first two bytes are the internal ID, (little endian). This is usually the same as the user ID, but not always. For example, d2 07 unpacks to 2002. If this is set to 00 00, that means the user is disabled.
  2. This is the PIN in ASCII. If there is no PIN, you get the 00 ff ff ff 5b entry. If all 5 bytes for the PIN aren't used, padding bytes are used (00).
  3. This is the username in ASCII. A string terminator (00) is used if the username is < 8 bytes. The string can be a maximum of 8 bytes.
  4. This is a 4 byte little endian integer representing the RFID number.
  5. Not sure what this is.
  6. This is a 4 byte little endian integer representing the User ID.
  7. The unlabeled box between 1 and 2 is the privilege level, 07 signifies an administrative user and 00 identifies an unprivileged user.

Replaying the exact same packet sequence creates the same user again. The user name and PIN values can be modified and the message re-sent, to insert new users (unauthenticated).

The image below shows a capture generated when a list of users is requested from the device. The server receives a complete user database containing user names, user IDs, PINs, and RFID numbers.

Selection 010

As can be seen above, after the first 4 bytes of data, the message is a byte by byte dump of the entire database. Our understanding of the protocol and ability to craft messages to be sent to the devices in such a network means that we are able to:

  1. Create users in the device
  2. Discover all details for all users in the device

These issues were disclosed to the vendor. During the disclosure communications MWR was informed that FingerTec does not have any control over most of the software stack on the device. The device is manufactured and branded for FingerTec by a company called ZKTeco. Therefore the issues discovered are not isolated to FingerTec, but could potentially affect all ZKTeco and ZKTeco based TCP/IP enabled devices.

It was also found that the devices do support authentication. While the device manuals recommend against setting the code, the software manual recommends setting a 5 digit number.

This is referred to as the Comm Key. It can be set with a number between 0 and 999999. When set to anything other than 0, every request is met with a response code of 2005, which translates as 'Authentication Required'.

A packet capture containing multiple successful authentications with a Comm Key was analyzed. It was found that the authentication codes were unique with each authentication response. Since the Comm Key was the same (the number "2"), the only difference between the two was the session key.

Selection 015

Selection 016

After searching around and not finding anything about how the actual code is generated, and after trying various 32-bit checksum/hashing functions with various permutations of "2"+session_key without success, a new approach was needed. The server software was loaded into x86dbg and an attempt made to identify the function responsible for hashing the Comm Key.

A number of DLLs were dynamically loaded and unloaded when executing the target function. The DLL 'comms.dll' was found to be a good place to start. After several runs and different breakpoints being set and analyzed, the function that generated the password was identified.

Selection 020

The function does the following: 

  1. Reverse the Comm Key integer bitwise. So 0b11001100101010101111111100000000 becomes 0b00000000111111110101010100110011 (Lines 1080 - 108E)
  2. Add the Session ID (Lines 1090 and 1094)
  3. XOR the result against 0x4F534B5A (Lines 1098 - 10AC)
  4. Swap the first 2 bytes with the second 2 bytes (Lines 10B1 - 10BB)
  5. Make a call to the system call GetTickCount, which returns an integer based on how long the system has been up and running. Bytes 1, 2, and 4 are then XORed against the last byte of the result from GetTickCount.
  6. Finally, that tick count is moved into byte 3.

 The end result is the actual pass code:

Selection 021

Which was verified in Wireshark:

Selection 022

Steps 5 and 6 are just meant to add a little randomness to the hash, and are completely optional. You can just not do step 5, and replace position 3 with 00 and it works just as well.

The hashing mechanism is very weak is due to the very limited keyspace. It has at most 3 bytes of password space, which works out to 2^24, or a maximum possible 16,777,216 passwords. On top of that, the software allows a maximum number of 999,999. That then means 10^6, or approx. a million passwords to enumerate. There is also no brute force protection or rate limiting.

Understanding the algorithm and the proprietary protocol in use allows us to automate a brute force of the key space. A tool was crafted with the following capabilities:

  • Create a back door administrative user account
  • Extract a list of all users on the device(s), including PINs and RFID numbers
  • Brute force the Comm Key - if it is set
  • Execute custom commands
  • Unlock a door for a short period of time

Going sequentially, it takes a little under 3 days to exhaust the entire available keyspace for the AC900s, so an average brute force attempt should be half of that. Multithreaded brute forcing locks up the device. The R2s are a little more powerful, and should be faster to brute force, however in the course of other research avenues, those devices have been bricked, so we are unable to test.

Scope of Impact

According to Shodan, there are over 4000 of these devices exposed on the internet. These devices are mainly concentrated in the US and China, but have a wide spread over the rest of the world.

zkteco map

According to ZKTeco:

"More than 220 million people use ZKTeco products in approximately 180 countries/regions every day. ZKTeco has become a well-recognized, respected and sought after brand in the security and biometric industries."


  • Do not host any IoT device on the public internet.
  • Use the maximum size Comm Key you can with these devices.
  • If you have these devices on your network, make sure they are fully segmented, and only accessible from the management server. They do not need to be able to talk to the public internet, or anything else on your intranet.
  • Change the root password.


The disclosure timeline was as follows:

  • 8-Jul-2016 - Initial disclosure
  • 20-Jul-2016 - Advisory updated with password information
  • 02-Aug-2016 - ZKTeco contacted
  • 28-Aug-2016 - Fixed released to FingerTec developers

Note the nature of the fix was not disclosed to MWR and a device with the implemented fix has yet to be analyzed. Therefore the effectiveness of the fix is as of yet, unknown.