Hàm memcpy là một công cụ mạnh mẽ trong C để sao chép các khối bộ nhớ. Tuy nhiên, việc sử dụng không đúng cách có thể dẫn đến các lỗ hổng bảo mật nghiêm trọng. Bài viết này sẽ hướng dẫn bạn qua cú pháp, các trường hợp sử dụng phổ biến, các lựa chọn thay thế và các biện pháp bảo mật để sử dụng memcpy một cách an toàn và hiệu quả. Ngoài ra, chúng ta sẽ tìm hiểu về cách các giải pháp bảo mật như Sternum EIV™ có thể giúp bảo vệ hệ thống IoT khỏi các rủi ro liên quan đến memcpy.
memcpy() là một hàm tiêu chuẩn trong ngôn ngữ lập trình C, được sử dụng để sao chép các khối bộ nhớ từ vị trí này sang vị trí khác. Nguyên mẫu của nó được định nghĩa trong tệp tiêu đề string.h
như sau:
void *memcpy(void *dest, const void *src, size_t n);
Hàm memcpy() sao chép nội dung của một bộ đệm nguồn (source buffer) sang một bộ đệm đích (destination buffer), bắt đầu từ vị trí bộ nhớ được trỏ bởi src
và tiếp tục trong n
byte. Các vùng nhớ không được phép chồng lấn lên nhau. Nếu có sự chồng lấn, hành vi của chương trình là không xác định.
Hàm này thường được sử dụng trong lập trình C cho các tác vụ như sao chép nội dung của một mảng sang một mảng khác, hoặc để sao chép các cấu trúc hoặc các kiểu dữ liệu khác mà không được xử lý bằng phép gán đơn giản. Sử dụng memcpy() thường được ưu tiên hơn so với cách tiếp cận dựa trên vòng lặp để sao chép bộ nhớ, vì nó thường nhanh hơn và hiệu quả hơn.
Tính linh hoạt của hàm memcpy() làm cho nó trở thành một lựa chọn phù hợp cho nhiều tác vụ lập trình liên quan đến chuỗi, mảng và con trỏ trong ngôn ngữ lập trình C, ví dụ:
dest
hoặc để nối hai chuỗi với nhau vào một bộ đệm mới.Hàm memcpy() có ba tham số:
dest
: một con trỏ đến vị trí bộ đệm đích nơi dữ liệu sẽ được sao chép.src
: một con trỏ đến bộ đệm nguồn từ nơi dữ liệu sẽ được sao chép.n
: số byte được sao chép.Dưới đây là một ví dụ đơn giản về cách tất cả kết hợp với nhau. Mã dưới đây sao chép nội dung của src
(bộ đệm nguồn) sang dest
(bộ đệm đích). Lưu ý rằng mã này sử dụng toán tử sizeof
thay vì n
(số byte cần sao chép), cho phép chương trình sao chép tất cả các byte từ bộ đệm nguồn.
#include
#include
int main() {
// Bộ đệm nguồn
char src[20] = "Hello";
// Bộ đệm đích
char dst[20] = {1};
// Sao chép bộ đệm nguồn vào dst
memcpy(dst, src, sizeof(src));
// In bộ đệm đích
printf("dst = %s\n", dst);
return 0;
}
Kết quả đầu ra trông như thế này:
dst = Hello
Tất nhiên, memcpy() không phải là cách duy nhất để di chuyển các khối bộ nhớ xung quanh trong C. Dưới đây là một bản tóm tắt nhanh về một số lựa chọn thay thế:
memcpy() và memmove() đều là các hàm thư viện chuẩn của ngôn ngữ C được sử dụng để sao chép các khối bộ nhớ giữa các vị trí khác nhau. Sự khác biệt chính giữa memcpy() và memmove() là cách chúng xử lý các vùng nhớ chồng lấn. Khi sao chép bộ nhớ, memcpy giả định rằng các vùng nhớ nguồn và đích không chồng lấn và sẽ tạo ra hành vi không xác định nếu chúng chồng lấn. Ngược lại, memmove() được thiết kế để xử lý các vùng nhớ chồng lấn và sẽ sao chép bộ nhớ một cách chính xác bất kể các vùng có chồng lấn hay không.
Trong ví dụ dưới đây, hàm memcpy() cho phép sao chép chuỗi con "world!" từ mảng str
sang mảng buffer
. Tuy nhiên, vì các vùng nguồn và đích chồng lấn, hành vi của hàm không được xác định và chương trình có thể tạo ra các kết quả không mong muốn.
Hàm memmove() thực hiện chính xác điều tương tự, sao chép chuỗi con sang mảng buffer, nhưng nó sẽ xử lý chính xác các vùng chồng lấn.
#include
#include
int main() {
char str[] = "Hello, world!";
char buffer[20];
// Sử dụng memcpy để sao chép chuỗi sang bộ đệm
memcpy(buffer, str + 7, 6);
printf("Sử dụng memcpy: %s\n", buffer);
// Sử dụng memmove để sao chép chuỗi sang bộ đệm
memmove(buffer, str + 7, 6);
printf("Sử dụng memmove: %s\n", buffer);
return 0;
}
strcpy() là một hàm thư viện chuẩn khác của ngôn ngữ C và (bạn đoán đúng) nó cũng được sử dụng để sao chép bộ nhớ giữa các vị trí. Sự khác biệt chính giữa nó và memcpy() là loại dữ liệu mà chúng được thiết kế để xử lý. Cụ thể, memcpy() là một hàm cấp thấp có thể sao chép bất kỳ loại dữ liệu nào, bao gồm các kiểu dữ liệu không phải ký tự như số nguyên, số dấu phẩy động và cấu trúc. Ngược lại, strcpy() được thiết kế đặc biệt để sao chép các chuỗi ký tự kết thúc bằng null và sẽ tạo ra hành vi không xác định nếu được sử dụng với dữ liệu không phải chuỗi.
Một sự khác biệt quan trọng khác giữa memcpy() và strcpy() là cách chúng xử lý ký tự kết thúc null. memcpy() không thêm ký tự kết thúc null vào dữ liệu được sao chép, vì vậy lập trình viên phải đảm bảo rằng bộ đệm đích được kết thúc đúng cách. Ngược lại, strcpy() tự động thêm ký tự kết thúc null vào chuỗi được sao chép, vì vậy lập trình viên không cần phải thêm một cách rõ ràng.
Ví dụ đơn giản dưới đây minh họa sự khác biệt giữa hai hàm. Trong ví dụ này, memcpy được sử dụng để sao chép nội dung của mảng nguồn sang mảng buffer. Vì nó không thêm ký tự kết thúc null, lập trình viên phải thêm một cách thủ công để đảm bảo rằng chuỗi được sao chép được kết thúc đúng cách. Ngược lại, hàm strcpy được sử dụng để sao chép cùng một chuỗi sang mảng buffer và tự động thêm ký tự kết thúc null vào cuối chuỗi được sao chép.
#include
#include
int main() {
char source[] = "Hello, world!";
char buffer[20];
// Sử dụng memcpy để sao chép chuỗi sang bộ đệm
memcpy(buffer, source, strlen(source));
buffer[strlen(source)] = '\0';
printf("Sử dụng memcpy: %s\n", buffer);
// Sử dụng strcpy để sao chép chuỗi sang bộ đệm
strcpy(buffer, source);
printf("Sử dụng strcpy: %s\n", buffer);
return 0;
}
memcpy() có thể được sử dụng một cách an toàn nếu được sử dụng cẩn thận. Tuy nhiên, vì nó là một hàm cấp thấp cho phép truy cập bộ nhớ trực tiếp, nó có thể nguy hiểm nếu được sử dụng không chính xác.
Có hai mối quan tâm an ninh chính với memcpy():
Để tránh những lo ngại về an ninh này, điều quan trọng là phải tuân theo những biện pháp tốt nhất này khi sử dụng memcpy():
Hàm memcpy() là một thủ phạm phổ biến đằng sau nhiều lỗ hổng trong các thiết bị IoT/nhúng.
Công nghệ EIV™ (xác minh tính toàn vẹn nhúng) được cấp bằng sáng chế của Sternum bảo vệ khỏi những điều này với bảo vệ thời gian chạy (tương tự RASP) mà quyết định ngăn chặn tất cả các nỗ lực thao tác bộ nhớ và mã, cung cấp bảo vệ toàn diện khỏi một loạt các điểm yếu phần mềm (CWE), bao gồm cả những điểm yếu liên quan đến việc sử dụng không đúng cách memcpy() và các hàm có thể khai thác khác.
Nhúng chính nó trực tiếp vào mã firmware, EIV™ không có tác nhân và không phụ thuộc vào kết nối. Hoạt động ở cấp bytecode, nó cũng tương thích phổ quát với bất kỳ thiết bị IoT hoặc hệ điều hành nào (RTOS, Linux, OpenWrt, Zephyr, Micirum, FreeRTOS, v.v.) và có chi phí thấp chỉ 1-3%, ngay cả trên các thiết bị cũ.
Các tính năng bảo vệ thời gian chạy cũng được tăng cường bởi các khả năng phát hiện mối đe dọa (tương tự XDR) của nền tảng Cloud của Sternum, khả năng phát hiện dị thường do AI cung cấp và khả năng giám sát mở rộng.
Để tìm hiểu thêm, hãy xem các nghiên cứu điển hình về cách công nghệ này đã được sử dụng để:
Ngoài ra, hãy xem video bên dưới để xem Sternum EIV™ hoạt động, vì nó cung cấp khả năng giảm thiểu ngay lập tức phần mềm độc hại Ripple20, được sử dụng cho các cuộc tấn công làm hỏng bộ nhớ.
Bắt đầu bảo vệ hệ thống của bạn ngay hôm nay. Nhận bản đánh giá miễn phí và khám phá cách Sternum EIV™ có thể bảo vệ các thiết bị IoT của bạn khỏi các mối đe dọa bộ nhớ và mã.
Bài viết liên quan