
हम सभी गतिशील लिंक पुस्तकालयों का उपयोग करते हैं। उनकी क्षमताएं वास्तव में शानदार हैं। सबसे पहले, ऐसी लाइब्रेरी को सभी प्रक्रियाओं के लिए केवल एक बार भौतिक पता स्थान में लोड किया जाता है। दूसरे, आप एक अतिरिक्त पुस्तकालय को लोड करके अपने कार्यक्रम की कार्यक्षमता का विस्तार कर सकते हैं, जो इस कार्यक्षमता को प्रदान करेगा। और यह सब कार्यक्रम को पुनरारंभ किए बिना। यह अपडेट की समस्या को भी हल करता है। गतिशील रूप से रचना योग्य लाइब्रेरी के लिए, आप एक मानक इंटरफ़ेस को परिभाषित कर सकते हैं और लाइब्रेरी संस्करण को बदलकर अपने मुख्य कार्यक्रम की कार्यक्षमता और गुणवत्ता को प्रभावित कर सकते हैं। इस तरह के कोड पुन: उपयोग के तरीकों को "प्लग-इन आर्किटेक्चर" भी कहा जाता है। लेकिन विषय उस बारे में नहीं है।
वैसे, अधीर अभी सब कुछ
डाउनलोड और कोशिश कर सकता है।
बेशक, शायद ही कभी इसके कार्यान्वयन में गतिशील रूप से संकलित पुस्तकालय पूरी तरह से खुद पर निर्भर करता है, अर्थात प्रोसेसर और मेमोरी की कंप्यूटिंग क्षमता। पुस्तकालय पुस्तकालयों का उपयोग करते हैं। या, कम से कम, मानक पुस्तकालयों। उदाहरण के लिए, C \ C ++ प्रोग्राम मानक C \ C ++ पुस्तकालयों का उपयोग करते हैं। वैसे, बाद में एक गतिशील रूप से रचना के रूप में सुविधा के लिए भी आयोजित किया जाता है (libc.so और libstdc ++। तो)। वे स्वयं एक विशेष प्रारूप की फाइलों में संग्रहीत हैं। मेरा शोध लिनक्स के लिए था, जिसमें गतिशील रूप से जुड़े पुस्तकालयों के लिए मुख्य प्रारूप ईएलएफ (निष्पादन योग्य और लिंक करने योग्य प्रारूप) है।
कुछ समय पहले, मुझे एक पुस्तकालय से दूसरे में फ़ंक्शन कॉल को इंटरसेप्ट करने की आवश्यकता के साथ सामना किया गया था। बस उन्हें एक विशेष तरीके से संसाधित करने के लिए। इसे कॉल रिडायरेक्शन कहा जाता है।
पुनर्निर्देशन के बारे में अधिक
शुरू करने के लिए, हम एक विशिष्ट उदाहरण का उपयोग करके समस्या का निर्माण करते हैं। मान लें कि हमारे पास C भाषा (फ़ाइल test.c) में "परीक्षण" नामक एक कार्यक्रम है और दो साझा पुस्तकालय (फाइलें libtest1.c और libtest2.c) समान सामग्री के साथ अग्रिम में संकलित किए गए हैं। ये पुस्तकालय क्रमशः एक कार्य प्रदान करते हैं: libtest1 () और libtest2 (), क्रमशः। उनके कार्यान्वयन में, उनमें से प्रत्येक सी मानक पुस्तकालय से पुट () फ़ंक्शन का उपयोग करता है।
कार्य इस प्रकार है:
- मुख्य कार्यक्रम (टेस्ट.c फ़ाइल) में कार्यान्वित रीडायरेक्ट_पुट्स () फ़ंक्शन कॉल के साथ दोनों पुस्तकालयों के पुट () फ़ंक्शन कॉल को बदलना आवश्यक है, जो मूल पुट () का उपयोग कर सकते हैं;
- किए गए परिवर्तनों को छोड़ दें, अर्थात्, सुनिश्चित करें कि libtest1 () और libtest2 () के लिए बार-बार कॉल करने से मूल पुट () हो जाता है।
उसी समय, कोड को बदलना या पुस्तकालयों को फिर से स्थापित करने की अनुमति नहीं है, केवल मुख्य कार्यक्रम।
इसकी आवश्यकता क्यों है?
यह उदाहरण इस तरह के पुनर्निर्देशित की दो बहुत ही दिलचस्प विशेषताओं को दिखाता है:
- यह विशेष रूप से एक विशिष्ट गतिशील संकलित पुस्तकालय के लिए किया जाता है, और पूरी प्रक्रिया के लिए नहीं, जैसा कि डायनेमिक लोडर के पर्यावरण चर LD_PRELOAD का उपयोग करते समय, जो अन्य मॉड्यूल को मूल फ़ंक्शन का सुरक्षित रूप से उपयोग करने की अनुमति देता है;
- यह तब होता है जब प्रोग्राम चल रहा होता है और उसे पुनरारंभ की आवश्यकता नहीं होती है।
यह कहां लागू किया जा सकता है? उदाहरण के लिए, प्लग-इन के एक समूह के साथ आपके कार्यक्रम में, आप अन्य प्लग-इन और एप्लिकेशन को प्रभावित किए बिना सिस्टम कॉल्स या कुछ अन्य पुस्तकालयों में उनकी कॉल को रोक सकते हैं। या यहां तक कि अपने प्लग-इन से कुछ एप्लिकेशन के लिए भी ऐसा ही करें।
इस समस्या को हल करने का कोई कानूनी तरीका नहीं है। एकमात्र विकल्प ईएलएफ से निपटना और स्मृति में आवश्यक परिवर्तन करना है।
चलो चलते हैं!संक्षेप में ईएलएफ के बारे में
ईएलएफ को समझने का सबसे अच्छा तरीका है कि धैर्य रखें और इसके विनिर्देश को एक-दो बार ध्यान से पढ़ें, फिर एक साधारण कार्यक्रम लिखें, इसे संकलित करें और एक हेक्स संपादक का उपयोग करके इसकी विस्तार से जांच करें कि आपने विनिर्देश के साथ क्या देखा। इस तरह के एक शोध पद्धति से ईएलएफ के लिए कुछ सरल पार्सर लिखने के विचार को तुरंत बढ़ावा मिलेगा, क्योंकि बहुत सारे नियमित काम होंगे। लेकिन, जल्दी मत करो। ऐसी कई उपयोगिताएँ पहले से ही निर्मित हैं। अनुसंधान के लिए, हम पिछले अनुभाग से फाइलें लेते हैं:
फ़ाइल test.c
#include <stdio.h> #include <dlfcn.h> #define LIBTEST1_PATH //position dependent code (for 32 bit only) #define LIBTEST2_PATH //position independent code void libtest1(); //from libtest1.so void libtest2(); //from libtest2.so int main() { void *handle1 = dlopen(LIBTEST1_PATH, RTLD_LAZY); void *handle2 = dlopen(LIBTEST2_PATH, RTLD_LAZY); if (NULL == handle1 || NULL == handle2) fprintf(stderr, %s\%s\, LIBTEST1_PATH, LIBTEST2_PATH); libtest1(); //calls puts() from libc.so twice libtest2(); //calls puts() from libc.so twice puts(); dlclose(handle1); dlclose(handle2); return 0; }
फ़ाइल libtest1.c
int puts(char const *); void libtest1() { puts("libtest1: 1st call to the original puts()"); puts("libtest1: 2nd call to the original puts()"); }
फ़ाइल libtest2.c
int puts(char const *); void libtest2() { puts("libtest2: 1st call to the original puts()"); puts("libtest2: 2nd call to the original puts()"); }
ELF के भाग क्या हैं?
इस प्रश्न का उत्तर देने के लिए, आपको ऐसी फ़ाइल के अंदर देखने की आवश्यकता है। इसके लिए ऐसी उपयोगिताएँ हैं:
- ईएलएफ फ़ाइल के वर्गों की सामग्री को देखने के लिए रीडफ़्ल एक बहुत शक्तिशाली उपकरण है
- objdump - पिछले एक के समान, वर्गों को अलग कर सकता है
- जीडीबी - लिनक्स के तहत डिबगिंग के लिए अपरिहार्य है, विशेष रूप से देखने के स्थानों को स्थानांतरित करने के लिए
रिलोकेशन एक ईएलएफ फ़ाइल में उस जगह के लिए एक विशेष शब्द है जो किसी अन्य मॉड्यूल के एक चरित्र को संदर्भित करता है। ऐसे स्थानों का प्रत्यक्ष संशोधन स्थैतिक (ld) या गतिशील (ld-linux.so.2) लिंकर के लोडर द्वारा किया जाता है।
कोई भी ELF फाइल एक विशेष हेडर से शुरू होती है। इसकी संरचना, साथ ही कई अन्य ईएलएफ तत्वों का वर्णन, /usr/include/linux/elf.h में पाया जा सकता है। हेडर में एक विशेष फ़ील्ड होता है जिसमें सेक्शन हेडर टेबल फ़ाइल की शुरुआत से ऑफसेट रिकॉर्ड किया जाता है। इस तालिका का प्रत्येक तत्व ईएलएफ में एक अनुभाग का वर्णन करता है। एक खंड एक ईएलएफ फ़ाइल में सबसे छोटा अविभाज्य संरचनात्मक तत्व है। मेमोरी में लोड करते समय, वर्गों को खंडों में संयोजित किया जाता है। खंड एक ELF फ़ाइल के सबसे छोटे अविभाज्य भाग होते हैं जिन्हें लोडर (ld-linux.so.2) द्वारा मेमोरी में मैप किया जा सकता है। सेगमेंट सेगमेंट की एक तालिका का वर्णन करता है, जिसकी ऑफसेट ईएलएफ फ़ाइल के हेडर में भी होती है।
उनमें से सबसे महत्वपूर्ण हैं:
- .text - में मॉड्यूल का कोड होता है
- .data - आरंभिक चर
- .bss - आरंभिक चर नहीं
- .symtab - मॉड्यूल प्रतीक: फ़ंक्शन और स्थिर चर
- .strtab - मॉड्यूल वर्णों के नाम
- .rel.text - कार्यों के लिए स्थानांतरण (सांख्यिकीय रूप से जुड़े मॉड्यूल के लिए)
- .rel.data - स्थिर चर के लिए स्थानांतरण (सांख्यिकीय रूप से जुड़े मॉड्यूल के लिए)
- .rel.plt - पीएलटी (प्रक्रिया लिंकेज तालिका) में तत्वों की एक सूची गतिशील लेआउट (पीएलटी का उपयोग करके) के दौरान स्थानांतरित की जानी चाहिए
- .rel.dyn - गतिशील रूप से जुड़े कार्यों के लिए स्थानांतरण (यदि PLT का उपयोग नहीं किया जाता है)
- .got - ग्लोबल ऑफ़सेट टेबल, रिलोकेबल ऑब्जेक्ट्स के ऑफ़सेट के बारे में जानकारी शामिल है
- .debug - डिबगिंग जानकारी
उपरोक्त फ़ाइलों को संकलित करने के लिए, निम्नलिखित कमांड निष्पादित करें:
gcc -g3 -m32 -shared -o libtest1.so libtest1.c gcc -g3 -m32 -fPIC -shared -o libtest2.so libtest2.c gcc -g3 -m32 -L$PWD -o test test.c -ltest1 -ltest2 –ldl
पहला कमांड डायनेमिक लिंक लाइब्रेरी libtest1.so बनाता है। दूसरा है libtest2.so। –FPIC स्विच पर ध्यान दें। यह संकलक को तथाकथित स्थिति स्वतंत्र कोड बनाने के लिए मजबूर करता है। अगले भाग में विवरण। तीसरा कमांड Test.c फ़ाइल को संकलित करके पहले से बनाए गए libtest1.so और libtest2.so पुस्तकालयों के साथ लिंक करके एक निष्पादन योग्य मॉड्यूल बनाता है। उत्तरार्द्ध वर्तमान निर्देशिका में हैं, जैसा कि -L $ PWD स्विच द्वारा परिलक्षित होता है। Dlopen () और dlclose () फ़ंक्शन का उपयोग करने के लिए libdl.so के साथ लिंक करना आवश्यक है।
प्रोग्राम को चलाने के लिए, आपको निम्नलिखित कमांड चलाने होंगे:
export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH ./test
यही है, पुस्तकालयों की खोज के लिए एक पथ के रूप में डायनेमिक लिंकर / लोडर के लिए वर्तमान निर्देशिका में पथ जोड़ें। कार्यक्रम का आउटपुट निम्नानुसार है:
libtest1: 1st call to the original puts()
libtest1: 2nd call to the original puts()
libtest2: 1st call to the original puts()
libtest2: 2nd call to the original puts()
-----------------------------
अब आइए परीक्षण मॉड्यूल के वर्गों को देखें। ऐसा करने के लिए, -a स्विच के साथ आसानी से चलाएँ। उनमें से सबसे दिलचस्प नीचे सूचीबद्ध हैं:
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x8048580
Start of program headers: 52 (bytes into file)
Start of section headers: 21256 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 8
Size of section headers: 40 (bytes)
Number of section headers: 39
Section header string table index: 36
निष्पादन योग्य मॉड्यूल के लिए मानक हेडर। पहले 16 बाइट्स में जादू का क्रम। मॉड्यूल का प्रकार (इस मामले में, निष्पादन योग्य, और भी ऑब्जेक्ट (.o) और साझा (.so), आर्किटेक्चर (i386), अनुशंसित प्रविष्टि बिंदु, सेगमेंट के हेडर और सेक्शन और उनके आकार इंगित किए जाते हैं। बहुत अंत में - अनुभाग नामों के लिए पंक्तियों की तालिका में ऑफसेट।
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048134 000134 000013 00 A 0 0 1
...
[ 5] .dynsym DYNSYM 08048200 000200 000110 10 A 6 1 4
[ 6] .dynstr STRTAB 08048310 000310 0000df 00 A 0 0 1
...
[ 9] .rel.dyn REL 08048464 000464 000010 08 A 5 0 4
[10] .rel.plt REL 08048474 000474 000040 08 A 5 12 4
[11] .init PROGBITS 080484b4 0004b4 000030 00 AX 0 0 4
[12] .plt PROGBITS 080484e4 0004e4 000090 04 AX 0 0 4
[13] .text PROGBITS 08048580 000580 0001fc 00 AX 0 0 16
[14] .fini PROGBITS 0804877c 00077c 00001c 00 AX 0 0 4
[15] .rodata PROGBITS 08048798 000798 00005c 00 A 0 0 4
...
[20] .dynamic DYNAMIC 08049f08 000f08 0000e8 08 WA 6 0 4
[21] .got PROGBITS 08049ff0 000ff0 000004 04 WA 0 0 4
[22] .got.plt PROGBITS 08049ff4 000ff4 00002c 04 WA 0 0 4
[23] .data PROGBITS 0804a020 001020 000008 00 WA 0 0 4
[24] .bss NOBITS 0804a028 001028 00000c 00 WA 0 0 4
...
[27] .debug_pubnames PROGBITS 00000000 0011b8 000040 00 0 0 1
[28] .debug_info PROGBITS 00000000 0011f8 0004d9 00 0 0 1
[29] .debug_abbrev PROGBITS 00000000 0016d1 000156 00 0 0 1
[30] .debug_line PROGBITS 00000000 001827 000309 00 0 0 1
[31] .debug_frame PROGBITS 00000000 001b30 00003c 00 0 0 4
[32] .debug_str PROGBITS 00000000 001b6c 00024e 01 MS 0 0 1
...
[36] .shstrtab STRTAB 00000000 0051a8 000160 00 0 0 1
[37] .symtab SYMTAB 00000000 005920 000530 10 38 57 4
[38] .strtab STRTAB 00000000 005e50 000268 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
यहां आप प्रयोगात्मक ईएलएफ फ़ाइल के सभी वर्गों की सूची देख सकते हैं, उनका प्रकार और मेमोरी में लोड करने का तरीका (आर, डब्ल्यू, एक्स और ए)।
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00100 0x00100 RE 0x4
INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x007f8 0x007f8 RE 0x1000
LOAD 0x000ef4 0x08049ef4 0x08049ef4 0x00134 0x00140 RW 0x1000
DYNAMIC 0x000f08 0x08049f08 0x08049f08 0x000e8 0x000e8 RW 0x4
NOTE 0x000148 0x08048148 0x08048148 0x00020 0x00020 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
GNU_RELRO 0x000ef4 0x08049ef4 0x08049ef4 0x0010c 0x0010c R 0x1
यह खंडों की एक सूची है - स्मृति में वर्गों के लिए एक प्रकार का कंटेनर। एक विशेष मॉड्यूल, डायनेमिक लिंकर \ लोडर का मार्ग भी इंगित किया गया है। यह उसके ऊपर है कि वह इस ईएलएफ फ़ाइल की सामग्री को मेमोरी में व्यवस्थित करे।
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag
06
07 .ctors .dtors .jcr .dynamic .got
और यहां बताया गया है कि लोडिंग के दौरान सेगमेंट द्वारा वर्गों का वितरण कैसे होगा।
लेकिन, सबसे दिलचस्प खंड, जो आयातित और निर्यातित गतिशील रूप से जुड़े कार्यों के बारे में जानकारी संग्रहीत करता है, को ".dynsym" कहा जाता है।
Symbol table '.dynsym' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FUNC GLOBAL DEFAULT UND libtest2
2: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
3: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
4: 00000000 0 FUNC GLOBAL DEFAULT UND dlclose@GLIBC_2.0 (2)
5: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.0 (3)
6: 00000000 0 FUNC GLOBAL DEFAULT UND libtest1
7: 00000000 0 FUNC GLOBAL DEFAULT UND dlopen@GLIBC_2.1 (4)
8: 00000000 0 FUNC GLOBAL DEFAULT UND fprintf@GLIBC_2.0 (3)
9: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.0 (3)
10: 0804a034 0 NOTYPE GLOBAL DEFAULT ABS _end
11: 0804a028 0 NOTYPE GLOBAL DEFAULT ABS _edata
12: 0804879c 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
13: 0804a028 4 OBJECT GLOBAL DEFAULT 24 stderr@GLIBC_2.0 (3)
14: 0804a028 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
15: 080484b4 0 FUNC GLOBAL DEFAULT 11 _init
16: 0804877c 0 FUNC GLOBAL DEFAULT 14 _fini
कार्यक्रम के सही लोडिंग / अनलोडिंग के लिए आवश्यक अन्य कार्यों में, आप परिचित नाम पा सकते हैं: libtest1, libtest2, dlopen, fprintf, put, dlclose। उन सभी के लिए, FUNC प्रकार सूचीबद्ध है और तथ्य यह है कि वे इस मॉड्यूल में परिभाषित नहीं हैं - अनुभाग सूचकांक को UND के रूप में चिह्नित किया गया है।
".Rel.dyn" और ".rel.plt" खंड ".dynsym" से उन वर्णों के लिए स्थानांतरण सारणी हैं जिन्हें आम तौर पर लेआउट के दौरान स्थानांतरण की आवश्यकता होती है।
Relocation section '.rel.dyn' at offset 0x464 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
08049ff0 00000206 R_386_GLOB_DAT 00000000 __gmon_start__
0804a028 00000d05 R_386_COPY 0804a028 stderr
Relocation section '.rel.plt' at offset 0x474 contains 8 entries:
Offset Info Type Sym.Value Sym. Name
0804a000 00000107 R_386_JUMP_SLOT 00000000 libtest2
0804a004 00000207 R_386_JUMP_SLOT 00000000 __gmon_start__
0804a008 00000407 R_386_JUMP_SLOT 00000000 dlclose
0804a00c 00000507 R_386_JUMP_SLOT 00000000 __libc_start_main
0804a010 00000607 R_386_JUMP_SLOT 00000000 libtest1
0804a014 00000707 R_386_JUMP_SLOT 00000000 dlopen
0804a018 00000807 R_386_JUMP_SLOT 00000000 fprintf
0804a01c 00000907 R_386_JUMP_SLOT 00000000 puts
फ़ंक्शंस के गतिशील लिंकिंग के संदर्भ में इन तालिकाओं के बीच अंतर क्या है? यह अगले भाग के लिए विषय है।
कैसे साझा की जाती हैं ईएलएफ लाइब्रेरी?
Libtest1.so और libtest2.so पुस्तकालयों का संकलन थोड़ा अलग था। libtest2.so -fPIC स्विच (स्थिति स्वतंत्र कोड उत्पन्न) के साथ संकलित किया गया। आइए देखें कि इससे इन दो मॉड्यूलों के लिए गतिशील प्रतीक तालिकाओं को कैसे प्रभावित किया गया है (हम आसानी से उपयोग करते हैं):
Symbol table '.dynsym' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
2: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
3: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.0 (2)
4: 00000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.1.3 (3)
5: 00002014 0 NOTYPE GLOBAL DEFAULT ABS _end
6: 0000200c 0 NOTYPE GLOBAL DEFAULT ABS _edata
7: 0000043c 32 FUNC GLOBAL DEFAULT 11 libtest1
8: 0000200c 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
9: 0000031c 0 FUNC GLOBAL DEFAULT 9 _init
10: 00000498 0 FUNC GLOBAL DEFAULT 12 _fini
Symbol table '.dynsym' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
2: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
3: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.0 (2)
4: 00000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.1.3 (3)
5: 00002018 0 NOTYPE GLOBAL DEFAULT ABS _end
6: 00002010 0 NOTYPE GLOBAL DEFAULT ABS _edata
7: 00002010 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
8: 00000304 0 FUNC GLOBAL DEFAULT 9 _init
9: 0000043c 52 FUNC GLOBAL DEFAULT 11 libtest2
10: 000004a8 0 FUNC GLOBAL DEFAULT 12 _fini
तो, दोनों पुस्तकालयों के लिए गतिशील प्रतीक तालिकाएं केवल प्रतीकों के अनुक्रम में भिन्न होती हैं। यह देखा जा सकता है कि दोनों अपरिभाषित फ़ंक्शन पुट () का उपयोग करते हैं, लेकिन libtest1 () या libtest2 () प्रदान करते हैं। रिलोकेशन टेबल कैसे बदल गए हैं?
Relocation section '.rel.dyn' at offset 0x2cc contains 8 entries:
Offset Info Type Sym.Value Sym. Name
00000445 00000008 R_386_RELATIVE
00000451 00000008 R_386_RELATIVE
00002008 00000008 R_386_RELATIVE
0000044a 00000302 R_386_PC32 00000000 puts
00000456 00000302 R_386_PC32 00000000 puts
00001fe8 00000106 R_386_GLOB_DAT 00000000 __gmon_start__
00001fec 00000206 R_386_GLOB_DAT 00000000 _Jv_RegisterClasses
00001ff0 00000406 R_386_GLOB_DAT 00000000 __cxa_finalize
Relocation section '.rel.plt' at offset 0x30c contains 2 entries:
Offset Info Type Sym.Value Sym. Name
00002000 00000107 R_386_JUMP_SLOT 00000000 __gmon_start__
00002004 00000407 R_386_JUMP_SLOT 00000000 __cxa_finalize
Libtest1.so के लिए, पुट () फ़ंक्शन के लिए स्थानांतरण ".rel.dyn" अनुभाग में दो बार होता है। आइए इन स्थानों को सीधे disassembler का उपयोग करके मॉड्यूल में देखें। यह libtest1 () फ़ंक्शन को खोजने के लिए आवश्यक है जिसमें डबल पुट () कॉल होता है। हम objdump –D का उपयोग करते हैं:
0000043c <libtest1>: 43c: 55 push %ebp 43d: 89 e5 mov %esp,%ebp 43f: 83 ec 08 sub $0x8,%esp 442: c7 04 24 b4 04 00 00 movl $0x4b4,(%esp) 449: e8 fc ff ff ff call 44a <libtest1+0xe> 44e: c7 04 24 e0 04 00 00 movl $0x4e0,(%esp) 455: e8 fc ff ff ff call 456 <libtest1+0x1a> 45a: c9 leave 45b: c3 ret 45c: 90 nop 45d: 90 nop 45e: 90 nop 45f: 90 nop
हमारे पास ऑपरेंड 0xFFFFFFFC के साथ दो रिश्तेदार कॉल निर्देश (कोड E8) हैं। इस तरह के एक ऑपरेंड के साथ सापेक्ष CALL अर्थहीन है, क्योंकि, संक्षेप में, यह CALL निर्देश के पते के सापेक्ष एक बाइट को नियंत्रित करता है। यदि आप ".rel.dyn" अनुभाग में पुट के लिए स्थानांतरण की ऑफसेट को देखते हैं, तो आप पाएंगे कि वे सिर्फ कॉल निर्देश के ऑपरेंड पर लागू होते हैं। इस प्रकार, पुट के लिए कॉल के दोनों मामलों में (, लोडर बस 0xFFFFFFFC को अधिलेखित कर देगा ताकि पुट पुट () फ़ंक्शन के सही पते पर कूद जाएगा।
यह कैसे प्रकार R_386_PC32 का स्थानांतरण है।
अब हम libtest2.so पर ध्यान दें:
Relocation section '.rel.dyn' at offset 0x2cc contains 4 entries:
Offset Info Type Sym.Value Sym. Name
0000200c 00000008 R_386_RELATIVE
00001fe8 00000106 R_386_GLOB_DAT 00000000 __gmon_start__
00001fec 00000206 R_386_GLOB_DAT 00000000 _Jv_RegisterClasses
00001ff0 00000406 R_386_GLOB_DAT 00000000 __cxa_finalize
Relocation section '.rel.plt' at offset 0x2ec contains 3 entries:
Offset Info Type Sym.Value Sym. Name
00002000 00000107 R_386_JUMP_SLOT 00000000 __gmon_start__
00002004 00000307 R_386_JUMP_SLOT 00000000 puts
00002008 00000407 R_386_JUMP_SLOT 00000000 __cxa_finalize
पुट () केवल एक बार और, ".rel.plt" अनुभाग में, इसके अलावा उल्लेख किया गया है। आइए हम कोडांतरक को देखें और कुछ डिबगिंग करें:
0000043c <libtest2>: 43c: 55 push %ebp 43d: 89 e5 mov %esp,%ebp 43f: 53 push %ebx 440: 83 ec 04 sub $0x4,%esp 443: e8 ef ff ff ff call 437 <__i686.get_pc_thunk.bx> 448: 81 c3 ac 1b 00 00 add $0x1bac,%ebx 44e: 8d 83 d0 e4 ff ff lea -0x1b30(%ebx),%eax 454: 89 04 24 mov %eax,(%esp) 457: e8 f8 fe ff ff call 354 <puts@plt> 45c: 8d 83 fc e4 ff ff lea -0x1b04(%ebx),%eax 462: 89 04 24 mov %eax,(%esp) 465: e8 ea fe ff ff call 354 <puts@plt> 46a: 83 c4 04 add $0x4,%esp 46d: 5b pop %ebx 46e: 5d pop %ebp 46f: c3 ret
CALL निर्देशों के परिचालक पहले से ही अलग और सार्थक हैं, जिसका अर्थ है कि वे किसी चीज़ की ओर इशारा करते हैं। यह अब केवल पैडिंग नहीं है। यह नोट करना भी उपयोगी है कि पुट से पहले () खुद कॉल करें, 0x1FF4 (0x1BAC + 0x448) EBX रजिस्टर को लिखा जाता है। डीबगर 0x448 के प्रारंभिक EBX मान को पहचानने में मदद करता है। तो, यह आगे कहीं काम में आएगा। पता 0x354 हमें एक बहुत ही दिलचस्प खंड ".plt" की ओर ले जाता है, जो ".text" की तरह, निष्पादन योग्य के रूप में चिह्नित है। यहाँ यह है:
Disassembly of section .plt: 00000334 <__gmon_start__@plt-0x10>: 334: ff b3 04 00 00 00 pushl 0x4(%ebx) 33a: ff a3 08 00 00 00 jmp *0x8(%ebx) 340: 00 00 add %al,(%eax) ... 00000344 <__gmon_start__@plt>: 344: ff a3 0c 00 00 00 jmp *0xc(%ebx) 34a: 68 00 00 00 00 push $0x0 34f: e9 e0 ff ff ff jmp 334 <_init+0x30> 00000354 <puts@plt>: 354: ff a3 10 00 00 00 jmp *0x10(%ebx) 35a: 68 08 00 00 00 push $0x8 35f: e9 d0 ff ff ff jmp 334 <_init+0x30> 00000364 <__cxa_finalize@plt>: 364: ff a3 14 00 00 00 jmp *0x14(%ebx) 36a: 68 10 00 00 00 push $0x10 36f: e9 c0 ff ff ff jmp 334 <_init+0x30>
हमारे लिए ब्याज के पते पर 0x354 हमें तीन निर्देश मिलते हैं। उनमें से पहले में, बिना शर्त कूद EBX (0x1FF4) प्लस 0x10 द्वारा इंगित पते पर होता है। सरल गणना के बाद, हमें सूचक 0x2004 का मूल्य मिलता है। ये पते ".got.plt" अनुभाग में आते हैं।
Disassembly of section .got.plt: 00001ff4 <.got.plt>: 1ff4: 20 1f and %bl,(%edi) ... 1ffe: 00 00 add %al,(%eax) 2000: 4a dec %edx 2001: 03 00 add (%eax),%eax 2003: 00 5a 03 add %bl,0x3(%edx) 2006: 00 00 add %al,(%eax) 2008: 6a 03 push $0x3 ...
सबसे दिलचस्प बात तब सामने आती है जब हम इस पॉइंटर को डिफाइन करते हैं और आखिर में 0x35A के बराबर बिना शर्त जम्प एड्रेस प्राप्त करते हैं। लेकिन, यह वास्तव में, निम्नलिखित निर्देश है! इस तरह की जटिल जोड़तोड़ करना और "हिदायत .pl.plt" खंड को केवल अगले निर्देश पर जाने के लिए क्यों आवश्यक था? PLT और GOT क्या है?
PLT (प्रोसीजर लिंकेज टेबल) एक प्रक्रिया लिंकिंग टेबल है। यह निष्पादन योग्य और साझा किए गए मॉड्यूल में मौजूद है। यह स्टब की एक सरणी है, प्रत्येक आयातित फ़ंक्शन के लिए एक है।
PLT[n+1]: jmp *GOT[n+3] push
PLT [n + 1] पर एक फ़ंक्शन कॉल के परिणामस्वरूप GOT [n + 3] पर अप्रत्यक्ष नियंत्रण संक्रमण होगा। पहली कॉल पर, GOT [n + 3] वापस PLT [n + 1] + 6 को इंगित करता है, जो PLT [0] पर अनुक्रम PUSH \ JMP है। PLT [0] से गुजरते हुए, लिंकर 'n' को निर्दिष्ट करने के लिए संग्रहीत स्टैक तर्क का उपयोग करता है और फिर 'n' अक्षर को हल करता है। लिंकर फिर GOT [n + 3] के मूल्य को सही करता है ताकि यह सीधे लक्ष्य की दिनचर्या की ओर इशारा करे, और अंततः इसे कॉल करे। पीएलटी [एन + 1] के बाद की प्रत्येक कॉल को जेएमपी निर्देश के माध्यम से अपने पते के समान संकल्प के बिना लक्ष्य दिनचर्या के लिए निर्देशित किया जाएगा।
पहला PLT तत्व विशेष है और इसका उपयोग डायनेमिक एड्रेस रिज़ॉल्यूशन कोड पर स्विच करने के लिए किया जाता है।
PLT[0]: push &GOT[1] jmp GOT[2] @points to resolver()
नियंत्रण लिंकर कोड को पारित किया है। 'n' पहले से ही स्टैक पर है और पता GOT [1] वहां जोड़ा गया है। इस तरह, लिंकर (/lib/ld-linux.so.2 में स्थित) यह निर्धारित कर सकता है कि किस पुस्तकालय को अपनी सेवाओं की आवश्यकता है।
GOT (Global Offset Table) एक वैश्विक ऑफसेट तालिका है। इसके पहले तीन तत्व आरक्षित हैं। जब GOT को पहली बार आरंभीकृत किया जाता है, तो इसके सभी तत्व जो PLT में पता समाधान से संबंधित होते हैं, PLT को वापस इंगित करते हैं।
ये विशेष तत्व हैं:
- लोडर द्वारा उपयोग की जाने वाली वस्तुओं की सूची [0]
- इस मॉड्यूल के स्थानांतरण तालिका के लिए GOT [1] सूचक
- GOT [2] ld-linux.so.2 पुस्तकालय से बूटलोडर कोड के लिए सूचक
- GOT [3]
- ... निम्नलिखित प्रत्येक आयातित कार्यों को अप्रत्यक्ष रूप से कॉल करने के लिए सहायक मूल्य हैं
- GOT [3 + M]
- GOT [3 + M + 1]
- ... तो वैश्विक चर के संदर्भ के लिए अप्रत्यक्ष संकेत हैं, प्रत्येक ऐसे प्रतीक के लिए
प्रत्येक पुस्तकालय और निष्पादन योग्य का अपना PLT और GOT होता है।
यह कैसे प्रकार R_386_JUMP_SLOT कार्यों का स्थानांतरण है, जिसका उपयोग libtest2.so पुस्तकालय में किया गया था। शेष प्रकार के स्थानांतरण स्थैतिक लेआउट से संबंधित हैं, इसलिए वे हमारे लिए उपयोगी नहीं होंगे।
कोड के बीच का अंतर जो मेमोरी में लोड करने की स्थिति पर निर्भर करता है और इस पर निर्भर नहीं है (PIC) आयातित कार्यों के लिए कॉल को हल करने के तरीकों में निहित है।
महत्वपूर्ण निष्कर्ष
आइए कुछ उपयोगी निष्कर्ष देते हैं:
- आयातित और निर्यात किए गए कार्यों की सभी जानकारी ".dynsym" अनुभाग में पाई जा सकती है
- यदि मॉड्यूल PIC मोड (-fPIC स्विच) में संकलित किया गया था, तो आयातित कार्यों को PLT और GOT के माध्यम से कॉल किया जाएगा, स्थानांतरण प्रत्येक फ़ंक्शन के लिए केवल एक बार किया जाएगा और PLT में एक निश्चित तत्व के पहले निर्देश पर लागू किया जाएगा। वास्तविक स्थानांतरण पर जानकारी ".rel.plt" अनुभाग में पाई जा सकती है
- यदि लाइब्रेरी को संकलित करते समय –fPIC स्विच का उपयोग नहीं किया गया था, तो प्रत्येक रिश्तेदार कॉल अनुदेश के ऑपरेंड पर कई बार रिलैक्स किया जाएगा क्योंकि कोड में कुछ आयातित फ़ंक्शन के कॉल हैं। वास्तविक स्थानांतरण की जानकारी ".rel.dyn" अनुभाग में पाई जा सकती है
नोट: -fPIC संकलन कुंजी 64-बिट आर्किटेक्चर के लिए आवश्यक है। यानी 64-बिट पुस्तकालयों में, आयातित कार्यों के लिए कॉल हमेशा PLT \ GOT के माध्यम से हल किए जाते हैं। इसके अलावा, इस तरह के आर्किटेक्चर सेक्शन पर रिलोकेशन्स को ".rela.plt" और ".rela.dyn" कहा जाता है।
लंबे समय से प्रतीक्षित निर्णय
कुछ डायनामिक लिंक लाइब्रेरी में एक इंपोर्टेड फंक्शन को रीडायरेक्ट करने के लिए, आपको निम्न जानने की जरूरत है:
- इस लायब्रेरी में फ़ाइल सिस्टम में पथ
- वह वर्चुअल पता जिस पर इसे डाउनलोड किया गया है
- फ़ंक्शन का नाम बदलने के लिए।
- वैकल्पिक समारोह का पता
रिवर्स पुनर्निर्देशन करने के लिए आपको मूल फ़ंक्शन का पता भी प्राप्त करना होगा और इस प्रकार, अपनी जगह पर सब कुछ वापस करना होगा।
C पुनर्निर्देशन फ़ंक्शन का प्रोटोटाइप निम्नानुसार है:
void *elf_hook(char const *library_filename, void const *library_address, char const *function_name, void const *substitution_address);
पुनर्निर्देशन एल्गोरिथ्म
यहाँ पुनर्निर्देशन फ़ंक्शन एल्गोरिथ्म है:
- लाइब्रेरी फ़ाइल खोलें।
- हमें ".dynsym" अनुभाग में प्रतीक का सूचकांक याद है, जिसका नाम वांछित फ़ंक्शन के नाम से मेल खाता है।
- हम ".rel.plt" अनुभाग को देखते हैं और निर्दिष्ट सूचकांक के साथ प्रतीक के लिए इसमें फिर से प्लेसमेंट की तलाश करते हैं।
- यदि ऐसा कोई प्रतीक पाया जाता है, तो हम मूल पते को सहेजते हैं, फिर इसे फ़ंक्शन से वापस करने के लिए, और स्थानान्तरण स्थान में इंगित स्थान पर स्थानापन्न फ़ंक्शन का पता लिखें। इस स्थान की गणना मेमोरी में लाइब्रेरी लोडिंग पते और स्थानांतरण में ऑफसेट के योग के रूप में की जाती है। वह सब है। फ़ंक्शन का पता बदल दिया गया है। जब भी लायब्रेरी इस फ़ंक्शन को कॉल करती है पुनर्निर्देशन होगा। हम फ़ंक्शन से बाहर निकलते हैं और मूल का पता वापस करते हैं।
- यदि ऐसा प्रतीक ".rel.plt" अनुभाग में नहीं पाया जाता है, तो हम इसे उसी सिद्धांत के अनुसार "rel.dyn" अनुभाग में खोज रहे हैं। लेकिन, आपको यह याद रखने की जरूरत है कि rel.dyn स्थानांतरण अनुभाग में वांछित सूचकांक के साथ प्रतीक एक से अधिक बार हो सकता है। इसलिए, आप पहले पुनर्निर्देशित के बाद खोज चक्र को पूरा नहीं कर सकते। लेकिन मूल के पते को सूचकांक के पहले संयोग पर याद किया जा सकता है और अब गणना नहीं की जा सकती है - यह अभी भी नहीं बदलता है।
- हम मूल फ़ंक्शन या NULL का पता वापस करते हैं यदि वांछित नाम वाला फ़ंक्शन नहीं मिला।
नीचे C में इस फ़ंक्शन का कोड है:
void *elf_hook(char const *module_filename, void const *module_address, char const *name, void const *substitution) { static size_t pagesize; int descriptor; //file descriptor of shared module Elf_Shdr *dynsym = NULL, // ".dynsym" section header *rel_plt = NULL, // ".rel.plt" section header *rel_dyn = NULL; // ".rel.dyn" section header Elf_Sym *symbol = NULL; //symbol table entry for symbol named "name" Elf_Rel *rel_plt_table = NULL, //array with ".rel.plt" entries *rel_dyn_table = NULL; //array with ".rel.dyn" entries size_t i, name_index = 0, //index of symbol named "name" in ".dyn.sym" rel_plt_amount = 0, // amount of ".rel.plt" entries rel_dyn_amount = 0, // amount of ".rel.dyn" entries *name_address = NULL; //address of relocation for symbol named "name" void *original = NULL; //address of the symbol being substituted if (NULL == module_address || NULL == name || NULL == substitution) return original; if (!pagesize) pagesize = sysconf(_SC_PAGESIZE); descriptor = open(module_filename, O_RDONLY); if (descriptor < 0) return original; if ( section_by_type(descriptor, SHT_DYNSYM, &dynsym) || //get ".dynsym" section symbol_by_name(descriptor, dynsym, name, &symbol, &name_index) || //actually, we need only the index of symbol named "name" in the ".dynsym" table section_by_name(descriptor, REL_PLT, &rel_plt) || //get ".rel.plt" (for 32-bit) or ".rela.plt" (for 64-bit) section section_by_name(descriptor, REL_DYN, &rel_dyn) //get ".rel.dyn" (for 32-bit) or ".rela.dyn" (for 64-bit) section ) { //if something went wrong free(dynsym); free(rel_plt); free(rel_dyn); free(symbol); close(descriptor); return original; } //release the data used free(dynsym); free(symbol); rel_plt_table = (Elf_Rel *)(((size_t)module_address) + rel_plt->sh_addr); //init the ".rel.plt" array rel_plt_amount = rel_plt->sh_size / sizeof(Elf_Rel); //and get its size rel_dyn_table = (Elf_Rel *)(((size_t)module_address) + rel_dyn->sh_addr); //init the ".rel.dyn" array rel_dyn_amount = rel_dyn->sh_size / sizeof(Elf_Rel); //and get its size //release the data used free(rel_plt); free(rel_dyn); //and descriptor close(descriptor); //now we've got ".rel.plt" (needed for PIC) table and ".rel.dyn" (for non-PIC) table and the symbol's index for (i = 0; i < rel_plt_amount; ++i) //lookup the ".rel.plt" table if (ELF_R_SYM(rel_plt_table[i].r_info) == name_index) //if we found the symbol to substitute in ".rel.plt" { original = (void *)*(size_t *)(((size_t)module_address) + rel_plt_table[i].r_offset); //save the original function address *(size_t *)(((size_t)module_address) + rel_plt_table[i].r_offset) = (size_t)substitution; //and replace it with the substitutional break; //the target symbol appears in ".rel.plt" only once } if (original) return original; //we will get here only with 32-bit non-PIC module for (i = 0; i < rel_dyn_amount; ++i) //lookup the ".rel.dyn" table if (ELF_R_SYM(rel_dyn_table[i].r_info) == name_index) //if we found the symbol to substitute in ".rel.dyn" { name_address = (size_t *)(((size_t)module_address) + rel_dyn_table[i].r_offset); //get the relocation address (address of a relative CALL (0xE8) instruction's argument) if (!original) original = (void *)(*name_address + (size_t)name_address + sizeof(size_t)); //calculate an address of the original function by a relative CALL (0xE8) instruction's argument mprotect((void *)(((size_t)name_address) & (((size_t)-1) ^ (pagesize - 1))), pagesize, PROT_READ | PROT_WRITE); //mark a memory page that contains the relocation as writable if (errno) return NULL; *name_address = (size_t)substitution - (size_t)name_address - sizeof(size_t); //calculate a new relative CALL (0xE8) instruction's argument for the substitutional function and write it down mprotect((void *)(((size_t)name_address) & (((size_t)-1) ^ (pagesize - 1))), pagesize, PROT_READ | PROT_EXEC); //mark a memory page that contains the relocation back as executable if (errno) //if something went wrong { *name_address = (size_t)original - (size_t)name_address - sizeof(size_t); //then restore the original function address return NULL; } } return original; }
परीक्षण मामलों के साथ इस फ़ंक्शन का पूर्ण कार्यान्वयन
डाउनलोड के लिए उपलब्ध है।
हम अपने परीक्षण कार्यक्रम को फिर से लिखते हैं:
#include <stdio.h> #include <dlfcn.h> #include "elf_hook.h" #define LIBTEST1_PATH "libtest1.so" //position dependent code (for 32 bit only) #define LIBTEST2_PATH "libtest2.so" //position independent code void libtest1(); //from libtest1.so void libtest2(); //from libtest2.so int hooked_puts(char const *s) { puts(s); //calls the original puts() from libc.so because our main executable module called "test" is intact by hook puts("is HOOKED!"); } int main() { void *handle1 = dlopen(LIBTEST1_PATH, RTLD_LAZY); void *handle2 = dlopen(LIBTEST2_PATH, RTLD_LAZY); void *original1, *original2; if (NULL == handle1 || NULL == handle2) fprintf(stderr, "Failed to open \"%s\" or \"%s\"!\n", LIBTEST1_PATH, LIBTEST2_PATH); libtest1(); //calls puts() from libc.so twice libtest2(); //calls puts() from libc.so twice puts("-----------------------------"); original1 = elf_hook(LIBTEST1_PATH, LIBRARY_ADDRESS_BY_HANDLE(handle1), "puts", hooked_puts); original2 = elf_hook(LIBTEST2_PATH, LIBRARY_ADDRESS_BY_HANDLE(handle2), "puts", hooked_puts); if (NULL == original1 || NULL == original2) fprintf(stderr, "Redirection failed!\n"); libtest1(); //calls hooked_puts() twice libtest2(); //calls hooked_puts() twice puts("-----------------------------"); original1 = elf_hook(LIBTEST1_PATH, LIBRARY_ADDRESS_BY_HANDLE(handle1), "puts", original1); original2 = elf_hook(LIBTEST2_PATH, LIBRARY_ADDRESS_BY_HANDLE(handle2), "puts", original2); if (NULL == original1 || original1 != original2) //both pointers should contain hooked_puts() address now fprintf(stderr, "Restoration failed!\n"); libtest1(); //again calls puts() from libc.so twice libtest2(); //again calls puts() from libc.so twice dlclose(handle1); dlclose(handle2); return 0; }
हम संकलन करते हैं:
gcc -g3 -m32 -shared -o libtest1.so libtest1.c gcc -g3 -m32 -fPIC -shared -o libtest2.so libtest2.c gcc -g3 -m32 -L$PWD -o test test.c elf_hook.c -ltest1 -ltest2 -ldl
और चलाएं:
export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH ./test
निष्कर्ष निम्नलिखित है:
libtest1: 1st call to the original puts()
libtest1: 2nd call to the original puts()
libtest2: 1st call to the original puts()
libtest2: 2nd call to the original puts()
-----------------------------
libtest1: 1st call to the original puts()
is HOOKED!
libtest1: 2nd call to the original puts()
is HOOKED!
libtest2: 1st call to the original puts()
is HOOKED!
libtest2: 2nd call to the original puts()
is HOOKED!
-----------------------------
libtest1: 1st call to the original puts()
libtest1: 2nd call to the original puts()
libtest2: 1st call to the original puts()
libtest2: 2nd call to the original puts()
यह शुरुआत में निर्धारित कार्य की पूर्णता को दर्शाता है। हुर्रे!
उस पते का पता कैसे लगाएं जहां साझा पुस्तकालय भरी हुई थी?
यह बहुत ही दिलचस्प सवाल उठता है जब आप पुनर्निर्देशित फ़ंक्शन के प्रोटोटाइप पर ध्यान से विचार करते हैं। कुछ शोध के बाद, मैं अपने डिस्क्रिप्टर से लाइब्रेरी लोडिंग पते को निर्धारित करने का एक तरीका खोजने में कामयाब रहा, जो कि dlopen () फ़ंक्शन रिटर्न देता है। यह इस मैक्रो के साथ किया जाता है:
#define LIBRARY_ADDRESS_BY_HANDLE(dlhandle) ((NULL == dlhandle) ? NULL : (void*)*(size_t const*)(dlhandle))
किसी नए फ़ंक्शन का पता कैसे रिकॉर्ड करें और पुनर्स्थापित करें?
".Rel.plt" अनुभाग से रिलोकेशन को इंगित करने वाले रीराइटिंग पतों के बारे में कोई समस्या नहीं है। अनिवार्य रूप से, ".plt" अनुभाग से संबंधित तत्व के JMP निर्देश का संचालन अधिलेखित है। और इस तरह के एक निर्देश के ऑपरेंड सिर्फ पते हैं।
अधिक दिलचस्प रिश्तेदार कॉल निर्देशों (कोड ई 8) के ऑपरेंड के लिए स्थानांतरण के आवेदन के साथ स्थिति है। उनमें संक्रमण के पते की गणना सूत्र द्वारा की जाती है:
address_of_a_function = CALL_argument + address_of_the_next_instruction
तो हम मूल फ़ंक्शन का पता लगा सकते हैं।
पिछले सूत्र से, हमें वह मान प्राप्त होता है जिसे हमें फ़ंक्शन को कॉल करने के लिए रिश्तेदार कॉल के तर्क के रूप में लिखा जाना चाहिए: ".rel.dyn" अनुभाग "RE" के रूप में चिह्नित सेगमेंट में आता है, जिसका अर्थ है कि इसके लिए पते लिखना केवल काम नहीं करता है। । आपको उस पृष्ठ के लिए लिखित अनुमति को जोड़ने की आवश्यकता है जिसके लिए आपको स्थानांतरित करना होगा और पुनर्निर्देशन के बाद सब कुछ पुनर्निर्देशित करना न भूलें। यह mprotect () फ़ंक्शन का उपयोग करके किया जाता है। इस फ़ंक्शन का पहला पैरामीटर उस पृष्ठ का पता है जिसमें रिलोकेशन है। यह हमेशा पृष्ठ आकार का एक से अधिक होना चाहिए। इसकी गणना करना मुश्किल नहीं है: आपको बस स्थानांतरण पते (पृष्ठ आकार के आधार पर) के कुछ कम से कम महत्वपूर्ण बाइट्स को शून्य करने की आवश्यकता है: उदाहरण के लिए, 32-बिट सिस्टम पर आकार 4096 (0x1000) बाइट्स के पृष्ठों के लिए, उपरोक्त अभिव्यक्ति को परिवर्तित किया गया है:CALL_argument = address_of_a_function - address_of_the_next_instruction
page_address = (size_t)relocation_address & ( ((size_t) -1) ^ (pagesize - 1) );
page_address = (size_t)relocation_address & (0xFFFFFFFF ^ 0xFFF) = (size_t)relocation_address & 0xFFFFF000;
आप sysconf (_SC_PAGESIZE) कॉल करके एक पृष्ठ का आकार पता कर सकते हैं।उदाहरण का उपयोग करें
एक अभ्यास के रूप में, आप फ़ायरफ़ॉक्स में एक प्लग-इन लिख सकते हैं जो सभी नेटवर्क कॉल को अपने आप में रीडायरेक्ट करेगा, उदाहरण के लिए, एडोब फ्लैश प्लग-इन (libflashplayer.so)। इस प्रकार, आप ब्राउज़र के नेटवर्क कॉल और अन्य प्लग-इन को प्रभावित किए बिना फ़ायरफ़ॉक्स प्रक्रिया से इंटरनेट पर सभी एडोब फ्लैश ट्रैफ़िक को नियंत्रित कर सकते हैं।सौभाग्य हैसंबंधित लिंक
उन लोगों को अंग्रेजी में पढ़ने की इच्छा है , रूसी में मैं पहली बार प्रकाशित करता हूं।