Last active
May 30, 2022 03:33
-
-
Save maple3142/d8da88c4d81dae7da99b2bdc68cb0d7d to your computer and use it in GitHub Desktop.
DEF CON Quals 2022 - admad
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# A Python 3 script trying to in include every bytecode in Python 3.12, but it actually misses these bytecode: | |
# {'LOAD_ASSERTION_ERROR', 'CHECK_EG_MATCH', 'UNPACK_EX', 'POP_JUMP_BACKWARD_IF_NONE', 'DELETE_DEREF', 'POP_JUMP_FORWARD_IF_NOT_NONE', 'EXTENDED_ARG', 'PRINT_EXPR', 'LOAD_CLASSDEREF', 'MAP_ADD', 'POP_JUMP_BACKWARD_IF_NOT_NONE', 'JUMP_IF_FALSE_OR_POP', 'IS_OP', 'LIST_TO_TUPLE', 'JUMP_IF_TRUE_OR_POP', 'PREP_RERAISE_STAR'} | |
# the idea comes from https://github.com/kholia/dedrop | |
def main(): | |
(lambda:1)() | |
zzz = 1 | |
del zzz | |
a = 1; b = 2 | |
(a, b) = (b, a) | |
a = 1 | |
(a, a, a) = (a, a, a) | |
{'a':1} | |
x = list(range(6)) | |
x[2:4] += 'abc' | |
a = 1 | |
a = +a | |
a = 1 | |
a = -a | |
a = 1 | |
a = not a | |
a = 1 | |
a = 1 | |
a = ~a | |
a = [i*i for i in (1,2)] | |
a = 2 | |
a = a ** 2 | |
a = 2 | |
a = a * 2 | |
a = 2 | |
a = a / 2 | |
a = 2 | |
a = a % 2 | |
a = 2 | |
a = a + 2 | |
a = 2 | |
a = a - 2 | |
a = [1] | |
a[0] | |
a = 2 | |
a = a // 2 | |
a = 2 | |
a = a / 2 | |
a = a // 2 | |
a = 1 | |
a //= 10.2 | |
a = 1 | |
a /= 2 | |
a //= 2 | |
b = 2 | |
a //= b | |
a /= b | |
a = [1,2,3] | |
a = a[:] | |
a = [1,2,3] | |
a = a[1:] | |
a = [1,2,3] | |
a = a[:2] | |
a = [1,2,3] | |
a = a[1:2] | |
a = [1,2,3] | |
a[:] = [1,2,3] | |
a = [1,2,3] | |
a[1:] = [2,3] | |
a = [1,2,3] | |
a[:2] = [1,2] | |
a = [1,2,3] | |
a[1:2] = [2] | |
a = [1,2,3] | |
del a[:] | |
a = [1,2,3] | |
del a[1:] | |
a = [1,2,3] | |
del a[:2] | |
a = [1,2,3] | |
del a[1:2] | |
a = 1 | |
a += 1 | |
a = 1 | |
a -= 1 | |
a = 1 | |
a *= 1 | |
a = 1 | |
a /= 1 | |
a = 1 | |
a %= 1 | |
a = [0, 1] | |
a[0] = 1 | |
a = [1] | |
del a[0] | |
a = 1 | |
a = a << 1 | |
a = 1 | |
a = a >> 1 | |
a = 1 | |
a = a & 1 | |
a = 1 | |
a = a ^ 1 | |
a = 1 | |
a = a | 1 | |
a = 1 | |
a **= 1 | |
for a in (1,2): pass | |
print("hello world!") | |
print() | |
def fv(a,b): pass | |
av = (1,2) | |
fv(*av) | |
def fkv(a,b,c): pass | |
akv = {"b":1,"c":2} | |
b = (3,) | |
fkv(*b, **akv) | |
def fk(a,b): pass | |
ak = {"a":1,"b":2} | |
fk(**ak) | |
import sys | |
print("hello world", end=' ', file=sys.stdout) | |
import sys | |
print(file=sys.stdout) | |
del sys.api_version | |
zzz = 89 | |
del zzz | |
a = 1 | |
a <<= 1 | |
a = 1 | |
a >>= 1 | |
a = 1 | |
a &= 1 | |
a = 1 | |
a ^= 1 | |
a = 1 | |
a |= 1 | |
for a in (1,2): break | |
try: | |
with open("1.txt") as f: | |
print(f.read()) | |
except: | |
pass | |
class a: pass | |
# empty file | |
exec("print('hello world')", globals(), locals()) | |
frozenset({1, 2, 3}) | |
for a in (1,2): break | |
try: | |
a = 1 | |
except ValueError: | |
a = 2 | |
finally: | |
a = 3 | |
class a: pass | |
a = 1 | |
a = 1 | |
del a | |
(a, b) = "ab" | |
for i in (1,2): pass | |
a = 0 | |
b = [0] | |
b[a] += 1 | |
a = 1 | |
a = 1 | |
a = a | |
a = 1; | |
a = (a, a) | |
[1,2,3] | |
{"a":1,"b":2} | |
[].sort() | |
a = 1 == 2 | |
a = 2+3+4 | |
"@"*4 | |
a="abc" + "def" | |
a = 3**4 | |
a = 13//4 | |
a //= 2 | |
from dis import opmap | |
if 1 == 2: pass | |
else: pass | |
if 1 == 2: pass | |
else: pass | |
if not(1 == 2): pass | |
else: pass | |
for i in (1,2): pass | |
for x in (1,2): | |
try: continue | |
except: pass | |
while 0 > 1: pass | |
try: | |
a = 1 | |
except ValueError: | |
a = 2 | |
finally: | |
a = 3 | |
try: | |
a = 1 | |
except ValueError: | |
a = 2 | |
finally: | |
a = 3 | |
a = [1,2,3,4] | |
b = a[::-1] | |
xyz = 0 | |
def lolcats(): | |
global xyz | |
pass | |
raise ValueError | |
def fc(): | |
a = 1 | |
zyx = set() | |
zyx.add("hello") | |
zyx.add("abc") | |
zyx.remove("hello") | |
def g(): | |
return a + 1 | |
return g() | |
print(fc()) | |
def f(): pass | |
f() | |
def f1(): | |
from sys import environ | |
a = 1 | |
a = a | |
def f2(): | |
a = 1 | |
a = a | |
def f3(): | |
a = 1 | |
del a | |
import sys | |
sys.stderr = sys.stdout | |
import sys | |
# del sys.stderr | |
l1 = 0 | |
def lolx(): | |
global l1 | |
l1 = 1 | |
l2 = 0 | |
def loly(): | |
global l2 | |
del l2 | |
def f(): | |
a = 3 | |
b = 5 | |
def g(): | |
return a + b | |
f() | |
mylist = [1, 1, 1, 2, 2, 3] | |
z = {x for x in mylist if mylist.count(x) >= 2} | |
a = {x for x in 'abracadabra' if x not in 'abc'} | |
set([20, 0]) | |
lolx() | |
loly() | |
def foo(): | |
print('hello') | |
yield 1 | |
print('world') | |
yield 2 | |
a = foo() | |
print(next(a)) | |
print(next(a)) | |
def myfunc(alist: list): | |
return len(alist) | |
def peko(*args, **kwargs): | |
yield from f() | |
1 | |
2 | |
3 | |
if True: | |
return 1 | |
1 | |
2 | |
3 | |
pass | |
async def f(): | |
await f() | |
async for x in f(): | |
pass | |
async def g(): | |
await g() | |
yield 1 | |
async with x: | |
pass | |
class A: | |
n: int | |
a = 1 | |
match a: | |
case 1: print(1) | |
case []: pass | |
case A(): pass | |
case {1:a}: pass | |
peko(*[123]) | |
peko(**{'a':1}) | |
peko(*[123], **{'a':1}) | |
(x for x in []) | |
dt={} | |
dt|={'1':123} | |
f'1{2}3' | |
{**{1:2}} | |
tuple([1,2,3]) | |
del a | |
from os import * | |
peko(*[1,2,3,4,5,6,7,8,9]) | |
assert True | |
class A: | |
pass | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# fix the opcode from mapping, and attempt to solve for the flag | |
# but it turns out the bytecode also patches python to make `bytes([ord('F'), ...])` behavior different | |
# so it is necessary to guess or reverse how it works | |
# out team got the flag by guessing | |
import dis | |
import marshal | |
from dis import opname | |
fmap = { | |
151: 151, | |
100: 108, | |
132: 133, | |
90: 98, | |
2: 12, | |
101: 103, | |
166: 166, | |
0: 10, | |
171: 172, | |
1: 11, | |
108: 100, | |
106: 106, | |
95: 95, | |
97: 99, | |
102: 101, | |
71: 70, | |
120: 120, | |
107: 104, | |
114: 111, | |
110: 115, | |
32: 30, | |
30: 33, | |
152: 152, | |
129: 129, | |
92: 96, | |
31: 32, | |
33: 31, | |
99: 97, | |
103: 107, | |
142: 142, | |
105: 102, | |
164: 163, | |
68: 71, | |
122: 122, | |
155: 155, | |
157: 157, | |
165: 162, | |
162: 0, | |
91: 91, | |
84: 84, | |
9: 15, | |
83: 86, | |
125: 125, | |
126: 126, | |
124: 124, | |
116: 116, | |
133: 131, | |
25: 35, | |
60: 61, | |
10: 0, | |
11: 1, | |
12: 2, | |
15: 9, | |
61: 60, | |
93: 93, | |
140: 140, | |
156: 156, | |
172: 171, | |
96: 92, | |
53: 53, | |
160: 160, | |
35: 25, | |
49: 0, | |
115: 112, | |
119: 119, | |
89: 87, | |
104: 109, | |
163: 164, | |
36: 49, | |
109: 105, | |
176: 176, | |
130: 130, | |
145: 145, | |
135: 137, | |
138: 138, | |
136: 136, | |
149: 146, | |
137: 139, | |
98: 90, | |
175: 175, | |
146: 147, | |
118: 118, | |
75: 82, | |
86: 83, | |
69: 69, | |
123: 0, | |
134: 128, | |
131: 0, | |
50: 37, | |
51: 51, | |
54: 54, | |
87: 89, | |
52: 52, | |
85: 85, | |
} | |
imap = {v: k for k, v in fmap.items()} | |
with open("chall.pyc", "rb") as f: | |
header = f.read(16) | |
code = marshal.load(f) | |
def walk(code): | |
new_code = bytes([v if i & 1 else imap[v] for i, v in enumerate(code.co_code)]) | |
new_consts = tuple( | |
[c if type(c) != type(code) else walk(c) for c in code.co_consts] | |
) | |
return code.replace(co_consts=new_consts, co_code=new_code) | |
new_code = walk(code) | |
# dis.dis(new_code) | |
with open("new_chall.pyc", "wb") as f: | |
f.write(header) | |
marshal.dump(new_code, f) | |
# dis.dis(new_code) | |
check_flag = new_code.co_consts[1] | |
bc = check_flag.co_code | |
consts = check_flag.co_consts | |
base = 48 | |
chk = 84 - 48 | |
flag = bytearray(59) | |
for i in range(1000): | |
j = base + chk * i | |
if j + 4 >= len(bc): | |
break | |
# dis.dis(bc[j:j+chk]) | |
# assert opname[bc[j + 4]] == "LOAD_CONST" | |
idx = consts[bc[j + 4 + 1]] | |
# assert opname[bc[j + 18]] == "BINARY_OP" | |
assert bc[j + 18 + 1] == 1 | |
# assert opname[bc[j + 16]] == "LOAD_CONST" | |
mask = consts[bc[j + 16 + 1]] | |
# assert opname[bc[j + 22]] == "LOAD_CONST" | |
value = consts[bc[j + 22 + 1]] | |
print(f"{j} flag[{idx}] & {mask} == {value}") | |
if mask == value: | |
flag[idx] |= mask | |
print(flag) | |
# bytearray(b'\x8e\x86\x8d\x95\xbb\xaa\xb9\xb2\xc8\xb3\xba\xc5\xaf\xc3\xc8\xc7\xc5\xb9\xcf\xe3\xe3\xe6\xd3\xd3\xd1\xe4\xe1\xcd\xe3\xf2\xed\xfa\xe0\xe9\xea\xddC0EH\xe7\xf3\x0f\xed\x06\x17\x02\xf5_Z^\x13`\x16\x15fhj)') | |
from new_chall import check_flag | |
print(check_flag(flag)) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# compare the bytecode difference to build a mapping from original opcode and patched opcode | |
# it is actually not correct as it assumes every instruction is 2 bytes, which is untrue is later python versions | |
import dis | |
import marshal | |
# rm -rf __pycache__;PYTHONHOME=`pwd` ./bin/python -c 'import hello' ; xxd __pycache__/hello.cpython-312.pyc && mv __pycache__/hello.cpython-312.pyc patched_hello.pyc | |
# rm -rf __pycache__; ./cpython-702e0da000bf28aa20cb7f3893b575d977506495/python -c 'import hello' ; xxd __pycache__/hello.cpython-312.pyc && mv __pycache__/hello.cpython-312.pyc original_hello.pyc | |
fp=open('patched_hello.pyc','rb') | |
fo=open('original_hello.pyc','rb') | |
fp.seek(16) | |
fo.seek(16) | |
orig = marshal.load(fo) | |
patched = marshal.load(fp) | |
dt = {} | |
dt2 = {} | |
def walk(orig, patched): | |
print(orig.co_name, len(orig.co_code), len(patched.co_code)) | |
for i in range(0, len(orig.co_code), 2): | |
o = dis.opname[orig.co_code[i]] | |
p = dis.opname[patched.co_code[i]] | |
if o in dt: | |
if dt[o] != p: | |
print('NEQ',o, dt[o], p) | |
dt[o] = p | |
dt2[orig.co_code[i]] = patched.co_code[i] | |
for x, y in zip(orig.co_consts, patched.co_consts): | |
if type(x) == type(orig) and type(y) == type(patched): | |
walk(x,y) | |
walk(orig, patched) | |
print(dt) | |
print(len(dt.keys())) | |
print(set(dis.opmap.keys()) - set(dt.keys())) | |
print(dt2) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# use xx.py to build the opcode mapping | |
# need to compile https://github.com/python/cpython/tree/702e0da000bf28aa20cb7f3893b575d977506495 at ./cpython-702e0da000bf28aa20cb7f3893b575d977506495 | |
rm -rf __pycache__;PYTHONHOME=`pwd` ./bin/python -c 'import hello' ; mv __pycache__/hello.cpython-312.pyc patched_hello.pyc | |
rm -rf __pycache__; ./cpython-702e0da000bf28aa20cb7f3893b575d977506495/python -c 'import hello' ; mv __pycache__/hello.cpython-312.pyc original_hello.pyc | |
./cpython-702e0da000bf28aa20cb7f3893b575d977506495/python xx.py |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment