Coding on Holiday - Hacking Huawei MiFi

Crowds of fun-seekers exploring a city on foot, "

Arjan Franzen

August 2019

Shaded eyes, peering into the sky: vision-care in

roadside disappointments on holiday

Back in 2014 i bought a Huawei B593s-22 which would allow me to be able to work non office locations using WiFi.

So using the router for a while all was fine and I had the option, when I ran out of my ‘daily allowance’ of 5GB to add extra allowance with the push of a button inside the telco’s app. How happy everything was!

Then, for boring reasons: I switched to a provider with much better reception ✔️ however I lost the ability to push a button for extra GBs ❌

I arrogantly thought to simply put on my coding gloves and fix a little script in 1 evening to send out a SMS (cute!) for my extra allowance.

As said 2 lines ago: I was arrogant and stupid: what happened instead was that I opened a can of worms. here are the things I tried:

⚠️Spoiler alert: After trying almost everything I bought a more recent version of a Huawei LTE box as the troubles of making the authentication work did not outweigh the price of an upgraded box.

Creating a script that sends a SMS

First I googled for a python script that logs in, sends a SMS and deletes incoming/outgoing stored SMSes

GitHub - Salamek/huawei-lte-api: API For huawei LAN/WAN LTE Modems

The above link gives a nice API and supports quite few Huawei boxes, mine was not on it but I was keen on adding support for it by modifying the code slightly.

The API is simple and concise:

1from huawei_lte_api.Client import Client
2from huawei_lte_api.AuthorizedConnection import AuthorizedConnection
3from huawei_lte_api.Connection import Connection
4
5# connection = Connection('http://192.168.8.1/') For limited access, I have valid credentials no need for limited access
6# connection = AuthorizedConnection('http://admin:MY_SUPER_TRUPER_PASSWORD@192.168.8.1/', login_on_demand=True) # If you wish to login on demand (when call requires authorization), pass login_on_demand=True
7connection = AuthorizedConnection('http://admin:MY_SUPER_TRUPER_PASSWORD@192.168.8.1/')
8
9client = Client(connection) # This just simplifies access to separate API groups, you can use device = Device(connection) if you want
10
11print(client.device.signal())  # Can be accessed without authorization
12print(client.device.information())  # Needs valid authorization, will throw exception if invalid credentials are passed in URL

Unfortunately I got a strange error when I tried the samplecode: the router always returned the following results when logging in:

1<html>
2<head>
3<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
4<title>replace</title>
5<body>
6<script language="JavaScript" type="text/javascript">
7var pageName = '/';
8window.location.replace(pageName);
9</script>
10</head>
11</body>
12</html>

The API library gave a parsing error as it expected XML responses. Instead it got HTML….

strange. perhaps someone else had more luck with a less recent attempt

To France!

Where someone tried, around the time the B593 were on sale, to use the API to send a SMSs

https://sobremarc.wordpress.com/2013/05/15/sending-sms-from-huawei-lte-b593-routeur-and-possibly-others/

1#Login with a default cookiejar
2curl -s $verbose --cookie-jar "$cookiejar" --cookie "$cookiejar" --data "Username=admin&Password=${password}" "http://${myhost}/index/login.cgi" >/dev/null

The key thing here is “${password}” which the documentation says is base64encoded.

fair enough: I’ll try that but…..

1<html>
2<head>
3<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
4<title>replace</title>
5<body>
6<script language="JavaScript" type="text/javascript">
7var pageName = '/';
8window.location.replace(pageName);
9</script>
10</head>
11</body>
12</html>

same response as the “new” API that does not support this B593 box…. did they perhaps change the way passwords are accepted by the B593?

To Finland!

Where someone completely and utterly owned these boxes. 🙂 great job!

https://blog.hqcodeshop.fi/archives/2014/08/C7.html

It looks like the B593 suffered from some amateur level security features that, when exploited were fixed by Huawei with updates to the firmware.

In the end, the most recent version of the firmware (dated 2014 😞 ) they decided to implement some fancy RSA in javascript for password security.

While I grant Huawei that it is more secure than sending the password across the line using base64 password encoding. It is more security by obscurity as the cipher etc are dated and in the end I was not able to get it to work. Perhaps a better programmer than me would be able to finish the job.

using the public key from the “Finland” article and some RSA python

1-----BEGIN PUBLIC KEY-----
2MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgQC+uQ+K9dinx9qMp0rEPh7opI5o
3YMDUal1pC+oILjp04VcfLFjpTuM5hipJqBGjG7Skj0GzvN/QVMNEO7YQtUGLPLr6
455NuG+Kv0uDfhlpuWcK43x6NVwJWfQqWUMsHpD3jkCCWnfCZf8pYfZqK5GJ88YR3
57AZ2XfOqj7RZ3Uya8wICJxE=
6-----END PUBLIC KEY----- 

The PEM public key above

The code from B593 below: at first glance nothing fancy going on here

1// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string
2function RSAEncryptB64(text) {
3  var h = this.encrypt(text);
4  if(h) return hex2b64(h); else return null;
5}
6
7...
8
9//my encrypt function, using fixed mudulus
10var modulus = "BEB90F8AF5D8A7C7DA8CA74AC43E1EE8A48E6860C0D46A5D690BEA082E3A74E1"
11             +"571F2C58E94EE339862A49A811A31BB4A48F41B3BCDFD054C3443BB610B5418B"
12             +"3CBAFAE7936E1BE2AFD2E0DF865A6E59C2B8DF1E8D5702567D0A9650CB07A43D"
13             +"E39020969DF0997FCA587D9A8AE4627CF18477EC06765DF3AA8FB459DD4C9AF3";
14var publicExponent = "10001";
15function MyRSAEncryptB64(text)
16{
17    var rsa = new RSAKey();
18    rsa.setPublic(modulus, publicExponent);
19    return rsa.encrypt_b64(text);
20}

Perhaps the implementation that they used, they may have modified because when I tried the following code

1import base64
2
3from Crypto.Cipher import PKCS1_v1_5, DES3
4from Crypto.PublicKey import RSA
5
6key = RSA.importKey("""-----BEGIN PUBLIC KEY-----
7MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgQC+uQ+K9dinx9qMp0rEPh7opI5o
8YMDUal1pC+oILjp04VcfLFjpTuM5hipJqBGjG7Skj0GzvN/QVMNEO7YQtUGLPLr6
955NuG+Kv0uDfhlpuWcK43x6NVwJWfQqWUMsHpD3jkCCWnfCZf8pYfZqK5GJ88YR3
107AZ2XfOqj7RZ3Uya8wICJxE=
11-----END PUBLIC KEY-----""")
12
13cipher = PKCS1_v1_5.new(key)
14print(base64.b64encode(cipher.encrypt(b'mysecretpassword!')))

When I tried to use the passwords this code generated: I got the following

1<html>
2<head>
3<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
4<title>replace</title>
5<body>
6<script language="JavaScript" type="text/javascript">
7var pageName = '/';
8window.location.replace(pageName);
9</script>
10</head>
11</body>
12</html>

It leads me to believe that my code is not correct and I have no way of debugging it.

This is where I declared the horse dead and stopped flogging it…. 😞

Plan-B: buy a newer box!

Enter: Huawei B525s-23a

Already this looks like a proper upgrade!

"Huawei logo; connected to Tele2 NL; text reads "

The connection is stable and “4G+” 😉

I’ll have a second stab at using the API

GitHub - Salamek/huawei-lte-api: API For huawei LAN/WAN LTE Modems

And immediately all went well!

1import os
2
3from huawei_lte_api.Client import Client
4from huawei_lte_api.AuthorizedConnection import AuthorizedConnection
5
6connection = AuthorizedConnection('http://192.168.8.1/', username="admin", password=os.environ['B525-PASSWORD'])
7client = Client(connection)
8
9print(client.sms.get_sms_list())
Dark magenta logo w/circle graphics, "0" in sky

nice! 🙂

Then expanding on it

1import os
2
3from huawei_lte_api.Client import Client
4from huawei_lte_api.AuthorizedConnection import AuthorizedConnection
5from huawei_lte_api.enums.sms import BoxTypeEnum
6
7connection = AuthorizedConnection('http://192.168.8.1/', username="admin", password=os.environ['B525_PASSWORD'])
8client = Client(connection)
9
10print(client.monitoring.traffic_statistics())
11all_messages = []
12
13#Cycle through all boxes and list messages
14for my_current_boxtype in BoxTypeEnum:
15    print("List all message in box {}".format(my_current_boxtype.name))
16    results = client.sms.get_sms_list(box_type=my_current_boxtype)
17    if results["Messages"] is None:
18        continue
19    found_messages = results["Messages"]['Message']
20    if not isinstance(found_messages, list):
21        all_messages.append(dict(found_messages))
22    else:
23        all_messages = all_messages + found_messages
24
25print("Found {} message(s)".format(len(all_messages)))
26
27for message in all_messages:
28    # Confirmation by Tele2 that new allowance is set; delete all SMS in the box and reset traffic count
29    if "Gelukt! U kunt weer" in message["Content"]:
30        print('request for extra data confirmed, delete all messages & reset counter')
31        for msg_for_id in all_messages:
32            print('delete SMS {}'.format(msg_for_id['Index']))
33            # use internal connection because of bug in delete-sms implementation;
34            # bug: will convert list to dict in XML message, dict in msg not supported
35            # correct examples here:
36            # <?xml version: "1.0" encoding="UTF-8"?><request><Index>40003</Index></request>
37            # <?xml version: "1.0" encoding="UTF-8"?><request><Index>40006</Index><Index>40000</Index></request>
38            # workaround, send dict ourselves using 1 index per call.
39            client.sms._connection.post('sms/delete-sms', {'Index': msg_for_id['Index']})
40        client.monitoring.set_clear_traffic()
41        client.sms.send_sms([os.environ['EXTRA_DATA_SMS']], "1 GB erbij voor de Mifi Router")
42        exit(0)
43
44    # Not a lot data left, let's ask for more!
45    if "U heeft nog 500MB over van uw dagbundel" in message["Content"]:
46        # When 500Mb left, send SMS
47        print("Less than 500Meg to go, better ask for more by sending a SMS!")
48        client.sms.send_sms(["1280"], "1GB Extra")
49
50

Run the above script every 10 minutes and it will check for a SMS stating “you have only 500Mb left” and wil ask for more using a SMS.

Then/also: it wil check to see if the extra megs have been given, when they have the script will delete all SMSes in the box (inbout & outbox)

Scripts and attempts here: https://gitlab.com/afranzen/mifi

Conclusion

  • ❌ Huawei supports their hardware for 10 milliseconds then abandon support for it in favour of a fresh model
  • ❌ The Huawei B593 has/had massive security issues. (u-12)
  • ✔️ Huawei LTE boxes have a nice API that works very well, on boxes more recent than the B593
  • ✔️ LTE reception of the B525 (newer) is even better than the B593 (older but also good reception)
background

Softwareontwikkeling ontmoeilijken