16
Liên kết động trong Linux và Windows (phần II) Xem lại Phần I

Liên kết động trong linux và windows (phần 2)

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: Liên kết động trong linux và windows (phần 2)

Liên kết động trong Linux và Windows (phần II)

Xem lại Phần I

Page 2: Liên kết động trong linux và windows (phần 2)

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

Page 3: Liên kết động trong linux và windows (phần 2)

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ỉ

Page 4: Liên kết động trong linux và windows (phần 2)

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

Page 5: Liên kết động trong linux và windows (phần 2)

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

Page 6: Liên kết động trong linux và windows (phần 2)

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.

Page 7: Liên kết động trong linux và windows (phần 2)

Hình 9. Lệnh nhảy thêm làm mất thêm thời gian để thực thi

Như đã chỉ ra trong hình 8, fndll1() gọi một kết quả trong CALL DWORD

PTR[0x412760]. Sau đó 0x412760 là điạ chỉ của điểm vào đầu tiên trong

bảng địa chỉ Delayimport như được chỉ ra trong hình 10:

Hình 10. Tìm kiếm địa chỉ của đỉêm vào đầu tiên trong bảng Delayimport.

Điểm vào này trỏ tới một thường trình giúp đỡ tìm và nạp trong DDL và sau

đó thay thế nội dung của bảng địa chỉ bằng địa chỉ thực.

Hình 11.

Hình 12.

Page 8: Liên kết động trong linux và windows (phần 2)

Như bạn có thể thấy ở trên, địa chỉ 0x412760 được trỏ trước 0x40104b, là

địa chỉ của thường trình giúp đỡ, bị bộ nạp ghi đè thành 0x351070, điạ chỉ

của fndll1 như hình 13 sau:

Hình 13. Địa chỉ thường trình giúp đỡ bị ghi đè.

Bên trong trình giúp đỡ nạp trễ

Windows cho phép bạn nạp thêm thường trình giúp đỡ nạp trễ của riêng bạn.

Bây giờ chúng ta sẽ tìm hiểu đặc trưng bên trong của thường trình giúp đỡ.

Các bạn nên chú ý sự tương tự của thủ tục liên kết lười biếng với phần đếm

của nó trong Linux.

Hãy bắt đầu bằng việc nhìn lại hình 9. Nếu bạn chú ý, bạn có thể thấy mối

liên kết thêm vào hai kiểu stub (gốc). Một kiểu là __imp_load_(function

name), và kiểu kia là __tailmerge_(dllname). Như đã thấy từ quy ước đặt

tên, kiểu đầu tiên của stub được tạo ra trên từng API, cho DDL, và kiểu thứ

hai là trên từng DDL.

Trong hình 9, câu lệnh thực hiện một lệnh nhảy tới

stub__imp_load_(function name) qua Bảng địa chỉ nhập trễ. Trong stub, câu

lệnh đầu tiên là:

Mov eax,offset __imp_fndll1

Câu lệnh này di chuyển địa chỉ điểm vào thứ 0 của bảng IAT Delay. (Chú ý

rằng đây là điạ chỉ Thường Trình Giúp Đỡ phải cập nhật địa chỉ tuyệt đối).

Page 9: Liên kết động trong linux và windows (phần 2)

Câu lệnh tiếp theo là lệnh nhảy tới stub cụ thể __tailmerge_(dllname). Trong

__tailmerge_ stub, sau khi giữ các thanh ghi ecx và edx, nó thực hiện một

lệnh push (đẩy) của thanh ghi eax. Câu lệnh tiếp theo sẽ là:

Push offset __DELAY_IMPORT_DESCRIPTOR_Dll1

Câu lệnh này đẩy địa chỉ của cấu trúc ImgDelayDescr trong DDL1. Cấu trúc

dữ liệu được định nghĩa trong DELAYIMP.h.

typedef struct ImgDelayDescr {

DWORD grAttrs; // attributes

LPCSTR szName; // pointer to dll name

HMODULE * phmod; // address of module handle

PImgThunkData pIAT; // address of the IAT

PCImgThunkData pINT; // address of the INT

PCImgThunkData pBoundIAT; // address of the optional bound IAT

PCImgThunkData pUnloadIAT; // address of optional copy of original

IAT

DWORD dwTimeStamp; // 0 if not bound,

// O.W. date/time stamp of DLL bound to (Old

BIND)

} ImgDelayDescr, * PImgDelayDescr;

typedef const ImgDelayDescr * PCImgDelayDescr;

Bây giờ chương trình hàm thực hiện một lệnh nhảy tới Thường Trình Giúp

Đỡ với các giá trị trong ngăn xếp là các đối số. Từ giờ trở đi chúng ta sẽ xem

xét vấn đề dựa trên mã giúp đỡ được định nghĩa trong DELAYHLP.CPP:

Page 10: Liên kết động trong linux và windows (phần 2)

Bộ Giúp Đỡ Tải Trễ đầu tiên cố gắng lấy quyền điều khiển modul từ

ImgDelayDescr.

//Tính toán chỉ mục hàm, là một chỉ mục hàm trong IAT.

iINT = IndexFromPImgThunkData(PCImgThunkData(ppfnIATEntry),

pidd->pIAT);

Như đã nói trước đây IAT và INT là hai cấu trúc song song:

//Dùng chỉ mục hàm để trỏ tới chỉ mục tương ứng ở INT.

PCImgThunkData pitd = &((pidd->pINT)[iINT]);

//Lấy tên hàm hay thứ tự từ INT phụ thuộc xem liệu bit thiết lập cao hơn

đã được nói chưa.

if (dli.dlp.fImportByName = ((pitd->u1.Ordinal &

IMAGE_ORDINAL_FLAG) == 0)) {

dli.dlp.szProcName = LPCSTR(pitd->u1.AddressOfData->Name);

}

else {

dli.dlp.dwOrdinal = IMAGE_ORDINAL(pitd->u1.Ordinal);

}

If (hmodule =0) //the first time

{

// Nạp thư viện

Page 11: Liên kết động trong linux và windows (phần 2)

// Sao chép quyền điều khiển biến số mở rộng (Call Free library()

// Nếu mối đe doạ khác ở đó trước chúng ta)

}

Bây giờ chúng ta phải tìm địa chỉ của chương trình con thủ tục bằng cách

gọi hàm GetProcAddress(), như đã đề cập ở trên trong phần giải thích

“Cách thức hoạt động”.

pfnRet = ::GetProcAddress(hmod, dli.dlp.szProcName);

Chúng ta cập nhật điểm vào IAT với địa chỉ:

*ppfnIATEntry = pfnRet;

//Quay lại __tail_merge_dll1

Bây giờ thanh ghi eax chứa giá trị trả về là các địa chỉ hàm tuyệt đối, cuối

cùng mã lệnh thực hiện:

Jmp eax // nhảy tới hàm.

Liên kết động Explicit

Cả Linux và Windows đều cung cấp các thường trình (chẳng hạn dlopen()

và dlsym() trong Linux, LoadLibrary(), GetProcAddress() trong Windows)

để nạp dứt khoát một thư viện và tìm địa chỉ của thường trình trong thư viện

đó. Các thường trình này chỉ là các trình bao bọc, trả ra lời gọi thường trình

liên kết động đã được gọi trước đó trong thời gian thực hiện liên kết ẩn qua

PLT hay IAT.

Page 12: Liên kết động trong linux và windows (phần 2)

Tăng tốc độ - Thư viện chia sẻ liên kết tĩnh

Thư viện chia sẻ trong thực tế có thể rất chậm. Sự giảm sút trong quá trình

thực thi diễn ra chủ yếu do chế độ nạp thời gian thực và liên kết địa chỉ,

không trực tiếp tham chiếu tới địa chỉ thường trình qua các bảng trực tiếp và

việc dành riêng các thanh ghi máy cho các bảng này. Ngày nay, với không

gian điạ chỉ lớn, có thể ghép nối một thư viện với một đoạn không gian địa

chỉ tại thời gian liên kết và cũng giải quyết vấn đề tham chiếu địa chỉ. Nếu

không gian địa chỉ đã sẵn sàng tại thời gian chạy, việc định vị lại địa chỉ có

thể tránh khỏi. Các thư viện, nơi các địa chỉ chương trình và dữ liệu được

ghép nối để thực thi trong thời gian chạy dược xem như là các thư viện liên

kết tĩnh.

Prelinking trong Linux

Gilbc là một thư viện chia sẻ trong Linux có thể thực hiện liên kết tĩnh. Với

các tuỳ chọn khác, Linux thay thế bằng việc dùng một khái niệm tương tự

gọi là prelinking. Một prelink gán một rãnh địa chỉ ảo đơn nhất cho từng thư

viện thực thi phụ thuộc và liên kết lại thư viện với địa chỉ cơ sở.

Hình 14. Danh sách thư viện

Nếu bạn kết xuất một thực thi prelink hay một thư viện chia sẻ, bạn sẽ phải

chú ý tới sự thay đổi trong định dạng của relocation (xác định lại vị trí).

Thông thường kiến trúc IA-32 chỉ dùng định dạng REL, trong đó phần phu

Page 13: Liên kết động trong linux và windows (phần 2)

chú của relocation chỉ được lưu trữ tại địa chỉ offset. Chỉ trường hợp bạn có

thể thấy một phân đoạn RELA dạng IA-32 thì nó mới ở đó.

Từ khi các thư viện chia sẻ prelink được dùng, thậm chí trong những thực thi

non-prelinked, thông tin phụ chú đã được lưu giữ lại. Để làm điều này, một

prelink chuyển phân đoạn .rel.dyn thành dạng RELA. Prelink tránh thực

hiện điều này trong trường hợp các phụ chú là 0 bằng cách thay đổi kiểu

định vị lại vị trí thành R_386_GLOB_DAT.

Hình 15. Duy trì thông tin phụ chú.

Tiện ích prelink cũng tạo ra một danh sách các xung đột trong quá trình thực

hiện và lưu trữ nó bên trong thực thi. Văn bản ELF mổ tả các ký tự không

được định nghĩa trong thư viện chia sẻ phải được tìm kiếm đầu tiên trong

chương trình thực thi chính, sau đó mới tìm kiếm trong các thư viện chia sẻ

cần thiết. Không phải tất cả các ký tự đều được tìm như nhau trong pham vi

tìm kiếm của một thư viện chia sẻ (chỉ thực hiện khi thư viện chia sẻ là

prelink) hay trong phạm vi tìm kiếm ký tự mở rộng. Cả các ký tự cũng được

gọi là các xung đột ví định vị lại vị trí các ký tự đó gây ra xung đột vị trí.

Các vị trí định vị lại bị xung đột được để vào một phân đoạn RELA riêng

biệt trong một thực thi. Trong trường hợp này Sym.name + phụ chú sẽ gồm

địa chỉ thực của biến xung đột (nếu không sẽ xem xét dến khía cạnh phạm vi

mở rộng của thực thi).

Page 14: Liên kết động trong linux và windows (phần 2)

Hình 16. Giải quyết xung đột.

Trong thời gian chạy đầu tiên liên kết động kiểm tra xem liệu tất cả các thư

viện phụ thuộc đã được bản đồ hoá thành công vào các rãnh không gian địa

chỉ chỉ định của chúng hay chưa. Và liệu chúng có thay đổi từ khi quá trình

prelinking làm việc hay không. Nếu là có, các prelinker chỉ phải thực hiện

một vài điều chỉnh được định nghĩa trong danh sách xung đột đã được tạo ra

trước đó.

Rebasing (cơ sở lại) và binding (ghép nối) trong Windows

Tương quan lại các vấn đề trong Windows, Windows DLL dùng các khái

niệm cơ sở lại (rebasing) và ghép nối (bounding). Mọi chương trình thực thi

và modul DLL đều có một địa chỉ cơ sở ưu tiên để nhận dạng địa chỉ mà

modul đã bản đồ hóa trong không gian địa chỉ tiến trình. Chẳng hạn với một

chương trình thực thi, giá trị mặc định là 0x00400000 và cho một DDL là

0x10000000. Điều này có nghĩa nếu thực thi được liên kết với hai DDL, một

trong số chúng sẽ phải định vị lại vị trí trong bộ nhớ. Để tránh điều này, bạn

có thể cơ sở lại DDL của bạn bằng cách cho nó một địa chỉ ưu tiên tại thời

gian biên dịch. Bạn có thể làm điều này bằng cách chuyển hay cơ sở lại địa

chỉ sang các tuỳ chọn project.

Như chúng ta đã thấy chương trình thực thi win32 có hai bảng phân biệt bao

gồm các thông tin cần thiết để tìm hàm quan trọng - bảng Import Name và

bảng Import Lookup. Bộ nạp chỉ đòi hỏi thêm một bản sao chép. Nối kết dễ

Page 15: Liên kết động trong linux và windows (phần 2)

dàng hơn trong việc này và ghi đè điểm vào IAT với địa chỉ thực của hàm

nhập tại thời gian liên kết. Việc nối kết cũng thêm các thông tin ràng

buộc,chẳng hạn như timestamp để ràng buộc chương trình thực thi. Trong

thời gian nạp bộ nạp sẽ kiểm chứng xem khu vực ký tự tham chiếu tới trong

phân đoạn xuất của DDL có không thay đổi hay không

Để kiểm tra lại tính hợp lệ của các thông tin ràng buộc, PE dùng một cấu

trúc dữ liệu IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT được trở bởi

thư mục dữ liệu. Cấu trúc này là một danh sách các phần tử

IMAGE_BOUND_DESCRIPTOR, tương ứng với từng DDL nhập. Cấu trúc

này lưu trữ timestamp, tên của một DDL quan trọng và số tham chiếu

chuyển tiếp. Khái niệm chuyển tiếp xuất khẩu là khá xa phạm vi của bài báo

này. Nhưng với mục đích hoàn chỉnh thì cũng tốt để biết rằng Windows cho

phép bạn tham chiếu tới một API nhập trong một DDL chuyển tiếp từ một

DDL khác.

Hình 17. Cấu trúc dữ liệu

IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT.

Nếu time stamp của DDL không thay đổi, nó dùng địa chỉ nối kết lưu trữ

trong IAT. Nếu không thì nó dùng thông tin trong bảng hint-name để thực

hiện các tra tìm thông thương.

Kết luận

Page 16: Liên kết động trong linux và windows (phần 2)

Mục đích của bài này là thảo luận khái niệm thư viện chia sẻ trong cả

Windows và Linux. Và 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 phần một chúng ta đã được

giới thiệu về Linux. Phần hai này chúng ta nghiên cứu sâu hơn về liên kết

động trong Windows, bao gồm Tiến trình liên kết lười và Trình giúp đõ nạp

trễ. Chúng ta cũng xem xét cách tăng tốc độ chủ yếu cho các thư viện chia

sẻ, thường hay chậm trong thực tế. Và xem xét vấn đề prelinking trong

Linux cũng như tương quan của nó trong windows: rebasing (cơ sở lại) và

binding (nối kết).

Hi vọng là loạt hai bài này đã cung cấp cho các bạn cái nhìn sâu sắc hơn và

toàn diện hơn về vấn đề liên kết động.