LD_PRELOAD injectionでOpenSSLによる暗号化処理を覗いてみる
LD_PRELOAD環境変数を使ったライブラリ関数フックにより、OpenSSLの暗号化処理を覗くコードを書いてみる。
環境
Ubuntu 14.04.4 LTS 64bit版
$ uname -a Linux vm-ubuntu64 3.19.0-25-generic #26~14.04.1-Ubuntu SMP Fri Jul 24 21:16:20 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 14.04.4 LTS Release: 14.04 Codename: trusty $ gcc --version gcc (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4
フックする共有ライブラリを作ってみる
OpenSSLの暗号化処理に関連する関数をフックする共有ライブラリのコードを書くと次のようになる。
/* hook_openssl.c */ /* for dlsym(3) */ #define _GNU_SOURCE #include <dlfcn.h> #include <stdio.h> static int (*orig_EVP_EncryptUpdate)(void *ctx, unsigned char *out, int *outl, unsigned char *in, int inl); static int (*orig_EVP_DecryptUpdate)(void *ctx, unsigned char *out, int *outl, unsigned char *in, int inl); static int (*orig_EVP_DecryptFinal_ex)(void *ctx, unsigned char *outm, int *outl); static int (*orig_SSL_write)(void *ssl, const void *buf, int num); static int (*orig_SSL_read)(void *ssl, void *buf, int num); static void __attribute__((constructor)) init() { orig_EVP_EncryptUpdate = dlsym(RTLD_NEXT, "EVP_EncryptUpdate"); orig_EVP_DecryptUpdate = dlsym(RTLD_NEXT, "EVP_DecryptUpdate"); orig_EVP_DecryptFinal_ex = dlsym(RTLD_NEXT, "EVP_DecryptFinal_ex"); orig_SSL_write = dlsym(RTLD_NEXT, "SSL_write"); orig_SSL_read = dlsym(RTLD_NEXT, "SSL_read"); } void log_stderr(char *funcname, const char *buf, int len) { fprintf(stderr, "[%s] ", funcname); fwrite(buf, len, 1, stderr); fprintf(stderr, "\n"); } int EVP_EncryptUpdate(void *ctx, unsigned char *out, int *outl, unsigned char *in, int inl) { log_stderr("EVP_EncryptUpdate", in, inl); return (*orig_EVP_EncryptUpdate)(ctx, out, outl, in, inl); } int EVP_DecryptUpdate(void *ctx, unsigned char *out, int *outl, unsigned char *in, int inl) { int ret = (*orig_EVP_DecryptUpdate)(ctx, out, outl, in, inl); log_stderr("EVP_DecryptUpdate", out, *outl); return ret; } int EVP_DecryptFinal_ex(void *ctx, unsigned char *outm, int *outl) { int ret = (*orig_EVP_DecryptFinal_ex)(ctx, outm, outl); log_stderr("EVP_DecryptFinal_ex", outm, *outl); return ret; } int SSL_write(void *ssl, const void *buf, int num) { log_stderr("SSL_write", buf, num); return (*orig_SSL_write)(ssl, buf, num); } int SSL_read(void *ssl, void *buf, int num) { int ret = (*orig_SSL_read)(ssl, buf, num); log_stderr("SSL_read", buf, num); return ret; }
ここで、EVP系の関数は一般の暗号化処理、SSL系の関数はSSLの暗号化処理に使われる関数である。
コンパイルして共有ライブラリを作り、curlコマンドにLD_PRELOADで共有ライブラリを読み込ませると次のようになる。
$ gcc -fPIC -shared hook_openssl.c -o hook_openssl.so $ LD_PRELOAD=./hook_openssl.so curl -s https://www.example.com/ >/dev/null [SSL_write] GET / HTTP/1.1 User-Agent: curl/7.35.0 Host: www.example.com Accept: */* [SSL_read] HTTP/1.1 200 OK Accept-Ranges: bytes Cache-Control: max-age=604800 Content-Type: text/html Date: Mon, 14 Mar 2016 06:23:20 GMT Etag: "359670651" Expires: Mon, 21 Mar 2016 06:23:20 GMT Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT Server: ECS (cpm/F845) Vary: Accept-Encoding X-Cache: HIT x-ec-custom-error: 1 Content-Length: 1270 [SSL_read] <!doctype html> <html> <head> (snip) </head> <body> <div> <h1>Example Domain</h1> <p>This domain is established to be used for illustrative examples in documents. You may use this domain in examples without prior coordination or asking for permission.</p> <p><a href="http://www.iana.org/domains/example">More information...</a></p> </div> </body> </html>
上の結果から、暗号化前および復号後のHTTPS通信の内容を取得できていることが確認できる。
一般の暗号化処理について調べてみる
上の例はSSLの暗号化処理であったが、一般の暗号化処理についても内容を取得できるか確認してみる。
まず、OpenSSLの開発用ライブラリをインストールする。
$ sudo apt-get install libssl-dev
下のページを参考に暗号化処理を行うテストコードを書くと次のようになる。
/* test.c */ #include <openssl/conf.h> #include <openssl/evp.h> #include <openssl/err.h> #include <string.h> int main (void) { /* Set up the key and iv. Do I need to say to not hard code these in a * real application? :-) */ /* A 256 bit key */ unsigned char *key = (unsigned char *)"01234567890123456789012345678901"; /* A 128 bit IV */ unsigned char *iv = (unsigned char *)"01234567890123456"; /* Message to be encrypted */ unsigned char *plaintext = (unsigned char *)"The quick brown fox jumps over the lazy dog"; /* Buffer for ciphertext. Ensure the buffer is long enough for the * ciphertext which may be longer than the plaintext, dependant on the * algorithm and mode */ unsigned char ciphertext[128]; /* Buffer for the decrypted text */ unsigned char decryptedtext[128]; int decryptedtext_len, ciphertext_len; /* Initialise the library */ ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); OPENSSL_config(NULL); /* Encrypt the plaintext */ ciphertext_len = encrypt (plaintext, strlen ((char *)plaintext), key, iv, ciphertext); /* Do something useful with the ciphertext here */ printf("Ciphertext is:\n"); BIO_dump_fp (stdout, (const char *)ciphertext, ciphertext_len); /* Decrypt the ciphertext */ decryptedtext_len = decrypt(ciphertext, ciphertext_len, key, iv, decryptedtext); /* Add a NULL terminator. We are expecting printable text */ decryptedtext[decryptedtext_len] = '\0'; /* Show the decrypted text */ printf("Decrypted text is:\n"); printf("%s\n", decryptedtext); /* Clean up */ EVP_cleanup(); ERR_free_strings(); return 0; } void handleErrors(void) { ERR_print_errors_fp(stderr); abort(); } int encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key, unsigned char *iv, unsigned char *ciphertext) { EVP_CIPHER_CTX *ctx; int len; int ciphertext_len; /* Create and initialise the context */ if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors(); /* Initialise the encryption operation. IMPORTANT - ensure you use a key * and IV size appropriate for your cipher * In this example we are using 256 bit AES (i.e. a 256 bit key). The * IV size for *most* modes is the same as the block size. For AES this * is 128 bits */ if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv)) handleErrors(); /* Provide the message to be encrypted, and obtain the encrypted output. * EVP_EncryptUpdate can be called multiple times if necessary */ if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) handleErrors(); ciphertext_len = len; /* Finalise the encryption. Further ciphertext bytes may be written at * this stage. */ if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) handleErrors(); ciphertext_len += len; /* Clean up */ EVP_CIPHER_CTX_free(ctx); return ciphertext_len; } int decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, unsigned char *iv, unsigned char *plaintext) { EVP_CIPHER_CTX *ctx; int len; int plaintext_len; /* Create and initialise the context */ if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors(); /* Initialise the decryption operation. IMPORTANT - ensure you use a key * and IV size appropriate for your cipher * In this example we are using 256 bit AES (i.e. a 256 bit key). The * IV size for *most* modes is the same as the block size. For AES this * is 128 bits */ if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv)) handleErrors(); /* Provide the message to be decrypted, and obtain the plaintext output. * EVP_DecryptUpdate can be called multiple times if necessary */ if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) handleErrors(); plaintext_len = len; /* Finalise the decryption. Further plaintext bytes may be written at * this stage. */ if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) handleErrors(); plaintext_len += len; /* Clean up */ EVP_CIPHER_CTX_free(ctx); return plaintext_len; }
テストコードをコンパイルし、LD_PRELOADで共有ライブラリを読み込ませた上で実行してみる。
$ gcc test.c -o test -lcrypto $ LD_PRELOAD=./hook_openssl.so ./test [EVP_EncryptUpdate] The quick brown fox jumps over the lazy dog Ciphertext is: 0000 - e0 6f 63 a7 11 e8 b7 aa-9f 94 40 10 7d 46 80 a1 .oc.......@.}F.. 0010 - 17 99 43 80 ea 31 d2 a2-99 b9 53 02 d4 39 b9 70 ..C..1....S..9.p 0020 - 2c 8e 65 a9 92 36 ec 92-07 04 91 5c f1 a9 8a 44 ,.e..6.....\...D [EVP_DecryptUpdate] The quick brown fox jumps over t [EVP_DecryptFinal_ex] he lazy dog Decrypted text is: The quick brown fox jumps over the lazy dog
上の結果から、一般の暗号化処理についても、暗号化前および復号後のメッセージの内容が取得できていることが確認できる。