এটি কীভাবে কোডটি পরিবর্তন করবে, যেমন ফাংশন কলগুলি?
উত্তর:
পিআইই হ'ল এক্সিকিউটেবল ফাইলগুলিতে অ্যাড্রেস স্পেস লেআউট র্যান্ডমাইজেশন (এএসএলআর) সমর্থন করে।
পিআইই মোডটি তৈরি হওয়ার আগে, প্রোগ্রামটির এক্সিকিউটেবল মেমরির এলোমেলো ঠিকানায় রাখা যায় না, কেবলমাত্র অবস্থানের স্বাধীন কোড (পিআইসি) ডায়নামিক লাইব্রেরিগুলি এলোমেলো অফসেটে স্থানান্তরিত করা যেতে পারে could এটি ডায়নামিক লাইব্রেরিগুলির জন্য পিআইসি যেভাবে কাজ করে তার মতো কাজ করে, পার্থক্যটি হ'ল একটি প্রক্রিয়া লিংকেজ টেবিল (পিএলটি) তৈরি করা হয় না, পরিবর্তে পিসি-আপেক্ষিক স্থানান্তর ব্যবহৃত হয়।
জিসিসি / লঙ্কারগুলিতে পিআইই সমর্থন সক্ষম করার পরে, প্রোগ্রামের মূল অংশটি সংকলিত হয় এবং অবস্থান-স্বাধীন কোড হিসাবে সংযুক্ত করা হয়। একটি গতিশীল লিঙ্কার গতিশীল লাইব্রেরির মতোই প্রোগ্রাম মডিউলে পুরো স্থান পরিবর্তন প্রক্রিয়াজাতকরণ করে। গ্লোবাল অফসেট টেবিল (জিওটি) এর মাধ্যমে গ্লোবাল ডেটার যে কোনও ব্যবহার অ্যাক্সেসে রূপান্তরিত হয় এবং জিওটি স্থানান্তর যুক্ত হয়।
এই ওপেনবিএসডি পিআইই উপস্থাপনায় পিআইই ভালভাবে বর্ণনা করা হয়েছে ।
ফাংশনগুলির পরিবর্তনগুলি এই স্লাইডে দেখানো হয়েছে (পিআইই বনাম পিআইসি)।
x86 পিক বনাম পাই
স্থানীয় গ্লোবাল ভেরিয়েবল এবং ফাংশন পাইতে অনুকূলিত হয়
বাহ্যিক গ্লোবাল ভেরিয়েবল এবং ফাংশনগুলি পিকের মতো
এবং এই স্লাইডে (PIE বনাম পুরানো-শৈলীর লিঙ্কিং)
x86 পাই বনাম কোনও পতাকা (স্থির)
স্থানীয় গ্লোবাল ভেরিয়েবল এবং ফাংশন স্থির সমান
বাহ্যিক গ্লোবাল ভেরিয়েবল এবং ফাংশনগুলি পিকের মতো
দ্রষ্টব্য, PIE এর সাথে বেমানান হতে পারে -static
সর্বনিম্ন চলমান উদাহরণ: জিডিবি দু'বার নির্বাহযোগ্য
যারা কিছু পদক্ষেপ দেখতে চান তাদের জন্য আসুন আমরা পিআইই এক্সিকিউটেবলের উপর ASLR এর কাজ দেখি এবং রানগুলি জুড়ে ঠিকানাগুলি পরিবর্তন করি:
main.c
#include <stdio.h>
int main(void) {
puts("hello");
}
main.sh
#!/usr/bin/env bash
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
for pie in no-pie pie; do
exe="${pie}.out"
gcc -O0 -std=c99 "-${pie}" "-f${pie}" -ggdb3 -o "$exe" main.c
gdb -batch -nh \
-ex 'set disable-randomization off' \
-ex 'break main' \
-ex 'run' \
-ex 'printf "pc = 0x%llx\n", (long long unsigned)$pc' \
-ex 'run' \
-ex 'printf "pc = 0x%llx\n", (long long unsigned)$pc' \
"./$exe" \
;
echo
echo
done
যাঁর সাথে রয়েছে তার জন্য -no-pie
, সবকিছু বিরক্তিকর:
Breakpoint 1 at 0x401126: file main.c, line 4.
Breakpoint 1, main () at main.c:4
4 puts("hello");
pc = 0x401126
Breakpoint 1, main () at main.c:4
4 puts("hello");
pc = 0x401126
সম্পাদন শুরুর আগে break main
একটি ব্রেকপয়েন্ট সেট করে 0x401126
।
তারপরে, উভয় ফাঁসি কার্যকর করার সময়, run
ঠিকানায় থামে 0x401126
।
সঙ্গে এক -pie
কিন্তু আরো অনেক আকর্ষণীয় হল:
Breakpoint 1 at 0x1139: file main.c, line 4.
Breakpoint 1, main () at main.c:4
4 puts("hello");
pc = 0x5630df2d6139
Breakpoint 1, main () at main.c:4
4 puts("hello");
pc = 0x55763ab2e139
মৃত্যুদন্ড শুরু করার আগে, শুধু GDB একটি "ডামি" ঠিকানাটি যে এক্সিকিউটেবল উপস্থিত লাগে: 0x1139
।
এটি আরম্ভ করার পরে, জিডিবি বুদ্ধিমানভাবে লক্ষ্য করেছে যে গতিশীল লোডার প্রোগ্রামটিকে একটি অন্য স্থানে রেখেছিল এবং প্রথম বিরতিটি বন্ধ হয়ে যায় 0x5630df2d6139
।
তারপরে, দ্বিতীয় রানটিও বুদ্ধিমানের সাথে লক্ষ্য করল যে এক্সিকিউটেবলটি আবার সরে গেছে, এবং শেষ হয়ে গেল 0x55763ab2e139
।
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
ASLR চালু রয়েছে তা নিশ্চিত করে (উবুন্টু 17.10 এ ডিফল্ট): আমি কীভাবে অস্থায়ীভাবে ASLR (ঠিকানা স্থানের বিন্যাস র্যান্ডমাইজেশন) অক্ষম করতে পারি? | উবুন্টুকে জিজ্ঞাসা করুন ।
set disable-randomization off
অন্যথায় প্রয়োজন হয় জিডিবি, নাম অনুসারে, ডিবাগিংয়ের অভিজ্ঞতার উন্নতি করার জন্য রানের জুড়ে নির্দিষ্ট ঠিকানা দেওয়ার জন্য ডিফল্টরূপে প্রক্রিয়াটির জন্য এএসএলআর বন্ধ করে দেয়: জিডিবি ঠিকানা এবং "প্রকৃত" ঠিকানাগুলির মধ্যে পার্থক্য? | স্ট্যাক ওভারফ্লো ।
readelf
বিশ্লেষণ
তদতিরিক্ত, আমরা এটি পর্যবেক্ষণ করতে পারি:
readelf -s ./no-pie.out | grep main
আসল রানটাইম লোড ঠিকানা দেয় (পিসি নিম্নলিখিত নির্দেশাবলী 4 বাইট পরে নির্দেশিত):
64: 0000000000401122 21 FUNC GLOBAL DEFAULT 13 main
যখন:
readelf -s ./pie.out | grep main
একটি অফসেট দেয়:
65: 0000000000001135 23 FUNC GLOBAL DEFAULT 14 main
এএসএলআর বন্ধ করে (উভয় randomize_va_space
বা দিয়ে set disable-randomization off
), জিডিবি সর্বদা main
ঠিকানা দেয় : 0x5555555547a9
তাই আমরা অনুমান করি যে -pie
ঠিকানাটি এর থেকে রচিত:
0x555555554000 + random offset + symbol offset (79a)
TODO যেখানে 0x555555554000 হার্ড লিনাক্স কার্নেল / glibc লোডার / কোথাও কোডড আছে? লিনাক্সে পিআইই এক্সিকিউটেবলের পাঠ্য বিভাগের ঠিকানা কীভাবে নির্ধারণ করা হয়?
ন্যূনতম সমাবেশ উদাহরণ
আর একটি দুর্দান্ত জিনিস যা আমরা করতে পারি তা হ'ল পিআইই মানে কী তা আরও দৃ concrete়তার সাথে বোঝার জন্য কিছু অ্যাসেম্বলি কোড নিয়ে ঘুরে বেড়ানো।
আমরা এটি লিনাক্স x86_64 ফ্রিস্ট্যান্ডিং অ্যাসেম্বলি হ্যালো ওয়ার্ল্ডের মাধ্যমে করতে পারি:
main.S
.text
.global _start
_start:
asm_main_after_prologue:
/* write */
mov $1, %rax /* syscall number */
mov $1, %rdi /* stdout */
mov $msg, %rsi /* buffer */
mov $len, %rdx /* len */
syscall
/* exit */
mov $60, %rax /* syscall number */
mov $0, %rdi /* exit status */
syscall
msg:
.ascii "hello\n"
len = . - msg
এবং এটি একত্রিত হয় এবং এর সাথে সূক্ষ্মভাবে চলে:
as -o main.o main.S
ld -o main.out main.o
./main.out
তবে, আমরা যদি এটির সাথে পিআইই হিসাবে লিঙ্ক করার চেষ্টা করি (তবে --no-dynamic-linker
এখানে বর্ণিত হিসাবে প্রয়োজনীয়: লিনাক্সে কীভাবে একটি স্ট্যাটিকালি লিঙ্কড পজিশন স্বাধীন এক্সিকিউটেবল ইএলএফ তৈরি করতে পারি? ):
ld --no-dynamic-linker -pie -o main.out main.o
তারপরে লিঙ্কটি ব্যর্থ হবে:
ld: main.o: relocation R_X86_64_32S against `.text' can not be used when making a PIE object; recompile with -fPIC
ld: final link failed: nonrepresentable section on output
কারণ লাইন:
mov $msg, %rsi /* buffer */
mov
অপারেন্ডে বার্তাটির ঠিকানাটি হার্ডকোড করে এবং তাই অবস্থানটি স্বাধীন নয় position
যদি আমরা পরিবর্তে এটি একটি অবস্থানের মতো স্বতন্ত্র উপায়ে লিখি:
lea msg(%rip), %rsi
তারপরে পিআইই লিঙ্কটি সূক্ষ্মভাবে কাজ করে এবং জিডিবি আমাদের দেখায় যে এক্সিকিউটেবলটি প্রতিবার মেমরিতে পৃথক স্থানে লোড হয় get
এখানে পার্থক্যটি হ'ল সিন্টেক্সের কারণে বর্তমান পিসি lea
ঠিকানার সাথে msg
সম্পর্কিত ঠিকানাটি এনকোড করা হয়েছে rip
, এছাড়াও দেখুন: 64৪-বিট বিধানসভা প্রোগ্রামে আরআইপি রিলেটিভ অ্যাড্রেসিং কীভাবে ব্যবহার করবেন?
উভয় সংস্করণকে এগুলি ছড়িয়ে দিয়ে আমরা এটিও বের করতে পারি:
objdump -S main.o
যা যথাক্রমে দেয়:
e: 48 c7 c6 00 00 00 00 mov $0x0,%rsi
e: 48 8d 35 19 00 00 00 lea 0x19(%rip),%rsi # 2e <msg>
000000000000002e <msg>:
2e: 68 65 6c 6c 6f pushq $0x6f6c6c65
সুতরাং আমরা স্পষ্টভাবে দেখতে পাই যে lea
ইতিমধ্যে msg
বর্তমান ঠিকানা + 0x19 হিসাবে এনকোডের পুরো সঠিক ঠিকানা রয়েছে ।
mov
সংস্করণ কিন্তু করতে ঠিকানা সেট করেছে 00 00 00 00
, যার মানে একটি স্থানান্তরের সেখানে সম্পাদিত হবে: linkers কি করবেন? রহস্যপূর্ণ R_X86_64_32S
মধ্যে ld
ত্রুটি বার্তা স্থানান্তরের প্রকৃত যে ধরনের প্রয়োজন ছিল এবং যা পাই এক্সেকিউটেবল ঘটতে পারে না।
আর একটি মজাদার জিনিস যা আমরা করতে পারি তা হ'ল msg
ডেটা বিভাগে .text
রেখে দেওয়া:
.data
msg:
.ascii "hello\n"
len = . - msg
এখন এতে .o
একত্রিত হন:
e: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 15 <_start+0x15>
সুতরাং এখন আরআইপি অফসেট 0
, এবং আমরা অনুমান করি যে এসেম্বেলারের দ্বারা একটি স্থান পরিবর্তন করার জন্য অনুরোধ করা হয়েছে। আমরা এটি দিয়ে নিশ্চিত করি:
readelf -r main.o
যা দেয়:
Relocation section '.rela.text' at offset 0x160 contains 1 entry:
Offset Info Type Sym. Value Sym. Name + Addend
000000000011 000200000002 R_X86_64_PC32 0000000000000000 .data - 4
তাই পরিষ্কারভাবে R_X86_64_PC32
একটি পিসি আপেক্ষিক স্থানান্তর যা ld
PIE এক্সিকিউটেবলের জন্য পরিচালনা করতে পারে।
এই পরীক্ষাটি আমাদের শিখিয়েছিল যে লিঙ্কার নিজেই প্রোগ্রামটি পরীক্ষা করে পিআইই হতে পারে এবং এটি হিসাবে চিহ্নিত করে।
তারপরে জিসিসির সাথে সংকলন করার সময়, -pie
জিসিসিকে অবস্থান স্বাধীন সমাবেশ তৈরি করতে বলে।
তবে আমরা যদি নিজেরাই সমাবেশ লিখি তবে আমাদের অবশ্যই ম্যানুয়ালি নিশ্চিত করতে হবে যে আমরা অবস্থানের স্বাধীনতা অর্জন করেছি।
এআরএমভি 8 আর্চ 64 এ, এডিআর নির্দেশের মাধ্যমে অবস্থান স্বাধীন হ্যালো ওয়ার্ল্ড অর্জন করা যেতে পারে ।
কোনও ইএলএফ অবস্থান স্বাধীন কিনা তা কীভাবে নির্ধারণ করবেন?
এটি কেবল জিডিবি দিয়ে চালানো ছাড়াও কয়েকটি স্থিতিশীল পদ্ধতিতে এখানে উল্লেখ করা হয়েছে:
উবুন্টুতে পরীক্ষা করা হয়েছে 18.10।