We are writing a plugin for Microsoft DNS server for protection against IDN spoofing

IDN spoofing is the generation of domain names that are “similar” to the chosen one, which is usually used to force a user to follow a link to an attacker's resource. Next, consider a more specific attack option.

Imagine that the attacked company owns the organization.org domain, and the portal.organization.org internal resource is used inside this company. The goal of the attacker is to obtain user credentials, and for this, he sends the link via e-mail or the messenger used in the company.


Having received such a message with high probability, you may not notice that the link leads somewhere to the wrong place. After clicking on the link, the username \ password will be requested, and the victim, thinking that he is on the internal resource, will enter his account information. The chances of an attacker are especially high if he has already penetrated the perimeter, having compromised the system of any employee, and now is fighting for the privileges of the system administrator.

It’s impossible to come up with an absolute “defense against a fool”, but you can try to intercept this attack at the stage of resolving a name through a dns request.

For protection, we will need to sequentially remember the names encountered in the intercepted dns requests. The company uses its internal resources, which means that we will quickly find it in a request to portal.organization.org. As soon as we come across a name "similar" to the one previously encountered, we can replace the dns response by returning an error instead of the attacker's ip address.
What can be the algorithms for determining "similarity"?

  • UTS39 Confusable Detection (http://www.unicode.org/reports/tr39/#Confusable_Detection) Unicode is not only a valuable fur symbol table, but also a bunch of standards and recommendations. UTS39 defines an algorithm for normalizing unicode strings, in which strings differing in homoglyphs (for example, Russian “a” and Latin “a”) will be converted to the same form
  • Words characterized by permutations of internal letters. It's pretty easy to confuse organization.org and orgainzation.org
  • Substitution of the first level domain. The first level of the name usually does not make any sense, and an employee of the company after seeing “organization” can ignore the difference in .org or .net, although there may be exceptions

Most likely, the corporate server will be not bind, which is the standard for web-hosts or providers, but the microsoft dns server due to the widespread use of the active directory. And the first problem I encountered when writing a filter to microsoft dns server - I did not find an API for filtering dns requests. This problem can be solved in different ways, I chose the dll injection and IAT hook on the socket work api.

To understand the methodology, knowledge of the PE format will be necessary, more details can be read, for example, here. The executable file consists of headers, a table of sections, and the sections themselves. The sections themselves are a block of data that the loader must map to memory at a relative address (Relative Virtual Address - RVA), and all resources, code, other data are contained within the sections. Also inside the header there are links (RVA) to a number of tables necessary for the application to work, in the framework of this article two will be important - the import table and the export table. The import table contains a list of functions that are necessary for the application to work, but are in other files. An export table is a “reverse” table that contains a list of functions that are exported from this file, or, in the case of export forwarding, the file name and the function name are specified to resolve the dependency.

Injection dll will be done without all the bored CreateRemoteThread. I decided to use PE export forwarding - this is a long-known technique when, in order to boot into the desired process, a dll is created in the directory with the exe file with the name equal to the name of any dll from the import table of the exe file (the main thing is not to use HKEY_LOCAL_MACHINE \ System \ CurrentControlSet \ Control \ Session Manager \ KnownDLLs). In the created dll, the export table is copied from the target dll, but instead of a pointer to the code of the exported function, you need to write RVA to the forward line of the form “endpoint! Sendto”. The microsoft dns server itself is implemented as a service HKEY_LOCAL_MACHINE \ System \ CurrentControlSet \ services \ DNS, which is located in% systemroot% \ system32 \ dns.exe

The final algorithm for injecting into the dns server will be as follows:

  • We create the% systemroot% \ system32 \ dnsflt directory (you can use any other one; finding the directory in system32 is optional).
  • We copy% systemroot% \ system32 \ dnsapi.dll there - this is the dll from which dns.exe imports something, you can choose any other “don’t knowndll”.
  • We rename the copied dll to endpoint.dll - we will use this name in the forward-line.
  • We take our injected dll and add the correct export table to it, copy our dll to% systemroot% \ system32 \ dnsflt
  • In the registry, in the key HKEY_LOCAL_MACHINE \ System \ CurrentControlSet \ services \ DNS, change the new binary address% systemroot% \ system32 \ dnsflt \ dns.exe in ImagePath
  • Create a symlink from% systemroot% \ system32 \ dnsflt \ dns.exe to% systemroot% \ system32 \ dns.exe

Why is the last step? The fact is that in windows there is a built-in firewall, and, by default, in windows server only the% systemroot% \ system32 \ dns.exe application has the right to listen to port 53. If you try to start it from another directory, there will be no rights to access the network. Why did I even copy it? In order to minimize the impact on the entire system and not touch the original dnsapi.dll. It turns out that if you can create symlink for the application, you can get its network rights. By default, only administrators have the rights to create symlink, but it’s rather unexpected to find that giving the user the right to create symlink allows you to bypass the built-in firewall.

After loading into the process from DllMain, it will be possible to create a stream and set the interception. In the simplest case, our dns service will tell the client the ip address for the name by sending a UDP packet from port 53 through the sendto function from ws2_32.dll. The standard assumes the possibility of using 53 TCP ports if the response is too large, and it is obvious that intercepting sendto in this case will be useless. However, handling the case with tcp, although more time consuming, can be done in a similar way. So far I will tell you the simplest case with UDP. So, we know that the code from dns.exe imports the sendto function from ws2_32.dll and will use it to respond to the dns request. There are also many different ways to intercept functions, the classic one is splicing, when the first sendto instructions are replaced with jmp in their function, and after its completion, a transition is made to the previously sent sendto instructions and then to the inside of the sendto function. Splicing will work even if GetProcAddress is used to call sendto, rather than an import table, but if you use an import table, it’s easier to use an IAT hook instead of splicing. To do this, find the import table in the downloaded dns.exe image. The table itself has a somewhat confusing structure and you will have to go into the description of the PE format for details.


The main thing is that the system, in the process of loading the image, writes a pointer to the start of the sendto function in the import table. This means that in order to intercept the sendto call, you just need to replace the address of the original sendto in the import table with the address of your function.

So, we set up interception and started receiving data. The prototype of the sendto function looks like this:

int sendto(
  _In_       SOCKET                s,
  _In_ const char                  *buf,
  _In_       int                   len,
  _In_       int                   flags,
  _In_       const struct sockaddr *to,
  _In_       int                   tolen

If s is a socket on port 53, then the dns response of len size will be located at the pointer buf. The format itself is described in RFC1035 , I will briefly describe what needs to be done to get to the data of interest.

The structure of the message in the standard is described as follows:

In the header of the necessary information: message type, error code and the number of elements in sections. The title itself looks like this:

  uint16_t id; // identification number
  uint8_t rd : 1; // recursion desired
  uint8_t tc : 1; // truncated message
  uint8_t aa : 1; // authoritive answer
  uint8_t opcode : 4; // purpose of message
  uint8_t qr : 1; // query/response flag
  uint8_t rcode : 4; // response code
  uint8_t cd : 1; // checking disabled
  uint8_t ad : 1; // authenticated data
  uint8_t z : 1; // its z! reserved
  uint8_t ra : 1; // recursion available
  uint16_t q_count; // number of question entries
  uint16_t ans_count; // number of answer entries
  uint16_t auth_count; // number of authority entries
  uint16_t add_count; // number of resource entries

The Question section will have to be parsed in order to get to Answer. The section itself consists of as many blocks as indicated in the header (q_count). Each block consists of a name, type and class of request. The name is encoded as a sequence of strings, each of which begins with a byte with a string length. At the end is a string of zero length. For example, the name homedomain2008.ru will look like this:

The Answers section looks similar: a block consists of a name, type, class, ttl and additional data. The IP address will be contained in add. data. With the parsing of the name there is another difficulty. Apparently, to reduce the size of the message, instead of the length of the label, you can find a link to another data area. It is encoded as follows: if the 2 most significant bits of length are 11, then the next byte, as well as the least significant bits of length, should be interpreted as an offset in bytes relative to the beginning of the message. Further analysis of the name must be done by going over this offset.

So, we intercepted the necessary API, parsed the dns answer, now we need to make a decision: skip this answer further or return an error. For each name that is not yet present in the database, from the answer you need to check whether it is "suspicious" or not.
We will consider “suspicious” such names for which the result of the skeleton function from Unicode Technical Standard tr39 matches the result from any of the names in the database, or those names that differ from those in the database by rearranging the internal letters. To implement checks, we will store 2 tables. The first one will consist of skeleton results for all names from the database, in the second table we write the lines that were obtained from the database lines by removing the first and last characters from each label except the first level, and then sorting the remaining characters of each label. Now, if the new name is included in one of the two tables, then we consider it suspicious.

The meaning of the skeleton function is to determine the similarity of two lines, for this, characters are normalized for each line. For example, Xlœ will be converted to Xloe, and thus, by comparing the result of a function, we can determine the similarity of unicode strings.

An example implementation of the above can be found on github .
Obviously, the solution presented in practice cannot provide normal protection, because in addition to minor technical problems with interception, there is an even bigger problem with the detection of “similar” names. It would be nice to handle:

  • Combinations of permutations and homoglyphs.
  • Adding \ replacing characters not counted by skeleton.
  • UTS tr39 is not limited to skeleton, you can still limit the mixing of character sets in one label.
  • Japanese full-width point and other label separator.
  • And beautiful things like rnicrosoft.com

Also popular now: