Programs
Trong task này, chúng tôi nghiên cứu cách các chương trình Set-UID đối phó với một số biến môi trường. Một số biến môi trường, bao gồm LD_PRELOAD, LD_LIBRARY PATH và LD_* khác ảnh hưởng đến hành vi của trình liên kết động. Trình liên kết động là một phần của hệ điều hành (OS) tải (liên tục từ bộ nhớ lưu trữ đến RAM) và liên kết các thư viện được chia sẻ cần thiết bởi một tệp thực thi tại thời điểm chạy.
Trong Linux, ld.so hoặc ld-linux.so, là trình liên kết động (mỗi loại cho các kiểu nhị phân khác nhau). Trong Linux, LD_LIBRARY PATH là một tập hợp các thư mục được phân tách bằng dấu hai chấm, nơi các thư viện sẽ được tìm kiếm đầu tiên, trước tập thư mục tiêu chuẩn. LD_PRELOAD chỉ định danh sách các thư viện được chia sẻ bổ sung, do người dùng chỉ định, sẽ được tải trước tất cả các thư viện khác. Trong task này, chúng ta sẽ chỉ nghiên cứu LD PRELOAD.
Đầu tiên, ta sẽ xây dựng một thư viện liên kết động. Trước hết, tạo một chương trình có tên mylib.c, nó có chức năng đơn giản là ghi đè hàm function sleep() vào libc của hệ thống. Thực hiện biên dịch và chạy chương trình trên bằng các lệnh.
gcc -fPIC -g -c mylib.c (trong đó -fPIC có nghĩa là phát ra mã độc lập về vị trí, thích hợp cho liên kết động và tránh bất kỳ giới hạn nào về kích thước của bảng bù toàn cục, -g có nghĩa là tạo ra thông tin gỡ lỗi và -c có nghĩa là biên dịch tệp nhưng không liên kết).
Bảng bù đắp toàn cục, hoặc GOT( Global Offset Table), là một phần của bộ nhớ chương trình máy tính được sử dụng để cho phép mã chương trình máy tính được biên dịch dưới dạng tệp ELF để chạy chính xác, không phụ thuộc vào địa chỉ bộ nhớ nơi mã hoặc dữ liệu của chương trình được tải trong thời gian chạy.
gcc -shared -o filename mylib.o -lc (trong đó -shared tạo ra một đối tượng được chia sẻ có thể được liên kết với các đối tượng khác để tạo thành một tệp thực thi, -o tệp lưu trữ kết quả đầu ra trong tệp.)
Tiếp theo, chúng tôi đề cập đến tệp đầu ra thực thi này là giá trị của biến LD_PRELOAD. Điều này làm cho bất kỳ chương trình nào phải tải thư viện này trước khi thực thi chương trình.
Sau đó, ở cùng thư mục task7 này tạo ra một chương trình gọi hàm sleep(), biên dịch và thực thi chương trình ấy. Và khi ta thực thi chương trình này với một user thường, ta có thể thấy chương trình gọi hàm sleep() được định nghĩa bởi chương trình này myprogcompiled và in ra dòng chữ mà ta đã code trong chương trình.
Khi chạy cùng một chương trình trong các tình huống khác nhau như được chỉ định trong tài liệu, ta nhận thấy rằng trong một số tình huống nhất định, thư viện chứa
hàm sleep() của ta không được gọi và thay vào đó, hàm sleep() do hệ thống xác định đã được thực thi. Để hiểu được hành vi này, ta cần chỉnh sửa chương trình của mình và thêm một lệnh gọi hệ thống để thực thi env | grep LD để xem các biến môi trường của quy trình. Như ta đã đề cập đến grep LD vì cách duy nhất mà bất kỳ chương trình nào tải thư viện đã xác định của tôi cũng thực thi hàm sleep() là bằng biến môi trường LD_PRELOAD. Chương trình myprogram.c được chỉnh sửa như sau.
Trường hợp 1: Sau khi đã tiến hành chỉnh sửa thêm lệnh system(“env | grep LD”);
ta tiến hành biên dịch và thực thi lại chương trình cũng là bằng user thường. Lúc này output của chương trình lần này là dòng in ra hàm sleep() và các biến môi trường của chương trình chứa chuỗi “LD”. Như ta đã thấy, tiến trình này sử dụng LD_PRELOAD như một trong những biến môi trường của nó.
Trường hợp 2: Ta đặt chương trình này thành chương trình root SET-UID và cho
chương trình chạy lại. Output cho thấy rằng thư viện chứa hàm sleep() của chương trình không được gọi và cũng cho thấy rằng biến môi trường của quá trình đó không chứa biến LD_PRELOAD. Điều này cho thấy rằng tiến trinh con SET-UID đã được tạo không kế
thừa biến LD_PRELOAD và do đó nó không tải thư viện và hàm sleep() do hệ thống xác định khi chương trình sleep().
Trường hợp 3: Vì chương trình đã là chương trình SET-UID root, ta chỉ cần đăng
nhập vào tài khoản user root và xác định biến LD_PRELOAD. Khi chạy chương trình, ta thấy rằng function sleep() do người dùng định nghĩa được thực thi và biến LD_PRELOAD hiện diện. Điều này xảy ra bởi vì ta đang ở trong tài khoản root và chủ sở hữu của function cũng là root. Điều này làm cho quá trình có cùng real ID và effective ID, do đó biến LD_PRELOAD không bị loại bỏ.
Tiếp theo, ta tạo một tài khoản user mới quochoang dựa trên tài khoản root.
Trường hợp 4: Ta tiến hành tạo một bản sao myprocompiled1 của
myprogcompiled để hạn chế nhầm lẫn.
Tiếp theo, ta đặt chủ sở hữu của tệp này là quochoang (một tài khoản người dùng khác không phải root) và đặt nó thành chương trình SET-UID. Sau đó, ta đăng nhập vào tài khoản user quochoang và đặt lại biến LD_PRELOAD. Khi chạy lại chương trình, chúng ta thấy rằng hàm sleep() do người user định nghĩa được gọi và biến LD_PRELOAD cũng có trong tiến trình hiện tại.
Hành vi này chỉ ra rằng biến LD_PRELOAD hiện diện nếu real ID và effective ID giống nhau và bị loại bỏ nếu chúng khác nhau. Điều này là do cơ chế bảo mật của chương trình SET-UID. Trong trường hợp đầu tiên, thứ ba và thứ tư, vì chủ sở hữu và tài khoản user thực thi tệp là giống nhau, biến LD_PRELOAD luôn có mặt và thư viện do người dùng xác định được tải trước. Trong khi đó, trong trường hợp thứ hai, effective ID là của user root và real ID là của user seed, biến LD_PRELOAD bị loại bỏ và thay vào đó thư viện liên kết động lib.c, hàm sleep() do hệ thống xác định sẽ được gọi, đây là biện pháp đối phó của hệ thống UNIX đối với các cuộc tấn công.