Hầu hết các hệ điều hành sử dụng cơ chế spawn để tạo một tiến trình mới với không gian địa chỉ mới, cập nhật cấu hình từ file thực thi và chạy những mã thực thi tương ứng. Tuy nhiên, Unix sử dụng một định hướng khác hẳn: khi một tiến trình mới được
29
tạo khi một tiến trình đã tồn tại tạo bản sao của chính nó. Ngoại trừ process ID, tiến trình con này có cùng môi trường với tiến trình cha. Quá trình này được gọi là forking (phân nhánh).
Sau khi tiến trình được forking, không gian địa chỉ của tiến trình con bị ghi đè với dữ liệu tiến trình mới thông qua lời gọi hệ thống tới hàm exec.
Cơ chế fork-and-exec thực hiện chuyển những câu lệnh cũ sang những câu lệnh mới tuy nhiên môi trường tiến trình được thực thi vẫn giữ nguyên, bao gồm cấu hình của các thiết bị đầu vào đầu ra, thông tin biến môi trường… đây là cơ chế được sử dụng để tạo tiến trình trong hệ điều hành Linux. Tiến trình đầu tiên init (process ID 1) cũng tuân theo cơ chế này, tiến trình này được tạo (forking) trong quá trình khởi động.
30
Hình 12 Cơ chế fork-and-exec
Trong nhiều trường hợp, tiến trình init trở thành tiến trình cha của nhiều process, trong khi không phải tiến trình init thực hiện khởi tạo tiến trình này. Đó là trường hợp tiến trình được gọi chạy ngầm (running in background). Khi đó, ngay cả khi tiến trình cha kết thúc hoặc bị buộc phải kết thúc, tiến trình con vẫn tiếp tục thực thi.
Xét dưới góc độ của lập trình viên:
Quá trình tạo một process trong Linux chia làm hai bước, sử dụng hai hàm API fork() và exec():
31
Đầu tiên fork() được gọi để tạo một tiến trình con, là bản sao của task đang làm việc (tiến trình cha) với giá trị PID (process ID) mới, giá trị PID của process gốc (gọi hàm fork()) được cập nhật vào trường PPID của process mới.
Sau đó exec() được gọi để nạp thông tin thực thi vào không gian địa chỉ mới và tiến hành chạy các mã thực thi tương ứng.
Xét dưới góc độ hệ thống:
Hình 13 Tạo tiến trình dưới góc độ hệ thống
Trong đó exec()/fork() đại diện chung cho các hàm chức năng tương tự trên usermode.
Từ góc độ một lập trình viên, ta thấy một tiến trình đơn giản được tạo thông qua các lời gọi tới các hàm API được cung cấp bởi linux kernel, thông qua các lời gọi hệ thống các tham số cấu hình tương ứng được truyền tới các hàm xử lý trong linux kernel. Cụ thể, ta thấy hàm fork() sẽ thực hiện truyền tham số từ usermodekernelmode thông qua: fork() sys_fork() do_fork(), do_fork() sẽ xử lý các chức năng tương ứng trong kernel để tiến hành khởi tạo môi trường cho tiến trình mới. Tương tự, với exec(), các
32
tham số cấu hình sẽ được truyền từ usermode kernelmode thông qua luồng làm việc sau:
exec() sys_execve() do_execve() do_execve_common() Trong đó, do_execve_common sẽ tiến hành nạp các cấu hình thực thi mới của tiến trình từ filesystem lên bộ nhớ với luồng làm việc như sau:
Hình 14 Luồng khởi tạo cấu hình thực thi mới
Linux kernel định nghĩa một cấu trúc linux_binfmt để quản lý chung các module thực thi được hỗ trợ (kernel module, file thực thi, thư viện, coredump). Mỗi khi một module thực thi muốn được nạp lên bộ nhớ sẽ phải thông qua cấu trúc này để ánh xạ các cấu hình thực thi từ filesystem lên bộ nhớ:
load_elf_binary: được sử dụng để ánh xạ các cấu hình thực thi của file thực thi lên bộ nhớ.
33
load_elf_library: được sử dụng để ánh xạ các hình thực thi của các file thư viện (shared library) lên bộ nhớ.
Trong đó, cả load_elf_binary và load_elf_library đều sử dụng chung một cơ chế ánh xạ lên bộ nhớ đó là sử dụng luồng:
vm_mmap vm_mmap_pgoff do_mmap_pgoff
…để ánh xạ các cấu hình tương ứng lên bộ nhớ thông qua việc phân tích cấu trúc file thực thi định dạng ELF.
34
CHƯƠNG 2 - CODE SIGNING VÀ ĐỊNH HƯỚNG TÍCH HỢP
2.1. Code Signing và các nghiên cứu liên quan 2.1.1. Định nghĩa code signing
« Code signing is the process of digitally signing executables and scripts to confirm the software author and guarantee that the code has not been altered or corrupted since it was signed by use of a cryptographic hash.”
Wikipedia – Code signing
Có rất nhiều các định nghĩa khác nhau về code signing nhưng về bản chất các định nghĩa này đều có chung ý nghĩa như sau:
Code signing là một kỹ thuật bảo vệ được sử dụng để đảm bảo sự toàn vẹn các mã (code integrity) đồng thời đánh giá độ an toàn của đoạn mã này thông qua việc xác định nguồn gốc của chúng.
Hay nói cách khác, khi một ứng dụng đã được “ký”, hệ thống có thể phát hiện được bất cứ sự thay đổi nào trên ứng dụng bất kể thay đổi đó là do vô tình hay bị khai thác tấn công. Chính vì vậy, code signing rất được người dùng đánh giá cao bởi khi cài đặt một ứng dụng đã được ký, người dùng không phải bận tâm tới các cảnh báo, nguy cơ bị tấn công v…v… từ ứng dụng.
35
Code signing thường được sử dụng để cung cấp tính an toàn khi triển khai, một số ngôn ngữ cũng sử dụng code signing để tránh các xung đột về namespace. Hầu hết việc triển khai code signing hiện nay cung cấp một số cơ chế chữ ký điện tử để xác định nguồn gốc (tác giả, hệ thống xây dựng) và một checksum để xác minh đối tượng chưa bị chỉnh sửa. Code signing cũng có thể được sử dụng để cung cấp thông tin phiên bản của đối tượng hoặc lưu các thông tin bổ sung về đối tượng.
Để sử dụng code signing, chúng ta phải thực hiện hai bước:
Tạo key: tác giả tạo một cặp private key và public key tương ứng (trong đó, public key thường được đóng gói dưới dạng một chứng thư điện tử (digital certificate)). Công đoạn này cũng có thể thực hiện qua các đại diện cung cấp chứng thư (trusted certificate authority).
Ký: chương trình/ứng dụng hoặc các mã được ký với private key của tác giả.
Xác minh: sử dụng public key của tác giả để verify các mã được ký.
Tạo key: Hiện nay có rất nhiều công cụ hỗ trợ để tạo một cặp private key/public key như: makecert.exe (Microsoft), ssh-keygen (linux), openssl, puttygen … Bên cạnh đó, các hãng phần mềm lớn như Microsoft, Oracle, … cũng tích hợp các API tạo chữ ký bộ SDK của mình nền việc tạo một cặp key để sử dụng là rất dễ dàng.
Ký: thường được thực hiện từ phía lập trình viên hoặc đội phát triển, trước khi chuyển sản phẩm tới người dùng.
36
Hình 15 Mô hình ký dữ liệu
Message digest được tạo thông qua tính Hash phần Data (phần code cần ký), chuỗi message digest này sau đó được mã hóa với private key ở trên để tạo ra một chữ ký điện tử tương ứng (digital signature).
Code-signed data được tạo bằng cách đóng gói tổ hợp chữ ký điện tử, data và certificate của người ký. Đây là phần dữ liệu sẽ được chuyển tới người sử dung.
Xác minh: được thực hiện phía người dùng, nơi các dữ liệu code sẽ được sử dụng và các bước thực hiện hoàn toàn trái ngược với khi thực hiện ký.
37
Hình 16 Mô hình xác minh chữ ký
Phần dữ liệu code-signed data sau khi được chuyển tới người dùng sẽ được phân tích, trích xuất thông tin thành ba phần: dữ liệu gốc (original data), digital certificate (chứng thư chứa public key), digital signature (phần được đã ký với private key ở trên). Sau đó, phần original data sẽ được đưa vào tính hash tương tự như bước ký, thành phần chứng thư điện tử sẽ được sử dụng để giải mã chữ ký điện tử. Kết quả, ta được hai chuỗi Message digest(1) và Message digest(2), tiến hành so khớp hai chuỗi này ta sẽ xác minh được tính toàn vẹn của đoạn code ban đầu từ đó, đánh giá được mức độ an toàn của ứng dụng.
Code signing thường được sử dụng vào các mục đích sau:
Đảm bảo tính toàn vẹn một đoạn mã, chương trình (đoạn mã không bị chỉnh sửa).
Xác định nguồn gốc, tác giả (người ký, người phát triển) của đoạn mã, chương trình.
38
Xác định tính an toàn của đoạn mã cho một mục đích cụ thể (ví dụ truy cập vào thư mục hệ thống, thay đổi cấu hình hệ điều hành …)
Code signing được đánh giá rất cao trong môi trường phân tán, nơi nguồn gốc các phần khác nhau có thể không được xác minh rõ ràng ngay ví dụ như các Java applets, điều khiển ActiveX và một số mã scripting được thực thi trên trình duyệt. Bên cạnh đó, code signing còn được sử dụng để cung cấp giải pháp cập nhật an toàn cho các phần mềm trong hệ thống. Windows, Mac OS X, và hầu hết các phiên bản Linux đều cung cấp cơ chế update sử dụng code signing để tránh rủi ro tấn công can thiệp vào các bản vá từ các hacker bằng cách thực hiện xác minh tính hợp lệ của tất cả các bản cập nhật bất kể bản cập nhật này được cung cấp bởi các hãng thứ ba hay các nguồn cập nhật khác (CD, băng từ …). Code signing cũng được rất nhiều hãng phần mềm hiện nay ứng dụng vào các phần mềm, thư viện, phần mở rộng cho ứng dụng (plugin, extension, add-ons):
Morilla cho phép lập trình viên kí lên các extension, add-ons cho trình duyệt firefox của mình để giúp người dùng xác minh nguồn gốc phần mềm.
39
Hình 17 Cảnh báo của Morilla khi cài đặt phần mềm
Tất cả những ứng dụng muốn được đưa lên kho ứng dụng (Appstore) của Apple (ứng dụng của Macs và iOS) đều phải được ký trước khi gửi lên. Hệ điều hành OS X và iOS sẽ verify chữ ký của những ứng dụng này khi tải về để đảm bảo chỉ những ứng dụng hợp lệ mới được chạy lên.
Hình 18 Xcode code signing
Xcode sử dụng các đặc tính số (digital identity) để cho phép các lập trình viên ký lên ứng dụng trong quá trình phát triển ứng dụng. Digital identity bao gồm một cặp public-private key và một chứng thư số (code signing certitificate). Chứng thư này bao gồm public key và thông tin tác giả chữ ký, được cung cấp
40
khi lập trình viên xác thực với Apple, nhờ đó, các ứng dụng được đưa lên Appstore đảm bảo xác minh nguồn gốc rõ ràng.
Microsoft cũng triển khai một dạng của code signing cung cấp cho các driver đã được Microsoft kiểm chứng. Các driver trên Windows chạy ở mức thấp của hệ điều hành, chúng có thể gây ra mất ổn định hệ thống hoặc để lại các lỗ hổng bảo mật trong hệ thống. Chính vì thế, Microsoft sử dụng chương trình đánh giá chất lượng phần cứng Windows (Windows Hardware Quality Labs testing program – WHQL) để kiểm tra độ an toàn của những driver do lập trình viên phát triển cho hệ thống hệ điều hành Windows. Những driver đạt tiêu chuẩn sẽ được ký để chứng nhận là an toàn. Khi những driver này được cài đặt lên hệ điều hành của người dùng nếu không được xác minh đạt chuẩn của WHQL hệ điều hành sẽ hiển thị các cảnh báo không an toàn tới người dùng hay trong nhiều trường hợp (hệ điều hành 64bit), driver đó sẽ không được phép load lên bộ nhớ để đảm bảo an toàn cho người sử dụng.
Java hỗ trợ code signing từ rất lâu nhưng đến tận phiên bản Java SE 7u21 đây vẫn là một tính năng được tùy chọn (optional feature). Java SE 7u21 cung cấp công cụ cấu hình định mức an toàn cho hệ thống qua Java Control Panel. Các lập trình viên, các hãng phần mềm muốn triển khai ứng dụng dưới dạng Java applets hay công nghệ Java Web Start hay các ứng dụng cần triển khai tới người dùng cuối phải được ký với một chứng thư tin cậy.Do đó, tất cả các mã Java thực thi trên trình duyệt đều được cảnh báo tới người dùng, nội dung cảnh báo phụ thuộc vào các nguy cơ tấn công được phân tích như code được ký, không được ký, code yêu cầu nâng quyền để thực thi v…v…
41
Chúng ta có thể thấy code signing mang lại lợi ích rất lớn trong việc kiểm tra tính toàn vẹn của dữ liệu cũng như xác minh nguồn gốc dữ liệu, đảm bảo một môi trường an toàn cho người sử dụng. Code signing đã được triển khai trên rất nhiều nền tảng khác nhau như Microsoft Windows, Macs OS X, Java Platform… vậy trên các hệ điều hành hệ thống Linux thì sao? Trong phần này, chúng ta sẽ đi tìm hiểu và đánh giá những nghiên cứu code signing đã có trên linux.
Tính đến thời điểm hiện tại, có thể có rất nhiều các nghiên cứu code signing trên linux nhưng do giới hạn về thời gian, trong khuôn khổ luận văn này, tác giả sẽ đưa ra đánh giá về những nghiên cứu sau:
Signelf – Digitally signing ELF binaries
The digsig project
Linux IMA (Integrity Measurement Architecture)
Signelf (Joseph A.Fox)
Signelf là một bộ công cụ được viết bằng C++ để ký vào các file dạng ELF đặc biệt là các file thư viện. Signelf sử dụng thư viện libcrypt để thực hiện các thao tác ký và xác minh các file tương ứng.
Mục đích của tác giả khi tạo project này là phục vụ cho hệ thống có kiến trúc dạng plugin (hệ thống sử dụng các thư viện chia sẻ như là plugin để bổ sung mở rộng các tính năng), do đó, cần có cơ chế xác minh nguồn gốc, độ an toàn của những plugin này. Bằng việc phân tích cấu trúc định ELF, tác giả đã tạo thêm một section mới vào các file nhị phân để chứa thông tin chữ ký. Thông tin chữ ký được tạo bằng cách tính băm hai section data và text trong file và ký với private key của tác giả:
42
Signing:
signelf <path/to/shared/object.so>
Để xác minh chữ ký hợp lệ, tác giả cung cấp API verifyLib: #include “signelf.h”
#include “pubkey.h”
bRetVal = signelf::verifyLib(szKeyBuf, sizeof(szKeyBuf, sizeof(szKeyBuf), “/path/to/share/object.so”);
trong đó szKeyBuf là public key và được định nghĩa trong pubkey.h
Đánh giá:
Signelf là một dự án đơn giản, chưa có cơ chế kiểm tra, xác minh tự động, việc kiểm tra xác minh dừng lại ở mức cung cấp API cho lập trình viên tích hợp vào phần mềm. Signelf thích hợp với mô hình tích hợp code signing dưới dạng module, để phần mềm tự kiểm tra xác minh các bản cập nhật, bản vá cho phần mềm.
The digsig project – The DigSig Team
DigSig là một kernel module, thực hiện kiểm tra các chữ ký điện tử của các file thực thi và thư viện (định dạng ELF) trước khi chúng được thực thi. Các file nhị phân này được ký bởi công cụ BSign – một công cụ dùng để ký lên các file nhị phần trên usermode. Cũng tương tự như công cụ signelf ở trên, BSign ký vào các file nhị phần và
43
đặt thông tin chữ ký vào section signature trong các file định dạng ELF này. Ở mức kernel, digsig sẽ thực hiện verify các chữ ký này mỗi khi chúng được gọi thực thi và từ chối thực thi những file có chữ ký không hợp lệ.
Digsig hỗ trợ các tính năng:
Xác minh chữ ký của các file thực thi (ELF binaries) và các thư viện chia sẻ.
Hỗ trợ thu hồi chữ ký.
Cơ chế cache chữ ký để tăng hiệu năng hệ thống.
Digsig hoạt động dưới dạng một kernel module nên để sử dụng digsig, người dùng (quản trị viên hệ thống) phải thực tích hợp digsig vào linux kernel:
# ./digsig.init start my_public_key.pub Testing if sysfs is mounted in /sys. sysfs found
Loading Digsig module. Loading public key. Done.
Sau đó thì việc xác minh chữ ký sẽ được kích hoạt, tất cả các tiến trình muốn được khởi tạo phải có chữ ký hợp lệ:
$./test $ su Password:
# tail -f /var/log/messages
colby kernel: DIGSIG MODULE - binary is ./test
44 Found signature section
colby kernel: DIGSIG MODULE - dsi_bprm_compute_creds: Signature verification successful
Tất cả các tiến trình có chữ ký không hợp lệ sẽ không được phép thực thi:
$ ./corrupt
bash: ./corrupt: Operation not permitted
colby kernel: DIGSIG MODULE - binary is ./corrupt
colby kernel: DIGSIG MODULE Error - dsi_bprm_compute_creds: Signatures do not match for ./corrupt
Digsig sử dụng LSM (Linux Security Module – một framework hỗ trợ tích hợp nhiều tính năng bảo mật vào linux kernel) thực hiện hook trong quá trình linux kernel thực thi một file binary để tiến hành kiểm tra tính hợp lệ của các chữ ký tương ứng.
Phiên bản cuối cùng của digsig là digsig-1.5 tương thích với phiên bản linux kernel 2.6.21 trở về trước và rất tiếc digsig không tiếp tục được phát triển để có thể tương