The following code snippet presents the first step of this challenge. It’s a highly nested Python lambda function. We need to find an URL pointing to a file so that we can pass to the second step.
The first thing I’ve noticed is that the function takes 3 arguments g, c, d and it has a default argument $ that is initialized to None. In order to understand what does the function, I adopted a bottom-up approach to dissect it, from the most nested one to the first one.
(lambda g, c, d: (lambda _: (_.__setitem__('$', ''.join([(_['chr']
 if ('chr'in _) else chr)((_['_'] if ('_' in _) else _)) for _['_'
] in (_['s'] if ('s'in _) else s)[::(-1)]])), _)[-1])( (lambda _:
(lambda f, _: f(f, _))((lambda__,_: ((lambda _: __(__, _))((lambda
 _: (_.__setitem__('i', ((_['i'] if ('i'in _) else i) + 1)),_)[(-1
)])((lambda _: (_.__setitem__('s',((_['s'] if ('s'in _) else s) +
[((_['l'] if ('l' in _) else l)[(_['i'] if ('i' in _) else i)] ^ (
_['c'] if ('c' in _) else c))])), _)[-1])(_))) if (((_['g'] if ('g
' in_) else g) % 4) and ((_['i'] if ('i' in _) else i)< (_['len']
if ('len' in _) else len)((_['l'] if ('l' in _) else l)))) else _)
), _) ) ( (lambda _: (_.__setitem__('!', []), _.__setitem__('s', _
['!']), _)[(-1)] ) ((lambda _: (_.__setitem__('!', ((_['d'] if ('d
' in _) else d) ^ (_['d'] if ('d' in _) elsed))), _.__setitem__('i
', _['!']), _)[(-1)])((lambda _: (_.__setitem__('!', [(_['j'] if (
'j' in _) else j) for  _[ 'i'] in (_['zip'] if ('zip' in _) elsezi
p)((_['l0'] if ('l0' in _) else l0), (_['l1'] if ('l1' in _) else
l1)) for_['j'] in (_['i'] if ('i' in _) else i)]), _.__setitem__('
l', _['!']), _)[-1])((lambda _: (_.__setitem__('!', [1373, 1281, 1
288, 1373, 1290, 1294, 1375,1371,1289, 1281, 1280, 1293, 1289, 128
0, 1373, 1294, 1289, 1280, 1372, 1288,1375,1375, 1289, 1373, 1290,
 1281, 1294, 1302, 1372, 1355, 1366, 1372, 1302,1360, 1368, 1354,
1364, 1370, 1371, 1365, 1362, 1368, 1352, 1374, 1365, 1302]), _.__
setitem__('l1',_['!']), _)[-1])((lambda _: (_.__setitem__('!',[137
5,1368, 1294, 1293, 1373, 1295, 1290, 1373, 1290, 1293, 1280, 1368
, 1368,1294,1293, 1368, 1372, 1292, 1290, 1291, 1371, 1375, 1280,
1372, 1281, 1293,1373,1371, 1354, 1370, 1356, 1354, 1355, 1370, 13
57, 1357, 1302, 1366, 1303,1368,1354, 1355, 1356, 1303, 1366, 1371
]), _.__setitem__('l0', _['!']), _)[(-1)])
({ 'g': g, 'c': c, 'd': d, '$': None})))))))['$'])In total, there are 7 lambda functions. I present first the final pseudocode and the details of each function comes after. The pseudocode is the following:
(
  lambda g, c, d: (dollar=reversedS)(
      (s=listXorC) (
          (s=listofZip) (
              (setIto0)(
                  (ziplist)(
                      (list1)(
                          (list0)(
                            { 'g': g, 'c': c, 'd': d, '$': None}
                          )
                      )
                  )
              ) 
          ) 
      )
  )['$']
)list0 adds to the dictionary a list of integers with key ’l0’.
list1 adds another list of integers with key ’l1’.
ziplist applies Python built-in function ZIP to ’l0’ and ’l1’
setIto0 adds to the dictionary an element with value 0 (by XORing the argument d with itself) and key ’i’. ’i’ is used later as the counter of iteration.
s=listofZip creates a list ’s’ from the list of tuples created by ’ziplist’
s=listXorC applies XOR operation on each element of list ’s’ with the argument c in condition that the argument $g\%4 != 0$
dollar=reversedS sets ’$’ to the character chain that contains each element of list ’s’ in the reversed order.
Therefore, I can trigger the decryption routine with g=2 (1, or 3), c=key, d=0:
>>> func = (lambda g, c, d: ...)
>>> for key in range(0x500, 0x500+100):
...   func(2, key, 0)
...To find the right key (0x570), I’ve brute forced about 100 numbers and the URL to the next step is the following:
/blog.quarkslab.com/static/resources/b7d8438de09fffb12e39
50e7ad4970a4a998403bdf3763dd4178adf
to find the title of a fan song hidden in the program. Its salted SHA256 (with bacalhau as salt) is:
 61b42c223973996c797a9a366c64c3595052ff71089b4ff13d3251b66b6366e9
The commands file and strings revealed the following information. For the reason of brevity, only some interesting strings are presented. By reading other strings, it turned out that this file is a Python interpreter.
$ mv b7d8438de09fffb12e3950e7ad4970a4a998403bdf3763dd4178adf python
$ file python python: ELF 64-bit LSB executable, x86-64, version 1
(SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, not
stripped
$ strings ./python
...
obfuscate/gen.pys
<genexpr>
Truet
quarkslabt
appendt
join(
obfuscate/gen.pyt
True(
Robert_Forsyth(
obfuscate/gen.pyt
There are two kinds of people in the world: those who say there is no
 such thing as infinite recursion, and those who say ``There are two
 kinds of people in the world: those who say there is no such thing 
as infinite recursion, and those who say ...
a9bd62e4d5f2+
nvcs/newopcodes
...With IDA Pro, I found quickly the referece of these strings and discovered that a new built-in module is added.
$ export PYTHONPATH=/usr/lib/python2.7:/usr/lib/python2.7/plat-
linux2:/usr/lib/python2.7/lib-old:/usr/lib/python2.7/lib-dynloa
d:/usr/local/lib/python2.7/dist-packages:/usr/lib/python2.7/dis
t-packages:/usr/lib/pymodules/python2.7
$ chmod u+x ./python
$ ./python
>>> import sys
>>> sys.builtin_module_names
('_builtin_', '_main_', 'do_not_run_me', ...}
>>> import do_not_run_me
>>> dir(do_not_run_me)
['_doc_', '_name_', '_package_', 'run_me']
>>> do_not_run_me.run_me()
 Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  TypeError: function takes exactly 1 argument (0 given)
>>> do_not_run_me.run_me("aaaa")
zsh: segmentation fault  ./pythonAfter have tested the function run_me, I found that it takes one argument and usually generates a segmentation fault by calling it. So I started to reverse this function. The assembler code of this function can be found in the Appendix. The following pseudocode presents the reversed run_me function. It loads three functions and executes them all. One of these functions in particular is read from run_me’s argument and the other two are loaded from memory. Obviously, I needed to reverse the two functions loaded from memory.
PyObject *run_me(PyObject *self, PyObject *args){
  char *input;
  int size;
  if(PyArg_ParseTuple(args, 's#', &input, &size)){
    PyObject* code_obj;
    code_obj = PyMarshal_ReadObjectFromString(0x56c940, 0x91);
    PyObject* incr = PyFunction_New(code_obj,
                        _PyThreadState_Current->frame->f_globals);
    PyObject* arg = PyTuple_New(0);
    PyObject_Call(incr, arg, NULL);
    code_obj = PyMarshal_ReadObjectFromString(input, size);
    PyObject* func = PyFunction_New(code_obj,
                        _PyThreadState_Current->frame->f_globals);
    arg = PyTuple_New(0);
    PyObject_Call(func, arg, NULL);
    code_obj = PyMarshal_ReadObjectFromString(0x56c720, 0x217);
    PyObject* decr = PyFunction_New(code_obj,
                        _PyThreadState_Current->frame->f_globals);
    arg = PyTuple_New(0);
    return PyObject_Call(decr, arg, NULL);
  }
  return NULL;
}Firstly, I dumped the concerned memory zones with GDB. Then I tried the Python package dis. But it couldn’t disassembly them because some attributes were removed.
$ gdb -q ./python
Reading symbols from ./python...done.
gdb-peda$ dump memory incr.9351 0x56c940 0x56c940+0x91
gdb-peda$ dump memory decr.9357 0x56c720 0x56c720+0x217
gdb-peda$ q $ python2
>>> import marshal
>>> import dis
>>> code_obj = marshal.load(open("incr.9351"))
>>> code_obj
<code object foo at 0x7ffff7ec8d30, file "obfuscate/gen.py", line 5>
>>> dis.dis(code_obj)
6           0 LOAD_CONST               1 (1)
3 LOAD_CLOSURE             0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/dis.py", line 43, in dis
disassemble(x)
File "/usr/lib/python2.7/dis.py", line 107, in disassemble
print '(' + free[oparg] + ')',
IndexError: tuple index out of range
>>> code_obj = marshal.load(open("decr.9357"))
>>> code_obj
<code object foo at 0x7ffff7e77f30, file "obfuscate/gen.py", line 17>
>>> dis.dis(code_obj)
19     >>    0 LOAD_FAST                0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/dis.py", line 43, in dis
disassemble(x)
File "/usr/lib/python2.7/dis.py", line 101, in disassemble
print '(' + co.co_varnames[oparg] + ')',
IndexError: tuple index out of rangeIn Quarkslab’s blog, there is another article Building an obfuscated Python interpreter: we need more opcodes that explains how to add new opcodes for a custom Python interpreter. In the same article, author cites Looking inside the (Drop) box) that explained Dropbox was using a custom Python interpreter with permuted opcodes. I’m not sure that it’s intended to be a hint, but it explains the challenge itself.
| Permuted opcode | Original opcode | 
|---|---|
| LOAD_CLOSURE | STORE_FAST | 
| LOAD_FAST | LOAD_GLOBAL | 
| STORE_SUBSCR | BINARY_ADD | 
| BINARY_TRUE_DIVIDE | RETURN_VALUE | 
| CONTINUE_LOOP | MAKE_FUNCTION | 
| RETURN_VALUE | GET_ITER | 
| MAKE_CLOSURE | CALL_FUNCTION | 
| IMPORT_STAR | POP_TOP | 
| SETUP_WITH | LOAD_FAST | 
| BUILD_CLASS | YIELD_VALUE | 
I checked that this Python interpreter does have permuted opcodes and a few new opcodes.The above table illustrates the permuted opcodes and their original opcodes In order to revsere the permuted opcodes, I compared their assembler code with the source code. I’ve written a Python script that used dis package to map opcode and its name. The source code can be found in Appendix. As for the new opcodes, there are actually only two:
LOAD_CONST and setCustomOpcodeOffset
LOAD_CONST and unsetCustomOpcodeOffset
The first one is always called before the second in order to set up a jump conditon (setCustomOpcodeOffset) so that the following opcode will jump to the expected address.
  gdb-peda$ x/10i 0x4b18a5
  => 0x4b18a5 <PyEval_EvalFrameEx+4837>:  mov    rax,QWORD PTR [rsp+0x58]
  0x4b18aa <PyEval_EvalFrameEx+4842>:  movsxd r8,r8d
  0x4b18ad <PyEval_EvalFrameEx+4845>:  mov    r13,rcx
  0x4b18b0 <PyEval_EvalFrameEx+4848>:  add    rbp,0x8
  0x4b18b4 <PyEval_EvalFrameEx+4852>:  mov    r14d,0x1 ;setCustomOpcodeOffset
  0x4b18ba <PyEval_EvalFrameEx+4858>:  xor    ebx,ebx
  0x4b18bc <PyEval_EvalFrameEx+4860>:  mov    r12,QWORD PTR [rax+r8*8+0x18]
  0x4b18c1 <PyEval_EvalFrameEx+4865>:  add    QWORD PTR [r12],0x1
  0x4b18c6 <PyEval_EvalFrameEx+4870>:  mov    QWORD PTR [rbp-0x8],r12
  0x4b18ca <PyEval_EvalFrameEx+4874>:  jmp    0x4b08f1 <PyEval_EvalFrameEx+817>The second opcode loads the value ’setCustomOpcodeOffset’ and use it as an index for the jump table. Since the first opcode always set this value to 1, the following opcode jumps to the same address and do a LOAD_CONST operation and unsetCustomOpcodeOffset, no matter what is its opcode.
  gdb-peda$ x/10i 0x4b0938
  => 0x4b0938 <PyEval_EvalFrameEx+888>:   test   r14d,r14d
  0x4b093b <PyEval_EvalFrameEx+891>:   je     0x4b0950 <PyEval_EvalFrameEx+912>
  0x4b093d <PyEval_EvalFrameEx+893>:   cmp    r14d,0xad
  0x4b0944 <PyEval_EvalFrameEx+900>:   ja     0x4b0950 <PyEval_EvalFrameEx+912>
  0x4b0946 <PyEval_EvalFrameEx+902>:   jmp    QWORD PTR [r14*8+0x55e780]
  gdb-peda$ x/10i 0x4b1073
  => 0x4b1073 <PyEval_EvalFrameEx+2739>:  mov    rax,QWORD PTR [rsp+0x58]
  0x4b1078 <PyEval_EvalFrameEx+2744>:  movsxd r8,r8d
  0x4b107b <PyEval_EvalFrameEx+2747>:  mov    r13,rcx
  0x4b107e <PyEval_EvalFrameEx+2750>:  add    rbp,0x8
  0x4b1082 <PyEval_EvalFrameEx+2754>:  xor    r14d,r14d ;unsetCustomOpcodeOffset
  0x4b1085 <PyEval_EvalFrameEx+2757>:  xor    ebx,ebx
  0x4b1087 <PyEval_EvalFrameEx+2759>:  mov    r12,QWORD PTR [rax+r8*8+0x18]
  0x4b108c <PyEval_EvalFrameEx+2764>:  add    QWORD PTR [r12],0x1
  0x4b1091 <PyEval_EvalFrameEx+2769>:  mov    QWORD PTR [rbp-0x8],r12
  0x4b1095 <PyEval_EvalFrameEx+2773>:  jmp    0x4b08f1 <PyEval_EvalFrameEx+817>In the following sections, I analyze the two code objects incr.9351 and decr.9357 thanks to the previously created disassembler.
As his name indicates, it add one to the global variable ’True’ and store it.
co_consts: (None, 1)
co_varnames: ('Robert_Forsyth',)
co_names: ('True',)
co_cellvars: ()
LOAD_CONST: 1     #co_consts[1]=1
STORE_FAST: 0
LOAD_GLOBAL: 0    #global variable 'True'
LOAD_CONST: 1     #co_consts[1]=1
BINARY_ADD        #add global variable 'True' by 1
STORE_GLOBAL: 0
LOAD_GLOBAL: 0
RETURN_VALUEThis function first loads the global variable ’True’ and check if it’s equal to 3. Then if the global variable ’quarkslab’ exists, it loads two Python built-in functions append and join, one empty string and another code object in memory. Actually there is no need to reverse this code object, I found the right song title without studying it. Anyway, I’ve put a few explanation of this code object in Appendix. Then it creates a new function with this code object. After that, it builds a list of integers and call the new function by passing this list as argument. Finally it creates a string by concatenating all elements in the list generated by previous function, append this string to the global variable ’quarkslab’ and return.
co_consts: (None, 3, 1, '', <code object <genexpr> at 0x7ffff7e77eb0,
 file "obfuscate/gen.py", line 22>, 75, 98, 127, 45, 89, 101, 104,
 67, 122, 65, 120, 99, 108, 95, 125, 111, 97, 100, 110)
co_varnames: ()
co_names: ('True', 'quarkslab', 'append', 'join')
co_cellvars: ()
LOAD_GLOBAL: 0          #global variable 'True'
LOAD_CONST: 1           #co_consts[1]=3
COMPARE_OP: 3           #dis.cmp_op[3]="!="
POP_JUMP_IF_FALSE: 25   #jump if variable 'True' equal to 3
LOAD_GLOBAL: 1          #global variable 'quarkslab'
LOAD_ATTR: 2            #getattr(quarkslab, 'append')
LOAD_CONST: 3           #co_consts[3]=''
LOAD_ATTR: 3            #getattr(quarkslab, 'join')
LOAD_CONST: 4           #co_consts[4]=code object <genexpr>
MAKE_FUNCTION: 0
LOAD_CONST: 5           #co_consts[5]
LOAD_CONST: 6           #co_consts[6]
LOAD_CONST: 7           #co_consts[7]
LOAD_CONST: 8           #co_consts[8]
LOAD_CONST: 9           #co_consts[9]
LOAD_CONST: 10          #co_consts[10]
LOAD_CONST: 11          #co_consts[11]
LOAD_CONST: 8           #co_consts[12]
LOAD_CONST: 12          #co_consts[11]
LOAD_CONST: 11          #co_consts[13]
LOAD_CONST: 13          #co_consts[8]
LOAD_CONST: 8           #co_consts[6]
LOAD_CONST: 14          #co_consts[14]
LOAD_CONST: 15          #co_consts[15]
LOAD_CONST: 16          #co_consts[16]
LOAD_CONST: 17          #co_consts[17]
LOAD_CONST: 7           #co_consts[7]
LOAD_CONST: 8           #co_consts[8]
LOAD_CONST: 18          #co_consts[18]
LOAD_CONST: 11          #co_consts[11]
LOAD_CONST: 19          #co_consts[19]
LOAD_CONST: 15          #co_consts[15]
LOAD_CONST: 20          #co_consts[20]
LOAD_CONST: 21          #co_consts[21]
LOAD_CONST: 22          #co_consts[22]
LOAD_CONST: 23          #co_consts[23]
BUILD_LIST: 26          #list0
GET_ITER
CALL_FUNCTION: 1        #list1=genexpr(iter(list0))
CALL_FUNCTION: 1        #str0=''.join(list1)
CALL_FUNCTION: 1        #quarkslab.appen(str0)
POP_TOP
LOAD_CONST: 0
RETURN_VALUEIn conclusion, run_me function first reads a code object from argument. Then it calls incr.9351 that increments the global variable ’True’ to 2. After this, the submitted code object is called and finally the function decr.9357. In order to call the in memory code object genexpr, two conditions should be satisfied:
the global variable ’True’ = 3.
the global variable ’quarkslab’ is a not empty list.
My solution is simple: pass the same code object as incr.9351 and set the global variable ’quarkslab’ before calling run_me.
$ cat ./run_me.py
from do_not_run_me import run_me                                                                                                                                                 
global quarkslab                                                                                                                                                                 
quarkslab = ['aaa']                                                                                                                                                              
run_me(open('incr.9351').read())
print(quarkslab[-1])
$ ./python ./run_me.py
For The New Lunar Republic
$ echo -n 'bacalhauFor The New Lunar Republic' | sha256sum
61b42c223973996c797a9a366c64c3595052ff71089b4ff13d3251b66b6366e9  -1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
gdb -q ./python
Reading symbols from ./python...done.
gdb-peda$ disassemble run_me
Dump of assembler code for function run_me:
   0x0000000000513d90 <+0>:     push   rbp
   0x0000000000513d91 <+1>:     mov    rdi,rsi
   0x0000000000513d94 <+4>:     xor    eax,eax
   0x0000000000513d96 <+6>:     mov    esi,0x56c70b
   0x0000000000513d9b <+11>:    push   rbx
   0x0000000000513d9c <+12>:    sub    rsp,0x28
   0x0000000000513da0 <+16>:    lea    rcx,[rsp+0x10]
   0x0000000000513da5 <+21>:    mov    rdx,rsp
   0x0000000000513da8 <+24>:    call   0x4cf430 <PyArg_ParseTuple>
   0x0000000000513dad <+29>:    xor    edx,edx
   0x0000000000513daf <+31>:    test   eax,eax
   0x0000000000513db1 <+33>:    je     0x513e5e <run_me+206>
   0x0000000000513db7 <+39>:    mov    rax,QWORD PTR [rip+0x2d4342]
   0x0000000000513dbe <+46>:    mov    esi,0x91
   0x0000000000513dc3 <+51>:    mov    edi,0x56c940
   0x0000000000513dc8 <+56>:    mov    rax,QWORD PTR [rax+0x10]
   0x0000000000513dcc <+60>:    mov    rbx,QWORD PTR [rax+0x30]
   0x0000000000513dd0 <+64>:    call   0x4dc020 <PyMarshal_ReadObjectFromString>
   0x0000000000513dd5 <+69>:    mov    rdi,rax
   0x0000000000513dd8 <+72>:    mov    rsi,rbx
   0x0000000000513ddb <+75>:    call   0x52c630 <PyFunction_New>
   0x0000000000513de0 <+80>:    xor    edi,edi
   0x0000000000513de2 <+82>:    mov    rbp,rax
   0x0000000000513de5 <+85>:    call   0x478f80 <PyTuple_New>
   0x0000000000513dea <+90>:    xor    edx,edx
   0x0000000000513dec <+92>:    mov    rdi,rbp
   0x0000000000513def <+95>:    mov    rsi,rax
   0x0000000000513df2 <+98>:    call   0x422b40 <PyObject_Call>
   0x0000000000513df7 <+103>:   mov    rsi,QWORD PTR [rsp+0x10]
   0x0000000000513dfc <+108>:   mov    rdi,QWORD PTR [rsp]
   0x0000000000513e00 <+112>:   call   0x4dc020 <PyMarshal_ReadObjectFromString>
   0x0000000000513e05 <+117>:   mov    rsi,rbx
   0x0000000000513e08 <+120>:   mov    rdi,rax
   0x0000000000513e0b <+123>:   call   0x52c630 <PyFunction_New>
   0x0000000000513e10 <+128>:   xor    edi,edi
   0x0000000000513e12 <+130>:   mov    rbp,rax
   0x0000000000513e15 <+133>:   call   0x478f80 <PyTuple_New>
   0x0000000000513e1a <+138>:   xor    edx,edx
   0x0000000000513e1c <+140>:   mov    rdi,rbp
   0x0000000000513e1f <+143>:   mov    rsi,rax
   0x0000000000513e22 <+146>:   call   0x422b40 <PyObject_Call>
   0x0000000000513e27 <+151>:   mov    esi,0x217
   0x0000000000513e2c <+156>:   mov    edi,0x56c720
   0x0000000000513e31 <+161>:   mov    rbp,rax
   0x0000000000513e34 <+164>:   call   0x4dc020 <PyMarshal_ReadObjectFromString>
   0x0000000000513e39 <+169>:   mov    rsi,rbx
   0x0000000000513e3c <+172>:   mov    rdi,rax
   0x0000000000513e3f <+175>:   call   0x52c630 <PyFunction_New>
   0x0000000000513e44 <+180>:   xor    edi,edi
   0x0000000000513e46 <+182>:   mov    rbx,rax
   0x0000000000513e49 <+185>:   call   0x478f80 <PyTuple_New>
   0x0000000000513e4e <+190>:   xor    edx,edx
   0x0000000000513e50 <+192>:   mov    rsi,rax
   0x0000000000513e53 <+195>:   mov    rdi,rbx
   0x0000000000513e56 <+198>:   call   0x422b40 <PyObject_Call>
   0x0000000000513e5b <+203>:   mov    rdx,rbp
   0x0000000000513e5e <+206>:   add    rsp,0x28
   0x0000000000513e62 <+210>:   mov    rax,rdx
   0x0000000000513e65 <+213>:   pop    rbx
   0x0000000000513e66 <+214>:   pop    rbp
   0x0000000000513e67 <+215>:   ret
End of assembler dump.
import dis
import marshal
from binascii import hexlify
permutedOpcodes = [ 0 for i in range(0x100) ]
permutedOpcodes[0x87] = dis.opmap['STORE_FAST']
permutedOpcodes[0x7c] = dis.opmap['LOAD_GLOBAL']
permutedOpcodes[0x3c] = dis.opmap['BINARY_ADD']
permutedOpcodes[0x1b] = dis.opmap['RETURN_VALUE']
permutedOpcodes[0x77] = dis.opmap['MAKE_FUNCTION']
permutedOpcodes[0x53] = dis.opmap['GET_ITER']
permutedOpcodes[0x86] = dis.opmap['CALL_FUNCTION']
permutedOpcodes[0x54] = dis.opmap['POP_TOP']
permutedOpcodes[0x8f] = dis.opmap['LOAD_FAST']
permutedOpcodes[0x59] = dis.opmap['YIELD_VALUE']
def disassembly(code):
    i = 0
    customOpcode = False
    hex_code = hexlify(code)
    while i < len(hex_code):
        opcode = int(hex_code[i:i+2], 16)
        i += 2
        #remap permuted opcodes
        if permutedOpcodes[opcode] != 0:
            opcode = permutedOpcodes[opcode]
        if opcode > 0x59:
            operand = (int(hex_code[i:i+2], 16) + (int(hex_code[i+2:i+4], 16)<<8) )
            i += 4
            if opcode == 0xa0:
                print("LOAD_CONST: %d and setCustomOpcodeOffset"%(operand))
                customOpcode = True
            elif customOpcode:
                print("LOAD_CONST: %d and unsetCustomOpcodeOffset"%(operand))
                customOpcode = False
            else:
                print("%s: %d"%(dis.opname[opcode], operand))
            if opcode == dis.opmap['POP_JUMP_IF_FALSE']:
                print("######Start of disassembling jmp target######")
                disassembly(code[operand:])
                print("######End of disassembling jmp target######")
            elif opcode == dis.opmap['JUMP_FORWARD']:
                i += operand * 2
        else:
            print("%s"%(dis.opname[opcode]))
incr = marshal.load(open("incr.9351", "rb"))
decr = marshal.load(open("decr.9357", "rb"))
print("########Disassembling incr.9351########")
code = incr.co_code
print("co_consts: %s"%repr(incr.co_consts))
print("co_varnames: %s"%repr(incr.co_varnames))
print("co_names: %s"%repr(incr.co_names))
print("co_cellvars: %s"%repr(incr.co_cellvars))
disassembly(code)
print("######Disassembling decr.9357#######")
code = decr.co_code
print("co_consts: %s"%repr(decr.co_consts))
print("co_varnames: %s"%repr(decr.co_varnames))
print("co_names: %s"%repr(decr.co_names))
print("co_cellvars: %s"%repr(decr.co_cellvars))
disassembly(code)
print("######Disassembling genexpr#######")
disassembly(decr.co_consts[4].co_code)This function apply a XOR operation on each element in the list with the value 13.
co_consts: (13, None)
%co_varnames: ('.0', '_')
co_names: ('chr',)
co_cellvars: ()
LOAD_FAST: 0
FOR_ITER: 21
STORE_FAST: 1
LOAD_GLOBAL: 0
LOAD_FAST: 1
LOAD_CONST: 0
INPLACE_XOR
CALL_FUNCTION: 1
YIELD_VALUE
POP_TOP
JUMP_ABSOLUTE: 3
LOAD_CONST: 1
RETURN_VALUE