吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4475|回复: 6
收起左侧

[iOS 原创] 基于lldb的trace脚本以及验证还原的算法的脚本

[复制链接]
yangyss 发表于 2021-7-30 14:23
由于在工作的需要,在逆向分析中,常遇到强混淆,以及vm虚拟化加固方案。在分析的过程中,痛不欲生。为了在逆向分析过程中,能安心的喝口咖啡,同时还能还原出高度混淆/vm虚拟化的代码,参考函数追踪的原理,弄了个 指令级的 lldb-trace脚本。

[前瞻]

平时分析中,难免遇到 c/c++ 函数,objc函数。而objc函数中,又包含类如release,引用计数函数...等等。故而,在trace过程中,objc中的release等引用计数等函数,系统函数,都直接过滤。而 objc_msgsend 函数,可以做重点分析:取决于你要不要分析此次对应的函数的实现。当然,此次方案中,我是不需要的,所以,我只对objc_msgsend函数的函数名做了trace,而对应的函数实现,我就没trace了。

[框架分析]

1,对不同的平台,做不同的配置处理
2,忽略掉的函数,都放到 忽略函数列表中
3,objc_msgsend函数需做特殊处理,所以放在受保护的函数列表中
4,在trace中,需要读取上一条指令中用到寄存器,故用正则匹配出其所有的寄存器。

更新:

1,trace 参数优化,绝大部分参数都为默认参数,方便使用。
2,结束地址可以有多个(在某些混淆情况下,不确定结束地址到哪里,可以多设置几个结束地址,用";"分开)。
3,增加了 暂停其他线程 的 可选参数。
4,增加了 只 trace 本模块的 可选参数。
5,增加了 进度 信息(防止以为脚本卡死…等的不耐心…从而关闭了 lldb。此处用到了objc脚本,读取ASLR,如果平台不同,可以忽略掉ASLR)。
6,增加了检测 逆向还原的算法 的脚本(用脚本化,去验证你分析的算法是否ok,懒人做法~_~)。
7,对msg_send 函数的参数解析开发中…

怎么使用该脚本:

1,在你准备追踪的地方下断点:(我的断点,从breakpoint函数 si 进入到 a函数的 第一行)

1111

1111
2,导入lldbTrace.py脚本。(你可以设置,默认的 log 文件路径。如果不设置,默认和脚本同位置)
2222.png
3,设置一个停止追踪的地址:(当前a函数,我把结束地址设为 最后地址,和 ret 地址。为了查看debug信息,我把log类型设置成了debug)

333

333

4,设置好,直接回车,结果如下:
4444.png

脚本在git上:lldb-trace脚本仓库

脚本还有很多不完善的地方,需要慢慢优化。
不过利用trace 结果,能还原 手写的算法,以及强混淆 或者 某些 vm虚拟机。

免费评分

参与人数 8威望 +2 吾爱币 +105 热心值 +8 收起 理由
junjia215 + 1 + 1 用心讨论,共获提升!
ADAI131 + 1 + 1 热心回复!
Diedi + 1 请勿灌水,提高回帖质量是每位会员应尽的义务!
404undefined + 1 + 1 我很赞同!
gubs + 1 谢谢@Thanks!
qtfreet00 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
520xYZ + 1 + 1 谢谢@Thanks!
20210721 + 1 + 1 谢谢@Thanks!

查看全部评分

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

 楼主| yangyss 发表于 2021-8-3 07:01
本帖最后由 yangyss 于 2021-8-3 07:04 编辑

* 实在有点尴尬,不太会用,谢谢版主大大的修改~_~,一直没编辑过,预览也没达到自己的目标,后面内容,我在回帖中,补充吧。

--------------------------------------------------

还原的算法 的验证脚本:

1,脚本开发背景:

在逆向过程中,我们对照汇编代码/ida伪代码 等翻译了算法。然后需要知道这个算法是不是正确的。平常的做法,就是一边继续调试一遍验证。对于代码比较少的,调试几次,就能搞定最后搞定;而对于算法复杂点,代码多点的,调试一遍,就需要花费不少经历,何况需要调试多次,才能验证整个算法的ok。工作量很大的。。。

2,脚本原理:

1,不论是原算法,还是翻译的算法,其数据流必须一样。(所以,借助lldb-trace的结果,拿到数据流... 也就是拿到trace的log信息。。。)
2,由于python sys模块中,有settrace相关的函数。如果设置好sys.settrace(dest_fun()),在dest_fun()中,我们就能监控到相关的信息。原理就是 用 python 调试器,调试你的python代码....大概就是这样。(别的语言,不太熟悉,恰好python,简单方便,所以就选他了..)
3,由于test_fun()监控的是所有的 python代码,而我们只用监测某些变量,所以引入一个api,check_value(value,flag) 表示监控此value,同时,让注册的回调函数,监控他。
4,根据 flag,解析lldb-trace的log信息,拿到所有的数据流...
5,然后判断数据流,在算法中,是否一一对应。。。

3,怎么使用该脚本:

1,在ida中,找到需要还原的算法(可以是汇编,也可以是伪代码),翻译成对应的python代码
111
2,利用lldbTrace.py脚本,把需要翻译的函数trace一哈
3,在ida伪代码中,切换到对应的汇编,对照trace结果,确定具体检测的地址。因为trace,当前打印的是上一句代码执行后的寄存器值。所以,我们把check的地址,定义为 0x10000393c,寄存器为 w8.
222
4,定义检测的 变量 Check_0x10000393c_w8 = ‘Check_0x10000393c_w8’ 。在翻译的代码中,添加检测函数 check_value(ret,Check_0x10000393c_w8) 。在解析tracelog文件之前,设置check的相关信息 set_trace_data(Check_0x10000393c_w8)
5,用python 调用此脚本,并传入 tracelog文件的路径。结果如下:
333

4,具体代码:

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
#@file    :   wtpytracer.py
#@Time    :   2021/07/27 18:17:18
#@AuThor  :   wt

import re
import sys
import inspect
from collections import OrderedDict

class TracebackFancy:

    def __init__(self, traceback):
        self.t = traceback

    def getFrame(self):
        return FrameFancy(self.t.tb_frame)

    def getLineNumber(self):
        return self.t.tb_lineno if self.t is not None else None

    def getNext(self):
        return TracebackFancy(self.t.tb_next)

    def __str__(self):
        if self.t is None:
            return ""
        str_self = "%s @ %s" % (
            self.getFrame().getName(), self.getLineNumber())
        return str_self + "\n" + self.getNext().__str__()

class ExceptionFancy:

    def __init__(self, frame):
        self.etraceback = frame.f_exc_traceback
        self.etype = frame.exc_type
        self.evalue = frame.f_exc_value

    def __init__(self, tb, ty, va):
        self.etraceback = tb
        self.etype = ty
        self.evalue = va

    def getTraceback(self):
        return TracebackFancy(self.etraceback)

    def __nonzero__(self):
        return self.etraceback is not None or self.etype is not None or self.evalue is not None

    def getType(self):
        return str(self.etype)

    def getValue(self):
        return self.evalue

class CodeFancy:

    def __init__(self, code):
        self.c = code

    def getArgCount(self):
        return self.c.co_argcount if self.c is not None else 0

    def getFilename(self):
        return self.c.co_filename if self.c is not None else ""

    def getVariables(self):
        return self.c.co_varnames if self.c is not None else []

    def getName(self):
        return self.c.co_name if self.c is not None else ""

    def getFileName(self):
        return self.c.co_filename if self.c is not None else ""

class ArgsFancy:

    def __init__(self, frame, arginfo):
        self.f = frame
        self.a = arginfo

    def __str__(self):
        args, varargs, kwargs = self.getArgs(), self.getVarArgs(), self.getKWArgs()
        ret = ""
        count = 0
        size = len(args)
        for arg in args:
            ret = ret + ("%s = %s" % (arg, args[arg]))
            count = count + 1
            if count < size:
                ret = ret + ", "
        if varargs:
            if size > 0:
                ret = ret + " "
            ret = ret + "varargs are " + str(varargs)
        if kwargs:
            if size > 0:
                ret = ret + " "
            ret = ret + "kwargs are " + str(kwargs)
        return ret

    def getNumArgs(wantVarargs=False, wantKWArgs=False):
        args, varargs, keywords, values = self.a
        size = len(args)
        if varargs and wantVarargs:
            size = size + len(self.getVarArgs())
        if keywords and wantKWArgs:
            size = size + len(self.getKWArgs())
        return size

    def getArgs(self):
        args, _, _, values = self.a
        argWValues = OrderedDict()
        for arg in args:
            argWValues[arg] = values[arg]
        return argWValues

    def getVarArgs(self):
        _, vargs, _, _ = self.a
        if vargs:
            return self.f.f_locals[vargs]
        return ()

    def getKWArgs(self):
        _, _, kwargs, _ = self.a
        if kwargs:
            return self.f.f_locals[kwargs]
        return {}

class FrameFancy:

    def __init__(self, frame):
        self.f = frame

    def getCaller(self):
        return FrameFancy(self.f.f_back)

    def getLineNumber(self):
        return self.f.f_lineno if self.f is not None else 0

    def getCodeInformation(self):
        return CodeFancy(self.f.f_code) if self.f is not None else None

    def getExceptionInfo(self):
        return ExceptionFancy(self.f) if self.f is not None else None

    def getName(self):
        return self.getCodeInformation().getName() if self.f is not None else ""

    def getFileName(self):
        return self.getCodeInformation().getFileName() if self.f is not None else ""

    def getLocals(self):
        return self.f.f_locals if self.f is not None else {}

    def getArgumentInfo(self):
        return ArgsFancy(
            self.f, inspect.getargvalues(
                self.f)) if self.f is not None else None

class TracerClass:

    def callEvent(self, frame):
        pass

    def lineEvent(self, frame):
        pass

    def returnEvent(self, frame, retval):
        pass

    def exceptionEvent(self, frame, exception, value, traceback):
        pass

    def cCallEvent(self, frame, cfunct):
        pass

    def cReturnEvent(self, frame, cfunct):
        pass

    def cExceptionEvent(self, frame, cfunct):
        pass

tracer_impl = TracerClass()
data_dic = {}
old_trace_func = None

def parser_flag(flag):
    import re
    aa = re.split(r'_',flag)
    if len(aa) != 3 :
        return None,None
    return aa[1],aa[2]

class CheckFunctionTracer():
    def callEvent(self, frame):
        if 'check_value' == frame.getName():
            flag = frame.getArgumentInfo().getArgs()['check_flag']
            value = frame.getArgumentInfo().getArgs()['value']
            addr,register = parser_flag(flag)
            if addr in data_dic and register in data_dic[addr]:
                run_index = data_dic[addr][register]['run_index']
                data_len = len(data_dic[addr][register]['data'])
                if run_index >= data_len:
                    print('*** err : at address : {} . run_index : {} out of rang'.format(addr,run_index))
                    return
                if value == data_dic[addr][register]['data']['{}'.format(run_index + 1)] :
                    print('check : {} at {} times,match.'.format(addr,run_index + 1))
                    data_dic[addr][register]['run_index'] = run_index + 1

            # print("->>LoggingTracer : call " + frame.getName() + " from " + frame.getCaller().getName() + " @ " + str(frame.getCaller().getLineNumber()) + " args are " + str(frame.getArgumentInfo()))

# @ check_flag 为 携带了地址,和寄存器名称
# @ value 为当前需要 check 的值
# 在 sys.settracer设置的回调中,只接管此函数
def check_value(value,check_flag):
    pass

def set_trace_data(check_flag):
    global data_dic
    addr,register = parser_flag(check_flag)
    if not addr or not register :
        print('err : check_flag is wrong.')
        return

    if addr in data_dic:
        data_dic[addr][register] = {
            'data':{},
            'run_index':0  
        }
    else:
        addr_dic = {
            register:{
                'data':{},
                'run_index':0
            }
        }
        data_dic[addr] = addr_dic

def add_data_in_data_dic(addr,register,value):
    global data_dic
    cur_reg_dic = data_dic[addr][register]
    data_len = len(cur_reg_dic['data'])
    data_dic[addr][register]['data']['{}'.format(data_len + 1)] = value

def parser_trace_log_file(fileName):
    global data_dic
    file = open(fileName)
    while True:
        lines = file.readlines(100000)
        if not lines:
            break
        for line in lines:
            matchObj = re.match(r'\s*(\S+)\s+',line,re.M|re.I)
            if matchObj:
                addr = str(matchObj.group()).replace(' ','')
                if addr in data_dic:
                    reg = data_dic[addr]
                    for register in data_dic[addr].keys():
                        register_out = re.findall(register +r'  : (\S+)',line)
                        if register_out:
                            register_value = int(register_out[0],16)
                            add_data_in_data_dic(addr,register,register_value)

    file.close()
    # {'1234':{'1':0,"2":1}}  # flag : {...}  address:{'x0':{data:{},run_index:0},'x1':{data:{},run_index:0}}

def the_tracer_check_data(frame, event, args = None):
    global data_dic
    global tracer_impl

    code = frame.f_code

    func_name = code.co_name

    line_no = frame.f_lineno
    if tracer_impl is None:
        print('@@@ tracer_impl : None.')
        return None

    if event == 'call':
        tracer_impl.callEvent(FrameFancy(frame))

    return the_tracer_check_data

def enable(tracer_implementation=None):
    global tracer_impl,old_trace_func
    if tracer_implementation:
        tracer_impl = tracer_implementation  # 传递 工厂实力的对象
    old_trace_func = sys.gettrace()
    sys.settrace(the_tracer_check_data) # 注册回调到系统中

def check_run_ok():
    global data_dic
    for addr,addr_dic in data_dic.items():
        for _,reg_dic in addr_dic.items():
            if reg_dic['run_index'] == len(reg_dic['data']):
                print('->>> at {} check value is perfect.'.format(addr))
            else:
                print('*** err : at {} check {} times.'.format(addr,reg_dic['run_index']))

def disable():
    check_run_ok()
    global old_trace_func
    sys.settrace(old_trace_func)

demo,如下:

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
#@File    :   test.py
#@Time    :   2021/07/29 15:15:38
#@Author  :   wt

from wtpytracer import *

####################
## command :
##      python3 test.py ~/Desktop/1627533881instrace.log

## 定义需要检测的 变量 : flag + '_' + 地址 + '_' + 寄存器
Check_0x10000393c_w8 = 'Check_0x10000393c_w8'

### 翻译的测试代码
def f(x):
    ret = 0
    for index in range(x):
        ret = ret + index
        check_value(ret,Check_0x10000393c_w8) # check ret 和 0x10000393c 的 w8 的寄存器值
    return ret + x

if __name__ == '__main__':
    import sys
    args_list = sys.argv
    if len(args_list) != 2 :
        exit()
    file_name = args_list[1]

    try:
        set_trace_data(Check_0x10000393c_w8)
        parser_trace_log_file(file_name)
        enable(CheckFunctionTracer())
        f(5)
    finally:
        disable()

代码,在lldb-trace项目/tools/verifyAlgorithm/中,这里就不继续写git地址了。


222

222

111

111

333

333
涛之雨 发表于 2021-8-3 00:06
图片的插入有点问题,已经帮你编辑好了,
具体参见论坛的帮助

https://www.52pojie.cn/misc.php? ... 29&messageid=36

免费评分

参与人数 1热心值 +1 收起 理由
光头鸠摩智 + 1 感谢您帮我解毒疗伤。

查看全部评分

wang7788 发表于 2021-8-3 15:48
404undefined 发表于 2021-8-5 12:42
向大佬学习
hqc17 发表于 2021-8-11 17:24
感觉以后要用,谢谢楼主的分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-11-15 11:44

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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