Guest User

Untitled

a guest
Mar 17th, 2018
69
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.69 KB | None | 0 0
  1. """Example usage:
  2.  
  3. In [1]: !cat ayy.s
  4. .text
  5. .global _start
  6. .type _start, @function
  7. _start:
  8. movq $10, %rdx
  9.  
  10. pushw $10
  11. pushw $28513
  12. pushw $28012
  13. pushw $8313
  14. pushw $31073
  15.  
  16. movq %rsp, %rsi
  17.  
  18. movq $1, %rdi
  19. movq $1, %rbx
  20. movq $1, %rax
  21. syscall
  22.  
  23. addq $10, %rsp
  24. movq $0, %rax
  25. retq
  26.  
  27. In [2]: !as -o ayy.o ayy.s
  28.  
  29. In [3]: with open('ayy.o', 'rb') as f:
  30. ...: f.seek(64)
  31. ...: payload = f.read()
  32. ...:
  33.  
  34. In [4]: from they_should_have_written_cpython_in_rust_lol import execute_binary_payload
  35.  
  36. In [5]: execute_binary_payload(payload)
  37. ayy lmao
  38.  
  39. """
  40. import dis
  41. import mmap
  42. import sys
  43. import types
  44.  
  45. # byte offsets for structures
  46. if hasattr(sys, 'gettotalrefcount'):
  47. # under PyDEBUG builds
  48. _f_localsplus_offset = 392
  49. _ob_item_offset = 40
  50. _view_offset = 72
  51. _sizeof_pyobject = 32
  52. _mbuf_offset = 40
  53. _master_offset = 48
  54. _view_offset = 72
  55. _tp_repr_offset = 104
  56. else:
  57. # under normal builds
  58. _f_localsplus_offset = 376
  59. _ob_item_offset = 24
  60. _view_offset = 56
  61. _sizeof_pyobject = 16
  62. _mbuf_offset = 24
  63. _master_offset = 32
  64. _view_offset = 56
  65. _tp_repr_offset = 88
  66.  
  67.  
  68. # the maximum allowed offset from the address of the frame's locals
  69. # to the target address measured in pointer counts.
  70. _write_range = 2 ** 22
  71.  
  72.  
  73. def _make_write_bytecode(range_):
  74. """Create bytecode that emits one branch for all values in
  75. [-range_, range_] like:
  76.  
  77. if this_branch():
  78. _n = value
  79.  
  80. where ``this_branch`` is a callable that checks if we should assign to
  81. the given index. ``value`` is constant 0. ``this_branch`` is not stored
  82. in the locals, instead we just store it on the stack.
  83.  
  84. To do negative values, we overflow oparg with man EXTENDED_ARG calls.
  85. """
  86. try:
  87. return _make_write_bytecode._cache[range_]
  88. except KeyError:
  89. pass
  90.  
  91. cached_file_name = f'_make_write_bytecode.{range_}'
  92. try:
  93. with open(cached_file_name, 'rb') as f:
  94. _make_write_bytecode._cache[range_] = ret = f.read()
  95. return ret
  96. except FileNotFoundError:
  97. pass
  98.  
  99. code = bytearray((
  100. dis.opmap['LOAD_CONST'], 0,
  101. dis.opmap['YIELD_VALUE'], 0,
  102. ))
  103.  
  104. def check(n):
  105. *extension, index = (n % 2 ** 32).to_bytes(4, 'big')
  106. extension = [*filter(bool, extension)]
  107.  
  108. code.extend((
  109. dis.opmap['DUP_TOP'], 0,
  110. dis.opmap['CALL_FUNCTION'], 0,
  111. dis.opmap['EXTENDED_ARG'], 0,
  112. dis.opmap['EXTENDED_ARG'], 0,
  113. dis.opmap['EXTENDED_ARG'], 0,
  114. dis.opmap['POP_JUMP_IF_FALSE'], 0,
  115. dis.opmap['LOAD_CONST'], 0,
  116. ))
  117.  
  118. ix = len(code) - 3
  119. for ext in extension:
  120. code.extend((dis.opmap['EXTENDED_ARG'], ext))
  121.  
  122. code.extend((
  123. dis.opmap['STORE_FAST'], index,
  124. ))
  125.  
  126. jump_index = len(code)
  127. *jump_extension, jump_index = jump_index.to_bytes(4, 'big')
  128. code[ix - 6] = jump_extension[0]
  129. code[ix - 4] = jump_extension[1]
  130. code[ix - 2] = jump_extension[2]
  131. code[ix] = jump_index
  132.  
  133. for n in range(range_):
  134. check(n)
  135. check(-n)
  136.  
  137. code.extend((
  138. dis.opmap['LOAD_CONST'], 0,
  139. dis.opmap['RETURN_VALUE'], 0
  140. ))
  141.  
  142. ret = _make_write_bytecode._cache[range_] = bytes(code)
  143. with open(cached_file_name, 'wb') as f:
  144. f.write(ret)
  145. return ret
  146.  
  147.  
  148. _make_write_bytecode._cache = {}
  149.  
  150.  
  151. def _make_write_codeobject(range_, value):
  152. return types.CodeType(
  153. 0,
  154. 0,
  155. 0,
  156. 2,
  157. 99,
  158. _make_write_bytecode(range_),
  159. (value,), # make co_consts[0] ``value`` to replace None
  160. (),
  161. (),
  162. '<string>',
  163. '<write-target-helper>',
  164. 1,
  165. b'',
  166. )
  167.  
  168.  
  169. def _write_target_address(target_address, value, *, verbose=False):
  170. # collection of frames to prevent deallocation
  171. frames = []
  172. while True:
  173. # spray the heap with generators looking for a location where the
  174. # frame's local variables are within ``range_ * 8`` bytes of the
  175. # target address
  176. code = _make_write_codeobject(_write_range, value)
  177. generator = types.FunctionType(code, {})()
  178.  
  179. locals_address = id(generator.gi_frame) + _f_localsplus_offset
  180.  
  181. offset_bytes = target_address - locals_address
  182. offset_index = offset_bytes // 8
  183. if -_write_range <= offset_index <= _write_range:
  184. # we found a frame that is close enough to the target address;
  185. # clear the saved frames and continue
  186. frames.clear()
  187.  
  188. if verbose:
  189. print(
  190. f'found write frame within range:'
  191. f' offset={offset_index};'
  192. f' locals=0x{locals_address:x}'
  193. f' target=0x{target_address:x}',
  194. )
  195. break
  196. else:
  197. if verbose:
  198. print(
  199. f'write frame too far away:'
  200. f' offset={offset_index};'
  201. f' locals=0x{locals_address:x}'
  202. f' target=0x{target_address:x}',
  203. )
  204. # this frame isn't close enough; save it in memory so we don't try
  205. # here again
  206. frames.append(generator.gi_frame)
  207.  
  208. # make sure we set None to ``value`` and prime the generator
  209. assert next(generator) is value, 'yielded the wrong value'
  210.  
  211. def branch_selector():
  212. for n in range(_write_range):
  213. if n == offset_index:
  214. yield True
  215. break
  216.  
  217. yield False
  218.  
  219. if -n == offset_index:
  220. yield True
  221. break
  222.  
  223. yield False
  224.  
  225. try:
  226. # send the selector into the generator; this gets bound as
  227. # ``this_branch``
  228. generator.send(branch_selector().__next__)
  229. except StopIteration:
  230. # the branch after ``branch_selector`` returns True will raise a
  231. # ``StopIteration``
  232. pass
  233.  
  234.  
  235. # the maximum allowed offset from the address of the frame's locals
  236. # to the target address measured in pointer counts.
  237. _load_range = 2 ** 22
  238.  
  239.  
  240. def _make_load_bytecode(range_):
  241. """Create bytecode that emits one branch for all values in
  242. [-range_, range_] like:
  243.  
  244. if this_branch():
  245. if False:
  246. _n = False # create a new local
  247. yield _n
  248.  
  249. where ``this_branch`` is a callable that checks if we should assign to
  250. the given index. ``this_branch`` is not stored
  251. in the locals, instead we just store it on the stack.
  252.  
  253. To do negative values, we overflow oparg with man EXTENDED_ARG calls.
  254. """
  255. try:
  256. return _make_load_bytecode._cache[range_]
  257. except KeyError:
  258. pass
  259.  
  260. cached_file_name = f'_make_load_bytecode.{range_}'
  261. try:
  262. with open(cached_file_name, 'rb') as f:
  263. _make_load_bytecode._cache[range_] = ret = f.read()
  264. return ret
  265. except FileNotFoundError:
  266. pass
  267.  
  268. code = bytearray((
  269. dis.opmap['LOAD_CONST'], 0,
  270. dis.opmap['YIELD_VALUE'], 0,
  271. ))
  272.  
  273. def check(n):
  274. *extension, index = (n % 2 ** 32).to_bytes(4, 'big')
  275. extension = [*filter(bool, extension)]
  276.  
  277. code.extend((
  278. dis.opmap['DUP_TOP'], 0,
  279. dis.opmap['CALL_FUNCTION'], 0,
  280. dis.opmap['EXTENDED_ARG'], 0,
  281. dis.opmap['EXTENDED_ARG'], 0,
  282. dis.opmap['EXTENDED_ARG'], 0,
  283. dis.opmap['POP_JUMP_IF_FALSE'], 0,
  284. ))
  285.  
  286. ix = len(code) - 1
  287. for ext in extension:
  288. code.extend((dis.opmap['EXTENDED_ARG'], ext))
  289.  
  290. code.extend((
  291. dis.opmap['LOAD_FAST'], index,
  292. dis.opmap['YIELD_VALUE'], 0,
  293. ))
  294.  
  295. jump_index = len(code)
  296. *jump_extension, jump_index = jump_index.to_bytes(4, 'big')
  297. code[ix - 6] = jump_extension[0]
  298. code[ix - 4] = jump_extension[1]
  299. code[ix - 2] = jump_extension[2]
  300. code[ix] = jump_index
  301.  
  302. for n in range(range_):
  303. check(n)
  304. check(-n)
  305.  
  306. code.extend((
  307. dis.opmap['LOAD_CONST'], 0,
  308. dis.opmap['RETURN_VALUE'], 0
  309. ))
  310.  
  311. ret = _make_load_bytecode._cache[range_] = bytes(code)
  312. with open(cached_file_name, 'wb') as f:
  313. f.write(ret)
  314. return ret
  315.  
  316.  
  317. _make_load_bytecode._cache = {}
  318.  
  319.  
  320. def _make_load_codeobject(range_):
  321. return types.CodeType(
  322. 0,
  323. 0,
  324. 0,
  325. 2,
  326. 99,
  327. _make_load_bytecode(range_),
  328. (None,),
  329. (),
  330. (),
  331. '<string>',
  332. '<load-target-helper>',
  333. 1,
  334. b'',
  335. )
  336.  
  337.  
  338. def _load_target_address(target_address, *, verbose=False):
  339. # collection of frames to prevent deallocation
  340. frames = []
  341. while True:
  342. # spray the heap with generators looking for a location where the
  343. # frame's local variables are withing ``range_ * 8`` bytes of the
  344. # target address
  345. code = _make_load_codeobject(_load_range)
  346. generator = types.FunctionType(code, {})()
  347.  
  348. locals_address = id(generator.gi_frame) + _f_localsplus_offset
  349.  
  350. offset_bytes = target_address - locals_address
  351. offset_index = offset_bytes // 8
  352. if -_load_range <= offset_index <= _load_range:
  353. # we found a frame that is close enough to the target address;
  354. # clear the saved frames and continue
  355. frames.clear()
  356.  
  357. if verbose:
  358. print(
  359. f'found load frame within range:'
  360. f' offset={offset_index};'
  361. f' locals=0x{locals_address:x}'
  362. f' target=0x{target_address:x}',
  363. )
  364. break
  365. else:
  366. if verbose:
  367. print(
  368. f'load frame too far away:'
  369. f' offset={offset_index};'
  370. f' locals=0x{locals_address:x}'
  371. f' target=0x{target_address:x}',
  372. )
  373. # this frame isn't close enough; save it in memory so we don't try
  374. # here again
  375. frames.append(generator.gi_frame)
  376.  
  377. # make sure we set None to ``value`` and prime the generator
  378. assert next(generator) is None, 'yielded the wrong value'
  379.  
  380. def branch_selector():
  381. for n in range(_write_range):
  382. if n == offset_index:
  383. yield True
  384. break
  385.  
  386. yield False
  387.  
  388. if -n == offset_index:
  389. yield True
  390. break
  391.  
  392. yield False
  393.  
  394. # send the selector into the generator; this gets bound as
  395. # ``this_branch``
  396. return generator.send(branch_selector().__next__)
  397.  
  398.  
  399. def tuple_setitem(t, ix, value, *, verbose=False):
  400. """``setitem`` for tuples.
  401.  
  402. Parameters
  403. ----------
  404. t : tuple
  405. The tuple to assign into.
  406. ix : int
  407. The index to assign to (without bounds checking).
  408. value : any
  409. The value to store at index ``ix``.
  410. verbose : bool, optional
  411. Print debugging information?
  412. """
  413. # the target address is the id of the tuple + the offset of the first
  414. # element + the index * the size of a pointer
  415. target_address = id(t) + _ob_item_offset + ix * 8
  416. _write_target_address(target_address, value, verbose=verbose)
  417.  
  418.  
  419. def access_memory(ob, size, *, verbose=False):
  420. """Access the underlying memory for an object as a mutable memory view.
  421.  
  422. Parameters
  423. ----------
  424. ob : object
  425. The object to access
  426. size : int
  427. The number of bytes to access.
  428. verbose : bool, optional
  429. Print debugging information?
  430.  
  431. Returns
  432. -------
  433. data : memoryview
  434. A view of ``size`` bytes starting at the address of ``ob``.
  435. """
  436. underlying_memory = bytearray(size)
  437. view = memoryview(underlying_memory)
  438.  
  439. managed_buffer = _load_target_address(
  440. id(view) + _mbuf_offset,
  441. verbose=verbose,
  442. )
  443.  
  444. _write_target_address(
  445. id(managed_buffer) + _master_offset,
  446. ob,
  447. verbose=verbose,
  448. )
  449. _write_target_address(
  450. id(view) + _view_offset,
  451. ob,
  452. verbose=verbose,
  453. )
  454.  
  455. return view
  456.  
  457.  
  458. def execute_binary_payload(payload, *, verbose=False):
  459. """Execute an arbitrary compiled payload.
  460.  
  461. Parameters
  462. ----------
  463. payload : bytes
  464. The compiled payload to execute.
  465. verbose : bool, optional
  466. Print debugging information?
  467. """
  468. buffer = mmap.mmap(-1, len(payload), prot=mmap.PROT_EXEC | mmap.PROT_WRITE)
  469. buffer[:] = payload
  470.  
  471. target_address = access_memory(buffer, 64, verbose=verbose)[
  472. _sizeof_pyobject:_sizeof_pyobject + 8
  473. ]
  474.  
  475. class C:
  476. pass
  477.  
  478. mem = access_memory(C, 124, verbose=verbose)
  479. mem[_tp_repr_offset:_tp_repr_offset + 8] = target_address
  480. try:
  481. repr(C())
  482. except SystemError:
  483. pass
Add Comment
Please, Sign In to add comment