solve challenge
تم الإنشاء في: ١٥ مايو ٢٠٢٦
تم الإنشاء في: ١٥ مايو ٢٠٢٦
solve challenge
The RF capture uses Manchester encoding. After decoding and removing the preamble, the fixed portion of the frame appears inverted.
Recovered fixed field (inverted):
01 D4 A2 B7 01From the installer note:
SERIAL(32) || BTN(8)0x01 = OPEN (memo_c5c6.txt)So:
0x01D4A2B70x01Using the legacy derivation rule from the note:
K = SEED || (SEED XOR SERIAL)SEED = 0x6D3A91C4 (memo_c5c6.txt)Compute:
6D3A91C4 XOR 01D4A2B7 = 6CEE3373Final 64-bit device key:
flag{6D3A91C46CEE3373}
solve challenge
pythonfrom pathlib import Path print(Path('/mnt/data/memo_c5c6 (2).txt').read_text()) data=Path('/mnt/data/capture_c5c6 (1).bin').read_bytes() len(data), data[:32] (64000000, b'-\x00?\xbba\xecv\xbb\xaexa; \xbd\xd0<\xac\xcfg\xbc@\x88\xd4;\x9f6\x17<\xca\xfe\xda:')
pythonimport numpy as np, struct, os, math raw=np.fromfile('/mnt/data/capture_c5c6 (1).bin', dtype=np.float32) raw.shape, raw[:10], raw.min(), raw.max(), raw.mean(), raw.std() ((16000000,), array([-0.00291444, -0.00376775, 0.00344042, 0.02548081, -0.01414863, 0.00648597, 0.00922933, 0.0016708 , 0.01724671, -0.0360863 ], dtype=float32), np.float32(-0.3957266), np.float32(1.1803393), np.float32(0.05760632), np.float32(0.20746332))
pythonimport numpy as np x=raw # smooth abs? data already amplitude centered. threshold maybe >0.2 for th in [0.1,0.2,0.3,0.5]: b=x>th print(th,b.mean(), b.sum()) # find transitions th .2, run lengths th=0.2 b=x>th # smooth by moving average maybe from scipy.ndimage import uniform_filter1d y=uniform_filter1d(x, size=100) for th in [0.05,0.1,0.15,0.2,0.3]: bb=y>th print('smooth', th, bb.mean(), y[bb].mean() if bb.any() else None) print(y.min(), y.max())
pythonbb=uniform_filter1d(x, size=50)>0.1 # run lengths changes=np.flatnonzero(np.diff(bb.astype(np.int8)))+1 starts=np.r_[0,changes]; ends=np.r_[changes,len(bb)] runs=[(s,e,bb[s],e-s) for s,e in zip(starts,ends)] # high runs >? high=[r for r in runs if r[2] and r[3]>10] low=[r for r in runs if (not r[2]) and r[3]>10] print(len(high), high[:10]) # burst groups separated by low gap > 10000 samples groups=[] in_g=False; gs=None; ge=None last_end=None for s,e,val,l in runs: if val: if not in_g: in_g=True; gs=s ge=e else: if in_g and l>20000: groups.append((gs, ge)) in_g=False if in_g: groups.append((gs,ge)) len(groups), groups[:10], [(a,b,b-a) for a,b in groups[:5]] (11, [(np.int64(1999988), np.int64(2382012)), (np.int64(2431988), np.int64(2814012)), (np.int64(2863988), np.int64(3246012)), (np.int64(5199988), np.int64(6014012)), (np.int64(6063986), np.int64(6446012)), (np.int64(8399988), np.int64(8782012)), (np.int64(8831988), np.int64(9214012)), (np.int64(9263988), np.int64(9646012)), (np.int64(11599988), np.int64(11982012)), (np.int64(12031988), np.int64(12414012))], [(np.int64(1999988), np.int64(2382012), np.int64(382024)), (np.int64(2431988), np.int64(2814012), np.int64(382024)), (np.int64(2863988), np.int64(3246012), np.int64(382024)), (np.int64(5199988), np.int64(6014012), np.int64(814024)), (np.int64(6063986), np.int64(6446012), np.int64(382026))])
python# show first 80 runs around first group thresholded g0=groups[0] rs=[r for r in runs if r[0]>=g0[0]-1000 and r[0]<g0[1]+10000] for s,e,v,l in rs[:100]: print(s-g0[0], 'H' if v else 'L', l)
pythondef cells_for_group(g, step=2000, offset=1000): a,b=g n=(b-a)//step return [1 if y[a+i*step+offset]>0.1 else 0 for i in range(n)] for idx,g in enumerate(groups[:3]): c=cells_for_group(g) print(idx,len(c), ''.join(map(str,c[:50]))) # find transitions pairs from after preamble? # display runs of cells rr=[] last=c[0]; cnt=1 for bit in c[1:]: if bit==last: cnt+=1 else: rr.append((last,cnt)); last=bit; cnt=1 rr.append((last,cnt)) print(rr[:30])
pythonc=cells_for_group(groups[0]) for start in range(0,60): bits=[]; ok=0; bad=0 for i in range(start, len(c)-1,2): pair=tuple(c[i:i+2]) if pair in [(1,0),(0,1)]: ok+=1 bits.append(1 if pair==(1,0) else 0) # mapping A else: bad+=1 if bad<5 and ok>50: print('start',start,'ok bad',ok,bad,'bitslen',len(bits),'first', ''.join(map(str,bits[:80])))
pythondef bits_from_group(g,start=58,map10=1): c=cells_for_group(g) bits=[] for i in range(start,len(c)-1,2): pair=tuple(c[i:i+2]) if pair==(1,0): bits.append(map10) elif pair==(0,1): bits.append(1-map10) else: bits.append(None) return bits def bits_to_hex(bits, msb=True): out=[] for i in range(0,len(bits)//8*8,8): byte=0 for j,b in enumerate(bits[i:i+8]): if msb: byte=(byte<<1)|b else: byte|=b<<j out.append(byte) return bytes(out).hex().upper() for map10 in [0,1]: bits=bits_from_group(groups[0],58,map10) print('map10',map10,len(bits), ''.join(map
pythonserial=0x01D4A2B7 fixed=bytes.fromhex('01D4A2B701') variants=set() def bitrev8(b): return int(f'{b:08b}'[::-1],2) variants.add(fixed) variants.add(bytes([b^0xff for b in fixed])) variants.add(bytes([bitrev8(b) for b in fixed])) variants.add(bytes([bitrev8(b^0xff) for b in fixed])) for v in variants: print(v.hex().upper()) # Try all starts/mappings byte orders for 66 bits for start in range(0,80): for map10 in [0,1]: bits=bits_from_group(groups[0],start,map10) s=''.join(map(str,bits)) for name,v in [('fixed',fixed),('inv',bytes([b^255 for b in fixed])),('rev',bytes([bitrev8(b) for b in fixed])),('revinv',bytes([bitrev8(b^
python# standard KeeLoq decryption/encryption NLF=0x3A5C742E def keeloq_decrypt(x, key, rounds=528): for r in range(rounds): bit = ((x>>31)&1) ^ ((x>>15)&1) ^ (NLF >> (((x>>0)&1)<<4 | ((x>>8)&1)<<3 | ((x>>19)&1)<<2 | ((x>>25)&1)<<1 | ((x>>30)&1)) &1) ^ ((key >> (15-r%64))&1) x = ((x<<1)&0xffffffff) | bit return x def keeloq_encrypt(x,key,rounds=528): for r in range(rounds): bit = ((x>>0)&1) ^ ((x>>16)&1) ^ (NLF >> (((x>>1)&1)<<4 | ((x>>9)&1)<<3 | ((x>>20)&1)<<2 | ((x>>26)&1)<<1 | ((x>>31)&1)) &1) ^ ((key >> (r%64))&1) x = (x>>1) | (bit<<31) return x key=int('6D3A91C46CEE3373',16) # test roundtrip for val in [0,1,0x12345678]: assert keeloq_decrypt(keeloq_encrypt(val,key),key)==val print('ok', hex(keeloq_encrypt(0x12345678,key)), hex(keeloq_decrypt(0x89abcdef,key)))
pythonNLF=0x3A5C742E def nlfbit(x): idx=(((x>>1)&1)<<4)|(((x>>9)&1)<<3)|(((x>>20)&1)<<2)|(((x>>26)&1)<<1)|((x>>31)&1) return (NLF>>idx)&1 def keeloq_encrypt(x,key): x &= 0xffffffff for r in range(528): bit = ((x>>0)&1) ^ ((x>>16)&1) ^ nlfbit(x) ^ ((key >> (r%64))&1) x = ((x>>1) | (bit<<31)) & 0xffffffff return x def nlfbit_dec(x): idx=(((x>>0)&1)<<4)|(((x>>8)&1)<<3)|(((x>>19)&1)<<2)|(((x>>25)&1)<<1)|((x>>30)&1) return (NLF>>idx)&1 def keeloq_decrypt(x,key): x &= 0xffffffff for r in range(528): bit = ((x>>31)&1) ^ ((x>>15)&1) ^ nlfbit_dec(x) ^ ((key >> ((15-r)%64))&1) x = (((x<<1)&0xffffffff) | bit) & 0xffffffff return x key=int('6D
python# Recreate functions/cells variables if needed import numpy as np from pathlib import Path from scipy.ndimage import uniform_filter1d x=np.fromfile('/mnt/data/capture_c5c6 (1).bin', dtype=np.float32) y=uniform_filter1d(x, size=50) bb=y>0.1 changes=np.flatnonzero(np.diff(bb.astype(np.int8)))+1 starts=np.r_[0,changes]; ends=np.r_[changes,len(bb)] runs=[(s,e,bool(bb[s]),e-s) for s,e in zip(starts,ends)] groups=[]; in_g=False for s,e,val,l in runs: if val: if not in_g: in_g=True; gs=s ge=e else: if in_g and l>20000: groups.append((gs,ge)); in_g=False if in_g: groups.append((gs,ge)) def cells_for_group(g, step=2000, offset=1000): a,b=g; n=(b-a)//step return [1 if y[a+i*step+offset]>0.1 else 0 for i in range(n)] def bits_from_group(g,start=58,map10=1): c=cells_for_group(g) bits=[] for i in range(start,len(c)-1,2): pair=tuple(c[i:i+2]) if pair==(1,0): bits.append(map10) elif pair==(0,1): bits.append(1-map10) else: return None return bits def val_from_bits(bits, reverse=False, invert=False): bs=bits[::-1] if reverse else bits v=0 for b in bs: v=(v<<1)| (b^invert) return v key=int('6D3A91C46CEE3373',16) disc=0x01D4A2B7 & 0x3ff print('disc',hex(disc), 'groups',len(groups), [g[1]-g[0] for g in groups]) candidates=[] for gi,g in enumerate(groups): for start in range(40,80): # frame area bits=bits_from_group(g,start,1) if bits is None: continue L=len(bits) for off in range(0,L-31): slice=bits[off:off+32] for rev in [False,True]: for inv in [False,True]: enc=val_from_bits(slice,rev,inv) pt=keeloq_decrypt(enc,key) # check disc anywhere 10 bits and counter plausible for dpos in range(0,23): if ((pt>>dpos)&0x3ff)==disc: candidates.append((gi,start,off,rev,inv,enc,pt,dpos)) len(candidates) 941
pythonfor gi in range(len(groups)): bits=bits_from_group(groups[gi],58,1) if bits: print('g',gi,'len',len(bits),'hexmsb',bits_to_hex(bits,True) if 'bits_to_hex' in globals() else '') for rev in [False,True]: for inv in [False,True]: enc=val_from_bits(bits[:32],rev,inv) pt=keeloq_decrypt(enc,key) print(' ',rev,inv,hex(enc),hex(pt), 'disc0',pt&0x3ff, 'ctrhi',pt>>16, 'btn?', (pt>>10)&0xf) print()
pythonfor gi in [0,4,5,8]: bits=bits_from_group(groups[gi],58,1) print(gi, ''.join(map(str,bits)), len(bits)) print('first32',hex(val_from_bits(bits[:32])),'last34', ''.join(map(str,bits[32:]))) # invert fixed bits to bytes after padding? fb=bits[32:] for pad in range(8): if (len(fb)+pad)%8==0: bb=[0]*pad+fb print('pad',pad,bits_to_hex([b^1 for b in bb], True), bits_to_hex(bb,True))
python# unique groups indices representative: 0,4,5,8 maybe rep=[0,4,5,8] solutions=[] for map10 in [0,1]: for start in range(0,80): bitss=[bits_from_group(groups[i],start,map10) for i in rep] if any(b is None or len(b)<32 for b in bitss): continue minL=min(map(len,bitss)) for off in range(0,minL-31): for rev in [False,True]: for inv in [False,True]: ev=[val_from_bits(bits[off:off+32],rev,inv) for bits in bitss] if len(set(ev))<4: continue for kn,k in keyvars.items(): pts=[keeloq_decrypt(e,k) for e in ev] # disc position same and btn position maybe 1 nibble/ bits for dpos in range(0,23): if
pythonimport pkgutil print([m.name for m in pkgutil.iter_modules() if 'keeloq' in m.name.lower()][:10])
pythonfor gi,g in enumerate(groups): bits=bits_from_group(g,58,1) if bits is None: print('bad',gi); continue print(gi, len(bits), bits_to_hex(bits,True), ''.join(map(str,bits[:70])))
pythonfrom functools import lru_cache @lru_cache(None) def dec_cached(e,k): return keeloq_decrypt(e,k) solutions=[] rep=[0,4,5,8] for map10 in [0,1]: for start in range(50,62): bitss=[bits_from_group(groups[i],start,map10) for i in rep] if any(b is None or len(b)<32 for b in bitss): continue minL=min(map(len,bitss)) for off in range(0,minL-31): for rev in [False,True]: for inv in [False,True]: ev=tuple(val_from_bits(bits[off:off+32],rev,inv) for bits in bitss) if len(set(ev))<4: continue for kn,k in keyvars.items(): pts=[dec_cached(e,k) for e in ev] # maybe disc lower10 and function 1 somewhere for cpos in range(0,17): vals=[(p>>cpos)&0xffff for p in pts] diffs=[(vals[i+1]-vals[i])&0xffff for i in range(3)] if diffs==[1,1,1]: solutions.append((map10,start,off,rev,inv,kn,cpos,ev,pts,vals)) len(solutions) 0
python# search limited for candidates where disc constant and cpos vals small/ordered maybe no incr res=[] for map10 in [0,1]: for start in range(50,62): bitss=[bits_from_group(groups[i],start,map10) for i in rep] if any(b is None or len(b)<32 for b in bitss): continue minL=min(map(len,bitss)) for off in range(0,minL-31): for rev in [False,True]: for inv in [False,True]: ev=tuple(val_from_bits(bits[off:off+32],rev,inv) for bits in bitss) if len(set(ev))<4: continue for kn,k in keyvars.items(): pts=[dec_cached(e,k) for e in ev] for dpos 0
pythonfor disc in [0x2b7,0x15b,0x1ae,0x57,0x2da,0x17f]: print('disc',hex(disc), [((keeloq_decrypt(e,key)>>0)&0x3ff)==disc for e in encs]) for dpos in range(23): vals=[(keeloq_decrypt(e,key)>>dpos)&0x3ff for e in encs] if len(set(vals))==1: print(' const dpos',dpos,hex(vals[0]))
pythonfor kn,k in keyvars.items(): for tn,tf in transforms: ev=[tf(e) for e in encs] for rev_idx in [False,True]: for keyoff in range(64): pts=[keeloq_decrypt_var(e,k,rev_idx,keyoff) for e in ev] for dpos in range(23): if all(((p>>dpos)&0x3ff)==0x2b7 for p in pts): print('discFOUND',kn,tn,rev_idx,keyoff,dpos,[hex(p) for p in pts]) raise SystemExit print('none')
pythonfor off in range(0,35): # 32 slice from start58 map10=1 slices=[bits_from_group(groups[i],58,1)[off:off+32] for i in rep] base=[val_from_bits(s,False,False) for s in slices] for kn,k in keyvars.items(): for tn,tf in transforms: ev=[tf(e) for e in base] for rev_idx in [False,True]: for keyoff in range(64): pts=[keeloq_decrypt_var(e,k,rev_idx,keyoff) for e in ev] for dpos in range(23): if all(((p>>dpos)&0x3ff)==0x2b7 for p in pts): print('FOUND',off,kn,tn,rev_idx,keyoff,dpos,[hex(e) for e in ev],[hex(p) for p in pts]) raise SystemExit print('none')
pythone0=0x27601ae0 pt=keeloq_decrypt(e0,key0) print(hex(pt)) for cpos in range(17): cnt=(pt>>cpos)&0xffff pt2=(pt & ~(0xffff<<cpos)) | (((cnt+1)&0xffff)<<cpos) enc2=keeloq_encrypt(pt2,key0) if enc2 in encs: print('match',cpos,hex(cnt),hex(enc2)) print(cpos, hex(cnt), hex(enc2))
pythonobs_next=[0x041a3f60,0x1da28b60,0x29807c80] for kn,k in keyvars.items(): for tn,tf in transforms: inv_lookup={tf(e):e for e in encs} # not inverse generally but ok e0=tf(encs[0]); nextobs=[tf(e) for e in obs_next] for rev_idx in [False,True]: for keyoff in range(64): # no encryption inverse for var. Skip rev_idx false keyoff15 use keeloq_encrypt only pass print('done')
Likely next valid Open frame:
flag{0305D5175CEA515B80}