
TP-Link Archer AX50 राउटर में conn-indicator बाइनरी में पाए गए स्टैक-आधारित बफर ओवरफ्लो के लिए एक्सप्लॉइट
TP-Link Archer AX50 राउटर अपने फर्मवेयर संस्करण 1.0.14 Build 20240108 rel.42655(4555) में स्टैक-आधारित बफर ओवरफ्लो के प्रति संवेदनशील है, जिससे LAN और WAN दोनों ओर रिमोट कोड एक्ज़ीक्यूशन (Remote Code Execution) हो सकता है। इस भेद्यता का मूल कारण CVE-2020-10881 के समान है, जिसे Flashback टीम ने खोजा था और उनकी वीडियो श्रृंखला में विस्तार से बताया गया है (संदर्भ देखें)। हालाँकि, शोषण प्रक्रिया थोड़ी अलग है, इसलिए एक नया एक्सप्लॉइट लिखना आवश्यक था।

यह भेद्यता conn-indicator बाइनरी में उत्पन्न होती है, जो यह जाँचने के लिए ज़िम्मेदार है कि राउटर इंटरनेट से जुड़ा है या नहीं। यह समय-समय पर DNS क्वेरी भेजता है और 32000 और 61000 के बीच एक यादृच्छिक UDP पोर्ट पर उनके उत्तरों (responses) को सुनता है।
इन DNS उत्तर पैकेटों को प्राप्त करने और पहले संसाधित करने वाला फ़ंक्शन TPDns_RecvAndResolve() है, जो 0x00405e3c पर स्थित है। जब recvfrom के साथ कोई पैकेट प्राप्त होता है, तो उसे buf में संग्रहीत किया जाता है, जिसका आकार 2960 बाइट्स है। फिर, यह जाँचता है कि रिटर्न कोड सही है (RCODE == 0) और पैकेट में प्रश्नों और उत्तरों की संख्या (QDCOUNT और ANCOUNT) की जाँच करता है। उत्तरों को संसाधित करने के लिए, यह process_resolved_IP() को कॉल करता है और उसे buf का एक पॉइंटर, answer_ptr, जो buf में पैकेट के भीतर उत्तरों का स्थान दर्शाने वाला पॉइंटर है, उत्तरों की संख्या (ANCOUNT) और अन्य फ़्लैग्स पास करता है:
undefined4 * TPDns_RecvAndResolve(int socket,void *param_2,int param_3){
[...]
byte buf [2960];
[...]
while( true ) {
recv_bytes = recvfrom(socket,buf + total_recv_bytes,0xb90 - total_recv_bytes,0,&sStack_50,
local_38);
piVar2 = __errno_location();
if (recv_bytes == 0) goto RECV_ERROR;
if (recv_bytes < 0) break;
total_recv_bytes = total_recv_bytes + recv_bytes;
if (2959 < total_recv_bytes) goto PROCESS_DNS_RESP;
}
[...]
/* Check that ANCOUNT is not 0 (answer contains at least one domain) */
puVar5 = (undefined4 *)0x0;
if (buf._6_2_ != 0) {
local_40 = 0;
puVar5 = process_resolved_IP(buf,answer_ptr,(uint)buf._6_2_,&local_3c,&local_40);
answer_ptr = answer_ptr + local_40;
}
[...]
process_resolved_IP() फ़ंक्शन, जो 0x00405818 पर स्थित है, एक-एक करके प्रत्येक उत्तर पर जाता है और उनमें से प्रत्येक के लिए DNS_answer_parser() को कॉल करता है। यह तर्कों के रूप में buf का वही पॉइंटर, answer, जो buf में मूल पैकेट के भीतर वर्तमान में पार्स किए जा रहे उत्तर की ओर संकेत करता है, और current_answer का एक पॉइंटर पास करता है, जो 256 बाइट्स का बफर है:
undefined4 * process_resolved_IP(byte *buf,byte *answer_ptr,uint ANCOUNT,undefined4 *param_4,int *param_5){
[...]
byte current_answer [256];
ushort answer_flags [5];
i = 0;
puVar9 = (undefined4 *)0x0;
puVar7 = (undefined4 *)0x0;
answer = answer_ptr;
do {
/* Check if all answers have been parsed already */
if (i == ANCOUNT) {
if (param_4 != (undefined4 *)0x0) {
*param_4 = puVar7;
}
if (param_5 != (int *)0x0) {
*param_5 = (int)answer - (int)answer_ptr;
}
return puVar9;
}
bytes_processed = DNS_answer_parser(buf,answer,current_answer,1);
memcpy(answer_flags,answer + bytes_processed,10);
uVar1 = answer_flags._4_4_;
bytes_processed = bytes_processed + 10;
uVar6 = (uint)answer_flags[4];
uVar8 = (uint)answer_flags[0];
if (uVar8 == 2) {
LAB_00405924:
DNS_answer_parser(buf,answer + bytes_processed,abStack_240,1);
}
else if (uVar8 < 3) {
pbVar2 = answer + bytes_processed;
if (uVar8 == 1) {
sprintf((char *)abStack_240,"%u.%u.%u.%u",(uint)*pbVar2,(uint)pbVar2[1],(uint)pbVar2[2],
(uint)pbVar2[3]);
}
}
else {
if (uVar8 == 5) goto LAB_00405924;
if (uVar8 == 0x1c) {
inet_ntop(10,answer,(char *)abStack_240,0xff);
}
}
answer = answer + bytes_processed + uVar6;
[...]
i = i + 1;
} while( true );
}
DNS_answer_parser() फ़ंक्शन, जो 0x004054e0 पर स्थित है, प्रत्येक उत्तर को अलग-अलग पार्स करता है, जिसमें एक डोमेन नाम होता है जिसे <len><domain><len><domain>... के रूप में दर्शाया जाता है। उदाहरण के लिए, example.com को 7example3com के रूप में दर्शाया जाएगा। फ़ंक्शन उत्तर में डोमेन नाम बनाने वाले <len><domain> के प्रत्येक जोड़े पर लूप करता है और जाँचता है कि <len> 63 से कम है (domain_name & 0xc0 != 0)। फिर, यह memcpy को कॉल करता है और <domain> के अनुरूप answer के <len> बाइट्स को current_answer में कॉपी करता है। यह डोमेन नाम में <len><domain> के अगले जोड़े के लिए प्रक्रिया दोहराता है।
int DNS_answer_parser(byte *buf,byte *answer,byte *current_answer,int flag){
int iVar1;
uint __n;
int iVar2;
uint uVar3;
ushort flag_and_offset;
byte domain_name;
iVar2 = 0;
do {
domain_name = *answer;
__n = (uint)domain_name;
iVar1 = 1;
if (__n == 0) {
*current_answer = 0;
LAB_004055b0:
return iVar2 + iVar1;
}
/* Check if compression mode is used */
if ((domain_name & 0xc0) != 0) {
flag_and_offset = CONCAT11(domain_name,answer[1]);
DNS_answer_parser(buf,buf + (flag_and_offset & 0x3fff),current_answer,flag);
iVar1 = 2;
goto LAB_004055b0;
}
uVar3 = __n + 1;
if (flag == 0) {
*current_answer = '.';
memcpy(current_answer + 1,answer + 1,__n);
__n = uVar3;
}
else {
memcpy(current_answer,answer + 1,__n);
}
answer = answer + uVar3;
current_answer = current_answer + __n;
iVar2 = iVar2 + uVar3;
flag = 0;
} while( true );
}
चूँकि current_answer केवल 256 बाइट्स लंबा है, एक हमलावर के लिए यह संभव है कि वह एक ऐसा पैकेट भेजे जिसके उत्तर में इतना बड़ा डोमेन नाम हो कि बफर ओवरफ्लो हो जाए।
शोषण प्रक्रिया उस प्रक्रिया के बहुत समान है जिसे Flashback टीम ने CVE-2020-10881 के लिए अपनी वीडियो श्रृंखला में समझाया था (संदर्भ देखें), लेकिन कुछ प्रमुख अंतर हैं:
move निर्देश में भिन्न है, क्योंकि अब memcpy() के count तर्क के रूप में $a2 में ले जाया जाने वाला रजिस्टर $s0 के बजाय $s1 है।process_resolved_IP() में i वेरिएबल $s8 के बजाय $s5 में लोड होता है। फिर, जब इसकी तुलना $v1 से की जाती है, तो $v1 को $sp+616 से लिया जाता है, जो ओवरफ्लो हुए बफर से बहुत दूर है, इसलिए स्टैक को उत्तर और कमांड से आगे तक corrupt करना पड़ा।/tmp को छोड़कर केवल-पठनीय है, इसलिए सभी रीड और राइट वहीं किए जाने चाहिए। इससे रिवर्स शेल का 62 वर्णों से कम होना असंभव हो जाता है, इसलिए इसे लक्ष्य पर तैनात करने के लिए, इसे एक वेब सर्वर से सर्व किया जाना चाहिए और लक्ष्य पर डाउनलोड करके निष्पादित करने के लिए एक कमांड भेजी जानी चाहिए।इसे ध्यान में रखते हुए, मैं फिर भी पूरी प्रक्रिया समझाऊँगा।
conn-indicator बाइनरी में NX सक्षम है और स्वयं को छोड़कर हर चीज़ में ASLR सक्षम है: