xosski

clockworks obfuscation

Mar 4th, 2026
136
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.39 KB | None | 0 0
  1. # obfuscate.py
  2. # Toy Lua packer/obfuscator using a "Clock-Direction RNG" driftwheel keystream.
  3. # Usage:
  4. # python obfuscate.py input.lua output_obf.lua --seed 7 --rounds 9
  5.  
  6. from __future__ import annotations
  7. import argparse
  8. import base64
  9. from dataclasses import dataclass
  10.  
  11. # ---------- Driftwheel RNG (Python side, mirrors Lua) ----------
  12.  
  13. @dataclass
  14. class DriftState:
  15. shape: int # 0..5
  16. color: int # 0..5
  17. direction: int # 1..12
  18.  
  19. # 12 clock positions -> angle bucket + base "shape/color" impulses (integers)
  20. DIRECTIONS = {
  21. 1: (1, 0),
  22. 2: (2, 1),
  23. 3: (3, 2),
  24. 4: (4, 3),
  25. 5: (5, 4),
  26. 6: (0, 5),
  27. 7: (1, 4),
  28. 8: (2, 3),
  29. 9: (3, 2),
  30. 10: (4, 1),
  31. 11: (5, 0),
  32. 12: (0, 1),
  33. }
  34.  
  35. def _mix(a: int, b: int) -> int:
  36. # small nonlinear mixer in 0..255 space
  37. x = (a * 73 + b * 151 + 19) & 0xFF
  38. x ^= ((x << 3) & 0xFF)
  39. x ^= (x >> 5)
  40. return x & 0xFF
  41.  
  42. def _step(st: DriftState, i: int) -> int:
  43. base_shape, base_color = DIRECTIONS[st.direction]
  44. # "morph" shape and color using recursive drift
  45. st.shape = (st.shape + base_shape + (i * 3)) % 6
  46. st.color = (st.color + base_color + (i * 5)) % 6
  47.  
  48. # derive a pseudo "area" value from shape/color/direction
  49. area = (st.shape + 1) * (st.color + 2) * (st.direction + 7)
  50. area = _mix(area & 0xFF, (area >> 8) & 0xFF)
  51.  
  52. # drift direction (clock step with feedback)
  53. drift = ((area % 11) + 1) # 1..12-ish
  54. st.direction = ((st.direction + drift + (i * 3)) % 12) or 12
  55.  
  56. return area
  57.  
  58. def keystream(seed: int, n: int, rounds: int) -> bytes:
  59. st = DriftState(shape=0, color=0, direction=((seed - 1) % 12) + 1)
  60. out = bytearray()
  61. i = 0
  62. while len(out) < n:
  63. # each byte comes from several drift rounds to add diffusion
  64. v = 0
  65. for _ in range(rounds):
  66. v = _mix(v, _step(st, i))
  67. i += 1
  68. out.append(v)
  69. return bytes(out)
  70.  
  71. def xor_bytes(data: bytes, ks: bytes) -> bytes:
  72. return bytes(b ^ ks[i % len(ks)] for i, b in enumerate(data))
  73.  
  74. # ---------- Lua template ----------
  75. LUA_LOADER_TEMPLATE = r'''-- Generated Toy Obfuscator (Clock-Drift Packer)
  76. -- NOT real security. Reversible. For basic IP deterrence.
  77.  
  78. local b64 = [[{B64_PAYLOAD}]]
  79. local SEED = {SEED}
  80. local ROUNDS = {ROUNDS}
  81.  
  82. local function b64dec(data)
  83. local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
  84. data = data:gsub('[^'..b..'=]', '')
  85. return (data:gsub('.', function(x)
  86. if x == '=' then return '' end
  87. local r,f='',(b:find(x)-1)
  88. for i=6,1,-1 do
  89. r=r..(f%2^i - f%2^(i-1) > 0 and '1' or '0')
  90. end
  91. return r
  92. end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
  93. if #x ~= 8 then return '' end
  94. local c=0
  95. for i=1,8 do
  96. c=c + (x:sub(i,i)=='1' and 2^(8-i) or 0)
  97. end
  98. return string.char(c)
  99. end))
  100. end
  101.  
  102. -- Clock-Direction Driftwheel RNG (Lua side)
  103. local DIRECTIONS = {
  104. [1]={1,0}, [2]={2,1}, [3]={3,2}, [4]={4,3}, [5]={5,4}, [6]={0,5},
  105. [7]={1,4}, [8]={2,3}, [9]={3,2}, [10]={4,1}, [11]={5,0}, [12]={0,1},
  106. }
  107.  
  108. local function mix(a,b)
  109. local x = (a*73 + b*151 + 19) % 256
  110. x = (x ~ ((x << 3) % 256)) % 256
  111. x = (x ~ (x >> 5)) % 256
  112. return x % 256
  113. end
  114.  
  115. local function step(st, i)
  116. local base = DIRECTIONS[st.dir]
  117. local baseShape, baseColor = base[1], base[2]
  118.  
  119. st.shape = (st.shape + baseShape + (i*3)) % 6
  120. st.color = (st.color + baseColor + (i*5)) % 6
  121.  
  122. local area = (st.shape+1) * (st.color+2) * (st.dir+7)
  123. area = mix(area % 256, math.floor(area / 256) % 256)
  124.  
  125. local drift = (area % 11) + 1
  126. st.dir = ((st.dir + drift + (i*3)) % 12)
  127. if st.dir == 0 then st.dir = 12 end
  128.  
  129. return area
  130. end
  131.  
  132. local function keystream(seed, n, rounds)
  133. local st = { shape=0, color=0, dir=((seed-1) % 12)+1 }
  134. local out = {}
  135. local i, v = 0, 0
  136. for k=1,n do
  137. v = 0
  138. for _=1,rounds do
  139. v = mix(v, step(st, i))
  140. i = i + 1
  141. end
  142. out[k] = string.char(v)
  143. end
  144. return table.concat(out)
  145. end
  146.  
  147. local function xor_str(data, ks)
  148. local out = {}
  149. local klen = #ks
  150. for i=1,#data do
  151. local db = data:byte(i)
  152. local kb = ks:byte(((i-1) % klen) + 1)
  153. out[i] = string.char(db ~ kb)
  154. end
  155. return table.concat(out)
  156. end
  157.  
  158. local enc = b64dec(b64)
  159. local ks = keystream(SEED, math.max(64, #enc), ROUNDS)
  160. local src = xor_str(enc, ks)
  161.  
  162. local fn, err = load(src, "clock_drift_payload", "t", _G)
  163. if not fn then error("decode/load failed: "..tostring(err)) end
  164. return fn()
  165. '''
  166.  
  167. def main() -> None:
  168. ap = argparse.ArgumentParser()
  169. ap.add_argument("input", help="Input Lua file")
  170. ap.add_argument("output", help="Output obfuscated Lua file")
  171. ap.add_argument("--seed", type=int, default=7, help="Seed direction (1..12 recommended)")
  172. ap.add_argument("--rounds", type=int, default=9, help="Rounds per keystream byte")
  173. args = ap.parse_args()
  174.  
  175. with open(args.input, "rb") as f:
  176. src = f.read()
  177.  
  178. ks = keystream(args.seed, max(64, len(src)), args.rounds)
  179. enc = xor_bytes(src, ks)
  180. b64 = base64.b64encode(enc).decode("ascii")
  181.  
  182. out = LUA_LOADER_TEMPLATE.format(
  183. B64_PAYLOAD=b64,
  184. SEED=int(args.seed),
  185. ROUNDS=int(args.rounds),
  186. )
  187.  
  188. with open(args.output, "w", encoding="utf-8") as f:
  189. f.write(out)
  190.  
  191. print(f"OK: wrote {args.output} (seed={args.seed}, rounds={args.rounds})")
  192.  
  193. if __name__ == "__main__":
  194. main()
Add Comment
Please, Sign In to add comment