haakonnessjoen / MAC-Telnet

Open source MAC Telnet client and server for connecting to Mikrotik RouterOS routers and Posix devices using MAC addresses

Home Page:http://lunatic.no/2010/10/routeros-mac-telnet-application-for-linux-users/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Compatibility with RouterOS 6.43

eworm-de opened this issue · comments

Starting with RouterOS 6.43rc17 the protocol changed. This is the relevant changelog entry:

*) mac-telnet - require at least v6.43 MAC Telnet client when connecting to v6.43 or later version server;

Please update the wire protocol to support latest RouterOS.

I don't know if that can help, but according to my knowledge the changes might be related to the removal of login challenge.

Starting from 6.43, none of mikrotik logins will be based on challenge but replaced by cleartext credentials (because of internal password no more stored in cleartext but hashed).

If change is similar to API, the difference, at login, will be:

instead of receiving challenge at connect, and use it for /login
send credentials directly with /login, in cleartext

Update: I think the change should happen in
/* If we receive encryptionkey, transmit auth data back */
if (cpkt.cptype == MT_CPTYPE_ENCRYPTIONKEY) {
memcpy(encryptionkey, cpkt.data, cpkt.length);
send_auth(username, password);
}
where we'll no more receive encryptionkey but something else to know router is ready, then, send the login+password in clear.

Thanks for the heads up. I have not had time to check this out. But I will do some digging into the traffic as soon as possible. Sounds strange to send it in clear text. I would think they'd rather send the internal seed, or do som other encryption. Do they really want people to send passwords in cleartext over l2? I would think the api change would kind of push people over to tls protected api access.

Well, I will check soon. If anyone else wants to do it in the meantime, please keep me updated.

Thanks @haakonnessjoen

That sounds weird to send passwords in cleartext, but in 6.43, API will work like this, without TLS, except if you use API-SSL.
A really big change for a minor release... moreover even with TLS, there is no guarantee (except using own certificates) to be sure the device you're talking to is really your router and not a "yes bot" recording your cleartext tries. :(

I'll make some captures, maybe today, if that can help i'll send my conclusions here.

I just made two captures, one with pre 6.43 and on with latest 6.43RC.

The differences i found was:

  • at "begin authentication" client sends an encryption key (it didn't on previous releases)
  • server replies with his encryption key (as previous releases)
  • dialog goes on as usual (seems to)

I didn't find how was hashed password.

On my captures, made with default admin/"" credentials, i got (according to wireshark decoding)
client key: 61646d696e002750a93030e5b7c186daa75f33a2c0121f73188d9c3fec202d86b4f41ff5f38101
server key: 0da6f6996aabdbd6c5359d3f98797127e6fd5fab77691b1137e2390aefc55fd9000c96a2cba25dbcccd3a94cf53807f1be
client password hash: cdfd993c78f37d513e5f66fe66858ecc0b3e7f0ec3ba7fa3c4e6796055c8d037

Attached are the pre and post 643 pcap captures.

pcap-captures.zip

I made some more tests, that is quite difficult for me to understand what Mikrotik does for its new hash.

I made 3 tests for two logins, one is valid "admin -> empty password" , one is invalid "toto -> empty password"

The client sends, as "key" the login followed by the real key.
The server sends a key.

What is strange is the key length is no more fix... but seems to depends on login size...

If anybody has any idea...

I asked Mikrotik support, they refused to tell anything about their proprietary protocol...

LOGIN "admin"

Client key
61:64:6d:69:6e:00:
73:89:d0:78:16:00:0d:d5:b4:83:f8:74:f1:02:73:02:53:5c:df:c2:12:9f:17:2b:f1:73:8c:b7:aa:94:b4:c7:01
Server key
12:f1:a5:82:82:68:2d:72:8c:ac:56:d6:10:7e:b7:61:76:b0:05:c8:a8:b5:55:67:90:a5:46:e1:3c:b0:33:c3:00:cd:02:95:3c:60:ed:44:75:17:fb:2e:45:b3:50:cd:4d
Password hash (empty)
9f:8f:50:04:97:71:2f:8e:5e:7e:f3:02:9a:1f:e4:a4:3f:de:30:09:f8:bc:a3:44:c9:64:15:12:af:b5:68:30

Client key
61:64:6d:69:6e:00:
32:c2:52:bc:30:20:c6:ad:df:60:7e:6d:83:fb:87:49:4e:55:0e:0f:33:1b:b1:9f:be:4b:4d:0b:d9:93:cb:1b:01
Server key
14:cb:88:a2:a9:06:7d:be:31:93:ae:da:da:92:59:6c:21:c5:55:8e:58:1b:eb:05:15:9d:37:10:2f:2c:41:5f:00:cd:02:95:3c:60:ed:44:75:17:fb:2e:45:b3:50:cd:4d
Password hash (empty)
6f:a2:25:36:fc:d9:d2:38:71:c6:9e:f9:c4:a6:45:bd:5a:a9:ba:d7:56:f9:3d:39:0c:ab:14:5f:c2:70:3d:f6

Client key
61:64:6d:69:6e:00:
77:44:7a:6a:35:d5:d6:49:bf:23:8b:f3:2e:8b:c4:08:b2:04:7b:76:b3:d8:c1:83:7e:ba:d8:7c:8b:02:7e:af:01
Server key
1c:c4:08:25:23:13:ec:9c:3e:11:98:7d:4e:a8:41:a9:ed:cb:2c:98:21:ae:c3:86:e8:88:f3:af:c4:56:a1:c9:00:cd:02:95:3c:60:ed:44:75:17:fb:2e:45:b3:50:cd:4d
Password hash (empty)
b1:c3:90:43:d9:37:6b:95:1a:e2:2c:dc:f5:0d:7d:1e:46:7a:4e:69:a8:4b:6e:d2:83:c2:b0:ff:e3:97:8b:5a

LOGIN "toto"

Client key
74:6f:74:6f:00:
3e:cd:da:cf:48:bd:cd:8e:31:40:96:79:17:1b:1d:2e:41:1f:b9:83:f1:e3:26:1a:6e:f9:5e:6b:bd:a7:40:b3:01
Server key
3a:2e:a1:04:e2:74:58:fb:bf:af:80:6c:5a:40:02:3c:44:c6:bf:d1:51:30:98:8e:a4:eb:fb:5b:cb:05:bf:c8:01
Password hash (empty)
24:a9:4e:eb:f9:6e:7b:6c:64:4c:33:5f:2f:f5:7c:28:4d:6a:24:15:3f:f7:2a:20

Client key
74:6f:74:6f:00:
18:a5:15:2b:a4:40:a6:e0:4f:0f:73:64:ba:1c:33:1c:9e:6a:40:f9:0c:19:14:a0:49:83:5f:de:c9:4d:63:24:00
Server key
5f:3f:2b:3d:d1:38:0c:9c:ca:86:d0:f9:1e:55:90:aa:8b:b9:2f:ff:21:43:28:b8:59:a9:d0:b9:81:c2:29:43:00
Password hash (empty)
6c:dc:1c:bd:84:f5:59:6a:2e:73:55:a5:f0:ce:a8:6a:7e:ec:3c:1f:7d:7d:b2:82

Client key
74:6f:74:6f:00:
4f:d8:63:62:06:7a:4f:94:6c:20:8b:19:a3:ca:46:09:65:4c:eb:62:fd:c6:24:d1:b6:35:75:88:53:9e:49:28:00
Server key
5f:4c:87:e8:5c:e3:bd:5b:7d:9b:64:72:5b:7e:de:fd:54:7b:ef:b0:13:0b:c1:bb:d7:f8:10:72:cd:86:93:78:01
Password hash (empty)
3e:6f:32:c6:ec:51:a5:34:b8:72:9b:fa:f2:95:24:bc:59:0e:0b:96:d1:32:c0:5b

Thanks for all your research. I will do some testing to see if I can get the same hashes.

I also reached out to Mikrotik, and ironically (since, they are heavily dependant upon open source), they don't want to disclose the information..

Thank you very much for the comments.
Currently mac-telnet is proprietary protocol, and we do not want to disclose additional information at the moment.

if the key has variable length depending on the login size, its likelly that the login is also included in the same string. moreover it seems not to be a hash but a encrypted key with some sort of algorithm since hash would always have a fixed length

@BrainSlayer RSA? The winbox.exe binary is using RSA at some point

@Enrico204 hard to say. i dont know yet what they changed in the protocol. i'm still using it as option in my dd-wrt firmware with some modifications since this project here had some bugs with multiple interfaces. as soon as i find out how it works with the changed protocol i do my best to integrate it . just had no mood yet to disassemble winbox to see what has been changed

Don't worry, they changed their mind, and went back to original behavior with mac-telnet, so the code is still working like a charm...

Hoping they won't get change their mind again...

We may consider this issue as closed... until they get back with these (or other) changes

i believe they changed it because of some security issues i was reading about. i dont know if it belongs to the protocol itself.

RouterOS used to store passwords in plain text, that changed with version 6.43. Due to password being hashed on the device the login mechanism had to change.

Just tried to login to RouterOS 6.43.12 and 6.44rc1 with current mactelnet... And succeeded. So wondering when, why and how that changed. I can't believe they changed their mind and keep clear text passwords...

Well the same changelog contained:

*) user - all passwords are now hashed and encrypted, plaintext passwords are kept for downgrade (will be removed in later upgrades);

Perhaps they decided to allow old and new login mechanism until final removal?

Latest changelog for version 6.45beta54 contains:

!) user - removed insecure password storage;

Manual:Security MikroTik Wiki lists security related changes for RouterOS v6.45 and later. It includes:

MAC telnet uses EC-SRP5 for authentication, to connect to newer server, client needs to be upgraded;

I can't find info about EC-SRP5. Does anyone has any clue about this? I'm willing to study it (in my free time) and try to see if I can make a pull request to have the compatibility again

As far as I remember, I found some info sbout it in a SIP rfc. But there they use a SIP uri as part of the key if I remember correctly. So the big question is how the key would look like on mac-telnet. Maybe someone could do some reverse engineering of terminal.exe if it still exists, or winbox when using mac-telnet.

EC-SRP5 is defined in IEEE 1363.2

Indeed, I tried to implement following the IEEE 1363.2 (https://tools.ietf.org/html/draft-liu-sipcore-ec-srp5-00), but they do not exactly follow the standard.

First message from client to "server" is Username-0x00-32bytehash (which I suppose is the public key).
Then "server" replies with 32bytehash-0x00orOx01-16bytealwaysthesame (which I suppose is the server public key and shared secret)
Then the client should response with a proof. Following the standard this could be a long hash since it's calculated with eliptic curve, but in case of Mikrotik it's always a 32 byte hash (I guess they do some SHA256 with it).

So, it's not completely clear what is being communicated, I can only guess for now. And also not clear what the final 32byte hash is representing and more importantly how to calculate it.

I didn't have much time yet and won't have in the next week, but if nobody else found it by then I will continue looking for it....

commented

Edited based on @ius and @jacob-baines comments.

As Mikrotik hinted us with the name "EC-SRP5" protocol used for Winbox and mac-telnet should be similar to SRP with original Diffie-Hellman (https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol). Instead of using multiplicative group of integers modulo p as in original DH it should be using elliptic curves("EC"). An elliptical Weierstrass curve Wei25519 makes 32 byte length messages that perfectly correlates with collected packets.

Crypto parameters assumptions

  • An elliptical Weierstrass curve Wei25519

  • Multiplier parameter k = 3, but might be less.

  • Hash function H is SHA256.

Step 0. Server

s = random() #salt
x_s = H(salt,H(username+':'+password)) stored in the /rw/store/user.dat
username, salt and hash password are now stored on the server instead of cleartext password
V = x_s * G # V = verifier, G - Base point of the elliptic curve

Step 1. Client start by sending username and ephemeral public key A

I = username
a = random()
A = a * G
Send(I, 0x00, A, 0x01)

Client packet 1 {username} {\x00} {32 bytes of public key data} {\x01}

Step 2. Server response by sending ephemeral public key B and salt

b = random()
B = k * V + b * G
Send(B,0x00,salt) #salt for user I

Server packet 1 {32 bytes of public key}{an extra byte}{16 bytes of salt}

Step 3. Client response by sending hash of session key

u = H(A, B)
x_c = H(salt,H(username+':'+password))
K_c = (a + u*x_c) * (B - k * x_c * G)
Send(K_c) or Send(H(K_c))

Client packet 2 {32 bytes of verification code} or {24 bytes if server did not return salt}.

Step 4. Server verifies received hash of session key

u = H(A, B)
K_s = b * (A + u * V)
Server compares calculated session key K_s with received K_c.

PS In general this protocol looks closer to ed25519/SPAKE2 more than SIP EC-SRP5.

PPS

I will do some testing to see if I can get the same hashes.

It is impossible due to unknown random values used by both client and server to generate public keys.

Following the standard this could be a long hash since it's calculated with eliptic curve, but in case of Mikrotik it's always a 32 byte hash (I guess they do some SHA256 with it).

Elliptic curve 25519 with sha256 gives you 32 byte messages

PPPS Protocol is vulnerable to user enumeration attack due to the fact that server doesn't reply with salt for non-existing usernames. Besides that protocol should be pretty bullet-proof.

commented

I've spent quite some time trying to replicate ECSRP::generateSRPResponse by reverse engineering WinBox (i.e. client only), some additonal observations:

  • RouterOS' libucrypto.so contains quite some symbols, but debugging it is a bit more involved compared to just attaching gdb to wine winbox.exe.

  • For the client end at least, I found the EC-SRP5 draft to be the most accurate representation. It relies on primitives from the IEEE 1362 series, so you'll need both..

  • Internally, MikroTik appears to map between Curve25519 and it's isomorphism Wei25519 (see lwig-curve-representations) and performs most curve arithmetic on the latter.

I'm actually quite close to being able to reproduce the protocol from a client perspective, but I've gotten stuck on the last few bits of curve maths, that is: I fail to reproduce the output of what should be calls to WCurve::add (0x4b017d) and WCurve::mul (0x4b029d).

I'd have to clean up my notes (and Sage code) for it to be remotely useful to anyone. If someone happens to understand those two WCurve operations I might be able to piece it all together..


Somewhat related: looks like @jacob-baines's WinBox protocol reimplementation might benefit from this as well, by the way.

commented

Interesting observations, yeah no problem to use other curves like the Weierstrass one instead. I suspected they can use different base point, but bringing non-standard curve is even beyond that. Typical Mikrotik I would say :)

Please share your notes. If the curve math is the only remaining part you are really close.
Also you might have the expected In/Out of addition/multiplication on the curve that will save time.

commented

One of the quick and dirty implementations of the curve math
https://github.com/seb-m/wcurve/blob/master/wcurve.py

It is done for standard secp256r1 but it should work for Wei25519 just by changing a/b/Gx/Gy

I'm unfamiliar with the interface that mac-telnet interacts with (and, unfortunately, mac-telnet in general), but as @ius mentioned above I sort of maintain a winbox implementation (although its more for offensive security purposes). Either way, by some dumb luck I'd actually been poking at this over the last two days. I'm happy to share what I know... which is precious little and potentially incorrect.

New Winbox establishes authentication/encryption via four packets. The first packet is from the client and it looks like this:

C: {length byte} {Handler in mproxy (\x06)} {username} {\x00} {32 bytes of public key data} {Useless trailing byte}

The public key, as mentioned, is a Curve25519 key. I've been using https://github.com/agl/curve25519-donna as my curve implementation. I previously used that library to get authentication with the web interface working again, so I thought it was probably a safe bet.

The server accepts the client's message and looks up the user's salt in /rw/store/user.dat. The server then generates its own key pair and replies with:

S: {length byte}{Handler in mproxy (\x06)}{32 bytes of public key}{an extra byte}{16 bytes of salt}

I'm not really sure what the Client does with this at the moment. My focus has been entirely on the server implementation. So, suffice to say the Client does something and then sends this:

C: {length byte}{Handler in mproyx{\x06}}{32 bytes of validation code}

So this is the part I'm working on. The server needs to generate the validation code on its own. The following is what I think it does / I want it to do, although I know for a fact that I'm not generating one variable correctly at the moment... so I could be totally wrong!

The server:

  1. Generates a SHA256 over both public keys (from packet 1 and packet 2).
  2. Extract the verify key from user.dat. This is generated when the user is added to the system. It's just the public key generated from the private key SHA256(salt,SHA256(username+':'+password)).
  3. Generate a session key using curve_donna(session, sha256(publicC, publicS), verify key)
    ...
    So my problem right now is I'm not getting the session key that I'm expecting. I'm actually a little confused with how the server is translating the verify key in this spot. Either way, once the correct value is generated there are a few additional SHA256 hashes to generate the matching verification code and then you can generate your session keys.

I'll share any code if I actually figure it out.

Edit: Oh and its worth mentioning that much of the Winbox logic is happening in /nova/bin/user. I'm not sure if the libucrypt.so ECSRP functions were inlined or what.

commented

@jacob-baines saw your Defcon presentation so I would say I am more than familiar with your work :)

mac-telnet is definitely using very similar algorithm from what you are describing and you've added couple missing parts on the server side that I missed In my assumption above.

  1. Generates a SHA256 over both public keys (from packet 1 and packet 2)

Exactly as my Step 3

  1. Extract the verify key from user.dat. This is generated when the user is added to the system. It's just the public key generated from the private key SHA256(salt,SHA256(username+':'+password)).

So you are saying that user.dat stores salt and SHA256(username+':'+password)? In the step 0 I assumed it is just password, but if you are sure I will update the description.

  1. Generate a session key using curve_donna(session, sha256(publicC, publicS), verify key)

/nova/bin/user is dynamically linked with libucrypt.so and imports all the necessary crypto functions. As @ius mentioned what you think is Curve25519 is most probably Wei25519 curve.

The calculation for session key is as follows with addition and multiplication on the Weierstrass curve.
K_c = (a + u*x_c) * (B - k * x_c * G)

curve25519_donna API implementation was designed for both sides knowing the cleartext password to generate the session key. So I am not sure if it is straightforward to reuse curve_donna for required calculations.

So you are saying that user.dat stores salt and SHA256(username+':'+password)? In the step 0 I assumed it is just password, but if you are sure I will update the description.

To clarify, for each user usr.dat stores a salt and curve25519 public key generated by creating a key pair with SHA(salt,SHA256(username:password)). Here's a snippet of code I wrote to implement this (forgive me, I never planned on sharing this code):

    std::string username("test");
    std::string sep(":");
    std::string password("lolwat");

    SHA256 test;
    test.init();
    test.update((const unsigned char*)username.c_str(), username.size());
    test.update((const unsigned char*)sep.c_str(), sep.size());
    test.update((const unsigned char*)password.c_str(), password.size());

    std::array<unsigned char, 32> result = { 0 };
    test.final(&result[0]);

    std::cout << "SHA(user:pass):" << std::endl;
    for (int i = 0; i < SHA256::DIGEST_SIZE; i++)
    {
        std::cout << std::hex << ((int)result[i] & 0xff) << " ";
    }
    std::cout << std::endl;

    SHA256 final;
    final.init();
    std::string salt("\x31\x7d\x14\x2c\x02\x2f\xf7\x8d\xd7\xf3\x03\xc0\x1d\x69\xef\xe1", 16);
    final.update((const unsigned char*)salt.c_str(), salt.size());
    final.update(&result[0], SHA256::DIGEST_SIZE);
    final.final(&result[0]);

    std::cout << "SHA(salt, SHA(user:pass)):" << std::endl;
    for (int i = 0; i < SHA256::DIGEST_SIZE; i++)
    {
        std::cout << std::hex << ((int)result[i] & 0xff) << " ";
    }
    std::cout << std::endl;

    //! Our curve25519 private key
    std::array<unsigned char, 32> verifier;
    std::array<unsigned char, 32> basepoint = {9};
    std::reverse(result.begin(), result.end());
    curve25519_donna(&verifier[0], &result[0], &basepoint[0]);
    std::reverse(verifier.begin(), verifier.end());
    std::cout << "user.dat public key:" << std::endl;
    for (int i = 0; i < 32; i++)
    {
        std::cout << std::hex << ((int)verifier[i] & 0xff) << " ";
    }
    std::cout << std::endl;
albinolobster@ns1:~/routeros1/poc/login/build$ ./login_test -i 192.168.1.50 -u admin --password lolwat
SHA(user:pass):
7b 43 17 60 ce 6a 2b 17 84 e3 14 2c 95 c8 da 2e 12 30 d4 90 42 80 6b ce 44 18 60 a5 30 95 7f f7 
SHA(salt, SHA(user:pass)):
1e 9f d0 96 f3 e6 19 86 23 22 88 ac 95 c7 f1 fe 91 e4 ed fb 6f 36 1e a1 7c 4e 31 58 d2 9e da 10 
user.dat public key:
2b cc f 24 1c 69 d3 c3 c6 dd 85 79 38 58 ec 1b d1 13 8d c7 55 61 46 4b 1b 22 b7 de a5 f2 ea f3

Screen Shot 2019-10-10 at 8 57 55 PM

commented

Interesting! Let me think about it.

commented

@ius and @jacob-baines this is what I came with:

Crypto parameters assumptions

Step 0. Server

s = random() #salt
i = H(salt,H(username+':'+password))
v = i*G # v = verifier, G - Base point of the elliptic curve, multiplication on the Curve25519 #42 (comment)

Salt and Verifier are stored in the /rw/store/user.dat

Step 1. Client starts by sending username and ephemeral public key Wc

Tc = random()
Wc = Tc*G
Send(username, 0x00, Wc , 0x01)

Client packet 1 {username} {\x00} {32 bytes of public key data} {\x01}

Step 2. Server responses by sending password entangled public key Ws and salt

Ts = random()
Ws = CPEPKGP-SRP5-SERVER(Ts, v)
Send(Ws,0x00,salt) #salt for user I

Server packet 1 {32 bytes of public key}{an extra byte}{16 bytes of salt}

Step 3. Client responses by sending confirmation value Cc

ic = H(salt,H(username+':'+password))
vc = ic * G
Z = ECSVDP-SRP5-CLIENT (Tc, Password , A , Ws)
Cc=H(0x04, Wc, Ws, Z, vc)

Client packet 2 {32 bytes of confirmation value Cc } or by some reason{24 bytes if server did not return salt}.

Step 4. Server verifies received confirmation value Cc with calculated Cc’

Zs = ECSDVP-SRP5-SERVER(Ts, v, Wc, Ws)
Cc'=H(0x04, Wc, Ws, Zs, v)
Server compares Cc’ with received Cc

commented

Regarding Wei25519 in fact it is just an alternative representation of points of Curve25519. I suck at ECC so got myself easily confused. Curve25519, Ed25519 and Wei25519 is just a representation form of the same elliptic curve. Different forms just provide benefits in doing different types of scalar multiplications. That is why Montgomery form of this curve multiplications worked for user.dat while Mikrotik is using math in Weierstrass form.

commented

what's the status on this? any progress being made? I'd be willing to help out with testing. Unfortunately, I can't be of much help with the reverse engineering of the encryption algo, but I'd be willing lend an extra set of hands in order to help get this worked out.

Another one with the same issue :(

mactelnet not working on 6.45.7 (current stable)

I think that many Mikrotik network administrator uses this tool maybe Mikrotik should give a hand...

Mikrotik give a hand :-) made my day. the only think mikrotik never cared of are third party developers and free software. mikrotik wants that you only use mikrotik software and hardware. welcome to this jail

Hello everybody! Few month ago I had a task to make Windows application which can connect to Mikrotik device by mac-telnet. After spending a lot of time serching and following this issue, I used other solution. We use second device Mikrotik with IP address, fortunately our customers usually have it in their networks. My application connects to it by SSH and from this device to other by mac-telnet, like this we can set IP address etc. Yes, this is not the best and optimal solution, but mac-telnet "device-device" will work, whatever Router-OS version. I hope this solution will be useful for someone.

Or you use CHR in a virtual machine... As bandwidth is not an issue even free license will do.
Still annoying, just using mac-telnet would be much more convenient.

I know that there are workarounds but I think we should keep this issue to improve the mac-telnet implementaton maintained by @haakonnessjoen, not to talk about the workarrounds without this software.

Looks like i found oss implementation for the winbox. tenable/routeros@8014a2b . Hopefully they using something +- same for other tools

commented

I guess that is the implementation from @jacob-baines or his colleagues for the web part of RouterOS 6.43..6.45 described in #42 (comment)

Yes, unfortunately, my routeros repo is not relevant to this blocker.

...thats sad, also found that. Same problem affects my btest OSS implementation.

If anyone is still working on this: The android app uses a native library for crypto, so we have a x86 shared library that should be usable on linux:
https://apkpure.com/mikrotik/com.mikrotik.android.tikapp/variant/1.3.12-XAPK
Everything is in libnative.so (xapk contains > config.x86.apk contains > lib), it exports much more library functions than used by Java, it has symbols and is quite readable in IDA. Could be a used as a easy starting point.

Exported functions
ECSRP::generateMSCHAPResponse(vector<uchar> const&)                                       0005808A        
ECSRP::generateResponse(vector<uchar> const&)                                             00059A1E        
ECSRP::generateSRPResponse(vector<uchar> const&,vector<uchar> const&)                     000584AE        
ECSRP::getChallenge(void)                                                                 000599B4        
ECSRP::verifyReply(vector<uchar> const&)                                                  00059B4C        
ECSRP::~ECSRP()                                                                           0005CD48        

SecureProxy::SecureProxy(void)                                                            0005B33C        
SecureProxy::disconnect(string const&)                                                    0005C7C2        
SecureProxy::getOutBuffer(void)                                                           0005CCB8        
SecureProxy::getState(void)                                                               0005B64A        
SecureProxy::handle(nv::message &)                                                        0005BDDA        
SecureProxy::isStateGood(void)                                                            0005B71A        
SecureProxy::processFrame(uint,uchar const*,uint)                                         0005C9B4        
SecureProxy::recvChallenge(uchar const*,uint)                                             0005C122        
SecureProxy::recvResponse(uchar const*,uint)                                              0005C7D8        
SecureProxy::sendChallenge(void)                                                          0005BCA0        
SecureProxy::sendFrame(int,uchar *,uint)                                                  0005B548        
SecureProxy::setState(nv::message const&)                                                 0005B72A        
SecureProxy::startKeyExchange(string const&,string const&)                                0005BACA        
SecureProxy::~SecureProxy()                                                               0005B448        
SecureProxy::~SecureProxy()                                                               0005B516        


WCurve::Point::Point(RedNum &&,RedNum &,RedNum &)                                         00055730        
WCurve::Point::Point(WCurve::Point const&)                                                00059BC6        
WCurve::Point::Point(void)                                                                00055C40        
WCurve::Point::operator=(WCurve::Point const&)                                            0005534E        
WCurve::WCurve(BigNum const&,BigNum const&,Reducer *)                                     00054FA0        
WCurve::add(WCurve::Point &,WCurve::Point const&,RedNum *)                                000550FE        
WCurve::add(WCurve::Point const&,WCurve::Point const&)                                    00059188        
WCurve::dbl(WCurve::Point &,RedNum *)                                                     000553AA        
WCurve::inv(WCurve::Point &&)                                                             000592BC        
WCurve::map(ECPoint const&)                                                               000555E6        
WCurve::map(WCurve::Point &&)                                                             000561D4        
WCurve::mul(BigNum const&,WCurve::Point const&)                                           000557EE        
WCurve::mul2add(BigNum const&,WCurve::Point const&,BigNum const&,WCurve::Point const&)    00055D18           
WCurve::~WCurve()                                                                         000567F4

Curve25519::Curve25519(void)                                                              000565AC        
Curve25519::fromBin(vector<uchar> const&)                                                 000573D8        
Curve25519::getX(vector<uchar> const&)                                                    000575EA        
Curve25519::getY(RedNum const&,uint,BigNum &)                                             00056838        
Curve25519::isValid(WCurve::Point const&)                                                 000572DE        
Curve25519::map(BigNum const&,uint)                                                       00056EA4        
Curve25519::map(WCurve::Point &&,uint *)                                                  00057076        
Curve25519::toBin(WCurve::Point &&)                                                       000574A8        
Curve25519::xBin(WCurve::Point &&)                                                        00058426        

Java_com_mikrotik_android_tikapp_JNILib_createProxy                                       000403ED        
Java_com_mikrotik_android_tikapp_JNILib_getRelayID                                        00040943        
Java_com_mikrotik_android_tikapp_JNILib_handle                                            000407DF        
Java_com_mikrotik_android_tikapp_JNILib_oldDoHandshaking                                  00040C56        
Java_com_mikrotik_android_tikapp_JNILib_oldGetRelayID                                     00040953        
Java_com_mikrotik_android_tikapp_JNILib_oldHandle                                         00040A78        
Java_com_mikrotik_android_tikapp_JNILib_oldInit                                           00040A24        
Java_com_mikrotik_android_tikapp_JNILib_oldIsConnected                                    0004097C        
Java_com_mikrotik_android_tikapp_JNILib_oldJniLogin                                       000409A6        
Java_com_mikrotik_android_tikapp_JNILib_oldProcessFrame                                   00040CCD        
Java_com_mikrotik_android_tikapp_JNILib_oldSetRelayID                                     00040966        
Java_com_mikrotik_android_tikapp_JNILib_oldStartHandshake                                 00040BA4        
Java_com_mikrotik_android_tikapp_JNILib_processFrame                                      000405B5        
Java_com_mikrotik_android_tikapp_JNILib_proxyState                                        00040909        
Java_com_mikrotik_android_tikapp_JNILib_rc4encrypt                                        00040EAE        
Java_com_mikrotik_android_tikapp_JNILib_setRelayID                                        00040930        
Java_com_mikrotik_android_tikapp_JNILib_startKeyExchange                                  00040410        

AES128::AES128(uchar const*)                                                              00050A4C        
AES128::blockLength(void)                                                                 0005137A        
AES128::decrypt(uchar const*,uchar *)                                                     00050EC0        
AES128::encrypt(uchar const*,uchar *)                                                     00050C20        
AES128::~AES128()                                                                         00051352        
AES192::AES192(uchar const*)                                                              000511E6        
AES192::blockLength(void)                                                                 000513B2        
AES192::decrypt(uchar const*,uchar *)                                                     00051260        
AES192::encrypt(uchar const*,uchar *)                                                     00051226        
AES192::~AES192()                                                                         0005138A        
AES256::AES256(uchar const*)                                                              0005129A        
AES256::blockLength(void)                                                                 000513EA        
AES256::decrypt(uchar const*,uchar *)                                                     00051318        
AES256::encrypt(uchar const*,uchar *)                                                     000512DE        
AES256::~AES256()                                                                         000513C2        
BigNum::BigNum(asn1::blob)                                                                000515C0        
BigNum::addToDigit(uint,uint)                                                             000518A6        
BigNum::bin(void)                                                                         000517B4        
BigNum::bits(void)                                                                        00051888        
BigNum::clamp(void)                                                                       00051772        
BigNum::modBy2Pow(uint)                                                                   0005276A        
BigNum::operator*=(BigNum const&)                                                         00052466        
BigNum::operator*=(uint)                                                                  00052518        
BigNum::operator+=(BigNum const&)                                                         00051B52        
BigNum::operator+=(uint)                                                                  00051CA6        
BigNum::operator-=(BigNum const&)                                                         00051CE0        
BigNum::operator<(BigNum const&)                                                          00051B06        
BigNum::operator<<=(uint)                                                                 00051C42        
BigNum::operator=(uint)                                                                   0005309A        
BigNum::operator>>=(uint)                                                                 0005269A        
BigNum::reduce(BigNum const&,uint)                                                        00052826        
BigNum::shiftLeftByDigits(uint)                                                           00051934        
BigNum::shiftRightByDigits(uint)                                                          0005197A        
BigNum::str(void)                                                                         00051A1E        
BigNum::~BigNum()                                                                         000548E2        
BlockCipher::~BlockCipher()                                                               00050A40                                                                                                                                                                                                                                                                                                                                                                                   0005AD04        
CBC_MAC_Cipher::ciphertextLength(uint)                                                    0005AD44        
CBC_MAC_Cipher::decrypt(uchar *,uint,uchar *,uint,uchar *)                                0005AF3E        
CBC_MAC_Cipher::encrypt(uchar *,uint,uchar *,uint,uchar *)                                0005ADEE        
CBC_MAC_Cipher::setIV(uchar *,uint)                                                       0005AD96        
CBC_MAC_Cipher::~CBC_MAC_Cipher()                                                         0005B0AA        
CBC_MAC_Cipher::~CBC_MAC_Cipher()                                                         0005B0FE        
CBC_decrypt(BlockCipher &,uchar *,uchar const*,uint,uchar *)                              0005A9C6        
CBC_encrypt(BlockCipher &,uchar *,uchar const*,uint,uchar *)                              0005A914        
CFB128_decrypt(BlockCipher &,uchar *,uchar const*,uint,uchar *)                           0005ABB7        
CFB128_encrypt(BlockCipher &,uchar *,uchar const*,uint,uchar *)                           0005AA64        
DES::DES(uchar const*)                                                                    000503B0        
DES::blockLength(void)                                                                    00050A30        
DES::decrypt(uchar const*,uchar *)                                                        00050812        
DES::encrypt(uchar const*,uchar *)                                                        00050518        
DES::~DES()                                                                               00050A08        
ECPoint::ECPoint(BigNum const&&,BigNum const&)                                            00056458        
GCMCipher::ciphertextLength(uint)                                                         0005A798        
GCMCipher::decrypt(uchar *,uint,uchar *,uint,uchar *)                                     0005A4B0        
GCMCipher::encrypt(uchar *,uint,uchar *,uint,uchar *)                                     0005A290        
GCMCipher::setIV(uchar *,uint)                                                            00059DAE        
GCMCipher::~GCMCipher()                                                                   0005A6E8        
GCMCipher::~GCMCipher()                                                                   0005A73C        
GHASH::GHASH(uchar *)                                                                     00059EE2        
GHASH::blockLength(void)                                                                  0005A8B0        
GHASH::digest(uchar *)                                                                    0005A8D0        
GHASH::digestLength(void)                                                                 0005A8C0        
GHASH::update(void const*,uint)                                                           0005A7D0        
GHASH::~GHASH()                                                                           0005A7A8        
HKPDF(Hash &,vector<uchar> const&,vector<uchar> const&,vector<uchar> const&,uint)         0005B15C        
HMAC::HMAC(Hash *,char const*)                                                            0004F0F0        
HMAC::HMAC(Hash *,string const&)                                                          0004F130        
HMAC::HMAC(Hash *,void const*,uint)                                                       0004F044        
HMAC::blockLength(void)                                                                   0004F34A        
HMAC::digest(uchar *)                                                                     0004F258        
HMAC::digestLength(void)                                                                  0004F374        
HMAC::init(void)                                                                          0004F166        
HMAC::update(void const*,uint)                                                            0004F206        
HMAC::~HMAC()                                                                             0004F3C6        
Hash::blockLength(void)                                                                   0004F3B6        
Hash::counterSize(void)                                                                   0004F39E        
Hash::digest(void)                                                                        0004EE10        
Hash::~Hash()                                                                             0004F3EE        
HashImpl::finish(bool)                                                                    0004EF1A        
HashImpl::update(void const*,uint)                                                        0004EE8A        
HashImpl::~HashImpl()                                                                     0004F3AE        
MD4::digest(uchar *)                                                                      0004F3F8        
MD4::digestLength(void)                                                                   0004F5B8        
MD4::init(void)                                                                           0004F442        
MD4::transform(void)                                                                      0004F46A        
MD4::~MD4()                                                                               0004F590        
MD5::digest(uchar *)                                                                      0004F5F0        
MD5::digestLength(void)                                                                   0004F7BE        
MD5::init(void)                                                                           0004F5C8        
MD5::transform(void)                                                                      0004F63A        
MD5::~MD5()                                                                               0004F796        
MSCHAPv2::MSCHAPv2(string const&,string const&,vector<uchar> const&,vector<uchar> const&)                                                                                                                                                                                                                                                                                                                                                                                                                                                                         00057660        
MSCHAPv2::getMasterKey(void)                                                              00057B12        
MSCHAPv2::getReply(void)                                                                  00057C26        
Mod25519Reducer::mul(BigNum &,BigNum const&,BigNum const&)                                00056526        
Mod25519Reducer::mul(BigNum &,uint)                                                       000564E4        
Mod25519Reducer::reduce(BigNum &)                                                         000564B2        
Mod25519Reducer::sqr(BigNum &,BigNum const&)                                              0005656A        
Mod25519Reducer::~Mod25519Reducer()                                                       00057634        
MontgomeryReducer::MontgomeryReducer(BigNum const&)                                       000547BA        
MontgomeryReducer::fromBigNum(BigNum &&)                                                  00054AAA        
MontgomeryReducer::fromBigNum(BigNum const&)                                              0005490A        
MontgomeryReducer::reduce(BigNum &)                                                       00054B32        
MontgomeryReducer::toBigNum(RedNum &&)                                                    00054AE0        
MontgomeryReducer::~MontgomeryReducer()                                                   00054F28        
MontgomeryReducer::~MontgomeryReducer()                                                   00054F5E        
OldSecureProxy::OldSecureProxy(uint)                                                      00041062        
OldSecureProxy::disconnect(string const&)                                                 00041DBE        
OldSecureProxy::doHandshaking(uchar const*,uint)                                          00041EB4        
OldSecureProxy::getBufChar(void)                                                          00042456        
OldSecureProxy::getBufLen(void)                                                           000424AC        
OldSecureProxy::getOutBuffer(void)                                                        000423F0        
OldSecureProxy::handle(nv::message &)                                                     000414C0        
OldSecureProxy::isConnected(void)                                                         000423D8        
OldSecureProxy::processFrame(uint,uchar const*,uint)                                      0004212A        
OldSecureProxy::sendFrame(int,uchar *,uint)                                               000413AC        
OldSecureProxy::startHandshake(void)                                                      000418CA        
OldSecureProxy::~OldSecureProxy()                                                         000412EE        
OldSecureProxy::~OldSecureProxy()                                                         0004137A        
Prng::decrypt(uchar *,uchar *,uint)                                                       00041ABA        
Prng::encrypt(uchar *,uchar *,uint)                                                       00041600        
Prng::init(uchar *,uchar *,uint)                                                          000411A8        
RC4::encrypt(uchar const*,uchar *,uint)                                                   000514F2        
RC4::gen(void)                                                                            000513FC        
RC4::setKey(uchar const*,uint)                                                            00051454        
RC4::skip(uint)                                                                           00051568        
RedNum::emptyReducer                                                                      000FF444        
RedNum::mod1                                                                              000FF43C        
RedNum::neg(void)                                                                         00059C58        
RedNum::operator+=(RedNum const&)                                                         00054C9A        
RedNum::operator-=(RedNum const&)                                                         00054CF2        
RedNum::operator=(RedNum const&)                                                          00053F1C        
RedNum::operator>>=(uint)                                                                 00055590        
Reducer::fromBigNum(BigNum &&)                                                            00054660        
Reducer::fromBigNum(BigNum const&)                                                        00054624        
Reducer::mul(BigNum &,BigNum const&,BigNum const&)                                        000546E0        
Reducer::mul(BigNum &,uint)                                                               00054724        
Reducer::reduce(BigNum &)                                                                 00054EF6        
Reducer::sqr(BigNum &,BigNum const&)                                                      00054778        
Reducer::toBigNum(RedNum &&)                                                              0005469C        
Reducer::~Reducer()                                                                       00054C90        
Reducer::~Reducer()                                                                       00054F00        
SHA1::digest(uchar *)                                                                     0004F9BC        
SHA1::digestLength(void)                                                                  0004FA30        
SHA1::init(void)                                                                          0004F7D0        
SHA1::transform(void)                                                                     0004F802        
SHA1::~SHA1()                                                                             0004FA08        
SHA256::digest(uchar *)                                                                   0004FA40        
SHA256::digestLength(void)                                                                0004FD36        
SHA256::init(void)                                                                        0004FA8C        
SHA256::transform(void)                                                                   0004FAC2        
SHA256::~SHA256()                                                                         0004FD0E        
SHA384::digestLength(void)                                                                0004FDE2        
SHA384::init(void)                                                                        0004FD48        
SHA384::~SHA384()                                                                         0004FDAA        
SHA512::blockLength(void)                                                                 0004FDD2        
SHA512::counterSize(void)                                                                 0004FD9A        
SHA512::digest(uchar *)                                                                   0004FDF4        
SHA512::digestLength(void)                                                                000503A0        
SHA512::init(void)                                                                        0004FE58        
SHA512::transform(void)                                                                   0004FEAA        
SHA512::~SHA512()                                                                         00050378        
SignedBigNum::add(BigNum const&,bool)                                                     0005408C        

@dbeinder good start for reverse engineering, but cannot be reused here. first for licensing issues, second its not cross platform compatible and requires also the android libc which is not compatible with glibc

/bump /subscribe

Any news? Seems that mactelnet does no longer send passwords in plaintext.

@Harvie just asking questions will not help. If you have skills and motivations to do that - make that and do PR. If not - this would not change. I personally not feeling myself motivated enough to spent days of work on replicating proprietary algo which could change anytime.

This cipher may provide useful to unlock the missing piece here:
RSA/ECB/OAEPWithSHA-256AndMGF1Padding

object = new OAEPParameterSpec("SHA-256", "MGF1", MGF1Parameterspec.SHA1. PSource.PSpecified.DEFAULT)

Standard crypto libraries can be used for this.

Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
keyPairGenerator,initialize((AlgorithmParamaterSpec)builder.setDigests(new String[]{"SHA-256", "SHA-512"}.setEncryptionPaddings(new String[]{"OAEPPadding"}.setUserAuthenticationRequired(true).build()).

Make sure to decode strings from Base64 as well.

@jacob-baines did you modify your donna curve implementation for the script you posted in: #42 (comment) ? I am trying to replicate your results in C++ to validate the curve equation and I'm computing a different public key using your pasted script and curve25519-donna.cpp from your RouterOS repo. The SHA hashes are the same, so the only thing I can think of is a different implementation of the elliptic curve.

edit - for reference:

SHA(user:pass):
7b 43 17 60 ce 6a 2b 17 84 e3 14 2c 95 c8 da 2e 12 30 d4 90 42 80 6b ce 44 18 60 a5 30 95 7f f7
SHA(salt, SHA(user:pass)):
1e 9f d0 96 f3 e6 19 86 23 22 88 ac 95 c7 f1 fe 91 e4 ed fb 6f 36 1e a1 7c 4e 31 58 d2 9e da 10
user.dat public key:
4f 5e c3 c2 b1 df 38 e0 19 11 b5 82 9b e7 57 34 98 f9 30 88 a0 91 b0 1a e2 21 5f 1a 83 df 3e 72

@jacob-baines did you modify your donna curve implementation for the script you posted in: #42 (comment) ? I am trying to replicate your results in C++ to validate the curve equation and I'm computing a different public key using your pasted script and curve25519-donna.cpp from your RouterOS repo. The SHA hashes are the same, so the only thing I can think of is a different implementation of the elliptic curve.

edit - for reference:

SHA(user:pass):
7b 43 17 60 ce 6a 2b 17 84 e3 14 2c 95 c8 da 2e 12 30 d4 90 42 80 6b ce 44 18 60 a5 30 95 7f f7
SHA(salt, SHA(user:pass)):
1e 9f d0 96 f3 e6 19 86 23 22 88 ac 95 c7 f1 fe 91 e4 ed fb 6f 36 1e a1 7c 4e 31 58 d2 9e da 10
user.dat public key:
4f 5e c3 c2 b1 df 38 e0 19 11 b5 82 9b e7 57 34 98 f9 30 88 a0 91 b0 1a e2 21 5f 1a 83 df 3e 72

According to my attempts, I am using 1e 9f d0 96 f3 e6 19 86 23 22 88 ac 95 c7 f1 fe 91 e4 ed fb 6f 36 1e a1 7c 4e 31 58 d2 9e da 10As a private key, the results obtained in curve25519-donna.cpp and curve25519.js are both 4f 5e c3 c2 b1 df 38 e0 19 11 b5 82 9b e7 57 34 98 f9 30 88 a0 91 b0 1a e2 21 5f 1a 83 df 3e 72
But when I was debugging libnative.so of the Mikrotik Android APP, I changed the privkey in WCurve::mul((WCurve *)bignum, (const BigNum *)&a1->privkey, (const WCurve::Point *)v8, (WCurve: :Point *)v9);When the corresponding private key memory is modified to 1e9f..., the result is 2B CC 0F 24 1C 69 D3 C3 C6 DD 85 79 38 58 EC 1B D1 13 8D C7 55 61 46 4B 1B 22 B7 DE A5 F2 EA F3
It is consistent with the result of @jacob-baines. This is the point that bothers me and I cannot explain it. Is the implementation of curve25519 in libnative.so different?

SHA(user:pass):
7b 43 17 60 ce 6a 2b 17 84 e3 14 2c 95 c8 da 2e 12 30 d4 90 42 80 6b ce 44 18 60 a5 30 95 7f f7 
SHA(salt, SHA(user:pass)):
1e 9f d0 96 f3 e6 19 86 23 22 88 ac 95 c7 f1 fe 91 e4 ed fb 6f 36 1e a1 7c 4e 31 58 d2 9e da 10 
user.dat public key:
4f 5e c3 c2 b1 df 38 e0 19 11 b5 82 9b e7 57 34 98 f9 30 88 a0 91 b0 1a e2 21 5f 1a 83 df 3e 72
function hexToBytes(hex) {
    for (var bytes = [], c = 0; c < hex.length; c += 2)
        bytes.push(parseInt(hex.substr(c, 2), 16));
    return bytes;
}

// Convert a byte array to a hex string
function bytesToHex(bytes) {
    for (var hex = [], i = 0; i < bytes.length; i++) {
        var current = bytes[i] < 0 ? bytes[i] + 256 : bytes[i];
        hex.push((current >>> 4).toString(16));
        hex.push((current & 0xF).toString(16));
    }
    return hex.join("");
}
var key = [];
key = hexToBytes("1e9fd096f3e61986232288ac95c7f1fe91e4edfb6f361ea17c4e3158d29eda10")
console.log(bytesToHex(key))

var pubKey = curve_u2a(curve25519(curve_a2u(key)));
console.log(bytesToHex(pubKey));

Hello all - excited to share an update on this effort. I just published a blog post and proof of concept programs that successfully authenticate with new MAC Telnet and Winbox versions. I wrote the POCs in Python, so apologies that it is not immediately portable to those who wrote their tooling in C, C++, etc. I'm a reverse engineer and not a developer, but happy to contribute knowledge and effort to fixing broken tools.

The protocol is EC-SRP5 (as we already knew), and is nearly identical to the link that @m0sia posted earlier. The issue is that RouterOS multiplies the private key over the Weierstrass 25519 curve and converts the resulting x coordinate to Montgomery form, returning that value as the public key. That nuanced difference is the reason that @MnrikSrins and I could not replicate the public key in user.dat. There are some other very interesting quirks about the protocol that I am happy to elaborate upon if anyone is interested.

Hope this is a big step in the direction of closing this issue and repairing our broken MikroTik tooling! Cheers!

Hello all - excited to share an update on this effort. I just published a blog post and proof of concept programs that successfully authenticate with new MAC Telnet and Winbox versions. I wrote the POCs in Python, so apologies that it is not immediately portable to those who wrote their tooling in C, C++, etc. I'm a reverse engineer and not a developer, but happy to contribute knowledge and effort to fixing broken tools.

The protocol is EC-SRP5 (as we already knew), and is nearly identical to the link that @m0sia posted earlier. The issue is that RouterOS multiplies the private key over the Weierstrass 25519 curve and converts the resulting x coordinate to Montgomery form, returning that value as the public key. That nuanced difference is the reason that @MnrikSrins and I could not replicate the public key in user.dat. There are some other very interesting quirks about the protocol that I am happy to elaborate upon if anyone is interested.

Hope this is a big step in the direction of closing this issue and repairing our broken MikroTik tooling! Cheers!

shit that i'm a absolute noob in python. otherwise i would be able to integrate it

commented

Thank you, @comed-ian. I have re-implemented your PoC programs in C.

Thank you, @comed-ian. I have re-implemented your PoC programs in C.

Do you have it publicly? So that we could look at implementing it in this codebase? Or do you want to fix it and do a pull request? 😅

commented

I have fixed it in pull-request #76.

@kmeaw that are great news. thank you

Thanks a lot to anybody involved to solve this!