Liên kết động trong Linux và Windows (phần II)
Xem lại Phần I
Phần II
Bài này thảo luận khái niệm thư viện chia sẻ trong cả Windows và Linux.
Đồng thời cũng lướt qua các kiểu cấu trúc dữ liệu khác nhau để giải thích
liên kết động làm việc như thế nào trong các hệ điều hành này. Bài này rất
hữu ích cho các nhà phát triển hứng thú nghiên cứu vấn đề về hàm ẩn bảo
mật, liên quan tới tốc độ liên kết động. Và cũng khẳng định một số kiến thức
cơ bản về liên kết động đã được đưa ra trước đây.
Phần một giới thiệu các khái niệm cho cả Linux và Windows, trong đó tập
trung chủ yếu vào Linux. Bây giờ trong phần hai chúng ta sẽ xem xét liên
kết động hoạt động như thế nào trong Windows và tiến tới so sánh hai môi
trường với nhau. Các bạn nên xem lại phần một trước khi tiếp tục với phần
hai này.
Cấu trúc dữ liệu Windows Portable Executable File Format (PE)
Chúng ta biết rằng một section (phân đoạn) là một đoạn mã lệnh hay dữ liệu
hợp logic với nhau, và dữ liệu cho một bảng nhập thực thi thì nằm trong một
section. Trong phần này chúng ta sẽ xem xét một số section trong các file
Windows PE.
Exports section (.edata)
Section “.edata” bắt đầu với cấu trúc thư mục xuất
IMAGE_EXPORT_DIRECTORY. Thư mục xuất bao gồm RVAs (relative
virtual addresses - địa chỉ ảo quan hệ) của Bảng địa chỉ xuất. Bảng này bao
gồm địa chỉ của các điểm vào xuất, dữ liệu xuất và tuyệt đối. Một dãy số thứ
tự được dùng để đánh chỉ mục cho bảng địa chỉ. Cơ sở thứ tự (Ordinal Base)
phải được trừ cho các số thứ tự trước khi đánh chỉ mục vào bảng.
Export Name Table Pointers (Các con trỏ bảng tên xuất): mảng này chứa
địa chỉ trong bảng tên xuất. Các con trỏ liên quan tới cớ sở hình ảnh và được
sắp xếp theo thứ tự từ vựng để tìm kiếm nhị phân. Export Name Table bao
gồm các tên theo chuẩn ASCII của các điểm vào xuất khẩu hình ảnh.
Export Ordinal Table (Bảng thứ tự xuất): các con trỏ bảng tên xuất khẩu và
bảng thứ tự xuất khẩu tạo thành hai mảng song song. Mảng bảng thứ tự xuất
bao gồm thứ tự kết hợp với tên xuất được trỏ bởi các con trỏ bảng tên xuất.
Thứ tự sẽ được dùng như là chỉ mục trong EAT.
Imports Section(.idata)
Section “.idata” là ngược lại với section e.data mô tả ở trên. Nó bản đồ hoá
các symbol hoặc thứ tự trở lại RVAs. Phân đoạn i.data bắt đầu với một bảng
thư mục nhập IMAGE_IMPORT_DIRECTORY. Bảng thư mục nhập này bao
gồm một mảng cấu trúc IMAGE_IMPORT_DESCRIPTOR. Trong đó mỗi
phần tử tương ứng với một thực thi. IMAGE_IMPORT_DESCRIPTOR bao
gồm RVAs của:
Import Lookup Table (Bảng tra tìm nhập): đây là mảng của cấu trúc
IMAGE_THUNK_DATA. Cấu trúc này bao gồm thứ tự hay hint (gợi ý) hoặc
tên của mỗi hàm nhập. Bảng nhận dạng các ký tự nhập khẩu, với điểm vào
trong Bảng tra tìm nhập khẩu là song song với điểm vào trong Bảng địa chỉ
nhập (Import Address Table - IAT). Nếu bit cao của điểm vào được thiết lập,
các bit thấp sẽ là thứ tự. Nếu không thì điểm vào là một RVA của đầu vào
trong bảng hint-name.
Import Address Table (Bảng điạ chỉ nhập): đây cũng là một mảng của cấu
trúc IMAGE_THUNK_DATA. Ban đầu cả Bảng tra tìm nhập khẩu và Bảng
địa chỉ nhập khẩu chứa các điểm vào tương tự nhau. Bộ nạp có tác dụng điền
địa chỉ cho các chu trình nhập trong bảng này, trong khi đó các mục trong
Import Lookup Table giữ lại dữ liệu gốc như trước. Chúng ta sẽ xem vì sao
mối liên kết lại duy trì thông tin gốc muộn hơn khi chúng ta nói đến sự liên
kết.
Hint-name Table (Bảng gợi ý-tên): Bảng bao gồm một hint (gợi ý) 4 byte
theo sau tên với ký tự kết thúc là null. Giá trị hint được dùng để đánh chỉ
mục cho mảng Con trỏ bảng tên xuất khẩu, cho phép nhanh hơn là nhập
bằng tên. Hint sẽ chính xác nếu DDL không thay đổi hay ít nhất là danh sách
ký tự xuất khẩu của nó không thay đổi. Nếu hint không chính xác thì việc
tìm kiếm nhị phân sẽ diễn ra trên bảng Export Name Pointer.
Cách thức hoạt động
Nạp một thực thi Windows và DDL cũng tương tự như nạp một chương
trình ELF liên kết động trong Linux. Sự khác nhau là, ở đây mối liên kết là
một phần của bản thân nhân kernel. Đầu tiên nhân kernel sẽ bản đồ hoá một
thực thi dẫn bởi tiêu đề của PE. Bộ nạp xem xét IAT của modul và do tìm
xem liệu DDL có phụ thuộc vào phần thêm DDLs hay không. Nếu có thì bộ
nạp cũng bản đồ hoá chúng. Tiến trình này tiếp tục cho đến khi tất cả modul
phụ thuộc đã được bản đồ hoá vào bộ nhớ.
Một hàm nhập có thể được lập danh sách theo tên hoặc theo số thứ tự. Thứ
tự thể hiện vị trí của nó trong Bảng địa chỉ xuất khẩu DDL. Nếu lập danh
sách bằng tên, bộ nạp thực hiện cuộc tìm kiếm nhị phân trong Bảng con trỏ
tên xuất khẩu của DDL tương ứng để tra tìm chỉ mục của ký tự đã biết. Sau
đó nó dùng chỉ mục này như là chỉ mục trong Bảng thứ tự xuất khẩu để lấy
ra thứ tự. Sau đó thứ tự này trả ra giá trị được dùng như chỉ mục trong Bảng
địa chỉ xuất khẩu. Việc thêm RVA của ký tự tìm thấy trong bảng EAT thành
địa chỉ nạp của DDL tương ứng tạo ra điạ chỉ tuyệt đối mà bộ nạp ghi trong
mục tương ứng của bảng IAT.
Nạp trễ trong Windows
Một DDL nạp trễ có cấu trúc ImgDelayDescr tương tự cấu trúc thư mục
nhập khẩu .idata, nhưng nó không nằm trong phân đoạn .idata.
ImgDelayDescr bao gồm điạ chỉ của một IAT và một INT cho DDL. Những
bảng này giống hệt định dạng của các bảng nhập thông thường khác. Nhưng
chúng được ghi và đọc bởi mã thư viện thời gian thực hơn là bởi hệ điều
hành. Khi lần đầu tiên bạn gọi một API từ một DDL nạp trễ, thư viện thời
gian thực nạp DDL (nếu cần), lấy địa chỉ và lưu trữ nó trong IAT nạp trễ để
sau này gọi trực tiếp tới API.
Sơ bộ về Windows lazy linking procedure
Trong phần này chúng ta sẽ nói về mối liên kết thực hiện định nghĩa cho địa
chỉ hàm trong một DDL nạp trễ như thế nào, cũng như ngữ nghĩa của việc
tạo các cuộc gọi hàm, nơi hàm được định nghĩa trong DDL.
Hình 7. Mối liên kết giải quyết địa chỉ hàm trong một DDL nạp trễ.
Trong trường hợp trên fndll1() được định nghĩa bên trong d111(delay load)
và fndll2 được định nghĩa bên trong d112. Nếu bạn chú ý các khai báo,
fndll1() đã được khai báo rõ ràng là __declspec(dllimport). Bộ sửa đổi hàm
__declspec(dllimport) nói rằng trình biên dịch mà hàm đang cư trú nằm
trong một DDL khác. Trình biên dịch cần mã CALL DWORD PTR [xxxx]
clue và generate, trong đó [xxxx] là một mục trong bảng IAT.
Hình 8. Trình biên dịch tạo mã CALL DWORD PTR [xxxx], trong đó xxxx
là một điểm vào trong IAT.
Trong trường hợp của fndll2(), trình biên dịch đưa ra một lệnh gọi với dạng
CALL xxxxx, trong đó xxxxx trỏ tới một gốc. Kêt quả là một làm mất thêm
thời gian để thực thi.