上一篇讲解了如何加载一个Luac文件到IDA Pro当中,加载进入idb数据库的内容犹如切好洗净的食材,并不能粗暴的直接展示给用户,还需要IDA Pro中的处理器模块对内容进行下一步的反汇编渲染与指令功能注释,才能最终装盘食用。

处理器模块的工作就是:解析不同段的内容,确定代码段后,通过指定的指令格式解析与构造指令;确定指令使用的数据类型、寄存器与助记符;执行代码段的线性式代码反汇编;为指令标记注释与交叉引用等。

处理器模块架构

IDA Pro没有详细的文档描述如何开发处理器模块,最有效的学习途径是阅读IDA Pro程序中自带的开源的处理器模块代码。IDA Pro的处理器模块比文件加载器在架构上要更加晦涩难懂,实现起来也要复杂得多。

本篇写作时,对应的IDA Pro版本为国内众所周知的IDA Pro版本7.0,实验环境为macOS 10.12平台,处理器模块的开发选择使用Python。在IDA Pro软件的加载器目录(macOS平台):/Applications/IDAPro7.0/ida.app/Contents/MacOS/procs中,有着3个Python编写的处理器模块代码,分别是spu.py、ebc.py、msp430.py,如果安装了IDA Pro的开发SDK,在其中的module/script目录下也会找到这些模块,另外,还会包含一个proctemplate.py模板。

理论上,本节编写的Luac处理器模块,放到Windows等其他平台上,不需要进行任何的修改,也可以很好的工作。 本次参考使用到的代码是ebc.py模块,因为它的实现代码量不算最少,但在指令的解码处理上,代码更加直观。

处理器模块要求py中有一个定义为PROCESSOR_ENTRY()的方法,它的返回值是一个processor_t类型的类结构,IDA Pro通过检查这个类的字段,与回调它的方法,来完成指令的处理。一个精简的代码架构如下:

class ebc_processor_t(processor_t):

...

# ----------------------------------------------------------------------

def __init__(self):

processor_t.__init__(self)

self.PTRSZ = 4 # Assume PTRSZ = 4 by default

self.init_instructions()

self.init_registers()

...

...

def PROCESSOR_ENTRY():

return ebc_processor_t()

ebc_processor_t类中有很多的回调函数,它们都会在特定的场景下触发执行,所有的回调方法,可以在当前版本的IDA Pro的ida_idp.py文件中,查看processor_t的类型声明得知,不过可以发现,processor_t的声明是由swig自动生成的桥接到C的代码,看不出任何有价值的地方,在实际编写代码时,可能需要查看Python编写的处理器模块的回调函数注释,来理解回调的参数与使用场景,也可以直接查看processor_t类型在C语言中的声明,它的定义可以在SDK的include目录下的idp.hpp头文件中找到,在实现上,SDK中也包含了很多C语言编写的处理器模块,代码也很有参考价值。

这里的ebc_processor_t的__init__()方法中,会调用init_instructions()初始化处理器模块用到的指令,以及调用init_registers()初始化处理器模块用到的寄存器信息,这是一种通用的设置流程,我们在下面的代码中也采用这种方式完成Luac的相关初始化。

Luac处理器模块的实现

下面来动手实现Luac的处理器模块,同样的,它只支持基于Lua 5.2生成的Luac文件。 将ebc.py模块复制一份改名为loacproc.py。并修改ebc_processor_t为lua_processor_t,它的`_init()`代码不需要进行修改,代码如下:

def __init__(self):

processor_t.__init__(self)

self.PTRSZ = 4 # Assume PTRSZ = 4 by default

self.init_instructions()

self.init_registers()

self.PTRSZ描述了使用到的指针类型所占的字节大小,对于32位的Luac来说,它的值为4,通常只有在编写64位的程序处理器模块时,它的值才是8。init_instructions()与init_registers()分别用来初始化指令与寄存器列表,我们需要修改它的方法的实现部分。

在开始讲解指令与寄存器的修改前,我们先看看processor_t中需要修改的一些字段,它们的片断如下:

PLFM_LUAC = 99

class lua_processor_t(processor_t):

# IDP id ( Numbers above 0x8000 are reserved for the third-party modules)

id = PLFM_LUAC

# Processor features

flag = PR_DEFSEG32 | PR_USE64 | PRN_HEX | PR_RNAMESOK | PR_NO_SEGMOVE | PR_TYPEINFO

# Number of bits in a byte for code segments (usually 8)

# IDA supports values up to 32 bits

cnbits = 8

# Number of bits in a byte for non-code segments (usually 8)

# IDA supports values up to 32 bits

dnbits = 8

# short processor names

# Each name should be shorter than 9 characters

psnames = ['Luac']

# long processor names

# No restriction on name lengthes.

plnames = ['Lua Byte code']

# size of a segment register in bytes

segreg_size = 0

# Array of typical code start sequences (optional)

# codestart = ['\x60\x00'] # 60 00 xx xx: MOVqw SP, SP-delta

# Array of 'return' instruction opcodes (optional)

# retcodes = ['\x04\x00'] # 04 00: RET

# You should define 2 virtual segment registers for CS and DS.

# Let's call them rVcs and rVds.

# icode of the first instruction

instruc_start = 0

#

# Size of long double (tbyte) for this processor

# (meaningful only if ash.a_tbyte != NULL)

#

tbyte_size = 0

segstarts = {}

segends = {}

...

id字段是一个数值的ID值,用来标识处理器模块,IDA Pro中定义了一些已经存在的id,它们的定义在ida_idp.py中可以找到,如下所示:

# processor_t.id

PLFM_386 = 0 # Intel 80x86

PLFM_Z80 = 1 # 8085, Z80

PLFM_I860 = 2 # Intel 860

...

PLFM_EBC = 57 # EFI Bytecode

PLFM_MSP430 = 58 # Texas Instruments MSP430

PLFM_SPU = 59 # Cell Broadband Engine Synergistic Processor Unit

我们这里将其设置为PLFM_LUAC,只要定义它为一个与系统上不冲突的数值即可。

flag字段描述了处理器用到的一些特性,用样可以在ida_idp.py中可以查看processor_t.flag小节中的可选值,例如PR_USE64表示支持64位的寻址方式,PR_NO_SEGMOVE表示不支持段移动,即不允许调用move_segm()接口,PR_TYPEINFO表示支持类型信息,即支持在IDA Pro中载入til中的类型。

cnbits字段与dnbits字段表示对于代码段与非代码段,一个字节占用多少位,通常取值8。

psnames字段用来设置处理器模块的短名称,这里设置为”Luac”。还记得上一节如下的代码么:

idaapi.set_processor_type("Luac", SETPROC_ALL|SETPROC_FATAL)

当注册了该名称后,文件加载器就可以通过idaapi.set_processor_type()来设置该处理器模块了。

plnames字段是长名称,起到描述性的作用。

segreg_size字段描述段寄存器的大小,当前面的flag字段包含了PR_SEGS标志,则需要设置它的值,这里取值为0。

codestart与retcodes用于描述函数的开始与结束的指令特征,用于IDA Pro线性扫描时,自动生成函数信息。

instruc_start为指令列表的起始索引。

tbyte_size字段描述long double类型的字节大小,这里没有用到,设置为0即可。

segstarts与segends用来记录段的开始与结束地址,这两个字段在其他的代码回调处很有用。

接下来还需要设置一个assembler字段,描述了反汇编的一些信息。包括设置反汇编器的名称,各种数据类型的助记符,比如字节、字、双字通常设置为db、dw、dd,这在其他的处理器模块中常见。然后是各种保留关键字与逻辑操作的助记符,这些内容在luac_proc中,可以选择保留或者删除。

在init_instructions()的内部实现中,被要求设置一个class idef,该类型用于描述指令的具体信息,包括:指令的名称、解码回调程序、规犯标志、注释等。当然,也可以选择不实现它。在本例中,选择了使用idef辅助进行指令处理,它的定义如下:

class idef:

"""

Internal class that describes an instruction by:

- instruction name

- instruction decoding routine

- canonical flags used by IDA

"""

def __init__(self, name, cf, d, cmt = None):

self.name = name

self.cf = cf

self.d = d

self.cmt = cmt

为了方便解码指令,这里定义了一张指令表self.itable,它列出了Luac中涉及到的所有指令,如下所示:

self.itable = {

0x00: idef(name='MOVE', d=self.decode_MOVE, cf=CF_USE1 | CF_USE2, cmt=''),

0x01: idef(name='LOADK', d=self.decode_LOADK, cf=CF_USE1 | CF_USE2, cmt=self.cmt_LOADK),

0x02: idef(name='LOADKX', d=self.decode_LOADKX, cf=CF_USE1 | CF_USE2, cmt=''),

0x03: idef(name='LOADBOOL', d=self.decode_LOADBOOL, cf=CF_USE1 | CF_USE2 | CF_USE3, cmt=''),

0x04: idef(name='LOADNIL', d=self.decode_LOADNIL, cf=CF_USE1 | CF_USE2, cmt=''),

0x05: idef(name='GETUPVAL', d=self.decode_GETUPVAL, cf=CF_USE1 | CF_USE2, cmt=''),

...

0x26: idef(name='VARARG', d=self.decode_VARARG, cf=CF_USE1 | CF_USE2, cmt=''),

0x27: idef(name='EXTRAARG', d=self.decode_EXTRAARG, cf=CF_USE1, cmt=''),

}

CF_USE1与CF_USE2标志表示使用了第一个操作数与第二个操作数,与之类似的还有CF_JUMP,表示这是一条跳转类型的指令,CF_CALL表示这是一条call类型的指令,所有支持的标志可以在ida_idp.py的instruc_t.feature小节查看。

完成这张表后,需要使用它来填充处理器模块的instruc字段,代码如下:

# Now create an instruction table compatible with IDA processor module requirements

Instructions = []

i = 0

for x in self.itable.values():

d = dict(name=x.name, feature=x.cf)

if x.cmt != None:

d['cmt'] = x.cmt

Instructions.append(d)

setattr(self, 'itype_' + x.name, i)

i += 1

# icode of the last instruction + 1

self.instruc_end = len(Instructions) + 1

# Array of instructions

self.instruc = Instructions

# Icode of return instruction. It is ok to give any of possible return

# instructions

self.icode_return = self.itype_RETURN

instruc_end字段为指令列表的结束索引,它对应着前面的instruc_start字段。

instruc字段通过Instructions进行设置,它只取了指令的名称与标志两个字段。

icode_return字段指明可能的返回指令,itype_RETURN是前面通过setattr()设置的RETURN指令。

下面看看指令的解码部分,即前面self.itable中定义的如self.decode_MOVE与self.decode_LOADK部分。

self.decode_MOVE的实现如下:

def decode_MOVE(self, insn, a, b, c, ax, bx, sbx):

"""

OP_MOVE,/* A B R(A) := R(B) */

"""

insn.Op1.type = o_reg

insn.Op1.reg = a

insn.Op1.dtype = dt_dword

insn.Op2.type = o_reg

insn.Op2.reg = b

insn.Op2.dtype = dt_dword

return True

可以看到,实现方法上,主要是填充inst指令的两个操作数,因为它的最终展示形式形如:

MOVE R(A), R(B)

在填充时,除了指定它是否为寄存器类型o_reg外,还需要设置它的具体值a与b,当a为2,b为1时,它生成的反汇编代码为:

MOVE R2, R1

然后以LOADK指令为例,它的解码回调为self.decode_LOADK,代码如下:

def decode_LOADK(self, insn, a, b, c, ax, bx, sbx):

"""

OP_LOADK,/* A Bx R(A) := Kst(Bx) */

"""

insn.Op1.type = o_reg

insn.Op1.reg = a

insn.Op1.dtype = dt_dword

insn.Op2.type = o_displ

insn.Op2.reg = bx

insn.Op2.dtype = dt_dword

return True

这一次,Op2的类型不为o_reg,而是o_displ,这是指针类型的数据,这里在最终解码时,我们会判断它操作的是否为Upvalue,来进一步确定它是UpValue,还是Constant常量,如果是前者,我们输出时会指定U开头,如果是后者,输出时会指定K开头。至于具体的判断方法,则是将指令的Op的specval值设置为1。如decode_SETUPVAL()的实现:

def decode_SETUPVAL(self, insn, a, b, c, ax, bx, sbx):

"""

OP_SETUPVAL,/* A B UpValue[B] := R(A) */

"""

insn.Op1.type = o_reg

insn.Op1.reg = a

insn.Op1.dtype = dt_dword

insn.Op2.type = o_displ

insn.Op2.reg = b

insn.Op2.dtype = dt_dword

insn.Op2.specval = 1

return True

接下来就是一条条的实现每一条指令的解码回调,这就是一个体力活。

接着是初始化寄存器的部分,代码如下:

def init_registers(self):

"""This function parses the register table and creates corresponding ireg_XXX constants"""

# Registers definition

self.reg_names = [

# General purpose registers

# #define MAXSTACK 250

# >>> for i in xrange(250):

# ... print("\"R%d\"," % i)

"R0",

"R1",

"R2",

"R3",

"R4",

"R5",

"R6",

"R7",

"R8",

...

# Fake segment registers

"CS",

"DS"

]

# Constants definition

self.constant_names = [

# #define MAXSTACK 250

# >>> for i in xrange(250):

# ... print("\"K%d\"," % i)

"K0",

"K1",

"K2",

"K3",

"K4",

"K5",

"K6",

"K7",

"K8",

...

]

# Upvalues definition

self.upvalue_names = [

# #define MAXSTACK 250

# >>> for i in xrange(250):

# ... print("\"U%d\"," % i)

"U0",

"U1",

"U2",

"U3",

"U4",

"U5",

"U6",

"U7",

"U8",

...

]

# Create the ireg_XXXX constants

for i in xrange(len(self.reg_names)):

setattr(self, 'ireg_' + self.reg_names[i], i)

# Create the iconst_XXXX constants

for i in xrange(len(self.constant_names)):

setattr(self, 'iconst_' + self.constant_names[i], i)

# Create the iupval_XXXX constants

for i in xrange(len(self.upvalue_names)):

setattr(self, 'iupval_' + self.upvalue_names[i], i)

# Segment register information (use virtual CS and DS registers if your

# processor doesn't have segment registers):

self.reg_first_sreg = self.ireg_CS

self.reg_last_sreg = self.ireg_DS

# number of CS register

self.reg_code_sreg = self.ireg_CS

# number of DS register

self.reg_data_sreg = self.ireg_DS

主要是定义了寄存器名称表reg_names,常量名称表constant_names,UpValue名称表upvalue_names。以及为这些表各自设置名称属性。

完成了这两卡的初始化,接着就是实现处理器模块的回调了。重要的有,notify_ana(),作用是解码每一条指令,它的实现代码如下:

def notify_ana(self, insn):

"""

Decodes an instruction into insn

"""

# take opcode byte

b = insn.get_next_dword()

# the 6bit opcode

opcode = b & 0x3F

arg_a = GET_BITS(b, 6, 13)

arg_b = GET_BITS(b, 23, 31)

arg_c = GET_BITS(b, 14, 22)

arg_ax = GET_BITS(b, 6, 31)

arg_bx = GET_BITS(b, 14, 31)

arg_sbx = GET_BITS(b, 14, 31) - 131071

print("opcode:%x, a:%x, b:%x, c:%x, ax:%x, bx:%x, sbx:%d" % (opcode, arg_a, arg_b, arg_c, arg_ax, arg_bx, arg_sbx))

# opcode supported?

try:

ins = self.itable[opcode]

# set default itype

insn.itype = getattr(self, 'itype_' + ins.name)

except:

return 4

# call the decoder

return insn.size if ins.d(insn, arg_a, arg_b, arg_c, arg_ax, arg_bx, arg_sbx) else 0

解析32位的指令,取它的opcode、arg_a、arg_b、arg_c、arg_ax、arg_bx、arg_cx等值,然后根据不同的opcode索引查表,设置指令的itype字段,最后返回指令的长度即可。

notify_out_insn()用于输出完整指令,out_mnem()用于输出助记符,notify_out_operand()用于输出操作数,后两个回调是前一个回调的两个拆分,这几个回调加在一起,可以处理指令输出的全部细节。notify_out_insn()与out_mnem()的实现对于多数的反汇编引擎部分是一样的,这里不去细究,主要看看notify_out_operand(),它的实现如下:

def notify_out_operand(self, ctx, op):

"""

Generate text representation of an instructon operand.

This function shouldn't change the database, flags or anything else.

All these actions should be performed only by u_emu() function.

The output text is placed in the output buffer initialized with init_output_buffer()

This function uses out_...() functions from ua.hpp to generate the operand text

Returns: 1-ok, 0-operand is hidden.

"""

#print("notify_out_operand called. op:%x" % op.type)

optype = op.type

fl = op.specval

def_arg = is_defarg(get_flags(ctx.insn.ea), op.n)

if optype == o_reg:

ctx.out_register(self.reg_names[op.reg])

elif optype == o_imm:

# for immediate loads, use the transfer width (type of first operand)

if op.n == 1:

width = self.dt_to_width(ctx.insn.Op1.dtype)

else:

width = OOFW_32 if self.PTRSZ == 4 else OOFW_64

ctx.out_value(op, OOFW_IMM | width)

elif optype in [o_near, o_mem]:

r = ctx.out_name_expr(op, op.addr, idc.BADADDR)

if not r:

ctx.out_tagon(COLOR_ERROR)

ctx.out_btoa(op.addr, 16)

ctx.out_tagoff(COLOR_ERROR)

remember_problem(PR_NONAME, ctx.insn.ea)

elif optype == o_displ:

is_upval = fl

if is_upval:

ctx.out_register(self.upvalue_names[op.reg]) #Upvalues

else:

ctx.out_register(self.constant_names[op.reg]) #Constants

#if op.addr != 0 or def_arg:

# ctx.out_value(op, OOF_ADDR | (OOFW_32 if self.PTRSZ == 4 else OOFW_64) | signed | OOFS_NEEDSIGN)

else:

return False

return True

根据不同的操作数类型,调用不同的方法进行输出。ctx.out_register负责输出寄存器;ctx.out_value负责输出立即数;而对于UpValue与Constant的输出,这里借用了ctx.out_register来输出,只是使用了不同的名称组。

取这里,指令的基本的反汇编就算完成了,IDA Pro在应用该处理器模块时,会线性的扫描所有的CODE类型的代码段,进行反汇编处理。由于篇幅的原因,这篇就到此这止了,有兴趣的读者,可以在此基础上,实现函数的创建、代码与数据的交叉引用、自动添加注释等功能。最终实现的效果如图所示:

python反编译luac_Lua程序逆向之为Luac编写IDA Pro处理器模块相关推荐

  1. python反编译luac_Lua程序逆向之为Luac编写IDA Pro文件加载器

    距离上一次讲Lua程序逆向已经有一段时间了,这一次我们书接上回,继续开启Lua程序逆向系列之旅. 在软件逆向工程实践中,为第三方文件编写文件格式分析器与指令反汇编器是一种常见的场景.这一篇的主要目的是 ...

  2. Python反编译pyinstaller打包的exe文件 从0开始(未加密篇)

    因为pyinstaller方便.兼容性相对较好,所以我们会经常见到一些用pyinstaller打包的Python程序,在这里我们了解一下如何对这些打包好的exe文件进行反编译(即反编译出.py文件), ...

  3. 【原创】用 Python 反编译 Python 软件

    [原创]用 Python 反编译 Python 软件 标 题: [原创]用 Python 反编译 Python 软件 作 者: Ptero 时 间: 2010-04-21,16:28:27 链 接: ...

  4. python反编译Pyinstaller打包的可执行文件

    背景:最近在帮朋友写一款类似抢票的脚本,朋友有好几个脚本了,但是效果不理想,想让我帮忙,由于这种需要对接口进行详细了解,有些事件没有条件去抓包,然后我就对朋友已有的几个脚本动了心思.首先像这种爬虫类小 ...

  5. 手把手教你反编译小程序

    本次实验环境 操作系统: win10 10.0.19042 node: v14.17.0 微信开发者工具: Stable 1.05.2110290 前期准备 在电脑端安装模拟器工具,这里以夜神模拟器为 ...

  6. Refactor反编译C#程序

    两篇很不错reflector的教程,很有用~ Reflactor就不介绍了,.net下的免费反编译工具.Reflactor在某些情况下是很必须的,当遇到bug的时候,可以通过Reflactor看其内部 ...

  7. 反编译小程序得到源代码

    前言 最近在研究小程序吗,在git上找了一些阅读类的开源代码,拿到小程序代码,用java写了一套后台支撑,经过上线,部署,审核,域名,备案,服务器等等,算是接近了阅读类app的尾声,等有时间一一整理一 ...

  8. python 反编译

    python 反编译工具名称: Easy Python Decompiler 工具下载地址:http://sourceforge.net/projects/easypythondecompiler/? ...

  9. Python 反编译:pyinstxtractor工具和uncompyle6库的使用

    uncompyle6 现仅适用于 Python 2.4 到 3.8 版本 Python 3.9 及以上版本请参见我另外一篇博客: Python 反编译:pycdc工具的使用 ✅作者简介:人工智能专业本 ...

最新文章

  1. 鸟哥的Linux私房菜(基础篇)- 第二十章、启动流程、模块管理与 Loader
  2. Numpy-创建数组
  3. 微软解释 Edge 浏览器比 Chrome 更加安全的原因
  4. 【做题记录】 [JLOI2011]不等式组
  5. 联想拯救者y7000加内存条_关于2020款联想拯救者Y7000、R7000和Y7000P,r7000p选哪个好?看这里就对了...
  6. 【设计模式】第六章 观察者模式
  7. Gitl用户在组中有五种权限:Guest、Reporter、Developer、Master、Owner 解释
  8. 谷尼国际软件-企业竞争情报系统
  9. 现实版的“疑犯追踪”是如何开展的?
  10. CCF 区块链国际会议 统计 有哪些接收区块链论文的会议 (最全)
  11. 【暴强】200种好口碑便宜护肤品 - 健康程序员,至尚生活!
  12. 乐鑫ESP32-S3双核处理器,专为 AIoT 市场打造
  13. 实时大数据处理real-time big data processing (RTDP)框架:挑战与解决方案
  14. 小白兔写话_二年级写话小白兔
  15. 运放稳定性连载13:RO何时转变为ZO?(2)
  16. 1389:买零食【C++】
  17. C++ std::ios::tie
  18. 到月宫上挖土-嫦娥五号背后的航天系统工程
  19. Elixir-Atoms
  20. 《Mysql是怎样运行的》读书笔记之B+树索引的使用

热门文章

  1. 上海爱立信实习笔试面试
  2. 2022AI决策智能实践:美宜佳
  3. 【游戏技术】SourceMod 用法说明
  4. VII html+css
  5. 渗透测试之破解详细演示
  6. python实现统计文本当中单词数量
  7. oracle spa性能测试,SPA for 11g 分析性能
  8. 重庆-新加坡国际电竞嘉年华落幕“文旅+”助推建设国际旅游目的地
  9. 人工智能 3.确定性推理方法
  10. 被指将赴美上市的雪球:累计融资近3亿美元,股东股权已全部出质