[CISCN2021]华北区_ctf_re_imnotavirus

[CISCN2021]华北区_ctf_re_imnotavirus

题目来源和附件

Written by Poilzero(blog:poilzero.cn)

  • 同步转发到简书和CSDN
  • 题目来源:

  • 2021年第十四届全国大学生信息安全竞赛——创新实践能力赛
  • re方向第一题
  • imnotavirus
  • 题目附件:

    https://poil.lanzoui.com/i0iImqjom6h

    个人解题目录和部分所需程序:

    https://poil.lanzoui.com/i9txPqz69pg

    文件是一个用python写的程序并且使用pyinstaller打包难点在于

  • 打包的时候加了-key参数,这样打包的时候会对主程序import的所有库进行加密可能是CFB加密?
  • python3.9无uncompyle6而源程序是用python3.9编写
  • 源码部分用python调用了win32API并且使用了多线程编程
  • 真正的flag生成过程实际上是使用了SMC技术(看雪《加密与解密17.2.2》)
  • 总体分解过程是step1-5

  • upx.exe脱压缩壳
  • *使用pyinstxtractor.py将exe文件逆向成pyc等文件(python3.9 x64)发现import的相关库被加密
  • 改文件头使用python38的uncompyle6生成main.py发现核心代码是是调用import的sign文件(被加密)
  • *解密sign文件(准确的来说是这个文件: main.exe_extracted\PYZ-00.pyz_extracted\sign.pyc.encrypted )得到源码
  • *分析源码写出注册机(使用了SMC技术)
  • step23逆向成py文件

    使用python38 pyinstxtractor.py: python pyinstxtractor.py [filename]

  • 脚本会根据文件头提示: Please run this script in Python39 to prevent extraction errors during unmarshalling
  • 所以使用python39(实测不用python39会不生成import的对应加密库)
  • 使用python39 pyinstxtractor.py: python pyinstxtractor.py [filename]

  • 再次运行脚本会生成一系列如: [!] Error: Failed to decompress PYZ-00.pyz_extracted\__future__.pyc, probably encrypted. Extracting as is.
  • 这其实是在逆向获得主文件import库的时候出错,因为这些文件加密了,即使用pyinstaller打包的时候加了-key参数
  • 但是正常生成文件了,解密(step4)也没法通过这个脚本(没有提供这个功能, 等我考完试看看能不能添加一下这个功能
  • 主文件 main.exe_extracted\main.pyc

    使用python38 uncompyle6库(pip install uncompyle):uncompyle6 [org_filename] > [out_filename]

  • 会报错,原因是如上的源项目是python3.9写的
  • 但是python39没有 uncompyle库
  • 我这里的解决方法是修改pyc文件头
  • 【*】修改pyc文件头,标其为python3.8的pyc文件

    使用二进制编辑器打开(如01editor winhex),参考我自己写的python38写的程序的反汇编pyc修改前16个字节为

    可以看到主文件其实调用了的是sign文件中的main()函数,因此下一步我们需要解密这个被加密的sign文件

    step4解密sign核心源码

    文件位于: main.exe_extracted\PYZ-00.pyz_extracted\sign.pyc.encrypted

    主办方提示:pyimod02_archive.py文件

    定位文件在pyinstaller项目源码: imnotavirus\pyinstaller-4.2\PyInstaller\loader\pyimod02_archive.py

    以及逆向生成的pyc文件: main.exe_extracted\pyimod02_archive.pyc

    分析得知:pyimod02_archive.py中有解密函数,参考其调用规则,在该文件末编写如下代码

    inf = open('sign.pyc.encrypted', 'rb')
    c = Cipher()
    buf = c.decrypt(inf.read())
    buf = zlib.decompress(buf) # 查看这个代码文件能发现密文是用zlib压缩过的所以需要解压缩
    out = open('sign.pyc', 'wb')
    out.write(buf)
    print('written down %n bytes' % len(buf))
    inf.close()
    out.close()
    

    将相关文件移入main.exe_extracted目录中

  • 加密文件:main.exe_extracted\PYZ-00.pyz_extracted\sign.pyc.encrypted
  • 解密文件:imnotavirus\pyinstaller-4.2\PyInstaller\loader\pyimod02_archive.py

    使用python39进入main.exe_extracted目录中,依次运行

  • 使用pyc加载相关的文件所以要在逆向目录中(比如密钥文件):pyimod02_archive.pyc
  • pyimod02_archive.py
  • 提示written down 3457 bytes代表正确生成了文件

    main.exe_extracted目录中的sign.py就是解压后的文件,参考前面的 <u>“【*】修改pyc文件头,标其为python3.8的pyc文件”</u>

    在文件最开始的地方插入十六个同前面的字节,然后用python38反编译得到核心源码uncompyle6 sign.pyc > sign.py

    核心代码如下

    # uncompyle6 version 3.7.4
    # Python bytecode 3.8 (3413)
    # Decompiled from: Python 3.8.5 (default, Sep  3 2020, 21:29:08) [MSC v.1916 64 bit (AMD64)]
    # Embedded file name: sign.py
    import ctypes, urllib, base64, hashlib, ast
    def ppp(bbb):
        qaq = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(bbb)), ctypes.c_int(12288), ctypes.c_int(64))
        bbb[31:35] = (qaq + 59).to_bytes(4, 'little')
        bbb[36:40] = (qaq + 178).to_bytes(4, 'little')
        qwq = (ctypes.c_char * len(bbb)).from_buffer(bbb)
        eval(base64.b64decode('Y3R5cGVzLndpbmRsbC5rZXJuZWwzMi5SdGxNb3ZlTWVtb3J5KGN0eXBlcy5jX3VpbnQzMihxYXEpLHF3cSxjdHlwZXMuY19pbnQobGVuKGJiYikpKQ==').decode())
        handle = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0), ctypes.c_int(0), ctypes.c_uint32(qaq), ctypes.c_int(0), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0)))
        ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(handle), ctypes.c_int(-1))
    def aaa(ttt, ggg):
        ggg = hashlib.md5(ggg).hexdigest()
        ttt = base64.b64decode(ttt)
        result = b''
        ggg_len = len(ggg)
        box = list(range(256))
        j = 0
        for i in range(256):
            j = (j + box[i] + ord(ggg[(i % ggg_len)])) % 256
            box[i], box[j] = box[j], box[i]
        else:
            i = j = 0
            for iii in ttt:
                i = (i + 1) % 256
                j = (j + box[i]) % 256
                box[i], box[j] = box[j], box[i]
                k = iii ^ box[((box[i] + box[j]) % 256)]
                result += bytes([k])
            else:
                return result
    def main():
        ctypes.windll.kernel32.VirtualAlloc.restyle = ctypes.c_uint32
        fff = input('Your input: ')
        b = 'pB2M/eG2iz1pB4kej4yXXCYvTFV3b/6NDPvMVc+iOs2QwI7Tg7QcItIK6KtB5seaZhd67NGYh6xyMPAocLhd0NeJhweg5/rsEuYnzxFxqMrysaizRAiD6HQhe50rwF5UnByay04giUxuLxy6zL8me5sAQqaUAuCv0c1EsDKBxv1B1zV8MEDav5bkgsTd2t3X3Jt0+lfGKk98bxg1FIXoUhtyZjhV489SpMi+Gzs2+/zaZ0d7p12KoppTYPNs3sj9l74Q8EJPYIAecUEnMSmKDF7yPaKIFloCdW5ghVaSeiaskr5OzfzfeccpvRPevL7PW9uW1R8WmcW2oWjN9aWsinwG2Gk1B7JPa+HusBGpIxxSQhK0wBrEjQpYlIMC7fTpFZca373+p/A2oXuaXqfmOoWtE62JCM74tWqZFrSWyLdnu1/vHaClrzcdzpHLum9shEOcNHYi88Dj11mYufJxH/sEx0CBtWkTCHwEhxVs1sYkGBHlDFUpFbfpY0UagbyPJdq+bmXBdmLKEhJ/M/2cCjmsjuma6IKvo+riA7/B8+T3GB06x31G5tibi3rJMDb4bVWBswzI3gg06mc4Q9EW5dQ4+/SRKbVoYhAfbGKlBeuxSIARKzSAuJPlfm+dmJ6pHx9a4qek2UIEKr4zxA0bSMALoYOSETDF1JWZX+K0HEJLY/hXajmzq5qSTX0EAKBLJtIPkJ0e+XsaQCXyhy4Cg8mYNlQGIORNo5vyNe4QPAD8d3GKr/PZnEMwJ5WsgyoSBOGDke4PGUJd70thEyGHKN9QfTXWknC8HBZdcEojvtC3Prj7LlxXI6y8uZ7ie/1HltGogj3EsUkqU0d3WDuBPZec1Tzj/7Vs44MFGRjEJ0IuSA0U0vOCShyeHUB23qhrXqODsrO/t+s/Zohmd2H0xS46qdoquQj8L1RY2fCt3H0US3Wffk0FKf7qYboKeW/7vlkOYlchgP/HXf0Mfo5gBXhJg3e9jGJ8K5J0gt6Zra9dhPINGgekDMIoxXE='
        c = aaa(b, 'blackhand'.encode('utf-8'))
        c = ast.literal_eval("b'" + c.decode().strip() + "'")
        qaq = eval(base64.b64decode('Y3R5cGVzLndpbmRsbC5rZXJuZWwzMi5WaXJ0dWFsQWxsb2MoY3R5cGVzLmNfaW50KDApLCBjdHlwZXMuY19pbnQobGVuKGZmZikpLCBjdHlwZXMuY19pbnQoMTIyODgpLCBjdHlwZXMuY19pbnQoNjQpKQ==').decode())
        qwq = (ctypes.c_char * (len(fff) + 1)).from_buffer(bytearray(fff.encode()) + bytes([0]))
        eval(base64.b64decode('Y3R5cGVzLndpbmRsbC5rZXJuZWwzMi5SdGxNb3ZlTWVtb3J5KGN0eXBlcy5jX3VpbnQzMihxYXEpLHF3cSxjdHlwZXMuY19pbnQobGVuKGZmZikpKQ==').decode())
        c = bytearray(c)
        c[55:59] = qaq.to_bytes(4, 'little')
        ppp(c)
        r = ctypes.create_string_buffer(len(fff))
        ctypes.windll.kernel32.ReadProcessMemory(ctypes.windll.kernel32.GetCurrentProcess(), ctypes.c_uint32(qaq), ctypes.byref(r), ctypes.c_ulong(len(fff)), 0)
        if r.raw == b'J\x04`~~s Q"Y!C [j\x05e\x06aB&N#B!E Qp\\ S{\x05{\x05{\x05':
            print('Yes! You got it!')
        else:
            print('Nope. Try harder :)')
    # okay decompiling sign.pyc
    

    step5注册机

    sign源码审计

    审计后发现代码内容主要做以下几件事

  • 读取输入值
  • *使用win32api操作
  • 把输入值和汇编形式的指针函数写入内存中
  • 使用线程调用这个指针函数从而加密输入值
  • 最后把加密结果和一个量比较判断是否正确
  • 其中指针函数在ppp()函数中,我写了个myproof函数把这个指针函数作为一个文件保存下来

    提示如下时代码正常运行

    require:

  • python3.9 x32版本
  • 标准库:ctypes, urllib, base64, hashlib, ast
  • Your input: 1111
    > written down 191 bytes
    > written in 0x3950000
    Nope. Try harder :)
    Process finished with exit code 0
    

    接下来就是审计用汇编写的指针函数了

    已添加注释如下,也可在附件中查看

    # uncompyle6 version 3.7.4
    # Python bytecode 3.8 (3413)
    # Decompiled from: Python 3.8.5 (default, Sep  3 2020, 21:29:08) [MSC v.1916 64 bit (AMD64)]
    # Embedded file name: sign.py
    import ctypes, urllib, base64, hashlib, ast
    def my_proof(QAQ, add):
        # save qaq function
        out = open('qaq_function', 'wb')
        out.write(QAQ)
        out.close(),print('> written down {} bytes'.format(len(QAQ)))
        print('> written in {}'.format(hex(add)))
    def getStringFromMem(source, length):
        r = ctypes.create_string_buffer(length)
        ctypes.windll.kernel32.ReadProcessMemory(
            ctypes.windll.kernel32.GetCurrentProcess()  # hinstance
            , ctypes.c_uint32(source)                   # from        address
            , ctypes.byref(r)                           # destination address
            , ctypes.c_ulong(length)                  # nSize
        # print('in getStringFromMem() qaq\'s address is {}'.format(hex(source)))
        return r.raw
    # converted by c(bbb)
    def ppp(bbb):
        # qaq = malloc(len(bbb))
        qaq = ctypes.windll.kernel32.VirtualAlloc(
            ctypes.c_int(0)          # destination address
            , ctypes.c_int(len(bbb)) # dwSize
            , ctypes.c_int(12288)    # type of allocation
            , ctypes.c_int(64)
        )      # type of access protection
        bbb[31:35] = (qaq + 59).to_bytes(4, 'little')
        bbb[36:40] = (qaq + 178).to_bytes(4, 'little')
        # qwq = bbb
        qwq = (ctypes.c_char * len(bbb)).from_buffer(bbb)
        # qaq = qwq (=bbb)
        ctypes.windll.kernel32.RtlMoveMemory(
            ctypes.c_uint32(qaq)     # destination address
            ,qwq                     # source address
            ,ctypes.c_int(len(bbb))  # nSize
        # bbb is code written to mem-add at qaq
        my_proof(bbb, qaq)  # to touch qaq=qwq=bbb written by me
    ####### threadprocess
        # qaq is a function!!!
        handle = ctypes.windll.kernel32.CreateThread(
            ctypes.c_int(0)         # must be NULL
            , ctypes.c_int(0)       # 0 means createThread STACK_SIZE_PARAM_IS_A_RESERVATION means as virtual memeory
            , ctypes.c_uint32(qaq)  # function pointer!!!!
            , ctypes.c_int(0)       # function's param lpvThreadParam
            , ctypes.c_int(0)       # type of CreateThread
            , ctypes.pointer(ctypes.c_int(0)) # lpRecevedReternValue if 0 it will not return to it
        ctypes.windll.kernel32.WaitForSingleObject(
            ctypes.c_int(handle)
            , ctypes.c_int(-1)
        return None
    return is constance
    def aaa(ttt, ggg):
        ggg = hashlib.md5(ggg).hexdigest() # ggg = md5(ggg,16,little)
        ttt = base64.b64decode(ttt)
    ##########
        result = b''
        ggg_len = len(ggg)
        box = list(range(256))
        j = 0
        for i in range(256):
            j = (j + box[i] + ord(ggg[(i % ggg_len)])) % 256
            box[i], box[j] = box[j], box[i]
        else:
            i = j = 0
            for iii in ttt:
                i = (i + 1) % 256
                j = (j + box[i]) % 256
                box[i], box[j] = box[j], box[i]
                k = iii ^ box[((box[i] + box[j]) % 256)]
                result += bytes([k])
            else:
                return result
    def main():
        ctypes.windll.kernel32.VirtualAlloc.restyle = ctypes.c_uint32
        # fff = 'Y3sImD3f1n2t3ly2v1ru5y0u2r3fck3dh2h2h2'
        fff = input('Your input: ')
        b = 'pB2M/eG2iz1pB4kej4yXXCYvTFV3b/6NDPvMVc+iOs2QwI7Tg7QcItIK6KtB5seaZhd67NGYh6xyMPAocLhd0NeJhweg5/rsEuYnzxFxqMrysaizRAiD6HQhe50rwF5UnByay04giUxuLxy6zL8me5sAQqaUAuCv0c1EsDKBxv1B1zV8MEDav5bkgsTd2t3X3Jt0+lfGKk98bxg1FIXoUhtyZjhV489SpMi+Gzs2+/zaZ0d7p12KoppTYPNs3sj9l74Q8EJPYIAecUEnMSmKDF7yPaKIFloCdW5ghVaSeiaskr5OzfzfeccpvRPevL7PW9uW1R8WmcW2oWjN9aWsinwG2Gk1B7JPa+HusBGpIxxSQhK0wBrEjQpYlIMC7fTpFZca373+p/A2oXuaXqfmOoWtE62JCM74tWqZFrSWyLdnu1/vHaClrzcdzpHLum9shEOcNHYi88Dj11mYufJxH/sEx0CBtWkTCHwEhxVs1sYkGBHlDFUpFbfpY0UagbyPJdq+bmXBdmLKEhJ/M/2cCjmsjuma6IKvo+riA7/B8+T3GB06x31G5tibi3rJMDb4bVWBswzI3gg06mc4Q9EW5dQ4+/SRKbVoYhAfbGKlBeuxSIARKzSAuJPlfm+dmJ6pHx9a4qek2UIEKr4zxA0bSMALoYOSETDF1JWZX+K0HEJLY/hXajmzq5qSTX0EAKBLJtIPkJ0e+XsaQCXyhy4Cg8mYNlQGIORNo5vyNe4QPAD8d3GKr/PZnEMwJ5WsgyoSBOGDke4PGUJd70thEyGHKN9QfTXWknC8HBZdcEojvtC3Prj7LlxXI6y8uZ7ie/1HltGogj3EsUkqU0d3WDuBPZec1Tzj/7Vs44MFGRjEJ0IuSA0U0vOCShyeHUB23qhrXqODsrO/t+s/Zohmd2H0xS46qdoquQj8L1RY2fCt3H0US3Wffk0FKf7qYboKeW/7vlkOYlchgP/HXf0Mfo5gBXhJg3e9jGJ8K5J0gt6Zra9dhPINGgekDMIoxXE='
        c = aaa(b, 'blackhand'.encode('utf-8'))
        # ast.literal_eval equal try-catch-mode eval()
        # strip() 去除首尾的所有 或\n
        c = ast.literal_eval("b'" + c.decode().strip() + "'") # c is constance
        # qaq = malloc(len(fff))
        qaq = ctypes.windll.kernel32.VirtualAlloc(
            ctypes.c_int(0)             # destination address
            , ctypes.c_int(len(fff))    # dwSize
            , ctypes.c_int(12288)       # type of allocation
            , ctypes.c_int(64)          # type of access protection
        # qwq=fff+'0'
        qwq = (ctypes.c_char * (len(fff) + 1)).from_buffer(bytearray(fff.encode()) + bytes([0]))
        # qaq = qwq(=fff+'\x0')
        ctypes.windll.kernel32.RtlMoveMemory(
            ctypes.c_uint32(qaq)     # destination address
            ,qwq                     # source address
            ,ctypes.c_int(len(fff))  # nSize
        c variable change start
        c = bytearray(c) # transefer to bytes list struct (like list)
        # c[55:59] = qaq_address[0:4]
        c[55:59] = qaq.to_bytes(4, 'little')
        # print(getStringFromMem(qaq,len(fff)))
        ppp(c)
        # print(getStringFromMem(qaq,len(fff)))
        change end
        # r.raw = qaq
        r = ctypes.create_string_buffer(len(fff))
        ctypes.windll.kernel32.ReadProcessMemory(
            ctypes.windll.kernel32.GetCurrentProcess() # hinstance
            , ctypes.c_uint32(qaq)       # from        address
            , ctypes.byref(r)            # destination address
            , ctypes.c_ulong(len(fff))   # nSize
        # compare
        if r.raw == b'J\x04`~~s Q"Y!C [j\x05e\x06aB&N#B!E Qp\\ S{\x05{\x05{\x05':
            print('Yes! You got it!')
        else:
            print('Nope. Try harder :)')
    # okay decompiling sign.pyc
    main()
    

    指针函数汇编审计

    ida打开qaq_function发现有乱码:

    seg000:00000000 ; ===========================================================================
    seg000:00000000
    seg000:00000000 ; Segment type: Pure code
    seg000:00000000 seg000          segment byte public 'CODE' use32
    seg000:00000000                 assume cs:seg000
    seg000:00000000                 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
    seg000:00000000                 push    ebp
    seg000:00000001                 mov     ebp, esp
    seg000:00000003                 sub     esp, 0E4h
    seg000:00000009                 push    ebx
    seg000:0000000A                 push    esi
    seg000:0000000B                 push    edi
    seg000:0000000C                 lea     edi, [ebp-0E4h]
    seg000:00000012                 mov     ecx, 39h ; '9'
    seg000:00000017                 mov     eax, 0CCCCCCCCh
    seg000:0000001C                 rep stosd
    seg000:0000001E                 mov     eax, 3BB003Bh
    seg000:00000023                 mov     ecx, 3BB00B2h
    seg000:00000028                 sub     ecx, eax
    seg000:0000002A
    seg000:0000002A loc_2A:                                 ; CODE XREF: seg000:00000032↓j
    seg000:0000002A                 mov     ebx, [eax]
    seg000:0000002C                 xor     ebx, 77h
    seg000:0000002F                 mov     [eax], ebx
    seg000:00000031                 inc     eax
    seg000:00000032                 loop    loc_2A
    seg000:00000034                 mov     dword ptr [ebp+8], 3BA0000h
    seg000:0000003B                 mov     al, 32h ; '2'
    seg000:0000003B ; ---------------------------------------------------------------------------
    seg000:0000003D                 db 8Fh, 2 dup(77h)
    seg000:00000040                 db  77h ; w
    seg000:00000041                 db 77h, 0B0h, 32h
    seg000:00000044                 dd 7777779Bh, 0FC7E9C77h, 0B7F49B32h, 9B32FE76h, 747F32FCh
    seg000:00000044                 dd 0C9789B32h, 3BEF27Fh, 8F32FC7Ch, 0FE76B7F4h, 0A89C8F32h
    seg000:00000044                 dd 779732B0h, 9C777777h, 9732FC7Eh, 0FE75B7F4h, 32FC9732h
    seg000:00000044                 dd 8F324C97h, 32FC5B0Ah, 9732747Fh, 0F47FC978h, 22FC6486h
    seg000:00000044                 dd 9722747Fh, 32FC7DFFh, 9732747Fh, 763FC978h, 0FC4086F4h
    seg000:00000044                 dd 22747F22h, 763DFF97h, 5E5FB49Ch, 0E4C4815Bh, 8B000000h
    seg000:000000BC                 db 0E5h, 5Dh, 0C3h
    seg000:000000BC seg000          ends
    seg000:000000BC
    seg000:000000BC
    seg000:000000BC                 end
    

    其中smc代码,修改了运行时的程序代码,规则是与0x77异或,即以下代码

    seg000:0000001E                 mov     eax, 3BB003Bh
    seg000:00000023                 mov     ecx, 3BB00B2h
    seg000:00000028                 sub     ecx, eax
    seg000:0000002A
    seg000:0000002A loc_2A:                                 ; CODE XREF: seg000:00000032↓j
    seg000:0000002A                 mov     ebx, [eax]
    seg000:0000002C                 xor     ebx, 77h
    seg000:0000002F                 mov     [eax], ebx
    seg000:00000031                 inc     eax
    seg000:00000032                 loop    loc_2A
    

    编写代码直接代替执行异或,对文件直接修改生成正确的运行时指针函数

    l = 0x3b
    r = 0xb2
    fi = open('qaq_function', 'rb')
    fo = open('qaq_function_smced', 'wb')
    cipher = fi.read()
    for i in range(len(cipher)):
        if i>=0x3b and i<0xb2:
            per = cipher[i]^0x77
        else:
            per = cipher[i]
        per = hex(per)
        per = "b'\\"+per[1:]+"'"
        if len(per)==6:
            per = per[:-2]+'0'+per[-2:]
        print('> written byte:', per)
        per = eval(per)
        fo.write(per)
    fi.close(),fo.close()
    

    得到qaq_function_smced文件ida打开如下:

    跳转流程:

    loc_54-》loc_6C(花指令本质上是强制跳转)-》loc_7E-》

    seg000:00000000 ; ===========================================================================
    seg000:00000000
    seg000:00000000 ; Segment type: Pure code
    seg000:00000000 seg000          segment byte public 'CODE' use32
    seg000:00000000                 assume cs:seg000
    seg000:00000000                 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
    seg000:00000000                 push    ebp
    seg000:00000001                 mov     ebp, esp
    seg000:00000003                 sub     esp, 0E4h
    seg000:00000009                 push    ebx
    seg000:0000000A                 push    esi
    seg000:0000000B                 push    edi
    seg000:0000000C                 lea     edi, [ebp-0E4h]
    seg000:00000012                 mov     ecx, 39h ; '9'
    seg000:00000017                 mov     eax, 0CCCCCCCCh
    seg000:0000001C                 rep stosd
    seg000:0000001E                 mov     eax, 3BB003Bh
    seg000:00000023                 mov     ecx, 3BB00B2h
    seg000:00000028                 sub     ecx, eax
    seg000:0000002A
    seg000:0000002A loc_2A:                                 ; CODE XREF: seg000:00000032↓j
    seg000:0000002A                 mov     ebx, [eax]
    seg000:0000002C                 xor     ebx, 77h
    seg000:0000002F                 mov     [eax], ebx
    seg000:00000031                 inc     eax
    seg000:00000032                 loop    loc_2A
    seg000:00000034                 mov     dword ptr [ebp+8], 3BA0000h
    seg000:0000003B                 mov     dword ptr [ebp-8], 0
    seg000:00000042                 mov     dword ptr [ebp-14h], 0
    seg000:00000049                 jmp     short loc_54
    seg000:0000004B ; ---------------------------------------------------------------------------
    seg000:0000004B
    seg000:0000004B loc_4B:                                 ; CODE XREF: seg000:0000006A↓j
    seg000:0000004B                 mov     eax, [ebp-14h]
    seg000:0000004E                 add     eax, 1
    seg000:00000051                 mov     [ebp-14h], eax
    seg000:00000054
    seg000:00000054 loc_54:                                 ; CODE XREF: seg000:00000049↑j
    seg000:00000054                 mov     eax, [ebp+8]
    seg000:00000057                 add     eax, [ebp-14h]
    seg000:0000005A                 movsx   ecx, byte ptr [eax]
    seg000:0000005D                 test    ecx, ecx
    seg000:0000005F                 jz      short loc_6C    ; 花指令,其实是强制跳转jmp
    seg000:00000061                 mov     eax, [ebp-8]
    seg000:00000064                 add     eax, 1
    seg000:00000067                 mov     [ebp-8], eax
    seg000:0000006A                 jmp     short loc_4B
    seg000:0000006C ; ---------------------------------------------------------------------------
    seg000:0000006C
    seg000:0000006C loc_6C:                                 ; CODE XREF: seg000:0000005F↑j
    seg000:0000006C                 mov     dword ptr [ebp-20h], 0
    seg000:00000073                 jmp     short loc_7E
    seg000:00000075 ; ---------------------------------------------------------------------------
    seg000:00000075
    seg000:00000075 loc_75:                                 ; CODE XREF: seg000:000000B0↓j
    seg000:00000075                 mov     eax, [ebp-20h]
    seg000:00000078                 add     eax, 2
    seg000:0000007B                 mov     [ebp-20h], eax
    seg000:0000007E
    seg000:0000007E loc_7E:                                 ; CODE XREF: seg000:00000073↑j
    seg000:0000007E                 mov     eax, [ebp-20h]
    seg000:00000081                 cmp     eax, [ebp-8]
    seg000:00000084                 jge     short loc_B2
    seg000:00000086                 mov     eax, [ebp+8]
    seg000:00000089                 add     eax, [ebp-20h]
    seg000:0000008C                 movsx   ecx, byte ptr [eax]
    seg000:0000008F                 xor     ecx, 13h
    seg000:00000092                 mov     edx, [ebp+8]
    seg000:00000095                 add     edx, [ebp-20h]
    seg000:00000098                 mov     [edx], cl
    seg000:0000009A                 mov     eax, [ebp+8]
    seg000:0000009D                 add     eax, [ebp-20h]
    seg000:000000A0                 movsx   ecx, byte ptr [eax+1]
    seg000:000000A4                 xor     ecx, 37h
    seg000:000000A7                 mov     edx, [ebp+8]
    seg000:000000AA                 add     edx, [ebp-20h]
    seg000:000000AD                 mov     [edx+1], cl
    seg000:000000B0                 jmp     short loc_75
    seg000:000000B2 ; ---------------------------------------------------------------------------
    seg000:000000B2
    seg000:000000B2 loc_B2:                                 ; CODE XREF: seg000:00000084↑j
    seg000:000000B2                 pop     edi
    seg000:000000B3                 pop     esi
    seg000:000000B4                 pop     ebx
    seg000:000000B5                 add     esp, 0E4h
    seg000:000000BB                 mov     esp, ebp
    seg000:000000BD                 pop     ebp
    seg000:000000BE                 retn
    seg000:000000BE seg000          ends
    seg000:000000BE
    seg000:000000BE
    seg000:000000BE                 end
    

    本质上是将输入值偶奇位分别于0x13和0x37进行异或,编写注册机:

    s = 'J\x04`~~s Q"Y!C [j\x05e\x06aB&N#B!E Qp\\ S{\x05{\x05{\x05'
    ans = ''
    for i in range(len(s)):
        per = ord(s[i])
        if i%2 ==0:
            ans += chr(per^0x13)
        else:
            ans += chr(per^0x37)
    print(ans)