We permutate the opcode of python2.7, and use it to encrypt the flag.. Try to recover it!
The provided pyc file could be parsed with the marshal module, yielding a code object representing a python module. Examining its co_{name,argcount,...} attributes showed that it had 3 names ('rotor', 'encrypt', 'decrypt') and four constants (-1, None, and two code objects). The module's bytecode could not be disassembled of course. Originally I thought by 'permutate' they meant 'rearrange' but later we realized it was 'substitute' instead.
co_name '<module>'
co_flags 64
co_firstlineno 1
co_argcount 0
co_nlocals 0
co_stacksize 2
co_names ('rotor', 'encrypt', 'decrypt')
co_varnames ()
co_consts (-1, None, <code object encrypt>, <code object decrypt>)
co_code 9900009901008600009100009902008800009101009903008...
The two inner code objects were functions called encrypt and decrypt. The only difference between them were the strings "encrypt" and "decrypt" and their co_firstlineno arguments. Their co_code attributes were the same too, thus we could focus only on decrypt.
co_name 'decrypt'
co_flags 67
co_firstlineno 10
co_argcount 1
co_nlocals 6
co_stacksize 3
co_names ('rotor', 'newrotor', 'decrypt')
co_varnames ('data', 'key_a', 'key_b', 'key_c', 'secret', 'rot')
co_consts (None, '!@#$%^&*', 'abcdefgh', '<>{}:"', 4, '|', 2, 'EOF')
co_code 99010068010099020068020099030068030061010099040046...
Using these attributes as reference I managed to create a module skeleton that had the same properties as the one in the pyc file:
1| import rotor
2| def encrypt(data):
3| pass
4|
5|
6|
7|
8|
9|
10| def decrypt(data):
11| pass
We compared this module's bytecode to the one in the pyc file and realized that permutation was in fact 'substitution' and that only the opcodes were replaced, the arguments -- usually the 2nd and 3rd byte in an instruction -- remained the same. We came up with this substitution table to fix some of the instructions:
SHOULD_BE = {
0x68: 0x7d, # BUILD_SET should be STORE_FAST
0x86: 0x6c, # ...
0x88: 0x84,
0x91: 0x5a,
0x99: 0x64,
}
The python compiler inserts objects into the co_names, co_varnames, and co_consts attributes in the same order as it encounters them while walking the ast of the compiled function. Using this property of the compiler, the partially fixed bytecode, and the source code of the rotor module found on the Internet we had an incomplete idea about the structure of the decrypt function:
10| def decrypt(data):
11| key_a = '!@#$%^&*'
12| key_b = 'abcdefgh'
13| key_c = 'abcdefgh'
14| secret = ?????????
15| rot = rotor.newrotor(secret)
16| return rot.decrypt(data)
At very simple hand-rolled disassembler was used to examine the bytecode of the original decrypt function. Some unknown opcodes still remained, e.g. PRINT_EXPR (only used in the interactive repl), <39> (invalid opcode), and STORE_GLOBAL. PRINT_EXPR and <39> were definitely single-byte instructions, as they had valid three-byte instructions around them. We guessed that STORE_GLOBAL was LOAD_FAST, and DELETE_ATTR was LOAD_ATTR.
As the constants used by the functions were strings or numbers it became apparent that PRINT_EXPR was BINARY_MULTIPY and that <39> was BINARY_ADD.
SHOUD_BE = {
0x68: 0x7d, # BUILD_SET should be STORE_FAST
0x86: 0x6c, # ...
0x88: 0x84,
0x91: 0x5a,
0x99: 0x64,
0x61: 0x7c, #
0x27: 23, # ADD
0x46: 20, # MULTIPLY
0x9b: 116, # LOAD_GLOBAL
0x3c: 106, # LOAD_ATTR
}
The 'secret' could be reconstructed from the fixed disassembly:
secret = key_a*4 + "|" + (key_b+key_a+key_c) * 2 + "|" + key_b*2 + "EOF"
The flag in the encrypted_flag file was: flag{Gue55_opcode_G@@@me}
The fixed disassembly of decrypt:
64 01 00 LOAD_CONST was: 0x99 = <153>
7d 01 00 STORE_FAST was: 0x68 = BUILD_SET
64 02 00 LOAD_CONST was: 0x99 = <153>
7d 02 00 STORE_FAST was: 0x68 = BUILD_SET
64 03 00 LOAD_CONST was: 0x99 = <153>
7d 03 00 STORE_FAST was: 0x68 = BUILD_SET
7c 01 00 LOAD_FAST was: 0x61 = STORE_GLOBAL
64 04 00 LOAD_CONST was: 0x99 = <153>
14 BINARY_MULTIPLY was: 0x46 = PRINT_EXPR
64 05 00 LOAD_CONST was: 0x99 = <153>
17 BINARY_ADD was: 0x27 = <39>
7c 02 00 LOAD_FAST was: 0x61 = STORE_GLOBAL
7c 01 00 LOAD_FAST was: 0x61 = STORE_GLOBAL
17 BINARY_ADD was: 0x27 = <39>
7c 03 00 LOAD_FAST was: 0x61 = STORE_GLOBAL
17 BINARY_ADD was: 0x27 = <39>
64 06 00 LOAD_CONST was: 0x99 = <153>
14 BINARY_MULTIPLY was: 0x46 = PRINT_EXPR
17 BINARY_ADD was: 0x27 = <39>
64 05 00 LOAD_CONST was: 0x99 = <153>
17 BINARY_ADD was: 0x27 = <39>
7c 02 00 LOAD_FAST was: 0x61 = STORE_GLOBAL
64 06 00 LOAD_CONST was: 0x99 = <153>
14 BINARY_MULTIPLY was: 0x46 = PRINT_EXPR
17 BINARY_ADD was: 0x27 = <39>
64 07 00 LOAD_CONST was: 0x99 = <153>
17 BINARY_ADD was: 0x27 = <39>
7d 04 00 STORE_FAST was: 0x68 = BUILD_SET
74 00 00 LOAD_GLOBAL was: 0x9b = <155>
6a 01 00 LOAD_ATTR was: 0x60 = DELETE_ATTR
7c 04 00 LOAD_FAST was: 0x61 = STORE_GLOBAL
83 01 00 CALL_FUNCTION TODO
7d 05 00 STORE_FAST was: 0x68 = BUILD_SET
7c 05 00 LOAD_FAST was: 0x61 = STORE_GLOBAL
6a 02 00 LOAD_ATTR was: 0x60 = DELETE_ATTR
7c 00 00 LOAD_FAST was: 0x61 = STORE_GLOBAL
83 01 00 CALL_FUNCTION TODO
53 RETURN_VALUE TODO