Package rekall :: Package plugins :: Package linux :: Module zsh
[frames] | no frames]

Source Code for Module rekall.plugins.linux.zsh

  1  #  Extracts Zsh command history 
  2  # 
  3  #    Copyright (c) 2017, Frank Block, ERNW GmbH <fblock@ernw.de> 
  4  # 
  5  #       All rights reserved. 
  6  # 
  7  #       Redistribution and use in source and binary forms, with or without modification, 
  8  #       are permitted provided that the following conditions are met: 
  9  # 
 10  #       * Redistributions of source code must retain the above copyright notice, this 
 11  #         list of conditions and the following disclaimer. 
 12  #       * Redistributions in binary form must reproduce the above copyright notice, 
 13  #         this list of conditions and the following disclaimer in the documentation 
 14  #         and/or other materials provided with the distribution. 
 15  #       * The names of the contributors may not be used to endorse or promote products 
 16  #         derived from this software without specific prior written permission. 
 17  # 
 18  #       THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 19  #       AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 20  #       IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 21  #       ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
 22  #       LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 23  #       DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 24  #       SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 25  #       CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 26  #       OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 27  #       OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 28   
 29  """Gathers all issued commands for zsh.""" 
 30   
 31  from rekall.plugins.overlays import basic 
 32  from rekall.plugins.linux import heap_analysis 
 33   
 34   
35 -class Zsh(heap_analysis.HeapAnalysis):
36 """Extracts the zsh command history, similar to the existing bash plugin. 37 """ 38 39 __name = "zsh" 40 41 table_header = [ 42 dict(name="divider", type="Divider"), 43 dict(name="task", hidden=True), 44 dict(name="counter", width=8), 45 dict(name="started", width=24), 46 dict(name="ended", width=24), 47 dict(name="command") 48 ] 49
50 - def __init__(self, **kwargs):
51 super(Zsh, self).__init__(**kwargs) 52 self._zsh_profile = None
53 54
55 - def collect(self):
56 if self.session.profile.metadata("arch") == 'AMD64': 57 self._zsh_profile = ZshProfile64(session=self.session) 58 59 else: 60 # default/fallback profile 61 self._zsh_profile = ZshProfile32(session=self.session) 62 63 chunk_size = self.get_aligned_size( 64 self._zsh_profile.get_obj_size('histent')) 65 66 for task in self.filter_processes(): 67 if self.init_for_task(task): 68 69 yield dict(divider="Task: %s (%s)" % (task.name, 70 task.pid)) 71 72 chunks_dict = dict() 73 74 data_offset = self.profile.get_obj_offset("malloc_chunk", "fd") 75 76 chunk_data_pointers = list() 77 for chunk in self.get_all_allocated_chunks(): 78 chunks_dict[chunk.v() + data_offset] = chunk 79 chunk_data_pointers.append(chunk.v() + data_offset) 80 81 commands_dict = dict() 82 83 valid_histentry = None 84 85 # we first try to find a chunk that most probably contains a 86 # histent struct 87 for chunk in self.get_all_allocated_chunks(): 88 89 if not chunk.chunksize() == chunk_size: 90 continue 91 92 histent = self._zsh_profile.histent( 93 offset=chunk.v()+data_offset, vm=self.process_as) 94 95 # we test if the current histent struct seems to be valid 96 # first test: do we know the chunks where relevant 97 # pointers point to 98 pointers = [histent.node.nam, histent.down, histent.up] 99 if not len(set(pointers) & set(chunk_data_pointers)) \ 100 == len(pointers): 101 continue 102 103 # second test: points the previous/next histent entry to 104 # this histent entry? 105 if not histent.up.down == histent or not histent.down.up \ 106 == histent: 107 continue 108 109 # we hopefully found one 110 valid_histentry = histent 111 break 112 113 if valid_histentry: 114 self.session.logging.info( 115 "We probably found a valid histent chunk and now " 116 "start walking.") 117 118 # entries are linked circular so walking in one direction 119 # should be sufficient 120 for histent in valid_histentry.walk_list('down'): 121 122 command = '' 123 124 try: 125 command = chunks_dict[histent.node.nam.v()] 126 command = command.to_string() 127 command = command[:command.index("\x00")] 128 129 except KeyError: 130 self.session.logging.warn( 131 "Unexpected error: chunk for given " 132 "command-reference does not seem to exist.") 133 134 except ValueError: 135 pass 136 137 if histent.stim == histent.ftim == 0 and command == '': 138 histent_vma = heap_analysis.get_vma_for_offset( 139 self.vmas, histent.v()) 140 141 if histent_vma not in self.heap_vmas: 142 # we most probably found the "curline" histent 143 # struct located in zsh's .bss section. as it 144 # doesn't contain an actual executed command, 145 # we are skipping it 146 continue 147 148 command_number = histent.histnum 149 start = self.profile.UnixTimeStamp(value=histent.stim) 150 end = self.profile.UnixTimeStamp(value=histent.ftim) 151 commands_dict[command_number] = [start, 152 end, 153 repr(command)] 154 155 156 for key, value in sorted(commands_dict.items()): 157 yield dict(task=task, counter=key, started=value[0], 158 ended=value[1], command=value[2])
159 160 161
162 -class ZshProfile32(basic.Profile32Bits, basic.BasicClasses):
163 """Profile to parse internal zsh data structures.""" 164 165 __abstract = True 166 167 # types come from zsh's zsh.h 168 zsh_vtype_32 = { 169 "histent": [48, { 170 "down": [16, ["Pointer", { 171 "target": "histent" 172 }]], 173 "ftim": [28, ["long int"]], 174 "histnum": [40, ["long long int"]], 175 "node": [0, ["hashnode"]], 176 "nwords": [36, ["int"]], 177 "stim": [24, ["long int"]], 178 "up": [12, ["Pointer", { 179 "target": "histent" 180 }]], 181 "words": [32, ["Pointer", { 182 "target": "short int" 183 }]], 184 "zle_text": [20, ["Pointer", { 185 "target": "char" 186 }]] 187 }], 188 "hashnode": [12, { 189 "flags": [8, ["int"]], 190 "nam": [4, ["Pointer", { 191 "target": "char" 192 }]], 193 "next": [0, ["Pointer", { 194 "target": "hashnode" 195 }]] 196 }] 197 } 198
199 - def __init__(self, **kwargs):
200 super(ZshProfile32, self).__init__(**kwargs) 201 self.add_types(self.zsh_vtype_32)
202 203 204
205 -class ZshProfile64(basic.ProfileLP64, basic.BasicClasses):
206 """Profile to parse internal zsh data structures.""" 207 208 __abstract = True 209 210 # types come from zsh's zsh.h 211 zsh_vtype_64 = { 212 "histent": [88, { 213 "down": [32, ["Pointer", { 214 "target": "histent" 215 }]], 216 "ftim": [56, ["long int"]], 217 "histnum": [80, ["long int"]], 218 "node": [0, ["hashnode"]], 219 "nwords": [72, ["int"]], 220 "stim": [48, ["long int"]], 221 "up": [24, ["Pointer", { 222 "target": "histent" 223 }]], 224 "words": [64, ["Pointer", { 225 "target": "short int" 226 }]], 227 "zle_text": [40, ["Pointer", { 228 "target": "char" 229 }]] 230 }], 231 "hashnode": [24, { 232 "flags": [16, ["int"]], 233 "nam": [8, ["Pointer", { 234 "target": "char" 235 }]], 236 "next": [0, ["Pointer", { 237 "target": "hashnode" 238 }]] 239 }] 240 } 241
242 - def __init__(self, **kwargs):
243 super(ZshProfile64, self).__init__(**kwargs) 244 self.add_types(self.zsh_vtype_64)
245