pyc文件花指令

news/2024/11/13 21:53:59/文章来源:https://www.cnblogs.com/PaperPlaneFly/p/18544903

pyc花指令

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

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

单重叠指令:

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

 0 JUMP_ABSOLUTE        [71 04]     5 2 PRINT_ITEM           [47 --]4 LOAD_CONST           [64 10]     166 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) // 4while j != 10:Key1[j] = ret[i:i+4]j = j + 1i = i + 4keyCheck = ''
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, disNOP = 9HAVE_ARGUMENT = 90JUMP_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 = 115CONTINUE_LOOP = 119
FOR_ITER = 93RETURN_VALUE = 83used_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 opcodedef getopcode(opname):   return opcode.opname.index(opname)NOP = getopcode('NOP')# HAVE_ARGUMENT = getopcode('HAVE_ARGUMENT')  # py2.7JUMP_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 aafilename = '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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/833165.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Hive的分区和排序

一、Hive的分区(十分重要) 1、分区是什么 答:我们可以把一个大的文件分隔成一个个小的文件,这样每次操作一个小文件就很方便了 2、为什么要进行分区 答:通过分区,当我们查询的时候,可以只扫描与条件相关的分区,这样做,避免了全局扫描,加快查询速度 1、静态分区(SP) 静…

项目冲刺4-3

仓库地址:https://github.com/bitpurleclude/GDUT-Goofish.git这个作业属于哪个课程 (https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/join?id=CfDJ8AOXHS93SCdEnLt5HW8VuxT_rAhbZKO3TfjMmbv1B0Re5Fp2d0_QACha2ZoYZ4fxF-ZKCCAhfJl7B8mvCfesLYE02X8T6kx_2R8w0SR-ykrgDVRKW…

【Linux】git note -v command not found

安装note.js Node.Js中文网 『Win+E』『此电脑』右键点击『属性』『高级系统设置』-『环境变量』『系统变量』-『NOTE_PATH』“C:\Program Files\nodejs”『用户变量』-『Path』“C:\Program Files\nodejs”『Win+R』重新启动控制台『cmd』-“$NOTE -v” 双击重新启动『Git Ba…

2024.11.13 DP题单

录制唱片 你刚刚继承了流行的 “破锣摇滚” 乐队录制的尚未发表的 \(N\)(\(1\leq N\leq 20\))首歌的版权。你打算从中精选一些歌曲,发行 \(M\)(\(1\leq M\leq 20\))张 CD。每一张 CD 最多可以容纳 \(T\)(\(1\leq T\leq 20\))分钟的音乐,一首歌不能分装在两张 CD 中。CD…

基于HASM模型的高精度建模matlab仿真

1.程序功能描述 本课题主要使用HASM进行高精度建模,主要对HASM模型进行介绍以及在实际中如何进行简化实现的。HASM原始的模型如下所示: 2.测试软件版本以及运行结果展示MATLAB2022A版本运行 3.核心程序%第一类基本变量E(i,j) = 1 + (( f(i,j+1,n) - f(i,j-1,n) )/( …

CICD04 Jenkins容器化CICD实现及分布式构建, 流水线Pipeline ubuntu使用

2.14.3 案例: 基于 Docker 插件实现自由风格任务实现 Docker 镜像 制作 不如前面的直接脚本编写灵活 2.14.3.2 安装插件 docker-build-step jenkins上安装 docker-build-step 插件#选择jenkins使用的docker服务 #左侧系统管理,右侧系统配置,Docker Builder下Docker URL输入 u…

数据类型和运算符

数据类型 动态类型编程语言运行时判断静态类型的编程语言 : Go 、C 、在开发的时候,就需要给一些定义的变量赋值空间大小。C 需要自己去开辟这个空间数据类型 : 每种在Go语言中出现的基本数据类型,会有一个默认的空间大小。 1、布尔类型数据 布尔型的值只可以是常量 true 或…

XXL JOB DockerCompose部署

官网给的方式是 Docker 命令启动,但是用起来太麻烦了,所以用DockerCompose 简化部署 创建数据库,导入 SQL SQL 脚本位置为/xxl-job/doc/db/tables_xxl_job.sql https://raw.githubusercontent.com/xuxueli/xxl-job/refs/heads/master/doc/db/tables_xxl_job.sql 编写 Docker…

CICD02 Jenkins安装,备份还原, 实现CICD核心功能 ubuntu使用

DevOps 之 CICD 服务器 Jenkins 1 Jenkins 部署与基本配置 1.2 Jenkins 安装和启动 1.2.1 Jenkins 的安装 Jenkins支持多种安装方法 1.包安装 2.JAVA的WAR文件 #要手动配置,不太方便 3.容器运行#系统要求 最低推荐配置:1.256MB可用内存2.1GB可用磁盘空间(作为一个Docker容…

CICD01 Git, GitLab, 部署方式 ubuntu使用

版本管理系统 Git 和 GitLab 1 DevOps 简介 1.3 持续集成、持续交付和持续部署 CICD CICD: 持续集成, 持续交付, 持续部署 1.6 常见的软件部署模式 生产中 蓝绿部署 和 金丝雀用的比较多 1.6.1 蓝绿部署 Blue-green Deployments 一个和生产环境一样的预发布环境, 和生产环…

jvm 垃圾回收算法的评价标准

如何实现回收的(核心思想): 1. 找到内存中存活的对象(与GC Root相关联) 2. 释放不再存活对象的内存,使得程序能再次利用这部分空间 --------------------------------------------------------------------------------- 垃圾回收算法的分类: -------- ----------------…

GO面试-切片

一、结构介绍 切片(Slice)在 Go 语言中,有一个很常用的数据结构,切片是一个拥有相同类型元素的可变长度的序列,它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。并发不安全。 切片是一种引用类型,它有三个属性:指针,长度和容量。 底层源码定义: type slice …