Reverse Engineering

Ini semua tentang Reverse Engineering

by invalid

Pendahuluan

Tutorial ini akan membahas cara melakukan dekompilasi manual terhadap file python bytecode. Sebelum melanjutkan, ada baiknya Anda membaca artikel pada blog reversingpwn dan blog Ned Batchelder yang menjelaskan mengenai python bytecode dan akan memudahkan Anda untuk memahami m̶a̶k̶n̶a̶ ̶k̶e̶h̶i̶d̶u̶p̶a̶n̶ artikel ini.

Langkah-langkah

Tutorial ini menggunakan sebuah file python bytecode yang dapat diunduh pada repositori di github. Pada repositori tersebut terdapat file dengan nama ME.py yang pada hakikatnya merupakan format python bytecode, namun ekstensinya adalah .py. Lakukan kloning terhadap repositori tersebut menggunakan git, atau cukup unduh file ME.py.

Selanjutnya, kita akan menggunakan uncompyle6 untuk memudahkan sebagian proses dekompilasi. Lakukan instalasi menggunakan pip dengan perintah:

% sudo pip install uncompyle6

Lanjutkan dengan melakukan dekompilasi terhadap file ME.py dengan terlebih dahulu mengubah namanya menjadi ME.pyc:

% mv ME.py ME.pyc && uncompyle6 ME.pyc > ME-decompiled.py

Setelah proses dekompilasi selesai, ternyata hasilnya seperti ini:

# uncompyle6 version 3.1.3
# Python bytecode 2.7 (62211)
# Decompiled from: Python 3.4.3 (default, Aug  9 2016, 15:36:17)
# [GCC 5.3.1 20160406 (Red Hat 5.3.1-6)]
# Embedded file name: ME.pye
# Compiled at: 2018-02-25 14:25:47
import marshal
exec marshal.loads('c\x00\x00\x00 ...

Dari potongan hasil dekompilasi tersebut, bisa terlihat bahwa skrip tersebut berisi python bytecode. Kita akan melakukan disassembly terhadap python bytecode tersebut. Caranya adalah dengan menjadikan parameter yang digunakan pada fungsi marshal.loads sebagai variabel. Ganti bagian exec marshal.loads('c\x00\x00\x00 ... \x01\n\x01\x0e\x01') menjadi e = 'c\x00\x00\x00 ... \x01\n\x01\x0e\x01'. Variabel tersebut akan digunakan untuk proses disassembly, dan berikut ini adalah potongan kode lengkapnya:

#!/usr/bin/env python
import sys
import dis
import time
import types
import struct
import marshal
import binascii

def show_hex(label, h, indent):
    h = binascii.hexlify(h)
    if len(h) < 60:
        print ("%s%s %s" % (indent, label, h))
    else:
        print ("%s%s" % (indent, label))
        for i in range(0, len(h), 60):
            print ("%s   %s" % (indent, h[i:i+60]))

def show_code(code, indent=''):
    print ("%scode" % indent)
    indent += '   '
    print ("%sargcount %d" % (indent, code.co_argcount))
    print ("%snlocals %d" % (indent, code.co_nlocals))
    print ("%sstacksize %d" % (indent, code.co_stacksize))
    print ("%sflags %04x" % (indent, code.co_flags))
    show_hex("code", code.co_code, indent=indent)
    dis.disassemble(code)
    print ("%sconsts" % indent)
    for const in code.co_consts:
        if type(const) == types.CodeType:
            show_code(const, indent+'   ')
        else:
            print ("   %s%r" % (indent, const))
    print ("%snames %r" % (indent, code.co_names))
    print ("%svarnames %r" % (indent, code.co_varnames))
    print ("%sfreevars %r" % (indent, code.co_freevars))
    print ("%scellvars %r" % (indent, code.co_cellvars))
    print ("%sfilename %r" % (indent, code.co_filename))
    print ("%sname %r" % (indent, code.co_name))
    print ("%sfirstlineno %d" % (indent, code.co_firstlineno))
    show_hex("lnotab", code.co_lnotab, indent=indent)

e = 'c\x00\x00\x00 ... \x01\n\x01\x0e\x01' ##### ISI_VARIABEL_INI_DENGAN_STRING_YANG_LENGKAP #####

show_code(marshal.loads(e))

Simpan potongan kode tersebut dengan nama 1.py, lalu jalankan dengan perintah berikut (atau set executable lalu jalankan):

% python 1.py > 1.dis

Hasilnya dapat dilihat pada file 1.dis. Berikut ini adalah bagian awal disassembly tersebut:

 1            0 LOAD_CONST               0 (-1)       #
              3 LOAD_CONST               1 (None)     #
              6 IMPORT_NAME              0 (marshal)  # import marshal
              9 STORE_NAME               0 (marshal)  #

 2           12 LOAD_CONST               2 (48)       #
             15 LOAD_CONST               3 (43)       #
             18 LOAD_CONST               4 (238)      #
             21 LOAD_CONST               5 (211)      #
             ...
          38256 BUILD_LIST           12748            # ukuran list 12748
          38259 STORE_NAME               1 (d)        # d = []

 3        38262 LOAD_CONST             258 ('')       #
          38265 STORE_NAME               2 (e)        # e = ''

 4        38268 LOAD_CONST             203 (0)        # 0
          38271 LOAD_CONST             203 (0)        # 0
          38274 BUILD_TUPLE              2            # (0,0)
          38277 UNPACK_SEQUENCE          2            #
          38280 STORE_NAME               3 (i)        # i
          38283 STORE_NAME               4 (j)        # j ... i,j = (0,0)

 5        38286 LOAD_CONST             162 (83)       #
          38289 LOAD_CONST               3 (43)       #
          38292 LOAD_CONST               4 (238)      #
          38295 LOAD_CONST               5 (211)      #
          ...
          39051 BUILD_LIST             255            # ukuran list 255
          39054 STORE_NAME               5 (k)        # k = []

Perhatikan bahwa pada skrip tersebut terdapat 2 variabel berupa list yaitu list d dan k, dimana list d memiliki elemen sebanyak 12748 dan list k memiliki 255 elemen. Elemen pada list d akan di XOR dengan elemen pada list k, dan jika elemen pada list k telah habis, maka akan diulangi dari index awal. Hasil operasi XOR tersebut kemudian disimpan kedalam variabel e. Selain itu, terdapat juga variabel i dan j yang digunakan untuk menghitung jumlah perulangan.

Selanjutnya, kita akan mengambil isi dari list d dan k dari file disassembly tersebut. Berikut ini adalah isi yang dimaksud:

 2           12 LOAD_CONST               2 (48)
                                             ^---- ambil bagian ini sebanyak 12748 untuk list "d"
                                                   dan 255 untuk list "k"

Simpan list tersebut seperti ini:

d = [48,43,238,211, ... 140,17,158,190]
k = [83,43,238,211, ... 144,191,119,38]

Selanjutnya, kita akan menerjemahkan bagian enkripsi pada potongan disassembly yang terdapat pada file 1.dis:

 6        39057 SETUP_LOOP             101 (to 39161) # perulangan while sampai alamat 39161 (12)
                                                      #
 7     >> 39060 LOAD_NAME                3 (i)        # i <<------------------------------------.
          39063 LOAD_NAME                6 (len)      # len()                                   |
          39066 LOAD_NAME                1 (d)        # d                                       |
          39069 CALL_FUNCTION            1            #                                         |
          39072 COMPARE_OP               5 (>=)       # if i >= len(d):                         |
          39075 POP_JUMP_IF_FALSE    39082            # jika tidak, maka lanjut ke 39082 (8)--. |
          39078 BREAK_LOOP                            # break                                 | |
          39079 JUMP_FORWARD             0 (to 39082) # lanjut ke 39082 (8)--.                | |
                                                      #                      |                | |
 8     >> 39082 LOAD_NAME                4 (j)        # variabel j <<--------+----------------' |
          39085 LOAD_NAME                6 (len)      # len()                                   |
          39088 LOAD_NAME                5 (k)        # variabel k                              |
          39091 CALL_FUNCTION            1            #                                         |
          39094 COMPARE_OP               5 (>=)       # if j >= len(k):                         |
          39097 POP_JUMP_IF_FALSE    39109            # jika tidak, maka lanjut ke 39109 (9)--. |
          39100 LOAD_CONST             203 (0)        # 0 (konstanta bernilai 0)              | |
          39103 STORE_NAME               4 (j)        # j = 0                                 | |
          39106 JUMP_FORWARD             0 (to 39109) # lanjut ke 39109 (9)--.                | |
                                                      #                      |                | |
 9     >> 39109 LOAD_NAME                2 (e)        # variabel e <<--------+----------------' |
          39112 LOAD_NAME                7 (chr)      # chr()                                   |
          39115 LOAD_NAME                1 (d)        # variabel d                              |
          39118 LOAD_NAME                3 (i)        # variabel i                              |
          39121 BINARY_SUBSCR                         # d[i]                                    |
          39122 LOAD_NAME                5 (k)        # variabel k                              |
          39125 LOAD_NAME                4 (j)        # variabel j                              |
          39128 BINARY_SUBSCR                         # k[j]                                    |
          39129 BINARY_XOR                            # ^ (operasi XOR)                         |
          39130 CALL_FUNCTION            1            # chr(d[i] ^ k[j])                        |
          39133 INPLACE_ADD                           #                                         |
          39134 STORE_NAME               2 (e)        # e +=                                    |
                                                      #                                         |
10        39137 LOAD_NAME                3 (i)        # variabel i                              |
          39140 LOAD_CONST              63 (1)        # 1 (konstanta bernilai 1)                |
          39143 INPLACE_ADD                           # + (operasi penjumlahan)                 |
          39144 STORE_NAME               3 (i)        # i += 1                                  |
                                                      #                                         |
11        39147 LOAD_NAME                4 (j)        # j (variabel j)                          |
          39150 LOAD_CONST              63 (1)        # 1 (konstanta bernilai 1)                |
          39153 INPLACE_ADD                           # + (operasi penjumlahan)                 |
          39154 STORE_NAME               4 (j)        # j += 1                                  |
          39157 JUMP_ABSOLUTE        39060            # ulangi ke alamat 39060 (7)--------------'
          39160 POP_BLOCK                             #

12     >> 39161 LOAD_NAME                0 (marshal)  #
          39164 LOAD_ATTR                8 (loads)    #
          39167 LOAD_NAME                2 (e)        #
          39170 CALL_FUNCTION            1            # marshal.loads(e)
          39173 LOAD_CONST               1 (None)     #
          39176 DUP_TOP                               #
          39177 EXEC_STMT                             # exec(marshal.loads(e))
          39178 LOAD_CONST               1 (None)     #
          39181 RETURN_VALUE                          #

Potongan disassembly di atas bentuknya seperti ini jika diterjemahkan ke dalam kode python:

while 1:
    if i >= len(d): break
    if j >= len(k): j = 0
    e += chr(d[i] ^ k[j])
    i += 1
    j += 1

Dari informasi tersebut, kita kemudian membuat sebuah skrip dengan nama 2.py yang isinya adalah sebagai berikut:

#!/usr/bin/env python
import sys
import dis
import time
import types
import struct
import marshal
import binascii

def show_hex(label, h, indent):
    h = binascii.hexlify(h)
    if len(h) < 60:
        print ("%s%s %s" % (indent, label, h))
    else:
        print ("%s%s" % (indent, label))
        for i in range(0, len(h), 60):
            print ("%s   %s" % (indent, h[i:i+60]))

def show_code(code, indent=''):
    print ("%scode" % indent)
    indent += '   '
    print ("%sargcount %d" % (indent, code.co_argcount))
    print ("%snlocals %d" % (indent, code.co_nlocals))
    print ("%sstacksize %d" % (indent, code.co_stacksize))
    print ("%sflags %04x" % (indent, code.co_flags))
    show_hex("code", code.co_code, indent=indent)
    dis.disassemble(code)
    print ("%sconsts" % indent)
    for const in code.co_consts:
        if type(const) == types.CodeType:
            show_code(const, indent+'   ')
        else:
            print ("   %s%r" % (indent, const))
    print ("%snames %r" % (indent, code.co_names))
    print ("%svarnames %r" % (indent, code.co_varnames))
    print ("%sfreevars %r" % (indent, code.co_freevars))
    print ("%scellvars %r" % (indent, code.co_cellvars))
    print ("%sfilename %r" % (indent, code.co_filename))
    print ("%sname %r" % (indent, code.co_name))
    print ("%sfirstlineno %d" % (indent, code.co_firstlineno))
    show_hex("lnotab", code.co_lnotab, indent=indent)

d = [48,43,238,211, ... 140,17,158,190] ##### ISI_BAGIAN_INI_DENGAN_LIST_YANG_LENGKAP #####
k = [83,43,238,211, ... 144,191,119,38] ##### ISI_BAGIAN_INI_DENGAN_LIST_YANG_LENGKAP #####

e = ''
i,j = (0,0)

while 1:
    if i >= len(d): break
    if j >= len(k): j = 0
    e += chr(d[i] ^ k[j])
    i += 1
    j += 1

show_code(marshal.loads(e))

Simpan dan jalankan skrip tersebut seperti ini:

% python 2.py > 2.dis

Hasil disassembly dari skrip 2.py akan disimpan pada file 2.dis dan bisa terlihat bahwa prosesnya sama dengan yang dilakukan pada file 1.dis, hanya list d yang berbeda elemennya.

Selanjutnya, informasi yang terdapat pada file 2.dis akan kita gunakan untuk menghasilkan file dalam format python bytecode dengan menggunakan formula yang sama dengan yang dilakukan pada file 2.py. Bedanya, kali ini kita akan menuliskan outputnya pada sebuah file. Format outputnya adalah python bytecode, sehingga kita akan menulis python bytecode header sebanyak 8 byte (4 byte berupa magic dan 4 byte lagi berupa timestamp) lalu diikuti dengan isi variabel e. Berikut ini adalah potongan skrip 3.py:

#!/usr/bin/env python

d = [48,43,238,211, ... 154,196,48,231] ##### ISI_BAGIAN_INI_DENGAN_LIST_YANG_LENGKAP #####
k = [83,43,238,211, ... 144,191,119,38] ##### ISI_BAGIAN_INI_DENGAN_LIST_YANG_LENGKAP #####

e = ''
i,j = (0,0)

while 1:
    if i >= len(d): break
    if j >= len(k): j = 0
    e += chr(d[i] ^ k[j])
    i += 1
    j += 1

with open('3.pyc','wb') as f:
    f.write('\x03\xf3\x0d\x0a\xeb\x56\x92\x5a' + e)

Jalankan skrip 3.py tersebut, dan akan dihasilkan output berupa file 3.pyc:

% python 3.py

Periksa format outputnya:

% file 3.pyc
3.pyc: python 2.7 byte-compiled

Lanjutkan dengan melakukan dekompilasi menggunakan uncompyle6 seperti ini:

% uncompyle6 3.pyc > out.py

Hasil dekompilasi akan disimpan pada file out.py, dan berikut ini adalah isinya:

# uncompyle6 version 3.1.3
# Python bytecode 2.7 (62211)
# Decompiled from: Python 3.4.3 (default, Aug  9 2016, 15:36:17)
# [GCC 5.3.1 20160406 (Red Hat 5.3.1-6)]
# Embedded file name: <script>
# Compiled at: 2018-02-25 14:25:47
import marshal, platform
from py_compile import compile as com
from sys import argv as D
from sys import exit
from random import randint as R
from os import remove
from os import rename

def tampil(x):
    w = 'amhkbpc'
    for i in w:
        x = x.replace('?%s' % i, '\x1b[%d;1m' % (90 + w.index(i)))

    x += '\x1b[0m'
    x = x.replace('?0', '\x1b[0m')
    print x


if platform.python_version().split('.')[0] != '2':
    tampil('?m[!] kamu menggunakan python versi 3 silahkan menggunakan versi 2')
    exit()

def baliho():
    tampil('?k+--------------------------------+\n|           ?p-?mPIRMANSX?p-           ?k|\n|   ?hhttps://github.com/pirmansx  ?k|\n+--------------------------------+')


if len(D) < 3:
    baliho()
    tampil('?hCara menggunakan:\n\t?bpython2 ME.py [file] [mode]\n?m*?cMode:  ?h1 ?c-> ?hCepat\n\t?k2 ?c-> ?kSedang\n\t?m3 ?c-> ?mLambat\n?hContoh: ?apython2 ME.py /sdcard/file.py 2')
    exit()
try:
    s = open(D[1], 'r').read()
except:
    baliho()
    tampil('?m[!] Gagal membuka ?c' + D[1])
    exit()

if D[2] not in ('1', '2', '3'):
    baliho()
    tampil('?m[!] Mode ?c%s?m tidak ada' % D[2])
    exit()
try:
    c = compile(s, '<script>', 'exec')
except:
    baliho()
    tampil('?mFile tidak support')
    exit()

d = marshal.dumps(c)
r = lambda : R(0, 255)
k = []
for i in range(255):
    k.append(r())

def enc(d, k):
    e = ''
    i, j = (0, 0)
    while 1:
        if i >= len(d):
            break
        if j >= len(k):
            j = 0
        e += chr(ord(d[i]) ^ k[j])
        i += 1
        j += 1

    return e


def enc0(d, k):
    x = []
    K = []
    for i in d:
        x.append(str(ord(i)))

    x = '[' + (',').join(x) + ']'
    for i in k:
        K.append(str(i))

    s = 'import marshal\nd = ' + x + "\ne = ''\ni,j = 0,0\nk = [" + (',').join(K) + ']\nwhile 1:\n\tif i >= len(d):break\n\tif j >= len(k):j = 0\n\te += chr(d[i]^k[j])\n\ti += 1\n\tj += 1\nexec(marshal.loads(e))'
    c = compile(s, '<script>', 'exec')
    d = marshal.dumps(c)
    return d


for i in range(int(D[2]) + 1):
    d = enc0(enc(d, k), k)

D = D[1] + 'e'
open(D, 'w').write('import marshal;exec(marshal.loads(' + repr(d) + '))')
baliho()
try:
    com(D)
    rename(D + 'c', ('').join(D.split('.')[:1]) + 'enc.py')
    remove(D)
    tampil('?h[v] Berhasil membuat file ?c' + ('').join(D.split('.')[:1]) + 'enc.py')
except:
    tampil('?mGAGAL meyimpan ?c' + D[:-1])
# okay decompiling 3.pyc

File-file yang terdapat pada artikel ini dapat Anda unduh/kloning dari repositori saya di github.

Penutup

Sekian tutorial singkat kali ini, semoga bermanfaat. Terima kasih kepada Tuhan Yang Maha Esa, dan Anda yang sudah membaca tutorial ini.