首页
社区
课程
招聘
基于lldb的trace脚本<辅助算法分析,算法还原,以及算法验证>
2021-7-29 16:40 94686

基于lldb的trace脚本<辅助算法分析,算法还原,以及算法验证>

2021-7-29 16:40
94686

由于工作的原因,在逆向分析中,经常遇到强混淆,vm虚拟化等加固方案。在分析中,痛不欲生。为了能在逆向过程中,还能安心的喝口咖啡,同时还能分析/还原各种高混淆/vm虚拟化的代码。参考函数追踪的原理,弄了个指令级的lldb-trace脚本。

[前瞻]:
平时分析过程中,难免遇到c/c++函数,以及objc函数。而objc函数中,又包含了retain,release...等函数。故而,在trace过程中,objc函数形如,retain,release..等等都直接忽略。而对于c++函数,类的构造函数,析构函数,系统函数等,也可以做过滤处理。对于objc函数objc_msgsend,我们可以做重点分析:在于你需不需要分析这个函数。而我不需要,所以,我只需要知道objc_msgsend函数的函数即可,而对应的函数实现,我就不trace了。

框架分析:

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

更新:

1,trace指令 参数的优化,绝大部分参数,都有默认值
2,tracing中,结束地址可以有多个(在某些混淆情况下,不确定结束地址在哪,可以多设置几个结束地址,用";"分割)
3,增加了暂停其他线程的 可选参数
4,增加了只trace本模块的 可选参数
5,增加了 进度 信息(防止以为脚本卡死…等的不耐心…从而关闭了 lldb
6,对msg_send 函数的参数解开发中…
7,增加了 对还原的算法检测脚本

脚本怎么使用:

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

2,导入lldbTrace.py脚本。(你可以设置,默认的 log 文件路径。如果不设置,默认和脚本同位置)

3,设置一个停止追踪的地址:(当前a函数,我把结束地址设为 最后地址,和 ret 地址。为了查看debug信息,我把log类型设置成了debug)

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

脚本在git上:

基于lldb的汇编指令级trace脚本

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

某手撸 aes 算法的trace结果:

基于trace脚本的基础上,对trace结果,以及实际分析中,做了一个 自动检测还原的函数 的脚本。利用脚本,不用每次都去动态调试,对照比较结果。

算法还原检测脚本 简介:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/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()

具体操作如下:

<a>,,在ida中,找到需要还原的算法(可以是汇编,也可以是伪代码),翻译成对应的python代码

<b>,利用lldbTrace.py脚本,把需要翻译的函数trace一哈。结果如下:

<c>,在ida伪代码中,切换到对应的汇编,对照trace结果,确定具体检测的地址。因为trace,当前打印的是上一句代码执行后的寄存器值。所以,我们把check的地址,定义为 0x10000393c,寄存器为 w8

<d>,,定义检测的 变量 Check_0x10000393c_w8 = ‘Check_0x10000393c_w8’ 。在翻译的代码中,添加检测函数 check_value(ret,Check_0x10000393c_w8) 。在解析tracelog文件之前,设置check的相关信息 set_trace_data(Check_0x10000393c_w8)

<e>,用python 调用此脚本,并传入 tracelog文件的路径。结果如下:

模块代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
#!/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)

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞10
打赏
分享
最新回复 (8)
雪    币: 4752
活跃值: (2923)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
LeadroyaL 1 2021-7-30 10:40
2
0
很不错
雪    币: 0
活跃值: (111)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
斑斓夜话 2021-8-6 18:40
3
0
牛哒!!!
雪    币: 3
活跃值: (588)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Qira 2022-5-20 15:28
4
0
感觉是很不错的东西, 竟然没啥人
雪    币: 90
活跃值: (381)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
青蓝梦 2022-7-28 12:29
5
0
666
雪    币: 198
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
天会晴心会暖 2022-7-28 15:59
6
0
挺好的,不过楼主可以看下frida的Stalker
我以前也自己写lldb脚本trace 直到我知道了Stalker
雪    币: 90
活跃值: (381)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
青蓝梦 2022-7-29 15:41
7
0
Thread: thread #1: tid = 0x1fb05, 0x000000010c353978 ModelName`___lldb_unnamed_symbol773000$$ModelName, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
Traceback (most recent call last):
  File "/Users/hexdump/Desktop/lldb-trace/lldbTrace.py", line 1628, in trace_block
    cur_block = blockObj.block_update_current_block() # 需要在 bl 以及 jmp 目标地址,和条件jmp的下一条指令处下断点,以及 增加对应的 block 到 blockList 中
  File "/Users/hexdump/Desktop/lldb-trace/lldbTrace.py", line 1253, in block_update_current_block
    if int(operands,16) > inst_addr :    
ValueError: invalid literal for int() with base 16: 'x10'
雪    币: 141
活跃值: (208)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zsorpei 2022-10-24 10:33
8
0
大佬,更新了吗
雪    币: 163
活跃值: (451)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
梦中的香 2022-10-26 11:56
9
0
zsorpei 大佬,更新了吗
第二版,还在更新中.....
游客
登录 | 注册 方可回帖
返回