First Reverse Engineering Writeup

Solving BambooFox's How2Decompyle challenge

Posted by Asa Hess-Matsumoto on Friday, January 17, 2020

Over the New Year’s holiday I decided to have a crack at the BambooFox CTF. Usually, my solo attempts at working through a CTF yield pretty marginal results. This time, however, I was pretty pleased with being able to solve my first “Reverse Engineering” -type problem.

I should preface this by saying this was an atypical problem. Most reverse engineering within CTFs involve decompiling/debugging an executable, such as an ELF binary. These problems involve parsing through system calls and assembly language-level code. Tools such as IDA pro, radare2, Ollydbg, and Evan’s Debugger are common in evaluating an executable’s behavior. In this instance however, we were given some Python byte code; we’ll get to this in a moment.

How2Decompyle

What is the file and how to reverse it?

Downloading the file gives us a file named “decompyle”. Like many CTF challenges, we begin by just getting some general information about the file:

file decompyle

This shows us that the file is a python 2.7 byte-compiled file (file extension .pyc). This matters to us because we cannot simply open it in a text editor; the result would be a mess of characters (although, we could glean some interesting details, such as the presence of the “Bamboo{}” flag format appearing).

As I was working on the problem in a Linux OS (namely, Kali Linux), I did a google search to see if there were any tools available that could translate the Python bytecode (.pyc) back into its equivalent Python source code. In fact, there was a wonderful tool just for the purpose called uncompyle6. All I had to do after installing was adjust the filename to one that uncompyle6 was expecting (namely: adding the “.pyc” extension to “decompyle” to make it “decompyle.pyc”). Then, we run the code:

mv decompyle decompyle.pyc
uncompyle decompyle.pyc

# uncompyle6 version 3.6.1
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.16 (default, Apr  6 2019, 01:42:57)
# [GCC 8.3.0]
# Embedded file name: decompyle.py
# Compiled at: 2019-09-22 08:18:03
import string
restrictions = [
 'uudcjkllpuqngqwbujnbhobowpx_kdkp_',
 'f_negcqevyxmauuhthijbwhpjbvalnhnm',
 'dsafqqwxaqtstghrfbxzp_x_xo_kzqxck',
 'mdmqs_tfxbwisprcjutkrsogarmijtcls',
 'kvpsbdddqcyuzrgdomvnmlaymnlbegnur',
 'oykgmfa_cmroybxsgwktlzfitgagwxawu',
 'ewxbxogihhmknjcpbymdxqljvsspnvzfv',
 'izjwevjzooutelioqrbggatwkqfcuzwin',
 'xtbifb_vzsilvyjmyqsxdkrrqwyyiu_vb',
 'watartiplxa_ktzn_ouwzndcrfutffyzd',
 'rqzhdgfhdnbpmomakleqfpmxetpwpobgj',
 'qggdzxprwisr_vkkipgftuvhsizlc_pbz',
 'jerzhlnsegcaqzathfpuufwunakdtceqw',
 'lbvlyyrugffgrwo_v_zrqvqszchqrrljq',
 'aiwuuhzbszvfpidwwkl_wynlujbsbhfox',
 'vmhrizxtiegxdxsqcdoiyxkffloudwtxg',
 'tffjnabob_jbf_qiszdsemczghnjysmah',
 'zrqkppvynlkelnevngwlkhgaputhoagtt',
 'nl_oojyafwoqccbedijmigpedkdzglq_f',
 'cksy_skctjlyxktuzchvstunyvcvabomc',
 'ppcxleeguvhvhengmvac_bykhzqohjuei',
 '_clmaicjrrzhwd_fescyaejtbyefxyihy',
 'hhopvwsmjtpjiffzatyhjrev_dwnsidyo',
 'sjevtrmkkk_zjalxrxfovjsbcxjx_pskp',
 'gnynwuuqypddbsylparpcczqimimqmvdl',
 'bxitcmhnmanwuhvjxnqeoiimlegrmkjra']
capital = [
 0, 4, 9, 19, 23, 26]
flag = raw_input('Please tell me something : ').lower()
flag = flag.lower()
if len(flag) != len(restrictions[0]):
    print 'No......You are wrong orzzzzz'
    exit(0)
for f in range(len(flag)):
    for r in restrictions:
        if flag[f] not in string.lowercase + '_' or flag[f] == r[f]:
            print 'No......You are wrong orzzzzzzzzzzzz'
            exit(0)

cap_flag = ''
for f in range(len(flag)):
    if f in capital:
        cap_flag += flag[f].upper()
    else:
        cap_flag += flag[f]

print 'Yeah, you got it !\nBambooFox{' + cap_flag + '}\n'
# okay decompiling decompyle.pyc

From examining the output of uncompyle6 for decompyle, there were several things I immediately inferred:

  • The flag was 33 characters long, or rather, as long as the string “uudcjkllpuqngqwbujnbhobowpxkdkp (or restrictions[0] within the python program).
  • The program compares some input (presumably, our guess for the flag) against the many seemingly random strings found in the array restrictions. More specifically, since the flag is the same length as all of the strings contained in restrictions, each character in our guess for the flag is must be unique when compared against characters found in the same position across all of the strings. In otherwords, if the first letter in our flag guess was “a”, then the letter “a” could not be the first letter in any of the strings in the restrictions array.
  • Certain characters within our flag must be capitalized. Likewise, while we do not expect to encounter any spaces in the flag, we should expect to find underscore (_) characters.

Now, we could go about trying to brute force the password, but this would seem a waste considering what we can already infer from the these rules. Instead, I thought to try and write my own code from scratch. I wanted my program to examine all of the characters in every string from the restrictions array, position-by-position; then, after doing so, it should spit out the lone character that’s absent from the alphabet (or an underscore, if all of the letters are accounted for). Lastly (for convenience sake), we will have the program capitalize the appropriate letters, as identified in the capital array:

restrictions = [
 'uudcjkllpuqngqwbujnbhobowpx_kdkp_',
 'f_negcqevyxmauuhthijbwhpjbvalnhnm',
 'dsafqqwxaqtstghrfbxzp_x_xo_kzqxck',
 'mdmqs_tfxbwisprcjutkrsogarmijtcls',
 'kvpsbdddqcyuzrgdomvnmlaymnlbegnur',
 'oykgmfa_cmroybxsgwktlzfitgagwxawu',
 'ewxbxogihhmknjcpbymdxqljvsspnvzfv',
 'izjwevjzooutelioqrbggatwkqfcuzwin',
 'xtbifb_vzsilvyjmyqsxdkrrqwyyiu_vb',
 'watartiplxa_ktzn_ouwzndcrfutffyzd',
 'rqzhdgfhdnbpmomakleqfpmxetpwpobgj',
 'qggdzxprwisr_vkkipgftuvhsizlc_pbz',
 'jerzhlnsegcaqzathfpuufwunakdtceqw',
 'lbvlyyrugffgrwo_v_zrqvqszchqrrljq',
 'aiwuuhzbszvfpidwwkl_wynlujbsbhfox',
 'vmhrizxtiegxdxsqcdoiyxkffloudwtxg',
 'tffjnabob_jbf_qiszdsemczghnjysmah',
 'zrqkppvynlkelnevngwlkhgaputhoagtt',
 'nl_oojyafwoqccbedijmigpedkdzglq_f',
 'cksy_skctjlyxktuzchvstunyvcvabomc',
 'ppcxleeguvhvhengmvac_bykhzqohjuei',
 '_clmaicjrrzhwd_fescyaejtbyefxyihy',
 'hhopvwsmjtpjiffzatyhjrev_dwnsidyo',
 'sjevtrmkkk_zjalxrxfovjsbcxjx_pskp',
 'gnynwuuqypddbsylparpcczqimimqmvdl',
 'bxitcmhnmanwuhvjxnqeoiimlegrmkjra']

alpha = 'abcdefghijklmnopqrstuvwxyz_'
ans = ''

for spot in range(len(restrictions[0])):
  for line in restrictions:
    if line[spot] in alpha:
      alpha = alpha.replace(line[spot], '')
  ans += alpha
  alpha = 'abcdefghijklmnopqrstuvwxyz_'

capital = [
 0, 4, 9, 19, 23, 26]

lans = list(ans)
for idx in capital:
  lans[idx] = lans[idx].upper()
ans = ''.join(lans)

print(ans)

This yielded the flag’s contents:

You_Know_Decompyle_And_Do_​Reverse