Initial
This commit is contained in:
commit
172224cfdb
23
Readme.md
Normal file
23
Readme.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Rigol
|
||||||
|
Script from the EEVBlog, that will activate all options on a rigol scope.
|
||||||
|
|
||||||
|
## Usage:
|
||||||
|
```
|
||||||
|
python3 rigol_kg.py 192.168.1.142
|
||||||
|
```
|
||||||
|
|
||||||
|
I had to force enable overwriting the key:
|
||||||
|
```python
|
||||||
|
def main(ip_addr):
|
||||||
|
[..]
|
||||||
|
prev_key = False
|
||||||
|
|
||||||
|
[..]
|
||||||
|
# ELHER necessary due to different key on my scope.
|
||||||
|
if len(new_key) < 388:
|
||||||
|
new_key += (388 - len(new_key)) * b"\xaa"
|
||||||
|
|
||||||
|
[..]
|
||||||
|
```
|
||||||
|
|
||||||
|
And reboot the scope and run again. Maybe it is not necessary and the reboot fixed the key?
|
388
rigol_kg.py
Normal file
388
rigol_kg.py
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
import base64
|
||||||
|
import binascii
|
||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import zlib
|
||||||
|
import requests
|
||||||
|
from tqdm import tqdm
|
||||||
|
from hashlib import sha256
|
||||||
|
from tabulate import tabulate
|
||||||
|
|
||||||
|
import xxtea
|
||||||
|
from struct import pack
|
||||||
|
from ecdsa import BRAINPOOLP256r1, SigningKey, VerifyingKey
|
||||||
|
|
||||||
|
|
||||||
|
FRAM_MD5 = 'aadc292fe4063a7ac392e3c3dde51e84'
|
||||||
|
FRAM_OFFSETS = [3120, 3204, 3206, 3207]
|
||||||
|
RESP_PAT = re.compile(rb'admin:\n(.+)\nOK\n', re.MULTILINE | re.DOTALL)
|
||||||
|
SERIAL_PAT = re.compile(rb'RIGOL TECHNOLOGIES\$(.+?)\$(.+?)\$(.+?)\n\$(.+?)\$(.+?)\$(.+?)\$(.+?)$')
|
||||||
|
OPTIONS = ['BW1T2', 'BW1T3', 'BW1T5', 'BW2T3', 'BW2T5',
|
||||||
|
'BW3T5', 'MSO', '2RL', '5RL', 'BND', 'COMP',
|
||||||
|
'EMBD', 'AUTO', 'FLEX', 'AUDIO', 'SENSOR',
|
||||||
|
'AERO', 'ARINC', 'DG', 'JITTER', 'MASK',
|
||||||
|
'PWR', 'DVM', 'CTR', 'EDK', '4CH', 'BW07T1',
|
||||||
|
'BW07T2', 'BW07T3', 'BW07T5']
|
||||||
|
PRIV_PATH = 'priv.pem'
|
||||||
|
KEY1 = b''.join(pack('<I', x) for x in [0x03920001, 0x08410841, 0x18C32104, 0x318639C7])
|
||||||
|
KEY2 = b''.join(pack('<I', x) for x in [0x478AA887, 0x99A85895, 0x1770078, 0x87888798])
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt_xxtea1(buf):
|
||||||
|
dec = xxtea.decrypt(buf, KEY1, padding=False)
|
||||||
|
return dec
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt_xxtea2(buf):
|
||||||
|
dec = xxtea.decrypt(buf, KEY2, padding=False)
|
||||||
|
return dec
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt_xxtea1(buf):
|
||||||
|
delta = len(buf) % 4
|
||||||
|
|
||||||
|
if delta:
|
||||||
|
buf += b'\x00' * (4 - delta)
|
||||||
|
|
||||||
|
enc = xxtea.encrypt(buf, KEY1, padding=False)
|
||||||
|
return enc
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt_xxtea2(buf):
|
||||||
|
delta = len(buf) % 4
|
||||||
|
|
||||||
|
if delta:
|
||||||
|
buf += b'\x00' * (4 - delta)
|
||||||
|
|
||||||
|
enc = xxtea.encrypt(buf, KEY2, padding=False)
|
||||||
|
return enc
|
||||||
|
|
||||||
|
|
||||||
|
def sign_option(opt):
|
||||||
|
bb = bytearray()
|
||||||
|
bb.extend(opt['model'].encode())
|
||||||
|
bb.extend(opt['serial'].encode())
|
||||||
|
bb.extend(opt['option'].encode())
|
||||||
|
bb.extend(opt['version'].encode())
|
||||||
|
bb.append(0x00)
|
||||||
|
bb.append(0x00)
|
||||||
|
buf = bytes(bb)
|
||||||
|
dig = sha256(buf).digest()
|
||||||
|
|
||||||
|
prev_key = True
|
||||||
|
if not os.path.exists(PRIV_PATH):
|
||||||
|
sk = SigningKey.generate(curve=BRAINPOOLP256r1, hashfunc=sha256)
|
||||||
|
with open(PRIV_PATH, 'wb') as w:
|
||||||
|
w.write(sk.to_pem())
|
||||||
|
|
||||||
|
prev_key = False
|
||||||
|
else:
|
||||||
|
with open(PRIV_PATH) as f:
|
||||||
|
sk = SigningKey.from_pem(f.read())
|
||||||
|
|
||||||
|
sign = sk.sign_digest_deterministic(dig)
|
||||||
|
vk = sk.verifying_key
|
||||||
|
vkk = b'04' + vk.to_string().hex().upper().encode()
|
||||||
|
vk = VerifyingKey.from_string(binascii.unhexlify(vkk), curve=BRAINPOOLP256r1)
|
||||||
|
|
||||||
|
assert vk.verify_digest(sign, dig)
|
||||||
|
return binascii.hexlify(sign).upper(), vkk, prev_key
|
||||||
|
|
||||||
|
|
||||||
|
BLOCK_HDR_FMT = '<IiIiI'
|
||||||
|
|
||||||
|
|
||||||
|
def calc_crc32(buf):
|
||||||
|
return zlib.crc32(buf) & 0xFFFFFFFF
|
||||||
|
|
||||||
|
|
||||||
|
def get_dw(buf, off):
|
||||||
|
dw = struct.unpack_from('<I', buf, off)[0]
|
||||||
|
return dw, off + 4
|
||||||
|
|
||||||
|
|
||||||
|
def get_dws(buf, off):
|
||||||
|
dw = struct.unpack_from('<i', buf, off)[0]
|
||||||
|
return dw, off + 4
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(buf, off, size):
|
||||||
|
block = buf[off:off + size]
|
||||||
|
return block, off + size
|
||||||
|
|
||||||
|
|
||||||
|
def read_block(buf, off):
|
||||||
|
start = off
|
||||||
|
id_, id_neg, data_size, data_size_neg, crc32 = struct.unpack_from(BLOCK_HDR_FMT, buf, off)
|
||||||
|
off += struct.calcsize(BLOCK_HDR_FMT)
|
||||||
|
|
||||||
|
assert ((id_ + id_neg) == 0) and ((data_size + data_size_neg) == 0)
|
||||||
|
|
||||||
|
block_data, off = get_data(buf, off, data_size)
|
||||||
|
crc32_real = calc_crc32(block_data)
|
||||||
|
|
||||||
|
assert crc32_real == crc32
|
||||||
|
|
||||||
|
return {
|
||||||
|
'offset': start,
|
||||||
|
'id': id_,
|
||||||
|
'data': block_data,
|
||||||
|
'crc32': crc32_real
|
||||||
|
}, off
|
||||||
|
|
||||||
|
|
||||||
|
def neg(val):
|
||||||
|
return -1 * val
|
||||||
|
|
||||||
|
|
||||||
|
def write_block(buf, off, block, block_data):
|
||||||
|
block_len = len(block_data)
|
||||||
|
assert len(block['data']) == block_len
|
||||||
|
crc32 = calc_crc32(block_data)
|
||||||
|
struct.pack_into(BLOCK_HDR_FMT, buf, off + block['offset'], block['id'], neg(block['id']), block_len,
|
||||||
|
neg(block_len), crc32)
|
||||||
|
block_data_off = off + block['offset'] + struct.calcsize(BLOCK_HDR_FMT)
|
||||||
|
buf[block_data_off:block_data_off + block_len] = block_data
|
||||||
|
|
||||||
|
|
||||||
|
def replace_cfram_key(cfram, new_key):
|
||||||
|
buf = cfram[0x100:]
|
||||||
|
|
||||||
|
off = 0
|
||||||
|
full_size, off = get_dw(buf, off)
|
||||||
|
full_size_neg, off = get_dws(buf, off)
|
||||||
|
|
||||||
|
assert (full_size + full_size_neg) == 0
|
||||||
|
|
||||||
|
items = {}
|
||||||
|
|
||||||
|
while off < full_size:
|
||||||
|
block, off = read_block(buf, off)
|
||||||
|
# print('%04X: id=%04d, data_sz=%04d, data=%s, crc32=%08X' % (
|
||||||
|
# block['offset'], block['id'], len(block['data']), binascii.hexlify(block['data']).decode(), block['crc32']))
|
||||||
|
items[block['id']] = block
|
||||||
|
|
||||||
|
# print('last_off: 0x%04X' % off)
|
||||||
|
|
||||||
|
data = bytearray(cfram)
|
||||||
|
pub_key = items[4512]
|
||||||
|
write_block(data, 0x100, pub_key, new_key)
|
||||||
|
|
||||||
|
return bytes(data)
|
||||||
|
|
||||||
|
|
||||||
|
def exec_rigol_cmd(ip_addr, cmd, need_res=True):
|
||||||
|
while True:
|
||||||
|
res = requests.post('http://%s/cgi-bin/changepwd.cgi' % ip_addr, data={'pass0': '', 'pass1': '; %s # "' % cmd})
|
||||||
|
|
||||||
|
if res.status_code == 500:
|
||||||
|
continue
|
||||||
|
|
||||||
|
body = res.content
|
||||||
|
m = RESP_PAT.match(body)
|
||||||
|
|
||||||
|
if m is None and need_res:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if need_res:
|
||||||
|
grp = m.group(1)
|
||||||
|
return grp.decode()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def read_cfram_data(ip_addr):
|
||||||
|
print('Reading CFRAM...')
|
||||||
|
cfram = bytearray()
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
with tqdm(total=0x2000) as pb:
|
||||||
|
while i < 0x2000:
|
||||||
|
cmd = '/rigol/tools/fram -r %0x' % i
|
||||||
|
res = exec_rigol_cmd(ip_addr, cmd)
|
||||||
|
|
||||||
|
if res is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
bb = binascii.unhexlify(res.replace(',', ''))
|
||||||
|
cfram.extend(bb)
|
||||||
|
i += 0x10
|
||||||
|
pb.update(0x10)
|
||||||
|
|
||||||
|
print('Reading CFRAM done.\n')
|
||||||
|
|
||||||
|
return bytes(cfram)
|
||||||
|
|
||||||
|
|
||||||
|
def read_rigol_model_serial(ip_addr):
|
||||||
|
res = requests.post('http://%s/cgi-bin/welcome.cgi' % ip_addr)
|
||||||
|
body = res.content
|
||||||
|
m = SERIAL_PAT.match(body)
|
||||||
|
|
||||||
|
if m is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
model = m.group(1).decode()
|
||||||
|
ser = m.group(2).decode()
|
||||||
|
ver = m.group(3).decode()
|
||||||
|
mac = m.group(4).decode()
|
||||||
|
|
||||||
|
print('Model: %s\nSerial: %s\nVersion: %s\nMAC: %s\n' % (model, ser, ver, mac))
|
||||||
|
|
||||||
|
return model, ser
|
||||||
|
|
||||||
|
|
||||||
|
def apply_new_key(ip_addr, new_cfram, new_key):
|
||||||
|
print('Applying new CFRAM...')
|
||||||
|
exec_rigol_cmd(ip_addr, 'echo -n -e \'\\x03\' > /tmp/byte1', need_res=False)
|
||||||
|
exec_rigol_cmd(ip_addr, 'echo -n -e \'\\x3d\' > /tmp/byte2', need_res=False)
|
||||||
|
exec_rigol_cmd(ip_addr, 'echo -n -e \'\\x5b\' > /tmp/byte3', need_res=False)
|
||||||
|
exec_rigol_cmd(ip_addr, 'echo -n -e \'\\xe5\' > /tmp/byte4', need_res=False)
|
||||||
|
|
||||||
|
exec_rigol_cmd(ip_addr, 'cp /rigol/tools/fram /rigol/tools/fram01', need_res=False)
|
||||||
|
exec_rigol_cmd(ip_addr, 'chmod +x /rigol/tools/fram01', need_res=False)
|
||||||
|
exec_rigol_cmd(ip_addr, 'dd if=/tmp/byte1 of=/rigol/tools/fram01 obs=1 seek=%d conv=notrunc' % FRAM_OFFSETS[0], need_res=False)
|
||||||
|
exec_rigol_cmd(ip_addr, 'dd if=/tmp/byte2 of=/rigol/tools/fram01 obs=1 seek=%d conv=notrunc' % FRAM_OFFSETS[1], need_res=False)
|
||||||
|
exec_rigol_cmd(ip_addr, 'dd if=/tmp/byte3 of=/rigol/tools/fram01 obs=1 seek=%d conv=notrunc' % FRAM_OFFSETS[2], need_res=False)
|
||||||
|
exec_rigol_cmd(ip_addr, 'dd if=/tmp/byte4 of=/rigol/tools/fram01 obs=1 seek=%d conv=notrunc' % FRAM_OFFSETS[3], need_res=False)
|
||||||
|
|
||||||
|
with tqdm(total=len(new_cfram)) as pb:
|
||||||
|
for i, b in enumerate(new_cfram):
|
||||||
|
exec_rigol_cmd(ip_addr, '/rigol/tools/fram01 -w %x %02x' % (i, b), need_res=False)
|
||||||
|
pb.update(1)
|
||||||
|
|
||||||
|
print('New CFRAM applied.\n')
|
||||||
|
|
||||||
|
exec_rigol_cmd(ip_addr, 'cp /rigol/data/Key.data /rigol/data/Key.data.bak', need_res=False)
|
||||||
|
print('Key.data backup created.')
|
||||||
|
exec_rigol_cmd(ip_addr, 'echo -n %s | base64 -d > /rigol/data/Key.data' % base64.b64encode(new_key).decode(), need_res=False)
|
||||||
|
print('New Key.data applied.')
|
||||||
|
|
||||||
|
|
||||||
|
def check_fram_tool(ip_addr):
|
||||||
|
res = exec_rigol_cmd(ip_addr, 'md5sum /rigol/tools/fram')
|
||||||
|
res = res.split(' ')[0]
|
||||||
|
|
||||||
|
if res != FRAM_MD5:
|
||||||
|
print('Different /rigol/tools/fram hash. You have to recalc FRAM_OFFSETS!')
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
print('/rigol/tools/fram is OK!\n')
|
||||||
|
|
||||||
|
|
||||||
|
def deactivate_option(ip_addr, code, line):
|
||||||
|
print('Deactivating: %s...' % code, end=' ')
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.connect((ip_addr, 5555))
|
||||||
|
s.sendall(b':SYST:OPT:UNIN %s\n' % line)
|
||||||
|
print('deactivated.')
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
|
||||||
|
def activate_option(ip_addr, code, line):
|
||||||
|
print('Activating: %s...' % code, end=' ')
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.settimeout(1)
|
||||||
|
s.connect((ip_addr, 5555))
|
||||||
|
s.sendall(b':SYST:OPT:INST %s\n' % line)
|
||||||
|
s.sendall(b':SYST:OPT:STAT? %s\n' % code.encode())
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = s.recv(2)
|
||||||
|
|
||||||
|
res = res.rstrip(b'\n')
|
||||||
|
|
||||||
|
if res == b'0':
|
||||||
|
print('not', end=' ')
|
||||||
|
|
||||||
|
print('activated.')
|
||||||
|
except TimeoutError:
|
||||||
|
print('unavailable.')
|
||||||
|
finally:
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
|
||||||
|
def get_unavail_options(ip_addr):
|
||||||
|
items = []
|
||||||
|
while True:
|
||||||
|
res = requests.post('http://%s/cgi-bin/options.cgi' % ip_addr)
|
||||||
|
|
||||||
|
if res.status_code == 500:
|
||||||
|
continue
|
||||||
|
|
||||||
|
body = res.content.decode()
|
||||||
|
items = body.split('#')
|
||||||
|
break
|
||||||
|
|
||||||
|
table = [['Code', 'Status', 'Description']]
|
||||||
|
actives = []
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
row = item.split('$')
|
||||||
|
table.append(row)
|
||||||
|
|
||||||
|
if row[1] == 'Forever':
|
||||||
|
actives.append(row[0])
|
||||||
|
|
||||||
|
print(tabulate(table, headers='firstrow', tablefmt='fancy_grid'))
|
||||||
|
|
||||||
|
return list(set(OPTIONS) - set(actives))
|
||||||
|
|
||||||
|
|
||||||
|
def main(ip_addr):
|
||||||
|
unavails = get_unavail_options(ip_addr)
|
||||||
|
|
||||||
|
check_fram_tool(ip_addr)
|
||||||
|
|
||||||
|
model, ser = read_rigol_model_serial(ip_addr)
|
||||||
|
|
||||||
|
opts = []
|
||||||
|
key_hex = None
|
||||||
|
prev_key = True
|
||||||
|
|
||||||
|
for option in unavails:
|
||||||
|
opt_sign, key_hex, prev_key = sign_option({
|
||||||
|
'model': model,
|
||||||
|
'serial': ser,
|
||||||
|
'option': option,
|
||||||
|
'version': '1.0'
|
||||||
|
})
|
||||||
|
opts.append((option, opt_sign))
|
||||||
|
|
||||||
|
# Set this to False to force overwrite the key
|
||||||
|
# prev_key = False
|
||||||
|
cfram = None
|
||||||
|
if not prev_key:
|
||||||
|
cfram = read_cfram_data(ip_addr)
|
||||||
|
|
||||||
|
|
||||||
|
new_key = b'brainpoolP256r1;%s' % key_hex
|
||||||
|
# ELHER necessary due to different key on my scope.
|
||||||
|
# if len(new_key) < 388:
|
||||||
|
# new_key += (388 - len(new_key)) * b"\xaa"
|
||||||
|
new_key = encrypt_xxtea1(new_key)
|
||||||
|
|
||||||
|
# new_key =
|
||||||
|
# new_key = encrypt_xxtea2(b'brainpoolP256r1;%s' % key_hex)
|
||||||
|
|
||||||
|
if not prev_key:
|
||||||
|
new_cfram = replace_cfram_key(cfram, new_key)
|
||||||
|
apply_new_key(ip_addr, new_cfram, new_key)
|
||||||
|
|
||||||
|
model = model[:-2] + '00'
|
||||||
|
|
||||||
|
for opt in opts:
|
||||||
|
code = opt[0].encode()
|
||||||
|
activate_option(ip_addr, code.decode(), b'%s-%s@%s' % (model.encode(), code, opt[1]))
|
||||||
|
|
||||||
|
get_unavail_options(ip_addr)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print('Usage: python rigol_kg.py 192.168.1.1')
|
||||||
|
main(sys.argv[1])
|
Loading…
Reference in New Issue
Block a user