Skip to content

Instantly share code, notes, and snippets.

@maple3142
Last active May 30, 2022 03:33
Show Gist options
  • Save maple3142/d8da88c4d81dae7da99b2bdc68cb0d7d to your computer and use it in GitHub Desktop.
Save maple3142/d8da88c4d81dae7da99b2bdc68cb0d7d to your computer and use it in GitHub Desktop.
DEF CON Quals 2022 - admad
# 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()
# 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))
# 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)
# 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