Skip to content

Instantly share code, notes, and snippets.

@lukpueh
Created May 24, 2018 10:38
Show Gist options
  • Save lukpueh/a595f74d8edfb512d4f5be7056dfdb1e to your computer and use it in GitHub Desktop.
Save lukpueh/a595f74d8edfb512d4f5be7056dfdb1e to your computer and use it in GitHub Desktop.
Script to help me position my `Alcatel Linkhub HH40v` LTE modem for the best signal strength
#!/usr/bin/env python
"""
<Program Name>
say_lte.py
<Author>
Lukas Puehringer <luk.puehringer@gmail.com>
<Purpose>
Script to help me position my `Alcatel Linkhub HH40v` LTE modem for the
best signal strength.
If a change in signal strength is detected, it uses the OS X command line
tool `say` to say the new signal strength.
<Usage>
1. Connect your computer to your Alcatel Linkhub's WiFi
2. Open a browser and go to the admin page at `192.168.1.1`
3. Open your browser's developer tools, go to the `network` tab and look
for requests to `http://192.168.1.1/jrd/webapi`
4. Take any request, copy the value of the header
`_TclRequestVerificationKey` and assign it to below variable
`REQUEST_KEY`
5. Turn up the volume of your computer and run the script
6. Walk your Alcatel Linkhub around your apartment (long extension cord is
helpful) and place it at where your computer tells you a high signal.
"""
import time
import requests
import subprocess
###############################################################################
# CHANGE REQUIRED !!!
# Add value of you _TclRequestVerificationKey here
###############################################################################
REQUEST_KEY = None
if not REQUEST_KEY:
raise Exception("You need to assign your `_TclRequestVerificationKey` to `REQUEST_KEY`")
URL = "http://192.168.1.1/jrd/webapi"
# JSON rpc request to get information such as signal strength
JSON_REQUEST = {
"id": "1",
"jsonrpc": "2.0",
"method": "GetSystemStatus",
"params": {}
}
# Headers required to authenticate with JSON rpc (assessed by trial and error)
HEADERS = {
"_TclRequestVerificationKey": REQUEST_KEY,
"Referer": "http://192.168.1.1/index.html"
}
def main():
""" Run indefinitely to (at an interval of 1 second)
- print requested signal strength to command line, and
- if signal strength changes, `say` the new strength.
"""
last = None
while True:
r = requests.post(URL, json=JSON_REQUEST, headers=HEADERS)
strength = r.json().get("result", {}).get("SignalStrength")
print "Signal Strength:", strength
if strength != last:
process = subprocess.Popen(["say", str(strength)],
stdout=subprocess.PIPE)
process.communicate()
last = strength
time.sleep(1)
if __name__ == "__main__":
main()
@algj
Copy link

algj commented Nov 17, 2020

This does work but there was one specific request to modem and it would give much more accurate info
Sadly I forgot what was it :(

@algj
Copy link

algj commented Jan 9, 2021

Found it!
POST http://192.168.1.1/jrd/webapi?api=GetNetworkInfo
{"jsonrpc":"2.0","method":"GetNetworkInfo","params":null,"id":"4.1"}

headers used:

Host: 192.168.1.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/plain, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 68
Origin: http://192.168.1.1
DNT: 1
Connection: keep-alive
Referer: http://192.168.1.1/default.html
Sec-GPC: 1
Pragma: no-cache
Cache-Control: no-cache

Only mandatory header is Content-Length

Response:

{ "jsonrpc": "2.0", "result": { "PLMN": "1521626", "NetworkType": 4, "NetworkName": "blablabla", "SpnName": "blablabla", "LAC": "0", "CellId": "15365136", "RncId": "reserved", "Roaming": 1, "Domestic_Roaming": 1, "SignalStrength": 1, "mcc": "246", "mnc": "2", "SINR": "-1", "RSRP": "-324", "RSSI": "-213", "eNBID": "12365", "CGI": "54235437632", "CenterFreq": "0.000000", "TxPWR": "0", "LTE_state": 53215326213, "PLMN_name": "", "Band": 111, "DL_channel": "reserved", "UL_channel": "reserved", "RSRQ": "-15", "EcIo": 0.0, "RSCP": -1 }, "id": "4.1" }

@mortyobnoxious
Copy link

mortyobnoxious commented Feb 23, 2021

@algj @lukpueh

Hi, how can I get connected devices list?

I get this:
{'jsonrpc': '2.0', 'error': {'code': '-32698', 'message': 'Request need login'}, 'id': '1'}

@algj
Copy link

algj commented Feb 23, 2021

@algj @lukpueh

Hi, how can I get connected devices list?

I get this:
{'jsonrpc': '2.0', 'error': {'code': '-32698', 'message': 'Request need login'}, 'id': '1'}

POST http://192.168.1.1/jrd/webapi?api=GetConnectedDeviceList
{"jsonrpc":"2.0","method":"GetConnectedDeviceList","params":null,"id":"12.1"}

@mortyobnoxious
Copy link

@algj It gives these error messages: Request need login or Authentication Failure.

@lukpueh
Copy link
Author

lukpueh commented Feb 24, 2021

Maybe you assigned a wrong value to REQUEST_KEY? Did you follow the instructions from above? Unfortunately, I don't have the device anymore, so I can't really help. Good luck!

@mortyobnoxious
Copy link

I did the instructions above, it works for some API like "method": "GetSystemStatus", but doesn't work for "method":"GetConnectedDeviceList". I think it needs more data for this API but I couldn't manage to make it work.

@lukpueh
Copy link
Author

lukpueh commented Feb 24, 2021

I'd probably try to find out if the client sends any additional headers after you login to the admin page via the web UI.

@mortyobnoxious
Copy link

I tried to create a "session" and get cookies etc but no luck since I don't know enough python. I will keep trying.

Thanks for your help.

@algj
Copy link

algj commented Feb 25, 2021

Oh sorry, forgot to mention I use Alcatel MW40v, for me it works without logging in.

Anyway, make sure you send content-length, _TclRequestVerificationKey, and maybe referer in headers.

@mortyobnoxious
Copy link

I use the same device (Alcatel MW40v) as well but it didn't work, I don't know why. Do you have a login password?

I sent content-length, _TclRequestVerificationKey and added referer in headers. In fact I even have copied everything from browser's network section and added them but still no luck.

I tried rebooting device with python, too. It also gave the same errors: Request need login or Authentication Failure.

I can't access some APIs such as: GetUsageRecord, GetConnectedDeviceList, SetDeviceReboot.

These are the things I can successfully access from different API endpoints:
image
(TotalConnNum, UnreadSMSCount, SignalStrength, Speed_Dl, Speed_Ul)

Do you mind sharing your code?

@algj
Copy link

algj commented Feb 27, 2021

Sorry for not replying, my password is default "admin". Maybe it has something to do with it?

I did not use any code, just did everything over Firefox network tab, even if I did I don't code with python but with javascript(node.js).
I previously did use ?api=GetNetworkInfo in node.js and it was working without any verification keys and etc, only content-length.

@mortyobnoxious
Copy link

Thank you for replying. I did find out the problem. Now I can reach APIs which requires login such as rebooting, seeing connected devices etc.

I look at the JS code from the debugger and find out how they create _TclRequestVerificationToken.

They encrypt this token and "admin" password and "username" with this JS code:

function encrypt(str) {
	if (str == "" || str == undefined) {
		return "";
	}
	var key = "abcdrfgh"; // There's a hardcoded key here.
	var str1 = [];
	var encryStr = "";
	for (var i = 0; i < str.length; i++) {
		var char_i = str.charAt(i);
		var num_char_i = char_i.charCodeAt();
		str1[2 * i] = (key[i % key.length].charCodeAt() & 0xf0) | ((num_char_i & 0xf) ^ (key[i % key.length].charCodeAt() & 0xf));
		str1[2 * i + 1] = (key[i % key.length].charCodeAt() & 0xf0) | ((num_char_i >> 4) ^ (key[i % key.length].charCodeAt() & 0xf));
	}
	for (var i = 0; i < str1.length; i++) {
		encryStr += String.fromCharCode(str1[i]);
	}
	return encryStr;
}

If i send "password" and _TclRequestVerificationToken without encrypting, it doesn't work.

So I did these:

  • I first reach Login API with these parameters params':{'UserName':'encryptedusername','Password':'encryptedpass'}. This gives me a token.
  • Then I encrypt this token, too and add it to headers s.headers.update({'_TclRequestVerificationToken': 'encryptedtoken'})
  • Now I can reach every API there is.

I used Js2Py to run JS code with Python.

@lukpueh
Copy link
Author

lukpueh commented Mar 3, 2021

Nice reverse engineering. :) Thanks for sharing your insights here, @mortyobnoxious!

@topetmtch
Copy link

Hi! Thanks for all the information! This is what I am looking for.
I can access some pages like GetSystemStatus and GetNetworkInfo which already helps me a lot. However, I would like to send sms (yeah, I know... there is one stupid legacy system I need to contact...) and I cannot get this to work.

@mortyobnoxious, can you please give me a hint how to login? I tried using the code above with Js2Py (cool project BTW, I didn't know this exists!). I can encrypt the username and password but I keep getting 'Authentication Failure'.

I tried to search for familiar strings in build.js but I was unable to find the correct information. Hence I guessed:

JSON_REQUEST_LOGIN = {
    "id": "1",
    "jsonrpc": "2.0",
    "method": "Login",
    "params":{"UserName":<encryptedUser>,"Password":<encryptedPW>}
}

-> also tried "login"

URL_LOGIN = "http://192.168.66.1/jrd/webapi"

HEADERS_LOGIN = {
    "Referer": "http://192.168.66.1/index.html"
}

(The router is changed to .66.)

r = requests.post ( URL_LOGIN, json=JSON_REQUEST_LOGIN, headers=HEADERS_LOGIN)
print ( str ( r.json () ) )

Result printed:

{'jsonrpc': '2.0', 'error': {'code': '-32699', 'message': 'Authentication Failure'}, 'id': '1'}

I was hoping to get a token and be logged in. Any help is very welcome! 😀

@topetmtch
Copy link

Update! It works! 😀

I misread the article above and got mixed up with _TclRequestVerificationToken and _TclRequestVerificationKey. 🙈

I re-added _TclRequestVerificationKeyto the original headers - and received the token.

 HEADERS_LOGIN = {
    "_TclRequestVerificationKey": REQUEST_KEY,
    "Referer": "http://192.168.66.1/index.html"
  }

JSON_REQUEST_LOGINremains as stated above.

I receive the token, encrypt it by the JS-function and add it to the headers. Now, I can receive methods which have been blocked by "login needed".

Thanks to all of you!!

@josedanielr
Copy link

josedanielr commented Jun 28, 2021

This is unrelated, but by any chance, does anyone know if there is an endpoint in the webapi to dial USSD codes? My device is MW41 and it doesn't allow it through the web interface, but I have read that the same device shipped by different operator does allow it.

UPDATE: I found it, for some reason it is hidden by default but you can get directly to it through http://192.168.1.1/default.html#more/ussdSetting.html.

@cipeczka
Copy link

Does it work with HUB71?

@spolette
Copy link

Does it work with HUB71?

Nope, this won't work on HUB71 because this hardware use a different method to generate the _TclRequestVerificationToken.
This however might work with this script: https://github.com/spolette/Alcatel_HH72

@cguillard35
Copy link

@spolette, in your script for HH72, please how have you found the key "e5dl12XYVggihggafXWf0f2YSf2Xngd1" ?
I've tried your script on Alcatel MW45V, it seems that encryption is different between login and password.
Thanks

@spolette
Copy link

@spolette, in your script for HH72, please how have you found the key "e5dl12XYVggihggafXWf0f2YSf2Xngd1" ? I've tried your script on Alcatel MW45V, it seems that encryption is different between login and password. Thanks

@cguillard35, I've found that key by reverse engineering Alcatel's android app (https://play.google.com/store/apps/details?id=com.alcatel.smartlinkv3)

@rigas40
Copy link

rigas40 commented May 30, 2023

@spolette can you made python script for MW40V ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment