CTF UniKL 2018 Writeup: python_dis.dis2
Written by IceM4nn on 2 May 2018
Category : Binary
Title : python_dis.dis2
Points : 200
Attachment: encrypt_decode.py
The question didn’t say anything but giving us a python 2.7 byte-compiled file. This can be identified by issuing the file
command. As the file says, it’s a compiled binary so you cannot see the sources directly.
By running the python binary, you get an output such:
$ python encrypt_decode.py
4 0 LOAD_CONST 1 ('')
3 STORE_FAST 0 (_1)
8 6 SETUP_LOOP 234 (to 243)
9 LOAD_CONST 2 (102)
12 LOAD_CONST 3 (114)
15 LOAD_CONST 4 (111)
18 LOAD_CONST 5 (109)
21 LOAD_CONST 6 (32)
24 LOAD_CONST 7 (122)
27 LOAD_CONST 8 (108)
30 LOAD_CONST 9 (105)
33 LOAD_CONST 10 (98)
36 LOAD_CONST 6 (32)
39 LOAD_CONST 9 (105)
42 LOAD_CONST 5 (109)
45 LOAD_CONST 11 (112)
48 LOAD_CONST 4 (111)
51 LOAD_CONST 3 (114)
54 LOAD_CONST 12 (116)
57 LOAD_CONST 6 (32)
60 LOAD_CONST 13 (100)
63 LOAD_CONST 14 (101)
66 LOAD_CONST 15 (99)
69 LOAD_CONST 4 (111)
72 LOAD_CONST 5 (109)
75 LOAD_CONST 11 (112)
78 LOAD_CONST 3 (114)
81 LOAD_CONST 14 (101)
84 LOAD_CONST 16 (115)
87 LOAD_CONST 16 (115)
90 LOAD_CONST 6 (32)
93 LOAD_CONST 17 (97)
96 LOAD_CONST 16 (115)
99 LOAD_CONST 6 (32)
102 LOAD_CONST 18 (121)
105 LOAD_CONST 19 (10)
108 LOAD_CONST 2 (102)
111 LOAD_CONST 3 (114)
114 LOAD_CONST 4 (111)
117 LOAD_CONST 5 (109)
120 LOAD_CONST 6 (32)
123 LOAD_CONST 10 (98)
126 LOAD_CONST 9 (105)
129 LOAD_CONST 20 (110)
132 LOAD_CONST 17 (97)
135 LOAD_CONST 16 (115)
138 LOAD_CONST 15 (99)
141 LOAD_CONST 9 (105)
144 LOAD_CONST 9 (105)
147 LOAD_CONST 6 (32)
150 LOAD_CONST 9 (105)
153 LOAD_CONST 5 (109)
156 LOAD_CONST 11 (112)
159 LOAD_CONST 4 (111)
162 LOAD_CONST 3 (114)
165 LOAD_CONST 12 (116)
168 LOAD_CONST 6 (32)
171 LOAD_CONST 21 (117)
174 LOAD_CONST 20 (110)
177 LOAD_CONST 22 (104)
180 LOAD_CONST 14 (101)
183 LOAD_CONST 23 (120)
186 LOAD_CONST 8 (108)
189 LOAD_CONST 9 (105)
192 LOAD_CONST 2 (102)
195 LOAD_CONST 18 (121)
198 LOAD_CONST 6 (32)
201 LOAD_CONST 17 (97)
204 LOAD_CONST 16 (115)
207 LOAD_CONST 6 (32)
210 LOAD_CONST 21 (117)
213 BUILD_LIST 68
216 GET_ITER
>> 217 FOR_ITER 22 (to 242)
220 STORE_FAST 1 (_5)
9 223 LOAD_FAST 0 (_1)
226 LOAD_NAME 0 (chr)
229 LOAD_FAST 1 (_5)
232 CALL_FUNCTION 1
235 INPLACE_ADD
236 STORE_FAST 0 (_1)
239 JUMP_ABSOLUTE 217
>> 242 POP_BLOCK
12 >> 243 LOAD_FAST 0 (_1)
246 LOAD_CONST 0 (None)
249 DUP_TOP
250 EXEC_STMT
13 251 LOAD_CONST 24 ('789c2dc5b10100200c02b097aa08d887f8ff043bb824b02e16c87cada066e0806e46427933ee990f0d0e0afd')
254 STORE_FAST 2 (d)
14 257 LOAD_NAME 1 (u)
260 LOAD_NAME 2 (y)
263 LOAD_NAME 1 (u)
266 LOAD_FAST 2 (d)
269 CALL_FUNCTION 1
272 CALL_FUNCTION 1
275 CALL_FUNCTION 1
278 STORE_FAST 3 (hi)
15 281 LOAD_NAME 3 (raw_input)
284 LOAD_CONST 25 ('Your input:')
287 CALL_FUNCTION 1
290 STORE_FAST 4 (yolo)
16 293 LOAD_FAST 3 (hi)
296 LOAD_FAST 4 (yolo)
299 COMPARE_OP 2 (==)
302 PRINT_ITEM
303 PRINT_NEWLINE
304 LOAD_CONST 0 (None)
307 RETURN_VALUE
Your input:test
False
At the end of the output, the python binary ask for some input. At first I though that it might want me to break the input so that it return true
or something. I try to give a format string input but nothing happen.
Your input: %s%x%x%x%s
False
I also try to give symbols characters, see if it breaks or something.
Your input: ~!@#$%&()<>?:"{}|"
False
Then I also try to giving a huge number characters to see if it crash. Seriously, a very huge char. But I didn’t pasted it all here.
Your input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...
False
All of my small attempts was failure, but I didn’t give up. Reading at the output looks like something the program hides from us. The first interesting line was the long number here:
13 251 LOAD_CONST 24 ('789c2dc5b10100200c02b097aa08d887f8ff043bb824b02e16c87cada066e0806e46427933ee990f0d0e0afd')
254 STORE_FAST 2 (d)
It might hidden something through a custom encryption in the program. This must be decrypt, it might be the flag! but how? I scroll again the output, it also gives us many LOAD_CONST
value. I think that this constant must hold some value but I still don’t know what the number is. Not until I found a line:
226 LOAD_NAME 0 (chr)
It gets me think that this might be the function chr()
in python that they use to return a string of a character whose ASCII code is the integer from the LOAD_CONST
variable. I try a few of them and it shows something from the LOAD_CONST
integer.
LOAD_CONST 2 (102) # chr(102) returns 'f'
12 LOAD_CONST 3 (114) # chr(114) returns 'r'
15 LOAD_CONST 4 (111) # chr(111) returns 'o'
18 LOAD_CONST 5 (109) # chr(109) returns 'm'
...
And my guess are correct! there must be hold some meaningful value. The variable is too much that I don’t have much time to chr()
one by one, so I made a script that can make my job easier. To do that, first I redirect the output to a file, so that I can extract the LOAD_CONST
integer value. That would make extraction easier.
$ python encrypt_decode.py > output.txt
By the output.txt
, I use awk
command to extract the pattern from the file.
$ awk -F "(" '{print $2}' output.txt | awk -F ")" '{print $1}' | tail -n+5 -
Then I simply copy the number variable to a file called chr.txt
. Then these number need to be load in a list python so that we can iterate in every integer in that list. I made another script to do that:
#!/usr/bin/python3
list = open("chr.txt").read().splitlines()
char = list(map(int, list))
for i in char:
print(chr(i), end='')
By running this script, I get the output:
$ ./script.py
from zlib import decompress as y
from binascii import unhexlify as u
Hmm.. what is this. Nothing as I expected. So think again, this might not gives a straight hint, but this might help in further digging. I stress out with the python binary I think that I really need to see the source code. I look for online disassembler and I found this! Uncomplye2 is a Python 2.5, 2.6, 2.7 byte-code decompiler that written in Python 2.7. Cool! since our binary is python 2.7 byte-compile file, this might help lead us something. So I clone the repo, install them and run the decompiler. And cool! it works so I have the source code now. It looks like this:
# Embedded file name: encrypt_decode.py
import dis
def myfunc():
_1 = ''
for _5 in [102,
114,
111,
109,
32,
122,
108,
105,
98,
32,
105,
109,
112,
111,
114,
116,
32,
100,
101,
99,
111,
109,
112,
114,
101,
115,
115,
32,
97,
115,
32,
121,
10,
102,
114,
111,
109,
32,
98,
105,
110,
97,
115,
99,
105,
105,
32,
105,
109,
112,
111,
114,
116,
32,
117,
110,
104,
101,
120,
108,
105,
102,
121,
32,
97,
115,
32,
117]:
_1 += chr(_5)
exec _1
d = '789c2dc5b10100200c02b097aa08d887f8ff043bb824b02e16c87cada066e0806e46427933ee990f0d0e0afd'
hi = u(y(u(d)))
yolo = raw_input('Your input:')
print hi == yolo
dis.dis(myfunc)
myfunc()
When I see the line below, I know that what the previous steps do. Which is importing some libraries to decrypt the d
long unknown variable.
d = '789c2dc5b10100200c02b097aa08d887f8ff043bb824b02e16c87cada066e0806e46427933ee990f0d0e0afd'
hi = u(y(u(d)))
yolo = raw_input('Your input:')
print hi == yolo
Without further a do, I write a solver script to end this thing. :) (smiles on the face)
#!/usr/bin/python3
from zlib import decompress as y
from binascii import unhexlify as u
d = '789c2dc5b10100200c02b097aa08d887f8ff043bb824b02e16c87cada066e0806e46427933ee990f0d0e0afd'
hi = u(y(u(d)))
print(hi.decode("utf-8"))
Running this script, gives us the flag cost 200 points which is 7h15_15_7o0_345y_f0r_y0u
.