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

由于工作的原因,在逆向分析中,经常遇到强混淆,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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值