26
2/Tràn số là gì? Tràn số (overflow): đó là khi giá trị của số qua lớn vượt quá số lượng bit biểu diễn của chúng hoặc làm tròn (round-off) xảy ra khi phân số có giá trị bị làm tròn dẫn đến sai số. (Nguồn: Giáo trình Tin học đại cương - Huỳnh Quang Linh @ HCMUT) Tóm lại, tràn số là hiện tượng khi mà giá trị lưu trữ trong biến vượt quá ngưỡng giới hạn của nó, dẫn đến sai số khi lưu trữ và tính toán. Cụ thể là khi ta gán một số quá lớn cho một biến có giới hạn nhỏ hơn thì kết quả nhận được sẽ bị xoay vòng, dẫn đến sai số. vd: short int n; n=32767; cout<< n=32770; cout<< n=100000; cout<< (Lưu ý: short int là kiểu dữ liệu 2 byte, giới hạn [-32768..32767] ) Thì kết quả xuất ra màn hình sẽ là: 32767 -32766 -32072 Giải thích: line 1: 32767: chưa tràn số nên kq viết ra bình thường line 2: 32770 = 32767 + 3, mà 32767 là số lớn nhất có thể lưu trữ, nên khi tăng lên 1 đvị nó trở thành số nhỏ nhất là -32768, và -32768 tăng thêm 2 đvị nữa thành -32766 line 3: 100000 = (32768 + 32768) + 32768 + 1696 = ( 0 + 32768) + 1696 = - 32768 + 1696 = -32072

biểu diễn nhị phân

  • Upload
    maitk4

  • View
    411

  • Download
    9

Embed Size (px)

Citation preview

Page 1: biểu diễn nhị phân

2/Tràn số là gì?

Tràn số (overflow): đó là khi giá trị của số qua lớn vượt quá số lượng bit biểu diễn của chúng hoặc làm tròn (round-off) xảy ra khi phân số có giá trị bị làm tròn dẫn đến sai số.

(Nguồn: Giáo trình Tin học đại cương - Huỳnh Quang Linh @ HCMUT)

Tóm lại, tràn số là hiện tượng khi mà giá trị lưu trữ trong biến vượt quá ngưỡng giới hạn của nó, dẫn đến sai số khi lưu trữ và tính toán. Cụ thể là khi ta gán một số quá lớn cho một biến có giới hạn nhỏ hơn thì kết quả nhận được sẽ bị xoay vòng, dẫn đến sai số.

vd:

short int n; n=32767; cout<< n=32770; cout<< n=100000; cout<<

(Lưu ý: short int là kiểu dữ liệu 2 byte, giới hạn [-32768..32767] )

Thì kết quả xuất ra màn hình sẽ là:

32767-32766-32072

Giải thích:

line 1: 32767: chưa tràn số nên kq viết ra bình thườngline 2: 32770 = 32767 + 3, mà 32767 là số lớn nhất có thể lưu trữ, nên khi tăng lên 1 đvị nó trở thành số nhỏ nhất là -32768, và -32768 tăng thêm 2 đvị nữa thành -32766line 3: 100000 = (32768 + 32768) + 32768 + 1696 = ( 0 + 32768) + 1696 = - 32768 + 1696 = -32072

Page 2: biểu diễn nhị phân

1.Biểu diễn các ký tự

Một trong các phương pháp để biểu diễn các ký tự trong máy tính là thiết kế một bộ mã. Ý nghĩa của cách thiết kế này là các ký tự khác nhau sẽ được đặc trưng bởi một nhóm bit duy nhất khác nhau, bằng cách này thông tin sẽ được mã hóa thành một chuỗi bit trong bộ nhớ hoặc ở các thiết bị lưu trữ. Tuy nhiên, sẽ có nhiều bộ mã khác nhau. Ðể giải quyết vấn đề này, Viện Chuẩn Hóa Hoa Kỳ (American National Standards Institute) đã đưa ra bộ mã chuẩn trong giao tiếp thông tin trên máy tính gọi là bộ mã ASCII (American Standard Code for Information Interchage) và đã trở thành chuẩn công nghiệp cho các nhà sản xuất máy tính. Bộ mã này dùng 7 bit để biểu diễn các ký tự, tuy vậy mỗi ký tự trong bảng mã ASCII vẫn chiếm hết một byte khi thực hiện trong bộ nhớ máy tính, bit dư ra sẽ bị bỏ qua hoặc được dùng cho biểu diễn một cho ký tự đặc biệt. Trong bảng mã ASCII sẽ bao gồm các ký tự chữ hoa, thường, ký tự số, ký tự khoảng trắng,...

Ví dụ

dãy bit sau là biểu diễn của chuỗi ký tự "Hi Sue "

Hiện nay bảng mã ASCII vẫn là bảng mã được sử dụng nhiều nhất. Một bảng mã khác cũng không kém phần được ưa chuộng là EBCDIC (Extended Binary Code Decimal Interchange Code) là bộ mã ở đó mỗi ký tự được biểu diễn với 8 bit, bộ mã này của công ty IBM.

2. Biểu diễn gi trị của c c con số� �

Mặc dù phương pháp lưu trữ thông tin như là sự mã hóa các ký tự bằng các dãy bit, nhưng nó dường như không hiệu quả khi lưu trữ dữ liệu thuần số. Chúng ta hãy xem tại sao điều này xảy ra? Chúng ta muốn lưu trữ số 25, nếu dùng bảng mã ASCII để biểu diễn thì mỗi ký số sẽ cần đến một byte lưu trữ do đó ta cần tới 16 bit lưu trữ. Hơn thế nữa, đối với các con số lớn hơn muốn lưu trữ ta phải cần phải dùng từ 16 bit trở lên. Một phương

Page 3: biểu diễn nhị phân

pháp hiệu quả hơn để lưu trữ giá trị cho với dữ liệu là số ở máy tính là dùng hệ nhị phân, phương pháp này dựa trên ví dụ sau:

Một đồng hồ đo kilomet của xe, khi xe còn mới thì đồng hồ chỉ ở mức 0000000

Mỗi số 0 đặc trưng cho một vòng quay, vòng quay sẽ nhận lần lượt các con số 1, 2, 3, 4, 5, 6, 7, 8, 9. Khi xe bắt đầu chạy thì vòng quay bên phải nhất sẽ bắt thay đổi cho đến khi chỉ số ở đồng hồ là 00000009

Vào thời điểm tiếp theo vòng quay phải nhất sẽ đẩy vòng quay kế lên một đơn vị, kết quả là vòng quay phải nhất đã quay được một vòng và sẽ trở về 0. Lúc đó chỉ số ở đồng hồ như sau: 00000010

Khi đó xe tiếp tục chạy và vòng quay phải nhất sẽ tiếp tục thay đổi cho đến 9 và sau đó sẽ đẩy vòng quay kế lên 1, khi đó chỉ số đồng hồ chuyển từ 00000019 thành 00000020

Phương pháp đếm trên hệ nhị phân cũng giống như quá trình trên, mỗi vòng chỉ có 0 và 1 khi đó 0 thay thế cho 9. Nếu đồng hồ kilomet dựa trên hệ đếm nhị phân thì chúng sẽ xuất hiện lần lượt như sau

Page 4: biểu diễn nhị phân

00000000

00000001

00000010

00000011

00000100

00000101

00000110

Sự thay đổi chỉ số trên thực chất là quá trình đếm từ 0 đến 6, nếu thay đổi từ 00000011 thành 00000100 thì cũng giống như chỉ số đồng hồ chuyển từ 00000099 thành 00000100. Nên nhớ rằng việc chuyển đổi từ 9 thành 0 ở đồng hồ tương tự cho chuyển đổi từ 1 thành 0 khi ở hệ nhị phân.

Quay trở lại vấn đề biểu diễn giá trị số khi dùng hệ nhị phân, ta nhận thấy một byte có thể lưu trữ một số nguyên có giá trị trong khoảng từ 0 đến 255 (00000000 đến 11111111), với 2 byte có thể lưu trữ một số nguyên có giá trị từ 0 đến 65535. Cách làm này sẽ làm tăng hiệu quả khả năng lưu trữ các số nguyên so với cách dùng một byte cho một chữ số trong bảng mã ASCII.

Một lý do khác sâu xa hơn cho việc lưu trữ thông tin ở dạng số khi dùng hệ nhị phân hay hơn dùng bảng mã, đó là hệ thống nhị phân mô tả chính xác kỹ thuật lưu trữ dùng bit trong máy tính. Ngoài ra ta có thể sử dụng hệ nhị phân để biểu diễn các số nguyên âm với phương pháp bù 2 (two’s complement notation) hoặc dùng phương pháp dấu chấm động (floating point notation) để biểu diễn hỗn số. Tuỳ theo giá trị của số mà ta có phương pháp biểu diể n khác nhau. Ở đây ta có hai khái niệm là tràn số (overflow) đó là khi giá� trị của số qua lớn vượt quá số lượng bit biểu diễn của chúng hoặc làm tròn (round-off) xảy ra khi phân số có giá trị bị làm tròn dẫn đến sai số.

Các số biểu diễn ở hệ nhị phân sẽ là một chuỗi bit, ứng với mỗi vị trí bit được gán một trọng số. Các trọng số này được xác định từ phải sang trái với các giá trị là 1, 2, 4, 8,... Với vị trí các bit tương ứng 0, 1, 2, 3,... Dựa theo qui luật : số sau sẽ bằng 2 lần số trước, ví dụ với biểu diễn nhị phân 100101 là biểu diễn nhị phân của 37.

Page 5: biểu diễn nhị phân

Phương pháp chuyển đổi giữa hệ thập phân và nhị phân bạn đọc có thể tham khảo ở phần 1. Sau đây chúng ta cùng tìm hiểu các thao tác xử lý khác trên hệ nhị phân.

3. Cộng nhị phân

Trong hệ nhị phân thao tác cộng cũng giống như thao tác cộng trong hệ thập phân với một số qui tắc sau

0 + 0 = 0 0 + 1 = 1

1 + 0 = 1 1 + 1 = 10

Khi cộng vẫn thực hiện cộng các cột từ phải sang trái, ứng với mỗi cột ta cộng 2 số theo qui tắc trên, nếu có nhớ thì cộng nhớ sang cột kế bên

Ví dụ :

cho 2 dãy bit

0 0 1 1 1 0 1 0

Page 6: biểu diễn nhị phân

+ 0 0 0 1 1 0 1 1

1

0 0 1 1 1 0 1 0

+ 0 0 0 1 1 0 1 1 Nhớ 1

0 1

0 0 1 1 1 0 1 0

+ 0 0 0 1 1 0 1 1

1 0 1

0 0 1 1 1 0 1 0

+ 0 0 0 1 1 0 1 1 Nhớ 1

0 1 0 1

0 0 1 1 1 0 1 0

+ 0 0 0 1 1 0 1 1 Nhớ 1

1 0 1 0 1

0 0 1 1 1 0 1 0

Page 7: biểu diễn nhị phân

+ 0 0 0 1 1 0 1 1 Nhớ 1

0 1 0 1 0 1

0 0 1 1 1 0 1 0

+ 0 0 0 1 1 0 1 1

1 0 1 0 1 0 1

0 0 1 1 1 0 1 0

+ 0 0 0 1 1 0 1 1

0 1 0 1 0 1 0 1

0 0 1 1 1 0 1 0

+ 0 0 0 1 1 0 1 1

= 0 1 0 1 0 1 0 1

Các phép toán khác ta cũng thực hiện tương tự.

Khi nghiên cứu kỹ thuật biểu diễn các số thông qua biểu diễn số trong hệ nhị phân đại diện cho các bit, ta chỉ đề cập đến các số nguyên dương, còn các số âm thì sao? Chính điều này ta cần có một hệ có thể biểu diễn cho cả số âm và số dương. Các nhà toán học trong thời gian dài đã quan tâm đến hệ thống biểu diễn số, nhiều ý kiến đã được đưa ra, trong các ý kiến đó, có một số ý kiến rất phù hợp với khả năng thiết kế các mạch điện trong máy tính, và hầu hết các ý kiến này vẫn dựa trên hệ nhị phân nhưng có một số biến đổi đó là hệ nhị phân có dấu. Có ba cách biểu diễn một số âm ở hệ nhị phân có dấu đó là : phương pháp dấu lượng.

4. Phương pháp dấu lượng (sign - magnitude)

Page 8: biểu diễn nhị phân

Theo cách biểu diễn này, bit cực trái được dùng làm bit dấu (1 là dấu + và 0 là dấu - ) các bit còn lại biểu diễn độ lớn của số.

Ví dụ:

với mẩu là 4 bit thì các số biểu diễn như sau:

Mẩu bit Giá trị được biểu diễn

1111 7

1110 6

1101 5

1100 4

1011 3

1010 2

1001 1

1000 0

0111 -1

0110 -2

0101 -3

0100 -4

0011 -5

0010 -6

0001 -7

0000 -8

Qui tắc 5

Phương pháp để biểu diễn một số âm về dạng nhị phân có dấu với mẩu K bit là lấy số cần biểu diễn cộng thêm 2K-1 sau đó biểu diễn chúng ở hệ nhị phân.

Page 9: biểu diễn nhị phân

Ví dụ:

với số +5 trong mẩu 4 bit thì biểu diễn là 5 + 8 =13 sẽ là 1101

với số -5 trong mẩu 4 bit thì biểu diễn là -5 + 8 =3 sẽ là 0011

Ngoài cách biểu diễn bằng dấu lượng các nhà toán học còn đưa ra 2 cách biểu diễn sau:

5. Phương pháp biểu diễn số bù 1 (one’s complement)

Theo cách biểu diễn này vẫn dùng bit cực trái làm bit dấu nhưng với qui định có thay đổi là 0 cho số dương và 1 cho số âm. Ðể biểu diễn số n theo dạng bù 1 ta thực hiện các thao tác sau :

Qui tắc 6 :

Biểu diễn dưới dạng nhị phân của trị tuyệt đối n theo mẩu k bit cố định cho trước. Nếu n < 0 thì đổi 1 thành 0 và ngược lại trong dãy số nhị phân.

Ví dụ:

với n = 5 dùng mẩu 4 bit thì biễ u diễn theo phương pháp bù 1 là 0101�

n = -5 dùng mẩu 4 bit thì biễ u diễn theo phương pháp bù 1 là 1010�

với n = 6 dùng mẩu 4 bit thì biễ u diễn theo phương pháp bù 1 là 0110�

n = -6 dùng mẩu 4 bit thì biễ u diễn theo phương pháp bù 1 là 1001�

nếu biểu diễn nhị phân của 6 là 0 1 1 0thì biểu diễn số bù 1 của -6 sẽ là 1 0 0 1

6. Phương pháp biểu diễn số bù 2 (two’s complement)

Theo cách biểu diễn này vẫn sử dụng bit cực trái làm bit dấu giống như bù 1, nhưng có một số khác biệt khi đổi sang hệ nhị phân có dấu, các buớc thực hiện như sau :

Qui tắc 7 :

Biểu diễn dưới dạng nhị phân của trị tuyệt đối n theo mẩu k bit cố định cho trước. Nếu n < 0 thì bắt đầu từ phải qua trái giữ nguyên các bit cho đến khi gặp bit có giá trị là 1 đầu tiên, sau đó các bit tiếp theo bên trái bit 1 đầu tiên đó đổi 1 thành 0 và ngược lại.

Page 10: biểu diễn nhị phân

Ví dụ :

cho n = -6 thì biểu diễn nhị phân của trị tuyệt đối của n cho mẩu 4 bit là 0110 khi đó biểu diễn của số bù 2 cho -6 là 1010

Biểu diễn số bù 2 qua mẩu 4 bit

Mẩu bit Giá trị được biểu diễn

0111 7

0110 6

0101 5

0100 4

0011 3

0010 2

0001 1

0000 0

1111 -1

1110 -2

1101 -3

1100 -4

1011 -5

1010 -6

1001 -7

1000 -8

Thực chất số biểu diễn dưới dạng bù 2 là số biểu diễn ở bù 1 sau đó ta cộng thêm 1.

Ví dụ :

Page 11: biểu diễn nhị phân

Số -6 có biểu diễn bù 1 là 1001 nếu ta lấy số bù 1 này cộng thêm 1 thì kết quả là 1001 + 1 = 1010 đây chính là dạng bù 2

Hình vẽ sau sẽ minh hoạ biểu diễn số bù 2 cho số -6 :

nếu biểu diễn nhị phân của 6 là 0 1 1 0thì biểu diễn số bù 1 của -6 sẽ là 1 0 0 1

cộng thêm 1 + 1thì biểu diễn số bù 2 của -6 sẽ là = 1 0 1 0

7. Phép cộng khi số được biểu diễn ở bù 1 và bù 2

Qui tắc 8:

Ðối với số dạng bù 1 khi thực hiện phép cộng ta vẫn thực hiện như phép toán tương ứng trên hệ nhị phân, nếu ở 2 bit cực trái khi thực hiện phép cộng mà phát sinh bit nhớ thì sẽ cộng nhớ vào kết quả.

ví dụ 1 :

-6 biểu diễn ở bù 1 với mẩu 4 bit là 1001

4 biểu diễn ở bù 1 với mẩu 4 bit là 0100

Kết quả phép cộng ở dạng bù 1 là 1101 (là biểu diễn của -2 ở bù 1)

-6 1 0 0 1

+ 4 + 0 1 0 0-2 1 1 0 1 -2 ở bù 1

ví dụ 2 :

-6 biểu diễn ở bù 1 với mẩu 5 bit là 11001

-4 biểu diễn ở bù 1 với mẩu 5 bit là 11011

Kết quả phép cộng ở dạng bù 1 là 10100 và còn nhớ 1 khi cộng 2 bit cực trái khi đó kết quả sẽ là 10100 + 1 = 10101 là biểu diễn của số -10 ở dạng bù 1.

Page 12: biểu diễn nhị phân

-6 1 1 0 0 1

+ -4 + 1 1 0 1 1

1 0 1 0 0

+ 1 Nhớ 1

-10 1 0 1 0 1 -10 ở bù 1

Qui tắc 9

Ðối với bù 2 ta vẫn thực hiện như phép cộng nhị phân, nhưng nếu ở 2 bit cực trái phát sinh bit nhớ thì bỏ.

ví dụ 1 :

-6 biểu diễn ở bù 2 với mẩu 4 bit là 1010

4 biểu diễn ở bù 2 với mẩu 4 bit là 0100

Kết quả phép cộng ở dạng bù 2 là 1110 (là biểu diễn của -2 ở bù 2)

-6 1 0 1 0 + 4 + 0 1 0 0

1 1 1 0 -2 ở bù 2

ví dụ 2 :

-6 biểu diễn ở bù 2 với mẩu 5 bit là 11010

-4 biểu diễn ở bù 2 với mẩu 5 bit là 11100

Kết quả phép cộng ở dạng bù 2 là 10110 và còn nhớ 1 khi cộng 2 bit cực trái nhưng ta� bỏ nhớ này và kết quả là 10110 là biểu diễn của -10

-6 1 1 0 1 0 + -4 + 1 1 1 0 0

1 0 1 1 0 -10 ở bù 2 bỏ đi nhớ

8. Lỗi tràn số

Trong các ví dụ trên bạn đọc chắc cũng thắc mắc tại sao ở ví dụ 2 trong phép cộng số bù 2 ta lại dùng mẩu 5 bit chứ không là 4 bit? Ý nghĩa của lỗi tràn số đã giới thiệu ở các các phần trước, đó là hiện tượng xảy ra khi số cần biểu diễn vượt quá số bit cho trước để biểu diễn nó.

Page 13: biểu diễn nhị phân

Ví dụ :

nếu ở ví dụ 2 ta dùng mẩu 4 bit cho biểu diễn bù 2 cho -6 và -4, khi đó bài toán được thực hiện như sau :

-6 biểu diễn ở bù 2 với mẩu 4 bit là 1010

-4 biểu diễn ở bù 2 với mẩu 4 bit là 1100

Kết quả phép cộng ở dạng bù 1 là 0110 là biểu diễn của +6, do đó kết quả bị sai.

Nguyên nhân là do ta lấy số lượng bit để biểu diễn quá ít nên xảy ra lỗi tràn số. Do đó người sử dụng máy tính phải lường trước được tình huống này khi muốn lưu trữ dữ liệu, để khắc phục ta tăng số lượng bit nhiều hơn thì sẽ không gây hiện tượng tràn. Ví dụ với mẩu 32 bit thì giá trị dương lớn nhất là 2147483647.

� Tổng quát ta có số ở phép biểu diễn bù 1 và bù 2 thì giá trị dương lớn nhất cho phép khi dùng mẩu n bit là : 2n-1 -1 và giá trị âm nhỏ nhất là -2n-1

9. Biểu diễn hỗn số bằng hệ nhị phân

Ðể biểu diễn hỗn số bằng hệ nhị phân ta dùng dấu chấm cơ số (radix point) giống như cách biểu diễn số có phần thập phân trong hệ cơ số 10, khi đó số bên trái dấu chấm cơ số là biểu diễn nhị phân của phần nguyên của hỗn số và bên phải là biểu diễn nhị phân của phân số, vị trí bit bên phải đầu tiên sau dấu chấm là biểu diễn cho số kế tiếp là , , ... Với qui luật số sau sẽ nhỏ hơn 2 lần so với số trước. Các giá trị này gọi là trọng số của bit tương ứng với vị trí tính từ vị trí đầu tiên bên phải của dấu chấm cơ số. Ðể biến đổi hỗn số ở hệ nhị phân sang hỗn số ở hệ thập phân, ta vẫn sử dụng cách thực hiện như đổi số nguyên sang hệ thập phân cho các số nhị phân bên trái và bên phải dấu chấm nhưng với chú ý các số nhị phân bên phải dấu chấm sẽ có trọng số là phân số bắt đầu từ và giảm một nửa khi đi từ trái sang phải

Ví dụ :

Cho hỗn số 5 thì sẽ biến đổi thành 101.101 và được biểu diễn theo lưu đồ sau :

Page 14: biểu diễn nhị phân

Phép cộng ở trên hỗn số biểu diễn dưới dạng nhị phân cũng được thực hiện như phép cộng nhị phân cho số nguyên, chỉ có chú ý là dấu chấm cơ số phải sắp thẳng hàng cho 2 số.

Ví dụ :

1 0 . 0 1 1 là biểu diễn của 2 3/8

+ 1 0 0 . 1 1 là biểu diễn của 4 3/4

= 1 1 1 . 0 0 1 là biểu diễn của 7 1/8

10.Các phép toán luận lý

Ba phép toán thông thường trong nhóm của các phép toán luận lý đó là AND, OR, và EXCLUSIVE OR (XOR). Chúng tương tự như phép cộng và trừ với hai toán hạng và� trả ra một kết quả duy nhất. (Trái lại có một số phép toán mà giá trị trả về của nó sẽ cho ra 2 số khác dấu nhau như là phép rút căn bậc hai, ví dụ như 4 khi rút căn sẽ cho hai kết quả là 2 và -2). Bây giờ chúng ta sẽ xem qua một số phép toán như sau :

a. Phép toán AND

Page 15: biểu diễn nhị phân

Hình 2-6 cho ta một bảng các kết quả của phép toán AND với một bit duy nhất. Chú ý rằng kết quả là 1 chỉ khi cả hai bit đều là 1.

1 1 0 0

AND 1 AND 0 AND 1 AND 0

Kết quả 1 Kết quả 0 Kết quả 0 Kết quả 0

Trái lại, với phép cộ ng cho các toán hạng là các bit thì sẽ cho kết quả không giống như� phép toán AND. Với hai dãy gồm nhiều bit là toán hạng cho phép toán AND, thì vẫn được áp dụng các qui tắc thực hiện phép toán AND như trong ví dụ dướ đây, khi đó thì ta sẽ tách riêng ra từng cặp bit thành các cột ở mỗi dãy và thực hiện qui tắc AND cho cặp bit đó.

Ví dụ thực hiện phép AND cho hai byte sau:

1 0 0 1 1 0 1 0

AND 1 0 0 1 1 0 1 0

Kết quả 1 0 0 0 1 0 0 0

Một trong những sử dụng chính của phép toán AND là thành phần 0 trong một dãy bit sẽ không bị ảnh hưởng bởi các phần khác. Hãy xem một ví dụ, điều gì sẽ xảy ra nếu dãy bit 00001111 là toán hạng đầu tiên của phép toán AND. Mặc dù ta không biết thành phần toán hạng hai, nhưng chúng ta vẫn suy ra được 4 bit bên trái nhất là các số 0.

Hơn thế nữa, bốn bit bên phải nhất là 4 bit cuối của toán hạng thứ hai, theo ví dụ ta có :

0 0 0 0 1 1 1 1

AND 0 0 0 0 1 0 1 0

Kết quả 0 0 0 0 1 0 1 0

Cách sử dụng phép toán AND trong ví dụ này được gọi là cách sử dụng mặt nạ (masking). Ở đây, toán hạng đầu được gọi là mặt nạ (mask), nó được sử dụng để xác định phần của toán hạng còn lại sẽ ảnh hưởng đến kết quả. Trong trường hợp này, mặt nạ được sử dụng sẽ cho ra kết quả là mỗi phần 4 bit của các toán hạng trong đó các số 0 với 4 bit đầu của toán hạng thứ nhất và 4 bit sau là phần của toán hạng thứ hai.

Page 16: biểu diễn nhị phân

Phép toán này thường được sử dụng trong phép kiểm tra bit là 1 hay 0. Ví dụ, một chuỗi gồm 52 bit, với mỗi bit là một đại diện cho một lá bài, có thể được sử dụng để biễu diễn trạng thái các lá bài cho một người chơi bằng cách gán 1 cho 5 bit tương ứng với các lá bài và các bit còn lại là 0. Khi đó nếu muốn kiểm tra lá bài thứ 6 trong 52 lá bài này có thuộc về một người nào đó hay không, thì ta có thể sử dụng phép toán AND. Một ví dụ khác ta có 8 bit trong một ô nhớ của bộ nhớ chính, ta muốn kiểm tra bit thứ 3 trong nhóm bit cao có tồn tại hay không? Bằng cách sử dụng mặt nạ 00100000 và thực hiện phép toán AND giữa dãy bit và mặt nạ. Nếu byte nhận được có giá trị là 0 thì bit thứ 3 trong phần cao không tồn tại và ngược lại là tồn tại. Do đó phép toán AND thường được sử dụng trong chương trình cùng với lệnh nhảy có điều kiện. Ngoài ra ta có bit thứ 3 này là 1, nhưng ta muốn thay đổi nó thành 0 mà không ảnh hưởng đến các bit khác, ta có thể AND với mặt nạ 11011111 và sau đó đưa kết quả trở lại dãy bit gốc.

b. Phép toán OR

1 1 0 0

OR 1 OR 0 OR 1 OR 0

Kết quả 1 Kết quả 1 Kết quả 1 Kết quả 0

Bây giờ chúng ta cùng tìm hiểu phép toán OR. Các qui tắc như hình 2-7. Chú ý rằng kết quả là 0 chỉ khi cả 2 bit toán hạng đều là 0. Một lần nữa các qui tắc cơ bản có thể được mở rộng đến các chuỗi các bit bằng cách dựa trên việc thực hiện phép toán cho các cột độc lập, như đã trình bày sau đây:

1 0 0 1 1 0 1 0

OR 1 1 0 0 1 0 0 1

1 1 0 1 0 0 1 1

Ở đây phép toán AND có thể được sử dụng để chép lại một phần của dãy bit và thêm vào 0 ở phần không chép lại. Còn đối với phép toán OR thì có thể sử dụng để chép lại một phần của dãy bit, và đặt giá trị 1 vào các phần không chép lại. Trong phần này chúng ta một lần nữa sử dụng mặt nạ, nhưng thời điểm này chúng ta xác định các vị trí bit được chép lại 0 và sử dụng 1 để chỉ các vị trí không được chép lại. Ví dụ, thực hiện phép toán OR với một byte có giá trị là 11110000 sau cho ta có được một kết quả với các số 1 ở 4 bit cao và ở 4 bit còn lại là 4 bit thấp của toán hạng kia.

Bài toán được trình bày như sau:

Page 17: biểu diễn nhị phân

1 1 1 1 0 0 0 0

OR 1 0 1 0 1 0 1 0

1 1 1 1 1 0 1 0

Từ đó ta thấy rằng phép toán AND và mặt nạ 11011111 có thể được sử dụng để buộc thành 0 ở bit thứ 3 của phần cao trong một dãy 8 bit, còn phép toán OR và mặt nạ 00100000 có thể buộc thành 1 ở vị trí đó.

c. Phép toán EXCLUSIVE OR (XOR)

Các qui tắc căn bản của phép toán XOR được trình bày trong hình 2-8. Trong trường hợp để kết quả là 1, thì hai bit toán tử chỉ có chính xác một bit là 1. Nghĩa là nếu một bit là 1 thì bit kia không được là 1, mới cho ra kết quả là 1. Ta có thể áp dụng các qui tắc này cho một dãy bit theo ví dụ như sau:

1 0 0 1 1 0 1 0

XOR 1 1 0 0 1 0 0 1

0 1 0 1 0 0 1 1

1 1 0 0

XOR 1 XOR 0 XOR 1 XOR 0

Kết quả 1 Kết quả 0 Kết quả 0 Kết quả 0

Sử dụng chính của phép toán này là lấy phần bù của một chuỗi bit.Ví dụ, để lấy phần bù của toán hạng thứ 2 ta thực hiện như sau:

1 1 1 1 1 1 1 1

XOR 1 0 1 0 1 0 1 0

0 1 0 1 0 1 0 1

Page 18: biểu diễn nhị phân

d. Các phép toán dịch chuyển và quay

Các phép toán thuộc lớp các phép toán như phép quay (rotation) và phép dịch chuyển (shift), đều có ý nghĩa biến đổi các bit trong một thanh ghi và thường được sử dụng để giải quyết các bài toán thực hiện trên bit. Ví dụ như biến đổi một byte theo một yêu cầu nào đó bằng cách sử dụng mặt nạ, hoặc thao tác trên phần định trị của các số biểu diễn ở dạng dấu chấm động. Những phép toán này được phân chia tùy theo hướng di chuyển của các dãy bit (sang trái hay sang phải).

Cho một byte gồm 8 bit, nếu ta thực hiện phép toán SHIFT cho dãy bit của nó sang hướng trái hay phải thì bit đầu tiên của byte (là bit cao nhất nếu dịch chuyển sang phải, hay bit thấp nhất khi dịch chuyển sang trái) sẽ bị chuyển đi, và bit cuối cùng của nó (là bit cao nhất nếu dịch chuyển sang trái, hay bit thấp nhất khi dịch chuyển sang phải) sẽ được đặt là 0.

Ví dụ cho một byte có giá trị là 10001110, khi SHIFT trái một lần sẽ là 00011100 hoặc SHIFT phải kết quả là 01000111.

Ðối với phép toán quay, cũng giống như phép SHIFT. Nhưng bit cuối sẽ được chuyển vào bit đầu tiên.

Ví dụ cho một byte có giá trị là 10001110, khi ta quay trái một bit thì kết quả sẽ là 00011101; quay phải một bit thì kết quả sẽ là 01000111.

Phép toán SHIFT thường được sử dụng cho các phép nhân hay chia cho 2, đối với SHIFT trái chính là nhân cho 2, và SHIFT phải là chia cho 2. Do đó phép toán này này ta gọi là phép chuyển số học (arithmetic Shifts).

Page 19: biểu diễn nhị phân

Lỗi tràn bộ nhớ đệmBách khoa toàn thư mở Wikipedia

Trong các lĩnh vực an ninh máy tính và lập trình, một lỗi tràn bộ nhớ đệm hay gọi tắt là lỗi tràn bộ đệm là một lỗi lập trình có thể gây ra một ngoại lệ truy nhập bộ nhớ máy tính và chương trình bị kết thúc, hoặc khi người dùng có ý phá hoại, họ có thể lợi dụng lỗi này để phá vỡ an ninh hệ thống.

Lỗi tràn bộ đệm là một điều kiện bất thường khi một tiến trình lưu dữ liệu vượt ra ngoài biên của một bộ nhớ đệm có chiều dài cố định. Kết quả là dữ liệu đó sẽ đè lên các vị trí bộ nhớ liền kề. Dữ liệu bị ghi đè có thể bao gồm các bộ nhớ đệm khác, các biến và dữ liệu điều khiển luồng chạy của chương trình (program flow control).

Các lỗi tràn bộ đệm có thể làm cho một tiến trình đổ vỡ hoặc cho ra các kết quả sai. Các lỗi này có thể được kích hoạt bởi các dữ liệu vào được thiết kế đặc biệt để thực thi các đoạn mã phá hoại hoặc để làm cho chương trình hoạt động một cách không như mong đợi. Bằng cách đó, các lỗi tràn bộ đệm gây ra nhiều lỗ hổng bảo mật (vulnerability) đối với phần mềm và tạo cơ sở cho nhiều thủ thuật khai thác (exploit). Việc kiểm tra biên (bounds checking) đầy đủ bởi lập trình viên hoặc trình biên dịch có thể ngăn chặn các lỗi tràn bộ đệm.

Mục lục

[ẩn]

• 1 Mô tả kỹ thuật o 1.1 Ví dụ cơ bản o 1.2 Tràn bộ nhớ đệm trên stack o 1.3 Mã nguồn ví dụ

• 2 Khai thác o 2.1 Khai thác lỗi tràn bộ đệm trên stack o 2.2 Khai thác lỗi tràn bộ đệm trên heap o 2.3 Cản trở đối với các thủ thuật khai thác

• 3 Chống tràn bộ đệm o 3.1 Lựa chọn ngôn ngữ lập trình o 3.2 Sử dụng các thư viện an toàn o 3.3 Chống tràn bộ nhớ đệm trên stack o 3.4 Bảo vệ không gian thực thi o 3.5 Ngẫu nhiên hóa sơ đồ không gian địa chỉ o 3.6 Kiểm tra sâu đối với gói tin

• 4 Lịch sử khai thác • 5 Xem thêm

Page 20: biểu diễn nhị phân

• 6 Chú thích

• 7 Liên kết ngoài

[sửa] Mô tả kỹ thuật

Một lỗi tràn bộ nhớ đệm xảy ra khi dữ liệu được viết vào một bộ nhớ đệm, mà do không kiểm tra biên đầy đủ nên đã ghi đè lên vùng bộ nhớ liền kề và làm hỏng các giá trị dữ liệu tại các địa chỉ bộ nhớ kề với vùng bộ nhớ đệm đó. Hiện tượng này hay xảy ra nhất khi sao chép một xâu ký tự từ một bộ nhớ đệm này sang một vùng bộ nhớ đệm khác.

[sửa] Ví dụ cơ bản

Trong ví dụ sau, một chương trình đã định nghĩa hai phần tử dữ liệu kề nhau trong bộ nhớ: A là một bộ nhớ đệm xâu ký tự dài 8 byte, và B là một số nguyên kích thước 2 byte. Ban đầu, A chỉ chứa toàn các byte giá trị 0, còn B chứa giá trị 3. Các ký tự có kích thước 1 byte.

A A A A A A A A B B

0 0 0 0 0 0 0 0 0 3

Bây giờ, chương trình ghi một xâu ký tự "excessive" vào bộ đệm A, theo sau là một byte 0 để đánh dấu kết thúc xâu. Vì không kiểm tra độ dài xâu, nên xâu ký tự mới đã đè lên giá trị của B:

A A A A A A A A B B

'e' 'x' 'c' 'e' 's' 's' 'i' 'v' 'e' 0

Tuy lập trình viên không có ý định sửa đổi B, nhưng giá trị của B đã bị thay thế bởi một số được tạo nên từ phần cuối của xâu ký tự. Trong ví dụ này, trên một hệ thống big-endian sử dụng mã ASCII, ký tự "e" và tiếp theo là một byte 0 sẽ trở thành số 25856.

Nếu B là phần tử dữ liệu duy nhất còn lại trong số các biến được chương trình định nghĩa, việc viết một xâu ký tự dài hơn nữa và vượt quá phần cuối của B sẽ có thể gây ra một lỗi chẳng hạn như segmentation fault (lỗi phân đoạn) và tiến trình sẽ kết thúc.

[sửa] Tràn bộ nhớ đệm trên stack

Bên cạch việc sửa đổi các biến không liên quan, hiện tượng tràn bộ đệm còn thường bị lợi dụng (khai thác) bởi tin tặc để làm cho một chương trình đang chạy thực thi một đoạn mã tùy ý được cung cấp. Các kỹ thuật để một tin tặc chiếm quyền điều khiển một tiến trình tùy theo vùng bộ nhớ mà bộ đệm được đặt tại đó. Ví dụ, vùng bộ nhớ stack, nơi dữ liệu có thể được tạm thời "đẩy" xuống "đỉnh" ngăn xếp (push), và sau đó được "nhấc ra" (pop) để đọc giá trị của biến. Thông thường, khi một hàm (function) bắt đầu thực thi, các

Page 21: biểu diễn nhị phân

phần tử dữ liệu tạm thời (các biến địa phương) được đẩy vào, và chương trình có thể truy nhập đến các dữ liệu này trong suốt thời gian chạy hàm đó. Không chỉ có hiện tượng tràn stack (stack overflow) mà còn có cả tràn heap (heap overflow).

Trong ví dụ sau, "X" là dữ liệu đã từng nằm tại stack khi chương trình bắt đầu thực thi; sau đó chương trình gọi hàm "Y", hàm này đòi hỏi một lượng nhỏ bộ nhớ cho riêng mình; và sau đó "Y" gọi hàm "Z", "Z" đòi hỏi một bộ nhớ đệm lớn:

Z Z Z Z Z Z Y X X X

: / / /

Nếu hàm "Z" gây tràn bộ nhớ đệm, nó có thể ghi đè dữ liệu thuộc về hàm Y hay chương trình chính:

Z Z Z Z Z Z Y X X X

. . . . . . . . / /

Điều này đặc biệt nghiêm trọng đối với hầu hết các hệ thống. Ngoài các dữ liệu thường, bộ nhớ stack còn lưu giữ địa chỉ trả về, nghĩa là vị trí của phần chương trình đang chạy trước khi hàm hiện tại được gọi. Khi hàm kết thúc, vùng bộ nhớ tạm thời sẽ được lấy ra khỏi stack, và thực thi được trao lại cho địa chỉ trả về. Như vậy, nếu địa chỉ trả về đã bị ghi đè bởi một lỗi tràn bộ đệm, nó sẽ trỏ tới một vị trí nào đó khác. Trong trường hợp một hiện tượng tràn bộ đệm không có chủ ý như trong ví dụ đầu tiên, hầu như chắc chắn rằng vị trí đó sẽ là một vị trí không hợp lệ, không chứa một lệnh nào của chương trình, và tiến trình sẽ đổ vỡ. Tuy nhiên, một kẻ tấn công có thể chỉnh địa chỉ trả về để trỏ tới một vị trí tùy ý sao cho nó có thể làm tổn hại an hinh hệ thống.

[sửa] Mã nguồn ví dụ

Mã nguồn C dưới đây thể hiện một lỗi lập trình thường gặp. Sau khi được biên dịch, chương trình sẽ tạo ra một lỗi tràn bộ đệm nếu nó được gọi với một tham số dòng lệnh là một xâu ký tự quá dài, vì tham số này được dùng để ghi vào một bộ nhớ đệm mà không kiểm tra độ dài của nó. [1]

/* overflow.c - demonstrates a buffer overflow */

#include <stdio.h>#include <string.h>

int main(int argc, char *argv[]){ char buffer[10]; if (argc < 2) { fprintf(stderr, "USAGE: %s string\n", argv[0]); return 1; }

Page 22: biểu diễn nhị phân

strcpy(buffer, argv[1]); return 0;}

Các xâu ký tự độ dài không quá 9 sẽ không gây tràn bộ đệm. Các xâu ký tự gồm từ 10 ký tự trở lên sẽ gây tràn bộ đệm: hiện tượng này luôn luôn là một lỗi sai nhưng không phải lúc nào cũng gây ra việc chương trình chạy sai hay gây lỗi segmentation fault.

Chương trình trên có thể được viết lại cho an toàn bằng cách sử dụng hàm strncpy như sau: [1]

/* better.c - demonstrates one method of fixing the problem */

#include <stdio.h>#include <string.h>

int main(int argc, char *argv[]){ char buffer[10]; if (argc < 2) { fprintf(stderr, "USAGE: %s string\n", argv[0]); return 1; } strncpy(buffer, argv[1], sizeof(buffer)); buffer[sizeof(buffer) - 1] = '\0'; return 0;}

[sửa] Khai thác

Có các kỹ thuật khác nhau cho việc khai thác lỗi tràn bộ nhớ đệm, tùy theo kiến trúc máy tính, hệ điều hành và vùng bộ nhớ. Ví dụ, khai thác tại heap (dùng cho các biến cấp phát động) rất khác với việc khai thác các biến tại stack.

[sửa] Khai thác lỗi tràn bộ đệm trên stack

Một người dùng thạo kỹ thuật và có ý đồ xấu có thể khai thác các lỗi tràn bộ đệm trên stack để thao túng chương trình theo một trong các cách sau:

• Ghi đè một biến địa phương nằm gần bộ nhớ đệm trong stack để thay đổi hành vi của chương trình nhằm tạo thuận lợi cho kẻ tấn công.

• Ghi đè địa chỉ trả về trong một khung stack (stack frame). Khi hàm trả về, thực thi sẽ được tiếp tục tại địa chỉ mà kẻ tấn công đã chỉ rõ, thường là tại một bộ đệm chứa dữ liệu vào của người dùng.

Nếu không biết địa chỉ của phần dữ liệu người dùng cung cấp, nhưng biết rằng địa chỉ của nó được lưu trong một thanh ghi, thì có thể ghi đè lên địa chỉ trả về một giá trị là địa chỉ của một opcode mà opcode này sẽ có tác dụng làm cho thực thi nhảy đến phần dữ liệu

Page 23: biểu diễn nhị phân

người dùng. Cụ thể, nếu địa chỉ đoạn mã độc hại muốn chạy được ghi trong một thanh ghi R, thì một lệnh nhảy đến vị trí chứa opcode cho một lệnh jump R, call R (hay một lệnh tương tự với hiệu ứng nhảy đến địa chi ghi trong R) sẽ làm cho đoạn mã trong phần dữ liệu người dùng được thực thi. Có thể tìm thấy địa chỉ của các opcode hay các byte thích hợp trong bộ nhớ tại các thư viện liên kết động (DLL) hay trong chính file thực thi. Tuy nhiên, địa chỉ của opcode đó thường không được chứa một ký tự null (hay byte 0) nào, và địa chỉ của các opcode này có thể khác nhau tùy theo các ứng dụng và các phiên bản của hệ điều hành. Dự án Metasploit là một trong các cơ sở dữ liệu chứa các opcode thích hợp, tuy rằng trong đó chỉ liệt kê các opcode trong hệ điều hành Microsoft Windows [2]

[sửa] Khai thác lỗi tràn bộ đệm trên heap

Bài chi tiết: Tràn heap

Một hiện tượng tràn bộ đệm xảy ra trong khu vực dữ liệu heap được gọi là một hiện tượng tràn heap và có thể khai thác được bằng các kỹ thuật khác với các lỗi tràn stack. Bộ nhớ heap được cấp phát động bởi các ứng dụng tại thời gian chạy và thường chứa dữ liệu của chương trình. Việc khai thác được thực hiện bằng cách phá dữ liệu này theo các cách đặc biệt để làm cho ứng dụng ghi đè lên các cấu trúc dữ liệu nội bộ chẳng hạn các con trỏ của danh sách liên kết. Lỗ hổng của Microsoft JPEG GDI+ là một ví dụ gần đây về sự nguy hiểm mà một lỗi tràn heap có thể dẫn đến. [3]

[sửa] Cản trở đối với các thủ thuật khai thác

Việc xử lý bộ đệm trước khi đọc hay thực thi nó có thể làm thất bại các cố gắng khai thác lỗi tràn bộ đệm. Các xử lý này có thể giảm bớt mối đe dọa của việc khai thác lỗi, nhưng có thể không ngăn chặn được một cách tuyệt đối. Việc xử lý có thể bao gồm: chuyển từ chữ hoa thành chữ thường, loại bỏ các ký tự đặc biệt (metacharacters) và lọc các xâu không chứa ký tự là chữ số hoặc chữ cái. Tuy nhiên, có các kỹ thuật để tránh việc lọc và xử lý này; alphanumeric code (mã gồm toàn chữ và số), polymorphic code (mã đa hình), Self-modifying code (mã tự sửa đổi) và tấn công kiểu return-to-libc. Cũng chính các phương pháp này có thể được dùng để tránh bị phát hiện bởi các hệ thống phát hiện thâm nhập (Intrusion detection system).

[sửa] Chống tràn bộ đệm

Nhiều kỹ thuật đa dạng với nhiều ưu nhược điểm đã được sử dụng để phát hiện hoặc ngăn chặn hiện tượng tràn bộ đệm. Cách đáng tin cậy nhất để tránh hoặc ngăn chặn tràn bộ đệm là sử dụng bảo vệ tự động tại mức ngôn ngữ lập trình. Tuy nhiên, loại bảo vệ này không thể áp dụng cho mã thừa kế (legacy code), và nhiều khi các ràng buộc kỹ thuật, kinh doanh hay văn hóa lại đòi hỏi sử dụng một ngôn ngữ không an toàn. Các mục sau đây mô tả các lựa chọn và cài đặt hiện có.

[sửa] Lựa chọn ngôn ngữ lập trình

Page 24: biểu diễn nhị phân

Lựa chọn về ngôn ngữ lập trình có thể có một ảnh hưởng lớn đối với sự xuất hiện của lỗi tràn bộ đệm. Năm 2006, C và C++ nằm trong số các ngôn ngữ lập trình thông dụng nhất, với một lượng khổng lồ các phần mềm đã được viết bằng hai ngôn ngữ này. C và C++ không cung cấp sẵn các cơ chế chống lại việc truy nhập hoặc ghi đè dữ liệu lên bất cứ phần nào của bộ nhớ thông qua các con trỏ bất hợp lệ; cụ thể, hai ngôn ngữ này không kiểm tra xem dữ liệu được ghi vào một mảng (cài đặt của một bộ nhớ đệm) có nằm trong biên của mảng đó hay không. Tuy nhiên, cần lưu ý rằng các thư viện chuẩn của C++, thư viện khuôn mẫu chuẩn - STL, cung cấp nhiều cách an toàn để lưu trữ dữ liệu trong bộ đệm, và các lập trình viên C cũng có thể tạo và sử dụng các tiện ích tương tự. Cũng như đối với các tính năng bất kỳ khác của C hay C++, mỗi lập trình viên phải tự xác định lựa chọn xem họ có muốn chấp nhận các hạn chế về tốc độ chương trình để thu lại các lợi ích tiềm năng (độ an toàn của chương trình) hay không.

Một số biến thể của C, chẳng hạn Cyclone, giúp ngăn chặn hơn nữa các lỗi tràn bộ đệm bằng việc chẳng hạn như gắn thông tin về kích thước mảng với các mảng. Ngôn ngữ lập trình D sử dụng nhiều kỹ thuật đa dạng để tránh gần hết việc sử dụng con trỏ và kiểm tra biên do người dùng xác định.

Nhiều ngôn ngữ lập trình khác cung cấp việc kiểm tra tại thời gian chạy, việc kiểm tra này gửi một cảnh báo hoặc ngoại lệ khi C hoặc C++ ghi đè dữ liệu. Ví dụ về các ngôn ngữ này rất đa dạng, từ Python tới Ada, từ Lisp tới Modula-2, và từ Smalltalk tới OCaml. Các môi trường bytecode của Java và .NET cũng đòi hỏi kiểm tra biên đối với tất cả các mảng. Gần như tất cả các ngôn ngữ thông dịch sẽ bảo vệ chương trình trước các hiện tượng tràn bộ đệm bằng cách thông báo một trạng thái lỗi định rõ (well-defined error). Thông thường, khi một ngôn ngữ cung cấp đủ thông tin về kiểu để thực hiện kiểm tra biên, ngôn ngữ đó thường cho phép lựa chọn kích hoạt hay tắt chế độ đó. Việc phân tích tĩnh (static analysis) có thể loại được nhiều kiểm tra kiểu và biên động, nhưng các cài đặt tồi và các trường hợp rối rắm có thể giảm đáng kể hiệu năng. Các kỹ sư phần mềm phải cẩn thận cân nhắc giữa các phí tổn cho an toàn và hiệu năng khi quyết định sẽ sử dụng ngôn ngữ nào và cấu hình như thế nào cho trình biên dịch.

[sửa] Sử dụng các thư viện an toàn

Vấn đề tràn bộ đệm thường gặp trong C và C++ vì các ngôn ngữ này để lộ các chi tiết biểu diễn mức thấp của các bộ nhớ đệm với vai trò các chỗ chứa cho các kiểu dữ liệu. Do đó, phải tránh tràn bộ đệm bằng cách gìn giữ tính đúng đắn cao cho các phần mã chương trình thực hiện việc quản lý bộ đệm. Việc sử dụng các thư viện được viết tốt và đã được kiểm thử, dành cho các kiểu dữ liệu trừu tượng mà các thư viện này thực hiện tự động việc quản lý bộ nhớ, trong đó có kiểm tra biên, có thể làm giảm sự xuất hiện và ảnh hưởng của các hiện tượng tràn bộ đệm. Trong các ngôn ngữ này, xâu ký tự và mảng là hai kiểu dữ liệu chính mà tại đó các hiện tượng tràn bộ đệm thường xảy ra; do đó, các thư viện ngăn chặn lỗi tràn bộ đệm tại các kiểu dữ liệu này có thể cung cấp phần chính của sự che chắn cần thiết. Dù vậy, việc sử dụng các thư viện an toàn một cách không đúng có thể dẫn đến tràn bộ đệm và một số lỗ hổng khác; và tất nhiên, một lỗi bất kỳ trong chính thư viện chính nó cũng là một lỗ hổng. Các cài đặt thư viện "an toàn" gồm The Better String Library, Arri Buffer API và Vstr. Thư viện C của hệ điều hành OpenBSD cung cấp

Page 25: biểu diễn nhị phân

các hàm hữu ích strlcpy strlcat, nhưng các hàm này nhiều hạn chế hơn nhiều so với các cài đặt thư viện an toàn đầy đủ.

Tháng 9 năm 2006, Báo cáo kỹ thuật số 24731 của hội đồng tiêu chuẩn C đã được công bố; báo cáo này mô tả một tập các hàm mới dựa trên các hàm vào ra dữ liệu và các hàm xử lý xâu ký tự của thư viện C chuẩn, các hàm mới này được bổ sung các tham số về kích thước bộ đệm.

[sửa] Chống tràn bộ nhớ đệm trên stack

Bài chi tiết: Stack-smashing protection

Stack-smashing protection là kỹ thuật được dùng để phát hiện các hiện tượng tràn bộ đệm phổ biến nhất. Kỹ thuật này kiểm tra xem stack đã bị sửa đổi hay chưa khi một hàm trả về. Nếu stack đã bị sửa đổ, chương trình kết thúc bằng một lỗi segmentation fault. Các hệ thống sử dụng kỹ thuật này gồm có Libsafe[4], StackGuard [5] và các bản vá lỗi (patch) ProPolice [6] gcc.

Chế độ Data Execution Prevention (cấm thực thi dữ liệu) của Microsoft bảo vệ thẳng các con trỏ tới SEH Exception Handler, không cho chúng bị ghi đè. [7]

Có thể bảo vệ stack hơn nữa bằng cách phân tách stack thành hai phần, một phần dành cho dữ liệu và một phần cho các bước trả về của hàm. Sự phân chia này được dùng trong ngôn ngữ lập trình Forth, tuy nó không phải một quyết định thiết kế dựa theo tiêu chí an toàn. Nhưng dù sao thì đây cũng không phải một giải pháp hoàn chỉnh đối với vấn đề tràn bộ đệm, khi các dữ liệu nhạy cảm không phải địa chỉ trả về vẫn có thể bị ghi đè.

Hiện tượng tràn bộ đệm xảy ra khi ta copy một xâu (char[]) dài vào một xâu được khai báo với kích thước nhỏ, trong chương trình C/C++, và sử dụng các hàm copy xâu không kiểm tra biên như strcpy(), gets()... Khi đó toàn bộ xâu nguồn với kích thước lớn sẽ được ghi đè lên xâu đích kích thước nhỏ, tràn qua phần bộ nhớ được cấp phát cho xâu đích và ghi đè lên các giá trị khác đang được lưu trong stack như biến nội bộ, địa chỉ trả về của chương trình hiện tại (Return address)...

Ví dụ về một chương trình có thể bị tràn bộ đệm:

... void f(char * source) { int i = 1; char buffer[16]; strcpy(buffer, source); } ... f(a_very_big_string); ...

Tuỳ theo độ dài và nội dung của xâu nguồn mà hậu quả của việc tràn bộ đệm buffer[16] ở trên có thể là:

-biến i bị thay đổi giá trị.

Page 26: biểu diễn nhị phân

-return address cua f() bi thay đổi giá trị, do dó, khi f chạy xong, thay vì quay về chương trình đã gọi f(), chương trình lại nhảy đến địa chỉ là giá trị mới của return address, coi đó là mã chương trình và chạy. Kết quả là chương trình bị crash hoặc có những biểu hiện không bình thường.

Thông thường, hacker đưa một đoạn mã máy (gọi là shellcode) vào đầu xâu a_very_big_string, cuối xâu là địa chỉ của xâu đó trong bộ nhớ chương trình. Mục đích là để khi hiện tượng tràn bộ đệm xảy ra, return address của f() sẽ bị ghi đè bởi địa chỉ của shellcode. Kết quả là sau khi f() kết thúc, shellcode sẽ được chạy. Shellcode chính là đoạn mã máy chứa các lệnh phá hoại, hoặc lệnh tạo ra một command shell trong Unix/Linux.