KalmarCTF 2026 Reverse-Linkme WriteUp

48k 词

0. Preface

So far, I have never spent so much time on a challenge. From the initial preparation of basic knowledge to the intricate process and incredible solving methods, this challenge has totally refreshed my understanding. Ten days of struggle, I feel honored to bring this WriteUp to everyone. There are still many imperfections in this WP, and many of the solving methods are not the best. Meanwhile, during the problem-solving process, I also greatly realized the limitations of human energy and patience. This challenge was jointly overcome by me and AI, and I once again felt the convenience brought by AI. Without AI, I might have lost patience early on due to the complex verification process. All in all, this is a human-machine collaborative battle, a song and dance between human and AI.

This is an ELF file under MIPS architecture, and the compile command mips-linux-gnu-gcc main.c flagchecker.o -fno-toplevel-reorder -static && qemu-mips ./a.out is using the ld linker as a VM. There’s no actual functions in the whole file’s .flagchecker segment, and because the .rel segment doesn’t have jumpable control flow, so the ‘program’ will only enter the verification phase after all relocation entries have been executed. This design effectively prevents side channel explosions. The extern C file has 26 vars in it named a-z. These 26 vars will affect the link process. And after removing the flag format at the beginning and end, there’s 18 unknown chars in the input.

I. Observing The Behavior Of ld Linker Under MIPS Architecture

By compiling ld with symbol, an error was observed in reloc_overflow with error address 0x53EA in .flagchecker. Isomorphic MIPS ld logic (link.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
import argparse
import io
from collections import Counter, defaultdict
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple
from elftools.elf.elffile import ELFFile

R_MIPS_16 = 1
R_MIPS_32 = 2
R_MIPS_HI16 = 5
R_MIPS_LO16 = 6

RELOC_NAME = {
R_MIPS_16: "R_MIPS_16",
R_MIPS_32: "R_MIPS_32",
R_MIPS_HI16: "R_MIPS_HI16",
R_MIPS_LO16: "R_MIPS_LO16",
}
RELOC_ID_BY_NAME = {v: k for k, v in RELOC_NAME.items()}

HOWTO = {
R_MIPS_16: {
"name": "R_MIPS_16",
"size": 4,
"bitsize": 16,
"src_mask": 0xFFFF,
"dst_mask": 0xFFFF,
"shift": 0,
"overflow": "signed16",
},
R_MIPS_32: {
"name": "R_MIPS_32",
"size": 4,
"bitsize": 32,
"src_mask": 0xFFFFFFFF,
"dst_mask": 0xFFFFFFFF,
"shift": 0,
"overflow": "none",
},
R_MIPS_HI16: {
"name": "R_MIPS_HI16",
"size": 4,
"bitsize": 16,
"src_mask": 0xFFFF,
"dst_mask": 0xFFFF,
"shift": 0,
"overflow": "none",
},
R_MIPS_LO16: {
"name": "R_MIPS_LO16",
"size": 4,
"bitsize": 16,
"src_mask": 0xFFFF,
"dst_mask": 0xFFFF,
"shift": 0,
"overflow": "none",
},
}

def parse_int(x: str) -> int: return int(x, 0)

def safe_sym(name: str) -> str: return name.encode("unicode_escape", errors="backslashreplace").decode("ascii")

def named_range_symbols() -> List[str]: return [chr(ord("a") + i) for i in range(26)]

def s16(x: int) -> int:
x &= 0xFFFF
return x - 0x10000 if x & 0x8000 else x

def sign_extend(value: int, bits: int) -> int:
mask = (1 << bits) - 1
value &= mask
sign = 1 << (bits - 1)
return value - (1 << bits) if value & sign else value

def fits_signed_16(x: int) -> bool: return -0x8000 <= x <= 0x7FFF

def u32be(buf: bytearray, off: int) -> int: return int.from_bytes(buf[off:off+4], "big", signed=False)

def w32be(buf: bytearray, off: int, value: int): buf[off:off+4] = (value & 0xFFFFFFFF).to_bytes(4, "big")

def load_named_symbols_from_elf(path: str, names: Optional[List[str]] = None) -> Dict[str, int]:
names = set(named_range_symbols() if names is None else names)
with open(path, "rb") as f:
elf = ELFFile(f)
symtab = elf.get_section_by_name(".symtab")
if symtab is None: raise ValueError(f"{path}: no .symtab")
out = {}
for sym in symtab.iter_symbols():
if sym.name in names: out[sym.name] = sym["st_value"]
missing = sorted(names - set(out))
if missing: raise ValueError(f"{path}: missing symbols {missing}")
return out

@dataclass
class RelocRec:
idx: int
off: int
typ: int
sym_idx: int
sym: str
S: int

@dataclass
class RelocEvent:
idx: int
off: int
typ: int
sym: str
S: int
detail: str

@dataclass
class WriteEvent:
idx: int
off: int
size: int
before: int
after: int
reason: str

@dataclass
class PairInfo:
hi_idx: int
hi_off: int
lo_idx: int
lo_off: int
sym: str

class MiniMipsLdLike:
def __init__(self, obj_path: str, *, chars: Optional[List[int]] = None, base_addr: int = 0, ext_symvals: Optional[Dict[str, int]] = None, watch_offsets: Optional[List[int]] = None, trace_writes: bool = False, stop_on_error: bool = False):
self.obj_path = obj_path
self.chars = chars
self.base_addr = base_addr
self.ext_symvals = {} if ext_symvals is None else dict(ext_symvals)
self.watch_offsets = set(watch_offsets or [])
self.trace_writes = trace_writes
self.stop_on_error = stop_on_error
self.events: List[RelocEvent] = []
self.write_log: List[WriteEvent] = []
self.errors: List[Tuple[int, int, int, str, int, int, int]] = []
self.reloc_counter = Counter()
self.last_addend_by_idx: Dict[int, int] = {}
self.last_addend_src_by_idx: Dict[int, str] = {}
with open(obj_path, "rb") as f: blob = f.read()
self._stream = io.BytesIO(blob)
self.elf = ELFFile(self._stream)
self.sec = self.elf.get_section_by_name(".flagchecker")
self.relsec = self.elf.get_section_by_name(".rel.flagchecker") or self.elf.get_section_by_name(".flagchecker.rel")
self.symtab = self.elf.get_section_by_name(".symtab")
if self.sec is None or self.relsec is None or self.symtab is None: raise ValueError("missing .flagchecker/.rel.flagchecker/.symtab")
self.contents = bytearray(self.sec.data())
self.symvals = self._build_symbol_values()
self.relocs = self._load_relocs()
self.hi_to_lo: Dict[int, int] = {}
self.lo_to_his: Dict[int, List[int]] = defaultdict(list)
self._prescan_pairs()

def _build_symbol_values(self) -> Dict[str, int]:
vals: Dict[str, int] = {}
for sym in self.symtab.iter_symbols():
if sym.name and sym["st_shndx"] == "SHN_ABS": vals[sym.name] = sym["st_value"]
vals.update(self.ext_symvals)
if self.chars is not None:
if len(self.chars) != 26: raise ValueError(f"chars length must be 26, got {len(self.chars)}")
cur = self.base_addr
names = named_range_symbols()
vals[names[0]] = cur
for i in range(1, 26):
cur += 4 * self.chars[i - 1]
vals[names[i]] = cur
return vals

def _sym_name(self, idx: int) -> str:
return self.symtab.get_symbol(idx).name

def _sym_value(self, idx: int) -> int:
sym = self.symtab.get_symbol(idx)
if sym.name in self.symvals: return self.symvals[sym.name]
return sym["st_value"]

def _load_relocs(self) -> List[RelocRec]:
out = []
for i, rel in enumerate(self.relsec.iter_relocations()):
off = rel["r_offset"]
typ = rel["r_info_type"]
sym_idx = rel["r_info_sym"]
out.append(RelocRec(i, off, typ, sym_idx, self._sym_name(sym_idx), self._sym_value(sym_idx)))
return out

def _prescan_pairs(self):
pending: Dict[str, List[int]] = defaultdict(list)
for rec in self.relocs:
if rec.typ == R_MIPS_HI16:
pending[rec.sym].append(rec.idx)
elif rec.typ == R_MIPS_LO16:
if pending[rec.sym]:
his = list(pending[rec.sym])
pending[rec.sym].clear()
for hi_idx in his:
self.hi_to_lo[hi_idx] = rec.idx
self.lo_to_his[rec.idx].append(hi_idx)

def _pair_info_for_hi(self, hi_idx: int) -> Optional[PairInfo]:
lo_idx = self.hi_to_lo.get(hi_idx)
if lo_idx is None: return None
hi = self.relocs[hi_idx]
lo = self.relocs[lo_idx]
return PairInfo(hi_idx, hi.off, lo_idx, lo.off, hi.sym)

def _next_same_offset_nonzero(self, idx: int) -> bool: return idx + 1 < len(self.relocs) and self.relocs[idx + 1].off == self.relocs[idx].off and self.relocs[idx + 1].typ != 0

def _obtain_contents(self, rec: RelocRec) -> int:
h = HOWTO[rec.typ]
word = u32be(self.contents, rec.off)
return word & h["src_mask"]

def _store_contents(self, rec: RelocRec, value: int):
h = HOWTO[rec.typ]
if h["size"] == 4:
old_word = u32be(self.contents, rec.off)
new_word = (old_word & (~h["dst_mask"] & 0xFFFFFFFF)) | (value & h["dst_mask"])
self._note_write(rec.idx, rec.off, 4, old_word, new_word, f"store {RELOC_NAME.get(rec.typ, rec.typ)}")
w32be(self.contents, rec.off, new_word)
else: raise NotImplementedError("only size=4 howto supported")

def _read_rel_addend(self, rec: RelocRec) -> Tuple[int, str]:
raw = self._obtain_contents(rec)
return raw, f"contents&mask@0x{rec.off:x}=0x{raw:x}"

def _add_lo16_rel_addend(self, rec: RelocRec, addend: int) -> Tuple[int, str]:
pair = self._pair_info_for_hi(rec.idx)
if pair is None: return addend, "no-match-lo16"
lo_rec = self.relocs[pair.lo_idx]
lo_addend, lo_src = self._read_rel_addend(lo_rec)
lo_sext = sign_extend(lo_addend, 16)
combined = ((addend & 0xFFFF) << 16) + lo_sext
return combined & 0xFFFFFFFF, f"match-lo16@0x{pair.lo_off:x} raw=0x{lo_addend:x} sext={lo_sext} src={lo_src}"

def _adjust_addend(self, rec: RelocRec, addend: int) -> Tuple[int, str]: return addend & 0xFFFFFFFF, "no-local-adjust"

def _calculate_relocation(self, rec: RelocRec, addend: int) -> Tuple[str, int, str]:
typ = rec.typ
S = rec.S & 0xFFFFFFFF
if typ == R_MIPS_32:
value = (S + addend) & 0xFFFFFFFF
return "ok", value, f"R_MIPS_32 value=0x{value:08x}"
if typ == R_MIPS_HI16:
full = (S + addend) & 0xFFFFFFFF
value = ((full - s16(full)) >> 16) & 0xFFFF
return "ok", value, f"R_MIPS_HI16 full=0x{full:08x} value=0x{value:04x}"
if typ == R_MIPS_LO16:
value = (S + sign_extend(addend, 16)) & 0xFFFFFFFF
return "ok", value, f"R_MIPS_LO16 value=0x{value:08x}"
if typ == R_MIPS_16:
A = sign_extend(addend, 16)
value = S + A
if not fits_signed_16(value if value <= 0x7FFFFFFF else value - 0x100000000): return "overflow", value & 0xFFFFFFFF, f"R_MIPS_16 overflow S=0x{S:08x} A={A} value=0x{value & 0xFFFFFFFF:08x}"
return "ok", value & 0xFFFFFFFF, f"R_MIPS_16 value=0x{value & 0xFFFFFFFF:08x}"
raise NotImplementedError(f"unsupported reloc type {typ}")

def _note_write(self, idx: int, off: int, size: int, before: int, after: int, reason: str):
if self.trace_writes or off in self.watch_offsets: self.write_log.append(WriteEvent(idx, off, size, before, after, reason))

def _log(self, rec: RelocRec, detail: str): self.events.append(RelocEvent(rec.idx, rec.off, rec.typ, safe_sym(rec.sym), rec.S, detail))

def apply(self):
addend = 0
saved_from_prev = False
for rec in self.relocs:
self.reloc_counter[rec.typ] += 1
if not saved_from_prev:
addend, src = self._read_rel_addend(rec)
if rec.typ == R_MIPS_HI16:
addend, extra = self._add_lo16_rel_addend(rec, addend)
src = f"{src}; {extra}"
else:
shift = HOWTO[rec.typ]["shift"]
addend = (addend << shift) & 0xFFFFFFFF
src = f"{src}; <<{shift}"
addend, adj = self._adjust_addend(rec, addend)
src = f"{src}; {adj}"
else: src = "saved-from-prev-same-offset"
self.last_addend_by_idx[rec.idx] = addend & 0xFFFFFFFF
self.last_addend_src_by_idx[rec.idx] = src
save_for_next = self._next_same_offset_nonzero(rec.idx)
status, value, calc = self._calculate_relocation(rec, addend)
self._log(rec, f"addend=0x{addend & 0xFFFFFFFF:08x} src={src} save_for_next={save_for_next} | {calc}")
if status == "overflow":
A = sign_extend(addend, 16) if rec.typ == R_MIPS_16 else addend
self.errors.append((rec.idx, rec.off, rec.typ, safe_sym(rec.sym), rec.S, A, value))
if self.stop_on_error: break
if save_for_next:
addend = value & 0xFFFFFFFF
saved_from_prev = True
self._log(rec, f"carry addend forward = 0x{addend:08x}")
else:
saved_from_prev = False
self._store_contents(rec, value)
return self

def dump_first_error(self):
if not self.errors:
print("no overflow / no early error")
return
i, off, typ, sym, S, A, V = self.errors[0]
print(f"first error at reloc #{i}")
print(f"offset = 0x{off:08x}")
print(f"type = {typ} ({RELOC_NAME.get(typ, 'UNKNOWN')})")
print(f"sym = {sym}")
print(f"S = 0x{S:08x}")
print(f"A = {A}")
print(f"V = {V} (0x{V & 0xFFFFFFFF:08x})")

def dump_context_around_index(self, idx: int, before: int = 8, after: int = 8):
lo = max(0, idx - before)
hi = min(len(self.events), idx + after + 1)
print(f"[event context around reloc #{idx}]")
for ev in self.events[lo:hi]:
mark = "<FOCUS>" if ev.idx == idx else " "
print(f"{mark} #{ev.idx:6d} off=0x{ev.off:04x} typ={RELOC_NAME.get(ev.typ, ev.typ):>11} sym={ev.sym:>20} S=0x{ev.S:08x} | {ev.detail}")

def dump_watch_history(self, limit: int = 200):
if not self.write_log:
print("[no watched writes]")
return
print("[watched writes]")
for wev in self.write_log[:limit]:
fmt = f"0x{{:0{wev.size * 2}x}}"
print(f" #{wev.idx:6d} off=0x{wev.off:04x} size={wev.size} {fmt.format(wev.before)} -> {fmt.format(wev.after)} | {wev.reason}")
if len(self.write_log) > limit: print(f" ... truncated {len(self.write_log) - limit} more writes")

def dump_reloc_stats(self):
print("[relocation stats]")
for typ, cnt in sorted(self.reloc_counter.items()):
print(f" {typ:2d} {RELOC_NAME.get(typ, 'UNKNOWN'):>12}: {cnt}")

def dump_actual_r16(self):
print("[actual R_MIPS_16 relocations]")
for rec in self.relocs:
if rec.typ != R_MIPS_16: continue
addend = self.last_addend_by_idx.get(rec.idx, 0)
A = sign_extend(addend, 16)
V = (rec.S + A) & 0xFFFFFFFF
status = "OVERFLOW" if not fits_signed_16((V if V <= 0x7FFFFFFF else V - 0x100000000)) else "ok"
print(f" #{rec.idx:6d} off=0x{rec.off:04x} sym={safe_sym(rec.sym)} S=0x{rec.S:08x} addend=0x{addend:08x} src={self.last_addend_src_by_idx.get(rec.idx,'')} A={A} V=0x{V:08x} {status}")

def build_sym_source(args) -> Tuple[Optional[Dict[str, int]], str]:
if args.sym_elf:
vals = load_named_symbols_from_elf(args.sym_elf)
if args.normalize_to_a:
base = vals["a"]
vals = {k: (v - base) & 0xFFFFFFFF for k, v in vals.items()}
if args.rebase: vals = {k: (v + args.rebase) & 0xFFFFFFFF for k, v in vals.items()}
return vals, f"raw ELF symbols from {args.sym_elf}"
if args.chars: return None, f"chars='{args.chars}' base=0x{args.base_addr:x}"
return None, "no external symbol source"

def main():
ap = argparse.ArgumentParser(description="Howto-aware MIPS mini-linker for Kalmar re-linkme")
ap.add_argument("--obj", default="flagchecker.o")
ap.add_argument("--sym-elf", help="Load a..z symbol values directly from main.o / dummy.out / other ELF")
ap.add_argument("--normalize-to-a", action="store_true")
ap.add_argument("--rebase", type=parse_int, default=0)
ap.add_argument("--chars", default="kalmar{flagflagflagflagfl}")
ap.add_argument("--base-addr", type=parse_int, default=0)
ap.add_argument("--watch-offset", action="append", default=[])
ap.add_argument("--trace-writes", action="store_true")
ap.add_argument("--dump-stats", action="store_true")
ap.add_argument("--dump-actual-r16", action="store_true")
ap.add_argument("--context-before", type=int, default=40)
ap.add_argument("--context-after", type=int, default=12)
ap.add_argument("--focus-reloc", action="append", default=[])
ap.add_argument("--focus-before", type=int, default=8)
ap.add_argument("--focus-after", type=int, default=8)
ap.add_argument("--stop-on-error", action="store_true")
args = ap.parse_args()
watched = [parse_int(x) for x in args.watch_offset]
ext_symvals, sym_desc = build_sym_source(args)
chars = None if ext_symvals is not None else [ord(c) for c in args.chars]
linker = MiniMipsLdLike(args.obj, chars=chars, base_addr=args.base_addr, ext_symvals=ext_symvals, watch_offsets=watched, trace_writes=args.trace_writes, stop_on_error=args.stop_on_error).apply()
print(f"[config] obj={args.obj}")
print(f"[config] symbol-source={sym_desc}")
if watched: print("[config] watched offsets=" + ", ".join(f"0x{x:x}" for x in watched))
if args.dump_stats: linker.dump_reloc_stats()
linker.dump_first_error()
if linker.errors: linker.dump_context_around_index(linker.errors[0][0], args.context_before, args.context_after)
for focus in args.focus_reloc: linker.dump_context_around_index(parse_int(focus), args.focus_before, args.focus_after)
if watched: linker.dump_watch_history()
if args.dump_actual_r16: linker.dump_actual_r16()

if __name__ == "__main__": main()

Usage:

1
python3 link.py --obj flagchecker.o --sym-elf dummy.out --watch-offset 0x53df --watch-offset 0x53e2 --watch-offset 0x53e6 --watch-offset 0x53ea --context-before 80 --context-after 12 --focus-reloc 151078 --focus-before 8 --focus-after 8 --dump-stats --dump-actual-r16

II. Initial Analysis & Basic Script Preparation

Change to stain taint analysis script (link_debug_slice_ir2.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
import argparse
import io
from collections import Counter, defaultdict
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Set, Tuple

from elftools.elf.elffile import ELFFile

R_MIPS_16 = 1
R_MIPS_32 = 2
R_MIPS_HI16 = 5
R_MIPS_LO16 = 6

RELOC_NAME = {
R_MIPS_16: "R_MIPS_16",
R_MIPS_32: "R_MIPS_32",
R_MIPS_HI16: "R_MIPS_HI16",
R_MIPS_LO16: "R_MIPS_LO16",
}

# Minimal howto fields reconstructed from reverse engineering.
HOWTO = {
R_MIPS_16: {
"name": "R_MIPS_16",
"size": 4,
"bitsize": 16,
"rightshift": 0,
"src_mask": 0xFFFF,
"dst_mask": 0xFFFF,
"overflow": "signed16",
},
R_MIPS_32: {
"name": "R_MIPS_32",
"size": 4,
"bitsize": 32,
"rightshift": 0,
"src_mask": 0xFFFFFFFF,
"dst_mask": 0xFFFFFFFF,
"overflow": "none",
},
R_MIPS_HI16: {
"name": "R_MIPS_HI16",
"size": 4,
"bitsize": 16,
"rightshift": 16,
"src_mask": 0xFFFF,
"dst_mask": 0xFFFF,
"overflow": "none",
},
R_MIPS_LO16: {
"name": "R_MIPS_LO16",
"size": 4,
"bitsize": 16,
"rightshift": 0,
"src_mask": 0xFFFF,
"dst_mask": 0xFFFF,
"overflow": "none",
},
}

KNOWN_TEMPLATE = "kalmar{flagflagflagflagfl}"

def parse_int(x: str) -> int:
return int(x, 0)

def safe_sym(name: str) -> str:
return name.encode("unicode_escape", errors="backslashreplace").decode("ascii")

def named_range_symbols() -> List[str]:
return [chr(ord("a") + i) for i in range(26)]

def s16(x: int) -> int:
x &= 0xFFFF
return x - 0x10000 if x & 0x8000 else x

def sign_extend(value: int, bits: int) -> int:
mask = (1 << bits) - 1
value &= mask
sign = 1 << (bits - 1)
return value - (1 << bits) if value & sign else value

def fits_signed_16(x: int) -> bool:
return -0x8000 <= x <= 0x7FFF

def u32be(buf: bytearray, off: int) -> int:
return int.from_bytes(buf[off:off+4], "big", signed=False)

def w32be(buf: bytearray, off: int, value: int):
buf[off:off+4] = (value & 0xFFFFFFFF).to_bytes(4, "big")

def load_named_symbols_from_elf(path: str, names: Optional[List[str]] = None) -> Dict[str, int]:
names = set(named_range_symbols() if names is None else names)
with open(path, "rb") as f:
elf = ELFFile(f)
symtab = elf.get_section_by_name(".symtab")
if symtab is None:
raise ValueError(f"{path}: no .symtab")
out = {}
for sym in symtab.iter_symbols():
if sym.name in names:
out[sym.name] = sym["st_value"]
missing = sorted(names - set(out))
if missing:
raise ValueError(f"{path}: missing symbols {missing}")
return out

@dataclass
class TraceExpr:
text: str
reloc_deps: List[int] = field(default_factory=list)
leaves: List[str] = field(default_factory=list)

def merged(self, extra_text: str, *others: "TraceExpr") -> "TraceExpr":
deps = list(self.reloc_deps)
leaves = list(self.leaves)
for o in others:
deps.extend(o.reloc_deps)
leaves.extend(o.leaves)
deps = list(dict.fromkeys(deps))
leaves = list(dict.fromkeys(leaves))
return TraceExpr(extra_text, deps, leaves)

@dataclass
class RelocRec:
idx: int
off: int
typ: int
sym_idx: int
sym: str
S: int

@dataclass
class PairInfo:
hi_idx: int
hi_off: int
lo_idx: int
lo_off: int
sym: str

@dataclass
class RelocTrace:
idx: int
off: int
typ: int
sym: str
S: int
addend: int
addend_expr: TraceExpr
save_for_next: bool
status: str
value: int
calc_text: str
saved_from_prev: bool = False
wrote: bool = False
write_before: Optional[int] = None
write_after: Optional[int] = None
write_reason: Optional[str] = None
semantic_deps: List[Tuple[str, int]] = field(default_factory=list)
notes: List[str] = field(default_factory=list)

class MiniMipsSlicePlus:
def __init__(
self,
obj_path: str,
*,
chars: Optional[List[int]] = None,
char_template: str = KNOWN_TEMPLATE,
base_addr: int = 0,
ext_symvals: Optional[Dict[str, int]] = None,
watch_offsets: Optional[List[int]] = None,
stop_on_error: bool = False,
):
self.obj_path = obj_path
self.chars = chars
self.char_template = char_template
self.base_addr = base_addr
self.ext_symvals = {} if ext_symvals is None else dict(ext_symvals)
self.watch_offsets = set(watch_offsets or [])
self.stop_on_error = stop_on_error

self.errors: List[Tuple[int, int, int, str, int, int, int]] = []
self.reloc_counter = Counter()
self.trace_by_idx: Dict[int, RelocTrace] = {}
self.write_log: List[Tuple[int, int, int, int, str]] = []
self.word_def_by_off: Dict[int, int] = {}
self.final_actual_r16: List[RelocTrace] = []

with open(obj_path, "rb") as f:
blob = f.read()
self._stream = io.BytesIO(blob)
self.elf = ELFFile(self._stream)

self.sec = self.elf.get_section_by_name(".flagchecker")
self.relsec = self.elf.get_section_by_name(".rel.flagchecker") or self.elf.get_section_by_name(".flagchecker.rel")
self.symtab = self.elf.get_section_by_name(".symtab")
if self.sec is None or self.relsec is None or self.symtab is None:
raise ValueError("missing .flagchecker/.rel.flagchecker/.symtab")

self.contents = bytearray(self.sec.data())
self.initial_contents = bytearray(self.contents)
self.symvals = self._build_symbol_values()
self.relocs = self._load_relocs()
self.hi_to_lo: Dict[int, int] = {}
self.lo_to_his: Dict[int, List[int]] = defaultdict(list)
self._prescan_pairs()

def _build_symbol_values(self) -> Dict[str, int]:
vals: Dict[str, int] = {}
for sym in self.symtab.iter_symbols():
if sym.name and sym["st_shndx"] == "SHN_ABS":
vals[sym.name] = sym["st_value"]
vals.update(self.ext_symvals)
if self.chars is not None:
if len(self.chars) != 26:
raise ValueError(f"chars length must be 26, got {len(self.chars)}")
cur = self.base_addr
names = named_range_symbols()
vals[names[0]] = cur
for i in range(1, 26):
cur += 4 * self.chars[i - 1]
vals[names[i]] = cur
return vals

def _sym_name(self, idx: int) -> str:
return self.symtab.get_symbol(idx).name

def _sym_value(self, idx: int) -> int:
sym = self.symtab.get_symbol(idx)
if sym.name in self.symvals:
return self.symvals[sym.name]
return sym["st_value"]

def _input_char_name(self, i: int) -> str:
if 0 <= i < len(self.char_template):
ch = self.char_template[i]
return f"x{i}('{safe_sym(ch)}')"
return f"x{i}"

def _addr_expr_for_symbol(self, sym_name: str) -> Optional[TraceExpr]:
if sym_name not in set(named_range_symbols()):
return None
idx = ord(sym_name) - ord('a')
if idx == 0:
txt = "ADDR(a)=BASE"
leaves = ["base"]
return TraceExpr(txt, [], leaves)
terms = [self._input_char_name(i) for i in range(idx)]
txt = f"ADDR({sym_name})=BASE+4*({'+'.join(terms)})"
leaves = ["base"] + [f"input:{t}" for t in terms]
return TraceExpr(txt, [], leaves)

def _sym_leaf(self, rec: RelocRec) -> TraceExpr:
name = safe_sym(rec.sym)
addr_expr = self._addr_expr_for_symbol(rec.sym)
if addr_expr is not None:
addr_expr.text += f" current=0x{rec.S:08x}"
return addr_expr
return TraceExpr(f"SYM({name})=0x{rec.S:08x}", [], [f"sym:{name}=0x{rec.S:08x}"])

def _load_relocs(self) -> List[RelocRec]:
out = []
for i, rel in enumerate(self.relsec.iter_relocations()):
off = rel["r_offset"]
typ = rel["r_info_type"]
sym_idx = rel["r_info_sym"]
out.append(RelocRec(i, off, typ, sym_idx, self._sym_name(sym_idx), self._sym_value(sym_idx)))
return out

def _prescan_pairs(self):
pending: Dict[str, List[int]] = defaultdict(list)
for rec in self.relocs:
if rec.typ == R_MIPS_HI16:
pending[rec.sym].append(rec.idx)
elif rec.typ == R_MIPS_LO16 and pending[rec.sym]:
his = list(pending[rec.sym])
pending[rec.sym].clear()
for hi_idx in his:
self.hi_to_lo[hi_idx] = rec.idx
self.lo_to_his[rec.idx].append(hi_idx)

def _pair_info_for_hi(self, hi_idx: int) -> Optional[PairInfo]:
lo_idx = self.hi_to_lo.get(hi_idx)
if lo_idx is None:
return None
hi = self.relocs[hi_idx]
lo = self.relocs[lo_idx]
return PairInfo(hi_idx, hi.off, lo_idx, lo.off, hi.sym)

def _next_same_offset_nonzero(self, idx: int) -> bool:
return idx + 1 < len(self.relocs) and self.relocs[idx + 1].off == self.relocs[idx].off and self.relocs[idx + 1].typ != 0

def _obtain_contents(self, rec: RelocRec) -> int:
h = HOWTO[rec.typ]
word = u32be(self.contents, rec.off)
return word & h["src_mask"]

def _read_rel_addend(self, rec: RelocRec) -> Tuple[int, TraceExpr, List[Tuple[str, int]], List[str]]:
raw = self._obtain_contents(rec)
prev = self.word_def_by_off.get(rec.off)
if prev is None:
expr = TraceExpr(
f"READ(off=0x{rec.off:x}, mask=0x{HOWTO[rec.typ]['src_mask']:x}) -> 0x{raw:x}",
[],
[f"init-word:0x{rec.off:x}=0x{u32be(self.initial_contents, rec.off):08x}"],
)
return raw, expr, [], [f"read-init-word@0x{rec.off:x}"]
expr = TraceExpr(
f"READ(off=0x{rec.off:x}, from_last_writer=#{prev}, mask=0x{HOWTO[rec.typ]['src_mask']:x}) -> 0x{raw:x}",
[prev],
[],
)
return raw, expr, [("last-writer", prev)], [f"read-from-last-writer #{prev} @0x{rec.off:x}"]

def _add_lo16_rel_addend(self, rec: RelocRec, hi_addend: int, hi_expr: TraceExpr) -> Tuple[int, TraceExpr, List[Tuple[str, int]], List[str]]:
pair = self._pair_info_for_hi(rec.idx)
if pair is None:
return hi_addend, hi_expr.merged(f"HI16(no LO16 pair) raw=0x{hi_addend:x}"), [], ["no-matched-lo16"]
lo_rec = self.relocs[pair.lo_idx]
lo_addend, lo_expr, lo_sem, lo_notes = self._read_rel_addend(lo_rec)
lo_sext = sign_extend(lo_addend, 16)
combined = (((hi_addend & 0xFFFF) << 16) + lo_sext) & 0xFFFFFFFF
expr = hi_expr.merged(
f"HI16+LO16(hi=0x{hi_addend:x}, lo@#{pair.lo_idx}/0x{pair.lo_off:x}=0x{lo_addend:x}, sext={lo_sext}) -> 0x{combined:08x}",
TraceExpr(f"MATCHED_LO16 #{pair.lo_idx}", [pair.lo_idx], []),
lo_expr,
)
sem = [("matched-lo16", pair.lo_idx)] + lo_sem
notes = [f"matched-lo16 #{pair.lo_idx} off=0x{pair.lo_off:x}"] + lo_notes
return combined, expr, sem, notes

def _adjust_addend(self, rec: RelocRec, addend: int, expr: TraceExpr) -> Tuple[int, TraceExpr]:
return addend & 0xFFFFFFFF, expr.merged(f"ADJ(no-local-adjust) -> 0x{addend & 0xFFFFFFFF:08x}")

def _calculate_relocation(self, rec: RelocRec, addend: int, expr: TraceExpr) -> Tuple[str, int, str, TraceExpr]:
typ = rec.typ
S = rec.S & 0xFFFFFFFF
sym_expr = self._sym_leaf(rec)
if typ == R_MIPS_32:
value = (S + addend) & 0xFFFFFFFF
calc_expr = expr.merged(f"R_MIPS_32(S=0x{S:08x} + A=0x{addend:08x}) -> 0x{value:08x}", sym_expr)
return "ok", value, f"R_MIPS_32 value=0x{value:08x}", calc_expr
if typ == R_MIPS_HI16:
full = (S + addend) & 0xFFFFFFFF
value = ((full - s16(full)) >> 16) & 0xFFFF
calc_expr = expr.merged(
f"R_MIPS_HI16(full=(S=0x{S:08x}+A=0x{addend:08x})=0x{full:08x}) -> 0x{value:04x}",
sym_expr,
)
return "ok", value, f"R_MIPS_HI16 full=0x{full:08x} value=0x{value:04x}", calc_expr
if typ == R_MIPS_LO16:
A = sign_extend(addend, 16)
value = (S + A) & 0xFFFFFFFF
calc_expr = expr.merged(
f"R_MIPS_LO16(S=0x{S:08x}+sext(A=0x{addend & 0xFFFF:x})={A}) -> 0x{value:08x}",
sym_expr,
)
return "ok", value, f"R_MIPS_LO16 value=0x{value:08x}", calc_expr
if typ == R_MIPS_16:
A = sign_extend(addend, 16)
value = S + A
calc_expr = expr.merged(
f"R_MIPS_16(S=0x{S:08x}+A={A}) -> 0x{value & 0xFFFFFFFF:08x}",
sym_expr,
)
signed_v = value if value <= 0x7FFFFFFF else value - 0x100000000
if not fits_signed_16(signed_v):
return "overflow", value & 0xFFFFFFFF, f"R_MIPS_16 overflow S=0x{S:08x} A={A} value=0x{value & 0xFFFFFFFF:08x}", calc_expr
return "ok", value & 0xFFFFFFFF, f"R_MIPS_16 value=0x{value & 0xFFFFFFFF:08x}", calc_expr
raise NotImplementedError(f"unsupported reloc type {typ}")

def _store_contents(self, rec: RelocRec, value: int, trace: RelocTrace):
h = HOWTO[rec.typ]
old_word = u32be(self.contents, rec.off)
new_word = (old_word & (~h["dst_mask"] & 0xFFFFFFFF)) | (value & h["dst_mask"])
trace.wrote = True
trace.write_before = old_word
trace.write_after = new_word
trace.write_reason = f"store {RELOC_NAME.get(rec.typ, rec.typ)}"
self.write_log.append((rec.idx, rec.off, old_word, new_word, trace.write_reason))
w32be(self.contents, rec.off, new_word)
self.word_def_by_off[rec.off] = rec.idx

def apply(self):
saved_addend = 0
saved_from_prev = False
saved_from_idx: Optional[int] = None

for rec in self.relocs:
self.reloc_counter[rec.typ] += 1
semantic_deps: List[Tuple[str, int]] = []
notes: List[str] = []

if not saved_from_prev:
addend, expr, sem0, notes0 = self._read_rel_addend(rec)
semantic_deps.extend(sem0)
notes.extend(notes0)
if rec.typ == R_MIPS_HI16:
addend, expr, sem1, notes1 = self._add_lo16_rel_addend(rec, addend, expr)
semantic_deps.extend(sem1)
notes.extend(notes1)
addend, expr = self._adjust_addend(rec, addend, expr)
else:
addend = saved_addend
expr = TraceExpr(f"SAVED_ADDEND from #{saved_from_idx} -> 0x{addend:08x}", [saved_from_idx] if saved_from_idx is not None else [], [])
if saved_from_idx is not None:
semantic_deps.append(("saved-addend-from", saved_from_idx))
notes.append(f"saved-addend from #{saved_from_idx}")

save_for_next = self._next_same_offset_nonzero(rec.idx)
status, value, calc_text, calc_expr = self._calculate_relocation(rec, addend, expr)
trace = RelocTrace(
idx=rec.idx,
off=rec.off,
typ=rec.typ,
sym=safe_sym(rec.sym),
S=rec.S,
addend=addend & 0xFFFFFFFF,
addend_expr=calc_expr,
save_for_next=save_for_next,
status=status,
value=value & 0xFFFFFFFF,
calc_text=calc_text,
saved_from_prev=saved_from_prev,
semantic_deps=list(dict.fromkeys(semantic_deps)),
notes=notes,
)
self.trace_by_idx[rec.idx] = trace

if rec.typ == R_MIPS_16:
self.final_actual_r16.append(trace)

if status == "overflow":
A = sign_extend(addend, 16) if rec.typ == R_MIPS_16 else addend
self.errors.append((rec.idx, rec.off, rec.typ, safe_sym(rec.sym), rec.S, A, value & 0xFFFFFFFF))
if self.stop_on_error:
break

if save_for_next:
saved_addend = value & 0xFFFFFFFF
saved_from_prev = True
saved_from_idx = rec.idx
else:
saved_from_prev = False
saved_from_idx = None
self._store_contents(rec, value, trace)

return self

def dump_first_error(self):
if not self.errors:
print("no overflow / no early error")
return None
i, off, typ, sym, S, A, V = self.errors[0]
print(f"first error at reloc #{i}")
print(f"offset = 0x{off:08x}")
print(f"type = {typ} ({RELOC_NAME.get(typ, 'UNKNOWN')})")
print(f"sym = {sym}")
print(f"S = 0x{S:08x}")
print(f"A = {A}")
print(f"V = {V} (0x{V & 0xFFFFFFFF:08x})")
return i

def dump_context_around_index(self, idx: int, before: int = 8, after: int = 8):
lo = max(0, idx - before)
hi = min(len(self.relocs), idx + after + 1)
print(f"[event context around reloc #{idx}]")
for i in range(lo, hi):
tr = self.trace_by_idx.get(i)
if tr is None:
continue
mark = "<FOCUS>" if tr.idx == idx else " "
sem = ", ".join(f"{k}:#{v}" for k, v in tr.semantic_deps[:3])
if len(tr.semantic_deps) > 3:
sem += ", ..."
print(
f"{mark} #{tr.idx:6d} off=0x{tr.off:04x} typ={RELOC_NAME.get(tr.typ, tr.typ):>11} "
f"sym={tr.sym:>30} S=0x{tr.S:08x} addend=0x{tr.addend:08x} save={tr.save_for_next} "
f"status={tr.status:>8} value=0x{tr.value:08x} | {tr.calc_text}"
)
if sem:
print(f" deps: {sem}")

def dump_watch_history(self, watched: Set[int], limit: int = 200):
rows = [x for x in self.write_log if x[1] in watched]
if not rows:
print("[no watched writes]")
return
print("[watched writes]")
for idx, off, before, after, reason in rows[:limit]:
print(f" #{idx:6d} off=0x{off:04x} 0x{before:08x} -> 0x{after:08x} | {reason}")
if len(rows) > limit:
print(f" ... truncated {len(rows) - limit} more writes")

def dump_reloc_stats(self):
print("[relocation stats]")
for typ, cnt in sorted(self.reloc_counter.items()):
print(f" {typ:2d} {RELOC_NAME.get(typ, 'UNKNOWN'):>12}: {cnt}")

def dump_actual_r16(self):
print("[actual R_MIPS_16 relocations]")
for tr in self.final_actual_r16:
A = sign_extend(tr.addend, 16)
V = (tr.S + A) & 0xFFFFFFFF
status = "OVERFLOW" if not fits_signed_16((V if V <= 0x7FFFFFFF else V - 0x100000000)) else "ok"
print(
f" #{tr.idx:6d} off=0x{tr.off:04x} sym={tr.sym} "
f"S=0x{tr.S:08x} addend=0x{tr.addend:08x} A={A} V=0x{V:08x} {status}"
)

def dump_slice(self, root_idx: int, max_depth: int = 8, include_notes: bool = True):
print(f"[backward slice from reloc #{root_idx}]")
seen: Set[Tuple[str, int]] = set()

def rec(kind: str, idx: int, depth: int):
indent = " " * depth
key = (kind, idx)
if key in seen:
print(f"{indent}{kind} #{idx} (already shown)")
return
seen.add(key)
tr = self.trace_by_idx.get(idx)
if tr is None:
print(f"{indent}{kind} #{idx} <missing>")
return
edge_prefix = "" if kind == "root" else f"[{kind}] "
print(
f"{indent}{edge_prefix}#{tr.idx} off=0x{tr.off:04x} {RELOC_NAME.get(tr.typ, tr.typ)} sym={tr.sym} "
f"addend=0x{tr.addend:08x} value=0x{tr.value:08x} status={tr.status}"
)
print(f"{indent} expr: {tr.addend_expr.text}")
if include_notes:
for note in tr.notes[:6]:
print(f"{indent} note: {note}")
if len(tr.notes) > 6:
print(f"{indent} note: ... {len(tr.notes) - 6} more")
for leaf in tr.addend_expr.leaves:
print(f"{indent} leaf: {leaf}")
if depth >= max_depth:
if tr.addend_expr.reloc_deps or tr.semantic_deps:
print(f"{indent} ... max-depth reached")
return
emitted: Set[int] = set()
# Semantic deps first: matched LO / saved-addend / last-writer are easier to read.
for label, dep in tr.semantic_deps:
emitted.add(dep)
rec(label, dep, depth + 1)
for dep in tr.addend_expr.reloc_deps:
if dep in emitted:
continue
rec("expr-dep", dep, depth + 1)

rec("root", root_idx, 0)

def dump_slice_from_first_error(self, max_depth: int = 8, include_notes: bool = True):
if not self.errors:
print("[no error to slice]")
return
self.dump_slice(self.errors[0][0], max_depth=max_depth, include_notes=include_notes)

def dump_last_writer_for_offset(self, off: int):
idx = self.word_def_by_off.get(off)
if idx is None:
print(f"[last writer] off=0x{off:x} <none>")
return
print(f"[last writer] off=0x{off:x} -> reloc #{idx}")
self.dump_slice(idx, max_depth=6)

def build_sym_source(args) -> Tuple[Optional[Dict[str, int]], str]:
if args.sym_elf:
vals = load_named_symbols_from_elf(args.sym_elf)
if args.normalize_to_a:
base = vals["a"]
vals = {k: (v - base) & 0xFFFFFFFF for k, v in vals.items()}
if args.rebase:
vals = {k: (v + args.rebase) & 0xFFFFFFFF for k, v in vals.items()}
return vals, f"raw ELF symbols from {args.sym_elf}"
if args.chars:
return None, f"chars='{args.chars}' base=0x{args.base_addr:x}"
return None, "no external symbol source"

def main():
ap = argparse.ArgumentParser(description="Stronger def-use / backward-slice tracer for Kalmar re-linkme")
ap.add_argument("--obj", default="flagchecker.o")
ap.add_argument("--sym-elf", help="Load a..z symbol values directly from main.o / dummy.out / other ELF")
ap.add_argument("--normalize-to-a", action="store_true")
ap.add_argument("--rebase", type=parse_int, default=0)
ap.add_argument("--chars", default=KNOWN_TEMPLATE)
ap.add_argument("--base-addr", type=parse_int, default=0)
ap.add_argument("--watch-offset", action="append", default=[])
ap.add_argument("--dump-stats", action="store_true")
ap.add_argument("--dump-actual-r16", action="store_true")
ap.add_argument("--context-before", type=int, default=40)
ap.add_argument("--context-after", type=int, default=12)
ap.add_argument("--focus-reloc", action="append", default=[])
ap.add_argument("--focus-before", type=int, default=8)
ap.add_argument("--focus-after", type=int, default=8)
ap.add_argument("--stop-on-error", action="store_true")
ap.add_argument("--slice-first-error", action="store_true")
ap.add_argument("--slice-reloc", action="append", default=[])
ap.add_argument("--slice-offset", action="append", default=[])
ap.add_argument("--slice-depth", type=int, default=8)
ap.add_argument("--no-slice-notes", action="store_true")
args = ap.parse_args()

watched = {parse_int(x) for x in args.watch_offset}
ext_symvals, sym_desc = build_sym_source(args)
chars = None if ext_symvals is not None else [ord(c) for c in args.chars]

tracer = MiniMipsSlicePlus(
args.obj,
chars=chars,
char_template=args.chars,
base_addr=args.base_addr,
ext_symvals=ext_symvals,
watch_offsets=list(watched),
stop_on_error=args.stop_on_error,
).apply()

print(f"[config] obj={args.obj}")
print(f"[config] symbol-source={sym_desc}")
if watched:
print("[config] watched offsets=" + ", ".join(f"0x{x:x}" for x in sorted(watched)))

if args.dump_stats:
tracer.dump_reloc_stats()

err_idx = tracer.dump_first_error()
if err_idx is not None:
tracer.dump_context_around_index(err_idx, args.context_before, args.context_after)
for focus in args.focus_reloc:
tracer.dump_context_around_index(parse_int(focus), args.focus_before, args.focus_after)
if watched:
tracer.dump_watch_history(watched)
if args.dump_actual_r16:
tracer.dump_actual_r16()
if args.slice_first_error:
tracer.dump_slice_from_first_error(max_depth=args.slice_depth, include_notes=not args.no_slice_notes)
for idx in args.slice_reloc:
tracer.dump_slice(parse_int(idx), max_depth=args.slice_depth, include_notes=not args.no_slice_notes)
for off in args.slice_offset:
tracer.dump_last_writer_for_offset(parse_int(off))

if __name__ == "__main__": main()

There are a total of 4 gates of check observed:

1
2
3
4
0x53DF: whether -fno option opened
0x53E2: flag ASCII check
0x53E6: format 'kalmar{}' check
0x53EA: content of flag check

The values for these positions are seemed to be initialized to 0x7F00. If there are corresponding errors, plus an addend of 0x100 to become 0x8000 and exit with an overflow error. 0x53DC is a possible state sharer for these errors.

Trace script (link_trace_plot.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
import argparse
import csv
import io
from collections import Counter
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple

from elftools.elf.elffile import ELFFile

R_MIPS_16 = 1
R_MIPS_32 = 2
R_MIPS_HI16 = 5
R_MIPS_LO16 = 6

RELOC_NAME = {
R_MIPS_16: "R_MIPS_16",
R_MIPS_32: "R_MIPS_32",
R_MIPS_HI16: "R_MIPS_HI16",
R_MIPS_LO16: "R_MIPS_LO16",
}
RELOC_ID_BY_NAME = {v: k for k, v in RELOC_NAME.items()}

HOWTO = {
R_MIPS_16: {
"name": "R_MIPS_16",
"size": 4,
"bitsize": 16,
"src_mask": 0xFFFF,
"dst_mask": 0xFFFF,
"shift": 0,
"overflow": "signed16",
},
R_MIPS_32: {
"name": "R_MIPS_32",
"size": 4,
"bitsize": 32,
"src_mask": 0xFFFFFFFF,
"dst_mask": 0xFFFFFFFF,
"shift": 0,
"overflow": "none",
},
R_MIPS_HI16: {
"name": "R_MIPS_HI16",
"size": 4,
"bitsize": 16,
"src_mask": 0xFFFF,
"dst_mask": 0xFFFF,
"shift": 0,
"overflow": "none",
},
R_MIPS_LO16: {
"name": "R_MIPS_LO16",
"size": 4,
"bitsize": 16,
"src_mask": 0xFFFF,
"dst_mask": 0xFFFF,
"shift": 0,
"overflow": "none",
},
}

def parse_int(x: str) -> int:
return int(x, 0)

def safe_sym(name: str) -> str:
return name.encode("unicode_escape", errors="backslashreplace").decode("ascii")

def named_range_symbols() -> List[str]:
return [chr(ord("a") + i) for i in range(26)]

def s16(x: int) -> int:
x &= 0xFFFF
return x - 0x10000 if x & 0x8000 else x

def sign_extend(value: int, bits: int) -> int:
mask = (1 << bits) - 1
value &= mask
sign = 1 << (bits - 1)
return value - (1 << bits) if value & sign else value

def fits_signed_16(x: int) -> bool:
return -0x8000 <= x <= 0x7FFF

def u32be(buf: bytearray, off: int) -> int:
return int.from_bytes(buf[off:off + 4], "big", signed=False)

def w32be(buf: bytearray, off: int, value: int):
buf[off:off + 4] = (value & 0xFFFFFFFF).to_bytes(4, "big")

def load_named_symbols_from_elf(path: str, names: Optional[List[str]] = None) -> Dict[str, int]:
names = set(named_range_symbols() if names is None else names)
with open(path, "rb") as f:
elf = ELFFile(f)
symtab = elf.get_section_by_name(".symtab")
if symtab is None:
raise ValueError(f"{path}: no .symtab")
out = {}
for sym in symtab.iter_symbols():
if sym.name in names:
out[sym.name] = sym["st_value"]
missing = sorted(names - set(out))
if missing:
raise ValueError(f"{path}: missing symbols {missing}")
return out

@dataclass
class RelocRec:
idx: int
off: int
typ: int
sym_idx: int
sym: str
S: int

@dataclass
class RelocEvent:
idx: int
off: int
typ: int
sym: str
S: int
detail: str

@dataclass
class WriteEvent:
idx: int
off: int
size: int
before: int
after: int
reason: str

@dataclass
class PairInfo:
hi_idx: int
hi_off: int
lo_idx: int
lo_off: int
sym: str

@dataclass
class TraceRow:
idx: int
off: int
typ: int
typ_name: str
sym: str
S: int
addend: int
addend_src: str
value: int
status: str
save_for_next: bool
carry_out: Optional[int]

class MiniMipsLdLike:
def __init__(
self,
obj_path: str,
*,
chars: Optional[List[int]] = None,
base_addr: int = 0,
ext_symvals: Optional[Dict[str, int]] = None,
watch_offsets: Optional[List[int]] = None,
trace_writes: bool = False,
stop_on_error: bool = False,
):
self.obj_path = obj_path
self.chars = chars
self.base_addr = base_addr
self.ext_symvals = {} if ext_symvals is None else dict(ext_symvals)
self.watch_offsets = set(watch_offsets or [])
self.trace_writes = trace_writes
self.stop_on_error = stop_on_error
self.events: List[RelocEvent] = []
self.write_log: List[WriteEvent] = []
self.errors: List[Tuple[int, int, int, str, int, int, int]] = []
self.reloc_counter = Counter()
self.last_addend_by_idx: Dict[int, int] = {}
self.last_addend_src_by_idx: Dict[int, str] = {}
self.trace_rows: List[TraceRow] = []

with open(obj_path, "rb") as f:
blob = f.read()
self._stream = io.BytesIO(blob)
self.elf = ELFFile(self._stream)
self.sec = self.elf.get_section_by_name(".flagchecker")
self.relsec = self.elf.get_section_by_name(".rel.flagchecker") or self.elf.get_section_by_name(".flagchecker.rel")
self.symtab = self.elf.get_section_by_name(".symtab")
if self.sec is None or self.relsec is None or self.symtab is None:
raise ValueError("missing .flagchecker/.rel.flagchecker/.symtab")
self.contents = bytearray(self.sec.data())
self.symvals = self._build_symbol_values()
self.relocs = self._load_relocs()
self.hi_to_lo: Dict[int, int] = {}
self.lo_to_his: Dict[int, List[int]] = {}
self._prescan_pairs()

def _build_symbol_values(self) -> Dict[str, int]:
vals: Dict[str, int] = {}
for sym in self.symtab.iter_symbols():
if sym.name and sym["st_shndx"] == "SHN_ABS":
vals[sym.name] = sym["st_value"]
vals.update(self.ext_symvals)
if self.chars is not None:
if len(self.chars) != 26:
raise ValueError(f"chars length must be 26, got {len(self.chars)}")
cur = self.base_addr
names = named_range_symbols()
vals[names[0]] = cur
for i in range(1, 26):
cur += 4 * self.chars[i - 1]
vals[names[i]] = cur
return vals

def _sym_name(self, idx: int) -> str:
return self.symtab.get_symbol(idx).name

def _sym_value(self, idx: int) -> int:
sym = self.symtab.get_symbol(idx)
if sym.name in self.symvals:
return self.symvals[sym.name]
return sym["st_value"]

def _load_relocs(self) -> List[RelocRec]:
out: List[RelocRec] = []
for i, rel in enumerate(self.relsec.iter_relocations()):
off = rel["r_offset"]
typ = rel["r_info_type"]
sym_idx = rel["r_info_sym"]
out.append(RelocRec(i, off, typ, sym_idx, self._sym_name(sym_idx), self._sym_value(sym_idx)))
return out

def _prescan_pairs(self):
from collections import defaultdict
pending: Dict[str, List[int]] = defaultdict(list)
lo_to_his = defaultdict(list)
for rec in self.relocs:
if rec.typ == R_MIPS_HI16:
pending[rec.sym].append(rec.idx)
elif rec.typ == R_MIPS_LO16:
if pending[rec.sym]:
his = list(pending[rec.sym])
pending[rec.sym].clear()
for hi_idx in his:
self.hi_to_lo[hi_idx] = rec.idx
lo_to_his[rec.idx].append(hi_idx)
self.lo_to_his = dict(lo_to_his)

def _pair_info_for_hi(self, hi_idx: int) -> Optional[PairInfo]:
lo_idx = self.hi_to_lo.get(hi_idx)
if lo_idx is None:
return None
hi = self.relocs[hi_idx]
lo = self.relocs[lo_idx]
return PairInfo(hi_idx, hi.off, lo_idx, lo.off, hi.sym)

def _next_same_offset_nonzero(self, idx: int) -> bool:
return idx + 1 < len(self.relocs) and self.relocs[idx + 1].off == self.relocs[idx].off and self.relocs[idx + 1].typ != 0

def _obtain_contents(self, rec: RelocRec) -> int:
h = HOWTO[rec.typ]
word = u32be(self.contents, rec.off)
return word & h["src_mask"]

def _store_contents(self, rec: RelocRec, value: int):
h = HOWTO[rec.typ]
if h["size"] != 4:
raise NotImplementedError("only size=4 howto supported")
old_word = u32be(self.contents, rec.off)
new_word = (old_word & (~h["dst_mask"] & 0xFFFFFFFF)) | (value & h["dst_mask"])
self._note_write(rec.idx, rec.off, 4, old_word, new_word, f"store {RELOC_NAME.get(rec.typ, rec.typ)}")
w32be(self.contents, rec.off, new_word)

def _read_rel_addend(self, rec: RelocRec) -> Tuple[int, str]:
raw = self._obtain_contents(rec)
return raw, f"contents&mask@0x{rec.off:x}=0x{raw:x}"

def _add_lo16_rel_addend(self, rec: RelocRec, addend: int) -> Tuple[int, str]:
pair = self._pair_info_for_hi(rec.idx)
if pair is None:
return addend, "no-match-lo16"
lo_rec = self.relocs[pair.lo_idx]
lo_addend, lo_src = self._read_rel_addend(lo_rec)
lo_sext = sign_extend(lo_addend, 16)
combined = ((addend & 0xFFFF) << 16) + lo_sext
return combined & 0xFFFFFFFF, f"match-lo16@0x{pair.lo_off:x} raw=0x{lo_addend:x} sext={lo_sext} src={lo_src}"

def _adjust_addend(self, rec: RelocRec, addend: int) -> Tuple[int, str]:
return addend & 0xFFFFFFFF, "no-local-adjust"

def _calculate_relocation(self, rec: RelocRec, addend: int) -> Tuple[str, int, str]:
typ = rec.typ
S = rec.S & 0xFFFFFFFF
if typ == R_MIPS_32:
value = (S + addend) & 0xFFFFFFFF
return "ok", value, f"R_MIPS_32 value=0x{value:08x}"
if typ == R_MIPS_HI16:
full = (S + addend) & 0xFFFFFFFF
value = ((full - s16(full)) >> 16) & 0xFFFF
return "ok", value, f"R_MIPS_HI16 full=0x{full:08x} value=0x{value:04x}"
if typ == R_MIPS_LO16:
value = (S + sign_extend(addend, 16)) & 0xFFFFFFFF
return "ok", value, f"R_MIPS_LO16 value=0x{value:08x}"
if typ == R_MIPS_16:
A = sign_extend(addend, 16)
value = S + A
if not fits_signed_16(value if value <= 0x7FFFFFFF else value - 0x100000000):
return "overflow", value & 0xFFFFFFFF, f"R_MIPS_16 overflow S=0x{S:08x} A={A} value=0x{value & 0xFFFFFFFF:08x}"
return "ok", value & 0xFFFFFFFF, f"R_MIPS_16 value=0x{value & 0xFFFFFFFF:08x}"
raise NotImplementedError(f"unsupported reloc type {typ}")

def _note_write(self, idx: int, off: int, size: int, before: int, after: int, reason: str):
if self.trace_writes or off in self.watch_offsets:
self.write_log.append(WriteEvent(idx, off, size, before, after, reason))

def _log(self, rec: RelocRec, detail: str):
self.events.append(RelocEvent(rec.idx, rec.off, rec.typ, safe_sym(rec.sym), rec.S, detail))

def apply(self):
addend = 0
saved_from_prev = False
for rec in self.relocs:
self.reloc_counter[rec.typ] += 1
if not saved_from_prev:
addend, src = self._read_rel_addend(rec)
if rec.typ == R_MIPS_HI16:
addend, extra = self._add_lo16_rel_addend(rec, addend)
src = f"{src}; {extra}"
else:
shift = HOWTO[rec.typ]["shift"]
addend = (addend << shift) & 0xFFFFFFFF
src = f"{src}; <<{shift}"
addend, adj = self._adjust_addend(rec, addend)
src = f"{src}; {adj}"
else:
src = "saved-from-prev-same-offset"
self.last_addend_by_idx[rec.idx] = addend & 0xFFFFFFFF
self.last_addend_src_by_idx[rec.idx] = src
save_for_next = self._next_same_offset_nonzero(rec.idx)
status, value, calc = self._calculate_relocation(rec, addend)
self._log(rec, f"addend=0x{addend & 0xFFFFFFFF:08x} src={src} save_for_next={save_for_next} | {calc}")
self.trace_rows.append(TraceRow(
idx=rec.idx,
off=rec.off,
typ=rec.typ,
typ_name=RELOC_NAME.get(rec.typ, str(rec.typ)),
sym=safe_sym(rec.sym),
S=rec.S,
addend=addend & 0xFFFFFFFF,
addend_src=src,
value=value & 0xFFFFFFFF,
status=status,
save_for_next=save_for_next,
carry_out=(value & 0xFFFFFFFF) if save_for_next else None,
))
if status == "overflow":
A = sign_extend(addend, 16) if rec.typ == R_MIPS_16 else addend
self.errors.append((rec.idx, rec.off, rec.typ, safe_sym(rec.sym), rec.S, A, value))
if self.stop_on_error:
break
if save_for_next:
addend = value & 0xFFFFFFFF
saved_from_prev = True
self._log(rec, f"carry addend forward = 0x{addend:08x}")
else:
saved_from_prev = False
self._store_contents(rec, value)
return self

def dump_first_error(self):
if not self.errors:
print("no overflow / no early error")
return
i, off, typ, sym, S, A, V = self.errors[0]
print(f"first error at reloc #{i}")
print(f"offset = 0x{off:08x}")
print(f"type = {typ} ({RELOC_NAME.get(typ, 'UNKNOWN')})")
print(f"sym = {sym}")
print(f"S = 0x{S:08x}")
print(f"A = {A}")
print(f"V = {V} (0x{V & 0xFFFFFFFF:08x})")

def dump_context_around_index(self, idx: int, before: int = 40, after: int = 12):
lo = max(0, idx - before)
hi = min(len(self.events), idx + after + 1)
print(f"[event context around reloc #{idx}]")
for ev in self.events[lo:hi]:
mark = "<FOCUS>" if ev.idx == idx else " "
print(f"{mark} #{ev.idx:6d} off=0x{ev.off:04x} typ={RELOC_NAME.get(ev.typ, ev.typ):>11} sym={ev.sym:>20} S=0x{ev.S:08x} | {ev.detail}")

def dump_watch_history(self, limit: int = 200):
if not self.write_log:
print("[no watched writes]")
return
print("[watched writes]")
for wev in self.write_log[:limit]:
fmt = f"0x{{:0{wev.size * 2}x}}"
print(f" #{wev.idx:6d} off=0x{wev.off:04x} size={wev.size} {fmt.format(wev.before)} -> {fmt.format(wev.after)} | {wev.reason}")
if len(self.write_log) > limit:
print(f" ... truncated {len(self.write_log) - limit} more writes")

def dump_reloc_stats(self):
print("[relocation stats]")
for typ, cnt in sorted(self.reloc_counter.items()):
print(f" {typ:2d} {RELOC_NAME.get(typ, 'UNKNOWN'):>12}: {cnt}")

def dump_actual_r16(self):
print("[actual R_MIPS_16 relocations]")
for rec in self.relocs:
if rec.typ != R_MIPS_16:
continue
addend = self.last_addend_by_idx.get(rec.idx, 0)
A = sign_extend(addend, 16)
V = (rec.S + A) & 0xFFFFFFFF
status = "OVERFLOW" if not fits_signed_16((V if V <= 0x7FFFFFFF else V - 0x100000000)) else "ok"
print(f" #{rec.idx:6d} off=0x{rec.off:04x} sym={safe_sym(rec.sym)} S=0x{rec.S:08x} addend=0x{addend:08x} src={self.last_addend_src_by_idx.get(rec.idx,'')} A={A} V=0x{V:08x} {status}")

def write_trace_csv(self, path: str):
with open(path, 'w', newline='', encoding='utf-8') as f:
w = csv.writer(f)
w.writerow([
'idx', 'off', 'off_hex', 'typ', 'typ_name', 'sym', 'S', 'S_hex',
'addend', 'addend_hex', 'addend_src', 'value', 'value_hex',
'status', 'save_for_next', 'carry_out', 'carry_out_hex'
])
for row in self.trace_rows:
w.writerow([
row.idx,
row.off,
f"0x{row.off:x}",
row.typ,
row.typ_name,
row.sym,
row.S,
f"0x{row.S & 0xFFFFFFFF:x}",
row.addend,
f"0x{row.addend:x}",
row.addend_src,
row.value,
f"0x{row.value:x}",
row.status,
int(row.save_for_next),
'' if row.carry_out is None else row.carry_out,
'' if row.carry_out is None else f"0x{row.carry_out:x}",
])

def build_sym_source(args) -> Tuple[Optional[Dict[str, int]], str]:
if args.sym_elf:
vals = load_named_symbols_from_elf(args.sym_elf)
if args.normalize_to_a:
base = vals['a']
vals = {k: (v - base) & 0xFFFFFFFF for k, v in vals.items()}
if args.rebase:
vals = {k: (v + args.rebase) & 0xFFFFFFFF for k, v in vals.items()}
return vals, f"raw ELF symbols from {args.sym_elf}"
if args.chars:
return None, f"chars='{args.chars}' base=0x{args.base_addr:x}"
return None, 'no external symbol source'

def plot_trace(trace_rows: List[TraceRow], out_png: str, *, title: str = 'Relocation trace', show_gates: bool = True, gates: Optional[List[int]] = None, annotate_error_idx: Optional[int] = None, line: bool = False):
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

if gates is None:
gates = [0x53df, 0x53e2, 0x53e6, 0x53ea]

xs = [r.idx for r in trace_rows]
ys = [r.off for r in trace_rows]
type_order = [R_MIPS_32, R_MIPS_HI16, R_MIPS_LO16, R_MIPS_16]
colors = {
R_MIPS_32: '#1f77b4',
R_MIPS_HI16: '#ff7f0e',
R_MIPS_LO16: '#2ca02c',
R_MIPS_16: '#d62728',
}

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(16, 10), sharex=True, gridspec_kw={'height_ratios': [3, 1]})

for typ in type_order:
tx = [r.idx for r in trace_rows if r.typ == typ]
ty = [r.off for r in trace_rows if r.typ == typ]
if tx:
ax1.scatter(tx, ty, s=5, alpha=0.55, label=RELOC_NAME.get(typ, str(typ)), c=colors[typ])
if line:
ax1.plot(xs, ys, linewidth=0.3, alpha=0.25, color='gray')

if show_gates:
for g in gates:
ax1.axhline(g, linestyle='--', linewidth=0.8, alpha=0.7, color='gray')
ax1.text(0.995, g, f' 0x{g:04x}', transform=ax1.get_yaxis_transform(), ha='right', va='bottom', fontsize=8)

if annotate_error_idx is not None:
hit = next((r for r in trace_rows if r.idx == annotate_error_idx), None)
if hit is not None:
ax1.scatter([hit.idx], [hit.off], s=45, c='black', marker='x', linewidths=1.2, zorder=5)
ax1.annotate(f'first error #{hit.idx}\\n0x{hit.off:04x}', (hit.idx, hit.off), xytext=(8, 8), textcoords='offset points', fontsize=8)

ax1.set_ylabel('target offset (hex scale values)')
ax1.set_title(title)
ax1.legend(loc='upper right', markerscale=3)
ax1.grid(alpha=0.15)

d_off = [0]
for i in range(1, len(trace_rows)):
d_off.append(trace_rows[i].off - trace_rows[i - 1].off)
ax2.scatter(xs, d_off, s=4, alpha=0.45, c='#444444')
ax2.axhline(0, linestyle='--', linewidth=0.8, alpha=0.6, color='gray')
ax2.set_ylabel('Δoff')
ax2.set_xlabel('relocation index')
ax2.grid(alpha=0.15)

fig.tight_layout()
fig.savefig(out_png, dpi=200)
plt.close(fig)

def write_summary(trace_rows: List[TraceRow], out_path: str, *, jump_threshold: int, top_offsets: int = 256, first_error_idx: Optional[int] = None):
from collections import Counter
cnt = Counter(r.off for r in trace_rows)
jumps = []
for i in range(1, len(trace_rows)):
prev = trace_rows[i - 1]
cur = trace_rows[i]
d = cur.off - prev.off
if abs(d) >= jump_threshold:
jumps.append((abs(d), prev.idx, prev.off, cur.idx, cur.off, d))
jumps.sort(reverse=True)

with open(out_path, 'w', encoding='utf-8') as f:
f.write('[top offsets]\\n')
for off, c in cnt.most_common(top_offsets):
f.write(f' off=0x{off:04x} count={c}\\n')
f.write('\\n[top jumps]\\n')
for _, prev_idx, prev_off, cur_idx, cur_off, d in jumps[:50]:
f.write(f' #{prev_idx} 0x{prev_off:04x} -> #{cur_idx} 0x{cur_off:04x} delta={d:+#x}\\n')
if first_error_idx is not None:
hit = next((r for r in trace_rows if r.idx == first_error_idx), None)
if hit is not None:
f.write('\\n[first error]\\n')
f.write(f' idx={hit.idx} off=0x{hit.off:04x} typ={hit.typ_name} sym={hit.sym} addend=0x{hit.addend:x} value=0x{hit.value:x} status={hit.status}\\n')

def main():
ap = argparse.ArgumentParser(description='Original-style mini-linker + full relocation trace + plot')
ap.add_argument('--obj', default='flagchecker.o')
ap.add_argument('--sym-elf', help='Load a..z symbol values directly from main.o / dummy.out / other ELF')
ap.add_argument('--normalize-to-a', action='store_true')
ap.add_argument('--rebase', type=parse_int, default=0)
ap.add_argument('--chars', default='kalmar{flagflagflagflagfl}')
ap.add_argument('--base-addr', type=parse_int, default=0)
ap.add_argument('--watch-offset', action='append', default=[])
ap.add_argument('--trace-writes', action='store_true')
ap.add_argument('--dump-stats', action='store_true')
ap.add_argument('--dump-actual-r16', action='store_true')
ap.add_argument('--context-before', type=int, default=40)
ap.add_argument('--context-after', type=int, default=12)
ap.add_argument('--focus-reloc', action='append', default=[])
ap.add_argument('--focus-before', type=int, default=8)
ap.add_argument('--focus-after', type=int, default=8)
ap.add_argument('--stop-on-error', action='store_true')
ap.add_argument('--trace-csv', help='Write full relocation trace to CSV')
ap.add_argument('--plot', help='Write relocation trace scatter plot PNG')
ap.add_argument('--plot-title', default='Relocation trace')
ap.add_argument('--plot-line', action='store_true')
ap.add_argument('--summary', help='Write textual trace summary')
ap.add_argument('--jump-threshold', type=parse_int, default=0x400)
args = ap.parse_args()

watched = [parse_int(x) for x in args.watch_offset]
ext_symvals, sym_desc = build_sym_source(args)
chars = None if ext_symvals is not None else [ord(c) for c in args.chars]

linker = MiniMipsLdLike(
args.obj,
chars=chars,
base_addr=args.base_addr,
ext_symvals=ext_symvals,
watch_offsets=watched,
trace_writes=args.trace_writes,
stop_on_error=args.stop_on_error,
).apply()

print(f"[config] obj={args.obj}")
print(f"[config] symbol-source={sym_desc}")
if watched:
print('[config] watched offsets=' + ', '.join(f'0x{x:x}' for x in watched))
if args.dump_stats:
linker.dump_reloc_stats()
linker.dump_first_error()
if linker.errors:
linker.dump_context_around_index(linker.errors[0][0], args.context_before, args.context_after)
for focus in args.focus_reloc:
linker.dump_context_around_index(parse_int(focus), args.focus_before, args.focus_after)
if watched:
linker.dump_watch_history()
if args.dump_actual_r16:
linker.dump_actual_r16()

if args.trace_csv:
linker.write_trace_csv(args.trace_csv)
print(f"[wrote trace csv] {args.trace_csv}")
first_error_idx = linker.errors[0][0] if linker.errors else None
if args.plot:
plot_trace(linker.trace_rows, args.plot, title=args.plot_title, annotate_error_idx=first_error_idx, line=args.plot_line)
print(f"[wrote plot] {args.plot}")
if args.summary:
write_summary(linker.trace_rows, args.summary, jump_threshold=args.jump_threshold, first_error_idx=first_error_idx)
print(f"[wrote summary] {args.summary}")

if __name__ == '__main__':
main()

Run:

1
python3 link_trace_plot.py --obj flagchecker.o --sym-elf dummy.out --trace-csv ./trace.csv --plot ./trace.png --summary ./trace.summary.txt --plot-title "re-linkme relocation trace" --plot-line --dump-stats --dump-actual-r16

The visualize plot:

Hot point seeking script (total_controlled.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#!/usr/bin/env python3
from __future__ import annotations

import argparse
import csv
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Iterable, List, Sequence

DEFAULT_BASELINE = "kalmar{flagflagflagflagfl}"
DEFAULT_TEST_VALUES = "0AaMmZz9"

def c_char_literal(ch: str) -> str:
code = ord(ch)
if ch == "'":
return r"'\\\\''"
if ch == "\\\\":
return r"'\\\\\\\\'"
if 32 <= code <= 126:
return f"'{ch}'"
return f"'\\\\x{code:02x}'"

def infer_unknown_positions(flag: str) -> List[int]:
if not (flag.startswith("kalmar{") and flag.endswith("}")):
raise ValueError("baseline must look like kalmar{...}")
return list(range(7, len(flag) - 1))

def mutate_single(flag: str, pos: int, ch: str) -> str:
if len(ch) != 1:
raise ValueError("single mutation requires exactly one character")
if not (0 <= pos < len(flag)):
raise ValueError(f"position out of range: {pos}")
return flag[:pos] + ch + flag[pos + 1:]

def mutate_balanced(flag: str, pos: int, delta: int) -> str:
if not (0 <= pos < len(flag) - 1):
raise ValueError(f"balanced position out of range: {pos}")
a = ord(flag[pos])
b = ord(flag[pos + 1])
na = a + delta
nb = b - delta
if not (0 <= na <= 255 and 0 <= nb <= 255):
raise ValueError(
f"balanced mutation overflows byte range at pos={pos}: {a}->{na}, {b}->{nb}"
)
chars = list(flag)
chars[pos] = chr(na)
chars[pos + 1] = chr(nb)
return "".join(chars)

def build_dummy_for_flag(flag: str, workdir: Path, gcc: str) -> Path:
if len(flag) != 26:
raise ValueError(f"flag length must be 26, got {len(flag)}")
names = [chr(ord("a") + i) for i in range(26)]
decls = [f"int {name}[{c_char_literal(ch)}];" for name, ch in zip(names, flag)]
main_text = (
"#include <stdio.h>\\n"
+ "\\n".join(decls)
+ "\\nextern char message[];\\n"
+ "int main(void) { puts(message); return 0; }\\n"
)
stub_text = 'char message[] = "x";\\n'

main_c = workdir / "main.c"
stub_c = workdir / "stub.c"
dummy_out = workdir / "dummy.out"
map_txt = workdir / "map.txt"

main_c.write_text(main_text, encoding="utf-8")
stub_c.write_text(stub_text, encoding="utf-8")

cmd = [
gcc,
str(main_c),
str(stub_c),
"-fno-toplevel-reorder",
"-static",
f"-Wl,-Map={map_txt}",
"-o",
str(dummy_out),
]
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
return dummy_out

def run_trace(
python_exe: str,
tracer: Path,
obj: Path,
out_csv: Path,
quiet: bool,
*,
sym_elf: Path | None = None,
chars: str | None = None,
timeout: int = 300,
) -> None:
cmd = [python_exe, str(tracer), "--obj", str(obj)]
if sym_elf is not None:
cmd.extend(["--sym-elf", str(sym_elf)])
elif chars is not None:
cmd.extend(["--chars", chars])
else:
raise ValueError("either sym_elf or chars must be provided")
cmd.extend(["--trace-csv", str(out_csv)])

if quiet:
proc = subprocess.run(
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True,
timeout=timeout,
)
stdout_text = ""
else:
proc = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=timeout,
)
stdout_text = proc.stdout

if proc.returncode != 0:
raise RuntimeError(
"trace command failed\\n"
f"cmd: {' '.join(cmd)}\\n"
f"stdout:\\n{stdout_text}\\n"
f"stderr:\\n{proc.stderr}"
)
if not out_csv.exists():
raise FileNotFoundError(f"trace csv not written: {out_csv}")
if not quiet and stdout_text.strip():
print(stdout_text.rstrip())

def write_manifest(manifest_path: Path, rows: Sequence[dict]) -> None:
fieldnames = [
"case_id",
"mode",
"position",
"position_hex",
"char",
"char_ord",
"delta",
"flag",
"csv_path",
]
with manifest_path.open("w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)

def parse_int_list(spec: str) -> List[int]:
out = []
for part in spec.split(","):
part = part.strip()
if not part:
continue
out.append(int(part, 0))
return out

def resolve_default_paths(script_dir: Path) -> tuple[Path, Path]:
obj1 = script_dir / "flagchecker.o"
if obj1.exists():
return obj1, script_dir / "link_trace_plot.py"
obj2 = script_dir / "revlink" / "rev-linkme" / "flagchecker.o"
if obj2.exists():
return obj2, script_dir / "link_trace_plot.py"
return obj1, script_dir / "link_trace_plot.py"

def ensure_file(path: Path, desc: str) -> None:
if not path.exists():
raise FileNotFoundError(f"missing {desc}: {path}")

def iter_single_cases(baseline: str, positions: Sequence[int], test_values: str) -> Iterable[dict]:
for pos in positions:
for ch in test_values:
yield {
"mode": "single",
"position": pos,
"char": ch,
"delta": "",
"flag": mutate_single(baseline, pos, ch),
}

def iter_balanced_cases(baseline: str, positions: Sequence[int], deltas: Sequence[int]) -> Iterable[dict]:
for pos in positions:
for delta in deltas:
yield {
"mode": "balanced",
"position": pos,
"char": "",
"delta": delta,
"flag": mutate_balanced(baseline, pos, delta),
}

def main() -> int:
script_dir = Path(__file__).resolve().parent
default_obj, default_tracer = resolve_default_paths(script_dir)

ap = argparse.ArgumentParser(description="Controlled trace generator for Linkme")
ap.add_argument("--baseline", default=DEFAULT_BASELINE)
ap.add_argument(
"--positions",
help="comma-separated absolute positions in the 26-byte input; default is last unknown position",
)
ap.add_argument(
"--unknown-only",
action="store_true",
help="treat --positions as 0-based indices inside the unknown 18-byte body (adds +7)",
)
ap.add_argument(
"--mode",
choices=["single", "balanced"],
default="single",
help="single: mutate one byte; balanced: x_i += d and x_{i+1} -= d",
)
ap.add_argument("--test-values", default=DEFAULT_TEST_VALUES, help="characters for single-byte scan")
ap.add_argument(
"--deltas",
default="1,-1,2,-2,4,-4",
help="comma-separated integer deltas for balanced mode",
)
ap.add_argument("--limit", type=int, default=0, help="limit number of generated mutant cases")
ap.add_argument("--outdir", default="trace_runs")
ap.add_argument("--obj", type=Path, default=default_obj)
ap.add_argument("--tracer", type=Path, default=default_tracer)
ap.add_argument("--gcc", default=shutil.which("mips-linux-gnu-gcc") or "mips-linux-gnu-gcc")
ap.add_argument(
"--symbol-source",
choices=["auto", "elf", "chars"],
default="auto",
help="auto: use dummy.out when gcc exists, otherwise fall back to --chars; elf: always build dummy.out; chars: pass --chars directly to tracer",
)
ap.add_argument("--python", dest="python_exe", default=sys.executable)
ap.add_argument("--timeout", type=int, default=300)
ap.add_argument("--keep-workdirs", action="store_true")
ap.add_argument("--quiet", action="store_true")
args = ap.parse_args()

if len(args.baseline) != 26:
raise ValueError(f"baseline must be length 26, got {len(args.baseline)}")

ensure_file(args.obj, "flagchecker object")
ensure_file(args.tracer, "trace script")
gcc_exists = shutil.which(args.gcc) is not None or Path(args.gcc).exists()
if args.symbol_source == "elf" and not gcc_exists:
raise FileNotFoundError(f"mips gcc not found: {args.gcc}")

unknown_positions = infer_unknown_positions(args.baseline)
if args.positions:
positions = parse_int_list(args.positions)
if args.unknown_only:
positions = [p + 7 for p in positions]
else:
positions = [unknown_positions[-1]]

if args.mode == "single":
cases = list(iter_single_cases(args.baseline, positions, args.test_values))
else:
deltas = parse_int_list(args.deltas)
cases = list(iter_balanced_cases(args.baseline, positions, deltas))

if args.limit > 0:
cases = cases[: args.limit]

effective_symbol_source = args.symbol_source
if effective_symbol_source == "auto":
effective_symbol_source = "elf" if gcc_exists else "chars"

outdir = Path(args.outdir)
outdir.mkdir(parents=True, exist_ok=True)
manifest_rows = []

baseline_csv = outdir / "baseline.csv"
baseline_workdir = outdir / "baseline_work"
if effective_symbol_source == "elf":
baseline_workdir.mkdir(parents=True, exist_ok=True)
baseline_dummy = build_dummy_for_flag(args.baseline, baseline_workdir, args.gcc)
run_trace(
args.python_exe,
args.tracer,
args.obj,
baseline_csv,
args.quiet,
sym_elf=baseline_dummy,
timeout=args.timeout,
)
else:
run_trace(
args.python_exe,
args.tracer,
args.obj,
baseline_csv,
args.quiet,
chars=args.baseline,
timeout=args.timeout,
)
manifest_rows.append(
{
"case_id": "baseline",
"mode": "baseline",
"position": "",
"position_hex": "",
"char": "",
"char_ord": "",
"delta": "",
"flag": args.baseline,
"csv_path": str(baseline_csv),
}
)
print(f"[baseline] wrote {baseline_csv}")

for case_index, case in enumerate(cases, start=1):
pos = int(case["position"])
if case["mode"] == "single":
ch = case["char"]
case_id = f"single_pos{pos:02d}_{ord(ch):02x}"
else:
delta = int(case["delta"])
case_id = f"balanced_pos{pos:02d}_{delta:+d}".replace("+", "p").replace("-", "m")

workdir = outdir / f"{case_id}_work"
out_csv = outdir / f"{case_id}.csv"
if effective_symbol_source == "elf":
workdir.mkdir(parents=True, exist_ok=True)
dummy_out = build_dummy_for_flag(case["flag"], workdir, args.gcc)
run_trace(
args.python_exe,
args.tracer,
args.obj,
out_csv,
args.quiet,
sym_elf=dummy_out,
timeout=args.timeout,
)
else:
run_trace(
args.python_exe,
args.tracer,
args.obj,
out_csv,
args.quiet,
chars=case["flag"],
timeout=args.timeout,
)

manifest_rows.append(
{
"case_id": case_id,
"mode": case["mode"],
"position": pos,
"position_hex": hex(pos),
"char": case.get("char", ""),
"char_ord": ord(case["char"]) if case.get("char") else "",
"delta": case.get("delta", ""),
"flag": case["flag"],
"csv_path": str(out_csv),
}
)
print(f"[{case_index}/{len(cases)}] wrote {out_csv}")

if not args.keep_workdirs:
shutil.rmtree(workdir, ignore_errors=True)

if not args.keep_workdirs:
shutil.rmtree(baseline_workdir, ignore_errors=True)

manifest_path = outdir / "manifest.csv"
write_manifest(manifest_path, manifest_rows)
print(f"[done] wrote baseline + {len(cases)} mutant traces under {outdir}")
print(f"[done] manifest: {manifest_path}")
return 0

if __name__ == "__main__":
raise SystemExit(main())

Compare csv template (csv_diff_semantic.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#!/usr/bin/env python3
from __future__ import annotations

import argparse
import csv
from collections import Counter
from pathlib import Path
from typing import Dict, Iterable, List, Tuple

GATE_OFFSETS = {
0x53DF: "-fno gate",
0x53E2: "ASCII gate",
0x53E6: "format gate",
0x53EA: "content gate",
}
KEY_OFFSETS = {
0x53DC: "state53dc",
0x04AB: "mid@0x04ab",
0x0488: "mid@0x0488",
0x0464: "mid@0x0464",
0x0EEE: "mid@0x0eee",
0x04A8: "mid@0x04a8",
0x0484: "mid@0x0484",
}
FOCUS_OFFSETS = {**GATE_OFFSETS, **KEY_OFFSETS}

class TraceTable:
def __init__(self, rows_by_idx: Dict[int, dict], path: Path):
self.rows_by_idx = rows_by_idx
self.path = path

@classmethod
def load(cls, path: Path) -> "TraceTable":
rows = {}
with path.open("r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
idx = int(row["idx"])
row["idx"] = idx
row["off"] = int(row["off"])
row["typ"] = int(row["typ"])
row["S"] = int(row["S"])
row["addend"] = int(row["addend"])
row["value"] = int(row["value"])
row["save_for_next"] = int(row["save_for_next"])
row["carry_out"] = None if row["carry_out"] == "" else int(row["carry_out"])
rows[idx] = row
return cls(rows, path)

def compress_ranges(values: Iterable[int]) -> List[Tuple[int, int]]:
values = sorted(set(values))
if not values:
return []
ranges = []
start = prev = values[0]
for v in values[1:]:
if v == prev + 1:
prev = v
continue
ranges.append((start, prev))
start = prev = v
ranges.append((start, prev))
return ranges

def field_changed(a: dict, b: dict, fields: Iterable[str]) -> bool:
return any(a.get(k) != b.get(k) for k in fields)

def compare_tables(base: TraceTable, other: TraceTable, compare_fields: List[str]) -> dict:
shared = sorted(set(base.rows_by_idx) & set(other.rows_by_idx))
changed_idxs = []
offset_counter = Counter()
by_offset_examples = {}
gate_diffs = []
focus_diffs = []

for idx in shared:
a = base.rows_by_idx[idx]
b = other.rows_by_idx[idx]
if field_changed(a, b, compare_fields):
changed_idxs.append(idx)
off = b["off"]
offset_counter[off] += 1
by_offset_examples.setdefault(off, (a, b))
if off in GATE_OFFSETS:
gate_diffs.append((off, a, b))
if off in FOCUS_OFFSETS:
focus_diffs.append((off, a, b))

return {
"base_only": sorted(set(base.rows_by_idx) - set(other.rows_by_idx)),
"other_only": sorted(set(other.rows_by_idx) - set(base.rows_by_idx)),
"changed_idxs": changed_idxs,
"changed_ranges": compress_ranges(changed_idxs),
"offset_counter": offset_counter,
"by_offset_examples": by_offset_examples,
"gate_diffs": gate_diffs,
"focus_diffs": focus_diffs,
}

def fmt_range(a: int, b: int) -> str:
return f"{a}" if a == b else f"{a}-{b}"

def print_pairwise_report(base: TraceTable, other: TraceTable, result: dict, topk: int) -> None:
print(f"[compare] baseline={base.path}")
print(f"[compare] other ={other.path}")
print(
f"[compare] changed_idxs={len(result['changed_idxs'])} "
f"base_only={len(result['base_only'])} other_only={len(result['other_only'])}"
)

print("\\n[gate diff]")
if not result["gate_diffs"]:
print(" <none>")
else:
seen = set()
for off, a, b in result["gate_diffs"]:
if off in seen:
continue
seen.add(off)
print(
f" {GATE_OFFSETS[off]:<12} off=0x{off:04x} idx=#{b['idx']} "
f"addend {a['addend_hex']} -> {b['addend_hex']} | "
f"value {a['value_hex']} -> {b['value_hex']} | "
f"status {a['status']} -> {b['status']}"
)

print("\\n[focus offset diff]")
if not result["focus_diffs"]:
print(" <none>")
else:
seen = set()
for off, a, b in result["focus_diffs"]:
if (off, b["idx"]) in seen:
continue
seen.add((off, b["idx"]))
label = FOCUS_OFFSETS[off]
print(
f" {label:<12} off=0x{off:04x} idx=#{b['idx']} typ={b['typ_name']} "
f"addend {a['addend_hex']} -> {b['addend_hex']} | "
f"value {a['value_hex']} -> {b['value_hex']} | "
f"status {a['status']} -> {b['status']}"
)

print("\\n[top changed offsets]")
if not result["offset_counter"]:
print(" <none>")
else:
for off, count in result["offset_counter"].most_common(topk):
a, b = result["by_offset_examples"][off]
label = FOCUS_OFFSETS.get(off, "")
suffix = f" ({label})" if label else ""
print(
f" off=0x{off:04x} count={count}{suffix} "
f"example idx=#{b['idx']} addend {a['addend_hex']} -> {b['addend_hex']} "
f"value {a['value_hex']} -> {b['value_hex']}"
)

print("\\n[changed idx ranges]")
if not result["changed_ranges"]:
print(" <none>")
else:
print(" " + ", ".join(fmt_range(a, b) for a, b in result["changed_ranges"][:40]))
if len(result["changed_ranges"]) > 40:
print(f" ... {len(result['changed_ranges']) - 40} more ranges")

def compare_many(base_path: Path, other_paths: List[Path], compare_fields: List[str]) -> List[dict]:
base = TraceTable.load(base_path)
rows = []
for other_path in other_paths:
other = TraceTable.load(other_path)
result = compare_tables(base, other, compare_fields)
top_offsets = [f"0x{off:04x}:{cnt}" for off, cnt in result["offset_counter"].most_common(8)]
gate_summary = []
seen = set()
for off, a, b in result["gate_diffs"]:
if off in seen:
continue
seen.add(off)
gate_summary.append(
f"{GATE_OFFSETS[off]}:{a['addend_hex']}->{b['addend_hex']}:{a['status']}->{b['status']}"
)
rows.append(
{
"other_csv": str(other_path),
"changed_idx_count": len(result["changed_idxs"]),
"changed_range_count": len(result["changed_ranges"]),
"top_offsets": " | ".join(top_offsets),
"gate_summary": " | ".join(gate_summary),
}
)
return rows

def write_summary_csv(path: Path, rows: List[dict]) -> None:
if not rows:
return
fieldnames = list(rows[0].keys())
with path.open("w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)

def main() -> int:
ap = argparse.ArgumentParser(description="Semantic diff for Linkme trace CSV files")
ap.add_argument("baseline", type=Path, help="baseline trace csv")
ap.add_argument("others", nargs="*", type=Path, help="other trace csv files")
ap.add_argument(
"--fields",
default="off,typ,sym,S,addend,value,status,save_for_next,carry_out",
help="comma-separated fields to compare",
)
ap.add_argument("--topk", type=int, default=20)
ap.add_argument(
"--glob",
help="glob pattern for batch compare, e.g. 'trace_runs/single_pos24_*.csv'",
)
ap.add_argument("--summary-csv", type=Path, help="write one-line-per-file batch summary")
args = ap.parse_args()

compare_fields = [x.strip() for x in args.fields.split(",") if x.strip()]
baseline_path = args.baseline
if not baseline_path.exists():
raise FileNotFoundError(baseline_path)

others = list(args.others)
if args.glob:
others.extend(sorted(Path().glob(args.glob)))
others = [p for p in others if p.resolve() != baseline_path.resolve()]
if not others:
raise ValueError("no comparison targets provided")

if len(others) == 1:
base = TraceTable.load(baseline_path)
other = TraceTable.load(others[0])
result = compare_tables(base, other, compare_fields)
print_pairwise_report(base, other, result, args.topk)
return 0

rows = compare_many(baseline_path, others, compare_fields)
rows.sort(key=lambda r: (-int(r["changed_idx_count"]), r["other_csv"]))
print("[batch summary]")
for row in rows:
print(
f" changed={row['changed_idx_count']:6d} ranges={row['changed_range_count']:4d} "
f"file={row['other_csv']}"
)
if row["gate_summary"]:
print(f" gates: {row['gate_summary']}")
if row["top_offsets"]:
print(f" top: {row['top_offsets']}")
if args.summary_csv:
write_summary_csv(args.summary_csv, rows)
print(f"[wrote summary csv] {args.summary_csv}")
return 0

if __name__ == "__main__":
raise SystemExit(main())

Example run command:

1
2
3
4
5
6
7
8
python total_controlled.py --symbol-source elf --unknown-only --positions 17 --test-values 0AaMmZz9 --outdir run_tail
python csv_diff_semantic.py run_tail/baseline.csv --glob 'run_tail/single_pos*.csv' --summary-csv run_tail/summary_tail.csv
python total_controlled.py --symbol-source elf --unknown-only --positions 0 --test-values 0AaMmZz9 --outdir run_head
python csv_diff_semantic.py run_head/baseline.csv --glob 'run_head/single_pos*.csv' --summary-csv run_head/summary_head.csv
python total_controlled.py --symbol-source elf --mode balanced --unknown-only --positions 16 --deltas 1,-1,2,-2 --outdir run_balanced23
python csv_diff_semantic.py run_balanced23/baseline.csv --glob 'run_balanced23/balanced_pos23_*.csv' --summary-csv run_balanced23/summary_balanced23.csv
python total_controlled.py --symbol-source elf --mode balanced --unknown-only --positions 15 --deltas 1,-1,2,-2 --outdir run_balanced22
python csv_diff_semantic.py run_balanced22/baseline.csv --glob 'run_balanced22/balanced_pos22_*.csv' --summary-csv run_balanced22/summary_balanced22.csv

III. Initial Findings

From the plot and the .csv output of the whole process, we can find some important findings at first:

1. R32 only appears in the begining

There’s 780 R32 in total, as 26*30, index from 0 to 9959, it load all a-z into address as:

$$ \text{addr}n = \begin{cases} 0x491A80 + 4 \cdot \sum{i=0}^{n-1} x_i, & n \geq 1 \ 0x491A80, & n = 0 \end{cases} $$

Next, all address values were multiplied by 0x10. In the end, so the final addresses are:

$$ \text{addr}n = \begin{cases} 0x10 \cdot (0x491A80 + 4 \cdot \sum{i=0}^{n-1} x_i), & n \geq 1 \ 0x491A800, & n = 0 \end{cases} $$

Each symbol use 390 entries to finish, in total of 10140 entries.

2. R16 only appears for 4 times as 4 cases of errors

The R_MIPS_16 only appears in these 4 times and all error cases:

1
2
3
4
151097,21471,0x53df,1,R_MIPS_16,\\x1b[2K\\rld: 'please compile with -fno-toplevel-reorder'\\x08,32512,0x7f00,0,0x0,contents&mask@0x53df=0x0; <<0; no-local-adjust,32512,0x7f00,ok,0,,
151098,21474,0x53e2,1,R_MIPS_16,\\x1b[2K\\rld: 'please use only ASCII'\\x08,32512,0x7f00,1,0x1,contents&mask@0x53e2=0x1; <<0; no-local-adjust,32513,0x7f01,ok,0,,
151099,21478,0x53e6,1,R_MIPS_16,\\x1b[2K\\rld: 'flag must be wrapped in kalmar{}'\\x08,32512,0x7f00,1,0x1,contents&mask@0x53e6=0x1; <<0; no-local-adjust,32513,0x7f01,ok,0,,
151100,21482,0x53ea,1,R_MIPS_16,\\x1b[2K\\rld: 'sorry, that’s the wrong flag :('\\x08,32512,0x7f00,256,0x100,contents&mask@0x53ea=0x100; <<0; no-local-adjust,32768,0x8000,overflow,0,,

3. Some special HI16 in part 3 (about index 27000 ~ 28000)

Note that there is a sharp increase in the address of LO16 around index 27000~28000. In fact, in this area has 94 special HI16, index from 27329 to 27635:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
27329,1196,0x4ac,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843009,0x1010101,contents&mask@0x4ac=0x101; match-lo16@0x4ac raw=0x101 sext=257 src=contents&mask@0x4ac=0x101; no-local-adjust,258,0x102,ok,1,258.0,0x102
27333,1196,0x4ac,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,2155839616,0x807f8080,contents&mask@0x4ac=0x8080; match-lo16@0x4ac raw=0x8080 sext=-32640 src=contents&mask@0x4ac=0x8080; no-local-adjust,32769,0x8001,ok,1,32769.0,0x8001
27335,1198,0x4ae,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,65792,0x10100,contents&mask@0x4ae=0x1; match-lo16@0xf1d raw=0x100 sext=256 src=contents&mask@0xf1d=0x100; no-local-adjust,2,0x2,ok,0,,
27340,1198,0x4ae,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,8355841,0x7f8001,contents&mask@0x4ae=0x80; match-lo16@0xf1d raw=0x8001 sext=-32767 src=contents&mask@0xf1d=0x8001; no-local-adjust,1,0x1,ok,0,,
27342,1199,0x4af,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16777217,0x1000001,contents&mask@0x4af=0x100; match-lo16@0x4ae raw=0x1 sext=1 src=contents&mask@0x4ae=0x1; no-local-adjust,256,0x100,ok,0,,
27346,1199,0x4af,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,25132801,0x17f7f01,contents&mask@0x4af=0x17f; match-lo16@0x4ae raw=0x7f01 sext=32513 src=contents&mask@0x4ae=0x7f01; no-local-adjust,256,0x100,ok,0,,
27348,1200,0x4b0,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,65792,0x10100,contents&mask@0x4b0=0x1; match-lo16@0xf1e raw=0x100 sext=256 src=contents&mask@0xf1e=0x100; no-local-adjust,2,0x2,ok,0,,
27352,1200,0x4b0,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,8355840,0x7f8000,contents&mask@0x4b0=0x80; match-lo16@0xf1e raw=0x8000 sext=-32768 src=contents&mask@0xf1e=0x8000; no-local-adjust,1,0x1,ok,0,,
27354,1201,0x4b1,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x4b1=0x101; match-lo16@0xf20 raw=0x100 sext=256 src=contents&mask@0xf20=0x100; no-local-adjust,258,0x102,ok,0,,
27359,1201,0x4b1,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x4b1=0x180; match-lo16@0xf20 raw=0x8001 sext=-32767 src=contents&mask@0xf20=0x8001; no-local-adjust,257,0x101,ok,0,,
27361,1300,0x514,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x514=0x101; match-lo16@0xf21 raw=0x100 sext=256 src=contents&mask@0xf21=0x100; no-local-adjust,258,0x102,ok,0,,
27366,1300,0x514,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x514=0x180; match-lo16@0xf21 raw=0x8001 sext=-32767 src=contents&mask@0xf21=0x8001; no-local-adjust,257,0x101,ok,0,,
27368,1301,0x515,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16777217,0x1000001,contents&mask@0x515=0x100; match-lo16@0xf1f raw=0x1 sext=1 src=contents&mask@0xf1f=0x1; no-local-adjust,256,0x100,ok,0,,
27372,1301,0x515,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,25132801,0x17f7f01,contents&mask@0x515=0x17f; match-lo16@0xf1f raw=0x7f01 sext=32513 src=contents&mask@0xf1f=0x7f01; no-local-adjust,256,0x100,ok,0,,
27374,1302,0x516,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,0,0x0,contents&mask@0x516=0x0; match-lo16@0xf23 raw=0x0 sext=0 src=contents&mask@0xf23=0x0; no-local-adjust,0,0x0,ok,0,,
27379,1302,0x516,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,8355584,0x7f7f00,contents&mask@0x516=0x7f; match-lo16@0xf23 raw=0x7f00 sext=32512 src=contents&mask@0xf23=0x7f00; no-local-adjust,0,0x0,ok,0,,
27381,1303,0x517,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,0,0x0,contents&mask@0x517=0x0; match-lo16@0xf24 raw=0x0 sext=0 src=contents&mask@0xf24=0x0; no-local-adjust,0,0x0,ok,0,,
27386,1303,0x517,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,8355584,0x7f7f00,contents&mask@0x517=0x7f; match-lo16@0xf24 raw=0x7f00 sext=32512 src=contents&mask@0xf24=0x7f00; no-local-adjust,0,0x0,ok,0,,
27388,1304,0x518,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,0,0x0,contents&mask@0x518=0x0; match-lo16@0xf25 raw=0x0 sext=0 src=contents&mask@0xf25=0x0; no-local-adjust,0,0x0,ok,0,,
27393,1304,0x518,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,8355584,0x7f7f00,contents&mask@0x518=0x7f; match-lo16@0xf25 raw=0x7f00 sext=32512 src=contents&mask@0xf25=0x7f00; no-local-adjust,0,0x0,ok,0,,
27395,1305,0x519,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,65792,0x10100,contents&mask@0x519=0x1; match-lo16@0xf22 raw=0x100 sext=256 src=contents&mask@0xf22=0x100; no-local-adjust,2,0x2,ok,0,,
27399,1305,0x519,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,8355840,0x7f8000,contents&mask@0x519=0x80; match-lo16@0xf22 raw=0x8000 sext=-32768 src=contents&mask@0xf22=0x8000; no-local-adjust,1,0x1,ok,0,,
27401,1306,0x51a,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x51a=0x101; match-lo16@0xf27 raw=0x100 sext=256 src=contents&mask@0xf27=0x100; no-local-adjust,258,0x102,ok,0,,
27406,1306,0x51a,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x51a=0x180; match-lo16@0xf27 raw=0x8001 sext=-32767 src=contents&mask@0xf27=0x8001; no-local-adjust,257,0x101,ok,0,,
27408,1407,0x57f,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16777217,0x1000001,contents&mask@0x57f=0x100; match-lo16@0xf26 raw=0x1 sext=1 src=contents&mask@0xf26=0x1; no-local-adjust,256,0x100,ok,0,,
27412,1407,0x57f,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,25132801,0x17f7f01,contents&mask@0x57f=0x17f; match-lo16@0xf26 raw=0x7f01 sext=32513 src=contents&mask@0xf26=0x7f01; no-local-adjust,256,0x100,ok,0,,
27414,1408,0x580,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,0,0x0,contents&mask@0x580=0x0; match-lo16@0xf29 raw=0x0 sext=0 src=contents&mask@0xf29=0x0; no-local-adjust,0,0x0,ok,0,,
27419,1408,0x580,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,8355584,0x7f7f00,contents&mask@0x580=0x7f; match-lo16@0xf29 raw=0x7f00 sext=32512 src=contents&mask@0xf29=0x7f00; no-local-adjust,0,0x0,ok,0,,
27421,1409,0x581,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,65792,0x10100,contents&mask@0x581=0x1; match-lo16@0xf28 raw=0x100 sext=256 src=contents&mask@0xf28=0x100; no-local-adjust,2,0x2,ok,0,,
27425,1409,0x581,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,8355840,0x7f8000,contents&mask@0x581=0x80; match-lo16@0xf28 raw=0x8000 sext=-32768 src=contents&mask@0xf28=0x8000; no-local-adjust,1,0x1,ok,0,,
27427,1410,0x582,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x582=0x101; match-lo16@0xf2b raw=0x100 sext=256 src=contents&mask@0xf2b=0x100; no-local-adjust,258,0x102,ok,0,,
27432,1410,0x582,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x582=0x180; match-lo16@0xf2b raw=0x8001 sext=-32767 src=contents&mask@0xf2b=0x8001; no-local-adjust,257,0x101,ok,0,,
27434,1411,0x583,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16777217,0x1000001,contents&mask@0x583=0x100; match-lo16@0xf2a raw=0x1 sext=1 src=contents&mask@0xf2a=0x1; no-local-adjust,256,0x100,ok,0,,
27438,1411,0x583,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,25132801,0x17f7f01,contents&mask@0x583=0x17f; match-lo16@0xf2a raw=0x7f01 sext=32513 src=contents&mask@0xf2a=0x7f01; no-local-adjust,256,0x100,ok,0,,
27440,1412,0x584,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,65792,0x10100,contents&mask@0x584=0x1; match-lo16@0xf2c raw=0x100 sext=256 src=contents&mask@0xf2c=0x100; no-local-adjust,2,0x2,ok,0,,
27444,1412,0x584,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,8355840,0x7f8000,contents&mask@0x584=0x80; match-lo16@0xf2c raw=0x8000 sext=-32768 src=contents&mask@0xf2c=0x8000; no-local-adjust,1,0x1,ok,0,,
27446,1413,0x585,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x585=0x101; match-lo16@0xf2e raw=0x100 sext=256 src=contents&mask@0xf2e=0x100; no-local-adjust,258,0x102,ok,0,,
27451,1413,0x585,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x585=0x180; match-lo16@0xf2e raw=0x8001 sext=-32767 src=contents&mask@0xf2e=0x8001; no-local-adjust,257,0x101,ok,0,,
27453,1514,0x5ea,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x5ea=0x101; match-lo16@0xf2f raw=0x100 sext=256 src=contents&mask@0xf2f=0x100; no-local-adjust,258,0x102,ok,0,,
27458,1514,0x5ea,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x5ea=0x180; match-lo16@0xf2f raw=0x8001 sext=-32767 src=contents&mask@0xf2f=0x8001; no-local-adjust,257,0x101,ok,0,,
27460,1515,0x5eb,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16777217,0x1000001,contents&mask@0x5eb=0x100; match-lo16@0xf2d raw=0x1 sext=1 src=contents&mask@0xf2d=0x1; no-local-adjust,256,0x100,ok,0,,
27464,1515,0x5eb,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,25132801,0x17f7f01,contents&mask@0x5eb=0x17f; match-lo16@0xf2d raw=0x7f01 sext=32513 src=contents&mask@0xf2d=0x7f01; no-local-adjust,256,0x100,ok,0,,
27466,1516,0x5ec,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,65792,0x10100,contents&mask@0x5ec=0x1; match-lo16@0xf30 raw=0x100 sext=256 src=contents&mask@0xf30=0x100; no-local-adjust,2,0x2,ok,0,,
27470,1516,0x5ec,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,8355840,0x7f8000,contents&mask@0x5ec=0x80; match-lo16@0xf30 raw=0x8000 sext=-32768 src=contents&mask@0xf30=0x8000; no-local-adjust,1,0x1,ok,0,,
27472,1517,0x5ed,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x5ed=0x101; match-lo16@0xf32 raw=0x100 sext=256 src=contents&mask@0xf32=0x100; no-local-adjust,258,0x102,ok,0,,
27477,1517,0x5ed,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x5ed=0x180; match-lo16@0xf32 raw=0x8001 sext=-32767 src=contents&mask@0xf32=0x8001; no-local-adjust,257,0x101,ok,0,,
27479,1518,0x5ee,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16777217,0x1000001,contents&mask@0x5ee=0x100; match-lo16@0xf31 raw=0x1 sext=1 src=contents&mask@0xf31=0x1; no-local-adjust,256,0x100,ok,0,,
27483,1518,0x5ee,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,25132801,0x17f7f01,contents&mask@0x5ee=0x17f; match-lo16@0xf31 raw=0x7f01 sext=32513 src=contents&mask@0xf31=0x7f01; no-local-adjust,256,0x100,ok,0,,
27485,1519,0x5ef,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,65792,0x10100,contents&mask@0x5ef=0x1; match-lo16@0xf33 raw=0x100 sext=256 src=contents&mask@0xf33=0x100; no-local-adjust,2,0x2,ok,0,,
27489,1519,0x5ef,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,8355840,0x7f8000,contents&mask@0x5ef=0x80; match-lo16@0xf33 raw=0x8000 sext=-32768 src=contents&mask@0xf33=0x8000; no-local-adjust,1,0x1,ok,0,,
27491,1520,0x5f0,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x5f0=0x101; match-lo16@0xf35 raw=0x100 sext=256 src=contents&mask@0xf35=0x100; no-local-adjust,258,0x102,ok,0,,
27496,1520,0x5f0,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x5f0=0x180; match-lo16@0xf35 raw=0x8001 sext=-32767 src=contents&mask@0xf35=0x8001; no-local-adjust,257,0x101,ok,0,,
27498,1621,0x655,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x655=0x101; match-lo16@0xf36 raw=0x100 sext=256 src=contents&mask@0xf36=0x100; no-local-adjust,258,0x102,ok,0,,
27503,1621,0x655,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x655=0x180; match-lo16@0xf36 raw=0x8001 sext=-32767 src=contents&mask@0xf36=0x8001; no-local-adjust,257,0x101,ok,0,,
27505,1622,0x656,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16777217,0x1000001,contents&mask@0x656=0x100; match-lo16@0xf34 raw=0x1 sext=1 src=contents&mask@0xf34=0x1; no-local-adjust,256,0x100,ok,0,,
27509,1622,0x656,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,25132801,0x17f7f01,contents&mask@0x656=0x17f; match-lo16@0xf34 raw=0x7f01 sext=32513 src=contents&mask@0xf34=0x7f01; no-local-adjust,256,0x100,ok,0,,
27511,1623,0x657,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,0,0x0,contents&mask@0x657=0x0; match-lo16@0xf38 raw=0x0 sext=0 src=contents&mask@0xf38=0x0; no-local-adjust,0,0x0,ok,0,,
27516,1623,0x657,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,8355584,0x7f7f00,contents&mask@0x657=0x7f; match-lo16@0xf38 raw=0x7f00 sext=32512 src=contents&mask@0xf38=0x7f00; no-local-adjust,0,0x0,ok,0,,
27518,1624,0x658,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,0,0x0,contents&mask@0x658=0x0; match-lo16@0xf39 raw=0x0 sext=0 src=contents&mask@0xf39=0x0; no-local-adjust,0,0x0,ok,0,,
27523,1624,0x658,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,8355584,0x7f7f00,contents&mask@0x658=0x7f; match-lo16@0xf39 raw=0x7f00 sext=32512 src=contents&mask@0xf39=0x7f00; no-local-adjust,0,0x0,ok,0,,
27525,1625,0x659,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,0,0x0,contents&mask@0x659=0x0; match-lo16@0xf3a raw=0x0 sext=0 src=contents&mask@0xf3a=0x0; no-local-adjust,0,0x0,ok,0,,
27530,1625,0x659,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,8355584,0x7f7f00,contents&mask@0x659=0x7f; match-lo16@0xf3a raw=0x7f00 sext=32512 src=contents&mask@0xf3a=0x7f00; no-local-adjust,0,0x0,ok,0,,
27532,1626,0x65a,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,65792,0x10100,contents&mask@0x65a=0x1; match-lo16@0xf37 raw=0x100 sext=256 src=contents&mask@0xf37=0x100; no-local-adjust,2,0x2,ok,0,,
27536,1626,0x65a,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,8355840,0x7f8000,contents&mask@0x65a=0x80; match-lo16@0xf37 raw=0x8000 sext=-32768 src=contents&mask@0xf37=0x8000; no-local-adjust,1,0x1,ok,0,,
27538,1627,0x65b,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x65b=0x101; match-lo16@0xf3c raw=0x100 sext=256 src=contents&mask@0xf3c=0x100; no-local-adjust,258,0x102,ok,0,,
27543,1627,0x65b,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x65b=0x180; match-lo16@0xf3c raw=0x8001 sext=-32767 src=contents&mask@0xf3c=0x8001; no-local-adjust,257,0x101,ok,0,,
27545,1728,0x6c0,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16777217,0x1000001,contents&mask@0x6c0=0x100; match-lo16@0xf3b raw=0x1 sext=1 src=contents&mask@0xf3b=0x1; no-local-adjust,256,0x100,ok,0,,
27549,1728,0x6c0,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,25132801,0x17f7f01,contents&mask@0x6c0=0x17f; match-lo16@0xf3b raw=0x7f01 sext=32513 src=contents&mask@0xf3b=0x7f01; no-local-adjust,256,0x100,ok,0,,
27551,1729,0x6c1,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,65792,0x10100,contents&mask@0x6c1=0x1; match-lo16@0xf3d raw=0x100 sext=256 src=contents&mask@0xf3d=0x100; no-local-adjust,2,0x2,ok,0,,
27555,1729,0x6c1,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,8355840,0x7f8000,contents&mask@0x6c1=0x80; match-lo16@0xf3d raw=0x8000 sext=-32768 src=contents&mask@0xf3d=0x8000; no-local-adjust,1,0x1,ok,0,,
27557,1730,0x6c2,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16777217,0x1000001,contents&mask@0x6c2=0x100; match-lo16@0xf3e raw=0x1 sext=1 src=contents&mask@0xf3e=0x1; no-local-adjust,256,0x100,ok,0,,
27561,1730,0x6c2,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,25132801,0x17f7f01,contents&mask@0x6c2=0x17f; match-lo16@0xf3e raw=0x7f01 sext=32513 src=contents&mask@0xf3e=0x7f01; no-local-adjust,256,0x100,ok,0,,
27563,1731,0x6c3,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,0,0x0,contents&mask@0x6c3=0x0; match-lo16@0xf40 raw=0x0 sext=0 src=contents&mask@0xf40=0x0; no-local-adjust,0,0x0,ok,0,,
27568,1731,0x6c3,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,8355584,0x7f7f00,contents&mask@0x6c3=0x7f; match-lo16@0xf40 raw=0x7f00 sext=32512 src=contents&mask@0xf40=0x7f00; no-local-adjust,0,0x0,ok,0,,
27570,1732,0x6c4,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,65792,0x10100,contents&mask@0x6c4=0x1; match-lo16@0xf3f raw=0x100 sext=256 src=contents&mask@0xf3f=0x100; no-local-adjust,2,0x2,ok,0,,
27574,1732,0x6c4,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,8355840,0x7f8000,contents&mask@0x6c4=0x80; match-lo16@0xf3f raw=0x8000 sext=-32768 src=contents&mask@0xf3f=0x8000; no-local-adjust,1,0x1,ok,0,,
27576,1733,0x6c5,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x6c5=0x101; match-lo16@0xf42 raw=0x100 sext=256 src=contents&mask@0xf42=0x100; no-local-adjust,258,0x102,ok,0,,
27581,1733,0x6c5,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x6c5=0x180; match-lo16@0xf42 raw=0x8001 sext=-32767 src=contents&mask@0xf42=0x8001; no-local-adjust,257,0x101,ok,0,,
27583,1734,0x6c6,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x6c6=0x101; match-lo16@0xf43 raw=0x100 sext=256 src=contents&mask@0xf43=0x100; no-local-adjust,258,0x102,ok,0,,
27588,1734,0x6c6,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x6c6=0x180; match-lo16@0xf43 raw=0x8001 sext=-32767 src=contents&mask@0xf43=0x8001; no-local-adjust,257,0x101,ok,0,,
27590,1835,0x72b,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x72b=0x101; match-lo16@0xf44 raw=0x100 sext=256 src=contents&mask@0xf44=0x100; no-local-adjust,258,0x102,ok,0,,
27595,1835,0x72b,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x72b=0x180; match-lo16@0xf44 raw=0x8001 sext=-32767 src=contents&mask@0xf44=0x8001; no-local-adjust,257,0x101,ok,0,,
27597,1836,0x72c,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x72c=0x101; match-lo16@0xf45 raw=0x100 sext=256 src=contents&mask@0xf45=0x100; no-local-adjust,258,0x102,ok,0,,
27602,1836,0x72c,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x72c=0x180; match-lo16@0xf45 raw=0x8001 sext=-32767 src=contents&mask@0xf45=0x8001; no-local-adjust,257,0x101,ok,0,,
27604,1837,0x72d,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16777217,0x1000001,contents&mask@0x72d=0x100; match-lo16@0xf41 raw=0x1 sext=1 src=contents&mask@0xf41=0x1; no-local-adjust,256,0x100,ok,0,,
27608,1837,0x72d,5,R_MIPS_HI16,_0xff808100,4286611712,0xff808100,25132801,0x17f7f01,contents&mask@0x72d=0x17f; match-lo16@0xf41 raw=0x7f01 sext=32513 src=contents&mask@0xf41=0x7f01; no-local-adjust,256,0x100,ok,0,,
27610,1838,0x72e,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,65792,0x10100,contents&mask@0x72e=0x1; match-lo16@0xf46 raw=0x100 sext=256 src=contents&mask@0xf46=0x100; no-local-adjust,2,0x2,ok,0,,
27614,1838,0x72e,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,8355840,0x7f8000,contents&mask@0x72e=0x80; match-lo16@0xf46 raw=0x8000 sext=-32768 src=contents&mask@0xf46=0x8000; no-local-adjust,1,0x1,ok,0,,
27616,1839,0x72f,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x72f=0x101; match-lo16@0xf48 raw=0x100 sext=256 src=contents&mask@0xf48=0x100; no-local-adjust,258,0x102,ok,0,,
27621,1839,0x72f,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x72f=0x180; match-lo16@0xf48 raw=0x8001 sext=-32767 src=contents&mask@0xf48=0x8001; no-local-adjust,257,0x101,ok,0,,
27623,1840,0x730,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x730=0x101; match-lo16@0xf49 raw=0x100 sext=256 src=contents&mask@0xf49=0x100; no-local-adjust,258,0x102,ok,0,,
27628,1840,0x730,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x730=0x180; match-lo16@0xf49 raw=0x8001 sext=-32767 src=contents&mask@0xf49=0x8001; no-local-adjust,257,0x101,ok,0,,
27630,1841,0x731,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0x731=0x101; match-lo16@0xf4a raw=0x100 sext=256 src=contents&mask@0xf4a=0x100; no-local-adjust,258,0x102,ok,0,,
27635,1841,0x731,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0x731=0x180; match-lo16@0xf4a raw=0x8001 sext=-32767 src=contents&mask@0xf4a=0x8001; no-local-adjust,257,0x101,ok,0,,

And for different input, just one character difference can cause exactly 26 lines to change; while when there are more than one character changes, it seems that only these 26 lines will undergo changes:

1
27337, 27356, 27363, 27376, 27383, 27390, 27403, 27416, 27429, 27448, 27455, 27474, 27493, 27500, 27513, 27520, 27527, 27540, 27565, 27578, 27585, 27592, 27599, 27618, 27625, 27632

4. Large number of duplicate offsets from 0xF4E to 0xFCB

From index ~27649 to ~138640 there is a big chunk with a lot of repeating indices, loops for 127 cycles, and the target offset is 0xF4E to 0xFCB (0xFCB has 129 times):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
off_0xF4E = [27649, 28025, 28026, 29535, 29536, 31297, 31298, 33057, 33058, 34817, 34818, 36579, 36580, 38335, 38336, 40097, 40098, 41855, 41856, 43617, 43618, 45379, 45380, 47137, 47138, 48899, 48900, 50655, 50656, 52417, 52418, 54175, 54176, 55927, 55928, 57689, 57690, 59439, 59440, 61199, 61200, 62959, 62960, 64717, 64718, 66475, 66476, 68237, 68238, 69997, 69998, 71757, 71758, 73517, 73518, 75277, 75278, 77039, 77040, 78801, 78802, 80563, 80564, 82325, 82326, 84085, 84086, 85847, 85848, 87609, 87610, 89363, 89364, 91121, 91122, 92883, 92884, 94645, 94646, 96403, 96404, 98165, 98166, 99909, 99910, 101671, 101672, 103433, 103434, 105195, 105196, 106955, 106956, 108713, 108714, 110475, 110476, 112237, 112238, 113999, 114000, 115761, 115762, 117521, 117522, 119283, 119284, 121045, 121046, 122807, 122808, 124569, 124570, 126329, 126330, 128089, 128090, 129851, 129852, 131613, 131614, 133369, 133370, 135131, 135132, 136891, 136892]
off_0xF4F = [27652, 28035, 28036, 29547, 29548, 31309, 31310, 33069, 33070, 34829, 34830, 36591, 36592, 38347, 38348, 40109, 40110, 41867, 41868, 43629, 43630, 45391, 45392, 47149, 47150, 48911, 48912, 50667, 50668, 52429, 52430, 54187, 54188, 55939, 55940, 57701, 57702, 59451, 59452, 61211, 61212, 62971, 62972, 64729, 64730, 66487, 66488, 68249, 68250, 70009, 70010, 71769, 71770, 73529, 73530, 75289, 75290, 77051, 77052, 78813, 78814, 80575, 80576, 82337, 82338, 84097, 84098, 85859, 85860, 87621, 87622, 89375, 89376, 91133, 91134, 92895, 92896, 94657, 94658, 96415, 96416, 98177, 98178, 99921, 99922, 101683, 101684, 103445, 103446, 105207, 105208, 106967, 106968, 108725, 108726, 110487, 110488, 112249, 112250, 114011, 114012, 115773, 115774, 117533, 117534, 119295, 119296, 121057, 121058, 122819, 122820, 124581, 124582, 126341, 126342, 128101, 128102, 129863, 129864, 131625, 131626, 133381, 133382, 135143, 135144, 136903, 136904]
off_0xF50 = [27655, 28047, 28048, 29561, 29562, 31323, 31324, 33083, 33084, 34843, 34844, 36605, 36606, 38361, 38362, 40123, 40124, 41881, 41882, 43643, 43644, 45405, 45406, 47163, 47164, 48925, 48926, 50681, 50682, 52443, 52444, 54201, 54202, 55953, 55954, 57715, 57716, 59465, 59466, 61225, 61226, 62985, 62986, 64743, 64744, 66501, 66502, 68263, 68264, 70023, 70024, 71783, 71784, 73543, 73544, 75303, 75304, 77065, 77066, 78827, 78828, 80589, 80590, 82351, 82352, 84111, 84112, 85873, 85874, 87635, 87636, 89389, 89390, 91147, 91148, 92909, 92910, 94671, 94672, 96429, 96430, 98191, 98192, 99935, 99936, 101697, 101698, 103459, 103460, 105221, 105222, 106981, 106982, 108739, 108740, 110501, 110502, 112263, 112264, 114025, 114026, 115787, 115788, 117547, 117548, 119309, 119310, 121071, 121072, 122833, 122834, 124595, 124596, 126355, 126356, 128115, 128116, 129877, 129878, 131639, 131640, 133395, 133396, 135157, 135158, 136917, 136918]
off_0xF51 = [27658, 28059, 28060, 29575, 29576, 31337, 31338, 33097, 33098, 34857, 34858, 36619, 36620, 38375, 38376, 40137, 40138, 41895, 41896, 43657, 43658, 45419, 45420, 47177, 47178, 48939, 48940, 50695, 50696, 52457, 52458, 54215, 54216, 55967, 55968, 57729, 57730, 59479, 59480, 61239, 61240, 62999, 63000, 64757, 64758, 66515, 66516, 68277, 68278, 70037, 70038, 71797, 71798, 73557, 73558, 75317, 75318, 77079, 77080, 78841, 78842, 80603, 80604, 82365, 82366, 84125, 84126, 85887, 85888, 87649, 87650, 89403, 89404, 91161, 91162, 92923, 92924, 94685, 94686, 96443, 96444, 98205, 98206, 99949, 99950, 101711, 101712, 103473, 103474, 105235, 105236, 106995, 106996, 108753, 108754, 110515, 110516, 112277, 112278, 114039, 114040, 115801, 115802, 117561, 117562, 119323, 119324, 121085, 121086, 122847, 122848, 124609, 124610, 126369, 126370, 128129, 128130, 129891, 129892, 131653, 131654, 133409, 133410, 135171, 135172, 136931, 136932]
off_0xF52 = [27661, 28071, 28072, 29589, 29590, 31351, 31352, 33111, 33112, 34871, 34872, 36633, 36634, 38389, 38390, 40151, 40152, 41909, 41910, 43671, 43672, 45433, 45434, 47191, 47192, 48953, 48954, 50709, 50710, 52471, 52472, 54229, 54230, 55981, 55982, 57743, 57744, 59493, 59494, 61253, 61254, 63013, 63014, 64771, 64772, 66529, 66530, 68291, 68292, 70051, 70052, 71811, 71812, 73571, 73572, 75331, 75332, 77093, 77094, 78855, 78856, 80617, 80618, 82379, 82380, 84139, 84140, 85901, 85902, 87663, 87664, 89417, 89418, 91175, 91176, 92937, 92938, 94699, 94700, 96457, 96458, 98219, 98220, 99963, 99964, 101725, 101726, 103487, 103488, 105249, 105250, 107009, 107010, 108767, 108768, 110529, 110530, 112291, 112292, 114053, 114054, 115815, 115816, 117575, 117576, 119337, 119338, 121099, 121100, 122861, 122862, 124623, 124624, 126383, 126384, 128143, 128144, 129905, 129906, 131667, 131668, 133423, 133424, 135185, 135186, 136945, 136946]
off_0xF53 = [27664, 28083, 28084, 29603, 29604, 31365, 31366, 33125, 33126, 34885, 34886, 36647, 36648, 38403, 38404, 40165, 40166, 41923, 41924, 43685, 43686, 45447, 45448, 47205, 47206, 48967, 48968, 50723, 50724, 52485, 52486, 54243, 54244, 55995, 55996, 57757, 57758, 59507, 59508, 61267, 61268, 63027, 63028, 64785, 64786, 66543, 66544, 68305, 68306, 70065, 70066, 71825, 71826, 73585, 73586, 75345, 75346, 77107, 77108, 78869, 78870, 80631, 80632, 82393, 82394, 84153, 84154, 85915, 85916, 87677, 87678, 89431, 89432, 91189, 91190, 92951, 92952, 94713, 94714, 96471, 96472, 98233, 98234, 99977, 99978, 101739, 101740, 103501, 103502, 105263, 105264, 107023, 107024, 108781, 108782, 110543, 110544, 112305, 112306, 114067, 114068, 115829, 115830, 117589, 117590, 119351, 119352, 121113, 121114, 122875, 122876, 124637, 124638, 126397, 126398, 128157, 128158, 129919, 129920, 131681, 131682, 133437, 133438, 135199, 135200, 136959, 136960]
off_0xF54 = [27667, 28095, 28096, 29617, 29618, 31379, 31380, 33139, 33140, 34899, 34900, 36661, 36662, 38417, 38418, 40179, 40180, 41937, 41938, 43699, 43700, 45461, 45462, 47219, 47220, 48981, 48982, 50737, 50738, 52499, 52500, 54257, 54258, 56009, 56010, 57771, 57772, 59521, 59522, 61281, 61282, 63041, 63042, 64799, 64800, 66557, 66558, 68319, 68320, 70079, 70080, 71839, 71840, 73599, 73600, 75359, 75360, 77121, 77122, 78883, 78884, 80645, 80646, 82407, 82408, 84167, 84168, 85929, 85930, 87691, 87692, 89445, 89446, 91203, 91204, 92965, 92966, 94727, 94728, 96485, 96486, 98247, 98248, 99991, 99992, 101753, 101754, 103515, 103516, 105277, 105278, 107037, 107038, 108795, 108796, 110557, 110558, 112319, 112320, 114081, 114082, 115843, 115844, 117603, 117604, 119365, 119366, 121127, 121128, 122889, 122890, 124651, 124652, 126411, 126412, 128171, 128172, 129933, 129934, 131695, 131696, 133451, 133452, 135213, 135214, 136973, 136974]
off_0xF55 = [27670, 28107, 28108, 29631, 29632, 31393, 31394, 33153, 33154, 34913, 34914, 36675, 36676, 38431, 38432, 40193, 40194, 41951, 41952, 43713, 43714, 45475, 45476, 47233, 47234, 48995, 48996, 50751, 50752, 52513, 52514, 54271, 54272, 56023, 56024, 57785, 57786, 59535, 59536, 61295, 61296, 63055, 63056, 64813, 64814, 66571, 66572, 68333, 68334, 70093, 70094, 71853, 71854, 73613, 73614, 75373, 75374, 77135, 77136, 78897, 78898, 80659, 80660, 82421, 82422, 84181, 84182, 85943, 85944, 87705, 87706, 89459, 89460, 91217, 91218, 92979, 92980, 94741, 94742, 96499, 96500, 98261, 98262, 100005, 100006, 101767, 101768, 103529, 103530, 105291, 105292, 107051, 107052, 108809, 108810, 110571, 110572, 112333, 112334, 114095, 114096, 115857, 115858, 117617, 117618, 119379, 119380, 121141, 121142, 122903, 122904, 124665, 124666, 126425, 126426, 128185, 128186, 129947, 129948, 131709, 131710, 133465, 133466, 135227, 135228, 136987, 136988]
off_0xF56 = [27673, 28119, 28120, 29645, 29646, 31407, 31408, 33167, 33168, 34927, 34928, 36689, 36690, 38445, 38446, 40207, 40208, 41965, 41966, 43727, 43728, 45489, 45490, 47247, 47248, 49009, 49010, 50765, 50766, 52527, 52528, 54285, 54286, 56037, 56038, 57799, 57800, 59549, 59550, 61309, 61310, 63069, 63070, 64827, 64828, 66585, 66586, 68347, 68348, 70107, 70108, 71867, 71868, 73627, 73628, 75387, 75388, 77149, 77150, 78911, 78912, 80673, 80674, 82435, 82436, 84195, 84196, 85957, 85958, 87719, 87720, 89473, 89474, 91231, 91232, 92993, 92994, 94755, 94756, 96513, 96514, 98275, 98276, 100019, 100020, 101781, 101782, 103543, 103544, 105305, 105306, 107065, 107066, 108823, 108824, 110585, 110586, 112347, 112348, 114109, 114110, 115871, 115872, 117631, 117632, 119393, 119394, 121155, 121156, 122917, 122918, 124679, 124680, 126439, 126440, 128199, 128200, 129961, 129962, 131723, 131724, 133479, 133480, 135241, 135242, 137001, 137002]
off_0xF57 = [27676, 28131, 28132, 29659, 29660, 31421, 31422, 33181, 33182, 34941, 34942, 36703, 36704, 38459, 38460, 40221, 40222, 41979, 41980, 43741, 43742, 45503, 45504, 47261, 47262, 49023, 49024, 50779, 50780, 52541, 52542, 54299, 54300, 56051, 56052, 57813, 57814, 59563, 59564, 61323, 61324, 63083, 63084, 64841, 64842, 66599, 66600, 68361, 68362, 70121, 70122, 71881, 71882, 73641, 73642, 75401, 75402, 77163, 77164, 78925, 78926, 80687, 80688, 82449, 82450, 84209, 84210, 85971, 85972, 87733, 87734, 89487, 89488, 91245, 91246, 93007, 93008, 94769, 94770, 96527, 96528, 98289, 98290, 100033, 100034, 101795, 101796, 103557, 103558, 105319, 105320, 107079, 107080, 108837, 108838, 110599, 110600, 112361, 112362, 114123, 114124, 115885, 115886, 117645, 117646, 119407, 119408, 121169, 121170, 122931, 122932, 124693, 124694, 126453, 126454, 128213, 128214, 129975, 129976, 131737, 131738, 133493, 133494, 135255, 135256, 137015, 137016]
off_0xF58 = [27679, 28143, 28144, 29673, 29674, 31435, 31436, 33195, 33196, 34955, 34956, 36717, 36718, 38473, 38474, 40235, 40236, 41993, 41994, 43755, 43756, 45517, 45518, 47275, 47276, 49037, 49038, 50793, 50794, 52555, 52556, 54313, 54314, 56065, 56066, 57827, 57828, 59577, 59578, 61337, 61338, 63097, 63098, 64855, 64856, 66613, 66614, 68375, 68376, 70135, 70136, 71895, 71896, 73655, 73656, 75415, 75416, 77177, 77178, 78939, 78940, 80701, 80702, 82463, 82464, 84223, 84224, 85985, 85986, 87747, 87748, 89501, 89502, 91259, 91260, 93021, 93022, 94783, 94784, 96541, 96542, 98303, 98304, 100047, 100048, 101809, 101810, 103571, 103572, 105333, 105334, 107093, 107094, 108851, 108852, 110613, 110614, 112375, 112376, 114137, 114138, 115899, 115900, 117659, 117660, 119421, 119422, 121183, 121184, 122945, 122946, 124707, 124708, 126467, 126468, 128227, 128228, 129989, 129990, 131751, 131752, 133507, 133508, 135269, 135270, 137029, 137030]
off_0xF59 = [27682, 28155, 28156, 29687, 29688, 31449, 31450, 33209, 33210, 34969, 34970, 36731, 36732, 38487, 38488, 40249, 40250, 42007, 42008, 43769, 43770, 45531, 45532, 47289, 47290, 49051, 49052, 50807, 50808, 52569, 52570, 54327, 54328, 56079, 56080, 57841, 57842, 59591, 59592, 61351, 61352, 63111, 63112, 64869, 64870, 66627, 66628, 68389, 68390, 70149, 70150, 71909, 71910, 73669, 73670, 75429, 75430, 77191, 77192, 78953, 78954, 80715, 80716, 82477, 82478, 84237, 84238, 85999, 86000, 87761, 87762, 89515, 89516, 91273, 91274, 93035, 93036, 94797, 94798, 96555, 96556, 98317, 98318, 100061, 100062, 101823, 101824, 103585, 103586, 105347, 105348, 107107, 107108, 108865, 108866, 110627, 110628, 112389, 112390, 114151, 114152, 115913, 115914, 117673, 117674, 119435, 119436, 121197, 121198, 122959, 122960, 124721, 124722, 126481, 126482, 128241, 128242, 130003, 130004, 131765, 131766, 133521, 133522, 135283, 135284, 137043, 137044]
off_0xF5A = [27685, 28167, 28168, 29701, 29702, 31463, 31464, 33223, 33224, 34983, 34984, 36745, 36746, 38501, 38502, 40263, 40264, 42021, 42022, 43783, 43784, 45545, 45546, 47303, 47304, 49065, 49066, 50821, 50822, 52583, 52584, 54341, 54342, 56093, 56094, 57855, 57856, 59605, 59606, 61365, 61366, 63125, 63126, 64883, 64884, 66641, 66642, 68403, 68404, 70163, 70164, 71923, 71924, 73683, 73684, 75443, 75444, 77205, 77206, 78967, 78968, 80729, 80730, 82491, 82492, 84251, 84252, 86013, 86014, 87775, 87776, 89529, 89530, 91287, 91288, 93049, 93050, 94811, 94812, 96569, 96570, 98331, 98332, 100075, 100076, 101837, 101838, 103599, 103600, 105361, 105362, 107121, 107122, 108879, 108880, 110641, 110642, 112403, 112404, 114165, 114166, 115927, 115928, 117687, 117688, 119449, 119450, 121211, 121212, 122973, 122974, 124735, 124736, 126495, 126496, 128255, 128256, 130017, 130018, 131779, 131780, 133535, 133536, 135297, 135298, 137057, 137058]
off_0xF5B = [27688, 28179, 28180, 29715, 29716, 31477, 31478, 33237, 33238, 34997, 34998, 36759, 36760, 38515, 38516, 40277, 40278, 42035, 42036, 43797, 43798, 45559, 45560, 47317, 47318, 49079, 49080, 50835, 50836, 52597, 52598, 54355, 54356, 56107, 56108, 57869, 57870, 59619, 59620, 61379, 61380, 63139, 63140, 64897, 64898, 66655, 66656, 68417, 68418, 70177, 70178, 71937, 71938, 73697, 73698, 75457, 75458, 77219, 77220, 78981, 78982, 80743, 80744, 82505, 82506, 84265, 84266, 86027, 86028, 87789, 87790, 89543, 89544, 91301, 91302, 93063, 93064, 94825, 94826, 96583, 96584, 98345, 98346, 100089, 100090, 101851, 101852, 103613, 103614, 105375, 105376, 107135, 107136, 108893, 108894, 110655, 110656, 112417, 112418, 114179, 114180, 115941, 115942, 117701, 117702, 119463, 119464, 121225, 121226, 122987, 122988, 124749, 124750, 126509, 126510, 128269, 128270, 130031, 130032, 131793, 131794, 133549, 133550, 135311, 135312, 137071, 137072]
off_0xF5C = [27691, 28191, 28192, 29729, 29730, 31491, 31492, 33251, 33252, 35011, 35012, 36773, 36774, 38529, 38530, 40291, 40292, 42049, 42050, 43811, 43812, 45573, 45574, 47331, 47332, 49093, 49094, 50849, 50850, 52611, 52612, 54369, 54370, 56121, 56122, 57883, 57884, 59633, 59634, 61393, 61394, 63153, 63154, 64911, 64912, 66669, 66670, 68431, 68432, 70191, 70192, 71951, 71952, 73711, 73712, 75471, 75472, 77233, 77234, 78995, 78996, 80757, 80758, 82519, 82520, 84279, 84280, 86041, 86042, 87803, 87804, 89557, 89558, 91315, 91316, 93077, 93078, 94839, 94840, 96597, 96598, 98359, 98360, 100103, 100104, 101865, 101866, 103627, 103628, 105389, 105390, 107149, 107150, 108907, 108908, 110669, 110670, 112431, 112432, 114193, 114194, 115955, 115956, 117715, 117716, 119477, 119478, 121239, 121240, 123001, 123002, 124763, 124764, 126523, 126524, 128283, 128284, 130045, 130046, 131807, 131808, 133563, 133564, 135325, 135326, 137085, 137086]
off_0xF5D = [27694, 28203, 28204, 29743, 29744, 31505, 31506, 33265, 33266, 35025, 35026, 36787, 36788, 38543, 38544, 40305, 40306, 42063, 42064, 43825, 43826, 45587, 45588, 47345, 47346, 49107, 49108, 50863, 50864, 52625, 52626, 54383, 54384, 56135, 56136, 57897, 57898, 59647, 59648, 61407, 61408, 63167, 63168, 64925, 64926, 66683, 66684, 68445, 68446, 70205, 70206, 71965, 71966, 73725, 73726, 75485, 75486, 77247, 77248, 79009, 79010, 80771, 80772, 82533, 82534, 84293, 84294, 86055, 86056, 87817, 87818, 89571, 89572, 91329, 91330, 93091, 93092, 94853, 94854, 96611, 96612, 98373, 98374, 100117, 100118, 101879, 101880, 103641, 103642, 105403, 105404, 107163, 107164, 108921, 108922, 110683, 110684, 112445, 112446, 114207, 114208, 115969, 115970, 117729, 117730, 119491, 119492, 121253, 121254, 123015, 123016, 124777, 124778, 126537, 126538, 128297, 128298, 130059, 130060, 131821, 131822, 133577, 133578, 135339, 135340, 137099, 137100]
off_0xF5E = [27697, 28215, 28216, 29757, 29758, 31519, 31520, 33279, 33280, 35039, 35040, 36801, 36802, 38557, 38558, 40319, 40320, 42077, 42078, 43839, 43840, 45601, 45602, 47359, 47360, 49121, 49122, 50877, 50878, 52639, 52640, 54397, 54398, 56149, 56150, 57911, 57912, 59661, 59662, 61421, 61422, 63181, 63182, 64939, 64940, 66697, 66698, 68459, 68460, 70219, 70220, 71979, 71980, 73739, 73740, 75499, 75500, 77261, 77262, 79023, 79024, 80785, 80786, 82547, 82548, 84307, 84308, 86069, 86070, 87831, 87832, 89585, 89586, 91343, 91344, 93105, 93106, 94867, 94868, 96625, 96626, 98387, 98388, 100131, 100132, 101893, 101894, 103655, 103656, 105417, 105418, 107177, 107178, 108935, 108936, 110697, 110698, 112459, 112460, 114221, 114222, 115983, 115984, 117743, 117744, 119505, 119506, 121267, 121268, 123029, 123030, 124791, 124792, 126551, 126552, 128311, 128312, 130073, 130074, 131835, 131836, 133591, 133592, 135353, 135354, 137113, 137114]
off_0xF5F = [27700, 28227, 28228, 29771, 29772, 31533, 31534, 33293, 33294, 35053, 35054, 36815, 36816, 38571, 38572, 40333, 40334, 42091, 42092, 43853, 43854, 45615, 45616, 47373, 47374, 49135, 49136, 50891, 50892, 52653, 52654, 54411, 54412, 56163, 56164, 57925, 57926, 59675, 59676, 61435, 61436, 63195, 63196, 64953, 64954, 66711, 66712, 68473, 68474, 70233, 70234, 71993, 71994, 73753, 73754, 75513, 75514, 77275, 77276, 79037, 79038, 80799, 80800, 82561, 82562, 84321, 84322, 86083, 86084, 87845, 87846, 89599, 89600, 91357, 91358, 93119, 93120, 94881, 94882, 96639, 96640, 98401, 98402, 100145, 100146, 101907, 101908, 103669, 103670, 105431, 105432, 107191, 107192, 108949, 108950, 110711, 110712, 112473, 112474, 114235, 114236, 115997, 115998, 117757, 117758, 119519, 119520, 121281, 121282, 123043, 123044, 124805, 124806, 126565, 126566, 128325, 128326, 130087, 130088, 131849, 131850, 133605, 133606, 135367, 135368, 137127, 137128]
off_0xF60 = [27703, 28239, 28240, 29785, 29786, 31547, 31548, 33307, 33308, 35067, 35068, 36829, 36830, 38585, 38586, 40347, 40348, 42105, 42106, 43867, 43868, 45629, 45630, 47387, 47388, 49149, 49150, 50905, 50906, 52667, 52668, 54425, 54426, 56177, 56178, 57939, 57940, 59689, 59690, 61449, 61450, 63209, 63210, 64967, 64968, 66725, 66726, 68487, 68488, 70247, 70248, 72007, 72008, 73767, 73768, 75527, 75528, 77289, 77290, 79051, 79052, 80813, 80814, 82575, 82576, 84335, 84336, 86097, 86098, 87859, 87860, 89613, 89614, 91371, 91372, 93133, 93134, 94895, 94896, 96653, 96654, 98415, 98416, 100159, 100160, 101921, 101922, 103683, 103684, 105445, 105446, 107205, 107206, 108963, 108964, 110725, 110726, 112487, 112488, 114249, 114250, 116011, 116012, 117771, 117772, 119533, 119534, 121295, 121296, 123057, 123058, 124819, 124820, 126579, 126580, 128339, 128340, 130101, 130102, 131863, 131864, 133619, 133620, 135381, 135382, 137141, 137142]
off_0xF61 = [27706, 28251, 28252, 29799, 29800, 31561, 31562, 33321, 33322, 35081, 35082, 36843, 36844, 38599, 38600, 40361, 40362, 42119, 42120, 43881, 43882, 45643, 45644, 47401, 47402, 49163, 49164, 50919, 50920, 52681, 52682, 54439, 54440, 56191, 56192, 57953, 57954, 59703, 59704, 61463, 61464, 63223, 63224, 64981, 64982, 66739, 66740, 68501, 68502, 70261, 70262, 72021, 72022, 73781, 73782, 75541, 75542, 77303, 77304, 79065, 79066, 80827, 80828, 82589, 82590, 84349, 84350, 86111, 86112, 87873, 87874, 89627, 89628, 91385, 91386, 93147, 93148, 94909, 94910, 96667, 96668, 98429, 98430, 100173, 100174, 101935, 101936, 103697, 103698, 105459, 105460, 107219, 107220, 108977, 108978, 110739, 110740, 112501, 112502, 114263, 114264, 116025, 116026, 117785, 117786, 119547, 119548, 121309, 121310, 123071, 123072, 124833, 124834, 126593, 126594, 128353, 128354, 130115, 130116, 131877, 131878, 133633, 133634, 135395, 135396, 137155, 137156]
off_0xF62 = [27709, 28263, 28264, 29813, 29814, 31575, 31576, 33335, 33336, 35095, 35096, 36857, 36858, 38613, 38614, 40375, 40376, 42133, 42134, 43895, 43896, 45657, 45658, 47415, 47416, 49177, 49178, 50933, 50934, 52695, 52696, 54453, 54454, 56205, 56206, 57967, 57968, 59717, 59718, 61477, 61478, 63237, 63238, 64995, 64996, 66753, 66754, 68515, 68516, 70275, 70276, 72035, 72036, 73795, 73796, 75555, 75556, 77317, 77318, 79079, 79080, 80841, 80842, 82603, 82604, 84363, 84364, 86125, 86126, 87887, 87888, 89641, 89642, 91399, 91400, 93161, 93162, 94923, 94924, 96681, 96682, 98443, 98444, 100187, 100188, 101949, 101950, 103711, 103712, 105473, 105474, 107233, 107234, 108991, 108992, 110753, 110754, 112515, 112516, 114277, 114278, 116039, 116040, 117799, 117800, 119561, 119562, 121323, 121324, 123085, 123086, 124847, 124848, 126607, 126608, 128367, 128368, 130129, 130130, 131891, 131892, 133647, 133648, 135409, 135410, 137169, 137170]
off_0xF63 = [27712, 28275, 28276, 29827, 29828, 31589, 31590, 33349, 33350, 35109, 35110, 36871, 36872, 38627, 38628, 40389, 40390, 42147, 42148, 43909, 43910, 45671, 45672, 47429, 47430, 49191, 49192, 50947, 50948, 52709, 52710, 54467, 54468, 56219, 56220, 57981, 57982, 59731, 59732, 61491, 61492, 63251, 63252, 65009, 65010, 66767, 66768, 68529, 68530, 70289, 70290, 72049, 72050, 73809, 73810, 75569, 75570, 77331, 77332, 79093, 79094, 80855, 80856, 82617, 82618, 84377, 84378, 86139, 86140, 87901, 87902, 89655, 89656, 91413, 91414, 93175, 93176, 94937, 94938, 96695, 96696, 98457, 98458, 100201, 100202, 101963, 101964, 103725, 103726, 105487, 105488, 107247, 107248, 109005, 109006, 110767, 110768, 112529, 112530, 114291, 114292, 116053, 116054, 117813, 117814, 119575, 119576, 121337, 121338, 123099, 123100, 124861, 124862, 126621, 126622, 128381, 128382, 130143, 130144, 131905, 131906, 133661, 133662, 135423, 135424, 137183, 137184]
off_0xF64 = [27715, 28287, 28288, 29841, 29842, 31603, 31604, 33363, 33364, 35123, 35124, 36885, 36886, 38641, 38642, 40403, 40404, 42161, 42162, 43923, 43924, 45685, 45686, 47443, 47444, 49205, 49206, 50961, 50962, 52723, 52724, 54481, 54482, 56233, 56234, 57995, 57996, 59745, 59746, 61505, 61506, 63265, 63266, 65023, 65024, 66781, 66782, 68543, 68544, 70303, 70304, 72063, 72064, 73823, 73824, 75583, 75584, 77345, 77346, 79107, 79108, 80869, 80870, 82631, 82632, 84391, 84392, 86153, 86154, 87915, 87916, 89669, 89670, 91427, 91428, 93189, 93190, 94951, 94952, 96709, 96710, 98471, 98472, 100215, 100216, 101977, 101978, 103739, 103740, 105501, 105502, 107261, 107262, 109019, 109020, 110781, 110782, 112543, 112544, 114305, 114306, 116067, 116068, 117827, 117828, 119589, 119590, 121351, 121352, 123113, 123114, 124875, 124876, 126635, 126636, 128395, 128396, 130157, 130158, 131919, 131920, 133675, 133676, 135437, 135438, 137197, 137198]
off_0xF65 = [27718, 28299, 28300, 29855, 29856, 31617, 31618, 33377, 33378, 35137, 35138, 36899, 36900, 38655, 38656, 40417, 40418, 42175, 42176, 43937, 43938, 45699, 45700, 47457, 47458, 49219, 49220, 50975, 50976, 52737, 52738, 54495, 54496, 56247, 56248, 58009, 58010, 59759, 59760, 61519, 61520, 63279, 63280, 65037, 65038, 66795, 66796, 68557, 68558, 70317, 70318, 72077, 72078, 73837, 73838, 75597, 75598, 77359, 77360, 79121, 79122, 80883, 80884, 82645, 82646, 84405, 84406, 86167, 86168, 87929, 87930, 89683, 89684, 91441, 91442, 93203, 93204, 94965, 94966, 96723, 96724, 98485, 98486, 100229, 100230, 101991, 101992, 103753, 103754, 105515, 105516, 107275, 107276, 109033, 109034, 110795, 110796, 112557, 112558, 114319, 114320, 116081, 116082, 117841, 117842, 119603, 119604, 121365, 121366, 123127, 123128, 124889, 124890, 126649, 126650, 128409, 128410, 130171, 130172, 131933, 131934, 133689, 133690, 135451, 135452, 137211, 137212]
off_0xF66 = [27721, 28311, 28312, 29869, 29870, 31631, 31632, 33391, 33392, 35151, 35152, 36913, 36914, 38669, 38670, 40431, 40432, 42189, 42190, 43951, 43952, 45713, 45714, 47471, 47472, 49233, 49234, 50989, 50990, 52751, 52752, 54509, 54510, 56261, 56262, 58023, 58024, 59773, 59774, 61533, 61534, 63293, 63294, 65051, 65052, 66809, 66810, 68571, 68572, 70331, 70332, 72091, 72092, 73851, 73852, 75611, 75612, 77373, 77374, 79135, 79136, 80897, 80898, 82659, 82660, 84419, 84420, 86181, 86182, 87943, 87944, 89697, 89698, 91455, 91456, 93217, 93218, 94979, 94980, 96737, 96738, 98499, 98500, 100243, 100244, 102005, 102006, 103767, 103768, 105529, 105530, 107289, 107290, 109047, 109048, 110809, 110810, 112571, 112572, 114333, 114334, 116095, 116096, 117855, 117856, 119617, 119618, 121379, 121380, 123141, 123142, 124903, 124904, 126663, 126664, 128423, 128424, 130185, 130186, 131947, 131948, 133703, 133704, 135465, 135466, 137225, 137226]
off_0xF67 = [27724, 28323, 28324, 29883, 29884, 31645, 31646, 33405, 33406, 35165, 35166, 36927, 36928, 38683, 38684, 40445, 40446, 42203, 42204, 43965, 43966, 45727, 45728, 47485, 47486, 49247, 49248, 51003, 51004, 52765, 52766, 54523, 54524, 56275, 56276, 58037, 58038, 59787, 59788, 61547, 61548, 63307, 63308, 65065, 65066, 66823, 66824, 68585, 68586, 70345, 70346, 72105, 72106, 73865, 73866, 75625, 75626, 77387, 77388, 79149, 79150, 80911, 80912, 82673, 82674, 84433, 84434, 86195, 86196, 87957, 87958, 89711, 89712, 91469, 91470, 93231, 93232, 94993, 94994, 96751, 96752, 98513, 98514, 100257, 100258, 102019, 102020, 103781, 103782, 105543, 105544, 107303, 107304, 109061, 109062, 110823, 110824, 112585, 112586, 114347, 114348, 116109, 116110, 117869, 117870, 119631, 119632, 121393, 121394, 123155, 123156, 124917, 124918, 126677, 126678, 128437, 128438, 130199, 130200, 131961, 131962, 133717, 133718, 135479, 135480, 137239, 137240]
off_0xF68 = [27727, 28335, 28336, 29897, 29898, 31659, 31660, 33419, 33420, 35179, 35180, 36941, 36942, 38697, 38698, 40459, 40460, 42217, 42218, 43979, 43980, 45741, 45742, 47499, 47500, 49261, 49262, 51017, 51018, 52779, 52780, 54537, 54538, 56289, 56290, 58051, 58052, 59801, 59802, 61561, 61562, 63321, 63322, 65079, 65080, 66837, 66838, 68599, 68600, 70359, 70360, 72119, 72120, 73879, 73880, 75639, 75640, 77401, 77402, 79163, 79164, 80925, 80926, 82687, 82688, 84447, 84448, 86209, 86210, 87971, 87972, 89725, 89726, 91483, 91484, 93245, 93246, 95007, 95008, 96765, 96766, 98527, 98528, 100271, 100272, 102033, 102034, 103795, 103796, 105557, 105558, 107317, 107318, 109075, 109076, 110837, 110838, 112599, 112600, 114361, 114362, 116123, 116124, 117883, 117884, 119645, 119646, 121407, 121408, 123169, 123170, 124931, 124932, 126691, 126692, 128451, 128452, 130213, 130214, 131975, 131976, 133731, 133732, 135493, 135494, 137253, 137254]
off_0xF69 = [27730, 28347, 28348, 29911, 29912, 31673, 31674, 33433, 33434, 35193, 35194, 36955, 36956, 38711, 38712, 40473, 40474, 42231, 42232, 43993, 43994, 45755, 45756, 47513, 47514, 49275, 49276, 51031, 51032, 52793, 52794, 54551, 54552, 56303, 56304, 58065, 58066, 59815, 59816, 61575, 61576, 63335, 63336, 65093, 65094, 66851, 66852, 68613, 68614, 70373, 70374, 72133, 72134, 73893, 73894, 75653, 75654, 77415, 77416, 79177, 79178, 80939, 80940, 82701, 82702, 84461, 84462, 86223, 86224, 87985, 87986, 89739, 89740, 91497, 91498, 93259, 93260, 95021, 95022, 96779, 96780, 98541, 98542, 100285, 100286, 102047, 102048, 103809, 103810, 105571, 105572, 107331, 107332, 109089, 109090, 110851, 110852, 112613, 112614, 114375, 114376, 116137, 116138, 117897, 117898, 119659, 119660, 121421, 121422, 123183, 123184, 124945, 124946, 126705, 126706, 128465, 128466, 130227, 130228, 131989, 131990, 133745, 133746, 135507, 135508, 137267, 137268]
off_0xF6A = [27733, 28359, 28360, 29925, 29926, 31687, 31688, 33447, 33448, 35207, 35208, 36969, 36970, 38725, 38726, 40487, 40488, 42245, 42246, 44007, 44008, 45769, 45770, 47527, 47528, 49289, 49290, 51045, 51046, 52807, 52808, 54565, 54566, 56317, 56318, 58079, 58080, 59829, 59830, 61589, 61590, 63349, 63350, 65107, 65108, 66865, 66866, 68627, 68628, 70387, 70388, 72147, 72148, 73907, 73908, 75667, 75668, 77429, 77430, 79191, 79192, 80953, 80954, 82715, 82716, 84475, 84476, 86237, 86238, 87999, 88000, 89753, 89754, 91511, 91512, 93273, 93274, 95035, 95036, 96793, 96794, 98555, 98556, 100299, 100300, 102061, 102062, 103823, 103824, 105585, 105586, 107345, 107346, 109103, 109104, 110865, 110866, 112627, 112628, 114389, 114390, 116151, 116152, 117911, 117912, 119673, 119674, 121435, 121436, 123197, 123198, 124959, 124960, 126719, 126720, 128479, 128480, 130241, 130242, 132003, 132004, 133759, 133760, 135521, 135522, 137281, 137282]
off_0xF6B = [27736, 28371, 28372, 29939, 29940, 31701, 31702, 33461, 33462, 35221, 35222, 36983, 36984, 38739, 38740, 40501, 40502, 42259, 42260, 44021, 44022, 45783, 45784, 47541, 47542, 49303, 49304, 51059, 51060, 52821, 52822, 54579, 54580, 56331, 56332, 58093, 58094, 59843, 59844, 61603, 61604, 63363, 63364, 65121, 65122, 66879, 66880, 68641, 68642, 70401, 70402, 72161, 72162, 73921, 73922, 75681, 75682, 77443, 77444, 79205, 79206, 80967, 80968, 82729, 82730, 84489, 84490, 86251, 86252, 88013, 88014, 89767, 89768, 91525, 91526, 93287, 93288, 95049, 95050, 96807, 96808, 98569, 98570, 100313, 100314, 102075, 102076, 103837, 103838, 105599, 105600, 107359, 107360, 109117, 109118, 110879, 110880, 112641, 112642, 114403, 114404, 116165, 116166, 117925, 117926, 119687, 119688, 121449, 121450, 123211, 123212, 124973, 124974, 126733, 126734, 128493, 128494, 130255, 130256, 132017, 132018, 133773, 133774, 135535, 135536, 137295, 137296]
off_0xF6C = [27739, 28383, 28384, 29953, 29954, 31715, 31716, 33475, 33476, 35235, 35236, 36997, 36998, 38753, 38754, 40515, 40516, 42273, 42274, 44035, 44036, 45797, 45798, 47555, 47556, 49317, 49318, 51073, 51074, 52835, 52836, 54593, 54594, 56345, 56346, 58107, 58108, 59857, 59858, 61617, 61618, 63377, 63378, 65135, 65136, 66893, 66894, 68655, 68656, 70415, 70416, 72175, 72176, 73935, 73936, 75695, 75696, 77457, 77458, 79219, 79220, 80981, 80982, 82743, 82744, 84503, 84504, 86265, 86266, 88027, 88028, 89781, 89782, 91539, 91540, 93301, 93302, 95063, 95064, 96821, 96822, 98583, 98584, 100327, 100328, 102089, 102090, 103851, 103852, 105613, 105614, 107373, 107374, 109131, 109132, 110893, 110894, 112655, 112656, 114417, 114418, 116179, 116180, 117939, 117940, 119701, 119702, 121463, 121464, 123225, 123226, 124987, 124988, 126747, 126748, 128507, 128508, 130269, 130270, 132031, 132032, 133787, 133788, 135549, 135550, 137309, 137310]
off_0xF6D = [27742, 28395, 28396, 29967, 29968, 31729, 31730, 33489, 33490, 35249, 35250, 37011, 37012, 38767, 38768, 40529, 40530, 42287, 42288, 44049, 44050, 45811, 45812, 47569, 47570, 49331, 49332, 51087, 51088, 52849, 52850, 54607, 54608, 56359, 56360, 58121, 58122, 59871, 59872, 61631, 61632, 63391, 63392, 65149, 65150, 66907, 66908, 68669, 68670, 70429, 70430, 72189, 72190, 73949, 73950, 75709, 75710, 77471, 77472, 79233, 79234, 80995, 80996, 82757, 82758, 84517, 84518, 86279, 86280, 88041, 88042, 89795, 89796, 91553, 91554, 93315, 93316, 95077, 95078, 96835, 96836, 98597, 98598, 100341, 100342, 102103, 102104, 103865, 103866, 105627, 105628, 107387, 107388, 109145, 109146, 110907, 110908, 112669, 112670, 114431, 114432, 116193, 116194, 117953, 117954, 119715, 119716, 121477, 121478, 123239, 123240, 125001, 125002, 126761, 126762, 128521, 128522, 130283, 130284, 132045, 132046, 133801, 133802, 135563, 135564, 137323, 137324]
off_0xF6E = [27745, 28407, 28408, 29981, 29982, 31743, 31744, 33503, 33504, 35263, 35264, 37025, 37026, 38781, 38782, 40543, 40544, 42301, 42302, 44063, 44064, 45825, 45826, 47583, 47584, 49345, 49346, 51101, 51102, 52863, 52864, 54621, 54622, 56373, 56374, 58135, 58136, 59885, 59886, 61645, 61646, 63405, 63406, 65163, 65164, 66921, 66922, 68683, 68684, 70443, 70444, 72203, 72204, 73963, 73964, 75723, 75724, 77485, 77486, 79247, 79248, 81009, 81010, 82771, 82772, 84531, 84532, 86293, 86294, 88055, 88056, 89809, 89810, 91567, 91568, 93329, 93330, 95091, 95092, 96849, 96850, 98611, 98612, 100355, 100356, 102117, 102118, 103879, 103880, 105641, 105642, 107401, 107402, 109159, 109160, 110921, 110922, 112683, 112684, 114445, 114446, 116207, 116208, 117967, 117968, 119729, 119730, 121491, 121492, 123253, 123254, 125015, 125016, 126775, 126776, 128535, 128536, 130297, 130298, 132059, 132060, 133815, 133816, 135577, 135578, 137337, 137338]
off_0xF6F = [27748, 28419, 28420, 29995, 29996, 31757, 31758, 33517, 33518, 35277, 35278, 37039, 37040, 38795, 38796, 40557, 40558, 42315, 42316, 44077, 44078, 45839, 45840, 47597, 47598, 49359, 49360, 51115, 51116, 52877, 52878, 54635, 54636, 56387, 56388, 58149, 58150, 59899, 59900, 61659, 61660, 63419, 63420, 65177, 65178, 66935, 66936, 68697, 68698, 70457, 70458, 72217, 72218, 73977, 73978, 75737, 75738, 77499, 77500, 79261, 79262, 81023, 81024, 82785, 82786, 84545, 84546, 86307, 86308, 88069, 88070, 89823, 89824, 91581, 91582, 93343, 93344, 95105, 95106, 96863, 96864, 98625, 98626, 100369, 100370, 102131, 102132, 103893, 103894, 105655, 105656, 107415, 107416, 109173, 109174, 110935, 110936, 112697, 112698, 114459, 114460, 116221, 116222, 117981, 117982, 119743, 119744, 121505, 121506, 123267, 123268, 125029, 125030, 126789, 126790, 128549, 128550, 130311, 130312, 132073, 132074, 133829, 133830, 135591, 135592, 137351, 137352]
off_0xF70 = [27751, 28431, 28432, 30009, 30010, 31771, 31772, 33531, 33532, 35291, 35292, 37053, 37054, 38809, 38810, 40571, 40572, 42329, 42330, 44091, 44092, 45853, 45854, 47611, 47612, 49373, 49374, 51129, 51130, 52891, 52892, 54649, 54650, 56401, 56402, 58163, 58164, 59913, 59914, 61673, 61674, 63433, 63434, 65191, 65192, 66949, 66950, 68711, 68712, 70471, 70472, 72231, 72232, 73991, 73992, 75751, 75752, 77513, 77514, 79275, 79276, 81037, 81038, 82799, 82800, 84559, 84560, 86321, 86322, 88083, 88084, 89837, 89838, 91595, 91596, 93357, 93358, 95119, 95120, 96877, 96878, 98639, 98640, 100383, 100384, 102145, 102146, 103907, 103908, 105669, 105670, 107429, 107430, 109187, 109188, 110949, 110950, 112711, 112712, 114473, 114474, 116235, 116236, 117995, 117996, 119757, 119758, 121519, 121520, 123281, 123282, 125043, 125044, 126803, 126804, 128563, 128564, 130325, 130326, 132087, 132088, 133843, 133844, 135605, 135606, 137365, 137366]
off_0xF71 = [27754, 28443, 28444, 30023, 30024, 31785, 31786, 33545, 33546, 35305, 35306, 37067, 37068, 38823, 38824, 40585, 40586, 42343, 42344, 44105, 44106, 45867, 45868, 47625, 47626, 49387, 49388, 51143, 51144, 52905, 52906, 54663, 54664, 56415, 56416, 58177, 58178, 59927, 59928, 61687, 61688, 63447, 63448, 65205, 65206, 66963, 66964, 68725, 68726, 70485, 70486, 72245, 72246, 74005, 74006, 75765, 75766, 77527, 77528, 79289, 79290, 81051, 81052, 82813, 82814, 84573, 84574, 86335, 86336, 88097, 88098, 89851, 89852, 91609, 91610, 93371, 93372, 95133, 95134, 96891, 96892, 98653, 98654, 100397, 100398, 102159, 102160, 103921, 103922, 105683, 105684, 107443, 107444, 109201, 109202, 110963, 110964, 112725, 112726, 114487, 114488, 116249, 116250, 118009, 118010, 119771, 119772, 121533, 121534, 123295, 123296, 125057, 125058, 126817, 126818, 128577, 128578, 130339, 130340, 132101, 132102, 133857, 133858, 135619, 135620, 137379, 137380]
off_0xF72 = [27757, 28455, 28456, 30037, 30038, 31799, 31800, 33559, 33560, 35319, 35320, 37081, 37082, 38837, 38838, 40599, 40600, 42357, 42358, 44119, 44120, 45881, 45882, 47639, 47640, 49401, 49402, 51157, 51158, 52919, 52920, 54677, 54678, 56429, 56430, 58191, 58192, 59941, 59942, 61701, 61702, 63461, 63462, 65219, 65220, 66977, 66978, 68739, 68740, 70499, 70500, 72259, 72260, 74019, 74020, 75779, 75780, 77541, 77542, 79303, 79304, 81065, 81066, 82827, 82828, 84587, 84588, 86349, 86350, 88111, 88112, 89865, 89866, 91623, 91624, 93385, 93386, 95147, 95148, 96905, 96906, 98667, 98668, 100411, 100412, 102173, 102174, 103935, 103936, 105697, 105698, 107457, 107458, 109215, 109216, 110977, 110978, 112739, 112740, 114501, 114502, 116263, 116264, 118023, 118024, 119785, 119786, 121547, 121548, 123309, 123310, 125071, 125072, 126831, 126832, 128591, 128592, 130353, 130354, 132115, 132116, 133871, 133872, 135633, 135634, 137393, 137394]
off_0xF73 = [27760, 28467, 28468, 30051, 30052, 31813, 31814, 33573, 33574, 35333, 35334, 37095, 37096, 38851, 38852, 40613, 40614, 42371, 42372, 44133, 44134, 45895, 45896, 47653, 47654, 49415, 49416, 51171, 51172, 52933, 52934, 54691, 54692, 56443, 56444, 58205, 58206, 59955, 59956, 61715, 61716, 63475, 63476, 65233, 65234, 66991, 66992, 68753, 68754, 70513, 70514, 72273, 72274, 74033, 74034, 75793, 75794, 77555, 77556, 79317, 79318, 81079, 81080, 82841, 82842, 84601, 84602, 86363, 86364, 88125, 88126, 89879, 89880, 91637, 91638, 93399, 93400, 95161, 95162, 96919, 96920, 98681, 98682, 100425, 100426, 102187, 102188, 103949, 103950, 105711, 105712, 107471, 107472, 109229, 109230, 110991, 110992, 112753, 112754, 114515, 114516, 116277, 116278, 118037, 118038, 119799, 119800, 121561, 121562, 123323, 123324, 125085, 125086, 126845, 126846, 128605, 128606, 130367, 130368, 132129, 132130, 133885, 133886, 135647, 135648, 137407, 137408]
off_0xF74 = [27763, 28479, 28480, 30065, 30066, 31827, 31828, 33587, 33588, 35347, 35348, 37109, 37110, 38865, 38866, 40627, 40628, 42385, 42386, 44147, 44148, 45909, 45910, 47667, 47668, 49429, 49430, 51185, 51186, 52947, 52948, 54705, 54706, 56457, 56458, 58219, 58220, 59969, 59970, 61729, 61730, 63489, 63490, 65247, 65248, 67005, 67006, 68767, 68768, 70527, 70528, 72287, 72288, 74047, 74048, 75807, 75808, 77569, 77570, 79331, 79332, 81093, 81094, 82855, 82856, 84615, 84616, 86377, 86378, 88139, 88140, 89893, 89894, 91651, 91652, 93413, 93414, 95175, 95176, 96933, 96934, 98695, 98696, 100439, 100440, 102201, 102202, 103963, 103964, 105725, 105726, 107485, 107486, 109243, 109244, 111005, 111006, 112767, 112768, 114529, 114530, 116291, 116292, 118051, 118052, 119813, 119814, 121575, 121576, 123337, 123338, 125099, 125100, 126859, 126860, 128619, 128620, 130381, 130382, 132143, 132144, 133899, 133900, 135661, 135662, 137421, 137422]
off_0xF75 = [27766, 28491, 28492, 30079, 30080, 31841, 31842, 33601, 33602, 35361, 35362, 37123, 37124, 38879, 38880, 40641, 40642, 42399, 42400, 44161, 44162, 45923, 45924, 47681, 47682, 49443, 49444, 51199, 51200, 52961, 52962, 54719, 54720, 56471, 56472, 58233, 58234, 59983, 59984, 61743, 61744, 63503, 63504, 65261, 65262, 67019, 67020, 68781, 68782, 70541, 70542, 72301, 72302, 74061, 74062, 75821, 75822, 77583, 77584, 79345, 79346, 81107, 81108, 82869, 82870, 84629, 84630, 86391, 86392, 88153, 88154, 89907, 89908, 91665, 91666, 93427, 93428, 95189, 95190, 96947, 96948, 98709, 98710, 100453, 100454, 102215, 102216, 103977, 103978, 105739, 105740, 107499, 107500, 109257, 109258, 111019, 111020, 112781, 112782, 114543, 114544, 116305, 116306, 118065, 118066, 119827, 119828, 121589, 121590, 123351, 123352, 125113, 125114, 126873, 126874, 128633, 128634, 130395, 130396, 132157, 132158, 133913, 133914, 135675, 135676, 137435, 137436]
off_0xF76 = [27769, 28503, 28504, 30093, 30094, 31855, 31856, 33615, 33616, 35375, 35376, 37137, 37138, 38893, 38894, 40655, 40656, 42413, 42414, 44175, 44176, 45937, 45938, 47695, 47696, 49457, 49458, 51213, 51214, 52975, 52976, 54733, 54734, 56485, 56486, 58247, 58248, 59997, 59998, 61757, 61758, 63517, 63518, 65275, 65276, 67033, 67034, 68795, 68796, 70555, 70556, 72315, 72316, 74075, 74076, 75835, 75836, 77597, 77598, 79359, 79360, 81121, 81122, 82883, 82884, 84643, 84644, 86405, 86406, 88167, 88168, 89921, 89922, 91679, 91680, 93441, 93442, 95203, 95204, 96961, 96962, 98723, 98724, 100467, 100468, 102229, 102230, 103991, 103992, 105753, 105754, 107513, 107514, 109271, 109272, 111033, 111034, 112795, 112796, 114557, 114558, 116319, 116320, 118079, 118080, 119841, 119842, 121603, 121604, 123365, 123366, 125127, 125128, 126887, 126888, 128647, 128648, 130409, 130410, 132171, 132172, 133927, 133928, 135689, 135690, 137449, 137450]
off_0xF77 = [27772, 28515, 28516, 30107, 30108, 31869, 31870, 33629, 33630, 35389, 35390, 37151, 37152, 38907, 38908, 40669, 40670, 42427, 42428, 44189, 44190, 45951, 45952, 47709, 47710, 49471, 49472, 51227, 51228, 52989, 52990, 54747, 54748, 56499, 56500, 58261, 58262, 60011, 60012, 61771, 61772, 63531, 63532, 65289, 65290, 67047, 67048, 68809, 68810, 70569, 70570, 72329, 72330, 74089, 74090, 75849, 75850, 77611, 77612, 79373, 79374, 81135, 81136, 82897, 82898, 84657, 84658, 86419, 86420, 88181, 88182, 89935, 89936, 91693, 91694, 93455, 93456, 95217, 95218, 96975, 96976, 98737, 98738, 100481, 100482, 102243, 102244, 104005, 104006, 105767, 105768, 107527, 107528, 109285, 109286, 111047, 111048, 112809, 112810, 114571, 114572, 116333, 116334, 118093, 118094, 119855, 119856, 121617, 121618, 123379, 123380, 125141, 125142, 126901, 126902, 128661, 128662, 130423, 130424, 132185, 132186, 133941, 133942, 135703, 135704, 137463, 137464]
off_0xF78 = [27775, 28527, 28528, 30121, 30122, 31883, 31884, 33643, 33644, 35403, 35404, 37165, 37166, 38921, 38922, 40683, 40684, 42441, 42442, 44203, 44204, 45965, 45966, 47723, 47724, 49485, 49486, 51241, 51242, 53003, 53004, 54761, 54762, 56513, 56514, 58275, 58276, 60025, 60026, 61785, 61786, 63545, 63546, 65303, 65304, 67061, 67062, 68823, 68824, 70583, 70584, 72343, 72344, 74103, 74104, 75863, 75864, 77625, 77626, 79387, 79388, 81149, 81150, 82911, 82912, 84671, 84672, 86433, 86434, 88195, 88196, 89949, 89950, 91707, 91708, 93469, 93470, 95231, 95232, 96989, 96990, 98751, 98752, 100495, 100496, 102257, 102258, 104019, 104020, 105781, 105782, 107541, 107542, 109299, 109300, 111061, 111062, 112823, 112824, 114585, 114586, 116347, 116348, 118107, 118108, 119869, 119870, 121631, 121632, 123393, 123394, 125155, 125156, 126915, 126916, 128675, 128676, 130437, 130438, 132199, 132200, 133955, 133956, 135717, 135718, 137477, 137478]
off_0xF79 = [27778, 28539, 28540, 30135, 30136, 31897, 31898, 33657, 33658, 35417, 35418, 37179, 37180, 38935, 38936, 40697, 40698, 42455, 42456, 44217, 44218, 45979, 45980, 47737, 47738, 49499, 49500, 51255, 51256, 53017, 53018, 54775, 54776, 56527, 56528, 58289, 58290, 60039, 60040, 61799, 61800, 63559, 63560, 65317, 65318, 67075, 67076, 68837, 68838, 70597, 70598, 72357, 72358, 74117, 74118, 75877, 75878, 77639, 77640, 79401, 79402, 81163, 81164, 82925, 82926, 84685, 84686, 86447, 86448, 88209, 88210, 89963, 89964, 91721, 91722, 93483, 93484, 95245, 95246, 97003, 97004, 98765, 98766, 100509, 100510, 102271, 102272, 104033, 104034, 105795, 105796, 107555, 107556, 109313, 109314, 111075, 111076, 112837, 112838, 114599, 114600, 116361, 116362, 118121, 118122, 119883, 119884, 121645, 121646, 123407, 123408, 125169, 125170, 126929, 126930, 128689, 128690, 130451, 130452, 132213, 132214, 133969, 133970, 135731, 135732, 137491, 137492]
off_0xF7A = [27781, 28551, 28552, 30149, 30150, 31911, 31912, 33671, 33672, 35431, 35432, 37193, 37194, 38949, 38950, 40711, 40712, 42469, 42470, 44231, 44232, 45993, 45994, 47751, 47752, 49513, 49514, 51269, 51270, 53031, 53032, 54789, 54790, 56541, 56542, 58303, 58304, 60053, 60054, 61813, 61814, 63573, 63574, 65331, 65332, 67089, 67090, 68851, 68852, 70611, 70612, 72371, 72372, 74131, 74132, 75891, 75892, 77653, 77654, 79415, 79416, 81177, 81178, 82939, 82940, 84699, 84700, 86461, 86462, 88223, 88224, 89977, 89978, 91735, 91736, 93497, 93498, 95259, 95260, 97017, 97018, 98779, 98780, 100523, 100524, 102285, 102286, 104047, 104048, 105809, 105810, 107569, 107570, 109327, 109328, 111089, 111090, 112851, 112852, 114613, 114614, 116375, 116376, 118135, 118136, 119897, 119898, 121659, 121660, 123421, 123422, 125183, 125184, 126943, 126944, 128703, 128704, 130465, 130466, 132227, 132228, 133983, 133984, 135745, 135746, 137505, 137506]
off_0xF7B = [27784, 28563, 28564, 30163, 30164, 31925, 31926, 33685, 33686, 35445, 35446, 37207, 37208, 38963, 38964, 40725, 40726, 42483, 42484, 44245, 44246, 46007, 46008, 47765, 47766, 49527, 49528, 51283, 51284, 53045, 53046, 54803, 54804, 56555, 56556, 58317, 58318, 60067, 60068, 61827, 61828, 63587, 63588, 65345, 65346, 67103, 67104, 68865, 68866, 70625, 70626, 72385, 72386, 74145, 74146, 75905, 75906, 77667, 77668, 79429, 79430, 81191, 81192, 82953, 82954, 84713, 84714, 86475, 86476, 88237, 88238, 89991, 89992, 91749, 91750, 93511, 93512, 95273, 95274, 97031, 97032, 98793, 98794, 100537, 100538, 102299, 102300, 104061, 104062, 105823, 105824, 107583, 107584, 109341, 109342, 111103, 111104, 112865, 112866, 114627, 114628, 116389, 116390, 118149, 118150, 119911, 119912, 121673, 121674, 123435, 123436, 125197, 125198, 126957, 126958, 128717, 128718, 130479, 130480, 132241, 132242, 133997, 133998, 135759, 135760, 137519, 137520]
off_0xF7C = [27787, 28575, 28576, 30177, 30178, 31939, 31940, 33699, 33700, 35459, 35460, 37221, 37222, 38977, 38978, 40739, 40740, 42497, 42498, 44259, 44260, 46021, 46022, 47779, 47780, 49541, 49542, 51297, 51298, 53059, 53060, 54817, 54818, 56569, 56570, 58331, 58332, 60081, 60082, 61841, 61842, 63601, 63602, 65359, 65360, 67117, 67118, 68879, 68880, 70639, 70640, 72399, 72400, 74159, 74160, 75919, 75920, 77681, 77682, 79443, 79444, 81205, 81206, 82967, 82968, 84727, 84728, 86489, 86490, 88251, 88252, 90005, 90006, 91763, 91764, 93525, 93526, 95287, 95288, 97045, 97046, 98807, 98808, 100551, 100552, 102313, 102314, 104075, 104076, 105837, 105838, 107597, 107598, 109355, 109356, 111117, 111118, 112879, 112880, 114641, 114642, 116403, 116404, 118163, 118164, 119925, 119926, 121687, 121688, 123449, 123450, 125211, 125212, 126971, 126972, 128731, 128732, 130493, 130494, 132255, 132256, 134011, 134012, 135773, 135774, 137533, 137534]
off_0xF7D = [27790, 28587, 28588, 30191, 30192, 31953, 31954, 33713, 33714, 35473, 35474, 37235, 37236, 38991, 38992, 40753, 40754, 42511, 42512, 44273, 44274, 46035, 46036, 47793, 47794, 49555, 49556, 51311, 51312, 53073, 53074, 54831, 54832, 56583, 56584, 58345, 58346, 60095, 60096, 61855, 61856, 63615, 63616, 65373, 65374, 67131, 67132, 68893, 68894, 70653, 70654, 72413, 72414, 74173, 74174, 75933, 75934, 77695, 77696, 79457, 79458, 81219, 81220, 82981, 82982, 84741, 84742, 86503, 86504, 88265, 88266, 90019, 90020, 91777, 91778, 93539, 93540, 95301, 95302, 97059, 97060, 98821, 98822, 100565, 100566, 102327, 102328, 104089, 104090, 105851, 105852, 107611, 107612, 109369, 109370, 111131, 111132, 112893, 112894, 114655, 114656, 116417, 116418, 118177, 118178, 119939, 119940, 121701, 121702, 123463, 123464, 125225, 125226, 126985, 126986, 128745, 128746, 130507, 130508, 132269, 132270, 134025, 134026, 135787, 135788, 137547, 137548]
off_0xF7E = [27793, 28599, 28600, 30205, 30206, 31967, 31968, 33727, 33728, 35487, 35488, 37249, 37250, 39005, 39006, 40767, 40768, 42525, 42526, 44287, 44288, 46049, 46050, 47807, 47808, 49569, 49570, 51325, 51326, 53087, 53088, 54845, 54846, 56597, 56598, 58359, 58360, 60109, 60110, 61869, 61870, 63629, 63630, 65387, 65388, 67145, 67146, 68907, 68908, 70667, 70668, 72427, 72428, 74187, 74188, 75947, 75948, 77709, 77710, 79471, 79472, 81233, 81234, 82995, 82996, 84755, 84756, 86517, 86518, 88279, 88280, 90033, 90034, 91791, 91792, 93553, 93554, 95315, 95316, 97073, 97074, 98835, 98836, 100579, 100580, 102341, 102342, 104103, 104104, 105865, 105866, 107625, 107626, 109383, 109384, 111145, 111146, 112907, 112908, 114669, 114670, 116431, 116432, 118191, 118192, 119953, 119954, 121715, 121716, 123477, 123478, 125239, 125240, 126999, 127000, 128759, 128760, 130521, 130522, 132283, 132284, 134039, 134040, 135801, 135802, 137561, 137562]
off_0xF7F = [27796, 28611, 28612, 30219, 30220, 31981, 31982, 33741, 33742, 35501, 35502, 37263, 37264, 39019, 39020, 40781, 40782, 42539, 42540, 44301, 44302, 46063, 46064, 47821, 47822, 49583, 49584, 51339, 51340, 53101, 53102, 54859, 54860, 56611, 56612, 58373, 58374, 60123, 60124, 61883, 61884, 63643, 63644, 65401, 65402, 67159, 67160, 68921, 68922, 70681, 70682, 72441, 72442, 74201, 74202, 75961, 75962, 77723, 77724, 79485, 79486, 81247, 81248, 83009, 83010, 84769, 84770, 86531, 86532, 88293, 88294, 90047, 90048, 91805, 91806, 93567, 93568, 95329, 95330, 97087, 97088, 98849, 98850, 100593, 100594, 102355, 102356, 104117, 104118, 105879, 105880, 107639, 107640, 109397, 109398, 111159, 111160, 112921, 112922, 114683, 114684, 116445, 116446, 118205, 118206, 119967, 119968, 121729, 121730, 123491, 123492, 125253, 125254, 127013, 127014, 128773, 128774, 130535, 130536, 132297, 132298, 134053, 134054, 135815, 135816, 137575, 137576]
off_0xF80 = [27799, 28623, 28624, 30233, 30234, 31995, 31996, 33755, 33756, 35515, 35516, 37277, 37278, 39033, 39034, 40795, 40796, 42553, 42554, 44315, 44316, 46077, 46078, 47835, 47836, 49597, 49598, 51353, 51354, 53115, 53116, 54873, 54874, 56625, 56626, 58387, 58388, 60137, 60138, 61897, 61898, 63657, 63658, 65415, 65416, 67173, 67174, 68935, 68936, 70695, 70696, 72455, 72456, 74215, 74216, 75975, 75976, 77737, 77738, 79499, 79500, 81261, 81262, 83023, 83024, 84783, 84784, 86545, 86546, 88307, 88308, 90061, 90062, 91819, 91820, 93581, 93582, 95343, 95344, 97101, 97102, 98863, 98864, 100607, 100608, 102369, 102370, 104131, 104132, 105893, 105894, 107653, 107654, 109411, 109412, 111173, 111174, 112935, 112936, 114697, 114698, 116459, 116460, 118219, 118220, 119981, 119982, 121743, 121744, 123505, 123506, 125267, 125268, 127027, 127028, 128787, 128788, 130549, 130550, 132311, 132312, 134067, 134068, 135829, 135830, 137589, 137590]
off_0xF81 = [27802, 28635, 28636, 30247, 30248, 32009, 32010, 33769, 33770, 35529, 35530, 37291, 37292, 39047, 39048, 40809, 40810, 42567, 42568, 44329, 44330, 46091, 46092, 47849, 47850, 49611, 49612, 51367, 51368, 53129, 53130, 54887, 54888, 56639, 56640, 58401, 58402, 60151, 60152, 61911, 61912, 63671, 63672, 65429, 65430, 67187, 67188, 68949, 68950, 70709, 70710, 72469, 72470, 74229, 74230, 75989, 75990, 77751, 77752, 79513, 79514, 81275, 81276, 83037, 83038, 84797, 84798, 86559, 86560, 88321, 88322, 90075, 90076, 91833, 91834, 93595, 93596, 95357, 95358, 97115, 97116, 98877, 98878, 100621, 100622, 102383, 102384, 104145, 104146, 105907, 105908, 107667, 107668, 109425, 109426, 111187, 111188, 112949, 112950, 114711, 114712, 116473, 116474, 118233, 118234, 119995, 119996, 121757, 121758, 123519, 123520, 125281, 125282, 127041, 127042, 128801, 128802, 130563, 130564, 132325, 132326, 134081, 134082, 135843, 135844, 137603, 137604]
off_0xF82 = [27805, 28647, 28648, 30261, 30262, 32023, 32024, 33783, 33784, 35543, 35544, 37305, 37306, 39061, 39062, 40823, 40824, 42581, 42582, 44343, 44344, 46105, 46106, 47863, 47864, 49625, 49626, 51381, 51382, 53143, 53144, 54901, 54902, 56653, 56654, 58415, 58416, 60165, 60166, 61925, 61926, 63685, 63686, 65443, 65444, 67201, 67202, 68963, 68964, 70723, 70724, 72483, 72484, 74243, 74244, 76003, 76004, 77765, 77766, 79527, 79528, 81289, 81290, 83051, 83052, 84811, 84812, 86573, 86574, 88335, 88336, 90089, 90090, 91847, 91848, 93609, 93610, 95371, 95372, 97129, 97130, 98891, 98892, 100635, 100636, 102397, 102398, 104159, 104160, 105921, 105922, 107681, 107682, 109439, 109440, 111201, 111202, 112963, 112964, 114725, 114726, 116487, 116488, 118247, 118248, 120009, 120010, 121771, 121772, 123533, 123534, 125295, 125296, 127055, 127056, 128815, 128816, 130577, 130578, 132339, 132340, 134095, 134096, 135857, 135858, 137617, 137618]
off_0xF83 = [27808, 28659, 28660, 30275, 30276, 32037, 32038, 33797, 33798, 35557, 35558, 37319, 37320, 39075, 39076, 40837, 40838, 42595, 42596, 44357, 44358, 46119, 46120, 47877, 47878, 49639, 49640, 51395, 51396, 53157, 53158, 54915, 54916, 56667, 56668, 58429, 58430, 60179, 60180, 61939, 61940, 63699, 63700, 65457, 65458, 67215, 67216, 68977, 68978, 70737, 70738, 72497, 72498, 74257, 74258, 76017, 76018, 77779, 77780, 79541, 79542, 81303, 81304, 83065, 83066, 84825, 84826, 86587, 86588, 88349, 88350, 90103, 90104, 91861, 91862, 93623, 93624, 95385, 95386, 97143, 97144, 98905, 98906, 100649, 100650, 102411, 102412, 104173, 104174, 105935, 105936, 107695, 107696, 109453, 109454, 111215, 111216, 112977, 112978, 114739, 114740, 116501, 116502, 118261, 118262, 120023, 120024, 121785, 121786, 123547, 123548, 125309, 125310, 127069, 127070, 128829, 128830, 130591, 130592, 132353, 132354, 134109, 134110, 135871, 135872, 137631, 137632]
off_0xF84 = [27811, 28671, 28672, 30289, 30290, 32051, 32052, 33811, 33812, 35571, 35572, 37333, 37334, 39089, 39090, 40851, 40852, 42609, 42610, 44371, 44372, 46133, 46134, 47891, 47892, 49653, 49654, 51409, 51410, 53171, 53172, 54929, 54930, 56681, 56682, 58443, 58444, 60193, 60194, 61953, 61954, 63713, 63714, 65471, 65472, 67229, 67230, 68991, 68992, 70751, 70752, 72511, 72512, 74271, 74272, 76031, 76032, 77793, 77794, 79555, 79556, 81317, 81318, 83079, 83080, 84839, 84840, 86601, 86602, 88363, 88364, 90117, 90118, 91875, 91876, 93637, 93638, 95399, 95400, 97157, 97158, 98919, 98920, 100663, 100664, 102425, 102426, 104187, 104188, 105949, 105950, 107709, 107710, 109467, 109468, 111229, 111230, 112991, 112992, 114753, 114754, 116515, 116516, 118275, 118276, 120037, 120038, 121799, 121800, 123561, 123562, 125323, 125324, 127083, 127084, 128843, 128844, 130605, 130606, 132367, 132368, 134123, 134124, 135885, 135886, 137645, 137646]
off_0xF85 = [27814, 28683, 28684, 30303, 30304, 32065, 32066, 33825, 33826, 35585, 35586, 37347, 37348, 39103, 39104, 40865, 40866, 42623, 42624, 44385, 44386, 46147, 46148, 47905, 47906, 49667, 49668, 51423, 51424, 53185, 53186, 54943, 54944, 56695, 56696, 58457, 58458, 60207, 60208, 61967, 61968, 63727, 63728, 65485, 65486, 67243, 67244, 69005, 69006, 70765, 70766, 72525, 72526, 74285, 74286, 76045, 76046, 77807, 77808, 79569, 79570, 81331, 81332, 83093, 83094, 84853, 84854, 86615, 86616, 88377, 88378, 90131, 90132, 91889, 91890, 93651, 93652, 95413, 95414, 97171, 97172, 98933, 98934, 100677, 100678, 102439, 102440, 104201, 104202, 105963, 105964, 107723, 107724, 109481, 109482, 111243, 111244, 113005, 113006, 114767, 114768, 116529, 116530, 118289, 118290, 120051, 120052, 121813, 121814, 123575, 123576, 125337, 125338, 127097, 127098, 128857, 128858, 130619, 130620, 132381, 132382, 134137, 134138, 135899, 135900, 137659, 137660]
off_0xF86 = [27817, 28695, 28696, 30317, 30318, 32079, 32080, 33839, 33840, 35599, 35600, 37361, 37362, 39117, 39118, 40879, 40880, 42637, 42638, 44399, 44400, 46161, 46162, 47919, 47920, 49681, 49682, 51437, 51438, 53199, 53200, 54957, 54958, 56709, 56710, 58471, 58472, 60221, 60222, 61981, 61982, 63741, 63742, 65499, 65500, 67257, 67258, 69019, 69020, 70779, 70780, 72539, 72540, 74299, 74300, 76059, 76060, 77821, 77822, 79583, 79584, 81345, 81346, 83107, 83108, 84867, 84868, 86629, 86630, 88391, 88392, 90145, 90146, 91903, 91904, 93665, 93666, 95427, 95428, 97185, 97186, 98947, 98948, 100691, 100692, 102453, 102454, 104215, 104216, 105977, 105978, 107737, 107738, 109495, 109496, 111257, 111258, 113019, 113020, 114781, 114782, 116543, 116544, 118303, 118304, 120065, 120066, 121827, 121828, 123589, 123590, 125351, 125352, 127111, 127112, 128871, 128872, 130633, 130634, 132395, 132396, 134151, 134152, 135913, 135914, 137673, 137674]
off_0xF87 = [27820, 28707, 28708, 30331, 30332, 32093, 32094, 33853, 33854, 35613, 35614, 37375, 37376, 39131, 39132, 40893, 40894, 42651, 42652, 44413, 44414, 46175, 46176, 47933, 47934, 49695, 49696, 51451, 51452, 53213, 53214, 54971, 54972, 56723, 56724, 58485, 58486, 60235, 60236, 61995, 61996, 63755, 63756, 65513, 65514, 67271, 67272, 69033, 69034, 70793, 70794, 72553, 72554, 74313, 74314, 76073, 76074, 77835, 77836, 79597, 79598, 81359, 81360, 83121, 83122, 84881, 84882, 86643, 86644, 88405, 88406, 90159, 90160, 91917, 91918, 93679, 93680, 95441, 95442, 97199, 97200, 98961, 98962, 100705, 100706, 102467, 102468, 104229, 104230, 105991, 105992, 107751, 107752, 109509, 109510, 111271, 111272, 113033, 113034, 114795, 114796, 116557, 116558, 118317, 118318, 120079, 120080, 121841, 121842, 123603, 123604, 125365, 125366, 127125, 127126, 128885, 128886, 130647, 130648, 132409, 132410, 134165, 134166, 135927, 135928, 137687, 137688]
off_0xF88 = [27823, 28719, 28720, 30345, 30346, 32107, 32108, 33867, 33868, 35627, 35628, 37389, 37390, 39145, 39146, 40907, 40908, 42665, 42666, 44427, 44428, 46189, 46190, 47947, 47948, 49709, 49710, 51465, 51466, 53227, 53228, 54985, 54986, 56737, 56738, 58499, 58500, 60249, 60250, 62009, 62010, 63769, 63770, 65527, 65528, 67285, 67286, 69047, 69048, 70807, 70808, 72567, 72568, 74327, 74328, 76087, 76088, 77849, 77850, 79611, 79612, 81373, 81374, 83135, 83136, 84895, 84896, 86657, 86658, 88419, 88420, 90173, 90174, 91931, 91932, 93693, 93694, 95455, 95456, 97213, 97214, 98975, 98976, 100719, 100720, 102481, 102482, 104243, 104244, 106005, 106006, 107765, 107766, 109523, 109524, 111285, 111286, 113047, 113048, 114809, 114810, 116571, 116572, 118331, 118332, 120093, 120094, 121855, 121856, 123617, 123618, 125379, 125380, 127139, 127140, 128899, 128900, 130661, 130662, 132423, 132424, 134179, 134180, 135941, 135942, 137701, 137702]
off_0xF89 = [27826, 28731, 28732, 30359, 30360, 32121, 32122, 33881, 33882, 35641, 35642, 37403, 37404, 39159, 39160, 40921, 40922, 42679, 42680, 44441, 44442, 46203, 46204, 47961, 47962, 49723, 49724, 51479, 51480, 53241, 53242, 54999, 55000, 56751, 56752, 58513, 58514, 60263, 60264, 62023, 62024, 63783, 63784, 65541, 65542, 67299, 67300, 69061, 69062, 70821, 70822, 72581, 72582, 74341, 74342, 76101, 76102, 77863, 77864, 79625, 79626, 81387, 81388, 83149, 83150, 84909, 84910, 86671, 86672, 88433, 88434, 90187, 90188, 91945, 91946, 93707, 93708, 95469, 95470, 97227, 97228, 98989, 98990, 100733, 100734, 102495, 102496, 104257, 104258, 106019, 106020, 107779, 107780, 109537, 109538, 111299, 111300, 113061, 113062, 114823, 114824, 116585, 116586, 118345, 118346, 120107, 120108, 121869, 121870, 123631, 123632, 125393, 125394, 127153, 127154, 128913, 128914, 130675, 130676, 132437, 132438, 134193, 134194, 135955, 135956, 137715, 137716]
off_0xF8A = [27829, 28743, 28744, 30373, 30374, 32135, 32136, 33895, 33896, 35655, 35656, 37417, 37418, 39173, 39174, 40935, 40936, 42693, 42694, 44455, 44456, 46217, 46218, 47975, 47976, 49737, 49738, 51493, 51494, 53255, 53256, 55013, 55014, 56765, 56766, 58527, 58528, 60277, 60278, 62037, 62038, 63797, 63798, 65555, 65556, 67313, 67314, 69075, 69076, 70835, 70836, 72595, 72596, 74355, 74356, 76115, 76116, 77877, 77878, 79639, 79640, 81401, 81402, 83163, 83164, 84923, 84924, 86685, 86686, 88447, 88448, 90201, 90202, 91959, 91960, 93721, 93722, 95483, 95484, 97241, 97242, 99003, 99004, 100747, 100748, 102509, 102510, 104271, 104272, 106033, 106034, 107793, 107794, 109551, 109552, 111313, 111314, 113075, 113076, 114837, 114838, 116599, 116600, 118359, 118360, 120121, 120122, 121883, 121884, 123645, 123646, 125407, 125408, 127167, 127168, 128927, 128928, 130689, 130690, 132451, 132452, 134207, 134208, 135969, 135970, 137729, 137730]
off_0xF8B = [27832, 28755, 28756, 30387, 30388, 32149, 32150, 33909, 33910, 35669, 35670, 37431, 37432, 39187, 39188, 40949, 40950, 42707, 42708, 44469, 44470, 46231, 46232, 47989, 47990, 49751, 49752, 51507, 51508, 53269, 53270, 55027, 55028, 56779, 56780, 58541, 58542, 60291, 60292, 62051, 62052, 63811, 63812, 65569, 65570, 67327, 67328, 69089, 69090, 70849, 70850, 72609, 72610, 74369, 74370, 76129, 76130, 77891, 77892, 79653, 79654, 81415, 81416, 83177, 83178, 84937, 84938, 86699, 86700, 88461, 88462, 90215, 90216, 91973, 91974, 93735, 93736, 95497, 95498, 97255, 97256, 99017, 99018, 100761, 100762, 102523, 102524, 104285, 104286, 106047, 106048, 107807, 107808, 109565, 109566, 111327, 111328, 113089, 113090, 114851, 114852, 116613, 116614, 118373, 118374, 120135, 120136, 121897, 121898, 123659, 123660, 125421, 125422, 127181, 127182, 128941, 128942, 130703, 130704, 132465, 132466, 134221, 134222, 135983, 135984, 137743, 137744]
off_0xF8C = [27835, 28767, 28768, 30401, 30402, 32163, 32164, 33923, 33924, 35683, 35684, 37445, 37446, 39201, 39202, 40963, 40964, 42721, 42722, 44483, 44484, 46245, 46246, 48003, 48004, 49765, 49766, 51521, 51522, 53283, 53284, 55041, 55042, 56793, 56794, 58555, 58556, 60305, 60306, 62065, 62066, 63825, 63826, 65583, 65584, 67341, 67342, 69103, 69104, 70863, 70864, 72623, 72624, 74383, 74384, 76143, 76144, 77905, 77906, 79667, 79668, 81429, 81430, 83191, 83192, 84951, 84952, 86713, 86714, 88475, 88476, 90229, 90230, 91987, 91988, 93749, 93750, 95511, 95512, 97269, 97270, 99031, 99032, 100775, 100776, 102537, 102538, 104299, 104300, 106061, 106062, 107821, 107822, 109579, 109580, 111341, 111342, 113103, 113104, 114865, 114866, 116627, 116628, 118387, 118388, 120149, 120150, 121911, 121912, 123673, 123674, 125435, 125436, 127195, 127196, 128955, 128956, 130717, 130718, 132479, 132480, 134235, 134236, 135997, 135998, 137757, 137758]
off_0xF8D = [27838, 28779, 28780, 30415, 30416, 32177, 32178, 33937, 33938, 35697, 35698, 37459, 37460, 39215, 39216, 40977, 40978, 42735, 42736, 44497, 44498, 46259, 46260, 48017, 48018, 49779, 49780, 51535, 51536, 53297, 53298, 55055, 55056, 56807, 56808, 58569, 58570, 60319, 60320, 62079, 62080, 63839, 63840, 65597, 65598, 67355, 67356, 69117, 69118, 70877, 70878, 72637, 72638, 74397, 74398, 76157, 76158, 77919, 77920, 79681, 79682, 81443, 81444, 83205, 83206, 84965, 84966, 86727, 86728, 88489, 88490, 90243, 90244, 92001, 92002, 93763, 93764, 95525, 95526, 97283, 97284, 99045, 99046, 100789, 100790, 102551, 102552, 104313, 104314, 106075, 106076, 107835, 107836, 109593, 109594, 111355, 111356, 113117, 113118, 114879, 114880, 116641, 116642, 118401, 118402, 120163, 120164, 121925, 121926, 123687, 123688, 125449, 125450, 127209, 127210, 128969, 128970, 130731, 130732, 132493, 132494, 134249, 134250, 136011, 136012, 137771, 137772]
off_0xF8E = [27841, 28791, 28792, 30429, 30430, 32191, 32192, 33951, 33952, 35711, 35712, 37473, 37474, 39229, 39230, 40991, 40992, 42749, 42750, 44511, 44512, 46273, 46274, 48031, 48032, 49793, 49794, 51549, 51550, 53311, 53312, 55069, 55070, 56821, 56822, 58583, 58584, 60333, 60334, 62093, 62094, 63853, 63854, 65611, 65612, 67369, 67370, 69131, 69132, 70891, 70892, 72651, 72652, 74411, 74412, 76171, 76172, 77933, 77934, 79695, 79696, 81457, 81458, 83219, 83220, 84979, 84980, 86741, 86742, 88503, 88504, 90257, 90258, 92015, 92016, 93777, 93778, 95539, 95540, 97297, 97298, 99059, 99060, 100803, 100804, 102565, 102566, 104327, 104328, 106089, 106090, 107849, 107850, 109607, 109608, 111369, 111370, 113131, 113132, 114893, 114894, 116655, 116656, 118415, 118416, 120177, 120178, 121939, 121940, 123701, 123702, 125463, 125464, 127223, 127224, 128983, 128984, 130745, 130746, 132507, 132508, 134263, 134264, 136025, 136026, 137785, 137786]
off_0xF8F = [27844, 28803, 28804, 30443, 30444, 32205, 32206, 33965, 33966, 35725, 35726, 37487, 37488, 39243, 39244, 41005, 41006, 42763, 42764, 44525, 44526, 46287, 46288, 48045, 48046, 49807, 49808, 51563, 51564, 53325, 53326, 55083, 55084, 56835, 56836, 58597, 58598, 60347, 60348, 62107, 62108, 63867, 63868, 65625, 65626, 67383, 67384, 69145, 69146, 70905, 70906, 72665, 72666, 74425, 74426, 76185, 76186, 77947, 77948, 79709, 79710, 81471, 81472, 83233, 83234, 84993, 84994, 86755, 86756, 88517, 88518, 90271, 90272, 92029, 92030, 93791, 93792, 95553, 95554, 97311, 97312, 99073, 99074, 100817, 100818, 102579, 102580, 104341, 104342, 106103, 106104, 107863, 107864, 109621, 109622, 111383, 111384, 113145, 113146, 114907, 114908, 116669, 116670, 118429, 118430, 120191, 120192, 121953, 121954, 123715, 123716, 125477, 125478, 127237, 127238, 128997, 128998, 130759, 130760, 132521, 132522, 134277, 134278, 136039, 136040, 137799, 137800]
off_0xF90 = [27847, 28815, 28816, 30457, 30458, 32219, 32220, 33979, 33980, 35739, 35740, 37501, 37502, 39257, 39258, 41019, 41020, 42777, 42778, 44539, 44540, 46301, 46302, 48059, 48060, 49821, 49822, 51577, 51578, 53339, 53340, 55097, 55098, 56849, 56850, 58611, 58612, 60361, 60362, 62121, 62122, 63881, 63882, 65639, 65640, 67397, 67398, 69159, 69160, 70919, 70920, 72679, 72680, 74439, 74440, 76199, 76200, 77961, 77962, 79723, 79724, 81485, 81486, 83247, 83248, 85007, 85008, 86769, 86770, 88531, 88532, 90285, 90286, 92043, 92044, 93805, 93806, 95567, 95568, 97325, 97326, 99087, 99088, 100831, 100832, 102593, 102594, 104355, 104356, 106117, 106118, 107877, 107878, 109635, 109636, 111397, 111398, 113159, 113160, 114921, 114922, 116683, 116684, 118443, 118444, 120205, 120206, 121967, 121968, 123729, 123730, 125491, 125492, 127251, 127252, 129011, 129012, 130773, 130774, 132535, 132536, 134291, 134292, 136053, 136054, 137813, 137814]
off_0xF91 = [27850, 28827, 28828, 30471, 30472, 32233, 32234, 33993, 33994, 35753, 35754, 37515, 37516, 39271, 39272, 41033, 41034, 42791, 42792, 44553, 44554, 46315, 46316, 48073, 48074, 49835, 49836, 51591, 51592, 53353, 53354, 55111, 55112, 56863, 56864, 58625, 58626, 60375, 60376, 62135, 62136, 63895, 63896, 65653, 65654, 67411, 67412, 69173, 69174, 70933, 70934, 72693, 72694, 74453, 74454, 76213, 76214, 77975, 77976, 79737, 79738, 81499, 81500, 83261, 83262, 85021, 85022, 86783, 86784, 88545, 88546, 90299, 90300, 92057, 92058, 93819, 93820, 95581, 95582, 97339, 97340, 99101, 99102, 100845, 100846, 102607, 102608, 104369, 104370, 106131, 106132, 107891, 107892, 109649, 109650, 111411, 111412, 113173, 113174, 114935, 114936, 116697, 116698, 118457, 118458, 120219, 120220, 121981, 121982, 123743, 123744, 125505, 125506, 127265, 127266, 129025, 129026, 130787, 130788, 132549, 132550, 134305, 134306, 136067, 136068, 137827, 137828]
off_0xF92 = [27853, 28839, 28840, 30485, 30486, 32247, 32248, 34007, 34008, 35767, 35768, 37529, 37530, 39285, 39286, 41047, 41048, 42805, 42806, 44567, 44568, 46329, 46330, 48087, 48088, 49849, 49850, 51605, 51606, 53367, 53368, 55125, 55126, 56877, 56878, 58639, 58640, 60389, 60390, 62149, 62150, 63909, 63910, 65667, 65668, 67425, 67426, 69187, 69188, 70947, 70948, 72707, 72708, 74467, 74468, 76227, 76228, 77989, 77990, 79751, 79752, 81513, 81514, 83275, 83276, 85035, 85036, 86797, 86798, 88559, 88560, 90313, 90314, 92071, 92072, 93833, 93834, 95595, 95596, 97353, 97354, 99115, 99116, 100859, 100860, 102621, 102622, 104383, 104384, 106145, 106146, 107905, 107906, 109663, 109664, 111425, 111426, 113187, 113188, 114949, 114950, 116711, 116712, 118471, 118472, 120233, 120234, 121995, 121996, 123757, 123758, 125519, 125520, 127279, 127280, 129039, 129040, 130801, 130802, 132563, 132564, 134319, 134320, 136081, 136082, 137841, 137842]
off_0xF93 = [27856, 28851, 28852, 30499, 30500, 32261, 32262, 34021, 34022, 35781, 35782, 37543, 37544, 39299, 39300, 41061, 41062, 42819, 42820, 44581, 44582, 46343, 46344, 48101, 48102, 49863, 49864, 51619, 51620, 53381, 53382, 55139, 55140, 56891, 56892, 58653, 58654, 60403, 60404, 62163, 62164, 63923, 63924, 65681, 65682, 67439, 67440, 69201, 69202, 70961, 70962, 72721, 72722, 74481, 74482, 76241, 76242, 78003, 78004, 79765, 79766, 81527, 81528, 83289, 83290, 85049, 85050, 86811, 86812, 88573, 88574, 90327, 90328, 92085, 92086, 93847, 93848, 95609, 95610, 97367, 97368, 99129, 99130, 100873, 100874, 102635, 102636, 104397, 104398, 106159, 106160, 107919, 107920, 109677, 109678, 111439, 111440, 113201, 113202, 114963, 114964, 116725, 116726, 118485, 118486, 120247, 120248, 122009, 122010, 123771, 123772, 125533, 125534, 127293, 127294, 129053, 129054, 130815, 130816, 132577, 132578, 134333, 134334, 136095, 136096, 137855, 137856]
off_0xF94 = [27859, 28863, 28864, 30513, 30514, 32275, 32276, 34035, 34036, 35795, 35796, 37557, 37558, 39313, 39314, 41075, 41076, 42833, 42834, 44595, 44596, 46357, 46358, 48115, 48116, 49877, 49878, 51633, 51634, 53395, 53396, 55153, 55154, 56905, 56906, 58667, 58668, 60417, 60418, 62177, 62178, 63937, 63938, 65695, 65696, 67453, 67454, 69215, 69216, 70975, 70976, 72735, 72736, 74495, 74496, 76255, 76256, 78017, 78018, 79779, 79780, 81541, 81542, 83303, 83304, 85063, 85064, 86825, 86826, 88587, 88588, 90341, 90342, 92099, 92100, 93861, 93862, 95623, 95624, 97381, 97382, 99143, 99144, 100887, 100888, 102649, 102650, 104411, 104412, 106173, 106174, 107933, 107934, 109691, 109692, 111453, 111454, 113215, 113216, 114977, 114978, 116739, 116740, 118499, 118500, 120261, 120262, 122023, 122024, 123785, 123786, 125547, 125548, 127307, 127308, 129067, 129068, 130829, 130830, 132591, 132592, 134347, 134348, 136109, 136110, 137869, 137870]
off_0xF95 = [27862, 28875, 28876, 30527, 30528, 32289, 32290, 34049, 34050, 35809, 35810, 37571, 37572, 39327, 39328, 41089, 41090, 42847, 42848, 44609, 44610, 46371, 46372, 48129, 48130, 49891, 49892, 51647, 51648, 53409, 53410, 55167, 55168, 56919, 56920, 58681, 58682, 60431, 60432, 62191, 62192, 63951, 63952, 65709, 65710, 67467, 67468, 69229, 69230, 70989, 70990, 72749, 72750, 74509, 74510, 76269, 76270, 78031, 78032, 79793, 79794, 81555, 81556, 83317, 83318, 85077, 85078, 86839, 86840, 88601, 88602, 90355, 90356, 92113, 92114, 93875, 93876, 95637, 95638, 97395, 97396, 99157, 99158, 100901, 100902, 102663, 102664, 104425, 104426, 106187, 106188, 107947, 107948, 109705, 109706, 111467, 111468, 113229, 113230, 114991, 114992, 116753, 116754, 118513, 118514, 120275, 120276, 122037, 122038, 123799, 123800, 125561, 125562, 127321, 127322, 129081, 129082, 130843, 130844, 132605, 132606, 134361, 134362, 136123, 136124, 137883, 137884]
off_0xF96 = [27865, 28887, 28888, 30541, 30542, 32303, 32304, 34063, 34064, 35823, 35824, 37585, 37586, 39341, 39342, 41103, 41104, 42861, 42862, 44623, 44624, 46385, 46386, 48143, 48144, 49905, 49906, 51661, 51662, 53423, 53424, 55181, 55182, 56933, 56934, 58695, 58696, 60445, 60446, 62205, 62206, 63965, 63966, 65723, 65724, 67481, 67482, 69243, 69244, 71003, 71004, 72763, 72764, 74523, 74524, 76283, 76284, 78045, 78046, 79807, 79808, 81569, 81570, 83331, 83332, 85091, 85092, 86853, 86854, 88615, 88616, 90369, 90370, 92127, 92128, 93889, 93890, 95651, 95652, 97409, 97410, 99171, 99172, 100915, 100916, 102677, 102678, 104439, 104440, 106201, 106202, 107961, 107962, 109719, 109720, 111481, 111482, 113243, 113244, 115005, 115006, 116767, 116768, 118527, 118528, 120289, 120290, 122051, 122052, 123813, 123814, 125575, 125576, 127335, 127336, 129095, 129096, 130857, 130858, 132619, 132620, 134375, 134376, 136137, 136138, 137897, 137898]
off_0xF97 = [27868, 28899, 28900, 30555, 30556, 32317, 32318, 34077, 34078, 35837, 35838, 37599, 37600, 39355, 39356, 41117, 41118, 42875, 42876, 44637, 44638, 46399, 46400, 48157, 48158, 49919, 49920, 51675, 51676, 53437, 53438, 55195, 55196, 56947, 56948, 58709, 58710, 60459, 60460, 62219, 62220, 63979, 63980, 65737, 65738, 67495, 67496, 69257, 69258, 71017, 71018, 72777, 72778, 74537, 74538, 76297, 76298, 78059, 78060, 79821, 79822, 81583, 81584, 83345, 83346, 85105, 85106, 86867, 86868, 88629, 88630, 90383, 90384, 92141, 92142, 93903, 93904, 95665, 95666, 97423, 97424, 99185, 99186, 100929, 100930, 102691, 102692, 104453, 104454, 106215, 106216, 107975, 107976, 109733, 109734, 111495, 111496, 113257, 113258, 115019, 115020, 116781, 116782, 118541, 118542, 120303, 120304, 122065, 122066, 123827, 123828, 125589, 125590, 127349, 127350, 129109, 129110, 130871, 130872, 132633, 132634, 134389, 134390, 136151, 136152, 137911, 137912]
off_0xF98 = [27871, 28911, 28912, 30569, 30570, 32331, 32332, 34091, 34092, 35851, 35852, 37613, 37614, 39369, 39370, 41131, 41132, 42889, 42890, 44651, 44652, 46413, 46414, 48171, 48172, 49933, 49934, 51689, 51690, 53451, 53452, 55209, 55210, 56961, 56962, 58723, 58724, 60473, 60474, 62233, 62234, 63993, 63994, 65751, 65752, 67509, 67510, 69271, 69272, 71031, 71032, 72791, 72792, 74551, 74552, 76311, 76312, 78073, 78074, 79835, 79836, 81597, 81598, 83359, 83360, 85119, 85120, 86881, 86882, 88643, 88644, 90397, 90398, 92155, 92156, 93917, 93918, 95679, 95680, 97437, 97438, 99199, 99200, 100943, 100944, 102705, 102706, 104467, 104468, 106229, 106230, 107989, 107990, 109747, 109748, 111509, 111510, 113271, 113272, 115033, 115034, 116795, 116796, 118555, 118556, 120317, 120318, 122079, 122080, 123841, 123842, 125603, 125604, 127363, 127364, 129123, 129124, 130885, 130886, 132647, 132648, 134403, 134404, 136165, 136166, 137925, 137926]
off_0xF99 = [27874, 28923, 28924, 30583, 30584, 32345, 32346, 34105, 34106, 35865, 35866, 37627, 37628, 39383, 39384, 41145, 41146, 42903, 42904, 44665, 44666, 46427, 46428, 48185, 48186, 49947, 49948, 51703, 51704, 53465, 53466, 55223, 55224, 56975, 56976, 58737, 58738, 60487, 60488, 62247, 62248, 64007, 64008, 65765, 65766, 67523, 67524, 69285, 69286, 71045, 71046, 72805, 72806, 74565, 74566, 76325, 76326, 78087, 78088, 79849, 79850, 81611, 81612, 83373, 83374, 85133, 85134, 86895, 86896, 88657, 88658, 90411, 90412, 92169, 92170, 93931, 93932, 95693, 95694, 97451, 97452, 99213, 99214, 100957, 100958, 102719, 102720, 104481, 104482, 106243, 106244, 108003, 108004, 109761, 109762, 111523, 111524, 113285, 113286, 115047, 115048, 116809, 116810, 118569, 118570, 120331, 120332, 122093, 122094, 123855, 123856, 125617, 125618, 127377, 127378, 129137, 129138, 130899, 130900, 132661, 132662, 134417, 134418, 136179, 136180, 137939, 137940]
off_0xF9A = [27877, 28935, 28936, 30597, 30598, 32359, 32360, 34119, 34120, 35879, 35880, 37641, 37642, 39397, 39398, 41159, 41160, 42917, 42918, 44679, 44680, 46441, 46442, 48199, 48200, 49961, 49962, 51717, 51718, 53479, 53480, 55237, 55238, 56989, 56990, 58751, 58752, 60501, 60502, 62261, 62262, 64021, 64022, 65779, 65780, 67537, 67538, 69299, 69300, 71059, 71060, 72819, 72820, 74579, 74580, 76339, 76340, 78101, 78102, 79863, 79864, 81625, 81626, 83387, 83388, 85147, 85148, 86909, 86910, 88671, 88672, 90425, 90426, 92183, 92184, 93945, 93946, 95707, 95708, 97465, 97466, 99227, 99228, 100971, 100972, 102733, 102734, 104495, 104496, 106257, 106258, 108017, 108018, 109775, 109776, 111537, 111538, 113299, 113300, 115061, 115062, 116823, 116824, 118583, 118584, 120345, 120346, 122107, 122108, 123869, 123870, 125631, 125632, 127391, 127392, 129151, 129152, 130913, 130914, 132675, 132676, 134431, 134432, 136193, 136194, 137953, 137954]
off_0xF9B = [27880, 28947, 28948, 30611, 30612, 32373, 32374, 34133, 34134, 35893, 35894, 37655, 37656, 39411, 39412, 41173, 41174, 42931, 42932, 44693, 44694, 46455, 46456, 48213, 48214, 49975, 49976, 51731, 51732, 53493, 53494, 55251, 55252, 57003, 57004, 58765, 58766, 60515, 60516, 62275, 62276, 64035, 64036, 65793, 65794, 67551, 67552, 69313, 69314, 71073, 71074, 72833, 72834, 74593, 74594, 76353, 76354, 78115, 78116, 79877, 79878, 81639, 81640, 83401, 83402, 85161, 85162, 86923, 86924, 88685, 88686, 90439, 90440, 92197, 92198, 93959, 93960, 95721, 95722, 97479, 97480, 99241, 99242, 100985, 100986, 102747, 102748, 104509, 104510, 106271, 106272, 108031, 108032, 109789, 109790, 111551, 111552, 113313, 113314, 115075, 115076, 116837, 116838, 118597, 118598, 120359, 120360, 122121, 122122, 123883, 123884, 125645, 125646, 127405, 127406, 129165, 129166, 130927, 130928, 132689, 132690, 134445, 134446, 136207, 136208, 137967, 137968]
off_0xF9C = [27883, 28959, 28960, 30625, 30626, 32387, 32388, 34147, 34148, 35907, 35908, 37669, 37670, 39425, 39426, 41187, 41188, 42945, 42946, 44707, 44708, 46469, 46470, 48227, 48228, 49989, 49990, 51745, 51746, 53507, 53508, 55265, 55266, 57017, 57018, 58779, 58780, 60529, 60530, 62289, 62290, 64049, 64050, 65807, 65808, 67565, 67566, 69327, 69328, 71087, 71088, 72847, 72848, 74607, 74608, 76367, 76368, 78129, 78130, 79891, 79892, 81653, 81654, 83415, 83416, 85175, 85176, 86937, 86938, 88699, 88700, 90453, 90454, 92211, 92212, 93973, 93974, 95735, 95736, 97493, 97494, 99255, 99256, 100999, 101000, 102761, 102762, 104523, 104524, 106285, 106286, 108045, 108046, 109803, 109804, 111565, 111566, 113327, 113328, 115089, 115090, 116851, 116852, 118611, 118612, 120373, 120374, 122135, 122136, 123897, 123898, 125659, 125660, 127419, 127420, 129179, 129180, 130941, 130942, 132703, 132704, 134459, 134460, 136221, 136222, 137981, 137982]
off_0xF9D = [27886, 28971, 28972, 30639, 30640, 32401, 32402, 34161, 34162, 35921, 35922, 37683, 37684, 39439, 39440, 41201, 41202, 42959, 42960, 44721, 44722, 46483, 46484, 48241, 48242, 50003, 50004, 51759, 51760, 53521, 53522, 55279, 55280, 57031, 57032, 58793, 58794, 60543, 60544, 62303, 62304, 64063, 64064, 65821, 65822, 67579, 67580, 69341, 69342, 71101, 71102, 72861, 72862, 74621, 74622, 76381, 76382, 78143, 78144, 79905, 79906, 81667, 81668, 83429, 83430, 85189, 85190, 86951, 86952, 88713, 88714, 90467, 90468, 92225, 92226, 93987, 93988, 95749, 95750, 97507, 97508, 99269, 99270, 101013, 101014, 102775, 102776, 104537, 104538, 106299, 106300, 108059, 108060, 109817, 109818, 111579, 111580, 113341, 113342, 115103, 115104, 116865, 116866, 118625, 118626, 120387, 120388, 122149, 122150, 123911, 123912, 125673, 125674, 127433, 127434, 129193, 129194, 130955, 130956, 132717, 132718, 134473, 134474, 136235, 136236, 137995, 137996]
off_0xF9E = [27889, 28983, 28984, 30653, 30654, 32415, 32416, 34175, 34176, 35935, 35936, 37697, 37698, 39453, 39454, 41215, 41216, 42973, 42974, 44735, 44736, 46497, 46498, 48255, 48256, 50017, 50018, 51773, 51774, 53535, 53536, 55293, 55294, 57045, 57046, 58807, 58808, 60557, 60558, 62317, 62318, 64077, 64078, 65835, 65836, 67593, 67594, 69355, 69356, 71115, 71116, 72875, 72876, 74635, 74636, 76395, 76396, 78157, 78158, 79919, 79920, 81681, 81682, 83443, 83444, 85203, 85204, 86965, 86966, 88727, 88728, 90481, 90482, 92239, 92240, 94001, 94002, 95763, 95764, 97521, 97522, 99283, 99284, 101027, 101028, 102789, 102790, 104551, 104552, 106313, 106314, 108073, 108074, 109831, 109832, 111593, 111594, 113355, 113356, 115117, 115118, 116879, 116880, 118639, 118640, 120401, 120402, 122163, 122164, 123925, 123926, 125687, 125688, 127447, 127448, 129207, 129208, 130969, 130970, 132731, 132732, 134487, 134488, 136249, 136250, 138009, 138010]
off_0xF9F = [27892, 28995, 28996, 30667, 30668, 32429, 32430, 34189, 34190, 35949, 35950, 37711, 37712, 39467, 39468, 41229, 41230, 42987, 42988, 44749, 44750, 46511, 46512, 48269, 48270, 50031, 50032, 51787, 51788, 53549, 53550, 55307, 55308, 57059, 57060, 58821, 58822, 60571, 60572, 62331, 62332, 64091, 64092, 65849, 65850, 67607, 67608, 69369, 69370, 71129, 71130, 72889, 72890, 74649, 74650, 76409, 76410, 78171, 78172, 79933, 79934, 81695, 81696, 83457, 83458, 85217, 85218, 86979, 86980, 88741, 88742, 90495, 90496, 92253, 92254, 94015, 94016, 95777, 95778, 97535, 97536, 99297, 99298, 101041, 101042, 102803, 102804, 104565, 104566, 106327, 106328, 108087, 108088, 109845, 109846, 111607, 111608, 113369, 113370, 115131, 115132, 116893, 116894, 118653, 118654, 120415, 120416, 122177, 122178, 123939, 123940, 125701, 125702, 127461, 127462, 129221, 129222, 130983, 130984, 132745, 132746, 134501, 134502, 136263, 136264, 138023, 138024]
off_0xFA0 = [27895, 29007, 29008, 30681, 30682, 32443, 32444, 34203, 34204, 35963, 35964, 37725, 37726, 39481, 39482, 41243, 41244, 43001, 43002, 44763, 44764, 46525, 46526, 48283, 48284, 50045, 50046, 51801, 51802, 53563, 53564, 55321, 55322, 57073, 57074, 58835, 58836, 60585, 60586, 62345, 62346, 64105, 64106, 65863, 65864, 67621, 67622, 69383, 69384, 71143, 71144, 72903, 72904, 74663, 74664, 76423, 76424, 78185, 78186, 79947, 79948, 81709, 81710, 83471, 83472, 85231, 85232, 86993, 86994, 88755, 88756, 90509, 90510, 92267, 92268, 94029, 94030, 95791, 95792, 97549, 97550, 99311, 99312, 101055, 101056, 102817, 102818, 104579, 104580, 106341, 106342, 108101, 108102, 109859, 109860, 111621, 111622, 113383, 113384, 115145, 115146, 116907, 116908, 118667, 118668, 120429, 120430, 122191, 122192, 123953, 123954, 125715, 125716, 127475, 127476, 129235, 129236, 130997, 130998, 132759, 132760, 134515, 134516, 136277, 136278, 138037, 138038]
off_0xFA1 = [27898, 29019, 29020, 30695, 30696, 32457, 32458, 34217, 34218, 35977, 35978, 37739, 37740, 39495, 39496, 41257, 41258, 43015, 43016, 44777, 44778, 46539, 46540, 48297, 48298, 50059, 50060, 51815, 51816, 53577, 53578, 55335, 55336, 57087, 57088, 58849, 58850, 60599, 60600, 62359, 62360, 64119, 64120, 65877, 65878, 67635, 67636, 69397, 69398, 71157, 71158, 72917, 72918, 74677, 74678, 76437, 76438, 78199, 78200, 79961, 79962, 81723, 81724, 83485, 83486, 85245, 85246, 87007, 87008, 88769, 88770, 90523, 90524, 92281, 92282, 94043, 94044, 95805, 95806, 97563, 97564, 99325, 99326, 101069, 101070, 102831, 102832, 104593, 104594, 106355, 106356, 108115, 108116, 109873, 109874, 111635, 111636, 113397, 113398, 115159, 115160, 116921, 116922, 118681, 118682, 120443, 120444, 122205, 122206, 123967, 123968, 125729, 125730, 127489, 127490, 129249, 129250, 131011, 131012, 132773, 132774, 134529, 134530, 136291, 136292, 138051, 138052]
off_0xFA2 = [27901, 29031, 29032, 30709, 30710, 32471, 32472, 34231, 34232, 35991, 35992, 37753, 37754, 39509, 39510, 41271, 41272, 43029, 43030, 44791, 44792, 46553, 46554, 48311, 48312, 50073, 50074, 51829, 51830, 53591, 53592, 55349, 55350, 57101, 57102, 58863, 58864, 60613, 60614, 62373, 62374, 64133, 64134, 65891, 65892, 67649, 67650, 69411, 69412, 71171, 71172, 72931, 72932, 74691, 74692, 76451, 76452, 78213, 78214, 79975, 79976, 81737, 81738, 83499, 83500, 85259, 85260, 87021, 87022, 88783, 88784, 90537, 90538, 92295, 92296, 94057, 94058, 95819, 95820, 97577, 97578, 99339, 99340, 101083, 101084, 102845, 102846, 104607, 104608, 106369, 106370, 108129, 108130, 109887, 109888, 111649, 111650, 113411, 113412, 115173, 115174, 116935, 116936, 118695, 118696, 120457, 120458, 122219, 122220, 123981, 123982, 125743, 125744, 127503, 127504, 129263, 129264, 131025, 131026, 132787, 132788, 134543, 134544, 136305, 136306, 138065, 138066]
off_0xFA3 = [27904, 29043, 29044, 30723, 30724, 32485, 32486, 34245, 34246, 36005, 36006, 37767, 37768, 39523, 39524, 41285, 41286, 43043, 43044, 44805, 44806, 46567, 46568, 48325, 48326, 50087, 50088, 51843, 51844, 53605, 53606, 55363, 55364, 57115, 57116, 58877, 58878, 60627, 60628, 62387, 62388, 64147, 64148, 65905, 65906, 67663, 67664, 69425, 69426, 71185, 71186, 72945, 72946, 74705, 74706, 76465, 76466, 78227, 78228, 79989, 79990, 81751, 81752, 83513, 83514, 85273, 85274, 87035, 87036, 88797, 88798, 90551, 90552, 92309, 92310, 94071, 94072, 95833, 95834, 97591, 97592, 99353, 99354, 101097, 101098, 102859, 102860, 104621, 104622, 106383, 106384, 108143, 108144, 109901, 109902, 111663, 111664, 113425, 113426, 115187, 115188, 116949, 116950, 118709, 118710, 120471, 120472, 122233, 122234, 123995, 123996, 125757, 125758, 127517, 127518, 129277, 129278, 131039, 131040, 132801, 132802, 134557, 134558, 136319, 136320, 138079, 138080]
off_0xFA4 = [27907, 29055, 29056, 30737, 30738, 32499, 32500, 34259, 34260, 36019, 36020, 37781, 37782, 39537, 39538, 41299, 41300, 43057, 43058, 44819, 44820, 46581, 46582, 48339, 48340, 50101, 50102, 51857, 51858, 53619, 53620, 55377, 55378, 57129, 57130, 58891, 58892, 60641, 60642, 62401, 62402, 64161, 64162, 65919, 65920, 67677, 67678, 69439, 69440, 71199, 71200, 72959, 72960, 74719, 74720, 76479, 76480, 78241, 78242, 80003, 80004, 81765, 81766, 83527, 83528, 85287, 85288, 87049, 87050, 88811, 88812, 90565, 90566, 92323, 92324, 94085, 94086, 95847, 95848, 97605, 97606, 99367, 99368, 101111, 101112, 102873, 102874, 104635, 104636, 106397, 106398, 108157, 108158, 109915, 109916, 111677, 111678, 113439, 113440, 115201, 115202, 116963, 116964, 118723, 118724, 120485, 120486, 122247, 122248, 124009, 124010, 125771, 125772, 127531, 127532, 129291, 129292, 131053, 131054, 132815, 132816, 134571, 134572, 136333, 136334, 138093, 138094]
off_0xFA5 = [27910, 29067, 29068, 30751, 30752, 32513, 32514, 34273, 34274, 36033, 36034, 37795, 37796, 39551, 39552, 41313, 41314, 43071, 43072, 44833, 44834, 46595, 46596, 48353, 48354, 50115, 50116, 51871, 51872, 53633, 53634, 55391, 55392, 57143, 57144, 58905, 58906, 60655, 60656, 62415, 62416, 64175, 64176, 65933, 65934, 67691, 67692, 69453, 69454, 71213, 71214, 72973, 72974, 74733, 74734, 76493, 76494, 78255, 78256, 80017, 80018, 81779, 81780, 83541, 83542, 85301, 85302, 87063, 87064, 88825, 88826, 90579, 90580, 92337, 92338, 94099, 94100, 95861, 95862, 97619, 97620, 99381, 99382, 101125, 101126, 102887, 102888, 104649, 104650, 106411, 106412, 108171, 108172, 109929, 109930, 111691, 111692, 113453, 113454, 115215, 115216, 116977, 116978, 118737, 118738, 120499, 120500, 122261, 122262, 124023, 124024, 125785, 125786, 127545, 127546, 129305, 129306, 131067, 131068, 132829, 132830, 134585, 134586, 136347, 136348, 138107, 138108]
off_0xFA6 = [27913, 29079, 29080, 30765, 30766, 32527, 32528, 34287, 34288, 36047, 36048, 37809, 37810, 39565, 39566, 41327, 41328, 43085, 43086, 44847, 44848, 46609, 46610, 48367, 48368, 50129, 50130, 51885, 51886, 53647, 53648, 55405, 55406, 57157, 57158, 58919, 58920, 60669, 60670, 62429, 62430, 64189, 64190, 65947, 65948, 67705, 67706, 69467, 69468, 71227, 71228, 72987, 72988, 74747, 74748, 76507, 76508, 78269, 78270, 80031, 80032, 81793, 81794, 83555, 83556, 85315, 85316, 87077, 87078, 88839, 88840, 90593, 90594, 92351, 92352, 94113, 94114, 95875, 95876, 97633, 97634, 99395, 99396, 101139, 101140, 102901, 102902, 104663, 104664, 106425, 106426, 108185, 108186, 109943, 109944, 111705, 111706, 113467, 113468, 115229, 115230, 116991, 116992, 118751, 118752, 120513, 120514, 122275, 122276, 124037, 124038, 125799, 125800, 127559, 127560, 129319, 129320, 131081, 131082, 132843, 132844, 134599, 134600, 136361, 136362, 138121, 138122]
off_0xFA7 = [27916, 29091, 29092, 30779, 30780, 32541, 32542, 34301, 34302, 36061, 36062, 37823, 37824, 39579, 39580, 41341, 41342, 43099, 43100, 44861, 44862, 46623, 46624, 48381, 48382, 50143, 50144, 51899, 51900, 53661, 53662, 55419, 55420, 57171, 57172, 58933, 58934, 60683, 60684, 62443, 62444, 64203, 64204, 65961, 65962, 67719, 67720, 69481, 69482, 71241, 71242, 73001, 73002, 74761, 74762, 76521, 76522, 78283, 78284, 80045, 80046, 81807, 81808, 83569, 83570, 85329, 85330, 87091, 87092, 88853, 88854, 90607, 90608, 92365, 92366, 94127, 94128, 95889, 95890, 97647, 97648, 99409, 99410, 101153, 101154, 102915, 102916, 104677, 104678, 106439, 106440, 108199, 108200, 109957, 109958, 111719, 111720, 113481, 113482, 115243, 115244, 117005, 117006, 118765, 118766, 120527, 120528, 122289, 122290, 124051, 124052, 125813, 125814, 127573, 127574, 129333, 129334, 131095, 131096, 132857, 132858, 134613, 134614, 136375, 136376, 138135, 138136]
off_0xFA8 = [27919, 29103, 29104, 30793, 30794, 32555, 32556, 34315, 34316, 36075, 36076, 37837, 37838, 39593, 39594, 41355, 41356, 43113, 43114, 44875, 44876, 46637, 46638, 48395, 48396, 50157, 50158, 51913, 51914, 53675, 53676, 55433, 55434, 57185, 57186, 58947, 58948, 60697, 60698, 62457, 62458, 64217, 64218, 65975, 65976, 67733, 67734, 69495, 69496, 71255, 71256, 73015, 73016, 74775, 74776, 76535, 76536, 78297, 78298, 80059, 80060, 81821, 81822, 83583, 83584, 85343, 85344, 87105, 87106, 88867, 88868, 90621, 90622, 92379, 92380, 94141, 94142, 95903, 95904, 97661, 97662, 99423, 99424, 101167, 101168, 102929, 102930, 104691, 104692, 106453, 106454, 108213, 108214, 109971, 109972, 111733, 111734, 113495, 113496, 115257, 115258, 117019, 117020, 118779, 118780, 120541, 120542, 122303, 122304, 124065, 124066, 125827, 125828, 127587, 127588, 129347, 129348, 131109, 131110, 132871, 132872, 134627, 134628, 136389, 136390, 138149, 138150]
off_0xFA9 = [27922, 29115, 29116, 30807, 30808, 32569, 32570, 34329, 34330, 36089, 36090, 37851, 37852, 39607, 39608, 41369, 41370, 43127, 43128, 44889, 44890, 46651, 46652, 48409, 48410, 50171, 50172, 51927, 51928, 53689, 53690, 55447, 55448, 57199, 57200, 58961, 58962, 60711, 60712, 62471, 62472, 64231, 64232, 65989, 65990, 67747, 67748, 69509, 69510, 71269, 71270, 73029, 73030, 74789, 74790, 76549, 76550, 78311, 78312, 80073, 80074, 81835, 81836, 83597, 83598, 85357, 85358, 87119, 87120, 88881, 88882, 90635, 90636, 92393, 92394, 94155, 94156, 95917, 95918, 97675, 97676, 99437, 99438, 101181, 101182, 102943, 102944, 104705, 104706, 106467, 106468, 108227, 108228, 109985, 109986, 111747, 111748, 113509, 113510, 115271, 115272, 117033, 117034, 118793, 118794, 120555, 120556, 122317, 122318, 124079, 124080, 125841, 125842, 127601, 127602, 129361, 129362, 131123, 131124, 132885, 132886, 134641, 134642, 136403, 136404, 138163, 138164]
off_0xFAA = [27925, 29127, 29128, 30821, 30822, 32583, 32584, 34343, 34344, 36103, 36104, 37865, 37866, 39621, 39622, 41383, 41384, 43141, 43142, 44903, 44904, 46665, 46666, 48423, 48424, 50185, 50186, 51941, 51942, 53703, 53704, 55461, 55462, 57213, 57214, 58975, 58976, 60725, 60726, 62485, 62486, 64245, 64246, 66003, 66004, 67761, 67762, 69523, 69524, 71283, 71284, 73043, 73044, 74803, 74804, 76563, 76564, 78325, 78326, 80087, 80088, 81849, 81850, 83611, 83612, 85371, 85372, 87133, 87134, 88895, 88896, 90649, 90650, 92407, 92408, 94169, 94170, 95931, 95932, 97689, 97690, 99451, 99452, 101195, 101196, 102957, 102958, 104719, 104720, 106481, 106482, 108241, 108242, 109999, 110000, 111761, 111762, 113523, 113524, 115285, 115286, 117047, 117048, 118807, 118808, 120569, 120570, 122331, 122332, 124093, 124094, 125855, 125856, 127615, 127616, 129375, 129376, 131137, 131138, 132899, 132900, 134655, 134656, 136417, 136418, 138177, 138178]
off_0xFAB = [27928, 29139, 29140, 30835, 30836, 32597, 32598, 34357, 34358, 36117, 36118, 37879, 37880, 39635, 39636, 41397, 41398, 43155, 43156, 44917, 44918, 46679, 46680, 48437, 48438, 50199, 50200, 51955, 51956, 53717, 53718, 55475, 55476, 57227, 57228, 58989, 58990, 60739, 60740, 62499, 62500, 64259, 64260, 66017, 66018, 67775, 67776, 69537, 69538, 71297, 71298, 73057, 73058, 74817, 74818, 76577, 76578, 78339, 78340, 80101, 80102, 81863, 81864, 83625, 83626, 85385, 85386, 87147, 87148, 88909, 88910, 90663, 90664, 92421, 92422, 94183, 94184, 95945, 95946, 97703, 97704, 99465, 99466, 101209, 101210, 102971, 102972, 104733, 104734, 106495, 106496, 108255, 108256, 110013, 110014, 111775, 111776, 113537, 113538, 115299, 115300, 117061, 117062, 118821, 118822, 120583, 120584, 122345, 122346, 124107, 124108, 125869, 125870, 127629, 127630, 129389, 129390, 131151, 131152, 132913, 132914, 134669, 134670, 136431, 136432, 138191, 138192]
off_0xFAC = [27931, 29151, 29152, 30849, 30850, 32611, 32612, 34371, 34372, 36131, 36132, 37893, 37894, 39649, 39650, 41411, 41412, 43169, 43170, 44931, 44932, 46693, 46694, 48451, 48452, 50213, 50214, 51969, 51970, 53731, 53732, 55489, 55490, 57241, 57242, 59003, 59004, 60753, 60754, 62513, 62514, 64273, 64274, 66031, 66032, 67789, 67790, 69551, 69552, 71311, 71312, 73071, 73072, 74831, 74832, 76591, 76592, 78353, 78354, 80115, 80116, 81877, 81878, 83639, 83640, 85399, 85400, 87161, 87162, 88923, 88924, 90677, 90678, 92435, 92436, 94197, 94198, 95959, 95960, 97717, 97718, 99479, 99480, 101223, 101224, 102985, 102986, 104747, 104748, 106509, 106510, 108269, 108270, 110027, 110028, 111789, 111790, 113551, 113552, 115313, 115314, 117075, 117076, 118835, 118836, 120597, 120598, 122359, 122360, 124121, 124122, 125883, 125884, 127643, 127644, 129403, 129404, 131165, 131166, 132927, 132928, 134683, 134684, 136445, 136446, 138205, 138206]
off_0xFAD = [27934, 29163, 29164, 30863, 30864, 32625, 32626, 34385, 34386, 36145, 36146, 37907, 37908, 39663, 39664, 41425, 41426, 43183, 43184, 44945, 44946, 46707, 46708, 48465, 48466, 50227, 50228, 51983, 51984, 53745, 53746, 55503, 55504, 57255, 57256, 59017, 59018, 60767, 60768, 62527, 62528, 64287, 64288, 66045, 66046, 67803, 67804, 69565, 69566, 71325, 71326, 73085, 73086, 74845, 74846, 76605, 76606, 78367, 78368, 80129, 80130, 81891, 81892, 83653, 83654, 85413, 85414, 87175, 87176, 88937, 88938, 90691, 90692, 92449, 92450, 94211, 94212, 95973, 95974, 97731, 97732, 99493, 99494, 101237, 101238, 102999, 103000, 104761, 104762, 106523, 106524, 108283, 108284, 110041, 110042, 111803, 111804, 113565, 113566, 115327, 115328, 117089, 117090, 118849, 118850, 120611, 120612, 122373, 122374, 124135, 124136, 125897, 125898, 127657, 127658, 129417, 129418, 131179, 131180, 132941, 132942, 134697, 134698, 136459, 136460, 138219, 138220]
off_0xFAE = [27937, 29175, 29176, 30877, 30878, 32639, 32640, 34399, 34400, 36159, 36160, 37921, 37922, 39677, 39678, 41439, 41440, 43197, 43198, 44959, 44960, 46721, 46722, 48479, 48480, 50241, 50242, 51997, 51998, 53759, 53760, 55517, 55518, 57269, 57270, 59031, 59032, 60781, 60782, 62541, 62542, 64301, 64302, 66059, 66060, 67817, 67818, 69579, 69580, 71339, 71340, 73099, 73100, 74859, 74860, 76619, 76620, 78381, 78382, 80143, 80144, 81905, 81906, 83667, 83668, 85427, 85428, 87189, 87190, 88951, 88952, 90705, 90706, 92463, 92464, 94225, 94226, 95987, 95988, 97745, 97746, 99507, 99508, 101251, 101252, 103013, 103014, 104775, 104776, 106537, 106538, 108297, 108298, 110055, 110056, 111817, 111818, 113579, 113580, 115341, 115342, 117103, 117104, 118863, 118864, 120625, 120626, 122387, 122388, 124149, 124150, 125911, 125912, 127671, 127672, 129431, 129432, 131193, 131194, 132955, 132956, 134711, 134712, 136473, 136474, 138233, 138234]
off_0xFAF = [27940, 29187, 29188, 30891, 30892, 32653, 32654, 34413, 34414, 36173, 36174, 37935, 37936, 39691, 39692, 41453, 41454, 43211, 43212, 44973, 44974, 46735, 46736, 48493, 48494, 50255, 50256, 52011, 52012, 53773, 53774, 55531, 55532, 57283, 57284, 59045, 59046, 60795, 60796, 62555, 62556, 64315, 64316, 66073, 66074, 67831, 67832, 69593, 69594, 71353, 71354, 73113, 73114, 74873, 74874, 76633, 76634, 78395, 78396, 80157, 80158, 81919, 81920, 83681, 83682, 85441, 85442, 87203, 87204, 88965, 88966, 90719, 90720, 92477, 92478, 94239, 94240, 96001, 96002, 97759, 97760, 99521, 99522, 101265, 101266, 103027, 103028, 104789, 104790, 106551, 106552, 108311, 108312, 110069, 110070, 111831, 111832, 113593, 113594, 115355, 115356, 117117, 117118, 118877, 118878, 120639, 120640, 122401, 122402, 124163, 124164, 125925, 125926, 127685, 127686, 129445, 129446, 131207, 131208, 132969, 132970, 134725, 134726, 136487, 136488, 138247, 138248]
off_0xFB0 = [27943, 29199, 29200, 30905, 30906, 32667, 32668, 34427, 34428, 36187, 36188, 37949, 37950, 39705, 39706, 41467, 41468, 43225, 43226, 44987, 44988, 46749, 46750, 48507, 48508, 50269, 50270, 52025, 52026, 53787, 53788, 55545, 55546, 57297, 57298, 59059, 59060, 60809, 60810, 62569, 62570, 64329, 64330, 66087, 66088, 67845, 67846, 69607, 69608, 71367, 71368, 73127, 73128, 74887, 74888, 76647, 76648, 78409, 78410, 80171, 80172, 81933, 81934, 83695, 83696, 85455, 85456, 87217, 87218, 88979, 88980, 90733, 90734, 92491, 92492, 94253, 94254, 96015, 96016, 97773, 97774, 99535, 99536, 101279, 101280, 103041, 103042, 104803, 104804, 106565, 106566, 108325, 108326, 110083, 110084, 111845, 111846, 113607, 113608, 115369, 115370, 117131, 117132, 118891, 118892, 120653, 120654, 122415, 122416, 124177, 124178, 125939, 125940, 127699, 127700, 129459, 129460, 131221, 131222, 132983, 132984, 134739, 134740, 136501, 136502, 138261, 138262]
off_0xFB1 = [27946, 29211, 29212, 30919, 30920, 32681, 32682, 34441, 34442, 36201, 36202, 37963, 37964, 39719, 39720, 41481, 41482, 43239, 43240, 45001, 45002, 46763, 46764, 48521, 48522, 50283, 50284, 52039, 52040, 53801, 53802, 55559, 55560, 57311, 57312, 59073, 59074, 60823, 60824, 62583, 62584, 64343, 64344, 66101, 66102, 67859, 67860, 69621, 69622, 71381, 71382, 73141, 73142, 74901, 74902, 76661, 76662, 78423, 78424, 80185, 80186, 81947, 81948, 83709, 83710, 85469, 85470, 87231, 87232, 88993, 88994, 90747, 90748, 92505, 92506, 94267, 94268, 96029, 96030, 97787, 97788, 99549, 99550, 101293, 101294, 103055, 103056, 104817, 104818, 106579, 106580, 108339, 108340, 110097, 110098, 111859, 111860, 113621, 113622, 115383, 115384, 117145, 117146, 118905, 118906, 120667, 120668, 122429, 122430, 124191, 124192, 125953, 125954, 127713, 127714, 129473, 129474, 131235, 131236, 132997, 132998, 134753, 134754, 136515, 136516, 138275, 138276]
off_0xFB2 = [27949, 29223, 29224, 30933, 30934, 32695, 32696, 34455, 34456, 36215, 36216, 37977, 37978, 39733, 39734, 41495, 41496, 43253, 43254, 45015, 45016, 46777, 46778, 48535, 48536, 50297, 50298, 52053, 52054, 53815, 53816, 55573, 55574, 57325, 57326, 59087, 59088, 60837, 60838, 62597, 62598, 64357, 64358, 66115, 66116, 67873, 67874, 69635, 69636, 71395, 71396, 73155, 73156, 74915, 74916, 76675, 76676, 78437, 78438, 80199, 80200, 81961, 81962, 83723, 83724, 85483, 85484, 87245, 87246, 89007, 89008, 90761, 90762, 92519, 92520, 94281, 94282, 96043, 96044, 97801, 97802, 99563, 99564, 101307, 101308, 103069, 103070, 104831, 104832, 106593, 106594, 108353, 108354, 110111, 110112, 111873, 111874, 113635, 113636, 115397, 115398, 117159, 117160, 118919, 118920, 120681, 120682, 122443, 122444, 124205, 124206, 125967, 125968, 127727, 127728, 129487, 129488, 131249, 131250, 133011, 133012, 134767, 134768, 136529, 136530, 138289, 138290]
off_0xFB3 = [27952, 29235, 29236, 30947, 30948, 32709, 32710, 34469, 34470, 36229, 36230, 37991, 37992, 39747, 39748, 41509, 41510, 43267, 43268, 45029, 45030, 46791, 46792, 48549, 48550, 50311, 50312, 52067, 52068, 53829, 53830, 55587, 55588, 57339, 57340, 59101, 59102, 60851, 60852, 62611, 62612, 64371, 64372, 66129, 66130, 67887, 67888, 69649, 69650, 71409, 71410, 73169, 73170, 74929, 74930, 76689, 76690, 78451, 78452, 80213, 80214, 81975, 81976, 83737, 83738, 85497, 85498, 87259, 87260, 89021, 89022, 90775, 90776, 92533, 92534, 94295, 94296, 96057, 96058, 97815, 97816, 99577, 99578, 101321, 101322, 103083, 103084, 104845, 104846, 106607, 106608, 108367, 108368, 110125, 110126, 111887, 111888, 113649, 113650, 115411, 115412, 117173, 117174, 118933, 118934, 120695, 120696, 122457, 122458, 124219, 124220, 125981, 125982, 127741, 127742, 129501, 129502, 131263, 131264, 133025, 133026, 134781, 134782, 136543, 136544, 138303, 138304]
off_0xFB4 = [27955, 29247, 29248, 30961, 30962, 32723, 32724, 34483, 34484, 36243, 36244, 38005, 38006, 39761, 39762, 41523, 41524, 43281, 43282, 45043, 45044, 46805, 46806, 48563, 48564, 50325, 50326, 52081, 52082, 53843, 53844, 55601, 55602, 57353, 57354, 59115, 59116, 60865, 60866, 62625, 62626, 64385, 64386, 66143, 66144, 67901, 67902, 69663, 69664, 71423, 71424, 73183, 73184, 74943, 74944, 76703, 76704, 78465, 78466, 80227, 80228, 81989, 81990, 83751, 83752, 85511, 85512, 87273, 87274, 89035, 89036, 90789, 90790, 92547, 92548, 94309, 94310, 96071, 96072, 97829, 97830, 99591, 99592, 101335, 101336, 103097, 103098, 104859, 104860, 106621, 106622, 108381, 108382, 110139, 110140, 111901, 111902, 113663, 113664, 115425, 115426, 117187, 117188, 118947, 118948, 120709, 120710, 122471, 122472, 124233, 124234, 125995, 125996, 127755, 127756, 129515, 129516, 131277, 131278, 133039, 133040, 134795, 134796, 136557, 136558, 138317, 138318]
off_0xFB5 = [27958, 29259, 29260, 30975, 30976, 32737, 32738, 34497, 34498, 36257, 36258, 38019, 38020, 39775, 39776, 41537, 41538, 43295, 43296, 45057, 45058, 46819, 46820, 48577, 48578, 50339, 50340, 52095, 52096, 53857, 53858, 55615, 55616, 57367, 57368, 59129, 59130, 60879, 60880, 62639, 62640, 64399, 64400, 66157, 66158, 67915, 67916, 69677, 69678, 71437, 71438, 73197, 73198, 74957, 74958, 76717, 76718, 78479, 78480, 80241, 80242, 82003, 82004, 83765, 83766, 85525, 85526, 87287, 87288, 89049, 89050, 90803, 90804, 92561, 92562, 94323, 94324, 96085, 96086, 97843, 97844, 99605, 99606, 101349, 101350, 103111, 103112, 104873, 104874, 106635, 106636, 108395, 108396, 110153, 110154, 111915, 111916, 113677, 113678, 115439, 115440, 117201, 117202, 118961, 118962, 120723, 120724, 122485, 122486, 124247, 124248, 126009, 126010, 127769, 127770, 129529, 129530, 131291, 131292, 133053, 133054, 134809, 134810, 136571, 136572, 138331, 138332]
off_0xFB6 = [27961, 29271, 29272, 30989, 30990, 32751, 32752, 34511, 34512, 36271, 36272, 38033, 38034, 39789, 39790, 41551, 41552, 43309, 43310, 45071, 45072, 46833, 46834, 48591, 48592, 50353, 50354, 52109, 52110, 53871, 53872, 55629, 55630, 57381, 57382, 59143, 59144, 60893, 60894, 62653, 62654, 64413, 64414, 66171, 66172, 67929, 67930, 69691, 69692, 71451, 71452, 73211, 73212, 74971, 74972, 76731, 76732, 78493, 78494, 80255, 80256, 82017, 82018, 83779, 83780, 85539, 85540, 87301, 87302, 89063, 89064, 90817, 90818, 92575, 92576, 94337, 94338, 96099, 96100, 97857, 97858, 99619, 99620, 101363, 101364, 103125, 103126, 104887, 104888, 106649, 106650, 108409, 108410, 110167, 110168, 111929, 111930, 113691, 113692, 115453, 115454, 117215, 117216, 118975, 118976, 120737, 120738, 122499, 122500, 124261, 124262, 126023, 126024, 127783, 127784, 129543, 129544, 131305, 131306, 133067, 133068, 134823, 134824, 136585, 136586, 138345, 138346]
off_0xFB7 = [27964, 29283, 29284, 31003, 31004, 32765, 32766, 34525, 34526, 36285, 36286, 38047, 38048, 39803, 39804, 41565, 41566, 43323, 43324, 45085, 45086, 46847, 46848, 48605, 48606, 50367, 50368, 52123, 52124, 53885, 53886, 55643, 55644, 57395, 57396, 59157, 59158, 60907, 60908, 62667, 62668, 64427, 64428, 66185, 66186, 67943, 67944, 69705, 69706, 71465, 71466, 73225, 73226, 74985, 74986, 76745, 76746, 78507, 78508, 80269, 80270, 82031, 82032, 83793, 83794, 85553, 85554, 87315, 87316, 89077, 89078, 90831, 90832, 92589, 92590, 94351, 94352, 96113, 96114, 97871, 97872, 99633, 99634, 101377, 101378, 103139, 103140, 104901, 104902, 106663, 106664, 108423, 108424, 110181, 110182, 111943, 111944, 113705, 113706, 115467, 115468, 117229, 117230, 118989, 118990, 120751, 120752, 122513, 122514, 124275, 124276, 126037, 126038, 127797, 127798, 129557, 129558, 131319, 131320, 133081, 133082, 134837, 134838, 136599, 136600, 138359, 138360]
off_0xFB8 = [27967, 29295, 29296, 31017, 31018, 32779, 32780, 34539, 34540, 36299, 36300, 38061, 38062, 39817, 39818, 41579, 41580, 43337, 43338, 45099, 45100, 46861, 46862, 48619, 48620, 50381, 50382, 52137, 52138, 53899, 53900, 55657, 55658, 57409, 57410, 59171, 59172, 60921, 60922, 62681, 62682, 64441, 64442, 66199, 66200, 67957, 67958, 69719, 69720, 71479, 71480, 73239, 73240, 74999, 75000, 76759, 76760, 78521, 78522, 80283, 80284, 82045, 82046, 83807, 83808, 85567, 85568, 87329, 87330, 89091, 89092, 90845, 90846, 92603, 92604, 94365, 94366, 96127, 96128, 97885, 97886, 99647, 99648, 101391, 101392, 103153, 103154, 104915, 104916, 106677, 106678, 108437, 108438, 110195, 110196, 111957, 111958, 113719, 113720, 115481, 115482, 117243, 117244, 119003, 119004, 120765, 120766, 122527, 122528, 124289, 124290, 126051, 126052, 127811, 127812, 129571, 129572, 131333, 131334, 133095, 133096, 134851, 134852, 136613, 136614, 138373, 138374]
off_0xFB9 = [27970, 29307, 29308, 31031, 31032, 32793, 32794, 34553, 34554, 36313, 36314, 38075, 38076, 39831, 39832, 41593, 41594, 43351, 43352, 45113, 45114, 46875, 46876, 48633, 48634, 50395, 50396, 52151, 52152, 53913, 53914, 55671, 55672, 57423, 57424, 59185, 59186, 60935, 60936, 62695, 62696, 64455, 64456, 66213, 66214, 67971, 67972, 69733, 69734, 71493, 71494, 73253, 73254, 75013, 75014, 76773, 76774, 78535, 78536, 80297, 80298, 82059, 82060, 83821, 83822, 85581, 85582, 87343, 87344, 89105, 89106, 90859, 90860, 92617, 92618, 94379, 94380, 96141, 96142, 97899, 97900, 99661, 99662, 101405, 101406, 103167, 103168, 104929, 104930, 106691, 106692, 108451, 108452, 110209, 110210, 111971, 111972, 113733, 113734, 115495, 115496, 117257, 117258, 119017, 119018, 120779, 120780, 122541, 122542, 124303, 124304, 126065, 126066, 127825, 127826, 129585, 129586, 131347, 131348, 133109, 133110, 134865, 134866, 136627, 136628, 138387, 138388]
off_0xFBA = [27973, 29319, 29320, 31045, 31046, 32807, 32808, 34567, 34568, 36327, 36328, 38089, 38090, 39845, 39846, 41607, 41608, 43365, 43366, 45127, 45128, 46889, 46890, 48647, 48648, 50409, 50410, 52165, 52166, 53927, 53928, 55685, 55686, 57437, 57438, 59199, 59200, 60949, 60950, 62709, 62710, 64469, 64470, 66227, 66228, 67985, 67986, 69747, 69748, 71507, 71508, 73267, 73268, 75027, 75028, 76787, 76788, 78549, 78550, 80311, 80312, 82073, 82074, 83835, 83836, 85595, 85596, 87357, 87358, 89119, 89120, 90873, 90874, 92631, 92632, 94393, 94394, 96155, 96156, 97913, 97914, 99675, 99676, 101419, 101420, 103181, 103182, 104943, 104944, 106705, 106706, 108465, 108466, 110223, 110224, 111985, 111986, 113747, 113748, 115509, 115510, 117271, 117272, 119031, 119032, 120793, 120794, 122555, 122556, 124317, 124318, 126079, 126080, 127839, 127840, 129599, 129600, 131361, 131362, 133123, 133124, 134879, 134880, 136641, 136642, 138401, 138402]
off_0xFBB = [27976, 29331, 29332, 31059, 31060, 32821, 32822, 34581, 34582, 36341, 36342, 38103, 38104, 39859, 39860, 41621, 41622, 43379, 43380, 45141, 45142, 46903, 46904, 48661, 48662, 50423, 50424, 52179, 52180, 53941, 53942, 55699, 55700, 57451, 57452, 59213, 59214, 60963, 60964, 62723, 62724, 64483, 64484, 66241, 66242, 67999, 68000, 69761, 69762, 71521, 71522, 73281, 73282, 75041, 75042, 76801, 76802, 78563, 78564, 80325, 80326, 82087, 82088, 83849, 83850, 85609, 85610, 87371, 87372, 89133, 89134, 90887, 90888, 92645, 92646, 94407, 94408, 96169, 96170, 97927, 97928, 99689, 99690, 101433, 101434, 103195, 103196, 104957, 104958, 106719, 106720, 108479, 108480, 110237, 110238, 111999, 112000, 113761, 113762, 115523, 115524, 117285, 117286, 119045, 119046, 120807, 120808, 122569, 122570, 124331, 124332, 126093, 126094, 127853, 127854, 129613, 129614, 131375, 131376, 133137, 133138, 134893, 134894, 136655, 136656, 138415, 138416]
off_0xFBC = [27979, 29343, 29344, 31073, 31074, 32835, 32836, 34595, 34596, 36355, 36356, 38117, 38118, 39873, 39874, 41635, 41636, 43393, 43394, 45155, 45156, 46917, 46918, 48675, 48676, 50437, 50438, 52193, 52194, 53955, 53956, 55713, 55714, 57465, 57466, 59227, 59228, 60977, 60978, 62737, 62738, 64497, 64498, 66255, 66256, 68013, 68014, 69775, 69776, 71535, 71536, 73295, 73296, 75055, 75056, 76815, 76816, 78577, 78578, 80339, 80340, 82101, 82102, 83863, 83864, 85623, 85624, 87385, 87386, 89147, 89148, 90901, 90902, 92659, 92660, 94421, 94422, 96183, 96184, 97941, 97942, 99703, 99704, 101447, 101448, 103209, 103210, 104971, 104972, 106733, 106734, 108493, 108494, 110251, 110252, 112013, 112014, 113775, 113776, 115537, 115538, 117299, 117300, 119059, 119060, 120821, 120822, 122583, 122584, 124345, 124346, 126107, 126108, 127867, 127868, 129627, 129628, 131389, 131390, 133151, 133152, 134907, 134908, 136669, 136670, 138429, 138430]
off_0xFBD = [27982, 29355, 29356, 31087, 31088, 32849, 32850, 34609, 34610, 36369, 36370, 38131, 38132, 39887, 39888, 41649, 41650, 43407, 43408, 45169, 45170, 46931, 46932, 48689, 48690, 50451, 50452, 52207, 52208, 53969, 53970, 55727, 55728, 57479, 57480, 59241, 59242, 60991, 60992, 62751, 62752, 64511, 64512, 66269, 66270, 68027, 68028, 69789, 69790, 71549, 71550, 73309, 73310, 75069, 75070, 76829, 76830, 78591, 78592, 80353, 80354, 82115, 82116, 83877, 83878, 85637, 85638, 87399, 87400, 89161, 89162, 90915, 90916, 92673, 92674, 94435, 94436, 96197, 96198, 97955, 97956, 99717, 99718, 101461, 101462, 103223, 103224, 104985, 104986, 106747, 106748, 108507, 108508, 110265, 110266, 112027, 112028, 113789, 113790, 115551, 115552, 117313, 117314, 119073, 119074, 120835, 120836, 122597, 122598, 124359, 124360, 126121, 126122, 127881, 127882, 129641, 129642, 131403, 131404, 133165, 133166, 134921, 134922, 136683, 136684, 138443, 138444]
off_0xFBE = [27985, 29367, 29368, 31101, 31102, 32863, 32864, 34623, 34624, 36383, 36384, 38145, 38146, 39901, 39902, 41663, 41664, 43421, 43422, 45183, 45184, 46945, 46946, 48703, 48704, 50465, 50466, 52221, 52222, 53983, 53984, 55741, 55742, 57493, 57494, 59255, 59256, 61005, 61006, 62765, 62766, 64525, 64526, 66283, 66284, 68041, 68042, 69803, 69804, 71563, 71564, 73323, 73324, 75083, 75084, 76843, 76844, 78605, 78606, 80367, 80368, 82129, 82130, 83891, 83892, 85651, 85652, 87413, 87414, 89175, 89176, 90929, 90930, 92687, 92688, 94449, 94450, 96211, 96212, 97969, 97970, 99731, 99732, 101475, 101476, 103237, 103238, 104999, 105000, 106761, 106762, 108521, 108522, 110279, 110280, 112041, 112042, 113803, 113804, 115565, 115566, 117327, 117328, 119087, 119088, 120849, 120850, 122611, 122612, 124373, 124374, 126135, 126136, 127895, 127896, 129655, 129656, 131417, 131418, 133179, 133180, 134935, 134936, 136697, 136698, 138457, 138458]
off_0xFBF = [27988, 29379, 29380, 31115, 31116, 32877, 32878, 34637, 34638, 36397, 36398, 38159, 38160, 39915, 39916, 41677, 41678, 43435, 43436, 45197, 45198, 46959, 46960, 48717, 48718, 50479, 50480, 52235, 52236, 53997, 53998, 55755, 55756, 57507, 57508, 59269, 59270, 61019, 61020, 62779, 62780, 64539, 64540, 66297, 66298, 68055, 68056, 69817, 69818, 71577, 71578, 73337, 73338, 75097, 75098, 76857, 76858, 78619, 78620, 80381, 80382, 82143, 82144, 83905, 83906, 85665, 85666, 87427, 87428, 89189, 89190, 90943, 90944, 92701, 92702, 94463, 94464, 96225, 96226, 97983, 97984, 99745, 99746, 101489, 101490, 103251, 103252, 105013, 105014, 106775, 106776, 108535, 108536, 110293, 110294, 112055, 112056, 113817, 113818, 115579, 115580, 117341, 117342, 119101, 119102, 120863, 120864, 122625, 122626, 124387, 124388, 126149, 126150, 127909, 127910, 129669, 129670, 131431, 131432, 133193, 133194, 134949, 134950, 136711, 136712, 138471, 138472]
off_0xFC0 = [27991, 29391, 29392, 31129, 31130, 32891, 32892, 34651, 34652, 36411, 36412, 38173, 38174, 39929, 39930, 41691, 41692, 43449, 43450, 45211, 45212, 46973, 46974, 48731, 48732, 50493, 50494, 52249, 52250, 54011, 54012, 55769, 55770, 57521, 57522, 59283, 59284, 61033, 61034, 62793, 62794, 64553, 64554, 66311, 66312, 68069, 68070, 69831, 69832, 71591, 71592, 73351, 73352, 75111, 75112, 76871, 76872, 78633, 78634, 80395, 80396, 82157, 82158, 83919, 83920, 85679, 85680, 87441, 87442, 89203, 89204, 90957, 90958, 92715, 92716, 94477, 94478, 96239, 96240, 97997, 97998, 99759, 99760, 101503, 101504, 103265, 103266, 105027, 105028, 106789, 106790, 108549, 108550, 110307, 110308, 112069, 112070, 113831, 113832, 115593, 115594, 117355, 117356, 119115, 119116, 120877, 120878, 122639, 122640, 124401, 124402, 126163, 126164, 127923, 127924, 129683, 129684, 131445, 131446, 133207, 133208, 134963, 134964, 136725, 136726, 138485, 138486]
off_0xFC1 = [27994, 29403, 29404, 31143, 31144, 32905, 32906, 34665, 34666, 36425, 36426, 38187, 38188, 39943, 39944, 41705, 41706, 43463, 43464, 45225, 45226, 46987, 46988, 48745, 48746, 50507, 50508, 52263, 52264, 54025, 54026, 55783, 55784, 57535, 57536, 59297, 59298, 61047, 61048, 62807, 62808, 64567, 64568, 66325, 66326, 68083, 68084, 69845, 69846, 71605, 71606, 73365, 73366, 75125, 75126, 76885, 76886, 78647, 78648, 80409, 80410, 82171, 82172, 83933, 83934, 85693, 85694, 87455, 87456, 89217, 89218, 90971, 90972, 92729, 92730, 94491, 94492, 96253, 96254, 98011, 98012, 99773, 99774, 101517, 101518, 103279, 103280, 105041, 105042, 106803, 106804, 108563, 108564, 110321, 110322, 112083, 112084, 113845, 113846, 115607, 115608, 117369, 117370, 119129, 119130, 120891, 120892, 122653, 122654, 124415, 124416, 126177, 126178, 127937, 127938, 129697, 129698, 131459, 131460, 133221, 133222, 134977, 134978, 136739, 136740, 138499, 138500]
off_0xFC2 = [27997, 29415, 29416, 31157, 31158, 32919, 32920, 34679, 34680, 36439, 36440, 38201, 38202, 39957, 39958, 41719, 41720, 43477, 43478, 45239, 45240, 47001, 47002, 48759, 48760, 50521, 50522, 52277, 52278, 54039, 54040, 55797, 55798, 57549, 57550, 59311, 59312, 61061, 61062, 62821, 62822, 64581, 64582, 66339, 66340, 68097, 68098, 69859, 69860, 71619, 71620, 73379, 73380, 75139, 75140, 76899, 76900, 78661, 78662, 80423, 80424, 82185, 82186, 83947, 83948, 85707, 85708, 87469, 87470, 89231, 89232, 90985, 90986, 92743, 92744, 94505, 94506, 96267, 96268, 98025, 98026, 99787, 99788, 101531, 101532, 103293, 103294, 105055, 105056, 106817, 106818, 108577, 108578, 110335, 110336, 112097, 112098, 113859, 113860, 115621, 115622, 117383, 117384, 119143, 119144, 120905, 120906, 122667, 122668, 124429, 124430, 126191, 126192, 127951, 127952, 129711, 129712, 131473, 131474, 133235, 133236, 134991, 134992, 136753, 136754, 138513, 138514]
off_0xFC3 = [28000, 29427, 29428, 31171, 31172, 32933, 32934, 34693, 34694, 36453, 36454, 38215, 38216, 39971, 39972, 41733, 41734, 43491, 43492, 45253, 45254, 47015, 47016, 48773, 48774, 50535, 50536, 52291, 52292, 54053, 54054, 55811, 55812, 57563, 57564, 59325, 59326, 61075, 61076, 62835, 62836, 64595, 64596, 66353, 66354, 68111, 68112, 69873, 69874, 71633, 71634, 73393, 73394, 75153, 75154, 76913, 76914, 78675, 78676, 80437, 80438, 82199, 82200, 83961, 83962, 85721, 85722, 87483, 87484, 89245, 89246, 90999, 91000, 92757, 92758, 94519, 94520, 96281, 96282, 98039, 98040, 99801, 99802, 101545, 101546, 103307, 103308, 105069, 105070, 106831, 106832, 108591, 108592, 110349, 110350, 112111, 112112, 113873, 113874, 115635, 115636, 117397, 117398, 119157, 119158, 120919, 120920, 122681, 122682, 124443, 124444, 126205, 126206, 127965, 127966, 129725, 129726, 131487, 131488, 133249, 133250, 135005, 135006, 136767, 136768, 138527, 138528]
off_0xFC4 = [28003, 29439, 29440, 31185, 31186, 32947, 32948, 34707, 34708, 36467, 36468, 38229, 38230, 39985, 39986, 41747, 41748, 43505, 43506, 45267, 45268, 47029, 47030, 48787, 48788, 50549, 50550, 52305, 52306, 54067, 54068, 55825, 55826, 57577, 57578, 59339, 59340, 61089, 61090, 62849, 62850, 64609, 64610, 66367, 66368, 68125, 68126, 69887, 69888, 71647, 71648, 73407, 73408, 75167, 75168, 76927, 76928, 78689, 78690, 80451, 80452, 82213, 82214, 83975, 83976, 85735, 85736, 87497, 87498, 89259, 89260, 91013, 91014, 92771, 92772, 94533, 94534, 96295, 96296, 98053, 98054, 99813, 99814, 101559, 101560, 103321, 103322, 105083, 105084, 106845, 106846, 108605, 108606, 110363, 110364, 112125, 112126, 113887, 113888, 115649, 115650, 117411, 117412, 119171, 119172, 120933, 120934, 122695, 122696, 124457, 124458, 126219, 126220, 127979, 127980, 129739, 129740, 131501, 131502, 133263, 133264, 135019, 135020, 136781, 136782, 138541, 138542]
off_0xFC5 = [28006, 29451, 29452, 31199, 31200, 32961, 32962, 34721, 34722, 36481, 36482, 38243, 38244, 39999, 40000, 41761, 41762, 43519, 43520, 45281, 45282, 47043, 47044, 48801, 48802, 50563, 50564, 52319, 52320, 54081, 54082, 55839, 55840, 57591, 57592, 59353, 59354, 61103, 61104, 62863, 62864, 64623, 64624, 66381, 66382, 68139, 68140, 69901, 69902, 71661, 71662, 73421, 73422, 75181, 75182, 76941, 76942, 78703, 78704, 80465, 80466, 82227, 82228, 83989, 83990, 85749, 85750, 87511, 87512, 89273, 89274, 91027, 91028, 92785, 92786, 94547, 94548, 96309, 96310, 98067, 98068, 99825, 99826, 101573, 101574, 103335, 103336, 105097, 105098, 106859, 106860, 108619, 108620, 110377, 110378, 112139, 112140, 113901, 113902, 115663, 115664, 117425, 117426, 119185, 119186, 120947, 120948, 122709, 122710, 124471, 124472, 126233, 126234, 127993, 127994, 129753, 129754, 131515, 131516, 133277, 133278, 135033, 135034, 136795, 136796, 138555, 138556]
off_0xFC6 = [28009, 29463, 29464, 31213, 31214, 32975, 32976, 34735, 34736, 36495, 36496, 38257, 38258, 40013, 40014, 41775, 41776, 43533, 43534, 45295, 45296, 47057, 47058, 48815, 48816, 50577, 50578, 52333, 52334, 54095, 54096, 55853, 55854, 57605, 57606, 59367, 59368, 61117, 61118, 62877, 62878, 64637, 64638, 66395, 66396, 68153, 68154, 69915, 69916, 71675, 71676, 73435, 73436, 75195, 75196, 76955, 76956, 78717, 78718, 80479, 80480, 82241, 82242, 84003, 84004, 85763, 85764, 87525, 87526, 89287, 89288, 91041, 91042, 92799, 92800, 94561, 94562, 96323, 96324, 98081, 98082, 99837, 99838, 101587, 101588, 103349, 103350, 105111, 105112, 106873, 106874, 108633, 108634, 110391, 110392, 112153, 112154, 113915, 113916, 115677, 115678, 117439, 117440, 119199, 119200, 120961, 120962, 122723, 122724, 124485, 124486, 126247, 126248, 128007, 128008, 129767, 129768, 131529, 131530, 133291, 133292, 135047, 135048, 136809, 136810, 138569, 138570]
off_0xFC7 = [28012, 29475, 29476, 31227, 31228, 32989, 32990, 34749, 34750, 36509, 36510, 38271, 38272, 40027, 40028, 41789, 41790, 43547, 43548, 45309, 45310, 47071, 47072, 48829, 48830, 50591, 50592, 52347, 52348, 54109, 54110, 55867, 55868, 57619, 57620, 59379, 59380, 61131, 61132, 62891, 62892, 64651, 64652, 66409, 66410, 68167, 68168, 69929, 69930, 71689, 71690, 73449, 73450, 75209, 75210, 76969, 76970, 78731, 78732, 80493, 80494, 82255, 82256, 84017, 84018, 85777, 85778, 87539, 87540, 89301, 89302, 91055, 91056, 92813, 92814, 94575, 94576, 96337, 96338, 98095, 98096, 99849, 99850, 101601, 101602, 103363, 103364, 105125, 105126, 106887, 106888, 108647, 108648, 110405, 110406, 112167, 112168, 113929, 113930, 115691, 115692, 117453, 117454, 119213, 119214, 120975, 120976, 122737, 122738, 124499, 124500, 126261, 126262, 128021, 128022, 129781, 129782, 131543, 131544, 133305, 133306, 135061, 135062, 136823, 136824, 138583, 138584]
off_0xFC8 = [28015, 29487, 29488, 31241, 31242, 33003, 33004, 34763, 34764, 36523, 36524, 38285, 38286, 40041, 40042, 41803, 41804, 43561, 43562, 45323, 45324, 47085, 47086, 48843, 48844, 50605, 50606, 52361, 52362, 54123, 54124, 55879, 55880, 57633, 57634, 59391, 59392, 61145, 61146, 62905, 62906, 64665, 64666, 66423, 66424, 68181, 68182, 69943, 69944, 71703, 71704, 73463, 73464, 75223, 75224, 76983, 76984, 78745, 78746, 80507, 80508, 82269, 82270, 84031, 84032, 85791, 85792, 87553, 87554, 89315, 89316, 91069, 91070, 92827, 92828, 94589, 94590, 96351, 96352, 98109, 98110, 99861, 99862, 101615, 101616, 103377, 103378, 105139, 105140, 106901, 106902, 108661, 108662, 110419, 110420, 112181, 112182, 113943, 113944, 115705, 115706, 117467, 117468, 119227, 119228, 120989, 120990, 122751, 122752, 124513, 124514, 126275, 126276, 128035, 128036, 129795, 129796, 131557, 131558, 133319, 133320, 135075, 135076, 136837, 136838, 138597, 138598]
off_0xFC9 = [28018, 29499, 29500, 31255, 31256, 33017, 33018, 34777, 34778, 36537, 36538, 38299, 38300, 40055, 40056, 41817, 41818, 43575, 43576, 45337, 45338, 47099, 47100, 48857, 48858, 50619, 50620, 52375, 52376, 54137, 54138, 55891, 55892, 57647, 57648, 59403, 59404, 61159, 61160, 62919, 62920, 64679, 64680, 66437, 66438, 68195, 68196, 69957, 69958, 71717, 71718, 73477, 73478, 75237, 75238, 76997, 76998, 78759, 78760, 80521, 80522, 82283, 82284, 84045, 84046, 85805, 85806, 87567, 87568, 89327, 89328, 91083, 91084, 92841, 92842, 94603, 94604, 96365, 96366, 98123, 98124, 99873, 99874, 101629, 101630, 103391, 103392, 105153, 105154, 106915, 106916, 108675, 108676, 110433, 110434, 112195, 112196, 113957, 113958, 115719, 115720, 117481, 117482, 119241, 119242, 121003, 121004, 122765, 122766, 124527, 124528, 126289, 126290, 128049, 128050, 129809, 129810, 131571, 131572, 133333, 133334, 135089, 135090, 136851, 136852, 138611, 138612]
off_0xFCA = [28021, 29511, 29512, 31269, 31270, 33031, 33032, 34791, 34792, 36551, 36552, 38311, 38312, 40069, 40070, 41831, 41832, 43589, 43590, 45351, 45352, 47113, 47114, 48871, 48872, 50631, 50632, 52389, 52390, 54151, 54152, 55903, 55904, 57661, 57662, 59415, 59416, 61173, 61174, 62933, 62934, 64693, 64694, 66451, 66452, 68209, 68210, 69971, 69972, 71731, 71732, 73491, 73492, 75251, 75252, 77011, 77012, 78773, 78774, 80535, 80536, 82297, 82298, 84059, 84060, 85819, 85820, 87581, 87582, 89339, 89340, 91097, 91098, 92855, 92856, 94617, 94618, 96379, 96380, 98137, 98138, 99885, 99886, 101643, 101644, 103405, 103406, 105167, 105168, 106929, 106930, 108689, 108690, 110447, 110448, 112209, 112210, 113971, 113972, 115733, 115734, 117495, 117496, 119255, 119256, 121017, 121018, 122779, 122780, 124541, 124542, 126303, 126304, 128063, 128064, 129823, 129824, 131585, 131586, 133345, 133346, 135103, 135104, 136865, 136866, 138625, 138626]
off_0xFCB = [28024, 28029, 28031, 29523, 29524, 31283, 31284, 33045, 33046, 34805, 34806, 36565, 36566, 38323, 38324, 40083, 40084, 41843, 41844, 43603, 43604, 45365, 45366, 47125, 47126, 48885, 48886, 50643, 50644, 52403, 52404, 54163, 54164, 55915, 55916, 57675, 57676, 59427, 59428, 61187, 61188, 62947, 62948, 64705, 64706, 66463, 66464, 68223, 68224, 69985, 69986, 71745, 71746, 73505, 73506, 75265, 75266, 77025, 77026, 78787, 78788, 80549, 80550, 82311, 82312, 84073, 84074, 85833, 85834, 87595, 87596, 89351, 89352, 91109, 91110, 92869, 92870, 94631, 94632, 96391, 96392, 98151, 98152, 99897, 99898, 101657, 101658, 103419, 103420, 105181, 105182, 106943, 106944, 108701, 108702, 110461, 110462, 112223, 112224, 113985, 113986, 115747, 115748, 117509, 117510, 119269, 119270, 121031, 121032, 122793, 122794, 124555, 124556, 126317, 126318, 128077, 128078, 129837, 129838, 131599, 131600, 133357, 133358, 135117, 135118, 136879, 136880, 138639, 138640]

Observation shows that these instruction lines follow the following rules:

  • The start index = 3 * n + 27649 for n = 0 … 125
  • The end index = 12 * n + 136892 for n = 0 & 1 and = 14 * n + 136904 for n = 1 … 125
  • The delta index of each offset is 9 for n = 0 and 11 for n = 1 … 125

Further observation revealed that the 18 symbols in the center of the flag will be xrefed here, and the instruction addresses responsible for these symbols roughly follow a linear pattern as:

$$ anchor.start(p)=0xF4E+7⋅(p−7) $$

At the same time, 18 * 7 is exactly 126.

5. Two scattered points of LO16 and HI16 before the final R16

There’s two scattered points at index ~151028 to ~151090:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
151028,3822,0xeee,6,R_MIPS_LO16,_0x7f00,32512,0x7f00,257,0x101,contents&mask@0xeee=0x101; <<0; no-local-adjust,32769,0x8001,ok,0,,
151030,3822,0xeee,6,R_MIPS_LO16,_0x8100,33024,0x8100,32769,0x8001,contents&mask@0xeee=0x8001; <<0; no-local-adjust,257,0x101,ok,0,,
151032,1195,0x4ab,6,R_MIPS_LO16,_0x7f00,32512,0x7f00,257,0x101,contents&mask@0x4ab=0x101; <<0; no-local-adjust,32769,0x8001,ok,0,,
151036,1195,0x4ab,6,R_MIPS_LO16,_0xff818100,4286677248,0xff818100,32769,0x8001,contents&mask@0x4ab=0x8001; <<0; no-local-adjust,4286644481,0xff810101,ok,0,,
151037,3868,0xf1c,6,R_MIPS_LO16,_0x7f00,32512,0x7f00,257,0x101,contents&mask@0xf1c=0x101; <<0; no-local-adjust,32769,0x8001,ok,0,,
151039,3868,0xf1c,6,R_MIPS_LO16,_0x8100,33024,0x8100,32769,0x8001,contents&mask@0xf1c=0x8001; <<0; no-local-adjust,257,0x101,ok,0,,
151041,3822,0xeee,6,R_MIPS_LO16,_0x7f00,32512,0x7f00,257,0x101,contents&mask@0xeee=0x101; <<0; no-local-adjust,32769,0x8001,ok,0,,
151045,3822,0xeee,6,R_MIPS_LO16,_0xff818100,4286677248,0xff818100,32769,0x8001,contents&mask@0xeee=0x8001; <<0; no-local-adjust,4286644481,0xff810101,ok,0,,
151046,3867,0xf1b,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843009,0x1010101,contents&mask@0xf1b=0x101; match-lo16@0xeee raw=0x101 sext=257 src=contents&mask@0xeee=0x101; no-local-adjust,258,0x102,ok,0,,
151047,3822,0xeee,6,R_MIPS_LO16,_0x7f00,32512,0x7f00,257,0x101,contents&mask@0xeee=0x101; <<0; no-local-adjust,32769,0x8001,ok,0,,
151049,3868,0xf1c,6,R_MIPS_LO16,_0x7e00,32256,0x7e00,513,0x201,contents&mask@0xf1c=0x201; <<0; no-local-adjust,32769,0x8001,ok,0,,
151050,3867,0xf1b,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133057,0x17f8001,contents&mask@0xf1b=0x180; match-lo16@0xeee raw=0x8001 sext=-32767 src=contents&mask@0xeee=0x8001; no-local-adjust,257,0x101,ok,0,,
151051,3822,0xeee,6,R_MIPS_LO16,_0xff818100,4286677248,0xff818100,32769,0x8001,contents&mask@0xeee=0x8001; <<0; no-local-adjust,4286644481,0xff810101,ok,0,,
151052,3917,0xf4d,6,R_MIPS_LO16,_0x7f00,32512,0x7f00,256,0x100,contents&mask@0xf4d=0x100; <<0; no-local-adjust,32768,0x8000,ok,0,,
151054,3917,0xf4d,6,R_MIPS_LO16,_0x8100,33024,0x8100,32768,0x8000,contents&mask@0xf4d=0x8000; <<0; no-local-adjust,256,0x100,ok,0,,
151061,3916,0xf4c,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,16843008,0x1010100,contents&mask@0xf4c=0x101; match-lo16@0x53e3 raw=0x100 sext=256 src=contents&mask@0x53e3=0x100; no-local-adjust,258,0x102,ok,0,,
151064,3917,0xf4d,6,R_MIPS_LO16,_0x7e00,32256,0x7e00,512,0x200,contents&mask@0xf4d=0x200; <<0; no-local-adjust,32768,0x8000,ok,0,,
151065,3916,0xf4c,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,25133056,0x17f8000,contents&mask@0xf4c=0x180; match-lo16@0x53e3 raw=0x8000 sext=-32768 src=contents&mask@0x53e3=0x8000; no-local-adjust,257,0x101,ok,0,,
151082,1194,0x4aa,5,R_MIPS_HI16,_0x7f00,32512,0x7f00,65536,0x10000,contents&mask@0x4aa=0x1; match-lo16@0x53eb raw=0x0 sext=0 src=contents&mask@0x53eb=0x0; no-local-adjust,1,0x1,ok,0,,
151085,1195,0x4ab,6,R_MIPS_LO16,_0x7e00,32256,0x7e00,257,0x101,contents&mask@0x4ab=0x101; <<0; no-local-adjust,32513,0x7f01,ok,0,,
151086,1194,0x4aa,5,R_MIPS_HI16,_0xff818100,4286677248,0xff818100,8355584,0x7f7f00,contents&mask@0x4aa=0x7f; match-lo16@0x53eb raw=0x7f00 sext=32512 src=contents&mask@0x53eb=0x7f00; no-local-adjust,1,0x1,ok,0,,
151088,1195,0x4ab,6,R_MIPS_LO16,_0x7f00,32512,0x7f00,257,0x101,contents&mask@0x4ab=0x101; <<0; no-local-adjust,32769,0x8001,ok,0,,
151090,1195,0x4ab,6,R_MIPS_LO16,_0x8100,33024,0x8100,32769,0x8001,contents&mask@0x4ab=0x8001; <<0; no-local-adjust,257,0x101,ok,0,,

These positions may be the convergence points of the previous 4 check results.

6. Message building in the end

In the end has a part which starts from 4 R_32, no longer processes flag-derived state. It is essentially a static success-message emitter. In particular, the 15 contiguous _0x7f00 HI16 runs at offsets 0x1..0xf have lengths [116,104,97,116,39,115,32,99,111,114,114,101,99,116,33], which decode directly to the ASCII string “that’s correct!”.

Simple Summary

We can see 7 clearly demarcated parts in the program:

Part Index Reloc Index (The estimated value is enclosed in parentheses) Characteristic Diffs Guessed Process
1 0 ~ 10139, as 390 * 26 1 line LO16; 1 line HI16; 1 line R32 Only diff when input diff load input symbols to the designated address
2 10140 ~ (>27000) 2 line LO16 as one grows more slowly than the other; 2 line HI16 as 1 all zero and 1 grows 10150 ~ 20906 all HI16; > 20906 also LO16 start extracting character features?
3 (27300) ~ (28026) LO16 and HI16 with steep rise 26 HI16 diffs change the input flag to 18 continuously hashes
4 (~28027) ~ (>138651) 3 line LO16 as one repeats 127 times for 126 indices; 1 line HI16 Many diffs core encryption / check
5 (>138651) ~ (~140667) has a steep rise sequence composed of scattered points of LO16 Many diffs summary of results?
6 (~140668) ~ 151098 a thick block of LO16 and HI16 at offset ~20000 Many diffs ???
7 151099 ~ 155320 4 * R16; 2 sacttered points; flat LO16 and HI16 lines No diffs after 151446 result output

IV. Further Analysis On Part 3

Further analysis revealed that these 26 rows of differential indices actually only generated 18 different values, as if index 27337 is the base, then:

idx delta (+)
27337 0x0
27356 0x1
27363, 27376, 27383, 27390 0x2
27403, 27416 0x3
27429 0x4
27448 0x5
27455 0x6
27474 0x7
27493 0x8
27500, 27513, 27520, 27527 0x9
27540 0xa
27565 0xa
27578 0xb
27585 0xc
27592 0xd
27599 0xe
27618 0xf
27625 0x10
27632 0x11

These values precisely covers from+0 to+17. Furthermore and more importantly, the value values of these rows seem to follow some linear pattern. Use script to do differential attacks :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/usr/bin/env python3
import argparse
import csv
import importlib.util
import os
from collections import defaultdict
from typing import Dict, List, Tuple

DEFAULT_BASE = 'kalmar{flagflagflagflagfl}'
DEFAULT_IDXS = [27337, 27356, 27363, 27376, 27383, 27390, 27403, 27416, 27429, 27448, 27455, 27474, 27493, 27500, 27513, 27520, 27527, 27540, 27565, 27578, 27585, 27592, 27599, 27618, 27625, 27632]
DEFAULT_SHIFTS = [-2, -1, 1, 2]
DEFAULT_REF_IDX = 27337

def load_link_trace_module(path: str):
spec = importlib.util.spec_from_file_location('link_trace_plot_mod', path)
mod = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(mod)
return mod

def parse_int_list(text: str) -> List[int]: return [int(x.strip(), 0) for x in text.split(',') if x.strip()]

def printable_char(c: str) -> str:
o = ord(c)
if 32 <= o <= 126: return c
return f'\\\\x{o:02x}'

def mutate_one(s: str, pos: int, shift: int) -> str:
chars = list(s)
chars[pos] = chr(ord(chars[pos]) + shift)
return ''.join(chars)

def run_case(mod, obj: str, flag: str) -> Tuple[Dict[int, int], int, int]:
linker = mod.MiniMipsLdLike(obj, chars=[ord(c) for c in flag], base_addr=0x491A80, ext_symvals=None, watch_offsets=[], trace_writes=False, stop_on_error=False).apply()
values = {row.idx: row.value for row in linker.trace_rows}
first_error_idx = linker.errors[0][0] if linker.errors else -1
first_error_off = linker.errors[0][1] if linker.errors else -1
return values, first_error_idx, first_error_off

def hx(v: int) -> str:
if v < 0: return f'-0x{(-v):x}'
return f'0x{v:x}'

def main():
ap = argparse.ArgumentParser(description='Scan 26 positions with ±1/±2 ASCII deltas and analyze bridge-row delta invariance.')
ap.add_argument('--obj', default='flagchecker.o')
ap.add_argument('--link-trace-plot', default='link_trace_plot.py')
ap.add_argument('--base', default=DEFAULT_BASE)
ap.add_argument('--idxs', default=','.join(str(x) for x in DEFAULT_IDXS))
ap.add_argument('--ref-idx', type=int, default=DEFAULT_REF_IDX)
ap.add_argument('--shifts', default=','.join(str(x) for x in DEFAULT_SHIFTS))
ap.add_argument('--outdir', default='./bridge_delta_scan')
args = ap.parse_args()
idxs = parse_int_list(args.idxs)
shifts = parse_int_list(args.shifts)
ref_idx = args.ref_idx
if ref_idx not in idxs: raise SystemExit(f'--ref-idx {ref_idx} must be included in --idxs')
os.makedirs(args.outdir, exist_ok=True)
mod = load_link_trace_module(args.link_trace_plot)
baseline_values, baseline_err_idx, baseline_err_off = run_case(mod, args.obj, args.base)
missing = [idx for idx in idxs if idx not in baseline_values]
if missing: raise SystemExit(f'Baseline missing trace idx(s): {missing}')
baseline_ref = baseline_values[ref_idx]
baseline_delta = {idx: baseline_values[idx] - baseline_ref for idx in idxs}
long_rows = []
wide_rows = []
residual_sets: Dict[int, set] = defaultdict(set)
delta_sets: Dict[int, set] = defaultdict(set)
cases = [('baseline', -1, 0, args.base)]
for pos in range(len(args.base)):
for shift in shifts:
mutated = mutate_one(args.base, pos, shift)
cases.append((f'pos{pos:02d}_{shift:+d}', pos, shift, mutated))
for case_id, pos, shift, flag in cases:
values, err_idx, err_off = run_case(mod, args.obj, flag)
missing = [idx for idx in idxs if idx not in values]
if missing: raise SystemExit(f'{case_id}: missing trace idx(s): {missing}')
ref_val = values[ref_idx]
row = {
'case_id': case_id,
'pos': pos,
'shift': shift,
'flag': flag,
'first_error_idx': err_idx,
'first_error_off_hex': '' if err_off < 0 else hx(err_off),
'ref_idx': ref_idx,
'ref_value_hex': hx(ref_val),
'baseline_ref_hex': hx(baseline_ref),
'ref_shift_from_baseline_hex': hx(ref_val - baseline_ref),
}
for idx in idxs:
value = values[idx]
delta = value - ref_val
residual = delta - baseline_delta[idx]
delta_sets[idx].add(delta)
residual_sets[idx].add(residual)
row[f'v_{idx}_hex'] = hx(value)
row[f'delta_{idx}_hex'] = hx(delta)
row[f'residual_{idx}_hex'] = hx(residual)
long_rows.append({
'case_id': case_id,
'pos': pos,
'shift': shift,
'mutated_char': '' if pos < 0 else printable_char(flag[pos]),
'mutated_ord': '' if pos < 0 else ord(flag[pos]),
'flag': flag,
'first_error_idx': err_idx,
'first_error_off_hex': '' if err_off < 0 else hx(err_off),
'ref_idx': ref_idx,
'ref_value_hex': hx(ref_val),
'idx': idx,
'value_hex': hx(value),
'baseline_value_hex': hx(baseline_values[idx]),
'delta_from_ref_hex': hx(delta),
'baseline_delta_hex': hx(baseline_delta[idx]),
'residual_hex': hx(residual),
'delta_matches_baseline': int(delta == baseline_delta[idx]),
})
wide_rows.append(row)

long_csv = os.path.join(args.outdir, 'bridge_delta_long.csv')
with open(long_csv, 'w', newline='', encoding='utf-8') as f:
fieldnames = [
'case_id', 'pos', 'shift', 'mutated_char', 'mutated_ord', 'flag',
'first_error_idx', 'first_error_off_hex', 'ref_idx', 'ref_value_hex',
'idx', 'value_hex', 'baseline_value_hex', 'delta_from_ref_hex',
'baseline_delta_hex', 'residual_hex', 'delta_matches_baseline']
w = csv.DictWriter(f, fieldnames=fieldnames)
w.writeheader()
w.writerows(long_rows)

wide_csv = os.path.join(args.outdir, 'bridge_delta_wide.csv')
with open(wide_csv, 'w', newline='', encoding='utf-8') as f:
fieldnames = list(wide_rows[0].keys())
w = csv.DictWriter(f, fieldnames=fieldnames)
w.writeheader()
w.writerows(wide_rows)
print(f'[wrote] {long_csv}')
print(f'[wrote] {wide_csv}')

if __name__ == '__main__': main()

From the results obtained from single byte differencing, it is not difficult to find that for only the internal 18 bytes, their weights are a decreasing sequence from 18 to 1 multiplied by 0x38. The result seems to be the product of the weight and the δ between the changed byte and the base byte. However, in reality, the algorithm still has a slight perturbation, which subtracts half of the number of even ASCII values in the input string and rounding them down. In the end, the final result is the value of the above calculation modulo 2^16, as:

$$ R(x) = \left( 0x9B5C + 0x38 \cdot\displaystyle\sum_{i=0}^{17} \left( (18-i) \cdot (x_i - 0x31) \right) - \left\lfloor \frac{Even(x)}{2} \right\rfloor \right) mod 2^{16} $$

Or:

1
2
3
4
5
6
7
8
def Process_3(inp):
assert len(inp) == 18
b_h = 0x9B5C
b_s = b"111111111111111111"
_sum = 0
for i in range(18): _sum += (18 - i) * (inp[i] - b_s[i])
fix = sum(1 for c in inp if not c & 1) // 2
return (b_h + 0x38 * _sum - fix) & 0xFFFF

V. Findings And The Full Simulation On Part 4

Obviously, part 4 is the largest and the most important part of the whole process, and it also exhibits a high degree of regularity. As previous visual image of the trace shows that there are 4 chains working on this part in total, these chains can be distinguished by the value of the sym and the offset:

chain index type sym value offset range usage
0 1HI16+1LO16 + 1 ~ 2 * (1HI16 + 1LO16) _0x7f00 0xfcc ~ 0x4dce (18chars * 7bits * 63round * 2offset) the most complex chain
1 1LO16 _0x8100 0xf4e ~ 0xfcb (18chars * 7bits) stage per round
2 1HI16+1LO16 _0x7f7e00 0xfcc ~ 0x4dd0 (18chars * 7bits * 63round * 2offset) carry out values
3 1HI16 from _0x7f00 + 3HI16+1LO16 _0x7f00 & _0xffc08100 0x0 + 0xfcb ~ 0x4dcd (18chars * 7bits * 63round * 2offset) the update of the part3 hash value

1. Chain _0xffc08100

After initialization in part3, the value R(x) + 0x11 will be immediately referenced in the following index 28030, appears in the addend and the calculation result will appear in the value. The value of each line can be used as an addend for the next line, forming a chain operation. Extract using script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import pandas as pd
import os
import sys
import re

def extract_contents_mask_at_0(addend_str):
match = re.search(r'contents&mask@0x0=(0x[0-9a-fA-F]+)', str(addend_str))
if match: return match.group(1)
return None

def to_signed_byte(value):
byte_val = value & 0xFF
if byte_val > 127: return byte_val - 256
return byte_val

def search_chain_complex(input_file, start_idx):
if not os.path.exists(input_file):
print(f"File not found: '{input_file}'")
return
try:
print(f"Reading file: {input_file} ...")
df = pd.read_csv(input_file)
df['addend_src'] = df['addend_src'].astype(str)
df['value_hex'] = df['value_hex'].astype(str)
df['off'] = pd.to_numeric(df['off'], errors='coerce')
results = []
start_row = df[df['idx'] == start_idx]
if start_row.empty:
print(f"Cannot find start Idx: {start_idx}")
return
start_row = start_row[start_row['off'] == 0]
if start_row.empty:
print(f"Cannot find start Idx: {start_idx} with off=0")
return
start_row = start_row.iloc[0]
start_val_hex = start_row['value_hex']
diff_28030 = 0
try:
contents_mask_hex = extract_contents_mask_at_0(start_row['addend_src'])
if contents_mask_hex:
curr_val_int = int(start_val_hex, 16)
addend_val_int = int(contents_mask_hex, 16)
diff_raw = curr_val_int - addend_val_int
diff_28030 = to_signed_byte(diff_raw)
print(f"28030: {start_val_hex} - {contents_mask_hex} = {diff_raw} -> signed byte: {diff_28030}")
else:
diff_28030 = "NaN"
print(f"28030: Cannot extract contents&mask@0x0")
except ValueError as e:
diff_28030 = "NaN"
print(f"28030: Error calculating diff: {e}")
results.append({
'idx': start_row['idx'],
'addend_src': start_row['addend_src'],
'value_hex': start_val_hex,
'diff': diff_28030})
print(f"Added Idx={start_idx}: Val={start_val_hex}, Diff={diff_28030}")
next_idx = start_idx + 1
next_row = df[df['idx'] == next_idx]
if not next_row.empty:
next_row = next_row.iloc[0]
next_value_hex = next_row['value_hex']
try:
diff_signed = int(next_value_hex, 16)
diff_signed = to_signed_byte(diff_signed)
print(f"28031: value_hex={next_value_hex} -> diff={diff_signed}")
except ValueError: diff_signed = "NaN"
results.append({
'idx': next_row['idx'],
'addend_src': next_row['addend_src'],
'value_hex': next_value_hex,
'diff': diff_signed})
print(f"Added Idx={next_idx}: Val={next_value_hex}, Diff={diff_signed}")
else: print(f"Warning: Cannot find Idx={next_idx}")
current_idx = start_idx
current_target_val = start_val_hex
print(f"\\nStart chain tracking from Idx={current_idx}")
processed_start = False

while True:
found_row = None

if not processed_start:
processed_start = True
future_df = df[(df['idx'] > current_idx) & (df['off'] == 0)]
mask = future_df['addend_src'].str.contains(current_target_val, regex=False)
candidates = future_df[mask]
if not candidates.empty:
found_row = candidates.iloc[0]
print(f"Link to: Idx={found_row['idx']}, Val={found_row['value_hex']}")
else:
print(f"Link end: Cannot find addend_src contains '{current_target_val}'")
break
else:
future_df = df[(df['idx'] > current_idx) & (df['off'] == 0)]
mask = future_df['addend_src'].str.contains(current_target_val, regex=False)
candidates = future_df[mask]
if not candidates.empty:
found_row = candidates.iloc[0]
print(f"Link to: Idx={found_row['idx']}, Val={found_row['value_hex']}")
else:
print(f"Link end: Cannot find addend_src contains '{current_target_val}'")
break
if found_row is not None:
row_idx = found_row['idx']
row_addend = found_row['addend_src']
row_value = found_row['value_hex']
diff_signed = "NaN"
try:
contents_mask_hex = extract_contents_mask_at_0(row_addend)
if contents_mask_hex:
curr_val_int = int(row_value, 16)
addend_val_int = int(contents_mask_hex, 16)
diff_raw = curr_val_int - addend_val_int
diff_signed = to_signed_byte(diff_raw)
print(f" {row_idx}: {row_value} - {contents_mask_hex} = {diff_raw} -> signed byte: {diff_signed}")
else: print(f" {row_idx}: Cannot extract contents&mask@0x0")
except ValueError as e: print(f" {row_idx}: Error calculating diff: {e}")
results.append({
'idx': row_idx,
'addend_src': row_addend,
'value_hex': row_value,
'diff': diff_signed})
current_idx = row_idx
current_target_val = row_value
if results:
result_df = pd.DataFrame(results)
output_file = sys.argv[2]
result_df.to_csv(output_file, index=False)
print(f"\\nSearch complete. Found {len(results)} lines and saved to {output_file}")
print("\\nPreview:")
print(result_df.head(10).to_string())
else: print("\\nFound nothing.")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()

if __name__ == "__main__":
CSV_FILE = sys.argv[1]
START_IDX = 28030
search_chain_complex(CSV_FILE, START_IDX)

The results show that the number follows an approximate arithmetic rule of tolerance -62~-64 and continues from index 28030 to 138651 (part 4), and from then on, the growth direction shifted until the index was around 140661 (part 5), finally becoming an irregular slow growth until the end index 151000+(part 6). During the process, if the upper and lower limits of 16 bits are touched, it will mod 2^16.

Subsequent observations revealed that the previous approximate arithmetic process was controlled by two different delta values, exhibiting a periodic alternation of -63~64 and then**+0~1**. The 0/+1 case is from sym _0x7f00, and is very regular: from index 28030 to 138651, collect all the diff 0/+1, add an special bit from index 28031 at the front, then reverse per 7 bits to switch endian, then from binary per 7 bits is exactly the input string cycled 63 times (18 * 63 = 1134 chars in total), as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import pandas as pd
import os

def to_signed_byte(val):
low_8_bits = val & 0xFF
if low_8_bits >= 0x80: return low_8_bits - 0x100
else: return low_8_bits

def binary_to_text_7bit(binary_string):
length_to_process = (len(binary_string) // 7) * 7
if length_to_process == 0: return ""
result = ""
for i in range(0, length_to_process, 7):
chunk = binary_string[i : i + 7]
decimal_value = int(chunk, 2)
char = chr(decimal_value)
result += char
return result

def reverse_in_chunks_of_7(input_string):
length_to_process = (len(input_string) // 7) * 7
if length_to_process == 0: return ""
result = ""
for i in range(0, length_to_process, 7):
chunk = input_string[i : i + 7]
result += chunk[::-1]
return result

def process_diff_compact(input_file):
if not os.path.exists(input_file):
print(f"FILE NOT FOUND: '{input_file}'")
return
try:
df = pd.read_csv(input_file)
line_1_raw = []
line_2_normalized = []
for index, row in df.iterrows():
val = row['diff']
try: val = int(val)
except (ValueError, TypeError): continue
if val in [0, 1]: line_1_raw.append(str(val))
elif val in [-64, -63]: line_2_normalized.append(str(val + 64))
else:
signed_val = to_signed_byte(val)
if signed_val in [-64, -63]: line_2_normalized.append(str(signed_val + 64))
s1 = "".join(line_1_raw)
print(binary_to_text_7bit(reverse_in_chunks_of_7(s1)))
# s2 = "".join(line_2_normalized)
# print(reverse_in_chunks_of_7(s2))
except Exception as e: print(f"Error: {e}")

if __name__ == "__main__":
INPUT_CSV = "chain_result_complex_flagflagflagflagfl.csv"
process_diff_compact(INPUT_CSV)

However the situation of -64/-63 which comes from _0xffc08100 is chaotic, the only thing known is that -64 bands with 0x8000 and -63 with 0x7f00, besides that, there is currently no discernible pattern.

2. Chain _0x8100

For the other branch, the initialize variation of the offset value written to 0xF4E~0xFCB during the process follows the following results: These 126 positions can be seen as 18 rows * 7 * (2 bytes), as the columns’ HIBYTE is the little endian of the char’s bit, and the LOBYTE is the little endian of the next char’s bit ROR with 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def byte2_7bit(i):
r = [[t-0x30 for t in format(c,'07b').encode()][::-1] for c in i]
return r

bits = byte2_7bit(b"flagflagflagflagfl")
res = [[0 for _ in range(7)] for _ in range(18)]

for i in range(18):
for j in range(7): res[i][j] = 0x100 * bits[i][j] + bits[(i+1)%18][(j+1)%7]

def print_matr(matrix):
for row in matrix:
hex_row = [f'0x{num:0X}' for num in row]
print(' '.join(hex_row))

print_matr(res)

Verification script (extract the matrix from the trace):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import os
import glob
import pandas as pd
import numpy as np

start_idx = 27649
step = 3
total_count = 126
rows, cols = 18, 7
linear_indices = [start_idx + i * step for i in range(total_count)]
Matr = np.array(linear_indices).reshape(rows, cols)
FILE_PATTERN = "out_*.csv"

def extract_and_reshape():
files = glob.glob(FILE_PATTERN)
if not files:
print(f"ERROR NOT FOUND")
return
print(f"Total {len(files)} files")
for file_path in sorted(files):
try:
df = pd.read_csv(file_path)
if 'idx' in df.columns:
df['idx'] = df['idx'].astype(int)
else:
print(f"ERROR:LACK IDX")
continue
if 'value_hex' in df.columns:
df['value_hex'] = df['value_hex'].astype(str)
else:
print(f"ERROR:LACK VALUE")
continue
basename = os.path.basename(file_path)
suffix = basename.replace("out_", "").replace(".csv", "")
result_matrix = np.empty((rows, cols), dtype=object)
for r in range(rows):
for c in range(cols):
target_idx = Matr[r, c]
row_data = df[df['idx'] == target_idx]
if not row_data.empty:
val = row_data.iloc[0]['value_hex']
result_matrix[r, c] = val
else:
result_matrix[r, c] = "NaN"

print(f"\\nMat_{suffix} = [")
for r in range(rows):
row_vals = [f"'{result_matrix[r, c]}'" for c in range(cols)]
row_str = ", ".join(row_vals)
if r == rows - 1: print(f" [{row_str}]")
else: print(f" [{row_str}],")
print("]")
except Exception as e:
print(f"Error on file {file_path}: {e}")
import traceback
traceback.print_exc()

if __name__ == "__main__":
extract_and_reshape()

3. Chain _0x7f7e00 and _0x7f00

Then, filtering the traces in part 4 to only leave rows that meet conditions (df['off'] != 0) & (df['typ'] == 5) & (df['sym_parsed'] == 0x7f00) & (df['save_for_next'] == 1) or (df['off'] != 0) & (df['typ'] == 5) & (df['sym_parsed'] == 0x7f7e00) & (df['save_for_next'] == 1) can yield 7875 or 7938 results, exactly 12563 or 12663. Use this script to filter the trace:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import pandas as pd
import sys
import os

def filter_csv(input_file):
if not os.path.exists(input_file): return
try:
df = pd.read_csv(input_file)
df['off'] = pd.to_numeric(df['off'], errors='coerce')
df['typ'] = pd.to_numeric(df['typ'], errors='coerce')
if 'sym' in df.columns:
def parse_sym(val):
if pd.isna(val):
return None
val_str = str(val).strip()
if val_str.startswith('_'):
val_str = val_str[1:]
if val_str.startswith('0x'):
try: return int(val_str, 16)
except: return None
else:
try: return int(val_str)
except: return None
df['sym_parsed'] = df['sym'].apply(parse_sym)
else: return
print(df['sym_parsed'].value_counts().head(10))
filtered_df = df[(df['idx'] > 28000) & (df['idx'] < 140000) & (df['off'] != 0) & (df['typ'] == 5) & (df['sym_parsed'] == 0x7f7e00) & (df['save_for_next'] == 1)]
if 'sym_parsed' in filtered_df.columns: filtered_df = filtered_df.drop(columns=['sym_parsed'])
base_name = os.path.splitext(input_file)[0]
output_file = f"{base_name}_filtered.csv"
filtered_df.to_csv(output_file, index=False)
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()

if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python filter_csv.py <input_csv_file>")
sys.exit(1)
input_file = sys.argv[1]
filter_csv(input_file)

Note that the chain with sym _0x7f00 is quite messy and appears to be a mixed output of two chains, but the chain with sym _0x7f7e00 is very clean and only has four saved values {0x7f, 0x17f, 0x280, 0x380}. These values may be equal values ranging from 0 to 3. After mapping it to 2-bit values, split its LOBIT and HIBIT, and then according to the previous rule, combine every 7 bits into characters in little-endian:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import pandas as pd
import os
import sys

def parse_carry_out_hex(hex_str):
if pd.isna(hex_str) or hex_str == '': return None
try:
hex_str = str(hex_str).strip()
if hex_str.startswith('0x'): val = int(hex_str, 16)
else: val = int(hex_str)
if val == 0x7f: return 0
elif val == 0x17f: return 1
elif val == 0x280: return 2
elif val == 0x380: return 3
else: return None
except (ValueError, TypeError): return None

def reverse_in_chunks_of_7(input_string):
length_to_process = (len(input_string) // 7) * 7
if length_to_process == 0: return ""
result = ""
for i in range(0, length_to_process, 7):
chunk = input_string[i : i + 7]
result += chunk[::-1]
return result

def binary_to_hex(binary_string):
length_to_process = (len(binary_string) // 7) * 7
if length_to_process == 0: return ""
result_bytes = []
for i in range(0, length_to_process, 7):
chunk = binary_string[i : i + 7]
decimal_value = int(chunk, 2)
result_bytes.append(decimal_value)
hex_string = ''.join(f'{byte:02x}' for byte in result_bytes)
return hex_string

def map_lobit(digit_string):
result = ""
for char in digit_string:
if char == '2': result += '0'
elif char == '3': result += '1'
else: result += char
return result

def map_hibit(digit_string):
result = ""
for char in digit_string:
if char == '1': result += '0'
elif char == '2' or char == '3': result += '1'
else: result += char
return result

def process_carry_out(input_file):
if not os.path.exists(input_file):
print(f"FILE NOT FOUND: '{input_file}'")
return
try:
df = pd.read_csv(input_file)
if 'carry_out_hex' not in df.columns: return
result_digits = []
for index, row in df.iterrows():
carry_val = row['carry_out_hex']
digit = parse_carry_out_hex(carry_val)
if digit is not None: result_digits.append(str(digit))
else: print(f"Row {index}: {carry_val} -> skipped (not recognized)")
if not result_digits: return
digit_string = "".join(result_digits)
reversed_string = reverse_in_chunks_of_7(digit_string)
remainder = len(digit_string) % 7
if remainder > 0:
remaining_part = digit_string[-remainder:]
print(f"Remaining {remainder} digits (not reversed): {remaining_part}")
lobit_string = map_lobit(reversed_string)
lobit_hex = binary_to_hex(lobit_string)
print(f"LOBIT: {lobit_hex}")
hibit_string = map_hibit(reversed_string)
hibit_hex = binary_to_hex(hibit_string)
print(f"HIBIT: {hibit_hex}")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()

if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python extract_carry_out.py <input_csv_file>")
sys.exit(1)
INPUT_CSV = sys.argv[1]
process_carry_out(INPUT_CSV)

We can see that the LOBIT layer is initially the plaintext we input, which is then continuously updated by scrolling, and there is a diffusion effect that gradually amplifies. For the HIBIT, change the previous -63 to 0 and -64 to 1 (as NOT operator) of the -63/-64 chain, the output is the same as this HIBIT layer — They’re the same thing. Next, change the trace script to make it allow non-printable characters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import ast

def parse_chars(chars_str):
if chars_str is None: return None
if chars_str.startswith('b"') or chars_str.startswith("b'"):
try:
byte_obj = ast.literal_eval(chars_str)
if isinstance(byte_obj, bytes): return list(byte_obj)
except (ValueError, SyntaxError) as e:
print(f"Warning: Failed to parse as byte string: {e}")
pass
return [ord(c) for c in chars_str]

chars = None if ext_symvals is not None else parse_chars(args.chars)

Usage:

1
python link_trace_plot.py --base-addr 0x491A80 --trace-csv out.csv --chars 'b"kalmar{\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00}"'

From the output corresponding to all-zero inputs, it can be seen that both chains perfectly present the result of being all zero — even after 63 rounds of evolution, which means all perturbation comes from the input itself. Use a total script to perform differential attack:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#!/usr/bin/env python3
import argparse
import ast
import json
from pathlib import Path
from typing import Iterable, List, Tuple, Dict, Any
import pandas as pd
from link_trace_plot import MiniMipsLdLike
from extr_filter import (parse_carry_out_hex, reverse_in_chunks_of_7, binary_to_hex, map_lobit, map_hibit)

DEFAULT_PREFIX = b"kalmar{"
DEFAULT_SUFFIX = b"}"

def parse_bytes_literal(s: str) -> bytes:
obj = ast.literal_eval(s)
if not isinstance(obj, (bytes, bytearray)): raise ValueError(f"expected Python bytes literal, got {type(obj).__name__}")
return bytes(obj)

def parse_payload_arg(args: argparse.Namespace) -> bytes:
selected = [args.payload_ascii is not None, args.payload_hex is not None, args.payload_bytes is not None]
if sum(selected) != 1: raise ValueError("exactly one of --payload-ascii / --payload-hex / --payload-bytes is required for single-run mode")
if args.payload_ascii is not None: payload = args.payload_ascii.encode("latin1")
elif args.payload_hex is not None: payload = bytes.fromhex(args.payload_hex)
else: payload = parse_bytes_literal(args.payload_bytes)
if len(payload) != args.payload_len: raise ValueError(f"payload length must be {args.payload_len}, got {len(payload)}")
return payload

def parse_sym_to_int(val: Any):
if pd.isna(val): return None
s = str(val).strip()
if s.startswith("_"): s = s[1:]
try: return int(s, 0)
except Exception: return None

def full_chars(prefix: bytes, payload: bytes, suffix: bytes) -> List[int]:
combo = prefix + payload + suffix
if len(combo) != 26: raise ValueError(f"full chars must have length 26, got {len(combo)}")
return list(combo)

def trace_to_df(linker: MiniMipsLdLike) -> pd.DataFrame:
rows = []
for r in linker.trace_rows:
rows.append({
"idx": r.idx,
"off": r.off,
"off_hex": f"0x{r.off:x}",
"typ": r.typ,
"typ_name": r.typ_name,
"sym": r.sym,
"S": r.S,
"S_hex": f"0x{r.S & 0xFFFFFFFF:x}",
"addend": r.addend,
"addend_hex": f"0x{r.addend:x}",
"addend_src": r.addend_src,
"value": r.value,
"value_hex": f"0x{r.value:x}",
"status": r.status,
"save_for_next": int(r.save_for_next),
"carry_out": "" if r.carry_out is None else r.carry_out,
"carry_out_hex": "" if r.carry_out is None else f"0x{r.carry_out:x}"})
df = pd.DataFrame(rows)
df["sym_parsed"] = df["sym"].apply(parse_sym_to_int)
return df

def filter_carry_chain(df: pd.DataFrame, sym: int = 0x7F7E00) -> pd.DataFrame: return df[(df["idx"] > 28000) & (df["idx"] < 140000) & (df["off"] != 0) & (df["typ"] == 5) & (df["sym_parsed"] == sym) & (df["save_for_next"] == 1)].copy()

def decode_tracks(filtered_df: pd.DataFrame) -> Dict[str, Any]:
digits: List[str] = []
unknown_rows = []
for i, row in filtered_df.iterrows():
d = parse_carry_out_hex(row["carry_out_hex"])
if d is None:
unknown_rows.append((int(row["idx"]), row["carry_out_hex"]))
continue
digits.append(str(d))
digit_string = "".join(digits)
reversed_string = reverse_in_chunks_of_7(digit_string)
lobit_bits = map_lobit(reversed_string)
hibit_bits = map_hibit(reversed_string)
lobit_hex = binary_to_hex(lobit_bits)
hibit_hex = binary_to_hex(hibit_bits)
lobit_bytes = bytes.fromhex(lobit_hex) if lobit_hex else b""
hibit_bytes = bytes.fromhex(hibit_hex) if hibit_hex else b""
return {
"digits": digit_string,
"reversed_digits": reversed_string,
"lobit_bits": lobit_bits,
"hibit_bits": hibit_bits,
"lobit_hex": lobit_hex,
"hibit_hex": hibit_hex,
"lobit_bytes": lobit_bytes,
"hibit_bytes": hibit_bytes,
"unknown_rows": unknown_rows}

def split_rounds(buf: bytes, round_width: int = 18) -> List[bytes]: return [buf[i : i + round_width] for i in range(0, len(buf), round_width)]

def nonzero_positions_per_round(buf: bytes, round_width: int = 18) -> List[List[int]]:
out = []
for block in split_rounds(buf, round_width): out.append([i for i, b in enumerate(block) if b != 0])
return out

def xor_bytes(a: bytes, b: bytes) -> bytes:
n = min(len(a), len(b))
return bytes(x ^ y for x, y in zip(a[:n], b[:n]))

def run_one(obj: Path, prefix: bytes, suffix: bytes, payload: bytes, base_addr: int, carry_sym: int) -> Dict[str, Any]:
linker = MiniMipsLdLike(str(obj), chars=full_chars(prefix, payload, suffix), base_addr=base_addr).apply()
df = trace_to_df(linker)
filtered = filter_carry_chain(df, sym=carry_sym)
tracks = decode_tracks(filtered)
return {"payload": payload, "trace_df": df, "filtered_df": filtered, "tracks": tracks}

def save_run(out_dir: Path, label: str, run: Dict[str, Any], save_trace: bool, save_filtered: bool) -> None:
out_dir.mkdir(parents=True, exist_ok=True)
if save_trace: run["trace_df"].to_csv(out_dir / f"{label}_trace.csv", index=False)
if save_filtered: run["filtered_df"].to_csv(out_dir / f"{label}_filtered.csv", index=False)
meta = {
"label": label,
"payload_hex": run["payload"].hex(),
"payload_repr": repr(run["payload"]),
"lobit_hex": run["tracks"]["lobit_hex"],
"hibit_hex": run["tracks"]["hibit_hex"],
"filtered_rows": int(len(run["filtered_df"])),
"unknown_rows": run["tracks"]["unknown_rows"]}
(out_dir / f"{label}_tracks.json").write_text(json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8")
(out_dir / f"{label}_lobit.hex").write_text(run["tracks"]["lobit_hex"] + "\\n", encoding="utf-8")
(out_dir / f"{label}_hibit.hex").write_text(run["tracks"]["hibit_hex"] + "\\n", encoding="utf-8")

def single_bit_payload(bit_idx: int, payload_len: int, bits_per_byte: int) -> bytes:
if bit_idx < 0 or bit_idx >= payload_len * bits_per_byte: raise ValueError(f"bit index {bit_idx} out of range for {payload_len} bytes * {bits_per_byte} bits")
arr = bytearray(payload_len)
byte_idx = bit_idx // bits_per_byte
bit_in_byte = bit_idx % bits_per_byte
arr[byte_idx] = 1 << bit_in_byte
return bytes(arr)

def summarize_scan(results: List[Tuple[int, Dict[str, Any]]], baseline: Dict[str, Any], out_csv: Path):
rows = []
base_l = baseline["tracks"]["lobit_bytes"]
base_h = baseline["tracks"]["hibit_bytes"]
for bit_idx, run in results:
lxor = xor_bytes(run["tracks"]["lobit_bytes"], base_l)
hxor = xor_bytes(run["tracks"]["hibit_bytes"], base_h)
lpos = nonzero_positions_per_round(lxor)
hpos = nonzero_positions_per_round(hxor)
first_round_l = next((i for i, v in enumerate(lpos) if v), None)
first_round_h = next((i for i, v in enumerate(hpos) if v), None)
rows.append({
"bit_idx": bit_idx,
"byte_idx": bit_idx // 7,
"bit_lsb": bit_idx % 7,
"payload_hex": run["payload"].hex(),
"first_round_lobit": first_round_l,
"first_round_hibit": first_round_h,
"first_positions_lobit": " ".join(map(str, lpos[first_round_l])) if first_round_l is not None else "",
"first_positions_hibit": " ".join(map(str, hpos[first_round_h])) if first_round_h is not None else "",
"total_nonzero_lobit": sum(len(x) for x in lpos),
"total_nonzero_hibit": sum(len(x) for x in hpos),
"last_round_lobit": max((i for i, v in enumerate(lpos) if v), default=None),
"last_round_hibit": max((i for i, v in enumerate(hpos) if v), default=None),
"lobit_hex_prefix": run["tracks"]["lobit_hex"][:72],
"hibit_hex_prefix": run["tracks"]["hibit_hex"][:72]})
pd.DataFrame(rows).sort_values("bit_idx").to_csv(out_csv, index=False)

def parse_bit_spec(spec: str) -> List[int]:
spec = spec.strip()
if "," in spec:
out = []
for part in spec.split(","): out.extend(parse_bit_spec(part))
return out
if ":" in spec:
parts = spec.split(":")
if len(parts) not in (2, 3): raise ValueError(f"bad range spec: {spec}")
start = int(parts[0])
stop = int(parts[1])
step = int(parts[2]) if len(parts) == 3 else 1
return list(range(start, stop, step))
return [int(spec)]

def main():
ap = argparse.ArgumentParser(description="Batch controller for link_trace_plot -> filter -> extr_filter pipeline")
ap.add_argument("--obj", default="./flagchecker.o")
ap.add_argument("--prefix", default="b'kalmar{'", help="Python bytes literal")
ap.add_argument("--suffix", default="b'}'", help="Python bytes literal")
ap.add_argument("--payload-len", type=int, default=18)
ap.add_argument("--base-addr", type=lambda x: int(x, 0), default=0x491A80)
ap.add_argument("--carry-sym", type=lambda x: int(x, 0), default=0x7F7E00)
ap.add_argument("--out-dir", default="trace_runs")
ap.add_argument("--save-trace", action="store_true")
ap.add_argument("--save-filtered", action="store_true")
sub = ap.add_subparsers(dest="mode", required=True)
one = sub.add_parser("one", help="run one payload")
one.add_argument("--label", default="sample")
one.add_argument("--payload-ascii")
one.add_argument("--payload-hex")
one.add_argument("--payload-bytes")
scan = sub.add_parser("single-bit-scan", help="scan one-hot payload bits against a zero baseline")
scan.add_argument("--bits-per-byte", type=int, default=7, choices=[7, 8])
scan.add_argument("--bit-spec", default=None, help="e.g. 0:126 or 5,9,17")
scan.add_argument("--keep-each-run", action="store_true")
args = ap.parse_args()
obj = Path(args.obj)
out_dir = Path(args.out_dir)
prefix = parse_bytes_literal(args.prefix)
suffix = parse_bytes_literal(args.suffix)

if args.mode == "one":
payload = parse_payload_arg(args)
run = run_one(obj, prefix, suffix, payload, args.base_addr, args.carry_sym)
save_run(out_dir, args.label, run, args.save_trace, args.save_filtered)
print(f"label={args.label}")
print(f"payload={payload!r}")
print(f"filtered_rows={len(run['filtered_df'])}")
print(f"LOBIT: {run['tracks']['lobit_hex']}")
print(f"HIBIT: {run['tracks']['hibit_hex']}")
return

if args.mode == "single-bit-scan":
total_bits = args.payload_len * args.bits_per_byte
bit_indices = list(range(total_bits)) if args.bit_spec is None else parse_bit_spec(args.bit_spec)
baseline = run_one(obj, prefix, suffix, b"\\x00" * args.payload_len, args.base_addr, args.carry_sym)
save_run(out_dir, "baseline_zero", baseline, args.save_trace, args.save_filtered)
results = []
for bit_idx in bit_indices:
payload = single_bit_payload(bit_idx, args.payload_len, args.bits_per_byte)
label = f"bit_{bit_idx:03d}_b{bit_idx // args.bits_per_byte:02d}_k{bit_idx % args.bits_per_byte}"
run = run_one(obj, prefix, suffix, payload, args.base_addr, args.carry_sym)
results.append((bit_idx, run))
if args.keep_each_run: save_run(out_dir, label, run, args.save_trace, args.save_filtered)
print(f"done {label}")
summarize_scan(results, baseline, out_dir / "single_bit_summary.csv")
print(f"wrote {out_dir / 'single_bit_summary.csv'}")
return

if __name__ == "__main__": main()

Usage:

1
2
python batch_trace_controller.py --out-dir runs_zero one --label zeros --payload-hex 000000000000000000000000000000000000
python batch_trace_controller.py --out-dir runs_126 single-bit-scan

The analysis results show that when there is only single bit plaintext in the entire input, only the diffusion layer takes effect, and the diffusion layer is like an incomplete bit translator. Specifically, the previous bit sorting method should be abandoned. Without reversing the bits, the diffusion layer will only diffuse from right to left and will not cross the left boundary. The 126 bits from left to right is: byte0bit0, byte0bit1, … , byte0bit7, byte1bit0, byte1bit1, … byte17bit6, byte17bit7. So the test of 000000000000000000000000000000000040 will produce the most complete evolutionary diagram. Print the diagram:

1
2
3
4
5
6
7
8
9
from Crypto.Util.number import *
state = bytearray.fromhex("00000000000000000000000000000000004000000000000000000000000000000000006000000000000000000000000000000000005800000000000000000000000000000000005600000000000000000000000000000000006b000000000000000000000000000000005846000000000000000000000000000000002c6300000000000000000000000000000040354c000000000000000000000000000000601a66000000000000000000000000000000300d7300000000000000000000000000000056314e0000000000000000000000000000006b186700000000000000000000000000005846394600000000000000000000000000002c631c630000000000000000000000000040354c334c000000000000000000000000006b1867184100000000000000000000000040354c334c60000000000000000000000040354c334c60400000000000000000000000300d730c13185000000000000000000000002c631c630406540000000000000000000040354c334c60404a0000000000000000000058463946090c2849000000000000000000002c631c6304065464000000000000000000006b18671841011559000000000000000000601a6619263020255600000000000000000058463946090c28495500000000000000000056314e3102032a32550000000000000000006b186718410115596a000000000000000040354c334c60404a2c750000000000000000601a661926302025567a0000000000000000300d730c131850122b7d00000000000000002c631c63040654642a5f000000000000000056314e3102032a32556f00000000000000006b186718410115596a77000000000000002c631c63040654642a5f4300000000000040354c334c60404a2c753b48000000000000601a661926302025567a1d64000000000000300d730c131850122b7d0e7200000000000056314e3102032a32556f214e0000000000006b186718410115596a77106700000000300d730c131850122b7d0e720c400000000058463946090c2849553e07390660000000002c631c63040654642a5f431c03700000000056314e3102032a32556f214e017800000040354c334c60404a2c753b4833005e00000058463946090c2849553e073906604b0000002c631c63040654642a5f431c03706500000056314e3102032a32556f214e0178720000006b186718410115596a771067003c79000040354c334c60404a2c753b4833005e7c0000300d730c131850122b7d0e720c40175f000058463946090c2849553e073906604b6f00002c631c63040654642a5f431c03706577000056314e3102032a32556f214e0178727b00006b186718410115596a771067003c797d00601a661926302025567a1d6419002f3e5f0058463946090c2849553e073906604b6f57002c631c63040654642a5f431c037065776b0056314e3102032a32556f214e0178727b75300d730c131850122b7d0e720c40175f2f4758463946090c2849553e073906604b6f576356314e3102032a32556f214e0178727b75586b186718410115596a771067003c797d3a6c")
for i in range(63):
b = state[18*i:18*(i+1)]
bin_str = ""
for byte in b:
bin_str += f"{byte & 0x7F:07b}"[::-1]
print(bin_str)
# print(bin_str.replace('0', '░').replace('1', '█'))

After careful observation, it was found that the diffusion layer extends nonlinearly from the rightmost to the leftmost within 63 rows, with a bit length exactly twice the number of rows. After observing the pattern, it is reasonable to speculate that the diffusion layer is actually a bit translator, with half of the rows removed to form the current pattern. Try to complete and visualize it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# -*- coding: utf-8 -*-
from pathlib import Path
import argparse
import json
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap

def read_rows(path: Path):
rows = []
with path.open("r", encoding="utf-8", errors="ignore") as f:
for line in f:
s = "".join(ch for ch in line.strip() if ch in "01")
if s: rows.append(s)
if not rows: raise ValueError(f"{path}: no valid 01 rows found")
w = len(rows[0])
for i, row in enumerate(rows):
if len(row) != w: raise ValueError(f"{path}: row {i} width mismatch ({len(row)} != {w})")
return rows

def leftmost_one(row: str):
idx = row.find("1")
return idx if idx != -1 else None

def expand_to_antidiag(rows):
width = len(rows[0])
expanded = ["0" * width for _ in range(width)]
nonempty_indices = []
mapping = []
collisions = []
for src_idx, row in enumerate(rows):
l = leftmost_one(row)
if l is None: continue
target = width - 1 - l
if "1" in expanded[target]: collisions.append((src_idx, target))
expanded[target] = row
nonempty_indices.append(target)
mapping.append({"src_row": src_idx,"expanded_row": target,"leftmost_one": l})
return expanded, sorted(nonempty_indices), mapping, collisions

def save_filled_png(expanded_rows, out_png: Path, title: str):
mat = np.array([[int(c) for c in r] for r in expanded_rows], dtype=np.uint8)
mat_plot = np.flipud(mat)
cmap = ListedColormap(["#230255", "#2EB2EE"])
plt.figure(figsize=(8, 8))
plt.imshow(mat_plot, interpolation="nearest", aspect="equal", cmap=cmap, vmin=0, vmax=1)
plt.title(title)
plt.xlabel("column")
plt.ylabel("row (flipped for display)")
plt.tight_layout()
plt.savefig(out_png, dpi=180)
plt.close()

def main():
ap = argparse.ArgumentParser()
ap.add_argument("input_txt")
ap.add_argument("--prefix", default=None)
args = ap.parse_args()
inp = Path(args.input_txt)
rows = read_rows(inp)
width = len(rows[0])
expanded, nonempty_indices, mapping, collisions = expand_to_antidiag(rows)
prefix = args.prefix or inp.with_suffix("").as_posix()
expanded_txt = Path(prefix + "_expanded.txt")
filled_png = Path(prefix + "_filled.png")
indices_txt = Path(prefix + "_nonempty_indices.txt")
mapping_json = Path(prefix + "_mapping.json")
expanded_txt.write_text("\\n".join(expanded) + "\\n", encoding="utf-8")
indices_txt.write_text("\\n".join(map(str, nonempty_indices)) + "\\n", encoding="utf-8")
mapping_json.write_text(json.dumps({"width": width,"input_rows": len(rows),"nonempty_row_indices": nonempty_indices,"mapping": mapping,"collisions": collisions,}, indent=2, ensure_ascii=False),encoding="utf-8")
save_filled_png(expanded, filled_png, f"Filled expanded trace ({width}x{width})")
print(f"[+] width : {width}")
print(f"[+] input rows : {len(rows)}")
print(f"[+] expanded rows : {len(expanded)}")
print(f"[+] nonempty row count : {len(nonempty_indices)}")
print(f"[+] collisions : {len(collisions)}")
print(f"[+] nonempty row indices: {nonempty_indices}")
print(f"[+] expanded txt : {expanded_txt}")
print(f"[+] filled png : {filled_png}")
print(f"[+] indices txt : {indices_txt}")
print(f"[+] mapping json : {mapping_json}")

if __name__ == "__main__": main()

The indices are: [0, 1, 3, 5, 6, 10, 11, 14, 15, 16, 19, 20, 24, 25, 28, 34, 35, 42, 44, 46, 49, 52, 53, 55, 57, 59, 61, 62, 63, 64, 65, 67, 68, 69, 74, 77, 78, 79, 82, 83, 93, 94, 95, 96, 98, 101, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 115, 116, 117, 121, 122, 124, 125]. The visualized output is pleasant:

And all lines that are abandoned have zero bit at the tail. So this can be express as:

1
2
3
4
a = "110101100011001110011000110010000011000000101010010011010101011111011100001001110011000000000111101001111101111101011100011011"

for i in range(126):
if not (a[:i+1])[-1] == "0": print("0"*(125-i)+a[:i+1])

For multi bit of input, every initial bit 1 will become a “source point” of the bit core a to grow. And at the meet point of two “waves”, the HIBIT plane starts to be activated. Within narrow column widths, some rows begin to fill with consecutive bits 1. In the LOBIT plane, the triangular region generated by wave coincidence overlaps and appears to affect the position outside the right boundary of the left wave source to the right. After testing, it was found that this process seems to be a binary addition, with the left side being the low bit and the right side being the high bit, and keeping the carry during the process. Script for simulation and validation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
def MATRIX_CORE():
a = "110101100011001110011000110010000011000000101010010011010101011111011100001001110011000000000111101001111101111101011100011011"
matrix = []
for i in range(126):
if not (a[:i+1])[-1] == "0":
row = "0" * (125 - i) + a[:i+1]
matrix.append(list(row))
return matrix

def read_matrix_from_file(filename):
matrix = []
with open(filename, 'r') as f:
for line in f:
line = line.strip()
if line: matrix.append(list(line))
return matrix

def SHL(matrix, N):
if not matrix: return matrix
new_matrix = []
for row in matrix:
new_row = row[N:] + ['0'] * N
new_matrix.append(new_row)
return new_matrix

def XOR(matrix1, matrix2):
assert len(matrix1) == len(matrix2)
result = []
for row1, row2 in zip(matrix1, matrix2):
assert len(row1) == len(row2)
xor_row = [str(int(b1) ^ int(b2)) for b1, b2 in zip(row1, row2)]
result.append(xor_row)
return result

def ADD(matrix1, matrix2):
assert len(matrix1) == len(matrix2)
result = []
for row1, row2 in zip(matrix1, matrix2):
assert len(row1) == len(row2)
carry = 0
sum_row = []
for b1, b2 in zip(row1, row2):
total = int(b1) + int(b2) + carry
sum_row.append(str(total % 2))
carry = total // 2
result.append(sum_row)
return result

def LIST_ADD(matrix_list):
if len(matrix_list) == 1: return matrix_list[0]
result = matrix_list[0]
for matrix in matrix_list[1:]: result = ADD(result, matrix)
return result

def RELOCATE(matrix):
targets = [0, 1, 3, 5, 6, 10, 11, 14, 15, 16, 19, 20, 24, 25, 28, 34, 35, 42, 44, 46, 49, 52, 53, 55, 57, 59, 61, 62, 63, 64, 65, 67, 68, 69, 74, 77, 78, 79, 82, 83, 93, 94, 95, 96, 98, 101, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 115, 116, 117, 121, 122, 124, 125]
if len(matrix) != 63: return matrix
if not matrix: return matrix
col_width = len(matrix[0])
new_matrix = [['0'] * col_width for _ in range(126)]
for i, row_data in enumerate(matrix):
if i < len(targets):
t = targets[i]
if 0 <= t < 126: new_matrix[t] = row_data
return new_matrix

def EXTEND_LEFT(matrix, N):
if not matrix: return matrix
new_matrix = []
for row in matrix:
new_row = ['0'] * N + row
new_matrix.append(new_row)
return new_matrix

def REMOVE_LEFT(matrix, N):
if not matrix: return matrix
new_matrix = []
for row in matrix:
new_row = row[N:]
new_matrix.append(new_row)
return new_matrix

def print_matrix(matrix):
for row in matrix:
binstr = ''.join(row)
print(binstr.replace('0', '░').replace('1', '█'))

def save_matrix_to_file(matrix, filename):
with open(filename, 'w') as f:
for row in matrix: f.write(''.join(row) + '\\n')

def hex2bit(inp):
bn = ""
for b in inp: bn += f"{b & 0x7F:07b}"[::-1]
return bn

def LOBIT(inp):
binp = hex2bit(inp)
mat = EXTEND_LEFT(MATRIX_CORE(), 126)
mats = []
for i in range(126):
if binp[i] == "1": mats.append(SHL(mat, 125-i))
res = LIST_ADD(mats)
return REMOVE_LEFT(res, 126)

inp = bytearray.fromhex("666c6167666c6167666c6167666c6167666c")
LOBIT_res = LOBIT(inp)
# Verify
res = read_matrix_from_file('plain1flag.txt')
diff = XOR(LOBIT_res, res)
print_matrix(diff) # All-zero

And for the HIBIT plane, its rule is very complex:

  • For the input bit stream, define that a block is a region composed of consecutive bit 1s, then,
  • For each block within the region on the LOBIT plane, if there is at least one bit 0 in that region, we call it an active block.
  • For each row of the HIBIT plane state matrix, first find all active blocks in this line, and then fill bit 1 from the leftmost bit 0.
  • The filling will stop at the earlier of the two types of boundaries:
  • The start point of the next active block on the right side, or,
  • The first LOBIT bit 1 that doesn’t come from the input location.
  • For the rightmost active block, if case 2 doesn’t exist, then it will stop at the end of the block.

Script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def hex2bit(inp):
bn = ""
for b in inp: bn += f"{b & 0x7F:07b}"[::-1]
return bn

def LOAD_MAT(filename):
matrix = []
with open(filename, 'r') as f:
for line in f:
line = line.strip()
if line: matrix.append(list(line))
return matrix

def PRINT_MAT(matrix):
for row in matrix:
binstr = ''.join(row)
print(binstr.replace('0', '░').replace('1', '█'))

def SAVE_MAT(matrix, filename):
with open(filename, 'w') as f:
for row in matrix: f.write(''.join(row) + '\\n')

def EXTRACT_BITS(matrix, row_index, col_start, col_end):
assert not (row_index < 0 or row_index >= len(matrix) or col_start < 0 or col_end >= len(matrix[0]) or col_start > col_end)
return ''.join(matrix[row_index][col_start:col_end + 1])

def UPDATE_MAT(matrix, row_index, col_start, col_end, new_bit):
assert not (row_index < 0 or row_index >= len(matrix) or col_start < 0 or col_end >= len(matrix[0]) or col_start > col_end)
if isinstance(new_bit, str): new_bit = list(new_bit)
length = col_end - col_start + 1
assert len(new_bit) == length
for i, bit in enumerate(new_bit): matrix[row_index][col_start + i] = bit
return matrix

def EXTEND_LEFT(matrix, N):
if not matrix: return matrix
new_matrix = []
for row in matrix:
new_row = ['0'] * N + row
new_matrix.append(new_row)
return new_matrix

def REMOVE_LEFT(matrix, N):
if not matrix: return matrix
new_matrix = []
for row in matrix:
new_row = row[N:]
new_matrix.append(new_row)
return new_matrix

def MATRIX_CORE():
a = "110101100011001110011000110010000011000000101010010011010101011111011100001001110011000000000111101001111101111101011100011011"
matrix = []
for i in range(126):
if not (a[:i+1])[-1] == "0":
row = "0" * (125 - i) + a[:i+1]
matrix.append(list(row))
return matrix

def RELOCATE(matrix):
targets = [0, 1, 3, 5, 6, 10, 11, 14, 15, 16, 19, 20, 24, 25, 28, 34, 35, 42, 44, 46, 49, 52, 53, 55, 57, 59, 61, 62, 63, 64, 65, 67, 68, 69, 74, 77, 78, 79, 82, 83, 93, 94, 95, 96, 98, 101, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 115, 116, 117, 121, 122, 124, 125]
if len(matrix) != 63: return matrix
if not matrix: return matrix
col_width = len(matrix[0])
new_matrix = [['0'] * col_width for _ in range(126)]
for i, row_data in enumerate(matrix):
if i < len(targets):
t = targets[i]
if 0 <= t < 126: new_matrix[t] = row_data
return new_matrix

def SHL(matrix, N):
if not matrix: return matrix
new_matrix = []
for row in matrix:
new_row = row[N:] + ['0'] * N
new_matrix.append(new_row)
return new_matrix

def XOR(matrix1, matrix2):
assert len(matrix1) == len(matrix2)
result = []
for row1, row2 in zip(matrix1, matrix2):
assert len(row1) == len(row2)
xor_row = [str(int(b1) ^ int(b2)) for b1, b2 in zip(row1, row2)]
result.append(xor_row)
return result

def ADD(matrix1, matrix2):
assert len(matrix1) == len(matrix2)
result = []
for row1, row2 in zip(matrix1, matrix2):
assert len(row1) == len(row2)
carry = 0
sum_row = []
for b1, b2 in zip(row1, row2):
total = int(b1) + int(b2) + carry
sum_row.append(str(total % 2))
carry = total // 2
result.append(sum_row)
return result

def LIST_ADD(matrix_list):
if len(matrix_list) == 1: return matrix_list[0]
result = matrix_list[0]
for matrix in matrix_list[1:]: result = ADD(result, matrix)
return result

def ADD_WITH_CARRY(matrix1, matrix2):
result = []
carries = []
for row1, row2 in zip(matrix1, matrix2):
carry = 0
sum_row = []
carry_row = []
for b1, b2 in zip(row1, row2):
total = int(b1) + int(b2) + carry
sum_row.append(total & 1)
carry = total >> 1
carry_row.append(1 if carry > 0 else 0)
result.append(sum_row)
carries.append(carry_row)
return result, carries

def LOBIT(inp):
binp = hex2bit(inp)
mat = EXTEND_LEFT(MATRIX_CORE(), 126)
mats = []
for i in range(126):
if binp[i] == "1": mats.append(SHL(mat, 125-i))
res = LIST_ADD(mats)
return REMOVE_LEFT(res, 126)

def FIND_BLOCKS(bits):
out = []
i = 0
while i < len(bits):
if bits[i] == '1':
l = i
while i + 1 < len(bits) and bits[i + 1] == '1':
i += 1
r = i
out.append((l, r))
i += 1
return out

def HIBIT(inp):
LOBIT_plain = LOBIT(inp)
input_bits = hex2bit(inp)
blocks = FIND_BLOCKS(input_bits)
H = [['0'] * 126 for _ in range(63)]
for row in range(63):
active = []
for l, r in blocks:
seg = ''.join(LOBIT_plain[row][l:r+1])
if '0' in seg:
alpha = l + seg.index('0')
active.append((l, r, alpha))
for i, (l, r, alpha) in enumerate(active):
gamma = None
for x in range(r + 1, 126):
if input_bits[x] == '0' and LOBIT_plain[row][x] == '1': gamma = x; break
if i + 1 < len(active):
next_alpha = active[i + 1][2]
beta = next_alpha if gamma is None else min(gamma, next_alpha)
else: beta = gamma if gamma is not None else (r + 1)
for x in range(alpha, beta): H[row][x] = '1'
return H

inp = bytearray.fromhex("666c6167666c6167666c6167666c6167666c")
HIBIT_res = HIBIT(inp)
res = LOAD_MAT('plain2flag.txt')
diff = XOR(HIBIT_res, res)
PRINT_MAT(diff)

Summary

This part realized a big algorithm on a 126 * 63 matrix. The workdir offset is 0xf4e ~ 0xfcb, and the matrix is from 0xfcc to 0x4dd0, delta of each row is 0xfc. Hash of part 3 is stored at offset 0x0 and keeps updating through the whole process. The only constant of the whole process is the diffusion core a. The process produces a 2-bit stream in the end, and the low bit is like a binary multiplication, high bit is like a signature of the matrix state.

VI. Chains On Part 5

Part 5 (index (138652) ~ (140667))can also be classified like Part 4:

chain index type sym value offset range value note
0 3HI16+1LO16 + 2 * LO16 _0x7f00 0xfcc ~ 0x4dcf + 0x4cd6 ~ 0x4ecc 0x0, 0x1, 0x2, 0x3, 0x100, 0x101, 0x102, 0x103 (value);
0x7f00, 0x7f7f, 0x7f80, 0x8000, 0x807f, 0x8080 (carry_out) 126chars * 3lines * 2sub_chains
1 ~ (1LO16+2LO16) * 126 _0x8100 0xfcc ~ 0x4cd4 + 0x4cd6 ~ 0x4dcf + 0x4e4f ~ 0x4ecc 0x100, 0x101 (rare), 0x17f, 0x180, 0x10000, 0x10001 (rare), 0x1007f, 0x10080
2 (1HI16+1LO16) + 1HI16 _0x7f7e00 0x4e4e ~ 0x4ecb + 0x4dd0 ~ 0x4e4d + 0x0 0x7f, 0x80, 0x17f, 0x180; 0x7f7e00, 0x7f7f00, 0x7f8000, 0x7f8100; hash control hash
3 3HI16+1LO16 _0xffc08100 0x0 + 0x4dcf ~ 0x4ecc 0x3f, 0x40, 0x13f, 0x140; 0x0, 0x1, 0x100, 0x101; hash; 0xffc00100, 0xffc10000 control hash

Among them, part of chain 2 and 3 are actually one chain, and it controls the hash. Extract the chain can get 251 deltas in total, as 126 * (-64/-63) and 125 * (+127/+128), and if treat -64 as bit 1 and -63 as bit 0, it is the same as an empty bit + treat 128 as bit 1 and 127 as bit 0, which means these two chains are controlled by one bit stream. However, this stream is not from LOBIT or HIBIT stream of part 4. Let’s temporarily call this stream Hs. Script to extract this chain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import csv

def extr(filename, start=138652, end=140662):
result = []
with open(filename, 'r') as f:
for line in f:
parts = line.strip().split(',')
if len(parts) >= 3:
try:
if start <= int(parts[0]) <= end: result.append(int(parts[-1]))
except ValueError: pass
return result

def add(list1, list2):
res = [0] * len(list1)
for i in range(len(list1)): res[i] = list1[i] + list2[i]
return res

def PRINT_VEC(vector):
for i in range(126): print(vector[i],end="")
print()

numbers = extr("chain_result_complex_flagflagflagflagfl.csv")
even_num = [-b-63 for b in numbers[::2]]
odd_num = [0] + [(b+256)%256-127 for b in numbers[1::2]]
PRINT_VEC(even_num)
PRINT_VEC(odd_num)

Then, extract the _0x7f7e00 chain on offset 0x4dd0 ~ 0x4e4d, and for each value do ((val - 0x7f0000) >> 8) - 0x7E, can get a 2-bit stream, and its high bit is the hash control stream Hs. For the low bit stream, it’s another unknown bit stream, call it as stream L. Script to extract this chain and verify the stream Hs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import csv

def extr(filename):
result = []
with open(filename, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
try:
off_hex_str = row['off_hex'].strip()
off_hex_value = int(off_hex_str, 16)
if 0 < off_hex_value < 0x4E4E:
value_hex = row['value_hex'].strip()
result.append(value_hex)
except (ValueError, KeyError): continue
return result

def PRINT_VEC(vector):
for i in range(126): print(vector[i],end="")
print()

if __name__ == "__main__":
vh = extr("out_flagflagflagflagfl_filtered_7f7e00.csv")
vl = [((int(v, 16) - 0x7f0000) >> 8) - 0x7E for v in vh]
hb = [i // 2 for i in vl]
PRINT_VEC(hb)
lb = [i % 2 for i in vl]
PRINT_VEC(lb)

And for offset 0x4e4e ~ 0x4ecb, it’s another chain, the high bit is SHR(low_bit, 1), temporarily call it C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import csv

def extr(filename):
result = []
with open(filename, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
try:
off_hex_str = row['off_hex'].strip()
off_hex_value = int(off_hex_str, 16)
if 0x4E4D < off_hex_value < 0x4ECC:
value_hex = row['value_hex'].strip()
result.append(value_hex)
except (ValueError, KeyError): continue
return result

def PRINT_VEC(vector):
for i in range(126): print(vector[i],end="")
print()

if __name__ == "__main__":
vh = extr("out_flagflagflagflagfl_filtered_7f7e00.csv")
vl = [((int(v, 16) - 0x7f) >> 7) + ((int(v, 16) - 0x7f) & 1) for v in vh]
hb = [i // 2 for i in vl]
PRINT_VEC(hb)
lb = [i % 2 for i in vl]
PRINT_VEC(lb)

For chain _0x8100, in fact it has three sub chains:

1. sub chain “B”: part 4 matrix row 62 ROL with 1

After transformation, the add value of this chain is actually the last row of the matrix in the part 4. Specifically, it is (62,1) to (62, 125) and then (62, 0):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import csv

def extr(filename):
result = []
with open(filename, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
try:
off_hex_str = row['off_hex'].strip()
off_hex_value = int(off_hex_str, 16)
if 0x4cd4 <= off_hex_value <= 0x4DD0 and off_hex_value % 2 == 0:
value_hex = row['addend_hex'].strip()
result.append(value_hex)
except (ValueError, KeyError): continue
return result

def dec(addend):
hi = (addend >> 8) & 0xff
lo = addend & 0xff
L = 1 if hi == 0x80 else 0
H = 1 if lo == 0x80 else 0
return L, H, (2*H + L)

def PRINT_VEC(vector):
for i in range(126): print(vector[i],end="")
print()

if __name__ == "__main__":
vh = extr("out_flagflagflagflagfl_filtered_8100.csv")
vl = [int(v, 16) for v in vh]
hb, lb, hl = [], [], []
for i in range(126): l, h, t = dec(vl[i]); hb.append(h); lb.append(l); hl.append(t)
PRINT_VEC(hb)
PRINT_VEC(lb)

# LOBIT: 110100000011101101110111100011111110110001011011011111011111101110010110001100100100110101011011100100110010001110001100001110
# HIBIT: 001001111110011000001111001100000000001111100000111100110000011001101111110011111011001000100110011000001111111101110011111001

# LB: 101000000111011011101111000111111101100010110110111110111111011100101100011001001001101010110111001001100100011100011000011101
# HB: 010011111100110000011110011000000000011111000001111001100000110011011111100111110110010001001100110000011111111011100111110000

Temporarily call this stream B.

2. sub chain prefix “A”

There’s an irregular chain with offset 0x0fcc <= off <= 0x4dce and off % 2 == 0 and off not in B_offsets or off == 0x4dcf, first extract the coordinate of this chain :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import csv

BASE = 0x0fcc
ROW_STRIDE = 0x00fc
B_OFFSETS = set(range(0x4cd4, 0x4dd0, 2))

def off_to_rc(off):
d = off - BASE
r = d // ROW_STRIDE
c = (d % ROW_STRIDE) // 2
return r, c

def extract_A_chain(filename):
out = []
with open(filename, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
off = int(row['off_hex'].strip(), 16)
add = int(row['addend_hex'].strip(), 16)
if 0x0fcc <= off <= 0x4dce and off % 2 == 0 and off not in B_OFFSETS or off == 0x4dcf:
r, c = off_to_rc(off)
out.append({"idx": int(row["idx"]), "off": off, "r": r, "c": c, "addend": add})
return out

if __name__ == "__main__":
A = extract_A_chain("out_flagflagflagflagfl_filtered_8100.csv")
for i in range(126): print(hex(P[i]['off']), A[i]['r'], A[i]['c'])

The coordinates are:

1
[(0, 0), (1, 0), (1, 1), (2, 0), (2, 1), (3, 0), (4, 0), (4, 1), (4, 2), (4, 3), (5, 0), (6, 0), (6, 1), (6, 2), (7, 0), (8, 0), (9, 0), (9, 1), (9, 2), (10, 0), (11, 0), (11, 1), (11, 2), (11, 3), (12, 0), (13, 0), (13, 1), (13, 2), (14, 0), (14, 1), (14, 2), (14, 3), (14, 4), (14, 5), (15, 0), (16, 0), (16, 1), (16, 2), (16, 3), (16, 4), (16, 5), (16, 6), (17, 0), (17, 1), (18, 0), (18, 1), (19, 0), (19, 1), (19, 2), (20, 0), (20, 1), (20, 2), (21, 0), (22, 0), (22, 1), (23, 0), (23, 1), (24, 0), (24, 1), (25, 0), (25, 1), (26, 0), (27, 0), (28, 0), (29, 0), (30, 0), (30, 1), (31, 0), (32, 0), (33, 0), (33, 1), (33, 2), (33, 3), (33, 4), (34, 0), (34, 1), (34, 2), (35, 0), (36, 0), (37, 0), (37, 1), (37, 2), (38, 0), (39, 0), (39, 1), (39, 2), (39, 3), (39, 4), (39, 5), (39, 6), (39, 7), (39, 8), (39, 9), (40, 0), (41, 0), (42, 0), (43, 0), (43, 1), (44, 0), (44, 1), (44, 2), (45, 0), (46, 0), (47, 0), (48, 0), (49, 0), (49, 1), (50, 0), (51, 0), (52, 0), (53, 0), (54, 0), (54, 1), (55, 0), (55, 1), (56, 0), (57, 0), (58, 0), (58, 1), (58, 2), (58, 3), (59, 0), (60, 0), (60, 1), (61, 0)]

Calculate from trace:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import csv

BASE = 0x0fcc
ROW_STRIDE = 0x00fc
A_PREFIX_LEN = [1,2,2,1,4,1,3,1,1,3,1,4,1,3,6,1,7,2,2,3,3,1,2,2,2,2,1,1,1,1,2,1,1,5,3,1,1,3,1,10,1,1,1,2,3,1,1,1,1,2,1,1,1,1,2,2,1,1,4,1,2,1]
TAIL_OFF = 0x4dcf

def rc_to_off(r, c): return BASE + r * ROW_STRIDE + 2 * c

def build_A_coords():
coords = []
for r, n in enumerate(A_PREFIX_LEN):
for c in range(n): coords.append((r, c))
return coords

def build_A_offsets(include_tail=True):
coords = build_A_coords()
offs = [rc_to_off(r, c) for (r, c) in coords]
if include_tail: offs.append(TAIL_OFF)
return offs

def dec(addend):
hi = (addend >> 8) & 0xff
lo = addend & 0xff
H = 1 if hi == 0x80 else 0
L = 1 if lo == 0x80 else 0
return H, L, (2 * H + L)

def extract_A(filename, include_tail=True):
wanted = build_A_offsets(include_tail=include_tail)
wanted_set = set(wanted)
found = {}
with open(filename, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
try:
off = int(row['off_hex'].strip(), 16)
if off in wanted_set: found[off] = int(row['addend_hex'].strip(), 16)
except Exception: continue
seq = []
for off in wanted:
if off not in found: raise ValueError(f"lack offset: {hex(off)}")
seq.append(found[off])
return seq

def PRINT_VEC(vector):
for x in vector: print(x, end="")
print()

if __name__ == "__main__":
vals = extract_A("out_flagflagflagflagfl_filtered_8100.csv", include_tail=True)
print("len =", len(vals))
print("last =", hex(vals[-1]))
hb, lb, hl = [], [], []
for v in vals:
h, l, t = dec(v)
hb.append(h)
lb.append(l)
hl.append(t)
print("A_H:")
PRINT_VEC(hb)
print("A_L:")
PRINT_VEC(lb)
print("A_T:")
print(hl)

3. the full chain “T”: A + B

In fact, the chain L and Hs come from one central chain T, and the function to calculate T is:

  • First get the chain A and B
  • Slice them to A_high, A_low, B_high and B_low
  • T = A_high + B_high
  • L = LOBIT(T); H = HIBIT(T)

The total calculation script is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import csv

BASE = 0x0fcc
ROW_STRIDE = 0x00fc
A_PREFIX_LEN = [1,2,2,1,4,1,3,1,1,3,1,4,1,3,6,1,7,2,2,3,3,1,2,2,2,2,1,1,1,1,2,1,1,5,3,1,1,3,1,10,1,1,1,2,3,1,1,1,1,2,1,1,1,1,2,2,1,1,4,1,2,1]
TAIL_OFF = 0x4dcf

def rc_to_off(r, c): return BASE + r * ROW_STRIDE + 2 * c

def build_A_coords():
coords = []
for r, n in enumerate(A_PREFIX_LEN):
for c in range(n): coords.append((r, c))
return coords

def build_A_offsets(include_tail=True):
coords = build_A_coords()
offs = [rc_to_off(r, c) for (r, c) in coords]
if include_tail: offs.append(TAIL_OFF)
return offs

def extract_A(filename, include_tail=True):
wanted = build_A_offsets(include_tail=include_tail)
wanted_set = set(wanted)
found = {}
with open(filename, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
try:
off = int(row['off_hex'].strip(), 16)
if off in wanted_set: found[off] = int(row['addend_hex'].strip(), 16)
except Exception: continue
seq = []
for off in wanted:
if off not in found: raise ValueError(f"lack offset: {hex(off)}")
seq.append(found[off])
return seq

def dec(addend):
hi = (addend >> 8) & 0xff
lo = addend & 0xff
H = 1 if hi == 0x80 else 0
L = 1 if lo == 0x80 else 0
return H, L

def PRINT_VEC(vector):
for i in range(126): print(vector[i],end="")
print()

def STR2BITS(s): return [int(ch) for ch in s.strip()]

def BITS2STR(v): return ''.join(str(x) for x in v)

def CALC_T(A1_str, B1_str):
A1 = STR2BITS(A1_str)
B1 = STR2BITS(B1_str)
assert len(A1) == len(B1)
C_in = []
T_hi = []
T_lo = []
T_q = []
c = 0
for a, b in zip(A1, B1):
C_in.append(c)
total = a + b + c
T_q.append(total)
T_lo.append(total & 1)
c = 1 if total >= 2 else 0
T_hi.append(c)
return (BITS2STR(T_hi), BITS2STR(T_lo), T_q)

def extr_rule_b(filename):
result = []
with open(filename, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
try:
off_hex_str = row['off_hex'].strip()
off_hex_value = int(off_hex_str, 16)
if 0x4cd4 <= off_hex_value <= 0x4DD0 and off_hex_value % 2 == 0:
value_hex = row['addend_hex'].strip()
result.append(value_hex)
except (ValueError, KeyError): continue
return result

def CALC_B(filename):
vh = extr_rule_b(filename)
vl = [int(v, 16) for v in vh]
hb, lb = [], []
for i in range(126): h, l = dec(vl[i]); hb.append(h); lb.append(l)
return BITS2STR(hb), BITS2STR(lb)

def CALC_A(filename):
vals = extract_A(filename, include_tail=True)
hb, lb = [], []
for v in vals: h, l = dec(v); hb.append(h); lb.append(l)
return BITS2STR(hb), BITS2STR(lb)

filename = "out_flagflagflagflagfl_filtered_8100.csv"
A1, A2 = CALC_A(filename)
B1, B2 = CALC_B(filename)
T_hi, T_lo, T_q = CALC_T(A1, B1)
print("T_hi : ", T_hi)
print("T_lo : ", T_lo)
print("T : ", T_q)

assert T_hi == "000000000011001111111111000111111101111000000010111111111111111111100000011111000000111010111111100000111100001110000000011111"
assert T_lo == "111001010100110000000100101001010110100110111101001000001011001000111111100000111011000101000001011101000110010001011100000001"

But, don’t forget this version of calculation relies on the trace file. The stream A only has 125 bits in fact. The 126th bit is from a special index 0x4dcf, which comes from ~B[124] accroding to the trace. So the full calculation script for Part 3,4 and 5 is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
def HASH(inp):
assert len(inp) == 18
b_h = 0x9B5C
b_s = b"111111111111111111"
_sum = 0
for i in range(18): _sum += (18 - i) * (inp[i] - b_s[i])
fix = sum(1 for c in inp if not c & 1) // 2
return (b_h + 0x38 * _sum - fix + 0x11) & 0xFFFF

def HEX2BIT(inp):
bn = ""
for b in inp: bn += f"{b & 0x7F:07b}"[::-1]
return bn

def LOAD_MAT(filename):
matrix = []
with open(filename, 'r') as f:
for line in f:
line = line.strip()
if line: matrix.append(list(line))
return matrix

def PRINT_MAT(matrix):
for row in matrix:
binstr = ''.join(row)
print(binstr.replace('0', '░').replace('1', '█'))

def SAVE_MAT(matrix, filename):
with open(filename, 'w') as f:
for row in matrix: f.write(''.join(row) + '\\n')

def EXTRACT_BITS(matrix, row_index, col_start, col_end):
assert not (row_index < 0 or row_index >= len(matrix) or col_start < 0 or col_end >= len(matrix[0]) or col_start > col_end)
return ''.join(matrix[row_index][col_start:col_end + 1])

def UPDATE_MAT(matrix, row_index, col_start, col_end, new_bit):
assert not (row_index < 0 or row_index >= len(matrix) or col_start < 0 or col_end >= len(matrix[0]) or col_start > col_end)
if isinstance(new_bit, str): new_bit = list(new_bit)
length = col_end - col_start + 1
assert len(new_bit) == length
for i, bit in enumerate(new_bit): matrix[row_index][col_start + i] = bit
return matrix

def EXTEND_LEFT(matrix, N):
if not matrix: return matrix
new_matrix = []
for row in matrix:
new_row = ['0'] * N + row
new_matrix.append(new_row)
return new_matrix

def REMOVE_LEFT(matrix, N):
if not matrix: return matrix
new_matrix = []
for row in matrix:
new_row = row[N:]
new_matrix.append(new_row)
return new_matrix

def MATRIX_CORE():
a = "110101100011001110011000110010000011000000101010010011010101011111011100001001110011000000000111101001111101111101011100011011"
matrix = []
for i in range(126):
if not (a[:i+1])[-1] == "0":
row = "0" * (125 - i) + a[:i+1]
matrix.append(list(row))
return matrix

def RELOCATE(matrix):
targets = [0, 1, 3, 5, 6, 10, 11, 14, 15, 16, 19, 20, 24, 25, 28, 34, 35, 42, 44, 46, 49, 52, 53, 55, 57, 59, 61, 62, 63, 64, 65, 67, 68, 69, 74, 77, 78, 79, 82, 83, 93, 94, 95, 96, 98, 101, 102, 103, 104, 105, 107, 108, 109, 110, 111, 113, 115, 116, 117, 121, 122, 124, 125]
if len(matrix) != 63: return matrix
if not matrix: return matrix
col_width = len(matrix[0])
new_matrix = [['0'] * col_width for _ in range(126)]
for i, row_data in enumerate(matrix):
if i < len(targets):
t = targets[i]
if 0 <= t < 126: new_matrix[t] = row_data
return new_matrix

def SHL(matrix, N):
if not matrix: return matrix
new_matrix = []
for row in matrix:
new_row = row[N:] + ['0'] * N
new_matrix.append(new_row)
return new_matrix

def XOR(matrix1, matrix2):
assert len(matrix1) == len(matrix2)
result = []
for row1, row2 in zip(matrix1, matrix2):
assert len(row1) == len(row2)
xor_row = [str(int(b1) ^ int(b2)) for b1, b2 in zip(row1, row2)]
result.append(xor_row)
return result

def ADD(matrix1, matrix2):
assert len(matrix1) == len(matrix2)
result = []
for row1, row2 in zip(matrix1, matrix2):
assert len(row1) == len(row2)
carry = 0
sum_row = []
for b1, b2 in zip(row1, row2):
total = int(b1) + int(b2) + carry
sum_row.append(str(total % 2))
carry = total // 2
result.append(sum_row)
return result

def LIST_ADD(matrix_list):
if len(matrix_list) == 1: return matrix_list[0]
result = matrix_list[0]
for matrix in matrix_list[1:]: result = ADD(result, matrix)
return result

def ADD_WITH_CARRY(matrix1, matrix2):
result = []
carries = []
for row1, row2 in zip(matrix1, matrix2):
carry = 0
sum_row = []
carry_row = []
for b1, b2 in zip(row1, row2):
total = int(b1) + int(b2) + carry
sum_row.append(total & 1)
carry = total >> 1
carry_row.append(1 if carry > 0 else 0)
result.append(sum_row)
carries.append(carry_row)
return result, carries

def LOBIT(inp):
binp = HEX2BIT(inp)
mat = EXTEND_LEFT(MATRIX_CORE(), 126)
mats = []
for i in range(126):
if binp[i] == "1": mats.append(SHL(mat, 125-i))
res = LIST_ADD(mats)
return REMOVE_LEFT(res, 126)

def FIND_BLOCKS(bits):
out = []
i = 0
while i < len(bits):
if bits[i] == '1':
l = i
while i + 1 < len(bits) and bits[i + 1] == '1':
i += 1
r = i
out.append((l, r))
i += 1
return out

def HIBIT(inp):
LOBIT_plain = LOBIT(inp)
input_bits = HEX2BIT(inp)
blocks = FIND_BLOCKS(input_bits)
H = [['0'] * 126 for _ in range(63)]
for row in range(63):
active = []
for l, r in blocks:
seg = ''.join(LOBIT_plain[row][l:r+1])
if '0' in seg:
alpha = l + seg.index('0')
active.append((l, r, alpha))
for i, (l, r, alpha) in enumerate(active):
gamma = None
for x in range(r + 1, 126):
if input_bits[x] == '0' and LOBIT_plain[row][x] == '1': gamma = x; break
if i + 1 < len(active):
next_alpha = active[i + 1][2]
beta = next_alpha if gamma is None else min(gamma, next_alpha)
else: beta = gamma if gamma is not None else (r + 1)
for x in range(alpha, beta): H[row][x] = '1'
return H

A_PREFIX_LEN = [1,2,2,1,4,1,3,1,1,3,1,4,1,3,6,1,7,2,2,3,3,1,2,2,2,2,1,1,1,1,2,1,1,5,3,1,1,3,1,10,1,1,1,2,3,1,1,1,1,2,1,1,1,1,2,2,1,1,4,1,2,1]

def BIT2STR(v): return ''.join(str(x) for x in v)

def MATBIT(mat, r, c): return int(mat[r][c])

def PART5_A_COORDS():
coords = []
for r, n in enumerate(A_PREFIX_LEN):
for c in range(n): coords.append((r, c))
assert len(coords) == 125
return coords

def CALC_B_FROM_MATRIX(LOBIT_mat, HIBIT_mat):
order = list(range(1, 126)) + [0]
B1 = [MATBIT(LOBIT_mat, 62, c) for c in order]
B2 = [MATBIT(HIBIT_mat, 62, c) for c in order]
assert len(B1) == 126 and len(B2) == 126
return B1, B2

def CALC_A_FROM_MATRIX(LOBIT_mat, HIBIT_mat):
coords = PART5_A_COORDS()
A1 = [MATBIT(LOBIT_mat, r, c) for (r, c) in coords]
A2 = [MATBIT(HIBIT_mat, r, c) for (r, c) in coords]
assert len(A1) == 125 and len(A2) == 125
return A1, A2

def BUILD_CF_RAW(A1_core, B1):
tail_hi = 1 - B1[124]
tail_lo = A1_core[0] ^ B1[0]
cf_raw = (tail_hi << 8) | tail_lo
return tail_hi, tail_lo, cf_raw

def BUILD_A_FULL(A1_core, A2_core, B1):
tail_hi, tail_lo, cf_raw = BUILD_CF_RAW(A1_core, B1)
A1 = A1_core + [tail_hi]
A2 = A2_core + [0]
return A1, A2, cf_raw

def CALC_T(A1, B1):
c = 0
C_in, T_hi, T_lo, T_q = [], [], [], []
for a, b in zip(A1, B1):
C_in.append(c)
total = a + b + c
T_q.append(total)
T_lo.append(total & 1)
c = 1 if total >= 2 else 0
T_hi.append(c)
return C_in, T_hi, T_lo, T_q

def PART345(inp):
assert len(inp) == 18
h3 = HASH(inp)
L = LOBIT(inp)
H = HIBIT(inp)
A1, A2 = CALC_A_FROM_MATRIX(L, H)
B1, B2 = CALC_B_FROM_MATRIX(L, H)
A1, A2, cf_raw = BUILD_A_FULL(A1, A2, B1)
C_in, T_hi, T_lo, T_q = CALC_T(A1, B1)
return {"hash3": h3,"LOBIT_mat": L,"HIBIT_mat": H,"LOBIT_bottom": ''.join(L[62]),"HIBIT_bottom": ''.join(H[62]),"A1": A1,"A2": A2,"B1": B1,"B2": B2,"C_in": C_in,"T_hi": T_hi,"T_lo": T_lo,"T_q": T_q}

def NOT(s):
table = str.maketrans('0', '10')
return s.translate(table)

if __name__ == "__main__":
inp = bytearray.fromhex("666c6167666c6167666c6167666c6167666c")
res = PART345(inp)
print("bottom LOBIT:", res["LOBIT_bottom"])
print("bottom HIBIT:", res["HIBIT_bottom"])
print("A1:", BIT2STR(res["A1"]))
print("A2:", BIT2STR(res["A2"]))
print("B1:", BIT2STR(res["B1"]))
print("B2:", BIT2STR(res["B2"]))
print("C :", BIT2STR(res["C_in"]))
print("T_hi:", BIT2STR(res["T_hi"]))
print("T_lo:", BIT2STR(res["T_lo"]))
print("~T_lo:", NOT(BIT2STR(res["T_lo"])))
print("T_q :", BIT2STR(res["T_q"]))
HASH_res = res['hash3']
HIBIT_res = res["HIBIT_mat"]
print("hash3:", hex(HASH_res & 0xFFFF))
for i in range(126): HASH_res += int(HEX2BIT(inp)[i]) * 0x3F
for i in range(63*126): HASH_res -= int(HIBIT_res[i//126][i%126]) + 0x3F
print("hash4:", hex(HASH_res & 0xFFFF))
for i in range(126): HASH_res -= (int(BIT2STR(res["T_hi"])[i]) + 0x3F)
for i in range(1,126): HASH_res += (int(BIT2STR(res["T_hi"])[i]) + 0x7F)
print("hash5:", hex(HASH_res & 0xFFFF))

VII. Final Algorithm And Verification On Part 6

After part 5, we have a 2-bit stream T, 2 low bit slice of 2 * 2-bit stream A2, B2 and a hash Hp5. Next, the stage goes to 6 and the encryption is at the final ending.

Part 6 has many _syms, as: _0x100, _0x200, _0x7e00, _0x7f00, _0x8100, _0xff00, _0x7e7f00, _0x7f7f00, _0xff7f8100, _0xff808100, _0xff818100, which means that _sym can no longer be used as a classification standard.

Algorithm

There exists a sub part from index 140667 to ~ 141047 in the beginning of part 6, and it changes T to 0x7f00 + 0x100⋅T_hi[i]+T_lo[i]. Source T is from 0x4dd00x4e4d and then `T_lo * 0x100` written to offert 0x4ecc~0x4f4a. Separate the three chains of this sub part:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import pandas as pd
import sys
import os

def filter_csv(input_file):
if not os.path.exists(input_file): return
try:
df = pd.read_csv(input_file)
df['off'] = pd.to_numeric(df['off'], errors='coerce')
df['typ'] = pd.to_numeric(df['typ'], errors='coerce')
if 'sym' in df.columns:
def parse_sym(val):
if pd.isna(val):
return None
val_str = str(val).strip()
if val_str.startswith('_'):
val_str = val_str[1:]
if val_str.startswith('0x'):
try: return int(val_str, 16)
except: return None
else:
try: return int(val_str)
except: return None
df['sym_parsed'] = df['sym'].apply(parse_sym)
else: return
print(df['sym_parsed'].value_counts().head(10))
filtered_df = df[(df['idx'] >= 140667) & (df['idx'] < 151097)]
if 'sym_parsed' in filtered_df.columns: filtered_df = filtered_df.drop(columns=['sym_parsed'])
base_name = os.path.splitext(input_file)[0]
output_file = f"{base_name}_filtered.csv"
filtered_df.to_csv(output_file, index=False)
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()

if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python filter_csv.py <input_csv_file>")
sys.exit(1)
input_file = sys.argv[1]
filter_csv(input_file)

import csv

def extr(filename, start=140667, end=141047):
result_7f00 = []
result_8100 = []
with open(filename, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
try:
idx = int(row['idx'])
if start <= idx <= end:
sym = row['sym'].strip()
value = int(row['value'])
if sym == '_0x7f00': result_7f00.append(value)
elif sym == '_0x8100': result_8100.append(value)
except (ValueError, KeyError) as e: continue
return result_7f00, result_8100

def add(list1, list2):
res = [0] * len(list1)
for i in range(len(list1)):
res[i] = list1[i] + list2[i]
return res

def PRINT_VEC(vector):
for i in range(len(vector)): print(vector[i], end="")
print()

def VEC_TO_STR(vector): return "".join(map(str, vector))

def LOBIT(vector): return [i&1 for i in vector]

def HIBIT(vector): return [i//2 for i in vector]

def decode(p):
for i in range(len(p)):
if p[i] == 0x10000: p[i] = 0
if p[i] == 0x10001: p[i] = 1
if p[i] == 0x100: p[i] = 2
if p[i] == 0x101: p[i] = 3
return p

def bit2hex(inp):
result = bytearray()
for i in range(0, len(inp), 7): result.append(int(inp[i:i+7][::-1], 2))
return result

numbers_7f00, numbers_8100 = extr("out_flagflagflagflagfl_filtered_p6.csv")
numbers_7f00 = [((i-0x7f00) >> 7) + ((i-0x7f00) & 1) for i in numbers_7f00]
PRINT_VEC(numbers_7f00)
PRINT_VEC(HIBIT(numbers_7f00))
PRINT_VEC(LOBIT(numbers_7f00))
print()
num_8100_1 = [(i >> 7) + (i & 1) for i in numbers_8100[::2]]
num_8100_2 = decode(numbers_8100[1::2])
PRINT_VEC(num_8100_1)
PRINT_VEC(HIBIT(num_8100_1))
PRINT_VEC(LOBIT(num_8100_1))
print()
PRINT_VEC(num_8100_2)
PRINT_VEC(HIBIT(num_8100_2))
PRINT_VEC(LOBIT(num_8100_2))

The _0x7f00 chain is 0x4dd0 ~ 0x4e4e and the same as the _0x8100 odd index chain as T_lo+T_lo[0] on high bit, the low bits of these two bit streams are both their high_bit[1:]+"0" , while the even index is a bit more complex, as T_lo[0]+NOT(T_lo) on high bit and NOT(T_lo+T_lo[0]) on low bit.

Then it goes to a quite messy part. It can be seen that there still seems to be a certain regularity in this stage, but it is extremely weak. During this stage, a value of an offset 0x4f4c slowly and intermittently growing from 0. Extract shows it will try to grow 450 times, using different and irregular _syms and raw values that related to the input. There are 5 kinds of symbols in total, and they will let the counter grow 1 only when:

  • _0x100: sext16(r) ≥ 0x7f00
  • _0x200: sext16(r) ≥ 0x7e00
  • _0xff00: sext16(r) ≥−0x7f00
  • _0x7f00: sext16(r) ≥ 0x100
  • _0x7e00: sext16(r) ≥ 0x200

And the choice of the symbol and the offset of the raw value are all fixed, not random or related to the input. The only variants are the raw values. Extract this chain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import pandas as pd
import sys
import os

def filter_csv(input_file):
if not os.path.exists(input_file): return
try:
df = pd.read_csv(input_file)
df['off'] = pd.to_numeric(df['off'], errors='coerce')
df['typ'] = pd.to_numeric(df['typ'], errors='coerce')
if 'sym' in df.columns:
def parse_sym(val):
if pd.isna(val):
return None
val_str = str(val).strip()
if val_str.startswith('_'):
val_str = val_str[1:]
if val_str.startswith('0x'):
try: return int(val_str, 16)
except: return None
else:
try: return int(val_str)
except: return None
df['sym_parsed'] = df['sym'].apply(parse_sym)
else: return
print(df['sym_parsed'].value_counts().head(10))
filtered_df = df[(df['idx'] > 140667) & (df['idx'] < 160000) & (df['off'] == 0x4f4c)]
if 'sym_parsed' in filtered_df.columns: filtered_df = filtered_df.drop(columns=['sym_parsed'])
base_name = os.path.splitext(input_file)[0]
output_file = f"{base_name}_filtered.csv"
filtered_df.to_csv(output_file, index=False)
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()

if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python filter_csv.py <input_csv_file>")
sys.exit(1)
input_file = sys.argv[1]
filter_csv(input_file)

Then:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import csv
import re
from collections import defaultdict, Counter

TARGET_OFF = 0x4f4c
THRESHOLDS = {"_0x100":0x7f00,"_0x200":0x7e00,"_0xff00":-0x7f00,"_0x7f00":0x0100,"_0x7e00":0x0200}
TOKEN_RE = re.compile(r"match-lo16@0x([0-9a-fA-F]+)\\s+raw=0x([0-9a-fA-F]+)\\s+sext=([\\-0-9]+)")
CONT_RE = re.compile(r"contents&mask@0x([0-9a-fA-F]+)=0x([0-9a-fA-F]+)")

def parse_int(s):
s = s.strip()
if s.startswith("0x") or s.startswith("0X"): return int(s, 16)
return int(s)

def sext16(x):
x &= 0xffff
return x - 0x10000 if x & 0x8000 else x

def parse_match_info(addend_src: str):
m = TOKEN_RE.search(addend_src or "")
if not m: return None, None, None
match_off = int(m.group(1), 16)
raw = int(m.group(2), 16)
sext = int(m.group(3))
return match_off, raw, sext

def parse_old_contents(addend_src: str):
m = CONT_RE.search(addend_src or "")
if not m: return None
return int(m.group(2), 16)

def predicted_pass(sym: str, token: int):
if sym not in THRESHOLDS or token is None: return None
return 1 if sext16(token) >= THRESHOLDS[sym] else 0

def classify_visible_diff(diff: int):
if diff == 0: return "+0"
if diff == 1: return "+1"
if diff == 126: return "+126"
if diff == 127: return "+127"
if diff == -128: return "-128"
return f"other({diff})"

def extract_4f4c_chain(filename: str):
rows = []
with open(filename, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
try:
off = parse_int(row["off_hex"])
typ_name = row["typ_name"].strip()
sym = row["sym"].strip()
except Exception: continue
if off != TARGET_OFF: continue
if typ_name != "R_MIPS_HI16": continue
if sym not in THRESHOLDS: continue
idx = int(row["idx"])
value = parse_int(row["value"])
value_hex = row["value_hex"].strip()
addend_src = row.get("addend_src", "")
match_off, token, token_sext = parse_match_info(addend_src)
old_contents = parse_old_contents(addend_src)
pred = predicted_pass(sym, token)
rows.append({"idx": idx,"sym": sym,"off": off,"value": value,"value_hex": value_hex,"token": token,"token_sext": token_sext,"match_off": match_off,"old_contents": old_contents,"pred_pass": pred,"addend_src": addend_src,})

for i, r in enumerate(rows):
if i == 0: r["diff"] = None
else: r["diff"] = r["value"] - rows[i - 1]["value"]
return rows

def print_chain(rows):
print(f"total steps = {len(rows)}")
print()
for i, r in enumerate(rows):
tok = "None" if r["token"] is None else f"0x{r['token']:04x}"
tsext = "None" if r["token_sext"] is None else f"{r['token_sext']:+6d}"
moff = "None" if r["match_off"] is None else hex(r["match_off"])
diff = "None" if r["diff"] is None else str(r["diff"])
dcls = "" if r["diff"] is None else f"[{classify_visible_diff(r['diff'])}]"
print(f"{i:03d} idx={r['idx']:6d} sym={r['sym']:8s} token={tok:>6s} ({tsext}) value={r['value']:4d} (0x{int(r['value_hex'], 16):03x}) diff={diff:>4s} {dcls:>8s} pred={r['pred_pass']} match={moff}")

def print_stats(rows):
print("\\n=== by sym ===")
bysym = defaultdict(list)
for r in rows:
bysym[r["sym"]].append(r)

for sym, items in bysym.items():
diffs = Counter(classify_visible_diff(x["diff"]) for x in items if x["diff"] is not None)
preds = Counter(x["pred_pass"] for x in items)
print(f"{sym}: total={len(items)}, preds={dict(preds)}, diffs={dict(diffs)}")

print("\\n=== by token ===")
bytok = defaultdict(list)
for r in rows: bytok[r["token"]].append(r)

for tok, items in sorted(bytok.items(), key=lambda kv: (kv[0] is None, kv[0])):
name = "None" if tok is None else f"0x{tok:04x}"
preds = Counter(x["pred_pass"] for x in items)
diffs = Counter(classify_visible_diff(x["diff"]) for x in items if x["diff"] is not None)
syms = Counter(x["sym"] for x in items)
print(f"{name}: n={len(items):02d}, preds={dict(preds)}, syms={dict(syms)}, diffs={dict(diffs)}")

if __name__ == "__main__":
rows = extract_4f4c_chain("out_flagflagflagflagfl_filtered.csv")
print_chain(rows)
print_stats(rows)

Observing shows that the offset range of this chain focus on 0x4dd1∼0x53db, and can be divide into 3 sub parts, one > 0x4f4c, and keeps linear growing, the second 0x4ed3 < < 0x4f4c, and the third 0x4dd1 < < 0x4e4d. The bottom 2 sub parts looks totally irregular:

However, don’t forget the offset range of T and T’s re-encoding area. In fact, these two parts are just these two areas. Record the two bit streams generated by _0x8100 in the previous subprocess as O(dd) and E(ven), then:

  • For the lowerest dots (0x4dd1~0x4e4d): 74 xrefs at O, 55 unique indices as [1, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 18, 22, 25, 29, 30, 33, 36, 39, 42, 45, 46, 48, 52, 53, 55, 56, 58, 59, 61, 68, 70, 71, 72, 74, 77, 79, 80, 81, 89, 90, 94, 97, 100, 104, 105, 108, 114, 115, 117, 119, 122, 123, 124, 125]
  • For the middle dots (0x4ed3~0x4f4b): 87 xrefs at E, 60 unique indices as [7, 8, 9, 12, 14, 17, 19, 20, 22, 23, 29, 34, 35, 37, 38, 39, 40, 41, 43, 45, 51, 52, 53, 54, 55, 56, 59, 60, 61, 65, 66, 67, 70, 72, 73, 77, 80, 81, 82, 84, 87, 92, 97, 99, 102, 103, 104, 105, 106, 108, 109, 115, 116, 117, 119, 120, 121, 122, 124, 127]
  • For the upper line (0x4f53~0x53db): 289 xrefs at the offsets the above two indices generated.

All tokens become 7*2 = 14 types ov values: [0x0, 0x1, 0x100, 0x101, 0x200, 0x201, 0x7e00, 0x7e01, 0x7f00, 0x7f01, 0x8000, 0x8001, 0x8100, 0x8101].

Due to the algorithm being too unordered here, we will temporarily skip and analyze the subsequent verification section.

Verification

Part 6 after the above area only has 69 lines left before part 7, and there’s no place for giant algorithm to realize. As the max value of 0x4f4c is 450,try patching (link_patch_probe.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import argparse
from typing import Dict, Tuple, Optional, List
from link_debug_slice_ir2 import MiniMipsSlicePlus, build_sym_source, parse_int, RELOC_NAME, GATE_LABELS, sign_extend, fits_signed_16, R_MIPS_16, w32be

def parse_patch_spec(spec: str) -> Tuple[str, int, int]:
if "=" not in spec or ":" not in spec:
raise ValueError(f"bad patch spec: {spec}")
lhs, rhs = spec.split("=", 1)
kind, key = lhs.split(":", 1)
kind = kind.strip().lower()
if kind not in {"off", "idx"}:
raise ValueError(f"patch kind must be off or idx: {spec}")
return kind, parse_int(key.strip()), parse_int(rhs.strip())

class PatchableTracer(MiniMipsSlicePlus):
def __init__(self, *args, patch_addend=None, patch_value=None, patch_init_word=None, **kwargs):
super().__init__(*args, **kwargs)
self.patch_addend = patch_addend or {}
self.patch_value = patch_value or {}
self.patch_init_word = patch_init_word or {}
self.patch_log: List[str] = []
if self.patch_init_word:
for (kind, key), value in self.patch_init_word.items():
if kind != "off":
raise ValueError("patch-init-word only supports off:<offset>=<value>")
old = int.from_bytes(self.contents[key:key+4], "big", signed=False)
w32be(self.contents, key, value)
w32be(self.initial_contents, key, value)
self.patch_log.append(f"init-word patch off=0x{key:x}: 0x{old:08x} -> 0x{value & 0xffffffff:08x}")

def _lookup_patch(self, table: Dict[Tuple[str, int], int], rec) -> Optional[int]:
if ("idx", rec.idx) in table:
return table[("idx", rec.idx)]
if ("off", rec.off) in table:
return table[("off", rec.off)]
return None

def apply(self):
saved_addend = 0
saved_from_prev = False
saved_from_idx = None

for rec in self.relocs:
self.reloc_counter[rec.typ] += 1
semantic_deps = []
notes = []

if not saved_from_prev:
addend, expr, sem0, notes0 = self._read_rel_addend(rec)
semantic_deps.extend(sem0)
notes.extend(notes0)
if rec.typ == 5:
addend, expr, sem1, notes1 = self._add_lo16_rel_addend(rec, addend, expr)
semantic_deps.extend(sem1)
notes.extend(notes1)
addend, expr = self._adjust_addend(rec, addend, expr)
else:
addend = saved_addend
from link_debug_slice_ir2 import TraceExpr
expr = TraceExpr(f"SAVED_ADDEND from #{saved_from_idx} -> 0x{addend:08x}", [saved_from_idx] if saved_from_idx is not None else [], [])
if saved_from_idx is not None:
semantic_deps.append(("saved-addend-from", saved_from_idx))
notes.append(f"saved-addend from #{saved_from_idx}")

patched_addend = self._lookup_patch(self.patch_addend, rec)
if patched_addend is not None:
self.patch_log.append(f"patch-addend #{rec.idx} off=0x{rec.off:x} {RELOC_NAME.get(rec.typ, rec.typ)}: 0x{addend & 0xffffffff:08x} -> 0x{patched_addend & 0xffffffff:08x}")
addend = patched_addend & 0xffffffff
notes.append(f"PATCH addend -> 0x{addend:08x}")

save_for_next = self._next_same_offset_nonzero(rec.idx)
status, value, calc_text, calc_expr = self._calculate_relocation(rec, addend, expr)

patched_value = self._lookup_patch(self.patch_value, rec)
if patched_value is not None:
old_value = value & 0xffffffff
value = patched_value & 0xffffffff
if rec.typ == R_MIPS_16:
signed_v = value if value <= 0x7fffffff else value - 0x100000000
status = "ok" if fits_signed_16(signed_v) else "overflow"
else:
status = "ok"
calc_text += f" [PATCH value 0x{old_value:08x} -> 0x{value:08x}]"
notes.append(f"PATCH value -> 0x{value:08x}")
self.patch_log.append(f"patch-value #{rec.idx} off=0x{rec.off:x} {RELOC_NAME.get(rec.typ, rec.typ)}: 0x{old_value:08x} -> 0x{value:08x}")

from link_debug_slice_ir2 import RelocTrace
trace = RelocTrace(
idx=rec.idx,
off=rec.off,
typ=rec.typ,
sym=rec.sym,
S=rec.S,
addend=addend & 0xffffffff,
addend_expr=calc_expr,
save_for_next=save_for_next,
status=status,
value=value & 0xffffffff,
calc_text=calc_text,
saved_from_prev=saved_from_prev,
semantic_deps=list(dict.fromkeys(semantic_deps)),
notes=notes)
self.trace_by_idx[rec.idx] = trace

if rec.typ == R_MIPS_16:
self.final_actual_r16.append(trace)

if status == "overflow":
A = sign_extend(addend, 16) if rec.typ == R_MIPS_16 else addend
self.errors.append((rec.idx, rec.off, rec.typ, rec.sym, rec.S, A, value & 0xffffffff))
if self.stop_on_error:
break

if save_for_next:
saved_addend = value & 0xffffffff
saved_from_prev = True
saved_from_idx = rec.idx
else:
saved_from_prev = False
saved_from_idx = None
self._store_contents(rec, value, trace)

return self

def main():
ap = argparse.ArgumentParser(description="Patch probe for linkme relocation VM")
ap.add_argument("--obj", default="flagchecker.o")
ap.add_argument("--sym-elf")
ap.add_argument("--normalize-to-a", action="store_true")
ap.add_argument("--rebase", type=parse_int, default=0)
ap.add_argument("--chars", default="kalmar{flagflagflagflagfl}")
ap.add_argument("--base-addr", type=parse_int, default=0)
ap.add_argument("--auto-build-dummy", action="store_true")
ap.add_argument("--workdir")
ap.add_argument("--watch-offset", action="append", default=[])
ap.add_argument("--context-before", type=int, default=25)
ap.add_argument("--context-after", type=int, default=8)
ap.add_argument("--stop-on-error", action="store_true")
ap.add_argument("--dump-gates", action="store_true")
ap.add_argument("--slice-first-error", action="store_true")
ap.add_argument("--slice-depth", type=int, default=10)
ap.add_argument("--patch-addend", action="append", default=[])
ap.add_argument("--patch-value", action="append", default=[])
ap.add_argument("--patch-init-word", action="append", default=[])
ap.add_argument("--focus-reloc", action="append", default=[])
args = ap.parse_args()

patch_addend = {}
patch_value = {}
patch_init_word = {}
for spec in args.patch_addend:
k, key, val = parse_patch_spec(spec)
patch_addend[(k, key)] = val
for spec in args.patch_value:
k, key, val = parse_patch_spec(spec)
patch_value[(k, key)] = val
for spec in args.patch_init_word:
k, key, val = parse_patch_spec(spec)
patch_init_word[(k, key)] = val

watched = {parse_int(x) for x in args.watch_offset}
ext_symvals, sym_desc, aux = build_sym_source(args)
chars = None if ext_symvals is not None else [ord(c) for c in args.chars]

tracer = PatchableTracer(
args.obj,
chars=chars,
char_template=args.chars,
base_addr=args.base_addr,
ext_symvals=ext_symvals,
watch_offsets=list(watched),
stop_on_error=args.stop_on_error,
patch_addend=patch_addend,
patch_value=patch_value,
patch_init_word=patch_init_word,
).apply()

print(f"[config] obj={args.obj}")
print(f"[config] symbol-source={sym_desc}")
for k, v in aux.items():
print(f"[config] {k}={v}")
if tracer.patch_log:
print("[patches]")
for line in tracer.patch_log:
print(" " + line)

if args.dump_gates:
tracer.dump_gate_summary()
err_idx = tracer.dump_first_error()
if err_idx is not None:
tracer.dump_context_around_index(err_idx, args.context_before, args.context_after)
for focus in args.focus_reloc:
tracer.dump_context_around_index(parse_int(focus), args.context_before, args.context_after)
if watched:
tracer.dump_watch_history(watched)
if args.slice_first_error:
tracer.dump_slice_from_first_error(max_depth=args.slice_depth)

if __name__ == "__main__": main()

Observation shows that the program can pass the verification only when the value of 0x4f4c is 450. In this way, the logic of verification becomes very concise: as long as all 450 verifications pass, all verifications pass.

Modeling and solving

The sources of all O/E/S are determined:

  • For O/E, because its high and low bit chains are formed by the addition of T_lo shifted, each bit of O/E comes from two bits of T_lo, namely the current bit and the previous bit.
  • For S, all S comes from the index of the previous line, as off_S - 1 , and the value is S_raw=((prev_value & 0xff)<<8). The line before it only has 3 symbols, as _0x7f00, _0x7e7f00 and _0x7f7f00. And each kind of symbol will only produce 3 kinds of tokens: {0x000, 0x1000, 0x200} for _0x7f00; {0x7e00, 0x7f00, 0x8000} for _0x7e7f00; and {0x7f00, 0x8000, 0x8100} for _0x7f7f00.

From this perspective, the token actually only has three values: 0, 1, and 2. Due to the fact that there are a total of 3 symbols that generate tokens, there should actually be 9 sets of tokens, but two of them duplicate two categories, so it has been reduced to 7. Similarly, there are actually 6 symbols used for verification, corresponding to the three major categories of greater than or equal to 1 and greater than or equal to 2 verification, as:

Family Range (0, 1, 2) Symbol of token Symbol of compare Result of compare (first/second) Meaning of symbol of compare (first/second)
1 0x000, 0x100, 0x200 _0x7f00 _0x7f00 (≥ 0x100), _0x7e00 (≥ 0x200) 0/0, 1/0, 1/1 token ≥ 1 ? 1:0 / token ≥ 2 ? 1:0
2 0x7e00, 0x7f00, 0x8000 _0x7e7f00 _0x200 (≥ 0x7e00), _0x100 (≥ 0x7f00) 1/0, 1/1, 0/0 token ≤ 1 ? 1:0 / token == 1 ? 1:0
3 0x7f00, 0x8000, 0x8100 _0x7f7f00 _0xff00 (≥ 0x8100), _0x100 (≥ 0x7f00) 1/1, 0/0, 1/0 token ≠ 1 ? 1:0 / token ≤ 0 ? 1:0

Extract the family and cmp symbol of 450 checks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
step,trace_idx,cmp_sym,category,first_layer_ref,src_off_hex,family
1,141062,_0x100,E,E[92],0x4f28,2
2,141093,_0x200,S,S+0x7,0x4f53,2
3,141119,_0xff00,S,S+0xa,0x4f56,3
4,141138,_0x200,E,E[12],0x4ed8,2
5,141170,_0x200,S,S+0x10,0x4f5c,2
6,141182,_0x100,E,E[67],0x4f0f,2
7,141194,_0x7f00,E,E[67],0x4f0f,1
8,141206,_0x100,E,E[43],0x4ef7,2
9,141231,_0x100,S,S+0x16,0x4f62,3
10,141243,_0x100,S,S+0x17,0x4f63,2
11,141273,_0x100,S,S+0x1b,0x4f67,2
12,141285,_0xff00,E,E[54],0x4f02,3
13,141315,_0x100,S,S+0x20,0x4f6c,2
14,141333,_0x200,S,S+0x22,0x4f6e,2
15,141357,_0x200,O,O[6],0x4dd6,2
16,141381,_0x7f00,E,E[41],0x4ef5,1
17,141412,_0xff00,E,E[56],0x4f04,3
18,141443,_0x100,S,S+0x30,0x4f7c,2
19,141455,_0x100,O,O[5],0x4dd5,2
20,141467,_0xff00,E,E[87],0x4f23,3
21,141491,_0x7f00,S,S+0x35,0x4f81,1
22,141516,_0xff00,S,S+0x38,0x4f84,3
23,141548,_0x200,O,O[61],0x4e0d,2
24,141567,_0x200,O,O[105],0x4e39,2
25,141597,_0x7f00,S,S+0x42,0x4f8e,1
26,141609,_0x7f00,S,S+0x43,0x4f8f,1
27,141633,_0xff00,O,O[12],0x4ddc,3
28,141658,_0x100,S,S+0x49,0x4f95,2
29,141676,_0x7e00,E,E[72],0x4f14,1
30,141706,_0x200,S,S+0x4f,0x4f9b,2
31,141724,_0x100,S,S+0x51,0x4f9d,2
32,141742,_0x100,S,S+0x53,0x4f9f,2
33,141768,_0x100,O,O[104],0x4e38,2
34,141798,_0x200,S,S+0x5a,0x4fa6,2
35,141810,_0x7f00,O,O[13],0x4ddd,1
36,141822,_0x7f00,S,S+0x5c,0x4fa8,1
37,141834,_0x100,O,O[46],0x4dfe,3
38,141858,_0x7f00,O,O[14],0x4dde,1
39,141890,_0x100,S,S+0x64,0x4fb0,2
40,141915,_0xff00,S,S+0x67,0x4fb3,3
41,141927,_0xff00,O,O[61],0x4e0d,3
42,141939,_0x7f00,E,E[53],0x4f01,1
43,141965,_0x100,O,O[77],0x4e1d,2
44,141995,_0x100,S,S+0x70,0x4fbc,3
45,142007,_0x200,O,O[18],0x4de2,2
46,142019,_0x100,S,S+0x72,0x4fbe,2
47,142037,_0x100,S,S+0x74,0x4fc0,2
48,142049,_0x7f00,E,E[77],0x4f19,1
49,142069,_0xff00,S,S+0x77,0x4fc3,3
50,142087,_0x100,S,S+0x79,0x4fc5,2
51,142117,_0x7f00,S,S+0x7d,0x4fc9,1
52,142147,_0x7f00,S,S+0x81,0x4fcd,1
53,142179,_0xff00,O,O[123],0x4e4b,3
54,142191,_0x7f00,E,E[104],0x4f34,1
55,142215,_0x100,S,S+0x89,0x4fd5,3
56,142227,_0xff00,S,S+0x8a,0x4fd6,3
57,142259,_0xff00,S,S+0x8e,0x4fda,3
58,142285,_0x7f00,S,S+0x91,0x4fdd,1
59,142311,_0x200,S,S+0x94,0x4fe0,2
60,142323,_0x7f00,E,E[7],0x4ed3,1
61,142341,_0x200,S,S+0x97,0x4fe3,2
62,142359,_0x7f00,S,S+0x99,0x4fe5,1
63,142371,_0x100,S,S+0x9a,0x4fe6,3
64,142405,_0x200,S,S+0x9e,0x4fea,2
65,142425,_0x7e00,S,S+0xa0,0x4fec,1
66,142457,_0x200,O,O[15],0x4ddf,2
67,142481,_0x7f00,S,S+0xa7,0x4ff3,1
68,142511,_0x7e00,S,S+0xab,0x4ff7,1
69,142529,_0xff00,S,S+0xad,0x4ff9,3
70,142541,_0x7e00,O,O[22],0x4de6,1
71,142573,_0x200,S,S+0xb2,0x4ffe,2
72,142585,_0x200,O,O[53],0x4e05,2
73,142609,_0x7e00,S,S+0xb6,0x5002,1
74,142628,_0x7f00,O,O[94],0x4e2e,1
75,142640,_0x100,E,E[121],0x4f45,2
76,142672,_0x200,S,S+0xbd,0x5009,2
77,142690,_0x200,S,S+0xbf,0x500b,2
78,142702,_0xff00,E,E[29],0x4ee9,3
79,142735,_0x200,S,S+0xc4,0x5010,2
80,142760,_0x100,S,S+0xc7,0x5013,2
81,142772,_0x100,E,E[117],0x4f41,2
82,142796,_0x7f00,S,S+0xcb,0x5017,1
83,142808,_0xff00,S,S+0xcc,0x5018,3
84,142826,_0x7e00,E,E[59],0x4f07,1
85,142852,_0x200,S,S+0xd1,0x501d,2
86,142870,_0x200,S,S+0xd3,0x501f,2
87,142888,_0xff00,E,E[80],0x4f1c,3
88,142908,_0x200,S,S+0xd7,0x5023,2
89,142920,_0x100,S,S+0xd8,0x5024,2
90,142950,_0x100,S,S+0xdc,0x5028,2
91,142980,_0x7f00,S,S+0xe0,0x502c,1
92,142992,_0xff00,S,S+0xe1,0x502d,3
93,143017,_0x100,S,S+0xe4,0x5030,2
94,143035,_0x7f00,O,O[53],0x4e05,1
95,143065,_0x7f00,S,S+0xea,0x5036,1
96,143083,_0x7f00,S,S+0xec,0x5038,1
97,143107,_0x100,E,E[61],0x4f09,2
98,143125,_0x7e00,S,S+0xf1,0x503d,1
99,143157,_0x100,S,S+0xf5,0x5041,3
100,143175,_0x100,O,O[30],0x4dee,3
101,143187,_0x100,O,O[71],0x4e17,3
102,143217,_0x200,S,S+0xfc,0x5048,2
103,143229,_0x7f00,S,S+0xfd,0x5049,1
104,143253,_0x100,S,S+0x100,0x504c,2
105,143279,_0x200,E,E[70],0x4f12,2
106,143303,_0x200,S,S+0x106,0x5052,2
107,143315,_0xff00,O,O[15],0x4ddf,3
108,143333,_0x100,S,S+0x109,0x5055,2
109,143359,_0x7f00,O,O[89],0x4e29,1
110,143383,_0x200,S,S+0x10f,0x505b,2
111,143414,_0x7e00,E,E[127],0x4f4b,1
112,143432,_0x7f00,S,S+0x115,0x5061,1
113,143451,_0x7e00,E,E[106],0x4f36,1
114,143483,_0x200,S,S+0x11b,0x5067,2
115,143513,_0x100,S,S+0x11f,0x506b,2
116,143538,_0x7f00,E,E[84],0x4f20,1
117,143558,_0x7e00,S,S+0x124,0x5070,1
118,143583,_0x200,S,S+0x127,0x5073,2
119,143613,_0x100,S,S+0x12b,0x5077,2
120,143631,_0x7f00,S,S+0x12d,0x5079,1
121,143643,_0xff00,E,E[105],0x4f35,3
122,143675,_0x100,S,S+0x132,0x507e,2
123,143693,_0x200,E,E[116],0x4f40,2
124,143705,_0x7f00,S,S+0x135,0x5081,1
125,143717,_0x200,O,O[7],0x4dd7,2
126,143741,_0x7f00,S,S+0x139,0x5085,1
127,143760,_0x7f00,E,E[17],0x4edd,1
128,143772,_0x200,E,E[119],0x4f43,2
129,143790,_0x7e00,S,S+0x13e,0x508a,1
130,143817,_0x7f00,S,S+0x141,0x508d,1
131,143847,_0x200,S,S+0x145,0x5091,2
132,143865,_0x7f00,E,E[127],0x4f4b,1
133,143892,_0x200,S,S+0x14a,0x5096,2
134,143922,_0x7f00,S,S+0x14e,0x509a,1
135,143952,_0x200,S,S+0x152,0x509e,2
136,143964,_0x100,S,S+0x153,0x509f,2
137,143983,_0xff00,E,E[41],0x4ef5,3
138,143995,_0x7f00,O,O[22],0x4de6,1
139,144013,_0x7f00,S,S+0x158,0x50a4,1
140,144043,_0x200,O,O[81],0x4e21,2
141,144055,_0x7f00,S,S+0x15d,0x50a9,1
142,144080,_0x7f00,S,S+0x160,0x50ac,1
143,144092,_0x7f00,O,O[7],0x4dd7,1
144,144104,_0x7f00,S,S+0x162,0x50ae,1
145,144123,_0xff00,O,O[13],0x4ddd,3
146,144135,_0x200,O,O[9],0x4dd9,2
147,144165,_0x200,S,S+0x169,0x50b5,2
148,144183,_0x7e00,E,E[66],0x4f0e,1
149,144202,_0x200,E,E[97],0x4f2d,2
150,144228,_0x100,E,E[38],0x4ef2,3
151,144252,_0xff00,S,S+0x173,0x50bf,3
152,144264,_0x7f00,O,O[36],0x4df4,1
153,144289,_0x100,S,S+0x177,0x50c3,2
154,144307,_0xff00,E,E[109],0x4f39,3
155,144325,_0x200,S,S+0x17b,0x50c7,2
156,144355,_0x100,S,S+0x17f,0x50cb,2
157,144387,_0x7f00,S,S+0x183,0x50cf,1
158,144417,_0x200,S,S+0x187,0x50d3,2
159,144429,_0x7e00,O,O[90],0x4e2a,1
160,144459,_0x100,S,S+0x18c,0x50d8,2
161,144471,_0x100,E,E[122],0x4f46,2
162,144503,_0x7f00,O,O[25],0x4de9,1
163,144527,_0x200,S,S+0x194,0x50e0,2
164,144560,_0xff00,S,S+0x198,0x50e4,3
165,144585,_0x200,E,E[116],0x4f40,2
166,144615,_0xff00,S,S+0x19f,0x50eb,3
167,144639,_0x200,S,S+0x1a2,0x50ee,2
168,144665,_0x100,E,E[122],0x4f46,2
169,144689,_0x7f00,S,S+0x1a8,0x50f4,1
170,144721,_0x100,O,O[122],0x4e4a,2
171,144751,_0x7f00,S,S+0x1b0,0x50fc,1
172,144763,_0x7e00,E,E[35],0x4eef,1
173,144781,_0x200,S,S+0x1b3,0x50ff,2
174,144805,_0x7f00,E,E[127],0x4f4b,1
175,144823,_0xff00,S,S+0x1b8,0x5104,3
176,144855,_0x7e00,S,S+0x1bc,0x5108,1
177,144873,_0xff00,O,O[56],0x4e08,3
178,144899,_0x7f00,S,S+0x1c1,0x510d,1
179,144929,_0xff00,E,E[22],0x4ee2,3
180,144953,_0xff00,S,S+0x1c8,0x5114,3
181,144980,_0x200,S,S+0x1cb,0x5117,2
182,145013,_0x100,O,O[11],0x4ddb,2
183,145025,_0x200,E,E[22],0x4ee2,2
184,145051,_0x200,E,E[92],0x4f28,2
185,145081,_0x200,S,S+0x1d7,0x5123,2
186,145114,_0x100,S,S+0x1db,0x5127,3
187,145146,_0x7e00,S,S+0x1df,0x512b,1
188,145158,_0x100,S,S+0x1e0,0x512c,3
189,145176,_0xff00,O,O[1],0x4dd1,3
190,145188,_0x7f00,S,S+0x1e3,0x512f,1
191,145222,_0xff00,S,S+0x1e7,0x5133,3
192,145240,_0x200,S,S+0x1e9,0x5135,2
193,145272,_0x7e00,S,S+0x1ed,0x5139,1
194,145302,_0x200,S,S+0x1f1,0x513d,2
195,145320,_0x100,E,E[109],0x4f39,3
196,145352,_0x100,S,S+0x1f7,0x5143,2
197,145382,_0xff00,S,S+0x1fb,0x5147,3
198,145408,_0x200,S,S+0x1fe,0x514a,2
199,145440,_0x7e00,E,E[34],0x4eee,1
200,145452,_0x200,S,S+0x203,0x514f,2
201,145470,_0x7e00,S,S+0x205,0x5151,1
202,145500,_0x200,O,O[74],0x4e1a,2
203,145526,_0x200,S,S+0x20c,0x5158,2
204,145544,_0x7e00,S,S+0x20e,0x515a,1
205,145562,_0xff00,E,E[60],0x4f08,3
206,145586,_0x200,S,S+0x213,0x515f,2
207,145598,_0x100,S,S+0x214,0x5160,3
208,145629,_0x100,O,O[45],0x4dfd,2
209,145659,_0x7f00,S,S+0x21c,0x5168,1
210,145671,_0x200,E,E[55],0x4f03,2
211,145683,_0x200,S,S+0x21e,0x516a,2
212,145701,_0x7f00,S,S+0x220,0x516c,1
213,145721,_0x100,S,S+0x222,0x516e,2
214,145733,_0x100,E,E[54],0x4f02,3
215,145757,_0x7f00,S,S+0x226,0x5172,1
216,145781,_0x200,E,E[104],0x4f34,2
217,145799,_0x7f00,S,S+0x22b,0x5177,1
218,145817,_0x200,S,S+0x22d,0x5179,2
219,145848,_0x7e00,S,S+0x231,0x517d,1
220,145868,_0x100,S,S+0x233,0x517f,3
221,145894,_0x100,S,S+0x236,0x5182,3
222,145912,_0x7e00,O,O[79],0x4e1f,1
223,145931,_0x100,E,E[9],0x4ed5,3
224,145961,_0x100,S,S+0x23e,0x518a,2
225,145985,_0x7e00,S,S+0x241,0x518d,1
226,146004,_0xff00,E,E[81],0x4f1d,3
227,146024,_0x200,S,S+0x245,0x5191,2
228,146044,_0x7f00,S,S+0x247,0x5193,1
229,146070,_0xff00,S,S+0x24a,0x5196,3
230,146090,_0x7f00,S,S+0x24c,0x5198,1
231,146116,_0x7f00,S,S+0x24f,0x519b,1
232,146148,_0x100,S,S+0x253,0x519f,3
233,146178,_0x7f00,S,S+0x257,0x51a3,1
234,146190,_0x200,S,S+0x258,0x51a4,2
235,146202,_0x100,E,E[17],0x4edd,2
236,146227,_0x7e00,S,S+0x25c,0x51a8,1
237,146251,_0x100,E,E[99],0x4f2f,2
238,146263,_0x7e00,O,O[29],0x4ded,1
239,146275,_0x200,S,S+0x261,0x51ad,2
240,146287,_0x7f00,S,S+0x262,0x51ae,1
241,146299,_0x200,S,S+0x263,0x51af,2
242,146329,_0x7f00,S,S+0x267,0x51b3,1
243,146341,_0x7e00,S,S+0x268,0x51b4,1
244,146371,_0x7e00,S,S+0x26c,0x51b8,1
245,146401,_0x200,S,S+0x270,0x51bc,2
246,146425,_0x200,S,S+0x273,0x51bf,2
247,146437,_0x7f00,S,S+0x274,0x51c0,1
248,146455,_0x7e00,S,S+0x276,0x51c2,1
249,146481,_0xff00,S,S+0x279,0x51c5,3
250,146513,_0xff00,S,S+0x27d,0x51c9,3
251,146537,_0x200,O,O[124],0x4e4c,2
252,146570,_0x7e00,E,E[77],0x4f19,1
253,146582,_0x7f00,S,S+0x285,0x51d1,1
254,146608,_0x100,E,E[82],0x4f1e,2
255,146640,_0xff00,S,S+0x28c,0x51d8,3
256,146664,_0x200,S,S+0x28f,0x51db,2
257,146697,_0x200,S,S+0x293,0x51df,2
258,146727,_0x100,S,S+0x297,0x51e3,2
259,146739,_0x100,S,S+0x298,0x51e4,2
260,146751,_0x100,S,S+0x299,0x51e5,3
261,146783,_0x200,S,S+0x29d,0x51e9,2
262,146801,_0x7f00,S,S+0x29f,0x51eb,1
263,146819,_0x7f00,O,O[114],0x4e42,1
264,146837,_0x200,E,E[67],0x4f0f,2
265,146855,_0x100,S,S+0x2a5,0x51f1,2
266,146873,_0x7f00,S,S+0x2a7,0x51f3,1
267,146903,_0x7e00,S,S+0x2ab,0x51f7,1
268,146933,_0x100,S,S+0x2af,0x51fb,2
269,146945,_0x7e00,S,S+0x2b0,0x51fc,1
270,146970,_0x200,S,S+0x2b3,0x51ff,2
271,147001,_0x7f00,E,E[124],0x4f48,1
272,147013,_0x7f00,S,S+0x2b8,0x5204,1
273,147031,_0x100,S,S+0x2ba,0x5206,3
274,147051,_0x7f00,S,S+0x2bc,0x5208,1
275,147069,_0x7e00,O,O[8],0x4dd8,1
276,147099,_0x200,S,S+0x2c2,0x520e,2
277,147111,_0x100,E,E[39],0x4ef3,3
278,147129,_0x100,S,S+0x2c5,0x5211,2
279,147153,_0x200,S,S+0x2c8,0x5214,2
280,147172,_0x7f00,E,E[51],0x4eff,1
281,147204,_0x200,S,S+0x2ce,0x521a,2
282,147216,_0x100,E,E[12],0x4ed8,3
283,147234,_0xff00,S,S+0x2d1,0x521d,3
284,147264,_0x100,O,O[108],0x4e3c,2
285,147276,_0xff00,E,E[65],0x4f0d,3
286,147301,_0x7f00,O,O[74],0x4e1a,1
287,147313,_0x7e00,O,O[119],0x4e47,1
288,147325,_0x100,S,S+0x2db,0x5227,2
289,147349,_0x200,O,O[59],0x4e0b,2
290,147361,_0x7e00,S,S+0x2df,0x522b,1
291,147379,_0x7e00,S,S+0x2e1,0x522d,1
292,147412,_0x100,S,S+0x2e5,0x5231,2
293,147430,_0x100,S,S+0x2e7,0x5233,2
294,147454,_0x200,S,S+0x2ea,0x5236,2
295,147478,_0x7f00,O,O[124],0x4e4c,1
296,147508,_0x200,S,S+0x2f1,0x523d,2
297,147520,_0xff00,E,E[120],0x4f44,3
298,147547,_0x7f00,S,S+0x2f5,0x5241,1
299,147571,_0x200,O,O[1],0x4dd1,2
300,147589,_0x200,S,S+0x2fa,0x5246,2
301,147601,_0xff00,S,S+0x2fb,0x5247,3
302,147613,_0x7f00,E,E[121],0x4f45,1
303,147645,_0xff00,S,S+0x300,0x524c,3
304,147675,_0xff00,S,S+0x304,0x5250,3
305,147707,_0x7f00,S,S+0x308,0x5254,1
306,147719,_0x100,E,E[52],0x4f00,2
307,147737,_0x7f00,O,O[70],0x4e16,1
308,147761,_0x100,S,S+0x30e,0x525a,2
309,147785,_0xff00,O,O[53],0x4e05,3
310,147815,_0x200,S,S+0x315,0x5261,2
311,147833,_0x200,S,S+0x317,0x5263,2
312,147845,_0x7f00,S,S+0x318,0x5264,1
313,147878,_0x7f00,E,E[117],0x4f41,1
314,147902,_0xff00,S,S+0x31f,0x526b,3
315,147914,_0x100,O,O[39],0x4df7,2
316,147940,_0x7e00,S,S+0x323,0x526f,1
317,147952,_0x200,S,S+0x324,0x5270,2
318,147985,_0x100,S,S+0x328,0x5274,3
319,148016,_0x100,S,S+0x32c,0x5278,2
320,148028,_0x7e00,S,S+0x32d,0x5279,1
321,148058,_0x7f00,O,O[119],0x4e47,1
322,148070,_0x7f00,O,O[100],0x4e34,1
323,148082,_0x200,E,E[102],0x4f32,2
324,148094,_0xff00,E,E[102],0x4f32,3
325,148112,_0x7f00,S,S+0x336,0x5282,1
326,148142,_0xff00,S,S+0x33a,0x5286,3
327,148160,_0x7f00,S,S+0x33c,0x5288,1
328,148184,_0xff00,O,O[29],0x4ded,3
329,148215,_0x200,E,E[29],0x4ee9,2
330,148245,_0x7f00,O,O[52],0x4e04,1
331,148257,_0x100,S,S+0x348,0x5294,3
332,148276,_0x7f00,E,E[61],0x4f09,1
333,148294,_0x7f00,S,S+0x34c,0x5298,1
334,148312,_0xff00,S,S+0x34e,0x529a,3
335,148342,_0x100,S,S+0x352,0x529e,2
336,148354,_0x200,O,O[42],0x4dfa,2
337,148366,_0x7f00,S,S+0x354,0x52a0,1
338,148378,_0x7f00,S,S+0x355,0x52a1,1
339,148402,_0xff00,E,E[108],0x4f38,3
340,148434,_0x7f00,S,S+0x35c,0x52a8,1
341,148465,_0x100,S,S+0x360,0x52ac,3
342,148489,_0x200,S,S+0x363,0x52af,2
343,148520,_0x100,E,E[115],0x4f3f,3
344,148544,_0x7f00,S,S+0x36a,0x52b6,1
345,148556,_0xff00,E,E[40],0x4ef4,3
346,148586,_0x7f00,S,S+0x36f,0x52bb,1
347,148618,_0x200,S,S+0x373,0x52bf,2
348,148644,_0x100,S,S+0x376,0x52c2,3
349,148674,_0x200,E,E[35],0x4eef,2
350,148698,_0x7f00,S,S+0x37d,0x52c9,1
351,148716,_0x7f00,S,S+0x37f,0x52cb,1
352,148728,_0x200,S,S+0x380,0x52cc,2
353,148746,_0x100,S,S+0x382,0x52ce,2
354,148770,_0x7f00,S,S+0x385,0x52d1,1
355,148803,_0xff00,S,S+0x389,0x52d5,3
356,148822,_0x7f00,O,O[33],0x4df1,1
357,148840,_0x200,E,E[12],0x4ed8,2
358,148864,_0x7f00,S,S+0x390,0x52dc,1
359,148876,_0x200,S,S+0x391,0x52dd,2
360,148894,_0x100,S,S+0x393,0x52df,3
361,148924,_0x200,S,S+0x397,0x52e3,2
362,148954,_0xff00,S,S+0x39b,0x52e7,3
363,148966,_0x7e00,S,S+0x39c,0x52e8,1
364,148986,_0x100,S,S+0x39e,0x52ea,2
365,149012,_0x200,S,S+0x3a1,0x52ed,2
366,149038,_0xff00,S,S+0x3a4,0x52f0,3
367,149064,_0x100,S,S+0x3a7,0x52f3,2
368,149083,_0xff00,O,O[48],0x4e00,3
369,149101,_0x100,S,S+0x3ab,0x52f7,2
370,149113,_0x7f00,S,S+0x3ac,0x52f8,1
371,149146,_0x7f00,S,S+0x3b0,0x52fc,1
372,149179,_0x7f00,S,S+0x3b4,0x5300,1
373,149199,_0x7f00,S,S+0x3b6,0x5302,1
374,149224,_0x7f00,S,S+0x3b9,0x5305,1
375,149249,_0xff00,S,S+0x3bc,0x5308,3
376,149261,_0x7f00,S,S+0x3bd,0x5309,1
377,149292,_0x7e00,O,O[77],0x4e1d,1
378,149325,_0x200,S,S+0x3c5,0x5311,2
379,149345,_0x200,S,S+0x3c7,0x5313,2
380,149363,_0x7f00,S,S+0x3c9,0x5315,1
381,149389,_0x7f00,O,O[100],0x4e34,1
382,149415,_0x7e00,S,S+0x3cf,0x531b,1
383,149446,_0x7f00,E,E[81],0x4f1d,1
384,149470,_0x200,E,E[103],0x4f33,2
385,149488,_0x200,S,S+0x3d8,0x5324,2
386,149514,_0xff00,S,S+0x3db,0x5327,3
387,149538,_0x7f00,O,O[108],0x4e3c,1
388,149570,_0xff00,S,S+0x3e2,0x532e,3
389,149600,_0x100,O,O[125],0x4e4d,2
390,149620,_0x100,S,S+0x3e8,0x5334,3
391,149644,_0xff00,S,S+0x3eb,0x5337,3
392,149668,_0x7e00,S,S+0x3ee,0x533a,1
393,149693,_0x200,E,E[87],0x4f23,2
394,149717,_0x100,E,E[23],0x4ee3,3
395,149735,_0x7f00,S,S+0x3f6,0x5342,1
396,149766,_0x200,O,O[114],0x4e42,2
397,149798,_0x100,S,S+0x3fe,0x534a,2
398,149829,_0x200,S,S+0x402,0x534e,2
399,149841,_0x200,O,O[81],0x4e21,2
400,149867,_0x7e00,O,O[7],0x4dd7,1
401,149898,_0x7f00,S,S+0x40a,0x5356,1
402,149910,_0x100,S,S+0x40b,0x5357,2
403,149941,_0x100,S,S+0x40f,0x535b,2
404,149953,_0xff00,O,O[72],0x4e18,3
405,149965,_0x200,S,S+0x411,0x535d,2
406,149984,_0x200,E,E[20],0x4ee0,2
407,150014,_0x200,O,O[97],0x4e31,2
408,150040,_0xff00,O,O[80],0x4e20,3
409,150071,_0x7f00,E,E[73],0x4f15,1
410,150095,_0x100,S,S+0x421,0x536d,2
411,150119,_0x200,E,E[37],0x4ef1,2
412,150143,_0x200,S,S+0x427,0x5373,2
413,150161,_0x200,E,E[14],0x4eda,2
414,150179,_0x7f00,S,S+0x42b,0x5377,1
415,150191,_0x7f00,S,S+0x42c,0x5378,1
416,150215,_0x7e00,S,S+0x42f,0x537b,1
417,150235,_0x200,S,S+0x431,0x537d,2
418,150265,_0xff00,S,S+0x435,0x5381,3
419,150297,_0x200,S,S+0x439,0x5385,2
420,150309,_0x200,S,S+0x43a,0x5386,2
421,150328,_0x100,E,E[45],0x4ef9,2
422,150358,_0x100,S,S+0x440,0x538c,2
423,150390,_0x200,S,S+0x444,0x5390,2
424,150409,_0x7e00,O,O[79],0x4e1f,1
425,150440,_0x100,S,S+0x44a,0x5396,2
426,150473,_0x100,E,E[19],0x4edf,2
427,150505,_0x100,S,S+0x452,0x539e,2
428,150529,_0x200,S,S+0x455,0x53a1,2
429,150541,_0x200,O,O[55],0x4e07,2
430,150568,_0x100,S,S+0x459,0x53a5,2
431,150598,_0x7f00,S,S+0x45d,0x53a9,1
432,150610,_0x100,S,S+0x45e,0x53aa,3
433,150636,_0x200,E,E[8],0x4ed4,2
434,150669,_0xff00,S,S+0x465,0x53b1,3
435,150699,_0xff00,S,S+0x469,0x53b5,3
436,150729,_0x7f00,S,S+0x46d,0x53b9,1
437,150755,_0x7f00,E,E[87],0x4f23,1
438,150767,_0xff00,S,S+0x471,0x53bd,3
439,150797,_0x7e00,E,E[51],0x4eff,1
440,150827,_0x200,O,O[115],0x4e43,2
441,150839,_0x7f00,E,E[80],0x4f1c,1
442,150857,_0x7f00,S,S+0x47c,0x53c8,1
443,150875,_0x7f00,S,S+0x47e,0x53ca,1
444,150893,_0x200,S,S+0x480,0x53cc,2
445,150911,_0x200,O,O[58],0x4e0a,2
446,150935,_0xff00,O,O[117],0x4e45,3
447,150965,_0xff00,S,S+0x489,0x53d5,3
448,150985,_0x200,O,O[68],0x4e14,2
449,150997,_0xff00,S,S+0x48c,0x53d8,3
450,151022,_0x7e00,S,S+0x48f,0x53db,1

Note that some of these indices are reused or not controlled by a single O/E, and these indices were mixed with other O/E sources during the construction phase before comparison, making them more complex. Fortunately, the subsequent token construction does not come from the previous check results and only inherits process data. Due to the illogical nature of 450 verifications, consider using the patch with reply with random init T_lo and hill-climbing algorithm:

First, write a script that can specialize patch part 6 and replay it to obtain the correct 4f4c (part6_tlo_bank_patch_replay.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
#!/usr/bin/env python3
import argparse
import csv
import re
import sys
from pathlib import Path
from typing import Dict, List, Optional, Sequence, Tuple
from link_trace_plot import MiniMipsLdLike, TraceRow, RELOC_NAME, parse_chars, u32be

DEFAULT_CUT_IDX = 141047
DEFAULT_START_IDX = 141048
DEFAULT_END_IDX = 151022
BASELINE_CHARS = 'kalmar{000000000000000000}'

Q_BASE_OFF = 0x4DD0
Q_COUNT = 127
Q_BYTES_START = Q_BASE_OFF + 2
Q_BYTES_LEN = Q_COUNT + 1

E_BASE_OFF = 0x4ECC
E_COUNT = 127
E_BYTES_START = E_BASE_OFF + 2
E_BYTES_LEN = E_COUNT + 1

RAW_RE = re.compile(r"raw=0x([0-9a-fA-F]+)")
CONTENTS_RE = re.compile(r"contents&mask@0x[0-9a-fA-F]+=0x([0-9a-fA-F]+)")

A_BITS = "110101100011001110011000110010000011000000101010010011010101011111011100001001110011000000000111101001111101111101011100011011"
A_PREFIX_LEN = (1, 2, 2, 1, 4, 1, 3, 1, 1, 3, 1, 4, 1, 3, 6, 1, 7, 2, 2, 3, 3, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 1, 1, 5, 3, 1, 1, 3, 1, 10, 1, 1, 1, 2, 3, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 1, 1, 4, 1, 2, 1)
ROW_WIDTH = 126
EXT_WIDTH = 252
MASK126 = (1 << ROW_WIDTH) - 1
MASK252 = (1 << EXT_WIDTH) - 1

def _row_str_to_int_le(row: str) -> int:
v = 0
for i, ch in enumerate(row):
if ch == '1': v |= 1 << i
return v

CORE_ROW_INTS = []
for i, ch in enumerate(A_BITS):
if ch == '1':
row = '0' * (125 - i) + A_BITS[: i + 1]
CORE_ROW_INTS.append(_row_str_to_int_le(row))
CORE_ROW_INTS = tuple(CORE_ROW_INTS)
A_PREFIX_MASKS = tuple((1 << n) - 1 for n in A_PREFIX_LEN)

class SnapshotReplay(MiniMipsLdLike):
def run_until_cut(self, cut_idx: int) -> bytearray:
self.trace_rows = []
self.events = []
self.write_log = []
self.errors = []
self.last_addend_by_idx = {}
self.last_addend_src_by_idx = {}
self._run_range(0, cut_idx)
return bytearray(self.contents)

def replay_from_snapshot(self, snapshot: bytearray, *, start_idx: int, end_idx: int):
self.contents = bytearray(snapshot)
self.trace_rows = []
self.events = []
self.write_log = []
self.errors = []
self.last_addend_by_idx = {}
self.last_addend_src_by_idx = {}
self._run_range(start_idx, end_idx)
return self

def _run_range(self, start_idx: int, end_idx: int):
addend = 0
saved_from_prev = False
for rec in self.relocs:
if rec.idx < start_idx: continue
if rec.idx > end_idx: break
self.reloc_counter[rec.typ] += 1
if not saved_from_prev:
addend, src = self._read_rel_addend(rec)
if rec.typ == 5:
addend, extra = self._add_lo16_rel_addend(rec, addend)
src = f"{src}; {extra}"
else:
addend &= 0xFFFFFFFF
src = f"{src}; <<0"
addend, adj = self._adjust_addend(rec, addend)
src = f"{src}; {adj}"
else:
src = 'saved-from-prev-same-offset'
self.last_addend_by_idx[rec.idx] = addend & 0xFFFFFFFF
self.last_addend_src_by_idx[rec.idx] = src
save_for_next = self._next_same_offset_nonzero(rec.idx)
status, value, calc = self._calculate_relocation(rec, addend)
self.trace_rows.append(TraceRow(
idx=rec.idx,
off=rec.off,
typ=rec.typ,
typ_name=RELOC_NAME.get(rec.typ, str(rec.typ)),
sym=rec.sym,
S=rec.S,
addend=addend & 0xFFFFFFFF,
addend_src=src,
value=value & 0xFFFFFFFF,
status=status,
save_for_next=save_for_next,
carry_out=(value & 0xFFFFFFFF) if save_for_next else None))
if status == 'overflow' and self.stop_on_error:
break
if save_for_next:
addend = value & 0xFFFFFFFF
saved_from_prev = True
else:
saved_from_prev = False
self._store_contents(rec, value)
return self

def extract_raw16(addend_src: str) -> str:
m = RAW_RE.search(addend_src)
if m: return f"0x{int(m.group(1), 16) & 0xFFFF:04x}"
m = CONTENTS_RE.search(addend_src)
if m: return f"0x{int(m.group(1), 16) & 0xFFFF:04x}"
return ''

def windows_to_byte_stream(windows: Sequence[int]) -> bytes:
if not windows:
return b''
out: List[int] = [((windows[0] >> 8) & 0xFF), (windows[0] & 0xFF)]
for i in range(1, len(windows)):
hi = (windows[i] >> 8) & 0xFF
lo = windows[i] & 0xFF
if out[i] != hi:
raise ValueError(f'inconsistent rolling windows at index {i}: prev low=0x{out[i]:02x} != next high=0x{hi:02x}')
out.append(lo)
return bytes(out)

def dump_bank_windows(buf: bytearray, *, base_off: int, count: int) -> List[int]: return [u32be(buf, base_off + i) & 0xFFFF for i in range(count)]

def dump_bank_bytes(buf: bytearray, *, bytes_start: int, bytes_len: int) -> bytes: return bytes(buf[bytes_start:bytes_start + bytes_len])

def write_bytes(buf: bytearray, off: int, data: bytes): buf[off:off + len(data)] = data

def load_targets(meta_csv: Optional[str]) -> Tuple[List[dict], set]:
if not meta_csv or not Path(meta_csv).exists(): return [], set()
with open(meta_csv, newline='', encoding='utf-8-sig') as f:
rdr = csv.DictReader(f)
rows = [r for r in rdr if any((v or '').strip() for v in r.values())]
if not rows: return [], set()
def _get_step(r): return int((r.get('step') or r.get('step') or '0').strip())
def _get_idx(r): return int((r.get('trace_idx') or r.get('idx') or '0').strip())
rows.sort(key=_get_step)
idxs = {_get_idx(r) for r in rows}
return rows, idxs

def write_token_csv(path: str, meta_rows: List[dict], token_by_idx: Dict[int, str]):
if not meta_rows: return
fields = list(meta_rows[0].keys()) + ['raw16']
with open(path, 'w', newline='', encoding='utf-8') as f:
w = csv.DictWriter(f, fieldnames=fields)
w.writeheader()
for r in meta_rows:
rr = dict(r)
rr['raw16'] = token_by_idx.get(int(r['trace_idx']), '')
w.writerow(rr)

def write_replay_trace_csv(path: str, trace_rows: Sequence[TraceRow]):
with open(path, 'w', newline='', encoding='utf-8') as f:
w = csv.writer(f)
w.writerow(['idx', 'off', 'off_hex', 'typ', 'typ_name', 'sym', 'addend_hex', 'value_hex', 'status', 'save_for_next', 'addend_src', 'raw16'])
for row in trace_rows:
w.writerow([
row.idx, row.off, f'0x{row.off:x}', row.typ, row.typ_name, row.sym,
f'0x{row.addend:x}', f'0x{row.value:x}', row.status, int(row.save_for_next), row.addend_src,
extract_raw16(row.addend_src)])

def write_bank_dump_csv(path: str, q_windows: Sequence[int], e_windows: Sequence[int], q_bytes: bytes, e_bytes: bytes):
n = max(len(q_windows), len(e_windows), len(q_bytes), len(e_bytes))
with open(path, 'w', newline='', encoding='utf-8') as f:
w = csv.writer(f)
w.writerow(['idx', 'q_window', 'e_window', 'q_byte', 'e_byte'])
for i in range(n):
w.writerow([
i,
f'0x{q_windows[i]:04x}' if i < len(q_windows) else '',
f'0x{e_windows[i]:04x}' if i < len(e_windows) else '',
f'{q_bytes[i]:08b}' if i < len(q_bytes) else '',
f'{e_bytes[i]:08b}' if i < len(e_bytes) else '',])

def _hex2bits_int(inp: bytearray) -> int:
v = 0
shift = 0
for b in inp:
x = b & 0x7F
v |= x << shift
shift += 7
return v

def _bitstr_le(v: int, width: int) -> str: return ''.join('1' if (v >> i) & 1 else '0' for i in range(width))

def _not_bitstr(s: str) -> str: return s.translate(str.maketrans('01', '10'))

def _lobit_rows_int_from_bits_int(bits_int: int) -> list[int]:
coeff = bits_int << 1
return [((row * coeff) & MASK252) >> ROW_WIDTH for row in CORE_ROW_INTS]

def _build_a1_core_int(lobit_rows: list[int]) -> int:
a1_core = 0
offset = 0
for row_int, mask, n in zip(lobit_rows[:62], A_PREFIX_MASKS, A_PREFIX_LEN):
a1_core |= (row_int & mask) << offset
offset += n
return a1_core

def _build_b1_int(bottom_row: int) -> int: return ((bottom_row >> 1) | ((bottom_row & 1) << 125)) & MASK126

def calc_toe(payload18: bytes) -> dict[str, str]:
assert len(payload18) == 18
bits_int = _hex2bits_int(bytearray(payload18))
lobit_rows = _lobit_rows_int_from_bits_int(bits_int)
a1_core_int = _build_a1_core_int(lobit_rows)
b1_int = _build_b1_int(lobit_rows[62])
tail_hi = 1 - ((b1_int >> 124) & 1)
a1_int = a1_core_int | (tail_hi << 125)
t_int = (a1_int + b1_int) & MASK126
t_lo = _bitstr_le(t_int, 126)
return {
'T_lo': t_lo,
'O_hi': t_lo + t_lo[0],
'O_lo': t_lo[1:] + t_lo[0] + '0',
'E_hi': t_lo[0] + _not_bitstr(t_lo),
'E_lo': _not_bitstr(t_lo + t_lo[0])}

def payload18_from_chars(chars: str) -> bytes:
if len(chars) == 18: return chars.encode('latin1')
if chars.startswith('kalmar{') and chars.endswith('}') and len(chars) == 26: return chars[7:-1].encode('latin1')
raise ValueError('chars must be 18-byte payload or full kalmar{18-chars}')

def parse_bit_assignments(spec: Optional[str]) -> Dict[int, str]:
if not spec: return {}
out: Dict[int, str] = {}
if spec.startswith('@'):
with open(spec[1:], newline='', encoding='utf-8') as f:
rdr = csv.DictReader(f)
for row in rdr: out[int(row['idx'])] = str(row['bit'])
return out
for part in spec.split(','):
part = part.strip()
if not part: continue
a, b = part.split('=')
b = b.strip()
if b not in ('0', '1'): raise ValueError(f'bit assignment must be 0 or 1: {part}')
out[int(a, 0)] = b
return out

def parse_bit_list(spec: Optional[str]) -> List[int]:
if not spec:
return []
if spec.startswith('@'):
text = Path(spec[1:]).read_text(encoding='utf-8')
spec = text.strip()
out = []
for part in spec.split(','):
part = part.strip()
if not part: continue
out.append(int(part, 0))
return out

def apply_tlo_edits(t_lo: str, *, flips: Sequence[int], sets: Dict[int, object]) -> str:
b = list(t_lo)
for idx in flips:
if not (0 <= idx < len(b)): raise ValueError(f'flip idx out of range: {idx}')
b[idx] = '1' if b[idx] == '0' else '0'
for idx, bit in sets.items():
if not (0 <= idx < len(b)): raise ValueError(f'set idx out of range: {idx}')
bit_s = str(bit)
if bit_s not in ('0', '1'): raise ValueError(f'set bit must be 0/1, got {bit!r} at idx {idx}')
b[idx] = bit_s
return ''.join(b)

def toe_from_tlo(t_lo: str) -> dict[str, str]:
if len(t_lo) != 126 or any(c not in '01' for c in t_lo):
raise ValueError('T_lo must be 126 bits of 0/1')
return {
'T_lo': t_lo,
'O_hi': t_lo + t_lo[0],
'O_lo': t_lo[1:] + t_lo[0] + '0',
'E_hi': t_lo[0] + _not_bitstr(t_lo),
'E_lo': _not_bitstr(t_lo + t_lo[0])}

def windows_from_bitstreams(hi: str, lo: str) -> List[int]:
if len(hi) != len(lo): raise ValueError('hi/lo bitstreams length mismatch')
return [((1 if hi[i] == '1' else 0) << 8) | (1 if lo[i] == '1' else 0) for i in range(len(hi))]

def identity_ok(a: Dict[int, str], b: Dict[int, str]) -> bool: return a == b

def main():
ap = argparse.ArgumentParser(description='Bitstream-driven post-opening patch + replay for part6. Patch T_lo (or its edits), regenerate O/E chains consistently, atomically patch the bank byte streams, then replay.')
ap.add_argument('--obj', default="flagchecker.o")
ap.add_argument('--chars', default=BASELINE_CHARS, help='18-char payload or full kalmar{18-chars}; used to derive baseline T_lo')
ap.add_argument('--tlo-bits', default=None, help='Explicit 126-bit T_lo override (0/1 string or @file).')
ap.add_argument('--flip-tlo', default=None, help='Comma list of T_lo bit indexes to flip, or @file.')
ap.add_argument('--set-tlo', default=None, help='Comma list like i=0,j=1, or @csv with idx,bit.')
ap.add_argument('--cut-idx', type=lambda x: int(x, 0), default=DEFAULT_CUT_IDX)
ap.add_argument('--start-idx', type=lambda x: int(x, 0), default=DEFAULT_START_IDX)
ap.add_argument('--end-idx', type=lambda x: int(x, 0), default=DEFAULT_END_IDX)
ap.add_argument('--meta-csv', default='merged_result_450_checks.csv')
ap.add_argument('--dump-bank-csv', default='part6_tlo_patch_banks.csv')
ap.add_argument('--baseline-token-csv', default='part6_tlo_patch_baseline_tokens.csv')
ap.add_argument('--replay-token-csv', default='part6_tlo_patch_replay_tokens.csv')
ap.add_argument('--replay-trace-csv', default='part6_tlo_patch_replay_trace.csv')
ap.add_argument('--dump-tlo-csv', default='part6_tlo_patch_summary.csv')
ap.add_argument('--identity-check', action='store_true', help='Patch regenerated baseline T_lo-derived O/E and compare replay tokens to baseline.')
ap.add_argument('--regen-check', action='store_true', help='Only verify that baseline chars -> generated O/E banks exactly match baseline snapshot at cutpoint.')
args = ap.parse_args()
payload18 = payload18_from_chars(args.chars)
base = calc_toe(payload18)
base_tlo = base['T_lo']
if args.tlo_bits:
tlo_spec = Path(args.tlo_bits[1:]).read_text(encoding='utf-8') if args.tlo_bits.startswith('@') else args.tlo_bits
tlo_bits = ''.join(ch for ch in tlo_spec if ch in '01')
if len(tlo_bits) != 126: raise ValueError(f'--tlo-bits must contain exactly 126 bits, got {len(tlo_bits)}')
else: tlo_bits = apply_tlo_edits(base_tlo, flips=parse_bit_list(args.flip_tlo), sets=parse_bit_assignments(args.set_tlo))
toe = toe_from_tlo(tlo_bits)
q_windows = windows_from_bitstreams(toe['O_hi'], toe['O_lo'])
e_windows = windows_from_bitstreams(toe['E_hi'], toe['E_lo'])
q_bytes = windows_to_byte_stream(q_windows)
e_bytes = windows_to_byte_stream(e_windows)
meta_rows, target_idxs = load_targets(args.meta_csv)
linker = SnapshotReplay(args.obj, chars=parse_chars(args.chars), watch_offsets=[], trace_writes=False, stop_on_error=False)
snapshot = linker.run_until_cut(args.cut_idx)
q_windows0 = dump_bank_windows(snapshot, base_off=Q_BASE_OFF, count=Q_COUNT)
e_windows0 = dump_bank_windows(snapshot, base_off=E_BASE_OFF, count=E_COUNT)
q_bytes0 = dump_bank_bytes(snapshot, bytes_start=Q_BYTES_START, bytes_len=Q_BYTES_LEN)
e_bytes0 = dump_bank_bytes(snapshot, bytes_start=E_BYTES_START, bytes_len=E_BYTES_LEN)
regen_q_ok = (q_windows == q_windows0 and q_bytes == q_bytes0)
regen_e_ok = (e_windows == e_windows0 and e_bytes == e_bytes0)
baseline = linker.replay_from_snapshot(snapshot, start_idx=args.start_idx, end_idx=args.end_idx)
baseline_by_idx = {row.idx: extract_raw16(row.addend_src) for row in baseline.trace_rows if row.idx in target_idxs}
if meta_rows: write_token_csv(args.baseline_token_csv, meta_rows, baseline_by_idx)
patched = bytearray(snapshot)
write_bytes(patched, Q_BYTES_START, q_bytes)
write_bytes(patched, E_BYTES_START, e_bytes)
patched_replay = linker.replay_from_snapshot(patched, start_idx=args.start_idx, end_idx=args.end_idx)
patched_by_idx = {row.idx: extract_raw16(row.addend_src) for row in patched_replay.trace_rows if row.idx in target_idxs}
if meta_rows: write_token_csv(args.replay-token-csv if False else args.replay_token_csv, meta_rows, patched_by_idx)
write_replay_trace_csv(args.replay_trace_csv, patched_replay.trace_rows)
write_bank_dump_csv(args.dump_bank_csv, q_windows, e_windows, q_bytes, e_bytes)
with open(args.dump_tlo_csv, 'w', newline='', encoding='utf-8') as f:
w = csv.writer(f)
w.writerow(['name', 'bits'])
for k in ['T_lo', 'O_hi', 'O_lo', 'E_hi', 'E_lo']: w.writerow([f'baseline_{k}', base[k]])
for k in ['T_lo', 'O_hi', 'O_lo', 'E_hi', 'E_lo']: w.writerow([f'patched_{k}', toe[k]])
changed_steps = 0
if meta_rows:
for r in meta_rows:
idx = int(r['trace_idx'])
if baseline_by_idx.get(idx, '') != patched_by_idx.get(idx, ''): changed_steps += 1
print(f'[ok] snapshot cut at idx={args.cut_idx}')
print(f'[ok] baseline T_lo derived from chars: {base_tlo[:32]}...{base_tlo[-16:]}')
if args.tlo_bits or args.flip_tlo or args.set_tlo:
print(f'[ok] patched T_lo used for bank regen: {tlo_bits[:32]}...{tlo_bits[-16:]}')
print(f'[regen-check] Q/O chain {'PASS' if regen_q_ok else 'FAIL'} ; E chain {'PASS' if regen_e_ok else 'FAIL'}')
print(f'[ok] bank dump csv: {args.dump_bank_csv}')
print(f'[ok] replay trace csv: {args.replay_trace_csv}')
print(f'[ok] tlo summary csv: {args.dump_tlo_csv}')
if meta_rows:
print(f'[ok] baseline token csv: {args.baseline_token_csv}')
print(f'[ok] replay token csv: {args.replay_token_csv}')
print(f'[ok] changed target-step raws: {changed_steps}/{len(meta_rows)}')
if args.identity_check:
ok = identity_ok(baseline_by_idx, patched_by_idx)
print(f"[identity-check] {'PASS' if ok else 'FAIL'}")
if not ok and meta_rows:
bad = []
for r in meta_rows:
idx = int(r['trace_idx'])
a = baseline_by_idx.get(idx, '')
b = patched_by_idx.get(idx, '')
if a != b:
bad.append((r['step'], idx, a, b))
if len(bad) >= 10: break
for step, idx, a, b in bad: print(f' step={step} idx={idx} baseline={a} patched={b}')

if __name__ == '__main__': main()

With a script can fast get score (part6_tlo_fast_score.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#!/usr/bin/env python3
import argparse
import csv
import sys
from pathlib import Path
from typing import Dict, List, Optional, Sequence, Tuple
import part6_tlo_bank_patch_replay as base
load_targets = base.load_targets
parse_chars = base.parse_chars
BASELINE_CHARS = base.BASELINE_CHARS
DEFAULT_CUT_IDX = base.DEFAULT_CUT_IDX
DEFAULT_START_IDX = base.DEFAULT_START_IDX
DEFAULT_END_IDX = base.DEFAULT_END_IDX
Q_BYTES_START = base.Q_BYTES_START
E_BYTES_START = base.E_BYTES_START
write_bytes = base.write_bytes
extract_raw16 = base.extract_raw16

TH = {'_0x100': 0x7F00, '_0x200': 0x7E00, '_0xff00': -0x7F00, '_0x7f00': 0x100, '_0x7e00': 0x200}

def sext16(x: int) -> int:
x &= 0xFFFF
return x - 0x10000 if x & 0x8000 else x

def passes(sym: str, raw16: Optional[int]) -> bool:
if raw16 is None: return False
return sext16(raw16) >= TH[sym]

def toe_from_tlo(t_lo: str) -> dict[str, str]: return base.toe_from_tlo(t_lo)

def _bytes_from_chain_hi_lo(hi: str, lo: str) -> bytes: return bytes((1 if c == '1' else 0) for c in hi) + bytes([1 if lo[-1] == '1' else 0])

def qe_bytes_from_tlo(t_lo: str) -> Tuple[bytes, bytes]:
toe = base.toe_from_tlo(t_lo)
q_bytes = _bytes_from_chain_hi_lo(toe['O_hi'], toe['O_lo'])
e_bytes = _bytes_from_chain_hi_lo(toe['E_hi'], toe['E_lo'])
return q_bytes, e_bytes

class FastSnapshotReplay(base.SnapshotReplay):
def __init__(self, *args, target_meta: Sequence[dict], **kwargs):
super().__init__(*args, **kwargs)
self.meta_rows = list(sorted(target_meta, key=lambda r: int(r['step'])))
self.target_pos = {int(r['trace_idx']): i for i, r in enumerate(self.meta_rows)}
self.target_order = [int(r['trace_idx']) for r in self.meta_rows]
self.target_syms = [r['sym'] if 'sym' in r else r.get('cmp_sym', '') for r in self.meta_rows]
self._relocs_cut = self.relocs[: base.DEFAULT_CUT_IDX + 1]
self._relocs_run = self.relocs[base.DEFAULT_START_IDX : base.DEFAULT_END_IDX + 1]

def run_until_cut(self, cut_idx: int) -> bytearray: return super().run_until_cut(cut_idx)

def score_from_snapshot(self, snapshot: bytearray, *, start_idx: int = base.DEFAULT_START_IDX, end_idx: int = base.DEFAULT_END_IDX):
if start_idx == base.DEFAULT_START_IDX and end_idx == base.DEFAULT_END_IDX:
relocs = self._relocs_run
else:
relocs = [rec for rec in self.relocs if start_idx <= rec.idx <= end_idx]
self.contents = bytearray(snapshot)
self.last_addend_by_idx = {}
self.last_addend_src_by_idx = {}
raws: List[Optional[int]] = [None] * len(self.meta_rows)
addend = 0
saved_from_prev = False
for rec in relocs:
if not saved_from_prev:
addend, src = self._read_rel_addend(rec)
if rec.typ == 5:
addend, extra = self._add_lo16_rel_addend(rec, addend)
src = f"{src}; {extra}"
else:
addend &= 0xFFFFFFFF
src = f"{src}; <<0"
addend, adj = self._adjust_addend(rec, addend)
src = f"{src}; {adj}"
else:
src = 'saved-from-prev-same-offset'
save_for_next = self._next_same_offset_nonzero(rec.idx)
status, value, _ = self._calculate_relocation(rec, addend)
pos = self.target_pos.get(rec.idx)
if pos is not None:
raw_hex = base.extract_raw16(src)
raws[pos] = int(raw_hex, 16) if raw_hex else None
if save_for_next:
addend = value & 0xFFFFFFFF
saved_from_prev = True
else:
saved_from_prev = False
self._store_contents(rec, value)
if status == 'overflow' and self.stop_on_error:
break
oks = [passes(sym, raw) for sym, raw in zip(self.target_syms, raws)]
first_bad = next((i + 1 for i, ok in enumerate(oks) if not ok), len(oks) + 1)
cnt = sum(1 for ok in oks if ok)
score = cnt + first_bad / 1000.0
return cnt, first_bad, score, oks, raws

class TloScorer:
def __init__(self, obj_path: str, meta_csv: str, chars: str = BASELINE_CHARS):
rows, _ = base.load_targets(meta_csv)
for r in rows:
if 'sym' not in r and 'cmp_sym' in r:
r['sym'] = r['cmp_sym']
self.meta_rows = rows
self.linker = FastSnapshotReplay(
obj_path,
chars=base.parse_chars(chars),
watch_offsets=[],
trace_writes=False,
stop_on_error=False,
target_meta=rows)
self.snapshot = self.linker.run_until_cut(base.DEFAULT_CUT_IDX)
self.cache: Dict[str, Tuple[int, int, float, List[bool], List[Optional[int]]]] = {}

def eval_tlo(self, tlo: str):
if tlo in self.cache:
return self.cache[tlo]
q_bytes, e_bytes = qe_bytes_from_tlo(tlo)
patched = bytearray(self.snapshot)
base.write_bytes(patched, base.Q_BYTES_START, q_bytes)
base.write_bytes(patched, base.E_BYTES_START, e_bytes)
val = self.linker.score_from_snapshot(patched)
self.cache[tlo] = val
return val

def random_tlo(rng=None) -> str:
import random
r = random if rng is None else rng
return ''.join(r.choice('01') for _ in range(126))

def main():
ap = argparse.ArgumentParser(description='Fast 4f4c scorer: patch T_lo -> regenerate O/E -> replay only score target checks.')
ap.add_argument('--obj', default="flagchecker.o")
ap.add_argument('--meta-csv', default='merged_result_450_checks.csv')
ap.add_argument('--chars', default=BASELINE_CHARS)
ap.add_argument('--tlo', default=None, help='126-bit T_lo or @file')
ap.add_argument('--random', action='store_true', help='Score a fresh random T_lo instead of --tlo.')
ap.add_argument('--show-raws', action='store_true')
args = ap.parse_args()
if args.random:
tlo = random_tlo()
else:
if args.tlo is None:
raise SystemExit('provide --tlo or --random')
if args.tlo.startswith('@'):
tlo = ''.join(ch for ch in Path(args.tlo[1:]).read_text(encoding='utf-8') if ch in '01')
else:
tlo = ''.join(ch for ch in args.tlo if ch in '01')
scorer = TloScorer(args.obj, args.meta_csv, args.chars)
cnt, first_bad, score, oks, raws = scorer.eval_tlo(tlo)
print(f'cnt={cnt} first_bad={first_bad} score={score:.3f}')
print(f'tlo={tlo}')
if args.show_raws:
for r, ok, raw in zip(scorer.meta_rows, oks, raws): print(r['step'], r['trace_idx'], r['sym'], 'PASS' if ok else 'FAIL', '' if raw is None else f'0x{raw:04x}')

if __name__ == '__main__':
main()

Then a script can randomly try T_los:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python3
import argparse
import random
from pathlib import Path
import part6_tlo_fast_score as fast

def main():
ap = argparse.ArgumentParser(description='Generate random T_lo strings and score them quickly.')
ap.add_argument('--obj', default="flagchecker.o")
ap.add_argument('--meta-csv', default='merged_result_450_checks.csv')
ap.add_argument('--chars', default=fast.BASELINE_CHARS)
ap.add_argument('--n', type=int, default=10)
ap.add_argument('--seed', type=int, default=0)
ap.add_argument('--out-csv', default='part6_tlo_random_scores.csv')
args = ap.parse_args()
rng = random.Random(args.seed)
scorer = fast.TloScorer(args.obj, args.meta_csv, args.chars)
rows = []
best = None
best_tlo = None
for i in range(args.n):
tlo = fast.random_tlo(rng)
cnt, first_bad, score, oks, raws = scorer.eval_tlo(tlo)
rows.append((i + 1, cnt, first_bad, f'{score:.3f}', tlo))
print(f'[{i+1:04d}] cnt={cnt} first_bad={first_bad} score={score:.3f}')
if best is None or score > best:
best = score
best_tlo = tlo
with open(args.out_csv, 'w', encoding='utf-8', newline='') as f:
import csv
w = csv.writer(f)
w.writerow(['idx', 'cnt', 'first_bad', 'score', 'tlo'])
w.writerows(rows)
print('best_score', best)
print('best_tlo', best_tlo)
print('wrote', args.out_csv)

if __name__ == '__main__':
main()

Found a T_lo with 316 score: 000001001111001000011010100110001101011110111010010000010010111000011110110111001011000101100100111010001011010000110101001111

Finally a hill-climbing script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/usr/bin/env python3
import argparse
import random
from pathlib import Path
import part6_tlo_fast_score as fast

def normalize_tlo(spec: str) -> str:
if spec.startswith('@'):
spec = Path(spec[1:]).read_text(encoding='utf-8')
tlo = ''.join(ch for ch in spec if ch in '01')
if len(tlo) != 126: raise ValueError(f'T_lo must be 126 bits, got {len(tlo)}')
return tlo

def main():
ap = argparse.ArgumentParser(description='Fast hill-climb on T_lo using the optimized replay scorer.')
ap.add_argument('--obj', default="flagchecker.o")
ap.add_argument('--meta-csv', default='merged_result_450_checks.csv')
ap.add_argument('--chars', default=fast.BASELINE_CHARS)
ap.add_argument('--tlo', default=None, help='Initial 126-bit T_lo or @file. If omitted, use --random-init.')
ap.add_argument('--random-init', action='store_true', help='Start from a random T_lo.')
ap.add_argument('--seed', type=int, default=0)
ap.add_argument('--iters', type=int, default=60)
ap.add_argument('--sample-pairs', type=int, default=4000)
ap.add_argument('--sample-period', type=int, default=5)
ap.add_argument('--restarts', type=int, default=1)
ap.add_argument('--best-out', default='part6_tlo_hillclimb_best.txt')
args = ap.parse_args()
rng = random.Random(args.seed)
scorer = fast.TloScorer(args.obj, args.meta_csv, args.chars)
global_best = None
global_tlo = None
for restart in range(args.restarts):
if args.tlo is not None and restart == 0:
tlo = normalize_tlo(args.tlo)
else:
tlo = fast.random_tlo(rng)
cur = scorer.eval_tlo(tlo)
print(f'[restart {restart+1}] start cnt={cur[0]} first_bad={cur[1]} score={cur[2]:.3f}')
for outer in range(args.iters):
cnt, first_bad, score, oks, raws = scorer.eval_tlo(tlo)
best = (score, None, None, None)
for i in range(126):
tl = list(tlo)
tl[i] = '1' if tl[i] == '0' else '0'
nt = ''.join(tl)
val = scorer.eval_tlo(nt)
if val[2] > best[0]:
best = (val[2], (i,), nt, val)
if best[1] is None or outer % max(1, args.sample_period) == max(1, args.sample_period) - 1:
pairs = rng.sample([(i, j) for i in range(126) for j in range(i + 1, 126)], args.sample_pairs)
for i, j in pairs:
tl = list(tlo)
tl[i] = '1' if tl[i] == '0' else '0'
tl[j] = '1' if tl[j] == '0' else '0'
nt = ''.join(tl)
val = scorer.eval_tlo(nt)
if val[2] > best[0]:
best = (val[2], (i, j), nt, val)
if best[1] is None:
print(f'[restart {restart+1}] local optimum iter={outer} cnt={cur[0]} first_bad={cur[1]} score={cur[2]:.3f}')
break
tlo = best[2]
cur = best[3]
print(f'[restart {restart+1}] iter={outer+1} move={best[1]} cnt={cur[0]} first_bad={cur[1]} score={cur[2]:.3f}')

if global_best is None or cur[2] > global_best[2]:
global_best = cur
global_tlo = tlo

if global_best is None:
raise SystemExit('no runs completed')
fails = [i + 1 for i, x in enumerate(global_best[3]) if not x]
print('=== best overall ===')
print('cnt', global_best[0], 'first_bad', global_best[1], 'score', f'{global_best[2]:.3f}')
print('ones', sum(1 for b in global_tlo if b == '1'))
print('tlo', global_tlo)
print('n_fail', len(fails), 'first30', fails[:30])
Path(args.best_out).write_text(
f'cnt={global_best[0]}\\nfirst_bad={global_best[1]}\\nscore={global_best[2]:.3f}\\n'
f'ones={sum(1 for b in global_tlo if b == "1")}\\n'
f'tlo={global_tlo}\\n'
f'n_fail={len(fails)}\\nfirst30={fails[:30]}\\n',
encoding='utf-8')

if __name__ == '__main__':
main()

Get a T_lo that can pass 450 checks: 000011011011111010101110110111000001011110000101110101110000101000011110111001011011101111110100000011001000111100110001001010.

VIII. The Solution

The encryption from input to T can be optimized as:

1
2
3
4
A = int_le(A_BITS)
x = _hex2bits_int(inp)
Q = A * x
T = ((Q & ((1<<125)-1)) + ((Q >> 126) & ((1<<125)-1)) + ((1 - ((Q >> 250) & 1) + ((Q >> 125) & 1)) << 125)) & ((1<<126)-1)

It is very closed to T = (A*x mod M) + δ, as δ ∈ {0, 2^125, 2^125-1, -1}. And there’s a small error in the previous T_lo, as previous analysis found that several checks accessed index 127 where E does not exist, the conflict term is at the end of T_lo. Consider the boundary problem, flipping the last bit of T, as 000011011011111010101110110111000001011110000101110101110000101000011110111001011011101111110100000011001000111100110001001011.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
A_BITS = "110101100011001110011000110010000011000000101010010011010101011111011100001001110011000000000111101001111101111101011100011011"
TARGET = "000011011011111010101110110111000001011110000101110101110000101000011110111001011011101111110100000011001000111100110001001011"

MASK126 = (1 << 126) - 1
M = MASK126

def bitstr_le_to_int(s: str) -> int:
v = 0
for i, ch in enumerate(s):
if ch == '1': v |= 1 << i
return v

A = bitstr_le_to_int(A_BITS)
T = bitstr_le_to_int(TARGET)

def pack_7bit_bytes(bs: bytes) -> int:
x = 0
for i, b in enumerate(bs): x |= (b & 0x7F) << (7 * i)
return x

def unpack_7bit_bytes(x: int, n=18) -> bytes: return bytes((x >> (7 * i)) & 0x7F for i in range(n))

def toe_fast_exact_from_x(x: int) -> int:
Q = A * x
return ((Q & ((1 << 125) - 1)) + ((Q >> 126) & ((1 << 125) - 1)) + ((1 - ((Q >> 250) & 1) + ((Q >> 125) & 1)) << 125)) & MASK126

def is_printable_7bit(bs: bytes) -> bool: return all(0x20 <= b <= 0x7E for b in bs)

invA = pow(A, -1, M)
deltas = [0, 1 << 125, (1 << 125) - 1]

for delta in deltas:
y = (T - delta) % M
x = (y * invA) % M
bs = unpack_7bit_bytes(x, 18)
if is_printable_7bit(bs) and toe_fast_exact_from_x(x) == T:
print("kalmar{"+bs.decode()+"}")

kalmar{R_MIPS_FLAGCHK!%#$}