PULLING MIKROTIK INTO THE LIMELIGHT

IoT 2年前 (2022) admin
592 0 0

So, you want to start reverse engineering MikroTik routers. Where do you start? As opposed to many routers which act more as a collection of independent binaries for each service, MikroTik devices implement a system of interconnected binaries which perform tasks for one another. Unfortunately, there is limited publicly available information about how this system-wide implementation works, and the good, technical information available is now a few years old. In that time, MikroTik released a number of minor version updates and one major revision software upgrade, making some of the technical details obsolete.

Consequently, we are left generally in the dark as to how MikroTik works, and digging into its dense, hand-rolled C++ binaries filled with custom library calls is a daunting task.

This blog post, which overviews our presentation at REcon 2022, outlines key knowledge and introduces tools that we created during our research over the past handful of months.

The goal of that talk, and this post, is to refresh the publicly available MikroTik knowledge and provide a crash course on MikroTik internals that will bring you from potentially zero experience to a point where you are familiar and comfortable with key MikroTik concepts and abstractions.

This knowledge will jump-start your research, tool development, or whatever MikroTik-related tinkering in which you are interested. Let’s get started!

Overview

We approach our overarching goal in four ways: first, we dive into MikroTik’s RouterOS operating system, understanding how it loads firmware and boots processes. Specifically, we focus on how firmware packages are cryptographically signed, and how we can bypass signing to obtain a developer shell in a MikroTik RouterOS virtual machine. Next, we focus on a key concept central to MikroTik: its proprietary messaging protocol used for IPC. Third, we dive into how we can authenticate to different services, specifically reviewing a proprietary, hand-rolled cryptographic protocol used for multiple publicly exposed services. Finally, we introduce a novel post-authentication jailbreak for MikroTik devices running v6 firmware (current long-term release branch) that pops a shell on any virtual or physical device.

  1. Diving Deep into RouterOS Internals
  2. RouterOS IPC
  3. Hand-rolled Authentication
  4. Jailbreaking RouterOS

Diving Deep into RouterOS Internals

NPK Files and Backdoors

Unlike some IoT devices which frustratingly require intercepting software downloads or extracting firmware directly from hardware, MikroTik hosts its proprietary firmware on its software downloads page. This conveniently allows us to investigate firmware components and understand how RouterOS, MikroTik’s customized operating system, is organized. Opening up the file system, we see the following components:

  • /flash/rw/{disk, logs, tmp, store...} – writable region
  • /lib – core libraries
  • /nova/bin – system binaries
  • /nova/lib – system libraries
  • /nova/etc – system configuration
  • /pckg/{name}/nova/{bin, lib, etc} – package data

RouterOS software is distributed in .npk files (which we think stands for “nova package”). In recent versions of RouterOS, each NPK file contains a squashfs with the package data along with a cryptographic signature that RouterOS verifies during installation and every reboot.

PULLING MIKROTIK INTO THE LIMELIGHT

While RouterOS is locked down – meaning we cannot easily get a developer shell – there is a known backdoor that has existed for a long time. Specifically, if we login as the user with the admin password and have the package installed, RouterOS launches instead of the default restricted shell.develoption/pckg/option/bin/bash

That is exactly what we want for security research! However, there are two problems:

  1. The package does not exist outside of MikroTik offices (of course…)option
  2. Packages are signed, which means we cannot easily construct our own packageoption

In some previous versions of RouterOS it was possible to leverage known CVEs to “install” the package post-boot and enable this developer backdoor. See Jacob Baines’s Cleaner Wrasse program for an example of an automated tool that accomplishes this.option

However, since version 6.44 (2019), this tool no longer works and we need a different strategy…

Bypassing Signature Validation

Since we are hackers with unfettered access to the RouterOS firmware, let’s go straight to the source and figure out how RouterOS actually validates packages. After a bit of poking around, we discover that package validation occurs in the binary, a part of the compressed file located in the disk image’s boot sector.initinitrd.rgz

PULLING MIKROTIK INTO THE LIMELIGHT

Luckily for us, the binary invokes a single function to validate each package. We can find this function by looking for a reference to the string. In pseudo-code, the function works as follows:init%s/flash/var/pdb/%s/disabled

int check_signature(...){
    // magic
    snprintf(buf, 0x80, "%s/flash/var/pdb/%s/disabled");
    return is_valid;
}

All we need to do to bypass signature validation is find this function and patch it to return 1 every time. However, we run into a problem when we try to recompress this and patch our original …initrd.rgz

It turns out that the kernel is very finicky about what looks like. Specifically, we need to make sure that we match the expected size (both compressed and decompressed) and also the exact position in the disk image. If we do not match these properties then the kernel fails to decompress and the router fails to boot.initrd.rgzinitrd.rgz

The “Entropy Trick”

To solve the first two constraints, we make use of an “entropy trick.” Specifically, we create a dummy file, , inside our directory and adjust its size to match the decompressed size of the original directory. Then, by adjusting the amount of entropy in the file, we control the compressed size of :padinitrdinitrdinitrd.rgz

PULLING MIKROTIK INTO THE LIMELIGHT

For example, if contains all zeros (low entropy), its compressed size is small. On the other hand, if contains all random bytes (high entropy), its compressed size is large. As long as our target size falls between these two extremes, we can perform a binary search on the ratio of zeros to random bytes in order to exactly match the original compressed size.padpad

Ctrl+H

Unfortunately, if we now mount the filesystem in the boot image and copy over our modified file, the kernel still won’t boot. This is because the kernel expects that resides at a very specific location in the boot image. When we mount the filesystem and copy the file, it adjusts the position of the actual data. This problem is relatively easy to fix; we can simply do a find-and-replace for every 512 byte sector of the original and swap it with our modified . This strategy effectively operates on the raw bytes in the disk image instead of mounting the boot sector as a filesystem.initrd.rgzinitrd.rgzinitrd.rgzinitrd.rgz

PULLING MIKROTIK INTO THE LIMELIGHT

Unlocking the Backdoor

Now that we have successfully patched out signature validation, we are free to install a fake package (with an invalid signature) and enable our persistent developer backdoor!option

It is also helpful to include busybox in the package for reverse engineering research, since recent versions of RouterOS do not actually ship with any standard tools.option/bin

Once we reboot, we can simply and provide the admin password to get a familiar bash shell!telnet -l devel <ip>

PULLING MIKROTIK INTO THE LIMELIGHT

RouterOS IPC

Nova Messages

MikroTik designs RouterOS in a very modular fashion. The operating system contains more than 80 processes which communicate with each other through internal messages, and each process is generally responsible for one specific feature. For example, the binary handles authentication for all other processes.user

Upon boot, the process spawns which is RouterOS’s main control process. is responsible for spawning all of the other processes and managing interprocess communication. In some sense, is “the router’s router.”init/nova/bin/loaderloader/nova/bin/loader

RouterOS implements the bulk of its IPC in the shared library. This library contains methods for serializing and deserializing messages, constructing abstraction layers to handle requests, and facilitating process-wide communication abstractions. The extensive utilization of this custom framework across RouterOS binaries is one of the things that makes RouterOS a difficult reverse engineering target.libumsg.so

So let’s break it down together.

We’ll start with the core player: “Nova Message” ( internally). A nova message is a typed, key-value mapping. It comes in two flavors: a pseudo-JSON variant (now deprecated), and a serialized binary variant. You can recognize the binary messages because they always start with in ascii:nv::messageM2

PULLING MIKROTIK INTO THE LIMELIGHT

Dissecting a Message

Reverse engineering this message protocol shows that there are six types of data, which can each exist as a single value or as a list. We include the following cheat sheet to describe the serialization format in depth, and you can also find some open-source libraries which implement this protocol.

PULLING MIKROTIK INTO THE LIMELIGHT

Each nova message key is a 24-bit integer and certain keys have a special meaning inside RouterOS. For example, keys of the form correspond to the namespace and are used during message routing:0xFFxxxxSYS

PULLING MIKROTIK INTO THE LIMELIGHT

Particularly of interest are the keys for (destination binary), (origin binary), and (what operation to invoke).SYS_TOSYS_FROMSYS_CMD

Armed with this information, we can now start to make sense of some RouterOS code. In the following image, we see sending an authentication request to . It constructs a new nova message, setting to the address and setting to .wwwuserSYS_TO[13, 4]SYS_CMD1

PULLING MIKROTIK INTO THE LIMELIGHT

Turning x3 into Pseudo-XML

So how do we actually know which process corresponds to? First, remember that is responsible for spawning all the processes. When we look inside , we see it reads from a configuration file at using functions in the library. Unfortunately, this .x3 file is not plain XML but appears to be some serialized format. With a bit of reverse engineering and some coffee, we recover MikroTik’s “pseudo-XML” format specification:[13, 4]/nova/bin/loaderloader/nova/etc/loader/system.x3libuxml++.so

PULLING MIKROTIK INTO THE LIMELIGHT

And now we can convert this serialized file into a more readable XML format:

PULLING MIKROTIK INTO THE LIMELIGHT

Aha! Here we clearly see a list of process entries. And each entry has a parameter which seems to correspond to the file path and a parameter which must correspond to the RouterOS ID.74

Nova Handlers

So that explains the (‘s ID), but what is the deal with the ?13user4

It turns out that RouterOS processes can register “Nova Handlers” () which act as subsidiary components, capable of handling and responding to their own requests. In this case, we see that registers several handlers in , one of which is registered at index :nv::Handlerusermain4

PULLING MIKROTIK INTO THE LIMELIGHT

Neat!

It’s worth noting that every process also constructs a “Nova Looper” () which acts at the interconnect between the process (e.g. ) and the main controller (). The also contains a default handler, so if we were to send a message to address (instead of ), for example, it would be handled by ’s rather than a registered .nv::Looper/nova/bin/user/nova/bin/loaderLooper[13][13,4]userLooperHandler

IPC Message Routing

So this is cool and all, but how does it actually work? What actually happens when exchanges a message? How does the message end up in ’s login handler?wwwuser

Let’s take a look at an example request and response. In this example, we have two processes: (at address ) and (at address ). Additionally, has a handler registered at address . In this example, sends a request to and then responds:foo12bar34bar50foobar/subbar/sub

PULLING MIKROTIK INTO THE LIMELIGHT

Request (in blue):

  1. foo constructs a message with () and and invokes SYS_TO=[34,50]bar/subSYS_FROM=[]Looper.exchMessage()
  2. foo’s forwards this message to over a socket created when spawned Looperloaderloaderfoo
  3. loader receives the message, determines it is from based on the receiving socket, and determines the destination is (based on the first entry in foobarSYS_TO)
  4. loader prepends to , strips from , and forwards the message over a socket connected to 12SYS_FROM34SYS_TObar
  5. bar’s receives the message and, seeing that is not empty, it locates a handler with address LooperSYS_TO50
  6. bar’s successfully identifies the handler, strips from , and forwards the message (in the same process) to Loopersub50SYS_TObar/sub
  7. bar/sub receives the message and, seeing that is empty, handles the message directlySYS_TO

Response (in yellow):

  1. After generates a response, it flips the and lists in the original message.  Now, () and bar/subSYS_TOSYS_FROMSYS_TO=[12]fooSYS_FROM=[]
  2. bar/sub pushes the message up to its parent Looper
  3. bar’s receives the message and identifies which handler sent it. In this case it is , so it prepends to and forwards the message to over a pre-established socketLooperbar/sub50SYS_FROMLoader
  4. loader receives the message, identifies the origin is (based on the incoming socket), and identifies the destination is (based on the first entry in barfooSYS_TO)
  5. loader prepends to and strips the first entry from and then forwards the message to over a socket34SYS_FROMSYS_TOfoo
  6. foo receives the message and, seeing as is empty, handles itSYS_TO
  7. foo’s identifies this is a response to a previous request, prepares the return value, and returns execution to Looperlooper.exchMessage()

This is a really cool way of routing messages and provides some useful features:

First, since is constructed piece-by-piece as a message moves up the stack, this protocol protects against forgery; a binary or handler cannot spoof a source ID.SYS_FROM

Secondly, is not just the hub of all process management, but also all message proxying. This means that is free to terminate services that are only intermittently needed (e.g., the authentication binary) to free resources. If any services later require user authentication, receives a message destined for the now-terminated service and restarts it prior to proxying the message!loaderloaderuserloader

Finally, since message routing is performed dynamically, specific handlers can be refactored to other processes without much difficulty.

Multicast and Broadcast

While most RouterOS messages are point-to-point (e.g. remote procedure calls or notification messages), RouterOS also provides functionality for multicast and broadcast.

PULLING MIKROTIK INTO THE LIMELIGHT

A binary sends a multicast message by setting equal to followed by a list of targets. Or for a broadcast message, a binary simply sets equal to . Internally, parses these special formats and duplicates the message as necessary.SYS_TO[0xFE0002]SYS_TO[0xFE0001]loader

Hooking loader to Visualize IPC

Knowing all of these details, we wrote a tool to trace every internal RouterOS message. Because handles all messages, we need only sniff traffic passing through . Specifically, we proxy all messages from and forward them to a graphical front-end to visualize and decompose them. Included below is a demo of the tool. Notice that when we log in to the web interface there is a burst of authentication and data retrieval messages. Then, as we paginate through the web interface, we see additional requests for required data.loaderloaderloader

Hand-rolled Authentication

Security through Obscurity

Our next goal is to understand MikroTik’s authentication scheme so that we can build configuration or research tooling that hits post-authorization endpoints. It is therefore time to talk about everyone’s favorite topic: cryptographic protocols!

Initial investigation shows that the binary, listening for web configuration traffic on port 80, uses a standard Elliptic Curve Diffie-Hellman (ECDH) protocol to generate a shared secret over the Curve25519 elliptic curve, which is subsequently used to generate RC4 transfer and receive stream cipher keys.www

That is all rather generic…too generic for the likes of MikroTik.

Thrillingly (or tragically, depending on your perspective), the binary listening for Winbox traffic on port 8291 seemingly does not use ECDH. Investigation of internal messages from to shows a different series of data exchanged when compared against ’s authentication protocol.mproxymproxyuserwww

In a rare stroke of luck, MikroTik’s wiki page details that Winbox uses “EC-SRP5” for authentication.  Elliptic Curve Secure Remote Protocol (EC-SRP) is a rather obscure protocol, and the internet is fairly void of any good guides detailing its implementation. After a lot of digging through archives and cryptography guides, we find that the Wayback Machine holds the keys to the puzzle in the form of an IEEE submission draft from 2001.

To the best of our knowledge, this draft was never actually included as an IEEE standard, yet it is a great resource as it meticulously details EC-SRP’s cryptographic calculations. Let’s dive into it!

PULLING MIKROTIK INTO THE LIMELIGHT

Ouch. That is a bit more complicated than ECDH. Let’s break it down piece by piece so we are not overwhelmed.

Rationalizing the Differences

What we first notice is that this guide has some noticeable differences compared to the MikroTik implementation. Two major discrepancies jump out.

First, the guide makes no reference to the Montgomery (Curve25519) curve and all calculations are performed over the Weierstrass curve. However, with some extensive reverse engineering we find that RouterOS only converts Weierstrass curve points to Montgomery form right before public keys are shared, and it performs all elliptic curve math over the Weierstrass curve.

So we can abstract away that detail and focus on the math.

The second difference is that MikroTik seemingly performs more math operations under the hood, convoluting the high-level operations that the IEEE submission draft details. Dynamic reverse engineering shows us that elliptic curve calculations result in points with z coordinates, which is curious given the commonly defined Weierstrass equation is two-dimensional: . With yet more research we find that MikroTik actually performs calculations using the projective Weierstrass form in three dimensions, , and later projects the three dimensional point onto the plane to convert back into two dimensions.Y2=X3+aX+by2=x3+axz4+bz6Z=1

Again, this is an implementation detail that we can abstract away for the sake of our comparison.

With these two details out of the way, we can focus on a one-to-one comparison between MikroTik and IEEE submission draft operations.

Fingerprinting the Similarities…(or lack thereof)

The first thing we notice is that the client public key calculation is identical, and also matches ECDH. The server public key calculation diverges from ECDH because it injects username and password information into the calculation to perform authentication during the key exchange. This is a feature of Password-Authenticated Key Exchanges (PAKEs), which is a core concept of all Secure Remote Protocols, including EC-SRP.

Unfortunately, when we compare MikroTik’s server public key implementation against the draft, we find a significant difference: MikroTik hashes the x coordinate of a generated gamma point twice, whereas the draft only hashes once.

PULLING MIKROTIK INTO THE LIMELIGHT

This is concerning because hashes are, by definition, irreversible. The MikroTik client will consequently need to compensate when performing its calculation to account for this difference.

It is therefore unsurprising when we find that there are a number of differences in the MikroTik calculation, as shown below in orange, versus the IEEE submission draft. It is rather remarkable that, even with these alterations, the MikroTik server and client still successfully generate a mutual shared secret. Feel free to marvel at that realization, we surely did!

PULLING MIKROTIK INTO THE LIMELIGHT

Finalizing the Protocol

There are a few final details required to successfully authenticate now that we have our shared secret. These include:

  • Preparing and transmitting confirmation codes to confirm that both sides share the same secret. This also guarantees authentication, since an incorrect username or password would generate a wildly different point
  • Generating AES-CBC and HMAC keys for tx and rx
  • Implementing unique block padding for the AES cipher, which is almost-but-not-quite PKCS7
  • Accounting for fragmented messages over 255 bytes in length

With those final details in place, we can now send encrypted messages and decrypt received messages from the MikroTik server!

Tools and Further Reading

If you are only interested in the final result, have no fear. We implemented a client version of Winbox and MAC Telnet (another common RouterOS configuration) service that you can plug-and-play. For those more interested in watching the authentication scheme progress, we also have a Winbox server implementation that can connect to the Winbox client. These tools are included in the first link below.

For those interested in the IEEE submission draft protocol, we created a client and server version of that protocol available in the second link.

Finally, we have additional details on the EC-SRP protocol and MikroTIk’s projective space calculations in a previous blog post, which you will find in the third link.

  1. MikroTik Authentication
  2. EC-SRP Authentication, as defined in the IEEE draft
  3. MikroTik Authentication Revealed

Jailbreaking RouterOS

Listening in on a Conversation between www…and Itself?

Let’s dive into a remote jailbreak we discovered in RouterOS! Our journey starts with the binary which manages MikroTik’s WebFig web configuration portal exposed on port 80 by default.www

After a bit of initial reverse engineering, we discover that uses a servlet model to handle requests. This is a fairly common pattern for web servers and generally makes code nice and modular. Specifically registers certain url prefixes to servlets which implement the actual , , etc., methods.wwwwwwdoGetdoPost

For example, if we load , the servlet handles these requests. Similarly if we load , this request is handled by the servlet. You get the picture. In total there are seven of these custom servlets (not including the built-in servlets like )./jsproxy/…jsproxy/scep/…scepdir

Interestingly, the code for these servlets is actually separated into special shared libraries located in . For example, the code for the jsproxy servlet is located in ..p/nova/etc/www/nova/etc/www/jsproxy.p

In the spirit of conserving virtual memory, utilizes lazy loading when dealing with servlets. loads no servlets initially, and only upon the first request to a servlet is one actually loaded.wwwwww

That seems like a pretty good model; but as we use the message tracer we noticed something interesting when a servlet is loaded. Specifically we see a strange message from to (handler #2).wwwwww/2

PULLING MIKROTIK INTO THE LIMELIGHT

There are two things that caught our attention:

  1. Why is sending a message to itself?www
  2. Why do some of the values look like virtual addresses? (note: we’re looking at 32-bit x86 here). IPC messaging is for sending messages between processes, and virtual addresses should be meaningless

When we examine the handler for we see something even more frightening: it appears to pull a pointer from the message object and invoke it as a function?!?www/2

It turns out that when a servlet is first loaded, it needs to register itself with the process. And even though the servlet is loaded in the same process as , the MikroTik developers decided to use IPC to perform this initial handshake rather than doing something more reasonable like having look up any needed symbols in the servlet library…wwwwwwwww

Very spicy! If we could hit this handler with an arbitrary message, we could invoke any pointer and surely get a shell! But can we hit it?

Permission Escalation to super-admin

As an end-user, it is possible to send arbitrary messages into the system through one of the several proxy binaries, but we need to authenticate first. And since we already reverse-engineered the Winbox client as described in the previous section, all the hard authentication work is done! We can send arbitrary messages, but unfortunately hitting the handler is not quite that easy.

It turns out that RouterOS handlers are also gated based on a policy bitmask. In this case, our vulnerable handler (handler #2, FoisHandler) has a required policy of which is unattainable via the GUI configuration panel. As an admin, our default policy bitmask is and the maximum we can set is .www/20x800000000x5FFFE0x7FFFE

Messages initiated internally have a maximum permission level by default, i.e., they run with super-admin privileges. But all of the messages we proxy through our Winbox client have their permission level set to something lower, which means we can never actually hit this handler with a proxied message.

Or can we?

The GUI controls policy levels with checkboxes that indicate certain privileges (e.g., read, write, ssh, reboot, etc). Using our message tracer, we see that internally this results in a single message sent with a combined bitmask value to indicate the permission level. The GUI will never send a message with a permission greater than , but what if we just send our own message with a permission of ?0x7FFFE0xFFFFFFFF

PULLING MIKROTIK INTO THE LIMELIGHT

This actually works and successfully upgrades our permission level from admin to super-admin, allowing us to hit the vulnerable handler!

ROPping and Popping

The last step is to achieve RCE, which we can do with the following steps:

  1. Upload a stage2 payload and busybox using FTP. The stage2 will execute a netcat listener using busybox and listen for traffic on port 1337. Because the exploit is post-authentication, we can use MikroTik’s FTP server to achieve this
  2. Send a crafted message to to escalate our privilegesuser
  3. Send a crafted message to FoisHandler, including a ROP chain in the body of our message which is stored on the stack
  4. Hijack PC using the controlled function pointer and pivot to the ROP chain
  5. ROP to to set our stage2 to executablechmod
  6. ROP to execute stage2
  7. Connect to the new reverse shell listening on port 1337
PULLING MIKROTIK INTO THE LIMELIGHT

POC or GTFO

And just like that, we remotely jailbreak RouterOS for the first time in three years! It should be noted that this exploit is only viable on RouterOS v6, as FoisHandler was removed in the v7 overhaul. While this outline gives you the information necessary to write your own exploit, stay tuned for a POC to be released in the coming days!

Conclusion

This blog post covered a lot, so it is worth rehashing the knowledge you gained if you stuck with us:

  • We now understand the construction of MikroTik firmware packages, and how to bypass cryptographic signing to get a developer shell on the RouterOS virtual machine
  • We took a deep dive into the IPC message protocol, how messages are crafted, and how acts as the “router’s router”loader
  • We endured a chaotic adventure into EC-SRP5 as implemented by MikroTik for its Winbox and MAC Telnet services, and now have client programs that perform authentication on our behalf
  • We added a novel jailbreak to our toolkit that exploits two post-authentication vulnerabilities to root any RouterOS v6 device, physical or virtual

Our intent for this post is to document these concepts and refresh the publicly available knowledge of MikroTik and RouterOS, with hopes of lowering the barrier to entry for other interested researchers and tinkerers. This is especially important because MikroTik gravitates towards obscurity, cluching tight to their hand-rolled source code and often leaving customer questions about implementation details unanswered.

You are now ready to start your own adventure into MikroTik research! Armed with this knowledge, together we can pull this target from the fringes of obscurity back into the limelight!

 

 

原文始发于Harrison Green & Ian Dupont :PULLING MIKROTIK INTO THE LIMELIGHT

版权声明:admin 发表于 2022年6月18日 上午10:44。
转载请注明:PULLING MIKROTIK INTO THE LIMELIGHT | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...