Challenge Info
I have a friend that enjoys coding and he hasn’t stopped talking about a snake recently He left this file on my computer and dares me to uncover a secret phrase from it. Can you assist?
The code for your convenience:
1 |
|
Basic understanding
Right away, we should notice the challenge is to reverse engineer an XOR key. Not only is XOR explicitly mentioned towards the bottom of the bytecode dump, but the bitwise operations between the two data sets, input_list
and key_list
(which we will dive into) gives it away.
Defining constants, loading values into input_list
1 | 1 0 LOAD_CONST 0 (4) |
LOAD_CONST
is called to loads several constant values onto the stack.- 4, 54, 41, etc. are being loaded.
BUILD_LIST
is called, it combines the constants that were loaded into a single list. The number 40 inBUILD_LIST 40
tells the (Python) interpret that the list will contain 40 values.STORE_NAME
assigns the list to a variable named “input_list”
This may seem complicated, but the Python code for this looks something like:
1 | input_list = [4, 54, 41, 0, 112, 32, 25, 49, 33, 3, 0, 0, 57, 32, ...] |
Initializing key_str
1 | 84 LOAD_CONST 31 ('J') |
As we can see, many values are loaded onto a “key” variable
. If we read it in order, it might seem as though the key is J_o3t
. This is not the case.
Read carefully, as this was designed to confuse you. The order in which the constant is loaded on the stack determines whether it’s added to the front or the back of the string.
{: .prompt-info}
LOAD_CONST ('J')
: This loads a constant “J” onto the stack.STORE_NAME (key_str)
: The value at the top of the stack (“J”, in this instance) is popped off the stack and assigned to the variablekey_str
.key_str = "J"
.
LOAD_CONST ('_')
: This loads a constant “_“ onto the stack.LOAD_NAME (key_str)
: This loadskey_str
, which is currently just “J”, as previously explained.BINARY_ADD
: Combines both_
andkey_str
(again, currently just “J”).STORE_NAME (key_str)
: Stores these changes tokey_str
.key_str = "J"
–>key_str = "_J"
.
LOAD_NAME (key_str)
: This loadskey_str
.LOAD_CONST ('o')
: This loads a constant “o” onto the stack.BINARY_ADD
: Combines both “O” andkey-str
, effectively adding “” tokey_str
.STORE_NAME (key_str)
: Stores these changes tokey_str
.key_str = "_J"
–>key_str = "_Jo"
.
LOAD_NAME (key_str)
This loadskey_str
.LOAD_CONST ('3')
: This loads a constant “3” onto the stack.BINARY_ADD
: This combine “3” andkey_str
.STORE_NAME (key_str)
: Stores this tokey-str
.key_str = "_Jo"
–>key_str = "_Jo3"
.
LOAD_CONST ('t')
: This loads a constant “t” onto the stack.LOAD_NAME (key_str)
: This loadskey_str
.BINARY_ADD
: This combine “t” andkey-str
.STORE_NAME (key_str)
.key-str = "_Jo3"
–>key_str = "t_Jo3"
.
Generating key_list
1 | 120 LOAD_CONST 36 (<code object <listcomp> at 0x7f704e8a4d40, file "snake.py", line 9>) >) |
120 LOAD_C NST 36(<code object <listcomp> at ..., file "snake.py", line 9>) >)
: This loads a constant at index 36 onto the stack, specifically, a code object for a list comprehension.- Essentially, a piece of code that will create a list. This code object will be used to generate the list later.
122 LOAD_CONST 37 ('<listcomp>')
: This loads a constant at index 37 onto the stack. The constant in this case is the string “<listcomp>”. This will be used to identify this code object as a list comprehension once it’s actually executed.124 MAKE_FUNCTION 0
: This is the simplest one, it simply creates a function from the code object and the string “<listcomp>” (which will be used for debugging and identification). When this new function object is called, it’ll execute the list comprehension code.126 LOAD_NAME 1 (key_str)
: Loads thekey_str
variable that was explained in the previous section128 GET_ITER
: Takes the value ofkey_str
, which was just loaded, and gets an iterator over it. Assuming thatkey_str
is a string, it’ll iterate over all its characters.130 CALL_FUNCTION 1
: Calls the function was made in step 124, it passes the iterator fromkey_str
as an argument to the list comprehension. The function runs the list comprehension, which will process the characters fromkey_str
.132 STORE_NAME 2 (key_list)
: The result of the list that was just made is stored in a variable namedkey_list
. This is simply the final output of the list comprehension.
In python, this might look something like this:
1 | key_list = [ord(item) for item in key] |
This is a simple list comprehension, where each elemtn in the list is an item/character from key_str
.
Extending key_list
if it’s shorter than input_list
1 | 134 LOAD_NAME 3 (len) |
The first half (before the line break):
- Check lengths:
len(key_list)
is compared tolen(input_list)
.- If
key_list
is shorter, then it is extended. - Otherwise, it just skips to the next part of the code.
The second half (after the line break)
- Extend
key_list
:key_list.extend(key_list)
duplicateskey_list
by adding its contents to itself.- After extending, the program jumps back (
JUMP_ABSOLUTE 134
) to recheck the lengths. - The program will continue to loop until
len(key_list)
is >=len_(input_list)
.
An example of how this might work:
- Initial Values:
1 | key_list = [1, 2, 3] |
- After extending once:
1 | key_list = [1, 2, 3, 1, 2, 3] |
XOR Operation between input_list
and key_list
1 | 162 LOAD_CONST 38 (<code object <listcomp> at ...>) |
Offsets
162 - 166
:- A code object for a list comprehension is loaded onto the stack.
- A name label for the list comprehension (
<listcomp>
) is loaded, which is used solely for debugging purposes, like I mentioned previously. - The compiled code and its label are combined into a callable function that will execute the list comprehension once it’s called.
- This list comprehension would look something like this:
1
result = [a ^ b for a, b in zip(input_list, key_list)]
Offsets
168
:- The built-in
zip
function is loaded onto the stack. - This function will combine
input_list
andkey_list
into pairs.
- The built-in
Offsets
170 - 172
:- The variables
input_list
andkey_list
are loaded onto the stack. - Example:
input_list = [4, 54, 41]
,key_list = [116, 95, 74]
- The variables
Note that these aren’t the actual values, I’m just using them as an example.
{: .prompt-info }
- Offsets
174 - 180
:- The
zip
function is called with 2 arguments:input_list
andkey_list
. The result is an iterator of pairs like:[(4, 116), (54, 95), (41, 74)]
. CALL_FUNCTION (1)
: the function created for the list comprehension is called with the iterator of pairs.STORE_NAME (result)
: The list that was just created from the list comprehension is stored in the variableresult
.
- The
So, the list comprehension should:
- Iterate through each pair (a, b) from the zipped object.
- Compute the XOR (a^b).
- Add these results to the new list called
result
Converting result into text
1 | 182 LOAD_CONST 39 ('') |
result
(which is a list of integers) is converted into characters usingchr()
.- These characters are then joined into a single string using
''.join()
. - The final output is stored into
result_text
(our flag!).
Solution
1 | input_list = [] |
Note: The
with
statement before thekey
is to automatically find the values ofinput_list
. This isn’t necessary of course, it’s just to automate this further.
{: .prompt-info }
flag: picoCTF{N0t_sO_coNfus1ng_sn@ke_516dfaee}