Skip to content

标题: MISC系列(41)--反编译py2exe生成的EXE文件

创建: 2015-04-30 14:28 更新: 2015-04-30 17:28 链接: https://scz.617.cn/python/201504301428.txt


目录:

☆ 背景介绍
☆ unpy2exe.py + uncompyle2
☆ Py2ExeDumper.exe + GetPyc.py + EasyPythonDecompiler.exe
☆ 一些失败的工具
☆ rom0scan.py(反编译结果展示)
☆ 反编译效果点评
☆ 多余的话
☆ 参考资源

☆ 背景介绍

参看:

《DNS系列(9)--GFW对DNS的干挠》(DNS_2.txt)

其中对比测试了四种Python转EXE的工具。

参看:

rom0scan(20150422).7z http://pan.baidu.com/s/1eQs51YE (解压密码rom0scan.exe)

GetHttpsInfo(20150422).7z http://pan.baidu.com/s/1c0ewyHU (解压密码GetHttpsInfo.exe)

《程序员的片段(2)--rom0scan.exe出台记》 https://scz.617.cn/python/201504171139.txt

在上文中我提到:


rom0scan.exe是rom0scan.py的py2exe版本。我是Windows的忠实用户,喜欢并且相信 一大票人同样喜欢绿色Windows版本。写这个工具是为了研究其他漏洞,它已经覆盖 了我的原始需求。就像superdns.exe、GetHttpsInfo.exe一样,这些小工具我自己就 在用。为了从繁重的体力劳动中解放出来,程序员就会给自己开发一些辅助工具,要 不怎么说懒惰是世界进步、人类进化的原动力之一呢。

我分享这些exe,目标人群从来都不是会自己编译源代码的群体,而是各路小白用户 或者绿色Windows版爱好者。对于会自己编译源代码的群体,你们就自力更生去吧, 广阔天地等着你们。


在GetHttpsInfo.exe的使用说明中我提到:


如被VirusTotal命中,并担心自己的小电影被我弄走的,请勿使用,弄走活该。

未上混淆手段或者反编译手段,纯属"爱心泛滥"。我就是那种为非专业人士以及小白 们考虑的泛爱人士啊,极大程度考虑到能用上的人就图个清净、省事。设若还得安装 别的依赖,那得多么不符合泛爱人士的美学。


好了,大致背景就是,我在百度网盘上以EXE形式分享了一些实用小工具。这些EXE其 实都是Python写的,为了小白们方便,更为了自己方便,我用py2exe处理后分享出来。 因为我的初衷不是保密实现,所以未上混淆手段或者反编译手段。对于内行人(有个 行字,不然成内人,那就不太妙了),这些EXE就是Python源代码。前段时间有朋友在 新浪微博上问我能否提供源代码,我笑而不语,注意这句话:


对于会自己编译源代码的群体,你们就自力更生去吧。

值此五一国际劳动节来临之际,我这种劳动模范就再做一次好人,告诉那些不明觉厉 的围观群众,什么叫"这些EXE就是Python源代码"。

请大家围观后,有钱的捧个钱场,没钱的捧个人场,顺便求第一会所邀请码一枚。

下面以rom0scan.exe为例,介绍如何反编译py2exe生成的EXE文件,得到源代码。

☆ unpy2exe.py + uncompyle2

$ unpy2exe.py -o rom0scan rom0scan.exe

这将生成"rom0scan\rom0scan.py.pyc"。

/uncompyle2-master$ setup.py install /uncompyle2-master$ C:\Python27\python.exe scripts\uncompyle2 -h

Usage: uncompyle2 [OPTIONS]... [ FILE | DIR]...

Examples: uncompyle2 foo.pyc bar.pyc # decompile foo.pyc, bar.pyc to stdout uncompyle2 -o . foo.pyc bar.pyc # decompile to ./foo.pyc_dis and ./bar.pyc_dis uncompyle2 -o /tmp /usr/lib/python1.5 # decompile whole library

Options: -o output decompiled files to this path: if multiple input files are decompiled, the common prefix is stripped from these names and the remainder appended to uncompyle -o /tmp bla/fasel.pyc bla/foo.pyc -> /tmp/fasel.pyc_dis, /tmp/foo.pyc_dis uncompyle -o /tmp bla/fasel.pyc bar/foo.pyc -> /tmp/bla/fasel.pyc_dis, /tmp/bar/foo.pyc_dis uncompyle -o /tmp /usr/lib/python1.5 -> /tmp/smtplib.pyc_dis ... /tmp/lib-tk/FixTk.pyc_dis -c attempts a disassembly after compiling -d do not print timestamps -p use number of processes -r recurse directories looking for .pyc and .pyo files --verify compare generated source with input byte-code (requires -o) --help show this message

Debugging Options: --showasm -a include byte-code (disables --verify) --showast -t include AST (abstract syntax tree) (disables --verify)

Extensions of generated files: '.pyc_dis' '.pyo_dis' successfully decompiled (and verified if --verify) + '_unverified' successfully decompile but --verify failed + '_failed' decompile failed (contact author for enhancement)

/uncompyle2-master$ C:\Python27\python.exe scripts\uncompyle2 -o \rom0scan \rom0scan\rom0scan.py.pyc

这将生成"rom0scan\rom0scan.py.pyc_dis",也就是原始的rom0scan.py。

☆ Py2ExeDumper.exe + GetPyc.py + EasyPythonDecompiler.exe

$ Py2ExeDumper.exe rom0scan.exe

这将在当前目录下生成:

library.zip PYTHONSCRIPT

注意,PYTHONSCRIPT是一个二进制文件,还不是.pyc文件,更不是.py文件。它有个 不定长的首部,一般占0x11字节。建议用WinHex打开PYTHONSCRIPT看看,加强感性认 识。

编辑GetPyc.py如下:


!/usr/bin/env python

-- encoding: utf-8 --

Note that you have to run the script in the same version of python which

was used to generate the exe. Otherwise unmarshalling will fail.

import marshal, imp

f = open( 'PYTHONSCRIPT', 'rb' )

struct Header

{

unsigned int tag;

unsigned int optimize;

unsigned int unbuffered;

unsigned int data_bytes;

unsigned char zippath[VARIABLE_SIZE]

};

Skip the header, you have to know the header size beforehand.

f.seek( 0x11 ) ob = marshal.load( f )

for i in xrange( 0, len( ob ) ) : open( str( i ) + '.pyc', 'wb' ).write( imp.get_magic() + '\0' * 4 + marshal.dumps( ob[i] ) )

f.close()

$ GetPyc.py

这将在当前目录下生成:

0.pyc 1.pyc 2.pyc

根据大小就可以简单判断2.pyc(最大)对应原始的rom0scan.pyc。

双击执行EasyPythonDecompiler.exe([2]),这是一个GUI程序,选择反编译2.pyc, 在2.pyc所在目录下得到2.pyc_dis,这就是原始的rom0scan.py。

☆ 一些失败的工具

py2exe-extract([3])本意是从.exe中析取资源(PYTHONSCRIPT),然后从中析取.pyc。 但其实现不够鲁棒,看看源代码理解原理就好。

pyREtic([4])是原来在Immunity工作过的一哥们写的,还在BlackHat 2010上做过介 绍。参看"docs\HOWTO.md",理论上它应该可以从.pyc还原出.py。

$ REpdb.py

(REpdb:default) set_project rom0scan (REpdb:rom0scan) gen_ref (REpdb:rom0scan) gen_obf C:/Python27/Lib/site-packages (REpdb:rom0scan) remap (REpdb:rom0scan) swap_opcodes (REpdb:rom0scan) quit

$ REpdb.py

(REpdb:default) set_project rom0scan (REpdb:rom0scan) fs_um_decompile \rom0scan.pyc

正常情况下应该生成:

\Projects\rom0scan\sourcecode\fs_um\singlefile\rom0scan.py

不过它好像很久没有更新,反正我这儿没用成功。

☆ rom0scan.py(反编译结果展示)


Embedded file name: rom0scan.py

import sys, string, getopt, os, struct, socket, random, inspect, re import pycurl, Queue, itertools, collections from threading import Thread, RLock from cStringIO import StringIO

class MyException(Exception):

def __init__(self, value = None):
    self.value = value

def __str__(self):
    return str(self.value)

def __repr__(self):
    return repr(self.value)

def itos32(num): return struct.pack('=I', int(num & 4294967295L))

def uint16(num): return int(struct.unpack('=H', struct.pack('=H', int(num & 65535)))[0])

def uint32(num): return int(struct.unpack('=I', struct.pack('=I', int(num & 4294967295L)))[0])

def readb16(buf, index): if index < 0: index += len(buf) return int(struct.unpack('>H', buf[index:index + 2])[0])

def readstr(buf, index): if index < 0: index += len(buf) return buf[index:].split('\x00')[0]

def resolvehost(host): try: ret = uint32(int(struct.unpack('>I', socket.inet_aton(socket.gethostbyname(host)))[0])) except: ret = 0

return ret

def dosomething(str): str += '\x00' xxx = '' i = 0 while i < len(str) - 1: if '\' != str[i]: xxx += str[i] elif i + 1 >= len(str): xxx += str[i] elif '\' == str[i + 1]: xxx += '\' i += 1 elif 'r' == str[i + 1]: xxx += '\r' i += 1 elif 'n' == str[i + 1]: xxx += '\n' i += 1 elif 't' == str[i + 1]: xxx += '\t' i += 1 elif '0' == str[i + 1]: xxx += '\x00' i += 1 elif 'x' == str[i + 1]: if str[i + 2] >= '0' and str[i + 2] <= '9' or str[i + 2] >= 'a' and str[i + 2] <= 'f' or str[i + 2] >= 'A' and str[i + 2] <= 'F': i += 2 tmp = str[i] if str[i + 1] >= '0' and str[i + 1] <= '9' or str[i + 1] >= 'a' and str[i + 1] <= 'f' or str[i + 1] >= 'A' and str[i + 1] <= 'F': i += 1 tmp += str[i] else: tmp = '0' + tmp xxx += tmp.decode('hex_codec') else: xxx += str[i] else: xxx += str[i] i += 1

return xxx

def AnyToSth(src, dst = 'utf_8'): codelist = ['utf_8', 'gb2312', 'gbk', 'gb18030', 'big5', 'latin_1'] ret = '(error)' for c in codelist: try: ret = src.decode(c).encode(dst) break except UnicodeDecodeError: pass except UnicodeEncodeError: pass

return ret

class BitReader():

def __init__(self, bytes):
    self.__bits__ = collections.deque()
    for byte in bytes:
        byte = ord(byte)
        for i in xrange(8):
            self.__bits__.append(byte >> 7 - i & 1)

def getBit(self):
    return self.__bits__.popleft()

def getBits(self, num):
    ret = 0
    for i in xrange(num):
        ret += self.getBit() << num - 1 - i

    return ret

def getLength(self):
    length = 2
    while True:
        i = self.getBits(2)
        length += i
        if not (3 == i and length < 8):
            break

    if 8 == length:
        while True:
            i = self.getBits(4)
            length += i
            if 15 != i:
                break

    return length

def LZSDecompress(data): reader = BitReader(data) result = '' while True: bit = reader.getBit() if not bit: byte = reader.getBits(8) result += chr(byte) continue bit = reader.getBit() if 1 == bit: offset = reader.getBits(7) else: offset = reader.getBits(11) if 1 == bit and 0 == offset: break if offset > len(result): break length = reader.getLength() for i in xrange(length): result += result[-offset]

return result

QueueInWorkerExit = False

class QueueInWorker(Thread):

def __init__(self, queue_in, queue_out, httpheader = [], proxypool = [], noproxy = 'localhost,127.0.0.1', referer = '', followlocation = 1, maxredirs = 5, redir_protocols = 3, protocols = 3, connecttimeout = 30, timeout = 30, connect_max = 16, monitor = False, port = None):
    super(QueueInWorker, self).__init__()
    self.queue_in = queue_in
    self.queue_out = queue_out
    self.httpheader = httpheader
    self.proxypool = proxypool
    self.noproxy = noproxy
    self.referer = referer
    self.followlocation = followlocation
    self.maxredirs = maxredirs
    self.redir_protocols = redir_protocols
    self.protocols = protocols
    self.connecttimeout = connecttimeout
    self.timeout = timeout
    self.connect_max = connect_max
    self.monitor = monitor
    self.port = port
    self.m = pycurl.CurlMulti()
    self.m.cpool = []
    self.setDaemon(True)

def __del__(self):
    if self.m is not None:
        for c in self.m.cpool:
            if c.host is not None:
                c.host = None
            if c.head is not None:
                c.head.close()
                c.head = None
            if c.body is not None:
                c.body.close()
                c.body = None
            self.m.remove_handle(c)
            self.m.cpool.remove(c)
            c.close()

        self.m.cpool = []
        self.m.close()
        self.m = None
    return

def c_init(self, c):
    c.setopt(pycurl.HTTPHEADER, self.httpheader)
    if len(self.proxypool) > 0:
        c.setopt(pycurl.PROXY, random.choice(self.proxypool))
        c.setopt(pycurl.NOPROXY, self.noproxy)
    if self.referer is None:
        c.setopt(pycurl.AUTOREFERER, 0)
    elif '' == self.referer:
        c.setopt(pycurl.AUTOREFERER, 1)
    else:
        c.setopt(pycurl.REFERER, self.referer)
    c.setopt(pycurl.FOLLOWLOCATION, self.followlocation)
    c.setopt(pycurl.MAXREDIRS, self.maxredirs)
    c.setopt(pycurl.REDIR_PROTOCOLS, self.redir_protocols)
    c.setopt(pycurl.PROTOCOLS, self.protocols)
    c.setopt(pycurl.NOSIGNAL, 1)
    c.setopt(pycurl.CONNECTTIMEOUT, self.connecttimeout)
    c.setopt(pycurl.TIMEOUT, self.timeout)
    c.setopt(pycurl.ENCODING, '')
    if self.port is not None:
        c.setopt(pycurl.PORT, self.port)
    return

def run(self):
    global QueueInWorkerExit
    random.seed()
    while True:
        num = len(self.m.cpool)
        if not QueueInWorkerExit and num < self.connect_max:
            num = self.connect_max - num
            for i in xrange(num):
                try:
                    host = self.queue_in.get(True, 1)
                    if host is None:
                        QueueInWorkerExit = True
                        break
                    else:
                        if self.monitor:
                            sys.stderr.write('%40s\r%s\r' % (' ', host))
                        c = pycurl.Curl()
                        self.c_init(c)
                        c.host = host
                        c.setopt(pycurl.URL, 'http://%s/rom-0' % host)
                        c.head = StringIO()
                        c.setopt(pycurl.HEADERFUNCTION, c.head.write)
                        c.body = StringIO()
                        c.setopt(pycurl.WRITEFUNCTION, c.body.write)
                        self.m.cpool.append(c)
                        self.m.add_handle(c)
                except Queue.Empty:
                    break

        num = len(self.m.cpool)
        if 0 == num:
            if QueueInWorkerExit:
                self.queue_out.put(None)
                break
            else:
                continue
        while True:
            ret, num_ret = self.m.perform()
            if pycurl.E_CALL_MULTI_PERFORM != ret:
                break

        while True:
            num_ret, ok_list, error_list = self.m.info_read()
            for c in ok_list:
                head = AnyToSth(c.head.getvalue())
                if '(error)' == head:
                    server = '(error)'
                else:
                    try:
                        server = re.search('Server: ([^\\r\\n]+)', head, re.U).group(1)
                        server = AnyToSth(server, 'gbk')
                    except AttributeError:
                        server = None

                code = c.getinfo(pycurl.HTTP_CODE)
                password = None
                if 200 == code:
                    buf = c.body.getvalue()
                    end = len(buf)
                    index = 8192
                    while index + 20 <= end:
                        name = readstr(buf, index + 6)
                        if 'autoexec.net' == name:
                            offset = readb16(buf, index + 4) + 12 + 4
                            size = readb16(buf, index + 2) - 12 - 4
                            if 8192 + offset > end or 8192 + offset + size > end:
                                break
                            data = LZSDecompress(buf[8192 + offset:8192 + offset + size])
                            if len(data) > 20:
                                password = readstr(data, 20)
                            break
                        index += 20

                self.queue_out.put((c.host,
                 code,
                 server,
                 password))
                c.host = None
                c.head.close()
                c.head = None
                c.body.close()
                c.body = None
                self.m.remove_handle(c)
                self.m.cpool.remove(c)
                c.close()

            for c, errno, errmsg in error_list:
                c.host = None
                c.head.close()
                c.head = None
                c.body.close()
                c.body = None
                self.m.remove_handle(c)
                self.m.cpool.remove(c)
                c.close()

            if 0 == num_ret:
                break

        self.m.select(1.0)

    return

QueueTmpWorkerExit = False QueueTmpNoneNum = 0 QueueTmpRLock = RLock()

class QueueTmpWorker(Thread):

def __init__(self, queue_in, queue_out, num_fetch, httpheader = [], proxypool = [], noproxy = 'localhost,127.0.0.1', referer = '', followlocation = 1, maxredirs = 5, redir_protocols = 3, protocols = 3, connecttimeout = 30, timeout = 30, connect_max = 16, port = None):
    super(QueueTmpWorker, self).__init__()
    self.queue_in = queue_in
    self.queue_out = queue_out
    self.num_fetch = num_fetch
    self.httpheader = httpheader
    self.proxypool = proxypool
    self.noproxy = noproxy
    self.referer = referer
    self.followlocation = followlocation
    self.maxredirs = maxredirs
    self.redir_protocols = redir_protocols
    self.protocols = protocols
    self.connecttimeout = connecttimeout
    self.timeout = timeout
    self.connect_max = connect_max
    self.port = port
    self.m = pycurl.CurlMulti()
    self.m.cpool = []
    self.setDaemon(True)

def __del__(self):
    if self.m is not None:
        for c in self.m.cpool:
            if c.data is not None:
                c.data = None
            if c.head is not None:
                c.head.close()
                c.head = None
            if c.body is not None:
                c.body.close()
                c.body = None
            self.m.remove_handle(c)
            self.m.cpool.remove(c)
            c.close()

        self.m.cpool = []
        self.m.close()
        self.m = None
    return

def c_init(self, c):
    c.setopt(pycurl.HTTPHEADER, self.httpheader)
    if len(self.proxypool) > 0:
        c.setopt(pycurl.PROXY, random.choice(self.proxypool))
        c.setopt(pycurl.NOPROXY, self.noproxy)
    if self.referer is None:
        c.setopt(pycurl.AUTOREFERER, 0)
    elif '' == self.referer:
        c.setopt(pycurl.AUTOREFERER, 1)
    else:
        c.setopt(pycurl.REFERER, self.referer)
    c.setopt(pycurl.FOLLOWLOCATION, self.followlocation)
    c.setopt(pycurl.MAXREDIRS, self.maxredirs)
    c.setopt(pycurl.REDIR_PROTOCOLS, self.redir_protocols)
    c.setopt(pycurl.PROTOCOLS, self.protocols)
    c.setopt(pycurl.NOSIGNAL, 1)
    c.setopt(pycurl.CONNECTTIMEOUT, self.connecttimeout)
    c.setopt(pycurl.TIMEOUT, self.timeout)
    c.setopt(pycurl.ENCODING, '')
    if self.port is not None:
        c.setopt(pycurl.PORT, self.port)
    return

def run(self):
    global QueueTmpWorkerExit
    global QueueTmpNoneNum
    random.seed()
    while True:
        num = len(self.m.cpool)
        if not QueueTmpWorkerExit and num < self.connect_max:
            num = self.connect_max - num
            for i in xrange(num):
                try:
                    data = self.queue_in.get(True, 1)
                    if data is None:
                        QueueTmpRLock.acquire()
                        QueueTmpNoneNum += 1
                        QueueTmpRLock.release()
                        if self.num_fetch == QueueTmpNoneNum:
                            QueueTmpWorkerExit = True
                            break
                    else:
                        c = pycurl.Curl()
                        self.c_init(c)
                        c.data = data
                        c.setopt(pycurl.URL, 'http://%s/' % data[0])
                        c.head = StringIO()
                        c.setopt(pycurl.HEADERFUNCTION, c.head.write)
                        c.body = StringIO()
                        c.setopt(pycurl.WRITEFUNCTION, c.body.write)
                        c.setopt(pycurl.CUSTOMREQUEST, 'HEAD')
                        c.setopt(pycurl.NOBODY, True)
                        self.m.cpool.append(c)
                        self.m.add_handle(c)
                except Queue.Empty:
                    break

        num = len(self.m.cpool)
        if 0 == num:
            if QueueTmpWorkerExit:
                self.queue_out.put(None)
                break
            else:
                continue
        while True:
            ret, num_ret = self.m.perform()
            if pycurl.E_CALL_MULTI_PERFORM != ret:
                break

        while True:
            num_ret, ok_list, error_list = self.m.info_read()
            for c in ok_list:
                code = c.getinfo(pycurl.HTTP_CODE)
                if 401 == code:
                    head = AnyToSth(c.head.getvalue())
                    if '(error)' == head:
                        model = '(error)'
                    else:
                        try:
                            model = re.search('WWW-Authenticate: Basic realm="(.+?)"', c.head.getvalue(), re.U).group(1)
                            model = AnyToSth(model, 'gbk')
                        except AttributeError:
                            model = None

                else:
                    model = '(%u)' % code
                self.queue_out.put((c.data[0],
                 c.data[1],
                 c.data[2],
                 c.data[3],
                 model))
                c.data = None
                c.head.close()
                c.head = None
                c.body.close()
                c.body = None
                self.m.remove_handle(c)
                self.m.cpool.remove(c)
                c.close()

            for c, errno, errmsg in error_list:
                model = '(%u:%s)' % (errno, errmsg)
                self.queue_out.put((c.data[0],
                 c.data[1],
                 c.data[2],
                 c.data[3],
                 model))
                c.data = None
                c.head.close()
                c.head = None
                c.body.close()
                c.body = None
                self.m.remove_handle(c)
                self.m.cpool.remove(c)
                c.close()

            if 0 == num_ret:
                break

        self.m.select(1.0)

    return

class QueueOutWorker(Thread):

def __init__(self, queue_in, num_fetch, quiet = False):
    super(QueueOutWorker, self).__init__()
    self.queue_in = queue_in
    self.num_fetch = num_fetch
    self.quiet = quiet
    self.setDaemon(True)

def __del__(self):
    pass

def run(self):
    num_none = 0
    while True:
        data = self.queue_in.get()
        if data is None:
            num_none += 1
            if self.num_fetch == num_none:
                break
        else:
            host = data[0]
            code = data[1]
            server = data[2]
            password = data[3]
            model = data[4]
            if server is None:
                server = ''
            if password is None:
                password = ''
            if model is None:
                model = ''
            try:
                if self.quiet:
                    if '' != password:
                        sys.stdout.write('%-15s [%s] [%s]\n' % (socket.gethostbyname(host), model, password))
                else:
                    sys.stdout.write('%-15s [%u] [%s] [%s] [%s]\n' % (socket.gethostbyname(host),
                     code,
                     server,
                     model,
                     password))
            except socket.gaierror:
                pass

    return

def main(prog, args): from msvcrt import setmode setmode(0, os.O_BINARY) setmode(1, os.O_BINARY) setmode(2, os.O_BINARY)

def usage(prog):
    sys.stderr.write('Usage: ' + prog + ' [-h|--help] [-v] [-q] [-m] [-b begin] [-e end] [--port port]\n' + '       [--head head] [-s second] [-c connectnum] [-t threadnum] [infile|-]\n')
    raise SystemExit

try:
    opts, args = getopt.getopt(args, 'hvb:c:e:mqs:t:', ['help', 'head=', 'port='])
except getopt.GetoptError:
    usage(prog)

if 0 == len(opts) and 0 == len(args):
    usage(prog)
second = 30
threadnum = 8
connectnum = 16
begin = 0
end = 0
monitor = False
quiet = False
port = None
head = None
try:
    for x, y in opts:
        if x in ('-h', '--help'):
            usage(prog)
        elif '-v' == x:
            sys.stdout.write('%s ver 2015-04-22 11:22\n' % prog)
            sys.exit(0)
        elif '-b' == x:
            begin = resolvehost(y)
        elif '-c' == x:
            try:
                connectnum = uint32(int(y, 0))
            except ValueError:
                raise MyException('Line[%u]: ValueError : Checking your [-c connectnum]' % sys.exc_info()[2].tb_lineno)

        elif '-e' == x:
            end = resolvehost(y)
        elif '--head' == x:
            head = dosomething(y)
        elif '-m' == x:
            monitor = True
        elif '--port' == x:
            port = uint16(int(y, 0))
        elif '-q' == x:
            quiet = True
        elif '-s' == x:
            try:
                second = uint32(int(y, 0))
            except ValueError:
                raise MyException('Line[%u]: ValueError : Checking your [-s second]' % sys.exc_info()[2].tb_lineno)

        elif '-t' == x:
            try:
                threadnum = uint32(int(y, 0))
            except ValueError:
                raise MyException('Line[%u]: ValueError : Checking your [-t threadnum]' % sys.exc_info()[2].tb_lineno)

        else:
            usage(prog)

    if 0 == second:
        raise MyException('Line[%u]: Checking your [-s second]' % inspect.currentframe().f_lineno)
    if not 0 < connectnum * threadnum <= 256:
        raise MyException('Line[%u]: invalid number of concurrent connections' % inspect.currentframe().f_lineno)
    if 0 == port:
        raise MyException('Line[%u]: Checking your [-p port]' % inspect.currentframe().f_lineno)
    if 0 == begin and 0 == len(args):
        raise MyException('Line[%u]: Checking your [-b begin]' % inspect.currentframe().f_lineno)
    if 0 == end:
        end = begin + 1
    else:
        end = end + 1
    httpheader = ['User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET4.0C; .NET4.0E)', 'Accept: image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*']
    if head is not None:
        httpheader += [ h.strip() for h in head.split('\n') ]
    queue_in = Queue.Queue(1024)
    queue_tmp = Queue.Queue(1024)
    queue_out = Queue.Queue(4096)
    for i in xrange(threadnum):
        QueueInWorker(queue_in, queue_tmp, httpheader=httpheader, connect_max=connectnum, connecttimeout=second, timeout=second, monitor=monitor, port=port, proxypool=[]).start()
        QueueTmpWorker(queue_tmp, queue_out, num_fetch=threadnum, httpheader=httpheader, connect_max=connectnum, connecttimeout=second, timeout=second, port=port, proxypool=[]).start()

    qoworker = QueueOutWorker(queue_out, threadnum, quiet)
    qoworker.start()
    if len(args) > 0:
        if '-' == args[0]:
            while True:
                try:
                    target = string.strip(raw_input())
                    queue_in.put(target)
                except EOFError as e:
                    queue_in.put(None)
                    break

        else:
            try:
                infile = open(args[0], 'r')
            except IOError as e:
                raise MyException('Line[%u]: IOError : %s' % (sys.exc_info()[2].tb_lineno, str(e)))

            for target in infile:
                target = string.strip(target)
                queue_in.put(target)

            infile.close()
            queue_in.put(None)
    else:
        for ip in itertools.islice(itertools.count(begin), end - begin):
            tail = ip & 255
            if not tail or 255 == tail:
                continue
            queue_in.put(socket.inet_ntoa(itos32(socket.ntohl(ip))))

        queue_in.put(None)
    qoworker.join()
except MyException as e:
    sys.stderr.write('%s\n' % e.value)
    raise SystemExit

return

if 'main' == name: try: main(os.path.basename(sys.argv[0]), sys.argv[1:]) except KeyboardInterrupt: pass


☆ 反编译效果点评

前述反编译结果已经相当到位,除了理论上就无法还原的我那BT的代码风格。它这个 代码风格只能说是符合Python语法。

在《程序员的片段(2)--rom0scan.exe出台记》中,我提到:

几乎可以确定,Python版本的LZS解压缩代码存在某些细小的BUG。有时尽管其解压结 果并不正确,但由于析取口令的代码逻辑有一定容错性,导致对于某些rom-0析取口 令成功,从另一些rom-0则析取出多余的无效数据。Filippo Valsorda的代码显然没 有得到广泛测试,一直没有更新过。我尝试给他发邮件反馈BUG,没有得到回应。无 奈之下,我只能自己阅读LZS算法:

Lempel Ziv Stac (LZS) http://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Stac

没有仔细分析Filippo Valsorda的代码,大致觉得有几处不靠谱:


1)

处理编码后的length时,代码逻辑不够清晰。从前述对比测试用例看,很可能是取 length时有BUG。

2)

BitReader.init()中为什么要用bool()做强制类型转换?完全不必要。

3)

LZSDecompress()中的window形参没必要出现,从而class RingList没必要存在。

最后我照着LZS算法的维基描述以及其C版本实现重写了Python版本实现,不想修改 Filippo Valsorda的代码,太不合我的味口了。

LZS解压算法正确的Python实现参看反编译结果中的:

class BitReader def LZSDecompress()

☆ 多余的话

那些过去索要过superdns.exe、GetHttpsInfo.exe源代码的兄弟,不要说我没有理你。

我真地是一个很善良的人。

☆ 参考资源

[1] Extract .pyc files from executables created with py2exe https://github.com/matiasb/unpy2exe

uncompyle2 converts Python byte-code back into equivalent Python source code
https://github.com/Mysterie/uncompyle2

[2] Py2ExeDumper is a tool to extract a py2exe generated executable file http://sourceforge.net/projects/py2exedumper

Easy Python Decompiler is python bytecode decompiler, decompiles pyc & pyo files
http://sourceforge.net/projects/easypythondecompiler
(只有二进制,没有源代码,这是一个GUI程序)

[3] py2exe-extract https://code.google.com/p/py2exe-extract/

[4] pyREtic is an extensible framework for in-memory Python bytecode reverse engineering https://github.com/MyNameIsMeerkat/pyREtic