首页 > 其他分享 >pyc文件花指令

pyc文件花指令

时间:2024-11-13 21:40:25浏览次数:1  
标签:文件 now code co JUMP 指令 consts set pyc

pyc花指令

常见的python花指令形式有两种:单重叠指令和多重叠指令。

以下以python3.8为例,指令长度为2字节。

单重叠指令:

例如pyc经过反编译后得到的东西为

 0 JUMP_ABSOLUTE        [71 04]     5 
 2 PRINT_ITEM           [47 --]
 4 LOAD_CONST           [64 10]     16
 6 STOP_CODE            [00 --]

实际在执行时,并不会执行 2 PRINT_ITEM [47 --]

0 JUMP_ABSOLUTE        [71 04]     5 
4 LOAD_CONST           [64 10]     16

单重叠指令多是分支的跳转,导致一些反编译工具如pycdc、uncompyle6出错。

多重叠指令:

 0 EXTENDED_ARG         [91 64] 
 2 EXTENDED_ARG         [91 53]
 4 JUMP_ABSOLUTE        [71 01]

实际执行时

 0 EXTENDED_ARG         [91 64] 
 2 EXTENDED_ARG         [91 53]
 4 JUMP_ABSOLUTE        [71 02]
 1 LOAD_CONST           [64 91]
 3 RETURN_VALUE         [53 --]

多重叠指令是将指令的数据部分当作下一条指令的opcode部分执行,在跳转基础上进一步混淆控制流的技术手段,可以有效对抗逆向者。

NOP花指令:

NOP为junk code,只要不影响正常执行逻辑,其他的指令可自由发挥,含有NOP的pyc均不可以被现有的反编译工具反编译成py代码。

去除花指令

pyc去除花指令后,很大可能是不能被现有工具反编译成源码的,因为现有反编译工具对pyc要求比较严格,不能有nop以及其他junk指令,但程序运行时python虚拟机却没有。

因此不同于用ida patch 汇编代码,想在patch过的pyc反编译回原来的源码,工作量还是蛮大的。

下面以[2022年安洵杯]flower.pyc为例

626     LOAD_GLOBAL             6: ord
628     LOAD_GLOBAL             18: Base64Table
630     LOAD_FAST               3: i
632     LOAD_CONST              22: 22
634     BINARY_XOR              
636     BINARY_SUBSCR           
638     CALL_FUNCTION           1
640     STORE_FAST              15: tmp2

这一段是把base64[i]改为了base64[i^22]

258     LOAD_NAME               16: ret
260     LOAD_NAME               18: i
262     LOAD_NAME               18: i
264     LOAD_CONST              40: 4
266     BINARY_ADD              
268     BUILD_SLICE             2
270     BINARY_SUBSCR           
272     LOAD_NAME               19: Key1
274     LOAD_NAME               17: j
276     STORE_SUBSCR            
278     LOAD_NAME               17: j
280     LOAD_CONST              41: 1
282     BINARY_ADD              
284     STORE_NAME              17: j
286     LOAD_NAME               18: i
288     LOAD_CONST              40: 4
290     BINARY_ADD              
292     STORE_NAME              18: i
294     LOAD_NAME               17: j
296     LOAD_CONST              42: 10
298     COMPARE_OP              2 (==)
300     POP_JUMP_IF_FALSE       258
304     JUMP_ABSOLUTE           312
308     JUMP_ABSOLUTE           258

转化成py代码就是

input_str = input()
ret = My_base64_encode(input_str)
j = 0
i = 0
Key1 = "1234512345"
len_ret = len(ret) // 4

while j != 10:
	Key1[j] = ret[i:i+4]
	j = j + 1
	i = i + 4

keyCheck = ''
if keyCheck[0] == keyInputCom[8]:

然后后面有一堆重复的,提取出来就是

0 == 8
1 == 9
2 == 1
3 == 7
4 == 5
5 == 0
6 == 6
7 == 4
8 == 3
9 == 2

然后再写题解的代码就可以了。


利用脚本去除花指令:用python模拟执行python的opcode,遇到分支就跳转,直到ret_value停止本次执行,采用的是简单的DFS递归算法

import marshal, sys, opcode, types, dis

NOP = 9

HAVE_ARGUMENT = 90

JUMP_FORWARD = 110
JUMP_IF_FALSE_OR_POP = 111
JUMP_IF_TRUE_OR_POP = 112
JUMP_ABSOLUTE = 113
POP_JUMP_IF_FALSE = 114
POP_JUMP_IF_TRUE = 115

CONTINUE_LOOP = 119
FOR_ITER = 93

RETURN_VALUE = 83

used_set = set()

def deconf_inner(code, now):
    global used_set

    while code[now] != RETURN_VALUE:
        if now in used_set:
            break
        used_set.add(now)
        if code[now] >= HAVE_ARGUMENT:
            used_set.add(now+1)
            used_set.add(now+2)
        op = code[now]

        #print(str(now) + " " + opcode.opname[op])

        if op == JUMP_FORWARD:
            arg = code[now+2] << 8 | code[now+1]
            now += arg + 3
            continue

        elif op == JUMP_ABSOLUTE:
            arg = code[now+2] << 8 | code[now+1]
            now = arg
            continue

        elif op == JUMP_IF_TRUE_OR_POP:
            arg = code[now+2] << 8 | code[now+1] 
            deconf_inner(code, arg)

        elif op == JUMP_IF_FALSE_OR_POP:
            arg = code[now+2] << 8 | code[now+1] 
            deconf_inner(code, arg)

        elif op == POP_JUMP_IF_TRUE:
            arg = code[now+2] << 8 | code[now+1] 
            deconf_inner(code, arg)

        elif op == POP_JUMP_IF_FALSE: 
            arg = code[now+2] << 8 | code[now+1] 
            deconf_inner(code, arg)

        elif op == CONTINUE_LOOP:
            arg = code[now+2] << 8 | code[now+1] 
            deconf_inner(code, arg)

        elif op == FOR_ITER: 
            arg = code[now+2] << 8 | code[now+1] 
            deconf_inner(code, now + arg + 3)

        if op < HAVE_ARGUMENT:
            now += 1
        else:
            now += 3

    used_set.add(now)
    if code[now] >= HAVE_ARGUMENT:
        used_set.add(now+1)
        used_set.add(now+2)

def deconf(code):
    global used_set

    used_set = set() #Remember to clean up used_set for every target function

    cod = list(map(ord, code))
    deconf_inner(cod, 0)

    for i in range(len(cod)):
        if i not in used_set:
            cod[i] = NOP

    return "".join(list(map(chr, cod)))

with open(sys.argv[1], 'rb') as f:
    header = f.read(8)
    code = marshal.load(f)

print(code.co_consts,type(code))
'''
print(dis.dis(deconf(code.co_consts[3].co_code)))
'''

consts = list()

for i in range(len(code.co_consts)):
    if hasattr(code.co_consts[i], 'co_code'):
        consts.append(types.CodeType(code.co_consts[i].co_argcount,
            # c.co_kwonlyargcount,  Add this in Python3
            code.co_consts[i].co_nlocals,
            code.co_consts[i].co_stacksize,
            code.co_consts[i].co_flags,
            deconf(code.co_consts[i].co_code),
            code.co_consts[i].co_consts,
            code.co_consts[i].co_names,
            code.co_consts[i].co_varnames,
            code.co_consts[i].co_filename,
            code.co_consts[i].co_name,
            code.co_consts[i].co_firstlineno,
            code.co_consts[i].co_lnotab,   # In general, You should adjust this
            code.co_consts[i].co_freevars,
            code.co_consts[i].co_cellvars))
    else:
        consts.append(code.co_consts[i])

mode = types.CodeType(code.co_argcount,
    # c.co_kwonlyargcount,  Add this in Python3
    code.co_nlocals,
    code.co_stacksize,
    code.co_flags,
    deconf(code.co_code),
    tuple(consts),
    code.co_names,
    code.co_varnames,
    code.co_filename,
    code.co_name,
    code.co_firstlineno,
    code.co_lnotab,   # In general, You should adjust this
    code.co_freevars,
    code.co_cellvars)

f = open(sys.argv[1]+".mod", 'wb') 
f.write(header)
marshal.dump(mode, f)
import marshal, sys, opcode, types, dis
import opcode


def getopcode(opname):
    return opcode.opname.index(opname)


NOP = getopcode('NOP')

# HAVE_ARGUMENT = getopcode('HAVE_ARGUMENT')  # py2.7

JUMP_FORWARD = getopcode('JUMP_FORWARD')
JUMP_IF_FALSE_OR_POP = getopcode('JUMP_IF_FALSE_OR_POP')
JUMP_IF_TRUE_OR_POP = getopcode('JUMP_IF_TRUE_OR_POP')
JUMP_ABSOLUTE = getopcode('JUMP_ABSOLUTE')
POP_JUMP_IF_FALSE = getopcode('POP_JUMP_IF_FALSE')
POP_JUMP_IF_TRUE = getopcode('POP_JUMP_IF_TRUE')
EXTENDED_ARG = getopcode('EXTENDED_ARG')
# CONTINUE_LOOP = getopcode('CONTINUE_LOOP')  # py2.7
FOR_ITER = getopcode('FOR_ITER')

RETURN_VALUE = getopcode('RETURN_VALUE')

used_set = set()


def deconf_inner(code, now):
    global used_set

    while code[now] != RETURN_VALUE:
        if now in used_set:
            break
        used_set.add(now)
        used_set.add(now + 1)
        op = code[now]


        # print(str(now) + " " + opcode.opname[op])

        if op == EXTENDED_ARG: # 对JUMP_FORWARD带有EXTENDED_ARG的处理
            # 第一层
            op_next = code[now + 2]
            now += 2
            used_set.add(now)
            used_set.add(now+1)
            if op_next == EXTENDED_ARG:
                # 第二层
                arg = code[now - 1] << 8|code[now + 1]
                op_next_next = code[now + 2]
                now += 2
                used_set.add(now)
                used_set.add(now+1)
                if op_next_next == EXTENDED_ARG:
                    arg = arg << 8 | code[now + 1]
                    # 第三层
                    if op_next == JUMP_FORWARD or op_next == FOR_ITER:
                        arg = arg << 8 | code[now + 1]
                        deconf_inner(code, arg + now + 2)
                    else:
                        arg = arg << 8 | code[now + 1]
                        deconf_inner(code, arg)
                elif op_next == JUMP_FORWARD or op_next == FOR_ITER:
                    arg = code[now - 1] << 8 | code[now + 1]
                    deconf_inner(code, arg + now + 2)
                else:
                    arg = code[now - 1] << 8 | code[now + 1]
                    deconf_inner(code, arg)
            elif op_next == JUMP_FORWARD or op_next == FOR_ITER:
                arg = code[now - 1] << 8 | code[now + 1]
                deconf_inner(code, arg + now + 2)
            else:
                arg = code[now - 1] << 8 | code[now + 1]
                deconf_inner(code, arg)

            
        elif op == JUMP_FORWARD:
            arg = code[now + 1]
            now += arg + 2
            op_next = code[now]
            if op_next == JUMP_FORWARD or arg == 0 or arg == 1 or arg == 2 or arg == 4: # 一般JUMP_FORWARD参数为0、2、4都为花指令
                used_set.remove(now - (arg + 2))
                used_set.remove(now - (arg + 2) + 1)
            continue

        elif op == JUMP_ABSOLUTE:
            arg = code[now + 1]
            now = arg
            continue

        elif op == JUMP_IF_TRUE_OR_POP:
            arg = code[now + 1]
            deconf_inner(code, arg)

        elif op == JUMP_IF_FALSE_OR_POP:
            arg = code[now + 1]
            deconf_inner(code, arg)

        elif op == POP_JUMP_IF_TRUE:
            arg = code[now + 1]
            deconf_inner(code, arg)

        elif op == POP_JUMP_IF_FALSE:
            arg = code[now + 1]
            deconf_inner(code, arg)

        elif op == FOR_ITER:
            arg = code[now + 1]
            deconf_inner(code, now + arg + 2)

        now += 2

    used_set.add(now)


def deconf(code):
    global used_set

    used_set = set()  # Remember to clean up used_set for every target function

    # cod = list(map(ord, code))
    cod = list(code)
    deconf_inner(cod, 0)

    for i in range(len(cod)):
        if i not in used_set:
            cod[i] = NOP
    # aa = bytes(cod)
    aa = b''.join(map(lambda x: int.to_bytes(x, 1, 'little'), cod))
    return aa


filename = 'PYC.pyc'
with open(filename, 'rb') as f:
    header = f.read(16)
    code = marshal.load(f)

print(code.co_consts)
'''

print(dis.dis(deconf(code.co_consts[3].co_code)))
'''

consts = list()

for i in range(len(code.co_consts)):
    if hasattr(code.co_consts[i], 'co_code'):
        consts.append(types.CodeType(code.co_consts[i].co_argcount,
                                     code.co_posonlyargcount,
                                     code.co_kwonlyargcount,  # Add this in Python3
                                     code.co_consts[i].co_nlocals,
                                     code.co_consts[i].co_stacksize,
                                     code.co_consts[i].co_flags,
                                     deconf(code.co_consts[i].co_code),
                                     code.co_consts[i].co_consts,
                                     code.co_consts[i].co_names,
                                     code.co_consts[i].co_varnames,
                                     code.co_consts[i].co_filename,
                                     code.co_consts[i].co_name,
                                     code.co_consts[i].co_firstlineno,
                                     code.co_consts[i].co_lnotab,  # In general, You should adjust this
                                     code.co_consts[i].co_freevars,
                                     code.co_consts[i].co_cellvars))
    else:
        consts.append(code.co_consts[i])

mode = types.CodeType(code.co_argcount,
                      code.co_posonlyargcount,
                      code.co_kwonlyargcount,  # Add this in Python3
                      code.co_nlocals,
                      code.co_stacksize,
                      code.co_flags,
                      deconf(code.co_code),
                      tuple(consts),
                      code.co_names,
                      code.co_varnames,
                      code.co_filename,
                      code.co_name,
                      code.co_firstlineno,
                      code.co_lnotab,  # In general, You should adjust this
                      code.co_freevars,
                      code.co_cellvars)

f = open(filename + ".mod", 'wb')
f.write(header)
marshal.dump(mode, f)

标签:文件,now,code,co,JUMP,指令,consts,set,pyc
From: https://www.cnblogs.com/PaperPlaneFly/p/18544903

相关文章

  • 关于Vulkan应用程序运行时编译GLSL Shader文件的方法
    最近在学习Vulkan,在相关参考书中给出的示例代码因为使用的VulkanSDK较老,出现无法构建成功的问题。而且示例代码给出的Vulkan应用程序运行时编译GLSLShader文件的方法放在现在已经是非常的麻烦。现在新版的VulkanSDK(1.3.X以上)将GLSL编译为SPIR-V非常方便了,可以使用Google提供的S......
  • 批量合成m文件
    importos#设置文件夹路径,假设所有m文件都在这个文件夹内folder_path='path_to_your_folder'#设置输出文件的名称output_file='merged_files.txt'#打开输出文件准备写入withopen(output_file,'w')asoutfile:#遍历文件夹中的所有文件forfilename......
  • [ Linux 命令基础 ] Linux 命令大全-命令前置知识-系统管理-文件和目录管理-文本处理
    ......
  • 告别头文件,编译效率提升 42%!C++ Modules 实战解析 | 干货推荐
    编者按:AlibabaCloudLinux(简称“Alinux”)是目前阿里云上占比第一的操作系统。2021年,龙蜥以Alinux产品为基础发布了AnolisOS8正式版。本文中,阿里云智能集团开发工程师李泽政以Alinux为操作环境,讲解模块相比传统头文件有哪些优势,并通过若干个例子,学习如何组织一个C++模......
  • GIS融合之路(八)-如何用Cesium直接加载OSGB文件(不用转换成3dtiles)
    系列传送门:山海鲸可视化:GIS融合之路(一)技术选型CesiumJS/loaders.gl/iTowns?山海鲸可视化:GIS融合之路(二)CesiumJS和ThreeJS深度缓冲区整合山海鲸可视化:GIS融合之路(三)CesiumJS和ThreeJS相机同步山海鲸可视化:GIS融合之路(四)如何用CesiumJS做出CesiumForUnreal的效果山海鲸可视化......
  • 根据后缀名把Excel文件转换成可以插入MongoDB数据库的数据
    importpandasaspdimportosdefconvert_file_to_json(file_path):#检查文件扩展名并读取文件_,file_extension=os.path.splitext(file_path)iffile_extension.lower()=='.csv':df=pd.read_csv(file_path)eliffile_extension.lower......
  • KTL (0.9.2,通达信mdt文件)
    K,K线,Candle蜡烛图。T,技术分析,工具平台L,公式Language语言使用c++14,Lite小巧简易。项目仓库:https://github.com/bbqz007/KTL    国内仓库:https://gitee.com/bbqz007/KTL 当前0.9.2新添加功能基于QCharts跟通达信mdt数据文件。1. 数据分析工具,AlgoSysDataTool.cpp添加Q......
  • 汇编语言-CALL和RET指令
    call和ret都是转移指令,修改Ip或同时修改csip。1.ret和retfret指令用栈中的数据,修改ip的内容。从而实现近转移retf指令用栈中的数据,修改cs和ip的内容,从而实现远转移。CPU执行ret指令时,进行下面两步操作(IP)=((SS)*16+(SP))(SP)=(SP)+2相当于POPIPCPU执行retf指令时进行下......
  • Linux文件系统属性解读
    原文分享:https://bbs.deepin.org/post/281192一、ls-ihl 每一列的含义二、inode和block理解inode要从文件存储说起。文件存储在硬盘上,硬盘的最小存储单位叫做“扇区”(Sector),每个扇区存储512字节(相当于0.5KB)。操作系统读取硬盘的时候,不会一个扇区一个扇区地读取,这样效率太......
  • springboot将文件处理成压缩文件
    前言在工作我们经常会出现有多个文件,为了节省资源会将多个文件放在一起进行压缩处理;为了让大家进一步了解我先将springboot处理的方法总结如下,有不到之处敬请大家批评指正!一、文件准备:https://qnsc.oss-cn-beijing.aliyuncs.com/crmebimage/public/product/2024/11/12/be35321......