Rất nhiều API Win32 đưa con trỏ đến cấu trúc với bố cục cụ thể. Trong số này, một tập hợp con lớn theo một mẫu chung, nơi DWORD đầu tiên phải được khởi tạo để có kích thước của cấu trúc trước khi nó được gọi. Đôi khi chúng yêu cầu một khối bộ nhớ được truyền, trong đó chúng sẽ viết cấu trúc và khối bộ nhớ phải có kích thước được xác định bằng cách gọi cùng một API với con trỏ NULL và đọc giá trị trả về để khám phá đúng kích thước. Một số API phân bổ một cấu trúc và trả về một con trỏ đến nó, sao cho con trỏ phải được deallocated với một cuộc gọi thứ hai.
Tôi sẽ không ngạc nhiên nếu tập hợp các API có thể được gọi một cách hữu ích trong một lần chụp, với các đối số riêng lẻ có thể chuyển đổi từ một biểu diễn chuỗi đơn giản, là khá nhỏ.
Để thực hiện ý tưởng này áp dụng chung, chúng ta sẽ phải đi đến khá cực đoan:
typedef void DynamicFunction(size_t argumentCount, const wchar_t *arguments[],
size_t maxReturnValueSize, wchar_t *returnValue);
DynamicFunction *GenerateDynamicFunction(const wchar_t *code);
Bạn sẽ vượt qua một đoạn mã đơn giản để GenerateDynamicFunction, và nó sẽ quấn mã mà trong một số soạn sẵn tiêu chuẩn và sau đó gọi trình biên dịch C/trình liên kết để tạo một DLL từ nó (có sẵn một vài tùy chọn miễn phí), chứa hàm. Sau đó, nó sẽ là LoadLibrary
mà DLL và sử dụng GetProcAddress
để tìm chức năng và sau đó trả lại. Điều này sẽ rất tốn kém, nhưng bạn sẽ làm điều đó một lần và lưu trữ kết quả là DynamicFunctionPtr để sử dụng lặp lại. Bạn có thể làm điều này một cách năng động bằng cách giữ con trỏ trong một hashtable, được khóa bởi chính các đoạn mã.
Các soạn sẵn có thể là:
#include <windows.h>
// and anything else that might be handy
void DynamicFunctionWrapper(size_t argumentCount, const wchar_t *arguments[],
size_t maxReturnValueSize, wchar_t *returnValue)
{
// --- insert code snipped here
}
Vì vậy, một cách sử dụng ví dụ về hệ thống này sẽ là:
DynamicFunction *getUserName = GenerateDynamicFunction(
"GetUserNameW(returnValue, (LPDWORD)(&maxReturnValueSize))");
wchar_t userName[100];
getUserName(0, NULL, sizeof(userName)/sizeof(wchar_t), userName);
Bạn có thể tăng cường này bằng cách làm GenerateDynamicFunction
chấp nhận tính tranh luận, vì vậy nó có thể tạo ra một kiểm tra lúc bắt đầu trình bao bọc mà số lượng đối số chính xác đã được thông qua. Và nếu bạn đặt một hashtable trong đó để cache các chức năng cho mỗi mã đã gặp phải, bạn có thể nhận được gần với ví dụ ban đầu của bạn. Hàm Call sẽ lấy một đoạn mã thay vì chỉ là một tên API, nhưng nếu không sẽ giống nhau. Nó sẽ tra cứu đoạn mã trong Hashtable, và nếu không có, nó sẽ gọi GenerateDynamicFunction và lưu trữ kết quả trong Hashtable cho lần sau. Sau đó nó sẽ thực hiện cuộc gọi trên hàm. Ví dụ về cách sử dụng:
wchar_t userName[100];
Call("GetUserNameW(returnValue, (LPDWORD)(&maxReturnValueSize))",
0, NULL, sizeof(userName)/sizeof(wchar_t), userName);
Tất nhiên sẽ không có nhiều điểm để thực hiện bất kỳ điều này trừ khi ý tưởng là mở một số lỗ hổng bảo mật chung. ví dụ. để hiển thị Call
dưới dạng dịch vụ web. Ý nghĩa bảo mật tồn tại cho ý tưởng ban đầu của bạn, nhưng ít rõ ràng hơn đơn giản vì phương pháp tiếp cận ban đầu mà bạn đề xuất sẽ không hiệu quả. Chúng ta càng làm cho nó càng mạnh, thì vấn đề bảo mật càng cao.
Cập nhật dựa trên nhận xét:
.NET Framework có một tính năng gọi là p/gọi, mà tồn tại một cách chính xác để giải quyết vấn đề của bạn. Vì vậy, nếu bạn đang làm điều này như là một dự án để tìm hiểu về công cụ, bạn có thể xem p/gọi để có được một ý tưởng về nó phức tạp như thế nào. Bạn có thể nhắm mục tiêu khuôn khổ .NET với ngôn ngữ kịch bản lệnh của bạn - thay vì diễn giải các kịch bản trong thời gian thực, hoặc biên dịch chúng thành bytecode của riêng bạn, bạn có thể biên dịch chúng thành IL. Hoặc bạn có thể lưu trữ một ngôn ngữ kịch bản hiện có từ nhiều ngôn ngữ đã có sẵn trên .NET.
Tôi đang sử dụng char * ở đây dưới dạng một mảng byte thay vì chuỗi. Giả định là tôi tạo siêu dữ liệu có sẵn cho hàm 'Gọi' mô tả kích thước của các loại thực tế (nghĩa là số byte) được yêu cầu. Theo nghĩa đó, các kiểu * được biết - chúng là số byte đã biết. –
Nói chung, bạn cần nhiều hơn số byte để thực hiện cuộc gọi, vì quy ước truyền có thể khác nhau đối với các loại dữ liệu khác nhau (đặc biệt đối với các đối số dấu phẩy động). Nhưng tôi bạn có thể đúng rằng win32 __stdcall chỉ cần đặt tất cả mọi thứ trên ngăn xếp, tôi không biết rằng một trong những chi tiết. – puetzk