1import logging
2from . import target
3import struct
4
5from xnu import *
6from core.operating_system import Armv8_RegisterSet, Armv7_RegisterSet, I386_RegisterSet, X86_64RegisterSet
7from core import caching
8
9""" these defines should come from an authoritative header file """
10CPU_TYPE_I386 = 0x00000007
11CPU_TYPE_X86_64 = 0x01000007
12CPU_TYPE_ARM = 0x0000000c
13CPU_TYPE_ARM64 = 0x0100000c
14CPU_TYPE_ARM64_32 = 0x0200000c
15
16def GetRegisterSetForCPU(cputype, subtype):
17    if cputype == CPU_TYPE_ARM64:
18        retval = Armv8_RegisterSet
19    elif cputype == CPU_TYPE_ARM64_32:
20        retval = Armv8_RegisterSet
21    elif cputype == CPU_TYPE_ARM:
22        retval = Armv7_RegisterSet
23    elif cputype == CPU_TYPE_I386:
24        retval = I386_RegisterSet
25    elif cputype == CPU_TYPE_X86_64:
26        retval = X86_64RegisterSet
27
28    """ crash if unknown cputype """
29
30    return retval.register_info['registers']
31
32
33class UserThreadObject(object):
34    """representation of userspace thread"""
35    def __init__(self, thr_obj, cputype, cpusubtype):
36        super().__init__()
37        self.thread = thr_obj
38        self.registerset = GetRegisterSetForCPU(cputype, cpusubtype)
39        self.thread_id = unsigned(self.thread.thread_id)
40        self.is64Bit = bool(cputype & 0x01000000)
41
42        if self.is64Bit:
43            if cputype == CPU_TYPE_X86_64:
44                self.reg_type = "x86_64"
45                self.saved_state = Cast(self.thread.machine.iss, 'x86_saved_state_t *').uss.ss_64
46            if cputype == CPU_TYPE_ARM64:
47                self.reg_type = "arm64"
48                self.saved_state = self.thread.machine.upcb.uss.ss_64
49        else:
50            if cputype == CPU_TYPE_I386:
51                self.reg_type = "i386"
52                self.saved_state = Cast(self.thread.machine.iss, 'x86_saved_state_t *').uss.ss_32
53            if cputype == CPU_TYPE_ARM:
54                self.reg_type = "arm"
55                self.saved_state = self.thread.machine.contextData.ss.uss.ss_32
56            if cputype == CPU_TYPE_ARM64_32:
57                self.reg_type = "arm64"
58                self.saved_state = self.thread.machine.upcb.uss.ss_64
59
60        logging.debug("created thread id 0x%x of type %s, cputype 0x%x"
61                      % (self.thread_id, self.reg_type, cputype))
62
63    def getRegisterValueByName(self, name):
64        if self.reg_type == 'arm64':
65            if name in ('x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9', 'x10', 'x11', 'x12', 'x13', 'x14', 'x15', 'x16', 'x17', 'x18', 'x19', 'x20', 'x21', 'x22', 'x23', 'x24', 'x25', 'x26', 'x27', 'x28'):
66                return unsigned(getattr(self.saved_state, 'x')[int(name.strip('x'))])
67
68            return unsigned(getattr(self.saved_state, name))
69
70        if self.reg_type == "x86_64":
71            if name in ('rip', 'rflags', 'cs', 'rsp', 'cpu'):
72                return unsigned(getattr(self.saved_state.isf, name))
73            return unsigned(getattr(self.saved_state, name))
74
75        if self.reg_type == "arm":
76            if name in ('r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10', 'r11', 'r12'):
77                retval = unsigned(getattr(self.saved_state, 'r')[int(name.strip('r'))])
78            else:
79                retval = unsigned(getattr(self.saved_state, name))
80            return retval
81
82        #TODO for i386
83
84    def getName(self):
85        return str(self.thread_id)
86
87    def getRegisterData(self, reg_num):
88        """ returns None if there is error """
89        if reg_num < 0 or reg_num >= len(self.registerset):
90            logging.warning("regnum %d is not defined for thread_id 0x%x" % (reg_num, self.thread_id))
91            return None
92        return self.getRegisterValueByName(self.registerset[reg_num]['name'])
93
94
95class UserProcess(target.Process):
96    """ Represent a user process and thread states """
97    def __init__(self, task):
98        self.task = task
99        self.proc = GetProcFromTask(task)
100        self.cache = {}
101        if self.proc is None:
102            raise ValueError("Task has no associated BSD process.")
103        dataregisters64bit = False
104        ptrsize = 4
105
106        if task.t_flags & 0x1:
107            ptrsize = 8
108        if task.t_flags & 0x2:
109            dataregisters64bit = True
110
111        self.cputype = unsigned(self.proc.p_cputype)
112        self.cpusubtype = unsigned(self.proc.p_cpusubtype)
113
114        super().__init__(self.cputype, self.cpusubtype, ptrsize)
115        dbg_message = "process:%s is64bit:%d ptrsize:%d cputype:0x%x cpusubtype:0x%x" % (hex(self.proc), int(dataregisters64bit), ptrsize, self.cputype, self.cpusubtype)
116        self.proc_platform = int(GetProcPlatform(self.proc))
117        if self.proc_platform == xnudefines.P_PLATFORM_MACOS:
118            self.hinfo['ostype'] = 'macosx'
119        elif self.proc_platform == xnudefines.P_PLATFORM_WATCHOS:
120            self.hinfo['ostype'] = 'watchos'
121        elif self.proc_platform == xnudefines.P_PLATFORM_TVOS:
122            self.hinfo['ostype'] = 'tvos'
123        else:
124            self.hinfo['ostype'] = 'ios'
125        dbg_message += " ostype:%s" % self.hinfo['ostype']
126
127        if str(kern.arch).lower().startswith('arm'):
128            addressing_bits = 64 - int(kern.globals.gT1Sz)
129            self.hinfo['addressing_bits'] = addressing_bits
130            dbg_message += " addressing_bits:%d" % addressing_bits
131
132        self.registerset = GetRegisterSetForCPU(self.cputype, self.cpusubtype)
133        logging.info(dbg_message)
134        self.threads = {}
135        self.threads_ids_list = []
136        logging.debug("iterating over threads in process")
137        for thval in IterateQueue(task.threads, 'thread *', 'task_threads'):
138            self.threads[unsigned(thval.thread_id)] = UserThreadObject(thval, self.cputype, self.cpusubtype)
139            self.threads_ids_list.append(unsigned(thval.thread_id))
140
141    def getRegisterDataForThread(self, th_id, reg_num):
142        if th_id not in self.threads:
143            logging.critical("0x%x thread id is not found in this task")
144            return ''
145        if reg_num < 0 or reg_num >= len(self.registerset):
146            logging.warning("regnum %d is not defined for thread_id 0x%x" % (reg_num, th_id))
147            return ''
148        value = self.threads[th_id].getRegisterData(reg_num)
149        return self.encodeRegisterData(value, bytesize=(self.registerset[reg_num]['bitsize'] // 8))
150
151    def getRegisterCombinedDataForThread(self, th_id):
152        if th_id not in self.threads:
153            logging.critical("0x%x thread id is not found in this task" % th_id)
154            return ''
155        cur_thread = self.threads[th_id]
156        retval = 'thread:%s;name:%s;' % (self.encodeThreadID(th_id), cur_thread.getName())
157        pos = 0
158        for rinfo in self.registerset:
159            name = rinfo['name']
160            format = "%02x:%s;"
161            value = cur_thread.getRegisterValueByName(name)
162            value_endian_correct_str = self.encodeRegisterData(value, bytesize=(rinfo['bitsize'] // 8))
163            retval += format % (pos, value_endian_correct_str)
164            pos += 1
165        return retval
166
167    def getThreadStopInfo(self, th_id):
168        if th_id not in self.threads:
169            logging.critical("0x%x thread id is not found in this task")
170            return ''
171        return 'T02' + self.getRegisterCombinedDataForThread(th_id) + 'threads:' + self.getThreadsInfo()+';'
172
173    def getRegisterInfo(self, regnum):
174        #something similar to
175        #"name:x1;bitsize:64;offset:8;encoding:uint;format:hex;gcc:1;dwarf:1;set:General Purpose Registers;"
176        if regnum >= len(self.registerset):
177            logging.debug("No register_info for number %d." % regnum)
178            return 'E45'
179
180        rinfo = self.registerset[regnum]
181        retval = ''
182        for i in list(rinfo.keys()):
183            i_val = str(rinfo[i])
184            if i == 'set':
185                i_val = 'General Purpose Registers'
186            retval += '%s:%s;' % (str(i), i_val)
187
188        return retval
189
190    def getProcessInfo(self):
191        retval = ''
192        #pid:d22c;parent-pid:d34d;real-uid:ecf;real-gid:b;effective-uid:ecf;effective-gid:b;cputype:1000007;cpusubtype:3;
193        #ostype:macosx;vendor:apple;endian:little;ptrsize:8;
194        pinfo = {'effective-uid': 'ecf', 'effective-gid': 'b', 'endian': 'little', 'vendor': 'apple'}
195        pinfo['pid'] = "%x" % (GetProcPIDForTask(self.task))
196        pinfo['parent-pid'] = "%x" % (unsigned(self.proc.p_ppid))
197        pinfo['ptrsize'] = str(self.ptrsize)
198        pinfo['ostype'] = 'macosx'
199        pinfo['cputype'] = "%x" % self.cputype
200        pinfo['cpusubtype'] = "%x" % self.cpusubtype
201        pinfo['real-uid'] = "%x" % (unsigned(self.proc.p_ruid))
202        pinfo['real-gid'] = "%x" % (unsigned(self.proc.p_rgid))
203        if str(kern.arch).find('arm') >= 0:
204            pinfo['ostype'] = 'ios'
205        for i in list(pinfo.keys()):
206            i_val = str(pinfo[i])
207            retval += '%s:%s;' % (str(i), i_val)
208        return retval
209
210    def readMemory(self, address, size):
211        cache_key = "{}-{}-{}".format(hex(self.task), hex(address), size)
212        cache_data = self.cache.get(cache_key, None)
213        if cache_data:
214            return self.encodeByteString(cache_data)
215        data = GetUserDataAsString(self.task, address, size)
216        if not data:
217            logging.error("Failed to read memory task:{: <#018x} {: <#018x} {:d}".format(self.task, address, size))
218        else:
219            self.cache[cache_key] = data
220        return self.encodeByteString(data)
221
222    def getSharedLibInfoAddress(self):
223        return unsigned(self.task.all_image_info_addr)
224