Skip to content

Commit dcd7939

Browse files
committed
Initial commit
0 parents  commit dcd7939

File tree

4 files changed

+423
-0
lines changed

4 files changed

+423
-0
lines changed

LICENSE

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Copyright (c) 2017 Duo Security, Inc. All rights reserved.
2+
3+
Redistribution and use in source and binary forms, with or without
4+
modification, are permitted provided that the following conditions
5+
are met:
6+
7+
1. Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
2. Redistributions in binary form must reproduce the above copyright
10+
notice, this list of conditions and the following disclaimer in the
11+
documentation and/or other materials provided with the distribution.
12+
3. Neither the name of the copyright holder nor the names of its
13+
contributors may be used to endorse or promote products derived from
14+
this software without specific prior written permission.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
17+
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
18+
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19+
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
20+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Duo Labs IDAPython Repository
2+
3+
This IDAPython repository contains a few Python modules developed for use with IDA Pro from the researchers at Duo Labs. There are currently two modules being released. These modules are discussed in the blog post at <duo.sc/personal-protection>, and in the associated paper [Examining Personal Protection Devices
4+
Hardware & Firmware Research Methodology in Action](https://duo.com/assets/ebooks/Duo-Labs-Personal-Protection-Devices.pdf).
5+
6+
We also wish to thank two contributors that discussed ARM code detection heuristics during the development of this code: Luis Miras and Josh Mitchell.
7+
8+
+ Cortex M Firmware (cortex_m_firmware.py) --
9+
This Cortex M Firmware module grooms an IDA Pro database containing firmware from an ARM Cortex M microcontroller. This module will annotate the firmware vector table, which contains a number of function pointers. This vector table annotation will cause IDA Pro to perform auto analysis against the functions these pointers point to. The Cortex M Firmware module also calls into the Amnesia module to automate discovery of additional code in the firmware image using the Amnesia heuristics.
10+
11+
This example shows the most common usage of the code, for loading firmware images with the vector table located at address 0x0:
12+
```python
13+
from cortex_m_firmware import *
14+
cortex = CortexMFirmware(auto=True)
15+
```
16+
17+
This example shows how to annotate multiple vector tables in a firmware:
18+
```python
19+
from cortex_m_firmware import *
20+
cortex = CortexMFirmware()
21+
cortex.annotate_vector_table(0x4000)
22+
cortex.annotate_vector_table(0x10000)
23+
cortex.find_functions()
24+
```
25+
26+
+ Amnesia (amnesia.py) --
27+
Amnesia is an IDAPython module designed to use byte level heuristics to find ARM thumb instructions in undefined bytes in an IDA Pro database. Currently, the heuristics in this module find code in a few different ways. Some instructions identify and define new code by looking for comon byte sequences that correspond to particular ARM opcodes. Other functions in this module define new functions based on sequences of defined instructions.
28+
29+
```python
30+
class Amnesia:
31+
def find_function_epilogue_bxlr(self, makecode=False)
32+
def find_pushpop_registers_thumb(self, makecode=False)
33+
def find_pushpop_registers_arm(self, makecode=False)
34+
def make_new_functions_heuristic_push_regs(self, makefunction=False)
35+
def nonfunction_first_instruction_heuristic(self, makefunction=False)
36+
```

amnesia.py

+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import idc
2+
import ida_bytes
3+
import ida_funcs
4+
import ida_search
5+
import idautils
6+
7+
import re
8+
9+
class Amnesia:
10+
'''
11+
Filename: amnesia.py
12+
Description: IDA Python module for finding code in ARM binaries.
13+
Contributors: [email protected], [email protected], jmitch
14+
15+
Notes:
16+
------
17+
This code currently focuses more on Thumb detection.
18+
Lots more work to do here on ARM and Thumb detection.
19+
For ARM Cortex, this code works pretty well. It also
20+
gave some good results with ARM Mach-o binaries.
21+
22+
This code will undergo continued development. Development might break scripts.
23+
'''
24+
25+
printflag = False
26+
27+
def find_function_epilogue_bxlr(self, makecode=False):
28+
'''
29+
Find opcode bytes corresponding to BX LR.
30+
This is a common way to return from a function call.
31+
Using the IDA API, convert these opcodes to code. This kicks off IDA analysis.
32+
'''
33+
EAstart = idc.MinEA()
34+
EAend = idc.MaxEA()
35+
36+
ea = EAstart
37+
length = 2 # this code isn't tolerant to values other than 2 right now
38+
39+
fmt_string = "Possible BX LR 0x%08x == "
40+
for i in range(length):
41+
fmt_string += "%02x "
42+
43+
while ea < EAend:
44+
instructions = []
45+
for i in range(length):
46+
instructions.append(idc.Byte(ea + i))
47+
48+
if not ida_bytes.isCode(ida_bytes.getFlags(ea)) and instructions[0] == 0x70 and instructions[1] == 0x47:
49+
if self.printflag:
50+
print fmt_string % (ea, instructions[0], instructions[1])
51+
if makecode:
52+
idc.MakeCode(ea)
53+
ea = ea + length
54+
55+
def find_pushpop_registers_thumb(self, makecode=False):
56+
'''
57+
Look for opcodes that push registers onto the stack, which are indicators of function prologues.
58+
Using the IDA API, convert these opcodes to code. This kicks off IDA analysis.
59+
'''
60+
61+
'''
62+
thumb register list from [email protected]
63+
'''
64+
65+
thumb_reg_list = [0x00, 0x02, 0x08, 0x0b, 0x0e, 0x10, 0x1c, 0x1f, 0x30, 0x30, 0x38, 0x3e, 0x4e,
66+
0x55, 0x70, 0x72, 0x73, 0x7c, 0x7f, 0x80, 0x90, 0xb0, 0xf0, 0xf3, 0xf7, 0xf8, 0xfe, 0xff]
67+
68+
EAstart = idc.MinEA()
69+
EAend = idc.MaxEA()
70+
71+
ea = EAstart
72+
length = 2 # this code isn't tolerant to values other than 2 right now
73+
74+
fmt_string = "Possible Function 0x%08x == "
75+
for i in range(length):
76+
fmt_string += "%02x "
77+
78+
while ea < EAend:
79+
instructions = []
80+
for i in range(length):
81+
instructions.append(idc.Byte(ea + i))
82+
83+
if not ida_bytes.isCode(ida_bytes.getFlags(ea)) and instructions[0] in thumb_reg_list and (instructions[1] == 0xb5 or instructions[1]== 0xbd):
84+
if self.printflag:
85+
print fmt_string % (ea, instructions[0], instructions[1])
86+
if makecode:
87+
idc.MakeCode(ea)
88+
ea = ea + length
89+
90+
def find_pushpop_registers_arm(self, makecode=False):
91+
'''
92+
Find opcodes for PUSH/POP registers in ARM mode
93+
Using the IDA API, convert these opcodes to code. This kicks off IDA analysis.
94+
95+
bigup jmitch
96+
** ** 2d e9 and ** ** bd e8
97+
'''
98+
99+
EAstart = idc.MinEA()
100+
EAend = idc.MaxEA()
101+
102+
ea = EAstart
103+
length = 2 # this code isn't tolerant to values other than 2 right now
104+
105+
fmt_string = "Possible %s {REGS} 0x%08x == "
106+
for i in range(length):
107+
fmt_string += "%02x "
108+
109+
while ea < EAend:
110+
instructions = []
111+
for i in range(length):
112+
instructions.append(idc.Byte(ea + i))
113+
114+
# print BX LR bytes
115+
if not ida_bytes.isCode(ida_bytes.getFlags(ea)) and \
116+
(instructions[0] == 0xbd and instructions[1] == 0xe8):
117+
if self.printflag:
118+
print fmt_string % ("POP ", ea, instructions[0], instructions[1])
119+
if makecode:
120+
idc.MakeCode(ea)
121+
122+
if not ida_bytes.isCode(ida_bytes.getFlags(ea)) and \
123+
(instructions[0] == 0x2d and instructions[1] == 0xe9) \
124+
:
125+
if self.printflag:
126+
print fmt_string % ("PUSH", ea, instructions[0], instructions[1])
127+
if makecode:
128+
idc.MakeCode(ea)
129+
ea = ea + length
130+
131+
def make_new_functions_heuristic_push_regs(self, makefunction=False):
132+
'''
133+
After converting bytes to instructions, Look for PUSH instructions that are likely the beginning of functions.
134+
Convert these code areas to functions.
135+
'''
136+
EAstart = idc.MinEA()
137+
EAend = idc.MaxEA()
138+
ea = EAstart
139+
140+
while ea < EAend:
141+
if self.printflag:
142+
print "EA %08x" % ea
143+
144+
ea_function_start = idc.GetFunctionAttr(ea, idc.FUNCATTR_START)
145+
146+
# If ea is inside a defined function, skip to end of function
147+
if ea_function_start != idc.BADADDR:
148+
ea = idc.FindFuncEnd(ea)
149+
continue
150+
151+
# If current ea is code
152+
if ida_bytes.isCode(ida_bytes.getFlags(ea)):
153+
# Looking for prologues that do PUSH {register/s}
154+
mnem = idc.GetMnem(ea)
155+
156+
#
157+
if (
158+
mnem == "PUSH"
159+
):
160+
if makefunction:
161+
if self.printflag:
162+
print "Converting code to function @ %08x" % ea
163+
idc.MakeFunction(ea)
164+
165+
eanewfunction = idc.FindFuncEnd(ea)
166+
if eanewfunction != idc.BADADDR:
167+
ea = eanewfunction
168+
continue
169+
170+
nextcode = ida_search.find_code(ea, idc.SEARCH_DOWN)
171+
172+
if nextcode != idc.BADADDR:
173+
ea = nextcode
174+
else:
175+
ea += 1
176+
177+
def nonfunction_first_instruction_heuristic(self, makefunction=False):
178+
EAstart = idc.MinEA()
179+
EAend = idc.MaxEA()
180+
ea = EAstart
181+
182+
flag_code_outside_function = False
183+
self.printflag = False
184+
185+
while ea < EAend:
186+
187+
# skip functions, next instruction will be the target to inspect
188+
function_name = idc.GetFunctionName(ea)
189+
if function_name != "":
190+
191+
flag_code_outside_function = False
192+
193+
# skip to end of function and keep going
194+
# ea = idc.FindFuncEnd(ea)
195+
#if self.printflag:
196+
# print "Skipping function %s" % (function_name)
197+
198+
ea = ida_search.find_not_func(ea, 1)
199+
continue
200+
201+
elif ida_bytes.isCode(ida_bytes.getFlags(ea)):
202+
203+
# code that is not a function
204+
# get mnemonic to see if this is a push
205+
mnem = idc.GetMnem(ea)
206+
207+
if makefunction and (mnem == "PUSH" or mnem == "PUSH.W" or mnem == "STM" or mnem=="MOV"):
208+
if self.printflag:
209+
print "nonfunction_first_instruction_heuristic() making function %08x" % ea
210+
idc.MakeFunction(ea)
211+
flag_code_outside_function = False
212+
ea =ida_search.find_not_func(ea, 1)
213+
continue
214+
215+
else:
216+
if self.printflag:
217+
print "nonfunction_first_instruction_heuristic() other instruction %08x\t'%s'" % (ea, mnem)
218+
ea = idc.NextFunction(ea)
219+
continue
220+
221+
ea += 1
222+

0 commit comments

Comments
 (0)