Abs0lut3Pwn4g3

writeups

16 December 2019

Watevr Ctf Repyc

by X3eRo0

Challenge: REPYC [REV]

                      _      _____ ___________   _____    _______________  ___  ___ ______ 
                     | | /| / / _ /_  __/ __/ | / / _ \  / ___/_  __/ __/ |_  |/ _ <  / _ \
                     | |/ |/ / __ |/ / / _/ | |/ / , _/ / /__  / / / _/  / __// // / /\_, /
                     |__/|__/_/ |_/_/ /___/ |___/_/|_|  \___/ /_/ /_/   /____/\___/_//___/ 
                                                                                           
                                             147 Points - REV

Challenge Description:

woo thi chal sooo repyc!
file: 3nohtyp.pyc
Difficulty estimate: Easy

We are given a pyc file and running file on this pyc reveals that its a python 3.6 compiled python bytecode.

[~]$ file 3nohtyp.pyc                                                                              
3nohtyp.pyc: python 3.6 byte-compiled

so i went with the usuall approach to a python pyc challenge, where the first part is to decompile the bytecode. i used uncompyle6 [~]$ uncompyle6 3nohtyp.pyc > repyc.py and the output was very weird just look at this file

repyc.py

I never thought that reversing a python source file would be so difficult. At first i thought maybe there is python obfuscator which obfuscates variable names with unicode Chinese looking charecters and there are a lot of other obfuscators which uses other techniques similar to this one but unfortunately i didn’t find any obfuscator which does this kind of obfuscation, i thought if i found the obfuscator maybe an unobfuscator also exists so that will make my work easier but i was wrong.

Some good obfuscators which i found are:

So the next thing i did was i started replacing each chinese charecter in the file with simple names like i translated first 3 lines like this

佤 = 0     
侰 = ~佤 * ~佤
俴 = 侰 + 侰      

translated to

A = 0
B = ~A * ~A
C = B + B

and finally

A = 0
B = 1
C = 2

And later in the code instead of just using 1, 2 and 3 they use A, B and C so i replaced all the A, B and Cs with their respective values

then comes this function

def 䯂(䵦):

At this point i already guessed it was a VM so i named this function VM(byte)

next we see some more declarations which turns out to be registers and memory and stack and stuff for example

괠 = [A] * C ** (C * C) translates to reg = [0] * 16 and 궓 = [A] * 100 translates to mem = [0] * 100 and after reading the rest of the code turns out 괣 = [] was the stack

so this part looks like this now

A = 0
B = 1
C = 2

def VM(byte):
    var1 = 0
    var2 = 0
    reg  = [0] * 16 # array of registers
    mem = [0] * 100 # contains our input
    stack = []

then there is a while loop while 䵦[굴][A] != '듃': which translated to byte[var1][0] != '듃' so it was clear that ‘듃’ is the hlt instruction as soon as it encounters ‘듃’ the VM will stop execution

and finally

굸 = 䵦[굴][A].lower()
亀 = 䵦[굴][B:]

translated to

opcode = byte[var1][0].lower()
_arg_ = byte[var1][1:]

and at this point it was time to analyse all the opcodes, so i used my big brain and only analysed the ones which were actually executed. when you see the call to the VM() function the arguments passed is a list of instructions for sat down and started to do find and replace on all the instructions. but then i noticed that the instructions in the bottom were not correctly indented so i fixed that and i ended up with this

solution.py

after that i just did a lot of find and replace and i finally fixed the whole python file and i ended up with this VM Bytecode

VM([
 ['mov', 0, 'Authentication token: '],
 ['mem_mov', 0, 0], # mem(r1) = r2
 ['mov', 6, 'á×äÓâæíäàßåÉÛãåäÉÖÓÉäàÓÉÖÓåäÉÓÚÕæïèäßÙÚÉÛÓäàÙÔÉÓâæÉàÓÚÕÓÒÙæäàÉäàßåÉßåÉäàÓÉÚÓáÉ·Ôâ×ÚÕÓÔɳÚÕæïèäßÙÚÉÅä×ÚÔ׿ÔÉ×Úïá×ïåÉßÉÔÙÚäÉæÓ×ÜÜïÉà×âÓÉ×ÉÑÙÙÔÉâßÔÉÖãäÉßÉæÓ×ÜÜïÉÓÚÞÙïÉäàßåÉåÙÚÑÉßÉàÙèÓÉïÙãÉáßÜÜÉÓÚÞÙïÉßäÉ×åáÓÜÜ\x97ÉïÙãäãÖÓ\x9aÕÙÛ\x99á×äÕà©â«³£ï²ÕÔÈ·±â¨ë'],
 ['mov', 2, 120],
 ['mov', 4, 15],
 ['mov', 3, 1],
 ['multiply', 2, 2, 3], # r2 = r2 * r3
 ['add', 2, 2, 4], # r2 = r2 + r4
 ['nop', 0, 2], # maybe nop
 ['zero_reg', 3], # sets r3 = 0
 ['xor_string', 6, 3],
 ['mov', 0, 'Thanks.'],
 ['mov', 1, 'Authorizing access...'],
 ['print_reg', 0], # prints r0
 ['reg2mem', 0, 0], # set r[arg0] = mem[arg1]
 ['xor_string', 0, 2],
 ['subtract_string', 0, 4],
 ['mov', 5, 19],
 ['strcmp', 0, 6, 5], # check strings ; if not equal r[7] = 1 ; push r[arg[2]]
 ['print_reg', 1],
 ['mov', 1, 'Access denied!'],
 ['print_reg', 1],
 ['hlt']])

clearly it loads this huge string into r6 and then xors it with 135 and then subtract 15 from each charecter and then compares with our input so i quicly wrote a decrypt function

def find_flag():

    b = 'á×äÓâæíäàßåÉÛãåäÉÖÓÉäàÓÉÖÓåäÉÓÚÕæïèäßÙÚÉÛÓäàÙÔÉÓâæÉàÓÚÕÓÒÙæäàÉäàßåÉßåÉäàÓÉÚÓáÉ·Ôâ×ÚÕÓÔɳÚÕæïèäßÙÚÉÅä×ÚÔ׿ÔÉ×Úïá×ïåÉßÉÔÙÚäÉæÓ×ÜÜïÉà×âÓÉ×ÉÑÙÙÔÉâßÔÉÖãäÉßÉæÓ×ÜÜïÉÓÚÞÙïÉäàßåÉåÙÚÑÉßÉàÙèÓÉïÙãÉáßÜÜÉÓÚÞÙïÉßäÉ×åáÓÜÜ\x97ÉïÙãäãÖÓ\x9aÕÙÛ\x99á×äÕà©â«³£ï²ÕÔÈ·±â¨ë'
    temp = ''
    for i in range(len(b)):
        temp += chr(ord(b[i]) + 15)

    a = temp
    temp = ""
    for i in range(len(a)):
        temp += chr(ord(a[i]) ^ 135)

    print(temp)

and just after that i got the flag

[~]$ python3 solution.py                                                                           
watevr{this_must_be_the_best_encryption_method_evr_henceforth_this_is_the_new_Advanced_Encryption_Standard_anyways_i_dont_really_have_a_good_vid_but_i_really_enjoy_this_song_i_hope_you_will_enjoy_it_aswell!_youtube.com/watch?v=E5yFcdPAGv0}

damn that flag is huge and i also wonder whats up with these youtube links?

Thanks for reading that. ~by X3eRo0

tags: