吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4673|回复: 5
收起左侧

[Android 原创] 菜鸟读capstone与keystone源码入门

[复制链接]
L剑仙 发表于 2020-3-31 15:55
本帖最后由 L剑仙 于 2020-3-31 19:00 编辑

原文发自看雪论坛https://bbs.pediy.com/thread-258473.htm

菜鸟最近想入门ollvm分析,得先找个反编译器,看了斑竹大佬的各种开源汇编、反汇编引擎的非专业比较https://bbs.pediy.com/thread-205590.htm,决定入门capstone
搜了搜网上,可能这玩意太简单,大佬都是直接用,只有c的入门,没啥python的入门帖子,菜鸟对各种函数参数还是没搞太明白,只好自己看下源码,记录一下用法。。。
一、capstone反汇编引擎,最重要功能是把二进制转化为汇编语言,关键代码在capstone 包里的__init__.py
最重要的2个类Cs和CsInsn
Cs类的disasm是最重要的反汇编函数,我得先搞明白它的参数和返回
[Python] 纯文本查看 复制代码
# Disassemble binary & return disassembled instructions in CsInsn objects
    def disasm(self, code, offset, count=0):
        all_insn = ctypes.POINTER(_cs_insn)()#_cs_insn缩写自_capstone_instruction, 是一个c的结构体;这句创建all_insn指针,指向_cs_insn类型
        '''if not _python2:
            print(code)
            code = code.encode()
            print(code)'''
        # Hack, unicorn's memory accessors give you back bytearrays, but they
        # cause TypeErrors when you hand them into Capstone.
        if isinstance(code, bytearray):
            code = bytes(code)
        res = _cs.cs_disasm(self.csh, code, len(code), offset, count, ctypes.byref(all_insn))#通过c函数cs_disasm解析code,获得一个all_insn引用(byref类似于pointer,返回一个引用)
        if res > 0:
            try:
                for i in range(res):
                    yield CsInsn(self, all_insn[i])#看下构造函数def __init__(self, cs, all_info)可以看出self._raw=all_info=all_insn[i],把all_insn这个_cs_insn结构传递给了_raw,所以_raw储存了address,mnemonic,op_str等关键信息
            finally:
                _cs.cs_free(all_insn, res)#通过yield输出完CsInsn实例后释放
        else:
            status = _cs.cs_errno(self.csh)
            if status != CS_ERR_OK:
                raise CsError(status)
            return
            yield

函数原型disasm(self, code, offset, count=0),code就是二进制binary,可以是一个字符串,也可以是打开的文件,offset是偏移

   
它调用了封装的c函数 cs_disasm,先通过c函数cs_disasm解析code,获得一个all_insn引用,然后通过过yield 输出CsInsn 的实例
看一下CsInsn类的__init__,可以看到,类型为ctypes.POINTER转换的_cs_insn这个c结构体的all_insn引作为all_info参数被传递给了CsInsn类的_raw字段
[Python] 纯文本查看 复制代码
def __init__(self, cs, all_info):
        self._raw = copy_ctypes(all_info)#这个_raw的值就是disasm函数获得的all_insn引用
        self._cs = cs
        if self._cs._detail and self._raw.id != 0:
            # save detail
            self._raw.detail = ctypes.pointer(all_info.detail._type_())
            ctypes.memmove(ctypes.byref(self._raw.detail[0]), ctypes.byref(all_info.detail[0]), ctypes.sizeof(type(all_info.detail[0])))

而这个结构体定义如下,最重要的几个字段address地址,mnemonic操作码,op_str操作数,它内部的detail也是一个c结构体
[Python] 纯文本查看 复制代码
class _cs_insn(ctypes.Structure):
    _fields_ = (
        ('id', ctypes.c_uint),
        ('address', ctypes.c_uint64),
        ('size', ctypes.c_uint16),
        ('bytes', ctypes.c_ubyte * 16),
        ('mnemonic', ctypes.c_char * 32),
        ('op_str', ctypes.c_char * 160),
        ('detail', ctypes.POINTER(_cs_detail)),
    )
class _cs_detail(ctypes.Structure):
    _fields_ = (
        ('regs_read', ctypes.c_uint16 * 12),
        ('regs_read_count', ctypes.c_ubyte),
        ('regs_write', ctypes.c_uint16 * 20),
        ('regs_write_count', ctypes.c_ubyte),
        ('groups', ctypes.c_ubyte * 8),
        ('groups_count', ctypes.c_ubyte),
        ('arch', _cs_arch),
    )

获得这些字段的方法在CsInsn里已经封装好了,id,address,size,bytes,mnemonic,op_str这些基本直接从_raw里面获取或者简单类型转换一下
detail这个c结构体包含一些额外的功能:
regs_read,字面理解是,返回存储所有读取的隐式寄存器的list,实测只有pc,lr,sp和状态寄存器会被存储在list中
regs_write,字面理解是,返回存储所有写入的隐式寄存器的list,实测只有pc,lr,sp和状态寄存器会被存储在list中
regs_access,合并上面2个的结果

举几个例子(节选自某个so的指令)
itttt   eq的 regs_write是[9],代表[itstate]
popeq.w {r8, sb, sl, fp}的 regs_read 与regs_write是[12][12],代表[sp] [sp]
cmp     r1, r0的 regs_write是[3],代表[cpsr]



groups,返回存储instruction属于的groups的list,常用的有jump跳转功能组,call函数调用功能组,ret返回功能组,int中断功能组


举几个例子
blx     #0x2274 的groups是[7, 150, 138, 149, 2, 1],分别代表[branch_relative,thumb,v5t,notmclass,call,jump]subs    r0, r1, r0的groups是[150, 151],分别代表[thumb,thumb1only ]
beq.w   #0x41ce 的groups是[7, 152, 1],分别代表[branch_relative,thumb2 ,jump ]

[Python] 纯文本查看 复制代码
# Common instruction groups - to be consistent across all architectures.
CS_GRP_INVALID = 0  # uninitialized/invalid group.
CS_GRP_JUMP    = 1  # all jump instructions (conditional+direct+indirect jumps)
CS_GRP_CALL    = 2  # all call instructions
CS_GRP_RET     = 3  # all return instructions
CS_GRP_INT     = 4  # all interrupt instructions (int+syscall)
CS_GRP_IRET    = 5  # all interrupt return instructions
CS_GRP_PRIVILEGE = 6  # all privileged instructions

举个例子,如果我们想筛选出所有符合跳转却不是调用的instruction,怎么做呢
[Asm] 纯文本查看 复制代码
    if (1 in i.groups and 2 not in i.groups):
        print("0x%x:\t%s\t%s\n" %(i.address, i.mnemonic, i.op_str))
        print("\t%s\n" %( i.groups))


后面还有几个基于上面字段的计数判断,没那么常用了,列举一个operand types
op_count(op_type)返回相同op_type的operand个数,举个例子
      popeq   {r4, r5, r6, r7, pc}的op_count(1)=op_count(CS_OP_REG )=5, 也就是操作数是寄存器的计数为5
      blx     #0x2274的op_count(2)=op_count(CS_OP_IMM )=1, 也就是操作数是立即寻址的计数为1

[Python] 纯文本查看 复制代码
# Common instruction operand types - to be consistent across all architectures.
CS_OP_INVALID = 0
CS_OP_REG = 1
CS_OP_IMM = 2
CS_OP_MEM = 3
CS_OP_FP  = 4

简单食用一下
[Asm] 纯文本查看 复制代码
#创建输入
bin= open('/src/main/lib/armeabi/libshell-super.2019.so','rb').read()
start=0x0000307C 
end=0x00004df4
#导入capstone新建Cs实例,安卓一般都是arm的
import capstone
cs = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
cs.detail = True#打开了detail 才有detail的功能
#打印地址,操作码,操作数
for i in cs.disasm(bin[start:end],start):
    print("0x%x:\t%s\t%s\n" %(i.address, i.mnemonic, i.op_str))
#打印groups  
    # print("\t%s\n" %( i.groups))
    # for a in i.groups:
    #    print(i.group_name(a))
#打印regs_read与regs_write     
    print("0x%x:\t%s\t%s\n" %(i.address, i.regs_read, i.regs_write))
    for a in i.regs_read:
        print("regs_read:"+i.reg_name(a))
    for a in i.regs_write:
        print("regs_write:"+i.reg_name(a))

__init__.py里面还有一个_dummy_cs类,提供了两个类似disasm但执行更快的反汇编函数,当然得到的信息也更少,我们可以选择使用
cs_disasm_quick  比disasm快一点

cs_disasm_lite  只返回(address, size, mnemonic, op_str)4个最重要的数据,比上面再快一点

二、下面学习keystone,最重要功能是把汇编代码变成二进制,关键代码在keystone包里的keystone.py,最关键的自然是asm函数了
原型def asm(self, string, addr=0, as_bytes=False) ,string是汇编字符串,addr开始地址
[Python] 纯文本查看 复制代码
    def asm(self, string, addr=0, as_bytes=False):
        encode = POINTER(c_ubyte)()
        encode_size = c_size_t()
        stat_count = c_size_t()
        if not isinstance(string, bytes) and isinstance(string, str):
            string = string.encode('ascii')

        status = _ks.ks_asm(self._ksh, string, addr, byref(encode), byref(encode_size), byref(stat_count))#这里调用c函数ks_asm获得encode,encode_size的引用
        if (status != 0):
            errno = _ks.ks_errno(self._ksh)
            raise KsError(errno, stat_count.value)
        else:
            if stat_count.value == 0:
                return (None, 0)
            else:
                if as_bytes:
                    encoding = string_at(encode, encode_size.value)
                else:
                    encoding = []
                    for i in range(encode_size.value):
                        encoding.append(encode[i]) #把所有encode附加到encoding作为返回值

                _ks.ks_free(encode)
                return (encoding, stat_count.value)

如果string是str,就先ascii编码一下,然后调用c函数ks_asm处理,这个函数在keystone.dll里
status = _ks.ks_asm(self._ksh, string, addr, byref(encode), byref(encode_size), byref(stat_count))#这里调用c函数ks_asm处理参数string获得encode,encode_size的引用
最后再看一下Ks类的__init__,通过arch和mode定义Ks实例,比如对于arm就是ks=keystone.Ks(keystone.KS_ARCH_ARM, keystone.KS_MODE_ARM)
[Python] 纯文本查看 复制代码
    def __init__(self, arch, mode):
        # verify version compatibility with the core before doing anything
        (major, minor, _combined) = ks_version()
        if major != KS_API_MAJOR or minor != KS_API_MINOR:#先判断下当前版本和核心api是否匹配
            self._ksh = None
            # our binding version is different from the core's API version
            raise KsError(KS_ERR_VERSION)

        self._arch, self._mode = arch, mode
        self._ksh = c_void_p()
        status = _ks.ks_open(arch, mode, byref(self._ksh))#根据参数arch和mode通过c函数ks_open打开ks_engine,获得_ksh的引用
        if status != KS_ERR_OK:
            self._ksh = None
            raise KsError(status)

        if arch == KS_ARCH_X86:
            # Intel syntax is default for X86
            self._syntax = KS_OPT_SYNTAX_INTEL
        else:
            self._syntax = None

很明显,在__init__函数里面先通过c函数ks_open打开ks引擎获得一个引用,才能调用它内部的asm函数,在__del__里还会调用ks_close关闭这几个函数的原型在这里

[Python] 纯文本查看 复制代码
# setup all the function prototype
def _setup_prototype(lib, fname, restype, *argtypes):
    getattr(lib, fname).restype = restype
    getattr(lib, fname).argtypes = argtypes

kserr = c_int
ks_engine = c_void_p
ks_hook_h = c_size_t

_setup_prototype(_ks, "ks_version", c_uint, POINTER(c_int), POINTER(c_int))
_setup_prototype(_ks, "ks_arch_supported", c_bool, c_int)
_setup_prototype(_ks, "ks_open", kserr, c_uint, c_uint, POINTER(ks_engine))
_setup_prototype(_ks, "ks_close", kserr, ks_engine)
_setup_prototype(_ks, "ks_strerror", c_char_p, kserr)
_setup_prototype(_ks, "ks_errno", kserr, ks_engine)
_setup_prototype(_ks, "ks_option", kserr, ks_engine, c_int, c_void_p)
_setup_prototype(_ks, "ks_asm", c_int, ks_engine, c_char_p, c_uint64, POINTER(POINTER(c_ubyte)), POINTER(c_size_t), POINTER(c_size_t))
_setup_prototype(_ks, "ks_free", None, POINTER(c_ubyte))


[Python] 纯文本查看 复制代码
from keystone import *
ks = Ks(KS_ARCH_ARM, KS_MODE_ARM)
code=b"sub r1, r2, r5"
encoding, count =ks.asm(code)
print("%s = [ " % code, end='')
for i in encoding:
    print("%02x " % i, end='')
print("]")


可以看到汇编语句转换成了16进制b'sub r1, r2, r5' = [ 05 10 42 e0 ]
总结一下,
capstone的disasm把文件的输入转换成CsInsn实例,通过CsInsn实例 可以获得汇编代码的address地址,mnemonic操作码,op_str操作数等重要信息,用于分析
就是[ 05 10 42 e0 ]→sub r1, r2, r5

keystone的asm把汇编代码转换成输出的16进制,用于patch打包
就是'sub r1, r2, r5'→ [ 05 10 42 e0 ]


ps:原来总共就封装了俩c函数,怪不得大佬懒得写教程,我这个彩笔居然还看了半天,记录下笔记,免得下次再忘了。。。。。。
参考:
https://bbs.pediy.com/thread-205590.htm
https://xz.aliyun.com/t/5753

免费评分

参与人数 2吾爱币 +1 热心值 +1 收起 理由
来自天堂的问候 + 1 一见到阅读源码的,不管是啥,就感觉很牛逼
liansen + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

liansen 发表于 2020-3-31 16:16
表示看不懂
来自天堂的问候 发表于 2020-3-31 16:49
Aaaaaaax 发表于 2020-3-31 16:58
wangyoo 发表于 2020-4-1 15:39
膜拜大佬
BMK 发表于 2022-8-2 11:19
膜拜大佬
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-27 03:19

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表