A Python module to crack Cloudflare's JavaScript challenge (aka "I'm Under Attack" mode)
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

294 wiersze
9.2 KiB

  1. import enum
  2. import functools
  3. import operator
  4. import re
  5. class ParsingError(Exception):
  6. pass
  7. class EvaluationError(Exception):
  8. pass
  9. class StrEnum(enum.Enum):
  10. def __repr__(self):
  11. return str(self)
  12. class Type(StrEnum):
  13. group = 1
  14. brackets = 2
  15. class Modifier(StrEnum):
  16. negate = 1
  17. plus = 2
  18. class JSBool:
  19. def __init__(self, value):
  20. self.value = bool(value)
  21. def __add__(self, other):
  22. if isinstance(other, (JSBool, JSInt)): # bool + bool/int -> int (addition)
  23. return JSInt(self) + JSInt(other)
  24. elif isinstance(other, JSString): # bool + string -> string (concatenation)
  25. return JSString(self) + other
  26. else:
  27. return NotImplemented
  28. def __sub__(self, other):
  29. if isinstance(other, (JSBool, JSInt, JSString)): # bool - bool/int/string -> int (subtraction)
  30. return JSInt(self) - JSInt(other)
  31. else:
  32. return NotImplemented
  33. def __mul__(self, other):
  34. if isinstance(other, (JSBool, JSInt, JSString)): # bool * bool/int/string -> int (multiplication)
  35. return JSInt(self) * JSInt(other)
  36. else:
  37. return NotImplemented
  38. def __eq__(self, other):
  39. return isinstance(other, JSBool) and self.value == other.value
  40. def __bool__(self):
  41. return self.value
  42. def __int__(self):
  43. return int(self.value)
  44. def __str__(self):
  45. return str(self.value).lower() # 'True' in Python is 'true' in JS
  46. def __repr__(self):
  47. return 'JSBool({!r})'.format(self.value)
  48. class JSInt:
  49. def __init__(self, value):
  50. self.value = int(value)
  51. def __add__(self, other):
  52. if isinstance(other, JSInt): # int + int -> int (addition)
  53. return JSInt(self.value + other.value)
  54. elif isinstance(other, JSBool): # int + bool -> int (addition)
  55. return self + JSInt(other)
  56. elif isinstance(other, JSString): # int + string -> string (concatenation)
  57. return JSString(self) + other
  58. else:
  59. return NotImplemented
  60. def __sub__(self, other):
  61. if isinstance(other, JSInt): # int - int -> int (subtraction)
  62. return JSInt(self.value - other.value)
  63. elif isinstance(other, (JSBool, JSString)): # int - bool/string -> int (subtraction)
  64. return self - JSInt(other)
  65. else:
  66. return NotImplemented
  67. def __mul__(self, other):
  68. if isinstance(other, JSInt): # int * int -> int (multiplication)
  69. return JSInt(self.value * other.value)
  70. elif isinstance(other, (JSBool, JSString)): # int * bool/string -> int (multiplication)
  71. return self * JSInt(other)
  72. else:
  73. return NotImplemented
  74. def __eq__(self, other):
  75. return isinstance(other, JSInt) and self.value == other.value
  76. def __bool__(self):
  77. return self.value != 0 # Any value other than zero is considered 'true'
  78. def __int__(self):
  79. return self.value
  80. def __str__(self):
  81. return str(self.value)
  82. def __repr__(self):
  83. return 'JSInt({!r})'.format(self.value)
  84. class JSString:
  85. def __init__(self, value):
  86. self.value = str(value)
  87. def __add__(self, other):
  88. if isinstance(other, JSString): # string + string -> string (concatenation)
  89. return JSString(self.value + other.value)
  90. elif isinstance(other, (JSInt, JSBool)): # string + int/bool -> string (concatenation)
  91. return self + JSString(other)
  92. else:
  93. return NotImplemented
  94. def __sub__(self, other):
  95. if isinstance(other, (JSBool, JSInt, JSString)): # string - bool/int/string -> int (subtraction)
  96. return JSInt(self) - JSInt(other)
  97. else:
  98. return NotImplemented
  99. def __mul__(self, other):
  100. if isinstance(other, (JSBool, JSInt, JSString)): # string * bool/int/string -> int (multiplication)
  101. return JSInt(self) * JSInt(other)
  102. else:
  103. return NotImplemented
  104. def __eq__(self, other):
  105. return isinstance(other, JSString) and self.value == other.value
  106. def __bool__(self):
  107. return self.value != '' # Any non-empty string is considered 'true'
  108. def __int__(self):
  109. if self.value == '':
  110. return 0
  111. return int(self.value)
  112. def __str__(self):
  113. return self.value
  114. def __repr__(self):
  115. return 'JSString({!r})'.format(self.value)
  116. _itemModifierToResultMapping = {
  117. (): JSString(''),
  118. (Modifier.plus,): JSInt(0),
  119. (Modifier.negate,): JSBool(False),
  120. #(Modifier.plus, Modifier.plus): syntax error
  121. (Modifier.plus, Modifier.negate): JSInt(0),
  122. (Modifier.negate, Modifier.plus): JSBool(True),
  123. (Modifier.negate, Modifier.negate): JSBool(True),
  124. #(Modifier.plus, Modifier.plus, Modifier.plus): syntax error
  125. #(Modifier.plus, Modifier.plus, Modifier.negate): syntax error
  126. (Modifier.plus, Modifier.negate, Modifier.plus): JSInt(1),
  127. (Modifier.plus, Modifier.negate, Modifier.negate): JSInt(1),
  128. #(Modifier.negate, Modifier.plus, Modifier.plus): syntax error
  129. (Modifier.negate, Modifier.plus, Modifier.negate): JSBool(True),
  130. (Modifier.negate, Modifier.negate, Modifier.plus): JSBool(False),
  131. (Modifier.negate, Modifier.negate, Modifier.negate): JSBool(False),
  132. }
  133. class Item:
  134. def __init__(self, type, modifiers, values = None):
  135. if type not in (Type.group, Type.brackets):
  136. raise ValueError('type must be Type.group or Type.brackets')
  137. iter(modifiers) # Test whether modifiers is an iterable, and let the potential TypeError bubble up
  138. if not all(x in (Modifier.negate, Modifier.plus) for x in modifiers):
  139. raise ValueError('modifiers must be an iterable that can only contain Modifier.negate or Modifier.plus')
  140. if values is not None and type != Type.group:
  141. raise ValueError('values can only be specified for group items')
  142. if type == Type.group and values is None:
  143. raise ValueError('values are required for group items')
  144. self.type = type
  145. self.modifiers = modifiers
  146. self.values = values
  147. def evaluate(self, evaluateFunction = None):
  148. if self.type == Type.group:
  149. if evaluateFunction is None or not callable(evaluateFunction):
  150. raise ValueError('must specify a callable evaluateFunction when evaluating a group item')
  151. return evaluateFunction(self.values, self.modifiers)
  152. else:
  153. try:
  154. return _itemModifierToResultMapping[tuple(self.modifiers)]
  155. except KeyError:
  156. raise EvaluationError('Unrecognised modifier pattern {!r}'.format(self.modifiers))
  157. def __eq__(self, other):
  158. return isinstance(other, Item) and self.type == other.type and self.modifiers == other.modifiers and self.values == other.values
  159. def __repr__(self):
  160. return 'Item({!r}, {!r}{})'.format(self.type, self.modifiers, ', values = {!r}'.format(self.values) if self.values is not None else '')
  161. def parse(s):
  162. '''
  163. Parse expression s into a tree of Items.
  164. Argument: s (string), the expression to evaluate
  165. Returns: tree (list of Items)
  166. '''
  167. itemStack = {0: []}
  168. modifierStack = {0: []}
  169. currentItemStack = itemStack[0]
  170. currentModifierStack = modifierStack[0]
  171. stackLevel = 0
  172. finishedItem = False
  173. pos = 0
  174. length = len(s)
  175. while pos < length:
  176. char = s[pos]
  177. if char == '+':
  178. if pos == 0 or not finishedItem:
  179. finishedItem = False
  180. currentModifierStack.append(Modifier.plus)
  181. # else: addition, nothing to do
  182. elif char == '!':
  183. finishedItem = False
  184. currentModifierStack.append(Modifier.negate)
  185. elif char == '(':
  186. finishedItem = False
  187. stackLevel += 1
  188. currentItemStack = itemStack[stackLevel] = []
  189. currentModifierStack = modifierStack[stackLevel] = []
  190. elif char == ')':
  191. if stackLevel == 0:
  192. raise ParsingError('Encountered ) without matching (')
  193. stackLevel -= 1
  194. currentItemStack = itemStack[stackLevel]
  195. currentItemStack.append(Item(type = Type.group, modifiers = modifierStack[stackLevel], values = itemStack[stackLevel + 1]))
  196. currentModifierStack = modifierStack[stackLevel] = []
  197. finishedItem = True
  198. elif char == '[':
  199. if s[pos + 1] != ']':
  200. raise ParsingError('Invalid byte found at position {}; expected ] but got {}'.format(pos + 1, s[pos + 1]))
  201. # End of modifier sequence
  202. currentItemStack.append(Item(type = Type.brackets, modifiers = currentModifierStack))
  203. currentModifierStack = []
  204. pos += 1 # Skip over closing bracket
  205. finishedItem = True
  206. pos += 1
  207. return itemStack[0]
  208. def evaluate(tree, modifiers = None):
  209. t = map(lambda x: x.evaluate(evaluate), tree)
  210. if len(tree) > 1:
  211. result = functools.reduce(operator.add, t) # Concatenation or addition, but this is all handled in the JS* classes
  212. else:
  213. result = next(t)
  214. if modifiers == [Modifier.plus]:
  215. return JSInt(result)
  216. return result
  217. def crack(url, html):
  218. m = re.search(r'setTimeout\(function\(\)\{\s+var\s+s,t,o,p,b,r,e,a,k,i,n,g,f,\s*(?P<parent>[a-zA-Z]+)=\{"(?P<child>[a-zA-Z]+)":(?P<initialExpression>[^}]+)\};' +
  219. r'\s*t\s*=\s*document\.createElement\(\'div\'\);' +
  220. r'\s*t\.innerHTML="<a href=\'/\'>x</a>";' +
  221. r'\s*t\s*=\s*t\.firstChild\.href;\s*r\s*=\s*t\.match\(/https\?:\\/\\//\)\[0\];' +
  222. r'\s*t\s*=\s*t\.substr\(r\.length\);\s*t\s*=\s*t\.substr\(0,t\.length-1\);' +
  223. r'\s*a\s*=\s*document\.getElementById\(\'jschl-answer\'\);' +
  224. r'\s*f\s*=\s*document\.getElementById\(\'challenge-form\'\);' +
  225. r'\s*;((?P=parent)\.(?P=child)\s*[*+-]=\s*[^;]+\s*;\s*)+a\.value\s*=\s*parseInt\((?P=parent)\.(?P=child),\s*10\)\s*\+\s*t\.length;\s*\';\s*121\'' +
  226. r'\s*f\.action\s*\+=\s*location\.hash;' +
  227. r'\s*f\.submit\(\);' +
  228. r'\s*\},\s*4000\);', html)
  229. if not m:
  230. return None
  231. d = m.groupdict()
  232. operators = {'*': operator.mul, '+': operator.add, '-': operator.sub}
  233. result = evaluate(parse(d['initialExpression']))
  234. for m in re.finditer(d['parent'] + r'\.' + d['child'] + r'\s*(?P<operator>[*+-])=\s*(?P<expression>[^;]+)', html):
  235. result = operators[m.group('operator')](result, evaluate(parse(m.group('expression'))))
  236. domain = re.search(r'^https?://([^/]+)/', url).group(1)
  237. return result + JSInt(len(domain))