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)

pyc文件花指令的更多相关文章

  1. Docker Kubernetes YAML文件常用指令

    YAML文件常用指令 配置文件说明: 定义配置时,指定最新稳定版API(当前为v1). 配置文件应该存储在集群之外的版本控制仓库中.如果需要,可以快速回滚配置.重新创建和恢复. 应该使用YAML格式编 ...

  2. .pyc文件的结构体PyCodeObject

    python执行程序时生成的pyc文件里面是,PyCodeObject 的结构体构成,每个命名空间(函数名.import模块等)都会形成一个core block,一个python程序的所有命名空间生成 ...

  3. Python之code对象与pyc文件(二)

    上一节:Python之code对象与pyc文件(一) 创建pyc文件的具体过程 前面我们提到,Python在通过import或from xxx import xxx时会对module进行动态加载,如果 ...

  4. Python之code对象与pyc文件(一)

    Python程序的执行过程 我们都知道,C语言在执行之前需要将源代码编译成可执行的二进制文件,也就是将源代码翻译成机器代码,这种二进制文件一旦生成,即可用于执行.但是,Python是否一样呢?或许很多 ...

  5. PYC文件简介

    PYC文件简介¶ 不说废话,这里说的pyc文件就是 Python 程序编译后得到的字节码文件 (py->pyc). 基本格式¶ pyc文件一般由3个部分组成: 最开始4个字节是一个Maigc i ...

  6. Python逆向(二)—— pyc文件结构分析

    一.前言 上一节我们知道了pyc文件是python在编译过程中出现的主要中间过程文件.pyc文件是二进制的,可以由python虚拟机直接执行的程序.分析pyc文件的文件结构对于实现python编译与反 ...

  7. 木马防杀 花指令 OllyDbg

    打开木马 入口地址 添加花指令 全0的地方,可以插入花指令 保存为可执行文件 随便选择几行,右击 保存文件

  8. 《python解释器源码剖析》第8章--python的字节码与pyc文件

    8.0 序 我们日常会写各种各样的python脚本,在运行的时候只需要输入python xxx.py程序就执行了.那么问题就来了,一个py文件是如何被python变成一系列的机器指令并执行的呢? 8. ...

  9. Python基础-1 python由来 Python安装入门 注释 pyc文件 python变量 获取用户输入 流程控制if while

    1.Python由来 Python前世今生 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚 ...

  10. .pyc文件是什么?

    一个.py文件就是一个模块,而模块名就是文件名,如module.py的模块名就是module.如果module.py文件里定义了一些函数和变量,而外部文件如test_module.py想使用这些函数或 ...

随机推荐

  1. manim边学边做--空心多边形

    空心的多边形Cutout是一种比较特殊的多边形,主要用于解决与形状.大小.位置等相关的数学问题. Cutout多边形可以定义物体表面的空洞或凹陷部分,从而更准确地模拟现实世界中的复杂形状. 比如,在P ...

  2. 2024-09-21:用go语言,给定一个字符串 s,字符串中的每个字符要么是小写字母,要么是问号‘?‘。对于一个仅包含小写字母的字符串t,我们定义cost(i)为在t的前i个字符中与t[i]相同的字

    2024-09-21:用go语言,给定一个字符串 s,字符串中的每个字符要么是小写字母,要么是问号'?'.对于一个仅包含小写字母的字符串t,我们定义cost(i)为在t的前i个字符中与t[i]相同的字 ...

  3. word在原有的方框里打勾

    按住键盘上的ALT键不放,然后在小键盘区输入"9745"这几个数字,最后松开 ALT 键,自动变成框框中带勾符号.

  4. Diffusion系列 - DDIM 公式推导 + 代码 -(三)

    DENOISING DIFFUSION IMPLICIT MODELS (DDIM) 从DDPM中我们知道,其扩散过程(前向过程.或加噪过程)被定义为一个马尔可夫过程,其去噪过程(也有叫逆向过程)也是 ...

  5. 最受DBA欢迎的数据库技术文档-巡检篇

    有人说,"数据库巡检是数据库运维领域最重要的工作".的确,为了保证数据库的稳定.安全运行,除了可以对数据库进行监控以及时知晓故障苗头,定期的"健康体检"则尤为重 ...

  6. 第三方的开源库FluentVaidation校验字段的

    内置的 using System.ComponentModel.DataAnnotations; 基本使用: 1. 安装包 FluentValidation.AspNetCOre 2. 注册服务 bu ...

  7. 001 C#配置多个版本Swagger说明

    1. AddSwaggerGen AddSwaggerGen 是配置多个版本的swagger的关键 Path.Combine 当前项目运行的路径 UseSwaggerUI 主要分为 2 步骤  : 1 ...

  8. 妙用编辑器:使用Notepad--的标记颜色功能更高效的阅读日志文件

    应用场景 在日常维护工作中,经常需要查看一些日志,以判断系统的运行状态或者进行问题定位,当系统出现故障时,一般都会有特殊的关键字,但对于浩如烟海的日志来说,识别这些关键字信息还是非常费眼力的,比如有如 ...

  9. Web渗透02_信息搜集

    以两个测试工具官方给出的用于工具实践的网站.一定不要拿在运营的网站做测试. http://testfire.net http://vulnweb.com DNS信息搜集 关注域名注册商,管理员的邮箱电 ...

  10. 国产东方通消息队列TongLINKQ8.1服务端安装步骤

    一.服务端安装 groupadd tlq # 新建组 useradd -m -g tlq tlq # 新建tlq用户并指定组tlq cd /home/tlq/ # 切换到安装目录并上传安装包 tar ...