mirror of
https://github.com/elseif/MikroTikPatch.git
synced 2025-01-23 05:25:00 +03:00
modified: .github/workflows/mikrotik_patch.yml
deleted: keygen.exe new file: netinstall.py new file: package.py modified: patch.py new file: upgrade.py keygen.zip
This commit is contained in:
parent
132b32a77b
commit
b61ebf4af1
25
.github/workflows/mikrotik_patch.yml
vendored
25
.github/workflows/mikrotik_patch.yml
vendored
@ -83,10 +83,16 @@ jobs:
|
||||
echo Latest Stabel Version:$LATEST_VERSION
|
||||
echo "LATEST_VERSION=${LATEST_VERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Create keygen
|
||||
- name: Get netinstall-${{ env.LATEST_VERSION }}.zip
|
||||
run: |
|
||||
zip keygen.zip ./keygen.exe
|
||||
|
||||
sudo wget -nv -O netinstall-$LATEST_VERSION.zip https://download.mikrotik.com/routeros/$LATEST_VERSION/netinstall-$LATEST_VERSION.zip
|
||||
sudo unzip netinstall-$LATEST_VERSION.zip
|
||||
- name: Patch netinstall-${{ env.LATEST_VERSION }}.exe
|
||||
run: |
|
||||
sudo -E python3 patch.py netinstall netinstall-$LATEST_VERSION.exe
|
||||
sudo zip netinstall-$LATEST_VERSION.zip ./netinstall-$LATEST_VERSION.exe
|
||||
|
||||
|
||||
- name: Get mikrotik-${{ env.LATEST_VERSION }}.iso
|
||||
run: |
|
||||
sudo wget -nv -O mikrotik-$LATEST_VERSION.iso https://download.mikrotik.com/routeros/$LATEST_VERSION/mikrotik-$LATEST_VERSION.iso
|
||||
@ -103,7 +109,7 @@ jobs:
|
||||
sudo rm -rf ./iso
|
||||
sudo rm -f mikrotik-$LATEST_VERSION.iso
|
||||
sudo mv ./new_iso/routeros-$LATEST_VERSION.npk ./
|
||||
sudo -E python3 patch.py routeros-$LATEST_VERSION.npk
|
||||
sudo -E python3 patch.py npk routeros-$LATEST_VERSION.npk
|
||||
sudo cp keygen.exe ./new_iso/
|
||||
NPK_FILES=$(find ./new_iso/*.npk)
|
||||
for file in $NPK_FILES; do
|
||||
@ -163,16 +169,16 @@ jobs:
|
||||
- name: Delete Release tag ${{ env.LATEST_VERSION }}
|
||||
run: |
|
||||
HEADER="Authorization: token ${{ secrets.GITHUB_TOKEN }}"
|
||||
RELEASE_INFO=$(curl -s -H $HEADER https://api.github.com/repos/${{ github.repository }}/releases/tags/${{ env.LATEST_VERSION }})
|
||||
RELEASE_INFO=$(curl -s -H $HEADER https://api.github.com/repos/${{ github.repository }}/releases/tags/$LATEST_VERSION)
|
||||
RELEASE_ID=$(echo $RELEASE_INFO | jq -r '.id')
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
if [ "$RELEASE_ID" != "null" ]; then
|
||||
curl -X DELETE -H "$HEADER" https://api.github.com/repos/${{ github.repository }}/git/refs/tags/${{ env.LATEST_VERSION }}
|
||||
echo "Tag ${{ env.LATEST_VERSION }} deleted successfully."
|
||||
curl -X DELETE -H "$HEADER" https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$LATEST_VERSION
|
||||
echo "Tag $LATEST_VERSION deleted successfully."
|
||||
curl -X DELETE -H "$HEADER" https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID
|
||||
echo "Release with tag ${{ env.LATEST_VERSION }} deleted successfully."
|
||||
echo "Release with tag $LATEST_VERSION deleted successfully."
|
||||
else
|
||||
echo "Release not found for tag: ${{ env.LATEST_VERSION }})"
|
||||
echo "Release not found for tag: $LATEST_VERSION)"
|
||||
fi
|
||||
|
||||
- name: Create Release tag ${{ env.LATEST_VERSION }}
|
||||
@ -185,4 +191,5 @@ jobs:
|
||||
files: |
|
||||
mikrotik-${{ env.LATEST_VERSION }}.iso
|
||||
install-image-${{ env.LATEST_VERSION }}.zip
|
||||
netinstall-${{ env.LATEST_VERSION }}.zip
|
||||
|
||||
|
BIN
keygen.exe
BIN
keygen.exe
Binary file not shown.
108
netinstall.py
Normal file
108
netinstall.py
Normal file
@ -0,0 +1,108 @@
|
||||
import struct,lzma
|
||||
ROUTEROS_BOOT = {
|
||||
129:{'arch':'power','name':'Powerboot','filter':lzma.FILTER_POWERPC},
|
||||
130:{'arch':'e500','name':'e500_boot'},
|
||||
131:{'arch':'mips','name':'Mips_boot'},
|
||||
135:{'arch':'400','name':'440__boot'},
|
||||
136:{'arch':'tile','name':'tile_boot'},
|
||||
137:{'arch':'arm','name':'ARM__boot','filter':lzma.FILTER_ARMTHUMB},
|
||||
138:{'arch':'mmips','name':'MMipsBoot'},
|
||||
139:{'arch':'arm64','name':'ARM64__boot','filter':lzma.FILTER_ARMTHUMB},
|
||||
143:{'arch':'x86_64','name':'x86_64boot'}
|
||||
}
|
||||
def find_7zXZ_data(data:bytes):
|
||||
offset1 = 0
|
||||
_data = data
|
||||
while b'\xFD7zXZ\x00\x00\x01' in _data:
|
||||
offset1 = offset1 + _data.index(b'\xFD7zXZ\x00\x00\x01') + 8
|
||||
_data = _data[offset1:]
|
||||
offset1 -= 8
|
||||
offset2 = 0
|
||||
_data = data
|
||||
while b'\x00\x01\x59\x5A' in _data:
|
||||
offset2 = offset2 + _data.index(b'\x00\x01\x59\x5A') + 4
|
||||
_data = _data[offset2:]
|
||||
offset2
|
||||
return data[offset1:offset2]
|
||||
|
||||
def patch_elf(data: bytes,key_dict:dict,filter=None):
|
||||
initrd_xz = find_7zXZ_data(data)
|
||||
initrd = lzma.decompress(initrd_xz)
|
||||
new_initrd = initrd
|
||||
for old_public_key,new_public_key in key_dict.items():
|
||||
if old_public_key in new_initrd:
|
||||
print(f'initramfs public key patched {old_public_key[:16].hex().upper()}...')
|
||||
new_initrd = new_initrd.replace(old_public_key,new_public_key)
|
||||
|
||||
filters=[{"id":filter},{"id": lzma.FILTER_LZMA2, "preset": 9,}] if filter else [{"id": lzma.FILTER_LZMA2, "preset": 9,}]
|
||||
new_initrd_xz = lzma.compress(new_initrd,check=lzma.CHECK_CRC32,filters=filters)
|
||||
assert len(new_initrd_xz) <= len(initrd_xz),'new initrd xz size is too big'
|
||||
|
||||
new_initrd_xz = new_initrd_xz.ljust(len(initrd_xz),b'\0')
|
||||
new_data = data.replace(initrd_xz,new_initrd_xz)
|
||||
return new_data
|
||||
|
||||
def patch_pe(data: bytes,key_dict:dict,filter=None):
|
||||
vmlinux_xz_offset = data.index(b'\xFD7zXZ\x00\x00\x01')
|
||||
vmlinux_xz_size = data.index(b'\x00\x01\x59\x5A') + 4 - vmlinux_xz_offset
|
||||
vmlinux_xz = data[vmlinux_xz_offset:vmlinux_xz_offset+vmlinux_xz_size]
|
||||
vmlinux = lzma.decompress(vmlinux_xz)
|
||||
initrd_xz_offset = vmlinux.index(b'\xFD7zXZ\x00\x00\x01')
|
||||
initrd_xz_size = vmlinux.index(b'\x00\x01\x59\x5A') + 4 - initrd_xz_offset
|
||||
initrd_xz = vmlinux[initrd_xz_offset:initrd_xz_offset+initrd_xz_size]
|
||||
initrd = lzma.decompress(initrd_xz)
|
||||
new_initrd = initrd
|
||||
for old_public_key,new_public_key in key_dict.items():
|
||||
if old_public_key in new_initrd:
|
||||
print(f'initrd public key patched {old_public_key[:16].hex().upper()}...')
|
||||
new_initrd = new_initrd.replace(old_public_key,new_public_key)
|
||||
|
||||
filters=[{"id":filter},{"id": lzma.FILTER_LZMA2, "preset": 9,}] if filter else [{"id": lzma.FILTER_LZMA2, "preset": 9,}]
|
||||
new_initrd_xz = lzma.compress(new_initrd,check=lzma.CHECK_CRC32,filters=filters)
|
||||
assert len(new_initrd_xz) <= len(initrd_xz),'new initrd xz size is too big'
|
||||
|
||||
new_initrd_xz = new_initrd_xz.ljust(len(initrd_xz),b'\0')
|
||||
new_vmlinux = vmlinux.replace(initrd_xz,new_initrd_xz)
|
||||
|
||||
filters=[{"id":filter},{"id": lzma.FILTER_LZMA2, "preset": 9,}] if filter else [{"id": lzma.FILTER_LZMA2, "preset": 9,}]
|
||||
new_vmlinux_xz = lzma.compress(new_vmlinux,check=lzma.CHECK_CRC32,filters=filters)
|
||||
assert len(new_vmlinux_xz) <= len(vmlinux_xz),'new vmlinux xz size is too big'
|
||||
|
||||
new_vmlinux_xz = new_vmlinux_xz.ljust(len(vmlinux_xz),b'\0')
|
||||
new_data = data.replace(vmlinux_xz,new_vmlinux_xz)
|
||||
return new_data
|
||||
|
||||
|
||||
|
||||
def patch_netinstall(key_dict: dict,input_file,output_file=None):
|
||||
import pefile
|
||||
with pefile.PE(input_file) as pe:
|
||||
for resource in pe.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
if resource.id == pefile.RESOURCE_TYPE["RT_RCDATA"]:
|
||||
for sub_resource in resource.directory.entries:
|
||||
if sub_resource.id in ROUTEROS_BOOT:
|
||||
bootloader = ROUTEROS_BOOT[sub_resource.id]
|
||||
filter = bootloader.get("filter")
|
||||
print(f'found {bootloader["arch"]}({sub_resource.id}) bootloader')
|
||||
rva = sub_resource.directory.entries[0].data.struct.OffsetToData
|
||||
size = sub_resource.directory.entries[0].data.struct.Size
|
||||
data = pe.get_data(rva,size)
|
||||
assert len(data) -4 >= struct.unpack_from('<I',data)[0] ,f'bootloader data size mismathch'
|
||||
data = data[4:]
|
||||
try:
|
||||
if data[:2] == b'MZ':
|
||||
new_data = patch_pe(data,key_dict,filter)
|
||||
elif data[:4] == b'\x7FELF':
|
||||
new_data = patch_elf(data,key_dict,filter)
|
||||
else:
|
||||
raise Exception(f'unknown bootloader format {data[:4].hex().upper()}')
|
||||
except Exception as e:
|
||||
print(f'patch {bootloader["arch"]}({sub_resource.id}) bootloader failed {e}')
|
||||
new_data = data
|
||||
new_data = struct.pack("<I",len(new_data)) + new_data.ljust(len(data),b'\0')
|
||||
pe.set_bytes_at_rva(rva,new_data)
|
||||
pe.write(output_file)
|
||||
|
||||
from package import check_install_package
|
||||
check_install_package(['pefile'])
|
||||
|
32
package.py
Normal file
32
package.py
Normal file
@ -0,0 +1,32 @@
|
||||
def install_package(package, version="upgrade", index_url='https://mirrors.aliyun.com/pypi/simple/'):
|
||||
from sys import executable
|
||||
from subprocess import check_call
|
||||
result = False
|
||||
try:
|
||||
if version.lower() == "upgrade":
|
||||
result = check_call([executable, "-m", "pip", "install", package, "--upgrade", "-i", index_url])
|
||||
else:
|
||||
from pkg_resources import get_distribution
|
||||
current_package_version = None
|
||||
try:
|
||||
current_package_version = get_distribution(package)
|
||||
except Exception:
|
||||
pass
|
||||
if current_package_version is None or current_package_version != version:
|
||||
installation_sign = "==" if ">=" not in version else ""
|
||||
result = check_call([executable, "-m", "pip", "install", package + installation_sign + version, "-i", index_url])
|
||||
except Exception as e:
|
||||
print(e)
|
||||
result = -1
|
||||
return result
|
||||
def check_package(package):
|
||||
from importlib import import_module
|
||||
try:
|
||||
import_module(package)
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
def check_install_package(packages):
|
||||
for package in packages:
|
||||
if not check_package(package):
|
||||
install_package(package)
|
34
patch.py
34
patch.py
@ -64,8 +64,8 @@ def patch_squashfs(path,key_dict):
|
||||
data = data.replace(old_public_key,new_public_key)
|
||||
open(file,'wb').write(data)
|
||||
|
||||
def patch_system_npk(npk_file,key_dict):
|
||||
npk = NovaPackage.load(npk_file)
|
||||
def patch_system_npk(key_dict,input_file,output_file=None):
|
||||
npk = NovaPackage.load(input_file)
|
||||
file_container = NpkFileContainer.unserialize_from(npk[NpkPartID.FILE_CONTAINER].data)
|
||||
for item in file_container:
|
||||
if item.name == b'boot/EFI/BOOT/BOOTX64.EFI':
|
||||
@ -95,17 +95,35 @@ def patch_system_npk(npk_file,key_dict):
|
||||
kcdsa_private_key = bytes.fromhex(os.environ['CUSTOM_LICENSE_PRIVATE_KEY'])
|
||||
eddsa_private_key = bytes.fromhex(os.environ['CUSTOM_NPK_SIGN_PRIVATE_KEY'])
|
||||
npk.sign(kcdsa_private_key,eddsa_private_key)
|
||||
npk.save(npk_file)
|
||||
npk.save(output_file or input_file)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os,sys
|
||||
|
||||
import argparse,os
|
||||
parser = argparse.ArgumentParser(description='MikroTik patcher')
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
npk_parser = subparsers.add_parser('npk',help='patch routeros.npk file')
|
||||
npk_parser.add_argument('input',type=str, help='Input file')
|
||||
npk_parser.add_argument('-o','--output',type=str,help='Output file')
|
||||
|
||||
netinstall_parser = subparsers.add_parser('netinstall',help='patch netinstall file')
|
||||
netinstall_parser.add_argument('input',type=str, help='Input file')
|
||||
netinstall_parser.add_argument('-o','--output',type=str,help='Output file')
|
||||
args = parser.parse_args()
|
||||
|
||||
key_dict = {
|
||||
bytes.fromhex(os.environ['MIKRO_LICENSE_PUBLIC_KEY']):bytes.fromhex(os.environ['CUSTOM_LICENSE_PUBLIC_KEY']),
|
||||
bytes.fromhex(os.environ['MIKRO_NPK_SIGN_PUBLIC_LKEY']):bytes.fromhex(os.environ['CUSTOM_NPK_SIGN_PUBLIC_KEY'])
|
||||
}
|
||||
if len(sys.argv) == 2:
|
||||
print(f'patching {sys.argv[1]} ...')
|
||||
patch_system_npk(sys.argv[1],key_dict)
|
||||
if args.command =='npk':
|
||||
print(f'patching {args.input} ...')
|
||||
patch_system_npk(key_dict,args.input)
|
||||
elif args.command == 'netinstall':
|
||||
from netinstall import patch_netinstall
|
||||
print(f'patching {args.input} ...')
|
||||
patch_netinstall(key_dict,args.input)
|
||||
else:
|
||||
print('usage: python patch.py npk_file')
|
||||
parser.print_help()
|
||||
|
||||
|
||||
|
||||
|
48
upgrade.py
Normal file
48
upgrade.py
Normal file
@ -0,0 +1,48 @@
|
||||
from mitmproxy import http
|
||||
import os
|
||||
class MwrAddon:
|
||||
def request(self,flow: http.HTTPFlow) -> None:
|
||||
if len(flow.request.path_components)==3 and flow.request.path_components[0] == 'routeros':
|
||||
version = flow.request.path_components[1]
|
||||
file = os.path.join(version,flow.request.path_components[2])
|
||||
if flow.request.method == 'HEAD':
|
||||
if os.path.exists(version) and os.path.isfile(file):
|
||||
flow.response = http.Response.make(
|
||||
status_code=200,
|
||||
headers={
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Accept-Ranges':'bytes',
|
||||
'Content-Length': str(os.stat(file).st_size),
|
||||
}
|
||||
)
|
||||
else:
|
||||
flow.response = http.Response.make(status_code=404)
|
||||
elif flow.request.method == 'GET' and flow.request.path_components[2].endswith('.npk'):
|
||||
if os.path.exists(version) and os.path.isfile(file):
|
||||
flow.response = http.Response.make(
|
||||
status_code=200,
|
||||
content=open(file,'rb').read(),
|
||||
headers={'Content-Type': 'application/octet-stream',},
|
||||
)
|
||||
else:
|
||||
flow.response = http.Response.make(status_code=404)
|
||||
addons = [MwrAddon()]
|
||||
async def start_listen(port):
|
||||
from mitmproxy.tools.dump import DumpMaster
|
||||
from mitmproxy import options
|
||||
opts = options.Options(listen_host='0.0.0.0',listen_port=port,mode=['reverse:https://upgrade.mikrotik.com/'])
|
||||
print(f'listening at *:{port}')
|
||||
print(f'open http://127.0.0.1:{port}')
|
||||
master = DumpMaster(opts)
|
||||
master.addons.add(*addons)
|
||||
try:
|
||||
await master.run()
|
||||
except KeyboardInterrupt:
|
||||
master.shutdown()
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
from package import check_install_package
|
||||
check_install_package(['mitmproxy'])
|
||||
print(f'ip dns static add name=upgrade.mikrotik.com address=<your ip address>')
|
||||
print(f'ip dns cache flush')
|
||||
asyncio.run(start_listen(80))
|
Loading…
Reference in New Issue
Block a user