
सीटीएफ पीडब्ल्यूएन चैलेंज राइटअप जो CVE-2021-4034 (pkexec) का हीप मैनिपुलेशन के माध्यम से शोषण करता है, जिसमें घिद्रा-आधारित रिवर्स इंजीनियरिंग और एक कस्टम shelly.so हेल्पर बाइनरी शामिल है।
यह एक CTF pwn चुनौती है जिसे मैंने C में लिखा है और जिसमें उपयोगकर्ता को CVE-2021-4034 भेद्यता का शोषण करने की आवश्यकता है। खिलाड़ियों को इस repo के challenge निर्देशिका में 2 बाइनरी दिए गए हैं। chal बाइनरी CTF चुनौती को लागू करता है और shelly.so एक सहायक बाइनरी है।
इस लेख को लिखने के समय, Dockerfile अभी भी पूर्ण नहीं है। लाइव CTF के दौरान इस चुनौती को तैनात करने के लिए Dockerfile आवश्यक है, लेकिन स्थानीय रूप से नहीं: आप अभी भी इस चुनौती को स्थानीय रूप से उपयोगकर्ता अनुमतियाँ सेट करके और कमजोर पैकेज स्थापित करके अनुकरण कर सकते हैं जैसा कि निम्नलिखित है:
libpolkit-gobject-1-0=0.105-26ubuntu1 libpolkit-agent-1-0=0.105-26ubuntu1 policykit-1=0.105-26ubuntu1 स्थापित करें।root:root द्वारा स्वामित्व वाली एक flag.txt फ़ाइल बनाएँ।challenge फ़ोल्डर में फ़ाइलों को वर्तमान निर्देशिका में डाउनलोड करें।chal चलाएँ।chal बाइनरी को चलाने से हमें यह अस्पष्ट विचार मिलता है कि यह बाइनरी क्या करती है:```
WELCOME TO THE HUB CTRL+ALT+DELICIOUS
We're not just a sandwich hub. We are the beacon of flavors, serving a symphony in every byte
`Add` फंक्शन में इनपुट साइज और `Cancel` फंक्शनों के इंडेक्स के साथ खेलने से हमें कुछ खास नहीं मिलता (कोई ओवरफ्लो या सेगमेंटेशन फॉल्ट नहीं)। हालांकि, कुछ दिलचस्प बातें:
- ऐसा लगता है कि ऑर्डर एक लिस्ट (शायद linked list) में रखे जाते हैं और वे 0-indexed हैं?
- यह `Order number: 0x7ffde93681f0` है जो स्टैक पर किसी स्थान को प्रिंट करता प्रतीत होता है?
- साथ ही, प्रोग्राम हमारे इनपुट नाम के साथ एक डायरेक्टरी बनाता है जिसमें 3 एक्जीक्यूटेबल्स होते हैं, जिनमें से एक वह helper binary file है जो हमें दी गई है:```sh
peasant@Tin-VM:~/Desktop$ ls
chal chal.c Dockerfile shelly.so solve.py tin
peasant@Tin-VM:~/Desktop$ ls -l tin/
total 28
-rwxrwx--- 1 peasant vboxsf 5 Feb 4 14:33 notes
-rwxrwx--- 1 peasant vboxsf 20 Feb 4 14:33 recipe
-rwxr-x--- 1 peasant vboxsf 16488 Feb 4 14:33 shelly.so
peasant@Tin-VM:~/Desktop$ cat tin/notes
0000
peasant@Tin-VM:~/Desktop$ cat tin/recipe
aaaa
bbbb
cccc
dddc
निष्पादन योग्य फ़ाइलों में हमारा इनपुट होता है।
आप अन्य फ़ंक्शनों के साथ प्रयोग कर सकते हैं और उम्मीद कर सकते हैं कि आपको कुछ बग मिलें (जो संभावित है), लेकिन मैं सीधे मुद्दे पर आता हूँ और प्रोग्राम को Ghidra में खोलता हूँ।
प्रोग्राम चलाने पर दिखने वाले स्ट्रिंग्स और Ghidra में मौजूद स्ट्रिंग्स की तुलना करके, हम कुछ FUN_* फ़ंक्शनों को परिचित नामों में बदल सकते हैं:```C
undefined8 main(void)
{ int iVar1; size_t sVar2; undefined2 *puVar3; long in_FS_OFFSET; int opt; int local_1c; char *local_18; long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28); local_18 = "/recipe"; print("WELCOME TO THE HUB CTRL+ALT+DELICIOUS\n"); print( "We're not just a sandwich hub. We are the beacon of flavors, serving a symphony in every by te\n\n" ); while( true ) { print("1. ENTER THE HUB\n"); print("2. QUIT\n"); __isoc99_scanf(&DAT_001030c5,&opt); getc(stdin); if (opt != 1) break; printf("Order number: %p\n",&local_18); print("Enter your name: "); __isoc99_scanf(&DAT_0010334f,&DAT_00105120); sVar2 = strlen(&DAT_00105120); puVar3 = (undefined2 *)malloc(sVar2 + 2); DAT_00105100 = puVar3; *puVar3 = 0x2f2e; *(undefined *)(puVar3 + 1) = 0; strcpy((char *)(DAT_00105100 + 1),&DAT_00105120); iVar1 = FUN_001022f0(DAT_00105100,&DAT_00105060); if (iVar1 == -1) { mkdir((char *)DAT_00105100,0x1c0); } DAT_00105140 = 0; order_cnt = 0; for (local_1c = 0; local_1c < 10; local_1c = local_1c + 1) { *(undefined8 *)(&ptr_array + (long)local_1c * 8) = 0; } main_menu(); } print("Come again :)\n");
The `printf("Order number: %p\n",&local_18);` **स्टैक पर एक स्थानीय चर का स्थान प्रिंट करता है।**
gdb में एक त्वरित जांच से पता चलता है कि लीक हुआ पता नियत स्ट्रिंग `/recipe` के पॉइंटर का पता है:```gdb
...
Order number: 0x7fffffffdfc0
...
gef➤ x/gx 0x7fffffffdfc0
0x7fffffffdfc0: 0x0000555555559020
gef➤ x/s 0x0000555555559020
0x555555559020: "/recipe"
हम एक mkdir कॉल देखते हैं, जो वर्तमान निर्देशिका में हमारे इनपुट नाम के साथ एक निर्देशिका बनाता है।
यह हमारे प्रोग्राम चलाने के समय हमारे अवलोकन के अनुरूप है। फिर यह main_menu फ़ंक्शन को कॉल करने से पहले कुछ वेरिएबल को आरंभ करता है, जो कुछ इस प्रकार दिखता है:```C
while( true ) {
while( true ) {
while( true ) {
while( true ) {
while( true ) {
while( true ) {
print("1. ADD NEW ORDER\n");
print("2. EDIT ORDER\n");
print("3. SHOW ORDER\n");
print("4. CANCEL ORDER\n");
print("5. CHECKOUT\n");
print("6. DONE\n");
__isoc99_scanf(&DAT_001030c5,&local_40);
getc(stdin);
if (local_40 != 1) break;
add_order();
}
if (local_40 != 2) break;
edit_order();
}
if (local_40 != 3) break;
show_order();
}
if (local_40 != 4) break;
cancel_order();
}
if (local_40 != 5) break;
checkout();
}
if (local_40 == 6) break;
if (local_40 == 0x539) {
print(
"\nGORDON RAMSAY: Finally, a worthy opponent, our battle will be legendary! I BET YOU CAN 'T GUESS THE SECRET RECIPE.\n"
);
fgets(inp,0x20,stdin);
getrandom(random-bytes,0x10,0);
for (local_3c = 0; local_3c < 0x10; local_3c = local_3c + 1) {
if (inp[local_3c] != random-bytes[local_3c]) {
print("...Nuh Uh!...\n");
/* WARNING: Subroutine does not return */
exit(0);
}
print("...Ooh Yes.. sCruMpTioUs...");
}
print("Fine... I'll give you a taste.\n");
FUN_00101504();
}
यदि आप REV से परिचित नहीं हैं, तो यह C में `switch` स्टेटमेंट का डीकंपाइलेशन है। एक दिलचस्प विकल्प है जिसे `0x539` के नाम से जाना जाता है। यह खिलाड़ियों को `0x10` रैंडम बाइट्स का अनुमान लगाने की अनुमति देता है। यदि सभी बाइट्स बराबर हैं, तो यह `FUN_00101504();` को कॉल करता है जो `system("cat flag.txt");` को कॉल करता है। अन्यथा, प्रोग्राम बाहर निकल जाता है।