【逆向分析】BugKu 逆向题目 树木的小秘密

admin 2024年10月6日09:56:21评论22 views字数 11873阅读39分34秒阅读模式

BugKu 逆向题目 树木的小秘密

题目地址:

https://ctf.bugku.com/challenges/detail/id/355.html

【逆向分析】BugKu 逆向题目 树木的小秘密

https://ctf.bugku.com/challenges/detail/id/355.html

【逆向分析】BugKu 逆向题目 树木的小秘密

【逆向分析】BugKu 逆向题目 树木的小秘密

首先,查壳

https://github.com/horsicq/DIE-engine/releases

【逆向分析】BugKu 逆向题目 树木的小秘密

使用pyinstxtractor.py反编译easy_reverse.exe后,把得到的123文件添加尾缀.pyc,用Notepad++打开发现输入提示Please input your flag:z ZmxhZ3tteV9uYW1lX2lzX3NodW11fQ==z,使用Base64解密即可得到flag{my_name_is_shumu}

#!/usr/bin/env python3# -*- coding: utf-8 -*-
# 改编自网上的pyinstxtractor.py
r"""PyInstaller Extractor v2.1 (Supports pyinstaller 3.3+, 3.2, 3.1, 3.0, 2.1, 2.0)Author : Extreme CodersE-mail : extremecoders(at)hotmail(dot)comWeb    : https://0xec.blogspot.comDate   : 29-November-2017Url    : https://sourceforge.net/projects/pyinstallerextractor/
For any suggestions, leave a comment onhttps://forum.tuts4you.com/topic/34455-pyinstaller-extractor/
This script extracts a pyinstaller generated executable file.Pyinstaller installation is not needed. The script has it all.
For best results, it is recommended to run this script in thesame version of python as was used to create the executable.This is just to prevent unmarshalling errors(if any) whileextracting the PYZ archive.
Usage : Just copy this script to the directory where your exe resides        and run the script with the exe file name as a parameter
C:pathtoexe>python pyinstxtractor.py <filename>$ /path/to/exe/python pyinstxtractor.py <filename>
Licensed under GNU General Public License (GPL) v3.You are free to modify this source.
CHANGELOG================================================
Version 1.1 (Jan 28, 2014)-------------------------------------------------- First Release- Supports only pyinstaller 2.0
Version 1.2 (Sept 12, 2015)-------------------------------------------------- Added support for pyinstaller 2.1 and 3.0 dev- Cleaned up code- Script is now more verbose- Executable extracted within a dedicated sub-directory
(Support for pyinstaller 3.0 dev is experimental)
Version 1.3 (Dec 12, 2015)-------------------------------------------------- Added support for pyinstaller 3.0 final- Script is compatible with both python 2.x & 3.x (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG)
Version 1.4 (Jan 19, 2016)-------------------------------------------------- Fixed a bug when writing pyc files >= version 3.3 (Thanks to Daniello Alto: https://github.com/Djamana)
Version 1.5 (March 1, 2016)-------------------------------------------------- Added support for pyinstaller 3.1 (Thanks to Berwyn Hoyt for reporting)
Version 1.6 (Sept 5, 2016)-------------------------------------------------- Added support for pyinstaller 3.2- Extractor will use a random name while extracting unnamed files.- For encrypted pyz archives it will dump the contents as is. Previously, the tool would fail.
Version 1.7 (March 13, 2017)-------------------------------------------------- Made the script compatible with python 2.6 (Thanks to Ross for reporting)
Version 1.8 (April 28, 2017)-------------------------------------------------- Support for sub-directories in .pyz files (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG)
Version 1.9 (November 29, 2017)-------------------------------------------------- Added support for pyinstaller 3.3- Display the scripts which are run at entry (Thanks to Michael Gillespie @ malwarehunterteam for the feature request)
***** 版本 2.0 (2020-12-13) *****- 修复了提取pyc文件的bug。
***** 版本 2.1.1 (2021-2-23) *****- 修复了从PYZ中提取pyc文件的bug, 兼容几乎所有Python3版本; 可直接提取pyz文件。
***** 版本 2.2 (2022-7-25) *****- 兼容Python 3.10。
***** 版本 2.3 (2024-10-5) *****- 兼容Python 3.11。"""
from __future__ import print_functionimport osimport structimport marshalimport zlibimport sysimport impimport typesfrom uuid import uuid4 as uniquename
# 新加入的代码try:    from xdis.magics import magicsexcept ImportError:print("错误: 需使用pip安装xdis模块。")
__version__='2.2'
class CTOCEntry:    def __init__(self, position, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name):        self.position = position        self.cmprsdDataSize = cmprsdDataSize        self.uncmprsdDataSize = uncmprsdDataSize        self.cmprsFlag = cmprsFlag        self.typeCmprsData = typeCmprsData        self.name = name

class PyInstArchive:    PYINST20_COOKIE_SIZE = 24           # For pyinstaller 2.0    PYINST21_COOKIE_SIZE = 24 + 64      # For pyinstaller 2.1+    MAGIC = b'MEI�14�13�12�13�16'  # Magic number which identifies pyinstaller
    def __init__(self, path):        self.filePath = path

    def open(self):        try:            self.fPtr = open(self.filePath, 'rb')            self.fileSize = os.stat(self.filePath).st_size        except:            print('[*] Error: Could not open {0}'.format(self.filePath))            return False        return True

    def close(self):        try:            self.fPtr.close()        except:            pass

    def checkFile(self):        print('[*] Processing {0}'.format(self.filePath))        # Check if it is a 2.0 archive        self.fPtr.seek(self.fileSize - self.PYINST20_COOKIE_SIZE, os.SEEK_SET)        magicFromFile = self.fPtr.read(len(self.MAGIC))
        if magicFromFile == self.MAGIC:            self.pyinstVer = 20     # pyinstaller 2.0            print('[*] Pyinstaller version: 2.0')            return True
        # Check for pyinstaller 2.1+ before bailing out        self.fPtr.seek(self.fileSize - self.PYINST21_COOKIE_SIZE, os.SEEK_SET)        magicFromFile = self.fPtr.read(len(self.MAGIC))
        if magicFromFile == self.MAGIC:            print('[*] Pyinstaller version: 2.1+')            self.pyinstVer = 21     # pyinstaller 2.1+            return True
        print('[*] Error : Unsupported pyinstaller version or not a pyinstaller archive')        return False

    def getCArchiveInfo(self):        try:            if self.pyinstVer == 20:                self.fPtr.seek(self.fileSize - self.PYINST20_COOKIE_SIZE, os.SEEK_SET)
                # Read CArchive cookie                (magic, lengthofPackage, toc, tocLen, self.pyver) =                 struct.unpack('!8siiii', self.fPtr.read(self.PYINST20_COOKIE_SIZE))
            elif self.pyinstVer == 21:                self.fPtr.seek(self.fileSize - self.PYINST21_COOKIE_SIZE, os.SEEK_SET)
                # Read CArchive cookie                (magic, lengthofPackage, toc, tocLen, self.pyver, pylibname) =                 struct.unpack('!8siiii64s', self.fPtr.read(self.PYINST21_COOKIE_SIZE))
        except:            print('[*] Error : The file is not a pyinstaller archive')            return False
        print('[*] Python version: {0}'.format(self.pyver))
        # Overlay is the data appended at the end of the PE        self.overlaySize = lengthofPackage        self.overlayPos = self.fileSize - self.overlaySize        self.tableOfContentsPos = self.overlayPos + toc        self.tableOfContentsSize = tocLen
        print('[*] Length of package: {0} bytes'.format(self.overlaySize))        return True

    def parseTOC(self):        # Go to the table of contents        self.fPtr.seek(self.tableOfContentsPos, os.SEEK_SET)
        self.tocList = []        parsedLen = 0
        # Parse table of contents        while parsedLen < self.tableOfContentsSize:            (entrySize, ) = struct.unpack('!i', self.fPtr.read(4))            nameLen = struct.calcsize('!iiiiBc')
            (entryPos, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name) =             struct.unpack(                 '!iiiBc{0}s'.format(entrySize - nameLen),                 self.fPtr.read(entrySize - 4))
            name = name.decode('utf-8').rstrip('�')            if len(name) == 0:                name = str(uniquename())                print('[!] Warning: Found an unamed file in CArchive. Using random name {0}'.format(name))
            self.tocList.append(                                 CTOCEntry(                                                          self.overlayPos + entryPos,                                     cmprsdDataSize,                                                 uncmprsdDataSize,                                               cmprsFlag,                                                      typeCmprsData,                                                  name                                                        ))
            parsedLen += entrySize        print('[*] Found {0} files in CArchive'.format(len(self.tocList)))


    def extractFiles(self):        print('[*] Beginning extraction...please standby')        extractionDir = os.path.join(os.getcwd(), os.path.basename(self.filePath) + '_extracted')
        if not os.path.exists(extractionDir):            os.mkdir(extractionDir)
        os.chdir(extractionDir)                # 新加入的代码:加入pyc文件的magic部分        pyverstr=str(self.pyver)        if len(pyverstr)==2:            magic=magics["%s.%s"%(pyverstr[0],pyverstr[1:])]        else:            magic=magics["%s.%s"%(pyverstr[0],pyverstr[2:])] # 兼容Python 3.10及以上        if self.pyver>=37: # 2.2.1版改进            pycheader=magic+b'x00'*12 # 文件头        else:            pycheader=magic+b'x00'*8 # 文件头
        for entry in self.tocList:            basePath = os.path.dirname(entry.name)            if basePath != '':                # Check if path exists, create if not                if not os.path.exists(basePath):                    os.makedirs(basePath)
            self.fPtr.seek(entry.position, os.SEEK_SET)            data = self.fPtr.read(entry.cmprsdDataSize)
            if entry.cmprsFlag == 1:                data = zlib.decompress(data)                # Malware may tamper with the uncompressed size                # Comment out the assertion in such a case                assert len(data) == entry.uncmprsdDataSize # Sanity Check
            f=open(entry.name, 'wb')            if entry.typeCmprsData == b's':                print('[+] Possible entry point: {0}'.format(entry.name))                f.write(pycheader+data)                f.close()            elif entry.typeCmprsData == b'z' or entry.typeCmprsData == b'Z':                f.write(data)                f.close()                self._extractPyz(entry.name)
    # 2.1版加入的代码    def _checkPyz(self,name):        with open(name, 'rb') as f:            pyzMagic = f.read(4)            return pyzMagic == b'PYZ�' # Sanity Check
    def _extractPyz(self, name):        dirName =  name + '_extracted'        # Create a directory for the contents of the pyz        if not os.path.exists(dirName):            os.mkdir(dirName)
        with open(name, 'rb') as f:            pyzMagic = f.read(4)            assert pyzMagic == b'PYZ�' # Sanity Check
            pycHeader = f.read(4) # Python magic value
            if imp.get_magic() != pycHeader:                print('[!] Warning: The script is running in a different python version than the one used to build the executable')                print('    Run this script in Python{0} to prevent extraction errors(if any) during unmarshalling'.format(self.pyver))
            (tocPosition, ) = struct.unpack('!i', f.read(4))            f.seek(tocPosition, os.SEEK_SET)
            try:                toc = marshal.load(f)            except:                print('[!] Unmarshalling FAILED. Cannot extract {0}. Extracting remaining files.'.format(name))                return
            print('[*] Found {0} files in PYZ archive'.format(len(toc)))
            # From pyinstaller 3.1+ toc is a list of tuples            if type(toc) == list:                toc = dict(toc)
            for key in toc.keys():                (ispkg, pos, length) = toc[key]                f.seek(pos, os.SEEK_SET)
                fileName = key                try:                    # for Python > 3.3 some keys are bytes object some are str object                    fileName = key.decode('utf-8')                except:                    pass
                # Make sure destination directory exists, ensuring we keep inside dirName                destName = os.path.join(dirName, fileName.replace("..", "__"))                destDirName = os.path.dirname(destName)                if not os.path.exists(destDirName):                    os.makedirs(destDirName)
                try:                    data = f.read(length)                    data = zlib.decompress(data)                except:                    print('[!] Error: Failed to decompress {0}, probably encrypted. Extracting as is.'.format(fileName))                    open(destName + '.pyc.encrypted', 'wb').write(data)                    continue
                with open(destName + '.pyc', 'wb') as pycFile:                    pycFile.write(pycHeader)      # Write pyc magic                    pycFile.write(b'�' * 4)      # Write timestamp
                    if self.pyver>=37:                         # 2.2.1版改进                        # 原来的代码: b'�' * 4                        pycFile.write(b'�' * 8)                    elif self.pyver>=33:                        pycFile.write(b'�' * 4) # Size parameter added in Python 3.3                    pycFile.write(data)

def main():    if len(sys.argv) < 2:        print('[*] Usage: pyinstxtractor.py <filename>')
    else:        arch = PyInstArchive(sys.argv[1])        if arch.open():            if arch.checkFile():                if arch.getCArchiveInfo():                    arch.parseTOC()                    arch.extractFiles()                    arch.close()                    print('[*] Successfully extracted pyinstaller archive: {0}'.format(sys.argv[1]))                    print('')                    print('''You can now use a python decompiler on the pyc files within the extracted directory''')
                    # 加入的代码                    try:                        import uncompyle6                    except ImportError:                        print("Warning: 你可能没有安装pyc反编译器")
                    return
            # 2.1版加入的代码            elif arch._checkPyz(sys.argv[1]):                arch.pyver=100 # 默认pyver                arch._extractPyz(sys.argv[1])
            arch.close()

if __name__ == '__main__':    main()

【逆向分析】BugKu 逆向题目 树木的小秘密

uncompyle6是反编译pyc文件的一个Python库。

在Windows中,按Win+R键,输入cmd,启动命令提示符。

先输入命令回车:pip install uncompyle6

然后输入命令:python -m uncompyle6 文件名.pyc,等待一段时间后,就能看到反编译的输出结果了。

另外,使用命令python -m uncompyle6 文件名.pyc > 输出文件名.py 可以将反编译的输出结果写入特定的py文件里。

#!/usr/bin/env python3# -*- coding: utf-8 -*-
import sys,os,tracebackimport uncompyle6.bin.uncompile as uncompiler__version__='2.0.1'
def run_uncompile(filename):    flag=False # 监测sys.stderr中有无警告或错误消息    _w=sys.stderr.write    def w(*arg,**kw):        nonlocal flag        flag=True        _w(*arg,**kw)    def start_check(): # 开始监测        sys.stderr.write=w    def end_check():  # 停止监测        sys.stderr.write=_w
    tofilename=filename[:-1]    if os.path.isfile(tofilename):        result=input("文件%s已存在,要替换它吗? "%tofilename)        if not result.lower().startswith('y'):return    try:        sys.stdout=open(tofilename,"w",encoding="utf-8")        sys.argv[1]=filename        start_check()        uncompiler.main_bin()    except Exception:        end_check()        print("文件%s反编译失败,错误消息详见%s"% (filename,tofilename)              ,file=sys.stderr)        #traceback.print_exc()        traceback.print_exc(file=sys.stdout)    else:        end_check()        if not flag:            print("文件%s反编译成功"%filename,file=sys.stderr)        else:            print("文件%s反编译失败, 有警告或错误"%filename,file=sys.stderr)            print("按Enter键继续...",end='',file=sys.stderr)            input()    finally:        sys.stdout.close()
if __name__=="__main__":    try:        if len(sys.argv)>1:            files=sys.argv[1:]            sys.argv[0]=uncompiler.__file__            sys.argv[1:]=['']            for file in files:                if not file.endswith(".pyc"):                    print("警告: %s 可能不是pyc文件"%file,file=sys.stderr)                run_uncompile(file)        else:            file=input("拖曳文件到本窗口,然后按回车 (或输入文件名):n").strip('"')            sys.argv[0]=uncompiler.__file__            sys.argv.append('')            run_uncompile(file)    finally:        sys.stdout=sys.__stdout__

【逆向分析】BugKu 逆向题目 树木的小秘密

# uncompyle6 version 3.9.2# Python bytecode version base 3.7.0 (3394)# Decompiled from: Python 3.11.9 (tags/v3.11.9:de54cf5, Apr  2 2024, 10:12:12) [MSC v.1938 64 bit (AMD64)]# Embedded file name: 123.pyimport base64, time, sysa = 705778b = input("Please input your flag:")c = "ZmxhZ3tteV9uYW1lX2lzX3NodW11fQ=="if str(a) == b:    m = base64.b64decode(c)    print("this is your flag:" + str(m))    time.sleep(1)    sys.exit()else:    print("error")    time.sleep(1)    sys.exit()
# okay decompiling C:UsersMannixDownloadsCompressedeasy_reverse.exe_extracted123.pyc

【逆向分析】BugKu 逆向题目 树木的小秘密

原文始发于微信公众号(利刃信安):【逆向分析】BugKu 逆向题目 树木的小秘密

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月6日09:56:21
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【逆向分析】BugKu 逆向题目 树木的小秘密http://cn-sec.com/archives/3234930.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息